diff --git a/.editorconfig b/.editorconfig index b32034ea7683..98730156867c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -18,11 +18,11 @@ indent_style = space indent_size = 2 # XML config files -[*.{config,nuspec,resx}] +[*.{config,nuspec,resx,natvis}] indent_style = space indent_size = 2 # Python files [*.py] indent_style = space -indent_size = 4 \ No newline at end of file +indent_size = 4 diff --git a/Engine/Binaries/ThirdParty/Mono/Linux/bin/mcs b/Engine/Binaries/ThirdParty/Mono/Linux/bin/mcs index e4dc52745b78..b5f4cc8ad9a3 100755 --- a/Engine/Binaries/ThirdParty/Mono/Linux/bin/mcs +++ b/Engine/Binaries/ThirdParty/Mono/Linux/bin/mcs @@ -1,4 +1,4 @@ #!/bin/sh -export PATH=$PATH:$UE_MONO_DIR/bin -export PKG_CONFIG_PATH=$UE_MONO_DIR/lib/pkgconfig:$PKG_CONFIG_PATH -exec $UE_MONO_DIR/bin/mono $MONO_OPTIONS $UE_MONO_DIR/lib/mono/4.5/mcs.exe "$@" +export PATH="$PATH:$UE_MONO_DIR/bin" +export PKG_CONFIG_PATH="$UE_MONO_DIR/lib/pkgconfig:$PKG_CONFIG_PATH" +exec "$UE_MONO_DIR/bin/mono" $MONO_OPTIONS "$UE_MONO_DIR/lib/mono/4.5/mcs.exe" "$@" diff --git a/Engine/Build/BatchFiles/Linux/GenerateProjectFiles.sh b/Engine/Build/BatchFiles/Linux/GenerateProjectFiles.sh index 6ffb5887bcf9..c9c76d48ce62 100755 --- a/Engine/Build/BatchFiles/Linux/GenerateProjectFiles.sh +++ b/Engine/Build/BatchFiles/Linux/GenerateProjectFiles.sh @@ -20,7 +20,7 @@ if [ ! -d "$BASE_PATH/../../../Source" ]; then exit 1 fi -source "$BASE_PATH/SetupMono.sh" $BASE_PATH +source "$BASE_PATH/SetupMono.sh" "$BASE_PATH" # make sure the UBT project has references to auto-discovered platform extension source files "${BASE_PATH}/../FindPlatformExtensionSources.sh" diff --git a/Engine/Build/BatchFiles/Linux/GitDependencies.sh b/Engine/Build/BatchFiles/Linux/GitDependencies.sh index f7ef7053ef5e..2316f948a868 100755 --- a/Engine/Build/BatchFiles/Linux/GitDependencies.sh +++ b/Engine/Build/BatchFiles/Linux/GitDependencies.sh @@ -19,7 +19,7 @@ BASE_PATH="`dirname "$SCRIPT_PATH"`" cd ../../../.. RESULT=0 -source "$BASE_PATH/SetupMono.sh" $BASE_PATH +source "$BASE_PATH/SetupMono.sh" "$BASE_PATH" while : ; do mono Engine/Binaries/DotNET/GitDependencies.exe $ARGS diff --git a/Engine/Build/Commit.gitdeps.xml b/Engine/Build/Commit.gitdeps.xml index bcfe4fb784e0..9dde7fceebca 100644 --- a/Engine/Build/Commit.gitdeps.xml +++ b/Engine/Build/Commit.gitdeps.xml @@ -5131,17 +5131,11 @@ - - - - - - - - - - - + + + + + @@ -7620,6 +7614,7 @@ + @@ -7627,6 +7622,7 @@ + @@ -7818,6 +7814,7 @@ + @@ -7838,6 +7835,10 @@ + + + + @@ -7879,6 +7880,8 @@ + + @@ -28667,7 +28670,7 @@ - + @@ -28985,6 +28988,7 @@ + @@ -30330,7 +30334,7 @@ - + @@ -31060,7 +31064,8 @@ - + + @@ -31077,6 +31082,7 @@ + @@ -31163,7 +31169,7 @@ - + @@ -31203,18 +31209,18 @@ - + - + - - + + @@ -31234,7 +31240,7 @@ - + @@ -33032,6 +33038,7 @@ + @@ -46421,21 +46428,20 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -48542,7 +48548,7 @@ - + @@ -48570,7 +48576,7 @@ - + @@ -48846,7 +48852,7 @@ - + @@ -49001,6 +49007,7 @@ + @@ -49192,7 +49199,6 @@ - @@ -49213,7 +49219,7 @@ - + @@ -49610,7 +49616,7 @@ - + @@ -49869,6 +49875,7 @@ + @@ -50745,7 +50752,6 @@ - @@ -50775,7 +50781,7 @@ - + @@ -50884,7 +50890,7 @@ - + @@ -50981,6 +50987,7 @@ + @@ -51112,7 +51119,6 @@ - @@ -51571,6 +51577,7 @@ + @@ -52133,7 +52140,7 @@ - + @@ -52194,7 +52201,7 @@ - + @@ -52564,7 +52571,7 @@ - + @@ -52669,7 +52676,7 @@ - + @@ -52969,7 +52976,6 @@ - @@ -53510,7 +53516,7 @@ - + @@ -53595,7 +53601,7 @@ - + @@ -53692,7 +53698,7 @@ - + @@ -53829,7 +53835,7 @@ - + @@ -54002,7 +54008,7 @@ - + @@ -54085,7 +54091,7 @@ - + @@ -54101,6 +54107,7 @@ + @@ -54436,7 +54443,6 @@ - @@ -54612,7 +54618,7 @@ - + @@ -54651,7 +54657,7 @@ - + @@ -54660,7 +54666,7 @@ - + @@ -54771,7 +54777,7 @@ - + @@ -54981,7 +54987,7 @@ - + @@ -55224,7 +55230,6 @@ - @@ -55375,7 +55380,7 @@ - + @@ -55571,6 +55576,7 @@ + @@ -55859,6 +55865,7 @@ + @@ -56096,7 +56103,7 @@ - + @@ -56980,7 +56987,7 @@ - + @@ -57406,7 +57413,7 @@ - + @@ -58185,7 +58192,7 @@ - + @@ -58351,7 +58358,7 @@ - + @@ -58481,7 +58488,7 @@ - + @@ -58542,6 +58549,7 @@ + @@ -58566,7 +58574,6 @@ - @@ -58775,7 +58782,6 @@ - @@ -58797,7 +58803,6 @@ - @@ -58834,7 +58839,7 @@ - + @@ -58993,7 +58998,7 @@ - + @@ -59058,7 +59063,7 @@ - + @@ -59482,7 +59487,7 @@ - + @@ -59570,7 +59575,7 @@ - + @@ -59872,6 +59877,7 @@ + @@ -60047,7 +60053,7 @@ - + @@ -60642,6 +60648,7 @@ + @@ -60663,7 +60670,7 @@ - + @@ -60677,7 +60684,7 @@ - + @@ -61174,7 +61181,7 @@ - + @@ -61969,6 +61976,7 @@ + @@ -62010,7 +62018,7 @@ - + @@ -62085,7 +62093,6 @@ - @@ -62493,7 +62500,7 @@ - + @@ -62578,7 +62585,7 @@ - + @@ -62814,6 +62821,7 @@ + @@ -62922,7 +62930,7 @@ - + @@ -63092,6 +63100,7 @@ + @@ -63218,7 +63227,7 @@ - + @@ -63705,11 +63714,11 @@ - + - + @@ -63864,7 +63873,7 @@ - + @@ -63915,7 +63924,7 @@ - + @@ -64150,7 +64159,6 @@ - @@ -64391,11 +64399,12 @@ - + + @@ -64495,7 +64504,6 @@ - @@ -64572,7 +64580,6 @@ - @@ -64665,7 +64672,6 @@ - @@ -64918,7 +64924,7 @@ - + @@ -64955,7 +64961,7 @@ - + @@ -65145,7 +65151,7 @@ - + @@ -65332,7 +65338,7 @@ - + @@ -65671,6 +65677,7 @@ + @@ -65738,7 +65745,7 @@ - + @@ -65871,7 +65878,6 @@ - @@ -66024,7 +66030,7 @@ - + @@ -66315,7 +66321,7 @@ - + @@ -66545,14 +66551,15 @@ - - + + + @@ -66595,7 +66602,7 @@ - + @@ -66922,7 +66929,7 @@ - + @@ -67119,7 +67126,7 @@ - + @@ -67133,10 +67140,10 @@ - + - + @@ -67180,7 +67187,7 @@ - + @@ -67245,7 +67252,7 @@ - + @@ -67367,6 +67374,7 @@ + @@ -67403,6 +67411,7 @@ + @@ -67507,6 +67516,7 @@ + @@ -67679,11 +67689,12 @@ + - + @@ -67692,7 +67703,7 @@ - + @@ -67866,7 +67877,6 @@ - @@ -67959,7 +67969,7 @@ - + @@ -68058,7 +68068,7 @@ - + @@ -68102,7 +68112,7 @@ - + @@ -68450,7 +68460,7 @@ - + @@ -68585,7 +68595,6 @@ - @@ -68610,7 +68619,6 @@ - @@ -68912,7 +68920,7 @@ - + @@ -69487,7 +69495,7 @@ - + @@ -69592,7 +69600,7 @@ - + @@ -69615,7 +69623,7 @@ - + @@ -69823,7 +69831,7 @@ - + @@ -70102,7 +70110,7 @@ - + @@ -70290,7 +70298,7 @@ - + @@ -70734,7 +70742,7 @@ - + @@ -71015,6 +71023,7 @@ + @@ -71337,7 +71346,7 @@ - + @@ -71570,6 +71579,7 @@ + @@ -71646,7 +71656,7 @@ - + @@ -71952,6 +71962,7 @@ + @@ -71972,7 +71983,7 @@ - + @@ -72175,7 +72186,7 @@ - + @@ -72193,6 +72204,7 @@ + @@ -72407,6 +72419,7 @@ + @@ -72621,6 +72634,7 @@ + @@ -72685,7 +72699,7 @@ - + @@ -72977,7 +72991,7 @@ - + @@ -73138,7 +73152,7 @@ - + @@ -73383,7 +73397,7 @@ - + @@ -73456,7 +73470,7 @@ - + @@ -73725,8 +73739,6 @@ - - @@ -74203,7 +74215,6 @@ - @@ -74717,7 +74728,6 @@ - @@ -74734,7 +74744,6 @@ - @@ -74836,7 +74845,7 @@ - + @@ -75074,8 +75083,7 @@ - - + @@ -75260,7 +75268,7 @@ - + @@ -75360,6 +75368,7 @@ + @@ -76145,7 +76154,6 @@ - @@ -76643,7 +76651,7 @@ - + @@ -76820,7 +76828,7 @@ - + @@ -77010,7 +77018,7 @@ - + @@ -77291,7 +77299,7 @@ - + @@ -77909,7 +77917,7 @@ - + @@ -77973,7 +77981,6 @@ - @@ -78009,7 +78016,7 @@ - + @@ -78326,7 +78333,6 @@ - @@ -78381,7 +78387,7 @@ - + @@ -78407,7 +78413,7 @@ - + @@ -78465,6 +78471,7 @@ + @@ -78619,6 +78626,7 @@ + @@ -78821,6 +78829,7 @@ + @@ -78929,7 +78938,7 @@ - + @@ -79727,6 +79736,7 @@ + @@ -79936,7 +79946,7 @@ - + @@ -80271,6 +80281,7 @@ + @@ -80451,7 +80462,7 @@ - + @@ -80729,7 +80740,7 @@ - + @@ -80890,7 +80901,7 @@ - + @@ -81009,7 +81020,7 @@ - + @@ -81074,7 +81085,7 @@ - + @@ -81285,7 +81296,6 @@ - @@ -81575,6 +81585,7 @@ + @@ -82045,7 +82056,6 @@ - @@ -82251,6 +82261,7 @@ + @@ -82377,7 +82388,6 @@ - @@ -82533,6 +82543,7 @@ + @@ -82632,6 +82643,7 @@ + @@ -82700,7 +82712,6 @@ - @@ -82718,7 +82729,6 @@ - @@ -82779,6 +82789,7 @@ + @@ -83005,6 +83016,7 @@ + @@ -83104,7 +83116,6 @@ - @@ -83227,7 +83238,6 @@ - @@ -83254,6 +83264,7 @@ + @@ -83586,6 +83597,7 @@ + @@ -83668,6 +83680,7 @@ + @@ -84110,7 +84123,6 @@ - @@ -84252,7 +84264,6 @@ - @@ -84297,7 +84308,6 @@ - @@ -84320,6 +84330,7 @@ + @@ -84419,7 +84430,6 @@ - @@ -84427,6 +84437,7 @@ + @@ -84633,7 +84644,6 @@ - @@ -84761,6 +84771,7 @@ + @@ -84824,14 +84835,15 @@ - + + @@ -84854,7 +84866,6 @@ - diff --git a/Engine/Build/InstalledEngineBuild.xml b/Engine/Build/InstalledEngineBuild.xml index 08dd375ebef0..8dd9d769612a 100644 --- a/Engine/Build/InstalledEngineBuild.xml +++ b/Engine/Build/InstalledEngineBuild.xml @@ -189,7 +189,7 @@ - + @@ -441,6 +441,7 @@ + diff --git a/Engine/Config/BaseEditorPerProjectUserSettings.ini b/Engine/Config/BaseEditorPerProjectUserSettings.ini index 590231f879c1..7109711df79d 100644 --- a/Engine/Config/BaseEditorPerProjectUserSettings.ini +++ b/Engine/Config/BaseEditorPerProjectUserSettings.ini @@ -613,46 +613,90 @@ BackupIntervalInMinutes=5 [/Script/UnrealEd.FbxImportUI] -bImportMaterials=True -bAutoCreateGroups=True -bInvertNormalMaps=False -bImportTextures=True bOverrideFullName=True +bCreatePhysicsAsset=True +bAutoComputeLodDistances=True +LodDistance0=0.0 +LodDistance1=0.0 +LodDistance2=0.0 +LodDistance3=0.0 +LodDistance4=0.0 +LodDistance5=0.0 +LodDistance6=0.0 +LodDistance7=0.0 +MinimumLodNumber=0 +LodNumber=0 +bImportAnimations=False +bImportMaterials=True +bImportTextures=True + +[/Script/UnrealEd.FbxAssetImportData] +ImportTranslation=(X=0.0,Y=0.0,Z=0.0) +ImportRotation=(Pitch=0.0,Yaw=0.0,Roll=0.0) +ImportUniformScale=1.0 bConvertScene=True bForceFrontXAxis=False bConvertSceneUnit=False -bRemoveNamespaces=True -bImportAnimations=False -bResampleAnimations=False -bImportRigidMesh=False -bCombineMeshes=True -bCreatePhysicsAsset=True -bImportMesh=True -MinimumLodNumber=0 -LodNumber=0 -bAutoComputeLodDistances=True - +[/Script/UnrealEd.FbxMeshImportData] +bTransformVertexToAbsolute=True +bBakePivotInVertex=False +bReorderMaterialToFbxOrder=True +bImportMeshLODs=False +NormalImportMethod=FBXNIM_ComputeNormals +NormalGenerationMethod=EFBXNormalGenerationMethod::MikkTSpace [/Script/UnrealEd.FbxStaticMeshImportData] -bOneConvexHullPerUCX=True -bImportMeshLODs=False -NormalImportMethod=FBXNIM_ImportNormals +StaticMeshLODGroup="" VertexColorImportOption=EVertexColorImportOption::Ignore VertexOverrideColor=(R=255,G=255,B=255,A=255) - +bRemoveDegenerates=True +bBuildAdjacencyBuffer=True +bBuildReversedIndexBuffer=True +bGenerateLightmapUVs=True +bOneConvexHullPerUCX=True +bAutoGenerateCollision=True +bCombineMeshes=False +; Override of the base class default value +NormalImportMethod=FBXNIM_ImportNormals [/Script/UnrealEd.FbxSkeletalMeshImportData] +VertexColorImportOption=EVertexColorImportOption::Replace +VertexOverrideColor=(R=0,G=0,B=0,A=0) +bUpdateSkeletonReferencePose=False +bUseT0AsRefPose=False bPreserveSmoothingGroups=True -bImportMeshLODs=False +bImportMeshesInBoneHierarchy=True bImportMorphTargets=False ThresholdPosition=0.00002 ThresholdTangentNormal=0.00002 ThresholdUV=0.0009765625 +[/Script/UnrealEd.FbxAnimSequenceImportData] +bImportMeshesInBoneHierarchy=True +AnimationLength=FBXALIT_ExportedTime +FrameImportRange=(Min=0, Max=0) +bUseDefaultSampleRate=False +CustomSampleRate=0 +bImportCustomAttribute=True +bDeleteExistingCustomAttributeCurves=False +bImportBoneTracks=True +bSetMaterialDriveParameterOnCustomAttribute=False +bRemoveRedundantKeys=True +bDeleteExistingMorphTargetCurves=False +bDoNotImportCurveWithZero=True +bPreserveLocalTransform=False [/Script/UnrealEd.FbxTextureImportData] - +bInvertNormalMaps=False +MaterialSearchLocation=EMaterialSearchLocation::Local +BaseMaterialName="" +BaseColorName="" +BaseDiffuseTextureName="" +BaseNormalTextureName="" +BaseEmissiveColorName="" +BaseEmmisiveTextureName="" +BaseSpecularTextureName="" [SoundSettings] ChirpSoundClasses=Dialog DialogMedium DialogLoud DialogDeafening diff --git a/Engine/Config/BaseEditorSettings.ini b/Engine/Config/BaseEditorSettings.ini index c6f2c99615dd..ff56d1d1a4be 100644 --- a/Engine/Config/BaseEditorSettings.ini +++ b/Engine/Config/BaseEditorSettings.ini @@ -53,7 +53,7 @@ IncludeCollectionNames=True +Categories=(Identifier="Animation",Title=NSLOCTEXT("TutorialCategories","AnimationTitle","Animation"),Description=NSLOCTEXT("TutorialCategories","AnimationDescription","Tutorials covering the animation system in Unreal Engine 4."),Icon=,Texture=/Engine/Tutorial/SubEditors/TutorialAssets/icon_ShowSkeletalMeshes_40x.icon_ShowSkeletalMeshes_40x,SortOrder=600) +Categories=(Identifier="Landscape",Title=NSLOCTEXT("TutorialCategories","LandscapeTitle","Landscape"),Description=NSLOCTEXT("TutorialCategories","LandscapeDescription","Tutorials covering the Unreal Editor 4 terrain editor: Landscape."),Icon="LevelEditor.LandscapeMode",Texture=/Engine/Tutorial/Landscape/TutorialAssets/Landscape.Landscape,SortOrder=700) +Categories=(Identifier="Foliage",Title=NSLOCTEXT("TutorialCategories","FoliageTitle","Foliage"),Description=NSLOCTEXT("TutorialCategories","FoliageDescription","Tutorials covering the Unreal Engine 4 Foliage tool."),Icon="LevelEditor.FoliageMode",Texture=/Engine/Tutorial/Foliage/TutorialAssets/Foliage.Foliage,SortOrder=800) -+Categories=(Identifier="Mobile",Title=NSLOCTEXT("TutorialCategories","MobileTitle","Mobile"),Description=NSLOCTEXT("TutorialCategories","MobileDescription","Mobile Tutorials."),Icon="MaterialEditor.ToggleMobileStats",Texture=None,SortOrder=900) ++Categories=(Identifier="Mobile",Title=NSLOCTEXT("TutorialCategories","MobileTitle","Mobile"),Description=NSLOCTEXT("TutorialCategories","MobileDescription","Mobile Tutorials."),Icon="MaterialEditor.TogglePlatformStats",Texture=None,SortOrder=900) +StartupTutorial=/Engine/Tutorial/Basics/LevelEditorAttract.LevelEditorAttract_C +TutorialContexts=(Context="StaticMeshEditor",BrowserFilter=,AttractTutorial=None,LaunchTutorial=/Engine/Tutorial/SubEditors/StaticMeshEditorTutorial.StaticMeshEditorTutorial_C) +TutorialContexts=(Context="LevelEditor",BrowserFilter=,AttractTutorial=None,LaunchTutorial=/Engine/Tutorial/Basics/LevelEditorOverview.LevelEditorOverview_C) diff --git a/Engine/Config/BaseEngine.ini b/Engine/Config/BaseEngine.ini index deccb0b17d86..dbb51999bca6 100644 --- a/Engine/Config/BaseEngine.ini +++ b/Engine/Config/BaseEngine.ini @@ -218,8 +218,6 @@ NetClientTicksPerSecond=200 +StatColorMappings=(StatName="Streaming fudge factor",ColorMap=((In=0.0,Out=(G=255)),(In=1.0,Out=(G=255)),(In=2.5,Out=(R=255,G=255)),(In=5.0,Out=(R=255)),(In=10.0,Out=(R=255)))) DisplayGamma=2.2 MinDesiredFrameRate=35.000000 -InitialButtonRepeatDelay=0.2 -ButtonRepeatDelay=0.1 NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="/Script/OnlineSubsystemUtils.IpNetDriver",DriverClassNameFallback="/Script/OnlineSubsystemUtils.IpNetDriver") +NetDriverDefinitions=(DefName="DemoNetDriver",DriverClassName="/Script/Engine.DemoNetDriver",DriverClassNameFallback="/Script/Engine.DemoNetDriver") NetErrorLogInterval=1.0 @@ -661,12 +659,19 @@ bUseStreamingPause=false +FunctionRedirects=(OldName="EditorUtilityWidget.OnDefaultActionClicked",NewName="EditorUtilityWidget.Run") +FunctionRedirects=(OldName="SkeletalMeshComponent.GetSubInstanceByName",NewName="KismetSystemLibrary.GetSubInstanceByTag") +StructRedirects=(OldName="/Script/AnimGraphRuntime.AnimNode_Root",NewName="/Script/Engine.AnimNode_Root") ++FunctionRedirects=(OldName="Widget.SetRenderAngle", NewName="Widget.SetRenderTransformAngle") +ClassRedirects=(OldName="/Script/CoreUObject.MulticastDelegateProperty",NewName="/Script/CoreUObject.MulticastInlineDelegateProperty") +ClassRedirects=(OldName="EditorAutomationActor",NewName="EditorUtilityActor") +ClassRedirects=(OldName="EditorAutomationActorComponent",NewName="EditorUtilityActorComponent") +ClassRedirects=(OldName="EditorAutomationObject",NewName="EditorUtilityObject") ++PropertyRedirects=(OldName="StructVariableDescription.bDontEditoOnInstance",NewName="bDontEditOnInstance") ++PropertyRedirects=(OldName="KismetMathLibrary.DegAtan2.A",NewName="Y") ++PropertyRedirects=(OldName="KismetMathLibrary.DegAtan2.B",NewName="X") ++PropertyRedirects=(OldName="KismetMathLibrary.Atan2.A",NewName="Y") ++PropertyRedirects=(OldName="KismetMathLibrary.Atan2.B",NewName="X") + ; These aren't fully "clean" redirects, but the previous setup was broken and the necessary fix cannot be redirected to cleanly in all cases ; - If calling these on "self" in a BP where the self implements IUserListEntry, the redirect works cleanly ; - If calling these on an external object (rare), the redirect will point users to the new function to use, but the nodes will need manual fixup @@ -886,6 +891,7 @@ RelevantTimeout=5.0 SpawnPrioritySeconds=1.0 ServerTravelPause=4.0 NetServerMaxTickRate=30 +MaxNetTickRate=120 NetConnectionClassName="/Script/OnlineSubsystemUtils.IpConnection" MaxPortCountToTry=512 diff --git a/Engine/Config/BaseInput.ini b/Engine/Config/BaseInput.ini index 4216b29548f1..1e886dabcd71 100644 --- a/Engine/Config/BaseInput.ini +++ b/Engine/Config/BaseInput.ini @@ -13,6 +13,8 @@ bEnableGestureRecognizer=false +ConsoleKeys=Tilde TriggerKeyIsConsideredPressed=0.75 TriggerKeyIsConsideredReleased=0.25 +InitialButtonRepeatDelay=0.2 +ButtonRepeatDelay=0.1 ;----------------------------------------------------------------------------------------- ; Axis properties @@ -205,6 +207,10 @@ MaxScrollbackSize=1024 +ManualAutoCompleteList=(Command="Stat SoundReverb",Desc="Shows active SoundReverb") +ManualAutoCompleteList=(Command="Stat SoundWaves",Desc="Shows active SoundWaves") +ManualAutoCompleteList=(Command="Stat Sounds",Desc=" <-debug> Shows active SoundCues and SoundWaves") ++ManualAutoCompleteList=(Command="ScriptAudit LongestFunctions",Desc="List functions that contain the most bytecode - optionally include # of entries to list") ++ManualAutoCompleteList=(Command="ScriptAudit FrequentFunctionsCalled",Desc="List functions that are most frequently called from bytecode - optionally include # of entries to list") ++ManualAutoCompleteList=(Command="ScriptAudit FrequentInstructions",Desc="List most frequently used instructions - optionally include # of entries to list") ++ManualAutoCompleteList=(Command="ScriptAudit TotalBytecodeSize",Desc="Gather total size of bytecode in bytes of currently loaded functions") +ManualAutoCompleteList=(Command="Audio3dVisualize",Desc="Shows locations of sound sources playing (white text) and their left and right channel locations respectively (red and green). Virtualized loops (if enabled) display in blue.") +ManualAutoCompleteList=(Command="StartMovieCapture",Desc=) +ManualAutoCompleteList=(Command="StopMovieCapture",Desc=) diff --git a/Engine/Config/HTML5/HTML5Engine.ini b/Engine/Config/HTML5/HTML5Engine.ini index 9ec6704f66ab..12acf747dbc5 100644 --- a/Engine/Config/HTML5/HTML5Engine.ini +++ b/Engine/Config/HTML5/HTML5Engine.ini @@ -1,7 +1,10 @@ [Audio] +; This audio backend can be used by running with the -noaudiomixer commandline argument: AudioDeviceModuleName=ALAudio -; Defining below allows switching to audio mixer using -audiomixer commandline +; This audio backend can be used by running with the -audiomixer commandline argument: AudioMixerModuleName=AudioMixerSDL +; By default, we use the AudioMixerSDL backend. +UseAudioMixer=true ; Defines a platform-specific volume headroom (in dB) for audio to provide better platform consistency with respect to volume levels. PlatformHeadroomDB=0 diff --git a/Engine/Extras/LLDBDataFormatters/UE4DataFormatters.py b/Engine/Extras/LLDBDataFormatters/UE4DataFormatters.py index f24ac4eb8336..bcaea76d8dc5 100644 --- a/Engine/Extras/LLDBDataFormatters/UE4DataFormatters.py +++ b/Engine/Extras/LLDBDataFormatters/UE4DataFormatters.py @@ -62,7 +62,7 @@ def UE4FNameSummaryProvider(valobj,dict): if IndexVal >= 4194304: return 'name=Invalid' else: - Expr = '(char*)(((FNameEntry***)GFNameTableForDebuggerVisualizers_MT)['+str(IndexVal)+' / 16384]['+str(IndexVal)+' % 16384])->AnsiName' + Expr = '(char*)((FNameEntry&)GNameBlocksDebug['+str(IndexVal)+' >> FNameDebugVisualizer::OffsetBits][FNameDebugVisualizer::EntryStride * ('+str(IndexVal)+' & FNameDebugVisualizer::OffsetMask)]).AnsiName' FNameRef = valobj.CreateValueFromExpression(str(IndexVal), Expr) assert FNameRef != None Val = FNameRef.GetSummary() @@ -77,7 +77,7 @@ def UE4FMinimalNameSummaryProvider(valobj,dict): if IndexVal >= 4194304: return 'name=Invalid' else: - Expr = '(char*)(((FNameEntry***)GFNameTableForDebuggerVisualizers_MT)['+str(IndexVal)+' / 16384]['+str(IndexVal)+' % 16384])->AnsiName' + Expr = '(char*)((FNameEntry&)GNameBlocksDebug['+str(IndexVal)+' >> FNameDebugVisualizer::OffsetBits][FNameDebugVisualizer::EntryStride * ('+str(IndexVal)+' & FNameDebugVisualizer::OffsetMask)]).AnsiName' FNameRef = valobj.CreateValueFromExpression(str(IndexVal), Expr) assert FNameRef != None Val = FNameRef.GetSummary() diff --git a/Engine/Extras/NatvisHelpers/EntryPoint.cpp b/Engine/Extras/NatvisHelpers/EntryPoint.cpp index 315c41544784..ecc7ea96ba0b 100644 --- a/Engine/Extras/NatvisHelpers/EntryPoint.cpp +++ b/Engine/Extras/NatvisHelpers/EntryPoint.cpp @@ -2,8 +2,8 @@ #include "Globals.h" -extern "C" __declspec(dllexport) void InitNatvisHelpers(FNameEntry*** NameTable, FChunkedFixedUObjectArray* ObjectArray) +extern "C" __declspec(dllexport) void InitNatvisHelpers(uint8** NameTable, FChunkedFixedUObjectArray* ObjectArray) { - GFNameTableForDebuggerVisualizers_MT = NameTable; + GNameBlocksDebug = NameTable; GObjectArrayForDebugVisualizers = ObjectArray; } diff --git a/Engine/Extras/NatvisHelpers/Globals.cpp b/Engine/Extras/NatvisHelpers/Globals.cpp index e240e2d0d801..301b7f2ced16 100644 --- a/Engine/Extras/NatvisHelpers/Globals.cpp +++ b/Engine/Extras/NatvisHelpers/Globals.cpp @@ -3,5 +3,5 @@ struct FNameEntry; class FChunkedFixedUObjectArray; -FNameEntry*** GFNameTableForDebuggerVisualizers_MT = nullptr; +uint8** GNameBlocksDebug = nullptr; FChunkedFixedUObjectArray* GObjectArrayForDebugVisualizers = nullptr; diff --git a/Engine/Extras/NatvisHelpers/Globals.h b/Engine/Extras/NatvisHelpers/Globals.h index bd187b2b99aa..a4638fde7387 100644 --- a/Engine/Extras/NatvisHelpers/Globals.h +++ b/Engine/Extras/NatvisHelpers/Globals.h @@ -5,5 +5,5 @@ struct FNameEntry; class FChunkedFixedUObjectArray; -extern FNameEntry*** GFNameTableForDebuggerVisualizers_MT; +extern uint8** GNameBlocksDebug; extern FChunkedFixedUObjectArray* GObjectArrayForDebugVisualizers; diff --git a/Engine/Extras/NatvisHelpers/README.txt b/Engine/Extras/NatvisHelpers/README.txt index f8592b594c27..32bd4b167a67 100644 --- a/Engine/Extras/NatvisHelpers/README.txt +++ b/Engine/Extras/NatvisHelpers/README.txt @@ -2,6 +2,5 @@ This library exists to support UE's custom native visualizers in Visual Studio w When generating a patch executable, we include this library and force the linker to include a reference to the global InitNatvisHelpers() function. -This creates references to GFNameTableForDebuggerVisualizers_MT and GObjectArrayForDebugVisualizers. Object files in static libraries are only -included if a symbol in them is referenced, so if those symbols already exist in the patch, those references will be used. Otherwise the definitions +This creates references to GNameBlocksDebug and GObjectArrayForDebugVisualizers. Object files in static libraries are only included if a symbol in them is referenced, so if those symbols already exist in the patch, those references will be used. Otherwise the definitions in Global.cpp will be used. diff --git a/Engine/Plugins/2D/Paper2D/Source/Paper2D/Classes/PaperFlipbookComponent.h b/Engine/Plugins/2D/Paper2D/Source/Paper2D/Classes/PaperFlipbookComponent.h index e4a0d96cacef..dca027d6e9df 100644 --- a/Engine/Plugins/2D/Paper2D/Source/Paper2D/Classes/PaperFlipbookComponent.h +++ b/Engine/Plugins/2D/Paper2D/Source/Paper2D/Classes/PaperFlipbookComponent.h @@ -33,23 +33,23 @@ protected: UMaterialInterface* Material_DEPRECATED; /** Current play rate of the flipbook */ - UPROPERTY(Category=Sprite, EditAnywhere, Replicated) + UPROPERTY(Category=Sprite, EditAnywhere) float PlayRate; /** Whether the flipbook should loop when it reaches the end, or stop */ - UPROPERTY(Replicated) + UPROPERTY() uint32 bLooping:1; /** If playback should move the current position backwards instead of forwards */ - UPROPERTY(Replicated) + UPROPERTY() uint32 bReversePlayback:1; /** Are we currently playing (moving Position) */ - UPROPERTY(Replicated) + UPROPERTY() uint32 bPlaying:1; /** Current position in the timeline */ - UPROPERTY(Replicated) + UPROPERTY() float AccumulatedTime; /** Last frame index calculated */ diff --git a/Engine/Plugins/2D/Paper2D/Source/Paper2D/Classes/PaperTileMapComponent.h b/Engine/Plugins/2D/Paper2D/Source/Paper2D/Classes/PaperTileMapComponent.h index 291ae12e55f7..b3a603e39887 100644 --- a/Engine/Plugins/2D/Paper2D/Source/Paper2D/Classes/PaperTileMapComponent.h +++ b/Engine/Plugins/2D/Paper2D/Source/Paper2D/Classes/PaperTileMapComponent.h @@ -22,13 +22,13 @@ struct FSpriteRenderSection; * This component is created when you drag a tile map asset from the content browser into a Blueprint, or * contained inside of the actor created when you drag one into the level. * - * NOTE: This is an early access preview class. While not considered production-ready, it is a step beyond + * NOTE: This is an beta preview class. While not considered production-ready, it is a step beyond * 'experimental' and is being provided as a preview of things to come: * - We will try to provide forward-compatibility for content you create. * - The classes may change significantly in the future. * - The code is in an early state and may not meet the desired polish / quality bar. * - There is probably no documentation or example content yet. - * - They will be promoted out of 'Early Access' when they are production ready. + * - They will be promoted out of 'beta' when they are production ready. * * @see UPrimitiveComponent, UPaperTileMap */ diff --git a/Engine/Plugins/2D/Paper2D/Source/Paper2D/Private/PaperRenderSceneProxy.h b/Engine/Plugins/2D/Paper2D/Source/Paper2D/Private/PaperRenderSceneProxy.h index a6749a74ef85..ce47669afecc 100644 --- a/Engine/Plugins/2D/Paper2D/Source/Paper2D/Private/PaperRenderSceneProxy.h +++ b/Engine/Plugins/2D/Paper2D/Source/Paper2D/Private/PaperRenderSceneProxy.h @@ -148,8 +148,6 @@ protected: bool IsCollisionView(const FEngineShowFlags& EngineShowFlags, bool& bDrawSimpleCollision, bool& bDrawComplexCollision) const; - friend class FPaperBatchSceneProxy; - void ConvertBatchesToNewStyle(TArray& SourceBatches); virtual void DebugDrawCollision(const FSceneView* View, int32 ViewIndex, FMeshElementCollector& Collector, bool bDrawSolid) const; diff --git a/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/SPaperEditorViewport.cpp b/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/SPaperEditorViewport.cpp index 186405099b27..6cf6c39c5c76 100644 --- a/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/SPaperEditorViewport.cpp +++ b/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/SPaperEditorViewport.cpp @@ -179,7 +179,7 @@ FReply SPaperEditorViewport::OnMouseButtonDown(const FGeometry& MyGeometry, cons ReplyState.CaptureMouse( SharedThis(this) ); ReplyState.UseHighPrecisionMouseMovement( SharedThis(this) ); - SoftwareCursorPosition = PanelCoordToGraphCoord( MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ) ); + SoftwareCursorPosition = PanelCoordToGraphCoord( MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ) * MyGeometry.Scale ); // clear any interpolation when you manually pan //DeferredMovementTargetObject = nullptr; @@ -189,7 +189,7 @@ FReply SPaperEditorViewport::OnMouseButtonDown(const FGeometry& MyGeometry, cons else if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) { // START MARQUEE SELECTION. - const FVector2D GraphMousePos = PanelCoordToGraphCoord( MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ) ); + const FVector2D GraphMousePos = PanelCoordToGraphCoord( MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ) * MyGeometry.Scale ); Marquee.Start( GraphMousePos, FMarqueeOperation::OperationTypeFromMouseEvent(MouseEvent) ); // Trigger a selection update now so that single-clicks without a drag still select something @@ -218,7 +218,7 @@ FReply SPaperEditorViewport::OnMouseButtonUp(const FGeometry& MyGeometry, const if (HasMouseCapture()) { FSlateRect ThisPanelScreenSpaceRect = MyGeometry.GetLayoutBoundingRect(); - const FVector2D ScreenSpaceCursorPos = MyGeometry.LocalToAbsolute( GraphCoordToPanelCoord( SoftwareCursorPosition ) ); + const FVector2D ScreenSpaceCursorPos = MyGeometry.LocalToAbsolute( GraphCoordToPanelCoord( SoftwareCursorPosition ) / MyGeometry.Scale ); FIntPoint BestPositionInViewport( FMath::RoundToInt( FMath::Clamp( ScreenSpaceCursorPos.X, ThisPanelScreenSpaceRect.Left, ThisPanelScreenSpaceRect.Right ) ), @@ -335,7 +335,7 @@ FReply SPaperEditorViewport::OnMouseMove(const FGeometry& MyGeometry, const FPoi { // We are marquee selecting - const FVector2D GraphMousePos = PanelCoordToGraphCoord( MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ) ); + const FVector2D GraphMousePos = PanelCoordToGraphCoord( MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ) * MyGeometry.Scale ); Marquee.Rect.UpdateEndPoint(GraphMousePos); return FReply::Handled(); @@ -349,7 +349,7 @@ FReply SPaperEditorViewport::OnMouseMove(const FGeometry& MyGeometry, const FPoi FReply SPaperEditorViewport::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { // We want to zoom into this point; i.e. keep it the same fraction offset into the panel - const FVector2D WidgetSpaceCursorPos = MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ); + const FVector2D WidgetSpaceCursorPos = MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ) * MyGeometry.Scale; FVector2D PointToMaintainGraphSpace = PanelCoordToGraphCoord( WidgetSpaceCursorPos ); @@ -515,7 +515,7 @@ void SPaperEditorViewport::PaintSoftwareCursor(const FGeometry& AllottedGeometry FSlateDrawElement::MakeBox( OutDrawElements, DrawLayerId, - AllottedGeometry.ToPaintGeometry( GraphCoordToPanelCoord(SoftwareCursorPosition) - ( Brush->ImageSize / 2 ), Brush->ImageSize ), + AllottedGeometry.ToPaintGeometry( GraphCoordToPanelCoord(SoftwareCursorPosition) / AllottedGeometry.Scale - ( Brush->ImageSize / 2 ), Brush->ImageSize ), Brush); } } diff --git a/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/TileMapEditing/TileMapEdModeToolkit.cpp b/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/TileMapEditing/TileMapEdModeToolkit.cpp index 73e305a84748..ea51acc4d506 100644 --- a/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/TileMapEditing/TileMapEdModeToolkit.cpp +++ b/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/TileMapEditing/TileMapEdModeToolkit.cpp @@ -317,7 +317,7 @@ TSharedRef FTileMapEdModeToolkit::BuildToolBar() const //@TODO: TileMapTerrain: Ugly styling FUIAction TerrainTypeDropdownAction; TerrainTypeDropdownAction.IsActionVisibleDelegate = FIsActionButtonVisible::CreateSP(this, &FTileMapEdModeToolkit::DoesSelectedTileSetHaveTerrains); - ToolsToolbar.AddComboButton(TerrainTypeDropdownAction, FOnGetContent::CreateSP(this, &FTileMapEdModeToolkit::GenerateTerrainMenu)); + ToolsToolbar.AddComboButton(TerrainTypeDropdownAction, FOnGetContent::CreateSP(const_cast(this), &FTileMapEdModeToolkit::GenerateTerrainMenu)); } return diff --git a/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/TileMapEditing/TileMapEditor.cpp b/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/TileMapEditing/TileMapEditor.cpp index 0d917634ec03..3e374b4a5ba7 100644 --- a/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/TileMapEditing/TileMapEditor.cpp +++ b/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/TileMapEditing/TileMapEditor.cpp @@ -233,7 +233,7 @@ TSharedRef FTileMapEditor::SpawnTab_Viewport(const FSpawnTabArgs& Args SNew(STextBlock) .Visibility(EVisibility::HitTestInvisible) .TextStyle(FEditorStyle::Get(), "Graph.CornerText") - .Text(LOCTEXT("TileMapEditorViewportEarlyAccessPreviewWarning", "Early access preview")) + .Text(LOCTEXT("TileMapEditorViewportEarlyAccessPreviewWarning", "Beta preview")) ] ]; } diff --git a/Engine/Plugins/AI/AISupport/AISupport.uplugin b/Engine/Plugins/AI/AISupport/AISupport.uplugin index e5a912e48966..aa145703e93e 100644 --- a/Engine/Plugins/AI/AISupport/AISupport.uplugin +++ b/Engine/Plugins/AI/AISupport/AISupport.uplugin @@ -19,7 +19,7 @@ { "Name": "AISupportModule", "Type": "Runtime", - "LoadingPhase": "PreDefault" + "LoadingPhase": "PostConfigInit" } ] } \ No newline at end of file diff --git a/Engine/Plugins/AI/AISupport/Source/AISupportModule/Private/AISupportModule.cpp b/Engine/Plugins/AI/AISupport/Source/AISupportModule/Private/AISupportModule.cpp index d39c8e014015..8690134ade68 100644 --- a/Engine/Plugins/AI/AISupport/Source/AISupportModule/Private/AISupportModule.cpp +++ b/Engine/Plugins/AI/AISupport/Source/AISupportModule/Private/AISupportModule.cpp @@ -2,6 +2,7 @@ #include "AISupportModule.h" #include "Modules/ModuleManager.h" +#include "AITypes.h" #define LOCTEXT_NAMESPACE "AISupport" @@ -16,17 +17,15 @@ IMPLEMENT_MODULE(FAISupportModule, AISupportModule) void FAISupportModule::StartupModule() { - // Called right after the module DLL has been loaded and the module object has been created - // Load dependent modules here, and they will be guaranteed to be available during ShutdownModule. ie: - // FModuleManager::Get().LoadModuleChecked(TEXT("HTTP")); + // We need to actually link in something from the AI module, otherwise DLL dependencies will not get loaded early enough + // It is NOT safe to actually call startup on the AI module though, as that depends on things that get initialized later in launch + static int32 ForceLink = FAIResources::GetResourcesCount(); + ForceLink++; } void FAISupportModule::ShutdownModule() { - // Called before the module is unloaded, right before the module object is destroyed. - // During normal shutdown, this is called in reverse order that modules finish StartupModule(). - // This means that, as long as a module references dependent modules in it's StartupModule(), it - // can safely reference those dependencies in ShutdownModule() as well. + } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Animation/LiveLink/Source/LiveLink/Private/LiveLinkModule.cpp b/Engine/Plugins/Animation/LiveLink/Source/LiveLink/Private/LiveLinkModule.cpp index a765c780407d..5054a62ff13e 100644 --- a/Engine/Plugins/Animation/LiveLink/Source/LiveLink/Private/LiveLinkModule.cpp +++ b/Engine/Plugins/Animation/LiveLink/Source/LiveLink/Private/LiveLinkModule.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ILiveLinkModule.h" @@ -149,7 +149,7 @@ public: for (const FHeaderEntry& Header : Headers) { TArray Subjects = LiveLinkSubjects.FindChecked(Header.Key); - Subjects.Sort(); + Subjects.Sort(FNameLexicalLess()); for (FName Subject : Subjects) { FName FullName = *FString::Format(TEXT("{0} ({1})"), { Subject.ToString(), Header.Value.ToString() }); diff --git a/Engine/Plugins/Compositing/Composure/Source/Composure/Private/MovieScene/MovieSceneComposureExportSectionTemplate.cpp b/Engine/Plugins/Compositing/Composure/Source/Composure/Private/MovieScene/MovieSceneComposureExportSectionTemplate.cpp index 4a6d1ec22486..92d99762d894 100644 --- a/Engine/Plugins/Compositing/Composure/Source/Composure/Private/MovieScene/MovieSceneComposureExportSectionTemplate.cpp +++ b/Engine/Plugins/Compositing/Composure/Source/Composure/Private/MovieScene/MovieSceneComposureExportSectionTemplate.cpp @@ -81,7 +81,7 @@ private: struct FMovieSceneComposureExportPasses { /** Map of internal transform pass name (or NAME_None for the Output), to the export config options */ - TSortedMap PassesToExport; + TSortedMap PassesToExport; void AddPass(const FMovieSceneComposureExportPass& InPass, ACompositingElement* CompShotElement) { diff --git a/Engine/Plugins/Compositing/Composure/Source/ComposureLayersEditor/Private/ComposureDetailCustomizations.cpp b/Engine/Plugins/Compositing/Composure/Source/ComposureLayersEditor/Private/ComposureDetailCustomizations.cpp index f3dc89cdd0bb..230481520682 100644 --- a/Engine/Plugins/Compositing/Composure/Source/ComposureLayersEditor/Private/ComposureDetailCustomizations.cpp +++ b/Engine/Plugins/Compositing/Composure/Source/ComposureLayersEditor/Private/ComposureDetailCustomizations.cpp @@ -25,7 +25,6 @@ #include "Widgets/Text/STextBlock.h" #include "Widgets/Input/SNumericEntryBox.h" #include "Misc/Optional.h" -#include "Widgets/Input/SVectorInputBox.h" #include "Widgets/SWidget.h" #include "Widgets/Colors/SColorPicker.h" #include "Widgets/Input/SComboButton.h" @@ -409,7 +408,7 @@ void FCompElementDetailsCustomization::ForceRefreshLayout() void FCompElementDetailsCustomization::GetInstanceCameraSourceComboStrings(TArray>& OutComboBoxStrings, TArray>& OutToolTips, TArray& OutRestrictedItems) { - const UEnum* CamSourceEnum = FindObject(ANY_PACKAGE, TEXT("ESceneCameraLinkType")); + const UEnum* CamSourceEnum = StaticEnum(); if (ensure(CamSourceEnum)) { for (int32 EnumIndex = 0; EnumIndex < CamSourceEnum->NumEnums()-1; ++EnumIndex) @@ -445,7 +444,7 @@ FString FCompElementDetailsCustomization::GetInstanceCameraSourceValueStr(TShare } else { - const UEnum* CamSourceEnum = FindObject(ANY_PACKAGE, TEXT("ESceneCameraLinkType")); + const UEnum* CamSourceEnum = StaticEnum(); if (ensure(CamSourceEnum)) { DisplayStr = CamSourceEnum->GetDisplayNameTextByValue(CurrentValue).ToString(); @@ -458,7 +457,7 @@ FString FCompElementDetailsCustomization::GetInstanceCameraSourceValueStr(TShare void FCompElementDetailsCustomization::OnCameraSourceSelected(const FString& Selection, TSharedPtr PropertyHandle) { - const UEnum* CamSourceEnum = FindObject(ANY_PACKAGE, TEXT("ESceneCameraLinkType")); + const UEnum* CamSourceEnum = StaticEnum(); if (ensure(CamSourceEnum) && PropertyHandle.IsValid()) { const int64 FoundValue = CamSourceEnum->GetValueByNameString(Selection); diff --git a/Engine/Plugins/Compositing/Composure/Source/ComposureLayersEditor/Private/Widgets/SCompElementPreviewPane.cpp b/Engine/Plugins/Compositing/Composure/Source/ComposureLayersEditor/Private/Widgets/SCompElementPreviewPane.cpp index f8fd1507f642..4291dac0d3e4 100644 --- a/Engine/Plugins/Compositing/Composure/Source/ComposureLayersEditor/Private/Widgets/SCompElementPreviewPane.cpp +++ b/Engine/Plugins/Compositing/Composure/Source/ComposureLayersEditor/Private/Widgets/SCompElementPreviewPane.cpp @@ -259,9 +259,9 @@ TSharedRef SCompElementPreviewPane::GenerateMenu() const FText(), FSlateIcon(), FUIAction( - FExecuteAction::CreateSP(this, &SCompElementPreviewPane::RedChannelToggled), + FExecuteAction::CreateSP(const_cast(this), &SCompElementPreviewPane::RedChannelToggled), FCanExecuteAction(), - FGetActionCheckState::CreateSP(this, &SCompElementPreviewPane::GetRedChannel) + FGetActionCheckState::CreateSP(const_cast(this), &SCompElementPreviewPane::GetRedChannel) ), NAME_None, EUserInterfaceActionType::ToggleButton @@ -271,9 +271,9 @@ TSharedRef SCompElementPreviewPane::GenerateMenu() const FText(), FSlateIcon(), FUIAction( - FExecuteAction::CreateSP(this, &SCompElementPreviewPane::GreenChannelToggled), + FExecuteAction::CreateSP(const_cast(this), &SCompElementPreviewPane::GreenChannelToggled), FCanExecuteAction(), - FGetActionCheckState::CreateSP(this, &SCompElementPreviewPane::GetGreenChannel) + FGetActionCheckState::CreateSP(const_cast(this), &SCompElementPreviewPane::GetGreenChannel) ), NAME_None, EUserInterfaceActionType::ToggleButton @@ -283,9 +283,9 @@ TSharedRef SCompElementPreviewPane::GenerateMenu() const FText(), FSlateIcon(), FUIAction( - FExecuteAction::CreateSP(this, &SCompElementPreviewPane::BlueChannelToggled), + FExecuteAction::CreateSP(const_cast(this), &SCompElementPreviewPane::BlueChannelToggled), FCanExecuteAction(), - FGetActionCheckState::CreateSP(this, &SCompElementPreviewPane::GetBlueChannel) + FGetActionCheckState::CreateSP(const_cast(this), &SCompElementPreviewPane::GetBlueChannel) ), NAME_None, EUserInterfaceActionType::ToggleButton @@ -295,9 +295,9 @@ TSharedRef SCompElementPreviewPane::GenerateMenu() const FText(), FSlateIcon(), FUIAction( - FExecuteAction::CreateSP(this, &SCompElementPreviewPane::AlphaChannelToggled), + FExecuteAction::CreateSP(const_cast(this), &SCompElementPreviewPane::AlphaChannelToggled), FCanExecuteAction(), - FGetActionCheckState::CreateSP(this, &SCompElementPreviewPane::GetAlphaChannel) + FGetActionCheckState::CreateSP(const_cast(this), &SCompElementPreviewPane::GetAlphaChannel) ), NAME_None, EUserInterfaceActionType::ToggleButton diff --git a/Engine/Plugins/Developer/Concert/ConcertFrontend/Source/ConcertFrontend/Private/Widgets/SConcertBrowser.cpp b/Engine/Plugins/Developer/Concert/ConcertFrontend/Source/ConcertFrontend/Private/Widgets/SConcertBrowser.cpp index 37ea24f40199..b2db36143f57 100644 --- a/Engine/Plugins/Developer/Concert/ConcertFrontend/Source/ConcertFrontend/Private/Widgets/SConcertBrowser.cpp +++ b/Engine/Plugins/Developer/Concert/ConcertFrontend/Source/ConcertFrontend/Private/Widgets/SConcertBrowser.cpp @@ -688,7 +688,7 @@ TSharedRef SConcertBrowser::MakeSessionRowWidget(TSharedPtrSessionName); ActiveSessionDef.Text = FEditorFontGlyphs::Info_Circle; ActiveSessionDef.ToolTipText = LOCTEXT("ActiveSessionToolTip", "See the current active session"); - ActiveSessionDef.OnClicked.BindSP(this, &SConcertBrowser::OnClickActiveSession); + ActiveSessionDef.OnClicked.BindSP(const_cast(this), &SConcertBrowser::OnClickActiveSession); // Resume Session FConcertUIButtonDefinition& ResumeSessionDef = ButtonDefs.AddDefaulted_GetRef(); @@ -696,7 +696,7 @@ TSharedRef SConcertBrowser::MakeSessionRowWidget(TSharedPtrSessionName); ResumeSessionDef.Text = FEditorFontGlyphs::Play_Circle; ResumeSessionDef.ToolTipText = LOCTEXT("ResumeSessionToolTip", "Resume receiving updates from this session"); - ResumeSessionDef.OnClicked.BindSP(this, &SConcertBrowser::OnClickResumeSession); + ResumeSessionDef.OnClicked.BindSP(const_cast(this), &SConcertBrowser::OnClickResumeSession); // Suspend Session FConcertUIButtonDefinition& SuspendSessionDef = ButtonDefs.AddDefaulted_GetRef(); @@ -704,7 +704,7 @@ TSharedRef SConcertBrowser::MakeSessionRowWidget(TSharedPtrSessionName); SuspendSessionDef.Text = FEditorFontGlyphs::Pause_Circle; SuspendSessionDef.ToolTipText = LOCTEXT("SuspendSessionToolTip", "Suspend receiving updates from this session"); - SuspendSessionDef.OnClicked.BindSP(this, &SConcertBrowser::OnClickSuspendSession); + SuspendSessionDef.OnClicked.BindSP(const_cast(this), &SConcertBrowser::OnClickSuspendSession); // Delete Session FConcertUIButtonDefinition& DeleteSessionDef = ButtonDefs.AddDefaulted_GetRef(); @@ -712,7 +712,7 @@ TSharedRef SConcertBrowser::MakeSessionRowWidget(TSharedPtrSessionName); + DeleteSessionDef.OnClicked.BindSP(const_cast(this), &SConcertBrowser::OnClickDeleteSession, Item->SessionName); // Join Session FConcertUIButtonDefinition& JoinSessionDef = ButtonDefs.AddDefaulted_GetRef(); @@ -720,7 +720,7 @@ TSharedRef SConcertBrowser::MakeSessionRowWidget(TSharedPtrSessionName); JoinSessionDef.Text = FEditorFontGlyphs::Sign_In; JoinSessionDef.ToolTipText = LOCTEXT("JoinSessionToolTip", "Join this session"); - JoinSessionDef.OnClicked.BindSP(this, &SConcertBrowser::OnClickJoinSession, Item->SessionName); + JoinSessionDef.OnClicked.BindSP(const_cast(this), &SConcertBrowser::OnClickJoinSession, Item->SessionName); // Leave Session FConcertUIButtonDefinition& LeaveSessionDef = ButtonDefs.AddDefaulted_GetRef(); @@ -728,7 +728,7 @@ TSharedRef SConcertBrowser::MakeSessionRowWidget(TSharedPtrSessionName); LeaveSessionDef.Text = FEditorFontGlyphs::Sign_Out; LeaveSessionDef.ToolTipText = LOCTEXT("LeaveSessionToolTip", "Leave this session"); - LeaveSessionDef.OnClicked.BindSP(this, &SConcertBrowser::OnClickLeaveSession); + LeaveSessionDef.OnClicked.BindSP(const_cast(this), &SConcertBrowser::OnClickLeaveSession); } diff --git a/Engine/Plugins/Developer/Concert/ConcertFrontend/Source/ConcertFrontend/Private/Widgets/SConcertScrollBox.cpp b/Engine/Plugins/Developer/Concert/ConcertFrontend/Source/ConcertFrontend/Private/Widgets/SConcertScrollBox.cpp index 53f3cd996a7e..8825dcd97125 100644 --- a/Engine/Plugins/Developer/Concert/ConcertFrontend/Source/ConcertFrontend/Private/Widgets/SConcertScrollBox.cpp +++ b/Engine/Plugins/Developer/Concert/ConcertFrontend/Source/ConcertFrontend/Private/Widgets/SConcertScrollBox.cpp @@ -16,7 +16,7 @@ void SConcertScrollBox::Construct(const FArguments& InArgs) bPreventLock = false; ScrollBar = SNew(SScrollBar) - .Thickness(FVector2D(8.0f, 8.0f)); + .Thickness(FVector2D(12.0f, 12.0f)); ChildSlot [ diff --git a/Engine/Plugins/Developer/Concert/ConcertMain/Source/ConcertTransport/Private/IdentifierTable/ConcertTransportArchives.cpp b/Engine/Plugins/Developer/Concert/ConcertMain/Source/ConcertTransport/Private/IdentifierTable/ConcertTransportArchives.cpp index 7466f30dd3f4..7f9e1d170179 100644 --- a/Engine/Plugins/Developer/Concert/ConcertMain/Source/ConcertTransport/Private/IdentifierTable/ConcertTransportArchives.cpp +++ b/Engine/Plugins/Developer/Concert/ConcertMain/Source/ConcertTransport/Private/IdentifierTable/ConcertTransportArchives.cpp @@ -33,12 +33,12 @@ FArchive& FConcertIdentifierWriter::operator<<(FName& Name) SerializeIntPacked(UnsignedIndex); }; - int32 HardcodedIndex = Name.GetComparisonIndex(); + const EName* Ename = Name.ToEName(); int32 IdentifierTableIndex = INDEX_NONE; - if (HardcodedIndex <= MAX_NETWORKED_HARDCODED_NAME) + if (Ename && ShouldReplicateAsInteger(*Ename)) { SerializeConcertIdentifierSource(EConcertIdentifierSource::HardcodedIndex); - SerializeIndexValue(HardcodedIndex); + SerializeIndexValue(*Ename); } else if (LocalIdentifierTable) { diff --git a/Engine/Plugins/Developer/Concert/ConcertMain/Source/ConcertTransport/Public/IdentifierTable/ConcertIdentifierTable.h b/Engine/Plugins/Developer/Concert/ConcertMain/Source/ConcertTransport/Public/IdentifierTable/ConcertIdentifierTable.h index 72cedfe647f5..8276aa29124b 100644 --- a/Engine/Plugins/Developer/Concert/ConcertMain/Source/ConcertTransport/Public/IdentifierTable/ConcertIdentifierTable.h +++ b/Engine/Plugins/Developer/Concert/ConcertMain/Source/ConcertTransport/Public/IdentifierTable/ConcertIdentifierTable.h @@ -19,7 +19,7 @@ struct TConcertIdentifierTable_CaseSensitivePlainNameKeyFuncs : BaseKeyFuncs CommandList = MakeShareable(new FUICommandList); MenuExtender->AddToolBarExtension("LocalizationService", EExtensionHook::First, CommandList, - FToolBarExtensionDelegate::CreateRaw(this, &FOneSkyLocalizationServiceProvider::AddTargetToolbarButtons, LocalizationTarget, CommandList)); + FToolBarExtensionDelegate::CreateRaw(const_cast(this), &FOneSkyLocalizationServiceProvider::AddTargetToolbarButtons, LocalizationTarget, CommandList)); } @@ -506,7 +506,7 @@ void FOneSkyLocalizationServiceProvider::CustomizeTargetSetToolbar(TSharedRef CommandList = MakeShareable(new FUICommandList); MenuExtender->AddToolBarExtension("LocalizationService", EExtensionHook::First, CommandList, - FToolBarExtensionDelegate::CreateRaw(this, &FOneSkyLocalizationServiceProvider::AddTargetSetToolbarButtons, InLocalizationTargetSet, CommandList)); + FToolBarExtensionDelegate::CreateRaw(const_cast(this), &FOneSkyLocalizationServiceProvider::AddTargetSetToolbarButtons, InLocalizationTargetSet, CommandList)); } diff --git a/Engine/Plugins/Developer/PerforceSourceControl/Source/PerforceSourceControl/Private/PerforceSourceControlOperations.cpp b/Engine/Plugins/Developer/PerforceSourceControl/Source/PerforceSourceControl/Private/PerforceSourceControlOperations.cpp index c4a8f5e58cdd..ee3ff981f1d0 100644 --- a/Engine/Plugins/Developer/PerforceSourceControl/Source/PerforceSourceControl/Private/PerforceSourceControlOperations.cpp +++ b/Engine/Plugins/Developer/PerforceSourceControl/Source/PerforceSourceControl/Private/PerforceSourceControlOperations.cpp @@ -1285,7 +1285,7 @@ bool FPerforceUpdateStatusWorker::Execute(FPerforceSourceControlCommand& InComma if(Operation->ShouldGetOpenedOnly()) { - const FString ContentFolder = FPaths::ConvertRelativePathToFull(FPaths::RootDir()); + const FString ContentFolder = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()); const FString FileQuery = FString::Printf(TEXT("%s..."), *ContentFolder); TArray Parameters = InCommand.Files; Parameters.Add(FileQuery); diff --git a/Engine/Plugins/Developer/SubversionSourceControl/Source/SubversionSourceControl/Private/SubversionSourceControlOperations.cpp b/Engine/Plugins/Developer/SubversionSourceControl/Source/SubversionSourceControl/Private/SubversionSourceControlOperations.cpp index 88c75ca31fa6..b64b49394814 100644 --- a/Engine/Plugins/Developer/SubversionSourceControl/Source/SubversionSourceControl/Private/SubversionSourceControlOperations.cpp +++ b/Engine/Plugins/Developer/SubversionSourceControl/Source/SubversionSourceControl/Private/SubversionSourceControlOperations.cpp @@ -619,7 +619,7 @@ bool FSubversionUpdateStatusWorker::Execute(FSubversionSourceControlCommand& InC Parameters.Add(TEXT("--verbose")); TArray Files; - Files.Add(FPaths::RootDir()); + Files.Add(FPaths::ProjectDir()); InCommand.bCommandSuccessful &= SubversionSourceControlUtils::RunCommand(TEXT("status"), Files, Parameters, ResultsXml, InCommand.ErrorMessages, InCommand.UserName); SubversionSourceControlUtils::ParseStatusResults(ResultsXml, InCommand.ErrorMessages, InCommand.UserName, InCommand.WorkingCopyRoot, OutStates); diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorCommands.cpp b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorCommands.cpp index 75c69ef4fba8..32a1bac5bf51 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorCommands.cpp +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorCommands.cpp @@ -22,6 +22,7 @@ void FAssetManagerEditorCommands::RegisterCommands() UI_COMMAND(ViewAssetAudit, "Audit Assets...", "Opens the Asset Audit UI and displays information about the selected assets", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Shift | EModifierKey::Alt, EKeys::A)); UI_COMMAND(OpenSelectedInAssetEditor, "Edit...", "Opens the selected asset in the relevent editor.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(ZoomToFit, "Zoom to Fit", "Zoom in and center the view on the selected item", EUserInterfaceActionType::Button, FInputChord(EKeys::Home)); UI_COMMAND(ReCenterGraph, "Re-Center Graph", "Re-centers the graph on this node, showing all referencers and references for this asset instead", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(CopyReferencedObjects, "Copy Referenced Objects List", "Copies the list of objects that the selected asset references to the clipboard.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(CopyReferencingObjects, "Copy Referencing Objects List", "Copies the list of objects that reference the selected asset to the clipboard.", EUserInterfaceActionType::Button, FInputChord()); @@ -29,6 +30,7 @@ void FAssetManagerEditorCommands::RegisterCommands() UI_COMMAND(ShowReferencingObjects, "Show Referencing Objects List", "Shows a list of objects that reference the selected asset.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(ShowReferenceTree, "Show Reference Tree", "Shows a reference tree for the selected asset.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(AuditReferencedObjects, "Audit References", "Opens the Asset Audit UI and displays information about all assets referenced by selected asset", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(Find, "Find", "Find objects in the reference viewer.", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control, EKeys::F)); UI_COMMAND(MakeLocalCollectionWithReferencers, "Local", "Local. This collection is only visible to you and is not in source control.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(MakePrivateCollectionWithReferencers, "Private", "Private. This collection is only visible to you.", EUserInterfaceActionType::Button, FInputChord()); diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorCommands.h b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorCommands.h index 45b9311cee3d..1f7f7b1de929 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorCommands.h +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorCommands.h @@ -49,6 +49,12 @@ public: // Adds all referenced objects to asset audit window TSharedPtr AuditReferencedObjects; + /** Zoom in to fit the selected objects in the window */ + TSharedPtr ZoomToFit; + + /** Start finding objects */ + TSharedPtr Find; + // Creates a new collection with the list of assets that this asset references, user selects which ECollectionShareType to use. TSharedPtr MakeLocalCollectionWithReferencers; TSharedPtr MakePrivateCollectionWithReferencers; diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorModule.cpp b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorModule.cpp index 9cce543027f0..16c1ec7bbcd6 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorModule.cpp +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorModule.cpp @@ -182,7 +182,7 @@ void IAssetManagerEditorModule::GeneratePrimaryAssetTypeComboBoxStrings(TArray< TArray TypeInfos; AssetManager.GetPrimaryAssetTypeInfoList(TypeInfos); - TypeInfos.Sort([](const FPrimaryAssetTypeInfo& LHS, const FPrimaryAssetTypeInfo& RHS) { return LHS.PrimaryAssetType < RHS.PrimaryAssetType; }); + TypeInfos.Sort([](const FPrimaryAssetTypeInfo& LHS, const FPrimaryAssetTypeInfo& RHS) { return LHS.PrimaryAssetType.LexicalLess(RHS.PrimaryAssetType); }); // Can the field be cleared if (bAllowClear) @@ -1983,7 +1983,7 @@ void FAssetManagerEditorModule::LogAssetsWithMultipleLabels() } } - PackageToLabelMap.KeySort(TLess()); + PackageToLabelMap.KeySort(FNameLexicalLess()); UE_LOG(LogAssetManagerEditor, Log, TEXT("\nAssets with multiple labels follow")); @@ -2036,7 +2036,7 @@ void FAssetManagerEditorModule::DumpAssetDependencies(const TArray& Arg Manager.GetPrimaryAssetTypeInfoList(TypeInfos); - TypeInfos.Sort([](const FPrimaryAssetTypeInfo& LHS, const FPrimaryAssetTypeInfo& RHS) { return LHS.PrimaryAssetType < RHS.PrimaryAssetType; }); + TypeInfos.Sort([](const FPrimaryAssetTypeInfo& LHS, const FPrimaryAssetTypeInfo& RHS) { return LHS.PrimaryAssetType.LexicalLess(RHS.PrimaryAssetType); }); UE_LOG(LogAssetManagerEditor, Log, TEXT("=========== Asset Manager Dependencies ===========")); @@ -2083,7 +2083,7 @@ void FAssetManagerEditorModule::DumpAssetDependencies(const TArray& Arg { UE_LOG(LogAssetManagerEditor, Log, TEXT(" Type %s:"), *TypeInfo.PrimaryAssetType.ToString()); - DependencyInfos.Sort([](const FDependencyInfo& LHS, const FDependencyInfo& RHS) { return LHS.AssetName < RHS.AssetName; }); + DependencyInfos.Sort([](const FDependencyInfo& LHS, const FDependencyInfo& RHS) { return LHS.AssetName.LexicalLess(RHS.AssetName); }); for (FDependencyInfo& DependencyInfo : DependencyInfos) { diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/ReferenceViewerSchema.cpp b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/ReferenceViewerSchema.cpp index 396c89bdf368..f7b9d42b9121 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/ReferenceViewerSchema.cpp +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/ReferenceViewerSchema.cpp @@ -62,11 +62,12 @@ void UReferenceViewerSchema::GetContextMenuActions(const UEdGraph* CurrentGraph, MenuBuilder->BeginSection(TEXT("Misc"), NSLOCTEXT("ReferenceViewerSchema", "MiscSectionLabel", "Misc")); { + MenuBuilder->AddMenuEntry(FAssetManagerEditorCommands::Get().ZoomToFit); MenuBuilder->AddMenuEntry(FAssetManagerEditorCommands::Get().ReCenterGraph); MenuBuilder->AddSubMenu( NSLOCTEXT("ReferenceViewerSchema", "MakeCollectionWithTitle", "Make Collection with"), NSLOCTEXT("ReferenceViewerSchema", "MakeCollectionWithTooltip", "Makes a collection with either the referencers or dependencies of the selected nodes."), - FNewMenuDelegate::CreateUObject(this, &UReferenceViewerSchema::GetMakeCollectionWithSubMenu) + FNewMenuDelegate::CreateUObject(const_cast(this), &UReferenceViewerSchema::GetMakeCollectionWithSubMenu) ); } MenuBuilder->EndSection(); diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.cpp b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.cpp index f1298dd107a0..b645d892f248 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.cpp +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.cpp @@ -15,6 +15,7 @@ #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SSearchBox.h" #include "Widgets/Input/SSpinBox.h" #include "EditorStyleSet.h" #include "Engine/Selection.h" @@ -32,6 +33,7 @@ #include "Widgets/Input/SComboBox.h" #include "HAL/PlatformApplicationMisc.h" #include "AssetManagerEditorModule.h" +#include "Framework/Application/SlateApplication.h" #include "ObjectTools.h" @@ -179,6 +181,18 @@ void SReferenceViewer::Construct(const FArguments& InArgs) .BorderImage( FEditorStyle::GetBrush("ToolPanel.GroupBorder") ) [ SNew(SVerticalBox) + +SVerticalBox::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Center) + .Padding(2.f) + .AutoHeight() + [ + SAssignNew(SearchBox, SSearchBox) + .HintText(LOCTEXT("Search", "Search...")) + .ToolTipText(LOCTEXT("SearchTooltip", "Type here to search (pressing Enter zooms to the results)")) + .OnTextChanged(this, &SReferenceViewer::HandleOnSearchTextChanged) + .OnTextCommitted(this, &SReferenceViewer::HandleOnSearchTextCommitted) + ] +SVerticalBox::Slot() .AutoHeight() @@ -898,6 +912,15 @@ void SReferenceViewer::RegisterActions() ReferenceViewerActions = MakeShareable(new FUICommandList); FAssetManagerEditorCommands::Register(); + ReferenceViewerActions->MapAction( + FAssetManagerEditorCommands::Get().ZoomToFit, + FExecuteAction::CreateSP(this, &SReferenceViewer::ZoomToFit), + FCanExecuteAction::CreateSP(this, &SReferenceViewer::CanZoomToFit)); + + ReferenceViewerActions->MapAction( + FAssetManagerEditorCommands::Get().Find, + FExecuteAction::CreateSP(this, &SReferenceViewer::OnFind)); + ReferenceViewerActions->MapAction( FGlobalEditorCommonCommands::Get().FindInContentBrowser, FExecuteAction::CreateSP(this, &SReferenceViewer::ShowSelectionInContentBrowser), @@ -1427,4 +1450,91 @@ void SReferenceViewer::OnInitialAssetRegistrySearchComplete() } } +void SReferenceViewer::ZoomToFit() +{ + if (GraphEditorPtr.IsValid()) + { + GraphEditorPtr->ZoomToFit(true); + } +} + +bool SReferenceViewer::CanZoomToFit() const +{ + if (GraphEditorPtr.IsValid()) + { + return true; + } + + return false; +} + +void SReferenceViewer::OnFind() +{ + FSlateApplication::Get().SetKeyboardFocus(SearchBox, EFocusCause::SetDirectly); +} + +void SReferenceViewer::HandleOnSearchTextChanged(const FText& SearchText) +{ + if (GraphObj == nullptr || !GraphEditorPtr.IsValid()) + { + return; + } + + GraphEditorPtr->ClearSelectionSet(); + + if (SearchText.IsEmpty()) + { + return; + } + + FString SearchString = SearchText.ToString(); + TArray SearchWords; + SearchString.ParseIntoArrayWS( SearchWords ); + + TArray AllNodes; + GraphObj->GetNodesOfClass( AllNodes ); + + TArray NodePackageNames; + for (UEdGraphNode_Reference* Node : AllNodes) + { + NodePackageNames.Empty(); + Node->GetAllPackageNames(NodePackageNames); + + for (const FName& PackageName : NodePackageNames) + { + // package name must match all words + bool bMatch = true; + for (const FString& Word : SearchWords) + { + if (!PackageName.ToString().Contains(Word)) + { + bMatch = false; + break; + } + } + + if (bMatch) + { + GraphEditorPtr->SetNodeSelection(Node, true); + break; + } + } + } +} + +void SReferenceViewer::HandleOnSearchTextCommitted(const FText& SearchText, ETextCommit::Type CommitType) +{ + if (!GraphEditorPtr.IsValid()) + { + return; + } + + if (CommitType == ETextCommit::OnCleared) + { + GraphEditorPtr->ClearSelectionSet(); + } + + GraphEditorPtr->ZoomToFit(true); +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.h b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.h index a909a1991f60..93e651dbb98e 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.h +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.h @@ -6,6 +6,7 @@ #include "Input/Reply.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SCompoundWidget.h" +#include "Widgets/Input/SSearchBox.h" #include "GraphEditor.h" #include "AssetData.h" #include "HistoryManager.h" @@ -130,6 +131,13 @@ private: void ShowReferenceTree(); void ViewSizeMap(); void ViewAssetAudit(); + void ZoomToFit(); + bool CanZoomToFit() const; + void OnFind(); + + /** Handlers for searching */ + void HandleOnSearchTextChanged(const FText& SearchText); + void HandleOnSearchTextCommitted(const FText& SearchText, ETextCommit::Type CommitType); void ReCenterGraphOnNodes(const TSet& Nodes); @@ -153,6 +161,7 @@ private: TSharedPtr GraphEditorPtr; TSharedPtr ReferenceViewerActions; + TSharedPtr SearchBox; UEdGraph_ReferenceViewer* GraphObj; diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/SAssetAuditBrowser.cpp b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/SAssetAuditBrowser.cpp index f077d9f3ffc4..1da3c3fb69fc 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/SAssetAuditBrowser.cpp +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/SAssetAuditBrowser.cpp @@ -619,7 +619,7 @@ TSharedRef SAssetAuditBrowser::CreateHistoryMenu(bool bInBackHistory) c MenuBuilder.AddMenuEntry(DisplayName, DisplayName, FSlateIcon(), FUIAction( - FExecuteAction::CreateRaw(this, &SAssetAuditBrowser::GoToHistoryIndex, HistoryIdx) + FExecuteAction::CreateRaw(const_cast(this), &SAssetAuditBrowser::GoToHistoryIndex, HistoryIdx) ), NAME_None, EUserInterfaceActionType::Button); --HistoryIdx; @@ -634,7 +634,7 @@ TSharedRef SAssetAuditBrowser::CreateHistoryMenu(bool bInBackHistory) c MenuBuilder.AddMenuEntry(DisplayName, DisplayName, FSlateIcon(), FUIAction( - FExecuteAction::CreateRaw(this, &SAssetAuditBrowser::GoToHistoryIndex, HistoryIdx) + FExecuteAction::CreateRaw(const_cast(this), &SAssetAuditBrowser::GoToHistoryIndex, HistoryIdx) ), NAME_None, EUserInterfaceActionType::Button); ++HistoryIdx; diff --git a/Engine/Plugins/Editor/DataValidation/Source/DataValidation/DataValidation.Build.cs b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/DataValidation.Build.cs index 4389fe6203bf..015e50808d72 100644 --- a/Engine/Plugins/Editor/DataValidation/Source/DataValidation/DataValidation.Build.cs +++ b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/DataValidation.Build.cs @@ -11,7 +11,8 @@ public class DataValidation : ModuleRules "Core", "CoreUObject", "Engine", - "TargetPlatform" + "TargetPlatform", + "EditorSubsystem" } ); // goto through these one by one and remove extra ones @@ -32,8 +33,9 @@ public class DataValidation : ModuleRules "GraphEditor", "BlueprintGraph", "KismetCompiler", - "SandboxFile" - } + "SandboxFile", + "Blutility" + } ); } } diff --git a/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/DataValidationCommandlet.cpp b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/DataValidationCommandlet.cpp index ae44ff12a3b5..264697faed80 100644 --- a/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/DataValidationCommandlet.cpp +++ b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/DataValidationCommandlet.cpp @@ -1,11 +1,11 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "DataValidationCommandlet.h" -#include "DataValidationManager.h" - #include "IAssetRegistry.h" #include "AssetRegistryHelpers.h" #include "AssetRegistryModule.h" +#include "Editor.h" +#include "EditorValidatorSubsystem.h" DEFINE_LOG_CATEGORY_STATIC(LogDataValidation, Warning, All); @@ -40,10 +40,10 @@ bool UDataValidationCommandlet::ValidateData() TArray AssetDataList; AssetRegistryModule.Get().GetAllAssets(AssetDataList); - UDataValidationManager* DataValidationManager = UDataValidationManager::Get(); - check(DataValidationManager); + UEditorValidatorSubsystem* EditorValidationSubsystem = GEditor->GetEditorSubsystem(); + check(EditorValidationSubsystem); - DataValidationManager->ValidateAssets(AssetDataList); + EditorValidationSubsystem->ValidateAssets(AssetDataList); return true; } diff --git a/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/DataValidationManager.cpp b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/DataValidationManager.cpp index 058d953cfeeb..2d5e016255e4 100644 --- a/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/DataValidationManager.cpp +++ b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/DataValidationManager.cpp @@ -13,39 +13,41 @@ #define LOCTEXT_NAMESPACE "DataValidationManager" -UDataValidationManager* GDataValidationManager = nullptr; +UDEPRECATED_DataValidationManager* GDataValidationManager = nullptr; /** - * UDataValidationManager + * UDEPRECATED_DataValidationManager */ -UDataValidationManager::UDataValidationManager(const FObjectInitializer& ObjectInitializer) +UDEPRECATED_DataValidationManager::UDEPRECATED_DataValidationManager(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { DataValidationManagerClassName = FSoftClassPath(TEXT("/Script/DataValidation.DataValidationManager")); bValidateOnSave = true; } -UDataValidationManager* UDataValidationManager::Get() +UDEPRECATED_DataValidationManager* UDEPRECATED_DataValidationManager::Get() { if (GDataValidationManager == nullptr) { - FSoftClassPath DataValidationManagerClassName = (UDataValidationManager::StaticClass()->GetDefaultObject())->DataValidationManagerClassName; + FSoftClassPath DataValidationManagerClassName = (UDEPRECATED_DataValidationManager::StaticClass()->GetDefaultObject())->DataValidationManagerClassName; UClass* SingletonClass = DataValidationManagerClassName.TryLoadClass(); checkf(SingletonClass != nullptr, TEXT("Data validation config value DataValidationManagerClassName is not a valid class name.")); - GDataValidationManager = NewObject(GetTransientPackage(), SingletonClass, NAME_None); + GDataValidationManager = NewObject(GetTransientPackage(), SingletonClass, NAME_None); checkf(GDataValidationManager != nullptr, TEXT("Data validation config value DataValidationManagerClassName is not a subclass of UDataValidationManager.")) GDataValidationManager->AddToRoot(); + PRAGMA_DISABLE_DEPRECATION_WARNINGS GDataValidationManager->Initialize(); + PRAGMA_ENABLE_DEPRECATION_WARNINGS } return GDataValidationManager; } -void UDataValidationManager::Initialize() +void UDEPRECATED_DataValidationManager::Initialize() { FMessageLogInitializationOptions InitOptions; InitOptions.bShowFilters = true; @@ -54,11 +56,11 @@ void UDataValidationManager::Initialize() MessageLogModule.RegisterLogListing("DataValidation", LOCTEXT("DataValidation", "Data Validation"), InitOptions); } -UDataValidationManager::~UDataValidationManager() +UDEPRECATED_DataValidationManager::~UDEPRECATED_DataValidationManager() { } -EDataValidationResult UDataValidationManager::IsObjectValid(UObject* InObject, TArray& ValidationErrors) const +EDataValidationResult UDEPRECATED_DataValidationManager::IsObjectValid(UObject* InObject, TArray& ValidationErrors) const { if (ensure(InObject)) { @@ -68,21 +70,23 @@ EDataValidationResult UDataValidationManager::IsObjectValid(UObject* InObject, T return EDataValidationResult::NotValidated; } -EDataValidationResult UDataValidationManager::IsAssetValid(FAssetData& AssetData, TArray& ValidationErrors) const +EDataValidationResult UDEPRECATED_DataValidationManager::IsAssetValid(FAssetData& AssetData, TArray& ValidationErrors) const { if (AssetData.IsValid()) { UObject* Obj = AssetData.GetAsset(); if (Obj) { + PRAGMA_DISABLE_DEPRECATION_WARNINGS return IsObjectValid(Obj, ValidationErrors); + PRAGMA_ENABLE_DEPRECATION_WARNINGS } } return EDataValidationResult::Invalid; } -int32 UDataValidationManager::ValidateAssets(TArray AssetDataList, bool bSkipExcludedDirectories, bool bShowIfNoFailures) const +int32 UDEPRECATED_DataValidationManager::ValidateAssets(TArray AssetDataList, bool bSkipExcludedDirectories, bool bShowIfNoFailures) const { FScopedSlowTask SlowTask(1.0f, LOCTEXT("ValidatingDataTask", "Validating Data...")); SlowTask.Visibility = bShowIfNoFailures ? ESlowTaskVisibility::ForceVisible : ESlowTaskVisibility::Invisible; @@ -109,14 +113,18 @@ int32 UDataValidationManager::ValidateAssets(TArray AssetDataList, b SlowTask.EnterProgressFrame(1.0f / NumFilesToValidate, FText::Format(LOCTEXT("ValidatingFilename", "Validating {0}"), FText::FromString(Data.GetFullName()))); // Check exclusion path + PRAGMA_DISABLE_DEPRECATION_WARNINGS if (bSkipExcludedDirectories && IsPathExcludedFromValidation(Data.PackageName.ToString())) { ++NumFilesSkipped; continue; } + PRAGMA_ENABLE_DEPRECATION_WARNINGS TArray ValidationErrors; + PRAGMA_DISABLE_DEPRECATION_WARNINGS EDataValidationResult Result = IsAssetValid(Data, ValidationErrors); + PRAGMA_ENABLE_DEPRECATION_WARNINGS ++NumFilesChecked; for (const FText& ErrorMsg : ValidationErrors) @@ -170,7 +178,7 @@ int32 UDataValidationManager::ValidateAssets(TArray AssetDataList, b return NumInvalidFiles; } -void UDataValidationManager::ValidateOnSave(TArray AssetDataList) const +void UDEPRECATED_DataValidationManager::ValidateOnSave(TArray AssetDataList) const { // Only validate if enabled and not auto saving if (!bValidateOnSave || GEditor->IsAutosaving()) @@ -179,6 +187,7 @@ void UDataValidationManager::ValidateOnSave(TArray AssetDataList) co } FMessageLog DataValidationLog("DataValidation"); + PRAGMA_DISABLE_DEPRECATION_WARNINGS if (ValidateAssets(AssetDataList, true, false) > 0) { const FText ErrorMessageNotification = FText::Format( @@ -186,9 +195,10 @@ void UDataValidationManager::ValidateOnSave(TArray AssetDataList) co AssetDataList.Num() == 1 ? FText::FromName(AssetDataList[0].AssetName) : LOCTEXT("MultipleErrors", "multiple assets")); DataValidationLog.Notify(ErrorMessageNotification, EMessageSeverity::Warning, /*bForce=*/ true); } + PRAGMA_ENABLE_DEPRECATION_WARNINGS } -void UDataValidationManager::ValidateSavedPackage(FName PackageName) +void UDEPRECATED_DataValidationManager::ValidateSavedPackage(FName PackageName) { // Only validate if enabled and not auto saving if (!bValidateOnSave || GEditor->IsAutosaving()) @@ -198,10 +208,12 @@ void UDataValidationManager::ValidateSavedPackage(FName PackageName) SavedPackagesToValidate.AddUnique(PackageName); - GEditor->GetTimerManager()->SetTimerForNextTick(this, &UDataValidationManager::ValidateAllSavedPackages); + PRAGMA_DISABLE_DEPRECATION_WARNINGS + GEditor->GetTimerManager()->SetTimerForNextTick(this, &UDEPRECATED_DataValidationManager::ValidateAllSavedPackages); + PRAGMA_ENABLE_DEPRECATION_WARNINGS } -bool UDataValidationManager::IsPathExcludedFromValidation(const FString& Path) const +bool UDEPRECATED_DataValidationManager::IsPathExcludedFromValidation(const FString& Path) const { for (const FDirectoryPath& ExcludedPath : ExcludedDirectories) { @@ -214,7 +226,7 @@ bool UDataValidationManager::IsPathExcludedFromValidation(const FString& Path) c return false; } -void UDataValidationManager::ValidateAllSavedPackages() +void UDEPRECATED_DataValidationManager::ValidateAllSavedPackages() { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); @@ -246,8 +258,9 @@ void UDataValidationManager::ValidateAllSavedPackages() AssetRegistry.GetAssetsByPackageName(PackageName, Assets); } + PRAGMA_DISABLE_DEPRECATION_WARNINGS ValidateOnSave(Assets); - + PRAGMA_ENABLE_DEPRECATION_WARNINGS SavedPackagesToValidate.Empty(); } diff --git a/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/DataValidationModule.cpp b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/DataValidationModule.cpp index 9047c352ef02..c655c0b5b5f6 100644 --- a/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/DataValidationModule.cpp +++ b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/DataValidationModule.cpp @@ -1,7 +1,6 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "DataValidationModule.h" -#include "DataValidationManager.h" #include "UObject/Object.h" #include "UObject/SoftObjectPath.h" #include "GameFramework/HUD.h" @@ -23,6 +22,7 @@ #include "WorkspaceMenuStructure.h" #include "WorkspaceMenuStructureModule.h" +#include "EditorValidatorSubsystem.h" #define LOCTEXT_NAMESPACE "DataValidationModule" @@ -205,10 +205,10 @@ void FDataValidationModule::ValidateAssets(TArray SelectedAssets, bo SelectedAssets = DependentAssets.Array(); } - UDataValidationManager* DataValidationManager = UDataValidationManager::Get(); - if (DataValidationManager) + UEditorValidatorSubsystem* EditorValidationSubsystem = GEditor->GetEditorSubsystem(); + if (EditorValidationSubsystem) { - DataValidationManager->ValidateAssets(SelectedAssets, false); + EditorValidationSubsystem->ValidateAssets(SelectedAssets, false); } } @@ -292,10 +292,10 @@ void FDataValidationModule::CreateDataValidationContentBrowserPathMenu(FMenuBuil void FDataValidationModule::OnPackageSaved(const FString& PackageFileName, UObject* PackageObj) { - UDataValidationManager* DataValidationManager = UDataValidationManager::Get(); - if (DataValidationManager && PackageObj) + UEditorValidatorSubsystem* EditorValidationSubsystem = GEditor->GetEditorSubsystem(); + if (EditorValidationSubsystem && PackageObj) { - DataValidationManager->ValidateSavedPackage(PackageObj->GetFName()); + EditorValidationSubsystem->ValidateSavedPackage(PackageObj->GetFName()); } } diff --git a/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/EditorValidatorBase.cpp b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/EditorValidatorBase.cpp new file mode 100644 index 000000000000..f9cedd911a58 --- /dev/null +++ b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/EditorValidatorBase.cpp @@ -0,0 +1,91 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "EditorValidatorBase.h" + +#include "Editor.h" +#include "AssetToolsModule.h" +#include "ObjectTools.h" +#include "Dialogs/Dialogs.h" +#include "EditorValidatorSubsystem.h" +#include "Logging/MessageLog.h" +#include "Misc/UObjectToken.h" +#include "Logging/TokenizedMessage.h" + +DEFINE_LOG_CATEGORY_STATIC(LogContentValidation, Log, Log); +#define LOCTEXT_NAMESPACE "AssetValidation" + +UEditorValidatorBase::UEditorValidatorBase() + : Super() +{ + bIsEnabled = true; +} + +bool UEditorValidatorBase::CanValidateAsset_Implementation(UObject* InAsset) const +{ + return false; +} + +EDataValidationResult UEditorValidatorBase::ValidateLoadedAsset_Implementation(UObject* InAsset, TArray& ValidationErrors) +{ + return EDataValidationResult::NotValidated; +} + +void UEditorValidatorBase::AssetFails(UObject* InAsset, const FText& InMessage, TArray& ValidationErrors) +{ + FFormatNamedArguments Arguments; + if (InAsset) + { + Arguments.Add(TEXT("AssetName"), FText::FromName(InAsset->GetFName())); + } + Arguments.Add(TEXT("CustomMessage"), InMessage); + Arguments.Add(TEXT("ValidatorName"), FText::FromString(GetClass()->GetName())); + + FText FailureMessage = FText::Format(LOCTEXT("AssetCheck_Message_Display", "{AssetName} failed: {CustomMessage}. ({ValidatorName})"), Arguments); + + if(LogContentValidation.GetVerbosity() >= ELogVerbosity::Verbose) + { + LogElapsedTime(Arguments); + + } + + ValidationErrors.Add(FailureMessage); + ValidationResult = EDataValidationResult::Invalid; +} + +void UEditorValidatorBase::LogElapsedTime(FFormatNamedArguments &Arguments) +{ + FDateTime CurrentTime = FDateTime::Now(); + FTimespan ElapsedTimeSpan = (CurrentTime - ValidationTime); + float ElapsedTimeMS = ElapsedTimeSpan.GetTotalMilliseconds(); + FNumberFormattingOptions TimeFormat; + TimeFormat.MinimumFractionalDigits = 5; + Arguments.Add(TEXT("ElapsedTime"), FText::AsNumber(ElapsedTimeMS, &TimeFormat)); + FText ElapsedTimeMessage = FText::Format(LOCTEXT("ElapsedTime", "Checking {AssetName} with {ValidatorName} took {ElapsedTime} ms."), Arguments); + UE_LOG(LogContentValidation, Verbose, TEXT("%s"), *ElapsedTimeMessage.ToString()); +} + +void UEditorValidatorBase::AssetPasses(UObject* InAsset) +{ + + if (LogContentValidation.GetVerbosity() >= ELogVerbosity::Verbose) + { + FFormatNamedArguments Arguments; + if (InAsset) + { + Arguments.Add(TEXT("AssetName"), FText::FromName(InAsset->GetFName())); + } + Arguments.Add(TEXT("ValidatorName"), FText::FromString(GetClass()->GetName())); + + LogElapsedTime(Arguments); + } + + ValidationResult = EDataValidationResult::Valid; +} + +void UEditorValidatorBase::ResetValidationState() +{ + ValidationResult = EDataValidationResult::NotValidated; + ValidationTime = FDateTime::Now(); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/EditorValidatorSubsystem.cpp b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/EditorValidatorSubsystem.cpp new file mode 100644 index 000000000000..11e87c243c17 --- /dev/null +++ b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Private/EditorValidatorSubsystem.cpp @@ -0,0 +1,360 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "EditorValidatorSubsystem.h" + +#include "Editor.h" +#include "AssetToolsModule.h" +#include "ObjectTools.h" +#include "AssetRegistryModule.h" +#include "EditorUtilityBlueprint.h" +#include "Settings/EditorLoadingSavingSettings.h" +#include "UnrealEdGlobals.h" +#include "Editor/UnrealEdEngine.h" +#include "CookOnTheSide/CookOnTheFlyServer.h" +#include "Logging/MessageLog.h" +#include "Misc/ScopedSlowTask.h" +#include "AssetData.h" + +#define LOCTEXT_NAMESPACE "EditorValidationSubsystem" + +UEditorValidatorSubsystem::UEditorValidatorSubsystem() + : UEditorSubsystem() +{ + bAllowBlueprintValidators = true; + bValidateOnSave = true; +} + +void UEditorValidatorSubsystem::Initialize(FSubsystemCollectionBase& Collection) +{ + const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + if (!AssetRegistryModule.Get().IsLoadingAssets()) + { + RegisterBlueprintValidators(); + } + else + { + // We are still discovering assets, listen for the completion delegate before building the graph + if (!AssetRegistryModule.Get().OnFilesLoaded().IsBoundToObject(this)) + { + AssetRegistryModule.Get().OnFilesLoaded().AddUObject(this, &UEditorValidatorSubsystem::RegisterBlueprintValidators); + } + } + + // C++ registration + TArray ValidatorClasses; + GetDerivedClasses(UEditorValidatorBase::StaticClass(), ValidatorClasses); + for (UClass* ValidatorClass : ValidatorClasses) + { + if (!ValidatorClass->HasAllClassFlags(CLASS_Abstract)) + { + UPackage* const ClassPackage = ValidatorClass->GetOuterUPackage(); + if (ClassPackage) + { + const FName ModuleName = FPackageName::GetShortFName(ClassPackage->GetFName()); + if (FModuleManager::Get().IsModuleLoaded(ModuleName)) + { + UEditorValidatorBase* Validator = NewObject(GetTransientPackage(), ValidatorClass); + AddValidator(Validator); + } + } + } + } +} + +// Rename to BP validators +void UEditorValidatorSubsystem::RegisterBlueprintValidators() +{ + if (bAllowBlueprintValidators) + { + // Locate all validators (include unloaded) + const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + TArray AllBPsAssetData; + AssetRegistryModule.Get().GetAssetsByClass(UEditorUtilityBlueprint::StaticClass()->GetFName(), AllBPsAssetData, true); + + for (FAssetData& BPAssetData : AllBPsAssetData) + { + UClass* ParentClass = nullptr; + FString ParentClassName; + if (!BPAssetData.GetTagValue(FBlueprintTags::NativeParentClassPath, ParentClassName)) + { + BPAssetData.GetTagValue(FBlueprintTags::ParentClassPath, ParentClassName); + } + + if (!ParentClassName.IsEmpty()) + { + UObject* Outer = nullptr; + ResolveName(Outer, ParentClassName, false, false); + ParentClass = FindObject(ANY_PACKAGE, *ParentClassName); + if (!ParentClass->IsChildOf(UEditorValidatorBase::StaticClass())) + { + continue; + } + } + + // If this object isn't currently loaded, load it + UObject* ValidatorObject = BPAssetData.ToSoftObjectPath().ResolveObject(); + if (ValidatorObject == nullptr) + { + ValidatorObject = BPAssetData.ToSoftObjectPath().TryLoad(); + } + if (ValidatorObject) + { + UEditorUtilityBlueprint* ValidatorBlueprint = Cast(ValidatorObject); + UEditorValidatorBase* Validator = NewObject(GetTransientPackage(), ValidatorBlueprint->GeneratedClass); + AddValidator(Validator); + } + } + } +} + +void UEditorValidatorSubsystem::Deinitialize() +{ + CleanupValidators(); + + Super::Deinitialize(); +} + +void UEditorValidatorSubsystem::AddValidator(UEditorValidatorBase* InValidator) +{ + if (InValidator) + { + Validators.Add(InValidator->GetClass(), InValidator); + } +} + +void UEditorValidatorSubsystem::CleanupValidators() +{ + Validators.Empty(); +} + +EDataValidationResult UEditorValidatorSubsystem::IsObjectValid(UObject* InObject, TArray& ValidationErrors) const +{ + EDataValidationResult Result = EDataValidationResult::NotValidated; + + if (ensure(InObject)) + { + // First check the class level validation + Result = InObject->IsDataValid(ValidationErrors); + // If the asset is still valid or there wasn't a class-level validation, keep validating with custom validators + if (Result != EDataValidationResult::Invalid) + { + for (TPair ValidatorPair : Validators) + { + if (ValidatorPair.Value && ValidatorPair.Value->IsEnabled() && ValidatorPair.Value->CanValidateAsset(InObject)) + { + ValidatorPair.Value->ResetValidationState(); + EDataValidationResult NewResult = ValidatorPair.Value->ValidateLoadedAsset(InObject, ValidationErrors); + + // Don't accidentally overwrite an invalid result with a valid or not-validated one + if(Result != EDataValidationResult::Invalid) + { + Result = NewResult; + } + + ensureMsgf(ValidatorPair.Value->IsValidationStateSet(), TEXT("Validator %s did not include a pass or fail state."), *ValidatorPair.Value->GetClass()->GetName()); + } + } + } + } + + return Result; +} + +EDataValidationResult UEditorValidatorSubsystem::IsAssetValid(FAssetData& AssetData, TArray& ValidationErrors) const +{ + if (AssetData.IsValid()) + { + UObject* Obj = AssetData.GetAsset(); + if (Obj) + { + return IsObjectValid(Obj, ValidationErrors); + } + return EDataValidationResult::NotValidated; + } + + return EDataValidationResult::Invalid; +} + +int32 UEditorValidatorSubsystem::ValidateAssets(TArray AssetDataList, bool bSkipExcludedDirectories, bool bShowIfNoFailures) const +{ + FScopedSlowTask SlowTask(1.0f, LOCTEXT("ValidatingDataTask", "Validating Data...")); + SlowTask.Visibility = bShowIfNoFailures ? ESlowTaskVisibility::ForceVisible : ESlowTaskVisibility::Invisible; + if (bShowIfNoFailures) + { + SlowTask.MakeDialogDelayed(.1f); + } + + FMessageLog DataValidationLog("AssetCheck"); + + int32 NumAdded = 0; + + int32 NumFilesChecked = 0; + int32 NumValidFiles = 0; + int32 NumInvalidFiles = 0; + int32 NumFilesSkipped = 0; + int32 NumFilesUnableToValidate = 0; + + int32 NumFilesToValidate = AssetDataList.Num(); + + // Now add to map or update as needed + for (FAssetData& Data : AssetDataList) + { + SlowTask.EnterProgressFrame(1.0f / NumFilesToValidate, FText::Format(LOCTEXT("ValidatingFilename", "Validating {0}"), FText::FromString(Data.GetFullName()))); + + // Check exclusion path + if (bSkipExcludedDirectories && IsPathExcludedFromValidation(Data.PackageName.ToString())) + { + ++NumFilesSkipped; + continue; + } + + TArray ValidationErrors; + EDataValidationResult Result = IsAssetValid(Data, ValidationErrors); + ++NumFilesChecked; + + for (const FText& ErrorMsg : ValidationErrors) + { + DataValidationLog.Error()->AddToken(FTextToken::Create(ErrorMsg)); + } + + if (Result == EDataValidationResult::Valid) + { + ++NumValidFiles; + } + else + { + if (Result == EDataValidationResult::Invalid) + { + DataValidationLog.Error()->AddToken(FAssetNameToken::Create(Data.PackageName.ToString())) + ->AddToken(FTextToken::Create(LOCTEXT("InvalidDataResult", "contains invalid data."))); + ++NumInvalidFiles; + } + else if (Result == EDataValidationResult::NotValidated) + { + if (bShowIfNoFailures) + { + DataValidationLog.Info()->AddToken(FAssetNameToken::Create(Data.PackageName.ToString())) + ->AddToken(FTextToken::Create(LOCTEXT("NotValidatedDataResult", "has no data validation."))); + } + ++NumFilesUnableToValidate; + } + } + } + + const bool bFailed = (NumInvalidFiles > 0); + + if (bFailed || bShowIfNoFailures) + { + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("Result"), bFailed ? LOCTEXT("Failed", "FAILED") : LOCTEXT("Succeeded", "SUCCEEDED")); + Arguments.Add(TEXT("NumChecked"), NumFilesChecked); + Arguments.Add(TEXT("NumValid"), NumValidFiles); + Arguments.Add(TEXT("NumInvalid"), NumInvalidFiles); + Arguments.Add(TEXT("NumSkipped"), NumFilesSkipped); + Arguments.Add(TEXT("NumUnableToValidate"), NumFilesUnableToValidate); + + TSharedRef ValidationLog = bFailed ? DataValidationLog.Error() : DataValidationLog.Info(); + ValidationLog->AddToken(FTextToken::Create(FText::Format(LOCTEXT("SuccessOrFailure", "Data validation {Result}."), Arguments))); + ValidationLog->AddToken(FTextToken::Create(FText::Format(LOCTEXT("ResultsSummary", "Files Checked: {NumChecked}, Passed: {NumValid}, Failed: {NumInvalid}, Skipped: {NumSkipped}, Unable to validate: {NumUnableToValidate}"), Arguments))); + + DataValidationLog.Open(EMessageSeverity::Info, true); + } + + return NumInvalidFiles; +} + +void UEditorValidatorSubsystem::ValidateOnSave(TArray AssetDataList) const +{ + // Only validate if enabled and not auto saving + if (!bValidateOnSave || GEditor->IsAutosaving()) + { + return; + } + + bool bIsCooking = GIsCookerLoadingPackage; + if ((!bValidateAssetsWhileSavingForCook && bIsCooking)) + { + return; + } + + FMessageLog DataValidationLog("DataValidation"); + if (ValidateAssets(AssetDataList, true, false) > 0) + { + const FText ErrorMessageNotification = FText::Format( + LOCTEXT("ValidationFailureNotification", "Validation failed when saving {0}, check Data Validation log"), + AssetDataList.Num() == 1 ? FText::FromName(AssetDataList[0].AssetName) : LOCTEXT("MultipleErrors", "multiple assets")); + DataValidationLog.Notify(ErrorMessageNotification, EMessageSeverity::Warning, /*bForce=*/ true); + } +} + +void UEditorValidatorSubsystem::ValidateSavedPackage(FName PackageName) +{ + // Only validate if enabled and not auto saving + if (!bValidateOnSave || GEditor->IsAutosaving()) + { + return; + } + + // For performance reasons, don't validate when cooking by default. Assumption is we validated when saving previously. + bool bIsCooking = GIsCookerLoadingPackage; + if ((!bValidateAssetsWhileSavingForCook && bIsCooking)) + { + return; + } + + SavedPackagesToValidate.AddUnique(PackageName); + + GEditor->GetTimerManager()->SetTimerForNextTick(this, &UEditorValidatorSubsystem::ValidateAllSavedPackages); +} + +bool UEditorValidatorSubsystem::IsPathExcludedFromValidation(const FString& Path) const +{ + for (const FDirectoryPath& ExcludedPath : ExcludedDirectories) + { + if (Path.Contains(ExcludedPath.Path)) + { + return true; + } + } + + return false; +} + +void UEditorValidatorSubsystem::ValidateAllSavedPackages() +{ + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); + + // Prior to validation, make sure Asset Registry is updated. + // DirectoryWatcher is responsible of scanning modified asset files, but validation can be called before. + if (SavedPackagesToValidate.Num()) + { + TArray FilesToScan; + FilesToScan.Reserve(SavedPackagesToValidate.Num()); + for (FName PackageName : SavedPackagesToValidate) + { + FString PackageFilename; + if (FPackageName::FindPackageFileWithoutExtension(FPackageName::LongPackageNameToFilename(PackageName.ToString()), PackageFilename)) + { + FilesToScan.Add(PackageFilename); + } + } + if (FilesToScan.Num()) + { + AssetRegistry.ScanModifiedAssetFiles(FilesToScan); + } + } + + TArray Assets; + for (FName PackageName : SavedPackagesToValidate) + { + // We need to query the in-memory data as the disk cache may not be accurate + AssetRegistry.GetAssetsByPackageName(PackageName, Assets); + } + + ValidateOnSave(Assets); + + SavedPackagesToValidate.Empty(); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Public/DataValidationManager.h b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Public/DataValidationManager.h index e7d1fb926e51..dd8da0e989ea 100644 --- a/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Public/DataValidationManager.h +++ b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Public/DataValidationManager.h @@ -18,8 +18,8 @@ class UUnitTest; * and some misc tasks like local log hooking */ -UCLASS(config=Editor) -class DATAVALIDATION_API UDataValidationManager : public UObject +UCLASS(config=Editor, Deprecated) +class DATAVALIDATION_API UDEPRECATED_DataValidationManager : public UObject { GENERATED_UCLASS_BODY() @@ -29,26 +29,30 @@ public: * * @return Returns the data validation manager */ - static UDataValidationManager* Get(); + UE_DEPRECATED(4.23, "Use GEditor->GetEditorSubsystem() and use the functions on the subsystem instead.") + static UDEPRECATED_DataValidationManager* Get(); /** * Initialize the data validation manager */ + UE_DEPRECATED(4.23, "Use GEditor->GetEditorSubsystem() and use the functions on the subsystem instead.") virtual void Initialize(); /** * Destructor for handling removal of log registration */ - virtual ~UDataValidationManager() override; + virtual ~UDEPRECATED_DataValidationManager() override; /** * @return Returns Valid if the object contains valid data; returns Invalid if the object contains invalid data; returns NotValidated if no validations was performed on the object */ + UE_DEPRECATED(4.23, "Use GEditor->GetEditorSubsystem() and use IsObjectValid on the subsystem instead.") virtual EDataValidationResult IsObjectValid(UObject* InObject, TArray& ValidationErrors) const; /** * @return Returns Valid if the object pointed to by AssetData contains valid data; returns Invalid if the object contains invalid data or does not exist; returns NotValidated if no validations was performed on the object */ + UE_DEPRECATED(4.23, "Use GEditor->GetEditorSubsystem() and use IsAssetValid on the subsystem instead.") virtual EDataValidationResult IsAssetValid(FAssetData& AssetData, TArray& ValidationErrors) const; /** @@ -57,39 +61,44 @@ public: * @param bShowIfNoFailures If true, will add notifications for files with no validation and display even if everything passes * @returns Number of assets with validation failures */ + UE_DEPRECATED(4.23, "Use GEditor->GetEditorSubsystem() and use IsAssetValid on the subsystem instead.") virtual int32 ValidateAssets(TArray AssetDataList, bool bSkipExcludedDirectories = true, bool bShowIfNoFailures = true) const; /** * Called to validate from an interactive save */ + UE_DEPRECATED(4.23, "Use GEditor->GetEditorSubsystem() and use IsAssetValid on the subsystem instead.") virtual void ValidateOnSave(TArray AssetDataList) const; /** * Schedule a validation of a saved package, this will activate next frame by default so it can combine them */ + UE_DEPRECATED(4.23, "Use GEditor->GetEditorSubsystem() and use ValidateSavedPackage on the subsystem instead.") virtual void ValidateSavedPackage(FName PackageName); protected: /** * @return Returns true if the current Path should be skipped for validation. Returns false otherwise. */ + UE_DEPRECATED(4.23, "Use GEditor->GetEditorSubsystem() and use IsPathExcludedFromValidation on the subsystem instead.") virtual bool IsPathExcludedFromValidation(const FString& Path) const; /** * Handles validating all pending save packages */ + UE_DEPRECATED(4.23, "Use GEditor->GetEditorSubsystem() and use ValidateAllSavedPackages on the subsystem instead.") void ValidateAllSavedPackages(); /** * Directories to ignore for data validation. Useful for test assets */ - UPROPERTY(config) + UPROPERTY(config, meta = (Deprecated, DeprecationMessage = "UDataValidationManager's ExcludedDirectories is deprecated, use UEditorValidatorSubsystem's ExcludedDirectories instead.")) TArray ExcludedDirectories; /** * Rather it should validate assets on save inside the editor */ - UPROPERTY(config) + UPROPERTY(config, meta = (Deprecated, DeprecationMessage = "UDataValidationManager's bValidateOnSave is deprecated, use UEditorValidatorSubsystem's bValidateOnSave instead.")) bool bValidateOnSave; /** List of saved package names to validate next frame */ diff --git a/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Public/EditorValidatorBase.h b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Public/EditorValidatorBase.h new file mode 100644 index 000000000000..d6521f96645b --- /dev/null +++ b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Public/EditorValidatorBase.h @@ -0,0 +1,81 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "EditorSubsystem.h" + +#include "UObject/ObjectMacros.h" +#include "UObject/UObjectGlobals.h" +#include "Templates/SubclassOf.h" +#include "UObject/Interface.h" +#include "UObject/ObjectMacros.h" +#include "Logging/LogVerbosity.h" +#include "Internationalization/Text.h" +#include "UObject/TextProperty.h" +#include "EditorValidatorBase.generated.h" + +/* +* The EditorValidatorBase is a class which verifies that an asset meets a specific ruleset. +* It should be used when checking engine-level classes, as UObject::IsDataValid requires +* overriding the base class. You can create project-specific version of the validator base, +* with custom logging and enabled logic. +* +* C++ and Blueprint validators will be gathered on editor start, while python validators need +* to register themselves +*/ +UCLASS(Abstract, Blueprintable, meta = (ShowWorldContextPin)) +class DATAVALIDATION_API UEditorValidatorBase : public UObject +{ + GENERATED_BODY() + +public: + UEditorValidatorBase(); + + /** Override this to determine whether or not you can validate a given asset with this validator */ + UFUNCTION(BlueprintNativeEvent, Category = "Asset Validation") + bool CanValidateAsset(UObject* InAsset) const; + + UFUNCTION(BlueprintNativeEvent, Category = "Asset Validation") + EDataValidationResult ValidateLoadedAsset(UObject* InAsset, UPARAM(ref) TArray& ValidationErrors); + + UFUNCTION(BlueprintCallable, Category = "Asset Validation") + void AssetFails(UObject* InAsset, const FText& InMessage, UPARAM(ref) TArray& ValidationErrors); + + UFUNCTION(BlueprintCallable, Category = "Asset Validation") + void AssetPasses(UObject* InAsset); + + virtual bool IsEnabled() const + { + return bIsEnabled; + } + + void ResetValidationState(); + + bool IsValidationStateSet() const + { + return ValidationResult != EDataValidationResult::NotValidated; + } + + UFUNCTION(BlueprintCallable, Category = "Asset Validation") + EDataValidationResult GetValidationResult() const + { + return ValidationResult; + } + +protected: + void LogElapsedTime(FFormatNamedArguments &Arguments); + +protected: + UPROPERTY(EditAnywhere, Category = "Asset Validation", meta = (BlueprintProtected = "true")) + bool bIsEnabled; + +private: + EDataValidationResult ValidationResult; + + FDateTime ValidationTime; +}; + + + + + diff --git a/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Public/EditorValidatorSubsystem.h b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Public/EditorValidatorSubsystem.h new file mode 100644 index 000000000000..e5198b220f38 --- /dev/null +++ b/Engine/Plugins/Editor/DataValidation/Source/DataValidation/Public/EditorValidatorSubsystem.h @@ -0,0 +1,121 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "EditorSubsystem.h" + +#include "UObject/ObjectMacros.h" +#include "UObject/UObjectGlobals.h" +#include "Templates/SubclassOf.h" +#include "UObject/Interface.h" +#include "UObject/ObjectMacros.h" +#include "EditorValidatorBase.h" +#include "Engine/EngineTypes.h" +#include "AssetData.h" + +#include "EditorValidatorSubsystem.generated.h" + +struct FAssetData; + +/** + * UEditorValidatorSubsystem manages all the asset validation in the engine. + * The first validation handled is UObject::IsDataValid and its overridden functions. + * Those validations require custom classes and are most suited to project-specific + * classes. The next validation set is of all registered UEditorValidationBases. + * These validators have a function to determine if they can validate a given asset, + * and if they are currently enabled. They are good candidates for validating engine + * classes or very specific project logic. + */ +UCLASS(Config = Editor) +class DATAVALIDATION_API UEditorValidatorSubsystem : public UEditorSubsystem +{ + GENERATED_BODY() + +public: + UEditorValidatorSubsystem(); + + virtual void Initialize(FSubsystemCollectionBase& Collection); + + virtual void Deinitialize(); + + /* + * Adds a validator to the list, making sure it is a unique instance + */ + UFUNCTION(BlueprintCallable, Category = "Validation") + void AddValidator(UEditorValidatorBase* InValidator); + + /** + * @return Returns Valid if the object contains valid data; returns Invalid if the object contains invalid data; returns NotValidated if no validations was performed on the object + */ + UFUNCTION(BlueprintCallable, Category = "Asset Validation") + virtual EDataValidationResult IsObjectValid(UObject* InObject, TArray& ValidationErrors) const; + + /** + * @return Returns Valid if the object pointed to by AssetData contains valid data; returns Invalid if the object contains invalid data or does not exist; returns NotValidated if no validations was performed on the object + */ + UFUNCTION(BlueprintCallable, Category = "Asset Validation") + virtual EDataValidationResult IsAssetValid(FAssetData& AssetData, TArray& ValidationErrors) const; + + /** + * Called to validate assets from either the UI or a commandlet + * @param bSkipExcludedDirectories If true, will not validate files in excluded directories + * @param bShowIfNoFailures If true, will add notifications for files with no validation and display even if everything passes + * @returns Number of assets with validation failures + */ + UFUNCTION(BlueprintCallable, Category = "Asset Validation") + virtual int32 ValidateAssets(TArray AssetDataList, bool bSkipExcludedDirectories = true, bool bShowIfNoFailures = true) const; + + /** + * Called to validate from an interactive save + */ + virtual void ValidateOnSave(TArray AssetDataList) const; + + /** + * Schedule a validation of a saved package, this will activate next frame by default so it can combine them + */ + virtual void ValidateSavedPackage(FName PackageName); + +protected: + void CleanupValidators(); + + /** + * @return Returns true if the current Path should be skipped for validation. Returns false otherwise. + */ + virtual bool IsPathExcludedFromValidation(const FString& Path) const; + + /** + * Handles validating all pending save packages + */ + void ValidateAllSavedPackages(); + + void RegisterBlueprintValidators(); + +protected: + /** + * Directories to ignore for data validation. Useful for test assets + */ + UPROPERTY(config) + TArray ExcludedDirectories; + + /** + * Whether it should validate assets on save inside the editor + */ + UPROPERTY(config) + bool bValidateOnSave; + + /** List of saved package names to validate next frame */ + TArray SavedPackagesToValidate; + + UPROPERTY(Transient) + TMap Validators; + + /** Specifies whether or not to validate assets on save when saving for a cook */ + UPROPERTY(config) + bool bValidateAssetsWhileSavingForCook; + + /** Specifies whether or not to allow Blueprint validators */ + UPROPERTY(config) + bool bAllowBlueprintValidators; + +}; + diff --git a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Classes/GameplayTagsK2Node_LiteralGameplayTag.h b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Classes/GameplayTagsK2Node_LiteralGameplayTag.h index f0ef91e00344..264afc4adbd9 100644 --- a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Classes/GameplayTagsK2Node_LiteralGameplayTag.h +++ b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Classes/GameplayTagsK2Node_LiteralGameplayTag.h @@ -35,7 +35,7 @@ class UGameplayTagsK2Node_LiteralGameplayTag : public UK2Node virtual bool IsNodePure() const override { return true; } virtual bool IsDeprecated() const override { return true; } virtual void ConvertDeprecatedNode(UEdGraph* Graph, bool bOnlySafeChanges) override; - virtual FString GetDeprecationMessage() const override; + virtual FEdGraphNodeDeprecationResponse GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const override; //~ End UK2Node Interface #endif diff --git a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/GameplayTagQueryCustomization.cpp b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/GameplayTagQueryCustomization.cpp index 15445c167d2a..f714124749b0 100644 --- a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/GameplayTagQueryCustomization.cpp +++ b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/GameplayTagQueryCustomization.cpp @@ -31,49 +31,49 @@ void FGameplayTagQueryCustomization::CustomizeHeader(TSharedRefIsEditConst(); HeaderRow - .NameContent() - [ - StructPropertyHandle->CreatePropertyNameWidget() - ] + .NameContent() + [ + StructPropertyHandle->CreatePropertyNameWidget() + ] .ValueContent() - .MaxDesiredWidth(512) - [ - SNew(SHorizontalBox) + .MaxDesiredWidth(512) + [ + SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SVerticalBox) +SVerticalBox::Slot() - .AutoHeight() - [ - SNew(SButton) - .Text(this, &FGameplayTagQueryCustomization::GetEditButtonText) - .OnClicked(this, &FGameplayTagQueryCustomization::OnEditButtonClicked) - ] + .AutoHeight() + [ + SNew(SButton) + .Text(this, &FGameplayTagQueryCustomization::GetEditButtonText) + .OnClicked(this, &FGameplayTagQueryCustomization::OnEditButtonClicked) + ] +SVerticalBox::Slot() - .AutoHeight() - [ - SNew(SButton) - .IsEnabled(!bReadOnly) - .Text(LOCTEXT("GameplayTagQueryCustomization_Clear", "Clear All")) - .OnClicked(this, &FGameplayTagQueryCustomization::OnClearAllButtonClicked) - .Visibility(this, &FGameplayTagQueryCustomization::GetClearAllVisibility) - ] + .AutoHeight() + [ + SNew(SButton) + .IsEnabled(!bReadOnly) + .Text(LOCTEXT("GameplayTagQueryCustomization_Clear", "Clear All")) + .OnClicked(this, &FGameplayTagQueryCustomization::OnClearAllButtonClicked) + .Visibility(this, &FGameplayTagQueryCustomization::GetClearAllVisibility) + ] ] +SHorizontalBox::Slot() .AutoWidth() [ SNew(SBorder) .Padding(4.0f) - .Visibility(this, &FGameplayTagQueryCustomization::GetQueryDescVisibility) - [ - SNew(STextBlock) - .Text(this, &FGameplayTagQueryCustomization::GetQueryDescText) - .AutoWrapText(true) - ] + .Visibility(this, &FGameplayTagQueryCustomization::GetQueryDescVisibility) + [ + SNew(STextBlock) + .Text(this, &FGameplayTagQueryCustomization::GetQueryDescText) + .AutoWrapText(true) ] - ]; + ] + ]; GEditor->RegisterForUndo(this); } @@ -99,6 +99,11 @@ FText FGameplayTagQueryCustomization::GetEditButtonText() const FReply FGameplayTagQueryCustomization::OnClearAllButtonClicked() { + if (StructPropertyHandle.IsValid()) + { + StructPropertyHandle->NotifyPreChange(); + } + for (auto& EQ : EditableQueries) { if (EQ.TagQuery) @@ -109,6 +114,11 @@ FReply FGameplayTagQueryCustomization::OnClearAllButtonClicked() RefreshQueryDescription(); + if (StructPropertyHandle.IsValid()) + { + StructPropertyHandle->NotifyPostChange(); + } + return FReply::Handled(); } @@ -145,7 +155,7 @@ void FGameplayTagQueryCustomization::RefreshQueryDescription() { QueryDescription = TEXT("Multiple Selected"); } - else if ( (EditableQueries.Num() == 1) && (EditableQueries[0].TagQuery != nullptr) ) + else if ((EditableQueries.Num() == 1) && (EditableQueries[0].TagQuery != nullptr)) { QueryDescription = EditableQueries[0].TagQuery->GetDescription(); } @@ -188,9 +198,10 @@ FReply FGameplayTagQueryCustomization::OnEditButtonClicked() .ClientSize(FVector2D(600, 400)) [ SNew(SGameplayTagQueryWidget, EditableQueries) - .OnSaveAndClose(this, &FGameplayTagQueryCustomization::CloseWidgetWindow, false) - .OnCancel(this, &FGameplayTagQueryCustomization::CloseWidgetWindow, true) - .ReadOnly(bReadOnly) + .OnClosePreSave(this, &FGameplayTagQueryCustomization::PreSave) + .OnSaveAndClose(this, &FGameplayTagQueryCustomization::CloseWidgetWindow, false) + .OnCancel(this, &FGameplayTagQueryCustomization::CloseWidgetWindow, true) + .ReadOnly(bReadOnly) ]; // NOTE: FGlobalTabmanager::Get()-> is actually dereferencing a SharedReference, not a SharedPtr, so it cannot be null. @@ -239,35 +250,29 @@ void FGameplayTagQueryCustomization::BuildEditableQueryList() } } +void FGameplayTagQueryCustomization::PreSave() +{ + if (StructPropertyHandle.IsValid()) + { + StructPropertyHandle->NotifyPreChange(); + } +} + void FGameplayTagQueryCustomization::CloseWidgetWindow(bool WasCancelled) { - // Notify change. This is required for these to work inside of UDataTables - if (!WasCancelled && PropertyUtilities.IsValid()) + // Notify change. + if (!WasCancelled && StructPropertyHandle.IsValid()) { - if (StructPropertyHandle.IsValid()) - { - UProperty* TheProperty = StructPropertyHandle->GetProperty(); - - FEditPropertyChain PropertyChain; - PropertyChain.AddHead(TheProperty); - PropertyChain.SetActivePropertyNode(TheProperty); - - FPropertyChangedEvent ChangeEvent(StructPropertyHandle->GetProperty(), EPropertyChangeType::ValueSet, nullptr); - FNotifyHook* NotifyHook = PropertyUtilities->GetNotifyHook(); - if (NotifyHook != nullptr) - { - NotifyHook->NotifyPostChange(ChangeEvent, &PropertyChain); - } - } + StructPropertyHandle->NotifyPostChange(); } - if (GameplayTagQueryWidgetWindow.IsValid()) - { - GameplayTagQueryWidgetWindow->RequestDestroyWindow(); + if (GameplayTagQueryWidgetWindow.IsValid()) + { + GameplayTagQueryWidgetWindow->RequestDestroyWindow(); GameplayTagQueryWidgetWindow = nullptr; RefreshQueryDescription(); - } + } } -#undef LOCTEXT_NAMESPACE +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/GameplayTagQueryCustomization.h b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/GameplayTagQueryCustomization.h index 7628d20ecfd2..019f9706a60e 100644 --- a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/GameplayTagQueryCustomization.h +++ b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/GameplayTagQueryCustomization.h @@ -48,6 +48,8 @@ private: FText GetQueryDescText() const; + void PreSave(); + void CloseWidgetWindow(bool WasCancelled); /** Build List of Editable Queries */ diff --git a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/GameplayTagsK2Node_LiteralGameplayTag.cpp b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/GameplayTagsK2Node_LiteralGameplayTag.cpp index 216bdfa0dc59..647f0ef935f5 100644 --- a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/GameplayTagsK2Node_LiteralGameplayTag.cpp +++ b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/GameplayTagsK2Node_LiteralGameplayTag.cpp @@ -57,9 +57,12 @@ FText UGameplayTagsK2Node_LiteralGameplayTag::GetMenuCategory() const return LOCTEXT("ActionMenuCategory", "Gameplay Tags"); } -FString UGameplayTagsK2Node_LiteralGameplayTag::GetDeprecationMessage() const +FEdGraphNodeDeprecationResponse UGameplayTagsK2Node_LiteralGameplayTag::GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const { - return LOCTEXT("NodeDeprecated_Warning", "@@ is deprecated, replace with Make Literal GameplayTagContainer function call").ToString(); + FEdGraphNodeDeprecationResponse Response = Super::GetDeprecationResponse(DeprecationType); + Response.MessageText = LOCTEXT("NodeDeprecated_Warning", "@@ is deprecated, replace with Make Literal GameplayTagContainer function call"); + + return Response; } void UGameplayTagsK2Node_LiteralGameplayTag::ConvertDeprecatedNode(UEdGraph* Graph, bool bOnlySafeChanges) diff --git a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagQueryWidget.cpp b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagQueryWidget.cpp index 6b9a089e804f..f682a8b45e2b 100644 --- a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagQueryWidget.cpp +++ b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagQueryWidget.cpp @@ -18,6 +18,7 @@ void SGameplayTagQueryWidget::Construct(const FArguments& InArgs, const TArrayOnFinishedChangingProperties().AddSP(this, &SGameplayTagQueryWidget::OnFinishedChangingProperties); ChildSlot - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) [ SNew(SVerticalBox) + SVerticalBox::Slot() - .AutoHeight() - .VAlign(VAlign_Top) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SButton) - .IsEnabled(!bReadOnly) - .Visibility(this, &SGameplayTagQueryWidget::GetSaveAndCloseButtonVisibility) - .OnClicked(this, &SGameplayTagQueryWidget::OnSaveAndCloseClicked) - .Text(LOCTEXT("GameplayTagQueryWidget_SaveAndClose", "Save and Close")) - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SButton) - .Visibility(this, &SGameplayTagQueryWidget::GetCancelButtonVisibility) - .OnClicked(this, &SGameplayTagQueryWidget::OnCancelClicked) - .Text(LOCTEXT("GameplayTagQueryWidget_Cancel", "Close Without Saving")) - ] - ] - // to delete! - + SVerticalBox::Slot() - [ - Details.ToSharedRef() - ] + .AutoHeight() + .VAlign(VAlign_Top) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .IsEnabled(!bReadOnly) + .Visibility(this, &SGameplayTagQueryWidget::GetSaveAndCloseButtonVisibility) + .OnClicked(this, &SGameplayTagQueryWidget::OnSaveAndCloseClicked) + .Text(LOCTEXT("GameplayTagQueryWidget_SaveAndClose", "Save and Close")) ] - ]; + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .Visibility(this, &SGameplayTagQueryWidget::GetCancelButtonVisibility) + .OnClicked(this, &SGameplayTagQueryWidget::OnCancelClicked) + .Text(LOCTEXT("GameplayTagQueryWidget_Cancel", "Close Without Saving")) + ] + ] + // to delete! + + SVerticalBox::Slot() + [ + Details.ToSharedRef() + ] + ] + ]; } void SGameplayTagQueryWidget::OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent) { + // Auto saved changes will not call pre and post notify; auto save should only be used to make changes coming from blueprints if (bAutoSave) { SaveToTagQuery(); @@ -151,6 +153,8 @@ void SGameplayTagQueryWidget::SaveToTagQuery() FReply SGameplayTagQueryWidget::OnSaveAndCloseClicked() { + OnClosePreSave.ExecuteIfBound(); + SaveToTagQuery(); OnSaveAndClose.ExecuteIfBound(); @@ -163,4 +167,4 @@ FReply SGameplayTagQueryWidget::OnCancelClicked() return FReply::Handled(); } -#undef LOCTEXT_NAMESPACE +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagQueryWidget.h b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagQueryWidget.h index 72fe56e4f626..198e737d162a 100644 --- a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagQueryWidget.h +++ b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagQueryWidget.h @@ -21,15 +21,16 @@ public: SLATE_BEGIN_ARGS( SGameplayTagQueryWidget ) : _ReadOnly(false), _AutoSave(false) {} - SLATE_ARGUMENT( bool, ReadOnly ) // Flag to set if the list is read only - SLATE_ARGUMENT( bool, AutoSave) // Flag to set if edits should be applied automatically (hides buttons) + SLATE_ARGUMENT(bool, ReadOnly) // Flag to set if the list is read only + SLATE_ARGUMENT(bool, AutoSave) // Flag to set if edits should be applied automatically (hides buttons) + SLATE_EVENT(FSimpleDelegate, OnClosePreSave) // Called when "Save and Close" button clicked SLATE_EVENT(FSimpleDelegate, OnSaveAndClose) // Called when "Save and Close" button clicked SLATE_EVENT(FSimpleDelegate, OnCancel) // Called when "Close Without Saving" button clicked SLATE_EVENT(FSimpleDelegate, OnQueryChanged) // Called when the user has modified the query - SLATE_END_ARGS() + SLATE_END_ARGS() - /** Simple struct holding a tag query and its owner for generic re-use of the widget */ - struct FEditableGameplayTagQueryDatum + /** Simple struct holding a tag query and its owner for generic re-use of the widget */ + struct FEditableGameplayTagQueryDatum { /** Constructor */ FEditableGameplayTagQueryDatum(class UObject* InOwnerObj, struct FGameplayTagQuery* InTagQuery, FString* InTagExportText=nullptr) @@ -64,13 +65,16 @@ private: /** Containers to modify */ TArray TagQueries; - /** Called when "save and close" is clicked */ + /** Called when "Save and Close" is clicked before we save the data. */ + FSimpleDelegate OnClosePreSave; + + /** Called when "Save and Close" is clicked after we have saved the data. */ FSimpleDelegate OnSaveAndClose; /** Called when the user has modified the query */ FSimpleDelegate OnQueryChanged; - /** Called when "cancel" is clicked */ + /** Called when "Close Without Saving" is clicked */ FSimpleDelegate OnCancel; /** Properties Tab */ @@ -79,17 +83,17 @@ private: class UEditableGameplayTagQuery* CreateEditableQuery(FGameplayTagQuery& Q); TWeakObjectPtr EditableQuery; - /** Called when the user clicks the "Expand All" button; Expands the entire tag tree */ + /** Called when the user clicks the "Save and Close" button */ FReply OnSaveAndCloseClicked(); - /** Called when the user clicks the "Expand All" button; Expands the entire tag tree */ + /** Called when the user clicks the "Close Without Saving" button */ FReply OnCancelClicked(); void OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent); - /** Controls visibility of the "save and close" button */ + /** Controls visibility of the "Save and Close" button */ EVisibility GetSaveAndCloseButtonVisibility() const; - /** Controls visibility of the "cancel without saving" button */ + /** Controls visibility of the "Close Without Saving" button */ EVisibility GetCancelButtonVisibility() const; void SaveToTagQuery(); diff --git a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagWidget.cpp b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagWidget.cpp index 8a038553c611..311a780bd6c4 100644 --- a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagWidget.cpp +++ b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagWidget.cpp @@ -1358,7 +1358,7 @@ void SGameplayTagWidget::OpenRenameGameplayTagDialog(TSharedPtr RenameTagDialog = SNew(SRenameGameplayTagDialog) .GameplayTagNode(GameplayTagNode) - .OnGameplayTagRenamed(this, &SGameplayTagWidget::OnGameplayTagRenamed); + .OnGameplayTagRenamed(const_cast(this), &SGameplayTagWidget::OnGameplayTagRenamed); RenameTagWindow->SetContent(RenameTagDialog); diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorMode.h b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorMode.h index 6ceeb8023040..c5dd90e85914 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorMode.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorMode.h @@ -335,7 +335,7 @@ protected: }; - class FSelectOrDeselectMeshElementsChange : public FChange + class FSelectOrDeselectMeshElementsChange : public FSwapChange { public: @@ -371,7 +371,7 @@ protected: }; - class FDeselectAllMeshElementsChange : public FChange + class FDeselectAllMeshElementsChange : public FSwapChange { public: @@ -414,7 +414,7 @@ protected: }; - class FSetElementSelectionModeChange : public FChange + class FSetElementSelectionModeChange : public FSwapChange { public: diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/SStylusInputDebugWidget.cpp b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/SStylusInputDebugWidget.cpp new file mode 100644 index 000000000000..ae57edefe002 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/SStylusInputDebugWidget.cpp @@ -0,0 +1,224 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SStylusInputDebugWidget.h" + +#include "IStylusState.h" + +#include "Widgets/SBoxPanel.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Input/SCheckBox.h" + +#define LOCTEXT_NAMESPACE "StylusInputDebugWidget" + +SStylusInputDebugWidget::SStylusInputDebugWidget() +{ +} + +SStylusInputDebugWidget::~SStylusInputDebugWidget() +{ + InputSubsystem->RemoveMessageHandler(*this); +} + +void SStylusInputDebugWidget::Construct(const FArguments& InArgs, UStylusInputSubsystem& InSubsystem) +{ + InputSubsystem = &InSubsystem; + InputSubsystem->AddMessageHandler(*this); + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("MostRecentIndex", "Most Recent Index")) + ] + + SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(this, &SStylusInputDebugWidget::GetIndexText) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("Position", "Position")) + ] + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(this, &SStylusInputDebugWidget::GetPositionText) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("NormalPressure", "Normal Pressure")) + ] + + SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(this, &SStylusInputDebugWidget::GetPressureText) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("TangentPressure", "Tangent Pressure")) + ] + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(this, &SStylusInputDebugWidget::GetTangentPressureText) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("Z", "Z")) + ] + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(this, &SStylusInputDebugWidget::GetZText) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("Twist", "Twist")) + ] + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(this, &SStylusInputDebugWidget::GetTwistText) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("Tilt", "Tilt")) + ] + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(this, &SStylusInputDebugWidget::GetTiltText) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("Size", "Size")) + ] + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(this, &SStylusInputDebugWidget::GetSizeText) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("IsTouching", "Is Touching?")) + ] + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(SCheckBox) + .IsChecked(this, &SStylusInputDebugWidget::IsTouching) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("IsInverted", "Is Inverted?")) + ] + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(SCheckBox) + .IsChecked(this, &SStylusInputDebugWidget::IsInverted) + ] + ] + ]; +} + +FText SStylusInputDebugWidget::GetVector2Text(FVector2D Value) +{ + return FText::FromString(FString::Format(TEXT("{0}, {1}"), { Value.X, Value.Y })); +} + +FText SStylusInputDebugWidget::GetFloatText(float Value) +{ + return FText::FromString(FString::Format(TEXT("{0}"), { Value })); +} + +ECheckBoxState SStylusInputDebugWidget::IsTouching() const +{ + return State.IsStylusDown() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +ECheckBoxState SStylusInputDebugWidget::IsInverted() const +{ + return State.IsStylusInverted() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/SStylusInputDebugWidget.h b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/SStylusInputDebugWidget.h new file mode 100644 index 000000000000..5e7bf2728ee4 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/SStylusInputDebugWidget.h @@ -0,0 +1,50 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Widgets/SCompoundWidget.h" + +#include "IStylusInputModule.h" +#include "IStylusState.h" + +class SStylusInputDebugWidget : public SCompoundWidget, + public IStylusMessageHandler +{ +public: + SStylusInputDebugWidget(); + virtual ~SStylusInputDebugWidget(); + + SLATE_BEGIN_ARGS(SStylusInputDebugWidget) + {} + SLATE_END_ARGS(); + + void Construct(const FArguments& InArgs, UStylusInputSubsystem& InSubsystem); + void OnStylusStateChanged(const FStylusState& InState, int32 InIndex) + { + State = InState; + LastIndex = InIndex; + } + +private: + + UStylusInputSubsystem* InputSubsystem; + FStylusState State; + int32 LastIndex; + + ECheckBoxState IsTouching() const; + ECheckBoxState IsInverted() const; + + FText GetPositionText() const { return GetVector2Text(State.GetPosition()); } + FText GetTiltText() const { return GetVector2Text(State.GetTilt()); } + FText GetSizeText() const { return GetVector2Text(State.GetSize()); } + + FText GetIndexText() const { return FText::FromString(FString::FromInt(LastIndex)); } + + FText GetPressureText() const { return GetFloatText(State.GetPressure()); } + FText GetTangentPressureText() const { return GetFloatText(State.GetTangentPressure()); } + FText GetZText() const { return GetFloatText(State.GetZ()); } + FText GetTwistText() const { return GetFloatText(State.GetTwist()); } + + static FText GetVector2Text(FVector2D Value); + static FText GetFloatText(float Value); +}; \ No newline at end of file diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/StylusInputModule.cpp b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/StylusInputModule.cpp new file mode 100644 index 000000000000..fde9d3453a41 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/StylusInputModule.cpp @@ -0,0 +1,131 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "IStylusInputModule.h" +#include "CoreMinimal.h" + +#include "Framework/Docking/TabManager.h" +#include "Interfaces/IMainFrameModule.h" +#include "Logging/LogMacros.h" +#include "Widgets/Docking/SDockTab.h" +#include "Widgets/SWindow.h" +#include "WorkspaceMenuStructure.h" +#include "WorkspaceMenuStructureModule.h" +#include "SStylusInputDebugWidget.h" + +#define LOCTEXT_NAMESPACE "FStylusInputModule" + + +static const FName StylusInputDebugTabName = FName("StylusInputDebug"); + +class FStylusInputModule : public IModuleInterface +{ +}; + +IMPLEMENT_MODULE(FStylusInputModule, StylusInput) + +// This is the function that all platform-specific implementations are required to implement. +TSharedPtr CreateStylusInputInterface(); + +#if PLATFORM_WINDOWS +#include "WindowsStylusInputInterface.h" +#else +TSharedPtr CreateStylusInputInterface() { return TSharedPtr(); } +#endif + +// TODO: Other platforms + +void UStylusInputSubsystem::Initialize(FSubsystemCollectionBase& Collection) +{ + Super::Initialize(Collection); + + UE_LOG(LogStylusInput, Log, TEXT("Initializing StylusInput subsystem.")); + + InputInterface = CreateStylusInputInterface(); + + if (!InputInterface.IsValid()) + { + UE_LOG(LogStylusInput, Log, TEXT("StylusInput not supported on this platform.")); + return; + } + + const TSharedRef& TabManager = FGlobalTabmanager::Get(); + const IWorkspaceMenuStructure& MenuStructure = WorkspaceMenu::GetMenuStructure(); + + TabManager->RegisterNomadTabSpawner(StylusInputDebugTabName, + FOnSpawnTab::CreateUObject(this, &UStylusInputSubsystem::OnSpawnPluginTab)) + .SetDisplayName(LOCTEXT("DebugTabTitle", "Stylus Input Debug")) + .SetTooltipText(LOCTEXT("DebugTabTooltip", "Debug panel to display current values of stylus inputs.")) + .SetGroup(MenuStructure.GetDeveloperToolsMiscCategory()); +} + +void UStylusInputSubsystem::Deinitialize() +{ + Super::Deinitialize(); + + FGlobalTabmanager::Get()->UnregisterTabSpawner(StylusInputDebugTabName); + + InputInterface.Reset(); + + UE_LOG(LogStylusInput, Log, TEXT("Shutting down StylusInput subsystem.")); +} + +int32 UStylusInputSubsystem::NumInputDevices() const +{ + if (InputInterface.IsValid()) + { + return InputInterface->NumInputDevices(); + } + return 0; +} + +const IStylusInputDevice* UStylusInputSubsystem::GetInputDevice(int32 Index) const +{ + if (InputInterface.IsValid()) + { + return InputInterface->GetInputDevice(Index); + } + return nullptr; +} + +void UStylusInputSubsystem::AddMessageHandler(IStylusMessageHandler& InHandler) +{ + MessageHandlers.AddUnique(&InHandler); +} + +void UStylusInputSubsystem::RemoveMessageHandler(IStylusMessageHandler& InHandler) +{ + MessageHandlers.Remove(&InHandler); +} + +void UStylusInputSubsystem::Tick(float DeltaTime) +{ + if (InputInterface.IsValid()) + { + for (int32 DeviceIdx = 0; DeviceIdx < NumInputDevices(); ++DeviceIdx) + { + IStylusInputDevice* InputDevice = InputInterface->GetInputDevice(DeviceIdx); + if (InputDevice->IsDirty()) + { + InputDevice->Tick(); + + for (IStylusMessageHandler* Handler : MessageHandlers) + { + Handler->OnStylusStateChanged(InputDevice->GetCurrentState(), DeviceIdx); + } + } + } + } +} + +TSharedRef UStylusInputSubsystem::OnSpawnPluginTab(const FSpawnTabArgs& Args) +{ + return SNew(SDockTab) + .TabRole(ETabRole::NomadTab) + [ + SNew(SStylusInputDebugWidget, *this) + ]; +} + + + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsRealTimeStylusPlugin.cpp b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsRealTimeStylusPlugin.cpp new file mode 100644 index 000000000000..b80520e0a5a5 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsRealTimeStylusPlugin.cpp @@ -0,0 +1,440 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "WindowsRealTimeStylusPlugin.h" + +#if PLATFORM_WINDOWS + +HRESULT FWindowsRealTimeStylusPlugin::QueryInterface(const IID& InterfaceID, void** Pointer) +{ + if ((InterfaceID == __uuidof(IStylusSyncPlugin)) || (InterfaceID == IID_IUnknown)) + { + *Pointer = this; + AddRef(); + return S_OK; + } + else if ((InterfaceID == IID_IMarshal) && (FreeThreadedMarshaller != nullptr)) + { + return FreeThreadedMarshaller->QueryInterface(InterfaceID, Pointer); + } + + *Pointer = nullptr; + return E_NOINTERFACE; +} + +HRESULT FWindowsRealTimeStylusPlugin::StylusDown(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, ULONG PacketSize, LONG* Packet, LONG** InOutPackets) +{ + FTabletContextInfo* TabletContext = FindTabletContext(StylusInfo->tcid); + if (TabletContext != nullptr) + { + TabletContext->WindowsState.IsTouching = true; + } + return S_OK; +} + +HRESULT FWindowsRealTimeStylusPlugin::StylusUp(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, ULONG PacketSize, LONG* Packet, LONG** InOutPackets) +{ + FTabletContextInfo* TabletContext = FindTabletContext(StylusInfo->tcid); + if (TabletContext != nullptr) + { + // we know this is not touching + TabletContext->WindowsState.IsTouching = false; + TabletContext->WindowsState.NormalPressure = 0; + } + return S_OK; +} + +static void SetupPacketDescriptions(IRealTimeStylus* RealTimeStylus, FTabletContextInfo& TabletContext) +{ + ULONG NumPacketProperties = 0; + PACKET_PROPERTY* PacketProperties = nullptr; + HRESULT hr = RealTimeStylus->GetPacketDescriptionData(TabletContext.ID, nullptr, nullptr, &NumPacketProperties, &PacketProperties); + if (SUCCEEDED(hr) && PacketProperties != nullptr) + { + for (ULONG PropIdx = 0; PropIdx < NumPacketProperties; ++PropIdx) + { + PACKET_PROPERTY CurrentProperty = PacketProperties[PropIdx]; + + EWindowsPacketType PacketType = EWindowsPacketType::None; + if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_X) + { + PacketType = EWindowsPacketType::X; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_Y) + { + PacketType = EWindowsPacketType::Y; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_Z) + { + PacketType = EWindowsPacketType::Z; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_PACKET_STATUS) + { + PacketType = EWindowsPacketType::Status; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_NORMAL_PRESSURE) + { + PacketType = EWindowsPacketType::NormalPressure; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_TANGENT_PRESSURE) + { + PacketType = EWindowsPacketType::TangentPressure; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_BUTTON_PRESSURE) + { + PacketType = EWindowsPacketType::ButtonPressure; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_ALTITUDE_ORIENTATION) + { + PacketType = EWindowsPacketType::Altitude; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_AZIMUTH_ORIENTATION) + { + PacketType = EWindowsPacketType::Azimuth; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_TWIST_ORIENTATION) + { + PacketType = EWindowsPacketType::Twist; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_X_TILT_ORIENTATION) + { + PacketType = EWindowsPacketType::XTilt; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_Y_TILT_ORIENTATION) + { + PacketType = EWindowsPacketType::YTilt; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_WIDTH) + { + PacketType = EWindowsPacketType::Width; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_HEIGHT) + { + PacketType = EWindowsPacketType::Height; + } + + int32 CreatedIdx = TabletContext.PacketDescriptions.Emplace(); + FPacketDescription& PacketDescription = TabletContext.PacketDescriptions[CreatedIdx]; + PacketDescription.Type = PacketType; + PacketDescription.Minimum = CurrentProperty.PropertyMetrics.nLogicalMin; + PacketDescription.Maximum = CurrentProperty.PropertyMetrics.nLogicalMax; + PacketDescription.Resolution = CurrentProperty.PropertyMetrics.fResolution; + } + + ::CoTaskMemFree(PacketProperties); + } +} + +static void SetupTabletSupportedPackets(TComPtr RealTimeStylus, FTabletContextInfo& TabletContext) +{ + IInkTablet* InkTablet; + RealTimeStylus->GetTabletFromTabletContextId(TabletContext.ID, &InkTablet); + + int16 Supported; + + BSTR GuidBSTR; + + GuidBSTR = SysAllocString(STR_GUID_X); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::X); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_Y); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::Y); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_Z); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::Z); + TabletContext.AddSupportedInput(EStylusInputType::Z); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_PAKETSTATUS); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::Status); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_NORMALPRESSURE); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::NormalPressure); + TabletContext.AddSupportedInput(EStylusInputType::Pressure); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_TANGENTPRESSURE); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::TangentPressure); + TabletContext.AddSupportedInput(EStylusInputType::TangentPressure); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_BUTTONPRESSURE); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::ButtonPressure); + TabletContext.AddSupportedInput(EStylusInputType::ButtonPressure); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_AZIMUTHORIENTATION); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::Azimuth); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_ALTITUDEORIENTATION); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::Altitude); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_XTILTORIENTATION); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::XTilt); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_YTILTORIENTATION); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::YTilt); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_TWISTORIENTATION); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::Twist); + TabletContext.AddSupportedInput(EStylusInputType::Twist); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_WIDTH); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::Width); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_HEIGHT); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::Height); + } + + SysFreeString(GuidBSTR); + + if (TabletContext.SupportedPackets.Contains(EWindowsPacketType::X) && + TabletContext.SupportedPackets.Contains(EWindowsPacketType::Y)) + { + TabletContext.AddSupportedInput(EStylusInputType::Position); + } + + if (TabletContext.SupportedPackets.Contains(EWindowsPacketType::XTilt) && + TabletContext.SupportedPackets.Contains(EWindowsPacketType::YTilt)) + { + TabletContext.AddSupportedInput(EStylusInputType::Tilt); + } + + if (TabletContext.SupportedPackets.Contains(EWindowsPacketType::Width) && + TabletContext.SupportedPackets.Contains(EWindowsPacketType::Height)) + { + TabletContext.AddSupportedInput(EStylusInputType::Size); + } +} + +FTabletContextInfo* FWindowsRealTimeStylusPlugin::FindTabletContext(TABLET_CONTEXT_ID TabletID) +{ + for (FTabletContextInfo& TabletContext : TabletContexts) + { + if (TabletContext.ID == TabletID) + { + return &TabletContext; + } + } + return nullptr; +} + +void FWindowsRealTimeStylusPlugin::AddTabletContext(IRealTimeStylus* RealTimeStylus, TABLET_CONTEXT_ID TabletID) +{ + FTabletContextInfo* FoundContext = FindTabletContext(TabletID); + if (FoundContext == nullptr) + { + int32 Created = TabletContexts.Emplace(); + FoundContext = &TabletContexts[Created]; + FoundContext->ID = TabletID; + } + + SetupTabletSupportedPackets(RealTimeStylus, *FoundContext); + SetupPacketDescriptions(RealTimeStylus, *FoundContext); +} + +void FWindowsRealTimeStylusPlugin::RemoveTabletContext(IRealTimeStylus* RealTimeStylus, TABLET_CONTEXT_ID TabletID) +{ + for (int32 ExistingIdx = 0; ExistingIdx < TabletContexts.Num(); ++ExistingIdx) + { + if (TabletContexts[ExistingIdx].ID == TabletID) + { + TabletContexts.RemoveAt(ExistingIdx); + break; + } + } +} + +HRESULT FWindowsRealTimeStylusPlugin::RealTimeStylusEnabled(IRealTimeStylus* RealTimeStylus, ULONG Num, const TABLET_CONTEXT_ID* InTabletContexts) +{ + for (ULONG TabletIdx = 0; TabletIdx < Num; ++TabletIdx) + { + AddTabletContext(RealTimeStylus, InTabletContexts[TabletIdx]); + } + return S_OK; +} + +HRESULT FWindowsRealTimeStylusPlugin::RealTimeStylusDisabled(IRealTimeStylus* RealTimeStylus, ULONG Num, const TABLET_CONTEXT_ID* InTabletContexts) +{ + for (ULONG TabletIdx = 0; TabletIdx < Num; ++TabletIdx) + { + RemoveTabletContext(RealTimeStylus, InTabletContexts[TabletIdx]); + } + return S_OK; +} + +HRESULT FWindowsRealTimeStylusPlugin::TabletAdded(IRealTimeStylus* RealTimeStylus, IInkTablet* InkTablet) +{ + TABLET_CONTEXT_ID TabletID; + if (SUCCEEDED(RealTimeStylus->GetTabletContextIdFromTablet(InkTablet, &TabletID))) + { + AddTabletContext(RealTimeStylus, TabletID); + } + return S_OK; +} + +HRESULT FWindowsRealTimeStylusPlugin::TabletRemoved(IRealTimeStylus* RealTimeStylus, LONG iTabletIndex) +{ + TabletContexts.RemoveAt(iTabletIndex); + return S_OK; +} + +static float Normalize(int Value, const FPacketDescription& Desc) +{ + return (float) (Value - Desc.Minimum) / (float) ((Desc.Maximum - Desc.Minimum) + 1); +} + +static float ToDegrees(int Value, const FPacketDescription& Desc) +{ + return Value / Desc.Resolution; +} + +void FWindowsRealTimeStylusPlugin::HandlePacket(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, ULONG PacketCount, ULONG PacketBufferLength, LONG* Packets) +{ + FTabletContextInfo* TabletContext = FindTabletContext(StylusInfo->tcid); + if (TabletContext == nullptr) + { + return; + } + + TabletContext->SetDirty(); + TabletContext->WindowsState.IsInverted = StylusInfo->bIsInvertedCursor; + + ULONG PropertyCount = PacketBufferLength / PacketCount; + + for (ULONG i = 0; i < PropertyCount; ++i) + { + const FPacketDescription& PacketDescription = TabletContext->PacketDescriptions[i]; + + float Normalized = Normalize(Packets[i], PacketDescription); + + switch (PacketDescription.Type) + { + case EWindowsPacketType::X: + TabletContext->WindowsState.Position.X = Packets[i]; + break; + case EWindowsPacketType::Y: + TabletContext->WindowsState.Position.Y = Packets[i]; + break; + case EWindowsPacketType::Status: + break; + case EWindowsPacketType::Z: + TabletContext->WindowsState.Z = Normalized; + break; + case EWindowsPacketType::NormalPressure: + TabletContext->WindowsState.NormalPressure = Normalized; + break; + case EWindowsPacketType::TangentPressure: + TabletContext->WindowsState.TangentPressure = Normalized; + break; + case EWindowsPacketType::Twist: + TabletContext->WindowsState.Twist = ToDegrees(Packets[i], PacketDescription); + break; + case EWindowsPacketType::XTilt: + TabletContext->WindowsState.Tilt.X = ToDegrees(Packets[i], PacketDescription); + break; + case EWindowsPacketType::YTilt: + TabletContext->WindowsState.Tilt.Y = ToDegrees(Packets[i], PacketDescription); + break; + case EWindowsPacketType::Width: + TabletContext->WindowsState.Size.X = Normalized; + break; + case EWindowsPacketType::Height: + TabletContext->WindowsState.Size.Y = Normalized; + break; + } + } +} + +HRESULT FWindowsRealTimeStylusPlugin::Packets(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, + ULONG PacketCount, ULONG PacketBufferLength, LONG* Packets, ULONG* InOutPackets, LONG** PtrInOutPackets) +{ + HandlePacket(RealTimeStylus, StylusInfo, PacketCount, PacketBufferLength, Packets); + return S_OK; +} + +HRESULT FWindowsRealTimeStylusPlugin::InAirPackets(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, + ULONG PacketCount, ULONG PacketBufferLength, LONG* Packets, ULONG* InOutPackets, LONG** PtrInOutPackets) +{ + HandlePacket(RealTimeStylus, StylusInfo, PacketCount, PacketBufferLength, Packets); + return S_OK; +} + +#endif // PLATFORM_WINDOWS \ No newline at end of file diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsRealTimeStylusPlugin.h b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsRealTimeStylusPlugin.h new file mode 100644 index 000000000000..11a07e19a5ca --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsRealTimeStylusPlugin.h @@ -0,0 +1,176 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#if PLATFORM_WINDOWS +#include "Windows/WindowsHWrapper.h" + +#include "Windows/AllowWindowsPlatformTypes.h" +#include "Windows/PreWindowsApi.h" +#include "Windows/COMPointer.h" + #include + #include +#include "Windows/PostWindowsApi.h" +#include "Windows/HideWindowsPlatformTypes.h" + +#include "IStylusState.h" + +/** + * Packet types as derived from IRealTimeStylus::GetPacketDescriptionData. + */ +enum class EWindowsPacketType +{ + None, + X, + Y, + Z, + Status, + NormalPressure, + TangentPressure, + ButtonPressure, + Azimuth, + Altitude, + Twist, + XTilt, + YTilt, + Width, + Height, +}; + +/** + * Stylus state for a single frame. + */ +struct FWindowsStylusState +{ + FVector2D Position; + float Z; + FVector2D Tilt; + float Twist; + float NormalPressure; + float TangentPressure; + FVector2D Size; + bool IsTouching : 1; + bool IsInverted : 1; + + FWindowsStylusState() : + Position(0, 0), Z(0), Tilt(0, 0), Twist(0), NormalPressure(0), TangentPressure(0), + Size(0, 0), IsTouching(false), IsInverted(false) + { + } + + FStylusState ToPublicState() const + { + return FStylusState(Position, Z, Tilt, Twist, NormalPressure, TangentPressure, Size, IsTouching, IsInverted); + } +}; + +/** + * Description of a packet's information, as derived from IRealTimeStylus::GetPacketDescriptionData. + */ +struct FPacketDescription +{ + EWindowsPacketType Type { EWindowsPacketType::None }; + int32 Minimum { 0 }; + int32 Maximum { 0 }; + float Resolution { 0 }; +}; + +struct FTabletContextInfo : public IStylusInputDevice +{ + int32 Index; + + TABLET_CONTEXT_ID ID; + TArray PacketDescriptions; + TArray SupportedPackets; + + FWindowsStylusState WindowsState; + + void AddSupportedInput(EStylusInputType Type) { SupportedInputs.Add(Type); } + void SetDirty() { Dirty = true; } + + virtual void Tick() override + { + PreviousState = CurrentState; + CurrentState = WindowsState.ToPublicState(); + Dirty = false; + } +}; + +/** + * An implementation of an IStylusSyncPlugin for use with the RealTimeStylus API. + */ +class FWindowsRealTimeStylusPlugin : public IStylusSyncPlugin +{ +public: + FWindowsRealTimeStylusPlugin() = default; + virtual ~FWindowsRealTimeStylusPlugin() + { + if (FreeThreadedMarshaller != nullptr) + { + FreeThreadedMarshaller->Release(); + } + } + + virtual ULONG AddRef() override { return ++RefCount; } + virtual ULONG Release() override + { + int NewRefCount = --RefCount; + if (NewRefCount == 0) + delete this; + + return NewRefCount; + } + + virtual HRESULT QueryInterface(const IID& InterfaceID, void** Pointer) override; + + virtual HRESULT TabletAdded(IRealTimeStylus* RealTimeStylus, IInkTablet* InkTablet) override; + virtual HRESULT TabletRemoved(IRealTimeStylus* RealTimeStylus, LONG iTabletIndex) override; + + virtual HRESULT RealTimeStylusEnabled(IRealTimeStylus* RealTimeStylus, ULONG Num, const TABLET_CONTEXT_ID* InTabletContexts) override; + virtual HRESULT RealTimeStylusDisabled(IRealTimeStylus* RealTimeStylus, ULONG Num, const TABLET_CONTEXT_ID* InTabletContexts) override; + + virtual HRESULT StylusInRange(IRealTimeStylus* RealTimeStylus, TABLET_CONTEXT_ID TabletContext, STYLUS_ID StylusID) override { return S_OK; } + virtual HRESULT StylusOutOfRange(IRealTimeStylus* RealTimeStylus, TABLET_CONTEXT_ID TabletContext, STYLUS_ID StylusID) override { return S_OK; } + + virtual HRESULT StylusDown(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, ULONG PacketSize, LONG* Packet, LONG** InOutPackets) override; + virtual HRESULT StylusUp(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, ULONG PacketSize, LONG* Packet, LONG** InOutPackets) override; + + virtual HRESULT StylusButtonDown(IRealTimeStylus* RealTimeStylus, STYLUS_ID StylusID, const GUID* GUID, POINT* Position) override { return S_OK; } + virtual HRESULT StylusButtonUp(IRealTimeStylus* RealTimeStylus, STYLUS_ID StylusID, const GUID* GUID, POINT* Position) override { return S_OK; } + + virtual HRESULT InAirPackets(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, + ULONG PacketCount, ULONG PacketBufferLength, LONG* Packets, ULONG* NumOutPackets, LONG** PtrOutPackets) override; + virtual HRESULT Packets(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, + ULONG PacketCount, ULONG PacketBufferSize, LONG* Packets, ULONG* NumOutPackets, LONG** PtrOutPackets) override; + + virtual HRESULT CustomStylusDataAdded(IRealTimeStylus* RealTimeStylus, const GUID* GUID, ULONG Data, const BYTE* ByteData) override { return S_OK; } + + virtual HRESULT SystemEvent(IRealTimeStylus* RealTimeStylus, TABLET_CONTEXT_ID TabletContext, STYLUS_ID StylusID, SYSTEM_EVENT EventType, SYSTEM_EVENT_DATA EventData) override { return S_OK; } + virtual HRESULT Error(IRealTimeStylus* RealTimeStylus, IStylusPlugin* Plugin, RealTimeStylusDataInterest DataInterest, HRESULT ErrorCode, LONG_PTR* Key) override { return S_OK; } + + virtual HRESULT DataInterest(RealTimeStylusDataInterest* OutDataInterest) override + { + *OutDataInterest = RTSDI_AllData; + return S_OK; + } + + virtual HRESULT UpdateMapping(IRealTimeStylus* RealTimeStylus) override { return S_OK; } + + FTabletContextInfo* FindTabletContext(TABLET_CONTEXT_ID TabletID); + + IUnknown* FreeThreadedMarshaller; + TArray TabletContexts; + bool HasChanges { false }; + +private: + int RefCount { 1 }; + + void HandlePacket(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, ULONG PacketCount, ULONG PacketBufferLength, LONG* Packets); + + void AddTabletContext(IRealTimeStylus* RealTimeStylus, TABLET_CONTEXT_ID TabletID); + void RemoveTabletContext(IRealTimeStylus* RealTimeStylus, TABLET_CONTEXT_ID TabletID); +}; + +#endif // PLATFORM_WINDOWS \ No newline at end of file diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsStylusInputInterface.cpp b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsStylusInputInterface.cpp new file mode 100644 index 000000000000..ad1fbc709871 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsStylusInputInterface.cpp @@ -0,0 +1,164 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "WindowsStylusInputInterface.h" +#include "WindowsRealTimeStylusPlugin.h" +#include "Interfaces/IMainFrameModule.h" + +#if PLATFORM_WINDOWS + +class FWindowsStylusInputInterfaceImpl +{ +public: + ~FWindowsStylusInputInterfaceImpl(); + + TComPtr RealTimeStylus; + TSharedPtr StylusPlugin; + void* DLLHandle { nullptr }; +}; + +FWindowsStylusInputInterface::FWindowsStylusInputInterface(FWindowsStylusInputInterfaceImpl* InImpl) +{ + Impl = InImpl; +} + +FWindowsStylusInputInterface::~FWindowsStylusInputInterface() +{ + delete Impl; +} + +int32 FWindowsStylusInputInterface::NumInputDevices() const +{ + return Impl->StylusPlugin->TabletContexts.Num(); +} + +IStylusInputDevice* FWindowsStylusInputInterface::GetInputDevice(int32 Index) const +{ + if (Index < 0 || Index >= Impl->StylusPlugin->TabletContexts.Num()) + { + return nullptr; + } + + return &Impl->StylusPlugin->TabletContexts[Index]; +} + +FWindowsStylusInputInterfaceImpl::~FWindowsStylusInputInterfaceImpl() +{ + RealTimeStylus->RemoveAllStylusSyncPlugins(); + RealTimeStylus.Reset(); + + StylusPlugin.Reset(); + + if (DLLHandle != nullptr) + { + FPlatformProcess::FreeDllHandle(DLLHandle); + DLLHandle = nullptr; + } +} + +static void OnMainFrameCreated(FWindowsStylusInputInterfaceImpl& WindowsImpl, TSharedPtr Window) +{ + TSharedPtr NativeWindow = Window->GetNativeWindow(); + HWND Hwnd = reinterpret_cast(NativeWindow->GetOSWindowHandle()); + + WindowsImpl.RealTimeStylus->put_HWND(reinterpret_cast(Hwnd)); + + // We desire to receive everything, but what we actually will receive is determined in AddTabletContext + TArray DesiredPackets = { + GUID_PACKETPROPERTY_GUID_X, + GUID_PACKETPROPERTY_GUID_Y, + GUID_PACKETPROPERTY_GUID_Z, + GUID_PACKETPROPERTY_GUID_PACKET_STATUS, + GUID_PACKETPROPERTY_GUID_NORMAL_PRESSURE, + GUID_PACKETPROPERTY_GUID_TANGENT_PRESSURE, + GUID_PACKETPROPERTY_GUID_X_TILT_ORIENTATION, + GUID_PACKETPROPERTY_GUID_Y_TILT_ORIENTATION, + GUID_PACKETPROPERTY_GUID_TWIST_ORIENTATION, + GUID_PACKETPROPERTY_GUID_WIDTH, + GUID_PACKETPROPERTY_GUID_HEIGHT, + // Currently not needed. + //GUID_PACKETPROPERTY_GUID_BUTTON_PRESSURE, + //GUID_PACKETPROPERTY_GUID_AZIMUTH_ORIENTATION, + //GUID_PACKETPROPERTY_GUID_ALTITUDE_ORIENTATION, + }; + + WindowsImpl.RealTimeStylus->SetDesiredPacketDescription(DesiredPackets.Num(), DesiredPackets.GetData()); + + WindowsImpl.RealTimeStylus->put_Enabled(Windows::TRUE); +} + +TSharedPtr CreateStylusInputInterface() +{ + FWindowsStylusInputInterfaceImpl* WindowsImpl = new FWindowsStylusInputInterfaceImpl(); + TSharedPtr InterfaceImpl = MakeShareable(new FWindowsStylusInputInterface(WindowsImpl)); + + if (!FWindowsPlatformMisc::CoInitialize()) + { + UE_LOG(LogStylusInput, Error, TEXT("Could not initialize COM library!")); + return nullptr; + } + + // Load RealTimeStylus DLL + const FString InkDLLDirectory = TEXT("C:\\Program Files\\Common Files\\microsoft shared\\ink"); + const FString RTSComDLL = TEXT("RTSCom.dll"); + FPlatformProcess::PushDllDirectory(*InkDLLDirectory); + + WindowsImpl->DLLHandle = FPlatformProcess::GetDllHandle(*(InkDLLDirectory / RTSComDLL)); + if (WindowsImpl->DLLHandle == nullptr) + { + FWindowsPlatformMisc::CoUninitialize(); + UE_LOG(LogStylusInput, Error, TEXT("Could not load RTSCom.dll!")); + return nullptr; + } + + FPlatformProcess::PopDllDirectory(*InkDLLDirectory); + + // Create RealTimeStylus interface + void* OutInstance { nullptr }; + HRESULT hr = ::CoCreateInstance(__uuidof(RealTimeStylus), nullptr, CLSCTX_INPROC, __uuidof(IRealTimeStylus), &OutInstance); + if (FAILED(hr)) + { + FWindowsPlatformMisc::CoUninitialize(); + UE_LOG(LogStylusInput, Error, TEXT("Could not create RealTimeStylus!")); + return nullptr; + } + + WindowsImpl->RealTimeStylus = static_cast(OutInstance); + WindowsImpl->StylusPlugin = MakeShareable(new FWindowsRealTimeStylusPlugin()); + + // Create free-threaded marshaller for the plugin + hr = ::CoCreateFreeThreadedMarshaler(WindowsImpl->StylusPlugin.Get(), + &WindowsImpl->StylusPlugin->FreeThreadedMarshaller); + if (FAILED(hr)) + { + FWindowsPlatformMisc::CoUninitialize(); + UE_LOG(LogStylusInput, Error, TEXT("Could not create FreeThreadedMarshaller!")); + return nullptr; + } + + // Add stylus plugin to the interface + hr = WindowsImpl->RealTimeStylus->AddStylusSyncPlugin(0, WindowsImpl->StylusPlugin.Get()); + if (FAILED(hr)) + { + FWindowsPlatformMisc::CoUninitialize(); + UE_LOG(LogStylusInput, Error, TEXT("Could not add stylus plugin to API!")); + return nullptr; + } + + // Set hook to catch main window creation + IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked("MainFrame"); + if (!MainFrameModule.IsWindowInitialized()) + { + MainFrameModule.OnMainFrameCreationFinished().AddLambda([WindowsImpl](TSharedPtr Window, bool) + { + OnMainFrameCreated(*WindowsImpl, Window); + }); + } + else + { + OnMainFrameCreated(*WindowsImpl, MainFrameModule.GetParentWindow()); + } + + return InterfaceImpl; +} + +#endif // PLATFORM_WINDOWS \ No newline at end of file diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsStylusInputInterface.h b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsStylusInputInterface.h new file mode 100644 index 000000000000..896e9af8ae60 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsStylusInputInterface.h @@ -0,0 +1,22 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "IStylusInputModule.h" + +class FWindowsStylusInputInterfaceImpl; + +class FWindowsStylusInputInterface : public IStylusInputInterfaceInternal +{ +public: + FWindowsStylusInputInterface(FWindowsStylusInputInterfaceImpl* InImpl); + virtual ~FWindowsStylusInputInterface(); + + virtual int32 NumInputDevices() const override; + virtual IStylusInputDevice* GetInputDevice(int32 Index) const override; + +private: + // pImpl to avoid including Windows headers. + FWindowsStylusInputInterfaceImpl* Impl; + TArray MessageHandlers; +}; diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Public/IStylusInputModule.h b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Public/IStylusInputModule.h new file mode 100644 index 000000000000..8c73cfd14230 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Public/IStylusInputModule.h @@ -0,0 +1,84 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "IStylusState.h" + +#include "EditorSubsystem.h" +#include "TickableEditorObject.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" +#include "Widgets/Docking/SDockTab.h" + +#include "IStylusInputModule.generated.h" + +DEFINE_LOG_CATEGORY_STATIC(LogStylusInput, Log, All); + +/** + * Module to handle Wacom-style tablet input using styluses. + */ +class STYLUSINPUT_API IStylusInputModule : public IModuleInterface +{ +public: + + /** + * Retrieve the module instance. + */ + static inline IStylusInputModule& Get() + { + return FModuleManager::LoadModuleChecked("StylusInput"); + } + + /** + * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true. + * + * @return True if the module is loaded and ready to use + */ + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded("StylusInput"); + } +}; + +// This is the interface that all platform-specific implementations must implement. +class IStylusInputInterfaceInternal +{ +public: + virtual IStylusInputDevice* GetInputDevice(int32 Index) const = 0; + virtual int32 NumInputDevices() const = 0; +}; + + +UCLASS() +class STYLUSINPUT_API UStylusInputSubsystem : + public UEditorSubsystem, + public FTickableEditorObject +{ + GENERATED_BODY() +public: + // UEditorSubsystem implementation + virtual void Initialize(FSubsystemCollectionBase& Collection) override; + virtual void Deinitialize() override; + + /** Retrieve the input device that is at the given index, or nullptr if not found. Corresponds to the StylusIndex in IStylusMessageHandler. */ + const IStylusInputDevice* GetInputDevice(int32 Index) const; + + /** Return the number of active input devices. */ + int32 NumInputDevices() const; + + /** Add a message handler to receive messages from the stylus. */ + void AddMessageHandler(IStylusMessageHandler& MessageHandler); + + /** Remove a previously registered message handler. */ + void RemoveMessageHandler(IStylusMessageHandler& MessageHandler); + + // FTickableEditorObject implementation + virtual void Tick(float DeltaTime) override; + virtual TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(UStylusInputSubsystem, STATGROUP_Tickables); } + +private: + TSharedPtr InputInterface; + TArray MessageHandlers; + + TSharedRef OnSpawnPluginTab(const FSpawnTabArgs& Args); +}; \ No newline at end of file diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Public/IStylusState.h b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Public/IStylusState.h new file mode 100644 index 000000000000..c81aa730e2c4 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Public/IStylusState.h @@ -0,0 +1,167 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Math/Vector2D.h" + +/** + * The types of stylus inputs that can be potentially supported by a stylus. + */ +enum class STYLUSINPUT_API EStylusInputType +{ + Position, + Z, + Pressure, + Tilt, + TangentPressure, + ButtonPressure, + Twist, + Size +}; + +/** + * The current state of a single stylus, as sent by IStylusMessageHandler. + */ +class STYLUSINPUT_API FStylusState +{ +public: + + FStylusState() : Position(0, 0), Z(0), Tilt(0, 0), Twist(0), Pressure(0), + TangentPressure(0), Size(0, 0), IsDown(false), IsInverted(false) + { + } + + FStylusState(FVector2D InPosition, float InZ, FVector2D InTilt, float InTwist, + float InPressure, float InTanPressure, FVector2D InSize, + bool InDown, bool InInverted) : + Position(InPosition), Z(InZ), Tilt(InTilt), Twist(InTwist), + Pressure(InPressure), TangentPressure(InTanPressure), Size(InSize), + IsDown(InDown), IsInverted(InInverted) + { + } + + FStylusState(const FStylusState& Other) : + Position(Other.Position), Z(Other.Z), Tilt(Other.Tilt), Twist(Other.Twist), + Pressure(Other.Pressure), TangentPressure(Other.TangentPressure), Size(Other.Size), + IsDown(Other.IsDown), IsInverted(Other.IsInverted) + { + } + + /** + * The current position of the stylus on (or above) the tablet. Always valid. + * This value is in logical coordinates, not pixels. + * The mouse position can be used to retrieve the screenspace value. + * A value of (0,0) is in the top-left of the tablet. + */ + FVector2D GetPosition() const { return Position; } + + /** + * The current height of the stylus above the tablet. + * Defaults to 0 if EStylusInputType::Z is not supported. + */ + float GetZ() const { return Z; } + + /** + * The current tilt along the X axis in degrees, normalized to the range of [-90, 90]. + * Defaults to (0,0) if EStylusInputType::Tilt is not supported. + * A value of (0,0) means that the stylus is perfectly vertical. + * A positive X value means that the stylus is tilted to the right. + * A positive Y value means that the stylus is tilted forwards, away from the user. + * A value of -90 or 90 means that the pen is lying on the tablet, though in practice this isn't widely supported. + */ + FVector2D GetTilt() const { return Tilt; } + + /** + * The current twist amount around the stylus' own axis in degrees, normalized to the range of [0, 360). + * Defaults to 0 if EStylusInputType::Twist is not supported. + * A value of 1 represents a full rotation clockwise. + */ + float GetTwist() const { return Twist; } + + /** + * Get the current pressure along the tablet's normal, usually straight down. + * Normalized to the range [0, 1]. + * Defaults to 0 if EStylusInputType::Pressure is not supported. + */ + float GetPressure() const { return Pressure; } + + /** + * Get the current pressure along the tablet's surface. + * Normalized to the range [0, 1]. + * Defaults to 0 if EStylusInputType::TangentPressure is not supported. + */ + float GetTangentPressure() const { return TangentPressure; } + + /** + * Get the size of the touch in logical coordinates. + * Defaults to (0,0) if EStylusInputType::Size is not supported. + */ + FVector2D GetSize() const { return Size; } + + /** + * Is the stylus inverted? Ie. the eraser part is pointing down. + * Defaults to false if EStylusInputType::Tilt is not supported. + */ + bool IsStylusInverted() const { return IsInverted; } + + /** Is the stylus currently touching the tablet? */ + bool IsStylusDown() const { return IsDown; } + +private: + + FVector2D Position; + float Z; + FVector2D Tilt; + float Twist; + float Pressure; + float TangentPressure; + FVector2D Size; + + bool IsDown : 1; + bool IsInverted : 1; +}; + +/** An input device representing a stylus and its current state. */ +class STYLUSINPUT_API IStylusInputDevice +{ +public: + + virtual ~IStylusInputDevice() {} + + /** + * Get the current stylus state. + */ + const FStylusState& GetCurrentState() const { return CurrentState; } + + /** + * Get the previous stylus state. + */ + const FStylusState& GetPreviousState() const { return PreviousState; } + + /** + * Get the supported inputs of this tablet. + */ + const TArray& GetSupportedInputs() const { return SupportedInputs; } + + /** Update the input device. Not intended to be called externally. */ + virtual void Tick() = 0; + + /** Does the input device need to be ticked? */ + bool IsDirty() const { return Dirty; } + +protected: + FStylusState CurrentState; + FStylusState PreviousState; + TArray SupportedInputs; + bool Dirty : 1; +}; + +/** + * Interface to implement for classes that want to receive messages when a stylus state change occurs. + * Will trigger once per frame. + */ +class IStylusMessageHandler +{ +public: + virtual void OnStylusStateChanged(const FStylusState& NewState, int32 StylusIndex) = 0; +}; diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/StylusInput.Build.cs b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/StylusInput.Build.cs new file mode 100644 index 000000000000..3082635e34af --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/StylusInput.Build.cs @@ -0,0 +1,52 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +namespace UnrealBuildTool.Rules +{ + public class StylusInput : ModuleRules + { + public StylusInput(ReadOnlyTargetRules Target) : base(Target) + { + 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[] + { + "CoreUObject", + "EditorSubsystem", + "Engine", + "UnrealEd" + // ... add other public dependencies that you statically link with here ... + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "MainFrame", + "SlateCore", + "Slate", + "WorkspaceMenuStructure" + // ... add private dependencies that you statically link with here ... + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } + } +} diff --git a/Engine/Plugins/Editor/StylusInput/StylusInput.uplugin b/Engine/Plugins/Editor/StylusInput/StylusInput.uplugin new file mode 100644 index 000000000000..9ef98989f510 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/StylusInput.uplugin @@ -0,0 +1,25 @@ +{ + "FileVersion" : 3, + "Version" : 1, + "VersionName" : "1.0", + "FriendlyName" : "Stylus & Tablet Plugin", + "Description" : "Support for advanced stylus and tablet inputs such as pressure, stylus & tablet buttons, and pen angles.", + "Category" : "Input Devices", + "CreatedBy" : "Epic Games, Inc.", + "CreatedByURL" : "http://epicgames.com", + "DocsURL" : "", + "MarketplaceURL" : "", + "SupportURL" : "", + "EnabledByDefault" : false, + "CanContainContent" : false, + "IsBetaVersion" : true, + "Installed" : false, + "Modules" : + [ + { + "Name" : "StylusInput", + "Type" : "Editor", + "LoadingPhase" : "Default" + } + ] +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcFile.cpp b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcFile.cpp index 11a11b410175..ddfb93450893 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcFile.cpp +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcFile.cpp @@ -21,10 +21,12 @@ #include "Windows/AllowWindowsPlatformTypes.h" #endif +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include "Materials/MaterialInstance.h" THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END #if PLATFORM_WINDOWS #include "Windows/HideWindowsPlatformTypes.h" @@ -498,4 +500,4 @@ UMaterialInterface** FAbcFile::GetMaterialByName(const FString& InMaterialName) return MaterialMap.Find(InMaterialName); } -#undef LOCTEXT_NAMESPACE // "AbcFile" \ No newline at end of file +#undef LOCTEXT_NAMESPACE // "AbcFile" diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImportUtilities.cpp b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImportUtilities.cpp index 56d25eec3273..bb26a3cca1a8 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImportUtilities.cpp +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImportUtilities.cpp @@ -14,6 +14,7 @@ #include "Windows/AllowWindowsPlatformTypes.h" #endif +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include @@ -24,6 +25,7 @@ THIRD_PARTY_INCLUDES_START #include #include THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END #if PLATFORM_WINDOWS #include "Windows/HideWindowsPlatformTypes.h" diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImportUtilities.h b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImportUtilities.h index 5d589c6240ee..27d69b9e28f3 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImportUtilities.h +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImportUtilities.h @@ -9,11 +9,13 @@ #include "Windows/AllowWindowsPlatformTypes.h" #endif +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include #include THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END #if PLATFORM_WINDOWS #include "Windows/HideWindowsPlatformTypes.h" diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImporter.cpp b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImporter.cpp index c1c4624bfb6e..cfca9a9955aa 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImporter.cpp +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImporter.cpp @@ -6,6 +6,7 @@ #include "Windows/WindowsHWrapper.h" #endif +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include @@ -13,6 +14,7 @@ THIRD_PARTY_INCLUDES_START #include #include THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END #include "Misc/Paths.h" #include "Misc/FeedbackContext.h" diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcPolyMesh.cpp b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcPolyMesh.cpp index 45d3a513e7f3..6a2181400caa 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcPolyMesh.cpp +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcPolyMesh.cpp @@ -7,10 +7,12 @@ #include "Modules/ModuleManager.h" #include "MeshUtilities.h" +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END static const ESampleReadFlags ReadAllFlags = ESampleReadFlags::Positions | ESampleReadFlags::Indices | ESampleReadFlags::UVs | ESampleReadFlags::Normals | ESampleReadFlags::Colors | ESampleReadFlags::MaterialIndices; diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcTransform.cpp b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcTransform.cpp index 23d925d45b61..a142beb72451 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcTransform.cpp +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcTransform.cpp @@ -5,10 +5,12 @@ #include "AbcFile.h" +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END FAbcTransform::FAbcTransform(const Alembic::AbcGeom::IXform& InTransform, const FAbcFile* InFile, IAbcObject* InParent /*= nullptr*/) : IAbcObject(InTransform, InFile, InParent), Transform(InTransform), Schema(InTransform.getSchema()) { diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/EigenHelper.h b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/EigenHelper.h index 6e731bcc0ea3..c54b83045667 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/EigenHelper.h +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/EigenHelper.h @@ -13,10 +13,12 @@ #pragma warning(push) #pragma warning(disable:6294) // Ill-defined for-loop: initial condition does not satisfy test. Loop body not executed. #endif +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END #if defined(_MSC_VER) && USING_CODE_ANALYSIS #pragma warning(pop) #endif diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcFile.h b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcFile.h index 2b9f3f830c55..11f8b5ca351f 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcFile.h +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcFile.h @@ -10,12 +10,13 @@ #include "Windows/AllowWindowsPlatformTypes.h" #endif +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include #include - THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END #if PLATFORM_WINDOWS #include "Windows/HideWindowsPlatformTypes.h" @@ -142,4 +143,4 @@ protected: /** Cached Mesh utilities ptr for normal calculations */ IMeshUtilities* MeshUtilities; -}; \ No newline at end of file +}; diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcImporter.h b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcImporter.h index bc677ad0c906..9cb54102b408 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcImporter.h +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcImporter.h @@ -13,9 +13,11 @@ #include "Windows/AllowWindowsPlatformTypes.h" #endif +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END #if PLATFORM_WINDOWS #include "Windows/HideWindowsPlatformTypes.h" @@ -217,4 +219,4 @@ private: /** ABC file representation for currently opened filed */ class FAbcFile* AbcFile; -}; \ No newline at end of file +}; diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcObject.h b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcObject.h index fbb16dae8f78..caa6818c2e78 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcObject.h +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcObject.h @@ -10,10 +10,12 @@ #include "Windows/AllowWindowsPlatformTypes.h" #endif +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END #if PLATFORM_WINDOWS #include "Windows/HideWindowsPlatformTypes.h" @@ -71,4 +73,4 @@ protected: float FrameTimes[MaxNumberOfResidentSamples]; int32 ResidentSampleIndices[MaxNumberOfResidentSamples]; bool InUseSamples[MaxNumberOfResidentSamples]; -}; \ No newline at end of file +}; diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/ThirdParty/Eigen/Eigen/src/Core/MathFunctions.h b/Engine/Plugins/Experimental/AlembicImporter/Source/ThirdParty/Eigen/Eigen/src/Core/MathFunctions.h index 6eb974d41daf..2e70cfa5993c 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/ThirdParty/Eigen/Eigen/src/Core/MathFunctions.h +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/ThirdParty/Eigen/Eigen/src/Core/MathFunctions.h @@ -383,7 +383,7 @@ inline NewType cast(const OldType& x) * Implementation of round * ****************************************************************************/ -#if EIGEN_HAS_CXX11_MATH +#if EIGEN_HAS_CXX11_MATH && !(defined(__ANDROID__) || defined(ANDROID)) template struct round_impl { static inline Scalar run(const Scalar& x) @@ -480,7 +480,7 @@ struct log1p_impl { static inline Scalar run(const Scalar& x) { EIGEN_STATIC_ASSERT_NON_INTEGER(Scalar) - #if EIGEN_HAS_CXX11_MATH + #if EIGEN_HAS_CXX11_MATH && !(defined(__ANDROID__) || defined(ANDROID)) using std::log1p; #endif using std_fallback::log1p; diff --git a/Engine/Plugins/Experimental/BackChannel/Source/BackChannel/Private/Transport/BackChannelConnection.cpp b/Engine/Plugins/Experimental/BackChannel/Source/BackChannel/Private/Transport/BackChannelConnection.cpp index 4f47fc9ce61f..74acd02818b8 100644 --- a/Engine/Plugins/Experimental/BackChannel/Source/BackChannel/Private/Transport/BackChannelConnection.cpp +++ b/Engine/Plugins/Experimental/BackChannel/Source/BackChannel/Private/Transport/BackChannelConnection.cpp @@ -176,7 +176,8 @@ bool FBackChannelConnection::Listen(const int16 Port) if (NewSocket == nullptr && SocketSubsystem != nullptr) { - NewSocket = SocketSubsystem->CreateSocket(NAME_Stream, TEXT("FBackChannelConnection ListenSocket"), true); + TSharedRef BindAddress = Endpoint.ToInternetAddr(); + NewSocket = SocketSubsystem->CreateSocket(NAME_Stream, TEXT("FBackChannelConnection ListenSocket"), BindAddress->GetProtocolType()); if (NewSocket != nullptr) { @@ -189,7 +190,7 @@ bool FBackChannelConnection::Listen(const int16 Port) if (!Error) { - Error = !NewSocket->Bind(*Endpoint.ToInternetAddr()); + Error = !NewSocket->Bind(*BindAddress); } if (!Error) diff --git a/Engine/Plugins/Experimental/CodeEditor/Source/CodeEditor/Private/SCodeEditor.cpp b/Engine/Plugins/Experimental/CodeEditor/Source/CodeEditor/Private/SCodeEditor.cpp index 5f81cd40e478..0d47f2de2726 100644 --- a/Engine/Plugins/Experimental/CodeEditor/Source/CodeEditor/Private/SCodeEditor.cpp +++ b/Engine/Plugins/Experimental/CodeEditor/Source/CodeEditor/Private/SCodeEditor.cpp @@ -32,12 +32,12 @@ void SCodeEditor::Construct(const FArguments& InArgs, UCodeProjectItem* InCodePr HorizontalScrollbar = SNew(SScrollBar) .Orientation(Orient_Horizontal) - .Thickness(FVector2D(10.0f, 10.0f)); + .Thickness(FVector2D(14.0f, 14.0f)); VerticalScrollbar = SNew(SScrollBar) .Orientation(Orient_Vertical) - .Thickness(FVector2D(10.0f, 10.0f)); + .Thickness(FVector2D(14.0f, 14.0f)); ChildSlot [ diff --git a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigDeveloper/Private/Graph/ControlRigGraphNode.cpp b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigDeveloper/Private/Graph/ControlRigGraphNode.cpp index ca973b356a71..78448cccba04 100644 --- a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigDeveloper/Private/Graph/ControlRigGraphNode.cpp +++ b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigDeveloper/Private/Graph/ControlRigGraphNode.cpp @@ -180,13 +180,10 @@ bool UControlRigGraphNode::IsDeprecated() const return Super::IsDeprecated(); } -bool UControlRigGraphNode::ShouldWarnOnDeprecation() const +FEdGraphNodeDeprecationResponse UControlRigGraphNode::GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const { - return true; -} + FEdGraphNodeDeprecationResponse Response = Super::GetDeprecationResponse(DeprecationType); -FString UControlRigGraphNode::GetDeprecationMessage() const -{ UScriptStruct* ScriptStruct = GetUnitScriptStruct(); if (ScriptStruct) { @@ -194,10 +191,13 @@ FString UControlRigGraphNode::GetDeprecationMessage() const ScriptStruct->GetStringMetaDataHierarchical(UControlRig::DeprecatedMetaName, &DeprecatedMetadata); if (!DeprecatedMetadata.IsEmpty()) { - return FString::Printf(TEXT("Warning: This node is deprecated from: %s"), *DeprecatedMetadata); + FFormatNamedArguments Args; + Args.Add(TEXT("DeprecatedMetadata"), FText::FromString(DeprecatedMetadata)); + Response.MessageText = FText::Format(LOCTEXT("ControlRigGraphNodeDeprecationMessage", "Warning: This node is deprecated from: {DeprecatedMetadata}"), Args); } } - return Super::GetDeprecationMessage(); + + return Response; } void UControlRigGraphNode::ReallocatePinsDuringReconstruction(const TArray& OldPins) diff --git a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigDeveloper/Public/Graph/ControlRigGraphNode.h b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigDeveloper/Public/Graph/ControlRigGraphNode.h index e4a50049cea5..77147c3b3d6d 100644 --- a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigDeveloper/Public/Graph/ControlRigGraphNode.h +++ b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigDeveloper/Public/Graph/ControlRigGraphNode.h @@ -177,9 +177,8 @@ public: virtual void AutowireNewNode(UEdGraphPin* FromPin) override; virtual void PrepareForCopying() override; - virtual bool IsDeprecated() const; - virtual bool ShouldWarnOnDeprecation() const; - virtual FString GetDeprecationMessage() const; + virtual bool IsDeprecated() const override; + virtual FEdGraphNodeDeprecationResponse GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const override; /** Set the cached dimensions of this node */ void SetDimensions(const FVector2D& InDimensions) { Dimensions = InDimensions; } diff --git a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/ControlRigEditorModule.cpp b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/ControlRigEditorModule.cpp index 50b4bead4eed..f669c63cfd70 100644 --- a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/ControlRigEditorModule.cpp +++ b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/ControlRigEditorModule.cpp @@ -840,7 +840,7 @@ void FControlRigEditorModule::GetContextMenuActions(const UControlRigGraphNode* LOCTEXT("ClearArray", "Clear"), LOCTEXT("ClearArray_Tooltip", "Clear this array of all of its entries"), FSlateIcon(), - FUIAction(FExecuteAction::CreateUObject(Node, &UControlRigGraphNode::HandleClearArray, Context.Pin->PinName.ToString()))); + FUIAction(FExecuteAction::CreateUObject(const_cast(Node), &UControlRigGraphNode::HandleClearArray, Context.Pin->PinName.ToString()))); Context.MenuBuilder->EndSection(); } @@ -856,13 +856,13 @@ void FControlRigEditorModule::GetContextMenuActions(const UControlRigGraphNode* LOCTEXT("RemoveArrayElement", "Remove"), LOCTEXT("RemoveArrayElement_Tooltip", "Remove this array element"), FSlateIcon(), - FUIAction(FExecuteAction::CreateUObject(Node, &UControlRigGraphNode::HandleRemoveArrayElement, Context.Pin->PinName.ToString()))); + FUIAction(FExecuteAction::CreateUObject(const_cast(Node), &UControlRigGraphNode::HandleRemoveArrayElement, Context.Pin->PinName.ToString()))); Context.MenuBuilder->AddMenuEntry( LOCTEXT("InsertArrayElement", "Insert"), LOCTEXT("InsertArrayElement_Tooltip", "Insert an array element after this one"), FSlateIcon(), - FUIAction(FExecuteAction::CreateUObject(Node, &UControlRigGraphNode::HandleInsertArrayElement, Context.Pin->PinName.ToString()))); + FUIAction(FExecuteAction::CreateUObject(const_cast(Node), &UControlRigGraphNode::HandleInsertArrayElement, Context.Pin->PinName.ToString()))); Context.MenuBuilder->EndSection(); } diff --git a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Graph/SControlRigGraphNode.cpp b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Graph/SControlRigGraphNode.cpp index e8d4f9d349ba..e2679fb4cb3b 100644 --- a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Graph/SControlRigGraphNode.cpp +++ b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Graph/SControlRigGraphNode.cpp @@ -695,7 +695,7 @@ FSlateColor SControlRigGraphNode::GetPinTextColor(TWeakPtr GraphPin) // If there is no schema there is no owning node (or basically this is a deleted node) if (GraphNode) { - if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || !GraphPin.Pin()->IsEditingEnabled()) + if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || !GraphPin.Pin()->IsEditingEnabled() || GraphNode->IsNodeUnrelated()) { return FLinearColor(1.0f, 1.0f, 1.0f, 0.5f); } diff --git a/Engine/Plugins/Experimental/GeometryProcessing/GTEngine.tps b/Engine/Plugins/Experimental/GeometryProcessing/GTEngine.tps new file mode 100644 index 000000000000..4117a04cc26c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/GTEngine.tps @@ -0,0 +1,13 @@ + + + GTEngine + /Engine/Plugins/Experimental/GeometryProcessing + 2D/3D geometry processing and geometric modeling. + https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt + + Licensees + Git + P4 + + /Engine/Source/ThirdParty/Licenses/GTEngine_License.txt + \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/GeometryProcessing.uplugin b/Engine/Plugins/Experimental/GeometryProcessing/GeometryProcessing.uplugin new file mode 100644 index 000000000000..3f4d9c1dd8c3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/GeometryProcessing.uplugin @@ -0,0 +1,43 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "0.1", + "FriendlyName": "GeometryProcessing", + "Description": "Classes and Algorithms for Processing 2D and 3D Geometry", + "Category": "Other", + "CreatedBy": "Epic Games, Inc.", + "CreatedByURL": "http://epicgames.com", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": true, + "IsBetaVersion": true, + "Installed": false, + "Modules": [ + { + "Name": "GeometricObjects", + "Type": "Runtime", + "LoadingPhase": "Default" + }, + { + "Name": "GeometryAlgorithms", + "Type": "Runtime", + "LoadingPhase": "Default" + }, + { + "Name": "DynamicMesh", + "Type": "Runtime", + "LoadingPhase": "Default" + }, + { + "Name": "MeshConversion", + "Type": "Runtime", + "LoadingPhase": "Default" + }, + { + "Name": "MeshSolverUtilities", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ] +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/DynamicMesh.Build.cs b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/DynamicMesh.Build.cs new file mode 100644 index 000000000000..b97899bccd56 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/DynamicMesh.Build.cs @@ -0,0 +1,18 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class DynamicMesh : ModuleRules +{ + public DynamicMesh(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange( + new string[] { + "Core", + "GeometricObjects" + } + ); + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3.cpp new file mode 100644 index 000000000000..3a4aa22a4891 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3.cpp @@ -0,0 +1,1110 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DynamicMesh3.h" +#include "DynamicMeshAttributeSet.h" +#include "Generators/MeshShapeGenerator.h" + + +const int FDynamicMesh3::InvalidID = IndexConstants::InvalidID; // -1; +const int FDynamicMesh3::NonManifoldID = -2; +const int FDynamicMesh3::InvalidGroupID = IndexConstants::InvalidID; // -1; + + +FDynamicMesh3::~FDynamicMesh3() +{ + if (VertexNormals != nullptr) + { + delete VertexNormals; + } + if (VertexColors != nullptr) + { + delete VertexColors; + } + if (VertexUVs != nullptr) + { + delete VertexUVs; + } + if (TriangleGroups != nullptr) + { + delete TriangleGroups; + } + if (AttributeSet != nullptr) + { + delete AttributeSet; + } +} + + + + +FDynamicMesh3::FDynamicMesh3(bool bWantNormals, bool bWantColors, bool bWantUVs, bool bWantTriGroups) +{ + Vertices = TDynamicVector(); + VertexNormals = (bWantNormals) ? new TDynamicVector() : nullptr; + VertexColors = (bWantColors) ? new TDynamicVector() : nullptr; + VertexUVs = (bWantUVs) ? new TDynamicVector() : nullptr; + + VertexEdgeLists = FSmallListSet(); + + VertexRefCounts = FRefCountVector(); + + Triangles = TDynamicVector(); + TriangleEdges = TDynamicVector(); + TriangleRefCounts = FRefCountVector(); + TriangleGroups = (bWantTriGroups) ? new TDynamicVector() : nullptr; + GroupIDCounter = 0; + + Edges = TDynamicVector(); + EdgeRefCounts = FRefCountVector(); + + AttributeSet = nullptr; +} + + +FDynamicMesh3::FDynamicMesh3(const FDynamicMesh3& CopyMesh, bool bCompact, EMeshComponents flags) + : FDynamicMesh3(CopyMesh, bCompact, ((int)flags & (int)EMeshComponents::VertexNormals) != 0, ((int)flags & (int)EMeshComponents::VertexColors) != 0, + ((int)flags & (int)EMeshComponents::VertexUVs) != 0) +{ +} + + + + +// normals/colors/uvs will only be copied if they exist +FDynamicMesh3::FDynamicMesh3(const FDynamicMesh3& CopyMesh, bool bCompact, bool bWantNormals, bool bWantColors, bool bWantUVs, bool bWantAttributes) +{ + VertexNormals = nullptr; + VertexColors = nullptr; + VertexUVs = nullptr; + TriangleGroups = nullptr; + AttributeSet = nullptr; + + if (bCompact) + { + CompactCopy(CopyMesh, bWantNormals, bWantColors, bWantUVs, bWantAttributes); + } + else + { + Copy(CopyMesh, bWantNormals, bWantColors, bWantUVs, bWantAttributes); + } +} + + + +const FDynamicMesh3& FDynamicMesh3::operator=(const FDynamicMesh3& CopyMesh) +{ + Copy(CopyMesh); + return *this; +} + + +FDynamicMesh3::FDynamicMesh3(const FMeshShapeGenerator* Generator) +{ + Copy(Generator); +} +void FDynamicMesh3::Copy(const FMeshShapeGenerator* Generator) +{ + Clear(); + + Vertices = TDynamicVector(); + VertexEdgeLists = FSmallListSet(); + VertexRefCounts = FRefCountVector(); + Triangles = TDynamicVector(); + TriangleEdges = TDynamicVector(); + TriangleRefCounts = FRefCountVector(); + TriangleGroups = new TDynamicVector(); + GroupIDCounter = 0; + Edges = TDynamicVector(); + EdgeRefCounts = FRefCountVector(); + + EnableAttributes(); + FDynamicMeshUVOverlay* UVOverlay = Attributes()->PrimaryUV(); + FDynamicMeshNormalOverlay* NormalOverlay = Attributes()->PrimaryNormals(); + + int NumVerts = Generator->Vertices.Num(); + for (int i = 0; i < NumVerts; ++i) + { + AppendVertex(Generator->Vertices[i]); + } + int NumUVs = Generator->UVs.Num(); + for (int i = 0; i < NumUVs; ++i) + { + UVOverlay->AppendElement(Generator->UVs[i], Generator->UVParentVertex[i]); + } + int NumNormals = Generator->Normals.Num(); + for (int i = 0; i < NumNormals; ++i) + { + NormalOverlay->AppendElement(Generator->Normals[i], Generator->NormalParentVertex[i]); + } + + int NumTris = Generator->Triangles.Num(); + for (int i = 0; i < NumTris; ++i) + { + int tid = AppendTriangle(Generator->Triangles[i], Generator->TrianglePolygonIDs[i]); + check(tid == i); + UVOverlay->SetTriangle(tid, Generator->TriangleUVs[i]); + NormalOverlay->SetTriangle(tid, Generator->TriangleNormals[i]); + } +} + + + + + +void FDynamicMesh3::Copy(const FDynamicMesh3& copy, bool bNormals, bool bColors, bool bUVs, bool bAttributes) +{ + // currently cannot re-use existing attribute buffers + DiscardVertexNormals(); + DiscardVertexColors(); + DiscardVertexUVs(); + DiscardTriangleGroups(); + DiscardAttributes(); + + Vertices = TDynamicVector(copy.Vertices); + + VertexNormals = (bNormals && copy.HasVertexNormals()) ? new TDynamicVector(*copy.VertexNormals) : nullptr; + VertexColors = (bColors && copy.HasVertexColors()) ? new TDynamicVector(*copy.VertexColors) : nullptr; + VertexUVs = (bUVs && copy.HasVertexUVs()) ? new TDynamicVector(*copy.VertexUVs) : nullptr; + + VertexRefCounts = FRefCountVector(copy.VertexRefCounts); + + VertexEdgeLists = FSmallListSet(copy.VertexEdgeLists); + + Triangles = TDynamicVector(copy.Triangles); + TriangleEdges = TDynamicVector(copy.TriangleEdges); + TriangleRefCounts = FRefCountVector(copy.TriangleRefCounts); + TriangleGroups = (copy.HasTriangleGroups()) ? new TDynamicVector(*copy.TriangleGroups) : nullptr; + GroupIDCounter = copy.GroupIDCounter; + + Edges = TDynamicVector(copy.Edges); + EdgeRefCounts = FRefCountVector(copy.EdgeRefCounts); + + if (bAttributes && copy.HasAttributes()) + { + EnableAttributes(); + AttributeSet->Copy(*copy.Attributes()); + } + + Timestamp = FMath::Max(Timestamp + 1, copy.Timestamp); + ShapeTimestamp = TopologyTimestamp = Timestamp; +} + + + + + +void FDynamicMesh3::CompactCopy(const FDynamicMesh3& copy, bool bNormals, bool bColors, bool bUVs, bool bAttributes, FCompactMaps* CompactInfo) +{ + // @todo these are not compacted and hence code cannot work. fix! + check(copy.AttributeSet == nullptr); + check(bNormals == false && bColors == false && bUVs == false); + + // TODO can't do until CompactInfo works + //if ( copy.IsCompact() ) { + // Copy(copy, bNormals, bColors, bUVs); + // CompactInfo ci = CompactInfo() { MapV = IdentityIndexMap() }; + // return ci; + //} + + // currently cannot re-use existing attribute buffers + DiscardVertexNormals(); + DiscardVertexColors(); + DiscardVertexUVs(); + DiscardTriangleGroups(); + + Vertices = TDynamicVector(); + VertexEdgeLists = FSmallListSet(); + VertexRefCounts = FRefCountVector(); + Triangles = TDynamicVector(); + TriangleEdges = TDynamicVector(); + TriangleRefCounts = FRefCountVector(); + Edges = TDynamicVector(); + EdgeRefCounts = FRefCountVector(); + GroupIDCounter = 0; + + TriangleGroups = (copy.HasTriangleGroups()) ? new TDynamicVector(*copy.TriangleGroups) : nullptr; + + // [TODO] if we knew some of these were dense we could copy directly... + + FVertexInfo vinfo; + TArray mapV; mapV.SetNum(copy.MaxVertexID()); + for (int vid : copy.VertexIndicesItr()) + { + copy.GetVertex(vid, vinfo, bNormals, bColors, bUVs); + mapV[vid] = AppendVertex(vinfo); + } + + // [TODO] would be much faster to explicitly copy triangle & edge data structures!! + if (copy.HasTriangleGroups()) + { + EnableTriangleGroups(0); + } + for (int tid : copy.TriangleIndicesItr()) + { + FIndex3i t = copy.GetTriangle(tid); + t = FIndex3i(mapV[t.A], mapV[t.B], mapV[t.C]); + int g = (copy.HasTriangleGroups()) ? copy.GetTriangleGroup(tid) : InvalidID; + AppendTriangle(t, g); + GroupIDCounter = FMath::Max(GroupIDCounter, g + 1); + } + + // generate compact info...? + if (CompactInfo != nullptr) + { + for (int vid : copy.VertexIndicesItr()) + { + CompactInfo->MapV.Add(vid, mapV[vid]); + } + } + + Timestamp = FMath::Max(Timestamp + 1, copy.Timestamp); + ShapeTimestamp = TopologyTimestamp = Timestamp; +} + + + + +void FDynamicMesh3::Clear() +{ + Vertices.Clear(); + VertexEdgeLists = FSmallListSet(); + VertexRefCounts = FRefCountVector(); + Triangles.Clear(); + TriangleEdges.Clear(); + TriangleRefCounts = FRefCountVector(); + Edges.Clear(); + EdgeRefCounts = FRefCountVector(); + + DiscardTriangleGroups(); + DiscardVertexColors(); + DiscardVertexUVs(); + DiscardVertexNormals(); + DiscardAttributes(); + + Timestamp = ShapeTimestamp = TopologyTimestamp = 0; + GroupIDCounter = 0; + CachedBoundingBoxTimestamp = -1; + bIsClosedCached = false; + CachedIsClosedTimestamp = -1; +} + + + +int FDynamicMesh3::GetComponentsFlags() const +{ + int c = 0; + if (HasVertexNormals()) + { + c |= (int)EMeshComponents::VertexNormals; + } + if (HasVertexColors()) + { + c |= (int)EMeshComponents::VertexColors; + } + if (HasVertexUVs()) + { + c |= (int)EMeshComponents::VertexUVs; + } + if (HasTriangleGroups()) + { + c |= (int)EMeshComponents::FaceGroups; + } + return c; +} + + + + + + + + + + +void FDynamicMesh3::EnableVertexNormals(const FVector3f& InitialNormal) +{ + if (HasVertexNormals()) + { + return; + } + VertexNormals = new TDynamicVector(); + int NV = MaxVertexID(); + VertexNormals->Resize(3 * NV); + for (int i = 0; i < NV; ++i) + { + int vi = 3 * i; + (*VertexNormals)[vi] = InitialNormal.X; + (*VertexNormals)[vi + 1] = InitialNormal.Y; + (*VertexNormals)[vi + 2] = InitialNormal.Z; + } +} + +void FDynamicMesh3::DiscardVertexNormals() +{ + if (VertexNormals != nullptr) + { + delete VertexNormals; + VertexNormals = nullptr; + } +} + + + + + +void FDynamicMesh3::EnableVertexColors(const FVector3f& InitialColor) +{ + if (HasVertexColors()) + { + return; + } + VertexColors = new TDynamicVector(); + int NV = MaxVertexID(); + VertexColors->Resize(3 * NV); + for (int i = 0; i < NV; ++i) + { + int vi = 3 * i; + (*VertexColors)[vi] = InitialColor.X; + (*VertexColors)[vi + 1] = InitialColor.Y; + (*VertexColors)[vi + 2] = InitialColor.Z; + } +} + +void FDynamicMesh3::DiscardVertexColors() +{ + if (VertexColors != nullptr) + { + delete VertexColors; + VertexColors = nullptr; + } +} + + + + +void FDynamicMesh3::EnableVertexUVs(const FVector2f& InitialUV) +{ + if (HasVertexUVs()) + { + return; + } + VertexUVs = new TDynamicVector(); + int NV = MaxVertexID(); + VertexUVs->Resize(2 * NV); + for (int i = 0; i < NV; ++i) + { + int vi = 2 * i; + (*VertexUVs)[vi] = InitialUV.X; + (*VertexUVs)[vi + 1] = InitialUV.Y; + } +} + +void FDynamicMesh3::DiscardVertexUVs() +{ + if (VertexUVs != nullptr) + { + delete VertexUVs; + VertexUVs = nullptr; + } +} + + + +void FDynamicMesh3::EnableTriangleGroups(int InitialGroup) +{ + if (HasTriangleGroups()) + { + return; + } + TriangleGroups = new TDynamicVector(); + int NT = MaxTriangleID(); + TriangleGroups->Resize(NT); + for (int i = 0; i < NT; ++i) + { + (*TriangleGroups)[i] = InitialGroup; + } + GroupIDCounter = 0; +} + +void FDynamicMesh3::DiscardTriangleGroups() +{ + if (TriangleGroups != nullptr) + { + delete TriangleGroups; + TriangleGroups = nullptr; + } + GroupIDCounter = 0; +} + + + + +void FDynamicMesh3::EnableAttributes() +{ + if (HasAttributes()) + { + return; + } + AttributeSet = new FDynamicMeshAttributeSet(this); + AttributeSet->Initialize(MaxVertexID(), MaxTriangleID()); +} + + +void FDynamicMesh3::DiscardAttributes() +{ + if (AttributeSet != nullptr) + { + delete AttributeSet; + AttributeSet = nullptr; + } +} + + + + + + +bool FDynamicMesh3::GetVertex(int vID, FVertexInfo& vinfo, bool bWantNormals, bool bWantColors, bool bWantUVs) const +{ + if (VertexRefCounts.IsValid(vID) == false) + { + return false; + } + int vi = 3 * vID; + vinfo.Position = FVector3d(Vertices[vi], Vertices[vi + 1], Vertices[vi + 2]); + vinfo.bHaveN = vinfo.bHaveUV = vinfo.bHaveC = false; + if (HasVertexNormals() && bWantNormals) + { + vinfo.bHaveN = true; + vinfo.Normal = FVector3f((*VertexNormals)[vi], (*VertexNormals)[vi + 1], (*VertexNormals)[vi + 2]); + } + if (HasVertexColors() && bWantColors) + { + vinfo.bHaveC = true; + vinfo.Color = FVector3f((*VertexColors)[vi], (*VertexColors)[vi + 1], (*VertexColors)[vi + 2]); + } + if (HasVertexUVs() && bWantUVs) + { + vinfo.bHaveUV = true; + vinfo.UV = FVector2f((*VertexUVs)[2*vID], (*VertexUVs)[2*vID + 1]); + } + return true; +} + + +int FDynamicMesh3::GetMaxVtxEdgeCount() const +{ + int max = 0; + for (int vid : VertexIndicesItr()) + { + max = FMath::Max(max, VertexEdgeLists.GetCount(vid)); + } + return max; +} + + + +FVertexInfo FDynamicMesh3::GetVertexInfo(int i) const +{ + FVertexInfo vi = FVertexInfo(); + vi.Position = GetVertex(i); + vi.bHaveN = vi.bHaveC = vi.bHaveUV = false; + if (HasVertexNormals()) + { + vi.bHaveN = true; + vi.Normal = GetVertexNormal(i); + } + if (HasVertexColors()) + { + vi.bHaveC = true; + vi.Color = GetVertexColor(i); + } + if (HasVertexUVs()) + { + vi.bHaveUV = true; + vi.UV = GetVertexUV(i); + } + return vi; +} + + + +FIndex3i FDynamicMesh3::GetTriNeighbourTris(int tID) const +{ + if (TriangleRefCounts.IsValid(tID)) + { + int tei = 3 * tID; + FIndex3i nbr_t = FIndex3i::Zero(); + for (int j = 0; j < 3; ++j) + { + int ei = 4 * TriangleEdges[tei + j]; + nbr_t[j] = (Edges[ei + 2] == tID) ? Edges[ei + 3] : Edges[ei + 2]; + } + return nbr_t; + } + else + { + return InvalidTriangle(); + } +} + + + +/** Enumerate triangle one ring at a vertex */ +FDynamicMesh3::vtx_triangles_enumerable FDynamicMesh3::VtxTrianglesItr(int vID) const +{ + check(VertexRefCounts.IsValid(vID)); + + TFunction expand_f = [this,vID](int eid, int& k) + { + int vOther = GetOtherEdgeVertex(eid, vID); + int i = 4 * eid; + if (k == -1) + { + int et0 = Edges[i + 2]; + if (TriHasSequentialVertices(et0, vID, vOther)) + { + k = 0; + return et0; + } + } + if (k != 1) + { + int et1 = Edges[i + 3]; + if (et1 != InvalidID && TriHasSequentialVertices(et1, vID, vOther)) + { + k = 1; + return et1; + } + } + k = -1; + return -1; + }; + + return vtx_triangles_enumerable(VertexEdgeLists.Values(vID), expand_f); +} + + + + + + + + +FString FDynamicMesh3::MeshInfoString() +{ + FString VtxString = FString::Printf(TEXT("Vertices count %d max %d %s VtxEdges %s"), + VertexCount(), MaxVertexID(), *VertexRefCounts.UsageStats(), *(VertexEdgeLists.MemoryUsage())); + FString TriString = FString::Printf(TEXT("Triangles count %d max %d %s"), + TriangleCount(), MaxTriangleID(), *TriangleRefCounts.UsageStats()); + FString EdgeString = FString::Printf(TEXT("Edges count %d max %d %s"), + EdgeCount(), MaxEdgeID(), *EdgeRefCounts.UsageStats()); + FString AttribString = FString::Printf(TEXT("VtxNormals %d VtxColors %d VtxUVs %d TriGroups %d Attributes %d"), + HasVertexNormals(), HasVertexColors(), HasVertexUVs(), HasTriangleGroups(), HasAttributes()); + FString InfoString = FString::Printf(TEXT("Closed %d Compact %d Timestamp %d ShapeTimestamp %d TopologyTimestamp %d MaxGroupID %d"), + GetCachedIsClosed(), IsCompact(), GetTimestamp(), GetShapeTimestamp(), GetTopologyTimestamp(), MaxGroupID()); + + return VtxString + "\n" + TriString + "\n" + EdgeString + "\n" + AttribString + "\n" + InfoString; +} + + + + +bool FDynamicMesh3::IsSameMesh(const FDynamicMesh3& m2, bool bCheckConnectivity, bool bCheckEdgeIDs, + bool bCheckNormals, bool bCheckColors, bool bCheckUVs, bool bCheckGroups, + float Epsilon) +{ + if (VertexCount() != m2.VertexCount()) + { + return false; + } + if (TriangleCount() != m2.TriangleCount()) + { + return false; + } + for (int vid : VertexIndicesItr()) + { + if (m2.IsVertex(vid) == false || VectorUtil::EpsilonEqual(GetVertex(vid), m2.GetVertex(vid), (double)Epsilon) == false) + { + return false; + } + } + for (int tid : TriangleIndicesItr()) + { + if (m2.IsTriangle(tid) == false || (GetTriangle(tid) != m2.GetTriangle(tid))) + { + return false; + } + } + if (bCheckConnectivity) + { + for (int eid : EdgeIndicesItr()) + { + FIndex4i e = GetEdge(eid); + int other_eid = m2.FindEdge(e[0], e[1]); + if (other_eid == InvalidID) + { + return false; + } + FIndex4i oe = m2.GetEdge(other_eid); + if (FMath::Min(e[2], e[3]) != FMath::Min(oe[2], oe[3]) || FMath::Max(e[2], e[3]) != FMath::Max(oe[2], oe[3])) + { + return false; + } + } + } + if (bCheckEdgeIDs) + { + if (EdgeCount() != m2.EdgeCount()) + { + return false; + } + for (int eid : EdgeIndicesItr()) + { + if (m2.IsEdge(eid) == false || GetEdge(eid) != m2.GetEdge(eid)) + { + return false; + } + } + } + if (bCheckNormals) + { + if (HasVertexNormals() != m2.HasVertexNormals()) + { + return false; + } + if (HasVertexNormals()) + { + for (int vid : VertexIndicesItr()) + { + if (VectorUtil::EpsilonEqual(GetVertexNormal(vid), m2.GetVertexNormal(vid), Epsilon) == false) + { + return false; + } + } + } + } + if (bCheckColors) + { + if (HasVertexColors() != m2.HasVertexColors()) + { + return false; + } + if (HasVertexColors()) + { + for (int vid : VertexIndicesItr()) + { + if (VectorUtil::EpsilonEqual(GetVertexColor(vid), m2.GetVertexColor(vid), Epsilon) == false) + { + return false; + } + } + } + } + if (bCheckUVs) + { + if (HasVertexUVs() != m2.HasVertexUVs()) + { + return false; + } + if (HasVertexUVs()) + { + for (int vid : VertexIndicesItr()) + { + if (VectorUtil::EpsilonEqual(GetVertexUV(vid), m2.GetVertexUV(vid), Epsilon) == false) + { + return false; + } + } + } + } + if (bCheckGroups) + { + if (HasTriangleGroups() != m2.HasTriangleGroups()) + { + return false; + } + if (HasTriangleGroups()) + { + for (int tid : TriangleIndicesItr()) + { + if (GetTriangleGroup(tid) != m2.GetTriangleGroup(tid)) + { + return false; + } + } + } + } + return true; +} + + + + + + + +bool FDynamicMesh3::CheckValidity(bool bAllowNonManifoldVertices, EValidityCheckFailMode FailMode) const +{ + + TArray triToVtxRefs; + triToVtxRefs.SetNum(MaxVertexID()); + //int[] triToVtxRefs = new int[this.MaxVertexID]; + + bool is_ok = true; + TFunction CheckOrFailF = [&](bool b) + { + is_ok = is_ok && b; + }; + if (FailMode == EValidityCheckFailMode::Check) + { + CheckOrFailF = [&](bool b) + { + checkf(b, TEXT("FEdgeLoop::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + else if (FailMode == EValidityCheckFailMode::Ensure) + { + CheckOrFailF = [&](bool b) + { + ensureMsgf(b, TEXT("FEdgeLoop::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + + + for (int tID : TriangleIndicesItr()) + { + CheckOrFailF(IsTriangle(tID)); + CheckOrFailF(TriangleRefCounts.GetRefCount(tID) == 1); + + // vertices must exist + FIndex3i tv = GetTriangle(tID); + for (int j = 0; j < 3; ++j) + { + CheckOrFailF(IsVertex(tv[j])); + triToVtxRefs[tv[j]] += 1; + } + + // edges must exist and reference this tri + FIndex3i e; + for (int j = 0; j < 3; ++j) + { + int a = tv[j], b = tv[(j + 1) % 3]; + e[j] = FindEdge(a, b); + CheckOrFailF(e[j] != InvalidID); + CheckOrFailF(EdgeHasTriangle(e[j], tID)); + CheckOrFailF(e[j] == FindEdgeFromTri(a, b, tID)); + } + CheckOrFailF(e[0] != e[1] && e[0] != e[2] && e[1] != e[2]); + + // tri nbrs must exist and reference this tri, or same edge must be boundary edge + FIndex3i te = GetTriEdges(tID); + for (int j = 0; j < 3; ++j) + { + int eid = te[j]; + CheckOrFailF(IsEdge(eid)); + int tOther = GetOtherEdgeTriangle(eid, tID); + if (tOther == InvalidID) + { + CheckOrFailF(IsBoundaryTriangle(tID)); + continue; + } + + CheckOrFailF(TriHasNeighbourTri(tOther, tID) == true); + + // edge must have same two verts as tri for same index + int a = tv[j], b = tv[(j + 1) % 3]; + FIndex2i ev = GetEdgeV(te[j]); + CheckOrFailF(IndexUtil::SamePairUnordered(a, b, ev[0], ev[1])); + + // also check that nbr edge has opposite orientation + FIndex3i othertv = GetTriangle(tOther); + int found = IndexUtil::FindTriOrderedEdge(b, a, othertv); + CheckOrFailF(found != InvalidID); + } + } + + + // edge verts/tris must exist + for (int eID : EdgeIndicesItr()) + { + CheckOrFailF(IsEdge(eID)); + CheckOrFailF(EdgeRefCounts.GetRefCount(eID) == 1); + FIndex2i ev = GetEdgeV(eID); + FIndex2i et = GetEdgeT(eID); + CheckOrFailF(IsVertex(ev[0])); + CheckOrFailF(IsVertex(ev[1])); + CheckOrFailF(et[0] != InvalidID); + CheckOrFailF(ev[0] < ev[1]); + CheckOrFailF(IsTriangle(et[0])); + if (et[1] != InvalidID) + { + CheckOrFailF(IsTriangle(et[1])); + } + } + + // verify compact check + bool is_compact = VertexRefCounts.IsDense(); + if (is_compact) + { + for (int vid = 0; vid < (int)Vertices.GetLength() / 3; ++vid) + { + CheckOrFailF(VertexRefCounts.IsValid(vid)); + } + } + + // vertex edges must exist and reference this vert + for (int vID : VertexIndicesItr()) + { + CheckOrFailF(IsVertex(vID)); + + FVector3d v = GetVertex(vID); + CheckOrFailF(FMathd::IsNaN(v.SquaredLength()) == false); + CheckOrFailF(FMathd::IsFinite(v.SquaredLength())); + + for (int edgeid : VertexEdgeLists.Values(vID)) + { + CheckOrFailF(IsEdge(edgeid)); + CheckOrFailF(EdgeHasVertex(edgeid, vID)); + + int otherV = GetOtherEdgeVertex(edgeid, vID); + int e2 = FindEdge(vID, otherV); + CheckOrFailF(e2 != InvalidID); + CheckOrFailF(e2 == edgeid); + e2 = FindEdge(otherV, vID); + CheckOrFailF(e2 != InvalidID); + CheckOrFailF(e2 == edgeid); + } + + for (int nbr_vid : VtxVerticesItr(vID)) + { + CheckOrFailF(IsVertex(nbr_vid)); + int edge = FindEdge(vID, nbr_vid); + CheckOrFailF(IsEdge(edge)); + } + + TArray vTris, vTris2; + GetVtxTriangles(vID, vTris, false); + GetVtxTriangles(vID, vTris2, true); + CheckOrFailF(vTris.Num() == vTris2.Num()); + //System.Console.WriteLine(string.Format("{0} {1} {2}", vID, vTris.Count, GetVtxEdges(vID).Count)); + if (bAllowNonManifoldVertices) + { + CheckOrFailF(vTris.Num() <= GetVtxEdgeCount(vID)); + } + else + { + CheckOrFailF(vTris.Num() == GetVtxEdgeCount(vID) || vTris.Num() == GetVtxEdgeCount(vID) - 1); + } + CheckOrFailF(VertexRefCounts.GetRefCount(vID) == vTris.Num() + 1); + CheckOrFailF(triToVtxRefs[vID] == vTris.Num()); + for (int tID : vTris) + { + CheckOrFailF(TriangleHasVertex(tID, vID)); + } + + // check that edges around vert only references tris above, and reference all of them! + TArray vRemoveTris(vTris); + for (int edgeid : VertexEdgeLists.Values(vID)) + { + FIndex2i edget = GetEdgeT(edgeid); + CheckOrFailF(vTris.Contains(edget[0])); + if (edget[1] != InvalidID) + { + CheckOrFailF(vTris.Contains(edget[1])); + } + vRemoveTris.Remove(edget[0]); + if (edget[1] != InvalidID) + { + vRemoveTris.Remove(edget[1]); + } + } + CheckOrFailF(vRemoveTris.Num() == 0); + } + + if (HasAttributes()) + { + Attributes()->PrimaryUV()->CheckValidity(true, FailMode); + Attributes()->PrimaryNormals()->CheckValidity(true, FailMode); + } + + return is_ok; +} + + + + + + + + + +int FDynamicMesh3::AddEdgeInternal(int vA, int vB, int tA, int tB) +{ + if (vB < vA) { + int t = vB; vB = vA; vA = t; + } + int eid = EdgeRefCounts.Allocate(); + int i = 4 * eid; + Edges.InsertAt(vA, i); + Edges.InsertAt(vB, i + 1); + Edges.InsertAt(tA, i + 2); + Edges.InsertAt(tB, i + 3); + + VertexEdgeLists.Insert(vA, eid); + VertexEdgeLists.Insert(vB, eid); + return eid; +} + + +int FDynamicMesh3::AddTriangleInternal(int a, int b, int c, int e0, int e1, int e2) +{ + int tid = TriangleRefCounts.Allocate(); + int i = 3 * tid; + Triangles.InsertAt(c, i + 2); + Triangles.InsertAt(b, i + 1); + Triangles.InsertAt(a, i); + TriangleEdges.InsertAt(e2, i + 2); + TriangleEdges.InsertAt(e1, i + 1); + TriangleEdges.InsertAt(e0, i + 0); + return tid; +} + + +int FDynamicMesh3::ReplaceEdgeVertex(int eID, int vOld, int vNew) +{ + int i = 4 * eID; + int a = Edges[i], b = Edges[i + 1]; + if (a == vOld) + { + Edges[i] = FMath::Min(b, vNew); + Edges[i + 1] = FMath::Max(b, vNew); + return 0; + } + else if (b == vOld) + { + Edges[i] = FMath::Min(a, vNew); + Edges[i + 1] = FMath::Max(a, vNew); + return 1; + } + else + { + return -1; + } +} + + +int FDynamicMesh3::ReplaceEdgeTriangle(int eID, int tOld, int tNew) +{ + int i = 4 * eID; + int a = Edges[i + 2], b = Edges[i + 3]; + if (a == tOld) { + if (tNew == InvalidID) + { + Edges[i + 2] = b; + Edges[i + 3] = InvalidID; + } + else + { + Edges[i + 2] = tNew; + } + return 0; + } + else if (b == tOld) + { + Edges[i + 3] = tNew; + return 1; + } + else + { + return -1; + } +} + +int FDynamicMesh3::ReplaceTriangleEdge(int tID, int eOld, int eNew) +{ + int i = 3 * tID; + if (TriangleEdges[i] == eOld) + { + TriangleEdges[i] = eNew; + return 0; + } + else if (TriangleEdges[i + 1] == eOld) + { + TriangleEdges[i + 1] = eNew; + return 1; + } + else if (TriangleEdges[i + 2] == eOld) + { + TriangleEdges[i + 2] = eNew; + return 2; + } + else + { + return -1; + } +} + + + +//! returns edge ID +int FDynamicMesh3::FindTriangleEdge(int tID, int vA, int vB) const +{ + int i = 3 * tID; + int tv0 = Triangles[i], tv1 = Triangles[i + 1]; + if (IndexUtil::SamePairUnordered(tv0, tv1, vA, vB)) return TriangleEdges[3 * tID]; + int tv2 = Triangles[i + 2]; + if (IndexUtil::SamePairUnordered(tv1, tv2, vA, vB)) return TriangleEdges[3 * tID + 1]; + if (IndexUtil::SamePairUnordered(tv2, tv0, vA, vB)) return TriangleEdges[3 * tID + 2]; + return InvalidID; +} + + + + + +int FDynamicMesh3::FindEdge(int vA, int vB) const +{ + check(IsVertex(vA)); + check(IsVertex(vB)); + + // [RMS] edge vertices must be sorted (min,max), + // that means we only need one index-check in inner loop. + // commented out code is robust to incorrect ordering, but slower. + int vO = FMath::Max(vA, vB); + int vI = FMath::Min(vA, vB); + for (int eid : VertexEdgeLists.Values(vI)) + { + if (Edges[4 * eid + 1] == vO) + { + //if (DoesEdgeHaveVertex(eid, vO)) + return eid; + } + } + return InvalidID; + + // this is slower, likely because it creates func<> every time. can we do w/o that? + //return VertexEdgeLists.Find(vI, (eid) => { return Edges[4 * eid + 1] == vO; }, InvalidID); +} + +int FDynamicMesh3::FindEdgeFromTri(int vA, int vB, int tID) const +{ + int i = 3 * tID; + int t0 = Triangles[i], t1 = Triangles[i + 1]; + if (IndexUtil::SamePairUnordered(vA, vB, t0, t1)) + { + return TriangleEdges[i]; + } + int t2 = Triangles[i + 2]; + if (IndexUtil::SamePairUnordered(vA, vB, t1, t2)) + { + return TriangleEdges[i + 1]; + } + if (IndexUtil::SamePairUnordered(vA, vB, t2, t0)) + { + return TriangleEdges[i + 2]; + } + return InvalidID; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3_Edits.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3_Edits.cpp new file mode 100644 index 000000000000..630333f0f72f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3_Edits.cpp @@ -0,0 +1,1837 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DynamicMesh3.h" +#include "DynamicMeshAttributeSet.h" + + + + +int FDynamicMesh3::AppendVertex(const FVertexInfo& VtxInfo) +{ + int vid = VertexRefCounts.Allocate(); + int i = 3 * vid; + Vertices.InsertAt(VtxInfo.Position[2], i + 2); + Vertices.InsertAt(VtxInfo.Position[1], i + 1); + Vertices.InsertAt(VtxInfo.Position[0], i); + + if (HasVertexNormals()) + { + FVector3f n = (VtxInfo.bHaveN) ? VtxInfo.Normal : FVector3f::UnitY(); + VertexNormals->InsertAt(n[2], i + 2); + VertexNormals->InsertAt(n[1], i + 1); + VertexNormals->InsertAt(n[0], i); + } + + if (HasVertexColors()) + { + FVector3f c = (VtxInfo.bHaveC) ? VtxInfo.Color : FVector3f::One(); + VertexColors->InsertAt(c[2], i + 2); + VertexColors->InsertAt(c[1], i + 1); + VertexColors->InsertAt(c[0], i); + } + + if (HasVertexUVs()) + { + FVector2f u = (VtxInfo.bHaveUV) ? VtxInfo.UV : FVector2f::Zero(); + int j = 2 * vid; + VertexUVs->InsertAt(u[1], j + 1); + VertexUVs->InsertAt(u[0], j); + } + + AllocateEdgesList(vid); + + UpdateTimeStamp(true, true); + return vid; +} + + + +int FDynamicMesh3::AppendVertex(const FDynamicMesh3& from, int fromVID) +{ + int bi = 3 * fromVID; + + int vid = VertexRefCounts.Allocate(); + int i = 3 * vid; + Vertices.InsertAt(from.Vertices[bi + 2], i + 2); + Vertices.InsertAt(from.Vertices[bi + 1], i + 1); + Vertices.InsertAt(from.Vertices[bi], i); + if (HasVertexNormals()) + { + if (from.HasVertexNormals()) + { + VertexNormals->InsertAt((*from.VertexNormals)[bi + 2], i + 2); + VertexNormals->InsertAt((*from.VertexNormals)[bi + 1], i + 1); + VertexNormals->InsertAt((*from.VertexNormals)[bi], i); + } + else + { + VertexNormals->InsertAt(0, i + 2); + VertexNormals->InsertAt(1, i + 1); // y-up + VertexNormals->InsertAt(0, i); + } + } + + if (HasVertexColors()) + { + if (from.HasVertexColors()) + { + VertexColors->InsertAt((*from.VertexColors)[bi + 2], i + 2); + VertexColors->InsertAt((*from.VertexColors)[bi + 1], i + 1); + VertexColors->InsertAt((*from.VertexColors)[bi], i); + } + else + { + VertexColors->InsertAt(1, i + 2); + VertexColors->InsertAt(1, i + 1); // white + VertexColors->InsertAt(1, i); + } + } + + if (HasVertexUVs()) + { + int j = 2 * vid; + if (from.HasVertexUVs()) + { + int bj = 2 * fromVID; + VertexUVs->InsertAt((*from.VertexUVs)[bj + 1], j + 1); + VertexUVs->InsertAt((*from.VertexUVs)[bj], j); + } + else + { + VertexUVs->InsertAt(0, j + 1); + VertexUVs->InsertAt(0, j); + } + } + + AllocateEdgesList(vid); + + UpdateTimeStamp(true, true); + return vid; +} + + + +EMeshResult FDynamicMesh3::InsertVertex(int vid, const FVertexInfo& info, bool bUnsafe) +{ + if (VertexRefCounts.IsValid(vid)) + { + return EMeshResult::Failed_VertexAlreadyExists; + } + + bool bOK = (bUnsafe) ? VertexRefCounts.AllocateAtUnsafe(vid) : + VertexRefCounts.AllocateAt(vid); + if (bOK == false) + { + return EMeshResult::Failed_CannotAllocateVertex; + } + + int i = 3 * vid; + Vertices.InsertAt(info.Position[2], i + 2); + Vertices.InsertAt(info.Position[1], i + 1); + Vertices.InsertAt(info.Position[0], i); + + if (HasVertexNormals()) + { + FVector3f n = (info.bHaveN) ? info.Normal : FVector3f::UnitY(); + VertexNormals->InsertAt(n[2], i + 2); + VertexNormals->InsertAt(n[1], i + 1); + VertexNormals->InsertAt(n[0], i); + } + + if (HasVertexColors()) + { + FVector3f c = (info.bHaveC) ? info.Color : FVector3f::One(); + VertexColors->InsertAt(c[2], i + 2); + VertexColors->InsertAt(c[1], i + 1); + VertexColors->InsertAt(c[0], i); + } + + if (HasVertexUVs()) + { + FVector2f u = (info.bHaveUV) ? info.UV : FVector2f::Zero(); + int j = 2 * vid; + VertexUVs->InsertAt(u[1], j + 1); + VertexUVs->InsertAt(u[0], j); + } + + AllocateEdgesList(vid); + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + + + +int FDynamicMesh3::AppendTriangle(const FIndex3i& tv, int gid) +{ + if (IsVertex(tv[0]) == false || IsVertex(tv[1]) == false || IsVertex(tv[2]) == false) + { + check(false); + return InvalidID; + } + if (tv[0] == tv[1] || tv[0] == tv[2] || tv[1] == tv[2]) + { + check(false); + return InvalidID; + } + + // look up edges. if any already have two triangles, this would + // create non-manifold geometry and so we do not allow it + int e0 = FindEdge(tv[0], tv[1]); + int e1 = FindEdge(tv[1], tv[2]); + int e2 = FindEdge(tv[2], tv[0]); + if ((e0 != InvalidID && IsBoundaryEdge(e0) == false) + || (e1 != InvalidID && IsBoundaryEdge(e1) == false) + || (e2 != InvalidID && IsBoundaryEdge(e2) == false)) + { + return NonManifoldID; + } + + bool bHasGroups = HasTriangleGroups(); // have to check before changing .triangles + + // now safe to insert triangle + int tid = TriangleRefCounts.Allocate(); + int i = 3 * tid; + Triangles.InsertAt(tv[2], i + 2); + Triangles.InsertAt(tv[1], i + 1); + Triangles.InsertAt(tv[0], i); + if (bHasGroups) + { + TriangleGroups->InsertAt(gid, tid); + GroupIDCounter = FMath::Max(GroupIDCounter, gid + 1); + } + + // increment ref counts and update/create edges + VertexRefCounts.Increment(tv[0]); + VertexRefCounts.Increment(tv[1]); + VertexRefCounts.Increment(tv[2]); + + AddTriangleEdge(tid, tv[0], tv[1], 0, e0); + AddTriangleEdge(tid, tv[1], tv[2], 1, e1); + AddTriangleEdge(tid, tv[2], tv[0], 2, e2); + + if (HasAttributes()) + { + Attributes()->OnNewTriangle(tid, false); + } + + UpdateTimeStamp(true, true); + return tid; +} + + + + +EMeshResult FDynamicMesh3::InsertTriangle(int tid, const FIndex3i& tv, int gid, bool bUnsafe) +{ + if (TriangleRefCounts.IsValid(tid)) + { + return EMeshResult::Failed_TriangleAlreadyExists; + } + + if (IsVertex(tv[0]) == false || IsVertex(tv[1]) == false || IsVertex(tv[2]) == false) + { + check(false); + return EMeshResult::Failed_NotAVertex; + } + if (tv[0] == tv[1] || tv[0] == tv[2] || tv[1] == tv[2]) + { + check(false); + return EMeshResult::Failed_InvalidNeighbourhood; + } + + // look up edges. if any already have two triangles, this would + // create non-manifold geometry and so we do not allow it + int e0 = FindEdge(tv[0], tv[1]); + int e1 = FindEdge(tv[1], tv[2]); + int e2 = FindEdge(tv[2], tv[0]); + if ((e0 != InvalidID && IsBoundaryEdge(e0) == false) + || (e1 != InvalidID && IsBoundaryEdge(e1) == false) + || (e2 != InvalidID && IsBoundaryEdge(e2) == false)) + { + return EMeshResult::Failed_WouldCreateNonmanifoldEdge; + } + + bool bOK = (bUnsafe) ? TriangleRefCounts.AllocateAtUnsafe(tid) : + TriangleRefCounts.AllocateAt(tid); + if (bOK == false) + { + return EMeshResult::Failed_CannotAllocateTriangle; + } + + // now safe to insert triangle + int i = 3 * tid; + Triangles.InsertAt(tv[2], i + 2); + Triangles.InsertAt(tv[1], i + 1); + Triangles.InsertAt(tv[0], i); + if (HasTriangleGroups()) + { + TriangleGroups->InsertAt(gid, tid); + GroupIDCounter = FMath::Max(GroupIDCounter, gid + 1); + } + + // increment ref counts and update/create edges + VertexRefCounts.Increment(tv[0]); + VertexRefCounts.Increment(tv[1]); + VertexRefCounts.Increment(tv[2]); + + AddTriangleEdge(tid, tv[0], tv[1], 0, e0); + AddTriangleEdge(tid, tv[1], tv[2], 1, e1); + AddTriangleEdge(tid, tv[2], tv[0], 2, e2); + + if (HasAttributes()) + { + Attributes()->OnNewTriangle(tid, true); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + + + + + + + + + +void FDynamicMesh3::CompactInPlace(FCompactMaps* CompactInfo) +{ + // @todo support this + check(HasAttributes() == false); + + //IndexMap mapV = (bComputeCompactInfo) ? IndexMap(MaxVertexID, VertexCount) : nullptr; + + // find first free vertex, and last used vertex + int iLastV = MaxVertexID() - 1, iCurV = 0; + while (iLastV >= 0 && VertexRefCounts.IsValidUnsafe(iLastV) == false) + { + iLastV--; + } + while (iCurV < iLastV && VertexRefCounts.IsValidUnsafe(iCurV)) + { + iCurV++; + } + + TDynamicVector &vref = VertexRefCounts.GetRawRefCountsUnsafe(); + + while (iCurV < iLastV) + { + int kc = iCurV * 3, kl = iLastV * 3; + Vertices[kc] = Vertices[kl]; Vertices[kc + 1] = Vertices[kl + 1]; Vertices[kc + 2] = Vertices[kl + 2]; + if (HasVertexNormals()) + { + (*VertexNormals)[kc] = (*VertexNormals)[kl]; + (*VertexNormals)[kc + 1] = (*VertexNormals)[kl + 1]; + (*VertexNormals)[kc + 2] = (*VertexNormals)[kl + 2]; + } + if (HasVertexColors()) + { + (*VertexColors)[kc] = (*VertexColors)[kl]; + (*VertexColors)[kc + 1] = (*VertexColors)[kl + 1]; + (*VertexColors)[kc + 2] = (*VertexColors)[kl + 2]; + } + if (HasVertexUVs()) + { + int ukc = iCurV * 2, ukl = iLastV * 2; + (*VertexUVs)[ukc] = (*VertexUVs)[ukl]; + (*VertexUVs)[ukc + 1] = (*VertexUVs)[ukl + 1]; + } + + for (int eid : VertexEdgeLists.Values(iLastV)) + { + // replace vertex in edges + ReplaceEdgeVertex(eid, iLastV, iCurV); + + // replace vertex in triangles + int t0 = Edges[4 * eid + 2]; + ReplaceTriangleVertex(t0, iLastV, iCurV); + int t1 = Edges[4 * eid + 3]; + if (t1 != InvalidID) + { + ReplaceTriangleVertex(t1, iLastV, iCurV); + } + } + + // shift vertex refcount to position + vref[iCurV] = vref[iLastV]; + vref[iLastV] = FRefCountVector::INVALID_REF_COUNT; + + // move edge list + VertexEdgeLists.Move(iLastV, iCurV); + + if (CompactInfo != nullptr) + { + CompactInfo->MapV[iLastV] = iCurV; + } + + // move cur forward one, last back one, and then search for next valid + iLastV--; iCurV++; + while (iLastV >= 0 && VertexRefCounts.IsValidUnsafe(iLastV) == false) + { + iLastV--; + } + while (iCurV < iLastV && VertexRefCounts.IsValidUnsafe(iCurV)) + { + iCurV++; + } + } + + // trim vertices data structures + VertexRefCounts.Trim(VertexCount()); + Vertices.Resize(VertexCount() * 3); + if (HasVertexNormals()) + { + VertexNormals->Resize(VertexCount() * 3); + } + if (HasVertexColors()) + { + VertexColors->Resize(VertexCount() * 3); + } + if (HasVertexUVs()) + { + VertexUVs->Resize(VertexCount() * 2); + } + + // [TODO] VertexEdgeLists!!! + + /** shift triangles **/ + + // find first free triangle, and last valid triangle + int iLastT = MaxTriangleID() - 1, iCurT = 0; + while (iLastT >= 0 && TriangleRefCounts.IsValidUnsafe(iLastT) == false) + { + iLastT--; + } + while (iCurT < iLastT && TriangleRefCounts.IsValidUnsafe(iCurT)) + { + iCurT++; + } + + TDynamicVector &tref = TriangleRefCounts.GetRawRefCountsUnsafe(); + + while (iCurT < iLastT) + { + int kc = iCurT * 3, kl = iLastT * 3; + + // shift triangle + for (int j = 0; j < 3; ++j) + { + Triangles[kc + j] = Triangles[kl + j]; + TriangleEdges[kc + j] = TriangleEdges[kl + j]; + } + if (HasTriangleGroups()) + { + (*TriangleGroups)[iCurT] = (*TriangleGroups)[iLastT]; + } + + // update edges + for (int j = 0; j < 3; ++j) + { + int eid = TriangleEdges[kc + j]; + ReplaceEdgeTriangle(eid, iLastT, iCurT); + } + + // shift triangle refcount to position + tref[iCurT] = tref[iLastT]; + tref[iLastT] = FRefCountVector::INVALID_REF_COUNT; + + // move cur forward one, last back one, and then search for next valid + iLastT--; iCurT++; + while (iLastT >= 0 && TriangleRefCounts.IsValidUnsafe(iLastT) == false) + { + iLastT--; + } + while (iCurT < iLastT && TriangleRefCounts.IsValidUnsafe(iCurT)) + { + iCurT++; + } + } + + // trim triangles data structures + TriangleRefCounts.Trim(TriangleCount()); + Triangles.Resize(TriangleCount() * 3); + TriangleEdges.Resize(TriangleCount() * 3); + if (HasTriangleGroups()) + { + TriangleGroups->Resize(TriangleCount()); + } + + /** shift edges **/ + + // find first free edge, and last used edge + int iLastE = MaxEdgeID() - 1, iCurE = 0; + while (iLastE >= 0 && EdgeRefCounts.IsValidUnsafe(iLastE) == false) + { + iLastE--; + } + while (iCurE < iLastE && EdgeRefCounts.IsValidUnsafe(iCurE)) + { + iCurE++; + } + + TDynamicVector &eref = EdgeRefCounts.GetRawRefCountsUnsafe(); + + while (iCurE < iLastE) + { + int kc = iCurE * 4, kl = iLastE * 4; + + // shift edge + for (int j = 0; j < 4; ++j) + { + Edges[kc + j] = Edges[kl + j]; + } + + // replace edge in vertex edges lists + int v0 = Edges[kc], v1 = Edges[kc + 1]; + VertexEdgeLists.Replace(v0, [iLastE](int eid) { return eid == iLastE; }, iCurE); + VertexEdgeLists.Replace(v1, [iLastE](int eid) { return eid == iLastE; }, iCurE); + + // replace edge in triangles + ReplaceTriangleEdge(Edges[kc + 2], iLastE, iCurE); + if (Edges[kc + 3] != InvalidID) + { + ReplaceTriangleEdge(Edges[kc + 3], iLastE, iCurE); + } + + // shift triangle refcount to position + eref[iCurE] = eref[iLastE]; + eref[iLastE] = FRefCountVector::INVALID_REF_COUNT; + + // move cur forward one, last back one, and then search for next valid + iLastE--; iCurE++; + while (iLastE >= 0 && EdgeRefCounts.IsValidUnsafe(iLastE) == false) + { + iLastE--; + } + while (iCurE < iLastE && EdgeRefCounts.IsValidUnsafe(iCurE)) + { + iCurE++; + } + } + + // trim edge data structures + EdgeRefCounts.Trim(EdgeCount()); + Edges.Resize(EdgeCount() * 4); +} + + + + + + + +EMeshResult FDynamicMesh3::ReverseTriOrientation(int tID) +{ + if (!IsTriangle(tID)) + { + return EMeshResult::Failed_NotATriangle; + } + ReverseTriOrientationInternal(tID); + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + +void FDynamicMesh3::ReverseTriOrientationInternal(int tID) +{ + FIndex3i t = GetTriangle(tID); + SetTriangleInternal(tID, t[1], t[0], t[2]); + FIndex3i te = GetTriEdges(tID); + SetTriangleEdgesInternal(tID, te[0], te[2], te[1]); + if (HasAttributes()) + { + Attributes()->OnReverseTriOrientation(tID); + } +} + +void FDynamicMesh3::ReverseOrientation(bool bFlipNormals) +{ + for (int tid : TriangleIndicesItr()) + { + ReverseTriOrientationInternal(tid); + } + if (bFlipNormals && HasVertexNormals()) + { + for (int vid : VertexIndicesItr()) + { + int i = 3 * vid; + (*VertexNormals)[i] = -(*VertexNormals)[i]; + (*VertexNormals)[i + 1] = -(*VertexNormals)[i + 1]; + (*VertexNormals)[i + 2] = -(*VertexNormals)[i + 2]; + } + } + UpdateTimeStamp(true, true); +} + + + + + + +EMeshResult FDynamicMesh3::RemoveVertex(int vID, bool bRemoveAllTriangles, bool bPreserveManifold) +{ + if (VertexRefCounts.IsValid(vID) == false) + { + return EMeshResult::Failed_NotAVertex; + } + + if (bRemoveAllTriangles) + { + // if any one-ring vtx is a boundary vtx and one of its outer-ring edges is an + // interior edge then we will create a bowtie if we remove that triangle + if (bPreserveManifold) + { + for (int tid : VtxTrianglesItr(vID)) + { + FIndex3i tri = GetTriangle(tid); + int j = IndexUtil::FindTriIndex(vID, tri); + int oa = tri[(j + 1) % 3], ob = tri[(j + 2) % 3]; + int eid = FindEdge(oa, ob); + if (IsBoundaryEdge(eid)) + { + continue; + } + if (IsBoundaryVertex(oa) || IsBoundaryVertex(ob)) + { + return EMeshResult::Failed_WouldCreateBowtie; + } + } + } + + TArray tris; + GetVtxTriangles(vID, tris, true); + for (int tID : tris) + { + EMeshResult result = RemoveTriangle(tID, false, bPreserveManifold); + if (result != EMeshResult::Ok) + { + return result; + } + } + } + + if (VertexRefCounts.GetRefCount(vID) != 1) + { + return EMeshResult::Failed_VertexStillReferenced; + } + + VertexRefCounts.Decrement(vID); + check(VertexRefCounts.IsValid(vID) == false); + VertexEdgeLists.Clear(vID); + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + + + + + + + +EMeshResult FDynamicMesh3::RemoveTriangle(int tID, bool bRemoveIsolatedVertices, bool bPreserveManifold) +{ + if (!TriangleRefCounts.IsValid(tID)) + { + check(false); + return EMeshResult::Failed_NotATriangle; + } + + FIndex3i tv = GetTriangle(tID); + FIndex3i te = GetTriEdges(tID); + + // if any tri vtx is a boundary vtx connected to two interior edges, then + // we cannot remove this triangle because it would create a bowtie vertex! + // (that vtx already has 2 boundary edges, and we would add two more) + if (bPreserveManifold) + { + for (int j = 0; j < 3; ++j) + { + if (IsBoundaryVertex(tv[j])) + { + if (IsBoundaryEdge(te[j]) == false && IsBoundaryEdge(te[(j + 2) % 3]) == false) + { + return EMeshResult::Failed_WouldCreateBowtie; + } + } + } + } + + // Remove triangle from its edges. if edge has no triangles left, + // then it is removed. + for (int j = 0; j < 3; ++j) + { + int eid = te[j]; + ReplaceEdgeTriangle(eid, tID, InvalidID); + if (Edges[4 * eid + 2] == InvalidID) + { + int a = Edges[4 * eid]; + VertexEdgeLists.Remove(a, eid); + + int b = Edges[4 * eid + 1]; + VertexEdgeLists.Remove(b, eid); + + EdgeRefCounts.Decrement(eid); + } + } + + // free this triangle + TriangleRefCounts.Decrement(tID); + check(TriangleRefCounts.IsValid(tID) == false); + + // Decrement vertex refcounts. If any hit 1 and we got remove-isolated flag, + // we need to remove that vertex + for (int j = 0; j < 3; ++j) + { + int vid = tv[j]; + VertexRefCounts.Decrement(vid); + if (bRemoveIsolatedVertices && VertexRefCounts.GetRefCount(vid) == 1) + { + VertexRefCounts.Decrement(vid); + check(VertexRefCounts.IsValid(vid) == false); + VertexEdgeLists.Clear(vid); + } + } + + if (HasAttributes()) + { + Attributes()->OnRemoveTriangle(tID, bRemoveIsolatedVertices); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + + + + + + +EMeshResult FDynamicMesh3::SetTriangle(int tID, const FIndex3i& newv, bool bRemoveIsolatedVertices) +{ + // @todo support this. + check(HasAttributes() == false); + + FIndex3i tv = GetTriangle(tID); + FIndex3i te = GetTriEdges(tID); + if (tv[0] == newv[0] && tv[1] == newv[1]) + { + te[0] = -1; + } + if (tv[1] == newv[1] && tv[2] == newv[2]) + { + te[1] = -1; + } + if (tv[2] == newv[2] && tv[0] == newv[0]) + { + te[2] = -1; + } + + if (!TriangleRefCounts.IsValid(tID)) + { + check(false); + return EMeshResult::Failed_NotATriangle; + } + if (IsVertex(newv[0]) == false || IsVertex(newv[1]) == false || IsVertex(newv[2]) == false) + { + check(false); + return EMeshResult::Failed_NotAVertex; + } + if (newv[0] == newv[1] || newv[0] == newv[2] || newv[1] == newv[2]) + { + check(false); + return EMeshResult::Failed_BrokenTopology; + } + // look up edges. if any already have two triangles, this would + // create non-manifold geometry and so we do not allow it + int e0 = FindEdge(newv[0], newv[1]); + int e1 = FindEdge(newv[1], newv[2]); + int e2 = FindEdge(newv[2], newv[0]); + if ((te[0] != -1 && e0 != InvalidID && IsBoundaryEdge(e0) == false) + || (te[1] != -1 && e1 != InvalidID && IsBoundaryEdge(e1) == false) + || (te[2] != -1 && e2 != InvalidID && IsBoundaryEdge(e2) == false)) + { + return EMeshResult::Failed_BrokenTopology; + } + + + // [TODO] check that we are not going to create invalid stuff... + + // Remove triangle from its edges. if edge has no triangles left, then it is removed. + for (int j = 0; j < 3; ++j) + { + int eid = te[j]; + if (eid == -1) // we don't need to modify this edge + { + continue; + } + ReplaceEdgeTriangle(eid, tID, InvalidID); + if (Edges[4 * eid + 2] == InvalidID) + { + int a = Edges[4 * eid]; + VertexEdgeLists.Remove(a, eid); + + int b = Edges[4 * eid + 1]; + VertexEdgeLists.Remove(b, eid); + + EdgeRefCounts.Decrement(eid); + } + } + + // Decrement vertex refcounts. If any hit 1 and we got remove-isolated flag, + // we need to remove that vertex + for (int j = 0; j < 3; ++j) + { + int vid = tv[j]; + if (vid == newv[j]) // we don't need to modify this vertex + { + continue; + } + VertexRefCounts.Decrement(vid); + if (bRemoveIsolatedVertices && VertexRefCounts.GetRefCount(vid) == 1) + { + VertexRefCounts.Decrement(vid); + check(VertexRefCounts.IsValid(vid) == false); + VertexEdgeLists.Clear(vid); + } + } + + + // ok now re-insert with vertices + int i = 3 * tID; + for (int j = 0; j < 3; ++j) + { + if (newv[j] != tv[j]) + { + Triangles[i + j] = newv[j]; + VertexRefCounts.Increment(newv[j]); + } + } + + if (te[0] != -1) + { + AddTriangleEdge(tID, newv[0], newv[1], 0, e0); + } + if (te[1] != -1) + { + AddTriangleEdge(tID, newv[1], newv[2], 1, e1); + } + if (te[2] != -1) + { + AddTriangleEdge(tID, newv[2], newv[0], 2, e2); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + + + + + + + + +EMeshResult FDynamicMesh3::SplitEdge(int eab, FEdgeSplitInfo& SplitInfo, double split_t) +{ + SplitInfo = FEdgeSplitInfo(); + + if (!IsEdge(eab)) + { + return EMeshResult::Failed_NotAnEdge; + } + + // look up primary edge & triangle + int eab_i = 4 * eab; + int a = Edges[eab_i], b = Edges[eab_i + 1]; + int t0 = Edges[eab_i + 2]; + if (t0 == InvalidID) + { + return EMeshResult::Failed_BrokenTopology; + } + FIndex3i T0tv = GetTriangle(t0); + int c = IndexUtil::OrientTriEdgeAndFindOtherVtx(a, b, T0tv); + if (VertexRefCounts.GetRawRefCount(c) > 32764) + { + return EMeshResult::Failed_HitValenceLimit; + } + if (a != Edges[eab_i]) + { + split_t = 1.0 - split_t; // if we flipped a/b order we need to reverse t + } + + SplitInfo.OriginalEdge = eab; + SplitInfo.OriginalVertices = FIndex2i(a, b); // this is the oriented a,b + SplitInfo.OriginalTriangles = FIndex2i(t0, InvalidID); + SplitInfo.SplitT = split_t; + + // quite a bit of code is duplicated between boundary and non-boundary case, but it + // is too hard to follow later if we factor it out... + if (IsBoundaryEdge(eab)) + { + // create vertex + FVector3d vNew = FVector3d::Lerp(GetVertex(a), GetVertex(b), split_t); + int f = AppendVertex(vNew); + if (HasVertexNormals()) + { + SetVertexNormal(f, FVector3f::Lerp(GetVertexNormal(a), GetVertexNormal(b), (float)split_t).Normalized()); + } + if (HasVertexColors()) + { + SetVertexColor(f, FVector3f::Lerp(GetVertexColor(a), GetVertexColor(b), (float)split_t)); + } + if (HasVertexUVs()) + { + SetVertexUV(f, FVector2f::Lerp(GetVertexUV(a), GetVertexUV(b), (float)split_t)); + } + + // look up edge bc, which needs to be modified + FIndex3i T0te = GetTriEdges(t0); + int ebc = T0te[IndexUtil::FindEdgeIndexInTri(b, c, T0tv)]; + + // rewrite existing triangle + ReplaceTriangleVertex(t0, b, f); + + // add second triangle + int t2 = AddTriangleInternal(f, b, c, InvalidID, InvalidID, InvalidID); + if (HasTriangleGroups()) + { + TriangleGroups->InsertAt((*TriangleGroups)[t0], t2); + } + + // rewrite edge bc, create edge af + ReplaceEdgeTriangle(ebc, t0, t2); + int eaf = eab; + ReplaceEdgeVertex(eaf, b, f); + VertexEdgeLists.Remove(b, eab); + VertexEdgeLists.Insert(f, eaf); + + // create edges fb and fc + int efb = AddEdgeInternal(f, b, t2); + int efc = AddEdgeInternal(f, c, t0, t2); + + // update triangle edge-nbrs + ReplaceTriangleEdge(t0, ebc, efc); + SetTriangleEdgesInternal(t2, efb, ebc, efc); + + // update vertex refcounts + VertexRefCounts.Increment(c); + VertexRefCounts.Increment(f, 2); + + SplitInfo.bIsBoundary = true; + SplitInfo.OtherVertices = FIndex2i(c, InvalidID); + SplitInfo.NewVertex = f; + SplitInfo.NewEdges = FIndex3i(efb, efc, InvalidID); + SplitInfo.NewTriangles = FIndex2i(t2, InvalidID); + + if (HasAttributes()) + { + Attributes()->OnSplitEdge(SplitInfo); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; + + } + else // interior triangle branch + { + // look up other triangle + int t1 = Edges[eab_i + 3]; + SplitInfo.OriginalTriangles.B = t1; + FIndex3i T1tv = GetTriangle(t1); + int d = IndexUtil::FindTriOtherVtx(a, b, T1tv); + if (VertexRefCounts.GetRawRefCount(d) > 32764) + { + return EMeshResult::Failed_HitValenceLimit; + } + + // create vertex + FVector3d vNew = FVector3d::Lerp(GetVertex(a), GetVertex(b), split_t); + int f = AppendVertex(vNew); + if (HasVertexNormals()) + { + SetVertexNormal(f, FVector3f::Lerp(GetVertexNormal(a), GetVertexNormal(b), (float)split_t).Normalized()); + } + if (HasVertexColors()) + { + SetVertexColor(f, FVector3f::Lerp(GetVertexColor(a), GetVertexColor(b), (float)split_t)); + } + if (HasVertexUVs()) + { + SetVertexUV(f, FVector2f::Lerp(GetVertexUV(a), GetVertexUV(b), (float)split_t)); + } + + // look up edges that we are going to need to update + // [TODO OPT] could use ordering to reduce # of compares here + FIndex3i T0te = GetTriEdges(t0); + int ebc = T0te[IndexUtil::FindEdgeIndexInTri(b, c, T0tv)]; + FIndex3i T1te = GetTriEdges(t1); + int edb = T1te[IndexUtil::FindEdgeIndexInTri(d, b, T1tv)]; + + // rewrite existing triangles + ReplaceTriangleVertex(t0, b, f); + ReplaceTriangleVertex(t1, b, f); + + // add two triangles to close holes we just created + int t2 = AddTriangleInternal(f, b, c, InvalidID, InvalidID, InvalidID); + int t3 = AddTriangleInternal(f, d, b, InvalidID, InvalidID, InvalidID); + if (HasTriangleGroups()) + { + TriangleGroups->InsertAt((*TriangleGroups)[t0], t2); + TriangleGroups->InsertAt((*TriangleGroups)[t1], t3); + } + + // update the edges we found above, to point to triangles + ReplaceEdgeTriangle(ebc, t0, t2); + ReplaceEdgeTriangle(edb, t1, t3); + + // edge eab became eaf + int eaf = eab; //Edge * eAF = eAB; + ReplaceEdgeVertex(eaf, b, f); + + // update a/b/f vertex-edges + VertexEdgeLists.Remove(b, eab); + VertexEdgeLists.Insert(f, eaf); + + // create edges connected to f (also updates vertex-edges) + int efb = AddEdgeInternal(f, b, t2, t3); + int efc = AddEdgeInternal(f, c, t0, t2); + int edf = AddEdgeInternal(d, f, t1, t3); + + // update triangle edge-nbrs + ReplaceTriangleEdge(t0, ebc, efc); + ReplaceTriangleEdge(t1, edb, edf); + SetTriangleEdgesInternal(t2, efb, ebc, efc); + SetTriangleEdgesInternal(t3, edf, edb, efb); + + // update vertex refcounts + VertexRefCounts.Increment(c); + VertexRefCounts.Increment(d); + VertexRefCounts.Increment(f, 4); + + SplitInfo.bIsBoundary = false; + SplitInfo.OtherVertices = FIndex2i(c, d); + SplitInfo.NewVertex = f; + SplitInfo.NewEdges = FIndex3i(efb, efc, edf); + SplitInfo.NewTriangles = FIndex2i(t2, t3); + + if (HasAttributes()) + { + Attributes()->OnSplitEdge(SplitInfo); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; + } + +} + + +EMeshResult FDynamicMesh3::SplitEdge(int vA, int vB, FEdgeSplitInfo& SplitInfo) +{ + int eid = FindEdge(vA, vB); + if (eid == InvalidID) + { + SplitInfo = FEdgeSplitInfo(); + return EMeshResult::Failed_NotAnEdge; + } + return SplitEdge(eid, SplitInfo); +} + + + + + + + + +EMeshResult FDynamicMesh3::FlipEdge(int eab, FEdgeFlipInfo& FlipInfo) +{ + FlipInfo = FEdgeFlipInfo(); + + if (!IsEdge(eab)) + { + return EMeshResult::Failed_NotAnEdge; + } + if (IsBoundaryEdge(eab)) + { + return EMeshResult::Failed_IsBoundaryEdge; + } + + // find oriented edge [a,b], tris t0,t1, and other verts c in t0, d in t1 + int eab_i = 4 * eab; + int a = Edges[eab_i], b = Edges[eab_i + 1]; + int t0 = Edges[eab_i + 2], t1 = Edges[eab_i + 3]; + FIndex3i T0tv = GetTriangle(t0), T1tv = GetTriangle(t1); + int c = IndexUtil::OrientTriEdgeAndFindOtherVtx(a, b, T0tv); + int d = IndexUtil::FindTriOtherVtx(a, b, T1tv); + if (c == InvalidID || d == InvalidID) + { + return EMeshResult::Failed_BrokenTopology; + } + + int flipped = FindEdge(c, d); + if (flipped != InvalidID) + { + return EMeshResult::Failed_FlippedEdgeExists; + } + + // find edges bc, ca, ad, db + int ebc = FindTriangleEdge(t0, b, c); + int eca = FindTriangleEdge(t0, c, a); + int ead = FindTriangleEdge(t1, a, d); + int edb = FindTriangleEdge(t1, d, b); + + // update triangles + SetTriangleInternal(t0, c, d, b); + SetTriangleInternal(t1, d, c, a); + + // update edge AB, which becomes flipped edge CD + SetEdgeVerticesInternal(eab, c, d); + SetEdgeTrianglesInternal(eab, t0, t1); + int ecd = eab; + + // update the two other edges whose triangle nbrs have changed + if (ReplaceEdgeTriangle(eca, t0, t1) == -1) + { + checkf(false, TEXT("FDynamicMesh3.FlipEdge: first ReplaceEdgeTriangle failed")); + return EMeshResult::Failed_UnrecoverableError; + } + if (ReplaceEdgeTriangle(edb, t1, t0) == -1) + { + checkf(false, TEXT("FDynamicMesh3.FlipEdge: second ReplaceEdgeTriangle failed")); + return EMeshResult::Failed_UnrecoverableError; + } + + // update triangle nbr lists (these are edges) + SetTriangleEdgesInternal(t0, ecd, edb, ebc); + SetTriangleEdgesInternal(t1, ecd, eca, ead); + + // remove old eab from verts a and b, and Decrement ref counts + if (VertexEdgeLists.Remove(a, eab) == false) + { + checkf(false, TEXT("FDynamicMesh3.FlipEdge: first edge list remove failed")); + return EMeshResult::Failed_UnrecoverableError; + } + if (VertexEdgeLists.Remove(b, eab) == false) + { + checkf(false, TEXT("FDynamicMesh3.FlipEdge: second edge list remove failed")); + return EMeshResult::Failed_UnrecoverableError; + } + VertexRefCounts.Decrement(a); + VertexRefCounts.Decrement(b); + if (IsVertex(a) == false || IsVertex(b) == false) + { + checkf(false, TEXT("FDynamicMesh3.FlipEdge: either a or b is not a vertex?")); + return EMeshResult::Failed_UnrecoverableError; + } + + // add edge ecd to verts c and d, and increment ref counts + VertexEdgeLists.Insert(c, ecd); + VertexEdgeLists.Insert(d, ecd); + VertexRefCounts.Increment(c); + VertexRefCounts.Increment(d); + + // success! collect up results + FlipInfo.EdgeID = eab; + FlipInfo.OriginalVerts = FIndex2i(a, b); + FlipInfo.OpposingVerts = FIndex2i(c, d); + FlipInfo.Triangles = FIndex2i(t0, t1); + + if (HasAttributes()) + { + Attributes()->OnFlipEdge(FlipInfo); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + +EMeshResult FDynamicMesh3::FlipEdge(int vA, int vB, FEdgeFlipInfo& FlipInfo) +{ + int eid = FindEdge(vA, vB); + if (eid == InvalidID) + { + FlipInfo = FEdgeFlipInfo(); + return EMeshResult::Failed_NotAnEdge; + } + return FlipEdge(eid, FlipInfo); +} + + + + + + + + + +EMeshResult FDynamicMesh3::CollapseEdge(int vKeep, int vRemove, double collapse_t, FEdgeCollapseInfo& CollapseInfo) +{ + CollapseInfo = FEdgeCollapseInfo(); + + if (IsVertex(vKeep) == false || IsVertex(vRemove) == false) + { + return EMeshResult::Failed_NotAnEdge; + } + + int b = vKeep; // renaming for sanity. We remove a and keep b + int a = vRemove; + + int eab = FindEdge(a, b); + if (eab == InvalidID) + { + return EMeshResult::Failed_NotAnEdge; + } + + int t0 = Edges[4 * eab + 2]; + if (t0 == InvalidID) + { + return EMeshResult::Failed_BrokenTopology; + } + FIndex3i T0tv = GetTriangle(t0); + int c = IndexUtil::FindTriOtherVtx(a, b, T0tv); + + // look up opposing triangle/vtx if we are not in boundary case + bool bIsBoundaryEdge = false; + int d = InvalidID; + int t1 = Edges[4 * eab + 3]; + if (t1 != InvalidID) + { + FIndex3i T1tv = GetTriangle(t1); + d = IndexUtil::FindTriOtherVtx(a, b, T1tv); + if (c == d) + { + return EMeshResult::Failed_FoundDuplicateTriangle; + } + } + else + { + bIsBoundaryEdge = true; + } + + CollapseInfo.OpposingVerts = FIndex2i(c, d); + + // We cannot collapse if edge lists of a and b share vertices other + // than c and d (because then we will make a triangle [x b b]. + // Unfortunately I cannot see a way to do this more efficiently than brute-force search + // [TODO] if we had tri iterator for a, couldn't we check each tri for b (skipping t0 and t1) ? + int edges_a_count = VertexEdgeLists.GetCount(a); + int eac = InvalidID, ead = InvalidID, ebc = InvalidID, ebd = InvalidID; + for (int eid_a : VertexEdgeLists.Values(a)) + { + int vax = GetOtherEdgeVertex(eid_a, a); + if (vax == c) + { + eac = eid_a; + continue; + } + if (vax == d) + { + ead = eid_a; + continue; + } + if (vax == b) + { + continue; + } + for (int eid_b : VertexEdgeLists.Values(b)) + { + if (GetOtherEdgeVertex(eid_b, b) == vax) + { + return EMeshResult::Failed_InvalidNeighbourhood; + } + } + } + + // [RMS] I am not sure this tetrahedron case will detect bowtie vertices. + // But the single-triangle case does + + // We cannot collapse if we have a tetrahedron. In this case a has 3 nbr edges, + // and edge cd exists. But that is not conclusive, we also have to check that + // cd is an internal edge, and that each of its tris contain a or b + if (edges_a_count == 3 && bIsBoundaryEdge == false) + { + int edc = FindEdge(d, c); + int edc_i = 4 * edc; + if (edc != InvalidID && Edges[edc_i + 3] != InvalidID) + { + int edc_t0 = Edges[edc_i + 2]; + int edc_t1 = Edges[edc_i + 3]; + + if ((TriangleHasVertex(edc_t0, a) && TriangleHasVertex(edc_t1, b)) + || (TriangleHasVertex(edc_t0, b) && TriangleHasVertex(edc_t1, a))) + { + return EMeshResult::Failed_CollapseTetrahedron; + } + } + + } + else if (bIsBoundaryEdge == true && IsBoundaryEdge(eac)) + { + // Cannot collapse edge if we are down to a single triangle + ebc = FindEdgeFromTri(b, c, t0); + if (IsBoundaryEdge(ebc)) + { + return EMeshResult::Failed_CollapseTriangle; + } + } + + // [RMS] this was added from C++ version...seems like maybe I never hit + // this case? Conceivably could be porting bug but looking at the + // C++ code I cannot see how we could possibly have caught this case... + // + // cannot collapse an edge where both vertices are boundary vertices + // because that would create a bowtie + // + // NOTE: potentially scanning all edges here...couldn't we + // pick up eac/bc/ad/bd as we go? somehow? + if (bIsBoundaryEdge == false && IsBoundaryVertex(a) && IsBoundaryVertex(b)) + { + return EMeshResult::Failed_InvalidNeighbourhood; + } + + // save vertex positions before we delete removed (can defer kept?) + FVector3d KeptPos = GetVertex(vKeep); + FVector3d RemovedPos = GetVertex(vRemove); + + // 1) remove edge ab from vtx b + // 2) find edges ad and ac, and tris tad, tac across those edges (will use later) + // 3) for other edges, replace a with b, and add that edge to b + // 4) replace a with b in all triangles connected to a + int tad = InvalidID, tac = InvalidID; + for (int eid : VertexEdgeLists.Values(a)) + { + int o = GetOtherEdgeVertex(eid, a); + if (o == b) + { + if (VertexEdgeLists.Remove(b, eid) != true) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at remove case o == b")); + return EMeshResult::Failed_UnrecoverableError; + } + } + else if (o == c) + { + if (VertexEdgeLists.Remove(c, eid) != true) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at remove case o == c")); + return EMeshResult::Failed_UnrecoverableError; + } + tac = GetOtherEdgeTriangle(eid, t0); + } + else if (o == d) + { + if (VertexEdgeLists.Remove(d, eid) != true) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at remove case o == c, step 1")); + return EMeshResult::Failed_UnrecoverableError; + } + tad = GetOtherEdgeTriangle(eid, t1); + } + else + { + if (ReplaceEdgeVertex(eid, a, b) == -1) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at remove case else")); + return EMeshResult::Failed_UnrecoverableError; + } + VertexEdgeLists.Insert(b, eid); + } + + // [TODO] perhaps we can already have unique tri list because of the manifold-nbrhood check we need to do... + for (int j = 0; j < 2; ++j) + { + int t_j = Edges[4 * eid + 2 + j]; + if (t_j != InvalidID && t_j != t0 && t_j != t1) + { + if (TriangleHasVertex(t_j, a)) + { + if (ReplaceTriangleVertex(t_j, a, b) == -1) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at remove last check")); + return EMeshResult::Failed_UnrecoverableError; + } + VertexRefCounts.Increment(b); + VertexRefCounts.Decrement(a); + } + } + } + } + + if (bIsBoundaryEdge == false) + { + // remove all edges from vtx a, then remove vtx a + VertexEdgeLists.Clear(a); + check(VertexRefCounts.GetRefCount(a) == 3); // in t0,t1, and initial ref + VertexRefCounts.Decrement(a, 3); + check(VertexRefCounts.IsValid(a) == false); + + // remove triangles T0 and T1, and update b/c/d refcounts + TriangleRefCounts.Decrement(t0); + TriangleRefCounts.Decrement(t1); + VertexRefCounts.Decrement(c); + VertexRefCounts.Decrement(d); + VertexRefCounts.Decrement(b, 2); + check(TriangleRefCounts.IsValid(t0) == false); + check(TriangleRefCounts.IsValid(t1) == false); + + // remove edges ead, eab, eac + EdgeRefCounts.Decrement(ead); + EdgeRefCounts.Decrement(eab); + EdgeRefCounts.Decrement(eac); + check(EdgeRefCounts.IsValid(ead) == false); + check(EdgeRefCounts.IsValid(eab) == false); + check(EdgeRefCounts.IsValid(eac) == false); + + // replace t0 and t1 in edges ebd and ebc that we kept + ebd = FindEdgeFromTri(b, d, t1); + if (ebc == InvalidID) // we may have already looked this up + { + ebc = FindEdgeFromTri(b, c, t0); + } + + if (ReplaceEdgeTriangle(ebd, t1, tad) == -1) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at isboundary=false branch, ebd replace triangle")); + return EMeshResult::Failed_UnrecoverableError; + } + + if (ReplaceEdgeTriangle(ebc, t0, tac) == -1) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at isboundary=false branch, ebc replace triangle")); + return EMeshResult::Failed_UnrecoverableError; + } + + // update tri-edge-nbrs in tad and tac + if (tad != InvalidID) + { + if (ReplaceTriangleEdge(tad, ead, ebd) == -1) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at isboundary=false branch, ebd replace triangle")); + return EMeshResult::Failed_UnrecoverableError; + } + } + if (tac != InvalidID) + { + if (ReplaceTriangleEdge(tac, eac, ebc) == -1) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at isboundary=false branch, ebd replace triangle")); + return EMeshResult::Failed_UnrecoverableError; + } + } + + } + else + { + // boundary-edge path. this is basically same code as above, just not referencing t1/d + + // remove all edges from vtx a, then remove vtx a + VertexEdgeLists.Clear(a); + check(VertexRefCounts.GetRefCount(a) == 2); // in t0 and initial ref + VertexRefCounts.Decrement(a, 2); + check(VertexRefCounts.IsValid(a) == false); + + // remove triangle T0 and update b/c refcounts + TriangleRefCounts.Decrement(t0); + VertexRefCounts.Decrement(c); + VertexRefCounts.Decrement(b); + check(TriangleRefCounts.IsValid(t0) == false); + + // remove edges eab and eac + EdgeRefCounts.Decrement(eab); + EdgeRefCounts.Decrement(eac); + check(EdgeRefCounts.IsValid(eab) == false); + check(EdgeRefCounts.IsValid(eac) == false); + + // replace t0 in edge ebc that we kept + ebc = FindEdgeFromTri(b, c, t0); + if (ReplaceEdgeTriangle(ebc, t0, tac) == -1) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at isboundary=false branch, ebc replace triangle")); + return EMeshResult::Failed_UnrecoverableError; + } + + // update tri-edge-nbrs in tac + if (tac != InvalidID) + { + if (ReplaceTriangleEdge(tac, eac, ebc) == -1) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at isboundary=true branch, ebd replace triangle")); + return EMeshResult::Failed_UnrecoverableError; + } + } + } + + // set kept vertex to interpolated collapse position + SetVertex(vKeep, FVector3d::Lerp(KeptPos, RemovedPos, collapse_t)); + + CollapseInfo.KeptVertex = vKeep; + CollapseInfo.RemovedVertex = vRemove; + CollapseInfo.bIsBoundary = bIsBoundaryEdge; + CollapseInfo.CollapsedEdge = eab; + CollapseInfo.RemovedTris = FIndex2i(t0, t1); + CollapseInfo.RemovedEdges = FIndex2i(eac, ead); + CollapseInfo.KeptEdges = FIndex2i(ebc, ebd); + CollapseInfo.CollapseT = collapse_t; + + if (HasAttributes()) + { + Attributes()->OnCollapseEdge(CollapseInfo); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + + + + + + + + + + + +EMeshResult FDynamicMesh3::MergeEdges(int eKeep, int eDiscard, FMergeEdgesInfo& MergeInfo) +{ + MergeInfo = FMergeEdgesInfo(); + + if (IsEdge(eKeep) == false || IsEdge(eDiscard) == false) + { + return EMeshResult::Failed_NotAnEdge; + } + + FIndex4i edgeinfo_keep = GetEdge(eKeep); + FIndex4i edgeinfo_discard = GetEdge(eDiscard); + if (edgeinfo_keep[3] != InvalidID || edgeinfo_discard[3] != InvalidID) + { + return EMeshResult::Failed_NotABoundaryEdge; + } + + int a = edgeinfo_keep[0], b = edgeinfo_keep[1]; + int tab = edgeinfo_keep[2]; + int eab = eKeep; + int c = edgeinfo_discard[0], d = edgeinfo_discard[1]; + int tcd = edgeinfo_discard[2]; + int ecd = eDiscard; + + // Need to correctly orient a,b and c,d and then check that + // we will not join triangles with incompatible winding order + // I can't see how to do this purely topologically. + // So relying on closest-pairs testing. + IndexUtil::OrientTriEdge(a, b, GetTriangle(tab)); + //int tcd_otherv = OrientTriEdgeAndFindOtherVtx(ref c, ref d, GetTriangle(tcd)); + IndexUtil::OrientTriEdge(c, d, GetTriangle(tcd)); + int x = c; c = d; d = x; // joinable bdry edges have opposing orientations, so flip to get ac and b/d correspondences + FVector3d Va = GetVertex(a), Vb = GetVertex(b), Vc = GetVertex(c), Vd = GetVertex(d); + if (((Va - Vc).SquaredLength() + (Vb - Vd).SquaredLength()) > + ((Va - Vd).SquaredLength() + (Vb - Vc).SquaredLength())) + { + return EMeshResult::Failed_SameOrientation; + } + + // alternative that detects normal flip of triangle tcd. This is a more + // robust geometric test, but fails if tri is degenerate...also more expensive + //FVector3d otherv = GetVertex(tcd_otherv); + //FVector3d Ncd = TMathUtil.FastNormalDirection(GetVertex(c), GetVertex(d), otherv); + //FVector3d Nab = TMathUtil.FastNormalDirection(GetVertex(a), GetVertex(b), otherv); + //if (Ncd.Dot(Nab) < 0) + //return EMeshResult::Failed_SameOrientation; + + MergeInfo.KeptEdge = eab; + MergeInfo.RemovedEdge = ecd; + + // if a/c or b/d are connected by an existing edge, we can't merge + if (a != c && FindEdge(a, c) != InvalidID) + { + return EMeshResult::Failed_InvalidNeighbourhood; + } + if (b != d && FindEdge(b, d) != InvalidID) + { + return EMeshResult::Failed_InvalidNeighbourhood; + } + + // if vertices at either end already share a common neighbour vertex, and we + // do the merge, that would create duplicate edges. This is something like the + // 'link condition' in edge collapses. + // Note that we have to catch cases where both edges to the shared vertex are + // boundary edges, in that case we will also merge this edge later on + if (a != c) + { + int ea = 0, ec = 0, other_v = (b == d) ? b : -1; + for (int cnbr : VtxVerticesItr(c)) + { + if (cnbr != other_v && (ea = FindEdge(a, cnbr)) != InvalidID) + { + ec = FindEdge(c, cnbr); + if (IsBoundaryEdge(ea) == false || IsBoundaryEdge(ec) == false) + { + return EMeshResult::Failed_InvalidNeighbourhood; + } + } + } + } + if (b != d) + { + int eb = 0, ed = 0, other_v = (a == c) ? a : -1; + for (int dnbr : VtxVerticesItr(d)) + { + if (dnbr != other_v && (eb = FindEdge(b, dnbr)) != InvalidID) + { + ed = FindEdge(d, dnbr); + if (IsBoundaryEdge(eb) == false || IsBoundaryEdge(ed) == false) + { + return EMeshResult::Failed_InvalidNeighbourhood; + } + } + } + } + + // [TODO] this acts on each interior tri twice. could avoid using vtx-tri iterator? + if (a != c) + { + // replace c w/ a in edges and tris connected to c, and move edges to a + for (int eid : VertexEdgeLists.Values(c)) + { + if (eid == eDiscard) + { + continue; + } + ReplaceEdgeVertex(eid, c, a); + short rc = 0; + if (ReplaceTriangleVertex(Edges[4 * eid + 2], c, a) >= 0) + { + rc++; + } + if (Edges[4 * eid + 3] != InvalidID) + { + if (ReplaceTriangleVertex(Edges[4 * eid + 3], c, a) >= 0) + { + rc++; + } + } + VertexEdgeLists.Insert(a, eid); + if (rc > 0) + { + VertexRefCounts.Increment(a, rc); + VertexRefCounts.Decrement(c, rc); + } + } + VertexEdgeLists.Clear(c); + VertexRefCounts.Decrement(c); + MergeInfo.RemovedVerts[0] = c; + } + else + { + VertexEdgeLists.Remove(a, ecd); + MergeInfo.RemovedVerts[0] = InvalidID; + } + MergeInfo.KeptVerts[0] = a; + + if (d != b) + { + // replace d w/ b in edges and tris connected to d, and move edges to b + for (int eid : VertexEdgeLists.Values(d)) + { + if (eid == eDiscard) + { + continue; + } + ReplaceEdgeVertex(eid, d, b); + short rc = 0; + if (ReplaceTriangleVertex(Edges[4 * eid + 2], d, b) >= 0) + { + rc++; + } + if (Edges[4 * eid + 3] != InvalidID) + { + if (ReplaceTriangleVertex(Edges[4 * eid + 3], d, b) >= 0) + { + rc++; + } + } + VertexEdgeLists.Insert(b, eid); + if (rc > 0) + { + VertexRefCounts.Increment(b, rc); + VertexRefCounts.Decrement(d, rc); + } + + } + VertexEdgeLists.Clear(d); + VertexRefCounts.Decrement(d); + MergeInfo.RemovedVerts[1] = d; + } + else + { + VertexEdgeLists.Remove(b, ecd); + MergeInfo.RemovedVerts[1] = InvalidID; + } + MergeInfo.KeptVerts[1] = b; + + // replace edge cd with edge ab in triangle tcd + ReplaceTriangleEdge(tcd, ecd, eab); + EdgeRefCounts.Decrement(ecd); + + // update edge-tri adjacency + SetEdgeTrianglesInternal(eab, tab, tcd); + + // Once we merge ab to cd, there may be additional edges (now) connected + // to either a or b that are connected to the same vertex on their 'other' side. + // So we now have two boundary edges connecting the same two vertices - disaster! + // We need to find and merge these edges. + // Q: I don't think it is possible to have multiple such edge-pairs at a or b + // But I am not certain...is a bit tricky to handle because we modify edges_v... + MergeInfo.ExtraRemovedEdges = FIndex2i(InvalidID, InvalidID); + MergeInfo.ExtraKeptEdges = MergeInfo.ExtraRemovedEdges; + for (int vi = 0; vi < 2; ++vi) + { + int v1 = a, v2 = c; // vertices of merged edge + if (vi == 1) + { + v1 = b; v2 = d; + } + if (v1 == v2) + { + continue; + } + + TArray edges_v; + GetVertexEdgesList(v1, edges_v); + int Nedges = (int)edges_v.Num(); + bool found = false; + // in this loop, we compare 'other' vert_1 and vert_2 of edges around v1. + // problem case is when vert_1 == vert_2 (ie two edges w/ same other vtx). + //restart_merge_loop: + for (int i = 0; i < Nedges && found == false; ++i) + { + int edge_1 = edges_v[i]; + if (IsBoundaryEdge(edge_1) == false) + { + continue; + } + int vert_1 = GetOtherEdgeVertex(edge_1, v1); + for (int j = i + 1; j < Nedges; ++j) + { + int edge_2 = edges_v[j]; + int vert_2 = GetOtherEdgeVertex(edge_2, v1); + if (vert_1 == vert_2 && IsBoundaryEdge(edge_2)) // if ! boundary here, we are in deep trouble... + { + // replace edge_2 w/ edge_1 in tri, update edge and vtx-edge-nbr lists + int tri_1 = Edges[4 * edge_1 + 2]; + int tri_2 = Edges[4 * edge_2 + 2]; + ReplaceTriangleEdge(tri_2, edge_2, edge_1); + SetEdgeTrianglesInternal(edge_1, tri_1, tri_2); + VertexEdgeLists.Remove(v1, edge_2); + VertexEdgeLists.Remove(vert_1, edge_2); + EdgeRefCounts.Decrement(edge_2); + MergeInfo.ExtraRemovedEdges[vi] = edge_2; + MergeInfo.ExtraKeptEdges[vi] = edge_1; + + //edges_v = VertexEdgeLists_list(v1); // this code allows us to continue checking, ie in case we had + //Nedges = edges_v.Count; // multiple such edges. but I don't think it's possible. + //goto restart_merge_loop; + found = true; // exit outer i loop + break; // exit inner j loop + } + } + } + } + + if (HasAttributes()) + { + Attributes()->OnMergeEdges(MergeInfo); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + + + + + + + + +EMeshResult FDynamicMesh3::PokeTriangle(int TriangleID, const FVector3d& BaryCoordinates, FPokeTriangleInfo& PokeResult) +{ + PokeResult = FPokeTriangleInfo(); + + if (!IsTriangle(TriangleID)) + { + return EMeshResult::Failed_NotATriangle; + } + + FIndex3i tv = GetTriangle(TriangleID); + FIndex3i te = GetTriEdges(TriangleID); + + // create vertex with interpolated vertex attribs + FVertexInfo vinfo; + GetTriBaryPoint(TriangleID, BaryCoordinates[0], BaryCoordinates[1], BaryCoordinates[2], vinfo); + int center = AppendVertex(vinfo); + + // add in edges to center vtx, do not connect to triangles yet + int eaC = AddEdgeInternal(tv[0], center, -1, -1); + int ebC = AddEdgeInternal(tv[1], center, -1, -1); + int ecC = AddEdgeInternal(tv[2], center, -1, -1); + VertexRefCounts.Increment(tv[0]); + VertexRefCounts.Increment(tv[1]); + VertexRefCounts.Increment(tv[2]); + VertexRefCounts.Increment(center, 3); + + // old triangle becomes tri along first edge + SetTriangleInternal(TriangleID, tv[0], tv[1], center); + SetTriangleEdgesInternal(TriangleID, te[0], ebC, eaC); + + // add two triangles + int t1 = AddTriangleInternal(tv[1], tv[2], center, te[1], ecC, ebC); + int t2 = AddTriangleInternal(tv[2], tv[0], center, te[2], eaC, ecC); + + // second and third edges of original tri have neighbours + ReplaceEdgeTriangle(te[1], TriangleID, t1); + ReplaceEdgeTriangle(te[2], TriangleID, t2); + + // set the triangles for the edges we created above + SetEdgeTrianglesInternal(eaC, TriangleID, t2); + SetEdgeTrianglesInternal(ebC, TriangleID, t1); + SetEdgeTrianglesInternal(ecC, t1, t2); + + // transfer groups + if (HasTriangleGroups()) + { + int g = (*TriangleGroups)[TriangleID]; + TriangleGroups->InsertAt(g, t1); + TriangleGroups->InsertAt(g, t2); + } + + PokeResult.OriginalTriangle = TriangleID; + PokeResult.TriVertices = tv; + PokeResult.NewVertex = center; + PokeResult.NewTriangles = FIndex2i(t1,t2); + PokeResult.NewEdges = FIndex3i(eaC, ebC, ecC); + PokeResult.BaryCoords = BaryCoordinates; + + if (HasAttributes()) + { + Attributes()->OnPokeTriangle(PokeResult); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3_Queries.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3_Queries.cpp new file mode 100644 index 000000000000..77d1db3bc356 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3_Queries.cpp @@ -0,0 +1,836 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DynamicMesh3.h" + + +FIndex2i FDynamicMesh3::GetEdgeOpposingV(int eID) const +{ + // [TODO] there was a comment here saying this does more work than necessary?? + // ** it is important that verts returned maintain [c,d] order!! + int i = 4 * eID; + int a = Edges[i], b = Edges[i + 1]; + int t0 = Edges[i + 2], t1 = Edges[i + 3]; + int c = IndexUtil::FindTriOtherVtx(a, b, Triangles, t0); + if (t1 != InvalidID) + { + int d = IndexUtil::FindTriOtherVtx(a, b, Triangles, t1); + return FIndex2i(c, d); + } + else + { + return FIndex2i(c, InvalidID); + } +} + + +int FDynamicMesh3::GetVtxBoundaryEdges(int vID, int& e0, int& e1) const +{ + if (VertexRefCounts.IsValid(vID)) + { + int count = 0; + for (int eid : VertexEdgeLists.Values(vID)) + { + int ei = 4 * eid; + if (Edges[ei + 3] == InvalidID) + { + if (count == 0) + { + e0 = eid; + } + else if (count == 1) + { + e1 = eid; + } + count++; + } + } + return count; + } + check(false); + return -1; +} + + +int FDynamicMesh3::GetAllVtxBoundaryEdges(int vID, TArray& EdgeListOut) const +{ + if (VertexRefCounts.IsValid(vID)) + { + int count = 0; + for (int eid : VertexEdgeLists.Values(vID)) + { + int ei = 4 * eid; + if (Edges[ei + 3] == InvalidID) + { + EdgeListOut.Add(eid); + count++; + } + } + return count; + } + check(false); + return -1; +} + + + +void FDynamicMesh3::GetVtxNbrhood(int eID, int vID, int& vOther, int& oppV1, int& oppV2, int& t1, int& t2) const +{ + int i = 4 * eID; + vOther = (Edges[i] == vID) ? Edges[i + 1] : Edges[i]; + t1 = Edges[i + 2]; + oppV1 = IndexUtil::FindTriOtherVtx(vID, vOther, Triangles, t1); + t2 = Edges[i + 3]; + if (t2 != InvalidID) + { + oppV2 = IndexUtil::FindTriOtherVtx(vID, vOther, Triangles, t2); + } + else + { + t2 = InvalidID; + } +} + + +int FDynamicMesh3::GetVtxTriangleCount(int vID, bool bBruteForce) const +{ + if (bBruteForce) + { + TArray vTriangles; + if (GetVtxTriangles(vID, vTriangles, false) != EMeshResult::Ok) + { + return -1; + } + return (int)vTriangles.Num(); + } + + if (!IsVertex(vID)) + { + return -1; + } + int N = 0; + for (int eid : VertexEdgeLists.Values(vID)) + { + int vOther = GetOtherEdgeVertex(eid, vID); + int i = 4 * eid; + int et0 = Edges[i + 2]; + if (TriHasSequentialVertices(et0, vID, vOther)) + { + N++; + } + int et1 = Edges[i + 3]; + if (et1 != InvalidID && TriHasSequentialVertices(et1, vID, vOther)) + { + N++; + } + } + return N; +} + + + +EMeshResult FDynamicMesh3::GetVtxTriangles(int vID, TArray& TrianglesOut, bool bUseOrientation) const +{ + if (!IsVertex(vID)) + { + return EMeshResult::Failed_NotAVertex; + } + + if (bUseOrientation) + { + for (int eid : VertexEdgeLists.Values(vID)) + { + int vOther = GetOtherEdgeVertex(eid, vID); + int i = 4 * eid; + int et0 = Edges[i + 2]; + if (TriHasSequentialVertices(et0, vID, vOther)) + { + TrianglesOut.Add(et0); + } + int et1 = Edges[i + 3]; + if (et1 != InvalidID && TriHasSequentialVertices(et1, vID, vOther)) + { + TrianglesOut.Add(et1); + } + } + } + else + { + // brute-force method + for (int eid : VertexEdgeLists.Values(vID)) + { + int i = 4 * eid; + int t0 = Edges[i + 2]; + TrianglesOut.AddUnique(t0); + + int t1 = Edges[i + 3]; + if (t1 != InvalidID) + { + TrianglesOut.AddUnique(t1); + } + } + } + return EMeshResult::Ok; +} + + + + + +bool FDynamicMesh3::IsBoundaryVertex(int vID) const +{ + check(IsVertex(vID)); + for (int eid : VertexEdgeLists.Values(vID)) + { + if (Edges[4 * eid + 3] == InvalidID) + { + return true; + } + } + return false; +} + + + +bool FDynamicMesh3::IsBoundaryTriangle(int tID) const +{ + check(IsTriangle(tID)); + int i = 3 * tID; + return IsBoundaryEdge(TriangleEdges[i]) || IsBoundaryEdge(TriangleEdges[i + 1]) || IsBoundaryEdge(TriangleEdges[i + 2]); +} + + + + +FIndex2i FDynamicMesh3::GetOrientedBoundaryEdgeV(int eID) const +{ + if (EdgeRefCounts.IsValid(eID)) + { + int ei = 4 * eID; + if (Edges[ei + 3] == InvalidID) + { + int a = Edges[ei], b = Edges[ei + 1]; + int ti = 3 * Edges[ei + 2]; + FIndex3i tri(Triangles[ti], Triangles[ti + 1], Triangles[ti + 2]); + int ai = IndexUtil::FindEdgeIndexInTri(a, b, tri); + return FIndex2i(tri[ai], tri[(ai + 1) % 3]); + } + } + check(false); + return InvalidEdge(); +} + + +bool FDynamicMesh3::IsGroupBoundaryEdge(int eID) const +{ + check(IsEdge(eID)); + check(HasTriangleGroups()); + + int et1 = Edges[4 * eID + 3]; + if (et1 == InvalidID) + { + return false; + } + int g1 = (*TriangleGroups)[et1]; + int et0 = Edges[4 * eID + 2]; + int g0 = (*TriangleGroups)[et0]; + return g1 != g0; +} + + + +bool FDynamicMesh3::IsGroupBoundaryVertex(int vID) const +{ + check(IsVertex(vID)); + check(HasTriangleGroups()); + + int group_id = InvalidGroupID; + for (int eID : VertexEdgeLists.Values(vID)) + { + int et0 = Edges[4 * eID + 2]; + int g0 = (*TriangleGroups)[et0]; + if (group_id != g0) + { + if (group_id == InvalidGroupID) + { + group_id = g0; + } + else + { + return true; // saw multiple group IDs + } + } + int et1 = Edges[4 * eID + 3]; + if (et1 != InvalidID) + { + int g1 = (*TriangleGroups)[et1]; + if (group_id != g1) + { + return true; // saw multiple group IDs + } + } + } + return false; +} + + + +bool FDynamicMesh3::IsGroupJunctionVertex(int vID) const +{ + check(IsVertex(vID)); + check(HasTriangleGroups()); + + FIndex2i groups(InvalidGroupID, InvalidGroupID); + for (int eID : VertexEdgeLists.Values(vID)) + { + FIndex2i et(Edges[4 * eID + 2], Edges[4 * eID + 3]); + for (int k = 0; k < 2; ++k) + { + if (et[k] == InvalidID) + { + continue; + } + int g0 = (*TriangleGroups)[et[k]]; + if (g0 != groups[0] && g0 != groups[1]) + { + if (groups[0] != InvalidGroupID && groups[1] != InvalidGroupID) + { + return true; + } + if (groups[0] == InvalidGroupID) + { + groups[0] = g0; + } + else + { + groups[1] = g0; + } + } + } + } + return false; +} + + +bool FDynamicMesh3::GetVertexGroups(int vID, FIndex4i& groups) const +{ + check(IsVertex(vID)); + check(HasTriangleGroups()); + + groups = FIndex4i(InvalidGroupID, InvalidGroupID, InvalidGroupID, InvalidGroupID); + int ng = 0; + + for (int eID : VertexEdgeLists.Values(vID)) + { + int et0 = Edges[4 * eID + 2]; + int g0 = (*TriangleGroups)[et0]; + if (groups.Contains(g0) == false) + { + groups[ng++] = g0; + } + if (ng == 4) + { + return false; + } + int et1 = Edges[4 * eID + 3]; + if (et1 != InvalidID) + { + int g1 = (*TriangleGroups)[et1]; + if (groups.Contains(g1) == false) + { + groups[ng++] = g1; + } + if (ng == 4) + { + return false; + } + } + } + return true; +} + + + +bool FDynamicMesh3::GetAllVertexGroups(int vID, TArray& GroupsOut) const +{ + check(IsVertex(vID)); + check(HasTriangleGroups()); + + for (int eID : VertexEdgeLists.Values(vID)) + { + int et0 = Edges[4 * eID + 2]; + int g0 = (*TriangleGroups)[et0]; + GroupsOut.AddUnique(g0); + + int et1 = Edges[4 * eID + 3]; + if (et1 != InvalidID) + { + int g1 = (*TriangleGroups)[et1]; + GroupsOut.AddUnique(g1); + } + } + return true; +} + + + + +/** + * returns true if vID is a "bowtie" vertex, ie multiple disjoint triangle sets in one-ring + */ +bool FDynamicMesh3::IsBowtieVertex(int vID) const +{ + check(VertexRefCounts.IsValid(vID)); + + int nEdges = VertexEdgeLists.GetCount(vID); + if (nEdges == 0) + { + return false; + } + + // find a boundary edge to start at + int start_eid = -1; + bool start_at_boundary = false; + for (int eid : VertexEdgeLists.Values(vID)) + { + if (Edges[4 * eid + 3] == InvalidID) + { + start_at_boundary = true; + start_eid = eid; + break; + } + } + // if no boundary edge, start at arbitrary edge + if (start_eid == -1) + { + start_eid = VertexEdgeLists.First(vID); + } + // initial triangle + int start_tid = Edges[4 * start_eid + 2]; + + int prev_tid = start_tid; + int prev_eid = start_eid; + + // walk forward to next edge. if we hit start edge or boundary edge, + // we are done the walk. count number of edges as we go. + int count = 1; + while (true) + { + int i = 3 * prev_tid; + FIndex3i tv(Triangles[i], Triangles[i + 1], Triangles[i + 2]); + FIndex3i te(TriangleEdges[i], TriangleEdges[i + 1], TriangleEdges[i + 2]); + int vert_idx = IndexUtil::FindTriIndex(vID, tv); + int e1 = te[vert_idx], e2 = te[(vert_idx + 2) % 3]; + int next_eid = (e1 == prev_eid) ? e2 : e1; + if (next_eid == start_eid) + { + break; + } + FIndex2i next_eid_tris = GetEdgeT(next_eid); + int next_tid = (next_eid_tris[0] == prev_tid) ? next_eid_tris[1] : next_eid_tris[0]; + if (next_tid == InvalidID) + { + break; + } + prev_eid = next_eid; + prev_tid = next_tid; + count++; + } + + // if we did not see all edges at vertex, we have a bowtie + int target_count = (start_at_boundary) ? nEdges - 1 : nEdges; + bool is_bowtie = (target_count != count); + return is_bowtie; +} + + + + +int FDynamicMesh3::FindTriangle(int a, int b, int c) const +{ + int eid = FindEdge(a, b); + if (eid == InvalidID) + { + return InvalidID; + } + int ei = 4 * eid; + + // triangles attached to edge [a,b] must contain verts a and b... + int ti = 3 * Edges[ei + 2]; + if (Triangles[ti] == c || Triangles[ti + 1] == c || Triangles[ti + 2] == c) + { + return Edges[ei + 2]; + } + if (Edges[ei + 3] != InvalidID) + { + ti = 3 * Edges[ei + 3]; + if (Triangles[ti] == c || Triangles[ti + 1] == c || Triangles[ti + 2] == c) + { + return Edges[ei + 3]; + } + } + + return InvalidID; +} + + + +/** + * Computes bounding box of all vertices. + */ +FAxisAlignedBox3d FDynamicMesh3::GetBounds() const +{ + double x = 0, y = 0, z = 0; + for (int vi : VertexIndicesItr()) // find initial valid vertex + { + int k = 3 * vi; + x = Vertices[k]; y = Vertices[k + 1]; z = Vertices[k + 2]; + break; + } + double minx = x, maxx = x, miny = y, maxy = y, minz = z, maxz = z; + for (int vi : VertexIndicesItr()) + { + int k = 3 * vi; + x = Vertices[k]; y = Vertices[k + 1]; z = Vertices[k + 2]; + if (x < minx) minx = x; else if (x > maxx) maxx = x; + if (y < miny) miny = y; else if (y > maxy) maxy = y; + if (z < minz) minz = z; else if (z > maxz) maxz = z; + } + return FAxisAlignedBox3d(FVector3d(minx, miny, minz), FVector3d(maxx, maxy, maxz)); +} + + +FAxisAlignedBox3d FDynamicMesh3::GetCachedBounds() +{ + if (CachedBoundingBoxTimestamp != GetShapeTimestamp()) + { + CachedBoundingBox = GetBounds(); + CachedBoundingBoxTimestamp = GetShapeTimestamp(); + } + return CachedBoundingBox; +} + + + + +bool FDynamicMesh3::IsClosed() const +{ + if (TriangleCount() == 0) + { + return false; + } + + int N = MaxEdgeID(); + for (int i = 0; i < N; ++i) + { + if (EdgeRefCounts.IsValid(i) && IsBoundaryEdge(i)) + { + return false; + } + } + return true; +} + + +bool FDynamicMesh3::GetCachedIsClosed() +{ + if (CachedIsClosedTimestamp != GetTopologyTimestamp()) + { + bIsClosedCached = IsClosed(); + CachedIsClosedTimestamp = GetTopologyTimestamp(); + } + return bIsClosedCached; +} + + + + + +// average of 1 or 2 face normals +FVector3d FDynamicMesh3::GetEdgeNormal(int eID) const +{ + if (EdgeRefCounts.IsValid(eID)) + { + int ei = 4 * eID; + FVector3d n = GetTriNormal(Edges[ei + 2]); + if (Edges[ei + 3] != InvalidID) + { + n += GetTriNormal(Edges[ei + 3]); + n.Normalize(); + } + return n; + } + check(false); + return FVector3d::Zero(); +} + +FVector3d FDynamicMesh3::GetEdgePoint(int eID, double t) const +{ + t = VectorUtil::Clamp(t, 0.0, 1.0); + if (EdgeRefCounts.IsValid(eID)) + { + int ei = 4 * eID; + int iv0 = 3 * Edges[ei]; + int iv1 = 3 * Edges[ei + 1]; + double mt = 1.0 - t; + return FVector3d( + mt*Vertices[iv0] + t * Vertices[iv1], + mt*Vertices[iv0 + 1] + t * Vertices[iv1 + 1], + mt*Vertices[iv0 + 2] + t * Vertices[iv1 + 2]); + } + check(false); + return FVector3d::Zero(); +} + + + +void FDynamicMesh3::GetVtxOneRingCentroid(int vID, FVector3d& centroid) const +{ + centroid = FVector3d::Zero(); + if (VertexRefCounts.IsValid(vID)) + { + int n = 0; + for (int eid : VertexEdgeLists.Values(vID)) + { + int other_idx = 3 * GetOtherEdgeVertex(eid, vID); + centroid.X += Vertices[other_idx]; + centroid.Y += Vertices[other_idx + 1]; + centroid.Z += Vertices[other_idx + 2]; + n++; + } + if (n > 0) + { + centroid *= 1.0 / n; + } + } +} + + + +FFrame3d FDynamicMesh3::GetVertexFrame(int vID, bool bFrameNormalY) const +{ + check(HasVertexNormals()); + + int vi = 3 * vID; + FVector3d v(Vertices[vi], Vertices[vi + 1], Vertices[vi + 2]); + FVector3d normal((*VertexNormals)[vi], (*VertexNormals)[vi + 1], (*VertexNormals)[vi + 2]); + int eid = VertexEdgeLists.First(vID); + int ovi = 3 * GetOtherEdgeVertex(eid, vID); + FVector3d ov(Vertices[ovi], Vertices[ovi + 1], Vertices[ovi + 2]); + FVector3d edge = (ov - v); + edge.Normalize(); + + FVector3d other = normal.Cross(edge); + edge = other.Cross(normal); + if (bFrameNormalY) + { + return FFrame3d(v, edge, normal, -other); + } + else + { + return FFrame3d(v, edge, other, normal); + } +} + + + +FVector3d FDynamicMesh3::GetTriNormal(int tID) const +{ + FVector3d v0, v1, v2; + GetTriVertices(tID, v0, v1, v2); + return VectorUtil::Normal(v0, v1, v2); +} + +double FDynamicMesh3::GetTriArea(int tID) const +{ + FVector3d v0, v1, v2; + GetTriVertices(tID, v0, v1, v2); + return VectorUtil::Area(v0, v1, v2); +} + + + +void FDynamicMesh3::GetTriInfo(int tID, FVector3d& Normal, double& Area, FVector3d& Centroid) const +{ + FVector3d v0, v1, v2; + GetTriVertices(tID, v0, v1, v2); + Centroid = (v0 + v1 + v2) * (1.0 / 3.0); + Area = VectorUtil::Area(v0, v1, v2); + Normal = VectorUtil::Normal(v0, v1, v2); + //normal = FastNormalArea(ref v0, ref v1, ref v2, out fArea); +} + + +FVector3d FDynamicMesh3::GetTriBaryPoint(int tID, double bary0, double bary1, double bary2) const +{ + int ai = 3 * Triangles[3 * tID], + bi = 3 * Triangles[3 * tID + 1], + ci = 3 * Triangles[3 * tID + 2]; + return FVector3d( + (bary0*Vertices[ai] + bary1 * Vertices[bi] + bary2 * Vertices[ci]), + (bary0*Vertices[ai + 1] + bary1 * Vertices[bi + 1] + bary2 * Vertices[ci + 1]), + (bary0*Vertices[ai + 2] + bary1 * Vertices[bi + 2] + bary2 * Vertices[ci + 2])); +} + + +FVector3d FDynamicMesh3::GetTriBaryNormal(int tID, double bary0, double bary1, double bary2) const +{ + check(HasVertexNormals()); + int ai = 3 * Triangles[3 * tID], + bi = 3 * Triangles[3 * tID + 1], + ci = 3 * Triangles[3 * tID + 2]; + const TDynamicVector& normalsR = *(this->VertexNormals); + FVector3d n = FVector3d( + (bary0*normalsR[ai] + bary1 * normalsR[bi] + bary2 * normalsR[ci]), + (bary0*normalsR[ai + 1] + bary1 * normalsR[bi + 1] + bary2 * normalsR[ci + 1]), + (bary0*normalsR[ai + 2] + bary1 * normalsR[bi + 2] + bary2 * normalsR[ci + 2])); + n.Normalize(); + return n; +} + +FVector3d FDynamicMesh3::GetTriCentroid(int tID) const +{ + int ai = 3 * Triangles[3 * tID], + bi = 3 * Triangles[3 * tID + 1], + ci = 3 * Triangles[3 * tID + 2]; + double f = (1.0 / 3.0); + return FVector3d( + (Vertices[ai] + Vertices[bi] + Vertices[ci]) * f, + (Vertices[ai + 1] + Vertices[bi + 1] + Vertices[ci + 1]) * f, + (Vertices[ai + 2] + Vertices[bi + 2] + Vertices[ci + 2]) * f); +} + + +void FDynamicMesh3::GetTriBaryPoint(int tID, double bary0, double bary1, double bary2, FVertexInfo& vinfo) const +{ + vinfo = FVertexInfo(); + int ai = 3 * Triangles[3 * tID], + bi = 3 * Triangles[3 * tID + 1], + ci = 3 * Triangles[3 * tID + 2]; + vinfo.Position = FVector3d( + (bary0 * Vertices[ai] + bary1 * Vertices[bi] + bary2 * Vertices[ci]), + (bary0 * Vertices[ai + 1] + bary1 * Vertices[bi + 1] + bary2 * Vertices[ci + 1]), + (bary0 * Vertices[ai + 2] + bary1 * Vertices[bi + 2] + bary2 * Vertices[ci + 2])); + vinfo.bHaveN = HasVertexNormals(); + if (vinfo.bHaveN) + { + TDynamicVector& normalsR = *(this->VertexNormals); + vinfo.Normal = FVector3f( + (float)(bary0 * normalsR[ai] + bary1 * normalsR[bi] + bary2 * normalsR[ci]), + (float)(bary0 * normalsR[ai + 1] + bary1 * normalsR[bi + 1] + bary2 * normalsR[ci + 1]), + (float)(bary0 * normalsR[ai + 2] + bary1 * normalsR[bi + 2] + bary2 * normalsR[ci + 2])); + vinfo.Normal.Normalize(); + } + vinfo.bHaveC = HasVertexColors(); + if (vinfo.bHaveC) + { + TDynamicVector& colorsR = *(this->VertexColors); + vinfo.Color = FVector3f( + (float)(bary0 * colorsR[ai] + bary1 * colorsR[bi] + bary2 * colorsR[ci]), + (float)(bary0 * colorsR[ai + 1] + bary1 * colorsR[bi + 1] + bary2 * colorsR[ci + 1]), + (float)(bary0 * colorsR[ai + 2] + bary1 * colorsR[bi + 2] + bary2 * colorsR[ci + 2])); + } + vinfo.bHaveUV = HasVertexUVs(); + if (vinfo.bHaveUV) + { + TDynamicVector& uvR = *(this->VertexUVs); + ai = 2 * Triangles[3 * tID]; + bi = 2 * Triangles[3 * tID + 1]; + ci = 2 * Triangles[3 * tID + 2]; + vinfo.UV = FVector2f( + (float)(bary0 * uvR[ai] + bary1 * uvR[bi] + bary2 * uvR[ci]), + (float)(bary0 * uvR[ai + 1] + bary1 * uvR[bi + 1] + bary2 * uvR[ci + 1])); + } +} + + +FAxisAlignedBox3d FDynamicMesh3::GetTriBounds(int tID) const +{ + int vi = 3 * Triangles[3 * tID]; + double x = Vertices[vi], y = Vertices[vi + 1], z = Vertices[vi + 2]; + double minx = x, maxx = x, miny = y, maxy = y, minz = z, maxz = z; + for (int i = 1; i < 3; ++i) + { + vi = 3 * Triangles[3 * tID + i]; + x = Vertices[vi]; y = Vertices[vi + 1]; z = Vertices[vi + 2]; + if (x < minx) minx = x; else if (x > maxx) maxx = x; + if (y < miny) miny = y; else if (y > maxy) maxy = y; + if (z < minz) minz = z; else if (z > maxz) maxz = z; + } + return FAxisAlignedBox3d(FVector3d(minx, miny, minz), FVector3d(maxx, maxy, maxz)); +} + + +FFrame3d FDynamicMesh3::GetTriFrame(int tID, int nEdge) const +{ + int ti = 3 * tID; + int a = 3 * Triangles[ti + (nEdge % 3)]; + int b = 3 * Triangles[ti + ((nEdge + 1) % 3)]; + int c = 3 * Triangles[ti + ((nEdge + 2) % 3)]; + FVector3d v1(Vertices[a], Vertices[a + 1], Vertices[a + 2]); + FVector3d v2(Vertices[b], Vertices[b + 1], Vertices[b + 2]); + FVector3d v3(Vertices[c], Vertices[c + 1], Vertices[c + 2]); + + FVector3d edge1 = v2 - v1; edge1.Normalize(); + FVector3d edge2 = v3 - v2; edge2.Normalize(); + FVector3d normal = edge2.Cross(edge1); normal.Normalize(); + + FVector3d other = normal.Cross(edge1); + + FVector3d center = (v1 + v2 + v3) / 3; + return FFrame3d(center, edge1, other, normal); +} + + + +double FDynamicMesh3::GetTriSolidAngle(int tID, const FVector3d& p) const +{ + // inlined version of GetTriVertices & VectorUtil::TriSolidAngle + int ti = 3 * tID; + int ta = 3 * Triangles[ti]; + FVector3d a(Vertices[ta] - p.X, Vertices[ta + 1] - p.Y, Vertices[ta + 2] - p.Z); + int tb = 3 * Triangles[ti + 1]; + FVector3d b(Vertices[tb] - p.X, Vertices[tb + 1] - p.Y, Vertices[tb + 2] - p.Z); + int tc = 3 * Triangles[ti + 2]; + FVector3d c(Vertices[tc] - p.X, Vertices[tc + 1] - p.Y, Vertices[tc + 2] - p.Z); + double la = a.Length(), lb = b.Length(), lc = c.Length(); + double top = (la * lb * lc) + a.Dot(b) * lc + b.Dot(c) * la + c.Dot(a) * lb; + double bottom = a.X * (b.Y * c.Z - c.Y * b.Z) - a.Y * (b.X * c.Z - c.X * b.Z) + a.Z * (b.X * c.Y - c.X * b.Y); + // -2 instead of 2 to account for UE winding + return -2.0 * atan2(bottom, top); +} + + + +double FDynamicMesh3::GetTriInternalAngleR(int tID, int i) +{ + int ti = 3 * tID; + int ta = 3 * Triangles[ti]; + FVector3d a(Vertices[ta], Vertices[ta + 1], Vertices[ta + 2]); + int tb = 3 * Triangles[ti + 1]; + FVector3d b(Vertices[tb], Vertices[tb + 1], Vertices[tb + 2]); + int tc = 3 * Triangles[ti + 2]; + FVector3d c(Vertices[tc], Vertices[tc + 1], Vertices[tc + 2]); + if (i == 0) + { + return (b - a).Normalized().AngleR((c - a).Normalized()); + } + else if (i == 1) + { + return (a - b).Normalized().AngleR((c - b).Normalized()); + } + else + { + return (a - c).Normalized().AngleR((b - c).Normalized()); + } +} + + +double FDynamicMesh3::CalculateWindingNumber(const FVector3d& QueryPoint) const +{ + double sum = 0; + for (int tid : TriangleIndicesItr()) + { + sum += GetTriSolidAngle(tid, QueryPoint); + } + return sum / FMathd::FourPi; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshAttributeSet.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshAttributeSet.cpp new file mode 100644 index 000000000000..1d3651a0b7b1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshAttributeSet.cpp @@ -0,0 +1,63 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DynamicMeshAttributeSet.h" + + + + +bool FDynamicMeshAttributeSet::IsSeamEdge(int eid) const +{ + return UV0.IsSeamEdge(eid) || Normals0.IsSeamEdge(eid); +} + + + +void FDynamicMeshAttributeSet::OnNewTriangle(int TriangleID, bool bInserted) +{ + UV0.InitializeNewTriangle(TriangleID); + Normals0.InitializeNewTriangle(TriangleID); +} + +void FDynamicMeshAttributeSet::OnRemoveTriangle(int TriangleID, bool bRemoveIsolatedVertices) +{ + UV0.OnRemoveTriangle(TriangleID, bRemoveIsolatedVertices); + Normals0.OnRemoveTriangle(TriangleID, bRemoveIsolatedVertices); +} + +void FDynamicMeshAttributeSet::OnReverseTriOrientation(int TriangleID) +{ + UV0.OnReverseTriOrientation(TriangleID); + Normals0.OnReverseTriOrientation(TriangleID); +} + +void FDynamicMeshAttributeSet::OnSplitEdge(const FDynamicMesh3::FEdgeSplitInfo & splitInfo) +{ + UV0.OnSplitEdge(splitInfo); + Normals0.OnSplitEdge(splitInfo); +} + +void FDynamicMeshAttributeSet::OnFlipEdge(const FDynamicMesh3::FEdgeFlipInfo & flipInfo) +{ + UV0.OnFlipEdge(flipInfo); + Normals0.OnFlipEdge(flipInfo); +} + + +void FDynamicMeshAttributeSet::OnCollapseEdge(const FDynamicMesh3::FEdgeCollapseInfo & collapseInfo) +{ + UV0.OnCollapseEdge(collapseInfo); + Normals0.OnCollapseEdge(collapseInfo); +} + +void FDynamicMeshAttributeSet::OnPokeTriangle(const FDynamicMesh3::FPokeTriangleInfo & pokeInfo) +{ + UV0.OnPokeTriangle(pokeInfo); + Normals0.OnPokeTriangle(pokeInfo); +} + +void FDynamicMeshAttributeSet::OnMergeEdges(const FDynamicMesh3::FMergeEdgesInfo & mergeInfo) +{ + UV0.OnMergeEdges(mergeInfo); + Normals0.OnMergeEdges(mergeInfo); +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshEditor.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshEditor.cpp new file mode 100644 index 000000000000..d9203485baad --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshEditor.cpp @@ -0,0 +1,614 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "DynamicMeshEditor.h" +#include "DynamicMeshAttributeSet.h" +#include "Util/BufferUtil.h" +#include "MeshRegionBoundaryLoops.h" + + + +void FMeshIndexMappings::Initialize(FDynamicMesh3* Mesh) +{ + if (Mesh->HasAttributes()) + { + FDynamicMeshAttributeSet* Attribs = Mesh->Attributes(); + UVMaps.SetNum(Attribs->NumUVLayers()); + NormalMaps.SetNum(Attribs->NumNormalLayers()); + } +} + + + + + +void FDynamicMeshEditResult::GetAllTriangles(TArray& TrianglesOut) const +{ + BufferUtil::AppendElements(TrianglesOut, NewTriangles); + + int NumQuads = NewQuads.Num(); + for (int k = 0; k < NumQuads; ++k) + { + TrianglesOut.Add(NewQuads[k].A); + TrianglesOut.Add(NewQuads[k].B); + } + int NumPolys = NewPolygons.Num(); + for (int k = 0; k < NumPolys; ++k) + { + BufferUtil::AppendElements(TrianglesOut, NewPolygons[k]); + } +} + + + + + + +bool FDynamicMeshEditor::StitchVertexLoopsMinimal(const TArray& Loop1, const TArray& Loop2, FDynamicMeshEditResult& ResultOut) +{ + int N = Loop1.Num(); + checkf(N == Loop2.Num(), TEXT("FDynamicMeshEditor::StitchLoop: loops are not the same length!")); + if (N != Loop2.Num()) + { + return false; + } + + ResultOut.NewQuads.Reserve(N); + ResultOut.NewGroups.Reserve(N); + + int i = 0; + for (; i < N; ++i) + { + int a = Loop1[i]; + int b = Loop1[(i + 1) % N]; + int c = Loop2[i]; + int d = Loop2[(i + 1) % N]; + + int NewGroupID = Mesh->AllocateTriangleGroup(); + ResultOut.NewGroups.Add(NewGroupID); + + FIndex3i t1(b, a, d); + int tid1 = Mesh->AppendTriangle(t1, NewGroupID); + + FIndex3i t2(a, c, d); + int tid2 = Mesh->AppendTriangle(t2, NewGroupID); + + ResultOut.NewQuads.Add(FIndex2i(tid1, tid2)); + + if (tid1 < 0 || tid2 < 0) + { + goto operation_failed; + } + } + + return true; + +operation_failed: + // remove what we added so far + if (i > 0) + { + ResultOut.NewTriangles.SetNum(2 * i + 1); + if (RemoveTriangles(ResultOut.NewTriangles, false) == false) + { + checkf(false, TEXT("FDynamicMeshEditor::StitchLoop: failed to add all triangles, and also failed to back out changes.")); + } + } + return false; +} + + + + +bool FDynamicMeshEditor::RemoveTriangles(const TArray& Triangles, bool bRemoveIsolatedVerts) +{ + bool bAllOK = true; + int NumTriangles = Triangles.Num(); + for (int i = 0; i < NumTriangles; ++i) + { + if (Mesh->IsTriangle(Triangles[i]) == false) + { + continue; + } + + EMeshResult result = Mesh->RemoveTriangle(Triangles[i], bRemoveIsolatedVerts, false); + if (result != EMeshResult::Ok) + { + bAllOK = false; + } + } + return bAllOK; +} + + + + + +/** + * Make a copy of provided triangles, with new vertices. You provide IndexMaps because + * you know if you are doing a small subset or a full-mesh-copy. + */ +void FDynamicMeshEditor::DuplicateTriangles(const TArray& Triangles, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut) +{ + ResultOut.Reset(); + IndexMaps.Initialize(Mesh); + + for (int TriangleID : Triangles) + { + FIndex3i Tri = Mesh->GetTriangle(TriangleID); + + int NewGroupID = FindOrCreateDuplicateGroup(TriangleID, IndexMaps, ResultOut); + + FIndex3i NewTri; + NewTri[0] = FindOrCreateDuplicateVertex(Tri[0], IndexMaps, ResultOut); + NewTri[1] = FindOrCreateDuplicateVertex(Tri[1], IndexMaps, ResultOut); + NewTri[2] = FindOrCreateDuplicateVertex(Tri[2], IndexMaps, ResultOut); + + int NewTriangleID = Mesh->AppendTriangle(NewTri, NewGroupID); + IndexMaps.SetTriangle(TriangleID, NewTriangleID); + ResultOut.NewTriangles.Add(NewTriangleID); + + CopyAttributes(TriangleID, NewTriangleID, IndexMaps, ResultOut); + + //Mesh->CheckValidity(true); + } + +} + + + + +bool FDynamicMeshEditor::DisconnectTriangles(const TArray& Triangles, TArray& LoopSetOut) +{ + check(Mesh->HasAttributes() == false); // not supported yet + + // find the region boundary loops + FMeshRegionBoundaryLoops RegionLoops(Mesh, Triangles, false); + bool bOK = RegionLoops.Compute(); + check(bOK); + if (!bOK) + { + return false; + } + TArray& Loops = RegionLoops.Loops; + + // need to test Contains() many times + TSet TriangleSet; + TriangleSet.Reserve(Triangles.Num() * 3); + for (int TriID : Triangles) + { + TriangleSet.Add(TriID); + } + + // process each loop island + int NumLoops = Loops.Num(); + LoopSetOut.SetNum(NumLoops); + for ( int li = 0; li < NumLoops; ++li) + { + FEdgeLoop& Loop = Loops[li]; + FLoopPairSet& LoopPair = LoopSetOut[li]; + LoopPair.LoopA = Loop; + + // duplicate the vertices + int NumVertices = Loop.Vertices.Num(); + TMap LoopVertexMap; LoopVertexMap.Reserve(NumVertices); + TArray NewVertexLoop; NewVertexLoop.SetNum(NumVertices); + for (int vi = 0; vi < NumVertices; ++vi) + { + int VertID = Loop.Vertices[vi]; + int NewVertID = Mesh->AppendVertex(*Mesh, VertID); + LoopVertexMap.Add(Loop.Vertices[vi], NewVertID); + NewVertexLoop[vi] = NewVertID; + } + + // for each border triangle, rewrite vertices + int NumEdges = Loop.Edges.Num(); + for (int ei = 0; ei < NumEdges; ++ei) + { + int EdgeID = Loop.Edges[ei]; + FIndex2i EdgeTris = Mesh->GetEdgeT(EdgeID); + int EditTID = TriangleSet.Contains(EdgeTris.A) ? EdgeTris.A : EdgeTris.B; + if (EditTID == FDynamicMesh3::InvalidID) + { + continue; // happens on final edge, and on input boundary edges + } + + FIndex3i OldTri = Mesh->GetTriangle(EditTID); + FIndex3i NewTri = OldTri; + int Modified = 0; + for (int j = 0; j < 3; ++j) + { + const int* NewVertID = LoopVertexMap.Find(OldTri[j]); + if (NewVertID != nullptr) + { + NewTri[j] = *NewVertID; + ++Modified; + } + } + if (Modified > 0) + { + Mesh->SetTriangle(EditTID, NewTri, false); + } + } + + LoopPair.LoopB.InitializeFromVertices(Mesh, NewVertexLoop, false); + } + + return true; +} + + + + +FVector3f FDynamicMeshEditor::ComputeAndSetQuadNormal(const FIndex2i& QuadTris, bool bIsPlanar) +{ + FVector3f Normal(0, 0, 1); + if (bIsPlanar) + { + Normal = (FVector3f)Mesh->GetTriNormal(QuadTris.A); + } + else + { + Normal = (FVector3f)Mesh->GetTriNormal(QuadTris.A); + Normal += (FVector3f)Mesh->GetTriNormal(QuadTris.B); + Normal.Normalize(); + } + SetQuadNormals(QuadTris, Normal); + return Normal; +} + + + + +void FDynamicMeshEditor::SetQuadNormals(const FIndex2i& QuadTris, const FVector3f& Normal) +{ + check(Mesh->HasAttributes()); + FDynamicMeshNormalOverlay* Normals = Mesh->Attributes()->PrimaryNormals(); + + FIndex3i Triangle1 = Mesh->GetTriangle(QuadTris.A); + + FIndex3i NormalTriangle1; + NormalTriangle1[0] = Normals->AppendElement(Normal, Triangle1[0]); + NormalTriangle1[1] = Normals->AppendElement(Normal, Triangle1[1]); + NormalTriangle1[2] = Normals->AppendElement(Normal, Triangle1[2]); + Normals->SetTriangle(QuadTris.A, NormalTriangle1); + + if (Mesh->IsTriangle(QuadTris.B)) + { + FIndex3i Triangle2 = Mesh->GetTriangle(QuadTris.B); + FIndex3i NormalTriangle2; + for (int j = 0; j < 3; ++j) + { + int i = Triangle1.IndexOf(Triangle2[j]); + if (i == -1) + { + NormalTriangle2[j] = Normals->AppendElement(Normal, Triangle2[j]); + } + else + { + NormalTriangle2[j] = NormalTriangle1[i]; + } + } + Normals->SetTriangle(QuadTris.B, NormalTriangle2); + } + +} + + +void FDynamicMeshEditor::SetTriangleNormals(const TArray& Triangles, const FVector3f& Normal) +{ + check(Mesh->HasAttributes()); + FDynamicMeshNormalOverlay* Normals = Mesh->Attributes()->PrimaryNormals(); + + TMap Vertices; + + for (int tid : Triangles) + { + FIndex3i BaseTri = Mesh->GetTriangle(tid); + FIndex3i ElemTri; + for (int j = 0; j < 3; ++j) + { + const int* FoundElementID = Vertices.Find(BaseTri[j]); + if (FoundElementID == nullptr) + { + ElemTri[j] = Normals->AppendElement(Normal, BaseTri[j]); + Vertices.Add(BaseTri[j], ElemTri[j]); + } + else + { + ElemTri[j] = *FoundElementID; + } + } + Normals->SetTriangle(tid, ElemTri); + } +} + + + + + +void FDynamicMeshEditor::SetQuadUVsFromProjection(const FIndex2i& QuadTris, const FFrame3f& ProjectionFrame, float UVScaleFactor, int UVLayerIndex) +{ + check(Mesh->HasAttributes() && Mesh->Attributes()->NumUVLayers() > UVLayerIndex ); + FDynamicMeshUVOverlay* UVs = Mesh->Attributes()->GetUVLayer(UVLayerIndex); + + FIndex4i AllUVIndices(-1, -1, -1, -1); + FVector2f AllUVs[4]; + + // project first triangle + FIndex3i Triangle1 = Mesh->GetTriangle(QuadTris.A); + FIndex3i UVTriangle1; + for (int j = 0; j < 3; ++j) + { + FVector2f UV = ProjectionFrame.ToPlaneUV( (FVector3f)Mesh->GetVertex(Triangle1[j]), 2); + UVTriangle1[j] = UVs->AppendElement(UV, Triangle1[j]); + AllUVs[j] = UV; + AllUVIndices[j] = UVTriangle1[j]; + } + UVs->SetTriangle(QuadTris.A, UVTriangle1); + + // project second triangle + if (Mesh->IsTriangle(QuadTris.B)) + { + FIndex3i Triangle2 = Mesh->GetTriangle(QuadTris.B); + FIndex3i UVTriangle2; + for (int j = 0; j < 3; ++j) + { + int i = Triangle1.IndexOf(Triangle2[j]); + if (i == -1) + { + FVector2f UV = ProjectionFrame.ToPlaneUV( (FVector3f)Mesh->GetVertex(Triangle2[j]), 2); + UVTriangle2[j] = UVs->AppendElement(UV, Triangle2[j]); + AllUVs[3] = UV; + AllUVIndices[3] = UVTriangle2[j]; + } + else + { + UVTriangle2[j] = UVTriangle1[i]; + } + } + UVs->SetTriangle(QuadTris.B, UVTriangle2); + } + + // shift UVs so that their bbox min-corner is at origin and scaled by external scale factor + FAxisAlignedBox2f UVBounds(FAxisAlignedBox2f::Empty()); + UVBounds.Contain(AllUVs[0]); UVBounds.Contain(AllUVs[1]); UVBounds.Contain(AllUVs[2]); + if (AllUVIndices[3] != -1) + { + UVBounds.Contain(AllUVs[3]); + } + for (int j = 0; j < 4; ++j) + { + if (AllUVIndices[j] != -1) + { + FVector2f TransformedUV = (AllUVs[j] - UVBounds.Min) * UVScaleFactor; + UVs->SetElement(AllUVIndices[j], TransformedUV); + } + } +} + + + + + + +void FDynamicMeshEditor::ReverseTriangleOrientations(const TArray& Triangles, bool bInvertNormals) +{ + for (int tid : Triangles) + { + Mesh->ReverseTriOrientation(tid); + } + if (bInvertNormals) + { + InvertTriangleNormals(Triangles); + } +} + + +void FDynamicMeshEditor::InvertTriangleNormals(const TArray& Triangles) +{ + // @todo re-use the TBitA + + if (Mesh->HasVertexNormals()) + { + TBitArray DoneVertices(false, Mesh->MaxVertexID()); + for (int TriangleID : Triangles) + { + FIndex3i Tri = Mesh->GetTriangle(TriangleID); + for (int j = 0; j < 3; ++j) + { + if (DoneVertices[Tri[j]] == false) + { + Mesh->SetVertexNormal(Tri[j], -Mesh->GetVertexNormal(Tri[j])); + DoneVertices[Tri[j]] = true; + } + } + } + } + + + if (Mesh->HasAttributes()) + { + for (FDynamicMeshNormalOverlay* Normals : Mesh->Attributes()->GetAllNormalLayers()) + { + TBitArray DoneNormals(false, Normals->MaxElementID()); + for (int TriangleID : Triangles) + { + FIndex3i ElemTri = Normals->GetTriangle(TriangleID); + for (int j = 0; j < 3; ++j) + { + if (DoneNormals[ElemTri[j]] == false) + { + Normals->SetElement(ElemTri[j], -Normals->GetElement(ElemTri[j])); + DoneNormals[ElemTri[j]] = true; + } + } + } + } + } +} + + + + + +void FDynamicMeshEditor::CopyAttributes(int FromTriangleID, int ToTriangleID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut) +{ + if (Mesh->HasAttributes() == false) + { + return; + } + + int UVLayerIndex = 0; + for (FDynamicMeshUVOverlay* UVOverlay : Mesh->Attributes()->GetAllUVLayers()) + { + FIndex3i FromElemTri = UVOverlay->GetTriangle(FromTriangleID); + FIndex3i ToElemTri = UVOverlay->GetTriangle(ToTriangleID); + for (int j = 0; j < 3; ++j) + { + if (FromElemTri[j] != FDynamicMesh3::InvalidID ) + { + int NewElemID = FindOrCreateDuplicateUV(FromElemTri[j], UVLayerIndex, IndexMaps); + ToElemTri[j] = NewElemID; + } + } + UVOverlay->SetTriangle(ToTriangleID, ToElemTri); + UVLayerIndex++; + } + + + int NormalLayerIndex = 0; + for (FDynamicMeshNormalOverlay* NormalOverlay : Mesh->Attributes()->GetAllNormalLayers()) + { + FIndex3i FromElemTri = NormalOverlay->GetTriangle(FromTriangleID); + FIndex3i ToElemTri = NormalOverlay->GetTriangle(ToTriangleID); + for (int j = 0; j < 3; ++j) + { + if (FromElemTri[j] != FDynamicMesh3::InvalidID) + { + int NewElemID = FindOrCreateDuplicateNormal(FromElemTri[j], NormalLayerIndex, IndexMaps); + ToElemTri[j] = NewElemID; + } + } + NormalOverlay->SetTriangle(ToTriangleID, ToElemTri); + NormalLayerIndex++; + } +} + + + +int FDynamicMeshEditor::FindOrCreateDuplicateUV(int ElementID, int UVLayerIndex, FMeshIndexMappings& IndexMaps) +{ + int NewElementID = IndexMaps.GetNewUV(UVLayerIndex, ElementID); + if (NewElementID == IndexMaps.InvalidID()) + { + FDynamicMeshUVOverlay* UVOverlay = Mesh->Attributes()->GetUVLayer(UVLayerIndex); + + // need to determine new parent vertex. It should be in the map already! + int ParentVertexID = UVOverlay->GetParentVertex(ElementID); + int NewParentVertexID = IndexMaps.GetNewVertex(ParentVertexID); + check(NewParentVertexID != IndexMaps.InvalidID()); + + NewElementID = UVOverlay->AppendElement( + UVOverlay->GetElement(ElementID), NewParentVertexID); + + IndexMaps.SetUV(UVLayerIndex, ElementID, NewElementID); + } + return NewElementID; +} + + + +int FDynamicMeshEditor::FindOrCreateDuplicateNormal(int ElementID, int NormalLayerIndex, FMeshIndexMappings& IndexMaps) +{ + int NewElementID = IndexMaps.GetNewNormal(NormalLayerIndex, ElementID); + if (NewElementID == IndexMaps.InvalidID()) + { + FDynamicMeshNormalOverlay* NormalOverlay = Mesh->Attributes()->GetNormalLayer(NormalLayerIndex); + + // need to determine new parent vertex. It should be in the map already! + int ParentVertexID = NormalOverlay->GetParentVertex(ElementID); + int NewParentVertexID = IndexMaps.GetNewVertex(ParentVertexID); + check(NewParentVertexID != IndexMaps.InvalidID()); + + NewElementID = NormalOverlay->AppendElement( + NormalOverlay->GetElement(ElementID), NewParentVertexID); + + IndexMaps.SetNormal(NormalLayerIndex, ElementID, NewElementID); + } + return NewElementID; +} + + + +int FDynamicMeshEditor::FindOrCreateDuplicateVertex(int VertexID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut) +{ + int NewVertexID = IndexMaps.GetNewVertex(VertexID); + if (NewVertexID == IndexMaps.InvalidID()) + { + NewVertexID = Mesh->AppendVertex(*Mesh, VertexID); + IndexMaps.SetVertex(VertexID, NewVertexID); + ResultOut.NewVertices.Add(NewVertexID); + } + return NewVertexID; +} + + + +int FDynamicMeshEditor::FindOrCreateDuplicateGroup(int TriangleID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut) +{ + int GroupID = Mesh->GetTriangleGroup(TriangleID); + int NewGroupID = IndexMaps.GetNewGroup(GroupID); + if (NewGroupID == IndexMaps.InvalidID()) + { + NewGroupID = Mesh->AllocateTriangleGroup(); + IndexMaps.SetGroup(GroupID, NewGroupID); + ResultOut.NewGroups.Add(NewGroupID); + } + return NewGroupID; +} + + + + +void FDynamicMeshEditor::AppendMesh(const FDynamicMesh3* AppendMesh, + FMeshIndexMappings& IndexMapsOut, FDynamicMeshEditResult& ResultOut, + TFunction PositionTransform, + TFunction NormalTransform) +{ + IndexMapsOut.Reset(); + + FIndexMapi& VertexMap = IndexMapsOut.GetVertexMap(); + VertexMap.Reserve(AppendMesh->VertexCount()); + for (int VertID : AppendMesh->VertexIndicesItr()) + { + FVector3d Position = AppendMesh->GetVertex(VertID); + if (PositionTransform != nullptr) + { + Position = PositionTransform(VertID, Position); + } + int NewVertID = Mesh->AppendVertex(Position); + VertexMap.Add(VertID, NewVertID); + + if (AppendMesh->HasVertexNormals() && Mesh->HasVertexNormals()) + { + FVector3f Normal = AppendMesh->GetVertexNormal(VertID); + if (NormalTransform != nullptr) + { + Normal = NormalTransform(VertID, Normal); + } + Mesh->SetVertexNormal(NewVertID, Normal); + } + + if (AppendMesh->HasVertexColors() && Mesh->HasVertexColors()) + { + FVector3f Color = AppendMesh->GetVertexColor(VertID); + Mesh->SetVertexColor(NewVertID, Color); + } + } + + for (int TriID : AppendMesh->TriangleIndicesItr()) + { + FIndex3i Tri = AppendMesh->GetTriangle(TriID); + int NewTriID = Mesh->AppendTriangle(VertexMap.GetTo(Tri.A), VertexMap.GetTo(Tri.B), VertexMap.GetTo(Tri.C)); + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshModule.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshModule.cpp new file mode 100644 index 000000000000..cf6ab46cf365 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshModule.cpp @@ -0,0 +1,20 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DynamicMeshModule.h" + +#define LOCTEXT_NAMESPACE "FDynamicMeshModule" + +void FDynamicMeshModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FDynamicMeshModule::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(FDynamicMeshModule, DynamicMesh) \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshOverlay.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshOverlay.cpp new file mode 100644 index 000000000000..87825f38a27d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshOverlay.cpp @@ -0,0 +1,668 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DynamicMeshOverlay.h" +#include "DynamicMesh3.h" + + + + +template +void TDynamicMeshOverlay::ClearElements() +{ + Elements.Clear(); + ElementsRefCounts = FRefCountVector(); + ParentVertices.Clear(); + InitializeTriangles(ParentMesh->MaxTriangleID()); +} + + + +template +int TDynamicMeshOverlay::AppendElement(RealType ConstantValue, int SourceVertex) +{ + int vid = ElementsRefCounts.Allocate(); + int i = ElementSize * vid; + for (int k = ElementSize - 1; k >= 0; --k) + { + Elements.InsertAt(ConstantValue, i + k); + } + ParentVertices.InsertAt(SourceVertex, vid); + + //updateTimeStamp(true); + return vid; +} + + +template +int TDynamicMeshOverlay::AppendElement(const RealType* Value, int SourceVertex) +{ + int vid = ElementsRefCounts.Allocate(); + int i = ElementSize * vid; + + // insert in reverse order so that Resize() is only called once + for (int k = ElementSize - 1; k >= 0; --k) + { + Elements.InsertAt(Value[k], i + k); + } + + ParentVertices.InsertAt(SourceVertex, vid); + + //updateTimeStamp(true); + return vid; +} + + + + +template +void TDynamicMeshOverlay::InitializeTriangles(int MaxTriangleID) +{ + ElementTriangles.Resize(0); + ElementTriangles.Resize(MaxTriangleID * 3, FDynamicMesh3::InvalidID); +} + + + + +template +EMeshResult TDynamicMeshOverlay::SetTriangle(int tid, const FIndex3i& tv) +{ + if (IsElement(tv[0]) == false || IsElement(tv[1]) == false || IsElement(tv[2]) == false) + { + check(false); + return EMeshResult::Failed_NotAVertex; + } + if (tv[0] == tv[1] || tv[0] == tv[2] || tv[1] == tv[2]) + { + check(false); + return EMeshResult::Failed_InvalidNeighbourhood; + } + + InternalSetTriangle(tid, tv, true); + + //updateTimeStamp(true); + return EMeshResult::Ok; +} + + +template +void TDynamicMeshOverlay::InternalSetTriangle(int tid, const FIndex3i& tv, bool bIncrementRefCounts) +{ + int i = 3 * tid; + ElementTriangles.InsertAt(tv[2], i + 2); + ElementTriangles.InsertAt(tv[1], i + 1); + ElementTriangles.InsertAt(tv[0], i); + + if (bIncrementRefCounts) + { + ElementsRefCounts.Increment(tv[0]); + ElementsRefCounts.Increment(tv[1]); + ElementsRefCounts.Increment(tv[2]); + } +} + + + +template +void TDynamicMeshOverlay::InitializeNewTriangle(int tid) +{ + int i = 3 * tid; + ElementTriangles.InsertAt(FDynamicMesh3::InvalidID, i + 2); + ElementTriangles.InsertAt(FDynamicMesh3::InvalidID, i + 1); + ElementTriangles.InsertAt(FDynamicMesh3::InvalidID, i); + + //updateTimeStamp(true); +} + + + + +template +bool TDynamicMeshOverlay::IsSeamEdge(int eid) const +{ + FIndex2i et = ParentMesh->GetEdgeT(eid); + if (et.B == FDynamicMesh3::InvalidID) + { + return true; + } + + FIndex2i ev = ParentMesh->GetEdgeV(eid); + int base_a = ev.A, base_b = ev.B; + + FIndex3i Triangle0 = GetTriangle(et.A); + FIndex3i BaseTriangle0(ParentVertices[Triangle0.A], ParentVertices[Triangle0.B], ParentVertices[Triangle0.C]); + int idx_base_a1 = BaseTriangle0.IndexOf(base_a); + int idx_base_b1 = BaseTriangle0.IndexOf(base_b); + + FIndex3i Triangle1 = GetTriangle(et.B); + FIndex3i BaseTriangle1(ParentVertices[Triangle1.A], ParentVertices[Triangle1.B], ParentVertices[Triangle1.C]); + int idx_base_a2 = BaseTriangle1.IndexOf(base_a); + int idx_base_b2 = BaseTriangle1.IndexOf(base_b); + + return !IndexUtil::SamePairUnordered(Triangle0[idx_base_a1], Triangle0[idx_base_b1], Triangle1[idx_base_a2], Triangle1[idx_base_b2]); + + + // [RMS] this doesn't seem to work but it should, and would be more efficient. + // - add ParentMesh->FindTriEdgeIndex(tid,eid) + // - SamePairUnordered query could directly index into ElementTriangles[] + //FIndex3i TriangleA = GetTriangle(et.A); + //FIndex3i TriangleB = GetTriangle(et.B); + + //FIndex3i BaseTriEdgesA = ParentMesh->GetTriEdges(et.A); + //int WhichA = (BaseTriEdgesA.A == eid) ? 0 : + // ((BaseTriEdgesA.B == eid) ? 1 : 2); + + //FIndex3i BaseTriEdgesB = ParentMesh->GetTriEdges(et.B); + //int WhichB = (BaseTriEdgesB.A == eid) ? 0 : + // ((BaseTriEdgesB.B == eid) ? 1 : 2); + + //return SamePairUnordered( + // TriangleA[WhichA], TriangleA[(WhichA + 1) % 3], + // TriangleB[WhichB], TriangleB[(WhichB + 1) % 3]); +} + + + +template +bool TDynamicMeshOverlay::IsSeamVertex(int vid) const +{ + // @todo can we do this more efficiently? At minimum we are looking up each triangle twice... + for (int edgeid : ParentMesh->VtxEdgesItr(vid)) + { + if (IsSeamEdge(edgeid)) + { + return true; + } + } + return false; +} + + +template +void TDynamicMeshOverlay::GetVertexElements(int vid, TArray& OutElements) const +{ + OutElements.Reset(); + for (int tid : ParentMesh->VtxTrianglesItr(vid)) + { + FIndex3i Triangle = GetTriangle(tid); + for (int j = 0; j < 3; ++j) + { + if (ParentVertices[Triangle[j]] == vid) + { + OutElements.AddUnique(Triangle[j]); + } + } + } +} + + + +template +int TDynamicMeshOverlay::CountVertexElements(int vid, bool bBruteForce) const +{ + TArray VertexElements; + if (bBruteForce) + { + for (int tid : ParentMesh->TriangleIndicesItr()) + { + FIndex3i Triangle = GetTriangle(tid); + for (int j = 0; j < 3; ++j) + { + if (ParentVertices[Triangle[j]] == vid) + { + VertexElements.AddUnique(Triangle[j]); + } + } + } + } + else + { + for (int tid : ParentMesh->VtxTrianglesItr(vid)) + { + FIndex3i Triangle = GetTriangle(tid); + for (int j = 0; j < 3; ++j) + { + if (ParentVertices[Triangle[j]] == vid) + { + VertexElements.AddUnique(Triangle[j]); + } + } + } + } + + return VertexElements.Num(); +} + + + + +template +void TDynamicMeshOverlay::OnRemoveTriangle(int TriangleID, bool bRemoveIsolatedVertices) +{ + FIndex3i Triangle = GetTriangle(TriangleID); + InitializeNewTriangle(TriangleID); + + // decrement element refcounts, and free element if it is now unreferenced + for (int j = 0; j < 3; ++j) + { + int elemid = Triangle[j]; + ElementsRefCounts.Decrement(elemid); + if (bRemoveIsolatedVertices && ElementsRefCounts.GetRefCount(elemid) == 1) + { + ElementsRefCounts.Decrement(elemid); + ParentVertices[elemid] = FDynamicMesh3::InvalidID; + check(ElementsRefCounts.IsValid(elemid) == false); + } + } +} + + +template +void TDynamicMeshOverlay::OnReverseTriOrientation(int TriangleID) +{ + FIndex3i Triangle = GetTriangle(TriangleID); + int i = 3 * TriangleID; + ElementTriangles[i] = Triangle[1]; // mirrors order in FDynamicMesh3::ReverseTriOrientationInternal + ElementTriangles[i + 1] = Triangle[0]; + ElementTriangles[i + 2] = Triangle[2]; +} + + + +//#pragma optimize( "", off ) +template +void TDynamicMeshOverlay::OnSplitEdge(const FDynamicMesh3::FEdgeSplitInfo& splitInfo) +{ + int orig_t0 = splitInfo.OriginalTriangles.A; + int orig_t1 = splitInfo.OriginalTriangles.B; + int base_a = splitInfo.OriginalVertices.A; + int base_b = splitInfo.OriginalVertices.B; + + // look up current triangle 0, and infer base triangle 0 + // @todo handle case where these are InvalidID because no UVs exist for this triangle + FIndex3i Triangle0 = GetTriangle(orig_t0); + FIndex3i BaseTriangle0(ParentVertices[Triangle0.A], ParentVertices[Triangle0.B], ParentVertices[Triangle0.C]); + int idx_base_a1 = BaseTriangle0.IndexOf(base_a); + int idx_base_b1 = BaseTriangle0.IndexOf(base_b); + int idx_base_c = IndexUtil::GetOtherTriIndex(idx_base_a1, idx_base_b1); + + // create new element at lerp position + int NewElemID = AppendElement((RealType)0, splitInfo.NewVertex); + SetElementFromLerp(NewElemID, Triangle0[idx_base_a1], Triangle0[idx_base_b1], (RealType)splitInfo.SplitT); + + // rewrite triangle 0 + ElementTriangles[3*orig_t0 + idx_base_b1] = NewElemID; + + // create new triangle 2 w/ correct winding order + FIndex3i NewTriangle2(NewElemID, Triangle0[idx_base_b1], Triangle0[idx_base_c]); // mirrors DMesh3::SplitEdge [f,b,c] + InternalSetTriangle(splitInfo.NewTriangles.A, NewTriangle2, false); + + // update ref counts + ElementsRefCounts.Increment(NewElemID, 2); + ElementsRefCounts.Increment(Triangle0[idx_base_c]); + + if (orig_t1 == FDynamicMesh3::InvalidID) + { + return; // we are done if this is a boundary triangle + } + + // look up current triangle1 and infer base triangle 1 + // @todo handle case where these are InvalidID because no UVs exist for this triangle + FIndex3i Triangle1 = GetTriangle(orig_t1); + FIndex3i BaseTriangle1(ParentVertices[Triangle1.A], ParentVertices[Triangle1.B], ParentVertices[Triangle1.C]); + int idx_base_a2 = BaseTriangle1.IndexOf(base_a); + int idx_base_b2 = BaseTriangle1.IndexOf(base_b); + int idx_base_d = IndexUtil::GetOtherTriIndex(idx_base_a2, idx_base_b2); + + int OtherNewElemID = NewElemID; + + // if we don't have a shared edge, we need to create another new UV for the other side + bool bHasSharedUVEdge = IndexUtil::SamePairUnordered(Triangle0[idx_base_a1], Triangle0[idx_base_b1], Triangle1[idx_base_a2], Triangle1[idx_base_b2]); + if (bHasSharedUVEdge == false) + { + // create new element at lerp position + OtherNewElemID = AppendElement((RealType)0, splitInfo.NewVertex); + SetElementFromLerp(OtherNewElemID, Triangle1[idx_base_a2], Triangle1[idx_base_b2], (RealType)splitInfo.SplitT); + } + + // rewrite triangle 1 + ElementTriangles[3*orig_t1 + idx_base_b2] = OtherNewElemID; + + // create new triangle 3 w/ correct winding order + FIndex3i NewTriangle3(OtherNewElemID, Triangle1[idx_base_d], Triangle1[idx_base_b2]); // mirrors DMesh3::SplitEdge [f,d,b] + InternalSetTriangle(splitInfo.NewTriangles.B, NewTriangle3, false); + + // update ref counts + ElementsRefCounts.Increment(OtherNewElemID, 2); + ElementsRefCounts.Increment(Triangle1[idx_base_d]); +} + + + +//#pragma optimize("", off) +template +void TDynamicMeshOverlay::OnFlipEdge(const FDynamicMesh3::FEdgeFlipInfo& FlipInfo) +{ + int orig_t0 = FlipInfo.Triangles.A; + int orig_t1 = FlipInfo.Triangles.B; + int base_a = FlipInfo.OriginalVerts.A; + int base_b = FlipInfo.OriginalVerts.B; + int base_c = FlipInfo.OpposingVerts.A; + int base_d = FlipInfo.OpposingVerts.B; + + // look up triangle 0 + FIndex3i Triangle0 = GetTriangle(orig_t0); + FIndex3i BaseTriangle0(ParentVertices[Triangle0.A], ParentVertices[Triangle0.B], ParentVertices[Triangle0.C]); + int idx_base_a1 = BaseTriangle0.IndexOf(base_a); + int idx_base_b1 = BaseTriangle0.IndexOf(base_b); + int idx_base_c = IndexUtil::GetOtherTriIndex(idx_base_a1, idx_base_b1); + + // look up triangle 1 (must exist because base mesh would never flip a boundary edge) + FIndex3i Triangle1 = GetTriangle(orig_t1); + FIndex3i BaseTriangle1(ParentVertices[Triangle1.A], ParentVertices[Triangle1.B], ParentVertices[Triangle1.C]); + int idx_base_a2 = BaseTriangle1.IndexOf(base_a); + int idx_base_b2 = BaseTriangle1.IndexOf(base_b); + int idx_base_d = IndexUtil::GetOtherTriIndex(idx_base_a2, idx_base_b2); + + // sanity checks + check(idx_base_c == BaseTriangle0.IndexOf(base_c)); + check(idx_base_d == BaseTriangle1.IndexOf(base_d)); + + // we should not have been called on a non-shared edge!! + bool bHasSharedUVEdge = IndexUtil::SamePairUnordered(Triangle0[idx_base_a1], Triangle0[idx_base_b1], Triangle1[idx_base_a2], Triangle1[idx_base_b2]); + check(bHasSharedUVEdge); + + int A = Triangle0[idx_base_a1]; + int B = Triangle0[idx_base_b1]; + int C = Triangle0[idx_base_c]; + int D = Triangle1[idx_base_d]; + + // set triangles to same index order as in FDynamicMesh::FlipEdge + int i0 = 3 * orig_t0; + ElementTriangles[i0] = C; ElementTriangles[i0+1] = D; ElementTriangles[i0+2] = B; + int i1 = 3 * orig_t1; + ElementTriangles[i1] = D; ElementTriangles[i1+1] = C; ElementTriangles[i1+2] = A; + + // update reference counts + ElementsRefCounts.Decrement(A); + ElementsRefCounts.Decrement(B); + ElementsRefCounts.Increment(C); + ElementsRefCounts.Increment(D); +} + + + + + +//#pragma optimize("", off) +template +void TDynamicMeshOverlay::OnCollapseEdge(const FDynamicMesh3::FEdgeCollapseInfo& collapseInfo) +{ + int vid_base_kept = collapseInfo.KeptVertex; + int vid_base_removed = collapseInfo.RemovedVertex; + int tid_removed0 = collapseInfo.RemovedTris.A; + int tid_removed1 = collapseInfo.RemovedTris.B; + + // look up triangle 0 + FIndex3i Triangle0 = GetTriangle(tid_removed0); + FIndex3i BaseTriangle0(ParentVertices[Triangle0.A], ParentVertices[Triangle0.B], ParentVertices[Triangle0.C]); + int idx_removed0_a = BaseTriangle0.IndexOf(vid_base_kept); + int idx_removed0_b = BaseTriangle0.IndexOf(vid_base_removed); + + // look up triangle 1 if this is not a boundary edge + FIndex3i Triangle1, BaseTriangle1; + if (collapseInfo.bIsBoundary == false) + { + Triangle1 = GetTriangle(tid_removed1); + BaseTriangle1 = FIndex3i(ParentVertices[Triangle1.A], ParentVertices[Triangle1.B], ParentVertices[Triangle1.C]); + + int idx_removed1_a = BaseTriangle1.IndexOf(vid_base_kept); + int idx_removed1_b = BaseTriangle1.IndexOf(vid_base_removed); + + // if this is an internal edge it cannot be a seam or we cannot collapse + bool bHasSharedUVEdge = IndexUtil::SamePairUnordered(Triangle0[idx_removed0_a], Triangle0[idx_removed0_b], Triangle1[idx_removed1_a], Triangle1[idx_removed1_b]); + check(bHasSharedUVEdge); + } + + // need to find the elementid for the "kept" vertex. Since this isn't a seam, + // there is just one, and we can grab it from "old" removed triangle + int kept_elemid = FDynamicMesh3::InvalidID; + for (int j = 0; j < 3; ++j) + { + if (BaseTriangle0[j] == vid_base_kept) + { + kept_elemid = Triangle0[j]; + } + } + check(kept_elemid != FDynamicMesh3::InvalidID); + + // look for still-existing triangles that have elements linked to the removed vertex. + // in that case, replace with the element we found + TArray removed_elements; + for (int onering_tid : ParentMesh->VtxTrianglesItr(vid_base_kept)) + { + FIndex3i elem_tri = GetTriangle(onering_tid); + for (int j = 0; j < 3; ++j) + { + int elem_id = elem_tri[j]; + if (ParentVertices[elem_id] == vid_base_removed) + { + ElementsRefCounts.Decrement(elem_id); + removed_elements.AddUnique(elem_id); + ElementTriangles[3*onering_tid + j] = kept_elemid; + ElementsRefCounts.Increment(kept_elemid); + } + } + } + check(removed_elements.Num() == 1); // should always be true for non-seam edges... + + // update position of kept element + SetElementFromLerp(kept_elemid, kept_elemid, removed_elements[0], collapseInfo.CollapseT); + + // clear the two triangles we removed + for (int j = 0; j < 3; ++j) + { + ElementsRefCounts.Decrement(Triangle0[j]); + ElementTriangles[3*tid_removed0 + j] = FDynamicMesh3::InvalidID; + if (collapseInfo.bIsBoundary == false) + { + ElementsRefCounts.Decrement(Triangle1[j]); + ElementTriangles[3*tid_removed1 + j] = FDynamicMesh3::InvalidID; + } + } + + // remove the elements associated with the removed vertex + for (int k = 0; k < removed_elements.Num(); ++k) + { + check(ElementsRefCounts.GetRefCount(removed_elements[k]) == 1); + ElementsRefCounts.Decrement(removed_elements[k]); + ParentVertices[removed_elements[k]] = FDynamicMesh3::InvalidID; + } + +} + + + +template +void TDynamicMeshOverlay::OnPokeTriangle(const FDynamicMesh3::FPokeTriangleInfo& PokeInfo) +{ + FIndex3i Triangle = GetTriangle(PokeInfo.OriginalTriangle); + + // create new element at barycentric position + int CenterElemID = AppendElement((RealType)0, PokeInfo.NewVertex); + FVector3 BaryCoords((RealType)PokeInfo.BaryCoords.X, (RealType)PokeInfo.BaryCoords.Y, (RealType)PokeInfo.BaryCoords.Z); + SetElementFromBary(CenterElemID, Triangle[0], Triangle[1], Triangle[2], BaryCoords); + + // update orig triangle and two new ones. Winding orders here mirror FDynamicMesh3::PokeTriangle + SetTriangle(PokeInfo.OriginalTriangle, FIndex3i(Triangle[0], Triangle[1], CenterElemID) ); + SetTriangle(PokeInfo.NewTriangles.A, FIndex3i(Triangle[1], Triangle[2], CenterElemID)); + SetTriangle(PokeInfo.NewTriangles.B, FIndex3i(Triangle[2], Triangle[0], CenterElemID)); + + ElementsRefCounts.Increment(Triangle[0]); + ElementsRefCounts.Increment(Triangle[1]); + ElementsRefCounts.Increment(Triangle[2]); + ElementsRefCounts.Increment(CenterElemID, 3); +} + + +template +void TDynamicMeshOverlay::OnMergeEdges(const FDynamicMesh3::FMergeEdgesInfo& MergeInfo) +{ + // MergeEdges just merges vertices. For now we will not also merge UVs. So all we need to + // do is rewrite the UV parent vertices + + FIndex3i ModifiedEdges(MergeInfo.KeptEdge, MergeInfo.ExtraKeptEdges.A, MergeInfo.ExtraKeptEdges.B); + for (int EdgeIdx = 0; EdgeIdx < 3; ++EdgeIdx) + { + if (ParentMesh->IsEdge(ModifiedEdges[EdgeIdx]) == false) + { + continue; + } + + FIndex2i EdgeTris = ParentMesh->GetEdgeT(ModifiedEdges[EdgeIdx]); + for (int j = 0; j < 2; ++j) + { + FIndex3i ElemTriangle = GetTriangle(EdgeTris[j]); + for (int k = 0; k < 3; ++k) + { + int ParentVID = ParentVertices[ElemTriangle[k]]; + if (ParentVID == MergeInfo.RemovedVerts.A) + { + ParentVertices[ElemTriangle[k]] = MergeInfo.KeptVerts.A; + } + else if (ParentVID == MergeInfo.RemovedVerts.B) + { + ParentVertices[ElemTriangle[k]] = MergeInfo.KeptVerts.B; + } + } + } + } +} + + + + +template +void TDynamicMeshOverlay::SetElementFromLerp(int SetElement, int ElementA, int ElementB, RealType Alpha) +{ + int IndexSet = ElementSize * SetElement; + int IndexA = ElementSize * ElementA; + int IndexB = ElementSize * ElementB; + RealType Beta = ((RealType)1 - Alpha); + for (int i = 0; i < ElementSize; ++i) + { + Elements[IndexSet+i] = Beta*Elements[IndexA+i] + Alpha*Elements[IndexB+i]; + } +} + +template +void TDynamicMeshOverlay::SetElementFromBary(int SetElement, int ElementA, int ElementB, int ElementC, const FVector3& BaryCoords) +{ + int IndexSet = ElementSize * SetElement; + int IndexA = ElementSize * ElementA; + int IndexB = ElementSize * ElementB; + int IndexC = ElementSize * ElementC; + for (int i = 0; i < ElementSize; ++i) + { + Elements[IndexSet + i] = + BaryCoords.X*Elements[IndexA+i] + BaryCoords.Y*Elements[IndexB+i] + BaryCoords.Z*Elements[IndexC+i]; + } +} + + +//#pragma optimize("", off) +template +bool TDynamicMeshOverlay::CheckValidity(bool bAllowNonManifoldVertices, EValidityCheckFailMode FailMode) const +{ + bool is_ok = true; + TFunction CheckOrFailF = [&](bool b) + { + is_ok = is_ok && b; + }; + if (FailMode == EValidityCheckFailMode::Check) + { + CheckOrFailF = [&](bool b) + { + checkf(b, TEXT("TDynamicMeshOverlay::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + else if (FailMode == EValidityCheckFailMode::Ensure) + { + CheckOrFailF = [&](bool b) + { + ensureMsgf(b, TEXT("TDynamicMeshOverlay::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + + + // @todo: check that all connected element-pairs are also edges in parentmesh + + // check that parent vtx of each element is actually a vertex + for (int elemid : ElementIndicesItr()) + { + int ParentVID = GetParentVertex(elemid); + CheckOrFailF(ParentMesh->IsVertex(ParentVID)); + } + + // check that parent vertices of each element triangle are the same as base triangle + for (int tid : ParentMesh->TriangleIndicesItr()) + { + FIndex3i ElemTri = GetTriangle(tid); + FIndex3i BaseTri = ParentMesh->GetTriangle(tid); + for (int j = 0; j < 3; ++j) + { + if (ElemTri[j] != FDynamicMesh3::InvalidID) + { + CheckOrFailF(GetParentVertex(ElemTri[j]) == BaseTri[j]); + } + } + } + + // count references to each element + TArray RealRefCounts; RealRefCounts.Init(0, MaxElementID()); + for (int tid : ParentMesh->TriangleIndicesItr()) + { + FIndex3i Tri = GetTriangle(tid); + for (int j = 0; j < 3; ++j) + { + if (Tri[j] != FDynamicMesh3::InvalidID) + { + RealRefCounts[Tri[j]] += 1; + } + } + } + // verify that refcount list counts are same as actual reference counts + for (int elemid : ElementsRefCounts.Indices()) + { + int CurRefCount = ElementsRefCounts.GetRefCount(elemid); + CheckOrFailF(CurRefCount == RealRefCounts[elemid] + 1); + } + + return is_ok; +} + + + + + +// These are explicit instantiations of the templates that are exported from the shared lib. +// Only these instantiations of the template can be used. +// This is necessary because we have placed most of the templated functions in this .cpp file, instead of the header. +template class DYNAMICMESH_API TDynamicMeshOverlay; +template class DYNAMICMESH_API TDynamicMeshOverlay; +template class DYNAMICMESH_API TDynamicMeshOverlay; +template class DYNAMICMESH_API TDynamicMeshOverlay; +template class DYNAMICMESH_API TDynamicMeshOverlay; +template class DYNAMICMESH_API TDynamicMeshOverlay; +template class DYNAMICMESH_API TDynamicMeshOverlay; +template class DYNAMICMESH_API TDynamicMeshOverlay; +template class DYNAMICMESH_API TDynamicMeshOverlay; + +template class DYNAMICMESH_API TDynamicMeshVectorOverlay; +template class DYNAMICMESH_API TDynamicMeshVectorOverlay; +template class DYNAMICMESH_API TDynamicMeshVectorOverlay; +template class DYNAMICMESH_API TDynamicMeshVectorOverlay; +template class DYNAMICMESH_API TDynamicMeshVectorOverlay; +template class DYNAMICMESH_API TDynamicMeshVectorOverlay; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/EdgeLoop.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/EdgeLoop.cpp new file mode 100644 index 000000000000..bf107effc261 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/EdgeLoop.cpp @@ -0,0 +1,251 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "EdgeLoop.h" + + +void FEdgeLoop::Initialize(const FDynamicMesh3* mesh, const TArray& vertices, const TArray & edges, const TArray* BowtieVerticesIn) +{ + Mesh = mesh; + Vertices = vertices; + Edges = edges; + if (BowtieVerticesIn != nullptr) + { + BowtieVertices = *BowtieVerticesIn; + bBowtiesCalculated = true; + } +} + + +void FEdgeLoop::InitializeFromEdges(const TArray& EdgesIn) +{ + check(Mesh != nullptr); + Edges = EdgesIn; + + int NumEdges = Edges.Num(); + Vertices.SetNum(NumEdges); + + FIndex2i start_ev = Mesh->GetEdgeV(Edges[0]); + FIndex2i prev_ev = start_ev; + for (int i = 1; i < NumEdges; ++i) + { + FIndex2i next_ev = Mesh->GetEdgeV(Edges[i % NumEdges]); + Vertices[i] = IndexUtil::FindSharedEdgeVertex(prev_ev, next_ev); + prev_ev = next_ev; + } + Vertices[0] = IndexUtil::FindEdgeOtherVertex(start_ev, Vertices[1]); +} + + +bool FEdgeLoop::InitializeFromVertices(const TArray& VerticesIn, bool bAutoOrient) +{ + check(Mesh != nullptr); + Vertices = VerticesIn; + + int NumVertices = Vertices.Num(); + Edges.SetNum(NumVertices); + for (int i = 0; i < NumVertices; ++i) + { + int a = Vertices[i], b = Vertices[(i + 1) % NumVertices]; + Edges[i] = Mesh->FindEdge(a, b); + if (Edges[i] == FDynamicMesh3::InvalidID) + { + checkf(false, TEXT("EdgeLoop.FromVertices: invalid edge [%d,%d]"), a, b); + return false; + } + } + + if (bAutoOrient) + { + SetCorrectOrientation(); + } + + return true; +} + + +void FEdgeLoop::CalculateBowtieVertices() +{ + BowtieVertices.Reset(); + int NumVertices = Vertices.Num(); + for (int i = 0; i < NumVertices; ++i) + { + if (Mesh->IsBowtieVertex(Vertices[i])) + { + BowtieVertices.Add(Vertices[i]); + } + } + bBowtiesCalculated = true; +} + + +FAxisAlignedBox3d FEdgeLoop::GetBounds() const +{ + FAxisAlignedBox3d box = FAxisAlignedBox3d::Empty(); + for (int i = 0; i < Vertices.Num(); ++i) + { + box.Contain(Mesh->GetVertex(Vertices[i])); + } + return box; +} + + +bool FEdgeLoop::SetCorrectOrientation() +{ + int NumEdges = Edges.Num(); + for (int i = 0; i < NumEdges; ++i) + { + int eid = Edges[i]; + if (Mesh->IsBoundaryEdge(eid)) + { + int a = Vertices[i], b = Vertices[(i + 1) % NumEdges]; + FIndex2i ev = Mesh->GetOrientedBoundaryEdgeV(eid); + if (ev.A == b && ev.B == a) + { + Reverse(); + return true; + } + else + { + return false; + } + } + } + return false; +} + + +bool FEdgeLoop::IsInternalLoop() const +{ + int NumEdges = Edges.Num(); + for (int i = 0; i < NumEdges; ++i) + { + if (Mesh->IsBoundaryEdge(Edges[i])) + { + return false; + } + } + return true; +} + + +bool FEdgeLoop::IsBoundaryLoop(const FDynamicMesh3* TestMesh) const +{ + const FDynamicMesh3* UseMesh = (TestMesh != nullptr) ? TestMesh : Mesh; + + int NumEdges = Edges.Num(); + for (int i = 0; i < NumEdges; ++i) + { + if (UseMesh->IsBoundaryEdge(Edges[i]) == false) + { + return false; + } + } + return true; +} + + +int FEdgeLoop::FindVertexIndex(int VertexID) const +{ + int N = Vertices.Num(); + for (int i = 0; i < N; ++i) + { + if (Vertices[i] == VertexID) + { + return i; + } + } + return -1; +} + + +int FEdgeLoop::FindNearestVertexIndex(const FVector3d& QueryPoint) const +{ + int iNear = -1; + double fNearSqr = TNumericLimits::Max(); + int N = Vertices.Num(); + for (int i = 0; i < N; ++i) + { + FVector3d lv = Mesh->GetVertex(Vertices[i]); + double d2 = QueryPoint.DistanceSquared(lv); + if (d2 < fNearSqr) + { + fNearSqr = d2; + iNear = i; + } + } + return iNear; +} + + + + +bool FEdgeLoop::CheckValidity(EValidityCheckFailMode FailMode) const +{ + bool is_ok = true; + TFunction CheckOrFailF = [&](bool b) + { + is_ok = is_ok && b; + }; + if (FailMode == EValidityCheckFailMode::Check) + { + CheckOrFailF = [&](bool b) + { + checkf(b, TEXT("FEdgeLoop::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + else if (FailMode == EValidityCheckFailMode::Ensure) + { + CheckOrFailF = [&](bool b) + { + ensureMsgf(b, TEXT("FEdgeLoop::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + + + CheckOrFailF(Vertices.Num() == Edges.Num()); + for (int ei = 0; ei < Edges.Num(); ++ei) + { + FIndex2i ev = Mesh->GetEdgeV(Edges[ei]); + CheckOrFailF(Mesh->IsVertex(ev.A)); + CheckOrFailF(Mesh->IsVertex(ev.B)); + CheckOrFailF(Mesh->FindEdge(ev.A, ev.B) != FDynamicMesh3::InvalidID); + CheckOrFailF(Vertices[ei] == ev.A || Vertices[ei] == ev.B); + CheckOrFailF(Vertices[(ei + 1) % Edges.Num()] == ev.A || Vertices[(ei + 1) % Edges.Num()] == ev.B); + } + for (int vi = 0; vi < Vertices.Num(); ++vi) + { + int a = Vertices[vi], b = Vertices[(vi + 1) % Vertices.Num()]; + CheckOrFailF(Mesh->IsVertex(a)); + CheckOrFailF(Mesh->IsVertex(b)); + CheckOrFailF(Mesh->FindEdge(a, b) != FDynamicMesh3::InvalidID); + int n = 0, edge_before_b = Edges[vi], edge_after_b = Edges[(vi + 1) % Vertices.Num()]; + for (int nbr_e : Mesh->VtxEdgesItr(b)) + { + if (nbr_e == edge_before_b || nbr_e == edge_after_b) + { + n++; + } + } + CheckOrFailF(n == 2); + } + return is_ok; +} + + + +void FEdgeLoop::VertexLoopToEdgeLoop(const FDynamicMesh3* Mesh, const TArray& VertexLoop, TArray& OutEdgeLoop) +{ + // @todo this function should be in a utility class? + + int NV = VertexLoop.Num(); + OutEdgeLoop.SetNum(NV); + for (int i = 0; i < NV; ++i) + { + int v0 = VertexLoop[i]; + int v1 = VertexLoop[(i + 1) % NV]; + OutEdgeLoop[i] = Mesh->FindEdge(v0, v1); + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/EdgeSpan.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/EdgeSpan.cpp new file mode 100644 index 000000000000..dfff9c575320 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/EdgeSpan.cpp @@ -0,0 +1,279 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "EdgeSpan.h" + +void FEdgeSpan::Initialize(const FDynamicMesh3* mesh, const TArray& vertices, const TArray & edges, const TArray* BowtieVerticesIn) +{ + Mesh = mesh; + Vertices = vertices; + Edges = edges; + if (BowtieVerticesIn != nullptr) + { + BowtieVertices = *BowtieVerticesIn; + bBowtiesCalculated = true; + } +} + + + +void FEdgeSpan::InitializeFromEdges(const TArray& EdgesIn) +{ + check(Mesh != nullptr); + Edges = EdgesIn; + + int NumEdges = Edges.Num(); + Vertices.SetNum(NumEdges + 1); + + FIndex2i start_ev = Mesh->GetEdgeV(Edges[0]); + FIndex2i prev_ev = start_ev; + if (NumEdges > 1) + { + for (int i = 1; i < Edges.Num(); ++i) + { + FIndex2i next_ev = Mesh->GetEdgeV(Edges[i]); + Vertices[i] = IndexUtil::FindSharedEdgeVertex(prev_ev, next_ev); + prev_ev = next_ev; + } + Vertices[0] = IndexUtil::FindEdgeOtherVertex(start_ev, Vertices[1]); + Vertices[Vertices.Num() - 1] = IndexUtil::FindEdgeOtherVertex(prev_ev, Vertices[Vertices.Num() - 2]); + } + else + { + Vertices[0] = start_ev[0]; + Vertices[1] = start_ev[1]; + } +} + + + + +bool FEdgeSpan::InitializeFromVertices(const TArray& VerticesIn, bool bAutoOrient) +{ + check(Mesh != nullptr); + Vertices = VerticesIn; + + int NumVertices = Vertices.Num(); + Edges.SetNum(NumVertices - 1); + for (int i = 0; i < NumVertices - 1; ++i) + { + int a = Vertices[i], b = Vertices[i + 1]; + Edges[i] = Mesh->FindEdge(a, b); + if (Edges[i] == FDynamicMesh3::InvalidID) + { + checkf(false, TEXT("EdgeSpan.FromVertices: invalid edge [%d,%d]"), a, b); + return false; + } + } + + if (bAutoOrient) + { + SetCorrectOrientation(); + } + + return true; +} + + +void FEdgeSpan::CalculateBowtieVertices() +{ + BowtieVertices.Reset(); + int NumVertices = Vertices.Num(); + for (int i = 0; i < NumVertices; ++i) + { + if (Mesh->IsBowtieVertex(Vertices[i])) + { + BowtieVertices.Add(Vertices[i]); + } + } + bBowtiesCalculated = true; +} + + + +FAxisAlignedBox3d FEdgeSpan::GetBounds() const +{ + FAxisAlignedBox3d box = FAxisAlignedBox3d::Empty(); + for (int i = 0; i < Vertices.Num(); ++i) + { + box.Contain(Mesh->GetVertex(Vertices[i])); + } + return box; +} + + + +void FEdgeSpan::GetPolyline(FPolyline3d& PolylineOut) const +{ + PolylineOut.Clear(); + for (int i = 0; i < Vertices.Num(); ++i) + { + PolylineOut.AppendVertex(Mesh->GetVertex(Vertices[i])); + } +} + + +bool FEdgeSpan::SetCorrectOrientation() +{ + int NumEdges = Edges.Num(); + for (int i = 0; i < NumEdges; ++i) + { + int eid = Edges[i]; + if (Mesh->IsBoundaryEdge(eid)) + { + int a = Vertices[i], b = Vertices[i + 1]; + FIndex2i ev = Mesh->GetOrientedBoundaryEdgeV(eid); + if (ev.A == b && ev.B == a) + { + Reverse(); + return true; + } + else + { + return false; + } + } + } + return false; +} + + + +bool FEdgeSpan::IsInternalspan() const +{ + int NumEdges = Edges.Num(); + for (int i = 0; i < NumEdges; ++i) + { + if (Mesh->IsBoundaryEdge(Edges[i])) + { + return false; + } + } + return true; +} + + +bool FEdgeSpan::IsBoundaryspan(const FDynamicMesh3* TestMesh) const +{ + const FDynamicMesh3* UseMesh = (TestMesh != nullptr) ? TestMesh : Mesh; + + int NumEdges = Edges.Num(); + for (int i = 0; i < NumEdges; ++i) + { + if (UseMesh->IsBoundaryEdge(Edges[i]) == false) + { + return false; + } + } + return true; +} + + + +int FEdgeSpan::FindVertexIndex(int VertexID) const +{ + int N = Vertices.Num(); + for (int i = 0; i < N; ++i) + { + if (Vertices[i] == VertexID) + { + return i; + } + } + return -1; +} + + + +int FEdgeSpan::FindNearestVertexIndex(const FVector3d& QueryPoint) const +{ + int iNear = -1; + double fNearSqr = TNumericLimits::Max(); + int N = Vertices.Num(); + for (int i = 0; i < N; ++i) + { + FVector3d lv = Mesh->GetVertex(Vertices[i]); + double d2 = QueryPoint.DistanceSquared(lv); + if (d2 < fNearSqr) + { + fNearSqr = d2; + iNear = i; + } + } + return iNear; +} + + + +bool FEdgeSpan::CheckValidity(EValidityCheckFailMode FailMode) const +{ + bool is_ok = true; + TFunction CheckOrFailF = [&](bool b) + { + is_ok = is_ok && b; + }; + if (FailMode == EValidityCheckFailMode::Check) + { + CheckOrFailF = [&](bool b) + { + checkf(b, TEXT("FEdgeSpan::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + else if (FailMode == EValidityCheckFailMode::Ensure) + { + CheckOrFailF = [&](bool b) + { + ensureMsgf(b, TEXT("FEdgeSpan::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + + + CheckOrFailF(Vertices.Num() == (Edges.Num() + 1)); + for (int ei = 0; ei < Edges.Num(); ++ei) + { + FIndex2i ev = Mesh->GetEdgeV(Edges[ei]); + CheckOrFailF(Mesh->IsVertex(ev.A)); + CheckOrFailF(Mesh->IsVertex(ev.B)); + CheckOrFailF(Mesh->FindEdge(ev.A, ev.B) != FDynamicMesh3::InvalidID); + CheckOrFailF(Vertices[ei] == ev.A || Vertices[ei] == ev.B); + CheckOrFailF(Vertices[ei + 1] == ev.A || Vertices[ei + 1] == ev.B); + } + for (int vi = 0; vi < Vertices.Num() - 1; ++vi) + { + int a = Vertices[vi], b = Vertices[vi + 1]; + CheckOrFailF(Mesh->IsVertex(a)); + CheckOrFailF(Mesh->IsVertex(b)); + CheckOrFailF(Mesh->FindEdge(a, b) != FDynamicMesh3::InvalidID); + + // @todo rewrite this test for span, has to handle endpoint vertices that only have one nbr + if (vi < Vertices.Num() - 2) { + int n = 0, edge_before_b = Edges[vi], edge_after_b = Edges[(vi + 1) % Vertices.Num()]; + for (int nbr_e : Mesh->VtxEdgesItr(b)) + { + if (nbr_e == edge_before_b || nbr_e == edge_after_b) + { + n++; + } + } + CheckOrFailF(n == 2); + } + } + return is_ok; +} + + + +void FEdgeSpan::VertexSpanToEdgeSpan(const FDynamicMesh3* Mesh, const TArray& VertexSpan, TArray& OutEdgeSpan) +{ + // @todo this function should be in a utility class? + + int NV = VertexSpan.Num(); + OutEdgeSpan.SetNum(NV - 1); + for (int i = 0; i < NV - 1; ++i) + { + int v0 = VertexSpan[i]; + int v1 = VertexSpan[i + 1]; + OutEdgeSpan[i] = Mesh->FindEdge(v0, v1); + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Generators/PlanarPolygonMeshGenerator.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Generators/PlanarPolygonMeshGenerator.cpp new file mode 100644 index 000000000000..7ecd28c5b35e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Generators/PlanarPolygonMeshGenerator.cpp @@ -0,0 +1,73 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Generators/PlanarPolygonMeshGenerator.h" +#include "CompGeom/PolygonTriangulation.h" + +FPlanarPolygonMeshGenerator::FPlanarPolygonMeshGenerator() +{ + Normal = FVector3f::UnitZ(); + IndicesMap = FIndex2i(0, 1); +} + + + +void FPlanarPolygonMeshGenerator::SetPolygon(const TArray& PolygonVerts) +{ + Polygon = FPolygon2d(); + int NumVerts = PolygonVerts.Num(); + for (int i = 0; i < NumVerts; ++i) + { + Polygon.AppendVertex(PolygonVerts[i]); + } +} + + + + +FMeshShapeGenerator& FPlanarPolygonMeshGenerator::Generate() +{ + int NumVertices = Polygon.VertexCount(); + check(NumVertices >= 3); + if (NumVertices < 3) + { + return *this; + } + + TArray TriangleList; + PolygonTriangulation::TriangulateSimplePolygon(Polygon.GetVertices(), TriangleList); + + int NumTriangles = TriangleList.Num(); + SetBufferSizes(NumVertices, NumTriangles, NumVertices, NumVertices); + + FAxisAlignedBox2d BoundingBox = Polygon.Bounds(); + double Width = BoundingBox.Width(), Height = BoundingBox.Height(); + double UVScale = FMath::Max(Width, Height); + + for (int VertIdx = 0; VertIdx < NumVertices; ++VertIdx) + { + FVector2d Pos = Polygon[VertIdx]; + + UVs[VertIdx] = FVector2f( + (float)((Pos.X - BoundingBox.Min.X) / UVScale), + (float)((Pos.Y - BoundingBox.Min.Y) / UVScale)); + UVParentVertex[VertIdx] = VertIdx; + + Normals[VertIdx] = Normal; + NormalParentVertex[VertIdx] = VertIdx; + + Vertices[VertIdx] = MakeVertex(Pos.X, Pos.Y); + } + + + for (int TriIdx = 0; TriIdx < NumTriangles; TriIdx++) + { + FIndex3i Tri = TriangleList[TriIdx]; + SetTriangle(TriIdx, Tri); + SetTriangleUVs(TriIdx, Tri); + SetTriangleNormals(TriIdx, Tri); + SetTrianglePolygon(TriIdx, 0); + } + + + return *this; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Generators/RectangleMeshGenerator.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Generators/RectangleMeshGenerator.cpp new file mode 100644 index 000000000000..4ed4d4345227 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Generators/RectangleMeshGenerator.cpp @@ -0,0 +1,104 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Generators/RectangleMeshGenerator.h" + + +FRectangleMeshGenerator::FRectangleMeshGenerator() +{ + Origin = FVector3d::Zero(); + Width = 10.0f; + Height = 10.0f; + WidthVertexCount = HeightVertexCount = 8; + Normal = FVector3f::UnitZ(); + IndicesMap = FIndex2i(0, 1); + bScaleUVByAspectRatio = true; +} + + + +FMeshShapeGenerator& FRectangleMeshGenerator::Generate() +{ + check(IndicesMap.A >= 0 && IndicesMap.A <= 2); + check(IndicesMap.B >= 0 && IndicesMap.B <= 2); + + int WidthNV = (WidthVertexCount > 1) ? WidthVertexCount : 2; + int HeightNV = (HeightVertexCount > 1) ? HeightVertexCount : 2; + + int TotalNumVertices = WidthNV * HeightNV; + int TotalNumTriangles = 2 * (WidthNV - 1) * (HeightNV - 1); + SetBufferSizes(TotalNumVertices, TotalNumTriangles, TotalNumVertices, TotalNumVertices); + + // corner vertices + FVector3d v00 = MakeVertex(-Width / 2.0f, -Height / 2.0f); + FVector3d v01 = MakeVertex(Width / 2.0f, -Height / 2.0f); + FVector3d v11 = MakeVertex(Width / 2.0f, Height / 2.0f); + FVector3d v10 = MakeVertex(-Width / 2.0f, Height / 2.0f); + + // corner UVs + float uvleft = 0.0f, uvright = 1.0f, uvbottom = 0.0f, uvtop = 1.0f; + if (bScaleUVByAspectRatio && Width != Height) + { + if (Width > Height) + { + uvtop = Height / Width; + } + else + { + uvright = Width / Height; + } + } + + FVector2f uv00 = FVector2f(uvleft, uvbottom); + FVector2f uv01 = FVector2f(uvright, uvbottom); + FVector2f uv11 = FVector2f(uvright, uvtop); + FVector2f uv10 = FVector2f(uvleft, uvtop); + + + int vi = 0; + int ti = 0; + + // add vertex rows + int start_vi = vi; + for (int yi = 0; yi < HeightNV; ++yi) + { + double ty = (double)yi / (double)(HeightNV - 1); + for (int xi = 0; xi < WidthNV; ++xi) + { + double tx = (double)xi / (double)(WidthNV - 1); + Normals[vi] = Normal; + NormalParentVertex[vi] = vi; + UVs[vi] = BilinearInterp(uv00, uv01, uv11, uv10, (float)tx, (float)ty); + UVParentVertex[vi] = vi; + Vertices[vi++] = BilinearInterp(v00, v01, v11, v10, tx, ty); + } + } + + // add triangulated quads + int PolyIndex = 0; + for (int y0 = 0; y0 < HeightNV - 1; ++y0) + { + for (int x0 = 0; x0 < WidthNV - 1; ++x0) + { + int i00 = start_vi + y0 * WidthNV + x0; + int i10 = start_vi + (y0 + 1)*WidthNV + x0; + int i01 = i00 + 1, i11 = i10 + 1; + + SetTriangle(ti, i00, i11, i01); + SetTrianglePolygon(ti, PolyIndex); + SetTriangleUVs(ti, i00, i11, i01); + SetTriangleNormals(ti, i00, i11, i01); + + ti++; + + SetTriangle(ti, i00, i10, i11); + SetTrianglePolygon(ti, PolyIndex); + SetTriangleUVs(ti, i00, i10, i11); + SetTriangleNormals(ti, i00, i10, i11); + + ti++; + PolyIndex++; + } + } + + return *this; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/GroupTopology.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/GroupTopology.cpp new file mode 100644 index 000000000000..ea64df81cb64 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/GroupTopology.cpp @@ -0,0 +1,483 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "GroupTopology.h" +#include "MeshRegionBoundaryLoops.h" + + + + + +bool FGroupTopology::FGroupEdge::IsConnectedToVertices(const TSet& Vertices) const +{ + for (int VertexID : Span.Vertices) + { + if (Vertices.Contains(VertexID)) + { + return true; + } + } + return false; +} + + + +FGroupTopology::FGroupTopology(const FDynamicMesh3* MeshIn, bool bAutoBuild) +{ + this->Mesh = MeshIn; + if (bAutoBuild) + { + RebuildTopology(); + } +} + + +void FGroupTopology::RebuildTopology() +{ + Groups.Reset(); + Edges.Reset(); + Corners.Reset(); + + // initialize groups map first to avoid resizes + GroupIDToGroupIndexMap.Reset(); + GroupIDToGroupIndexMap.Init(-1, Mesh->MaxGroupID()); + TArray GroupFaceCounts; + GroupFaceCounts.Init(0, Mesh->MaxGroupID()); + for (int tid : Mesh->TriangleIndicesItr()) + { + int GroupID = GetGroupID(tid); + if (GroupIDToGroupIndexMap[GroupID] == -1) + { + FGroup NewGroup; + NewGroup.GroupID = GroupID; + GroupIDToGroupIndexMap[GroupID] = Groups.Add(NewGroup); + } + GroupFaceCounts[GroupID]++; + } + for (FGroup& Group : Groups) + { + Group.Faces.Reserve(GroupFaceCounts[Group.GroupID]); + } + + + // sort faces into groups + for (int tid : Mesh->TriangleIndicesItr()) + { + int GroupID = GetGroupID(tid); + Groups[GroupIDToGroupIndexMap[GroupID]].Faces.Add(tid); + } + + // precompute junction vertices set + CornerVerticesFlags.Init(false, Mesh->MaxVertexID()); + for (int vid : Mesh->VertexIndicesItr()) + { + if (IsCornerVertex(vid)) + { + CornerVerticesFlags[vid] = true; + FCorner Corner = { vid }; + Corners.Add(Corner); + } + } + for (FCorner& Corner : Corners) + { + Mesh->GetAllVertexGroups(Corner.VertexID, Corner.NeighbourGroupIDs); + } + + + // construct boundary loops + for (FGroup& Group : Groups) + { + // finds FGroupEdges and uses to populate Group.Boundaries + ExtractGroupEdges(Group); + + // collect up .NeighbourGroupIDs and set .bIsOnBoundary + for (FGroupBoundary& Boundary : Group.Boundaries) + { + Boundary.bIsOnBoundary = false; + for (int EdgeIndex : Boundary.GroupEdges) + { + FGroupEdge& Edge = Edges[EdgeIndex]; + + int OtherGroupID = (Edge.Groups.A == Group.GroupID) ? Edge.Groups.B : Edge.Groups.A; + if (OtherGroupID != FDynamicMesh3::InvalidID) + { + Boundary.NeighbourGroupIDs.AddUnique(OtherGroupID); + } + else + { + Boundary.bIsOnBoundary = true; + } + } + } + + // make all-neighbour-groups list at group level + for (FGroupBoundary& Boundary : Group.Boundaries) + { + for (int NbrGroupID : Boundary.NeighbourGroupIDs) + { + Group.NeighbourGroupIDs.AddUnique(NbrGroupID); + } + } + } + +} + + +bool FGroupTopology::IsCornerVertex(int VertexID) const +{ + FIndex3i UniqueGroups; + int UniqueCount = 0; + for (int tid : Mesh->VtxTrianglesItr(VertexID)) + { + int GroupID = GetGroupID(tid); + if (UniqueCount == 0) + { + UniqueGroups[0] = GroupID; + UniqueCount++; + } + else if (UniqueCount == 1 && GroupID != UniqueGroups[0]) + { + UniqueGroups[1] = GroupID; + UniqueCount++; + } + else if (UniqueCount == 2 && GroupID != UniqueGroups[0] && GroupID != UniqueGroups[1]) + { + return true; + } + } + return false; +} + + + + +int FGroupTopology::GetCornerVertexID(int CornerID) const +{ + check(CornerID >= 0 && CornerID < Corners.Num()); + return Corners[CornerID].VertexID; +} + + +const FGroupTopology::FGroup* FGroupTopology::FindGroupByID(int GroupID) const +{ + if (GroupID < 0 || GroupID >= GroupIDToGroupIndexMap.Num() || GroupIDToGroupIndexMap[GroupID] == -1) + { + return nullptr; + } + return &Groups[GroupIDToGroupIndexMap[GroupID]]; +} + + +const TArray& FGroupTopology::GetGroupFaces(int GroupID) const +{ + const FGroup* Found = FindGroupByID(GroupID); + ensure(Found != nullptr); + return (Found != nullptr) ? Found->Faces : EmptyArray; +} + +const TArray& FGroupTopology::GetGroupNbrGroups(int GroupID) const +{ + const FGroup* Found = FindGroupByID(GroupID); + ensure(Found != nullptr); + return (Found != nullptr) ? Found->NeighbourGroupIDs : EmptyArray; +} + + + +int FGroupTopology::FindGroupEdgeID(int MeshEdgeID) const +{ + int GroupID = GetGroupID(Mesh->GetEdgeT(MeshEdgeID).A); + const FGroup* Group = FindGroupByID(GroupID); + ensure(Group != nullptr); + if (Group != nullptr) + { + for (const FGroupBoundary& Boundary : Group->Boundaries) + { + for (int EdgeID : Boundary.GroupEdges) + { + const FGroupEdge& Edge = Edges[EdgeID]; + if (Edge.Span.Edges.Contains(MeshEdgeID)) + { + return EdgeID; + } + } + } + } + return -1; +} + + +const TArray& FGroupTopology::GetGroupEdgeVertices(int GroupEdgeID) const +{ + check(GroupEdgeID >= 0 && GroupEdgeID < Edges.Num()); + return Edges[GroupEdgeID].Span.Vertices; +} + + + +void FGroupTopology::FindEdgeNbrGroups(int GroupEdgeID, TArray& GroupsOut) const +{ + check(GroupEdgeID >= 0 && GroupEdgeID < Edges.Num()); + const TArray & Vertices = GetGroupEdgeVertices(GroupEdgeID); + FindVertexNbrGroups(Vertices[0], GroupsOut); + FindVertexNbrGroups(Vertices[Vertices.Num() - 1], GroupsOut); +} + +void FGroupTopology::FindEdgeNbrGroups(const TArray& GroupEdgeIDs, TArray& GroupsOut) const +{ + for (int GroupEdgeID : GroupEdgeIDs) + { + FindEdgeNbrGroups(GroupEdgeID, GroupsOut); + } +} + + + +void FGroupTopology::FindCornerNbrGroups(int CornerID, TArray& GroupsOut) const +{ + check(CornerID >= 0 && CornerID < Corners.Num()); + for (int GroupID : Corners[CornerID].NeighbourGroupIDs) + { + GroupsOut.AddUnique(GroupID); + } +} + +void FGroupTopology::FindCornerNbrGroups(const TArray& CornerIDs, TArray& GroupsOut) const +{ + for (int cid : CornerIDs) + { + FindCornerNbrGroups(cid, GroupsOut); + } +} + + + + +void FGroupTopology::FindVertexNbrGroups(int VertexID, TArray& GroupsOut) const +{ + for (int tid : Mesh->VtxTrianglesItr(VertexID)) + { + int GroupID = GetGroupID(tid); + GroupsOut.AddUnique(GroupID); + } +} + +void FGroupTopology::FindVertexNbrGroups(const TArray& VertexIDs, TArray& GroupsOut) const +{ + for (int vid : VertexIDs) + { + for (int tid : Mesh->VtxTrianglesItr(vid)) + { + int GroupID = GetGroupID(tid); + GroupsOut.AddUnique(GroupID); + } + } +} + + + +void FGroupTopology::CollectGroupVertices(int GroupID, TSet& Vertices) const +{ + const FGroup* Found = FindGroupByID(GroupID); + ensure(Found != nullptr); + if (Found != nullptr) + { + for (int TriID : Found->Faces) + { + FIndex3i TriVerts = Mesh->GetTriangle(TriID); + Vertices.Add(TriVerts.A); + Vertices.Add(TriVerts.B); + Vertices.Add(TriVerts.C); + } + } +} + + + +void FGroupTopology::CollectGroupBoundaryVertices(int GroupID, TSet& Vertices) const +{ + const FGroup* Group = FindGroupByID(GroupID); + ensure(Group != nullptr); + if (Group != nullptr) + { + for (const FGroupBoundary& Boundary : Group->Boundaries) + { + for (int EdgeIndex : Boundary.GroupEdges) + { + const FGroupEdge& Edge = Edges[EdgeIndex]; + for (int vid : Edge.Span.Vertices) + { + Vertices.Add(vid); + } + } + } + } +} + + + + + +void FGroupTopology::ForGroupEdges(int GroupID, + const TFunction& EdgeFunc) const +{ + const FGroup* Group = FindGroupByID(GroupID); + ensure(Group != nullptr); + if (Group != nullptr) + { + for (const FGroupBoundary& Boundary : Group->Boundaries) + { + for (int EdgeIndex : Boundary.GroupEdges) + { + EdgeFunc(Edges[EdgeIndex], EdgeIndex); + } + } + } +} + + + +void FGroupTopology::ForGroupSetEdges(const TArray& GroupIDs, + const TFunction& EdgeFunc) const +{ + TArray DoneEdges; + + for (int GroupID : GroupIDs) + { + const FGroup* Group = FindGroupByID(GroupID); + ensure(Group != nullptr); + if (Group != nullptr) + { + for (const FGroupBoundary& Boundary : Group->Boundaries) + { + for (int EdgeIndex : Boundary.GroupEdges) + { + if (DoneEdges.Contains(EdgeIndex) == false) + { + EdgeFunc(Edges[EdgeIndex], EdgeIndex); + DoneEdges.Add(EdgeIndex); + } + } + } + } + } +} + + + + +void FGroupTopology::ExtractGroupEdges(FGroup& Group) +{ + FMeshRegionBoundaryLoops BdryLoops(Mesh, Group.Faces, true); + int NumLoops = BdryLoops.Loops.Num(); + + Group.Boundaries.SetNum(NumLoops); + for ( int li = 0; li < NumLoops; ++li ) + { + FEdgeLoop& Loop = BdryLoops.Loops[li]; + FGroupBoundary& Boundary = Group.Boundaries[li]; + + // find indices of corners of group polygon + TArray CornerIndices; + int NumV = Loop.Vertices.Num(); + for (int i = 0; i < NumV; ++i) + { + if (CornerVerticesFlags[Loop.Vertices[i]]) + { + CornerIndices.Add(i); + } + } + + // if we had no indices then this is like the cap of a cylinder, just one single long edge + if ( CornerIndices.Num() == 0 ) + { + FIndex2i EdgeID = MakeEdgeID(Loop.Edges[0]); + int OtherGroupID = (EdgeID.A == Group.GroupID) ? EdgeID.B : EdgeID.A; + int EdgeIndex = FindExistingGroupEdge(Group.GroupID, OtherGroupID, Loop.Vertices[0]); + if (EdgeIndex == -1) + { + FGroupEdge Edge = { EdgeID }; + Edge.Span = FEdgeSpan(Mesh); + Edge.Span.InitializeFromEdges(Loop.Edges); + EdgeIndex = Edges.Add(Edge); + } + Boundary.GroupEdges.Add(EdgeIndex); + continue; + } + + // duplicate first corner vertex so that we can just loop back around to it w/ modulo count + int NumSpans = CornerIndices.Num(); + int FirstIdx = CornerIndices[0]; + CornerIndices.Add(FirstIdx); + + // add each span + for (int k = 0; k < NumSpans; ++k) + { + int i0 = CornerIndices[k]; + + FIndex2i EdgeID = MakeEdgeID(Loop.Edges[i0]); + int OtherGroupID = (EdgeID.A == Group.GroupID) ? EdgeID.B : EdgeID.A; + int EdgeIndex = FindExistingGroupEdge(Group.GroupID, OtherGroupID, Loop.Vertices[i0]); + if (EdgeIndex != -1) + { + FGroupEdge& Existing = Edges[EdgeIndex]; + Boundary.GroupEdges.Add(EdgeIndex); + continue; + } + + FGroupEdge Edge = { EdgeID }; + + int i1 = CornerIndices[k+1]; + check(i0 != i1); // this case will happen on a cylinder w/ a cut side, I think... + // (but that case won't exist because that's wouldn't be a group boundary edge!) + TArray SpanVertices; + while (i0 != i1) + { + SpanVertices.Add(Loop.Vertices[i0]); + i0 = (i0 + 1) % NumV; + } + SpanVertices.Add(Loop.Vertices[i0]); + + Edge.Span = FEdgeSpan(Mesh); + Edge.Span.InitializeFromVertices(SpanVertices); + EdgeIndex = Edges.Add(Edge); + Boundary.GroupEdges.Add(EdgeIndex); + } + } +} + + + + + + +int FGroupTopology::FindExistingGroupEdge(int GroupID, int OtherGroupID, int FirstVertexID) +{ + if (OtherGroupID < 0) + { + return -1; + } + + FGroup& OtherGroup = Groups[GroupIDToGroupIndexMap[OtherGroupID]]; + FIndex2i EdgeID = MakeEdgeID(GroupID, OtherGroupID); + + for (FGroupBoundary& Boundary : OtherGroup.Boundaries) + { + for (int EdgeIndex : Boundary.GroupEdges) + { + if (Edges[EdgeIndex].Groups == EdgeID) + { + // same EdgeID pair may occur multiple times in the same boundary loop! + // need to check that at least one endpoint is the same vertex + + TArray& Vertices = Edges[EdgeIndex].Span.Vertices; + if (Vertices[0] == FirstVertexID || Vertices[Vertices.Num() - 1] == FirstVertexID) + { + return EdgeIndex; + } + } + } + } + return -1; +} + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshAdapterUtil.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshAdapterUtil.cpp new file mode 100644 index 000000000000..0011fa2bc6dd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshAdapterUtil.cpp @@ -0,0 +1,75 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshAdapterUtil.h" + +FPointSetAdapterd MeshAdapterUtil::MakeVerticesAdapter(const FDynamicMesh3* Mesh) +{ + FPointSetAdapterd Adapter; + Adapter.MaxPointID = [Mesh]() { return Mesh->MaxVertexID(); }; + Adapter.PointCount = [Mesh]() { return Mesh->VertexCount(); }; + Adapter.IsPoint = [Mesh](int Idx) { return Mesh->IsVertex(Idx); }; + Adapter.GetPoint = [Mesh](int Idx) { return Mesh->GetVertex(Idx); }; + Adapter.Timestamp = [Mesh] { return Mesh->GetTimestamp(); }; + + Adapter.HasNormals = [Mesh] { return Mesh->HasVertexNormals(); }; + Adapter.GetPointNormal = [Mesh](int Idx) {return Mesh->GetVertexNormal(Idx); }; + + return Adapter; +} + + +FPointSetAdapterd MeshAdapterUtil::MakeTriCentroidsAdapter(const FDynamicMesh3* Mesh) +{ + FPointSetAdapterd Adapter; + Adapter.MaxPointID = [Mesh]() { return Mesh->MaxTriangleID(); }; + Adapter.PointCount = [Mesh]() { return Mesh->TriangleCount(); }; + Adapter.IsPoint = [Mesh](int Idx) { return Mesh->IsTriangle(Idx); }; + Adapter.GetPoint = [Mesh](int Idx) { return Mesh->GetTriCentroid(Idx); }; + Adapter.Timestamp = [Mesh] { return Mesh->GetTimestamp(); }; + + Adapter.HasNormals = [] { return true; }; + Adapter.GetPointNormal = [Mesh](int Idx) {return (FVector3f)Mesh->GetTriNormal(Idx); }; + + return Adapter; +} + + + + +FPointSetAdapterd MeshAdapterUtil::MakeEdgeMidpointsAdapter(const FDynamicMesh3* Mesh) +{ + FPointSetAdapterd Adapter; + Adapter.MaxPointID = [Mesh]() { return Mesh->MaxEdgeID(); }; + Adapter.PointCount = [Mesh]() { return Mesh->EdgeCount(); }; + Adapter.IsPoint = [Mesh] (int Idx) { return Mesh->IsEdge(Idx); }; + Adapter.GetPoint = [Mesh](int Idx) { return Mesh->GetEdgePoint(Idx, 0.5); }; + Adapter.Timestamp = [Mesh] { return Mesh->GetTimestamp(); }; + + Adapter.HasNormals = [] { return false; }; + Adapter.GetPointNormal = [](int Idx) { return FVector3f::UnitY();}; + + return Adapter; +} + + +FPointSetAdapterd MeshAdapterUtil::MakeBoundaryEdgeMidpointsAdapter(const FDynamicMesh3* Mesh) +{ + // may be possible to do this more quickly by directly iterating over Mesh.EdgesBuffer[eid*4+3] (still need to check valid) + int NumBoundaryEdges = 0; + for (int eid : Mesh->BoundaryEdgeIndicesItr()) + { + NumBoundaryEdges++; + } + + FPointSetAdapterd Adapter; + Adapter.MaxPointID = [Mesh]() { return Mesh->MaxEdgeID(); }; + Adapter.PointCount = [NumBoundaryEdges]() { return NumBoundaryEdges; }; + Adapter.IsPoint = [Mesh](int Idx) { return Mesh->IsEdge(Idx) && Mesh->IsBoundaryEdge(Idx); }; + Adapter.GetPoint = [Mesh](int Idx) { return Mesh->GetEdgePoint(Idx, 0.5); }; + Adapter.Timestamp = [Mesh] { return Mesh->GetTimestamp(); }; + + Adapter.HasNormals = [] { return false; }; + Adapter.GetPointNormal = [](int Idx) { return FVector3f::UnitY(); }; + + return Adapter; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshBoundaryLoops.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshBoundaryLoops.cpp new file mode 100644 index 000000000000..61bb18c41d5f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshBoundaryLoops.cpp @@ -0,0 +1,655 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "MeshBoundaryLoops.h" + + + +int FMeshBoundaryLoops::GetMaxVerticesLoopIndex() const +{ + int j = 0; + for (int i = 1; i < Loops.Num(); ++i) + { + if (Loops[i].Vertices.Num() > Loops[j].Vertices.Num()) + { + j = i; + } + } + return j; +} + + +FIndex2i FMeshBoundaryLoops::FindVertexInLoop(int VertexID) const +{ + int N = Loops.Num(); + for (int li = 0; li < N; ++li) + { + int idx = Loops[li].FindVertexIndex(VertexID); + if (idx >= 0) + { + return FIndex2i(li, idx); + } + } + return FIndex2i::Invalid(); +} + + + +int FMeshBoundaryLoops::FindLoopContainingVertex(int VertexID) const +{ + int N = Loops.Num(); + for (int li = 0; li < N; ++li) + { + if (Loops[li].Vertices.Contains(VertexID)) + { + return li; + } + } + return -1; +} + + +int FMeshBoundaryLoops::FindLoopContainingEdge(int EdgeID) const +{ + int N = Loops.Num(); + for (int li = 0; li < N; ++li) + { + if (Loops[li].Edges.Contains(EdgeID)) + { + return li; + } + } + return -1; +} + + + + +bool FMeshBoundaryLoops::Compute() +{ + // This algorithm assumes that triangles are oriented consistently, + // so closed boundary-loop can be followed by walking edges in-order + + Loops.Reset(); Spans.Reset(); + bSawOpenSpans = bFellBackToSpansOnFailure = false; + + // early-out if we don't actually have boundaries + if (Mesh->IsClosed()) + { + return true; + } + + int NE = Mesh->MaxEdgeID(); + + // Temporary memory used to indicate when we have "used" an edge. + TArray used_edge; + used_edge.Init(false, Mesh->MaxEdgeID()); + + // current loop is stored here, cleared after each loop extracted + TArray loop_edges; + TArray loop_verts; + TArray bowties; + + // Temp buffer for reading back all boundary edges of a vertex. + // probably always small but : pathological cases it could be large... + TArray all_e; + all_e.Reserve(32); + + // [TODO] might make sense to precompute some things here, like num_be for each bdry vtx? + + // process all edges of mesh + for (int eid = 0; eid < NE; ++eid) + { + if (Mesh->IsEdge(eid) == false) + { + continue; + } + if (used_edge[eid] == true) + { + continue; + } + if (Mesh->IsBoundaryEdge(eid) == false) + { + continue; + } + + if (EdgeFilterFunc != nullptr && EdgeFilterFunc(eid) == false) + { + used_edge[eid] = true; + continue; + } + + // ok this is start of a boundary chain + int eStart = eid; + used_edge[eStart] = true; + loop_edges.Add(eStart); + + int eCur = eid; + + // follow the chain in order of oriented edges + bool bClosed = false; + bool bIsOpenSpan = false; + while (!bClosed) + { + FIndex2i ev = Mesh->GetOrientedBoundaryEdgeV(eCur); + int cure_a = ev.A, cure_b = ev.B; + if (bIsOpenSpan) + { + cure_a = ev.B; cure_b = ev.A; + } + else + { + loop_verts.Add(cure_a); + } + + int e0 = -1, e1 = 1; + int bdry_nbrs = Mesh->GetVtxBoundaryEdges(cure_b, e0, e1); + + // have to filter this list, if we are filtering. this is ugly. + if (EdgeFilterFunc != nullptr) + { + if (bdry_nbrs > 2) + { + // we may repreat this below...irritating... + all_e.Reset(); + int num_be = Mesh->GetAllVtxBoundaryEdges(cure_b, all_e); + num_be = BufferUtil::CountValid(all_e, EdgeFilterFunc, num_be); + } + else + { + if (EdgeFilterFunc(e0) == false) bdry_nbrs--; + if (EdgeFilterFunc(e1) == false) bdry_nbrs--; + } + } + + + if (bdry_nbrs < 2) + { // hit an 'endpoint' vertex (should only happen when Filter is on...) + if (SpanBehavior == ESpanBehaviors::Abort) + { + bSawOpenSpans = true; + goto CATASTROPHIC_ABORT; + } + if (bIsOpenSpan) + { + bClosed = true; + continue; + } + else + { + bIsOpenSpan = true; // begin open span + eCur = loop_edges[0]; // restart at other end of loop + Algo::Reverse(loop_edges); // do this so we can push to front + continue; + } + } + + int eNext = -1; + + if (bdry_nbrs > 2) + { + // found "bowtie" vertex...things just got complicated! + + if (cure_b == loop_verts[0]) + { + // The "end" of the current edge is the same as the start vertex. + // This means we can close the loop here. Might as well! + eNext = -2; // sentinel value used below + + } + else + { + // try to find an unused outgoing edge that is oriented properly. + // This could create sub-loops, we will handle those later + all_e.Reset(); + int num_be = Mesh->GetAllVtxBoundaryEdges(cure_b, all_e); + check(num_be == bdry_nbrs); + + if (EdgeFilterFunc != nullptr) + { + num_be = BufferUtil::FilterInPlace(all_e, EdgeFilterFunc, num_be); + } + + // Try to pick the best "turn left" vertex. + eNext = FindLeftTurnEdge(eCur, cure_b, all_e, num_be, used_edge); + if (eNext == -1) + { + if (FailureBehavior == EFailureBehaviors::Abort || SpanBehavior == ESpanBehaviors::Abort) + { + goto CATASTROPHIC_ABORT; + } + + // ok, we are stuck. all we can do now is terminate this loop and keep it as a span + if (bIsOpenSpan) + { + bClosed = true; + } + else + { + bIsOpenSpan = true; + bClosed = true; + } + continue; + } + } + + if (bowties.Contains(cure_b) == false) + { + bowties.Add(cure_b); + } + + } + else + { + // walk forward to next available edge + check(e0 == eCur || e1 == eCur); + eNext = (e0 == eCur) ? e1 : e0; + } + + if (eNext == -2) + { + // found a bowtie vert that is the same as start-of-loop, so we + // are just closing it off explicitly + bClosed = true; + } + else if (eNext == eStart) + { + // found edge at start of loop, so loop is done. + bClosed = true; + } + else if (used_edge[eNext] != false) + { + // disaster case - the next edge is already used, but it is not the start of our loop + // All we can do is convert to open span and terminate + if (FailureBehavior == EFailureBehaviors::Abort || SpanBehavior == ESpanBehaviors::Abort) + { + goto CATASTROPHIC_ABORT; + } + bIsOpenSpan = true; + bClosed = true; + + } + else + { + // push onto accumulated list + check(used_edge[eNext] == false); + loop_edges.Add(eNext); + used_edge[eNext] = true; + eCur = eNext; + } + } + + if (bIsOpenSpan) + { + bSawOpenSpans = true; + if (SpanBehavior == ESpanBehaviors::Compute) + { + Algo::Reverse(loop_edges); // orient properly + FEdgeSpan& NewSpan = Spans[Spans.Emplace()]; + NewSpan.InitializeFromEdges(Mesh, loop_edges); + } + } + else if (bowties.Num() > 0) + { + // if we saw a bowtie vertex, we might need to break up this loop, + // so call ExtractSubloops + Subloops subloops; + bool bSubloopsOK = ExtractSubloops(loop_verts, loop_edges, bowties, subloops); + if (bSubloopsOK == false) + { + goto CATASTROPHIC_ABORT; + } + for (FEdgeLoop& loop : subloops.Loops) + { + Loops.Add(loop); + } + if (subloops.Spans.Num() > 0) + { + bFellBackToSpansOnFailure = true; + for (FEdgeSpan& span : subloops.Spans) + { + Spans.Add(span); + } + } + } + else + { + // clean simple loop, convert to EdgeLoop instance + FEdgeLoop& NewLoop = Loops[Loops.Emplace()]; + NewLoop.Initialize(Mesh, loop_verts, loop_edges); + } + + // reset these lists + loop_edges.Reset(); + loop_verts.Reset(); + bowties.Reset(); + } + + return true; + +CATASTROPHIC_ABORT: + bAborted = true; + return false; +} + + + +FVector3d FMeshBoundaryLoops::GetVertexNormal(int vid) +{ + FVector3d n = FVector3d::Zero(); + for (int ti : Mesh->VtxTrianglesItr(vid)) + { + n += Mesh->GetTriNormal(ti); + } + n.Normalize(); + return n; +} + + + + +// ok, bdry_edges[0...bdry_edges_count] contains the boundary edges coming out of bowtie_v. +// We want to pick the best one to continue the loop that came in to bowtie_v on incoming_e. +// If the loops are all sane, then we will get the smallest loops by "turning left" at bowtie_v. +// So, we compute the tangent plane at bowtie_v, and then the signed angle for each +// viable edge in this plane. +// +// [TODO] handle degenerate edges. what do we do then? Currently will only chose +// degenerate edge if there are no other options (I think...) +int FMeshBoundaryLoops::FindLeftTurnEdge(int incoming_e, int bowtie_v, TArray& bdry_edges, int bdry_edges_count, TArray& used_edges) +{ + // compute normal and edge [a,bowtie] + FVector3d n = GetVertexNormal(bowtie_v); + //int other_v = Mesh->edge_other_v(incoming_e, bowtie_v); + FIndex2i ev = Mesh->GetEdgeV(incoming_e); + int other_v = (ev.A == bowtie_v) ? ev.B : ev.A; + FVector3d ab = Mesh->GetVertex(bowtie_v) - Mesh->GetVertex(other_v); + + // our winner + int best_e = -1; + double best_angle = TNumericLimits::Max(); + + for (int i = 0; i < bdry_edges_count; ++i) + { + int bdry_eid = bdry_edges[i]; + if (used_edges[bdry_eid] == true) + { + continue; // this edge is already used + } + + FIndex2i bdry_ev = Mesh->GetOrientedBoundaryEdgeV(bdry_eid); + if (bdry_ev.A != bowtie_v) + { + continue; // have to be able to chain to end of current edge, orientation-wise + } + + // compute projected angle + FVector3d bc = Mesh->GetVertex(bdry_ev.B) - Mesh->GetVertex(bowtie_v); + double fAngleS = VectorUtil::PlaneAngleSignedD(ab, bc, n); + + // turn left! + if (best_angle == TNumericLimits::Max() || fAngleS < best_angle) + { + best_angle = fAngleS; + best_e = bdry_eid; + } + } + + // [RMS] w/ bowtie vertices and open spans, this does happen + //Debug.Assert(best_e != -1); + + return best_e; +} + + + + + +// This is called when loopV contains one or more "bowtie" vertices. +// These vertices *might* be duplicated in loopV (but not necessarily) +// If they are, we have to break loopV into subloops that don't contain duplicates. +// +// The list bowties contains all the possible duplicates +// (all v in bowties occur in loopV at least once) +// +// Currently loopE is not used, and the returned EdgeLoop objects do not have their Edges +// arrays initialized. Perhaps to improve in future. +// +// An unhandled case to think about is where we have a sequence [..A..B..A..B..] where +// A and B are bowties. In this case there are no A->A or B->B subloops. What should +// we do here?? +bool FMeshBoundaryLoops::ExtractSubloops(TArray& loopV, TArray& loopE, TArray& bowties, Subloops& SubloopsOut) +{ + Subloops& subs = SubloopsOut; + + // figure out which bowties we saw are actually duplicated in loopV + TArray dupes; + for (int bv : bowties) + { + if (CountInList(loopV, bv) > 1) + { + dupes.Add(bv); + } + } + + // we might not actually have any duplicates, if we got luck. Early out in that case + if (dupes.Num() == 0) + { + FEdgeLoop& NewLoop = subs.Loops[subs.Loops.Emplace()]; + NewLoop.Initialize(Mesh, loopV, loopE, &bowties); + return true; + } + + // This loop extracts subloops until we have dealt with all the + // duplicate vertices in loopV + while (dupes.Num() > 0) + { + + // Find shortest "simple" loop, ie a loop from a bowtie to itself that + // does not contain any other bowties. This is an independent loop. + // We're doing a lot of extra work here if we only have one element in dupes... + int bi = 0, bv = 0; + int start_i = -1, end_i = -1; + int bv_shortest = -1; int shortest = TNumericLimits::Max(); + for (; bi < dupes.Num(); ++bi) + { + bv = dupes[bi]; + if (IsSimpleBowtieLoop(loopV, dupes, bv, start_i, end_i)) + { + int len = CountSpan(loopV, start_i, end_i); + if (len < shortest) + { + bv_shortest = bv; + shortest = len; + } + } + } + + // failed to find a simple loop. Not sure what to do in this situation. + // If we don't want to throw, all we can do is convert the remaining + // loop to a span and return. + // (Or should we keep it as a loop and set flag??) + if (bv_shortest == -1) + { + if (FailureBehavior == EFailureBehaviors::Abort) + { + FailureBowties = dupes; + bAborted = true; + return false; + } + + VerticesTemp.Reset(); + for (int i = 0; i < loopV.Num(); ++i) + { + if (loopV[i] != -1) + { + VerticesTemp.Add(loopV[i]); + } + } + + FEdgeSpan& NewSpan = subs.Spans[subs.Spans.Emplace()]; + NewSpan.InitializeFromVertices(Mesh, VerticesTemp, false); + NewSpan.SetBowtieVertices(bowties); + } + + if (bv != bv_shortest) + { + bv = bv_shortest; + // running again just to get start_i and end_i... + IsSimpleBowtieLoop(loopV, dupes, bv, start_i, end_i); + } + + check(loopV[start_i] == bv && loopV[end_i] == bv); + + FEdgeLoop& NewLoop = subs.Loops[subs.Loops.Emplace()]; + + VerticesTemp.Reset(); + ExtractSpan(loopV, start_i, end_i, true, VerticesTemp); + NewLoop.InitializeFromVertices(Mesh, VerticesTemp, false); + NewLoop.SetBowtieVertices(bowties); + + // If there are no more duplicates of this bowtie, we can treat + // it like a regular vertex now + if (CountInList(loopV, bv) < 2) + { + dupes.Remove(bv); + } + } + + // Should have one loop left that contains duplicates. + // Extract this as a separate loop + int nLeft = 0; + for (int i = 0; i < loopV.Num(); ++i) + { + if (loopV[i] != -1) + nLeft++; + } + if (nLeft > 0) + { + FEdgeLoop& NewLoop = subs.Loops[subs.Loops.Emplace()]; + + VerticesTemp.Reset(); + for (int i = 0; i < loopV.Num(); ++i) + { + if (loopV[i] != -1) + { + VerticesTemp.Add(loopV[i]); + } + } + NewLoop.InitializeFromVertices(Mesh, VerticesTemp, false); + NewLoop.SetBowtieVertices(bowties); + } + + return true; +} + + + +// Check if the loop from bowtieV to bowtieV inside loopV contains any other bowtie verts. +// Also returns start and end indices in loopV of "clean" loop +// Note that start may be < end, if the "clean" loop wraps around the end +bool FMeshBoundaryLoops::IsSimpleBowtieLoop(const TArray& LoopVerts, const TArray& BowtieVerts, int BowtieVertex, int& start_i, int& end_i) +{ + // find two indices of bowtie vert + start_i = FindIndex(LoopVerts, 0, BowtieVertex); + end_i = FindIndex(LoopVerts, start_i + 1, BowtieVertex); + + if (IsSimplePath(LoopVerts, BowtieVerts, BowtieVertex, start_i, end_i)) + { + return true; + } + else if (IsSimplePath(LoopVerts, BowtieVerts, BowtieVertex, end_i, start_i)) + { + int tmp = start_i; start_i = end_i; end_i = tmp; + return true; + } + else + { + return false; // not a simple bowtie loop! + } +} + + +// check if forward path from loopV[i1] to loopV[i2] contains any bowtie verts other than bowtieV +bool FMeshBoundaryLoops::IsSimplePath(const TArray& LoopVerts, const TArray& BowtieVerts, int BowtieVertex, int i1, int i2) +{ + int N = LoopVerts.Num(); + for (int i = i1; i != i2; i = (i + 1) % N) + { + int vi = LoopVerts[i]; + if (vi == -1) + { + continue; // skip removed vertices + } + if (vi != BowtieVertex && BowtieVerts.Contains(vi)) + { + return false; + } + } + return true; +} + + +// Read out the span from loop[i0] to loop [i1-1] into an array. +// If bMarkInvalid, then these values are set to -1 in loop +void FMeshBoundaryLoops::ExtractSpan(TArray& Loop, int i0, int i1, bool bMarkInvalid, TArray& OutSpan) +{ + int num = CountSpan(Loop, i0, i1); + OutSpan.SetNum(num); + int ai = 0; + int N = Loop.Num(); + for (int i = i0; i != i1; i = (i + 1) % N) + { + if (Loop[i] != -1) + { + OutSpan[ai++] = Loop[i]; + if (bMarkInvalid) + { + Loop[i] = -1; + } + } + } +} + + +// count number of valid vertices in l between loop[i0] and loop[i1-1] +int FMeshBoundaryLoops::CountSpan(const TArray& Loop, int i0, int i1) +{ + int c = 0; + int N = Loop.Num(); + for (int i = i0; i != i1; i = (i + 1) % N) + { + if (Loop[i] != -1) + { + c++; + } + } + return c; +} + +// find the index of item in loop, starting at start index +int FMeshBoundaryLoops::FindIndex(const TArray& Loop, int Start, int Item) +{ + for (int i = Start; i < Loop.Num(); ++i) + { + if (Loop[i] == Item) + { + return i; + } + } + return -1; +} + +// count number of times item appears in loop +int FMeshBoundaryLoops::CountInList(const TArray& Loop, int Item) +{ + int c = 0; + for (int i = 0; i < Loop.Num(); ++i) + { + if (Loop[i] == Item) + { + c++; + } + } + return c; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshConstraintsUtil.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshConstraintsUtil.cpp new file mode 100644 index 000000000000..049a2c1ab0db --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshConstraintsUtil.cpp @@ -0,0 +1,27 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshConstraintsUtil.h" + + +void FMeshConstraintsUtil::ConstrainAllSeams(FMeshConstraints& Constraints, const FDynamicMesh3& Mesh, bool bAllowSplits, bool bAllowSmoothing) +{ + if (Mesh.HasAttributes() == false) + { + return; + } + const FDynamicMeshAttributeSet* Attributes = Mesh.Attributes(); + + FEdgeConstraint EdgeConstraint = (bAllowSplits) ? FEdgeConstraint::SplitsOnly() : FEdgeConstraint::FullyConstrained(); + FVertexConstraint VtxConstraint = (bAllowSmoothing) ? FVertexConstraint::PinnedMovable() : FVertexConstraint::Pinned(); + + for (int EdgeID : Mesh.EdgeIndicesItr()) + { + if (Attributes->IsSeamEdge(EdgeID)) + { + Constraints.SetOrUpdateEdgeConstraint(EdgeID, EdgeConstraint); + FIndex2i EdgeVerts = Mesh.GetEdgeV(EdgeID); + Constraints.SetOrUpdateVertexConstraint(EdgeVerts.A, VtxConstraint); + Constraints.SetOrUpdateVertexConstraint(EdgeVerts.B, VtxConstraint); + } + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshIndexUtil.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshIndexUtil.cpp new file mode 100644 index 000000000000..4aab1391329a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshIndexUtil.cpp @@ -0,0 +1,48 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "MeshIndexUtil.h" + + +void MeshIndexUtil::ConverTriangleToVertexIDs(const FDynamicMesh3* Mesh, const TArray& TriangleIDs, TArray& VertexIDsOut) +{ + // if we are getting close to full mesh it is probably more efficient to use a bitmap... + + int NumTris = TriangleIDs.Num(); + + // @todo profile this constant + if (NumTris < 25) + { + for (int k = 0; k < NumTris; ++k) + { + if (Mesh->IsTriangle(TriangleIDs[k])) + { + FIndex3i Tri = Mesh->GetTriangle(TriangleIDs[k]); + VertexIDsOut.AddUnique(Tri[0]); + VertexIDsOut.AddUnique(Tri[1]); + VertexIDsOut.AddUnique(Tri[2]); + } + } + } + else + { + TSet VertexSet; + VertexSet.Reserve(TriangleIDs.Num()*3); + for (int k = 0; k < NumTris; ++k) + { + if (Mesh->IsTriangle(TriangleIDs[k])) + { + FIndex3i Tri = Mesh->GetTriangle(TriangleIDs[k]); + VertexSet.Add(Tri[0]); + VertexSet.Add(Tri[1]); + VertexSet.Add(Tri[2]); + } + } + + VertexIDsOut.Reserve(VertexSet.Num()); + for (int VertexID : VertexSet) + { + VertexIDsOut.Add(VertexID); + } + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshNormals.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshNormals.cpp new file mode 100644 index 000000000000..2f08fe2205e3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshNormals.cpp @@ -0,0 +1,138 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp MeshNormals + +#include "MeshNormals.h" + + +void FMeshNormals::SetCount(int Count, bool bClearToZero) +{ + if (Normals.Num() < Count) + { + Normals.SetNum(Count); + } + if (bClearToZero) + { + for (int i = 0; i < Count; ++i) + { + Normals[i] = FVector3d::Zero(); + } + } +} + + + +void FMeshNormals::CopyToVertexNormals(FDynamicMesh3* SetMesh, bool bInvert) const +{ + if (SetMesh->HasVertexNormals() == false) + { + SetMesh->EnableVertexNormals(FVector3f::UnitX()); + } + + float sign = (bInvert) ? -1.0f : 1.0f; + int N = FMath::Min(Normals.Num(), SetMesh->MaxVertexID()); + for (int vi = 0; vi < N; ++vi) + { + if (Mesh->IsVertex(vi) && SetMesh->IsVertex(vi)) + { + SetMesh->SetVertexNormal(vi, sign * (FVector3f)Normals[vi]); + } + } +} + + + +void FMeshNormals::CopyToOverlay(FDynamicMeshNormalOverlay* NormalOverlay, bool bInvert) const +{ + float sign = (bInvert) ? -1.0f : 1.0f; + for (int ElemIdx : NormalOverlay->ElementIndicesItr()) + { + NormalOverlay->SetElement(ElemIdx, sign * (FVector3f)Normals[ElemIdx]); + } +} + + +void FMeshNormals::Compute_FaceAvg_AreaWeighted() +{ + SetCount(Mesh->MaxVertexID(), true); + + for (int TriIdx : Mesh->TriangleIndicesItr()) + { + FVector3d TriNormal, TriCentroid; double TriArea; + Mesh->GetTriInfo(TriIdx, TriNormal, TriArea, TriCentroid); + TriNormal *= TriArea; + + FIndex3i Triangle = Mesh->GetTriangle(TriIdx); + Normals[Triangle.A] += TriNormal; + Normals[Triangle.B] += TriNormal; + Normals[Triangle.C] += TriNormal; + } + + for (int VertIdx : Mesh->VertexIndicesItr()) + { + Normals[VertIdx].Normalize(); + } +} + + + +void FMeshNormals::Compute_Triangle() +{ + SetCount(Mesh->MaxTriangleID(), false); + + for (int TriIdx : Mesh->TriangleIndicesItr()) + { + Normals[TriIdx] = Mesh->GetTriNormal(TriIdx); + } +} + + + + +void FMeshNormals::Compute_Overlay_FaceAvg_AreaWeighted(const FDynamicMeshNormalOverlay* NormalOverlay) +{ + SetCount(NormalOverlay->MaxElementID(), true); + + for (int TriIdx : Mesh->TriangleIndicesItr()) + { + FVector3d TriNormal, TriCentroid; double TriArea; + Mesh->GetTriInfo(TriIdx, TriNormal, TriArea, TriCentroid); + TriNormal *= TriArea; + + FIndex3i Tri = NormalOverlay->GetTriangle(TriIdx); + for (int j = 0; j < 3; ++j) + { + if (Tri[j] != FDynamicMesh3::InvalidID) + { + Normals[Tri[j]] += TriNormal; + } + } + } + + for (int ElemIdx : NormalOverlay->ElementIndicesItr()) + { + Normals[ElemIdx].Normalize(); + } +} + + + +void FMeshNormals::QuickComputeVertexNormals(FDynamicMesh3& Mesh, bool bInvert) +{ + FMeshNormals normals(&Mesh); + normals.ComputeVertexNormals(); + normals.CopyToVertexNormals(&Mesh, bInvert); +} + + +FVector3d FMeshNormals::ComputeVertexNormal(const FDynamicMesh3& Mesh, int VertIdx) +{ + FVector3d SumNormal = FVector3d::Zero(); + for (int TriIdx : Mesh.VtxTrianglesItr(VertIdx)) + { + FVector3d Normal, Centroid; double Area; + Mesh.GetTriInfo(TriIdx, Normal, Area, Centroid); + SumNormal += Area * Normal; + } + return SumNormal.Normalized(); +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshRefinerBase.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshRefinerBase.cpp new file mode 100644 index 000000000000..5790432090b3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshRefinerBase.cpp @@ -0,0 +1,298 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshRefinerBase.h" +#include "DynamicMeshAttributeSet.h" + + + + + + +/* +* Check if edge collapse will create a face-normal flip. +* Also checks if collapse would violate link condition, since we are iterating over one-ring anyway. +* This only checks one-ring of vid, so you have to call it twice, with vid and vother reversed, to check both one-rings +*/ +bool FMeshRefinerBase::CheckIfCollapseCreatesFlipOrInvalid(int vid, int vother, const FVector3d& newv, int tc, int td) const +{ + FVector3d va = FVector3d::Zero(), vb = FVector3d::Zero(), vc = FVector3d::Zero(); + for (int tid : Mesh->VtxTrianglesItr(vid)) + { + if (tid == tc || tid == td) + { + continue; + } + FIndex3i curt = Mesh->GetTriangle(tid); + if (curt[0] == vother || curt[1] == vother || curt[2] == vother) + { + return true; // invalid nbrhood for collapse + } + Mesh->GetTriVertices(tid, va, vb, vc); + FVector3d ncur = (vb - va).Cross(vc - va); + double sign = 0; + if (curt[0] == vid) + { + FVector3d nnew = (vb - newv).Cross(vc - newv); + sign = ComputeEdgeFlipMetric(ncur, nnew); + } + else if (curt[1] == vid) + { + FVector3d nnew = (newv - va).Cross(vc - va); + sign = ComputeEdgeFlipMetric(ncur, nnew); + } + else if (curt[2] == vid) + { + FVector3d nnew = (vb - va).Cross(newv - va); + sign = ComputeEdgeFlipMetric(ncur, nnew); + } + else + { + check(false); // this should never happen! + } + if (sign <= EdgeFlipTolerance) + { + return true; + } + } + return false; +} + + + + +/** + * Check if edge flip might reverse normal direction. + * Not entirely clear on how to best implement this test. Currently checking if any normal-pairs are reversed. + */ +bool FMeshRefinerBase::CheckIfFlipInvertsNormals(int a, int b, int c, int d, int t0) const +{ + FVector3d vC = Mesh->GetVertex(c), vD = Mesh->GetVertex(d); + FIndex3i tri_v = Mesh->GetTriangle(t0); + int oa = a, ob = b; + IndexUtil::OrientTriEdge(oa, ob, tri_v); + FVector3d vOA = Mesh->GetVertex(oa), vOB = Mesh->GetVertex(ob); + FVector3d n0 = VectorUtil::FastNormalDirection(vOA, vOB, vC); + FVector3d n1 = VectorUtil::FastNormalDirection(vOB, vOA, vD); + FVector3d f0 = VectorUtil::FastNormalDirection(vC, vD, vOB); + if (ComputeEdgeFlipMetric(n0, f0) <= EdgeFlipTolerance || ComputeEdgeFlipMetric(n1, f0) <= EdgeFlipTolerance) + { + return true; + } + FVector3d f1 = VectorUtil::FastNormalDirection(vD, vC, vOA); + if (ComputeEdgeFlipMetric(n0, f1) <= EdgeFlipTolerance || ComputeEdgeFlipMetric(n1, f1) <= EdgeFlipTolerance) + { + return true; + } + + // this only checks if output faces are pointing towards eachother, which seems + // to still result in normal-flips in some cases + //if (f0.Dot(f1) < 0) + // return true; + + return false; +} + + + + + + +/** + * Figure out if we can collapse edge eid=[a,b] under current constraint set. + * First we resolve vertex constraints using CanCollapseVertex(). However this + * does not catch some topological cases at the edge-constraint level, which + * which we will only be able to detect once we know if we are losing a or b. + * See comments on CanCollapseVertex() for what collapse_to is for. + */ +bool FMeshRefinerBase::CanCollapseEdge(int eid, int a, int b, int c, int d, int tc, int td, int& collapse_to) const +{ + collapse_to = -1; + if (Constraints == nullptr) + { + return true; + } + bool bVtx = CanCollapseVertex(eid, a, b, collapse_to); + if (bVtx == false) + { + return false; + } + + // when we lose a vtx in a collapse, we also lose two edges [iCollapse,c] and [iCollapse,d]. + // If either of those edges is constrained, we would lose that constraint. + // This would be bad. + int iCollapse = (collapse_to == a) ? b : a; + if (c != IndexConstants::InvalidID) + { + int ec = Mesh->FindEdgeFromTri(iCollapse, c, tc); + if (Constraints->GetEdgeConstraint(ec).IsUnconstrained() == false) + { + return false; + } + } + if (d != IndexConstants::InvalidID) + { + int ed = Mesh->FindEdgeFromTri(iCollapse, d, td); + if (Constraints->GetEdgeConstraint(ed).IsUnconstrained() == false) + { + return false; + } + } + + return true; +} + + + + + + + +/** + * Resolve vertex constraints for collapsing edge eid=[a,b]. Generally we would + * collapse a to b, and set the new position as 0.5*(v_a+v_b). However if a *or* b + * are constrained, then we want to keep that vertex and collapse to its position. + * This vertex (a or b) will be returned in collapse_to, which is -1 otherwise. + * If a *and* b are constrained, then things are complicated (and documented below). + */ +bool FMeshRefinerBase::CanCollapseVertex(int eid, int a, int b, int& collapse_to) const +{ + collapse_to = -1; + if (Constraints == nullptr) + { + return true; + } + FVertexConstraint ca, cb; + Constraints->GetVertexConstraint(a, ca); + Constraints->GetVertexConstraint(b, cb); + + // no constraint at all + if (ca.Fixed == false && cb.Fixed == false && ca.Target == nullptr && cb.Target == nullptr) + { + return true; + } + + // handle a or b fixed + if (ca.Fixed == true && cb.Fixed == false) + { + // if b is fixed to a target, and it is different than a's target, we can't collapse + if (cb.Target != nullptr && cb.Target != ca.Target) + { + return false; + } + collapse_to = a; + return true; + } + if (cb.Fixed == true && ca.Fixed == false) + { + if (ca.Target != nullptr && ca.Target != cb.Target) + { + return false; + } + collapse_to = b; + return true; + } + // if both fixed, and options allow, treat this edge as unconstrained (eg collapse to midpoint) + // [RMS] tried picking a or b here, but something weird happens, where + // eg cylinder cap will entirely erode away. Somehow edge lengths stay below threshold?? + if (AllowCollapseFixedVertsWithSameSetID + && ca.FixedSetID >= 0 + && ca.FixedSetID == cb.FixedSetID) + { + return true; + } + + // handle a or b w/ target + if (ca.Target != nullptr && cb.Target == nullptr) + { + collapse_to = a; + return true; + } + if (cb.Target != nullptr && ca.Target == nullptr) + { + collapse_to = b; + return true; + } + // if both vertices are on the same target, and the edge is on that target, + // then we can collapse to either and use the midpoint (which will be projected + // to the target). *However*, if the edge is not on the same target, then we + // cannot collapse because we would be changing the constraint topology! + if (cb.Target != nullptr && ca.Target != nullptr && ca.Target == cb.Target) + { + if (Constraints->GetEdgeConstraint(eid).Target == ca.Target) + { + return true; + } + } + + return false; +} + + + + + + +void FMeshRefinerBase::RuntimeDebugCheck(int eid) +{ + if (DebugEdges.Contains(eid)) + ensure(false); +} + +void FMeshRefinerBase::DoDebugChecks(bool bEndOfPass) +{ + if (DEBUG_CHECK_LEVEL == 0) + return; + + DebugCheckVertexConstraints(); + + if ((DEBUG_CHECK_LEVEL > 2) || (bEndOfPass && DEBUG_CHECK_LEVEL > 1)) + { + Mesh->CheckValidity(true); + DebugCheckUVSeamConstraints(); + } +} + + +void FMeshRefinerBase::DebugCheckUVSeamConstraints() +{ + // verify UV constraints (temporary?) + if (Mesh->HasAttributes() && Mesh->Attributes()->PrimaryUV() != nullptr && Constraints != nullptr) + { + for (int eid : Mesh->EdgeIndicesItr()) + { + if (Mesh->Attributes()->PrimaryUV()->IsSeamEdge(eid)) + { + auto cons = Constraints->GetEdgeConstraint(eid); + check(cons.IsUnconstrained() == false); + } + } + for (int vid : Mesh->VertexIndicesItr()) + { + if (Mesh->Attributes()->PrimaryUV()->IsSeamVertex(vid)) + { + auto cons = Constraints->GetVertexConstraint(vid); + check(cons.Fixed == true); + } + } + } +} + + +void FMeshRefinerBase::DebugCheckVertexConstraints() +{ + if (Constraints == nullptr) + { + return; + } + auto AllVtxConstraints = Constraints->GetVertexConstraints(); + for (const TPair& vc : AllVtxConstraints) + { + int vid = vc.Key; + if (vc.Value.Target != nullptr) + { + FVector3d curpos = Mesh->GetVertex(vid); + FVector3d projected = vc.Value.Target->Project(curpos, vid); + check((curpos - projected).SquaredLength() < 0.0001f); + } + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshRegionBoundaryLoops.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshRegionBoundaryLoops.cpp new file mode 100644 index 000000000000..ebfecb1a70ca --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshRegionBoundaryLoops.cpp @@ -0,0 +1,476 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "MeshRegionBoundaryLoops.h" +#include "MeshBoundaryLoops.h" // has a set of internal static functions we re-use +#include "VectorUtil.h" + + + +FMeshRegionBoundaryLoops::FMeshRegionBoundaryLoops(const FDynamicMesh3* MeshIn, const TArray& RegionTris, bool bAutoCompute) +{ + this->Mesh = MeshIn; + + // make flag set for included triangles + triangles.Init(false, Mesh->MaxTriangleID()); + for (int i = 0; i < RegionTris.Num(); ++i) + { + triangles[RegionTris[i]] = true; + } + + // make flag set for included edges + // NOTE: this currently processes non-boundary-edges twice. Could + // avoid w/ another IndexFlagSet, but the check is inexpensive... + edges.Init(false, Mesh->MaxEdgeID()); + for (int i = 0; i < RegionTris.Num(); ++i) + { + int tid = RegionTris[i]; + FIndex3i te = Mesh->GetTriEdges(tid); + for (int j = 0; j < 3; ++j) + { + int eid = te[j]; + if (!ContainsElement(edges, eid)) + { + FIndex2i et = Mesh->GetEdgeT(eid); + if (et.B == IndexConstants::InvalidID || triangles[et.A] != triangles[et.B]) + { + edges_roi.Add(eid); + edges[eid] = true; + } + } + } + } + + if (bAutoCompute) + { + Compute(); + } +} + + + +int FMeshRegionBoundaryLoops::GetMaxVerticesLoopIndex() const +{ + int j = 0; + for (int i = 1; i < Loops.Num(); ++i) + { + if (Loops[i].Vertices.Num() > Loops[j].Vertices.Num()) + { + j = i; + } + } + return j; +} + + + +bool FMeshRegionBoundaryLoops::Compute() +{ + // This algorithm assumes that triangles are oriented consistently, + // so closed boundary-loop can be followed by walking edges in-order + Loops.SetNum(0); + + // Temporary memory used to indicate when we have "used" an edge. + TArray used_edge; + used_edge.Init(false, Mesh->MaxEdgeID()); + + // current loop is stored here, cleared after each loop extracted + TArray loop_edges; + TArray loop_verts; + TArray bowties; + + // Temp buffer for reading back all boundary edges of a vertex. + // probably always small but : pathological cases it could be large... + TArray all_e; + all_e.SetNum(16); + + // process all edges of mesh + for (int eid : edges_roi) + { + if (used_edge[eid] == true) + { + continue; + } + if (IsEdgeOnBoundary(eid) == false) + { + continue; + } + + // ok this is start of a boundary chain + int eStart = eid; + used_edge[eStart] = true; + loop_edges.Add(eStart); + + int eCur = eid; + + // follow the chain : order of oriented edges + bool bClosed = false; + while (!bClosed) + { + + // [TODO] can do this more efficiently? + int tid_in = IndexConstants::InvalidID, tid_out = IndexConstants::InvalidID; + IsEdgeOnBoundary(eCur, tid_in, tid_out); + + FIndex2i ev = GetOrientedEdgeVerts(eCur, tid_in, tid_out); + int cure_a = ev.A, cure_b = ev.B; + loop_verts.Add(cure_a); + + int e0 = -1, e1 = 1; + int bdry_nbrs = GetVertexBoundaryEdges(cure_b, e0, e1); + + check(bdry_nbrs >= 2); // if (bdry_nbrs < 2) throw new MeshBoundaryLoopsException("MeshRegionBoundaryLoops.Compute: found broken neighbourhood at vertex " + cure_b){ UnclosedLoop = true }; + + int eNext = -1; + if (bdry_nbrs > 2) + { + // found "bowtie" vertex...things just got complicated! + + if (cure_b == loop_verts[0]) + { + // The "end" of the current edge is the same as the start vertex. + // This means we can close the loop here. Might as well! + eNext = -2; // sentinel value used below + + } + else + { + // try to find an unused outgoing edge that is oriented properly. + // This could create sub-loops, we will handle those later + if (bdry_nbrs >= all_e.Num()) + all_e.SetNum(bdry_nbrs); + int num_be = GetAllVertexBoundaryEdges(cure_b, all_e); + + check(num_be == bdry_nbrs); + + // Try to pick the best "turn left" vertex. + eNext = FindLeftTurnEdge(eCur, cure_b, all_e, num_be, used_edge); + + check(eNext != -1); // throw new MeshBoundaryLoopsException("MeshRegionBoundaryLoops.Compute: cannot find valid outgoing edge at bowtie vertex " + cure_b){ BowtieFailure = true }; + } + + if (bowties.Contains(cure_b) == false) + { + bowties.Add(cure_b); + } + + } + else + { + check(e0 == eCur || e1 == eCur); + eNext = (e0 == eCur) ? e1 : e0; + } + + if (eNext == -2) + { + // found a bowtie vert that is the same as start-of-loop, so we + // are just closing it off explicitly + bClosed = true; + } + else if (eNext == eStart) + { + // found edge at start of loop, so loop is done. + bClosed = true; + } + else + { + // push onto accumulated list + check(used_edge[eNext] == false); + loop_edges.Add(eNext); + eCur = eNext; + used_edge[eCur] = true; + } + } + + // if we saw a bowtie vertex, we might need to break up this loop, + // so call ExtractSubloops + if (bowties.Num() > 0) + { + TArray subloops = ExtractSubloops(loop_verts, loop_edges, bowties); + for (int i = 0; i < subloops.Num(); ++i) + { + Loops.Add(subloops[i]); + } + } + else + { + // clean simple loop, convert to FEdgeLoop instance + FEdgeLoop loop(Mesh); + loop.Vertices = loop_verts; + loop.Edges = loop_edges; + Loops.Add(loop); + } + + // reset these lists + loop_edges.SetNum(0); + loop_verts.SetNum(0); + bowties.SetNum(0); + } + + return true; +} + + + + + + +// returns true for both internal and mesh boundary edges +// tid_in and tid_out are triangles 'in' and 'out' of set, respectively +bool FMeshRegionBoundaryLoops::IsEdgeOnBoundary(int eid, int& tid_in, int& tid_out) const +{ + if (ContainsElement(edges, eid) == false) + { + return false; + } + + tid_in = tid_out = IndexConstants::InvalidID; + FIndex2i et = Mesh->GetEdgeT(eid); + if (et.B == IndexConstants::InvalidID) // boundary edge! + { + tid_in = et.A; + tid_out = et.B; + return true; + } + + bool in0 = triangles[et.A]; + bool in1 = triangles[et.B]; + if (in0 != in1) + { + tid_in = (in0) ? et.A : et.B; + tid_out = (in0) ? et.B : et.A; + return true; + } + return false; +} + + + +// return same indices as GetEdgeV, but oriented based on attached triangle +FIndex2i FMeshRegionBoundaryLoops::GetOrientedEdgeVerts(int eID, int tid_in, int tid_out) +{ + FIndex2i edgev = Mesh->GetEdgeV(eID); + int a = edgev.A, b = edgev.B; + FIndex3i tri = Mesh->GetTriangle(tid_in); + int ai = IndexUtil::FindEdgeIndexInTri(a, b, tri); + return FIndex2i(tri[ai], tri[(ai + 1) % 3]); +} + + +int FMeshRegionBoundaryLoops::GetVertexBoundaryEdges(int vID, int& e0, int& e1) +{ + int count = 0; + for (int eid : Mesh->VtxEdgesItr(vID)) + { + if (IsEdgeOnBoundary(eid)) + { + if (count == 0) + { + e0 = eid; + } + else if (count == 1) + { + e1 = eid; + } + count++; + } + } + return count; +} + + +int FMeshRegionBoundaryLoops::GetAllVertexBoundaryEdges(int vID, TArray& e) +{ + int count = 0; + for (int eid : Mesh->VtxEdgesItr(vID)) + { + if (IsEdgeOnBoundary(eid)) + { + e[count++] = eid; + } + } + return count; +} + + +FVector3d FMeshRegionBoundaryLoops::GetVertexNormal(int vid) +{ + FVector3d n = FVector3d::Zero(); + for (int ti : Mesh->VtxTrianglesItr(vid)) + { + n += Mesh->GetTriNormal(ti); + } + n.Normalize(); + return n; +} + + + +// +// [TODO] for internal vertices, there is no ambiguity : which is the left-turn edge, +// we should be using 'closest' left-neighbour edge. +// +// ok, bdry_edges[0...bdry_edges_count] contains the boundary edges coming out of bowtie_v. +// We want to pick the best one to continue the loop that came : to bowtie_v on incoming_e. +// If the loops are all sane, then we will get the smallest loops by "turning left" at bowtie_v. +// So, we compute the tangent plane at bowtie_v, and then the signed angle for each +// viable edge : this plane. +int FMeshRegionBoundaryLoops::FindLeftTurnEdge(int incoming_e, int bowtie_v, TArray& bdry_edges, int bdry_edges_count, TArray& used_edges) +{ + // compute normal and edge [a,bowtie] + FVector3d n = GetVertexNormal(bowtie_v); + //int other_v = Mesh->edge_other_v(incoming_e, bowtie_v); + FIndex2i ev = Mesh->GetEdgeV(incoming_e); + int other_v = (ev.A == bowtie_v) ? ev.B : ev.A; + FVector3d ab = Mesh->GetVertex(bowtie_v) - Mesh->GetVertex(other_v); + + // our winner + int best_e = -1; + double best_angle = TNumericLimits::Max(); + + for (int i = 0; i < bdry_edges_count; ++i) + { + int bdry_eid = bdry_edges[i]; + if (used_edges[bdry_eid] == true) + continue; // this edge is already used + + // [TODO] can do this more efficiently? + int tid_in = IndexConstants::InvalidID, tid_out = IndexConstants::InvalidID; + IsEdgeOnBoundary(bdry_eid, tid_in, tid_out); + FIndex2i bdry_ev = GetOrientedEdgeVerts(bdry_eid, tid_in, tid_out); + //FIndex2i bdry_ev = Mesh.GetOrientedBoundaryEdgeV(bdry_eid); + + if (bdry_ev.A != bowtie_v) { + continue; // have to be able to chain to end of current edge, orientation-wise + } + + // compute projected angle + FVector3d bc = Mesh->GetVertex(bdry_ev.B) - Mesh->GetVertex(bowtie_v); + double fAngleS = VectorUtil::PlaneAngleSignedD(ab, bc, n); + + // turn left! + if (best_angle == TNumericLimits::Max() || fAngleS < best_angle) + { + best_angle = fAngleS; + best_e = bdry_eid; + } + } + check(best_e != -1); + + return best_e; +} + + + + +// This is called when loopV contains one or more "bowtie" vertices. +// These vertices *might* be duplicated : loopV (but not necessarily) +// If they are, we have to break loopV into subloops that don't contain duplicates. +// +// The list bowties contains all the possible duplicates +// (all v in bowties occur in loopV at least once) +// +// Currently loopE is not used, and the returned FEdgeLoop objects do not have their Edges +// arrays initialized. Perhaps to improve : future. +TArray FMeshRegionBoundaryLoops::ExtractSubloops(TArray& loopV, const TArray& loopE, const TArray& bowties) +{ + TArray subs; + + // figure out which bowties we saw are actually duplicated : loopV + TArray dupes; + for (int bv : bowties) + { + if (FMeshBoundaryLoops::CountInList(loopV, bv) > 1) + { + dupes.Add(bv); + } + } + + // we might not actually have any duplicates, if we got luck. Early out : that case + if (dupes.Num() == 0) + { + FEdgeLoop NewLoop(Mesh); + NewLoop.Vertices = loopV; + NewLoop.Edges = loopE; + NewLoop.BowtieVertices = bowties; + subs.Add(NewLoop); + return subs; + } + + // This loop extracts subloops until we have dealt with all the + // duplicate vertices : loopV + while (dupes.Num() > 0) + { + + // Find shortest "simple" loop, ie a loop from a bowtie to itself that + // does not contain any other bowties. This is an independent loop. + // We're doing a lot of extra work here if we only have one element : dupes... + int bi = 0, bv = 0; + int start_i = -1, end_i = -1; + int bv_shortest = -1; int shortest = TNumericLimits::Max(); + for (; bi < dupes.Num(); ++bi) + { + bv = dupes[bi]; + if (FMeshBoundaryLoops::IsSimpleBowtieLoop(loopV, dupes, bv, start_i, end_i)) + { + int len = FMeshBoundaryLoops::CountSpan(loopV, start_i, end_i); + if (len < shortest) + { + bv_shortest = bv; + shortest = len; + } + } + } + check(bv_shortest != -1); // throw new MeshBoundaryLoopsException("MeshRegionBoundaryLoops.Compute: Cannot find a valid simple loop"); + if (bv != bv_shortest) + { + bv = bv_shortest; + // running again just to get start_i and end_i... + FMeshBoundaryLoops::IsSimpleBowtieLoop(loopV, dupes, bv, start_i, end_i); + } + + check(loopV[start_i] == bv && loopV[end_i] == bv); + + FEdgeLoop loop(Mesh); + FMeshBoundaryLoops::ExtractSpan(loopV, start_i, end_i, true, loop.Vertices); + FEdgeLoop::VertexLoopToEdgeLoop(Mesh, loop.Vertices, loop.Edges); + loop.BowtieVertices = bowties; + subs.Add(loop); + + // If there are no more duplicates of this bowtie, we can treat + // it like a regular vertex now + if (FMeshBoundaryLoops::CountInList(loopV, bv) < 2) + { + dupes.Remove(bv); + } + } + + // Should have one loop left that contains duplicates. + // Extract this as a separate loop + int nLeft = 0; + for (int i = 0; i < loopV.Num(); ++i) + { + if (loopV[i] != -1) + { + nLeft++; + } + } + if (nLeft > 0) + { + FEdgeLoop loop(Mesh); + loop.Vertices.SetNum(nLeft); + int vi = 0; + for (int i = 0; i < loopV.Num(); ++i) + { + if (loopV[i] != -1) + { + loop.Vertices[vi++] = loopV[i]; + } + } + FEdgeLoop::VertexLoopToEdgeLoop(Mesh, loop.Vertices, loop.Edges); + loop.BowtieVertices = bowties; + subs.Add(loop); + } + + return subs; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshSimplification.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshSimplification.cpp new file mode 100644 index 000000000000..9322c92c8866 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshSimplification.cpp @@ -0,0 +1,842 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshSimplification.h" +#include "DynamicMeshAttributeSet.h" +#include "Util/IndexUtil.h" + + + +template +QuadricErrorType TMeshSimplification::ComputeFaceQuadric(const int tid, FVector3d& nface, FVector3d& c, double& Area) const +{ + // compute the new quadric for this tri. + Mesh->GetTriInfo(tid, nface, Area, c); + + return FQuadricErrorType(nface, c); +} + + +// Face Quadric Error computation specialized for FAttrBasedQuadricErrord +template<> +FAttrBasedQuadricErrord TMeshSimplification::ComputeFaceQuadric(const int tid, FVector3d& nface, FVector3d& c, double& Area) const +{ + // compute the new quadric for this tri. + Mesh->GetTriInfo(tid, nface, Area, c); + + FVector3f n0; FVector3f n1; FVector3f n2; + + if (NormalOverlay != nullptr) + { + NormalOverlay->GetTriElements(tid, n0, n1, n2); + } + else + { + FIndex3i vids = Mesh->GetTriangle(tid); + n0 = Mesh->GetVertexNormal(vids[0]); + n1 = Mesh->GetVertexNormal(vids[1]); + n2 = Mesh->GetVertexNormal(vids[2]); + } + + + FVector3d p0, p1, p2; + Mesh->GetTriVertices(tid, p0, p1, p2); + + FVector3d n0d(n0.X, n0.Y, n0.Z); + FVector3d n1d(n1.X, n1.Y, n1.Z); + FVector3d n2d(n2.X, n2.Y, n2.Z); + + double attrweight = 1.; + return FQuadricErrorType(p0, p1, p2, n0d, n1d, n2d, nface, c, attrweight); +} + +template +void TMeshSimplification::InitializeTriQuadrics() +{ + const int NT = Mesh->MaxTriangleID(); + triQuadrics.SetNum(NT); + triAreas.SetNum(NT); + + // tested with ParallelFor - no measurable benifit + //@todo parallel version + //gParallel.BlockStartEnd(0, Mesh->MaxTriangleID - 1, (start_tid, end_tid) = > { + FVector3d n, c; + for (int tid : Mesh->TriangleIndicesItr()) + { + triQuadrics[tid] = ComputeFaceQuadric(tid, n, c, triAreas[tid]); + } + +} + +template +void TMeshSimplification::InitializeVertexQuadrics() +{ + + int NV = Mesh->MaxVertexID(); + vertQuadrics.SetNum(NV); + // tested with ParallelFor - no measurable benifit + //gParallel.BlockStartEnd(0, Mesh->MaxVertexID - 1, (start_vid, end_vid) = > { + for (int vid : Mesh->VertexIndicesItr()) + { + vertQuadrics[vid] = FQuadricErrorType::Zero(); + for (int tid : Mesh->VtxTrianglesItr(vid)) + { + vertQuadrics[vid].Add(triAreas[tid], triQuadrics[tid]); + } + //check(TMathUtil.EpsilonEqual(0, vertQuadrics[i].Evaluate(Mesh->GetVertex(i)), TMathUtil.Epsilon * 10)); + } + +} + + + +template +void TMeshSimplification::InitializeQueue() +{ + int NE = Mesh->EdgeCount(); + int MaxEID = Mesh->MaxEdgeID(); + + EdgeQuadrics.SetNum(MaxEID); + EdgeQueue.Initialize(MaxEID); + TArray EdgeErrors; + EdgeErrors.SetNum(MaxEID); + + // @todo vertex quadrics can be computed in parallel + //gParallel.BlockStartEnd(0, MaxEID - 1, (start_eid, end_eid) = > { + //for (int eid = start_eid; eid <= end_eid; eid++) { + for (int eid : Mesh->EdgeIndicesItr()) + { + FIndex2i ev = Mesh->GetEdgeV(eid); + FQuadricErrorType Q(vertQuadrics[ev.A], vertQuadrics[ev.B]); + FVector3d opt = OptimalPoint(eid, Q, ev.A, ev.B); + EdgeErrors[eid] = { (float)Q.Evaluate(opt), eid }; + EdgeQuadrics[eid] = QEdge(eid, Q, opt); + } + + // sorted pq insert is faster, so sort edge errors array and index map + EdgeErrors.Sort(); + + // now do inserts + int N = EdgeErrors.Num(); + for (int i = 0; i < N; ++i) + { + int eid = EdgeErrors[i].eid; + if (Mesh->IsEdge(eid)) + { + QEdge edge = EdgeQuadrics[eid]; + EdgeQueue.Insert(edge.eid, EdgeErrors[i].error); + } + } + + /* + // previous code that does unsorted insert. This is marginally slower, but + // might get even slower on larger meshes? have only tried up to about 350k. + // (still, this function is not the bottleneck...) + int cur_eid = StartEdges(); + bool done = false; + do { + if (Mesh->IsEdge(cur_eid)) { + QEdge edge = EdgeQuadrics[cur_eid]; + double err = errList[cur_eid]; + EdgeQueue.Enqueue(cur_eid, (float)err); + } + cur_eid = GetNextEdge(cur_eid, out done); + } while (done == false); + */ +} + + + + +template +FVector3d TMeshSimplification::OptimalPoint(int eid, const FQuadricErrorType& q, int ea, int eb) +{ + // if we would like to preserve boundary, we need to know that here + // so that we properly score these edges + if (bHaveBoundary && bPreserveBoundaryShape) + { + if (Mesh->IsBoundaryEdge(eid)) + { + return (Mesh->GetVertex(ea) + Mesh->GetVertex(eb)) * 0.5; + } + else + { + if (IsBoundaryVertex(ea)) + { + return Mesh->GetVertex(ea); + } + else if (IsBoundaryVertex(eb)) + { + return Mesh->GetVertex(eb); + } + } + } + + // [TODO] if we have constraints, we should apply them here, for same reason as bdry above... + + if (bMinimizeQuadricPositionError == false) + { + return GetProjectedPoint((Mesh->GetVertex(ea) + Mesh->GetVertex(eb)) * 0.5); + } + else + { + FVector3d result = FVector3d::Zero(); + if (q.OptimalPoint(result)) + { + return GetProjectedPoint(result); + } + + // degenerate matrix, evaluate quadric at edge end and midpoints + // (could do line search here...) + FVector3d va = Mesh->GetVertex(ea); + FVector3d vb = Mesh->GetVertex(eb); + FVector3d c = GetProjectedPoint((va + vb) * 0.5); + double fa = q.Evaluate(va); + double fb = q.Evaluate(vb); + double fc = q.Evaluate(c); + double m = FMath::Min3(fa, fb, fc); + if (m == fa) + { + return va; + } + else if (m == fb) + { + return vb; + } + return c; + } +} + + + + +// update queue weight for each edge in vertex one-ring +template <> +void DYNAMICMESH_API TMeshSimplification::UpdateNeighbours(int vid, FIndex2i removedTris, FIndex2i opposingVerts) +{ + for (int eid : Mesh->VtxEdgesItr(vid)) + { + FIndex2i nev = Mesh->GetEdgeV(eid); + FQuadricErrord Q(vertQuadrics[nev.A], vertQuadrics[nev.B]); + FVector3d opt = OptimalPoint(eid, Q, nev.A, nev.B); + double err = Q.Evaluate(opt); + EdgeQuadrics[eid] = QEdge(eid, Q, opt); + if (EdgeQueue.Contains(eid)) + { + EdgeQueue.Update(eid, (float)err); + } + else + { + EdgeQueue.Insert(eid, (float)err); + } + } +} + +// update queue weight for each edge in vertex one-ring. Memoryless +template +void TMeshSimplification::UpdateNeighbours(int vid, FIndex2i removedTris, FIndex2i opposingVerts) +{ + + + // This is the faster version that selectively updates the one-ring + { + + // compute the change in affected face quadrics, and then propagate + // that change to the face adjacent verts. + FVector3d n, c; + double NewtriArea; + + // Update the triangle areas and quadrics that will have changed + for (int tid : Mesh->VtxTrianglesItr(vid)) + { + + const double OldtriArea = triAreas[tid]; + const FQuadricErrorType OldtriQuadric = triQuadrics[tid]; + + + // compute the new quadric for this tri. + FQuadricErrorType NewtriQuadric = ComputeFaceQuadric(tid, n, c, NewtriArea); + + // update the arrays that hold the current face area & quadrics + triAreas[tid] = NewtriArea; + triQuadrics[tid] = NewtriQuadric; + + FIndex3i tri_vids = Mesh->GetTriangle(tid); + + // update the vert quadrics that are adjacent to vid. + for (int32 i = 0; i < 3; ++i) + { + if (tri_vids[i] == vid) continue; + + // correct the adjacent vertQuadrics + vertQuadrics[tri_vids[i]].Add(-OldtriArea, OldtriQuadric); // subtract old quadric + vertQuadrics[tri_vids[i]].Add(NewtriArea, NewtriQuadric); // add new quadric + } + } + + // remove the influence of the dead tris from the two verts that were opposing the collapsed edge + { + for (int i = 0; i < 2; ++i) + { + if (removedTris[i] != FDynamicMesh3::InvalidID) + { + const double oldArea = triAreas[removedTris[i]]; + FQuadricErrorType oldQuadric = triQuadrics[removedTris[i]]; + + triAreas[removedTris[i]] = 0.; + + // subtract the quadric from the opposing vert + vertQuadrics[opposingVerts[i]].Add(-oldArea, oldQuadric); + } + } + } + // Rebuild the quadric for the vert that was retained during the collapse. + // NB: in the version with memory this quadric took the value of the edge quadric that collapsed. + { + FQuadricErrorType vertQuadric; + for (int tid : Mesh->VtxTrianglesItr(vid)) + { + vertQuadric.Add(triAreas[tid], triQuadrics[tid]); + } + vertQuadrics[vid] = vertQuadric; + } + } + + // Update all the edges + { + TArray> EdgesToUpdate; + for (int adjvid : Mesh->VtxVerticesItr(vid)) + { + for (int eid : Mesh->VtxEdgesItr(adjvid)) + { + EdgesToUpdate.AddUnique(eid); + } + } + + for (int eid : EdgesToUpdate) + { + // The volume conservation plane data held in the + // vertex quadrics will have duplicates for + // the two face adjacent to the edge. + + const FIndex4i edgeData = Mesh->GetEdge(eid); + FQuadricErrorType Q(vertQuadrics[edgeData[0]], vertQuadrics[edgeData[1]]); + + FVector3d opt = OptimalPoint(eid, Q, edgeData[0], edgeData[1]); + double err = Q.Evaluate(opt); + EdgeQuadrics[eid] = QEdge(eid, Q, opt); + if (EdgeQueue.Contains(eid)) + { + EdgeQueue.Update(eid, (float)err); + } + else + { + EdgeQueue.Insert(eid, (float)err); + } + } + } +} + + + + + +template +void TMeshSimplification::Precompute(bool bMeshIsClosed) +{ + bHaveBoundary = false; + IsBoundaryVtxCache.SetNum(Mesh->MaxVertexID()); + if (bMeshIsClosed == false) + { + for (int eid : Mesh->BoundaryEdgeIndicesItr()) + { + FIndex2i ev = Mesh->GetEdgeV(eid); + IsBoundaryVtxCache[ev.A] = true; + IsBoundaryVtxCache[ev.B] = true; + bHaveBoundary = true; + } + } +} + + + + +template +void TMeshSimplification::DoSimplify() +{ + if (Mesh->TriangleCount() == 0) // badness if we don't catch this... + { + return; + } + + ProfileBeginPass(); + + ProfileBeginSetup(); + Precompute(); + if (Cancelled()) + { + return; + } + InitializeTriQuadrics(); + if (Cancelled()) + { + return; + } + InitializeVertexQuadrics(); + if (Cancelled()) + { + return; + } + InitializeQueue(); + if (Cancelled()) + { + return; + } + ProfileEndSetup(); + + ProfileBeginOps(); + + ProfileBeginCollapse(); + while (EdgeQueue.GetCount() > 0) + { + // termination criteria + if (SimplifyMode == ETargetModes::VertexCount) + { + if (Mesh->VertexCount() <= TargetCount) + { + break; + } + } + else + { + if (Mesh->TriangleCount() <= TargetCount) + { + break; + } + } + + COUNT_ITERATIONS++; + int eid = EdgeQueue.Dequeue(); + if (Mesh->IsEdge(eid) == false) + { + continue; + } + if (Cancelled()) + { + return; + } + + // find triangles adjacent to the target edge + // and the verts opposite the edge. + FIndex2i targetTris = Mesh->GetEdgeT(eid); + FIndex2i targetVrts = Mesh->GetEdgeOpposingV(eid); + + + int vKeptID; + ESimplificationResult result = CollapseEdge(eid, EdgeQuadrics[eid].collapse_pt, vKeptID); + if (result == ESimplificationResult::Ok_Collapsed) + { + vertQuadrics[vKeptID] = EdgeQuadrics[eid].q; + UpdateNeighbours(vKeptID, targetTris, targetVrts); + } + } + ProfileEndCollapse(); + ProfileEndOps(); + + if (Cancelled()) + { + return; + } + + Reproject(); + + ProfileEndPass(); +} + + +template +void TMeshSimplification::SimplifyToTriangleCount(int nCount) +{ + SimplifyMode = ETargetModes::TriangleCount; + TargetCount = FMath::Max(1, nCount); + MinEdgeLength = FMathd::MaxReal; + DoSimplify(); +} + +template +void TMeshSimplification::SimplifyToVertexCount(int nCount) +{ + SimplifyMode = ETargetModes::VertexCount; + TargetCount = FMath::Max(3, nCount); + MinEdgeLength = FMathd::MaxReal; + DoSimplify(); +} + +template +void TMeshSimplification::SimplifyToEdgeLength(double minEdgeLen) +{ + SimplifyMode = ETargetModes::MinEdgeLength; + TargetCount = 1; + MinEdgeLength = minEdgeLen; + DoSimplify(); +} + + + +template +void TMeshSimplification::FastCollapsePass(double fMinEdgeLength, int nRounds, bool MeshIsClosedHint) +{ + if (Mesh->TriangleCount() == 0) // badness if we don't catch this... + { + return; + } + + MinEdgeLength = fMinEdgeLength; + double min_sqr = MinEdgeLength * MinEdgeLength; + + // we don't collapse on the boundary + bHaveBoundary = false; + + ProfileBeginPass(); + + ProfileBeginSetup(); + Precompute(MeshIsClosedHint); + if (Cancelled()) + { + return; + } + ProfileEndSetup(); + + ProfileBeginOps(); + + ProfileBeginCollapse(); + + int N = Mesh->MaxEdgeID(); + int num_last_pass = 0; + for (int ri = 0; ri < nRounds; ++ri) + { + num_last_pass = 0; + + FVector3d va = FVector3d::Zero(), vb = FVector3d::Zero(); + for (int eid = 0; eid < N; ++eid) + { + if ((!Mesh->IsEdge(eid)) || Mesh->IsBoundaryEdge(eid)) + { + continue; + } + if (Cancelled()) + { + return; + } + + Mesh->GetEdgeV(eid, va, vb); + if (va.DistanceSquared(vb) > min_sqr) + { + continue; + } + + COUNT_ITERATIONS++; + + FVector3d midpoint = (va + vb) * 0.5; + int vKeptID; + ESimplificationResult result = CollapseEdge(eid, midpoint, vKeptID); + if (result == ESimplificationResult::Ok_Collapsed) + { + ++num_last_pass; + } + } + + if (num_last_pass == 0) // converged + { + break; + } + } + ProfileEndCollapse(); + ProfileEndOps(); + + if (Cancelled()) + { + return; + } + + Reproject(); + + ProfileEndPass(); +} + + + + + + + + + + + + + + +template +ESimplificationResult TMeshSimplification::CollapseEdge(int edgeID, FVector3d vNewPos, int& collapseToV) +{ + collapseToV = FDynamicMesh3::InvalidID; + RuntimeDebugCheck(edgeID); + + FEdgeConstraint constraint = + (Constraints == nullptr) ? FEdgeConstraint::Unconstrained() : Constraints->GetEdgeConstraint(edgeID); + if (constraint.NoModifications()) + { + return ESimplificationResult::Ignored_EdgeIsFullyConstrained; + } + if (constraint.CanCollapse() == false) + { + return ESimplificationResult::Ignored_EdgeIsFullyConstrained; + } + + + // look up verts and tris for this edge + if (Mesh->IsEdge(edgeID) == false) + { + return ESimplificationResult::Failed_NotAnEdge; + } + FIndex4i edgeInfo = Mesh->GetEdge(edgeID); + int a = edgeInfo.A, b = edgeInfo.B, t0 = edgeInfo.C, t1 = edgeInfo.D; + bool bIsBoundaryEdge = (t1 == FDynamicMesh3::InvalidID); + + // look up 'other' verts c (from t0) and d (from t1, if it exists) + FIndex3i T0tv = Mesh->GetTriangle(t0); + int c = IndexUtil::FindTriOtherVtx(a, b, T0tv); + FIndex3i T1tv = (bIsBoundaryEdge) ? FDynamicMesh3::InvalidTriangle() : Mesh->GetTriangle(t1); + int d = (bIsBoundaryEdge) ? FDynamicMesh3::InvalidID : IndexUtil::FindTriOtherVtx(a, b, T1tv); + + FVector3d vA = Mesh->GetVertex(a); + FVector3d vB = Mesh->GetVertex(b); + double edge_len_sqr = (vA - vB).SquaredLength(); + if (edge_len_sqr > MinEdgeLength * MinEdgeLength) + { + return ESimplificationResult::Ignored_EdgeTooLong; + } + + ProfileBeginCollapse(); + + // check if we should collapse, and also find which vertex we should collapse to, + // in cases where we have constraints/etc + int collapse_to = -1; + bool bCanCollapse = CanCollapseEdge(edgeID, a, b, c, d, t0, t1, collapse_to); + if (bCanCollapse == false) + { + return ESimplificationResult::Ignored_Constrained; + } + + // if we have a boundary, we want to collapse to boundary + if (bPreserveBoundaryShape && bHaveBoundary) + { + if (collapse_to != -1) + { + if ((IsBoundaryVertex(b) && collapse_to != b) || + (IsBoundaryVertex(a) && collapse_to != a)) + { + return ESimplificationResult::Ignored_Constrained; + } + } + if (IsBoundaryVertex(b)) + { + collapse_to = b; + } + else if (IsBoundaryVertex(a)) + { + collapse_to = a; + } + } + + // optimization: if edge cd exists, we cannot collapse or flip. look that up here? + // funcs will do it internally... + // (or maybe we can collapse if cd exists? edge-collapse doesn't check for it explicitly...) + ESimplificationResult retVal = ESimplificationResult::Failed_OpNotSuccessful; + + int iKeep = b, iCollapse = a; + + // if either vtx is fixed, collapse to that position + double collapse_t = 0; + if (collapse_to == b) + { + vNewPos = vB; + collapse_t = 0; + } + else if (collapse_to == a) + { + iKeep = a; iCollapse = b; + vNewPos = vA; + collapse_t = 0; + } + else + { + vNewPos = GetProjectedCollapsePosition(iKeep, vNewPos); + double div = vA.Distance(vB); + collapse_t = (div < FMathd::ZeroTolerance) ? 0.5 : (vNewPos.Distance(Mesh->GetVertex(iKeep))) / div; + collapse_t = VectorUtil::Clamp(collapse_t, 0.0, 1.0); + } + + // check if this collapse will create a normal flip. Also checks + // for invalid collapse nbrhood, since we are doing one-ring iter anyway. + // [TODO] could we skip this one-ring check in CollapseEdge? pass in hints? + if (CheckIfCollapseCreatesFlipOrInvalid(a, b, vNewPos, t0, t1) || CheckIfCollapseCreatesFlipOrInvalid(b, a, vNewPos, t0, t1)) + { + ProfileEndCollapse(); + return ESimplificationResult::Ignored_CreatesFlip; + } + + // lots of cases where we cannot collapse, but we should just let + // Mesh sort that out, right? + COUNT_COLLAPSES++; + FDynamicMesh3::FEdgeCollapseInfo collapseInfo; + EMeshResult result = Mesh->CollapseEdge(iKeep, iCollapse, collapse_t, collapseInfo); + if (result == EMeshResult::Ok) + { + collapseToV = iKeep; + Mesh->SetVertex(iKeep, vNewPos); + if (Constraints != nullptr) + { + Constraints->ClearEdgeConstraint(edgeID); + Constraints->ClearEdgeConstraint(collapseInfo.RemovedEdges.A); + if (collapseInfo.RemovedEdges.B != FDynamicMesh3::InvalidID) + { + Constraints->ClearEdgeConstraint(collapseInfo.RemovedEdges.B); + } + Constraints->ClearVertexConstraint(iCollapse); + } + OnEdgeCollapse(edgeID, iKeep, iCollapse, collapseInfo); + DoDebugChecks(); + + retVal = ESimplificationResult::Ok_Collapsed; + } + + ProfileEndCollapse(); + return retVal; +} + + + + + + + + +// Project vertices onto projection target. +// We can do projection in parallel if we have .net +template +void TMeshSimplification::FullProjectionPass() +{ + auto project = [&](int vID) + { + if (IsVertexConstrained(vID)) + { + return; + } + if (VertexControlF != nullptr && ((int)VertexControlF(vID) & (int)EVertexControl::NoProject) != 0) + { + return; + } + FVector3d curpos = Mesh->GetVertex(vID); + FVector3d projected = ProjTarget->Project(curpos, vID); + Mesh->SetVertex(vID, projected); + }; + + ApplyToProjectVertices(project); + + // [RMS] not sure how to do this... + //if (EnableParallelProjection) { + // gParallel.ForEach(project_vertices(), project); + //} else { + // foreach (int vid in project_vertices()) + // project(vid); + //} +} + +template +void TMeshSimplification::ApplyToProjectVertices(const TFunction& apply_f) +{ + for (int vid : Mesh->VertexIndicesItr()) + { + apply_f(vid); + } +} + +template +void TMeshSimplification::ProjectVertex(int vID, IProjectionTarget* targetIn) +{ + FVector3d curpos = Mesh->GetVertex(vID); + FVector3d projected = targetIn->Project(curpos, vID); + Mesh->SetVertex(vID, projected); +} + +// used by collapse-edge to get projected position for new vertex +template +FVector3d TMeshSimplification::GetProjectedCollapsePosition(int vid, const FVector3d& vNewPos) +{ + if (Constraints != nullptr) + { + FVertexConstraint vc = Constraints->GetVertexConstraint(vid); + if (vc.Target != nullptr) + { + return vc.Target->Project(vNewPos, vid); + } + if (vc.Fixed) + { + return vNewPos; + } + } + // no constraint applied, so if we have a target surface, project to that + if (EnableInlineProjection() && ProjTarget != nullptr) + { + if (VertexControlF == nullptr || ((int)VertexControlF(vid) & (int)EVertexControl::NoProject) == 0) + { + return ProjTarget->Project(vNewPos, vid); + } + } + return vNewPos; +} + + +// Custom behavior for FAttrBasedQuadric simplifier. +template<> +void TMeshSimplification::OnEdgeCollapse(int edgeID, int va, int vb, const FDynamicMesh3::FEdgeCollapseInfo& collapseInfo) +{ + + // Update the normal + FAttrBasedQuadricErrord& Quadric = EdgeQuadrics[edgeID].q; + FVector3d collapse_pt = EdgeQuadrics[edgeID].collapse_pt; + + FVector3d UpdatedNormald; + Quadric.ComputeAttributes(collapse_pt, UpdatedNormald); + + FVector3f UpdatedNormal(UpdatedNormald.X, UpdatedNormald.Y, UpdatedNormald.Z); + UpdatedNormal.Normalize(); + + if (NormalOverlay != nullptr) + { + // Get all the elements associated with this vertex (could be more than one to account for split vertex data) + TArray ElementIdArray; + NormalOverlay->GetVertexElements(va, ElementIdArray); + + // update everyone with the same normal. + for (int ElementId : ElementIdArray) + { + NormalOverlay->SetElement(ElementId, UpdatedNormal); + } + } + else + { + Mesh->SetVertexNormal(va, UpdatedNormal); + } + +} + + + +// These are explicit instantiations of the templates that are exported from the shared lib. +// Only these instantiations of the template can be used. +// This is necessary because we have placed most of the templated functions in this .cpp file, instead of the header. +template class DYNAMICMESH_API TMeshSimplification< FAttrBasedQuadricErrord >; +template class DYNAMICMESH_API TMeshSimplification< FVolPresQuadricErrord >; +template class DYNAMICMESH_API TMeshSimplification< FQuadricErrord >; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshTangents.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshTangents.cpp new file mode 100644 index 000000000000..c4948e374d12 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshTangents.cpp @@ -0,0 +1,76 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshTangents.h" +#include "Async/ParallelFor.h" + + +template +void TMeshTangents::SetTangentCount(int Count, bool bClearToZero) +{ + if (Tangents.Num() < Count) + { + Tangents.SetNum(Count); + } + if (Bitangents.Num() < Count) + { + Bitangents.SetNum(Count); + } + if (bClearToZero) + { + for (int i = 0; i < Count; ++i) + { + Tangents[i] = FVector3::Zero(); + Bitangents[i] = FVector3::Zero(); + } + } +} + + + +template +void TMeshTangents::Internal_ComputePerTriangleTangents(const FDynamicMeshNormalOverlay* NormalOverlay, const FDynamicMeshUVOverlay* UVOverlay) +{ + int MaxTriangleID = Mesh->MaxTriangleID(); + InitializePerTriangleTangents(false); + + ParallelFor(MaxTriangleID, [this, NormalOverlay, UVOverlay](int TriangleID) + { + if (Mesh->IsTriangle(TriangleID) == false) + { + return; + } + + FVector3d TriVertices[3]; + Mesh->GetTriVertices(TriangleID, TriVertices[0], TriVertices[1], TriVertices[2]); + FVector2f TriUVs[3]; + UVOverlay->GetTriElements(TriangleID, TriUVs[0], TriUVs[1], TriUVs[2]); + + for (int j = 0; j < 3; ++j) + { + FVector3d DPosition1 = TriVertices[(j+1)%3] - TriVertices[j]; + FVector3d DPosition2 = TriVertices[(j+2)%3] - TriVertices[j]; + FVector2d DUV1 = (FVector2d)TriUVs[(j+1)%3] - (FVector2d)TriUVs[j]; + FVector2d DUV2 = (FVector2d)TriUVs[(j+2)%3] - (FVector2d)TriUVs[j]; + + //@todo handle degenerate edges + + double DetUV = DUV1.Cross(DUV2); + double InvDetUV = (DetUV == 0.0f) ? 0.0f : 1.0 / DetUV; + FVector3d Tangent = (DPosition1 * DUV2.Y - DPosition2 * DUV1.Y) * InvDetUV; + Tangent.Normalize(); + Tangents[3*TriangleID + j] = (FVector3)Tangent; + + FVector3d Bitangent = (DPosition2 * DUV1.X - DPosition1 * DUV2.X) * InvDetUV; + Bitangent.Normalize(); + Bitangents[3*TriangleID + j] = (FVector3)Bitangent; + } + }); +} + + +template class DYNAMICMESH_API TMeshTangents; +template class DYNAMICMESH_API TMeshTangents; + + + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshWeights.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshWeights.cpp new file mode 100644 index 000000000000..d5b1cda8d757 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshWeights.cpp @@ -0,0 +1,147 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshWeights.h" +#include "VectorUtil.h" + + +FVector3d FMeshWeights::UniformCentroid(const FDynamicMesh3 & mesh, int VertexIndex) +{ + FVector3d Centroid; + mesh.GetVtxOneRingCentroid(VertexIndex, Centroid); + return Centroid; +} + + +FVector3d FMeshWeights::MeanValueCentroid(const FDynamicMesh3 & mesh, int v_i) +{ + // based on equations in https://www.inf.usi.ch/hormann/papers/Floater.2006.AGC.pdf (formula 9) + // refer to that paper for variable names/etc + + FVector3d vSum = FVector3d::Zero(); + double wSum = 0; + FVector3d Vi = mesh.GetVertex(v_i); + + int v_j = FDynamicMesh3::InvalidID, opp_v1 = FDynamicMesh3::InvalidID, opp_v2 = FDynamicMesh3::InvalidID; + int t1 = FDynamicMesh3::InvalidID, t2 = FDynamicMesh3::InvalidID; + for (int eid : mesh.VtxEdgesItr(v_i) ) + { + opp_v2 = FDynamicMesh3::InvalidID; + mesh.GetVtxNbrhood(eid, v_i, v_j, opp_v1, opp_v2, t1, t2); + + FVector3d Vj = mesh.GetVertex(v_j); + FVector3d vVj = (Vj - Vi); + double len_vVj = vVj.Normalize(); + // [RMS] is this the right thing to do? if vertices are coincident, + // weight of this vertex should be very high! + if (len_vVj < FMathd::ZeroTolerance) + continue; + FVector3d vVdelta = (mesh.GetVertex(opp_v1) - Vi).Normalized(); + double w_ij = VectorUtil::VectorTanHalfAngle(vVj, vVdelta); + + if (opp_v2 != FDynamicMesh3::InvalidID) { + FVector3d vVgamma = (mesh.GetVertex(opp_v2) - Vi).Normalized(); + w_ij += VectorUtil::VectorTanHalfAngle(vVj, vVgamma); + } + + w_ij /= len_vVj; + + vSum += w_ij * Vj; + wSum += w_ij; + } + if (wSum < FMathd::ZeroTolerance) + return Vi; + return vSum / wSum; +} + + + + +FVector3d FMeshWeights::CotanCentroid(const FDynamicMesh3& mesh, int v_i) +{ + // based on equations in http://www.geometry.caltech.edu/pubs/DMSB_III.pdf + + FVector3d vSum = FVector3d::Zero(); + double wSum = 0; + FVector3d Vi = mesh.GetVertex(v_i); + + int v_j = FDynamicMesh3::InvalidID, opp_v1 = FDynamicMesh3::InvalidID, opp_v2 = FDynamicMesh3::InvalidID; + int t1 = FDynamicMesh3::InvalidID, t2 = FDynamicMesh3::InvalidID; + bool bAborted = false; + for (int eid : mesh.VtxEdgesItr(v_i)) + { + opp_v2 = FDynamicMesh3::InvalidID; + mesh.GetVtxNbrhood(eid, v_i, v_j, opp_v1, opp_v2, t1, t2); + FVector3d Vj = mesh.GetVertex(v_j); + + FVector3d Vo1 = mesh.GetVertex(opp_v1); + double cot_alpha_ij = VectorUtil::VectorCot( + (Vi - Vo1).Normalized(), (Vj - Vo1).Normalized()); + if (cot_alpha_ij == 0) { + bAborted = true; + break; + } + double w_ij = cot_alpha_ij; + + if (opp_v2 != FDynamicMesh3::InvalidID) { + FVector3d Vo2 = mesh.GetVertex(opp_v2); + double cot_beta_ij = VectorUtil::VectorCot( + (Vi - Vo2).Normalized(), (Vj - Vo2).Normalized()); + if (cot_beta_ij == 0) { + bAborted = true; + break; + } + w_ij += cot_beta_ij; + } + + vSum += w_ij * Vj; + wSum += w_ij; + } + if (bAborted || fabs(wSum) < FMathd::ZeroTolerance) + return Vi; + return vSum / wSum; +} + + + +double FMeshWeights::VoronoiArea(const FDynamicMesh3& mesh, int v_i) +{ + // based on equations in http://www.geometry.caltech.edu/pubs/DMSB_III.pdf + + double areaSum = 0; + FVector3d Vi = mesh.GetVertex(v_i); + + for (int tid : mesh.VtxTrianglesItr(v_i) ) + { + FIndex3i t = mesh.GetTriangle(tid); + int ti = (t[0] == v_i) ? 0 : ((t[1] == v_i) ? 1 : 2); + FVector3d Vj = mesh.GetVertex(t[(ti + 1) % 3]); + FVector3d Vk = mesh.GetVertex(t[(ti + 2) % 3]); + + if (VectorUtil::IsObtuse(Vi, Vj, Vk)) + { + // if triangle is obtuse voronoi area is undefind and we just return portion of triangle area + FVector3d Vij = Vj - Vi; + FVector3d Vik = Vk - Vi; + Vij.Normalize(); Vik.Normalize(); + double areaT = 0.5 * Vij.Cross(Vik).Length(); + areaSum += ( Vij.AngleR(Vik) > FMathd::HalfPi ) ? // obtuse at v_i ? + (areaT * 0.5) : (areaT * 0.25); + + } + else + { + // voronoi area + FVector3d Vji = Vi - Vj; + double dist_ji = Vji.Normalize(); + FVector3d Vki = Vi - Vk; + double dist_ki = Vki.Normalize(); + FVector3d Vkj = (Vj - Vk).Normalized(); + + double cot_alpha_ij = VectorUtil::VectorCot(Vki, Vkj); + double cot_alpha_ik = VectorUtil::VectorCot(Vji, -Vkj); + areaSum += dist_ji * dist_ji * cot_alpha_ij * 0.125; + areaSum += dist_ki * dist_ki * cot_alpha_ik * 0.125; + } + } + return areaSum; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/ExtrudeMesh.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/ExtrudeMesh.cpp new file mode 100644 index 000000000000..ca9a2e0f7f4a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/ExtrudeMesh.cpp @@ -0,0 +1,131 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Operations/ExtrudeMesh.h" +#include "MeshNormals.h" +#include "DynamicMeshEditor.h" + + +FExtrudeMesh::FExtrudeMesh(FDynamicMesh3* mesh) : Mesh(mesh) +{ + ExtrudedPositionFunc = [this](const FVector3d& Position, const FVector3f& Normal, int VertexID) + { + return Position + this->DefaultExtrudeDistance * Normal; + }; +} + + +bool FExtrudeMesh::Apply() +{ + //@todo should apply per-connected-component? will then handle bowties properly + + FMeshNormals Normals; + bool bHaveVertexNormals = Mesh->HasVertexNormals(); + if (!bHaveVertexNormals) + { + Normals = FMeshNormals(Mesh); + Normals.ComputeVertexNormals(); + } + + InitialLoops.SetMesh(Mesh); + InitialLoops.Compute(); + int NumInitialLoops = InitialLoops.GetLoopCount(); + + BufferUtil::AppendElements(InitialTriangles, Mesh->TriangleIndicesItr()); + BufferUtil::AppendElements(InitialVertices, Mesh->VertexIndicesItr()); + + // duplicate triangles of mesh + + FDynamicMeshEditor Editor(Mesh); + + FMeshIndexMappings IndexMap; + FDynamicMeshEditResult DuplicateResult; + Editor.DuplicateTriangles(InitialTriangles, IndexMap, DuplicateResult); + OffsetTriangles = DuplicateResult.NewTriangles; + OffsetTriGroups = DuplicateResult.NewGroups; + InitialToOffsetMapV = IndexMap.GetVertexMap().GetForwardMap(); + + // set vertices to new positions + for (int vid : InitialVertices) + { + int newvid = InitialToOffsetMapV[vid]; + if ( ! Mesh->IsVertex(newvid) ) + { + continue; + } + + FVector3d v = Mesh->GetVertex(vid); + FVector3f n = (bHaveVertexNormals) ? Mesh->GetVertexNormal(vid) : (FVector3f)Normals[vid]; + FVector3d newv = ExtrudedPositionFunc(v, n, vid); + + Mesh->SetVertex(newvid, newv); + } + + // we need to reverse one side + if (IsPositiveOffset) + { + Editor.ReverseTriangleOrientations(InitialTriangles, true); + } + else + { + Editor.ReverseTriangleOrientations(OffsetTriangles, true); + } + + // stitch each loop + NewLoops.SetNum(NumInitialLoops); + StitchTriangles.SetNum(NumInitialLoops); + StitchPolygonIDs.SetNum(NumInitialLoops); + int LoopIndex = 0; + for (FEdgeLoop& BaseLoop : InitialLoops.Loops) + { + int LoopCount = BaseLoop.GetVertexCount(); + + TArray OffsetLoop; + OffsetLoop.SetNum(LoopCount); + for (int k = 0; k < LoopCount; ++k) + { + OffsetLoop[k] = InitialToOffsetMapV[BaseLoop.Vertices[k]]; + } + + FDynamicMeshEditResult StitchResult; + if (IsPositiveOffset) + { + Editor.StitchVertexLoopsMinimal(OffsetLoop, BaseLoop.Vertices, StitchResult); + } + else + { + Editor.StitchVertexLoopsMinimal(BaseLoop.Vertices, OffsetLoop, StitchResult); + } + StitchResult.GetAllTriangles(StitchTriangles[LoopIndex]); + StitchPolygonIDs[LoopIndex] = StitchResult.NewGroups; + + // for each polygon we created in stitch, set UVs and normals + if (Mesh->HasAttributes()) + { + int NumNewQuads = StitchResult.NewQuads.Num(); + for (int k = 0; k < NumNewQuads; k++) + { + FVector3f Normal = Editor.ComputeAndSetQuadNormal(StitchResult.NewQuads[k], true); + + // @todo is there a simpler way to construct rotation from 3 known axes (third id Normal.Cross(UnitY)) + // (converting from matrix might end up being more efficient due to trig in ConstrainedAlignAxis?) + FFrame3f ProjectFrame(FVector3f::Zero(), Normal); + if (FMathd::Abs(ProjectFrame.Y().Dot(FVector3f::UnitY())) < 0.01) + { + ProjectFrame.ConstrainedAlignAxis(0, FVector3f::UnitX(), ProjectFrame.Z()); + } + else + { + ProjectFrame.ConstrainedAlignAxis(1, FVector3f::UnitY(), ProjectFrame.Z()); + } + Editor.SetQuadUVsFromProjection(StitchResult.NewQuads[k], ProjectFrame, UVScaleFactor); + } + } + + NewLoops[LoopIndex].InitializeFromVertices(Mesh, OffsetLoop); + LoopIndex++; + } + + return true; +} + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/GroupTopologyDeformer.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/GroupTopologyDeformer.cpp new file mode 100644 index 000000000000..7c9fee07c7d0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/GroupTopologyDeformer.cpp @@ -0,0 +1,309 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Operations/GroupTopologyDeformer.h" +#include "DynamicMesh3.h" +#include "GroupTopology.h" +#include "SegmentTypes.h" +#include "Async/ParallelFor.h" +#include "Containers/BitArray.h" + + +void FGroupTopologyDeformer::Initialize(const FDynamicMesh3* MeshIn, const FGroupTopology* TopologyIn) +{ + Mesh = MeshIn; + Topology = TopologyIn; +} + + + +void FGroupTopologyDeformer::Reset() +{ + HandleVertices.Reset(); + HandleBoundaryVertices.Reset(); + FixedBoundaryVertices.Reset(); + ROIEdgeVertices.Reset(); + ROIFaces.Reset(); + InitialPositions.Reset(); + ROIEdges.Reset(); + + EdgeEncodings.Reset(); + FaceEncodings.Reset(); +} + + +void FGroupTopologyDeformer::SetActiveHandleFaces(const TArray& FaceGroupIDs) +{ + Reset(); + + check(FaceGroupIDs.Num() == 1); // multi-face not supported yet + int GroupID = FaceGroupIDs[0]; + + // find set of vertices in handle + Topology->CollectGroupVertices(GroupID, HandleVertices); + Topology->CollectGroupBoundaryVertices(GroupID, HandleBoundaryVertices); + ModifiedVertices = HandleVertices; + + // find neighbour group set + TArray HandleGroups = FaceGroupIDs; + const TArray& GroupNbrGroups = Topology->GetGroupNbrGroups(GroupID); + CalculateROI(HandleGroups, GroupNbrGroups); + + SaveInitialPositions(); + + ComputeEncoding(); +} + + + +void FGroupTopologyDeformer::SetActiveHandleEdges(const TArray& TopologyEdgeIDs) +{ + Reset(); + + for (int EdgeID : TopologyEdgeIDs) + { + const TArray& EdgeVerts = Topology->GetGroupEdgeVertices(EdgeID); + for (int VertID : EdgeVerts) + { + HandleVertices.Add(VertID); + } + } + HandleBoundaryVertices = HandleVertices; + ModifiedVertices = HandleVertices; + + TArray HandleGroups; + TArray NbrGroups; + Topology->FindEdgeNbrGroups(TopologyEdgeIDs, NbrGroups); + CalculateROI(HandleGroups, NbrGroups); + + SaveInitialPositions(); + + ComputeEncoding(); +} + + +void FGroupTopologyDeformer::SetActiveHandleCorners(const TArray& CornerIDs) +{ + Reset(); + + for (int CornerID : CornerIDs) + { + int VertID = Topology->GetCornerVertexID(CornerID); + if (VertID >= 0) + { + HandleVertices.Add(VertID); + } + } + HandleBoundaryVertices = HandleVertices; + ModifiedVertices = HandleVertices; + + TArray HandleGroups; + TArray NbrGroups; + Topology->FindCornerNbrGroups(CornerIDs, NbrGroups); + CalculateROI(HandleGroups, NbrGroups); + + SaveInitialPositions(); + + ComputeEncoding(); +} + + + + +void FGroupTopologyDeformer::SaveInitialPositions() +{ + for (int VertID : ModifiedVertices) + { + InitialPositions.AddVertex(Mesh, VertID); + } +} + + + +void FGroupTopologyDeformer::CalculateROI(const TArray& HandleGroups, const TArray& ROIGroups) +{ + // sort ROI edges into various sets + // HandleBoundaryVertices: all vertices on border of handle + // FixedBoundaryVertices: all vertices on fixed border (ie part of edges not connected to handle) + // ROIEdges: list of edge data structures for edges we will deform + ROIEdges.Reserve(ROIGroups.Num() * 5); // guesstimate + Topology->ForGroupSetEdges(ROIGroups, [this, &HandleGroups](const FGroupTopology::FGroupEdge& Edge, int EdgeIndex) + { + if (HandleGroups.Contains(Edge.Groups.A) || HandleGroups.Contains(Edge.Groups.B)) + { + return; // this is a Handle boundary edge + } + + bool bIsConnectedToHandle = Edge.IsConnectedToVertices(HandleBoundaryVertices); + if (bIsConnectedToHandle) + { + for (int VertID : Edge.Span.Vertices) + { + ROIEdgeVertices.Add(VertID); + } + FROIEdge& ROIEdge = ROIEdges[ROIEdges.Add(FROIEdge())]; + ROIEdge.EdgeIndex = EdgeIndex; + ROIEdge.Span = Edge.Span; + if (HandleBoundaryVertices.Contains(ROIEdge.Span.Vertices[0]) == false) + { + ROIEdge.Span.Reverse(); + } + } + else + { + for (int VertID : Edge.Span.Vertices) + { + FixedBoundaryVertices.Add(VertID); + } + } + }); + + + // create ROI faces + ROIFaces.SetNum(ROIGroups.Num()); + int FaceIdx = 0; + for (int NbrGroupID : ROIGroups) + { + FaceVertsTemp.Reset(); + FaceBoundaryVertsTemp.Reset(); + Topology->CollectGroupVertices(NbrGroupID, FaceVertsTemp); + Topology->CollectGroupBoundaryVertices(NbrGroupID, FaceBoundaryVertsTemp); + + FROIFace& Face = ROIFaces[FaceIdx++]; + for (int vid : FaceVertsTemp) + { + TArray& AddTo = (FaceBoundaryVertsTemp.Contains(vid)) ? Face.BoundaryVerts : Face.InteriorVerts; + AddTo.Add(vid); + ModifiedVertices.Add(vid); + } + } +} + + + + + + +void FGroupTopologyDeformer::ComputeEncoding() +{ + // encode ROI edges + int NumROIEdges = ROIEdges.Num(); + EdgeEncodings.SetNum(NumROIEdges); + for (int ei = 0; ei < NumROIEdges; ei++) + { + FEdgeSpan& Span = ROIEdges[ei].Span; + int NumVerts = Span.Vertices.Num(); + + FEdgeEncoding& Encoding = EdgeEncodings[ei]; + Encoding.Vertices.SetNum(NumVerts); + + FVector3d StartV = Mesh->GetVertex(Span.Vertices[0]); + FVector3d EndV = Mesh->GetVertex(Span.Vertices[NumVerts - 1]); + FSegment3d Seg(StartV, EndV); + + for (int k = 1; k < NumVerts - 1; ++k) + { + FVector3d Pos = Mesh->GetVertex(Span.Vertices[k]); + FEdgeVertexEncoding& Enc = Encoding.Vertices[k]; + Enc.T = Seg.ProjectUnitRange(Pos); + //Enc.T *= Enc.T; + //Enc.T = FMathd::Sqrt(Enc.T); + Enc.Delta = Pos - Seg.PointBetween(Enc.T); + } + } + + // encode ROI faces + int NumROIFaces = ROIFaces.Num(); + FaceEncodings.SetNum(NumROIFaces); + for (int fi = 0; fi < NumROIFaces; ++fi) + { + FROIFace& Face = ROIFaces[fi]; + int NumBoundaryV = Face.BoundaryVerts.Num(); + int NumInteriorV = Face.InteriorVerts.Num(); + FFaceEncoding& Encoding = FaceEncodings[fi]; + Encoding.Vertices.SetNum(NumInteriorV); + for (int vi = 0; vi < NumInteriorV; ++vi) + { + FVector3d Pos = Mesh->GetVertex(Face.InteriorVerts[vi]); + FFaceVertexEncoding& VtxEncoding = Encoding.Vertices[vi]; + VtxEncoding.Weights.SetNum(NumBoundaryV); + VtxEncoding.Deltas.SetNum(NumBoundaryV); + double WeightSum = 0; + for (int k = 0; k < NumBoundaryV; ++k) + { + FVector3d BorderPos = Mesh->GetVertex(Face.BoundaryVerts[k]); + FVector3d DeltaVec = Pos - BorderPos; + double Weight = 1.0 / DeltaVec.SquaredLength(); + VtxEncoding.Deltas[k] = DeltaVec; + VtxEncoding.Weights[k] = Weight; + WeightSum += Weight; + } + FVector3d Reconstruct(0, 0, 0); + for (int k = 0; k < NumBoundaryV; ++k) + { + VtxEncoding.Weights[k] /= WeightSum; + Reconstruct += VtxEncoding.Weights[k] * (Mesh->GetVertex(Face.BoundaryVerts[k]) + VtxEncoding.Deltas[k]); + } + check(Reconstruct.Distance(Pos) < 0.0001); + } + } +} + + + +void FGroupTopologyDeformer::ClearSolution(FDynamicMesh3* TargetMesh) +{ + InitialPositions.SetPositions(TargetMesh); +} + + +void FGroupTopologyDeformer::UpdateSolution(FDynamicMesh3* TargetMesh, const TFunction& HandleVertexDeformFunc) +{ + InitialPositions.SetPositions(TargetMesh); + + for (int VertIdx : HandleVertices) + { + FVector3d DeformPos = HandleVertexDeformFunc(TargetMesh, VertIdx); + TargetMesh->SetVertex(VertIdx, DeformPos); + } + + // reconstruct edges + int NumEdges = ROIEdges.Num(); + for (int ei = 0; ei < NumEdges; ++ei) + { + const FEdgeSpan& Span = ROIEdges[ei].Span; + const FEdgeEncoding& Encoding = EdgeEncodings[ei]; + int NumVerts = Span.Vertices.Num(); + FVector3d A = TargetMesh->GetVertex(Span.Vertices[0]); + FVector3d B = TargetMesh->GetVertex(Span.Vertices[NumVerts - 1]); + FSegment3d Seg(A, B); + for (int k = 1; k < NumVerts - 1; ++k) + { + FVector3d NewPos = Seg.PointBetween(Encoding.Vertices[k].T); + NewPos += Encoding.Vertices[k].Delta; + TargetMesh->SetVertex(Span.Vertices[k], NewPos); + } + } + + // reconstruct faces + int NumFaces = ROIFaces.Num(); + ParallelFor(NumFaces, [this, &TargetMesh](int FaceIdx) { + const FROIFace& Face = ROIFaces[FaceIdx]; + const FFaceEncoding& Encoding = FaceEncodings[FaceIdx]; + int NumBorder = Face.BoundaryVerts.Num(); + int NumVerts = Face.InteriorVerts.Num(); + for (int vi = 0; vi < NumVerts; ++vi) + { + const FFaceVertexEncoding& VtxEncoding = Encoding.Vertices[vi]; + FVector3d Sum(0, 0, 0); + for (int k = 0; k < NumBorder; ++k) + { + Sum += VtxEncoding.Weights[k] * + (TargetMesh->GetVertex(Face.BoundaryVerts[k]) + VtxEncoding.Deltas[k]); + } + TargetMesh->SetVertex(Face.InteriorVerts[vi], Sum); + } + }); +} + + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/MergeCoincidentMeshEdges.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/MergeCoincidentMeshEdges.cpp new file mode 100644 index 000000000000..39fa4359b504 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/MergeCoincidentMeshEdges.cpp @@ -0,0 +1,198 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "Operations/MergeCoincidentMeshEdges.h" +#include "DynamicMesh3.h" +#include "MeshAdapterUtil.h" +#include "Spatial/PointSetHashTable.h" +#include "Util/IndexPriorityQueue.h" +#include "Util/IndexUtil.h" + + +const double FMergeCoincidentMeshEdges::DEFAULT_TOLERANCE = FMathf::ZeroTolerance; + + +bool FMergeCoincidentMeshEdges::Apply() +{ + MergeVtxDistSqr = MergeVertexTolerance * MergeVertexTolerance; + double UseMergeSearchTol = (MergeSearchTolerance > 0) ? MergeSearchTolerance : 2*MergeVertexTolerance; + + // + // construct hash table for edge midpoints + // + + FPointSetAdapterd EdgeMidpoints = MeshAdapterUtil::MakeBoundaryEdgeMidpointsAdapter(Mesh); + FPointSetHashtable MidpointsHash(&EdgeMidpoints); + + // use denser grid as vertex count increases + int hashN = 64; + if (Mesh->TriangleCount() > 100000) hashN = 128; + if (Mesh->TriangleCount() > 1000000) hashN = 256; + FAxisAlignedBox3d bounds = Mesh->GetCachedBounds(); + MidpointsHash.Build(bounds.MaxDim() / hashN, bounds.Min); + + // temp values and buffers + FVector3d A, B, C, D; + TArray equivBuffer; + TArray SearchMatches; + SearchMatches.SetNum(1024); SearchMatches.Reset(); // allocate buffer + + // + // construct edge equivalence sets. First we find all other edges with same + // midpoint, and then we form equivalence set for edge from subset that also + // has same endpoints + // + + typedef TArray EdgesList; + TArray EquivalenceSets; + EquivalenceSets.Init(nullptr, Mesh->MaxEdgeID()); + TSet RemainingEdges; + + // @todo equivalence sets should be symmetric. this neither enforces that, + // nor takes advantage of it. + for (int eid : Mesh->BoundaryEdgeIndicesItr()) + { + FVector3d midpt = Mesh->GetEdgePoint(eid, 0.5); + + // find all other edges with same midpoint in query sphere + SearchMatches.Reset(); + MidpointsHash.FindPointsInBall(midpt, UseMergeSearchTol, SearchMatches); + + int N = SearchMatches.Num(); + if (N == 1 && SearchMatches[0] != eid) + { + check(false); // how could this happen?! + } + if (N <= 1) + { + continue; // edge has no matches + } + + Mesh->GetEdgeV(eid, A, B); + + // if same endpoints, add to equivalence set for this edge + equivBuffer.Reset(); + for (int i = 0; i < N; ++i) + { + if (SearchMatches[i] != eid) + { + Mesh->GetEdgeV(SearchMatches[i], C, D); + if ( IsSameEdge(A, B, C, D) ) + { + equivBuffer.Add(SearchMatches[i]); + } + } + } + if (equivBuffer.Num() > 0) + { + EquivalenceSets[eid] = new EdgesList(equivBuffer); + RemainingEdges.Add(eid); + } + } + + + // + // add potential duplicate edges to priority queue, sorted by number of possible matches. + // + + // [TODO] could replace remaining hashset w/ PQ, and use conservative count? + // [TODO] Does this need to be a PQ? Not updating PQ below anyway... + FIndexPriorityQueue DuplicatesQueue; + DuplicatesQueue.Initialize(Mesh->MaxEdgeID()); + for (int eid : RemainingEdges) + { + if (OnlyUniquePairs) + { + if (EquivalenceSets[eid]->Num() != 1) + { + continue; + } + + // check that reverse match is the same and unique + int other_eid = (*EquivalenceSets[eid])[0]; + if (EquivalenceSets[other_eid]->Num() != 1 || (*EquivalenceSets[other_eid])[0] != eid) + { + continue; + } + } + + DuplicatesQueue.Insert(eid, EquivalenceSets[eid]->Num()); + } + + // + // process all potential matches, merging edges as we go in a greedy fashion. + // + + while (DuplicatesQueue.GetCount() > 0) + { + int eid = DuplicatesQueue.Dequeue(); + + if (Mesh->IsEdge(eid) == false || EquivalenceSets[eid] == nullptr || RemainingEdges.Contains(eid) == false) + { + continue; // dealt with this edge already + } + if (Mesh->IsBoundaryEdge(eid) == false) + { + continue; // this edge got merged already + } + + EdgesList& Matches = *EquivalenceSets[eid]; + + // select best viable match (currently just "first"...) + // @todo could we make better decisions here? prefer planarity? + bool bMerged = false; + int FailedCount = 0; + for (int i = 0; i < Matches.Num() && bMerged == false; ++i) + { + int other_eid = Matches[i]; + if (Mesh->IsEdge(other_eid) == false || Mesh->IsBoundaryEdge(other_eid) == false) + { + continue; + } + + FDynamicMesh3::FMergeEdgesInfo mergeInfo; + EMeshResult result = Mesh->MergeEdges(eid, other_eid, mergeInfo); + if (result != EMeshResult::Ok) + { + // if the operation failed we remove this edge from the equivalence set + Matches.RemoveAt(i); + i--; + + EquivalenceSets[other_eid]->Remove(eid); + //DuplicatesQueue.UpdatePriority(...); // should we do this? + + FailedCount++; + } + else + { + // ok we merged, other edge is no longer free + bMerged = true; + delete EquivalenceSets[other_eid]; + EquivalenceSets[other_eid] = nullptr; + RemainingEdges.Remove(other_eid); + } + } + + // Removing branch with two identical cases to fix static analysis warning. + // However, these two branches are *not* the same...we're just not sure + // what the right thing to do is in the else case + //if (bMerged) + //{ + delete EquivalenceSets[eid]; + EquivalenceSets[eid] = nullptr; + RemainingEdges.Remove(eid); + //} + //else + //{ + // // should we do something else here? doesn't make sense to put + // // back into Q, as it should be at the top, right? + // delete EquivalenceSets[eid]; + // EquivalenceSets[eid] = nullptr; + // RemainingEdges.Remove(eid); + //} + + } + + return true; +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Remesher.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Remesher.cpp new file mode 100644 index 000000000000..8041b4742468 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Remesher.cpp @@ -0,0 +1,638 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Remesher.h" +#include "DynamicMeshAttributeSet.h" +#include "MeshWeights.h" + + + + +void FRemesher::SetTargetEdgeLength(double fLength) +{ + // from Botsch paper + //MinEdgeLength = fLength * (4.0/5.0); + //MaxEdgeLength = fLength * (4.0/3.0); + // much nicer!! makes sense as when we split, edges are both > min ! + MinEdgeLength = fLength * 0.66; + MaxEdgeLength = fLength * 1.33; +} + + + +void FRemesher::Precompute() +{ + // if we know Mesh is closed, we can skip is-boundary checks, which makes + // the flip-valence tests much faster! + bMeshIsClosed = true; + for (int eid : Mesh->EdgeIndicesItr()) + { + if (Mesh->IsBoundaryEdge(eid)) + { + bMeshIsClosed = false; + break; + } + } +} + + + + + +void FRemesher::BasicRemeshPass() +{ + if (Mesh->TriangleCount() == 0) // badness if we don't catch this... + { + return; + } + + ProfileBeginPass(); + + // Iterate over all edges in the mesh at start of pass. + // Some may be removed, so we skip those. + // However, some old eid's may also be re-used, so we will touch + // some new edges. Can't see how we could efficiently prevent this. + // + ProfileBeginOps(); + + int cur_eid = StartEdges(); + bool done = false; + ModifiedEdgesLastPass = 0; + do + { + if (Mesh->IsEdge(cur_eid)) + { + EProcessResult result = ProcessEdge(cur_eid); + if (result == EProcessResult::Ok_Collapsed || result == EProcessResult::Ok_Flipped || result == EProcessResult::Ok_Split) + { + ModifiedEdgesLastPass++; + } + } + if (Cancelled()) // expensive to check every iter? + { + return; + } + cur_eid = GetNextEdge(cur_eid, done); + } while (done == false); + ProfileEndOps(); + + if (Cancelled()) + { + return; + } + + ProfileBeginSmooth(); + if (bEnableSmoothing && SmoothSpeedT > 0) + { + if (bEnableSmoothInPlace) + { + //FullSmoothPass_InPlace(EnableParallelSmooth); + check(false); + } + else + { + FullSmoothPass_Buffer(bEnableParallelSmooth); + } + DoDebugChecks(); + } + ProfileEndSmooth(); + + if (Cancelled()) + { + return; + } + + ProfileBeginProject(); + if (ProjTarget != nullptr && ProjectionMode == ETargetProjectionMode::AfterRefinement) + { + FullProjectionPass(); + DoDebugChecks(); + } + ProfileEndProject(); + + DoDebugChecks(true); + + if (Cancelled()) + { + return; + } + + ProfileEndPass(); +} + + + + + +FRemesher::EProcessResult FRemesher::ProcessEdge(int edgeID) +{ + RuntimeDebugCheck(edgeID); + + FEdgeConstraint constraint = + (Constraints == nullptr) ? FEdgeConstraint::Unconstrained() : Constraints->GetEdgeConstraint(edgeID); + if (constraint.NoModifications()) + { + return EProcessResult::Ignored_EdgeIsFullyConstrained; + } + + // look up verts and tris for this edge + if (Mesh->IsEdge(edgeID) == false) + { + return EProcessResult::Failed_NotAnEdge; + } + FIndex4i edge_info = Mesh->GetEdge(edgeID); + int a = edge_info.A, b = edge_info.B, t0 = edge_info.C, t1 = edge_info.D; + bool bIsBoundaryEdge = (t1 == IndexConstants::InvalidID); + + // look up 'other' verts c (from t0) and d (from t1, if it exists) + FIndex2i ov = Mesh->GetEdgeOpposingV(edgeID); + int c = ov[0], d = ov[1]; + + FVector3d vA = Mesh->GetVertex(a); + FVector3d vB = Mesh->GetVertex(b); + double edge_len_sqr = (vA - vB).SquaredLength(); + + ProfileBeginCollapse(); + + // check if we should collapse, and also find which vertex we should collapse to, + // in cases where we have constraints/etc + int collapse_to = -1; + bool bCanCollapse = bEnableCollapses + && constraint.CanCollapse() + && edge_len_sqr < MinEdgeLength*MinEdgeLength + && CanCollapseEdge(edgeID, a, b, c, d, t0, t1, collapse_to); + + // optimization: if edge cd exists, we cannot collapse or flip. look that up here? + // funcs will do it internally... + // (or maybe we can collapse if cd exists? edge-collapse doesn't check for it explicitly...) + + // if edge length is too short, we want to collapse it + bool bTriedCollapse = false; + if (bCanCollapse) + { + int iKeep = b, iCollapse = a; + double collapse_t = 0.5; // need to know t-value along edge to update lerpable attributes properly + FVector3d vNewPos = (vA + vB) * collapse_t; + + // if either vtx is fixed, collapse to that position + if (collapse_to == b) + { + collapse_t = 0; + vNewPos = vB; + } + else if (collapse_to == a) + { + iKeep = a; iCollapse = b; + collapse_t = 0; + vNewPos = vA; + } + else + { + vNewPos = GetProjectedCollapsePosition(iKeep, vNewPos); + double div = vA.Distance(vB); + collapse_t = (div < FMathd::ZeroTolerance) ? 0.5 : (vNewPos.Distance(Mesh->GetVertex(iKeep))) / div; + collapse_t = VectorUtil::Clamp(collapse_t, 0.0, 1.0); + } + + // if new position would flip normal of one of the existing triangles + // either one-ring, don't allow it + if (bPreventNormalFlips) + { + if (CheckIfCollapseCreatesFlipOrInvalid(a, b, vNewPos, t0, t1) || CheckIfCollapseCreatesFlipOrInvalid(b, a, vNewPos, t0, t1)) + { + goto abort_collapse; + } + } + + // lots of cases where we cannot collapse, but we should just let + // mesh sort that out, right? + COUNT_COLLAPSES++; + FDynamicMesh3::FEdgeCollapseInfo collapseInfo; + EMeshResult result = Mesh->CollapseEdge(iKeep, iCollapse, collapse_t, collapseInfo); + if (result == EMeshResult::Ok) + { + Mesh->SetVertex(iKeep, vNewPos); + if (Constraints != nullptr) + { + Constraints->ClearEdgeConstraint(edgeID); + Constraints->ClearEdgeConstraint(collapseInfo.RemovedEdges.A); + if (collapseInfo.RemovedEdges.B != IndexConstants::InvalidID) + { + Constraints->ClearEdgeConstraint(collapseInfo.RemovedEdges.B); + } + Constraints->ClearVertexConstraint(iCollapse); + } + OnEdgeCollapse(edgeID, iKeep, iCollapse, collapseInfo); + DoDebugChecks(); + + return EProcessResult::Ok_Collapsed; + } + else + { + bTriedCollapse = true; + } + } +abort_collapse: + + ProfileEndCollapse(); + ProfileBeginFlip(); + + // if this is not a boundary edge, maybe we want to flip + bool bTriedFlip = false; + if (bEnableFlips && constraint.CanFlip() && bIsBoundaryEdge == false) + { + // can we do this more efficiently somehow? + bool a_is_boundary_vtx = (bMeshIsClosed) ? false : (bIsBoundaryEdge || Mesh->IsBoundaryVertex(a)); + bool b_is_boundary_vtx = (bMeshIsClosed) ? false : (bIsBoundaryEdge || Mesh->IsBoundaryVertex(b)); + bool c_is_boundary_vtx = (bMeshIsClosed) ? false : Mesh->IsBoundaryVertex(c); + bool d_is_boundary_vtx = (bMeshIsClosed) ? false : Mesh->IsBoundaryVertex(d); + int valence_a = Mesh->GetVtxEdgeCount(a), valence_b = Mesh->GetVtxEdgeCount(b); + int valence_c = Mesh->GetVtxEdgeCount(c), valence_d = Mesh->GetVtxEdgeCount(d); + int valence_a_target = (a_is_boundary_vtx) ? valence_a : 6; + int valence_b_target = (b_is_boundary_vtx) ? valence_b : 6; + int valence_c_target = (c_is_boundary_vtx) ? valence_c : 6; + int valence_d_target = (d_is_boundary_vtx) ? valence_d : 6; + + // if total valence error improves by flip, we want to do it + int curr_err = abs(valence_a - valence_a_target) + abs(valence_b - valence_b_target) + + abs(valence_c - valence_c_target) + abs(valence_d - valence_d_target); + int flip_err = abs((valence_a - 1) - valence_a_target) + abs((valence_b - 1) - valence_b_target) + + abs((valence_c + 1) - valence_c_target) + abs((valence_d + 1) - valence_d_target); + + bool bTryFlip = flip_err < curr_err; + if (bTryFlip && bPreventNormalFlips && CheckIfFlipInvertsNormals(a, b, c, d, t0)) + { + bTryFlip = false; + } + + if (bTryFlip) + { + FDynamicMesh3::FEdgeFlipInfo flipInfo; + COUNT_FLIPS++; + EMeshResult result = Mesh->FlipEdge(edgeID, flipInfo); + if (result == EMeshResult::Ok) + { + DoDebugChecks(); + return EProcessResult::Ok_Flipped; + } + else + { + bTriedFlip = true; + } + + } + } + + ProfileEndFlip(); + ProfileBeginSplit(); + + // if edge length is too long, we want to split it + bool bTriedSplit = false; + if (bEnableSplits && constraint.CanSplit() && edge_len_sqr > MaxEdgeLength*MaxEdgeLength) + { + FDynamicMesh3::FEdgeSplitInfo SplitInfo; + COUNT_SPLITS++; + EMeshResult result = Mesh->SplitEdge(edgeID, SplitInfo); + if (result == EMeshResult::Ok) + { + UpdateAfterSplit(edgeID, a, b, SplitInfo); + OnEdgeSplit(edgeID, a, b, SplitInfo); + DoDebugChecks(); + return EProcessResult::Ok_Split; + } + else + { + bTriedSplit = true; + } + } + + ProfileEndSplit(); + + + if (bTriedFlip || bTriedSplit || bTriedCollapse) + { + return EProcessResult::Failed_OpNotSuccessful; + } + else + { + return EProcessResult::Ignored_EdgeIsFine; + } +} + + + + + + +void FRemesher::UpdateAfterSplit(int edgeID, int va, int vb, const FDynamicMesh3::FEdgeSplitInfo& SplitInfo) +{ + bool bPositionFixed = false; + if (Constraints != nullptr && Constraints->HasEdgeConstraint(edgeID)) + { + // inherit edge constraint + Constraints->SetOrUpdateEdgeConstraint(SplitInfo.NewEdges.A, Constraints->GetEdgeConstraint(edgeID)); + + // [RMS] update vertex constraints. Note that there is some ambiguity here. + // Both verts being constrained doesn't inherently mean that the edge is on + // a constraint, that's why these checks are only applied if edge is constrained. + // But constrained edge doesn't necessarily mean we want to inherit vert constraints!! + // + // although, pretty safe to assume that we would at least disable flips + // if both vertices are constrained to same line/curve. So, maybe this makes sense... + // + // (perhaps edge constraint should be explicitly tagged to resolve this ambiguity??) + + // vert inherits Fixed if both orig edge verts Fixed, and both tagged with same SetID + FVertexConstraint ca = Constraints->GetVertexConstraint(va); + FVertexConstraint cb = Constraints->GetVertexConstraint(vb); + if (ca.Fixed && cb.Fixed) + { + int nSetID = (ca.FixedSetID > 0 && ca.FixedSetID == cb.FixedSetID) ? + ca.FixedSetID : FVertexConstraint::InvalidSetID; + bool bMovable = ca.Movable && cb.Movable; + Constraints->SetOrUpdateVertexConstraint(SplitInfo.NewVertex, + FVertexConstraint(true, bMovable, nSetID)); + bPositionFixed = true; + } + + // vert inherits Target if: + // 1) both source verts and edge have same Target, and is same as edge target + // 2) either vert has same target as edge, and other vert is fixed + if (ca.Target != nullptr || cb.Target != nullptr) + { + IProjectionTarget* edge_target = Constraints->GetEdgeConstraint(edgeID).Target; + IProjectionTarget* set_target = nullptr; + if (ca.Target == cb.Target && ca.Target == edge_target) + { + set_target = edge_target; + } + else if (ca.Target == edge_target && cb.Fixed) + { + set_target = edge_target; + } + else if (cb.Target == edge_target && ca.Fixed) + { + set_target = edge_target; + } + + if (set_target != nullptr) + { + Constraints->SetOrUpdateVertexConstraint(SplitInfo.NewVertex, + FVertexConstraint(set_target)); + ProjectVertex(SplitInfo.NewVertex, set_target); + bPositionFixed = true; + } + } + } + + if (EnableInlineProjection() && bPositionFixed == false && ProjTarget != nullptr) + { + ProjectVertex(SplitInfo.NewVertex, ProjTarget); + } +} + + + +void FRemesher::ProjectVertex(int VertexID, IProjectionTarget* UseTarget) +{ + FVector3d curpos = Mesh->GetVertex(VertexID); + FVector3d projected = UseTarget->Project(curpos, VertexID); + Mesh->SetVertex(VertexID, projected); +} + +// used by collapse-edge to get projected position for new vertex +FVector3d FRemesher::GetProjectedCollapsePosition(int vid, const FVector3d& vNewPos) +{ + if (Constraints != nullptr) + { + FVertexConstraint vc = Constraints->GetVertexConstraint(vid); + if (vc.Target != nullptr) + { + return vc.Target->Project(vNewPos, vid); + } + if (vc.Fixed) + { + return vNewPos; + } + } + // no constraint applied, so if we have a target surface, project to that + if (EnableInlineProjection() && ProjTarget != nullptr) + { + if (VertexControlF == nullptr || ((int)VertexControlF(vid) & (int)EVertexControl::NoProject) == 0) + { + return ProjTarget->Project(vNewPos, vid); + } + } + return vNewPos; +} + + + + +static FVector3d UniformSmooth(const FDynamicMesh3& mesh, int vID, double t) +{ + FVector3d v = mesh.GetVertex(vID); + FVector3d c; + mesh.GetVtxOneRingCentroid(vID, c); + return (1.0 - t)*v + (t)*c; +} + + +static FVector3d MeanValueSmooth(const FDynamicMesh3& mesh, int vID, double t) +{ + FVector3d v = mesh.GetVertex(vID); + FVector3d c = FMeshWeights::MeanValueCentroid(mesh, vID); + return (1.0 - t)*v + (t)*c; +} + +static FVector3d CotanSmooth(const FDynamicMesh3& mesh, int vID, double t) +{ + FVector3d v = mesh.GetVertex(vID); + FVector3d c = FMeshWeights::CotanCentroid(mesh, vID); + return (1.0 - t)*v + (t)*c; +} + +void FRemesher::FullSmoothPass_Buffer(bool bParallel) +{ + InitializeVertexBufferForPass(); + + TFunction UseSmoothFunc = UniformSmooth; + //Func smoothFunc = MeshUtil.UniformSmooth; + if (CustomSmoothF != nullptr) + { + UseSmoothFunc = CustomSmoothF; + } + else + { + if (SmoothType == ESmoothTypes::MeanValue) + { + UseSmoothFunc = MeanValueSmooth; + } + else if (SmoothType == ESmoothTypes::Cotan) + { + UseSmoothFunc = CotanSmooth; + } + } + + auto SmoothAndUpdateFunc = [&](int vID) + { + bool bModified = false; + FVector3d vSmoothed = ComputeSmoothedVertexPos(vID, UseSmoothFunc, bModified); + if (bModified) + { + TempFlagBuffer[vID] = true; + TempPosBuffer[vID] = vSmoothed; + } + }; + + //if (bParallel) { + // gParallel.ForEach(smooth_vertices(), smooth); + //} else { + // foreach (int vID in smooth_vertices()) + // smooth(vID); + //} + ApplyToSmoothVertices(SmoothAndUpdateFunc); + + ApplyVertexBuffer(bParallel); +} + + + + + + +void FRemesher::InitializeVertexBufferForPass() +{ + if ((int)TempPosBuffer.GetLength() < Mesh->MaxVertexID()) + { + TempPosBuffer.Resize(Mesh->MaxVertexID() + Mesh->MaxVertexID() / 5); + } + if (TempFlagBuffer.Num() < Mesh->MaxVertexID()) + { + TempFlagBuffer.SetNum(2 * Mesh->MaxVertexID()); + } + + TempFlagBuffer.Init(false, TempFlagBuffer.Num()); + //TempFlagBuffer.assign(TempFlagBuffer.size(), false); +} + +void FRemesher::ApplyVertexBuffer(bool bParallel) +{ + for (int vid : Mesh->VertexIndicesItr()) + { + if (TempFlagBuffer[vid]) + { + Mesh->SetVertex(vid, TempPosBuffer[vid]); + } + } + + // [TODO] can probably use block-parallel here... + //if (bParallel) { + // gParallel.BlockStartEnd(0, Mesh->MaxVertexID-1, (a,b) => { + // for (int vid = a; vid <= b; vid++) { + // if (TempFlagBuffer[vid]) + // Mesh->SetVertex(vid, TempPosBuffer[vid]); + // } + // }); + //} else { + // foreach (int vid in Mesh->VertexIndices()) { + // if (TempFlagBuffer[vid]) + // Mesh->SetVertex(vid, TempPosBuffer[vid]); + // } + //} +} + + + +FVector3d FRemesher::ComputeSmoothedVertexPos(int vID, + TFunction smoothFunc, bool& bModified) +{ + bModified = false; + FVertexConstraint vConstraint = FVertexConstraint::Unconstrained(); + GetVertexConstraint(vID, vConstraint); + if (vConstraint.Fixed && vConstraint.Movable == false) + { + return Mesh->GetVertex(vID); + } + EVertexControl vControl = (VertexControlF == nullptr) ? EVertexControl::AllowAll : VertexControlF(vID); + if (((int)vControl & (int)EVertexControl::NoSmooth) != 0) + { + return Mesh->GetVertex(vID); + } + + FVector3d vSmoothed = smoothFunc(*Mesh, vID, SmoothSpeedT); + check(VectorUtil::IsFinite(vSmoothed)); // this will really catch a lot of bugs... + + // project onto either vtx constraint target, or surface target + if (vConstraint.Target != nullptr) + { + vSmoothed = vConstraint.Target->Project(vSmoothed, vID); + } + else if (EnableInlineProjection() && ProjTarget != nullptr) + { + if (((int)vControl & (int)EVertexControl::NoProject) == 0) + { + vSmoothed = ProjTarget->Project(vSmoothed, vID); + } + } + + bModified = true; + return vSmoothed; +} + + +void FRemesher::ApplyToSmoothVertices(const TFunction& VertexSmoothFunc) +{ + for (int vid : Mesh->VertexIndicesItr()) + { + VertexSmoothFunc(vid); + } +} + + + + +// Project vertices onto projection target. +// We can do projection in parallel if we have .net +void FRemesher::FullProjectionPass() +{ + auto UseProjectionFunc = [&](int vID) + { + if (IsVertexConstrained(vID)) + { + return; + } + if (VertexControlF != nullptr && ((int)VertexControlF(vID) & (int)EVertexControl::NoProject) != 0) + { + return; + } + FVector3d curpos = Mesh->GetVertex(vID); + FVector3d projected = ProjTarget->Project(curpos, vID); + Mesh->SetVertex(vID, projected); + }; + + ApplyToProjectVertices(UseProjectionFunc); + + // [RMS] not sure how to do this... + //if (EnableParallelProjection) { + // gParallel.ForEach(project_vertices(), project); + //} else { + // foreach (int vid in project_vertices()) + // project(vid); + //} +} + + + + +void FRemesher::ApplyToProjectVertices(const TFunction& VertexProjectFunc) +{ + for (int vid : Mesh->VertexIndicesItr()) + { + VertexProjectFunc(vid); + } +} + + + + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMesh3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMesh3.h new file mode 100644 index 000000000000..7feb9a356b3a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMesh3.h @@ -0,0 +1,1414 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp DMesh3 + +#pragma once + +#include "BoxTypes.h" +#include "FrameTypes.h" +#include "GeometryTypes.h" +#include "MathUtil.h" +#include "Quaternion.h" +#include "Util/DynamicVector.h" +#include "Util/IndexUtil.h" +#include "Util/IteratorUtil.h" +#include "Util/RefCountVector.h" +#include "Util/SmallListSet.h" +#include "VectorTypes.h" +#include "VectorUtil.h" + + +class FDynamicMeshAttributeSet; +class FMeshShapeGenerator; + +enum class EMeshComponents +{ + None = 0, + VertexNormals = 1, + VertexColors = 2, + VertexUVs = 4, + FaceGroups = 8 +}; + +/** + * FVertexInfo stores information about vertex attributes - position, normal, color, UV + */ +struct FVertexInfo +{ + FVector3d Position; + FVector3f Normal; + FVector3f Color; + FVector2f UV; + bool bHaveN, bHaveUV, bHaveC; + + FVertexInfo() + { + Position = FVector3d::Zero(); + Normal = Color = FVector3f::Zero(); + UV = FVector2f::Zero(); + bHaveN = bHaveC = bHaveUV = false; + } + FVertexInfo(const FVector3d& PositionIn) + { + Position = PositionIn; + Normal = Color = FVector3f::Zero(); + UV = FVector2f::Zero(); + bHaveN = bHaveC = bHaveUV = false; + } + FVertexInfo(const FVector3d& PositionIn, const FVector3f& NormalIn) + { + Position = PositionIn; + Normal = NormalIn; + Color = FVector3f::Zero(); + UV = FVector2f::Zero(); + bHaveN = true; + bHaveC = bHaveUV = false; + } + FVertexInfo(const FVector3d& PositionIn, const FVector3f& NormalIn, const FVector3f& ColorIn) + { + Position = PositionIn; + Normal = NormalIn; + Color = ColorIn; + UV = FVector2f::Zero(); + bHaveN = bHaveC = true; + bHaveUV = false; + } + FVertexInfo(const FVector3d& PositionIn, const FVector3f& NormalIn, const FVector3f& ColorIn, const FVector2f& UVIn) + { + Position = PositionIn; + Normal = NormalIn; + Color = ColorIn; + UV = UVIn; + bHaveN = bHaveC = bHaveUV = true; + } +}; + +/** +* FDynamicMesh3 is a dynamic triangle mesh class. The mesh has has connectivity, +* is an indexed mesh, and allows for gaps in the index space. +* +* internally, all data is stored in POD-type buffers, except for the vertex->edge +* links, which are stored as List's. The arrays of POD data are stored in +* TDynamicVector's, so they grow in chunks, which is relatively efficient. The actual +* blocks are arrays, so they can be efficiently mem-copied into larger buffers +* if necessary. +* +* Reference counts for verts/tris/edges are stored as separate FRefCountVector +* instances. +* +* Vertices are stored as doubles, although this should be easily changed +* if necessary, as the internal data structure is not exposed +* +* Per-vertex Vertex Normals, Colors, and UVs are optional and stored as floats. +* +* For each vertex, VertexEdgeLists[i] is the unordered list of connected edges. The +* elements of the list are indices into the edges list. +* This list is unsorted but can be traversed in-order (ie cw/ccw) at some additional cost. +* +* Triangles are stored as 3 ints, with optionally a per-triangle integer group id. +* +* The edges of a triangle are similarly stored as 3 ints, in triangle_edes. If the +* triangle is [v1,v2,v3], then the triangle edges [e1,e2,e3] are +* e1=edge(v1,v2), e2=edge(v2,v3), e3=edge(v3,v1), where the e# are indexes into edges. +* +* Edges are stored as tuples of 4 ints. If the edge is between v1 and v2, with neighbour +* tris t1 and t2, then the edge is [min(v1,v2), max(v1,v2), t1, t2]. For a boundary +* edge, t2 is InvalidID. t1 is never InvalidID. +* +* Most of the class assumes that the mesh is manifold. Many functions will +* work if the topology is non-manifold, but behavior of operators like Split/Flip/Collapse +* edge is untested. +* +* The function CheckValidity() does extensive sanity checking on the mesh data structure. +* Use this to test your code, both for mesh construction and editing!! +* +* +* TODO: +* - Many of the iterators depend on lambda functions, can we replace these with calls to +* internal/static functions that do the same thing? +* - efficient TriTrianglesItr() implementation? +* - additional Topology timestamp? +* - CompactInPlace() does not compact VertexEdgeLists +* - CompactCopy does not support per-vertex or extended attributes +* ? TDynamicVector w/ 'stride' option, so that we can guarantee that tuples are in single block. +* The can have custom accessor that looks up entire tuple +*/ +class DYNAMICMESH_API FDynamicMesh3 +{ +public: + /** InvalidID indicates that a vertex/edge/triangle ID is invalid */ + static const int InvalidID; // = IndexConstants::InvalidID = -1 + /** NonManifoldID is returned by AppendTriangle() to indicate that the added triangle would result in nonmanifold geometry and hence was ignored */ + static const int NonManifoldID; // = -2 + /** InvalidGroupID indicates that a group ID is invalid */ + static const int InvalidGroupID; // = IndexConstants::InvalidID = -1 + + static FVector3d InvalidVertex() + { + return FVector3d(TNumericLimits::Max(), 0, 0); + } + static FIndex3i InvalidTriangle() + { + return FIndex3i(InvalidID, InvalidID, InvalidID); + } + static FIndex2i InvalidEdge() + { + return FIndex2i(InvalidID, InvalidID); + } + +protected: + /** Reference counts of vertex indices. Iterate over this to find out which vertex indices are valid. */ + FRefCountVector VertexRefCounts; + /** List of vertex positions */ + TDynamicVector Vertices; + /** (optional) List of per-vertex normals */ + TDynamicVector* VertexNormals = nullptr; + /** (optional) List of per-vertex colors */ + TDynamicVector* VertexColors = nullptr; + /** (optional) List of per-vertex uvs */ + TDynamicVector* VertexUVs = nullptr; + + /** List of per-vertex edge one-rings */ + FSmallListSet VertexEdgeLists; + + /** Reference counts of triangle indices. Iterate over this to find out which triangle indices are valid. */ + FRefCountVector TriangleRefCounts; + /** List of triangle vertex-index triplets [Vert0 Vert1 Vert2]*/ + TDynamicVector Triangles; + /** List of triangle edge triplets [Edge0 Edge1 Edge2] */ + TDynamicVector TriangleEdges; + /** (optional) List of per-triangle group identifiers */ + TDynamicVector* TriangleGroups = nullptr; + + /** Reference counts of edge indices. Iterate over this to find out which edge indices are valid. */ + FRefCountVector EdgeRefCounts; + /** List of edge elements. An edge is four elements [VertA, VertB, Tri0, Tri1], where VertA < VertB, and Tri1 may be InvalidID (if the edge is a boundary edge) */ + TDynamicVector Edges; + + FDynamicMeshAttributeSet* AttributeSet = nullptr; + + /** The mesh timestamp is incremented any time a function that modifies the mesh is called */ + int Timestamp = 0; + /** The shape timestamp is incremented any time a function that modifies the mesh shape or topology is called */ + int ShapeTimestamp = 0; + /** The topology timestamp is incremented any time a function that modifies the mesh topology is called */ + int TopologyTimestamp = 0; + + /** Upper bound on the triangle group IDs used in the mesh (may be larger than the actual maximum if triangles have been deleted) */ + int GroupIDCounter = 0; + + /** Cached vertex bounding box (includes un-referenced vertices) */ + FAxisAlignedBox3d CachedBoundingBox; + /** timestamp for CachedBoundingBox, if less than current timestamp, cache is invalid */ + int CachedBoundingBoxTimestamp = -1; + /** Cached value of IsClosed() */ + bool bIsClosedCached = false; + /** timestamp for bIsClosedCached, if less than current timestamp, cache is invalid */ + int CachedIsClosedTimestamp = -1; + +public: + virtual ~FDynamicMesh3(); + + explicit FDynamicMesh3(bool bWantNormals = true, bool bWantColors = false, bool bWantUVs = false, bool bWantTriGroups = false); + + FDynamicMesh3(EMeshComponents flags) + : FDynamicMesh3(((int)flags & (int)EMeshComponents::VertexNormals) != 0, ((int)flags & (int)EMeshComponents::VertexColors) != 0, + ((int)flags & (int)EMeshComponents::VertexUVs) != 0, ((int)flags & (int)EMeshComponents::FaceGroups) != 0) + { + } + + /** + * @param CopyMesh mesh to copy + * @param bCompact if true, compact CopyMesh on the fly + * @param bWantNormals should we copy per-vertex normals, if they exist + * @param bWantColors should we copy per-vertex colors, if they exist + * @param bWantUVs should we copy per-vertex uvs, if they exist + */ + FDynamicMesh3(const FDynamicMesh3& CopyMesh, bool bCompact = false, bool bWantNormals = true, bool bWantColors = true, bool bWantUVs = true, bool bWantAttributes = true); + + /** + * @param CopyMesh mesh to copy + * @param bCompact if true, compact CopyMesh on the fly + * @param Flags which components of CopyMesh to copy, if they exist + */ + FDynamicMesh3(const FDynamicMesh3& CopyMesh, bool bCompact, EMeshComponents Flags); + + /** Initialize mesh from the output of a MeshShapeGenerator (assumes Generate() was already called) */ + FDynamicMesh3(const FMeshShapeGenerator* Generator); + + /** copy assignment operator */ + const FDynamicMesh3& operator=(const FDynamicMesh3& CopyMesh); + + // + // Copy functions to construct a mesh from an input mesh + // +public: + /** Set internal data structures to be a copy of input mesh */ + void Copy(const FDynamicMesh3& CopyMesh, bool bNormals = true, bool bColors = true, bool bUVs = true, bool bAttributes = true); + + /** Initialize mesh from the output of a MeshShapeGenerator (assumes Generate() was already called) */ + void Copy(const FMeshShapeGenerator* Generator); + + // @todo make this work + struct FCompactMaps + { + TMap MapV; + }; + + /** Copy input mesh while compacting, ie removing unused vertices/triangles/edges */ + void CompactCopy(const FDynamicMesh3& CopyMesh, bool bNormals = true, bool bColors = true, bool bUVs = true, bool bAttributes = true, FCompactMaps* CompactInfo = nullptr); + + /** Discard all data */ + void Clear(); + + +public: + + /** @return number of vertices in the mesh */ + int VertexCount() const + { + return (int)VertexRefCounts.GetCount(); + } + /** @return number of triangles in the mesh */ + int TriangleCount() const + { + return (int)TriangleRefCounts.GetCount(); + } + /** @return number of edges in the mesh */ + int EdgeCount() const + { + return (int)EdgeRefCounts.GetCount(); + } + + /** @return upper bound on vertex IDs used in the mesh, ie all vertex IDs in use are < MaxVertexID */ + int MaxVertexID() const + { + return (int)VertexRefCounts.GetMaxIndex(); + } + /** @return upper bound on triangle IDs used in the mesh, ie all triangle IDs in use are < MaxTriangleID */ + int MaxTriangleID() const + { + return (int)TriangleRefCounts.GetMaxIndex(); + } + /** @return upper bound on edge IDs used in the mesh, ie all edge IDs in use are < MaxEdgeID */ + int MaxEdgeID() const + { + return (int)EdgeRefCounts.GetMaxIndex(); + } + /** @return upper bound on group IDs used in the mesh, ie all group IDs in use are < MaxGroupID */ + int MaxGroupID() const + { + return GroupIDCounter; + } + + + /** @return true if this mesh has per-vertex normals */ + bool HasVertexNormals() const + { + return VertexNormals != nullptr; + } + /** @return true if this mesh has per-vertex colors */ + bool HasVertexColors() const + { + return VertexColors != nullptr; + } + /** @return true if this mesh has per-vertex UVs */ + bool HasVertexUVs() const + { + return VertexUVs != nullptr; + } + /** @return true if this mesh has per-triangle groups */ + bool HasTriangleGroups() const + { + return TriangleGroups != nullptr; + } + + + /** @return true if this mesh has attribute layers */ + bool HasAttributes() const + { + return AttributeSet != nullptr; + } + + /** @return bitwise-or of EMeshComponents flags specifying which extra data this mesh has */ + int GetComponentsFlags() const; + + + /** @return true if VertexID is a valid vertex in this mesh */ + inline bool IsVertex(int VertexID) const + { + return VertexRefCounts.IsValid(VertexID); + } + /** @return true if TriangleID is a valid triangle in this mesh */ + inline bool IsTriangle(int TriangleID) const + { + return TriangleRefCounts.IsValid(TriangleID); + } + /** @return true if EdgeID is a valid edge in this mesh */ + inline bool IsEdge(int EdgeID) const + { + return EdgeRefCounts.IsValid(EdgeID); + } + + + // + // Mesh Element Iterators + // The functions VertexIndicesItr() / TriangleIndicesItr() / EdgeIndicesItr() allow you to do: + // for ( int eid : EdgeIndicesItr() ) { ... } + // and other related begin() / end() idioms +public: + // simplify names for iterations + typedef typename FRefCountVector::IndexEnumerable vertex_iterator; + typedef typename FRefCountVector::IndexEnumerable triangle_iterator; + typedef typename FRefCountVector::IndexEnumerable edge_iterator; + template + using value_iteration = FRefCountVector::MappedEnumerable; + using vtx_triangles_enumerable = ExpandEnumerable; + + /** @return enumerable object for valid vertex indices suitable for use with range-based for, ie for ( int i : VertexIndicesItr() ) */ + vertex_iterator VertexIndicesItr() const + { + return VertexRefCounts.Indices(); + } + + /** @return enumerable object for valid triangle indices suitable for use with range-based for, ie for ( int i : TriangleIndicesItr() ) */ + triangle_iterator TriangleIndicesItr() const + { + return TriangleRefCounts.Indices(); + } + + /** @return enumerable object for valid edge indices suitable for use with range-based for, ie for ( int i : EdgeIndicesItr() ) */ + edge_iterator EdgeIndicesItr() const + { + return EdgeRefCounts.Indices(); + } + + // TODO: write helper functions that allow us to do these iterations w/o lambdas + + /** @return enumerable object for boundary edge indices suitable for use with range-based for, ie for ( int i : BoundaryEdgeIndicesItr() ) */ + FRefCountVector::FilteredEnumerable BoundaryEdgeIndicesItr() const + { + return EdgeRefCounts.FilteredIndices([this](int EdgeID) {\ + return Edges[4*EdgeID + 3] == InvalidID; + }); + } + + /** Enumerate positions of all vertices in mesh */ + value_iteration VerticesItr() const + { + return VertexRefCounts.MappedIndices([this](int VertexID) { + int i = 3 * VertexID; + return FVector3d(Vertices[i], Vertices[i + 1], Vertices[i + 2]); + }); + } + + /** Enumerate all triangles in the mesh */ + value_iteration TrianglesItr() const + { + return TriangleRefCounts.MappedIndices([this](int TriangleID) { + int i = 3 * TriangleID; + return FIndex3i(Triangles[i], Triangles[i + 1], Triangles[i + 2]); + }); + } + + /** Enumerate edges. Each returned element is [v0,v1,t0,t1], where t1 will be InvalidID if this is a boundary edge */ + value_iteration EdgesItr() const + { + return EdgeRefCounts.MappedIndices([this](int EdgeID) { + int i = 4 * EdgeID; + return FIndex4i(Edges[i], Edges[i + 1], Edges[i + 2], Edges[i + 3]); + }); + } + + /** @return enumerable object for one-ring vertex neighbours of a vertex, suitable for use with range-based for, ie for ( int i : VtxVerticesItr(VertexID) ) */ + FSmallListSet::ValueEnumerable VtxVerticesItr(int VertexID) const + { + check(VertexRefCounts.IsValid(VertexID)); + return VertexEdgeLists.Values(VertexID, [VertexID, this](int eid) { return GetOtherEdgeVertex(eid, VertexID); }); + } + + /** @return enumerable object for one-ring edges of a vertex, suitable for use with range-based for, ie for ( int i : VtxEdgesItr(VertexID) ) */ + FSmallListSet::ValueEnumerable VtxEdgesItr(int VertexID) const + { + check(VertexRefCounts.IsValid(VertexID)); + return VertexEdgeLists.Values(VertexID); + } + + /** @return enumerable object for one-ring triangles of a vertex, suitable for use with range-based for, ie for ( int i : VtxTrianglesItr(VertexID) ) */ + vtx_triangles_enumerable VtxTrianglesItr(int VertexID) const; + + + + // + // Mesh Construction + // +public: + /** Append vertex at position and other fields, returns vid */ + int AppendVertex(const FVertexInfo& VertInfo); + + /** Append vertex at position, returns vid */ + int AppendVertex(const FVector3d& Position) + { + return AppendVertex(FVertexInfo(Position)); + } + + /** Copy vertex SourceVertexID from existing SourceMesh, returns new vertex id */ + int AppendVertex(const FDynamicMesh3& SourceMesh, int SourceVertexID); + + int AppendTriangle(const FIndex3i& TriVertices, int GroupID = -1); + + inline int AppendTriangle(int Vertex0, int Vertex1, int Vertex2, int GroupID = -1) + { + return AppendTriangle(FIndex3i(Vertex0, Vertex1, Vertex2), GroupID); + } + + // + // Support for inserting vertex and triangle at specific IDs. This is a bit tricky + // because we likely will need to update the free lists in the RefCountVectors, which + // can be expensive. If you are going to do many inserts (eg inside a loop), wrap in + // BeginUnsafe / EndUnsafe calls, and pass bUnsafe = true to the InsertX() calls, to + // the defer free list rebuild until you are done. + // + + /** Call this before a set of unsafe InsertVertex() calls */ + virtual void BeginUnsafeVerticesInsert() + { + // do nothing... + } + + /** Call after a set of unsafe InsertVertex() calls to rebuild free list */ + virtual void EndUnsafeVerticesInsert() + { + VertexRefCounts.RebuildFreeList(); + } + + /** + * Insert vertex at given index, assuming it is unused. + * If bUnsafe, we use fast id allocation that does not update free list. + * You should only be using this between BeginUnsafeVerticesInsert() / EndUnsafeVerticesInsert() calls + */ + EMeshResult InsertVertex(int VertexID, const FVertexInfo& VertInfo, bool bUnsafe = false); + + /** Call this before a set of unsafe InsertTriangle() calls */ + virtual void BeginUnsafeTrianglesInsert() + { + // do nothing... + } + + /** Call after a set of unsafe InsertTriangle() calls to rebuild free list */ + virtual void EndUnsafeTrianglesInsert() + { + TriangleRefCounts.RebuildFreeList(); + } + + /** + * Insert triangle at given index, assuming it is unused. + * If bUnsafe, we use fast id allocation that does not update free list. + * You should only be using this between BeginUnsafeTrianglesInsert() / EndUnsafeTrianglesInsert() calls + */ + EMeshResult InsertTriangle(int TriangleID, const FIndex3i& TriVertices, int GroupID = -1, bool bUnsafe = false); + + // + // Vertex/Tri/Edge accessors + // +public: + + /** @return the vertex position */ + inline FVector3d GetVertex(int VertexID) const + { + check(IsVertex(VertexID)); + int i = 3 * VertexID; + return FVector3d(Vertices[i], Vertices[i + 1], Vertices[i + 2]); + } + + /** Set vertex position */ + inline void SetVertex(int VertexID, const FVector3d& vNewPos) + { + check(VectorUtil::IsFinite(vNewPos)); + check(IsVertex(VertexID)); + int i = 3 * VertexID; + Vertices[i] = vNewPos.X; + Vertices[i + 1] = vNewPos.Y; + Vertices[i + 2] = vNewPos.Z; + UpdateTimeStamp(true, false); + } + + /** Get extended vertex information */ + bool GetVertex(int VertexID, FVertexInfo& VertInfo, bool bWantNormals, bool bWantColors, bool bWantUVs) const; + + /** Get all vertex information available */ + FVertexInfo GetVertexInfo(int VertexID) const; + + /** @return the valence of a vertex (the number of connected edges) */ + int GetVtxEdgeCount(int VertexID) const + { + return VertexRefCounts.IsValid(VertexID) ? VertexEdgeLists.GetCount(VertexID) : -1; + } + + /** @return the max valence of all vertices in the mesh */ + int GetMaxVtxEdgeCount() const; + + /** Get triangle vertices */ + inline FIndex3i GetTriangle(int TriangleID) const + { + check(IsTriangle(TriangleID)); + int i = 3 * TriangleID; + return FIndex3i(Triangles[i], Triangles[i + 1], Triangles[i + 2]); + } + + /** Get triangle edges */ + inline FIndex3i GetTriEdges(int TriangleID) const + { + check(IsTriangle(TriangleID)); + int i = 3 * TriangleID; + return FIndex3i(TriangleEdges[i], TriangleEdges[i + 1], TriangleEdges[i + 2]); + } + + /** Get one of the edges of a triangle */ + inline int GetTriEdge(int TriangleID, int j) const + { + check(IsTriangle(TriangleID)); + return TriangleEdges[3 * TriangleID + j]; + } + + /** Find the neighbour triangles of a triangle (any of them might be InvalidID) */ + FIndex3i GetTriNeighbourTris(int TriangleID) const; + + /** Get the three vertex positions of a triangle */ + inline void GetTriVertices(int TriangleID, FVector3d& v0, FVector3d& v1, FVector3d& v2) const + { + int i = 3 * TriangleID; + int ai = 3 * Triangles[i]; + v0.X = Vertices[ai]; v0.Y = Vertices[ai + 1]; v0.Z = Vertices[ai + 2]; + int bi = 3 * Triangles[i + 1]; + v1.X = Vertices[bi]; v1.Y = Vertices[bi + 1]; v1.Z = Vertices[bi + 2]; + int ci = 3 * Triangles[i + 2]; + v2.X = Vertices[ci]; v2.Y = Vertices[ci + 1]; v2.Z = Vertices[ci + 2]; + } + + /** Get the position of one of the vertices of a triangle */ + inline FVector3d GetTriVertex(int TriangleID, int j) const + { + int vi = 3 * Triangles[3 * TriangleID + j]; + return FVector3d(Vertices[vi], Vertices[vi + 1], Vertices[vi + 2]); + } + + /** Get the vertices and triangles of an edge, returned as [v0,v1,t0,t1], where t1 may be InvalidID */ + inline FIndex4i GetEdge(int EdgeID) const + { + check(IsEdge(EdgeID)); + int i = 4 * EdgeID; + return FIndex4i(Edges[i], Edges[i + 1], Edges[i + 2], Edges[i + 3]); + } + + /** Get the vertex pair for an edge */ + inline FIndex2i GetEdgeV(int EdgeID) const + { + check(IsEdge(EdgeID)); + int i = 4 * EdgeID; + return FIndex2i(Edges[i], Edges[i + 1]); + } + + /** Get the vertex positions of an edge */ + inline bool GetEdgeV(int EdgeID, FVector3d& a, FVector3d& b) const + { + check(IsEdge(EdgeID)); + int iv0 = 3 * Edges[4 * EdgeID]; + a.X = Vertices[iv0]; + a.Y = Vertices[iv0 + 1]; + a.Z = Vertices[iv0 + 2]; + int iv1 = 3 * Edges[4 * EdgeID + 1]; + b.X = Vertices[iv1]; + b.Y = Vertices[iv1 + 1]; + b.Z = Vertices[iv1 + 2]; + return true; + } + + /** Get the triangle pair for an edge. The second triangle may be InvalidID */ + inline FIndex2i GetEdgeT(int EdgeID) const + { + check(IsEdge(EdgeID)); + int i = 4 * EdgeID; + return FIndex2i(Edges[i + 2], Edges[i + 3]); + } + + /** Return edge vertex indices, but oriented based on attached triangle (rather than min-sorted) */ + FIndex2i GetOrientedBoundaryEdgeV(int EdgeID) const; + + // + // Vertex and Triangle attribute arrays + // +public: + void EnableVertexNormals(const FVector3f& InitialNormal); + void DiscardVertexNormals(); + + FVector3f GetVertexNormal(int vID) const + { + if (HasVertexNormals() == false) + { + return FVector3f::UnitY(); + } + check(IsVertex(vID)); + int i = 3 * vID; + return FVector3f((*VertexNormals)[i], (*VertexNormals)[i + 1], (*VertexNormals)[i + 2]); + } + + void SetVertexNormal(int vID, const FVector3f& vNewNormal) + { + if (HasVertexNormals()) + { + check(IsVertex(vID)); + int i = 3 * vID; + (*VertexNormals)[i] = vNewNormal.X; + (*VertexNormals)[i + 1] = vNewNormal.Y; + (*VertexNormals)[i + 2] = vNewNormal.Z; + UpdateTimeStamp(false, false); + } + } + + void EnableVertexColors(const FVector3f& InitialColor); + void DiscardVertexColors(); + + + FVector3f GetVertexColor(int vID) const + { + if (HasVertexColors() == false) + { + return FVector3f::One(); + } + check(IsVertex(vID)); + int i = 3 * vID; + return FVector3f((*VertexColors)[i], (*VertexColors)[i + 1], (*VertexColors)[i + 2]); + } + + void SetVertexColor(int vID, const FVector3f& vNewColor) + { + if (HasVertexColors()) + { + check(IsVertex(vID)); + int i = 3 * vID; + (*VertexColors)[i] = vNewColor.X; + (*VertexColors)[i + 1] = vNewColor.Y; + (*VertexColors)[i + 2] = vNewColor.Z; + UpdateTimeStamp(false, false); + } + } + + void EnableVertexUVs(const FVector2f& InitialUV); + void DiscardVertexUVs(); + + FVector2f GetVertexUV(int vID) const + { + if (HasVertexUVs() == false) + { + return FVector2f::Zero(); + } + check(IsVertex(vID)); + int i = 2 * vID; + return FVector2f((*VertexUVs)[i], (*VertexUVs)[i + 1]); + } + + void SetVertexUV(int vID, const FVector2f& vNewUV) + { + if (HasVertexUVs()) + { + check(IsVertex(vID)); + int i = 2 * vID; + (*VertexUVs)[i] = vNewUV.X; + (*VertexUVs)[i + 1] = vNewUV.Y; + UpdateTimeStamp(false, false); + } + } + + void EnableTriangleGroups(int InitialGroupID = 0); + void DiscardTriangleGroups(); + + int AllocateTriangleGroup() { return ++GroupIDCounter; } + + int GetTriangleGroup(int tID) const + { + return (HasTriangleGroups() == false) ? -1 + : (TriangleRefCounts.IsValid(tID) ? (*TriangleGroups)[tID] : 0); + } + + void SetTriangleGroup(int tid, int group_id) + { + if (HasTriangleGroups()) + { + check(IsTriangle(tid)); + (*TriangleGroups)[tid] = group_id; + GroupIDCounter = FMath::Max(GroupIDCounter, group_id + 1); + UpdateTimeStamp(false, false); + } + } + + FDynamicMeshAttributeSet* Attributes() + { + return AttributeSet; + } + const FDynamicMeshAttributeSet* Attributes() const + { + return AttributeSet; + } + + void EnableAttributes(); + + void DiscardAttributes(); + + + // + // topological queries + // +public: + /** Returns true if edge is on the mesh boundary, ie only connected to one triangle */ + inline bool IsBoundaryEdge(int EdgeID) const + { + check(IsEdge(EdgeID)); + return Edges[4 * EdgeID + 3] == InvalidID; + } + + /** Returns true if the vertex is part of any boundary edges */ + bool IsBoundaryVertex(int VertexID) const; + + /** Returns true if any edge of triangle is a boundary edge */ + bool IsBoundaryTriangle(int TriangleID) const; + + /** Find id of edge connecting A and B */ + int FindEdge(int VertexA, int VertexB) const; + + /** Find edgeid for edge [a,b] from triangle that contains the edge. Faster than FindEdge() because it is constant-time. */ + int FindEdgeFromTri(int VertexA, int VertexB, int TriangleID) const; + + /** Find triangle made up of any permutation of vertices [a,b,c] */ + int FindTriangle(int A, int B, int C) const; + + /** + * If edge has vertices [a,b], and is connected two triangles [a,b,c] and [a,b,d], + * this returns [c,d], or [c,InvalidID] for a boundary edge + */ + FIndex2i GetEdgeOpposingV(int EdgeID) const; + + /** + * Given an edge and vertex on that edge, returns other vertex of edge, the two opposing verts, and the two connected triangles (OppVert2Out and Tri2Out are be InvalidID for boundary edge) + */ + void GetVtxNbrhood(int EdgeID, int VertexID, int& OtherVertOut, int& OppVert1Out, int& OppVert2Out, int& Tri1Out, int& Tri2Out) const; + + /** + * Returns count of boundary edges at vertex, and the first two boundary + * edges if found. If return is > 2, call GetAllVtxBoundaryEdges + */ + int GetVtxBoundaryEdges(int VertexID, int& Edge0Out, int& Edge1Out) const; + + /** + * Find edge ids of boundary edges connected to vertex. + * @param vID Vertex ID + * @param EdgeListOut boundary edge IDs are appended to this list + * @return count of number of elements of e that were filled + */ + int GetAllVtxBoundaryEdges(int VertexID, TArray& EdgeListOut) const; + + /** + * return # of triangles attached to vID, or -1 if invalid vertex + * if bBruteForce = true, explicitly checks, which creates a list and is expensive + * default is false, uses orientation, no memory allocation + */ + int GetVtxTriangleCount(int VertexID, bool bBruteForce = false) const; + + /** + * Get triangle one-ring at vertex. + * bUseOrientation is more efficient but returns incorrect result if vertex is a bowtie + */ + EMeshResult GetVtxTriangles(int VertexID, TArray& TrianglesOut, bool bUseOrientation) const; + + /** Returns true if the two triangles connected to edge have different group IDs */ + bool IsGroupBoundaryEdge(int EdgeID) const; + + /** Returns true if vertex has more than one tri group in its tri nbrhood */ + bool IsGroupBoundaryVertex(int VertexID) const; + + /** Returns true if more than two group boundary edges meet at vertex (ie 3+ groups meet at this vertex) */ + bool IsGroupJunctionVertex(int VertexID) const; + + /** Returns up to 4 group IDs at vertex. Returns false if > 4 encountered */ + bool GetVertexGroups(int VertexID, FIndex4i& GroupsOut) const; + + /** Returns all group IDs at vertex */ + bool GetAllVertexGroups(int VertexID, TArray& GroupsOut) const; + + /** returns true if vID is a "bowtie" vertex, ie multiple disjoint triangle sets in one-ring */ + bool IsBowtieVertex(int VertexID) const; + + /** returns true if vertices, edges, and triangles are all dense (Count == MaxID) **/ + bool IsCompact() const + { + return VertexRefCounts.IsDense() && EdgeRefCounts.IsDense() && TriangleRefCounts.IsDense(); + } + + /** @return true if vertex count == max vertex id */ + bool IsCompactV() const + { + return VertexRefCounts.IsDense(); + } + + /** @return true if triangle count == max triangle id */ + bool IsCompactT() const + { + return TriangleRefCounts.IsDense(); + } + + /** returns measure of compactness in range [0,1], where 1 is fully compacted */ + double CompactMetric() const + { + return ((double)VertexCount() / (double)MaxVertexID() + (double)TriangleCount() / (double)MaxTriangleID()) * 0.5; + } + + /** @return true if mesh has no boundary edges */ + bool IsClosed() const; + + /** @return value of IsClosed() and cache result. cache is invalidated and recomputed if topology has changed since last call */ + bool GetCachedIsClosed(); + + + /** Timestamp is incremented any time any change is made to the mesh */ + inline int GetTimestamp() const + { + return Timestamp; + } + + /** ShapeTimestamp is incremented any time any vertex position is changed or the mesh topology is modified */ + inline int GetShapeTimestamp() const + { + return ShapeTimestamp; + } + + /** TopologyTimestamp is incremented any time any vertex position is changed or the mesh topology is modified */ + inline int GetTopologyTimestamp() const + { + return TopologyTimestamp; + } + + // + // Geometric queries + // +public: + /** Returns bounding box of all mesh vertices (including unreferenced vertices) */ + FAxisAlignedBox3d GetBounds() const; + + /** Returns GetBounds() and saves result, cache is invalidated and recomputed if topology has changed since last call */ + FAxisAlignedBox3d GetCachedBounds(); + + /** + * Compute a normal/tangent frame at vertex that is "stable" as long as + * the mesh topology doesn't change, meaning that one axis of the frame + * will be computed from projection of outgoing edge. Requires that vertex normals are available. + * By default, frame.Z is normal, and .X points along mesh edge. + * If bFrameNormalY, then frame.Y is normal (X still points along mesh edge) + */ + FFrame3d GetVertexFrame(int VertexID, bool bFrameNormalY = false) const; + + /** Calculate face normal of triangle */ + FVector3d GetTriNormal(int TriangleID) const; + + /** Calculate area triangle */ + double GetTriArea(int TriangleID) const; + + /** + * Compute triangle normal, area, and centroid all at once. Re-uses vertex + * lookups and computes normal & area simultaneously. *However* does not produce + * the same normal/area as separate calls, because of this. + */ + void GetTriInfo(int TriangleID, FVector3d& Normal, double& Area, FVector3d& Centroid) const; + + /** Compute centroid of triangle */ + FVector3d GetTriCentroid(int TriangleID) const; + + /** Interpolate vertex positions of triangle using barycentric coordinates */ + FVector3d GetTriBaryPoint(int TriangleID, double Bary0, double Bary1, double Bary2) const; + + /** Interpolate vertex normals of triangle using barycentric coordinates */ + FVector3d GetTriBaryNormal(int TriangleID, double Bary0, double Bary1, double Bary2) const; + + /** Compute interpolated vertex attributes at point of triangle */ + void GetTriBaryPoint(int TriangleID, double Bary0, double Bary1, double Bary2, FVertexInfo& VertInfo) const; + + /** Construct bounding box of triangle as efficiently as possible */ + FAxisAlignedBox3d GetTriBounds(int TriangleID) const; + + /** Construct stable frame at triangle centroid, where frame.Z is face normal, and frame.X is aligned with edge nEdge of triangle. */ + FFrame3d GetTriFrame(int TriangleID, int Edge = 0) const; + + /** Compute solid angle of oriented triangle tID relative to point p - see WindingNumber() */ + double GetTriSolidAngle(int TriangleID, const FVector3d& p) const; + + /** Compute internal angle at vertex i of triangle (where i is 0,1,2); */ + double GetTriInternalAngleR(int TriangleID, int i); + + /** Returns average normal of connected face normals */ + FVector3d GetEdgeNormal(int EdgeID) const; + + /** Get point along edge, t clamped to range [0,1] */ + FVector3d GetEdgePoint(int EdgeID, double ParameterT) const; + + /** + * Fastest possible one-ring centroid. This is used inside many other algorithms + * so it helps to have it be maximally efficient + */ + void GetVtxOneRingCentroid(int VertexID, FVector3d& CentroidOut) const; + + /** + * Compute mesh winding number, from Jacobson et al, Robust Inside-Outside Segmentation using Generalized Winding Numbers + * http://igl.ethz.ch/projects/winding-number/ + * returns ~0 for points outside a closed, consistently oriented mesh, and a positive or negative integer + * for points inside, with value > 1 depending on how many "times" the point inside the mesh (like in 2D polygon winding) + */ + double CalculateWindingNumber(const FVector3d& QueryPoint) const; + + // + // direct buffer access + // +public: + const TDynamicVector& GetVerticesBuffer() + { + return Vertices; + } + const FRefCountVector& GetVerticesRefCounts() + { + return VertexRefCounts; + } + const TDynamicVector* GetNormalsBuffer() + { + return VertexNormals; + } + const TDynamicVector* GetColorsBuffer() + { + return VertexColors; + } + const TDynamicVector* GetUVBuffer() + { + return VertexUVs; + } + + const TDynamicVector& GetTrianglesBuffer() + { + return Triangles; + } + const FRefCountVector& GetTrianglesRefCounts() + { + return TriangleRefCounts; + } + const TDynamicVector* GetTriangleGroupsBuffer() + { + return TriangleGroups; + } + + const TDynamicVector& GetEdgesBuffer() + { + return Edges; + } + const FRefCountVector& GetEdgesRefCounts() + { + return EdgeRefCounts; + } + const FSmallListSet& GetVertexEdges() + { + return VertexEdgeLists; + } + + // + // Mesh Edit operations + // +public: + /** + * Compact mesh in-place, by moving vertices around and rewriting indices. + * Should be faster if the amount of compacting is not too significant, and is useful in some places. + * + * @param bComputeCompactInfo if false, then returned CompactMaps is not initialized + * @todo VertexEdgeLists is not compacted. does not affect indices, but does keep memory. + * @todo returned CompactMaps is currently not valid + */ + void CompactInPlace(FCompactMaps* CompactInfo = nullptr); + + /** + * Reverse the ccw/cw orientation of all triangles in the mesh, and + * optionally flip the vertex normals if they exist + */ + void ReverseOrientation(bool bFlipNormals = true); + + /** + * Reverse the ccw/cw orientation of a triangle + */ + EMeshResult ReverseTriOrientation(int TriangleID); + + /** + * Remove vertex vID, and all connected triangles if bRemoveAllTriangles = true + * Returns Failed_VertexStillReferenced if vertex is still referenced by triangles. + * if bPreserveManifold, checks that we will not create a bowtie vertex first + */ + EMeshResult RemoveVertex(int VertexID, bool bRemoveAllTriangles = true, bool bPreserveManifold = false); + + /** + * Remove a triangle from the mesh. Also removes any unreferenced edges after tri is removed. + * If bRemoveIsolatedVertices is false, then if you remove all tris from a vert, that vert is also removed. + * If bPreserveManifold, we check that you will not create a bowtie vertex (and return false). + * If this check is not done, you have to make sure you don't create a bowtie, because other + * code assumes we don't have bowties, and will not handle it properly + */ + EMeshResult RemoveTriangle(int TriangleID, bool bRemoveIsolatedVertices = true, bool bPreserveManifold = false); + + /** + * Rewrite the triangle to reference the new tuple of vertices. + * + * @todo this function currently does not guarantee that the returned mesh is well-formed. Only call if you know it's OK. + */ + virtual EMeshResult SetTriangle(int TriangleID, const FIndex3i& NewVertices, bool bRemoveIsolatedVertices = true); + + + + /** Information about the mesh elements created by a call to SplitEdge() */ + struct FEdgeSplitInfo + { + int OriginalEdge; // the edge that was split + FIndex2i OriginalVertices; // original edge vertices [a,b] + FIndex2i OtherVertices; // original opposing vertices [c,d] - d is InvalidID for boundary edges + FIndex2i OriginalTriangles; // original edge triangles [t0,t1] + bool bIsBoundary; // was the split edge a boundary edge? (redundant) + + int NewVertex; // new vertex f that was created + FIndex2i NewTriangles; // new triangles [t2,t3], oriented as explained below + FIndex3i NewEdges; // new edges are [f,b], [f,c] and [f,d] if this is not a boundary edge + + double SplitT; // parameter value for NewVertex along original edge + }; + + /** + * Split an edge of the mesh by inserting a vertex. This creates a new triangle on either side of the edge (ie a 2-4 split). + * If the original edge had vertices [a,b], with triangles t0=[a,b,c] and t1=[b,a,d], then the split inserts new vertex f. + * After the split t0=[a,f,c] and t1=[f,a,d], and we have t2=[f,b,c] and t3=[f,d,b] (it's best to draw it out on paper...) + * + * @param EdgeAB index of the edge to be split + * @param SplitInfo returned information about new and modified mesh elements + * @param SplitParameterT defines the position along the edge that we split at, must be between 0 and 1, and is assumed to be based on the order of vertices returned by GetEdgeV() + * @return Ok on success, or enum value indicates why operation cannot be applied. Mesh remains unmodified on error. + */ + virtual EMeshResult SplitEdge(int EdgeAB, FEdgeSplitInfo& SplitInfo, double SplitParameterT = 0.5); + + /** + * Splits the edge between two vertices at the midpoint, if this edge exists + * @param EdgeVertA index of first vertex + * @param EdgeVertB index of second vertex + * @param SplitInfo returned information about new and modified mesh elements + * @return Ok on success, or enum value indicates why operation cannot be applied. Mesh remains unmodified on error. + */ + EMeshResult SplitEdge(int EdgeVertA, int EdgeVertB, FEdgeSplitInfo& SplitInfo); + + + + /** Information about the mesh elements modified by a call to FlipEdge() */ + struct FEdgeFlipInfo + { + int EdgeID; // the edge that was flipped + FIndex2i OriginalVerts; // original verts of the flipped edge, that are no longer connected + FIndex2i OpposingVerts; // the opposing verts of the flipped edge, that are now connected + FIndex2i Triangles; // the two triangle IDs. Original tris vert [Vert0,Vert1,OtherVert0] and [Vert1,Vert0,OtherVert1]. + // New triangles are [OtherVert0, OtherVert1, Vert1] and [OtherVert1, OtherVert0, Vert0] + }; + + /** + * Flip/Rotate an edge of the mesh. This does not change the number of edges, vertices, or triangles. + * Boundary edges of the mesh cannot be flipped. + * @param EdgeAB index of edge to be flipped + * @param FlipInfo returned information about new and modified mesh elements + * @return Ok on success, or enum value indicates why operation cannot be applied. Mesh remains unmodified on error. + */ + virtual EMeshResult FlipEdge(int EdgeAB, FEdgeFlipInfo& FlipInfo); + + /** calls FlipEdge() on the edge between two vertices, if it exists + * @param EdgeVertA index of first vertex + * @param EdgeVertB index of second vertex + * @param FlipInfo returned information about new and modified mesh elements + * @return Ok on success, or enum value indicates why operation cannot be applied. Mesh remains unmodified on error. + */ + virtual EMeshResult FlipEdge(int EdgeVertA, int EdgeVertB, FEdgeFlipInfo& FlipInfo); + + + + /** Information about mesh elements modified/removed by CollapseEdge() */ + struct FEdgeCollapseInfo + { + int KeptVertex; // the vertex that was kept (ie collapsed "to") + int RemovedVertex; // the vertex that was removed + FIndex2i OpposingVerts; // the opposing vertices [c,d]. If the edge was a boundary edge, d is InvalidID + bool bIsBoundary; // was the edge a boundary edge + + int CollapsedEdge; // the edge that was collapsed/removed + FIndex2i RemovedTris; // the triangles that were removed in the collapse (second is InvalidID for boundary edge) + FIndex2i RemovedEdges; // the edges that were removed (second is InvalidID for boundary edge) + FIndex2i KeptEdges; // the edges that were kept (second is InvalidID for boundary edge) + + double CollapseT; // interpolation parameter along edge for new vertex in range [0,1] + }; + + /** + * Collapse the edge between the two vertices, if topologically possible. + * @param KeepVertID index of the vertex that should be kept + * @param RemoveVertID index of the vertex that should be removed + * @param EdgeParameterT vKeep is moved to Lerp(KeepPos, RemovePos, collapse_t) + * @param CollapseInfo returned information about new and modified mesh elements + * @return Ok on success, or enum value indicates why operation cannot be applied. Mesh remains unmodified on error. + */ + virtual EMeshResult CollapseEdge(int KeepVertID, int RemoveVertID, double EdgeParameterT, FEdgeCollapseInfo& CollapseInfo); + virtual EMeshResult CollapseEdge(int KeepVertID, int RemoveVertID, FEdgeCollapseInfo& CollapseInfo) + { + return CollapseEdge(KeepVertID, RemoveVertID, 0, CollapseInfo); + } + + + + /** Information about mesh elements modified by MergeEdges() */ + struct FMergeEdgesInfo + { + int KeptEdge; // the edge that was kept + int RemovedEdge; // the edge that was removed + + FIndex2i KeptVerts; // The two vertices that were kept (redundant w/ KeptEdge?) + FIndex2i RemovedVerts; // The removed vertices of RemovedEdge. Either may be InvalidID if it was same as the paired KeptVert + + FIndex2i ExtraRemovedEdges; // extra removed edges, see description below. Either may be or InvalidID + FIndex2i ExtraKeptEdges; // extra kept edges, paired with ExtraRemovedEdges + }; + + /** + * Given two edges of the mesh, weld both their vertices, so that one edge is removed. + * This could result in one neighbour edge-pair attached to each vertex also collapsing, + * so those cases are detected and handled (eg middle edge-pair in abysmal ascii drawing below) + * + * ._._._. (dots are vertices) + * \._./ + * + * @param KeepEdgeID index of the edge that should be kept + * @param DiscardEdgeID index of the edge that should be removed + * @param MergeInfo returned information about new and modified mesh elements + * @return Ok on success, or enum value indicates why operation cannot be applied. Mesh remains unmodified on error. + */ + virtual EMeshResult MergeEdges(int KeepEdgeID, int DiscardEdgeID, FMergeEdgesInfo& MergeInfo); + + + + /** Information about mesh elements modified/created by PokeTriangle() */ + struct FPokeTriangleInfo + { + int OriginalTriangle; // the triangle that was poked + FIndex3i TriVertices; // vertices of the original triangle + + int NewVertex; // the new vertex that was inserted + FIndex2i NewTriangles; // the two new triangles that were added (OriginalTriangle is re-used, see code for vertex orders) + FIndex3i NewEdges; // the three new edges connected to NewVertex + + FVector3d BaryCoords; // barycentric coords that NewVertex was inserted at + }; + + /** + * Insert a new vertex inside a triangle, ie do a 1 to 3 triangle split + * @param TriangleID index of triangle to poke + * @param BaryCoordinates barycentric coordinates of poke position + * @param PokeInfo returned information about new and modified mesh elements + * @return Ok on success, or enum value indicates why operation cannot be applied. Mesh remains unmodified on error. + */ + virtual EMeshResult PokeTriangle(int TriangleID, const FVector3d& BaryCoordinates, FPokeTriangleInfo& PokeInfo); + + /** Call PokeTriangle at the centroid of the triangle */ + virtual EMeshResult PokeTriangle(int TriangleID, FPokeTriangleInfo& PokeInfo) + { + return PokeTriangle(TriangleID, FVector3d::One() / 3.0, PokeInfo); + } + + // + // Debug utility functions + // +public: + /** + * Returns a debug string that contains mesh statistics and other information + */ + virtual FString MeshInfoString(); + + /** + * Check if another mesh is the same as this mesh. By default only checks + * vertices and triangles, turn on other parameters w/ flags + */ + virtual bool IsSameMesh(const FDynamicMesh3& OtherMesh, bool bCheckConnectivity, bool bCheckEdgeIDs = false, + bool bCheckNormals = false, bool bCheckColors = false, bool bCheckUVs = false, + bool bCheckGroups = false, + float Epsilon = TMathUtil::Epsilon); + + + /** + * Checks that the mesh is well-formed, ie all internal data structures are consistent + */ + virtual bool CheckValidity(bool bAllowNonManifoldVertices = false, EValidityCheckFailMode FailMode = EValidityCheckFailMode::Check) const; + + // + // Internal functions + // +protected: + + inline void SetTriangleInternal(int TriangleID, int v0, int v1, int v2) + { + int i = 3 * TriangleID; + Triangles[i] = v0; + Triangles[i + 1] = v1; + Triangles[i + 2] = v2; + } + inline void SetTriangleEdgesInternal(int TriangleID, int e0, int e1, int e2) + { + int i = 3 * TriangleID; + TriangleEdges[i] = e0; + TriangleEdges[i + 1] = e1; + TriangleEdges[i + 2] = e2; + } + + int AddEdgeInternal(int vA, int vB, int tA, int tB = InvalidID); + int AddTriangleInternal(int a, int b, int c, int e0, int e1, int e2); + + inline int ReplaceTriangleVertex(int TriangleID, int vOld, int vNew) + { + int i = 3 * TriangleID; + if (Triangles[i] == vOld) + { + Triangles[i] = vNew; + return 0; + } + if (Triangles[i + 1] == vOld) + { + Triangles[i + 1] = vNew; + return 1; + } + if (Triangles[i + 2] == vOld) + { + Triangles[i + 2] = vNew; + return 2; + } + return -1; + } + + inline void AllocateEdgesList(int VertexID) + { + if (VertexID < (int)VertexEdgeLists.Size()) + { + VertexEdgeLists.Clear(VertexID); + } + VertexEdgeLists.AllocateAt(VertexID); + } + + void GetVertexEdgesList(int VertexID, TArray& EdgesOut) const + { + for (int eid : VertexEdgeLists.Values(VertexID)) + { + EdgesOut.Add(eid); + } + } + + inline void SetEdgeVerticesInternal(int EdgeID, int a, int b) + { + int i = 4 * EdgeID; + if (a < b) + { + Edges[i] = a; + Edges[i + 1] = b; + } + else + { + Edges[i] = b; + Edges[i + 1] = a; + } + } + + inline void SetEdgeTrianglesInternal(int EdgeID, int t0, int t1) + { + int i = 4 * EdgeID; + Edges[i + 2] = t0; + Edges[i + 3] = t1; + } + + int ReplaceEdgeVertex(int EdgeID, int vOld, int vNew); + int ReplaceEdgeTriangle(int EdgeID, int tOld, int tNew); + int ReplaceTriangleEdge(int EdgeID, int eOld, int eNew); + + inline bool TriangleHasVertex(int TriangleID, int VertexID) const + { + int i = 3 * TriangleID; + return Triangles[i] == VertexID || Triangles[i + 1] == VertexID || Triangles[i + 2] == VertexID; + } + + inline bool TriHasNeighbourTri(int CheckTriID, int NbrTriID) const + { + int i = 3 * CheckTriID; + return EdgeHasTriangle(TriangleEdges[i], NbrTriID) || EdgeHasTriangle(TriangleEdges[i + 1], NbrTriID) || EdgeHasTriangle(TriangleEdges[i + 2], NbrTriID); + } + + inline bool TriHasSequentialVertices(int TriangleID, int vA, int vB) const + { + int i = 3 * TriangleID; + int v0 = Triangles[i], v1 = Triangles[i + 1], v2 = Triangles[i + 2]; + return ((v0 == vA && v1 == vB) || (v1 == vA && v2 == vB) || (v2 == vA && v0 == vB)); + } + + int FindTriangleEdge(int TriangleID, int vA, int vB) const; + + inline bool EdgeHasVertex(int EdgeID, int VertexID) const + { + int i = 4 * EdgeID; + return (Edges[i] == VertexID) || (Edges[i + 1] == VertexID); + } + inline bool EdgeHasTriangle(int EdgeID, int TriangleID) const + { + int i = 4 * EdgeID; + return (Edges[i + 2] == TriangleID) || (Edges[i + 3] == TriangleID); + } + + inline int GetOtherEdgeVertex(int EdgeID, int VertexID) const + { + int i = 4 * EdgeID; + int ev0 = Edges[i], ev1 = Edges[i + 1]; + return (ev0 == VertexID) ? ev1 : ((ev1 == VertexID) ? ev0 : InvalidID); + } + inline int GetOtherEdgeTriangle(int EdgeID, int TriangleID) const + { + int i = 4 * EdgeID; + int et0 = Edges[i + 2], et1 = Edges[i + 3]; + return (et0 == TriangleID) ? et1 : ((et1 == TriangleID) ? et0 : InvalidID); + } + + inline void AddTriangleEdge(int TriangleID, int v0, int v1, int j, int EdgeID) + { + if (EdgeID != InvalidID) + { + Edges[4 * EdgeID + 3] = TriangleID; + TriangleEdges.InsertAt(EdgeID, 3 * TriangleID + j); + } + else + { + TriangleEdges.InsertAt(AddEdgeInternal(v0, v1, TriangleID), 3 * TriangleID + j); + } + } + + void ReverseTriOrientationInternal(int TriangleID); + + void UpdateTimeStamp(bool bShapeChange, bool bTopologyChange) + { + Timestamp++; + if (bShapeChange) + { + ShapeTimestamp++; + } + if (bTopologyChange) + { + check(bShapeChange); // we consider topology change to be a shape change! + TopologyTimestamp++; + } + } + + + +}; + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshAABBTree3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshAABBTree3.h new file mode 100644 index 000000000000..2082ca9caa02 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshAABBTree3.h @@ -0,0 +1,6 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Spatial/MeshAABBTree3.h" +#include "DynamicMesh3.h" + +typedef TMeshAABBTree3 FDynamicMeshAABBTree3; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshAttributeSet.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshAttributeSet.h new file mode 100644 index 000000000000..31ba43ec4ddd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshAttributeSet.h @@ -0,0 +1,171 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#pragma once + +#include "DynamicMeshOverlay.h" + +/** Standard UV overlay type - 2-element float */ +typedef TDynamicMeshVectorOverlay FDynamicMeshUVOverlay; +/** Standard Normal overlay type - 3-element float */ +typedef TDynamicMeshVectorOverlay FDynamicMeshNormalOverlay; + + +/** + * FDynamicMeshAttributeSet manages a set of extended attributes for a FDynamicMesh3. + * This includes UV and Normal overlays, etc. + * + * Currently the default is to always have one UV layer and one Normal layer + * + * @todo current internal structure is a work-in-progress + */ +class DYNAMICMESH_API FDynamicMeshAttributeSet +{ +public: + + FDynamicMeshAttributeSet(FDynamicMesh3* Mesh) + : ParentMesh(Mesh), UV0(Mesh), Normals0(Mesh) + { + UVLayers.Add(&UV0); + NormalLayers.Add(&Normals0); + } + + virtual ~FDynamicMeshAttributeSet() + { + } + + void Copy(const FDynamicMeshAttributeSet& Copy) + { + UV0.Copy(Copy.UV0); + Normals0.Copy(Copy.Normals0); + // parent mesh is *not* copied! + } + + /** @return the parent mesh for this overlay */ + const FDynamicMesh3* GetParentMesh() const { return ParentMesh; } + /** @return the parent mesh for this overlay */ + FDynamicMesh3* GetParentMesh() { return ParentMesh; } + + + /** @return number of UV layers */ + virtual int NumUVLayers() const + { + return 1; + } + + /** @return number of Normals layers */ + virtual int NumNormalLayers() const + { + return 1; + } + + /** @return true if the given edge is a seam edge in any overlay */ + virtual bool IsSeamEdge(int EdgeID) const; + + + // + // UV Layers + // + + + /** @return the UV layer at the given Index */ + FDynamicMeshUVOverlay* GetUVLayer(int Index) + { + return (Index == 0) ? &UV0 : nullptr; + } + + /** @return the UV layer at the given Index */ + const FDynamicMeshUVOverlay* GetUVLayer(int Index) const + { + return (Index == 0) ? &UV0 : nullptr; + } + + /** @return list of all UV layers */ + const TArray& GetAllUVLayers() const + { + return UVLayers; + } + + /** @return the primary UV layer (layer 0) */ + FDynamicMeshUVOverlay* PrimaryUV() + { + return &UV0; + } + /** @return the primary UV layer (layer 0) */ + const FDynamicMeshUVOverlay* PrimaryUV() const + { + return &UV0; + } + + + // + // Normal Layers + // + + /** @return the Normal layer at the given Index */ + FDynamicMeshNormalOverlay* GetNormalLayer(int Index) + { + return (Index == 0) ? &Normals0 : nullptr; + } + + /** @return the Normal layer at the given Index */ + const FDynamicMeshNormalOverlay* GetNormalLayer(int Index) const + { + return (Index == 0) ? &Normals0 : nullptr; + } + + /** @return list of all Normal layers */ + const TArray& GetAllNormalLayers() const + { + return NormalLayers; + } + + /** @return the primary Normal layer (layer 0) */ + FDynamicMeshNormalOverlay* PrimaryNormals() + { + return &Normals0; + } + /** @return the primary Normal layer (layer 0) */ + const FDynamicMeshNormalOverlay* PrimaryNormals() const + { + return &Normals0; + } + + +protected: + /** Parent mesh of this attribute set */ + FDynamicMesh3* ParentMesh; + + /** Default UV layer */ + FDynamicMeshUVOverlay UV0; + /** Default Normals layer */ + FDynamicMeshNormalOverlay Normals0; + + TArray UVLayers; + TArray NormalLayers; + + +protected: + friend class FDynamicMesh3; + + /** + * Initialize the existing attribute layers with the given vertex and triangle sizes + */ + void Initialize(int MaxVertexID, int MaxTriangleID) + { + UV0.InitializeTriangles(MaxTriangleID); + Normals0.InitializeTriangles(MaxTriangleID); + } + + // These functions are called by the FDynamicMesh3 to update the various + // attributes when the parent mesh topology has been modified. + virtual void OnNewTriangle(int TriangleID, bool bInserted); + virtual void OnRemoveTriangle(int TriangleID, bool bRemoveIsolatedVertices); + virtual void OnReverseTriOrientation(int TriangleID); + virtual void OnSplitEdge(const FDynamicMesh3::FEdgeSplitInfo & splitInfo); + virtual void OnFlipEdge(const FDynamicMesh3::FEdgeFlipInfo & flipInfo); + virtual void OnCollapseEdge(const FDynamicMesh3::FEdgeCollapseInfo & collapseInfo); + virtual void OnPokeTriangle(const FDynamicMesh3::FPokeTriangleInfo & pokeInfo); + virtual void OnMergeEdges(const FDynamicMesh3::FMergeEdgesInfo & mergeInfo); +}; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshEditor.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshEditor.h new file mode 100644 index 000000000000..317c950beb12 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshEditor.h @@ -0,0 +1,304 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp MeshEditor + +#pragma once + +#include "DynamicMesh3.h" +#include "EdgeLoop.h" + + + + +/** + * FMeshIndexMappings stores a set of integer IndexMaps for a mesh + * This is a convenient object to have, to avoid passing around large numbers of separate maps. + * The individual maps are not necessarily all filled by every operation. + */ +struct DYNAMICMESH_API FMeshIndexMappings +{ +protected: + FIndexMapi VertexMap; + FIndexMapi TriangleMap; + FIndexMapi GroupMap; + + TArray UVMaps; + TArray NormalMaps; + +public: + /** Size internal arrays-of-maps to be suitable for this Mesh */ + void Initialize(FDynamicMesh3* Mesh); + + /** @return the value used to indicate "invalid" in the mapping */ + int InvalidID() { return VertexMap.GetInvalidID(); } + + void Reset() + { + VertexMap.Reset(); + TriangleMap.Reset(); + GroupMap.Reset(); + for (FIndexMapi& UVMap : UVMaps) + { + UVMap.Reset(); + } + for (FIndexMapi& NormalMap : NormalMaps) + { + NormalMap.Reset(); + } + } + + FIndexMapi& GetVertexMap() { return VertexMap; } + inline void SetVertex(int FromID, int ToID) { VertexMap.Add(FromID, ToID); } + inline int GetNewVertex(int FromID) const { return VertexMap.GetTo(FromID); } + inline bool ContainsVertex(int FromID) const { return VertexMap.ContainsFrom(FromID); } + + FIndexMapi& GetTriangleMap() { return TriangleMap; } + void SetTriangle(int FromID, int ToID) { TriangleMap.Add(FromID, ToID); } + int GetNewTriangle(int FromID) const { return TriangleMap.GetTo(FromID); } + inline bool ContainsTriangle(int FromID) const { return TriangleMap.ContainsFrom(FromID); } + + FIndexMapi& GetGroupMap() { return GroupMap; } + void SetGroup(int FromID, int ToID) { GroupMap.Add(FromID, ToID); } + int GetNewGroup(int FromID) const { return GroupMap.GetTo(FromID); } + inline bool ContainsGroup(int FromID) const { return GroupMap.ContainsFrom(FromID); } + + FIndexMapi& GetUVMap(int UVLayer) { return UVMaps[UVLayer]; } + void SetUV(int UVLayer, int FromID, int ToID) { UVMaps[UVLayer].Add(FromID, ToID); } + int GetNewUV(int UVLayer, int FromID) const { return UVMaps[UVLayer].GetTo(FromID); } + inline bool ContainsUV(int UVLayer, int FromID) const { return UVMaps[UVLayer].ContainsFrom(FromID); } + + FIndexMapi& GetNormalMap(int NormalLayer) { return NormalMaps[NormalLayer]; } + void SetNormal(int NormalLayer, int FromID, int ToID) { NormalMaps[NormalLayer].Add(FromID, ToID); } + int GetNewNormal(int NormalLayer, int FromID) const { return NormalMaps[NormalLayer].GetTo(FromID); } + inline bool ContainsNormal(int NormalLayer, int FromID) const { return NormalMaps[NormalLayer].ContainsFrom(FromID); } + +}; + + +/** + * FDynamicMeshEditResult is used to return information about new mesh elements + * created by mesh changes, primarily in FDynamicMeshEditor + */ +struct DYNAMICMESH_API FDynamicMeshEditResult +{ + /** New vertices created by an edit */ + TArray NewVertices; + + /** New triangles created by an edit. Note that this list may be empty if the operation created quads or polygons */ + TArray NewTriangles; + /** New quads created by an edit, where each quad is a pair of triangle IDs */ + TArray NewQuads; + /** New polygons created by an edit, where each polygon is a list of triangle IDs */ + TArray> NewPolygons; + + /** New triangle groups created by an edit */ + TArray NewGroups; + + /** clear this data structure */ + void Reset() + { + NewVertices.Reset(); + NewTriangles.Reset(); + NewQuads.Reset(); + NewPolygons.Reset(); + NewGroups.Reset(); + } + + /** Flatten the triangle/quad/polygon lists into a single list of all triangles */ + void GetAllTriangles(TArray& TrianglesOut) const; +}; + + + + +/** + * FDynamicMeshEditor implements low-level mesh editing operations. These operations + * can be used to construct higher-level operations. For example an Extrude operation + * could be implemented via DuplicateTriangles() and StitchLoopMinimal(). + */ +class DYNAMICMESH_API FDynamicMeshEditor +{ +public: + /** The mesh we will be editing */ + FDynamicMesh3* Mesh; + + FDynamicMeshEditor(FDynamicMesh3* MeshIn) + { + Mesh = MeshIn; + } + + + ////////////////////////////////////////////////////////////////////////// + // Create and Remove Triangle Functions + ////////////////////////////////////////////////////////////////////////// + + /** + * Stitch together two loops of vertices with a quad-strip of triangles. + * Loops must be oriented (ordered) correctly for your use case. + * @param Loop1 first loop of sequential vertices + * @param Loop2 second loop of sequential vertices + * @param ResultOut lists of newly created triangles/vertices/etc + * @return true if operation suceeded. If a failure occurs, any added triangles are removed via RemoveTriangles + */ + bool StitchVertexLoopsMinimal(const TArray& VertexLoop1, const TArray& VertexLoop2, FDynamicMeshEditResult& ResultOut); + + + /** + * Duplicate triangles of a mesh. This duplicates the current groups and also any attributes existing on the triangles. + * @param Triangles the triangles to duplicate + * @param IndexMaps returned mappings from old to new triangles/vertices/etc (you may initialize to optimize memory usage, etc) + * @param ResultOut lists of newly created triangles/vertices/etc + */ + void DuplicateTriangles(const TArray& Triangles, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut); + + + /** + * Pair of associated edge loops. + */ + struct FLoopPairSet + { + FEdgeLoop LoopA; + FEdgeLoop LoopB; + }; + + /** + * Finds boundary loops of connected components of a set of triangles, and duplicates the vertices + * along the boundary, such that the triangles become disconnected. + * @param Triangles set of triangles + * @param LoopSetOut set of boundary loops. LoopA is original loop which remains with "outer" triangles, and LoopB is new boundary loop of triangle set + * @return true on success + */ + bool DisconnectTriangles(const TArray& Triangles, TArray& LoopSetOut); + + + /** + * Remove a list of triangles from the mesh, and optionally any vertices that are now orphaned + * @param Triangles the triangles to remove + * @param bRemoveIsolatedVerts if true, remove vertices that end up with no triangles + * @return true if all removes succeeded + */ + bool RemoveTriangles(const TArray& Triangles, bool bRemoveIsolatedVerts); + + + ////////////////////////////////////////////////////////////////////////// + // Normal utility functions + ////////////////////////////////////////////////////////////////////////// + + /** + * Reverse the orientation of the given triangles, and optionally flip relevant normals + * @param Triangles the triangles to modify + * @param bInvertNormals if ture we call InvertTriangleNormals() + */ + void ReverseTriangleOrientations(const TArray& Triangles, bool bInvertNormals); + + /** + * Flip the normals of the given triangles. This includes their vertex normals, if they + * exist, as well as any per-triangle attribute normals. @todo currently creates full-mesh bit arrays, could be more efficient on subsets + * @param Triangles the triangles to modify + */ + void InvertTriangleNormals(const TArray& Triangles); + + + /** + * Calculate and set the per-triangle normals of the two input quads. + * Average of the two face normals is used unless the quad is planar + * @param QuadTris pair of triangle IDs. If second ID is invalid, it is ignored + * @param bIsPlanar if the quad is known to be planar, operation is more efficient + * @return the normal vector that was set + */ + FVector3f ComputeAndSetQuadNormal(const FIndex2i& QuadTris, bool bIsPlanar = false); + + + /** + * Create and set new shared per-triangle normals for a pair of triangles that share one edge (ie a quad) + * @param QuadTris pair of triangle IDs. If second ID is invalid, it is ignored + * @param Normal normal vector to set + */ + void SetQuadNormals(const FIndex2i& QuadTris, const FVector3f& Normal); + + /** + * Create and set new shared per-triangle normals for a list of triangles + * @param Triangles list of triangle IDs + * @param Normal normal vector to set + */ + void SetTriangleNormals(const TArray& Triangles, const FVector3f& Normal); + + + ////////////////////////////////////////////////////////////////////////// + // UV utility functions + ////////////////////////////////////////////////////////////////////////// + + + /** + * Project the two triangles of the quad onto a plane defined by the normal and use that to create/set new shared per-triangle UVs. + * UVs are translated so that their bbox min-corner is at origin, and scaled by given scale factor + * @param QuadTris pair of triangle IDs. If second ID is invalid, it is ignored + * @param ProjectFrame vertices are projected into XY axes of this frame + * @param UVScaleFactor UVs are scaled + * @param UVLayerIndex which UV layer to operate on (must exist) + */ + void SetQuadUVsFromProjection(const FIndex2i& QuadTris, const FFrame3f& ProjectFrame, float UVScaleFactor = 1.0f, int UVLayerIndex = 0); + + + + ////////////////////////////////////////////////////////////////////////// + // mesh element copying / duplication + ////////////////////////////////////////////////////////////////////////// + + + /** + * Find "new" vertex for input vertex under Index mapping, or create new if missing + * @param VertexID the source vertex we want a copy of + * @param IndexMaps source/destination mapping of already-duplicated vertices + * @param ResultOut newly-created vertices are stored here + * @return index of duplicate vertex + */ + int FindOrCreateDuplicateVertex(int VertexID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut); + + /** + * Find "new" group for input group under Index mapping, or create new if missing + * @param TriangleID the source triangle whose group we want a copy of + * @param IndexMaps source/destination mapping of already-duplicated groups + * @param ResultOut newly-created groups are stored here + * @return index of duplicate group + */ + int FindOrCreateDuplicateGroup(int TriangleID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut); + + /** + * Find "new" UV for input UV element under Index mapping, or create new if missing + * @param ElementID the source UV we want a duplicate of + * @param UVLayerIndex which UV layer to consider + * @param IndexMaps source/destination mapping of already-duplicated UVs + * @return index of duplicate UV in given UV layer + */ + int FindOrCreateDuplicateUV(int ElementID, int UVLayerIndex, FMeshIndexMappings& IndexMaps); + + /** + * Find "new" normal for input normal element under Index mapping, or create new if missing + * @param ElementID the source normal we want a duplicate of + * @param NormalLayerIndex which normal layer to consider + * @param IndexMaps source/destination mapping of already-duplicated normals + * @return index of duplicate normal in given normal layer + */ + int FindOrCreateDuplicateNormal(int ElementID, int NormalLayerIndex, FMeshIndexMappings& IndexMaps); + + + /** + * Copy all attribute-layer values from one triangle to another, using the IndexMaps to track and re-use shared attribute values. + * @param FromTriangleID source triangle + * @param ToTriangleID destination triangle + * @param IndexMaps mappings passed to FindOrCreateDuplicateX functions to track already-created attributes + * @param ResultOut information about new attributes is stored here (@todo populate this, at time of writing there are no attribute fields) + */ + void CopyAttributes(int FromTriangleID, int ToTriangleID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut); + + + + + void AppendMesh(const FDynamicMesh3* AppendMesh, FMeshIndexMappings& IndexMapsOut, FDynamicMeshEditResult& ResultOut, + TFunction PositionTransform = nullptr, + TFunction NormalTransform = nullptr); + +}; + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshModule.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshModule.h new file mode 100644 index 000000000000..34ea25d2c6b1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshModule.h @@ -0,0 +1,15 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FDynamicMeshModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshOverlay.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshOverlay.h new file mode 100644 index 000000000000..b2d5ad9f65a8 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshOverlay.h @@ -0,0 +1,282 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#pragma once + +#include "Util/DynamicVector.h" +#include "Util/RefCountVector.h" +#include "VectorTypes.h" +#include "GeometryTypes.h" +#include "DynamicMesh3.h" + + +/** + * TDynamicMeshOverlay is an add-on to a FDynamicMesh3 that allows for per-triangle storage + * of an "element" (eg like a per-triangle UV or normal). However the elements can be shared + * between triangles because they are stored in a separate indexable list. As a result, + * the overlay has it's own topology, IE there may be "seam" boundary edges in the + * overlay topology that are not mesh boundary edges in the associated/parent mesh. + * + * A "seam" edge is one where at least one of the elements of the triangles on either + * side of the edge is not shared between the two triangles. + * + * The FDynamicMesh3 mesh topology operations (eg split/flip/collapse edge, poke face, etc) + * can be mirrored to the overlay via OnSplitEdge(), etc. + * + * Note that although this is a template, many of the functions are defined in the .cpp file. + * As a result you need to explicitly instantiate and export the instance of the template that + * you wish to use in the block at the top of DynamicMeshOverlay.cpp + */ +template +class TDynamicMeshOverlay +{ + +protected: + /** The parent mesh this overlay belongs to */ + FDynamicMesh3* ParentMesh; + + /** Reference counts of element indices. Iterate over this to find out which elements are valid. */ + FRefCountVector ElementsRefCounts; + /** List of element values */ + TDynamicVector Elements; + /** List of parent vertex indices, one per element */ + TDynamicVector ParentVertices; + + /** List of triangle element-index triplets [Elem0 Elem1 Elem2]*/ + TDynamicVector ElementTriangles; + + friend class FDynamicMesh3; + friend class FDynamicMeshAttributeSet; + +public: + /** Create an empty overlay */ + TDynamicMeshOverlay() + { + ParentMesh = nullptr; + } + + /** Create an overlay for the given parent mesh */ + TDynamicMeshOverlay(FDynamicMesh3* ParentMeshIn) + { + ParentMesh = ParentMeshIn; + } + + /** @return the parent mesh for this overlay */ + const FDynamicMesh3* GetParentMesh() const { return ParentMesh; } + /** @return the parent mesh for this overlay */ + FDynamicMesh3* GetParentMesh() { return ParentMesh; } + + /** Set this overlay to contain the same arrays as the copy overlay */ + void Copy(const TDynamicMeshOverlay& Copy) + { + ElementsRefCounts = FRefCountVector(Copy.ElementsRefCounts); + Elements = Copy.Elements; + ParentVertices = Copy.ParentVertices; + ElementTriangles = Copy.ElementTriangles; + } + + /** Discard current set of elements, but keep triangles */ + void ClearElements(); + + /** @return the number of in-use Elements in the overlay */ + int ElementCount() const { return (int)ElementsRefCounts.GetCount(); } + /** @return the maximum element index in the overlay. This may be larger than the count if Elements have been deleted. */ + int MaxElementID() const { return (int)ElementsRefCounts.GetMaxIndex(); } + /** @return true if this element index is in use */ + inline bool IsElement(int vID) const { return ElementsRefCounts.IsValid(vID); } + + + typedef typename FRefCountVector::IndexEnumerable element_iterator; + + /** @return enumerator for valid element indices suitable for use with range-based for */ + element_iterator ElementIndicesItr() const { return ElementsRefCounts.Indices(); } + + + /** Allocate a new element with the given constant value and parent-mesh vertex */ + int AppendElement(RealType ConstantValue, int ParentVertex); + /** Allocate a new element with the given value and parent-mesh vertex */ + int AppendElement(const RealType* Value, int ParentVertex); + + /** Initialize the triangle list to the given size, and set all triangles to InvalidID */ + void InitializeTriangles(int MaxTriangleID); + /** Set the triangle to the given Element index tuple, and increment element reference counts */ + EMeshResult SetTriangle(int TriangleID, const FIndex3i& TriElements); + + /** Get the element at a given index */ + inline void GetElement(int ElementID, RealType* Data) const + { + int k = ElementID * ElementSize; + for (int i = 0; i < ElementSize; ++i) + { + Data[i] = Elements[k + i]; + } + } + + /** Get the element at a given index */ + template + void GetElement(int ElementID, AsType& Data) const + { + int k = ElementID * ElementSize; + for (int i = 0; i < ElementSize; ++i) + { + Data[i] = Elements[k + i]; + } + } + + + /** Get the parent vertex id for the element at a given index */ + inline int GetParentVertex(int ElementID) const + { + return ParentVertices[ElementID]; + } + + + /** Get the element index tuple for a triangle */ + inline FIndex3i GetTriangle(int TriangleID) const + { + int i = 3 * TriangleID; + return FIndex3i(ElementTriangles[i], ElementTriangles[i + 1], ElementTriangles[i + 2]); + } + + + /** Set the element at a given index */ + inline void SetElement(int ElementID, const RealType* Data) + { + int k = ElementID * ElementSize; + for (int i = 0; i < ElementSize; ++i) + { + Elements[k + i] = Data[i]; + } + } + + /** Set the element at a given index */ + template + void SetElement(int ElementID, const AsType& Data) + { + int k = ElementID * ElementSize; + for (int i = 0; i < ElementSize; ++i) + { + Elements[k + i] = Data[i]; + } + } + + + + /** Returns true if the parent-mesh edge is a "Seam" in this overlay */ + bool IsSeamEdge(int EdgeID) const; + /** Returns true if the parent-mesh vertex is connected to any seam edges */ + bool IsSeamVertex(int VertexID) const; + + /** find the elements associated with a given parent-mesh vertex */ + void GetVertexElements(int VertexID, TArray& OutElements) const; + /** Count the number of unique elements for a given parent-mesh vertex */ + int CountVertexElements(int VertexID, bool bBruteForce = false) const; + + /** + * Checks that the overlay mesh is well-formed, ie all internal data structures are consistent + */ + bool CheckValidity(bool bAllowNonManifoldVertices = true, EValidityCheckFailMode FailMode = EValidityCheckFailMode::Check) const; + + +public: + /** Set a triangle's element indices to InvalidID */ + void InitializeNewTriangle(int TriangleID); + /** Remove a triangle from the overlay */ + void OnRemoveTriangle(int TriangleID, bool bRemoveIsolatedVertices); + /** Reverse the orientation of a triangle's elements */ + void OnReverseTriOrientation(int TriangleID); + /** Update the overlay to reflect an edge split in the parent mesh */ + void OnSplitEdge(const FDynamicMesh3::FEdgeSplitInfo& SplitInfo); + /** Update the overlay to reflect an edge flip in the parent mesh */ + void OnFlipEdge(const FDynamicMesh3::FEdgeFlipInfo& FlipInfo); + /** Update the overlay to reflect an edge collapse in the parent mesh */ + void OnCollapseEdge(const FDynamicMesh3::FEdgeCollapseInfo& CollapseInfo); + /** Update the overlay to reflect a face poke in the parent mesh */ + void OnPokeTriangle(const FDynamicMesh3::FPokeTriangleInfo& PokeInfo); + /** Update the overlay to reflect an edge merge in the parent mesh */ + void OnMergeEdges(const FDynamicMesh3::FMergeEdgesInfo& MergeInfo); + +protected: + /** Set the value at an Element to be a linear interpolation of two other Elements */ + void SetElementFromLerp(int SetElement, int ElementA, int ElementB, RealType Alpha); + /** Set the value at an Element to be a barycentric interpolation of three other Elements */ + void SetElementFromBary(int SetElement, int ElementA, int ElementB, int ElementC, const FVector3& BaryCoords); + + /** updates the triangles array and optionally the element reference counts */ + void InternalSetTriangle(int TriangleID, const FIndex3i& TriElements, bool bIncrementRefCounts); +}; + + + + + + +/** + * TDynamicMeshVectorOverlay is an convenient extension of TDynamicMeshOverlay that adds + * a specific N-element Vector type to the template, and adds accessor functions + * that convert between that N-element vector type and the N-element arrays used by TDynamicMeshOverlay. + */ +template +class TDynamicMeshVectorOverlay : public TDynamicMeshOverlay +{ +public: + using BaseType = TDynamicMeshOverlay; + + TDynamicMeshVectorOverlay() + : TDynamicMeshOverlay() + { + } + + TDynamicMeshVectorOverlay(FDynamicMesh3* parentMesh) + : TDynamicMeshOverlay(parentMesh) + { + } + + /** + * Append a new Element to the overlay + */ + inline int AppendElement(const VectorType& Value, int SourceVertex) + { + return BaseType::AppendElement((const RealType*)Value, SourceVertex); + } + + /** + * Get Element at a specific ID + */ + inline VectorType GetElement(int ElementID) const + { + VectorType V; + BaseType::GetElement(ElementID, V); + return V; + } + + /** + * Get Element at a specific ID + */ + inline void GetElement(int ElementID, VectorType& V) const + { + BaseType::GetElement(ElementID, V); + } + + /** + * Get the three Elements associated with a triangle + */ + inline void GetTriElements(int TriangleID, VectorType& A, VectorType& B, VectorType& C) const + { + int i = 3 * TriangleID; + GetElement(BaseType::ElementTriangles[i], A); + GetElement(BaseType::ElementTriangles[i+1], B); + GetElement(BaseType::ElementTriangles[i+2], C); + } + + + /** + * Set Element at a specific ID + */ + inline void SetElement(int ElementID, const VectorType& Value) + { + BaseType::SetElement(ElementID, Value); + } +}; + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/EdgeLoop.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/EdgeLoop.h new file mode 100644 index 000000000000..ee9f1f3be222 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/EdgeLoop.h @@ -0,0 +1,204 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp EdgeLoop + +#pragma once + +#include "DynamicMesh3.h" +#include "Util/IndexUtil.h" + + +/** + * Sequential lists of vertices/edges in a mesh that form a closed loop + */ +class DYNAMICMESH_API FEdgeLoop +{ +public: + /** The Mesh that contains this EdgeLoop */ + const FDynamicMesh3* Mesh; + + /** Ordered list of vertices forming the EdgeLoop */ + TArray Vertices; + /** Ordered list of edges forming the EdgeLoop */ + TArray Edges; + + /** List of bowtie vertices. This list is only valid if bBowtiesCalculated = true. */ + TArray BowtieVertices; + + /** If true, then BowtieVertices list is valid */ + bool bBowtiesCalculated = false; + + FEdgeLoop() + { + Mesh = nullptr; + } + + FEdgeLoop(const FDynamicMesh3* mesh) + { + Mesh = mesh; + } + + /** + * Initialize EdgeLoop with the given vertices and edges + */ + FEdgeLoop(const FDynamicMesh3* mesh, const TArray& vertices, const TArray & edges) + { + Mesh = mesh; + Vertices = vertices; + Edges = edges; + } + + + /** + * Initialize the loop data + */ + void Initialize(const FDynamicMesh3* mesh, const TArray& vertices, const TArray & edges, const TArray* BowtieVerticesIn = nullptr); + + /** + * Construct an FEdgeLoop from a list of edges of the mesh + * @param EdgesIn list of sequential connected edges + */ + void InitializeFromEdges(const TArray& EdgesIn); + + /** + * Construct an FEdgeLoop from a list of edges of the mesh + * @param MeshIn the mesh the edges exist on + * @param EdgesIn list of sequential connected edges + */ + void InitializeFromEdges(const FDynamicMesh3* MeshIn, const TArray& EdgesIn) + { + Mesh = MeshIn; + InitializeFromEdges(EdgesIn); + } + + + + /** + * Construct EdgeLoop from list of vertices of mesh + * @param VerticesIn list of vertices that are sequentially connected by edges + * @param bAutoOrient if true, and any of the edges are boundary edges, we will re-orient the loop to be consistent with boundary edges + * @return false if we found any parts of vertices that are not connected by an edge + */ + bool InitializeFromVertices(const TArray& VerticesIn, bool bAutoOrient = true); + + /** + * Construct EdgeLoop from list of vertices of mesh + * @param MeshIn Mesh that contains the loop + * @param VerticesIn list of vertices that are sequentially connected by edges + * @param bAutoOrient if true, and any of the edges are boundary edges, we will re-orient the loop to be consistent with boundary edges + * @return false if we found any parts of vertices that are not connected by an edge + */ + bool InitializeFromVertices(const FDynamicMesh3* MeshIn, const TArray& VerticesIn, bool bAutoOrient = true) + { + Mesh = MeshIn; + return InitializeFromVertices(VerticesIn, bAutoOrient); + } + + + + + /** Set the bowtie vertices */ + void SetBowtieVertices(const TArray& Bowties) + { + BowtieVertices = Bowties; + bBowtiesCalculated = true; + } + + + /** + * Populate the BowtieVertices member + */ + void CalculateBowtieVertices(); + + /** + * @return number of vertices in the loop + */ + int GetVertexCount() const + { + return Vertices.Num(); + } + + /** + * @return number of edges in the loop + */ + int GetEdgeCount() const + { + return Edges.Num(); + } + + /** + * @return vertex position in loop at the given LoopIndex + */ + inline FVector3d GetVertex(int LoopIndex) const + { + return Mesh->GetVertex(Vertices[LoopIndex]); + } + + /** + * @return bounding box of the vertices of the EdgeLoop + */ + FAxisAlignedBox3d GetBounds() const; + + + /** + * If any edges if the loop are on a mesh boundary, we can check that the loop is + * oriented such that it corresponds with the boundary edges, and if not, reverse it. + * @return true if the loop was reversed + */ + bool SetCorrectOrientation(); + + + /** + * Reverse order of vertices and edges in loop + */ + void Reverse() + { + Algo::Reverse(Vertices); + Algo::Reverse(Edges); + } + + + /** + * @return true if all edges of this loop are internal edges, ie not on the mesh boundary + */ + bool IsInternalLoop() const; + + + /** + * @param TestMesh use this mesh instead of the internal Mesh pointer + * @return true if all edges of this loop are boundary edges + */ + bool IsBoundaryLoop(const FDynamicMesh3* TestMesh = nullptr) const; + + + /** + * @return index of VertexID in the Vertices list, or -1 if not found + */ + int FindVertexIndex(int VertexID) const; + + + /** + * @return index of vertex in the Vertices list that is closest to QueryPoint + */ + int FindNearestVertexIndex(const FVector3d& QueryPoint) const; + + + + /** + * Exhaustively check that verts and edges of this EdgeLoop are consistent. This is quite expensive. + * @return true if loop is consistent/valid + */ + bool CheckValidity(EValidityCheckFailMode FailMode = EValidityCheckFailMode::Check) const; + + + + + /** + * Utility function to convert a vertex loop to an edge loop + * @param Mesh mesh to operate on + * @param VertexLoop ordered list of vertices + * @param OutEdgeLoop computed list of sequential connected vertices + */ + static void VertexLoopToEdgeLoop(const FDynamicMesh3* Mesh, const TArray& VertexLoop, TArray& OutEdgeLoop); + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/EdgeSpan.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/EdgeSpan.h new file mode 100644 index 000000000000..c4eafb723088 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/EdgeSpan.h @@ -0,0 +1,207 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp EdgeSpan + +#pragma once + +#include "DynamicMesh3.h" +#include "Util/IndexUtil.h" +#include "Polyline3.h" + + +/** + * Sequential lists of vertices/edges in a mesh that is *not* closed. + * If the list is closed it should be an FEdgeLoop + */ +class DYNAMICMESH_API FEdgeSpan +{ +public: + /** The Mesh that contains this EdgeSpan */ + const FDynamicMesh3* Mesh; + + /** Ordered list of vertices forming the EdgeSpan */ + TArray Vertices; + /** Ordered list of edges forming the EdgeSpan */ + TArray Edges; + + /** List of bowtie vertices. This list is only valid if bBowtiesCalculated = true. */ + TArray BowtieVertices; + + /** If true, then BowtieVertices list is valid */ + bool bBowtiesCalculated = false; + + FEdgeSpan() + { + Mesh = nullptr; + } + + FEdgeSpan(const FDynamicMesh3* mesh) + { + Mesh = mesh; + } + + /** + * Initialize EdgeSpan with the given vertices and edges + */ + FEdgeSpan(const FDynamicMesh3* mesh, const TArray& vertices, const TArray & edges) + { + Mesh = mesh; + Vertices = vertices; + Edges = edges; + } + + + /** + * Initialize the loop data + */ + void Initialize(const FDynamicMesh3* mesh, const TArray& vertices, const TArray & edges, const TArray* BowtieVerticesIn = nullptr); + + + /** + * Construct an FEdgeSpan from a list of edges of the mesh + * @param EdgesIn list of sequential connected edges + */ + void InitializeFromEdges(const TArray& EdgesIn); + + + /** + * Construct an FEdgeSpan from a list of edges of the mesh + * @param MeshIn the mesh the edges exist on + * @param EdgesIn list of sequential connected edges + */ + void InitializeFromEdges(const FDynamicMesh3* MeshIn, const TArray& EdgesIn) + { + Mesh = MeshIn; + InitializeFromEdges(EdgesIn); + } + + + /** + * Construct EdgeSpan from list of vertices of mesh + * @param MeshIn Mesh that contains the span + * @param VerticesIn list of vertices that are sequentially connected by edges + * @param bAutoOrient if true, and any of the edges are boundary edges, we will re-orient the span to be consistent with boundary edges + * @return false if we found any parts of VerticesIn that are not connected by an edge + */ + bool InitializeFromVertices(const FDynamicMesh3* MeshIn, const TArray& VerticesIn, bool bAutoOrient = true) + { + Mesh = MeshIn; + return InitializeFromVertices(VerticesIn, bAutoOrient); + } + + + /** + * Construct EdgeSpan from list of vertices of mesh + * @param VerticesIn list of vertices that are sequentially connected by edges + * @param bAutoOrient if true, and any of the edges are boundary edges, we will re-orient the span to be consistent with boundary edges + * @return false if we found any parts of VerticesIn that are not connected by an edge + */ + bool InitializeFromVertices(const TArray& VerticesIn, bool bAutoOrient = true); + + + /** Set the bowtie vertices */ + void SetBowtieVertices(const TArray& Bowties) + { + BowtieVertices = Bowties; + bBowtiesCalculated = true; + } + + + /** + * Populate the BowtieVertices member + */ + void CalculateBowtieVertices(); + + + /** + * @return number of vertices in the span + */ + int GetVertexCount() const + { + return Vertices.Num(); + } + + /** + * @return number of edges in the span + */ + int GetEdgeCount() const + { + return Edges.Num(); + } + + /** + * @return vertex position in span at the given SpanIndex + */ + FVector3d GetVertex(int SpanIndex) const + { + return Mesh->GetVertex(Vertices[SpanIndex]); + } + + /** + * @return bounding box of the vertices of the EdgeSpan + */ + FAxisAlignedBox3d GetBounds() const; + + + /** + * Extract Polyline from Mesh based on vertex list + */ + void GetPolyline(FPolyline3d& PolylineOut) const; + + /** + * If any edges if the span are on a mesh boundary, we can check that the span is + * oriented such that it corresponds with the boundary edges, and if not, reverse it. + * @return true if the span was reversed + */ + bool SetCorrectOrientation(); + + + /** + * Reverse order of vertices and edges in span + */ + void Reverse() + { + Algo::Reverse(Vertices); + Algo::Reverse(Edges); + } + + + /** + * @return true if all edges of this span are internal edges, ie not on the mesh boundary + */ + bool IsInternalspan() const; + + /** + * @param TestMesh use this mesh instead of the internal Mesh pointer + * @return true if all edges of this span are boundary edges + */ + bool IsBoundaryspan(const FDynamicMesh3* TestMesh = nullptr) const; + + /** + * @return index of VertexID in the Vertices list, or -1 if not found + */ + int FindVertexIndex(int VertexID) const; + + /** + * @return index of vertex in the Vertices list that is closest to QueryPoint + */ + int FindNearestVertexIndex(const FVector3d& QueryPoint) const; + + + /** + * Exhaustively check that verts and edges of this EdgeSpan are consistent. This is quite expensive. + * @return true if span is consistent/valid + */ + bool CheckValidity(EValidityCheckFailMode FailMode = EValidityCheckFailMode::Check) const; + + + + /** + * Utility function to convert a vertex span to an edge span + * @param Mesh mesh to operate on + * @param Vertexspan ordered list of vertices + * @param OutEdgeSpan computed list of sequential connected vertices + */ + static void VertexSpanToEdgeSpan(const FDynamicMesh3* Mesh, const TArray& VertexSpan, TArray& OutEdgeSpan); + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/MeshShapeGenerator.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/MeshShapeGenerator.h new file mode 100644 index 000000000000..c4fb685b074a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/MeshShapeGenerator.h @@ -0,0 +1,253 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "VectorTypes.h" +#include "IndexTypes.h" + +/** + * Base class for triangle mesh generators (eg like to generate sphere, cylinder, etc) + * Subclasses must implement ::Generate() + */ +class DYNAMICMESH_API FMeshShapeGenerator +{ +public: + /** Array of vertex positions */ + TArray Vertices; + + /** Array of UV positions. These are completely independent of vertex list (ie not per-vertex unless that's what generator produces) */ + TArray UVs; + /** Parent vertex index for each UV. Same length as UVs array. */ + TArray UVParentVertex; + + /** Array of Normals. These are completely independent of vertex list (ie not per-vertex unless that's what generator produces) */ + TArray Normals; + /** Parent vertex index for each Normal. Same length as Normals array. */ + TArray NormalParentVertex; + + /** Array of triangle corner positions, stored as tuples of indices into Vertices array */ + TArray Triangles; + /** Array of triangle corner UVs, stored as tuples of indices into UVs array. Same length as Triangles array. */ + TArray TriangleUVs; + /** Array of triangle corner Normals, stored as tuples of indices into Normals array. Same length as Triangles array. */ + TArray TriangleNormals; + /** ARray of per-triangle integer polygon IDs. Same length as Triangles array. */ + TArray TrianglePolygonIDs; + + +public: + /** If true, reverse orientation of created mesh */ + bool bReverseOrientation = false; + + virtual ~FMeshShapeGenerator() + { + } + + + /** + * Subclasses implement this to generate mesh + */ + virtual FMeshShapeGenerator& Generate() = 0; + + + /** clear arrays so that Generate() can be run again */ + void Reset() + { + Vertices.Reset(); + UVs.Reset(); + UVParentVertex.Reset(); + Normals.Reset(); + NormalParentVertex.Reset(); + Triangles.Reset(); + TriangleUVs.Reset(); + TriangleNormals.Reset(); + TrianglePolygonIDs.Reset(); + } + + + /** + * Set the various internal buffers to the correct sizes for the given element counts + */ + void SetBufferSizes(int NumVertices, int NumTriangles, int NumUVs, int NumNormals) + { + if (NumVertices > 0) + { + Vertices.SetNum(NumVertices); + } + if (NumTriangles > 0) + { + Triangles.SetNum(NumTriangles); + TriangleUVs.SetNum(NumTriangles); + TriangleNormals.SetNum(NumTriangles); + TrianglePolygonIDs.SetNum(NumTriangles); + } + if (NumUVs > 0) + { + UVs.SetNum(NumUVs); + UVParentVertex.SetNum(NumUVs); + } + if (NumNormals > 0) + { + Normals.SetNum(NumNormals); + NormalParentVertex.SetNum(NumNormals); + } + } + /** + * Extends the various internal buffers to the correct sizes for the given additional element counts + */ + void ExtendBufferSizes(int AddVertices, int AddTriangles, int AddUVs, int AddNormals) + { + if (AddVertices > 0) + { + int NumVertices = Vertices.Num() + AddVertices; + Vertices.SetNum(NumVertices); + } + if (AddTriangles > 0) + { + int NumTriangles = Triangles.Num() + AddTriangles; + Triangles.SetNum(NumTriangles); + TriangleUVs.SetNum(NumTriangles); + TriangleNormals.SetNum(NumTriangles); + TrianglePolygonIDs.SetNum(NumTriangles); + } + if (AddUVs > 0) + { + int NumUVs = UVs.Num() + AddUVs; + UVs.SetNum(NumUVs); + UVParentVertex.SetNum(NumUVs); + } + if (AddNormals > 0) + { + int NumNormals = Normals.Num() + AddNormals; + Normals.SetNum(NumNormals); + NormalParentVertex.SetNum(NumNormals); + } + } + + + /** Set vertex at Index to given Position */ + inline void SetVertex(int Index, const FVector3d& Position) + { + Vertices[Index] = Position; + } + /** Append a new vertex at the given Position */ + inline int AppendVertex(const FVector3d& Position) + { + Vertices.Add(Position); + return Vertices.Num()-1; + } + + /** Set UV at Index to given value with given ParentVertex */ + inline void SetUV(int Index, const FVector2f& UV, int ParentVertex) + { + UVs[Index] = UV; + UVParentVertex[Index] = ParentVertex; + } + inline int AppendUV(const FVector2f& UV, int ParentVertex) + { + UVs.Add(UV); + UVParentVertex.Add(ParentVertex); + return UVs.Num() - 1; + } + + /** Set Normal at Index to given value with given ParentVertex */ + inline void SetNormal(int Index, const FVector3f& Normal, int ParentVertex) + { + Normals[Index] = Normal; + NormalParentVertex[Index] = ParentVertex; + } + inline int AppendNormal(const FVector3f& Normal, int ParentVertex) + { + Normals.Add(Normal); + NormalParentVertex.Add(ParentVertex); + return Normals.Num() - 1; + } + + + inline void SetTriangle(int Index, const FIndex3i& Tri) + { + Triangles[Index] = bReverseOrientation ? FIndex3i(Tri.C, Tri.B, Tri.A) : Tri; + } + inline void SetTriangle(int Index, int A, int B, int C) + { + Triangles[Index] = bReverseOrientation ? FIndex3i(C, B, A) : FIndex3i(A, B, C); + } + inline void SetTriangle(int Index, int A, int B, int C, bool bClockwiseOverride) + { + Triangles[Index] = (bReverseOrientation != bClockwiseOverride) ? FIndex3i(C, B, A) : FIndex3i(A, B, C); + } + + + inline int AppendTriangle(int Index, int A, int B, int C) + { + Triangles.Add( bReverseOrientation ? FIndex3i(C, B, A) : FIndex3i(A, B, C) ); + return Triangles.Num() - 1; + } + + + inline void SetTriangleUVs(int Index, const FIndex3i& Tri) + { + TriangleUVs[Index] = bReverseOrientation ? FIndex3i(Tri.C, Tri.B, Tri.A) : Tri; + } + inline void SetTriangleUVs(int Index, int A, int B, int C) + { + TriangleUVs[Index] = bReverseOrientation ? FIndex3i(C, B, A) : FIndex3i(A, B, C); + } + inline void SetTriangleUVs(int Index, int A, int B, int C, bool bClockwiseOverride) + { + TriangleUVs[Index] = (bReverseOrientation != bClockwiseOverride) ? FIndex3i(C, B, A) : FIndex3i(A, B, C); + } + + + inline void SetTriangleNormals(int Index, const FIndex3i& Tri) + { + TriangleNormals[Index] = bReverseOrientation ? FIndex3i(Tri.C, Tri.B, Tri.A) : Tri; + } + inline void SetTriangleNormals(int Index, int A, int B, int C) + { + TriangleNormals[Index] = bReverseOrientation ? FIndex3i(C, B, A) : FIndex3i(A, B, C); + } + inline void SetTriangleNormals(int Index, int A, int B, int C, bool bClockwiseOverride) + { + TriangleNormals[Index] = (bReverseOrientation != bClockwiseOverride) ? FIndex3i(C, B, A) : FIndex3i(A, B, C); + } + + inline void SetTrianglePolygon(int Index, int PolygonID) + { + TrianglePolygonIDs[Index] = PolygonID; + } + + + + + static FVector3d BilinearInterp(const FVector3d &v00, const FVector3d &v10, const FVector3d &v11, const FVector3d &v01, double tx, double ty) + { + FVector3d a = FVector3d::Lerp(v00, v01, ty); + FVector3d b = FVector3d::Lerp(v10, v11, ty); + return FVector3d::Lerp(a, b, tx); + } + + static FVector2d BilinearInterp(const FVector2d &v00, const FVector2d &v10, const FVector2d &v11, const FVector2d &v01, double tx, double ty) + { + FVector2d a = FVector2d::Lerp(v00, v01, ty); + FVector2d b = FVector2d::Lerp(v10, v11, ty); + return FVector2d::Lerp(a, b, tx); + } + + static FVector2f BilinearInterp(const FVector2f &v00, const FVector2f &v10, const FVector2f &v11, const FVector2f &v01, float tx, float ty) + { + FVector2f a = FVector2f::Lerp(v00, v01, ty); + FVector2f b = FVector2f::Lerp(v10, v11, ty); + return FVector2f::Lerp(a, b, tx); + } + + + static FVector3i LinearInterp(const FVector3i &a, const FVector3i &b, double t) + { + FVector3d c = FVector3d::Lerp((FVector3d)a, (FVector3d)b, t); + return FVector3i((int)round(c.X), (int)round(c.Y), (int)round(c.Z)); + } + + + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/MinimalBoxMeshGenerator.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/MinimalBoxMeshGenerator.h new file mode 100644 index 000000000000..713e398b59ff --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/MinimalBoxMeshGenerator.h @@ -0,0 +1,86 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MeshShapeGenerator.h" +#include "OrientedBoxTypes.h" +#include "Util/IndexUtil.h" + +/** + * Generate an oriented Box mesh with the smallest number of triangles possible (6 vertices, 12 triangles) + */ +class /*DYNAMICMESH_API*/ FMinimalBoxMeshGenerator : public FMeshShapeGenerator +{ +public: + /** 3D box */ + FOrientedBox3d Box; + + /** If true (default), UVs are scaled so that there is no stretching. If false, UVs are scaled to fill unit square */ + bool bScaleUVByAspectRatio = true; + +public: + + /** Generate the mesh */ + virtual FMeshShapeGenerator& Generate() override + { + // box has 8 corners, 6 quad-faces, each face has 2 triangles and 4 attribute-corners + SetBufferSizes(8, 12, 24, 24); + + + for (int i = 0; i < 8; ++i) + { + Vertices[i] = Box.GetCorner(i); + } + + double MaxDimension = 2.0*Box.Extents.MaxAbs(); + float UVScale = (bScaleUVByAspectRatio) ? (1.0f / (float)MaxDimension) : 1.0f; + + int TriIndex = 0; + int AttribIndex = 0; + for (int fi = 0; fi < 6; ++fi) + { + int NormalAxisIdx = IndexUtil::BoxFaceNormals[fi]; + FVector3f FaceNormal = (FVector3f)(FMath::Sign(NormalAxisIdx) * Box.GetAxis(FMath::Abs(NormalAxisIdx) - 1)); + + double FaceWidth = Vertices[IndexUtil::BoxFaces[fi][0]].Distance(Vertices[IndexUtil::BoxFaces[fi][1]]); + double FaceHeight = Vertices[IndexUtil::BoxFaces[fi][1]].Distance(Vertices[IndexUtil::BoxFaces[fi][2]]); + double WidthUVScale = FaceWidth * UVScale; + double HeightUVScale = FaceHeight * UVScale; + + int ElementIndices[4]; + + for (int j = 0; j < 4; ++j) + { + Normals[AttribIndex] = FaceNormal; + FVector2i CornerUV = IndexUtil::BoxFacesUV[j]; + UVs[AttribIndex] = FVector2f(WidthUVScale*(float)CornerUV.X, HeightUVScale*(float)CornerUV.Y); + ElementIndices[j] = AttribIndex; + AttribIndex++; + } + + + SetTriangle(TriIndex, + IndexUtil::BoxFaces[fi][0], IndexUtil::BoxFaces[fi][1], IndexUtil::BoxFaces[fi][2]); + SetTrianglePolygon(TriIndex, fi); + SetTriangleUVs(TriIndex, ElementIndices[0], ElementIndices[1], ElementIndices[2]); + SetTriangleNormals(TriIndex, ElementIndices[0], ElementIndices[1], ElementIndices[2]); + TriIndex++; + + SetTriangle(TriIndex, + IndexUtil::BoxFaces[fi][0], IndexUtil::BoxFaces[fi][2], IndexUtil::BoxFaces[fi][3]); + SetTrianglePolygon(TriIndex, fi); + SetTriangleUVs(TriIndex, ElementIndices[0], ElementIndices[2], ElementIndices[3]); + SetTriangleNormals(TriIndex, ElementIndices[0], ElementIndices[2], ElementIndices[3]); + TriIndex++; + + + } + + return *this; + } + + + + + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/PlanarPolygonMeshGenerator.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/PlanarPolygonMeshGenerator.h new file mode 100644 index 000000000000..3d5f2b05aa12 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/PlanarPolygonMeshGenerator.h @@ -0,0 +1,41 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MeshShapeGenerator.h" +#include "Polygon2.h" + +/** + * Generate planar triangulation of a Polygon. + */ +class DYNAMICMESH_API FPlanarPolygonMeshGenerator : public FMeshShapeGenerator +{ +public: + /** Polygon to triangulate. If Polygon has self-intersections or degenerate edges, result is undefined. */ + FPolygon2d Polygon; + + /** Normal vector of all vertices will be set to this value. Default is +Z axis. */ + FVector3f Normal; + + /** How to map 2D indices to 3D. Default is (0,1) = (x,y,0). */ + FIndex2i IndicesMap; + +public: + FPlanarPolygonMeshGenerator(); + + /** Initialize the polygon from an array of 2D vertices */ + void SetPolygon(const TArray& PolygonVerts); + + /** Generate the triangulation */ + virtual FMeshShapeGenerator& Generate() override; + + + /** Create vertex at position under IndicesMap, shifted to Origin*/ + inline FVector3d MakeVertex(double x, double y) + { + FVector3d v(0, 0, 0); + v[IndicesMap.A] = x; + v[IndicesMap.B] = y; + return v; + } +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/RectangleMeshGenerator.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/RectangleMeshGenerator.h new file mode 100644 index 000000000000..c981ffe70af5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/RectangleMeshGenerator.h @@ -0,0 +1,52 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MeshShapeGenerator.h" + +/** + * Generate planar rectangular mesh with variable number of subdivisions along width and height. + * By default, center of rectangle is centered at (0,0,0) origin + */ +class DYNAMICMESH_API FRectangleMeshGenerator : FMeshShapeGenerator +{ +public: + /** Rectangle will be translated so that center is at this point */ + FVector3d Origin; + /** Normal vector of all vertices will be set to this value. Default is +Z axis. */ + FVector3f Normal; + + /** Width of rectangle */ + double Width; + /** Height of rectangle */ + double Height; + + /** Number of vertices along Width axis */ + int WidthVertexCount; + /** Number of vertices along Height axis */ + int HeightVertexCount; + + /** If true (default), UVs are scaled so that there is no stretching. If false, UVs are scaled to fill unit square */ + bool bScaleUVByAspectRatio = true; + + /** Specifices how 2D indices are mapped to 3D points. Default is (0,1) = (x,y,0). */ + FIndex2i IndicesMap; + +public: + FRectangleMeshGenerator(); + + /** Generate the mesh */ + virtual FMeshShapeGenerator& Generate() override; + + /** Create vertex at position under IndicesMap, shifted to Origin*/ + virtual FVector3d MakeVertex(double x, double y) + { + FVector3d v(0, 0, 0); + v[IndicesMap.A] = x; + v[IndicesMap.B] = y; + return Origin + v; + } + + + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/SweepGenerator.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/SweepGenerator.h new file mode 100644 index 000000000000..48057afe8d70 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/SweepGenerator.h @@ -0,0 +1,286 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp CylinderGenerator + +#pragma once + +#include "Math/UnrealMathUtility.h" +#include "MeshShapeGenerator.h" +#include "Misc/AssertionMacros.h" + +#include "CompGeom/PolygonTriangulation.h" +#include "FrameTypes.h" +#include "MatrixTypes.h" +#include "Polygon2.h" + +/** + * ECapType indicates the type of cap to use on a sweep + */ +enum class /*DYNAMICMESH_API*/ ECapType +{ + None = 0, + FlatTriangulation = 1 + // TODO: Cone, other caps ... +}; + +class /*DYNAMICMESH_API*/ FSweepGeneratorBase : public FMeshShapeGenerator +{ +public: + virtual ~FSweepGeneratorBase() + { + } + +protected: + int32 CapVertStart[2], CapNormalStart[2], CapUVStart[2], CapTriangleStart[2], CapPolygonStart[2]; + + /** + * Shared logic for creating vertex buffers and triangulations across all sweep primitives + * Note: Does not set vertex positions or normals; a separate call must do that. + */ + void ConstructMeshTopology(const FPolygon2d& CrossSection, + const TArrayView& UVSections, + const TArrayView& NormalSections, + int32 NumCrossSections, + const ECapType Caps[2], + FVector2d UVScale, FVector2d UVOffset) + { + // per cross section + int32 XVerts = CrossSection.VertexCount(); + int32 XNormals = XVerts + NormalSections.Num(); + int32 XUVs = XVerts + UVSections.Num() + 1; + + int32 NumVerts = XVerts * NumCrossSections; + int32 NumNormals = NumCrossSections > 1 ? XNormals * NumCrossSections : 0; + int32 NumUVs = NumCrossSections > 1 ? XUVs * NumCrossSections : 0; + int32 NumPolygons = (NumCrossSections - 1) * XVerts; + int32 NumTriangles = NumPolygons * 2; + + TArray OutTriangles; + + for (int32 CapIdx = 0; CapIdx < 2; CapIdx++) + { + CapVertStart[CapIdx] = NumVerts; + CapNormalStart[CapIdx] = NumNormals; + CapUVStart[CapIdx] = NumUVs; + CapTriangleStart[CapIdx] = NumTriangles; + CapPolygonStart[CapIdx] = NumPolygons; + + if (Caps[CapIdx] == ECapType::FlatTriangulation) + { + NumTriangles += XVerts - 2; + NumPolygons++; + NumUVs += XVerts; + NumNormals += XVerts; + } + + // TODO: support more cap type; e.g.: + //else if (Caps[CapIdx] == ECapType::FlatMidpointFan) + //{ + // NumTriangles += XVerts; + // NumPolygons += XVerts; + // NumUVs += XVerts + 1; + // NumNormals += XVerts + 1; + // NumVerts += 1; + //} + //else if (Caps[CapIdx] == ECapType::Cone) + //{ + // NumTriangles += XVerts; + // NumPolygons += XVerts; + // NumUVs += XVerts + 1; + // NumNormals += XVerts * 2; + // NumVerts += 1; + //} + } + + SetBufferSizes(NumVerts, NumTriangles, NumUVs, NumNormals); + + for (int32 CapIdx = 0; CapIdx < 2; CapIdx++) + { + + if (Caps[CapIdx] == ECapType::FlatTriangulation) + { + int32 VertOffset = CapIdx * (XVerts * (NumCrossSections - 1)); + + PolygonTriangulation::TriangulateSimplePolygon(CrossSection.GetVertices(), OutTriangles); + int32 TriIdx = CapTriangleStart[CapIdx]; + int32 PolyIdx = CapPolygonStart[CapIdx]; + for (const FIndex3i& Triangle : OutTriangles) + { + bool Flipped = CapIdx == 0; + Flipped = !Flipped; + SetTriangle(TriIdx, + Triangle.A + VertOffset, Triangle.B + VertOffset, Triangle.C + VertOffset, + Flipped); + SetTriangleUVs(TriIdx, + Triangle.A + CapUVStart[CapIdx], + Triangle.B + CapUVStart[CapIdx], + Triangle.C + CapUVStart[CapIdx], + Flipped); + SetTriangleNormals(TriIdx, + Triangle.A + CapNormalStart[CapIdx], + Triangle.B + CapNormalStart[CapIdx], + Triangle.C + CapNormalStart[CapIdx], + Flipped); + SetTrianglePolygon(TriIdx, PolyIdx); + TriIdx++; + } + for (int32 Idx = 0; Idx < XVerts; Idx++) + { + FVector2d CenteredVert = CrossSection.GetVertices()[Idx] * UVScale + UVOffset; + SetUV(CapUVStart[CapIdx] + Idx, FVector2f(CenteredVert.X, CenteredVert.Y), VertOffset + Idx); + SetNormal(CapNormalStart[CapIdx] + Idx, FVector3f::Zero(), VertOffset + Idx); + } + } + } + + // fill in UVs and triangles along length + if (NumCrossSections > 1) + { + int32 UVSection = 0, UVSubIdx = 0; + + int32 NumSections = UVSections.Num(); + int32 NextDupVertIdx = UVSection < NumSections ? UVSections[UVSection] : -1; + for (int32 VertSubIdx = 0; VertSubIdx < XVerts; UVSubIdx++) + { + float UVX = VertSubIdx / float(XVerts); + for (int32 XIdx = 0; XIdx < NumCrossSections; XIdx++) + { + float UVY = XIdx / float(NumCrossSections - 1); + SetUV(XIdx * XUVs + UVSubIdx, FVector2f(UVX, UVY), XIdx * XVerts + VertSubIdx); + } + + if (VertSubIdx == NextDupVertIdx) + { + NextDupVertIdx = UVSection < NumSections ? UVSections[UVSection] : -1; + } + else + { + for (int32 XIdx = 0; XIdx + 1 < NumCrossSections; XIdx++) + { + SetTriangleUVs( + XVerts * 2 * XIdx + 2 * VertSubIdx, + XIdx * XUVs + UVSubIdx, + XIdx * XUVs + UVSubIdx + 1, + (XIdx + 1) * XUVs + UVSubIdx, true); + SetTriangleUVs( + XVerts * 2 * XIdx + 2 * VertSubIdx + 1, + (XIdx + 1) * XUVs + UVSubIdx + 1, + (XIdx + 1) * XUVs + UVSubIdx, + XIdx * XUVs + UVSubIdx + 1, true); + } + VertSubIdx++; + } + } + { + // final UV + float UVX = 1.0f; + int32 VertSubIdx = 0; + for (int32 XIdx = 0; XIdx < NumCrossSections; XIdx++) + { + float UVY = XIdx / float(NumCrossSections - 1); + SetUV(XIdx * XUVs + UVSubIdx, FVector2f(UVX, UVY), XIdx * XVerts + VertSubIdx); + } + } + NumSections = NormalSections.Num(); + int32 NormalSection = 0; + NextDupVertIdx = NormalSection < NumSections ? NormalSections[NormalSection] : -1; + check(NextDupVertIdx < XVerts); + for (int32 VertSubIdx = 0, NormalSubIdx = 0; VertSubIdx < XVerts; NormalSubIdx++) + { + for (int32 XIdx = 0; XIdx < NumCrossSections; XIdx++) + { + // just set the normal parent; don't compute normal yet + SetNormal(XIdx * XNormals + NormalSubIdx, FVector3f(0, 0, 0), XIdx * XVerts + VertSubIdx); + } + + if (VertSubIdx == NextDupVertIdx) + { + NextDupVertIdx = NormalSection < NumSections ? NormalSections[NormalSection] : -1; + check(NextDupVertIdx < XVerts); + } + else + { + int32 WrappedNextNormalSubIdx = (NormalSubIdx + 1) % XNormals; + int32 WrappedNextVertexSubIdx = (VertSubIdx + 1) % XVerts; + for (int32 XIdx = 0; XIdx + 1 < NumCrossSections; XIdx++) + { + int32 T0Idx = XVerts * 2 * XIdx + 2 * VertSubIdx; + int32 T1Idx = T0Idx + 1; + int32 PIdx = XVerts * XIdx + VertSubIdx; + SetTrianglePolygon(T0Idx, PIdx); + SetTrianglePolygon(T1Idx, PIdx); + SetTriangle(T0Idx, + XIdx * XVerts + VertSubIdx, + XIdx * XVerts + WrappedNextVertexSubIdx, + (XIdx + 1) * XVerts + VertSubIdx, true); + SetTriangle(T1Idx, + (XIdx + 1) * XVerts + WrappedNextVertexSubIdx, + (XIdx + 1) * XVerts + VertSubIdx, + XIdx * XVerts + WrappedNextVertexSubIdx, true); + SetTriangleNormals( + T0Idx, + XIdx * XNormals + NormalSubIdx, + XIdx * XNormals + WrappedNextNormalSubIdx, + (XIdx + 1) * XNormals + NormalSubIdx, true); + SetTriangleNormals( + T1Idx, + (XIdx + 1) * XNormals + WrappedNextNormalSubIdx, + (XIdx + 1) * XNormals + NormalSubIdx, + XIdx * XNormals + WrappedNextNormalSubIdx, true); + } + VertSubIdx++; + } + } + } + } +}; + +/** + * Generate a cylinder with optional end caps + */ +class /*DYNAMICMESH_API*/ FCylinderGenerator : public FSweepGeneratorBase +{ +public: + float Radius[2] = {1.0f, 1.0f}; + float Height = 1.0f; + int AngleSamples = 16; + bool bCapped = false; + +public: + /** Generate the mesh */ + virtual FMeshShapeGenerator& Generate() override + { + FPolygon2d X = FPolygon2d::MakeCircle(1.0, AngleSamples); + const TArray& XVerts = X.GetVertices(); + ECapType Caps[2] = {ECapType::None, ECapType::None}; + + if (bCapped) + { + Caps[0] = ECapType::FlatTriangulation; + Caps[1] = ECapType::FlatTriangulation; + } + ConstructMeshTopology(X, {}, {}, 2, Caps, FVector2d(1, 1), FVector2d(0, 0)); + + for (int SubIdx = 0; SubIdx < X.VertexCount(); SubIdx++) + { + for (int XIdx = 0; XIdx < 2; ++XIdx) + { + Vertices[SubIdx + XIdx * AngleSamples] = + FVector3d(XVerts[SubIdx].X * Radius[XIdx], XVerts[SubIdx].Y * Radius[XIdx], XIdx * Height); + Normals[SubIdx + XIdx * AngleSamples] = FVector3f(XVerts[SubIdx].X, XVerts[SubIdx].Y, 0); + + if (bCapped) + { + Normals[CapNormalStart[XIdx] + SubIdx] = FVector3f(0, 0, 2 * XIdx - 1); + } + } + } + + for (int k = 0; k < Normals.Num(); ++k) + { + Normals[k].Normalize(); + } + + return *this; + } +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/GroupTopology.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/GroupTopology.h new file mode 100644 index 000000000000..0ada8383e864 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/GroupTopology.h @@ -0,0 +1,191 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DynamicMesh3.h" +#include "EdgeSpan.h" +#include "Util/IndexUtil.h" +#include "Containers/BitArray.h" + + +/** + * Given a per-triangle integer ("group"), FGroupTopology extracts a + * group-level topological graph from an input Mesh. The graph consists + * of three elements: + * Corners: there is a corner at each vertex where 3 or more groups meet (ie 3 or more groups in one-ring) + * Edges: a group edge is a list of one or more connected edges that have the same pair of different groups on either side + * Group: a group is a set of connected faces with the same GroupID + * + * By default, the triangle group attribute of the input Mesh is used. + * You can override GetGroupID to provide your own grouping. + * + * Various query functions are provided to allow group topology to be interrogated. + * Note that these functions refer to "GroupID", "CornerID", and "GroupEdgeID", + * these are simply indices into the internal .Groups, .Corners, and .Edges arrays + */ +class DYNAMICMESH_API FGroupTopology +{ +public: + FGroupTopology() {} + FGroupTopology(const FDynamicMesh3* Mesh, bool bAutoBuild); + + virtual ~FGroupTopology() {} + + + /** + * Build the group topology graph. + */ + void RebuildTopology(); + + /** + * Adjacency of Per-Triangle integers are what define the triangle groups. + * Override this function to provide an alternate group definition. + * @return group id integer for given TriangleID + */ + virtual int GetGroupID(int TriangleID) const + { + return Mesh->GetTriangleGroup(TriangleID); + } + + + /** + * FCorner is a "corner" in the group topology, IE a vertex where 3+ groups meet. + */ + struct DYNAMICMESH_API FCorner + { + /** Mesh Vertex ID */ + int VertexID; + /** List of IDs of groups connected to this Corner */ + TArray NeighbourGroupIDs; + }; + + /** List of Corners in the topology graph (ie these are the nodes/vertices of the graph) */ + TArray Corners; + + /** A Group is bounded by closed loops of FGroupEdge elements. A FGroupBoundary is one such loop. */ + struct DYNAMICMESH_API FGroupBoundary + { + /** Ordered list of edges forming this boundary */ + TArray GroupEdges; + /** List of IDs of groups on the "other side" of this boundary (this GroupBoundary is owned by a particular FGroup) */ + TArray NeighbourGroupIDs; + /** true if one or more edges in GroupEdges is on the mesh boundary */ + bool bIsOnBoundary; + }; + + /** FGroup is a set of connected triangles with the same GroupID */ + struct DYNAMICMESH_API FGroup + { + /** GroupID for this group */ + int GroupID; + /** List of triangles forming this group */ + TArray Faces; + + /** List of boundaries of this group (may be empty, eg on a closed component with only one group) */ + TArray Boundaries; + /** List of groups that are adjacent to this group */ + TArray NeighbourGroupIDs; + }; + + /** List of Groups in the topology graph (ie faces) */ + TArray Groups; + + + /** + * FGroupEdge is a sequence of group-boundary-edges where the two groups on either side of each edge + * are the same. The sequence is stored an FEdgeSpan. + * + * FGroupEdge instances are *shared* between the two FGroups on either side, via FGroupBoundary. + */ + struct DYNAMICMESH_API FGroupEdge + { + /** Identifier for this edge, as a pair of groups, sorted by increasing value */ + FIndex2i Groups; + /** Edge span for this edge */ + FEdgeSpan Span; + + /** @return the member of .Groups that is not GroupID */ + int OtherGroupID(int GroupID) const + { + check(Groups.A == GroupID || Groups.B == GroupID); + return (Groups.A == GroupID) ? Groups.B : Groups.A; + } + + /** @return true if any vertex in the Span is in the Vertices set*/ + bool IsConnectedToVertices(const TSet& Vertices) const; + }; + + /** List of Edges in the topology graph (each edge connects two corners/nodes) */ + TArray Edges; + + + + /** @return the mesh vertex ID for the given Corner ID */ + int GetCornerVertexID(int CornerID) const; + + /** @return the FGroup for the given GroupID, or nullptr if not found */ + const FGroup* FindGroupByID(int GroupID) const; + /** @return the list of triangles in the given GroupID, or empty list if not found */ + const TArray& GetGroupFaces(int GroupID) const; + /** @return the list of neighbour GroupIDs for the given GroupID, or empty list if not found */ + const TArray& GetGroupNbrGroups(int GroupID) const; + + /** @return the ID of the FGroupEdge that contains the given Mesh Edge ID*/ + int FindGroupEdgeID(int MeshEdgeID) const; + /** @return the list of vertices of a FGroupEdge identified by the GroupEdgeID */ + const TArray& GetGroupEdgeVertices(int GroupEdgeID) const; + + /** Add the groups connected to the given GroupEdgeID to the GroupsOut list. This is not the either-side pair, but the set of groups on the one-ring of each connected corner. */ + void FindEdgeNbrGroups(int GroupEdgeID, TArray& GroupsOut) const; + /** Add the groups connected to all the GroupEdgeIDs to the GroupsOut list. This is not the either-side pair, but the set of groups on the one-ring of each connected corner. */ + void FindEdgeNbrGroups(const TArray& GroupEdgeIDs, TArray& GroupsOut) const; + + /** Add all the groups connected to the given Corner to the GroupsOut list */ + void FindCornerNbrGroups(int CornerID, TArray& GroupsOut) const; + /** Add all the groups connected to the given Corners to the GroupsOut list */ + void FindCornerNbrGroups(const TArray& CornerIDs, TArray& GroupsOut) const; + + /** Add all the groups connected to the given Mesh Vertex to the GroupsOut list */ + void FindVertexNbrGroups(int VertexID, TArray& GroupsOut ) const; + /** Add all the groups connected to the given Mesh Vertices to the GroupsOut list */ + void FindVertexNbrGroups(const TArray& VertexIDs, TArray& GroupsOut) const; + + /** Call EdgeFunc for each boundary edge of the given Group (no order defined) */ + void ForGroupEdges(int GroupID, + const TFunction& EdgeFunc) const; + + /** Call EdgeFunc for each boundary edge of each of the given Groups (no order defined) */ + void ForGroupSetEdges(const TArray& GroupIDs, + const TFunction& EdgeFunc) const; + + /** Add all the vertices of the given GroupID to the Vertices set */ + void CollectGroupVertices(int GroupID, TSet& Vertices) const; + /** Add all the group boundary vertices of the given GroupID to the Vertices set */ + void CollectGroupBoundaryVertices(int GroupID, TSet& Vertices) const; + + +protected: + const FDynamicMesh3* Mesh = nullptr; + + TArray GroupIDToGroupIndexMap; // allow fast lookup of index in .Groups, given GroupID + TBitArray<> CornerVerticesFlags; // bit array of corners for fast testing in ExtractGroupEdges + TArray EmptyArray; + + /** @return true if given mesh vertex is a Corner vertex */ + virtual bool IsCornerVertex(int VertexID) const; + + void ExtractGroupEdges(FGroup& Group); + + FIndex2i MakeEdgeID(int MeshEdgeID) + { + FIndex2i EdgeTris = Mesh->GetEdgeT(MeshEdgeID); + return MakeEdgeID(GetGroupID(EdgeTris.A), GetGroupID(EdgeTris.B)); + } + FIndex2i MakeEdgeID(int Group1, int Group2) + { + check(Group1 != Group2); + return (Group1 < Group2) ? FIndex2i(Group1, Group2) : FIndex2i(Group2, Group1); + } + + int FindExistingGroupEdge(int GroupID, int OtherGroupID, int FirstVertexID); +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshAdapterUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshAdapterUtil.h new file mode 100644 index 000000000000..db53a05ecfb0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshAdapterUtil.h @@ -0,0 +1,35 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "DynamicMesh3.h" +#include "PointSetAdapter.h" + +/** + * Utility functions for constructing PointSetAdapter instances from various + * point sets on a mesh + */ +namespace MeshAdapterUtil +{ + /** + * @return Mesh vertices as a point set + */ + FPointSetAdapterd DYNAMICMESH_API MakeVerticesAdapter(const FDynamicMesh3* Mesh); + + /** + * @return Mesh triangle centroids as a point set + */ + FPointSetAdapterd DYNAMICMESH_API MakeTriCentroidsAdapter(const FDynamicMesh3* Mesh); + + /** + * @return mesh edge midpoints as a point set + */ + FPointSetAdapterd DYNAMICMESH_API MakeEdgeMidpointsAdapter(const FDynamicMesh3* Mesh); + + /** + * @return Mesh boundary edge midpoints as a point set + */ + FPointSetAdapterd DYNAMICMESH_API MakeBoundaryEdgeMidpointsAdapter(const FDynamicMesh3* Mesh); +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshBoundaryLoops.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshBoundaryLoops.h new file mode 100644 index 000000000000..164d4503fed1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshBoundaryLoops.h @@ -0,0 +1,180 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp MeshBoundaryLoops + +#pragma once + +#include "DynamicMesh3.h" +#include "EdgeLoop.h" +#include "EdgeSpan.h" +#include "VectorUtil.h" +#include "Util/BufferUtil.h" + + +class DYNAMICMESH_API FMeshBoundaryLoops +{ +public: + /** Mesh we are finding loops on */ + const FDynamicMesh3* Mesh = nullptr; + + /** Resulting set of loops filled by Compute() */ + TArray Loops; + + /** Resulting set of spans filled by Compute(), if SpanBehavior == Compute */ + TArray Spans; + + /** If true, we aborted computation due to unrecoverable errors */ + bool bAborted = false; + /** If true, we found at least one open span during Compute(). This can occur in failure cases or if the search is restricted to a subset using EdgeFilterFunc */ + bool bSawOpenSpans = false; + /** If true, we had to call back to spans because of failures during Compute(). This happens if we cannot extract simple loops from a loop with bowties. */ + bool bFellBackToSpansOnFailure = false; + + enum class ESpanBehaviors + { + Ignore, Abort, Compute + }; + /** What Compute() will do if it encounter open spans */ + ESpanBehaviors SpanBehavior = ESpanBehaviors::Compute; + + + enum class EFailureBehaviors + { + Abort, // die, and you clean up + ConvertToOpenSpan // keep un-closed loop as a span + }; + /** What Compute() will do if it encounters unrecoverable errors while walking around a loop */ + EFailureBehaviors FailureBehavior = EFailureBehaviors::ConvertToOpenSpan; + + /** If non-null, then only edges that pass this filter are considered. This may result in open spans. */ + TFunction EdgeFilterFunc = nullptr; + + /** If we encountered unrecoverable errors, it is generally due to bowtie vertices. The problematic bowties will be returned here so that the client can try repairing these vertices. */ + TArray FailureBowties; + + +public: + FMeshBoundaryLoops() {} + + FMeshBoundaryLoops(const FDynamicMesh3* MeshIn, bool bAutoCompute = true) + { + Mesh = MeshIn; + if (bAutoCompute) + { + Compute(); + } + } + + void SetMesh(const FDynamicMesh3* MeshIn) { Mesh = MeshIn; } + + /** + * Find the set of boundary EdgeLoops and EdgeSpans. + * .SpanBehavior and .FailureBehavior control what happens if we run into problem cases + * @return false if errors occurred, in this case output set is incomplete + */ + bool Compute(); + + + + /** @return number of loops found by Compute() */ + int GetLoopCount() const + { + return Loops.Num(); + } + + /** @return number of spans found by Compute() */ + int GetSpanCount() const + { + return Spans.Num(); + } + + /** @return Loop at the given index */ + const FEdgeLoop& operator[](int Index) const + { + return Loops[Index]; + } + + + /** @return index of loop with maximum number of vertices */ + int GetMaxVerticesLoopIndex() const; + + /** + * @return pair (LoopIndex,VertexIndexInLoop) of VertexID in EdgeLoops, or FIndex2i::Invalid if not found + */ + FIndex2i FindVertexInLoop(int VertexID) const; + + /** + * @return index of loop that contains vertex, or -1 if not found + */ + int FindLoopContainingVertex(int VertexID) const; + + /** + * @return index of loop that contains edge, or -1 if not found + */ + int FindLoopContainingEdge(int EdgeID) const; + + + + + +protected: + + TArray VerticesTemp; + + // [TODO] cache this : a dictionary? we will not need very many, but we will + // need each multiple times! + FVector3d GetVertexNormal(int vid); + + // ok, bdry_edges[0...bdry_edges_count] contains the boundary edges coming out of bowtie_v. + // We want to pick the best one to continue the loop that came in to bowtie_v on incoming_e. + // If the loops are all sane, then we will get the smallest loops by "turning left" at bowtie_v. + int FindLeftTurnEdge(int incoming_e, int bowtie_v, TArray& bdry_edges, int bdry_edges_count, TArray& used_edges); + + struct Subloops + { + TArray Loops; + TArray Spans; + }; + + + // This is called when loopV contains one or more "bowtie" vertices. + // These vertices *might* be duplicated in loopV (but not necessarily) + // If they are, we have to break loopV into subloops that don't contain duplicates. + bool ExtractSubloops(TArray& loopV, TArray& loopE, TArray& bowties, Subloops& SubloopsOut); + + + +protected: + + friend class FMeshRegionBoundaryLoops; + + /* + * static Utility functions that can be re-used in MeshRegionBoundaryLoops + * In all the functions below, the list loopV is assumed to possibly + * contain "removed" vertices indicated by -1. These are ignored. + */ + + // Check if the loop from bowtieV to bowtieV inside loopV contains any other bowtie verts. + // Also returns start and end indices in loopV of "clean" loop + // Note that start may be < end, if the "clean" loop wraps around the end + static bool IsSimpleBowtieLoop(const TArray& LoopVerts, const TArray& BowtieVerts, int BowtieVertex, int& start_i, int& end_i); + + // check if forward path from loopV[i1] to loopV[i2] contains any bowtie verts other than bowtieV + static bool IsSimplePath(const TArray& LoopVerts, const TArray& BowtieVerts, int BowtieVertex, int i1, int i2); + + + // Read out the span from loop[i0] to loop [i1-1] into an array. + // If bMarkInvalid, then these values are set to -1 in loop + static void ExtractSpan(TArray& Loop, int i0, int i1, bool bMarkInvalid, TArray& OutSpan); + + // count number of valid vertices in l between loop[i0] and loop[i1-1] + static int CountSpan(const TArray& Loop, int i0, int i1); + + // find the index of item in loop, starting at start index + static int FindIndex(const TArray& Loop, int Start, int Item); + + // count number of times item appears in loop + static int CountInList(const TArray& Loop, int Item); + + +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshConstraints.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshConstraints.h new file mode 100644 index 000000000000..e9c3d4d8bc57 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshConstraints.h @@ -0,0 +1,325 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp MeshConstraints + +#pragma once + +#include "Spatial/SpatialInterfaces.h" // for projection target + +/** + * EEdgeRefineFlags indicate constraints on triangle mesh edges + */ +enum class EEdgeRefineFlags +{ + /** Edge is unconstrained */ + NoConstraint = 0, + /** Edge cannot be flipped */ + NoFlip = 1, + /** Edge cannot be split */ + NoSplit = 2, + /** Edge cannot be collapsed */ + NoCollapse = 4, + /** Edge cannot be flipped, split, or collapsed */ + FullyConstrained = NoFlip | NoSplit | NoCollapse, + /** Edge can only be split */ + SplitsOnly = NoFlip | NoCollapse +}; + + +/** + * FEdgeConstraint is a constraint on a triangle mesh edge + */ +struct DYNAMICMESH_API FEdgeConstraint +{ +public: + /** Constraint flags on this edge */ + EEdgeRefineFlags RefineFlags; + /** Edge is associated with this projection target. */ + IProjectionTarget * Target; + + /** This ID is not a constraint, but can be used to find descendents of a constrained input edge after splits */ + int TrackingSetID; + + FEdgeConstraint() + { + RefineFlags = EEdgeRefineFlags::NoConstraint; + Target = nullptr; + TrackingSetID = -1; + } + + explicit FEdgeConstraint(EEdgeRefineFlags ConstraintFlags) + { + RefineFlags = ConstraintFlags; + Target = nullptr; + TrackingSetID = -1; + } + + explicit FEdgeConstraint(EEdgeRefineFlags ConstraintFlags, IProjectionTarget* TargetIn) + { + RefineFlags = ConstraintFlags; + Target = TargetIn; + TrackingSetID = -1; + } + + /** @return true if edge can be flipped */ + bool CanFlip() const + { + return ((int)RefineFlags & (int)EEdgeRefineFlags::NoFlip) == 0; + } + + /** @return true if edge can be split */ + bool CanSplit() const + { + return ((int)RefineFlags & (int)EEdgeRefineFlags::NoSplit) == 0; + } + + /** @return true if edge can be collapsed */ + bool CanCollapse() const + { + return ((int)RefineFlags & (int)EEdgeRefineFlags::NoCollapse) == 0; + } + + /** @return true if edge cannot be modified at all */ + bool NoModifications() const + { + return ((int)RefineFlags & (int)EEdgeRefineFlags::FullyConstrained) == (int)EEdgeRefineFlags::FullyConstrained; + } + + /** @return true if edge is unconstrained */ + bool IsUnconstrained() const + { + return RefineFlags == EEdgeRefineFlags::NoConstraint && Target == nullptr; + } + + /** @return an unconstrained edge constraint (ie not constrained at all) */ + static FEdgeConstraint Unconstrained() { return FEdgeConstraint(EEdgeRefineFlags::NoConstraint); } + + /** @return a no-flip edge constraint */ + static FEdgeConstraint NoFlips() { return FEdgeConstraint(EEdgeRefineFlags::NoFlip); } + + /** @return a splits-only edge constraint */ + static FEdgeConstraint SplitsOnly() { return FEdgeConstraint(EEdgeRefineFlags::SplitsOnly); } + + /** @return a fully constrained edge constraint */ + static FEdgeConstraint FullyConstrained() { return FEdgeConstraint(EEdgeRefineFlags::FullyConstrained); } +}; + + + +/** + * FVertexConstraint is a constraint on a triangle mesh vertex + */ +struct DYNAMICMESH_API FVertexConstraint +{ +public: + /** value for FixedSetID that is treated as not-a-fixed-set-ID by various functions (ie don't use this value yourself) */ + static constexpr int InvalidSetID = -1; + + /** Is this vertex topologically fixed, ie cannot be removed by topology-change operations */ + bool Fixed; + /** Can this vertex be moved */ + bool Movable; + /** Fixed vertices with the same FixedSetID can optionally be collapsed together (ie in Remesher) */ + int FixedSetID; + + /** Vertex is associated with this ProjectionTarget, and should be projected onto it (ie in Remesher) */ + IProjectionTarget* Target; + + FVertexConstraint() + { + Fixed = false; + Movable = false; + FixedSetID = InvalidSetID; + Target = nullptr; + } + + explicit FVertexConstraint(bool bIsFixed, bool bIsMovable, int SetID = InvalidSetID) + { + Fixed = bIsFixed; + Movable = bIsMovable; + FixedSetID = SetID; + Target = nullptr; + } + + explicit FVertexConstraint(IProjectionTarget* TargetIn) + { + Fixed = false; + Movable = false; + FixedSetID = InvalidSetID; + Target = TargetIn; + } + + /** @return an unconstrained vertex constraint (ie not constrained at all) */ + static FVertexConstraint Unconstrained() { return FVertexConstraint(false, true); } + + /** @return a Pinned vertex constraint */ + static FVertexConstraint Pinned() { return FVertexConstraint(true, false); } + + /** @return a Pinned-but-Movable vertex constraint */ + static FVertexConstraint PinnedMovable() { return FVertexConstraint(true, true); } +}; + + + + +/** + * FMeshConstraints is a set of Edge and Vertex constraints for a Triangle Mesh + */ +class DYNAMICMESH_API FMeshConstraints +{ +protected: + + /** Map of mesh edge IDs to active EdgeConstraints */ + TMap Edges; + + /** Map of mesh vertex IDs to active VertexConstraints */ + TMap Vertices; + + /** internal counter used to allocate new FixedSetIDs */ + int FixedSetIDCounter; + +public: + + FMeshConstraints() + { + FixedSetIDCounter = 0; + } + + /** @return an unused Fixed Set ID */ + int AllocateSetID() + { + return FixedSetIDCounter++; + } + + /** @return map of active edge constraints */ + const TMap& GetEdgeConstraints() const + { + return Edges; + } + + /** @return map of active vertex constraints */ + const TMap& GetVertexConstraints() const + { + return Vertices; + } + + + /** @return true if any edges or vertices are constrained */ + bool HasConstraints() const + { + return Edges.Num() > 0 || Vertices.Num() > 0; + } + + + + /** @return true if given EdgeID has active constraint */ + bool HasEdgeConstraint(int EdgeID) const + { + return Edges.Find(EdgeID) != nullptr; + } + + /** @return EdgeConstraint for give EdgeID, may be Unconstrained */ + FEdgeConstraint GetEdgeConstraint(int EdgeID) const + { + const FEdgeConstraint* Found = Edges.Find(EdgeID); + if (Found == nullptr) + { + return FEdgeConstraint::Unconstrained(); + } + else + { + return *Found; + } + } + + /** @return true if EdgeID is constrained and ConstraintOut is filled */ + bool GetEdgeConstraint(int EdgeID, FEdgeConstraint& ConstraintOut) const + { + const FEdgeConstraint* Found = Edges.Find(EdgeID); + if (Found == nullptr) + { + return false; + } + else + { + ConstraintOut = *Found; + return true; + } + } + + /** Set the constraint on the given EdgeID */ + void SetOrUpdateEdgeConstraint(int EdgeID, const FEdgeConstraint& ec) + { + Edges.Add(EdgeID, ec); + } + + /** Clear the constraint on the given EdgeID */ + void ClearEdgeConstraint(int EdgeID) + { + Edges.Remove(EdgeID); + } + + + /** Find all constraint edges for the given SetID, and return via FoundListOut */ + void FindConstrainedEdgesBySetID(int SetID, TArray& FoundListOut) const + { + for (const TPair& pair : Edges) + { + if (pair.Value.TrackingSetID == SetID) + { + FoundListOut.Add(pair.Key); + } + } + } + + + + /** @return true if given VertexID has active constraint */ + bool HasVertexConstraint(int VertexID) const + { + return Vertices.Find(VertexID) != nullptr; + } + + + /** @return VertexConstraint for give VertexID, may be Unconstrained */ + FVertexConstraint GetVertexConstraint(int VertexID) const + { + const FVertexConstraint* Found = Vertices.Find(VertexID); + if (Found == nullptr) + { + return FVertexConstraint::Unconstrained(); + } + else + { + return *Found; + } + } + + /** @return true if VetexID is constrained and ConstraintOut is filled */ + bool GetVertexConstraint(int VertexID, FVertexConstraint& ConstraintOut)const + { + const FVertexConstraint* Found = Vertices.Find(VertexID); + if (Found == nullptr) + { + return false; + } + else + { + ConstraintOut = *Found; + return true; + } + } + + /** Set the constraint on the given VertexID */ + void SetOrUpdateVertexConstraint(int VertexID, const FVertexConstraint& vc) + { + Vertices.Add(VertexID, vc); + } + + /** Clear the constraint on the given VertexID */ + void ClearVertexConstraint(int VertexID) + { + Vertices.Remove(VertexID); + } + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshConstraintsUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshConstraintsUtil.h new file mode 100644 index 000000000000..566ed6a1dc8e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshConstraintsUtil.h @@ -0,0 +1,81 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp MeshConstraintsUtil + +#pragma once + +#include "DynamicMesh3.h" +#include "MeshConstraints.h" +#include "DynamicMeshAttributeSet.h" + + +/** + * Utility functions for configuring a FMeshConstraints instance + */ +class DYNAMICMESH_API FMeshConstraintsUtil +{ +public: + + + /** + * Constrain all attribute seams for all overlays of a mesh + * @param Constraints the set of constraints to add to + * @param Mesh the mesh to constrain + * @param bAllowSplits should we allow constrained edges to be split + * @param bAllowSmoothing should we allow constrained vertices to be smoothed + */ + static void ConstrainAllSeams(FMeshConstraints& Constraints, const FDynamicMesh3& Mesh, bool bAllowSplits, bool bAllowSmoothing); + + + /** + * Constrain attribute seams of the given overlay + * @param Constraints the set of constraints to add to + * @param Mesh the mesh to constrain + * @param Overlay the attribute overlay to find seams in + */ + template + static void ConstrainSeams(FMeshConstraints& Constraints, const FDynamicMesh3& Mesh, const TDynamicMeshOverlay& Overlay) + { + for (int EdgeID : Mesh.EdgeIndicesItr()) + { + if (Overlay.IsSeamEdge(EdgeID)) + { + Constraints.SetOrUpdateEdgeConstraint(EdgeID, FEdgeConstraint::FullyConstrained()); + FIndex2i EdgeVerts = Mesh.GetEdgeV(EdgeID); + Constraints.SetOrUpdateVertexConstraint(EdgeVerts.A, FVertexConstraint::Pinned()); + Constraints.SetOrUpdateVertexConstraint(EdgeVerts.B, FVertexConstraint::Pinned()); + } + } + } + + + + /** + * For all edges, disable flip/split/collapse. For all vertices, pin in current position. + * @param Constraints the set of constraints to add to + * @param Mesh the mesh to constrain + */ + template + static void FullyConstrainEdges(FMeshConstraints& Constraints, const FDynamicMesh3& Mesh, iter BeginEdges, iter EndEdges) + { + while (BeginEdges != EndEdges) + { + int EdgeID = *BeginEdges; + if (Mesh.IsEdge(EdgeID)) + { + Constraints.SetOrUpdateEdgeConstraint(EdgeID, FEdgeConstraint::FullyConstrained()); + FIndex2i EdgeVerts = Mesh.GetEdgeV(EdgeID); + Constraints.SetOrUpdateVertexConstraint(EdgeVerts.A, FVertexConstraint::Pinned()); + Constraints.SetOrUpdateVertexConstraint(EdgeVerts.B, FVertexConstraint::Pinned()); + } + BeginEdges++; + } + } + + + +private: + FMeshConstraintsUtil() = delete; // this class is not constructible + + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshIndexUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshIndexUtil.h new file mode 100644 index 000000000000..1ed881aad411 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshIndexUtil.h @@ -0,0 +1,20 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DynamicMesh3.h" + +/** + * Utility functions for dealing with mesh indices + */ +namespace MeshIndexUtil +{ + /** + * @param Mesh input mesh + * @param TriangleIDs list of triangle IDs of Mesh + * @param VertexIDsOut list of vertices contained by triangles + */ + DYNAMICMESH_API void ConverTriangleToVertexIDs(const FDynamicMesh3* Mesh, const TArray& TriangleIDs, TArray& VertexIDsOut); + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshNormals.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshNormals.h new file mode 100644 index 000000000000..e36b9615377f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshNormals.h @@ -0,0 +1,114 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp MeshNormals + +#pragma once + +#include "DynamicMesh3.h" +#include "DynamicMeshAttributeSet.h" + +/** + * FMeshNormals is a utility class that can calculate and store various types of + * normal vectors for a FDynamicMesh. + */ +class DYNAMICMESH_API FMeshNormals +{ +protected: + /** Target Mesh */ + const FDynamicMesh3* Mesh; + /** Set of computed normals */ + TArray Normals; + +public: + FMeshNormals() + { + Mesh = nullptr; + } + + FMeshNormals(const FDynamicMesh3* Mesh) + { + SetMesh(Mesh); + } + + + void SetMesh(const FDynamicMesh3* MeshIn) + { + this->Mesh = MeshIn; + } + + const TArray& GetNormals() const { return Normals; } + + FVector3d& operator[](int i) { return Normals[i]; } + const FVector3d& operator[](int i) const { return Normals[i]; } + + + /** + * Set the size of the Normals array to Count, and optionally clear all values to (0,0,0) + */ + void SetCount(int Count, bool bClearToZero); + + /** + * Compute standard per-vertex normals by averaging one-ring face normals + */ + void ComputeVertexNormals() + { + Compute_FaceAvg_AreaWeighted(); + } + + /** + * Compute per-triangle normals + */ + void ComputeTriangleNormals() + { + Compute_Triangle(); + } + + /** + * Recompute the per-element normals of the given overlay by averaging one-ring face normals + * @warning NormalOverlay must be attached to ParentMesh or an exact copy + */ + void RecomputeOverlayNormals(FDynamicMeshNormalOverlay* NormalOverlay) + { + Compute_Overlay_FaceAvg_AreaWeighted(NormalOverlay); + } + + + + /** + * Copy the current set of normals to the vertex normals of SetMesh + * @warning assumes that the computed normals are vertex normals + * @param bInvert if true, normals are flipped + */ + void CopyToVertexNormals(FDynamicMesh3* SetMesh, bool bInvert = false) const; + + /** + * Copy the current set of normals to the NormalOverlay attribute layer + * @warning assumes that the computed normals are attribute normals + * @param bInvert if true, normals are flipped + */ + void CopyToOverlay(FDynamicMeshNormalOverlay* NormalOverlay, bool bInvert = false) const; + + + + /** + * Compute per-vertex normals for the given Mesh + * @param bInvert if true, normals are flipped + */ + static void QuickComputeVertexNormals(FDynamicMesh3& Mesh, bool bInvert = false); + + + /** + * @return the vertex normal at vertex VertIDx of Mesh + */ + static FVector3d ComputeVertexNormal(const FDynamicMesh3& Mesh, int VertIdx); + + +protected: + /** Compute per-vertex normals using area-weighted averaging of one-ring triangles */ + void Compute_FaceAvg_AreaWeighted(); + /** Compute per-triangle normals */ + void Compute_Triangle(); + /** Recompute the element Normals of the given attribute overlay using area-weighted averaging of one-ring triangles */ + void Compute_Overlay_FaceAvg_AreaWeighted(const FDynamicMeshNormalOverlay* NormalOverlay); + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshRefinerBase.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshRefinerBase.h new file mode 100644 index 000000000000..a3ea12d9ae04 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshRefinerBase.h @@ -0,0 +1,264 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp FMeshRefinerBase + +#pragma once + +#include "DynamicMesh3.h" +#include "MeshConstraints.h" +#include "Util/ProgressCancel.h" + + +/** + * This is a base class that implements common functionality for various triangle mesh resampling strategies + * (ie FRemesher and FReducer). You probably should not use this class directly. + */ +class DYNAMICMESH_API FMeshRefinerBase +{ +protected: + /** Mesh that will be refined */ + FDynamicMesh3* Mesh = nullptr; + + /** Constraints are used to control how certain edges and vertices can be modified */ + FMeshConstraints* Constraints = nullptr; + + /** Vertices can be projected onto this surface when they are modified */ + IProjectionTarget* ProjTarget = nullptr; + + + FMeshRefinerBase(FDynamicMesh3* MeshIn) + { + this->Mesh = MeshIn; + } + + FMeshRefinerBase() + { + } + + +public: + + /** + * If true, then when two Fixed vertices have the same non-invalid SetID, + * we treat them as not fixed and allow collapse + */ + bool AllowCollapseFixedVertsWithSameSetID = true; + + + enum class EVertexControl + { + AllowAll = 0, + NoSmooth = 1, + NoProject = 2, + NoMovement = NoSmooth | NoProject + }; + /** + * This function allows client to specify fine-grained control over what happens to specific vertices. + * Somewhat redundant w/ FVertexConstraints, but simpler to code and has the option to be dynamic during remesh pass. + */ + TFunction VertexControlF = nullptr; + + + /** Options for projecting vertices onto target surface */ + enum class ETargetProjectionMode + { + NoProjection = 0, // disable projection + AfterRefinement = 1, // do all projection after the refine/smooth pass + Inline = 2 // project after each vertex update. Better results but more + // expensive because eg we might create a vertex with + // split, then project, then smooth, then project again. + }; + + /** Method to use to project vertices onto target surface. Default is no projection. */ + ETargetProjectionMode ProjectionMode = ETargetProjectionMode::NoProjection; + + + + /** Set this to be able to cancel running Remesher/Reducer*/ + FProgressCancel* Progress = nullptr; + + + + /** This is a debugging aid, will break to debugger if these edges are touched, in debug builds */ + TArray DebugEdges; + + /** Set to true to profile various passes @todo re-enable this! */ + bool ENABLE_PROFILING = false; + + /** 0 = no checking, 1 = check constraints and validity each pass, 2 = check validity after every mesh change (v slow but best for debugging) */ + int DEBUG_CHECK_LEVEL = 0; + + +public: + virtual ~FMeshRefinerBase() {} + + /** Get the current mesh we are operating on */ + FDynamicMesh3* GetMesh() { return Mesh; } + + /** Get the current mesh constraints */ + FMeshConstraints* GetConstraints() { return Constraints; } + + /** + * Set external constraints. + * Note that this object will be updated during computation. + */ + void SetExternalConstraints(FMeshConstraints* ConstraintsIn) { Constraints = ConstraintsIn; } + + + /** Get the current Projection Target */ + IProjectionTarget* ProjectionTarget() { return this->ProjTarget; } + + /** Set a Projection Target */ + void SetProjectionTarget(IProjectionTarget* TargetIn) { this->ProjTarget = TargetIn; } + + + + + + /** @return edge flip tolerance */ + double GetEdgeFlipTolerance() { return EdgeFlipTolerance; } + + /** Set edge flip tolerance. Value is clamped to range [-1,1] */ + void SetEdgeFlipTolerance(double NewTolerance) { EdgeFlipTolerance = VectorUtil::Clamp(NewTolerance, -1.0, 1.0); } + + + + + + /** If this returns true, abort computation. */ + virtual bool Cancelled() + { + return (Progress == nullptr) ? false : Progress->Cancelled(); + } + + + +protected: + + /** If normals dot product is less than this, we consider it a normal flip. default = 0 */ + double EdgeFlipTolerance = 0.0f; + + /** + * @return edge-flip dotproduct metric in range [-1,1] measured between two possibly-not-normalized normal directions. if EdgeFlipTolerance is 0, only the sign of the returned value is valid + */ + inline double ComputeEdgeFlipMetric(const FVector3d& Direction0, const FVector3d& Direction1) const + { + if (EdgeFlipTolerance == 0) + { + return Direction0.Dot(Direction1); + } + else + { + return Direction0.Normalized().Dot(Direction1.Normalized()); + } + } + + + /** + * Check if edge collapse will create a face-normal flip. + * Also checks if collapse would violate link condition, since we are iterating over one-ring anyway. + * This only checks one-ring of vid, so you have to call it twice, with vid and vother reversed, to check both one-rings + * @param vid first vertex of edge + * @param vother other vertex of edge + * @param newv new vertex position after collapse + * @param tc triangle on one side of edge + * @param td triangle on other side of edge + */ + bool CheckIfCollapseCreatesFlipOrInvalid(int vid, int vother, const FVector3d& newv, int tc, int td) const; + + /** + * Check if edge flip might reverse normal direction. + * Not entirely clear on how to best implement this test. Currently checking if any normal-pairs are reversed. + * @param a first vertex of edge + * @param b second vertex of edge + * @param c opposing vertex 1 + * @param d opposing vertex 2 + * @param t0 index of triangle containing [a,b,c] + */ + bool CheckIfFlipInvertsNormals(int a, int b, int c, int d, int t0) const; + + /** + * Figure out if we can collapse edge eid=[a,b] under current constraint set. + * First we resolve vertex constraints using CanCollapseVertex(). However this + * does not catch some topological cases at the edge-constraint level, which + * which we will only be able to detect once we know if we are losing a or b. + * See comments on CanCollapseVertex() for what collapse_to is for. + * @param a first vertex of edge + * @param b second vertex of edge + * @param c opposing vertex 1 + * @param d opposing vertex 2 + * @param tc index of triangle [a,b,c] + * @param td index of triangle [a,b,d] + * @param collapse_to either a or b if we should collapse to one of those, or -1 if either is acceptable + */ + bool CanCollapseEdge(int eid, int a, int b, int c, int d, int tc, int td, int& collapse_to) const; + + /** + * Resolve vertex constraints for collapsing edge eid=[a,b]. Generally we would + * collapse a to b, and set the new position as 0.5*(v_a+v_b). However if a *or* b + * are constrained, then we want to keep that vertex and collapse to its position. + * This vertex (a or b) will be returned in collapse_to, which is -1 otherwise. + * If a *and* b are constrained, then things are complicated (and documented below). + * @param eid edge ID + * @param a first vertex of edge + * @param b second vertex of edge* + * @param collapse_to either a or b if we should collapse to one of those, or -1 if either is acceptable + */ + bool CanCollapseVertex(int eid, int a, int b, int& collapse_to) const; + + /** + * @return true if given vertex is Fixed under current constraints + */ + inline bool IsVertexFixed(int VertexID) + { + return (Constraints != nullptr && Constraints->GetVertexConstraint(VertexID).Fixed); + } + + + /** + * @return true if given vertex is Fixed or has a projection target + */ + inline bool IsVertexConstrained(int VertexID) + { + if (Constraints != nullptr) + { + FVertexConstraint vc = Constraints->GetVertexConstraint(VertexID); + return (vc.Fixed || vc.Target != nullptr); + } + return false; + } + + /** + * @return constraint for given vertex + */ + inline FVertexConstraint GetVertexConstraint(int VertexID) + { + if (Constraints != nullptr) + { + return Constraints->GetVertexConstraint(VertexID); + } + return FVertexConstraint::Unconstrained(); + } + + /** + * @return true if vertex has constraint + */ + inline bool GetVertexConstraint(int VertexID, FVertexConstraint& OutConstraint) + { + return (Constraints == nullptr) ? false : + Constraints->GetVertexConstraint(VertexID, OutConstraint); + } + + + + + + /* + * testing/debug/profiling stuff + */ + void RuntimeDebugCheck(int EdgeID); + virtual void DoDebugChecks(bool bEndOfPass = false); + void DebugCheckUVSeamConstraints(); + void DebugCheckVertexConstraints(); + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshRegionBoundaryLoops.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshRegionBoundaryLoops.h new file mode 100644 index 000000000000..5d1212e1f17d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshRegionBoundaryLoops.h @@ -0,0 +1,97 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp MeshRegionBoundaryLoops + +#pragma once + +#include "DynamicMesh3.h" +#include "EdgeLoop.h" + + +/** + * Extract FEdgeLoops on the boundary of a set of triangles of a mesh. + * @todo this was an early port and possibly can be optimized + */ +class DYNAMICMESH_API FMeshRegionBoundaryLoops +{ +public: + /** Mesh we are finding loops on */ + const FDynamicMesh3* Mesh = nullptr; + /** Resulting set of loops filled by Compute() */ + TArray Loops; + +public: + FMeshRegionBoundaryLoops() {} + + FMeshRegionBoundaryLoops(const FDynamicMesh3* MeshIn, const TArray& RegionTris, bool bAutoCompute = true); + + /** + * Find set of FEdgeLoops on the border of the input triangle set + * @return false if errors occurred, in this case output set is incomplete + */ + bool Compute(); + + + /** @return number of loops found by Compute() */ + int GetLoopCount() const + { + return Loops.Num(); + } + + /** @return Loop at the given index */ + const FEdgeLoop& operator[](int Index) const + { + return Loops[Index]; + } + + /** @return index of loop with maximum number of vertices */ + int GetMaxVerticesLoopIndex() const; + + + +protected: + + + // TODO: C# code used IndexFlagSet here, which is sparse if region size << mesh size + // list of included triangles and edges + TArray triangles; + TArray edges; + TArray edges_roi; + + + static bool ContainsElement(const TArray& flagset, int index) { return flagset[index] == true; } + + bool IsEdgeOnBoundary(int eid) const { return ContainsElement(edges, eid); } + + // returns true for both internal and mesh boundary edges + // tid_in and tid_out are triangles 'in' and 'out' of set, respectively + bool IsEdgeOnBoundary(int eid, int& tid_in, int& tid_out) const; + + // return same indices as GetEdgeV, but oriented based on attached triangle + FIndex2i GetOrientedEdgeVerts(int eID, int tid_in, int tid_out); + + // returns first two boundary edges, and count of total boundary edges + int GetVertexBoundaryEdges(int vID, int& e0, int& e1); + + // e needs to be large enough (ie call GetVtxBoundaryEdges, or as large as max one-ring) + // returns count, ie number of elements of e that were filled + int GetAllVertexBoundaryEdges(int vID, TArray& e); + + + // [TODO] cache this : a dictionary? we will not need very many, but we will + // need each multiple times! + FVector3d GetVertexNormal(int vid); + + // ok, bdry_edges[0...bdry_edges_count] contains the boundary edges coming out of bowtie_v. + // We want to pick the best one to continue the loop that came : to bowtie_v on incoming_e. + // If the loops are all sane, then we will get the smallest loops by "turning left" at bowtie_v. + int FindLeftTurnEdge(int incoming_e, int bowtie_v, TArray& bdry_edges, int bdry_edges_count, TArray& used_edges); + + + // This is called when loopV contains one or more "bowtie" vertices. + // These vertices *might* be duplicated : loopV (but not necessarily) + // If they are, we have to break loopV into subloops that don't contain duplicates. + TArray ExtractSubloops(TArray& loopV, const TArray& loopE, const TArray& bowties); + + +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshSimplification.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshSimplification.h new file mode 100644 index 000000000000..aace8f740a82 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshSimplification.h @@ -0,0 +1,333 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp Reducer + +#pragma once + +#include "MeshRefinerBase.h" +#include "QuadricError.h" +#include "Util/IndexPriorityQueue.h" + +#include "DynamicMeshAttributeSet.h" + + +enum class ESimplificationResult +{ + Ok_Collapsed = 0, + Ignored_CannotCollapse = 1, + Ignored_EdgeIsFullyConstrained = 2, + Ignored_EdgeTooLong = 3, + Ignored_Constrained = 4, + Ignored_CreatesFlip = 5, + Failed_OpNotSuccessful = 6, + Failed_NotAnEdge = 7 +}; + +/** + * Implementation of Garland & Heckbert Quadric Error Metric (QEM) Triangle Mesh Simplification + */ +template +class TMeshSimplification : public FMeshRefinerBase +{ +public: + + typedef QuadricErrorType FQuadricErrorType; + + /** + * If true, we try to find position for collapsed vertices that minimizes quadric error. + * If false we just use midpoints, which is actually significantly slower, because it results + * in may more points that would cause a triangle flip, which are then rejected. + */ + bool bMinimizeQuadricPositionError = true; + + /** if true, we try to keep boundary vertices on boundary. You probably want this. */ + bool bPreserveBoundaryShape = true; + + + + TMeshSimplification(FDynamicMesh3* m) : FMeshRefinerBase(m) + { + NormalOverlay = nullptr; + if (m->Attributes()) + { + NormalOverlay = m->Attributes()->PrimaryNormals(); + } + } + + + /** + * Simplify mesh until we reach a specific triangle count. + * Note that because triangles are removed in pairs, the resulting count may be TriangleCount-1 + * @param TriangleCount the target triangle count + */ + virtual void SimplifyToTriangleCount(int TriangleCount); + + /** + * Simplify mesh until it has a specific vertex count + * @param VertexCount the target vertex count + */ + virtual void SimplifyToVertexCount(int VertexCount); + + /** + * Simplify mesh until no edges smaller than min length remain. This is not a great criteria. + * @param MinEdgeLength collapse any edge longer than this + */ + virtual void SimplifyToEdgeLength(double MinEdgeLength); + + /** + * Does N rounds of collapsing edges longer than fMinEdgeLength. Does not use Quadrics or priority queue. + * This is a quick way to get triangle count down on huge meshes (eg like marching cubes output). + * @param MinEdgeLength collapse any edge longer than this + * @param Rounds number of collapse rounds + * @param MeshIsClosedHint if you know the mesh is closed, this pass this true to avoid some precomputes + */ + virtual void FastCollapsePass(double MinEdgeLength, int Rounds = 1, bool bMeshIsClosedHint = false); + + + +protected: + + TMeshSimplification() // for subclasses that extend our behavior + { + } + + // this just lets us write more concise code + bool EnableInlineProjection() const { return ProjectionMode == ETargetProjectionMode::Inline; } + + + double MinEdgeLength = FMathd::MaxReal; + int TargetCount = INT_MAX; + enum class ETargetModes + { + TriangleCount = 0, + VertexCount = 1, + MinEdgeLength = 2 + }; + ETargetModes SimplifyMode = ETargetModes::TriangleCount; + + + + /** Top-level function that does the simplification */ + virtual void DoSimplify(); + + + + // StartEdges() and GetNextEdge() control the iteration over edges that will be refined. + // Default here is to iterate over entire mesh-> + // Subclasses can override these two functions to restrict the affected edges (eg EdgeLoopRemesher) + + // We are using a modulo-index loop to break symmetry/pathological conditions. + const int ModuloPrime = 31337; // any prime will do... + int MaxEdgeID = 0; + virtual int StartEdges() + { + MaxEdgeID = Mesh->MaxEdgeID(); + return 0; + } + + virtual int GetNextEdge(int CurEdgeID, bool& bDoneOut) + { + int new_eid = (CurEdgeID + ModuloPrime) % MaxEdgeID; + bDoneOut = (new_eid == 0); + return new_eid; + } + + + + + TArray vertQuadrics; + virtual void InitializeVertexQuadrics(); + + TArray triAreas; + TArray triQuadrics; + virtual void InitializeTriQuadrics(); + + FDynamicMeshNormalOverlay* NormalOverlay; + + FQuadricErrorType ComputeFaceQuadric(const int tid, FVector3d& nface, FVector3d& c, double& Area) const; + + // internal class for priority queue + struct QEdge + { + int eid; + FQuadricErrorType q; + FVector3d collapse_pt; + + QEdge() { eid = 0; } + + QEdge(int edge_id, const FQuadricErrorType& qin, const FVector3d& pt) + { + eid = edge_id; + q = qin; + collapse_pt = pt; + } + }; + + TArray EdgeQuadrics; + FIndexPriorityQueue EdgeQueue; + + struct FEdgeError + { + float error; + int eid; + bool operator<(const FEdgeError& e2) const + { + return error < e2.error; + } + }; + + virtual void InitializeQueue(); + + // return point that minimizes quadric error for edge [ea,eb] + FVector3d OptimalPoint(int eid, const FQuadricErrorType& q, int ea, int eb); + + FVector3d GetProjectedPoint(const FVector3d& pos) + { + if (EnableInlineProjection() && ProjTarget != nullptr) + { + return ProjTarget->Project(pos); + } + return pos; + } + + + // update queue weight for each edge in vertex one-ring + virtual void UpdateNeighbours(int vid, FIndex2i removedTris, FIndex2i opposingVerts); + + + virtual void Reproject() + { + ProfileBeginProject(); + if (ProjTarget != nullptr && ProjectionMode == ETargetProjectionMode::AfterRefinement) + { + FullProjectionPass(); + DoDebugChecks(); + } + ProfileEndProject(); + } + + + + + + bool bHaveBoundary; + TArray IsBoundaryVtxCache; + void Precompute(bool bMeshIsClosed = false); + + inline bool IsBoundaryVertex(int vid) const + { + return IsBoundaryVtxCache[vid]; + } + + + + + + ESimplificationResult CollapseEdge(int edgeID, FVector3d vNewPos, int& collapseToV); + + + + // subclasses can override these to implement custom behavior... + virtual void OnEdgeCollapse(int edgeID, int va, int vb, const FDynamicMesh3::FEdgeCollapseInfo& collapseInfo) + { + // this is for subclasses... + } + + + + + + // Project vertices onto projection target. + virtual void FullProjectionPass(); + + virtual void ProjectVertex(int vID, IProjectionTarget* targetIn); + + // used by collapse-edge to get projected position for new vertex + virtual FVector3d GetProjectedCollapsePosition(int vid, const FVector3d& vNewPos); + + virtual void ApplyToProjectVertices(const TFunction& apply_f); + + + + /* + * testing/debug/profiling stuff + */ +protected: + + + // + // profiling functions, turn on ENABLE_PROFILING to see output in console + // + int COUNT_COLLAPSES; + int COUNT_ITERATIONS; + //Stopwatch AllOpsW, SetupW, ProjectW, CollapseW; + + virtual void ProfileBeginPass() + { + if (ENABLE_PROFILING) + { + COUNT_COLLAPSES = 0; + COUNT_ITERATIONS = 0; + //AllOpsW = new Stopwatch(); + //SetupW = new Stopwatch(); + //ProjectW = new Stopwatch(); + //CollapseW = new Stopwatch(); + } + } + + virtual void ProfileEndPass() + { + if (ENABLE_PROFILING) + { + //System.Console.WriteLine(string.Format( + // "ReducePass: T {0} V {1} collapses {2} iterations {3}", mesh->TriangleCount, mesh->VertexCount, COUNT_COLLAPSES, COUNT_ITERATIONS + //)); + //System.Console.WriteLine(string.Format( + // " Timing1: setup {0} ops {1} project {2}", Util.ToSecMilli(SetupW.Elapsed), Util.ToSecMilli(AllOpsW.Elapsed), Util.ToSecMilli(ProjectW.Elapsed) + //)); + } + } + + virtual void ProfileBeginOps() + { + //if (ENABLE_PROFILING) AllOpsW.Start(); + } + virtual void ProfileEndOps() + { + //if (ENABLE_PROFILING) AllOpsW.Stop(); + } + virtual void ProfileBeginSetup() + { + //if (ENABLE_PROFILING) SetupW.Start(); + } + virtual void ProfileEndSetup() + { + //if (ENABLE_PROFILING) SetupW.Stop(); + } + + virtual void ProfileBeginProject() + { + //if (ENABLE_PROFILING) ProjectW.Start(); + } + virtual void ProfileEndProject() + { + //if (ENABLE_PROFILING) ProjectW.Stop(); + } + + virtual void ProfileBeginCollapse() + { + //if (ENABLE_PROFILING) CollapseW.Start(); + } + virtual void ProfileEndCollapse() + { + //if (ENABLE_PROFILING) CollapseW.Stop(); + } + + +}; + + +// The simplifier +typedef TMeshSimplification< FAttrBasedQuadricErrord > FAttrMeshSimplification; +typedef TMeshSimplification < FVolPresQuadricErrord > FVolPresMeshSimplification; +typedef TMeshSimplification< FQuadricErrord > FQEMSimplification; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshTangents.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshTangents.h new file mode 100644 index 000000000000..f90a4d60fe07 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshTangents.h @@ -0,0 +1,103 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DynamicMesh3.h" +#include "DynamicMeshAttributeSet.h" + +/** + * TMeshTangents is a utility class that can calculate and store various types of + * tangent vectors for a FDynamicMesh. + */ +template +class DYNAMICMESH_API TMeshTangents +{ +protected: + /** Target Mesh */ + const FDynamicMesh3* Mesh; + /** Set of computed tangents */ + TArray> Tangents; + /** Set of computed bitangents */ + TArray> Bitangents; + +public: + TMeshTangents() + { + Mesh = nullptr; + } + + TMeshTangents(const FDynamicMesh3* Mesh) + { + SetMesh(Mesh); + } + + void SetMesh(const FDynamicMesh3* MeshIn) + { + this->Mesh = MeshIn; + } + + const TArray>& GetTangents() const { return Tangents; } + + const TArray>& GetBitangents() const { return Bitangents; } + + + /** + * Return tangent and bitangent at a vertex of triangle for per-triangle computed tangents + * @param TriangleID triangle index in mesh + * @param TriVertIdx vertex index in range 0,1,2 + */ + void GetPerTriangleTangent(int TriangleID, int TriVertIdx, FVector3& TangentOut, FVector3& BitangentOut) const + { + int k = TriangleID * 3 + TriVertIdx; + TangentOut = Tangents[k]; + BitangentOut = Bitangents[k]; + } + + + /** + * Set tangent and bitangent at a vertex of triangle for per-triangle computed tangents. + * @param TriangleID triangle index in mesh + * @param TriVertIdx vertex index in range 0,1,2 + */ + void SetPerTriangleTangent(int TriangleID, int TriVertIdx, const FVector3& Tangent, const FVector3& Bitangent) + { + int k = TriangleID * 3 + TriVertIdx; + Tangents[k] = Tangent; + Bitangents[k] = Bitangent; + } + + + /** + * Calculate per-triangle tangent spaces based on the given per-triangle normal and UV overlays. + * In this mode there is no averaging of tangents across triangles. So if we have N triangles + * in the mesh, then 3*N tangents are generated. These tangents are computed in parallel. + */ + void ComputePerTriangleTangents(const FDynamicMeshNormalOverlay* NormalOverlay, const FDynamicMeshUVOverlay* UVOverlay) + { + Internal_ComputePerTriangleTangents(NormalOverlay, UVOverlay); + } + + + /** + * Set internal buffer sizes suitable for calculating per-triangle tangents. + * This is intended to be used if you wish to calculate your own tangents and use SetPerTriangleTangent() + */ + void InitializePerTriangleTangents(bool bClearToZero) + { + SetTangentCount(Mesh->MaxTriangleID() * 3, bClearToZero); + } + + + +protected: + /** + * Set the size of the Tangents array to Count, and optionally clear all values to (0,0,0) + */ + void SetTangentCount(int Count, bool bClearToZero); + + // Calculate per-triangle tangents + void Internal_ComputePerTriangleTangents(const FDynamicMeshNormalOverlay* NormalOverlay, const FDynamicMeshUVOverlay* UVOverlay); +}; + +typedef TMeshTangents FMeshTangentsf; +typedef TMeshTangents FMeshTangentsd; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshWeights.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshWeights.h new file mode 100644 index 000000000000..47ffc3feef23 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshWeights.h @@ -0,0 +1,44 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp MeshWeights + +#pragma once + +#include "DynamicMesh3.h" + +/** + * FMeshWeights implements various techniques for computing local weights of a mesh, + * for example one-ring weights like Cotangent or Mean-Value. + */ +class DYNAMICMESH_API FMeshWeights +{ +public: + + /** + * Compute uniform centroid of a vertex one-ring. + * These weights are strictly positive and all equal to 1 / valence + */ + static FVector3d UniformCentroid(const FDynamicMesh3 & mesh, int VertexIndex); + + + /** + * Compute mean-value centroid of a vertex one-ring. + * These weights are strictly positive. + */ + static FVector3d MeanValueCentroid(const FDynamicMesh3 & mesh, int VertexIndex); + + /** + * Compute cotan-weighted centroid of a vertex one-ring. + * These weights are numerically unstable if any of the triangles are degenerate. + * We catch these problems and return input vertex as centroid + */ + static FVector3d CotanCentroid(const FDynamicMesh3 & mesh, int VertexIndex); + + /** + * Compute the voronoi area associated with a vertex. + */ + static double VoronoiArea(const FDynamicMesh3& mesh, int VertexIndex); + +protected: + FMeshWeights() = delete; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/ExtrudeMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/ExtrudeMesh.h new file mode 100644 index 000000000000..10b741f6f1c5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/ExtrudeMesh.h @@ -0,0 +1,98 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp MergeCoincidentEdges + +#pragma once + +#include "MathUtil.h" +#include "VectorTypes.h" +#include "GeometryTypes.h" +#include "MeshBoundaryLoops.h" + + +class FDynamicMesh3; + +/** + * FExtrudeMesh implements a full-mesh extrusion of a mesh. This happens in two stages: + * 1) all triangles of input mesh are duplicated and offset + * 2) base and offset border loops are stitched together with triangulated quads + * Step 2 does not occur if there are no boundary loops (ie for a closed input mesh) + * + * Each quad of the border loop is assigned it's own normal and UVs (ie each is a separate UV-island) + */ +class DYNAMICMESH_API FExtrudeMesh +{ +public: + + // + // Inputs + // + + /** The mesh that we are modifying */ + FDynamicMesh3* Mesh; + + /** This function is called to generate the offset vertex position. Default returns (Position + DefaultExtrudeDistance * Normal) */ + TFunction ExtrudedPositionFunc; + + /** If no Extrude function is set, we will displace by DefaultExtrudeDistance*Normal */ + double DefaultExtrudeDistance = 1.0; + + /** if Extrusion is "negative" (ie negative distance, inset, etc) then this value must be set to false or the output will have incorrect winding orientation */ + bool IsPositiveOffset = true; + + /** quads on the stitch loop are planar-projected and scaled by this amount */ + float UVScaleFactor = 1.0f; + + + // + // Outputs + // + + /** Initial boundary loops on the mesh (may be empty) */ + FMeshBoundaryLoops InitialLoops; + /** set of triangles that were extruded */ + TArray InitialTriangles; + /** set of vertices that were extruded */ + TArray InitialVertices; + /** Map from initial vertices to new offset vertices */ + TMap InitialToOffsetMapV; + /** list of triangles on offset surface, in correspondence with InitialTriangles (note: can get vertices via InitialToOffsetMapV(InitialVertices) */ + TArray OffsetTriangles; + /** list of new groups of triangles on offset surface */ + TArray OffsetTriGroups; + + /** New edge loops on borders of offset patches (1-1 correspondence w/ InitialLoops.Loops) */ + TArray NewLoops; + /** Lists of triangle-strip "tubes" that connect each loop-pair */ + TArray> StitchTriangles; + /** List of group ids / polygon ids on each triangle-strip "tube" */ + TArray> StitchPolygonIDs; + + + + +public: + FExtrudeMesh(FDynamicMesh3* mesh); + + virtual ~FExtrudeMesh() {} + + + /** + * @return EOperationValidationResult::Ok if we can apply operation, or error code if we cannot + */ + virtual EOperationValidationResult Validate() + { + // @todo calculate MeshBoundaryLoops and make sure it is valid + + // is there any reason we couldn't do this?? + + return EOperationValidationResult::Ok; + } + + + /** + * Apply the Extrude operation to the input mesh. + * @return true if the algorithm succeeds + */ + virtual bool Apply(); +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/GroupTopologyDeformer.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/GroupTopologyDeformer.h new file mode 100644 index 000000000000..5e9974c553e7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/GroupTopologyDeformer.h @@ -0,0 +1,199 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "EdgeSpan.h" + +class FDynamicMesh3; +class FGroupTopology; + + + +/** + * Basic cache of vertex positions. + */ +class DYNAMICMESH_API FVertexPositionCache +{ +public: + TArray Vertices; + TArray Positions; + + void Reset() + { + Vertices.Reset(); + Positions.Reset(); + } + + /** Save this vertex. Does not check that VertexID hasn't already been added. */ + void AddVertex(const FDynamicMesh3* Mesh, int VertexID) + { + Vertices.Add(VertexID); + Positions.Add(Mesh->GetVertex(VertexID)); + } + + /** Apply saved positions to Mesh */ + void SetPositions(FDynamicMesh3* Mesh) + { + int Num = Vertices.Num(); + for (int k = 0; k < Num; ++k) + { + Mesh->SetVertex(Vertices[k], Positions[k]); + } + } +}; + + + +/** + * FGroupTopologyDeformer supports deforming a Mesh based on an overlaid FGroupTopology. + * First the client defines a set of "Handle" elements (Faces/Corners/Edges) using SetActiveHandleX(). + * The client will provide new vertex positions for the vertices of these elements via the + * HandleVertexDeformFunc argument to UpdateSolution(). Once the Handle vertices have been + * updated, the deformer solves for updated vertex positions in the GroupTopology Faces + * that are adjacent to the handles. This region is referred to as the "ROI" (Region-of-Interest) + * + * The default deformation is to first solve for the updated edges, and then + * solve for updated faces. This is done via linear encoding of the edge and face vertices + * relative to their boundaries (edge boundary is endpoint corners, face boundary is edges). + * + * Various functions can be overrided to customize behavior. + * + */ +class DYNAMICMESH_API FGroupTopologyDeformer +{ +public: + virtual ~FGroupTopologyDeformer() {} + + /** Set the Mesh and Topology to use for the deformation */ + void Initialize(const FDynamicMesh3* Mesh, const FGroupTopology* Topology); + + const FDynamicMesh3* GetMesh() const { return Mesh; } + const FGroupTopology* GetTopology() const { return Topology; } + + // + // Handle setup/configuraiton + // + + /** + * Set the active handle to the given Faces + */ + virtual void SetActiveHandleFaces(const TArray& FaceGroupIDs); + + /** + * Set the active handle to the given Corners + */ + virtual void SetActiveHandleCorners(const TArray& TopologyCornerIDs); + + /** + * Set the active handle to the given Edges + */ + virtual void SetActiveHandleEdges(const TArray& TopologyEdgeIDs); + + // + // Solving + // + + /** + * Update TargetMesh by first calling HandleVertexDeformFunc() to get new + * handle vertex positions, then solving for new ROI vertex positions. + */ + virtual void UpdateSolution(FDynamicMesh3* TargetMesh, const TFunction& HandleVertexDeformFunc); + + /** + * Restore the Handle and ROI vertex positions to their initial state + */ + virtual void ClearSolution(FDynamicMesh3* TargetMesh); + + + + /** @return the set of handle vertices */ + const TSet& GetHandleVertices() const { return HandleVertices; } + /** @return the set of all vertices that will be modified by the deformation*/ + const TSet& GetModifiedVertices() const { return ModifiedVertices; } + + +protected: + // + // These are functions that subclasses may wish to override + // + + /** reset all internal data structures, eg when changing handle */ + virtual void Reset(); + + /** + * Populate the internal ROI data structures based on the given HandleGroups and ROIGroups. + * HandleGroups will be empty if the Handle is a set of Vertices or Edges. + */ + virtual void CalculateROI(const TArray& HandleGroups, const TArray& ROIGroups); + + /** + * Save the positions of all vertices that will be modified + */ + virtual void SaveInitialPositions(); + + /** + * Precompute the representation of the ROI vertices at the initial positions. + */ + virtual void ComputeEncoding(); + +protected: + const FDynamicMesh3* Mesh = nullptr; + const FGroupTopology* Topology = nullptr; + + FVertexPositionCache InitialPositions; + TSet ModifiedVertices; + TSet HandleVertices; + TSet HandleBoundaryVertices; + TSet FixedBoundaryVertices; + TSet ROIEdgeVertices; + TSet FaceVertsTemp; + TSet FaceBoundaryVertsTemp; + + struct FROIEdge + { + int EdgeIndex; + FEdgeSpan Span; + }; + TArray ROIEdges; + + struct FROIFace + { + TArray BoundaryVerts; + TArray InteriorVerts; + }; + TArray ROIFaces; + + + // + // Deformation strategy (should be in subclass?) + // + + struct FEdgeVertexEncoding + { + double T; + FVector3d Delta; + FEdgeVertexEncoding() { T = 0; Delta = FVector3d::Zero(); } + }; + struct FEdgeEncoding + { + TArray Vertices; + }; + TArray EdgeEncodings; + + + + struct FFaceVertexEncoding + { + TArray Weights; + TArray Deltas; + }; + struct FFaceEncoding + { + TArray Vertices; + }; + TArray FaceEncodings; + + + + +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/MergeCoincidentMeshEdges.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/MergeCoincidentMeshEdges.h new file mode 100644 index 000000000000..c6961cc403a5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/MergeCoincidentMeshEdges.h @@ -0,0 +1,65 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp MergeCoincidentEdges + +#pragma once + +#include "MathUtil.h" +#include "VectorTypes.h" + +class FDynamicMesh3; + +/** + * FMergeCoincidentMeshEdges finds pairs of boundary edges of the mesh that are identical + * (ie have endpoint vertices at the same locations) and merges the pair into a single edge. + * This is similar to welding vertices but safer because it prevents bowties from being formed. + * + * Currently if the two edges have the same "orientation" (ie from their respective triangles) + * they cannot be merged. + * + */ +class DYNAMICMESH_API FMergeCoincidentMeshEdges +{ +public: + /** default tolerance is float ZeroTolerance */ + static const double DEFAULT_TOLERANCE; // = FMathf::ZeroTolerance; + + /** The mesh that we are modifying */ + FDynamicMesh3* Mesh; + + /** Edges are coincident if both pairs of endpoint vertices are closer than this distance */ + double MergeVertexTolerance = DEFAULT_TOLERANCE; + + /** Only merge unambiguous pairs that have unique duplicate-edge matches */ + bool OnlyUniquePairs = false; + + /** + * Edges are considered as potentially the same if their midpoints are within this distance. + * Due to floating-point roundoff this should be larger than MergeVertexTolerance. + * If zero, we set to MergeVertexTolerance*2 + */ + double MergeSearchTolerance = 0; + +public: + FMergeCoincidentMeshEdges(FDynamicMesh3* mesh) : Mesh(mesh) + { + } + + /** + * Run the merge operation and modify .Mesh + * @return true if the algorithm succeeds + */ + virtual bool Apply(); + + +protected: + double MergeVtxDistSqr; // cached value + + // returns true if endpoint vertices are within tolerance. Note that we do not know the order of + // the vertices here so we try both combinations. + inline bool IsSameEdge(const FVector3d& a, const FVector3d& b, const FVector3d& c, const FVector3d& d) const + { + return (a.DistanceSquared(c) < MergeVtxDistSqr && b.DistanceSquared(d) < MergeVtxDistSqr) || + (a.DistanceSquared(d) < MergeVtxDistSqr && b.DistanceSquared(c) < MergeVtxDistSqr); + } +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ProjectionTargets.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ProjectionTargets.h new file mode 100644 index 000000000000..abcd3bf382f2 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ProjectionTargets.h @@ -0,0 +1,65 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +// Port of geometry3cpp ProjectionTargets + +#include "DynamicMeshAABBTree3.h" +#include "Distance/DistPoint3Triangle3.h" + + +/** + * MeshProjectionTarget provides an IProjectionTarget interface to a FDynamicMesh + FDynamicMeshAABBTree3 + * Use to project points to mesh surface. + */ +class MeshProjectionTarget : public IOrientedProjectionTarget +{ +public: + /** The mesh to project onto */ + FDynamicMesh3* Mesh; + /** An AABBTree for Mesh */ + FDynamicMeshAABBTree3* Spatial; + + ~MeshProjectionTarget() {} + + MeshProjectionTarget(FDynamicMesh3* MeshIn, FDynamicMeshAABBTree3* SpatialIn) + { + Mesh = MeshIn; + Spatial = SpatialIn; + } + + + /** + * @return Projection of Point onto this target + */ + virtual FVector3d Project(const FVector3d& Point, int Identifier = -1) override + { + double fDistSqr; + int tNearestID = Spatial->FindNearestTriangle(Point, fDistSqr); + FTriangle3d Triangle; + Mesh->GetTriVertices(tNearestID, Triangle.V[0], Triangle.V[1], Triangle.V[2]); + + FDistPoint3Triangle3d DistanceQuery(Point, Triangle); + DistanceQuery.GetSquared(); + return DistanceQuery.ClosestTrianglePoint; + } + + /** + * @return Projection of Point onto this target, and set ProjectNormalOut to the triangle normal at the returned point (*not* interpolated vertex normal) + */ + virtual FVector3d Project(const FVector3d& Point, FVector3d& ProjectNormalOut, int Identifier = -1) override + { + double fDistSqr; + int tNearestID = Spatial->FindNearestTriangle(Point, fDistSqr); + FTriangle3d Triangle; + Mesh->GetTriVertices(tNearestID, Triangle.V[0], Triangle.V[1], Triangle.V[2]); + + ProjectNormalOut = Triangle.Normal(); + + FDistPoint3Triangle3d DistanceQuery(Point, Triangle); + DistanceQuery.GetSquared(); + return DistanceQuery.ClosestTrianglePoint; + } + + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Remesher.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Remesher.h new file mode 100644 index 000000000000..3938dc61ad60 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Remesher.h @@ -0,0 +1,343 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp Remesher + +#pragma once + +#include "MeshRefinerBase.h" + + +/** + * FRemesher implements edge flip/split/collapse/smooth Remeshing. + * The basic process is very similar to the one described in: + * A Remeshing Approach to Multiresolution Modeling. Botsch & Kobbelt, SGP 2004. + * https://graphics.uni-bielefeld.de/publications/sgp04.pdf + * + * In the iterative usage, each call to BasicRemeshPass() makes a pass over the mesh + * and attempts to update the topology and shape to satisfy the target criteria. + * This function iterates over the mesh edges and calls ProcessEdge(), which tries to + * Collapse, then Split, then Flip the edge. After the topology pass, we optionally + * do a full-mesh Smoothing pass, and a full-mesh Reprojection pass, to project + * onto a target surface. + * + * A highly flexible constraint system can be used to control what is allowed to + * happen to individual edges and vertices, as well as Project vertices onto arbitrary + * proejction targets. This is done via FMeshConstraints which is set in the MeshRefinerBase base class. + * + * Many aspects of the algorithm can be controlled by the public variables in the block below. + * In addition many of the core internal functions can be overriden to customize behavior. + * Various callbacks functions are called when topology changes occur which allows + * you to for instance track changed regions of the mesh. + * + * @todo parallel smoothing and projection + * @todo port fast queue-based convergent remeshing + * + */ +class DYNAMICMESH_API FRemesher : public FMeshRefinerBase +{ +public: + // + // Configuration variables and flags + // + + /** Controls whether edge-flips are allowed during remeshing pass */ + bool bEnableFlips = true; + /** Controls whether edge collapses are allowed during remeshing pass */ + bool bEnableCollapses = true; + /** Controls whether edge splits are allowed during remeshing pass */ + bool bEnableSplits = true; + /** Controls whether vertex smoothing is applied during remeshing pass */ + bool bEnableSmoothing = true; + + /** Controls whether we try to prevent normal flips inside operations and smoothing. + * This is moderately expensive and often is not needed. Disabled by default. + */ + bool bPreventNormalFlips = false; + + /** Minimum target edge length. Edges shorter than this will be collapsed if possible. */ + double MinEdgeLength = 1.0; + /** Maximum target edge length. Edges longer than this will be split if possible. */ + double MaxEdgeLength = 3.0; + + /** Smoothing speed, in range [0,1] */ + double SmoothSpeedT = 0.1; + + /** Built-in Smoothing types */ + enum class ESmoothTypes + { + Uniform = 0, /** Uniform weights, produces regular mesh and fastest convergence */ + Cotan = 1, /** Cotangent weights prevent tangential flow and hence preserve triangle shape / texture coordinates, but can become unstable... */ + MeanValue = 2 /** Mean Value weights also have reduced tangential flow but are never negative and hence more stable */ + }; + + /** Type of smoothing that will be applied unless overridden by CustomSmoothF */ + ESmoothTypes SmoothType = ESmoothTypes::Uniform; + + /** Override default smoothing function */ + TFunction CustomSmoothF; + + + + /** enable parallel projection. Only applied in AfterRefinement mode */ + bool bEnableParallelProjection = true; + + /** + * Enable parallel smoothing. This will produce slightly different results + * across runs because we smooth in-place and hence there will be order side-effects. + */ + bool bEnableParallelSmooth = true; + + /** + * If smoothing is done in-place, we don't need an extra buffer, but also there will some randomness introduced in results. Probably worse. + */ + bool bEnableSmoothInPlace = false; + + + +public: + // + // Public API + // + + FRemesher(FDynamicMesh3* MeshIn) : FMeshRefinerBase(MeshIn) + { + } + + + /** Set min/max edge-lengths to sane values for given target edge length */ + void SetTargetEdgeLength(double fLength); + + + /** + * We can speed things up if we precompute some invariants. + * You need to re-run this if you are changing the mesh externally + * between remesh passes, otherwise you will get weird results. + * But you will probably still come out ahead, computation-time-wise + */ + virtual void Precompute(); + + + /** + * Linear edge-refinement pass, followed by smoothing and projection + * - Edges are processed in prime-modulo-order to break symmetry + * - smoothing is done in parallel if EnableParallelSmooth = true + * - Projection pass if ProjectionMode == AfterRefinement + * - number of modified edges returned in ModifiedEdgesLastPass + */ + virtual void BasicRemeshPass(); + + + +public: + // + // Callbacks and in-progress information + // + + + /** Callback for subclasses to override to implement custom behavior */ + virtual void OnEdgeSplit(int EdgeID, int VertexA, int VertexB, const FDynamicMesh3::FEdgeSplitInfo& SplitInfo) + { + // this is for subclasses... + } + + /** Callback for subclasses to override to implement custom behavior */ + virtual void OnEdgeCollapse(int EdgeID, int VertexA, int VertexB, const FDynamicMesh3::FEdgeCollapseInfo& CollapseInfo) + { + // this is for subclasses... + } + + /** + * Number of edges that were modified in previous Remesh pass. + * If this number gets small relative to edge count, you have probably converged (ish) + */ + int ModifiedEdgesLastPass = 0; + + +protected: + + FRemesher() // for subclasses that extend our behavior + { + } + + /** we can avoid some checks/etc if we know that the mesh has no boundary edges. Set by Precompute() */ + bool bMeshIsClosed = false; + + // this just lets us write more concise code + bool EnableInlineProjection() const { return ProjectionMode == ETargetProjectionMode::Inline; } + + + // StartEdges() and GetNextEdge() control the iteration over edges that will be refined. + // Default here is to iterate over entire mesh-> + // Subclasses can override these two functions to restrict the affected edges (eg EdgeLoopFRemesher) + + + // We are using a modulo-index loop to break symmetry/pathological conditions. + // For example in a highly tessellated minimal cylinder, if the top/bottom loops have + // sequential edge IDs, and all edges are < min edge length, then we can easily end + // up successively collapsing each tiny edge, and eroding away the entire mesh! + // By using modulo-index loop we jump around and hence this is unlikely to happen. + const int ModuloPrime = 31337; // any prime will do... + int MaxEdgeID = 0; + virtual int StartEdges() + { + MaxEdgeID = Mesh->MaxEdgeID(); + return 0; + } + + virtual int GetNextEdge(int CurEdgeID, bool& bDone) + { + int new_eid = (CurEdgeID + ModuloPrime) % MaxEdgeID; + bDone = (new_eid == 0); + return new_eid; + } + + + + + + enum class EProcessResult + { + Ok_Collapsed, + Ok_Flipped, + Ok_Split, + Ignored_EdgeIsFine, + Ignored_EdgeIsFullyConstrained, + Failed_OpNotSuccessful, + Failed_NotAnEdge + }; + + virtual EProcessResult ProcessEdge(int edgeID); + + + + // After we split an edge, we have created a new edge and a new vertex. + // The edge needs to inherit the constraint on the other pre-existing edge that we kept. + // In addition, if the edge vertices were both constrained, then we /might/ + // want to also constrain this new vertex, possibly project to constraint target. + virtual void UpdateAfterSplit(int edgeID, int va, int vb, const FDynamicMesh3::FEdgeSplitInfo& splitInfo); + + + + TDynamicVector TempPosBuffer; // this is a temporary buffer used by smoothing and projection + TArray TempFlagBuffer; // list of which indices in TempPosBuffer were modified + + virtual void InitializeVertexBufferForPass(); + + virtual void ApplyVertexBuffer(bool bParallel); + + + + + virtual void FullSmoothPass_Buffer(bool bParallel); + + /** computes smoothed vertex position w/ proper constraints/etc. Does not modify mesh. */ + virtual FVector3d ComputeSmoothedVertexPos(int VertexID, + TFunction SmoothFunc, bool& bModified); + + virtual void ApplyToSmoothVertices(const TFunction& VertexSmoothFunc); + + + + + // Project vertices onto projection target. + virtual void FullProjectionPass(); + + virtual void ProjectVertex(int VertexID, IProjectionTarget* UseTarget); + + // used by collapse-edge to get projected position for new vertex + virtual FVector3d GetProjectedCollapsePosition(int VertexID, const FVector3d& vNewPos); + + virtual void ApplyToProjectVertices(const TFunction& VertexProjectFunc); + + + /* + * testing/debug/profiling stuff + */ +protected: + + // + // profiling functions, turn on ENABLE_PROFILING to see output in console + // + int COUNT_SPLITS, COUNT_COLLAPSES, COUNT_FLIPS; + //Stopwatch AllOpsW, SmoothW, ProjectW, FlipW, SplitW, CollapseW; + + virtual void ProfileBeginPass() + { + if (ENABLE_PROFILING) + { + COUNT_SPLITS = COUNT_COLLAPSES = COUNT_FLIPS = 0; + //AllOpsW = new Stopwatch(); + //SmoothW = new Stopwatch(); + //ProjectW = new Stopwatch(); + //FlipW = new Stopwatch(); + //SplitW = new Stopwatch(); + //CollapseW = new Stopwatch(); + } + } + + virtual void ProfileEndPass() + { + if (ENABLE_PROFILING) { + //System.Console.WriteLine(string.Format( + // "RemeshPass: T {0} V {1} splits {2} flips {3} collapses {4}", mesh->TriangleCount, mesh->VertexCount, COUNT_SPLITS, COUNT_FLIPS, COUNT_COLLAPSES + // )); + //System.Console.WriteLine(string.Format( + // " Timing1: ops {0} smooth {1} project {2}", Util.ToSecMilli(AllOpsW.Elapsed), Util.ToSecMilli(SmoothW.Elapsed), Util.ToSecMilli(ProjectW.Elapsed) + // )); + //System.Console.WriteLine(string.Format( + // " Timing2: collapse {0} flip {1} split {2}", Util.ToSecMilli(CollapseW.Elapsed), Util.ToSecMilli(FlipW.Elapsed), Util.ToSecMilli(SplitW.Elapsed) + // )); + } + } + + virtual void ProfileBeginOps() + { + //if ( ENABLE_PROFILING ) AllOpsW.Start(); + } + virtual void ProfileEndOps() + { + //if ( ENABLE_PROFILING ) AllOpsW.Stop(); + } + virtual void ProfileBeginSmooth() + { + //if ( ENABLE_PROFILING ) SmoothW.Start(); + } + virtual void ProfileEndSmooth() + { + //if ( ENABLE_PROFILING ) SmoothW.Stop(); + } + virtual void ProfileBeginProject() + { + //if ( ENABLE_PROFILING ) ProjectW.Start(); + } + virtual void ProfileEndProject() + { + //if ( ENABLE_PROFILING ) ProjectW.Stop(); + } + + virtual void ProfileBeginCollapse() + { + //if ( ENABLE_PROFILING ) CollapseW.Start(); + } + virtual void ProfileEndCollapse() + { + //if ( ENABLE_PROFILING ) CollapseW.Stop(); + } + virtual void ProfileBeginFlip() + { + //if ( ENABLE_PROFILING ) FlipW.Start(); + } + virtual void ProfileEndFlip() + { + //if ( ENABLE_PROFILING ) FlipW.Stop(); + } + virtual void ProfileBeginSplit() + { + //if ( ENABLE_PROFILING ) SplitW.Start(); + } + virtual void ProfileEndSplit() + { + //if ( ENABLE_PROFILING ) SplitW.Stop(); + } + +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/SubRegionRemesher.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/SubRegionRemesher.h new file mode 100644 index 000000000000..5b71e3f43983 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/SubRegionRemesher.h @@ -0,0 +1,96 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Remesher.h" +#include "Util/BufferUtil.h" + +/** + * FSubRegionRemesher is an extension + */ +class FSubRegionRemesher : public FRemesher +{ +protected: + // temp buffer of edges connected to VertexROI + TSet EdgeROI; + // list of edges to consider during current remesh pass (not updated during pass) + TArray Edges; + // current edge we are at in StartEdges/GetNextEdge iteration + int CurEdge; + +public: + FSubRegionRemesher(FDynamicMesh3* Mesh) : FRemesher(Mesh) + { + VertexControlF = [this](int vid) { + return this->VertexFilter(vid); + }; + } + + /** + * Set of vertices in ROI. You add vertices here initially, then + * we will update the list during each Remesh pass + */ + TSet VertexROI; + + + /** + * Must call UpdateROI before each remesh pass. + */ + void UpdateROI() + { + EdgeROI.Reset(); + for (int VertIdx : VertexROI) + { + for (int eid : GetMesh()->VtxEdgesItr(VertIdx)) + { + EdgeROI.Add(eid); + } + } + Edges.Reset(); + BufferUtil::AppendElements(Edges, EdgeROI); + //Edges = std::vector(EdgeROI.begin(), EdgeROI.end()); + } + + + + // + // specialization of Remesher functionality + // +protected: + virtual int StartEdges() override + { + CurEdge = 0; + return (Edges.Num() > 0) ? Edges[CurEdge] : IndexConstants::InvalidID; + } + + virtual int GetNextEdge(int CurEdgeID, bool& bDone) override + { + CurEdge++; + if (CurEdge >= Edges.Num()) + { + bDone = true; + return -1; + } + else + { + bDone = false; + return Edges[CurEdge]; + } + } + + virtual void OnEdgeSplit(int EdgeID, int VertexA, int VertexB, const FDynamicMesh3::FEdgeSplitInfo& SplitInfo) override + { + VertexROI.Add(SplitInfo.NewVertex); + } + + virtual void OnEdgeCollapse(int EdgeID, int VertexA, int VertexB, const FDynamicMesh3::FEdgeCollapseInfo& CollapseInfo) override + { + VertexROI.Remove(CollapseInfo.RemovedVertex); + } + + EVertexControl VertexFilter(int VertexID) + { + return (VertexROI.Contains(VertexID) == false) ? EVertexControl::NoMovement : EVertexControl::AllowAll; + } + +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/GeometricObjects.Build.cs b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/GeometricObjects.Build.cs new file mode 100644 index 000000000000..08c95e93e1df --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/GeometricObjects.Build.cs @@ -0,0 +1,17 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class GeometricObjects : ModuleRules +{ + public GeometricObjects(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange( + new string[] { + "Core" + } + ); + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/CompGeom/PolygonTriangulation.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/CompGeom/PolygonTriangulation.cpp new file mode 100644 index 000000000000..9ca402e691d5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/CompGeom/PolygonTriangulation.cpp @@ -0,0 +1,157 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "CompGeom/PolygonTriangulation.h" +#include "TriangleTypes.h" + + +// explicit instantiations +namespace PolygonTriangulation +{ + template GEOMETRICOBJECTS_API void TriangulateSimplePolygon(const TArray>& VertexPositions, TArray& OutTriangles); + template GEOMETRICOBJECTS_API void TriangulateSimplePolygon(const TArray>& VertexPositions, TArray& OutTriangles); +} + + + + +// +// Triangulate using ear clipping +// This is based on the 3D triangulation code from MeshDescription.cpp, simplified for 2D polygons +// +template +void PolygonTriangulation::TriangulateSimplePolygon(const TArray>& VertexPositions, TArray& OutTriangles) +{ + struct Local + { + static inline bool IsTriangleFlipped(T OrientationSign, const FVector2& VertexPositionA, const FVector2& VertexPositionB, const FVector2& VertexPositionC) + { + T TriSignedArea = TTriangle2::SignedArea(VertexPositionA, VertexPositionB, VertexPositionC); + return TriSignedArea * OrientationSign < 0; + } + }; + + // Polygon must have at least three vertices/edges + int32 PolygonVertexCount = VertexPositions.Num(); + check(PolygonVertexCount >= 3); + + + // compute signed area of polygon + double PolySignedArea = 0; + for (int i = 0; i < PolygonVertexCount; ++i) + { + const FVector2& v1 = VertexPositions[i]; + const FVector2& v2 = VertexPositions[(i + 1) % PolygonVertexCount]; + PolySignedArea += v1.X*v2.Y - v1.Y*v2.X; + } + PolySignedArea *= 0.5; + bool bIsClockwise = PolySignedArea < 0; + double OrientationSign = (bIsClockwise) ? -1.0 : 1.0; + + + OutTriangles.Reset(); + + + // If perimeter has 3 vertices, just copy content of perimeter out + if (PolygonVertexCount == 3) + { + OutTriangles.Add(FIndex3i(0, 1, 2)); + return; + } + + // Make a simple linked list array of the previous and next vertex numbers, for each vertex number + // in the polygon. This will just save us having to iterate later on. + static TArray PrevVertexNumbers, NextVertexNumbers; + + PrevVertexNumbers.SetNumUninitialized(PolygonVertexCount, false); + NextVertexNumbers.SetNumUninitialized(PolygonVertexCount, false); + + for (int32 VertexNumber = 0; VertexNumber < PolygonVertexCount; ++VertexNumber) + { + PrevVertexNumbers[VertexNumber] = VertexNumber - 1; + NextVertexNumbers[VertexNumber] = VertexNumber + 1; + } + PrevVertexNumbers[0] = PolygonVertexCount - 1; + NextVertexNumbers[PolygonVertexCount - 1] = 0; + + + int32 EarVertexNumber = 0; + int32 EarTestCount = 0; + for (int32 RemainingVertexCount = PolygonVertexCount; RemainingVertexCount >= 3; ) + { + bool bIsEar = true; + + // If we're down to only a triangle, just treat it as an ear. Also, if we've tried every possible candidate + // vertex looking for an ear, go ahead and just treat the current vertex as an ear. This can happen when + // vertices are collinear or other degenerate cases. + if (RemainingVertexCount > 3 && EarTestCount < RemainingVertexCount) + { + const FVector2& PrevVertexPosition = VertexPositions[PrevVertexNumbers[EarVertexNumber]]; + const FVector2& EarVertexPosition = VertexPositions[EarVertexNumber]; + const FVector2& NextVertexPosition = VertexPositions[NextVertexNumbers[EarVertexNumber]]; + + // Figure out whether the potential ear triangle is facing the same direction as the polygon + // itself. If it's facing the opposite direction, then we're dealing with a concave triangle + // and we'll skip it for now. + if (!Local::IsTriangleFlipped( + OrientationSign, PrevVertexPosition, EarVertexPosition, NextVertexPosition)) + { + int32 TestVertexNumber = NextVertexNumbers[NextVertexNumbers[EarVertexNumber]]; + + do + { + // Test every other remaining vertex to make sure that it doesn't lie inside our potential ear + // triangle. If we find a vertex that's inside the triangle, then it cannot actually be an ear. + const FVector2& TestVertexPosition = VertexPositions[TestVertexNumber]; + if (TTriangle2::IsInside(PrevVertexPosition, EarVertexPosition, NextVertexPosition, TestVertexPosition)) + { + bIsEar = false; + break; + } + + TestVertexNumber = NextVertexNumbers[TestVertexNumber]; + } while (TestVertexNumber != PrevVertexNumbers[EarVertexNumber]); + } + else + { + bIsEar = false; + } + } + + if (bIsEar) + { + // OK, we found an ear! Let's save this triangle in our output buffer. + { + OutTriangles.Emplace(); + FIndex3i& Triangle = OutTriangles.Last(); + Triangle.A = PrevVertexNumbers[EarVertexNumber]; + Triangle.B = EarVertexNumber; + Triangle.C = NextVertexNumbers[EarVertexNumber]; + } + + // Update our linked list. We're effectively cutting off the ear by pointing the ear vertex's neighbors to + // point at their next sequential neighbor, and reducing the remaining vertex count by one. + { + NextVertexNumbers[PrevVertexNumbers[EarVertexNumber]] = NextVertexNumbers[EarVertexNumber]; + PrevVertexNumbers[NextVertexNumbers[EarVertexNumber]] = PrevVertexNumbers[EarVertexNumber]; + --RemainingVertexCount; + } + + // Move on to the previous vertex in the list, now that this vertex was cut + EarVertexNumber = PrevVertexNumbers[EarVertexNumber]; + + EarTestCount = 0; + } + else + { + // The vertex is not the ear vertex, because it formed a triangle that either had a normal which pointed in the opposite direction + // of the polygon, or at least one of the other polygon vertices was found to be inside the triangle. Move on to the next vertex. + EarVertexNumber = NextVertexNumbers[EarVertexNumber]; + + // Keep track of how many ear vertices we've tested, so that if we exhaust all remaining vertices, we can + // fall back to clipping the triangle and adding it to our mesh anyway. This is important for degenerate cases. + ++EarTestCount; + } + } + + check(OutTriangles.Num() > 0); +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/GeometricObjectsModule.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/GeometricObjectsModule.cpp new file mode 100644 index 000000000000..ab24447d7439 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/GeometricObjectsModule.cpp @@ -0,0 +1,20 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "GeometricObjectsModule.h" + +#define LOCTEXT_NAMESPACE "FGeometricObjectsModule" + +void FGeometricObjectsModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FGeometricObjectsModule::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(FGeometricObjectsModule, GeometricObjects) \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/IndexUtil.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/IndexUtil.cpp new file mode 100644 index 000000000000..c795f44ad45e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/IndexUtil.cpp @@ -0,0 +1,57 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Util/IndexUtil.h" + + +// integer indices offsets in x/y/z directions and diagonals +const FVector3i IndexUtil::GridOffsets26[] = +{ + // face-nbrs + FVector3i(0, 0,-1), FVector3i(0, 0, 1), + FVector3i(-1, 0, 0), FVector3i(1, 0, 0), + FVector3i(0,-1, 0), FVector3i(0, 1, 0), + // edge-nbrs (+y, 0, -y) + FVector3i(1, 1, 0), FVector3i(-1, 1, 0), + FVector3i(0, 1, 1), FVector3i(0, 1,-1), + FVector3i(1, 0, 1), FVector3i(-1, 0, 1), + FVector3i(1, 0,-1), FVector3i(-1, 0,-1), + FVector3i(1, -1, 0), FVector3i(-1,-1, 0), + FVector3i(0, -1, 1), FVector3i(0,-1,-1), + // corner-nbrs (+y,-y) + FVector3i(1, 1, 1), FVector3i(-1, 1, 1), + FVector3i(1, 1,-1), FVector3i(-1, 1,-1), + FVector3i(1,-1, 1), FVector3i(-1,-1, 1), + FVector3i(1,-1,-1), FVector3i(-1,-1,-1) +}; + + + + +// corners [ (-x,-y), (x,-y), (x,y), (-x,y) ], -z, then +z +// +// 7---6 +z or 3---2 -z +// |\ |\ |\ |\ +// 4-\-5 \ 0-\-1 \ +// \ 3---2 \ 7---6 +// \| | \| | +// 0---1 -z 4---5 +z +// + +const int IndexUtil::BoxFaces[6][4] = +{ + { 0, 1, 2, 3 }, // back, -z + { 5, 4, 7, 6 }, // front, +z + { 4, 0, 3, 7 }, // left, -x + { 1, 5, 6, 2 }, // right, +x, + { 4, 5, 1, 0 }, // bottom, -y + { 3, 2, 6, 7 } // top, +y +}; + + +static const FVector2i UV00(0, 0); +static const FVector2i UV10(1, 0); +static const FVector2i UV11(1, 1); +static const FVector2i UV01(0, 1); +const FVector2i IndexUtil::BoxFacesUV[4] = { UV00, UV10, UV11, UV01 }; + +const int IndexUtil::BoxFaceNormals[6] = { -3, 3, -1, 1, -2, 2 }; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/MathUtil.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/MathUtil.cpp new file mode 100644 index 000000000000..6b25e564ef5b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/MathUtil.cpp @@ -0,0 +1,50 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MathUtil.h" + +#include +#include + +#include "Math/UnrealMath.h" + +template<> GEOMETRICOBJECTS_API const float TMathUtil::Epsilon = FLT_EPSILON; +template<> GEOMETRICOBJECTS_API const float TMathUtil::ZeroTolerance = 1e-06f; +template<> GEOMETRICOBJECTS_API const float TMathUtil::MaxReal = FLT_MAX; +template<> GEOMETRICOBJECTS_API const float TMathUtil::Pi = (float)(4.0*atan(1.0)); +template<> GEOMETRICOBJECTS_API const float TMathUtil::FourPi = 4.0f*TMathUtil::Pi; +template<> GEOMETRICOBJECTS_API const float TMathUtil::TwoPi = 2.0f*TMathUtil::Pi; +template<> GEOMETRICOBJECTS_API const float TMathUtil::HalfPi = 0.5f*TMathUtil::Pi; +template<> GEOMETRICOBJECTS_API const float TMathUtil::InvPi = 1.0f / TMathUtil::Pi; +template<> GEOMETRICOBJECTS_API const float TMathUtil::InvTwoPi = 1.0f / TMathUtil::TwoPi; +template<> GEOMETRICOBJECTS_API const float TMathUtil::DegToRad = TMathUtil::Pi / 180.0f; +template<> GEOMETRICOBJECTS_API const float TMathUtil::RadToDeg = 180.0f / TMathUtil::Pi; +//template<> const float TMathUtil::LN_2 = TMathUtil::Log(2.0f); +//template<> const float TMathUtil::LN_10 = TMathUtil::Log(10.0f); +//template<> const float TMathUtil::INV_LN_2 = 1.0f / TMathUtil::LN_2; +//template<> const float TMathUtil::INV_LN_10 = 1.0f / TMathUtil::LN_10; +template<> GEOMETRICOBJECTS_API const float TMathUtil::Sqrt2 = (float)(sqrt(2.0)); +template<> GEOMETRICOBJECTS_API const float TMathUtil::InvSqrt2 = 1.0f / TMathUtil::Sqrt2; +template<> GEOMETRICOBJECTS_API const float TMathUtil::Sqrt3 = (float)(sqrt(3.0)); +template<> GEOMETRICOBJECTS_API const float TMathUtil::InvSqrt3 = 1.0f / TMathUtil::Sqrt3; + +template<> GEOMETRICOBJECTS_API const double TMathUtil::Epsilon = DBL_EPSILON; +template<> GEOMETRICOBJECTS_API const double TMathUtil::ZeroTolerance = 1e-08; +template<> GEOMETRICOBJECTS_API const double TMathUtil::MaxReal = DBL_MAX; +template<> GEOMETRICOBJECTS_API const double TMathUtil::Pi = 4.0*atan(1.0); +template<> GEOMETRICOBJECTS_API const double TMathUtil::FourPi = 4.0*TMathUtil::Pi; +template<> GEOMETRICOBJECTS_API const double TMathUtil::TwoPi = 2.0*TMathUtil::Pi; +template<> GEOMETRICOBJECTS_API const double TMathUtil::HalfPi = 0.5*TMathUtil::Pi; +template<> GEOMETRICOBJECTS_API const double TMathUtil::InvPi = 1.0 / TMathUtil::Pi; +template<> GEOMETRICOBJECTS_API const double TMathUtil::InvTwoPi = 1.0 / TMathUtil::TwoPi; +template<> GEOMETRICOBJECTS_API const double TMathUtil::DegToRad = TMathUtil::Pi / 180.0; +template<> GEOMETRICOBJECTS_API const double TMathUtil::RadToDeg = 180.0 / TMathUtil::Pi; +//template<> const double TMathUtil::LN_2 = TMathUtil::Log(2.0); +//template<> const double TMathUtil::LN_10 = TMathUtil::Log(10.0); +//template<> const double TMathUtil::INV_LN_2 = 1.0 / TMathUtil::LN_2; +//template<> const double TMathUtil::INV_LN_10 = 1.0 / TMathUtil::LN_10; +template<> GEOMETRICOBJECTS_API const double TMathUtil::Sqrt2 = sqrt(2.0); +template<> GEOMETRICOBJECTS_API const double TMathUtil::InvSqrt2 = 1.0f / TMathUtil::Sqrt2; +template<> GEOMETRICOBJECTS_API const double TMathUtil::Sqrt3 = sqrt(3.0); +template<> GEOMETRICOBJECTS_API const double TMathUtil::InvSqrt3 = 1.0f / TMathUtil::Sqrt3; + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Spatial/GeometrySet3.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Spatial/GeometrySet3.cpp new file mode 100644 index 000000000000..0c462f39dcfd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Spatial/GeometrySet3.cpp @@ -0,0 +1,169 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Spatial/GeometrySet3.h" +#include "Distance/DistRay3Segment3.h" +#include "Async/ParallelFor.h" + + +void FGeometrySet3::Reset(bool bPoints, bool bCurves) +{ + if (bPoints) + { + Points.Reset(); + } + if (bCurves) + { + Curves.Reset(); + } +} + + + +void FGeometrySet3::AddPoint(int PointID, const FVector3d& Point) +{ + check(PointIDToIndex.Contains(PointID) == false); + FPoint NewPoint = { PointID, Point }; + int NewIndex = Points.Add(NewPoint); + PointIDToIndex.Add(PointID, NewIndex); +} + +void FGeometrySet3::AddCurve(int CurveID, const FPolyline3d& Polyline) +{ + check(CurveIDToIndex.Contains(CurveID) == false); + int NewIndex = Curves.Add(FCurve()); + FCurve& NewCurve = Curves[NewIndex]; + NewCurve.ID = CurveID; + NewCurve.Geometry = Polyline; + NewCurve.Bounds = Polyline.GetBounds(); + CurveIDToIndex.Add(CurveID, NewIndex); +} + + +void FGeometrySet3::UpdatePoint(int PointID, const FVector3d& Point) +{ + const int* Index = PointIDToIndex.Find(PointID); + check(Index != nullptr); + Points[*Index].Position = Point; +} + +void FGeometrySet3::UpdateCurve(int CurveID, const FPolyline3d& Polyline) +{ + const int* Index = CurveIDToIndex.Find(CurveID); + check(Index != nullptr); + Curves[*Index].Geometry.SetVertices(Polyline.GetVertices()); + Curves[*Index].Bounds = Polyline.GetBounds(); +} + + + +bool FGeometrySet3::FindNearestPointToRay(const FRay3d& Ray, FNearest& ResultOut, + TFunction PointWithinToleranceTest) const +{ + double MinRayParamT = TNumericLimits::Max(); + int NearestID = -1; + int NearestIndex = -1; + + FCriticalSection Critical; + + int NumPoints = Points.Num(); + ParallelFor(NumPoints, [&](int pi) + { + const FPoint& Point = Points[pi]; + double RayT = Ray.Project(Point.Position); + FVector3d RayPt = Ray.NearestPoint(Point.Position); + if (PointWithinToleranceTest(RayPt, Point.Position)) + { + Critical.Lock(); + if (RayT < MinRayParamT) + { + MinRayParamT = RayT; + NearestIndex = pi; + NearestID = Points[pi].ID; + } + Critical.Unlock(); + } + }); + + if (NearestID != -1) + { + ResultOut.ID = NearestID; + ResultOut.bIsPoint = true; + ResultOut.NearestRayPoint = Ray.PointAt(MinRayParamT); + ResultOut.NearestGeoPoint = Points[NearestIndex].Position; + ResultOut.RayParam = MinRayParamT; + } + + return NearestID != -1; +} + + + + + +bool FGeometrySet3::FindNearestCurveToRay(const FRay3d& Ray, FNearest& ResultOut, + TFunction PointWithinToleranceTest) const +{ + double MinRayParamT = TNumericLimits::Max(); + int NearestID = -1; + int NearestIndex = -1; + int NearestSegmentIdx = -1; + double NearestSegmentParam = 0; + + FCriticalSection Critical; + + int NumCurves = Curves.Num(); + ParallelFor(NumCurves, [&](int ci) + { + const FCurve& Curve = Curves[ci]; + const FPolyline3d& Polyline = Curve.Geometry; + int NumSegments = Polyline.SegmentCount(); + + double CurveMinRayParamT = TNumericLimits::Max(); + int CurveNearestSegmentIdx = -1; + double CurveNearestSegmentParam = 0; + for (int si = 0; si < NumSegments; ++si) + { + double SegRayParam; double SegSegParam; + double SegDistSqr = FDistRay3Segment3d::SquaredDistance(Ray, Polyline.GetSegment(si), SegRayParam, SegSegParam); + if (SegRayParam < CurveMinRayParamT) + { + FVector3d RayPosition = Ray.PointAt(SegRayParam); + FVector3d CurvePosition = Polyline.GetSegmentPoint(si, SegSegParam); + if (PointWithinToleranceTest(RayPosition, CurvePosition)) + { + CurveMinRayParamT = SegRayParam; + CurveNearestSegmentIdx = si; + CurveNearestSegmentParam = SegSegParam; + } + } + } + + // if we found a possible point, lock outer structures and merge in + if (CurveMinRayParamT < TNumericLimits::Max()) + { + Critical.Lock(); + if (CurveMinRayParamT < MinRayParamT) + { + MinRayParamT = CurveMinRayParamT; + NearestIndex = ci; + NearestID = Curve.ID; + NearestSegmentIdx = CurveNearestSegmentIdx; + NearestSegmentParam = CurveNearestSegmentParam; + } + Critical.Unlock(); + } + }); // end ParallelFor + + if (NearestID != -1) + { + ResultOut.ID = NearestID; + ResultOut.bIsPoint = false; + ResultOut.NearestRayPoint = Ray.PointAt(MinRayParamT); + ResultOut.NearestGeoPoint = Curves[NearestIndex].Geometry.GetSegmentPoint(NearestSegmentIdx, NearestSegmentParam); + ResultOut.RayParam = MinRayParamT; + ResultOut.PolySegmentIdx = NearestSegmentIdx; + ResultOut.PolySegmentParam = NearestSegmentParam; + } + + return NearestID != -1; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Spatial/PointSetHashTable.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Spatial/PointSetHashTable.cpp new file mode 100644 index 000000000000..a2bc5a263e89 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Spatial/PointSetHashTable.cpp @@ -0,0 +1,88 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Spatial/PointSetHashTable.h" +#include "Util/IndexUtil.h" + +void FPointSetHashtable::Build(double CellSizeIn, const FVector3d& OriginIn) +{ + Origin = OriginIn; + CellSize = CellSizeIn; + GridIndexer = FShiftGridIndexer3d(Origin, CellSize); + + Grid = TSparseGrid3(); + + // insert all points into cell lists + int MaxID = Points->MaxPointID(); + for (int i = 0; i < MaxID; ++i) + { + if (Points->IsPoint(i)) + { + FVector3d Pt = Points->GetPoint(i); + FVector3i Idx = GridIndexer.ToGrid(Pt); + PointList* CellList = Grid.Get(Idx); + CellList->Add(i); + } + } +} + + +bool FPointSetHashtable::FindPointsInBall(const FVector3d& QueryPt, double QueryRadius, TArray& ResultOut) +{ + double HalfCellSize = CellSize * 0.5; + FVector3i QueryCellIdx = GridIndexer.ToGrid(QueryPt); + FVector3d QueryCellCenter = GridIndexer.FromGrid(QueryCellIdx) + HalfCellSize * FVector3d::One(); + + // currently large radius is unsupported... + // @todo support large radius! + // @todo we could alternately clamp this value + check(QueryRadius <= CellSize); + double RadiusSqr = QueryRadius * QueryRadius; + + // check all in this cell + PointList* CenterCellList = Grid.Get(QueryCellIdx, false); + if (CenterCellList != nullptr) + { + for (int vid : *CenterCellList) + { + if (QueryPt.DistanceSquared(Points->GetPoint(vid)) < RadiusSqr) + { + ResultOut.Add(vid); + } + } + } + + // if we are close enough to cell border we need to check nbrs + // [TODO] could iterate over fewer cells here, if r is bounded by CellSize, + // then we should only ever need to look at 3, depending on which octant we are in. + if ((QueryPt - QueryCellCenter).MaxAbs() + QueryRadius > HalfCellSize) + { + for (int ci = 0; ci < 26; ++ci) + { + FVector3i NbrOffset = IndexUtil::GridOffsets26[ci]; + + // if we are within r from face, we need to look into it + FVector3d PtToFaceCenter( + QueryCellCenter.X + HalfCellSize*NbrOffset.X - QueryPt.X, + QueryCellCenter.Y + HalfCellSize*NbrOffset.Y - QueryPt.Y, + QueryCellCenter.Z + HalfCellSize*NbrOffset.Z - QueryPt.Z); + if (PtToFaceCenter.MinAbs() > QueryRadius) + { + continue; + } + + PointList* NbrCellList = Grid.Get(QueryCellIdx + NbrOffset, false); + if (NbrCellList != nullptr) + { + for (int vid : *NbrCellList) + { + if (QueryPt.DistanceSquared(Points->GetPoint(vid)) < RadiusSqr) + { + ResultOut.Add(vid); + } + } + } + } + } + + return true; +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Tests/VectorTypes.spec.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Tests/VectorTypes.spec.cpp new file mode 100644 index 000000000000..9f8a9c1ef99e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Tests/VectorTypes.spec.cpp @@ -0,0 +1,192 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Misc/AutomationTest.h" +#include "VectorTypes.h" + +#if WITH_DEV_AUTOMATION_TESTS + +BEGIN_DEFINE_SPEC(FVectorTypesSpec, + "GeometryProcessing.Unit", + EAutomationTestFlags::ProductFilter | EAutomationTestFlags::ApplicationContextMask) +END_DEFINE_SPEC(FVectorTypesSpec) + +void FVectorTypesSpec::Define() +{ + using Vec = FVector3; + Describe("FVector3", [this]() + { + Describe("Constructors", [this]() + { + constexpr float initialValues[3]{1.f, 2.f, 3.f}; + It("FVector3()", [this]() + { + Vec v{}; + }); + It("FVector3(float, float, float)", [this, initialValues]() + { + Vec v{initialValues[0], initialValues[1], initialValues[2]}; + TestEqual("v.X", v.X, initialValues[0]); + TestEqual("v.Y", v.Y, initialValues[1]); + TestEqual("v.Z", v.Z, initialValues[2]); + }); + It("FVector3(const float*)", [this, initialValues]() + { + Vec v{initialValues}; + TestEqual("v.X", v.X, initialValues[0]); + TestEqual("v.Y", v.Y, initialValues[1]); + TestEqual("v.Z", v.Z, initialValues[2]); + }); + It("FVector3(const FVector&)", [this, initialValues]() + { + FVector fVector{initialValues[0], initialValues[1], + initialValues[2]}; + Vec fVector3{fVector}; + TestEqual("v.X", fVector.X, fVector3.X); + TestEqual("v.Y", fVector.Y, fVector3.Y); + TestEqual("v.Z", fVector.Z, fVector3.Z); + }); + It("FVector3(const FLinearColor&)", [this, initialValues]() + { + FLinearColor fLinearColor{initialValues[0], initialValues[1], + initialValues[2]}; + Vec fVector3{FVector{fLinearColor}}; + TestEqual("v.X", fLinearColor.R, fVector3.X); + TestEqual("v.Y", fLinearColor.G, fVector3.Y); + TestEqual("v.Z", fLinearColor.B, fVector3.Z); + }); + }); + Describe("Conversion", [this]() + { + It("(const float*)", [this]() + { + constexpr float initialValues[3]{1.f, 2.f, 3.f}; + Vec testVec{initialValues}; + const float *converted = static_cast(testVec); + for (int index : {0, 1, 2}) { + TestEqual(FString::Printf(TEXT("Component %d"), index), + initialValues[index], converted[index]); + } + }); + It("(float*)", [this]() + { + constexpr float initialValues[3]{1.f, 2.f, 3.f}; + Vec testVec{initialValues}; + float *converted = static_cast(testVec); + for (int index : {0, 1, 2}) { + TestEqual(FString::Printf(TEXT("Component %d"), index), + initialValues[index], converted[index]); + } + for (int index : {0, 1, 2}) { + converted[index] *= 2.f; + } + TestEqual("v.X", testVec.X, 2.f * initialValues[0]); + TestEqual("v.Y", testVec.Y, 2.f * initialValues[1]); + TestEqual("v.Z", testVec.Z, 2.f * initialValues[2]); + }); + It("(FVector)", [this]() + { + constexpr float initialValues[3]{1.f, 2.f, 3.f}; + Vec testVec{initialValues}; + FVector converted = static_cast(testVec); + for (int index : {0, 1, 2}) { + TestEqual(FString::Printf(TEXT("Component %d"), index), + testVec[index], converted[index]); + } + }); + It("(FLinearColor)", [this]() + { + constexpr float initialValues[3]{0.5f, 0.5f, 0.5f}; + Vec testVec{initialValues}; + FLinearColor converted = {testVec}; + TestEqual("Color.R", testVec.X, converted.R); + TestEqual("Color.G", testVec.Y, converted.G); + TestEqual("Color.B", testVec.Z, converted.B); + TestEqual("Color.A", 1.f, converted.A); + }); + }); + Describe("Assignment Operator", [this]() + { + It("assigns every component", [this]() + { + constexpr float initialValues[3]{1.f, 2.f, 3.f}; + Vec testVec{initialValues}; + Vec testVecCopy{}; + testVecCopy = testVec; + TestEqual("v.X", testVec.X, testVecCopy.X); + TestEqual("v.Y", testVec.Y, testVecCopy.Y); + TestEqual("v.Z", testVec.Z, testVecCopy.Z); + }); + }); + Describe("Element Access", [this]() + { + It("can be const", [this]() + { + constexpr float initialValues[3]{1.f, 2.f, 3.f}; + Vec testVec{initialValues}; + for ( int i : { 0, 1, 2 } ) + { + const float& val = testVec[i]; + TestEqual(FString::Printf(TEXT("Component %d"), i), val, initialValues[i]); + } + }); + It("can be mutable", [this]() + { + constexpr float initialValues[3]{1.f, 2.f, 3.f}; + Vec testVec{initialValues}; + for ( int i : { 0, 1, 2 } ) + { + float& val = testVec[i]; + TestEqual(FString::Printf(TEXT("Component %d"), i), val, testVec[i]); + val *= 2.f; + TestEqual(FString::Printf(TEXT("Component %d"), i), testVec[i], initialValues[i] * 2.f); + } + }); + }); + Describe("Length", [this]() + { + It("of Zero vector is 0.f", [this]() + { + TestEqual("Zero.Length()", Vec::Zero().Length(), 0.f); + }); + It("of One vector is Sqrt(3.f)", [this]() + { + TestEqual("One.Length()", Vec::One().Length(), FMathf::Sqrt(3.f)); + }); + It("of Unit vectors is 1.f", [this]() + { + TestEqual("UnitX.Length()", Vec::UnitX().Length(), 1.f); + TestEqual("UnitY.Length()", Vec::UnitY().Length(), 1.f); + TestEqual("UnitZ.Length()", Vec::UnitZ().Length(), 1.f); + }); + }); + Describe("SquaredLength", [this]() + { + It("of Zero vector is 0.f", [this]() + { + TestEqual("Zero.SquaredLength()", Vec::Zero().SquaredLength(), 0.f); + }); + It("of One vector is 3.f", [this]() + { + TestEqual("One.SquaredLength()", Vec::One().SquaredLength(), 3.f); + }); + It("of Unit vectors is 1.f", [this]() + { + TestEqual("UnitX.SquaredLength()", Vec::UnitX().SquaredLength(), 1.f); + TestEqual("UnitY.SquaredLength()", Vec::UnitY().SquaredLength(), 1.f); + TestEqual("UnitZ.SquaredLength()", Vec::UnitZ().SquaredLength(), 1.f); + }); + }); + Describe("Distance", [this]() + { + It("from vector to itself is 0.f", [this]() + { + const Vec testVecs[] = {Vec::Zero(), Vec::One(), Vec::UnitX(), Vec::UnitY(), Vec::UnitZ(), Vec(1.0, 2.0, 3.0)}; + for (const auto &testVec : testVecs) + { + TestEqual("Self Distance", testVec.Distance(testVec), 0.f); + } + }); + }); + }); +} +#endif diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Util/SmallListSet.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Util/SmallListSet.cpp new file mode 100644 index 000000000000..11198a6f306e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Util/SmallListSet.cpp @@ -0,0 +1,400 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Util/SmallListSet.h" + +const int32 FSmallListSet::NullValue = -1; + + +FSmallListSet::FSmallListSet() +{ + ListHeads = TDynamicVector(); + LinkedListElements = TDynamicVector(); + FreeHeadIndex = NullValue; + ListBlocks = TDynamicVector(); + FreeBlocks = TDynamicVector(); +} + + +FSmallListSet::FSmallListSet(const FSmallListSet& copy) +{ + LinkedListElements = TDynamicVector(copy.LinkedListElements); + FreeHeadIndex = copy.FreeHeadIndex; + ListHeads = TDynamicVector(copy.ListHeads); + ListBlocks = TDynamicVector(copy.ListBlocks); + FreeBlocks = TDynamicVector(copy.FreeBlocks); +} + + +void FSmallListSet::Resize(int32 NewSize) +{ + int32 CurSize = (int32)ListHeads.GetLength(); + if (NewSize > CurSize) + { + ListHeads.Resize(NewSize); + for (int32 k = CurSize; k < NewSize; ++k) + { + ListHeads[k] = NullValue; + } + } +} + + +void FSmallListSet::AllocateAt(int32 ListIndex) +{ + check(ListIndex >= 0); + if (ListIndex >= (int)ListHeads.GetLength()) + { + int32 j = (int32)ListHeads.GetLength(); + ListHeads.InsertAt(NullValue, ListIndex); + // need to set intermediate values to null! + while (j < ListIndex) + { + ListHeads[j] = NullValue; + j++; + } + } + else + { + checkf(ListHeads[ListIndex] == NullValue, TEXT("FSmallListSet: list at %d is not empty!"), ListIndex); + } +} + + + + +void FSmallListSet::Insert(int32 ListIndex, int32 Value) +{ + check(ListIndex >= 0); + int32 block_ptr = ListHeads[ListIndex]; + if (block_ptr == NullValue) + { + block_ptr = AllocateBlock(); + ListBlocks[block_ptr] = 0; + ListHeads[ListIndex] = block_ptr; + } + + int32 N = ListBlocks[block_ptr]; + if (N < BLOCKSIZE) + { + ListBlocks[block_ptr + N + 1] = Value; + } + else + { + // spill to linked list + int32 cur_head = ListBlocks[block_ptr + BLOCK_LIST_OFFSET]; + + if (FreeHeadIndex == NullValue) + { + // allocate linkedlist node + int32 new_ptr = (int32)LinkedListElements.GetLength(); + LinkedListElements.Add(Value); + LinkedListElements.Add(cur_head); + ListBlocks[block_ptr + BLOCK_LIST_OFFSET] = new_ptr; + } + else + { + // pull from free list + int32 free_ptr = FreeHeadIndex; + FreeHeadIndex = LinkedListElements[free_ptr + 1]; + LinkedListElements[free_ptr] = Value; + LinkedListElements[free_ptr + 1] = cur_head; + ListBlocks[block_ptr + BLOCK_LIST_OFFSET] = free_ptr; + } + } + + // count element + ListBlocks[block_ptr] += 1; +} + + + + +bool FSmallListSet::Remove(int32 ListIndex, int32 Value) +{ + check(ListIndex >= 0); + int32 block_ptr = ListHeads[ListIndex]; + int32 N = ListBlocks[block_ptr]; + + + int32 iEnd = block_ptr + FMath::Min(N, BLOCKSIZE); + for (int32 i = block_ptr + 1; i <= iEnd; ++i) + { + + if (ListBlocks[i] == Value) + { + // TODO since this is a set and order doesn't matter, shouldn't you just move the last thing + // to the empty spot rather than shifting the whole list left? + for (int32 j = i + 1; j <= iEnd; ++j) // shift left + { + ListBlocks[j - 1] = ListBlocks[j]; + } + //block_store[iEnd] = -2; // OPTIONAL + + if (N > BLOCKSIZE) + { + int32 cur_ptr = ListBlocks[block_ptr + BLOCK_LIST_OFFSET]; + ListBlocks[block_ptr + BLOCK_LIST_OFFSET] = LinkedListElements[cur_ptr + 1]; // point32 to cur->next + ListBlocks[iEnd] = LinkedListElements[cur_ptr]; + AddFreeLink(cur_ptr); + } + + ListBlocks[block_ptr] -= 1; + return true; + } + + } + + // search list + if (N > BLOCKSIZE) + { + if (RemoveFromLinkedList(block_ptr, Value)) + { + ListBlocks[block_ptr] -= 1; + return true; + } + } + + return false; +} + + + + +void FSmallListSet::Move(int32 FromIndex, int32 ToIndex) +{ + check(FromIndex >= 0); + check(ToIndex >= 0); + check(ListHeads[ToIndex] == NullValue); + check(ListHeads[FromIndex] != NullValue); + ListHeads[ToIndex] = ListHeads[FromIndex]; + ListHeads[FromIndex] = NullValue; +} + + + + +void FSmallListSet::Clear(int32 ListIndex) +{ + check(ListIndex >= 0); + int32 block_ptr = ListHeads[ListIndex]; + if (block_ptr != NullValue) + { + int32 N = ListBlocks[block_ptr]; + + // if we have spilled to linked-list, free nodes + if (N > BLOCKSIZE) + { + int32 cur_ptr = ListBlocks[block_ptr + BLOCK_LIST_OFFSET]; + while (cur_ptr != NullValue) + { + int32 free_ptr = cur_ptr; + cur_ptr = LinkedListElements[cur_ptr + 1]; + AddFreeLink(free_ptr); + } + ListBlocks[block_ptr + BLOCK_LIST_OFFSET] = NullValue; + } + + // free our block + ListBlocks[block_ptr] = 0; + FreeBlocks.Add(block_ptr); + ListHeads[ListIndex] = NullValue; + } +} + + + + +bool FSmallListSet::Contains(int32 ListIndex, int32 Value) const +{ + check(ListIndex >= 0); + int32 block_ptr = ListHeads[ListIndex]; + if (block_ptr != NullValue) + { + int32 N = ListBlocks[block_ptr]; + if (N < BLOCKSIZE) + { + int32 iEnd = block_ptr + N; + for (int32 i = block_ptr + 1; i <= iEnd; ++i) + { + if (ListBlocks[i] == Value) + { + return true; + } + } + } + else + { + // we spilled to linked list, have to iterate through it as well + int32 iEnd = block_ptr + BLOCKSIZE; + for (int32 i = block_ptr + 1; i <= iEnd; ++i) + { + if (ListBlocks[i] == Value) + { + return true; + } + } + int32 cur_ptr = ListBlocks[block_ptr + BLOCK_LIST_OFFSET]; + while (cur_ptr != NullValue) + { + if (LinkedListElements[cur_ptr] == Value) + { + return true; + } + cur_ptr = LinkedListElements[cur_ptr + 1]; + } + } + } + return false; +} + + + + + + + +int32 FSmallListSet::Find(int32 ListIndex, const TFunction& PredicateFunc, int32 InvalidValue) const +{ + int32 block_ptr = ListHeads[ListIndex]; + if (block_ptr != NullValue) + { + int32 N = ListBlocks[block_ptr]; + if (N < BLOCKSIZE) + { + int32 iEnd = block_ptr + N; + for (int32 i = block_ptr + 1; i <= iEnd; ++i) + { + int32 Value = ListBlocks[i]; + if (PredicateFunc(Value)) + { + return Value; + } + } + } + else + { + // we spilled to linked list, have to iterate through it as well + int32 iEnd = block_ptr + BLOCKSIZE; + for (int32 i = block_ptr + 1; i <= iEnd; ++i) + { + int32 Value = ListBlocks[i]; + if (PredicateFunc(Value)) + { + return Value; + } + } + int32 cur_ptr = ListBlocks[block_ptr + BLOCK_LIST_OFFSET]; + while (cur_ptr != NullValue) + { + int32 Value = LinkedListElements[cur_ptr]; + if (PredicateFunc(Value)) + { + return Value; + } + cur_ptr = LinkedListElements[cur_ptr + 1]; + } + } + } + return InvalidValue; +} + + + + + + +bool FSmallListSet::Replace(int32 ListIndex, const TFunction& PredicateFunc, int32 NewValue) +{ + int32 block_ptr = ListHeads[ListIndex]; + if (block_ptr != NullValue) + { + int32 N = ListBlocks[block_ptr]; + if (N < BLOCKSIZE) + { + int32 iEnd = block_ptr + N; + for (int32 i = block_ptr + 1; i <= iEnd; ++i) + { + int32 Value = ListBlocks[i]; + if (PredicateFunc(Value)) + { + ListBlocks[i] = NewValue; + return true; + } + } + } + else + { + // we spilled to linked list, have to iterate through it as well + int32 iEnd = block_ptr + BLOCKSIZE; + for (int32 i = block_ptr + 1; i <= iEnd; ++i) + { + int32 Value = ListBlocks[i]; + if (PredicateFunc(Value)) + { + ListBlocks[i] = NewValue; + return true; + } + } + int32 cur_ptr = ListBlocks[block_ptr + BLOCK_LIST_OFFSET]; + while (cur_ptr != NullValue) + { + int32 Value = LinkedListElements[cur_ptr]; + if (PredicateFunc(Value)) + { + LinkedListElements[cur_ptr] = NewValue; + return true; + } + cur_ptr = LinkedListElements[cur_ptr + 1]; + } + } + } + return false; +} + + + + + +int32 FSmallListSet::AllocateBlock() +{ + int32 nfree = (int32)FreeBlocks.GetLength(); + if (nfree > 0) + { + int32 ptr = FreeBlocks[nfree - 1]; + FreeBlocks.PopBack(); + return ptr; + } + int32 nsize = (int32)ListBlocks.GetLength(); + ListBlocks.InsertAt(NullValue, nsize + BLOCK_LIST_OFFSET); + ListBlocks[nsize] = 0; + AllocatedCount++; + return nsize; +} + + + +bool FSmallListSet::RemoveFromLinkedList(int32 block_ptr, int32 val) +{ + int32 cur_ptr = ListBlocks[block_ptr + BLOCK_LIST_OFFSET]; + int32 prev_ptr = NullValue; + while (cur_ptr != NullValue) + { + if (LinkedListElements[cur_ptr] == val) + { + int32 next_ptr = LinkedListElements[cur_ptr + 1]; + if (prev_ptr == NullValue) + { + ListBlocks[block_ptr + BLOCK_LIST_OFFSET] = next_ptr; + } + else + { + LinkedListElements[prev_ptr + 1] = next_ptr; + } + AddFreeLink(cur_ptr); + return true; + } + prev_ptr = cur_ptr; + cur_ptr = LinkedListElements[cur_ptr + 1]; + } + return false; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/BoxTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/BoxTypes.h new file mode 100644 index 000000000000..b89114569d13 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/BoxTypes.h @@ -0,0 +1,544 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Math/Box.h" +#include "Math/Box2D.h" +#include "VectorTypes.h" + +template +struct TInterval1 +{ + RealType Min; + RealType Max; + + TInterval1() + { + } + TInterval1(const RealType& Min, const RealType& Max) + { + this->Min = Min; + this->Max = Max; + } + + static TInterval1 Empty() + { + return TInterval1(TNumericLimits::Max(), -TNumericLimits::Max()); + } + + RealType Center() const + { + return (Min + Max) * (RealType)0.5; + } + + RealType Extent() const + { + return (Max - Min)*(RealType).5; + } + RealType Length() const + { + return Max - Min; + } + + void Contain(const RealType& V) + { + if (V < Min) + { + Min = V; + } + if (V > Max) + { + Max = V; + } + } + + bool Contains(RealType D) const + { + return D >= Min && D <= Max; + } + + bool Overlaps(const TInterval1& O) const + { + return !(O.Min > Max || O.Max < Min); + } + + RealType SquaredDist(const TInterval1& O) const + { + if (Max < O.Min) + { + return (O.Min - Max) * (O.Min - Max); + } + else if (Min > O.Max) + { + return (Min - O.Max) * (Min - O.Max); + } + else + { + return 0; + } + } + RealType Dist(const TInterval1& O) const + { + if (Max < O.Min) + { + return O.Min - Max; + } + else if (Min > O.Max) + { + return Min - O.Max; + } + else + { + return 0; + } + } + + TInterval1 IntersectionWith(const TInterval1& O) const + { + if (O.Min > Max || O.Max < Min) + { + return TInterval1::Empty(); + } + return TInterval1(TMathUtil::Max(Min, O.Min), TMathUtil::Min(Max, O.Max)); + } + + /** + * clamp Value f to interval [Min,Max] + */ + RealType Clamp(RealType f) const + { + return (f < Min) ? Min : (f > Max) ? Max : f; + } + + /** + * interpolate between Min and Max using Value T in range [0,1] + */ + RealType Interpolate(RealType T) const + { + return (1 - T) * Min + (T)*Max; + } + + /** + * Convert Value into (clamped) T Value in range [0,1] + */ + RealType GetT(RealType Value) const + { + if (Value <= Min) + { + return 0; + } + else if (Value >= Max) + { + return 1; + } + else if (Min == Max) + { + return 0.5; + } + else + { + return (Value - Min) / (Max - Min); + } + } + + void Set(TInterval1 O) + { + Min = O.Min; + Max = O.Max; + } + + void Set(RealType A, RealType B) + { + Min = A; + Max = B; + } + + TInterval1 operator-(TInterval1 V) const + { + return TInterval1(-V.Min, -V.Max); + } + + TInterval1 operator+(RealType f) const + { + return TInterval1(Min + f, Max + f); + } + + TInterval1 operator-(RealType f) const + { + return TInterval1(Min - f, Max - f); + } + + TInterval1 operator*(RealType f) const + { + return TInterval1(Min * f, Max * f); + } +}; + +typedef TInterval1 FInterval1f; +typedef TInterval1 FInterval1d; +typedef TInterval1 FInterval1i; + + + +template +struct TAxisAlignedBox3 +{ + FVector3 Min; + FVector3 Max; + + TAxisAlignedBox3() + { + } + TAxisAlignedBox3(const FVector3& Min, const FVector3& Max) + { + this->Min = Min; + this->Max = Max; + } + TAxisAlignedBox3(const TAxisAlignedBox3& Box, const TFunction(const FVector3&)> TransformF) + { + if (TransformF == nullptr) + { + Min = Box.Min; + Max = Box.Max; + return; + } + + FVector3 C0 = TransformF(Box.GetCorner(0)); + Min = C0; + Max = C0; + for (int i = 1; i < 8; ++i) + { + Contain(TransformF(Box.GetCorner(i))); + } + } + + operator FBox() const + { + FVector MinV((float)Min.X, (float)Min.Y, (float)Min.Z); + FVector MaxV((float)Max.X, (float)Max.Y, (float)Max.Z); + return FBox(MinV, MaxV); + } + TAxisAlignedBox3(const FBox& Box) + { + Min = Box.Min; + Max = Box.Max; + } + + /** + * @param Index corner index in range 0-7 + * @return Corner point on the box identified by the given index. See diagram in OrientedBoxTypes.h for index/corner mapping. + */ + FVector3 GetCorner(int Index) const + { + check(Index >= 0 && Index <= 7); + double X = (((Index & 1) != 0) ^ ((Index & 2) != 0)) ? (Max.X) : (Min.X); + double Y = ((Index / 2) % 2 == 0) ? (Min.Y) : (Max.Y); + double Z = (Index < 4) ? (Min.Z) : (Max.Z); + return FVector3(X, Y, Z); + } + + static TAxisAlignedBox3 Empty() + { + return TAxisAlignedBox3( + FVector3(TNumericLimits::Max(), TNumericLimits::Max(), TNumericLimits::Max()), + FVector3(-TNumericLimits::Max(), -TNumericLimits::Max(), -TNumericLimits::Max())); + } + + FVector3 Center() const + { + return FVector3( + (Min.X + Max.X) * (RealType)0.5, + (Min.Y + Max.Y) * (RealType)0.5, + (Min.Z + Max.Z) * (RealType)0.5); + } + + FVector3 Extents() const + { + return (Max - Min) * (RealType).5; + } + + void Contain(const FVector3& V) + { + if (V.X < Min.X) + { + Min.X = V.X; + } + if (V.X > Max.X) + { + Max.X = V.X; + } + if (V.Y < Min.Y) + { + Min.Y = V.Y; + } + if (V.Y > Max.Y) + { + Max.Y = V.Y; + } + if (V.Z < Min.Z) + { + Min.Z = V.Z; + } + if (V.Z > Max.Z) + { + Max.Z = V.Z; + } + } + + void Contain(const TAxisAlignedBox3& Other) + { + // todo: can be optimized + Contain(Other.Min); + Contain(Other.Max); + } + + bool Contains(const FVector3& V) const + { + return (Min.X <= V.X) && (Min.Y <= V.Y) && (Min.Z <= V.Z) && (Max.X >= V.X) && (Max.Y >= V.Y) && (Max.Z >= V.Z); + } + + TAxisAlignedBox3 Intersect(const TAxisAlignedBox3& Box) const + { + TAxisAlignedBox3 Intersection( + FVector3(TMathUtil::Max(Min.X, Box.Min.X), TMathUtil::Max(Min.Y, Box.Min.Y), TMathUtil::Max(Min.Z, Box.Min.Z)), + FVector3(TMathUtil::Min(Max.X, Box.Max.X), TMathUtil::Min(Max.Y, Box.Max.Y), TMathUtil::Min(Max.Z, Box.Max.Z))); + if (Intersection.Height() <= 0 || Intersection.Width() <= 0 || Intersection.Depth() <= 0) + { + return TAxisAlignedBox3::Empty(); + } + else + { + return Intersection; + } + } + + bool Intersects(TAxisAlignedBox3 Box) const + { + return !((Box.Max.X <= Min.X) || (Box.Min.X >= Max.X) || (Box.Max.Y <= Min.Y) || (Box.Min.Y >= Max.Y) || (Box.Max.Z <= Min.Z) || (Box.Min.Z >= Max.Z)); + } + + RealType DistanceSquared(const FVector3& V) const + { + RealType dx = (V.X < Min.X) ? Min.X - V.X : (V.X > Max.X ? V.X - Max.X : 0); + RealType dy = (V.Y < Min.Y) ? Min.Y - V.Y : (V.Y > Max.Y ? V.Y - Max.Y : 0); + RealType dz = (V.Z < Min.Z) ? Min.Z - V.Z : (V.Z > Max.Z ? V.Z - Max.Z : 0); + return dx * dx + dy * dy + dz * dz; + } + + RealType DistanceSquared(const TAxisAlignedBox3& Box) + { + // compute lensqr( max(0, abs(center1-center2) - (extent1+extent2)) ) + RealType delta_x = TMathUtil::Abs((Box.Min.X + Box.Max.X) - (Min.X + Max.X)) + - ((Max.X - Min.X) + (Box.Max.X - Box.Min.X)); + if (delta_x < 0) + { + delta_x = 0; + } + RealType delta_y = TMathUtil::Abs((Box.Min.Y + Box.Max.Y) - (Min.Y + Max.Y)) + - ((Max.Y - Min.Y) + (Box.Max.Y - Box.Min.Y)); + if (delta_y < 0) + { + delta_y = 0; + } + RealType delta_z = TMathUtil::Abs((Box.Min.Z + Box.Max.Z) - (Min.Z + Max.Z)) + - ((Max.Z - Min.Z) + (Box.Max.Z - Box.Min.Z)); + if (delta_z < 0) + { + delta_z = 0; + } + return (RealType)0.25 * (delta_x * delta_x + delta_y * delta_y + delta_z * delta_z); + } + + RealType Width() const + { + return TMathUtil::Max(Max.X - Min.X, (RealType)0); + } + + RealType Height() const + { + return TMathUtil::Max(Max.Y - Min.Y, (RealType)0); + } + + RealType Depth() const + { + return TMathUtil::Max(Max.Z - Min.Z, (RealType)0); + } + + RealType Volume() const + { + return Width() * Height() * Depth(); + } + + RealType DiagonalLength() const + { + return TMathUtil::Sqrt((Max.X - Min.X) * (Max.X - Min.X) + (Max.Y - Min.Y) * (Max.Y - Min.Y) + (Max.Z - Min.Z) * (Max.Z - Min.Z)); + } + + RealType MaxDim() const + { + return TMathUtil::Max(Width(), TMathUtil::Max(Height(), Depth())); + } + + FVector3 Diagonal() const + { + return FVector3(Max.X - Min.X, Max.Y - Min.Y, Max.Z - Min.Z); + } +}; + +template +struct TAxisAlignedBox2 +{ + FVector2 Min; + FVector2 Max; + + TAxisAlignedBox2() + { + } + TAxisAlignedBox2(const FVector2& Min, const FVector2& Max) + : Min(Min), Max(Max) + { + } + TAxisAlignedBox2(RealType SquareSize) + : Min((RealType)0, (RealType)0), Max(SquareSize, SquareSize) + { + } + TAxisAlignedBox2(RealType Width, RealType Height) + : Min((RealType)0, (RealType)0), Max(Width, Height) + { + } + + operator FBox2D() const + { + FVector2D MinV((float)Min.X, (float)Min.Y); + FVector2D MaxV((float)Max.X, (float)Max.Y); + return FBox2D(MinV, MaxV); + } + TAxisAlignedBox2(const FBox2D& Box) + { + Min = Box.Min; + Max = Box.Max; + } + + static TAxisAlignedBox2 Empty() + { + return TAxisAlignedBox2( + FVector2(TNumericLimits::Max(), TNumericLimits::Max()), + FVector2(-TNumericLimits::Max(), -TNumericLimits::Max())); + } + + FVector2 Center() const + { + return FVector2( + (Min.X + Max.X) * (RealType)0.5, + (Min.Y + Max.Y) * (RealType)0.5); + } + + FVector2 Extents() const + { + return (Max - Min) * (RealType).5; + } + + inline void Contain(const FVector2& V) + { + if (V.X < Min.X) + { + Min.X = V.X; + } + if (V.X > Max.X) + { + Max.X = V.X; + } + if (V.Y < Min.Y) + { + Min.Y = V.Y; + } + if (V.Y > Max.Y) + { + Max.Y = V.Y; + } + } + + inline void Contain(const TAxisAlignedBox2& Other) + { + // todo: can be optimized + Contain(Other.Min); + Contain(Other.Max); + } + + void Contain(const TArray>& Pts) + { + for (const FVector2& Pt : Pts) + { + Contain(Pt); + } + } + + bool Contains(const FVector2& V) const + { + return (Min.X <= V.X) && (Min.Y <= V.Y) && (Max.X >= V.X) && (Max.Y >= V.Y); + } + + bool Intersects(const TAxisAlignedBox2& Box) const + { + return !((Box.Max.X < Min.X) || (Box.Min.X > Max.X) || (Box.Max.Y < Min.Y) || (Box.Min.Y > Max.Y)); + } + + TAxisAlignedBox2 Intersect(const TAxisAlignedBox2 &Box) const + { + TAxisAlignedBox2 Intersection( + FVector2(TMathUtil::Max(Min.X, Box.Min.X), TMathUtil::Max(Min.Y, Box.Min.Y)), + FVector2(TMathUtil::Min(Max.X, Box.Max.X), TMathUtil::Min(Max.Y, Box.Max.Y))); + if (Intersection.Height() <= 0 || Intersection.Width() <= 0) + { + return TAxisAlignedBox2::Empty(); + } + else + { + return Intersection; + } + } + + RealType DistanceSquared(const FVector2& V) const + { + RealType dx = (V.X < Min.X) ? Min.X - V.X : (V.X > Max.X ? V.X - Max.X : 0); + RealType dy = (V.Y < Min.Y) ? Min.Y - V.Y : (V.Y > Max.Y ? V.Y - Max.Y : 0); + return dx * dx + dy * dy; + } + + inline RealType Width() const + { + return TMathUtil::Max(Max.X - Min.X, (RealType)0); + } + + inline RealType Height() const + { + return TMathUtil::Max(Max.Y - Min.Y, (RealType)0); + } + + inline RealType Area() const + { + return Width() * Height(); + } + + RealType DiagonalLength() const + { + return (RealType)TMathUtil::Sqrt((Max.X - Min.X) * (Max.X - Min.X) + (Max.Y - Min.Y) * (Max.Y - Min.Y)); + } + + inline RealType MaxDim() const + { + return TMathUtil::Max(Width(), Height()); + } + + inline RealType MinDim() const + { + return TMathUtil::Min(Width(), Height()); + } +}; + +typedef TAxisAlignedBox2 FAxisAlignedBox2f; +typedef TAxisAlignedBox2 FAxisAlignedBox2d; +typedef TAxisAlignedBox2 FAxisAlignedBox2i; +typedef TAxisAlignedBox3 FAxisAlignedBox3f; +typedef TAxisAlignedBox3 FAxisAlignedBox3d; +typedef TAxisAlignedBox3 FAxisAlignedBox3i; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/CompGeom/PolygonTriangulation.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/CompGeom/PolygonTriangulation.h new file mode 100644 index 000000000000..f5fba9188a7a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/CompGeom/PolygonTriangulation.h @@ -0,0 +1,19 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "VectorTypes.h" +#include "IndexTypes.h" + +namespace PolygonTriangulation +{ + + /** + * Compute triangulation of simple polygon using ear-clipping + * @param VertexPositions ordered vertices of polygon + * @param OutTriangles computed triangulation. Each triangle is a tuple of indices into VertexPositions. + */ + template + void GEOMETRICOBJECTS_API TriangulateSimplePolygon(const TArray>& VertexPositions, TArray& OutTriangles); + +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Curve/DynamicGraph.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Curve/DynamicGraph.h new file mode 100644 index 000000000000..9b624afadd3b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Curve/DynamicGraph.h @@ -0,0 +1,640 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "CoreTypes.h" + +#include "VectorTypes.h" +#include "IndexTypes.h" +#include "BoxTypes.h" +#include "GeometryTypes.h" +#include "Util/DynamicVector.h" +#include "Util/IndexUtil.h" +#include "Util/IteratorUtil.h" +#include "Util/RefCountVector.h" +#include "Util/SmallListSet.h" +#include "VectorUtil.h" + +#pragma once + +class FDynamicGraph +{ +public: + static constexpr int InvalidID = IndexConstants::InvalidID; // -1; + static constexpr int DuplicateEdgeID = -2; + + struct FEdge + { + int A, B, Group; + }; + + static FIndex2i InvalidEdgeV() + { + return FIndex2i(InvalidID, InvalidID); + } + static FEdge InvalidEdge3() + { + return FEdge{InvalidID, InvalidID, InvalidID}; + } + +protected: + FRefCountVector vertices_refcount; + + FSmallListSet vertex_edges; + + FRefCountVector edges_refcount; + TDynamicVector edges; // each edge is a tuple (v0,v0,GroupID) + + int timestamp = 0; + int shape_timestamp = 0; + + int max_group_id = 0; + +public: + FDynamicGraph() + : max_group_id(0) + { + } + + virtual ~FDynamicGraph() + { + } + +protected: + void updateTimeStamp(bool bShapeChange) + { + timestamp++; + if (bShapeChange) + { + shape_timestamp++; + } + } + +public: + int Timestamp() const + { + return timestamp; + } + int ShapeTimestamp() const + { + return shape_timestamp; + } + + int VertexCount() const + { + return vertices_refcount.GetCount(); + } + int EdgeCount() const + { + return edges_refcount.GetCount(); + } + + // these values are (max_used+1), ie so an iteration should be < MaxVertexID, not <= + int MaxVertexID() const + { + return vertices_refcount.GetMaxIndex(); + } + int MaxEdgeID() const + { + return edges_refcount.GetMaxIndex(); + } + int MaxGroupID() const + { + return max_group_id; + } + + bool IsVertex(int VID) const + { + return vertices_refcount.IsValid(VID); + } + bool IsEdge(int EID) const + { + return edges_refcount.IsValid(EID); + } + + /** + * Enumerate "other" vertices of edges connected to vertex (i.e. vertex one-ring) + */ + FSmallListSet::ValueEnumerable VtxVerticesItr(int VID) const + { + check(vertices_refcount.IsValid(VID)); + return vertex_edges.Values(VID, [VID, this](int EID) { return edge_other_v(EID, VID); }); + } + + /** + * Enumerate edge ids connected to vertex (i.e. edge one-ring) + */ + FSmallListSet::ValueEnumerable VtxEdgesItr(int VID) const + { + check(vertices_refcount.IsValid(VID)); + return vertex_edges.Values(VID); + } + + int GetVtxEdgeCount(int VID) const + { + return vertices_refcount.IsValid(VID) ? vertex_edges.GetCount(VID) : -1; + } + + int GetMaxVtxEdgeCount() const + { + int max = 0; + for (int VID : vertices_refcount.Indices()) + { + max = FMath::Max(max, vertex_edges.GetCount(VID)); + } + return max; + } + + FIndex2i GetEdgeV(int EID) const + { + return edges_refcount.IsValid(EID) ? FIndex2i(edges[EID].A, edges[EID].B) : InvalidEdgeV(); + } + + int GetEdgeGroup(int EID) const + { + return edges_refcount.IsValid(EID) ? edges[EID].Group : -1; + } + + void SetEdgeGroup(int EID, int GroupID) + { + check(edges_refcount.IsValid(EID)); + if (edges_refcount.IsValid(EID)) + { + edges[EID].Group = GroupID; + max_group_id = FMath::Max(max_group_id, GroupID + 1); + updateTimeStamp(false); + } + } + + int AllocateEdgeGroup() + { + return max_group_id++; + } + + FEdge GetEdge(int EID) const + { + return edges_refcount.IsValid(EID) ? edges[EID] : InvalidEdge3(); + } + +protected: + int append_vertex_internal() + { + int VID = vertices_refcount.Allocate(); + allocate_edges_list(VID); + updateTimeStamp(true); + return VID; + } + +private: + void allocate_edges_list(int VID) + { + if (VID < (int)vertex_edges.Size()) + { + vertex_edges.Clear(VID); + } + vertex_edges.AllocateAt(VID); + } + +public: + int AppendEdge(const FEdge& E) + { + return AppendEdge(E.A, E.B, E.Group); + } + int AppendEdge(const FIndex2i& ev, int GID = -1) + { + return AppendEdge(ev[0], ev[1], GID); + } + int AppendEdge(int v0, int v1, int GID = -1) + { + if (IsVertex(v0) == false || IsVertex(v1) == false) + { + check(false); + return InvalidID; + } + if (v0 == v1) + { + check(false); + return InvalidID; + } + int e0 = FindEdge(v0, v1); + if (e0 != InvalidID) + return DuplicateEdgeID; + + // Increment ref counts and update/create edges + vertices_refcount.Increment(v0); + vertices_refcount.Increment(v1); + max_group_id = FMath::Max(max_group_id, GID + 1); + + // now safe to insert edge + int EID = add_edge(v0, v1, GID); + + updateTimeStamp(true); + return EID; + } + +protected: + int add_edge(int A, int B, int GID) + { + if (B < A) + { + int t = B; + B = A; + A = t; + } + int EID = edges_refcount.Allocate(); + edges.InsertAt(FEdge{A, B, GID}, EID); + + vertex_edges.Insert(A, EID); + vertex_edges.Insert(B, EID); + return EID; + } + +public: + // + // iterators + // The functions vertices() / triangles() / edges() are provided so you can do: + // for ( int EID : edges() ) { ... } + // and other related begin() / end() idioms + // + typedef typename FRefCountVector::IndexEnumerable vertex_iterator; + vertex_iterator VertexIndices() const + { + return vertices_refcount.Indices(); + } + + typedef typename FRefCountVector::IndexEnumerable edge_iterator; + edge_iterator EdgeIndices() const + { + return edges_refcount.Indices(); + } + + template + using value_iteration = FRefCountVector::MappedEnumerable; + + /** + * Enumerate edges. + */ + value_iteration Edges() const + { + return edges_refcount.MappedIndices([=](int EID) { + return edges[EID]; + }); + } + + int FindEdge(int VA, int VB) const + { + int vMax = FMath::Max(VA, VB); + for (int EID : vertex_edges.Values(FMath::Min(VA, VB))) + { + //if (edge_has_v(EID, vMax)) // (slower option; does not use the fact that edges always have larger vertex as B) + if (edges[EID].B == vMax) + { + return EID; + } + } + return InvalidID; + } + + EMeshResult RemoveEdge(int EID, bool bRemoveIsolatedVertices) + { + if (!edges_refcount.IsValid(EID)) + { + check(false); + return EMeshResult::Failed_NotAnEdge; + } + + FIndex2i ev(edges[EID].A, edges[EID].B); + vertex_edges.Remove(ev.A, EID); + vertex_edges.Remove(ev.B, EID); + + edges_refcount.Decrement(EID); + + // Decrement vertex refcounts. If any hit 1 and we got remove-isolated flag, + // we need to remove that vertex + for (int j = 0; j < 2; ++j) + { + int VID = ev[j]; + vertices_refcount.Decrement(VID); + if (bRemoveIsolatedVertices && vertices_refcount.GetRefCount(VID) == 1) + { + vertices_refcount.Decrement(VID); + check(vertices_refcount.IsValid(VID) == false); + vertex_edges.Clear(VID); + } + } + + updateTimeStamp(true); + return EMeshResult::Ok; + } + + EMeshResult RemoveVertex(int VID, bool bRemoveIsolatedVertices) + { + for (int EID : VtxEdgesItr(VID)) + { + EMeshResult result = RemoveEdge(EID, bRemoveIsolatedVertices); + if (result != EMeshResult::Ok) + return result; + } + return EMeshResult::Ok; + } + + struct FEdgeSplitInfo + { + int VNew; + int ENewBN; // new edge [VNew,VB] (original was AB) + }; + EMeshResult SplitEdge(int VA, int VB, FEdgeSplitInfo& Split) + { + int EID = FindEdge(VA, VB); + if (EID == InvalidID) + { + return EMeshResult::Failed_NotAnEdge; + } + return SplitEdge(EID, Split); + } + EMeshResult SplitEdge(int EAB, FEdgeSplitInfo& Split) + { + if (!IsEdge(EAB)) + { + return EMeshResult::Failed_NotAnEdge; + } + + // look up primary edge + int eab_i = 3 * EAB; + int A = edges[EAB].A, B = edges[EAB].B; + int GID = edges[EAB].Group; + + // create new vertex + int f = append_new_split_vertex(A, B); + + // rewrite edge bc, create edge af + int eaf = EAB; + replace_edge_vertex(eaf, B, f); + vertex_edges.Remove(B, EAB); + vertex_edges.Insert(f, eaf); + + // create new edge fb + int efb = add_edge(f, B, GID); + + // update vertex refcounts + vertices_refcount.Increment(f, 2); + + Split.VNew = f; + Split.ENewBN = efb; + + updateTimeStamp(true); + return EMeshResult::Ok; + } + +protected: + virtual int append_new_split_vertex(int A, int B) + { + // Not Implemented: DGraph2.append_new_split_vertex + unimplemented(); + return InvalidID; + } + +public: + struct FEdgeCollapseInfo + { + int VKept; + int VRemoved; + + int ECollapsed; // edge we collapsed + }; + EMeshResult CollapseEdge(int VKeep, int VRemove, FEdgeCollapseInfo& Collapse) + { + bool DiscardIsolatedVertices = true; + + if (IsVertex(VKeep) == false || IsVertex(VRemove) == false) + { + return EMeshResult::Failed_NotAnEdge; + } + + int B = VKeep; // renaming for sanity. We remove A and keep B + int A = VRemove; + + int EAB = FindEdge(A, B); + if (EAB == InvalidID) + { + return EMeshResult::Failed_NotAnEdge; + } + + // get rid of any edges that will be duplicates + bool done = false; + while (!done) + { + done = true; + for (int eax : vertex_edges.Values(A)) + { + int o = edge_other_v(eax, A); + if (o != B && FindEdge(B, o) != InvalidID) + { + RemoveEdge(eax, DiscardIsolatedVertices); + done = false; + break; + } + } + } + + vertex_edges.Remove(B, EAB); + for (int eax : vertex_edges.Values(A)) + { + int o = edge_other_v(eax, A); + if (o == B) + { + continue; // discard this edge + } + replace_edge_vertex(eax, A, B); + vertices_refcount.Decrement(A); + vertex_edges.Insert(B, eax); + vertices_refcount.Increment(B); + } + + edges_refcount.Decrement(EAB); + vertices_refcount.Decrement(B); + vertices_refcount.Decrement(A); + if (DiscardIsolatedVertices) + { + vertices_refcount.Decrement(A); // second Decrement discards isolated vtx + check(!IsVertex(A)); + } + + vertex_edges.Clear(A); + + Collapse.VKept = VKeep; + Collapse.VRemoved = VRemove; + Collapse.ECollapsed = EAB; + + updateTimeStamp(true); + return EMeshResult::Ok; + } + +protected: + bool edge_has_v(int EID, int VID) const + { + return (edges[EID].A == VID) || (edges[EID].B == VID); + } + int edge_other_v(int EID, int VID) const + { + int ev0 = edges[EID].A, ev1 = edges[EID].B; + return (ev0 == VID) ? ev1 : ((ev1 == VID) ? ev0 : InvalidID); + } + int replace_edge_vertex(int EID, int VOld, int VNew) + { + int A = edges[EID].A, B = edges[EID].B; + if (A == VOld) + { + edges[EID].A = FMath::Min(B, VNew); + edges[EID].B = FMath::Max(B, VNew); + return 0; + } + else if (B == VOld) + { + edges[EID].A = FMath::Min(A, VNew); + edges[EID].B = FMath::Max(A, VNew); + return 1; + } + else + return -1; + } + +public: + bool IsCompact() const + { + return vertices_refcount.IsDense() && edges_refcount.IsDense(); + } + bool IsCompactV() const + { + return vertices_refcount.IsDense(); + } + + bool IsBoundaryVertex(int VID) const + { + return vertices_refcount.IsValid(VID) && vertex_edges.GetCount(VID) == 1; + } + + bool IsJunctionVertex(int VID) const + { + return vertices_refcount.IsValid(VID) && vertex_edges.GetCount(VID) > 2; + } + + bool IsRegularVertex(int VID) const + { + return vertices_refcount.IsValid(VID) && vertex_edges.GetCount(VID) == 2; + } + + + /** + * This function checks that the graph is well-formed, ie all internal data + * structures are consistent + */ + virtual bool CheckValidity(EValidityCheckFailMode FailMode = EValidityCheckFailMode::Check) const + { + bool is_ok = true; + TFunction CheckOrFailF = [&](bool b) + { + is_ok = is_ok && b; + }; + if (FailMode == EValidityCheckFailMode::Check) + { + CheckOrFailF = [&](bool b) + { + checkf(b, TEXT("FEdgeLoop::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + else if (FailMode == EValidityCheckFailMode::Ensure) + { + CheckOrFailF = [&](bool b) + { + ensureMsgf(b, TEXT("FEdgeLoop::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + + // edge verts/tris must exist + for (int EID : EdgeIndices()) + { + CheckOrFailF(IsEdge(EID)); + CheckOrFailF(edges_refcount.GetRefCount(EID) == 1); + FIndex2i ev = GetEdgeV(EID); + CheckOrFailF(IsVertex(ev[0])); + CheckOrFailF(IsVertex(ev[1])); + CheckOrFailF(ev[0] < ev[1]); + } + + // verify compact check + bool is_compact = vertices_refcount.IsDense(); + if (is_compact) + { + for (int VID = 0; VID < VertexCount(); ++VID) + { + CheckOrFailF(vertices_refcount.IsValid(VID)); + } + } + + // vertex edges must exist and reference this vert + for (int VID : VertexIndices()) + { + CheckOrFailF(IsVertex(VID)); + + //FVector3d V = GetVertex(VID); + //CheckOrFailF(double.IsNaN(V.SquaredLength()) == false); + //CheckOrFailF(double.IsInfinity(V.SquaredLength()) == false); + + for (int edgeid : vertex_edges.Values(VID)) + { + CheckOrFailF(IsEdge(edgeid)); + CheckOrFailF(edge_has_v(edgeid, VID)); + + int otherV = edge_other_v(edgeid, VID); + int e2 = FindEdge(VID, otherV); + CheckOrFailF(e2 != InvalidID); + CheckOrFailF(e2 == edgeid); + e2 = FindEdge(otherV, VID); + CheckOrFailF(e2 != InvalidID); + CheckOrFailF(e2 == edgeid); + } + + CheckOrFailF(vertices_refcount.GetRefCount(VID) == vertex_edges.GetCount(VID) + 1); + } + + subclass_validity_checks(CheckOrFailF); + + return is_ok; + } + +protected: + virtual void subclass_validity_checks(TFunction CheckOrFailF) const + { + } + + inline void debug_check_is_vertex(int V) const + { + check(IsVertex(V)); + } + + inline void debug_check_is_edge(int E) const + { + check(IsEdge(E)); + } +}; + +/** + * Implementation of DGraph that has no dimensionality, ie no data + * stored for vertices besides indices. + */ +class FDynamicGraphN : public FDynamicGraph +{ +public: + int AppendVertex() + { + return append_vertex_internal(); + } + +protected: + // internal used in SplitEdge + virtual int append_new_split_vertex(int A, int B) override + { + return AppendVertex(); + } +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Curve/DynamicGraph2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Curve/DynamicGraph2.h new file mode 100644 index 000000000000..958dc0b793ca --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Curve/DynamicGraph2.h @@ -0,0 +1,239 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "CoreTypes.h" + +#include "BoxTypes.h" +#include "DynamicGraph.h" +#include "SegmentTypes.h" +#include "Util/DynamicVector.h" +#include "Util/IndexUtil.h" +#include "Util/IteratorUtil.h" +#include "Util/RefCountVector.h" +#include "Util/SmallListSet.h" +#include "VectorTypes.h" +#include "VectorUtil.h" + +#pragma once + +template +class FDynamicGraph2 : public FDynamicGraph +{ + TAxisAlignedBox2 cached_bounds; + int cached_bounds_timestamp = -1; + + TDynamicVectorN vertices; + +public: + static FVector2 InvalidVertex() + { + return FVector2(TNumericLimits::Max(), 0); + } + + FVector2 GetVertex(int VID) const + { + return vertices_refcount.IsValid(VID) ? vertices.AsVector2(VID) : InvalidVertex(); + } + + void SetVertex(int VID, FVector2 VNewPos) + { + check(VectorUtil::IsFinite(VNewPos)); // this will really catch a lot of bugs... + if (vertices_refcount.IsValid(VID)) + { + vertices.SetVector2(VID, VNewPos); + updateTimeStamp(true); + } + } + + using FDynamicGraph::GetEdgeV; + bool GetEdgeV(int EID, FVector2& A, FVector2& B) const + { + if (edges_refcount.IsValid(EID)) + { + A = vertices.AsVector2(edges[EID].A); + B = vertices.AsVector2(edges[EID].B); + return true; + } + return false; + } + + TSegment2 GetEdgeSegment(int EID) const + { + checkf(edges_refcount.IsValid(EID), TEXT("FDynamicGraph2.GetEdgeSegment: invalid segment with id %d"), EID); + const FEdge& e = edges[EID]; + return TSegment2( + vertices.AsVector2(e.A), + vertices.AsVector2(e.B)); + } + + FVector2 GetEdgeCenter(int EID) const + { + checkf(edges_refcount.IsValid(EID), TEXT("FDynamicGraph2.GetEdgeCenter: invalid segment with id %d"), EID); + const FEdge& e = edges[EID]; + return 0.5 * (vertices.AsVector2(e.A) + vertices.AsVector2(e.B)); + } + + int AppendVertex(FVector2d V) + { + int vid = append_vertex_internal(); + vertices.InsertAt({{V.X, V.Y}}, vid); + return vid; + } + + //void AppendPolygon(Polygon2d poly, int gid = -1) { + // int first = -1; + // int prev = -1; + // int N = poly.VertexCount; + // for (int i = 0; i < N; ++i) { + // int cur = AppendVertex(poly[i]); + // if (prev == -1) + // first = cur; + // else + // AppendEdge(prev, cur, gid); + // prev = cur; + // } + // AppendEdge(prev, first, gid); + //} + //void AppendPolygon(GeneralPolygon2d poly, int gid = -1) + //{ + // AppendPolygon(poly.Outer, gid); + // foreach(var hole in poly.Holes) + // AppendPolygon(hole, gid); + //} + + //void AppendPolyline(PolyLine2d poly, int gid = -1) + //{ + // int prev = -1; + // int N = poly.VertexCount; + // for (int i = 0; i < N; ++i) { + // int cur = AppendVertex(poly[i]); + // if (i > 0) + // AppendEdge(prev, cur, gid); + // prev = cur; + // } + //} + + /* + void AppendGraph(FDynamicGraph2 graph, int gid = -1) + { + int[] mapV = new int[graph.MaxVertexID]; + foreach(int vid in graph.VertexIndices()) { + mapV[vid] = this.AppendVertex(graph.GetVertex(vid)); + } + foreach(int eid in graph.EdgeIndices()) { + FIndex2i ev = graph.GetEdgeV(eid); + int use_gid = (gid == -1) ? graph.GetEdgeGroup(eid) : gid; + this.AppendEdge(mapV[ev.A], mapV[ev.B], use_gid); + } + } + +*/ + + /** Enumerate positions of all vertices in graph */ + value_iteration> Vertices() const + { + return vertices_refcount.MappedIndices>( + [=](int vid) { + return vertices.template AsVector2(vid); + }); + } + + /** + * return edges around VID sorted by angle, in clockwise order + */ + bool SortedVtxEdges(int VID, TArray& Sorted) const + { + if (vertices_refcount.IsValid(VID) == false) + return false; + Sorted.SetNumUninitialized(vertex_edges.GetCount(VID)); + for (int EID : vertex_edges.Values(VID)) + { + Sorted.Add(EID); + } + FVector2 V = vertices.AsVector2(VID); + Algo::SortBy(Sorted, [&](int EID) { + int NbrVID = edge_other_v(EID, VID); + FVector2 D = vertices.AsVector2(NbrVID) - V; + return TMathUtil::Atan2Positive(D.Y, D.X); + }); + + return true; + } + + // compute vertex bounding box + FAxisAlignedBox2d GetBounds() const + { + TAxisAlignedBox2 AABB; + for (const FVector2& V : Vertices()) + { + AABB.Contain(V); + } + return AABB; + } + + //! cached bounding box, lazily re-computed on access if mesh has changed + TAxisAlignedBox2 CachedBounds() + { + if (cached_bounds_timestamp != Timestamp()) + { + cached_bounds = GetBounds(); + cached_bounds_timestamp = Timestamp(); + } + return cached_bounds; + } + + /** + * Compute opening angle at vertex VID. + * If not a vertex, or valence != 2, returns InvalidValue argument. + * If either edge is degenerate, returns InvalidValue argument. + */ + double OpeningAngle(int VID, double InvalidValue = TNumericLimits::Max()) const + { + if (vertices_refcount.IsValid(VID) == false) + { + return InvalidValue; + } + if (vertex_edges.GetCount(VID) != 2) + { + return InvalidValue; + } + FSmallListSet::ValueIterator ValueIterate = vertex_edges.Values(VID).begin(); + + int nbra = edge_other_v(*ValueIterate, VID); + int nbrb = edge_other_v(*++ValueIterate, VID); + + FVector2 V = vertices.AsVector2(VID); + FVector2 A = vertices.AsVector2(nbra); + FVector2 B = vertices.AsVector2(nbrb); + A -= V; + if (A.Normalize() == 0) + { + return InvalidValue; + } + B -= V; + if (B.Normalize() == 0) + { + return InvalidValue; + } + return A.AngleD(B); + } + +protected: + // internal used in SplitEdge + virtual int append_new_split_vertex(int A, int B) override + { + FVector2 vNew = 0.5 * (GetVertex(A) + GetVertex(B)); + int f = AppendVertex(vNew); + return f; + } + + virtual void subclass_validity_checks(TFunction CheckOrFailF) const override + { + for (int VID : VertexIndices()) + { + FVector2 V = GetVertex(VID); + CheckOrFailF(VectorUtil::IsFinite(V)); + } + } +}; + +typedef FDynamicGraph2 FDynamicGraph2d; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Ray3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Ray3.h new file mode 100644 index 000000000000..14a74395a84d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Ray3.h @@ -0,0 +1,107 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp DistLine3Ray3 + +#pragma once + +#include "VectorTypes.h" +#include "LineTypes.h" +#include "RayTypes.h" + +/** + * Compute distance between 3D line and 3D ray + */ +template +class TDistLine3Ray3 +{ +public: + // Input + TLine3 Line; + TRay3 Ray; + + // Results + Real DistanceSquared = -1.0; + + FVector3 LineClosestPoint; + Real LineParameter; + FVector3 RayClosestPoint; + Real RayParameter; + + + TDistLine3Ray3(const TLine3& LineIn, const TRay3& RayIn) + { + Line = LineIn; + Ray = RayIn; + } + + Real Get() + { + return (Real)sqrt(ComputeResult()); + } + Real GetSquared() + { + return ComputeResult(); + } + + Real ComputeResult() + { + if (DistanceSquared >= 0) + { + return DistanceSquared; + } + + FVector3 kDiff = Line.Origin - Ray.Origin; + Real a01 = -Line.Direction.Dot(Ray.Direction); + Real b0 = kDiff.Dot(Line.Direction); + Real c = kDiff.SquaredLength(); + Real det = FMath::Abs((Real)1 - a01 * a01); + Real b1, s0, s1, sqrDist; + + if (det >= TMathUtil::ZeroTolerance) + { + b1 = -kDiff.Dot(Ray.Direction); + s1 = a01 * b0 - b1; + + if (s1 >= (Real)0) + { + // Two interior points are closest, one on Line and one on Ray. + Real invDet = ((Real)1) / det; + s0 = (a01 * b1 - b0) * invDet; + s1 *= invDet; + sqrDist = s0 * (s0 + a01 * s1 + ((Real)2) * b0) + + s1 * (a01 * s0 + s1 + ((Real)2) * b1) + c; + } + else + { + // Origin of Ray and interior point of Line are closest. + s0 = -b0; + s1 = (Real)0; + sqrDist = b0 * s0 + c; + } + } + else + { + // Lines are parallel, closest pair with one point at Ray origin. + s0 = -b0; + s1 = (Real)0; + sqrDist = b0 * s0 + c; + } + + LineClosestPoint = Line.Origin + s0 * Line.Direction; + RayClosestPoint = Ray.Origin + s1 * Ray.Direction; + LineParameter = s0; + RayParameter = s1; + + // Account for numerical round-off errors. + if (sqrDist < (Real)0) + { + sqrDist = (Real)0; + } + DistanceSquared = sqrDist; + + return sqrDist; + } +}; + +typedef TDistLine3Ray3 FDistLine3Ray3f; +typedef TDistLine3Ray3 FDistLine3Ray3d; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Segment3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Segment3.h new file mode 100644 index 000000000000..f52973f36c07 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Segment3.h @@ -0,0 +1,122 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp DistLine3Segment3 +// which was ported from WildMagic 5 + +#pragma once + +#include "VectorTypes.h" +#include "SegmentTypes.h" +#include "LineTypes.h" + + +/** +* Compute unsigned distance between 3D line and 3D segment +*/ +template +class TDistLine3Segment3 +{ +public: + // Input + TLine3 Line; + TSegment3 Segment; + + // Output + Real DistanceSquared = -1.0; + Real LineParameter, SegmentParameter; + FVector3 LineClosest, SegmentClosest; + + + TDistLine3Segment3(const TLine3& LineIn, const TSegment3& SegmentIn) : Line(LineIn), Segment(SegmentIn) + { + } + + Real Get() + { + return TMathUtil::Sqrt(ComputeResult()); + } + Real GetSquared() + { + return ComputeResult(); + } + + Real ComputeResult() + { + if (DistanceSquared >= 0) + { + return DistanceSquared; + } + + FVector3 diff = Line.Origin - Segment.Center; + Real a01 = -Line.Direction.Dot(Segment.Direction); + Real b0 = diff.Dot(Line.Direction); + Real c = diff.SquaredLength(); + Real det = TMathUtil::Abs(1 - a01 * a01); + Real b1, s0, s1, sqrDist, extDet; + + if (det >= TMathUtil::ZeroTolerance) + { + // The Line and Segment are not parallel. + b1 = -diff.Dot(Segment.Direction); + s1 = a01 * b0 - b1; + extDet = Segment.Extent * det; + + if (s1 >= -extDet) + { + if (s1 <= extDet) + { + // Two interior points are closest, one on the Line and one + // on the Segment. + Real invDet = (1) / det; + s0 = (a01 * b1 - b0) * invDet; + s1 *= invDet; + sqrDist = s0 * (s0 + a01 * s1 + (2) * b0) + + s1 * (a01 * s0 + s1 + (2) * b1) + c; + } + else + { + // The endpoint e1 of the Segment and an interior point of + // the Line are closest. + s1 = Segment.Extent; + s0 = -(a01 * s1 + b0); + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + } + else + { + // The end point e0 of the Segment and an interior point of the + // Line are closest. + s1 = -Segment.Extent; + s0 = -(a01 * s1 + b0); + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + } + else + { + // The Line and Segment are parallel. Choose the closest pair so that + // one point is at Segment center. + s1 = 0; + s0 = -b0; + sqrDist = b0 * s0 + c; + } + + LineClosest = Line.Origin + s0 * Line.Direction; + SegmentClosest = Segment.Center + s1 * Segment.Direction; + LineParameter = s0; + SegmentParameter = s1; + + // Account for numerical round-off errors. + if (sqrDist < 0) + { + sqrDist = 0; + } + + DistanceSquared = sqrDist; + + return DistanceSquared; + } +}; + +typedef TDistLine3Segment3 FDistLine3Segment3f; +typedef TDistLine3Segment3 FDistLine3Segment3d; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Triangle3.h new file mode 100644 index 000000000000..a86f22c76f2f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Triangle3.h @@ -0,0 +1,127 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp DistLine3Triangle3 +// which was ported from WildMagic 5 + +#pragma once + +#include "VectorTypes.h" +#include "TriangleTypes.h" +#include "SegmentTypes.h" +#include "DistLine3Segment3.h" + + +/** +* Compute unsigned distance between 3D line and 3D triangle +*/ +template +class TDistLine3Triangle3 +{ +public: + // Input + TLine3 Line; + TTriangle3 Triangle; + + // Output + Real DistanceSquared = -1.0; + Real LineParam; + FVector3 LineClosest, TriangleClosest, TriangleBaryCoords; + + + TDistLine3Triangle3(const TLine3& LineIn, const TTriangle3& TriangleIn) : Line(LineIn), Triangle(TriangleIn) + { + } + + Real Get() + { + return TMathUtil::Sqrt(ComputeResult()); + } + Real GetSquared() + { + return ComputeResult(); + } + + Real ComputeResult() + { + if (DistanceSquared >= 0) + { + return DistanceSquared; + } + + // Test if Line intersects Triangle. If so, the squared distance is zero. + FVector3 edge0 = Triangle.V[1] - Triangle.V[0]; + FVector3 edge1 = Triangle.V[2] - Triangle.V[0]; + FVector3 normal = edge0.Cross(edge1); + normal.Normalize(); + Real NdD = normal.Dot(Line.Direction); + if (TMathUtil::Abs(NdD) > TMathUtil::ZeroTolerance) + { + // The line and triangle are not parallel, so the line intersects + // the plane of the triangle. + FVector3 diff = Line.Origin - Triangle.V[0]; + FVector3 U, V; + VectorUtil::MakePerpVectors(Line.Direction, U, V); + Real UdE0 = U.Dot(edge0); + Real UdE1 = U.Dot(edge1); + Real UdDiff = U.Dot(diff); + Real VdE0 = V.Dot(edge0); + Real VdE1 = V.Dot(edge1); + Real VdDiff = V.Dot(diff); + Real invDet = (1) / (UdE0 * VdE1 - UdE1 * VdE0); + + // Barycentric coordinates for the point of intersection. + Real b1 = (VdE1 * UdDiff - UdE1 * VdDiff) * invDet; + Real b2 = (UdE0 * VdDiff - VdE0 * UdDiff) * invDet; + Real b0 = 1 - b1 - b2; + + if (b0 >= 0 && b1 >= 0 && b2 >= 0) + { + // Line parameter for the point of intersection. + Real DdE0 = Line.Direction.Dot(edge0); + Real DdE1 = Line.Direction.Dot(edge1); + Real DdDiff = Line.Direction.Dot(diff); + LineParam = b1 * DdE0 + b2 * DdE1 - DdDiff; + + // Barycentric coordinates for the point of intersection. + TriangleBaryCoords = FVector3(b0, b1, b2); + + // The intersection point is inside or on the Triangle. + LineClosest = Line.Origin + LineParam * Line.Direction; + TriangleClosest = Triangle.V[0] + b1 * edge0 + b2 * edge1; + DistanceSquared = 0; + return 0; + } + } + + // Either (1) the Line is not parallel to the Triangle and the point of + // intersection of the Line and the plane of the Triangle is outside the + // Triangle or (2) the Line and Triangle are parallel. Regardless, the + // closest point on the Triangle is on an edge of the Triangle. Compare + // the Line to all three edges of the Triangle. + Real sqrDist = TMathUtil::MaxReal; + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + TSegment3 segment(Triangle.V[i0], Triangle.V[i1]); + TDistLine3Segment3 queryLS(Line, segment); + Real sqrDistTmp = queryLS.GetSquared(); + if (sqrDistTmp < sqrDist) + { + LineClosest = queryLS.LineClosest; + TriangleClosest = queryLS.SegmentClosest; + sqrDist = sqrDistTmp; + LineParam = queryLS.LineParameter; + Real ratio = queryLS.SegmentParameter / segment.Extent; + TriangleBaryCoords[i0] = (0.5) * (1 - ratio); + TriangleBaryCoords[i1] = 1 - TriangleBaryCoords[i0]; + TriangleBaryCoords[3 - i0 - i1] = 0; + } + } + + DistanceSquared = sqrDist; + return DistanceSquared; + } +}; + +typedef TDistLine3Triangle3 FDistLine3Triangle3f; +typedef TDistLine3Triangle3 FDistLine3Triangle3d; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistPoint3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistPoint3Triangle3.h new file mode 100644 index 000000000000..d39af3ca44ab --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistPoint3Triangle3.h @@ -0,0 +1,275 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp DistPoint3Triangle3 + +#pragma once + +#include "VectorTypes.h" +#include "TriangleTypes.h" + + +/** + * Compute unsigned distance between 3D point and 3D triangle + */ +template +class TDistPoint3Triangle3 +{ +public: + // Input + FVector3 Point; + TTriangle3 Triangle; + + // Results + FVector3 TriangleBaryCoords; + FVector3 ClosestTrianglePoint; // do we need this just use Triangle.BarycentricPoint + + + TDistPoint3Triangle3(const FVector3& PointIn, const TTriangle3& TriangleIn) + { + Point = PointIn; + Triangle = TriangleIn; + } + + Real Get() + { + return (Real)sqrt(ComputeResult()); + } + Real GetSquared() + { + return ComputeResult(); + } + + Real ComputeResult() + { + FVector3 diff = Triangle.V[0] - Point; + FVector3 edge0 = Triangle.V[1] - Triangle.V[0]; + FVector3 edge1 = Triangle.V[2] - Triangle.V[0]; + Real a00 = edge0.SquaredLength(); + Real a01 = edge0.Dot(edge1); + Real a11 = edge1.SquaredLength(); + Real b0 = diff.Dot(edge0); + Real b1 = diff.Dot(edge1); + Real c = diff.SquaredLength(); + Real det = FMath::Abs(a00*a11 - a01 * a01); + Real s = a01 * b1 - a11 * b0; + Real t = a01 * b0 - a00 * b1; + Real sqrDistance; + + if (s + t <= det) + { + if (s < (Real)0) + { + if (t < (Real)0) // region 4 + { + if (b0 < (Real)0) + { + t = (Real)0; + if (-b0 >= a00) + { + s = (Real)1; + sqrDistance = a00 + ((Real)2)*b0 + c; + } + else + { + s = -b0 / a00; + sqrDistance = b0 * s + c; + } + } + else + { + s = (Real)0; + if (b1 >= (Real)0) + { + t = (Real)0; + sqrDistance = c; + } + else if (-b1 >= a11) + { + t = (Real)1; + sqrDistance = a11 + ((Real)2)*b1 + c; + } + else + { + t = -b1 / a11; + sqrDistance = b1 * t + c; + } + } + } + else // region 3 + { + s = (Real)0; + if (b1 >= (Real)0) + { + t = (Real)0; + sqrDistance = c; + } + else if (-b1 >= a11) + { + t = (Real)1; + sqrDistance = a11 + ((Real)2)*b1 + c; + } + else + { + t = -b1 / a11; + sqrDistance = b1 * t + c; + } + } + } + else if (t < (Real)0) // region 5 + { + t = (Real)0; + if (b0 >= (Real)0) + { + s = (Real)0; + sqrDistance = c; + } + else if (-b0 >= a00) + { + s = (Real)1; + sqrDistance = a00 + ((Real)2)*b0 + c; + } + else + { + s = -b0 / a00; + sqrDistance = b0 * s + c; + } + } + else // region 0 + { + // minimum at interior point + Real invDet = ((Real)1) / det; + s *= invDet; + t *= invDet; + sqrDistance = s * (a00*s + a01 * t + ((Real)2)*b0) + t * (a01*s + a11 * t + ((Real)2)*b1) + c; + } + } + else + { + Real tmp0, tmp1, numer, denom; + + if (s < (Real)0) // region 2 + { + tmp0 = a01 + b0; + tmp1 = a11 + b1; + if (tmp1 > tmp0) + { + numer = tmp1 - tmp0; + denom = a00 - ((Real)2)*a01 + a11; + if (numer >= denom) + { + s = (Real)1; + t = (Real)0; + sqrDistance = a00 + ((Real)2)*b0 + c; + } + else + { + s = numer / denom; + t = (Real)1 - s; + sqrDistance = s * (a00*s + a01 * t + ((Real)2)*b0) + t * (a01*s + a11 * t + ((Real)2)*b1) + c; + } + } + else + { + s = (Real)0; + if (tmp1 <= (Real)0) + { + t = (Real)1; + sqrDistance = a11 + ((Real)2)*b1 + c; + } + else if (b1 >= (Real)0) + { + t = (Real)0; + sqrDistance = c; + } + else + { + t = -b1 / a11; + sqrDistance = b1 * t + c; + } + } + } + else if (t < (Real)0) // region 6 + { + tmp0 = a01 + b1; + tmp1 = a00 + b0; + if (tmp1 > tmp0) + { + numer = tmp1 - tmp0; + denom = a00 - ((Real)2)*a01 + a11; + if (numer >= denom) + { + t = (Real)1; + s = (Real)0; + sqrDistance = a11 + ((Real)2)*b1 + c; + } + else + { + t = numer / denom; + s = (Real)1 - t; + sqrDistance = s * (a00*s + a01 * t + ((Real)2)*b0) + t * (a01*s + a11 * t + ((Real)2)*b1) + c; + } + } + else + { + t = (Real)0; + if (tmp1 <= (Real)0) + { + s = (Real)1; + sqrDistance = a00 + ((Real)2)*b0 + c; + } + else if (b0 >= (Real)0) + { + s = (Real)0; + sqrDistance = c; + } + else + { + s = -b0 / a00; + sqrDistance = b0 * s + c; + } + } + } + else // region 1 + { + numer = a11 + b1 - a01 - b0; + if (numer <= (Real)0) + { + s = (Real)0; + t = (Real)1; + sqrDistance = a11 + ((Real)2)*b1 + c; + } + else + { + denom = a00 - ((Real)2)*a01 + a11; + if (numer >= denom) + { + s = (Real)1; + t = (Real)0; + sqrDistance = a00 + ((Real)2)*b0 + c; + } + else + { + s = numer / denom; + t = (Real)1 - s; + sqrDistance = s * (a00*s + a01 * t + ((Real)2)*b0) + t * (a01*s + a11 * t + ((Real)2)*b1) + c; + } + } + } + } + + // Account for numerical round-off error. + if (sqrDistance < (Real)0) + { + sqrDistance = (Real)0; + } + + ClosestTrianglePoint = Triangle.V[0] + s * edge0 + t * edge1; + TriangleBaryCoords = FVector3((Real)1 - s - t, s, t); + return sqrDistance; + } + +}; + +typedef TDistPoint3Triangle3 FDistPoint3Triangle3f; +typedef TDistPoint3Triangle3 FDistPoint3Triangle3d; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistRay3Segment3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistRay3Segment3.h new file mode 100644 index 000000000000..3dee02df99dd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistRay3Segment3.h @@ -0,0 +1,389 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp DistRay3Segment3 + +#pragma once + +#include "VectorTypes.h" +#include "SegmentTypes.h" +#include "RayTypes.h" + +/** + * Compute distance between 3D ray and 3D segment + */ +template +class TDistRay3Segment3 +{ +public: + // Input + TRay3 Ray; + TSegment3 Segment; + + // Results + Real DistanceSquared = -1.0; + + FVector3 RayClosestPoint; + Real RayParameter; + FVector3 SegmentClosestPoint; + Real SegmentParameter; + + + TDistRay3Segment3(const TRay3& RayIn, const TSegment3& SegmentIn) + { + Ray = RayIn; + Segment = SegmentIn; + } + + Real Get() + { + return (Real)sqrt(ComputeResult()); + } + Real GetSquared() + { + return ComputeResult(); + } + + Real ComputeResult() + { + if (DistanceSquared >= 0) + { + return DistanceSquared; + } + FVector3d diff = Ray.Origin - Segment.Center; + double a01 = -Ray.Direction.Dot(Segment.Direction); + double b0 = diff.Dot(Ray.Direction); + double b1 = -diff.Dot(Segment.Direction); + double c = diff.SquaredLength(); + double det = TMathUtil::Abs(1 - a01 * a01); + double s0, s1, sqrDist, extDet; + + if (det >= TMathUtil::ZeroTolerance ) + { + // The Ray and Segment are not parallel. + s0 = a01 * b1 - b0; + s1 = a01 * b0 - b1; + extDet = Segment.Extent * det; + + if (s0 >= 0) + { + if (s1 >= -extDet) + { + if (s1 <= extDet) // region 0 + { + // Minimum at interior points of Ray and Segment. + double invDet = (1) / det; + s0 *= invDet; + s1 *= invDet; + sqrDist = s0 * (s0 + a01 * s1 + (2) * b0) + + s1 * (a01 * s0 + s1 + (2) * b1) + c; + } + else // region 1 + { + s1 = Segment.Extent; + s0 = -(a01 * s1 + b0); + if (s0 > 0) + { + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + } + else // region 5 + { + s1 = -Segment.Extent; + s0 = -(a01 * s1 + b0); + if (s0 > 0) + { + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + } + else + { + if (s1 <= -extDet) // region 4 + { + s0 = -(-a01 * Segment.Extent + b0); + if (s0 > 0) + { + s1 = -Segment.Extent; + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + s1 = -b1; + if (s1 < -Segment.Extent) + { + s1 = -Segment.Extent; + } + else if (s1 > Segment.Extent) + { + s1 = Segment.Extent; + } + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + else if (s1 <= extDet) // region 3 + { + s0 = 0; + s1 = -b1; + if (s1 < -Segment.Extent) + { + s1 = -Segment.Extent; + } + else if (s1 > Segment.Extent) + { + s1 = Segment.Extent; + } + sqrDist = s1 * (s1 + (2) * b1) + c; + } + else // region 2 + { + s0 = -(a01 * Segment.Extent + b0); + if (s0 > 0) + { + s1 = Segment.Extent; + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + s1 = -b1; + if (s1 < -Segment.Extent) + { + s1 = -Segment.Extent; + } + else if (s1 > Segment.Extent) + { + s1 = Segment.Extent; + } + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + } + } + else + { + // Ray and Segment are parallel. + if (a01 > 0) + { + // Opposite direction vectors. + s1 = -Segment.Extent; + } + else + { + // Same direction vectors. + s1 = Segment.Extent; + } + + s0 = -(a01 * s1 + b0); + if (s0 > 0) + { + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + + RayClosestPoint = Ray.Origin + s0 * Ray.Direction; + SegmentClosestPoint = Segment.Center + s1 * Segment.Direction; + RayParameter = s0; + SegmentParameter = s1; + + // Account for numerical round-off errors. + if (sqrDist < (Real)0) + { + sqrDist = (Real)0; + } + DistanceSquared = sqrDist; + + return sqrDist; + } + + + + + + static double SquaredDistance(const TRay3& Ray, const TSegment3& Segment, + Real& RayParam, Real& SegParam) + { + FVector3 diff = Ray.Origin - Segment.Center; + double a01 = -Ray.Direction.Dot(Segment.Direction); + double b0 = diff.Dot(Ray.Direction); + double b1 = -diff.Dot(Segment.Direction); + double c = diff.SquaredLength(); + double det = TMathUtil::Abs(1 - a01 * a01); + double s0, s1, sqrDist, extDet; + + if (det >= TMathUtil::ZeroTolerance) + { + // The Ray and Segment are not parallel. + s0 = a01 * b1 - b0; + s1 = a01 * b0 - b1; + extDet = Segment.Extent * det; + + if (s0 >= 0) + { + if (s1 >= -extDet) + { + if (s1 <= extDet) // region 0 + { + // Minimum at interior points of Ray and Segment. + double invDet = (1) / det; + s0 *= invDet; + s1 *= invDet; + sqrDist = s0 * (s0 + a01 * s1 + (2) * b0) + + s1 * (a01 * s0 + s1 + (2) * b1) + c; + } + else // region 1 + { + s1 = Segment.Extent; + s0 = -(a01 * s1 + b0); + if (s0 > 0) + { + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + } + else // region 5 + { + s1 = -Segment.Extent; + s0 = -(a01 * s1 + b0); + if (s0 > 0) + { + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + } + else + { + if (s1 <= -extDet) // region 4 + { + s0 = -(-a01 * Segment.Extent + b0); + if (s0 > 0) + { + s1 = -Segment.Extent; + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + s1 = -b1; + if (s1 < -Segment.Extent) + { + s1 = -Segment.Extent; + } + else if (s1 > Segment.Extent) + { + s1 = Segment.Extent; + } + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + else if (s1 <= extDet) // region 3 + { + s0 = 0; + s1 = -b1; + if (s1 < -Segment.Extent) + { + s1 = -Segment.Extent; + } + else if (s1 > Segment.Extent) + { + s1 = Segment.Extent; + } + sqrDist = s1 * (s1 + (2) * b1) + c; + } + else // region 2 + { + s0 = -(a01 * Segment.Extent + b0); + if (s0 > 0) + { + s1 = Segment.Extent; + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + s1 = -b1; + if (s1 < -Segment.Extent) + { + s1 = -Segment.Extent; + } + else if (s1 > Segment.Extent) + { + s1 = Segment.Extent; + } + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + } + } + else { + // Ray and Segment are parallel. + if (a01 > 0) + { + // Opposite direction vectors. + s1 = -Segment.Extent; + } + else + { + // Same direction vectors. + s1 = Segment.Extent; + } + + s0 = -(a01 * s1 + b0); + if (s0 > 0) + { + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + + RayParam = s0; + SegParam = s1; + + // Account for numerical round-off errors. + if (sqrDist < 0) + { + sqrDist = 0; + } + return sqrDist; + } + + + + + + + +}; + +typedef TDistRay3Segment3 FDistRay3Segment3f; +typedef TDistRay3Segment3 FDistRay3Segment3d; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistSegment3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistSegment3Triangle3.h new file mode 100644 index 000000000000..6ebd65e2eb02 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistSegment3Triangle3.h @@ -0,0 +1,86 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp DistSegment3Triangle3 +// which was ported from WildMagic 5 + +#pragma once + +#include "VectorTypes.h" +#include "TriangleTypes.h" +#include "SegmentTypes.h" +#include "DistLine3Triangle3.h" +#include "DistPoint3Triangle3.h" + +/** +* Compute unsigned distance between 3D segment and 3D triangle +*/ +template +class TDistSegment3Triangle3 +{ +public: + // Input + TSegment3 Segment; + TTriangle3 Triangle; + + // Output + Real DistanceSquared = -1.0; + Real SegmentParameter; + FVector3 TriangleClosest, TriangleBaryCoords, SegmentClosest; + + TDistSegment3Triangle3(const TSegment3& SegmentIn, const TTriangle3& TriangleIn) : Segment(SegmentIn), Triangle(TriangleIn) + { + } + + Real Get() + { + return TMathUtil::Sqrt(ComputeResult()); + } + Real GetSquared() + { + return ComputeResult(); + } + + Real ComputeResult() + { + if (DistanceSquared >= 0) + { + return DistanceSquared; + } + + TLine3 line(Segment.Center, Segment.Direction); + TDistLine3Triangle3 queryLT(line, Triangle); + double sqrDist = queryLT.GetSquared(); + SegmentParameter = queryLT.LineParam; + + if (SegmentParameter >= -Segment.Extent) { + if (SegmentParameter <= Segment.Extent) { + SegmentClosest = queryLT.LineClosest; + TriangleClosest = queryLT.TriangleClosest; + TriangleBaryCoords = queryLT.TriangleBaryCoords; + } + else { + SegmentClosest = Segment.EndPoint(); + TDistPoint3Triangle3 queryPT(SegmentClosest, Triangle); + sqrDist = queryPT.GetSquared(); + TriangleClosest = queryPT.ClosestTrianglePoint; + SegmentParameter = Segment.Extent; + TriangleBaryCoords = queryPT.TriangleBaryCoords; + } + } + else { + SegmentClosest = Segment.StartPoint(); + TDistPoint3Triangle3 queryPT(SegmentClosest, Triangle); + sqrDist = queryPT.GetSquared(); + TriangleClosest = queryPT.ClosestTrianglePoint; + SegmentParameter = -Segment.Extent; + TriangleBaryCoords = queryPT.TriangleBaryCoords; + } + + DistanceSquared = sqrDist; + return DistanceSquared; + } +}; + +typedef TDistSegment3Triangle3 FDistSegment3Triangle3f; +typedef TDistSegment3Triangle3 FDistSegment3Triangle3d; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistTriangle3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistTriangle3Triangle3.h new file mode 100644 index 000000000000..9004be13458d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistTriangle3Triangle3.h @@ -0,0 +1,122 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp DistTriangle3Triangle3 +// which was ported from WildMagic 5 + + +#pragma once + +#include "VectorTypes.h" +#include "TriangleTypes.h" +#include "SegmentTypes.h" +#include "DistSegment3Triangle3.h" +#include "DistPoint3Triangle3.h" + +/** +* Compute unsigned distance between 3D segment and 3D triangle +*/ +template +class TDistTriangle3Triangle3 +{ +public: + // Input + TTriangle3 Triangle[2]; + + // Output + Real DistanceSquared = -1.0; + FVector3 TriangleClosest[2]; + FVector3 TriangleBaryCoords[2]; + + + TDistTriangle3Triangle3() + { + } + TDistTriangle3Triangle3(const TTriangle3& TriangleA, const TTriangle3& TriangleB) + { + Triangle[0] = TriangleA; + Triangle[1] = TriangleB; + } + + Real Get() + { + return TMathUtil::Sqrt(ComputeResult()); + } + Real GetSquared() + { + return ComputeResult(); + } + + Real ComputeResult() + { + if (DistanceSquared >= 0) + { + return DistanceSquared; + } + + + // Compare edges of Triangle[0] to the interior of Triangle[1]. + Real sqrDist = TMathUtil::MaxReal, sqrDistTmp; + Real ratio; + int i0, i1; + for (i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + TSegment3 edge(Triangle[0].V[i0], Triangle[0].V[i1]); + + TDistSegment3Triangle3 queryST(edge, Triangle[1]); + sqrDistTmp = queryST.GetSquared(); + if (sqrDistTmp < sqrDist) + { + TriangleClosest[0] = queryST.SegmentClosest; + TriangleClosest[1] = queryST.TriangleClosest; + sqrDist = sqrDistTmp; + + ratio = queryST.SegmentParameter / edge.Extent; + TriangleBaryCoords[0][i0] = (0.5) * (1 - ratio); + TriangleBaryCoords[0][i1] = 1 - TriangleBaryCoords[0][i0]; + TriangleBaryCoords[0][3 - i0 - i1] = 0; + TriangleBaryCoords[1] = queryST.TriangleBaryCoords; + + if (sqrDist <= TMathUtil::ZeroTolerance) + { + DistanceSquared = 0; + return 0; + } + } + } + + // Compare edges of Triangle[1] to the interior of Triangle[0]. + for (i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + TSegment3 edge(Triangle[1].V[i0], Triangle[1].V[i1]); + + TDistSegment3Triangle3 queryST(edge, Triangle[0]); + sqrDistTmp = queryST.GetSquared(); + if (sqrDistTmp < sqrDist) + { + TriangleClosest[0] = queryST.SegmentClosest; + TriangleClosest[1] = queryST.TriangleClosest; + sqrDist = sqrDistTmp; + + ratio = queryST.SegmentParameter / edge.Extent; + TriangleBaryCoords[1][i0] = (0.5) * (1 - ratio); + TriangleBaryCoords[1][i1] = 1 - TriangleBaryCoords[1][i0]; + TriangleBaryCoords[1][3 - i0 - i1] = 0; + TriangleBaryCoords[0] = queryST.TriangleBaryCoords; + + if (sqrDist <= TMathUtil::ZeroTolerance) + { + DistanceSquared = 0; + return 0; + } + } + } + + + DistanceSquared = sqrDist; + return DistanceSquared; + } +}; + +typedef TDistTriangle3Triangle3 FDistTriangle3Triangle3f; +typedef TDistTriangle3Triangle3 FDistTriangle3Triangle3d; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/FrameTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/FrameTypes.h new file mode 100644 index 000000000000..92696261c6a1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/FrameTypes.h @@ -0,0 +1,377 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#pragma once + +#include "VectorTypes.h" +#include "VectorUtil.h" +#include "RayTypes.h" +#include "Quaternion.h" + +/** + * TFrame3 is an object that represents an oriented 3D coordinate frame, ie orthogonal X/Y/Z axes at a point in space. + * One can think of this Frame as a local coordinate space measured along these axes. + * Functions are provided to map geometric objects to/from the Frame coordinate space. + * + * Internally the representation is the same as an FTransform, except a Frame has no Scale. + */ +template +struct TFrame3 +{ + /** + * Origin of the frame + */ + FVector3 Origin; + + /** + * Rotation of the frame. Think of this as the rotation of the unit X/Y/Z axes to the 3D frame axes. + */ + TQuaternion Rotation; + + /** + * Construct a frame positioned at (0,0,0) aligned to the unit axes + */ + TFrame3() + { + Origin = FVector3::Zero(); + Rotation = TQuaternion::Identity(); + } + + /** + * Construct a frame at the given Origin aligned to the unit axes + */ + TFrame3(const FVector3& OriginIn) + { + Origin = OriginIn; + Rotation = TQuaternion::Identity(); + } + + /** + * Construct a Frame from the given Origin and Rotation + */ + TFrame3(const FVector3& OriginIn, const TQuaternion RotationIn) + { + Origin = OriginIn; + Rotation = RotationIn; + } + + /** + * Construct a frame with the Z axis aligned to a target axis + * @param OriginIn origin of frame + * @param SetZ target Z axis + */ + TFrame3(const FVector3& OriginIn, const FVector3& SetZ) + { + Origin = OriginIn; + Rotation.SetFromTo(FVector3::UnitZ(), SetZ); + } + + /** + * Construct Frame from X/Y/Z axis vectors. Vectors must be mutually orthogonal. + * @param OriginIn origin of frame + * @param X desired X axis of frame + * @param Y desired Y axis of frame + * @param Z desired Z axis of frame + */ + TFrame3(const FVector3& OriginIn, const FVector3& X, const FVector3& Y, const FVector3& Z) + { + Origin = OriginIn; + Rotation = TQuaternion( TMatrix3(X, Y, Z, false) ); + } + + /** Construct a Frame from an FTransform */ + TFrame3(const FTransform& Transform) + { + Origin = Transform.GetTranslation(); + Rotation = Transform.GetRotation(); + } + + + /** + * @param AxisIndex index of axis of frame, either 0, 1, or 2 + * @return axis vector + */ + FVector3 GetAxis(int AxisIndex) const + { + switch (AxisIndex) + { + case 0: + return Rotation.AxisX(); + case 1: + return Rotation.AxisY(); + case 2: + return Rotation.AxisZ(); + default: + checkNoEntry(); + return FVector3::Zero(); // compiler demands a return value + } + } + + /** @return X axis of frame (axis 0) */ + FVector3 X() const + { + return Rotation.AxisX(); + } + + /** @return Y axis of frame (axis 1) */ + FVector3 Y() const + { + return Rotation.AxisY(); + } + + /** @return Z axis of frame (axis 2) */ + FVector3 Z() const + { + return Rotation.AxisZ(); + } + + /** @return conversion of this Frame to FTransform */ + FTransform ToFTransform() const + { + return FTransform(Rotation, Origin); + } + + + /** @return point at distances along frame axes */ + FVector3 PointAt(RealType X, RealType Y, RealType Z) const + { + return Rotation * FVector3(X,Y,Z) + Origin; + } + + /** @return point at distances along frame axes */ + FVector3 PointAt(const FVector3& Point) const + { + return Rotation * FVector3(Point.X, Point.Y, Point.Z) + Origin; + } + + /** @return input Point transformed into local coordinate system of Frame */ + FVector3 ToFramePoint(const FVector3& Point) const + { + return Rotation.InverseMultiply((Point-Origin)); + } + /** @return input Point transformed from local coordinate system of Frame into "World" coordinate system */ + FVector3 FromFramePoint(const FVector3& Point) const + { + return Rotation * Point + Origin; + } + + + /** @return input Vector transformed into local coordinate system of Frame */ + FVector3 ToFrameVector(const FVector3& Vector) const + { + return Rotation.InverseMultiply(Vector); + } + /** @return input Vector transformed from local coordinate system of Frame into "World" coordinate system */ + FVector3 FromFrameVector(const FVector3& Vector) const + { + return Rotation * Vector; + } + + + /** @return input Quaternion transformed into local coordinate system of Frame */ + TQuaternion ToFrame(const TQuaternion& Quat) const + { + return Rotation.Inverse() * Quat; + } + /** @return input Quaternion transformed from local coordinate system of Frame into "World" coordinate system */ + TQuaternion FromFrame(const TQuaternion& Quat) const + { + return Rotation * Quat; + } + + + /** @return input Ray transformed into local coordinate system of Frame */ + TRay3 ToFrame(const TRay3& Ray) const + { + return TRay3(ToFramePoint(Ray.Origin), ToFrameVector(Ray.Direction)); + } + /** @return input Ray transformed from local coordinate system of Frame into "World" coordinate system */ + TRay3 FromFrame(const TRay3& Ray) const + { + return TRay3(ToFramePoint(Ray.Origin), ToFrameVector(Ray.Direction)); + } + + + /** @return input Frame transformed into local coordinate system of this Frame */ + TFrame3 ToFrame(const TFrame3& Frame) const + { + return TFrame3(ToFramePoint(Frame.Origin), ToFrame(Frame.Rotation)); + } + /** @return input Frame transformed from local coordinate system of this Frame into "World" coordinate system */ + TFrame3 FromFrame(const TFrame3& Frame) const + { + return TFrame3(ToFramePoint(Frame.Origin), FromFrame(Frame.Rotation)); + } + + + + + /** + * Project 3D point into plane and convert to UV coordinates in that plane + * @param Pos 3D position + * @param PlaneNormalAxis which plane to project onto, identified by perpendicular normal. Default is 2, ie normal is Z, plane is (X,Y) + * @return 2D coordinates in UV plane, relative to origin + */ + FVector2 ToPlaneUV(const FVector3& Pos, int PlaneNormalAxis) const + { + int Axis0 = 0, Axis1 = 1; + if (PlaneNormalAxis == 0) + { + Axis0 = 2; + } + else if (PlaneNormalAxis == 1) + { + Axis1 = 2; + } + FVector3 LocalPos = Pos - Origin; + RealType U = LocalPos.Dot(GetAxis(Axis0)); + RealType V = LocalPos.Dot(GetAxis(Axis1)); + return FVector2(U, V); + } + + + + /** + * Map a point from local UV plane coordinates to the corresponding 3D point in one of the planes of the frame + * @param PosUV local UV plane coordinates + * @param PlaneNormalAxis which plane to map to, identified by perpendicular normal. Default is 2, ie normal is Z, plane is (X,Y) + * @return 3D coordinates in frame's plane (including Origin translation) + */ + FVector3 FromPlaneUV(const FVector2& PosUV, int PlaneNormalAxis) const + { + FVector3 PlanePos(PosUV[0], PosUV[1], 0); + if (PlaneNormalAxis == 0) + { + PlanePos[0] = 0; PlanePos[2] = PosUV[0]; + } + else if (PlaneNormalAxis == 1) + { + PlanePos[1] = 0; PlanePos[2] = PosUV[1]; + } + return Rotation*PlanePos + Origin; + } + + + + /** + * Project a point onto one of the planes of the frame + * @param Pos 3D position + * @param PlaneNormalAxis which plane to project onto, identified by perpendicular normal. Default is 2, ie normal is Z, plane is (X,Y) + * @return 3D coordinate in the plane + */ + FVector3 ToPlane(const FVector3& Pos, int PlaneNormalAxis) const + { + FVector3 Normal = GetAxis(PlaneNormalAxis); + FVector3 LocalVec = Pos - Origin; + RealType SignedDist = LocalVec.Dot(Normal); + return Pos - SignedDist * Normal; + } + + + + + /** + * Rotate this frame by given quaternion + */ + void Rotate(const TQuaternion& Quat) + { + Rotation = Quat * Rotation; + } + + + /** + * transform this frame by the given transform + */ + void Transform(const FTransform& XForm) + { + Origin = (FVector3)XForm.TransformPosition((FVector3f)Origin); + Rotation = TQuaternion(XForm.GetRotation()) * Rotation; + } + + + /** + * Align an axis of this frame with a target direction + * @param AxisIndex which axis to align + * @param ToDirection target direction + */ + void AlignAxis(int AxisIndex, const FVector3& ToDirection) + { + TQuaternion RelRotation(GetAxis(AxisIndex), ToDirection); + Rotate(RelRotation); + } + + + /** + * Compute rotation around vector that best-aligns axis of frame with target direction + * @param AxisIndex which axis to try to align + * @param ToDirection target direction + * @param AroundVector rotation is constrained to be around this vector (ie this direction in frame stays constant) + */ + void ConstrainedAlignAxis(int AxisIndex, const FVector3& ToDirection, const FVector3& AroundVector) + { + //@todo PlaneAngleSigned does acos() and then SetAxisAngleD() does cos/sin...can we optimize this? + FVector3 AxisVec = GetAxis(AxisIndex); + RealType AngleDeg = VectorUtil::PlaneAngleSignedD(AxisVec, ToDirection, AroundVector); + TQuaternion RelRotation; + RelRotation.SetAxisAngleD(AroundVector, AngleDeg); + Rotate(RelRotation); + } + + + /** + * Compute rotation around NormalAxis that best-aligns one of the other two frame axes with either given UpAxis or FallbackAxis + * (FallbackAxis is required if Dot(NormalAxis,UpAxis) > UpDotTolerance, ie if the Normal and Up directions are too closely aligned. + * Basically this divides direction-sphere into three regions - polar caps with size defined by UpDotTolerance, and + * a wide equator band covering the rest. When crossing between these regions the alignment has a discontinuity. + * It is impossible to avoid this discontinuity because it is impossible to comb a sphere. + * @param PerpAxis1 Index of first axis orthogonal to NormalAxis + * @param PerpAxis2 Index of second axis orthogonal to NormalAxis + * @param NormalAxis Axis of frame to rotate around + * @param UpAxis Target axis in equator region, defaults to UnitZ + * @param FallbackAxis Target axis in polar region, defaults to UnitX + * @param UpDotTolerance defaults to cos(45), ie flip between regions happens roughly half way to poles + */ + void ConstrainedAlignPerpAxes(int PerpAxis1 = 0, int PerpAxis2 = 1, int NormalAxis = 2, + const FVector3& UpAxis = FVector3::UnitZ(), + const FVector3& FallbackAxis = FVector3::UnitX(), + RealType UpDotTolerance = (RealType)0.707) + { + check(PerpAxis1 != PerpAxis2 && PerpAxis1 != NormalAxis && PerpAxis2 != NormalAxis); + const FVector3 NormalVec = GetAxis(NormalAxis); + + // decide if we should use Fallback (polar-cap) axis or main (equator-region) axis + const FVector3& TargetAxis = + (TMathUtil::Abs(NormalVec.Dot(UpAxis)) > UpDotTolerance) ? + FallbackAxis : UpAxis; + + // figure out which PerpAxis is closer to target, and align that one to +/- TargetAxis (whichever is smaller rotation) + FVector2 Dots(GetAxis(PerpAxis1).Dot(TargetAxis), GetAxis(PerpAxis2).Dot(TargetAxis)); + int UseAxis = (TMathUtil::Abs(Dots.X) > TMathUtil::Abs(Dots.Y)) ? 0 : 1; + RealType UseSign = Dots[UseAxis] < 0 ? -1 : 1; + ConstrainedAlignAxis(UseAxis, UseSign*TargetAxis, NormalVec); + } + + + /** + * Compute intersection of ray with plane defined by frame origin and axis as normal + * @param RayOrigin origin of ray + * @param RayDirection direction of ray + * @param PlaneNormalAxis which axis of frame to use as plane normal + * @return intersection point, or FVector3::Max() if ray is parallel to plane + */ + FVector3 RayPlaneIntersection(const FVector3& RayOrigin, const FVector3& RayDirection, int PlaneNormalAxis) + { + FVector3 Normal = GetAxis(PlaneNormalAxis); + RealType PlaneD = -Origin.Dot(Normal); + RealType NormalDot = RayDirection.Dot(Normal); + if (VectorUtil::EpsilonEqual(NormalDot, (RealType)0, TMathUtil::ZeroTolerance)) + return FVector3::Max(); + RealType t = -( RayOrigin.Dot(Normal) + PlaneD) / NormalDot; + return RayOrigin + t * RayDirection; + } + +}; + +typedef TFrame3 FFrame3f; +typedef TFrame3 FFrame3d; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/GeometricObjectsModule.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/GeometricObjectsModule.h new file mode 100644 index 000000000000..1238f56b0bd5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/GeometricObjectsModule.h @@ -0,0 +1,15 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FGeometricObjectsModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/GeometryTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/GeometryTypes.h new file mode 100644 index 000000000000..e651d8a226b6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/GeometryTypes.h @@ -0,0 +1,169 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +/** + * EMeshResult is returned by various mesh/graph operations to either indicate success, + * or communicate which type of error ocurred (some errors are recoverable, and some not). + */ +enum class EMeshResult +{ + Ok = 0, + Failed_NotAVertex = 1, + Failed_NotATriangle = 2, + Failed_NotAnEdge = 3, + + Failed_BrokenTopology = 10, + Failed_HitValenceLimit = 11, + + Failed_IsBoundaryEdge = 20, + Failed_FlippedEdgeExists = 21, + Failed_IsBowtieVertex = 22, + Failed_InvalidNeighbourhood = 23, // these are all failures for CollapseEdge + Failed_FoundDuplicateTriangle = 24, + Failed_CollapseTetrahedron = 25, + Failed_CollapseTriangle = 26, + Failed_NotABoundaryEdge = 27, + Failed_SameOrientation = 28, + + Failed_WouldCreateBowtie = 30, + Failed_VertexAlreadyExists = 31, + Failed_CannotAllocateVertex = 32, + Failed_VertexStillReferenced = 33, + + Failed_WouldCreateNonmanifoldEdge = 50, + Failed_TriangleAlreadyExists = 51, + Failed_CannotAllocateTriangle = 52, + + Failed_UnrecoverableError = 1000 +}; + + + +/** + * EOperationValidationResult is meant to be returned by Validate() functions of + * Operation classes (eg like ExtrudeMesh, etc) to indicate whether the operation + * can be successfully applied. + */ +enum class EOperationValidationResult +{ + Ok = 0, + Failed_UnknownReason = 1, + + Failed_InvalidTopology = 2 +}; + + +/** + * EValidityCheckFailMode is passed to CheckValidity() functions of various classes + * to specify how validity checks should fail. + */ +enum class EValidityCheckFailMode +{ + /** Function returns false if a failure is encountered */ + ReturnOnly = 0, + /** Function check()'s if a failure is encountered */ + Check = 1, + /** Function ensure()'s if a failure is encountered */ + Ensure = 2 +}; + + + + + +/** + * TIndexMap stores mappings between indices, which are assumed to be an integer type. + * Both forward and backward mapping are stored + * + * @todo make either mapping optional + * @todo optionally support using flat arrays instead of TMaps + * @todo constructors that pick between flat array and TMap modes depending on potential size/sparsity of mapped range + * @todo identity and shift modes that don't actually store anything + */ +template +struct TIndexMap +{ +protected: + TMap ForwardMap; + TMap ReverseMap; + bool bWantForward; + bool bWantReverse; + IntType InvalidID; + +public: + + TIndexMap() + { + bWantForward = bWantReverse = true; + InvalidID = (IntType)-9999999; + } + + void Reset() + { + ForwardMap.Reset(); + ReverseMap.Reset(); + } + + /** @return the value used to indicate "invalid" in the mapping */ + inline IntType GetInvalidID() const { return InvalidID; } + + TMap& GetForwardMap() { return ForwardMap; } + const TMap& GetForwardMap() const { return ForwardMap; } + + TMap& GetReverseMap() { return ReverseMap; } + const TMap& GetReverseMap() const { return ReverseMap; } + + /** add mapping from one index to another */ + inline void Add(IntType FromID, IntType ToID) + { + ForwardMap.Add(FromID, ToID); + ReverseMap.Add(ToID, FromID); + } + + /** @return true if we can map forward from this value */ + inline bool ContainsFrom(IntType FromID) const + { + check(bWantForward); + return ForwardMap.Contains(FromID); + } + + /** @return true if we can reverse-map from this value */ + inline bool ContainsTo(IntType ToID) const + { + check(bWantReverse); + return ReverseMap.Contains(ToID); + } + + + /** @return forward-map of input value */ + inline IntType GetTo(IntType FromID) const + { + check(bWantForward); + const IntType* FoundVal = ForwardMap.Find(FromID); + return (FoundVal == nullptr) ? InvalidID : *FoundVal; + } + + /** @return reverse-map of input value */ + inline IntType GetFrom(IntType ToID) const + { + check(bWantReverse); + const IntType* FoundVal = ReverseMap.Find(ToID); + return (FoundVal == nullptr) ? InvalidID : *FoundVal; + } + + void Reserve(int NumElements) + { + if (bWantForward) + { + ForwardMap.Reserve(NumElements); + } + if (bWantReverse) + { + ReverseMap.Reserve(NumElements); + } + } +}; +typedef TIndexMap FIndexMapi; + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/IndexTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/IndexTypes.h new file mode 100644 index 000000000000..a707a623d154 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/IndexTypes.h @@ -0,0 +1,229 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Math/IntVector.h" +#include + + +namespace IndexConstants +{ + constexpr int InvalidID = -1; +} + +/** + * 2-index tuple. Ported from g3Sharp library, with the intention of + * maintaining compatibility with existing g3Sharp code. Has an API + * similar to WildMagic, GTEngine, Eigen, etc. + */ +struct FIndex2i +{ + int A, B; + + FIndex2i() + { + } + FIndex2i(int ValA, int ValB) + { + this->A = ValA; + this->B = ValB; + } + + static FIndex2i Zero() + { + return FIndex2i(0, 0); + } + static FIndex2i Max() + { + return FIndex2i(TNumericLimits::Max(), TNumericLimits::Max()); + } + static FIndex2i Invalid() + { + return FIndex2i(IndexConstants::InvalidID, IndexConstants::InvalidID); + } + + int& operator[](int Idx) + { + return (&A)[Idx]; + } + const int& operator[](int Idx) const + { + return (&A)[Idx]; + } + + inline bool operator==(const FIndex2i& Other) const + { + return A == Other.A && B == Other.B; + } + + inline bool operator!=(const FIndex2i& Other) const + { + return A != Other.A || B != Other.B; + } + + inline void Swap() + { + int tmp = A; + A = B; + B = tmp; + } +}; + +FORCEINLINE uint32 GetTypeHash(const FIndex2i& Index) +{ + // (this is how FIntVector and all the other FVectors do their hash functions) + // Note: this assumes there's no padding that could contain uncompared data. + return FCrc::MemCrc_DEPRECATED(&Index, sizeof(FIndex2i)); +} + + + +/** + * 3-index tuple. Ported from g3Sharp library, with the intention of + * maintaining compatibility with existing g3Sharp code. Has an API + * similar to WildMagic, GTEngine, Eigen, etc. + * + * Implicit casts to/from FIntVector are defined. + */ +struct FIndex3i +{ + int A, B, C; + + FIndex3i() + { + } + FIndex3i(int ValA, int ValB, int ValC) + { + this->A = ValA; + this->B = ValB; + this->C = ValC; + } + + static FIndex3i Zero() + { + return FIndex3i(0, 0, 0); + } + static FIndex3i Max() + { + return FIndex3i(TNumericLimits::Max(), TNumericLimits::Max(), TNumericLimits::Max()); + } + static FIndex3i Invalid() + { + return FIndex3i(IndexConstants::InvalidID, IndexConstants::InvalidID, IndexConstants::InvalidID); + } + + int& operator[](int Idx) + { + return (&A)[Idx]; + } + const int& operator[](int Idx) const + { + return (&A)[Idx]; + } + + bool operator==(const FIndex3i& Other) const + { + return A == Other.A && B == Other.B && C == Other.C; + } + + bool operator!=(const FIndex3i& Other) const + { + return A != Other.A || B != Other.B || C != Other.C; + } + + int IndexOf(int Value) const + { + return (A == Value) ? 0 : ((B == Value) ? 1 : (C == Value ? 2 : -1)); + } + + int Contains(int Value) const + { + return (A == Value) || (B == Value) || (C == Value); + } + + operator FIntVector() const + { + return FIntVector(A, B, C); + } + FIndex3i(const FIntVector& Vec) + { + A = Vec.X; + B = Vec.Y; + C = Vec.Z; + } +}; + +FORCEINLINE uint32 GetTypeHash(const FIndex3i& Index) +{ + // (this is how FIntVector and all the other FVectors do their hash functions) + // Note: this assumes there's no padding that could contain uncompared data. + return FCrc::MemCrc_DEPRECATED(&Index, sizeof(FIndex3i)); +} + + + +/** + * 4-index tuple. Ported from g3Sharp library, with the intention of + * maintaining compatibility with existing g3Sharp code. Has an API + * similar to WildMagic, GTEngine, Eigen, etc. + */ + +struct FIndex4i +{ + int A, B, C, D; + + FIndex4i() + { + } + FIndex4i(int ValA, int ValB, int ValC, int ValD) + { + this->A = ValA; + this->B = ValB; + this->C = ValC; + this->D = ValD; + } + + static FIndex4i Zero() + { + return FIndex4i(0, 0, 0, 0); + } + static FIndex4i Max() + { + return FIndex4i(TNumericLimits::Max(), TNumericLimits::Max(), TNumericLimits::Max(), TNumericLimits::Max()); + } + static FIndex4i Invalid() + { + return FIndex4i(IndexConstants::InvalidID, IndexConstants::InvalidID, IndexConstants::InvalidID, IndexConstants::InvalidID); + } + + int& operator[](int Idx) + { + return (&A)[Idx]; + } + const int& operator[](int Idx) const + { + return (&A)[Idx]; + } + + bool operator==(const FIndex4i& Other) const + { + return A == Other.A && B == Other.B && C == Other.C && D == Other.D; + } + + bool operator!=(const FIndex4i& Other) const + { + return A != Other.A || B != Other.B || C != Other.C || D != Other.D; + } + + bool Contains(int Idx) const + { + return A == Idx || B == Idx || C == Idx || D == Idx; + } +}; + +FORCEINLINE uint32 GetTypeHash(const FIndex4i& Index) +{ + // (this is how FIntVector and all the other FVectors do their hash functions) + // Note: this assumes there's no padding that could contain uncompared data. + return FCrc::MemCrc_DEPRECATED(&Index, sizeof(FIndex4i)); +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntersectionUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntersectionUtil.h new file mode 100644 index 000000000000..02459e8793a3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntersectionUtil.h @@ -0,0 +1,267 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "VectorTypes.h" +#include "BoxTypes.h" + + + +/** + * FLinearIntersection contains intersection information returned by linear/primitive intersection functions + */ +struct FLinearIntersection +{ + bool intersects; + int numIntersections; // 0, 1, or 2 + FInterval1d parameter; // t-values along ray +}; + + +/** + * IntersectionUtil contains functions to compute intersections between geometric objects + */ +namespace IntersectionUtil +{ + + template + bool RayTriangleTest(const FVector3& RayOrigin, const FVector3& RayDirection, const FVector3& V0, const FVector3& V1, const FVector3& V2) + { + // same code as IntrRay3Triangle3, but can be called w/o constructing additional data structures + + // Compute the offset origin, edges, and normal. + FVector3 diff = RayOrigin - V0; + FVector3 edge1 = V1 - V0; + FVector3 edge2 = V2 - V0; + FVector3 normal = edge1.Cross(edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + RealType DdN = RayDirection.Dot(normal); + RealType sign; + if (DdN > TMathUtil::ZeroTolerance) + { + sign = 1; + } + else if (DdN < -TMathUtil::ZeroTolerance) + { + sign = -1; + DdN = -DdN; + } + else + { + // Ray and triangle are parallel, call it a "no intersection" + // even if the ray does intersect. + return false; + } + + RealType DdQxE2 = sign * RayDirection.Dot(diff.Cross(edge2)); + if (DdQxE2 >= 0) + { + RealType DdE1xQ = sign * RayDirection.Dot(edge1.Cross(diff)); + if (DdE1xQ >= 0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check if ray does. + RealType QdN = -sign * diff.Dot(normal); + if (QdN >= 0) + { + // Ray intersects triangle. + return true; + } + // else: t < 0, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + return false; + + } + + + + /** + * Test if line intersects sphere + * @return true if line intersects sphere + */ + template + bool LineSphereTest( + const FVector3& LineOrigin, + const FVector3& LineDirection, + const FVector3& SphereCenter, + RealType SphereRadius) + { + // [RMS] adapted from GeometricTools GTEngine + // https://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrLine3Sphere3.h + + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to obtain a + // quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where a1 = D^T*(P-C), + // and a0 = (P-C)^T*(P-C)-1. + + FVector3 diff = LineOrigin - SphereCenter; + RealType a0 = diff.SquaredLength() - SphereRadius * SphereRadius; + RealType a1 = LineDirection.Dot(diff); + + // Intersection occurs when Q(t) has real roots. + RealType discr = a1 * a1 - a0; + return (discr >= 0); + } + + + /** + * Intersect line with sphere and return intersection info (# hits, ray parameters) + * @return true if line intersects sphere + */ + template + bool LineSphereIntersection( + const FVector3& LineOrigin, + const FVector3& LineDirection, + const FVector3& SphereCenter, + RealType SphereRadius, + FLinearIntersection& ResultOut) + { + // [RMS] adapted from GeometricTools GTEngine + // https://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrLine3Sphere3.h + + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to obtain a + // quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where a1 = D^T*(P-C), + // and a0 = (P-C)^T*(P-C)-1. + FVector3 diff = LineOrigin - SphereCenter; + RealType a0 = diff.SquaredLength() - SphereRadius * SphereRadius; + RealType a1 = LineDirection.Dot(diff); + + // Intersection occurs when Q(t) has real roots. + RealType discr = a1 * a1 - a0; + if (discr > 0) + { + ResultOut.intersects = true; + ResultOut.numIntersections = 2; + RealType root = FMath::Sqrt(discr); + ResultOut.parameter.Min = -a1 - root; + ResultOut.parameter.Max = -a1 + root; + } + else if (discr < 0) + { + ResultOut.intersects = false; + ResultOut.numIntersections = 0; + } + else + { + ResultOut.intersects = true; + ResultOut.numIntersections = 1; + ResultOut.parameter.Min = -a1; + ResultOut.parameter.Max = -a1; + } + return ResultOut.intersects; + } + + template + FLinearIntersection LineSphereIntersection( + const FVector3& LineOrigin, + const FVector3& LineDirection, + const FVector3& SphereCenter, + RealType SphereRadius) + { + FLinearIntersection result; + LineSphereIntersection(LineOrigin, LineDirection, SphereCenter, SphereRadius, result); + return result; + } + + + + /** + * @return true if ray intersects sphere + */ + template + bool RaySphereTest( + const FVector3& RayOrigin, + const FVector3& RayDirection, + const FVector3& SphereCenter, + RealType SphereRadius) + { + // [RMS] adapted from GeometricTools GTEngine + // https://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Sphere3.h + + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to obtain a + // quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where a1 = D^T*(P-C), + // and a0 = (P-C)^T*(P-C)-1. + + FVector3 diff = RayOrigin - SphereCenter; + RealType a0 = diff.SquaredLength() - SphereRadius * SphereRadius; + if (a0 <= 0) + { + return true; // P is inside the sphere. + } + // else: P is outside the sphere + RealType a1 = RayDirection.Dot(diff); + if (a1 >= 0) + { + return false; + } + + // Intersection occurs when Q(t) has RealType roots. + RealType discr = a1 * a1 - a0; + return (discr >= 0); + } + + + /** + * Intersect ray with sphere and return intersection info (# hits, ray parameters) + * @return true if ray intersects sphere + */ + template + bool RaySphereIntersection( + const FVector3& RayOrigin, + const FVector3& RayDirection, + const FVector3& SphereCenter, + RealType SphereRadius, + FLinearIntersection& Result) + { + // [RMS] adapted from GeometricTools GTEngine + // https://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Sphere3.h + + RaySphereIntersection(RayOrigin, RayDirection, SphereCenter, SphereRadius, Result); + if (Result.intersects) + { + // The line containing the ray intersects the sphere; the t-interval + // is [t0,t1]. The ray intersects the sphere as long as [t0,t1] + // overlaps the ray t-interval [0,+infinity). + if (Result.parameter.Max < 0) + { + Result.intersects = false; + Result.numIntersections = 0; + } + else if (Result.parameter.Min < 0) + { + Result.numIntersections--; + Result.parameter.Min = Result.parameter.Max; + } + } + return Result.intersects; + } + + template + FLinearIntersection RaySphereIntersection( + const FVector3& RayOrigin, + const FVector3& RayDirection, + const FVector3& SphereCenter, + RealType SphereRadius) + { + FLinearIntersection result; + LineSphereIntersection(RayOrigin, RayDirection, SphereCenter, SphereRadius, result); + return result; + } + +}; + + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/Intersector1.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/Intersector1.h new file mode 100644 index 000000000000..2bcebe30d937 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/Intersector1.h @@ -0,0 +1,108 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +/*============================================================================= + Intersector1.h ported from WildMagic5 +=============================================================================*/ + +#pragma once + +#include "Math/UnrealMath.h" +#include "VectorTypes.h" +#include "BoxTypes.h" + +/** + * TIntersector1 computes the intersection of one-dimensional intervals [u0,u1] and [v0,v1]. + * The end points of the input intervals must be ordered u0 <= u1 and v0 <= v1. + * Infinite and degenerate intervals are allowed. + */ +template +class TIntersector1 +{ +private: + /** intersection point/interval, access via GetIntersection */ + TInterval1 Intersections; + +public: + /** First interval */ + TInterval1 U; + /** Second interval */ + TInterval1 V; + + /** + * Number of intersections found. + * 0: intervals do not overlap + * 1: intervals are touching at a single point + * 2: intervals intersect in an interval + */ + int NumIntersections = 0; + + TIntersector1(RealType u0, RealType u1, RealType v0, RealType v1) : Intersections(0,0), U(u0, u1), V(v0, v1) + { + check(u0 <= u1); + check(v0 <= v1); + } + + TIntersector1(const TInterval1& u, const TInterval1& v) : Intersections(0, 0), U(u), V(v) + { + } + + /** + * Fast check to see if intervals intersect, but does not calculate intersection interval + * @return true if intervals intersect + */ + bool Test() const + { + return U.Min <= V.Max && U.Max >= V.Min; + } + + /** + * @return first or second point of intersection interval + */ + RealType GetIntersection(int i) + { + return i == 0 ? Intersections.Min : Intersections.Max; + } + + /** + * Calculate the intersection interval + * @return true if the intervals intersect + */ + bool Find() + { + if (U.Max < V.Min || U.Min > V.Max) + { + NumIntersections = 0; + } + else if (U.Max > V.Min) + { + if (U.Min < V.Max) + { + NumIntersections = 2; + Intersections.Min = (U.Min < V.Min ? V.Min : U.Min); + Intersections.Max = (U.Max > V.Max ? V.Max : U.Max); + if (Intersections.Min == Intersections.Max) + { + NumIntersections = 1; + } + } + else + { + // U.Min == V.Max + NumIntersections = 1; + Intersections.Min = U.Min; + } + } + else + { + // U.Max == V.Min + NumIntersections = 1; + Intersections.Min = U.Max; + } + + return NumIntersections > 0; + } + +}; + +typedef TIntersector1 FIntersector1d; +typedef TIntersector1 FIntersector1f; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrLine2Line2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrLine2Line2.h new file mode 100644 index 000000000000..ad2e4e94af3a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrLine2Line2.h @@ -0,0 +1,175 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp IntrLine2Line2 + +#pragma once + +#include "VectorTypes.h" +#include "LineTypes.h" +#include "VectorUtil.h" // for EIntersectionType + +/** + * Compute intersection between two 2D lines + */ +template +class TIntrLine2Line2 +{ +protected: + // input data + TLine2 Line1; + TLine2 Line2; + RealType dotThresh = TMathUtil::ZeroTolerance; + +public: + // result data + FVector2 Point; + RealType Segment1Parameter; + RealType Segment2Parameter; + int Quantity = 0; + EIntersectionResult Result = EIntersectionResult::NotComputed; + EIntersectionType Type = EIntersectionType::Empty; + + + + TIntrLine2Line2(const TLine2& Line1In, const TLine2& Line2In) + : Line1(Line1In), Line2(Line2In) + { + } + + + const TLine2& GetLine1() const + { + return Line1; + } + + void SetLine1(const TLine2& Value) + { + Line1 = Value; + Result = EIntersectionResult::NotComputed; + } + + const TLine2& GetLine2() const + { + return Line2; + } + + void SetLine2(TLine2& Value) + { + Line2 = Value; + Result = EIntersectionResult::NotComputed; + } + + + RealType GetDotThreshold() const + { + return dotThresh; + } + + void SetDotThreshold(RealType Value) + { + dotThresh = FMath::Max(Value, (RealType)0); + Result = EIntersectionResult::NotComputed; + } + + bool IsSimpleIntersection() const + { + return Result == EIntersectionResult::Intersects && Type == EIntersectionType::Point; + } + + + TIntrLine2Line2& Compute() + { + Find(); + return *this; + } + + + bool Find() + { + if (Result != EIntersectionResult::NotComputed) + { + return (Result == EIntersectionResult::Intersects); + } + + // [RMS] if either line direction is not a normalized vector, + // results are garbage, so fail query + if (Line1.Direction.IsNormalized() == false || Line2.Direction.IsNormalized() == false) + { + Type = EIntersectionType::Empty; + Result = EIntersectionResult::InvalidQuery; + return false; + } + + FVector2 s = FVector2::Zero(); + Type = Classify(Line1.Origin, Line1.Direction, + Line2.Origin, Line2.Direction, dotThresh, s); + + if (Type == EIntersectionType::Point) + { + Quantity = 1; + Point = Line1.Origin + s.X*Line1.Direction; + Segment1Parameter = s.X; + Segment2Parameter = s.Y; + } + else if (Type == EIntersectionType::Line) + { + Quantity = std::numeric_limits::max(); + } + else + { + Quantity = 0; + } + + Result = (Type != EIntersectionType::Empty) ? + EIntersectionResult::Intersects : EIntersectionResult::NoIntersection; + return (Result == EIntersectionResult::Intersects); + } + + + + static EIntersectionType Classify( + const FVector2& P0, const FVector2& D0, + const FVector2& P1, const FVector2& D1, + RealType dotThreshold, FVector2& s) + { + // Ensure dotThreshold is nonnegative. + dotThreshold = FMath::Max(dotThreshold, (RealType)0); + + // The intersection of two lines is a solution to P0+s0*D0 = P1+s1*D1. + // Rewrite this as s0*D0 - s1*D1 = P1 - P0 = Q. If D0.Dot(Perp(D1)) = 0, + // the lines are parallel. Additionally, if Q.Dot(Perp(D1)) = 0, the + // lines are the same. If D0.Dot(Perp(D1)) is not zero, then + // s0 = Q.Dot(Perp(D1))/D0.Dot(Perp(D1)) + // produces the point of intersection. Also, + // s1 = Q.Dot(Perp(D0))/D0.Dot(Perp(D1)) + + FVector2 diff = P1 - P0; + RealType D0DotPerpD1 = D0.DotPerp(D1); + if (FMath::Abs(D0DotPerpD1) > dotThreshold) + { + // Lines intersect in a single point. + RealType invD0DotPerpD1 = 1.0 / D0DotPerpD1; + RealType diffDotPerpD0 = diff.DotPerp(D0); + RealType diffDotPerpD1 = diff.DotPerp(D1); + s[0] = diffDotPerpD1 * invD0DotPerpD1; + s[1] = diffDotPerpD0 * invD0DotPerpD1; + return EIntersectionType::Point; + } + + // Lines are parallel. + diff.Normalize(); + RealType diffNDotPerpD1 = diff.DotPerp(D1); + if (FMath::Abs(diffNDotPerpD1) <= dotThreshold) + { + // Lines are collinear. + return EIntersectionType::Line; + } + + // Lines are parallel, but distinct. + return EIntersectionType::Empty; + } + +}; + +typedef TIntrLine2Line2 FIntrLine2TLine2d; +typedef TIntrLine2Line2 FIntrLine2Line2f; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrRay3AxisAlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrRay3AxisAlignedBox3.h new file mode 100644 index 000000000000..0c9ded110a5f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrRay3AxisAlignedBox3.h @@ -0,0 +1,220 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of WildMagic IntrRay3Box3, simplified for Axis-Aligned Bounding Box + +#pragma once + +#include "RayTypes.h" +#include "BoxTypes.h" +#include "VectorUtil.h" + + +/** + * Compute intersection between 3D ray and 3D axis-aligned box + */ +template +class TIntrRay3AxisAlignedBox3 +{ +public: + + // @todo port object version + + + + /** + * Test if ray intersects box + * @param Ray query ray + * @param Box query box + * @param ExpandExtents box is expanded by this amount in each direction, useful for dealing with float imprecision + * @return true if ray intersects box + */ + static bool TestIntersection(const TRay3& Ray, const TAxisAlignedBox3& Box, RealType ExpandExtents = 0) + { + FVector3 WdU = FVector3::Zero(); + FVector3 AWdU = FVector3::Zero(); + FVector3 DdU = FVector3::Zero(); + FVector3 ADdU = FVector3::Zero(); + RealType RHS; + + FVector3 diff = Ray.Origin - Box.Center(); + FVector3 extent = Box.Extents() + ExpandExtents; + + WdU.X = Ray.Direction.X; + AWdU.X = FMath::Abs(WdU.X); + DdU.X = diff.X; + ADdU.X = FMath::Abs(DdU.X); + if (ADdU.X > extent.X && DdU.X * WdU.X >= (RealType)0) + { + return false; + } + + WdU.Y = Ray.Direction.Y; + AWdU.Y = FMath::Abs(WdU.Y); + DdU.Y = diff.Y; + ADdU.Y = FMath::Abs(DdU.Y); + if (ADdU.Y > extent.Y && DdU.Y * WdU.Y >= (RealType)0) + { + return false; + } + + WdU.Z = Ray.Direction.Z; + AWdU.Z = FMath::Abs(WdU.Z); + DdU.Z = diff.Z; + ADdU.Z = FMath::Abs(DdU.Z); + if (ADdU.Z > extent.Z && DdU.Z * WdU.Z >= (RealType)0) + { + return false; + } + + FVector3 WxD = Ray.Direction.Cross(diff); + FVector3 AWxDdU = FVector3::Zero(); + + AWxDdU.X = FMath::Abs(WxD.X); + RHS = extent.Y * AWdU.Z + extent.Z * AWdU.Y; + if (AWxDdU.X > RHS) + { + return false; + } + + AWxDdU.Y = FMath::Abs(WxD.Y); + RHS = extent.X * AWdU.Z + extent.Z * AWdU.X; + if (AWxDdU.Y > RHS) + { + return false; + } + + AWxDdU.Z = FMath::Abs(WxD.Z); + RHS = extent.X * AWdU.Y + extent.Y * AWdU.X; + if (AWxDdU.Z > RHS) + { + return false; + } + + return true; + } + + + + /** + * Find intersection of ray with AABB and returns ray T-value of intersection point (or TNumericLimits::Max() on miss) + * @param Ray query ray + * @param Box query box + * @param RayParamOut ray intersect T-value, or TNumericLimits::Max() + * @return true if ray intersects box + */ + static bool FindIntersection(const TRay3& Ray, const TAxisAlignedBox3& Box, RealType& RayParamOut) + { + RealType RayParam0 = 0.0; + RealType RayParam1 = TNumericLimits::Max(); + int Quantity = 0; + FVector3 Point0 = FVector3::Zero(); + FVector3 Point1 = FVector3::Zero(); + EIntersectionType Type = EIntersectionType::Empty; + DoClipping(RayParam0, RayParam1, Ray.Origin, Ray.Direction, Box, + true, Quantity, Point0, Point1, Type); + + if (Type != EIntersectionType::Empty) + { + RayParamOut = RayParam0; + return true; + } + else + { + RayParamOut = TNumericLimits::Max(); + return false; + } + } + + + + +protected: + + // internal functions + + static bool DoClipping(RealType t0, RealType t1, + const FVector3& RayOrigin, const FVector3& RayDirection, + const TAxisAlignedBox3& Box, bool solid, + int& quantity, FVector3& Point0, FVector3& Point1, EIntersectionType& intrType) + { + FVector3 BOrigin = RayOrigin - Box.Center(); + FVector3 extent = Box.Extents(); + + RealType saveT0 = t0, saveT1 = t1; + bool notAllClipped = + Clip(+RayDirection.X, -BOrigin.X - extent.X, t0, t1) && + Clip(-RayDirection.X, +BOrigin.X - extent.X, t0, t1) && + Clip(+RayDirection.Y, -BOrigin.Y - extent.Y, t0, t1) && + Clip(-RayDirection.Y, +BOrigin.Y - extent.Y, t0, t1) && + Clip(+RayDirection.Z, -BOrigin.Z - extent.Z, t0, t1) && + Clip(-RayDirection.Z, +BOrigin.Z - extent.Z, t0, t1); + + if (notAllClipped && (solid || t0 != saveT0 || t1 != saveT1)) + { + if (t1 > t0) + { + intrType = EIntersectionType::Segment; + quantity = 2; + Point0 = RayOrigin + t0 * RayDirection; + Point1 = RayOrigin + t1 * RayDirection; + } + else + { + intrType = EIntersectionType::Point; + quantity = 1; + Point0 = RayOrigin + t0 * RayDirection; + } + } + else + { + quantity = 0; + intrType = EIntersectionType::Empty; + } + + return intrType != EIntersectionType::Empty; + } + + + + + static bool Clip(RealType denom, RealType numer, RealType& t0, RealType& t1) + { + // Return value is 'true' if line segment intersects the current test + // plane. Otherwise 'false' is returned in which case the line segment + // is entirely clipped. + + if (denom > (RealType)0) + { + if (numer > denom*t1) + { + return false; + } + if (numer > denom*t0) + { + t0 = numer / denom; + } + return true; + } + else if (denom < (RealType)0) + { + if (numer > denom*t0) + { + return false; + } + if (numer > denom*t1) + { + t1 = numer / denom; + } + return true; + } + else + { + return numer <= (RealType)0; + } + } + + +}; + +typedef TIntrRay3AxisAlignedBox3 FIntrRay3AxisAlignedBox3f; +typedef TIntrRay3AxisAlignedBox3 FIntrRay3AxisAlignedBox3d; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrRay3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrRay3Triangle3.h new file mode 100644 index 000000000000..1b2fdd695a60 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrRay3Triangle3.h @@ -0,0 +1,172 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of WildMagic IntrRay3Triangle3 + +#pragma once + +#include "VectorTypes.h" +#include "RayTypes.h" +#include "TriangleTypes.h" +#include "VectorUtil.h" + +/** + * Compute intersection between 3D ray and 3D triangle + */ +template +class TIntrRay3Triangle3 +{ +public: + // Input + TRay3 Ray; + TTriangle3 Triangle; + + // Output + Real RayParameter; + FVector3d TriangleBaryCoords; + EIntersectionType IntersectionType; + + + TIntrRay3Triangle3(const TRay3& RayIn, const TTriangle3& TriangleIn) + { + Ray = RayIn; + Triangle = TriangleIn; + } + + + /** + * @return true if ray intersects triangle + */ + bool Test() + { + // Compute the offset origin, edges, and normal. + FVector3 diff = Ray.Origin - Triangle.V[0]; + FVector3 edge1 = Triangle.V[1] - Triangle.V[0]; + FVector3 edge2 = Triangle.V[2] - Triangle.V[0]; + FVector3 normal = edge1.Cross(edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Ray.Direction.Dot(normal); + Real sign; + if (DdN > TMathUtil::ZeroTolerance) + { + sign = (Real)1; + } + else if (DdN < -TMathUtil::ZeroTolerance) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Ray and triangle are parallel, call it a "no intersection" + // even if the ray does intersect. + IntersectionType = EIntersectionType::Empty; + return false; + } + + Real DdQxE2 = sign * Ray.Direction.Dot(diff.Cross(edge2)); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign * Ray.Direction.Dot(edge1.Cross(diff)); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check if ray does. + Real QdN = -sign * diff.Dot(normal); + if (QdN >= (Real)0) + { + // Ray intersects triangle. + IntersectionType = EIntersectionType::Point; + return true; + } + // else: t < 0, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + IntersectionType = EIntersectionType::Empty; + return false; + } + + + /** + * Find intersection point + * @return true if ray intersects triangle + */ + bool Find() + { + // Compute the offset origin, edges, and normal. + FVector3 diff = Ray.Origin - Triangle.V[0]; + FVector3 edge1 = Triangle.V[1] - Triangle.V[0]; + FVector3 edge2 = Triangle.V[2] - Triangle.V[0]; + FVector3 normal = edge1.Cross(edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Ray.Direction.Dot(normal); + Real sign; + if (DdN > TMathUtil::ZeroTolerance) + { + sign = (Real)1; + } + else if (DdN < -TMathUtil::ZeroTolerance) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Ray and triangle are parallel, call it a "no intersection" + // even if the ray does intersect. + IntersectionType = EIntersectionType::Empty; + return false; + } + + Real DdQxE2 = sign * Ray.Direction.Dot(diff.Cross(edge2)); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign * Ray.Direction.Dot(edge1.Cross(diff)); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check if ray does. + Real QdN = -sign * diff.Dot(normal); + if (QdN >= (Real)0) + { + // Ray intersects triangle. + Real inv = ((Real)1) / DdN; + RayParameter = QdN * inv; + TriangleBaryCoords.Y = DdQxE2 * inv; + TriangleBaryCoords.Z = DdE1xQ * inv; + TriangleBaryCoords.X = (Real)1 - TriangleBaryCoords.Y - TriangleBaryCoords.Z; + IntersectionType = EIntersectionType::Point; + return true; + } + // else: t < 0, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + IntersectionType = EIntersectionType::Empty; + return false; + } + +}; + +typedef TIntrRay3Triangle3 FIntrRay3Triangle3f; +typedef TIntrRay3Triangle3 FIntrRay3Triangle3d; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrSegment2Segment2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrSegment2Segment2.h new file mode 100644 index 000000000000..962e1e2c9674 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrSegment2Segment2.h @@ -0,0 +1,246 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of WildMagic IntrSegment2Segment2 + +#pragma once + +#include "SegmentTypes.h" +#include "Intersection/Intersector1.h" +#include "Intersection/IntersectionUtil.h" +#include "Intersection/IntrLine2Line2.h" +#include "MathUtil.h" +#include "VectorTypes.h" +#include "VectorUtil.h" // for EIntersectionType + +// ported from WildMagic5 +// +// +// RealType IntervalThreshold +// The intersection testing uses the center-extent form for line segments. +// If you start with endpoints (Vector2) and create Segment2 +// objects, the conversion to center-extent form can contain small +// numerical round-off errors. Testing for the intersection of two +// segments that share an endpoint might lead to a failure due to the +// round-off errors. To allow for this, you may specify a small positive +// threshold that slightly enlarges the intervals for the segments. The +// default value is zero. +// +// RealType DotThreshold +// The computation for determining whether the linear components are +// parallel might contain small floating-point round-off errors. The +// default threshold is TMathUtil::ZeroTolerance. If you set the value, +// pass in a nonnegative number. +// +// +// The intersection set: Let q = Quantity. The cases are +// +// q = 0: The segments do not intersect. Type is Empty +// +// q = 1: The segments intersect in a single point. Type is Point +// Intersection point is Point0. +// +// q = 2: The segments are collinear and intersect in a segment. +// Type is Segment. Points are Point0 and Point1 + +/** + * Calculate intersection between two 2D line segments + */ +template +class TIntrSegment2Segment2 +{ +protected: + // inputs + TSegment2 Segment1; + TSegment2 Segment2; + RealType IntervalThreshold = 0; + RealType DotThreshold = TMathUtil::ZeroTolerance; + +public: + // outputs + int Quantity = 0; + EIntersectionResult Result = EIntersectionResult::NotComputed; + EIntersectionType Type = EIntersectionType::Empty; + // these values are all on segment 1, unlike many other tests!! + FVector2 Point0; + FVector2 Point1; // only set if Quantity == 2, ie segment overlap + + RealType Parameter0; + RealType Parameter1; // only set if Quantity == 2, ie segment overlap + + + + TIntrSegment2Segment2(const TSegment2& Segment1In, const TSegment2& Segment2In) + : Segment1(Segment1In), Segment2(Segment2In) + { + } + + + const TSegment2& GetSegment1() const + { + return Segment1; + } + + void SetSegment1(const TSegment2& Value) + { + Segment1 = Value; + Result = EIntersectionResult::NotComputed; + } + + const TSegment2& GetSegment2() const + { + return Segment2; + } + + void SetSegment2(const TSegment2& Value) + { + Segment2 = Value; + Result = EIntersectionResult::NotComputed; + } + + RealType GetIntervalThreshold() const + { + return IntervalThreshold; + } + + void SetIntervalThreshold(RealType Value) + { + IntervalThreshold = FMath::Max(Value, (RealType)0); + Result = EIntersectionResult::NotComputed; + } + + RealType GetDotThreshold() const + { + return DotThreshold; + } + + void SetDotThreshold(RealType Value) + { + DotThreshold = FMath::Max(Value, (RealType)0); + Result = EIntersectionResult::NotComputed; + } + + bool IsSimpleIntersection() const + { + return Result == EIntersectionResult::Intersects && Type == EIntersectionType::Point; + } + + + TIntrSegment2Segment2& Compute() + { + Find(); + return *this; + } + + + bool Find() + { + if (Result != EIntersectionResult::NotComputed) + { + return (Result == EIntersectionResult::Intersects); + } + + // [RMS] if either segment direction is not a normalized vector, + // results are garbage, so fail query + if (Segment1.Direction.IsNormalized() == false || Segment2.Direction.IsNormalized() == false) + { + Type = EIntersectionType::Empty; + Result = EIntersectionResult::InvalidQuery; + return false; + } + + + FVector2 s = FVector2::Zero(); + Type = TIntrLine2Line2::Classify(Segment1.Center, Segment1.Direction, + Segment2.Center, Segment2.Direction, + DotThreshold, s); + + if (Type == EIntersectionType::Point) + { + // Test whether the line-line intersection is on the segments. + if (FMath::Abs(s[0]) <= Segment1.Extent + IntervalThreshold + && FMath::Abs(s[1]) <= Segment2.Extent + IntervalThreshold) + { + Quantity = 1; + Point0 = Segment1.Center + s[0] * Segment1.Direction; + Parameter0 = s[0]; + } + else + { + Quantity = 0; + Type = EIntersectionType::Empty; + } + } + else if (Type == EIntersectionType::Line) + { + // Compute the location of Segment1 endpoints relative to segment0. + FVector2 diff = Segment2.Center - Segment1.Center; + RealType t1 = Segment1.Direction.Dot(diff); + RealType tmin = t1 - Segment2.Extent; + RealType tmax = t1 + Segment2.Extent; + TIntersector1 calc(-Segment1.Extent, Segment1.Extent, tmin, tmax); + calc.Find(); + Quantity = calc.NumIntersections; + if (Quantity == 2) + { + Type = EIntersectionType::Segment; + Parameter0 = calc.GetIntersection(0); + Point0 = Segment1.Center + + Parameter0 * Segment1.Direction; + Parameter1 = calc.GetIntersection(1); + Point1 = Segment1.Center + + Parameter1 * Segment1.Direction; + } + else if (Quantity == 1) + { + Type = EIntersectionType::Point; + Parameter0 = calc.GetIntersection(0); + Point0 = Segment1.Center + + Parameter0 * Segment1.Direction; + } + else + { + Type = EIntersectionType::Empty; + } + } + else + { + Quantity = 0; + } + + Result = (Type != EIntersectionType::Empty) ? + EIntersectionResult::Intersects : EIntersectionResult::NoIntersection; + + // [RMS] for debugging... + //SanityCheck(); + + return (Result == EIntersectionResult::Intersects); + } + + +protected: + void SanityCheck() + { + if (Quantity == 0) + { + check(Type == EIntersectionType::Empty); + check(Result == EIntersectionResult::NoIntersection); + } + else if (Quantity == 1) + { + check(Type == EIntersectionType::Point); + check(Segment1.DistanceSquared(Point0) < TMathUtil::ZeroTolerance); + check(Segment2.DistanceSquared(Point0) < TMathUtil::ZeroTolerance); + } + else if (Quantity == 2) + { + check(Type == EIntersectionType::Segment); + check(Segment1.DistanceSquared(Point0) < TMathUtil::ZeroTolerance); + check(Segment1.DistanceSquared(Point1) < TMathUtil::ZeroTolerance); + check(Segment2.DistanceSquared(Point0) < TMathUtil::ZeroTolerance); + check(Segment2.DistanceSquared(Point1) < TMathUtil::ZeroTolerance); + } + } +}; + +typedef TIntrSegment2Segment2 FIntrSegment2Segment2d; +typedef TIntrSegment2Segment2 FIntrSegment2Segment2f; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/LineTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/LineTypes.h new file mode 100644 index 000000000000..dc16ab69ab61 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/LineTypes.h @@ -0,0 +1,215 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// line types ported from WildMagic and geometry3Sharp + +#pragma once + +#include "Math/UnrealMath.h" +#include "VectorTypes.h" + +/** + * TLine2 is a two-dimensional infinite line. + * The line is stored in (Center,Direction) form. + */ +template +struct TLine2 +{ + /** Origin / Center Point of Line */ + FVector2 Origin; + + /** Direction of Line, Normalized */ + FVector2 Direction; + + /** + * Construct default line along X axis + */ + TLine2() + { + Origin = FVector2::Zero(); + Direction = FVector2::UnitX(); + } + + /** + * Construct line with given Origin and Direction + */ + TLine2(const FVector2& OriginIn, const FVector2& DirectionIn) + : Origin(OriginIn), Direction(DirectionIn) + { + } + + + /** + * @return line between two points + */ + static TLine2 FromPoints(const FVector2& Point0, const FVector2& Point1) + { + return TLine2(Point0, (Point1 - Point0).Normalized()); + } + + + /** + * @return point on line at given line parameter value (distance along line from origin) + */ + inline FVector2 PointAt(T LineParameter) const + { + return Origin + LineParameter * Direction; + } + + + /** + * @return line parameter (ie distance from Origin) at nearest point on line to QueryPoint + */ + inline T Project(const FVector2& QueryPoint) const + { + return (QueryPoint - Origin).Dot(Direction); + } + + /** + * @return smallest squared distance from line to QueryPoint + */ + inline T DistanceSquared(const FVector2& QueryPoint) const + { + T ParameterT = (QueryPoint - Origin).Dot(Direction); + FVector2 proj = Origin + ParameterT * Direction; + return (proj - QueryPoint).SquaredLength(); + } + + /** + * @return nearest point on line to QueryPoint + */ + inline FVector2 NearestPoint(const FVector2& QueryPoint) const + { + T ParameterT = (QueryPoint - Origin).Dot(Direction); + return Origin + ParameterT * Direction; + } + + + /** + * @return +1 if QueryPoint is "right" of line, -1 if "left" or 0 if "on" line (up to given tolerance) + */ + inline int WhichSide(const FVector2& QueryPoint, T OnLineTolerance = 0) const + { + T x0 = QueryPoint.X - Origin.X; + T y0 = QueryPoint.Y - Origin.Y; + T x1 = Direction.X; + T y1 = Direction.Y; + T det = x0 * y1 - x1 * y0; + return (det > OnLineTolerance ? +1 : (det < -OnLineTolerance ? -1 : 0)); + } + + + /** + * Calculate intersection point between this line and another one + * @param OtherLine line to test against + * @param IntersectionPointOut intersection point is stored here, if found + * @param ParallelDotTolerance tolerance used to determine if lines are parallel (and hence cannot intersect) + * @return true if lines intersect and IntersectionPointOut was computed + */ + bool IntersectionPoint(const TLine2& OtherLine, FVector2& IntersectionPointOut, T ParallelDotTolerance = TMathUtil::ZeroTolerance) const + { + // see IntrTLine2TLine2 for more detailed explanation + FVector2 diff = OtherLine.Origin - Origin; + T D0DotPerpD1 = Direction.DotPerp(OtherLine.Direction); + if (TMathUtil::Abs(D0DotPerpD1) > ParallelDotTolerance) // TLines intersect in a single point. + { + T invD0DotPerpD1 = ((T)1) / D0DotPerpD1; + T diffDotPerpD1 = diff.DotPerp(OtherLine.Direction); + T s = diffDotPerpD1 * invD0DotPerpD1; + IntersectionPointOut = Origin + s * Direction; + return true; + } + // TLines are parallel. + return false; + } +}; + + +typedef TLine2 FLine2d; +typedef TLine2 FLine2f; + + + + + + +/** + * TLine3 is a three-dimensional infinite line. + * The line is stored in (Center,Direction) form. + */ +template +struct TLine3 +{ + /** Origin / Center Point of Line */ + FVector3 Origin; + + /** Direction of Line, Normalized */ + FVector3 Direction; + + /** + * Construct default line along X axis + */ + TLine3() + { + Origin = FVector3::Zero(); + Direction = FVector3::UnitX(); + } + + /** + * Construct line with given Origin and Direction + */ + TLine3(const FVector3& OriginIn, const FVector3& DirectionIn) + : Origin(OriginIn), Direction(DirectionIn) + { + } + + + /** + * @return line between two points + */ + static TLine3 FromPoints(const FVector3& Point0, const FVector3& Point1) + { + return TLine3(Point0, (Point1 - Point0).Normalized()); + } + + + /** + * @return point on line at given line parameter value (distance along line from origin) + */ + inline FVector3 PointAt(T LineParameter) const + { + return Origin + LineParameter * Direction; + } + + + /** + * @return line parameter (ie distance from Origin) at nearest point on line to QueryPoint + */ + inline T Project(const FVector3& QueryPoint) const + { + return (QueryPoint - Origin).Dot(Direction); + } + + /** + * @return smallest squared distance from line to QueryPoint + */ + inline T DistanceSquared(const FVector3& QueryPoint) const + { + T t = (QueryPoint - Origin).Dot(Direction); + FVector3 proj = Origin + t * Direction; + return (proj - QueryPoint).SquaredLength(); + } + + /** + * @return nearest point on line to QueryPoint + */ + inline FVector3 NearestPoint(const FVector3& QueryPoint) const + { + T ParameterT = (QueryPoint - Origin).Dot(Direction); + return Origin + ParameterT * Direction; + } + + +}; + +typedef TLine3 FLine3d; +typedef TLine3 FLine3f; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MathUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MathUtil.h new file mode 100644 index 000000000000..285ca4e196fd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MathUtil.h @@ -0,0 +1,179 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" // required for GEOMETRICOBJECTS_API macro +#include + +/** + * Math constants and utility functions, templated on float/double type + */ +template +class TMathUtil +{ +public: + /** Machine Epsilon - float approx 1e-7, double approx 2e-16 */ + GEOMETRICOBJECTS_API static const RealType Epsilon; + /** Zero tolerance for math operations (eg like parallel tests) - float 1e-6, double 1e-8 */ + GEOMETRICOBJECTS_API static const RealType ZeroTolerance; + + /** largest possible number for type */ + GEOMETRICOBJECTS_API static const RealType MaxReal; + + /** 3.14159... */ + GEOMETRICOBJECTS_API static const RealType Pi; + GEOMETRICOBJECTS_API static const RealType FourPi; + GEOMETRICOBJECTS_API static const RealType TwoPi; + GEOMETRICOBJECTS_API static const RealType HalfPi; + + /** 1.0 / Pi */ + GEOMETRICOBJECTS_API static const RealType InvPi; + /** 1.0 / (2*Pi) */ + GEOMETRICOBJECTS_API static const RealType InvTwoPi; + + /** pi / 180 */ + GEOMETRICOBJECTS_API static const RealType DegToRad; + /** 180 / pi */ + GEOMETRICOBJECTS_API static const RealType RadToDeg; + + //static const RealType LN_2; + //static const RealType LN_10; + //static const RealType INV_LN_2; + //static const RealType INV_LN_10; + + GEOMETRICOBJECTS_API static const RealType Sqrt2; + GEOMETRICOBJECTS_API static const RealType InvSqrt2; + GEOMETRICOBJECTS_API static const RealType Sqrt3; + GEOMETRICOBJECTS_API static const RealType InvSqrt3; + + static inline bool IsNaN(const RealType Value); + static inline bool IsFinite(const RealType Value); + static inline RealType Abs(const RealType Value); + static inline RealType Clamp(const RealType Value, const RealType ClampMin, const RealType ClampMax); + static inline RealType Sign(const RealType Value); + static inline RealType SignNonZero(const RealType Value); + static inline RealType Max(const RealType A, const RealType B); + static inline RealType Max3(const RealType A, const RealType B, const RealType C); + static inline RealType Min(const RealType A, const RealType B); + static inline RealType Min3(const RealType A, const RealType B, const RealType C); + static inline RealType Sqrt(const RealType Value); + static inline RealType Atan2(const RealType ValueY, const RealType ValueX); + static inline RealType Sin(const RealType Value); + static inline RealType Cos(const RealType Value); + + + + /** + * @return result of Atan2 shifted to [0,2pi] (normal ATan2 returns in range [-pi,pi]) + */ + static inline RealType Atan2Positive(const RealType Y, const RealType X); + +private: + TMathUtil() = delete; +}; +typedef TMathUtil FMathf; +typedef TMathUtil FMathd; + + +template +bool TMathUtil::IsNaN(const RealType Value) +{ + return std::isnan(Value); +} + + +template +bool TMathUtil::IsFinite(const RealType Value) +{ + return std::isfinite(Value); +} + + +template +RealType TMathUtil::Abs(const RealType Value) +{ + return (Value >= (RealType)0) ? Value : -Value; +} + + +template +RealType TMathUtil::Clamp(const RealType Value, const RealType ClampMin, const RealType ClampMax) +{ + return (Value < ClampMin) ? ClampMin : ((Value > ClampMax) ? ClampMax : Value); +} + + +template +RealType TMathUtil::Sign(const RealType Value) +{ + return (Value > (RealType)0) ? (RealType)1 : ((Value < (RealType)0) ? (RealType)-1 : (RealType)0); +} + +template +RealType TMathUtil::SignNonZero(const RealType Value) +{ + return (Value < (RealType)0) ? (RealType)-1 : (RealType)1; +} + +template +RealType TMathUtil::Max(const RealType A, const RealType B) +{ + return (A >= B) ? A : B; +} + +template +RealType TMathUtil::Max3(const RealType A, const RealType B, const RealType C) +{ + return Max(Max(A, B), C); +} + +template +RealType TMathUtil::Min(const RealType A, const RealType B) +{ + return (A <= B) ? A : B; +} + +template +RealType TMathUtil::Min3(const RealType A, const RealType B, const RealType C) +{ + return Min(Min(A, B), C); +} + +template +RealType TMathUtil::Sqrt(const RealType Value) +{ + return sqrt(Value); +} + +template +RealType TMathUtil::Atan2(const RealType ValueY, const RealType ValueX) +{ + return atan2(ValueY, ValueX); +} + +template +RealType TMathUtil::Sin(const RealType Value) +{ + return sin(Value); +} + +template +RealType TMathUtil::Cos(const RealType Value) +{ + return cos(Value); +} + + +template +RealType TMathUtil::Atan2Positive(const RealType Y, const RealType X) +{ + // @todo this is a float atan2 !! + RealType Theta = TMathUtil::Atan2(Y, X); + if (Theta < 0) + { + return ((RealType)2 * TMathUtil::Pi) + Theta; + } + return Theta; +} + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MatrixTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MatrixTypes.h new file mode 100644 index 000000000000..278c9e42f606 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MatrixTypes.h @@ -0,0 +1,419 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "VectorTypes.h" +#include "VectorUtil.h" + +template +struct TMatrix3 +{ + FVector3 Row0; + FVector3 Row1; + FVector3 Row2; + + TMatrix3() + { + } + + TMatrix3(RealType ConstantValue) + { + Row0 = FVector3(ConstantValue, ConstantValue, ConstantValue); + Row1 = Row0; + Row2 = Row0; + } + + TMatrix3(RealType Diag0, RealType Diag1, RealType Diag2) + { + Row0 = FVector3(Diag0, 0, 0); + Row1 = FVector3(0, Diag1, 0); + Row2 = FVector3(0, 0, Diag2); + } + + /** + * Construct outer-product of U*transpose(V) of U and V + * result is that Mij = u_i * v_j + */ + TMatrix3(const FVector3& U, const FVector3& V) + : Row0(U.X * V.X, U.X * V.Y, U.X * V.Z), + Row1(U.Y * V.X, U.Y * V.Y, U.Y * V.Z), + Row2(U.Z * V.X, U.Z * V.Y, U.Z * V.Z) + { + } + + TMatrix3(RealType M00, RealType M01, RealType M02, RealType M10, RealType M11, RealType M12, RealType M20, RealType M21, RealType M22) + : Row0(M00, M01, M02), + Row1(M10, M11, M12), + Row2(M20, M21, M22) + { + } + + TMatrix3(const FVector3& V1, const FVector3& V2, const FVector3& V3, bool bRows) + { + if (bRows) + { + Row0 = V1; + Row1 = V2; + Row2 = V3; + } + else + { + Row0 = FVector3(V1.X, V2.X, V3.X); + Row1 = FVector3(V1.Y, V2.Y, V3.Y); + Row2 = FVector3(V1.Z, V2.Z, V3.Z); + } + } + + static TMatrix3 Zero() + { + return TMatrix3(0); + } + static TMatrix3 Identity() + { + return TMatrix3(1, 1, 1); + } + + RealType operator()(int Row, int Col) const + { + check(Row >= 0 && Row < 3 && Col >= 0 && Col < 3); + if (Row == 0) + { + return Row0[Col]; + } + else if (Row == 1) + { + return Row1[Col]; + } + else + { + return Row2[Col]; + } + } + + TMatrix3 operator*(RealType Scale) const + { + return TMatrix3( + Row0.X * Scale, Row0.Y * Scale, Row0.Z * Scale, + Row1.X * Scale, Row1.Y * Scale, Row1.Z * Scale, + Row2.X * Scale, Row2.Y * Scale, Row2.Z * Scale); + } + + FVector3 operator*(const FVector3& V) const + { + return FVector3( + Row0.X * V.X + Row0.Y * V.Y + Row0.Z * V.Z, + Row1.X * V.X + Row1.Y * V.Y + Row1.Z * V.Z, + Row2.X * V.X + Row2.Y * V.Y + Row2.Z * V.Z); + } + + TMatrix3 operator*(const TMatrix3& Mat2) const + { + RealType M00 = Row0.X * Mat2.Row0.X + Row0.Y * Mat2.Row1.X + Row0.Z * Mat2.Row2.X; + RealType M01 = Row0.X * Mat2.Row0.Y + Row0.Y * Mat2.Row1.Y + Row0.Z * Mat2.Row2.Y; + RealType M02 = Row0.X * Mat2.Row0.Z + Row0.Y * Mat2.Row1.Z + Row0.Z * Mat2.Row2.Z; + + RealType M10 = Row1.X * Mat2.Row0.X + Row1.Y * Mat2.Row1.X + Row1.Z * Mat2.Row2.X; + RealType M11 = Row1.X * Mat2.Row0.Y + Row1.Y * Mat2.Row1.Y + Row1.Z * Mat2.Row2.Y; + RealType M12 = Row1.X * Mat2.Row0.Z + Row1.Y * Mat2.Row1.Z + Row1.Z * Mat2.Row2.Z; + + RealType M20 = Row2.X * Mat2.Row0.X + Row2.Y * Mat2.Row1.X + Row2.Z * Mat2.Row2.X; + RealType M21 = Row2.X * Mat2.Row0.Y + Row2.Y * Mat2.Row1.Y + Row2.Z * Mat2.Row2.Y; + RealType M22 = Row2.X * Mat2.Row0.Z + Row2.Y * Mat2.Row1.Z + Row2.Z * Mat2.Row2.Z; + + return TMatrix3(M00, M01, M02, M10, M11, M12, M20, M21, M22); + } + + TMatrix3 operator+(const TMatrix3& Mat2) + { + return TMatrix3(Row0 + Mat2.Row0, Row1 + Mat2.Row1, Row2 + Mat2.Row2, true); + } + + TMatrix3 operator-(const TMatrix3& Mat2) + { + return TMatrix3(Row0 - Mat2.Row0, Row1 - Mat2.Row1, Row2 - Mat2.Row2, true); + } + + inline TMatrix3& operator*=(const RealType& Scalar) + { + Row0 *= Scalar; + Row1 *= Scalar; + Row2 *= Scalar; + return *this; + } + + inline TMatrix3& operator+=(const TMatrix3& Mat2) + { + Row0 += Mat2.Row0; + Row1 += Mat2.Row1; + Row2 += Mat2.Row2; + return *this; + } + + RealType InnerProduct(const TMatrix3& Mat2) const + { + return Row0.Dot(Mat2.Row0) + Row1.Dot(Mat2.Row1) + Row2.Dot(Mat2.Row2); + } + + RealType Determinant() const + { + RealType a11 = Row0.X, a12 = Row0.Y, a13 = Row0.Z, a21 = Row1.X, a22 = Row1.Y, a23 = Row1.Z, a31 = Row2.X, a32 = Row2.Y, a33 = Row2.Z; + RealType i00 = a33 * a22 - a32 * a23; + RealType i01 = -(a33 * a12 - a32 * a13); + RealType i02 = a23 * a12 - a22 * a13; + return a11 * i00 + a21 * i01 + a31 * i02; + } + + TMatrix3 Inverse() const + { + RealType a11 = Row0.X, a12 = Row0.Y, a13 = Row0.Z, a21 = Row1.X, a22 = Row1.Y, a23 = Row1.Z, a31 = Row2.X, a32 = Row2.Y, a33 = Row2.Z; + RealType i00 = a33 * a22 - a32 * a23; + RealType i01 = -(a33 * a12 - a32 * a13); + RealType i02 = a23 * a12 - a22 * a13; + + RealType i10 = -(a33 * a21 - a31 * a23); + RealType i11 = a33 * a11 - a31 * a13; + RealType i12 = -(a23 * a11 - a21 * a13); + + RealType i20 = a32 * a21 - a31 * a22; + RealType i21 = -(a32 * a11 - a31 * a12); + RealType i22 = a22 * a11 - a21 * a12; + + RealType det = a11 * i00 + a21 * i01 + a31 * i02; + ensure(TMathUtil::Abs(det) >= TMathUtil::Epsilon); + det = 1.0 / det; + return TMatrix3(i00 * det, i01 * det, i02 * det, i10 * det, i11 * det, i12 * det, i20 * det, i21 * det, i22 * det); + } + + TMatrix3 Transpose() const + { + return TMatrix3( + Row0.X, Row1.X, Row2.X, + Row0.Y, Row1.Y, Row2.Y, + Row0.Z, Row1.Z, Row2.Z); + } + + bool EpsilonEqual(const TMatrix3& Mat2, RealType Epsilon) const + { + return VectorUtil::EpsilonEqual(Row0, Mat2.Row0, Epsilon) && + VectorUtil::EpsilonEqual(Row1, Mat2.Row1, Epsilon) && + VectorUtil::EpsilonEqual(Row2, Mat2.Row2, Epsilon); + } + + static TMatrix3 AxisAngleR(const FVector3& Axis, RealType AngleRad) + { + RealType cs = TMathUtil::Cos(AngleRad); + RealType sn = TMathUtil::Sin(AngleRad); + RealType oneMinusCos = 1.0 - cs; + RealType x2 = Axis[0] * Axis[0]; + RealType y2 = Axis[1] * Axis[1]; + RealType z2 = Axis[2] * Axis[2]; + RealType xym = Axis[0] * Axis[1] * oneMinusCos; + RealType xzm = Axis[0] * Axis[2] * oneMinusCos; + RealType yzm = Axis[1] * Axis[2] * oneMinusCos; + RealType xSin = Axis[0] * sn; + RealType ySin = Axis[1] * sn; + RealType zSin = Axis[2] * sn; + return TMatrix3( + x2 * oneMinusCos + cs, xym - zSin, xzm + ySin, + xym + zSin, y2 * oneMinusCos + cs, yzm - xSin, + xzm - ySin, yzm + xSin, z2 * oneMinusCos + cs); + } + + static TMatrix3 AxisAngleD(const FVector3& Axis, RealType AngleDeg) + { + return AxisAngleR(Axis, TMathUtil::DegreesToRadians(AngleDeg)); + } +}; + + +template +struct TMatrix2 +{ + FVector2 Row0; + FVector2 Row1; + + TMatrix2() + { + } + + TMatrix2(RealType ConstantValue) + { + Row0 = Row1 = FVector2(ConstantValue, ConstantValue); + } + + TMatrix2(RealType Diag0, RealType Diag1) + { + Row0 = FVector2(Diag0, 0); + Row1 = FVector2(0, Diag1); + } + + /** + * Construct outer-product of U*transpose(V) of U and V + * result is that Mij = u_i * v_j + */ + TMatrix2(const FVector2& U, const FVector2& V) + : Row0(U.X * V.X, U.X * V.Y), + Row1(U.Y * V.X, U.Y * V.Y) + { + } + + TMatrix2(RealType M00, RealType M01, RealType M10, RealType M11) + : Row0(M00, M01), + Row1(M10, M11) + { + } + + TMatrix2(const FVector2& V1, const FVector2& V2, bool bRows) + { + if (bRows) + { + Row0 = V1; + Row1 = V2; + } + else + { + Row0 = FVector2(V1.X, V2.X); + Row1 = FVector2(V1.Y, V2.Y); + } + } + + static TMatrix2 Zero() + { + return TMatrix2(0); + } + static TMatrix2 Identity() + { + return TMatrix2(1, 1); + } + + RealType operator()(int Row, int Col) const + { + check(Row >= 0 && Row < 2 && Col >= 0 && Col < 2); + if (Row == 0) + { + return Row0[Col]; + } + else + { + return Row1[Col]; + } + } + + TMatrix2 operator*(RealType Scale) const + { + return TMatrix2( + Row0.X * Scale, Row0.Y * Scale, + Row1.X * Scale, Row1.Y * Scale); + } + + FVector2 operator*(const FVector2& V) const + { + return FVector2( + Row0.X * V.X + Row0.Y * V.Y, + Row1.X * V.X + Row1.Y * V.Y); + } + + TMatrix2 operator*(const TMatrix2& Mat2) const + { + RealType M00 = Row0.X * Mat2.Row0.X + Row0.Y * Mat2.Row1.X; + RealType M01 = Row0.X * Mat2.Row0.Y + Row0.Y * Mat2.Row1.Y; + + RealType M10 = Row1.X * Mat2.Row0.X + Row1.Y * Mat2.Row1.X; + RealType M11 = Row1.X * Mat2.Row0.Y + Row1.Y * Mat2.Row1.Y; + + return TMatrix2(M00, M01, M10, M11); + } + + TMatrix2 operator+(const TMatrix2& Mat2) + { + return TMatrix2(Row0 + Mat2.Row0, Row1 + Mat2.Row1, true); + } + + TMatrix2 operator-(const TMatrix2& Mat2) + { + return TMatrix2(Row0 - Mat2.Row0, Row1 - Mat2.Row1, true); + } + + inline TMatrix2& operator*=(const RealType& Scalar) + { + Row0 *= Scalar; + Row1 *= Scalar; + return *this; + } + + inline TMatrix2& operator+=(const TMatrix2& Mat2) + { + Row0 += Mat2.Row0; + Row1 += Mat2.Row1; + return *this; + } + + RealType InnerProduct(const TMatrix2& Mat2) const + { + return Row0.Dot(Mat2.Row0) + Row1.Dot(Mat2.Row1); + } + + RealType Determinant() const + { + return Row0.X * Row1.Y - Row0.Y * Row1.X; + } + + TMatrix2 Inverse() const + { + RealType Det = Determinant(); + ensure(TMathUtil::Abs(Det) >= TMathUtil::Epsilon); + RealType DetInv = 1.0 / Det; + return TMatrix2(Row1.Y * DetInv, -Row0.Y * DetInv, -Row1.X * DetInv, Row0.X * DetInv); + } + + TMatrix2 Transpose() const + { + return TMatrix2( + Row0.X, Row1.X, + Row0.Y, Row1.Y); + } + + bool EpsilonEqual(const TMatrix2& Mat2, RealType Epsilon) const + { + return VectorUtil::EpsilonEqual(Row0, Mat2.Row0, Epsilon) && + VectorUtil::EpsilonEqual(Row1, Mat2.Row1, Epsilon); + } + + static TMatrix2 RotationRad(RealType AngleRad) + { + RealType cs = TMathUtil::Cos(AngleRad); + RealType sn = TMathUtil::Sin(AngleRad); + return TMatrix2(cs, -sn, sn, cs); + } + + /** + * Assumes we have a rotation matrix (uniform scale ok) + */ + RealType GetAngleRad() + { + return TMathUtil::Atan2(Row1.X, Row0.X); + } + +}; + +template +inline TMatrix3 operator*(RealType Scale, const TMatrix3& Mat) +{ + return TMatrix3( + Mat.Row0.X * Scale, Mat.Row0.Y * Scale, Mat.Row0.Z * Scale, + Mat.Row1.X * Scale, Mat.Row1.Y * Scale, Mat.Row1.Z * Scale, + Mat.Row2.X * Scale, Mat.Row2.Y * Scale, Mat.Row2.Z * Scale); +} + +template +inline TMatrix2 operator*(RealType Scale, const TMatrix2& Mat) +{ + return TMatrix2( + Mat.Row0.X * Scale, Mat.Row0.Y * Scale, + Mat.Row1.X * Scale, Mat.Row1.Y * Scale); +} + +typedef TMatrix3 FMatrix3f; +typedef TMatrix3 FMatrix3d; +typedef TMatrix2 FMatrix2f; +typedef TMatrix2 FMatrix2d; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MeshAdapter.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MeshAdapter.h new file mode 100644 index 000000000000..ce97e98cf025 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MeshAdapter.h @@ -0,0 +1,48 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TriangleTypes.h" +#include "VectorTypes.h" + +#include "Math/IntVector.h" +#include "Math/Vector.h" + +template +struct TTriangleMeshAdapter +{ + TFunction IsTriangle; + TFunction IsVertex; + TFunction MaxTriangleID; + TFunction MaxVertexID; + TFunction TriangleCount; + TFunction VertexCount; + TFunction GetShapeTimestamp; + TFunction GetTriangle; + TFunction(int32)> GetVertex; + + inline void GetTriVertices(int TID, FVector3& V0, FVector3& V1, FVector3& V2) const + { + FIndex3i TriIndices = GetTriangle(TID); + V0 = GetVertex(TriIndices.A); + V1 = GetVertex(TriIndices.B); + V2 = GetVertex(TriIndices.C); + } +}; + +typedef TTriangleMeshAdapter TTriangleMeshAdapterd; +typedef TTriangleMeshAdapter TTriangleMeshAdapterf; + +TTriangleMeshAdapterd GetArrayMesh(TArray& Vertices, TArray& Triangles) +{ + return { + [&](int) { return true; }, + [&](int) { return true; }, + [&]() { return Triangles.Num(); }, + [&]() { return Vertices.Num(); }, + [&]() { return Triangles.Num(); }, + [&]() { return Vertices.Num(); }, + [&]() { return 0; }, + [&](int Idx) { return FIndex3i(Triangles[Idx]); }, + [&](int Idx) { return FVector3d(Vertices[Idx]); }}; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MeshQueries.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MeshQueries.h new file mode 100644 index 000000000000..4bfc5a842709 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MeshQueries.h @@ -0,0 +1,154 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Distance/DistPoint3Triangle3.h" +#include "Intersection/IntrRay3Triangle3.h" +#include "BoxTypes.h" +#include "IndexTypes.h" + +template +class TMeshQueries +{ +public: + TMeshQueries() = delete; + + /** + * construct a DistPoint3Triangle3 object for a Mesh triangle + */ + static FDistPoint3Triangle3d TriangleDistance(const TriangleMeshType& Mesh, int TriIdx, FVector3d Point) + { + check(Mesh.IsTriangle(TriIdx)); + FTriangle3d tri; + Mesh.GetTriVertices(TriIdx, tri.V[0], tri.V[1], tri.V[2]); + FDistPoint3Triangle3d q(Point, tri); + q.GetSquared(); + return q; + } + + /** + * convenience function to construct a IntrRay3Triangle3 object for a Mesh triangle + */ + static FIntrRay3Triangle3d TriangleIntersection(const TriangleMeshType& Mesh, int TriIdx, const FRay3d& Ray) + { + check(Mesh.IsTriangle(TriIdx)); + FTriangle3d tri; + Mesh.GetTriVertices(TriIdx, tri.V[0], tri.V[1], tri.V[2]); + FIntrRay3Triangle3d q(Ray, tri); + q.Find(); + return q; + } + + /** + * Compute triangle centroid + * @param Mesh Mesh with triangle + * @param TriIdx Index of triangle + * @return Computed centroid + */ + static FVector3d GetTriCentroid(const TriangleMeshType& Mesh, int TriIdx) + { + FTriangle3d Triangle; + Mesh.GetTriVertices(TriIdx, Triangle.V[0], Triangle.V[1], Triangle.V[2]); + return Triangle.Centroid(); + } + + /** + * Compute the normal, area, and centroid of a triangle all together + * @param Mesh Mesh w/ triangle + * @param TriIdx Index of triangle + * @param Normal Computed normal (returned by reference) + * @param Area Computed area (returned by reference) + * @param Centroid Computed centroid (returned by reference) + */ + static void GetTriNormalAreaCentroid(const TriangleMeshType& Mesh, int TriIdx, FVector3d& Normal, double& Area, FVector3d& Centroid) + { + FTriangle3d Triangle; + Mesh.GetTriVertices(TriIdx, Triangle.V[0], Triangle.V[1], Triangle.V[2]); + Centroid = Triangle.Centroid(); + Normal = VectorUtil::FastNormalArea(Triangle.V[0], Triangle.V[1], Triangle.V[2], Area); + } + + static FAxisAlignedBox3d GetTriBounds(const TriangleMeshType& Mesh, int TID) + { + FIndex3i TriInds = Mesh.GetTriangle(TID); + FVector3d MinV, MaxV, V = Mesh.GetVertex(TriInds.A); + MinV = MaxV = V; + for (int i = 1; i < 3; ++i) + { + V = Mesh.GetVertex(TriInds[i]); + if (V.X < MinV.X) MinV.X = V.X; + else if (V.X > MaxV.X) MaxV.X = V.X; + if (V.Y < MinV.Y) MinV.Y = V.Y; + else if (V.Y > MaxV.Y) MaxV.Y = V.Y; + if (V.Z < MinV.Z) MinV.Z = V.Z; + else if (V.Z > MaxV.Z) MaxV.Z = V.Z; + } + return FAxisAlignedBox3d(MinV, MaxV); + } + + // brute force search for nearest triangle to Point + static int FindNearestTriangle_LinearSearch(const TriangleMeshType& Mesh, const FVector3d& P) + { + int tNearest = IndexConstants::InvalidID; + double fNearestSqr = TNumericLimits::Max(); + for (int TriIdx : Mesh.TriangleIndicesItr()) + { + double distSqr = TriDistanceSqr(Mesh, TriIdx, P); + if (distSqr < fNearestSqr) + { + fNearestSqr = distSqr; + tNearest = TriIdx; + } + } + return tNearest; + } + + /** + * Compute distance from Point to triangle in Mesh, with minimal extra objects/etc + */ + static double TriDistanceSqr(const TriangleMeshType& Mesh, int TriIdx, const FVector3d& Point) + { + FTriangle3d Triangle; + Mesh.GetTriVertices(TriIdx, Triangle.V[0], Triangle.V[1], Triangle.V[2]); + + FDistPoint3Triangle3d Distance(Point, Triangle); + return Distance.GetSquared(); + } + + // brute force search for nearest triangle intersection + static int FindHitTriangle_LinearSearch(const TriangleMeshType& Mesh, const FRay3d& Ray) + { + int tNearestID = IndexConstants::InvalidID; + double fNearestT = TNumericLimits::Max(); + FTriangle3d Triangle; + + for (int TriIdx : Mesh.TriangleIndicesItr()) + { + Mesh.GetTriVertices(TriIdx, Triangle.V[0], Triangle.V[1], Triangle.V[2]); + FIntrRay3Triangle3d Query(Ray, Triangle); + if (Query.Find()) + { + if (Query.RayParameter < fNearestT) + { + fNearestT = Query.RayParameter; + tNearestID = TriIdx; + } + } + } + + return tNearestID; + } + + /** + * convenience function to construct a IntrRay3Triangle3 object for a Mesh triangle + */ + static FIntrRay3Triangle3d RayTriangleIntersection(const TriangleMeshType& Mesh, int TriIdx, const FRay3d& Ray) + { + FTriangle3d Triangle; + Mesh.GetTriVertices(TriIdx, Triangle.V[0], Triangle.V[1], Triangle.V[2]); + + FIntrRay3Triangle3d Query(Ray, Triangle); + Query.Find(); + return Query; + } +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/OrientedBoxTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/OrientedBoxTypes.h new file mode 100644 index 000000000000..064257d4ea56 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/OrientedBoxTypes.h @@ -0,0 +1,222 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// ported from geometry3Sharp Box3 + +#pragma once + +#include "VectorTypes.h" +#include "BoxTypes.h" +#include "FrameTypes.h" + +/** + * TOrientedBox3 is a non-axis-aligned 3D box defined by a 3D frame and extents along the axes of that frame + * The frame is at the center of the box. + */ +template +struct TOrientedBox3 +{ + // available for porting: ContainPoint, MergeBoxes() + + + /** 3D position (center) and orientation (axes) of the box */ + TFrame3 Frame; + /** Half-dimensions of box measured along the three axes */ + FVector3 Extents; + + TOrientedBox3() : Extents(1,1,1) {} + + /** + * Create axis-aligned box with given Origin and Extents + */ + TOrientedBox3(const FVector3& Origin, const FVector3 & ExtentsIn) + : Frame(Origin), Extents(ExtentsIn) + { + } + + /** + * Create oriented box with given Frame and Extents + */ + TOrientedBox3(const TFrame3& FrameIn, const FVector3 & ExtentsIn) + : Frame(FrameIn), Extents(ExtentsIn) + { + } + + /** + * Create oriented box from axis-aligned box + */ + TOrientedBox3(const TAxisAlignedBox3& AxisBox) + : Frame(AxisBox.Center()), Extents((RealType)0.5 * AxisBox.Diagonal()) + { + } + + + /** @return box with unit dimensions centered at origin */ + static TOrientedBox3 UnitZeroCentered() { return TOrientedBox3(FVector3::Zero(), (RealType)0.5*FVector3::One()); } + + /** @return box with unit dimensions where minimum corner is at origin */ + static TOrientedBox3 UnitPositive() { return TOrientedBox3((RealType)0.5*FVector3::One(), (RealType)0.5*FVector3::One()); } + + + /** @return center of the box */ + FVector3 Center() const { return Frame.Origin; } + + /** @return X axis of the box */ + FVector3 AxisX() const { return Frame.X(); } + + /** @return Y axis of the box */ + FVector3 AxisY() const { return Frame.Y(); } + + /** @return Z axis of the box */ + FVector3 AxisZ() const { return Frame.Z(); } + + /** @return an axis of the box */ + FVector3 GetAxis(int AxisIndex) const { return Frame.GetAxis(AxisIndex); } + + /** @return maximum extent of box */ + inline RealType MaxExtent() const + { + return Extents.MaxAbs(); + } + + /** @return minimum extent of box */ + inline RealType MinExtent() const + { + return Extents.MinAbs(); + } + + /** @return vector from minimum-corner to maximum-corner of box */ + inline FVector3 Diagonal() const + { + return Frame.PointAt(Extents.X, Extents.Y, Extents.Z) - Frame.PointAt(-Extents.X, -Extents.Y, -Extents.Z); + } + + /** @return volume of box */ + inline RealType Volume() const + { + return (RealType)8 * (Extents.X) * (Extents.Y) * (Extents.Z); + } + + /** @return true if box contains point */ + inline bool Contains(const FVector3& Point) + { + FVector3 InFramePoint = Frame.ToFramePoint(Point); + return (TMathUtil::Abs(InFramePoint.X) <= Extents.X) && + (TMathUtil::Abs(InFramePoint.Y) <= Extents.Y) && + (TMathUtil::Abs(InFramePoint.Z) <= Extents.Z); + } + + + // corners [ (-x,-y), (x,-y), (x,y), (-x,y) ], -z, then +z + // + // 7---6 +z or 3---2 -z + // |\ |\ |\ |\ + // 4-\-5 \ 0-\-1 \ + // \ 3---2 \ 7---6 + // \| | \| | + // 0---1 -z 4---5 +z + // + // @todo does this ordering make sense for UE? we are in LHS instead of RHS here + // if this is modified, likely need to update IndexUtil::BoxFaces and BoxFaceNormals + + /** + * @param Index corner index in range 0-7 + * @return Corner point on the box identified by the given index. See diagram in OrientedBoxTypes.h for index/corner mapping. + */ + FVector3 GetCorner(int Index) const + { + check(Index >= 0 && Index <= 7); + RealType dx = (((Index & 1) != 0) ^ ((Index & 2) != 0)) ? (Extents.X) : (-Extents.X); + RealType dy = ((Index / 2) % 2 == 0) ? (-Extents.Y) : (Extents.Y); + RealType dz = (Index < 4) ? (-Extents.Z) : (Extents.Z); + return Frame.PointAt(dx, dy, dz); + } + + + + + + /** + * Find squared distance to box. + * @param Point input point + * @return squared distance from point to box, or 0 if point is inside box + */ + RealType DistanceSquared(FVector3 Point) + { + // Ported from WildMagic5 Wm5DistPoint3Box3.cpp + + // Work in the box's coordinate system. + Point -= Frame.Origin; + + // Compute squared distance and closest point on box. + RealType sqrDistance = 0; + RealType delta; + FVector3 closest(0, 0, 0); + for (int i = 0; i < 3; ++i) + { + closest[i] = Point.Dot(GetAxis(i)); + if (closest[i] < -Extents[i]) + { + delta = closest[i] + Extents[i]; + sqrDistance += delta * delta; + closest[i] = -Extents[i]; + } + else if (closest[i] > Extents[i]) + { + delta = closest[i] - Extents[i]; + sqrDistance += delta * delta; + closest[i] = Extents[i]; + } + } + + return sqrDistance; + } + + + + + /** + * Find closest point on box + * @param Point input point + * @return closest point on box. Input point is returned if it is inside box. + */ + FVector3 ClosestPoint(FVector3 Point) + { + // Work in the box's coordinate system. + Point -= Frame.Origin; + + // Compute squared distance and closest point on box. + RealType sqrDistance = 0; + RealType delta; + FVector3 closest; + FVector3 Axes[3]; + for (int i = 0; i < 3; ++i) + { + Axes[i] = GetAxis(i); + closest[i] = Point.Dot(Axes[i]); + RealType extent = Extents[i]; + if (closest[i] < -extent) + { + delta = closest[i] + extent; + sqrDistance += delta * delta; + closest[i] = -extent; + } + else if (closest[i] > extent) + { + delta = closest[i] - extent; + sqrDistance += delta * delta; + closest[i] = extent; + } + } + + return Frame.Origin + closest.X*Axes[0] + closest.Y*Axes[1] + closest.Z*Axes[2]; + } + + + +}; + + + + +typedef TOrientedBox3 FOrientedBox3f; +typedef TOrientedBox3 FOrientedBox3d; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/PointSetAdapter.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/PointSetAdapter.h new file mode 100644 index 000000000000..b34bb44856e3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/PointSetAdapter.h @@ -0,0 +1,33 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "VectorTypes.h" + +/** + * TPointSetAdapter provides a very generic interface to an indexable list of points. + * The list may be sparse, ie some indices may be invalid. + */ +template +struct TPointSetAdapter +{ + /** Maximum point index */ + TFunction MaxPointID; + /** Number of points. If PointCount == MaxPointID, then there are no gaps in the index list */ + TFunction PointCount; + /** Returns true if this index valid */ + TFunction IsPoint; + /** Get point at this index */ + TFunction(int32)> GetPoint; + /** Returns a timestamp. If point set changes, timestamp should also change. */ + TFunction Timestamp; + + /** Returns true if this point set has per-point normals */ + TFunction HasNormals; + /** Get the normal at a point index */ + TFunction GetPointNormal; +}; + +typedef TPointSetAdapter FPointSetAdapterd; +typedef TPointSetAdapter FPointSetAdapterf; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Polygon2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Polygon2.h new file mode 100644 index 000000000000..cb63c6fecdff --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Polygon2.h @@ -0,0 +1,1056 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// port of geometry3Sharp Polygon + +#pragma once + +#include "Templates/UnrealTemplate.h" +#include "Math/UnrealMath.h" +#include "VectorTypes.h" +#include "BoxTypes.h" +#include "SegmentTypes.h" +#include "LineTypes.h" +#include "MathUtil.h" +#include "Intersection/IntrSegment2Segment2.h" +#include "Util/DynamicVector.h" + +/** + * TPolygon2 is a 2D polygon represented as a list of Vertices. + * + * @todo move operators + */ +template +class TPolygon2 +{ +protected: + /** The list of vertices/corners of the polygon */ + TArray> Vertices; + + /** A counter that is incremented every time the polygon vertices are modified */ + int Timestamp; + +public: + + TPolygon2() : Timestamp(0) + { + } + + /** + * Construct polygon that is a copy of another polygon + */ + TPolygon2(const TPolygon2& Copy) : Vertices(Copy.Vertices), Timestamp(Copy.Timestamp) + { + } + + /** + * Construct polygon with given list of vertices + */ + TPolygon2(const TArray>& VertexList) : Vertices(VertexList), Timestamp(0) + { + } + + /** @return the Timestamp for the polygon, which is updated every time the polygon is modified */ + int GetTimestamp() const + { + return Timestamp; + } + + /** + * Get the vertex at a given index + */ + const FVector2& operator[](int Index) const + { + return Vertices[Index]; + } + + /** + * Get the vertex at a given index + * @warning changing the vertex via this operator does not update Timestamp! + */ + FVector2& operator[](int Index) + { + return Vertices[Index]; + } + + + /** + * @return first vertex of Polygon + */ + const FVector2& Start() const + { + return Vertices[0]; + } + + /** + * @return list of Vertices of Polygon + */ + const TArray>& GetVertices() const + { + return Vertices; + } + + /** + * @return number of Vertices in Polygon + */ + int VertexCount() const + { + return Vertices.Num(); + } + + /** + * Add a vertex to the Polygon + */ + void AppendVertex(const FVector2& Position) + { + Vertices.Add(Position); + Timestamp++; + } + + /** + * Add a list of Vertices to the Polygon + */ + void AppendVertices(const TArray>& NewVertices) + { + Vertices.Append(NewVertices); + Timestamp++; + } + + /** + * Set vertex at given index to a new Position + */ + void Set(int VertexIndex, const FVector2& Position) + { + Vertices[VertexIndex] = Position; + Timestamp++; + } + + /** + * Remove a vertex of the Polygon (existing Vertices are shifted) + */ + void RemoveVertex(int VertexIndex) + { + Vertices.RemoveAt(VertexIndex); + Timestamp++; + } + + /** + * Replace the list of Vertices with a new list + */ + void SetVertices(const TArray>& NewVertices) + { + Vertices = NewVertices; + Timestamp++; + } + + + /** + * Reverse the order of the Vertices in the Polygon (ie switch between Clockwise and CounterClockwise) + */ + void Reverse() + { + int32 j = Vertices.Num()-1; + for (int32 VertexIndex = 0; VertexIndex < j; VertexIndex++, j--) + { + Swap(Vertices[VertexIndex], Vertices[j]); + } + Timestamp++; + } + + + /** + * Get the tangent vector at a vertex of the polygon, which is the normalized + * vector from the previous vertex to the next vertex + */ + FVector2 GetTangent(int VertexIndex) const + { + FVector2 next = Vertices[(VertexIndex + 1) % Vertices.Num()]; + FVector2 prev = Vertices[VertexIndex == 0 ? Vertices.Num() - 1 : VertexIndex - 1]; + return (next - prev).Normalized(); + } + + + /** + * Get the normal vector at a vertex of the polygon, which is perpendicular to GetTangent() + * Points "inward" for a Clockwise Polygon, and outward for CounterClockwise + */ + FVector2 GetNormal(int VertexIndex) const + { + return GetTangent(VertexIndex).Perp(); + } + + + /** + * Construct a normal at a vertex of the Polygon by averaging the adjacent face normals. + * This vector is independent of the lengths of the adjacent segments. + * Points "inward" for a Clockwise Polygon, and outward for CounterClockwise + */ + FVector2 GetNormal_FaceAvg(int VertexIndex) const + { + FVector2 next = Vertices[(VertexIndex + 1) % Vertices.Num()]; + FVector2 prev = Vertices[VertexIndex == 0 ? Vertices.Num() - 1 : VertexIndex - 1]; + next -= Vertices[VertexIndex]; next.Normalize(); + prev -= Vertices[VertexIndex]; prev.Normalize(); + + FVector2 n = (next.Perp() - prev.Perp()); + T len = n.Normalize(); + if (len == 0) + { + return (next + prev).Normalized(); // this gives right direction for degenerate angle + } + else + { + return n; + } + } + + + /** + * @return the bounding box of the Polygon Vertices + */ + TAxisAlignedBox2 Bounds() const + { + TAxisAlignedBox2 box = TAxisAlignedBox2::Empty(); + box.Contain(Vertices); + return box; + } + + + /** + * SegmentIterator is used to iterate over the TSegment2 segments of the polygon + */ + class SegmentIterator + { + public: + inline bool operator!() + { + return i < polygon->VertexCount(); + } + inline TSegment2 operator*() const + { + check(polygon != nullptr && i < polygon->VertexCount()); + return TSegment2(polygon->Vertices[i], polygon->Vertices[i+1 % polygon->VertexCount()]); + } + //inline TSegment2 & operator*(); + inline SegmentIterator & operator++() // prefix + { + i++; + return *this; + } + inline SegmentIterator operator++(int) // postfix + { + SegmentIterator copy(*this); + i++; + return copy; + } + inline bool operator==(const SegmentIterator & i2) { return i2.polygon == polygon && i2.i == i; } + inline bool operator!=(const SegmentIterator & i2) { return i2.polygon != polygon || i2.i != i; } + protected: + const TPolygon2 * polygon; + int i; + inline SegmentIterator(const TPolygon2 * p, int iCur) : polygon(p), i(iCur) {} + friend class TPolygon2; + }; + friend class SegmentIterator; + + SegmentIterator SegmentItr() const + { + return SegmentIterator(this, 0); + } + + /** + * Wrapper around SegmentIterator that has begin() and end() suitable for range-based for loop + */ + class SegmentEnumerable + { + public: + const TPolygon2* polygon; + SegmentEnumerable() : polygon(nullptr) {} + SegmentEnumerable(const TPolygon2 * p) : polygon(p) {} + SegmentIterator begin() { return polygon->SegmentItr(); } + SegmentIterator end() { return SegmentIterator(polygon, polygon->VertexCount()); } + }; + + /** + * @return an object that can be used in a range-based for loop to iterate over the Segments of the Polygon + */ + SegmentEnumerable Segments() const + { + return SegmentEnumerable(this); + } + + + /** + * @return true if the Polygon Vertices have Clockwise winding order / orientation (signed area is negative) + */ + bool IsClockwise() const + { + return SignedArea() < 0; + } + + /** + * @return the signed area of the Polygon + */ + T SignedArea() const + { + T fArea = 0; + int N = Vertices.Num(); + if (N == 0) + { + return 0; + } + for (int i = 0; i < N; ++i) + { + const FVector2& v1 = Vertices[i]; + const FVector2& v2 = Vertices[(i + 1) % N]; + fArea += v1.X * v2.Y - v1.Y * v2.X; + } + return fArea * 0.5; + } + + /** + * @return the unsigned area of the Polygon + */ + T Area() const + { + return TMathUtil::Abs(SignedArea()); + } + + /** + * @return the total perimeter length of the Polygon + */ + T Perimeter() const + { + T fPerim = 0; + int N = Vertices.Num(); + for (int i = 0; i < N; ++i) + { + fPerim += Vertices[i].Distance(Vertices[(i + 1) % N]); + } + return fPerim; + } + + + /** + * Get the previous and next vertex positions for a given vertex of the Polygon + */ + void NeighbourPoints(int iVertex, FVector2 &PrevNbrOut, FVector2 &NextNbrOut) const + { + int N = Vertices.Num(); + PrevNbrOut = Vertices[(iVertex == 0) ? N - 1 : iVertex - 1]; + NextNbrOut = Vertices[(iVertex + 1) % N]; + } + + + /** + * Get the vectors from a given vertex to the previous and next Vertices, optionally normalized + */ + void NeighbourVectors(int iVertex, FVector2 &ToPrevOut, FVector2 &ToNextOut, bool bNormalize = false) const + { + int N = Vertices.Num(); + ToPrevOut = Vertices[(iVertex == 0) ? N - 1 : iVertex - 1] - Vertices[iVertex]; + ToNextOut = Vertices[(iVertex + 1) % N] - Vertices[iVertex]; + if (bNormalize) + { + ToPrevOut.Normalize(); + ToNextOut.Normalize(); + } + } + + + /** + * @return the opening angle in degrees at a vertex of the Polygon + */ + T OpeningAngleDeg(int iVertex) const + { + FVector2 e0, e1; + NeighbourVectors(iVertex, e0, e1, true); + return e0.AngleD(e1); + } + + + /** + * @return analytic winding integral for this Polygon at an arbitrary point + */ + T WindingIntegral(const FVector2& QueryPoint) const + { + T sum = 0; + int N = Vertices.Num(); + FVector2 a = Vertices[0] - QueryPoint, b = FVector2::Zero(); + for (int i = 0; i < N; ++i) + { + b = Vertices[(i + 1) % N] - QueryPoint; + sum += TMathUtil::Atan2(a.X * b.Y - a.Y * b.X, a.X * b.X + a.Y * b.Y); + a = b; + } + return sum / FMathd::TwoPi; + } + + + /** + * @return true if the given query point is inside the Polygon, based on the winding integral + */ + bool Contains(const FVector2& QueryPoint) const + { + int nWindingNumber = 0; + + int N = Vertices.Num(); + FVector2 a = Vertices[0], b = FVector2::Zero(); + for (int i = 0; i < N; ++i) + { + b = Vertices[(i + 1) % N]; + + if (a.Y <= QueryPoint.Y) // y <= P.Y (below) + { + if (b.Y > QueryPoint.Y) // an upward crossing + { + if (FVector2::Orient(a, b, QueryPoint) > 0) // P left of edge + ++nWindingNumber; // have a valid up intersect + } + } + else // y > P.Y (above) + { + if (b.Y <= QueryPoint.Y) // a downward crossing + { + if (FVector2::Orient(a, b, QueryPoint) < 0) // P right of edge + { + --nWindingNumber; // have a valid down intersect + } + } + } + a = b; + } + return nWindingNumber != 0; + } + + + /** + * @return true if the Polygon fully contains the OtherPolygon + */ + bool Contains(const TPolygon2& OtherPoly) const + { + // @todo fast bbox check? + + int N = OtherPoly.VertexCount(); + for (int i = 0; i < N; ++i) + { + if (Contains(OtherPoly[i]) == false) + { + return false; + } + } + + if (Intersects(OtherPoly)) + { + return false; + } + + return true; + } + + + /** + * @return true if the Segment is fully contained inside the Polygon + */ + bool Contains(const TSegment2& Segment) const + { + // [TODO] Add bbox check + if (Contains(Segment.StartPoint()) == false || Contains(Segment.EndPoint()) == false) + { + return false; + } + + for (TSegment2 seg : Segments()) + { + if (seg.Intersects(Segment)) + { + return false; + } + } + return true; + } + + + /** + * @return true if at least one edge of the OtherPolygon intersects the Polygon + */ + bool Intersects(const TPolygon2& OtherPoly) const + { + if (!Bounds().Intersects(OtherPoly.Bounds())) + { + return false; + } + + for (TSegment2 seg : Segments()) + { + for (TSegment2 oseg : OtherPoly.Segments()) + { + if (seg.Intersects(oseg)) + { + return true; + } + } + } + return false; + } + + + /** + * @return true if the Segment intersects an edge of the Polygon + */ + bool Intersects(const TSegment2& Segment) const + { + // [TODO] Add bbox check + if (Contains(Segment.StartPoint()) == true || Contains(Segment.EndPoint()) == true) + { + return true; + } + + // [TODO] Add bbox check + for (TSegment2 seg : Segments()) + { + if (seg.Intersects(Segment)) + { + return true; + } + } + return false; + } + + + /** + * Find all the points where an edge of the Polygon intersects an edge of the OtherPolygon + * @param OtherPoly polygon to test against + * @param OutArray intersection points are stored here + * @return true if any intersections were found + */ + bool FindIntersections(const TPolygon2& OtherPoly, TArray>& OutArray) const + { + if (!Bounds().Intersects(OtherPoly.Bounds())) + { + return false; + } + + bool bFoundIntersections = false; + for (TSegment2 seg : Segments()) + { + for (TSegment2 oseg : OtherPoly.Segments()) + { + // this computes test twice for intersections, but seg.intersects doesn't + // create any new objects so it should be much faster for majority of segments (should profile!) + if (seg.Intersects(oseg)) + { + //@todo can we replace with something like seg.intersects? + TIntrSegment2Segment2 intr(seg, oseg); + if (intr.Find()) + { + bFoundIntersections = true; + OutArray.Add(intr.Point0); + if (intr.Quantity == 2) + { + OutArray.Add(intr.Point1); + } + } + } + } + } + + return bFoundIntersections; + } + + + /** + * @return edge of the polygon starting at vertex SegmentIndex + */ + TSegment2 Segment(int SegmentIndex) const + { + return TSegment2(Vertices[SegmentIndex], Vertices[(SegmentIndex + 1) % Vertices.Num()]); + } + + /** + * @param SegmentIndex index of first vertex of the edge + * @param SegmentParam parameter in range [-Extent,Extent] along segment + * @return point on the segment at the given parameter value + */ + FVector2 GetSegmentPoint(int SegmentIndex, T SegmentParam) const + { + TSegment2 seg(Vertices[SegmentIndex], Vertices[(SegmentIndex + 1) % Vertices.Num()]); + return seg.PointAt(SegmentParam); + } + + /** + * @param SegmentIndex index of first vertex of the edge + * @param SegmentParam parameter in range [0,1] along segment + * @return point on the segment at the given parameter value + */ + FVector2 GetSegmentPointUnitParam(int SegmentIndex, T SegmentParam) const + { + TSegment2 seg(Vertices[SegmentIndex], Vertices[(SegmentIndex + 1) % Vertices.Num()]); + return seg.PointBetween(SegmentParam); + } + + + /** + * @param SegmentIndex index of first vertex of the edge + * @param SegmentParam parameter in range [0,1] along segment + * @return interpolated normal to the segment at the given parameter value + */ + FVector2 GetNormal(int iSeg, T SegmentParam) const + { + TSegment2 seg(Vertices[iSeg], Vertices[(iSeg + 1) % Vertices.Num()]); + T t = ((SegmentParam / seg.Extent) + 1.0) / 2.0; + + FVector2 n0 = GetNormal(iSeg); + FVector2 n1 = GetNormal((iSeg + 1) % Vertices.Num()); + return ((T(1) - t) * n0 + t * n1).Normalized(); + } + + + + /** + * Calculate the squared distance from a point to the polygon + * @param QueryPoint the query point + * @param NearestSegIndexOut The index of the nearest segment + * @param NearestSegParamOut the parameter value of the nearest point on the segment + * @return squared distance to the polygon + */ + T DistanceSquared(const FVector2& QueryPoint, int& NearestSegIndexOut, T& NearestSegParamOut) const + { + NearestSegIndexOut = -1; + NearestSegParamOut = TNumericLimits::Max(); + T dist = TNumericLimits::Max(); + int N = Vertices.Num(); + for (int vi = 0; vi < N; ++vi) + { + // @todo can't we just use segment function here now? + TSegment2 seg = TSegment2(Vertices[vi], Vertices[(vi + 1) % N]); + T t = (QueryPoint - seg.Center).Dot(seg.Direction); + T d = TNumericLimits::Max(); + if (t >= seg.Extent) + { + d = seg.EndPoint().DistanceSquared(QueryPoint); + } + else if (t <= -seg.Extent) + { + d = seg.StartPoint().DistanceSquared(QueryPoint); + } + else + { + d = (seg.PointAt(t) - QueryPoint).SquaredLength(); + } + if (d < dist) + { + dist = d; + NearestSegIndexOut = vi; + NearestSegParamOut = TMathUtil::Clamp(t, -seg.Extent, seg.Extent); + } + } + return dist; + } + + + /** + * Calculate the squared distance from a point to the polygon + * @param QueryPoint the query point + * @return squared distance to the polygon + */ + T DistanceSquared(const FVector2& QueryPoint) const + { + int seg; T segt; + return DistanceSquared(QueryPoint, seg, segt); + } + + + /** + * @return average edge length of all the edges of the Polygon + */ + T AverageEdgeLength() const + { + T avg = 0; int N = Vertices.Num(); + for (int i = 1; i < N; ++i) { + avg += Vertices[i].Distance(Vertices[i - 1]); + } + avg += Vertices[N - 1].Distance(Vertices[0]); + return avg / N; + } + + + /** + * Translate the polygon + * @returns the Polygon, so that you can chain calls like Translate().Scale() + */ + TPolygon2& Translate(const FVector2& Translate) + { + int N = Vertices.Num(); + for (int i = 0; i < N; ++i) + { + Vertices[i] += Translate; + } + Timestamp++; + return *this; + } + + /** + * Scale the Polygon relative to a given point + * @returns the Polygon, so that you can chain calls like Translate().Scale() + */ + TPolygon2& Scale(const FVector2& Scale, const FVector2& Origin) + { + int N = Vertices.Num(); + for (int i = 0; i < N; ++i) + { + Vertices[i] = Scale * (Vertices[i] - Origin) + Origin; + } + Timestamp++; + return *this; + } + + + /** + * Apply an arbitrary transformation to the Polygon + * @returns the Polygon, so that you can chain calls like Translate().Scale() + */ + TPolygon2& Transform(const TFunction (const FVector2&)>& TransformFunc) + { + int N = Vertices.Num(); + for (int i = 0; i < N; ++i) + { + Vertices[i] = TransformFunc(Vertices[i]); + } + Timestamp++; + return *this; + } + + + + + /** + * Offset each point by the given Distance along vertex "normal" direction + * @param OffsetDistance the distance to offset + * @param bUseFaceAvg if true, we offset by the average-face normal instead of the perpendicular-tangent normal + */ + void VtxNormalOffset(T OffsetDistance, bool bUseFaceAvg = false) + { + TArray> NewVertices; + NewVertices.SetNumUninitialized(Vertices.Num()); + if (bUseFaceAvg) + { + for (int k = 0; k < Vertices.Num(); ++k) + { + NewVertices[k] = Vertices[k] + OffsetDistance * GetNormal_FaceAvg(k); + } + } + else + { + for (int k = 0; k < Vertices.Num(); ++k) + { + NewVertices[k] = Vertices[k] + OffsetDistance * GetNormal(k); + } + } + for (int k = 0; k < Vertices.Num(); ++k) + { + Vertices[k] = NewVertices[k]; + } + + Timestamp++; + } + + + /** + * Offset polygon by fixed distance, by offsetting and intersecting edges. + * CounterClockWise Polygon offsets "outwards", ClockWise "inwards". + */ + void PolyOffset(T OffsetDistance) + { + // [TODO] possibly can do with half as many normalizes if we do w/ sequential edges, + // rather than centering on each v? + TArray> NewVertices; + NewVertices.SetNumUninitialized(Vertices.Num()); + for (int k = 0; k < Vertices.Num(); ++k) + { + FVector2 v = Vertices[k]; + FVector2 next = Vertices[(k + 1) % Vertices.Num()]; + FVector2 prev = Vertices[k == 0 ? Vertices.Num() - 1 : k - 1]; + FVector2 dn = (next - v).Normalized(); + FVector2 dp = (prev - v).Normalized(); + TLine2 ln(v + OffsetDistance * dn.Perp(), dn); + TLine2 lp(v - OffsetDistance * dp.Perp(), dp); + + bool bIntersectsAtPoint = ln.IntersectionPoint(lp, NewVertices[k]); + if (!bIntersectsAtPoint) // lines were parallel + { + NewVertices[k] = Vertices[k] + OffsetDistance * GetNormal_FaceAvg(k); + } + } + for (int k = 0; k < Vertices.Num(); ++k) + { + Vertices[k] = NewVertices[k]; + } + + Timestamp++; + } + + +private: + // Polygon simplification + // code adapted from: http://softsurfer.com/Archive/algorithm_0205/algorithm_0205.htm + // simplifyDP(): + // This is the Douglas-Peucker recursive simplification routine + // It just marks Vertices that are part of the simplified polyline + // for approximating the polyline subchain v[j] to v[k]. + // Input: tol = approximation tolerance + // v[] = polyline array of vertex points + // j,k = indices for the subchain v[j] to v[k] + // Output: mk[] = array of markers matching vertex array v[] + static void SimplifyDouglasPeucker(T Tolerance, const TArray>& Vertices, int j, int k, TArray& Marked) + { + Marked.SetNum(Vertices.Num()); + if (k <= j + 1) // there is nothing to simplify + return; + + // check for adequate approximation by segment S from v[j] to v[k] + int maxi = j; // index of vertex farthest from S + T maxd2 = 0; // distance squared of farthest vertex + T tol2 = Tolerance * Tolerance; // tolerance squared + TSegment2 S = TSegment2(Vertices[j], Vertices[k]); // segment from v[j] to v[k] + + // test each vertex v[i] for max distance from S + // Note: this works in any dimension (2D, 3D, ...) + for (int i = j + 1; i < k; i++) + { + T dv2 = S.DistanceSquared(Vertices[i]); + if (dv2 <= maxd2) + continue; + // v[i] is a max vertex + maxi = i; + maxd2 = dv2; + } + if (maxd2 > tol2) // error is worse than the tolerance + { + // split the polyline at the farthest vertex from S + Marked[maxi] = true; // mark v[maxi] for the simplified polyline + // recursively simplify the two subpolylines at v[maxi] + SimplifyDouglasPeucker(Tolerance, Vertices, j, maxi, Marked); // polyline v[j] to v[maxi] + SimplifyDouglasPeucker(Tolerance, Vertices, maxi, k, Marked); // polyline v[maxi] to v[k] + } + // else the approximation is OK, so ignore intermediate Vertices + return; + } + + +public: + + /** + * Simplify the Polygon to reduce the vertex count + * @param ClusterTolerance Vertices closer than this distance will be merged into a single vertex + * @param LineDeviationTolerance Vertices are allowed to deviate this much from the input polygon lines + */ + void Simplify(T ClusterTolerance = 0.0001, T LineDeviationTolerance = 0.01) + { + int n = Vertices.Num(); + if (n < 3) + { + return; + } + + int i, k, pv; // misc counters + TArray> NewVertices; + NewVertices.SetNumUninitialized(n + 1); // vertex buffer + TArray Marked; + Marked.SetNumUninitialized(n + 1); + for (i = 0; i < n + 1; ++i) // marker buffer + { + Marked[i] = false; + } + + // STAGE 1. Vertex Reduction within tolerance of prior vertex cluster + T clusterTol2 = ClusterTolerance * ClusterTolerance; + NewVertices[0] = Vertices[0]; // start at the beginning + for (i = 1, k = 1, pv = 0; i < n; i++) + { + if (Vertices[i].DistanceSquared(Vertices[pv]) < clusterTol2) + { + continue; + } + NewVertices[k++] = Vertices[i]; + pv = i; + } + bool skip_dp = false; + if (k == 1) + { + NewVertices[k++] = Vertices[1]; + NewVertices[k++] = Vertices[2]; + skip_dp = true; + } + else if (k == 2) + { + NewVertices[k++] = Vertices[0]; + skip_dp = true; + } + + // push on start vertex again, because simplifyDP is for polylines, not polygons + NewVertices[k++] = Vertices[0]; + + // STAGE 2. Douglas-Peucker polyline simplification + int nv = 0; + if (skip_dp == false && LineDeviationTolerance > 0) + { + Marked[0] = Marked[k - 1] = true; // mark the first and last Vertices + SimplifyDouglasPeucker(LineDeviationTolerance, NewVertices, 0, k - 1, Marked); + for (i = 0; i < k - 1; ++i) + { + if (Marked[i]) + { + nv++; + } + } + } + else + { + for (i = 0; i < k; ++i) + { + Marked[i] = true; + } + nv = k - 1; + } + + // polygon requires at least 3 Vertices + if (nv == 2) + { + for (i = 1; i < k - 1; ++i) + { + if (Marked[1] == false) + { + Marked[1] = true; + } + else if (Marked[k - 2] == false) + { + Marked[k - 2] = true; + } + } + nv++; + } + else if (nv == 1) + { + Marked[1] = true; + Marked[2] = true; + nv += 2; + } + + // copy marked Vertices back to this polygon + Vertices.Reset(); + for (i = 0; i < k - 1; ++i) // last vtx is copy of first, and definitely marked + { + if (Marked[i]) + { + Vertices.Add(NewVertices[i]); + } + } + + Timestamp++; + return; + } + + + + /** + * Chamfer each vertex corner of the Polygon + * @param ChamferDist offset distance from corner that we cut at + */ + void Chamfer(T ChamferDist, T MinConvexAngleDeg = 30, T MinConcaveAngleDeg = 30) + { + check(IsClockwise()); + + TArray> OldV = Vertices; + int N = OldV.Num(); + Vertices.Reset(); + + int iCur = 0; + do { + FVector2 center = OldV[iCur]; + + int iPrev = (iCur == 0) ? N - 1 : iCur - 1; + FVector2 prev = OldV[iPrev]; + int iNext = (iCur + 1) % N; + FVector2 next = OldV[iNext]; + + FVector2 cp = prev - center; + T cpdist = cp.Normalize(); + FVector2 cn = next - center; + T cndist = cn.Normalize(); + + // if degenerate, skip this vert + if (cpdist < TMathUtil::ZeroTolerance || cndist < TMathUtil::ZeroTolerance) + { + iCur = iNext; + continue; + } + + T angle = cp.AngleD(cn); + // TODO document what this means sign-wise + // TODO re-test post Unreal port that this DotPerp is doing the right thing + T sign = cn.DotPerp(cp); + bool bConcave = (sign > 0); + + T thresh = (bConcave) ? MinConcaveAngleDeg : MinConvexAngleDeg; + + // ok not too sharp + if (angle > thresh) + { + Vertices.Add(center); + iCur = iNext; + continue; + } + + + T prev_cut_dist = TMathUtil::Min(ChamferDist, cpdist*0.5); + FVector2 prev_cut = center + cp * prev_cut_dist; + T next_cut_dist = TMathUtil::Min(ChamferDist, cndist * 0.5); + FVector2 next_cut = center + cn * next_cut_dist; + + Vertices.Add(prev_cut); + Vertices.Add(next_cut); + iCur = iNext; + } while (iCur != 0); + + Timestamp++; + } + + + + + /** + * Construct a four-vertex rectangle Polygon + */ + static TPolygon2 MakeRectangle(const FVector2& Center, T Width, T Height) + { + TPolygon2 Rectangle; + Rectangle.Vertices.SetNumUninitialized(4); + Rectangle.Set(0, FVector2(Center.X - Width / 2, Center.Y - Height / 2)); + Rectangle.Set(1, FVector2(Center.X + Width / 2, Center.Y - Height / 2)); + Rectangle.Set(2, FVector2(Center.X + Width / 2, Center.Y + Height / 2)); + Rectangle.Set(3, FVector2(Center.X - Width / 2, Center.Y + Height / 2)); + return Rectangle; + } + + + /** + * Construct a circular Polygon + */ + static TPolygon2 MakeCircle(T Radius, int Steps, T AngleShiftRadians = 0) + { + TPolygon2 Circle; + Circle.Vertices.SetNumUninitialized(Steps); + + for (int i = 0; i < Steps; ++i) + { + T t = (T)i / (T)Steps; + T a = TMathUtil::TwoPi * t + AngleShiftRadians; + Circle.Set(i, FVector2(Radius * TMathUtil::Cos(a), Radius * TMathUtil::Sin(a))); + } + + return Circle; + } +}; + +typedef TPolygon2 FPolygon2d; +typedef TPolygon2 FPolygon2f; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Polyline3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Polyline3.h new file mode 100644 index 000000000000..2da246fad5f6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Polyline3.h @@ -0,0 +1,443 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "VectorTypes.h" +#include "SegmentTypes.h" +#include "LineTypes.h" +#include "RayTypes.h" +#include "BoxTypes.h" + +/** + * TPolyline3 represents a 3D polyline stored as a list of Vertices. + * + * @todo move operators + */ +template +class TPolyline3 +{ +protected: + /** The list of vertices of the polyline */ + TArray> Vertices; + + /** A counter that is incremented every time the polyline vertices are modified */ + int Timestamp; + +public: + + + TPolyline3() : Timestamp(0) + { + } + + /** + * Construct polyline that is a copy of another polyline + */ + TPolyline3(const TPolyline3& Copy) : Vertices(Copy.Vertices), Timestamp(Copy.Timestamp) + { + } + + /** + * Construct polyline with given list of vertices + */ + TPolyline3(const TArray>& VertexList) : Vertices(VertexList), Timestamp(0) + { + } + + /** @return the Timestamp for the polyline, which is updated every time the polyline is modified */ + int GetTimestamp() const + { + return Timestamp; + } + + /** Explicitly increment the Timestamp */ + void IncrementTimestamp() + { + Timestamp++; + } + + /** + * Get the vertex at a given index + */ + const FVector3& operator[](int Index) const + { + return Vertices[Index]; + } + + /** + * Get the vertex at a given index + * @warning changing the vertex via this operator does not update Timestamp! + */ + FVector3& operator[](int Index) + { + return Vertices[Index]; + } + + + /** + * @return first vertex of polyline + */ + const FVector3& Start() const + { + return Vertices[0]; + } + + /** + * @return last vertex of polyline + */ + const FVector3& End() const + { + return Vertices[Vertices.Num()-1]; + } + + + /** + * @return list of Vertices of polyline + */ + const TArray>& GetVertices() const + { + return Vertices; + } + + /** + * @return number of Vertices in polyline + */ + int VertexCount() const + { + return Vertices.Num(); + } + + /** + * @return number of segments in polyline + */ + int SegmentCount() const + { + return Vertices.Num()-1; + } + + + /** Discard all vertices of polyline */ + void Clear() + { + Vertices.Reset(); + Timestamp++; + } + + /** + * Add a vertex to the polyline + */ + void AppendVertex(const FVector3& Position) + { + Vertices.Add(Position); + Timestamp++; + } + + /** + * Add a list of Vertices to the polyline + */ + void AppendVertices(const TArray>& NewVertices) + { + Vertices.Append(NewVertices); + Timestamp++; + } + + /** + * Set vertex at given index to a new Position + */ + void Set(int VertexIndex, const FVector3& Position) + { + Vertices[VertexIndex] = Position; + Timestamp++; + } + + /** + * Remove a vertex of the polyline (existing Vertices are shifted) + */ + void RemoveVertex(int VertexIndex) + { + Vertices.RemoveAt(VertexIndex); + Timestamp++; + } + + /** + * Replace the list of Vertices with a new list + */ + void SetVertices(const TArray>& NewVertices) + { + int NumVerts = NewVertices.Num(); + Vertices.SetNum(NumVerts, false); + for (int k = 0; k < NumVerts; ++k) + { + Vertices[k] = NewVertices[k]; + } + Timestamp++; + } + + + /** + * Reverse the order of the Vertices in the polyline (ie switch between Clockwise and CounterClockwise) + */ + void Reverse() + { + int32 j = Vertices.Num() - 1; + for (int32 VertexIndex = 0; VertexIndex < j; VertexIndex++, j--) + { + Swap(Vertices[VertexIndex], Vertices[j]); + } + Timestamp++; + } + + + /** + * Get the tangent vector at a vertex of the polyline, which is the normalized + * vector from the previous vertex to the next vertex + */ + FVector3 GetTangent(int VertexIndex) const + { + if (VertexIndex == 0) + { + return (Vertices[1] - Vertices[0]).Normalized(); + } + int NumVerts = Vertices.Num(); + if (VertexIndex == NumVerts - 1) + { + return (Vertices[NumVerts-1] - Vertices[NumVerts-2]).Normalized(); + } + return (Vertices[VertexIndex+1] - Vertices[VertexIndex-1]).Normalized(); + } + + + + /** + * @return edge of the poyline starting at vertex SegmentIndex + */ + TSegment3 GetSegment(int SegmentIndex) const + { + return TSegment3(Vertices[SegmentIndex], Vertices[SegmentIndex+1]); + } + + + /** + * @param SegmentIndex index of first vertex of the edge + * @param SegmentParam parameter in range [-Extent,Extent] along segment + * @return point on the segment at the given parameter value + */ + FVector3 GetSegmentPoint(int SegmentIndex, T SegmentParam) const + { + TSegment3 seg(Vertices[SegmentIndex], Vertices[SegmentIndex + 1]); + return seg.PointAt(SegmentParam); + } + + + /** + * @param SegmentIndex index of first vertex of the edge + * @param SegmentParam parameter in range [0,1] along segment + * @return point on the segment at the given parameter value + */ + FVector3 GetSegmentPointUnitParam(int SegmentIndex, T SegmentParam) const + { + TSegment3 seg(Vertices[SegmentIndex], Vertices[SegmentIndex + 1]); + return seg.PointBetween(SegmentParam); + } + + + + /** + * @return the bounding box of the polyline Vertices + */ + TAxisAlignedBox3 GetBounds() const + { + TAxisAlignedBox3 box = TAxisAlignedBox3::Empty(); + int NumVertices = Vertices.Num(); + for (int k = 0; k < NumVertices; ++k) + { + box.Contain(Vertices[k]); + } + return box; + } + + + + /** + * @return the total perimeter length of the Polygon + */ + T Length() const + { + T length = 0; + int N = SegmentCount(); + for (int i = 0; i < N; ++i) + { + length += Vertices[i].Distance(Vertices[i+1]); + } + return length; + } + + + + /** + * SegmentIterator is used to iterate over the TSegment3 segments of the polyline + */ + class SegmentIterator + { + public: + inline bool operator!() + { + return i < Polyline->SegmentCount(); + } + inline TSegment3 operator*() const + { + check(Polyline != nullptr && i < Polyline->SegmentCount()); + return TSegment3(Polyline->Vertices[i], Polyline->Vertices[i+1]); + } + inline SegmentIterator & operator++() // prefix + { + i++; + return *this; + } + inline SegmentIterator operator++(int) // postfix + { + SegmentIterator copy(*this); + i++; + return copy; + } + inline bool operator==(const SegmentIterator & i3) { return i3.Polyline == Polyline && i3.i == i; } + inline bool operator!=(const SegmentIterator & i3) { return i3.Polyline != Polyline || i3.i != i; } + protected: + const TPolyline3 * Polyline; + int i; + inline SegmentIterator(const TPolyline3 * p, int iCur) : Polyline(p), i(iCur) {} + friend class TPolyline3; + }; + friend class SegmentIterator; + + SegmentIterator SegmentItr() const + { + return SegmentIterator(this, 0); + } + + /** + * Wrapper around SegmentIterator that has begin() and end() suitable for range-based for loop + */ + class SegmentEnumerable + { + public: + const TPolyline3* Polyline; + SegmentEnumerable() : Polyline(nullptr) {} + SegmentEnumerable(const TPolyline3 * p) : Polyline(p) {} + SegmentIterator begin() { return Polyline->SegmentItr(); } + SegmentIterator end() { return SegmentIterator(Polyline, Polyline->SegmentCount()); } + }; + + /** + * @return an object that can be used in a range-based for loop to iterate over the Segments of the Polyline + */ + SegmentEnumerable Segments() const + { + return SegmentEnumerable(this); + } + + + + + + + /** + * Calculate the squared distance from a point to the polyline + * @param QueryPoint the query point + * @param NearestSegIndexOut The index of the nearest segment + * @param NearestSegParamOut the parameter value of the nearest point on the segment + * @return squared distance to the polyline + */ + T DistanceSquared(const FVector3& QueryPoint, int& NearestSegIndexOut, T& NearestSegParamOut) const + { + NearestSegIndexOut = -1; + NearestSegParamOut = TNumericLimits::Max(); + T dist = TNumericLimits::Max(); + int N = SegmentCount(); + for (int vi = 0; vi < N; ++vi) + { + // @todo can't we just use segment function here now? + TSegment3 seg = TSegment3(Vertices[vi], Vertices[vi+1]); + T t = (QueryPoint - seg.Center).Dot(seg.Direction); + T d = TNumericLimits::Max(); + if (t >= seg.Extent) + { + d = seg.EndPoint().DistanceSquared(QueryPoint); + } + else if (t <= -seg.Extent) + { + d = seg.StartPoint().DistanceSquared(QueryPoint); + } + else + { + d = (seg.PointAt(t) - QueryPoint).SquaredLength(); + } + if (d < dist) + { + dist = d; + NearestSegIndexOut = vi; + NearestSegParamOut = TMathUtil::Clamp(t, -seg.Extent, seg.Extent); + } + } + return dist; + } + + + + /** + * Calculate the squared distance from a point to the polyline + * @param QueryPoint the query point + * @return squared distance to the polyline + */ + T DistanceSquared(const FVector3& QueryPoint) const + { + int seg; T segt; + return DistanceSquared(QueryPoint, seg, segt); + } + + + + + /** + * @return average edge length of all the edges of the Polygon + */ + T AverageEdgeLength() const + { + T avg = 0; int N = Vertices.Num(); + for (int i = 1; i < N; ++i) { + avg += Vertices[i].Distance(Vertices[i - 1]); + } + return avg / (T)(N-1); + } + + + + /** + * Produce a new polyline that is smoother than this one + */ + void SmoothSubdivide(TPolyline3& NewPolyline) const + { + const T Alpha = (T)1 / (T)3; + const T OneMinusAlpha = (T)2 / (T)3; + + int N = Vertices.Num(); + NewPolyline.Vertices.SetNum(2*(N-1)); + NewPolyline.Vertices[0] = Vertices[0]; + N = N - 1; + int k = 1; + for (int i = 1; i < N; ++i) + { + const FVector3& Prev = Vertices[i-1]; + const FVector3& Cur = Vertices[i]; + const FVector3& Next = Vertices[i+1]; + NewPolyline.Vertices[k++] = Alpha * Prev + OneMinusAlpha * Cur; + NewPolyline.Vertices[k++] = OneMinusAlpha * Cur + Alpha * Next; + } + NewPolyline.Vertices[k] = Vertices[N]; + } + + +}; + +typedef TPolyline3 FPolyline3d; +typedef TPolyline3 FPolyline3f; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/QuadricError.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/QuadricError.h new file mode 100644 index 000000000000..a6f52ff7b99e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/QuadricError.h @@ -0,0 +1,690 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MathUtil.h" +#include "VectorTypes.h" +#include "MatrixTypes.h" + + +/** + * QuadricError represents a quadratic function that evaluates distance to plane. + * Stores minimal 10-coefficient form, following http://mgarland.org/files/papers/qtheory.pdf + * (symmetric matrix A, vector b, constant c) + */ +template +struct TQuadricError +{ + RealType Axx, Axy, Axz, Ayy, Ayz, Azz; + RealType bx, by, bz; //d = -normal.dot(c); b = d*normal + RealType c; // d*d + + inline static TQuadricError Zero() { return TQuadricError(); }; + + TQuadricError() + { + Axx = Axy = Axz = Ayy = Ayz = Azz = bx = by = bz = c = 0; + } + + /** + * Construct TQuadricError a plane with the given normal and a point on plane + */ + TQuadricError(const FVector3& Normal, const FVector3& Point) + { + Axx = Normal.X * Normal.X; + Axy = Normal.X * Normal.Y; + Axz = Normal.X * Normal.Z; + Ayy = Normal.Y * Normal.Y; + Ayz = Normal.Y * Normal.Z; + Azz = Normal.Z * Normal.Z; + bx = by = bz = c = 0; + FVector3 v = MultiplyA(Point); + bx = -v.X; by = -v.Y; bz = -v.Z; // b = -Normal.Dot(Point) Normal + c = Point.Dot(v); // note this is the same as Normal.Dot(Point) ^2 + } + + /** + * Construct TQuadricError that is the sum of two other TQuadricErrors + */ + TQuadricError(const TQuadricError& a, const TQuadricError& b) + { + Axx = a.Axx + b.Axx; + Axy = a.Axy + b.Axy; + Axz = a.Axz + b.Axz; + Ayy = a.Ayy + b.Ayy; + Ayz = a.Ayz + b.Ayz; + Azz = a.Azz + b.Azz; + bx = a.bx + b.bx; + by = a.by + b.by; + bz = a.bz + b.bz; + c = a.c + b.c; + } + + + /** + * Add scalar multiple of a TQuadricError to this TQuadricError + */ + void Add(RealType w, const TQuadricError& b) + { + Axx += w * b.Axx; + Axy += w * b.Axy; + Axz += w * b.Axz; + Ayy += w * b.Ayy; + Ayz += w * b.Ayz; + Azz += w * b.Azz; + bx += w * b.bx; + by += w * b.by; + bz += w * b.bz; + c += w * b.c; + } + + void Add(const TQuadricError& b) + { + Axx += b.Axx; + Axy += b.Axy; + Axz += b.Axz; + Ayy += b.Ayy; + Ayz += b.Ayz; + Azz += b.Azz; + bx += b.bx; + by += b.by; + bz += b.bz; + c += b.c; + } + + TQuadricError& operator=(const TQuadricError& b) + { + Axx = b.Axx; + Axy = b.Axy; + Axz = b.Axz; + Ayy = b.Ayy; + Ayz = b.Ayz; + Azz = b.Azz; + bx = b.bx; + by = b.by; + bz = b.bz; + c = b.c; + + return *this; + } + + /** + * Evaluates p*A*p + 2*dot(p,b) + c + * @return + */ + RealType Evaluate(const FVector3& pt) const + { + RealType x = Axx * pt.X + Axy * pt.Y + Axz * pt.Z; + RealType y = Axy * pt.X + Ayy * pt.Y + Ayz * pt.Z; + RealType z = Axz * pt.X + Ayz * pt.Y + Azz * pt.Z; + return (pt.X * x + pt.Y * y + pt.Z * z) + + 2.0 * (pt.X * bx + pt.Y * by + pt.Z * bz) + c; + } + + /** + * + */ + FVector3 MultiplyA(const FVector3& pt) const + { + RealType x = Axx * pt.X + Axy * pt.Y + Axz * pt.Z; + RealType y = Axy * pt.X + Ayy * pt.Y + Ayz * pt.Z; + RealType z = Axz * pt.X + Ayz * pt.Y + Azz * pt.Z; + return FVector3(x, y, z); + } + + + bool SolveAxEqualsb(FVector3& OutResult, const RealType bvecx, const RealType bvecy, const RealType bvecz, const RealType minThresh = 1000.0*TMathUtil::Epsilon) const + { + RealType a11 = Azz * Ayy - Ayz * Ayz; + RealType a12 = Axz * Ayz - Azz * Axy; + RealType a13 = Axy * Ayz - Axz * Ayy; + RealType a22 = Azz * Axx - Axz * Axz; + RealType a23 = Axy * Axz - Axx * Ayz; + RealType a33 = Axx * Ayy - Axy * Axy; + RealType det = (Axx * a11) + (Axy * a12) + (Axz * a13); + + // [RMS] not sure what we should be using for this threshold...have seen + // det less than 10^-9 on "normal" meshes. + if (FMath::Abs(det) > minThresh) + { + det = 1.0 / det; + a11 *= det; a12 *= det; a13 *= det; + a22 *= det; a23 *= det; a33 *= det; + RealType x = a11 * bvecx + a12 * bvecy + a13 * bvecz; + RealType y = a12 * bvecx + a22 * bvecy + a23 * bvecz; + RealType z = a13 * bvecx + a23 * bvecy + a33 * bvecz; + OutResult = FVector3(x, y, z); + return true; + } + else + { + return false; + } + } + + static bool InvertSymmetricMatrix(const RealType SM[6], RealType InvSM[6], RealType minThresh = 1000.0*TMathUtil::Epsilon) + { + bool Result = false; + // Axx = SM[0]; Axy = SM[1]; Axz = SM[2] + // Ayy = SM[3]; Ayz = SM[4] + // Azz = SM[5] + + InvSM[0] = SM[5] * SM[3] - SM[4] * SM[4]; //s11 + InvSM[1] = SM[2] * SM[4] - SM[5] * SM[1]; //s12 + InvSM[2] = SM[1] * SM[4] - SM[2] * SM[3]; //s13 + InvSM[3] = SM[5] * SM[0] - SM[2] * SM[2]; //s22 + InvSM[4] = SM[1] * SM[2] - SM[0] * SM[4]; //s23 + InvSM[5] = SM[0] * SM[3] - SM[1] * SM[1]; //s33 + RealType Det = (SM[0] * InvSM[0]) + (SM[1] * InvSM[1]) + (SM[2] * InvSM[2]); + if (FMath::Abs(Det) > minThresh) + { + RealType InvDet = RealType(1) / Det; + InvSM[0] *= InvDet; InvSM[1] *= InvDet; InvSM[2] *= InvDet; + InvSM[3] *= InvDet; InvSM[4] *= InvDet; + InvSM[5] *= InvDet; + Result = true; + } + return Result; + } + + static FVector3 MultiplySymmetricMatrix(const RealType SM[6], const RealType vec[3]) + { + + RealType a = SM[0] * vec[0] + SM[1] * vec[1] + SM[2] * vec[2]; + RealType b = SM[1] * vec[0] + SM[3] * vec[1] + SM[4] * vec[2]; + RealType c = SM[2] * vec[0] + SM[4] * vec[1] + SM[5] * vec[2]; + + return FVector3(a, b, c); + } + static FVector3 MultiplySymmetricMatrix(const RealType SM[6], const FVector3& vec) + { + RealType vectmp[3] = { vec.X, vec.Y, vec.Z }; + return MultiplySymmetricMatrix(SM, vectmp); + } + + + bool OptimalPoint(FVector3& OutResult, RealType minThresh = 1000.0*TMathUtil::Epsilon ) const + { + return SolveAxEqualsb(OutResult, -bx, -by, -bz, minThresh); + } + +}; + + +typedef TQuadricError FQuadricErrorf; +typedef TQuadricError FQuadricErrord; + +/** +* Quadric Error type for use in memory-less simplification with volume preservation constraints. +* +* See: http://hhoppe.com/newqem.pdf or https://www.cc.gatech.edu/~turk/my_papers/memless_vis98.pdf +* for information about the volume preservation. +*/ +template +class TVolPresQuadricError : public TQuadricError +{ +public: + + typedef TQuadricError BaseStruct; + + struct FPlaneData + { + FPlaneData(const FVector3& Normal, const FVector3& Point) + { + N = Normal; + Dist = -Normal.Dot(Point); + } + + FPlaneData(RealType w, const FPlaneData& other) + { + N = w * other.N; + Dist = w * other.Dist; + } + + FPlaneData(const FPlaneData& other) + { + N = other.N; + Dist = other.Dist; + } + + FPlaneData() + { + N = FVector3::Zero(); + Dist = RealType(0); + } + + void Add(const FPlaneData& other) + { + N += other.N; + Dist += other.Dist; + } + + void Add(RealType w, const FPlaneData& other) + { + N += w * other.N; + Dist += w * other.Dist; + } + + FPlaneData& operator=(const FPlaneData& other) + { + N = other.N; + Dist = other.Dist; + + return *this; + } + + FVector3 N; + RealType Dist; + }; + + FPlaneData PlaneData; + +public: + + inline static TVolPresQuadricError Zero() { return TVolPresQuadricError(); }; + + TVolPresQuadricError() : BaseStruct() + { + } + + TVolPresQuadricError(const FVector3& Normal, const FVector3& Point) + : BaseStruct(Normal, Point) + , PlaneData(Normal, Point) + { } + + + TVolPresQuadricError(const TVolPresQuadricError& a, const TVolPresQuadricError& b) + : BaseStruct(a, b) + , PlaneData(a.PlaneData) + { + PlaneData.Add(b.PlaneData); + } + + TVolPresQuadricError(const TVolPresQuadricError& a, const TVolPresQuadricError& b, const FPlaneData& DuplicatePlaneData) + : BaseStruct(a, b) + , PlaneData(a.PlaneData) + { + PlaneData.Add(b.PlaneData); + + // Subtract a single copy of the duplicate plane data. + PlaneData.Add(-1., DuplicatePlaneData); + } + + void Add(RealType w, const TVolPresQuadricError& b) + { + BaseStruct::Add(w, b); + + PlaneData.Add(w, b.PlaneData); + } + + void Add(const TVolPresQuadricError& b) + { + BaseStruct::Add(b); + PlaneData.Add(b.PlaneData); + } + + TVolPresQuadricError& operator=(const TVolPresQuadricError& other) + { + BaseStruct::operator=(other); + PlaneData = other.PlaneData; + return *this; + } + + bool OptimalPoint(FVector3& OutResult, RealType minThresh = 1000.0*TMathUtil::Epsilon) const + { + // Compute the unconstrained optimal point + bool bValid = BaseStruct::OptimalPoint(OutResult, minThresh); + + // if it failed ( the A matrix wasn't invertible) we early out + if (!bValid) + { + return bValid; + } + + // Adjust with the volume constraint. + // See: http://hhoppe.com/newqem.pdf or https://www.cc.gatech.edu/~turk/my_papers/memless_vis98.pdf + + FPlaneData gvol(1. / 3., PlaneData); + + // Compute the effect of the volumetric constraint. + RealType Tol = minThresh; //(1.e-7); NB: using minThresh here to better compare with the attribute version.. + //constexpr RealType Tol = (1.e-7); //NB: using minThresh here to better compare with the attribute version.. + + FVector3 Ainv_g; + if (BaseStruct::SolveAxEqualsb(Ainv_g, gvol.N.X, gvol.N.Y, gvol.N.Z, Tol)) + { + + RealType gt_Ainv_g = gvol.N.Dot(Ainv_g); + + // move the point to the constrained optimal + RealType gt_unopt = gvol.N.Dot(OutResult); + RealType lambda = (gvol.Dist + gt_unopt) / gt_Ainv_g; + + // Check if the constraint failed. + + if (FMath::Abs(lambda) > 1.e5) + { + return bValid; + } + + OutResult -= lambda * Ainv_g; + } + + return bValid; + + + } +}; + + +typedef TVolPresQuadricError FVolPresQuadricErrorf; +typedef TVolPresQuadricError FVolPresQuadricErrord; + +/** +* Quadric Error type for use in volume memory-less simplification with volume preservation constraints. +* using the normal as three additional attributes to contribute to the quadric error. +* See: http://hhoppe.com/newqem.pdf +*/ +template +class TAttrBasedQuadricError : public TVolPresQuadricError +{ +public: + typedef TVolPresQuadricError BaseClass; + typedef TQuadricError BaseStruct; + + // Triangle Quadric constructor. Take vertex locations, vertex normals, face normal, and center of face. + TAttrBasedQuadricError(const FVector3& P0, const FVector3& P1, const FVector3& P2, + const FVector3& N0, const FVector3& N1, const FVector3& N2, + const FVector3& NFace, const FVector3& CenterPoint, RealType AttrWeight) + : BaseClass(NFace, CenterPoint), a(0) + { + /** + * Given scalar attribute 'a' defined at the vertices e.g. a0, a1, a2 + * + * solve 4x4 system: + * (p0^t 1) (g[0]) = (a0) + * (p1^t 1) (g[1]) (a1) + * (p2^t 1) (g[2]) (a2) + * (n^t 0) ( d ) (0 ) + * + * for the interpolating gradient 'g' -- note this is really p0.dot(g) + d = a0 and n.dot(g) = 0 + * + * in practice we actually use a Schur complement approach that only requires inverting a 3x3 matrix. + * + * For this quadric, each component of the normal is treated as separate attribute. + */ + TMatrix3 Matrix(P0, P1, P2, true); // row-based matrix constructor. + + bool bSolved = false; + double det = Matrix.Determinant(); + if (FMath::Abs(det) > 1.e-5) + { + FMatrix3d InvMatrix = Matrix.Inverse(); + FVector3d NFaceT_InvMatrix; + for (int i = 0; i < 3; ++i) + { + NFaceT_InvMatrix[i] = NFace[0] * InvMatrix.Row0[i] + NFace[1] * InvMatrix.Row1[i] + NFace[2] * InvMatrix.Row2[i]; + } + + double NFaceDotInvMOne = NFaceT_InvMatrix.Dot(FVector3d(1, 1, 1)); + + if (FMath::Abs(NFaceDotInvMOne) > 1.e-5) + { + bSolved = true; + for (int i = 0; i < 3; ++i) + { + FVector3d AttrVec(N0[i], N1[i], N2[i]); + AttrVec *= AttrWeight; + + d[i] = NFaceT_InvMatrix.Dot(AttrVec) / NFaceDotInvMOne; + grad[i] = InvMatrix * (AttrVec - FVector3d(d[i], d[i], d[i])); + + BaseStruct::Axx += grad[i].X * grad[i].X; + + BaseStruct::Axy += grad[i].X * grad[i].Y; + BaseStruct::Axz += grad[i].X * grad[i].Z; + + BaseStruct::Ayy += grad[i].Y * grad[i].Y; + BaseStruct::Ayz += grad[i].Y * grad[i].Z; + BaseStruct::Azz += grad[i].Z * grad[i].Z; + + BaseStruct::bx += d[i] * grad[i].X; + BaseStruct::by += d[i] * grad[i].Y; + BaseStruct::bz += d[i] * grad[i].Z; + + BaseStruct::c += d[i] * d[i]; + } + } + + } + + if (!bSolved) + { + for (int i = 0; i < 3; ++i) + { + grad[i] = FVector3::Zero(); + d[i] = (N0[i] + N1[i] + N2[i]) / 3.; // just average the attribute. + } + } + } + + + TAttrBasedQuadricError() + :BaseClass() + { + a = 0.; + const RealType zero[3] = { RealType(0), RealType(0), RealType(0) }; + grad[0] = FVector3(zero); + grad[1] = FVector3(zero); + grad[2] = FVector3(zero); + + d[0] = RealType(0); + d[1] = RealType(0); + d[2] = RealType(0); + } + + TAttrBasedQuadricError(const TAttrBasedQuadricError& Aother, const TAttrBasedQuadricError& Bother) + :BaseClass(Aother, Bother) + { + a = Aother.a + Bother.a; + + grad[0] = Aother.grad[0] + Bother.grad[0]; + grad[1] = Aother.grad[1] + Bother.grad[1]; + grad[2] = Aother.grad[2] + Bother.grad[2]; + + d[0] = Aother.d[0] + Bother.d[0]; + d[1] = Aother.d[1] + Bother.d[1]; + d[2] = Aother.d[2] + Bother.d[2]; + } + + static TAttrBasedQuadricError Zero() { return TAttrBasedQuadricError(); } + + void Add(RealType w, const TAttrBasedQuadricError& other) + { + BaseClass::Add(w, other); + + a += w; // accumulate weight + + grad[0] += w * other.grad[0]; + grad[1] += w * other.grad[1]; + grad[2] += w * other.grad[2]; + + d[0] += w * other.d[0]; + d[1] += w * other.d[1]; + d[2] += w * other.d[2]; + } + + void Add(const TAttrBasedQuadricError& other) + { + BaseClass::Add(other); + a += other.a; + + grad[0] += other.grad[0]; + grad[1] += other.grad[1]; + grad[2] += other.grad[2]; + + d[0] += other.d[0]; + d[1] += other.d[1]; + d[2] += other.d[2]; + } + + TAttrBasedQuadricError& operator=(const TAttrBasedQuadricError& other) + { + BaseClass::operator=(other); + + a = other.a; + + grad[0] = other.grad[0]; + grad[1] = other.grad[1]; + grad[2] = other.grad[2]; + + d[0] = other.d[0]; + d[1] = other.d[1]; + d[2] = other.d[2]; + + return *this; + } + + // The optimal point not counting volume conservation. + bool OptimalPoint(FVector3& OptPoint, RealType minThresh = 1000.0*TMathUtil::Epsilon) const + { + // Generate symmetric matrix + RealType SM[6] = { 0., 0., 0., 0., 0., 0. }; + + + for (int i = 0; i < 3; ++i) + { + SM[0] -= grad[i].X * grad[i].X; SM[1] -= grad[i].X * grad[i].Y; SM[2] -= grad[i].X * grad[i].Z; + SM[3] -= grad[i].Y * grad[i].Y; SM[4] -= grad[i].Y * grad[i].Z; + SM[5] -= grad[i].Z * grad[i].Z; + } + + RealType InvA = (FMath::Abs(a) > 1.e-7) ? RealType(1) / a : 0.; + + SM[0] *= InvA; SM[1] *= InvA; SM[2] *= InvA; + SM[3] *= InvA; SM[4] *= InvA; + SM[5] *= InvA; + + // A - (grad_attr0, grad_attr1.. grad_attrn) . (grad_attr0, grad_attr1.. grad_attrn)^T + + SM[0] += BaseStruct::Axx; SM[1] += BaseStruct::Axy; SM[2] += BaseStruct::Axz; + SM[3] += BaseStruct::Ayy; SM[4] += BaseStruct::Ayz; + SM[5] += BaseStruct::Azz; + + + RealType InvSM[6]; + bool bValid = BaseStruct::InvertSymmetricMatrix(SM, InvSM, minThresh); + + RealType b[3] = { -BaseStruct::bx, -BaseStruct::by, -BaseStruct::bz }; + b[0] += InvA * (grad[0].X * d[0] + grad[1].X * d[1] + grad[2].X * d[2]); + b[1] += InvA * (grad[0].Y * d[0] + grad[1].Y * d[1] + grad[2].Y * d[2]); + b[2] += InvA * (grad[0].Z * d[0] + grad[1].Z * d[1] + grad[2].Z * d[2]); + + // add volume constraint + typename BaseClass::FPlaneData gvol(1. / 3., BaseClass::PlaneData); + + if (bValid) + { + // optimal point prior to volume correction + + OptPoint = BaseStruct::MultiplySymmetricMatrix(InvSM, b); + //SolveAxEqualsb(OptPoint, b[0], b[1], b[2]); + + FVector3 InvSMgvol = BaseStruct::MultiplySymmetricMatrix(InvSM, gvol.N); + //SolveAxEqualsb(InvSMgvol, gvol.N.X, gvol.N.Y, gvol.N.Z); + + RealType gvolDotInvSMgvol = gvol.N.Dot(InvSMgvol); + + // move the point to the constrained optimal + RealType gvolDotuncopt = gvol.N.Dot(OptPoint); + RealType lambda = (gvol.Dist + gvolDotuncopt) / gvolDotInvSMgvol; + + // Check if the constraint failed. + + if (FMath::Abs(lambda) > 1.e5) + { + return bValid; + } + + OptPoint -= lambda * InvSMgvol; + } + + return bValid; + + } + + RealType Evaluate(const FVector3& point, const FVector3& attr) const + { + // 6x6 symmetric matrix ( A -grad[0], -grad[1], -grad[2] ) + // ( -grad[0]^T ) + // ( -grad[1]^T aI ) + // ( -grad[2]^T ) + // + // aI is Identity matrix scaled by the accumulated area 'a' + // + // extended state vector v = ( point ) + // ( attr ) + // + + + RealType ptAp = point.Dot(BaseStruct::MultiplyA(point)); + RealType attrDotattr = attr.Dot(attr); + FVector3 Gradattr(grad[0].X * attr[0] + grad[1].X * attr[1] + grad[2].X * attr[2], + grad[0].Y * attr[0] + grad[1].Y * attr[1] + grad[2].Y * attr[2], + grad[0].Z * attr[0] + grad[1].Z * attr[1] + grad[2].Z * attr[2]); + RealType ptGradattr = point.Dot(Gradattr); + + RealType vtSv = ptAp + a * attrDotattr - RealType(2) * ptGradattr; + + // extended 'b' vector + // ( b ) + // (-d ) + // compute 2 + RealType vtb = point.X * BaseStruct::bx + point.Y * BaseStruct::by + point.Z * BaseStruct::bz + - (d[0] * attr[0] + d[1] * attr[1] + d[2] * attr[2]); + + RealType QuadricError = vtSv + RealType(2) * vtb + BaseStruct::c; + + return QuadricError; + } + + void ComputeAttributes(const FVector3& point, FVector3& attr) const + { + if (FMath::Abs(a) > 1.e-5) + { + RealType aInv = RealType(1) / a; + + attr.X = grad[0].Dot(point) + d[0]; + attr.Y = grad[1].Dot(point) + d[1]; + attr.Z = grad[2].Dot(point) + d[2]; + + attr *= aInv; + } + else + { + attr.X = RealType(0); + attr.Y = RealType(0); + attr.Z = RealType(0); + } + } + + RealType Evaluate(const FVector3& point) const + { + FVector3 attr; + ComputeAttributes(point, attr); + + return Evaluate(point, attr); + } + +public: + + RealType a; + // Additional planes for the attributes. + FVector3 grad[3]; + RealType d[3]; + + +}; + +typedef TAttrBasedQuadricError FAttrBasedQuadricErrorf; +typedef TAttrBasedQuadricError FAttrBasedQuadricErrord; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Quaternion.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Quaternion.h new file mode 100644 index 000000000000..48313f2e59ee --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Quaternion.h @@ -0,0 +1,456 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "VectorTypes.h" +#include "MatrixTypes.h" +#include "IndexTypes.h" + +// ported from geometry3Sharp Quaternion + +template +struct TQuaternion +{ + // note: in Wm5 version, this is a 4-element arraY stored in order (w,x,y,z). + RealType X; + RealType Y; + RealType Z; + RealType W; + + TQuaternion(); + TQuaternion(RealType X, RealType Y, RealType Z, RealType W); + TQuaternion(const RealType* Values); + TQuaternion(const TQuaternion& Copy); + TQuaternion(const FVector3& Axis, RealType Angle, bool bAngleIsDegrees); + TQuaternion(const FVector3& From, const FVector3& To); + TQuaternion(const TQuaternion& From, const TQuaternion& To, RealType InterpT); + TQuaternion(const TMatrix3& RotationMatrix); + + void SetAxisAngleD(const FVector3& Axis, RealType AngleDeg); + void SetAxisAngleR(const FVector3& Axis, RealType AngleRad); + void SetFromTo(const FVector3& From, const FVector3& To); + void SetToSlerp(const TQuaternion& From, const TQuaternion& To, RealType InterpT); + void SetFromRotationMatrix(const TMatrix3& RotationMatrix); + + static TQuaternion Zero() { return TQuaternion((RealType)0, (RealType)0, (RealType)0, (RealType)0); } + static TQuaternion Identity() { return TQuaternion((RealType)0, (RealType)0, (RealType)0, (RealType)1); } + + RealType & operator[](int i) { return (&X)[i]; } + const RealType & operator[](int i) const { return (&X)[i]; } + + bool EpsilonEqual(const TQuaternion& Other, RealType Epsilon) const; + + RealType Length() const { return (RealType)sqrt(X*X + Y*Y + Z*Z + W*W); } + RealType SquaredLength() const { return X*X + Y*Y + Z*Z + W*W; } + + FVector3 AxisX() const; + FVector3 AxisY() const; + FVector3 AxisZ() const; + + RealType Normalize(const RealType epsilon = 0); + TQuaternion Normalized(const RealType epsilon = 0) const; + + RealType Dot(const TQuaternion& Other) const; + + TQuaternion Inverse() const; + FVector3 InverseMultiply(const FVector3& Other) const; + + TMatrix3 ToRotationMatrix() const; + + + // available for porting: + // SetFromRotationMatrix(FMatrix3f) + + + inline operator FQuat() const + { + FQuat Quat; + Quat.X = (float)X; + Quat.Y = (float)Y; + Quat.Z = (float)Z; + Quat.W = (float)W; + return Quat; + } + inline TQuaternion(const FQuat& Quat) + { + X = (RealType)Quat.X; + Y = (RealType)Quat.Y; + Z = (RealType)Quat.Z; + W = (RealType)Quat.W; + } + +}; + + + +typedef TQuaternion FQuaternionf; +typedef TQuaternion FQuaterniond; + + + + +template +TQuaternion::TQuaternion() +{ + X = Y = Z = 0; W = 1; +} + +template +TQuaternion::TQuaternion(RealType x, RealType y, RealType z, RealType w) +{ + X = x; + Y = y; + Z = z; + W = w; +} + +template +TQuaternion::TQuaternion(const RealType* Values) +{ + X = Values[0]; + Y = Values[1]; + Z = Values[2]; + W = Values[3]; +} + +template +TQuaternion::TQuaternion(const TQuaternion& Copy) +{ + X = Copy.X; + Y = Copy.Y; + Z = Copy.Z; + W = Copy.W; +} + +template +TQuaternion::TQuaternion(const FVector3& Axis, RealType Angle, bool bAngleIsDegrees) +{ + X = Y = Z = 0; W = 1; + SetAxisAngleR(Axis, Angle * (bAngleIsDegrees ? TMathUtil::DegToRad : (RealType)1)); +} + +template +TQuaternion::TQuaternion(const FVector3& From, const FVector3& To) +{ + X = Y = Z = 0; W = 1; + SetFromTo(From, To); +} + +template +TQuaternion::TQuaternion(const TQuaternion& From, const TQuaternion& To, RealType InterpT) +{ + X = Y = Z = 0; W = 1; + SetToSlerp(From, To, InterpT); +} + +template +TQuaternion::TQuaternion(const TMatrix3& RotationMatrix) +{ + X = Y = Z = 0; W = 1; + SetFromRotationMatrix(RotationMatrix); +} + + + + + +template +RealType TQuaternion::Normalize(const RealType Epsilon) +{ + RealType length = Length(); + if (length > Epsilon) + { + RealType invLength = ((RealType)1) / length; + X *= invLength; + Y *= invLength; + Z *= invLength; + W *= invLength; + return invLength; + } + X = Y = Z = W = (RealType)0; + return 0; +} + +template +TQuaternion TQuaternion::Normalized(const RealType Epsilon) const +{ + RealType length = Length(); + if (length > Epsilon) + { + RealType invLength = ((RealType)1) / length; + return TQuaternion(X*invLength, Y*invLength, Z*invLength, W*invLength); + } + return Zero(); +} + +template +RealType TQuaternion::Dot(const TQuaternion& Other) const +{ + return X * Other.X + Y * Other.Y + Z * Other.Z + W * Other.W; +} + + +template +TQuaternion operator*(const TQuaternion& A, const TQuaternion& B) +{ + RealType W = A.W * B.W - A.X * B.X - A.Y * B.Y - A.Z * B.Z; + RealType X = A.W * B.X + A.X * B.W + A.Y * B.Z - A.Z * B.Y; + RealType Y = A.W * B.Y + A.Y * B.W + A.Z * B.X - A.X * B.Z; + RealType Z = A.W * B.Z + A.Z * B.W + A.X * B.Y - A.Y * B.X; + return TQuaternion(X, Y, Z, W); +} + +template +TQuaternion operator -(const TQuaternion& A, const TQuaternion& B) +{ + return TQuaternion(A.X - B.X, A.Y - B.Y, A.Z - B.Z, A.W - B.W); +} + +template +FVector3 operator*(const TQuaternion& Q, const FVector3& V) +{ + //return q.ToRotationMatrix() * v; + // inline-expansion of above: + RealType twoX = (RealType)2*Q.X; RealType twoY = (RealType)2*Q.Y; RealType twoZ = (RealType)2*Q.Z; + RealType twoWX = twoX * Q.W; RealType twoWY = twoY * Q.W; RealType twoWZ = twoZ * Q.W; + RealType twoXX = twoX * Q.X; RealType twoXY = twoY * Q.X; RealType twoXZ = twoZ * Q.X; + RealType twoYY = twoY * Q.Y; RealType twoYZ = twoZ * Q.Y; RealType twoZZ = twoZ * Q.Z; + return FVector3( + V.X * ((RealType)1 - (twoYY + twoZZ)) + V.Y * (twoXY - twoWZ) + V.Z * (twoXZ + twoWY), + V.X * (twoXY + twoWZ) + V.Y * ((RealType)1 - (twoXX + twoZZ)) + V.Z * (twoYZ - twoWX), + V.X * (twoXZ - twoWY) + V.Y * (twoYZ + twoWX) + V.Z * ((RealType)1 - (twoXX + twoYY))); ; +} + + +template +FVector3 TQuaternion::InverseMultiply(const FVector3& V) const +{ + RealType norm = SquaredLength(); + if (norm > 0) + { + RealType invNorm = (RealType)1 / norm; + RealType qX = -X * invNorm, qY = -Y * invNorm, qZ = -Z * invNorm, qW = W * invNorm; + RealType twoX = (RealType)2 * qX; RealType twoY = (RealType)2 * qY; RealType twoZ = (RealType)2 * qZ; + RealType twoWX = twoX * qW; RealType twoWY = twoY * qW; RealType twoWZ = twoZ * qW; + RealType twoXX = twoX * qX; RealType twoXY = twoY * qX; RealType twoXZ = twoZ * qX; + RealType twoYY = twoY * qY; RealType twoYZ = twoZ * qY; RealType twoZZ = twoZ * qZ; + return FVector3( + V.X * ((RealType)1 - (twoYY + twoZZ)) + V.Y * (twoXY - twoWZ) + V.Z * (twoXZ + twoWY), + V.X * (twoXY + twoWZ) + V.Y * ((RealType)1 - (twoXX + twoZZ)) + V.Z * (twoYZ - twoWX), + V.X * (twoXZ - twoWY) + V.Y * (twoYZ + twoWX) + V.Z * ((RealType)1 - (twoXX + twoYY))); + } + return FVector3::Zero(); +} + + +template +FVector3 TQuaternion::AxisX() const +{ + RealType twoY = (RealType)2 * Y; RealType twoZ = (RealType)2 * Z; + RealType twoWY = twoY * W; RealType twoWZ = twoZ * W; + RealType twoXY = twoY * X; RealType twoXZ = twoZ * X; + RealType twoYY = twoY * Y; RealType twoZZ = twoZ * Z; + return FVector3((RealType)1 - (twoYY + twoZZ), twoXY + twoWZ, twoXZ - twoWY); +} + +template +FVector3 TQuaternion::AxisY() const +{ + RealType twoX = (RealType)2 * X; RealType twoY = (RealType)2 * Y; RealType twoZ = (RealType)2 * Z; + RealType twoWX = twoX * W; RealType twoWZ = twoZ * W; RealType twoXX = twoX * X; + RealType twoXY = twoY * X; RealType twoYZ = twoZ * Y; RealType twoZZ = twoZ * Z; + return FVector3(twoXY - twoWZ, (RealType)1 - (twoXX + twoZZ), twoYZ + twoWX); +} + +template +FVector3 TQuaternion::AxisZ() const +{ + RealType twoX = (RealType)2 * X; RealType twoY = (RealType)2 * Y; RealType twoZ = (RealType)2 * Z; + RealType twoWX = twoX * W; RealType twoWY = twoY * W; RealType twoXX = twoX * X; + RealType twoXZ = twoZ * X; RealType twoYY = twoY * Y; RealType twoYZ = twoZ * Y; + return FVector3(twoXZ + twoWY, twoYZ - twoWX, (RealType)1 - (twoXX + twoYY)); +} + +template +TQuaternion TQuaternion::Inverse() const +{ + RealType norm = SquaredLength(); + if (norm > (RealType)0) + { + RealType invNorm = (RealType)1 / norm; + return TQuaternion( + -X * invNorm, -Y * invNorm, -Z * invNorm, W * invNorm); + } + return TQuaternion::Zero(); +} + + +template +void TQuaternion::SetAxisAngleD(const FVector3& Axis, RealType AngleDeg) +{ + SetAxisAngleR(Axis, TMathUtil::DegToRad * AngleDeg); +} + +template +void TQuaternion::SetAxisAngleR(const FVector3& Axis, RealType AngleRad) +{ + RealType halfAngle = (RealType)0.5 * AngleRad; + RealType sn = (RealType)sin(halfAngle); + W = (RealType)cos(halfAngle); + X = (sn * Axis.X); + Y = (sn * Axis.Y); + Z = (sn * Axis.Z); +} + + + +// this function can take non-normalized vectors vFrom and vTo (normalizes internally) +template +void TQuaternion::SetFromTo(const FVector3& From, const FVector3& To) +{ + // [TODO] this page seems to have optimized version: + // http://lolengine.net/blog/2013/09/18/beautiful-maths-quaternion-from-vectors + + // [RMS] not ideal to explicitly normalize here, but if we don't, + // output TQuaternion is not normalized and this causes problems, + // eg like drift if we do repeated SetFromTo() + FVector3 from = From.Normalized(), to = To.Normalized(); + FVector3 bisector = (from + to).Normalized(); + W = from.Dot(bisector); + if (W != 0) + { + FVector3 cross = from.Cross(bisector); + X = cross.X; + Y = cross.Y; + Z = cross.Z; + } + else + { + RealType invLength; + if ( (RealType)fabs(from.X) >= (RealType)fabs(from.Y)) + { + // V1.X or V1.Z is the largest magnitude component. + invLength = ((RealType)1 / (RealType)sqrt(from.X * from.X + from.Z * from.Z)); + X = -from.Z * invLength; + Y = 0; + Z = +from.X * invLength; + } + else + { + // V1.Y or V1.Z is the largest magnitude component. + invLength = ((RealType)1 / (RealType)sqrt(from.Y * from.Y + from.Z * from.Z)); + X = 0; + Y = +from.Z * invLength; + Z = -from.Y * invLength; + } + } + Normalize(); // just to be safe... +} + + + +template +void TQuaternion::SetToSlerp( const TQuaternion& From, const TQuaternion& To, RealType InterpT) +{ + RealType cs = From.Dot(To); + RealType angle = (RealType)acos(cs); + if (TMathUtil::Abs(angle) >= TMathUtil::ZeroTolerance) + { + RealType sn = (RealType)sin(angle); + RealType invSn = 1 / sn; + RealType tAngle = InterpT * angle; + RealType coeff0 = (RealType)sin(angle - tAngle) * invSn; + RealType coeff1 = (RealType)sin(tAngle) * invSn; + X = coeff0 * From.X + coeff1 * To.X; + Y = coeff0 * From.Y + coeff1 * To.Y; + Z = coeff0 * From.Z + coeff1 * To.Z; + W = coeff0 * From.W + coeff1 * To.W; + } + else + { + X = From.X; + Y = From.Y; + Z = From.Z; + W = From.W; + } +} + + + +template +void TQuaternion::SetFromRotationMatrix(const TMatrix3& RotationMatrix) +{ + // Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes + // article "TQuaternion Calculus and Fast Animation". + FIndex3i next(1, 2, 0); + + RealType trace = RotationMatrix(0,0) + RotationMatrix(1,1) + RotationMatrix(2, 2); + RealType root; + + if (trace > 0) + { + // |w| > 1/2, may as well choose w > 1/2 + root = (RealType)sqrt(trace + (RealType)1); // 2w + W = ((RealType)0.5) * root; + root = ((RealType)0.5) / root; // 1/(4w) + X = (RotationMatrix(2, 1) - RotationMatrix(1, 2)) * root; + Y = (RotationMatrix(0, 2) - RotationMatrix(2, 0)) * root; + Z = (RotationMatrix(1, 0) - RotationMatrix(0, 1)) * root; + } + else + { + // |w| <= 1/2 + int i = 0; + if (RotationMatrix(1, 1) > RotationMatrix(0, 0)) + { + i = 1; + } + if (RotationMatrix(2, 2) > RotationMatrix(i, i)) + { + i = 2; + } + int j = next[i]; + int k = next[j]; + + root = (RealType)sqrt(RotationMatrix(i, i) - RotationMatrix(j, j) - RotationMatrix(k, k) + (RealType)1); + + FVector3 quat(X, Y, Z); + quat[i] = ((RealType)0.5) * root; + root = ((RealType)0.5) / root; + W = (RotationMatrix(k, j) - RotationMatrix(j, k)) * root; + quat[j] = (RotationMatrix(j, i) + RotationMatrix(i, j)) * root; + quat[k] = (RotationMatrix(k, i) + RotationMatrix(i, k)) * root; + X = quat.X; Y = quat.Y; Z = quat.Z; + } + + Normalize(); // we prefer normalized TQuaternions... +} + + + + +template +TMatrix3 TQuaternion::ToRotationMatrix() const +{ + RealType twoX = 2 * X; RealType twoY = 2 * Y; RealType twoZ = 2 * Z; + RealType twoWX = twoX * W; RealType twoWY = twoY * W; RealType twoWZ = twoZ * W; + RealType twoXX = twoX * X; RealType twoXY = twoY * X; RealType twoXZ = twoZ * X; + RealType twoYY = twoY * Y; RealType twoYZ = twoZ * Y; RealType twoZZ = twoZ * Z; + TMatrix3 m = TMatrix3::Zero(); + m.Row0 = FVector3(1 - (twoYY + twoZZ), twoXY - twoWZ, twoXZ + twoWY); + m.Row1 = FVector3(twoXY + twoWZ, 1 - (twoXX + twoZZ), twoYZ - twoWX); + m.Row2 = FVector3(twoXZ - twoWY, twoYZ + twoWX, 1 - (twoXX + twoYY)); + return m; +} + + + +template +bool TQuaternion::EpsilonEqual(const TQuaternion& Other, RealType Epsilon) const +{ + return (RealType)fabs(X - Other.X) <= Epsilon && + (RealType)fabs(Y - Other.Y) <= Epsilon && + (RealType)fabs(Z - Other.Z) <= Epsilon && + (RealType)fabs(W - Other.W) <= Epsilon; +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/RayTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/RayTypes.h new file mode 100644 index 000000000000..df2916bb92cf --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/RayTypes.h @@ -0,0 +1,115 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// port of geometry3Sharp Ray3 + +#pragma once + +#include "Math/UnrealMath.h" +#include "VectorTypes.h" +#include "Math/Ray.h" + +/* + * 3D Ray stored as Origin point and normalized Direction vector + */ +template +class TRay3 +{ +public: + /** Origin point */ + FVector3 Origin; + /** Direction vector, always normalized */ + FVector3 Direction; + + /** Construct ray at origin pointed down Z axis */ + TRay3() + { + Origin = FVector3::Zero(); + Direction = FVector3::UnitZ(); + } + + /** + * Construct ray from origin and direction + * @param Origin origin point + * @param Direction direction vector. Must be normalized if bIsNormalized=true + * @param bIsNormalized if true, Direction will not be re-normalized + */ + TRay3(const FVector3& Origin, const FVector3& Direction, bool bIsNormalized = false) + { + this->Origin = Origin; + this->Direction = Direction; + if (bIsNormalized == false) + { + this->Direction.Normalize(); + } + } + + /** + * @return point on ray at given (signed) Distance from the ray Origin + */ + FVector3 PointAt(RealType Distance) const + { + return Origin + Distance * Direction; + } + + + /** + * @return ray parameter (ie positive distance from Origin) at nearest point on ray to QueryPoint + */ + inline RealType Project(const FVector3& QueryPoint) const + { + RealType LineParam = (QueryPoint - Origin).Dot(Direction); + return (LineParam < 0) ? 0 : LineParam; + } + + /** + * @return smallest squared distance from line to QueryPoint + */ + inline RealType DistanceSquared(const FVector3& QueryPoint) const + { + RealType LineParam = (QueryPoint - Origin).Dot(Direction); + if (LineParam < 0) + { + return Origin.DistanceSquared(QueryPoint); + } + else + { + FVector3 NearestPt = Origin + LineParam * Direction; + return NearestPt.DistanceSquared(QueryPoint); + } + } + + /** + * @return nearest point on line to QueryPoint + */ + inline FVector3 NearestPoint(const FVector3& QueryPoint) const + { + RealType LineParam = (QueryPoint - Origin).Dot(Direction); + if (LineParam < 0) + { + return Origin; + } + else + { + return Origin + LineParam * Direction; + } + } + + + + // conversion operators + + inline operator FRay() const + { + return FRay((FVector)Origin, (FVector)Direction); + } + inline TRay3(const FRay & RayIn) + { + Origin = (FVector3)RayIn.Origin; + Direction = (FVector3)RayIn.Direction; + } + + +}; +typedef TRay3 FRay3f; +typedef TRay3 FRay3d; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/SegmentTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/SegmentTypes.h new file mode 100644 index 000000000000..d4a73077e10a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/SegmentTypes.h @@ -0,0 +1,506 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// ported from geometry3Sharp Segment2 + +#pragma once + +#include "Math/UnrealMath.h" +#include "VectorTypes.h" + + +/* + * 2D Line Segmented stored as Center point, normalized Direction vector, and scalar Extent + */ +template +struct TSegment2 +{ +public: + /** Center point of segment */ + FVector2 Center; + /** normalized Direction vector of segment */ + FVector2 Direction; + /** Extent of segment, which is half the total length */ + T Extent; + + /** + * Construct a Segment from two Points + */ + TSegment2(const FVector2& Point0, const FVector2& Point1) + { + // set from endpoints + Center = T(.5) * (Point0 + Point1); + Direction = Point1 - Point0; + Extent = T(.5) * Direction.Normalize(); + } + + /** + * Construct a segment from a Center Point, normalized Direction, and scalar Extent + */ + TSegment2(const FVector2& CenterIn, const FVector2& DirectionIn, T ExtentIn) + { + Center = CenterIn; + Direction = DirectionIn; + Extent = ExtentIn; + } + + + + /** Update the Segment with a new start point */ + inline void SetStartPoint(const FVector2& Point) + { + update_from_endpoints(Point, EndPoint()); + } + + /** Update the Segment with a new end point */ + inline void SetEndPoint(const FVector2& Point) + { + update_from_endpoints(StartPoint(), Point); + } + + /** Reverse the segment */ + void Reverse() + { + update_from_endpoints(EndPoint(), StartPoint()); + } + + + + + /** @return start point of segment */ + inline FVector2 StartPoint() const + { + return Center - Extent * Direction; + } + + /** @return end point of segment */ + inline FVector2 EndPoint() const + { + return Center + Extent * Direction; + } + + + /** @return the Length of the segment */ + inline T Length() const + { + return (T)2 * Extent; + } + + /** @return first (i == 0) or second (i == 1) endpoint of the Segment */ + inline FVector2 GetPointFromIndex(int i) const + { + return (i == 0) ? (Center - Extent * Direction) : (Center + Extent * Direction); + } + + /** + * @return point on segment at given (signed) Distance from the segment Origin + */ + inline FVector2 PointAt(T DistanceParameter) const + { + return Center + DistanceParameter * Direction; + } + + /** + * @param UnitParameter value in range [0,1] + * @return point on segment at that linearly interpolates between start and end based on Unit Parameter + */ + inline FVector2 PointBetween(T UnitParameter) const + { + return Center + ((T)2 * UnitParameter - (T)1) * Extent * Direction; + } + + /** + * @return minimum squared distance from Point to segment + */ + inline T DistanceSquared(const FVector2& Point) const + { + T DistParameter; + return DistanceSquared(Point, DistParameter); + } + + + + /** + * @param Point query point + * @param DistParameterOut calculated distance parameter in range [-Extent,Extent] + * @return minimum squared distance from Point to Segment + */ + T DistanceSquared(const FVector2& Point, T& DistParameterOut) const + { + DistParameterOut = (Point - Center).Dot(Direction); + if (DistParameterOut >= Extent) + { + DistParameterOut = Extent; + return Point.DistanceSquared(EndPoint()); + } + else if (DistParameterOut <= -Extent) + { + DistParameterOut = -Extent; + return Point.DistanceSquared(StartPoint()); + } + FVector2 ProjectedPt = Center + DistParameterOut * Direction; + return ProjectedPt.DistanceSquared(Point); + } + + + /** + * @return nearest point on segment to QueryPoint + */ + inline FVector2 NearestPoint(const FVector2& QueryPoint) const + { + T t = (QueryPoint - Center).Dot(Direction); + if (t >= Extent) + { + return EndPoint(); + } + if (t <= -Extent) + { + return StartPoint(); + } + return Center + t * Direction; + } + + + /** + * @return scalar projection of QueryPoint onto line of Segment (not clamped to Extents) + */ + inline T Project(const FVector2& QueryPoint) const + { + return (QueryPoint - Center).Dot(Direction); + } + + + /** + * @return scalar projection of QueryPoint onto line of Segment, mapped to [0,1] range along segment + */ + inline T ProjectUnitRange(const FVector2& QueryPoint) const + { + T ProjT = (QueryPoint - Center).Dot(Direction); + T Alpha = ((ProjT / Extent) + (T)1) * (T)0.5; + return TMathUtil::Clamp(Alpha, (T)0, (T)1); + } + + + + /** + * Determine which side of the segment the query point lies on + * @param QueryPoint test point + * @param Tolerance tolerance band in which we return 0 + * @return +1 if point is to right of line, -1 if left, and 0 if on line or within tolerance band + */ + int WhichSide(const FVector2& QueryPoint, T Tolerance = 0) + { + // [TODO] subtract Center from test? + FVector2 EndPt = Center + Extent * Direction; + FVector2 StartPt = Center - Extent * Direction; + T det = -FVector2::Orient(EndPt, StartPt, QueryPoint); + return (det > Tolerance ? +1 : (det < -Tolerance ? -1 : 0)); + } + + + /** + * Test if this segment intersects with OtherSegment. Returns true for parallel-line overlaps. Returns same result as IntrSegment2Segment2 + * @param OtherSegment segment to test against + * @param DotThresh dot-product tolerance used to determine if segments are parallel + * @param IntervalThresh distance tolerance used to allow slighly-not-touching segments to be considered overlapping + * @return true if segments intersect + */ + bool Intersects(const TSegment2& OtherSegment, T DotThresh = TMathUtil::Epsilon, T IntervalThresh = 0) const + { + // see IntrLine2Line2 and IntrSegment2Segment2 for details on this code + + FVector2 diff = OtherSegment.Center - Center; + T D0DotPerpD1 = Direction.DotPerp(OtherSegment.Direction); + if (TMathUtil::Abs(D0DotPerpD1) > DotThresh) // Lines intersect in a single point. + { + T invD0DotPerpD1 = ((T)1) / D0DotPerpD1; + T diffDotPerpD0 = diff.DotPerp(Direction); + T diffDotPerpD1 = diff.DotPerp(OtherSegment.Direction); + T s = diffDotPerpD1 * invD0DotPerpD1; + T s2 = diffDotPerpD0 * invD0DotPerpD1; + return TMathUtil::Abs(s) <= (Extent + IntervalThresh) + && TMathUtil::Abs(s2) <= (OtherSegment.Extent + IntervalThresh); + } + + // Lines are parallel. + diff.Normalize(); + T diffNDotPerpD1 = diff.DotPerp(OtherSegment.Direction); + if (TMathUtil::Abs(diffNDotPerpD1) <= DotThresh) + { + // Compute the location of OtherSegment endpoints relative to our Segment + diff = OtherSegment.Center - Center; + T t1 = Direction.Dot(diff); + T tmin = t1 - OtherSegment.Extent; + T tmax = t1 + OtherSegment.Extent; + TInterval1 extents(-Extent, Extent); + if (extents.Overlaps(TInterval1(tmin, tmax))) + { + return true; + } + return false; + } + + // lines are parallel but not collinear + return false; + } + + + + + + // 2D segment utility functions + + + /** + * Calculate distance from QueryPoint to segment (StartPt,EndPt) + */ + static T FastDistanceSquared(const FVector2& StartPt, const FVector2& EndPt, const FVector2& QueryPt, T Tolerance = TMathUtil::Epsilon) + { + T vx = EndPt.X - StartPt.X, vy = EndPt.Y - StartPt.Y; + T len2 = vx * vx + vy * vy; + T dx = QueryPt.X - StartPt.X, dy = QueryPt.Y - StartPt.Y; + //if (len2 < 1e-13) + if (len2 < Tolerance) + { + return dx * dx + dy * dy; + } + T t = (dx*vx + dy * vy); + if (t <= 0) + { + return dx * dx + dy * dy; + } + else if (t >= len2) + { + dx = QueryPt.X - EndPt.X; + dy = QueryPt.Y - EndPt.Y; + return dx * dx + dy * dy; + } + dx = QueryPt.X - (StartPt.X + ((t * vx) / len2)); + dy = QueryPt.Y - (StartPt.Y + ((t * vy) / len2)); + return dx * dx + dy * dy; + } + + + /** + * Determine which side of the segment the query point lies on + * @param StartPt first point of Segment + * @param EndPt second point of Segment + * @param QueryPoint test point + * @param Tolerance tolerance band in which we return 0 + * @return +1 if point is to right of line, -1 if left, and 0 if on line or within tolerance band + */ + static int WhichSide(const FVector2& StartPt, const FVector2& EndPt, const FVector2& QueryPt, T Tolerance = (T)0) + { + T det = -FVector2::Orient(StartPt, EndPt, QueryPt); + return (det > Tolerance ? +1 : (det < -Tolerance ? -1 : 0)); + } + + + + + +protected: + + // update segment based on new endpoints + inline void update_from_endpoints(const FVector2& p0, const FVector2& p1) + { + Center = 0.5 * (p0 + p1); + Direction = p1 - p0; + Extent = 0.5 * Direction.Normalize(); + } + +}; +typedef TSegment2 FSegment2f; +typedef TSegment2 FSegment2d; + + + + + + + + +/* + * 3D Line Segmented stored as Center point, normalized Direction vector, and scalar Extent + */ +template +struct TSegment3 +{ +public: + /** Center point of segment */ + FVector3 Center; + /** normalized Direction vector of segment */ + FVector3 Direction; + /** Extent of segment, which is half the total length */ + T Extent; + + /** + * Construct a Segment from two Points + */ + TSegment3(const FVector3& Point0, const FVector3& Point1) + { + // set from endpoints + Center = T(.5) * (Point0 + Point1); + Direction = Point1 - Point0; + Extent = T(.5) * Direction.Normalize(); + } + + /** + * Construct a segment from a Center Point, normalized Direction, and scalar Extent + */ + TSegment3(const FVector3& CenterIn, const FVector3& DirectionIn, T ExtentIn) + { + Center = CenterIn; + Direction = DirectionIn; + Extent = ExtentIn; + } + + + + /** Update the Segment with a new start point */ + inline void SetStartPoint(const FVector3& Point) + { + update_from_endpoints(Point, EndPoint()); + } + + /** Update the Segment with a new end point */ + inline void SetEndPoint(const FVector3& Point) + { + update_from_endpoints(StartPoint(), Point); + } + + /** Reverse the segment */ + void Reverse() + { + update_from_endpoints(EndPoint(), StartPoint()); + } + + + + + /** @return start point of segment */ + inline FVector3 StartPoint() const + { + return Center - Extent * Direction; + } + + /** @return end point of segment */ + inline FVector3 EndPoint() const + { + return Center + Extent * Direction; + } + + + /** @return the Length of the segment */ + inline T Length() const + { + return (T)2 * Extent; + } + + /** @return first (i == 0) or second (i == 1) endpoint of the Segment */ + inline FVector3 GetPointFromIndex(int i) const + { + return (i == 0) ? (Center - Extent * Direction) : (Center + Extent * Direction); + } + + /** + * @return point on segment at given (signed) Distance from the segment Origin + */ + inline FVector3 PointAt(T DistanceParameter) const + { + return Center + DistanceParameter * Direction; + } + + /** + * @param UnitParameter value in range [0,1] + * @return point on segment at that linearly interpolates between start and end based on Unit Parameter + */ + inline FVector3 PointBetween(T UnitParameter) const + { + return Center + ((T)2 * UnitParameter - (T)1) * Extent * Direction; + } + + /** + * @return minimum squared distance from Point to segment + */ + inline T DistanceSquared(const FVector3& Point) const + { + T DistParameter; + return DistanceSquared(Point, DistParameter); + } + + + + /** + * @param Point query point + * @param DistParameterOut calculated distance parameter in range [-Extent,Extent] + * @return minimum squared distance from Point to Segment + */ + T DistanceSquared(const FVector3& Point, T& DistParameterOut) const + { + DistParameterOut = (Point - Center).Dot(Direction); + if (DistParameterOut >= Extent) + { + DistParameterOut = Extent; + return Point.DistanceSquared(EndPoint()); + } + else if (DistParameterOut <= -Extent) + { + DistParameterOut = -Extent; + return Point.DistanceSquared(StartPoint()); + } + FVector3 ProjectedPt = Center + DistParameterOut * Direction; + return ProjectedPt.DistanceSquared(Point); + } + + + /** + * @return nearest point on segment to QueryPoint + */ + inline FVector3 NearestPoint(const FVector3& QueryPoint) const + { + T t = (QueryPoint - Center).Dot(Direction); + if (t >= Extent) + { + return EndPoint(); + } + if (t <= -Extent) + { + return StartPoint(); + } + return Center + t * Direction; + } + + + /** + * @return scalar projection of QueryPoint onto line of Segment (not clamped to Extents) + */ + inline T Project(const FVector3& QueryPoint) const + { + return (QueryPoint - Center).Dot(Direction); + } + + + /** + * @return scalar projection of QueryPoint onto line of Segment, mapped to [0,1] range along segment + */ + inline T ProjectUnitRange(const FVector3& QueryPoint) const + { + T ProjT = (QueryPoint - Center).Dot(Direction); + T Alpha = ((ProjT / Extent) + (T)1) * (T)0.5; + return TMathUtil::Clamp(Alpha, (T)0, (T)1); + } + + +protected: + + // update segment based on new endpoints + inline void update_from_endpoints(const FVector3& p0, const FVector3& p1) + { + Center = 0.5 * (p0 + p1); + Direction = p1 - p0; + Extent = 0.5 * Direction.Normalize(); + } + +}; +typedef TSegment3 FSegment3f; +typedef TSegment3 FSegment3d; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/FastWinding.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/FastWinding.h new file mode 100644 index 000000000000..841bd44c1f51 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/FastWinding.h @@ -0,0 +1,461 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MathUtil.h" +#include "MeshQueries.h" +#include "Spatial/MeshAABBTree3.h" +#include "Util/MeshCaches.h" +#include "MatrixTypes.h" + +/** + * Formulas for triangle winding number approximation + */ +namespace FastTriWinding +{ + /** + * precompute constant coefficients of triangle winding number approximation + * P: 'Center' of expansion for Triangles (area-weighted centroid avg) + * R: max distance from P to Triangles + * Order1: first-order vector coeff + * Order2: second-order matrix coeff + * TriCache: precomputed triangle centroid/normal/area (todo @jimmy either support passing in as possibly-nullptr or remove commented-out null branches!) + */ + template + void ComputeCoeffs(const TriangleMeshType& Mesh, + const IterableTriangleIndices& Triangles, + const FMeshTriInfoCache& TriCache, + FVector3d& P, double& R, + FVector3d& Order1, FMatrix3d& Order2) + { + P = FVector3d::Zero(); + Order1 = FVector3d::Zero(); + Order2 = FMatrix3d::Zero(); + R = 0; + + // compute area-weighted centroid of Triangles, we use this as the expansion point + FVector3d P0 = FVector3d::Zero(), P1 = FVector3d::Zero(), P2 = FVector3d::Zero(); + double sum_area = 0; + for (int tid : Triangles) + { + //if (TriCache == null) + //{ + // double area = TriCache.Areas[tid]; + // sum_area += area; + // P += area * TriCache.Centroids[tid]; + //} + //else + { + Mesh.GetTriVertices(tid, P0, P1, P2); + double area = VectorUtil::Area(P0, P1, P2); + sum_area += area; + P += area * ((P0 + P1 + P2) / 3.0); + } + } + P /= sum_area; + + // compute first and second-order coefficients of FWN taylor expansion, as well as + // 'radius' value R, which is max dist from any tri vertex to P + FVector3d n, c; + double a = 0; + for (int tid : Triangles) + { + Mesh.GetTriVertices(tid, P0, P1, P2); + + //if (TriCache == null) + //{ + // c = (1.0 / 3.0) * (P0 + P1 + P2); + // n = FMathd::FastNormalArea(P0, P1, P2, out a); + //} + //else + { + TriCache.GetTriInfo(tid, n, a, c); + } + + Order1 += a * n; + + FVector3d dcp = c - P; + Order2 += a * FMatrix3d(dcp, n); + + // this is just for return value... + double maxdist = FMath::Max3(P0.DistanceSquared(P), P1.DistanceSquared(P), P2.DistanceSquared(P)); + R = FMath::Max(R, sqrt(maxdist)); + } + } + + /** + * Evaluate first-order FWN approximation at point Q, relative to Center c + */ + double EvaluateOrder1Approx(const FVector3d& Center, const FVector3d& Order1Coeff, const FVector3d& Q) + { + FVector3d dpq = (Center - Q); + double len = dpq.Length(); + + return (1.0 / FMathd::FourPi) * Order1Coeff.Dot(dpq / (len * len * len)); + } + + /** + * Evaluate second-order FWN approximation at point Q, relative to Center c + */ + double EvaluateOrder2Approx(const FVector3d& Center, const FVector3d& Order1Coeff, const FMatrix3d& Order2Coeff, const FVector3d& Q) + { + FVector3d dpq = (Center - Q); + double len = dpq.Length(); + double len3 = len * len * len; + double fourPi_len3 = 1.0 / (FMathd::FourPi * len3); + + double Order1 = fourPi_len3 * Order1Coeff.Dot(dpq); + + // second-order hessian \grad^2(G) + double c = -3.0 / (FMathd::FourPi * len3 * len * len); + + // expanded-out version below avoids extra constructors + //FMatrix3d xqxq(dpq, dpq); + //FMatrix3d hessian(fourPi_len3, fourPi_len3, fourPi_len3) - c * xqxq; + FMatrix3d hessian( + fourPi_len3 + c * dpq.X * dpq.X, c * dpq.X * dpq.Y, c * dpq.X * dpq.Z, + c * dpq.Y * dpq.X, fourPi_len3 + c * dpq.Y * dpq.Y, c * dpq.Y * dpq.Z, + c * dpq.Z * dpq.X, c * dpq.Z * dpq.Y, fourPi_len3 + c * dpq.Z * dpq.Z); + + double Order2 = Order2Coeff.InnerProduct(hessian); + + return Order1 + Order2; + } + + // triangle-winding-number first-order approximation. + // T is triangle, P is 'Center' of cluster of dipoles, Q is evaluation point + // (This is really just for testing) + double Order1Approx(const FTriangle3d& T, const FVector3d& P, const FVector3d& XN, double XA, const FVector3d& Q) + { + FVector3d at0 = XA * XN; + + FVector3d dpq = (P - Q); + double len = dpq.Length(); + double len3 = len * len * len; + + return (1.0 / FMathd::FourPi) * at0.Dot(dpq / (len * len * len)); + } + + // triangle-winding-number second-order approximation + // T is triangle, P is 'Center' of cluster of dipoles, Q is evaluation point + // (This is really just for testing) + double Order2Approx(const FTriangle3d& T, const FVector3d& P, const FVector3d& XN, double XA, const FVector3d& Q) + { + FVector3d dpq = (P - Q); + + double len = dpq.Length(); + double len3 = len * len * len; + + // first-order approximation - integrated_normal_area * \grad(G) + double Order1 = (XA / FMathd::FourPi) * XN.Dot(dpq / len3); + + // second-order hessian \grad^2(G) + FMatrix3d xqxq(dpq, dpq); + xqxq *= 3.0 / (FMathd::FourPi * len3 * len * len); + double diag = 1 / (FMathd::FourPi * len3); + FMatrix3d hessian = FMatrix3d(diag, diag, diag) - xqxq; + + // second-order LHS - integrated second-order area matrix (formula 26) + FVector3d centroid( + (T.V[0].X + T.V[1].X + T.V[2].X) / 3.0, (T.V[0].Y + T.V[1].Y + T.V[2].Y) / 3.0, (T.V[0].Z + T.V[1].Z + T.V[2].Z) / 3.0); + FVector3d dcp = centroid - P; + FMatrix3d o2_lhs(dcp, XN); + double Order2 = XA * o2_lhs.InnerProduct(hessian); + + return Order1 + Order2; + } +} // namespace FastTriWinding + +/** + * Fast Mesh Winding Number computation + * + * TODO: this is tightly coupled to the internal guts of TMeshAABBTree3 (because it is ported from code where it *was* the guts of the AABBTree) + * we should probably at least try to decouple it some + */ +template +class TFastWindingTree +{ + TMeshAABBTree3* Tree; + + struct FWNInfo + { + FVector3d Center; + double R; + FVector3d Order1Vec; + FMatrix3d Order2Mat; + }; + + TMap FastWindingCache; + int FastWindingCacheTimestamp = -1; + +public: + /** + * FWN beta parameter - is 2.0 in paper + */ + double FWNBeta = 2.0; + + /** + * FWN approximation order. Must be 1 or 2. 2 is more accurate, obviously. + */ + int FWNApproxOrder = 2; + + TFastWindingTree(TMeshAABBTree3* TreeToRef) + { + SetTree(TreeToRef); + } + + void SetTree(TMeshAABBTree3* TreeToRef) + { + this->Tree = TreeToRef; + Build(true); + } + + void Build(bool bAlwaysBuildRegardlessOfTimestamp = true) + { + check(Tree); + if (Tree->MeshTimestamp != Tree->Mesh->GetShapeTimestamp()) + { + Tree->Build(); + } + if (bAlwaysBuildRegardlessOfTimestamp || FastWindingCacheTimestamp != Tree->MeshTimestamp) + { + build_fast_winding_cache(); + FastWindingCacheTimestamp = Tree->MeshTimestamp; + } + } + + /** + * Fast approximation of winding number using far-field approximations + */ + double FastWindingNumber(const FVector3d& P) + { + Build(false); + double sum = branch_fast_winding_num(Tree->RootIndex, P); + return sum; + } + +private: + // evaluate winding number contribution for all Triangles below IBox + double branch_fast_winding_num(int IBox, FVector3d P) + { + double branch_sum = 0; + + int idx = Tree->BoxToIndex[IBox]; + if (idx < Tree->TrianglesEnd) + { // triangle-list case, array is [N t1 t2 ... tN] + int num_tris = Tree->IndexList[idx]; + for (int i = 1; i <= num_tris; ++i) + { + FVector3d a, b, c; + int ti = Tree->IndexList[idx + i]; + Tree->Mesh->GetTriVertices(ti, a, b, c); + double angle = VectorUtil::TriSolidAngle(a, b, c, P); + branch_sum += angle / FMathd::FourPi; + } + } + else + { // internal node, either 1 or 2 child boxes + int iChild1 = Tree->IndexList[idx]; + if (iChild1 < 0) + { // 1 child, descend if nearer than cur min-dist + iChild1 = (-iChild1) - 1; + + // if we have winding cache, we can more efficiently compute contribution of all Triangles + // below this box. Otherwise, recursively descend Tree. + bool contained = Tree->box_contains(iChild1, P); + if (contained == false && can_use_fast_winding_cache(iChild1, P)) + { + branch_sum += evaluate_box_fast_winding_cache(iChild1, P); + } + else + { + branch_sum += branch_fast_winding_num(iChild1, P); + } + } + else + { // 2 children, descend closest first + iChild1 = iChild1 - 1; + int iChild2 = Tree->IndexList[idx + 1] - 1; + + bool contained1 = Tree->box_contains(iChild1, P); + if (contained1 == false && can_use_fast_winding_cache(iChild1, P)) + { + branch_sum += evaluate_box_fast_winding_cache(iChild1, P); + } + else + { + branch_sum += branch_fast_winding_num(iChild1, P); + } + + bool contained2 = Tree->box_contains(iChild2, P); + if (contained2 == false && can_use_fast_winding_cache(iChild2, P)) + { + branch_sum += evaluate_box_fast_winding_cache(iChild2, P); + } + else + { + branch_sum += branch_fast_winding_num(iChild2, P); + } + } + } + + return branch_sum; + } + + void build_fast_winding_cache() + { + // set this to a larger number to ignore caches if number of Triangles is too small. + // (seems to be no benefit to doing this...is holdover from Tree-decomposition FWN code) + int WINDING_CACHE_THRESH = 1; + + //FMeshTriInfoCache TriCache = null; + FMeshTriInfoCache TriCache = FMeshTriInfoCache::BuildTriInfoCache(*Tree->Mesh); + + FastWindingCache.Empty(); // = TMap(); + TOptional> root_hash; + build_fast_winding_cache(Tree->RootIndex, 0, WINDING_CACHE_THRESH, root_hash, TriCache); + } + int build_fast_winding_cache(int IBox, int Depth, int TriCountThresh, TOptional>& TriHash, const FMeshTriInfoCache& TriCache) + { + TriHash.Reset(); + + int idx = Tree->BoxToIndex[IBox]; + if (idx < Tree->TrianglesEnd) + { // triangle-list case, array is [N t1 t2 ... tN] + int num_tris = Tree->IndexList[idx]; + return num_tris; + } + else + { // internal node, either 1 or 2 child boxes + int iChild1 = Tree->IndexList[idx]; + if (iChild1 < 0) + { // 1 child, descend if nearer than cur min-dist + iChild1 = (-iChild1) - 1; + int num_child_tris = build_fast_winding_cache(iChild1, Depth + 1, TriCountThresh, TriHash, TriCache); + + // if count in child is large enough, we already built a cache at lower node + return num_child_tris; + } + else + { // 2 children, descend closest first + iChild1 = iChild1 - 1; + int iChild2 = Tree->IndexList[idx + 1] - 1; + + // let each child build its own cache if it wants. If so, it will return the + // list of its child tris + TOptional> child2_hash; + int num_tris_1 = build_fast_winding_cache(iChild1, Depth + 1, TriCountThresh, TriHash, TriCache); + int num_tris_2 = build_fast_winding_cache(iChild2, Depth + 1, TriCountThresh, child2_hash, TriCache); + bool build_cache = (num_tris_1 + num_tris_2 > TriCountThresh); + + if (Depth == 0) + { + return num_tris_1 + num_tris_2; // cannot build cache at level 0... + } + + // collect up the Triangles we need. there are various cases depending on what children already did + if (TriHash.IsSet() || child2_hash.IsSet() || build_cache) + { + if (!TriHash.IsSet() && child2_hash.IsSet()) + { + collect_triangles(iChild1, child2_hash.GetValue()); + TriHash = child2_hash; + } + else + { + if (!TriHash.IsSet()) + { + TriHash.Emplace(); + collect_triangles(iChild1, TriHash.GetValue()); + } + if (!child2_hash.IsSet()) + { + collect_triangles(iChild2, TriHash.GetValue()); + } + else + { + TriHash->Append(child2_hash.GetValue()); + } + } + } + if (build_cache) + { + check(TriHash.IsSet()); + make_box_fast_winding_cache(IBox, TriHash.GetValue(), TriCache); + } + + return (num_tris_1 + num_tris_2); + } + } + } + + // check if value is in cache and far enough away from Q that we can use cached value + bool can_use_fast_winding_cache(int IBox, const FVector3d& Q) + { + FWNInfo* cacheInfo = FastWindingCache.Find(IBox); + if (!cacheInfo) + { + return false; + } + + double dist_qp = cacheInfo->Center.Distance(Q); + if (dist_qp > FWNBeta * cacheInfo->R) + { + return true; + } + + return false; + } + + // compute FWN cache for all Triangles underneath this box + void make_box_fast_winding_cache(int IBox, const TSet& Triangles, const FMeshTriInfoCache& TriCache) + { + check(FastWindingCache.Find(IBox) == nullptr); + + // construct cache + FWNInfo cacheInfo; + FastTriWinding::ComputeCoeffs(*Tree->Mesh, Triangles, TriCache, cacheInfo.Center, cacheInfo.R, cacheInfo.Order1Vec, cacheInfo.Order2Mat); + + FastWindingCache.Add(IBox, cacheInfo); + } + + // evaluate the FWN cache for IBox + double evaluate_box_fast_winding_cache(int IBox, const FVector3d& Q) + { + const FWNInfo& cacheInfo = FastWindingCache[IBox]; + + if (FWNApproxOrder == 2) + { + return FastTriWinding::EvaluateOrder2Approx(cacheInfo.Center, cacheInfo.Order1Vec, cacheInfo.Order2Mat, Q); + } + else + { + return FastTriWinding::EvaluateOrder1Approx(cacheInfo.Center, cacheInfo.Order1Vec, Q); + } + } + + // collect all the Triangles below IBox in a hash + void collect_triangles(int IBox, TSet& Triangles) + { + int idx = Tree->BoxToIndex[IBox]; + if (idx < Tree->TrianglesEnd) + { // triangle-list case, array is [N t1 t2 ... tN] + int num_tris = Tree->IndexList[idx]; + for (int i = 1; i <= num_tris; ++i) + { + Triangles.Add(Tree->IndexList[idx + i]); + } + } + else + { + int iChild1 = Tree->IndexList[idx]; + if (iChild1 < 0) + { // 1 child, descend if nearer than cur min-dist + collect_triangles((-iChild1) - 1, Triangles); + } + else + { // 2 children, descend closest first + collect_triangles(iChild1 - 1, Triangles); + collect_triangles(Tree->IndexList[idx + 1] - 1, Triangles); + } + } + } +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/GeometrySet3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/GeometrySet3.h new file mode 100644 index 000000000000..0187242af30b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/GeometrySet3.h @@ -0,0 +1,100 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "BoxTypes.h" +#include "RayTypes.h" +#include "Polyline3.h" + +/** + * FGeometrySet3 stores a set of 3D Points and Polyline curves, + * and supports spatial queries against these sets. + * + * Since Points and Curves have no area to hit, hit-tests are done via nearest-point-on-ray. + */ +class GEOMETRICOBJECTS_API FGeometrySet3 +{ +public: + /** + * @param bPoints if true, discard all points + * @param bCurves if true, discard all polycurves + */ + void Reset(bool bPoints = true, bool bCurves = true); + + /** Add a point with given PointID at the given Position*/ + void AddPoint(int PointID, const FVector3d& Position); + /** Add a polycurve with given CurveID and the give Polyline */ + void AddCurve(int CurveID, const FPolyline3d& Polyline); + + /** Update the Position of previously-added PointID */ + void UpdatePoint(int PointID, const FVector3d& Position); + /** Update the Polyline of previously-added CurveID */ + void UpdateCurve(int CurveID, const FPolyline3d& Polyline); + + /** + * FNearest is returned by nearest-point queries + */ + struct GEOMETRICOBJECTS_API FNearest + { + /** ID of point or curve */ + int ID; + /** true for point, false for polyline curve*/ + bool bIsPoint; + + /** Nearest point on ray */ + FVector3d NearestRayPoint; + /** Nearest point on geometry (ie the point, or point on curve)*/ + FVector3d NearestGeoPoint; + + /** parameter of nearest point on ray (equivalent to NearestRayPoint) */ + double RayParam; + + /** if bIsPoint=false, index of nearest segment on polyline curve */ + int PolySegmentIdx; + /** if bIsPoint=false, parameter of NearestGeoPoint along segment defined by PolySegmentIdx */ + double PolySegmentParam; + }; + + + /** + * @param Ray query ray + * @param ResultOut populated with information about successful nearest point result + * @param PointWithinToleranceTest should return true if two 3D points are "close enough" to be considered a hit + * @return true if the nearest point on Ray to some point in the set passed the PointWithinToleranceTest. + * @warning PointWithinToleranceTest is called in parallel and hence must be thread-safe/re-entrant! + */ + bool FindNearestPointToRay(const FRay3d& Ray, FNearest& ResultOut, + TFunction PointWithinToleranceTest) const; + + /** + * @param Ray query ray + * @param ResultOut populated with information about successful nearest point result + * @param PointWithinToleranceTest should return true if two 3D points are "close enough" to be considered a hit + * @return true if the nearest point on Ray to some curve in the set passed the PointWithinToleranceTest. + * @warning PointWithinToleranceTest is called in parallel and hence must be thread-safe/re-entrant! + */ + bool FindNearestCurveToRay(const FRay3d& Ray, FNearest& ResultOut, + TFunction PointWithinToleranceTest) const; + + +protected: + + struct FPoint + { + int ID; + FVector3d Position; + }; + TArray Points; + TMap PointIDToIndex; + + struct FCurve + { + int ID; + FPolyline3d Geometry; + + FAxisAlignedBox3d Bounds; + }; + TArray Curves; + TMap CurveIDToIndex; + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/MeshAABBTree3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/MeshAABBTree3.h new file mode 100644 index 000000000000..480046595e20 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/MeshAABBTree3.h @@ -0,0 +1,921 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp TMeshAABBTree3 + +#pragma once + +#include "Util/DynamicVector.h" +#include "Intersection/IntrRay3AxisAlignedBox3.h" +#include "MeshQueries.h" +#include "Spatial/SpatialInterfaces.h" +#include "Distance/DistTriangle3Triangle3.h" + +template +class TFastWindingTree; + +template +class TMeshAABBTree3 : public IMeshSpatial +{ + friend class TFastWindingTree; + +protected: + TriangleMeshType* Mesh; + int MeshTimestamp; + int TopDownLeafMaxTriCount = 4; + +public: + static constexpr double DOUBLE_MAX = TNumericLimits::Max(); + + /** + * If non-null, only triangle IDs that pass this filter (ie filter is true) are considered + */ + TFunction TriangleFilterF = nullptr; + + TMeshAABBTree3() + { + Mesh = nullptr; + } + + TMeshAABBTree3(TriangleMeshType* SourceMesh, bool bAutoBuild = true) + { + SetMesh(SourceMesh, bAutoBuild); + } + + void SetMesh(TriangleMeshType* SourceMesh, bool bAutoBuild = true) + { + Mesh = SourceMesh; + if (bAutoBuild) + { + Build(); + } + } + + const TriangleMeshType* GetMesh() const + { + return Mesh; + } + + void Build() + { + BuildTopDown(false); + MeshTimestamp = Mesh->GetShapeTimestamp(); + } + + virtual bool SupportsNearestTriangle() override + { + return true; + } + + /** + * Find the triangle closest to P, and distance to it, within distance MaxDist, or return InvalidID + * Use MeshQueries.TriangleDistance() to get more information + */ + virtual int FindNearestTriangle(const FVector3d& P, double& NearestDistSqr, double MaxDist = TNumericLimits::Max()) override + { + check(MeshTimestamp == Mesh->GetShapeTimestamp()); + + NearestDistSqr = (MaxDist < DOUBLE_MAX) ? MaxDist * MaxDist : DOUBLE_MAX; + int tNearID = IndexConstants::InvalidID; + find_nearest_tri(RootIndex, P, NearestDistSqr, tNearID); + return tNearID; + } + void find_nearest_tri(int IBox, const FVector3d& P, double& NearestDistSqr, int& TID) + { + int idx = BoxToIndex[IBox]; + if (idx < TrianglesEnd) + { // triangle-list case, array is [N t1 t2 ... tN] + int num_tris = IndexList[idx]; + for (int i = 1; i <= num_tris; ++i) + { + int ti = IndexList[idx + i]; + if (TriangleFilterF != nullptr && TriangleFilterF(ti) == false) + { + continue; + } + double fTriDistSqr = TMeshQueries::TriDistanceSqr(*Mesh, ti, P); + if (fTriDistSqr < NearestDistSqr) + { + NearestDistSqr = fTriDistSqr; + TID = ti; + } + } + } + else + { // internal node, either 1 or 2 child boxes + int iChild1 = IndexList[idx]; + if (iChild1 < 0) + { // 1 child, descend if nearer than cur min-dist + iChild1 = (-iChild1) - 1; + double fChild1DistSqr = BoxDistanceSqr(iChild1, P); + if (fChild1DistSqr <= NearestDistSqr) + { + find_nearest_tri(iChild1, P, NearestDistSqr, TID); + } + } + else + { // 2 children, descend closest first + iChild1 = iChild1 - 1; + int iChild2 = IndexList[idx + 1] - 1; + + double fChild1DistSqr = BoxDistanceSqr(iChild1, P); + double fChild2DistSqr = BoxDistanceSqr(iChild2, P); + if (fChild1DistSqr < fChild2DistSqr) + { + if (fChild1DistSqr < NearestDistSqr) + { + find_nearest_tri(iChild1, P, NearestDistSqr, TID); + if (fChild2DistSqr < NearestDistSqr) + { + find_nearest_tri(iChild2, P, NearestDistSqr, TID); + } + } + } + else + { + if (fChild2DistSqr < NearestDistSqr) + { + find_nearest_tri(iChild2, P, NearestDistSqr, TID); + if (fChild1DistSqr < NearestDistSqr) + { + find_nearest_tri(iChild1, P, NearestDistSqr, TID); + } + } + } + } + } + } + + virtual bool SupportsTriangleRayIntersection() override + { + return true; + } + + virtual int FindNearestHitTriangle(const FRay3d& Ray, double MaxDist = TNumericLimits::Max()) override + { + check(MeshTimestamp == Mesh->GetShapeTimestamp()); + // TODO: check( ray_is_normalized) + + // [RMS] note: using float.MaxValue here because we need to use <= to compare Box hit + // to NearestT, and Box hit returns double.MaxValue on no-hit. So, if we set + // nearestT to double.MaxValue, then we will test all boxes (!) + double NearestT = (MaxDist < TNumericLimits::Max()) ? MaxDist : TNumericLimits::Max(); + int tNearID = IndexConstants::InvalidID; + FindHitTriangle(RootIndex, Ray, NearestT, tNearID); + return tNearID; + } + + void FindHitTriangle(int IBox, const FRay3d& Ray, double& NearestT, int& TID) + { + int idx = BoxToIndex[IBox]; + if (idx < TrianglesEnd) + { // triangle-list case, array is [N t1 t2 ... tN] + FTriangle3d Triangle; + int num_tris = IndexList[idx]; + for (int i = 1; i <= num_tris; ++i) + { + int ti = IndexList[idx + i]; + if (TriangleFilterF != nullptr && TriangleFilterF(ti) == false) + { + continue; + } + + Mesh->GetTriVertices(ti, Triangle.V[0], Triangle.V[1], Triangle.V[2]); + FIntrRay3Triangle3d Query = FIntrRay3Triangle3d(Ray, Triangle); + if (Query.Find()) + { + if (Query.RayParameter < NearestT) + { + NearestT = Query.RayParameter; + TID = ti; + } + } + } + } + else + { // internal node, either 1 or 2 child boxes + double e = FMathd::ZeroTolerance; + + int iChild1 = IndexList[idx]; + if (iChild1 < 0) + { // 1 child, descend if nearer than cur min-dist + iChild1 = (-iChild1) - 1; + double fChild1T = box_ray_intersect_t(iChild1, Ray); + if (fChild1T <= NearestT + e) + { + FindHitTriangle(iChild1, Ray, NearestT, TID); + } + } + else + { // 2 children, descend closest first + iChild1 = iChild1 - 1; + int iChild2 = IndexList[idx + 1] - 1; + + double fChild1T = box_ray_intersect_t(iChild1, Ray); + double fChild2T = box_ray_intersect_t(iChild2, Ray); + if (fChild1T < fChild2T) + { + if (fChild1T <= NearestT + e) + { + FindHitTriangle(iChild1, Ray, NearestT, TID); + if (fChild2T <= NearestT + e) + { + FindHitTriangle(iChild2, Ray, NearestT, TID); + } + } + } + else + { + if (fChild2T <= NearestT + e) + { + FindHitTriangle(iChild2, Ray, NearestT, TID); + if (fChild1T <= NearestT + e) + { + FindHitTriangle(iChild1, Ray, NearestT, TID); + } + } + } + } + } + } + + /** + * Find nearest pair of triangles on this tree with otherTree, within max_dist. + * TransformF transforms vertices of otherTree into our coordinates. can be null. + * returns triangle-id pair (my_tri,other_tri), or Index2i::Invalid if not found within max_dist + * Use MeshQueries.TrianglesDistance() to get more information + */ + virtual FIndex2i FindNearestTriangles(TMeshAABBTree3& OtherTree, TFunction TransformF, double& Distance, double MaxDist = FMathd::MaxReal) + { + check(MeshTimestamp == Mesh->GetShapeTimestamp()); + + double NearestSqr = FMathd::MaxReal; + if (MaxDist < FMathd::MaxReal) + { + NearestSqr = MaxDist * MaxDist; + } + FIndex2i NearestPair = FIndex2i::Invalid(); + + find_nearest_triangles(RootIndex, OtherTree, TransformF, OtherTree.RootIndex, 0, NearestSqr, NearestPair); + Distance = (NearestSqr < FMathd::MaxReal) ? FMathd::Sqrt(NearestSqr) : FMathd::MaxReal; + return NearestPair; + } + + + + virtual bool SupportsPointContainment() override + { + return false; + } + + virtual bool IsInside(const FVector3d& P) override + { + return false; + } + + class FTreeTraversal + { + public: + // return false to terminate this branch + // arguments are Box and Depth in tree + TFunction NextBoxF = [](const FAxisAlignedBox3d& Box, int Depth) { return true; }; + TFunction NextTriangleF = [](int TID) {}; + }; + + /** + * Hierarchically descend through the tree Nodes, calling the TreeTrversal functions at each level + */ + virtual void DoTraversal(FTreeTraversal& Traversal) + { + check(MeshTimestamp == Mesh->GetShapeTimestamp()); + TreeTraversalImpl(RootIndex, 0, Traversal); + } + + // Traversal implementation. you can override to customize this if necessary. + virtual void TreeTraversalImpl(int IBox, int Depth, FTreeTraversal& Traversal) + { + int idx = BoxToIndex[IBox]; + + if (idx < TrianglesEnd) + { + // triangle-list case, array is [N t1 t2 ... tN] + int n = IndexList[idx]; + for (int i = 1; i <= n; ++i) + { + int ti = IndexList[idx + i]; + if (TriangleFilterF != nullptr && TriangleFilterF(ti) == false) + { + continue; + } + Traversal.NextTriangleF(ti); + } + } + else + { + int i0 = IndexList[idx]; + if (i0 < 0) + { + // negative index means we only have one 'child' Box to descend into + i0 = (-i0) - 1; + if (Traversal.NextBoxF(GetBox(i0), Depth + 1)) + { + TreeTraversalImpl(i0, Depth + 1, Traversal); + } + } + else + { + // positive index, two sequential child Box indices to descend into + i0 = i0 - 1; + if (Traversal.NextBoxF(GetBox(i0), Depth + 1)) + { + TreeTraversalImpl(i0, Depth + 1, Traversal); + } + int i1 = IndexList[idx + 1] - 1; + if (Traversal.NextBoxF(GetBox(i1), Depth + 1)) + { + TreeTraversalImpl(i1, Depth + 1, Traversal); + } + } + } + } + +protected: + // + // Internals - data structures, construction, etc + // + + FAxisAlignedBox3d GetBox(int IBox) const + { + const FVector3d& c = BoxCenters[IBox]; + const FVector3d& e = BoxExtents[IBox]; + FVector3d Min = c - e, Max = c + e; + return FAxisAlignedBox3d(Min, Max); + } + FAxisAlignedBox3d GetBox(int iBox, TFunction TransformF) + { + if (TransformF != nullptr) + { + FAxisAlignedBox3d box = GetBox(iBox); + return FAxisAlignedBox3d(box, TransformF); + } + else + { + return GetBox(iBox); + } + } + + FAxisAlignedBox3d GetBoxEps(int IBox, double Epsilon = FMathd::ZeroTolerance) const + { + const FVector3d& c = BoxCenters[IBox]; + FVector3d e = BoxExtents[IBox]; + e[0] += Epsilon; + e[1] += Epsilon; + e[2] += Epsilon; + FVector3d Min = c - e, Max = c + e; + return FAxisAlignedBox3d(Min, Max); + } + + const double BoxEps = 50.0 * FMathd::Epsilon; + + double BoxDistanceSqr(int IBox, const FVector3d& V) const + { + const FVector3d& c = BoxCenters[IBox]; + const FVector3d& e = BoxExtents[IBox]; + + // per-axis delta is max(abs(P-c) - e, 0)... ? + double dx = FMath::Max(fabs(V.X - c.X) - e.X, 0.0); + double dy = FMath::Max(fabs(V.Y - c.Y) - e.Y, 0.0); + double dz = FMath::Max(fabs(V.Z - c.Z) - e.Z, 0.0); + return dx * dx + dy * dy + dz * dz; + } + + bool box_contains(int IBox, const FVector3d& P) const + { + FAxisAlignedBox3d Box = GetBoxEps(IBox, BoxEps); + return Box.Contains(P); + } + + double box_ray_intersect_t(int IBox, const FRay3d& Ray) const + { + const FVector3d& c = BoxCenters[IBox]; + FVector3d e = BoxExtents[IBox] + BoxEps; + FAxisAlignedBox3d Box(c - e, c + e); + + double ray_t = TNumericLimits::Max(); + if (FIntrRay3AxisAlignedBox3d::FindIntersection(Ray, Box, ray_t)) + { + return ray_t; + } + else + { + return TNumericLimits::Max(); + } + } + + // storage for Box Nodes. + // - BoxToIndex is a pointer into IndexList + // - BoxCenters and BoxExtents are the Centers/extents of the bounding boxes + TDynamicVector BoxToIndex; + TDynamicVector BoxCenters; + TDynamicVector BoxExtents; + + // list of indices for a given Box. There is *no* marker/sentinel between + // boxes, you have to get the starting index from BoxToIndex[] + // + // There are three kinds of records: + // - if i < TrianglesEnd, then the list is a number of Triangles, + // stored as [N t1 t2 t3 ... tN] + // - if i > TrianglesEnd and IndexList[i] < 0, this is a single-child + // internal Box, with index (-IndexList[i])-1 (shift-by-one in case actual value is 0!) + // - if i > TrianglesEnd and IndexList[i] > 0, this is a two-child + // internal Box, with indices IndexList[i]-1 and IndexList[i+1]-1 + TDynamicVector IndexList; + + // IndexList[i] for i < TrianglesEnd is a triangle-index list, otherwise Box-index pair/single + int TrianglesEnd = -1; + + // BoxToIndex[RootIndex] is the root node of the tree + int RootIndex = -1; + + struct FBoxesSet + { + TDynamicVector BoxToIndex; + TDynamicVector BoxCenters; + TDynamicVector BoxExtents; + TDynamicVector IndexList; + int IBoxCur; + int IIndicesCur; + FBoxesSet() + { + IBoxCur = 0; + IIndicesCur = 0; + } + }; + + void BuildTopDown(bool bSorted) + { + // build list of valid Triangles & Centers. We skip any + // Triangles that have infinite/garbage vertices... + int i = 0; + TArray Triangles; + Triangles.SetNumUninitialized(Mesh->TriangleCount()); + TArray Centers; + Centers.SetNumUninitialized(Mesh->TriangleCount()); + for (int ti = 0; ti < Mesh->MaxTriangleID(); ti++) + { + if (!Mesh->IsTriangle(ti)) + { + continue; + } + FVector3d centroid = TMeshQueries::GetTriCentroid(*Mesh, ti); + double d2 = centroid.SquaredLength(); + bool bInvalid = FMathd::IsNaN(d2) || (FMathd::IsFinite(d2) == false); + check(bInvalid == false); + if (bInvalid == false) + { + Triangles[i] = ti; + Centers[i] = TMeshQueries::GetTriCentroid(*Mesh, ti); + i++; + } // otherwise skip this tri + } + + FBoxesSet Tris; + FBoxesSet Nodes; + FAxisAlignedBox3d rootBox; + int rootnode = + //(bSorted) ? split_tri_set_sorted(Triangles, Centers, 0, Mesh->TriangleCount, 0, TopDownLeafMaxTriCount, Tris, Nodes, out rootBox) : + SplitTriSetMidpoint(Triangles, Centers, 0, Mesh->TriangleCount(), 0, TopDownLeafMaxTriCount, Tris, Nodes, rootBox); + + BoxToIndex = Tris.BoxToIndex; + BoxCenters = Tris.BoxCenters; + BoxExtents = Tris.BoxExtents; + IndexList = Tris.IndexList; + TrianglesEnd = Tris.IIndicesCur; + int iIndexShift = TrianglesEnd; + int iBoxShift = Tris.IBoxCur; + + // ok now append internal node boxes & index ptrs + for (i = 0; i < Nodes.IBoxCur; ++i) + { + BoxCenters.InsertAt(Nodes.BoxCenters[i], iBoxShift + i); + BoxExtents.InsertAt(Nodes.BoxExtents[i], iBoxShift + i); + // internal node indices are shifted + BoxToIndex.InsertAt(iIndexShift + Nodes.BoxToIndex[i], iBoxShift + i); + } + + // now append index list + for (i = 0; i < Nodes.IIndicesCur; ++i) + { + int child_box = Nodes.IndexList[i]; + if (child_box < 0) + { // this is a Triangles Box + child_box = (-child_box) - 1; + } + else + { + child_box += iBoxShift; + } + child_box = child_box + 1; + IndexList.InsertAt(child_box, iIndexShift + i); + } + + RootIndex = rootnode + iBoxShift; + } + + int SplitTriSetMidpoint( + TArray& Triangles, + TArray& Centers, + int IStart, int ICount, int Depth, int MinTriCount, + FBoxesSet& Tris, FBoxesSet& Nodes, FAxisAlignedBox3d& Box) + { + Box = FAxisAlignedBox3d::Empty(); + int IBox = -1; + + if (ICount < MinTriCount) + { + // append new Triangles Box + IBox = Tris.IBoxCur++; + Tris.BoxToIndex.InsertAt(Tris.IIndicesCur, IBox); + + Tris.IndexList.InsertAt(ICount, Tris.IIndicesCur++); + for (int i = 0; i < ICount; ++i) + { + Tris.IndexList.InsertAt(Triangles[IStart + i], Tris.IIndicesCur++); + Box.Contain(TMeshQueries::GetTriBounds(*Mesh, Triangles[IStart + i])); + } + + Tris.BoxCenters.InsertAt(Box.Center(), IBox); + Tris.BoxExtents.InsertAt(Box.Extents(), IBox); + + return -(IBox + 1); + } + + //compute interval along an axis and find midpoint + int axis = Depth % 3; + FInterval1d interval = FInterval1d::Empty(); + for (int i = 0; i < ICount; ++i) + { + interval.Contain(Centers[IStart + i][axis]); + } + double midpoint = interval.Center(); + + int n0, n1; + if (interval.Length() > FMathd::ZeroTolerance) + { + // we have to re-sort the Centers & Triangles lists so that Centers < midpoint + // are first, so that we can recurse on the two subsets. We walk in from each side, + // until we find two out-of-order locations, then we swap them. + int l = 0; + int r = ICount - 1; + while (l < r) + { + // [RMS] is <= right here? if V.axis == midpoint, then this loop + // can get stuck unless one of these has an equality test. But + // I did not think enough about if this is the right thing to do... + while (Centers[IStart + l][axis] <= midpoint) + { + l++; + } + while (Centers[IStart + r][axis] > midpoint) + { + r--; + } + if (l >= r) + { + break; //done! + //swap + } + FVector3d tmpc = Centers[IStart + l]; + Centers[IStart + l] = Centers[IStart + r]; + Centers[IStart + r] = tmpc; + int tmpt = Triangles[IStart + l]; + Triangles[IStart + l] = Triangles[IStart + r]; + Triangles[IStart + r] = tmpt; + } + + n0 = l; + n1 = ICount - n0; + check(n0 >= 1 && n1 >= 1); + } + else + { + // interval is near-empty, so no point trying to do sorting, just split half and half + n0 = ICount / 2; + n1 = ICount - n0; + } + + // create child boxes + FAxisAlignedBox3d box1; + int child0 = SplitTriSetMidpoint(Triangles, Centers, IStart, n0, Depth + 1, MinTriCount, Tris, Nodes, Box); + int child1 = SplitTriSetMidpoint(Triangles, Centers, IStart + n0, n1, Depth + 1, MinTriCount, Tris, Nodes, box1); + Box.Contain(box1); + + // append new Box + IBox = Nodes.IBoxCur++; + Nodes.BoxToIndex.InsertAt(Nodes.IIndicesCur, IBox); + + Nodes.IndexList.InsertAt(child0, Nodes.IIndicesCur++); + Nodes.IndexList.InsertAt(child1, Nodes.IIndicesCur++); + + Nodes.BoxCenters.InsertAt(Box.Center(), IBox); + Nodes.BoxExtents.InsertAt(Box.Extents(), IBox); + + return IBox; + } + + + void find_nearest_triangles(int iBox, TMeshAABBTree3& otherTree, TFunction TransformF, int oBox, int depth, double &nearest_sqr, FIndex2i &nearest_pair) + { + int idx = BoxToIndex[iBox]; + int odx = otherTree.BoxToIndex[oBox]; + + if (idx < TrianglesEnd && odx < otherTree.TrianglesEnd) + { + // ok we are at triangles for both trees, do triangle-level testing + FTriangle3d tri, otri; + int num_tris = IndexList[idx], onum_tris = otherTree.IndexList[odx]; + + FDistTriangle3Triangle3d dist; + + // outer iteration is "other" tris that need to be transformed (more expensive) + for (int j = 1; j <= onum_tris; ++j) + { + int tj = otherTree.IndexList[odx + j]; + if (otherTree.TriangleFilterF != nullptr && otherTree.TriangleFilterF(tj) == false) + { + continue; + } + otherTree.Mesh->GetTriVertices(tj, otri.V[0], otri.V[1], otri.V[2]); + if (TransformF != nullptr) + { + otri.V[0] = TransformF(otri.V[0]); + otri.V[1] = TransformF(otri.V[1]); + otri.V[2] = TransformF(otri.V[2]); + } + dist.Triangle[0] = otri; + + // inner iteration over "our" triangles + for (int i = 1; i <= num_tris; ++i) + { + int ti = IndexList[idx + i]; + if (TriangleFilterF != nullptr && TriangleFilterF(ti) == false) + { + continue; + } + Mesh->GetTriVertices(ti, tri.V[0], tri.V[1], tri.V[2]); + dist.Triangle[1] = tri; + double dist_sqr = dist.GetSquared(); + if (dist_sqr < nearest_sqr) + { + nearest_sqr = dist_sqr; + nearest_pair = FIndex2i(ti, tj); + } + } + } + + return; + } + + // we either descend "our" tree or the other tree + // - if we have hit triangles on "our" tree, we have to descend other + // - if we hit triangles on "other", we have to descend ours + // - otherwise, we alternate at each depth. This produces wider + // branching but is significantly faster (~10x) for both hits and misses + bool bDescendOther = (idx < TrianglesEnd || depth % 2 == 0); + if (bDescendOther && odx < otherTree.TrianglesEnd) + { + bDescendOther = false; // can't + } + + if (bDescendOther) + { + // ok we reached triangles on our side but we need to still reach triangles on + // the other side, so we descend "their" children + FAxisAlignedBox3d bounds = GetBox(iBox); + + int oChild1 = otherTree.IndexList[odx]; + if (oChild1 < 0) // 1 child, descend if nearer than cur min-dist + { + oChild1 = (-oChild1) - 1; + FAxisAlignedBox3d oChild1Box = otherTree.GetBox(oChild1, TransformF); + if (oChild1Box.DistanceSquared(bounds) < nearest_sqr) + { + find_nearest_triangles(iBox, otherTree, TransformF, oChild1, depth + 1, nearest_sqr, nearest_pair); + } + } + else // 2 children + { + oChild1 = oChild1 - 1; + int oChild2 = otherTree.IndexList[odx + 1] - 1; + + FAxisAlignedBox3d oChild1Box = otherTree.GetBox(oChild1, TransformF); + FAxisAlignedBox3d oChild2Box = otherTree.GetBox(oChild2, TransformF); + + // descend closer box first + double d1Sqr = oChild1Box.DistanceSquared(bounds); + double d2Sqr = oChild2Box.DistanceSquared(bounds); + if (d2Sqr < d1Sqr) + { + if (d2Sqr < nearest_sqr) + { + find_nearest_triangles(iBox, otherTree, TransformF, oChild2, depth + 1, nearest_sqr, nearest_pair); + } + if (d1Sqr < nearest_sqr) + { + find_nearest_triangles(iBox, otherTree, TransformF, oChild1, depth + 1, nearest_sqr, nearest_pair); + } + } + else + { + if (d1Sqr < nearest_sqr) + { + find_nearest_triangles(iBox, otherTree, TransformF, oChild1, depth + 1, nearest_sqr, nearest_pair); + } + if (d2Sqr < nearest_sqr) + { + find_nearest_triangles(iBox, otherTree, TransformF, oChild2, depth + 1, nearest_sqr, nearest_pair); + } + } + } + } + else + { + // descend our tree nodes if they intersect w/ current bounds of other tree + FAxisAlignedBox3d oBounds = otherTree.GetBox(oBox, TransformF); + + int iChild1 = IndexList[idx]; + if (iChild1 < 0) // 1 child, descend if nearer than cur min-dist + { + iChild1 = (-iChild1) - 1; + if (box_box_distsqr(iChild1, oBounds) < nearest_sqr) + { + find_nearest_triangles(iChild1, otherTree, TransformF, oBox, depth + 1, nearest_sqr, nearest_pair); + } + } + else // 2 children + { + iChild1 = iChild1 - 1; + int iChild2 = IndexList[idx + 1] - 1; + + // descend closer box first + double d1Sqr = box_box_distsqr(iChild1, oBounds); + double d2Sqr = box_box_distsqr(iChild2, oBounds); + if (d2Sqr < d1Sqr) + { + if (d2Sqr < nearest_sqr) + { + find_nearest_triangles(iChild2, otherTree, TransformF, oBox, depth + 1, nearest_sqr, nearest_pair); + } + if (d1Sqr < nearest_sqr) + { + find_nearest_triangles(iChild1, otherTree, TransformF, oBox, depth + 1, nearest_sqr, nearest_pair); + } + } + else + { + if (d1Sqr < nearest_sqr) + { + find_nearest_triangles(iChild1, otherTree, TransformF, oBox, depth + 1, nearest_sqr, nearest_pair); + } + if (d2Sqr < nearest_sqr) + { + find_nearest_triangles(iChild2, otherTree, TransformF, oBox, depth + 1, nearest_sqr, nearest_pair); + } + } + } + } + } + + double box_box_distsqr(int iBox, const FAxisAlignedBox3d& testBox) + { + // [TODO] could compute this w/o constructing box + FAxisAlignedBox3d box = GetBoxEps(iBox, BoxEps); + return box.DistanceSquared(testBox); + } + + + +public: + // 1) make sure we can reach every tri in Mesh through tree (also demo of how to traverse tree...) + // 2) make sure that Triangles are contained in parent boxes + void TestCoverage() + { + TArray tri_counts; + tri_counts.SetNumZeroed(Mesh->MaxTriangleID()); + + TArray parent_indices; + parent_indices.SetNumZeroed(BoxToIndex.GetLength()); + + test_coverage(tri_counts, parent_indices, RootIndex); + + for (int ti = 0; ti < Mesh->MaxTriangleID(); ti++) + { + if (!Mesh->IsTriangle(ti)) + { + continue; + } + check(tri_counts[ti] == 1); + } + } + + /** + * Total sum of volumes of all boxes in the tree. Mainly useful to evaluate tree quality. + */ + double TotalVolume() + { + double volSum = 0; + FTreeTraversal t; + t.NextBoxF = [&](const FAxisAlignedBox3d& Box, int) + { + volSum += Box.Volume(); + return true; + }; + DoTraversal(t); + return volSum; + } + +private: + // accumulate triangle counts and track each Box-parent index. + // also checks that Triangles are contained in boxes + void test_coverage(TArray& tri_counts, TArray& parent_indices, int IBox) + { + int idx = BoxToIndex[IBox]; + + debug_check_child_tris_in_box(IBox); + + if (idx < TrianglesEnd) + { + // triangle-list case, array is [N t1 t2 ... tN] + int n = IndexList[idx]; + FAxisAlignedBox3d Box = GetBoxEps(IBox); + for (int i = 1; i <= n; ++i) + { + int ti = IndexList[idx + i]; + tri_counts[ti]++; + + FIndex3i tv = Mesh->GetTriangle(ti); + for (int j = 0; j < 3; ++j) + { + FVector3d V = Mesh->GetVertex(tv[j]); + check(Box.Contains(V)); + } + } + } + else + { + int i0 = IndexList[idx]; + if (i0 < 0) + { + // negative index means we only have one 'child' Box to descend into + i0 = (-i0) - 1; + parent_indices[i0] = IBox; + test_coverage(tri_counts, parent_indices, i0); + } + else + { + // positive index, two sequential child Box indices to descend into + i0 = i0 - 1; + parent_indices[i0] = IBox; + test_coverage(tri_counts, parent_indices, i0); + int i1 = IndexList[idx + 1]; + i1 = i1 - 1; + parent_indices[i1] = IBox; + test_coverage(tri_counts, parent_indices, i1); + } + } + } + // do full tree Traversal below IBox and make sure that all Triangles are further + // than Box-distance-sqr + void debug_check_child_tri_distances(int IBox, const FVector3d& P) + { + double fBoxDistSqr = BoxDistanceSqr(IBox, P); + + // [TODO] + FTreeTraversal t; + t.NextTriangleF = [&](int TID) + { + double fTriDistSqr = TMeshQueries::TriDistanceSqr(*Mesh, TID, P); + if (fTriDistSqr < fBoxDistSqr) + { + check(fabs(fTriDistSqr - fBoxDistSqr) <= FMathd::ZeroTolerance * 100); + } + }; + TreeTraversalImpl(IBox, 0, t); + } + + // do full tree Traversal below IBox to make sure that all child Triangles are contained + void debug_check_child_tris_in_box(int IBox) + { + FAxisAlignedBox3d Box = GetBoxEps(IBox); + FTreeTraversal t; + t.NextTriangleF = [&](int TID) + { + FIndex3i tv = Mesh->GetTriangle(TID); + for (int j = 0; j < 3; ++j) + { + FVector3d V = Mesh->GetVertex(tv[j]); + check(Box.Contains(V)); + } + }; + TreeTraversalImpl(IBox, 0, t); + } +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/PointHashGrid2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/PointHashGrid2.h new file mode 100644 index 000000000000..fc464e66ff1c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/PointHashGrid2.h @@ -0,0 +1,207 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp PointHashGrid2 + +#include "CoreMinimal.h" +#include "Misc/ScopeLock.h" +#include "Util/GridIndexing2.h" + +#pragma once + + +/** + * Hash Grid for values associated with 2D points. + * + * This class addresses the situation where you have a list of (point, point_data) and you + * would like to be able to do efficient proximity queries, ie find the nearest point_data + * for a given query point. + * + * We don't store copies of the 2D points. You provide a point_data type. This could just be the + * integer index into your list for example, a pointer to something more complex, etc. + * Insert and Remove functions require you to pass in the 2D point for the point_data. + * To Update a point you need to know it's old and new 2D coordinates. + * + * @todo also template on point realtype? + */ +template +class TPointHashGrid2d +{ +private: + TMultiMap Hash; + FCriticalSection CriticalSection; + FScaleGridIndexer2d Indexer; + PointDataType invalidValue; + +public: + + /** + * Construct 2D hashgrid + * @param cellSize size of grid cells + * @param invalidValue this value will be returned by queries if no valid result is found (eg bounded-distance query) + */ + TPointHashGrid2d(double cellSize, PointDataType InvalidValue) : Indexer(cellSize), invalidValue(InvalidValue) + { + } + + /** Invalid grid value */ + PointDataType InvalidValue() const + { + return invalidValue; + } + + /** + * Insert at given position. This function is thread-safe. + * @param Value the point/value to insert + * @param Position the position associated with this value + */ + void InsertPoint(const PointDataType& Value, const FVector2d& Position) + { + FVector2i idx = Indexer.ToGrid(Position); + { + FScopeLock Lock(&CriticalSection); + Hash.Add(idx, Value); + } + } + + /** + * Insert at given position, without locking / thread-safety + * @param Value the point/value to insert + * @param Position the position associated with this value + */ + void InsertPointUnsafe(const PointDataType& Value, const FVector2d& Position) + { + FVector2i idx = Indexer.ToGrid(Position); + Hash.Add(idx, Value); + } + + + /** + * Remove at given position. This function is thread-safe. + * @param Value the point/value to remove + * @param Position the position associated with this value + * @return true if the value existed at this position + */ + bool RemovePoint(const PointDataType& Value, const FVector2d& Position) + { + FVector2i idx = Indexer.ToGrid(Position); + { + FScopeLock Lock(&CriticalSection); + return Hash.RemoveSingle(idx, Value) > 0; + } + } + + /** + * Remove at given position, without locking / thread-safety + * @param Value the point/value to remove + * @param Position the position associated with this value + * @return true if the value existed at this position + */ + bool RemovePointUnsafe(const PointDataType& Value, const FVector2d& Position) + { + FVector2i idx = Indexer.ToGrid(Position); + return Hash.RemoveSingle(idx, Value) > 0; + } + + + /** + * Move value from old to new position. This function is thread-safe. + * @param Value the point/value to update + * @param OldPosition the current position associated with this value + * @param NewPosition the new position for this value + */ + void UpdatePoint(const PointDataType& Value, const FVector2d& OldPosition, const FVector2d& NewPosition) + { + FVector2i old_idx = Indexer.ToGrid(OldPosition); + FVector2i new_idx = Indexer.ToGrid(NewPosition); + if (old_idx == new_idx) + { + return; + } + bool bWasAtOldPos; + { + FScopeLock Lock(&CriticalSection); + bWasAtOldPos = Hash.RemoveSingle(old_idx, Value); + } + check(bWasAtOldPos); + { + FScopeLock Lock(&CriticalSection); + Hash.Add(new_idx, Value); + } + return; + } + + + /** + * Move value from old to new position, without locking / thread-safety + * @param Value the point/value to update + * @param OldPosition the current position associated with this value + * @param NewPosition the new position for this value + */ + void UpdatePointUnsafe(const PointDataType& Value, const FVector2d& OldPosition, const FVector2d& NewPosition) + { + FVector2i old_idx = Indexer.ToGrid(OldPosition); + FVector2i new_idx = Indexer.ToGrid(NewPosition); + if (old_idx == new_idx) + { + return; + } + bool bWasAtOldPos = Hash.RemoveSingle(old_idx, Value); + check(bWasAtOldPos); + Hash.Add(new_idx, Value); + return; + } + + + /** + * Find nearest point in grid, within a given sphere, without locking / thread-safety. + * @param QueryPoint the center of the query sphere + * @param Radius the radius of the query sphere + * @param DistanceFunc Function you provide which measures the distance between QueryPoint and a Value + * @param IgnoreFunc optional Function you may provide which will result in a Value being ignored if IgnoreFunc(Value) returns true + * @return the found pair (Value,DistanceFunc(Value)), or (InvalidValue,MaxDouble) if not found + */ + TPair FindNearestInRadius( + const FVector2d& QueryPoint, double Radius, + TFunction DistanceSqFunc, + TFunction IgnoreFunc = [](const PointDataType& data) { return false; }) const + { + if (!Hash.Num()) + { + return TPair(InvalidValue(), TNumericLimits::Max()); + } + + FVector2i min_idx = Indexer.ToGrid(QueryPoint - Radius * FVector2d::One()); + FVector2i max_idx = Indexer.ToGrid(QueryPoint + Radius * FVector2d::One()); + + double min_distsq = TNumericLimits::Max(); + PointDataType nearest = InvalidValue(); + double RadiusSquared = Radius * Radius; + + TArray Values; + for (int yi = min_idx.Y; yi <= max_idx.Y; yi++) + { + for (int xi = min_idx.X; xi <= max_idx.X; xi++) + { + FVector2i idx(xi, yi); + Values.Reset(); + Hash.MultiFind(idx, Values); + for (PointDataType Value : Values) + { + if (IgnoreFunc(Value)) + { + continue; + } + double distsq = DistanceSqFunc(Value); + if (distsq < RadiusSquared && distsq < min_distsq) + { + nearest = Value; + min_distsq = distsq; + } + } + } + } + + return TPair(nearest, min_distsq); + } +}; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/PointSetHashTable.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/PointSetHashTable.h new file mode 100644 index 000000000000..9688ac9b0df9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/PointSetHashTable.h @@ -0,0 +1,61 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp PointSetHashTable + +#pragma once + +#include "CoreMinimal.h" +#include "BoxTypes.h" +#include "PointSetAdapter.h" +#include "Spatial/SparseGrid3.h" +#include "Util/GridIndexing3.h" + +/** + * FPointSetHashTable builds a spatial data structure that supports efficient + * range queries on a point set (in FPointSetAdapterd form). The spatial data + * structure is currently a uniform sparse 3D grid. + * + * @todo support larger search radius than cellsize + */ +class GEOMETRICOBJECTS_API FPointSetHashtable +{ +protected: + typedef TArray PointList; + + /** Input point set */ + FPointSetAdapterd* Points; + /** Sparse grid, each voxel contains lists of contained points */ + TSparseGrid3 Grid; + /** index mapping object */ + FShiftGridIndexer3d GridIndexer; + + /** World origin of sparse grid */ + FVector3d Origin; + /** Cell size of grid */ + double CellSize; + +public: + FPointSetHashtable(FPointSetAdapterd* PointSetIn) + { + Points = PointSetIn; + } + + /** + * Construct the spatial data structure for the current point set + * @param CellSize the size of the cells/voxels in the grid. + * @param Origin World origin of the grid (not strictly necessary since grid is sparse, can set to origin for example) + */ + void Build(double CellSize, const FVector3d& Origin); + + /** + * Find all points within given query distance from query point. + * Note that in current implementation the distance must be less than the CellSize used in construction, + * ie at most the directly adjacent neighbours next to the cell containing QueryPt can be searched + * @param QueryPt center of search sphere/ball + * @param QueryRadius radius of search sphere/ball. Points within this distance of QueryPt are returned. + * @param ResultOut indices of discovered points are stored in this list + * @return true on success + */ + bool FindPointsInBall(const FVector3d& QueryPt, double QueryRadius, TArray& ResultOut); + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/SparseGrid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/SparseGrid3.h new file mode 100644 index 000000000000..3707c7dbbf21 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/SparseGrid3.h @@ -0,0 +1,147 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp DSparseGrid3 + +#pragma once + +#include "CoreMinimal.h" +#include "BoxTypes.h" + +/** + * Dynamic sparse 3D grid. Useful in cases where we have grid of some type of non-trivial + * object and we don't want to pre-allocate full grid of them. So we allocate on-demand. + * This can be used to implement multi-grid schemes, eg for example the ElemType + * could be sub-grid of fixed dimension. + * + * @todo support pooling/re-use of allocated elements? currently not required by any use cases. + * @todo port AllocatedIndices() and Allocated() iterators + */ +template +class TSparseGrid3 +{ +protected: + /** sparse grid of allocated elements */ + TMap Elements; + /** accumulated bounds of all indices inserted into Elements. Not currently used internally. */ + FAxisAlignedBox3i Bounds; + +public: + + /** + * Create empty grid + */ + TSparseGrid3() + { + Bounds = FAxisAlignedBox3i::Empty(); + } + + /** + * Deletes all grid elements + */ + ~TSparseGrid3() + { + FreeAll(); + } + + + /** + * @param Index an integer grid index + * @return true if there is an allocated element at this index + */ + bool Has(const FVector3i& Index) const + { + return Elements.Contains(Index); + } + + + /** + * Get the grid element at this index, and optionally allocate it if it doesn't exist + * @param Index integer grid index + * @param bAllocateIfMissing if the element at this index is null, allocate a new one + * @return pointer to ElemType instance, or nullptr if element doesn't exist + */ + ElemType* Get(const FVector3i& Index, bool bAllocateIfMissing = true) + { + ElemType* result = Elements.FindRef(Index); + if (result != nullptr) + { + return result; + } + if (bAllocateIfMissing == true) + { + return Allocate(Index); + } + return nullptr; + } + + /** + * Delete an element in the grid + * @param Index integer grid index + * @return true if the element existed, false if it did not + */ + bool Free(const FVector3i& Index) + { + if (Elements.Contains(Index)) + { + delete Elements[Index]; + Elements.Remove(Index); + return true; + } + return false; + } + + /** + * Delete all elements in the grid + */ + void FreeAll() + { + for (auto pair : Elements) + { + delete pair.Value; + } + Elements.Reset(); + } + + /** + * @return number of allocated elements in the grid + */ + int GetCount() const + { + return Elements.Num(); + } + + /** + * @return ratio of allocated elements to total number of possible cells for current bounds + */ + float GetDensity() const + { + return (float)Elements.Num() / (float)Bounds.Volume(); + } + + /** + * @return integer range of valid grid indices [min,max] (inclusive) + */ + FAxisAlignedBox3i GetBoundsInclusive() const + { + return Bounds; + } + + /** + * @return dimensions of grid along each axis + */ + FVector3i GetDimensions() const + { + return Bounds.Diagonal() + FVector3i::One(); + } + + +protected: + + ElemType* Allocate(const FVector3i& index) + { + ElemType* NewElem = new ElemType(); + Elements.Add(index, NewElem); + Bounds.Contain(index); + return NewElem; + } +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/SpatialInterfaces.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/SpatialInterfaces.h new file mode 100644 index 000000000000..caf532cc59c5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/SpatialInterfaces.h @@ -0,0 +1,133 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp SpatialInterfaces + +#pragma once + +#include "VectorTypes.h" +#include "RayTypes.h" + + +/** + * ISpatial is a base interface for spatial queries + */ +class ISpatial +{ +public: + virtual ~ISpatial() {} + + /** + * @return true if this object supports point-containment inside/outside queries + */ + virtual bool SupportsPointContainment() = 0; + + /** + * @return true if the query point is inside the object + */ + virtual bool IsInside(const FVector3d & Point) = 0; +}; + + + +/** + * IMeshSpatial is an extension of ISpatial specifically for meshes + */ +class IMeshSpatial : public ISpatial +{ +public: + virtual ~IMeshSpatial() {} + + /** + * @return true if this object supports nearest-triangle queries + */ + virtual bool SupportsNearestTriangle() = 0; + + /** + * @param Query point + * @param NearestDistSqrOut returned nearest squared distance, if triangle is found + * @param MaxDistance maximum search distance + * @return ID of triangle nearest to Point within MaxDistance, or InvalidID if not found + */ + virtual int FindNearestTriangle(const FVector3d& Point, double& NearestDistSqrOut, double MaxDistance = TNumericLimits::Max()) = 0; + + + /** + * @return true if this object supports ray-triangle intersection queries + */ + virtual bool SupportsTriangleRayIntersection() = 0; + + /** + * @param Ray query ray + * @param MaxDistance maximum hit distance + * @return ID of triangle intersected by ray within MaxDistance, or InvalidID if not found + */ + virtual int FindNearestHitTriangle(const FRay3d& Ray, double MaxDistance = TNumericLimits::Max()) = 0; +}; + + + +/** + * IProjectionTarget is an object that supports projecting a 3D point onto it + */ +class IProjectionTarget +{ +public: + virtual ~IProjectionTarget() {} + + /** + * @param Point the point to project onto the target + * @param Identifier client-defined integer identifier of the point (may not be used) + * @return position of Point projected onto the target + */ + virtual FVector3d Project(const FVector3d& Point, int Identifier = -1) = 0; +}; + + + +/** + * IOrientedProjectionTarget is a projection target that can return a normal in addition to the projected point + */ +class IOrientedProjectionTarget : public IProjectionTarget +{ +public: + virtual ~IOrientedProjectionTarget() {} + + /** + * @param Point the point to project onto the target + * @param Identifier client-defined integer identifier of the point (may not be used) + * @return position of Point projected onto the target + */ + virtual FVector3d Project(const FVector3d& Point, int Identifier = -1) override = 0; + + /** + * @param Point the point to project onto the target + * @param ProjectNormalOut the normal at the projection point + * @param Identifier client-defined integer identifier of the point (may not be used) + * @return position of Point projected onto the target + */ + virtual FVector3d Project(const FVector3d& Point, FVector3d& ProjectNormalOut, int Identifier = -1) = 0; +}; + + + +/** + * IIntersectionTarget is an object that can be intersected with a ray + */ +class IIntersectionTarget +{ +public: + virtual ~IIntersectionTarget() {} + + /** + * @return true if RayIntersect will return a normal + */ + virtual bool HasNormal() = 0; + + /** + * @param Ray query ray + * @param HitOut returned hit point + * @param HitNormalOut returned hit point normal + * @return true if ray hit the object + */ + virtual bool RayIntersect(const FRay3d& Ray, FVector3d& HitOut, FVector3d& HitNormalOut) = 0; +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/TriangleTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/TriangleTypes.h new file mode 100644 index 000000000000..d665f3eadfc3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/TriangleTypes.h @@ -0,0 +1,174 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of WildMagic DistPoint3TTriangle3 + +#pragma once + +#include "VectorTypes.h" +#include "VectorUtil.h" + +/** + * Triangle utility functions + */ +namespace TriangleUtil +{ + /** + * @return the edge length of an equilateral/regular triangle with the given area + */ + template + RealType EquilateralEdgeLengthForArea(RealType TriArea) + { + return TMathUtil::Sqrt(((RealType)4 * TriArea) / TMathUtil::Sqrt3); + } + +}; + + + + +template +struct TTriangle2 +{ + FVector2 V[3]; + + TTriangle2() {} + + TTriangle2(const FVector2& V0, const FVector2& V1, const FVector2& V2) + { + V[0] = V0; + V[1] = V1; + V[2] = V2; + } + + TTriangle2(const FVector2 VIn[3]) + { + V[0] = VIn[0]; + V[1] = VIn[1]; + V[2] = VIn[2]; + } + + FVector2 BarycentricPoint(RealType Bary0, RealType Bary1, RealType Bary2) const + { + return Bary0 * V[0] + Bary1 * V[1] + Bary2 * V[2]; + } + + FVector2 BarycentricPoint(const FVector3& BaryCoords) const + { + return BaryCoords[0] * V[0] + BaryCoords[1] * V[1] + BaryCoords[2] * V[2]; + } + + + /** + * @param A first vertex of triangle + * @param B second vertex of triangle + * @param C third vertex of triangle + * @return signed area of triangle + */ + static RealType SignedArea(const FVector2& A, const FVector2& B, const FVector2& C) + { + return ((RealType)0.5) * ((A.X*B.Y - A.Y*B.X) + (B.X*C.Y - B.Y*C.X) + (C.X*A.Y - C.Y*A.X)); + } + + /** @return signed area of triangle */ + RealType SignedArea() const + { + return SignedArea(V[0], V[1], V[2]); + } + + + /** + * @param A first vertex of triangle + * @param B second vertex of triangle + * @param C third vertex of triangle + * @param QueryPoint test point + * @return true if QueryPoint is inside triangle + */ + static bool IsInside(const FVector2& A, const FVector2& B, const FVector2& C, const FVector2& QueryPoint) + { + RealType Sign1 = FVector2::Orient(A, B, QueryPoint); + RealType Sign2 = FVector2::Orient(B, C, QueryPoint); + RealType Sign3 = FVector2::Orient(C, A, QueryPoint); + return (Sign1*Sign2 > 0) && (Sign2*Sign3 > 0) && (Sign3*Sign1 > 0); + } + + /** @return true if QueryPoint is inside triangle */ + bool IsInside(const FVector2& QueryPoint) const + { + return IsInside(V[0], V[1], V[2], QueryPoint); + } + + +}; + +typedef TTriangle2 FTriangle2f; +typedef TTriangle2 FTriangle2d; +typedef TTriangle2 FTriangle2i; + + + + + + +template +struct TTriangle3 +{ + FVector3 V[3]; + + TTriangle3() {} + + TTriangle3(const FVector3& V0, const FVector3& V1, const FVector3& V2) + { + V[0] = V0; + V[1] = V1; + V[2] = V2; + } + + TTriangle3(const FVector3 VIn[3]) + { + V[0] = VIn[0]; + V[1] = VIn[1]; + V[2] = VIn[2]; + } + + FVector3 BarycentricPoint(RealType Bary0, RealType Bary1, RealType Bary2) const + { + return Bary0*V[0] + Bary1*V[1] + Bary2*V[2]; + } + + FVector3 BarycentricPoint(const FVector3 & BaryCoords) const + { + return BaryCoords[0]*V[0] + BaryCoords[1]*V[1] + BaryCoords[2]*V[2]; + } + + + /** @return vector that is perpendicular to the plane of this triangle */ + FVector3 Normal() const + { + return VectorUtil::Normal(V[0], V[1], V[2]); + } + + /** @return centroid of this triangle */ + FVector3 Centroid() const + { + constexpr RealType f = 1.0 / 3.0; + return FVector3( + (V[0].X + V[1].X + V[2].X) * f, + (V[0].Y + V[1].Y + V[2].Y) * f, + (V[0].Z + V[1].Z + V[2].Z) * f + ); + } + + /** grow the triangle around the centroid */ + void Expand(RealType Delta) + { + FVector3 Centroid(Centroid()); + V[0] += Delta * ((V[0] - Centroid).Normalized()); + V[1] += Delta * ((V[1] - Centroid).Normalized()); + V[2] += Delta * ((V[2] - Centroid).Normalized()); + } +}; + +typedef TTriangle3 FTriangle3f; +typedef TTriangle3 FTriangle3d; +typedef TTriangle3 FTriangle3i; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/BufferUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/BufferUtil.h new file mode 100644 index 000000000000..041328b42a32 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/BufferUtil.h @@ -0,0 +1,79 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include + +/** + * Utility functions for processing arrays of objects + * @todo possibly these functions should go in Algo:: or somewhere else in Core? + */ +namespace BufferUtil +{ + + + /** + * Count number of elements in array (or within requested range) that pass Predicate test + * @param Data Array to process + * @param Predicate filtering function, return true indicates valid + * @param MaxIndex optional maximum index in array (exclusive, default is to use array size) + * @param StartIndex optional start index in array (default 0) + * @return number of values in array that returned true for Predicate + */ + template + int CountValid(const TArray& Data, const TFunction& Predicate, int MaxIndex = -1, int StartIndex = 0) + { + int StopIndex = (MaxIndex == -1) ? Data.Num() : MaxIndex; + int NumValid = 0; + for (int i = StartIndex; i < StopIndex; ++i) + { + if (Predicate(Data[i]) == true) + { + NumValid++; + } + } + return NumValid; + } + + + /** + * Removes elements of array (or within requested range) that do not pass Predicate, by shifting forward. + * Does not remove remaining elements + * @param Data Array to process + * @param Predicate filtering function, return true indicates valid + * @param MaxIndex optional maximum index in array (exclusive, default is to use array size) + * @param StartIndex optional start index in array (default 0) + * @return Number of valid elements in Data after filtering + */ + template + int FilterInPlace(TArray& Data, const TFunction& Predicate, int MaxIndex = -1, int StartIndex = 0) + { + int StopIndex = (MaxIndex == -1) ? Data.Num() : MaxIndex; + int StoreIndex = StartIndex; + for (int i = StartIndex; i < StopIndex; ++i) + { + if (Predicate(Data[i]) == true) + { + Data[StoreIndex++] = Data[i]; + } + } + return StoreIndex; + } + + + + /** + * Append enumerable elements to an array + * @param Data array to append to + * @param Enumerable object that can be iterated over with a range-based for loop + */ + template + void AppendElements(TArray& AppendTo, EnumerableType Enumerable) + { + for ( T value : Enumerable ) + { + AppendTo.Add(value); + } + } + +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/DynamicVector.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/DynamicVector.h new file mode 100644 index 000000000000..28480429224e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/DynamicVector.h @@ -0,0 +1,498 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp dvector + +#pragma once + +#include +#include "Containers/StaticArray.h" +#include "VectorTypes.h" +#include "IndexTypes.h" + + +/* + * Blocked array with fixed, power-of-two sized blocks. + * + * Iterator functions suitable for use with range-based for are provided + */ +template +class TDynamicVector +{ +public: + TDynamicVector() + { + CurBlock = 0; + CurBlockUsed = 0; + Blocks.Emplace(); + } + + TDynamicVector(const TDynamicVector& Copy) + { + *this = Copy; + } + + TDynamicVector(TDynamicVector&& Moved) + { + *this = MoveTemp(Moved); + } + + ~TDynamicVector() {} + + const TDynamicVector& operator=(const TDynamicVector& Copy); + const TDynamicVector& operator=(TDynamicVector&& Moved); + + inline void Clear(); + inline void Fill(const Type& Value); + inline void Resize(size_t Count); + inline void Resize(size_t Count, const Type& InitValue); + inline void SetNum(size_t Count) { Resize(Count); } + + inline bool IsEmpty() const { return CurBlock == 0 && CurBlockUsed == 0; } + inline size_t GetLength() const { return CurBlock * BlockSize + CurBlockUsed; } + inline size_t Num() const { return CurBlock * BlockSize + CurBlockUsed; } + inline int GetBlockSize() const { return BlockSize; } + inline size_t GetByteCount() const { return Blocks.Num() * BlockSize * sizeof(Type); } + + inline void Add(const Type& Data); + inline void Add(const TDynamicVector& Data); + inline void PopBack(); + + inline void InsertAt(const Type& Data, unsigned int Index); + + inline const Type& Front() const { return Blocks[0][0]; } + inline const Type& Back() const { return Blocks[CurBlock][CurBlockUsed - 1]; } + + inline Type& operator[](unsigned int Index) + { + return Blocks[Index >> nShiftBits][Index & BlockIndexBitmask]; + } + inline const Type& operator[](unsigned int Index) const + { + return Blocks[Index >> nShiftBits][Index & BlockIndexBitmask]; + } + + // apply f() to each member sequentially + template + void Apply(const Func& f); + + +protected: + // [RMS] BlockSize must be a power-of-two, so we can use bit-shifts in operator[] + static constexpr int BlockSize = 2048; // (1 << 11) + static constexpr int nShiftBits = 11; + static constexpr int BlockIndexBitmask = 2047; // low 11 bits + + unsigned int CurBlock; + unsigned int CurBlockUsed; + + using BlockType = TStaticArray; + TArray Blocks; + + friend class FIterator; + + +public: + /* + * FIterator class iterates over values of vector + */ + class FIterator + { + public: + inline const Type& operator*() const + { + return (*DVector)[Idx]; + } + inline Type& operator*() + { + return (*DVector)[Idx]; + } + inline FIterator& operator++() // prefix + { + Idx++; + return *this; + } + inline FIterator operator++(int) // postfix + { + FIterator Copy(*this); + Idx++; + return Copy; + } + inline bool operator==(const FIterator& Itr2) + { + return DVector == Itr2.DVector && Idx == Itr2.Idx; + } + inline bool operator!=(const FIterator& Itr2) + { + return DVector != Itr2.DVector || Idx != Itr2.Idx; + } + + protected: + TDynamicVector* DVector; + int Idx; + inline FIterator(TDynamicVector* Parent, int ICur) + { + DVector = Parent; + Idx = ICur; + } + friend class TDynamicVector; + }; + + /** @return iterator at beginning of vector */ + FIterator begin() + { + return IsEmpty() ? end() : FIterator(this, 0); + } + /** @return iterator at end of vector */ + FIterator end() + { + return FIterator(this, (int)GetLength()); + } +}; + + + + + + +template +class TDynamicVectorN +{ +public: + TDynamicVectorN() + { + } + TDynamicVectorN(const TDynamicVectorN& Copy) + : Data(Copy.Data) + { + } + TDynamicVectorN(TDynamicVectorN&& Moved) + : Data(MoveTemp(Moved.Data)) + { + } + ~TDynamicVectorN() + { + } + + const TDynamicVectorN& operator=(const TDynamicVectorN& Copy) + { + Data = Copy.Data; + return *this; + } + const TDynamicVectorN& operator=(TDynamicVectorN&& Moved) + { + Data = MoveTemp(Moved.Data); + return *this; + } + + inline void Clear() + { + Data.Clear(); + } + inline void Fill(const Type& Value) + { + Data.Fill(Value); + } + inline void Resize(size_t Count) + { + Data.Resize(Count * N); + } + inline void Resize(size_t Count, const Type& InitValue) + { + Data.Resize(Count * N, InitValue); + } + + inline bool IsEmpty() const + { + return Data.IsEmpty(); + } + inline size_t GetLength() const + { + return Data.GetLength() / N; + } + inline int GetBlockSize() const + { + return Data.GetBlockSize(); + } + inline size_t GetByteCount() const + { + return Data.GetByteCount(); + } + + // simple struct to help pass N-dimensional data without presuming a vector type (e.g. just via initializer list) + struct ElementVectorN + { + Type Data[N]; + }; + + inline void Add(const ElementVectorN& AddData) + { + // todo specialize for N=2,3,4 + for (int i = 0; i < N; i++) + { + Data.Add(AddData.Data[i]); + } + } + + inline void PopBack() + { + for (int i = 0; i < N; i++) + { + PopBack(); + } + } // TODO specialize + + inline void InsertAt(const ElementVectorN& AddData, unsigned int Index) + { + for (int i = 1; i <= N; i++) + { + Data.InsertAt(AddData.Data[N - i], N * (Index + 1) - i); + } + } + + inline Type& operator()(unsigned int TopIndex, unsigned int SubIndex) + { + return Data[TopIndex * N + SubIndex]; + } + inline const Type& operator()(unsigned int TopIndex, unsigned int SubIndex) const + { + return Data[TopIndex * N + SubIndex]; + } + inline void SetVector2(unsigned int TopIndex, const FVector2& V) + { + check(N >= 2); + unsigned int i = TopIndex * N; + Data[i] = V.X; + Data[i + 1] = V.Y; + } + inline void SetVector3(unsigned int TopIndex, const FVector3& V) + { + check(N >= 3); + unsigned int i = TopIndex * N; + Data[i] = V.X; + Data[i + 1] = V.Y; + Data[i + 2] = V.Z; + } + inline FVector2 AsVector2(unsigned int TopIndex) const + { + check(N >= 2); + return FVector2( + Data[TopIndex * N + 0], + Data[TopIndex * N + 1]); + } + inline FVector3 AsVector3(unsigned int TopIndex) const + { + check(N >= 3); + return FVector3( + Data[TopIndex * N + 0], + Data[TopIndex * N + 1], + Data[TopIndex * N + 2]); + } + inline FIndex2i AsIndex2(unsigned int TopIndex) const + { + check(N >= 2); + return FIndex2i( + Data[TopIndex * N + 0], + Data[TopIndex * N + 1]); + } + inline FIndex3i AsIndex3(unsigned int TopIndex) const + { + check(N >= 3); + return FIndex3i( + Data[TopIndex * N + 0], + Data[TopIndex * N + 1], + Data[TopIndex * N + 2]); + } + inline FIndex4i AsIndex4(unsigned int TopIndex) const + { + check(N >= 4); + return FIndex4i( + Data[TopIndex * N + 0], + Data[TopIndex * N + 1], + Data[TopIndex * N + 2], + Data[TopIndex * N + 3]); + } + +protected: + TDynamicVector Data; + + friend class FIterator; +}; + +template class TDynamicVectorN; + +typedef TDynamicVectorN TDynamicVector3f; +typedef TDynamicVectorN TDynamicVector2f; +typedef TDynamicVectorN TDynamicVector3d; +typedef TDynamicVectorN TDynamicVector2d; +typedef TDynamicVectorN TDynamicVector3i; +typedef TDynamicVectorN TDynamicVector2i; + + + + + +template +const TDynamicVector& TDynamicVector::operator=(const TDynamicVector& Copy) +{ + Blocks = Copy.Blocks; + CurBlock = Copy.CurBlock; + CurBlockUsed = Copy.CurBlockUsed; + return *this; +} + +template +const TDynamicVector& TDynamicVector::operator=(TDynamicVector&& Moved) +{ + Blocks = MoveTemp(Moved.Blocks); + CurBlock = Moved.CurBlock; + CurBlockUsed = Moved.CurBlockUsed; + return *this; +} + +template +void TDynamicVector::Clear() +{ + Blocks.Empty(); + CurBlock = 0; + CurBlockUsed = 0; + Blocks.Add(BlockType()); +} + +template +void TDynamicVector::Fill(const Type& Value) +{ + size_t Count = Blocks.Num(); + for (unsigned int i = 0; i < Count; ++i) + { + for (uint32 ElementIndex = 0; ElementIndex < BlockSize; ++ElementIndex) + { + Blocks[i][ElementIndex] = Value; + } + } +} + +template +void TDynamicVector::Resize(size_t Count) +{ + if (GetLength() == Count) + { + return; + } + + // figure out how many segments we need + int nNumSegs = 1 + (int)Count / BlockSize; + + // figure out how many are currently allocated... + size_t nCurCount = Blocks.Num(); + + // resize to right number of segments + if (nNumSegs >= Blocks.Num()) + { + // allocate new segments + for (int i = (int)nCurCount; i < nNumSegs; ++i) + { + Blocks.Emplace(); + } + } + else + { + //Blocks.RemoveRange(nNumSegs, Blocks.Count - nNumSegs); + Blocks.SetNum(nNumSegs); + } + + // mark last segment + CurBlockUsed = (unsigned int)(Count - (nNumSegs - 1) * BlockSize); + CurBlock = nNumSegs - 1; +} + +template +void TDynamicVector::Resize(size_t Count, const Type& InitValue) +{ + size_t nCurSize = GetLength(); + Resize(Count); + for (size_t Index = nCurSize; Index < Count; ++Index) + { + this->operator[](Index) = InitValue; + } +} + +template +void TDynamicVector::Add(const Type& Value) +{ + if (CurBlockUsed == BlockSize) + { + if (CurBlock == Blocks.Num() - 1) + { + Blocks.Emplace(); + } + CurBlock++; + CurBlockUsed = 0; + } + Blocks[CurBlock][CurBlockUsed] = Value; + CurBlockUsed++; +} + + +template +void TDynamicVector::Add(const TDynamicVector& AddData) +{ + // @todo it could be more efficient to use memcopies here... + size_t nSize = AddData.Num(); + for (unsigned int k = 0; k < nSize; ++k) + { + Add(AddData[k]); + } +} + +template +void TDynamicVector::PopBack() +{ + if (CurBlockUsed > 0) + { + CurBlockUsed--; + } + if (CurBlockUsed == 0 && CurBlock > 0) + { + CurBlock--; + CurBlockUsed = BlockSize; + // remove block ?? + } +} + +template +void TDynamicVector::InsertAt(const Type& AddData, unsigned int Index) +{ + size_t s = GetLength(); + if (Index == s) + { + Add(AddData); + } + else if (Index > s) + { + Resize(Index); + Add(AddData); + } + else + { + (*this)[Index] = AddData; + } +} + +template +template +void TDynamicVector::Apply(const Func& applyF) +{ + for (int bi = 0; bi < CurBlock; ++bi) + { + auto block = Blocks[bi]; + for (int k = 0; k < BlockSize; ++k) + { + applyF(block[k], k); + } + } + auto lastblock = Blocks[CurBlock]; + for (int k = 0; k < CurBlockUsed; ++k) + { + applyF(lastblock[k], k); + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/GridIndexing2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/GridIndexing2.h new file mode 100644 index 000000000000..b63bafde3424 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/GridIndexing2.h @@ -0,0 +1,86 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp GridIndexing2 + +#pragma once + +#include "VectorTypes.h" + + +/** + * Convert between integer grid coordinates and scaled real-valued coordinates (ie assumes integer grid origin == real origin) + */ +template +struct TScaleGridIndexer2 +{ + /** Real-valued size of an integer grid cell */ + RealType CellSize; + + TScaleGridIndexer2(RealType CellSize) : CellSize(CellSize) + { + ensure(CellSize >= TMathUtil::ZeroTolerance); + } + + /** Convert real-valued point to integer grid coordinates */ + inline FVector2i ToGrid(const FVector2& P) const + { + return FVector2i(int(P.X / CellSize), int(P.Y / CellSize)); + } + + /** Convert integer grid coordinates to real-valued point */ + inline FVector2 FromGrid(const FVector2i& GridPoint) const + { + return FVector2(GridPoint.X*CellSize, GridPoint.Y*CellSize); + } +}; +typedef TScaleGridIndexer2 FScaleGridIndexer2f; +typedef TScaleGridIndexer2 FScaleGridIndexer2d; + + +/** + * Convert between integer grid coordinates and scaled+translated real-valued coordinates + */ +template +struct TShiftGridIndexer2 +{ + /** Real-valued size of an integer grid cell */ + RealType CellSize; + /** Real-valued origin of grid, position of integer grid origin */ + FVector2 Origin; + + TShiftGridIndexer2(const FVector2& origin, RealType cellSize) + { + Origin = origin; + CellSize = cellSize; + ensure(CellSize >= TMathUtil::ZeroTolerance); + } + + /** Convert real-valued point to integer grid coordinates */ + inline FVector2i ToGrid(const FVector2& point) const + { + return FVector2i( + (int)((point.X - Origin.X) / CellSize), + (int)((point.Y - Origin.Y) / CellSize)); + } + + /** Convert integer grid coordinates to real-valued point */ + inline FVector2 FromGrid(const FVector2i& gridpoint) const + { + return FVector2( + ((RealType)gridpoint.X * CellSize) + Origin.X, + ((RealType)gridpoint.Y * CellSize) + Origin.Y); + } + + /** Convert real-valued grid coordinates to real-valued point */ + inline FVector2 FromGrid(const FVector2& gridpointf) const + { + return FVector2( + ((RealType)gridpointf.X * CellSize) + Origin.X, + ((RealType)gridpointf.Y * CellSize) + Origin.Y); + } +}; +typedef TShiftGridIndexer2 FShiftGridIndexer2f; +typedef TShiftGridIndexer2 FShiftGridIndexer2d; + + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/GridIndexing3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/GridIndexing3.h new file mode 100644 index 000000000000..cd139c06984c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/GridIndexing3.h @@ -0,0 +1,96 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp GridIndexing3 + +#pragma once + +#include "VectorTypes.h" + + +/** + * Convert between integer grid coordinates and scaled real-valued coordinates (ie assumes integer grid origin == real origin) + */ +template +struct TScaleGridIndexer3 +{ + /** Real-valued size of an integer grid cell */ + RealType CellSize; + + TScaleGridIndexer3() : CellSize((RealType)1) + { + } + + TScaleGridIndexer3(RealType CellSize) : CellSize(CellSize) + { + } + + /** Convert real-valued point to integer grid coordinates */ + inline FVector3i ToGrid(const FVector3& P) const + { + return FVector3i((int)(P.X / CellSize), (int)(P.Y / CellSize), (int)(P.Z / CellSize)); + } + + /** Convert integer grid coordinates to real-valued point */ + inline FVector3 FromGrid(const FVector3i& GridPoint) const + { + return FVector3(GridPoint.X*CellSize, GridPoint.Y*CellSize, GridPoint.Z*CellSize); + } +}; +typedef TScaleGridIndexer3 FScaleGridIndexer3f; +typedef TScaleGridIndexer3 FScaleGridIndexer3d; + + + +/** + * Convert between integer grid coordinates and scaled+translated real-valued coordinates + */ +template +struct TShiftGridIndexer3 +{ + /** Real-valued size of an integer grid cell */ + RealType CellSize; + /** Real-valued origin of grid, position of integer grid origin */ + FVector3 Origin; + + TShiftGridIndexer3() + : CellSize((RealType)1), Origin(FVector3::Zero()) + { + } + + TShiftGridIndexer3(const FVector3& origin, RealType cellSize) + : CellSize(cellSize), Origin(origin) + { + } + + /** Convert real-valued point to integer grid coordinates */ + inline FVector3i ToGrid(const FVector3& point) const + { + return FVector3i( + (int)((point.X - Origin.X) / CellSize), + (int)((point.Y - Origin.Y) / CellSize), + (int)((point.Z - Origin.Z) / CellSize)); + } + + /** Convert integer grid coordinates to real-valued point */ + inline FVector3 FromGrid(const FVector3i& gridpoint) const + { + return FVector3( + ((RealType)gridpoint.X * CellSize) + Origin.X, + ((RealType)gridpoint.Y * CellSize) + Origin.Y, + ((RealType)gridpoint.Z * CellSize) + Origin.Z); + } + + /** Convert real-valued grid coordinates to real-valued point */ + inline FVector3 FromGrid(const FVector3& gridpointf) const + { + return FVector3d( + ((RealType)gridpointf.X * CellSize) + Origin.X, + ((RealType)gridpointf.Y * CellSize) + Origin.Y, + ((RealType)gridpointf.Z * CellSize) + Origin.Z); + } +}; +typedef TShiftGridIndexer3 FShiftGridIndexer3f; +typedef TShiftGridIndexer3 FShiftGridIndexer3d; + + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IndexPriorityQueue.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IndexPriorityQueue.h new file mode 100644 index 000000000000..e6c243b5d29c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IndexPriorityQueue.h @@ -0,0 +1,389 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MathUtil.h" +#include "Util/DynamicVector.h" + + +/** + * This is a min-heap priority queue class that does not use an object for each queue node. + * Integer IDs must be provided by the user to identify unique nodes. + * Internally an array is used to keep track of the mapping from ids to internal indices, so the max ID must also be provided. + * + * @todo based on C# code where TDynamicVector could not return a reference to internal element. In C++ we can and code could be updated to be more efficient in many places. + * @todo id_to_index could be sparse in many situations... + */ +class FIndexPriorityQueue +{ +public: + /** set this to true during development to catch issues */ + bool EnableDebugChecks = false; + + struct FQueueNode + { + int id; // external id + + float priority; // the priority of this id + int index; // index in tree data structure (tree is stored as flat array) + }; + + /** tree of allocated nodes, stored linearly. active up to num_nodes (allocated may be larger) */ + TDynamicVector nodes; + /** count of active nodes */ + int num_nodes; + /** mapping from external ids to internal node indices */ + TArray id_to_index; + + + /** + * This constructor is provided for convenience, you must call Initialize() + */ + FIndexPriorityQueue() + { + Initialize(0); + } + + /** + * Calls Initialize() + * @param maxID maximum external ID that will be passed to any public functions + */ + FIndexPriorityQueue(int maxID) + { + Initialize(maxID); + } + + + /** + * Initialize internal data structures. Internally a fixed-size array is used to track mapping + * from IDs to internal node indices, so maxID must be provided up-front. + * If this seems problematic or inefficient, this is not the Priority Queue for you. + * @param MaxNodeID maximum external ID that will be passed to any public functions + */ + void Initialize(int MaxNodeID) + { + nodes.Clear(); + id_to_index.Init(-1, MaxNodeID); + num_nodes = 0; + } + + + /** + * Reset the queue to empty state. + * if bFreeMemory is false, we don't discard internal data structures, so there will be less allocation next time + */ + void Clear(bool bFreeMemory = true) + { + check(bFreeMemory); // not sure exactly how to implement bFreeMemory=false w/ classes below... + nodes.Clear(); + id_to_index.Init(-1, id_to_index.Num()); + num_nodes = 0; + } + + /** @return current size of queue */ + int GetCount() const + { + return num_nodes; + } + + /** @return id of node at head of queue */ + int GetFirstNodeID() const + { + return nodes[1].id; + } + + /** @return id of node at head of queue */ + float GetFirstNodePriority() const + { + return nodes[1].priority; + } + + /** @return true if id is already in queue */ + bool Contains(int NodeID) const + { + int NodeIndex = id_to_index[NodeID]; + if (NodeIndex <= 0 || NodeIndex > num_nodes) + return false; + return nodes[NodeIndex].index > 0; + } + + + /** + * Add id to list w/ given priority. Do not call with same id twice! + */ + void Insert(int NodeID, float priority) + { + if (EnableDebugChecks) + { + check(Contains(NodeID) == false); + } + + FQueueNode node; + node.id = NodeID; + node.priority = priority; + num_nodes++; + node.index = num_nodes; + id_to_index[NodeID] = node.index; + nodes.InsertAt(node, num_nodes); + move_up(nodes[num_nodes].index); + } + + /** + * Remove node at head of queue, update queue, and return id for that node + * @return ID of node at head of queue + */ + int Dequeue() + { + if (EnableDebugChecks) + { + check(GetCount() > 0); + } + + int NodeID = nodes[1].id; + remove_at_index(1); + return NodeID; + } + + /** + * Remove node associated with given ID from queue. Behavior is undefined if you call w/ id that is not in queue + */ + void Remove(int NodeID) + { + if (EnableDebugChecks) + { + check(Contains(NodeID) == true); + } + + int NodeIndex = id_to_index[NodeID]; + remove_at_index(NodeIndex); + } + + + /** + * Update priority at node id, and then move it to correct position in queue. Behavior is undefined if you call w/ id that is not in queue + */ + void Update(int NodeID, float Priority) + { + if (EnableDebugChecks) + { + check(Contains(NodeID) == true); + } + + int NodeIndex = id_to_index[NodeID]; + + FQueueNode n = nodes[NodeIndex]; + n.priority = Priority; + nodes[NodeIndex] = n; + + on_node_updated(NodeIndex); + } + + + /** + * Query the priority at node id, assuming it exists in queue. Behavior is undefined if you call w/ id that is not in queue + * @return priority for node + */ + float GetPriority(int id) + { + if (EnableDebugChecks) + { + check(Contains(id) == true); + } + + int iNode = id_to_index[id]; + return nodes[iNode].priority; + } + + + /* + * Internals + */ +private: + + /** remove node at index and update tree */ + void remove_at_index(int NodeIndex) + { + // node-is-last-node case + if (NodeIndex == num_nodes) + { + nodes[NodeIndex] = FQueueNode(); + num_nodes--; + return; + } + + // [RMS] is there a better way to do this? seems random to move the last node to + // top of tree? But I guess otherwise we might have to shift entire branches?? + + //Swap the node with the last node + swap_nodes_at_indices(NodeIndex, num_nodes); + // after swap, NodeIndex is the one we want to keep, and numNodes is the one we discard + nodes[num_nodes] = FQueueNode(); + num_nodes--; + + //Now shift iNode (ie the former last node) up or down as appropriate + on_node_updated(NodeIndex); + } + + + /** swap two nodes in the true */ + void swap_nodes_at_indices(int i1, int i2) + { + FQueueNode n1 = nodes[i1]; + n1.index = i2; + FQueueNode n2 = nodes[i2]; + n2.index = i1; + nodes[i1] = n2; + nodes[i2] = n1; + + id_to_index[n2.id] = i1; + id_to_index[n1.id] = i2; + } + + /** move node at iFrom to iTo */ + void move(int iFrom, int iTo) + { + FQueueNode n = nodes[iFrom]; + n.index = iTo; + nodes[iTo] = n; + id_to_index[n.id] = iTo; + } + + /** set node at iTo */ + void set(int iTo, FQueueNode& n) + { + n.index = iTo; + nodes[iTo] = n; + id_to_index[n.id] = iTo; + } + + + /** move iNode up tree to correct position by iteratively swapping w/ parent */ + void move_up(int iNode) + { + // save start node, we will move this one to correct position in tree + int iStart = iNode; + FQueueNode iStartNode = nodes[iStart]; + + // while our priority is lower than parent, we swap upwards, ie move parent down + int iParent = iNode / 2; + while (iParent >= 1) + { + if (nodes[iParent].priority < iStartNode.priority) + { + break; + } + move(iParent, iNode); + iNode = iParent; + iParent = nodes[iNode].index / 2; + } + + // write input node into final position, if we moved it + if (iNode != iStart) + { + set(iNode, iStartNode); + } + } + + + /** move iNode down tree branches to correct position, by iteratively swapping w/ children */ + void move_down(int iNode) + { + // save start node, we will move this one to correct position in tree + int iStart = iNode; + FQueueNode iStartNode = nodes[iStart]; + + // keep moving down until lower nodes have higher priority + while (true) + { + int iMoveTo = iNode; + int iLeftChild = 2 * iNode; + + // past end of tree, must be in the right spot + if (iLeftChild > num_nodes) + { + break; + } + + // check if priority is larger than either child - if so we want to swap + float min_priority = iStartNode.priority; + float left_child_priority = nodes[iLeftChild].priority; + if (left_child_priority < min_priority) + { + iMoveTo = iLeftChild; + min_priority = left_child_priority; + } + int iRightChild = iLeftChild + 1; + if (iRightChild <= num_nodes) + { + if (nodes[iRightChild].priority < min_priority) + { + iMoveTo = iRightChild; + } + } + + // if we found node with higher priority, swap with it (ie move it up) and take its place + // (but we only write start node to final position, not intermediary slots) + if (iMoveTo != iNode) + { + move(iMoveTo, iNode); + iNode = iMoveTo; + } + else + { + break; + } + } + + // if we moved node, write it to its new position + if (iNode != iStart) + { + set(iNode, iStartNode); + } + } + + + /** call this after node is modified, to move it to correct position in queue */ + void on_node_updated(int iNode) + { + int iParent = iNode / 2; + if (iParent > 0 && has_higher_priority(iNode, iParent)) + move_up(iNode); + else + move_down(iNode); + } + + + /** @return true if priority at iHigher is less than at iLower */ + bool has_higher_priority(int iHigher, int iLower) const + { + return (nodes[iHigher].priority < nodes[iLower].priority); + } + + + + +public: + /** + * Check if node ordering is correct (for debugging/testing) + */ + bool IsValidQueue() const + { + for (int i = 1; i < num_nodes; i++) { + int childLeftIndex = 2 * i; + if (childLeftIndex < num_nodes && has_higher_priority(childLeftIndex, i)) + return false; + + int childRightIndex = childLeftIndex + 1; + if (childRightIndex < num_nodes && has_higher_priority(childRightIndex, i)) + return false; + } + return true; + } + + //void DebugPrint() { + // for (int i = 1; i <= num_nodes; ++i) + // WriteLine("{0} : p {1} index {2} id {3}", i, nodes[i].priority, nodes[i].index, nodes[i].id); + //} + + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IndexUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IndexUtil.h new file mode 100644 index 000000000000..3685166aa8a0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IndexUtil.h @@ -0,0 +1,284 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp index_util.h + +#pragma once + +#include "Util/DynamicVector.h" +#include "IndexTypes.h" +#include "VectorTypes.h" + + +namespace IndexUtil +{ + + + /** + * @return true if [a0,a1] and [b0,b1] are the same pair, ignoring order + */ + template + inline bool SamePairUnordered(T a0, T a1, T b0, T b1) + { + return (a0 == b0) ? + (a1 == b1) : + (a0 == b1 && a1 == b0); + } + + + /** + * @return the vertex that is the same in both EdgeVerts1 and EdgeVerts2, or InvalidID if not found + */ + inline int FindSharedEdgeVertex(const FIndex2i & EdgeVerts1, const FIndex2i & EdgeVerts2) + { + if (EdgeVerts1.A == EdgeVerts2.A) return EdgeVerts1.A; + else if (EdgeVerts1.A == EdgeVerts2.B) return EdgeVerts1.A; + else if (EdgeVerts1.B == EdgeVerts2.A) return EdgeVerts1.B; + else if (EdgeVerts1.B == EdgeVerts2.B) return EdgeVerts1.B; + else return IndexConstants::InvalidID; + } + + /** + * @return the vertex in the pair ev that is not v, or InvalidID if not found + */ + inline int FindEdgeOtherVertex(const FIndex2i & EdgeVerts, int VertexID) + { + if (EdgeVerts.A == VertexID) return EdgeVerts.B; + else if (EdgeVerts.B == VertexID) return EdgeVerts.A; + else return IndexConstants::InvalidID; + } + + + /** + * @return index 0/1/2 of VertexID in TriangleVerts, or InvalidID if not found + */ + template + inline int FindTriIndex(T VertexID, const Vec & TriangleVerts) + { + if (TriangleVerts[0] == VertexID) return 0; + if (TriangleVerts[1] == VertexID) return 1; + if (TriangleVerts[2] == VertexID) return 2; + return IndexConstants::InvalidID; + } + + /** + * Find unordered edge [VertexID1,VertexID2] in TriangleVerts + * @return index in range 0-2, or InvalidID if not found + */ + template + inline int FindEdgeIndexInTri(T VertexID1, T VertexID2, const Vec & TriangleVerts) + { + if (SamePairUnordered(VertexID1, VertexID2, TriangleVerts[0], TriangleVerts[1])) return 0; + if (SamePairUnordered(VertexID1, VertexID2, TriangleVerts[1], TriangleVerts[2])) return 1; + if (SamePairUnordered(VertexID1, VertexID2, TriangleVerts[2], TriangleVerts[0])) return 2; + return IndexConstants::InvalidID; + } + + + /** + * Find ordered edge [VertexID1,VertexID2] in TriangleVerts + * @return index in range 0-2, or InvalidID if not found + */ + template + inline int FindTriOrderedEdge(T VertexID1, T VertexID2, const Vec & TriangleVerts) + { + if (TriangleVerts[0] == VertexID1 && TriangleVerts[1] == VertexID2) return 0; + if (TriangleVerts[1] == VertexID1 && TriangleVerts[2] == VertexID2) return 1; + if (TriangleVerts[2] == VertexID1 && TriangleVerts[0] == VertexID2) return 2; + return IndexConstants::InvalidID; + } + + + /** + * Find ordered edge [VertexID1,VertexID2] in TriangleVerts and then return the remaining third vertex + * @return vertex id of other vertex, or InvalidID if not found + */ + template + inline int FindTriOtherVtx(T VertexID1, T VertexID2, const Vec & TriangleVerts) + { + for (int j = 0; j < 3; ++j) + { + if (SamePairUnordered(VertexID1, VertexID2, TriangleVerts[j], TriangleVerts[(j + 1) % 3])) + { + return TriangleVerts[(j + 2) % 3]; + } + } + return IndexConstants::InvalidID; + } + + /** + * Find ordered edge [VertexID1,VertexID2] in a triangle that is in an array of triangles, and return remaining third vertex + * @param VertexID1 first vertex of edge + * @param VertexID2 second vertex of edge + * @param TriIndexArray array of triangle tuples + * @param TriangleIndex which triangle in array to search + * @return vertex id of other vertex, or InvalidID if not found + */ + template + inline int FindTriOtherVtx(T VertexID1, T VertexID2, const TDynamicVector & TriIndexArray, T TriangleIndex) + { + int i = 3 * TriangleIndex; + for (int j = 0; j < 3; ++j) + { + if (SamePairUnordered(VertexID1, VertexID2, TriIndexArray[i + j], TriIndexArray[i + ((j+1)%3)])) + { + return TriIndexArray[i + ((j + 2) % 3)]; + } + } + return IndexConstants::InvalidID; + } + + + + /** + * Find ordered edge [VertexID1,VertexID2] in TriangleVerts and then return the index of the remaining third vertex + * @return index of third vertex, or InvalidID if not found + */ + template + inline int FindTriOtherIndex(T VertexID1, T VertexID2, const Vec & TriangleVerts) + { + for (int j = 0; j < 3; ++j) + { + if (SamePairUnordered(VertexID1, VertexID2, TriangleVerts[j], TriangleVerts[(j + 1) % 3])) + { + return (j + 2) % 3; + } + } + return IndexConstants::InvalidID; + } + + + /** + * If i0 and i1 are unordered indices into a triangle, each in range 0-2, return the third index + */ + inline int GetOtherTriIndex(int i0, int i1) + { + // @todo can we do this with a formula? I don't see it right now. + static const int values[4] = { 0, 2, 1, 0 }; + return values[i0+i1]; + } + + + /** + * Assuming [Vertex1,Vertex2] is an unordered edge in TriangleVerts, return Vertex1 and Vertex2 in the correct order (ie the same as TriangleVerts) + * @warning result is garbage if either vertex is not an edge in TriangleVerts + * @return true if order was swapped + */ + template + inline bool OrientTriEdge(T & Vertex1, T & Vertex2, const Vec & TriangleVerts) + { + if (Vertex1 == TriangleVerts[0]) + { + if (TriangleVerts[2] == Vertex2) + { + T Temp = Vertex1; Vertex1 = Vertex2; Vertex2 = Temp; + return true; + } + } + else if (Vertex1 == TriangleVerts[1]) + { + if (TriangleVerts[0] == Vertex2) + { + T Temp = Vertex1; Vertex1 = Vertex2; Vertex2 = Temp; + return true; + } + } + else if (Vertex1 == TriangleVerts[2]) + { + if (TriangleVerts[1] == Vertex2) + { + T Temp = Vertex1; Vertex1 = Vertex2; Vertex2 = Temp; + return true; + } + } + return false; + } + + + /** + * Assuming [Vertex1,Vertex2] is an unordered edge in TriangleVerts, return Vertex1 and Vertex2 in the correct order (ie the same as TriangleVerts), + * and returns the vertex ID of the other vertex + * @return ID of third vertex, or InvalidID if the edge was not found + */ + template + inline int OrientTriEdgeAndFindOtherVtx(T & Vertex1, T & Vertex2, const Vec & TriangleVerts) + { + for (int j = 0; j < 3; ++j) + { + if (SamePairUnordered(Vertex1, Vertex2, TriangleVerts[j], TriangleVerts[(j + 1) % 3])) + { + Vertex1 = TriangleVerts[j]; + Vertex2 = TriangleVerts[(j + 1) % 3]; + return TriangleVerts[(j + 2) % 3]; + } + } + return IndexConstants::InvalidID; + } + + + /** + * Replace Val with MapFunc[Val] using index operator + */ + template + void ApplyMap(FIndex3i & Val, Func MapFunc) + { + Val[0] = MapFunc[Val[0]]; + Val[1] = MapFunc[Val[1]]; + Val[2] = MapFunc[Val[2]]; + } + + /** + * Replace Val with MapFunc[Val] using index operator + */ + template + void ApplyMap(FVector3 & Val, Func MapFunc) + { + Val[0] = MapFunc[Val[0]]; + Val[1] = MapFunc[Val[1]]; + Val[2] = MapFunc[Val[2]]; + } + + + /** + * @return MapFunc[Val] using index operator + */ + template + FIndex3i ApplyMap(const FIndex3i & Val, Func MapFunc) + { + return FIndex3i(MapFunc[Val[0]], MapFunc[Val[1]], MapFunc[Val[2]]); + } + + + /** + * @return MapFunc[Val] using index operator + */ + template + FVector3 ApplyMap(const FVector3 & Val, Func MapFunc) + { + return FVector3(MapFunc[Val[0]], MapFunc[Val[1]], MapFunc[Val[2]]); + } + + + + /** + * all permutations of (+-1, +-1, +-1), can be used to iterate over connected face/edge/corner neighbours of a grid cell + */ + extern GEOMETRICOBJECTS_API const FVector3i GridOffsets26[26]; + + /** + * Corner vertices of box faces - see FOrientedBox3.GetCorner for points associated w/ indexing + */ + extern GEOMETRICOBJECTS_API const int BoxFaces[6][4]; + + /** + * Corner unit-UV ordering of box faces - {0,0}, {1,0}, {1,1}, {0,1} + */ + extern GEOMETRICOBJECTS_API const FVector2i BoxFacesUV[4]; + + /** + * Box Face Normal Axes associated with BoxFaces. Use Sign(BoxFaceNormals[i]) * Box.Axis( Abs(BoxFaceNormals[i])-1 ) to get vector + */ + extern GEOMETRICOBJECTS_API const int BoxFaceNormals[6]; + + + // @todo other index array constants +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IteratorUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IteratorUtil.h new file mode 100644 index 000000000000..6b3306adb9e4 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IteratorUtil.h @@ -0,0 +1,243 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp iterator_util.h + +#pragma once + + +/** + * Wrapper around an object of type IteratorT that provides STL + * iterator-like semantics, that converts from the iteration type + * (FromType) to a new type (ToType). + * + * Conversion is done via a provided mapping function + */ +template +class MappedIterator +{ + using MapFunctionT = TFunction; + +public: + inline MappedIterator() { } + + inline bool operator==(const MappedIterator& Other) const + { + return Cur == Other.Cur; + } + inline bool operator!=(const MappedIterator& Other) const + { + return Cur != Other.Cur; + } + + inline ToType operator*() const + { + return MapFunction(*Cur); + } + + inline const MappedIterator& operator++() // prefix + { + Cur++; + return *this; + } + + inline MappedIterator(const IteratorT& CurItr, const MapFunctionT& MapFunctionIn) + { + Cur = CurItr; + MapFunction = MapFunctionIn; + } + + IteratorT Cur; + MapFunctionT MapFunction; +}; + + + + + + + +/** + * Wrapper around an existing iterator that skips over + * values for which the filter_func returns false. + */ +template +class FilteredIterator +{ + using FilterFunctionT = TFunction; + +public: + inline FilteredIterator() { } + + inline bool operator==(const FilteredIterator& Other) const + { + return Cur == Other.Cur; + } + inline bool operator!=(const FilteredIterator& Other) const + { + return Cur != Other.Cur; + } + + inline ValueType operator*() const + { + return *Cur; + } + + inline const FilteredIterator& operator++() // prefix + { + GotoNextElement(); + return *this; + } + + inline void GotoNextElement() + { + do { + Cur++; + } while (Cur != End && FilterFunc(*Cur) == false); + } + + inline FilteredIterator(const IteratorT& CurItr, const IteratorT& EndItr, const FilterFunctionT& FilterFuncIn) + { + Cur = CurItr; + End = EndItr; + this->FilterFunc = FilterFuncIn; + if (FilterFunc(*Cur) == false) + { + GotoNextElement(); + } + } + + IteratorT Cur; + IteratorT End; + FilterFunctionT FilterFunc; +}; + + + + + + + + + + +/** + * Wrapper around existing iterator that returns multiple values, of potentially + * different type, for each value that input iterator returns. + * + * This is done via an "expansion" function that takes an int reference which + * indicates "where" we are in the expansion (eg like a state machine). + * How you use this value is up to you. + * + * When the input is -1, you should interpret this as the "beginning" of + * handling the input value (ie we have not returned any values yet for + * this input value) + * + * When you are "done" with an input value, set the outgoing int reference to -1 + * and the base iterator will be incremented. + * + * If you have more values to return for this input value, set it to some positive + * number of your choosing. + * + * See FDynamicMesh3::VtxTrianglesItr for an example + */ +template +class ExpandIterator +{ + using ExpandFunctionT = TFunction; + +public: + inline ExpandIterator() { } + + inline bool operator==(const ExpandIterator& Other) const + { + return Cur == Other.Cur; + } + inline bool operator!=(const ExpandIterator & Other) const + { + return Cur != Other.Cur; + } + + inline OutputType operator*() const + { + return CurValue; + } + + inline const ExpandIterator& operator++() // prefix + { + goto_next(); + return *this; + } + + inline void goto_next() + { + while (Cur != End) + { + CurValue = ExpandFunc(*Cur, CurExpandI); + if (CurExpandI == -1) + { + ++Cur; // done with this base value + } + else + { + break; // want caller to see current output value + } + } + } + + inline ExpandIterator(const InputIteratorT& CurItr, const InputIteratorT& EndItr, const ExpandFunctionT& ExpandFuncIn) + { + Cur = CurItr; + End = EndItr; + ExpandFunc = ExpandFuncIn; + CurExpandI = -1; + goto_next(); + } + + InputIteratorT Cur; + InputIteratorT End; + OutputType CurValue; + int CurExpandI; + ExpandFunctionT ExpandFunc; +}; + + + +/** + * Generic "enumerable" object that provides begin/end semantics for an ExpandIterator suitable for use with range-based for. + * You can either provide begin/end iterators, or another "enumerable" object that has begin()/end() functions. + */ +template +class ExpandEnumerable +{ + using ExpandFunctionT = TFunction; + using ExpandIteratorT = ExpandIterator; + +public: + ExpandFunctionT ExpandFunc; + InputIteratorT BeginItr, EndItr; + + ExpandEnumerable(const InputIteratorT& BeginIn, const InputIteratorT& EndIn, ExpandFunctionT ExpandFuncIn) + { + this->BeginItr = BeginIn; + this->EndItr = EndIn; + this->ExpandFunc = ExpandFuncIn; + } + + template + ExpandEnumerable(const IteratorSource& Source, ExpandFunctionT ExpandFuncIn) + { + this->BeginItr = Source.begin(); + this->EndItr = Source.end(); + this->ExpandFunc = ExpandFuncIn; + } + + ExpandIteratorT begin() + { + return ExpandIteratorT(BeginItr, EndItr, ExpandFunc); + } + + ExpandIteratorT end() + { + return ExpandIteratorT(EndItr, EndItr, ExpandFunc); + } +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/MeshCaches.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/MeshCaches.h new file mode 100644 index 000000000000..de71ee2d78f4 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/MeshCaches.h @@ -0,0 +1,40 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DynamicVector.h" + +#include "Async/ParallelFor.h" + +/* + * Basic cache of per-triangle information for a mesh + */ +struct FMeshTriInfoCache +{ + TDynamicVector Centroids; + TDynamicVector Normals; + TDynamicVector Areas; + + void GetTriInfo(int TriangleID, FVector3d& NormalOut, double& AreaOut, FVector3d& CentroidOut) const + { + NormalOut = Normals[TriangleID]; + AreaOut = Areas[TriangleID]; + CentroidOut = Centroids[TriangleID]; + } + + template + static FMeshTriInfoCache BuildTriInfoCache(const TriangleMeshType& Mesh) + { + FMeshTriInfoCache Cache; + int NT = Mesh.TriangleCount(); + Cache.Centroids.Resize(NT); + Cache.Normals.Resize(NT); + Cache.Areas.Resize(NT); + + ParallelFor(NT, [&](int TriIdx) + { + TMeshQueries::GetTriNormalAreaCentroid(Mesh, TriIdx, Cache.Normals[TriIdx], Cache.Areas[TriIdx], Cache.Centroids[TriIdx]); + }); + + return Cache; + } +}; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/ProgressCancel.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/ProgressCancel.h new file mode 100644 index 000000000000..6f2904d48a80 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/ProgressCancel.h @@ -0,0 +1,76 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp ProgressCancel + +#pragma once + + +/** + * ICancelSource is an object that provides a cancel function + */ +class ICancelSource +{ +public: + virtual ~ICancelSource() {} + + /** + * @return true if object wishes to cancel expensive operation + */ + virtual bool Cancelled() = 0; +}; + + +/** + * FCancelFunction uses a TFunction to implement the ICancelSource interface + */ +class FCancelFunction : public ICancelSource +{ +public: + virtual ~FCancelFunction() {} + + /** function that returns true if object wishes to cancel operation */ + TFunction CancelF; + + FCancelFunction(const TFunction & cancelF) : CancelF(cancelF) + { + } + + /** + * @return true if object wishes to cancel expensive operation + */ + bool Cancelled() { return CancelF(); } +}; + + + +/** + * FProgressCancel is an obejct that is intended to be passed to long-running + * computes to do two things: + * 1) provide progress info back to caller (not ported yet) + * 2) allow caller to cancel the computation + */ +class FProgressCancel +{ +public: + TSharedPtr Source; + + bool WasCancelled = false; // will be set to true if CancelF() ever returns true + + FProgressCancel(TSharedPtr source) + { + Source = source; + } + + /** + * @return true if client would like to cancel operation + */ + bool Cancelled() + { + if (WasCancelled) + { + return true; + } + WasCancelled = Source->Cancelled(); + return WasCancelled; + } +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/RefCountVector.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/RefCountVector.h new file mode 100644 index 000000000000..c4ea8081fd15 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/RefCountVector.h @@ -0,0 +1,487 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp FRefCountVector + +#pragma once + +#include "CoreMinimal.h" +#include "Util/DynamicVector.h" +#include "Util/IteratorUtil.h" + + +/** + * FRefCountVector is used to keep track of which indices in a linear Index list are in use/referenced. + * A free list is tracked so that unreferenced indices can be re-used. + * + * The enumerator iterates over valid indices (ie where refcount > 0) + * @warning refcounts are 16-bit ints (shorts) so the maximum count is 65536. behavior is undefined if this overflows. + * @warning No overflow checking is done in release builds. + */ +class FRefCountVector +{ +public: + static constexpr short INVALID_REF_COUNT = -1; + + TDynamicVector RefCounts; + TDynamicVector FreeIndices; + int UsedCount; + + FRefCountVector() + { + RefCounts = TDynamicVector(); + FreeIndices = TDynamicVector(); + UsedCount = 0; + } + + FRefCountVector(const FRefCountVector& Copy) + { + RefCounts = TDynamicVector(Copy.RefCounts); + FreeIndices = TDynamicVector(Copy.FreeIndices); + UsedCount = Copy.UsedCount; + } + + + bool IsEmpty() const + { + return UsedCount == 0; + } + + size_t GetCount() const + { + return UsedCount; + } + + size_t GetMaxIndex() const + { + return RefCounts.GetLength(); + } + + bool IsDense() const + { + return FreeIndices.GetLength() == 0; + } + + + bool IsValid(int Index) const + { + return (Index >= 0 && Index < (int)RefCounts.GetLength() && RefCounts[Index] > 0); + } + + bool IsValidUnsafe(int Index) const + { + return RefCounts[Index] > 0; + } + + + int GetRefCount(int Index) const + { + int n = RefCounts[Index]; + return (n == INVALID_REF_COUNT) ? 0 : n; + } + + int GetRawRefCount(int Index) const + { + return RefCounts[Index]; + } + + + int Allocate() + { + UsedCount++; + if (FreeIndices.IsEmpty()) + { + // [RMS] do we need this branch anymore? + RefCounts.Add(1); + return (int)RefCounts.GetLength() - 1; + } + else + { + int iFree = INVALID_REF_COUNT; + while (iFree == INVALID_REF_COUNT && FreeIndices.IsEmpty() == false) + { + iFree = FreeIndices.Back(); + FreeIndices.PopBack(); + } + if (iFree != INVALID_REF_COUNT) + { + RefCounts[iFree] = 1; + return iFree; + } + else + { + RefCounts.Add(1); + return (int)RefCounts.GetLength() - 1; + } + } + } + + + + int Increment(int Index, short IncrementCount = 1) + { + check(IsValid(Index)); + // debug check for overflow... + check((short)(RefCounts[Index] + IncrementCount) > 0); + RefCounts[Index] += IncrementCount; + return RefCounts[Index]; + } + + void Decrement(int Index, short DecrementCount = 1) + { + check(IsValid(Index)); + RefCounts[Index] -= DecrementCount; + check(RefCounts[Index] >= 0); + if (RefCounts[Index] == 0) + { + FreeIndices.Add(Index); + RefCounts[Index] = INVALID_REF_COUNT; + UsedCount--; + } + } + + + + /** + * allocate at specific Index, which must either be larger than current max Index, + * or on the free list. If larger, all elements up to this one will be pushed onto + * free list. otherwise we have to do a linear search through free list. + * If you are doing many of these, it is likely faster to use + * AllocateAtUnsafe(), and then RebuildFreeList() after you are done. + */ + bool AllocateAt(int Index) + { + if (Index >= (int)RefCounts.GetLength()) + { + int j = (int)RefCounts.GetLength(); + while (j < Index) + { + int InvalidCount = INVALID_REF_COUNT; // required on older clang because a constexpr can't be passed by ref + RefCounts.Add(InvalidCount); + FreeIndices.Add(j); + ++j; + } + RefCounts.Add(1); + UsedCount++; + return true; + + } + else + { + if (RefCounts[Index] > 0) + { + return false; + } + + int N = (int)FreeIndices.GetLength(); + for (int i = 0; i < N; ++i) + { + if (FreeIndices[i] == Index) + { + FreeIndices[i] = INVALID_REF_COUNT; + RefCounts[Index] = 1; + UsedCount++; + return true; + } + } + return false; + } + } + + + /** + * allocate at specific Index, which must be free or larger than current max Index. + * However, we do not update free list. So, you probably need to do RebuildFreeList() after calling this. + */ + bool AllocateAtUnsafe(int Index) + { + if (Index >= (int)RefCounts.GetLength()) + { + int j = (int)RefCounts.GetLength(); + while (j < Index) + { + int InvalidCount = INVALID_REF_COUNT; // required on older clang because a constexpr can't be passed by ref + RefCounts.Add(InvalidCount); + ++j; + } + RefCounts.Add(1); + UsedCount++; + return true; + + } + else + { + if (RefCounts[Index] > 0) + { + return false; + } + RefCounts[Index] = 1; + UsedCount++; + return true; + } + } + + + + const TDynamicVector& GetRawRefCounts() const + { + return RefCounts; + } + + /** + * @warning you should not use this! + */ + TDynamicVector& GetRawRefCountsUnsafe() + { + return RefCounts; + } + + /** + * @warning you should not use this! + */ + void SetRefCountUnsafe(int Index, short ToCount) + { + RefCounts[Index] = ToCount; + } + + // todo: + // remove + // clear + + + void RebuildFreeList() + { + FreeIndices = TDynamicVector(); + UsedCount = 0; + + int N = (int)RefCounts.GetLength(); + for (int i = 0; i < N; ++i) + { + if (RefCounts[i] > 0) + { + UsedCount++; + } + else + { + FreeIndices.Add(i); + } + } + } + + + + void Trim(int maxIndex) + { + FreeIndices = TDynamicVector(); + RefCounts.Resize(maxIndex); + UsedCount = maxIndex; + } + + + + // + // Iterators + // + + + + /** + * base iterator for indices with valid refcount (skips zero-refcount indices) + */ + class BaseIterator + { + public: + inline BaseIterator() + { + Vector = nullptr; + Index = 0; + LastIndex = 0; + } + + inline bool operator==(const BaseIterator& Other) const + { + return Index == Other.Index; + } + inline bool operator!=(const BaseIterator& Other) const + { + return Index != Other.Index; + } + + protected: + inline void goto_next() + { + if (Index != LastIndex) + { + Index++; + } + while (Index != LastIndex && Vector->IsValidUnsafe(Index) == false) + { + Index++; + } + } + + inline BaseIterator(const FRefCountVector* VectorIn, int IndexIn, int LastIn) + { + Vector = VectorIn; + Index = IndexIn; + LastIndex = LastIn; + if (Index != LastIndex && Vector->IsValidUnsafe(Index) == false) + { + goto_next(); // initialize + } + } + const FRefCountVector * Vector; + int Index; + int LastIndex; + friend class FRefCountVector; + }; + + + /* + * iterator over valid indices (ie non-zero refcount) + */ + class IndexIterator : public BaseIterator + { + public: + inline IndexIterator() : BaseIterator() {} + + inline int operator*() const + { + return this->Index; + } + + inline IndexIterator & operator++() // prefix + { + this->goto_next(); + return *this; + } + inline IndexIterator operator++(int) // postfix + { + IndexIterator copy(*this); + this->goto_next(); + return copy; + } + + protected: + inline IndexIterator(const FRefCountVector* VectorIn, int Index, int Last) : BaseIterator(VectorIn, Index, Last) + {} + friend class FRefCountVector; + }; + + + inline IndexIterator BeginIndices() const + { + return IndexIterator(this, (int)0, (int)RefCounts.GetLength()); + } + + inline IndexIterator EndIndices() const + { + return IndexIterator(this, (int)RefCounts.GetLength(), (int)RefCounts.GetLength()); + } + + + + /** + * enumerable object that provides begin()/end() semantics, so + * you can iterate over valid indices using range-based for loop + */ + class IndexEnumerable + { + public: + const FRefCountVector* Vector; + IndexEnumerable() { Vector = nullptr; } + IndexEnumerable(const FRefCountVector* VectorIn) { Vector = VectorIn; } + typename FRefCountVector::IndexIterator begin() { return Vector->BeginIndices(); } + typename FRefCountVector::IndexIterator end() { return Vector->EndIndices(); } + }; + + /** + * returns iteration object over valid indices + * usage: for (int idx : indices()) { ... } + */ + inline IndexEnumerable Indices() const + { + return IndexEnumerable(this); + } + + + /* + * enumerable object that maps indices output by Index_iteration to a second type + */ + template + class MappedEnumerable + { + public: + TFunction MapFunc; + IndexEnumerable enumerable; + + MappedEnumerable(const IndexEnumerable& enumerable, TFunction MapFunc) + { + this->enumerable = enumerable; + this->MapFunc = MapFunc; + } + + MappedIterator begin() + { + return MappedIterator(enumerable.begin(), MapFunc); + } + + MappedIterator end() + { + return MappedIterator(enumerable.end(), MapFunc); + } + }; + + + /** + * returns iteration object over mapping applied to valid indices + * eg usage: for (FVector3d v : mapped_indices(fn_that_looks_up_mesh_vtx_from_id)) { ... } + */ + template + inline MappedEnumerable MappedIndices(TFunction MapFunc) const + { + return MappedEnumerable(Indices(), MapFunc); + } + + + + + /* + * iteration object that maps indices output by Index_iteration to a second type + */ + class FilteredEnumerable + { + public: + TFunction FilterFunc; + IndexEnumerable enumerable; + + FilteredEnumerable(const IndexEnumerable& enumerable, TFunction FilterFuncIn) + { + this->enumerable = enumerable; + this->FilterFunc = FilterFuncIn; + } + + FilteredIterator begin() + { + return FilteredIterator(enumerable.begin(), enumerable.end(), FilterFunc); + } + + FilteredIterator end() + { + return FilteredIterator(enumerable.end(), enumerable.end(), FilterFunc); + } + }; + + inline FilteredEnumerable FilteredIndices(TFunction FilterFunc) const + { + return FilteredEnumerable(Indices(), FilterFunc); + } + + + FString UsageStats() + { + return FString::Printf(TEXT("RefCountSize %d FreeSize %d FreeMem %dkb"), + RefCounts.GetLength(), FreeIndices.GetLength(), (FreeIndices.GetByteCount() / 1024)); + } + + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/SmallListSet.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/SmallListSet.h new file mode 100644 index 000000000000..f397fee629d5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/SmallListSet.h @@ -0,0 +1,384 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp small_list_set + +#pragma once + +#include "Util/DynamicVector.h" + +/** + * FSmallListSet stores a set of short integer-valued variable-size lists. + * The lists are encoded into a few large TDynamicVector buffers, with internal pooling, + * so adding/removing lists usually does not involve any or delete ops. + * + * The lists are stored in two parts. The first N elements are stored in a linear + * subset of a TDynamicVector. If the list spills past these N elements, the extra elements + * are stored in a linked list (which is also stored in a flat array). + * + * Each list stores its count, so list-size operations are constant time. + * All the internal "pointers" are 32-bit. + * + * @todo look at usage of TFunction, are we making unnecessary copies? + */ +class GEOMETRICOBJECTS_API FSmallListSet +{ +protected: + /** This value is used to indicate Null in internal pointers */ + static const int32 NullValue; // = -1; // note: cannot be constexpr because we pass as reference to several functions, requires C++17 + + /** size of initial linear-memory portion of lists */ + static constexpr int32 BLOCKSIZE = 8; + /** offset from start of linear-memory portion of list that contains pointer to head of variable-length linked list */ + static constexpr int32 BLOCK_LIST_OFFSET = BLOCKSIZE + 1; + + /** mapping from list index to offset into block_store that contains list data */ + TDynamicVector ListHeads; + + /** + * flat buffer used to store per-list linear-memory blocks. + * blocks are BLOCKSIZE+2 long, elements are [CurrentCount, item0...itemN, LinkedListPtr] + */ + TDynamicVector ListBlocks; + + /** list of free blocks as indices/offsets into block_store */ + TDynamicVector FreeBlocks; + + /** number of allocated lists */ + int32 AllocatedCount = 0; + + /** + * flat buffer used to store linked-list "spill" elements + * each element is [value, next_ptr] + */ + TDynamicVector LinkedListElements; + + /** index of first free element in linked_store */ + int32 FreeHeadIndex; + + +public: + FSmallListSet(); + + FSmallListSet(const FSmallListSet& copy); + + + /** + * @return largest current list index + */ + size_t Size() const + { + return ListHeads.GetLength(); + } + + /** + * set new number of lists + */ + void Resize(int32 NewSize); + + + /** + * @return true if a list has been allocated at the given ListIndex + */ + bool IsAllocated(int32 ListIndex) const + { + return (ListIndex >= 0 && ListIndex < (int32)ListHeads.GetLength() && ListHeads[ListIndex] != NullValue); + } + + /** + * Create a list at the given ListIndex + */ + void AllocateAt(int32 ListIndex); + + + /** + * Insert Value into list at ListIndex + */ + void Insert(int32 ListIndex, int32 Value); + + + + /** + * remove Value from the list at ListIndex + * @return false if Value was not in this list + */ + bool Remove(int32 ListIndex, int32 Value); + + + + /** + * Move list at FromIndex to ToIndex + */ + void Move(int32 FromIndex, int32 ToIndex); + + + + /** + * Remove all elements from the list at ListIndex + */ + void Clear(int32 ListIndex); + + + /** + * @return the size of the list at ListIndex + */ + inline int32 GetCount(int32 ListIndex) const + { + check(ListIndex >= 0); + int32 block_ptr = ListHeads[ListIndex]; + return (block_ptr == NullValue) ? 0 : ListBlocks[block_ptr]; + } + + + /** + * @return the first item in the list at ListIndex + * @warning does not check for zero-size-list! + */ + inline int32 First(int32 ListIndex) const + { + check(ListIndex >= 0); + int32 block_ptr = ListHeads[ListIndex]; + return ListBlocks[block_ptr + 1]; + } + + + /** + * Search for the given Value in list at ListIndex + * @return true if found + */ + bool Contains(int32 ListIndex, int32 Value) const; + + + /** + * Search the list at ListIndex for a value where PredicateFunc(value) returns true + * @return the found value, or the InvalidValue argument if not found + */ + int32 Find(int32 ListIndex, const TFunction& PredicateFunc, int32 InvalidValue = -1) const; + + + + /** + * Search the list at ListIndex for a value where PredicateFunc(value) returns true, and replace it with NewValue + * @return true if the value was found and replaced + */ + bool Replace(int32 ListIndex, const TFunction& PredicateFunc, int32 NewValue); + + + + // + // iterator support + // + + + friend class ValueIterator; + + /** + * ValueIterator iterates over the values of a small list + * An optional mapping function can be provided which will then be applied to the values returned by the * operator + */ + class ValueIterator + { + public: + inline ValueIterator() + { + ListSet = nullptr; + MapFunc = nullptr; + ListIndex = 0; + } + + inline bool operator==(const ValueIterator& Other) const + { + return ListSet == Other.ListSet && ListIndex == Other.ListIndex; + } + inline bool operator!=(const ValueIterator& Other) const + { + return ListSet != Other.ListSet || ListIndex != Other.ListIndex || iCur != Other.iCur || cur_ptr != Other.cur_ptr; + } + + inline int32 operator*() const + { + return (MapFunc == nullptr) ? cur_value : MapFunc(cur_value); + } + + inline const ValueIterator& operator++() // prefix + { + this->GotoNext(); + return *this; + } + //inline ValueIterator operator++(int32) { // postfix + // index_iterator copy(*this); + // this->GotoNext(); + // return copy; + //} + + + protected: + inline void GotoNext() + { + if (N == 0) + { + SetToEnd(); + return; + } + GotoNextOverflow(); + } + + inline void GotoNextOverflow() + { + if (iCur <= iEnd) + { + cur_value = ListSet->ListBlocks[iCur]; + iCur++; + } + else if (cur_ptr != NullValue) + { + cur_value = ListSet->LinkedListElements[cur_ptr]; + cur_ptr = ListSet->LinkedListElements[cur_ptr + 1]; + } + else + { + SetToEnd(); + } + } + + inline ValueIterator( + const FSmallListSet* ListSetIn, + int32 ListIndex, + bool is_end, + const TFunction& MapFuncIn = nullptr) + { + this->ListSet = ListSetIn; + this->MapFunc = MapFuncIn; + this->ListIndex = ListIndex; + if (is_end) + { + SetToEnd(); + } + else + { + block_ptr = ListSet->ListHeads[ListIndex]; + if (block_ptr != ListSet->NullValue) + { + N = ListSet->ListBlocks[block_ptr]; + iEnd = (N < BLOCKSIZE) ? (block_ptr + N) : (block_ptr + BLOCKSIZE); + iCur = block_ptr + 1; + cur_ptr = (N < BLOCKSIZE) ? NullValue : ListSet->ListBlocks[block_ptr + BLOCK_LIST_OFFSET]; + GotoNext(); + } + else + { + SetToEnd(); + } + } + } + + inline void SetToEnd() + { + block_ptr = ListSet->NullValue; + N = 0; + iCur = -1; + cur_ptr = -1; + } + + const FSmallListSet * ListSet; + TFunction MapFunc; + int32 ListIndex; + int32 block_ptr; + int32 N; + int32 iEnd; + int32 iCur; + int32 cur_ptr; + int32 cur_value; + friend class FSmallListSet; + }; + + /** + * @return iterator for start of list at ListIndex + */ + inline ValueIterator BeginValues(int32 ListIndex) const + { + return ValueIterator(this, ListIndex, false); + } + + /** + * @return iterator for start of list at ListIndex, with given value mapping function + */ + inline ValueIterator BeginValues(int32 ListIndex, const TFunction& MapFunc) const + { + return ValueIterator(this, ListIndex, false, MapFunc); + } + + /** + * @return iterator for end of list at ListIndex + */ + inline ValueIterator EndValues(int32 ListIndex) const + { + return ValueIterator(this, ListIndex, true); + } + + /** + * ValueEnumerable is an object that provides begin/end semantics for a small list, suitable for use with a range-based for loop + */ + class ValueEnumerable + { + public: + const FSmallListSet* ListSet; + int32 ListIndex; + TFunction MapFunc; + ValueEnumerable() {} + ValueEnumerable(const FSmallListSet* ListSetIn, int32 ListIndex, TFunction MapFunc = nullptr) + { + this->ListSet = ListSetIn; + this->ListIndex = ListIndex; + this->MapFunc = MapFunc; + } + typename FSmallListSet::ValueIterator begin() const { return ListSet->BeginValues(ListIndex, MapFunc); } + typename FSmallListSet::ValueIterator end() const { return ListSet->EndValues(ListIndex); } + }; + + /** + * @return a value enumerable for the given ListIndex + */ + inline ValueEnumerable Values(int32 ListIndex) const + { + return ValueEnumerable(this, ListIndex); + } + + /** + * @return a value enumerable for the given ListIndex, with the given value mapping function + */ + inline ValueEnumerable Values(int32 ListIndex, const TFunction& MapFunc) const + { + return ValueEnumerable(this, ListIndex, MapFunc); + } + + + + +protected: + + + // grab a block from the free list, or allocate a one + int32 AllocateBlock(); + + // push a link-node onto the free list + inline void AddFreeLink(int32 ptr) + { + LinkedListElements[ptr + 1] = FreeHeadIndex; + FreeHeadIndex = ptr; + } + + + // remove val from the linked-list attached to block_ptr + bool RemoveFromLinkedList(int32 block_ptr, int32 val); + + +public: + inline FString MemoryUsage() + { + return FString::Printf(TEXT("ListSize %d Blocks Count %d Free %d Mem %dkb Linked Mem %dkb"), + ListHeads.GetLength(), AllocatedCount, (FreeBlocks.GetLength() * sizeof(int32) / 1024), + ListBlocks.GetLength(), (LinkedListElements.GetLength() * sizeof(int32) / 1024)); + } + + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/VectorTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/VectorTypes.h new file mode 100644 index 000000000000..7612f6265b2e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/VectorTypes.h @@ -0,0 +1,604 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Math/Vector.h" +#include "MathUtil.h" + + +/** + * Templated 3D Vector. Ported from g3Sharp library, with the intention of + * maintaining compatibility with existing g3Sharp code. Has an API + * similar to WildMagic, GTEngine, Eigen, etc. + * + * Convenience typedefs for FVector3f/FVector3d/FVector3i are defined, and + * should be preferentially used over the base template type + * + * Implicit casts to/from FVector are defined, for all types. + * + * @todo Possibly can be replaced/merged with Chaos TVector + */ +template +struct FVector3 +{ + T X, Y, Z; + + FVector3() + { + } + FVector3(T ValX, T ValY, T ValZ) + : X(ValX), Y(ValY), Z(ValZ) + { + } + FVector3(const T* Data) + { + X = Data[0]; + Y = Data[1]; + Z = Data[2]; + } + + inline operator const T*() const + { + return &X; + }; + inline operator T*() + { + return &X; + } + + inline operator FVector() const + { + return FVector((float)X, (float)Y, (float)Z); + } + inline FVector3(const FVector& Vec) + { + X = (T)Vec.X; + Y = (T)Vec.Y; + Z = (T)Vec.Z; + } + + + explicit inline operator FLinearColor() const + { + return FLinearColor((float)X, (float)Y, (float)Z); + } + inline FVector3(const FLinearColor& Color) + { + X = (T)Color.R; + Y = (T)Color.G; + Z = (T)Color.B; + } + + explicit inline operator FColor() const + { + return FColor( + FMathf::Clamp((int)((float)X*255.0f), 0, 255), + FMathf::Clamp((int)((float)Y*255.0f), 0, 255), + FMathf::Clamp((int)((float)Z*255.0f), 0, 255)); + } + + + static FVector3 Zero() + { + return FVector3((T)0, (T)0, (T)0); + } + static FVector3 One() + { + return FVector3((T)1, (T)1, (T)1); + } + static FVector3 UnitX() + { + return FVector3((T)1, (T)0, (T)0); + } + static FVector3 UnitY() + { + return FVector3((T)0, (T)1, (T)0); + } + static FVector3 UnitZ() + { + return FVector3((T)0, (T)0, (T)1); + } + static FVector3 Max() + { + return FVector3(TNumericLimits::Max(), TNumericLimits::Max(), TNumericLimits::Max()); + } + + FVector3& operator=(const FVector3& V2) + { + X = V2.X; + Y = V2.Y; + Z = V2.Z; + return *this; + } + + T& operator[](int Idx) + { + return (&X)[Idx]; + } + const T& operator[](int Idx) const + { + return (&X)[Idx]; + } + + T Length() const + { + return TMathUtil::Sqrt(X * X + Y * Y + Z * Z); + } + T SquaredLength() const + { + return X * X + Y * Y + Z * Z; + } + + inline T Distance(const FVector3& V2) const + { + T dx = V2.X - X; + T dy = V2.Y - Y; + T dz = V2.Z - Z; + return TMathUtil::Sqrt(dx * dx + dy * dy + dz * dz); + } + inline T DistanceSquared(const FVector3& V2) const + { + T dx = V2.X - X; + T dy = V2.Y - Y; + T dz = V2.Z - Z; + return dx * dx + dy * dy + dz * dz; + } + + inline FVector3 operator-() const + { + return FVector3(-X, -Y, -Z); + } + + inline FVector3 operator+(const FVector3& V2) const + { + return FVector3(X + V2.X, Y + V2.Y, Z + V2.Z); + } + + inline FVector3 operator-(const FVector3& V2) const + { + return FVector3(X - V2.X, Y - V2.Y, Z - V2.Z); + } + + inline FVector3 operator+(const T& Scalar) const + { + return FVector3(X + Scalar, Y + Scalar, Z + Scalar); + } + + inline FVector3 operator-(const T& Scalar) const + { + return FVector3(X - Scalar, Y - Scalar, Z - Scalar); + } + + inline FVector3 operator*(const T& Scalar) const + { + return FVector3(X * Scalar, Y * Scalar, Z * Scalar); + } + + inline FVector3 operator*(const FVector3& V2) const // component-wise + { + return FVector3(X * V2.X, Y * V2.Y, Z * V2.Z); + } + + inline FVector3 operator/(const T& Scalar) const + { + return FVector3(X / Scalar, Y / Scalar, Z / Scalar); + } + + inline FVector3& operator+=(const FVector3& V2) + { + X += V2.X; + Y += V2.Y; + Z += V2.Z; + return *this; + } + + inline FVector3& operator-=(const FVector3& V2) + { + X -= V2.X; + Y -= V2.Y; + Z -= V2.Z; + return *this; + } + + inline FVector3& operator*=(const T& Scalar) + { + X *= Scalar; + Y *= Scalar; + Z *= Scalar; + return *this; + } + + inline FVector3& operator/=(const T& Scalar) + { + X /= Scalar; + Y /= Scalar; + Z /= Scalar; + return *this; + } + + T Dot(const FVector3& V2) const + { + return X * V2.X + Y * V2.Y + Z * V2.Z; + } + + FVector3 Cross(const FVector3& V2) const + { + return FVector3( + Y * V2.Z - Z * V2.Y, + Z * V2.X - X * V2.Z, + X * V2.Y - Y * V2.X); + } + + // Angle in Degrees + T AngleD(const FVector3& V2) const + { + T DotVal = Dot(V2); + T ClampedDot = (DotVal < (T)-1) ? (T)-1 : ((DotVal > (T)1) ? (T)1 : DotVal); + return (T)(acos(ClampedDot) * (T)(180.0 / 3.14159265358979323846)); + } + + // Angle in Radians + T AngleR(const FVector3& V2) const + { + T DotVal = Dot(V2); + T ClampedDot = (DotVal < (T)-1) ? (T)-1 : ((DotVal > (T)1) ? (T)1 : DotVal); + return (T)acos(ClampedDot); + } + + inline bool IsNormalized() + { + return TMathUtil::Abs((X * X + Y * Y + Z * Z) - 1) < TMathUtil::ZeroTolerance; + } + + T Normalize(const T Epsilon = 0) + { + T length = Length(); + if (length > Epsilon) + { + T invLength = ((T)1) / length; + X *= invLength; + Y *= invLength; + Z *= invLength; + return length; + } + X = Y = Z = (T)0; + return 0; + } + + inline FVector3 Normalized(const T Epsilon = 0) const + { + T length = Length(); + if (length > Epsilon) + { + T invLength = ((T)1) / length; + return FVector3(X * invLength, Y * invLength, Z * invLength); + } + return FVector3((T)0, (T)0, (T)0); + } + + inline T MaxAbs() const + { + return TMathUtil::Max3(TMathUtil::Abs(X), TMathUtil::Abs(Y), TMathUtil::Abs(Z)); + } + + inline T MinAbs() const + { + return TMathUtil::Min3(TMathUtil::Abs(X), TMathUtil::Abs(Y), TMathUtil::Abs(Z)); + } + + static FVector3 Lerp(const FVector3& A, const FVector3& B, T Alpha) + { + T OneMinusAlpha = (T)1 - Alpha; + return FVector3(OneMinusAlpha * A.X + Alpha * B.X, + OneMinusAlpha * A.Y + Alpha * B.Y, + OneMinusAlpha * A.Z + Alpha * B.Z); + } + + inline bool operator==(const FVector3& Other) const + { + return X == Other.X && Y == Other.Y && Z == Other.Z; + } +}; + +template +inline FVector3 operator*(RealType Scalar, const FVector3& V) +{ + return FVector3(Scalar * V.X, Scalar * V.Y, Scalar * V.Z); +} + +typedef FVector3 FVector3f; +typedef FVector3 FVector3d; +typedef FVector3 FVector3i; + +template +FORCEINLINE uint32 GetTypeHash(const FVector3& Vector) +{ + // (this is how FIntVector and all the other FVectors do their hash functions) + // Note: this assumes there's no padding that could contain uncompared data. + return FCrc::MemCrc_DEPRECATED(&Vector, sizeof(FVector3)); +} + + + + + + +/** + * Templated 2D Vector. Ported from g3Sharp library, with the intention of + * maintaining compatibility with existing g3Sharp code. Has an API + * similar to WildMagic, GTEngine, Eigen, etc. + * + * Convenience typedefs for FVector2f/FVector2d/FVector2i are defined, and + * should be preferentially used over the base template type + * + * Implicit casts to/from FVector2D are defined, for all types. + * + * @todo Possibly can be replaced/merged with Chaos TVector + */ +template +struct FVector2 +{ + T X, Y; + + FVector2() + { + } + FVector2(T ValX, T ValY) + : X(ValX), Y(ValY) + { + } + FVector2(const T* Data) + { + X = Data[0]; + Y = Data[1]; + } + + inline operator const T*() const + { + return &X; + }; + inline operator T*() + { + return &X; + } + + operator FVector2D() const + { + return FVector2D((float)X, (float)Y); + } + FVector2(const FVector2D& Vec) + { + X = (T)Vec.X; + Y = (T)Vec.Y; + } + + static FVector2 Zero() + { + return FVector2((T)0, (T)0); + } + static FVector2 One() + { + return FVector2((T)1, (T)1); + } + static FVector2 UnitX() + { + return FVector2((T)1, (T)0); + } + static FVector2 UnitY() + { + return FVector2((T)0, (T)1); + } + + FVector2& operator=(const FVector2& V2) + { + X = V2.X; + Y = V2.Y; + return *this; + } + + T& operator[](int Idx) + { + return (&X)[Idx]; + } + const T& operator[](int Idx) const + { + return (&X)[Idx]; + } + + T Length() const + { + return TMathUtil::Sqrt(X * X + Y * Y); + } + T SquaredLength() const + { + return X * X + Y * Y; + } + + inline T Distance(const FVector2& V2) const + { + T dx = V2.X - X; + T dy = V2.Y - Y; + return TMathUtil::Sqrt(dx * dx + dy * dy); + } + inline T DistanceSquared(const FVector2& V2) const + { + T dx = V2.X - X; + T dy = V2.Y - Y; + return dx * dx + dy * dy; + } + + T Dot(const FVector2& V2) const + { + return X * V2.X + Y * V2.Y; + } + + // Note: DotPerp and FVector2's version of Cross are the same function + inline T DotPerp(const FVector2& V2) const + { + return X * V2.Y - Y * V2.X; + } + inline T Cross(const FVector2& V2) const + { + return X * V2.Y - Y * V2.X; + } + + FVector2 Perp() const + { + return FVector2(Y, -X); + } + + inline FVector2 operator-() const + { + return FVector2(-X, -Y); + } + + inline FVector2 operator+(const FVector2& V2) const + { + return FVector2(X + V2.X, Y + V2.Y); + } + + inline FVector2 operator-(const FVector2& V2) const + { + return FVector2(X - V2.X, Y - V2.Y); + } + + inline FVector2 operator+(const T& Scalar) const + { + return FVector2(X + Scalar, Y + Scalar); + } + + inline FVector2 operator-(const T& Scalar) const + { + return FVector2(X - Scalar, Y - Scalar); + } + + inline FVector2 operator*(const T& Scalar) const + { + return FVector2(X * Scalar, Y * Scalar); + } + + inline FVector2 operator*(const FVector2& V2) const // component-wise + { + return FVector2(X * V2.X, Y * V2.Y); + } + + inline FVector2 operator/(const T& Scalar) const + { + return FVector2(X / Scalar, Y / Scalar); + } + + inline FVector2& operator+=(const FVector2& V2) + { + X += V2.X; + Y += V2.Y; + return *this; + } + + inline FVector2& operator-=(const FVector2& V2) + { + X -= V2.X; + Y -= V2.Y; + return *this; + } + + inline FVector2& operator*=(const T& Scalar) + { + X *= Scalar; + Y *= Scalar; + return *this; + } + + inline FVector2& operator/=(const T& Scalar) + { + X /= Scalar; + Y /= Scalar; + return *this; + } + + inline bool IsNormalized() + { + return TMathUtil::Abs((X * X + Y * Y) - 1) < TMathUtil::ZeroTolerance; + } + + // Angle in Degrees + T AngleD(const FVector2& V2) const + { + T DotVal = Dot(V2); + T ClampedDot = (DotVal < (T)-1) ? (T)-1 : ((DotVal > (T)1) ? (T)1 : DotVal); + return (T)(acos(ClampedDot) * (T)(180.0 / 3.14159265358979323846)); + } + + // Angle in Radians + T AngleR(const FVector2& V2) const + { + T DotVal = Dot(V2); + T ClampedDot = (DotVal < (T)-1) ? (T)-1 : ((DotVal > (T)1) ? (T)1 : DotVal); + return (T)acos(ClampedDot); + } + + T Normalize(const T Epsilon = 0) + { + T length = Length(); + if (length > Epsilon) + { + T invLength = ((T)1) / length; + X *= invLength; + Y *= invLength; + return length; + } + X = Y = (T)0; + return 0; + } + + inline FVector2 Normalized(const T Epsilon = 0) const + { + T length = Length(); + if (length > Epsilon) + { + T invLength = ((T)1) / length; + return FVector2(X * invLength, Y * invLength); + } + return FVector2((T)0, (T)0); + } + + + + bool operator==(const FVector2& Other) const + { + return X == Other.X && Y == Other.Y; + } + + + static FVector2 Lerp(const FVector2& A, const FVector2& B, T Alpha) + { + T OneMinusAlpha = (T)1 - Alpha; + return FVector2(OneMinusAlpha * A.X + Alpha * B.X, + OneMinusAlpha * A.Y + Alpha * B.Y); + } + + // Note: This was called "IsLeft" in the Geometry3Sharp code (where it also went through Math.Sign) + // Returns >0 if C is to the left of the A->B Line, <0 if to the right, 0 if on the line + static T Orient(const FVector2& A, const FVector2& B, const FVector2& C) + { + return (B - A).DotPerp(C - A); + } +}; + +template +FORCEINLINE uint32 GetTypeHash(const FVector2& Vector) +{ + // (this is how FIntVector and all the other FVectors do their hash functions) + // Note: this assumes there's no padding that could contain uncompared data. + return FCrc::MemCrc_DEPRECATED(&Vector, sizeof(FVector2)); +} + +template +inline FVector2 operator*(RealType Scalar, const FVector2& V) +{ + return FVector2(Scalar * V.X, Scalar * V.Y); +} + +typedef FVector2 FVector2f; +typedef FVector2 FVector2d; +typedef FVector2 FVector2i; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/VectorUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/VectorUtil.h new file mode 100644 index 000000000000..39c285c09694 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/VectorUtil.h @@ -0,0 +1,348 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MathUtil.h" +#include "VectorTypes.h" + + +enum class EIntersectionResult +{ + NotComputed, + Intersects, + NoIntersection, + InvalidQuery +}; + +enum class EIntersectionType +{ + Empty, + Point, + Segment, + Line, + Polygon, + Plane, + Unknown +}; + +namespace VectorUtil +{ + /** + * @return true if all components of V are finite + */ + template + inline bool IsFinite(const FVector2& V) + { + return TMathUtil::IsFinite(V.X) && TMathUtil::IsFinite(V.Y); + } + + /** + * @return true if all components of V are finite + */ + template + inline bool IsFinite(const FVector3& V) + { + return TMathUtil::IsFinite(V.X) && TMathUtil::IsFinite(V.Y) && TMathUtil::IsFinite(V.Z); + } + + + /** + * @return input Value clamped to range [MinValue, MaxValue] + */ + template + inline RealType Clamp(RealType Value, RealType MinValue, RealType MaxValue) + { + return (Value < MinValue) ? MinValue : ((Value > MaxValue) ? MaxValue : Value); + } + + /** + * @return normalized vector that is perpendicular to triangle V0,V1,V2 (triangle normal) + */ + template + inline FVector3 Normal(const FVector3& V0, const FVector3& V1, const FVector3& V2) + { + FVector3 edge1(V1 - V0); + FVector3 edge2(V2 - V0); + edge1.Normalize(); + edge2.Normalize(); + // Unreal has Left-Hand Coordinate System so we need to reverse this cross-product to get proper triangle normal + FVector3 vCross(edge2.Cross(edge1)); + //FVector3 vCross(edge1.Cross(edge2)); + return vCross.Normalized(); + } + + /** + * @return un-normalized direction that is parallel to normal of triangle V0,V1,V2 + */ + template + inline FVector3 FastNormalDirection(const FVector3& V0, const FVector3& V1, const FVector3& V2) + { + // Unreal has Left-Hand Coordinate System so we need to reverse this cross-product to get proper triangle normal + return (V2 - V0).Cross(V1 - V0); + //return (V1 - V0).Cross(V2 - V0); + } + + /** + * @return area of 3D triangle V0,V1,V2 + */ + template + inline RealType Area(const FVector3& V0, const FVector3& V1, const FVector3& V2) + { + FVector3 edge1(V1 - V0); + FVector3 edge2(V2 - V0); + RealType Dot = edge1.Dot(edge2); + return (RealType)0.5 * TMathUtil::Sqrt(edge1.SquaredLength() * edge2.SquaredLength() - Dot * Dot); + } + + /** + * @return area of 2D triangle V0,V1,V2 + */ + template + inline RealType Area(const FVector2& V0, const FVector2& V1, const FVector2& V2) + { + FVector2 edge1(V1 - V0); + FVector2 edge2(V2 - V0); + RealType Dot = edge1.Dot(edge2); + return (RealType)0.5 * TMathUtil::Sqrt(edge1.SquaredLength() * edge2.SquaredLength() - Dot * Dot); + } + + /** + * @return true if triangle V1,V2,V3 is obtuse + */ + template + inline bool IsObtuse(const FVector3& V1, const FVector3& V2, const FVector3& V3) + { + RealType a2 = V1.DistanceSquared(V2); + RealType b2 = V1.DistanceSquared(V3); + RealType c2 = V2.DistanceSquared(V3); + return (a2 + b2 < c2) || (b2 + c2 < a2) || (c2 + a2 < b2); + } + + /** + * Calculate Normal and Area of triangle V0,V1,V2 + * @return triangle normal + */ + template + inline FVector3 FastNormalArea(const FVector3& V0, const FVector3& V1, const FVector3& V2, RealType& AreaOut) + { + FVector3 edge1(V1 - V0); + FVector3 edge2(V2 - V0); + // Unreal has Left-Hand Coordinate System so we need to reverse this cross-product to get proper triangle normal + FVector3d vCross = edge2.Cross(edge1); + //FVector3d vCross = edge1.Cross(edge2); + AreaOut = RealType(0.5) * vCross.Normalize(); + return vCross; + } + + /** @return true if Abs(A-B) is less than Epsilon */ + template + inline bool EpsilonEqual(RealType A, RealType B, RealType Epsilon) + { + return TMathUtil::Abs(A - B) < Epsilon; + } + + /** @return true if all coordinates of V0 and V1 are within Epsilon of eachother */ + template + inline bool EpsilonEqual(const FVector2& V0, const FVector2& V1, RealType Epsilon) + { + return EpsilonEqual(V0.X, V1.X, Epsilon) && EpsilonEqual(V0.Y, V1.Y, Epsilon); + } + + /** @return true if all coordinates of V0 and V1 are within Epsilon of eachother */ + template + inline bool EpsilonEqual(const FVector3& V0, const FVector3& V1, RealType Epsilon) + { + return EpsilonEqual(V0.X, V1.X, Epsilon) && EpsilonEqual(V0.Y, V1.Y, Epsilon) && EpsilonEqual(V0.Z, V1.Z, Epsilon); + } + + + /** @return 0/1/2 index of smallest value in Vector3 */ + template + inline int Min3Index(const ValueVecType& Vector3) + { + if (Vector3[0] <= Vector3[1]) + { + return Vector3[0] <= Vector3[2] ? 0 : 2; + } + else + { + return (Vector3[1] <= Vector3[2]) ? 1 : 2; + } + } + + + /** + * Calculates two vectors perpendicular to input Normal, as efficiently as possible. + */ + template + inline void MakePerpVectors(const FVector3& Normal, FVector3& OutPerp1, FVector3& OutPerp2) + { + // Duff et al method, from https://graphics.pixar.com/library/OrthonormalB/paper.pdf + if (Normal.Z < (RealType)0) + { + RealType A = (RealType)1 / ((RealType)1 - Normal.Z); + RealType B = Normal.X * Normal.Y * A; + OutPerp1.X = (RealType)1 - Normal.X * Normal.X * A; + OutPerp1.Y = -B; + OutPerp1.Z = Normal.X; + OutPerp2.X = B; + OutPerp2.Y = Normal.Y * Normal.Y * A - (RealType)1; + OutPerp2.Z = -Normal.Y; + } + else + { + RealType A = (RealType)1 / ((RealType)1 + Normal.Z); + RealType B = -Normal.X * Normal.Y * A; + OutPerp1.X = (RealType)1 - Normal.X * Normal.X * A; + OutPerp1.Y = B; + OutPerp1.Z = -Normal.X; + OutPerp2.X = B; + OutPerp2.Y = (RealType)1 - Normal.Y * Normal.Y * A; + OutPerp2.Z = -Normal.Y; + } + } + + /** + * Calculates one vector perpendicular to input Normal, as efficiently as possible. + */ + template + inline void MakePerpVector(const FVector3& Normal, FVector3& OutPerp1) + { + // Duff et al method, from https://graphics.pixar.com/library/OrthonormalB/paper.pdf + if (Normal.Z < (RealType)0) + { + RealType A = (RealType)1 / ((RealType)1 - Normal.Z); + RealType B = Normal.X * Normal.Y * A; + OutPerp1.X = (RealType)1 - Normal.X * Normal.X * A; + OutPerp1.Y = -B; + OutPerp1.Z = Normal.X; + } + else + { + RealType A = (RealType)1 / ((RealType)1 + Normal.Z); + RealType B = -Normal.X * Normal.Y * A; + OutPerp1.X = (RealType)1 - Normal.X * Normal.X * A; + OutPerp1.Y = B; + OutPerp1.Z = -Normal.X; + } + } + + + /** + * Calculates angle between VFrom and VTo after projection onto plane with normal defined by PlaneN + * @return angle in degrees + */ + template + inline RealType PlaneAngleSignedD(const FVector3& VFrom, const FVector3& VTo, const FVector3& PlaneN) + { + FVector3 vFrom = VFrom - VFrom.Dot(PlaneN) * PlaneN; + FVector3 vTo = VTo - VTo.Dot(PlaneN) * PlaneN; + vFrom.Normalize(); + vTo.Normalize(); + FVector3 C = vFrom.Cross(vTo); + if (C.SquaredLength() < TMathUtil::ZeroTolerance) + { // vectors are parallel + return vFrom.Dot(vTo) < 0 ? (RealType)180 : (RealType)0; + } + RealType fSign = C.Dot(PlaneN) < 0 ? (RealType)-1 : (RealType)1; + return (RealType)(fSign * vFrom.AngleD(vTo)); + } + + /** + * tan(theta/2) = +/- sqrt( (1-cos(theta)) / (1+cos(theta)) ) + * @return positive value of tan(theta/2) where theta is angle between normalized vectors A and B + */ + template + RealType VectorTanHalfAngle(const FVector3& A, const FVector3& B) + { + RealType cosAngle = A.Dot(B); + RealType sqr = ((RealType)1 - cosAngle) / ((RealType)1 + cosAngle); + sqr = Clamp(sqr, (RealType)0, TMathUtil::MaxReal); + return TMathUtil::Sqrt(sqr); + } + + /** + * Fast cotangent of angle between two vectors. + * cot = cos/sin, both of which can be computed from vector identities + * @return cotangent of angle between V1 and V2, or zero if result would be unstable (eg infinity) + */ + template + RealType VectorCot(const FVector3& V1, const FVector3& V2) + { + // formula from http://www.geometry.caltech.edu/pubs/DMSB_III.pdf + RealType fDot = V1.Dot(V2); + RealType lensqr1 = V1.SquaredLength(); + RealType lensqr2 = V2.SquaredLength(); + RealType d = Clamp(lensqr1 * lensqr2 - fDot * fDot, (RealType)0.0, TMathUtil::MaxReal); + if (d < TMathUtil::ZeroTolerance) + { + return (RealType)0; + } + else + { + return fDot / TMathUtil::Sqrt(d); + } + } + + /** + * @return solid angle at point P for triangle A,B,C + */ + template + inline RealType TriSolidAngle(FVector3 A, FVector3 B, FVector3 C, const FVector3& P) + { + // Formula from https://igl.ethz.ch/projects/winding-number/ + A -= P; + B -= P; + C -= P; + RealType la = A.Length(), lb = B.Length(), lc = C.Length(); + RealType top = (la * lb * lc) + A.Dot(B) * lc + B.Dot(C) * la + C.Dot(A) * lb; + RealType bottom = A.X * (B.Y * C.Z - C.Y * B.Z) - A.Y * (B.X * C.Z - C.X * B.Z) + A.Z * (B.X * C.Y - C.X * B.Y); + // -2 instead of 2 to account for UE winding + return RealType(-2.0) * atan2(bottom, top); + } + + /** + * @return angle between vectors (A-CornerPt) and (B-CornerPt) + */ + template + inline RealType OpeningAngleD(FVector3 A, FVector3 B, const FVector3& P) + { + A -= P; + A.Normalize(); + B -= P; + B.Normalize(); + return A.AngleD(B); + } + + + + /** + * @return sign of Binormal/Bitangent relative to Normal and Tangent + */ + template + inline RealType BinormalSign(const FVector3& NormalIn, const FVector3& TangentIn, const FVector3& BinormalIn) + { + // following math from RenderUtils.h::GetBasisDeterminantSign() + RealType Cross00 = BinormalIn.Y*NormalIn.Z - BinormalIn.Z*NormalIn.Y; + RealType Cross10 = BinormalIn.Z*NormalIn.X - BinormalIn.X*NormalIn.Z; + RealType Cross20 = BinormalIn.X*NormalIn.Y - BinormalIn.Y*NormalIn.X; + RealType Determinant = TangentIn.X*Cross00 + TangentIn.Y*Cross10 + TangentIn.Z*Cross20; + return (Determinant < 0) ? (RealType)-1 : (RealType)1; + } + + /** + * @return Binormal vector based on given Normal, Tangent, and Sign value (+1/-1) + */ + template + inline FVector3 Binormal(const FVector3& NormalIn, const FVector3& TangentIn, RealType BinormalSign) + { + return BinormalSign * FVector3( + NormalIn.Y*TangentIn.Z - NormalIn.Z*TangentIn.Y, + NormalIn.Z*TangentIn.X - NormalIn.X*TangentIn.Z, + NormalIn.X*TangentIn.Y - NormalIn.Y*TangentIn.X); + } + + + + +}; // namespace VectorUtil diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/GeometryAlgorithms.Build.cs b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/GeometryAlgorithms.Build.cs new file mode 100644 index 000000000000..a768c29fb1fc --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/GeometryAlgorithms.Build.cs @@ -0,0 +1,18 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class GeometryAlgorithms : ModuleRules +{ + public GeometryAlgorithms(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange( + new string[] { + "Core", + "GeometricObjects" + } + ); + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/Arrangement2d.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/Arrangement2d.cpp new file mode 100644 index 000000000000..4e382d11cbbe --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/Arrangement2d.cpp @@ -0,0 +1,253 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp Arrangement2d + +#include "Arrangement2d.h" +#include "ThirdParty/GTEngine/Mathematics/GteConstrainedDelaunay2.h" +#include "ThirdParty/GTEngine/Mathematics/GteBSNumber.h" +#include "ThirdParty/GTEngine/Mathematics/GteUIntegerFP32.h" +#include + + + +namespace +{ +//#define DEBUG_FILE_DUMPING 1 +#ifndef DEBUG_FILE_DUMPING + void DumpGraphForDebug(const FDynamicGraph2d& Graph, const FString& PathBase) + { + } + void DumpGraphForDebugAsOBJ(const FDynamicGraph2d& Graph, const FString& PathBase) + { + } + void DumpTriangulationForDebug(const FDynamicGraph2d& Graph, const TArray& Triangles, const FString& PathBase) + { + } +#else +#include + static int num = 0; + void DumpGraphForDebug(const FDynamicGraph2d& Graph, const FString& PathBase) + { + num++; + FString Path = PathBase + FString::FromInt(num) + ".txt"; + std::ofstream f(*Path); + + for (int32 VertexIdx = 0; VertexIdx < Graph.MaxVertexID(); VertexIdx++) + { + const FVector2d& Vertex = Graph.GetVertex(VertexIdx); + f << "v " << Vertex.X << " " << Vertex.Y << " " << std::endl; + } + for (const FDynamicGraph::FEdge& Edge : Graph.Edges()) + { + f << "e " << Edge.A << " " << Edge.B << std::endl; + } + f.close(); + } + void DumpGraphForDebugAsOBJ(const FDynamicGraph2d& Graph, const FString& PathBase) + { + num++; + FString Path = PathBase + FString::FromInt(num) + ".obj"; + std::ofstream f(*Path); + + for (int32 VertexIdx = 0; VertexIdx < Graph.MaxVertexID(); VertexIdx++) + { + const FVector2d& Vertex = Graph.GetVertex(VertexIdx); + f << "v " << Vertex.X << " " << Vertex.Y << " 0" << std::endl; + } + for (int32 VertexIdx = 0; VertexIdx < Graph.MaxVertexID(); VertexIdx++) + { + const FVector2d& Vertex = Graph.GetVertex(VertexIdx); + f << "v " << Vertex.X << " " << Vertex.Y << " .5" << std::endl; + } + for (const FDynamicGraph::FEdge& Edge : Graph.Edges()) + { + f << "f " << Edge.A + 1 << " " << Edge.B + 1 << " " << 1 + Edge.A + Graph.MaxVertexID() << std::endl; + } + f.close(); + } + void DumpTriangulationForDebug(const FDynamicGraph2d& Graph, const TArray& Triangles, const FString& PathBase) + { + num++; + FString Path = PathBase + FString::FromInt(num) + ".obj"; + std::ofstream f(*Path); + for (int32 VertexIdx = 0; VertexIdx < Graph.MaxVertexID(); VertexIdx++) + { + const FVector2d& Vertex = Graph.GetVertex(VertexIdx); + f << "v " << Vertex.X << " " << Vertex.Y << " 0" << std::endl; + } + for (const FIntVector& Tri : Triangles) + { + f << "f " << 1 + Tri.X << " " << 1 + Tri.Y << " " << 1 + Tri.Z << std::endl; + } + f.close(); + } +#endif +} + +bool FArrangement2d::AttemptTriangulate(TArray &Triangles, TArray &SkippedEdges, int32 BoundaryEdgeGroupID) +{ + Triangles.Empty(); + + // Use this declaration for the non-robust version of the algorithm; this is very non-robust, and not recommended! + //gte::ConstrainedDelaunay2 Delaunay; + + // Here are the types you could use for robust math (in theory makes the algorithm overall robust; in practice I am not sure) + gte::ConstrainedDelaunay2>> Delaunay; // Value of 263 is from comment in GTEngine/Mathematics/GteDelaunay2.h + //gte::ConstrainedDelaunay2> Delaunay; // Full arbitrary precision (slowest method, creates a std::vector per number) + + std::vector> InputVertices; + + // If there are unused vertices, define a vertex index remapping so the delaunay code won't have to understand that + bool bNeedsRemap = Graph.MaxVertexID() != Graph.VertexCount(); + TArray InputIndices, OutputIndices; + if (bNeedsRemap) + { + InputIndices.SetNumZeroed(Graph.MaxVertexID()); + OutputIndices.SetNumZeroed(Graph.VertexCount()); + int TotalOffset = 0; + for (int i = 0; i < Graph.MaxVertexID(); i++) + { + if (Graph.IsVertex(i)) + { + OutputIndices[i - TotalOffset] = i; + InputIndices[i] = i - TotalOffset; + FVector2d Vertex = Graph.GetVertex(i); + InputVertices.push_back(gte::Vector2 {{Vertex.X, Vertex.Y}}); + } + else + { + InputIndices[i] = -1; + TotalOffset++; + } + } + } + else + { // no index remapping needed; just copy the vertices + for (int i = 0; i < Graph.MaxVertexID(); i++) + { + FVector2d Vertex = Graph.GetVertex(i); + InputVertices.push_back(gte::Vector2 {{Vertex.X, Vertex.Y}}); + } + } + if (!Delaunay(Graph.VertexCount(), &InputVertices[0], 0.001f)) + { + return false; + } + std::vector OutEdges; + bool InsertConstraintFailure = false; + TSet> BoundarySet; // tracks all the boundary edges as they are added, so we can later eat what's outside them + bool bBoundaryTrackingFailure = false; + + for (int EdgeIdx : Graph.EdgeIndices()) + { + FDynamicGraph::FEdge Edge = Graph.GetEdge(EdgeIdx); + if (bNeedsRemap) + { + Edge.A = InputIndices[Edge.A]; + Edge.B = InputIndices[Edge.B]; + } + if (!Delaunay.Insert({{Edge.A, Edge.B}}, OutEdges)) + { + // Note the failed edge; we will try to proceed anyway, just without this edge. Hopefully the CDT is robust and this never happens! + ensureMsgf(false, TEXT("CDT edge insertion failed")); + InsertConstraintFailure = true; + SkippedEdges.Add(EdgeIdx); + // specially note if we failed to add a boundary edge; in this case removing the outside-boundary triangles would likely delete all triangles from the output + // in this case I choose (below) to just add all triangles to the output, ignoring boundaries, as this may create less noticeable artifacts in the typical cases + if (Edge.Group == BoundaryEdgeGroupID) + { + bBoundaryTrackingFailure = true; + } + } + else + { + if (Edge.Group == BoundaryEdgeGroupID) + { + for (size_t i = 0; i + 1 < OutEdges.size(); i++) + { + int MinV = FMath::Min(OutEdges[i], OutEdges[i + 1]); + int MaxV = FMath::Max(OutEdges[i], OutEdges[i + 1]); + BoundarySet.Add(TPair(MinV, MaxV)); + } + } + } + } + const std::vector& Indices = Delaunay.GetIndices(); + if (!bBoundaryTrackingFailure && BoundarySet.Num() > 0) { + auto IsBoundaryEdge = [&BoundarySet](int V0, int V1) + { + int MinV = FMath::Min(V0, V1); + int MaxV = FMath::Max(V0, V1); + return BoundarySet.Contains(TPair(MinV, MaxV)); + }; + + // eat hull to boundary + const std::vector& Adj = Delaunay.GetAdjacencies(); + int TriNum = int(Adj.size() / 3); + TArray Eaten; + Eaten.SetNumZeroed(TriNum); + + TArray ToEatQ; + // seed the feed queue with any triangles that are on hull but w/ an edge that is not on intended boundary + for (int TriIdx = 0; TriIdx < TriNum; TriIdx++) + { + int BaseIdx = TriIdx * 3; + bool bEatIt = false; + for (int SubIdx = 0, NextIdx = 2; !bEatIt && SubIdx < 3; NextIdx = SubIdx++) + { + // eat the triangle if it has a hull edge that is not a boundary edge + bEatIt = Adj[BaseIdx + NextIdx] == -1 && !IsBoundaryEdge(Indices[BaseIdx + SubIdx], Indices[BaseIdx + NextIdx]); + } + if (bEatIt) + { + ToEatQ.Add(TriIdx); + Eaten[TriIdx] = true; + } + } + + // eat any triangle that we can get to by crossing a non-boundary edge from an already-been-eaten triangle + while (ToEatQ.Num()) + { + int EatTri = ToEatQ.Pop(); + int BaseIdx = EatTri * 3; + for (int SubIdx = 0, NextIdx = 2; SubIdx < 3; NextIdx = SubIdx++) + { + int AdjTri = Adj[BaseIdx + NextIdx]; + if (AdjTri != -1 && !Eaten[AdjTri] && !IsBoundaryEdge(Indices[BaseIdx + SubIdx], Indices[BaseIdx + NextIdx])) + { + ToEatQ.Add(AdjTri); + Eaten[AdjTri] = true; + } + } + } + + // copy uneaten triangles + for (int i = 0; i < TriNum; i++) + { + if (!Eaten[i]) + { + Triangles.Add(FIntVector(Indices[i * 3], Indices[i * 3 + 1], Indices[i * 3 + 2])); + } + } + ensure(Triangles.Num()); // technically it could be valid to not have any triangles after eating the outside-boundary ones, but it would be unusual and could also be a bug + } + else + { + // copy all triangles over + for (size_t i = 0, n = Indices.size() / 3; i < n; i++) + { + Triangles.Add(FIntVector(Indices[i * 3], Indices[i * 3 + 1], Indices[i * 3 + 2])); + } + } + if (bNeedsRemap) + { + for (FIntVector& Face : Triangles) + { + Face.X = OutputIndices[Face.X]; + Face.Y = OutputIndices[Face.Y]; + Face.Z = OutputIndices[Face.Z]; + } + } + return !InsertConstraintFailure; +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/GeometryAlgorithmsModule.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/GeometryAlgorithmsModule.cpp new file mode 100644 index 000000000000..8eab6723c498 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/GeometryAlgorithmsModule.cpp @@ -0,0 +1,20 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "GeometryAlgorithmsModule.h" + +#define LOCTEXT_NAMESPACE "FGeometryAlgorithmsModule" + +void FGeometryAlgorithmsModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FGeometryAlgorithmsModule::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(FGeometryAlgorithmsModule, GeometryAlgorithms) \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEngine.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEngine.h new file mode 100644 index 000000000000..95dab42643b0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEngine.h @@ -0,0 +1,11 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.4 (2019/02/12) + +#pragma once + +#include +#include diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEngineDEF.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEngineDEF.h new file mode 100644 index 000000000000..5bffe76cfb04 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEngineDEF.h @@ -0,0 +1,80 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +//---------------------------------------------------------------------------- +// The platform specification. +// +// __MSWINDOWS__ : Microsoft Windows (WIN32 or WIN64) +// __APPLE__ : Macintosh OS X +// __LINUX__ : Linux or Cygwin +//---------------------------------------------------------------------------- + +#if !defined(__LINUX__) && (defined(WIN32) || defined(_WIN64)) +#define __MSWINDOWS__ + +#if !defined(_MSC_VER) +#error Microsoft Visual Studio 2013 or later is required. +#endif + +// MSVC 6 is version 12.0 +// MSVC 7.0 is version 13.0 (MSVS 2002) +// MSVC 7.1 is version 13.1 (MSVS 2003) +// MSVC 8.0 is version 14.0 (MSVS 2005) +// MSVC 9.0 is version 15.0 (MSVS 2008) +// MSVC 10.0 is version 16.0 (MSVS 2010) +// MSVC 11.0 is version 17.0 (MSVS 2012) +// MSVC 12.0 is version 18.0 (MSVS 2013) +// MSVC 14.0 is version 19.0 (MSVS 2015) +// Currently, projects are provided only for MSVC 12.0 and 14.0. +#if _MSC_VER < 1800 +#error Microsoft Visual Studio 2013 or later is required. +#endif + +// Debug build values (choose_your_value is 0, 1, or 2) +// 0: Disables checked iterators and disables iterator debugging. +// 1: Enables checked iterators and disables iterator debugging. +// 2: (default) Enables iterator debugging; checked iterators are not relevant. +// +// Release build values (choose_your_value is 0 or 1) +// 0: (default) Disables checked iterators. +// 1: Enables checked iterators; iterator debugging is not relevant. +// +// #define _ITERATOR_DEBUG_LEVEL choose_your_value + +#endif // WIN32 or _WIN64 + +// TODO: Windows DLL configurations have not yet been added to the project, +// but these defines are required to support them (when we do add them). +// +// Add GTE_EXPORT to project preprocessor options for dynamic library +// configurations to export their symbols. +#if defined(GTE_EXPORT) + // For the dynamic library configurations. + #define GTE_IMPEXP __declspec(dllexport) +#else + // For a client of the dynamic library or for the static library + // configurations. + #define GTE_IMPEXP +#endif + +// Expose exactly one of these. +#define GTE_USE_ROW_MAJOR +//#define GTE_USE_COL_MAJOR + +// Expose exactly one of these. +#define GTE_USE_MAT_VEC +//#define GTE_USE_VEC_MAT + +#if (defined(GTE_USE_ROW_MAJOR) && defined(GTE_USE_COL_MAJOR)) || (!defined(GTE_USE_ROW_MAJOR) && !defined(GTE_USE_COL_MAJOR)) +#error Exactly one storage order must be specified. +#endif + +#if (defined(GTE_USE_MAT_VEC) && defined(GTE_USE_VEC_MAT)) || (!defined(GTE_USE_MAT_VEC) && !defined(GTE_USE_VEC_MAT)) +#error Exactly one multiplication convention must be specified. +#endif diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEnginePCH.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEnginePCH.cpp new file mode 100644 index 000000000000..61878e84167e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEnginePCH.cpp @@ -0,0 +1,8 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEnginePCH.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEnginePCH.h new file mode 100644 index 000000000000..30b7bd3d407b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEnginePCH.h @@ -0,0 +1,13 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +// Define GTE_DISABLE_PCH to turn off the precompiled header system. +#ifndef GTE_DISABLE_PCH +#include +#endif + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTLowLevel.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTLowLevel.h new file mode 100644 index 000000000000..e89f91035c12 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTLowLevel.h @@ -0,0 +1,33 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2019/01/09) + +#pragma once + +// DataTypes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Logger +#include +#include +#include +#include +#include + +// Timer +#include diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTMathematics.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTMathematics.h new file mode 100644 index 000000000000..ca2775b7e12d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTMathematics.h @@ -0,0 +1,398 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.29 (2019/02/13) + +#pragma once + +// I used to have these sections ordered alphabetically by the names in +// the comments. Two phase name lookups for template matching by the +// MSVS 2017 compiler had no problems with the ordering, but the Linux +// g++ compiler does. This occurred when trying to match std::sqrt(...) +// and other math functions when the inputs are based on BSNumber or +// BSRational. The "Arithmetic" section has been moved before all other +// headers, and the UInteger* files have been moved before the BS* files. + +// Arithmetic +#include +#include +#include +#include +#include +#include + +// Algebra +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Approximation +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ComputationalGeometry +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Containment +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// CurvesSurfacesVolumes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Distance +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Functions +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// GeometricPrimitives +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Interpolation +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Intersection +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// NumericalMethods +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Projection +#include + +// SIMD (removed due to compile issues + not needed currently) +// #if defined(__MSWINDOWS__) && !defined(MINGW) +// #include +// #include +// #endif + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray2.h new file mode 100644 index 000000000000..2da04998e363 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray2.h @@ -0,0 +1,171 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The Array2 class represents a 2-dimensional array that minimizes the number +// of new and delete calls. The T objects are stored in a contiguous array. + +namespace gte +{ + +template +class Array2 +{ +public: + // Construction. The first constructor generates an array of objects that + // are owned by Array2. The second constructor is given an array of + // objects that are owned by the caller. The array has bound0 columns and + // bound1 rows. + Array2(size_t bound0, size_t bound1); + Array2(size_t bound0, size_t bound1, T* objects); + + // Support for dynamic resizing, copying, or moving. If 'other' does + // not own the original 'objects', they are not copied by the assignment + // operator. + Array2(); + Array2(Array2 const& other); + Array2& operator=(Array2 const& other); + Array2(Array2&& other); + Array2& operator=(Array2&& other); + + // Access to the array. Sample usage is + // Array2 myArray(3, 2); + // T* row1 = myArray[1]; + // T row1Col2 = myArray[1][2]; + inline size_t GetBound0() const; + inline size_t GetBound1() const; + inline T const* operator[] (int row) const; + inline T* operator[] (int row); + +private: + void SetPointers(T* objects); + void SetPointers(Array2 const& other); + + size_t mBound0, mBound1; + std::vector mObjects; + std::vector mIndirect1; +}; + +template +Array2::Array2(size_t bound0, size_t bound1) + : + mBound0(bound0), + mBound1(bound1), + mObjects(bound0 * bound1), + mIndirect1(bound1) +{ + SetPointers(mObjects.data()); +} + +template +Array2::Array2(size_t bound0, size_t bound1, T* objects) + : + mBound0(bound0), + mBound1(bound1), + mIndirect1(bound1) +{ + SetPointers(objects); +} + +template +Array2::Array2() + : + mBound0(0), + mBound1(0) +{ +} + +template +Array2::Array2(Array2 const& other) +{ + *this = other; +} + +template +Array2& Array2::operator=(Array2 const& other) +{ + // The copy is valid whether or not other.mObjects has elements. + mObjects = other.mObjects; + SetPointers(other); + return *this; +} + +template +Array2::Array2(Array2&& other) +{ + *this = std::move(other); +} + +template +Array2& Array2::operator=(Array2&& other) +{ + // The move is valid whether or not other.mObjects has elements. + mObjects = std::move(other.mObjects); + SetPointers(other); + return *this; +} + +template inline +size_t Array2::GetBound0() const +{ + return mBound0; +} + +template inline +size_t Array2::GetBound1() const +{ + return mBound1; +} + +template inline +T const* Array2::operator[] (int row) const +{ + return mIndirect1[row]; +} + +template inline +T* Array2::operator[] (int row) +{ + return mIndirect1[row]; +} + +template +void Array2::SetPointers(T* objects) +{ + for (size_t i1 = 0; i1 < mBound1; ++i1) + { + size_t j0 = mBound0 * i1; // = bound0 * (i1 + j1) where j1 = 0 + mIndirect1[i1] = &objects[j0]; + } +} + +template +void Array2::SetPointers(Array2 const& other) +{ + mBound0 = other.mBound0; + mBound1 = other.mBound1; + mIndirect1.resize(mBound1); + + if (mBound0 > 0) + { + // The objects are owned. + SetPointers(mObjects.data()); + } + else if (mIndirect1.size() > 0) + { + // The objects are not owned. + SetPointers(other.mIndirect1[0]); + } + // else 'other' is an empty Array2. +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray3.h new file mode 100644 index 000000000000..9cccb47f0618 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray3.h @@ -0,0 +1,192 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The Array3 class represents a 3-dimensional array that minimizes the number +// of new and delete calls. The T objects are stored in a contiguous array. + +namespace gte +{ + +template +class Array3 +{ +public: + // Construction. The first constructor generates an array of objects that + // are owned by Array3. The second constructor is given an array of + // objects that are owned by the caller. The array has bound0 columns, + // bound1 rows, and bound2 slices. + Array3(size_t bound0, size_t bound1, size_t bound2); + Array3(size_t bound0, size_t bound1, size_t bound2, T* objects); + + // Support for dynamic resizing, copying, or moving. If 'other' does + // not own the original 'objects', they are not copied by the assignment + // operator. + Array3(); + Array3(Array3 const&); + Array3& operator=(Array3 const&); + Array3(Array3&&); + Array3& operator=(Array3&&); + + // Access to the array. Sample usage is + // Array3 myArray(4, 3, 2); + // T** slice1 = myArray[1]; + // T* slice1row2 = myArray[1][2]; + // T slice1Row2Col3 = myArray[1][2][3]; + inline size_t GetBound0() const; + inline size_t GetBound1() const; + inline size_t GetBound2() const; + inline T* const* operator[] (int slice) const; + inline T** operator[] (int slice); + +private: + void SetPointers(T* objects); + void SetPointers(Array3 const& other); + + size_t mBound0, mBound1, mBound2; + std::vector mObjects; + std::vector mIndirect1; + std::vector mIndirect2; +}; + +template +Array3::Array3(size_t bound0, size_t bound1, size_t bound2) + : + mBound0(bound0), + mBound1(bound1), + mBound2(bound2), + mObjects(bound0 * bound1 * bound2), + mIndirect1(bound1 * bound2), + mIndirect2(bound2) +{ + SetPointers(mObjects.data()); +} + +template +Array3::Array3(size_t bound0, size_t bound1, size_t bound2, T* objects) + : + mBound0(bound0), + mBound1(bound1), + mBound2(bound2), + mIndirect1(bound1 * bound2), + mIndirect2(bound2) +{ + SetPointers(objects); +} + +template +Array3::Array3() + : + mBound0(0), + mBound1(0), + mBound2(0) +{ +} + +template +Array3::Array3(Array3 const& other) +{ + *this = other; +} + +template +Array3& Array3::operator=(Array3 const& other) +{ + // The copy is valid whether or not other.mObjects has elements. + mObjects = other.mObjects; + SetPointers(other); + return *this; +} + +template +Array3::Array3(Array3&& other) +{ + *this = std::move(other); +} + +template +Array3& Array3::operator=(Array3&& other) +{ + // The move is valid whether or not other.mObjects has elements. + mObjects = std::move(other.mObjects); + SetPointers(other); + return *this; +} + +template inline +size_t Array3::GetBound0() const +{ + return mBound0; +} + +template inline +size_t Array3::GetBound1() const +{ + return mBound1; +} + +template inline +size_t Array3::GetBound2() const +{ + return mBound2; +} + +template inline +T* const* Array3::operator[] (int slice) const +{ + return mIndirect2[slice]; +} + +template inline +T** Array3::operator[] (int slice) +{ + return mIndirect2[slice]; +} + +template +void Array3::SetPointers(T* objects) +{ + for (size_t i2 = 0; i2 < mBound2; ++i2) + { + size_t j1 = mBound1 * i2; // = bound1 * (i2 + j2) where j2 = 0 + mIndirect2[i2] = &mIndirect1[j1]; + for (size_t i1 = 0; i1 < mBound1; ++i1) + { + size_t j0 = mBound0 * (i1 + j1); + mIndirect2[i2][i1] = &objects[j0]; + } + } +} + +template +void Array3::SetPointers(Array3 const& other) +{ + mBound0 = other.mBound0; + mBound1 = other.mBound1; + mBound2 = other.mBound2; + mIndirect1.resize(mBound1 * mBound2); + mIndirect2.resize(mBound2); + + if (mBound0 > 0) + { + // The objects are owned. + SetPointers(mObjects.data()); + } + else if (mIndirect1.size() > 0) + { + // The objects are not owned. + SetPointers(other.mIndirect2[0][0]); + } + // else 'other' is an empty Array3. +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray4.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray4.h new file mode 100644 index 000000000000..93dc6d8a1321 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray4.h @@ -0,0 +1,213 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The Array4 class represents a 4-dimensional array that minimizes the number +// of new and delete calls. The T objects are stored in a contiguous array. + +namespace gte +{ + +template +class Array4 +{ +public: + // Construction. The first constructor generates an array of objects that + // are owned by Array4. The second constructor is given an array of + // objects that are owned by the caller. The array has bound0 columns, + // bound1 rows, bound2 slices, and bound3 cuboids. + Array4(size_t bound0, size_t bound1, size_t bound2, size_t bound3); + Array4(size_t bound0, size_t bound1, size_t bound2, size_t bound3, T* objects); + + // Support for dynamic resizing, copying, or moving. If 'other' does + // not own the original 'objects', they are not copied by the assignment + // operator. + Array4(); + Array4(Array4 const&); + Array4& operator=(Array4 const&); + Array4(Array4&&); + Array4& operator=(Array4&&); + + // Access to the array. Sample usage is + // Array4 myArray(5, 4, 3, 2); + // T*** cuboid1 = myArray[1]; + // T** cuboid1Slice2 = myArray[1][2]; + // T* cuboid1Slice2Row3 = myArray[1][2][3]; + // T cuboid1Slice2Row3Col4 = myArray[1][2][3][4]; + inline size_t GetBound0() const; + inline size_t GetBound1() const; + inline size_t GetBound2() const; + inline size_t GetBound3() const; + inline T** const* operator[] (int cuboid) const; + inline T*** operator[] (int cuboid); + +private: + void SetPointers(T* objects); + void SetPointers(Array4 const& other); + + size_t mBound0, mBound1, mBound2, mBound3; + std::vector mObjects; + std::vector mIndirect1; + std::vector mIndirect2; + std::vector mIndirect3; +}; + +template +Array4::Array4(size_t bound0, size_t bound1, size_t bound2, size_t bound3) + : + mBound0(bound0), + mBound1(bound1), + mBound2(bound2), + mBound3(bound3), + mObjects(bound0 * bound1 * bound2 * bound3), + mIndirect1(bound1 * bound2 * bound3), + mIndirect2(bound2 * bound3), + mIndirect3(bound3) +{ + SetPointers(mObjects.data()); +} + +template +Array4::Array4(size_t bound0, size_t bound1, size_t bound2, size_t bound3, T* objects) + : + mBound0(bound0), + mBound1(bound1), + mBound2(bound2), + mBound3(bound3), + mIndirect1(bound1 * bound2 * bound3), + mIndirect2(bound2 * bound3), + mIndirect3(bound3) +{ + SetPointers(objects); +} + +template +Array4::Array4() + : + mBound0(0), + mBound1(0), + mBound2(0), + mBound3(0) +{ +} + +template +Array4::Array4(Array4 const& other) +{ + *this = other; +} + +template +Array4& Array4::operator=(Array4 const& other) +{ + // The copy is valid whether or not other.mObjects has elements. + mObjects = other.mObjects; + SetPointers(other); + return *this; +} + +template +Array4::Array4(Array4&& other) +{ + *this = std::move(other); +} + +template +Array4& Array4::operator=(Array4&& other) +{ + // The move is valid whether or not other.mObjects has elements. + mObjects = std::move(other.mObjects); + SetPointers(other); + return *this; +} + +template inline +size_t Array4::GetBound0() const +{ + return mBound0; +} + +template inline +size_t Array4::GetBound1() const +{ + return mBound1; +} + +template inline +size_t Array4::GetBound2() const +{ + return mBound2; +} + +template inline +size_t Array4::GetBound3() const +{ + return mBound3; +} + +template inline +T** const* Array4::operator[] (int cuboid) const +{ + return mIndirect3[cuboid]; +} + +template inline +T*** Array4::operator[] (int cuboid) +{ + return mIndirect3[cuboid]; +} + +template +void Array4::SetPointers(T* objects) +{ + for (size_t i3 = 0; i3 < mBound3; ++i3) + { + size_t j2 = mBound2 * i3; // = bound2 * (i3 + j3) where j3 = 0 + mIndirect3[i3] = &mIndirect2[j2]; + for (size_t i2 = 0; i2 < mBound2; ++i2) + { + size_t j1 = mBound1 * (i2 + j2); + mIndirect3[i3][i2] = &mIndirect1[j1]; + for (size_t i1 = 0; i1 < mBound1; ++i1) + { + size_t j0 = mBound0 * (i1 + j1); + mIndirect3[i3][i2][i1] = &objects[j0]; + } + } + } +} + +template +void Array4::SetPointers(Array4 const& other) +{ + mBound0 = other.mBound0; + mBound1 = other.mBound1; + mBound2 = other.mBound2; + mBound3 = other.mBound3; + mIndirect1.resize(mBound1 * mBound2 * mBound3); + mIndirect2.resize(mBound2 * mBound3); + mIndirect3.resize(mBound3); + + if (mBound0 > 0) + { + // The objects are owned. + SetPointers(mObjects.data()); + } + else if (mIndirect1.size() > 0) + { + // The objects are not owned. + SetPointers(other.mIndirect3[0][0][0]); + } + // else 'other' is an empty Array3. +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteAtomicMinMax.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteAtomicMinMax.h new file mode 100644 index 000000000000..d30031972d6d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteAtomicMinMax.h @@ -0,0 +1,52 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// Implementations of atomic minimum and atomic maximum computations. These +// are based on std::atomic_compare_exchange_strong. + +namespace gte +{ + +template +T AtomicMin(std::atomic& v0, T const& v1); + +template +T AtomicMax(std::atomic& v0, T const& v1); + + +template +T AtomicMin(std::atomic& v0, T const& v1) +{ + T vInitial, vMin; + do + { + vInitial = v0; + vMin = std::min(vInitial, v1); + } while (!std::atomic_compare_exchange_strong(&v0, &vInitial, vMin)); + return vInitial; +} + +template +T AtomicMax(std::atomic& v0, T const& v1) +{ + T vInitial, vMax; + do + { + vInitial = v0; + vMax = std::max(vInitial, v1); + } while (!std::atomic_compare_exchange_strong(&v0, &vInitial, vMax)); + return vInitial; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteComputeModel.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteComputeModel.h new file mode 100644 index 000000000000..3185964c4dfa --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteComputeModel.h @@ -0,0 +1,102 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Expose this define if you want GPGPU support in computing any algorithms +// that have a GPU implemetnation. Alternatively, your application can +// define this in the project settings so that you do not have to edit this +// source file. +// +//#define GTE_COMPUTE_MODEL_ALLOW_GPGPU + +// The ComputeModel class allows you to select the type of hardware to use +// in your computational algorithms. +// +// If your computational algorithm requires the GPU, set 'inEngine' to the +// object that will execute the compute shaders. If your algorithm +// requires CPU multithreading, set the 'inNumThreads' to the desired +// number of threads, presumably 2 or larger. You can query for the number +// of concurrent hardware threads using std::thread::hardware_concurrency(). +// If you want single-threaded computations (on the main thread), set +// inNumThreads to 1. An example of using this class is +// +// ComputeModel cmodel(...); +// if (cmodel.engine) +// { +// ComputeUsingGPU(...); +// } +// else if (cmodel.numThreads > 1) +// { +// ComputeUsingCPUMultipleThreads(); +// } +// else +// { +// ComputeUsingCPUSingleThread(); +// } +// See GenerateMeshUV::SolveSystem(...) for a concrete example. +// +// Of course, your algorithm can interpret cmodel anyway it likes. For +// example, you might ignore cmodel.engine if all you care about is +// multithreading on the CPU. + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) +#include +#endif + +namespace gte +{ + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) +class GraphicsEngine; +class ProgramFactory; +#endif + +class GTE_IMPEXP ComputeModel +{ +public: + // Construction and destruction. You may derive from this class to make + // additional behavior available to your algorithms. For example, you + // might add a callback function to report progress of the algorithm. + virtual ~ComputeModel() + { + } + + ComputeModel() + : + numThreads(1) + { + } + + ComputeModel(unsigned int inNumThreads) + : + numThreads(inNumThreads > 0 ? inNumThreads : 1) + { + } + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) + ComputeModel(unsigned int inNumThreads, + std::shared_ptr const& inEngine, + std::shared_ptr const& inFactory) + : + numThreads(inNumThreads > 0 ? inNumThreads : 1), + engine(inEngine), + factory(inFactory) + { + } +#endif + + unsigned int numThreads; +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) + std::shared_ptr engine; + std::shared_ptr factory; +#endif +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLexicoArray2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLexicoArray2.h new file mode 100644 index 000000000000..6e1e19030ed6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLexicoArray2.h @@ -0,0 +1,219 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +// A template class to provide 2D array access that conforms to row-major +// order (RowMajor = true) or column-major order (RowMajor = false). The +template +class LexicoArray2 {}; + +// The array dimensions are known only at run time. +template +class LexicoArray2 +{ +public: + inline LexicoArray2(int numRows, int numCols, Real* matrix); + + inline int GetNumRows() const; + inline int GetNumCols() const; + inline Real& operator()(int r, int c); + inline Real const& operator()(int r, int c) const; + +private: + int mNumRows, mNumCols; + Real* mMatrix; +}; + +template +class LexicoArray2 +{ +public: + inline LexicoArray2(int numRows, int numCols, Real* matrix); + + inline int GetNumRows() const; + inline int GetNumCols() const; + inline Real& operator()(int r, int c); + inline Real const& operator()(int r, int c) const; + +private: + int mNumRows, mNumCols; + Real* mMatrix; +}; + +// The array dimensions are known at compile time. +template +class LexicoArray2 +{ +public: + inline LexicoArray2(Real* matrix); + + inline int GetNumRows() const; + inline int GetNumCols() const; + inline Real& operator()(int r, int c); + inline Real const& operator()(int r, int c) const; + +private: + Real* mMatrix; +}; + +template +class LexicoArray2 +{ +public: + inline LexicoArray2(Real* matrix); + + inline int GetNumRows() const; + inline int GetNumCols() const; + inline Real& operator()(int r, int c); + inline Real const& operator()(int r, int c) const; + +private: + Real* mMatrix; +}; + + +template inline +LexicoArray2::LexicoArray2(int numRows, int numCols, Real* matrix) + : + mNumRows(numRows), + mNumCols(numCols), + mMatrix(matrix) +{ +} + +template inline +int LexicoArray2::GetNumRows() const +{ + return mNumRows; +} + +template inline +int LexicoArray2::GetNumCols() const +{ + return mNumCols; +} + +template inline +Real& LexicoArray2::operator()(int r, int c) +{ + return mMatrix[c + mNumCols*r]; +} + +template inline +Real const& LexicoArray2::operator()(int r, int c) const +{ + return mMatrix[c + mNumCols*r]; +} + + + +template inline +LexicoArray2::LexicoArray2(int numRows, int numCols, Real* matrix) + : + mNumRows(numRows), + mNumCols(numCols), + mMatrix(matrix) +{ +} + +template inline +int LexicoArray2::GetNumRows() const +{ + return mNumRows; +} + +template inline +int LexicoArray2::GetNumCols() const +{ + return mNumCols; +} + +template inline +Real& LexicoArray2::operator()(int r, int c) +{ + return mMatrix[r + mNumRows*c]; +} + +template inline +Real const& LexicoArray2::operator()(int r, int c) const +{ + return mMatrix[r + mNumRows*c]; +} + + + +template inline +LexicoArray2::LexicoArray2(Real* matrix) + : + mMatrix(matrix) +{ +} + +template inline +int LexicoArray2::GetNumRows() const +{ + return NumRows; +} + +template inline +int LexicoArray2::GetNumCols() const +{ + return NumCols; +} + +template inline +Real& LexicoArray2::operator()(int r, int c) +{ + return mMatrix[c + NumCols*r]; +} + +template inline +Real const& LexicoArray2::operator()(int r, int c) const +{ + return mMatrix[c + NumCols*r]; +} + + + +template inline +LexicoArray2::LexicoArray2(Real* matrix) + : + mMatrix(matrix) +{ +} + +template inline +int LexicoArray2::GetNumRows() const +{ + return NumRows; +} + +template inline +int LexicoArray2::GetNumCols() const +{ + return NumCols; +} + +template inline +Real& LexicoArray2::operator()(int r, int c) +{ + return mMatrix[r + NumRows*c]; +} + +template inline +Real const& LexicoArray2::operator()(int r, int c) const +{ + return mMatrix[r + NumRows*c]; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogReporter.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogReporter.cpp new file mode 100644 index 000000000000..8f708ce68238 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogReporter.cpp @@ -0,0 +1,47 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +using namespace gte; + +LogReporter::~LogReporter() +{ + if (mLogToStdout) + { + Logger::Unsubscribe(mLogToStdout.get()); + } + + if (mLogToFile) + { + Logger::Unsubscribe(mLogToFile.get()); + } + + +} + +LogReporter::LogReporter(std::string const& logFile, int logFileFlags, + int logStdoutFlags) + : + mLogToFile(nullptr), + mLogToStdout(nullptr) + +{ + if (logFileFlags != Logger::Listener::LISTEN_FOR_NOTHING) + { + mLogToFile = std::make_unique(logFile, logFileFlags); + Logger::Subscribe(mLogToFile.get()); + } + + if (logStdoutFlags != Logger::Listener::LISTEN_FOR_NOTHING) + { + mLogToStdout = std::make_unique(logStdoutFlags); + Logger::Subscribe(mLogToStdout.get()); + } + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogReporter.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogReporter.h new file mode 100644 index 000000000000..5686c4ba5571 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogReporter.h @@ -0,0 +1,34 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +class GTE_IMPEXP LogReporter +{ +public: + // Construction and destruction. Create one of these objects in an + // application for logging. The GenerateProject tool creates such code. + // If you do not want a particular logger, set the flags to + // LISTEN_FOR_NOTHING and set logFile to "" if you do not want a file. + ~LogReporter(); + + LogReporter(std::string const& logFile, int logFileFlags, int logStdoutFlags); + +private: + std::unique_ptr mLogToFile; + std::unique_ptr mLogToStdout; + +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToFile.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToFile.cpp new file mode 100644 index 000000000000..4825c9068cd6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToFile.cpp @@ -0,0 +1,53 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +#include +using namespace gte; + + +LogToFile::LogToFile(std::string const& filename, int flags) + : + Logger::Listener(flags), + mFilename(filename) +{ + std::ofstream logFile(filename); + if (logFile) + { + // This clears the file contents from any previous runs. + logFile.close(); + } + else + { + // The file cannot be opened. Use a null string for Report to know + // not to attempt opening the file for append. + mFilename = ""; + } +} + +void LogToFile::Report(std::string const& message) +{ + if (mFilename != "") + { + // Open for append. + std::ofstream logFile(mFilename, + std::ios_base::out | std::ios_base::app); + if (logFile) + { + logFile << message.c_str(); + logFile.close(); + } + else + { + // The file cannot be opened. Use a null string for Report not + // to attempt opening the file for append on the next call. + mFilename = ""; + } + } +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToFile.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToFile.h new file mode 100644 index 000000000000..86a58a77fa34 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToFile.h @@ -0,0 +1,26 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +class GTE_IMPEXP LogToFile : public Logger::Listener +{ +public: + LogToFile(std::string const& filename, int flags); + +private: + virtual void Report(std::string const& message); + + std::string mFilename; +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStdout.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStdout.cpp new file mode 100644 index 000000000000..5d29845f0d99 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStdout.cpp @@ -0,0 +1,24 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +#include +using namespace gte; + + +LogToStdout::LogToStdout(int flags) + : + Logger::Listener(flags) +{ +} + +void LogToStdout::Report(std::string const& message) +{ + std::cout << message.c_str() << std::flush; +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStdout.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStdout.h new file mode 100644 index 000000000000..0b390b649275 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStdout.h @@ -0,0 +1,24 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +class GTE_IMPEXP LogToStdout : public Logger::Listener +{ +public: + LogToStdout(int flags); + +private: + virtual void Report(std::string const& message); +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStringArray.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStringArray.cpp new file mode 100644 index 000000000000..28b47ac2fe1a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStringArray.cpp @@ -0,0 +1,39 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +using namespace gte; + + +LogToStringArray::LogToStringArray(std::string const& name, int flags) + : + Logger::Listener(flags), + mName(name) +{ +} + +std::string const& LogToStringArray::GetName() const +{ + return mName; +} + +std::vector const& LogToStringArray::GetMessages() const +{ + return mMessages; +} + +std::vector& LogToStringArray::GetMessages() +{ + return mMessages; +} + +void LogToStringArray::Report(std::string const& message) +{ + mMessages.push_back(message); +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStringArray.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStringArray.h new file mode 100644 index 000000000000..1fa0fe2092b6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStringArray.h @@ -0,0 +1,32 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +class GTE_IMPEXP LogToStringArray : public Logger::Listener +{ +public: + LogToStringArray(std::string const& name, int flags); + + std::string const& GetName() const; + std::vector const& GetMessages() const; + std::vector& GetMessages(); + +private: + virtual void Report(std::string const& message); + + std::string mName; + std::vector mMessages; +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogger.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogger.cpp new file mode 100644 index 000000000000..11190b18503f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogger.cpp @@ -0,0 +1,137 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +using namespace gte; + + +Logger::Logger(char const* file, char const* function, int line, + std::string const& message) +{ + mMessage = + "File: " + std::string(file) + "\n" + + "Func: " + std::string(function) + "\n" + +#ifdef _MSC_VER // std::to_string not available on some platforms + "Line: " + std::to_string(line) + "\n" + +#endif + message + "\n\n"; +} + +void Logger::Assertion() +{ + msMutex.lock(); + for (auto listener : msListeners) + { + if (listener->GetFlags() & Listener::LISTEN_FOR_ASSERTION) + { + listener->Assertion(mMessage); + } + } + msMutex.unlock(); +} + +void Logger::Error() +{ + msMutex.lock(); + for (auto listener : msListeners) + { + if (listener->GetFlags() & Listener::LISTEN_FOR_ERROR) + { + listener->Error(mMessage); + } + } + msMutex.unlock(); +} + +void Logger::Warning() +{ + msMutex.lock(); + for (auto listener : msListeners) + { + if (listener->GetFlags() & Listener::LISTEN_FOR_WARNING) + { + listener->Warning(mMessage); + } + } + msMutex.unlock(); +} + +void Logger::Information() +{ + msMutex.lock(); + for (auto listener : msListeners) + { + if (listener->GetFlags() & Listener::LISTEN_FOR_INFORMATION) + { + listener->Information(mMessage); + } + } + msMutex.unlock(); +} + +void Logger::Subscribe(Listener* listener) +{ + msMutex.lock(); + msListeners.insert(listener); + msMutex.unlock(); +} + +void Logger::Unsubscribe(Listener* listener) +{ + msMutex.lock(); + msListeners.erase(listener); + msMutex.unlock(); +} + + + +// Logger::Listener + +Logger::Listener::~Listener() +{ +} + +Logger::Listener::Listener(int flags) + : + mFlags(flags) +{ +} + +int Logger::Listener::GetFlags() const +{ + return mFlags; +} + +void Logger::Listener::Assertion(std::string const& message) +{ + Report("\nGTE ASSERTION:\n" + message); +} + +void Logger::Listener::Error(std::string const& message) +{ + Report("\nGTE ERROR:\n" + message); +} + +void Logger::Listener::Warning(std::string const& message) +{ + Report("\nGTE WARNING:\n" + message); +} + +void Logger::Listener::Information(std::string const& message) +{ + Report("\nGTE INFORMATION:\n" + message); +} + +void Logger::Listener::Report(std::string const&) +{ + // Stub for derived classes. +} + + +std::mutex Logger::msMutex; +std::set Logger::msListeners; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogger.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogger.h new file mode 100644 index 000000000000..aa574cd29a8f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogger.h @@ -0,0 +1,110 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Uncomment this to turn off the logging system. The macros LogAssert, +// LogError, LogWarning, and LogInformation expand to nothing. (Do this for +// optimal performance.) +//#define GTE_NO_LOGGER + +namespace gte +{ + +class GTE_IMPEXP Logger +{ +public: + // Construction. The Logger object is designed to exist only for a + // single-line call. A string is generated from the input parameters and + // is used for reporting. + Logger(char const* file, char const* function, int line, + std::string const& message); + + // Notify current listeners about the logged information. + void Assertion(); + void Error(); + void Warning(); + void Information(); + + // Listeners subscribe to Logger to receive message strings. + class Listener + { + public: + enum + { + LISTEN_FOR_NOTHING = 0x00000000, + LISTEN_FOR_ASSERTION = 0x00000001, + LISTEN_FOR_ERROR = 0x00000002, + LISTEN_FOR_WARNING = 0x00000004, + LISTEN_FOR_INFORMATION = 0x00000008, + LISTEN_FOR_ALL = 0xFFFFFFFF + }; + + // Construction and destruction. + virtual ~Listener(); + Listener(int flags = LISTEN_FOR_NOTHING); + + // What the listener wants to hear. + int GetFlags() const; + + // Handlers for the messages received from the logger. + void Assertion(std::string const& message); + void Error(std::string const& message); + void Warning(std::string const& message); + void Information(std::string const& message); + + private: + virtual void Report(std::string const& message); + + int mFlags; + }; + + static void Subscribe(Listener* listener); + static void Unsubscribe(Listener* listener); + +private: + std::string mMessage; + + static std::mutex msMutex; + static std::set msListeners; +}; + +} + + +#if !defined(GTE_NO_LOGGER) + +#define LogAssert(condition, message) \ + if (!(condition)) \ + { \ + gte::Logger(__FILE__, __FUNCTION__, __LINE__, message).Assertion(); \ + } + +#define LogError(message) \ + gte::Logger(__FILE__, __FUNCTION__, __LINE__, message).Error() + +#define LogWarning(message) \ + gte::Logger(__FILE__, __FUNCTION__, __LINE__, message).Warning() + +#define LogInformation(message) \ + gte::Logger(__FILE__, __FUNCTION__, __LINE__, message).Information() + +#else + +// No logging of assertions, warnings, errors, or information. +#define LogAssert(condition, message) +#define LogError(message) +#define LogWarning(message) +#define LogInformation(message) + +#endif + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteMinHeap.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteMinHeap.h new file mode 100644 index 000000000000..d44bc8d78b58 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteMinHeap.h @@ -0,0 +1,442 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// A min-heap is a binary tree whose nodes have weights and with the +// constraint that the weight of a parent node is less than or equal to the +// weights of its children. This data structure may be used as a priority +// queue. If the std::priority_queue interface suffices for your needs, use +// that instead. However, for some geometric algorithms, that interface is +// insufficient for optimal performance. For example, if you have a polyline +// vertices that you want to decimate, each vertex's weight depends on its +// neighbors' locations. If the minimum-weight vertex is removed from the +// min-heap, the neighboring vertex weights must be updated--something that +// is O(1) time when you store the vertices as a doubly linked list. The +// neighbors are already in the min-heap, so modifying their weights without +// removing then from--and then reinserting into--the min-heap requires they +// must be moved to their proper places to restore the invariant of the +// min-heap. With std::priority_queue, you have no direct access to the +// modified vertices, forcing you to search for those vertices, remove them, +// update their weights, and re-insert them. The min-heap implementation here +// does support the update without removal and reinsertion. +// +// The ValueType represents the weight and it must support comparisons +// "<" and "<=". Additional information can be stored in the min-heap for +// convenient access; this is stored as the KeyType. In the (open) polyline +// decimation example, the KeyType is a structure that stores indices to +// a vertex and its neighbors. The following code illustrates the creation +// and use of the min-heap. The Weight() function is whatever you choose to +// guide which vertices are removed first from the polyline. +// +// struct Vertex { int previous, current, next; }; +// int numVertices = ; +// std::vector> positions(numVertices); +// ; +// MinHeap minHeap(numVertices); +// std::vector::Record*> records(numVertices); +// for (int i = 0; i < numVertices; ++i) +// { +// Vertex vertex; +// vertex.previous = (i + numVertices - 1) % numVertices; +// vertex.current = i; +// vertex.next = (i + 1) % numVertices; +// records[i] = minHeap.Insert(vertex, Weight(positions, vertex)); +// } +// +// while (minHeap.GetNumElements() >= 2) +// { +// Vertex vertex; +// Real weight; +// minHeap.Remove(vertex, weight); +// ; +// +// // Remove 'vertex' from the doubly linked list. +// Vertex& vp = records[vertex.previous]->key; +// Vertex& vc = records[vertex.current]->key; +// Vertex& vn = records[vertex.next]->key; +// vp.next = vc.next; +// vn.previous = vc.previous; +// +// // Update the neighbors' weights in the min-heap. +// minHeap.Update(records[vertex.previous], Weight(positions, vp)); +// minHeap.Update(records[vertex.next], Weight(positions, vn)); +// } + +namespace gte +{ + +template +class MinHeap +{ +public: + struct Record + { + KeyType key; + ValueType value; + int index; + }; + + // Construction. The record 'value' members are uninitialized for native + // types chosen for ValueType. If ValueType is of class type, then the + // default constructor is used to set the 'value' members. + MinHeap(MinHeap const& minHeap); + MinHeap(int maxElements = 0); + + // Assignment. + MinHeap& operator=(MinHeap const& minHeap); + + // Clear the min-heap so that it has the specified max elements, + // mNumElements is zero, and mPointers are set to the natural ordering + // of mRecords. + void Reset(int maxElements); + + // Get the remaining number of elements in the min-heap. This number is + // in the range {0..maxElements}. + inline int GetNumElements() const; + + // Get the root of the min-heap. The return value is 'true' whenever the + // min-heap is not empty. This function reads the root but does not + // remove the element from the min-heap. + inline bool GetMinimum(KeyType& key, ValueType& value) const; + + // Insert into the min-heap the 'value' that corresponds to the 'key'. + // The return value is a pointer to the heap record that stores a copy of + // 'value', and the pointer value is constant for the life of the min-heap. + // If you must update a member of the min-heap, say, as illustrated in the + // polyline decimation example, pass the pointer to Update: + // auto* valueRecord = minHeap.Insert(key, value); + // ; + // minHeap.Update(valueRecord, newValue). + Record* Insert(KeyType const& key, ValueType const& value); + + // Remove the root of the heap and return its 'key' and 'value members. + // The root contains the minimum value of all heap elements. The return + // value is 'true' whenever the min-heap was not empty before the Remove + // call. + bool Remove(KeyType& key, ValueType& value); + + // The value of a heap record must be modified through this function call. + // The side effect is that the heap is updated accordingly to restore the + // data structure to a min-heap. The input 'record' should be a pointer + // returned by Insert(value); see the comments for the Insert() function. + void Update(Record* record, ValueType const& value); + + // Support for debugging. The functions test whether the data structure + // is a valid min-heap. + bool IsValid() const; + +private: + // A 2-level storage system is used. The pointers have two roles. + // Firstly, they are unique to each inserted value in order to support + // the Update() capability of the min-heap. Secondly, they avoid + // potentially expensive copying of Record objects as sorting occurs in + // the heap. + int mNumElements; + std::vector mRecords; + std::vector mPointers; +}; + + +template +MinHeap::MinHeap(int maxElements) +{ + Reset(maxElements); +} + +template +MinHeap::MinHeap(MinHeap const& minHeap) +{ + *this = minHeap; +} + +template +MinHeap& MinHeap::operator=(MinHeap const& minHeap) +{ + mNumElements = minHeap.mNumElements; + mRecords = minHeap.mRecords; + mPointers.resize(minHeap.mPointers.size()); + for (auto& record : mRecords) + { + mPointers[record.index] = &record; + } + return *this; +} + +template +void MinHeap::Reset(int maxElements) +{ + mNumElements = 0; + if (maxElements > 0) + { + mRecords.resize(maxElements); + mPointers.resize(maxElements); + for (int i = 0; i < maxElements; ++i) + { + mPointers[i] = &mRecords[i]; + mPointers[i]->index = i; + } + } + else + { + mRecords.clear(); + mPointers.clear(); + } +} + +template +inline int MinHeap::GetNumElements() const +{ + return mNumElements; +} + +template +inline bool MinHeap::GetMinimum(KeyType& key, ValueType& value) const +{ + if (mNumElements > 0) + { + key = mPointers[0]->key; + value = mPointers[0]->value; + return true; + } + else + { + return false; + } +} + +template +typename MinHeap::Record* +MinHeap::Insert(KeyType const& key, ValueType const& value) +{ + // Return immediately when the heap is full. + if (mNumElements == static_cast(mRecords.size())) + { + return nullptr; + } + + // Store the input information in the last heap record, which is the last + // leaf in the tree. + int child = mNumElements++; + Record * record = mPointers[child]; + record->key = key; + record->value = value; + + // Propagate the information toward the root of the tree until it reaches + // its correct position, thus restoring the tree to a valid heap. + while (child > 0) + { + int parent = (child - 1) / 2; + if (mPointers[parent]->value <= value) + { + // The parent has a value smaller than or equal to the child's + // value, so we now have a valid heap. + break; + } + + // The parent has a larger value than the child's value. Swap the + // parent and child: + + // Move the parent into the child's slot. + mPointers[child] = mPointers[parent]; + mPointers[child]->index = child; + + // Move the child into the parent's slot. + mPointers[parent] = record; + mPointers[parent]->index = parent; + + child = parent; + } + + return mPointers[child]; +} + +template +bool MinHeap::Remove(KeyType& key, ValueType& value) +{ + // Return immediately when the heap is empty. + if (mNumElements == 0) + { + return false; + } + + // Get the information from the root of the heap. + Record* root = mPointers[0]; + key = root->key; + value = root->value; + + // Restore the tree to a heap. Abstractly, record is the new root of + // the heap. It is moved down the tree via parent-child swaps until it + // is in a location that restores the tree to a heap. + int last = --mNumElements; + Record* record = mPointers[last]; + int parent = 0, child = 1; + while (child <= last) + { + if (child < last) + { + // Select the child with smallest value to be the one that is + // swapped with the parent, if necessary. + int childP1 = child + 1; + if (mPointers[childP1]->value < mPointers[child]->value) + { + child = childP1; + } + } + + if (record->value <= mPointers[child]->value) + { + // The tree is now a heap. + break; + } + + // Move the child into the parent's slot. + mPointers[parent] = mPointers[child]; + mPointers[parent]->index = parent; + + parent = child; + child = 2 * child + 1; + } + + // The previous 'last' record was moved to the root and propagated down + // the tree to its final resting place, restoring the tree to a heap. + // The slot mPointers[parent] is that resting place. + mPointers[parent] = record; + mPointers[parent]->index = parent; + + // The old root record must not be lost. Attach it to the slot that + // contained the old last record. + mPointers[last] = root; + mPointers[last]->index = last; + return true; +} + +template +void MinHeap::Update(Record* record, ValueType const& value) +{ + // Return immediately on invalid record. + if (!record) + { + return; + } + + int parent, child, childP1, maxChild; + + if (record->value < value) + { + record->value = value; + + // The new value is larger than the old value. Propagate it toward + // the leaves. + parent = record->index; + child = 2 * parent + 1; + while (child < mNumElements) + { + // At least one child exists. Locate the one of maximum value. + childP1 = child + 1; + if (childP1 < mNumElements) + { + // Two children exist. + if (mPointers[child]->value <= mPointers[childP1]->value) + { + maxChild = child; + } + else + { + maxChild = childP1; + } + } + else + { + // One child exists. + maxChild = child; + } + + if (value <= mPointers[maxChild]->value) + { + // The new value is in the correct place to restore the tree + // to a heap. + break; + } + + // The child has a larger value than the parent's value. Swap + // the parent and child: + + // Move the child into the parent's slot. + mPointers[parent] = mPointers[maxChild]; + mPointers[parent]->index = parent; + + // Move the parent into the child's slot. + mPointers[maxChild] = record; + mPointers[maxChild]->index = maxChild; + + parent = maxChild; + child = 2 * parent + 1; + } + } + else if (value < record->value) + { + record->value = value; + + // The new weight is smaller than the old weight. Propagate it + // toward the root. + child = record->index; + while (child > 0) + { + // A parent exists. + parent = (child - 1) / 2; + + if (mPointers[parent]->value <= value) + { + // The new value is in the correct place to restore the tree + // to a heap. + break; + } + + // The parent has a smaller value than the child's value. Swap + // the child and parent: + + // Move the parent into the child's slot. + mPointers[child] = mPointers[parent]; + mPointers[child]->index = child; + + // Move the child into the parent's slot. + mPointers[parent] = record; + mPointers[parent]->index = parent; + + child = parent; + } + } +} + +template +bool MinHeap::IsValid() const +{ + for (int child = 0; child < mNumElements; ++child) + { + int parent = (child - 1) / 2; + if (parent > 0) + { + if (mPointers[child]->value < mPointers[parent]->value) + { + return false; + } + + if (mPointers[parent]->index != parent) + { + return false; + } + } + } + + return true; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteRangeIteration.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteRangeIteration.h new file mode 100644 index 000000000000..28a915b3161b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteRangeIteration.h @@ -0,0 +1,69 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// For information on range-based for-loops, see +// http://en.cppreference.com/w/cpp/language/range-for + +namespace gte +{ + +// The function gte::reverse supports reverse iteration in range-based +// for-loops using the auto keyword. For example, +// +// std::vector numbers(4); +// int i = 0; +// for (auto& number : numbers) +// { +// number = i++; +// std::cout << number << ' '; +// } +// // Output: 0 1 2 3 +// +// for (auto& number : gte::reverse(numbers)) +// { +// std::cout << number << ' '; +// } +// // Output: 3 2 1 0 + +template +class ReversalObject +{ +public: + ReversalObject(Iterator begin, Iterator end) + : + mBegin(begin), + mEnd(end) + { + } + + Iterator begin() const { return mBegin; } + Iterator end() const { return mEnd; } + +private: + Iterator mBegin, mEnd; +}; + +template +< + typename Iterable, + typename Iterator = decltype(std::begin(std::declval())), + typename ReverseIterator = std::reverse_iterator +> +ReversalObject reverse(Iterable&& range) +{ + return ReversalObject( + ReverseIterator(std::end(range)), + ReverseIterator(std::begin(range))); +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteSharedPtrCompare.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteSharedPtrCompare.h new file mode 100644 index 000000000000..e076a86cf2d6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteSharedPtrCompare.h @@ -0,0 +1,86 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2018 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.20.0 (2019/01/09) + +#pragma once + +#include + +// Comparison operators for std::shared_ptr objects. The type T must implement +// comparison operators. You must be careful when managing containers whose +// ordering is based on std::shared_ptr comparisons. The underlying objects +// can change, which invalidates the container ordering. If objects do not +// change while the container persists, these are safe to use. +// +// NOTE: std::shared_ptr already has comparison operators, but these +// compare pointer values instead of comparing the objects referenced by the +// pointers. If a container sorted using std::shared_ptr is created for +// two different executions of a program, the object ordering implied by the +// pointer ordering can differ. This might be undesirable for reproducibility +// of results between executions. + +namespace gte +{ + // sp0 == sp1 + template + struct SharedPtrEQ + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return (sp0 ? (sp1 ? *sp0 == *sp1 : false) : !sp1); + } + }; + + // sp0 != sp1 + template + struct SharedPtrNEQ + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return !SharedPtrEQ()(sp0, sp1); + } + }; + + // sp0 < sp1 + template + struct SharedPtrLT + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return (sp1 ? (!sp0 || *sp0 < *sp1) : false); + } + }; + + // sp0 <= sp1 + template + struct SharedPtrLTE + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return !SharedPtrLT()(sp1, sp0); + } + }; + + // sp0 > sp1 + template + struct SharedPtrGT + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return SharedPtrLT()(sp1, sp0); + } + }; + + // sp0 >= sp1 + template + struct SharedPtrGTE + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return !SharedPtrLT()(sp0, sp1); + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteThreadSafeMap.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteThreadSafeMap.h new file mode 100644 index 000000000000..1edf007033fa --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteThreadSafeMap.h @@ -0,0 +1,161 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class ThreadSafeMap +{ +public: + // Construction and destruction. + virtual ~ThreadSafeMap(); + ThreadSafeMap(); + + // All the operations are thread-safe. + bool HasElements() const; + bool Exists(Key key) const; + void Insert(Key key, Value value); + bool Remove(Key key, Value& value); + void RemoveAll(); + bool Get(Key key, Value& value) const; + void GatherAll(std::vector& values) const; + +protected: + std::map mMap; + mutable std::mutex mMutex; +}; + + +template +ThreadSafeMap::~ThreadSafeMap() +{ +} + +template +ThreadSafeMap::ThreadSafeMap() +{ +} + +template +bool ThreadSafeMap::HasElements() const +{ + bool hasElements; + mMutex.lock(); + { + hasElements = (mMap.size() > 0); + } + mMutex.unlock(); + return hasElements; +} + +template +bool ThreadSafeMap::Exists(Key key) const +{ + bool exists; + mMutex.lock(); + { + exists = (mMap.find(key) != mMap.end()); + } + mMutex.unlock(); + return exists; +} + +template +void ThreadSafeMap::Insert(Key key, Value value) +{ + mMutex.lock(); + { + mMap[key] = value; + } + mMutex.unlock(); +} + +template +bool ThreadSafeMap::Remove(Key key, Value& value) +{ + bool exists; + mMutex.lock(); + { + auto iter = mMap.find(key); + if (iter != mMap.end()) + { + value = iter->second; + mMap.erase(iter); + exists = true; + } + else + { + exists = false; + } + } + mMutex.unlock(); + return exists; +} + +template +void ThreadSafeMap::RemoveAll() +{ + mMutex.lock(); + { + mMap.clear(); + } + mMutex.unlock(); +} + +template +bool ThreadSafeMap::Get(Key key, Value& value) const +{ + bool exists; + mMutex.lock(); + { + auto iter = mMap.find(key); + if (iter != mMap.end()) + { + value = iter->second; + exists = true; + } + else + { + exists = false; + } + } + mMutex.unlock(); + return exists; +} + +template +void ThreadSafeMap::GatherAll(std::vector& values) const +{ + mMutex.lock(); + { + if (mMap.size() > 0) + { + values.resize(mMap.size()); + auto viter = values.begin(); + for (auto const& m : mMap) + { + *viter++ = m.second; + } + } + else + { + values.clear(); + } + } + mMutex.unlock(); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteThreadSafeQueue.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteThreadSafeQueue.h new file mode 100644 index 000000000000..570e43bfe7f8 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteThreadSafeQueue.h @@ -0,0 +1,116 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class ThreadSafeQueue +{ +public: + // Construction and destruction. + virtual ~ThreadSafeQueue(); + ThreadSafeQueue(size_t maxNumElements = 0); + + // All the operations are thread-safe. + size_t GetMaxNumElements() const; + size_t GetNumElements() const; + bool Push(Element const& element); + bool Pop(Element& element); + +protected: + size_t mMaxNumElements; + std::queue mQueue; + mutable std::mutex mMutex; +}; + + +template +ThreadSafeQueue::~ThreadSafeQueue() +{ +} + +template +ThreadSafeQueue::ThreadSafeQueue(size_t maxNumElements) + : + mMaxNumElements(maxNumElements) +{ +} + +template +size_t ThreadSafeQueue::GetMaxNumElements() const +{ + size_t maxNumElements; + mMutex.lock(); + { + maxNumElements = mMaxNumElements; + } + mMutex.unlock(); + return maxNumElements; +} + +template +size_t ThreadSafeQueue::GetNumElements() const +{ + size_t numElements; + mMutex.lock(); + { + numElements = mQueue.size(); + } + mMutex.unlock(); + return numElements; +} + +template +bool ThreadSafeQueue::Push(Element const& element) +{ + bool pushed; + mMutex.lock(); + { + if (mQueue.size() < mMaxNumElements) + { + mQueue.push(element); + pushed = true; + } + else + { + pushed = false; + } + } + mMutex.unlock(); + return pushed; +} + +template +bool ThreadSafeQueue::Pop(Element& element) +{ + bool popped; + mMutex.lock(); + { + if (mQueue.size() > 0) + { + element = mQueue.front(); + mQueue.pop(); + popped = true; + } + else + { + popped = false; + } + } + mMutex.unlock(); + return popped; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteTimer.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteTimer.cpp new file mode 100644 index 000000000000..051a851777f9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteTimer.cpp @@ -0,0 +1,115 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +using namespace gte; + +#if defined(GTE_NEEDS_64_BIT_CLOCK) + +#include +#include + +Timer::Timer() + : + mFrequency(0), + mInitialTicks(0), + mInvFrequency(0.0) +{ + LARGE_INTEGER frequency = { 1 }, counter = { 0 }; + QueryPerformanceFrequency(&frequency); + QueryPerformanceCounter(&counter); + mFrequency = static_cast(frequency.QuadPart); + mInitialTicks = static_cast(counter.QuadPart); + mInvFrequency = 1.0 / static_cast(mFrequency); +} + +int64_t Timer::GetNanoseconds() const +{ + int64_t ticks = GetTicks(); + double seconds = mInvFrequency * static_cast(ticks); + int64_t nanoseconds = static_cast(std::ceil(1000000000.0 * seconds)); + return nanoseconds; +} + +int64_t Timer::GetMicroseconds() const +{ + int64_t ticks = GetTicks(); + double seconds = mInvFrequency * static_cast(ticks); + int64_t microseconds = static_cast(std::ceil(1000000.0 * seconds)); + return microseconds; +} + +int64_t Timer::GetMilliseconds() const +{ + int64_t ticks = GetTicks(); + double seconds = mInvFrequency * static_cast(ticks); + int64_t milliseconds = static_cast(std::ceil(1000.0 * seconds)); + return milliseconds; +} + +double Timer::GetSeconds() const +{ + int64_t ticks = GetTicks(); + double seconds = mInvFrequency * static_cast(ticks); + return seconds; +} + +void Timer::Reset() +{ + LARGE_INTEGER counter = { 0 }; + QueryPerformanceCounter(&counter); + mInitialTicks = static_cast(counter.QuadPart); +} + +int64_t Timer::GetTicks() const +{ + LARGE_INTEGER counter = { 0 }; + QueryPerformanceCounter(&counter); + return static_cast(counter.QuadPart) - mInitialTicks; +} + +#else + +Timer::Timer() +{ + Reset(); +} + +int64_t Timer::GetNanoseconds() const +{ + auto currentTime = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast( + currentTime - mInitialTime).count(); +} + +int64_t Timer::GetMicroseconds() const +{ + auto currentTime = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast( + currentTime - mInitialTime).count(); +} + +int64_t Timer::GetMilliseconds() const +{ + auto currentTime = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast( + currentTime - mInitialTime).count(); +} + +double Timer::GetSeconds() const +{ + int64_t msecs = GetMilliseconds(); + return static_cast(msecs) / 1000.0; +} + +void Timer::Reset() +{ + mInitialTime = std::chrono::high_resolution_clock::now(); +} + +#endif diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteTimer.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteTimer.h new file mode 100644 index 000000000000..ca5c41fc5571 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteTimer.h @@ -0,0 +1,56 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +#if defined(__MSWINDOWS__) && _MSC_VER < 1900 +// MSVS 2013 has an implementation of std::chrono::high_resolution_clock +// that does not use a 64-bit clock (QueryPerformanceCounter). Instead, +// it appears to use a file-system clock that is 24 bits. To obtain +// accurate measurements, we need the 64-bit clock. However, MSVS 2015 +// does use a 64-bit clock. +#define GTE_NEEDS_64_BIT_CLOCK +#endif + +#if !defined(GTE_NEEDS_64_BIT_CLOCK) +#include +#endif + +namespace gte +{ + +class GTE_IMPEXP Timer +{ +public: + // Construction of a high-resolution timer (64-bit). + Timer(); + + // Get the current time relative to the initial time. + int64_t GetNanoseconds() const; + int64_t GetMicroseconds() const; + int64_t GetMilliseconds() const; + double GetSeconds() const; + + // Reset so that the current time is the initial time. + void Reset(); + +private: +#if defined(GTE_NEEDS_64_BIT_CLOCK) + // Internally use QueryPerformanceCounter. + int64_t GetTicks() const; + + int64_t mFrequency, mInitialTicks; + double mInvFrequency; +#else + std::chrono::high_resolution_clock::time_point mInitialTime; +#endif +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWeakPtrCompare.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWeakPtrCompare.h new file mode 100644 index 000000000000..1801137faecf --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWeakPtrCompare.h @@ -0,0 +1,81 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2018 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.20.0 (2019/01/09) + +#pragma once + +#include + +// Comparison operators for std::weak_ptr objects. The type T must implement +// comparison operators. You must be careful when managing containers whose +// ordering is based on std::weak_ptr comparisons. The underlying objects +// can change, which invalidates the container ordering. If objects do not +// change while the container persists, these are safe to use. + +namespace gte +{ + // wp0 == wp1 + template + struct WeakPtrEQ + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + auto sp0 = wp0.lock(), sp1 = wp1.lock(); + return (sp0 ? (sp1 ? *sp0 == *sp1 : false) : !sp1); + } + }; + + // wp0 != wp1 + template + struct WeakPtrNEQ + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + return !WeakPtrEQ()(wp0, wp1); + } + }; + + // wp0 < wp1 + template + struct WeakPtrLT + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + auto sp0 = wp0.lock(), sp1 = wp1.lock(); + return (sp1 ? (!sp0 || *sp0 < *sp1) : false); + } + }; + + // wp0 <= wp1 + template + struct WeakPtrLTE + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + return !WeakPtrLT()(wp1, wp0); + } + }; + + // wp0 > wp1 + template + struct WeakPtrGT + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + return WeakPtrLT()(wp1, wp0); + } + }; + + // wp0 >= wp1 + template + struct WeakPtrGTE + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + return !WeakPtrLT()(wp0, wp1); + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWrapper.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWrapper.cpp new file mode 100644 index 000000000000..333f6fecacb1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWrapper.cpp @@ -0,0 +1,36 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +#include +#include + +namespace gte +{ + +void Memcpy(void* target, void const* source, size_t count) +{ +#if defined(__MSWINDOWS__) + errno_t result = memcpy_s(target, count, source, count); + (void)result; // 0 on success +#else + memcpy(target, source, count); +#endif +} + +void Memcpy(wchar_t* target, wchar_t const* source, size_t count) +{ +#if defined(__MSWINDOWS__) + errno_t result = wmemcpy_s(target, count, source, count); + (void)result; // 0 on success +#else + wmemcpy(target, source, count); +#endif +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWrapper.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWrapper.h new file mode 100644 index 000000000000..23efe5c742db --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWrapper.h @@ -0,0 +1,21 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// Wrappers around platform-specific low-level library calls. + +namespace gte +{ + +void Memcpy(void* target, void const* source, size_t count); +void Memcpy(wchar_t* target, wchar_t const* source, size_t count); + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteACosEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteACosEstimate.h new file mode 100644 index 000000000000..9e90bf62fa54 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteACosEstimate.h @@ -0,0 +1,161 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Approximations to acos(x) of the form f(x) = sqrt(1-x)*p(x) +// where the polynomial p(x) of degree D minimizes the quantity +// maximum{|acos(x)/sqrt(1-x) - p(x)| : x in [0,1]} over all +// polynomials of degree D. + +namespace gte +{ + +template +class ACosEstimate +{ +public: + // The input constraint is x in [0,1]. For example, + // float x; // in [0,1] + // float result = ACosEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<1>, Real x); + inline static Real Evaluate(degree<2>, Real x); + inline static Real Evaluate(degree<3>, Real x); + inline static Real Evaluate(degree<4>, Real x); + inline static Real Evaluate(degree<5>, Real x); + inline static Real Evaluate(degree<6>, Real x); + inline static Real Evaluate(degree<7>, Real x); + inline static Real Evaluate(degree<8>, Real x); +}; + + +template +template +inline Real ACosEstimate::Degree(Real x) +{ + return Evaluate(degree(), x); +} + +template +inline Real ACosEstimate::Evaluate(degree<1>, Real x) +{ + Real poly; + poly = (Real)GTE_C_ACOS_DEG1_C1; + poly = (Real)GTE_C_ACOS_DEG1_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; +} + +template +inline Real ACosEstimate::Evaluate(degree<2>, Real x) +{ + Real poly; + poly = (Real)GTE_C_ACOS_DEG2_C2; + poly = (Real)GTE_C_ACOS_DEG2_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG2_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; +} + +template +inline Real ACosEstimate::Evaluate(degree<3>, Real x) +{ + Real poly; + poly = (Real)GTE_C_ACOS_DEG3_C3; + poly = (Real)GTE_C_ACOS_DEG3_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG3_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG3_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; +} + +template +inline Real ACosEstimate::Evaluate(degree<4>, Real x) +{ + Real poly; + poly = (Real)GTE_C_ACOS_DEG4_C4; + poly = (Real)GTE_C_ACOS_DEG4_C3 + poly * x; + poly = (Real)GTE_C_ACOS_DEG4_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG4_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG4_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; +} + +template +inline Real ACosEstimate::Evaluate(degree<5>, Real x) +{ + Real poly; + poly = (Real)GTE_C_ACOS_DEG5_C5; + poly = (Real)GTE_C_ACOS_DEG5_C4 + poly * x; + poly = (Real)GTE_C_ACOS_DEG5_C3 + poly * x; + poly = (Real)GTE_C_ACOS_DEG5_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG5_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG5_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; +} + +template +inline Real ACosEstimate::Evaluate(degree<6>, Real x) +{ + Real poly; + poly = (Real)GTE_C_ACOS_DEG6_C6; + poly = (Real)GTE_C_ACOS_DEG6_C5 + poly * x; + poly = (Real)GTE_C_ACOS_DEG6_C4 + poly * x; + poly = (Real)GTE_C_ACOS_DEG6_C3 + poly * x; + poly = (Real)GTE_C_ACOS_DEG6_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG6_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG6_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; +} + +template +inline Real ACosEstimate::Evaluate(degree<7>, Real x) +{ + Real poly; + poly = (Real)GTE_C_ACOS_DEG7_C7; + poly = (Real)GTE_C_ACOS_DEG7_C6 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C5 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C4 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C3 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; +} + +template +inline Real ACosEstimate::Evaluate(degree<8>, Real x) +{ + Real poly; + poly = (Real)GTE_C_ACOS_DEG8_C8; + poly = (Real)GTE_C_ACOS_DEG8_C7 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C6 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C5 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C4 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C3 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteASinEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteASinEstimate.h new file mode 100644 index 000000000000..5bd83bb95dbb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteASinEstimate.h @@ -0,0 +1,40 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Approximations to asin(x) of the form f(x) = pi/2 - sqrt(1-x)*p(x) +// where the polynomial p(x) of degree D minimizes the quantity +// maximum{|acos(x)/sqrt(1-x) - p(x)| : x in [0,1]} over all +// polynomials of degree D. We use the identity asin(x) = pi/2 - acos(x). + +namespace gte +{ + +template +class ASinEstimate +{ +public: + // The input constraint is x in [0,1]. For example, + // float x; // in [0,1] + // float result = ASinEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); +}; + + +template +template +inline Real ASinEstimate::Degree(Real x) +{ + return (Real)GTE_C_HALF_PI - ACosEstimate::Degree(x); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteATanEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteATanEstimate.h new file mode 100644 index 000000000000..2492610619de --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteATanEstimate.h @@ -0,0 +1,159 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Minimax polynomial approximations to atan(x). The polynomial p(x) of +// degree D has only odd-power terms, is required to have linear term x, +// and p(1) = atan(1) = pi/4. It minimizes the quantity +// maximum{|atan(x) - p(x)| : x in [-1,1]} over all polynomials of +// degree D subject to the constraints mentioned. + +namespace gte +{ + +template +class ATanEstimate +{ +public: + // The input constraint is x in [-1,1]. For example, + // float x; // in [-1,1] + // float result = ATanEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input x can be any real number. Range reduction is used via + // the identities atan(x) = pi/2 - atan(1/x) for x > 0, and + // atan(x) = -pi/2 - atan(1/x) for x < 0. For example, + // float x; // x any real number + // float result = ATanEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<3>, Real x); + inline static Real Evaluate(degree<5>, Real x); + inline static Real Evaluate(degree<7>, Real x); + inline static Real Evaluate(degree<9>, Real x); + inline static Real Evaluate(degree<11>, Real x); + inline static Real Evaluate(degree<13>, Real x); +}; + + +template +template +inline Real ATanEstimate::Degree(Real x) +{ + return Evaluate(degree(), x); +} + +template +template +inline Real ATanEstimate::DegreeRR(Real x) +{ + if (std::abs(x) <= (Real)1) + { + return Degree(x); + } + else if (x > (Real)1) + { + return (Real)GTE_C_HALF_PI - Degree((Real)1 / x); + } + else + { + return (Real)-GTE_C_HALF_PI - Degree((Real)1 / x); + } +} + +template +inline Real ATanEstimate::Evaluate(degree<3>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG3_C1; + poly = (Real)GTE_C_ATAN_DEG3_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real ATanEstimate::Evaluate(degree<5>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG5_C2; + poly = (Real)GTE_C_ATAN_DEG5_C1 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG5_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real ATanEstimate::Evaluate(degree<7>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG7_C3; + poly = (Real)GTE_C_ATAN_DEG7_C2 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG7_C1 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG7_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real ATanEstimate::Evaluate(degree<9>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG9_C4; + poly = (Real)GTE_C_ATAN_DEG9_C3 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG9_C2 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG9_C1 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG9_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real ATanEstimate::Evaluate(degree<11>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG11_C5; + poly = (Real)GTE_C_ATAN_DEG11_C4 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG11_C3 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG11_C2 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG11_C1 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG11_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real ATanEstimate::Evaluate(degree<13>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG13_C6; + poly = (Real)GTE_C_ATAN_DEG13_C5 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG13_C4 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG13_C3 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG13_C2 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG13_C1 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG13_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteAlignedBox.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteAlignedBox.h new file mode 100644 index 000000000000..723650e84c26 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteAlignedBox.h @@ -0,0 +1,136 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The box is aligned with the standard coordinate axes, which allows us to +// represent it using minimum and maximum values along each axis. Some +// algorithms prefer the centered representation that is used for oriented +// boxes. The center is C and the extents are the half-lengths in each +// coordinate-axis direction. + +namespace gte +{ + +template +class AlignedBox +{ +public: + // Construction and destruction. The default constructor sets the + // minimum values to -1 and the maximum values to +1. + AlignedBox(); + + // Please ensure that inMin[i] <= inMax[i] for all i. + AlignedBox(Vector const& inMin, Vector const& inMax); + + // Compute the centered representation. NOTE: If you set the minimum + // and maximum values, compute C and extents, and then recompute the + // minimum and maximum values, the numerical round-off errors can lead to + // results different from what you started with. + void GetCenteredForm(Vector& center, Vector& extent) + const; + + // Public member access. It is required that min[i] <= max[i]. + Vector min, max; + +public: + // Comparisons to support sorted containers. + bool operator==(AlignedBox const& box) const; + bool operator!=(AlignedBox const& box) const; + bool operator< (AlignedBox const& box) const; + bool operator<=(AlignedBox const& box) const; + bool operator> (AlignedBox const& box) const; + bool operator>=(AlignedBox const& box) const; +}; + +// Template aliases for convenience. +template +using AlignedBox2 = AlignedBox<2, Real>; + +template +using AlignedBox3 = AlignedBox<3, Real>; + + +template +AlignedBox::AlignedBox() +{ + for (int i = 0; i < N; ++i) + { + min[i] = (Real)-1; + max[i] = (Real)+1; + } +} + +template +AlignedBox::AlignedBox(Vector const& inMin, + Vector const& inMax) +{ + for (int i = 0; i < N; ++i) + { + min[i] = inMin[i]; + max[i] = inMax[i]; + } +} + +template +void AlignedBox::GetCenteredForm(Vector& center, + Vector& extent) const +{ + center = (max + min) * (Real)0.5; + extent = (max - min) * (Real)0.5; +} + +template +bool AlignedBox::operator==(AlignedBox const& box) const +{ + return min == box.min && max == box.max; +} + +template +bool AlignedBox::operator!=(AlignedBox const& box) const +{ + return !operator==(box); +} + +template +bool AlignedBox::operator<(AlignedBox const& box) const +{ + if (min < box.min) + { + return true; + } + + if (min > box.min) + { + return false; + } + + return max < box.max; +} + +template +bool AlignedBox::operator<=(AlignedBox const& box) const +{ + return operator<(box) || operator==(box); +} + +template +bool AlignedBox::operator>(AlignedBox const& box) const +{ + return !operator<=(box); +} + +template +bool AlignedBox::operator>=(AlignedBox const& box) const +{ + return !operator<(box); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCircle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCircle2.h new file mode 100644 index 000000000000..568d2aa80cfe --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCircle2.h @@ -0,0 +1,101 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Least-squares fit of a circle to a set of points. A successful fit is +// indicated by the return value of 'true'. The return value is the number +// of iterations used. If the number is the maximum, you can either accept +// the result or try increasing the maximum number and calling the function +// again. TODO: Currently, the test for terminating the algorithm is exact +// (diff == (0,0)). Expose an epsilon for this? +// +// If initialCenterIsAverage is set to 'true', the initial guess for the +// circle center is the average of the data points. If the data points are +// clustered along a small arc, ApprCircle2 is very slow to converge. If +// initialCenterIsAverage is set to 'false', the initial guess for the +// circle center is computed using a least-squares estimate of the +// coefficients for a quadratic equation that represents a circle. This +// approach tends to converge rapidly. + +namespace gte +{ + +template +class ApprCircle2 +{ +public: + unsigned int operator()(int numPoints, Vector2 const* points, + unsigned int maxIterations, bool initialCenterIsAverage, + Circle2& circle); +}; + + +template +unsigned int ApprCircle2::operator()(int numPoints, + Vector2 const* points, unsigned int maxIterations, + bool initialCenterIsAverage, Circle2& circle) +{ + // Compute the average of the data points. + Vector2 average = points[0]; + for (int i = 1; i < numPoints; ++i) + { + average += points[i]; + } + Real invNumPoints = ((Real)1) / static_cast(numPoints); + average *= invNumPoints; + + // The initial guess for the center. + if (initialCenterIsAverage) + { + circle.center = average; + } + else + { + ApprQuadraticCircle2()(numPoints, points, circle); + } + + unsigned int iteration; + for (iteration = 0; iteration < maxIterations; ++iteration) + { + // Update the iterates. + Vector2 current = circle.center; + + // Compute average L, dL/da, dL/db. + Real lenAverage = (Real)0; + Vector2 derLenAverage = Vector2::Zero(); + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - circle.center; + Real length = Length(diff); + if (length >(Real)0) + { + lenAverage += length; + Real invLength = ((Real)1) / length; + derLenAverage -= invLength * diff; + } + } + lenAverage *= invNumPoints; + derLenAverage *= invNumPoints; + + circle.center = average + lenAverage * derLenAverage; + circle.radius = lenAverage; + + Vector2 diff = circle.center - current; + if (diff == Vector2::Zero()) + { + break; + } + } + + return ++iteration; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCone3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCone3.h new file mode 100644 index 000000000000..de09603371eb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCone3.h @@ -0,0 +1,340 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.14.2 (2018/10/30) + +#pragma once + +#include +#include +#include +#include + +// The cone vertex is V, the unit-length axis direction is U and the +// cone angle is A in (0,pi/2). The cone is defined algebraically by +// those points X for which +// Dot(U,X-V)/Length(X-V) = cos(A) +// This can be written as a quadratic equation +// (V-X)^T * (cos(A)^2 - U * U^T) * (V-X) = 0 +// with the implicit constraint that Dot(U, X-V) > 0 (X is on the +// "positive" cone). Define W = U/cos(A), so Length(W) > 1 and +// F(X;V,W) = (V-X)^T * (I - W * W^T) * (V-X) = 0 +// The nonlinear least squares fitting of points {X[i]}_{i=0}^{n-1} +// computes V and W to minimize the error function +// E(V,W) = sum_{i=0}^{n-1} F(X[i];V,W)^2 +// I recommend using the Gauss-Newton minimizer when your cone points +// are truly nearly a cone; otherwise, try the Levenberg-Marquardt +// minimizer. +// +// The mathematics used in this implementation are found in +// http://www.geometrictools.com/Documentation/LeastSquaresFitting.pdf +// In particular, the details for choosing an initial cone for fitting +// are somewhat complicated. + +namespace gte +{ + template + class ApprCone3 + { + public: + ApprCone3() + : + mNumPoints(0), + mPoints(nullptr) + { + // F[i](V,W) = D^T * (I - W * W^T) * D, D = V - X[i], P = (V,W) + mFFunction = [this](GVector const& P, GVector& F) + { + Vector<3, Real> V = { P[0], P[1], P[2] }; + Vector<3, Real> W = { P[3], P[4], P[5] }; + for (int i = 0; i < mNumPoints; ++i) + { + Vector<3, Real> delta = V - mPoints[i]; + Real deltaDotW = Dot(delta, W); + F[i] = Dot(delta, delta) - deltaDotW * deltaDotW; + } + }; + + // dF[i]/dV = 2 * (D - Dot(W, D) * W) + // dF[i]/dW = -2 * Dot(W, D) * D + mJFunction = [this](GVector const& P, GMatrix& J) + { + Vector<3, Real> V = { P[0], P[1], P[2] }; + Vector<3, Real> W = { P[3], P[4], P[5] }; + for (int row = 0; row < mNumPoints; ++row) + { + Vector<3, Real> delta = V - mPoints[row]; + Real deltaDotW = Dot(delta, W); + Vector<3, Real> temp0 = delta - deltaDotW * W; + Vector<3, Real> temp1 = deltaDotW * delta; + for (int col = 0; col < 3; ++col) + { + J(row, col) = (Real)2 * temp0[col]; + J(row, col + 3) = (Real)-2 * temp1[col]; + } + } + }; + } + + // If you want to specify that coneVertex, coneAxis and coneAngle + // are the initial guesses for the minimizer, set the parameter + // useConeInputAsInitialGuess to 'true'. If you want the function + // to compute initial guesses, set that parameter to 'false'. + // A Gauss-Newton minimizer is used to fit a cone using nonlinear + // least-squares. The fitted cone is returned in coneVertex, + // coneAxis and coneAngle. See GteGaussNewtonMinimizer.h for a + // description of the least-squares algorithm and the parameters + // that it requires. + typename GaussNewtonMinimizer::Result + operator()(int numPoints, Vector<3, Real> const* points, + size_t maxIterations, Real updateLengthTolerance, Real errorDifferenceTolerance, + bool useConeInputAsInitialGuess, + Vector<3, Real>& coneVertex, Vector<3, Real>& coneAxis, Real& coneAngle) + { + mNumPoints = numPoints; + mPoints = points; + GaussNewtonMinimizer minimizer(6, mNumPoints, mFFunction, mJFunction); + + Real coneCosAngle; + if (useConeInputAsInitialGuess) + { + Normalize(coneAxis); + coneCosAngle = std::cos(coneAngle); + } + else + { + ComputeInitialCone(coneVertex, coneAxis, coneCosAngle); + } + + // The initial guess for the cone vertex. + GVector initial(6); + initial[0] = coneVertex[0]; + initial[1] = coneVertex[1]; + initial[2] = coneVertex[2]; + + // The initial guess for the weighted cone axis. + initial[3] = coneAxis[0] / coneCosAngle; + initial[4] = coneAxis[1] / coneCosAngle; + initial[5] = coneAxis[2] / coneCosAngle; + + auto result = minimizer(initial, maxIterations, updateLengthTolerance, + errorDifferenceTolerance); + + // No test is made for result.converged so that we return some + // estimates of the cone. The caller can decide how to respond + // when result.converged is false. + for (int i = 0; i < 3; ++i) + { + coneVertex[i] = result.minLocation[i]; + coneAxis[i] = result.minLocation[i + 3]; + } + + // We know that coneCosAngle will be nonnegative. The std::min + // call guards against rounding errors leading to a number + // slightly larger than 1. The clamping ensures std::acos will + // not return a NaN. + coneCosAngle = std::min((Real)1 / Normalize(coneAxis), (Real)1); + coneAngle = std::acos(coneCosAngle); + + mNumPoints = 0; + mPoints = nullptr; + return result; + } + + // The parameters coneVertex, coneAxis and coneAngle are in/out + // variables. The caller must provide initial guesses for these. + // The function estimates the cone parameters and returns them. See + // GteGaussNewtonMinimizer.h for a description of the least-squares + // algorithm and the parameters that it requires. + typename LevenbergMarquardtMinimizer::Result + operator()(int numPoints, Vector<3, Real> const* points, + size_t maxIterations, Real updateLengthTolerance, Real errorDifferenceTolerance, + Real lambdaFactor, Real lambdaAdjust, size_t maxAdjustments, + bool useConeInputAsInitialGuess, + Vector<3, Real>& coneVertex, Vector<3, Real>& coneAxis, Real& coneAngle) + { + mNumPoints = numPoints; + mPoints = points; + LevenbergMarquardtMinimizer minimizer(6, mNumPoints, mFFunction, mJFunction); + + Real coneCosAngle; + if (useConeInputAsInitialGuess) + { + Normalize(coneAxis); + coneCosAngle = std::cos(coneAngle); + } + else + { + ComputeInitialCone(coneVertex, coneAxis, coneCosAngle); + } + + // The initial guess for the cone vertex. + GVector initial(6); + initial[0] = coneVertex[0]; + initial[1] = coneVertex[1]; + initial[2] = coneVertex[2]; + + // The initial guess for the weighted cone axis. + initial[3] = coneAxis[0] / coneCosAngle; + initial[4] = coneAxis[1] / coneCosAngle; + initial[5] = coneAxis[2] / coneCosAngle; + + auto result = minimizer(initial, maxIterations, updateLengthTolerance, + errorDifferenceTolerance, lambdaFactor, lambdaAdjust, maxAdjustments); + + // No test is made for result.converged so that we return some + // estimates of the cone. The caller can decide how to respond + // when result.converged is false. + for (int i = 0; i < 3; ++i) + { + coneVertex[i] = result.minLocation[i]; + coneAxis[i] = result.minLocation[i + 3]; + } + + // We know that coneCosAngle will be nonnegative. The std::min + // call guards against rounding errors leading to a number + // slightly larger than 1. The clamping ensures std::acos will + // not return a NaN. + coneCosAngle = std::min((Real)1 / Normalize(coneAxis), (Real)1); + coneAngle = std::acos(coneCosAngle); + + mNumPoints = 0; + mPoints = nullptr; + return result; + } + + private: + void ComputeInitialCone(Vector<3, Real>& coneVertex, Vector<3, Real>& coneAxis, Real& coneCosAngle) + { + // Compute the average of the sample points. + Vector<3, Real> center{ (Real)0, (Real)0, (Real)0 }; + Real const invNumPoints = (Real)1 / (Real)mNumPoints; + for (int i = 0; i < mNumPoints; ++i) + { + center += mPoints[i]; + } + center *= invNumPoints; + + // The cone axis is estimated from ZZTZ (see the PDF). + coneAxis = { (Real)0, (Real)0, (Real)0 }; + for (int i = 0; i < mNumPoints; ++i) + { + Vector<3, Real> diff = mPoints[i] - center; + coneAxis += Dot(diff, diff) * diff; + } + coneAxis *= invNumPoints; + Normalize(coneAxis); + + // Compute the averages of powers and products of powers of + // a[i] = Dot(U,X[i]-C) and b[i] = Dot(X[i]-C,X[i]-C). + Real c10 = (Real)0, c20 = (Real)0, c30 = (Real)0, c01 = (Real)0; + Real c02 = (Real)0, c11 = (Real)0, c21 = (Real)0; + for (int i = 0; i < mNumPoints; ++i) + { + Vector<3, Real> diff = mPoints[i] - center; + Real ai = Dot(coneAxis, diff); + Real aisqr = ai * ai; + Real bi = Dot(diff, diff); + c10 += ai; + c20 += aisqr; + c30 += aisqr * ai; + c01 += bi; + c02 += bi * bi; + c11 += ai * bi; + c21 += aisqr * bi; + } + c10 *= invNumPoints; + c20 *= invNumPoints; + c30 *= invNumPoints; + c01 *= invNumPoints; + c02 *= invNumPoints; + c11 *= invNumPoints; + c21 *= invNumPoints; + + // Compute the coefficients of p3(t) and q3(t). + Real e0 = (Real)3 * c10; + Real e1 = (Real)2 * c20 + c01; + Real e2 = c11; + Real e3 = (Real)3 * c20; + Real e4 = c30; + + // Compute the coefficients of g(t). + Real g0 = c11 * c21 - c02 * c30; + Real g1 = c01 * c21 - (Real)3 * c02 * c20 + (Real)2 * (c20 * c21 - c11 * (c30 - c11)); + Real g2 = (Real)3 * (c11 * (c01 - c20) + c10 * (c21 - c02)); + Real g3 = c21 - c02 + c01 * (c01 + c20) + (Real)2 * (c10 * (c30 - c11) - c20 * c20); + Real g4 = c30 - c11 + c10 * (c01 - c20); + + // Compute the roots of g(t) = 0. + std::map rmMap; + RootsPolynomial::SolveQuartic(g0, g1, g2, g3, g4, rmMap); + + // Locate the positive root t that leads to an s = cos(theta) + // in (0,1) and that has minimum least-squares error. In theory, + // there must always be such a root, but floating-point rounding + // errors might lead to no such root. The implementation returns + // the center as the estimate of V and pi/4 as the estimate of + // the angle (s = 1/2). + std::vector> info; + Real s, t; + for (auto const& element : rmMap) + { + t = element.first; + if (t > (Real)0) + { + Real p3 = e2 + t * (e1 + t * (e0 + t)); + if (p3 != (Real)0) + { + Real q3 = e4 + t * (e3 + t * (e0 + t)); + s = q3 / p3; + if ((Real)0 < s && s < (Real)1) + { + Real error(0); + for (int i = 0; i < mNumPoints; ++i) + { + Vector<3, Real> diff = mPoints[i] - center; + Real ai = Dot(coneAxis, diff); + Real bi = Dot(diff, diff); + Real tpai = t + ai; + Real Fi = s * (bi + t * ((Real)2 * ai + t)) - tpai * tpai; + error += Fi * Fi; + } + error *= invNumPoints; + std::array item = {{ s, t, error }}; + info.push_back(item); + } + } + } + } + + Real minError = std::numeric_limits::max(); + std::array minItem = {{ (Real)0, (Real)0, minError }}; + for (auto const& item : info) + { + if (item[2] < minError) + { + minItem = item; + } + } + + if (minItem[2] < std::numeric_limits::max()) + { + // minItem = { minS, minT, minError } + coneVertex = center - minItem[1] * coneAxis; + coneCosAngle = std::sqrt(minItem[0]); + } + else + { + coneVertex = center; + coneCosAngle = (Real)GTE_C_INV_SQRT_2; + } + } + + int mNumPoints; + Vector<3, Real> const* mPoints; + std::function const&, GVector&)> mFFunction; + std::function const&, GMatrix&)> mJFunction; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCylinder3.h new file mode 100644 index 000000000000..35a8c3e131be --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCylinder3.h @@ -0,0 +1,462 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.3.5 (2018/10/26) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// The algorithm for least-squares fitting of a point set by a cylinder is +// described in +// http://www.geometrictools.com/Documentation/CylinderFitting.pdf +// This document shows how to compute the cylinder radius r and the cylinder +// axis as a line C+t*W with origin C, unit-length direction W, and any +// real-valued t. The implementation here adds one addition step. It +// projects the point set onto the cylinder axis, computes the bounding +// t-interval [tmin,tmax] for the projections, and sets the cylinder center +// to C + 0.5*(tmin+tmax)*W and the cylinder height to tmax-tmin. + +namespace gte +{ + template + class ApprCylinder3 + { + public: + // Search the hemisphere for a minimum, choose numThetaSamples and + // numPhiSamples to be positive (and preferably large). These are used + // to generate a hemispherical grid of samples to be evaluated to find + // the cylinder axis-direction W. If the grid samples is quite large + // and the number of points to be fitted is large, you most likely will + // want to run multithreaded. Set numThreads to 0 to run single-threaded + // in the main process. Set numThreads > 0 to run multithreaded. If + // either of numThetaSamples or numPhiSamples is zero, the operator() sets + // the cylinder origin and axis to the zero vectors, the radius and height + // to zero, and returns std::numeric_limits::max(). + ApprCylinder3(unsigned int numThreads, unsigned int numThetaSamples, unsigned int numPhiSamples) + : + mConstructorType(FIT_BY_HEMISPHERE_SEARCH), + mNumThreads(numThreads), + mNumThetaSamples(numThetaSamples), + mNumPhiSamples(numPhiSamples), + mEigenIndex(0), + mInvNumPoints((Real)0) + { + mCylinderAxis = { (Real)0, (Real)0, (Real)0 }; + } + + // Choose one of the eigenvectors for the covariance matrix as the + // cylinder axis direction. If eigenIndex is 0, the eigenvector + // associated with the smallest eigenvalue is chosen. If eigenIndex is 2, + // the eigenvector associated with the largest eigenvalue is chosen. If + // eigenIndex is 1, the eigenvector associated with the median eigenvalue + // is chosen; keep in mind that this could be the minimum or maximum + // eigenvalue if the eigenspace has dimension 2 or 3. If eigenIndex is + // 3 or larger, the operator() sets the cylinder origin and axis to the + // zero vectors, the radius and height to zero, and returns + // std::numeric_limits::max(). + ApprCylinder3(unsigned int eigenIndex) + : + mConstructorType(FIT_USING_COVARIANCE_EIGENVECTOR), + mNumThreads(0), + mNumThetaSamples(0), + mNumPhiSamples(0), + mEigenIndex(eigenIndex), + mInvNumPoints((Real)0) + { + mCylinderAxis = { (Real)0, (Real)0, (Real)0 }; + } + + // Choose the cylinder axis. If cylinderAxis is not the zero vector, + // the constructor will normalize it. If cylinderAxis is the zero vector, + // the operator() sets the cylinder origin and axis to the zero vectors, + // the radius and height to zero, and returns + // std::numeric_limits::max(). + ApprCylinder3(Vector3 const& cylinderAxis) + : + mConstructorType(FIT_USING_SPECIFIED_AXIS), + mNumThreads(0), + mNumThetaSamples(0), + mNumPhiSamples(0), + mEigenIndex(0), + mCylinderAxis(cylinderAxis), + mInvNumPoints((Real)0) + { + Normalize(mCylinderAxis, true); + } + + // The algorithm must estimate 6 parameters, so the number of points must + // be at least 6 but preferably larger. The returned value is the + // root-mean-square of the least-squares error. If numPoints is less than + // 6 or if points is a null pointer, the operator() sets the cylinder origin + // and axis to the zero vectors, the radius and height to zero, and returns + // std::numeric_limits::max(). + Real operator()(unsigned int numPoints, Vector3 const* points, Cylinder3& cylinder) + { + mX.clear(); + mInvNumPoints = (Real)0; + cylinder.axis.origin = Vector3::Zero(); + cylinder.axis.direction = Vector3::Zero(); + cylinder.radius = (Real)0; + cylinder.height = (Real)0; + + // Validate the input parameters. + if (numPoints < 6 || !points) + { + return std::numeric_limits::max(); + } + + Vector3 average; + Preprocess(numPoints, points, average); + + // Fit the points based on which constructor the caller used. The + // direction is either estimated or selected directly or indirectly + // by the caller. The center and squared radius are estimated. + Vector3 minPC, minW; + Real minRSqr, minError; + + if (mConstructorType == FIT_BY_HEMISPHERE_SEARCH) + { + // Validate the relevant internal parameters. + if (mNumThetaSamples == 0 || mNumPhiSamples == 0) + { + return std::numeric_limits::max(); + } + + // Search the hemisphere for the vector that leads to minimum error + // and use it for the cylinder axis. + if (mNumThreads == 0) + { + // Execute the algorithm in the main process. + minError = ComputeSingleThreaded(minPC, minW, minRSqr); + } + else + { + // Execute the algorithm in multiple threads. + minError = ComputeMultiThreaded(minPC, minW, minRSqr); + } + } + else if (mConstructorType == FIT_USING_COVARIANCE_EIGENVECTOR) + { + // Validate the relevant internal parameters. + if (mEigenIndex >= 3) + { + return std::numeric_limits::max(); + } + + // Use the eigenvector corresponding to the mEigenIndex of the + // eigenvectors of the covariance matrix as the cylinder axis + // direction. The eigenvectors are sorted from smallest + // eigenvalue (mEigenIndex = 0) to largest eigenvalue + // (mEigenIndex = 2). + minError = ComputeUsingCovariance(minPC, minW, minRSqr); + } + else // mConstructorType == FIT_USING_SPECIFIED_AXIS + { + // Validate the relevant internal parameters. + if (mCylinderAxis == Vector3::Zero()) + { + return std::numeric_limits::max(); + } + + minError = ComputeUsingDirection(minPC, minW, minRSqr); + } + + // Translate back to the original space by the average of the points. + cylinder.axis.origin = minPC + average; + cylinder.axis.direction = minW; + + // Compute the cylinder radius. + cylinder.radius = std::sqrt(minRSqr); + + // Project the points onto the cylinder axis and choose the cylinder + // center and cylinder height as described in the comments at the top of + // this header file. + Real tmin = (Real)0, tmax = (Real)0; + for (unsigned int i = 0; i < numPoints; ++i) + { + Real t = Dot(cylinder.axis.direction, points[i] - cylinder.axis.origin); + tmin = std::min(t, tmin); + tmax = std::max(t, tmax); + } + + cylinder.axis.origin += ((tmin + tmax) * (Real)0.5) * cylinder.axis.direction; + cylinder.height = tmax - tmin; + return minError; + } + + private: + enum ConstructorType + { + FIT_BY_HEMISPHERE_SEARCH, + FIT_USING_COVARIANCE_EIGENVECTOR, + FIT_USING_SPECIFIED_AXIS + }; + + void Preprocess(unsigned int numPoints, Vector3 const* points, Vector3& average) + { + mX.resize(numPoints); + mInvNumPoints = (Real)1 / (Real)numPoints; + + // Copy the points and translate by the average for numerical robustness. + average.MakeZero(); + for (unsigned int i = 0; i < numPoints; ++i) + { + average += points[i]; + } + average *= mInvNumPoints; + for (unsigned int i = 0; i < numPoints; ++i) + { + mX[i] = points[i] - average; + } + + Vector<6, Real> zero{ (Real)0 }; + std::vector> products(mX.size(), zero); + mMu = zero; + for (size_t i = 0; i < mX.size(); ++i) + { + products[i][0] = mX[i][0] * mX[i][0]; + products[i][1] = mX[i][0] * mX[i][1]; + products[i][2] = mX[i][0] * mX[i][2]; + products[i][3] = mX[i][1] * mX[i][1]; + products[i][4] = mX[i][1] * mX[i][2]; + products[i][5] = mX[i][2] * mX[i][2]; + mMu[0] += products[i][0]; + mMu[1] += (Real)2 * products[i][1]; + mMu[2] += (Real)2 * products[i][2]; + mMu[3] += products[i][3]; + mMu[4] += (Real)2 * products[i][4]; + mMu[5] += products[i][5]; + } + mMu *= mInvNumPoints; + + mF0.MakeZero(); + mF1.MakeZero(); + mF2.MakeZero(); + for (size_t i = 0; i < mX.size(); ++i) + { + Vector<6, Real> delta; + delta[0] = products[i][0] - mMu[0]; + delta[1] = (Real)2 * products[i][1] - mMu[1]; + delta[2] = (Real)2 * products[i][2] - mMu[2]; + delta[3] = products[i][3] - mMu[3]; + delta[4] = (Real)2 * products[i][4] - mMu[4]; + delta[5] = products[i][5] - mMu[5]; + mF0(0, 0) += products[i][0]; + mF0(0, 1) += products[i][1]; + mF0(0, 2) += products[i][2]; + mF0(1, 1) += products[i][3]; + mF0(1, 2) += products[i][4]; + mF0(2, 2) += products[i][5]; + mF1 += OuterProduct(mX[i], delta); + mF2 += OuterProduct(delta, delta); + } + mF0 *= mInvNumPoints; + mF0(1, 0) = mF0(0, 1); + mF0(2, 0) = mF0(0, 2); + mF0(2, 1) = mF0(1, 2); + mF1 *= mInvNumPoints; + mF2 *= mInvNumPoints; + } + + Real ComputeUsingDirection(Vector3& minPC, Vector3& minW, Real& minRSqr) + { + minW = mCylinderAxis; + return G(minW, minPC, minRSqr); + } + + Real ComputeUsingCovariance(Vector3& minPC, Vector3& minW, Real& minRSqr) + { + Matrix3x3 covar = Matrix3x3::Zero(); + for (auto const& X : mX) + { + covar += OuterProduct(X, X); + } + covar *= mInvNumPoints; + std::array eval; + std::array, 3> evec; + SymmetricEigensolver3x3()( + covar(0, 0), covar(0, 1), covar(0, 2), covar(1, 1), covar(1, 2), covar(2, 2), + true, +1, eval, evec); + minW = evec[mEigenIndex]; + return G(minW, minPC, minRSqr); + } + + Real ComputeSingleThreaded(Vector3& minPC, Vector3& minW, Real& minRSqr) + { + Real const iMultiplier = (Real)GTE_C_TWO_PI / (Real)mNumThetaSamples; + Real const jMultiplier = (Real)GTE_C_HALF_PI / (Real)mNumPhiSamples; + + // Handle the north pole (0,0,1) separately. + minW = { (Real)0, (Real)0, (Real)1 }; + Real minError = G(minW, minPC, minRSqr); + + for (unsigned int j = 1; j <= mNumPhiSamples; ++j) + { + Real phi = jMultiplier * static_cast(j); // in [0,pi/2] + Real csphi = std::cos(phi); + Real snphi = std::sin(phi); + for (unsigned int i = 0; i < mNumThetaSamples; ++i) + { + Real theta = iMultiplier * static_cast(i); // in [0,2*pi) + Real cstheta = std::cos(theta); + Real sntheta = std::sin(theta); + Vector3 W{ cstheta * snphi, sntheta * snphi, csphi }; + Vector3 PC; + Real rsqr; + Real error = G(W, PC, rsqr); + if (error < minError) + { + minError = error; + minRSqr = rsqr; + minW = W; + minPC = PC; + } + } + } + + return minError; + } + + Real ComputeMultiThreaded(Vector3& minPC, Vector3& minW, Real& minRSqr) + { + Real const iMultiplier = (Real)GTE_C_TWO_PI / (Real)mNumThetaSamples; + Real const jMultiplier = (Real)GTE_C_HALF_PI / (Real)mNumPhiSamples; + + // Handle the north pole (0,0,1) separately. + minW = { (Real)0, (Real)0, (Real)1 }; + Real minError = G(minW, minPC, minRSqr); + + struct Local + { + Real error; + Real rsqr; + Vector3 W; + Vector3 PC; + unsigned int jmin; + unsigned int jmax; + }; + + std::vector local(mNumThreads); + unsigned int numPhiSamplesPerThread = mNumPhiSamples / mNumThreads; + for (unsigned int t = 0; t < mNumThreads; ++t) + { + local[t].error = std::numeric_limits::max(); + local[t].rsqr = (Real)0; + local[t].W = Vector3::Zero(); + local[t].PC = Vector3::Zero(); + local[t].jmin = numPhiSamplesPerThread * t; + local[t].jmax = numPhiSamplesPerThread * (t + 1); + } + local[mNumThreads - 1].jmax = mNumPhiSamples + 1; + + std::vector process(mNumThreads); + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t] = std::thread + ( + [this, t, iMultiplier, jMultiplier, &local]() + { + for (unsigned int j = local[t].jmin; j < local[t].jmax; ++j) + { + Real phi = jMultiplier * static_cast(j); // in [0,pi/2] + Real csphi = std::cos(phi); + Real snphi = std::sin(phi); + for (unsigned int i = 0; i < mNumThetaSamples; ++i) + { + Real theta = iMultiplier * static_cast(i); // in [0,2*pi) + Real cstheta = std::cos(theta); + Real sntheta = std::sin(theta); + Vector3 W{ cstheta * snphi, sntheta * snphi, csphi }; + Vector3 PC; + Real rsqr; + Real error = G(W, PC, rsqr); + if (error < local[t].error) + { + local[t].error = error; + local[t].rsqr = rsqr; + local[t].W = W; + local[t].PC = PC; + } + } + } + } + ); + } + + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t].join(); + + if (local[t].error < minError) + { + minError = local[t].error; + minRSqr = local[t].rsqr; + minW = local[t].W; + minPC = local[t].PC; + } + } + + return minError; + } + + Real G(Vector3 const& W, Vector3& PC, Real& rsqr) + { + Matrix3x3 P = Matrix3x3::Identity() - OuterProduct(W, W); + Matrix3x3 S + { + (Real)0, -W[2], W[1], + W[2], (Real)0, -W[0], + -W[1], W[0], (Real)0 + }; + + Matrix<3, 3, Real> A = P * mF0 * P; + Matrix<3, 3, Real> hatA = -(S * A * S); + Matrix<3, 3, Real> hatAA = hatA * A; + Real trace = Trace(hatAA); + Matrix<3, 3, Real> Q = hatA / trace; + Vector<6, Real> pVec{ P(0, 0), P(0, 1), P(0, 2), P(1, 1), P(1, 2), P(2, 2) }; + Vector<3, Real> alpha = mF1 * pVec; + Vector<3, Real> beta = Q * alpha; + Real G = (Dot(pVec, mF2 * pVec) - (Real)4 * Dot(alpha, beta) + (Real)4 * Dot(beta, mF0 * beta)) / (Real)mX.size(); + + PC = beta; + rsqr = Dot(pVec, mMu) + Dot(PC, PC); + return G; + } + + ConstructorType mConstructorType; + + // Parameters for the hemisphere-search constructor. + unsigned int mNumThreads; + unsigned int mNumThetaSamples; + unsigned int mNumPhiSamples; + + // Parameters for the eigenvector-index constructor. + unsigned int mEigenIndex; + + // Parameters for the specified-axis constructor. + Vector3 mCylinderAxis; + + // A copy of the input points but translated by their average for + // numerical robustness. + std::vector> mX; + Real mInvNumPoints; + + // Preprocessed information that depends only on the sample points. + // This allows precomputed summations so that G(...) can be evaluated + // extremely fast. + Vector<6, Real> mMu; + Matrix<3, 3, Real> mF0; + Matrix<3, 6, Real> mF1; + Matrix<6, 6, Real> mF2; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipse2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipse2.h new file mode 100644 index 000000000000..ab4c158751eb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipse2.h @@ -0,0 +1,144 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +// The ellipse in general form is X^t A X + B^t X + C = 0 where A is a +// positive definite 2x2 matrix, B is a 2x1 vector, C is a scalar, and X is +// a 2x1 vector X. Completing the square, (X-U)^t A (X-U) = U^t A U - C +// where U = -0.5 A^{-1} B. Define M = A/(U^t A U - C). The ellipse is +// (X-U)^t M (X-U) = 1. Factor M = R^t D R where R is orthonormal and D is +// diagonal with positive diagonal terms. The ellipse in factored form is +// (X-U)^t R^t D^t R (X-U) = 1. +// +// Find the least squares fit of a set of N points P[0] through P[N-1]. +// The return value is the least-squares energy function at (U,R,D). + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class ApprEllipse2 +{ +public: + Real operator()(int numPoints, Vector2 const* points, + Vector2& center, Matrix2x2& rotate, Real diagonal[2]); + +private: + static Real Energy(int numPoints, Vector2 const* points, + Real const* input); +}; + + +template +Real ApprEllipse2::operator()(int numPoints, + Vector2 const* points, Vector2& center, + Matrix2x2& rotate, Real diagonal[2]) +{ + // Energy function is E : R^5 -> R where + // V = (V0, V1, V2, V3, V4) + // = (D[0], D[1], U.x, U.y, atan2(R(0,1),R(0,0))). + std::function energy = + [numPoints, points](Real const* input) + { + return Energy(numPoints, points, input); + }; + + MinimizeN minimizer(5, energy, 8, 8, 32); + + // The initial guess for the minimizer is based on an oriented box that + // contains the points. + OrientedBox2 box; + GetContainer(numPoints, points, box); + center = box.center; + for (int i = 0; i < 2; ++i) + { + rotate.SetRow(i, box.axis[i]); + diagonal[i] = box.extent[i]; + } + + Real angle = std::atan2(rotate(0, 1), rotate(0, 0)); + Real e0 = + diagonal[0] * std::abs(rotate(0, 0)) + + diagonal[1] * std::abs(rotate(1, 0)); + Real e1 = + diagonal[0] * std::abs(rotate(0, 1)) + + diagonal[1] * std::abs(rotate(1, 1)); + + Real v0[5] = + { + ((Real)0.5)*diagonal[0], + ((Real)0.5)*diagonal[1], + center[0] - e0, + center[1] - e1, + -(Real)GTE_C_PI + }; + + Real v1[5] = + { + ((Real)2)*diagonal[0], + ((Real)2)*diagonal[1], + center[0] + e0, + center[1] + e1, + (Real)GTE_C_PI + }; + + Real vInitial[5] = + { + diagonal[0], + diagonal[1], + center[0], + center[1], + angle + }; + + Real vMin[5], error; + minimizer.GetMinimum(v0, v1, vInitial, vMin, error); + + diagonal[0] = vMin[0]; + diagonal[1] = vMin[1]; + center[0] = vMin[2]; + center[1] = vMin[3]; + MakeRotation(-vMin[4], rotate); + + return error; +} + +template +Real ApprEllipse2::Energy(int numPoints, Vector2 const* points, + Real const* input) +{ + // Build rotation matrix. + Matrix2x2 rotate; + MakeRotation(-input[4], rotate); + + Ellipse2 ellipse(Vector2::Zero(), { Vector2::Unit(0), + Vector2::Unit(1) }, { input[0], input[1] }); + + // Transform the points to the coordinate system of center C and + // columns of rotation R. + DCPQuery, Ellipse2> peQuery; + Real energy = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - Vector2{ input[2], input[3] }; + Vector2 prod = rotate * diff; + Real dist = peQuery(prod, ellipse).distance; + energy += dist; + } + + return energy; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipseByArcs.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipseByArcs.h new file mode 100644 index 000000000000..89354ad7e313 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipseByArcs.h @@ -0,0 +1,127 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +// The ellipse is (x/a)^2 + (y/b)^2 = 1, but only the portion in the first +// quadrant (x >= 0 and y >= 0) is approximated. Generate numArcs >= 2 arcs +// by constructing points corresponding to the weighted averages of the +// curvatures at the ellipse points (a,0) and (0,b). The returned input point +// array has numArcs+1 elements and the returned input center and radius +// arrays each have numArc elements. The arc associated with points[i] and +// points[i+1] has center centers[i] and radius radii[i]. The algorithm +// is described in +// http://www.geometrictools.com/Documentation/ApproximateEllipse.pdf +// +// The function returns 'true' when the approximation succeeded, in which +// case the output arrays are nonempty. If the 'numArcs' is smaller than +// 2 or a == b or one of the calls to Circumscribe fails, the function +// returns 'false'. + +template +bool ApproximateEllipseByArcs(Real a, Real b, int numArcs, + std::vector>& points, std::vector>& centers, + std::vector& radii); + + +template +bool ApproximateEllipseByArcs(Real a, Real b, int numArcs, + std::vector>& points, std::vector>& centers, + std::vector& radii) +{ + if (numArcs < 2 || a == b) + { + // At least 2 arcs are required. The ellipse cannot already be a + // circle. + points.clear(); + centers.clear(); + radii.clear(); + return false; + } + + points.resize(numArcs + 1); + centers.resize(numArcs); + radii.resize(numArcs); + + // Compute intermediate ellipse quantities. + Real a2 = a * a, b2 = b * b, ab = a * b; + Real invB2mA2 = ((Real)1) / (b2 - a2); + + // Compute the endpoints of the ellipse in the first quadrant. The + // points are generated in counterclockwise order. + points[0] = { a, (Real)0 }; + points[numArcs] = { (Real)0, b }; + + // Compute the curvature at the endpoints. These are used when computing + // the arcs. + Real curv0 = a / b2; + Real curv1 = b / a2; + + // Select the ellipse points based on curvature properties. + Real invNumArcs = ((Real)1) / numArcs; + for (int i = 1; i < numArcs; ++i) + { + // The curvature at a new point is a weighted average of curvature + // at the endpoints. + Real weight1 = static_cast(i) * invNumArcs; + Real weight0 = (Real)1 - weight1; + Real curv = weight0 * curv0 + weight1 * curv1; + + // Compute point having this curvature. + Real tmp = std::pow(ab / curv, (Real)2 / (Real)3); + points[i][0] = a * std::sqrt(std::abs((tmp - a2) * invB2mA2)); + points[i][1] = b * std::sqrt(std::abs((tmp - b2) * invB2mA2)); + } + + // Compute the arc at (a,0). + Circle2 circle; + Vector2 const& p0 = points[0]; + Vector2 const& p1 = points[1]; + if (!Circumscribe(Vector2{ p1[0], -p1[1] }, p0, p1, circle)) + { + // This should not happen for the arc-fitting algorithm. + points.clear(); + centers.clear(); + radii.clear(); + return false; + } + centers[0] = circle.center; + radii[0] = circle.radius; + + // Compute arc at (0,b). + int last = numArcs - 1; + Vector2 const& pNm1 = points[last]; + Vector2 const& pN = points[numArcs]; + if (!Circumscribe(Vector2{ -pNm1[0], pNm1[1] }, pN, pNm1, circle)) + { + // This should not happen for the arc-fitting algorithm. + points.clear(); + centers.clear(); + radii.clear(); + return false; + } + + centers[last] = circle.center; + radii[last] = circle.radius; + + // Compute arcs at intermediate points between (a,0) and (0,b). + for (int iM = 0, i = 1, iP = 2; i < last; ++iM, ++i, ++iP) + { + Circumscribe(points[iM], points[i], points[iP], circle); + centers[i] = circle.center; + radii[i] = circle.radius; + } + return true; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipsoid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipsoid3.h new file mode 100644 index 000000000000..ec4919b86fa6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipsoid3.h @@ -0,0 +1,227 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +// The ellipsoid in general form is X^t A X + B^t X + C = 0 where +// A is a positive definite 3x3 matrix, B is a 3x1 vector, C is a +// scalar, and X is a 3x1 vector. Completing the square, +// (X-U)^t A (X-U) = U^t A U - C where U = -0.5 A^{-1} B. Define +// M = A/(U^t A U - C). The ellipsoid is (X-U)^t M (X-U) = 1. Factor +// M = R^t D R where R is orthonormal and D is diagonal with positive +// diagonal terms. The ellipsoid in factored form is +// (X-U)^t R^t D^t R (X-U) = 1 +// +// Find the least squares fit of a set of N points P[0] through P[N-1]. +// The error return value is the least-squares energy function at (U,R,D). + +#include +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class ApprEllipsoid3 +{ +public: + Real operator()(int numPoints, Vector3 const* points, + Vector3& center, Matrix3x3& rotate, Real diagonal[3]); + +private: + static void MatrixToAngles(Matrix3x3 const& rotate, Real angle[3]); + static void AnglesToMatrix(Real const angle[3], Matrix3x3& rotate); + static Real Energy(int numPoints, Vector3 const* points, + Real const* input); +}; + + +template +Real ApprEllipsoid3::operator()(int numPoints, + Vector3 const* points, Vector3& center, + Matrix3x3& rotate, Real diagonal[3]) +{ + // Energy function is E : R^9 -> R where + // V = (V0,V1,V2,V3,V4,V5,V6,V7,V8) + // = (D[0],D[1],D[2],U[0],U,y,U[2],A0,A1,A2). + std::function energy = + [numPoints, points](Real const* input) + { + return Energy(numPoints, points, input); + }; + + MinimizeN minimizer(9, energy, 8, 8, 32); + + // The initial guess for the minimizer is based on an oriented box that + // contains the points. + OrientedBox3 box; + GetContainer(numPoints, points, box); + center = box.center; + for (int i = 0; i < 3; ++i) + { + rotate.SetRow(i, box.axis[i]); + diagonal[i] = box.extent[i]; + } + + Real angle[3]; + MatrixToAngles(rotate, angle); + + Real extent[3] = + { + diagonal[0] * std::abs(rotate(0, 0)) + + diagonal[1] * std::abs(rotate(0, 1)) + + diagonal[2] * std::abs(rotate(0, 2)), + + diagonal[0] * std::abs(rotate(1, 0)) + + diagonal[1] * std::abs(rotate(1, 1)) + + diagonal[2] * std::abs(rotate(1, 2)), + + diagonal[0] * std::abs(rotate(2, 0)) + + diagonal[1] * std::abs(rotate(2, 1)) + + diagonal[2] * std::abs(rotate(2, 2)) + }; + + Real v0[9] = + { + ((Real)0.5)*diagonal[0], + ((Real)0.5)*diagonal[1], + ((Real)0.5)*diagonal[2], + center[0] - extent[0], + center[1] - extent[1], + center[2] - extent[2], + -(Real)GTE_C_PI, + (Real)0, + (Real)0 + }; + + Real v1[9] = + { + ((Real)2)*diagonal[0], + ((Real)2)*diagonal[1], + ((Real)2)*diagonal[2], + center[0] + extent[0], + center[1] + extent[1], + center[2] + extent[2], + (Real)GTE_C_PI, + (Real)GTE_C_PI, + (Real)GTE_C_PI + }; + + Real vInitial[9] = + { + diagonal[0], + diagonal[1], + diagonal[2], + center[0], + center[1], + center[2], + angle[0], + angle[1], + angle[2] + }; + + Real vMin[9], error; + minimizer.GetMinimum(v0, v1, vInitial, vMin, error); + + diagonal[0] = vMin[0]; + diagonal[1] = vMin[1]; + diagonal[2] = vMin[2]; + center[0] = vMin[3]; + center[1] = vMin[4]; + center[2] = vMin[5]; + AnglesToMatrix(&vMin[6], rotate); + + return error; +} + +template +void ApprEllipsoid3::MatrixToAngles(Matrix3x3 const& rotate, + Real angle[3]) +{ + // rotation axis = (cos(a0)sin(a1),sin(a0)sin(a1),cos(a1)) + // a0 in [-pi,pi], a1 in [0,pi], a2 in [0,pi] + + Real const zero = (Real)0; + Real const one = (Real)1; + AxisAngle<3, Real> aa = Rotation<3, Real>(rotate); + + if (-one < aa.axis[2]) + { + if (aa.axis[2] < one) + { + angle[0] = std::atan2(aa.axis[1], aa.axis[0]); + angle[1] = std::acos(aa.axis[2]); + } + else + { + angle[0] = zero; + angle[1] = zero; + } + } + else + { + angle[0] = zero; + angle[1] = (Real)GTE_C_PI; + } +} + +template +void ApprEllipsoid3::AnglesToMatrix(Real const angle[3], + Matrix3x3& rotate) +{ + // rotation axis = (cos(a0)sin(a1),sin(a0)sin(a1),cos(a1)) + // a0 in [-pi,pi], a1 in [0,pi], a2 in [0,pi] + + Real cs0 = std::cos(angle[0]); + Real sn0 = std::sin(angle[0]); + Real cs1 = std::cos(angle[1]); + Real sn1 = std::sin(angle[1]); + AxisAngle<3, Real> aa; + aa.axis = { cs0*sn1, sn0*sn1, cs1 }; + aa.angle = angle[2]; + rotate = Rotation<3, Real>(aa); +} + +template +Real ApprEllipsoid3::Energy(int numPoints, Vector3 const* points, + Real const* input) +{ + // Build rotation matrix. + Matrix3x3 rotate; + AnglesToMatrix(&input[6], rotate); + + // Uniformly scale the extents to keep reasonable floating point values + // in the distance calculations. + Real maxValue = std::max(std::max(input[0], input[1]), input[2]); + Real invMax = ((Real)1) / maxValue; + Ellipsoid3 ellipsoid(Vector3::Zero(), { Vector3::Unit(0), + Vector3::Unit(1), Vector3::Unit(2) }, { invMax*input[0], + invMax*input[1], invMax*input[2] }); + + // Transform the points to the coordinate system of center C and columns + // of rotation R. + DCPQuery, Ellipsoid3> peQuery; + Real energy = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - + Vector3{ input[3], input[4], input[5] }; + + Vector3 prod = invMax * (diff * rotate); + Real dist = peQuery(prod, ellipsoid).distance; + energy += maxValue * dist; + } + + return energy; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGaussian2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGaussian2.h new file mode 100644 index 000000000000..b3299800c8f9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGaussian2.h @@ -0,0 +1,181 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Fit points with a Gaussian distribution. The center is the mean of the +// points, the axes are the eigenvectors of the covariance matrix, and the +// extents are the eigenvalues of the covariance matrix and are returned in +// increasing order. An oriented box is used to store the mean, axes, and +// extents. + +namespace gte +{ + +template +class ApprGaussian2 + : + public ApprQuery, Vector2> +{ +public: + // Initialize the model parameters to zero. + ApprGaussian2(); + + // Basic fitting algorithm. + bool Fit(int numPoints, Vector2 const* points); + OrientedBox2 const& GetParameters() const; + + // Functions called by ApprQuery::RANSAC. See GteApprQuery.h for a + // detailed description. + int GetMinimumRequired() const; + Real Error(Vector2 const& observation) const; + bool Fit(std::vector> const& observations, + std::vector const& indices); + +private: + OrientedBox2 mParameters; +}; + + +template +ApprGaussian2::ApprGaussian2() +{ + mParameters.center = Vector2::Zero(); + mParameters.axis[0] = Vector2::Zero(); + mParameters.axis[1] = Vector2::Zero(); + mParameters.extent = Vector2::Zero(); +} + +template +bool ApprGaussian2::Fit(int numPoints, Vector2 const* points) +{ + if (numPoints >= GetMinimumRequired() && points) + { + // Compute the mean of the points. + Vector2 mean = Vector2::Zero(); + for (int i = 0; i < numPoints; ++i) + { + mean += points[i]; + } + Real invSize = ((Real)1) / (Real)numPoints; + mean *= invSize; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar11 = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar11 += diff[1] * diff[1]; + } + covar00 *= invSize; + covar01 *= invSize; + covar11 *= invSize; + + // Solve the eigensystem. + SymmetricEigensolver2x2 es; + std::array eval; + std::array, 2> evec; + es(covar00, covar01, covar11, +1, eval, evec); + mParameters.center = mean; + mParameters.axis[0] = evec[0]; + mParameters.axis[1] = evec[1]; + mParameters.extent = eval; + return true; + } + + mParameters.center = Vector2::Zero(); + mParameters.axis[0] = Vector2::Zero(); + mParameters.axis[1] = Vector2::Zero(); + mParameters.extent = Vector2::Zero(); + return false; +} + +template +OrientedBox2 const& ApprGaussian2::GetParameters() const +{ + return mParameters; +} + +template +int ApprGaussian2::GetMinimumRequired() const +{ + return 2; +} + +template +bool ApprGaussian2::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (static_cast(indices.size()) >= GetMinimumRequired()) + { + // Compute the mean of the points. + Vector2 mean = Vector2::Zero(); + for (auto index : indices) + { + mean += observations[index]; + } + Real invSize = ((Real)1) / (Real)indices.size(); + mean *= invSize; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar11 = (Real)0; + for (auto index : indices) + { + Vector2 diff = observations[index] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar11 += diff[1] * diff[1]; + } + covar00 *= invSize; + covar01 *= invSize; + covar11 *= invSize; + + // Solve the eigensystem. + SymmetricEigensolver2x2 es; + std::array eval; + std::array, 2> evec; + es(covar00, covar01, covar11, +1, eval, evec); + mParameters.center = mean; + mParameters.axis[0] = evec[0]; + mParameters.axis[1] = evec[1]; + mParameters.extent = eval; + } + + mParameters.center = Vector2::Zero(); + mParameters.axis[0] = Vector2::Zero(); + mParameters.axis[1] = Vector2::Zero(); + mParameters.extent = Vector2::Zero(); + return false; +} + +template +Real ApprGaussian2::Error(Vector2 const& observation) const +{ + Vector2 diff = observation - mParameters.center; + Real error = (Real)0; + for (int i = 0; i < 2; ++i) + { + if (mParameters.extent[i] >(Real)0) + { + Real ratio = Dot(diff, mParameters.axis[i]) / + mParameters.extent[i]; + error += ratio * ratio; + } + } + return error; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGaussian3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGaussian3.h new file mode 100644 index 000000000000..3f00fba88256 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGaussian3.h @@ -0,0 +1,202 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Fit points with a Gaussian distribution. The center is the mean of the +// points, the axes are the eigenvectors of the covariance matrix, and the +// extents are the eigenvalues of the covariance matrix and are returned in +// increasing order. An oriented box is used to store the mean, axes, and +// extents. + +namespace gte +{ + +template +class ApprGaussian3 + : + public ApprQuery, Vector3> +{ +public: + // Initialize the model parameters to zero. + ApprGaussian3(); + + // Basic fitting algorithm. + bool Fit(int numPoints, Vector3 const* points); + OrientedBox3 const& GetParameters() const; + + // Functions called by ApprQuery::RANSAC. See GteApprQuery.h for a + // detailed description. + int GetMinimumRequired() const; + Real Error(Vector3 const& observation) const; + bool Fit(std::vector> const& observations, + std::vector const& indices); + +private: + OrientedBox3 mParameters; +}; + + +template +ApprGaussian3::ApprGaussian3() +{ + mParameters.center = Vector3::Zero(); + mParameters.axis[0] = Vector3::Zero(); + mParameters.axis[1] = Vector3::Zero(); + mParameters.axis[2] = Vector3::Zero(); + mParameters.extent = Vector3::Zero(); +} + +template +bool ApprGaussian3::Fit(int numPoints, Vector3 const* points) +{ + if (numPoints >= GetMinimumRequired() && points) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + for (int i = 0; i < numPoints; ++i) + { + mean += points[i]; + } + Real invSize = ((Real)1) / (Real)numPoints; + mean *= invSize; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + covar00 *= invSize; + covar01 *= invSize; + covar02 *= invSize; + covar11 *= invSize; + covar12 *= invSize; + covar22 *= invSize; + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, + eval, evec); + mParameters.center = mean; + mParameters.axis[0] = evec[0]; + mParameters.axis[1] = evec[1]; + mParameters.axis[2] = evec[2]; + mParameters.extent = eval; + return true; + } + + mParameters.center = Vector3::Zero(); + mParameters.axis[0] = Vector3::Zero(); + mParameters.axis[1] = Vector3::Zero(); + mParameters.axis[2] = Vector3::Zero(); + mParameters.extent = Vector3::Zero(); + return false; +} + +template +OrientedBox3 const& ApprGaussian3::GetParameters() const +{ + return mParameters; +} + +template +int ApprGaussian3::GetMinimumRequired() const +{ + return 2; +} + +template +Real ApprGaussian3::Error(Vector3 const& observation) const +{ + Vector3 diff = observation - mParameters.center; + Real error = (Real)0; + for (int i = 0; i < 3; ++i) + { + if (mParameters.extent[i] >(Real)0) + { + Real ratio = Dot(diff, mParameters.axis[i]) / + mParameters.extent[i]; + error += ratio * ratio; + } + } + return error; +} + +template +bool ApprGaussian3::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (static_cast(indices.size()) >= GetMinimumRequired()) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + for (auto index : indices) + { + mParameters.center += observations[index]; + } + Real invSize = ((Real)1) / (Real)indices.size(); + mean *= invSize; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (auto index : indices) + { + Vector3 diff = observations[index] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + covar00 *= invSize; + covar01 *= invSize; + covar02 *= invSize; + covar11 *= invSize; + covar12 *= invSize; + covar22 *= invSize; + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, + eval, evec); + mParameters.center = mean; + mParameters.axis[0] = evec[0]; + mParameters.axis[1] = evec[1]; + mParameters.axis[2] = evec[2]; + mParameters.extent = eval; + } + + mParameters.center = Vector3::Zero(); + mParameters.axis[0] = Vector3::Zero(); + mParameters.axis[1] = Vector3::Zero(); + mParameters.axis[2] = Vector3::Zero(); + mParameters.extent = Vector3::Zero(); + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGreatCircle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGreatCircle3.h new file mode 100644 index 000000000000..1f11fea318da --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGreatCircle3.h @@ -0,0 +1,141 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +// Least-squares fit of a great circle to unit-length vectors (x,y,z) by +// using distance measurements orthogonal (and measured along great circles) +// to the proposed great circle. The inputs akPoint[] are unit length. The +// returned value is unit length, call it N. The fitted great circle is +// defined by Dot(N,X) = 0, where X is a unit-length vector on the great +// circle. +template +class ApprGreatCircle3 +{ +public: + void operator()(int numPoints, Vector3 const* points, + Vector3& normal); +}; + + +// In addition to the least-squares fit of a great circle, the input vectors +// are projected onto that circle. The sector of smallest angle (possibly +// obtuse) that contains the points is computed. The endpoints of the arc +// of the sector are returned. The returned endpoints A0 and A1 are +// perpendicular to the returned normal N. Moreover, when you view the +// arc by looking at the plane of the great circle with a view direction +// of -N, the arc is traversed counterclockwise starting at A0 and ending +// at A1. +template +class ApprGreatArc3 +{ +public: + void operator()(int numPoints, Vector3 const* points, + Vector3& normal, Vector3& arcEnd0, + Vector3& arcEnd1); +}; + + +template +void ApprGreatCircle3::operator()(int numPoints, + Vector3 const* points, Vector3& normal) +{ + // Compute the covariance matrix of the vectors. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (int i = 0; i < numPoints; i++) + { + Vector3 diff = points[i]; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + + Real invNumPoints = ((Real)1) / static_cast(numPoints); + covar00 *= invNumPoints; + covar01 *= invNumPoints; + covar02 *= invNumPoints; + covar11 *= invNumPoints; + covar12 *= invNumPoints; + covar22 *= invNumPoints; + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, + eval, evec); + normal = evec[0]; +} + +template +void ApprGreatArc3::operator()(int numPoints, + Vector3 const* points, Vector3& normal, + Vector3& arcEnd0, Vector3& arcEnd1) +{ + // Get the least-squares great circle for the vectors. The circle is on + // the plane Dot(N,X) = 0. Generate a basis from N. + Vector3 basis[3]; // { N, U, V } + ApprGreatCircle3()(numPoints, points, basis[0]); + ComputeOrthogonalComplement(1, basis); + + // The vectors are X[i] = u[i]*U + v[i]*V + w[i]*N. The projections + // are P[i] = (u[i]*U + v[i]*V)/sqrt(u[i]*u[i] + v[i]*v[i]). The great + // circle is parameterized by C(t) = cos(t)*U + sin(t)*V. Compute the + // angles t in [-pi,pi] for the projections onto the great circle. It + // is not necesarily to normalize (u[i],v[i]), instead computing + // t = atan2(v[i],u[i]). + std::vector> items(numPoints); // item (u, v, angle) + for (int i = 0; i < numPoints; ++i) + { + items[i][0] = Dot(basis[1], points[i]); + items[i][1] = Dot(basis[2], points[i]); + items[i][2] = std::atan2(items[i][1], items[i][0]); + } + std::sort(items.begin(), items.end(), + [](std::array const& item0, std::array const& item1) + { + return item0[2] < item1[2]; + } + ); + + // Locate the pair of consecutive angles whose difference is a maximum. + // Effectively, we are constructing a cone of minimum angle that contains + // the unit-length vectors. + int numPointsM1 = numPoints - 1; + Real maxDiff = (Real)GTE_C_TWO_PI + items[0][2] - items[numPointsM1][2]; + int end0 = 0, end1 = numPointsM1; + for (int i0 = 0, i1 = 1; i0 < numPointsM1; i0 = i1++) + { + Real diff = items[i1][2] - items[i0][2]; + if (diff > maxDiff) + { + maxDiff = diff; + end0 = i1; + end1 = i0; + } + } + + normal = basis[0]; + arcEnd0 = items[end0][0] * basis[1] + items[end0][1] * basis[2]; + arcEnd1 = items[end1][0] * basis[1] + items[end1][1] * basis[2]; + Normalize(arcEnd0); + Normalize(arcEnd1); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprHeightLine2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprHeightLine2.h new file mode 100644 index 000000000000..1cff8cbc838e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprHeightLine2.h @@ -0,0 +1,153 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// Least-squares fit of a line to height data (x,f(x)). The line is of the +// form: (y - yAvr) = a*(x - xAvr), where (xAvr,yAvr) is the average of the +// sample points. The return value of Fit is 'true' iff the fit is successful +// (the input points are not degenerate to a single point). The mParameters +// values are ((xAvr,yAvr),(a,-1)) on success and ((0,0),(0,0)) on failure. +// The error for (x0,y0) is [a*(x0-xAvr)-(y0-yAvr)]^2. + +namespace gte +{ + +template +class ApprHeightLine2 + : + public ApprQuery, Vector2> +{ +public: + // Initialize the model parameters to zero. + ApprHeightLine2(); + + // Basic fitting algorithm. + bool Fit(int numPoints, Vector2 const* points); + std::pair, Vector2> const& GetParameters() const; + + // Functions called by ApprQuery::RANSAC. See GteApprQuery.h for a + // detailed description. + int GetMinimumRequired() const; + Real Error(Vector2 const& observation) const; + bool Fit(std::vector> const& observations, + std::vector const& indices); + +private: + std::pair, Vector2> mParameters; +}; + + +template +ApprHeightLine2::ApprHeightLine2() +{ + mParameters.first = Vector2::Zero(); + mParameters.second = Vector2::Zero(); +} + +template +bool ApprHeightLine2::Fit(int numPoints, Vector2 const* points) +{ + if (numPoints >= GetMinimumRequired() && points) + { + // Compute the mean of the points. + Vector2 mean = Vector2::Zero(); + for (int i = 0; i < numPoints; ++i) + { + mean += points[i]; + } + mean /= (Real)numPoints; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + } + + // Decompose the covariance matrix. + if (covar00 >(Real)0) + { + mParameters.first = mean; + mParameters.second[0] = covar01 / covar00; + mParameters.second[1] = (Real)-1; + return true; + } + } + + mParameters.first = Vector2::Zero(); + mParameters.second = Vector2::Zero(); + return false; +} + +template +std::pair, Vector2> const& +ApprHeightLine2::GetParameters() const +{ + return mParameters; +} + +template +int ApprHeightLine2::GetMinimumRequired() const +{ + return 2; +} + +template +Real ApprHeightLine2::Error(Vector2 const& observation) const +{ + Real d = Dot(observation - mParameters.first, mParameters.second); + Real error = d*d; + return error; +} + +template +bool ApprHeightLine2::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (static_cast(indices.size()) >= GetMinimumRequired()) + { + // Compute the mean of the points. + Vector2 mean = Vector2::Zero(); + for (auto index : indices) + { + mean += observations[index]; + } + mean /= (Real)indices.size(); + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0; + for (auto index : indices) + { + Vector2 diff = observations[index] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + } + + // Decompose the covariance matrix. + if (covar00 > (Real)0) + { + mParameters.first = mean; + mParameters.second[0] = covar01 / covar00; + mParameters.second[1] = (Real)-1; + return true; + } + } + + mParameters.first = Vector2::Zero(); + mParameters.second = Vector2::Zero(); + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprHeightPlane3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprHeightPlane3.h new file mode 100644 index 000000000000..a90c4bc7d7ab --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprHeightPlane3.h @@ -0,0 +1,171 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// Least-squares fit of a plane to height data (x,y,f(x,y)). The plane is of +// the form (z - zAvr) = a*(x - xAvr) + b*(y - yAvr), where (xAvr,yAvr,zAvr) +// is the average of the sample points. The return value is 'true' iff the +// fit is successful (the input points are noncollinear). The mParameters +// values are ((xAvr,yAvr,zAvr),(a,b,-1)) on success and ((0,0,0),(0,0,0)) on +// failure. The error for (x0,y0,z0) is [a*(x0-xAvr)+b*(y0-yAvr)-(z0-zAvr)]^2. + +namespace gte +{ + +template +class ApprHeightPlane3 + : + public ApprQuery, Vector3> +{ +public: + // Initialize the model parameters to zero. + ApprHeightPlane3(); + + // Basic fitting algorithm. + bool Fit(int numPoints, Vector3 const* points); + std::pair, Vector3> const& GetParameters() const; + + // Functions called by ApprQuery::RANSAC. See GteApprQuery.h for a + // detailed description. + int GetMinimumRequired() const; + Real Error(Vector3 const& observation) const; + bool Fit(std::vector> const& observations, + std::vector const& indices); + +private: + std::pair, Vector3> mParameters; +}; + + +template +ApprHeightPlane3::ApprHeightPlane3() +{ + mParameters.first = Vector3::Zero(); + mParameters.second = Vector3::Zero(); +} + +template +bool ApprHeightPlane3::Fit(int numPoints, Vector3 const* points) +{ + if (numPoints >= GetMinimumRequired() && points) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + for (int i = 0; i < numPoints; ++i) + { + mean += points[i]; + } + mean /= (Real)numPoints; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + } + + // Decompose the covariance matrix. + Real det = covar00*covar11 - covar01*covar01; + if (det != (Real)0) + { + Real invDet = ((Real)1) / det; + mParameters.first = mean; + mParameters.second[0] = + (covar11*covar02 - covar01*covar12)*invDet; + mParameters.second[1] = + (covar00*covar12 - covar01*covar02)*invDet; + mParameters.second[2] = (Real)-1; + return true; + } + } + + mParameters.first = Vector3::Zero(); + mParameters.second = Vector3::Zero(); + return false; +} + +template +std::pair, Vector3> const& +ApprHeightPlane3::GetParameters() const +{ + return mParameters; +} + +template +int ApprHeightPlane3::GetMinimumRequired() const +{ + return 3; +} + +template +Real ApprHeightPlane3::Error(Vector3 const& observation) const +{ + Real d = Dot(observation - mParameters.first, mParameters.second); + Real error = d*d; + return error; +} + +template +bool ApprHeightPlane3::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (static_cast(indices.size()) >= GetMinimumRequired()) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + for (auto index : indices) + { + mean += observations[index]; + } + mean /= (Real)indices.size(); + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0; + for (auto index : indices) + { + Vector3 diff = observations[index] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + } + + // Decompose the covariance matrix. + Real det = covar00*covar11 - covar01*covar01; + if (det != (Real)0) + { + Real invDet = ((Real)1) / det; + mParameters.first = mean; + mParameters.second[0] = + (covar11*covar02 - covar01*covar12)*invDet; + mParameters.second[1] = + (covar00*covar12 - covar01*covar02)*invDet; + mParameters.second[2] = (Real)-1; + return true; + } + } + + mParameters.first = Vector3::Zero(); + mParameters.second = Vector3::Zero(); + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalLine2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalLine2.h new file mode 100644 index 000000000000..2ae547c6361f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalLine2.h @@ -0,0 +1,169 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Least-squares fit of a line to (x,y) data by using distance measurements +// orthogonal to the proposed line. The return value is 'true' iff the fit +// is unique (always successful, 'true' when a minimum eigenvalue is unique). +// The mParameters value is a line with (P,D) = (origin,direction). The +// error for S = (x0,y0) is (S-P)^T*(I - D*D^T)*(S-P). + +namespace gte +{ + +template +class ApprOrthogonalLine2 + : + public ApprQuery, Vector2> +{ +public: + // Initialize the model parameters to zero. + ApprOrthogonalLine2(); + + // Basic fitting algorithm. + bool Fit(int numPoints, Vector2 const* points); + Line2 const& GetParameters() const; + + // Functions called by ApprQuery::RANSAC. See GteApprQuery.h for a + // detailed description. + int GetMinimumRequired() const; + Real Error(Vector2 const& observation) const; + bool Fit(std::vector> const& observations, + std::vector const& indices); + +private: + Line2 mParameters; +}; + + +template +ApprOrthogonalLine2::ApprOrthogonalLine2() + : + mParameters(Vector2::Zero(), Vector2::Zero()) +{ +} + +template +bool ApprOrthogonalLine2::Fit(int numPoints, + Vector2 const* points) +{ + if (numPoints >= GetMinimumRequired() && points) + { + // Compute the mean of the points. + Vector2 mean = Vector2::Zero(); + for (int i = 0; i < numPoints; ++i) + { + mean += points[i]; + } + mean /= (Real)numPoints; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar11 = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar11 += diff[1] * diff[1]; + } + + // Solve the eigensystem. + SymmetricEigensolver2x2 es; + std::array eval; + std::array, 2> evec; + es(covar00, covar01, covar11, +1, eval, evec); + + // The line direction is the eigenvector in the direction of largest + // variance of the points. + mParameters.origin = mean; + mParameters.direction = evec[1]; + + // The fitted line is unique when the maximum eigenvalue has + // multiplicity 1. + return eval[0] < eval[1]; + } + + mParameters = Line2(Vector2::Zero(), Vector2::Zero()); + return false; +} + +template +Line2 const& ApprOrthogonalLine2::GetParameters() const +{ + return mParameters; +} + +template +int ApprOrthogonalLine2::GetMinimumRequired() const +{ + return 2; +} + +template +Real ApprOrthogonalLine2::Error(Vector2 const& observation) const +{ + Vector2 diff = observation - mParameters.origin; + Real sqrlen = Dot(diff, diff); + Real dot = Dot(diff, mParameters.direction); + Real error = std::abs(sqrlen - dot*dot); + return error; +} + +template +bool ApprOrthogonalLine2::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (static_cast(indices.size()) >= GetMinimumRequired()) + { + // Compute the mean of the points. + Vector2 mean = Vector2::Zero(); + for (auto index : indices) + { + mean += observations[index]; + } + mean /= (Real)indices.size(); + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar11 = (Real)0; + for (auto index : indices) + { + Vector2 diff = observations[index] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar11 += diff[1] * diff[1]; + } + + // Solve the eigensystem. + // Solve the eigensystem. + SymmetricEigensolver2x2 es; + std::array eval; + std::array, 2> evec; + es(covar00, covar01, covar11, +1, eval, evec); + + // The line direction is the eigenvector in the direction of largest + // variance of the points. + mParameters.origin = mean; + mParameters.direction = evec[1]; + + // The fitted line is unique when the maximum eigenvalue has + // multiplicity 1. + return eval[0] < eval[1]; + } + + mParameters = Line2(Vector2::Zero(), Vector2::Zero()); + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalLine3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalLine3.h new file mode 100644 index 000000000000..099d442adfa3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalLine3.h @@ -0,0 +1,191 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Least-squares fit of a line to (x,y,z) data by using distance measurements +// orthogonal to the proposed line. The return value is 'true' iff the fit +// is unique (always successful, 'true' when a minimum eigenvalue is unique). +// The mParameters value is a line with (P,D) = (origin,direction). The +// error for S = (x0,y0,z0) is (S-P)^T*(I - D*D^T)*(S-P). + +namespace gte +{ + +template +class ApprOrthogonalLine3 + : + public ApprQuery, Vector3> +{ +public: + // Initialize the model parameters to zero. + ApprOrthogonalLine3(); + + // Basic fitting algorithm. + bool Fit(int numPoints, Vector3 const* points); + Line3 const& GetParameters() const; + + // Functions called by ApprQuery::RANSAC. See GteApprQuery.h for a + // detailed description. + int GetMinimumRequired() const; + Real Error(Vector3 const& observation) const; + bool Fit(std::vector> const& observations, + std::vector const& indices); + +private: + Line3 mParameters; +}; + + +template +ApprOrthogonalLine3::ApprOrthogonalLine3() + : + mParameters(Vector3::Zero(), Vector3::Zero()) +{ +} + +template +bool ApprOrthogonalLine3::Fit(int numPoints, Vector3 const* points) +{ + if (numPoints >= GetMinimumRequired() && points) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + for (int i = 0; i < numPoints; ++i) + { + mean += points[i]; + } + Real invSize = ((Real)1) / (Real)numPoints; + mean *= invSize; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + covar00 *= invSize; + covar01 *= invSize; + covar02 *= invSize; + covar11 *= invSize; + covar12 *= invSize; + covar22 *= invSize; + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, + eval, evec); + + // The line direction is the eigenvector in the direction of largest + // variance of the points. + mParameters.origin = mean; + mParameters.direction = evec[2]; + + // The fitted line is unique when the maximum eigenvalue has + // multiplicity 1. + return eval[1]< eval[2]; + } + + mParameters = Line3(Vector3::Zero(), Vector3::Zero()); + return false; +} + +template +Line3 const& ApprOrthogonalLine3::GetParameters() const +{ + return mParameters; +} + +template +int ApprOrthogonalLine3::GetMinimumRequired() const +{ + return 2; +} + +template +Real ApprOrthogonalLine3::Error(Vector3 const& observation) const +{ + Vector3 diff = observation - mParameters.origin; + Real sqrlen = Dot(diff, diff); + Real dot = Dot(diff, mParameters.direction); + Real error = std::abs(sqrlen - dot*dot); + return error; +} + +template +bool ApprOrthogonalLine3::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (static_cast(indices.size()) >= GetMinimumRequired()) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + for (auto index : indices) + { + mean += observations[index]; + } + Real invSize = ((Real)1) / (Real)indices.size(); + mean *= invSize; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (auto index : indices) + { + Vector3 diff = observations[index] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + covar00 *= invSize; + covar01 *= invSize; + covar02 *= invSize; + covar11 *= invSize; + covar12 *= invSize; + covar22 *= invSize; + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, + eval, evec); + + // The line direction is the eigenvector in the direction of largest + // variance of the points. + mParameters.origin = mean; + mParameters.direction = evec[2]; + + // The fitted line is unique when the maximum eigenvalue has + // multiplicity 1. + return eval[1]< eval[2]; + } + + mParameters = Line3(Vector3::Zero(), Vector3::Zero()); + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalPlane3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalPlane3.h new file mode 100644 index 000000000000..09ed3129488b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalPlane3.h @@ -0,0 +1,181 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// Least-squares fit of a plane to (x,y,z) data by using distance measurements +// orthogonal to the proposed plane. The return value is 'true' iff the fit +// is unique (always successful, 'true' when a minimum eigenvalue is unique). +// The mParameters value is (P,N) = (origin,normal). The error for +// S = (x0,y0,z0) is (S-P)^T*(I - N*N^T)*(S-P). + +namespace gte +{ + +template +class ApprOrthogonalPlane3 + : + public ApprQuery, Vector3> +{ +public: + // Initialize the model parameters to zero. + ApprOrthogonalPlane3(); + + // Basic fitting algorithm. + bool Fit(int numPoints, Vector3 const* points); + std::pair, Vector3> const& GetParameters() const; + + // Functions called by ApprQuery::RANSAC. See GteApprQuery.h for a + // detailed description. + int GetMinimumRequired() const; + Real Error(Vector3 const& observation) const; + bool Fit(std::vector> const& observations, + std::vector const& indices); + +private: + std::pair, Vector3> mParameters; +}; + + +template +ApprOrthogonalPlane3::ApprOrthogonalPlane3() +{ + mParameters.first = Vector3::Zero(); + mParameters.second = Vector3::Zero(); +} + +template +bool ApprOrthogonalPlane3::Fit(int numPoints, + Vector3 const* points) +{ + if (numPoints >= GetMinimumRequired() && points) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + for (int i = 0; i < numPoints; ++i) + { + mean += points[i]; + } + mean /= (Real)numPoints; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, + eval, evec); + + // The plane normal is the eigenvector in the direction of smallest + // variance of the points. + mParameters.first = mean; + mParameters.second = evec[0]; + + // The fitted plane is unique when the minimum eigenvalue has + // multiplicity 1. + return eval[0] < eval[1]; + } + + mParameters.first = Vector3::Zero(); + mParameters.second = Vector3::Zero(); + return false; +} + +template +std::pair, Vector3> const& +ApprOrthogonalPlane3::GetParameters() const +{ + return mParameters; +} + +template +int ApprOrthogonalPlane3::GetMinimumRequired() const +{ + return 3; +} + +template +Real ApprOrthogonalPlane3::Error(Vector3 const& observation) +const +{ + Vector3 diff = observation - mParameters.first; + Real sqrlen = Dot(diff, diff); + Real dot = Dot(diff, mParameters.second); + Real error = std::abs(sqrlen - dot*dot); + return error; +} + +template +bool ApprOrthogonalPlane3::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (static_cast(indices.size()) >= GetMinimumRequired()) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + for (auto index : indices) + { + mean += observations[index]; + } + mean /= (Real)indices.size(); + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (auto index : indices) + { + Vector3 diff = observations[index] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, + eval, evec); + + // The plane normal is the eigenvector in the direction of smallest + // variance of the points. + mParameters.first = mean; + mParameters.second = evec[0]; + + // The fitted plane is unique when the minimum eigenvalue has + // multiplicity 1. + return eval[0] < eval[1]; + } + + mParameters.first = Vector3::Zero(); + mParameters.second = Vector3::Zero(); + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprParaboloid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprParaboloid3.h new file mode 100644 index 000000000000..6b84fc8b374d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprParaboloid3.h @@ -0,0 +1,136 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// Least-squares fit of a paraboloid to a set of point. The paraboloid is +// of the form z = c0*x^2+c1*x*y+c2*y^2+c3*x+c4*y+c5. A successful fit is +// indicated by return value of 'true'. +// +// Given a set of samples (x_i,y_i,z_i) for 0 <= i < N, and assuming +// that the true values lie on a paraboloid +// z = p0*x*x + p1*x*y + p2*y*y + p3*x + p4*y + p5 = Dot(P,Q(x,y)) +// where P = (p0,p1,p2,p3,p4,p5) and Q(x,y) = (x*x,x*y,y*y,x,y,1), +// select P to minimize the sum of squared errors +// E(P) = sum_{i=0}^{N-1} [Dot(P,Q_i)-z_i]^2 +// where Q_i = Q(x_i,y_i). +// +// The minimum occurs when the gradient of E is the zero vector, +// grad(E) = 2 sum_{i=0}^{N-1} [Dot(P,Q_i)-z_i] Q_i = 0 +// Some algebra converts this to a system of 6 equations in 6 unknowns: +// [(sum_{i=0}^{N-1} Q_i Q_i^t] P = sum_{i=0}^{N-1} z_i Q_i +// The product Q_i Q_i^t is a product of the 6x1 matrix Q_i with the +// 1x6 matrix Q_i^t, the result being a 6x6 matrix. +// +// Define the 6x6 symmetric matrix A = sum_{i=0}^{N-1} Q_i Q_i^t and the 6x1 +// vector B = sum_{i=0}^{N-1} z_i Q_i. The choice for P is the solution to +// the linear system of equations A*P = B. The entries of A and B indicate +// summations over the appropriate product of variables. For example, +// s(x^3 y) = sum_{i=0}^{N-1} x_i^3 y_i. +// +// +- -++ + +- -+ +// | s(x^4) s(x^3 y) s(x^2 y^2) s(x^3) s(x^2 y) s(x^2) ||p0| |s(z x^2)| +// | s(x^2 y^2) s(x y^3) s(x^2 y) s(x y^2) s(x y) ||p1| |s(z x y)| +// | s(y^4) s(x y^2) s(y^3) s(y^2) ||p2| = |s(z y^2)| +// | s(x^2) s(x y) s(x) ||p3| |s(z x) | +// | s(y^2) s(y) ||p4| |s(z y) | +// | s(1) ||p5| |s(z) | +// +- -++ + +- -+ + +namespace gte +{ + +template +class ApprParaboloid3 +{ +public: + bool operator()(int numPoints, Vector3 const* points, + Real coefficients[6]); +}; + + +template +bool ApprParaboloid3::operator()(int numPoints, + Vector3 const* points, Real coefficients[6]) +{ + Matrix<6, 6, Real> A; + Vector<6, Real> B; + for (int i = 0; i < numPoints; i++) + { + Real x2 = points[i][0] * points[i][0]; + Real xy = points[i][0] * points[i][1]; + Real y2 = points[i][1] * points[i][1]; + Real zx = points[i][2] * points[i][0]; + Real zy = points[i][2] * points[i][1]; + Real x3 = points[i][0] * x2; + Real x2y = x2*points[i][1]; + Real xy2 = points[i][0] * y2; + Real y3 = points[i][1] * y2; + Real zx2 = points[i][2] * x2; + Real zxy = points[i][2] * xy; + Real zy2 = points[i][2] * y2; + Real x4 = x2*x2; + Real x3y = x3*points[i][1]; + Real x2y2 = x2*y2; + Real xy3 = points[i][0] * y3; + Real y4 = y2*y2; + + A(0, 0) += x4; + A(0, 1) += x3y; + A(0, 2) += x2y2; + A(0, 3) += x3; + A(0, 4) += x2y; + A(0, 5) += x2; + A(1, 2) += xy3; + A(1, 4) += xy2; + A(1, 5) += xy; + A(2, 2) += y4; + A(2, 4) += y3; + A(2, 5) += y2; + A(3, 3) += x2; + A(3, 5) += points[i][0]; + A(4, 5) += points[i][1]; + + B[0] += zx2; + B[1] += zxy; + B[2] += zy2; + B[3] += zx; + B[4] += zy; + B[5] += points[i][2]; + } + + A(1, 0) = A(0, 1); + A(1, 1) = A(0, 2); + A(1, 3) = A(0, 4); + A(2, 0) = A(0, 2); + A(2, 1) = A(1, 2); + A(2, 3) = A(1, 4); + A(3, 0) = A(0, 3); + A(3, 1) = A(1, 3); + A(3, 2) = A(2, 3); + A(3, 4) = A(1, 5); + A(4, 0) = A(0, 4); + A(4, 1) = A(1, 4); + A(4, 2) = A(2, 4); + A(4, 3) = A(3, 4); + A(4, 4) = A(2, 5); + A(5, 0) = A(0, 5); + A(5, 1) = A(1, 5); + A(5, 2) = A(2, 5); + A(5, 3) = A(3, 5); + A(5, 4) = A(4, 5); + A(5, 5) = static_cast(numPoints); + + return LinearSystem().Solve(6, &A[0], &B[0], &coefficients[0]); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial2.h new file mode 100644 index 000000000000..aac79a8b84b5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial2.h @@ -0,0 +1,200 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The samples are (x[i],w[i]) for 0 <= i < S. Think of w as a function of +// x, say w = f(x). The function fits the samples with a polynomial of +// degree d, say w = sum_{i=0}^d c[i]*x^i. The method is a least-squares +// fitting algorithm. The mParameters stores the coefficients c[i] for +// 0 <= i <= d. The observation type is std::array, which represents +// a pair (x,w). +// +// WARNING. The fitting algorithm for polynomial terms +// (1,x,x^2,...,x^d) +// is known to be nonrobust for large degrees and for large magnitude data. +// One alternative is to use orthogonal polynomials +// (f[0](x),...,f[d](x)) +// and apply the least-squares algorithm to these. Another alternative is to +// transform +// (x',w') = ((x-xcen)/rng, w/rng) +// where xmin = min(x[i]), xmax = max(x[i]), xcen = (xmin+xmax)/2, and +// rng = xmax-xmin. Fit the (x',w') points, +// w' = sum_{i=0}^d c'[i]*(x')^i. +// The original polynomial is evaluated as +// w = rng*sum_{i=0}^d c'[i]*((x-xcen)/rng)^i + +namespace gte +{ + +template +class ApprPolynomial2 + : + public ApprQuery, std::array> +{ +public: + // Initialize the model parameters to zero. + ApprPolynomial2(int degree); + + // The minimum number of observations required to fit the model. + int GetMinimumRequired() const; + + // Estimate the model parameters for all observations specified by the + // indices. This function is called by the base-class Fit(...) functions. + bool Fit(std::vector> const& observations, + std::vector const& indices); + + // Compute the model error for the specified observation for the current + // model parameters. The returned value for observation (x0,w0) is + // |w(x0) - w0|, where w(x) is the fitted polynomial. + Real Error(std::array const& observation) const; + + // Get the parameters of the model. + std::vector const& GetParameters() const; + + // Evaluate the polynomial. The domain interval is provided so you can + // interpolate (x in domain) or extrapolate (x not in domain). + std::array const& GetXDomain() const; + Real Evaluate(Real x) const; + +private: + int mDegree, mSize; + std::array mXDomain; + std::vector mParameters; +}; + + +template +ApprPolynomial2::ApprPolynomial2(int degree) + : + mDegree(degree), + mSize(degree + 1), + mParameters(mSize) +{ + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + std::fill(mParameters.begin(), mParameters.end(), (Real)0); +} + +template +int ApprPolynomial2::GetMinimumRequired() const +{ + return mSize; +} + +template +bool ApprPolynomial2::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (indices.size() > 0) + { + int s, i0, i1; + + // Compute the powers of x. + int numSamples = static_cast(indices.size()); + int twoDegree = 2 * mDegree; + Array2 xPower(twoDegree + 1, numSamples); + for (s = 0; s < numSamples; ++s) + { + Real x = observations[indices[s]][0]; + mXDomain[0] = std::min(x, mXDomain[0]); + mXDomain[1] = std::max(x, mXDomain[1]); + + xPower[s][0] = (Real)1; + for (i0 = 1; i0 <= twoDegree; ++i0) + { + xPower[s][i0] = x * xPower[s][i0 - 1]; + } + } + + // Matrix A is the Vandermonde matrix and vector B is the right-hand + // side of the linear system A*X = B. + GMatrix A(mSize, mSize); + GVector B(mSize); + for (i0 = 0; i0 <= mDegree; ++i0) + { + Real sum = (Real)0; + for (s = 0; s < numSamples; ++s) + { + Real w = observations[indices[s]][1]; + sum += w * xPower[s][i0]; + } + + B[i0] = sum; + + for (i1 = 0; i1 <= mDegree; ++i1) + { + sum = (Real)0; + for (s = 0; s < numSamples; ++s) + { + sum += xPower[s][i0 + i1]; + } + + A(i0, i1) = sum; + } + } + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < mSize; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; + + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; +} + +template +Real ApprPolynomial2::Error(std::array const& observation) +const +{ + Real w = Evaluate(observation[0]); + Real error = std::abs(w - observation[1]); + return error; +} + +template +std::vector const& ApprPolynomial2::GetParameters() const +{ + return mParameters; +} + +template +std::array const& ApprPolynomial2::GetXDomain() const +{ + return mXDomain; +} + +template +Real ApprPolynomial2::Evaluate(Real x) const +{ + int i = mDegree; + Real w = mParameters[i]; + while (--i >= 0) + { + w = mParameters[i] + w * x; + } + return w; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial3.h new file mode 100644 index 000000000000..ccab10357519 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial3.h @@ -0,0 +1,256 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The samples are (x[i],y[i],w[i]) for 0 <= i < S. Think of w as a function +// of x and y, say w = f(x,y). The function fits the samples with a +// polynomial of degree d0 in x and degree d1 in y, say +// w = sum_{i=0}^{d0} sum_{j=0}^{d1} c[i][j]*x^i*y^j +// The method is a least-squares fitting algorithm. The mParameters stores +// c[i][j] = mParameters[i+(d0+1)*j] for a total of (d0+1)*(d1+1) +// coefficients. The observation type is std::array, which represents +// a triple (x,y,w). +// +// WARNING. The fitting algorithm for polynomial terms +// (1,x,x^2,...,x^d0), (1,y,y^2,...,y^d1) +// is known to be nonrobust for large degrees and for large magnitude data. +// One alternative is to use orthogonal polynomials +// (f[0](x),...,f[d0](x)), (g[0](y),...,g[d1](y)) +// and apply the least-squares algorithm to these. Another alternative is to +// transform +// (x',y',w') = ((x-xcen)/rng, (y-ycen)/rng, w/rng) +// where xmin = min(x[i]), xmax = max(x[i]), xcen = (xmin+xmax)/2, +// ymin = min(y[i]), ymax = max(y[i]), ycen = (ymin+ymax)/2, and +// rng = max(xmax-xmin,ymax-ymin). Fit the (x',y',w') points, +// w' = sum_{i=0}^{d0} sum_{j=0}^{d1} c'[i][j]*(x')^i*(y')^j +// The original polynomial is evaluated as +// w = rng * sum_{i=0}^{d0} sum_{j=0}^{d1} c'[i][j] * +// ((x-xcen)/rng)^i * ((y-ycen)/rng)^j + +namespace gte +{ + +template +class ApprPolynomial3 + : + public ApprQuery, std::array> +{ +public: + // Initialize the model parameters to zero. + ApprPolynomial3(int xDegree, int yDegree); + + // The minimum number of observations required to fit the model. + int GetMinimumRequired() const; + + // Estimate the model parameters for all observations specified by the + // indices. This function is called by the base-class Fit(...) functions. + bool Fit(std::vector> const& observations, + std::vector const& indices); + + // Compute the model error for the specified observation for the current + // model parameters. The returned value for observation (x0,y0,w0) is + // |w(x0,y0) - w0|, where w(x,y) is the fitted polynomial. + Real Error(std::array const& observation) const; + + // Get the parameters of the model. + std::vector const& GetParameters() const; + + // Evaluate the polynomial. The domain intervals are provided so you can + // interpolate ((x,y) in domain) or extrapolate ((x,y) not in domain). + std::array const& GetXDomain() const; + std::array const& GetYDomain() const; + Real Evaluate(Real x, Real y) const; + +private: + int mXDegree, mYDegree, mXDegreeP1, mYDegreeP1, mSize; + std::array mXDomain, mYDomain; + std::vector mParameters; + + // This array is used by Evaluate() to avoid reallocation of the 'vector' + // for each call. The member is mutable because, to the user, the call + // to Evaluate does not modify the polynomial. + mutable std::vector mYCoefficient; +}; + + +template +ApprPolynomial3::ApprPolynomial3(int xDegree, int yDegree) + : + mXDegree(xDegree), + mYDegree(yDegree), + mXDegreeP1(xDegree + 1), + mYDegreeP1(yDegree + 1), + mSize(mXDegreeP1 * mYDegreeP1), + mParameters(mSize), + mYCoefficient(mYDegreeP1) +{ + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + mYDomain[0] = std::numeric_limits::max(); + mYDomain[1] = -mYDomain[0]; + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + std::fill(mYCoefficient.begin(), mYCoefficient.end(), (Real)0); +} + +template +int ApprPolynomial3::GetMinimumRequired() const +{ + return mSize; +} + +template +bool ApprPolynomial3::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (indices.size() > 0) + { + int s, i0, j0, k0, i1, j1, k1; + + // Compute the powers of x and y. + int numSamples = static_cast(indices.size()); + int twoXDegree = 2 * mXDegree; + int twoYDegree = 2 * mYDegree; + Array2 xPower(twoXDegree + 1, numSamples); + Array2 yPower(twoYDegree + 1, numSamples); + for (s = 0; s < numSamples; ++s) + { + Real x = observations[indices[s]][0]; + Real y = observations[indices[s]][1]; + mXDomain[0] = std::min(x, mXDomain[0]); + mXDomain[1] = std::max(x, mXDomain[1]); + mYDomain[0] = std::min(y, mYDomain[0]); + mYDomain[1] = std::max(y, mYDomain[1]); + + xPower[s][0] = (Real)1; + for (i0 = 1; i0 <= twoXDegree; ++i0) + { + xPower[s][i0] = x * xPower[s][i0 - 1]; + } + + yPower[s][0] = (Real)1; + for (j0 = 1; j0 <= twoYDegree; ++j0) + { + yPower[s][j0] = y * yPower[s][j0 - 1]; + } + } + + // Matrix A is the Vandermonde matrix and vector B is the right-hand + // side of the linear system A*X = B. + GMatrix A(mSize, mSize); + GVector B(mSize); + for (j0 = 0; j0 <= mYDegree; ++j0) + { + for (i0 = 0; i0 <= mXDegree; ++i0) + { + Real sum = (Real)0; + k0 = i0 + mXDegreeP1 * j0; + for (s = 0; s < numSamples; ++s) + { + Real w = observations[indices[s]][2]; + sum += w * xPower[s][i0] * yPower[s][j0]; + } + + B[k0] = sum; + + for (j1 = 0; j1 <= mYDegree; ++j1) + { + for (i1 = 0; i1 <= mXDegree; ++i1) + { + sum = (Real)0; + k1 = i1 + mXDegreeP1 * j1; + for (s = 0; s < numSamples; ++s) + { + sum += xPower[s][i0 + i1] * yPower[s][j0 + j1]; + } + + A(k0, k1) = sum; + } + } + } + } + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < mSize; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; +} + +template +Real ApprPolynomial3::Error(std::array const& observation) +const +{ + Real w = Evaluate(observation[0], observation[1]); + Real error = std::abs(w - observation[2]); + return error; +} + +template +std::vector const& ApprPolynomial3::GetParameters() const +{ + return mParameters; +} + +template +std::array const& ApprPolynomial3::GetXDomain() const +{ + return mXDomain; +} + +template +std::array const& ApprPolynomial3::GetYDomain() const +{ + return mYDomain; +} + +template +Real ApprPolynomial3::Evaluate(Real x, Real y) const +{ + int i0, i1; + Real w; + + for (i1 = 0; i1 <= mYDegree; ++i1) + { + i0 = mXDegree; + w = mParameters[i0 + mXDegreeP1 * i1]; + while (--i0 >= 0) + { + w = mParameters[i0 + mXDegreeP1 * i1] + w * x; + } + mYCoefficient[i1] = w; + } + + i1 = mYDegree; + w = mYCoefficient[i1]; + while (--i1 >= 0) + { + w = mYCoefficient[i1] + w * y; + } + + return w; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial4.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial4.h new file mode 100644 index 000000000000..6726d67d719f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial4.h @@ -0,0 +1,310 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The samples are (x[i],y[i],z[i],w[i]) for 0 <= i < S. Think of w as a +// function of x, y, and z, say w = f(x,y,z). The function fits the samples +// with a polynomial of degree d0 in x, degree d1 in y, and degree d2 in z, +// say +// w = sum_{i=0}^{d0} sum_{j=0}^{d1} sum_{k=0}^{d2} c[i][j][k]*x^i*y^j*z^k +// The method is a least-squares fitting algorithm. The mParameters stores +// c[i][j][k] = mParameters[i+(d0+1)*(j+(d1+1)*k)] for a total of +// (d0+1)*(d1+1)*(d2+1) coefficients. The observation type is +// std::array, which represents a tuple (x,y,z,w). +// +// WARNING. The fitting algorithm for polynomial terms +// (1,x,x^2,...,x^d0), (1,y,y^2,...,y^d1), (1,z,z^2,...,z^d2) +// is known to be nonrobust for large degrees and for large magnitude data. +// One alternative is to use orthogonal polynomials +// (f[0](x),...,f[d0](x)), (g[0](y),...,g[d1](y)), (h[0](z),...,h[d2](z)) +// and apply the least-squares algorithm to these. Another alternative is to +// transform +// (x',y',z',w') = ((x-xcen)/rng, (y-ycen)/rng, (z-zcen)/rng, w/rng) +// where xmin = min(x[i]), xmax = max(x[i]), xcen = (xmin+xmax)/2, +// ymin = min(y[i]), ymax = max(y[i]), ycen = (ymin+ymax)/2, zmin = min(z[i]), +// zmax = max(z[i]), zcen = (zmin+zmax)/2, and +// rng = max(xmax-xmin,ymax-ymin,zmax-zmin). Fit the (x',y',z',w') points, +// w' = sum_{i=0}^{d0} sum_{j=0}^{d1} sum_{k=0}^{d2} c'[i][j][k] * +// (x')^i*(y')^j*(z')^k +// The original polynomial is evaluated as +// w = rng * sum_{i=0}^{d0} sum_{j=0}^{d1} sum_{k=0}^{d2} c'[i][j][k] * +// ((x-xcen)/rng)^i * ((y-ycen)/rng)^j * ((z-zcen)/rng)^k + +namespace gte +{ + +template +class ApprPolynomial4 + : + public ApprQuery, std::array> +{ +public: + // Initialize the model parameters to zero. + ApprPolynomial4(int xDegree, int yDegree, int zDegree); + + // The minimum number of observations required to fit the model. + int GetMinimumRequired() const; + + // Estimate the model parameters for all observations specified by the + // indices. This function is called by the base-class Fit(...) functions. + bool Fit(std::vector> const& observations, + std::vector const& indices); + + // Compute the model error for the specified observation for the current + // model parameters. The returned value for observation (x0,y0,z0,w0) is + // |w(x0,y0,z0) - w0|, where w(x,y,z) is the fitted polynomial. + Real Error(std::array const& observation) const; + + // Get the parameters of the model. + std::vector const& GetParameters() const; + + // Evaluate the polynomial. The domain intervals are provided so you can + // interpolate ((x,y,z) in domain) or extrapolate ((x,y,z) not in domain). + std::array const& GetXDomain() const; + std::array const& GetYDomain() const; + std::array const& GetZDomain() const; + Real Evaluate(Real x, Real y, Real z) const; + +private: + int mXDegree, mYDegree, mZDegree; + int mXDegreeP1, mYDegreeP1, mZDegreeP1, mSize; + std::array mXDomain, mYDomain, mZDomain; + std::vector mParameters; + + // These arrays are used by Evaluate() to avoid reallocation of the + // 'vector's for each call. The member is mutable because, to the + // user, the call to Evaluate does not modify the polynomial. + mutable std::vector mYZCoefficient; + mutable std::vector mZCoefficient; +}; + + +template +ApprPolynomial4::ApprPolynomial4(int xDegree, int yDegree, int zDegree) + : + mXDegree(xDegree), + mYDegree(yDegree), + mZDegree(zDegree), + mXDegreeP1(xDegree + 1), + mYDegreeP1(yDegree + 1), + mZDegreeP1(zDegree + 1), + mSize(mXDegreeP1 * mYDegreeP1 * mZDegreeP1), + mParameters(mSize), + mYZCoefficient(mYDegreeP1 * mZDegreeP1), + mZCoefficient(mZDegreeP1) +{ + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + mYDomain[0] = std::numeric_limits::max(); + mYDomain[1] = -mYDomain[0]; + mZDomain[0] = std::numeric_limits::max(); + mZDomain[1] = -mZDomain[0]; + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + std::fill(mYZCoefficient.begin(), mYZCoefficient.end(), (Real)0); + std::fill(mZCoefficient.begin(), mZCoefficient.end(), (Real)0); +} + +template +int ApprPolynomial4::GetMinimumRequired() const +{ + return mSize; +} + +template +bool ApprPolynomial4::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (indices.size() > 0) + { + int s, i0, j0, k0, n0, i1, j1, k1, n1; + + // Compute the powers of x, y, and z. + int numSamples = static_cast(indices.size()); + int twoXDegree = 2 * mXDegree; + int twoYDegree = 2 * mYDegree; + int twoZDegree = 2 * mZDegree; + Array2 xPower(twoXDegree + 1, numSamples); + Array2 yPower(twoYDegree + 1, numSamples); + Array2 zPower(twoZDegree + 1, numSamples); + for (s = 0; s < numSamples; ++s) + { + Real x = observations[indices[s]][0]; + Real y = observations[indices[s]][1]; + Real z = observations[indices[s]][2]; + mXDomain[0] = std::min(x, mXDomain[0]); + mXDomain[1] = std::max(x, mXDomain[1]); + mYDomain[0] = std::min(y, mYDomain[0]); + mYDomain[1] = std::max(y, mYDomain[1]); + mZDomain[0] = std::min(z, mZDomain[0]); + mZDomain[1] = std::max(z, mZDomain[1]); + + xPower[s][0] = (Real)1; + for (i0 = 1; i0 <= twoXDegree; ++i0) + { + xPower[s][i0] = x * xPower[s][i0 - 1]; + } + + yPower[s][0] = (Real)1; + for (j0 = 1; j0 <= twoYDegree; ++j0) + { + yPower[s][j0] = y * yPower[s][j0 - 1]; + } + + zPower[s][0] = (Real)1; + for (k0 = 1; k0 <= twoZDegree; ++k0) + { + zPower[s][k0] = z * zPower[s][k0 - 1]; + } + } + + // Matrix A is the Vandermonde matrix and vector B is the right-hand + // side of the linear system A*X = B. + GMatrix A(mSize, mSize); + GVector B(mSize); + for (k0 = 0; k0 <= mZDegree; ++k0) + { + for (j0 = 0; j0 <= mYDegree; ++j0) + { + for (i0 = 0; i0 <= mXDegree; ++i0) + { + Real sum = (Real)0; + n0 = i0 + mXDegreeP1*(j0 + mYDegreeP1*k0); + for (s = 0; s < numSamples; ++s) + { + Real w = observations[indices[s]][3]; + sum += w * xPower[s][i0] * yPower[s][j0] * + zPower[s][k0]; + } + + B[n0] = sum; + + for (k1 = 0; k1 <= mZDegree; ++k1) + { + for (j1 = 0; j1 <= mYDegree; ++j1) + { + for (i1 = 0; i1 <= mXDegree; ++i1) + { + sum = (Real)0; + n1 = i1 + mXDegreeP1*(j1 + mYDegreeP1*k1); + for (s = 0; s < numSamples; ++s) + { + sum += xPower[s][i0 + i1] * + yPower[s][j0 + j1] * + zPower[s][k0 + k1]; + } + + A(n0, n1) = sum; + } + } + } + } + } + } + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < mSize; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; +} + +template +Real ApprPolynomial4::Error(std::array const& observation) +const +{ + Real w = Evaluate(observation[0], observation[1], observation[2]); + Real error = std::abs(w - observation[3]); + return error; +} + +template +std::vector const& ApprPolynomial4::GetParameters() const +{ + return mParameters; +} + +template +std::array const& ApprPolynomial4::GetXDomain() const +{ + return mXDomain; +} + +template +std::array const& ApprPolynomial4::GetYDomain() const +{ + return mYDomain; +} + +template +std::array const& ApprPolynomial4::GetZDomain() const +{ + return mZDomain; +} + +template +Real ApprPolynomial4::Evaluate(Real x, Real y, Real z) const +{ + int i0, i1, i2; + Real w; + + for (i2 = 0; i2 <= mZDegree; ++i2) + { + for (i1 = 0; i1 <= mYDegree; ++i1) + { + i0 = mXDegree; + w = mParameters[i0 + mXDegreeP1 * (i1 + mYDegreeP1 * i2)]; + while (--i0 >= 0) + { + w = mParameters[i0 + mXDegreeP1 * (i1 + mYDegreeP1 * i2)] + + w * x; + } + // @todo original line here did no assignment, is this correct? Assume will be fixed in future versions of GTEngine. + mYZCoefficient[i1 + mYDegree * i2] = w; + } + } + + for (i2 = 0; i2 <= mZDegree; ++i2) + { + i1 = mYDegree; + w = mYZCoefficient[i1 + mYDegreeP1 * i2]; + while (--i1 >= 0) + { + w = mParameters[i1 + mYDegreeP1 * i2] + w * y; + } + mZCoefficient[i2] = w; + } + + i2 = mZDegree; + w = mZCoefficient[i2]; + while (--i2 >= 0) + { + w = mZCoefficient[i2] + w * z; + } + + return w; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial2.h new file mode 100644 index 000000000000..db83dd02e2fb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial2.h @@ -0,0 +1,305 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Fit the data with a polynomial of the form +// w = sum_{i=0}^{n-1} c[i]*x^{p[i]} +// where p[i] are distinct nonnegative powers provided by the caller. A +// least-squares fitting algorithm is used, but the input data is first +// mapped to (x,w) in [-1,1]^2 for numerical robustness. + +namespace gte +{ + +template +class ApprPolynomialSpecial2 + : + public ApprQuery, std::array> +{ +public: + // Initialize the model parameters to zero. The degrees must be + // nonnegative and strictly increasing. + ApprPolynomialSpecial2(std::vector const& degrees); + + // The minimum number of observations required to fit the model. + int GetMinimumRequired() const; + + // Estimate the model parameters for all observations specified by the + // indices. This function is called by the base-class Fit(...) functions. + bool Fit(std::vector> const& observations, + std::vector const& indices); + + // Compute the model error for the specified observation for the current + // model parameters. The returned value for observation (x0,w0) is + // |w(x0) - w0|, where w(x) is the fitted polynomial. + Real Error(std::array const& observation) const; + + // Get the parameters of the model. + std::vector const& GetParameters() const; + + // Evaluate the polynomial. The domain interval is provided so you can + // interpolate (x in domain) or extrapolate (x not in domain). + std::array const& GetXDomain() const; + Real Evaluate(Real x) const; + +private: + // Transform the (x,w) values to (x',w') in [-1,1]^2. + void Transform(std::vector> const& observations, + std::vector const& indices, + std::vector>& transformed); + + // The least-squares fitting algorithm for the transformed data. + bool DoLeastSquares(std::vector>& transformed); + + std::vector mDegrees; + std::vector mParameters; + + // Support for evaluation. The coefficients were generated for the + // samples mapped to [-1,1]^2. The Evaluate() function must transform + // x to x' in [-1,1], compute w' in [-1,1], then transform w' to w. + std::array mXDomain, mWDomain; + std::array mScale; + Real mInvTwoWScale; + + // This array is used by Evaluate() to avoid reallocation of the 'vector' + // for each call. The member is mutable because, to the user, the call + // to Evaluate does not modify the polynomial. + mutable std::vector mXPowers; +}; + + +template +ApprPolynomialSpecial2::ApprPolynomialSpecial2( + std::vector const& degrees) + : + mDegrees(degrees), + mParameters(degrees.size()) +{ +#if !defined(GTE_NO_LOGGER) + LogAssert(mDegrees.size() > 0, "The input array must have elements."); + int lastDegree = -1; + for (auto degree : mDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } +#endif + + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + mWDomain[0] = std::numeric_limits::max(); + mWDomain[1] = -mWDomain[0]; + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + + mScale[0] = (Real)0; + mScale[1] = (Real)0; + mInvTwoWScale = (Real)0; + + // Powers of x are computed up to twice the powers when constructing the + // fitted polynomial. Powers of x are computed up to the powers for the + // evaluation of the fitted polynomial. + mXPowers.resize(2 * mDegrees.back() + 1); + mXPowers[0] = (Real)1; +} + +template +int ApprPolynomialSpecial2::GetMinimumRequired() const +{ + return static_cast(mParameters.size()); +} + +template +bool ApprPolynomialSpecial2::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (indices.size() > 0) + { + // Transform the observations to [-1,1]^2 for numerical robustness. + std::vector> transformed; + Transform(observations, indices, transformed); + + // Fit the transformed data using a least-squares algorithm. + return DoLeastSquares(transformed); + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; +} + +template +Real ApprPolynomialSpecial2::Error( + std::array const& observation) const +{ + Real w = Evaluate(observation[0]); + Real error = std::abs(w - observation[1]); + return error; +} + +template +std::vector const& ApprPolynomialSpecial2::GetParameters() const +{ + return mParameters; +} + +template +std::array const& ApprPolynomialSpecial2::GetXDomain() const +{ + return mXDomain; +} + +template +Real ApprPolynomialSpecial2::Evaluate(Real x) const +{ + // Transform x to x' in [-1,1]. + x = (Real)-1 + ((Real)2) * mScale[0] * (x - mXDomain[0]); + + // Compute relevant powers of x. + int jmax = mDegrees.back(); + for (int j = 1; j <= jmax; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + + Real w = (Real)0; + int isup = static_cast(mDegrees.size()); + for (int i = 0; i < isup; ++i) + { + Real xp = mXPowers[mDegrees[i]]; + w += mParameters[i] * xp; + } + + // Transform w from [-1,1] back to the original space. + w = (w + (Real)1) * mInvTwoWScale + mWDomain[0]; + return w; +} + +template +void ApprPolynomialSpecial2::Transform( + std::vector> const& observations, + std::vector const& indices, + std::vector>& transformed) +{ + int numSamples = static_cast(indices.size()); + transformed.resize(numSamples); + + std::array omin = observations[indices[0]]; + std::array omax = omin; + std::array obs; + int s, i; + for (s = 1; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 2; ++i) + { + if (obs[i] < omin[i]) + { + omin[i] = obs[i]; + } + else if (obs[i] > omax[i]) + { + omax[i] = obs[i]; + } + } + } + + mXDomain[0] = omin[0]; + mXDomain[1] = omax[0]; + mWDomain[0] = omin[1]; + mWDomain[1] = omax[1]; + for (i = 0; i < 2; ++i) + { + mScale[i] = ((Real)1) / (omax[i] - omin[i]); + } + + for (s = 0; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 2; ++i) + { + transformed[s][i] = (Real)-1 + ((Real)2) * mScale[i] * + (obs[i] - omin[i]); + } + } + mInvTwoWScale = ((Real)0.5) / mScale[1]; +} + +template +bool ApprPolynomialSpecial2::DoLeastSquares( + std::vector>& transformed) +{ + // Set up a linear system A*X = B, where X are the polynomial + // coefficients. + int size = static_cast(mDegrees.size()); + GMatrix A(size, size); + A.MakeZero(); + GVector B(size); + B.MakeZero(); + + int numSamples = static_cast(transformed.size()); + int twoMaxXDegree = 2 * mDegrees.back(); + int row, col; + for (int i = 0; i < numSamples; ++i) + { + // Compute relevant powers of x. + Real x = transformed[i][0]; + Real w = transformed[i][1]; + for (int j = 0; j <= twoMaxXDegree; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + + for (row = 0; row < size; ++row) + { + // Update the upper-triangular portion of the symmetric matrix. + for (col = row; col < size; ++col) + { + A(row, col) += mXPowers[mDegrees[row] + mDegrees[col]]; + } + + // Update the right-hand side of the system. + B[row] += mXPowers[mDegrees[row]] * w; + } + } + + // Copy the upper-triangular portion of the symmetric matrix to the + // lower-triangular portion. + for (row = 0; row < size; ++row) + { + for (col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + // Precondition by normalizing the sums. + Real invNumSamples = ((Real)1) / (Real)numSamples; + A *= invNumSamples; + B *= invNumSamples; + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < size; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial3.h new file mode 100644 index 000000000000..a50cf266b1c5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial3.h @@ -0,0 +1,352 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Fit the data with a polynomial of the form +// w = sum_{i=0}^{n-1} c[i]*x^{p[i]}*y^{q[i]} +// where are distinct pairs of nonnegative powers provided by the +// caller. A least-squares fitting algorithm is used, but the input data is +// first mapped to (x,y,w) in [-1,1]^3 for numerical robustness. + +namespace gte +{ + +template +class ApprPolynomialSpecial3 + : + public ApprQuery, std::array> +{ +public: + // Initialize the model parameters to zero. The degrees must be + // nonnegative and strictly increasing. + ApprPolynomialSpecial3(std::vector const& xDegrees, + std::vector const& yDegrees); + + // The minimum number of observations required to fit the model. + int GetMinimumRequired() const; + + // Estimate the model parameters for all observations specified by the + // indices. This function is called by the base-class Fit(...) functions. + bool Fit(std::vector> const& observations, + std::vector const& indices); + + // Compute the model error for the specified observation for the current + // model parameters. The returned value for observation (x0,w0) is + // |w(x0) - w0|, where w(x) is the fitted polynomial. + Real Error(std::array const& observation) const; + + // Get the parameters of the model. + std::vector const& GetParameters() const; + + // Evaluate the polynomial. The domain interval is provided so you can + // interpolate ((x,y) in domain) or extrapolate ((x,y) not in domain). + std::array const& GetXDomain() const; + std::array const& GetYDomain() const; + Real Evaluate(Real x, Real y) const; + +private: + // Transform the (x,y,w) values to (x',y',w') in [-1,1]^3. + void Transform(std::vector> const& observations, + std::vector const& indices, + std::vector>& transformed); + + // The least-squares fitting algorithm for the transformed data. + bool DoLeastSquares(std::vector>& transformed); + + std::vector mXDegrees, mYDegrees; + std::vector mParameters; + + // Support for evaluation. The coefficients were generated for the + // samples mapped to [-1,1]^3. The Evaluate() function must transform + // (x,y) to (x',y') in [-1,1]^2, compute w' in [-1,1], then transform w' + // to w. + std::array mXDomain, mYDomain, mWDomain; + std::array mScale; + Real mInvTwoWScale; + + // This array is used by Evaluate() to avoid reallocation of the 'vector's + // for each call. The members are mutable because, to the user, the call + // to Evaluate does not modify the polynomial. + mutable std::vector mXPowers, mYPowers; +}; + + +template +ApprPolynomialSpecial3::ApprPolynomialSpecial3( + std::vector const& xDegrees, std::vector const& yDegrees) + : + mXDegrees(xDegrees), + mYDegrees(yDegrees), + mParameters(mXDegrees.size() * mYDegrees.size()) +{ +#if !defined(GTE_NO_LOGGER) + LogAssert(mXDegrees.size() == mYDegrees.size(), + "The input arrays must have the same size."); + + LogAssert(mXDegrees.size() > 0, "The input array must have elements."); + int lastDegree = -1; + for (auto degree : mXDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } + + LogAssert(mYDegrees.size() > 0, "The input array must have elements."); + lastDegree = -1; + for (auto degree : mYDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } +#endif + + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + mYDomain[0] = std::numeric_limits::max(); + mYDomain[1] = -mYDomain[0]; + mWDomain[0] = std::numeric_limits::max(); + mWDomain[1] = -mWDomain[0]; + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + + mScale[0] = (Real)0; + mScale[1] = (Real)0; + mScale[2] = (Real)0; + mInvTwoWScale = (Real)0; + + // Powers of x and y are computed up to twice the powers when constructing + // the fitted polynomial. Powers of x and y are computed up to the powers + // for the evaluation of the fitted polynomial. + mXPowers.resize(2 * mXDegrees.back() + 1); + mXPowers[0] = (Real)1; + mYPowers.resize(2 * mYDegrees.back() + 1); + mYPowers[0] = (Real)1; +} + +template +int ApprPolynomialSpecial3::GetMinimumRequired() const +{ + return static_cast(mParameters.size()); +} + +template +bool ApprPolynomialSpecial3::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (indices.size() > 0) + { + // Transform the observations to [-1,1]^3 for numerical robustness. + std::vector> transformed; + Transform(observations, indices, transformed); + + // Fit the transformed data using a least-squares algorithm. + return DoLeastSquares(transformed); + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; +} + +template +Real ApprPolynomialSpecial3::Error( + std::array const& observation) const +{ + Real w = Evaluate(observation[0], observation[1]); + Real error = std::abs(w - observation[2]); + return error; +} + +template +std::vector const& ApprPolynomialSpecial3::GetParameters() const +{ + return mParameters; +} + +template +std::array const& ApprPolynomialSpecial3::GetXDomain() const +{ + return mXDomain; +} + +template +std::array const& ApprPolynomialSpecial3::GetYDomain() const +{ + return mYDomain; +} + +template +Real ApprPolynomialSpecial3::Evaluate(Real x, Real y) const +{ + // Transform (x,y) to (x',y') in [-1,1]^2. + x = (Real)-1 + ((Real)2) * mScale[0] * (x - mXDomain[0]); + y = (Real)-1 + ((Real)2) * mScale[1] * (y - mYDomain[0]); + + // Compute relevant powers of x and y. + int jmax = mXDegrees.back(); + for (int j = 1; j <= jmax; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + + jmax = mYDegrees.back(); + for (int j = 1; j <= jmax; ++j) + { + mYPowers[j] = mYPowers[j - 1] * y; + } + + Real w = (Real)0; + int isup = static_cast(mXDegrees.size()); + for (int i = 0; i < isup; ++i) + { + Real xp = mXPowers[mXDegrees[i]]; + Real yp = mYPowers[mYDegrees[i]]; + w += mParameters[i] * xp * yp; + } + + // Transform w from [-1,1] back to the original space. + w = (w + (Real)1) * mInvTwoWScale + mWDomain[0]; + return w; +} + +template +void ApprPolynomialSpecial3::Transform( + std::vector> const& observations, + std::vector const& indices, + std::vector>& transformed) +{ + int numSamples = static_cast(indices.size()); + transformed.resize(numSamples); + + std::array omin = observations[indices[0]]; + std::array omax = omin; + std::array obs; + int s, i; + for (s = 1; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 3; ++i) + { + if (obs[i] < omin[i]) + { + omin[i] = obs[i]; + } + else if (obs[i] > omax[i]) + { + omax[i] = obs[i]; + } + } + } + + mXDomain[0] = omin[0]; + mXDomain[1] = omax[0]; + mYDomain[0] = omin[1]; + mYDomain[1] = omax[1]; + mWDomain[0] = omin[2]; + mWDomain[1] = omax[2]; + for (i = 0; i < 3; ++i) + { + mScale[i] = ((Real)1) / (omax[i] - omin[i]); + } + + for (s = 0; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 3; ++i) + { + transformed[s][i] = (Real)-1 + ((Real)2) * mScale[i] * + (obs[i] - omin[i]); + } + } + mInvTwoWScale = ((Real)0.5) / mScale[2]; +} + +template +bool ApprPolynomialSpecial3::DoLeastSquares( + std::vector>& transformed) +{ + // Set up a linear system A*X = B, where X are the polynomial + // coefficients. + int size = static_cast(mXDegrees.size()); + GMatrix A(size, size); + A.MakeZero(); + GVector B(size); + B.MakeZero(); + + int numSamples = static_cast(transformed.size()); + int twoMaxXDegree = 2 * mXDegrees.back(); + int twoMaxYDegree = 2 * mYDegrees.back(); + int row, col; + for (int i = 0; i < numSamples; ++i) + { + // Compute relevant powers of x and y. + Real x = transformed[i][0]; + Real y = transformed[i][1]; + Real w = transformed[i][2]; + for (int j = 1; j <= 2 * twoMaxXDegree; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + for (int j = 1; j <= 2 * twoMaxYDegree; ++j) + { + mYPowers[j] = mYPowers[j - 1] * y; + } + + for (row = 0; row < size; ++row) + { + // Update the upper-triangular portion of the symmetric matrix. + Real xp, yp; + for (col = row; col < size; ++col) + { + xp = mXPowers[mXDegrees[row] + mXDegrees[col]]; + yp = mYPowers[mYDegrees[row] + mYDegrees[col]]; + A(row, col) += xp * yp; + } + + // Update the right-hand side of the system. + xp = mXPowers[mXDegrees[row]]; + yp = mYPowers[mYDegrees[row]]; + B[row] += xp * yp * w; + } + } + + // Copy the upper-triangular portion of the symmetric matrix to the + // lower-triangular portion. + for (row = 0; row < size; ++row) + { + for (col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + // Precondition by normalizing the sums. + Real invNumSamples = ((Real)1) / (Real)numSamples; + A *= invNumSamples; + B *= invNumSamples; + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < size; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial4.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial4.h new file mode 100644 index 000000000000..4bbcf10c51c8 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial4.h @@ -0,0 +1,393 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Fit the data with a polynomial of the form +// w = sum_{i=0}^{n-1} c[i]*x^{p[i]}*y^{q[i]}*z^{r[i]} +// where are distinct triples of nonnegative powers provided +// by the caller. A least-squares fitting algorithm is used, but the input +// data is first mapped to (x,y,z,w) in [-1,1]^4 for numerical robustness. + +namespace gte +{ + +template +class ApprPolynomialSpecial4 + : + public ApprQuery, std::array> +{ +public: + // Initialize the model parameters to zero. The degrees must be + // nonnegative and strictly increasing. + ApprPolynomialSpecial4(std::vector const& xDegrees, + std::vector const& yDegrees, std::vector const& zDegrees); + + // The minimum number of observations required to fit the model. + int GetMinimumRequired() const; + + // Estimate the model parameters for all observations specified by the + // indices. This function is called by the base-class Fit(...) functions. + bool Fit(std::vector> const& observations, + std::vector const& indices); + + // Compute the model error for the specified observation for the current + // model parameters. The returned value for observation (x0,w0) is + // |w(x0) - w0|, where w(x) is the fitted polynomial. + Real Error(std::array const& observation) const; + + // Get the parameters of the model. + std::vector const& GetParameters() const; + + // Evaluate the polynomial. The domain interval is provided so you can + // interpolate ((x,y,z) in domain) or extrapolate ((x,y,z) not in domain). + std::array const& GetXDomain() const; + std::array const& GetYDomain() const; + std::array const& GetZDomain() const; + Real Evaluate(Real x, Real y, Real z) const; + +private: + // Transform the (x,y,z,w) values to (x',y',z',w') in [-1,1]^4. + void Transform(std::vector> const& observations, + std::vector const& indices, + std::vector>& transformed); + + // The least-squares fitting algorithm for the transformed data. + bool DoLeastSquares(std::vector>& transformed); + + std::vector mXDegrees, mYDegrees, mZDegrees; + std::vector mParameters; + + // Support for evaluation. The coefficients were generated for the + // samples mapped to [-1,1]^4. The Evaluate() function must transform + // (x,y,z) to (x',y',z') in [-1,1]^3, compute w' in [-1,1], then transform + // w' to w. + std::array mXDomain, mYDomain, mZDomain, mWDomain; + std::array mScale; + Real mInvTwoWScale; + + // This array is used by Evaluate() to avoid reallocation of the 'vector's + // for each call. The members are mutable because, to the user, the call + // to Evaluate does not modify the polynomial. + mutable std::vector mXPowers, mYPowers, mZPowers; +}; + + +template +ApprPolynomialSpecial4::ApprPolynomialSpecial4( + std::vector const& xDegrees, std::vector const& yDegrees, + std::vector const& zDegrees) + : + mXDegrees(xDegrees), + mYDegrees(yDegrees), + mZDegrees(zDegrees), + mParameters(mXDegrees.size() * mYDegrees.size() * mZDegrees.size()) +{ +#if !defined(GTE_NO_LOGGER) + LogAssert(mXDegrees.size() == mYDegrees.size() + && mXDegrees.size() == mZDegrees.size(), + "The input arrays must have the same size."); + + LogAssert(mXDegrees.size() > 0, "The input array must have elements."); + int lastDegree = -1; + for (auto degree : mXDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } + + LogAssert(mYDegrees.size() > 0, "The input array must have elements."); + lastDegree = -1; + for (auto degree : mYDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } + + LogAssert(mZDegrees.size() > 0, "The input array must have elements."); + lastDegree = -1; + for (auto degree : mZDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } +#endif + + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + mYDomain[0] = std::numeric_limits::max(); + mYDomain[1] = -mYDomain[0]; + mZDomain[0] = std::numeric_limits::max(); + mZDomain[1] = -mZDomain[0]; + mWDomain[0] = std::numeric_limits::max(); + mWDomain[1] = -mWDomain[0]; + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + + mScale[0] = (Real)0; + mScale[1] = (Real)0; + mScale[2] = (Real)0; + mScale[3] = (Real)0; + mInvTwoWScale = (Real)0; + + // Powers of x, y, and z are computed up to twice the powers when + // constructing the fitted polynomial. Powers of x, y, and z are + // computed up to the powers for the evaluation of the fitted polynomial. + mXPowers.resize(2 * mXDegrees.back() + 1); + mXPowers[0] = (Real)1; + mYPowers.resize(2 * mYDegrees.back() + 1); + mYPowers[0] = (Real)1; + mZPowers.resize(2 * mZDegrees.back() + 1); + mZPowers[0] = (Real)1; +} + +template +int ApprPolynomialSpecial4::GetMinimumRequired() const +{ + return static_cast(mParameters.size()); +} + +template +bool ApprPolynomialSpecial4::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (indices.size() > 0) + { + // Transform the observations to [-1,1]^4 for numerical robustness. + std::vector> transformed; + Transform(observations, indices, transformed); + + // Fit the transformed data using a least-squares algorithm. + return DoLeastSquares(transformed); + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; +} + +template +Real ApprPolynomialSpecial4::Error( + std::array const& observation) const +{ + Real w = Evaluate(observation[0], observation[1], observation[2]); + Real error = std::abs(w - observation[3]); + return error; +} + +template +std::vector const& ApprPolynomialSpecial4::GetParameters() const +{ + return mParameters; +} + +template +std::array const& ApprPolynomialSpecial4::GetXDomain() const +{ + return mXDomain; +} + +template +std::array const& ApprPolynomialSpecial4::GetYDomain() const +{ + return mYDomain; +} + +template +std::array const& ApprPolynomialSpecial4::GetZDomain() const +{ + return mZDomain; +} + +template +Real ApprPolynomialSpecial4::Evaluate(Real x, Real y, Real z) const +{ + // Transform (x,y,z) to (x',y',z') in [-1,1]^3. + x = (Real)-1 + ((Real)2) * mScale[0] * (x - mXDomain[0]); + y = (Real)-1 + ((Real)2) * mScale[1] * (y - mYDomain[0]); + z = (Real)-1 + ((Real)2) * mScale[2] * (z - mZDomain[0]); + + // Compute relevant powers of x, y, and z. + int jmax = mXDegrees.back();; + for (int j = 1; j <= jmax; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + + jmax = mYDegrees.back();; + for (int j = 1; j <= jmax; ++j) + { + mYPowers[j] = mYPowers[j - 1] * y; + } + + jmax = mZDegrees.back();; + for (int j = 1; j <= jmax; ++j) + { + mZPowers[j] = mZPowers[j - 1] * z; + } + + Real w = (Real)0; + int isup = static_cast(mXDegrees.size()); + for (int i = 0; i < isup; ++i) + { + Real xp = mXPowers[mXDegrees[i]]; + Real yp = mYPowers[mYDegrees[i]]; + Real zp = mYPowers[mZDegrees[i]]; + w += mParameters[i] * xp * yp * zp; + } + + // Transform w from [-1,1] back to the original space. + w = (w + (Real)1) * mInvTwoWScale + mWDomain[0]; + return w; +} + +template +void ApprPolynomialSpecial4::Transform( + std::vector> const& observations, + std::vector const& indices, + std::vector>& transformed) +{ + int numSamples = static_cast(indices.size()); + transformed.resize(numSamples); + + std::array omin = observations[indices[0]]; + std::array omax = omin; + std::array obs; + int s, i; + for (s = 1; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 4; ++i) + { + if (obs[i] < omin[i]) + { + omin[i] = obs[i]; + } + else if (obs[i] > omax[i]) + { + omax[i] = obs[i]; + } + } + } + + mXDomain[0] = omin[0]; + mXDomain[1] = omax[0]; + mYDomain[0] = omin[1]; + mYDomain[1] = omax[1]; + mZDomain[0] = omin[2]; + mZDomain[1] = omax[2]; + mWDomain[0] = omin[3]; + mWDomain[1] = omax[3]; + for (i = 0; i < 4; ++i) + { + mScale[i] = ((Real)1) / (omax[i] - omin[i]); + } + + for (s = 0; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 4; ++i) + { + transformed[s][i] = (Real)-1 + ((Real)2) * mScale[i] * + (obs[i] - omin[i]); + } + } + mInvTwoWScale = ((Real)0.5) / mScale[3]; +} + +template +bool ApprPolynomialSpecial4::DoLeastSquares( + std::vector>& transformed) +{ + // Set up a linear system A*X = B, where X are the polynomial + // coefficients. + int size = static_cast(mXDegrees.size()); + GMatrix A(size, size); + A.MakeZero(); + GVector B(size); + B.MakeZero(); + + int numSamples = static_cast(transformed.size()); + int twoMaxXDegree = 2 * mXDegrees.back(); + int twoMaxYDegree = 2 * mYDegrees.back(); + int twoMaxZDegree = 2 * mZDegrees.back(); + int row, col; + for (int i = 0; i < numSamples; ++i) + { + // Compute relevant powers of x, y, and z. + Real x = transformed[i][0]; + Real y = transformed[i][1]; + Real z = transformed[i][2]; + Real w = transformed[i][3]; + for (int j = 1; j <= twoMaxXDegree; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + for (int j = 1; j <= twoMaxYDegree; ++j) + { + mYPowers[j] = mYPowers[j - 1] * y; + } + for (int j = 1; j <= twoMaxZDegree; ++j) + { + mZPowers[j] = mZPowers[j - 1] * z; + } + + for (row = 0; row < size; ++row) + { + // Update the upper-triangular portion of the symmetric matrix. + Real xp, yp, zp; + for (col = row; col < size; ++col) + { + xp = mXPowers[mXDegrees[row] + mXDegrees[col]]; + yp = mYPowers[mYDegrees[row] + mYDegrees[col]]; + zp = mZPowers[mZDegrees[row] + mZDegrees[col]]; + A(row, col) += xp * yp * zp; + } + + // Update the right-hand side of the system. + xp = mXPowers[mXDegrees[row]]; + yp = mYPowers[mYDegrees[row]]; + zp = mZPowers[mZDegrees[row]]; + B[row] += xp * yp * zp * w; + } + } + + // Copy the upper-triangular portion of the symmetric matrix to the + // lower-triangular portion. + for (row = 0; row < size; ++row) + { + for (col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + // Precondition by normalizing the sums. + Real invNumSamples = ((Real)1) / (Real)numSamples; + A *= invNumSamples; + B *= invNumSamples; + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < size; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuadratic2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuadratic2.h new file mode 100644 index 000000000000..94c622aa4dda --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuadratic2.h @@ -0,0 +1,218 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +// The quadratic fit is +// +// 0 = C[0] + C[1]*X + C[2]*Y + C[3]*X^2 + C[4]*Y^2 + C[5]*X*Y +// +// subject to Length(C) = 1. Minimize E(C) = C^t M C with Length(C) = 1 +// and M = (sum_i V_i)(sum_i V_i)^t where +// +// V = (1, X, Y, X^2, Y^2, X*Y) +// +// The minimum value is the smallest eigenvalue of M and C is a corresponding +// unit length eigenvector. +// +// Input: +// n = number of points to fit +// p[0..n-1] = array of points to fit +// +// Output: +// c[0..5] = coefficients of quadratic fit (the eigenvector) +// return value of function is nonnegative and a measure of the fit +// (the minimum eigenvalue; 0 = exact fit, positive otherwise) + +// Canonical forms. The quadratic equation can be factored into +// P^T A P + B^T P + K = 0 where P = (X,Y,Z), K = C[0], B = (C[1],C[2],C[3]), +// and A is a 3x3 symmetric matrix with A00 = C[4], A11 = C[5], A22 = C[6], +// A01 = C[7]/2, A02 = C[8]/2, and A12 = C[9]/2. Matrix A = R^T D R where +// R is orthogonal and D is diagonal (using an eigendecomposition). Define +// V = R P = (v0,v1,v2), E = R B = (e0,e1,e2), D = diag(d0,d1,d2), and f = K +// to obtain +// +// d0 v0^2 + d1 v1^2 + d2 v^2 + e0 v0 + e1 v1 + e2 v2 + f = 0 +// +// The characterization depends on the signs of the d_i. + +template +class ApprQuadratic2 +{ +public: + Real operator()(int numPoints, Vector2 const* points, + Real coefficients[6]); +}; + + +// If you think your points are nearly circular, use this. The circle is of +// the form C'[0]+C'[1]*X+C'[2]*Y+C'[3]*(X^2+Y^2), where Length(C') = 1. The +// function returns C = (C'[0]/C'[3],C'[1]/C'[3],C'[2]/C'[3]), so the fitted +// circle is C[0]+C[1]*X+C[2]*Y+X^2+Y^2. The center is (xc,yc) = +// -0.5*(C[1],C[2]) and the radius is r = sqrt(xc*xc+yc*yc-C[0]). + +template +class ApprQuadraticCircle2 +{ +public: + Real operator()(int numPoints, Vector2 const* points, + Circle2& circle); +}; + + +template +Real ApprQuadratic2::operator()(int numPoints, + Vector2 const* points, Real coefficients[6]) +{ + Matrix<6, 6, Real> A; + for (int i = 0; i < numPoints; ++i) + { + Real x = points[i][0]; + Real y = points[i][1]; + Real x2 = x*x; + Real y2 = y*y; + Real xy = x*y; + Real x3 = x*x2; + Real xy2 = x*y2; + Real x2y = x*xy; + Real y3 = y*y2; + Real x4 = x*x3; + Real x2y2 = x*xy2; + Real x3y = x*x2y; + Real y4 = y*y3; + Real xy3 = x*y3; + + A(0, 1) += x; + A(0, 2) += y; + A(0, 3) += x2; + A(0, 4) += y2; + A(0, 5) += xy; + A(1, 3) += x3; + A(1, 4) += xy2; + A(1, 5) += x2y; + A(2, 4) += y3; + A(3, 3) += x4; + A(3, 4) += x2y2; + A(3, 5) += x3y; + A(4, 4) += y4; + A(4, 5) += xy3; + } + + A(0, 0) = static_cast(numPoints); + A(1, 1) = A(0, 3); + A(1, 2) = A(0, 5); + A(2, 2) = A(0, 4); + A(2, 3) = A(1, 5); + A(2, 5) = A(1, 4); + A(5, 5) = A(3, 4); + + for (int row = 0; row < 6; ++row) + { + for (int col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + Real invNumPoints = ((Real)1) / static_cast(numPoints); + for (int row = 0; row < 6; ++row) + { + for (int col = 0; col < 6; ++col) + { + A(row, col) *= invNumPoints; + } + } + + SymmetricEigensolver es(6, 1024); + es.Solve(&A[0], +1); + es.GetEigenvector(0, &coefficients[0]); + + // For an exact fit, numeric round-off errors might make the minimum + // eigenvalue just slightly negative. Return the absolute value since + // the application might rely on the return value being nonnegative. + return std::abs(es.GetEigenvalue(0)); +} + +template +Real ApprQuadraticCircle2::operator()(int numPoints, + Vector2 const* points, Circle2& circle) +{ + Matrix<4, 4, Real> A; + for (int i = 0; i < numPoints; ++i) + { + Real x = points[i][0]; + Real y = points[i][1]; + Real x2 = x*x; + Real y2 = y*y; + Real xy = x*y; + Real r2 = x2 + y2; + Real xr2 = x*r2; + Real yr2 = y*r2; + Real r4 = r2*r2; + + A(0, 1) += x; + A(0, 2) += y; + A(0, 3) += r2; + A(1, 1) += x2; + A(1, 2) += xy; + A(1, 3) += xr2; + A(2, 2) += y2; + A(2, 3) += yr2; + A(3, 3) += r4; + } + + A(0, 0) = static_cast(numPoints); + + for (int row = 0; row < 4; ++row) + { + for (int col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + Real invNumPoints = ((Real)1) / static_cast(numPoints); + for (int row = 0; row < 4; ++row) + { + for (int col = 0; col < 4; ++col) + { + A(row, col) *= invNumPoints; + } + } + + SymmetricEigensolver es(4, 1024); + es.Solve(&A[0], +1); + Vector<4, Real> evector; + es.GetEigenvector(0, &evector[0]); + + Real inv = ((Real)1) / evector[3]; // TODO: Guard against zero divide? + Real coefficients[3]; + for (int row = 0; row < 3; ++row) + { + coefficients[row] = inv * evector[row]; + } + + circle.center[0] = ((Real)-0.5) * coefficients[1]; + circle.center[1] = ((Real)-0.5) * coefficients[2]; + circle.radius = std::sqrt(std::abs(Dot(circle.center, circle.center) - coefficients[0])); + + // For an exact fit, numeric round-off errors might make the minimum + // eigenvalue just slightly negative. Return the absolute value since + // the application might rely on the return value being nonnegative. + return std::abs(es.GetEigenvalue(0)); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuadratic3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuadratic3.h new file mode 100644 index 000000000000..b2372c5958d3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuadratic3.h @@ -0,0 +1,285 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +// The quadratic fit is +// +// 0 = C[0] + C[1]*X + C[2]*Y + C[3]*Z + C[4]*X^2 + C[5]*Y^2 +// + C[6]*Z^2 + C[7]*X*Y + C[8]*X*Z + C[9]*Y*Z +// +// subject to Length(C) = 1. Minimize E(C) = C^t M C with Length(C) = 1 +// and M = (sum_i V_i)(sum_i V_i)^t where +// +// V = (1, X, Y, Z, X^2, Y^2, Z^2, X*Y, X*Z, Y*Z) +// +// The minimum value is the smallest eigenvalue of M and C is a corresponding +// unit length eigenvector. +// +// Input: +// n = number of points to fit +// p[0..n-1] = array of points to fit +// +// Output: +// c[0..9] = coefficients of quadratic fit (the eigenvector) +// return value of function is nonnegative and a measure of the fit +// (the minimum eigenvalue; 0 = exact fit, positive otherwise) + +// Canonical forms. The quadratic equation can be factored into +// P^T A P + B^T P + K = 0 where P = (X,Y,Z), K = C[0], B = (C[1],C[2],C[3]), +// and A is a 3x3 symmetric matrix with A00 = C[4], A11 = C[5], A22 = C[6], +// A01 = C[7]/2, A02 = C[8]/2, and A12 = C[9]/2. Matrix A = R^T D R where +// R is orthogonal and D is diagonal (using an eigendecomposition). Define +// V = R P = (v0,v1,v2), E = R B = (e0,e1,e2), D = diag(d0,d1,d2), and f = K +// to obtain +// +// d0 v0^2 + d1 v1^2 + d2 v^2 + e0 v0 + e1 v1 + e2 v2 + f = 0 +// +// The characterization depends on the signs of the d_i. + +template +class ApprQuadratic3 +{ +public: + Real operator()(int numPoints, Vector3 const* points, + Real coefficidents[10]); +}; + + +// If you think your points are nearly spherical, use this. Sphere is of form +// C'[0]+C'[1]*X+C'[2]*Y+C'[3]*Z+C'[4]*(X^2+Y^2+Z^2) where Length(C') = 1. +// Function returns C = (C'[0]/C'[4],C'[1]/C'[4],C'[2]/C'[4],C'[3]/C'[4]), so +// fitted sphere is C[0]+C[1]*X+C[2]*Y+C[3]*Z+X^2+Y^2+Z^2. Center is +// (xc,yc,zc) = -0.5*(C[1],C[2],C[3]) and radius is rad = +// sqrt(xc*xc+yc*yc+zc*zc-C[0]). +template +class ApprQuadraticSphere3 +{ +public: + Real operator()(int numPoints, Vector3 const* points, + Sphere3& sphere); +}; + + +template +Real ApprQuadratic3::operator()(int numPoints, + Vector3 const* points, Real coefficients[10]) +{ + Matrix<10, 10, Real> A; + for (int i = 0; i < numPoints; ++i) + { + Real x = points[i][0]; + Real y = points[i][1]; + Real z = points[i][2]; + Real x2 = x*x; + Real y2 = y*y; + Real z2 = z*z; + Real xy = x*y; + Real xz = x*z; + Real yz = y*z; + Real x3 = x*x2; + Real xy2 = x*y2; + Real xz2 = x*z2; + Real x2y = x*xy; + Real x2z = x*xz; + Real xyz = x*y*z; + Real y3 = y*y2; + Real yz2 = y*z2; + Real y2z = y*yz; + Real z3 = z*z2; + Real x4 = x*x3; + Real x2y2 = x*xy2; + Real x2z2 = x*xz2; + Real x3y = x*x2y; + Real x3z = x*x2z; + Real x2yz = x*xyz; + Real y4 = y*y3; + Real y2z2 = y*yz2; + Real xy3 = x*y3; + Real xy2z = x*y2z; + Real y3z = y*y2z; + Real z4 = z*z3; + Real xyz2 = x*yz2; + Real xz3 = x*z3; + Real yz3 = y*z3; + + A(0, 1) += x; + A(0, 2) += y; + A(0, 3) += z; + A(0, 4) += x2; + A(0, 5) += y2; + A(0, 6) += z2; + A(0, 7) += xy; + A(0, 8) += xz; + A(0, 9) += yz; + A(1, 4) += x3; + A(1, 5) += xy2; + A(1, 6) += xz2; + A(1, 7) += x2y; + A(1, 8) += x2z; + A(1, 9) += xyz; + A(2, 5) += y3; + A(2, 6) += yz2; + A(2, 9) += y2z; + A(3, 6) += z3; + A(4, 4) += x4; + A(4, 5) += x2y2; + A(4, 6) += x2z2; + A(4, 7) += x3y; + A(4, 8) += x3z; + A(4, 9) += x2yz; + A(5, 5) += y4; + A(5, 6) += y2z2; + A(5, 7) += xy3; + A(5, 8) += xy2z; + A(5, 9) += y3z; + A(6, 6) += z4; + A(6, 7) += xyz2; + A(6, 8) += xz3; + A(6, 9) += yz3; + A(9, 9) += y2z2; + } + + A(0, 0) = static_cast(numPoints); + A(1, 1) = A(0, 4); + A(1, 2) = A(0, 7); + A(1, 3) = A(0, 8); + A(2, 2) = A(0, 5); + A(2, 3) = A(0, 9); + A(2, 4) = A(1, 7); + A(2, 7) = A(1, 5); + A(2, 8) = A(1, 9); + A(3, 3) = A(0, 6); + A(3, 4) = A(1, 8); + A(3, 5) = A(2, 9); + A(3, 7) = A(1, 9); + A(3, 8) = A(1, 6); + A(3, 9) = A(2, 6); + A(7, 7) = A(4, 5); + A(7, 8) = A(4, 9); + A(7, 9) = A(5, 8); + A(8, 8) = A(4, 6); + A(8, 9) = A(6, 7); + A(9, 9) = A(5, 6); + + for (int row = 0; row < 10; ++row) + { + for (int col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + Real invNumPoints = ((Real)1) / static_cast(numPoints); + for (int row = 0; row < 10; ++row) + { + for (int col = 0; col < 10; ++col) + { + A(row, col) *= invNumPoints; + } + } + + SymmetricEigensolver es(10, 1024); + es.Solve(&A[0], +1); + es.GetEigenvector(0, &coefficients[0]); + + // For an exact fit, numeric round-off errors might make the minimum + // eigenvalue just slightly negative. Return the absolute value since + // the application might rely on the return value being nonnegative. + return std::abs(es.GetEigenvalue(0)); +} + +template +Real ApprQuadraticSphere3::operator()(int numPoints, + Vector3 const* points, Sphere3& sphere) +{ + Matrix<5, 5, Real> A; + for (int i = 0; i < numPoints; ++i) + { + Real x = points[i][0]; + Real y = points[i][1]; + Real z = points[i][2]; + Real x2 = x*x; + Real y2 = y*y; + Real z2 = z*z; + Real xy = x*y; + Real xz = x*z; + Real yz = y*z; + Real r2 = x2 + y2 + z2; + Real xr2 = x*r2; + Real yr2 = y*r2; + Real zr2 = z*r2; + Real r4 = r2*r2; + + A(0, 1) += x; + A(0, 2) += y; + A(0, 3) += z; + A(0, 4) += r2; + A(1, 1) += x2; + A(1, 2) += xy; + A(1, 3) += xz; + A(1, 4) += xr2; + A(2, 2) += y2; + A(2, 3) += yz; + A(2, 4) += yr2; + A(3, 3) += z2; + A(3, 4) += zr2; + A(4, 4) += r4; + } + + A(0, 0) = static_cast(numPoints); + + for (int row = 0; row < 5; ++row) + { + for (int col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + Real invNumPoints = ((Real)1) / static_cast(numPoints); + for (int row = 0; row < 5; ++row) + { + for (int col = 0; col < 5; ++col) + { + A(row, col) *= invNumPoints; + } + } + + SymmetricEigensolver es(5, 1024); + es.Solve(&A[0], +1); + Vector<5, Real> evector; + es.GetEigenvector(0, &evector[0]); + + Real inv = ((Real)1) / evector[4]; // TODO: Guard against zero divide? + Real coefficients[4]; + for (int row = 0; row < 4; ++row) + { + coefficients[row] = inv * evector[row]; + } + + sphere.center[0] = ((Real)-0.5) * coefficients[1]; + sphere.center[1] = ((Real)-0.5) * coefficients[2]; + sphere.center[2] = ((Real)-0.5) * coefficients[3]; + sphere.radius = std::sqrt(std::abs(Dot(sphere.center, sphere.center) - coefficients[0])); + + // For an exact fit, numeric round-off errors might make the minimum + // eigenvalue just slightly negative. Return the absolute value since + // the application might rely on the return value being nonnegative. + return std::abs(es.GetEigenvalue(0)); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuery.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuery.h new file mode 100644 index 000000000000..1559c67f7464 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuery.h @@ -0,0 +1,207 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Class ApprQuery supports the RANSAC algorithm for fitting and uses the +// Curiously Recurring Template Pattern. The ModelType must be a class or +// struct with the following interfaces: +// +// // The minimum number of observations required to fit the model. +// int ModelType::GetMinimumRequired() const; +// +// // Compute the model error for the specified observation for the current +// // model parameters. +// Real Error(ObservationType const& observation) const; +// +// // Estimate the model parameters for all observations specified by the +// // indices. The three Fit() functions of ApprQuery manipulate their +// // inputs in order to pass them to ModelType::Fit(). +// ModelType::Fit(std::vector const& observations, +// std::vector const& indices); + +namespace gte +{ + +template +class ApprQuery +{ +public: + // Estimate the model parameters for all observations. + bool Fit(std::vector const& observations); + + // Estimate the model parameters for a contiguous subset of observations. + bool Fit(std::vector const& observations, + int const imin, int const imax); + + // Estimate the model parameters for the subset of observations specified + // by the indices and the number of indices that is possibly smaller than + // indices.size(). + bool Fit(std::vector const& observations, + std::vector const& indices, int const numIndices); + + // Apply the RANdom SAmple Consensus algorithm for fitting a model to + // observations. + static bool RANSAC( + ModelType& candidateModel, + std::vector const& observations, + int const numRequiredForGoodFit, Real const maxErrorForGoodFit, + int const numIterations, std::vector& bestConsensus, + ModelType& bestModel); +}; + + +template +bool ApprQuery::Fit( + std::vector const& observations) +{ + std::vector indices(observations.size()); + int i = 0; + for (auto& index : indices) + { + index = i++; + } + + return ((ModelType*)this)->Fit(observations, indices); +} + +template +bool ApprQuery::Fit( + std::vector const& observations, + int const imin, int const imax) +{ + if (imin <= imax) + { + int numIndices = imax - imin + 1; + std::vector indices(numIndices); + int i = imin; + for (auto& index : indices) + { + index = i++; + } + + return ((ModelType*)this)->Fit(observations, indices); + } + else + { + return false; + } +} + +template +bool ApprQuery::Fit( + std::vector const& observations, + std::vector const& indices, int const numIndices) +{ + int imax = std::min(numIndices, static_cast(observations.size())); + std::vector localindices(imax); + int i = 0; + for (auto& index : localindices) + { + index = indices[i++]; + } + + return ((ModelType*)this)->Fit(observations, indices); +} + +template +bool ApprQuery::RANSAC( + ModelType& candidateModel, + std::vector const& observations, + int const numRequiredForGoodFit, Real const maxErrorForGoodFit, + int const numIterations, std::vector& bestConsensus, + ModelType& bestModel) +{ + int const numObservations = static_cast(observations.size()); + int const minRequired = candidateModel.GetMinimumRequired(); + if (numObservations < minRequired) + { + // Too few observations for model fitting. + return false; + } + + // The first part of the array will store the consensus set, initially + // filled with the minimumu number of indices that correspond to the + // candidate inliers. The last part will store the remaining indices. + // These points are tested against the model and are added to the + // consensus set when they fit. All the index manipulation is done + // in place. Initially, the candidates are the identity permutation. + std::vector candidates(numObservations); + int j = 0; + for (auto& c : candidates) + { + c = j++; + } + + if (numObservations == minRequired) + { + // We have the minimum number of observations to generate the model, + // so RANSAC cannot be used. Compute the model with the entire set + // of observations. + bestConsensus = candidates; + return bestModel.Fit(observations); + } + + int bestNumFittedObservations = minRequired; + + for (int i = 0; i < numIterations; ++i) + { + // Randomly permute the previous candidates, partitioning the array + // into GetMinimumRequired() indices (the candidate inliers) followed + // by the remaining indices (candidates for testing against the + // model). + std::shuffle(candidates.begin(), candidates.end(), + std::default_random_engine()); + + // Fit the model to the inliers. + if (candidateModel.Fit(observations, candidates, minRequired)) + { + // Test each remaining observation whether it fits the model. If + // it does, include it in the consensus set. + int numFittedObservations = minRequired; + for (j = minRequired; j < numObservations; ++j) + { + if (candidateModel.Error(observations[candidates[j]]) + <= maxErrorForGoodFit) + { + std::swap(candidates[j], + candidates[numFittedObservations]); + ++numFittedObservations; + } + } + + if (numFittedObservations >= numRequiredForGoodFit) + { + // We have observations that fit the model. Update the best + // model using the consensus set. + candidateModel.Fit(observations, candidates, + numFittedObservations); + if (numFittedObservations > bestNumFittedObservations) + { + // The consensus set is larger than the previous consensus + // set, so its model becomes the best one. + bestModel = candidateModel; + bestConsensus.resize(numFittedObservations); + std::copy(candidates.begin(), + candidates.begin() + numFittedObservations, + bestConsensus.begin()); + bestNumFittedObservations = numFittedObservations; + } + } + } + } + + return bestNumFittedObservations >= numRequiredForGoodFit; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprSphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprSphere3.h new file mode 100644 index 000000000000..476516fb8fde --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprSphere3.h @@ -0,0 +1,101 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Least-squares fit of a sphere to a set of points. A successful fit is +// indicated by the return value of 'true'. The return value is the number +// of iterations used. If the number is the maximum, you can either accept +// the result or try increasing the maximum number and calling the function +// again. TODO: Currently, the test for terminating the algorithm is exact +// (diff == (0,0,0)). Expose an epsilon for this? +// +// If initialCenterIsAverage is set to 'true', the initial guess for the +// sphere center is the average of the data points. If the data points are +// clustered along a solid angle, ApprSphere32 is very slow to converge. If +// initialCenterIsAverage is set to 'false', the initial guess for the +// sphere center is computed using a least-squares estimate of the +// coefficients for a quadratic equation that represents a sphere. This +// approach tends to converge rapidly. + +namespace gte +{ + +template +class ApprSphere3 +{ +public: + unsigned int operator()(int numPoints, Vector3 const* points, + unsigned int maxIterations, bool initialCenterIsAverage, + Sphere3& sphere); +}; + + +template +unsigned int ApprSphere3::operator()(int numPoints, + Vector3 const* points, unsigned int maxIterations, + bool initialCenterIsAverage, Sphere3& sphere) +{ + // Compute the average of the data points. + Vector3 average = points[0]; + for (int i = 1; i < numPoints; ++i) + { + average += points[i]; + } + Real invNumPoints = ((Real)1) / static_cast(numPoints); + average *= invNumPoints; + + // The initial guess for the center. + if (initialCenterIsAverage) + { + sphere.center = average; + } + else + { + ApprQuadraticSphere3()(numPoints, points, sphere); + } + + unsigned int iteration; + for (iteration = 0; iteration < maxIterations; ++iteration) + { + // Update the iterates. + Vector3 current = sphere.center; + + // Compute average L, dL/da, dL/db, dL/dc. + Real lenAverage = (Real)0; + Vector3 derLenAverage = Vector3::Zero(); + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - sphere.center; + Real length = Length(diff); + if (length >(Real)0) + { + lenAverage += length; + Real invLength = ((Real)1) / length; + derLenAverage -= invLength * diff; + } + } + lenAverage *= invNumPoints; + derLenAverage *= invNumPoints; + + sphere.center = average + lenAverage * derLenAverage; + sphere.radius = lenAverage; + + Vector3 diff = sphere.center - current; + if (diff == Vector3::Zero()) + { + break; + } + } + + return ++iteration; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprTorus3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprTorus3.h new file mode 100644 index 000000000000..358cf831f16c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprTorus3.h @@ -0,0 +1,397 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.14.3 (2018/10/30) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Let the torus center be C with plane of symmetry containing C and having +// directions D0 and D1. The axis of symmetry is the line containing C and +// having direction N (the plane normal). The radius from the center of the +// torus is r0 and the radius of the tube of the torus is r1. A point P may +// be written as P = C + x*D0 + y*D1 + z*N, where matrix [D0 D1 N] is +// orthogonal and has determinant 1. Thus, x = Dot(D0,P-C), y = Dot(D1,P-C) +// and z = Dot(N,P-C). The implicit equation defining the torus is +// (|P-C|^2 + r0^2 - r1^2)^2 - 4*r0^2*(|P-C|^2 - (Dot(N,P-C))^2) = 0 +// Observe that D0 and D1 are not present in the equation, which is to be +// expected by the symmetry. +// +// Define u = r0^2 and v = r0^2 - r1^2. Define +// F(X;C,N,u,v) = (|P-C|^2 + v)^2 - 4*u*(|P-C|^2 - (Dot(N,P-C))^2) +// The nonlinear least-squares fitting of points {X[i]}_{i=0}^{n-1} computes +// C, N, u and v to minimize the error function +// E(C,N,u,v) = sum_{i=0}^{n-1} F(X[i];C,N,u,v)^2 +// When the sample points are distributed so that there is large coverage +// by a purported fitted torus, a variation on fitting is the following. +// Compute the least-squares plane with origin C and normal N that fits the +// points. Define G(X;u,v) = F(X;C,N,u,v); the only variables now are u and +// v. Define L[i] = |X[i]-C|^2 and S[i] = 4 * (L[i] - (Dot(N,X[i]-C))^2). +// Define the error function +// H(u,v) = sum_{i=0}^{n-1} G(X[i];u,v)^2 +// = sum_{i=0}^{n-1} ((v + L[i])^2 - S[i]*u)^2 +// The first-order partial derivatives are +// dH/du = -2 sum_{i=0}^{n-1} ((v + L[i])^2 - S[i]*u) * S[i] +// dH/dv = 4 sum_{i=0}^{n-1} ((v + L[i])^2 - S[i]*u) * (v + L[i]) +// Setting these to zero and expanding the terms, we have +// 0 = a2 * v^2 + a1 * v + a0 - b0 * u +// 0 = c3 * v^3 + c2 * v^2 + c1 * v + c0 - u * (d1 * v + d0) +// where a2 = sum(S[i]), a1 = 2*sum(S[i]*L[i]), a2 = sum(S[i]*L[i]^2), +// b0 = sum(S[i]^2), c3 = sum(1) = n, c2 = 3*sum(L[i]), c1 = 3*sum(L[i]^2), +// c0 = sum(L[i]^3), d1 = sum(S[i]) = a2 and d0 = sum(S[i]*L[i]) = a1/2. +// The first equation is solved for +// u = (a2 * v^2 + a1 * v + a0) / b0 = e2 * v^2 + e1 * v + e0 +// and substituted into the second equation to obtain a cubic polynomial +// equation +// 0 = f3 * v^3 + f2 * v^2 + f1 * v + f0 +// where f3 = c3 - d1 * e2, f2 = c2 - d1 * e1 - d0 * e2, +// f1 = c1 - d1 * e0 - d0 * e1 and f0 = c0 - d0 * e0. The positive v-roots +// are computed. For each root compute the corresponding u. For all pairs +// (u,v) with u > v > 0, evaluate H(u,v) and choose the pair that minimizes +// H(u,v). The torus radii are r0 = sqrt(u) and r1 = sqrt(u - v). + +namespace gte +{ + template + class ApprTorus3 + { + public: + ApprTorus3() + { + // The unit-length normal is + // N = (cos(theta)*sin(phi), sin(theta)*sin(phi), cos(phi) + // for theta in [0,2*pi) and phi in [0,*pi). The radii are + // encoded as + // u = r0^2, v = r0^2 - r1^2 + // with 0 < v < u. Let D = C - X[i] where X[i] is a sample point. + // The parameters P = (C0,C1,C2,theta,phi,u,v). + + // F[i](C,theta,phi,u,v) = + // (|D|^2 + v)^2 - 4*u*(|D|^2 - Dot(N,D)^2) + mFFunction = [this](GVector const& P, GVector& F) + { + Real csTheta = std::cos(P[3]); + Real snTheta = std::sin(P[3]); + Real csPhi = std::cos(P[4]); + Real snPhi = std::sin(P[4]); + Vector<3, Real> C = { P[0], P[1], P[2] }; + Vector<3, Real> N = { csTheta * snPhi, snTheta * snPhi, csPhi }; + Real u = P[5]; + Real v = P[6]; + for (int i = 0; i < mNumPoints; ++i) + { + Vector<3, Real> D = C - mPoints[i]; + Real DdotD = Dot(D, D), NdotD = Dot(N, D); + Real sum = DdotD + v; + F[i] = sum * sum - (Real)4 * u * (DdotD - NdotD * NdotD); + } + }; + + // dF[i]/dC = 4 * (|D|^2 + v) * D - 8 * u * (I - N*N^T) * D + // dF[i]/dTheta = 8 * u * Dot(dN/dTheta, D) + // dF[i]/dPhi = 8 * u * Dot(dN/dPhi, D) + // dF[i]/du = -4 * u * (|D|^2 - Dot(N,D)^2) + // dF[i]/dv = 2 * (|D|^2 + v) + mJFunction = [this](GVector const& P, GMatrix& J) + { + Real const r2(2), r4(4), r8(8); + Real csTheta = std::cos(P[3]); + Real snTheta = std::sin(P[3]); + Real csPhi = std::cos(P[4]); + Real snPhi = std::sin(P[4]); + Vector<3, Real> C = { P[0], P[1], P[2] }; + Vector<3, Real> N = { csTheta * snPhi, snTheta * snPhi, csPhi }; + Real u = P[5]; + Real v = P[6]; + for (int row = 0; row < mNumPoints; ++row) + { + Vector<3, Real> D = C - mPoints[row]; + Real DdotD = Dot(D, D), NdotD = Dot(N, D); + Real sum = DdotD + v; + Vector<3, Real> dNdTheta{ -snTheta * snPhi, csTheta * snPhi, (Real)0 }; + Vector<3, Real> dNdPhi{ csTheta * csPhi, snTheta * csPhi, -snPhi }; + Vector<3, Real> temp = r4 * sum * D - r8 * u * (D - NdotD * N); + J(row, 0) = temp[0]; + J(row, 1) = temp[1]; + J(row, 2) = temp[2]; + J(row, 3) = r8 * u * Dot(dNdTheta, D); + J(row, 4) = r8 * u * Dot(dNdPhi, D); + J(row, 5) = -r4 * u * (DdotD - NdotD * NdotD); + J(row, 6) = r2 * sum; + } + }; + } + + // When the samples are distributed approximately uniformly near a + // torus, use this method. For example, if the purported torus has + // center (0,0,0) and normal (0,0,1), you want the (x,y,z) samples + // to occur in all 8 octants. If the samples occur, say, only in + // one octant, this method will estimate a C and N that are nowhere + // near (0,0,0) and (0,0,1). The function sets the output variables + // C, N, r0 and r1 as the fitted torus. + // + // The return value is a pair . The first element is + // 'true' when the estimate is valid, in which case the second + // element is the least-squares error for that estimate. If any + // unexpected condition occurs that prevents computing an estimate, + // the first element is 'false' and the second element is + // std::numeric_limits::max(). + std::pair + operator()(int numPoints, Vector<3, Real> const* points, + Vector<3, Real>& C, Vector<3, Real>& N, Real& r0, Real& r1) + { + ApprOrthogonalPlane3 fitter; + if (!fitter.Fit(numPoints, points)) + { + return std::make_pair(false, std::numeric_limits::max()); + } + C = fitter.GetParameters().first; + N = fitter.GetParameters().second; + + Real const zero(0); + Real a0 = zero, a1 = zero, a2 = zero, b0 = zero; + Real c0 = zero, c1 = zero, c2 = zero, c3 = (Real)numPoints; + for (int i = 0; i < numPoints; ++i) + { + Vector<3, Real> delta = points[i] - C; + Real dot = Dot(N, delta); + Real L = Dot(delta, delta), L2 = L * L, L3 = L * L2; + Real S = (Real)4 * (L - dot * dot), S2 = S * S; + a2 += S; + a1 += S * L; + a0 += S * L2; + b0 += S2; + c2 += L; + c1 += L2; + c0 += L3; + } + Real d1 = a2; + Real d0 = a1; + a1 *= (Real)2; + c2 *= (Real)3; + c1 *= (Real)3; + Real invB0 = (Real)1 / b0; + Real e0 = a0 * invB0; + Real e1 = a1 * invB0; + Real e2 = a2 * invB0; + + Rational f0 = c0 - d0 * e0; + Rational f1 = c1 - d1 * e0 - d0 * e1; + Rational f2 = c2 - d1 * e1 - d0 * e2; + Rational f3 = c3 - d1 * e2; + std::map rmMap; + RootsPolynomial::SolveCubic(f0, f1, f2, f3, rmMap); + + Real hmin = std::numeric_limits::max(); + Real umin = zero, vmin = zero; + for (auto const& element : rmMap) + { + Real v = element.first; + if (v > zero) + { + Real u = e0 + v * (e1 + v * e2); + if (u > v) + { + Real h = zero; + for (int i = 0; i < numPoints; ++i) + { + Vector<3, Real> delta = points[i] - C; + Real dot = Dot(N, delta); + Real L = Dot(delta, delta); + Real S = (Real)4 * (L - dot * dot); + Real sum = v + L; + Real term = sum * sum - S * u; + h += term * term; + } + if (h < hmin) + { + hmin = h; + umin = u; + vmin = v; + } + } + } + } + + if (hmin == std::numeric_limits::max()) + { + return std::make_pair(false, std::numeric_limits::max()); + } + + r0 = std::sqrt(umin); + r1 = std::sqrt(umin - vmin); + return std::make_pair(true, hmin); + } + + // If you want to specify that C, N, r0 and r1 are the initial guesses + // for the minimizer, set the parameter useTorusInputAsInitialGuess to + // 'true'. If you want the function to compute initial guesses, set + // useTorusInputAsInitialGuess to 'false'. A Gauss-Newton minimizer + // is used to fit a torus using nonlinear least-squares. The fitted + // torus is returned in C, N, r0 and r1. See GteGaussNewtonMinimizer.h + // for a description of the least-squares algorithm and the parameters + // that it requires. + typename GaussNewtonMinimizer::Result + operator()(int numPoints, Vector<3, Real> const* points, + size_t maxIterations, Real updateLengthTolerance, Real errorDifferenceTolerance, + bool useTorusInputAsInitialGuess, + Vector<3, Real>& C, Vector<3, Real>& N, Real& r0, Real& r1) + { + mNumPoints = numPoints; + mPoints = points; + GaussNewtonMinimizer minimizer(7, mNumPoints, mFFunction, mJFunction); + + if (!useTorusInputAsInitialGuess) + { + operator()(numPoints, points, C, N, r0, r1); + } + + GVector initial(7); + + // The initial guess for the plane origin. + initial[0] = C[0]; + initial[1] = C[1]; + initial[2] = C[2]; + + // The initial guess for the plane normal. The angles must be + // extracted for spherical coordinates. + if (std::abs(N[2]) < (Real)1) + { + initial[3] = std::atan2(N[1], N[0]); + initial[4] = std::acos(N[2]); + } + else + { + initial[3] = (Real)0; + initial[4] = (Real)0; + } + + // The initial guess for the radii-related parameters. + initial[5] = r0 * r0; + initial[6] = initial[5] - r1 * r1; + + auto result = minimizer(initial, maxIterations, updateLengthTolerance, + errorDifferenceTolerance); + + // No test is made for result.converged so that we return some + // estimates of the torus. The caller can decide how to respond + // when result.converged is false. + C[0] = result.minLocation[0]; + C[1] = result.minLocation[1]; + C[2] = result.minLocation[2]; + + Real theta = result.minLocation[3]; + Real phi = result.minLocation[4]; + Real csTheta = std::cos(theta); + Real snTheta = std::sin(theta); + Real csPhi = std::cos(phi); + Real snPhi = std::sin(phi); + N[0] = csTheta * snPhi; + N[1] = snTheta * snPhi; + N[2] = csPhi; + + Real u = result.minLocation[5]; + Real v = result.minLocation[6]; + r0 = std::sqrt(u); + r1 = std::sqrt(u - v); + + mNumPoints = 0; + mPoints = nullptr; + return result; + } + + // If you want to specify that C, N, r0 and r1 are the initial guesses + // for the minimizer, set the parameter useTorusInputAsInitialGuess to + // 'true'. If you want the function to compute initial guesses, set + // useTorusInputAsInitialGuess to 'false'. A Gauss-Newton minimizer + // is used to fit a torus using nonlinear least-squares. The fitted + // torus is returned in C, N, r0 and r1. See GteGaussNewtonMinimizer.h + // for a description of the least-squares algorithm and the parameters + // that it requires. + typename LevenbergMarquardtMinimizer::Result + operator()(int numPoints, Vector<3, Real> const* points, + size_t maxIterations, Real updateLengthTolerance, Real errorDifferenceTolerance, + Real lambdaFactor, Real lambdaAdjust, size_t maxAdjustments, + bool useTorusInputAsInitialGuess, + Vector<3, Real>& C, Vector<3, Real>& N, Real& r0, Real& r1) + { + mNumPoints = numPoints; + mPoints = points; + LevenbergMarquardtMinimizer minimizer(7, mNumPoints, mFFunction, mJFunction); + + if (!useTorusInputAsInitialGuess) + { + operator()(numPoints, points, C, N, r0, r1); + } + + GVector initial(7); + + // The initial guess for the plane origin. + initial[0] = C[0]; + initial[1] = C[1]; + initial[2] = C[2]; + + // The initial guess for the plane normal. The angles must be + // extracted for spherical coordinates. + if (std::abs(N[2]) < (Real)1) + { + initial[3] = std::atan2(N[1], N[0]); + initial[4] = std::acos(N[2]); + } + else + { + initial[3] = (Real)0; + initial[4] = (Real)0; + } + + // The initial guess for the radii-related parameters. + initial[5] = r0 * r0; + initial[6] = initial[5] - r1 * r1; + + auto result = minimizer(initial, maxIterations, updateLengthTolerance, + errorDifferenceTolerance, lambdaFactor, lambdaAdjust, maxAdjustments); + + // No test is made for result.converged so that we return some + // estimates of the torus. The caller can decide how to respond + // when result.converged is false. + C[0] = result.minLocation[0]; + C[1] = result.minLocation[1]; + C[2] = result.minLocation[2]; + + Real theta = result.minLocation[3]; + Real phi = result.minLocation[4]; + Real csTheta = std::cos(theta); + Real snTheta = std::sin(theta); + Real csPhi = std::cos(phi); + Real snPhi = std::sin(phi); + N[0] = csTheta * snPhi; + N[1] = snTheta * snPhi; + N[2] = csPhi; + + Real u = result.minLocation[5]; + Real v = result.minLocation[6]; + r0 = std::sqrt(u); + r1 = std::sqrt(u - v); + + mNumPoints = 0; + mPoints = nullptr; + return result; + } + + private: + typedef BSRational Rational; + + int mNumPoints; + Vector<3, Real> const* mPoints; + std::function const&, GVector&)> mFFunction; + std::function const&, GMatrix&)> mJFunction; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteArbitraryPrecision.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteArbitraryPrecision.h new file mode 100644 index 000000000000..ea7ff7c33565 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteArbitraryPrecision.h @@ -0,0 +1,15 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.21.0 (2019/01/17) + +#pragma once + +#include +#include +#include +#include +#include +#include diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteArc2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteArc2.h new file mode 100644 index 000000000000..7d5ccc0a1fac --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteArc2.h @@ -0,0 +1,154 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +// The circle containing the arc is represented as |X-C| = R where C is the +// center and R is the radius. The arc is defined by two points end0 and end1 +// on the circle so that end1 is obtained from end0 by traversing +// counterclockwise. The application is responsible for ensuring that end0 +// and end1 are on the circle and that they are properly ordered. + +template +class Arc2 +{ +public: + // Construction and destruction. The default constructor sets the center + // to (0,0), radius to 1, end0 to (1,0), and end1 to (0,1). + Arc2(); + Arc2(Vector2 const& inCenter, Real inRadius, + Vector2const& inEnd0, Vector2const & inEnd1); + + // Test whether P is on the arc. The application must ensure that P is on + // the circle; that is, |P-C| = R. This test works for any angle between + // B-C and A-C, not just those between 0 and pi radians. + bool Contains(Vector2 const& p) const; + + Vector2 center; + Real radius; + Vector2 end[2]; + +public: + // Comparisons to support sorted containers. + bool operator==(Arc2 const& arc) const; + bool operator!=(Arc2 const& arc) const; + bool operator< (Arc2 const& arc) const; + bool operator<=(Arc2 const& arc) const; + bool operator> (Arc2 const& arc) const; + bool operator>=(Arc2 const& arc) const; +}; + + +template +Arc2::Arc2() + : + center(Vector2::Zero()), + radius((Real)1) +{ + end[0] = Vector2::Unit(0); + end[1] = Vector2::Unit(1); +} + +template +Arc2::Arc2(Vector2 const& inCenter, Real inRadius, + Vector2const& inEnd0, Vector2const & inEnd1) + : + center(inCenter), + radius(inRadius) +{ + end[0] = inEnd0; + end[1] = inEnd1; +} + +template +bool Arc2::Contains(Vector2 const& p) const +{ + // Assert: |P-C| = R where P is the input point, C is the circle center, + // and R is the circle radius. For P to be on the arc from A to B, it + // must be on the side of the plane containing A with normal N = Perp(B-A) + // where Perp(u,v) = (v,-u). + + Vector2 diffPE0 = p - end[0]; + Vector2 diffE1E0 = end[1] - end[0]; + Real dotPerp = DotPerp(diffPE0, diffE1E0); + return dotPerp >= (Real)0; +} + +template +bool Arc2::operator==(Arc2 const& arc) const +{ + return center == arc.center && radius == arc.radius + && end[0] == arc.end[0] && end[1] == arc.end[1]; +} + +template +bool Arc2::operator!=(Arc2 const& arc) const +{ + return !operator==(arc); +} + +template +bool Arc2::operator<(Arc2 const& arc) const +{ + if (center < arc.center) + { + return true; + } + + if (center > arc.center) + { + return false; + } + + if (radius < arc.radius) + { + return true; + } + + if (radius > arc.radius) + { + return false; + } + + if (end[0] < arc.end[0]) + { + return true; + } + + if (end[0] > arc.end[0]) + { + return false; + } + + return end[1] < arc.end[1]; +} + +template +bool Arc2::operator<=(Arc2 const& arc) const +{ + return operator<(arc) || operator==(arc); +} + +template +bool Arc2::operator>(Arc2 const& arc) const +{ + return !operator<=(arc); +} + +template +bool Arc2::operator>=(Arc2 const& arc) const +{ + return !operator<(arc); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteAxisAngle.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteAxisAngle.h new file mode 100644 index 000000000000..2f28a39bbc2d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteAxisAngle.h @@ -0,0 +1,48 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +// Axis-angle representation for N = 3 or N = 4. When N = 4, the axis +// must be a vector of the form (x,y,z,0) [affine representation of the +// 3-tuple direction]. + +template +class AxisAngle +{ +public: + AxisAngle(); + AxisAngle(Vector const& inAxis, Real inAngle); + + Vector axis; + Real angle; +}; + + +template +AxisAngle::AxisAngle() +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + // Uninitialized. +} + +template +AxisAngle::AxisAngle(Vector const& inAxis, Real inAngle) + : + axis(inAxis), + angle(inAngle) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSNumber.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSNumber.h new file mode 100644 index 000000000000..4f99397d7dd7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSNumber.h @@ -0,0 +1,1106 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2019/01/17) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The class BSNumber (binary scientific number) is designed to provide exact +// arithmetic for robust algorithms, typically those for which we need to know +// the exact sign of determinants. The template parameter UIntegerType must +// have support for at least the following public interface. The fstream +// objects for Write/Read must be created using std::ios::binary. The return +// value of Write/Read is 'true' iff the operation was successful. +// +// class UIntegerType +// { +// public: +// UIntegerType(); +// UIntegerType(UIntegerType const& number); +// UIntegerType(uint32_t number); +// UIntegerType(uint64_t number); +// UIntegerType(int numBits); +// UIntegerType& operator=(UIntegerType const& number); +// UIntegerType(UIntegerType&& number); +// UIntegerType& operator=(UIntegerType&& number); +// int32_t GetNumBits() const; +// bool operator==(UIntegerType const& number) const; +// bool operator< (UIntegerType const& number) const; +// void Add(UIntegerType const& n0, UIntegerType const& n1); +// void Sub(UIntegerType const& n0, UIntegerType const& n1); +// void Mul(UIntegerType const& n0, UIntegerType const& n1); +// void ShiftLeft(UIntegerType const& number, int shift); +// int32_t ShiftRightToOdd(UIntegerType const& number); +// uint64_t GetPrefix(int numRequested) const; +// bool Write(std::ofstream& output) const; +// bool Read(std::ifstream& input); +// }; +// +// GTEngine currently has 32-bits-per-word storage for UIntegerType. See the +// classes UIntegerAP32 (arbitrary precision), UIntegerFP32 (fixed +// precision), and UIntegerALU32 (arithmetic logic unit shared by the previous +// two classes). The document at the following link describes the design, +// implementation, and use of BSNumber and BSRational. +// http://www.geometrictools.com/Documentation/ArbitraryPrecision.pdf +// +// Support for debugging algorithms that use exact rational arithmetic. Each +// BSNumber and BSRational has a double-precision member that is exposed when +// the conditional define is enabled. Be aware that this can be very slow +// because of the conversion to double-precision whenever new objects are +// created by arithmetic operations. As a faster alternative, you can add +// temporary code in your algorithms that explicitly convert specific rational +// numbers to double precision. +// +//#define GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE + +namespace gte +{ + template class BSRational; + + template + class BSNumber + { + public: + // Construction. The default constructor generates the zero BSNumber. + BSNumber() + : + mSign(0), + mBiasedExponent(0) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(BSNumber const& number) + { + *this = number; + } + + BSNumber(float number) + { + ConvertFrom(number); +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(double number) + { + ConvertFrom(number); +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(int32_t number) + { + if (number == 0) + { + mSign = 0; + mBiasedExponent = 0; + } + else + { + if (number < 0) + { + mSign = -1; + number = -number; + } + else + { + mSign = 1; + } + + mBiasedExponent = GetTrailingBit(number); + mUInteger = (uint32_t)number; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(uint32_t number) + { + if (number == 0) + { + mSign = 0; + mBiasedExponent = 0; + } + else + { + mSign = 1; + mBiasedExponent = GetTrailingBit(number); + mUInteger = number; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(int64_t number) + { + if (number == 0) + { + mSign = 0; + mBiasedExponent = 0; + } + else + { + if (number < 0) + { + mSign = -1; + number = -number; + } + else + { + mSign = 1; + } + + mBiasedExponent = GetTrailingBit(number); + mUInteger = (uint64_t)number; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(uint64_t number) + { + if (number == 0) + { + mSign = 0; + mBiasedExponent = 0; + } + else + { + mSign = 1; + mBiasedExponent = GetTrailingBit(number); + mUInteger = number; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + // Implicit conversions. + inline operator float() const + { + return ConvertTo(); + } + + inline operator double() const + { + return ConvertTo(); + } + + // Assignment. + BSNumber& operator=(BSNumber const& number) + { + mSign = number.mSign; + mBiasedExponent = number.mBiasedExponent; + mUInteger = number.mUInteger; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = number.mValue; +#endif + return *this; + } + + // Support for std::move. + BSNumber(BSNumber&& number) + { + *this = std::move(number); + } + + BSNumber& operator=(BSNumber&& number) + { + mSign = number.mSign; + mBiasedExponent = number.mBiasedExponent; + mUInteger = std::move(number.mUInteger); + number.mSign = 0; + number.mBiasedExponent = 0; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = number.mValue; +#endif + return *this; + } + + // Member access. + inline int32_t GetSign() const + { + return mSign; + } + + inline int32_t GetBiasedExponent() const + { + return mBiasedExponent; + } + + inline int32_t GetExponent() const + { + return mBiasedExponent + mUInteger.GetNumBits() - 1; + } + + inline UIntegerType const& GetUInteger() const + { + return mUInteger; + } + + // Comparisons. + bool operator==(BSNumber const& number) const + { + return (mSign == number.mSign ? EqualIgnoreSign(*this, number) : false); + } + + bool operator!=(BSNumber const& number) const + { + return !operator==(number); + } + + bool operator< (BSNumber const& number) const + { + if (mSign > 0) + { + if (number.mSign <= 0) + { + return false; + } + + // Both numbers are positive. + return LessThanIgnoreSign(*this, number); + } + else if (mSign < 0) + { + if (number.mSign >= 0) + { + return true; + } + + // Both numbers are negative. + return LessThanIgnoreSign(number, *this); + } + else + { + return number.mSign > 0; + } + } + + bool operator<=(BSNumber const& number) const + { + return !number.operator<(*this); + } + + bool operator> (BSNumber const& number) const + { + return number.operator<(*this); + } + + bool operator>=(BSNumber const& number) const + { + return !operator<(number); + } + + // Unary operations. + BSNumber operator+() const + { + return *this; + } + + BSNumber operator-() const + { + BSNumber result = *this; + result.mSign = -result.mSign; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + result.mValue = (double)result; +#endif + return result; + } + + // Arithmetic. + BSNumber operator+(BSNumber const& n1) const + { + BSNumber const& n0 = *this; + + if (n0.mSign == 0) + { + return n1; + } + + if (n1.mSign == 0) + { + return n0; + } + + if (n0.mSign > 0) + { + if (n1.mSign > 0) + { + // n0 + n1 = |n0| + |n1| + return AddIgnoreSign(n0, n1, +1); + } + else // n1.mSign < 0 + { + if (!EqualIgnoreSign(n0, n1)) + { + if (LessThanIgnoreSign(n1, n0)) + { + // n0 + n1 = |n0| - |n1| > 0 + return SubIgnoreSign(n0, n1, +1); + } + else + { + // n0 + n1 = -(|n1| - |n0|) < 0 + return SubIgnoreSign(n1, n0, -1); + } + } + // else n0 + n1 = 0 + } + } + else // n0.mSign < 0 + { + if (n1.mSign < 0) + { + // n0 + n1 = -(|n0| + |n1|) + return AddIgnoreSign(n0, n1, -1); + } + else // n1.mSign > 0 + { + if (!EqualIgnoreSign(n0, n1)) + { + if (LessThanIgnoreSign(n1, n0)) + { + // n0 + n1 = -(|n0| - |n1|) < 0 + return SubIgnoreSign(n0, n1, -1); + } + else + { + // n0 + n1 = |n1| - |n0| > 0 + return SubIgnoreSign(n1, n0, +1); + } + } + // else n0 + n1 = 0 + } + } + + return BSNumber(); // = 0 + } + + BSNumber operator-(BSNumber const& n1) const + { + BSNumber const& n0 = *this; + + if (n0.mSign == 0) + { + return -n1; + } + + if (n1.mSign == 0) + { + return n0; + } + + if (n0.mSign > 0) + { + if (n1.mSign < 0) + { + // n0 - n1 = |n0| + |n1| + return AddIgnoreSign(n0, n1, +1); + } + else // n1.mSign > 0 + { + if (!EqualIgnoreSign(n0, n1)) + { + if (LessThanIgnoreSign(n1, n0)) + { + // n0 - n1 = |n0| - |n1| > 0 + return SubIgnoreSign(n0, n1, +1); + } + else + { + // n0 - n1 = -(|n1| - |n0|) < 0 + return SubIgnoreSign(n1, n0, -1); + } + } + // else n0 - n1 = 0 + } + } + else // n0.mSign < 0 + { + if (n1.mSign > 0) + { + // n0 - n1 = -(|n0| + |n1|) + return AddIgnoreSign(n0, n1, -1); + } + else // n1.mSign < 0 + { + if (!EqualIgnoreSign(n0, n1)) + { + if (LessThanIgnoreSign(n1, n0)) + { + // n0 - n1 = -(|n0| - |n1|) < 0 + return SubIgnoreSign(n0, n1, -1); + } + else + { + // n0 - n1 = |n1| - |n0| > 0 + return SubIgnoreSign(n1, n0, +1); + } + } + // else n0 - n1 = 0 + } + } + + return BSNumber(); // = 0 + } + + BSNumber operator*(BSNumber const& number) const + { + BSNumber result; // = 0 + int sign = mSign * number.mSign; + if (sign != 0) + { + result.mSign = sign; + result.mBiasedExponent = mBiasedExponent + number.mBiasedExponent; + result.mUInteger.Mul(mUInteger, number.mUInteger); + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + result.mValue = (double)result; +#endif + return result; + } + + BSNumber& operator+=(BSNumber const& number) + { + *this = operator+(number); + return *this; + } + + BSNumber& operator-=(BSNumber const& number) + { + *this = operator-(number); + return *this; + } + + BSNumber& operator*=(BSNumber const& number) + { + *this = operator*(number); + return *this; + } + + // Disk input/output. The fstream objects should be created using + // std::ios::binary. The return value is 'true' iff the operation + // was successful. + bool Write(std::ofstream& output) const + { + if (output.write((char const*)&mSign, sizeof(mSign)).bad()) + { + return false; + } + + if (output.write((char const*)&mBiasedExponent, + sizeof(mBiasedExponent)).bad()) + { + return false; + } + + return mUInteger.Write(output); + } + + bool Read(std::ifstream& input) + { + if (input.read((char*)&mSign, sizeof(mSign)).bad()) + { + return false; + } + + if (input.read((char*)&mBiasedExponent, sizeof(mBiasedExponent)).bad()) + { + return false; + } + + return mUInteger.Read(input); + } + + private: + // Helpers for operator==, operator<, operator+, operator-. + static bool EqualIgnoreSign(BSNumber const& n0, BSNumber const& n1) + { + return n0.mBiasedExponent == n1.mBiasedExponent && n0.mUInteger == n1.mUInteger; + } + + static bool LessThanIgnoreSign(BSNumber const& n0, BSNumber const& n1) + { + int32_t e0 = n0.GetExponent(), e1 = n1.GetExponent(); + if (e0 < e1) + { + return true; + } + if (e0 > e1) + { + return false; + } + return n0.mUInteger < n1.mUInteger; + } + + // Add two positive numbers. + static BSNumber AddIgnoreSign(BSNumber const& n0, BSNumber const& n1, int32_t resultSign) + { + BSNumber result, temp; + + int32_t diff = n0.mBiasedExponent - n1.mBiasedExponent; + if (diff > 0) + { + temp.mUInteger.ShiftLeft(n0.mUInteger, diff); + result.mUInteger.Add(temp.mUInteger, n1.mUInteger); + result.mBiasedExponent = n1.mBiasedExponent; + } + else if (diff < 0) + { + temp.mUInteger.ShiftLeft(n1.mUInteger, -diff); + result.mUInteger.Add(n0.mUInteger, temp.mUInteger); + result.mBiasedExponent = n0.mBiasedExponent; + } + else + { + temp.mUInteger.Add(n0.mUInteger, n1.mUInteger); + int32_t shift = result.mUInteger.ShiftRightToOdd(temp.mUInteger); + result.mBiasedExponent = n0.mBiasedExponent + shift; + } + + result.mSign = resultSign; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + result.mValue = (double)result; +#endif + return result; + } + + // Subtract two positive numbers where n0 > n1. + static BSNumber SubIgnoreSign(BSNumber const& n0, BSNumber const& n1, int32_t resultSign) + { + BSNumber result, temp; + + int32_t diff = n0.mBiasedExponent - n1.mBiasedExponent; + if (diff > 0) + { + temp.mUInteger.ShiftLeft(n0.mUInteger, diff); + result.mUInteger.Sub(temp.mUInteger, n1.mUInteger); + result.mBiasedExponent = n1.mBiasedExponent; + } + else if (diff < 0) + { + temp.mUInteger.ShiftLeft(n1.mUInteger, -diff); + result.mUInteger.Sub(n0.mUInteger, temp.mUInteger); + result.mBiasedExponent = n0.mBiasedExponent; + } + else + { + temp.mUInteger.Sub(n0.mUInteger, n1.mUInteger); + int32_t shift = result.mUInteger.ShiftRightToOdd(temp.mUInteger); + result.mBiasedExponent = n0.mBiasedExponent + shift; + } + + result.mSign = resultSign; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + result.mValue = (double)result; +#endif + return result; + } + + // Support for conversions from floating-point numbers to BSNumber. + template + void ConvertFrom(typename IEEE::FloatType number) + { + IEEE x(number); + + // Extract sign s, biased exponent e, and trailing significand t. + typename IEEE::UIntType s = x.GetSign(); + typename IEEE::UIntType e = x.GetBiased(); + typename IEEE::UIntType t = x.GetTrailing(); + + if (e == 0) + { + if (t == 0) // zeros + { + // x = (-1)^s * 0 + mSign = 0; + mBiasedExponent = 0; + } + else // subnormal numbers + { + // x = (-1)^s * 0.t * 2^{1-EXPONENT_BIAS} + int32_t last = GetTrailingBit(t); + int32_t diff = IEEE::NUM_TRAILING_BITS - last; + mSign = (s > 0 ? -1 : 1); + mBiasedExponent = IEEE::MIN_SUB_EXPONENT - diff; + mUInteger = (t >> last); + } + } + else if (e < IEEE::MAX_BIASED_EXPONENT) // normal numbers + { + // x = (-1)^s * 1.t * 2^{e-EXPONENT_BIAS} + if (t > 0) + { + int32_t last = GetTrailingBit(t); + int32_t diff = IEEE::NUM_TRAILING_BITS - last; + mSign = (s > 0 ? -1 : 1); + mBiasedExponent = + static_cast(e) - IEEE::EXPONENT_BIAS - diff; + mUInteger = ((t | IEEE::SUP_TRAILING) >> last); + } + else + { + mSign = (s > 0 ? -1 : 1); + mBiasedExponent = static_cast(e) - IEEE::EXPONENT_BIAS; + mUInteger = (typename IEEE::UIntType)1; + } + } + else // e == MAX_BIASED_EXPONENT, special numbers + { + if (t == 0) // infinities + { + // x = (-1)^s * infinity + LogWarning("Input is " + std::string(s > 0 ? "-" : "+") + + "infinity."); + + // Return (-1)^s * 2^{1+EXPONENT_BIAS} for a graceful exit. + mSign = (s > 0 ? -1 : 1); + mBiasedExponent = 1 + IEEE::EXPONENT_BIAS; + mUInteger = (typename IEEE::UIntType)1; + } + else // not-a-number (NaN) + { + LogError("Input is a " + + std::string(t & IEEE::NAN_QUIET_MASK ? + "quiet" : "signaling") + " NaN with payload [redacted]" + // disabling this because std::to_string is not available on some platforms and mscver ifdef causes a weird error + //+ std::to_string(t & IEEE::NAN_PAYLOAD_MASK) + "." + ); + + // Return 0 for a graceful exit. + mSign = 0; + mBiasedExponent = 0; + } + } + } + + // Support for conversions from BSNumber to floating-point numbers. + template + typename IEEE::FloatType ConvertTo() const + { + typename IEEE::UIntType s = (mSign < 0 ? 1 : 0); + typename IEEE::UIntType e, t; + + if (mSign != 0) + { + // The conversions use round-to-nearest-ties-to-even semantics. + int32_t exponent = GetExponent(); + if (exponent < IEEE::MIN_EXPONENT) + { + if (exponent < IEEE::MIN_EXPONENT - 1 + || mUInteger.GetNumBits() == 1) // x = 1.0*2^{MIN_EXPONENT-1} + { + // Round to zero. + e = 0; + t = 0; + } + else + { + // Round to min subnormal. + e = 0; + t = 1; + } + } + else if (exponent < IEEE::MIN_SUB_EXPONENT) + { + // The second input is in {0, ..., NUM_TRAILING_BITS-1}. + t = GetTrailing(0, IEEE::MIN_SUB_EXPONENT - exponent - 1); + if (t & IEEE::SUP_TRAILING) + { + // Leading NUM_SIGNIFICAND_BITS bits were all 1, so round to + // min normal. + e = 1; + t = 0; + } + else + { + e = 0; + } + } + else if (exponent <= IEEE::EXPONENT_BIAS) + { + e = static_cast(exponent + IEEE::EXPONENT_BIAS); + t = GetTrailing(1, 0); + if (t & (IEEE::SUP_TRAILING << 1)) + { + // Carry-out occurred, so increase exponent by 1 and + // shift right to compensate. + ++e; + t >>= 1; + } + // Eliminate the leading 1 (implied for normals). + t &= ~IEEE::SUP_TRAILING; + } + else + { + // Set to infinity. + e = IEEE::MAX_BIASED_EXPONENT; + t = 0; + } + } + else + { + // The input is zero. + e = 0; + t = 0; + } + + IEEE x(s, e, t); + return x.number; + } + + template + typename IEEE::UIntType GetTrailing(int32_t normal, int32_t sigma) const + { + int32_t const numRequested = IEEE::NUM_SIGNIFICAND_BITS + normal; + + // We need numRequested bits to determine rounding direction. These are + // stored in the high-order bits of 'prefix'. + uint64_t prefix = mUInteger.GetPrefix(numRequested); + + // The first bit index after the implied binary point for rounding. + int32_t diff = numRequested - sigma; + int32_t roundBitIndex = 64 - diff; + + // Determine rounding value based on round-to-nearest-ties-to-even. + uint64_t mask = (1ull << roundBitIndex); + uint64_t round; + if (prefix & mask) + { + // The first bit of the remainder is 1. + if (mUInteger.GetNumBits() == diff) + { + // The first bit of the remainder is the lowest-order bit of + // mBits[0]. Apply the ties-to-even rule. + if (prefix & (mask << 1)) + { + // The last bit of the trailing significand is odd, so + // round up. + round = 1; + } + else + { + // The last bit of the trailing significand is even, so + // round down. + round = 0; + } + } + else + { + // The first bit of the remainder is not the lowest-order bit of + // mBits[0]. The remainder as a fraction is larger than 1/2, so + // round up. + round = 1; + } + } + else + { + // The first bit of the remainder is 0, so round down. + round = 0; + } + + // Get the unrounded trailing significand. + uint64_t trailing = prefix >> (roundBitIndex + 1); + + // Apply the rounding. + trailing += round; + return static_cast(trailing); + } + +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + public: + // List this first so that it shows up first in the debugger watch window. + double mValue; + private: +#endif + + // The number 0 is represented by: mSign = 0, mBiasedExponent = 0, and + // mUInteger = 0. For nonzero numbers, mSign != 0 and mUInteger > 0. + int32_t mSign; + int32_t mBiasedExponent; + UIntegerType mUInteger; + + // Access to members to avoid exposing them publically when they are + // needed only internally. + friend class BSRational; + friend class UnitTestBSNumber; + }; +} + +namespace std +{ + // TODO: Allow for implementations of the math functions in which a + // specified precision is used when computing the result. + + template + inline gte::BSNumber abs(gte::BSNumber const& x) + { + return (x.GetSign() >= 0 ? x : -x); + } + + template + inline gte::BSNumber acos(gte::BSNumber const& x) + { + return (gte::BSNumber)std::acos((double)x); + } + + template + inline gte::BSNumber acosh(gte::BSNumber const& x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::BSNumber)0; +#else + return (gte::BSNumber)std::acosh((double)x); +#endif + } + + template + inline gte::BSNumber asin(gte::BSNumber const& x) + { + return (gte::BSNumber)std::asin((double)x); + } + + template + inline gte::BSNumber asinh(gte::BSNumber const& x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::BSNumber)0; +#else + return (gte::BSNumber)std::asinh((double)x); +#endif + } + + template + inline gte::BSNumber atan(gte::BSNumber const& x) + { + return (gte::BSNumber)std::atan((double)x); + } + + template + inline gte::BSNumber atanh(gte::BSNumber const& x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::BSNumber)0; +#else + return (gte::BSNumber)std::atanh((double)x); +#endif + } + + template + inline gte::BSNumber atan2(gte::BSNumber const& y, gte::BSNumber const& x) + { + return (gte::BSNumber)std::atan2((double)y, (double)x); + } + + template + inline gte::BSNumber ceil(gte::BSNumber const& x) + { + return (gte::BSNumber)std::ceil((double)x); + } + + template + inline gte::BSNumber cos(gte::BSNumber const& x) + { + return (gte::BSNumber)std::cos((double)x); + } + + template + inline gte::BSNumber cosh(gte::BSNumber const& x) + { + return (gte::BSNumber)std::cosh((double)x); + } + + template + inline gte::BSNumber exp(gte::BSNumber const& x) + { + return (gte::BSNumber)std::exp((double)x); + } + + template + inline gte::BSNumber exp2(gte::BSNumber const& x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::BSNumber)0; +#else + return (gte::BSNumber)std::exp2((double)x); +#endif + } + + template + inline gte::BSNumber floor(gte::BSNumber const& x) + { + return (gte::BSNumber)std::floor((double)x); + } + + template + inline gte::BSNumber fmod(gte::BSNumber const& x, gte::BSNumber const& y) + { + return (gte::BSNumber)std::fmod((double)x, (double)y); + } + + template + inline gte::BSNumber frexp(gte::BSNumber const& x, int* exponent) + { + return (gte::BSNumber)std::frexp((double)x, exponent); + } + + template + inline gte::BSNumber ldexp(gte::BSNumber const& x, int exponent) + { + return (gte::BSNumber)std::ldexp((double)x, exponent); + } + + template + inline gte::BSNumber log(gte::BSNumber const& x) + { + return (gte::BSNumber)std::log((double)x); + } + + template + inline gte::BSNumber log2(gte::BSNumber const& x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::BSNumber)0; +#else + return (gte::BSNumber)std::log2((double)x); +#endif + } + + template + inline gte::BSNumber log10(gte::BSNumber const& x) + { + return (gte::BSNumber)std::log10((double)x); + } + + template + inline gte::BSNumber pow(gte::BSNumber const& x, gte::BSNumber const& y) + { + return (gte::BSNumber)std::pow((double)x, (double)y); + } + + template + inline gte::BSNumber sin(gte::BSNumber const& x) + { + return (gte::BSNumber)std::sin((double)x); + } + + template + inline gte::BSNumber sinh(gte::BSNumber const& x) + { + return (gte::BSNumber)std::sinh((double)x); + } + + template + inline gte::BSNumber sqrt(gte::BSNumber const& x) + { + return (gte::BSNumber)std::sqrt((double)x); + } + + template + inline gte::BSNumber tan(gte::BSNumber const& x) + { + return (gte::BSNumber)std::tan((double)x); + } + + template + inline gte::BSNumber tanh(gte::BSNumber const& x) + { + return (gte::BSNumber)std::tanh((double)x); + } +} + +namespace gte +{ + template + inline BSNumber atandivpi(BSNumber const& x) + { + return (BSNumber)atandivpi((double)x); + } + + template + inline BSNumber atan2divpi(BSNumber const& y, BSNumber const& x) + { + return (BSNumber)atan2divpi((double)y, (double)x); + } + + template + inline BSNumber clamp(BSNumber const& x, BSNumber const& xmin, BSNumber const& xmax) + { + return (BSNumber)clamp((double)x, (double)xmin, (double)xmax); + } + + template + inline BSNumber cospi(BSNumber const& x) + { + return (BSNumber)cospi((double)x); + } + + template + inline BSNumber exp10(BSNumber const& x) + { + return (BSNumber)exp10((double)x); + } + + template + inline BSNumber invsqrt(BSNumber const& x) + { + return (BSNumber)invsqrt((double)x); + } + + template + inline int isign(BSNumber const& x) + { + return isign((double)x); + } + + template + inline BSNumber saturate(BSNumber const& x) + { + return (BSNumber)saturate((double)x); + } + + template + inline BSNumber sign(BSNumber const& x) + { + return (BSNumber)sign((double)x); + } + + template + inline BSNumber sinpi(BSNumber const& x) + { + return (BSNumber)sinpi((double)x); + } + + template + inline BSNumber sqr(BSNumber const& x) + { + return (BSNumber)sqr((double)x); + } + + // See the comments in GteMath.h about trait is_arbitrary_precision. + template + struct is_arbitrary_precision_internal> : std::true_type {}; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSPrecision.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSPrecision.cpp new file mode 100644 index 000000000000..dcfec8373de9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSPrecision.cpp @@ -0,0 +1,232 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +#include +using namespace gte; + + +BSPrecision::BSPrecision(bool isFloat, bool forBSNumber) + : + mForBSNumber(forBSNumber) +{ + // NOTE: It is not clear what the rationale is for the C++ Standard + // Library to set numeric_limits::min_exponent to -125 + // instead of -126; same issue with numeric_limits::min_exponent + // set to -1021 instead of -1022. Similarly, it is not clear why + // numeric_limits::max_exponent is set to 128 when the maximum + // finite 'float' has exponent 127. The exponent 128 is used in the + // 'float' infinity, but treating this as 2^{128} does not seem to be + // consistent with the IEEE 754-2008 standard. Same issue with + // numeric_limits::max_exponent set to 1024 rather than 1023. + + if (isFloat) + { + mNumBits = std::numeric_limits::digits; // 24 + mMinBiasedExponent = + std::numeric_limits::min_exponent - mNumBits; // -149 + mMaxExponent = std::numeric_limits::max_exponent - 1; // 127 + } + else + { + mNumBits = std::numeric_limits::digits; // 53 + mMinBiasedExponent = + std::numeric_limits::min_exponent - mNumBits; // -1074 + mMaxExponent = std::numeric_limits::max_exponent - 1; // 1023 + } +} + +BSPrecision::BSPrecision(bool isFloat, int32_t maxExponent, bool forBSNumber) + : + mForBSNumber(forBSNumber) +{ + if (isFloat) + { + mNumBits = std::numeric_limits::digits; // 24 + mMinBiasedExponent = + std::numeric_limits::min_exponent - mNumBits; // -149 + mMaxExponent = maxExponent; + } + else + { + mNumBits = std::numeric_limits::digits; // 53 + mMinBiasedExponent = + std::numeric_limits::min_exponent - mNumBits; // -1074 + mMaxExponent = maxExponent; + } +} + +BSPrecision::BSPrecision(int32_t numBits, int32_t minBiasedExponent, + int32_t maxExponent, bool forBSNumber) + : + mNumBits(numBits), + mMinBiasedExponent(minBiasedExponent), + mMaxExponent(maxExponent), + mForBSNumber(forBSNumber) +{ +} + +int32_t BSPrecision::GetNumWords() const +{ + return mNumBits / 32 + ((mNumBits % 32) > 0 ? 1 : 0); +} + +int32_t BSPrecision::GetNumBits() const +{ + return mNumBits; +} + +int32_t BSPrecision::GetMinBiasedExponent() const +{ + return mMinBiasedExponent; +} + +int32_t BSPrecision::GetMinExponent() const +{ + return mMinBiasedExponent + mNumBits - 1; +} + +int32_t BSPrecision::GetMaxExponent() const +{ + return mMaxExponent; +} + +BSPrecision BSPrecision::operator*(BSPrecision const& precision) +{ + // BSRational operator* involves a product of numerators and a product + // of denominators. In worst case, the precision requirements are the + // same as a BSNumber operator*, so testing of mForBSNumber is not + // needed. + int32_t numBits = mNumBits + precision.mNumBits; + int32_t maxExponent = mMaxExponent + precision.mMaxExponent + 1; + int32_t minBiasedExponent = + mMinBiasedExponent + precision.mMinBiasedExponent; + return BSPrecision(numBits, minBiasedExponent, maxExponent, mForBSNumber); +} + +BSPrecision BSPrecision::operator+(BSPrecision const& precision) +{ + if (mForBSNumber) + { + int32_t minBiasedExponent = + std::min(mMinBiasedExponent, precision.mMinBiasedExponent); + int32_t maxExponent = + std::max(mMaxExponent, precision.mMaxExponent) + 1; + int32_t numBits = maxExponent - minBiasedExponent; + return BSPrecision(numBits, minBiasedExponent, maxExponent, + mForBSNumber); + } + else + { + // n0/d0 + n1/d1 = (n0*d1 + n1*d0) / (d0*d1) + BSPrecision product = operator*(precision); + product.mForBSNumber = true; + BSPrecision sum = product + product; + sum.mForBSNumber = false; + BSPrecision division = sum / product; + division.mForBSNumber = false; + return division; + } +} + +BSPrecision BSPrecision::operator-(BSPrecision const& precision) +{ + return operator+(precision); +} + +BSPrecision BSPrecision::operator/(BSPrecision const& precision) +{ + if (mForBSNumber) + { + return precision; + } + else + { + // Division leads to multiplication of numerators and denominators. + return operator*(precision); + } +} + +BSPrecision BSPrecision::operator==(BSPrecision const& precision) +{ + if (mForBSNumber) + { + return precision; + } + else + { + // Comparison leads to multiplication of numerators and denominators. + return operator*(precision); + } +} + +BSPrecision BSPrecision::operator!=(BSPrecision const& precision) +{ + if (mForBSNumber) + { + return precision; + } + else + { + // Comparison leads to multiplication of numerators and denominators. + return operator*(precision); + } +} + +BSPrecision BSPrecision::operator< (BSPrecision const& precision) +{ + if (mForBSNumber) + { + return precision; + } + else + { + // Comparison leads to multiplication of numerators and denominators. + return operator*(precision); + } +} + +BSPrecision BSPrecision::operator<=(BSPrecision const& precision) +{ + if (mForBSNumber) + { + return precision; + } + else + { + // Comparison leads to multiplication of numerators and denominators. + return operator*(precision); + } +} + +BSPrecision BSPrecision::operator> (BSPrecision const& precision) +{ + if (mForBSNumber) + { + return precision; + } + else + { + // Comparison leads to multiplication of numerators and denominators. + return operator*(precision); + } +} + +BSPrecision BSPrecision::operator>=(BSPrecision const& precision) +{ + if (mForBSNumber) + { + return precision; + } + else + { + // Comparison leads to multiplication of numerators and denominators. + return operator*(precision); + } +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSPrecision.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSPrecision.h new file mode 100644 index 000000000000..e451d2aeeacf --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSPrecision.h @@ -0,0 +1,77 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// Support for determining the number of bits of precision required to compute +// an expression. See the document +// http://www.geometrictools.com/Documentation/ArbitraryPrecision.pdf +// for an example of how to use this class. + +namespace gte +{ + +class BSPrecision +{ +public: + // This constructor is used for 'float' or 'double'. The floating-point + // inputs for the expressions have no restrictions; that is, the inputs + // can be any finite floating-point numbers (normal or subnormal). + BSPrecision(bool isFloat, bool forBSNumber); + + // If you know that your inputs are limited in magnitude, use this + // constructor. For example, if you know that your inputs x satisfy + // |x| <= 8, you can specify maxExponent of 3. The minimum power will + // still be that for the smallest positive subnormal. + BSPrecision(bool isFloat, int32_t maxExponent, bool forBSNumber); + + // You must use this constructor carefully based on knowledge of your + // expressions. For example, if you know that your inputs are 'float' + // and in the interval [1,2), you would choose 24 for the number of bits + // of precision, a minimum biased exponent of -23 because the largest + // 'float' smaller than 2 is 1.1^{23}*2^0 = 1^{24}*2^{-23}, and a maximum + // exponent of 0. These numbers work to determine bits of precision to + // compute x*y+z*w. However, if you then compute an expression such as + // x-y for x and y in [1,2) and multiply by powers of 1/2, the bit + // counting will not be correct because the results can be subnormals + // where the minimum biased exponent is -149, not -23. + BSPrecision(int32_t numBits, int32_t minBiasedExponent, + int32_t maxExponent, bool forBSNumber); + + // Member access. + int32_t GetNumWords() const; + int32_t GetNumBits() const; + int32_t GetMinBiasedExponent() const; + int32_t GetMinExponent() const; + int32_t GetMaxExponent() const; + + // Support for determining the number of bits of precision required to + // compute an expression using BSNumber or BSRational. + BSPrecision operator*(BSPrecision const& precision); + BSPrecision operator+(BSPrecision const& precision); + BSPrecision operator-(BSPrecision const& precision); + + // Support for determining the number of bits of precision required to + // compute a BSRational expression (operations not relevant for BSNumber). + BSPrecision operator/(BSPrecision const& precision); + BSPrecision operator==(BSPrecision const& precision); + BSPrecision operator!=(BSPrecision const& precision); + BSPrecision operator< (BSPrecision const& precision); + BSPrecision operator<=(BSPrecision const& precision); + BSPrecision operator> (BSPrecision const& precision); + BSPrecision operator>=(BSPrecision const& precision); + +private: + int32_t mNumBits, mMinBiasedExponent, mMaxExponent; + bool mForBSNumber; +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSRational.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSRational.h new file mode 100644 index 000000000000..eeb543f603f9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSRational.h @@ -0,0 +1,748 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2019/01/17) + +#pragma once + +#include + +// See the comments in GteBSNumber.h about the UIntegerType requirements. The +// denominator of a BSRational is chosen to be positive, which allows some +// simplification of comparisons. Also see the comments about exposing the +// GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE conditional define. + +namespace gte +{ + template + class BSRational + { + public: + // Construction. The default constructor generates the zero BSRational. + // The constructors that take only numerators set the denominators to one. + BSRational() + : + mNumerator(0), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(BSRational const& r) + { + *this = r; + } + + BSRational(float numerator) + : + mNumerator(numerator), + mDenominator(1.0f) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(double numerator) + : + mNumerator(numerator), + mDenominator(1.0) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(int32_t numerator) + : + mNumerator(numerator), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(uint32_t numerator) + : + mNumerator(numerator), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(int64_t numerator) + : + mNumerator(numerator), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(uint64_t numerator) + : + mNumerator(numerator), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(BSNumber const& numerator) + : + mNumerator(numerator), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(float numerator, float denominator) + : + mNumerator(numerator), + mDenominator(denominator) + { + LogAssert(mDenominator.mSign != 0, "Division by zero not allowed."); + if (mDenominator.mSign < 0) + { + mNumerator.mSign = -mNumerator.mSign; + mDenominator.mSign = 1; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(double numerator, double denominator) + : + mNumerator(numerator), + mDenominator(denominator) + { + LogAssert(mDenominator.mSign != 0, "Division by zero not allowed."); + if (mDenominator.mSign < 0) + { + mNumerator.mSign = -mNumerator.mSign; + mDenominator.mSign = 1; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(BSNumber const& numerator, BSNumber const& denominator) + : + mNumerator(numerator), + mDenominator(denominator) + { + LogAssert(mDenominator.mSign != 0, "Division by zero not allowed."); + if (mDenominator.mSign < 0) + { + mNumerator.mSign = -mNumerator.mSign; + mDenominator.mSign = 1; + } + + // Set the exponent of the denominator to zero, but you can do so only + // by modifying the biased exponent. Adjust the numerator accordingly. + // This prevents large growth of the exponents in both numerator and + // denominator simultaneously. + mNumerator.mBiasedExponent -= mDenominator.GetExponent(); + mDenominator.mBiasedExponent = + -(mDenominator.GetUInteger().GetNumBits() - 1); + +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + // Implicit conversions. + operator float() const + { + return Convert(); + } + + operator double() const + { + return Convert(); + } + + // Assignment. + BSRational& operator=(BSRational const& r) + { + mNumerator = r.mNumerator; + mDenominator = r.mDenominator; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + return *this; + } + + // Support for std::move. + BSRational(BSRational&& r) + { + *this = std::move(r); + } + + BSRational& operator=(BSRational&& r) + { + mNumerator = std::move(r.mNumerator); + mDenominator = std::move(r.mDenominator); +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + return *this; + } + + // Member access. + inline int GetSign() const + { + return mNumerator.GetSign() * mDenominator.GetSign(); + } + + inline BSNumber const& GetNumerator() const + { + return mNumerator; + } + + inline BSNumber const& GetDenominator() const + { + return mDenominator; + } + + // Comparisons. + bool operator==(BSRational const& r) const + { + // Do inexpensive sign tests first for optimum performance. + if (mNumerator.mSign != r.mNumerator.mSign) + { + return false; + } + if (mNumerator.mSign == 0) + { + // The numbers are both zero. + return true; + } + + return mNumerator * r.mDenominator == mDenominator * r.mNumerator; + } + + bool operator!=(BSRational const& r) const + { + return !operator==(r); + } + + bool operator< (BSRational const& r) const + { + // Do inexpensive sign tests first for optimum performance. + if (mNumerator.mSign > 0) + { + if (r.mNumerator.mSign <= 0) + { + return false; + } + } + else if (mNumerator.mSign == 0) + { + return r.mNumerator.mSign > 0; + } + else if (mNumerator.mSign < 0) + { + if (r.mNumerator.mSign >= 0) + { + return true; + } + } + + return mNumerator * r.mDenominator < mDenominator * r.mNumerator; + } + + bool operator<=(BSRational const& r) const + { + return !r.operator<(*this); + } + + bool operator> (BSRational const& r) const + { + return r.operator<(*this); + } + + bool operator>=(BSRational const& r) const + { + return !operator<(r); + } + + // Unary operations. + BSRational operator+() const + { + return *this; + } + + BSRational operator-() const + { + return BSRational(-mNumerator, mDenominator); + } + + // Arithmetic. + BSRational operator+(BSRational const& r) const + { + BSNumber product0 = mNumerator * r.mDenominator; + BSNumber product1 = mDenominator * r.mNumerator; + BSNumber numerator = product0 + product1; + + // Complex expressions can lead to 0/denom, where denom is not 1. + if (numerator.mSign != 0) + { + BSNumber denominator = mDenominator * r.mDenominator; + return BSRational(numerator, denominator); + } + else + { + return BSRational(0); + } + } + + BSRational operator-(BSRational const& r) const + { + BSNumber product0 = mNumerator * r.mDenominator; + BSNumber product1 = mDenominator * r.mNumerator; + BSNumber numerator = product0 - product1; + + // Complex expressions can lead to 0/denom, where denom is not 1. + if (numerator.mSign != 0) + { + BSNumber denominator = mDenominator * r.mDenominator; + return BSRational(numerator, denominator); + } + else + { + return BSRational(0); + } + } + + BSRational operator*(BSRational const& r) const + { + BSNumber numerator = mNumerator * r.mNumerator; + + // Complex expressions can lead to 0/denom, where denom is not 1. + if (numerator.mSign != 0) + { + BSNumber denominator = mDenominator * r.mDenominator; + return BSRational(numerator, denominator); + } + else + { + return BSRational(0); + } + } + + BSRational operator/(BSRational const& r) const + { + LogAssert(r.mNumerator.mSign != 0, "Division by zero not allowed."); + + BSNumber numerator = mNumerator * r.mDenominator; + + // Complex expressions can lead to 0/denom, where denom is not 1. + if (numerator.mSign != 0) + { + BSNumber denominator = mDenominator * r.mNumerator; + if (denominator.mSign < 0) + { + numerator.mSign = -numerator.mSign; + denominator.mSign = 1; + } + return BSRational(numerator, denominator); + } + else + { + return BSRational(0); + } + } + + BSRational& operator+=(BSRational const& r) + { + *this = operator+(r); + return *this; + } + + BSRational& operator-=(BSRational const& r) + { + *this = operator-(r); + return *this; + } + + BSRational& operator*=(BSRational const& r) + { + *this = operator*(r); + return *this; + } + + BSRational& operator/=(BSRational const& r) + { + *this = operator/(r); + return *this; + } + + // Disk input/output. The fstream objects should be created using + // std::ios::binary. The return value is 'true' iff the operation + // was successful. + bool Write(std::ofstream& output) const + { + return mNumerator.Write(output) && mDenominator.Write(output); + } + + bool Read(std::ifstream& input) + { + return mNumerator.Read(input) && mDenominator.Read(input); + } + + private: + // Generic conversion code that converts to the correctly rounded + // result using round-to-nearest-ties-to-even. + template + RealType Convert() const + { + if (mNumerator.mSign == 0) + { + return (RealType)0; + } + + // The ratio is abstractly of the form (1.u*2^p)/(1.v*2^q). + // Convert to the form (1.u/1.v)*2^{p-q}, if 1.u >= 1.v, or to the + // form (2*(1.u)/1.v)*2*{p-q-1}) if 1.u < 1.v. The final form + // n/d must be in the interval [1,2). + BSNumber n = mNumerator, d = mDenominator; + int32_t sign = n.mSign * d.mSign; + n.mSign = 1; + d.mSign = 1; + int32_t pmq = n.GetExponent() - d.GetExponent(); + n.mBiasedExponent = 1 - n.GetUInteger().GetNumBits(); + d.mBiasedExponent = 1 - d.GetUInteger().GetNumBits(); + if (BSNumber::LessThanIgnoreSign(n, d)) + { + ++n.mBiasedExponent; + --pmq; + } + + // At this time, n/d = 1.c in [1,2). Define the sequence of bits + // w = 1c = w_{imax} w_{imax-1} ... w_0 w_{-1} w_{-2} ... where + // imax = precision(RealType)-1 and w_{imax} = 1. + + // Compute 'precision' bits for w, the leading bit guaranteed to + // be 1 and occurring at index (1 << (precision-1)). + BSNumber one(1), two(2); + int const imax = std::numeric_limits::digits - 1; + UIntType w = 0; + UIntType mask = ((UIntType)1 << imax); + for (int i = imax; i >= 0; --i, mask >>= 1) + { + if (BSNumber::LessThanIgnoreSign(n, d)) + { + n = two * n; + } + else + { + n = two * (n - d); + w |= mask; + } + } + + // Apply the mode round-to-nearest-ties-to-even to decide whether + // to round down or up. We computed w = w_{imax} ... w_0. The + // remainder is n/d = w_{imax+1}.w_{imax+2}... in [0,2). Compute + // n'/d = (n-d)/d in [-1,1). Round-to-nearest-ties-to-even mode + // is the following, where we need only test the sign of n'. A + // remainder of "half" is the case n' = 0. + // Round down when n' < 0 or (n' = 0 and w_0 = 0): use w + // Round up when n' > 0 or (n' = 0 and w_0 == 1): use w+1 + n = n - d; + if (n.mSign > 0 || (n.mSign == 0 && (w & 1) == 1)) + { + ++w; + } + + if (w > 0) + { + // Ensure that the low-order bit of w is 1, which is required + // for the BSNumber integer part. + int32_t trailing = GetTrailingBit(w); + w >>= trailing; + pmq += trailing; + + // Compute a BSNumber with integer part w and the appropriate + // number of bits and exponents. + BSNumber result(w); + result.mBiasedExponent = pmq - imax; + RealType converted = (RealType)result; + if (sign < 0) + { + converted = -converted; + } + return converted; + } + else + { + return (RealType)0; + } + } + +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + public: + // List this first so that it shows up first in the debugger watch + // window. + double mValue; + private: +#endif + + BSNumber mNumerator, mDenominator; + + friend class UnitTestBSRational; + }; +} + +namespace std +{ + // TODO: Allow for implementations of the math functions in which a + // specified precision is used when computing the result. + + template + inline gte::BSRational abs(gte::BSRational const& x) + { + return (x.GetSign() >= 0 ? x : -x); + } + + template + inline gte::BSRational acos(gte::BSRational const& x) + { + return (gte::BSRational)std::acos((double)x); + } + + template + inline gte::BSRational acosh(gte::BSRational const& x) + { + return (gte::BSRational)std::acosh((double)x); + } + + template + inline gte::BSRational asin(gte::BSRational const& x) + { + return (gte::BSRational)std::asin((double)x); + } + + template + inline gte::BSRational asinh(gte::BSRational const& x) + { + return (gte::BSRational)std::asinh((double)x); + } + + template + inline gte::BSRational atan(gte::BSRational const& x) + { + return (gte::BSRational)std::atan((double)x); + } + + template + inline gte::BSRational atanh(gte::BSRational const& x) + { + return (gte::BSRational)std::atanh((double)x); + } + + template + inline gte::BSRational atan2(gte::BSRational const& y, gte::BSRational const& x) + { + return (gte::BSRational)std::atan2((double)y, (double)x); + } + + template + inline gte::BSRational ceil(gte::BSRational const& x) + { + return (gte::BSRational)std::ceil((double)x); + } + + template + inline gte::BSRational cos(gte::BSRational const& x) + { + return (gte::BSRational)std::cos((double)x); + } + + template + inline gte::BSRational cosh(gte::BSRational const& x) + { + return (gte::BSRational)std::cosh((double)x); + } + + template + inline gte::BSRational exp(gte::BSRational const& x) + { + return (gte::BSRational)std::exp((double)x); + } + + template + inline gte::BSRational exp2(gte::BSRational const& x) + { + return (gte::BSRational)std::exp2((double)x); + } + + template + inline gte::BSRational floor(gte::BSRational const& x) + { + return (gte::BSRational)std::floor((double)x); + } + + template + inline gte::BSRational fmod(gte::BSRational const& x, gte::BSRational const& y) + { + return (gte::BSRational)std::fmod((double)x, (double)y); + } + + template + inline gte::BSRational frexp(gte::BSRational const& x, int* exponent) + { + return (gte::BSRational)std::frexp((double)x, exponent); + } + + template + inline gte::BSRational ldexp(gte::BSRational const& x, int exponent) + { + return (gte::BSRational)std::ldexp((double)x, exponent); + } + + template + inline gte::BSRational log(gte::BSRational const& x) + { + return (gte::BSRational)std::log((double)x); + } + + template + inline gte::BSRational log2(gte::BSRational const& x) + { + return (gte::BSRational)std::log2((double)x); + } + + template + inline gte::BSRational log10(gte::BSRational const& x) + { + return (gte::BSRational)std::log10((double)x); + } + + template + inline gte::BSRational pow(gte::BSRational const& x, gte::BSRational const& y) + { + return (gte::BSRational)std::pow((double)x, (double)y); + } + + template + inline gte::BSRational sin(gte::BSRational const& x) + { + return (gte::BSRational)std::sin((double)x); + } + + template + inline gte::BSRational sinh(gte::BSRational const& x) + { + return (gte::BSRational)std::sinh((double)x); + } + + template + inline gte::BSRational sqrt(gte::BSRational const& x) + { + return (gte::BSRational)std::sqrt((double)x); + } + + template + inline gte::BSRational tan(gte::BSRational const& x) + { + return (gte::BSRational)std::tan((double)x); + } + + template + inline gte::BSRational tanh(gte::BSRational const& x) + { + return (gte::BSRational)std::tanh((double)x); + } +} + +namespace gte +{ + template + inline BSRational atandivpi(BSRational const& x) + { + return (BSRational)atandivpi((double)x); + } + + template + inline BSRational atan2divpi(BSRational const& y, BSRational const& x) + { + return (BSRational)atan2divpi((double)y, (double)x); + } + + template + inline BSRational clamp(BSRational const& x, BSRational const& xmin, BSRational const& xmax) + { + return (BSRational)clamp((double)x, (double)xmin, (double)xmax); + } + + template + inline BSRational cospi(BSRational const& x) + { + return (BSRational)cospi((double)x); + } + + template + inline BSRational exp10(BSRational const& x) + { + return (BSRational)exp10((double)x); + } + + template + inline BSRational invsqrt(BSRational const& x) + { + return (BSRational)invsqrt((double)x); + } + + template + inline int isign(BSRational const& x) + { + return isign((double)x); + } + + template + inline BSRational saturate(BSRational const& x) + { + return (BSRational)saturate((double)x); + } + + template + inline BSRational sign(BSRational const& x) + { + return (BSRational)sign((double)x); + } + + template + inline BSRational sinpi(BSRational const& x) + { + return (BSRational)sinpi((double)x); + } + + template + inline BSRational sqr(BSRational const& x) + { + return (BSRational)sqr((double)x); + } + + // See the comments in GteMath.h about traits is_arbitrary_precision + // and has_division_operator. + template + struct is_arbitrary_precision_internal> : std::true_type {}; + + template + struct has_division_operator_internal> : std::true_type {}; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineCurve.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineCurve.h new file mode 100644 index 000000000000..ec100830ad74 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineCurve.h @@ -0,0 +1,195 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/02/17) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class BSplineCurve : public ParametricCurve +{ +public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points, pass a null pointer + // and later access the control points via GetControls() or SetControl() + // member functions. The domain is t in [t[d],t[n]], where t[d] and t[n] + // are knots with d the degree and n the number of control points. To + // validate construction, create an object as shown: + // BSplineCurve curve(parameters); + // if (!curve) { ; } + BSplineCurve(BasisFunctionInput const& input, + Vector const* controls); + + // Member access. + inline BasisFunction const& GetBasisFunction() const; + inline int GetNumControls() const; + inline Vector const* GetControls() const; + inline Vector* GetControls(); + void SetControl(int i, Vector const& control); + Vector const& GetControl(int i) const; + + // Evaluation of the curve. The function supports derivative calculation + // through order 3; that is, maxOrder <= 3 is required. If you want + // only the position, pass in maxOrder of 0. If you want the position and + // first derivative, pass in maxOrder of 1, and so on. The output + // 'values' are ordered as: position, first derivative, second derivative, + // third derivative. + virtual void Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const; + +private: + // Support for Evaluate(...). + Vector Compute(unsigned int order, int imin, int imax) const; + + BasisFunction mBasisFunction; + std::vector> mControls; +}; + + +template +BSplineCurve::BSplineCurve(BasisFunctionInput const& input, + Vector const* controls) + : + ParametricCurve((Real)0, (Real)1), + mBasisFunction(input) +{ + if (!mBasisFunction) + { + // Errors were already generated during construction of the + // basis function. + return; + } + + // The mBasisFunction stores the domain but so does ParametricCurve. + this->mTime.front() = mBasisFunction.GetMinDomain(); + this->mTime.back() = mBasisFunction.GetMaxDomain(); + + // The replication of control points for periodic splines is avoided + // by wrapping the i-loop index in Evaluate. + mControls.resize(input.numControls); + if (controls) + { + std::copy(controls, controls + input.numControls, mControls.begin()); + } + else + { + Vector zero{ (Real)0 }; + std::fill(mControls.begin(), mControls.end(), zero); + } + this->mConstructed = true; +} + +template +BasisFunction const& BSplineCurve::GetBasisFunction() const +{ + return mBasisFunction; +} + +template +int BSplineCurve::GetNumControls() const +{ + return static_cast(mControls.size()); +} + +template +Vector const* BSplineCurve::GetControls() const +{ + return mControls.data(); +} + +template +Vector* BSplineCurve::GetControls() +{ + return mControls.data(); +} + +template +void BSplineCurve::SetControl(int i, Vector const& control) +{ + if (0 <= i && i < GetNumControls()) + { + mControls[i] = control; + } +} + +template +Vector const& BSplineCurve::GetControl(int i) const +{ + if (0 <= i && i < GetNumControls()) + { + return mControls[i]; + } + else + { + return mControls[0]; + } +} + +template +void BSplineCurve::Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const +{ + if (!this->mConstructed) + { + // Errors were already generated during construction. + for (unsigned int order = 0; order < 4; ++order) + { + values[order].MakeZero(); + } + return; + } + + int imin, imax; + mBasisFunction.Evaluate(t, maxOrder, imin, imax); + + // Compute position. + values[0] = Compute(0, imin, imax); + if (maxOrder >= 1) + { + // Compute first derivative. + values[1] = Compute(1, imin, imax); + if (maxOrder >= 2) + { + // Compute second derivative. + values[2] = Compute(2, imin, imax); + if (maxOrder == 3) + { + values[3] = Compute(3, imin, imax); + } + else + { + values[3].MakeZero(); + } + } + } +} + +template +Vector BSplineCurve::Compute(unsigned int order, int imin, + int imax) const +{ + // The j-index introduces a tiny amount of overhead in order to handle + // both aperiodic and periodic splines. For aperiodic splines, j = i + // always. + + int numControls = GetNumControls(); + Vector result; + result.MakeZero(); + for (int i = imin; i <= imax; ++i) + { + Real tmp = mBasisFunction.GetValue(order, i); + int j = (i >= numControls ? i - numControls : i); + result += tmp * mControls[j]; + } + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineCurveFit.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineCurveFit.h new file mode 100644 index 000000000000..9df8b3c33d02 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineCurveFit.h @@ -0,0 +1,257 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class BSplineCurveFit +{ +public: + // Construction. The preconditions for calling the constructor are + // 1 <= degree && degree < numControls <= numSamples + // The samples points are contiguous blocks of 'dimension' real values + // stored in sampleData. + BSplineCurveFit(int dimension, int numSamples, Real const* sampleData, + int degree, int numControls); + + // Access to input sample information. + inline int GetDimension() const; + inline int GetNumSamples() const; + inline Real const* GetSampleData() const; + + // Access to output control point and curve information. + inline int GetDegree() const; + inline int GetNumControls() const; + inline Real const* GetControlData() const; + inline BasisFunction const& GetBasis() const; + + // Evaluation of the B-spline curve. It is defined for 0 <= t <= 1. If a + // t-value is outside [0,1], an open spline clamps it to [0,1]. The + // caller must ensure that position[] has at least 'dimension' elements. + void GetPosition(Real t, Real* position) const; + +private: + // Input sample information. + int mDimension; + int mNumSamples; + Real const* mSampleData; + + // The fitted B-spline curve, open and with uniform knots. + int mDegree; + int mNumControls; + std::vector mControlData; + BasisFunction mBasis; +}; + + +template +BSplineCurveFit::BSplineCurveFit(int dimension, int numSamples, + Real const* sampleData, int degree, int numControls) + : + mDimension(dimension), + mNumSamples(numSamples), + mSampleData(sampleData), + mDegree(degree), + mNumControls(numControls), + mControlData(dimension * numControls) +{ + LogAssert(dimension >= 1, "Invalid dimension."); + LogAssert(1 <= degree && degree < numControls, "Invalid degree."); + LogAssert(sampleData, "Invalid sample data."); + LogAssert(numControls <= numSamples, "Invalid number of controls."); + + BasisFunctionInput input; + input.numControls = numControls; + input.degree = degree; + input.uniform = true; + input.periodic = false; + input.numUniqueKnots = numControls - degree + 1; + input.uniqueKnots.resize(input.numUniqueKnots); + input.uniqueKnots[0].t = (Real)0; + input.uniqueKnots[0].multiplicity = degree + 1; + int last = input.numUniqueKnots - 1; + Real factor = ((Real)1) / (Real)last; + for (int i = 1; i < last; ++i) + { + input.uniqueKnots[i].t = factor * (Real)i; + input.uniqueKnots[i].multiplicity = 1; + } + input.uniqueKnots[last].t = (Real)1; + input.uniqueKnots[last].multiplicity = degree + 1; + mBasis.Create(input); + + // Fit the data points with a B-spline curve using a least-squares error + // metric. The problem is of the form A^T*A*Q = A^T*P, where A^T*A is a + // banded matrix, P contains the sample data, and Q is the unknown vector + // of control points. + Real tMultiplier = ((Real)1) / (Real)(mNumSamples - 1); + Real t; + int i0, i1, i2, imin, imax, j; + + // Construct the matrix A^T*A. + int degp1 = mDegree + 1; + int numBands = (mNumControls > degp1 ? degp1 : mDegree); + BandedMatrix ATAMat(mNumControls, numBands, numBands); + for (i0 = 0; i0 < mNumControls; ++i0) + { + for (i1 = 0; i1 < i0; ++i1) + { + ATAMat(i0, i1) = ATAMat(i1, i0); + } + + int i1Max = i0 + mDegree; + if (i1Max >= mNumControls) + { + i1Max = mNumControls - 1; + } + + for (i1 = i0; i1 <= i1Max; ++i1) + { + Real value = (Real)0; + for (i2 = 0; i2 < mNumSamples; ++i2) + { + t = tMultiplier * (Real)i2; + mBasis.Evaluate(t, 0, imin, imax); + if (imin <= i0 && i0 <= imax && imin <= i1 && i1 <= imax) + { + Real b0 = mBasis.GetValue(0, i0); + Real b1 = mBasis.GetValue(0, i1); + value += b0 * b1; + } + } + ATAMat(i0, i1) = value; + } + } + + // Construct the matrix A^T. + Array2 ATMat(mNumSamples, mNumControls); + memset(ATMat[0], 0, mNumControls * mNumSamples * sizeof(Real)); + for (i0 = 0; i0 < mNumControls; ++i0) + { + for (i1 = 0; i1 < mNumSamples; ++i1) + { + t = tMultiplier * (Real)i1; + mBasis.Evaluate(t, 0, imin, imax); + if (imin <= i0 && i0 <= imax) + { + ATMat[i0][i1] = mBasis.GetValue(0, i0); + } + } + } + + // Compute X0 = (A^T*A)^{-1}*A^T by solving the linear system + // A^T*A*X = A^T. + bool solved = ATAMat.template SolveSystem(ATMat[0], mNumSamples); + LogAssert(solved, "Failed to solve linear system."); + (void)solved; + + // The control points for the fitted curve are stored in the vector + // Q = X0*P, where P is the vector of sample data. + std::fill(mControlData.begin(), mControlData.end(), (Real)0); + for (i0 = 0; i0 < mNumControls; ++i0) + { + Real* Q = &mControlData[i0 * mDimension]; + for (i1 = 0; i1 < mNumSamples; ++i1) + { + Real const* P = mSampleData + i1 * mDimension; + Real xValue = ATMat[i0][i1]; + for (j = 0; j < mDimension; ++j) + { + Q[j] += xValue*P[j]; + } + } + } + + // Set the first and last output control points to match the first and + // last input samples. This supports the application of fitting keyframe + // data with B-spline curves. The user expects that the curve passes + // through the first and last positions in order to support matching two + // consecutive keyframe sequences. + Real* cEnd0 = &mControlData[0]; + Real const* sEnd0 = mSampleData; + Real* cEnd1 = &mControlData[mDimension * (mNumControls - 1)]; + Real const* sEnd1 = &mSampleData[mDimension * (mNumSamples - 1)]; + for (j = 0; j < mDimension; ++j) + { + *cEnd0++ = *sEnd0++; + *cEnd1++ = *sEnd1++; + } +} + +template inline +int BSplineCurveFit::GetDimension() const +{ + return mDimension; +} + +template inline +int BSplineCurveFit::GetNumSamples() const +{ + return mNumSamples; +} + +template inline +Real const* BSplineCurveFit::GetSampleData() const +{ + return mSampleData; +} + +template inline +int BSplineCurveFit::GetDegree() const +{ + return mDegree; +} + +template inline +int BSplineCurveFit::GetNumControls() const +{ + return mNumControls; +} + +template inline +Real const* BSplineCurveFit::GetControlData() const +{ + return &mControlData[0]; +} + +template inline +BasisFunction const& BSplineCurveFit::GetBasis() const +{ + return mBasis; +} + +template +void BSplineCurveFit::GetPosition(Real t, Real* position) const +{ + int imin, imax; + mBasis.Evaluate(t, 0, imin, imax); + + Real const* source = &mControlData[mDimension * imin]; + Real basisValue = mBasis.GetValue(0, imin); + for (int j = 0; j < mDimension; ++j) + { + position[j] = basisValue * (*source++); + } + + for (int i = imin + 1; i <= imax; ++i) + { + basisValue = mBasis.GetValue(0, i); + for (int j = 0; j < mDimension; ++j) + { + position[j] += basisValue * (*source++); + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineSurface.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineSurface.h new file mode 100644 index 000000000000..f787f62ffe99 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineSurface.h @@ -0,0 +1,208 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/02/17) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class BSplineSurface : public ParametricSurface +{ +public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points, pass a null pointer + // and later access the control points via GetControls() or SetControl() + // member functions. The input 'controls' must be stored in row-major + // order, control[i0 + numControls0*i1]. As a 2D array, this corresponds + // to control2D[i1][i0]. To validate construction, create an object as + // shown: + // BSplineSurface surface(parameters); + // if (!surface) { ; } + BSplineSurface(BasisFunctionInput const input[2], + Vector const* controls); + + // Member access. The index 'dim' must be in {0,1}. + inline BasisFunction const& GetBasisFunction(int dim) const; + inline int GetNumControls(int dim) const; + inline Vector* GetControls(); + inline Vector const* GetControls() const; + void SetControl(int i0, int i1, Vector const& control); + Vector const& GetControl(int i0, int i1) const; + + // Evaluation of the surface. The function supports derivative + // calculation through order 2; that is, maxOrder <= 2 is required. If + // you want only the position, pass in maxOrder of 0. If you want the + // position and first-order derivatives, pass in maxOrder of 1, and so on. + // The output 'values' are ordered as: position X; first-order derivatives + // dX/du, dX/dv; second-order derivatives d2X/du2, d2X/dudv, d2X/dv2. + virtual void Evaluate(Real u, Real v, unsigned int maxOrder, + Vector values[6]) const; + +private: + // Support for Evaluate(...). + Vector Compute(unsigned int uOrder, unsigned int vOrder, + int iumin, int iumax, int ivmin, int ivmax) const; + + std::array, 2> mBasisFunction; + std::array mNumControls; + std::vector> mControls; +}; + + +template +BSplineSurface::BSplineSurface(BasisFunctionInput const input[2], + Vector const* controls) + : + ParametricSurface((Real)0, (Real)1, (Real)0, (Real)1, true) +{ + for (int i = 0; i < 2; ++i) + { + mNumControls[i] = input[i].numControls; + mBasisFunction[i].Create(input[i]); + if (!mBasisFunction[i]) + { + // Errors were already generated during construction of the + // basis functions. + return; + } + } + + // The mBasisFunction stores the domain but so does ParametricCurve. + this->mUMin = mBasisFunction[0].GetMinDomain(); + this->mUMax = mBasisFunction[0].GetMaxDomain(); + this->mVMin = mBasisFunction[1].GetMinDomain(); + this->mVMax = mBasisFunction[1].GetMaxDomain(); + + // The replication of control points for periodic splines is avoided + // by wrapping the i-loop index in Evaluate. + int numControls = mNumControls[0] * mNumControls[1]; + mControls.resize(numControls); + if (controls) + { + std::copy(controls, controls + numControls, mControls.begin()); + } + else + { + Vector zero{ (Real)0 }; + std::fill(mControls.begin(), mControls.end(), zero); + } + this->mConstructed = true; +} + +template +BasisFunction const& BSplineSurface::GetBasisFunction(int dim) const +{ + return mBasisFunction[dim]; +} + +template +int BSplineSurface::GetNumControls(int dim) const +{ + return mNumControls[dim]; +} + +template +Vector const* BSplineSurface::GetControls() const +{ + return mControls.data(); +} + +template +Vector* BSplineSurface::GetControls() +{ + return mControls.data(); +} + +template +void BSplineSurface::SetControl(int i0, int i1, Vector const& control) +{ + if (0 <= i0 && i0 < GetNumControls(0) + && 0 <= i1 && i1 < GetNumControls(1)) + { + mControls[i0 + mNumControls[0] * i1] = control; + } +} + +template +Vector const& BSplineSurface::GetControl(int i0, int i1) const +{ + if (0 <= i0 && i0 < GetNumControls(0) && 0 <= i1 && i1 < GetNumControls(1)) + { + return mControls[i0 + mNumControls[0] * i1]; + } + else + { + return mControls[0]; + } +} + +template +void BSplineSurface::Evaluate(Real u, Real v, unsigned int maxOrder, + Vector values[6]) const +{ + if (!this->mConstructed) + { + // Errors were already generated during construction. + for (int i = 0; i < 6; ++i) + { + values[i].MakeZero(); + } + return; + } + + int iumin, iumax, ivmin, ivmax; + mBasisFunction[0].Evaluate(u, maxOrder, iumin, iumax); + mBasisFunction[1].Evaluate(v, maxOrder, ivmin, ivmax); + + // Compute position. + values[0] = Compute(0, 0, iumin, iumax, ivmin, ivmax); + if (maxOrder >= 1) + { + // Compute first-order derivatives. + values[1] = Compute(1, 0, iumin, iumax, ivmin, ivmax); + values[2] = Compute(0, 1, iumin, iumax, ivmin, ivmax); + if (maxOrder >= 2) + { + // Compute second-order derivatives. + values[3] = Compute(2, 0, iumin, iumax, ivmin, ivmax); + values[4] = Compute(1, 1, iumin, iumax, ivmin, ivmax); + values[5] = Compute(0, 2, iumin, iumax, ivmin, ivmax); + } + } +} + +template +Vector BSplineSurface::Compute(unsigned int uOrder, + unsigned int vOrder, int iumin, int iumax, int ivmin, int ivmax) const +{ + // The j*-indices introduce a tiny amount of overhead in order to handle + // both aperiodic and periodic splines. For aperiodic splines, j* = i* + // always. + + int const numControls0 = mNumControls[0]; + int const numControls1 = mNumControls[1]; + Vector result; + result.MakeZero(); + for (int iv = ivmin; iv <= ivmax; ++iv) + { + Real tmpv = mBasisFunction[1].GetValue(vOrder, iv); + int jv = (iv >= numControls1 ? iv - numControls1 : iv); + for (int iu = iumin; iu <= iumax; ++iu) + { + Real tmpu = mBasisFunction[0].GetValue(uOrder, iu); + int ju = (iu >= numControls0 ? iu - numControls0 : iu); + result += (tmpu * tmpv) * mControls[ju + numControls0 * jv]; + } + } + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineSurfaceFit.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineSurfaceFit.h new file mode 100644 index 000000000000..2fe716694409 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineSurfaceFit.h @@ -0,0 +1,268 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class BSplineSurfaceFit +{ +public: + // Construction. The preconditions for calling the constructor are + // 1 <= degree0 && degree0 + 1 < numControls0 <= numSamples0 + // 1 <= degree1 && degree1 + 1 < numControls1 <= numSamples1 + // The sample data must be in row-major order. The control data is + // also stored in row-major order. + BSplineSurfaceFit(int degree0, int numControls0, int numSamples0, + int degree1, int numControls1, int numSamples1, + Vector3 const* sampleData); + + // Access to input sample information. + inline int GetNumSamples(int dimension) const; + inline Vector3 const* GetSampleData() const; + + // Access to output control point and surface information. + inline int GetDegree(int dimension) const; + inline int GetNumControls(int dimension) const; + inline Vector3 const* GetControlData() const; + inline BasisFunction const& GetBasis(int dimension) const; + + // Evaluation of the B-spline surface. It is defined for 0 <= u <= 1 + // and 0 <= v <= 1. If a parameter value is outside [0,1], it is clamped + // to [0,1]. + Vector3 GetPosition(Real u, Real v) const; + +private: + // Input sample information. + int mNumSamples[2]; + Vector3 const* mSampleData; + + // The fitted B-spline surface, open and with uniform knots. + int mDegree[2]; + int mNumControls[2]; + std::vector> mControlData; + BasisFunction mBasis[2]; +}; + + +template +BSplineSurfaceFit::BSplineSurfaceFit(int degree0, int numControls0, + int numSamples0, int degree1, int numControls1, int numSamples1, + Vector3 const* sampleData) + : + mSampleData(sampleData), + mControlData(numControls0 * numControls1) +{ + LogAssert(1 <= degree0 && degree0 + 1 < numControls0, "Invalid degree."); + LogAssert(numControls0 <= numSamples0, "Invalid number of controls."); + LogAssert(1 <= degree1 && degree1 + 1 < numControls1, "Invalid degree."); + LogAssert(numControls1 <= numSamples1, "Invalid number of controls."); + LogAssert(sampleData, "Invalid sample data."); + + mDegree[0] = degree0; + mNumSamples[0] = numSamples0; + mNumControls[0] = numControls0; + mDegree[1] = degree1; + mNumSamples[1] = numSamples1; + mNumControls[1] = numControls1; + + BasisFunctionInput input; + Real tMultiplier[2]; + int dim; + for (dim = 0; dim < 2; ++dim) + { + input.numControls = mNumControls[dim]; + input.degree = mDegree[dim]; + input.uniform = true; + input.periodic = false; + input.numUniqueKnots = mNumControls[dim] - mDegree[dim] + 1; + input.uniqueKnots.resize(input.numUniqueKnots); + input.uniqueKnots[0].t = (Real)0; + input.uniqueKnots[0].multiplicity = mDegree[dim] + 1; + int last = input.numUniqueKnots - 1; + Real factor = ((Real)1) / (Real)last; + for (int i = 1; i < last; ++i) + { + input.uniqueKnots[i].t = factor * (Real)i; + input.uniqueKnots[i].multiplicity = 1; + } + input.uniqueKnots[last].t = (Real)1; + input.uniqueKnots[last].multiplicity = mDegree[dim] + 1; + mBasis[dim].Create(input); + + tMultiplier[dim] = ((Real)1) / (Real)(mNumSamples[dim] - 1); + } + + // Fit the data points with a B-spline surface using a least-squares error + // metric. The problem is of the form A0^T*A0*Q*A1^T*A1 = A0^T*P*A1, where + // A0^T*A0 and A1^T*A1 are banded matrices, P contains the sample data, + // and Q is the unknown matrix of control points. + Real t; + int i0, i1, i2, imin, imax; + + // Construct the matrices A0^T*A0 and A1^T*A1. + BandedMatrix ATAMat[2] = + { + BandedMatrix(mNumControls[0], mDegree[0] + 1, mDegree[0] + 1), + BandedMatrix(mNumControls[1], mDegree[1] + 1, mDegree[1] + 1) + }; + + for (dim = 0; dim < 2; ++dim) + { + for (i0 = 0; i0 < mNumControls[dim]; ++i0) + { + for (i1 = 0; i1 < i0; ++i1) + { + ATAMat[dim](i0, i1) = ATAMat[dim](i1, i0); + } + + int i1Max = i0 + mDegree[dim]; + if (i1Max >= mNumControls[dim]) + { + i1Max = mNumControls[dim] - 1; + } + + for (i1 = i0; i1 <= i1Max; ++i1) + { + Real value = (Real)0; + for (i2 = 0; i2 < mNumSamples[dim]; ++i2) + { + t = tMultiplier[dim] * (Real)i2; + mBasis[dim].Evaluate(t, 0, imin, imax); + if (imin <= i0 && i0 <= imax && imin <= i1 && i1 <= imax) + { + Real b0 = mBasis[dim].GetValue(0, i0); + Real b1 = mBasis[dim].GetValue(0, i1); + value += b0 * b1; + } + } + ATAMat[dim](i0, i1) = value; + } + } + } + + // Construct the matrices A0^T and A1^T. A[d]^T has mNumControls[d] + // rows and mNumSamples[d] columns. + Array2 ATMat[2]; + for (dim = 0; dim < 2; dim++) + { + ATMat[dim] = Array2(mNumSamples[dim], mNumControls[dim]); + size_t numBytes = mNumControls[dim] * mNumSamples[dim] * sizeof(Real); + memset(ATMat[dim][0], 0, numBytes); + for (i0 = 0; i0 < mNumControls[dim]; ++i0) + { + for (i1 = 0; i1 < mNumSamples[dim]; ++i1) + { + t = tMultiplier[dim] * (Real)i1; + mBasis[dim].Evaluate(t, 0, imin, imax); + if (imin <= i0 && i0 <= imax) + { + ATMat[dim][i0][i1] = mBasis[dim].GetValue(0, i0); + } + } + } + } + + // Compute X0 = (A0^T*A0)^{-1}*A0^T and X1 = (A1^T*A1)^{-1}*A1^T by + // solving the linear systems A0^T*A0*X0 = A0^T and A1^T*A1*X1 = A1^T. + for (dim = 0; dim < 2; ++dim) + { + bool solved = ATAMat[dim].template SolveSystem(ATMat[dim][0], + mNumSamples[dim]); + LogAssert(solved, "Failed to solve linear system."); + (void)solved; + } + + // The control points for the fitted surface are stored in the matrix + // Q = X0*P*X1^T, where P is the matrix of sample data. + for (i1 = 0; i1 < mNumControls[1]; ++i1) + { + for (i0 = 0; i0 < mNumControls[0]; ++i0) + { + Vector3 sum = Vector3::Zero(); + for (int j1 = 0; j1 < mNumSamples[1]; ++j1) + { + Real x1Value = ATMat[1][i1][j1]; + for (int j0 = 0; j0 < mNumSamples[0]; ++j0) + { + Real x0Value = ATMat[0][i0][j0]; + Vector3 sample = + mSampleData[j0 + mNumSamples[0] * j1]; + sum += (x0Value * x1Value) * sample; + } + } + mControlData[i0 + mNumControls[0] * i1] = sum; + } + } +} + +template inline +int BSplineSurfaceFit::GetNumSamples(int dimension) const +{ + return mNumSamples[dimension]; +} + +template inline +Vector3 const* BSplineSurfaceFit::GetSampleData() const +{ + return mSampleData; +} + +template inline +int BSplineSurfaceFit::GetDegree(int dimension) const +{ + return mDegree[dimension]; +} + +template inline +int BSplineSurfaceFit::GetNumControls(int dimension) const +{ + return mNumControls[dimension]; +} + +template inline +Vector3 const* BSplineSurfaceFit::GetControlData() const +{ + return &mControlData[0]; +} + +template inline +BasisFunction const& BSplineSurfaceFit::GetBasis(int dimension) +const +{ + return mBasis[dimension]; +} + +template +Vector3 BSplineSurfaceFit::GetPosition(Real u, Real v) const +{ + int iumin, iumax, ivmin, ivmax; + mBasis[0].Evaluate(u, 0, iumin, iumax); + mBasis[1].Evaluate(v, 0, ivmin, ivmax); + + Vector3 position = Vector3::Zero(); + for (int iv = ivmin; iv <= ivmax; ++iv) + { + Real value1 = mBasis[1].GetValue(0, iv); + for (int iu = iumin; iu <= iumax; ++iu) + { + Real value0 = mBasis[0].GetValue(0, iu); + Vector3 control = mControlData[iu + mNumControls[0] * iv]; + position += (value0 * value1) * control; + } + } + return position; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineVolume.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineVolume.h new file mode 100644 index 000000000000..85d2c36e3591 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineVolume.h @@ -0,0 +1,254 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/02/17) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class BSplineVolume +{ +public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points, pass a null pointer + // and later access the control points via GetControls() or SetControl() + // member functions. The input 'controls' must be stored in + // lexicographical order, control[i0+numControls0*(i1+numControls1*i2)]. + // As a 3D array, this corresponds to control3D[i2][i1][i0]. + BSplineVolume(BasisFunctionInput const input[3], + Vector const* controls); + + // To validate construction, create an object as shown: + // BSplineVolume volume(parameters); + // if (!volume) { ; } + inline operator bool() const; + + // Member access. The index 'dim' must be in {0,1,2}. + inline BasisFunction const& GetBasisFunction(int dim) const; + inline Real GetMinDomain(int dim) const; + inline Real GetMaxDomain(int dim) const; + inline int GetNumControls(int dim) const; + inline Vector const* GetControls() const; + inline Vector* GetControls(); + void SetControl(int i0, int i1, int i2, Vector const& control); + Vector const& GetControl(int i0, int i1, int i2) const; + + // Evaluation of the volume. The function supports derivative + // calculation through order 2; that is, maxOrder <= 2 is required. If + // you want only the position, pass in maxOrder of 0. If you want the + // position and first-order derivatives, pass in maxOrder of 1, and so on. + // The output 'values' are ordered as: position X; first-order derivatives + // dX/du, dX/dv, dX/dw; second-order derivatives d2X/du2, d2X/dv2, + // d2X/dw2, d2X/dudv, d2X/dudw, d2X/dvdw. + void Evaluate(Real u, Real v, Real w, unsigned int maxOrder, + Vector values[10]) const; + +private: + // Support for Evaluate(...). + Vector Compute(unsigned int uOrder, unsigned int vOrder, + unsigned int wOrder, int iumin, int iumax, int ivmin, int ivmax, + int iwmin, int iwmax) const; + + std::array, 3> mBasisFunction; + std::array mNumControls; + std::vector> mControls; + bool mConstructed; +}; + + +template +BSplineVolume::BSplineVolume(BasisFunctionInput const input[3], + Vector const* controls) + : + mConstructed(false) +{ + for (int i = 0; i < 3; ++i) + { + mNumControls[i] = input[i].numControls; + mBasisFunction[i].Create(input[i]); + if (!mBasisFunction[i]) + { + // Errors were already generated during construction of the + // basis functions. + return; + } + } + + // The replication of control points for periodic splines is avoided + // by wrapping the i-loop index in Evaluate. + int numControls = mNumControls[0] * mNumControls[1] * mNumControls[2]; + mControls.resize(numControls); + if (controls) + { + std::copy(controls, controls + numControls, mControls.begin()); + } + else + { + Vector zero{ (Real)0 }; + std::fill(mControls.begin(), mControls.end(), zero); + } + mConstructed = true; +} + +template +BasisFunction const& BSplineVolume::GetBasisFunction(int dim) const +{ + return mBasisFunction[dim]; +} + +template +Real BSplineVolume::GetMinDomain(int dim) const +{ + return mBasisFunction[dim].GetMinDomain(); +} + +template +Real BSplineVolume::GetMaxDomain(int dim) const +{ + return mBasisFunction[dim].GetMaxDomain(); +} + +template +BSplineVolume::operator bool() const +{ + return mConstructed; +} + +template +int BSplineVolume::GetNumControls(int dim) const +{ + return mNumControls[dim]; +} + +template +Vector const* BSplineVolume::GetControls() const +{ + return mControls.data(); +} + +template +Vector* BSplineVolume::GetControls() +{ + return mControls.data(); +} + +template +void BSplineVolume::SetControl(int i0, int i1, int i2, + Vector const& control) +{ + if (0 <= i0 && i0 < GetNumControls(0) + && 0 <= i1 && i1 < GetNumControls(1) + && 0 <= i2 && i2 < GetNumControls(2)) + { + mControls[i0 + mNumControls[0] * (i1 + mNumControls[1] * i2)] = control; + } +} + +template +Vector const& BSplineVolume::GetControl(int i0, int i1, int i2) const +{ + if (0 <= i0 && i0 < GetNumControls(0) + && 0 <= i1 && i1 < GetNumControls(1) + && 0 <= i2 && i2 < GetNumControls(2)) + { + return mControls[i0 + mNumControls[0] * (i1 + mNumControls[1] * i2)]; + } + else + { + return mControls[0]; + } +} + +template +void BSplineVolume::Evaluate(Real u, Real v, Real w, + unsigned int maxOrder, Vector values[10]) const +{ + if (!mConstructed) + { + // Errors were already generated during construction. + for (int i = 0; i < 10; ++i) + { + values[i].MakeZero(); + } + return; + } + + int iumin, iumax, ivmin, ivmax, iwmin, iwmax; + mBasisFunction[0].Evaluate(u, maxOrder, iumin, iumax); + mBasisFunction[1].Evaluate(v, maxOrder, ivmin, ivmax); + mBasisFunction[2].Evaluate(w, maxOrder, iwmin, iwmax); + + // Compute position. + values[0] = Compute(0, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + if (maxOrder >= 1) + { + // Compute first-order derivatives. + values[1] = + Compute(1, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + values[2] = + Compute(0, 1, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + values[3] = + Compute(0, 0, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + if (maxOrder >= 2) + { + // Compute second-order derivatives. + values[4] = + Compute(2, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + values[5] = + Compute(0, 2, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + values[6] = + Compute(0, 0, 2, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + values[7] = + Compute(1, 1, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + values[8] = + Compute(1, 0, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + values[9] = + Compute(0, 1, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + } + } +} + +template +Vector BSplineVolume::Compute(unsigned int uOrder, + unsigned int vOrder, unsigned int wOrder, int iumin, int iumax, int ivmin, + int ivmax, int iwmin, int iwmax) const +{ + // The j*-indices introduce a tiny amount of overhead in order to handle + // both aperiodic and periodic splines. For aperiodic splines, j* = i* + // always. + + int const numControls0 = mNumControls[0]; + int const numControls1 = mNumControls[1]; + int const numControls2 = mNumControls[2]; + Vector result; + result.MakeZero(); + for (int iw = iwmin; iw <= iwmax; ++iw) + { + Real tmpw = mBasisFunction[2].GetValue(wOrder, iw); + int jw = (iw >= numControls2 ? iw - numControls2 : iw); + for (int iv = ivmin; iv <= ivmax; ++iv) + { + Real tmpv = mBasisFunction[1].GetValue(vOrder, iv); + Real tmpvw = tmpv * tmpw; + int jv = (iv >= numControls1 ? iv - numControls1 : iv); + for (int iu = iumin; iu <= iumax; ++iu) + { + Real tmpu = mBasisFunction[0].GetValue(uOrder, iu); + int ju = (iu >= numControls0 ? iu - numControls0 : iu); + result += (tmpu * tmpvw) * + mControls[ju + numControls0*(jv + numControls1*jw)]; + } + } + } + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBandedMatrix.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBandedMatrix.h new file mode 100644 index 000000000000..d738db62540d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBandedMatrix.h @@ -0,0 +1,573 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class BandedMatrix +{ +public: + // Construction and destruction. + ~BandedMatrix(); + BandedMatrix(int size, int numLBands, int numUBands); + + // Member access. + inline int GetSize() const; + inline std::vector& GetDBand(); + inline std::vector const& GetDBand() const; + inline std::vector>& GetLBands(); + inline std::vector> const& GetLBands() const; + inline std::vector>& GetUBands(); + inline std::vector> const& GetUBands() const; + Real& operator()(int r, int c); + Real const& operator()(int r, int c) const; + + // Factor the square banded matrix A into A = L*L^T, where L is a + // lower-triangular matrix (L^T is an upper-triangular matrix). This is + // an LU decomposition that allows for stable inversion of A to solve + // A*X = B. The return value is 'true' iff the factorizing is successful + // (L is invertible). If successful, A contains the Cholesky + // factorization: L in the lower-triangular part of A and L^T in the + // upper-triangular part of A. + bool CholeskyFactor(); + + // Solve the linear system A*X = B, where A is an NxN banded matrix and B + // is an Nx1 vector. The unknown X is also Nx1. The input to this + // function is B. The output X is computed and stored in B. The return + // value is 'true' iff the system has a solution. The matrix A and the + // vector B are both modified by this function. If successful, A contains + // the Cholesky factorization: L in the lower-triangular part of A and + // L^T in the upper-triangular part of A. + bool SolveSystem(Real* bVector); + + // Solve the linear system A*X = B, where A is an NxN banded matrix and + // B is an NxM matrix. The unknown X is also NxM. The input to this + // function is B. The output X is computed and stored in B. The return + // value is 'true' iff the system has a solution. The matrix A and the + // vector B are both modified by this function. If successful, A contains + // the Cholesky factorization: L in the lower-triangular part of A and + // L^T in the upper-triangular part of A. + // + // 'bMatrix' must have the storage order specified by the template + // parameter. + template + bool SolveSystem(Real* bMatrix, int numBColumns); + + // Compute the inverse of the banded matrix. The return value is 'true' + // when the matrix is invertible, in which case the 'inverse' output is + // valid. The return value is 'false' when the matrix is not invertible, + // in which case 'inverse' is invalid and should not be used. The + // input matrix 'inverse' must be the same size as 'this'. + // + // 'bMatrix' must have the storage order specified by the template + // parameter. + template + bool ComputeInverse(Real* inverse) const; + +private: + // The linear system is L*U*X = B, where A = L*U and U = L^T, Reduce this + // to U*X = L^{-1}*B. The return value is 'true' iff the operation is + // successful. + bool SolveLower(Real* dataVector) const; + + // The linear system is U*X = L^{-1}*B. Reduce this to + // X = U^{-1}*L^{-1}*B. The return value is 'true' iff the operation is + // successful. + bool SolveUpper(Real* dataVector) const; + + // The linear system is L*U*X = B, where A = L*U and U = L^T, Reduce this + // to U*X = L^{-1}*B. The return value is 'true' iff the operation is + // successful. See the comments for SolveSystem(Real*,int) about the + // storage for dataMatrix. + template + bool SolveLower(Real* dataMatrix, int numColumns) const; + + // The linear system is U*X = L^{-1}*B. Reduce this to + // X = U^{-1}*L^{-1}*B. The return value is 'true' iff the operation is + // successful. See the comments for SolveSystem(Real*,int) about the + // storage for dataMatrix. + template + bool SolveUpper(Real* dataMatrix, int numColumns) const; + + int mSize; + std::vector mDBand; + std::vector> mLBands, mUBands; + + // For return by operator()(int,int) for valid indices not in the bands, + // in which case the matrix entries are zero, + mutable Real mZero; +}; + + +template +BandedMatrix::~BandedMatrix() +{ +} + +template +BandedMatrix::BandedMatrix(int size, int numLBands, int numUBands) + : + mSize(size), + mZero((Real)0) +{ + if (size > 0 + && 0 <= numLBands && numLBands < size + && 0 <= numUBands && numUBands < size) + { + mDBand.resize(size); + std::fill(mDBand.begin(), mDBand.end(), (Real)0); + + if (numLBands > 0) + { + mLBands.resize(numLBands); + int numElements = size - 1; + for (auto& band : mLBands) + { + band.resize(numElements--); + std::fill(band.begin(), band.end(), (Real)0); + } + } + + if (numUBands > 0) + { + mUBands.resize(numUBands); + int numElements = size - 1; + for (auto& band : mUBands) + { + band.resize(numElements--); + std::fill(band.begin(), band.end(), (Real)0); + } + } + } + else + { + // Invalid argument to BandedMatrix constructor. + mSize = 0; + } +} + +template inline +int BandedMatrix::GetSize() const +{ + return mSize; +} + +template inline +std::vector& BandedMatrix::GetDBand() +{ + return mDBand; +} + +template inline +std::vector const& BandedMatrix::GetDBand() const +{ + return mDBand; +} + +template inline +std::vector>& BandedMatrix::GetLBands() +{ + return mLBands; +} + +template inline +std::vector> const& BandedMatrix::GetLBands() const +{ + return mLBands; +} + +template inline +std::vector>& BandedMatrix::GetUBands() +{ + return mUBands; +} + +template inline +std::vector> const& BandedMatrix::GetUBands() const +{ + return mUBands; +} + +template +Real& BandedMatrix::operator()(int r, int c) +{ + if (0 <= r && r < mSize && 0 <= c && c < mSize) + { + int band = c - r; + if (band > 0) + { + int const numUBands = static_cast(mUBands.size()); + if (--band < numUBands && r < mSize - 1 - band) + { + return mUBands[band][r]; + } + } + else if (band < 0) + { + band = -band; + int const numLBands = static_cast(mLBands.size()); + if (--band < numLBands && c < mSize - 1 - band) + { + return mLBands[band][c]; + } + } + else + { + return mDBand[r]; + } + } + // else invalid index + + + // Set the value to zero in case someone unknowingly modified mZero on a + // previous call to operator(int,int). + mZero = (Real)0; + return mZero; +} + +template +Real const& BandedMatrix::operator()(int r, int c) const +{ + if (0 <= r && r < mSize && 0 <= c && c < mSize) + { + int band = c - r; + if (band > 0) + { + int const numUBands = static_cast(mUBands.size()); + if (--band < numUBands && r < mSize - 1 - band) + { + return mUBands[band][r]; + } + } + else if (band < 0) + { + band = -band; + int const numLBands = static_cast(mLBands.size()); + if (--band < numLBands && c < mSize - 1 - band) + { + return mLBands[band][c]; + } + } + else + { + return mDBand[r]; + } + } + // else invalid index + + + // Set the value to zero in case someone unknowingly modified mZero on a + // previous call to operator(int,int). + mZero = (Real)0; + return mZero; +} + +template +bool BandedMatrix::CholeskyFactor() +{ + if (mDBand.size() == 0 || mLBands.size() != mUBands.size()) + { + // Invalid number of bands. + return false; + } + + int const sizeM1 = mSize - 1; + int const numBands = static_cast(mLBands.size()); + + int k, kMax; + for (int i = 0; i < mSize; ++i) + { + int jMin = i - numBands; + if (jMin < 0) + { + jMin = 0; + } + + int j; + for (j = jMin; j < i; ++j) + { + kMax = j + numBands; + if (kMax > sizeM1) + { + kMax = sizeM1; + } + + for (k = i; k <= kMax; ++k) + { + operator()(k, i) -= operator()(i, j)*operator()(k, j); + } + } + + kMax = j + numBands; + if (kMax > sizeM1) + { + kMax = sizeM1; + } + + for (k = 0; k < i; ++k) + { + operator()(k, i) = operator()(i, k); + } + + Real diagonal = operator()(i, i); + if (diagonal <= (Real)0) + { + return false; + } + Real invSqrt = ((Real)1) / std::sqrt(diagonal); + for (k = i; k <= kMax; ++k) + { + operator()(k, i) *= invSqrt; + } + } + + return true; +} + +template +bool BandedMatrix::SolveSystem(Real* bVector) +{ + return CholeskyFactor() + && SolveLower(bVector) + && SolveUpper(bVector); +} + +template +template +bool BandedMatrix::SolveSystem(Real* bMatrix, int numBColumns) +{ + return CholeskyFactor() + && SolveLower(bMatrix, numBColumns) + && SolveUpper(bMatrix, numBColumns); +} + +template +template +bool BandedMatrix::ComputeInverse(Real* inverse) const +{ + LexicoArray2 invA(mSize, mSize, inverse); + + BandedMatrix tmpA = *this; + for (int row = 0; row < mSize; ++row) + { + for (int col = 0; col < mSize; ++col) + { + if (row != col) + { + invA(row, col) = (Real)0; + } + else + { + invA(row, row) = (Real)1; + } + } + } + + // Forward elimination. + for (int row = 0; row < mSize; ++row) + { + // The pivot must be nonzero in order to proceed. + Real diag = tmpA(row, row); + if (diag == (Real)0) + { + return false; + } + + Real invDiag = ((Real)1) / diag; + tmpA(row, row) = (Real)1; + + // Multiply the row to be consistent with diagonal term of 1. + int colMin = row + 1; + int colMax = colMin + static_cast(mUBands.size()); + if (colMax > mSize) + { + colMax = mSize; + } + + int c; + for (c = colMin; c < colMax; ++c) + { + tmpA(row, c) *= invDiag; + } + for (c = 0; c <= row; ++c) + { + invA(row, c) *= invDiag; + } + + // Reduce the remaining rows. + int rowMin = row + 1; + int rowMax = rowMin + static_cast(mLBands.size()); + if (rowMax > mSize) + { + rowMax = mSize; + } + + for (int r = rowMin; r < rowMax; ++r) + { + Real mult = tmpA(r, row); + tmpA(r, row) = (Real)0; + for (c = colMin; c < colMax; ++c) + { + tmpA(r, c) -= mult*tmpA(row, c); + } + for (c = 0; c <= row; ++c) + { + invA(r, c) -= mult*invA(row, c); + } + } + } + + // Backward elimination. + for (int row = mSize - 1; row >= 1; --row) + { + int rowMax = row - 1; + int rowMin = row - static_cast(mUBands.size()); + if (rowMin < 0) + { + rowMin = 0; + } + + for (int r = rowMax; r >= rowMin; --r) + { + Real mult = tmpA(r, row); + tmpA(r, row) = (Real)0; + for (int c = 0; c < mSize; ++c) + { + invA(r, c) -= mult*invA(row, c); + } + } + } + + return true; +} + +template +bool BandedMatrix::SolveLower(Real* dataVector) const +{ + int const size = static_cast(mDBand.size()); + for (int r = 0; r < size; ++r) + { + Real lowerRR = operator()(r, r); + if (lowerRR >(Real)0) + { + for (int c = 0; c < r; ++c) + { + Real lowerRC = operator()(r, c); + dataVector[r] -= lowerRC * dataVector[c]; + } + dataVector[r] /= lowerRR; + } + else + { + return false; + } + } + return true; +} + +template +bool BandedMatrix::SolveUpper(Real* dataVector) const +{ + int const size = static_cast(mDBand.size()); + for (int r = size - 1; r >= 0; --r) + { + Real upperRR = operator()(r, r); + if (upperRR > (Real)0) + { + for (int c = r + 1; c < size; ++c) + { + Real upperRC = operator()(r, c); + dataVector[r] -= upperRC * dataVector[c]; + } + + dataVector[r] /= upperRR; + } + else + { + return false; + } + } + return true; +} + +template +template +bool BandedMatrix::SolveLower(Real* dataMatrix, int numColumns) const +{ + LexicoArray2 data(mSize, numColumns, dataMatrix); + + for (int r = 0; r < mSize; ++r) + { + Real lowerRR = operator()(r, r); + if (lowerRR >(Real)0) + { + for (int c = 0; c < r; ++c) + { + Real lowerRC = operator()(r, c); + for (int bCol = 0; bCol < numColumns; ++bCol) + { + data(r, bCol) -= lowerRC * data(c, bCol); + } + } + + Real inverse = ((Real)1) / lowerRR; + for (int bCol = 0; bCol < numColumns; ++bCol) + { + data(r, bCol) *= inverse; + } + } + else + { + return false; + } + } + return true; +} + +template +template +bool BandedMatrix::SolveUpper(Real* dataMatrix, int numColumns) const +{ + LexicoArray2 data(mSize, numColumns, dataMatrix); + + for (int r = mSize - 1; r >= 0; --r) + { + Real upperRR = operator()(r, r); + if (upperRR > (Real)0) + { + for (int c = r + 1; c < mSize; ++c) + { + Real upperRC = operator()(r, c); + for (int bCol = 0; bCol < numColumns; ++bCol) + { + data(r, bCol) -= upperRC * data(c, bCol); + } + } + + Real inverse = ((Real)1) / upperRR; + for (int bCol = 0; bCol < numColumns; ++bCol) + { + data(r, bCol) *= inverse; + } + } + else + { + return false; + } + } + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBasisFunction.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBasisFunction.h new file mode 100644 index 000000000000..84bf1c5c3576 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBasisFunction.h @@ -0,0 +1,596 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +struct UniqueKnot +{ + Real t; + int multiplicity; +}; + +template +struct BasisFunctionInput +{ + // The members are uninitialized. + BasisFunctionInput(); + + // Construct an open uniform curve with t in [0,1]. + BasisFunctionInput(int inNumControls, int inDegree); + + int numControls; + int degree; + bool uniform; + bool periodic; + int numUniqueKnots; + std::vector> uniqueKnots; +}; + +template +class BasisFunction +{ +public: + // Let n be the number of control points. Let d be the degree, where + // 1 <= d <= n-1. The number of knots is k = n + d + 1. The knots are + // t[i] for 0 <= i < k and must be nondecreasing, t[i] <= t[i+1], but a + // knot value can be repeated. Let s be the number of distinct knots. + // Let the distinct knots be u[j] for 0 <= j < s, so u[j] < u[j+1] for + // all j. The set of u[j] is called a 'breakpoint sequence'. Let + // m[j] >= 1 be the multiplicity; that is, if t[i] is the first occurrence + // of u[j], then t[i+r] = t[i] for 1 <= r < m[j]. The multiplicities have + // the constraints m[0] <= d+1, m[s-1] <= d+1, and m[j] <= d for + // 1 <= j <= s-2. Also, k = sum_{j=0}^{s-1} m[j], which says the + // multiplicities account for all k knots. + // + // Given a knot vector (t[0],...,t[n+d]), the domain of the corresponding + // B-spline curve is the interval [t[d],t[n]]. + // + // The corresponding B-spline or NURBS curve is characterized as follows. + // See "Geometric Modeling with Splines: An Introduction" by Elaine Cohen, + // Richard F. Riesenfeld, and Gershon Elber, AK Peters, 2001, Natick MA. + // The curve is 'open' when m[0] = m[s-1] = d+1; otherwise, it is + // 'floating'. An open curve is uniform when the knots t[d] through + // t[n] are equally spaced; that is, t[i+1] - t[i] are a common value + // for d <= i <= n-1. By implication, s = n-d+1 and m[j] = 1 for + // 1 <= j <= s-2. An open curve that does not satisfy these conditions + // is said to be nonuniform. A floating curve is uniform when + // m[j] = 1 for 0 <= j <= s-1 and t[i+1] - t[i] are a common value for + // 0 <= i <= k-2; otherwise, the floating curve is nonuniform. + // + // A special case of a floating curve is a periodic curve. The intent + // is that the curve is closed, so the first and last control points + // should be the same, which ensures C^{0} continuity. Higher-order + // continuity is obtained by repeating more control points. If the + // control points are P[0] through P[n-1], append the points P[0] + // through P[d-1] to ensure C^{d-1} continuity. Additionally the knots + // must be chosen properly. You may choose t[d] through t[n] as you + // wish. The other knots are defined by + // t[i] - t[i-1] = t[n-d+i] - t[n-d+i-1] + // t[n+i] - t[n+i-1] = t[d+i] - t[d+i-1] + // for 1 <= i <= d. + + // Construction and destruction. The determination that the curve is + // open or floating is based on the multiplicities. The 'uniform' input + // is used to avoid misclassifications due to floating-point rounding + // errors. Specifically, the breakpoints might be equally spaced + // (uniform) as real numbers, but the floating-point representations can + // have rounding errors that cause the knot differences not to be exactly + // the same constant. A periodic curve can have uniform or nonuniform + // knots. This object makes copies of the input arrays. + + ~BasisFunction(); + BasisFunction(); + BasisFunction(BasisFunctionInput const& input); + + // No copying is allowed. + BasisFunction(BasisFunction const&) = delete; + BasisFunction& operator=(BasisFunction const&) = delete; + + // Support for explicit creation in classes that have std::array + // members involving BasisFunction. This is a call-once function. + void Create(BasisFunctionInput const& input); + + // The inputs have complicated validation tests. You should create a + // BasisFunction object as shown: + // BasisFunction function(parameters); + // if (!function) { ; } + inline operator bool() const; + + // Member access. + inline int GetNumControls() const; + inline int GetDegree() const; + inline int GetNumUniqueKnots() const; + inline int GetNumKnots() const; + inline Real GetMinDomain() const; + inline Real GetMaxDomain() const; + inline bool IsOpen() const; + inline bool IsUniform() const; + inline bool IsPeriodic() const; + inline UniqueKnot const* GetUniqueKnots() const; + inline Real const* GetKnots() const; + + // Evaluation of the basis function and its derivatives through order 3. + // For the function value only, pass order 0. For the function and first + // derivative, pass order 1, and so on. + void Evaluate(Real t, unsigned int order, int& minIndex, int& maxIndex) + const; + + // Access the results of the call to Evaluate(...). The index i must + // satisfy minIndex <= i <= maxIndex. If it is not, the function returns + // zero. The separation of evaluation and access is based on local + // control of the basis function; that is, only the accessible values are + // (potentially) not zero. + Real GetValue(unsigned int order, int i) const; + +private: + // Determine the index i for which knot[i] <= t < knot[i+1]. The t-value + // is modified (wrapped for periodic splines, clamped for nonperiodic + // splines). + int GetIndex(Real& t) const; + + // Constructor inputs and values derived from them. + int mNumControls; + int mDegree; + Real mTMin, mTMax, mTLength; + bool mOpen; + bool mUniform; + bool mPeriodic; + bool mConstructed; + std::vector> mUniqueKnots; + std::vector mKnots; + + // Lookup information for the GetIndex() function. The first element of + // the pair is a unique knot value. The second element is the index in + // mKnots[] for the last occurrence of that knot value. NOTE: This is + // a heavily used function during Evaluate calls. This member was + // initially set to be std::vector<*>, but in debug mode the range-based + // for-loop in GetIndex was taking an extremely long time due to checked + // calls involving iterators. We modified this to a regular array to + // ensure that debug performance is better. + std::vector> mKeys; + + // Storage for the basis functions and their first three derivatives. + mutable Array2 mValue[4]; // mValue[i] is array[d+1][n+d] +}; + + +template +BasisFunctionInput::BasisFunctionInput() +{ +} + +template +BasisFunctionInput::BasisFunctionInput(int inNumControls, int inDegree) + : + numControls(inNumControls), + degree(inDegree), + uniform(true), + periodic(false), + numUniqueKnots(numControls - degree + 1), + uniqueKnots(numUniqueKnots) +{ + uniqueKnots.front().t = (Real)0; + uniqueKnots.front().multiplicity = degree + 1; + for (int i = 1; i <= numUniqueKnots - 2; ++i) + { + uniqueKnots[i].t = i / (numUniqueKnots - (Real)1); + uniqueKnots[i].multiplicity = 1; + } + uniqueKnots.back().t = (Real)1; + uniqueKnots.back().multiplicity = degree + 1; +} + +template +BasisFunction::~BasisFunction() +{ +} + +template +BasisFunction::BasisFunction() + : + mNumControls(0), + mDegree(0), + mTMin((Real)0), + mTMax((Real)0), + mTLength((Real)0), + mOpen(false), + mUniform(false), + mPeriodic(false), + mConstructed(false) +{ +} + +template +BasisFunction::BasisFunction(BasisFunctionInput const& input) + : + mConstructed(false) +{ + Create(input); +} + + +template +void BasisFunction::Create(BasisFunctionInput const& input) +{ + if (mConstructed) + { + LogError("Object already created."); + return; + } + + mNumControls = (input.periodic ? input.numControls + input.degree : input.numControls); + mDegree = input.degree; + mTMin = (Real)0; + mTMax = (Real)0; + mTLength = (Real)0; + mOpen = false; + mUniform = input.uniform; + mPeriodic = input.periodic; + for (int i = 0; i < 4; ++i) + { + mValue[i] = Array2(); + } + + if (input.numControls < 2) + { + LogError("Invalid number of control points."); + return; + } + + if (input.degree < 1 || input.degree >= input.numControls) + { + LogError("Invalid degree."); + return; + } + + if (input.numUniqueKnots < 2) + { + LogError("Invalid number of unique knots."); + return; + } + + mUniqueKnots.resize(input.numUniqueKnots); + std::copy(input.uniqueKnots.begin(), input.uniqueKnots.begin() + input.numUniqueKnots, + mUniqueKnots.begin()); + + Real u = mUniqueKnots.front().t; + for (int i = 1; i < input.numUniqueKnots - 1; ++i) + { + Real uNext = mUniqueKnots[i].t; + if (u >= uNext) + { + LogError("Unique knots are not strictly increasing."); + return; + } + u = uNext; + } + + int mult0 = mUniqueKnots.front().multiplicity; + if (mult0 < 1 || mult0 > mDegree + 1) + { + LogError("Invalid first multiplicity."); + return; + } + + int mult1 = mUniqueKnots.back().multiplicity; + if (mult1 < 1 || mult1 > mDegree + 1) + { + LogError("Invalid last multiplicity."); + return; + } + + for (int i = 1; i <= input.numUniqueKnots - 2; ++i) + { + int mult = mUniqueKnots[i].multiplicity; + if (mult < 1 || mult > mDegree) + { + LogError("Invalid interior multiplicity."); + return; + } + } + + mOpen = (mult0 == mult1 && mult0 == mDegree + 1); + + mKnots.resize(mNumControls + mDegree + 1); + mKeys.resize(input.numUniqueKnots); + int sum = 0; + for (int i = 0, j = 0; i < input.numUniqueKnots; ++i) + { + Real tCommon = mUniqueKnots[i].t; + int mult = mUniqueKnots[i].multiplicity; + for (int k = 0; k < mult; ++k, ++j) + { + mKnots[j] = tCommon; + } + + mKeys[i].first = tCommon; + mKeys[i].second = sum - 1; + sum += mult; + } + + mTMin = mKnots[mDegree]; + mTMax = mKnots[mNumControls]; + mTLength = mTMax - mTMin; + + size_t numRows = mDegree + 1; + size_t numCols = mNumControls + mDegree; + size_t numBytes = numRows * numCols * sizeof(Real); + for (int i = 0; i < 4; ++i) + { + mValue[i] = Array2(numCols, numRows); + memset(mValue[i][0], 0, numBytes); + } + + mConstructed = true; +} + +template inline +BasisFunction::operator bool() const +{ + return mConstructed; +} + +template inline +int BasisFunction::GetNumControls() const +{ + return mNumControls; +} + +template inline +int BasisFunction::GetDegree() const +{ + return mDegree; +} + +template inline +int BasisFunction::GetNumUniqueKnots() const +{ + return static_cast(mUniqueKnots.size()); +} + +template inline +int BasisFunction::GetNumKnots() const +{ + return static_cast(mKnots.size()); +} + +template inline +Real BasisFunction::GetMinDomain() const +{ + return mTMin; +} + +template inline +Real BasisFunction::GetMaxDomain() const +{ + return mTMax; +} + +template inline +bool BasisFunction::IsOpen() const +{ + return mOpen; +} + +template inline +bool BasisFunction::IsUniform() const +{ + return mUniform; +} + +template inline +bool BasisFunction::IsPeriodic() const +{ + return mPeriodic; +} + +template inline +UniqueKnot const* BasisFunction::GetUniqueKnots() const +{ + return &mUniqueKnots[0]; +} + +template inline +Real const* BasisFunction::GetKnots() const +{ + return &mKnots[0]; +} + +template +void BasisFunction::Evaluate(Real t, unsigned int order, int& minIndex, + int& maxIndex) const +{ + if (!mConstructed) + { + // Errors were already generated during construction. Return an index + // range that leads to zero-valued positions and derivatives. + minIndex = -1; + maxIndex = -1; + return; + } + + if (order > 3) + { + LogError("Only derivatives through order 3 are supported."); + minIndex = 0; + maxIndex = 0; + return; + } + + int i = GetIndex(t); + mValue[0][0][i] = (Real)1; + + if (order >= 1) + { + mValue[1][0][i] = (Real)0; + if (order >= 2) + { + mValue[2][0][i] = (Real)0; + if (order >= 3) + { + mValue[3][0][i] = (Real)0; + } + } + } + + Real n0 = t - mKnots[i], n1 = mKnots[i + 1] - t; + Real e0, e1, d0, d1, invD0, invD1; + int j; + for (j = 1; j <= mDegree; j++) + { + d0 = mKnots[i + j] - mKnots[i]; + d1 = mKnots[i + 1] - mKnots[i - j + 1]; + invD0 = (d0 > (Real)0 ? (Real)1 / d0 : (Real)0); + invD1 = (d1 > (Real)0 ? (Real)1 / d1 : (Real)0); + + e0 = n0*mValue[0][j - 1][i]; + mValue[0][j][i] = e0*invD0; + e1 = n1*mValue[0][j - 1][i - j + 1]; + mValue[0][j][i - j] = e1*invD1; + + if (order >= 1) + { + e0 = n0 * mValue[1][j - 1][i] + mValue[0][j - 1][i]; + mValue[1][j][i] = e0 * invD0; + e1 = n1 * mValue[1][j - 1][i - j + 1] - mValue[0][j - 1][i - j + 1]; + mValue[1][j][i - j] = e1 * invD1; + + if (order >= 2) + { + e0 = n0 * mValue[2][j - 1][i] + ((Real)2) * mValue[1][j - 1][i]; + mValue[2][j][i] = e0 * invD0; + e1 = n1 * mValue[2][j - 1][i - j + 1] - ((Real)2) * mValue[1][j - 1][i - j + 1]; + mValue[2][j][i - j] = e1 * invD1; + + if (order >= 3) + { + e0 = n0 * mValue[3][j - 1][i] + ((Real)3) * mValue[2][j - 1][i]; + mValue[3][j][i] = e0 * invD0; + e1 = n1 * mValue[3][j - 1][i - j + 1] - ((Real)3) * mValue[2][j - 1][i - j + 1]; + mValue[3][j][i - j] = e1 * invD1; + } + } + } + } + + for (j = 2; j <= mDegree; ++j) + { + for (int k = i - j + 1; k < i; ++k) + { + n0 = t - mKnots[k]; + n1 = mKnots[k + j + 1] - t; + d0 = mKnots[k + j] - mKnots[k]; + d1 = mKnots[k + j + 1] - mKnots[k + 1]; + invD0 = (d0 >(Real)0 ? (Real)1 / d0 : (Real)0); + invD1 = (d1 > (Real)0 ? (Real)1 / d1 : (Real)0); + + e0 = n0*mValue[0][j - 1][k]; + e1 = n1*mValue[0][j - 1][k + 1]; + mValue[0][j][k] = e0 * invD0 + e1 * invD1; + + if (order >= 1) + { + e0 = n0 * mValue[1][j - 1][k] + mValue[0][j - 1][k]; + e1 = n1 * mValue[1][j - 1][k + 1] - mValue[0][j - 1][k + 1]; + mValue[1][j][k] = e0 * invD0 + e1 * invD1; + + if (order >= 2) + { + e0 = n0 * mValue[2][j - 1][k] + ((Real)2) * mValue[1][j - 1][k]; + e1 = n1 * mValue[2][j - 1][k + 1] - ((Real)2) * mValue[1][j - 1][k + 1]; + mValue[2][j][k] = e0 * invD0 + e1 * invD1; + + if (order >= 3) + { + e0 = n0 * mValue[3][j - 1][k] + ((Real)3) * mValue[2][j - 1][k]; + e1 = n1 * mValue[3][j - 1][k + 1] - ((Real)3) * mValue[2][j - 1][k + 1]; + mValue[3][j][k] = e0 * invD0 + e1 * invD1; + } + } + } + } + } + + minIndex = i - mDegree; + maxIndex = i; +} + +template +Real BasisFunction::GetValue(unsigned int order, int i) const +{ + if (!mConstructed) + { + // Errors were already generated during construction. Return a value + // that leads to zero-valued positions and derivatives. + return (Real)0; + } + + if (order < 4) + { + if (0 <= i && i < mNumControls + mDegree) + { + return mValue[order][mDegree][i]; + } + } + + LogError("Invalid input."); + return (Real)0; +} + +template +int BasisFunction::GetIndex(Real& t) const +{ + // Find the index i for which knot[i] <= t < knot[i+1]. + if (mPeriodic) + { + // Wrap to [tmin,tmax]. + Real r = std::fmod(t - mTMin, mTLength); + if (r < (Real)0) + { + r += mTLength; + } + t = mTMin + r; + } + + // Clamp to [tmin,tmax]. For the periodic case, this handles small + // numerical rounding errors near the domain endpoints. + if (t <= mTMin) + { + t = mTMin; + return mDegree; + } + if (t >= mTMax) + { + t = mTMax; + return mNumControls - 1; + } + + // At this point, tmin < t < tmax. + for (auto const& key : mKeys) + { + if (t < key.first) + { + return key.second; + } + } + + // We should not reach this code. + LogError("Unexpected condition."); + t = mTMin; + return mDegree; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBezierCurve.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBezierCurve.h new file mode 100644 index 000000000000..36fb9687d2aa --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBezierCurve.h @@ -0,0 +1,202 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class BezierCurve : public ParametricCurve +{ +public: + // Construction and destruction. The number of control points must be + // degree + 1. This object copies the input array. The domain is t + // in [0,1]. To validate construction, create an object as shown: + // BezierCurve curve(parameters); + // if (!curve) { ; } + virtual ~BezierCurve(); + BezierCurve(int degree, Vector const* controls); + + // Member access. + inline int GetDegree() const; + inline int GetNumControls() const; + inline Vector const* GetControls() const; + + // Evaluation of the curve. The function supports derivative calculation + // through order 3; that is, maxOrder <= 3 is required. If you want + // only the position, pass in maxOrder of 0. If you want the position and + // first derivative, pass in maxOrder of 1, and so on. The output + // 'values' are ordered as: position, first derivative, second derivative, + // third derivative. + virtual void Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const; + +protected: + // Support for Evaluate(...). + Vector Compute(Real t, Real omt, int order) const; + + int mDegree, mNumControls; + std::vector> mControls[4]; + Array2 mChoose; +}; + + +template +BezierCurve::~BezierCurve() +{ +} + +template +BezierCurve::BezierCurve(int degree, Vector const* controls) + : + ParametricCurve((Real)0, (Real)1), + mDegree(degree), + mNumControls(degree + 1), + mChoose(mNumControls, mNumControls) +{ + if (degree < 2 || !controls) + { + LogError("Invalid input."); + return; + } + + // Copy the controls. + mControls[0].resize(mNumControls); + std::copy(controls, controls + mNumControls, mControls[0].begin()); + + // Compute first-order differences. + mControls[1].resize(mNumControls - 1); + for (int i = 0; i < mNumControls - 1; ++i) + { + mControls[1][i] = mControls[0][i + 1] - mControls[0][i]; + } + + // Compute second-order differences. + mControls[2].resize(mNumControls - 2); + for (int i = 0; i < mNumControls - 2; ++i) + { + mControls[2][i] = mControls[1][i + 1] - mControls[1][i]; + } + + // Compute third-order differences. + if (degree >= 3) + { + mControls[3].resize(mNumControls - 3); + for (int i = 0; i < mNumControls - 3; ++i) + { + mControls[3][i] = mControls[2][i + 1] - mControls[2][i]; + } + } + + // Compute combinatorial values Choose(n,k) and store in mChoose[n][k]. + // The values mChoose[r][c] are invalid for r < c; that is, we use only + // the entries for r >= c. + mChoose[0][0] = (Real)1; + mChoose[1][0] = (Real)1; + mChoose[1][1] = (Real)1; + for (int i = 2; i <= mDegree; ++i) + { + mChoose[i][0] = (Real)1; + mChoose[i][i] = (Real)1; + for (int j = 1; j < i; ++j) + { + mChoose[i][j] = mChoose[i - 1][j - 1] + mChoose[i - 1][j]; + } + } + + this->mConstructed = true; +} + +template inline +int BezierCurve::GetDegree() const +{ + return mDegree; +} + +template inline +int BezierCurve::GetNumControls() const +{ + return mNumControls; +} + +template inline +Vector const* BezierCurve::GetControls() const +{ + return &mControls[0][0]; +} + +template +void BezierCurve::Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const +{ + if (!this->mConstructed) + { + // Errors were already generated during construction. + for (unsigned int order = 0; order < 4; ++order) + { + values[order].MakeZero(); + } + return; + } + + // Compute position. + Real omt = (Real)1 - t; + values[0] = Compute(t, omt, 0); + if (maxOrder >= 1) + { + // Compute first derivative. + values[1] = Compute(t, omt, 1); + if (maxOrder >= 2) + { + // Compute second derivative. + values[2] = Compute(t, omt, 2); + if (maxOrder >= 3 && mDegree >= 3) + { + // Compute third derivative. + values[3] = Compute(t, omt, 3); + } + else + { + values[3].MakeZero(); + } + } + } +} + +template +Vector BezierCurve::Compute(Real t, Real omt, int order) +const +{ + Vector result = omt * mControls[order][0]; + + Real tpow = t; + int isup = mDegree - order; + for (int i = 1; i < isup; ++i) + { + Real c = mChoose[isup][i] * tpow; + result = (result + c * mControls[order][i]) * omt; + tpow *= t; + } + result = (result + tpow * mControls[order][isup]); + + int multiplier = 1; + for (int i = 0; i < order; ++i) + { + multiplier *= mDegree - i; + } + result *= (Real)multiplier; + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBitHacks.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBitHacks.cpp new file mode 100644 index 000000000000..1960fe6dde93 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBitHacks.cpp @@ -0,0 +1,191 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2017/10/22) + +#include +#include + +namespace gte +{ + +static int32_t const gsLeadingBitTable[32] = +{ + 0, 9, 1, 10, 13, 21, 2, 29, + 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, + 19, 27, 23, 6, 26, 5, 4, 31 +}; + +static int32_t const gsTrailingBitTable[32] = +{ + 0, 1, 28, 2, 29, 14, 24, 3, + 30, 22, 20, 15, 25, 17, 4, 8, + 31, 27, 13, 23, 21, 19, 16, 7, + 26, 12, 18, 6, 11, 5, 10, 9 +}; + + +bool IsPowerOfTwo(uint32_t value) +{ + return (value > 0) && ((value & (value - 1)) == 0); +} + +bool IsPowerOfTwo(int32_t value) +{ + return (value > 0) && ((value & (value - 1)) == 0); +} + +uint32_t Log2OfPowerOfTwo(uint32_t powerOfTwo) +{ + uint32_t log2 = (powerOfTwo & 0xAAAAAAAAu) != 0; + log2 |= ((powerOfTwo & 0xFFFF0000u) != 0) << 4; + log2 |= ((powerOfTwo & 0xFF00FF00u) != 0) << 3; + log2 |= ((powerOfTwo & 0xF0F0F0F0u) != 0) << 2; + log2 |= ((powerOfTwo & 0xCCCCCCCCu) != 0) << 1; + return log2; +} + +int32_t Log2OfPowerOfTwo(int32_t powerOfTwo) +{ + uint32_t log2 = (powerOfTwo & 0xAAAAAAAAu) != 0; + log2 |= ((powerOfTwo & 0xFFFF0000u) != 0) << 4; + log2 |= ((powerOfTwo & 0xFF00FF00u) != 0) << 3; + log2 |= ((powerOfTwo & 0xF0F0F0F0u) != 0) << 2; + log2 |= ((powerOfTwo & 0xCCCCCCCCu) != 0) << 1; + return static_cast(log2); +} + +int32_t GetLeadingBit(uint32_t value) +{ + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + uint32_t key = (value * 0x07C4ACDDu) >> 27; + return gsLeadingBitTable[key]; +} + +int32_t GetLeadingBit(int32_t value) +{ + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + uint32_t key = (value * 0x07C4ACDDu) >> 27; + return gsLeadingBitTable[key]; +} + +int32_t GetLeadingBit(uint64_t value) +{ + uint32_t v1 = (uint32_t)((value >> 32) & 0x00000000FFFFFFFFull); + if (v1 != 0) + { + return GetLeadingBit(v1) + 32; + } + + uint32_t v0 = (uint32_t)(value & 0x00000000FFFFFFFFull); + return GetLeadingBit(v0); +} + +int32_t GetLeadingBit(int64_t value) +{ + int32_t v1 = (int32_t)((value >> 32) & 0x00000000FFFFFFFFull); + if (v1 != 0) + { + return GetLeadingBit(v1) + 32; + } + + int32_t v0 = (int32_t)(value & 0x00000000FFFFFFFFull); + return GetLeadingBit(v0); +} + +int32_t GetTrailingBit(uint32_t value) +{ + // Avoid warning for negation of unsigned 'value'. + int32_t const& iValue = reinterpret_cast(value); + uint32_t key = ((uint32_t)((iValue & -iValue) * 0x077CB531u)) >> 27; + return gsTrailingBitTable[key]; +} + +int32_t GetTrailingBit(int32_t value) +{ + uint32_t key = ((uint32_t)((value & -value) * 0x077CB531u)) >> 27; + return gsTrailingBitTable[key]; +} + +int32_t GetTrailingBit(uint64_t value) +{ + uint32_t v0 = (uint32_t)(value & 0x00000000FFFFFFFFull); + if (v0 != 0) + { + return GetTrailingBit(v0); + } + + uint32_t v1 = (uint32_t)((value >> 32) & 0x00000000FFFFFFFFull); + if (v1 != 0) + { + return GetTrailingBit(v1) + 32; + } + return 0; +} + +int32_t GetTrailingBit(int64_t value) +{ + int32_t v0 = (int32_t)(value & 0x00000000FFFFFFFFull); + if (v0 != 0) + { + return GetTrailingBit(v0); + } + + int32_t v1 = (int32_t)((value >> 32) & 0x00000000FFFFFFFFull); + if (v1 != 0) + { + return GetTrailingBit(v1) + 32; + } + return 0; +} + +uint64_t RoundUpToPowerOfTwo(uint32_t value) +{ + if (value > 0) + { + int32_t leading = GetLeadingBit(value); + uint32_t mask = (1 << leading); + if ((value & ~mask) == 0) + { + // value is a power of two + return static_cast(value); + } + else + { + // round up to a power of two + return (static_cast(mask) << 1); + } + + } + else + { + return 1ull; + } +} + +uint32_t RoundDownToPowerOfTwo(uint32_t value) +{ + if (value > 0) + { + int32_t leading = GetLeadingBit(value); + uint32_t mask = (1 << leading); + return mask; + } + else + { + return 0; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBitHacks.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBitHacks.h new file mode 100644 index 000000000000..fc2421f21e74 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBitHacks.h @@ -0,0 +1,40 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2017/07/04) + +#pragma once + +#include +#include + +namespace gte +{ + +GTE_IMPEXP bool IsPowerOfTwo(uint32_t value); +GTE_IMPEXP bool IsPowerOfTwo(int32_t value); + +GTE_IMPEXP uint32_t Log2OfPowerOfTwo(uint32_t powerOfTwo); +GTE_IMPEXP int32_t Log2OfPowerOfTwo(int32_t powerOfTwo); + +// Call these only for nonzero values. If value is zero, then GetLeadingBit +// and GetTrailingBit return zero. +GTE_IMPEXP int32_t GetLeadingBit(uint32_t value); +GTE_IMPEXP int32_t GetLeadingBit(int32_t value); +GTE_IMPEXP int32_t GetLeadingBit(uint64_t value); +GTE_IMPEXP int32_t GetLeadingBit(int64_t value); +GTE_IMPEXP int32_t GetTrailingBit(uint32_t value); +GTE_IMPEXP int32_t GetTrailingBit(int32_t value); +GTE_IMPEXP int32_t GetTrailingBit(uint64_t value); +GTE_IMPEXP int32_t GetTrailingBit(int64_t value); + +// Round up to a power of two. If input is zero, the return is 1. If input +// is larger than 2^{31}, the return is 2^{32}. +GTE_IMPEXP uint64_t RoundUpToPowerOfTwo(uint32_t value); + +// Round down to a power of two. If input is zero, the return is 0. +GTE_IMPEXP uint32_t RoundDownToPowerOfTwo(uint32_t value); + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCapsule.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCapsule.h new file mode 100644 index 000000000000..17e6304dd01b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCapsule.h @@ -0,0 +1,109 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +// A capsule is the set of points that are equidistant from a segment, the +// common distance called the radius. + +template +class Capsule +{ +public: + // Construction and destruction. The default constructor sets the segment + // to have endpoints p0 = (-1,0,...,0) and p1 = (1,0,...,0), and the + // radius is 1. + Capsule(); + Capsule(Segment const& inSegment, Real inRadius); + + // Public member access. + Segment segment; + Real radius; + +public: + // Comparisons to support sorted containers. + bool operator==(Capsule const& capsule) const; + bool operator!=(Capsule const& capsule) const; + bool operator< (Capsule const& capsule) const; + bool operator<=(Capsule const& capsule) const; + bool operator> (Capsule const& capsule) const; + bool operator>=(Capsule const& capsule) const; +}; + +// Template alias for convenience. +template +using Capsule3 = Capsule<3, Real>; + + +template +Capsule::Capsule() + : + radius((Real)1) +{ +} + +template +Capsule::Capsule(Segment const& inSegment, Real inRadius) + : + segment(inSegment), + radius(inRadius) +{ +} + +template +bool Capsule::operator==(Capsule const& capsule) const +{ + return segment == capsule.segment && radius == capsule.radius; +} + +template +bool Capsule::operator!=(Capsule const& capsule) const +{ + return !operator==(capsule); +} + +template +bool Capsule::operator<(Capsule const& capsule) const +{ + if (segment < capsule.segment) + { + return true; + } + + if (segment > capsule.segment) + { + return false; + } + + return radius < capsule.radius; +} + +template +bool Capsule::operator<=(Capsule const& capsule) const +{ + return operator<(capsule) || operator==(capsule); +} + +template +bool Capsule::operator>(Capsule const& capsule) const +{ + return !operator<=(capsule); +} + +template +bool Capsule::operator>=(Capsule const& capsule) const +{ + return !operator<(capsule); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteChebyshevRatio.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteChebyshevRatio.h new file mode 100644 index 000000000000..44492055aaa8 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteChebyshevRatio.h @@ -0,0 +1,192 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +// Let f(t,A) = sin(t*A)/sin(A). The slerp of quaternions q0 and q1 is +// slerp(t,q0,q1) = f(1-t,A)*q0 + f(t,A)*q1. +// Let y = 1-cos(A); we allow A in [0,pi], so y in [0,1]. As a function of y, +// a series representation for f(t,y) is +// f(t,y) = sum_{i=0}^{infinity} c_{i}(t) y^{i} +// where c_0(t) = t, c_{i}(t) = c_{i-1}(t)*(i^2 - t^2)/(i*(2*i+1)) for i >= 1. +// The c_{i}(t) are polynomials in t of degree 2*i+1. The paper +// +// A Fast and Accurate Algorithm for Computing SLERP, +// David Eberly, +// Journal of Graphics, GPU, and Game Tools, 15 : 3, 161 - 176, 2011 +// http://www.tandfonline.com/doi/abs/10.1080/2151237X.2011.610255#.VHRB7ouUd8E +// +// derives an approximation +// g(t,y) = sum_{i=0}^{n-1} c_{i}(t) y^{i} + (1+u_n) c_{n}(t) y^n +// which has degree 2*n+1 in t and degree n in y. The u_n were chosen to +// balance the error at y = 1 (A = pi/2). Unfortunately, the analysis for the +// global error bounds (0 <= y <= 1) is flawed; the error bounds printed in +// the paper are too small by about one order of magnitude (factor of 10). +// Instead they are the following, verified by Mathematica using larger +// precision than 'float' or 'double': +// +// n | error | n | error | n | error | n | error +// --+------------+----+------------+----+------------+----+------------- +// 1 | 2.60602e-2 | 5 | 3.63188e-4 | 9 | 1.12223e-5 | 13 | 4.37180e-7 +// 2 | 7.43321e-3 | 6 | 1.47056e-4 | 10 | 4.91138e-6 | 14 | 1.98230e-7 +// 3 | 2.51798e-3 | 7 | 6.11808e-5 | 11 | 2.17345e-6 | 15 | 9.04302e-8 +// 4 | 9.30819e-4 | 8 | 2.59880e-5 | 12 | 9.70876e-7 | 16 | 4.03665e-8 +// +// The maximum errors all occur for an angle that is nearly pi/2. The +// approximation is much more accurate when for angles A in [0,pi/4]. With +// this restriction, the global error bounds are +// +// n | error | n | error | n | error | n | error +// --+------------+----+------------+----+-------------+----+------------- +// 1 | 1.90648e-2 | 5 | 5.83197e-6 | 9 | 6.80465e-10 | 13 | 3.39728e-13 +// 2 | 2.43581e-3 | 6 | 8.00278e-6 | 10 | 9.12477e-11 | 14 | 4.70735e-14 +// 3 | 3.20235e-4 | 7 | 1.10649e-7 | 11 | 4.24608e-11 | 15 | 1.55431e-15 +// 4 | 4.29242e-5 | 8 | 1.53828e-7 | 12 | 5.93392e-12 | 16 | 1.11022e-16 +// +// Given q0 and q1 such that cos(A) = dot(q0,q1) in [0,1], in which case +// A in [0,pi/2], let qh = (q0+q1)/|q0 + q1| = slerp(1/2,q0,q1). Note that +// |q0 + q1| = 2*cos(A/2) because +// sin(A/2)/sin(A) = sin(A/2)/(2*sin(A/2)*cos(A/2)) = 1/(2*cos(A/2)) +// The angle between q0 and qh is the same as the angle between qh and q1, +// namely, A/2 in [0,pi/4]. It may be shown that +// +-- +// slerp(t,q0,q1) = | slerp(2*t,q0,qh), 0 <= t <= 1/2 +// | slerp(2*t-1,qh,q1), 1/2 <= t <= 1 +// +-- +// The slerp functions on the right-hand side involve angles in [0,pi/4], so +// the approximation is more accurate for those evaluations than using the +// original. + +namespace gte +{ + +template +class ChebyshevRatio +{ +public: + // Compute f(t,A) = sin(t*A)/sin(A), where t in [0,1], A in [0,pi/2], + // cosA = cos(A), f0 = f(1-t,A), and f1 = f(t,A). + inline static void Get(Real t, Real cosA, Real& f0, Real& f1); + + // Compute estimates for f(t,y) = sin(t*A)/sin(A), where t in [0,1], + // A in [0,pi/2], y = 1 - cos(A), f0 is the estimate for f(1-t,y), and + // f1 is the estimate for f(t,y). The approximating function is a + // polynomial of two variables. The template parameter N must be in + // {1..16}. The degree in t is 2*N+1 and the degree in Y is N. + template + inline static void GetEstimate(Real t, Real y, Real& f0, Real& f1); +}; + + +template inline +void ChebyshevRatio::Get(Real t, Real cosA, Real& f0, Real& f1) +{ + if (cosA < (Real)1) + { + // The angle A is in (0,pi/2]. + Real A = std::acos(cosA); + Real invSinA = ((Real)1) / std::sin(A); + f0 = std::sin(((Real)1 - t) * A) * invSinA; + f1 = std::sin(t * A) * invSinA; + } + else + { + // The angle theta is 0. + f0 = (Real)1 - t; + f1 = (Real)t; + } +} + +template +template inline +void ChebyshevRatio::GetEstimate(Real t, Real y, Real& f0, Real& f1) +{ + static_assert(1 <= N && N <= 16, "Invalid degree."); + + // The ASM output of the MSVS 2013 Release build shows that the constants + // in these arrays are loaded to XMM registers as literal values, and only + // those constants required for the specified degree D are loaded. That + // is, the compiler does a good job of optimizing the code. + + Real const onePlusMu[16] = + { + (Real)1.62943436108234530, + (Real)1.73965850021313961, + (Real)1.79701067629566813, + (Real)1.83291820510335812, + (Real)1.85772477879039977, + (Real)1.87596835698904785, + (Real)1.88998444919711206, + (Real)1.90110745351730037, + (Real)1.91015881189952352, + (Real)1.91767344933047190, + (Real)1.92401541194159076, + (Real)1.92944142668012797, + (Real)1.93413793373091059, + (Real)1.93824371262559758, + (Real)1.94186426368404708, + (Real)1.94508125972497303 + }; + + Real const a[16] = + { + (N != 1 ? (Real)1 : onePlusMu[0]) / ((Real)1 * (Real)3), + (N != 2 ? (Real)1 : onePlusMu[1]) / ((Real)2 * (Real)5), + (N != 3 ? (Real)1 : onePlusMu[2]) / ((Real)3 * (Real)7), + (N != 4 ? (Real)1 : onePlusMu[3]) / ((Real)4 * (Real)9), + (N != 5 ? (Real)1 : onePlusMu[4]) / ((Real)5 * (Real)11), + (N != 6 ? (Real)1 : onePlusMu[5]) / ((Real)6 * (Real)13), + (N != 7 ? (Real)1 : onePlusMu[6]) / ((Real)7 * (Real)15), + (N != 8 ? (Real)1 : onePlusMu[7]) / ((Real)8 * (Real)17), + (N != 9 ? (Real)1 : onePlusMu[8]) / ((Real)9 * (Real)19), + (N != 10 ? (Real)1 : onePlusMu[9]) / ((Real)10 * (Real)21), + (N != 11 ? (Real)1 : onePlusMu[10]) / ((Real)11 * (Real)23), + (N != 12 ? (Real)1 : onePlusMu[11]) / ((Real)12 * (Real)25), + (N != 13 ? (Real)1 : onePlusMu[12]) / ((Real)13 * (Real)27), + (N != 14 ? (Real)1 : onePlusMu[13]) / ((Real)14 * (Real)29), + (N != 15 ? (Real)1 : onePlusMu[14]) / ((Real)15 * (Real)31), + (N != 16 ? (Real)1 : onePlusMu[15]) / ((Real)16 * (Real)33) + }; + + Real const b[16] = + { + (N != 1 ? (Real)1 : onePlusMu[0]) * (Real)1 / (Real)3, + (N != 2 ? (Real)1 : onePlusMu[1]) * (Real)2 / (Real)5, + (N != 3 ? (Real)1 : onePlusMu[2]) * (Real)3 / (Real)7, + (N != 4 ? (Real)1 : onePlusMu[3]) * (Real)4 / (Real)9, + (N != 5 ? (Real)1 : onePlusMu[4]) * (Real)5 / (Real)11, + (N != 6 ? (Real)1 : onePlusMu[5]) * (Real)6 / (Real)13, + (N != 7 ? (Real)1 : onePlusMu[6]) * (Real)7 / (Real)15, + (N != 8 ? (Real)1 : onePlusMu[7]) * (Real)8 / (Real)17, + (N != 9 ? (Real)1 : onePlusMu[8]) * (Real)9 / (Real)19, + (N != 10 ? (Real)1 : onePlusMu[9]) * (Real)10 / (Real)21, + (N != 11 ? (Real)1 : onePlusMu[10]) * (Real)11 / (Real)23, + (N != 12 ? (Real)1 : onePlusMu[11]) * (Real)12 / (Real)25, + (N != 13 ? (Real)1 : onePlusMu[12]) * (Real)13 / (Real)27, + (N != 14 ? (Real)1 : onePlusMu[13]) * (Real)14 / (Real)29, + (N != 15 ? (Real)1 : onePlusMu[14]) * (Real)15 / (Real)31, + (N != 16 ? (Real)1 : onePlusMu[15]) * (Real)16 / (Real)33 + }; + + Real term0 = (Real)1 - t, term1 = t; + Real sqr0 = term0 * term0, sqr1 = term1 * term1; + f0 = term0; + f1 = term1; + for (int i = 0; i < N; ++i) + { + term0 *= (b[i] - a[i] * sqr0) * y; + term1 *= (b[i] - a[i] * sqr1) * y; + f0 += term0; + f1 += term1; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCholeskyDecomposition.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCholeskyDecomposition.h new file mode 100644 index 000000000000..64be7cd44d86 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCholeskyDecomposition.h @@ -0,0 +1,560 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.14.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + // Implementation for size known at compile time. + template + class CholeskyDecomposition + { + public: + // Ensure that N > 0 at compile time. + CholeskyDecomposition() + { + static_assert(N > 0, "Invalid size in CholeskyDecomposition constructor."); + } + + // Disallow copies and moves. + CholeskyDecomposition(CholeskyDecomposition const&) = delete; + CholeskyDecomposition& operator=(CholeskyDecomposition const&) = delete; + CholeskyDecomposition(CholeskyDecomposition&&) = delete; + CholeskyDecomposition& operator=(CholeskyDecomposition&&) = delete; + + // On input, A is symmetric. Only the lower-triangular portion is + // modified. On output, the lower-triangular portion is L where + // A = L * L^T. + bool Factor(Matrix& A) + { + for (int c = 0; c < N; ++c) + { + if (A(c, c) <= (Real)0) + { + return false; + } + A(c, c) = std::sqrt(A(c, c)); + + for (int r = c + 1; r < N; ++r) + { + A(r, c) /= A(c, c); + } + + for (int k = c + 1; k < N; ++k) + { + for (int r = k; r < N; ++r) + { + A(r, k) -= A(r, c) * A(k, c); + } + } + } + return true; + } + + // Solve L*Y = B, where L is lower triangular and invertible. The + // input value of Y is B. On output, Y is the solution. + void SolveLower(Matrix const& L, Vector& Y) + { + for (int r = 0; r < N; ++r) + { + for (int c = 0; c < r; ++c) + { + Y[r] -= L(r, c) * Y[c]; + } + Y[r] /= L(r, r); + } + } + + // Solve L^T*X = Y, where L is lower triangular (L^T is upper + // triangular) and invertible. The input value of X is Y. On + // output, X is the solution. + void SolveUpper(Matrix const& L, Vector& X) + { + for (int r = N - 1; r >= 0; --r) + { + for (int c = r + 1; c < N; ++c) + { + X[r] -= L(c, r) * X[c]; + } + X[r] /= L(r, r); + } + } + }; + + // Implementation for size known only at run time. + template + class CholeskyDecomposition + { + public: + int const N; + + // Ensure that N > 0 at run time. + CholeskyDecomposition(int n) + : + N(n) + { + } + + // Disallow copies and moves. This is required to avoid compiler + // complaints about the 'int const N' member. + CholeskyDecomposition(CholeskyDecomposition const&) = delete; + CholeskyDecomposition& operator=(CholeskyDecomposition const&) = delete; + CholeskyDecomposition(CholeskyDecomposition&&) = delete; + CholeskyDecomposition& operator=(CholeskyDecomposition&&) = delete; + + // On input, A is symmetric. Only the lower-triangular portion is + // modified. On output, the lower-triangular portion is L where + // A = L * L^T. + bool Factor(GMatrix& A) + { + if (A.GetNumRows() == N && A.GetNumCols() == N) + { + for (int c = 0; c < N; ++c) + { + if (A(c, c) <= (Real)0) + { + return false; + } + A(c, c) = std::sqrt(A(c, c)); + + for (int r = c + 1; r < N; ++r) + { + A(r, c) /= A(c, c); + } + + for (int k = c + 1; k < N; ++k) + { + for (int r = k; r < N; ++r) + { + A(r, k) -= A(r, c) * A(k, c); + } + } + } + return true; + } + else + { + LogError("Matrix must be square in CholeskyDecomposition::Factor."); + return false; + } + } + + // Solve L*Y = B, where L is lower triangular and invertible. The + // input value of Y is B. On output, Y is the solution. + void SolveLower(GMatrix const& L, GVector& Y) + { + if (L.GetNumRows() == N && L.GetNumCols() == N && Y.GetSize() == N) + { + for (int r = 0; r < N; ++r) + { + for (int c = 0; c < r; ++c) + { + Y[r] -= L(r, c) * Y[c]; + } + Y[r] /= L(r, r); + } + } + else + { + LogError("Invalid size in CholeskyDecomposition::SolveLower."); + Y.MakeZero(); + } + } + + // Solve L^T*X = Y, where L is lower triangular (L^T is upper + // triangular) and invertible. The input value of X is Y. On + // output, X is the solution. + void SolveUpper(GMatrix const& L, GVector& X) + { + if (L.GetNumRows() == N && L.GetNumCols() == N && X.GetSize() == N) + { + for (int r = N - 1; r >= 0; --r) + { + for (int c = r + 1; c < N; ++c) + { + X[r] -= L(c, r) * X[c]; + } + X[r] /= L(r, r); + } + } + else + { + LogError("Invalid size in CholeskyDecomposition::SolveLower."); + X.MakeZero(); + } + } + }; + + + // Implementation for sizes known at compile time. + template + class BlockCholeskyDecomposition + { + public: + // Let B represent the block size and N represent the number of + // blocks. The matrix A is (N*B)-by-(N*B) but partitioned into an + // N-by-N matrix of blocks, each block of size B-by-B. The value + // N*B is NumDimensions. + enum + { + NumDimensions = NumBlocks * BlockSize + }; + + typedef std::array, NumBlocks> BlockVector; + typedef std::array, NumBlocks>, NumBlocks> BlockMatrix; + + // Ensure that BlockSize > 0 and NumBlocks > 0 at compile time. + BlockCholeskyDecomposition() + { + static_assert(BlockSize > 0 && NumBlocks > 0, "Invalid size in BlockCholeskyDecomposition constructor."); + } + + // Disallow copies and moves. + BlockCholeskyDecomposition(BlockCholeskyDecomposition const&) = delete; + BlockCholeskyDecomposition& operator=(BlockCholeskyDecomposition const&) = delete; + BlockCholeskyDecomposition(BlockCholeskyDecomposition&&) = delete; + BlockCholeskyDecomposition& operator=(BlockCholeskyDecomposition&&) = delete; + + // Treating the matrix as a 2D table of scalars with NUM_DIMENSIONS + // rows and NUM_DIMENSIONS columns, look up the correct block that + // stores the requested element and return a reference. + Real Get(BlockMatrix const& M, int row, int col) + { + int b0 = col / BlockSize, b1 = row / BlockSize; + int i0 = col % BlockSize, i1 = row % BlockSize; + auto const& block = M[b1][b0]; + return block(i1, i0); + } + + void Set(BlockMatrix& M, int row, int col, Real value) + { + int b0 = col / BlockSize, b1 = row / BlockSize; + int i0 = col % BlockSize, i1 = row % BlockSize; + auto& block = M[b1][b0]; + block(i1, i0) = value; + } + + bool Factor(BlockMatrix& A) + { + for (int c = 0; c < NumBlocks; ++c) + { + if (!mDecomposer.Factor(A[c][c])) + { + return false; + } + + for (int r = c + 1; r < NumBlocks; ++r) + { + LowerTriangularSolver(r, c, A); + } + + for (int k = c + 1; k < NumBlocks; ++k) + { + for (int r = k; r < NumBlocks; ++r) + { + SubtractiveUpdate(r, k, c, A); + } + } + } + return true; + } + + // Solve L*Y = B, where L is an invertible lower-triangular block + // matrix whose diagonal blocks are lower-triangular matrices. + // The input B is a block vector of commensurate size. The input + // value of Y is B. On output, Y is the solution. + void SolveLower(BlockMatrix const& L, BlockVector& Y) + { + for (int r = 0; r < NumBlocks; ++r) + { + auto& Yr = Y[r]; + for (int c = 0; c < r; ++c) + { + auto const& Lrc = L[r][c]; + auto const& Yc = Y[c]; + for (int i = 0; i < BlockSize; ++i) + { + for (int j = 0; j < BlockSize; ++j) + { + Yr[i] -= Lrc(i, j) * Yc[j]; + } + } + } + mDecomposer.SolveLower(L[r][r], Yr); + } + } + + // Solve L^T*X = Y, where L is an invertible lower-triangular block + // matrix (L^T is an upper-triangular block matrix) whose diagonal + // blocks are lower-triangular matrices. The input value of X is Y. + // On output, X is the solution. + void SolveUpper(BlockMatrix const& L, BlockVector& X) + { + for (int r = NumBlocks - 1; r >= 0; --r) + { + auto& Xr = X[r]; + for (int c = r + 1; c < NumBlocks; ++c) + { + auto const& Lcr = L[c][r]; + auto const& Xc = X[c]; + for (int i = 0; i < BlockSize; ++i) + { + for (int j = 0; j < BlockSize; ++j) + { + Xr[i] -= Lcr(j, i) * Xc[j]; + } + } + } + mDecomposer.SolveUpper(L[r][r], Xr); + } + } + + private: + // Solve G(c,c)*G(r,c)^T = A(r,c)^T for G(r,c). The matrices + // G(c,c) and A(r,c) are known quantities, and G(c,c) occupies + // the lower triangular portion of A(c,c). The solver stores + // its results in-place, so A(r,c) stores the G(r,c) result. + void LowerTriangularSolver(int r, int c, BlockMatrix& A) + { + auto const& Acc = A[c][c]; + auto& Arc = A[r][c]; + for (int j = 0; j < BlockSize; ++j) + { + for (int i = 0; i < j; ++i) + { + Real Lji = Acc(j, i); + for (int k = 0; k < BlockSize; ++k) + { + Arc(k, j) -= Lji * Arc(k, i); + } + } + + Real Ljj = Acc(j, j); + for (int k = 0; k < BlockSize; ++k) + { + Arc(k, j) /= Ljj; + } + } + } + + void SubtractiveUpdate(int r, int k, int c, BlockMatrix& A) + { + auto const& Arc = A[r][c]; + auto const& Akc = A[k][c]; + auto& Ark = A[r][k]; + for (int j = 0; j < BlockSize; ++j) + { + for (int i = 0; i < BlockSize; ++i) + { + for (int m = 0; m < BlockSize; ++m) + { + Ark(j, i) -= Arc(j, m) * Akc(i, m); + } + } + } + } + + CholeskyDecomposition mDecomposer; + }; + + // Implementation for sizes known only at run time. + template + class BlockCholeskyDecomposition + { + public: + // Let B represent the block size and N represent the number of + // blocks. The matrix A is (N*B)-by-(N*B) but partitioned into an + // N-by-N matrix of blocks, each block of size B-by-B. The value + // N*B is NumDimensions. + int const BlockSize; + int const NumBlocks; + int const NumDimensions; + + // The number of elements in a BlockVector object must be NumBlocks + // and each GVector element has BlockSize components. + typedef std::vector> BlockVector; + + // The BlockMatrix is an array of NumBlocks-by-NumBlocks matrices. + // Each block matrix is stored in row-major order. The BlockMatrix + // elements themselves are stored in row-major order. The block + // matrix element M = BlockMatrix[col + NumBlocks * row] is of size + // BlockSize-by-BlockSize (in row-major order) and is in the (row,col) + // location of the full matrix of blocks. + typedef std::vector> BlockMatrix; + + // Ensure that BlockSize > 0 and NumDimensions > 0 at run time. + BlockCholeskyDecomposition(int blockSize, int numBlocks) + : + BlockSize(blockSize), + NumBlocks(numBlocks), + NumDimensions(numBlocks * blockSize), + mDecomposer(blockSize) + { + LogAssert(blockSize > 0 && numBlocks > 0, "Invalid size in BlockCholeskyDecomposition constructor."); + } + + // Disallow copies and moves. This is required to avoid compiler + // complaints about the 'int const' members. + BlockCholeskyDecomposition(BlockCholeskyDecomposition const&) = delete; + BlockCholeskyDecomposition& operator=(BlockCholeskyDecomposition const&) = delete; + BlockCholeskyDecomposition(BlockCholeskyDecomposition&&) = delete; + BlockCholeskyDecomposition& operator=(BlockCholeskyDecomposition&&) = delete; + + // Treating the matrix as a 2D table of scalars with NumDimensions + // rows and NumDimensions columns, look up the correct block that + // stores the requested element and return a reference. + Real Get(BlockMatrix const& M, int row, int col) + { + int b0 = col / BlockSize, b1 = row / BlockSize; + int i0 = col % BlockSize, i1 = row % BlockSize; + auto const& block = M[GetIndex(b1, b0)]; + return block(i1, i0); + } + + void Set(BlockMatrix& M, int row, int col, Real value) + { + int b0 = col / BlockSize, b1 = row / BlockSize; + int i0 = col % BlockSize, i1 = row % BlockSize; + auto& block = M[GetIndex(b1, b0)]; + block(i1, i0) = value; + } + + bool Factor(BlockMatrix& A) + { + for (int c = 0; c < NumBlocks; ++c) + { + if (!mDecomposer.Factor(A[GetIndex(c, c)])) + { + return false; + } + + for (int r = c + 1; r < NumBlocks; ++r) + { + LowerTriangularSolver(r, c, A); + } + + for (int k = c + 1; k < NumBlocks; ++k) + { + for (int r = k; r < NumBlocks; ++r) + { + SubtractiveUpdate(r, k, c, A); + } + } + } + return true; + } + + // Solve L*Y = B, where L is an invertible lower-triangular block + // matrix whose diagonal blocks are lower-triangular matrices. + // The input B is a block vector of commensurate size. The input + // value of Y is B. On output, Y is the solution. + void SolveLower(BlockMatrix const& L, BlockVector& Y) + { + for (int r = 0; r < NumBlocks; ++r) + { + auto& Yr = Y[r]; + for (int c = 0; c < r; ++c) + { + auto const& Lrc = L[GetIndex(r, c)]; + auto const& Yc = Y[c]; + for (int i = 0; i < NumBlocks; ++i) + { + for (int j = 0; j < NumBlocks; ++j) + { + Yr[i] -= Lrc[GetIndex(i, j)] * Yc[j]; + } + } + } + mDecomposer.SolveLower(L[GetIndex(r, r)], Yr); + } + } + + // Solve L^T*X = Y, where L is an invertible lower-triangular block + // matrix (L^T is an upper-triangular block matrix) whose diagonal + // blocks are lower-triangular matrices. The input value of X is Y. + // On output, X is the solution. + void SolveUpper(BlockMatrix const& L, BlockVector& X) + { + for (int r = NumBlocks - 1; r >= 0; --r) + { + auto& Xr = X[r]; + for (int c = r + 1; c < NumBlocks; ++c) + { + auto const& Lcr = L[GetIndex(c, r)]; + auto const& Xc = X[c]; + for (int i = 0; i < BlockSize; ++i) + { + for (int j = 0; j < BlockSize; ++j) + { + Xr[i] -= Lcr[GetIndex(j, i)] * Xc[j]; + } + } + } + mDecomposer.SolveUpper(L[GetIndex(r, r)], Xr); + } + } + + private: + // Compute the 1-dimensional index of the block matrix in a + // 2-dimensional BlockMatrix object. + inline int GetIndex(int row, int col) const + { + return col + row * NumBlocks; + } + + // Solve G(c,c)*G(r,c)^T = A(r,c)^T for G(r,c). The matrices + // G(c,c) and A(r,c) are known quantities, and G(c,c) occupies + // the lower triangular portion of A(c,c). The solver stores + // its results in-place, so A(r,c) stores the G(r,c) result. + void LowerTriangularSolver(int r, int c, BlockMatrix& A) + { + auto const& Acc = A[GetIndex(c, c)]; + auto& Arc = A[GetIndex(r, c)]; + for (int j = 0; j < BlockSize; ++j) + { + for (int i = 0; i < j; ++i) + { + Real Lji = Acc[GetIndex(j, i)]; + for (int k = 0; k < BlockSize; ++k) + { + Arc[GetIndex(k, j)] -= Lji * Arc[GetIndex(k, i)]; + } + } + + Real Ljj = Acc[GetIndex(j, j)]; + for (int k = 0; k < BlockSize; ++k) + { + Arc[GetIndex(k, j)] /= Ljj; + } + } + } + + void SubtractiveUpdate(int r, int k, int c, BlockMatrix& A) + { + auto const& Arc = A[GetIndex(r, c)]; + auto const& Akc = A[GetIndex(k, c)]; + auto& Ark = A[GetIndex(r, k)]; + for (int j = 0; j < BlockSize; ++j) + { + for (int i = 0; i < BlockSize; ++i) + { + for (int m = 0; m < BlockSize; ++m) + { + Ark[GetIndex(j, i)] -= Arc[GetIndex(j, m)] * Akc[GetIndex(i, m)]; + } + } + } + } + + // The decomposer has size BlockSize. + CholeskyDecomposition mDecomposer; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCircle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCircle3.h new file mode 100644 index 000000000000..0e13b200f15d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCircle3.h @@ -0,0 +1,123 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The circle is the intersection of the sphere |X-C|^2 = r^2 and the +// plane Dot(N,X-C) = 0, where C is the circle center, r is the radius, +// and N is a unit-length plane normal. + +namespace gte +{ + +template +class Circle3 +{ +public: + + // Construction and destruction. The default constructor sets center to + // (0,0,0), normal to (0,0,1), and radius to 1. + Circle3(); + Circle3(Vector3 const& inCenter, Vector3 const& inNormal, + Real inRadius); + + // Public member access. + Vector3 center, normal; + Real radius; + +public: + // Comparisons to support sorted containers. + bool operator==(Circle3 const& circle) const; + bool operator!=(Circle3 const& circle) const; + bool operator< (Circle3 const& circle) const; + bool operator<=(Circle3 const& circle) const; + bool operator> (Circle3 const& circle) const; + bool operator>=(Circle3 const& circle) const; +}; + + +template +Circle3::Circle3() + : + center(Vector3::Zero()), + normal(Vector3::Unit(2)), + radius((Real)1) +{ +} + +template +Circle3::Circle3(Vector3 const& inCenter, + Vector3 const& inNormal, Real inRadius) + : + center(inCenter), + normal(inNormal), + radius(inRadius) +{ +} + +template +bool Circle3::operator==(Circle3 const& circle) const +{ + return center == circle.center + && normal == circle.normal + && radius == circle.radius; +} + +template +bool Circle3::operator!=(Circle3 const& circle) const +{ + return !operator==(circle); +} + +template +bool Circle3::operator<(Circle3 const& circle) const +{ + if (center < circle.center) + { + return true; + } + + if (center > circle.center) + { + return false; + } + + if (normal < circle.normal) + { + return true; + } + + if (normal > circle.normal) + { + return false; + } + + return radius < circle.radius; +} + +template +bool Circle3::operator<=(Circle3 const& circle) const +{ + return operator<(circle) || operator==(circle); +} + +template +bool Circle3::operator>(Circle3 const& circle) const +{ + return !operator<=(circle); +} + +template +bool Circle3::operator>=(Circle3 const& circle) const +{ + return !operator<(circle); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCone.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCone.h new file mode 100644 index 000000000000..cc30d5331c40 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCone.h @@ -0,0 +1,175 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2019/02/15) + +#pragma once + +#include +#include +#include + +// An acute cone is Dot(A,X-V) = |X-V| cos(t) where V is the vertex, A is the +// unit-length direction of the axis of the cone, and T is the cone angle with +// 0 < t < pi/2. The cone interior is defined by the inequality +// Dot(A,X-V) >= |X-V| cos(t). Since cos(t) > 0, we can avoid computing +// square roots. The solid cone is defined by the inequality +// Dot(A,X-V)^2 >= Dot(X-V,X-V) cos(t)^2. I refer to this object as an +// "infinite cone." Cone axis points are V + h * A, where h is referred to +// as height and 0 <= h < +infinity. +// +// The cone can be truncated by a plane perpendicular to its axis at a height +// hmax with 0 < hmax < +infinity. I refer to this object as a "finite cone." +// The finite cone is capped by has a circular disk opposite the vertex; the +// disk has radius hmax*tan(t). +// +// The finite cone can be additionally truncated at a height hmin with +// 0 < hmin < hmax < +infinity. I refer to this a a "cone frustum." + +namespace gte +{ + template + class Cone + { + public: + // The default constructor creates an infinite cone with + // vertex = (0,...,0) + // axis = (0,...,0,1) + // angle = pi/4 + // minimum height = 0 + // maximum height = std::numeric_limits::max() + Cone() + : + minHeight((Real)0), + maxHeight(std::numeric_limits::max()) + { + ray.origin.MakeZero(); + ray.direction.MakeUnit(N - 1); + SetAngle((Real)GTE_C_QUARTER_PI); + } + + // This constructor creates an infinite cone with the specified + // vertex, axis direction and angle, and with heights + // minimum height = 0 + // maximum height = std::numeric_limits::max() + Cone(Ray const& inRay, Real inAngle) + : + ray(inRay), + minHeight((Real)0), + maxHeight(std::numeric_limits::max()) + { + SetAngle(inAngle); + } + + // This constructor creates a cone with all parameters specified. + Cone(Ray const& inRay, Real inAngle, Real inMinHeight, Real inMaxHeight) + : + ray(inRay), + minHeight(inMinHeight), + maxHeight(inMaxHeight) + { + LogAssert((Real)0 <= minHeight && minHeight < maxHeight, "Invalid height interval."); + SetAngle(inAngle); + } + + // The angle must be in (0,pi/2). The function sets 'angle' and + // computes 'cosAngle', 'sinAngle', 'tanAngle', 'cosAngleSqr', + // 'sinAngleSqr' and 'invSinAngle'. + void SetAngle(Real inAngle) + { + LogAssert((Real)0 < inAngle && inAngle < (Real)GTE_C_HALF_PI, "Invalid angle."); + angle = inAngle; + cosAngle = std::cos(angle); + sinAngle = std::sin(angle); + tanAngle = std::tan(angle); + cosAngleSqr = cosAngle * cosAngle; + sinAngleSqr = sinAngle * sinAngle; + invSinAngle = (Real)1 / sinAngle; + } + + // The cone axis direction must be unit length. The angle must + // be in (0,pi/2). The heights must satisfy + // 0 <= minHeight < maxHeight <= std::numeric_limits::max(). + Ray ray; + Real angle; + Real minHeight, maxHeight; + + // Members derived from 'angle', to avoid calling trigonometric + // functions in geometric queries (for speed). You may set 'angle' + // and compute these by calling SetAngle(inAngle). + Real cosAngle, sinAngle, tanAngle; + Real cosAngleSqr, sinAngleSqr, invSinAngle; + + public: + // Comparisons to support sorted containers. These based only on + // 'ray', 'angle', 'minHeight' and 'maxHeight'. + bool operator==(Cone const& cone) const + { + return ray == cone.ray + && angle == cone.angle + && minHeight == cone.minHeight + && maxHeight == cone.maxHeight; + } + + bool operator!=(Cone const& cone) const + { + return !operator==(cone); + } + + bool operator< (Cone const& cone) const + { + if (ray < cone.ray) + { + return true; + } + + if (ray > cone.ray) + { + return false; + } + + if (angle < cone.angle) + { + return true; + } + + if (angle > cone.angle) + { + return false; + } + + if (minHeight < cone.minHeight) + { + return true; + } + + if (minHeight > cone.minHeight) + { + return false; + } + + return maxHeight < cone.maxHeight; + } + + bool operator<=(Cone const& cone) const + { + return !cone.operator<(*this); + } + + bool operator> (Cone const& cone) const + { + return cone.operator<(*this); + } + + bool operator>=(Cone const& cone) const + { + return !operator<(cone); + } + }; + + // Template alias for convenience. + template + using Cone3 = Cone<3, Real>; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConstrainedDelaunay2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConstrainedDelaunay2.h new file mode 100644 index 000000000000..c33fd5312b3b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConstrainedDelaunay2.h @@ -0,0 +1,790 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// Various parts of the code have this trap for error conditions. With a +// correct algorithm and exact arithmetic, we do not expect to trigger the +// error. However, with floating-point arithmetic, it is possible that the +// triangulation becomes malformed. The constrained Delaunay triangulation +// implementation is designed to return gracefully with such a failure. The +// following macros are used to make the code more readable. Do NOT disable +// them, because they have necessary side effects. +#define GTE_CDT_REQUIRE(c) { if (!(c)) { Trap(); return false; } } +#define GTE_CDT_FAILURE { Trap(); return false; } +#define GTE_CDT_REQUIRE_RET(c, r) { if (!(c)) { Trap(); return r; } } +#define GTE_CDT_FAILURE_RET(r) { Trap(); return r; } + +namespace gte +{ + +template +class ConstrainedDelaunay2 : public Delaunay2 +{ +public: + // The class is a functor to support computing the constrained Delaunay + // triangulation of multiple data sets using the same class object. + virtual ~ConstrainedDelaunay2(); + ConstrainedDelaunay2(); + + // This operator computes the Delaunay triangulation only. Read the + // Delaunay2 constructor comments about 'vertices' and 'epsilon'. The + // 'edges' array has indices into the 'vertices' array. No two edges + // should intersect except at endpoints. + bool operator()(int numVertices, Vector2 const* vertices, + InputType epsilon); + + // Insert required edges into the triangulation. For correctness of the + // algorithm, if two edges passed to this function intersect, they must + // do so only at vertices passed to operator(). If you have two edges + // that intersect at a point not in the vertices, compute that point of + // intersection and subdivide the edges at that intersection (to form + // more edges), and add the point to the vertices before calling + // operator(). This function has an output array that contains the + // input edge when the only vertices on the edge are its endpoints. If + // the input edge passes through more vertices, the edge is subdivided + // in this function. The output edge is that subdivision with first + // vertex edge[0] and last vertex edge[1], and the other vertices are + // correctly ordered along the edge. + bool Insert(std::array const& edge, std::vector& outEdge); + +private: + // The top-level entry point for inserting an edge in the triangulation. + bool Insert(std::array const& edge, int v0Triangle, + std::vector& outEdge); + + // Process the coincident edge. + bool ProcessCoincident(int tri, int v0, int v1, int vOther, + std::vector& outEdge); + + // Process the triangle strip originating at the first endpoint of the + // edge. + bool ProcessInterior(int tri, int v0, int v1, int vNext, int vPrev, + std::vector& outEdge); + + // Remove the triangles in the triangle strip and retriangulate the + // left and right polygons using the empty circumcircle condition. + bool Retriangulate(std::vector& polygon, + std::vector> const& lBoundary, + std::vector> const& rBoundary); + + int RetriangulateLRecurse( + std::vector> const& lBoundary, + int i0, int i1, int a0, std::vector& polygon); + + int RetriangulateRRecurse( + std::vector> const& rBoundary, + int i0, int i1, int a0, std::vector& polygon); + + int SelectSplit(std::vector> const& boundary, int i0, + int i1) const; + + // Compute a pseudosquared distance from the vertex at v2 to the edge + // . + ComputeType ComputePSD(int v0, int v1, int v2) const; + + // Search the triangulation for a triangle that contains the specified + // vertex. + int GetLinkTriangle(int v) const; + + // Determine the index in {0,1,2} for the triangle 'tri' that contains + // the vertex 'v'. + int GetIndexOfVertex(int tri, int v) const; + + // Given a triangle 'tri' with CCW-edge , return where + // 'adj' is the index of the triangle adjacent to 'tri' that shares the + // edge and 'v2' is the vertex of the adjacent triangle opposite the + // edge. This function supports traversing a triangle strip that contains + // a constraint edge, so it is called only when an adjacent triangle + // actually exists. + std::array GetAdjInterior(int tri, int v0, int v1) const; + + // Given a triangle 'tri' of the triangle strip, the boundary edge must + // contain the vertex with index 'needBndVertex'. The input + // 'needAdjVIndex' specifies where to look for the index of the triangle + // outside the strip but adjacent to the boundary edge. The return + // value is and is used to connect 'tri' and 'adj' + // across a triangle strip boundary. + std::array GetAdjBoundary(int tri, int needBndVertex, + int needAdjVIndex) const; + + // Set the indices and adjacencies arrays so that 'tri' and 'adj' share + // the common edge; 'tri' has CCW-edge and 'adj' has CCW-edge + // . + bool Connect(int tri, int adj, int v0, int v1); + + // Create an ordered list of triangles forming the link of a vertex. The + // pair of the list is . This allows us + // to cache the index of v relative to each triangle in the link. The + // vertex v might be a boundary vertex, in which case the neighborhood is + // open; otherwise, v is an interior vertex and the neighborhood is + // closed. The 'isOpen' parameter specifies the case. + bool BuildLink(int v, int vTriangle, std::list>& link, + bool& isOpen) const; + + // Support for return-false-on-error. This allows us to investigate the + // error by setting a break point, trigger an assert when the logger is + // active, and get a call stack. + void Trap() const; +}; + + +template +ConstrainedDelaunay2::~ConstrainedDelaunay2() +{ +} + +template +ConstrainedDelaunay2::ConstrainedDelaunay2() + : + Delaunay2() +{ +} + +template +bool ConstrainedDelaunay2::operator()( + int numVertices, Vector2 const* vertices, InputType epsilon) +{ + return Delaunay2::operator()(numVertices, + vertices, epsilon); +} + +template +bool ConstrainedDelaunay2::Insert( + std::array const& edge, std::vector& outEdge) +{ + int v0 = edge[0], v1 = edge[1]; + if (0 <= v0 && v0 < this->mNumVertices + && 0 <= v1 && v1 < this->mNumVertices) + { + int v0Triangle = GetLinkTriangle(v0); + if (v0Triangle >= 0) + { + // Once an edge is inserted, the base-class mGraph no longer + // represents the triangulation. Clear it in case the user tries + // to access it. + this->mGraph.Clear(); + + outEdge.clear(); + return Insert(edge, v0Triangle, outEdge); + } + } + return false; +} + +template +bool ConstrainedDelaunay2::Insert( + std::array const& edge, int v0Triangle, std::vector& outEdge) +{ + // TODO: this check helps avoid cases where the algorithm gets stuck in endless recursion walking the mesh (resulting in a stack overflow) ... overflow could still happen for a large mesh, so more work to prevent such an endless loop would be valuable! + // TODO: (and/or: rewrite the insert function to not be recursive!) + GTE_CDT_REQUIRE(outEdge.size() <= this->mComputeVertices.size()); + + // Create the neighborhood of triangles that share the vertex v0. On + // entry we already know one such triangle (v0Triangle). + int v0 = edge[0], v1 = edge[1]; + std::list> link; + bool isOpen = true; + GTE_CDT_REQUIRE(BuildLink(v0, v0Triangle, link, isOpen)); + + // Determine which triangle contains the edge. Process the edge according + // to whether it is strictly between two triangle edges or is coincident + // with a triangle edge. + auto item = link.begin(); + std::array indices; + GTE_CDT_REQUIRE(this->GetIndices(item->first, indices)); + int vNext = indices[(item->second + 1) % 3]; + int qr0 = this->mQuery.ToLine(v1, v0, vNext); + while (item != link.end()) + { + if (qr0 == 0) + { + // We have to be careful about parallel edges that point in + // the opposite direction of . + Vector2 const& ctv0 = this->mComputeVertices[v0]; + Vector2 const& ctv1 = this->mComputeVertices[v1]; + Vector2 const& ctvnext = + this->mComputeVertices[vNext]; + if (Dot(ctv1 - ctv0, ctvnext - ctv0) > (ComputeType)0) + { + // is coincident to triangle edge0. + return ProcessCoincident(item->first, v0, v1, vNext, + outEdge); + } + + // Make sure we enter the next "if" statement to continue + // traversing the link. + qr0 = 1; + } + + if (qr0 > 0) + { + // is not in triangle. Visit the next triangle. + if (++item == link.end()) + { + return false; + } + GTE_CDT_REQUIRE(this->GetIndices(item->first, indices)); + vNext = indices[(item->second + 1) % 3]; + qr0 = this->mQuery.ToLine(v1, v0, vNext); + continue; + } + + int vPrev = indices[(item->second + 2) % 3]; + int qr1 = this->mQuery.ToLine(v1, v0, vPrev); + while (item != link.end()) + { + if (qr1 == 0) + { + // We have to be careful about parallel edges that point in + // the opposite direction of . + Vector2 const& ctv0 = this->mComputeVertices[v0]; + Vector2 const& ctv1 = this->mComputeVertices[v1]; + Vector2 const& ctvprev = + this->mComputeVertices[vPrev]; + if (Dot(ctv1 - ctv0, ctvprev - ctv0) > (ComputeType)0) + { + // is coincident to triangle edge1. + return ProcessCoincident(item->first, v0, v1, vPrev, + outEdge); + } + + // Make sure we enter the next "if" statement to continue + // traversing the link. + qr1 = -1; + } + + if (qr1 < 0) + { + // is not in triangle. Visit the next triangle. + if (++item == link.end()) + { + return false; + } + this->GetIndices(item->first, indices); + vNext = vPrev; + vPrev = indices[(item->second + 2) % 3]; + qr1 = this->mQuery.ToLine(v1, v0, vPrev); + continue; + } + + // is interior to triangle . + return ProcessInterior(item->first, v0, v1, vNext, vPrev, + outEdge); + } + break; + } + + // The edge must be contained in some link triangle. + GTE_CDT_FAILURE; +} + +template +bool ConstrainedDelaunay2::ProcessCoincident(int tri, + int v0, int v1, int vOther, std::vector& outEdge) +{ + outEdge.push_back(v0); + if (v1 != vOther) + { + // Decompose the edge and process the right-most subedge. + return Insert({{ vOther, v1 }}, tri, outEdge); + } + else + { + // is already in the triangulation. + outEdge.push_back(v1); + return true; + } +} + +template +bool ConstrainedDelaunay2::ProcessInterior(int tri, + int v0, int v1, int vNext, int vPrev, std::vector& outEdge) +{ + // The triangles of the strip are stored in 'polygon'. The + // retriangulation leads to the same number of triangles, so we can reuse + // the mIndices[] and mAdjacencies[] locations implied by the 'polygons' + // indices. + std::vector polygon; + + // The sBoundary[i] (s in {l,r}) array element is ; see the + // header comments for GetAdjBoundary about what these mean. The + // boundary vertex is 'v0', the adjacent triangle 'adj' is outside the + // strip and shares the edge with a + // triangle in 'polygon'. This information allows us to connect the + // adjacent triangles outside the strip to new triangles inserted by + // the retriangulation. The value sBoundary[0][1,2] values are set to + // -1 but they are not used in the construction. + std::vector> lBoundary, rBoundary; + std::array binfo; + + polygon.push_back(tri); + + lBoundary.push_back({{ v0, -1 }}); + binfo = GetAdjBoundary(tri, vPrev, vPrev); + GTE_CDT_REQUIRE(binfo[0] != -2); + lBoundary.push_back(binfo); + + rBoundary.push_back({{ v0, -1 }}); + binfo = GetAdjBoundary(tri, vNext, v0); + GTE_CDT_REQUIRE(binfo[0] != -2); + rBoundary.push_back(binfo); + + // Visit the triangles in the strip. Guard against an infinite loop. + for (int i = 0; i < this->mNumTriangles; ++i) + { + // Find the vertex of the adjacent triangle that is opposite the + // edge shared with the current triangle. + auto iinfo = GetAdjInterior(tri, vNext, vPrev); + int adj = iinfo[0], vOpposite = iinfo[1]; + GTE_CDT_REQUIRE(vOpposite >= 0); + + // Visit the adjacent triangle and insert it into the polygon. + tri = adj; + polygon.push_back(tri); + + int qr = this->mQuery.ToLine(vOpposite, v0, v1); + if (qr == 0) + { + // We have encountered a vertex that terminates the triangle + // strip. Retriangulate the polygon. If the edge continues + // through vOpposite, decompose the edge and insert the + // right-most subedge. + binfo = GetAdjBoundary(tri, vOpposite, vOpposite); + GTE_CDT_REQUIRE(binfo[0] != -2); + lBoundary.push_back(binfo); + + binfo = GetAdjBoundary(tri, vOpposite, vNext); + GTE_CDT_REQUIRE(binfo[0] != -2); + rBoundary.push_back(binfo); + + Retriangulate(polygon, lBoundary, rBoundary); + if (vOpposite != v1) + { + outEdge.push_back(v0); + return Insert({{ vOpposite, v1 }}, tri, outEdge); + } + else + { + outEdge.push_back(v0); + outEdge.push_back(v1); + return true; + } + } + + if (qr < 0) + { + binfo = GetAdjBoundary(tri, vOpposite, vOpposite); + GTE_CDT_REQUIRE(binfo[0] != -2); + lBoundary.push_back(binfo); + vPrev = vOpposite; + } + else // qr > 0 + { + binfo = GetAdjBoundary(tri, vOpposite, vNext); + GTE_CDT_REQUIRE(binfo[0] != -2); + rBoundary.push_back(binfo); + vNext = vOpposite; + } + } + + // The triangle strip should have been located in the loop. + GTE_CDT_FAILURE; +} + +template +bool ConstrainedDelaunay2::Retriangulate( + std::vector& polygon, + std::vector> const& lBoundary, + std::vector> const& rBoundary) +{ + int t0 = RetriangulateLRecurse(lBoundary, 0, + static_cast(lBoundary.size()) - 1, -1, polygon); + + int t1 = RetriangulateRRecurse(rBoundary, 0, + static_cast(rBoundary.size()) - 1, -1, polygon); + + int v0 = lBoundary.front()[0]; + int v1 = lBoundary.back()[0]; + GTE_CDT_REQUIRE(Connect(t0, t1, v0, v1)); + return true; +} + +template +int ConstrainedDelaunay2::RetriangulateLRecurse( + std::vector> const& lBoundary, int i0, int i1, int a0, + std::vector& polygon) +{ + // Create triangles when recursing down, connect adjacent triangles when + // returning. + + int v0 = lBoundary[i0][0]; + int v1 = lBoundary[i1][0]; + + if (i1 - i0 == 1) + { + GTE_CDT_REQUIRE_RET(Connect(a0, lBoundary[i1][1], v1, v0), -2); + return -1; // No triangle created. + } + else + { + // Select i2 in [i0+1,i1-1] for minimum distance to edge . + int i2 = SelectSplit(lBoundary, i0, i1); + int v2 = lBoundary[i2][0]; + + // Reuse a triangle and fill in its new vertices. + int tri = polygon.back(); + polygon.pop_back(); + this->mIndices[3 * tri + 0] = v0; + this->mIndices[3 * tri + 1] = v1; + this->mIndices[3 * tri + 2] = v2; + + // Recurse downward and create triangles. + int ret0 = RetriangulateLRecurse(lBoundary, i0, i2, tri, polygon); + GTE_CDT_REQUIRE_RET(ret0 >= -1, -2); + int ret1 = RetriangulateLRecurse(lBoundary, i2, i1, tri, polygon); + GTE_CDT_REQUIRE_RET(ret1 >= -1, -2); + + // Return and connect triangles. + GTE_CDT_REQUIRE_RET(Connect(a0, tri, v1, v0), -2); + return tri; + } +} + +template +int ConstrainedDelaunay2::RetriangulateRRecurse( + std::vector> const& rBoundary, int i0, int i1, int a0, + std::vector& polygon) +{ + // Create triangles when recursing down, connect adjacent triangles when + // returning. + + int v0 = rBoundary[i0][0]; + int v1 = rBoundary[i1][0]; + + if (i1 - i0 == 1) + { + GTE_CDT_REQUIRE_RET(Connect(a0, rBoundary[i1][1], v0, v1), -2); + return -1; // No triangle created. + } + else + { + // Select i2 in [i0+1,i1-1] for minimum distance to edge . + int i2 = SelectSplit(rBoundary, i0, i1); + int v2 = rBoundary[i2][0]; + + // Reuse a triangle and fill in its new vertices. + int tri = polygon.back(); + polygon.pop_back(); + this->mIndices[3 * tri + 0] = v1; + this->mIndices[3 * tri + 1] = v0; + this->mIndices[3 * tri + 2] = v2; + + // Recurse downward and create triangles. + int ret0 = RetriangulateRRecurse(rBoundary, i0, i2, tri, polygon); + GTE_CDT_REQUIRE_RET(ret0 >= -1, -2); + int ret1 = RetriangulateRRecurse(rBoundary, i2, i1, tri, polygon); + GTE_CDT_REQUIRE_RET(ret1 >= -1, -2); + + // Return and connect triangles. + GTE_CDT_REQUIRE_RET(Connect(a0, tri, v0, v1), -2); + return tri; + } +} + +template +int ConstrainedDelaunay2::SelectSplit( + std::vector> const& boundary, int i0, int i1) const +{ + int i2; + if (i1 - i0 == 2) + { + // This is the only candidate. + i2 = i0 + 1; + } + else // i1 - i0 > 2 + { + // Select the index i2 in [i0+1,i1-1] for which the distance from the + // vertex v2 at i2 to the edge is minimized. To allow exact + // arithmetic, use a pseudosquared distance that avoids divisions and + // square roots. + i2 = i0 + 1; + int v0 = boundary[i0][0]; + int v1 = boundary[i1][0]; + int v2 = boundary[i2][0]; + ComputeType minpsd = ComputePSD(v0, v1, v2); + for (int i = i2 + 1; i < i1; ++i) + { + v2 = boundary[i][0]; + ComputeType psd = ComputePSD(v0, v1, v2); + if (psd < minpsd) + { + minpsd = psd; + i2 = i; + } + } + } + return i2; +} + +template +ComputeType ConstrainedDelaunay2::ComputePSD(int v0, + int v1, int v2) const +{ + ComputeType psd; + + Vector2 const& ctv0 = this->mComputeVertices[v0]; + Vector2 const& ctv1 = this->mComputeVertices[v1]; + Vector2 const& ctv2 = this->mComputeVertices[v2]; + + Vector2 V1mV0 = ctv1 - ctv0; + Vector2 V2mV0 = ctv2 - ctv0; + ComputeType sqrlen10 = Dot(V1mV0, V1mV0); + ComputeType dot = Dot(V1mV0, V2mV0); + ComputeType zero(0); + + if (dot <= zero) + { + ComputeType sqrlen20 = Dot(V2mV0, V2mV0); + psd = sqrlen10 * sqrlen20; + } + else + { + Vector2 V2mV1 = ctv2 - ctv1; + dot = Dot(V1mV0, V2mV1); + if (dot >= zero) + { + ComputeType sqrlen21 = Dot(V2mV1, V2mV1); + psd = sqrlen10 * sqrlen21; + } + else + { + dot = DotPerp(V2mV0, V1mV0); + psd = dot * dot; + } + } + + return psd; +} + +template +int ConstrainedDelaunay2::GetLinkTriangle(int v) const +{ + // Remap in case an edge vertex was specified that is a duplicate. + v = this->mDuplicates[v]; + + // TODO: this algorithm does not work for point location in a CDT; it's only guaranteed for a (non-C)DT. + // See https://hal.inria.fr/inria-00072509/document for a report on different approaches to walking a triangulation, + // including an example of where this approach (visibility walk) fails. + // For now, I've added a brute-force fallback below. It would be better to choose a correct algorithm to put here though! + int tri = 0; + for (int i = 0; i < this->mNumTriangles; ++i) + { + // Test whether v is a vertex of the triangle. + std::array indices; + GTE_CDT_REQUIRE(this->GetIndices(tri, indices)); + for (int j = 0; j < 3; ++j) + { + if (v == indices[j]) + { + return tri; + } + } + + // v must be outside the triangle. + for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++) + { + if (this->mQuery.ToLine(v, indices[j0], indices[j1]) > 0) + { + // Vertex v sees the edge from outside, so traverse to the + // triangle sharing the edge. + std::array adjacencies; + GTE_CDT_REQUIRE(this->GetAdjacencies(tri, adjacencies)); + int adj = adjacencies[j0]; + GTE_CDT_REQUIRE_RET(adj >= 0, -1); + tri = adj; + break; + } + } + } + + // fallback to brute force search + // TODO: rm this after fixing the above point location to something that works for all triangulations + for (tri = 0; tri < this->mNumTriangles; ++tri) + { + // Test whether v is a vertex of the triangle. + std::array indices; + GTE_CDT_REQUIRE(this->GetIndices(tri, indices)); + for (int j = 0; j < 3; ++j) + { + if (v == indices[j]) + { + return tri; + } + } + } + + // The vertex must be in the triangulation. + GTE_CDT_FAILURE_RET(-1); +} + +template +int ConstrainedDelaunay2::GetIndexOfVertex(int tri, + int v) const +{ + std::array indices; + GTE_CDT_REQUIRE_RET(this->GetIndices(tri, indices), -1); + int indexOfV; + for (indexOfV = 0; indexOfV < 3; ++indexOfV) + { + if (v == indices[indexOfV]) + { + return indexOfV; + } + } + + // Unexpected failure. + GTE_CDT_FAILURE_RET(-1); +} + +template +std::array ConstrainedDelaunay2:: +GetAdjInterior(int tri, int v0, int v1) const +{ + std::array error = {{ -2, -2 }}; + int vIndex = GetIndexOfVertex(tri, v0); + GTE_CDT_REQUIRE_RET(vIndex >= 0, error); + int adj = this->mAdjacencies[3 * tri + vIndex]; + if (adj >= 0) + { + for (int v2Index = 0; v2Index < 3; ++v2Index) + { + int v2 = this->mIndices[3 * adj + v2Index]; + if (v2 != v0 && v2 != v1) + { + return {{ adj, v2 }}; + } + } + } + else + { + return {{ -1, -1 }}; + } + + GTE_CDT_FAILURE_RET(error); +} + +template +std::array ConstrainedDelaunay2:: +GetAdjBoundary(int tri, int needBndVertex, int needAdjVIndex) const +{ + std::array error = {{ -2, -2 }}; + int vIndex = GetIndexOfVertex(tri, needAdjVIndex); + GTE_CDT_REQUIRE_RET(vIndex >= 0, error); + int adj = this->mAdjacencies[3 * tri + vIndex]; + return {{ needBndVertex, adj }}; +} + +template +bool ConstrainedDelaunay2::Connect(int tri, int adj, + int v0, int v1) +{ + if (tri >= 0) + { + int v0Index = GetIndexOfVertex(tri, v0); + GTE_CDT_REQUIRE(v0Index >= 0); + if (adj >= 0) + { + int v1Index = GetIndexOfVertex(adj, v1); + GTE_CDT_REQUIRE(v1Index >= 0); + this->mAdjacencies[3 * adj + v1Index] = tri; + } + + this->mAdjacencies[3 * tri + v0Index] = adj; + } + // else: tri = -1, which occurs in the top-level call to retriangulate + return true; +} + +template +bool ConstrainedDelaunay2::BuildLink(int v, + int vTriangle, std::list>& link, bool& isOpen) const +{ + // The list starts with a known triangle in the link of v. + int vStartIndex = GetIndexOfVertex(vTriangle, v); + GTE_CDT_REQUIRE(vStartIndex >= 0); + link.push_front(std::make_pair(vTriangle, vStartIndex)); + + // Traverse adjacent triangles to the "left" of v. Guard against an + // infinite loop. + int tri = vTriangle, vIndex = vStartIndex; + std::array adjacencies; + for (int i = 0; i < this->mNumTriangles; ++i) + { + GTE_CDT_REQUIRE(this->GetAdjacencies(tri, adjacencies)); + int adjPrev = adjacencies[(vIndex + 2) % 3]; + if (adjPrev >= 0) + { + if (adjPrev != vTriangle) + { + tri = adjPrev; + vIndex = GetIndexOfVertex(tri, v); + GTE_CDT_REQUIRE(vIndex >= 0); + link.push_back(std::make_pair(tri, vIndex)); + } + else + { + // We have reached the starting triangle, so v is an interior + // vertex. + isOpen = false; + return true; + } + } + else + { + // We have reached a triangle with boundary edge, so v is a + // boundary vertex. We mush find more triangles by searching + // to the "right" of v. Guard against an infinite loop. + isOpen = true; + tri = vTriangle; + vIndex = vStartIndex; + for (int j = 0; j < this->mNumTriangles; ++j) + { + this->GetAdjacencies(tri, adjacencies); + int adjNext = adjacencies[vIndex]; + if (adjNext >= 0) + { + tri = adjNext; + vIndex = GetIndexOfVertex(tri, v); + GTE_CDT_REQUIRE(vIndex >= 0); + link.push_front(std::make_pair(tri, vIndex)); + } + else + { + // We have reached the other boundary edge that shares v. + return true; + } + } + break; + } + } + + // Unexpected failure. + GTE_CDT_FAILURE; +} + +template +void ConstrainedDelaunay2::Trap() const +{ + LogError("CDT failure."); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContAlignedBox.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContAlignedBox.h new file mode 100644 index 000000000000..95de78920280 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContAlignedBox.h @@ -0,0 +1,65 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.13.0 (2018/04/15) + +#pragma once + +#include + +namespace gte +{ + +// Compute the minimum size aligned bounding box of the points. The extreme +// values are the minima and maxima of the point coordinates. +template +bool GetContainer(int numPoints, Vector const* points, + AlignedBox& box); + +// Test for containment. +template +bool InContainer(Vector const& point, AlignedBox const& box); + +// Construct an aligned box that contains two other aligned boxes. The +// result is the minimum size box containing the input boxes. +template +bool MergeContainers(AlignedBox const& box0, + AlignedBox const& box1, AlignedBox& merge); + + +template +bool GetContainer(int numPoints, Vector const* points, + AlignedBox& box) +{ + return ComputeExtremes(numPoints, points, box.min, box.max); +} + +template +bool InContainer(Vector const& point, AlignedBox const& box) +{ + for (int i = 0; i < N; ++i) + { + Real value = point[i]; + if (value < box.min[i] || value > box.max[i]) + { + return false; + } + } + return true; +} + +template +bool MergeContainers(AlignedBox const& box0, + AlignedBox const& box1, AlignedBox& merge) +{ + for (int i = 0; i < N; ++i) + { + merge.min[i] = std::min(box0.min[i], box1.min[i]); + merge.max[i] = std::max(box0.max[i], box1.max[i]); + } + return true; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCapsule3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCapsule3.h new file mode 100644 index 000000000000..52c6e7baf120 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCapsule3.h @@ -0,0 +1,290 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +// Compute the axis of the capsule segment using least-squares fitting. The +// radius is the maximum distance from the points to the axis. Hemispherical +// caps are chosen as close together as possible. +template +bool GetContainer(int numPoints, Vector3 const* points, + Capsule3& capsule); + +// Test for containment of a point by a capsule. +template +bool InContainer(Vector3 const& point, Capsule3 const& capsule); + +// Test for containment of a sphere by a capsule. +template +bool InContainer(Sphere3 const& sphere, Capsule3 const& capsule); + +// Test for containment of a capsule by a capsule. +template +bool InContainer(Capsule3 const& testCapsule, + Capsule3 const& capsule); + +// Compute a capsule that contains the input capsules. The returned capsule +// is not necessarily the one of smallest volume that contains the inputs. +template +bool MergeContainers(Capsule3 const& capsule0, + Capsule3 const& capsule1, Capsule3& merge); + + +template +bool GetContainer(int numPoints, Vector3 const* points, + Capsule3& capsule) +{ + ApprOrthogonalLine3 fitter; + fitter.Fit(numPoints, points); + Line3 line = fitter.GetParameters(); + + DCPQuery, Line3> plQuery; + Real maxRadiusSqr = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + auto result = plQuery(points[i], line); + if (result.sqrDistance > maxRadiusSqr) + { + maxRadiusSqr = result.sqrDistance; + } + } + + Vector3 basis[3]; + basis[0] = line.direction; + ComputeOrthogonalComplement(1, basis); + + Real minValue = std::numeric_limits::max(); + Real maxValue = -std::numeric_limits::max(); + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - line.origin; + Real uDotDiff = Dot(diff, basis[1]); + Real vDotDiff = Dot(diff, basis[2]); + Real wDotDiff = Dot(diff, basis[0]); + Real discr = maxRadiusSqr - (uDotDiff*uDotDiff + vDotDiff*vDotDiff); + Real radical = std::sqrt(std::max(discr, (Real)0)); + + Real test = wDotDiff + radical; + if (test < minValue) + { + minValue = test; + } + + test = wDotDiff - radical; + if (test > maxValue) + { + maxValue = test; + } + } + + Vector3 center = line.origin + + (((Real)0.5)*(minValue + maxValue))*line.direction; + + Real extent; + if (maxValue > minValue) + { + // Container is a capsule. + extent = ((Real)0.5)*(maxValue - minValue); + } + else + { + // Container is a sphere. + extent = (Real)0; + } + + capsule.segment = Segment3(center, line.direction, extent); + capsule.radius = std::sqrt(maxRadiusSqr); + return true; +} + +template +bool InContainer(Vector3 const& point, Capsule3 const& capsule) +{ + DCPQuery, Segment3> psQuery; + auto result = psQuery(point, capsule.segment); + return result.distance <= capsule.radius; +} + +template +bool InContainer(Sphere3 const& sphere, Capsule3 const& capsule) +{ + Real rDiff = capsule.radius - sphere.radius; + if (rDiff >= (Real)0) + { + DCPQuery, Segment3> psQuery; + auto result = psQuery(sphere.center, capsule.segment); + return result.distance <= rDiff; + } + return false; +} + +template +bool InContainer(Capsule3 const& testCapsule, + Capsule3 const& capsule) +{ + Sphere3 spherePosEnd(testCapsule.segment.p[1], testCapsule.radius); + Sphere3 sphereNegEnd(testCapsule.segment.p[0], testCapsule.radius); + return InContainer(spherePosEnd, capsule) + && InContainer(sphereNegEnd, capsule); +} + +template +bool MergeContainers(Capsule3 const& capsule0, + Capsule3 const& capsule1, Capsule3& merge) +{ + if (InContainer(capsule0, capsule1)) + { + merge = capsule1; + return true; + } + + if (InContainer(capsule1, capsule0)) + { + merge = capsule0; + return true; + } + + Vector3 P0, P1, D0, D1; + Real extent0, extent1; + capsule0.segment.GetCenteredForm(P0, D0, extent0); + capsule1.segment.GetCenteredForm(P1, D1, extent1); + + // Axis of final capsule. + Line3 line; + + // Axis center is average of input axis centers. + line.origin = ((Real)0.5)*(P0 + P1); + + // Axis unit direction is average of input axis unit directions. + if (Dot(D0, D1) >= (Real)0) + { + line.direction = D0 + D1; + } + else + { + line.direction = D0 - D1; + } + Normalize(line.direction); + + // Cylinder with axis 'line' must contain the spheres centered at the + // endpoints of the input capsules. + DCPQuery, Line3> plQuery; + Vector3 posEnd0 = capsule0.segment.p[1]; + Real radius = plQuery(posEnd0, line).distance + capsule0.radius; + + Vector3 negEnd0 = capsule0.segment.p[0]; + Real tmp = plQuery(negEnd0, line).distance + capsule0.radius; + + Vector3 posEnd1 = capsule1.segment.p[1]; + tmp = plQuery(posEnd1, line).distance + capsule1.radius; + if (tmp > radius) + { + radius = tmp; + } + + Vector3 negEnd1 = capsule1.segment.p[0]; + tmp = plQuery(negEnd1, line).distance + capsule1.radius; + if (tmp > radius) + { + radius = tmp; + } + + // Process sphere . + Real rDiff = radius - capsule0.radius; + Real rDiffSqr = rDiff*rDiff; + Vector3 diff = line.origin - posEnd0; + Real k0 = Dot(diff, diff) - rDiffSqr; + Real k1 = Dot(diff, line.direction); + Real discr = k1*k1 - k0; // assert: k1*k1-k0 >= 0, guard against anyway + Real root = std::sqrt(std::max(discr, (Real)0)); + Real tPos = -k1 - root; + Real tNeg = -k1 + root; + + // Process sphere . + diff = line.origin - negEnd0; + k0 = Dot(diff, diff) - rDiffSqr; + k1 = Dot(diff, line.direction); + discr = k1*k1 - k0; // assert: k1*k1-k0 >= 0, guard against anyway + root = std::sqrt(std::max(discr, (Real)0)); + tmp = -k1 - root; + if (tmp > tPos) + { + tPos = tmp; + } + tmp = -k1 + root; + if (tmp < tNeg) + { + tNeg = tmp; + } + + // Process sphere . + rDiff = radius - capsule1.radius; + rDiffSqr = rDiff*rDiff; + diff = line.origin - posEnd1; + k0 = Dot(diff, diff) - rDiffSqr; + k1 = Dot(diff, line.direction); + discr = k1*k1 - k0; // assert: k1*k1-k0 >= 0, guard against anyway + root = std::sqrt(std::max(discr, (Real)0)); + tmp = -k1 - root; + if (tmp > tPos) + { + tPos = tmp; + } + tmp = -k1 + root; + if (tmp < tNeg) + { + tNeg = tmp; + } + + // Process sphere . + diff = line.origin - negEnd1; + k0 = Dot(diff, diff) - rDiffSqr; + k1 = Dot(diff, line.direction); + discr = k1*k1 - k0; // assert: k1*k1-k0 >= 0, guard against anyway + root = std::sqrt(std::max(discr, (Real)0)); + tmp = -k1 - root; + if (tmp > tPos) + { + tPos = tmp; + } + tmp = -k1 + root; + if (tmp < tNeg) + { + tNeg = tmp; + } + + Vector3 center = line.origin + + ((Real)0.5)*(tPos + tNeg)*line.direction; + + Real extent; + if (tPos > tNeg) + { + // Container is a capsule. + extent = ((Real)0.5)*(tPos - tNeg); + } + else + { + // Container is a sphere. + extent = (Real)0; + } + + merge.segment = Segment3(center, line.direction, extent); + merge.radius = radius; + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCircle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCircle2.h new file mode 100644 index 000000000000..1063035785ab --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCircle2.h @@ -0,0 +1,81 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/16) + +#pragma once + +#include +#include + +namespace gte +{ + // Compute the smallest bounding circle whose center is the average of + // the input points. + template + bool GetContainer(int numPoints, Vector2 const* points, Circle2& circle) + { + circle.center = points[0]; + for (int i = 1; i < numPoints; ++i) + { + circle.center += points[i]; + } + circle.center /= (Real)numPoints; + + circle.radius = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - circle.center; + Real radiusSqr = Dot(diff, diff); + if (radiusSqr > circle.radius) + { + circle.radius = radiusSqr; + } + } + + circle.radius = std::sqrt(circle.radius); + return true; + } + + // Test for containment of a point inside a circle. + template + bool InContainer(Vector2 const& point, Circle2 const& circle) + { + Vector2 diff = point - circle.center; + return Length(diff) <= circle.radius; + } + + // Compute the smallest bounding circle that contains the input circles. + template + bool MergeContainers(Circle2 const& circle0, Circle2 const& circle1, Circle2& merge) + { + Vector2 cenDiff = circle1.center - circle0.center; + Real lenSqr = Dot(cenDiff, cenDiff); + Real rDiff = circle1.radius - circle0.radius; + Real rDiffSqr = rDiff * rDiff; + + if (rDiffSqr >= lenSqr) + { + merge = (rDiff >= (Real)0 ? circle1 : circle0); + } + else + { + Real length = std::sqrt(lenSqr); + if (length > (Real)0) + { + Real coeff = (length + rDiff) / (((Real)2)*length); + merge.center = circle0.center + coeff * cenDiff; + } + else + { + merge.center = circle0.center; + } + + merge.radius = ((Real)0.5)*(length + circle0.radius + circle1.radius); + } + + return true; + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCone.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCone.h new file mode 100644 index 000000000000..a80d062c1225 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCone.h @@ -0,0 +1,22 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.19.0 (2018/11/30) + +#pragma once + +#include + +namespace gte +{ + // Test for containment of a point by a cone. + template + bool InContainer(Vector const& point, Cone const& cone) + { + Vector diff = point - cone.ray.origin; + Real h = Dot(cone.ray.direction, diff); + return cone.minHeight <= h && h <= cone.maxHeight && h >= cone.cosAngle * Length(diff); + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCylinder3.h new file mode 100644 index 000000000000..ce56f8d043a1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCylinder3.h @@ -0,0 +1,88 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +// Compute the cylinder axis segment using least-squares fit. The radius is +// the maximum distance from points to the axis. The height is determined by +// projection of points onto the axis and determining the containing interval. +template +bool GetContainer(int numPoints, Vector3 const* points, + Cylinder3& cylinder); + +// Test for containment of a point by a cylinder. +template +bool InContainer(Vector3 const& point, Cylinder3 const& cylinder); + + +template +bool GetContainer(int numPoints, Vector3 const* points, + Cylinder3& cylinder) +{ + ApprOrthogonalLine3 fitter; + fitter.Fit(numPoints, points); + Line3 line = fitter.GetParameters(); + + DCPQuery, Line3> plQuery; + Real maxRadiusSqr = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + auto result = plQuery(points[i], line); + if (result.sqrDistance > maxRadiusSqr) + { + maxRadiusSqr = result.sqrDistance; + } + } + + Vector3 diff = points[0] - line.origin; + Real wMin = Dot(line.direction, diff); + Real wMax = wMin; + for (int i = 1; i < numPoints; ++i) + { + diff = points[i] - line.origin; + Real w = Dot(line.direction, diff); + if (w < wMin) + { + wMin = w; + } + else if (w > wMax) + { + wMax = w; + } + } + + cylinder.axis.origin = line.origin + + (((Real)0.5)*(wMax + wMin))*line.direction; + cylinder.axis.direction = line.direction; + cylinder.radius = std::sqrt(maxRadiusSqr); + cylinder.height = wMax - wMin; + return true; +} + +template +bool InContainer(Vector3 const& point, Cylinder3 const& cylinder) +{ + Vector3 diff = point - cylinder.axis.origin; + Real zProj = Dot(diff, cylinder.axis.direction); + if (std::abs(zProj)*(Real)2 > cylinder.height) + { + return false; + } + + Vector3 xyProj = diff - zProj*cylinder.axis.direction; + return Length(xyProj) <= cylinder.radius; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipse2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipse2.h new file mode 100644 index 000000000000..96660de251c8 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipse2.h @@ -0,0 +1,164 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +// The input points are fit with a Gaussian distribution. The center C of the +// ellipsoid is chosen to be the mean of the distribution. The axes of the +// ellipsoid are chosen to be the eigenvectors of the covariance matrix M. +// The shape of the ellipsoid is determined by the absolute values of the +// eigenvalues. NOTE: The construction is ill-conditioned if the points are +// (nearly) collinear. In this case M has a (nearly) zero eigenvalue, so +// inverting M can be a problem numerically. +template +bool GetContainer(int numPoints, Vector2 const* points, + Ellipse2& ellipse); + +// Test for containment of a point inside an ellipse. +template +bool InContainer(Vector2 const& point, Ellipse2 const& ellipse); + +// Construct a bounding ellipse for the two input ellipses. The result is +// not necessarily the minimum-area ellipse containing the two ellipses. +template +bool MergeContainers(Ellipse2 const& ellipse0, + Ellipse2 const& ellipse1, Ellipse2& merge); + + +template +bool GetContainer(int numPoints, Vector2 const* points, + Ellipse2& ellipse) +{ + // Fit the points with a Gaussian distribution. The covariance matrix + // is M = sum_j D[j]*U[j]*U[j]^T, where D[j] are the eigenvalues and U[j] + // are corresponding unit-length eigenvectors. + ApprGaussian2 fitter; + if (fitter.Fit(numPoints, points)) + { + OrientedBox2 box = fitter.GetParameters(); + + // If either eigenvalue is nonpositive, adjust the D[] values so that + // we actually build an ellipse. + for (int j = 0; j < 2; ++j) + { + if (box.extent[j] < (Real)0) + { + box.extent[j] = -box.extent[j]; + } + } + + // Grow the ellipse, while retaining its shape determined by the + // covariance matrix, to enclose all the input points. The quadratic + // form that is used for the ellipse construction is + // Q(X) = (X-C)^T*M*(X-C) + // = (X-C)^T*(sum_j D[j]*U[j]*U[j]^T)*(X-C) + // = sum_j D[j]*Dot(U[j],X-C)^2 + // If the maximum value of Q(X[i]) for all input points is V^2, then a + // bounding ellipse is Q(X) = V^2, because Q(X[i]) <= V^2 for all i. + + Real maxValue = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - box.center; + Real dot[2] = + { + Dot(box.axis[0], diff), + Dot(box.axis[1], diff) + }; + + Real value = + box.extent[0] * dot[0] * dot[0] + + box.extent[1] * dot[1] * dot[1]; + + if (value > maxValue) + { + maxValue = value; + } + } + + // Arrange for the quadratic to satisfy Q(X) <= 1. + ellipse.center = box.center; + for (int j = 0; j < 2; ++j) + { + ellipse.axis[j] = box.axis[j]; + ellipse.extent[j] = std::sqrt(maxValue / box.extent[j]); + } + return true; + + } + + return false; +} + +template +bool InContainer(Vector2 const& point, Ellipse2 const& ellipse) +{ + Vector2 diff = point - ellipse.center; + Vector2 standardized{ + Dot(diff, ellipse.axis[0]) / ellipse.extent[0], + Dot(diff, ellipse.axis[1]) / ellipse.extent[1] }; + return Length(standardized) <= (Real)1; +} + +template +bool MergeContainers(Ellipse2 const& ellipse0, + Ellipse2 const& ellipse1, Ellipse2& merge) +{ + // Compute the average of the input centers. + merge.center = ((Real)0.5)*(ellipse0.center + ellipse1.center); + + // The bounding ellipse orientation is the average of the input + // orientations. + if (Dot(ellipse0.axis[0], ellipse1.axis[0]) >= (Real)0) + { + merge.axis[0] = ((Real)0.5)*(ellipse0.axis[0] + ellipse1.axis[0]); + } + else + { + merge.axis[0] = ((Real)0.5)*(ellipse0.axis[0] - ellipse1.axis[0]); + } + Normalize(merge.axis[0]); + merge.axis[1] = -Perp(merge.axis[0]); + + // Project the input ellipses onto the axes obtained by the average of the + // orientations and that go through the center obtained by the average of + // the centers. + for (int j = 0; j < 2; ++j) + { + // Projection axis. + Line2 line(merge.center, merge.axis[j]); + + // Project ellipsoids onto the axis. + Real min0, max0, min1, max1; + Project(ellipse0, line, min0, max0); + Project(ellipse1, line, min1, max1); + + // Determine the smallest interval containing the projected + // intervals. + Real maxIntr = (max0 >= max1 ? max0 : max1); + Real minIntr = (min0 <= min1 ? min0 : min1); + + // Update the average center to be the center of the bounding box + // defined by the projected intervals. + merge.center += line.direction*(((Real)0.5)*(minIntr + maxIntr)); + + // Compute the extents of the box based on the new center. + merge.extent[j] = ((Real)0.5)*(maxIntr - minIntr); + } + + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipse2MinCR.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipse2MinCR.h new file mode 100644 index 000000000000..38dc5e541414 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipse2MinCR.h @@ -0,0 +1,233 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// Compute the minimum-area ellipse, (X-C)^T R D R^T (X-C) = 1, given the +// center C and the orientation matrix R. The columns of R are the axes of +// the ellipse. The algorithm computes the diagonal matrix D. The minimum +// area is pi/sqrt(D[0]*D[1]), where D = diag(D[0],D[1]). The problem is +// equivalent to maximizing the product D[0]*D[1] for a given C and R, and +// subject to the constraints +// (P[i]-C)^T R D R^T (P[i]-C) <= 1 +// for all input points P[i] with 0 <= i < N. Each constraint has the form +// A[0]*D[0] + A[1]*D[1] <= 1 +// where A[0] >= 0 and A[1] >= 0. + +namespace gte +{ + +template +class ContEllipse2MinCR +{ +public: + void operator()(int numPoints, Vector2 const* points, + Vector2 const& C, Matrix2x2 const& R, Real D[2]) const; + +private: + static void MaxProduct(std::vector>& A, Real D[2]); +}; + + +template +void ContEllipse2MinCR::operator()(int numPoints, + Vector2 const* points, Vector2 const& C, + Matrix2x2const & R, Real D[2]) const +{ + // Compute the constraint coefficients, of the form (A[0],A[1]) for + // each i. + std::vector> A(numPoints); + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - C; // P[i] - C + Vector2 prod = diff*R; // R^T*(P[i] - C) = (u,v) + A[i] = prod * prod; // (u^2, v^2) + } + + // Sort to eliminate redundant constraints. Use a lexicographical sort, + // (x0,y0) > (x1,y1) if x0 > x1 or if x0 = x1 and y0 > y1. Remove all + // but the first entry in blocks with x0 = x1 because the corresponding + // constraint lines for the first entry "hides" all the others from the + // origin. + std::sort(A.begin(), A.end(), + [](Vector2 const& P0, Vector2 const& P1) + { + if (P0[0] > P1[0]) { return true; } + if (P0[0] < P1[0]) { return false; } + return P0[1] > P1[1]; + } + ); + auto end = std::unique(A.begin(), A.end(), + [](Vector2 const& P0, Vector2 const& P1) + { + return P0[0] == P1[0]; + } + ); + A.erase(end, A.end()); + + // Lexicographical sort, (x0,y0) > (x1,y1) if y0 > y1 or if y0 = y1 and + // x0 > x1. Remove all but first entry in blocks with y0 = y1 since the + // corresponding constraint lines for the first entry "hides" all the + // others from the origin. + std::sort(A.begin(), A.end(), + [](Vector2 const& P0, Vector2 const& P1) + { + if (P0[1] > P1[1]) + { + return true; + } + + if (P0[1] < P1[1]) + { + return false; + } + + return P0[0] > P1[0]; + } + ); + end = std::unique(A.begin(), A.end(), + [](Vector2 const& P0, Vector2 const& P1) + { + return P0[1] == P1[1]; + } + ); + A.erase(end, A.end()); + + MaxProduct(A, D); +} + +template +void ContEllipse2MinCR::MaxProduct(std::vector>& A, + Real D[2]) +{ + // Keep track of which constraint lines have already been used in the + // search. + int numConstraints = static_cast(A.size()); + std::vector used(A.size()); + std::fill(used.begin(), used.end(), false); + + // Find the constraint line whose y-intercept (0,ymin) is closest to the + // origin. This line contributes to the convex hull of the constraints + // and the search for the maximum starts here. Also find the constraint + // line whose x-intercept (xmin,0) is closest to the origin. This line + // contributes to the convex hull of the constraints and the search for + // the maximum terminates before or at this line. + int i, iYMin = -1; + int iXMin = -1; + Real axMax = (Real)0, ayMax = (Real)0; // A[i] >= (0,0) by design + for (i = 0; i < numConstraints; ++i) + { + // The minimum x-intercept is 1/A[iXMin][0] for A[iXMin][0] the + // maximum of the A[i][0]. + if (A[i][0] > axMax) + { + axMax = A[i][0]; + iXMin = i; + } + + // The minimum y-intercept is 1/A[iYMin][1] for A[iYMin][1] the + // maximum of the A[i][1]. + if (A[i][1] > ayMax) + { + ayMax = A[i][1]; + iYMin = i; + } + } + LogAssert(iXMin != -1 && iYMin != -1, "Unexpected condition."); + used[iYMin] = true; + + // The convex hull is searched in a clockwise manner starting with the + // constraint line constructed above. The next vertex of the hull occurs + // as the closest point to the first vertex on the current constraint + // line. The following loop finds each consecutive vertex. + Real x0 = (Real)0, xMax = ((Real)1) / axMax; + int j; + for (j = 0; j < numConstraints; ++j) + { + // Find the line whose intersection with the current line is closest + // to the last hull vertex. The last vertex is at (x0,y0) on the + // current line. + Real x1 = xMax; + int line = -1; + for (i = 0; i < numConstraints; ++i) + { + if (!used[i]) + { + // This line not yet visited, process it. Given current + // constraint line a0*x+b0*y =1 and candidate line + // a1*x+b1*y = 1, find the point of intersection. The + // determinant of the system is d = a0*b1-a1*b0. We only + // care about lines that have more negative slope than the + // previous one, that is, -a1/b1 < -a0/b0, in which case we + // process only lines for which d < 0. + Real det = DotPerp(A[iYMin], A[i]); + if (det < (Real)0) + { + // Compute the x-value for the point of intersection, + // (x1,y1). There may be floating point error issues in + // the comparision 'D[0] <= fX1'. Consider modifying to + // 'D[0] <= fX1+epsilon'. + D[0] = (A[i][1] - A[iYMin][1]) / det; + if (x0 < D[0] && D[0] <= x1) + { + line = i; + x1 = D[0]; + } + } + } + } + + // Next vertex is at (x1,y1) whose x-value was computed above. First + // check for the maximum of x*y on the current line for x in [x0,x1]. + // On this interval the function is f(x) = x*(1-a0*x)/b0. The + // derivative is f'(x) = (1-2*a0*x)/b0 and f'(r) = 0 when + // r = 1/(2*a0). The three candidates for the maximum are f(x0), + // f(r), and f(x1). Comparisons are made between r and the end points + // x0 and x1. Since a0 = 0 is possible (constraint line is horizontal + // and f is increasing on line), the division in r is not performed + // and the comparisons are made between 1/2 = a0*r and a0*x0 or a0*x1. + + // Compare r < x0. + if ((Real)0.5 < A[iYMin][0] * x0) + { + // The maximum is f(x0) since the quadratic f decreases for + // x > r. + D[0] = x0; + D[1] = ((Real)1 - A[iYMin][0] * D[0]) / A[iYMin][1]; // = f(x0) + break; + } + + // Compare r < x1. + if ((Real)0.5 < A[iYMin][0] * x1) + { + // The maximum is f(r). The search ends here because the + // current line is tangent to the level curve of f(x)=f(r) + // and x*y can therefore only decrease as we traverse further + // around the hull in the clockwise direction. + D[0] = ((Real)0.5) / A[iYMin][0]; + D[1] = ((Real)0.5) / A[iYMin][1]; // = f(r) + break; + } + + // The maximum is f(x1). The function x*y is potentially larger + // on the next line, so continue the search. + LogAssert(line != -1, "Unexpected condition."); + x0 = x1; + x1 = xMax; + used[line] = true; + iYMin = line; + } + + LogAssert(j < numConstraints, "Unexpected condition."); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipsoid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipsoid3.h new file mode 100644 index 000000000000..c3bedcef3bf1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipsoid3.h @@ -0,0 +1,181 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +// The input points are fit with a Gaussian distribution. The center C of the +// ellipsoid is chosen to be the mean of the distribution. The axes of the +// ellipsoid are chosen to be the eigenvectors of the covariance matrix M. +// The shape of the ellipsoid is determined by the absolute values of the +// eigenvalues. NOTE: The construction is ill-conditioned if the points are +// (nearly) collinear or (nearly) planar. In this case M has a (nearly) zero +// eigenvalue, so inverting M is problematic. +template +bool GetContainer(int numPoints, Vector3 const* points, + Ellipsoid3& ellipsoid); + +// Test for containment of a point inside an ellipsoid. +template +bool InContainer(Vector3 const& point, + Ellipsoid3 const& ellipsoid); + +// Construct a bounding ellipsoid for the two input ellipsoids. The result is +// not necessarily the minimum-volume ellipsoid containing the two ellipsoids. +template +bool MergeContainers(Ellipsoid3 const& ellipsoid0, + Ellipsoid3 const& ellipsoid1, Ellipsoid3& merge); + + +template +bool GetContainer(int numPoints, Vector3 const* points, + Ellipsoid3& ellipsoid) +{ + // Fit the points with a Gaussian distribution. The covariance matrix + // is M = sum_j D[j]*U[j]*U[j]^T, where D[j] are the eigenvalues and U[j] + // are corresponding unit-length eigenvectors. + ApprGaussian3 fitter; + if (fitter.Fit(numPoints, points)) + { + OrientedBox3 box = fitter.GetParameters(); + + // If either eigenvalue is nonpositive, adjust the D[] values so that + // we actually build an ellipse. + for (int j = 0; j < 3; ++j) + { + if (box.extent[j] < (Real)0) + { + box.extent[j] = -box.extent[j]; + } + } + + // Grow the ellipsoid, while retaining its shape determined by the + // covariance matrix, to enclose all the input points. The quadratic + // form that is used for the ellipsoid construction is + // Q(X) = (X-C)^T*M*(X-C) + // = (X-C)^T*(sum_j D[j]*U[j]*U[j]^T)*(X-C) + // = sum_j D[j]*Dot(U[j],X-C)^2 + // If the maximum value of Q(X[i]) for all input points is V^2, then a + // bounding ellipsoid is Q(X) = V^2 since Q(X[i]) <= V^2 for all i. + + Real maxValue = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - box.center; + Real dot[3] = + { + Dot(box.axis[0], diff), + Dot(box.axis[1], diff), + Dot(box.axis[2], diff) + }; + + Real value = + box.extent[0] * dot[0] * dot[0] + + box.extent[1] * dot[1] * dot[1] + + box.extent[2] * dot[2] * dot[2]; + + if (value > maxValue) + { + maxValue = value; + } + } + + // Arrange for the quadratic to satisfy Q(X) <= 1. + ellipsoid.center = box.center; + for (int j = 0; j < 3; ++j) + { + ellipsoid.axis[j] = box.axis[j]; + ellipsoid.extent[j] = std::sqrt(maxValue / box.extent[j]); + } + return true; + } + + return false; +} + +template +bool InContainer(Vector3 const& point, + Ellipsoid3 const& ellipsoid) +{ + Vector3 diff = point - ellipsoid.center; + Vector3 standardized{ + Dot(diff, ellipsoid.axis[0]) / ellipsoid.extent[0], + Dot(diff, ellipsoid.axis[1]) / ellipsoid.extent[1], + Dot(diff, ellipsoid.axis[2]) / ellipsoid.extent[2] }; + return Length(standardized) <= (Real)1; +} + +template +bool MergeContainers(Ellipsoid3 const& ellipsoid0, + Ellipsoid3 const& ellipsoid1, Ellipsoid3& merge) +{ + // Compute the average of the input centers + merge.center = ((Real)0.5)*(ellipsoid0.center + ellipsoid1.center); + + // The bounding ellipsoid orientation is the average of the input + // orientations. + Matrix3x3 rot0, rot1; + rot0.SetCol(0, ellipsoid0.axis[0]); + rot0.SetCol(1, ellipsoid0.axis[1]); + rot0.SetCol(2, ellipsoid0.axis[2]); + rot1.SetCol(0, ellipsoid1.axis[0]); + rot1.SetCol(1, ellipsoid1.axis[1]); + rot1.SetCol(2, ellipsoid1.axis[2]); + Quaternion q0 = Rotation<3, Real>(rot0); + Quaternion q1 = Rotation<3, Real>(rot1); + if (Dot(q0, q1) < (Real)0) + { + q1 = -q1; + } + + Quaternion q = q0 + q1; + Normalize(q); + Matrix3x3 rot = Rotation<3, Real>(q); + for (int j = 0; j < 3; ++j) + { + merge.axis[j] = rot.GetCol(j); + } + + // Project the input ellipsoids onto the axes obtained by the average + // of the orientations and that go through the center obtained by the + // average of the centers. + for (int i = 0; i < 3; ++i) + { + // Projection axis. + Line3 line(merge.center, merge.axis[i]); + + // Project ellipsoids onto the axis. + Real min0, max0, min1, max1; + Project(ellipsoid0, line, min0, max0); + Project(ellipsoid1, line, min1, max1); + + // Determine the smallest interval containing the projected + // intervals. + Real maxIntr = (max0 >= max1 ? max0 : max1); + Real minIntr = (min0 <= min1 ? min0 : min1); + + // Update the average center to be the center of the bounding box + // defined by the projected intervals. + merge.center += line.direction*(((Real)0.5)*(minIntr + maxIntr)); + + // Compute the extents of the box based on the new center. + merge.extent[i] = ((Real)0.5)*(maxIntr - minIntr); + } + + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipsoid3MinCR.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipsoid3MinCR.h new file mode 100644 index 000000000000..814a0baae4d4 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipsoid3MinCR.h @@ -0,0 +1,331 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +// Compute the minimum-volume ellipsoid, (X-C)^T R D R^T (X-C) = 1, given the +// center C and orientation matrix R. The columns of R are the axes of the +// ellipsoid. The algorithm computes the diagonal matrix D. The minimum +// volume is (4*pi/3)/sqrt(D[0]*D[1]*D[2]), where D = diag(D[0],D[1],D[2]). +// The problem is equivalent to maximizing the product D[0]*D[1]*D[2] for a +// given C and R, and subject to the constraints +// (P[i]-C)^T R D R^T (P[i]-C) <= 1 +// for all input points P[i] with 0 <= i < N. Each constraint has the form +// A[0]*D[0] + A[1]*D[1] + A[2]*D[2] <= 1 +// where A[0] >= 0, A[1] >= 0, and A[2] >= 0. + +namespace gte +{ + +template +class ContEllipsoid3MinCR +{ +public: + void operator()(int numPoints, Vector3 const* points, + Vector3 const& C, Matrix3x3 const& R, Real D[3]); + +private: + void FindEdgeMax(std::vector>& A, int& plane0, + int& plane1, Real D[3]); + + void FindFacetMax(std::vector>& A, int& plane0, + Real D[3]); + + void MaxProduct(std::vector>& A, Real D[3]); +}; + + +template +void ContEllipsoid3MinCR::operator()(int numPoints, + Vector3 const* points, Vector3 const& C, + Matrix3x3 const& R, Real D[3]) +{ + // Compute the constraint coefficients, of the form (A[0],A[1]) for + // each i. + std::vector> A(numPoints); + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - C; // P[i] - C + Vector3 prod = diff*R; // R^T*(P[i] - C) = (u,v,w) + A[i] = prod * prod; // (u^2, v^2, w^2) + } + + // TODO: Sort the constraints to eliminate redundant ones. It is clear + // how to do this in ContEllipse2MinCR. How to do this in 3D? + + MaxProduct(A, D); +} + +template +void ContEllipsoid3MinCR::FindEdgeMax(std::vector>& A, + int& plane0, int& plane1, Real D[3]) +{ + // Compute direction to local maximum point on line of intersection. + Real xDir = A[plane0][1] * A[plane1][2] - A[plane1][1] * A[plane0][2]; + Real yDir = A[plane0][2] * A[plane1][0] - A[plane1][2] * A[plane0][0]; + Real zDir = A[plane0][0] * A[plane1][1] - A[plane1][0] * A[plane0][1]; + + // Build quadratic Q'(t) = (d/dt)(x(t)y(t)z(t)) = a0+a1*t+a2*t^2. + Real a0 = D[0] * D[1] * zDir + D[0] * D[2] * yDir + D[1] * D[2] * xDir; + Real a1 = ((Real)2)*(D[2] * xDir*yDir + D[1] * xDir*zDir + D[0] * yDir*zDir); + Real a2 = ((Real)3)*(xDir*yDir*zDir); + + // Find root to Q'(t) = 0 corresponding to maximum. + Real tFinal; + if (a2 != (Real)0) + { + Real invA2 = ((Real)1) / a2; + Real discr = a1*a1 - ((Real)4)*a0*a2; + discr = std::sqrt(std::max(discr, (Real)0)); + tFinal = -((Real)0.5)*(a1 + discr)*invA2; + if (a1 + ((Real)2)*a2*tFinal > (Real)0) + { + tFinal = ((Real)0.5)*(-a1 + discr)*invA2; + } + } + else if (a1 != (Real)0) + { + tFinal = -a0 / a1; + } + else if (a0 != (Real)0) + { + Real fmax = std::numeric_limits::max(); + tFinal = (a0 >= (Real)0 ? fmax : -fmax); + } + else + { + return; + } + + if (tFinal < (Real)0) + { + // Make (xDir,yDir,zDir) point in direction of increase of Q. + tFinal = -tFinal; + xDir = -xDir; + yDir = -yDir; + zDir = -zDir; + } + + // Sort remaining planes along line from current point to local maximum. + Real tMax = tFinal; + int plane2 = -1; + int numPoints = static_cast(A.size()); + for (int i = 0; i < numPoints; ++i) + { + if (i == plane0 || i == plane1) + { + continue; + } + + Real norDotDir = A[i][0] * xDir + A[i][1] * yDir + A[i][2] * zDir; + if (norDotDir <= (Real)0) + { + continue; + } + + // Theoretically the numerator must be nonnegative since an invariant + // in the algorithm is that (x0,y0,z0) is on the convex hull of the + // constraints. However, some numerical error may make this a small + // negative number. In that case set tmax = 0 (no change in + // position). + Real numer = (Real)1 - A[i][0] * D[0] - A[i][1] * D[1] - A[i][2] * D[2]; + if (numer < (Real)0) + { + LogError("Unexpected condition."); + plane2 = i; + tMax = (Real)0; + break; + } + + Real t = numer / norDotDir; + if (0 <= t && t < tMax) + { + plane2 = i; + tMax = t; + } + } + + D[0] += tMax*xDir; + D[1] += tMax*yDir; + D[2] += tMax*zDir; + + if (tMax == tFinal) + { + return; + } + + if (tMax > (Real)0) + { + plane0 = plane2; + FindFacetMax(A, plane0, D); + return; + } + + // tmax == 0, so return with D[0], D[1], and D[2] unchanged. +} + +template +void ContEllipsoid3MinCR::FindFacetMax(std::vector>& A, + int& plane0, Real D[3]) +{ + Real tFinal, xDir, yDir, zDir; + + if (A[plane0][0] > (Real)0 + && A[plane0][1] > (Real)0 + && A[plane0][2] > (Real)0) + { + // Compute local maximum point on plane. + Real oneThird = ((Real)1) / (Real)3; + Real xMax = oneThird / A[plane0][0]; + Real yMax = oneThird / A[plane0][1]; + Real zMax = oneThird / A[plane0][2]; + + // Compute direction to local maximum point on plane. + tFinal = (Real)1; + xDir = xMax - D[0]; + yDir = yMax - D[1]; + zDir = zMax - D[2]; + } + else + { + tFinal = std::numeric_limits::max(); + + if (A[plane0][0] > (Real)0) + { + xDir = (Real)0; + } + else + { + xDir = (Real)1; + } + + if (A[plane0][1] > (Real)0) + { + yDir = (Real)0; + } + else + { + yDir = (Real)1; + } + + if (A[plane0][2] > (Real)0) + { + zDir = (Real)0; + } + else + { + zDir = (Real)1; + } + } + + // Sort remaining planes along line from current point. + Real tMax = tFinal; + int plane1 = -1; + int numPoints = static_cast(A.size()); + for (int i = 0; i < numPoints; ++i) + { + if (i == plane0) + { + continue; + } + + Real norDotDir = A[i][0] * xDir + A[i][1] * yDir + A[i][2] * zDir; + if (norDotDir <= (Real)0) + { + continue; + } + + // Theoretically the numerator must be nonnegative since an invariant + // in the algorithm is that (x0,y0,z0) is on the convex hull of the + // constraints. However, some numerical error may make this a small + // negative number. In that case, set tmax = 0 (no change in + // position). + Real numer = (Real)1 - A[i][0] * D[0] - A[i][1] * D[1] - A[i][2] * D[2]; + if (numer < (Real)0) + { + LogError("Unexpected condition."); + plane1 = i; + tMax = (Real)0; + break; + } + + Real t = numer / norDotDir; + if (0 <= t && t < tMax) + { + plane1 = i; + tMax = t; + } + } + + D[0] += tMax*xDir; + D[1] += tMax*yDir; + D[2] += tMax*zDir; + + if (tMax == (Real)1) + { + return; + } + + if (tMax > (Real)0) + { + plane0 = plane1; + FindFacetMax(A, plane0, D); + return; + } + + FindEdgeMax(A, plane0, plane1, D); +} + +template +void ContEllipsoid3MinCR::MaxProduct(std::vector>& A, + Real D[3]) +{ + // Maximize x*y*z subject to x >= 0, y >= 0, z >= 0, and + // A[i]*x+B[i]*y+C[i]*z <= 1 for 0 <= i < N where A[i] >= 0, + // B[i] >= 0, and C[i] >= 0. + + // Jitter the lines to avoid cases where more than three planes + // intersect at the same point. Should also break parallelism + // and planes parallel to the coordinate planes. + std::mt19937 mte; + std::uniform_real_distribution rnd((Real)0, (Real)1); + Real maxJitter = (Real)1e-12; + int numPoints = static_cast(A.size()); + int i; + for (i = 0; i < numPoints; ++i) + { + A[i][0] += maxJitter*rnd(mte); + A[i][1] += maxJitter*rnd(mte); + A[i][2] += maxJitter*rnd(mte); + } + + // Sort lines along the z-axis (x = 0 and y = 0). + int plane = -1; + Real zmax = (Real)0; + for (i = 0; i < numPoints; ++i) + { + if (A[i][2] > zmax) + { + zmax = A[i][2]; + plane = i; + } + } + LogAssert(plane != -1, "Unexpected condition."); + + // Walk along convex hull searching for maximum. + D[0] = (Real)0; + D[1] = (Real)0; + D[2] = ((Real)1) / zmax; + FindFacetMax(A, plane, D); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContOrientedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContOrientedBox2.h new file mode 100644 index 000000000000..0a7c0d6017ec --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContOrientedBox2.h @@ -0,0 +1,186 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +// Compute an oriented bounding box of the points. The box center is the +// average of the points. The box axes are the eigenvectors of the covariance +// matrix. +template +bool GetContainer(int numPoints, Vector2 const* points, + OrientedBox2& box); + +// Test for containment. Let X = C + y0*U0 + y1*U1 where C is the box center +// and U0 and U1 are the orthonormal axes of the box. X is in the box when +// |y_i| <= E_i for all i, where E_i are the extents of the box. +template +bool InContainer(Vector2 const& point, OrientedBox2 const& box); + +// Construct an oriented box that contains two other oriented boxes. The +// result is not guaranteed to be the minimum area box containing the input +// boxes. +template +bool MergeContainers(OrientedBox2 const& box0, + OrientedBox2 const& box1, OrientedBox2& merge); + + +template +bool GetContainer(int numPoints, Vector2 const* points, + OrientedBox2& box) +{ + // Fit the points with a Gaussian distribution. + ApprGaussian2 fitter; + if (fitter.Fit(numPoints, points)) + { + box = fitter.GetParameters(); + + // Let C be the box center and let U0 and U1 be the box axes. Each + // input point is of the form X = C + y0*U0 + y1*U1. The following + // code computes min(y0), max(y0), min(y1), and max(y1). The box + // center is then adjusted to be + // C' = C + 0.5*(min(y0)+max(y0))*U0 + 0.5*(min(y1)+max(y1))*U1 + + Vector2 diff = points[0] - box.center; + Vector2 pmin{ Dot(diff, box.axis[0]), Dot(diff, box.axis[1]) }; + Vector2 pmax = pmin; + for (int i = 1; i < numPoints; ++i) + { + diff = points[i] - box.center; + for (int j = 0; j < 2; ++j) + { + Real dot = Dot(diff, box.axis[j]); + if (dot < pmin[j]) + { + pmin[j] = dot; + } + else if (dot > pmax[j]) + { + pmax[j] = dot; + } + } + } + + for (int j = 0; j < 2; ++j) + { + box.center += (((Real)0.5)*(pmin[j] + pmax[j]))*box.axis[j]; + box.extent[j] = ((Real)0.5)*(pmax[j] - pmin[j]); + } + return true; + } + + return false; +} + +template +bool InContainer(Vector2 const& point, OrientedBox2 const& box) +{ + Vector2 diff = point - box.center; + for (int i = 0; i < 2; ++i) + { + Real coeff = Dot(diff, box.axis[i]); + if (std::abs(coeff) > box.extent[i]) + { + return false; + } + } + return true; +} + +template +bool MergeContainers(OrientedBox2 const& box0, + OrientedBox2 const& box1, OrientedBox2& merge) +{ + // The first guess at the box center. This value will be updated later + // after the input box vertices are projected onto axes determined by an + // average of box axes. + merge.center = ((Real)0.5)*(box0.center + box1.center); + + // The merged box axes are the averages of the input box axes. The axes + // of the second box are negated, if necessary, so they form acute angles + // with the axes of the first box. + if (Dot(box0.axis[0], box1.axis[0]) >= (Real)0) + { + merge.axis[0] = ((Real)0.5)*(box0.axis[0] + box1.axis[0]); + } + else + { + merge.axis[0] = ((Real)0.5)*(box0.axis[0] - box1.axis[0]); + } + Normalize(merge.axis[0]); + merge.axis[1] = -Perp(merge.axis[0]); + + // Project the input box vertices onto the merged-box axes. Each axis + // D[i] containing the current center C has a minimum projected value + // min[i] and a maximum projected value max[i]. The corresponding + // endpoints on the axes are C+min[i]*D[i] and C+max[i]*D[i]. The point + // C is not necessarily the midpoint for any of the intervals. The actual + // box center will be adjusted from C to a point C' that is the midpoint + // of each interval, + // C' = C + sum_{i=0}^1 0.5*(min[i]+max[i])*D[i] + // The box extents are + // e[i] = 0.5*(max[i]-min[i]) + + std::array, 4> vertex; + Vector2 pmin{ (Real)0, (Real)0 }; + Vector2 pmax{ (Real)0, (Real)0 }; + + box0.GetVertices(vertex); + for (int i = 0; i < 4; ++i) + { + Vector2 diff = vertex[i] - merge.center; + for (int j = 0; j < 2; ++j) + { + Real dot = Dot(diff, merge.axis[j]); + if (dot > pmax[j]) + { + pmax[j] = dot; + } + else if (dot < pmin[j]) + { + pmin[j] = dot; + } + } + } + + box1.GetVertices(vertex); + for (int i = 0; i < 4; ++i) + { + Vector2 diff = vertex[i] - merge.center; + for (int j = 0; j < 2; ++j) + { + Real dot = Dot(diff, merge.axis[j]); + if (dot > pmax[j]) + { + pmax[j] = dot; + } + else if (dot < pmin[j]) + { + pmin[j] = dot; + } + } + } + + // [min,max] is the axis-aligned box in the coordinate system of the + // merged box axes. Update the current box center to be the center of + // the new box. Compute the extents based on the new center. + Real const half = (Real)0.5; + for (int j = 0; j < 2; ++j) + { + merge.center += half*(pmax[j] + pmin[j])*merge.axis[j]; + merge.extent[j] = half*(pmax[j] - pmin[j]); + } + + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContOrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContOrientedBox3.h new file mode 100644 index 000000000000..1b4840b157b8 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContOrientedBox3.h @@ -0,0 +1,204 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +// Compute an oriented bounding box of the points. The box center is the +// average of the points. The box axes are the eigenvectors of the covariance +// matrix. +template +bool GetContainer(int numPoints, Vector3 const* points, + OrientedBox3& box); + +// Test for containment. Let X = C + y0*U0 + y1*U1 + y2*U2 where C is the +// box center and U0, U1, U2 are the orthonormal axes of the box. X is in +// the box if |y_i| <= E_i for all i where E_i are the extents of the box. +template +bool InContainer(Vector3 const& point, OrientedBox3 const& box); + +// Construct an oriented box that contains two other oriented boxes. The +// result is not guaranteed to be the minimum volume box containing the +// input boxes. +template +bool MergeContainers(OrientedBox3 const& box0, + OrientedBox3 const& box1, OrientedBox3& merge); + + +template +bool GetContainer(int numPoints, Vector3 const* points, + OrientedBox3& box) +{ + // Fit the points with a Gaussian distribution. + ApprGaussian3 fitter; + if (fitter.Fit(numPoints, points)) + { + box = fitter.GetParameters(); + + // Let C be the box center and let U0, U1, and U2 be the box axes. + // Each input point is of the form X = C + y0*U0 + y1*U1 + y2*U2. + // The following code computes min(y0), max(y0), min(y1), max(y1), + // min(y2), and max(y2). The box center is then adjusted to be + // C' = C + 0.5*(min(y0)+max(y0))*U0 + 0.5*(min(y1)+max(y1))*U1 + + // 0.5*(min(y2)+max(y2))*U2 + + Vector3 diff = points[0] - box.center; + Vector3 pmin{ Dot(diff, box.axis[0]), Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) }; + Vector3 pmax = pmin; + for (int i = 1; i < numPoints; ++i) + { + diff = points[i] - box.center; + for (int j = 0; j < 3; ++j) + { + Real dot = Dot(diff, box.axis[j]); + if (dot < pmin[j]) + { + pmin[j] = dot; + } + else if (dot > pmax[j]) + { + pmax[j] = dot; + } + } + } + + for (int j = 0; j < 3; ++j) + { + box.center += (((Real)0.5)*(pmin[j] + pmax[j]))*box.axis[j]; + box.extent[j] = ((Real)0.5)*(pmax[j] - pmin[j]); + } + return true; + } + + return false; +} + +template +bool InContainer(Vector3 const& point, OrientedBox3 const& box) +{ + Vector3 diff = point - box.center; + for (int i = 0; i < 3; ++i) + { + Real coeff = Dot(diff, box.axis[i]); + if (std::abs(coeff) > box.extent[i]) + { + return false; + } + } + return true; +} + +template +bool MergeContainers(OrientedBox3 const& box0, + OrientedBox3 const& box1, OrientedBox3& merge) +{ + // The first guess at the box center. This value will be updated later + // after the input box vertices are projected onto axes determined by an + // average of box axes. + merge.center = ((Real)0.5)*(box0.center + box1.center); + + // A box's axes, when viewed as the columns of a matrix, form a rotation + // matrix. The input box axes are converted to quaternions. The average + // quaternion is computed, then normalized to unit length. The result is + // the slerp of the two input quaternions with t-value of 1/2. The result + // is converted back to a rotation matrix and its columns are selected as + // the merged box axes. + Matrix3x3 rot0, rot1; + rot0.SetCol(0, box0.axis[0]); + rot0.SetCol(1, box0.axis[1]); + rot0.SetCol(2, box0.axis[2]); + rot1.SetCol(0, box1.axis[0]); + rot1.SetCol(1, box1.axis[1]); + rot1.SetCol(2, box1.axis[2]); + Quaternion q0 = Rotation<3, Real>(rot0); + Quaternion q1 = Rotation<3, Real>(rot1); + if (Dot(q0, q1) < (Real)0) + { + q1 = -q1; + } + + Quaternion q = q0 + q1; + Normalize(q); + Matrix3x3 rot = Rotation<3, Real>(q); + for (int j = 0; j < 3; ++j) + { + merge.axis[j] = rot.GetCol(j); + } + + // Project the input box vertices onto the merged-box axes. Each axis + // D[i] containing the current center C has a minimum projected value + // min[i] and a maximum projected value max[i]. The corresponding end + // points on the axes are C+min[i]*D[i] and C+max[i]*D[i]. The point C + // is not necessarily the midpoint for any of the intervals. The actual + // box center will be adjusted from C to a point C' that is the midpoint + // of each interval, + // C' = C + sum_{i=0}^2 0.5*(min[i]+max[i])*D[i] + // The box extents are + // e[i] = 0.5*(max[i]-min[i]) + + std::array, 8> vertex; + Vector3 pmin{ (Real)0, (Real)0, (Real)0 }; + Vector3 pmax{ (Real)0, (Real)0, (Real)0 }; + + box0.GetVertices(vertex); + for (int i = 0; i < 8; ++i) + { + Vector3 diff = vertex[i] - merge.center; + for (int j = 0; j < 3; ++j) + { + Real dot = Dot(diff, merge.axis[j]); + if (dot > pmax[j]) + { + pmax[j] = dot; + } + else if (dot < pmin[j]) + { + pmin[j] = dot; + } + } + } + + box1.GetVertices(vertex); + for (int i = 0; i < 8; ++i) + { + Vector3 diff = vertex[i] - merge.center; + for (int j = 0; j < 3; ++j) + { + Real dot = Dot(diff, merge.axis[j]); + if (dot > pmax[j]) + { + pmax[j] = dot; + } + else if (dot < pmin[j]) + { + pmin[j] = dot; + } + } + } + + // [min,max] is the axis-aligned box in the coordinate system of the + // merged box axes. Update the current box center to be the center of + // the new box. Compute the extents based on the new center. + Real const half = (Real)0.5; + for (int j = 0; j < 3; ++j) + { + merge.center += half*(pmax[j] + pmin[j])*merge.axis[j]; + merge.extent[j] = half*(pmax[j] - pmin[j]); + } + + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContPointInPolygon2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContPointInPolygon2.h new file mode 100644 index 000000000000..c5b0460b32bd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContPointInPolygon2.h @@ -0,0 +1,227 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Given a polygon as an order list of vertices (x[i],y[i]) for 0 <= i < N +// and a test point (xt,yt), return 'true' if (xt,yt) is in the polygon and +// 'false' if it is not. All queries require that the number of vertices +// satisfies N >= 3. + +namespace gte +{ + +template +class PointInPolygon2 +{ +public: + // The class object stores a copy of 'points', so be careful about the + // persistence of 'points' when you have created a PointInPolygon2 object. + PointInPolygon2(int numPoints, Vector2 const* points); + + // Simple polygons (ray-intersection counting). + bool Contains(Vector2 const& p) const; + + // Algorithms for convex polygons. The input polygons must have vertices + // in counterclockwise order. + + // O(N) algorithm (which-side-of-edge tests) + bool ContainsConvexOrderN(Vector2 const& p) const; + + // O(log N) algorithm (bisection and recursion, like BSP tree) + bool ContainsConvexOrderLogN(Vector2 const& p) const; + + // The polygon must have exactly four vertices. This method is like the + // O(log N) and uses three which-side-of-segment test instead of four + // which-side-of-edge tests. If the polygon does not have four vertices, + // the function returns false. + bool ContainsQuadrilateral(Vector2 const& p) const; + +private: + // For recursion in ContainsConvexOrderLogN. + bool SubContainsPoint(Vector2 const& p, int i0, int i1) const; + + int mNumPoints; + Vector2 const* mPoints; +}; + + +template +PointInPolygon2::PointInPolygon2(int numPoints, + Vector2 const* points) + : + mNumPoints(numPoints), + mPoints(points) +{ +} + +template +bool PointInPolygon2::Contains(Vector2 const& p) const +{ + bool inside = false; + for (int i = 0, j = mNumPoints - 1; i < mNumPoints; j = i++) + { + Vector2 const& U0 = mPoints[i]; + Vector2 const& U1 = mPoints[j]; + Real rhs, lhs; + + if (p[1] < U1[1]) // U1 above ray + { + if (U0[1] <= p[1]) // U0 on or below ray + { + lhs = (p[1] - U0[1])*(U1[0] - U0[0]); + rhs = (p[0] - U0[0])*(U1[1] - U0[1]); + if (lhs > rhs) + { + inside = !inside; + } + } + } + else if (p[1] < U0[1]) // U1 on or below ray, U0 above ray + { + lhs = (p[1] - U0[1])*(U1[0] - U0[0]); + rhs = (p[0] - U0[0])*(U1[1] - U0[1]); + if (lhs < rhs) + { + inside = !inside; + } + } + } + return inside; +} + +template +bool PointInPolygon2::ContainsConvexOrderN(Vector2 const& p) const +{ + for (int i1 = 0, i0 = mNumPoints - 1; i1 < mNumPoints; i0 = i1++) + { + Real nx = mPoints[i1][1] - mPoints[i0][1]; + Real ny = mPoints[i0][0] - mPoints[i1][0]; + Real dx = p[0] - mPoints[i0][0]; + Real dy = p[1] - mPoints[i0][1]; + if (nx*dx + ny*dy >(Real)0) + { + return false; + } + } + return true; +} + +template +bool PointInPolygon2::ContainsConvexOrderLogN(Vector2 const& p) +const +{ + return SubContainsPoint(p, 0, 0); +} + +template +bool PointInPolygon2::ContainsQuadrilateral(Vector2 const& p) +const +{ + if (mNumPoints != 4) + { + return false; + } + + Real nx = mPoints[2][1] - mPoints[0][1]; + Real ny = mPoints[0][0] - mPoints[2][0]; + Real dx = p[0] - mPoints[0][0]; + Real dy = p[1] - mPoints[0][1]; + + if (nx*dx + ny*dy > (Real)0) + { + // P potentially in + nx = mPoints[1][1] - mPoints[0][1]; + ny = mPoints[0][0] - mPoints[1][0]; + if (nx*dx + ny*dy > (Real)0.0) + { + return false; + } + + nx = mPoints[2][1] - mPoints[1][1]; + ny = mPoints[1][0] - mPoints[2][0]; + dx = p[0] - mPoints[1][0]; + dy = p[1] - mPoints[1][1]; + if (nx*dx + ny*dy > (Real)0) + { + return false; + } + } + else + { + // P potentially in + nx = mPoints[0][1] - mPoints[3][1]; + ny = mPoints[3][0] - mPoints[0][0]; + if (nx*dx + ny*dy > (Real)0) + { + return false; + } + + nx = mPoints[3][1] - mPoints[2][1]; + ny = mPoints[2][0] - mPoints[3][0]; + dx = p[0] - mPoints[3][0]; + dy = p[1] - mPoints[3][1]; + if (nx*dx + ny*dy > (Real)0) + { + return false; + } + } + return true; +} + +template +bool PointInPolygon2::SubContainsPoint(Vector2 const& p, int i0, + int i1) const +{ + Real nx, ny, dx, dy; + + int diff = i1 - i0; + if (diff == 1 || (diff < 0 && diff + mNumPoints == 1)) + { + nx = mPoints[i1][1] - mPoints[i0][1]; + ny = mPoints[i0][0] - mPoints[i1][0]; + dx = p[0] - mPoints[i0][0]; + dy = p[1] - mPoints[i0][1]; + return nx*dx + ny*dy <= (Real)0; + } + + // Bisect the index range. + int mid; + if (i0 < i1) + { + mid = (i0 + i1) >> 1; + } + else + { + mid = ((i0 + i1 + mNumPoints) >> 1); + if (mid >= mNumPoints) + { + mid -= mNumPoints; + } + } + + // Determine which side of the splitting line contains the point. + nx = mPoints[mid][1] - mPoints[i0][1]; + ny = mPoints[i0][0] - mPoints[mid][0]; + dx = p[0] - mPoints[i0][0]; + dy = p[1] - mPoints[i0][1]; + if (nx*dx + ny*dy > (Real)0) + { + // P potentially in + return SubContainsPoint(p, i0, mid); + } + else + { + // P potentially in + return SubContainsPoint(p, mid, i1); + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContPointInPolyhedron3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContPointInPolyhedron3.h new file mode 100644 index 000000000000..ea3bbd16ebbc --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContPointInPolyhedron3.h @@ -0,0 +1,613 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2016/11/25) + +#pragma once + +#include +#include +#include +#include +#include + +// This class contains various implementations for point-in-polyhedron +// queries. The planes stored with the faces are used in all cases to +// reject ray-face intersection tests, a quick culling operation. +// +// The algorithm is to cast a ray from the input point P and test for +// intersection against each face of the polyhedron. If the ray only +// intersects faces at interior points (not vertices, not edge points), +// then the point is inside when the number of intersections is odd and +// the point is outside when the number of intersections is even. If the +// ray intersects an edge or a vertex, then the counting must be handled +// differently. The details are tedious. As an alternative, the approach +// here is to allow you to specify 2*N+1 rays, where N >= 0. You should +// choose these rays randomly. Each ray reports "inside" or "outside". +// Whichever result occurs N+1 or more times is the "winner". The input +// rayQuantity is 2*N+1. The input array Direction must have rayQuantity +// elements. If you are feeling lucky, choose rayQuantity to be 1. + +namespace gte +{ + +template +class PointInPolyhedron3 +{ +public: + // For simple polyhedra with triangle faces. + class TriangleFace + { + public: + // When you view the face from outside, the vertices are + // counterclockwise ordered. The Indices array stores the indices + // into the vertex array. + int indices[3]; + + // The normal vector is unit length and points to the outside of the + // polyhedron. + Plane3 plane; + }; + + // The Contains query will use ray-triangle intersection queries. + PointInPolyhedron3(int numPoints, Vector3 const* points, + int numFaces, TriangleFace const* faces, int numRays, + Vector3 const* directions); + + // For simple polyhedra with convex polygon faces. + class ConvexFace + { + public: + // When you view the face from outside, the vertices are + // counterclockwise ordered. The Indices array stores the indices + // into the vertex array. + std::vector indices; + + // The normal vector is unit length and points to the outside of the + // polyhedron. + Plane3 plane; + }; + + // The Contains query will use ray-convexpolygon intersection queries. A + // ray-convexpolygon intersection query can be implemented in many ways. + // In this context, uiMethod is one of three value: + // 0 : Use a triangle fan and perform a ray-triangle intersection query + // for each triangle. + // 1 : Find the point of intersection of ray and plane of polygon. Test + // whether that point is inside the convex polygon using an O(N) + // test. + // 2 : Find the point of intersection of ray and plane of polygon. Test + // whether that point is inside the convex polygon using an O(log N) + // test. + PointInPolyhedron3(int numPoints, Vector3 const* points, + int numFaces, ConvexFace const* faces, int numRays, + Vector3 const* directions, unsigned int method); + + // For simple polyhedra with simple polygon faces that are generally + // not all convex. + class SimpleFace + { + public: + // When you view the face from outside, the vertices are + // counterclockwise ordered. The Indices array stores the indices + // into the vertex array. + std::vector indices; + + // The normal vector is unit length and points to the outside of the + // polyhedron. + Plane3 plane; + + // Each simple face may be triangulated. The indices are relative to + // the vertex array. Each triple of indices represents a triangle in + // the triangulation. + std::vector triangles; + }; + + // The Contains query will use ray-simplepolygon intersection queries. A + // ray-simplepolygon intersection query can be implemented in a couple of + // ways. In this context, uiMethod is one of two value: + // 0 : Iterate over the triangles of each face and perform a + // ray-triangle intersection query for each triangle. This requires + // that the SimpleFace::Triangles array be initialized for each + // face. + // 1 : Find the point of intersection of ray and plane of polygon. Test + // whether that point is inside the polygon using an O(N) test. The + // SimpleFace::Triangles array is not used for this method, so it + // does not have to be initialized for each face. + PointInPolyhedron3(int numPoints, Vector3 const* points, + int numFaces, SimpleFace const* faces, int numRays, + Vector3 const* directions, unsigned int method); + + // This function will select the actual algorithm based on which + // constructor you used for this class. + bool Contains(Vector3 const& p) const; + +private: + // For all types of faces. The ray origin is the test point. The ray + // direction is one of those passed to the constructors. The plane origin + // is a point on the plane of the face. The plane normal is a unit-length + // normal to the face and that points outside the polyhedron. + static bool FastNoIntersect(Ray3 const& ray, + Plane3 const& plane); + + // For triangle faces. + bool ContainsT0(Vector3 const& p) const; + + // For convex faces. + bool ContainsC0(Vector3 const& p) const; + bool ContainsC1C2(Vector3 const& p, unsigned int method) const; + + // For simple faces. + bool ContainsS0(Vector3 const& p) const; + bool ContainsS1(Vector3 const& p) const; + + int mNumPoints; + Vector3 const* mPoints; + + int mNumFaces; + TriangleFace const* mTFaces; + ConvexFace const* mCFaces; + SimpleFace const* mSFaces; + + unsigned int mMethod; + int mNumRays; + Vector3 const* mDirections; + + // Temporary storage for those methods that reduce the problem to 2D + // point-in-polygon queries. The array stores the projections of + // face vertices onto the plane of the face. It is resized as needed. + mutable std::vector> mProjVertices; +}; + + +template +PointInPolyhedron3::PointInPolyhedron3(int numPoints, + Vector3 const* points, int numFaces, TriangleFace const* faces, + int numRays, Vector3 const* directions) + : + mNumPoints(numPoints), + mPoints(points), + mNumFaces(numFaces), + mTFaces(faces), + mCFaces(nullptr), + mSFaces(nullptr), + mMethod(0), + mNumRays(numRays), + mDirections(directions) +{ +} + +template +PointInPolyhedron3::PointInPolyhedron3(int numPoints, + Vector3 const* points, int numFaces, ConvexFace const* faces, + int numRays, Vector3 const* directions, unsigned int method) + : + mNumPoints(numPoints), + mPoints(points), + mNumFaces(numFaces), + mTFaces(nullptr), + mCFaces(faces), + mSFaces(nullptr), + mMethod(method), + mNumRays(numRays), + mDirections(directions) +{ +} + +template +PointInPolyhedron3::PointInPolyhedron3(int numPoints, + Vector3 const* points, int numFaces, SimpleFace const* faces, + int numRays, Vector3 const* directions, unsigned int method) + : + mNumPoints(numPoints), + mPoints(points), + mNumFaces(numFaces), + mTFaces(nullptr), + mCFaces(nullptr), + mSFaces(faces), + mMethod(method), + mNumRays(numRays), + mDirections(directions) +{ +} + +template +bool PointInPolyhedron3::Contains(Vector3 const& p) const +{ + if (mTFaces) + { + return ContainsT0(p); + } + + if (mCFaces) + { + if (mMethod == 0) + { + return ContainsC0(p); + } + + return ContainsC1C2(p, mMethod); + } + + if (mSFaces) + { + if (mMethod == 0) + { + return ContainsS0(p); + } + + if (mMethod == 1) + { + return ContainsS1(p); + } + } + + return false; +} + +template +bool PointInPolyhedron3::FastNoIntersect(Ray3 const& ray, + Plane3 const& plane) +{ + Real planeDistance = Dot(plane.normal, ray.origin) - plane.constant; + Real planeAngle = Dot(plane.normal, ray.direction); + + if (planeDistance < (Real)0) + { + // The ray origin is on the negative side of the plane. + if (planeAngle <= (Real)0) + { + // The ray points away from the plane. + return true; + } + } + + if (planeDistance >(Real)0) + { + // The ray origin is on the positive side of the plane. + if (planeAngle >= (Real)0) + { + // The ray points away from the plane. + return true; + } + } + + return false; +} + +template +bool PointInPolyhedron3::ContainsT0(Vector3 const& p) const +{ + int insideCount = 0; + + TIQuery, Triangle3> rtQuery; + Triangle3 triangle; + Ray3 ray; + ray.origin = p; + + for (int j = 0; j < mNumRays; ++j) + { + ray.direction = mDirections[j]; + + // Zero intersections to start with. + bool odd = false; + + TriangleFace const* face = mTFaces; + for (int i = 0; i < mNumFaces; ++i, ++face) + { + // Attempt to quickly cull the triangle. + if (FastNoIntersect(ray, face->plane)) + { + continue; + } + + // Get the triangle vertices. + for (int k = 0; k < 3; ++k) + { + triangle.v[k] = mPoints[face->indices[k]]; + } + + // Test for intersection. + if (rtQuery(ray, triangle).intersect) + { + // The ray intersects the triangle. + odd = !odd; + } + } + + if (odd) + { + insideCount++; + } + } + + return insideCount > mNumRays / 2; +} + +template +bool PointInPolyhedron3::ContainsC0(Vector3 const& p) const +{ + int insideCount = 0; + + TIQuery, Triangle3> rtQuery; + Triangle3 triangle; + Ray3 ray; + ray.origin = p; + + for (int j = 0; j < mNumRays; ++j) + { + ray.direction = mDirections[j]; + + // Zero intersections to start with. + bool odd = false; + + ConvexFace const* face = mCFaces; + for (int i = 0; i < mNumFaces; ++i, ++face) + { + // Attempt to quickly cull the triangle. + if (FastNoIntersect(ray, face->plane)) + { + continue; + } + + // Process the triangles in a trifan of the face. + size_t numVerticesM1 = face->indices.size() - 1; + triangle.v[0] = mPoints[face->indices[0]]; + for (size_t k = 1; k < numVerticesM1; ++k) + { + triangle.v[1] = mPoints[face->indices[k]]; + triangle.v[2] = mPoints[face->indices[k + 1]]; + + if (rtQuery(ray, triangle).intersect) + { + // The ray intersects the triangle. + odd = !odd; + } + } + } + + if (odd) + { + insideCount++; + } + } + + return insideCount > mNumRays / 2; +} + +template +bool PointInPolyhedron3::ContainsS0(Vector3 const& p) const +{ + int insideCount = 0; + + TIQuery, Triangle3> rtQuery; + Triangle3 triangle; + Ray3 ray; + ray.origin = p; + + for (int j = 0; j < mNumRays; ++j) + { + ray.direction = mDirections[j]; + + // Zero intersections to start with. + bool odd = false; + + SimpleFace const* face = mSFaces; + for (int i = 0; i < mNumFaces; ++i, ++face) + { + // Attempt to quickly cull the triangle. + if (FastNoIntersect(ray, face->plane)) + { + continue; + } + + // The triangulation must exist to use it. + size_t numTriangles = face->triangles.size() / 3; + LogAssert(numTriangles > 0, "Triangulation must exist."); + + // Process the triangles in a triangulation of the face. + int const* currIndex = &face->triangles[0]; + for (size_t t = 0; t < numTriangles; ++t) + { + // Get the triangle vertices. + for (int k = 0; k < 3; ++k) + { + triangle.v[k] = mPoints[*currIndex++]; + } + + // Test for intersection. + if (rtQuery(ray, triangle).intersect) + { + // The ray intersects the triangle. + odd = !odd; + } + } + } + + if (odd) + { + insideCount++; + } + } + + return insideCount > mNumRays / 2; +} + +template +bool PointInPolyhedron3::ContainsC1C2(Vector3 const& p, + unsigned int method) const +{ + int insideCount = 0; + + FIQuery, Plane3> rpQuery; + Ray3 ray; + ray.origin = p; + + for (int j = 0; j < mNumRays; ++j) + { + ray.direction = mDirections[j]; + + // Zero intersections to start with. + bool odd = false; + + ConvexFace const* face = mCFaces; + for (int i = 0; i < mNumFaces; ++i, ++face) + { + // Attempt to quickly cull the triangle. + if (FastNoIntersect(ray, face->plane)) + { + continue; + } + + // Compute the ray-plane intersection. + auto result = rpQuery(ray, face->plane); + + // If you trigger this assertion, numerical round-off errors have + // led to a discrepancy between FastNoIntersect and the Find() + // result. + LogAssert(result.intersect, "Unexpected condition."); + + // Get a coordinate system for the plane. Use vertex 0 as the + // origin. + Vector3 const& V0 = mPoints[face->indices[0]]; + Vector3 basis[3]; + basis[0] = face->plane.normal; + ComputeOrthogonalComplement(1, basis); + + // Project the intersection onto the plane. + Vector3 diff = result.point - V0; + Vector2 projIntersect{ + Dot(basis[1], diff), Dot(basis[2], diff) }; + + // Project the face vertices onto the plane of the face. + if (face->indices.size() > mProjVertices.size()) + { + mProjVertices.resize(face->indices.size()); + } + + // Project the remaining vertices. Vertex 0 is always the origin. + size_t numIndices = face->indices.size(); + mProjVertices[0] = Vector2::Zero(); + for (size_t k = 1; k < numIndices; ++k) + { + diff = mPoints[face->indices[k]] - V0; + mProjVertices[k][0] = Dot(basis[1], diff); + mProjVertices[k][1] = Dot(basis[2], diff); + } + + // Test whether the intersection point is in the convex polygon. + PointInPolygon2 PIP(static_cast(mProjVertices.size()), + &mProjVertices[0]); + + if (method == 1) + { + if (PIP.ContainsConvexOrderN(projIntersect)) + { + // The ray intersects the triangle. + odd = !odd; + } + } + else + { + if (PIP.ContainsConvexOrderLogN(projIntersect)) + { + // The ray intersects the triangle. + odd = !odd; + } + } + } + + if (odd) + { + insideCount++; + } + } + + return insideCount > mNumRays / 2; +} + +template +bool PointInPolyhedron3::ContainsS1(Vector3 const& p) const +{ + int insideCount = 0; + + FIQuery, Plane3> rpQuery; + Ray3 ray; + ray.origin = p; + + for (int j = 0; j < mNumRays; ++j) + { + ray.direction = mDirections[j]; + + // Zero intersections to start with. + bool odd = false; + + SimpleFace const* face = mSFaces; + for (int i = 0; i < mNumFaces; ++i, ++face) + { + // Attempt to quickly cull the triangle. + if (FastNoIntersect(ray, face->plane)) + { + continue; + } + + // Compute the ray-plane intersection. + auto result = rpQuery(ray, face->plane); + + // If you trigger this assertion, numerical round-off errors have + // led to a discrepancy between FastNoIntersect and the Find() + // result. + LogAssert(result.intersect, "Unexpected condition."); + + // Get a coordinate system for the plane. Use vertex 0 as the + // origin. + Vector3 const& V0 = mPoints[face->indices[0]]; + Vector3 basis[3]; + basis[0] = face->plane.normal; + ComputeOrthogonalComplement(1, basis); + + // Project the intersection onto the plane. + Vector3 diff = result.point - V0; + Vector2 projIntersect{ + Dot(basis[1], diff), Dot(basis[2], diff) }; + + // Project the face vertices onto the plane of the face. + if (face->indices.size() > mProjVertices.size()) + { + mProjVertices.resize(face->indices.size()); + } + + // Project the remaining vertices. Vertex 0 is always the origin. + size_t numIndices = face->indices.size(); + mProjVertices[0] = Vector2::Zero(); + for (size_t k = 1; k < numIndices; ++k) + { + diff = mPoints[face->indices[k]] - V0; + mProjVertices[k][0] = Dot(basis[1], diff); + mProjVertices[k][1] = Dot(basis[2], diff); + } + + // Test whether the intersection point is in the convex polygon. + PointInPolygon2 PIP(static_cast(mProjVertices.size()), + &mProjVertices[0]); + + if (PIP.Contains(projIntersect)) + { + // The ray intersects the triangle. + odd = !odd; + } + } + + if (odd) + { + insideCount++; + } + } + + return insideCount > mNumRays / 2; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContScribeCircle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContScribeCircle2.h new file mode 100644 index 000000000000..431b11ad9604 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContScribeCircle2.h @@ -0,0 +1,73 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +// All functions return 'true' if the circle has been constructed, 'false' +// otherwise (input points are linearly dependent). + +// Circle circumscribing triangle. +template +bool Circumscribe(Vector2 const& v0, Vector2 const& v1, + Vector2 const& v2, Circle2& circle); + +// Circle inscribing triangle. +template +bool Inscribe(Vector2 const& v0, Vector2 const& v1, + Vector2 const& v2, Circle2& circle); + + +template +bool Circumscribe(Vector2 const& v0, Vector2 const& v1, + Vector2 const& v2, Circle2& circle) +{ + Vector2 e10 = v1 - v0; + Vector2 e20 = v2 - v0; + Matrix2x2 A{ e10[0], e10[1], e20[0], e20[1] }; + Vector2 B{ ((Real)0.5)*Dot(e10, e10), ((Real)0.5)*Dot(e20, e20) }; + Vector2 solution; + if (LinearSystem::Solve(A, B, solution)) + { + circle.center = v0 + solution; + circle.radius = Length(solution); + return true; + } + return false; +} + +template +bool Inscribe(Vector2 const& v0, Vector2 const& v1, + Vector2 const& v2, Circle2& circle) +{ + Vector2 d10 = v1 - v0; + Vector2 d20 = v2 - v0; + Vector2 d21 = v2 - v1; + Real len10 = Length(d10); + Real len20 = Length(d20); + Real len21 = Length(d21); + Real perimeter = len10 + len20 + len21; + if (perimeter > (Real)0) + { + Real inv = ((Real)1) / perimeter; + len10 *= inv; + len20 *= inv; + len21 *= inv; + circle.center = len21*v0 + len20*v1 + len10*v2; + circle.radius = inv*std::abs(DotPerp(d10, d20)); + return circle.radius > (Real)0; + } + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContScribeCircle3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContScribeCircle3Sphere3.h new file mode 100644 index 000000000000..86291c4bd932 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContScribeCircle3Sphere3.h @@ -0,0 +1,189 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +// All functions return 'true' if circle/sphere has been constructed, +// 'false' otherwise (input points are linearly dependent). + +// Circle circumscribing a triangle in 3D. +template +bool Circumscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Circle3& circle); + +// Sphere circumscribing a tetrahedron. +template +bool Circumscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Vector3 const& v3, Sphere3& sphere); + +// Circle inscribing a triangle in 3D. +template +bool Inscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Circle3& circle); + +// Sphere inscribing tetrahedron. +template +bool Inscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Vector3 const& v3, Sphere3& sphere); + + +template +bool Circumscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Circle3& circle) +{ + Vector3 E02 = v0 - v2; + Vector3 E12 = v1 - v2; + Real e02e02 = Dot(E02, E02); + Real e02e12 = Dot(E02, E12); + Real e12e12 = Dot(E12, E12); + Real det = e02e02*e12e12 - e02e12*e02e12; + if (det != (Real)0) + { + Real halfInvDet = ((Real)0.5) / det; + Real u0 = halfInvDet*e12e12*(e02e02 - e02e12); + Real u1 = halfInvDet*e02e02*(e12e12 - e02e12); + Vector3 tmp = u0*E02 + u1*E12; + circle.center = v2 + tmp; + circle.normal = UnitCross(E02, E12); + circle.radius = Length(tmp); + return true; + } + return false; +} + +template +bool Circumscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Vector3 const& v3, Sphere3& sphere) +{ + Vector3 E10 = v1 - v0; + Vector3 E20 = v2 - v0; + Vector3 E30 = v3 - v0; + + Matrix3x3 A; + A.SetRow(0, E10); + A.SetRow(1, E20); + A.SetRow(2, E30); + + Vector3 B{ + ((Real)0.5)*Dot(E10, E10), + ((Real)0.5)*Dot(E20, E20), + ((Real)0.5)*Dot(E30, E30) }; + + Vector3 solution; + if (LinearSystem::Solve(A, B, solution)) + { + sphere.center = v0 + solution; + sphere.radius = Length(solution); + return true; + } + return false; +} + +template +bool Inscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Circle3& circle) +{ + // Edges. + Vector3 E0 = v1 - v0; + Vector3 E1 = v2 - v1; + Vector3 E2 = v0 - v2; + + // Plane normal. + circle.normal = Cross(E1, E0); + + // Edge normals within the plane. + Vector3 N0 = UnitCross(circle.normal, E0); + Vector3 N1 = UnitCross(circle.normal, E1); + Vector3 N2 = UnitCross(circle.normal, E2); + + Real a0 = Dot(N1, E0); + if (a0 == (Real)0) + { + return false; + } + + Real a1 = Dot(N2, E1); + if (a1 == (Real)0) + { + return false; + } + + Real a2 = Dot(N0, E2); + if (a2 == (Real)0) + { + return false; + } + + Real invA0 = ((Real)1) / a0; + Real invA1 = ((Real)1) / a1; + Real invA2 = ((Real)1) / a2; + + circle.radius = ((Real)1) / (invA0 + invA1 + invA2); + circle.center = circle.radius*(invA0*v0 + invA1*v1 + invA2*v2); + Normalize(circle.normal); + return true; +} + +template +bool Inscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Vector3 const& v3, Sphere3& sphere) +{ + // Edges. + Vector3 E10 = v1 - v0; + Vector3 E20 = v2 - v0; + Vector3 E30 = v3 - v0; + Vector3 E21 = v2 - v1; + Vector3 E31 = v3 - v1; + + // Normals. + Vector3 N0 = Cross(E31, E21); + Vector3 N1 = Cross(E20, E30); + Vector3 N2 = Cross(E30, E10); + Vector3 N3 = Cross(E10, E20); + + // Normalize the normals. + if (Normalize(N0) == (Real)0) + { + return false; + } + if (Normalize(N1) == (Real)0) + { + return false; + } + if (Normalize(N2) == (Real)0) + { + return false; + } + if (Normalize(N3) == (Real)0) + { + return false; + } + + Matrix3x3 A; + A.SetRow(0, N1 - N0); + A.SetRow(1, N2 - N0); + A.SetRow(2, N3 - N0); + Vector3 B{ (Real)0, (Real)0, -Dot(N3, E30) }; + Vector3 solution; + if (LinearSystem::Solve(A, B, solution)) + { + sphere.center = v3 + solution; + sphere.radius = std::abs(Dot(N0, solution)); + return true; + } + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContSphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContSphere3.h new file mode 100644 index 000000000000..bcf246fdcbf5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContSphere3.h @@ -0,0 +1,81 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/16) + +#pragma once + +#include +#include + +namespace gte +{ + // Compute the smallest bounding sphere whose center is the average of + // the input points. + template + bool GetContainer(int numPoints, Vector3 const* points, Sphere3& sphere) + { + sphere.center = points[0]; + for (int i = 1; i < numPoints; ++i) + { + sphere.center += points[i]; + } + sphere.center /= (Real)numPoints; + + sphere.radius = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - sphere.center; + Real radiusSqr = Dot(diff, diff); + if (radiusSqr > sphere.radius) + { + sphere.radius = radiusSqr; + } + } + + sphere.radius = std::sqrt(sphere.radius); + return true; + } + + // Test for containment of a point inside a sphere. + template + bool InContainer(Vector3 const& point, Sphere3 const& sphere) + { + Vector3 diff = point - sphere.center; + return Length(diff) <= sphere.radius; + } + + // Compute the smallest bounding sphere that contains the input spheres. + template + bool MergeContainers(Sphere3 const& sphere0, Sphere3 const& sphere1, Sphere3& merge) + { + Vector3 cenDiff = sphere1.center - sphere0.center; + Real lenSqr = Dot(cenDiff, cenDiff); + Real rDiff = sphere1.radius - sphere0.radius; + Real rDiffSqr = rDiff * rDiff; + + if (rDiffSqr >= lenSqr) + { + merge = (rDiff >= (Real)0 ? sphere1 : sphere0); + } + else + { + Real length = std::sqrt(lenSqr); + if (length > (Real)0) + { + Real coeff = (length + rDiff) / (((Real)2)*length); + merge.center = sphere0.center + coeff * cenDiff; + } + else + { + merge.center = sphere0.center; + } + + merge.radius = ((Real)0.5)*(length + sphere0.radius + sphere1.radius); + } + + return true; + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvertCoordinates.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvertCoordinates.h new file mode 100644 index 000000000000..c816d2f80c76 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvertCoordinates.h @@ -0,0 +1,353 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/16) + +#pragma once + +#include +#include + +// Convert points and transformations between two coordinate systems. +// The mathematics involves a change of basis. See the document +// http://www.geometrictools.com/Documentation/ConvertingBetweenCoordinateSystems.pdf +// for the details. Typical usage for 3D conversion is shown next. +// +// // Linear change of basis. The columns of U are the basis vectors for the +// // source coordinate system. A vector X = { x0, x1, x2 } in the source +// // coordinate system is represented by +// // X = x0*(1,0,0) + x1*(0,1,0) + x2*(0,0,1) +// // The Cartesian coordinates for the point are the combination of these +// // terms, +// // X = (x0, x1, x2) +// // The columns of V are the basis vectors for the target coordinate system. +// // A vector Y = { y0, y1, y2 } in the target coordinate system is +// // represented by +// // Y = y0*(1,0,0,0) + y1*(0,0,1) + y2*(0,1,0) +// // The Cartesian coordinates for the vector are the combination of these +// // terms, +// // Y = (y0, y2, y1) +// // The call Y = convert.UToV(X) computes y0, y1 and y2 so that the Cartesian +// // coordinates for X and for Y are the same. For example, +// // X = { 1.0, 2.0, 3.0 } +// // = 1.0*(1,0,0) + 2.0*(0,1,0) + 3.0*(0,0,1) +// // = (1, 2, 3) +// // Y = { 1.0, 3.0, 2.0 } +// // = 1.0*(1,0,0) + 3.0*(0,0,1) + 2.0*(0,1,0) +// // = (1, 2, 3) +// // X and Y represent the same vector (equal Cartesian coordinates) but have +// // different representations in the source and target coordinates. +// +// ConvertCoordinates<3, double> convert; +// Vector<3, double> X, Y, P0, P1, diff; +// Matrix<3, 3, double> U, V, A, B; +// bool isRHU, isRHV; +// U.SetCol(0, Vector3{1.0, 0.0, 0.0}); +// U.SetCol(1, Vector3{0.0, 1.0, 0.0}); +// U.SetCol(2, Vector3{0.0, 0.0, 1.0}); +// V.SetCol(0, Vector3{1.0, 0.0, 0.0}); +// V.SetCol(1, Vector3{0.0, 0.0, 1.0}); +// V.SetCol(2, Vector3{0.0, 1.0, 0.0}); +// convert(U, true, V, true); +// isRHU = convert.IsRightHandedU(); // true +// isRHV = convert.IsRightHandedV(); // false +// X = { 1.0, 2.0, 3.0 }; +// Y = convert.UToV(X); // { 1.0, 3.0, 2.0 } +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0 } +// Y = { 0.0, 1.0, 2.0 }; +// X = convert.VToU(Y); // { 0.0, 2.0, 1.0 } +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0 } +// double cs = 0.6, sn = 0.8; // cs*cs + sn*sn = 1 +// A.SetCol(0, Vector3{ c, s, 0.0}); +// A.SetCol(1, Vector3{ -s, c, 0.0}); +// A.SetCol(2, Vector3{0.0, 0.0, 1.0}); +// B = convert.UToV(A); +// // B.GetCol(0) = { c, 0, s} +// // B.GetCol(1) = { 0, 1, 0} +// // B.GetCol(2) = {-s, 0, c} +// X = A*X; // U is VOR +// Y = B*Y; // V is VOR +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0 } +// +// // Affine change of basis. The first three columns of U are the basis +// // vectors for the source coordinate system and must have last components +// // set to 0. The last column is the origin for that system and must have +// // last component set to 1. A point X = { x0, x1, x2, 1 } in the source +// // coordinate system is represented by +// // X = x0*(-1,0,0,0) + x1*(0,0,1,0) + x2*(0,-1,0,0) + 1*(1,2,3,1) +// // The Cartesian coordinates for the point are the combination of these +// // terms, +// // X = (-x0 + 1, -x2 + 2, x1 + 3, 1) +// // The first three columns of V are the basis vectors for the target +// // coordinate system and must have last components set to 0. The last +// // column is the origin for that system and must have last component set +// // to 1. A point Y = { y0, y1, y2, 1 } in the target coordinate system is +// // represented by +// // Y = y0*(0,1,0,0) + y1*(-1,0,0,0) + y2*(0,0,1,0) + 1*(4,5,6,1) +// // The Cartesian coordinates for the point are the combination of these +// // terms, +// // Y = (-y1 + 4, y0 + 5, y2 + 6, 1) +// // The call Y = convert.UToV(X) computes y0, y1 and y2 so that the Cartesian +// // coordinates for X and for Y are the same. For example, +// // X = { -1.0, 4.0, -3.0, 1.0 } +// // = -1.0*(-1,0,0,0) + 4.0*(0,0,1,0) - 3.0*(0,-1,0,0) + 1.0*(1,2,3,1) +// // = (2, 5, 7, 1) +// // Y = { 0.0, 2.0, 1.0, 1.0 } +// // = 0.0*(0,1,0,0) + 2.0*(-1,0,0,0) + 1.0*(0,0,1,0) + 1.0*(4,5,6,1) +// // = (2, 5, 7, 1) +// // X and Y represent the same point (equal Cartesian coordinates) but have +// // different representations in the source and target affine coordinates. +// +// ConvertCoordinates<4, double> convert; +// Vector<4, double> X, Y, P0, P1, diff; +// Matrix<4, 4, double> U, V, A, B; +// bool isRHU, isRHV; +// U.SetCol(0, Vector4{-1.0, 0.0, 0.0, 0.0}); +// U.SetCol(1, Vector4{0.0, 0.0, 1.0, 0.0}); +// U.SetCol(2, Vector4{0.0, -1.0, 0.0, 0.0}); +// U.SetCol(3, Vector4{1.0, 2.0, 3.0, 1.0}); +// V.SetCol(0, Vector4{0.0, 1.0, 0.0, 0.0}); +// V.SetCol(1, Vector4{-1.0, 0.0, 0.0, 0.0}); +// V.SetCol(2, Vector4{0.0, 0.0, 1.0, 0.0}); +// V.SetCol(3, Vector4{4.0, 5.0, 6.0, 1.0}); +// convert(U, true, V, false); +// isRHU = convert.IsRightHandedU(); // false +// isRHV = convert.IsRightHandedV(); // true +// X = { -1.0, 4.0, -3.0, 1.0 }; +// Y = convert.UToV(X); // { 0.0, 2.0, 1.0, 1.0 } +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0, 0 } +// Y = { 1.0, 2.0, 3.0, 1.0 }; +// X = convert.VToU(Y); // { -1.0, 6.0, -4.0, 1.0 } +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0, 0 } +// double c = 0.6, s = 0.8; // c*c + s*s = 1 +// A.SetCol(0, Vector4{ c, s, 0.0, 0.0}); +// A.SetCol(1, Vector4{ -s, c, 0.0, 0.0}); +// A.SetCol(2, Vector4{0.0, 0.0, 1.0, 0.0}); +// A.SetCol(3, Vector4{0.3, 1.0, -2.0, 1.0}); +// B = convert.UToV(A); +// // B.GetCol(0) = { 1, 0, 0, 0 } +// // B.GetCol(1) = { 0, c, s, 0 } +// // B.GetCol(2) = { 0, -s, c, 0 } +// // B.GetCol(3) = { 2.0, -0.9, -2.6, 1 } +// X = A*X; // U is VOR +// Y = Y*B; // V is VOL (not VOR) +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0, 0 } + +namespace gte +{ + template + class ConvertCoordinates + { + public: + // Construction of the change of basis matrix. The implementation + // supports both linear change of basis and affine change of basis. + ConvertCoordinates() + : + mIsVectorOnRightU(true), + mIsVectorOnRightV(true), + mIsRightHandedU(true), + mIsRightHandedV(true) + { + mC.MakeIdentity(); + mInverseC.MakeIdentity(); + } + + // Compute a change of basis between two coordinate systems. The return + // value is 'true' iff U and V are invertible. The matrix-vector + // multiplication conventions affect the conversion of matrix + // transformations. The Boolean inputs indicate how you want the matrices + // to be interpreted when applied as transformations of a vector. + bool operator()( + Matrix const& U, bool vectorOnRightU, + Matrix const& V, bool vectorOnRightV) + { + // Initialize in case of early exit. + mC.MakeIdentity(); + mInverseC.MakeIdentity(); + mIsVectorOnRightU = true; + mIsVectorOnRightV = true; + mIsRightHandedU = true; + mIsRightHandedV = true; + + Matrix inverseU; + Real determinantU; + bool invertibleU = GaussianElimination()(N, &U[0], &inverseU[0], + determinantU, nullptr, nullptr, nullptr, 0, nullptr); + if (!invertibleU) + { + return false; + } + + Matrix inverseV; + Real determinantV; + bool invertibleV = GaussianElimination()(N, &V[0], &inverseV[0], + determinantV, nullptr, nullptr, nullptr, 0, nullptr); + if (!invertibleV) + { + return false; + } + + mC = inverseU * V; + mInverseC = inverseV * U; + mIsVectorOnRightU = vectorOnRightU; + mIsVectorOnRightV = vectorOnRightV; + mIsRightHandedU = (determinantU > (Real)0); + mIsRightHandedV = (determinantV > (Real)0); + return true; + } + + // Member access. + inline Matrix const& GetC() const + { + return mC; + } + + inline Matrix const& GetInverseC() const + { + return mInverseC; + } + + inline bool IsVectorOnRightU() const + { + return mIsVectorOnRightU; + } + + inline bool IsVectorOnRightV() const + { + return mIsVectorOnRightV; + } + + inline bool IsRightHandedU() const + { + return mIsRightHandedU; + } + + inline bool IsRightHandedV() const + { + return mIsRightHandedV; + } + + // Convert points between coordinate systems. The names of the systems + // are U and V to make it clear which inputs of operator() they are + // associated with. The X vector stores coordinates for the U-system and + // the Y vector stores coordinates for the V-system. + + // Y = C^{-1}*X + inline Vector UToV(Vector const& X) const + { + return mInverseC * X; + } + + // X = C*Y + inline Vector VToU(Vector const& Y) const + { + return mC * Y; + } + + // Convert transformations between coordinate systems. The outputs are + // computed according to the tables shown before the function + // declarations. The superscript T denotes the transpose operator. + // vectorOnRightU = true: transformation is X' = A*X + // vectorOnRightU = false: transformation is (X')^T = X^T*A + // vectorOnRightV = true: transformation is Y' = B*Y + // vectorOnRightV = false: transformation is (Y')^T = Y^T*B + + // vectorOnRightU | vectorOnRightV | output + // ----------------+-----------------+--------------------- + // true | true | C^{-1} * A * C + // true | false | (C^{-1} * A * C)^T + // false | true | C^{-1} * A^T * C + // false | false | (C^{-1} * A^T * C)^T + Matrix UToV(Matrix const& A) const + { + Matrix product; + + if (mIsVectorOnRightU) + { + product = mInverseC * A * mC; + if (mIsVectorOnRightV) + { + return product; + } + else + { + return Transpose(product); + } + } + else + { + product = mInverseC * MultiplyATB(A, mC); + if (mIsVectorOnRightV) + { + return product; + } + else + { + return Transpose(product); + } + } + } + + // vectorOnRightU | vectorOnRightV | output + // ----------------+-----------------+--------------------- + // true | true | C * B * C^{-1} + // true | false | C * B^T * C^{-1} + // false | true | (C * B * C^{-1})^T + // false | false | (C * B^T * C^{-1})^T + Matrix VToU(Matrix const& B) const + { + // vectorOnRightU | vectorOnRightV | output + // ----------------+-----------------+--------------------- + // true | true | C * B * C^{-1} + // true | false | C * B^T * C^{-1} + // false | true | (C * B * C^{-1})^T + // false | false | (C * B^T * C^{-1})^T + Matrix product; + + if (mIsVectorOnRightV) + { + product = mC * B * mInverseC; + if (mIsVectorOnRightU) + { + return product; + } + else + { + return Transpose(product); + } + } + else + { + product = mC * MultiplyATB(B, mInverseC); + if (mIsVectorOnRightU) + { + return product; + } + else + { + return Transpose(product); + } + } + } + + private: + // C = U^{-1}*V, C^{-1} = V^{-1}*U + Matrix mC, mInverseC; + bool mIsVectorOnRightU, mIsVectorOnRightV; + bool mIsRightHandedU, mIsRightHandedV; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexHull2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexHull2.h new file mode 100644 index 000000000000..7432b77c0822 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexHull2.h @@ -0,0 +1,409 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +// Compute the convex hull of 2D points using a divide-and-conquer algorithm. +// This is an O(N log N) algorithm for N input points. The only way to ensure +// a correct result for the input vertices (assumed to be exact) is to choose +// ComputeType for exact rational arithmetic. You may use BSNumber. No +// divisions are performed in this computation, so you do not have to use +// BSRational. +// +// The worst-case choices of N for Real of type BSNumber or BSRational with +// integer storage UIntegerFP32 are listed in the next table. The numerical +// computations are encapsulated in PrimalQuery2::ToLineExtended. We +// recommend using only BSNumber, because no divisions are performed in the +// convex-hull computations. +// +// input type | compute type | N +// -----------+--------------+------ +// float | BSNumber | 18 +// double | BSNumber | 132 +// float | BSRational | 214 +// double | BSRational | 1587 + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class ConvexHull2 +{ +public: + // The class is a functor to support computing the convex hull of multiple + // data sets using the same class object. + ConvexHull2(); + + // The input is the array of points whose convex hull is required. The + // epsilon value is used to determine the intrinsic dimensionality of the + // vertices (d = 0, 1, or 2). When epsilon is positive, the determination + // is fuzzy--points approximately the same point, approximately on a + // line, or planar. The return value is 'true' if and only if the hull + // construction is successful. + bool operator()(int numPoints, Vector2 const* points, InputType epsilon); + + // Dimensional information. If GetDimension() returns 1, the points lie + // on a line P+t*D (fuzzy comparison when epsilon > 0). You can sort + // these if you need a polyline output by projecting onto the line each + // vertex X = P+t*D, where t = Dot(D,X-P). + inline InputType GetEpsilon() const; + inline int GetDimension() const; + inline Line2 const& GetLine() const; + + // Member access. + inline int GetNumPoints() const; + inline int GetNumUniquePoints() const; + inline Vector2 const* GetPoints() const; + inline PrimalQuery2 const& GetQuery() const; + + // The convex hull is a convex polygon whose vertices are listed in + // counterclockwise order. + inline std::vector const& GetHull() const; + +private: + // Support for divide-and-conquer. + void GetHull(int& i0, int& i1); + void Merge(int j0, int j1, int j2, int j3, int& i0, int& i1); + void GetTangent(int j0, int j1, int j2, int j3, int& i0, int& i1); + + // The epsilon value is used for fuzzy determination of intrinsic + // dimensionality. If the dimension is 0 or 1, the constructor returns + // early. The caller is responsible for retrieving the dimension and + // taking an alternate path should the dimension be smaller than 2. If + // the dimension is 0, the caller may as well treat all points[] as a + // single point, say, points[0]. If the dimension is 1, the caller can + // query for the approximating line and project points[] onto it for + // further processing. + InputType mEpsilon; + int mDimension; + Line2 mLine; + + // The array of points used for geometric queries. If you want to be + // certain of a correct result, choose ComputeType to be BSNumber. + std::vector> mComputePoints; + PrimalQuery2 mQuery; + + int mNumPoints; + int mNumUniquePoints; + Vector2 const* mPoints; + std::vector mMerged, mHull; +}; + + +template +ConvexHull2::ConvexHull2() + : + mEpsilon((InputType)0), + mDimension(0), + mLine(Vector2::Zero(), Vector2::Zero()), + mNumPoints(0), + mNumUniquePoints(0), + mPoints(nullptr) +{ +} + +template +bool ConvexHull2::operator()(int numPoints, + Vector2 const* points, InputType epsilon) +{ + mEpsilon = std::max(epsilon, (InputType)0); + mDimension = 0; + mLine.origin = Vector2::Zero(); + mLine.direction = Vector2::Zero(); + mNumPoints = numPoints; + mNumUniquePoints = 0; + mPoints = points; + mMerged.clear(); + mHull.clear(); + + int i, j; + if (mNumPoints < 3) + { + // ConvexHull2 should be called with at least three points. + return false; + } + + IntrinsicsVector2 info(mNumPoints, mPoints, mEpsilon); + if (info.dimension == 0) + { + // mDimension is 0 + return false; + } + + if (info.dimension == 1) + { + // The set is (nearly) collinear. + mDimension = 1; + mLine = Line2(info.origin, info.direction[0]); + return false; + } + + mDimension = 2; + + // Compute the points for the queries. + mComputePoints.resize(mNumPoints); + mQuery.Set(mNumPoints, &mComputePoints[0]); + for (i = 0; i < mNumPoints; ++i) + { + for (j = 0; j < 2; ++j) + { + mComputePoints[i][j] = points[i][j]; + } + } + + // Sort the points. + mHull.resize(mNumPoints); + for (i = 0; i < mNumPoints; ++i) + { + mHull[i] = i; + } + std::sort(mHull.begin(), mHull.end(), + [points](int i0, int i1) + { + if (points[i0][0] < points[i1][0]) { return true; } + if (points[i0][0] > points[i1][0]) { return false; } + return points[i0][1] < points[i1][1]; + } + ); + + // Remove duplicates. + auto newEnd = std::unique(mHull.begin(), mHull.end(), + [points](int i0, int i1) + { + return points[i0] == points[i1]; + } + ); + mHull.erase(newEnd, mHull.end()); + mNumUniquePoints = static_cast(mHull.size()); + + // Use a divide-and-conquer algorithm. The merge step computes the + // convex hull of two convex polygons. + mMerged.resize(mNumUniquePoints); + int i0 = 0, i1 = mNumUniquePoints - 1; + GetHull(i0, i1); + mHull.resize(i1 - i0 + 1); + return true; +} + +template inline +InputType ConvexHull2::GetEpsilon() const +{ + return mEpsilon; +} + +template inline +int ConvexHull2::GetDimension() const +{ + return mDimension; +} + +template inline +Line2 const& ConvexHull2::GetLine() const +{ + return mLine; +} + +template inline +int ConvexHull2::GetNumPoints() const +{ + return mNumPoints; +} + +template inline +int ConvexHull2::GetNumUniquePoints() const +{ + return mNumUniquePoints; +} + +template inline +Vector2 const* ConvexHull2::GetPoints() const +{ + return mPoints; +} + +template inline +PrimalQuery2 const& ConvexHull2::GetQuery() const +{ + return mQuery; +} + +template inline +std::vector const& ConvexHull2::GetHull() const +{ + return mHull; +} + +template inline +void ConvexHull2::GetHull(int& i0, int& i1) +{ + int numVertices = i1 - i0 + 1; + if (numVertices > 1) + { + // Compute the middle index of input range. + int mid = (i0 + i1) / 2; + + // Compute the hull of subsets (mid-i0+1 >= i1-mid). + int j0 = i0, j1 = mid, j2 = mid + 1, j3 = i1; + GetHull(j0, j1); + GetHull(j2, j3); + + // Merge the convex hulls into a single convex hull. + Merge(j0, j1, j2, j3, i0, i1); + } + // else: The convex hull is a single point. +} + +template inline +void ConvexHull2::Merge(int j0, int j1, int j2, int j3, + int& i0, int& i1) +{ + // Subhull0 is to the left of subhull1 because of the initial sorting of + // the points by x-components. We need to find two mutually visible + // points, one on the left subhull and one on the right subhull. + int size0 = j1 - j0 + 1; + int size1 = j3 - j2 + 1; + + int i; + Vector2 p; + + // Find the right-most point of the left subhull. + Vector2 pmax0 = mComputePoints[mHull[j0]]; + int imax0 = j0; + for (i = j0 + 1; i <= j1; ++i) + { + p = mComputePoints[mHull[i]]; + if (pmax0 < p) + { + pmax0 = p; + imax0 = i; + } + } + + // Find the left-most point of the right subhull. + Vector2 pmin1 = mComputePoints[mHull[j2]]; + int imin1 = j2; + for (i = j2 + 1; i <= j3; ++i) + { + p = mComputePoints[mHull[i]]; + if (p < pmin1) + { + pmin1 = p; + imin1 = i; + } + } + + // Get the lower tangent to hulls (LL = lower-left, LR = lower-right). + int iLL = imax0, iLR = imin1; + GetTangent(j0, j1, j2, j3, iLL, iLR); + + // Get the upper tangent to hulls (UL = upper-left, UR = upper-right). + int iUL = imax0, iUR = imin1; + GetTangent(j2, j3, j0, j1, iUR, iUL); + + // Construct the counterclockwise-ordered merged-hull vertices. + int k; + int numMerged = 0; + + i = iUL; + for (k = 0; k < size0; ++k) + { + mMerged[numMerged++] = mHull[i]; + if (i == iLL) + { + break; + } + i = (i < j1 ? i + 1 : j0); + } + LogAssert(k < size0, "Unexpected condition."); + + i = iLR; + for (k = 0; k < size1; ++k) + { + mMerged[numMerged++] = mHull[i]; + if (i == iUR) + { + break; + } + i = (i < j3 ? i + 1 : j2); + } + LogAssert(k < size1, "Unexpected condition."); + + int next = j0; + for (k = 0; k < numMerged; ++k) + { + mHull[next] = mMerged[k]; + ++next; + } + + i0 = j0; + i1 = next - 1; +} + +template inline +void ConvexHull2::GetTangent(int j0, int j1, int j2, int j3, + int& i0, int& i1) +{ + // In theory the loop terminates in a finite number of steps, but the + // upper bound for the loop variable is used to trap problems caused by + // floating point roundoff errors that might lead to an infinite loop. + + int size0 = j1 - j0 + 1; + int size1 = j3 - j2 + 1; + int const imax = size0 + size1; + int i, iLm1, iRp1; + Vector2 L0, L1, R0, R1; + + for (i = 0; i < imax; i++) + { + // Get the endpoints of the potential tangent. + L1 = mComputePoints[mHull[i0]]; + R0 = mComputePoints[mHull[i1]]; + + // Walk along the left hull to find the point of tangency. + if (size0 > 1) + { + iLm1 = (i0 > j0 ? i0 - 1 : j1); + L0 = mComputePoints[mHull[iLm1]]; + auto order = mQuery.ToLineExtended(R0, L0, L1); + if (order == PrimalQuery2::ORDER_NEGATIVE + || order == PrimalQuery2::ORDER_COLLINEAR_RIGHT) + { + i0 = iLm1; + continue; + } + } + + // Walk along right hull to find the point of tangency. + if (size1 > 1) + { + iRp1 = (i1 < j3 ? i1 + 1 : j2); + R1 = mComputePoints[mHull[iRp1]]; + auto order = mQuery.ToLineExtended(L1, R0, R1); + if (order == PrimalQuery2::ORDER_NEGATIVE + || order == PrimalQuery2::ORDER_COLLINEAR_LEFT) + { + i1 = iRp1; + continue; + } + } + + // The tangent segment has been found. + break; + } + + // Detect an "infinite loop" caused by floating point round-off errors. + LogAssert(i < imax, "Unexpected condition."); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexHull3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexHull3.h new file mode 100644 index 000000000000..4dc95f347090 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexHull3.h @@ -0,0 +1,430 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +// Compute the convex hull of 3D points using incremental insertion. The only +// way to ensure a correct result for the input vertices (assumed to be exact) +// is to choose ComputeType for exact rational arithmetic. You may use +// BSNumber. No divisions are performed in this computation, so you do not +// have to use BSRational. +// +// The worst-case choices of N for Real of type BSNumber or BSRational with +// integer storage UIntegerFP32 are listed in the next table. The numerical +// computations are encapsulated in PrimalQuery3::ToPlane. We recommend +// using only BSNumber, because no divisions are performed in the convex-hull +// computations. +// +// input type | compute type | N +// -----------+--------------+------ +// float | BSNumber | 27 +// double | BSNumber | 197 +// float | BSRational | 2882 +// double | BSRational | 21688 + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class ConvexHull3 +{ +public: + // The class is a functor to support computing the convex hull of multiple + // data sets using the same class object. For multithreading in Update, + // choose 'numThreads' subject to the constraints + // 1 <= numThreads <= std::thread::hardware_concurrency(). + ConvexHull3(unsigned int numThreads = 1); + + // The input is the array of points whose convex hull is required. The + // epsilon value is used to determine the intrinsic dimensionality of the + // vertices (d = 0, 1, 2, or 3). When epsilon is positive, the + // determination is fuzzy--points approximately the same point, + // approximately on a line, approximately planar, or volumetric. + bool operator()(int numPoints, Vector3 const* points, + InputType epsilon); + + // Dimensional information. If GetDimension() returns 1, the points lie + // on a line P+t*D (fuzzy comparison when epsilon > 0). You can sort + // these if you need a polyline output by projecting onto the line each + // vertex X = P+t*D, where t = Dot(D,X-P). If GetDimension() returns 2, + // the points line on a plane P+s*U+t*V (fuzzy comparison when + // epsilon > 0). You can project each point X = P+s*U+t*V, where + // s = Dot(U,X-P) and t = Dot(V,X-P), then apply ConvexHull2 to the (s,t) + // tuples. + inline InputType GetEpsilon() const; + inline int GetDimension() const; + inline Line3 const& GetLine() const; + inline Plane3 const& GetPlane() const; + + // Member access. + inline int GetNumPoints() const; + inline int GetNumUniquePoints() const; + inline Vector3 const* GetPoints() const; + inline PrimalQuery3 const& GetQuery() const; + + // The convex hull is a convex polyhedron with triangular faces. + inline std::vector> const& GetHullUnordered() const; + ETManifoldMesh const& GetHullMesh() const; + +private: + // Support for incremental insertion. + void Update(int i); + + // The epsilon value is used for fuzzy determination of intrinsic + // dimensionality. If the dimension is 0, 1, or 2, the constructor + // returns early. The caller is responsible for retrieving the dimension + // and taking an alternate path should the dimension be smaller than 3. + // If the dimension is 0, the caller may as well treat all points[] as a + // single point, say, points[0]. If the dimension is 1, the caller can + // query for the approximating line and project points[] onto it for + // further processing. If the dimension is 2, the caller can query for + // the approximating plane and project points[] onto it for further + // processing. + InputType mEpsilon; + int mDimension; + Line3 mLine; + Plane3 mPlane; + + // The array of points used for geometric queries. If you want to be + // certain of a correct result, choose ComputeType to be BSNumber. + std::vector> mComputePoints; + PrimalQuery3 mQuery; + + int mNumPoints; + int mNumUniquePoints; + Vector3 const* mPoints; + std::vector> mHullUnordered; + mutable ETManifoldMesh mHullMesh; + unsigned int mNumThreads; +}; + + +template +ConvexHull3::ConvexHull3(unsigned int numThreads) + : + mEpsilon((InputType)0), + mDimension(0), + mLine(Vector3::Zero(), Vector3::Zero()), + mPlane(Vector3::Zero(), (InputType)0), + mNumPoints(0), + mNumUniquePoints(0), + mPoints(nullptr), + mNumThreads(numThreads) +{ +} + +template +bool ConvexHull3::operator()(int numPoints, + Vector3 const* points, InputType epsilon) +{ + mEpsilon = std::max(epsilon, (InputType)0); + mDimension = 0; + mLine.origin = Vector3::Zero(); + mLine.direction = Vector3::Zero(); + mPlane.normal = Vector3::Zero(); + mPlane.constant = (InputType)0; + mNumPoints = numPoints; + mNumUniquePoints = 0; + mPoints = points; + mHullUnordered.clear(); + mHullMesh.Clear(); + + int i, j; + if (mNumPoints < 4) + { + // ConvexHull3 should be called with at least four points. + return false; + } + + IntrinsicsVector3 info(mNumPoints, mPoints, mEpsilon); + if (info.dimension == 0) + { + // The set is (nearly) a point. + return false; + } + + if (info.dimension == 1) + { + // The set is (nearly) collinear. + mDimension = 1; + mLine = Line3(info.origin, info.direction[0]); + return false; + } + + if (info.dimension == 2) + { + // The set is (nearly) coplanar. + mDimension = 2; + mPlane = Plane3(UnitCross(info.direction[0], + info.direction[1]), info.origin); + return false; + } + + mDimension = 3; + + // Compute the vertices for the queries. + mComputePoints.resize(mNumPoints); + mQuery.Set(mNumPoints, &mComputePoints[0]); + for (i = 0; i < mNumPoints; ++i) + { + for (j = 0; j < 3; ++j) + { + mComputePoints[i][j] = points[i][j]; + } + } + + // Insert the faces of the (nondegenerate) tetrahedron constructed by the + // call to GetInformation. + if (!info.extremeCCW) + { + std::swap(info.extreme[2], info.extreme[3]); + } + + mHullUnordered.push_back(TriangleKey(info.extreme[1], + info.extreme[2], info.extreme[3])); + mHullUnordered.push_back(TriangleKey(info.extreme[0], + info.extreme[3], info.extreme[2])); + mHullUnordered.push_back(TriangleKey(info.extreme[0], + info.extreme[1], info.extreme[3])); + mHullUnordered.push_back(TriangleKey(info.extreme[0], + info.extreme[2], info.extreme[1])); + + // Incrementally update the hull. The set of processed points is + // maintained to eliminate duplicates, either in the original input + // points or in the points obtained by snap rounding. + std::set> processed; + for (i = 0; i < 4; ++i) + { + processed.insert(points[info.extreme[i]]); + } + for (i = 0; i < mNumPoints; ++i) + { + if (processed.find(points[i]) == processed.end()) + { + Update(i); + processed.insert(points[i]); + } + } + mNumUniquePoints = static_cast(processed.size()); + return true; +} + +template inline +InputType ConvexHull3::GetEpsilon() const +{ + return mEpsilon; +} + +template inline +int ConvexHull3::GetDimension() const +{ + return mDimension; +} + +template inline +Line3 const& ConvexHull3::GetLine() const +{ + return mLine; +} + +template inline +Plane3 const& ConvexHull3::GetPlane() const +{ + return mPlane; +} + +template inline +int ConvexHull3::GetNumPoints() const +{ + return mNumPoints; +} + +template inline +int ConvexHull3::GetNumUniquePoints() const +{ + return mNumUniquePoints; +} + +template inline +Vector3 const* ConvexHull3::GetPoints() const +{ + return mPoints; +} + +template inline +PrimalQuery3 const& ConvexHull3::GetQuery() const +{ + return mQuery; +} + +template inline +std::vector> const& ConvexHull3::GetHullUnordered() const +{ + return mHullUnordered; +} + +template +ETManifoldMesh const& ConvexHull3::GetHullMesh() const +{ + // Create the mesh only on demand. + if (mHullMesh.GetTriangles().size() == 0) + { + for (auto const& tri : mHullUnordered) + { + mHullMesh.Insert(tri.V[0], tri.V[1], tri.V[2]); + } + } + + return mHullMesh; +} + +template +void ConvexHull3::Update(int i) +{ + // The terminator that separates visible faces from nonvisible faces is + // constructed by this code. Visible faces for the incoming hull are + // removed, and the boundary of that set of triangles is the terminator. + // New visible faces are added using the incoming point and the edges of + // the terminator. + // + // A simple algorithm for computing terminator edges is the following. + // Back-facing triangles are located and the three edges are processed. + // The first time an edge is visited, insert it into the terminator. If + // it is visited a second time, the edge is removed because it is shared + // by another back-facing triangle and, therefore, cannot be a terminator + // edge. After visiting all back-facing triangles, the only remaining + // edges in the map are the terminator edges. + // + // The order of vertices of an edge is important for adding new faces with + // the correct vertex winding. However, the edge "toggle" (insert edge, + // remove edge) should use edges with unordered vertices, because the + // edge shared by one triangle has opposite ordering relative to that of + // the other triangle. The map uses unordered edges as the keys but + // stores the ordered edge as the value. This avoids having to look up + // an edge twice in a map with ordered edge keys. + + unsigned int numFaces = static_cast(mHullUnordered.size()); + std::vector queryResult(numFaces); + if (mNumThreads > 1 && numFaces >= mNumThreads) + { + // Partition the data for multiple threads. + unsigned int numFacesPerThread = numFaces / mNumThreads; + std::vector jmin(mNumThreads), jmax(mNumThreads); + for (unsigned int t = 0; t < mNumThreads; ++t) + { + jmin[t] = t * numFacesPerThread; + jmax[t] = jmin[t] + numFacesPerThread - 1; + } + jmax[mNumThreads - 1] = numFaces - 1; + + // Execute the point-plane queries in multiple threads. + std::vector process(mNumThreads); + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t] = std::thread([this, i, t, &jmin, &jmax, + &queryResult]() + { + for (unsigned int j = jmin[t]; j <= jmax[t]; ++j) + { + TriangleKey const& tri = mHullUnordered[j]; + queryResult[j] = mQuery.ToPlane(i, tri.V[0], tri.V[1], tri.V[2]); + } + }); + } + + // Wait for all threads to finish. + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t].join(); + } + } + else + { + unsigned int j = 0; + for (auto const& tri : mHullUnordered) + { + queryResult[j++] = mQuery.ToPlane(i, tri.V[0], tri.V[1], tri.V[2]); + } + } + + std::map, std::pair> terminator; + std::vector> backFaces; + bool existsFrontFacingTriangle = false; + unsigned int j = 0; + for (auto const& tri : mHullUnordered) + { + if (queryResult[j++] <= 0) + { + // The triangle is back facing. These include triangles that + // are coplanar with the incoming point. + backFaces.push_back(tri); + + // The current hull is a 2-manifold watertight mesh. The + // terminator edges are those shared with a front-facing triangle. + // The first time an edge of a back-facing triangle is visited, + // insert it into the terminator. If it is visited a second time, + // the edge is removed because it is shared by another back-facing + // triangle. After all back-facing triangles are visited, the + // only remaining edges are shared by a single back-facing + // triangle, which makes them terminator edges. + for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++) + { + int v0 = tri.V[j0], v1 = tri.V[j1]; + EdgeKey edge(v0, v1); + auto iter = terminator.find(edge); + if (iter == terminator.end()) + { + // The edge is visited for the first time. + terminator.insert(std::make_pair(edge, std::make_pair(v0, v1))); + } + else + { + // The edge is visited for the second time. + terminator.erase(edge); + } + } + } + else + { + // If there are no strictly front-facing triangles, then the + // incoming point is inside or on the convex hull. If we get + // to this code, then the point is truly outside and we can + // update the hull. + existsFrontFacingTriangle = true; + } + } + + if (!existsFrontFacingTriangle) + { + // The incoming point is inside or on the current hull, so no + // update of the hull is necessary. + return; + } + + // The updated hull contains the triangles not visible to the incoming + // point. + mHullUnordered = backFaces; + + // Insert the triangles formed by the incoming point and the terminator + // edges. + for (auto const& edge : terminator) + { + mHullUnordered.push_back(TriangleKey(i, edge.second.second, edge.second.first)); + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexPolyhedron3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexPolyhedron3.h new file mode 100644 index 000000000000..95194e3dac6f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexPolyhedron3.h @@ -0,0 +1,113 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.0 (2016/11/25) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class ConvexPolyhedron3 +{ +public: + // Construction. The convex polyhedra represented by this class has + // triangle faces that are counterclockwise ordered when viewed from + // outside the polyhedron. No attempt is made to verify that the + // polyhedron is convex; the caller is responsible for enforcing this. + // The constructors move (not copy!) the input arrays. The constructor + // succeeds when the number of vertices is at least 4 and the number of + // indices is at least 12. If the constructor fails, no move occurs + // and the member arrays have no elements. + // + // To support geometric algorithms that are formulated using convex + // quadratic programming (such as computing the distance from a point to + // a convex polyhedron), it is necessary to know the planes of the faces + // and an axis-aligned bounding box. If you want either the faces or the + // box, pass 'true' to the appropriate parameters. When planes are + // generated, the normals are not created to be unit length in order to + // support queries using exact rational arithmetic. If a normal to a + // face is N = (n0,n1,n2) and V is a vertex of the face, the plane is + // Dot(N,X-V) = 0 and is stored as Dot(n0,n1,n2,-Dot(N,V)). The normals + // are computed to be outer pointing. + ConvexPolyhedron3(); + ConvexPolyhedron3(std::vector>&& inVertices, std::vector&& inIndices, + bool wantPlanes, bool wantAlignedBox); + + // If you modifty the vertices or indices and you want the new face + // planes or aligned box computed, call these functions. + void GeneratePlanes(); + void GenerateAlignedBox(); + + std::vector> vertices; + std::vector indices; + std::vector> planes; + AlignedBox3 alignedBox; +}; + + +template +ConvexPolyhedron3::ConvexPolyhedron3() +{ +} + +template +ConvexPolyhedron3::ConvexPolyhedron3(std::vector>&& inVertices, + std::vector&& inIndices, bool wantPlanes, bool wantAlignedBox) +{ + if (inVertices.size() >= 4 && inIndices.size() >= 12) + { + vertices = std::move(inVertices); + indices = std::move(inIndices); + + if (wantPlanes) + { + GeneratePlanes(); + } + + if (wantAlignedBox) + { + GenerateAlignedBox(); + } + } +} + +template +void ConvexPolyhedron3::GeneratePlanes() +{ + if (vertices.size() > 0 && indices.size() > 0) + { + uint32_t const numTriangles = static_cast(indices.size()) / 3; + planes.resize(numTriangles); + for (uint32_t t = 0, i = 0; t < numTriangles; ++t) + { + Vector3 V0 = vertices[indices[i++]]; + Vector3 V1 = vertices[indices[i++]]; + Vector3 V2 = vertices[indices[i++]]; + Vector3 E1 = V1 - V0; + Vector3 E2 = V2 - V0; + Vector3 N = Cross(E1, E2); + planes[t] = HLift(N, -Dot(N, V0)); + } + } +} + +template +void ConvexPolyhedron3::GenerateAlignedBox() +{ + if (vertices.size() > 0 && indices.size() > 0) + { + ComputeExtremes(static_cast(vertices.size()), vertices.data(), + alignedBox.min, alignedBox.max); + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCosEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCosEstimate.h new file mode 100644 index 000000000000..ed2d326b178d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCosEstimate.h @@ -0,0 +1,164 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Minimax polynomial approximations to cos(x). The polynomial p(x) of +// degree D has only even-power terms, is required to have constant term 1, +// and p(pi/2) = cos(pi/2) = 0. It minimizes the quantity +// maximum{|cos(x) - p(x)| : x in [-pi/2,pi/2]} over all polynomials of +// degree D subject to the constraints mentioned. + +namespace gte +{ + +template +class CosEstimate +{ +public: + // The input constraint is x in [-pi/2,pi/2]. For example, + // float x; // in [-pi/2,pi/2] + // float result = CosEstimate::Degree<4>(x); + template + inline static Real Degree(Real x); + + // The input x can be any real number. Range reduction is used to + // generate a value y in [-pi/2,pi/2] and a sign s for which + // cos(y) = s*cos(x). For example, + // float x; // x any real number + // float result = CosEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<2>, Real x); + inline static Real Evaluate(degree<4>, Real x); + inline static Real Evaluate(degree<6>, Real x); + inline static Real Evaluate(degree<8>, Real x); + inline static Real Evaluate(degree<10>, Real x); + + // Support for range reduction. + inline static void Reduce(Real x, Real& y, Real& sign); +}; + + +template +template +inline Real CosEstimate::Degree(Real x) +{ + return Evaluate(degree(), x); +} + +template +template +inline Real CosEstimate::DegreeRR(Real x) +{ + Real y, sign; + Reduce(x, y, sign); + Real poly = sign * Degree(y); + return poly; +} + +template +inline Real CosEstimate::Evaluate(degree<2>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_COS_DEG2_C1; + poly = (Real)GTE_C_COS_DEG2_C0 + poly * xsqr; + return poly; +} + +template +inline Real CosEstimate::Evaluate(degree<4>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_COS_DEG4_C2; + poly = (Real)GTE_C_COS_DEG4_C1 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG4_C0 + poly * xsqr; + return poly; +} + +template +inline Real CosEstimate::Evaluate(degree<6>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_COS_DEG6_C3; + poly = (Real)GTE_C_COS_DEG6_C2 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG6_C1 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG6_C0 + poly * xsqr; + return poly; +} + +template +inline Real CosEstimate::Evaluate(degree<8>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_COS_DEG8_C4; + poly = (Real)GTE_C_COS_DEG8_C3 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG8_C2 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG8_C1 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG8_C0 + poly * xsqr; + return poly; +} + +template +inline Real CosEstimate::Evaluate(degree<10>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_COS_DEG10_C5; + poly = (Real)GTE_C_COS_DEG10_C4 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG10_C3 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG10_C2 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG10_C1 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG10_C0 + poly * xsqr; + return poly; +} + +template +inline void CosEstimate::Reduce(Real x, Real& y, Real& sign) +{ + // Map x to y in [-pi,pi], x = 2*pi*quotient + remainder. + Real quotient = (Real)GTE_C_INV_TWO_PI * x; + if (x >= (Real)0) + { + quotient = (Real)((int)(quotient + (Real)0.5)); + } + else + { + quotient = (Real)((int)(quotient - (Real)0.5)); + } + y = x - (Real)GTE_C_TWO_PI * quotient; + + // Map y to [-pi/2,pi/2] with cos(y) = sign*cos(x). + if (y > (Real)GTE_C_HALF_PI) + { + y = (Real)GTE_C_PI - y; + sign = (Real)-1; + } + else if (y < (Real)-GTE_C_HALF_PI) + { + y = (Real)-GTE_C_PI - y; + sign = (Real)-1; + } + else + { + sign = (Real)1; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCubicRootsQR.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCubicRootsQR.h new file mode 100644 index 000000000000..0d680782f121 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCubicRootsQR.h @@ -0,0 +1,241 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +// An implementation of the QR algorithm described in "Matrix Computations, +// 2nd edition" by G. H. Golub and C. F. Van Loan, The Johns Hopkins +// University Press, Baltimore MD, Fourth Printing 1993. In particular, +// the implementation is based on Chapter 7 (The Unsymmetric Eigenvalue +// Problem), Section 7.5 (The Practical QR Algorithm). The algorithm is +// specialized for the companion matrix associated with a cubic polynomial. + +namespace gte +{ + +template +class CubicRootsQR +{ +public: + typedef std::array, 3> Matrix; + + // Solve p(x) = c0 + c1 * x + c2 * x^2 + x^3 = 0. + uint32_t operator() (uint32_t maxIterations, Real c0, Real c1, Real c2, + uint32_t& numRoots, std::array& roots); + + // Compute the real eigenvalues of the upper Hessenberg matrix A. The + // matrix is modified by in-place operations, so if you need to remember + // A, you must make your own copy before calling this function. + uint32_t operator() (uint32_t maxIterations, Matrix& A, + uint32_t& numRoots, std::array& roots); + +private: + void DoIteration(std::array const& V, Matrix& A); + + template + std::array House(std::array const& X); + + template + void RowHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A); + + template + void ColHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A); + + void GetQuadraticRoots(int i0, int i1, Matrix const & A, + uint32_t& numRoots, std::array& roots); +}; + + +template +uint32_t CubicRootsQR::operator() (uint32_t maxIterations, Real c0, Real c1, Real c2, + uint32_t& numRoots, std::array& roots) +{ + // Create the companion matrix for the polynomial. The matrix is in upper + // Hessenberg form. + Matrix A; + A[0][0] = (Real)0; + A[0][1] = (Real)0; + A[0][2] = -c0; + A[1][0] = (Real)1; + A[1][1] = (Real)0; + A[1][2] = -c1; + A[2][0] = (Real)0; + A[2][1] = (Real)1; + A[2][2] = -c2; + + // Avoid the QR-cycle when c1 = c2 = 0 and avoid the slow convergence + // when c1 and c2 are nearly zero. + std::array V{ + (Real)1, + (Real)0.36602540378443865, + (Real)0.36602540378443865 }; + DoIteration(V, A); + + return operator()(maxIterations, A, numRoots, roots); +} + +template +uint32_t CubicRootsQR::operator() (uint32_t maxIterations, Matrix& A, + uint32_t& numRoots, std::array& roots) +{ + numRoots = 0; + std::fill(roots.begin(), roots.end(), (Real)0); + + for (uint32_t numIterations = 0; numIterations < maxIterations; ++numIterations) + { + // Apply a Francis QR iteration. + Real tr = A[1][1] + A[2][2]; + Real det = A[1][1] * A[2][2] - A[1][2] * A[2][1]; + std::array X{ + A[0][0] * A[0][0] + A[0][1] * A[1][0] - tr * A[0][0] + det, + A[1][0] * (A[0][0] + A[1][1] - tr), + A[1][0] * A[2][1] }; + std::array V = House<3>(X); + DoIteration(V, A); + + // Test for uncoupling of A. + Real tr01 = A[0][0] + A[1][1]; + if (tr01 + A[1][0] == tr01) + { + numRoots = 1; + roots[0] = A[0][0]; + GetQuadraticRoots(1, 2, A, numRoots, roots); + return numIterations; + } + + Real tr12 = A[1][1] + A[2][2]; + if (tr12 + A[2][1] == tr12) + { + numRoots = 1; + roots[0] = A[2][2]; + GetQuadraticRoots(0, 1, A, numRoots, roots); + return numIterations; + } + } + return maxIterations; +} + +template +void CubicRootsQR::DoIteration(std::array const& V, Matrix& A) +{ + Real multV = ((Real)-2) / (V[0] * V[0] + V[1] * V[1] + V[2] * V[2]); + std::array MV{ multV * V[0], multV * V[1], multV * V[2] }; + RowHouse<3>(0, 2, 0, 2, V, MV, A); + ColHouse<3>(0, 2, 0, 2, V, MV, A); + + std::array Y{ A[1][0], A[2][0] }; + std::array W = House<2>(Y); + Real multW = ((Real)-2) / (W[0] * W[0] + W[1] * W[1]); + std::array MW{ multW * W[0], multW * W[1] }; + RowHouse<2>(1, 2, 0, 2, W, MW, A); + ColHouse<2>(0, 2, 1, 2, W, MW, A); +} + +template +template +std::array CubicRootsQR::House(std::array const & X) +{ + std::array V; + Real length = (Real)0; + for (int i = 0; i < N; ++i) + { + length += X[i] * X[i]; + } + length = std::sqrt(length); + if (length != (Real)0) + { + Real sign = (X[0] >= (Real)0 ? (Real)1 : (Real)-1); + Real denom = X[0] + sign * length; + for (int i = 1; i < N; ++i) + { + V[i] = X[i] / denom; + } + } + V[0] = (Real)1; + return V; +} + +template +template +void CubicRootsQR::RowHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A) +{ + std::array W; // only elements cmin through cmax are used + + for (int c = cmin; c <= cmax; ++c) + { + W[c] = (Real)0; + for (int r = rmin, k = 0; r <= rmax; ++r, ++k) + { + W[c] += V[k] * A[r][c]; + } + } + + for (int r = rmin, k = 0; r <= rmax; ++r, ++k) + { + for (int c = cmin; c <= cmax; ++c) + { + A[r][c] += MV[k] * W[c]; + } + } +} + +template +template +void CubicRootsQR::ColHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A) +{ + std::array W; // only elements rmin through rmax are used + + for (int r = rmin; r <= rmax; ++r) + { + W[r] = (Real)0; + for (int c = cmin, k = 0; c <= cmax; ++c, ++k) + { + W[r] += V[k] * A[r][c]; + } + } + + for (int r = rmin; r <= rmax; ++r) + { + for (int c = cmin, k = 0; c <= cmax; ++c, ++k) + { + A[r][c] += W[r] * MV[k]; + } + } +} + +template +void CubicRootsQR::GetQuadraticRoots(int i0, int i1, Matrix const& A, + uint32_t& numRoots, std::array& roots) +{ + // Solve x^2 - t * x + d = 0, where t is the trace and d is the + // determinant of the 2x2 matrix defined by indices i0 and i1. The + // discriminant is D = (t/2)^2 - d. When D >= 0, the roots are real + // values t/2 - sqrt(D) and t/2 + sqrt(D). To avoid potential numerical + // issues with subtractive cancellation, the roots are computed as + // r0 = t/2 + sign(t/2)*sqrt(D), r1 = trace - r0. + Real trace = A[i0][i0] + A[i1][i1]; + Real halfTrace = trace * (Real)0.5; + Real determinant = A[i0][i0] * A[i1][i1] - A[i0][i1] * A[i1][i0]; + Real discriminant = halfTrace * halfTrace - determinant; + if (discriminant >= (Real)0) + { + Real sign = (trace >= (Real)0 ? (Real)1 : (Real)-1); + Real root = halfTrace + sign * std::sqrt(discriminant); + roots[numRoots++] = root; + roots[numRoots++] = trace - root; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCylinder3.h new file mode 100644 index 000000000000..a60e8c559242 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCylinder3.h @@ -0,0 +1,123 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The cylinder axis is a line. The origin of the cylinder is chosen to be +// the line origin. The cylinder wall is at a distance R units from the axis. +// An infinite cylinder has infinite height. A finite cylinder has center C +// at the line origin and has a finite height H. The segment for the finite +// cylinder has endpoints C-(H/2)*D and C+(H/2)*D where D is a unit-length +// direction of the line. + +namespace gte +{ + +template +class Cylinder3 +{ +public: + // Construction and destruction. The default constructor sets axis to + // (0,0,1), radius to 1, and height to 1. + Cylinder3(); + Cylinder3(Line3 const& inAxis, Real inRadius, Real inHeight); + + Line3 axis; + Real radius, height; + +public: + // Comparisons to support sorted containers. + bool operator==(Cylinder3 const& cylinder) const; + bool operator!=(Cylinder3 const& cylinder) const; + bool operator< (Cylinder3 const& cylinder) const; + bool operator<=(Cylinder3 const& cylinder) const; + bool operator> (Cylinder3 const& cylinder) const; + bool operator>=(Cylinder3 const& cylinder) const; +}; + + +template +Cylinder3::Cylinder3() + : + axis(Line3()), + radius((Real)1), + height((Real)1) +{ +} + +template +Cylinder3::Cylinder3(Line3 const& inAxis, Real inRadius, + Real inHeight) + : + axis(inAxis), + radius(inRadius), + height(inHeight) +{ +} + +template +bool Cylinder3::operator==(Cylinder3 const& cylinder) const +{ + return axis == cylinder.axis + && radius == cylinder.radius + && height == cylinder.height; +} + +template +bool Cylinder3::operator!=(Cylinder3 const& cylinder) const +{ + return !operator==(cylinder); +} + +template +bool Cylinder3::operator<(Cylinder3 const& cylinder) const +{ + if (axis < cylinder.axis) + { + return true; + } + + if (axis > cylinder.axis) + { + return false; + } + + if (radius < cylinder.radius) + { + return true; + } + + if (radius > cylinder.radius) + { + return false; + } + + return height < cylinder.height; +} + +template +bool Cylinder3::operator<=(Cylinder3 const& cylinder) const +{ + return operator<(cylinder) || operator==(cylinder); +} + +template +bool Cylinder3::operator>(Cylinder3 const& cylinder) const +{ + return !operator<=(cylinder); +} + +template +bool Cylinder3::operator>=(Cylinder3 const& cylinder) const +{ + return !operator<(cylinder); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDCPQuery.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDCPQuery.h new file mode 100644 index 000000000000..d439a2d6c6e1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDCPQuery.h @@ -0,0 +1,32 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +namespace gte +{ + +// Distance and closest-point queries. +template +class DCPQuery +{ +public: + struct Result + { + // A DCPQuery-base class B must define a B::Result struct with member + // 'Real distance'. A DCPQuery-derived class D must also derive a + // D::Result from B:Result but may have no members. The idea is to + // allow Result to store closest-point information in addition to the + // distance. The operator() is non-const to allow DCPQuery to store + // and modify private state that supports the query. + }; + Result operator()(Type0 const& primitive0, Type1 const& primitive1); +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDarbouxFrame.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDarbouxFrame.h new file mode 100644 index 000000000000..900c38d33951 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDarbouxFrame.h @@ -0,0 +1,160 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ +template +class DarbouxFrame3 +{ +public: + // Construction. The curve must persist as long as the DarbouxFrame3 + // object does. + DarbouxFrame3(std::shared_ptr> const& surface); + + // Get a coordinate frame, {T0, T1, N}. At a nondegenerate surface + // points, dX/du and dX/dv are linearly independent tangent vectors. + // The frame is constructed as + // T0 = (dX/du)/|dX/du| + // N = Cross(dX/du,dX/dv)/|Cross(dX/du,dX/dv)| + // T1 = Cross(N, T0) + // so that {T0, T1, N} is a right-handed orthonormal set. + void operator()(Real u, Real v, Vector3& position, + Vector3& tangent0, Vector3& tangent1, Vector3& normal) const; + + // Compute the principal curvatures and principal directions. + void GetPrincipalInformation(Real u, Real v, Real& curvature0, Real& curvature1, + Vector3& direction0, Vector3& direction1) const; + +private: + std::shared_ptr> mSurface; +}; + + +template +DarbouxFrame3::DarbouxFrame3( + std::shared_ptr> const& surface) + : + mSurface(surface) +{ +} + +template +void DarbouxFrame3::operator()(Real u, Real v, Vector3& position, + Vector3& tangent0, Vector3& tangent1, Vector3& normal) const +{ + Vector3 values[6]; + mSurface->Evaluate(u, v, 1, values); + position = values[0]; + tangent0 = values[1]; + Normalize(tangent0); + tangent1 = values[2]; + Normalize(tangent1); + normal = UnitCross(tangent0, tangent1); + tangent1 = Cross(normal, tangent0); +} + +template +void DarbouxFrame3::GetPrincipalInformation(Real u, Real v, Real& curvature0, + Real& curvature1, Vector3& direction0, Vector3& direction1) const +{ + // Tangents: T0 = (x_u,y_u,z_u), T1 = (x_v,y_v,z_v) + // Normal: N = Cross(T0,T1)/Length(Cross(T0,T1)) + // Metric Tensor: G = +- -+ + // | Dot(T0,T0) Dot(T0,T1) | + // | Dot(T1,T0) Dot(T1,T1) | + // +- -+ + // + // Curvature Tensor: B = +- -+ + // | -Dot(N,T0_u) -Dot(N,T0_v) | + // | -Dot(N,T1_u) -Dot(N,T1_v) | + // +- -+ + // + // Principal curvatures k are the generalized eigenvalues of + // + // Bw = kGw + // + // If k is a curvature and w=(a,b) is the corresponding solution to + // Bw = kGw, then the principal direction as a 3D vector is d = a*U+b*V. + // + // Let k1 and k2 be the principal curvatures. The mean curvature + // is (k1+k2)/2 and the Gaussian curvature is k1*k2. + + // Compute derivatives. + Vector3 values[6]; + mSurface->Evaluate(u, v, 2, values); + Vector3 derU = values[1]; + Vector3 derV = values[2]; + Vector3 derUU = values[3]; + Vector3 derUV = values[4]; + Vector3 derVV = values[5]; + + // Compute the metric tensor. + Matrix2x2 metricTensor; + metricTensor(0, 0) = Dot(values[1], values[1]); + metricTensor(0, 1) = Dot(values[1], values[2]); + metricTensor(1, 0) = metricTensor(0, 1); + metricTensor(1, 1) = Dot(values[2], values[2]); + + // Compute the curvature tensor. + Vector3 normal = UnitCross(values[1], values[2]); + Matrix2x2 curvatureTensor; + curvatureTensor(0, 0) = -Dot(normal, derUU); + curvatureTensor(0, 1) = -Dot(normal, derUV); + curvatureTensor(1, 0) = curvatureTensor(0, 1); + curvatureTensor(1, 1) = -Dot(normal, derVV); + + // Characteristic polynomial is 0 = det(B-kG) = c2*k^2+c1*k+c0. + Real c0 = Determinant(curvatureTensor); + Real c1 = ((Real)2) * curvatureTensor(0, 1) * metricTensor(0, 1) - + curvatureTensor(0, 0) * metricTensor(1, 1) - + curvatureTensor(1, 1) * metricTensor(0, 0); + Real c2 = Determinant(metricTensor); + + // Principal curvatures are roots of characteristic polynomial. + Real temp = std::sqrt(std::abs(c1 * c1 - ((Real)4) * c0 * c2)); + Real mult = ((Real)0.5) / c2; + curvature0 = -mult * (c1 + temp); + curvature1 = -mult * (c1 - temp); + + // Principal directions are solutions to (B-kG)w = 0, + // w1 = (b12-k1*g12,-(b11-k1*g11)) OR (b22-k1*g22,-(b12-k1*g12)). + Real a0 = curvatureTensor(0, 1) - curvature0 * metricTensor(0, 1); + Real a1 = curvature0 * metricTensor(0, 0) - curvatureTensor(0, 0); + Real length = std::sqrt(a0 * a0 + a1 * a1); + if (length > (Real)0) + { + direction0 = a0 * derU + a1 * derV; + } + else + { + a0 = curvatureTensor(1, 1) - curvature0 * metricTensor(1, 1); + a1 = curvature0 * metricTensor(0, 1) - curvatureTensor(0, 1); + length = std::sqrt(a0 * a0 + a1 * a1); + if (length > (Real)0) + { + direction0 = a0*derU + a1*derV; + } + else + { + // Umbilic (surface is locally sphere, any direction principal). + direction0 = derU; + } + } + Normalize(direction0); + + // Second tangent is cross product of first tangent and normal. + direction1 = Cross(direction0, normal); +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay2.h new file mode 100644 index 000000000000..bbbae367ca42 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay2.h @@ -0,0 +1,862 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// Delaunay triangulation of points (intrinsic dimensionality 2). +// VQ = number of vertices +// V = array of vertices +// TQ = number of triangles +// I = Array of 3-tuples of indices into V that represent the triangles +// (3*TQ total elements). Access via GetIndices(*). +// A = Array of 3-tuples of indices into I that represent the adjacent +// triangles (3*TQ total elements). Access via GetAdjacencies(*). +// The i-th triangle has vertices +// vertex[0] = V[I[3*i+0]] +// vertex[1] = V[I[3*i+1]] +// vertex[2] = V[I[3*i+2]] +// and edge index pairs +// edge[0] = +// edge[1] = +// edge[2] = +// The triangles adjacent to these edges have indices +// adjacent[0] = A[3*i+0] is the triangle sharing edge[0] +// adjacent[1] = A[3*i+1] is the triangle sharing edge[1] +// adjacent[2] = A[3*i+2] is the triangle sharing edge[2] +// If there is no adjacent triangle, the A[*] value is set to -1. The +// triangle adjacent to edge[j] has vertices +// adjvertex[0] = V[I[3*adjacent[j]+0]] +// adjvertex[1] = V[I[3*adjacent[j]+1]] +// adjvertex[2] = V[I[3*adjacent[j]+2]] +// The only way to ensure a correct result for the input vertices (assumed to +// be exact) is to choose ComputeType for exact rational arithmetic. You may +// use BSNumber. No divisions are performed in this computation, so you do +// not have to use BSRational. +// +// The worst-case choices of N for Real of type BSNumber or BSRational with +// integer storage UIntegerFP32 are listed in the next table. The numerical +// computations are encapsulated in PrimalQuery2::ToLine and +// PrimalQuery2::ToCircumcircle, the latter query the dominant one in +// determining N. We recommend using only BSNumber, because no divisions are +// performed in the convex-hull computations. +// +// input type | compute type | N +// -----------+--------------+------ +// float | BSNumber | 35 +// double | BSNumber | 263 +// float | BSRational | 12302 +// double | BSRational | 92827 + +namespace gte +{ + +template +class Delaunay2 +{ +public: + // The class is a functor to support computing the Delaunay triangulation + // of multiple data sets using the same class object. + virtual ~Delaunay2(); + Delaunay2(); + + // The input is the array of vertices whose Delaunay triangulation is + // required. The epsilon value is used to determine the intrinsic + // dimensionality of the vertices (d = 0, 1, or 2). When epsilon is + // positive, the determination is fuzzy--vertices approximately the same + // point, approximately on a line, or planar. The return value is 'true' + // if and only if the hull construction is successful. + bool operator()(int numVertices, Vector2 const* vertices, InputType epsilon); + + // Dimensional information. If GetDimension() returns 1, the points lie + // on a line P+t*D (fuzzy comparison when epsilon > 0). You can sort + // these if you need a polyline output by projecting onto the line each + // vertex X = P+t*D, where t = Dot(D,X-P). + inline InputType GetEpsilon() const; + inline int GetDimension() const; + inline Line2 const& GetLine() const; + + // Member access. + inline int GetNumVertices() const; + inline int GetNumUniqueVertices() const; + inline int GetNumTriangles() const; + inline Vector2 const* GetVertices() const; + inline PrimalQuery2 const& GetQuery() const; + inline ETManifoldMesh const& GetGraph() const; + inline std::vector const& GetIndices() const; + inline std::vector const& GetAdjacencies() const; + + // If 'vertices' has no duplicates, GetDuplicates()[i] = i for all i. + // If vertices[i] is the first occurrence of a vertex and if vertices[j] + // is found later, then GetDuplicates()[j] = i. + inline std::vector const& GetDuplicates() const; + + // Locate those triangle edges that do not share other triangles. The + // returned array has hull.size() = 2*numEdges, each pair representing an + // edge. The edges are not ordered, but the pair of vertices for an edge + // is ordered so that they conform to a counterclockwise traversal of the + // hull. The return value is 'true' iff the dimension is 2. + bool GetHull(std::vector& hull) const; + + // Get the vertex indices for triangle i. The function returns 'true' + // when the dimension is 2 and i is a valid triangle index, in which case + // the vertices are valid; otherwise, the function returns 'false' and the + // vertices are invalid. + bool GetIndices(int i, std::array& indices) const; + + // Get the indices for triangles adjacent to triangle i. The function + // returns 'true' when the dimension is 2 and if i is a valid triangle + // index, in which case the adjacencies are valid; otherwise, the function + // returns 'false' and the adjacencies are invalid. + bool GetAdjacencies(int i, std::array& adjacencies) const; + + // Support for searching the triangulation for a triangle that contains + // a point. If there is a containing triangle, the returned value is a + // triangle index i with 0 <= i < GetNumTriangles(). If there is not a + // containing triangle, -1 is returned. The computations are performed + // using exact rational arithmetic. + // + // The SearchInfo input stores information about the triangle search when + // looking for the triangle (if any) that contains p. The first triangle + // searched is 'initialTriangle'. On return 'path' stores those (ordered) + // triangle indices visited during the search. The last visited triangle + // has index 'finalTriangle and vertex indices 'finalV[0,1,2]', stored in + // counterclockwise order. The last edge of the search is + // . For spatially coherent inputs p for numerous + // calls to this function, you will want to specify 'finalTriangle' from + // the previous call as 'initialTriangle' for the next call, which should + // reduce search times. + struct SearchInfo + { + int initialTriangle; + int numPath; + std::vector path; + int finalTriangle; + std::array finalV; + }; + int GetContainingTriangle(Vector2 const& p, SearchInfo& info) const; + +protected: + // Support for incremental Delaunay triangulation. + typedef ETManifoldMesh::Triangle Triangle; + bool GetContainingTriangle(int i, std::shared_ptr& tri) const; + bool GetAndRemoveInsertionPolygon(int i, std::set>& candidates, + std::set>& boundary); + bool Update(int i); + + // The epsilon value is used for fuzzy determination of intrinsic + // dimensionality. If the dimension is 0 or 1, the constructor returns + // early. The caller is responsible for retrieving the dimension and + // taking an alternate path should the dimension be smaller than 2. + // If the dimension is 0, the caller may as well treat all vertices[] + // as a single point, say, vertices[0]. If the dimension is 1, the + // caller can query for the approximating line and project vertices[] + // onto it for further processing. + InputType mEpsilon; + int mDimension; + Line2 mLine; + + // The array of vertices used for geometric queries. If you want to be + // certain of a correct result, choose ComputeType to be BSNumber. + std::vector> mComputeVertices; + PrimalQuery2 mQuery; + + // The graph information. + int mNumVertices; + int mNumUniqueVertices; + int mNumTriangles; + Vector2 const* mVertices; + ETManifoldMesh mGraph; + std::vector mIndices; + std::vector mAdjacencies; + + // If a vertex occurs multiple times in the 'vertices' input to the + // constructor, the first processed occurrence of that vertex has an + // index stored in this array. If there are no duplicates, then + // mDuplicates[i] = i for all i. + + struct ProcessedVertex + { + ProcessedVertex(); + ProcessedVertex(Vector2 const& inVertex, int inLocation); + bool operator<(ProcessedVertex const& v) const; + + Vector2 vertex; + int location; + }; + + std::vector mDuplicates; + + // Indexing for the vertices of the triangle adjacent to a vertex. The + // edge adjacent to vertex j is and is listed + // so that the triangle interior is to your left as you walk around the + // edges. TODO: Use the "opposite edge" to be consistent with that of + // TetrahedronKey. The "opposite" idea extends easily to higher + // dimensions. + std::array, 3> mIndex; +}; + + +template +Delaunay2::~Delaunay2() +{ +} + +template +Delaunay2::Delaunay2() + : + mEpsilon((InputType)0), + mDimension(0), + mLine(Vector2::Zero(), Vector2::Zero()), + mNumVertices(0), + mNumUniqueVertices(0), + mNumTriangles(0), + mVertices(nullptr) +{ + // INVESTIGATE. If the initialization of mIndex is placed in the + // constructor initializer list, MSVS 2012 generates an internal + // compiler error. + mIndex = { { { {0, 1} }, { {1, 2} }, { {2, 0} } } }; +} + +template +bool Delaunay2::operator()(int numVertices, + Vector2 const* vertices, InputType epsilon) +{ + mEpsilon = std::max(epsilon, (InputType)0); + mDimension = 0; + mLine.origin = Vector2::Zero(); + mLine.direction = Vector2::Zero(); + mNumVertices = numVertices; + mNumUniqueVertices = 0; + mNumTriangles = 0; + mVertices = vertices; + mGraph.Clear(); + mIndices.clear(); + mAdjacencies.clear(); + mDuplicates.resize(std::max(numVertices, 3)); + + int i, j; + if (mNumVertices < 3) + { + // Delaunay2 should be called with at least three points. + return false; + } + + IntrinsicsVector2 info(mNumVertices, vertices, mEpsilon); + if (info.dimension == 0) + { + // mDimension is 0; mGraph, mIndices, and mAdjacencies are empty + return false; + } + + if (info.dimension == 1) + { + // The set is (nearly) collinear. + mDimension = 1; + mLine = Line2(info.origin, info.direction[0]); + return false; + } + + mDimension = 2; + + // Compute the vertices for the queries. + mComputeVertices.resize(mNumVertices); + mQuery.Set(mNumVertices, &mComputeVertices[0]); + for (i = 0; i < mNumVertices; ++i) + { + for (j = 0; j < 2; ++j) + { + mComputeVertices[i][j] = vertices[i][j]; + } + } + + // Insert the (nondegenerate) triangle constructed by the call to + // GetInformation. This is necessary for the circumcircle-visibility + // algorithm to work correctly. + if (!info.extremeCCW) + { + std::swap(info.extreme[1], info.extreme[2]); + } + if (!mGraph.Insert(info.extreme[0], info.extreme[1], info.extreme[2])) + { + return false; + } + + // Incrementally update the triangulation. The set of processed points + // is maintained to eliminate duplicates, either in the original input + // points or in the points obtained by snap rounding. + std::set processed; + for (i = 0; i < 3; ++i) + { + j = info.extreme[i]; + processed.insert(ProcessedVertex(vertices[j], j)); + mDuplicates[j] = j; + } + for (i = 0; i < mNumVertices; ++i) + { + ProcessedVertex v(vertices[i], i); + auto iter = processed.find(v); + if (iter == processed.end()) + { + if (!Update(i)) + { + // A failure can occur if ComputeType is not an exact + // arithmetic type. + return false; + } + processed.insert(v); + mDuplicates[i] = i; + } + else + { + mDuplicates[i] = iter->location; + } + } + mNumUniqueVertices = static_cast(processed.size()); + + // Assign integer values to the triangles for use by the caller. + std::map, int> permute; + i = -1; + permute[nullptr] = i++; + for (auto const& element : mGraph.GetTriangles()) + { + permute[element.second] = i++; + } + + // Put Delaunay triangles into an array (vertices and adjacency info). + mNumTriangles = static_cast(mGraph.GetTriangles().size()); + int numindices = 3 * mNumTriangles; + if (numindices > 0) + { + mIndices.resize(numindices); + mAdjacencies.resize(numindices); + i = 0; + for (auto const& element : mGraph.GetTriangles()) + { + std::shared_ptr tri = element.second; + for (j = 0; j < 3; ++j, ++i) + { + mIndices[i] = tri->V[j]; + mAdjacencies[i] = permute[tri->T[j].lock()]; + } + } + } + + return true; +} + +template inline +InputType Delaunay2::GetEpsilon() const +{ + return mEpsilon; +} + +template inline +int Delaunay2::GetDimension() const +{ + return mDimension; +} + +template inline +Line2 const& Delaunay2::GetLine() const +{ + return mLine; +} + +template inline +int Delaunay2::GetNumVertices() const +{ + return mNumVertices; +} + +template inline +int Delaunay2::GetNumUniqueVertices() const +{ + return mNumUniqueVertices; +} + +template inline +int Delaunay2::GetNumTriangles() const +{ + return mNumTriangles; +} + +template inline +Vector2 const* Delaunay2::GetVertices() const +{ + return mVertices; +} + +template inline +PrimalQuery2 const& Delaunay2::GetQuery() const +{ + return mQuery; +} + +template inline +ETManifoldMesh const& Delaunay2::GetGraph() const +{ + return mGraph; +} + +template inline +std::vector const& Delaunay2::GetIndices() const +{ + return mIndices; +} + +template inline +std::vector const& Delaunay2::GetAdjacencies() const +{ + return mAdjacencies; +} + +template inline +std::vector const& Delaunay2::GetDuplicates() const +{ + return mDuplicates; +} + +template +bool Delaunay2::GetHull(std::vector& hull) const +{ + if (mDimension == 2) + { + // Count the number of edges that are not shared by two triangles. + int numEdges = 0; + for (auto adj : mAdjacencies) + { + if (adj == -1) + { + ++numEdges; + } + } + + if (numEdges > 0) + { + // Enumerate the edges. + hull.resize(2 * numEdges); + int current = 0, i = 0; + for (auto adj : mAdjacencies) + { + if (adj == -1) + { + int tri = i / 3, j = i % 3; + hull[current++] = mIndices[3 * tri + j]; + hull[current++] = mIndices[3 * tri + ((j + 1) % 3)]; + } + ++i; + } + return true; + } + else + { + LogError("Unexpected. There must be at least one triangle."); + } + } + else + { + LogError("The dimension must be 2."); + } + return false; +} + +template +bool Delaunay2::GetIndices(int i, std::array& indices) const +{ + if (mDimension == 2) + { + int numTriangles = static_cast(mIndices.size() / 3); + if (0 <= i && i < numTriangles) + { + indices[0] = mIndices[3 * i]; + indices[1] = mIndices[3 * i + 1]; + indices[2] = mIndices[3 * i + 2]; + return true; + } + } + else + { + LogError("The dimension must be 2."); + } + return false; +} + +template +bool Delaunay2::GetAdjacencies(int i, std::array& adjacencies) const +{ + if (mDimension == 2) + { + int numTriangles = static_cast(mIndices.size() / 3); + if (0 <= i && i < numTriangles) + { + adjacencies[0] = mAdjacencies[3 * i]; + adjacencies[1] = mAdjacencies[3 * i + 1]; + adjacencies[2] = mAdjacencies[3 * i + 2]; + return true; + } + } + else + { + LogError("The dimension must be 2."); + } + return false; +} + +template +int Delaunay2::GetContainingTriangle( + Vector2 const& p, SearchInfo& info) const +{ + if (mDimension == 2) + { + Vector2 test{ p[0], p[1] }; + + int numTriangles = static_cast(mIndices.size() / 3); + info.path.resize(numTriangles); + info.numPath = 0; + int triangle; + if (0 <= info.initialTriangle && info.initialTriangle < numTriangles) + { + triangle = info.initialTriangle; + } + else + { + info.initialTriangle = 0; + triangle = 0; + } + + // Use triangle edges as binary separating lines. + for (int i = 0; i < numTriangles; ++i) + { + int ibase = 3 * triangle; + int const* v = &mIndices[ibase]; + + info.path[info.numPath++] = triangle; + info.finalTriangle = triangle; + info.finalV[0] = v[0]; + info.finalV[1] = v[1]; + info.finalV[2] = v[2]; + + if (mQuery.ToLine(test, v[0], v[1]) > 0) + { + triangle = mAdjacencies[ibase]; + if (triangle == -1) + { + info.finalV[0] = v[0]; + info.finalV[1] = v[1]; + info.finalV[2] = v[2]; + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[1], v[2]) > 0) + { + triangle = mAdjacencies[ibase + 1]; + if (triangle == -1) + { + info.finalV[0] = v[1]; + info.finalV[1] = v[2]; + info.finalV[2] = v[0]; + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[2], v[0]) > 0) + { + triangle = mAdjacencies[ibase + 2]; + if (triangle == -1) + { + info.finalV[0] = v[2]; + info.finalV[1] = v[0]; + info.finalV[2] = v[1]; + return -1; + } + continue; + } + + return triangle; + } + } + else + { + LogError("The dimension must be 2."); + } + return -1; +} + +template +bool Delaunay2::GetContainingTriangle(int i, std::shared_ptr& tri) const +{ + int numTriangles = static_cast(mGraph.GetTriangles().size()); + for (int t = 0; t < numTriangles; ++t) + { + int j; + for (j = 0; j < 3; ++j) + { + int v0 = tri->V[mIndex[j][0]]; + int v1 = tri->V[mIndex[j][1]]; + if (mQuery.ToLine(i, v0, v1) > 0) + { + // Point i sees edge from outside the triangle. + auto adjTri = tri->T[j].lock(); + if (adjTri) + { + // Traverse to the triangle sharing the face. + tri = adjTri; + break; + } + else + { + // We reached a hull edge, so the point is outside the + // hull. TODO: Once a hull data structure is in place, + // return tri->T[j] as the candidate for starting a search + // for visible hull edges. + return false; + } + } + + } + + if (j == 3) + { + // The point is inside all four edges, so the point is inside + // a triangle. + return true; + } + } + + LogError("Unexpected termination of GetContainingTriangle."); + return false; +} + +template +bool Delaunay2::GetAndRemoveInsertionPolygon(int i, + std::set>& candidates, std::set>& boundary) +{ + // Locate the triangles that make up the insertion polygon. + ETManifoldMesh polygon; + while (candidates.size() > 0) + { + std::shared_ptr tri = *candidates.begin(); + candidates.erase(candidates.begin()); + + for (int j = 0; j < 3; ++j) + { + auto adj = tri->T[j].lock(); + if (adj && candidates.find(adj) == candidates.end()) + { + int a0 = adj->V[0]; + int a1 = adj->V[1]; + int a2 = adj->V[2]; + if (mQuery.ToCircumcircle(i, a0, a1, a2) <= 0) + { + // Point i is in the circumcircle. + candidates.insert(adj); + } + } + } + + if (!polygon.Insert(tri->V[0], tri->V[1], tri->V[2])) + { + return false; + } + if (!mGraph.Remove(tri->V[0], tri->V[1], tri->V[2])) + { + return false; + } + } + + // Get the boundary edges of the insertion polygon. + for (auto const& element : polygon.GetTriangles()) + { + std::shared_ptr tri = element.second; + for (int j = 0; j < 3; ++j) + { + if (!tri->T[j].lock()) + { + boundary.insert(EdgeKey(tri->V[mIndex[j][0]], tri->V[mIndex[j][1]])); + } + } + } + return true; +} + +template +bool Delaunay2::Update(int i) +{ + // The return value of mGraph.Insert(...) is nullptr if there was a + // failure to insert. The Update function will return 'false' when + // the insertion fails. + + auto const& tmap = mGraph.GetTriangles(); + std::shared_ptr tri = tmap.begin()->second; + if (GetContainingTriangle(i, tri)) + { + // The point is inside the convex hull. The insertion polygon + // contains only triangles in the current triangulation; the + // hull does not change. + + // Use a depth-first search for those triangles whose circumcircles + // contain point i. + std::set> candidates; + candidates.insert(tri); + + // Get the boundary of the insertion polygon C that contains the + // triangles whose circumcircles contain point i. C contains the + // point i. + std::set> boundary; + if (!GetAndRemoveInsertionPolygon(i, candidates, boundary)) + { + return false; + } + + // The insertion polygon consists of the triangles formed by + // point i and the faces of C. + for (auto const& key : boundary) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + if (mQuery.ToLine(i, v0, v1) < 0) + { + if (!mGraph.Insert(i, v0, v1)) + { + return false; + } + } + // else: Point i is on an edge of 'tri', so the + // subdivision has degenerate triangles. Ignore these. + } + } + else + { + // The point is outside the convex hull. The insertion polygon + // is formed by point i and any triangles in the current + // triangulation whose circumcircles contain point i. + + // Locate the convex hull of the triangles. TODO: Maintain a hull + // data structure that is updated incrementally. + std::set> hull; + for (auto const& element : tmap) + { + std::shared_ptr t = element.second; + for (int j = 0; j < 3; ++j) + { + if (!t->T[j].lock()) + { + hull.insert(EdgeKey(t->V[mIndex[j][0]], t->V[mIndex[j][1]])); + } + } + } + + // TODO: Until the hull change, for now just iterate over all the + // hull edges and use the ones visible to point i to locate the + // insertion polygon. + auto const& emap = mGraph.GetEdges(); + std::set> candidates; + std::set> visible; + for (auto const& key : hull) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + if (mQuery.ToLine(i, v0, v1) > 0) + { + auto iter = emap.find(EdgeKey(v0, v1)); + if (iter != emap.end() && iter->second->T[1].lock() == nullptr) + { + auto adj = iter->second->T[0].lock(); + if (adj && candidates.find(adj) == candidates.end()) + { + int a0 = adj->V[0]; + int a1 = adj->V[1]; + int a2 = adj->V[2]; + if (mQuery.ToCircumcircle(i, a0, a1, a2) <= 0) + { + // Point i is in the circumcircle. + candidates.insert(adj); + } + else + { + // Point i is not in the circumcircle but the hull + // edge is visible. + visible.insert(key); + } + } + } + else + { + LogError("Unexpected condition (ComputeType not exact?)"); + return false; + } + } + } + + // Get the boundary of the insertion subpolygon C that contains the + // triangles whose circumcircles contain point i. + std::set> boundary; + if (!GetAndRemoveInsertionPolygon(i, candidates, boundary)) + { + return false; + } + + // The insertion polygon P consists of the triangles formed by point i + // and the back edges of C *and* the visible edges of mGraph-C. + for (auto const& key : boundary) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + if (mQuery.ToLine(i, v0, v1) < 0) + { + // This is a back edge of the boundary. + if (!mGraph.Insert(i, v0, v1)) + { + return false; + } + } + } + for (auto const& key : visible) + { + if (!mGraph.Insert(i, key.V[1], key.V[0])) + { + return false; + } + } + } + + return true; +} + +template +Delaunay2::ProcessedVertex::ProcessedVertex() +{ +} + +template +Delaunay2::ProcessedVertex::ProcessedVertex( + Vector2 const& inVertex, int inLocation) + : + vertex(inVertex), + location(inLocation) +{ +} + +template +bool Delaunay2::ProcessedVertex::operator<( + ProcessedVertex const& v) const +{ + return vertex < v.vertex; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay2Mesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay2Mesh.h new file mode 100644 index 000000000000..3114760fc043 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay2Mesh.h @@ -0,0 +1,169 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +template +class Delaunay2Mesh +{ +public: + // Construction. + Delaunay2Mesh(Delaunay2 const& delaunay); + + // Mesh information. + inline int GetNumVertices() const; + inline int GetNumTriangles() const; + inline Vector2 const* GetVertices() const; + inline int const* GetIndices() const; + inline int const* GetAdjacencies() const; + + // Containment queries. + int GetContainingTriangle(Vector2 const& P) const; + bool GetVertices(int t, std::array, 3>& vertices) + const; + bool GetIndices(int t, std::array& indices) const; + bool GetAdjacencies(int t, std::array& adjacencies) const; + bool GetBarycentrics(int t, Vector2 const& P, + std::array& bary) const; + +private: + Delaunay2 const* mDelaunay; +}; + + +template +Delaunay2Mesh::Delaunay2Mesh( + Delaunay2 const& delaunay) + : + mDelaunay(&delaunay) +{ +} + +template +inline int Delaunay2Mesh:: +GetNumVertices() const +{ + return mDelaunay->GetNumVertices(); +} + +template +inline int Delaunay2Mesh:: +GetNumTriangles() const +{ + return mDelaunay->GetNumTriangles(); +} + +template +inline Vector2 const* +Delaunay2Mesh:: +GetVertices() const +{ + return mDelaunay->GetVertices(); +} + +template +inline int const* Delaunay2Mesh:: +GetIndices() const +{ + return &mDelaunay->GetIndices()[0]; +} + +template +inline int const* Delaunay2Mesh:: +GetAdjacencies() const +{ + return &mDelaunay->GetAdjacencies()[0]; +} + +template +int Delaunay2Mesh:: +GetContainingTriangle(Vector2 const& P) const +{ + typename Delaunay2::SearchInfo info; + return mDelaunay->GetContainingTriangle(P, info); +} + +template +bool Delaunay2Mesh:: +GetVertices(int t, std::array, 3>& vertices) const +{ + if (mDelaunay->GetDimension() == 2) + { + std::array indices; + if (mDelaunay->GetIndices(t, indices)) + { + PrimalQuery2 const& query = mDelaunay->GetQuery(); + Vector2 const* ctVertices = query.GetVertices(); + for (int i = 0; i < 3; ++i) + { + Vector2 const& V = ctVertices[indices[i]]; + for (int j = 0; j < 2; ++j) + { + vertices[i][j] = (InputType)V[j]; + } + } + return true; + } + } + return false; +} + +template +bool Delaunay2Mesh:: +GetIndices(int t, std::array& indices) const +{ + return mDelaunay->GetIndices(t, indices); +} + +template +bool Delaunay2Mesh:: +GetAdjacencies(int t, std::array& indices) const +{ + return mDelaunay->GetAdjacencies(t, indices); +} + +template +bool Delaunay2Mesh:: +GetBarycentrics(int t, Vector2 const& P, +std::array& bary) const +{ + std::array indices; + if (mDelaunay->GetIndices(t, indices)) + { + PrimalQuery2 const& query = mDelaunay->GetQuery(); + Vector2 const* vertices = query.GetVertices(); + Vector2 rtP{ P[0], P[1] }; + std::array, 3> rtV; + for (int i = 0; i < 3; ++i) + { + Vector2 const& V = vertices[indices[i]]; + for (int j = 0; j < 2; ++j) + { + rtV[i][j] = (RationalType)V[j]; + } + }; + + RationalType rtBary[3]; + if (ComputeBarycentrics(rtP, rtV[0], rtV[1], rtV[2], rtBary)) + { + for (int i = 0; i < 3; ++i) + { + bary[i] = (InputType)rtBary[i]; + } + return true; + } + } + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay3.h new file mode 100644 index 000000000000..0e1bf06ac472 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay3.h @@ -0,0 +1,884 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Delaunay tetrahedralization of points (intrinsic dimensionality 3). +// VQ = number of vertices +// V = array of vertices +// TQ = number of tetrahedra +// I = Array of 4-tuples of indices into V that represent the tetrahedra +// (4*TQ total elements). Access via GetIndices(*). +// A = Array of 4-tuples of indices into I that represent the adjacent +// tetrahedra (4*TQ total elements). Access via GetAdjacencies(*). +// The i-th tetrahedron has vertices +// vertex[0] = V[I[4*i+0]] +// vertex[1] = V[I[4*i+1]] +// vertex[2] = V[I[4*i+2]] +// vertex[3] = V[I[4*i+3]] +// and face index triples listed below. The face vertex ordering when +// viewed from outside the tetrahedron is counterclockwise. +// face[0] = +// face[1] = +// face[2] = +// face[3] = +// The tetrahedra adjacent to these faces have indices +// adjacent[0] = A[4*i+0] is the tetrahedron opposite vertex[0], so it +// is the tetrahedron sharing face[0]. +// adjacent[1] = A[4*i+1] is the tetrahedron opposite vertex[1], so it +// is the tetrahedron sharing face[1]. +// adjacent[2] = A[4*i+2] is the tetrahedron opposite vertex[2], so it +// is the tetrahedron sharing face[2]. +// adjacent[3] = A[4*i+3] is the tetrahedron opposite vertex[3], so it +// is the tetrahedron sharing face[3]. +// If there is no adjacent tetrahedron, the A[*] value is set to -1. The +// tetrahedron adjacent to face[j] has vertices +// adjvertex[0] = V[I[4*adjacent[j]+0]] +// adjvertex[1] = V[I[4*adjacent[j]+1]] +// adjvertex[2] = V[I[4*adjacent[j]+2]] +// adjvertex[3] = V[I[4*adjacent[j]+3]] +// The only way to ensure a correct result for the input vertices (assumed to +// be exact) is to choose ComputeType for exact rational arithmetic. You may +// use BSNumber. No divisions are performed in this computation, so you do +// not have to use BSRational. +// +// The worst-case choices of N for Real of type BSNumber or BSRational with +// integer storage UIntegerFP32 are listed in the next table. The numerical +// computations are encapsulated in PrimalQuery3::ToPlane and +// PrimalQuery3::ToCircumsphere, the latter query the dominant one in +// determining N. We recommend using only BSNumber, because no divisions are +// performed in the convex-hull computations. +// +// input type | compute type | N +// -----------+--------------+-------- +// float | BSNumber | 44 +// float | BSRational | 329 +// double | BSNumber | 298037 +// double | BSRational | 2254442 + +namespace gte +{ + +template +class Delaunay3 +{ +public: + // The class is a functor to support computing the Delaunay + // tetrahedralization of multiple data sets using the same class object. + Delaunay3(); + + // The input is the array of vertices whose Delaunay tetrahedralization + // is required. The epsilon value is used to determine the intrinsic + // dimensionality of the vertices (d = 0, 1, 2, or 3). When epsilon is + // positive, the determination is fuzzy--vertices approximately the same + // point, approximately on a line, approximately planar, or volumetric. + bool operator()(int numVertices, Vector3 const* vertices, InputType epsilon); + + // Dimensional information. If GetDimension() returns 1, the points lie + // on a line P+t*D (fuzzy comparison when epsilon > 0). You can sort + // these if you need a polyline output by projecting onto the line each + // vertex X = P+t*D, where t = Dot(D,X-P). If GetDimension() returns 2, + // the points line on a plane P+s*U+t*V (fuzzy comparison when + // epsilon > 0). You can project each vertex X = P+s*U+t*V, where + // s = Dot(U,X-P) and t = Dot(V,X-P), then apply Delaunay2 to the (s,t) + // tuples. + inline InputType GetEpsilon() const; + inline int GetDimension() const; + inline Line3 const& GetLine() const; + inline Plane3 const& GetPlane() const; + + // Member access. + inline int GetNumVertices() const; + inline int GetNumUniqueVertices() const; + inline int GetNumTetrahedra() const; + inline Vector3 const* GetVertices() const; + inline PrimalQuery3 const& GetQuery() const; + inline TSManifoldMesh const& GetGraph() const; + inline std::vector const& GetIndices() const; + inline std::vector const& GetAdjacencies() const; + + // Locate those tetrahedra faces that do not share other tetrahedra. The + // returned array has hull.size() = 3*numFaces indices, each triple + // representing a triangle. The triangles are counterclockwise ordered + // when viewed from outside the hull. The return value is 'true' iff the + // dimension is 3. + bool GetHull(std::vector& hull) const; + + // Get the vertex indices for tetrahedron i. The function returns 'true' + // when the dimension is 3 and i is a valid tetrahedron index, in which + // case the vertices are valid; otherwise, the function returns 'false' + // and the vertices are invalid. + bool GetIndices(int i, std::array& indices) const; + + // Get the indices for tetrahedra adjacent to tetrahedron i. The function + // returns 'true' when the dimension is 3 and i is a valid tetrahedron + // index, in which case the adjacencies are valid; otherwise, the function + // returns 'false' and the adjacencies are invalid. + bool GetAdjacencies(int i, std::array& adjacencies) const; + + // Support for searching the tetrahedralization for a tetrahedron that + // contains a point. If there is a containing tetrahedron, the returned + // value is a tetrahedron index i with 0 <= i < GetNumTetrahedra(). If + // there is not a containing tetrahedron, -1 is returned. The + // computations are performed using exact rational arithmetic. + // + // The SearchInfo input stores information about the tetrahedron search + // when looking for the tetrahedron (if any) that contains p. The first + // tetrahedron searched is 'initialTetrahedron'. On return 'path' stores + // those (ordered) tetrahedron indices visited during the search. The + // last visited tetrahedron has index 'finalTetrahedron and vertex indices + // 'finalV[0,1,2,3]', stored in volumetric counterclockwise order. The + // last face of the search is . For + // spatially coherent inputs p for numerous calls to this function, you + // will want to specify 'finalTetrahedron' from the previous call as + // 'initialTetrahedron' for the next call, which should reduce search + // times. + struct SearchInfo + { + int initialTetrahedron; + int numPath; + std::vector path; + int finalTetrahedron; + std::array finalV; + }; + int GetContainingTetrahedron(Vector3 const& p, SearchInfo& info) const; + +private: + // Support for incremental Delaunay tetrahedralization. + typedef TSManifoldMesh::Tetrahedron Tetrahedron; + bool GetContainingTetrahedron(int i, std::shared_ptr& tetra) const; + bool GetAndRemoveInsertionPolyhedron(int i, std::set>& candidates, + std::set>& boundary); + bool Update(int i); + + // The epsilon value is used for fuzzy determination of intrinsic + // dimensionality. If the dimension is 0, 1, or 2, the constructor + // returns early. The caller is responsible for retrieving the dimension + // and taking an alternate path should the dimension be smaller than 3. + // If the dimension is 0, the caller may as well treat all vertices[] + // as a single point, say, vertices[0]. If the dimension is 1, the + // caller can query for the approximating line and project vertices[] + // onto it for further processing. If the dimension is 2, the caller can + // query for the approximating plane and project vertices[] onto it for + // further processing. + InputType mEpsilon; + int mDimension; + Line3 mLine; + Plane3 mPlane; + + // The array of vertices used for geometric queries. If you want to be + // certain of a correct result, choose ComputeType to be BSNumber. + std::vector> mComputeVertices; + PrimalQuery3 mQuery; + + // The graph information. + int mNumVertices; + int mNumUniqueVertices; + int mNumTetrahedra; + Vector3 const* mVertices; + TSManifoldMesh mGraph; + std::vector mIndices; + std::vector mAdjacencies; +}; + + +template +Delaunay3::Delaunay3() + : + mEpsilon((InputType)0), + mDimension(0), + mLine(Vector3::Zero(), Vector3::Zero()), + mPlane(Vector3::Zero(), (InputType)0), + mNumVertices(0), + mNumUniqueVertices(0), + mNumTetrahedra(0), + mVertices(nullptr) +{ +} + +template +bool Delaunay3::operator()(int numVertices, + Vector3 const* vertices, InputType epsilon) +{ + mEpsilon = std::max(epsilon, (InputType)0); + mDimension = 0; + mLine.origin = Vector3::Zero(); + mLine.direction = Vector3::Zero(); + mPlane.normal = Vector3::Zero(); + mPlane.constant = (InputType)0; + mNumVertices = numVertices; + mNumUniqueVertices = 0; + mNumTetrahedra = 0; + mVertices = vertices; + mGraph = TSManifoldMesh(); + mIndices.clear(); + mAdjacencies.clear(); + + int i, j; + if (mNumVertices < 4) + { + // Delaunay3 should be called with at least four points. + return false; + } + + IntrinsicsVector3 info(mNumVertices, vertices, mEpsilon); + if (info.dimension == 0) + { + // mDimension is 0; mGraph, mIndices, and mAdjacencies are empty + return false; + } + + if (info.dimension == 1) + { + // The set is (nearly) collinear. + mDimension = 1; + mLine = Line3(info.origin, info.direction[0]); + return false; + } + + if (info.dimension == 2) + { + // The set is (nearly) coplanar. + mDimension = 2; + mPlane = Plane3(UnitCross(info.direction[0], + info.direction[1]), info.origin); + return false; + } + + mDimension = 3; + + // Compute the vertices for the queries. + mComputeVertices.resize(mNumVertices); + mQuery.Set(mNumVertices, &mComputeVertices[0]); + for (i = 0; i < mNumVertices; ++i) + { + for (j = 0; j < 3; ++j) + { + mComputeVertices[i][j] = vertices[i][j]; + } + } + + // Insert the (nondegenerate) tetrahedron constructed by the call to + // GetInformation. This is necessary for the circumsphere-visibility + // algorithm to work correctly. + if (!info.extremeCCW) + { + std::swap(info.extreme[2], info.extreme[3]); + } + if (!mGraph.Insert(info.extreme[0], info.extreme[1], info.extreme[2], info.extreme[3])) + { + return false; + } + + // Incrementally update the tetrahedralization. The set of processed + // points is maintained to eliminate duplicates, either in the original + // input points or in the points obtained by snap rounding. + std::set> processed; + for (i = 0; i < 4; ++i) + { + processed.insert(vertices[info.extreme[i]]); + } + for (i = 0; i < mNumVertices; ++i) + { + if (processed.find(vertices[i]) == processed.end()) + { + if (!Update(i)) + { + // A failure can occur if ComputeType is not an exact + // arithmetic type. + return false; + } + processed.insert(vertices[i]); + } + } + mNumUniqueVertices = static_cast(processed.size()); + + // Assign integer values to the tetrahedra for use by the caller. + std::map, int> permute; + i = -1; + permute[nullptr] = i++; + for (auto const& element : mGraph.GetTetrahedra()) + { + permute[element.second] = i++; + } + + // Put Delaunay tetrahedra into an array (vertices and adjacency info). + mNumTetrahedra = static_cast(mGraph.GetTetrahedra().size()); + int numIndices = 4 * mNumTetrahedra; + if (mNumTetrahedra > 0) + { + mIndices.resize(numIndices); + mAdjacencies.resize(numIndices); + i = 0; + for (auto const& element : mGraph.GetTetrahedra()) + { + std::shared_ptr tetra = element.second; + for (j = 0; j < 4; ++j, ++i) + { + mIndices[i] = tetra->V[j]; + mAdjacencies[i] = permute[tetra->S[j].lock()]; + } + } + } + + return true; +} + +template inline +InputType Delaunay3::GetEpsilon() const +{ + return mEpsilon; +} + +template inline +int Delaunay3::GetDimension() const +{ + return mDimension; +} + +template inline +Line3 const& Delaunay3::GetLine() const +{ + return mLine; +} + +template inline +Plane3 const& Delaunay3::GetPlane() const +{ + return mPlane; +} + +template inline +int Delaunay3::GetNumVertices() const +{ + return mNumVertices; +} + +template inline +int Delaunay3::GetNumUniqueVertices() const +{ + return mNumUniqueVertices; +} + +template inline +int Delaunay3::GetNumTetrahedra() const +{ + return mNumTetrahedra; +} + +template inline +Vector3 const* Delaunay3::GetVertices() const +{ + return mVertices; +} + +template inline +PrimalQuery3 const& Delaunay3::GetQuery() const +{ + return mQuery; +} + +template inline +TSManifoldMesh const& Delaunay3::GetGraph() const +{ + return mGraph; +} + +template inline +std::vector const& Delaunay3::GetIndices() const +{ + return mIndices; +} + +template inline +std::vector const& Delaunay3::GetAdjacencies() const +{ + return mAdjacencies; +} + +template +bool Delaunay3::GetHull(std::vector& hull) const +{ + if (mDimension == 3) + { + // Count the number of triangles that are not shared by two + // tetrahedra. + int numTriangles = 0; + for (auto adj : mAdjacencies) + { + if (adj == -1) + { + ++numTriangles; + } + } + + if (numTriangles > 0) + { + // Enumerate the triangles. The prototypical case is the single + // tetrahedron V[0] = (0,0,0), V[1] = (1,0,0), V[2] = (0,1,0), and + // V[3] = (0,0,1) with no adjacent tetrahedra. The mIndices[] + // array is <0,1,2,3>. + // i = 0, face = 0: + // skip index 0, , no swap, triangle = <1,2,3> + // i = 1, face = 1: + // skip index 1, <0,x,2,3>, swap, triangle = <0,3,2> + // i = 2, face = 2: + // skip index 2, <0,1,x,3>, no swap, triangle = <0,1,3> + // i = 3, face = 3: + // skip index 3, <0,1,2,x>, swap, triangle = <0,2,1> + // To guarantee counterclockwise order of triangles when viewed + // outside the tetrahedron, the swap of the last two indices + // occurs when face is an odd number; (face % 2) != 0. + hull.resize(3 * numTriangles); + int current = 0, i = 0; + for (auto adj : mAdjacencies) + { + if (adj == -1) + { + int tetra = i / 4, face = i % 4; + for (int j = 0; j < 4; ++j) + { + if (j != face) + { + hull[current++] = mIndices[4 * tetra + j]; + } + } + if ((face % 2) != 0) + { + std::swap(hull[current - 1], hull[current - 2]); + } + } + ++i; + } + return true; + } + else + { + LogError("Unexpected. There must be at least one tetrahedron."); + } + } + else + { + LogError("The dimension must be 3."); + } + return false; +} + +template +bool Delaunay3::GetIndices(int i, std::array& indices) const +{ + if (mDimension == 3) + { + int numTetrahedra = static_cast(mIndices.size() / 4); + if (0 <= i && i < numTetrahedra) + { + indices[0] = mIndices[4 * i]; + indices[1] = mIndices[4 * i + 1]; + indices[2] = mIndices[4 * i + 2]; + indices[3] = mIndices[4 * i + 3]; + return true; + } + } + else + { + LogError("The dimension must be 3."); + } + return false; +} + +template +bool Delaunay3::GetAdjacencies(int i, std::array& adjacencies) const +{ + if (mDimension == 3) + { + int numTetrahedra = static_cast(mIndices.size() / 4); + if (0 <= i && i < numTetrahedra) + { + adjacencies[0] = mAdjacencies[4 * i]; + adjacencies[1] = mAdjacencies[4 * i + 1]; + adjacencies[2] = mAdjacencies[4 * i + 2]; + adjacencies[3] = mAdjacencies[4 * i + 3]; + return true; + } + } + else + { + LogError("The dimension must be 3."); + } + return false; +} + +template +int Delaunay3::GetContainingTetrahedron(Vector3 const& p, SearchInfo& info) const +{ + if (mDimension == 3) + { + Vector3 test{ p[0], p[1], p[2] }; + + int numTetrahedra = static_cast(mIndices.size() / 4); + info.path.resize(numTetrahedra); + info.numPath = 0; + int tetrahedron; + if (0 <= info.initialTetrahedron + && info.initialTetrahedron < numTetrahedra) + { + tetrahedron = info.initialTetrahedron; + } + else + { + info.initialTetrahedron = 0; + tetrahedron = 0; + } + + // Use tetrahedron faces as binary separating planes. + for (int i = 0; i < numTetrahedra; ++i) + { + int ibase = 4 * tetrahedron; + int const* v = &mIndices[ibase]; + + info.path[info.numPath++] = tetrahedron; + info.finalTetrahedron = tetrahedron; + info.finalV[0] = v[0]; + info.finalV[1] = v[1]; + info.finalV[2] = v[2]; + info.finalV[3] = v[3]; + + // counterclockwise when viewed outside tetrahedron. + if (mQuery.ToPlane(test, v[1], v[2], v[3]) > 0) + { + tetrahedron = mAdjacencies[ibase]; + if (tetrahedron == -1) + { + info.finalV[0] = v[1]; + info.finalV[1] = v[2]; + info.finalV[2] = v[3]; + info.finalV[3] = v[0]; + return -1; + } + continue; + } + + // counterclockwise when viewed outside tetrahedron. + if (mQuery.ToPlane(test, v[0], v[2], v[3]) < 0) + { + tetrahedron = mAdjacencies[ibase + 1]; + if (tetrahedron == -1) + { + info.finalV[0] = v[0]; + info.finalV[1] = v[2]; + info.finalV[2] = v[3]; + info.finalV[3] = v[1]; + return -1; + } + continue; + } + + // counterclockwise when viewed outside tetrahedron. + if (mQuery.ToPlane(test, v[0], v[1], v[3]) > 0) + { + tetrahedron = mAdjacencies[ibase + 2]; + if (tetrahedron == -1) + { + info.finalV[0] = v[0]; + info.finalV[1] = v[1]; + info.finalV[2] = v[3]; + info.finalV[3] = v[2]; + return -1; + } + continue; + } + + // counterclockwise when viewed outside tetrahedron. + if (mQuery.ToPlane(test, v[0], v[1], v[2]) < 0) + { + tetrahedron = mAdjacencies[ibase + 3]; + if (tetrahedron == -1) + { + info.finalV[0] = v[0]; + info.finalV[1] = v[1]; + info.finalV[2] = v[2]; + info.finalV[3] = v[3]; + return -1; + } + continue; + } + + return tetrahedron; + } + } + else + { + LogError("The dimension must be 3."); + } + return -1; +} + +template +bool Delaunay3::GetContainingTetrahedron(int i, std::shared_ptr& tetra) const +{ + int numTetrahedra = static_cast(mGraph.GetTetrahedra().size()); + for (int t = 0; t < numTetrahedra; ++t) + { + int j; + for (j = 0; j < 4; ++j) + { + auto const& opposite = TetrahedronKey::oppositeFace; + int v0 = tetra->V[opposite[j][0]]; + int v1 = tetra->V[opposite[j][1]]; + int v2 = tetra->V[opposite[j][2]]; + if (mQuery.ToPlane(i, v0, v1, v2) > 0) + { + // Point i sees face from outside the tetrahedron. + auto adjTetra = tetra->S[j].lock(); + if (adjTetra) + { + // Traverse to the tetrahedron sharing the face. + tetra = adjTetra; + break; + } + else + { + // We reached a hull face, so the point is outside the + // hull. TODO: Once a hull data structure is in place, + // return tetra->S[j] as the candidate for starting a + // search for visible hull faces. + return false; + } + } + + } + + if (j == 4) + { + // The point is inside all four faces, so the point is inside + // a tetrahedron. + return true; + } + } + + LogError("Unexpected termination of GetContainingTetrahedron."); + return false; +} + +template +bool Delaunay3::GetAndRemoveInsertionPolyhedron(int i, + std::set>& candidates, std::set>& boundary) +{ + // Locate the tetrahedra that make up the insertion polyhedron. + TSManifoldMesh polyhedron; + while (candidates.size() > 0) + { + std::shared_ptr tetra = *candidates.begin(); + candidates.erase(candidates.begin()); + + for (int j = 0; j < 4; ++j) + { + auto adj = tetra->S[j].lock(); + if (adj && candidates.find(adj) == candidates.end()) + { + int a0 = adj->V[0]; + int a1 = adj->V[1]; + int a2 = adj->V[2]; + int a3 = adj->V[3]; + if (mQuery.ToCircumsphere(i, a0, a1, a2, a3) <= 0) + { + // Point i is in the circumsphere. + candidates.insert(adj); + } + } + } + + int v0 = tetra->V[0]; + int v1 = tetra->V[1]; + int v2 = tetra->V[2]; + int v3 = tetra->V[3]; + if (!polyhedron.Insert(v0, v1, v2, v3)) + { + return false; + } + if (!mGraph.Remove(v0, v1, v2, v3)) + { + return false; + } + } + + // Get the boundary triangles of the insertion polyhedron. + for (auto const& element : polyhedron.GetTetrahedra()) + { + std::shared_ptr tetra = element.second; + for (int j = 0; j < 4; ++j) + { + if (!tetra->S[j].lock()) + { + auto const& opposite = TetrahedronKey::oppositeFace; + int v0 = tetra->V[opposite[j][0]]; + int v1 = tetra->V[opposite[j][1]]; + int v2 = tetra->V[opposite[j][2]]; + boundary.insert(TriangleKey(v0, v1, v2)); + } + } + } + return true; +} + +template +bool Delaunay3::Update(int i) +{ + auto const& smap = mGraph.GetTetrahedra(); + std::shared_ptr tetra = smap.begin()->second; + if (GetContainingTetrahedron(i, tetra)) + { + // The point is inside the convex hull. The insertion polyhedron + // contains only tetrahedra in the current tetrahedralization; the + // hull does not change. + + // Use a depth-first search for those tetrahedra whose circumspheres + // contain point i. + std::set> candidates; + candidates.insert(tetra); + + // Get the boundary of the insertion polyhedron C that contains the + // tetrahedra whose circumspheres contain point i. C contains the + // point i. + std::set> boundary; + if (!GetAndRemoveInsertionPolyhedron(i, candidates, boundary)) + { + return false; + } + + // The insertion polyhedron consists of the tetrahedra formed by + // point i and the faces of C. + for (auto const& key : boundary) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + int v2 = key.V[2]; + if (mQuery.ToPlane(i, v0, v1, v2) < 0) + { + if (!mGraph.Insert(i, v0, v1, v2)) + { + return false; + } + } + // else: Point i is on an edge or face of 'tetra', so the + // subdivision has degenerate tetrahedra. Ignore these. + } + } + else + { + // The point is outside the convex hull. The insertion polyhedron + // is formed by point i and any tetrahedra in the current + // tetrahedralization whose circumspheres contain point i. + + // Locate the convex hull of the tetrahedra. TODO: Maintain a hull + // data structure that is updated incrementally. + std::set> hull; + for (auto const& element : smap) + { + std::shared_ptr t = element.second; + for (int j = 0; j < 4; ++j) + { + if (!t->S[j].lock()) + { + auto const& opposite = TetrahedronKey::oppositeFace; + int v0 = t->V[opposite[j][0]]; + int v1 = t->V[opposite[j][1]]; + int v2 = t->V[opposite[j][2]]; + hull.insert(TriangleKey(v0, v1, v2)); + } + } + } + + // TODO: Until the hull change, for now just iterate over all the + // hull faces and use the ones visible to point i to locate the + // insertion polyhedron. + auto const& tmap = mGraph.GetTriangles(); + std::set> candidates; + std::set> visible; + for (auto const& key : hull) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + int v2 = key.V[2]; + if (mQuery.ToPlane(i, v0, v1, v2) > 0) + { + auto iter = tmap.find(TriangleKey(v0, v1, v2)); + if (iter != tmap.end() && iter->second->T[1].lock() == nullptr) + { + auto adj = iter->second->T[0].lock(); + if (adj && candidates.find(adj) == candidates.end()) + { + int a0 = adj->V[0]; + int a1 = adj->V[1]; + int a2 = adj->V[2]; + int a3 = adj->V[3]; + if (mQuery.ToCircumsphere(i, a0, a1, a2, a3) <= 0) + { + // Point i is in the circumsphere. + candidates.insert(adj); + } + else + { + // Point i is not in the circumsphere but the hull + // face is visible. + visible.insert(key); + } + } + } + else + { + LogError("Unexpected condition (ComputeType not exact?)"); + return false; + } + } + } + + // Get the boundary of the insertion subpolyhedron C that contains the + // tetrahedra whose circumspheres contain point i. + std::set> boundary; + if (!GetAndRemoveInsertionPolyhedron(i, candidates, boundary)) + { + return false; + } + + // The insertion polyhedron P consists of the tetrahedra formed by + // point i and the back faces of C *and* the visible faces of + // mGraph-C. + for (auto const& key : boundary) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + int v2 = key.V[2]; + if (mQuery.ToPlane(i, v0, v1, v2) < 0) + { + // This is a back face of the boundary. + if (!mGraph.Insert(i, v0, v1, v2)) + { + return false; + } + } + } + for (auto const& key : visible) + { + if (!mGraph.Insert(i, key.V[0], key.V[2], key.V[1])) + { + return false; + } + } + } + + return true; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay3Mesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay3Mesh.h new file mode 100644 index 000000000000..9e5e29aaada5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay3Mesh.h @@ -0,0 +1,169 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +template +class Delaunay3Mesh +{ +public: + // Construction. + Delaunay3Mesh(Delaunay3 const& delaunay); + + // Mesh information. + inline int GetNumVertices() const; + inline int GetNumTetrahedra() const; + inline Vector3 const* GetVertices() const; + inline int const* GetIndices() const; + inline int const* GetAdjacencies() const; + + // Containment queries. + int GetContainingTetrahedron(Vector3 const& P) const; + bool GetVertices(int t, std::array, 4>& vertices) + const; + bool GetIndices(int t, std::array& indices) const; + bool GetAdjacencies(int t, std::array& adjacencies) const; + bool GetBarycentrics(int t, Vector3 const& P, + std::array& bary) const; + +private: + Delaunay3 const* mDelaunay; +}; + + +template +Delaunay3Mesh::Delaunay3Mesh( + Delaunay3 const& delaunay) + : + mDelaunay(&delaunay) +{ +} + +template +inline int Delaunay3Mesh:: +GetNumVertices() const +{ + return mDelaunay->GetNumVertices(); +} + +template +inline int Delaunay3Mesh:: +GetNumTetrahedra() const +{ + return mDelaunay->GetNumTetrahedra(); +} + +template +inline Vector3 const* +Delaunay3Mesh:: +GetVertices() const +{ + return mDelaunay->GetVertices(); +} + +template +inline int const* Delaunay3Mesh:: +GetIndices() const +{ + return &mDelaunay->GetIndices()[0]; +} + +template +inline int const* Delaunay3Mesh:: +GetAdjacencies() const +{ + return &mDelaunay->GetAdjacencies()[0]; +} + +template +int Delaunay3Mesh:: +GetContainingTetrahedron(Vector3 const& P) const +{ + typename Delaunay3::SearchInfo info; + return mDelaunay->GetContainingTetrahedron(P, info); +} + +template +bool Delaunay3Mesh:: +GetVertices(int t, std::array, 4>& vertices) const +{ + if (mDelaunay->GetDimension() == 3) + { + std::array indices; + if (mDelaunay->GetIndices(t, indices)) + { + PrimalQuery3 const& query = mDelaunay->GetQuery(); + Vector3 const* ctVertices = query.GetVertices(); + for (int i = 0; i < 4; ++i) + { + Vector3 const& V = ctVertices[indices[i]]; + for (int j = 0; j < 3; ++j) + { + vertices[i][j] = (InputType)V[j]; + } + } + return true; + } + } + return false; +} + +template +bool Delaunay3Mesh:: +GetIndices(int t, std::array& indices) const +{ + return mDelaunay->GetIndices(t, indices); +} + +template +bool Delaunay3Mesh:: +GetAdjacencies(int t, std::array& indices) const +{ + return mDelaunay->GetAdjacencies(t, indices); +} + +template +bool Delaunay3Mesh:: +GetBarycentrics(int t, Vector3 const& P, +std::array& bary) const +{ + std::array indices; + if (mDelaunay->GetIndices(t, indices)) + { + PrimalQuery3 const& query = mDelaunay->GetQuery(); + Vector3 const* vertices = query.GetVertices(); + Vector3 rtP{ P[0], P[1], P[2] }; + std::array, 4> rtV; + for (int i = 0; i < 4; ++i) + { + Vector3 const& V = vertices[indices[i]]; + for (int j = 0; j < 3; ++j) + { + rtV[i][j] = (RationalType)V[j]; + } + }; + + RationalType rtBary[4]; + if (ComputeBarycentrics(rtP, rtV[0], rtV[1], rtV[2], rtV[3], rtBary)) + { + for (int i = 0; i < 4; ++i) + { + bary[i] = (InputType)rtBary[i]; + } + return true; + } + } + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistAlignedBox3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistAlignedBox3OrientedBox3.h new file mode 100644 index 000000000000..11b61e1faec0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistAlignedBox3OrientedBox3.h @@ -0,0 +1,154 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, OrientedBox3> +{ +public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array box0Parameter, box1Parameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of whether + // the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 144 (n = 12, maxIterations = n*n). If + // the solver fails to converge, try increasing the maximum number of + // iterations. + void SetMaxLCPIterations(int maxLCPIterations); + + Result operator()(AlignedBox3 const& box0, OrientedBox3 const& box1); + +private: + LCPSolver mLCP; +}; + + +template +void DCPQuery, OrientedBox3>::SetMaxLCPIterations(int maxLCPIterations) +{ + mLCP.SetMaxIterations(maxLCPIterations); +} + +template +typename DCPQuery, OrientedBox3>::Result + DCPQuery, OrientedBox3>::operator()( + AlignedBox3 const& box0, OrientedBox3 const& box1) +{ + Result result; + + // Translate the boxes so that the aligned box becomes a canonical box. + // Modify the oriented box coefficients to be nonnegative. + Vector3 K = box0.max - box0.min; + Vector3 delta = box1.center - box0.min; + for (int i = 0; i < 3; ++i) + { + delta -= box1.extent[i] * box1.axis[i]; + } + + Vector3 rotDelta; + for (int i = 0; i < 3; ++i) + { + rotDelta[i] = Dot(box1.axis[i], delta); + } + + Vector3 twoExtent = box1.extent * (Real)2; + + // The LCP has 6 variables and 6 (nontrivial) inequality constraints. + std::array q = + { + -delta[0], -delta[1], -delta[2], rotDelta[0], rotDelta[1], rotDelta[2], + K[0], K[1], K[2], twoExtent[0], twoExtent[1], twoExtent[2] + }; + + std::array, 12> M; + { + Real const z = (Real)0; + Real const p = (Real)1; + Real const m = (Real)-1; + Vector3 const& U0 = box1.axis[0]; + Vector3 const& U1 = box1.axis[1]; + Vector3 const& U2 = box1.axis[2]; + M[ 0] = { p, z, z, -U0[0], -U1[0], -U1[2], p, z, z, z, z, z }; + M[ 1] = { z, p, z, -U0[1], -U1[1], -U1[1], z, p, z, z, z, z }; + M[ 2] = { z, z, p, -U0[2], -U1[2], -U1[2], z, z, p, z, z, z }; + M[ 3] = { -U0[0], -U0[1], -U0[2], p, z, z, z, z, z, p, z, z }; + M[ 4] = { -U1[0], -U1[1], -U1[2], z, p, z, z, z, z, z, p, z }; + M[ 5] = { -U2[0], -U2[1], -U2[2], z, z, p, z, z, z, z, z, p }; + M[ 6] = { m, z, z, z, z, z, z, z, z, z, z, z }; + M[ 7] = { z, m, z, z, z, z, z, z, z, z, z, z }; + M[ 8] = { z, z, m, z, z, z, z, z, z, z, z, z }; + M[ 9] = { z, z, z, m, z, z, z, z, z, z, z, z }; + M[10] = { z, z, z, z, m, z, z, z, z, z, z, z }; + M[11] = { z, z, z, z, z, m, z, z, z, z, z, z }; + } + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + + for (int i = 0; i < 3; ++i) + { + result.box0Parameter[i] = z[i] + box0.min[i]; + result.closestPoint[0][i] = result.box0Parameter[i]; + } + + result.closestPoint[1] = box1.center; + for (int i = 0, j = 3; i < 3; ++i, ++j) + { + result.box1Parameter[i] = z[j] - box1.extent[i]; + result.closestPoint[1] += result.box1Parameter[i] * box1.axis[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations was not + // specified to be large enough or there is a problem due to + // floating-point rounding errors. If you believe the latter is + // true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 3; ++i) + { + result.box0Parameter[i] = (Real)0; + result.box1Parameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistAlignedBoxAlignedBox.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistAlignedBoxAlignedBox.h new file mode 100644 index 000000000000..09c6e037087d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistAlignedBoxAlignedBox.h @@ -0,0 +1,79 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.2 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, AlignedBox> +{ +public: + struct Result + { + Real distance, sqrDistance; + + // To compute a single closest point on each box, use + // Vector closest0 = (closestPoints[0].min + closestPoints[0].max)/2; + // Vector closest1 = (closestPoints[1].min + closestPoints[1].max)/2; + AlignedBox closestPoints[2]; + }; + + Result operator()(AlignedBox const& box0, AlignedBox const& box1); +}; + + +template +typename DCPQuery, AlignedBox>::Result +DCPQuery, AlignedBox>::operator()( + AlignedBox const& box0, AlignedBox const& box1) +{ + Result result; + result.sqrDistance = (Real)0; + for (int i = 0; i < N; ++i) + { + if (box0.min[i] >= box1.max[i]) + { + Real delta = box0.min[i] - box1.min[i]; + result.sqrDistance += delta * delta; + result.closestPoints[0].min[i] = box0.min[i]; + result.closestPoints[0].max[i] = box0.min[i]; + result.closestPoints[1].min[i] = box1.max[i]; + result.closestPoints[1].max[i] = box1.max[i]; + } + else if (box1.min[i] >= box0.max[i]) + { + Real delta = box1.min[i] - box0.max[i]; + result.sqrDistance += delta * delta; + result.closestPoints[0].min[i] = box0.max[i]; + result.closestPoints[0].max[i] = box0.max[i]; + result.closestPoints[1].min[i] = box1.min[i]; + result.closestPoints[1].max[i] = box1.min[i]; + } + else + { + std::array intr0 = {{ box0.min[i], box0.max[i] }}; + std::array intr1 = {{ box1.min[i], box1.max[i] }}; + FIQuery, std::array> query; + auto iiResult = query(intr0, intr1); + for (int j = 0; j < 2; ++j) + { + result.closestPoints[j].min[i] = iiResult.overlap[0]; + result.closestPoints[j].max[i] = iiResult.overlap[1]; + } + } + } + result.distance = std::sqrt(result.sqrDistance); + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistCircle3Circle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistCircle3Circle3.h new file mode 100644 index 000000000000..c40558be0e61 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistCircle3Circle3.h @@ -0,0 +1,382 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/10/17) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The 3D circle-circle distance algorithm is described in +// http://www.geometrictools.com/Documentation/DistanceToCircle3.pdf +// The notation used in the code matches that of the document. + +namespace gte +{ + template + class DCPQuery, Circle3> + { + public: + struct Result + { + Real distance, sqrDistance; + int numClosestPairs; + Vector3 circle0Closest[2], circle1Closest[2]; + bool equidistant; + }; + + Result operator()(Circle3 const& circle0, Circle3 const& circle1) + { + Result result; + Vector3 const vzero = Vector3::Zero(); + Real const zero = (Real)0; + + Vector3 N0 = circle0.normal, N1 = circle1.normal; + Real r0 = circle0.radius, r1 = circle1.radius; + Vector3 D = circle1.center - circle0.center; + Vector3 N0xN1 = Cross(N0, N1); + + if (N0xN1 != vzero) + { + // Get parameters for constructing the degree-8 polynomial phi. + Real const one = (Real)1, two = (Real)2; + Real r0sqr = r0 * r0, r1sqr = r1 * r1; + + // Compute U1 and V1 for the plane of circle1. + Vector3 basis[3]; + basis[0] = circle1.normal; + ComputeOrthogonalComplement(1, basis); + Vector3 U1 = basis[1], V1 = basis[2]; + + // Construct the polynomial phi(cos(theta)). + Vector3 N0xD = Cross(N0, D); + Vector3 N0xU1 = Cross(N0, U1), N0xV1 = Cross(N0, V1); + Real a0 = r1 * Dot(D, U1), a1 = r1 * Dot(D, V1); + Real a2 = Dot(N0xD, N0xD), a3 = r1 * Dot(N0xD, N0xU1); + Real a4 = r1 * Dot(N0xD, N0xV1), a5 = r1sqr * Dot(N0xU1, N0xU1); + Real a6 = r1sqr * Dot(N0xU1, N0xV1), a7 = r1sqr * Dot(N0xV1, N0xV1); + Polynomial1 p0{ a2 + a7, two * a3, a5 - a7 }; + Polynomial1 p1{ two * a4, two * a6 }; + Polynomial1 p2{ zero, a1 }; + Polynomial1 p3{ -a0 }; + Polynomial1 p4{ -a6, a4, two * a6 }; + Polynomial1 p5{ -a3, a7 - a5 }; + Polynomial1 tmp0{ one, zero, -one }; + Polynomial1 tmp1 = p2 * p2 + tmp0 * p3 * p3; + Polynomial1 tmp2 = two * p2 * p3; + Polynomial1 tmp3 = p4 * p4 + tmp0 * p5 * p5; + Polynomial1 tmp4 = two * p4 * p5; + Polynomial1 p6 = p0 * tmp1 + tmp0 * p1 * tmp2 - r0sqr * tmp3; + Polynomial1 p7 = p0 * tmp2 + p1 * tmp1 - r0sqr * tmp4; + + // The use of 'double' is intentional in case Real is a BSNumber or + // BSRational type. We want the bisections to terminate in a + // reasonable amount of time. + unsigned int const maxIterations = GTE_C_MAX_BISECTIONS_GENERIC; + Real roots[8], sn, temp; + int i, degree, numRoots; + + // The RootsPolynomial::Find(...) function currently does not + // combine duplicate roots. We need only the unique ones here. + std::set uniqueRoots; + + std::array, 16> pairs; + int numPairs = 0; + if (p7.GetDegree() > 0 || p7[0] != zero) + { + // H(cs,sn) = p6(cs) + sn * p7(cs) + Polynomial1 phi = p6 * p6 - tmp0 * p7 * p7; + degree = static_cast(phi.GetDegree()); + LogAssert(degree > 0, "Unexpected degree for phi."); + numRoots = RootsPolynomial::Find(degree, &phi[0], + maxIterations, roots); + for (i = 0; i < numRoots; ++i) + { + uniqueRoots.insert(roots[i]); + } + + for (auto cs : uniqueRoots) + { + if (std::abs(cs) <= one) + { + temp = p7(cs); + if (temp != zero) + { + sn = -p6(cs) / temp; + pairs[numPairs++] = std::make_pair(cs, sn); + } + else + { + temp = std::max(one - cs * cs, zero); + sn = std::sqrt(temp); + pairs[numPairs++] = std::make_pair(cs, sn); + if (sn != zero) + { + pairs[numPairs++] = std::make_pair(cs, -sn); + } + } + } + } + } + else + { + // H(cs,sn) = p6(cs) + degree = static_cast(p6.GetDegree()); + LogAssert(degree > 0, "Unexpected degree for p6."); + numRoots = RootsPolynomial::Find(degree, &p6[0], + maxIterations, roots); + for (i = 0; i < numRoots; ++i) + { + uniqueRoots.insert(roots[i]); + } + + for (auto cs : uniqueRoots) + { + if (std::abs(cs) <= one) + { + temp = std::max(one - cs * cs, zero); + sn = std::sqrt(temp); + pairs[numPairs++] = std::make_pair(cs, sn); + if (sn != zero) + { + pairs[numPairs++] = std::make_pair(cs, -sn); + } + } + } + } + + std::array candidates; + for (i = 0; i < numPairs; ++i) + { + ClosestInfo& info = candidates[i]; + Vector3 delta = + D + r1 * (pairs[i].first * U1 + pairs[i].second * V1); + info.circle1Closest = circle0.center + delta; + Real N0dDelta = Dot(N0, delta); + Real lenN0xDelta = Length(Cross(N0, delta)); + if (lenN0xDelta > 0) + { + Real diff = lenN0xDelta - r0; + info.sqrDistance = N0dDelta * N0dDelta + diff * diff; + delta -= N0dDelta * circle0.normal; + Normalize(delta); + info.circle0Closest = circle0.center + r0 * delta; + info.equidistant = false; + } + else + { + Vector3 r0U0 = r0 * GetOrthogonal(N0, true); + Vector3 diff = delta - r0U0; + info.sqrDistance = Dot(diff, diff); + info.circle0Closest = circle0.center + r0U0; + info.equidistant = true; + } + } + + std::sort(candidates.begin(), candidates.begin() + numPairs); + + result.numClosestPairs = 1; + result.sqrDistance = candidates[0].sqrDistance; + result.circle0Closest[0] = candidates[0].circle0Closest; + result.circle1Closest[0] = candidates[0].circle1Closest; + result.equidistant = candidates[0].equidistant; + if (numRoots > 1 + && candidates[1].sqrDistance == candidates[0].sqrDistance) + { + result.numClosestPairs = 2; + result.circle0Closest[1] = candidates[1].circle0Closest; + result.circle1Closest[1] = candidates[1].circle1Closest; + } + } + else + { + // The planes of the circles are parallel. Whether the planes are the + // same or different, the problem reduces to determining how two + // circles in the same plane are separated, tangent with one circle + // outside the other, overlapping, or one circle contained inside the + // other circle. + DoQueryParallelPlanes(circle0, circle1, D, result); + } + + result.distance = std::sqrt(result.sqrDistance); + return result; + } + + private: + class SCPolynomial + { + public: + SCPolynomial() + { + } + + SCPolynomial(Real oneTerm, Real cosTerm, Real sinTerm) + { + mPoly[0] = Polynomial1{ oneTerm, cosTerm }; + mPoly[1] = Polynomial1{ sinTerm }; + } + + inline Polynomial1 const& operator[] (unsigned int i) const + { + return mPoly[i]; + } + + inline Polynomial1& operator[] (unsigned int i) + { + return mPoly[i]; + } + + SCPolynomial operator+(SCPolynomial const& object) const + { + SCPolynomial result; + result.mPoly[0] = mPoly[0] + object.mPoly[0]; + result.mPoly[1] = mPoly[1] + object.mPoly[1]; + return result; + } + + SCPolynomial operator-(SCPolynomial const& object) const + { + SCPolynomial result; + result.mPoly[0] = mPoly[0] - object.mPoly[0]; + result.mPoly[1] = mPoly[1] - object.mPoly[1]; + return result; + } + + SCPolynomial operator*(SCPolynomial const& object) const + { + Polynomial1 omcsqr{ (Real)1, (Real)0, (Real)-1 }; // 1 - c^2 + SCPolynomial result; + result.mPoly[0] = mPoly[0] * object.mPoly[0] + omcsqr * mPoly[1] * object.mPoly[1]; + result.mPoly[1] = mPoly[0] * object.mPoly[1] + mPoly[1] * object.mPoly[0]; + return result; + } + + SCPolynomial operator*(Real scalar) const + { + SCPolynomial result; + result.mPoly[0] = scalar * mPoly[0]; + result.mPoly[1] = scalar * mPoly[1]; + return result; + } + + private: + Polynomial1 mPoly[2]; // poly0(c) + s * poly1(c) + }; + + struct ClosestInfo + { + Real sqrDistance; + Vector3 circle0Closest, circle1Closest; + bool equidistant; + + inline bool operator< (ClosestInfo const& info) const + { + return sqrDistance < info.sqrDistance; + } + }; + + // The two circles are in parallel planes where D = C1 - C0, the + // difference of circle centers. + void DoQueryParallelPlanes(Circle3 const& circle0, Circle3 const& circle1, + Vector3 const& D, Result& result) + { + Real N0dD = Dot(circle0.normal, D); + Vector3 normProj = N0dD * circle0.normal; + Vector3 compProj = D - normProj; + Vector3 U = compProj; + Real d = Normalize(U); + + // The configuration is determined by the relative location of the + // intervals of projection of the circles on to the D-line. Circle0 + // projects to [-r0,r0] and circle1 projects to [d-r1,d+r1]. + Real r0 = circle0.radius, r1 = circle1.radius; + Real dmr1 = d - r1; + Real distance; + if (dmr1 >= r0) // d >= r0 + r1 + { + // The circles are separated (d > r0 + r1) or tangent with one + // outside the other (d = r0 + r1). + distance = dmr1 - r0; + result.numClosestPairs = 1; + result.circle0Closest[0] = circle0.center + r0 * U; + result.circle1Closest[0] = circle1.center - r1 * U; + result.equidistant = false; + } + else // d < r0 + r1 + { + // The cases implicitly use the knowledge that d >= 0. + Real dpr1 = d + r1; + if (dpr1 <= r0) + { + // Circle1 is inside circle0. + distance = r0 - dpr1; + result.numClosestPairs = 1; + if (d > (Real)0) + { + result.circle0Closest[0] = circle0.center + r0 * U; + result.circle1Closest[0] = circle1.center + r1 * U; + result.equidistant = false; + } + else + { + // The circles are concentric, so U = (0,0,0). Construct + // a vector perpendicular to N0 to use for closest points. + U = GetOrthogonal(circle0.normal, true); + result.circle0Closest[0] = circle0.center + r0 * U; + result.circle1Closest[0] = circle1.center + r1 * U; + result.equidistant = true; + } + } + else if (dmr1 <= -r0) + { + // Circle0 is inside circle1. + distance = -r0 - dmr1; + result.numClosestPairs = 1; + if (d > (Real)0) + { + result.circle0Closest[0] = circle0.center - r0 * U; + result.circle1Closest[0] = circle1.center - r1 * U; + result.equidistant = false; + } + else + { + // The circles are concentric, so U = (0,0,0). Construct + // a vector perpendicular to N0 to use for closest points. + U = GetOrthogonal(circle0.normal, true); + result.circle0Closest[0] = circle0.center + r0 * U; + result.circle1Closest[0] = circle1.center + r1 * U; + result.equidistant = true; + } + } + else + { + // The circles are overlapping. The two points of intersection + // are C0 + s*(C1-C0) +/- h*Cross(N,U), where + // s = (1 + (r0^2 - r1^2)/d^2)/2 and h = sqrt(r0^2 - s^2 * d^2). + Real r0sqr = r0 * r0, r1sqr = r1 * r1, dsqr = d * d; + Real s = ((Real)1 + (r0sqr - r1sqr) / dsqr) / (Real)2; + Real arg = std::max(r0sqr - dsqr * s * s, (Real)0); + Real h = std::sqrt(arg); + Vector3 midpoint = circle0.center + s * compProj; + Vector3 hNxU = h * Cross(circle0.normal, U); + distance = (Real)0; + result.numClosestPairs = 2; + result.circle0Closest[0] = midpoint + hNxU; + result.circle0Closest[1] = midpoint - hNxU; + result.circle1Closest[0] = result.circle0Closest[0] + normProj; + result.circle1Closest[1] = result.circle0Closest[1] + normProj; + result.equidistant = false; + } + } + + result.sqrDistance = distance * distance + N0dD * N0dD; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3AlignedBox3.h new file mode 100644 index 000000000000..0ea921d56a5c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3AlignedBox3.h @@ -0,0 +1,553 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, AlignedBox3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real lineParameter; + Vector3 closestPoint[2]; + }; + + Result operator()(Line3 const& line, AlignedBox3 const& box); + +protected: + // Compute the distance and closest point between a line and an + // axis-aligned box whose center is the origin. On input, 'point' is the + // line origin and 'direction' is the line direction. On output, 'point' + // is the point on the box closest to the line. The 'direction' is + // non-const to allow transforming the problem into the first octant. + void DoQuery(Vector3& point, Vector3& direction, + Vector3 const& boxExtent, Result& result); + +private: + void Face(int i0, int i1, int i2, Vector3& pnt, + Vector3 const& dir, Vector3 const& PmE, + Vector3 const& boxExtent, Result& result); + + void CaseNoZeros(Vector3& pnt, Vector3 const& dir, + Vector3 const& boxExtent, Result& result); + + void Case0(int i0, int i1, int i2, Vector3& pnt, + Vector3 const& dir, Vector3 const& boxExtent, + Result& result); + + void Case00(int i0, int i1, int i2, Vector3& pnt, + Vector3 const& dir, Vector3 const& boxExtent, + Result& result); + + void Case000(Vector3& pnt, Vector3 const& boxExtent, + Result& result); +}; + + +template +typename DCPQuery, AlignedBox3>::Result +DCPQuery, AlignedBox3>::operator()( + Line3 const& line, AlignedBox3 const& box) +{ + // Translate the line and box so that the box has center at the origin. + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + Vector3 point = line.origin - boxCenter; + Vector3 direction = line.direction; + + Result result; + DoQuery(point, direction, boxExtent, result); + + // Compute the closest point on the line. + result.closestPoint[0] = + line.origin + result.lineParameter*line.direction; + + // Compute the closest point on the box. + result.closestPoint[1] = boxCenter + point; + return result; +} + +template +void DCPQuery, AlignedBox3>::DoQuery( + Vector3& point, Vector3& direction, + Vector3 const& boxExtent, Result& result) +{ + result.sqrDistance = (Real)0; + result.lineParameter = (Real)0; + + // Apply reflections so that direction vector has nonnegative components. + bool reflect[3]; + for (int i = 0; i < 3; ++i) + { + if (direction[i] < (Real)0) + { + point[i] = -point[i]; + direction[i] = -direction[i]; + reflect[i] = true; + } + else + { + reflect[i] = false; + } + } + + if (direction[0] > (Real)0) + { + if (direction[1] > (Real)0) + { + if (direction[2] > (Real)0) // (+,+,+) + { + CaseNoZeros(point, direction, boxExtent, result); + } + else // (+,+,0) + { + Case0(0, 1, 2, point, direction, boxExtent, result); + } + } + else + { + if (direction[2] > (Real)0) // (+,0,+) + { + Case0(0, 2, 1, point, direction, boxExtent, result); + } + else // (+,0,0) + { + Case00(0, 1, 2, point, direction, boxExtent, result); + } + } + } + else + { + if (direction[1] > (Real)0) + { + if (direction[2] > (Real)0) // (0,+,+) + { + Case0(1, 2, 0, point, direction, boxExtent, result); + } + else // (0,+,0) + { + Case00(1, 0, 2, point, direction, boxExtent, result); + } + } + else + { + if (direction[2] > (Real)0) // (0,0,+) + { + Case00(2, 0, 1, point, direction, boxExtent, result); + } + else // (0,0,0) + { + Case000(point, boxExtent, result); + } + } + } + + // Undo the reflections applied previously. + for (int i = 0; i < 3; ++i) + { + if (reflect[i]) + { + point[i] = -point[i]; + } + } + + result.distance = std::sqrt(result.sqrDistance); +} + +template +void DCPQuery, AlignedBox3>::Face(int i0, int i1, + int i2, Vector3& pnt, Vector3 const& dir, + Vector3 const& PmE, Vector3 const& boxExtent, Result& result) +{ + Vector3 PpE; + Real lenSqr, inv, tmp, param, t, delta; + + PpE[i1] = pnt[i1] + boxExtent[i1]; + PpE[i2] = pnt[i2] + boxExtent[i2]; + if (dir[i0] * PpE[i1] >= dir[i1] * PmE[i0]) + { + if (dir[i0] * PpE[i2] >= dir[i2] * PmE[i0]) + { + // v[i1] >= -e[i1], v[i2] >= -e[i2] (distance = 0) + pnt[i0] = boxExtent[i0]; + inv = ((Real)1) / dir[i0]; + pnt[i1] -= dir[i1] * PmE[i0] * inv; + pnt[i2] -= dir[i2] * PmE[i0] * inv; + result.lineParameter = -PmE[i0] * inv; + } + else + { + // v[i1] >= -e[i1], v[i2] < -e[i2] + lenSqr = dir[i0] * dir[i0] + dir[i2] * dir[i2]; + tmp = lenSqr*PpE[i1] - dir[i1] * (dir[i0] * PmE[i0] + + dir[i2] * PpE[i2]); + if (tmp <= ((Real)2)*lenSqr*boxExtent[i1]) + { + t = tmp / lenSqr; + lenSqr += dir[i1] * dir[i1]; + tmp = PpE[i1] - t; + delta = dir[i0] * PmE[i0] + dir[i1] * tmp + dir[i2] * PpE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + tmp*tmp + + PpE[i2] * PpE[i2] + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = t - boxExtent[i1]; + pnt[i2] = -boxExtent[i2]; + } + else + { + lenSqr += dir[i1] * dir[i1]; + delta = dir[i0] * PmE[i0] + dir[i1] * PmE[i1] + dir[i2] * PpE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PmE[i1] * PmE[i1] + + PpE[i2] * PpE[i2] + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = boxExtent[i1]; + pnt[i2] = -boxExtent[i2]; + } + } + } + else + { + if (dir[i0] * PpE[i2] >= dir[i2] * PmE[i0]) + { + // v[i1] < -e[i1], v[i2] >= -e[i2] + lenSqr = dir[i0] * dir[i0] + dir[i1] * dir[i1]; + tmp = lenSqr*PpE[i2] - dir[i2] * (dir[i0] * PmE[i0] + + dir[i1] * PpE[i1]); + if (tmp <= ((Real)2)*lenSqr*boxExtent[i2]) + { + t = tmp / lenSqr; + lenSqr += dir[i2] * dir[i2]; + tmp = PpE[i2] - t; + delta = dir[i0] * PmE[i0] + dir[i1] * PpE[i1] + dir[i2] * tmp; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PpE[i1] * PpE[i1] + + tmp*tmp + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = -boxExtent[i1]; + pnt[i2] = t - boxExtent[i2]; + } + else + { + lenSqr += dir[i2] * dir[i2]; + delta = dir[i0] * PmE[i0] + dir[i1] * PpE[i1] + dir[i2] * PmE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PpE[i1] * PpE[i1] + + PmE[i2] * PmE[i2] + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = -boxExtent[i1]; + pnt[i2] = boxExtent[i2]; + } + } + else + { + // v[i1] < -e[i1], v[i2] < -e[i2] + lenSqr = dir[i0] * dir[i0] + dir[i2] * dir[i2]; + tmp = lenSqr*PpE[i1] - dir[i1] * (dir[i0] * PmE[i0] + + dir[i2] * PpE[i2]); + if (tmp >= (Real)0) + { + // v[i1]-edge is closest + if (tmp <= ((Real)2)*lenSqr*boxExtent[i1]) + { + t = tmp / lenSqr; + lenSqr += dir[i1] * dir[i1]; + tmp = PpE[i1] - t; + delta = dir[i0] * PmE[i0] + dir[i1] * tmp + dir[i2] * PpE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + tmp*tmp + + PpE[i2] * PpE[i2] + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = t - boxExtent[i1]; + pnt[i2] = -boxExtent[i2]; + } + else + { + lenSqr += dir[i1] * dir[i1]; + delta = dir[i0] * PmE[i0] + dir[i1] * PmE[i1] + + dir[i2] * PpE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PmE[i1] * PmE[i1] + + PpE[i2] * PpE[i2] + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = boxExtent[i1]; + pnt[i2] = -boxExtent[i2]; + } + return; + } + + lenSqr = dir[i0] * dir[i0] + dir[i1] * dir[i1]; + tmp = lenSqr*PpE[i2] - dir[i2] * (dir[i0] * PmE[i0] + + dir[i1] * PpE[i1]); + if (tmp >= (Real)0) + { + // v[i2]-edge is closest + if (tmp <= ((Real)2)*lenSqr*boxExtent[i2]) + { + t = tmp / lenSqr; + lenSqr += dir[i2] * dir[i2]; + tmp = PpE[i2] - t; + delta = dir[i0] * PmE[i0] + dir[i1] * PpE[i1] + dir[i2] * tmp; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PpE[i1] * PpE[i1] + + tmp*tmp + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = -boxExtent[i1]; + pnt[i2] = t - boxExtent[i2]; + } + else + { + lenSqr += dir[i2] * dir[i2]; + delta = dir[i0] * PmE[i0] + dir[i1] * PpE[i1] + + dir[i2] * PmE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PpE[i1] * PpE[i1] + + PmE[i2] * PmE[i2] + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = -boxExtent[i1]; + pnt[i2] = boxExtent[i2]; + } + return; + } + + // (v[i1],v[i2])-corner is closest + lenSqr += dir[i2] * dir[i2]; + delta = dir[i0] * PmE[i0] + dir[i1] * PpE[i1] + dir[i2] * PpE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PpE[i1] * PpE[i1] + + PpE[i2] * PpE[i2] + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = -boxExtent[i1]; + pnt[i2] = -boxExtent[i2]; + } + } +} + +template +void DCPQuery, AlignedBox3>::CaseNoZeros( + Vector3& pnt, Vector3 const& dir, + Vector3 const& boxExtent, Result& result) +{ + Vector3 PmE = pnt - boxExtent; + Real prodDxPy = dir[0] * PmE[1]; + Real prodDyPx = dir[1] * PmE[0]; + Real prodDzPx, prodDxPz, prodDzPy, prodDyPz; + + if (prodDyPx >= prodDxPy) + { + prodDzPx = dir[2] * PmE[0]; + prodDxPz = dir[0] * PmE[2]; + if (prodDzPx >= prodDxPz) + { + // line intersects x = e0 + Face(0, 1, 2, pnt, dir, PmE, boxExtent, result); + } + else + { + // line intersects z = e2 + Face(2, 0, 1, pnt, dir, PmE, boxExtent, result); + } + } + else + { + prodDzPy = dir[2] * PmE[1]; + prodDyPz = dir[1] * PmE[2]; + if (prodDzPy >= prodDyPz) + { + // line intersects y = e1 + Face(1, 2, 0, pnt, dir, PmE, boxExtent, result); + } + else + { + // line intersects z = e2 + Face(2, 0, 1, pnt, dir, PmE, boxExtent, result); + } + } +} + +template +void DCPQuery, AlignedBox3>::Case0(int i0, int i1, + int i2, Vector3& pnt, Vector3 const& dir, + Vector3 const& boxExtent, Result& result) +{ + Real PmE0 = pnt[i0] - boxExtent[i0]; + Real PmE1 = pnt[i1] - boxExtent[i1]; + Real prod0 = dir[i1] * PmE0; + Real prod1 = dir[i0] * PmE1; + Real delta, invLSqr, inv; + + if (prod0 >= prod1) + { + // line intersects P[i0] = e[i0] + pnt[i0] = boxExtent[i0]; + + Real PpE1 = pnt[i1] + boxExtent[i1]; + delta = prod0 - dir[i0] * PpE1; + if (delta >= (Real)0) + { + invLSqr = ((Real)1) / (dir[i0] * dir[i0] + dir[i1] * dir[i1]); + result.sqrDistance += delta*delta*invLSqr; + pnt[i1] = -boxExtent[i1]; + result.lineParameter = -(dir[i0] * PmE0 + dir[i1] * PpE1)*invLSqr; + } + else + { + inv = ((Real)1) / dir[i0]; + pnt[i1] -= prod0*inv; + result.lineParameter = -PmE0*inv; + } + } + else + { + // line intersects P[i1] = e[i1] + pnt[i1] = boxExtent[i1]; + + Real PpE0 = pnt[i0] + boxExtent[i0]; + delta = prod1 - dir[i1] * PpE0; + if (delta >= (Real)0) + { + invLSqr = ((Real)1) / (dir[i0] * dir[i0] + dir[i1] * dir[i1]); + result.sqrDistance += delta*delta*invLSqr; + pnt[i0] = -boxExtent[i0]; + result.lineParameter = -(dir[i0] * PpE0 + dir[i1] * PmE1)*invLSqr; + } + else + { + inv = ((Real)1) / dir[i1]; + pnt[i0] -= prod1*inv; + result.lineParameter = -PmE1*inv; + } + } + + if (pnt[i2] < -boxExtent[i2]) + { + delta = pnt[i2] + boxExtent[i2]; + result.sqrDistance += delta*delta; + pnt[i2] = -boxExtent[i2]; + } + else if (pnt[i2] > boxExtent[i2]) + { + delta = pnt[i2] - boxExtent[i2]; + result.sqrDistance += delta*delta; + pnt[i2] = boxExtent[i2]; + } +} + +template +void DCPQuery, AlignedBox3>::Case00(int i0, int i1, + int i2, Vector3& pnt, Vector3 const& dir, + Vector3 const& boxExtent, Result& result) +{ + Real delta; + + result.lineParameter = (boxExtent[i0] - pnt[i0]) / dir[i0]; + + pnt[i0] = boxExtent[i0]; + + if (pnt[i1] < -boxExtent[i1]) + { + delta = pnt[i1] + boxExtent[i1]; + result.sqrDistance += delta*delta; + pnt[i1] = -boxExtent[i1]; + } + else if (pnt[i1] > boxExtent[i1]) + { + delta = pnt[i1] - boxExtent[i1]; + result.sqrDistance += delta*delta; + pnt[i1] = boxExtent[i1]; + } + + if (pnt[i2] < -boxExtent[i2]) + { + delta = pnt[i2] + boxExtent[i2]; + result.sqrDistance += delta*delta; + pnt[i2] = -boxExtent[i2]; + } + else if (pnt[i2] > boxExtent[i2]) + { + delta = pnt[i2] - boxExtent[i2]; + result.sqrDistance += delta*delta; + pnt[i2] = boxExtent[i2]; + } +} + +template +void DCPQuery, AlignedBox3>::Case000( + Vector3& pnt, Vector3 const& boxExtent, Result& result) +{ + Real delta; + + if (pnt[0] < -boxExtent[0]) + { + delta = pnt[0] + boxExtent[0]; + result.sqrDistance += delta*delta; + pnt[0] = -boxExtent[0]; + } + else if (pnt[0] > boxExtent[0]) + { + delta = pnt[0] - boxExtent[0]; + result.sqrDistance += delta*delta; + pnt[0] = boxExtent[0]; + } + + if (pnt[1] < -boxExtent[1]) + { + delta = pnt[1] + boxExtent[1]; + result.sqrDistance += delta*delta; + pnt[1] = -boxExtent[1]; + } + else if (pnt[1] > boxExtent[1]) + { + delta = pnt[1] - boxExtent[1]; + result.sqrDistance += delta*delta; + pnt[1] = boxExtent[1]; + } + + if (pnt[2] < -boxExtent[2]) + { + delta = pnt[2] + boxExtent[2]; + result.sqrDistance += delta*delta; + pnt[2] = -boxExtent[2]; + } + else if (pnt[2] > boxExtent[2]) + { + delta = pnt[2] - boxExtent[2]; + result.sqrDistance += delta*delta; + pnt[2] = boxExtent[2]; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Circle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Circle3.h new file mode 100644 index 000000000000..2f735b1162a5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Circle3.h @@ -0,0 +1,465 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// The 3D line-circle distance algorithm is described in +// http://www.geometrictools.com/Documentation/DistanceToCircle3.pdf +// The notation used in the code matches that of the document. + +namespace gte +{ + +template +class DCPQuery, Circle3> +{ +public: + // The possible number of closest line-circle pairs is 1, 2 or all circle + // points. If 1 or 2, numClosestPairs is set to this number and + // 'equidistant' is false; the number of valid elements in lineClosest[] + // and circleClosest[] is numClosestPairs. If all circle points are + // closest, the line must be C+t*N where C is the circle center, N is + // the normal to the plane of the circle, and lineClosest[0] is set to C. + // In this case, 'equidistant' is true and circleClosest[0] is set to + // C+r*U, where r is the circle radius and U is a vector perpendicular + // to N. + struct Result + { + Real distance, sqrDistance; + int numClosestPairs; + Vector3 lineClosest[2], circleClosest[2]; + bool equidistant; + }; + + // The polynomial-based algorithm. Type Real can be floating-point or + // rational. + Result operator()(Line3 const& line, Circle3 const& circle); + + // The nonpolynomial-based algorithm that uses bisection. Because the + // bisection is iterative, you should choose Real to be a floating-point + // type. However, the algorithm will still work for a rational type, but + // it is costly because of the increase in arbitrary-size integers used + // during the bisection. + Result Robust(Line3 const& line, Circle3 const& circle); + +private: + // Support for operator(...). + struct ClosestInfo + { + Real sqrDistance; + Vector3 lineClosest, circleClosest; + bool equidistant; + + bool operator< (ClosestInfo const& info) const + { + return sqrDistance < info.sqrDistance; + } + }; + + void GetPair(Line3 const& line, Circle3 const& circle, + Vector3 const& D, Real t, Vector3& lineClosest, + Vector3& circleClosest); + + // Support for Robust(...). Bisect the function + // F(s) = s + m2b2 - r*m0sqr*s/sqrt(m0sqr*s*s + b1sqr) + // on the specified interval [smin,smax]. + Real Bisect(Real m2b2, Real rm0sqr, Real m0sqr, Real b1sqr, + Real smin, Real smax); +}; + + +template +typename DCPQuery, Circle3>::Result +DCPQuery, Circle3>::operator()( + Line3 const& line, Circle3 const& circle) +{ + Result result; + Vector3 const vzero = Vector3::Zero(); + Real const zero = (Real)0; + + Vector3 D = line.origin - circle.center; + Vector3 NxM = Cross(circle.normal, line.direction); + Vector3 NxD = Cross(circle.normal, D); + Real t; + + if (NxM != vzero) + { + if (NxD != vzero) + { + Real NdM = Dot(circle.normal, line.direction); + if (NdM != zero) + { + // H(t) = (a*t^2 + 2*b*t + c)*(t + d)^2 - r^2*(a*t + b)^2 + // = h0 + h1*t + h2*t^2 + h3*t^3 + h4*t^4 + Real a = Dot(NxM, NxM), b = Dot(NxM, NxD); + Real c = Dot(NxD, NxD), d = Dot(line.direction, D); + Real rsqr = circle.radius * circle.radius; + Real asqr = a * a, bsqr = b * b, dsqr = d * d; + Real h0 = c * dsqr - bsqr * rsqr; + Real h1 = 2 * (c * d + b * dsqr - a * b * rsqr); + Real h2 = c + 4 * b * d + a * dsqr - asqr * rsqr; + Real h3 = 2 * (b + a * d); + Real h4 = a; + + std::map rmMap; + RootsPolynomial::template SolveQuartic( + h0, h1, h2, h3, h4, rmMap); + std::array candidates; + int numRoots = 0; + for (auto const& rm : rmMap) + { + t = rm.first; + ClosestInfo info; + Vector3 NxDelta = NxD + t * NxM; + if (NxDelta != vzero) + { + GetPair(line, circle, D,t, info.lineClosest, + info.circleClosest); + info.equidistant = false; + } + else + { + Vector3 U = GetOrthogonal(circle.normal, true); + info.lineClosest = circle.center; + info.circleClosest = + circle.center + circle.radius * U; + info.equidistant = true; + } + Vector3 diff = info.lineClosest - info.circleClosest; + info.sqrDistance = Dot(diff, diff); + candidates[numRoots++] = info; + } + + std::sort(candidates.begin(), candidates.begin() + numRoots); + + result.numClosestPairs = 1; + result.lineClosest[0] = candidates[0].lineClosest; + result.circleClosest[0] = candidates[0].circleClosest; + if (numRoots > 1 + && candidates[1].sqrDistance == candidates[0].sqrDistance) + { + result.numClosestPairs = 2; + result.lineClosest[1] = candidates[1].lineClosest; + result.circleClosest[1] = candidates[1].circleClosest; + } + } + else + { + // The line is parallel to the plane of the circle. The + // polynomial has the form H(t) = (t+v)^2*[(t+v)^2-(r^2-u^2)]. + Real u = Dot(NxM, D), v = Dot(line.direction, D); + Real discr = circle.radius * circle.radius - u * u; + if (discr > zero) + { + result.numClosestPairs = 2; + Real rootDiscr = std::sqrt(discr); + t = -v + rootDiscr; + GetPair(line, circle, D, t, result.lineClosest[0], + result.circleClosest[0]); + t = -v - rootDiscr; + GetPair(line, circle, D, t, result.lineClosest[1], + result.circleClosest[1]); + } + else + { + result.numClosestPairs = 1; + t = -v; + GetPair(line, circle, D, t, result.lineClosest[0], + result.circleClosest[0]); + } + } + } + else + { + // The line is C+t*M, where M is not parallel to N. The polynomial + // is H(t) = |Cross(N,M)|^2*t^2*(t^2 - r^2*|Cross(N,M)|^2), where + // root t = 0 does not correspond to the global minimum. The other + // roots produce the global minimum. + result.numClosestPairs = 2; + t = circle.radius * Length(NxM); + GetPair(line, circle, D, t, result.lineClosest[0], + result.circleClosest[0]); + t = -t; + GetPair(line, circle, D, t, result.lineClosest[1], + result.circleClosest[1]); + } + result.equidistant = false; + } + else + { + if (NxD != vzero) + { + // The line is A+t*N (perpendicular to plane) but with A != C. + // The polyhomial is H(t) = |Cross(N,D)|^2*(t + Dot(M,D))^2. + result.numClosestPairs = 1; + t = -Dot(line.direction, D); + GetPair(line, circle, D, t, result.lineClosest[0], + result.circleClosest[0]); + result.equidistant = false; + } + else + { + // The line is C+t*N, so C is the closest point for the line and + // all circle points are equidistant from it. + Vector3 U = GetOrthogonal(circle.normal, true); + result.numClosestPairs = 1; + result.lineClosest[0] = circle.center; + result.circleClosest[0] = circle.center + circle.radius * U; + result.equidistant = true; + } + } + + Vector3 diff = result.lineClosest[0] - result.circleClosest[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + +template +typename DCPQuery, Circle3>::Result +DCPQuery, Circle3>::Robust( + Line3 const& line, Circle3 const& circle) +{ + // The line is P(t) = B+t*M. The circle is |X-C| = r with Dot(N,X-C)=0. + Result result; + Vector3 vzero = Vector3::Zero(); + Real const zero = (Real)0; + + Vector3 D = line.origin - circle.center; + Vector3 MxN = Cross(line.direction, circle.normal); + Vector3 DxN = Cross(D, circle.normal); + + Real m0sqr = Dot(MxN, MxN); + if (m0sqr > zero) + { + // Compute the critical points s for F'(s) = 0. + Vector3 P0, P1; + Real s, t; + int numRoots = 0; + std::array roots; + + // The line direction M and the plane normal N are not parallel. Move + // the line origin B = (b0,b1,b2) to B' = B + lambda*line.direction = + // (0,b1',b2'). + Real m0 = std::sqrt(m0sqr); + Real rm0 = circle.radius * m0; + Real lambda = -Dot(MxN, DxN) / m0sqr; + Vector3 oldD = D; + D += lambda * line.direction; + DxN += lambda * MxN; + Real m2b2 = Dot(line.direction, D); + Real b1sqr = Dot(DxN, DxN); + if (b1sqr > zero) + { + // B' = (0,b1',b2') where b1' != 0. See Sections 1.1.2 and 1.2.2 + // of the PDF documentation. + Real b1 = std::sqrt(b1sqr); + Real rm0sqr = circle.radius * m0sqr; + if (rm0sqr > b1) + { + Real const twoThirds = (Real)2 / (Real)3; + Real sHat = std::sqrt(std::pow(rm0sqr * b1sqr, twoThirds) - b1sqr) / m0; + Real gHat = rm0sqr * sHat / std::sqrt(m0sqr * sHat * sHat + b1sqr); + Real cutoff = gHat - sHat; + if (m2b2 <= -cutoff) + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2, + -m2b2 + rm0); + roots[numRoots++] = s; + if (m2b2 == -cutoff) + { + roots[numRoots++] = -sHat; + } + } + else if (m2b2 >= cutoff) + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2 - rm0, + -m2b2); + roots[numRoots++] = s; + if (m2b2 == cutoff) + { + roots[numRoots++] = sHat; + } + } + else + { + if (m2b2 <= zero) + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2, + -m2b2 + rm0); + roots[numRoots++] = s; + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2 - rm0, + -sHat); + roots[numRoots++] = s; + } + else + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2 - rm0, + -m2b2); + roots[numRoots++] = s; + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, sHat, + -m2b2 + rm0); + roots[numRoots++] = s; + } + } + } + else + { + if (m2b2 < zero) + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2, + -m2b2 + rm0); + } + else if (m2b2 > zero) + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2 - rm0, + -m2b2); + } + else + { + s = zero; + } + roots[numRoots++] = s; + } + } + else + { + // The new line origin is B' = (0,0,b2'). + if (m2b2 < zero) + { + s = -m2b2 + rm0; + roots[numRoots++] = s; + } + else if (m2b2 > zero) + { + s = -m2b2 - rm0; + roots[numRoots++] = s; + } + else + { + s = -m2b2 + rm0; + roots[numRoots++] = s; + s = -m2b2 - rm0; + roots[numRoots++] = s; + } + } + + std::array candidates; + for (int i = 0; i < numRoots; ++i) + { + t = roots[i] + lambda; + ClosestInfo info; + Vector3 NxDelta = + Cross(circle.normal, oldD + t * line.direction); + if (NxDelta != vzero) + { + GetPair(line, circle, oldD, t, info.lineClosest, + info.circleClosest); + info.equidistant = false; + } + else + { + Vector3 U = GetOrthogonal(circle.normal, true); + info.lineClosest = circle.center; + info.circleClosest = circle.center + circle.radius * U; + info.equidistant = true; + } + Vector3 diff = info.lineClosest - info.circleClosest; + info.sqrDistance = Dot(diff, diff); + candidates[i] = info; + } + + std::sort(candidates.begin(), candidates.begin() + numRoots); + + result.numClosestPairs = 1; + result.lineClosest[0] = candidates[0].lineClosest; + result.circleClosest[0] = candidates[0].circleClosest; + if (numRoots > 1 + && candidates[1].sqrDistance == candidates[0].sqrDistance) + { + result.numClosestPairs = 2; + result.lineClosest[1] = candidates[1].lineClosest; + result.circleClosest[1] = candidates[1].circleClosest; + } + + result.equidistant = false; + } + else + { + // The line direction and the plane normal are parallel. + if (DxN != vzero) + { + // The line is A+t*N but with A != C. + result.numClosestPairs = 1; + GetPair(line, circle, D, -Dot(line.direction, D), + result.lineClosest[0], result.circleClosest[0]); + result.equidistant = false; + } + else + { + // The line is C+t*N, so C is the closest point for the line and + // all circle points are equidistant from it. + Vector3 U = GetOrthogonal(circle.normal, true); + result.numClosestPairs = 1; + result.lineClosest[0] = circle.center; + result.circleClosest[0] = circle.center + circle.radius * U; + result.equidistant = true; + } + } + + Vector3 diff = result.lineClosest[0] - result.circleClosest[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + +template +void DCPQuery, Circle3>::GetPair( + Line3 const& line, Circle3 const& circle, + Vector3 const& D, Real t, Vector3& lineClosest, + Vector3& circleClosest) +{ + Vector3 delta = D + t * line.direction; + lineClosest = circle.center + delta; + delta -= Dot(circle.normal, delta) * circle.normal; + Normalize(delta); + circleClosest = circle.center + circle.radius * delta; +} + +template +Real DCPQuery, Circle3>::Bisect(Real m2b2, + Real rm0sqr, Real m0sqr, Real b1sqr, Real smin, Real smax) +{ + std::function G = [&, m2b2, rm0sqr, m0sqr, b1sqr](Real s) + { + return s + m2b2 - rm0sqr*s / std::sqrt(m0sqr*s*s + b1sqr); + }; + + // The function is known to be increasing, so we can specify -1 and +1 + // as the function values at the bounding interval endpoints. The use + // of 'double' is intentional in case Real is a BSNumber or BSRational + // type. We want the bisections to terminate in a reasonable amount of + // time. + unsigned int const maxIterations = GTE_C_MAX_BISECTIONS_GENERIC; + Real root; + RootsBisection::Find(G, smin, smax, (Real)-1, (Real)+1, + maxIterations, root); + Real gmin; + gmin = G(root); + return root; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3OrientedBox3.h new file mode 100644 index 000000000000..94f8244e3cd5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3OrientedBox3.h @@ -0,0 +1,66 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, OrientedBox3> + : + public DCPQuery, AlignedBox3> +{ +public: + struct Result + : + public DCPQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Line3 const& line, OrientedBox3 const& box); +}; + + +template +typename DCPQuery, OrientedBox3>::Result +DCPQuery, OrientedBox3>::operator()( + Line3 const& line, OrientedBox3 const& box) +{ + // Transform the line to the coordinate system of the oriented box. + // In this system, the box is axis-aligned with center at the origin. + Vector3 diff = line.origin - box.center; + Vector3 point, direction; + for (int i = 0; i < 3; ++i) + { + point[i] = Dot(diff, box.axis[i]); + direction[i] = Dot(line.direction, box.axis[i]); + } + + Result result; + this->DoQuery(point, direction, box.extent, result); + + // Compute the closest point on the line. + result.closestPoint[0] = + line.origin + result.lineParameter*line.direction; + + // Compute the closest point on the box. + result.closestPoint[1] = box.center; + for (int i = 0; i < 3; ++i) + { + result.closestPoint[1] += point[i] * box.axis[i]; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Rectangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Rectangle3.h new file mode 100644 index 000000000000..daeeb9b4e9f7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Rectangle3.h @@ -0,0 +1,140 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Rectangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real lineParameter, rectangleParameter[2]; + Vector3 closestPoint[2]; + }; + + Result operator()(Line3 const& line, + Rectangle3 const& rectangle); +}; + +// Template alias for convenience. +template +using DCPLine3Rectangle3 = +DCPQuery, Rectangle3>; + + +template +typename DCPQuery, Rectangle3>::Result +DCPQuery, Rectangle3>::operator()( + Line3 const& line, Rectangle3 const& rectangle) +{ + Result result; + + // Test if line intersects rectangle. If so, the squared distance is + // zero. + Vector3 N = Cross(rectangle.axis[0], rectangle.axis[1]); + Real NdD = Dot(N, line.direction); + if (std::abs(NdD) > (Real)0) + { + // The line and rectangle are not parallel, so the line intersects + // the plane of the rectangle. + Vector3 diff = line.origin - rectangle.center; + Vector3 basis[3]; // {D, U, V} + basis[0] = line.direction; + ComputeOrthogonalComplement(1, basis); + Real UdD0 = Dot(basis[1], rectangle.axis[0]); + Real UdD1 = Dot(basis[1], rectangle.axis[1]); + Real UdPmC = Dot(basis[1], diff); + Real VdD0 = Dot(basis[2], rectangle.axis[0]); + Real VdD1 = Dot(basis[2], rectangle.axis[1]); + Real VdPmC = Dot(basis[2], diff); + Real invDet = ((Real)1) / (UdD0*VdD1 - UdD1*VdD0); + + // Rectangle coordinates for the point of intersection. + Real s0 = (VdD1*UdPmC - UdD1*VdPmC)*invDet; + Real s1 = (UdD0*VdPmC - VdD0*UdPmC)*invDet; + + if (std::abs(s0) <= rectangle.extent[0] + && std::abs(s1) <= rectangle.extent[1]) + { + // Line parameter for the point of intersection. + Real DdD0 = Dot(line.direction, rectangle.axis[0]); + Real DdD1 = Dot(line.direction, rectangle.axis[1]); + Real DdDiff = Dot(line.direction, diff); + result.lineParameter = s0*DdD0 + s1*DdD1 - DdDiff; + + // Rectangle coordinates for the point of intersection. + result.rectangleParameter[0] = s0; + result.rectangleParameter[1] = s1; + + // The intersection point is inside or on the rectangle. + result.closestPoint[0] = line.origin + + result.lineParameter*line.direction; + + result.closestPoint[1] = rectangle.center + + s0*rectangle.axis[0] + s1*rectangle.axis[1]; + + result.distance = (Real)0; + result.sqrDistance = (Real)0; + return result; + } + } + + // Either (1) the line is not parallel to the rectangle and the point of + // intersection of the line and the plane of the rectangle is outside the + // rectangle or (2) the line and rectangle are parallel. Regardless, the + // closest point on the rectangle is on an edge of the rectangle. Compare + // the line to all four edges of the rectangle. + result.distance = std::numeric_limits::max(); + result.sqrDistance = std::numeric_limits::max(); + Vector3 scaledDir[2] = + { + rectangle.extent[0] * rectangle.axis[0], + rectangle.extent[1] * rectangle.axis[1] + }; + for (int i1 = 0, omi1 = 1; i1 <= 1; ++i1, --omi1) + { + for (int i0 = -1; i0 <= 1; i0 += 2) + { + Vector3 segCenter = + rectangle.center + scaledDir[i1] * (Real)i0; + Vector3 segDirection = rectangle.axis[omi1]; + Real segExtent = rectangle.extent[omi1]; + Segment3 segment(segCenter, segDirection, segExtent); + + DCPQuery, Segment3> query; + auto lsResult = query(line, segment); + if (lsResult.sqrDistance < result.sqrDistance) + { + result.sqrDistance = lsResult.sqrDistance; + result.distance = lsResult.distance; + result.lineParameter = lsResult.parameter[0]; + Real ratio = lsResult.parameter[1] / segExtent; // in [-1,1] + result.rectangleParameter[0] = + rectangle.extent[0] * (omi1 * i0 + i1 * ratio); + result.rectangleParameter[1] = + rectangle.extent[1] * (i1 * i0 + omi1 * ratio); + result.closestPoint[0] = lsResult.closestPoint[0]; + result.closestPoint[1] = lsResult.closestPoint[1]; + } + } + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Triangle3.h new file mode 100644 index 000000000000..30486a3b7384 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Triangle3.h @@ -0,0 +1,127 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Triangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real lineParameter, triangleParameter[3]; + Vector3 closestPoint[2]; + }; + + Result operator()(Line3 const& line, + Triangle3 const& triangle); +}; + + +template +typename DCPQuery, Triangle3>::Result +DCPQuery, Triangle3>::operator()( + Line3 const& line, Triangle3 const& triangle) +{ + Result result; + + // Test if line intersects triangle. If so, the squared distance is zero. + Vector3 edge0 = triangle.v[1] - triangle.v[0]; + Vector3 edge1 = triangle.v[2] - triangle.v[0]; + Vector3 normal = UnitCross(edge0, edge1); + Real NdD = Dot(normal, line.direction); + if (std::abs(NdD) > (Real)0) + { + // The line and triangle are not parallel, so the line intersects + // the plane of the triangle. + Vector3 diff = line.origin - triangle.v[0]; + Vector3 basis[3]; // {D, U, V} + basis[0] = line.direction; + ComputeOrthogonalComplement(1, basis); + Real UdE0 = Dot(basis[1], edge0); + Real UdE1 = Dot(basis[1], edge1); + Real UdDiff = Dot(basis[1], diff); + Real VdE0 = Dot(basis[2], edge0); + Real VdE1 = Dot(basis[2], edge1); + Real VdDiff = Dot(basis[2], diff); + Real invDet = ((Real)1) / (UdE0*VdE1 - UdE1*VdE0); + + // Barycentric coordinates for the point of intersection. + Real b1 = (VdE1*UdDiff - UdE1*VdDiff)*invDet; + Real b2 = (UdE0*VdDiff - VdE0*UdDiff)*invDet; + Real b0 = (Real)1 - b1 - b2; + + if (b0 >= (Real)0 && b1 >= (Real)0 && b2 >= (Real)0) + { + // Line parameter for the point of intersection. + Real DdE0 = Dot(line.direction, edge0); + Real DdE1 = Dot(line.direction, edge1); + Real DdDiff = Dot(line.direction, diff); + result.lineParameter = b1*DdE0 + b2*DdE1 - DdDiff; + + // Barycentric coordinates for the point of intersection. + result.triangleParameter[0] = b0; + result.triangleParameter[1] = b1; + result.triangleParameter[2] = b2; + + // The intersection point is inside or on the triangle. + result.closestPoint[0] = line.origin + + result.lineParameter*line.direction; + result.closestPoint[1] = triangle.v[0] + b1*edge0 + b2*edge1; + + result.distance = (Real)0; + result.sqrDistance = (Real)0; + return result; + } + } + + // Either (1) the line is not parallel to the triangle and the point of + // intersection of the line and the plane of the triangle is outside the + // triangle or (2) the line and triangle are parallel. Regardless, the + // closest point on the triangle is on an edge of the triangle. Compare + // the line to all three edges of the triangle. + result.distance = std::numeric_limits::max(); + result.sqrDistance = std::numeric_limits::max(); + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + Vector3 segCenter = ((Real)0.5)*(triangle.v[i0] + + triangle.v[i1]); + Vector3 segDirection = triangle.v[i1] - triangle.v[i0]; + Real segExtent = ((Real)0.5)*Normalize(segDirection); + Segment3 segment(segCenter, segDirection, segExtent); + + DCPQuery, Segment3> query; + auto lsResult = query(line, segment); + if (lsResult.sqrDistance < result.sqrDistance) + { + result.sqrDistance = lsResult.sqrDistance; + result.distance = lsResult.distance; + result.lineParameter = lsResult.parameter[0]; + result.triangleParameter[i0] = ((Real)0.5)*((Real)1 - + lsResult.parameter[0] / segExtent); + result.triangleParameter[i1] = (Real)1 - + result.triangleParameter[i0]; + result.triangleParameter[3 - i0 - i1] = (Real)0; + result.closestPoint[0] = lsResult.closestPoint[0]; + result.closestPoint[1] = lsResult.closestPoint[1]; + } + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineLine.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineLine.h new file mode 100644 index 000000000000..0d83130dd760 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineLine.h @@ -0,0 +1,79 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, Line> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closestPoint[2]; + }; + + Result operator()(Line const& line0, Line const& line1); +}; + +// Template aliases for convenience. +template +using DCPLineLine = DCPQuery, Line>; + +template +using DCPLine2Line2 = DCPLineLine<2, Real>; + +template +using DCPLine3Line3 = DCPLineLine<3, Real>; + + +template +typename DCPQuery, Line>::Result +DCPQuery, Line>::operator()( + Line const& line0, Line const& line1) +{ + Result result; + + Vector diff = line0.origin - line1.origin; + Real a01 = -Dot(line0.direction, line1.direction); + Real b0 = Dot(diff, line0.direction); + Real s0, s1; + + if (std::abs(a01) < (Real)1) + { + // Lines are not parallel. + Real det = (Real)1 - a01 * a01; + Real b1 = -Dot(diff, line1.direction); + s0 = (a01 * b1 - b0) / det; + s1 = (a01 * b0 - b1) / det; + } + else + { + // Lines are parallel, select any pair of closest points. + s0 = -b0; + s1 = (Real)0; + } + + result.parameter[0] = s0; + result.parameter[1] = s1; + result.closestPoint[0] = line0.origin + s0 * line0.direction; + result.closestPoint[1] = line1.origin + s1 * line1.direction; + diff = result.closestPoint[0] - result.closestPoint[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineRay.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineRay.h new file mode 100644 index 000000000000..6227b0bc5629 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineRay.h @@ -0,0 +1,91 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Ray> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closestPoint[2]; + }; + + Result operator()(Line const& line, Ray const& ray); +}; + +// Template aliases for convenience. +template +using DCPLineRay = DCPQuery, Ray>; + +template +using DCPLine2Ray2 = DCPLineRay<2, Real>; + +template +using DCPLine3Ray3 = DCPLineRay<3, Real>; + + +template +typename DCPQuery, Ray>::Result +DCPQuery, Ray>::operator()( + Line const& line, Ray const& ray) +{ + Result result; + + Vector diff = line.origin - ray.origin; + Real a01 = -Dot(line.direction, ray.direction); + Real b0 = Dot(diff, line.direction); + Real s0, s1; + + if (std::abs(a01) < (Real)1) + { + Real b1 = -Dot(diff, ray.direction); + s1 = a01 * b0 - b1; + + if (s1 >= (Real)0) + { + // Two interior points are closest, one on line and one on ray. + Real det = (Real)1 - a01 * a01; + s0 = (a01 * b1 - b0) / det; + s1 /= det; + } + else + { + // Origin of ray and interior point of line are closest. + s0 = -b0; + s1 = (Real)0; + } + } + else + { + // Lines are parallel, closest pair with one point at ray origin. + s0 = -b0; + s1 = (Real)0; + } + + result.parameter[0] = s0; + result.parameter[1] = s1; + result.closestPoint[0] = line.origin + s0 * line.direction; + result.closestPoint[1] = ray.origin + s1 * ray.direction; + diff = result.closestPoint[0] - result.closestPoint[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineSegment.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineSegment.h new file mode 100644 index 000000000000..4f6129468f1e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineSegment.h @@ -0,0 +1,113 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Segment> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closestPoint[2]; + }; + + // The centered form of the 'segment' is used. Thus, parameter[1] of + // the result is in [-e,e], where e = |segment.p[1] - segment.p[0]|/2. + Result operator()(Line const& line, + Segment const& segment); +}; + +// Template aliases for convenience. +template +using DCPLineSegment = DCPQuery, Segment>; + +template +using DCPLine2Segment2 = DCPLineSegment<2, Real>; + +template +using DCPLine3Segment3 = DCPLineSegment<3, Real>; + + +template +typename DCPQuery, Segment>::Result +DCPQuery, Segment>::operator()( + Line const& line, Segment const& segment) +{ + Result result; + + Vector segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Vector diff = line.origin - segCenter; + Real a01 = -Dot(line.direction, segDirection); + Real b0 = Dot(diff, line.direction); + Real s0, s1; + + if (std::abs(a01) < (Real)1) + { + // The line and segment are not parallel. + Real det = (Real)1 - a01 * a01; + Real extDet = segExtent * det; + Real b1 = -Dot(diff, segDirection); + s1 = a01 * b0 - b1; + + if (s1 >= -extDet) + { + if (s1 <= extDet) + { + // Two interior points are closest, one on the line and one + // on the segment. + s0 = (a01 * b1 - b0) / det; + s1 /= det; + } + else + { + // The endpoint e1 of the segment and an interior point of + // the line are closest. + s1 = segExtent; + s0 = -(a01 * s1 + b0); + } + } + else + { + // The endpoint e0 of the segment and an interior point of the + // line are closest. + s1 = -segExtent; + s0 = -(a01 * s1 + b0); + } + } + else + { + // The line and segment are parallel. Choose the closest pair so that + // one point is at segment origin. + s1 = (Real)0; + s0 = -b0; + } + + result.parameter[0] = s0; + result.parameter[1] = s1; + result.closestPoint[0] = line.origin + s0 * line.direction; + result.closestPoint[1] = segCenter + s1 * segDirection; + diff = result.closestPoint[0] - result.closestPoint[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistOrientedBox3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistOrientedBox3OrientedBox3.h new file mode 100644 index 000000000000..d23b64b20b62 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistOrientedBox3OrientedBox3.h @@ -0,0 +1,162 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, OrientedBox3> +{ +public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array box0Parameter, box1Parameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of whether + // the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 144 (n = 12, maxIterations = n*n). If + // the solver fails to converge, try increasing the maximum number of + // iterations. + void SetMaxLCPIterations(int maxLCPIterations); + + Result operator()(OrientedBox3 const& box0, OrientedBox3 const& box1); + +private: + LCPSolver mLCP; +}; + + +template +void DCPQuery, OrientedBox3>::SetMaxLCPIterations(int maxLCPIterations) +{ + mLCP.SetMaxIterations(maxLCPIterations); +} + +template +typename DCPQuery, OrientedBox3>::Result + DCPQuery, OrientedBox3>::operator()( + OrientedBox3 const& box0, OrientedBox3 const& box1) +{ + Result result; + + // Translate the center of box0 to the origin. Modify the oriented box + // coefficients to be nonnegative. + Vector3 delta = box1.center - box0.center; + for (int i = 0; i < 3; ++i) + { + delta += box0.extent[i] * box0.axis[i]; + delta -= box1.extent[i] * box1.axis[i]; + } + + Vector3 R0Delta, R1Delta; + for (int i = 0; i < 3; ++i) + { + R0Delta[i] = Dot(box0.axis[i], delta); + R1Delta[i] = Dot(box1.axis[i], delta); + } + + std::array, 3> R0TR1; + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + R0TR1[r][c] = Dot(box0.axis[r], box1.axis[c]); + } + } + + Vector3 twoExtent0 = box0.extent * (Real)2; + Vector3 twoExtent1 = box1.extent * (Real)2; + + // The LCP has 6 variables and 6 (nontrivial) inequality constraints. + std::array q = + { + -R0Delta[0], -R0Delta[1], -R0Delta[2], R1Delta[0], R1Delta[1], R1Delta[2], + twoExtent0[0], twoExtent0[1], twoExtent0[2], twoExtent1[0], twoExtent1[1], twoExtent1[2] + }; + + std::array, 12> M; + { + Real const z = (Real)0; + Real const p = (Real)1; + Real const m = (Real)-1; + M[ 0] = { p, z, z, -R0TR1[0][0], -R0TR1[0][1], -R0TR1[0][2], p, z, z, z, z, z }; + M[ 1] = { z, p, z, -R0TR1[1][0], -R0TR1[1][1], -R0TR1[1][2], z, p, z, z, z, z }; + M[ 2] = { z, z, p, -R0TR1[2][0], -R0TR1[2][1], -R0TR1[2][2], z, z, p, z, z, z }; + M[ 3] = { -R0TR1[0][0], -R0TR1[1][0], -R0TR1[2][0], p, z, z, z, z, z, p, z, z }; + M[ 4] = { -R0TR1[0][1], -R0TR1[1][1], -R0TR1[2][1], z, p, z, z, z, z, z, p, z }; + M[ 5] = { -R0TR1[0][2], -R0TR1[1][2], -R0TR1[2][2], z, z, p, z, z, z, z, z, p }; + M[ 6] = { m, z, z, z, z, z, z, z, z, z, z, z }; + M[ 7] = { z, m, z, z, z, z, z, z, z, z, z, z }; + M[ 8] = { z, z, m, z, z, z, z, z, z, z, z, z }; + M[ 9] = { z, z, z, m, z, z, z, z, z, z, z, z }; + M[10] = { z, z, z, z, m, z, z, z, z, z, z, z }; + M[11] = { z, z, z, z, z, m, z, z, z, z, z, z }; + } + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + + result.closestPoint[0] = box0.center; + for (int i = 0; i < 3; ++i) + { + result.box0Parameter[i] = z[i] - box0.extent[i]; + result.closestPoint[0] += result.box0Parameter[i] * box0.axis[i]; + } + + result.closestPoint[1] = box1.center; + for (int i = 0, j = 3; i < 3; ++i, ++j) + { + result.box1Parameter[i] = z[j] - box1.extent[i]; + result.closestPoint[1] += result.box1Parameter[i] * box1.axis[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations was not + // specified to be large enough or there is a problem due to + // floating-point rounding errors. If you believe the latter is + // true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 3; ++i) + { + result.box0Parameter[i] = (Real)0; + result.box1Parameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Circle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Circle3.h new file mode 100644 index 000000000000..9eacd922bc00 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Circle3.h @@ -0,0 +1,76 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include + +// The 3D point-circle distance algorithm is described in +// http://www.geometrictools.com/Documentation/DistanceToCircle3.pdf +// The notation used in the code matches that of the document. + +namespace gte +{ + +template +class DCPQuery, Circle3> +{ +public: + // Either a single point on the circle is closest to 'point', in which + // case 'equidistant' is false, or the entire circle is closest to + // 'point', in which case 'equidistant' is true. In the latter case, the + // query returns the circle point C+r*U, where C is the circle center, r + // is the circle radius, and U is a vector perpendicular to the normal N + // for the plane of the circle. + struct Result + { + Real distance, sqrDistance; + Vector3 circleClosest; + bool equidistant; + }; + + Result operator()(Vector3 const& point, + Circle3 const& circle); +}; + + +template +typename DCPQuery, Circle3>::Result +DCPQuery, Circle3>::operator()( + Vector3 const& point, Circle3 const& circle) +{ + Result result; + + // Projection of P-C onto plane is Q-C = P-C - Dot(N,P-C)*N. + Vector3 PmC = point - circle.center; + Vector3 QmC = PmC - Dot(circle.normal, PmC)*circle.normal; + Real lengthQmC = Length(QmC); + if (lengthQmC > (Real)0) + { + result.circleClosest = + circle.center + (circle.radius / lengthQmC) * QmC; + result.equidistant = false; + } + else + { + // All circle points are equidistant from P. Return one of them. + Vector3 basis[3]; + basis[0] = circle.normal; + ComputeOrthogonalComplement(1, basis); + result.circleClosest = circle.center + circle.radius * basis[1]; + result.equidistant = true; + } + + Vector3 diff = point - result.circleClosest; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3ConvexPolyhedron3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3ConvexPolyhedron3.h new file mode 100644 index 000000000000..fd3e051a9dd8 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3ConvexPolyhedron3.h @@ -0,0 +1,188 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, ConvexPolyhedron3> +{ +public: + // Construction. If you have no knowledge of the number of faces for the + // convex polyhedra you plan on applying the query to, pass 'numTriangles' + // of zero. This is a request to the operator() function to create the + // LCP solver for each query, and this requires memory allocation and + // deallocation per query. If you plan on applying the query multiple + // times to a single polyhedron, even if the vertices of the polyhedron + // are modified for each query, then pass 'numTriangles' to be the number + // of triangle faces for that polyhedron. This lets the operator() + // function know to create the LCP solver once at construction time, thus + // avoiding the memory management costs during the query. + DCPQuery(int numTriangles = 0); + + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of whether + // the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 81 (n = 9, maxIterations = n*n). If + // the solver fails to converge, try increasing the maximum number of + // iterations. + void SetMaxLCPIterations(int maxLCPIterations); + + Result operator()(Vector3 const& point, ConvexPolyhedron3 const& polyhedron); + +private: + int mMaxLCPIterations; + std::unique_ptr> mLCP; +}; + + +template +DCPQuery, ConvexPolyhedron3>::DCPQuery(int numTriangles) +{ + if (numTriangles > 0) + { + int const n = numTriangles + 3; + mLCP = std::make_unique>(n); + mMaxLCPIterations = mLCP->GetMaxIterations(); + } + else + { + mMaxLCPIterations = 0; + } +} + +template +void DCPQuery, ConvexPolyhedron3>::SetMaxLCPIterations(int maxLCPIterations) +{ + mMaxLCPIterations = maxLCPIterations; + if (mLCP) + { + mLCP->SetMaxIterations(mMaxLCPIterations); + } +} + +template +typename DCPQuery, ConvexPolyhedron3>::Result +DCPQuery, ConvexPolyhedron3>::operator()( + Vector3 const& point, ConvexPolyhedron3 const& polyhedron) +{ + Result result; + + int const numTriangles = static_cast(polyhedron.planes.size()); + if (numTriangles == 0) + { + // The polyhedron planes and aligned box need to be created. + result.queryIsSuccessful = false; + for (int i = 0; i < 3; ++i) + { + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + result.numLCPIterations = 0; + return result; + } + + int const n = numTriangles + 3; + + // Translate the point and convex polyhedron so that the polyhedron is in + // the first octant. The translation is not explicit; rather, the q and M + // for the LCP are initialized using the translation information. + Vector4 hmin = HLift(polyhedron.alignedBox.min, (Real)1); + + std::vector q(n); + for (int r = 0; r < 3; ++r) + { + q[r] = polyhedron.alignedBox.min[r] - point[r]; + } + for (int r = 3, t = 0; r < n; ++r, ++t) + { + q[r] = -Dot(polyhedron.planes[t], hmin); + } + + std::vector M(n * n); + M[0] = (Real)1; M[1] = (Real)0; M[2] = (Real)0; + M[n] = (Real)0; M[n + 1] = (Real)1; M[n + 2] = (Real)0; + M[2 * n] = (Real)0; M[2 * n + 1] = (Real)0; M[2 * n + 2] = (Real)1; + for (int t = 0, c = 3; t < numTriangles; ++t, ++c) + { + Vector3 normal = HProject(polyhedron.planes[t]); + for (int r = 0; r < 3; ++r) + { + M[c + n * r] = normal[r]; + M[r + n * c] = -normal[r]; + } + } + for (int r = 3; r < n; ++r) + { + for (int c = 3; c < n; ++c) + { + M[c + n * r] = (Real)0; + } + } + + bool needsLCP = (mLCP == nullptr); + if (needsLCP) + { + mLCP = std::make_unique>(n); + if (mMaxLCPIterations > 0) + { + mLCP->SetMaxIterations(mMaxLCPIterations); + } + } + + std::vector w(n), z(n); + if (mLCP->Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + result.closestPoint[0] = point; + for (int i = 0; i < 3; ++i) + { + result.closestPoint[1][i] = z[i] + polyhedron.alignedBox.min[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations was not + // specified to be large enough or there is a problem due to + // floating-point rounding errors. If you believe the latter is + // true, file a bug report. + result.queryIsSuccessful = false; + } + + result.numLCPIterations = mLCP->GetNumIterations(); + if (needsLCP) + { + mLCP = nullptr; + } + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Cylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Cylinder3.h new file mode 100644 index 000000000000..da08b60cb210 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Cylinder3.h @@ -0,0 +1,126 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +// The queries consider the cylinder to be a solid. + +namespace gte +{ + +template +class DCPQuery, Cylinder3> +{ +public: + struct Result + { + Real distance; + Vector3 cylinderClosest; + }; + + Result operator()(Vector3 const& point, + Cylinder3 const& cylinder); + +private: + void DoQueryInfiniteCylinder(Vector3 const& P, Real radius, + Result& result); + + void DoQueryFiniteCylinder(Vector3 const& P, Real radius, + Real height, Result& result); +}; + + +template +typename DCPQuery, Cylinder3>::Result +DCPQuery, Cylinder3>::operator()( + Vector3 const& point, Cylinder3 const& cylinder) +{ + Result result; + + // Convert the point to the cylinder coordinate system. In this system, + // the point believes (0,0,0) is the cylinder axis origin and (0,0,1) is + // the cylinder axis direction. + Vector3 basis[3]; + basis[0] = cylinder.axis.direction; + ComputeOrthogonalComplement(1, basis); + + Vector3 delta = point - cylinder.axis.origin; + Vector3 P + { + Dot(basis[1], delta), + Dot(basis[2], delta), + Dot(basis[0], delta) + }; + + if (cylinder.height == std::numeric_limits::max()) + { + DoQueryInfiniteCylinder(P, cylinder.radius, result); + } + else + { + DoQueryFiniteCylinder(P, cylinder.radius, cylinder.height, result); + } + + // Convert the closest point from the cylinder coordinate system to the + // original coordinate system. + result.cylinderClosest = cylinder.axis.origin + + result.cylinderClosest[0] * basis[1] + + result.cylinderClosest[1] * basis[2] + + result.cylinderClosest[2] * basis[0]; + + return result; +} + +template +void DCPQuery, Cylinder3>::DoQueryInfiniteCylinder( + Vector3 const& P, Real radius, Result& result) +{ + Real sqrRadius = radius * radius; + Real sqrDistance = P[0] * P[0] + P[1] * P[1]; + if (sqrDistance >= sqrRadius) + { + // The point is outside the cylinder or on the cylinder wall. + Real distance = std::sqrt(sqrDistance); + result.distance = distance - radius; + Real temp = radius / distance; + result.cylinderClosest[0] = P[0] * temp; + result.cylinderClosest[1] = P[1] * temp; + result.cylinderClosest[2] = P[2]; + } + else + { + // The point is inside the cylinder. + result.distance = (Real)0; + result.cylinderClosest = P; + } +} + +template +void DCPQuery, Cylinder3>::DoQueryFiniteCylinder( + Vector3 const& P, Real radius, Real height, Result& result) +{ + DoQueryInfiniteCylinder(P, radius, result); + + // Clamp the infinite cylinder's closest point to the finite cylinder. + if (result.cylinderClosest[2] > height) + { + result.cylinderClosest[2] = height; + result.distance = Length(result.cylinderClosest - P); + } + else if (result.cylinderClosest[2] < -height) + { + result.cylinderClosest[2] = -height; + result.distance = Length(result.cylinderClosest - P); + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Frustum3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Frustum3.h new file mode 100644 index 000000000000..3e69cbea750a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Frustum3.h @@ -0,0 +1,430 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, Frustum3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Vector3 frustumClosestPoint; + }; + + Result operator()(Vector3 const& point, + Frustum3 const& frustum); +}; + + +template +typename DCPQuery, Frustum3>::Result +DCPQuery, Frustum3>::operator()( + Vector3 const& point, Frustum3 const& frustum) +{ + Result result; + + // Compute coordinates of point with respect to frustum coordinate system. + Vector3 diff = point - frustum.origin; + Vector3 test = { + Dot(diff, frustum.rVector), + Dot(diff, frustum.uVector), + Dot(diff, frustum.dVector) }; + + // Perform calculations in octant with nonnegative R and U coordinates. + bool rSignChange; + if (test[0] < (Real)0) + { + rSignChange = true; + test[0] = -test[0]; + } + else + { + rSignChange = false; + } + + bool uSignChange; + if (test[1] < (Real)0) + { + uSignChange = true; + test[1] = -test[1]; + } + else + { + uSignChange = false; + } + + // Frustum derived parameters. + Real rmin = frustum.rBound; + Real rmax = frustum.GetDRatio()*rmin; + Real umin = frustum.uBound; + Real umax = frustum.GetDRatio()*umin; + Real dmin = frustum.dMin; + Real dmax = frustum.dMax; + Real rminSqr = rmin*rmin; + Real uminSqr = umin*umin; + Real dminSqr = dmin*dmin; + Real minRDDot = rminSqr + dminSqr; + Real minUDDot = uminSqr + dminSqr; + Real minRUDDot = rminSqr + minUDDot; + Real maxRDDot = frustum.GetDRatio()*minRDDot; + Real maxUDDot = frustum.GetDRatio()*minUDDot; + Real maxRUDDot = frustum.GetDRatio()*minRUDDot; + + // Algorithm computes closest point in all cases by determining in which + // Voronoi region of the vertices, edges, and faces of the frustum that + // the test point lives. + Vector3 closest; + Real rDot, uDot, rdDot, udDot, rudDot, rEdgeDot, uEdgeDot, t; + if (test[2] >= dmax) + { + if (test[0] <= rmax) + { + if (test[1] <= umax) + { + // F-face + closest[0] = test[0]; + closest[1] = test[1]; + closest[2] = dmax; + } + else + { + // UF-edge + closest[0] = test[0]; + closest[1] = umax; + closest[2] = dmax; + } + } + else + { + if (test[1] <= umax) + { + // LF-edge + closest[0] = rmax; + closest[1] = test[1]; + closest[2] = dmax; + } + else + { + // LUF-vertex + closest[0] = rmax; + closest[1] = umax; + closest[2] = dmax; + } + } + } + else if (test[2] <= dmin) + { + if (test[0] <= rmin) + { + if (test[1] <= umin) + { + // N-face + closest[0] = test[0]; + closest[1] = test[1]; + closest[2] = dmin; + } + else + { + udDot = umin*test[1] + dmin*test[2]; + if (udDot >= maxUDDot) + { + // UF-edge + closest[0] = test[0]; + closest[1] = umax; + closest[2] = dmax; + } + else if (udDot >= minUDDot) + { + // U-face + uDot = dmin*test[1] - umin*test[2]; + t = uDot / minUDDot; + closest[0] = test[0]; + closest[1] = test[1] - t*dmin; + closest[2] = test[2] + t*umin; + } + else + { + // UN-edge + closest[0] = test[0]; + closest[1] = umin; + closest[2] = dmin; + } + } + } + else + { + if (test[1] <= umin) + { + rdDot = rmin*test[0] + dmin*test[2]; + if (rdDot >= maxRDDot) + { + // LF-edge + closest[0] = rmax; + closest[1] = test[1]; + closest[2] = dmax; + } + else if (rdDot >= minRDDot) + { + // L-face + rDot = dmin*test[0] - rmin*test[2]; + t = rDot / minRDDot; + closest[0] = test[0] - t*dmin; + closest[1] = test[1]; + closest[2] = test[2] + t*rmin; + } + else + { + // LN-edge + closest[0] = rmin; + closest[1] = test[1]; + closest[2] = dmin; + } + } + else + { + rudDot = rmin*test[0] + umin*test[1] + dmin*test[2]; + rEdgeDot = umin*rudDot - minRUDDot*test[1]; + if (rEdgeDot >= (Real)0) + { + rdDot = rmin*test[0] + dmin*test[2]; + if (rdDot >= maxRDDot) + { + // LF-edge + closest[0] = rmax; + closest[1] = test[1]; + closest[2] = dmax; + } + else if (rdDot >= minRDDot) + { + // L-face + rDot = dmin*test[0] - rmin*test[2]; + t = rDot / minRDDot; + closest[0] = test[0] - t*dmin; + closest[1] = test[1]; + closest[2] = test[2] + t*rmin; + } + else + { + // LN-edge + closest[0] = rmin; + closest[1] = test[1]; + closest[2] = dmin; + } + } + else + { + uEdgeDot = rmin*rudDot - minRUDDot*test[0]; + if (uEdgeDot >= (Real)0) + { + udDot = umin*test[1] + dmin*test[2]; + if (udDot >= maxUDDot) + { + // UF-edge + closest[0] = test[0]; + closest[1] = umax; + closest[2] = dmax; + } + else if (udDot >= minUDDot) + { + // U-face + uDot = dmin*test[1] - umin*test[2]; + t = uDot / minUDDot; + closest[0] = test[0]; + closest[1] = test[1] - t*dmin; + closest[2] = test[2] + t*umin; + } + else + { + // UN-edge + closest[0] = test[0]; + closest[1] = umin; + closest[2] = dmin; + } + } + else + { + if (rudDot >= maxRUDDot) + { + // LUF-vertex + closest[0] = rmax; + closest[1] = umax; + closest[2] = dmax; + } + else if (rudDot >= minRUDDot) + { + // LU-edge + t = rudDot / minRUDDot; + closest[0] = t*rmin; + closest[1] = t*umin; + closest[2] = t*dmin; + } + else + { + // LUN-vertex + closest[0] = rmin; + closest[1] = umin; + closest[2] = dmin; + } + } + } + } + } + } + else + { + rDot = dmin*test[0] - rmin*test[2]; + uDot = dmin*test[1] - umin*test[2]; + if (rDot <= (Real)0.0) + { + if (uDot <= (Real)0) + { + // point inside frustum + closest = test; + } + else + { + udDot = umin*test[1] + dmin*test[2]; + if (udDot >= maxUDDot) + { + // UF-edge + closest[0] = test[0]; + closest[1] = umax; + closest[2] = dmax; + } + else + { + // U-face + t = uDot / minUDDot; + closest[0] = test[0]; + closest[1] = test[1] - t*dmin; + closest[2] = test[2] + t*umin; + } + } + } + else + { + if (uDot <= (Real)0) + { + rdDot = rmin*test[0] + dmin*test[2]; + if (rdDot >= maxRDDot) + { + // LF-edge + closest[0] = rmax; + closest[1] = test[1]; + closest[2] = dmax; + } + else + { + // L-face + t = rDot / minRDDot; + closest[0] = test[0] - t*dmin; + closest[1] = test[1]; + closest[2] = test[2] + t*rmin; + } + } + else + { + rudDot = rmin*test[0] + umin*test[1] + dmin*test[2]; + rEdgeDot = umin*rudDot - minRUDDot*test[1]; + if (rEdgeDot >= (Real)0) + { + rdDot = rmin*test[0] + dmin*test[2]; + if (rdDot >= maxRDDot) + { + // LF-edge + closest[0] = rmax; + closest[1] = test[1]; + closest[2] = dmax; + } + else // assert( rdDot >= minRDDot ) from geometry + { + // L-face + t = rDot / minRDDot; + closest[0] = test[0] - t*dmin; + closest[1] = test[1]; + closest[2] = test[2] + t*rmin; + } + } + else + { + uEdgeDot = rmin*rudDot - minRUDDot*test[0]; + if (uEdgeDot >= (Real)0) + { + udDot = umin*test[1] + dmin*test[2]; + if (udDot >= maxUDDot) + { + // UF-edge + closest[0] = test[0]; + closest[1] = umax; + closest[2] = dmax; + } + else // assert( udDot >= minUDDot ) from geometry + { + // U-face + t = uDot / minUDDot; + closest[0] = test[0]; + closest[1] = test[1] - t*dmin; + closest[2] = test[2] + t*umin; + } + } + else + { + if (rudDot >= maxRUDDot) + { + // LUF-vertex + closest[0] = rmax; + closest[1] = umax; + closest[2] = dmax; + } + else // assert( rudDot >= minRUDDot ) from geometry + { + // LU-edge + t = rudDot / minRUDDot; + closest[0] = t*rmin; + closest[1] = t*umin; + closest[2] = t*dmin; + } + } + } + } + } + } + + diff = test - closest; + + // Convert back to original quadrant. + if (rSignChange) + { + closest[0] = -closest[0]; + } + + if (uSignChange) + { + closest[1] = -closest[1]; + } + + // Convert back to original coordinates. + result.frustumClosestPoint = frustum.origin + + closest[0] * frustum.rVector + + closest[1] * frustum.uVector + + closest[2] * frustum.dVector; + + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Plane3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Plane3.h new file mode 100644 index 000000000000..cf36fab76a99 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Plane3.h @@ -0,0 +1,44 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Plane3> +{ +public: + struct Result + { + Real distance, signedDistance; + Vector3 planeClosestPoint; + }; + + Result operator()(Vector3 const& point, Plane3 const& plane); +}; + + +template +typename DCPQuery, Plane3>::Result +DCPQuery, Plane3>::operator()( + Vector3 const& point, Plane3 const& plane) +{ + Result result; + result.signedDistance = Dot(plane.normal, point) - plane.constant; + result.distance = std::abs(result.signedDistance); + result.planeClosestPoint = point - result.signedDistance*plane.normal; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Rectangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Rectangle3.h new file mode 100644 index 000000000000..205c7ebd8343 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Rectangle3.h @@ -0,0 +1,85 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Rectangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real rectangleParameter[2]; + Vector3 rectangleClosestPoint; + }; + + Result operator()(Vector3 const& point, + Rectangle3 const& rectangle); +}; + + +template +typename DCPQuery, Rectangle3>::Result +DCPQuery, Rectangle3>::operator()( + Vector3 const& point, Rectangle3 const& rectangle) +{ + Result result; + + Vector3 diff = rectangle.center - point; + Real b0 = Dot(diff, rectangle.axis[0]); + Real b1 = Dot(diff, rectangle.axis[1]); + Real s0 = -b0, s1 = -b1; + result.sqrDistance = Dot(diff, diff); + + if (s0 < -rectangle.extent[0]) + { + s0 = -rectangle.extent[0]; + } + else if (s0 > rectangle.extent[0]) + { + s0 = rectangle.extent[0]; + } + result.sqrDistance += s0*(s0 + ((Real)2)*b0); + + if (s1 < -rectangle.extent[1]) + { + s1 = -rectangle.extent[1]; + } + else if (s1 > rectangle.extent[1]) + { + s1 = rectangle.extent[1]; + } + result.sqrDistance += s1*(s1 + ((Real)2)*b1); + + // Account for numerical round-off error. + if (result.sqrDistance < (Real)0) + { + result.sqrDistance = (Real)0; + } + + result.distance = std::sqrt(result.sqrDistance); + result.rectangleParameter[0] = s0; + result.rectangleParameter[1] = s1; + result.rectangleClosestPoint = rectangle.center; + for (int i = 0; i < 2; ++i) + { + result.rectangleClosestPoint += + result.rectangleParameter[i] * rectangle.axis[i]; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Tetrahedron3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Tetrahedron3.h new file mode 100644 index 000000000000..c591557aebfb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Tetrahedron3.h @@ -0,0 +1,83 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Tetrahedron3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Vector3 tetrahedronClosestPoint; + }; + + Result operator()(Vector3 const& point, + Tetrahedron3 const& tetrahedron); +}; + + +template +typename DCPQuery, Tetrahedron3>::Result +DCPQuery, Tetrahedron3>::operator()( + Vector3 const& point, Tetrahedron3 const& tetrahedron) +{ + Result result; + + // Construct the planes for the faces of the tetrahedron. The normals + // are outer pointing, but specified not to be unit length. We only need + // to know sidedness of the query point, so we will save cycles by not + // computing unit-length normals. + Plane3 planes[4]; + tetrahedron.GetPlanes(planes); + + // Determine which faces are visible to the query point. Only these + // need to be processed by point-to-triangle distance queries. + result.sqrDistance = std::numeric_limits::max(); + result.tetrahedronClosestPoint = Vector3::Zero(); + for (int i = 0; i < 4; ++i) + { + if (Dot(planes[i].normal, point) >= planes[i].constant) + { + int indices[3] = { 0, 0, 0 }; + tetrahedron.GetFaceIndices(i, indices); + Triangle3 triangle( + tetrahedron.v[indices[0]], + tetrahedron.v[indices[1]], + tetrahedron.v[indices[2]]); + + DCPQuery, Triangle3> query; + auto ptResult = query(point, triangle); + if (ptResult.sqrDistance < result.sqrDistance) + { + result.sqrDistance = ptResult.sqrDistance; + result.tetrahedronClosestPoint = ptResult.closest; + } + } + } + + if (result.sqrDistance == std::numeric_limits::max()) + { + // The query point is inside the solid tetrahedron. Report a zero + // distance. The closest points are identical. + result.sqrDistance = (Real)0; + result.tetrahedronClosestPoint = point; + } + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointAlignedBox.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointAlignedBox.h new file mode 100644 index 000000000000..6eea3ddaa848 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointAlignedBox.h @@ -0,0 +1,91 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, AlignedBox> +{ +public: + struct Result + { + Real distance, sqrDistance; + Vector boxClosest; + }; + + Result operator()(Vector const& point, + AlignedBox const& box); + +protected: + // On input, 'point' is the difference of the query point and the box + // center. On output, 'point' is the point on the box closest to the + // query point. + void DoQuery(Vector& point, Vector const& boxExtent, + Result& result); +}; + +// Template aliases for convenience. +template +using DCPPointAlignedBox = +DCPQuery, AlignedBox>; + +template +using DCPPoint2AlignedBox2 = DCPPointAlignedBox<2, Real>; + +template +using DCPPoint3AlignedBox3 = DCPPointAlignedBox<3, Real>; + + +template +typename DCPQuery, AlignedBox>::Result +DCPQuery, AlignedBox>::operator()( + Vector const& point, AlignedBox const& box) +{ + // Translate the point and box so that the box has center at the origin. + Vector boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + Vector closest = point - boxCenter; + + Result result; + DoQuery(closest, boxExtent, result); + + // Compute the closest point on the box. + result.boxClosest = boxCenter + closest; + return result; +} + +template +void DCPQuery, AlignedBox>::DoQuery( + Vector& point, Vector const& boxExtent, Result& result) +{ + result.sqrDistance = (Real)0; + for (int i = 0; i < N; ++i) + { + if (point[i] < -boxExtent[i]) + { + Real delta = point[i] + boxExtent[i]; + result.sqrDistance += delta * delta; + point[i] = -boxExtent[i]; + } + else if (point[i] > boxExtent[i]) + { + Real delta = point[i] - boxExtent[i]; + result.sqrDistance += delta * delta; + point[i] = boxExtent[i]; + } + } + result.distance = std::sqrt(result.sqrDistance); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointHyperellipsoid.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointHyperellipsoid.h new file mode 100644 index 000000000000..a78daa3d3e68 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointHyperellipsoid.h @@ -0,0 +1,382 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +// Compute the distance from a point to a hyperellipsoid. In 2D, this is a +// point-ellipse distance query. In 3D, this is a point-ellipsoid distance +// query. The following document describes the algorithm. +// http://www.geometrictools.com/Documentation/DistancePointEllipseEllipsoid.pdf +// The hyperellipsoid can have arbitrary center and orientation; that is, it +// does not have to be axis-aligned with center at the origin. +// +// For the 2D query, +// Vector2 point; // initialized to something +// Ellipse2 ellipse; // initialized to something +// DCPPoint2Ellipse2 query; +// auto result = query(point, ellipse); +// Real distance = result.distance; +// Vector2 closestEllipsePoint = result.closest; +// +// For the 3D query, +// Vector3 point; // initialized to something +// Ellipsoid3 ellipsoid; // initialized to something +// DCPPoint3Ellipsoid3 query; +// auto result = query(point, ellipsoid); +// Real distance = result.distance; +// Vector3 closestEllipsoidPoint = result.closest; + +namespace gte +{ + +template +class DCPQuery, Hyperellipsoid> +{ +public: + struct Result + { + Real distance, sqrDistance; + Vector closest; + }; + + // The query for any hyperellipsoid. + Result operator()(Vector const& point, + Hyperellipsoid const& hyperellipsoid); + + // The 'hyperellipsoid' is assumed to be axis-aligned and centered at the + // origin , so only the extent[] values are used. + Result operator()(Vector const& point, + Vector const& extent); + +private: + // The hyperellipsoid is sum_{d=0}^{N-1} (x[d]/e[d])^2 = 1 with no + // constraints on the orderind of the e[d]. The query point is + // (y[0],...,y[N-1]) with no constraints on the signs of the components. + // The function returns the squared distance from the query point to the + // hyperellipsoid. It also computes the hyperellipsoid point + // (x[0],...,x[N-1]) that is closest to (y[0],...,y[N-1]). + Real SqrDistance(Vector const& e, + Vector const& y, Vector& x); + + // The hyperellipsoid is sum_{d=0}^{N-1} (x[d]/e[d])^2 = 1 with the e[d] + // positive and nonincreasing: e[d] >= e[d + 1] for all d. The query + // point is (y[0],...,y[N-1]) with y[d] >= 0 for all d. The function + // returns the squared distance from the query point to the + // hyperellipsoid. It also computes the hyperellipsoid point + // (x[0],...,x[N-1]) that is closest to (y[0],...,y[N-1]), where + // x[d] >= 0 for all d. + Real SqrDistanceSpecial(Vector const& e, + Vector const& y, Vector& x); + + // The bisection algorithm to find the unique root of F(t). + Real Bisector(int numComponents, Vector const& e, + Vector const& y, Vector& x); +}; + +// Template aliases for convenience. +template +using DCPPointHyperellipsoid = +DCPQuery, Hyperellipsoid>; + +template +using DCPPoint2Ellipse2 = DCPPointHyperellipsoid<2, Real>; + +template +using DCPPoint3Ellipsoid3 = DCPPointHyperellipsoid<3, Real>; + + +template +typename DCPQuery, Hyperellipsoid>::Result +DCPQuery, Hyperellipsoid>::operator()( + Vector const& point, + Hyperellipsoid const& hyperellipsoid) +{ + Result result; + + // Compute the coordinates of Y in the hyperellipsoid coordinate system. + Vector diff = point - hyperellipsoid.center; + Vector y; + for (int i = 0; i < N; ++i) + { + y[i] = Dot(diff, hyperellipsoid.axis[i]); + } + + // Compute the closest hyperellipsoid point in the axis-aligned + // coordinate system. + Vector x; + result.sqrDistance = SqrDistance(hyperellipsoid.extent, y, x); + result.distance = std::sqrt(result.sqrDistance); + + // Convert back to the original coordinate system. + result.closest = hyperellipsoid.center; + for (int i = 0; i < N; ++i) + { + result.closest += x[i] * hyperellipsoid.axis[i]; + } + + return result; +} + +template +typename DCPQuery, Hyperellipsoid>::Result +DCPQuery, Hyperellipsoid>::operator()( + Vector const& point, Vector const& extent) +{ + Result result; + result.sqrDistance = SqrDistance(extent, point, result.closest); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + +template +Real DCPQuery, Hyperellipsoid>::SqrDistance( + Vector const& e, Vector const& y, Vector& x) +{ + // Determine negations for y to the first octant. + std::array negate; + int i, j; + for (i = 0; i < N; ++i) + { + negate[i] = (y[i] < (Real)0); + } + + // Determine the axis order for decreasing extents. + std::array, N> permute; + for (i = 0; i < N; ++i) + { + permute[i].first = -e[i]; + permute[i].second = i; + } + std::sort(permute.begin(), permute.end()); + + std::array invPermute; + for (i = 0; i < N; ++i) + { + invPermute[permute[i].second] = i; + } + + Vector locE, locY; + for (i = 0; i < N; ++i) + { + j = permute[i].second; + locE[i] = e[j]; + locY[i] = std::abs(y[j]); + } + + Vector locX; + Real sqrDistance = SqrDistanceSpecial(locE, locY, locX); + + // Restore the axis order and reflections. + for (i = 0; i < N; ++i) + { + j = invPermute[i]; + if (negate[i]) + { + locX[j] = -locX[j]; + } + x[i] = locX[j]; + } + + return sqrDistance; +} + +template +Real DCPQuery, Hyperellipsoid>:: +SqrDistanceSpecial(Vector const& e, Vector const& y, + Vector& x) +{ + Real sqrDistance = (Real)0; + + Vector ePos, yPos, xPos; + int numPos = 0; + int i; + for (i = 0; i < N; ++i) + { + if (y[i] >(Real)0) + { + ePos[numPos] = e[i]; + yPos[numPos] = y[i]; + ++numPos; + } + else + { + x[i] = (Real)0; + } + } + + if (y[N - 1] > (Real)0) + { + sqrDistance = Bisector(numPos, ePos, yPos, xPos); + } + else // y[N-1] = 0 + { + Vector numer, denom; + Real eNm1Sqr = e[N - 1] * e[N - 1]; + for (i = 0; i < numPos; ++i) + { + numer[i] = ePos[i] * yPos[i]; + denom[i] = ePos[i] * ePos[i] - eNm1Sqr; + } + + bool inSubHyperbox = true; + for (i = 0; i < numPos; ++i) + { + if (numer[i] >= denom[i]) + { + inSubHyperbox = false; + break; + } + } + + bool inSubHyperellipsoid = false; + if (inSubHyperbox) + { + // yPos[] is inside the axis-aligned bounding box of the + // subhyperellipsoid. This intermediate test is designed to guard + // against the division by zero when ePos[i] == e[N-1] for some i. + Vector xde; + Real discr = (Real)1; + for (i = 0; i < numPos; ++i) + { + xde[i] = numer[i] / denom[i]; + discr -= xde[i] * xde[i]; + } + if (discr >(Real)0) + { + // yPos[] is inside the subhyperellipsoid. The closest + // hyperellipsoid point has x[N-1] > 0. + sqrDistance = (Real)0; + for (i = 0; i < numPos; ++i) + { + xPos[i] = ePos[i] * xde[i]; + Real diff = xPos[i] - yPos[i]; + sqrDistance += diff*diff; + } + x[N - 1] = e[N - 1] * std::sqrt(discr); + sqrDistance += x[N - 1] * x[N - 1]; + inSubHyperellipsoid = true; + } + } + + if (!inSubHyperellipsoid) + { + // yPos[] is outside the subhyperellipsoid. The closest + // hyperellipsoid point has x[N-1] == 0 and is on the + // domain-boundary hyperellipsoid. + x[N - 1] = (Real)0; + sqrDistance = Bisector(numPos, ePos, yPos, xPos); + } + } + + // Fill in those x[] values that were not zeroed out initially. + for (i = 0, numPos = 0; i < N; ++i) + { + if (y[i] >(Real)0) + { + x[i] = xPos[numPos]; + ++numPos; + } + } + + return sqrDistance; +} + +template +Real DCPQuery, Hyperellipsoid>::Bisector( + int numComponents, Vector const& e, Vector const& y, + Vector& x) +{ + Vector z; + Real sumZSqr = (Real)0; + int i; + for (i = 0; i < numComponents; ++i) + { + z[i] = y[i] / e[i]; + sumZSqr += z[i] * z[i]; + } + + if (sumZSqr == (Real)1) + { + // The point is on the hyperellipsoid. + for (i = 0; i < numComponents; ++i) + { + x[i] = y[i]; + } + return (Real)0; + } + + Real emin = e[numComponents - 1]; + Vector pSqr, numerator; + for (i = 0; i < numComponents; ++i) + { + Real p = e[i] / emin; + pSqr[i] = p * p; + numerator[i] = pSqr[i] * z[i]; + } + + Real s = (Real)0, smin = z[numComponents - 1] - (Real)1, smax; + if (sumZSqr < (Real)1) + { + // The point is strictly inside the hyperellipsoid. + smax = (Real)0; + } + else + { + // The point is strictly outside the hyperellipsoid. + smax = Length(numerator, true) - (Real)1; + } + + // The use of 'double' is intentional in case Real is a BSNumber or + // BSRational type. We want the bisections to terminate in a reasonable + // amount of time. + unsigned int const jmax = GTE_C_MAX_BISECTIONS_GENERIC; + for (unsigned int j = 0; j < jmax; ++j) + { + s = (smin + smax) * (Real)0.5; + if (s == smin || s == smax) + { + break; + } + + Real g = (Real)-1; + for (i = 0; i < numComponents; ++i) + { + Real ratio = numerator[i] / (s + pSqr[i]); + g += ratio * ratio; + } + + if (g >(Real)0) + { + smin = s; + } + else if (g < (Real)0) + { + smax = s; + } + else + { + break; + } + } + + Real sqrDistance = (Real)0; + for (i = 0; i < numComponents; ++i) + { + x[i] = pSqr[i] * y[i] / (s + pSqr[i]); + Real diff = x[i] - y[i]; + sqrDistance += diff*diff; + } + return sqrDistance; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointLine.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointLine.h new file mode 100644 index 000000000000..46c6509ed89f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointLine.h @@ -0,0 +1,62 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, Line> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real lineParameter; // t in (-infinity,+infinity) + Vector lineClosest; // origin + t * direction + }; + + Result operator()(Vector const& point, + Line const& line); +}; + +// Template aliases for convenience. +template +using DCPPointLine = +DCPQuery, Line>; + +template +using DCPPoint2Line2 = DCPPointLine<2, Real>; + +template +using DCPPoint3Line3 = DCPPointLine<3, Real>; + + +template +typename DCPQuery, Line>::Result +DCPQuery, Line>::operator()( + Vector const& point, Line const& line) +{ + Result result; + + Vector diff = point - line.origin; + result.lineParameter = Dot(line.direction, diff); + result.lineClosest = line.origin + result.lineParameter*line.direction; + + diff = point - result.lineClosest; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointOrientedBox.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointOrientedBox.h new file mode 100644 index 000000000000..89ece503a327 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointOrientedBox.h @@ -0,0 +1,72 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, OrientedBox> + : + public DCPQuery, AlignedBox> +{ +public: + struct Result + : + public DCPQuery, AlignedBox>::Result + { + // No additional information to compute. + }; + + Result operator()(Vector const& point, + OrientedBox const& box); +}; + +// Template aliases for convenience. +template +using DCPPointOrientedBox = +DCPQuery, AlignedBox>; + +template +using DCPPoint2OrientedBox2 = DCPPointOrientedBox<2, Real>; + +template +using DCPPoint3OrientedBox3 = DCPPointOrientedBox<3, Real>; + + +template +typename DCPQuery, OrientedBox>::Result +DCPQuery, OrientedBox>::operator()( + Vector const& point, OrientedBox const& box) +{ + // Translate the point to the coordinate system of the box. In this + // system, the box is axis-aligned with center at the origin. + Vector diff = point - box.center; + Vector closest; + for (int i = 0; i < N; ++i) + { + closest[i] = Dot(diff, box.axis[i]); + } + + Result result; + this->DoQuery(closest, box.extent, result); + + // Compute the closest point on the box. + result.boxClosest = box.center; + for (int i = 0; i < N; ++i) + { + result.boxClosest += closest[i] * box.axis[i]; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointRay.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointRay.h new file mode 100644 index 000000000000..e7a0b9fc8277 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointRay.h @@ -0,0 +1,68 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, Ray> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real rayParameter; // t in [0,+infinity) + Vector rayClosest; // origin + t * direction + }; + + Result operator()(Vector const& point, Ray const& ray); +}; + +// Template aliases for convenience. +template +using DCPPointRay = +DCPQuery, Ray>; + +template +using DCPPoint2Ray2 = DCPPointRay<2, Real>; + +template +using DCPPoint3Ray3 = DCPPointRay<3, Real>; + + +template +typename DCPQuery, Ray>::Result +DCPQuery, Ray>::operator()( + Vector const& point, Ray const& ray) +{ + Result result; + + Vector diff = point - ray.origin; + result.rayParameter = Dot(ray.direction, diff); + if (result.rayParameter > (Real)0) + { + result.rayClosest = ray.origin + result.rayParameter*ray.direction; + } + else + { + result.rayClosest = ray.origin; + } + + diff = point - result.rayClosest; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointSegment.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointSegment.h new file mode 100644 index 000000000000..e44589620cfc --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointSegment.h @@ -0,0 +1,94 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, Segment> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real segmentParameter; // t in [0,1] + Vector segmentClosest; // (1-t)*p[0] + t*p[1] + }; + + Result operator()(Vector const& point, + Segment const& segment); +}; + +// Template aliases for convenience. +template +using DCPPointSegment = +DCPQuery, Segment>; + +template +using DCPPoint2Segment2 = DCPPointSegment<2, Real>; + +template +using DCPPoint3Segment3 = DCPPointSegment<3, Real>; + + +template +typename DCPQuery, Segment>::Result +DCPQuery, Segment>::operator()( + Vector const& point, Segment const& segment) +{ + Result result; + + // The direction vector is not unit length. The normalization is deferred + // until it is needed. + Vector direction = segment.p[1] - segment.p[0]; + Vector diff = point - segment.p[1]; + Real t = Dot(direction, diff); + if (t >= (Real)0) + { + result.segmentParameter = (Real)1; + result.segmentClosest = segment.p[1]; + } + else + { + diff = point - segment.p[0]; + t = Dot(direction, diff); + if (t <= (Real)0) + { + result.segmentParameter = (Real)0; + result.segmentClosest = segment.p[0]; + } + else + { + Real sqrLength = Dot(direction, direction); + if (sqrLength > (Real)0) + { + t /= sqrLength; + result.segmentParameter = t; + result.segmentClosest = segment.p[0] + t * direction; + } + else + { + result.segmentParameter = (Real)0; + result.segmentClosest = segment.p[0]; + } + } + } + + diff = point - result.segmentClosest; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointTriangle.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointTriangle.h new file mode 100644 index 000000000000..c5a513e8d33c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointTriangle.h @@ -0,0 +1,266 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Triangle> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real parameter[3]; // barycentric coordinates for triangle.v[3] + Vector closest; + }; + + Result operator()(Vector const& point, + Triangle const& triangle); + +private: + inline void GetMinEdge02(Real const& a11, Real const& b1, + Vector<2, Real>& p) const; + + inline void GetMinEdge12(Real const& a01, Real const& a11, Real const& b1, + Real const& f10, Real const& f01, Vector<2, Real>& p) const; + + inline void GetMinInterior(Vector<2, Real> const& p0, Real const& h0, + Vector<2, Real> const& p1, Real const& h1, Vector<2, Real>& p) const; +}; + +// Template aliases for convenience. +template +using DCPPointTriangle = +DCPQuery, Triangle>; + +template +using DCPPoint2Triangle2 = DCPPointTriangle<2, Real>; + +template +using DCPPoint3Triangle3 = DCPPointTriangle<3, Real>; + + +template inline +void DCPQuery, Triangle>::GetMinEdge02( + Real const& a11, Real const& b1, Vector<2, Real>& p) const +{ + p[0] = (Real)0; + if (b1 >= (Real)0) + { + p[1] = (Real)0; + } + else if (a11 + b1 <= (Real)0) + { + p[1] = (Real)1; + } + else + { + p[1] = -b1 / a11; + } +} + +template inline +void DCPQuery, Triangle>::GetMinEdge12( + Real const& a01, Real const& a11, Real const& b1, Real const& f10, + Real const& f01, Vector<2, Real>& p) const +{ + Real h0 = a01 + b1 - f10; + if (h0 >= (Real)0) + { + p[1] = (Real)0; + } + else + { + Real h1 = a11 + b1 - f01; + if (h1 <= (Real)0) + { + p[1] = (Real)1; + } + else + { + p[1] = h0 / (h0 - h1); + } + } + p[0] = (Real)1 - p[1]; +} + +template inline +void DCPQuery, Triangle>::GetMinInterior( + Vector<2, Real> const& p0, Real const& h0, Vector<2, Real> const& p1, + Real const& h1, Vector<2, Real>& p) const +{ + Real z = h0 / (h0 - h1); + p = ((Real)1 - z) * p0 + z * p1; +} + +template +typename DCPQuery, Triangle>::Result +DCPQuery, Triangle>::operator()( + Vector const& point, Triangle const& triangle) +{ + Vector diff = point - triangle.v[0]; + Vector edge0 = triangle.v[1] - triangle.v[0]; + Vector edge1 = triangle.v[2] - triangle.v[0]; + Real a00 = Dot(edge0, edge0); + Real a01 = Dot(edge0, edge1); + Real a11 = Dot(edge1, edge1); + Real b0 = -Dot(diff, edge0); + Real b1 = -Dot(diff, edge1); + + Real f00 = b0; + Real f10 = b0 + a00; + Real f01 = b0 + a01; + + Vector<2, Real> p0, p1, p; + Real dt1, h0, h1; + + // Compute the endpoints p0 and p1 of the segment. The segment is + // parameterized by L(z) = (1-z)*p0 + z*p1 for z in [0,1] and the + // directional derivative of half the quadratic on the segment is + // H(z) = Dot(p1-p0,gradient[Q](L(z))/2), where gradient[Q]/2 = (F,G). + // By design, F(L(z)) = 0 for cases (2), (4), (5), and (6). Cases (1) and + // (3) can correspond to no-intersection or intersection of F = 0 with the + // triangle. + if (f00 >= (Real)0) + { + if (f01 >= (Real)0) + { + // (1) p0 = (0,0), p1 = (0,1), H(z) = G(L(z)) + GetMinEdge02(a11, b1, p); + } + else + { + // (2) p0 = (0,t10), p1 = (t01,1-t01), H(z) = (t11 - t10)*G(L(z)) + p0[0] = (Real)0; + p0[1] = f00 / (f00 - f01); + p1[0] = f01 / (f01 - f10); + p1[1] = (Real)1 - p1[0]; + dt1 = p1[1] - p0[1]; + h0 = dt1 * (a11 * p0[1] + b1); + if (h0 >= (Real)0) + { + GetMinEdge02(a11, b1, p); + } + else + { + h1 = dt1 * (a01 * p1[0] + a11 * p1[1] + b1); + if (h1 <= (Real)0) + { + GetMinEdge12(a01, a11, b1, f10, f01, p); + } + else + { + GetMinInterior(p0, h0, p1, h1, p); + } + } + } + } + else if (f01 <= (Real)0) + { + if (f10 <= (Real)0) + { + // (3) p0 = (1,0), p1 = (0,1), H(z) = G(L(z)) - F(L(z)) + GetMinEdge12(a01, a11, b1, f10, f01, p); + } + else + { + // (4) p0 = (t00,0), p1 = (t01,1-t01), H(z) = t11*G(L(z)) + p0[0] = f00 / (f00 - f10); + p0[1] = (Real)0; + p1[0] = f01 / (f01 - f10); + p1[1] = (Real)1 - p1[0]; + h0 = p1[1] * (a01 * p0[0] + b1); + if (h0 >= (Real)0) + { + p = p0; // GetMinEdge01 + } + else + { + h1 = p1[1] * (a01 * p1[0] + a11 * p1[1] + b1); + if (h1 <= (Real)0) + { + GetMinEdge12(a01, a11, b1, f10, f01, p); + } + else + { + GetMinInterior(p0, h0, p1, h1, p); + } + } + } + } + else if (f10 <= (Real)0) + { + // (5) p0 = (0,t10), p1 = (t01,1-t01), H(z) = (t11 - t10)*G(L(z)) + p0[0] = (Real)0; + p0[1] = f00 / (f00 - f01); + p1[0] = f01 / (f01 - f10); + p1[1] = (Real)1 - p1[0]; + dt1 = p1[1] - p0[1]; + h0 = dt1 * (a11 * p0[1] + b1); + if (h0 >= (Real)0) + { + GetMinEdge02(a11, b1, p); + } + else + { + h1 = dt1 * (a01 * p1[0] + a11 * p1[1] + b1); + if (h1 <= (Real)0) + { + GetMinEdge12(a01, a11, b1, f10, f01, p); + } + else + { + GetMinInterior(p0, h0, p1, h1, p); + } + } + } + else + { + // (6) p0 = (t00,0), p1 = (0,t11), H(z) = t11*G(L(z)) + p0[0] = f00 / (f00 - f10); + p0[1] = (Real)0; + p1[0] = (Real)0; + p1[1] = f00 / (f00 - f01); + h0 = p1[1] * (a01 * p0[0] + b1); + if (h0 >= (Real)0) + { + p = p0; // GetMinEdge01 + } + else + { + h1 = p1[1] * (a11 * p1[1] + b1); + if (h1 <= (Real)0) + { + GetMinEdge02(a11, b1, p); + } + else + { + GetMinInterior(p0, h0, p1, h1, p); + } + } + } + + Result result; + result.parameter[0] = (Real)1 - p[0] - p[1]; + result.parameter[1] = p[0]; + result.parameter[2] = p[1]; + result.closest = triangle.v[0] + p[0] * edge0 + p[1] * edge1; + diff = point - result.closest; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointTriangleExact.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointTriangleExact.h new file mode 100644 index 000000000000..587b8f9f1c8d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointTriangleExact.h @@ -0,0 +1,238 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DistancePointTriangleExact +{ +public: + struct Result + { + Rational sqrDistance; + Rational parameter[3]; // barycentric coordinates for triangle.v[3] + Vector closest; + }; + + Result operator()(Vector const& point, + Triangle const& triangle); +}; + + +template +typename DistancePointTriangleExact::Result +DistancePointTriangleExact::operator()( + Vector const& point, Triangle const& triangle) +{ + Vector diff = point - triangle.v[0]; + Vector edge0 = triangle.v[1] - triangle.v[0]; + Vector edge1 = triangle.v[2] - triangle.v[0]; + Rational a00 = Dot(edge0, edge0); + Rational a01 = Dot(edge0, edge1); + Rational a11 = Dot(edge1, edge1); + Rational b0 = -Dot(diff, edge0); + Rational b1 = -Dot(diff, edge1); + Rational const zero = (Rational)0; + Rational const one = (Rational)1; + Rational det = a00 * a11 - a01 * a01; + Rational t0 = a01 * b1 - a11 * b0; + Rational t1 = a01 * b0 - a00 * b1; + + if (t0 + t1 <= det) + { + if (t0 < zero) + { + if (t1 < zero) // region 4 + { + if (b0 < zero) + { + t1 = zero; + if (-b0 >= a00) // V1 + { + t0 = one; + } + else // E01 + { + t0 = -b0 / a00; + } + } + else + { + t0 = zero; + if (b1 >= zero) // V0 + { + t1 = zero; + } + else if (-b1 >= a11) // V2 + { + t1 = one; + } + else // E20 + { + t1 = -b1 / a11; + } + } + } + else // region 3 + { + t0 = zero; + if (b1 >= zero) // V0 + { + t1 = zero; + } + else if (-b1 >= a11) // V2 + { + t1 = one; + } + else // E20 + { + t1 = -b1 / a11; + } + } + } + else if (t1 < zero) // region 5 + { + t1 = zero; + if (b0 >= zero) // V0 + { + t0 = zero; + } + else if (-b0 >= a00) // V1 + { + t0 = one; + } + else // E01 + { + t0 = -b0 / a00; + } + } + else // region 0, interior + { + Rational invDet = one / det; + t0 *= invDet; + t1 *= invDet; + } + } + else + { + Rational tmp0, tmp1, numer, denom; + + if (t0 < zero) // region 2 + { + tmp0 = a01 + b0; + tmp1 = a11 + b1; + if (tmp1 > tmp0) + { + numer = tmp1 - tmp0; + denom = a00 - ((Rational)2)*a01 + a11; + if (numer >= denom) // V1 + { + t0 = one; + t1 = zero; + } + else // E12 + { + t0 = numer / denom; + t1 = one - t0; + } + } + else + { + t0 = zero; + if (tmp1 <= zero) // V2 + { + t1 = one; + } + else if (b1 >= zero) // V0 + { + t1 = zero; + } + else // E20 + { + t1 = -b1 / a11; + } + } + } + else if (t1 < zero) // region 6 + { + tmp0 = a01 + b1; + tmp1 = a00 + b0; + if (tmp1 > tmp0) + { + numer = tmp1 - tmp0; + denom = a00 - ((Rational)2)*a01 + a11; + if (numer >= denom) // V2 + { + t1 = one; + t0 = zero; + } + else // E12 + { + t1 = numer / denom; + t0 = one - t1; + } + } + else + { + t1 = zero; + if (tmp1 <= zero) // V1 + { + t0 = one; + } + else if (b0 >= zero) // V0 + { + t0 = zero; + } + else // E01 + { + t0 = -b0 / a00; + } + } + } + else // region 1 + { + numer = a11 + b1 - a01 - b0; + if (numer <= zero) // V2 + { + t0 = zero; + t1 = one; + } + else + { + denom = a00 - ((Rational)2)*a01 + a11; + if (numer >= denom) // V1 + { + t0 = one; + t1 = zero; + } + else // 12 + { + t0 = numer / denom; + t1 = one - t0; + } + } + } + } + + Result result; + result.parameter[0] = one - t0 - t1; + result.parameter[1] = t0; + result.parameter[2] = t1; + result.closest = triangle.v[0] + t0 * edge0 + t1 * edge1; + diff = point - result.closest; + result.sqrDistance = Dot(diff, diff); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3AlignedBox3.h new file mode 100644 index 000000000000..605d949eab7b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3AlignedBox3.h @@ -0,0 +1,64 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, AlignedBox3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real rayParameter; + Vector3 closestPoint[2]; + }; + + Result operator()(Ray3 const& ray, AlignedBox3 const& box); +}; + + +template +typename DCPQuery, AlignedBox3>::Result +DCPQuery, AlignedBox3> ::operator()( + Ray3 const& ray, AlignedBox3 const& box) +{ + Result result; + + Line3 line(ray.origin, ray.direction); + DCPQuery, AlignedBox3> lbQuery; + auto lbResult = lbQuery(line, box); + + if (lbResult.lineParameter >= (Real)0) + { + result.sqrDistance = lbResult.sqrDistance; + result.distance = lbResult.distance; + result.rayParameter = lbResult.lineParameter; + result.closestPoint[0] = lbResult.closestPoint[0]; + result.closestPoint[1] = lbResult.closestPoint[1]; + } + else + { + DCPQuery, AlignedBox3> pbQuery; + auto pbResult = pbQuery(ray.origin, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.rayParameter = (Real)0; + result.closestPoint[0] = ray.origin; + result.closestPoint[1] = pbResult.boxClosest; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3OrientedBox3.h new file mode 100644 index 000000000000..13f8a6fa5142 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3OrientedBox3.h @@ -0,0 +1,65 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, OrientedBox3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real rayParameter; + Vector3 closestPoint[2]; + }; + + Result operator()(Ray3 const& ray, OrientedBox3 const& box); +}; + + +template +typename DCPQuery, OrientedBox3>::Result +DCPQuery, OrientedBox3> ::operator()( + Ray3 const& ray, OrientedBox3 const& box) +{ + Result result; + + Line3 line(ray.origin, ray.direction); + DCPQuery, OrientedBox3> lbQuery; + auto lbResult = lbQuery(line, box); + + if (lbResult.lineParameter >= (Real)0) + { + result.sqrDistance = lbResult.sqrDistance; + result.distance = lbResult.distance; + result.rayParameter = lbResult.lineParameter; + result.closestPoint[0] = lbResult.closestPoint[0]; + result.closestPoint[1] = lbResult.closestPoint[1]; + } + else + { + DCPQuery, OrientedBox3> pbQuery; + auto pbResult = pbQuery(ray.origin, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.rayParameter = (Real)0; + result.closestPoint[0] = ray.origin; + result.closestPoint[1] = pbResult.boxClosest; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3Rectangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3Rectangle3.h new file mode 100644 index 000000000000..cb399d2525e4 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3Rectangle3.h @@ -0,0 +1,70 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Rectangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real rayParameter, rectangleParameter[2]; + Vector3 closestPoint[2]; + }; + + Result operator()(Ray3 const& ray, + Rectangle3 const& rectangle); +}; + + +template +typename DCPQuery, Rectangle3>::Result +DCPQuery, Rectangle3>::operator()( + Ray3 const& ray, Rectangle3 const& rectangle) +{ + Result result; + + Line3 line(ray.origin, ray.direction); + DCPQuery, Rectangle3> lrQuery; + auto lrResult = lrQuery(line, rectangle); + + if (lrResult.lineParameter >= (Real)0) + { + result.distance = lrResult.distance; + result.sqrDistance = lrResult.sqrDistance; + result.rayParameter = lrResult.lineParameter; + result.rectangleParameter[0] = lrResult.rectangleParameter[0]; + result.rectangleParameter[1] = lrResult.rectangleParameter[1]; + result.closestPoint[0] = lrResult.closestPoint[0]; + result.closestPoint[1] = lrResult.closestPoint[1]; + } + else + { + DCPQuery, Rectangle3> prQuery; + auto prResult = prQuery(ray.origin, rectangle); + result.distance = prResult.distance; + result.sqrDistance = prResult.sqrDistance; + result.rayParameter = (Real)0; + result.rectangleParameter[0] = prResult.rectangleParameter[0]; + result.rectangleParameter[1] = prResult.rectangleParameter[1]; + result.closestPoint[0] = ray.origin; + result.closestPoint[1] = prResult.rectangleClosestPoint; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3Triangle3.h new file mode 100644 index 000000000000..595cf8e45baa --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3Triangle3.h @@ -0,0 +1,71 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Triangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real rayParameter, triangleParameter[3]; + Vector3 closestPoint[2]; + }; + + Result operator()(Ray3 const& ray, Triangle3 const& triangle); +}; + + +template +typename DCPQuery, Triangle3>::Result +DCPQuery, Triangle3>::operator()( + Ray3 const& ray, Triangle3 const& triangle) +{ + Result result; + + Line3 line(ray.origin, ray.direction); + DCPQuery, Triangle3> ltQuery; + auto ltResult = ltQuery(line, triangle); + + if (ltResult.lineParameter >= (Real)0) + { + result.distance = ltResult.distance; + result.sqrDistance = ltResult.sqrDistance; + result.rayParameter = ltResult.lineParameter; + result.triangleParameter[0] = ltResult.triangleParameter[0]; + result.triangleParameter[1] = ltResult.triangleParameter[1]; + result.triangleParameter[2] = ltResult.triangleParameter[2]; + result.closestPoint[0] = ltResult.closestPoint[0]; + result.closestPoint[1] = ltResult.closestPoint[1]; + } + else + { + DCPQuery, Triangle3> ptQuery; + auto ptResult = ptQuery(ray.origin, triangle); + result.distance = ptResult.distance; + result.sqrDistance = ptResult.sqrDistance; + result.rayParameter = (Real)0; + result.triangleParameter[0] = ptResult.parameter[0]; + result.triangleParameter[1] = ptResult.parameter[1]; + result.triangleParameter[2] = ptResult.parameter[2]; + result.closestPoint[0] = ray.origin; + result.closestPoint[1] = ptResult.closest; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRayRay.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRayRay.h new file mode 100644 index 000000000000..450ef2348a71 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRayRay.h @@ -0,0 +1,162 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, Ray> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closestPoint[2]; + }; + + Result operator()(Ray const& ray0, Ray const& ray1); +}; + +// Template aliases for convenience. +template +using DCPRayRay = DCPQuery, Ray>; + +template +using DCPRay2Ray2 = DCPRayRay<2, Real>; + +template +using DCPRay3Ray3 = DCPRayRay<3, Real>; + + +template +typename DCPQuery, Ray>::Result +DCPQuery, Ray>::operator()( + Ray const& ray0, Ray const& ray1) +{ + Result result; + + Vector diff = ray0.origin - ray1.origin; + Real a01 = -Dot(ray0.direction, ray1.direction); + Real b0 = Dot(diff, ray0.direction), b1; + Real s0, s1; + + if (std::abs(a01) < (Real)1) + { + // Rays are not parallel. + b1 = -Dot(diff, ray1.direction); + s0 = a01 * b1 - b0; + s1 = a01 * b0 - b1; + + if (s0 >= (Real)0) + { + if (s1 >= (Real)0) // region 0 (interior) + { + // Minimum at two interior points of rays. + Real det = (Real)1 - a01 * a01; + s0 /= det; + s1 /= det; + } + else // region 3 (side) + { + s1 = (Real)0; + if (b0 >= (Real)0) + { + s0 = (Real)0; + } + else + { + s0 = -b0; + } + } + } + else + { + if (s1 >= (Real)0) // region 1 (side) + { + s0 = (Real)0; + if (b1 >= (Real)0) + { + s1 = (Real)0; + } + else + { + s1 = -b1; + } + } + else // region 2 (corner) + { + if (b0 < (Real)0) + { + s0 = -b0; + s1 = (Real)0; + } + else + { + s0 = (Real)0; + if (b1 >= (Real)0) + { + s1 = (Real)0; + } + else + { + s1 = -b1; + } + } + } + } + } + else + { + // Rays are parallel. + if (a01 > (Real)0) + { + // Opposite direction vectors. + s1 = (Real)0; + if (b0 >= (Real)0) + { + s0 = (Real)0; + } + else + { + s0 = -b0; + } + } + else + { + // Same direction vectors. + if (b0 >= (Real)0) + { + b1 = -Dot(diff, ray1.direction); + s0 = (Real)0; + s1 = -b1; + } + else + { + s0 = -b0; + s1 = (Real)0; + } + } + } + + result.parameter[0] = s0; + result.parameter[1] = s1; + result.closestPoint[0] = ray0.origin + s0 * ray0.direction; + result.closestPoint[1] = ray1.origin + s1 * ray1.direction; + diff = result.closestPoint[0] - result.closestPoint[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRaySegment.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRaySegment.h new file mode 100644 index 000000000000..f07fcc4bda05 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRaySegment.h @@ -0,0 +1,179 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Segment> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closestPoint[2]; + }; + + // The centered form of the 'segment' is used. Thus, parameter[1] of + // the result is in [-e,e], where e = |segment.p[1] - segment.p[0]|/2. + Result operator()(Ray const& ray, + Segment const& segment); +}; + +// Template aliases for convenience. +template +using DCPRaySegment = DCPQuery, Segment>; + +template +using DCPRay2Segment2 = DCPRaySegment<2, Real>; + +template +using DCPRay3Segment3 = DCPRaySegment<3, Real>; + + +template +typename DCPQuery, Segment>::Result +DCPQuery, Segment>::operator()( + Ray const& ray, Segment const& segment) +{ + Result result; + + Vector segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Vector diff = ray.origin - segCenter; + Real a01 = -Dot(ray.direction, segDirection); + Real b0 = Dot(diff, ray.direction); + Real s0, s1; + + if (std::abs(a01) < (Real)1) + { + // The ray and segment are not parallel. + Real det = (Real)1 - a01 * a01; + Real extDet = segExtent*det; + Real b1 = -Dot(diff, segDirection); + s0 = a01 * b1 - b0; + s1 = a01 * b0 - b1; + + if (s0 >= (Real)0) + { + if (s1 >= -extDet) + { + if (s1 <= extDet) // region 0 + { + // Minimum at interior points of ray and segment. + s0 /= det; + s1 /= det; + } + else // region 1 + { + s1 = segExtent; + s0 = std::max(-(a01*s1 + b0), (Real)0); + } + } + else // region 5 + { + s1 = -segExtent; + s0 = std::max(-(a01*s1 + b0), (Real)0); + } + } + else + { + if (s1 <= -extDet) // region 4 + { + s0 = -(-a01*segExtent + b0); + if (s0 > (Real)0) + { + s1 = -segExtent; + } + else + { + s0 = (Real)0; + s1 = -b1; + if (s1 < -segExtent) + { + s1 = -segExtent; + } + else if (s1 > segExtent) + { + s1 = segExtent; + } + } + } + else if (s1 <= extDet) // region 3 + { + s0 = (Real)0; + s1 = -b1; + if (s1 < -segExtent) + { + s1 = -segExtent; + } + else if (s1 > segExtent) + { + s1 = segExtent; + } + } + else // region 2 + { + s0 = -(a01*segExtent + b0); + if (s0 > (Real)0) + { + s1 = segExtent; + } + else + { + s0 = (Real)0; + s1 = -b1; + if (s1 < -segExtent) + { + s1 = -segExtent; + } + else if (s1 > segExtent) + { + s1 = segExtent; + } + } + } + } + } + else + { + // Ray and segment are parallel. + if (a01 > (Real)0) + { + // Opposite direction vectors. + s1 = -segExtent; + } + else + { + // Same direction vectors. + s1 = segExtent; + } + + s0 = std::max(-(a01*s1 + b0), (Real)0); + } + + result.parameter[0] = s0; + result.parameter[1] = s1; + result.closestPoint[0] = ray.origin + s0 * ray.direction; + result.closestPoint[1] = segCenter + s1 * segDirection; + diff = result.closestPoint[0] - result.closestPoint[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3AlignedBox3.h new file mode 100644 index 000000000000..74e2a667711c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3AlignedBox3.h @@ -0,0 +1,145 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, AlignedBox3> +{ +public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array rectangleParameter; + std::array boxParameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of whether + // the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 81 (n = 9, maxIterations = n*n). If + // the solver fails to converge, try increasing the maximum number of + // iterations. + void SetMaxLCPIterations(int maxLCPIterations); + + Result operator()(Rectangle3 const& rectangle, AlignedBox3 const& box); + +private: + LCPSolver mLCP; +}; + + +template +void DCPQuery, AlignedBox3>::SetMaxLCPIterations(int maxLCPIterations) +{ + mLCP.SetMaxIterations(maxLCPIterations); +} + +template +typename DCPQuery, AlignedBox3>::Result +DCPQuery, AlignedBox3>::operator()( + Rectangle3 const& rectangle, AlignedBox3 const& box) +{ + Result result; + + // Translate the rectangle and aligned box so that the aligned box becomes + // a canonical box. + Vector3 K = box.max - box.min; + Vector3 V = rectangle.center - box.min; + + // Convert the oriented rectangle to a regular one (origin at a corner). + Vector3 scaledE0 = rectangle.axis[0] * rectangle.extent[0]; + Vector3 scaledE1 = rectangle.axis[1] * rectangle.extent[1]; + Vector3 E0 = scaledE0 * (Real)2; + Vector3 E1 = scaledE1 * (Real)2; + V -= scaledE0 + scaledE1; + + // Compute quantities to initialize q and M in the LCP. + Real dotVE0 = Dot(V, E0); + Real dotVE1 = Dot(V, E1); + Real dotE0E0 = Dot(E0, E0); + Real dotE1E1 = Dot(E1, E1); + + // The LCP has 5 variables and 5 (nontrivial) inequality constraints. + std::array q = + { + -V[0], -V[1], -V[2], dotVE0, dotVE1, K[0], K[1], K[2], (Real)1, (Real)1 + }; + + std::array, 10> M; + M[0] = { (Real)1, (Real)0, (Real)0, -E0[0], -E1[0], (Real)1, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[1] = { (Real)0, (Real)1, (Real)0, -E0[1], -E1[1], (Real)0, (Real)1, (Real)0, (Real)0, (Real)0 }; + M[2] = { (Real)0, (Real)0, (Real)1, -E0[2], -E1[2], (Real)0, (Real)0, (Real)1, (Real)0 , (Real)0 }; + M[3] = { -E0[0], -E0[1], -E0[2], dotE0E0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)1, (Real)0 }; + M[4] = { -E1[0], -E1[1], -E1[2], (Real)0, dotE1E1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[5] = { (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[6] = { (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[7] = { (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[8] = { (Real)0, (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[9] = { (Real)0, (Real)0, (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + Real t0 = (z[3] * (Real)2 - (Real)1) * rectangle.extent[0]; + Real t1 = (z[4] * (Real)2 - (Real)1) * rectangle.extent[1]; + result.rectangleParameter[0] = t0; + result.rectangleParameter[1] = t1; + result.closestPoint[0] = rectangle.center + t0 * rectangle.axis[0] + t1 * rectangle.axis[1]; + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = z[i] + box.min[i]; + result.closestPoint[1][i] = result.boxParameter[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations was not + // specified to be large enough or there is a problem due to + // floating-point rounding errors. If you believe the latter is + // true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 2; ++i) + { + result.rectangleParameter[i] = (Real)0; + } + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3OrientedBox3.h new file mode 100644 index 000000000000..67922f959fb9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3OrientedBox3.h @@ -0,0 +1,154 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, OrientedBox3> +{ +public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array rectangleParameter; + std::array boxParameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of whether + // the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 81 (n = 9, maxIterations = n*n). If + // the solver fails to converge, try increasing the maximum number of + // iterations. + void SetMaxLCPIterations(int maxLCPIterations); + + Result operator()(Rectangle3 const& rectangle, OrientedBox3 const& box); + +private: + LCPSolver mLCP; +}; + + +template +void DCPQuery, OrientedBox3>::SetMaxLCPIterations(int maxLCPIterations) +{ + mLCP.SetMaxIterations(maxLCPIterations); +} + +template +typename DCPQuery, OrientedBox3>::Result +DCPQuery, OrientedBox3>::operator()( + Rectangle3 const& rectangle, OrientedBox3 const& box) +{ + Result result; + + // Rigidly transform the rectangle and oriented box so that the oriented + // box becomes a canonical box. + Vector3 K = box.extent * (Real)2; + Vector3 tempV = rectangle.center - box.center; + Vector3 V, E0, E1; + for (int i = 0; i < 3; ++i) + { + V[i] = Dot(box.axis[i], tempV) + box.extent[i]; + E0[i] = Dot(box.axis[i], rectangle.axis[0]); + E1[i] = Dot(box.axis[i], rectangle.axis[1]); + } + + // Convert the oriented rectangle to a regular one (origin at a corner). + Vector3 scaledE0 = E0 * rectangle.extent[0]; + Vector3 scaledE1 = E1 * rectangle.extent[1]; + V -= scaledE0 + scaledE1; + E0 = scaledE0 * (Real)2; + E1 = scaledE1 * (Real)2; + + // Compute quantities to initialize q and M in the LCP. + Real dotVE0 = Dot(V, E0); + Real dotVE1 = Dot(V, E1); + Real dotE0E0 = Dot(E0, E0); + Real dotE1E1 = Dot(E1, E1); + + // The LCP has 5 variables and 5 (nontrivial) inequality constraints. + std::array q = + { + -V[0], -V[1], -V[2], dotVE0, dotVE1, K[0], K[1], K[2], (Real)1, (Real)1 + }; + + std::array, 10> M; + M[0] = { (Real)1, (Real)0, (Real)0, -E0[0], -E1[0], (Real)1, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[1] = { (Real)0, (Real)1, (Real)0, -E0[1], -E1[1], (Real)0, (Real)1, (Real)0, (Real)0, (Real)0 }; + M[2] = { (Real)0, (Real)0, (Real)1, -E0[2], -E1[2], (Real)0, (Real)0, (Real)1, (Real)0 , (Real)0 }; + M[3] = { -E0[0], -E0[1], -E0[2], dotE0E0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)1, (Real)0 }; + M[4] = { -E1[0], -E1[1], -E1[2], (Real)0, dotE1E1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[5] = { (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[6] = { (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[7] = { (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[8] = { (Real)0, (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[9] = { (Real)0, (Real)0, (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + + Real t0 = (z[3] * (Real)2 - (Real)1) * rectangle.extent[0]; + Real t1 = (z[4] * (Real)2 - (Real)1) * rectangle.extent[1]; + result.rectangleParameter[0] = t0; + result.rectangleParameter[1] = t1; + result.closestPoint[0] = rectangle.center + t0 * rectangle.axis[0] + t1 * rectangle.axis[1]; + result.closestPoint[1] = box.center; + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = z[i] - box.extent[i]; + result.closestPoint[1] += result.boxParameter[i] * box.axis[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations was not + // specified to be large enough or there is a problem due to + // floating-point rounding errors. If you believe the latter is + // true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 2; ++i) + { + result.rectangleParameter[i] = (Real)0; + } + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3Rectangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3Rectangle3.h new file mode 100644 index 000000000000..18fcb2c5927a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3Rectangle3.h @@ -0,0 +1,104 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +template +class DCPQuery, Rectangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real rectangle0Parameter[2], rectangle1Parameter[2]; + Vector3 closestPoint[2]; + }; + + Result operator()(Rectangle3 const& rectangle0, + Rectangle3 const& rectangle1); +}; + + +template +typename DCPQuery, Rectangle3>::Result +DCPQuery, Rectangle3>::operator()( + Rectangle3 const& rectangle0, Rectangle3 const& rectangle1) +{ + Result result; + + DCPQuery, Rectangle3> srQuery; + typename DCPQuery, Rectangle3>::Result + srResult; + result.sqrDistance = std::numeric_limits::max(); + + // Compare edges of rectangle0 to the interior of rectangle1. + for (int i1 = 0; i1 < 2; ++i1) + { + for (int i0 = -1; i0 <= 1; i0 += 2) + { + Real s = i0 * rectangle0.extent[1 - i1]; + Vector3 segCenter = rectangle0.center + + s * rectangle0.axis[1 - i1]; + Segment3 edge(segCenter, rectangle0.axis[i1], + rectangle0.extent[i1]); + + srResult = srQuery(edge, rectangle1); + if (srResult.sqrDistance < result.sqrDistance) + { + result.distance = srResult.distance; + result.sqrDistance = srResult.sqrDistance; + result.rectangle0Parameter[i1] = s; + result.rectangle0Parameter[1 - i1] = + srResult.segmentParameter; + result.rectangle1Parameter[0] = + srResult.rectangleParameter[0]; + result.rectangle1Parameter[1] = + srResult.rectangleParameter[1]; + result.closestPoint[0] = srResult.closestPoint[0]; + result.closestPoint[1] = srResult.closestPoint[1]; + } + } + } + + // Compare edges of rectangle1 to the interior of rectangle0. + for (int i1 = 0; i1 < 2; ++i1) + { + for (int i0 = -1; i0 <= 1; i0 += 2) + { + Real s = i0 * rectangle1.extent[1 - i1]; + Vector3 segCenter = rectangle1.center + + s * rectangle1.axis[1 - i1]; + Segment3 edge(segCenter, rectangle0.axis[i1], + rectangle0.extent[i1]); + + srResult = srQuery(edge, rectangle0); + if (srResult.sqrDistance < result.sqrDistance) + { + result.distance = srResult.distance; + result.sqrDistance = srResult.sqrDistance; + result.rectangle0Parameter[0] = + srResult.rectangleParameter[0]; + result.rectangle0Parameter[1] = + srResult.rectangleParameter[1]; + result.rectangle1Parameter[i1] = s; + result.rectangle1Parameter[1 - i1] = + srResult.segmentParameter; + result.closestPoint[0] = srResult.closestPoint[1]; + result.closestPoint[1] = srResult.closestPoint[0]; + } + } + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3AlignedBox3.h new file mode 100644 index 000000000000..78947e7eaca6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3AlignedBox3.h @@ -0,0 +1,84 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2016/11/17) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, AlignedBox3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real segmentParameter; + Vector3 closestPoint[2]; + }; + + Result operator()(Segment3 const& segment, + AlignedBox3 const& box); +}; + + +template +typename DCPQuery, AlignedBox3>::Result +DCPQuery, AlignedBox3> ::operator()( + Segment3 const& segment, AlignedBox3 const& box) +{ + Result result; + + Vector3 segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Line3 line(segCenter, segDirection); + DCPQuery, AlignedBox3> lbQuery; + auto lbResult = lbQuery(line, box); + + if (lbResult.lineParameter >= -segExtent) + { + if (lbResult.lineParameter <= segExtent) + { + result.sqrDistance = lbResult.sqrDistance; + result.distance = lbResult.distance; + result.segmentParameter = lbResult.lineParameter; + result.closestPoint[0] = lbResult.closestPoint[0]; + result.closestPoint[1] = lbResult.closestPoint[1]; + } + else + { + DCPQuery, AlignedBox3> pbQuery; + Vector3 point = segCenter + segExtent*segDirection; + auto pbResult = pbQuery(point, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = pbResult.boxClosest; + } + } + else + { + DCPQuery, AlignedBox3> pbQuery; + Vector3 point = segCenter - segExtent*segDirection; + auto pbResult = pbQuery(point, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.segmentParameter = -segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = pbResult.boxClosest; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3OrientedBox3.h new file mode 100644 index 000000000000..bf4bb07b4a5b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3OrientedBox3.h @@ -0,0 +1,86 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, OrientedBox3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real segmentParameter; + Vector3 closestPoint[2]; + }; + + Result operator()(Segment3 const& segment, + OrientedBox3 const& box); +}; + + +template +typename DCPQuery, OrientedBox3>::Result +DCPQuery, OrientedBox3> ::operator()( + Segment3 const& segment, OrientedBox3 const& box) +{ + Result result; + + Vector3 segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Line3 line(segCenter, segDirection); + DCPQuery, OrientedBox3> lbQuery; + auto lbResult = lbQuery(line, box); + + if (lbResult.lineParameter >= -segExtent) + { + if (lbResult.lineParameter <= segExtent) + { + result.sqrDistance = lbResult.sqrDistance; + result.distance = lbResult.distance; + result.segmentParameter = lbResult.lineParameter; + result.closestPoint[0] = lbResult.closestPoint[0]; + result.closestPoint[1] = lbResult.closestPoint[1]; + } + else + { + DCPQuery, OrientedBox3> pbQuery; + Vector3 point = segCenter + segExtent*segDirection; + auto pbResult = pbQuery(point, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = pbResult.boxClosest; + } + } + else + { + DCPQuery, OrientedBox3> pbQuery; + Vector3 point = segCenter - segExtent*segDirection; + auto pbResult = pbQuery(point, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = pbResult.boxClosest; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3Rectangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3Rectangle3.h new file mode 100644 index 000000000000..d7b0082f6cb2 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3Rectangle3.h @@ -0,0 +1,87 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Rectangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real segmentParameter, rectangleParameter[2]; + Vector3 closestPoint[2]; + }; + + Result operator()(Segment3 const& segment, + Rectangle3 const& rectangle); +}; + + +template +typename DCPQuery, Rectangle3>::Result +DCPQuery, Rectangle3>::operator()( + Segment3 const& segment, Rectangle3 const& rectangle) +{ + Result result; + + Vector3 segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Line3 line(segCenter, segDirection); + DCPQuery, Rectangle3> lrQuery; + auto lrResult = lrQuery(line, rectangle); + + if (lrResult.lineParameter >= -segExtent) + { + if (lrResult.lineParameter <= segExtent) + { + result.distance = lrResult.distance; + result.sqrDistance = lrResult.sqrDistance; + result.segmentParameter = lrResult.lineParameter; + result.rectangleParameter[0] = lrResult.rectangleParameter[0]; + result.rectangleParameter[1] = lrResult.rectangleParameter[1]; + result.closestPoint[0] = lrResult.closestPoint[0]; + result.closestPoint[1] = lrResult.closestPoint[1]; + } + else + { + DCPQuery, Rectangle3> prQuery; + Vector3 point = segCenter + segExtent*segDirection; + auto prResult = prQuery(point, rectangle); + result.sqrDistance = prResult.sqrDistance; + result.distance = prResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = prResult.rectangleClosestPoint; + } + } + else + { + DCPQuery, Rectangle3> prQuery; + Vector3 point = segCenter - segExtent*segDirection; + auto prResult = prQuery(point, rectangle); + result.sqrDistance = prResult.sqrDistance; + result.distance = prResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = prResult.rectangleClosestPoint; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3Triangle3.h new file mode 100644 index 000000000000..e1091d9ec585 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3Triangle3.h @@ -0,0 +1,88 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Triangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real segmentParameter, triangleParameter[3]; + Vector3 closestPoint[2]; + }; + + Result operator()(Segment3 const& segment, + Triangle3 const& triangle); +}; + + +template +typename DCPQuery, Triangle3>::Result +DCPQuery, Triangle3>::operator()( + Segment3 const& segment, Triangle3 const& triangle) +{ + Result result; + + Vector3 segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Line3 line(segCenter, segDirection); + DCPQuery, Triangle3> ltQuery; + auto ltResult = ltQuery(line, triangle); + + if (ltResult.lineParameter >= -segExtent) + { + if (ltResult.lineParameter <= segExtent) + { + result.distance = ltResult.distance; + result.sqrDistance = ltResult.sqrDistance; + result.segmentParameter = ltResult.lineParameter; + result.triangleParameter[0] = ltResult.triangleParameter[0]; + result.triangleParameter[1] = ltResult.triangleParameter[1]; + result.triangleParameter[2] = ltResult.triangleParameter[2]; + result.closestPoint[0] = ltResult.closestPoint[0]; + result.closestPoint[1] = ltResult.closestPoint[1]; + } + else + { + DCPQuery, Triangle3> ptQuery; + Vector3 point = segCenter + segExtent*segDirection; + auto ptResult = ptQuery(point, triangle); + result.sqrDistance = ptResult.sqrDistance; + result.distance = ptResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = ptResult.closest; + } + } + else + { + DCPQuery, Triangle3> ptQuery; + Vector3 point = segCenter - segExtent*segDirection; + auto ptResult = ptQuery(point, triangle); + result.sqrDistance = ptResult.sqrDistance; + result.distance = ptResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = ptResult.closest; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegmentSegment.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegmentSegment.h new file mode 100644 index 000000000000..efb833c0dde9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegmentSegment.h @@ -0,0 +1,427 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +// Compute the closest points on the line segments P(s) = (1-s)*P0 + s*P1 and +// Q(t) = (1-t)*Q0 + t*Q1 for 0 <= s <= 1 and 0 <= t <= 1. The algorithm is +// robust even for nearly parallel segments. Effectively, it uses a conjugate +// gradient search for the minimum of the squared distance function, which +// avoids the numerical problems introduced by divisions in the case the +// minimum is located at an interior point of the domain. See the document +// http://www.geometrictools.com/Documentation/DistanceLine3Line3.pdf +// for details. + +namespace gte +{ + +template +class DCPQuery, Segment> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closest[2]; + }; + + Result operator()(Segment const& segment0, + Segment const& segment1); + + Result operator()(Vector const& P0, Vector const& P1, + Vector const& Q0, Vector const& Q1); + +private: + // Compute the root of h(z) = h0 + slope*z and clamp it to the interval + // [0,1]. It is required that for h1 = h(1), either (h0 < 0 and h1 > 0) + // or (h0 > 0 and h1 < 0). + Real GetClampedRoot(Real slope, Real h0, Real h1); + + // Compute the intersection of the line dR/ds = 0 with the domain [0,1]^2. + // The direction of the line dR/ds is conjugate to (1,0), so the algorithm + // for minimization is effectively the conjugate gradient algorithm for a + // quadratic function. + void ComputeIntersection(Real const sValue[2], int const classify[2], + int edge[2], Real end[2][2]); + + // Compute the location of the minimum of R on the segment of intersection + // for the line dR/ds = 0 and the domain [0,1]^2. + void ComputeMinimumParameters(int const edge[2], Real const end[2][2], + Real parameter[2]); + + // The coefficients of R(s,t), not including the constant term. + Real mA, mB, mC, mD, mE; + + // dR/ds(i,j) at the four corners of the domain + Real mF00, mF10, mF01, mF11; + + // dR/dt(i,j) at the four corners of the domain + Real mG00, mG10, mG01, mG11; +}; + +// Template aliases for convenience. +template +using DCPSegmentSegment = DCPQuery, Segment>; + +template +using DCPSegment2Segment2 = DCPSegmentSegment<2, Real>; + +template +using DCPSegment3Segment3 = DCPSegmentSegment<3, Real>; + + +template +typename DCPQuery, Segment>::Result +DCPQuery, Segment>::operator()( + Segment const& segment0, Segment const& segment1) +{ + return operator()(segment0.p[0], segment0.p[1], segment1.p[0], + segment1.p[1]); +} + +template +typename DCPQuery, Segment>::Result +DCPQuery, Segment>::operator()( + Vector const& P0, Vector const& P1, + Vector const& Q0, Vector const& Q1) +{ + Result result; + + // The code allows degenerate line segments; that is, P0 and P1 can be + // the same point or Q0 and Q1 can be the same point. The quadratic + // function for squared distance between the segment is + // R(s,t) = a*s^2 - 2*b*s*t + c*t^2 + 2*d*s - 2*e*t + f + // for (s,t) in [0,1]^2 where + // a = Dot(P1-P0,P1-P0), b = Dot(P1-P0,Q1-Q0), c = Dot(Q1-Q0,Q1-Q0), + // d = Dot(P1-P0,P0-Q0), e = Dot(Q1-Q0,P0-Q0), f = Dot(P0-Q0,P0-Q0) + Vector P1mP0 = P1 - P0; + Vector Q1mQ0 = Q1 - Q0; + Vector P0mQ0 = P0 - Q0; + mA = Dot(P1mP0, P1mP0); + mB = Dot(P1mP0, Q1mQ0); + mC = Dot(Q1mQ0, Q1mQ0); + mD = Dot(P1mP0, P0mQ0); + mE = Dot(Q1mQ0, P0mQ0); + + mF00 = mD; + mF10 = mF00 + mA; + mF01 = mF00 - mB; + mF11 = mF10 - mB; + + mG00 = -mE; + mG10 = mG00 - mB; + mG01 = mG00 + mC; + mG11 = mG10 + mC; + + if (mA > (Real)0 && mC > (Real)0) + { + // Compute the solutions to dR/ds(s0,0) = 0 and dR/ds(s1,1) = 0. The + // location of sI on the s-axis is stored in classifyI (I = 0 or 1). If + // sI <= 0, classifyI is -1. If sI >= 1, classifyI is 1. If 0 < sI < 1, + // classifyI is 0. This information helps determine where to search for + // the minimum point (s,t). The fij values are dR/ds(i,j) for i and j in + // {0,1}. + + Real sValue[2]; + sValue[0] = GetClampedRoot(mA, mF00, mF10); + sValue[1] = GetClampedRoot(mA, mF01, mF11); + + int classify[2]; + for (int i = 0; i < 2; ++i) + { + if (sValue[i] <= (Real)0) + { + classify[i] = -1; + } + else if (sValue[i] >= (Real)1) + { + classify[i] = +1; + } + else + { + classify[i] = 0; + } + } + + if (classify[0] == -1 && classify[1] == -1) + { + // The minimum must occur on s = 0 for 0 <= t <= 1. + result.parameter[0] = (Real)0; + result.parameter[1] = GetClampedRoot(mC, mG00, mG01); + } + else if (classify[0] == +1 && classify[1] == +1) + { + // The minimum must occur on s = 1 for 0 <= t <= 1. + result.parameter[0] = (Real)1; + result.parameter[1] = GetClampedRoot(mC, mG10, mG11); + } + else + { + // The line dR/ds = 0 intersects the domain [0,1]^2 in a + // nondegenerate segment. Compute the endpoints of that segment, + // end[0] and end[1]. The edge[i] flag tells you on which domain + // edge end[i] lives: 0 (s=0), 1 (s=1), 2 (t=0), 3 (t=1). + int edge[2]; + Real end[2][2]; + ComputeIntersection(sValue, classify, edge, end); + + // The directional derivative of R along the segment of + // intersection is + // H(z) = (end[1][1]-end[1][0])*dR/dt((1-z)*end[0] + z*end[1]) + // for z in [0,1]. The formula uses the fact that dR/ds = 0 on + // the segment. Compute the minimum of H on [0,1]. + ComputeMinimumParameters(edge, end, result.parameter); + } + } + else + { + if (mA > (Real)0) + { + // The Q-segment is degenerate (Q0 and Q1 are the same point) and + // the quadratic is R(s,0) = a*s^2 + 2*d*s + f and has (half) + // first derivative F(t) = a*s + d. The closest P-point is + // interior to the P-segment when F(0) < 0 and F(1) > 0. + result.parameter[0] = GetClampedRoot(mA, mF00, mF10); + result.parameter[1] = (Real)0; + } + else if (mC > (Real)0) + { + // The P-segment is degenerate (P0 and P1 are the same point) and + // the quadratic is R(0,t) = c*t^2 - 2*e*t + f and has (half) + // first derivative G(t) = c*t - e. The closest Q-point is + // interior to the Q-segment when G(0) < 0 and G(1) > 0. + result.parameter[0] = (Real)0; + result.parameter[1] = GetClampedRoot(mC, mG00, mG01); + } + else + { + // P-segment and Q-segment are degenerate. + result.parameter[0] = (Real)0; + result.parameter[1] = (Real)0; + } + } + + + result.closest[0] = + ((Real)1 - result.parameter[0]) * P0 + result.parameter[0] * P1; + result.closest[1] = + ((Real)1 - result.parameter[1]) * Q0 + result.parameter[1] * Q1; + Vector diff = result.closest[0] - result.closest[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + +template +Real DCPQuery, Segment>::GetClampedRoot( + Real slope, Real h0, Real h1) +{ + // Theoretically, r is in (0,1). However, when the slope is nearly zero, + // then so are h0 and h1. Significant numerical rounding problems can + // occur when using floating-point arithmetic. If the rounding causes r + // to be outside the interval, clamp it. It is possible that r is in + // (0,1) and has rounding errors, but because h0 and h1 are both nearly + // zero, the quadratic is nearly constant on (0,1). Any choice of p + // should not cause undesirable accuracy problems for the final distance + // computation. + // + // NOTE: You can use bisection to recompute the root or even use + // bisection to compute the root and skip the division. This is generally + // slower, which might be a problem for high-performance applications. + + Real r; + if (h0 < (Real)0) + { + if (h1 > (Real)0) + { + r = -h0 / slope; + if (r > (Real)1) + { + r = (Real)0.5; + } + // The slope is positive and -h0 is positive, so there is no + // need to test for a negative value and clamp it. + } + else + { + r = (Real)1; + } + } + else + { + r = (Real)0; + } + return r; +} + +template +void DCPQuery, Segment>::ComputeIntersection( + Real const sValue[2], int const classify[2], int edge[2], Real end[2][2]) +{ + // The divisions are theoretically numbers in [0,1]. Numerical rounding + // errors might cause the result to be outside the interval. When this + // happens, it must be that both numerator and denominator are nearly + // zero. The denominator is nearly zero when the segments are nearly + // perpendicular. The numerator is nearly zero when the P-segment is + // nearly degenerate (mF00 = a is small). The choice of 0.5 should not + // cause significant accuracy problems. + // + // NOTE: You can use bisection to recompute the root or even use + // bisection to compute the root and skip the division. This is generally + // slower, which might be a problem for high-performance applications. + + if (classify[0] < 0) + { + edge[0] = 0; + end[0][0] = (Real)0; + end[0][1] = mF00 / mB; + if (end[0][1] < (Real)0 || end[0][1] > (Real)1) + { + end[0][1] = (Real)0.5; + } + + if (classify[1] == 0) + { + edge[1] = 3; + end[1][0] = sValue[1]; + end[1][1] = (Real)1; + } + else // classify[1] > 0 + { + edge[1] = 1; + end[1][0] = (Real)1; + end[1][1] = mF10 / mB; + if (end[1][1] < (Real)0 || end[1][1] > (Real)1) + { + end[1][1] = (Real)0.5; + } + } + } + else if (classify[0] == 0) + { + edge[0] = 2; + end[0][0] = sValue[0]; + end[0][1] = (Real)0; + + if (classify[1] < 0) + { + edge[1] = 0; + end[1][0] = (Real)0; + end[1][1] = mF00 / mB; + if (end[1][1] < (Real)0 || end[1][1] > (Real)1) + { + end[1][1] = (Real)0.5; + } + } + else if (classify[1] == 0) + { + edge[1] = 3; + end[1][0] = sValue[1]; + end[1][1] = (Real)1; + } + else + { + edge[1] = 1; + end[1][0] = (Real)1; + end[1][1] = mF10 / mB; + if (end[1][1] < (Real)0 || end[1][1] > (Real)1) + { + end[1][1] = (Real)0.5; + } + } + } + else // classify[0] > 0 + { + edge[0] = 1; + end[0][0] = (Real)1; + end[0][1] = mF10 / mB; + if (end[0][1] < (Real)0 || end[0][1] > (Real)1) + { + end[0][1] = (Real)0.5; + } + + if (classify[1] == 0) + { + edge[1] = 3; + end[1][0] = sValue[1]; + end[1][1] = (Real)1; + } + else + { + edge[1] = 0; + end[1][0] = (Real)0; + end[1][1] = mF00 / mB; + if (end[1][1] < (Real)0 || end[1][1] > (Real)1) + { + end[1][1] = (Real)0.5; + } + } + } +} + +template +void DCPQuery, Segment>:: +ComputeMinimumParameters(int const edge[2], Real const end[2][2], + Real parameter[2]) +{ + Real delta = end[1][1] - end[0][1]; + Real h0 = delta * (-mB * end[0][0] + mC * end[0][1] - mE); + if (h0 >= (Real)0) + { + if (edge[0] == 0) + { + parameter[0] = (Real)0; + parameter[1] = GetClampedRoot(mC, mG00, mG01); + } + else if (edge[0] == 1) + { + parameter[0] = (Real)1; + parameter[1] = GetClampedRoot(mC, mG10, mG11); + } + else + { + parameter[0] = end[0][0]; + parameter[1] = end[0][1]; + } + } + else + { + Real h1 = delta * (-mB * end[1][0] + mC * end[1][1] - mE); + if (h1 <= (Real)0) + { + if (edge[1] == 0) + { + parameter[0] = (Real)0; + parameter[1] = GetClampedRoot(mC, mG00, mG01); + } + else if (edge[1] == 1) + { + parameter[0] = (Real)1; + parameter[1] = GetClampedRoot(mC, mG10, mG11); + } + else + { + parameter[0] = end[1][0]; + parameter[1] = end[1][1]; + } + } + else // h0 < 0 and h1 > 0 + { + Real z = std::min(std::max(h0 / (h0 - h1), (Real)0), (Real)1); + Real omz = (Real)1 - z; + parameter[0] = omz * end[0][0] + z * end[1][0]; + parameter[1] = omz * end[0][1] + z * end[1][1]; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegmentSegmentExact.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegmentSegmentExact.h new file mode 100644 index 000000000000..49b024579636 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegmentSegmentExact.h @@ -0,0 +1,289 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// Compute the closest points on the line segments P(s) = (1-s)*P0 + s*P1 and +// Q(t) = (1-t)*Q0 + t*Q1 for 0 <= s <= 1 and 0 <= t <= 1. The algorithm +// relies on exact rational arithmetic. + +namespace gte +{ + +template +class DistanceSegmentSegmentExact +{ +public: + struct Result + { + Rational sqrDistance; + Rational parameter[2]; + Vector closest[2]; + }; + + Result operator()(Segment const& segment0, + Segment const& segment1); + + Result operator()( + Vector const& P0, Vector const& P1, + Vector const& Q0, Vector const& Q1); +}; + + +template +typename DistanceSegmentSegmentExact::Result +DistanceSegmentSegmentExact::operator()( + Segment const& segment0, + Segment const& segment1) +{ + return operator()(segment0.p[0], segment0.p[1], segment1.p[0], + segment1.p[1]); +} + +template +typename DistanceSegmentSegmentExact::Result +DistanceSegmentSegmentExact::operator()( + Vector const& P0, Vector const& P1, + Vector const& Q0, Vector const& Q1) +{ + Vector P1mP0 = P1 - P0; + Vector Q1mQ0 = Q1 - Q0; + Vector P0mQ0 = P0 - Q0; + Rational a = Dot(P1mP0, P1mP0); + Rational b = Dot(P1mP0, Q1mQ0); + Rational c = Dot(Q1mQ0, Q1mQ0); + Rational d = Dot(P1mP0, P0mQ0); + Rational e = Dot(Q1mQ0, P0mQ0); + Rational const zero = (Rational)0; + Rational const one = (Rational)1; + Rational det = a * c - b * b; + Rational s, t, nd, bmd, bte, ctd, bpe, ate, btd; + + if (det > zero) + { + bte = b * e; + ctd = c * d; + if (bte <= ctd) // s <= 0 + { + s = zero; + if (e <= zero) // t <= 0 + { + // region 6 + t = zero; + nd = -d; + if (nd >= a) + { + s = one; + } + else if (nd > zero) + { + s = nd / a; + } + // else: s is already zero + } + else if (e < c) // 0 < t < 1 + { + // region 5 + t = e / c; + } + else // t >= 1 + { + // region 4 + t = one; + bmd = b - d; + if (bmd >= a) + { + s = one; + } + else if (bmd > zero) + { + s = bmd / a; + } + // else: s is already zero + } + } + else // s > 0 + { + s = bte - ctd; + if (s >= det) // s >= 1 + { + // s = 1 + s = one; + bpe = b + e; + if (bpe <= zero) // t <= 0 + { + // region 8 + t = zero; + nd = -d; + if (nd <= zero) + { + s = zero; + } + else if (nd < a) + { + s = nd / a; + } + // else: s is already one + } + else if (bpe < c) // 0 < t < 1 + { + // region 1 + t = bpe / c; + } + else // t >= 1 + { + // region 2 + t = one; + bmd = b - d; + if (bmd <= zero) + { + s = zero; + } + else if (bmd < a) + { + s = bmd / a; + } + // else: s is already one + } + } + else // 0 < s < 1 + { + ate = a * e; + btd = b * d; + if (ate <= btd) // t <= 0 + { + // region 7 + t = zero; + nd = -d; + if (nd <= zero) + { + s = zero; + } + else if (nd >= a) + { + s = one; + } + else + { + s = nd / a; + } + } + else // t > 0 + { + t = ate - btd; + if (t >= det) // t >= 1 + { + // region 3 + t = one; + bmd = b - d; + if (bmd <= zero) + { + s = zero; + } + else if (bmd >= a) + { + s = one; + } + else + { + s = bmd / a; + } + } + else // 0 < t < 1 + { + // region 0 + s /= det; + t /= det; + } + } + } + } + } + else + { + // The segments are parallel. The quadratic factors to R(s,t) = + // a*(s-(b/a)*t)^2 + 2*d*(s - (b/a)*t) + f, where a*c = b^2, + // e = b*d/a, f = |P0-Q0|^2, and b is not zero. R is constant along + // lines of the form s-(b/a)*t = k, and the minimum of R occurs on the + // line a*s - b*t + d = 0. This line must intersect both the s-axis + // and the t-axis because 'a' and 'b' are not zero. Because of + // parallelism, the line is also represented by -b*s + c*t - e = 0. + // + // The code determines an edge of the domain [0,1]^2 that intersects + // the minimum line, or if none of the edges intersect, it determines + // the closest corner to the minimum line. The conditionals are + // designed to test first for intersection with the t-axis (s = 0) + // using -b*s + c*t - e = 0 and then with the s-axis (t = 0) using + // a*s - b*t + d = 0. + + // When s = 0, solve c*t - e = 0 (t = e/c). + if (e <= zero) // t <= 0 + { + // Now solve a*s - b*t + d = 0 for t = 0 (s = -d/a). + t = zero; + nd = -d; + if (nd <= zero) // s <= 0 + { + // region 6 + s = zero; + } + else if (nd >= a) // s >= 1 + { + // region 8 + s = one; + } + else // 0 < s < 1 + { + // region 7 + s = nd / a; + } + } + else if (e >= c) // t >= 1 + { + // Now solve a*s - b*t + d = 0 for t = 1 (s = (b-d)/a). + t = one; + bmd = b - d; + if (bmd <= zero) // s <= 0 + { + // region 4 + s = zero; + } + else if (bmd >= a) // s >= 1 + { + // region 2 + s = one; + } + else // 0 < s < 1 + { + // region 3 + s = bmd / a; + } + } + else // 0 < t < 1 + { + // The point (0,e/c) is on the line and domain, so we have one + // point at which R is a minimum. + s = zero; + t = e / c; + } + } + + Result result; + result.parameter[0] = s; + result.parameter[1] = t; + result.closest[0] = P0 + s * P1mP0; + result.closest[1] = Q0 + t * Q1mQ0; + Vector diff = result.closest[1] - result.closest[0]; + result.sqrDistance = Dot(diff, diff); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3AlignedBox3.h new file mode 100644 index 000000000000..66e357d05c05 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3AlignedBox3.h @@ -0,0 +1,136 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, AlignedBox3> +{ +public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array triangleParameter, boxParameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of whether + // the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 81 (n = 9, maxIterations = n*n). If + // the solver fails to converge, try increasing the maximum number of + // iterations. + void SetMaxLCPIterations(int maxLCPIterations); + + Result operator()(Triangle3 const& triangle, AlignedBox3 const& box); + +private: + LCPSolver mLCP; +}; + + +template +void DCPQuery, AlignedBox3>::SetMaxLCPIterations(int maxLCPIterations) +{ + mLCP.SetMaxIterations(maxLCPIterations); +} + +template +typename DCPQuery, AlignedBox3>::Result +DCPQuery, AlignedBox3>::operator()( + Triangle3 const& triangle, AlignedBox3 const& box) +{ + Result result; + + // Translate the triangle and aligned box so that the aligned box becomes + // a canonical box. + Vector3 K = box.max - box.min; + Vector3 V = triangle.v[0] - box.min; + Vector3 E0 = triangle.v[1] - triangle.v[0]; + Vector3 E1 = triangle.v[2] - triangle.v[0]; + + // Compute quantities to initialize q and M in the LCP. + Real dotVE0 = Dot(V, E0); + Real dotVE1 = Dot(V, E1); + Real dotE0E0 = Dot(E0, E0); + Real dotE0E1 = Dot(E0, E1); + Real dotE1E1 = Dot(E1, E1); + + // The LCP has 5 variables and 4 (nontrivial) inequality constraints. + std::array q = + { + -V[0], -V[1], -V[2], dotVE0, dotVE1, K[0], K[1], K[2], (Real)1 + }; + + std::array, 9> M; + M[0] = { (Real)1, (Real)0, (Real)0, -E0[0], -E1[0], (Real)1, (Real)0, (Real)0, (Real)0 }; + M[1] = { (Real)0, (Real)1, (Real)0, -E0[1], -E1[1], (Real)0, (Real)1, (Real)0, (Real)0 }; + M[2] = { (Real)0, (Real)0, (Real)1, -E0[2], -E1[2], (Real)0, (Real)0, (Real)1, (Real)0 }; + M[3] = { -E0[0], -E0[1], -E0[2], dotE0E0, dotE0E1, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[4] = { -E1[0], -E1[1], -E1[2], dotE0E1, dotE1E1, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[5] = { (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[6] = { (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[7] = { (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[8] = { (Real)0, (Real)0, (Real)0, (Real)-1, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0 }; + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + + result.triangleParameter[0] = (Real)1 - z[3] - z[4]; + result.triangleParameter[1] = z[3]; + result.triangleParameter[2] = z[4]; + result.closestPoint[0] = triangle.v[0] + z[3] * E0 + z[4] * E1; + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = z[i] + box.min[i]; + result.closestPoint[1][i] = result.boxParameter[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations was not + // specified to be large enough or there is a problem due to + // floating-point rounding errors. If you believe the latter is + // true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 3; ++i) + { + result.triangleParameter[i] = (Real)0; + result.boxParameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3OrientedBox3.h new file mode 100644 index 000000000000..a0f0507f1613 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3OrientedBox3.h @@ -0,0 +1,144 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, OrientedBox3> +{ +public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array triangleParameter, boxParameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of whether + // the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 81 (n = 9, maxIterations = n*n). If + // the solver fails to converge, try increasing the maximum number of + // iterations. + void SetMaxLCPIterations(int maxLCPIterations); + + Result operator()(Triangle3 const& triangle, OrientedBox3 const& box); + +private: + LCPSolver mLCP; +}; + + +template +void DCPQuery, OrientedBox3>::SetMaxLCPIterations(int maxLCPIterations) +{ + mLCP.SetMaxIterations(maxLCPIterations); +} + +template +typename DCPQuery, OrientedBox3>::Result +DCPQuery, OrientedBox3>::operator()( + Triangle3 const& triangle, OrientedBox3 const& box) +{ + Result result; + + // Rigidly transform the triangle and oriented box so that the oriented + // box becomes a canonical box. + Vector3 K = box.extent * (Real)2; + Vector3 tempV = triangle.v[0] - box.center; + Vector3 tempE0 = triangle.v[1] - triangle.v[0]; + Vector3 tempE1 = triangle.v[2] - triangle.v[0]; + Vector3 V, E0, E1; + for (int i = 0; i < 3; ++i) + { + V[i] = Dot(box.axis[i], tempV) + box.extent[i]; + E0[i] = Dot(box.axis[i], tempE0); + E1[i] = Dot(box.axis[i], tempE1); + } + + // Compute quantities to initialize q and M in the LCP. + Real dotVE0 = Dot(V, E0); + Real dotVE1 = Dot(V, E1); + Real dotE0E0 = Dot(E0, E0); + Real dotE0E1 = Dot(E0, E1); + Real dotE1E1 = Dot(E1, E1); + + // The LCP has 5 variables and 4 (nontrivial) inequality constraints. + std::array q = + { + -V[0], -V[1], -V[2], dotVE0, dotVE1, K[0], K[1], K[2], (Real)1 + }; + + std::array, 9> M; + M[0] = { (Real)1, (Real)0, (Real)0, -E0[0], -E1[0], (Real)1, (Real)0, (Real)0, (Real)0 }; + M[1] = { (Real)0, (Real)1, (Real)0, -E0[1], -E1[1], (Real)0, (Real)1, (Real)0, (Real)0 }; + M[2] = { (Real)0, (Real)0, (Real)1, -E0[2], -E1[2], (Real)0, (Real)0, (Real)1, (Real)0 }; + M[3] = { -E0[0], -E0[1], -E0[2], dotE0E0, dotE0E1, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[4] = { -E1[0], -E1[1], -E1[2], dotE0E1, dotE1E1, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[5] = { (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[6] = { (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[7] = { (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[8] = { (Real)0, (Real)0, (Real)0, (Real)-1, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0 }; + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + + result.triangleParameter[0] = (Real)1 - z[3] - z[4]; + result.triangleParameter[1] = z[3]; + result.triangleParameter[2] = z[4]; + result.closestPoint[0] = triangle.v[0] + z[3] * tempE0 + z[4] * tempE1; + result.closestPoint[1] = box.center; + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = z[i] - box.extent[i]; + result.closestPoint[1] += result.boxParameter[i] * box.axis[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations was not + // specified to be large enough or there is a problem due to + // floating-point rounding errors. If you believe the latter is + // true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 3; ++i) + { + result.triangleParameter[i] = (Real)0; + result.boxParameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3Rectangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3Rectangle3.h new file mode 100644 index 000000000000..765191e175fb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3Rectangle3.h @@ -0,0 +1,99 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, Rectangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real triangleParameter[3], rectangleParameter[2]; + Vector3 closestPoint[2]; + }; + + Result operator()(Triangle3 const& triangle, + Rectangle3 const& rectangle); +}; + + +template +typename DCPQuery, Rectangle3>::Result +DCPQuery, Rectangle3>::operator()( + Triangle3 const& triangle, Rectangle3 const& rectangle) +{ + Result result; + + result.sqrDistance = std::numeric_limits::max(); + + // Compare edges of triangle to the interior of rectangle. + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + Vector3 segCenter = ((Real)0.5)*(triangle.v[i0] + + triangle.v[i1]); + Vector3 segDirection = triangle.v[i1] - triangle.v[i0]; + Real segExtent = ((Real)0.5)*Normalize(segDirection); + Segment3 edge(segCenter, segDirection, segExtent); + + DCPQuery, Rectangle3> srQuery; + auto srResult = srQuery(edge, rectangle); + if (srResult.sqrDistance < result.sqrDistance) + { + result.distance = srResult.distance; + result.sqrDistance = srResult.sqrDistance; + Real ratio = srResult.segmentParameter / segExtent; // in [-1,1] + result.triangleParameter[i0] = ((Real)0.5)*((Real)1 - ratio); + result.triangleParameter[i1] = + (Real)1 - result.triangleParameter[i0]; + result.triangleParameter[3 - i0 - i1] = (Real)0; + result.rectangleParameter[0] = srResult.rectangleParameter[0]; + result.rectangleParameter[1] = srResult.rectangleParameter[1]; + result.closestPoint[0] = srResult.closestPoint[0]; + result.closestPoint[1] = srResult.closestPoint[1]; + } + } + + // Compare edges of rectangle to the interior of triangle. + for (int i1 = 0; i1 < 2; ++i1) + { + for (int i0 = -1; i0 <= 1; i0 += 2) + { + Real s = i0 * rectangle.extent[1 - i1]; + Vector3 segCenter = rectangle.center + + s * rectangle.axis[1 - i1]; + Segment3 edge(segCenter, rectangle.axis[i1], + rectangle.extent[i1]); + + DCPQuery, Triangle3> stQuery; + auto stResult = stQuery(edge, triangle); + if (stResult.sqrDistance < result.sqrDistance) + { + result.distance = stResult.distance; + result.sqrDistance = stResult.sqrDistance; + result.triangleParameter[0] = stResult.triangleParameter[0]; + result.triangleParameter[1] = stResult.triangleParameter[1]; + result.triangleParameter[2] = stResult.triangleParameter[2]; + result.rectangleParameter[i1] = s; + result.rectangleParameter[1 - i1] = stResult.segmentParameter; + result.closestPoint[0] = stResult.closestPoint[1]; + result.closestPoint[1] = stResult.closestPoint[0]; + } + } + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3Triangle3.h new file mode 100644 index 000000000000..88f4d50bb140 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3Triangle3.h @@ -0,0 +1,100 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +template +class DCPQuery, Triangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real triangle0Parameter[3], triangle1Parameter[3]; + Vector3 closestPoint[2]; + }; + + Result operator()(Triangle3 const& triangle0, + Triangle3 const& triangle1); +}; + + +template +typename DCPQuery, Triangle3>::Result +DCPQuery, Triangle3>::operator()( + Triangle3 const& triangle0, Triangle3 const& triangle1) +{ + Result result; + + DCPQuery, Triangle3> stQuery; + typename DCPQuery, Triangle3>::Result + stResult; + result.sqrDistance = std::numeric_limits::max(); + + // Compare edges of triangle0 to the interior of triangle1. + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + Vector3 segCenter = ((Real)0.5)*(triangle0.v[i0] + + triangle0.v[i1]); + Vector3 segDirection = triangle0.v[i1] - triangle0.v[i0]; + Real segExtent = ((Real)0.5)*Normalize(segDirection); + Segment3 edge(segCenter, segDirection, segExtent); + + stResult = stQuery(edge, triangle1); + if (stResult.sqrDistance < result.sqrDistance) + { + result.distance = stResult.distance; + result.sqrDistance = stResult.sqrDistance; + Real ratio = stResult.segmentParameter / segExtent; // in [-1,1] + result.triangle0Parameter[i0] = ((Real)0.5)*((Real)1 - ratio); + result.triangle0Parameter[i1] = + (Real)1 - result.triangle0Parameter[i0]; + result.triangle0Parameter[3 - i0 - i1] = (Real)0; + result.triangle1Parameter[0] = stResult.triangleParameter[0]; + result.triangle1Parameter[1] = stResult.triangleParameter[1]; + result.triangle1Parameter[2] = stResult.triangleParameter[2]; + result.closestPoint[0] = stResult.closestPoint[0]; + result.closestPoint[1] = stResult.closestPoint[1]; + } + } + + // Compare edges of triangle1 to the interior of triangle0. + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + Vector3 segCenter = ((Real)0.5)*(triangle1.v[i0] + + triangle1.v[i1]); + Vector3 segDirection = triangle1.v[i1] - triangle1.v[i0]; + Real segExtent = ((Real)0.5)*Normalize(segDirection); + Segment3 edge(segCenter, segDirection, segExtent); + + stResult = stQuery(edge, triangle0); + if (stResult.sqrDistance < result.sqrDistance) + { + result.distance = stResult.distance; + result.sqrDistance = stResult.sqrDistance; + Real ratio = stResult.segmentParameter / segExtent; // in [-1,1] + result.triangle0Parameter[0] = stResult.triangleParameter[0]; + result.triangle0Parameter[1] = stResult.triangleParameter[1]; + result.triangle0Parameter[2] = stResult.triangleParameter[2]; + result.triangle1Parameter[i0] = ((Real)0.5)*((Real)1 - ratio); + result.triangle1Parameter[i1] = + (Real)1 - result.triangle0Parameter[i0]; + result.triangle1Parameter[3 - i0 - i1] = (Real)0; + result.closestPoint[0] = stResult.closestPoint[0]; + result.closestPoint[1] = stResult.closestPoint[1]; + } + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETManifoldMesh.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETManifoldMesh.cpp new file mode 100644 index 000000000000..5a6fb5e882a9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETManifoldMesh.cpp @@ -0,0 +1,384 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2017/01/02) + +#include +#include +#include + +using namespace gte; + +ETManifoldMesh::~ETManifoldMesh() +{ +} + +ETManifoldMesh::ETManifoldMesh(ECreator eCreator, TCreator tCreator) + : + mECreator(eCreator ? eCreator : CreateEdge), + mTCreator(tCreator ? tCreator : CreateTriangle), + mAssertOnNonmanifoldInsertion(true) +{ +} + +ETManifoldMesh::ETManifoldMesh(ETManifoldMesh const& mesh) +{ + *this = mesh; +} + +ETManifoldMesh& ETManifoldMesh::operator=(ETManifoldMesh const& mesh) +{ + Clear(); + + mECreator = mesh.mECreator; + mTCreator = mesh.mTCreator; + mAssertOnNonmanifoldInsertion = mesh.mAssertOnNonmanifoldInsertion; + for (auto const& element : mesh.mTMap) + { + Insert(element.first.V[0], element.first.V[1], element.first.V[2]); + } + + return *this; +} + +ETManifoldMesh::EMap const& ETManifoldMesh::GetEdges() const +{ + return mEMap; +} + +ETManifoldMesh::TMap const& ETManifoldMesh::GetTriangles() const +{ + return mTMap; +} + +bool ETManifoldMesh::AssertOnNonmanifoldInsertion(bool doAssert) +{ + std::swap(doAssert, mAssertOnNonmanifoldInsertion); + return doAssert; // return the previous state +} + +std::shared_ptr ETManifoldMesh::Insert(int v0, int v1, int v2) +{ + TriangleKey tkey(v0, v1, v2); + if (mTMap.find(tkey) != mTMap.end()) + { + // The triangle already exists. Return a null pointer as a signal to + // the caller that the insertion failed. + return nullptr; + } + + // Create the new triangle. It will be added to mTMap at the end of the + // function so that if an assertion is triggered and the function returns + // early, the (bad) triangle will not be part of the mesh. + std::shared_ptr tri = mTCreator(v0, v1, v2); + + // Add the edges to the mesh if they do not already exist. + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + EdgeKey ekey(tri->V[i0], tri->V[i1]); + std::shared_ptr edge; + auto eiter = mEMap.find(ekey); + if (eiter == mEMap.end()) + { + // This is the first time the edge is encountered. + edge = mECreator(tri->V[i0], tri->V[i1]); + mEMap[ekey] = edge; + + // Update the edge and triangle. + edge->T[0] = tri; + tri->E[i0] = edge; + } + else + { + // This is the second time the edge is encountered. + edge = eiter->second; + if (!edge) + { + LogError("Unexpected condition."); + return nullptr; + } + + // Update the edge. + if (edge->T[1].lock()) + { + if (mAssertOnNonmanifoldInsertion) + { + LogInformation("The mesh must be manifold."); + } + return nullptr; + } + edge->T[1] = tri; + + // Update the adjacent triangles. + auto adjacent = edge->T[0].lock(); + if (!adjacent) + { + LogError("Unexpected condition."); + return nullptr; + } + for (int j = 0; j < 3; ++j) + { + if (adjacent->E[j].lock() == edge) + { + adjacent->T[j] = tri; + break; + } + } + + // Update the triangle. + tri->E[i0] = edge; + tri->T[i0] = adjacent; + } + } + + mTMap[tkey] = tri; + return tri; +} + +bool ETManifoldMesh::Remove(int v0, int v1, int v2) +{ + TriangleKey tkey(v0, v1, v2); + auto titer = mTMap.find(tkey); + if (titer == mTMap.end()) + { + // The triangle does not exist. + return false; + } + + // Get the triangle. + std::shared_ptr tri = titer->second; + + // Remove the edges and update adjacent triangles if necessary. + for (int i = 0; i < 3; ++i) + { + // Inform the edges the triangle is being deleted. + auto edge = tri->E[i].lock(); + if (!edge) + { + // The triangle edge should be nonnull. + LogError("Unexpected condition."); + return false; + } + + if (edge->T[0].lock() == tri) + { + // One-triangle edges always have pointer at index zero. + edge->T[0] = edge->T[1]; + edge->T[1].reset(); + } + else if (edge->T[1].lock() == tri) + { + edge->T[1].reset(); + } + else + { + LogError("Unexpected condition."); + return false; + } + + // Remove the edge if you have the last reference to it. + if (!edge->T[0].lock() && !edge->T[1].lock()) + { + EdgeKey ekey(edge->V[0], edge->V[1]); + mEMap.erase(ekey); + } + + // Inform adjacent triangles the triangle is being deleted. + auto adjacent = tri->T[i].lock(); + if (adjacent) + { + for (int j = 0; j < 3; ++j) + { + if (adjacent->T[j].lock() == tri) + { + adjacent->T[j].reset(); + break; + } + } + } + } + + mTMap.erase(tkey); + return true; +} + +void ETManifoldMesh::Clear() +{ + mEMap.clear(); + mTMap.clear(); +} + +bool ETManifoldMesh::IsClosed() const +{ + for (auto const& element : mEMap) + { + auto edge = element.second; + if (!edge->T[0].lock() || !edge->T[1].lock()) + { + return false; + } + } + return true; +} + +bool ETManifoldMesh::IsOriented() const +{ + for (auto const& element : mEMap) + { + auto edge = element.second; + if (edge->T[0].lock() && edge->T[1].lock()) + { + // In each triangle, find the ordered edge that corresponds to the + // unordered edge element.first. Also find the vertex opposite + // that edge. + bool edgePositive[2] = { false, false }; + int vOpposite[2] = { -1, -1 }; + for (int j = 0; j < 2; ++j) + { + auto tri = edge->T[j].lock(); + for (int i = 0; i < 3; ++i) + { + if (tri->V[i] == element.first.V[0]) + { + int vNext = tri->V[(i + 1) % 3]; + if (vNext == element.first.V[1]) + { + edgePositive[j] = true; + vOpposite[j] = tri->V[(i + 2) % 3]; + } + else + { + edgePositive[j] = false; + vOpposite[j] = vNext; + } + break; + } + } + } + + // To be oriented consistently, the edges must have reversed + // ordering and the oppositive vertices cannot match. + if (edgePositive[0] == edgePositive[1] || vOpposite[0] == vOpposite[1]) + { + return false; + } + } + } + return true; +} + +void ETManifoldMesh::GetComponents( + std::vector>>& components) const +{ + // visited: 0 (unvisited), 1 (discovered), 2 (finished) + std::map, int> visited; + for (auto const& element : mTMap) + { + visited.insert(std::make_pair(element.second, 0)); + } + + for (auto& element : mTMap) + { + auto tri = element.second; + if (visited[tri] == 0) + { + std::vector> component; + DepthFirstSearch(tri, visited, component); + components.push_back(component); + } + } +} + +void ETManifoldMesh::GetComponents( + std::vector>>& components) const +{ + // visited: 0 (unvisited), 1 (discovered), 2 (finished) + std::map, int> visited; + for (auto const& element : mTMap) + { + visited.insert(std::make_pair(element.second, 0)); + } + + for (auto& element : mTMap) + { + std::shared_ptr tri = element.second; + if (visited[tri] == 0) + { + std::vector> component; + DepthFirstSearch(tri, visited, component); + + std::vector> keyComponent; + keyComponent.reserve(component.size()); + for (auto const& t : component) + { + keyComponent.push_back(TriangleKey(t->V[0], t->V[1], t->V[2])); + } + components.push_back(keyComponent); + } + } +} + +void ETManifoldMesh::DepthFirstSearch(std::shared_ptr const& tInitial, + std::map, int>& visited, + std::vector>& component) const +{ + // Allocate the maximum-size stack that can occur in the depth-first + // search. The stack is empty when the index top is -1. + std::vector> tStack(mTMap.size()); + int top = -1; + tStack[++top] = tInitial; + while (top >= 0) + { + std::shared_ptr tri = tStack[top]; + visited[tri] = 1; + int i; + for (i = 0; i < 3; ++i) + { + std::shared_ptr adj = tri->T[i].lock(); + if (adj && visited[adj] == 0) + { + tStack[++top] = adj; + break; + } + } + if (i == 3) + { + visited[tri] = 2; + component.push_back(tri); + --top; + } + } +} + +std::shared_ptr ETManifoldMesh::CreateEdge(int v0, int v1) +{ + return std::make_shared(v0, v1); +} + +std::shared_ptr ETManifoldMesh::CreateTriangle(int v0, int v1, int v2) +{ + return std::make_shared(v0, v1, v2); +} + +ETManifoldMesh::Edge::~Edge() +{ +} + +ETManifoldMesh::Edge::Edge(int v0, int v1) +{ + V[0] = v0; + V[1] = v1; +} + +ETManifoldMesh::Triangle::~Triangle() +{ +} + +ETManifoldMesh::Triangle::Triangle(int v0, int v1, int v2) +{ + V[0] = v0; + V[1] = v1; + V[2] = v2; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETManifoldMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETManifoldMesh.h new file mode 100644 index 000000000000..e891009912fc --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETManifoldMesh.h @@ -0,0 +1,145 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2017/01/02) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +class GTE_IMPEXP ETManifoldMesh +{ +public: + // Edge data types. + class Edge; + typedef std::shared_ptr (*ECreator)(int, int); + typedef std::map, std::shared_ptr> EMap; + + // Triangle data types. + class Triangle; + typedef std::shared_ptr (*TCreator)(int, int, int); + typedef std::map, std::shared_ptr> TMap; + + // Edge object. + class GTE_IMPEXP Edge + { + public: + virtual ~Edge(); + Edge(int v0, int v1); + + // Vertices of the edge. + int V[2]; + + // Triangles sharing the edge. + std::weak_ptr T[2]; + }; + + // Triangle object. + class GTE_IMPEXP Triangle + { + public: + virtual ~Triangle(); + Triangle(int v0, int v1, int v2); + + // Vertices, listed in counterclockwise order (V[0],V[1],V[2]). + int V[3]; + + // Adjacent edges. E[i] points to edge (V[i],V[(i+1)%3]). + std::weak_ptr E[3]; + + // Adjacent triangles. T[i] points to the adjacent triangle + // sharing edge E[i]. + std::weak_ptr T[3]; + }; + + + // Construction and destruction. + virtual ~ETManifoldMesh(); + ETManifoldMesh(ECreator eCreator = nullptr, TCreator tCreator = nullptr); + + // Support for a deep copy of the mesh. The mEMap and mTMap objects have + // dynamically allocated memory for edges and triangles. A shallow copy + // of the pointers to this memory is problematic. Allowing sharing, say, + // via std::shared_ptr, is an option but not really the intent of copying + // the mesh graph. + ETManifoldMesh(ETManifoldMesh const& mesh); + ETManifoldMesh& operator=(ETManifoldMesh const& mesh); + + // Member access. + EMap const& GetEdges() const; + TMap const& GetTriangles() const; + + // If the insertion of a triangle fails because the mesh would become + // nonmanifold, the default behavior is to trigger a LogError message. + // You can disable this behavior in situations where you want the Logger + // system on but you want to continue gracefully without an assertion. + // The return value is the previous value of the internal state + // mAssertOnNonmanifoldInsertion. + bool AssertOnNonmanifoldInsertion(bool doAssert); + + // If is not in the mesh, a Triangle object is created and + // returned; otherwise, is in the mesh and nullptr is returned. + // If the insertion leads to a nonmanifold mesh, the call fails with a + // nullptr returned. + virtual std::shared_ptr Insert(int v0, int v1, int v2); + + // If is in the mesh, it is removed and 'true' is returned; + // otherwise, is not in the mesh and 'false' is returned. + virtual bool Remove(int v0, int v1, int v2); + + // Destroy the edges and triangles to obtain an empty mesh. + virtual void Clear(); + + // A manifold mesh is closed if each edge is shared twice. A closed + // mesh is not necessarily oriented. For example, you could have a + // mesh with spherical topology. The upper hemisphere has outer-facing + // normals and the lower hemisphere has inner-facing normals. The + // discontinuity in orientation occurs on the circle shared by the + // hemispheres. + bool IsClosed() const; + + // Test whether all triangles in the mesh are oriented consistently and + // that no two triangles are coincident. The latter means that you + // cannot have both triangles and in the mesh to + // be considered oriented. + bool IsOriented() const; + + // Compute the connected components of the edge-triangle graph that the + // mesh represents. The first function returns pointers into 'this' + // object's containers, so you must consume the components before + // clearing or destroying 'this'. The second function returns triangle + // keys, which requires three times as much storage as the pointers but + // allows you to clear or destroy 'this' before consuming the components. + void GetComponents(std::vector>>& components) const; + void GetComponents(std::vector>>& components) const; + +protected: + // The edge data and default edge creation. + static std::shared_ptr CreateEdge(int v0, int v1); + ECreator mECreator; + EMap mEMap; + + // The triangle data and default triangle creation. + static std::shared_ptr CreateTriangle(int v0, int v1, int v2); + TCreator mTCreator; + TMap mTMap; + bool mAssertOnNonmanifoldInsertion; // default: true + + // Support for computing connected components. This is a straightforward + // depth-first search of the graph but uses a preallocated stack rather + // than a recursive function that could possibly overflow the call stack. + void DepthFirstSearch(std::shared_ptr const& tInitial, + std::map, int>& visited, + std::vector>& component) const; +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETNonmanifoldMesh.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETNonmanifoldMesh.cpp new file mode 100644 index 000000000000..105bf5a96196 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETNonmanifoldMesh.cpp @@ -0,0 +1,310 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2018 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.20.0 (2019/01/09) + +#include +#include +#include + +using namespace gte; + +ETNonmanifoldMesh::~ETNonmanifoldMesh() +{ +} + +ETNonmanifoldMesh::ETNonmanifoldMesh(ECreator eCreator, TCreator tCreator) + : + mECreator(eCreator ? eCreator : CreateEdge), + mTCreator(tCreator ? tCreator : CreateTriangle) +{ +} + +ETNonmanifoldMesh::ETNonmanifoldMesh(ETNonmanifoldMesh const& mesh) +{ + *this = mesh; +} + +ETNonmanifoldMesh& ETNonmanifoldMesh::operator=(ETNonmanifoldMesh const& mesh) +{ + Clear(); + + mECreator = mesh.mECreator; + mTCreator = mesh.mTCreator; + for (auto const& element : mesh.mTMap) + { + Insert(element.first.V[0], element.first.V[1], element.first.V[2]); + } + + return *this; +} + +ETNonmanifoldMesh::EMap const& ETNonmanifoldMesh::GetEdges() const +{ + return mEMap; +} + +ETNonmanifoldMesh::TMap const& ETNonmanifoldMesh::GetTriangles() const +{ + return mTMap; +} + +std::shared_ptr ETNonmanifoldMesh::Insert(int v0, int v1, int v2) +{ + TriangleKey tkey(v0, v1, v2); + if (mTMap.find(tkey) != mTMap.end()) + { + // The triangle already exists. Return a null pointer as a signal to + // the caller that the insertion failed. + return nullptr; + } + + // Create the new triangle. It will be added to mTMap at the end of the + // function so that if an assertion is triggered and the function returns + // early, the (bad) triangle will not be part of the mesh. + std::shared_ptr tri = mTCreator(v0, v1, v2); + + // Add the edges to the mesh if they do not already exist. + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + EdgeKey ekey(tri->V[i0], tri->V[i1]); + std::shared_ptr edge; + auto eiter = mEMap.find(ekey); + if (eiter == mEMap.end()) + { + // This is the first time the edge is encountered. + edge = mECreator(tri->V[i0], tri->V[i1]); + mEMap[ekey] = edge; + } + else + { + // The edge was previously encountered and created. + edge = eiter->second; + if (!edge) + { + LogError("Unexpected condition."); + return nullptr; + } + } + + // Associate the edge with the triangle. + tri->E[i0] = edge; + + // Update the adjacent set of triangles for the edge. + edge->T.insert(tri); + } + + mTMap[tkey] = tri; + return tri; +} + +bool ETNonmanifoldMesh::Remove(int v0, int v1, int v2) +{ + TriangleKey tkey(v0, v1, v2); + auto titer = mTMap.find(tkey); + if (titer == mTMap.end()) + { + // The triangle does not exist. + return false; + } + + // Get the triangle. + std::shared_ptr tri = titer->second; + + // Remove the edges and update adjacent triangles if necessary. + for (int i = 0; i < 3; ++i) + { + // Inform the edges the triangle is being deleted. + auto edge = tri->E[i].lock(); + if (!edge) + { + LogError("Unexpected condition."); + return false; + } + + // Remove the triangle from the edge's set of adjacent triangles. + size_t numRemoved = edge->T.erase(tri); + if (numRemoved == 0) + { + LogError("Unexpected condition."); + return false; + } + + // Remove the edge if you have the last reference to it. + if (edge->T.size() == 0) + { + EdgeKey ekey(edge->V[0], edge->V[1]); + mEMap.erase(ekey); + } + } + + // Remove the triangle from the graph. + mTMap.erase(tkey); + return true; +} + +void ETNonmanifoldMesh::Clear() +{ + mEMap.clear(); + mTMap.clear(); +} + +bool ETNonmanifoldMesh::IsManifold() const +{ + for (auto const& element : mEMap) + { + if (element.second->T.size() > 2) + { + return false; + } + } + return true; +} + +bool ETNonmanifoldMesh::IsClosed() const +{ + for (auto const& element : mEMap) + { + if (element.second->T.size() != 2) + { + return false; + } + } + return true; +} + +void ETNonmanifoldMesh::GetComponents(std::vector>>& components) const +{ + // visited: 0 (unvisited), 1 (discovered), 2 (finished) + std::map, int> visited; + for (auto const& element : mTMap) + { + visited.insert(std::make_pair(element.second, 0)); + } + + for (auto& element : mTMap) + { + auto tri = element.second; + if (visited[tri] == 0) + { + std::vector> component; + DepthFirstSearch(tri, visited, component); + components.push_back(component); + } + } +} + +void ETNonmanifoldMesh::GetComponents(std::vector>>& components) const +{ + // visited: 0 (unvisited), 1 (discovered), 2 (finished) + std::map, int> visited; + for (auto const& element : mTMap) + { + visited.insert(std::make_pair(element.second, 0)); + } + + for (auto& element : mTMap) + { + std::shared_ptr tri = element.second; + if (visited[tri] == 0) + { + std::vector> component; + DepthFirstSearch(tri, visited, component); + + std::vector> keyComponent; + keyComponent.reserve(component.size()); + for (auto const& t : component) + { + keyComponent.push_back(TriangleKey(t->V[0], t->V[1], t->V[2])); + } + components.push_back(keyComponent); + } + } +} + +void ETNonmanifoldMesh::DepthFirstSearch(std::shared_ptr const& tInitial, + std::map, int>& visited, std::vector>& component) const +{ + // Allocate the maximum-size stack that can occur in the depth-first + // search. The stack is empty when the index top is -1. + std::vector> tStack(mTMap.size()); + int top = -1; + tStack[++top] = tInitial; + while (top >= 0) + { + std::shared_ptr tri = tStack[top]; + visited[tri] = 1; + int i; + for (i = 0; i < 3; ++i) + { + auto edge = tri->E[i].lock(); + if (!edge) + { + LogError("Unexpected condition."); + return; + } + + bool foundUnvisited = false; + for (auto const& adjw : edge->T) + { + auto adj = adjw.lock(); + if (!adj) + { + LogError("Unexpected condition."); + return; + } + + if (visited[adj] == 0) + { + tStack[++top] = adj; + foundUnvisited = true; + break; + } + } + + if (foundUnvisited) + { + break; + } + } + if (i == 3) + { + visited[tri] = 2; + component.push_back(tri); + --top; + } + } +} + +std::shared_ptr ETNonmanifoldMesh::CreateEdge(int v0, int v1) +{ + return std::make_shared(v0, v1); +} + +std::shared_ptr ETNonmanifoldMesh::CreateTriangle(int v0, int v1, int v2) +{ + return std::make_shared(v0, v1, v2); +} + +ETNonmanifoldMesh::Edge::~Edge() +{ +} + +ETNonmanifoldMesh::Edge::Edge(int v0, int v1) +{ + V[0] = v0; + V[1] = v1; +} + +ETNonmanifoldMesh::Triangle::~Triangle() +{ +} + +ETNonmanifoldMesh::Triangle::Triangle(int v0, int v1, int v2) +{ + V[0] = v0; + V[1] = v1; + V[2] = v2; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETNonmanifoldMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETNonmanifoldMesh.h new file mode 100644 index 000000000000..83a216b2a426 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETNonmanifoldMesh.h @@ -0,0 +1,141 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2018 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.20.0 (2019/01/09) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace gte +{ + class GTE_IMPEXP ETNonmanifoldMesh + { + public: + // Edge data types. + class Edge; + typedef std::shared_ptr(*ECreator)(int, int); + typedef std::map, std::shared_ptr> EMap; + + // Triangle data types. + class Triangle; + typedef std::shared_ptr(*TCreator)(int, int, int); + typedef std::map, std::shared_ptr> TMap; + + // Edge object. + class GTE_IMPEXP Edge + { + public: + virtual ~Edge(); + Edge(int v0, int v1); + + bool operator<(Edge const& other) const + { + return EdgeKey(V[0], V[1]) < EdgeKey(other.V[0], other.V[1]); + } + + // Vertices of the edge. + int V[2]; + + // Triangles sharing the edge. + std::set, WeakPtrLT> T; + }; + + // Triangle object. + class GTE_IMPEXP Triangle + { + public: + virtual ~Triangle(); + Triangle(int v0, int v1, int v2); + + bool operator<(Triangle const& other) const + { + return TriangleKey(V[0], V[1], V[2]) < TriangleKey(other.V[0], other.V[1], other.V[2]); + } + + // Vertices listed in counterclockwise order (V[0],V[1],V[2]). + int V[3]; + + // Adjacent edges. E[i] points to edge (V[i],V[(i+1)%3]). + std::weak_ptr E[3]; + }; + + + // Construction and destruction. + virtual ~ETNonmanifoldMesh(); + ETNonmanifoldMesh(ECreator eCreator = nullptr, TCreator tCreator = nullptr); + + // Support for a deep copy of the mesh. The mEMap and mTMap objects + // have dynamically allocated memory for edges and triangles. A + // shallow copy of the pointers to this memory is problematic. + // Allowing sharing, say, via std::shared_ptr, is an option but not + // really the intent of copying the mesh graph. + ETNonmanifoldMesh(ETNonmanifoldMesh const& mesh); + ETNonmanifoldMesh& operator=(ETNonmanifoldMesh const& mesh); + + // Member access. + EMap const& GetEdges() const; + TMap const& GetTriangles() const; + + // If is not in the mesh, a Triangle object is created and + // returned; otherwise, is in the mesh and nullptr is + // returned. + virtual std::shared_ptr Insert(int v0, int v1, int v2); + + // If is in the mesh, it is removed and 'true' is returned; + // otherwise, is not in the mesh and 'false' is returned. + virtual bool Remove(int v0, int v1, int v2); + + // Destroy the edges and triangles to obtain an empty mesh. + virtual void Clear(); + + // A manifold mesh has the property that an edge is shared by at most + // two triangles sharing. + bool IsManifold() const; + + // A manifold mesh is closed if each edge is shared twice. A closed + // mesh is not necessarily oriented. For example, you could have a + // mesh with spherical topology. The upper hemisphere has outer-facing + // normals and the lower hemisphere has inner-facing normals. The + // discontinuity in orientation occurs on the circle shared by the + // hemispheres. + bool IsClosed() const; + + // Compute the connected components of the edge-triangle graph that + // the mesh represents. The first function returns pointers into + // 'this' object's containers, so you must consume the components + // before clearing or destroying 'this'. The second function returns + // triangle keys, which requires three times as much storage as the + // pointers but allows you to clear or destroy 'this' before consuming + // the components. + void GetComponents(std::vector>>& components) const; + void GetComponents(std::vector>>& components) const; + + protected: + // The edge data and default edge creation. + static std::shared_ptr CreateEdge(int v0, int v1); + ECreator mECreator; + EMap mEMap; + + // The triangle data and default triangle creation. + static std::shared_ptr CreateTriangle(int v0, int v1, int v2); + TCreator mTCreator; + TMap mTMap; + + // Support for computing connected components. This is a + // straightforward depth-first search of the graph but uses a + // preallocated stack rather than a recursive function that could + // possibly overflow the call stack. + void DepthFirstSearch(std::shared_ptr const& tInitial, + std::map, int>& visited, + std::vector>& component) const; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEdgeKey.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEdgeKey.h new file mode 100644 index 000000000000..71848f3b11be --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEdgeKey.h @@ -0,0 +1,65 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/05/22) + +#pragma once + +#include + +namespace gte +{ + template + class EdgeKey : public FeatureKey<2, Ordered> + { + public: + // An ordered edge has (V[0],V[1]) = (v0,v1). An unordered edge has + // (V[0],V[1]) = (min(V[0],V[1]),max(V[0],V[1])). + EdgeKey(); // creates key (-1,-1) + explicit EdgeKey(int v0, int v1); + }; + + template<> + inline + EdgeKey::EdgeKey() + { + V[0] = -1; + V[1] = -1; + } + + template<> + inline + EdgeKey::EdgeKey(int v0, int v1) + { + V[0] = v0; + V[1] = v1; + } + + template<> + inline + EdgeKey::EdgeKey() + { + V[0] = -1; + V[1] = -1; + } + + template<> + inline + EdgeKey::EdgeKey(int v0, int v1) + { + if (v0 < v1) + { + // v0 is minimum + V[0] = v0; + V[1] = v1; + } + else + { + // v1 is minimum + V[0] = v1; + V[1] = v0; + } + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEllipse3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEllipse3.h new file mode 100644 index 000000000000..356c40de979b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEllipse3.h @@ -0,0 +1,157 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The plane containing ellipse is Dot(N,X-C) = 0 where X is any point in the +// plane, C is the ellipse center, and N is a unit-length normal to the plane. +// Vectors A0, A1, and N form an orthonormal right-handed set. The ellipse in +// the plane is parameterized by X = C + e0*cos(t)*A0 + e1*sin(t)*A1, where A0 +// is the major axis, A1 is the minor axis, and e0 and e1 are the extents +// along those axes. The angle t is in [-pi,pi) and e0 >= e1 > 0. + +namespace gte +{ + +template +class Ellipse3 +{ +public: + // Construction and destruction. The default constructor sets center to + // (0,0,0), A0 to (1,0,0), A1 to (0,1,0), normal to (0,0,1), e0 to 1, and + // e1 to 1. + Ellipse3(); + Ellipse3(Vector3 const& inCenter, Vector3 const& inNormal, + Vector3 const inAxis[2], Vector2 const& inExtent); + + // Public member access. + Vector3 center, normal; + Vector3 axis[2]; + Vector2 extent; + +public: + // Comparisons to support sorted containers. + bool operator==(Ellipse3 const& ellipse) const; + bool operator!=(Ellipse3 const& ellipse) const; + bool operator< (Ellipse3 const& ellipse) const; + bool operator<=(Ellipse3 const& ellipse) const; + bool operator> (Ellipse3 const& ellipse) const; + bool operator>=(Ellipse3 const& ellipse) const; +}; + + +template +Ellipse3::Ellipse3() + : + center(Vector3::Zero()), + normal(Vector3::Unit(2)), + extent({ (Real)1, (Real)1 }) +{ + axis[0] = Vector3::Unit(0); + axis[1] = Vector3::Unit(1); +} + +template +Ellipse3::Ellipse3(Vector3 const& inCenter, + Vector3 const& inNormal, Vector3 const inAxis[2], + Vector2 const& inExtent) + : + center(inCenter), + normal(inNormal), + extent(inExtent) +{ + for (int i = 0; i < 2; ++i) + { + axis[i] = inAxis[i]; + } +} + +template +bool Ellipse3::operator==(Ellipse3 const& ellipse) const +{ + return center == ellipse.center + && normal == ellipse.normal + && axis[0] == ellipse.axis[0] + && axis[1] == ellipse.axis[1] + && extent == ellipse.extent; +} + +template +bool Ellipse3::operator!=(Ellipse3 const& ellipse) const +{ + return !operator==(ellipse); +} + +template +bool Ellipse3::operator<(Ellipse3 const& ellipse) const +{ + if (center < ellipse.center) + { + return true; + } + + if (center > ellipse.center) + { + return false; + } + + if (normal < ellipse.normal) + { + return true; + } + + if (normal > ellipse.normal) + { + return false; + } + + if (axis[0] < ellipse.axis[0]) + { + return true; + } + + if (axis[0] > ellipse.axis[0]) + { + return false; + } + + if (axis[1] < ellipse.axis[1]) + { + return true; + } + + if (axis[1] > ellipse.axis[1]) + { + return false; + } + + return extent < ellipse.extent; +} + +template +bool Ellipse3::operator<=(Ellipse3 const& ellipse) const +{ + return operator<(ellipse) || operator==(ellipse); +} + +template +bool Ellipse3::operator>(Ellipse3 const& ellipse) const +{ + return !operator<=(ellipse); +} + +template +bool Ellipse3::operator>=(Ellipse3 const& ellipse) const +{ + return !operator<(ellipse); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEulerAngles.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEulerAngles.h new file mode 100644 index 000000000000..6d5d9e7f0067 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEulerAngles.h @@ -0,0 +1,82 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +// Factorization into Euler angles is not necessarily unique. Let the +// integer indices for the axes be (N0,N1,N2), which must be in the set +// {(0,1,2),(0,2,1),(1,0,2),(1,2,0),(2,0,1),(2,1,0), +// (0,1,0),(0,2,0),(1,0,1),(1,2,1),(2,0,2),(2,1,2)} +// Let the corresponding angles be (angleN0,angleN1,angleN2). If the +// result is ER_NOT_UNIQUE_SUM, then the multiple solutions occur because +// angleN2+angleN0 is constant. If the result is ER_NOT_UNIQUE_DIF, then +// the multiple solutions occur because angleN2-angleN0 is constant. In +// either type of nonuniqueness, the function returns angleN0=0. +enum EulerResult +{ + // The solution is invalid (incorrect axis indices). + ER_INVALID, + + // The solution is unique. + ER_UNIQUE, + + // The solution is not unique. A sum of angles is constant. + ER_NOT_UNIQUE_SUM, + + // The solution is not unique. A difference of angles is constant. + ER_NOT_UNIQUE_DIF +}; + +template +class EulerAngles +{ +public: + EulerAngles(); + EulerAngles(int i0, int i1, int i2, Real a0, Real a1, Real a2); + + int axis[3]; + Real angle[3]; + + // This member is set during conversions from rotation matrices, + // quaternions, or axis-angles. + EulerResult result; +}; + + +template +EulerAngles::EulerAngles() + : + result(ER_INVALID) +{ + for (int i = 0; i < 3; ++i) + { + axis[i] = 0; + angle[i] = (Real)0; + } +} + +template +EulerAngles::EulerAngles(int i0, int i1, int i2, Real a0, Real a1, + Real a2) + : + result(ER_UNIQUE) +{ + axis[0] = i0; + axis[1] = i1; + axis[2] = i2; + angle[0] = a0; + angle[1] = a1; + angle[2] = a2; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteExp2Estimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteExp2Estimate.h new file mode 100644 index 000000000000..c3983ad088d5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteExp2Estimate.h @@ -0,0 +1,154 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Minimax polynomial approximations to 2^x. The polynomial p(x) of +// degree D minimizes the quantity maximum{|2^x - p(x)| : x in [0,1]} +// over all polynomials of degree D. + +namespace gte +{ + +template +class Exp2Estimate +{ +public: + // The input constraint is x in [0,1]. For example, + // float x; // in [0,1] + // float result = Exp2Estimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input x can be any real number. Range reduction is used to + // generate a value y in [0,1], call Degree(y), and combine the output + // with the proper exponent to obtain the approximation. For example, + // float x; // x >= 0 + // float result = Exp2Estimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<1>, Real t); + inline static Real Evaluate(degree<2>, Real t); + inline static Real Evaluate(degree<3>, Real t); + inline static Real Evaluate(degree<4>, Real t); + inline static Real Evaluate(degree<5>, Real t); + inline static Real Evaluate(degree<6>, Real t); + inline static Real Evaluate(degree<7>, Real t); +}; + + +template +template +inline Real Exp2Estimate::Degree(Real x) +{ + return Evaluate(degree(), x); +} + +template +template +inline Real Exp2Estimate::DegreeRR(Real x) +{ + Real p = std::floor(x); + Real y = x - p; + Real poly = Degree(y); + Real result = std::ldexp(poly, (int)p); + return result; +} + +template +inline Real Exp2Estimate::Evaluate(degree<1>, Real t) +{ + Real poly; + poly = (Real)GTE_C_EXP2_DEG1_C1; + poly = (Real)GTE_C_EXP2_DEG1_C0 + poly * t; + return poly; +} + +template +inline Real Exp2Estimate::Evaluate(degree<2>, Real t) +{ + Real poly; + poly = (Real)GTE_C_EXP2_DEG2_C2; + poly = (Real)GTE_C_EXP2_DEG2_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG2_C0 + poly * t; + return poly; +} + +template +inline Real Exp2Estimate::Evaluate(degree<3>, Real t) +{ + Real poly; + poly = (Real)GTE_C_EXP2_DEG3_C3; + poly = (Real)GTE_C_EXP2_DEG3_C2 + poly * t; + poly = (Real)GTE_C_EXP2_DEG3_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG3_C0 + poly * t; + return poly; +} + +template +inline Real Exp2Estimate::Evaluate(degree<4>, Real t) +{ + Real poly; + poly = (Real)GTE_C_EXP2_DEG4_C4; + poly = (Real)GTE_C_EXP2_DEG4_C3 + poly * t; + poly = (Real)GTE_C_EXP2_DEG4_C2 + poly * t; + poly = (Real)GTE_C_EXP2_DEG4_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG4_C0 + poly * t; + return poly; +} + +template +inline Real Exp2Estimate::Evaluate(degree<5>, Real t) +{ + Real poly; + poly = (Real)GTE_C_EXP2_DEG5_C5; + poly = (Real)GTE_C_EXP2_DEG5_C4 + poly * t; + poly = (Real)GTE_C_EXP2_DEG5_C3 + poly * t; + poly = (Real)GTE_C_EXP2_DEG5_C2 + poly * t; + poly = (Real)GTE_C_EXP2_DEG5_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG5_C0 + poly * t; + return poly; +} + +template +inline Real Exp2Estimate::Evaluate(degree<6>, Real t) +{ + Real poly; + poly = (Real)GTE_C_EXP2_DEG6_C6; + poly = (Real)GTE_C_EXP2_DEG6_C5 + poly * t; + poly = (Real)GTE_C_EXP2_DEG6_C4 + poly * t; + poly = (Real)GTE_C_EXP2_DEG6_C3 + poly * t; + poly = (Real)GTE_C_EXP2_DEG6_C2 + poly * t; + poly = (Real)GTE_C_EXP2_DEG6_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG6_C0 + poly * t; + return poly; +} + +template +inline Real Exp2Estimate::Evaluate(degree<7>, Real t) +{ + Real poly; + poly = (Real)GTE_C_EXP2_DEG7_C7; + poly = (Real)GTE_C_EXP2_DEG7_C6 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C5 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C4 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C3 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C2 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C0 + poly * t; + return poly; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteExpEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteExpEstimate.h new file mode 100644 index 000000000000..53de0dfb3cae --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteExpEstimate.h @@ -0,0 +1,56 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Minimax polynomial approximations to 2^x. The polynomial p(x) of +// degree D minimizes the quantity maximum{|2^x - p(x)| : x in [0,1]} +// over all polynomials of degree D. The natural exponential is +// computed using exp(x) = 2^{x log(2)}, where log(2) is the natural +// logarithm of 2. + +namespace gte +{ + +template +class ExpEstimate +{ +public: + // The input constraint is x in [0,1]. For example, + // float x; // in [0,1] + // float result = ExpEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input x can be any real number. Range reduction is used to + // generate a value y in [0,1], call Degree(y), and combine the output + // with the proper exponent to obtain the approximation. For example, + // float x; // x >= 0 + // float result = ExpEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); +}; + + +template +template +inline Real ExpEstimate::Degree(Real x) +{ + return Exp2Estimate::Degree((Real)GTE_C_LN_2 * x); +} + +template +template +inline Real ExpEstimate::DegreeRR(Real x) +{ + return Exp2Estimate::DegreeRR((Real)GTE_C_LN_2 * x); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFIQuery.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFIQuery.h new file mode 100644 index 000000000000..4a7233c90d06 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFIQuery.h @@ -0,0 +1,33 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include + +namespace gte +{ + +// Find-intersection queries. + +template +class FIQuery +{ +public: + struct Result + { + // A FIQuery-base class B must define a B::Result struct with member + // 'bool intersect'. A FIQuery-derived class D must also derive a + // D::Result from B:Result but may have no members. The member + // 'intersect' is 'true' iff the primitives intersect. The operator() + // is non-const to allow FIQuery to store and modify private state + // that supports the query. + }; + Result operator()(Type0 const& primitive0, Type1 const& primitive1); +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFeatureKey.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFeatureKey.h new file mode 100644 index 000000000000..41ba56178c87 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFeatureKey.h @@ -0,0 +1,76 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +template +class FeatureKey +{ +protected: + // Abstract base class with V[] uninitialized. The derived classes must + // set the V[] values accordingly. + // + // An ordered feature key has V[0] = min(V[]) with (V[0],V[1],...,V[N-1]) a + // permutation of N inputs with an even number of transpositions. + // + // An unordered feature key has V[0] < V[1] < ... < V[N-1]. + // + // Note that the word 'order' is about the geometry of the feature, not + // the comparison order for any sorting. + FeatureKey(); + +public: + bool operator<(FeatureKey const& key) const; + bool operator==(FeatureKey const& key) const; + + int V[N]; +}; + + +template +FeatureKey::FeatureKey() +{ +} + +template +bool FeatureKey::operator<(FeatureKey const& key) const +{ + for (int i = N - 1; i >= 0; --i) + { + if (V[i] < key.V[i]) + { + return true; + } + + if (V[i] > key.V[i]) + { + return false; + } + } + return false; +} + +template +bool FeatureKey::operator==(FeatureKey const& key) const +{ + for (int i = 0; i < N; ++i) + { + if (V[i] != key.V[i]) + { + return false; + } + } + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFrenetFrame.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFrenetFrame.h new file mode 100644 index 000000000000..a2f86803fa90 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFrenetFrame.h @@ -0,0 +1,159 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class FrenetFrame2 +{ +public: + // Construction. The curve must persist as long as the FrenetFrame2 + // object does. + FrenetFrame2(std::shared_ptr> const& curve); + + // The normal is perpendicular to the tangent, rotated clockwise by + // pi/2 radians. + void operator()(Real t, Vector2& position, Vector2& tangent, + Vector2& normal) const; + + Real GetCurvature(Real t) const; + +private: + std::shared_ptr> mCurve; +}; + + +template +class FrenetFrame3 +{ +public: + // Construction. The curve must persist as long as the FrenetFrame3 + // object does. + FrenetFrame3(std::shared_ptr> const& curve); + + // The binormal is Cross(tangent, normal). + void operator()(Real t, Vector3& position, Vector3& tangent, + Vector3& normal, Vector3& binormal) const; + + Real GetCurvature(Real t) const; + Real GetTorsion(Real t) const; + +private: + std::shared_ptr> mCurve; +}; + + +template +FrenetFrame2::FrenetFrame2(std::shared_ptr> const& curve) + : + mCurve(curve) +{ +} + +template +void FrenetFrame2::operator()(Real t, Vector2& position, + Vector2& tangent, Vector2& normal) const +{ + Vector2 values[4]; + mCurve->Evaluate(t, 1, values); + position = values[0]; + tangent = values[1]; + Normalize(tangent); + normal = Perp(tangent); +} + +template +Real FrenetFrame2::GetCurvature(Real t) const +{ + Vector2 values[4]; + mCurve->Evaluate(t, 2, values); + Real speedSqr = Dot(values[1], values[1]); + if (speedSqr > (Real)0) + { + Real numer = DotPerp(values[1], values[2]); + Real denom = std::pow(speedSqr, (Real)1.5); + return numer / denom; + } + else + { + // Curvature is indeterminate, just return 0. + return (Real)0; + } +} + + + +template +FrenetFrame3::FrenetFrame3(std::shared_ptr> const& curve) + : + mCurve(curve) +{ +} + +template +void FrenetFrame3::operator()(Real t, Vector3& position, + Vector3& tangent, Vector3& normal, Vector3& binormal) const +{ + Vector3 values[4]; + mCurve->Evaluate(t, 2, values); + position = values[0]; + Real VDotV = Dot(values[1], values[1]); + Real VDotA = Dot(values[1], values[2]); + normal = VDotV * values[2] - VDotA * values[1]; + Normalize(normal); + tangent = values[1]; + Normalize(tangent); + binormal = Cross(tangent, normal); +} + +template +Real FrenetFrame3::GetCurvature(Real t) const +{ + Vector3 values[4]; + mCurve->Evaluate(t, 2, values); + Real speedSqr = Dot(values[1], values[1]); + if (speedSqr > (Real)0) + { + Real numer = Length(Cross(values[1], values[2])); + Real denom = std::pow(speedSqr, (Real)1.5); + return numer / denom; + } + else + { + // Curvature is indeterminate, just return 0. + return (Real)0; + } +} + +template +Real FrenetFrame3::GetTorsion(Real t) const +{ + Vector3 values[4]; + mCurve->Evaluate(t, 3, values); + Vector3 cross = Cross(values[1], values[2]); + Real denom = Dot(cross, cross); + if (denom > (Real)0) + { + Real numer = Dot(cross, values[3]); + return numer / denom; + } + else + { + // Torsion is indeterminate, just return 0. + return (Real)0; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFrustum3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFrustum3.h new file mode 100644 index 000000000000..e674452cc646 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFrustum3.h @@ -0,0 +1,257 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Orthogonal frustum. Let E be the origin, D be the direction vector, U be +// the up vector, and R be the right vector. Let u > 0 and r > 0 be the +// extents in the U and R directions, respectively. Let n and f be the +// extents in the D direction with 0 < n < f. The four corners of the frustum +// in the near plane are E + n*D + s0*u*U + s1*r*R where |s0| = |s1| = 1 (four +// choices). The four corners of the frustum in the far plane are +// E + f*D + (f/n)*(s0*u*U + s1*r*R) where |s0| = |s1| = 1 (four choices). + +namespace gte +{ + +template +class Frustum3 +{ +public: + // Construction and destruction. The default constructor sets the + // following values: origin (E) to (0,0,0), dVector (D) to (0,0,1), + // uVector (U) to (0,1,0), rVector (R) to (1,0,0), dMin (n) to 1, + // dMax (f) to 2, uBound (u) to 1, and rBound (r) to 1. + Frustum3(); + Frustum3(Vector3 const& inOrigin, Vector3 const& inDVector, + Vector3 const& inUVector, Vector3 const& inRVector, + Real inDMin, Real inDMax, Real inUBound, Real inRBound); + + // The Update() function must be called whenever changes are made to DMIN, + // DMax, UBound, or RBound. The values mDRatio, mMTwoUF, and mMTwoRF are + // dependent on the changes, so call the Get*() accessors only after the + // Update() call. + void Update(); + inline Real GetDRatio() const; + inline Real GetMTwoUF() const; + inline Real GetMTwoRF() const; + + void ComputeVertices(Vector3 vertex[8]) const; + + Vector3 origin, dVector, uVector, rVector; + Real dMin, dMax, uBound, rBound; + +public: + // Comparisons to support sorted containers. + bool operator==(Frustum3 const& frustum) const; + bool operator!=(Frustum3 const& frustum) const; + bool operator< (Frustum3 const& frustum) const; + bool operator<=(Frustum3 const& frustum) const; + bool operator> (Frustum3 const& frustum) const; + bool operator>=(Frustum3 const& frustum) const; + +protected: + // Quantities derived from the constructor inputs. + Real mDRatio, mMTwoUF, mMTwoRF; +}; + + +template +Frustum3::Frustum3() + : + origin(Vector3::Zero()), + dVector(Vector3::Unit(2)), + uVector(Vector3::Unit(1)), + rVector(Vector3::Unit(0)), + dMin((Real)1), + dMax((Real)2), + uBound((Real)1), + rBound((Real)1) +{ + Update(); +} + +template +Frustum3::Frustum3(Vector3 const& inOrigin, + Vector3 const& inDVector, Vector3 const& inUVector, + Vector3 const& inRVector, Real inDMin, Real inDMax, Real inUBound, + Real inRBound) + : + origin(inOrigin), + dVector(inDVector), + uVector(inUVector), + rVector(inRVector), + dMin(inDMin), + dMax(inDMax), + uBound(inUBound), + rBound(inRBound) +{ + Update(); +} + +template +void Frustum3::Update() +{ + mDRatio = dMax / dMin; + mMTwoUF = ((Real)-2) * uBound * dMax; + mMTwoRF = ((Real)-2) * rBound * dMax; +} + +template inline +Real Frustum3::GetDRatio() const +{ + return mDRatio; +} + +template inline +Real Frustum3::GetMTwoUF() const +{ + return mMTwoUF; +} + +template inline +Real Frustum3::GetMTwoRF() const +{ + return mMTwoRF; +} + +template +void Frustum3::ComputeVertices(Vector3 vertex[8]) const +{ + Vector3 dScaled = dMin * dVector; + Vector3 uScaled = uBound * uVector; + Vector3 rScaled = rBound * rVector; + + vertex[0] = dScaled - uScaled - rScaled; + vertex[1] = dScaled - uScaled + rScaled; + vertex[2] = dScaled + uScaled + rScaled; + vertex[3] = dScaled + uScaled - rScaled; + + for (int i = 0, ip = 4; i < 4; ++i, ++ip) + { + vertex[ip] = origin + mDRatio * vertex[i]; + vertex[i] += origin; + } +} + +template +bool Frustum3::operator==(Frustum3 const& frustum) const +{ + return origin == frustum.origin + && dVector == frustum.dVector + && uVector == frustum.uVector + && rVector == frustum.rVector + && dMin == frustum.dMin + && dMax == frustum.dMax + && uBound == frustum.uBound + && rBound == frustum.rBound; +} + +template +bool Frustum3::operator!=(Frustum3 const& frustum) const +{ + return !operator==(frustum); +} + +template +bool Frustum3::operator<(Frustum3 const& frustum) const +{ + if (origin < frustum.origin) + { + return true; + } + + if (origin > frustum.origin) + { + return false; + } + + if (dVector < frustum.dVector) + { + return true; + } + + if (dVector > frustum.dVector) + { + return false; + } + + if (uVector < frustum.uVector) + { + return true; + } + + if (uVector > frustum.uVector) + { + return false; + } + + if (rVector < frustum.rVector) + { + return true; + } + + if (rVector > frustum.rVector) + { + return false; + } + + if (dMin < frustum.dMin) + { + return true; + } + + if (dMin > frustum.dMin) + { + return false; + } + + if (dMax < frustum.dMax) + { + return true; + } + + if (dMax > frustum.dMax) + { + return false; + } + + if (uBound < frustum.uBound) + { + return true; + } + + if (uBound > frustum.uBound) + { + return false; + } + + return rBound < frustum.rBound; +} + +template +bool Frustum3::operator<=(Frustum3 const& frustum) const +{ + return operator<(frustum) || operator==(frustum); +} + +template +bool Frustum3::operator>(Frustum3 const& frustum) const +{ + return !operator<=(frustum); +} + +template +bool Frustum3::operator>=(Frustum3 const& frustum) const +{ + return !operator<(frustum); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGMatrix.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGMatrix.h new file mode 100644 index 000000000000..b1ab4a21d696 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGMatrix.h @@ -0,0 +1,847 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include + +// Uncomment these to test for out-of-range indices and size mismatches. +//#define GTE_ASSERT_ON_GMATRIX_INDEX_OUT_OF_RANGE +//#define GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH + +namespace gte +{ + +template +class GMatrix +{ +public: + // The table is length zero and mNumRows and mNumCols are set to zero. + GMatrix(); + + // The table is length numRows*numCols and the elements are initialized + // to zero. + GMatrix(int numRow, int numCols); + + // For 0 <= r < numRows and 0 <= c < numCols, element (r,c) is 1 and all + // others are 0. If either of r or c is invalid, the zero matrix is + // created. This is a convenience for creating the standard Euclidean + // basis matrices; see also MakeUnit(int,int) and Unit(int,int). + GMatrix(int numRows, int numCols, int r, int c); + + // The copy constructor, destructor, and assignment operator are generated + // by the compiler. + + // Member access for which the storage representation is transparent. The + // matrix entry in row r and column c is A(r,c). The first operator() + // returns a const reference rather than a Real value. This supports + // writing via standard file operations that require a const pointer to + // data. + void SetSize(int numRows, int numCols); + inline void GetSize(int& numRows, int& numCols) const; + inline int GetNumRows() const; + inline int GetNumCols() const; + inline int GetNumElements() const; + inline Real const& operator()(int r, int c) const; + inline Real& operator()(int r, int c); + + // Member access by rows or by columns. The input vectors must have the + // correct number of elements for the matrix size. + void SetRow(int r, GVector const& vec); + void SetCol(int c, GVector const& vec); + GVector GetRow(int r) const; + GVector GetCol(int c) const; + + // Member access by 1-dimensional index. NOTE: These accessors are + // useful for the manipulation of matrix entries when it does not + // matter whether storage is row-major or column-major. Do not use + // constructs such as M[c+NumCols*r] or M[r+NumRows*c] that expose the + // storage convention. + inline Real const& operator[](int i) const; + inline Real& operator[](int i); + + // Comparisons for sorted containers and geometric ordering. + inline bool operator==(GMatrix const& mat) const; + inline bool operator!=(GMatrix const& mat) const; + inline bool operator< (GMatrix const& mat) const; + inline bool operator<=(GMatrix const& mat) const; + inline bool operator> (GMatrix const& mat) const; + inline bool operator>=(GMatrix const& mat) const; + + // Special matrices. + void MakeZero(); // All components are 0. + void MakeUnit(int r, int c); // Component (r,c) is 1, all others zero. + void MakeIdentity(); // Diagonal entries 1, others 0, even when nonsquare + static GMatrix Zero(int numRows, int numCols); + static GMatrix Unit(int numRows, int numCols, int r, int c); + static GMatrix Identity(int numRows, int numCols); + +protected: + // The matrix is stored as a 1-dimensional array. The convention of + // row-major or column-major is your choice. + int mNumRows, mNumCols; + std::vector mElements; +}; + +// Unary operations. +template +GMatrix operator+(GMatrix const& M); + +template +GMatrix operator-(GMatrix const& M); + +// Linear-algebraic operations. +template +GMatrix operator+(GMatrix const& M0, GMatrix const& M1); + +template +GMatrix operator-(GMatrix const& M0, GMatrix const& M1); + +template +GMatrix operator*(GMatrix const& M, Real scalar); + +template +GMatrix operator*(Real scalar, GMatrix const& M); + +template +GMatrix operator/(GMatrix const& M, Real scalar); + +template +GMatrix& operator+=(GMatrix& M0, GMatrix const& M1); + +template +GMatrix& operator-=(GMatrix& M0, GMatrix const& M1); + +template +GMatrix& operator*=(GMatrix& M, Real scalar); + +template +GMatrix& operator/=(GMatrix& M, Real scalar); + +// Geometric operations. +template +Real L1Norm(GMatrix const& M); + +template +Real L2Norm(GMatrix const& M); + +template +Real LInfinityNorm(GMatrix const& M); + +template +GMatrix Inverse(GMatrix const& M, + bool* reportInvertibility = nullptr); + +template +Real Determinant(GMatrix const& M); + +// M^T +template +GMatrix Transpose(GMatrix const& M); + +// M*V +template +GVector operator*(GMatrix const& M, GVector const& V); + +// V^T*M +template +GVector operator*(GVector const& V, GMatrix const& M); + +// A*B +template +GMatrix operator*(GMatrix const& A, GMatrix const& B); + +template +GMatrix MultiplyAB(GMatrix const& A, GMatrix const& B); + +// A*B^T +template +GMatrix MultiplyABT(GMatrix const& A, GMatrix const& B); + +// A^T*B +template +GMatrix MultiplyATB(GMatrix const& A, GMatrix const& B); + +// A^T*B^T +template +GMatrix MultiplyATBT(GMatrix const& A, GMatrix const& B); + +// M*D, D is square diagonal (stored as vector) +template +GMatrix MultiplyMD(GMatrix const& M, GVector const& D); + +// D*M, D is square diagonal (stored as vector) +template +GMatrix MultiplyDM(GVector const& D, GMatrix const& M); + +// U*V^T, U is N-by-1, V is M-by-1, result is N-by-M. +template +GMatrix OuterProduct(GVector const& U, GVector const& V); + +// Initialization to a diagonal matrix whose diagonal entries are the +// components of D. +template +void MakeDiagonal(GVector const& D, GMatrix& M); + + +template +GMatrix::GMatrix() + : + mNumRows(0), + mNumCols(0) +{ +} + +template +GMatrix::GMatrix(int numRows, int numCols) +{ + SetSize(numRows, numCols); + std::fill(mElements.begin(), mElements.end(), (Real)0); +} + +template +GMatrix::GMatrix(int numRows, int numCols, int r, int c) +{ + SetSize(numRows, numCols); + MakeUnit(r, c); +} + +template +void GMatrix::SetSize(int numRows, int numCols) +{ + if (numRows > 0 && numCols > 0) + { + mNumRows = numRows; + mNumCols = numCols; + mElements.resize(mNumRows * mNumCols); + } + else + { + mNumRows = 0; + mNumCols = 0; + mElements.clear(); + } +} + +template inline +void GMatrix::GetSize(int& numRows, int& numCols) const +{ + numRows = mNumRows; + numCols = mNumCols; +} + +template inline +int GMatrix::GetNumRows() const +{ + return mNumRows; +} + +template inline +int GMatrix::GetNumCols() const +{ + return mNumCols; +} + +template inline +int GMatrix::GetNumElements() const +{ + return static_cast(mElements.size()); +} + +template inline +Real const& GMatrix::operator()(int r, int c) const +{ +#if defined(GTE_ASSERT_ON_GMATRIX_INDEX_OUT_OF_RANGE) + LogAssert(0 <= r && r < GetNumRows() && 0 <= c && c < GetNumCols(), + "Invalid index."); +#endif + +#if defined(GTE_USE_ROW_MAJOR) + return mElements[c + mNumCols*r]; +#else + return mElements[r + mNumRows*c]; +#endif +} + +template inline +Real& GMatrix::operator()(int r, int c) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_INDEX_OUT_OF_RANGE) + LogAssert(0 <= r && r < GetNumRows() && 0 <= c && c < GetNumCols(), + "Invalid index."); +#endif + +#if defined(GTE_USE_ROW_MAJOR) + return mElements[c + mNumCols*r]; +#else + return mElements[r + mNumRows*c]; +#endif +} + +template +void GMatrix::SetRow(int r, GVector const& vec) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(vec.GetSize() == GetNumCols(), "Mismatched size."); +#endif + for (int c = 0; c < mNumCols; ++c) + { + operator()(r, c) = vec[c]; + } +} + +template +void GMatrix::SetCol(int c, GVector const& vec) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(vec.GetSize() == GetNumRows(), "Mismatched size."); +#endif + for (int r = 0; r < mNumRows; ++r) + { + operator()(r, c) = vec[r]; + } +} + +template +GVector GMatrix::GetRow(int r) const +{ + GVector vec(mNumCols); + for (int c = 0; c < mNumCols; ++c) + { + vec[c] = operator()(r, c); + } + return vec; +} + +template +GVector GMatrix::GetCol(int c) const +{ + GVector vec(mNumRows); + for (int r = 0; r < mNumRows; ++r) + { + vec[r] = operator()(r, c); + } + return vec; +} + +template inline +Real const& GMatrix::operator[](int i) const +{ + return mElements[i]; +} + +template inline +Real& GMatrix::operator[](int i) +{ + return mElements[i]; +} + +template inline +bool GMatrix::operator==(GMatrix const& mat) const +{ + return mNumRows == mat.mNumRows && mNumCols == mat.mNumCols + && mElements == mat.mElements; +} + +template inline +bool GMatrix::operator!=(GMatrix const& mat) const +{ + return !operator==(mat); +} + +template inline +bool GMatrix::operator<(GMatrix const& mat) const +{ + return mNumRows == mat.mNumRows && mNumCols == mat.mNumCols + && mElements < mat.mElements; +} + +template inline +bool GMatrix::operator<=(GMatrix const& mat) const +{ + return mNumRows == mat.mNumRows && mNumCols == mat.mNumCols + && mElements <= mat.mElements; +} + +template inline +bool GMatrix::operator>(GMatrix const& mat) const +{ + return mNumRows == mat.mNumRows && mNumCols == mat.mNumCols + && mElements > mat.mElements; +} + +template inline +bool GMatrix::operator>=(GMatrix const& mat) const +{ + return mNumRows == mat.mNumRows && mNumCols == mat.mNumCols + && mElements >= mat.mElements; +} + +template +void GMatrix::MakeZero() +{ + std::fill(mElements.begin(), mElements.end(), (Real)0); +} + +template +void GMatrix::MakeUnit(int r, int c) +{ + MakeZero(); + if (0 <= r && r < mNumRows && 0 <= c && c < mNumCols) + { + operator()(r, c) = (Real)1; + } +} + +template +void GMatrix::MakeIdentity() +{ + MakeZero(); + int const numDiagonal = (mNumRows <= mNumCols ? mNumRows : mNumCols); + for (int i = 0; i < numDiagonal; ++i) + { + operator()(i, i) = (Real)1; + } +} + +template +GMatrix GMatrix::Zero(int numRows, int numCols) +{ + GMatrix M(numRows, numCols); + M.MakeZero(); + return M; +} + +template +GMatrix GMatrix::Unit(int numRows, int numCols, int r, int c) +{ + GMatrix M(numRows, numCols); + M.MakeUnit(r, c); + return M; +} + +template +GMatrix GMatrix::Identity(int numRows, int numCols) +{ + GMatrix M(numRows, numCols); + M.MakeIdentity(); + return M; +} + + + +template +GMatrix operator+(GMatrix const& M) +{ + return M; +} + +template +GMatrix operator-(GMatrix const& M) +{ + GMatrix result(M.GetNumRows(), M.GetNumCols()); + for (int i = 0; i < M.GetNumElements(); ++i) + { + result[i] = -M[i]; + } + return result; +} + +template +GMatrix operator+(GMatrix const& M0, GMatrix const& M1) +{ + GMatrix result = M0; + return result += M1; +} + +template +GMatrix operator-(GMatrix const& M0, GMatrix const& M1) +{ + GMatrix result = M0; + return result -= M1; +} + +template +GMatrix operator*(GMatrix const& M, Real scalar) +{ + GMatrix result = M; + return result *= scalar; +} + +template +GMatrix operator*(Real scalar, GMatrix const& M) +{ + GMatrix result = M; + return result *= scalar; +} + +template +GMatrix operator/(GMatrix const& M, Real scalar) +{ + GMatrix result = M; + return result /= scalar; +} + +template +GMatrix& operator+=(GMatrix& M0, GMatrix const& M1) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(M0.GetNumRows() == M1.GetNumRows() && + M0.GetNumCols() == M1.GetNumCols(), "Mismatched size."); +#endif + + for (int i = 0; i < M0.GetNumElements(); ++i) + { + M0[i] += M1[i]; + } + return M0; +} + +template +GMatrix& operator-=(GMatrix& M0, GMatrix const& M1) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(M0.GetNumRows() == M1.GetNumRows() && + M0.GetNumCols() == M1.GetNumCols(), "Mismatched size."); +#endif + + for (int i = 0; i < M0.GetNumElements(); ++i) + { + M0[i] -= M1[i]; + } + return M0; +} + +template +GMatrix& operator*=(GMatrix& M, Real scalar) +{ + for (int i = 0; i < M.GetNumElements(); ++i) + { + M[i] *= scalar; + } + return M; +} + +template +GMatrix& operator/=(GMatrix& M, Real scalar) +{ + if (scalar != (Real)0) + { + Real invScalar = ((Real)1) / scalar; + for (int i = 0; i < M.GetNumElements(); ++i) + { + M[i] *= invScalar; + } + } + else + { + for (int i = 0; i < M.GetNumElements(); ++i) + { + M[i] = (Real)0; + } + } + return M; +} + +template +Real L1Norm(GMatrix const& M) +{ + Real sum = std::abs(M[0]); + for (int i = 1; i < M.GetNumElements(); ++i) + { + sum += std::abs(M[i]); + } + return sum; +} + +template +Real L2Norm(GMatrix const& M) +{ + Real sum = M[0] * M[0]; + for (int i = 1; i < M.GetNumElements(); ++i) + { + sum += M[i] * M[i]; + } + return std::sqrt(sum); +} + +template +Real LInfinityNorm(GMatrix const& M) +{ + Real maxAbsElement = M[0]; + for (int i = 1; i < M.GetNumElements(); ++i) + { + Real absElement = std::abs(M[i]); + if (absElement > maxAbsElement) + { + maxAbsElement = absElement; + } + } + return maxAbsElement; +} + +template +GMatrix Inverse(GMatrix const& M, bool* reportInvertibility) +{ + GMatrix invM(M.GetNumRows(), M.GetNumCols()); + if (M.GetNumRows() == M.GetNumCols()) + { + Real determinant; + bool invertible = GaussianElimination()(M.GetNumRows(), &M[0], + &invM[0], determinant, nullptr, nullptr, nullptr, 0, nullptr); + if (reportInvertibility) + { + *reportInvertibility = invertible; + } + } + else + { +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogError("Matrix must be square."); +#endif + invM.MakeZero(); + if (reportInvertibility) + { + *reportInvertibility = false; + } + } + return invM; +} + +template +Real Determinant(GMatrix const& M) +{ + Real determinant; + if (M.GetNumRows() == M.GetNumCols()) + { + GaussianElimination()(M.GetNumRows(), &M[0], nullptr, + determinant, nullptr, nullptr, nullptr, 0, nullptr); + } + else + { +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogError("Matrix must be square."); +#endif + determinant = (Real)0; + } + return determinant; +} + +template +GMatrix Transpose(GMatrix const& M) +{ + GMatrix result(M.GetNumCols(), M.GetNumRows()); + for (int r = 0; r < M.GetNumRows(); ++r) + { + for (int c = 0; c < M.GetNumCols(); ++c) + { + result(c, r) = M(r, c); + } + } + return result; +} + +template +GVector operator*(GMatrix const& M, GVector const& V) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(V.GetSize() == M.GetNumRows(), "Mismatched size."); +#endif + GVector result(M.GetNumRows()); + for (int r = 0; r < M.GetNumRows(); ++r) + { + result[r] = (Real)0; + for (int c = 0; c < M.GetNumCols(); ++c) + { + result[r] += M(r, c) * V[c]; + } + } + return result; +} + +template +GVector operator*(GVector const& V, GMatrix const& M) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(V.GetSize() == M.GetNumCols(), "Mismatched size."); +#endif + GVector result(M.GetNumCols()); + for (int c = 0; c < M.GetNumCols(); ++c) + { + result[c] = (Real)0; + for (int r = 0; r < M.GetNumRows(); ++r) + { + result[c] += V[r] * M(r, c); + } + } + return result; +} + +template +GMatrix operator*(GMatrix const& A, GMatrix const& B) +{ + return MultiplyAB(A, B); +} + +template +GMatrix MultiplyAB(GMatrix const& A, GMatrix const& B) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(A.GetNumCols() == B.GetNumRows(), "Mismatched size."); +#endif + int const numCommon = A.GetNumCols(); + GMatrix result(A.GetNumRows(), B.GetNumCols()); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < numCommon; ++i) + { + result(r, c) += A(r, i) * B(i, c); + } + } + } + return result; +} + +template +GMatrix MultiplyABT(GMatrix const& A, GMatrix const& B) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(A.GetNumCols() == B.GetNumCols(), "Mismatched size."); +#endif + int const numCommon = A.GetNumCols(); + GMatrix result(A.GetNumRows(), B.GetNumRows()); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < numCommon; ++i) + { + result(r, c) += A(r, i) * B(c, i); + } + } + } + return result; +} + +template +GMatrix MultiplyATB(GMatrix const& A, GMatrix const& B) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(A.GetNumRows() == B.GetNumRows(), "Mismatched size."); +#endif + int const numCommon = A.GetNumRows(); + GMatrix result(A.GetNumCols(), B.GetNumCols()); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < numCommon; ++i) + { + result(r, c) += A(i, r) * B(i, c); + } + } + } + return result; +} + +template +GMatrix MultiplyATBT(GMatrix const& A, GMatrix const& B) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(A.GetNumRows() == B.GetNumCols(), "Mismatched size."); +#endif + int const numCommon = A.GetNumRows(); + GMatrix result(A.GetNumCols(), B.GetNumRows()); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < numCommon; ++i) + { + result(r, c) += A(i, r) * B(c, i); + } + } + } + return result; +} + +template +GMatrix MultiplyMD(GMatrix const& M, GVector const& D) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(D.GetSize() == M.GetNumCols(), "Mismatched size."); +#endif + GMatrix result(M.GetNumRows(), M.GetNumCols()); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = M(r, c) * D[c]; + } + } + return result; +} + +template +GMatrix MultiplyDM(GVector const& D, GMatrix const& M) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(D.GetSize() == M.GetNumRows(), "Mismatched size."); +#endif + GMatrix result(M.GetNumRows(), M.GetNumCols()); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = D[r] * M(r, c); + } + } + return result; +} + +template +GMatrix OuterProduct(GVector const& U, GVector const& V) +{ + GMatrix result(U.GetSize(), V.GetSize()); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = U[r] * V[c]; + } + } + return result; +} + +template +void MakeDiagonal(GVector const& D, GMatrix& M) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(M.GetNumRows() == M.GetNumCols(), "Mismatched size."); +#endif + int const N = M.GetNumRows(); + for (int i = 0; i < N*N; ++i) + { + M[i] = (Real)0; + } + + for (int i = 0; i < N; ++i) + { + M(i, i) = D[i]; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGVector.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGVector.h new file mode 100644 index 000000000000..01f8306aa04d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGVector.h @@ -0,0 +1,646 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/10/05) + +#pragma once + +#include +#include +#include + +// Uncomment this to test for size mismatches that are wrapped by +// GVector::ValidateSize. If the test is not enabled, the compiler should +// not generate any code for ValidateSize in a release build. +//#define GTE_ASSERT_ON_GVECTOR_SIZE_MISMATCH + +namespace gte +{ + +template +class GVector +{ +public: + // The tuple is length zero (uninitialized). + GVector(); + + // The tuple is length 'size' and the elements are uninitialized. + GVector(int size); + + // For 0 <= d <= size, element d is 1 and all others are zero. If d is + // invalid, the zero vector is created. This is a convenience for + // creating the standard Euclidean basis vectors; see also + // MakeUnit(int,int) and Unit(int,int). + GVector(int size, int d); + + // The copy constructor, destructor, and assignment operator are generated + // by the compiler. + + // Member access. SetSize(int) does not initialize the tuple. The first + // operator[] returns a const reference rather than a Real value. This + // supports writing via standard file operations that require a const + // pointer to data. + void SetSize(int size); + inline int GetSize() const; + inline Real const& operator[](int i) const; + inline Real& operator[](int i); + + // Comparison (for use by STL containers). + inline bool operator==(GVector const& vec) const; + inline bool operator!=(GVector const& vec) const; + inline bool operator< (GVector const& vec) const; + inline bool operator<=(GVector const& vec) const; + inline bool operator> (GVector const& vec) const; + inline bool operator>=(GVector const& vec) const; + + // Special vectors. + void MakeZero(); // All components are 0. + void MakeUnit(int d); // Component d is 1, all others are zero. + static GVector Zero(int size); + static GVector Unit(int size, int d); + +protected: + // This data structure takes advantage of the built-in operator[], + // range checking, and visualizers in MSVS. + std::vector mTuple; +}; + +// Unary operations. +template +GVector operator+(GVector const& v); + +template +GVector operator-(GVector const& v); + +// Linear-algebraic operations. +template +GVector operator+(GVector const& v0, GVector const& v1); + +template +GVector operator-(GVector const& v0, GVector const& v1); + +template +GVector operator*(GVector const& v, Real scalar); + +template +GVector operator*(Real scalar, GVector const& v); + +template +GVector operator/(GVector const& v, Real scalar); + +template +GVector& operator+=(GVector& v0, GVector const& v1); + +template +GVector& operator-=(GVector& v0, GVector const& v1); + +template +GVector& operator*=(GVector& v, Real scalar); + +template +GVector& operator/=(GVector& v, Real scalar); + +// Geometric operations. The functions with 'robust' set to 'false' use the +// standard algorithm for normalizing a vector by computing the length as a +// square root of the squared length and dividing by it. The results can be +// infinite (or NaN) if the length is zero. When 'robust' is set to 'true', +// the algorithm is designed to avoid floating-point overflow and sets the +// normalized vector to zero when the length is zero. +template +Real Dot(GVector const& v0, GVector const& v1); + +template +Real Length(GVector const& v, bool robust = false); + +template +Real Normalize(GVector& v, bool robust = false); + +// Gram-Schmidt orthonormalization to generate orthonormal vectors from the +// linearly independent inputs. The function returns the smallest length of +// the unnormalized vectors computed during the process. If this value is +// nearly zero, it is possible that the inputs are linearly dependent (within +// numerical round-off errors). On input, 1 <= numElements <= N and v[0] +// through v[numElements-1] must be initialized. On output, the vectors +// v[0] through v[numElements-1] form an orthonormal set. +template +Real Orthonormalize(int numElements, GVector* v, bool robust = false); + +// Compute the axis-aligned bounding box of the vectors. The return value is +// 'true' iff the inputs are valid, in which case vmin and vmax have valid +// values. +template +bool ComputeExtremes(int numVectors, GVector const* v, + GVector& vmin, GVector& vmax); + +// Lift n-tuple v to homogeneous (n+1)-tuple (v,last). +template +GVector HLift(GVector const& v, Real last); + +// Project homogeneous n-tuple v = (u,v[n-1]) to (n-1)-tuple u. +template +GVector HProject(GVector const& v); + +// Lift n-tuple v = (w0,w1) to (n+1)-tuple u = (w0,u[inject],w1). By +// inference, w0 is a (inject)-tuple [nonexistent when inject=0] and w1 is a +// (n-inject)-tuple [nonexistent when inject=n]. +template +GVector Lift(GVector const& v, int inject, Real value); + +// Project n-tuple v = (w0,v[reject],w1) to (n-1)-tuple u = (w0,w1). By +// inference, w0 is a (reject)-tuple [nonexistent when reject=0] and w1 is a +// (n-1-reject)-tuple [nonexistent when reject=n-1]. +template +GVector Project(GVector const& v, int reject); + + +template +GVector::GVector() +{ + // Uninitialized. +} + +template +GVector::GVector(int size) +{ + SetSize(size); +} + +template +GVector::GVector(int size, int d) +{ + SetSize(size); + MakeUnit(d); +} + +template +void GVector::SetSize(int size) +{ +#if defined(GTE_ASSERT_ON_GVECTOR_SIZE_MISMATCH) + LogAssert(size >= 0, "Mismatched size."); +#endif + if (size > 0) + { + mTuple.resize(size); + } +} + +template inline +int GVector::GetSize() const +{ + return static_cast(mTuple.size()); +} + +template inline +Real const& GVector::operator[](int i) const +{ + return mTuple[i]; +} + +template inline +Real& GVector::operator[](int i) +{ + return mTuple[i]; +} + +template inline +bool GVector::operator==(GVector const& vec) const +{ + return mTuple == vec.mTuple; +} + +template inline +bool GVector::operator!=(GVector const& vec) const +{ + return mTuple != vec.mTuple; +} + +template inline +bool GVector::operator<(const GVector& vec) const +{ + return mTuple < vec.mTuple; +} + +template inline +bool GVector::operator<=(const GVector& vec) const +{ + return mTuple <= vec.mTuple; +} + +template inline +bool GVector::operator>(const GVector& vec) const +{ + return mTuple > vec.mTuple; +} + +template inline +bool GVector::operator>=(const GVector& vec) const +{ + return mTuple >= vec.mTuple; +} + +template +void GVector::MakeZero() +{ + std::fill(mTuple.begin(), mTuple.end(), (Real)0); +} + +template +void GVector::MakeUnit(int d) +{ + std::fill(mTuple.begin(), mTuple.end(), (Real)0); + if (0 <= d && d < (int)mTuple.size()) + { + mTuple[d] = (Real)1; + } +} + +template +GVector GVector::Zero(int size) +{ + GVector v(size); + v.MakeZero(); + return v; +} + +template +GVector GVector::Unit(int size, int d) +{ + GVector v(size); + v.MakeUnit(d); + return v; +} + + + +template +GVector operator+(GVector const& v) +{ + return v; +} + +template +GVector operator-(GVector const& v) +{ + GVector result(v.GetSize()); + for (int i = 0; i < v.GetSize(); ++i) + { + result[i] = -v[i]; + } + return result; +} + +template +GVector operator+(GVector const& v0, GVector const& v1) +{ + GVector result = v0; + return result += v1; +} + +template +GVector operator-(GVector const& v0, GVector const& v1) +{ + GVector result = v0; + return result -= v1; +} + +template +GVector operator*(GVector const& v, Real scalar) +{ + GVector result = v; + return result *= scalar; +} + +template +GVector operator*(Real scalar, GVector const& v) +{ + GVector result = v; + return result *= scalar; +} + +template +GVector operator/(GVector const& v, Real scalar) +{ + GVector result = v; + return result /= scalar; +} + +template +GVector& operator+=(GVector& v0, GVector const& v1) +{ + if (v0.GetSize() == v1.GetSize()) + { + for (int i = 0; i < v0.GetSize(); ++i) + { + v0[i] += v1[i]; + } + } + else + { +#if defined(GTE_ASSERT_ON_GVECTOR_SIZE_MISMATCH) + LogError("Mismatched size."); +#endif + } + return v0; +} + +template +GVector& operator-=(GVector& v0, GVector const& v1) +{ + if (v0.GetSize() == v1.GetSize()) + { + for (int i = 0; i < v0.GetSize(); ++i) + { + v0[i] -= v1[i]; + } + } + else + { +#if defined(GTE_ASSERT_ON_GVECTOR_SIZE_MISMATCH) + LogError("Mismatched size."); +#endif + } + return v0; +} + +template +GVector& operator*=(GVector& v, Real scalar) +{ + for (int i = 0; i < v.GetSize(); ++i) + { + v[i] *= scalar; + } + return v; +} + +template +GVector& operator/=(GVector& v, Real scalar) +{ + if (scalar != (Real)0) + { + Real invScalar = ((Real)1) / scalar; + for (int i = 0; i < v.GetSize(); ++i) + { + v[i] *= invScalar; + } + } + else + { + for (int i = 0; i < v.GetSize(); ++i) + { + v[i] = (Real)0; + } + } + return v; +} + +template +Real Dot(GVector const& v0, GVector const& v1) +{ + if (v0.GetSize() == v1.GetSize()) + { + Real dot = v0[0] * v1[0]; + for (int i = 1; i < v0.GetSize(); ++i) + { + dot += v0[i] * v1[i]; + } + return dot; + } + else + { +#if defined(GTE_ASSERT_ON_GVECTOR_SIZE_MISMATCH) + LogError("Mismatched size."); +#endif + return (Real)0; + } +} + +template +Real Length(GVector const& v, bool robust) +{ + if (robust) + { + Real maxAbsComp = std::abs(v[0]); + for (int i = 1; i < v.GetSize(); ++i) + { + Real absComp = std::abs(v[i]); + if (absComp > maxAbsComp) + { + maxAbsComp = absComp; + } + } + + Real length; + if (maxAbsComp > (Real)0) + { + GVector scaled = v / maxAbsComp; + length = maxAbsComp * std::sqrt(Dot(scaled, scaled)); + } + else + { + length = (Real)0; + } + return length; + } + else + { + return std::sqrt(Dot(v, v)); + } +} + +template +Real Normalize(GVector& v, bool robust) +{ + if (robust) + { + Real maxAbsComp = std::abs(v[0]); + for (int i = 1; i < v.GetSize(); ++i) + { + Real absComp = std::abs(v[i]); + if (absComp > maxAbsComp) + { + maxAbsComp = absComp; + } + } + + Real length; + if (maxAbsComp > (Real)0) + { + v /= maxAbsComp; + length = std::sqrt(Dot(v, v)); + v /= length; + length *= maxAbsComp; + } + else + { + length = (Real)0; + for (int i = 0; i < v.GetSize(); ++i) + { + v[i] = (Real)0; + } + } + return length; + } + else + { + Real length = std::sqrt(Dot(v, v)); + if (length > (Real)0) + { + v /= length; + } + else + { + for (int i = 0; i < v.GetSize(); ++i) + { + v[i] = (Real)0; + } + } + return length; + } +} + +template +Real Orthonormalize(int numInputs, GVector* v, bool robust) +{ + if (v && 1 <= numInputs && numInputs <= v[0].GetSize()) + { +#if defined(GTE_ASSERT_ON_GVECTOR_SIZE_MISMATCH) + for (int i = 1; i < numInputs; ++i) + { + LogAssert(v[0].GetSize() == v[i].GetSize(), "Mismatched size."); + } +#endif + Real minLength = Normalize(v[0], robust); + for (int i = 1; i < numInputs; ++i) + { + for (int j = 0; j < i; ++j) + { + Real dot = Dot(v[i], v[j]); + v[i] -= v[j] * dot; + } + Real length = Normalize(v[i], robust); + if (length < minLength) + { + minLength = length; + } + } + return minLength; + } + + LogError("Invalid input."); + return (Real)0; +} + +template +bool ComputeExtremes(int numVectors, GVector const* v, + GVector& vmin, GVector& vmax) +{ + if (v && numVectors > 0) + { +#if defined(GTE_ASSERT_ON_GVECTOR_SIZE_MISMATCH) + for (int i = 1; i < numVectors; ++i) + { + LogAssert(v[0].GetSize() == v[i].GetSize(), "Mismatched size."); + } +#endif + int const size = v[0].GetSize(); + vmin = v[0]; + vmax = vmin; + for (int j = 1; j < numVectors; ++j) + { + GVector const& vec = v[j]; + for (int i = 0; i < size; ++i) + { + if (vec[i] < vmin[i]) + { + vmin[i] = vec[i]; + } + else if (vec[i] > vmax[i]) + { + vmax[i] = vec[i]; + } + } + } + return true; + } + + LogError("Invalid input."); + return false; +} + +template +GVector HLift(GVector const& v, Real last) +{ + int const size = v.GetSize(); + GVector result(size + 1); + for (int i = 0; i < size; ++i) + { + result[i] = v[i]; + } + result[size] = last; + return result; +} + +template +GVector HProject(GVector const& v) +{ + int const size = v.GetSize(); + if (size > 1) + { + GVector result(size - 1); + for (int i = 0; i < size - 1; ++i) + { + result[i] = v[i]; + } + return result; + } + else + { + return GVector(); + } +} + +template +GVector Lift(GVector const& v, int inject, Real value) +{ + int const size = v.GetSize(); + GVector result(size + 1); + int i; + for (i = 0; i < inject; ++i) + { + result[i] = v[i]; + } + result[i] = value; + int j = i; + for (++j; i < size; ++i, ++j) + { + result[j] = v[i]; + } + return result; +} + +template +GVector Project(GVector const& v, int reject) +{ + int const size = v.GetSize(); + if (size > 1) + { + GVector result(size - 1); + for (int i = 0, j = 0; i < size - 1; ++i, ++j) + { + if (j == reject) + { + ++j; + } + result[i] = v[j]; + } + return result; + } + else + { + return GVector(); + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGaussNewtonMinimizer.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGaussNewtonMinimizer.h new file mode 100644 index 000000000000..38b1f922ddcd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGaussNewtonMinimizer.h @@ -0,0 +1,228 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.14.0 (2018/07/16) + +#pragma once + +#include +#include + +// Let F(p) = (F_{0}(p), F_{1}(p), ..., F_{n-1}(p)) be a vector-valued +// function of the parameters p = (p_{0}, p_{1}, ..., p_{m-1}). The +// nonlinear least-squares problem is to minimize the real-valued error +// function E(p) = |F(p)|^2, which is the squared length of F(p). +// +// Let J = dF/dp = [dF_{r}/dp_{c}] denote the Jacobian matrix, which is the +// matrix of first-order partial derivatives of F. The matrix has n rows and +// m columns, and the indexing (r,c) refers to row r and column c. A +// first-order approximation is F(p + d) = F(p) + J(p)d, where d is an m-by-1 +// vector with small length. Consequently, an approximation to E is E(p + d) +// = |F(p + d)|^2 = |F(p) + J(p)d|^2. The goal is to choose d to minimize +// |F(p) + J(p)d|^2 and, hopefully, with E(p + d) < E(p). Choosing an initial +// p_{0}, the hope is that the algorithm generates a sequence p_{i} for which +// E(p_{i+1}) < E(p_{i}) and, in the limit, E(p_{j}) approaches the global +// minimum of E. The algorithm is referred to as Gauss-Newton iteration. If +// E does not decrease for a step of the algorithm, one can modify the +// algorithm to the Levenberg-Marquardt iteration. See +// GteLevenbergMarquardtMinimizer.h for a description and an implementation. +// +// For a single Gauss-Newton iteration, we need to choose d to minimize +// |F(p) + J(p)d|^2 where p is fixed. This is a linear least squares problem +// which can be formulated using the normal equations +// (J^T(p)*J(p))*d = -J^T(p)*F(p). The matrix J^T*J is positive semidefinite. +// If it is invertible, then d = -(J^T(p)*J(p))^{-1}*F(p). If it is not +// invertible, some other algorithm must be used to choose d; one option is +// to use gradient descent for the step. A Cholesky decomposition can be +// used to solve the linear system. +// +// Although an implementation can allow the caller to pass an array of +// functions F_{i}(p) and an array of derivatives dF_{r}/dp_{c}, some +// applications might involve a very large n that precludes storing all +// the computed Jacobian matrix entries because of excessive memory +// requirements. In such an application, it is better to compute instead +// the entries of the m-by-m matrix J^T*J and the m-by-1 vector J^T*F. +// Typically, m is small, so the memory requirements are not excessive. Also, +// there might be additional structure to F for which the caller can take +// advantage; for example, 3-tuples of components of F(p) might correspond to +// vectors that can be manipulated using an already existing mathematics +// library. The implementation here supports both approaches. + +namespace gte +{ + template + class GaussNewtonMinimizer + { + public: + // Convenient types for the domain vectors, the range vectors, the + // function F and the Jacobian J. + typedef GVector DVector; // NumPDimensions elements + typedef GVector RVector; // NumFDimensions elements + typedef GMatrix JMatrix; // NumFDimensions-by-NumPDimensions elements + typedef GMatrix JTJMatrix; // NumPDimensions-by-NumPDimensions + typedef GVector JTFVector; // NumPDimensions elements + typedef std::function FFunction; + typedef std::function JFunction; + typedef std::function JPlusFunction; + + // NOTE: The C++ compiler for Microsoft Visual Studio 12.0.21005.1 REL + // (MSVS 2013) appears to have a bug regarding passing std::function + // arguments. The unit-test code for the first GaussNewtonMinimizer + // constructor was passed std::function objects of type FFunction and + // JFunction. The compiler claimed that the constructor call is + // ambiguous because the JFunction portion of the first constructor's + // signature matches the JPlusFunction portion of the second + // constructor's signature, which is clearly not correct. When using + // MSVS 2013, you need to explicitly typecast using + // GaussNewtonMinimizer minimizer( + // numPDimensions, numFDimensions, + // (GaussNewtonMinimizer::FFunction const&) myFFunction, + // (GaussNewtonMinimizer::JFunction const&) myJFunction); + // The same typecasting is also required for the second constructor. + + // Create the minimizer that computes F(p) and J(p) directly. + GaussNewtonMinimizer(int numPDimensions, int numFDimensions, + FFunction const& inFFunction, JFunction const& inJFunction) + : + mNumPDimensions(numPDimensions), + mNumFDimensions(numFDimensions), + mFFunction(inFFunction), + mJFunction(inJFunction), + mF(mNumFDimensions), + mJ(mNumFDimensions, mNumPDimensions), + mJTJ(mNumPDimensions, mNumPDimensions), + mNegJTF(mNumPDimensions), + mDecomposer(mNumPDimensions), + mUseJFunction(true) + { + LogAssert(mNumPDimensions > 0 && mNumFDimensions > 0, "Invalid dimensions."); + } + + // Create the minimizer that computes J^T(p)*J(p) and -J(p)*F(p). + GaussNewtonMinimizer(int numPDimensions, int numFDimensions, + FFunction const& inFFunction, JPlusFunction const& inJPlusFunction) + : + mNumPDimensions(numPDimensions), + mNumFDimensions(numFDimensions), + mFFunction(inFFunction), + mJPlusFunction(inJPlusFunction), + mF(mNumFDimensions), + mJ(mNumFDimensions, mNumPDimensions), + mJTJ(mNumPDimensions, mNumPDimensions), + mNegJTF(mNumPDimensions), + mDecomposer(mNumPDimensions), + mUseJFunction(false) + { + LogAssert(mNumPDimensions > 0 && mNumFDimensions > 0, "Invalid dimensions."); + } + + // Disallow copy, assignment and move semantics. + GaussNewtonMinimizer(GaussNewtonMinimizer const&) = delete; + GaussNewtonMinimizer& operator=(GaussNewtonMinimizer const&) = delete; + GaussNewtonMinimizer(GaussNewtonMinimizer&&) = delete; + GaussNewtonMinimizer& operator=(GaussNewtonMinimizer&&) = delete; + + inline int GetNumPDimensions() const { return mNumPDimensions; } + inline int GetNumFDimensions() const { return mNumFDimensions; } + + struct Result + { + DVector minLocation; + Real minError; + Real minErrorDifference; + Real minUpdateLength; + size_t numIterations; + bool converged; + }; + + Result operator()(DVector const& p0, size_t maxIterations, + Real updateLengthTolerance, Real errorDifferenceTolerance) + { + Result result; + result.minLocation = p0; + result.minError = std::numeric_limits::max(); + result.minErrorDifference = std::numeric_limits::max(); + result.minUpdateLength = (Real)0; + result.numIterations = 0; + result.converged = false; + + // As a simple precaution, ensure the tolerances are nonnegative. + updateLengthTolerance = std::max(updateLengthTolerance, (Real)0); + errorDifferenceTolerance = std::max(errorDifferenceTolerance, (Real)0); + + // Compute the initial error. + mFFunction(p0, mF); + result.minError = Dot(mF, mF); + + // Do the Gauss-Newton iterations. + auto pCurrent = p0; + for (result.numIterations = 1; result.numIterations <= maxIterations; ++result.numIterations) + { + ComputeLinearSystemInputs(pCurrent); + if (!mDecomposer.Factor(mJTJ)) + { + // TODO: The matrix mJTJ is positive semi-definite, so the + // failure can occur when mJTJ has a zero eigenvalue in + // which case mJTJ is not invertible. Generate an iterate + // anyway, perhaps using gradient descent? + return result; + } + mDecomposer.SolveLower(mJTJ, mNegJTF); + mDecomposer.SolveUpper(mJTJ, mNegJTF); + + auto pNext = pCurrent + mNegJTF; + mFFunction(pNext, mF); + Real error = Dot(mF, mF); + if (error < result.minError) + { + result.minErrorDifference = result.minError - error; + result.minUpdateLength = Length(mNegJTF); + result.minLocation = pNext; + result.minError = error; + if (result.minErrorDifference <= errorDifferenceTolerance + || result.minUpdateLength <= updateLengthTolerance) + { + result.converged = true; + return result; + } + } + + pCurrent = pNext; + } + + return result; + } + + private: + void ComputeLinearSystemInputs(DVector const& pCurrent) + { + if (mUseJFunction) + { + mJFunction(pCurrent, mJ); + mJTJ = MultiplyATB(mJ, mJ); + mNegJTF = -(mF * mJ); + } + else + { + mJPlusFunction(pCurrent, mJTJ, mNegJTF); + } + } + + int mNumPDimensions, mNumFDimensions; + FFunction mFFunction; + JFunction mJFunction; + JPlusFunction mJPlusFunction; + + // Storage for J^T(p)*J(p) and -J^T(p)*F(p) during the iterations. + RVector mF; + JMatrix mJ; + JTJMatrix mJTJ; + JTFVector mNegJTF; + + CholeskyDecomposition mDecomposer; + + bool mUseJFunction; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGaussianElimination.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGaussianElimination.h new file mode 100644 index 000000000000..2e8306ec466f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGaussianElimination.h @@ -0,0 +1,290 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The input matrix M must be NxN. The storage convention for element lookup +// is determined by GTE_USE_ROW_MAJOR or GTE_USE_COL_MAJOR, whichever is +// active. If you want the inverse of M, pass a nonnull pointer inverseM; +// this matrix must also be NxN and use the same storage convention as M. If +// you do not want the inverse of M, pass a nullptr for inverseM. If you want +// to solve M*X = B for X, where X and B are Nx1, pass nonnull pointers for B +// and X. If you want to solve M*Y = C for Y, where X and C are NxK, pass +// nonnull pointers for C and Y and pass K to numCols. In all cases, pass +// N to numRows. + +namespace gte +{ + +template +class GaussianElimination +{ +public: + bool operator()(int numRows, + Real const* M, Real* inverseM, Real& determinant, + Real const* B, Real* X, + Real const* C, int numCols, Real* Y) const; + +private: + // Support for copying source to target or to set target to zero. If + // source is nullptr, then target is set to zero; otherwise source is + // copied to target. This function hides the type traits used to + // determine whether Real is native floating-point or otherwise (such + // as BSNumber or BSRational). + void Set(int numElements, Real const* source, Real* target) const; +}; + + +template +bool GaussianElimination::operator()(int numRows, Real const* M, + Real* inverseM, Real& determinant, Real const* B, Real* X, Real const* C, + int numCols, Real* Y) const +{ + if (numRows <= 0 || !M + || ((B != nullptr) != (X != nullptr)) + || ((C != nullptr) != (Y != nullptr)) + || (C != nullptr && numCols < 1)) + { + LogError("Invalid input."); + return false; + } + + int numElements = numRows * numRows; + bool wantInverse = (inverseM != nullptr); + std::vector localInverseM; + if (!wantInverse) + { + localInverseM.resize(numElements); + inverseM = localInverseM.data(); + } + Set(numElements, M, inverseM); + + if (B) + { + Set(numRows, B, X); + } + + if (C) + { + Set(numRows * numCols, C, Y); + } + +#if defined(GTE_USE_ROW_MAJOR) + LexicoArray2 matInvM(numRows, numRows, inverseM); + LexicoArray2 matY(numRows, numCols, Y); +#else + LexicoArray2 matInvM(numRows, numRows, inverseM); + LexicoArray2 matY(numRows, numCols, Y); +#endif + + std::vector colIndex(numRows), rowIndex(numRows), pivoted(numRows); + std::fill(pivoted.begin(), pivoted.end(), 0); + + Real const zero = (Real)0; + Real const one = (Real)1; + bool odd = false; + determinant = one; + + // Elimination by full pivoting. + int i1, i2, row = 0, col = 0; + for (int i0 = 0; i0 < numRows; ++i0) + { + // Search matrix (excluding pivoted rows) for maximum absolute entry. + Real maxValue = zero; + for (i1 = 0; i1 < numRows; ++i1) + { + if (!pivoted[i1]) + { + for (i2 = 0; i2 < numRows; ++i2) + { + if (!pivoted[i2]) + { + Real absValue = std::abs(matInvM(i1, i2)); + if (absValue > maxValue) + { + maxValue = absValue; + row = i1; + col = i2; + } + } + } + } + } + + if (maxValue == zero) + { + // The matrix is not invertible. + if (wantInverse) + { + Set(numElements, nullptr, inverseM); + } + determinant = zero; + + if (B) + { + Set(numRows, nullptr, X); + } + + if (C) + { + Set(numRows * numCols, nullptr, Y); + } + return false; + } + + pivoted[col] = true; + + // Swap rows so that the pivot entry is in row 'col'. + if (row != col) + { + odd = !odd; + for (int i = 0; i < numRows; ++i) + { + std::swap(matInvM(row, i), matInvM(col, i)); + } + + if (B) + { + std::swap(X[row], X[col]); + } + + if (C) + { + for (int i = 0; i < numCols; ++i) + { + std::swap(matY(row, i), matY(col, i)); + } + } + } + + // Keep track of the permutations of the rows. + rowIndex[i0] = row; + colIndex[i0] = col; + + // Scale the row so that the pivot entry is 1. + Real diagonal = matInvM(col, col); + determinant *= diagonal; + Real inv = one / diagonal; + matInvM(col, col) = one; + for (i2 = 0; i2 < numRows; ++i2) + { + matInvM(col, i2) *= inv; + } + + if (B) + { + X[col] *= inv; + } + + if (C) + { + for (i2 = 0; i2 < numCols; ++i2) + { + matY(col, i2) *= inv; + } + } + + // Zero out the pivot column locations in the other rows. + for (i1 = 0; i1 < numRows; ++i1) + { + if (i1 != col) + { + Real save = matInvM(i1, col); + matInvM(i1, col) = zero; + for (i2 = 0; i2 < numRows; ++i2) + { + matInvM(i1, i2) -= matInvM(col, i2) * save; + } + + if (B) + { + X[i1] -= X[col] * save; + } + + if (C) + { + for (i2 = 0; i2 < numCols; ++i2) + { + matY(i1, i2) -= matY(col, i2) * save; + } + } + } + } + } + + if (wantInverse) + { + // Reorder rows to undo any permutations in Gaussian elimination. + for (i1 = numRows - 1; i1 >= 0; --i1) + { + if (rowIndex[i1] != colIndex[i1]) + { + for (i2 = 0; i2 < numRows; ++i2) + { + std::swap(matInvM(i2, rowIndex[i1]), + matInvM(i2, colIndex[i1])); + } + } + } + } + + if (odd) + { + determinant = -determinant; + } + + return true; +} + +template +void GaussianElimination::Set(int numElements, Real const* source, + Real* target) const +{ + if (std::is_floating_point() == std::true_type()) + { + // Fast set/copy for native floating-point. + size_t numBytes = numElements * sizeof(Real); + if (source) + { + Memcpy(target, source, numBytes); + } + else + { + memset(target, 0, numBytes); + } + } + else + { + // The inputs are not std containers, so ensure assignment works + // correctly. + if (source) + { + for (int i = 0; i < numElements; ++i) + { + target[i] = source[i]; + } + } + else + { + Real const zero = (Real)0; + for (int i = 0; i < numElements; ++i) + { + target[i] = zero; + } + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGenerateMeshUV.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGenerateMeshUV.cpp new file mode 100644 index 000000000000..2d2553bdb477 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGenerateMeshUV.cpp @@ -0,0 +1,100 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include + +using namespace gte; + +std::string const GenerateMeshUVBase::msGLSLSource = +"uniform Bounds\n" +"{\n" +" ivec2 bound;\n" +" int numBoundaryEdges;\n" +" int numInputs;\n" +"};\n" +"\n" +"struct VertexGraphData\n" +"{\n" +" int adjacent;\n" +" Real weight;\n" +"};\n" +"\n" +"buffer vertexGraph { ivec3 data[]; } vertexGraphSB;\n" +"buffer vertexGraphData { VertexGraphData data[]; } vertexGraphDataSB;\n" +"buffer orderedVertices { int data[]; } orderedVerticesSB;\n" +"buffer inTCoords { Real2 data[]; } inTCoordsSB;\n" +"buffer outTCoords { Real2 data[]; } outTCoordsSB;\n" +"\n" +"layout (local_size_x = NUM_X_THREADS, local_size_y = NUM_Y_THREADS, local_size_z = 1) in;\n" +"void main()\n" +"{\n" +" ivec2 t = ivec2(gl_GlobalInvocationID.xy);\n" +" int index = t.x + bound.x * t.y;\n" +" if (step(index, numInputs-1) == 1)\n" +" {\n" +" int v = orderedVerticesSB.data[numBoundaryEdges + index];\n" +" ivec2 range = vertexGraphSB.data[v].yz;\n" +" Real2 tcoord = Real2(0, 0);\n" +" Real weightSum = 0;\n" +" for (int j = 0; j < range.y; ++j)\n" +" {\n" +" VertexGraphData vgd = vertexGraphDataSB.data[range.x + j];\n" +" weightSum += vgd.weight;\n" +" tcoord += vgd.weight * inTCoordsSB.data[vgd.adjacent];\n" +" }\n" +" tcoord /= weightSum;\n" +" outTCoordsSB.data[v] = tcoord;\n" +" }\n" +"}\n"; + +std::string const GenerateMeshUVBase::msHLSLSource = +"cbuffer Bounds\n" +"{\n" +" int2 bound;\n" +" int numBoundaryEdges;\n" +" int numInputs;\n" +"};\n" +"\n" +"struct VertexGraphData\n" +"{\n" +" int adjacent;\n" +" Real weight;\n" +"};\n" +"\n" +"StructuredBuffer vertexGraph;\n" +"StructuredBuffer vertexGraphData;\n" +"StructuredBuffer orderedVertices;\n" +"StructuredBuffer inTCoords;\n" +"RWStructuredBuffer outTCoords;\n" +"\n" +"[numthreads(NUM_X_THREADS, NUM_Y_THREADS, 1)]\n" +"void CSMain(int2 t : SV_DispatchThreadID)\n" +"{\n" +" int index = t.x + bound.x * t.y;\n" +" if (step(index, numInputs-1))\n" +" {\n" +" int v = orderedVertices[numBoundaryEdges + index];\n" +" int2 range = vertexGraph[v].yz;\n" +" Real2 tcoord = Real2(0, 0);\n" +" Real weightSum = 0;\n" +" for (int j = 0; j < range.y; ++j)\n" +" {\n" +" VertexGraphData data = vertexGraphData[range.x + j];\n" +" weightSum += data.weight;\n" +" tcoord += data.weight * inTCoords[data.adjacent];\n" +" }\n" +" tcoord /= weightSum;\n" +" outTCoords[v] = tcoord;\n" +" }\n" +"}\n"; + +std::string const* GenerateMeshUVBase::msSource[] = +{ + &msGLSLSource, + &msHLSLSource +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGenerateMeshUV.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGenerateMeshUV.h new file mode 100644 index 000000000000..3c9b15024358 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGenerateMeshUV.h @@ -0,0 +1,851 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +// This class is an implementation of the barycentric mapping algorithm +// described in Section 5.3 of the book +// Polygon Mesh Processing +// Mario Botsch, Leif Kobbelt, Mark Pauly, Pierre Alliez, Bruno Levy +// AK Peters, Ltd., Natick MA, 2010 +// It uses the mean value weights described in Section 5.3.1 to allow the mesh +// geometry to influence the texture coordinate generation, and it uses +// Gauss-Seidel iteration to solve the sparse linear system. The authors' +// advice is that the Gauss-Seidel approach works well for at most about 5000 +// vertices, presumably the convergence rate degrading as the number of +// vertices increases. +// +// The algorithm implemented here has an additional preprocessing step that +// computes a topological distance transform of the vertices. The boundary +// texture coordinates are propagated inward by updating the vertices in +// topological distance order, leading to fast convergence for large numbers +// of vertices. + +namespace gte +{ + +class GenerateMeshUVBase +{ +protected: + static std::string const msGLSLSource; + static std::string const msHLSLSource; + static std::string const* msSource[]; +}; + +template +class GenerateMeshUV : public GenerateMeshUVBase +{ +public: + class UVComputeModel : public ComputeModel + { + public: + virtual ~UVComputeModel(); + + UVComputeModel(); + + UVComputeModel(unsigned int inNumThreads, + std::function const* inProgress); + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) + UVComputeModel(unsigned int inNumThreads, + std::shared_ptr const& inEngine, + std::shared_ptr const& inFactory, + std::function const* inProgress); +#endif + + std::function const* progress; + }; + + // Construction. + GenerateMeshUV(std::shared_ptr const& cmodel); + + // The incoming mesh must be edge-triangle manifold and have rectangle + // topology (simply connected, closed polyline boundary). The arrays + // 'vertices' and 'tcoords' must both have 'numVertices' elements. Set + // 'useSquareTopology' to true for the generated coordinates to live + // in the uv-square [0,1]^2. Set it to false for the generated + // coordinates to live in a convex polygon that inscribes the uv-disk + // of center (1/2,1/2) and radius 1/2. + void operator()(unsigned int numIterations, bool useSquareTopology, + int numVertices, Vector3 const* vertices, int numIndices, + int const* indices, Vector2* tcoords); + +private: + void TopologicalVertexDistanceTransform(); + void AssignBoundaryTextureCoordinatesSquare(); + void AssignBoundaryTextureCoordinatesDisk(); + void ComputeMeanValueWeights(); + void SolveSystem(unsigned int numIterations); + void SolveSystemCPUSingle(unsigned int numIterations); + void SolveSystemCPUMultiple(unsigned int numIterations); + + // Convenience members that store the input parameters to operator(). + int mNumVertices; + Vector3 const* mVertices; + Vector2* mTCoords; + + // The edge-triangle manifold graph, where each edge is shared by at most + // two triangles. + ETManifoldMesh mGraph; + + // The mVertexInfo array stores -1 for the interior vertices. For a + // boundary edge that is counterclockwise, mVertexInfo[v0] = v1, + // which gives us an orded boundary polyline. + enum { INTERIOR_VERTEX = -1 }; + std::vector mVertexInfo; + int mNumBoundaryEdges, mBoundaryStart; + typedef ETManifoldMesh::Edge Edge; + std::set> mInteriorEdges; + + // The vertex graph required to set up a sparse linear system of equations + // to determine the texture coordinates. + struct Vertex + { + // The topological distance from the boundary of the mesh. + int distance; + + // The value range[0] is the index into mVertexGraphData for the first + // adjacent vertex. The value range[1] is the number of adjacent + // vertices. + std::array range; + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) && defined(GTE_DEV_OPENGL) + int _padding; // GLSL will map the ivec3 Vertex to ivec4 in an array +#endif + }; + std::vector mVertexGraph; + std::vector> mVertexGraphData; + + // The vertices are listed in the order determined by a topological distance + // transform. Boundary vertices have 'distance' 0. Any vertices that are + // not boundary vertices but are edge-adjacent to boundary vertices have + // 'distance' 1. Neighbors of those have distance '2', and so on. The + // mOrderedVertices array stores distance-0 vertices first, distance-1 + // vertices second, and so on. + std::vector mOrderedVertices; + + std::shared_ptr mCModel; + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) + // Support for solving the sparse linear system on the GPU. + void SolveSystemGPU(unsigned int numIterations); + std::shared_ptr mSolveSystem; + std::shared_ptr mBoundBuffer; + std::shared_ptr mVGBuffer; + std::shared_ptr mVGDBuffer; + std::shared_ptr mOVBuffer; + std::shared_ptr mTCoordsBuffer[2]; +#endif +}; + + +template +GenerateMeshUV::UVComputeModel::~UVComputeModel() +{ +} + +template +GenerateMeshUV::UVComputeModel::UVComputeModel() + : + progress(nullptr) +{ +} + +template +GenerateMeshUV::UVComputeModel::UVComputeModel(unsigned int inNumThreads, + std::function const* inProgress) + : + ComputeModel(inNumThreads), + progress(inProgress) +{ +} + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) + +template +GenerateMeshUV::UVComputeModel::UVComputeModel(unsigned int inNumThreads, + std::shared_ptr const& inEngine, + std::shared_ptr const& inFactory, + std::function const* inProgress) + : + ComputeModel(inNumThreads, inEngine, inFactory), + progress(inProgress) +{ +} + +#endif + +template +GenerateMeshUV::GenerateMeshUV(std::shared_ptr const& cmodel) + : + mNumVertices(0), + mVertices(nullptr), + mTCoords(nullptr), + mNumBoundaryEdges(0), + mBoundaryStart(0), + mCModel(cmodel) +{ +} + +template +void GenerateMeshUV::operator()(unsigned int numIterations, + bool useSquareTopology, int numVertices, Vector3 const* vertices, + int numIndices, int const* indices, Vector2* tcoords) +{ + // Ensure that numIterations is even, which avoids having a memory + // copy from the temporary ping-pong buffer to 'tcoords'. + if (numIterations & 1) + { + ++numIterations; + } + + mNumVertices = numVertices; + mVertices = vertices; + mTCoords = tcoords; + + // The linear system solver has a first pass to initialize the texture + // coordinates to ensure the Gauss-Seidel iteration converges rapidly. + // This requires the texture coordinates all start as (-1,-1). + for (int i = 0; i < numVertices; ++i) + { + mTCoords[i][0] = (Real)-1; + mTCoords[i][1] = (Real)-1; + } + + // Create the manifold mesh data structure. + mGraph.Clear(); + int const numTriangles = numIndices / 3; + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + mGraph.Insert(v0, v1, v2); + } + + TopologicalVertexDistanceTransform(); + + if (useSquareTopology) + { + AssignBoundaryTextureCoordinatesSquare(); + } + else + { + AssignBoundaryTextureCoordinatesDisk(); + } + + ComputeMeanValueWeights(); + SolveSystem(numIterations); +} + +template +void GenerateMeshUV::TopologicalVertexDistanceTransform() +{ + // Initialize the graph information. + mVertexInfo.resize(mNumVertices); + std::fill(mVertexInfo.begin(), mVertexInfo.end(), INTERIOR_VERTEX); + mVertexGraph.resize(mNumVertices); + mVertexGraphData.resize(2 * mGraph.GetEdges().size()); + std::pair initialData = std::make_pair(-1, (Real)-1); + std::fill(mVertexGraphData.begin(), mVertexGraphData.end(), initialData); + mOrderedVertices.resize(mNumVertices); + mInteriorEdges.clear(); + mNumBoundaryEdges = 0; + mBoundaryStart = std::numeric_limits::max(); + + // Count the number of adjacent vertices for each vertex. For data sets + // with a large number of vertices, this is a preprocessing step to avoid + // a dynamic data structure that has a large number of std:map objects + // that take a very long time to destroy when a debugger is attached to + // the executable. Instead, we allocate a single array that stores all + // the adjacency information. It is also necessary to bundle the data + // this way for a GPU version of the algorithm. + std::vector numAdjacencies(mNumVertices); + std::fill(numAdjacencies.begin(), numAdjacencies.end(), 0); + + for (auto const& element : mGraph.GetEdges()) + { + ++numAdjacencies[element.first.V[0]]; + ++numAdjacencies[element.first.V[1]]; + + if (element.second->T[1].lock()) + { + // This is an interior edge. + mInteriorEdges.insert(element.second); + } + else + { + // This is a boundary edge. Determine the ordering of the + // vertex indices to make the edge counterclockwise. + ++mNumBoundaryEdges; + int v0 = element.second->V[0], v1 = element.second->V[1]; + auto tri = element.second->T[0].lock(); + int i; + for (i = 0; i < 3; ++i) + { + int v2 = tri->V[i]; + if (v2 != v0 && v2 != v1) + { + // The vertex is opposite the boundary edge. + v0 = tri->V[(i + 1) % 3]; + v1 = tri->V[(i + 2) % 3]; + mVertexInfo[v0] = v1; + mBoundaryStart = std::min(mBoundaryStart, v0); + break; + } + } + } + } + + // Set the range data for each vertex. + for (int vIndex = 0, aIndex = 0; vIndex < mNumVertices; ++vIndex) + { + int numAdjacent = numAdjacencies[vIndex]; + mVertexGraph[vIndex].range = { { aIndex, numAdjacent } }; + aIndex += numAdjacent; + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) && defined(GTE_DEV_OPENGL) + // Initialize the padding, even though it is unused. + mVertexGraph[vIndex]._padding = 0; +#endif + } + + // Compute a topological distance transform of the vertices. + std::set currFront; + for (auto const& element : mGraph.GetEdges()) + { + int v0 = element.second->V[0], v1 = element.second->V[1]; + for (int i = 0; i < 2; ++i) + { + if (mVertexInfo[v0] == INTERIOR_VERTEX) + { + mVertexGraph[v0].distance = -1; + } + else + { + mVertexGraph[v0].distance = 0; + currFront.insert(v0); + } + + // Insert v1 into the first available slot of the adjacency array. + std::array range = mVertexGraph[v0].range; + for (int j = 0; j < range[1]; ++j) + { + std::pair& data = mVertexGraphData[range[0] + j]; + if (data.second == (Real)-1) + { + data.first = v1; + data.second = (Real)0; + break; + } + } + + std::swap(v0, v1); + } + } + + // Use a breadth-first search to propagate the distance information. + int nextDistance = 1; + size_t numFrontVertices = currFront.size(); + std::copy(currFront.begin(), currFront.end(), mOrderedVertices.begin()); + while (currFront.size() > 0) + { + std::set nextFront; + for (auto v : currFront) + { + std::array range = mVertexGraph[v].range; + auto* current = &mVertexGraphData[range[0]]; + for (int j = 0; j < range[1]; ++j, ++current) + { + int a = current->first; + if (mVertexGraph[a].distance == -1) + { + mVertexGraph[a].distance = nextDistance; + nextFront.insert(a); + } + } + } + std::copy(nextFront.begin(), nextFront.end(), mOrderedVertices.begin() + numFrontVertices); + numFrontVertices += nextFront.size(); + currFront = std::move(nextFront); + ++nextDistance; + } +} + +template +void GenerateMeshUV::AssignBoundaryTextureCoordinatesSquare() +{ + // Map the boundary of the mesh to the unit square [0,1]^2. The selection + // of square vertices is such that the relative distances between boundary + // vertices and the relative distances between polygon vertices is + // preserved, except that the four corners of the square are required to + // have boundary points mapped to them. The first boundary point has an + // implied distance of zero. The value distance[i] is the length of the + // boundary polyline from vertex 0 to vertex i+1. + std::vector distance(mNumBoundaryEdges); + Real total = (Real)0; + int v0 = mBoundaryStart, v1, i; + for (i = 0; i < mNumBoundaryEdges; ++i) + { + v1 = mVertexInfo[v0]; + total += Length(mVertices[v1] - mVertices[v0]); + distance[i] = total; + v0 = v1; + } + + Real invTotal = ((Real)1) / total; + for (auto& d : distance) + { + d *= invTotal; + } + + auto begin = distance.begin(), end = distance.end(); + int endYMin = (int)(std::lower_bound(begin, end, (Real)0.25) - begin); + int endXMax = (int)(std::lower_bound(begin, end, (Real)0.50) - begin); + int endYMax = (int)(std::lower_bound(begin, end, (Real)0.75) - begin); + int endXMin = (int)distance.size() - 1; + + // The first polygon vertex is (0,0). The remaining vertices are chosen + // counterclockwise around the square. + v0 = mBoundaryStart; + mTCoords[v0][0] = (Real)0; + mTCoords[v0][1] = (Real)0; + for (i = 0; i < endYMin; ++i) + { + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = distance[i] * (Real)4; + mTCoords[v1][1] = (Real)0; + v0 = v1; + } + + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)1; + mTCoords[v1][1] = (Real)0; + v0 = v1; + for (++i; i < endXMax; ++i) + { + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)1; + mTCoords[v1][1] = distance[i] * (Real)4 - (Real)1; + v0 = v1; + } + + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)1; + mTCoords[v1][1] = (Real)1; + v0 = v1; + for (++i; i < endYMax; ++i) + { + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)3 - distance[i] * (Real)4; + mTCoords[v1][1] = (Real)1; + v0 = v1; + } + + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)0; + mTCoords[v1][1] = (Real)1; + v0 = v1; + for (++i; i < endXMin; ++i) + { + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)0; + mTCoords[v1][1] = (Real)4 - distance[i] * (Real)4; + v0 = v1; + } +} + +template +void GenerateMeshUV::AssignBoundaryTextureCoordinatesDisk() +{ + // Map the boundary of the mesh to a convex polygon. The selection of + // convex polygon vertices is such that the relative distances between + // boundary vertices and the relative distances between polygon vertices + // is preserved. The first boundary point has an implied distance of + // zero. The value distance[i] is the length of the boundary polyline + // from vertex 0 to vertex i+1. + std::vector distance(mNumBoundaryEdges); + Real total = (Real)0; + int v0 = mBoundaryStart; + for (int i = 0; i < mNumBoundaryEdges; ++i) + { + int v1 = mVertexInfo[v0]; + total += Length(mVertices[v1] - mVertices[v0]); + distance[i] = total; + v0 = v1; + } + + // The convex polygon lives in [0,1]^2 and inscribes a circle with center + // (1/2,1/2) and radius 1/2. The polygon center is not necessarily the + // circle center! This is the case when a boundary edge has length larger + // than half the total length of the boundary polyline; we do not expect + // such data for our meshes. The first polygon vertex is (1/2,0). The + // remaining vertices are chosen counterclockwise around the polygon. + Real multiplier = ((Real)GTE_C_TWO_PI) / total; + v0 = mBoundaryStart; + mTCoords[v0][0] = (Real)1; + mTCoords[v0][1] = (Real)0.5; + for (int i = 1; i < mNumBoundaryEdges; ++i) + { + int v1 = mVertexInfo[v0]; + Real angle = multiplier * distance[i - 1]; + mTCoords[v1][0] = (std::cos(angle) + (Real)1) * (Real)0.5; + mTCoords[v1][1] = (std::sin(angle) + (Real)1) * (Real)0.5; + v0 = v1; + } +} + +template +void GenerateMeshUV::ComputeMeanValueWeights() +{ + for (auto const& edge : mInteriorEdges) + { + int v0 = edge->V[0], v1 = edge->V[1]; + for (int i = 0; i < 2; ++i) + { + // Compute the direction from X0 to X1 and compute the length + // of the edge (X0,X1). + Vector3 X0 = mVertices[v0]; + Vector3 X1 = mVertices[v1]; + Vector3 X1mX0 = X1 - X0; + Real x1mx0length = Normalize(X1mX0); + Real weight; + if (x1mx0length >(Real)0) + { + // Compute the weight for X0 associated with X1. + weight = (Real)0; + for (int j = 0; j < 2; ++j) + { + // Find the vertex of triangle T[j] opposite edge . + auto tri = edge->T[j].lock(); + int k; + for (k = 0; k < 3; ++k) + { + int v2 = tri->V[k]; + if (v2 != v0 && v2 != v1) + { + Vector3 X2 = mVertices[v2]; + Vector3 X2mX0 = X2 - X0; + Real x2mx0Length = Normalize(X2mX0); + if (x2mx0Length >(Real)0) + { + Real dot = Dot(X2mX0, X1mX0); + Real cs = std::min(std::max(dot, (Real)-1), (Real)1); + Real angle = std::acos(cs); + weight += std::tan(angle * (Real)0.5); + } + else + { + weight += (Real)1; + } + break; + } + } + } + weight /= x1mx0length; + } + else + { + weight = (Real)1; + } + + std::array range = mVertexGraph[v0].range; + for (int j = 0; j < range[1]; ++j) + { + std::pair& data = mVertexGraphData[range[0] + j]; + if (data.first == v1) + { + data.second = weight; + } + } + + std::swap(v0, v1); + } + } +} + +template +void GenerateMeshUV::SolveSystem(unsigned int numIterations) +{ + // On the first pass, average only neighbors whose texture coordinates + // have been computed. This is a good initial guess for the linear system + // and leads to relatively fast convergence of the Gauss-Seidel iterates. + Real zero = (Real)0; + for (int i = mNumBoundaryEdges; i < mNumVertices; ++i) + { + int v0 = mOrderedVertices[i]; + std::array range = mVertexGraph[v0].range; + auto const* current = &mVertexGraphData[range[0]]; + Vector2 tcoord{ zero, zero }; + Real weight, weightSum = zero; + for (int j = 0; j < range[1]; ++j, ++current) + { + int v1 = current->first; + if (mTCoords[v1][0] != -1.0f) + { + weight = current->second; + weightSum += weight; + tcoord += weight * mTCoords[v1]; + } + } + tcoord /= weightSum; + mTCoords[v0] = tcoord; + } + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) + if (mCModel->engine) + { + SolveSystemGPU(numIterations); + } + else +#endif + { + if (mCModel->numThreads > 1) + { + SolveSystemCPUMultiple(numIterations); + } + else + { + SolveSystemCPUSingle(numIterations); + } + } +} + +template +void GenerateMeshUV::SolveSystemCPUSingle(unsigned int numIterations) +{ + // Use ping-pong buffers for the texture coordinates. + std::vector> tcoords(mNumVertices); + size_t numBytes = mNumVertices * sizeof(Vector2); + Memcpy(&tcoords[0], mTCoords, numBytes); + Vector2* inTCoords = mTCoords; + Vector2* outTCoords = &tcoords[0]; + + // The value numIterations is even, so we always swap an even number + // of times. This ensures that on exit from the loop, outTCoords is + // tcoords. + for (unsigned int i = 1; i <= numIterations; ++i) + { + if (mCModel->progress) + { + (*mCModel->progress)(i); + } + + for (int j = mNumBoundaryEdges; j < mNumVertices; ++j) + { + int v0 = mOrderedVertices[j]; + std::array range = mVertexGraph[v0].range; + auto const* current = &mVertexGraphData[range[0]]; + Vector2 tcoord{ (Real)0, (Real)0 }; + Real weight, weightSum = (Real)0; + for (int k = 0; k < range[1]; ++k, ++current) + { + int v1 = current->first; + weight = current->second; + weightSum += weight; + tcoord += weight * inTCoords[v1]; + } + tcoord /= weightSum; + outTCoords[v0] = tcoord; + } + + std::swap(inTCoords, outTCoords); + } +} + +template +void GenerateMeshUV::SolveSystemCPUMultiple(unsigned int numIterations) +{ + // Use ping-pong buffers for the texture coordinates. + std::vector> tcoords(mNumVertices); + size_t numBytes = mNumVertices * sizeof(Vector2); + Memcpy(&tcoords[0], mTCoords, numBytes); + Vector2* inTCoords = mTCoords; + Vector2* outTCoords = &tcoords[0]; + + // Partition the data for multiple threads. + int numV = mNumVertices - mNumBoundaryEdges; + int numVPerThread = numV / mCModel->numThreads; + std::vector vmin(mCModel->numThreads), vmax(mCModel->numThreads); + for (unsigned int t = 0; t < mCModel->numThreads; ++t) + { + vmin[t] = mNumBoundaryEdges + t * numVPerThread; + vmax[t] = vmin[t] + numVPerThread - 1; + } + vmax[mCModel->numThreads - 1] = mNumVertices - 1; + + // The value numIterations is even, so we always swap an even number + // of times. This ensures that on exit from the loop, outTCoords is + // tcoords. + for (unsigned int i = 1; i <= numIterations; ++i) + { + if (mCModel->progress) + { + (*mCModel->progress)(i); + } + + // Execute Gauss-Seidel iterations in multiple threads. + std::vector process(mCModel->numThreads); + for (unsigned int t = 0; t < mCModel->numThreads; ++t) + { + process[t] = std::thread([this, t, &vmin, &vmax, inTCoords, + outTCoords]() + { + for (int j = vmin[t]; j <= vmax[t]; ++j) + { + int v0 = mOrderedVertices[j]; + std::array range = mVertexGraph[v0].range; + auto const* current = &mVertexGraphData[range[0]]; + Vector2 tcoord{ (Real)0, (Real)0 }; + Real weight, weightSum = (Real)0; + for (int k = 0; k < range[1]; ++k, ++current) + { + int v1 = current->first; + weight = current->second; + weightSum += weight; + tcoord += weight * inTCoords[v1]; + } + tcoord /= weightSum; + outTCoords[v0] = tcoord; + } + }); + } + + // Wait for all threads to finish. + for (unsigned int t = 0; t < mCModel->numThreads; ++t) + { + process[t].join(); + } + + std::swap(inTCoords, outTCoords); + } +} + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) + +template +void GenerateMeshUV::SolveSystemGPU(unsigned int numIterations) +{ + mCModel->factory->defines.Set("NUM_X_THREADS", 8); + mCModel->factory->defines.Set("NUM_Y_THREADS", 8); + if (std::numeric_limits::max() == std::numeric_limits::max()) + { + mCModel->factory->defines.Set("Real", "float"); +#if defined(GTE_DEV_OPENGL) + mCModel->factory->defines.Set("Real2", "vec2"); +#else + mCModel->factory->defines.Set("Real2", "float2"); +#endif + } + else + { + mCModel->factory->defines.Set("Real", "double"); +#if defined(GTE_DEV_OPENGL) + mCModel->factory->defines.Set("Real2", "dvec2"); +#else + mCModel->factory->defines.Set("Real2", "double2"); +#endif + } + + // TODO: Test mSolveSystem for null and respond accordingly. + int api = mCModel->factory->GetAPI(); + mSolveSystem = mCModel->factory->CreateFromSource(*msSource[api]); + std::shared_ptr cshader = mSolveSystem->GetCShader(); + + // Compute the number of thread groups. + int numInputs = mNumVertices - mNumBoundaryEdges; + Real factor0 = std::ceil(std::sqrt((Real)numInputs)); + Real factor1 = std::ceil((Real)numInputs / factor0); + int xElements = static_cast(factor0); + int yElements = static_cast(factor1); + int xRem = (xElements % 8); + if (xRem > 0) + { + xElements += 8 - xRem; + } + int yRem = (yElements % 8); + if (yRem > 0) + { + yElements += 8 - yRem; + } + unsigned int numXGroups = xElements / 8; + unsigned int numYGroups = yElements / 8; + + mBoundBuffer = std::make_shared(4 * sizeof(int), false); + int* data = mBoundBuffer->Get(); + data[0] = xElements; + data[1] = yElements; + data[2] = mNumBoundaryEdges; + data[3] = numInputs; + cshader->Set("Bounds", mBoundBuffer); + + unsigned int const vgSize = static_cast(mVertexGraph.size()); + mVGBuffer = std::make_shared(vgSize, sizeof(Vertex)); + Memcpy(mVGBuffer->GetData(), &mVertexGraph[0], mVGBuffer->GetNumBytes()); + cshader->Set("vertexGraph", mVGBuffer); + + unsigned int const vgdSize = static_cast(mVertexGraphData.size()); + mVGDBuffer = std::make_shared(vgdSize, sizeof(std::pair)); + Memcpy(mVGDBuffer->GetData(), &mVertexGraphData[0], mVGDBuffer->GetNumBytes()); + cshader->Set("vertexGraphData", mVGDBuffer); + + unsigned int const ovSize = static_cast(mOrderedVertices.size()); + mOVBuffer = std::make_shared(ovSize, sizeof(int)); + Memcpy(mOVBuffer->GetData(), &mOrderedVertices[0], mOVBuffer->GetNumBytes()); + cshader->Set("orderedVertices", mOVBuffer); + + for (int j = 0; j < 2; ++j) + { + mTCoordsBuffer[j] = std::make_shared(mNumVertices, sizeof(Vector2)); + mTCoordsBuffer[j]->SetUsage(Resource::SHADER_OUTPUT); + Memcpy(mTCoordsBuffer[j]->GetData(), mTCoords, mTCoordsBuffer[j]->GetNumBytes()); + } + mTCoordsBuffer[0]->SetCopyType(Resource::COPY_STAGING_TO_CPU); + + // The value numIterations is even, so we always swap an even number + // of times. This ensures that on exit from the loop, + // mTCoordsBuffer[0] has the final output. + for (unsigned int i = 1; i <= numIterations; ++i) + { + if (mCModel->progress) + { + (*mCModel->progress)(i); + } + + cshader->Set("inTCoords", mTCoordsBuffer[0]); + cshader->Set("outTCoords", mTCoordsBuffer[1]); + mCModel->engine->Execute(mSolveSystem, numXGroups, numYGroups, 1); + std::swap(mTCoordsBuffer[0], mTCoordsBuffer[1]); + } + + mCModel->engine->CopyGpuToCpu(mTCoordsBuffer[0]); + Memcpy(mTCoords, mTCoordsBuffer[0]->GetData(), mTCoordsBuffer[0]->GetNumBytes()); +} + +#endif + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHalfspace.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHalfspace.h new file mode 100644 index 000000000000..2d1d38ed5452 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHalfspace.h @@ -0,0 +1,113 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The halfspace is represented as Dot(N,X) >= c where N is a unit-length +// normal vector, c is the plane constant, and X is any point in space. +// The user must ensure that the normal vector is unit length. + +namespace gte +{ + +template +class Halfspace +{ +public: + // Construction and destruction. The default constructor sets the normal + // to (0,...,0,1) and the constant to zero (halfspace x[N-1] >= 0). + Halfspace(); + + // Specify N and c directly. + Halfspace(Vector const& inNormal, Real inConstant); + + // Public member access. + Vector normal; + Real constant; + +public: + // Comparisons to support sorted containers. + bool operator==(Halfspace const& halfspace) const; + bool operator!=(Halfspace const& halfspace) const; + bool operator< (Halfspace const& halfspace) const; + bool operator<=(Halfspace const& halfspace) const; + bool operator> (Halfspace const& halfspace) const; + bool operator>=(Halfspace const& halfspace) const; +}; + +// Template alias for convenience. +template +using Halfspace3 = Halfspace<3, Real>; + + +template +Halfspace::Halfspace() + : + constant((Real)0) +{ + normal.MakeUnit(N - 1); +} + +template +Halfspace::Halfspace(Vector const& inNormal, + Real inConstant) + : + normal(inNormal), + constant(inConstant) +{ +} + +template +bool Halfspace::operator==(Halfspace const& halfspace) const +{ + return normal == halfspace.normal && constant == halfspace.constant; +} + +template +bool Halfspace::operator!=(Halfspace const& halfspace) const +{ + return !operator==(halfspace); +} + +template +bool Halfspace::operator<(Halfspace const& halfspace) const +{ + if (normal < halfspace.normal) + { + return true; + } + + if (normal > halfspace.normal) + { + return false; + } + + return constant < halfspace.constant; +} + +template +bool Halfspace::operator<=(Halfspace const& halfspace) const +{ + return operator<(halfspace) || operator==(halfspace); +} + +template +bool Halfspace::operator>(Halfspace const& halfspace) const +{ + return !operator<=(halfspace); +} + +template +bool Halfspace::operator>=(Halfspace const& halfspace) const +{ + return !operator<(halfspace); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHyperellipsoid.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHyperellipsoid.h new file mode 100644 index 000000000000..1d74c87adee5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHyperellipsoid.h @@ -0,0 +1,383 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include + +// A hyperellipsoid has center K; axis directions U[0] through U[N-1], all +// unit-length vectors; and extents e[0] through e[N-1], all positive numbers. +// A point X = K + sum_{d=0}^{N-1} y[d]*U[d] is on the hyperellipsoid whenever +// sum_{d=0}^{N-1} (y[d]/e[d])^2 = 1. An algebraic representation for the +// hyperellipsoid is (X-K)^T * M * (X-K) = 1, where M is the NxN symmetric +// matrix M = sum_{d=0}^{N-1} U[d]*U[d]^T/e[d]^2, where the superscript T +// denotes transpose. Observe that U[i]*U[i]^T is a matrix, not a scalar dot +// product. The hyperellipsoid is also represented by a quadratic equation +// 0 = C + B^T*X + X^T*A*X, where C is a scalar, B is an Nx1 vector, and A is +// an NxN symmetric matrix with positive eigenvalues. The coefficients can be +// stored from lowest degree to highest degree, +// C = k[0] +// B = k[1], ..., k[N] +// A = k[N+1], ..., k[(N+1)(N+2)/2 - 1] +// where the A-coefficients are the upper-triangular elements of A listed in +// row-major order. For N = 2, X = (x[0],x[1]) and +// 0 = k[0] + +// k[1]*x[0] + k[2]*x[1] + +// k[3]*x[0]*x[0] + k[4]*x[0]*x[1] +// + k[5]*x[1]*x[1] +// For N = 3, X = (x[0],x[1],x[2]) and +// 0 = k[0] + +// k[1]*x[0] + k[2]*x[1] + k[3]*x[2] + +// k[4]*x[0]*x[0] + k[5]*x[0]*x[1] + k[6]*x[0]*x[2] + +// + k[7]*x[1]*x[1] + k[8]*x[1]*x[2] + +// + k[9]*x[2]*x[2] +// This equation can be factored to the form (X-K)^T * M * (X-K) = 1, where +// K = -A^{-1}*B/2, M = A/(B^T*A^{-1}*B/4-C). + +namespace gte +{ + +template +class Hyperellipsoid +{ +public: + + // Construction and destruction. The default constructor sets the center + // to Vector::Zero(), the axes to Vector::Unit(d), and all + // extents to 1. + Hyperellipsoid(); + Hyperellipsoid(Vector const& inCenter, + std::array, N> const inAxis, + Vector const& inExtent); + + // Compute M = sum_{d=0}^{N-1} U[d]*U[d]^T/e[d]^2. + void GetM(Matrix& M) const; + + // Compute M^{-1} = sum_{d=0}^{N-1} U[d]*U[d]^T*e[d]^2. + void GetMInverse(Matrix& MInverse) const; + + // Construct the coefficients in the quadratic equation that represents + // the hyperellipsoid. + void ToCoefficients(std::array& coeff) const; + void ToCoefficients(Matrix& A, Vector& B, + Real& C) const; + + // Construct C, U[i], and e[i] from the equation. The return value is + // 'true' if and only if the input coefficients represent a + // hyperellipsoid. If the function returns 'false', the hyperellipsoid + // data members are undefined. + bool FromCoefficients(std::array const& coeff); + bool FromCoefficients(Matrix const& A, + Vector const& B, Real C); + + // Public member access. + Vector center; + std::array, N> axis; + Vector extent; + +private: + static void Convert(std::array const& coeff, + Matrix& A, Vector& B, Real& C); + + static void Convert(Matrix const& A, Vector const& B, + Real C, std::array& coeff); + +public: + // Comparisons to support sorted containers. + bool operator==(Hyperellipsoid const& hyperellipsoid) const; + bool operator!=(Hyperellipsoid const& hyperellipsoid) const; + bool operator< (Hyperellipsoid const& hyperellipsoid) const; + bool operator<=(Hyperellipsoid const& hyperellipsoid) const; + bool operator> (Hyperellipsoid const& hyperellipsoid) const; + bool operator>=(Hyperellipsoid const& hyperellipsoid) const; +}; + +// Template aliases for convenience. +template +using Ellipse2 = Hyperellipsoid<2, Real>; + +template +using Ellipsoid3 = Hyperellipsoid<3, Real>; + + +template +Hyperellipsoid::Hyperellipsoid() +{ + center.MakeZero(); + for (int d = 0; d < N; ++d) + { + axis[d].MakeUnit(d); + extent[d] = (Real)1; + } +} + +template +Hyperellipsoid::Hyperellipsoid(Vector const& inCenter, + std::array, N> const inAxis, + Vector const& inExtent) + : + center(inCenter), + axis(inAxis), + extent(inExtent) +{ +} + +template +void Hyperellipsoid::GetM(Matrix& M) const +{ + M.MakeZero(); + for (int d = 0; d < N; ++d) + { + Vector ratio = axis[d] / extent[d]; + M += OuterProduct(ratio, ratio); + } +} + +template +void Hyperellipsoid::GetMInverse(Matrix& MInverse) const +{ + MInverse.MakeZero(); + for (int d = 0; d < N; ++d) + { + Vector product = axis[d] * extent[d]; + MInverse += OuterProduct(product, product); + } +} + +template +void Hyperellipsoid::ToCoefficients( + std::array& coeff) const +{ + int const numCoefficients = (N + 1)*(N + 2) / 2; + Matrix A; + Vector B; + Real C; + ToCoefficients(A, B, C); + Convert(A, B, C, coeff); + + // Arrange for one of the coefficients of the quadratic terms to be 1. + int quadIndex = numCoefficients - 1; + int maxIndex = quadIndex; + Real maxValue = std::abs(coeff[quadIndex]); + for (int d = 2; d < N; ++d) + { + quadIndex -= d; + Real absValue = std::abs(coeff[quadIndex]); + if (absValue > maxValue) + { + maxIndex = quadIndex; + maxValue = absValue; + } + } + + Real invMaxValue = ((Real)1) / maxValue; + for (int i = 0; i < numCoefficients; ++i) + { + if (i != maxIndex) + { + coeff[i] *= invMaxValue; + } + else + { + coeff[i] = (Real)1; + } + } +} + +template +void Hyperellipsoid::ToCoefficients(Matrix& A, + Vector& B, Real& C) const +{ + GetM(A); + Vector product = A * center; + B = ((Real)-2) * product; + C = Dot(center, product) - (Real)1; +} + +template +bool Hyperellipsoid::FromCoefficients( + std::array const& coeff) +{ + Matrix A; + Vector B; + Real C; + Convert(coeff, A, B, C); + return FromCoefficients(A, B, C); +} + +template +bool Hyperellipsoid::FromCoefficients(Matrix const& A, + Vector const& B, Real C) +{ + // Compute the center K = -A^{-1}*B/2. + bool invertible; + Matrix invA = Inverse(A, &invertible); + if (!invertible) + { + return false; + } + + center = ((Real)-0.5) * (invA * B); + + // Compute B^T*A^{-1}*B/4 - C = K^T*A*K - C = -K^T*B/2 - C. + Real rightSide = -((Real)0.5) * Dot(center, B) - C; + if (rightSide == (Real)0) + { + return false; + } + + // Compute M = A/(K^T*A*K - C). + Real invRightSide = ((Real)1) / rightSide; + Matrix M = invRightSide * A; + + // Factor into M = R*D*R^T. M is symmetric, so it does not matter whether + // the matrix is stored in row-major or column-major order; they are + // equivalent. The output R, however, is in row-major order. + SymmetricEigensolver es(N, 32); + Matrix rotation; + std::array diagonal; + es.Solve(&M[0], +1); // diagonal[i] are nondecreasing + es.GetEigenvalues(&diagonal[0]); + es.GetEigenvectors(&rotation[0]); + if (es.GetEigenvectorMatrixType() == 0) + { + auto negLast = -rotation.GetCol(N - 1); + rotation.SetCol(N - 1, negLast); + } + + for (int d = 0; d < N; ++d) + { + if (diagonal[d] <= (Real)0) + { + return false; + } + + extent[d] = ((Real)1) / std::sqrt(diagonal[d]); + axis[d] = rotation.GetCol(d); + } + + return true; +} + +template +void Hyperellipsoid::Convert( + std::array const& coeff, Matrix& A, + Vector& B, Real& C) +{ + int i = 0; + C = coeff[i++]; + + for (int j = 0; j < N; ++j) + { + B[j] = coeff[i++]; + } + + for (int r = 0; r < N; ++r) + { + for (int c = 0; c < r; ++c) + { + A(r, c) = A(c, r); + } + + A(r, r) = coeff[i++]; + + for (int c = r + 1; c < N; ++c) + { + A(r, c) = coeff[i++] * (Real)0.5; + } + } +} + +template +void Hyperellipsoid::Convert(Matrix const& A, + Vector const& B, Real C, + std::array& coeff) +{ + int i = 0; + coeff[i++] = C; + + for (int j = 0; j < N; ++j) + { + coeff[i++] = B[j]; + } + + for (int r = 0; r < N; ++r) + { + coeff[i++] = A(r, r); + for (int c = r + 1; c < N; ++c) + { + coeff[i++] = A(r, c) * (Real)2; + } + } +} + +template +bool Hyperellipsoid::operator==( + Hyperellipsoid const& hyperellipsoid) const +{ + return center == hyperellipsoid.center && axis == hyperellipsoid.axis + && extent == hyperellipsoid.extent; +} + +template +bool Hyperellipsoid::operator!=( + Hyperellipsoid const& hyperellipsoid) const +{ + return !operator==(hyperellipsoid); +} + +template +bool Hyperellipsoid::operator<( + Hyperellipsoid const& hyperellipsoid) const +{ + if (center < hyperellipsoid.center) + { + return true; + } + + if (center > hyperellipsoid.center) + { + return false; + } + + if (axis < hyperellipsoid.axis) + { + return true; + } + + if (axis > hyperellipsoid.axis) + { + return false; + } + + return extent < hyperellipsoid.extent; +} + +template +bool Hyperellipsoid::operator<=( + Hyperellipsoid const& hyperellipsoid) const +{ + return operator<(hyperellipsoid) || operator==(hyperellipsoid); +} + +template +bool Hyperellipsoid::operator>( + Hyperellipsoid const& hyperellipsoid) const +{ + return !operator<=(hyperellipsoid); +} + +template +bool Hyperellipsoid::operator>=( + Hyperellipsoid const& hyperellipsoid) const +{ + return !operator<(hyperellipsoid); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHyperplane.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHyperplane.h new file mode 100644 index 000000000000..54a2a382b678 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHyperplane.h @@ -0,0 +1,149 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2016/07/28) + +#pragma once + +#include +#include + +// The plane is represented as Dot(U,X) = c where U is a unit-length normal +// vector, c is the plane constant, and X is any point on the plane. The user +// must ensure that the normal vector is unit length. + +namespace gte +{ + +template +class Hyperplane +{ +public: + // Construction and destruction. The default constructor sets the normal + // to (0,...,0,1) and the constant to zero (plane z = 0). + Hyperplane(); + + // Specify U and c directly. + Hyperplane(Vector const& inNormal, Real inConstant); + + // U is specified, c = Dot(U,p) where p is a point on the hyperplane. + Hyperplane(Vector const& inNormal, Vector const& p); + + // U is a unit-length vector in the orthogonal complement of the set + // {p[1]-p[0],...,p[n-1]-p[0]} and c = Dot(U,p[0]), where the p[i] are + // pointson the hyperplane. + Hyperplane(std::array, N> const& p); + + // Public member access. + Vector normal; + Real constant; + +public: + // Comparisons to support sorted containers. + bool operator==(Hyperplane const& hyperplane) const; + bool operator!=(Hyperplane const& hyperplane) const; + bool operator< (Hyperplane const& hyperplane) const; + bool operator<=(Hyperplane const& hyperplane) const; + bool operator> (Hyperplane const& hyperplane) const; + bool operator>=(Hyperplane const& hyperplane) const; +}; + +// Template alias for convenience. +template +using Plane3 = Hyperplane<3, Real>; + + +template +Hyperplane::Hyperplane() + : + constant((Real)0) +{ + normal.MakeUnit(N - 1); +} + +template +Hyperplane::Hyperplane(Vector const& inNormal, + Real inConstant) + : + normal(inNormal), + constant(inConstant) +{ +} + +template +Hyperplane::Hyperplane(Vector const& inNormal, + Vector const& p) + : + normal(inNormal), + constant(Dot(inNormal, p)) +{ +} + +template +Hyperplane::Hyperplane(std::array, N> const& p) +{ + Matrix edge; + for (int i = 0; i < N - 1; ++i) + { + edge.SetCol(i, p[i + 1] - p[0]); + } + + // Compute the 1-dimensional orthogonal complement of the edges of the + // simplex formed by the points p[]. + SingularValueDecomposition svd(N, N - 1, 32); + svd.Solve(&edge[0], -1); + svd.GetUColumn(N - 1, &normal[0]); + + constant = Dot(normal, p[0]); +} + +template +bool Hyperplane::operator==(Hyperplane const& hyperplane) const +{ + return normal == hyperplane.normal && constant == hyperplane.constant; +} + +template +bool Hyperplane::operator!=(Hyperplane const& hyperplane) const +{ + return !operator==(hyperplane); +} + +template +bool Hyperplane::operator<(Hyperplane const& hyperplane) const +{ + if (normal < hyperplane.normal) + { + return true; + } + + if (normal > hyperplane.normal) + { + return false; + } + + return constant < hyperplane.constant; +} + +template +bool Hyperplane::operator<=(Hyperplane const& hyperplane) const +{ + return operator<(hyperplane) || operator==(hyperplane); +} + +template +bool Hyperplane::operator>(Hyperplane const& hyperplane) const +{ + return !operator<=(hyperplane); +} + +template +bool Hyperplane::operator>=(Hyperplane const& hyperplane) const +{ + return !operator<(hyperplane); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHypersphere.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHypersphere.h new file mode 100644 index 000000000000..b23b00b13ace --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHypersphere.h @@ -0,0 +1,114 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The hypersphere is represented as |X-C| = R where C is the center and R is +// the radius. The hypersphere is a circle for dimension 2 or a sphere for +// dimension 3. + +namespace gte +{ + +template +class Hypersphere +{ +public: + // Construction and destruction. The default constructor sets the center + // to (0,...,0) and the radius to 1. + Hypersphere(); + Hypersphere(Vector const& inCenter, Real inRadius); + + // Public member access. + Vector center; + Real radius; + +public: + // Comparisons to support sorted containers. + bool operator==(Hypersphere const& hypersphere) const; + bool operator!=(Hypersphere const& hypersphere) const; + bool operator< (Hypersphere const& hypersphere) const; + bool operator<=(Hypersphere const& hypersphere) const; + bool operator> (Hypersphere const& hypersphere) const; + bool operator>=(Hypersphere const& hypersphere) const; +}; + +// Template aliases for convenience. +template +using Circle2 = Hypersphere<2, Real>; + +template +using Sphere3 = Hypersphere<3, Real>; + + +template +Hypersphere::Hypersphere() + : + radius((Real)1) +{ + center.MakeZero(); +} + +template +Hypersphere::Hypersphere(Vector const& inCenter, + Real inRadius) + : + center(inCenter), + radius(inRadius) +{ +} + +template +bool Hypersphere::operator==(Hypersphere const& hypersphere) const +{ + return center == hypersphere.center && radius == hypersphere.radius; +} + +template +bool Hypersphere::operator!=(Hypersphere const& hypersphere) const +{ + return !operator==(hypersphere); +} + +template +bool Hypersphere::operator<(Hypersphere const& hypersphere) const +{ + if (center < hypersphere.center) + { + return true; + } + + if (center > hypersphere.center) + { + return false; + } + + return radius < hypersphere.radius; +} + +template +bool Hypersphere::operator<=(Hypersphere const& hypersphere) const +{ + return operator<(hypersphere) || operator==(hypersphere); +} + +template +bool Hypersphere::operator>(Hypersphere const& hypersphere) const +{ + return !operator<=(hypersphere); +} + +template +bool Hypersphere::operator>=(Hypersphere const& hypersphere) const +{ + return !operator<(hypersphere); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary.h new file mode 100644 index 000000000000..1ddc8cb70fc0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary.h @@ -0,0 +1,448 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class IEEEBinary +{ +public: + // For generic access of the template types. + typedef Float FloatType; + typedef UInt UIntType; + + // Construction from an encoding. Copy constructor, destructor, and + // assignment operator are implicitly generated. For the 3-parameter + // constructor, see the comments for SetEncoding(...). + ~IEEEBinary (); + IEEEBinary (); // The encoding is uninitialized. + IEEEBinary (IEEEBinary const& object); + IEEEBinary (UInt inEncoding); + IEEEBinary (UInt inSign, UInt inBiased, UInt inTrailing); + IEEEBinary (Float inNumber); + + // Implicit conversion to floating-point type. + operator UInt () const; + operator Float () const; + + // Assignment. + IEEEBinary& operator= (IEEEBinary const& object); + + // Special constants. + static int const NUM_ENCODING_BITS = NumBits; + static int const NUM_EXPONENT_BITS = NumBits - Precision; + static int const NUM_SIGNIFICAND_BITS = Precision; + static int const NUM_TRAILING_BITS = Precision - 1; + static int const EXPONENT_BIAS = (1 << (NUM_EXPONENT_BITS - 1)) - 1; + static int const MAX_BIASED_EXPONENT = (1 << NUM_EXPONENT_BITS) - 1; + static int const MIN_SUB_EXPONENT = 1 - EXPONENT_BIAS; + static int const MIN_EXPONENT = MIN_SUB_EXPONENT - NUM_TRAILING_BITS; + static int const SIGN_SHIFT = NumBits - 1; + + static UInt const SIGN_MASK = (UInt(1) << (NumBits - 1)); + static UInt const NOT_SIGN_MASK = UInt(~SIGN_MASK); + static UInt const TRAILING_MASK = (UInt(1) << NUM_TRAILING_BITS) - 1; + static UInt const EXPONENT_MASK = NOT_SIGN_MASK & ~TRAILING_MASK; + static UInt const NAN_QUIET_MASK = (UInt(1) << (NUM_TRAILING_BITS - 1)); + static UInt const NAN_PAYLOAD_MASK = (TRAILING_MASK >> 1); + static UInt const MAX_TRAILING = TRAILING_MASK; + static UInt const SUP_TRAILING = (UInt(1) << NUM_TRAILING_BITS); + static UInt const POS_ZERO = UInt(0); + static UInt const NEG_ZERO = SIGN_MASK; + static UInt const MIN_SUBNORMAL = UInt(1); + static UInt const MAX_SUBNORMAL = TRAILING_MASK; + static UInt const MIN_NORMAL = SUP_TRAILING; + static UInt const MAX_NORMAL = NOT_SIGN_MASK & ~SUP_TRAILING; + static UInt const POS_INFINITY = EXPONENT_MASK; + static UInt const NEG_INFINITY = SIGN_MASK | EXPONENT_MASK; + + // The types of numbers. + enum Classification + { + CLASS_NEG_INFINITY, + CLASS_NEG_SUBNORMAL, + CLASS_NEG_NORMAL, + CLASS_NEG_ZERO, + CLASS_POS_ZERO, + CLASS_POS_SUBNORMAL, + CLASS_POS_NORMAL, + CLASS_POS_INFINITY, + CLASS_QUIET_NAN, + CLASS_SIGNALING_NAN + }; + + Classification GetClassification () const; + bool IsZero () const; + bool IsSignMinus () const; + bool IsSubnormal () const; + bool IsNormal () const; + bool IsFinite () const; + bool IsInfinite () const; + bool IsNaN () const; + bool IsSignalingNaN () const; + + // Get neighboring numbers. + UInt GetNextUp () const; + UInt GetNextDown () const; + + // Encode and decode the binary representation. The sign is 0 (number + // is nonnegative) or 1 (number is negative). The biased exponent is in + // the range [0,MAX_BIASED_EXPONENT]. The trailing significand is in the + // range [0,MAX_TRAILING]. + UInt GetSign () const; + UInt GetBiased () const; + UInt GetTrailing () const; + void SetEncoding (UInt sign, UInt biased, UInt trailing); + void GetEncoding (UInt& sign, UInt& biased, UInt& trailing) const; + + // Access for direct manipulation of the object. + union + { + UInt encoding; + Float number; + }; +}; + +typedef IEEEBinary IEEEBinary32; +typedef IEEEBinary IEEEBinary64; + + +template +IEEEBinary::~IEEEBinary() +{ +} + +template +IEEEBinary::IEEEBinary() +{ + // The member mEncoding is uninitialized. +} + +template +IEEEBinary::IEEEBinary( + IEEEBinary const& object) + : + encoding(object.encoding) +{ +} + +template +IEEEBinary::IEEEBinary(UInt inEncoding) + : + encoding(inEncoding) +{ +} + +template +IEEEBinary::IEEEBinary(UInt inSign, + UInt inBiased, UInt inTrailing) +{ + SetEncoding(inSign, inBiased, inTrailing); +} + +template +IEEEBinary::IEEEBinary(Float inNumber) + : + number(inNumber) +{ +} + +template +IEEEBinary::operator UInt () const +{ + return encoding; +} + +template +IEEEBinary::operator Float () const +{ + return number; +} + +template +IEEEBinary& +IEEEBinary::operator= (IEEEBinary const& object) +{ + encoding = object.encoding; + return *this; +} + +template +typename IEEEBinary::Classification +IEEEBinary::GetClassification() const +{ + UInt sign, biased, trailing; + GetEncoding(sign, biased, trailing); + + if (biased == 0) + { + if (trailing == 0) + { + return (sign != 0 ? CLASS_NEG_ZERO : CLASS_POS_ZERO); + } + else + { + return (sign != 0 ? CLASS_NEG_SUBNORMAL : CLASS_POS_SUBNORMAL); + } + } + else if (biased < MAX_BIASED_EXPONENT) + { + return (sign != 0 ? CLASS_NEG_NORMAL : CLASS_POS_NORMAL); + } + else if (trailing == 0) + { + return (sign != 0 ? CLASS_NEG_INFINITY : CLASS_POS_INFINITY); + } + else if (trailing & NAN_QUIET_MASK) + { + return CLASS_QUIET_NAN; + } + else + { + return CLASS_SIGNALING_NAN; + } +} + +template +bool IEEEBinary::IsZero() const +{ + return encoding == POS_ZERO || encoding == NEG_ZERO; +} + +template +bool IEEEBinary::IsSignMinus() const +{ + return (encoding & SIGN_MASK) != 0; +} + +template +bool IEEEBinary::IsSubnormal() const +{ + return GetBiased() == 0 && GetTrailing() > 0; +} + +template +bool IEEEBinary::IsNormal() const +{ + UInt biased = GetBiased(); + return 0 < biased && biased < MAX_BIASED_EXPONENT; +} + +template +bool IEEEBinary::IsFinite() const +{ + return GetBiased() < MAX_BIASED_EXPONENT; +} + +template +bool IEEEBinary::IsInfinite() const +{ + return GetBiased() == MAX_BIASED_EXPONENT && GetTrailing() == 0; +} + +template +bool IEEEBinary::IsNaN() const +{ + return GetBiased() == MAX_BIASED_EXPONENT && GetTrailing() != 0; +} + +template +bool IEEEBinary::IsSignalingNaN() const +{ + UInt trailing = GetTrailing(); + return GetBiased() == MAX_BIASED_EXPONENT + && (trailing & NAN_QUIET_MASK) == 0 + && (trailing & NAN_PAYLOAD_MASK) != 0; +} + +template +UInt IEEEBinary::GetNextUp() const +{ + UInt sign, biased, trailing; + GetEncoding(sign, biased, trailing); + + if (biased == 0) + { + if (trailing == 0) + { + // The next-up for both -0 and +0 is MIN_SUBNORMAL. + return MIN_SUBNORMAL; + } + else + { + if (sign != 0) + { + // When trailing is 1, 'this' is -MIN_SUBNORMAL and next-up + // is -0. + --trailing; + return SIGN_MASK | trailing; + } + else + { + // When trailing is MAX_TRAILING, 'this' is MAX_SUBNORMAL + // and next-up is MIN_NORMAL. + ++trailing; + return trailing; + } + } + } + else if (biased < MAX_BIASED_EXPONENT) + { + UInt nonnegative = (encoding & NOT_SIGN_MASK); + if (sign != 0) + { + --nonnegative; + return SIGN_MASK | nonnegative; + } + else + { + ++nonnegative; + return nonnegative; + } + } + else if (trailing == 0) + { + if (sign != 0) + { + // The next-up of -INFINITY is -MAX_NORMAL. + return SIGN_MASK | MAX_NORMAL; + } + else + { + // The next-up of +INFINITY is +INFINITY. + return POS_INFINITY; + } + } + else if (trailing & NAN_QUIET_MASK) + { + // TODO. The IEEE standard is not clear what to do here. Figure + // out what it means. + return 0; + } + else + { + // TODO. The IEEE standard is not clear what to do here. Figure + // out what it means. + return 0; + } +} + +template +UInt IEEEBinary::GetNextDown() const +{ + UInt sign, biased, trailing; + GetEncoding(sign, biased, trailing); + + if (biased == 0) + { + if (trailing == 0) + { + // The next-down for both -0 and +0 is -MIN_SUBNORMAL. + return SIGN_MASK | MIN_SUBNORMAL; + } + else + { + if (sign == 0) + { + // When trailing is 1, 'this' is MIN_SUBNORMAL and next-down + // is +0. + --trailing; + return trailing; + } + else + { + // When trailing is MAX_TRAILING, 'this' is -MAX_SUBNORMAL + // and next-down is -MIN_NORMAL. + ++trailing; + return SIGN_MASK | trailing; + } + } + } + else if (biased < MAX_BIASED_EXPONENT) + { + UInt nonnegative = (encoding & NOT_SIGN_MASK); + if (sign == 0) + { + --nonnegative; + return nonnegative; + } + else + { + ++nonnegative; + return SIGN_MASK | nonnegative; + } + } + else if (trailing == 0) + { + if (sign == 0) + { + // The next-down of +INFINITY is +MAX_NORMAL. + return MAX_NORMAL; + } + else + { + // The next-down of -INFINITY is -INFINITY. + return NEG_INFINITY; + } + } + else if (trailing & NAN_QUIET_MASK) + { + // TODO. The IEEE standard is not clear what to do here. Figure + // out what it means. + return 0; + } + else + { + // TODO. The IEEE standard is not clear what to do here. Figure + // out what it means. + return 0; + } +} + +template +UInt IEEEBinary::GetSign() const +{ + return (encoding & SIGN_MASK) >> SIGN_SHIFT; +} + +template +UInt IEEEBinary::GetBiased() const +{ + return (encoding & EXPONENT_MASK) >> NUM_TRAILING_BITS; +} + +template +UInt IEEEBinary::GetTrailing() const +{ + return encoding & TRAILING_MASK; +} + +template +void IEEEBinary::SetEncoding(UInt sign, + UInt biased, UInt trailing) +{ + encoding = + (sign << SIGN_SHIFT) | (biased << NUM_TRAILING_BITS) | trailing; +} + +template +void IEEEBinary::GetEncoding(UInt& sign, + UInt& biased, UInt& trailing) const +{ + sign = GetSign(); + biased = GetBiased(); + trailing = GetTrailing(); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary16.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary16.cpp new file mode 100644 index 000000000000..fc4d51287ec4 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary16.cpp @@ -0,0 +1,368 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#include +#include +#include + +namespace gte +{ + +IEEEBinary16::~IEEEBinary16() +{ +} + +IEEEBinary16::IEEEBinary16() + : + IEEEBinary() +{ + // uninitialized +} + +IEEEBinary16::IEEEBinary16(IEEEBinary16 const& object) + : + IEEEBinary(object) +{ +} + +IEEEBinary16::IEEEBinary16(float number) + : + IEEEBinary() +{ + union { float n; uint32_t e; } temp = { number }; + encoding = Convert32To16(temp.e); +} + +IEEEBinary16::IEEEBinary16(double number) + : + IEEEBinary() +{ + union { float n; uint32_t e; } temp; + temp.n = (float)number; + encoding = Convert32To16(temp.e); +} + +IEEEBinary16::IEEEBinary16(uint16_t encoding) + : + IEEEBinary(encoding) +{ +} + +IEEEBinary16::operator float() const +{ + union { uint32_t e; float n; } temp = { Convert16To32(encoding) }; + return temp.n; +} + +IEEEBinary16::operator double() const +{ + union { uint32_t e; float n; } temp = { Convert16To32(encoding) }; + return (double)temp.n; +} + +IEEEBinary16& IEEEBinary16::operator= (IEEEBinary16 const& object) +{ + IEEEBinary::operator=(object); + return *this; +} + +bool IEEEBinary16::operator==(IEEEBinary16 const& object) const +{ + return (float)*this == (float)object; +} + +bool IEEEBinary16::operator!=(IEEEBinary16 const& object) const +{ + return (float)*this != (float)object; +} + +bool IEEEBinary16::operator< (IEEEBinary16 const& object) const +{ + return (float)*this < (float)object; +} + +bool IEEEBinary16::operator<=(IEEEBinary16 const& object) const +{ + return (float)*this <= (float)object; +} + +bool IEEEBinary16::operator> (IEEEBinary16 const& object) const +{ + return (float)*this > (float)object; +} + +bool IEEEBinary16::operator>=(IEEEBinary16 const& object) const +{ + return (float)*this >= (float)object; +} + +uint16_t IEEEBinary16::Convert32To16(uint32_t encoding) +{ + // Extract the channels for the binary32 number. + uint32_t sign32 = (encoding & F32_SIGN_MASK); + uint32_t biased32 = + ((encoding & F32_BIASED_EXPONENT_MASK) >> F32_NUM_TRAILING_BITS); + uint32_t trailing32 = (encoding & F32_TRAILING_MASK); + uint32_t nonneg32 = (encoding & F32_NOT_SIGN_MASK); + + // Generate the channels for the IEEEBinary16 number. + uint16_t sign16 = static_cast(sign32 >> DIFF_NUM_ENCODING_BITS); + uint16_t biased16, trailing16; + uint32_t frcpart; + + if (biased32 == 0) + { + // nonneg32 is 32-zero or 32-subnormal, nearest is 16-zero. + return sign16; + } + + if (biased32 < F32_MAX_BIASED_EXPONENT) + { + // nonneg32 is 32-normal. + if (nonneg32 <= F16_AVR_MIN_SUBNORMAL_ZERO) + { + // nonneg32 <= 2^{-25}, nearest is 16-zero. + return sign16; + } + + if (nonneg32 <= F16_MIN_SUBNORMAL) + { + // 2^{-25} < nonneg32 <= 2^{-24}, nearest is 16-min-subnormal. + return sign16 | IEEEBinary16::MIN_SUBNORMAL; + } + + if (nonneg32 < F16_MIN_NORMAL) + { + // 2^{-24} < nonneg32 < 2^{-14}, round to nearest + // 16-subnormal with ties to even. Note that biased16 is zero. + trailing16 = static_cast(((trailing32 & INT_PART_MASK) >> DIFF_NUM_TRAILING_BITS)); + frcpart = (trailing32 & FRC_PART_MASK); + if (frcpart > FRC_HALF || (frcpart == FRC_HALF && (trailing16 & 1))) + { + // If there is a carry into the exponent, the nearest is + // actually 16-min-normal 1.0*2^{-14}, so the high-order bit + // of trailing16 makes biased16 equal to 1 and the result is + // correct. + ++trailing16; + } + return sign16 | trailing16; + } + + if (nonneg32 <= F16_MAX_NORMAL) + { + // 2^{-14} <= nonneg32 <= 1.1111111111*2^{15}, round to nearest + // 16-normal with ties to even. + biased16 = static_cast((biased32 - F32_EXPONENT_BIAS + + IEEEBinary16::EXPONENT_BIAS) + << IEEEBinary16::NUM_TRAILING_BITS); + trailing16 = static_cast(((trailing32 & INT_PART_MASK) >> DIFF_NUM_TRAILING_BITS)); + frcpart = (trailing32 & FRC_PART_MASK); + if (frcpart > FRC_HALF || (frcpart == FRC_HALF && (trailing16 & 1))) + { + // If there is a carry into the exponent, the addition of + // trailing16 to biased16 (rather than or-ing) produces the + // correct result. + ++trailing16; + } + return sign16 | (biased16 + trailing16); + } + + if (nonneg32 < F16_AVR_MAX_NORMAL_INFINITY) + { + // 1.1111111111*2^{15} < nonneg32 < (MAX_NORMAL+INFINITY)/2, so + // the number is closest to 16-max-normal. + return sign16 | IEEEBinary16::MAX_NORMAL; + } + + // nonneg32 >= (MAX_NORMAL+INFINITY)/2, so convert to 16-infinite. + return sign16 | IEEEBinary16::POS_INFINITY; + } + + if (trailing32 == 0) + { + // The number is 32-infinite. Convert to 16-infinite. + return sign16 | IEEEBinary16::POS_INFINITY; + } + + // The number is 32-NaN. Convert to 16-NaN with 16-payload the high-order + // 9 bits of the 32-payload. The code also grabs the 32-quietNaN mask + // bit. + uint16_t maskPayload = static_cast( + (trailing32 & 0x007FE000u) >> 13); + return sign16 | IEEEBinary16::EXPONENT_MASK | maskPayload; +} + +uint32_t IEEEBinary16::Convert16To32(uint16_t encoding) +{ + // Extract the channels for the IEEEBinary16 number. + uint16_t sign16 = (encoding & IEEEBinary16::SIGN_MASK); + uint16_t biased16 = ((encoding & IEEEBinary16::EXPONENT_MASK) + >> IEEEBinary16::NUM_TRAILING_BITS); + uint16_t trailing16 = (encoding & IEEEBinary16::TRAILING_MASK); + + // Generate the channels for the binary32 number. + uint32_t sign32 = static_cast(sign16 << DIFF_NUM_ENCODING_BITS); + uint32_t biased32, trailing32; + + if (biased16 == 0) + { + if (trailing16 == 0) + { + // The number is 16-zero. Convert to 32-zero. + return sign32; + } + else + { + // The number is 16-subnormal. Convert to 32-normal. + trailing32 = static_cast(trailing16); + int32_t leading = GetLeadingBit(trailing32); + int32_t shift = 23 - leading; + biased32 = static_cast(F32_EXPONENT_BIAS - 1 - shift); + trailing32 = (trailing32 << shift) & F32_TRAILING_MASK; + return sign32 | (biased32 << F32_NUM_TRAILING_BITS) | trailing32; + } + } + + if (biased16 < IEEEBinary16::MAX_BIASED_EXPONENT) + { + // The number is 16-normal. Convert to 32-normal. + biased32 = static_cast(biased16 - IEEEBinary16::EXPONENT_BIAS + + F32_EXPONENT_BIAS); + trailing32 = (static_cast( + trailing16) << DIFF_NUM_TRAILING_BITS); + return sign32 | (biased32 << F32_NUM_TRAILING_BITS) | trailing32; + } + + if (trailing16 == 0) + { + // The number is 16-infinite. Convert to 32-infinite. + return sign32 | F32_BIASED_EXPONENT_MASK; + } + + // The number is 16-NaN. Convert to 32-NaN with 16-payload embedded in + // the high-order 9 bits of the 32-payload. The code also copies the + // 16-quietNaN mask bit. + uint32_t maskPayload = + ((trailing16 & IEEEBinary16::TRAILING_MASK) << DIFF_PAYLOAD_SHIFT); + return sign32 | F32_BIASED_EXPONENT_MASK | maskPayload; +} + +IEEEBinary16 operator- (IEEEBinary16 x) +{ + uint16_t result = static_cast(x) ^ IEEEBinary16::SIGN_MASK; + return result; +} + +float operator+ (IEEEBinary16 x, IEEEBinary16 y) +{ + return static_cast(x)+static_cast(y); +} + +float operator- (IEEEBinary16 x, IEEEBinary16 y) +{ + return static_cast(x)-static_cast(y); +} + +float operator* (IEEEBinary16 x, IEEEBinary16 y) +{ + return static_cast(x)* static_cast(y); +} + +float operator/ (IEEEBinary16 x, IEEEBinary16 y) +{ + return static_cast(x) / static_cast(y); +} + +float operator+ (IEEEBinary16 x, float y) +{ + return static_cast(x)+y; +} + +float operator- (IEEEBinary16 x, float y) +{ + return static_cast(x)-y; +} + +float operator* (IEEEBinary16 x, float y) +{ + return static_cast(x)* y; +} + +float operator/ (IEEEBinary16 x, float y) +{ + return static_cast(x) / y; +} + +float operator+ (float x, IEEEBinary16 y) +{ + return x + static_cast(y); +} + +float operator- (float x, IEEEBinary16 y) +{ + return x - static_cast(y); +} + +float operator* (float x, IEEEBinary16 y) +{ + return x * static_cast(y); +} + +float operator/ (float x, IEEEBinary16 y) +{ + return x / static_cast(y); +} + +IEEEBinary16& operator+= (IEEEBinary16& x, IEEEBinary16 y) +{ + x = static_cast(x)+static_cast(y); + return x; +} + +IEEEBinary16& operator-= (IEEEBinary16& x, IEEEBinary16 y) +{ + x = static_cast(x)-static_cast(y); + return x; +} + +IEEEBinary16& operator*= (IEEEBinary16& x, IEEEBinary16 y) +{ + x = static_cast(x)* static_cast(y); + return x; +} + +IEEEBinary16& operator/= (IEEEBinary16& x, IEEEBinary16 y) +{ + x = static_cast(x) / static_cast(y); + return x; +} + +IEEEBinary16& operator+= (IEEEBinary16& x, float y) +{ + x = static_cast(x)+y; + return x; +} + +IEEEBinary16& operator-= (IEEEBinary16& x, float y) +{ + x = static_cast(x)-y; + return x; +} + +IEEEBinary16& operator*= (IEEEBinary16& x, float y) +{ + x = static_cast(x)* y; + return x; +} + +IEEEBinary16& operator/= (IEEEBinary16& x, float y) +{ + x = static_cast(x) / y; + return x; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary16.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary16.h new file mode 100644 index 000000000000..0582cfb58f56 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary16.h @@ -0,0 +1,313 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + class IEEEBinary16 : public IEEEBinary + { + public: + // Construction and destruction. The base class destructor is hidden, + // but this is safe because there are no side effects of the + // destruction. + ~IEEEBinary16(); + IEEEBinary16(); // uninitialized + IEEEBinary16(IEEEBinary16 const& object); + IEEEBinary16(float number); + IEEEBinary16(double number); + IEEEBinary16(uint16_t encoding); + + // Implicit conversions. + operator float() const; + operator double() const; + + // Assignment. + IEEEBinary16& operator=(IEEEBinary16 const& object); + + // Comparison. + bool operator==(IEEEBinary16 const& object) const; + bool operator!=(IEEEBinary16 const& object) const; + bool operator< (IEEEBinary16 const& object) const; + bool operator<=(IEEEBinary16 const& object) const; + bool operator> (IEEEBinary16 const& object) const; + bool operator>=(IEEEBinary16 const& object) const; + + private: + // Support for conversions between encodings. + enum + { + F32_NUM_ENCODING_BITS = 32, + F32_NUM_TRAILING_BITS = 23, + F32_EXPONENT_BIAS = 127, + F32_MAX_BIASED_EXPONENT = 255, + F32_SIGN_MASK = 0x80000000, + F32_NOT_SIGN_MASK = 0x7FFFFFFF, + F32_BIASED_EXPONENT_MASK = 0x7F800000, + F32_TRAILING_MASK = 0x007FFFFF, + F16_AVR_MIN_SUBNORMAL_ZERO = 0x33000000, + F16_MIN_SUBNORMAL = 0x33800000, + F16_MIN_NORMAL = 0x38800000, + F16_MAX_NORMAL = 0x477FE000, + F16_AVR_MAX_NORMAL_INFINITY = 0x477FF000, + DIFF_NUM_ENCODING_BITS = 16, + DIFF_NUM_TRAILING_BITS = 13, + DIFF_PAYLOAD_SHIFT = 13, + INT_PART_MASK = 0x007FE000, + FRC_PART_MASK = 0x00001FFF, + FRC_HALF = 0x00001000 + }; + + static uint16_t Convert32To16(uint32_t encoding); + static uint32_t Convert16To32(uint16_t encoding); + }; + + // Arithmetic operations (high-precision). + IEEEBinary16 operator-(IEEEBinary16 x); + float operator+(IEEEBinary16 x, IEEEBinary16 y); + float operator-(IEEEBinary16 x, IEEEBinary16 y); + float operator*(IEEEBinary16 x, IEEEBinary16 y); + float operator/(IEEEBinary16 x, IEEEBinary16 y); + float operator+(IEEEBinary16 x, float y); + float operator-(IEEEBinary16 x, float y); + float operator*(IEEEBinary16 x, float y); + float operator/(IEEEBinary16 x, float y); + float operator+(float x, IEEEBinary16 y); + float operator-(float x, IEEEBinary16 y); + float operator*(float x, IEEEBinary16 y); + float operator/(float x, IEEEBinary16 y); + + // Arithmetic updates. + IEEEBinary16& operator+=(IEEEBinary16& x, IEEEBinary16 y); + IEEEBinary16& operator-=(IEEEBinary16& x, IEEEBinary16 y); + IEEEBinary16& operator*=(IEEEBinary16& x, IEEEBinary16 y); + IEEEBinary16& operator/=(IEEEBinary16& x, IEEEBinary16 y); + IEEEBinary16& operator+=(IEEEBinary16& x, float y); + IEEEBinary16& operator-=(IEEEBinary16& x, float y); + IEEEBinary16& operator*=(IEEEBinary16& x, float y); + IEEEBinary16& operator/=(IEEEBinary16& x, float y); + +} + +namespace std +{ + inline gte::IEEEBinary16 abs(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::abs((float)x); + } + + inline gte::IEEEBinary16 acos(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::acos((float)x); + } + + inline gte::IEEEBinary16 acosh(gte::IEEEBinary16 x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::IEEEBinary16)0.0f; +#else + return (gte::IEEEBinary16)std::acosh((float)x); +#endif + } + + inline gte::IEEEBinary16 asin(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::asin((float)x); + } + + inline gte::IEEEBinary16 asinh(gte::IEEEBinary16 x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::IEEEBinary16)0.0f; +#else + return (gte::IEEEBinary16)std::asinh((float)x); +#endif + } + + inline gte::IEEEBinary16 atan(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::atan((float)x); + } + + inline gte::IEEEBinary16 atanh(gte::IEEEBinary16 x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::IEEEBinary16)0.0f; +#else + return (gte::IEEEBinary16)std::atanh((float)x); +#endif + } + + inline gte::IEEEBinary16 atan2(gte::IEEEBinary16 y, gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::atan2((float)y, (float)x); + } + + inline gte::IEEEBinary16 ceil(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::ceil((float)x); + } + + inline gte::IEEEBinary16 cos(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::cos((float)x); + } + + inline gte::IEEEBinary16 cosh(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::cosh((float)x); + } + + inline gte::IEEEBinary16 exp(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::exp((float)x); + } + + inline gte::IEEEBinary16 exp2(gte::IEEEBinary16 x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::IEEEBinary16)0.0f; +#else + return (gte::IEEEBinary16)std::exp2((float)x); +#endif + } + + inline gte::IEEEBinary16 floor(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::floor((float)x); + } + + inline gte::IEEEBinary16 fmod(gte::IEEEBinary16 x, gte::IEEEBinary16 y) + { + return (gte::IEEEBinary16)std::fmod((float)x, (float)y); + } + + inline gte::IEEEBinary16 frexp(gte::IEEEBinary16 x, int* exponent) + { + return (gte::IEEEBinary16)std::frexp((float)x, exponent); + } + + inline gte::IEEEBinary16 ldexp(gte::IEEEBinary16 x, int exponent) + { + return (gte::IEEEBinary16)std::ldexp((float)x, exponent); + } + + inline gte::IEEEBinary16 log(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::log((float)x); + } + + inline gte::IEEEBinary16 log2(gte::IEEEBinary16 x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::IEEEBinary16)0.0f; +#else + return (gte::IEEEBinary16)std::log2((float)x); +#endif + } + + inline gte::IEEEBinary16 log10(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::log10((float)x); + } + + inline gte::IEEEBinary16 pow(gte::IEEEBinary16 x, gte::IEEEBinary16 y) + { + return (gte::IEEEBinary16)std::pow((float)x, (float)y); + } + + inline gte::IEEEBinary16 sin(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::sin((float)x); + } + + inline gte::IEEEBinary16 sinh(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::sinh((float)x); + } + + inline gte::IEEEBinary16 sqrt(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::sqrt((float)x); + } + + inline gte::IEEEBinary16 tan(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::tan((float)x); + } + + inline gte::IEEEBinary16 tanh(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::tanh((float)x); + } +} + +namespace gte +{ + inline IEEEBinary16 atandivpi(IEEEBinary16 x) + { + return (IEEEBinary16)atandivpi((float)x); + } + + inline IEEEBinary16 atan2divpi(IEEEBinary16 y, IEEEBinary16 x) + { + return (IEEEBinary16)atan2divpi((float)y, (float)x); + } + + inline IEEEBinary16 clamp(IEEEBinary16 x, IEEEBinary16 xmin, IEEEBinary16 xmax) + { + return (IEEEBinary16)clamp((float)x, (float)xmin, (float)xmax); + } + + inline IEEEBinary16 cospi(IEEEBinary16 x) + { + return (IEEEBinary16)cospi((float)x); + } + + inline IEEEBinary16 exp10(IEEEBinary16 x) + { + return (IEEEBinary16)exp10((float)x); + } + + inline IEEEBinary16 invsqrt(IEEEBinary16 x) + { + return (IEEEBinary16)invsqrt((float)x); + } + + inline int isign(IEEEBinary16 x) + { + return isign((float)x); + } + + inline IEEEBinary16 saturate(IEEEBinary16 x) + { + return (IEEEBinary16)saturate((float)x); + } + + inline IEEEBinary16 sign(IEEEBinary16 x) + { + return (IEEEBinary16)sign((float)x); + } + + inline IEEEBinary16 sinpi(IEEEBinary16 x) + { + return (IEEEBinary16)sinpi((float)x); + } + + inline IEEEBinary16 sqr(IEEEBinary16 x) + { + return (IEEEBinary16)sqr((float)x); + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIndexAttribute.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIndexAttribute.h new file mode 100644 index 000000000000..a66f03a51cc6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIndexAttribute.h @@ -0,0 +1,102 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.3.1 (2016/11/14) + +#pragma once + +#include +#include +#include + +// The IndexAttribute class represents an array of triples of indices into a +// vertex array for an indexed triangle mesh. For now, the source must be +// either uint16_t or uint32_t. + +namespace gte +{ + +struct IndexAttribute +{ + // Construction. + inline IndexAttribute(); + inline IndexAttribute(void* inSource, size_t inSize); + + // Triangle access. + inline void SetTriangle(uint32_t t, uint32_t v0, uint32_t v1, uint32_t v2); + inline void GetTriangle(uint32_t t, uint32_t& v0, uint32_t& v1, uint32_t& v2) const; + + // The source pointer must be 4-byte aligned, which is guaranteed on + // 32-bit and 64-bit architectures. The size is the number of bytes + // per index. + void* source; + size_t size; +}; + + +inline IndexAttribute::IndexAttribute() + : + source(nullptr), + size(0) +{ +} + +inline IndexAttribute::IndexAttribute(void* inSource, size_t inSize) + : + source(inSource), + size(inSize) +{ +} + +inline void IndexAttribute::SetTriangle(uint32_t t, uint32_t v0, uint32_t v1, uint32_t v2) +{ + if (size == sizeof(uint32_t)) + { + uint32_t* index = reinterpret_cast(source) + 3 * t; + index[0] = v0; + index[1] = v1; + index[2] = v2; + return; + } + + if (size == sizeof(uint16_t)) + { + uint16_t* index = reinterpret_cast(source) + 3 * t; + index[0] = static_cast(v0); + index[1] = static_cast(v1); + index[2] = static_cast(v2); + return; + } + + // Unsupported type. +} + +inline void IndexAttribute::GetTriangle(uint32_t t, uint32_t& v0, uint32_t& v1, uint32_t& v2) const +{ + if (size == sizeof(uint32_t)) + { + uint32_t* index = reinterpret_cast(source) + 3 * t; + v0 = index[0]; + v1 = index[1]; + v2 = index[2]; + return; + } + + if (size == sizeof(uint16_t)) + { + uint16_t* index = reinterpret_cast(source) + 3 * t; + v0 = static_cast(index[0]); + v1 = static_cast(index[1]); + v2 = static_cast(index[2]); + return; + } + + // Unsupported type. + v0 = 0; + v1 = 0; + v2 = 0; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntegration.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntegration.h new file mode 100644 index 000000000000..78f515c85d69 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntegration.h @@ -0,0 +1,238 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class Integration +{ +public: + // A simple algorithm, but slow to converge as the number of samples is + // increased. The 'numSamples' needs to be two or larger. + static Real TrapezoidRule(int numSamples, Real a, Real b, + std::function const& integrand); + + // The trapezoid rule is used to generate initial estimates, but then + // Richardson extrapolation is used to improve the estimates. This is + // preferred over TrapezoidRule. The 'order' must be positive. + static Real Romberg(int order, Real a, Real b, + std::function const& integrand); + + // Gaussian quadrature estimates the integral of a function f(x) defined + // on [-1,1] using + // integral_{-1}^{1} f(t) dt = sum_{i=0}^{n-1} c[i]*f(r[i]) + // where r[i] are the roots to the Legendre polynomial p(t) of degree n + // and c[i] = integral_{-1}^{1} prod_{j=0,j!=i} (t-r[j]/(r[i]-r[j]) dt. + // To integrate over [a,b], a transformation to [-1,1] is applied + // internally: x - ((b-a)*t + (b+a))/2. The Legendre polynomials are + // generated by + // P[0](x) = 1, P[1](x) = x, + // P[k](x) = ((2*k-1)*x*P[k-1](x) - (k-1)*P[k-2](x))/k, k >= 2 + // Implementing the polynomial generation is simple, and computing the + // roots requires a numerical method for finding polynomial roots. The + // challenging task is to develop an efficient algorithm for computing + // the coefficients c[i] for a specified degree. The 'degree' must be + // two or larger. + + static void ComputeQuadratureInfo(int degree, std::vector& roots, + std::vector& coefficients); + + static Real GaussianQuadrature(std::vector const& roots, + std::vectorconst & coefficients, Real a, Real b, + std::function const& integrand); +}; + + +template +Real Integration::TrapezoidRule(int numSamples, Real a, Real b, + std::function const& integrand) +{ + Real h = (b - a) / (Real)(numSamples - 1); + Real result = ((Real)0.5) * (integrand(a) + integrand(b)); + for (int i = 1; i <= numSamples - 2; ++i) + { + result += integrand(a + i*h); + } + result *= h; + return result; +} + +template +Real Integration::Romberg(int order, Real a, Real b, + std::function const& integrand) +{ + Real const half = (Real)0.5; + std::vector> rom(order); + Real h = b - a; + rom[0][0] = half * h * (integrand(a) + integrand(b)); + for (int i0 = 2, p0 = 1; i0 <= order; ++i0, p0 *= 2, h *= half) + { + // Approximations via the trapezoid rule. + Real sum = (Real)0; + int i1; + for (i1 = 1; i1 <= p0; ++i1) + { + sum += integrand(a + h * (i1 - half)); + } + + // Richardson extrapolation. + rom[0][1] = half * (rom[0][0] + h * sum); + for (int i2 = 1, p2 = 4; i2 < i0; ++i2, p2 *= 4) + { + rom[i2][1] = (p2*rom[i2 - 1][1] - rom[i2 - 1][0]) / (p2 - 1); + } + + for (i1 = 0; i1 < i0; ++i1) + { + rom[i1][0] = rom[i1][1]; + } + } + + Real result = rom[order - 1][0]; + return result; +} + +template +void Integration::ComputeQuadratureInfo(int degree, + std::vector& roots, std::vector& coefficients) +{ + Real const zero = (Real)0; + Real const one = (Real)1; + Real const half = (Real)0.5; + + std::vector> poly(degree + 1); + + poly[0].resize(1); + poly[0][0] = one; + + poly[1].resize(2); + poly[1][0] = zero; + poly[1][1] = one; + + for (int n = 2; n <= degree; ++n) + { + Real mult0 = (Real)(n - 1) / (Real)n; + Real mult1 = (Real)(2 * n - 1) / (Real)n; + + poly[n].resize(n + 1); + poly[n][0] = -mult0 * poly[n - 2][0]; + for (int i = 1; i <= n - 2; ++i) + { + poly[n][i] = mult1 * poly[n - 1][i - 1] - mult0 * poly[n - 2][i]; + } + poly[n][n - 1] = mult1 * poly[n - 1][n - 2]; + poly[n][n] = mult1 * poly[n - 1][n - 1]; + } + + roots.resize(degree); + RootsPolynomial::Find(degree, &poly[degree][0], 2048, &roots[0]); + + coefficients.resize(roots.size()); + size_t n = roots.size() - 1; + std::vector subroots(n); + for (size_t i = 0; i < roots.size(); ++i) + { + Real denominator = (Real)1; + for (size_t j = 0, k = 0; j < roots.size(); ++j) + { + if (j != i) + { + subroots[k++] = roots[j]; + denominator *= roots[i] - roots[j]; + } + } + + std::array delta = + { + -one - subroots.back(), + +one - subroots.back() + }; + + std::vector> weights(n); + weights[0][0] = half * delta[0] * delta[0]; + weights[0][1] = half * delta[1] * delta[1]; + for (size_t k = 1; k < n; ++k) + { + Real dk = (Real)k; + Real mult = -dk / (dk + (Real)2); + weights[k][0] = mult * delta[0] * weights[k - 1][0]; + weights[k][1] = mult * delta[1] * weights[k - 1][1]; + } + + struct Info + { + int numBits; + std::array product; + }; + + int numElements = (1 << static_cast(n - 1)); + std::vector info(numElements); + info[0].numBits = 0; + info[0].product[0] = one; + info[0].product[1] = one; + for (int ipow = 1, r = 0; ipow < numElements; ipow <<= 1, ++r) + { + info[ipow].numBits = 1; + info[ipow].product[0] = -one - subroots[r]; + info[ipow].product[1] = +one - subroots[r]; + for (int m = 1, j = ipow + 1; m < ipow; ++m, ++j) + { + info[j].numBits = info[m].numBits + 1; + info[j].product[0] = + info[ipow].product[0] * info[m].product[0]; + info[j].product[1] = + info[ipow].product[1] * info[m].product[1]; + } + } + + std::vector> sum(n); + std::array zero2 = { zero, zero }; + std::fill(sum.begin(), sum.end(), zero2); + for (size_t k = 0; k < info.size(); ++k) + { + sum[info[k].numBits][0] += info[k].product[0]; + sum[info[k].numBits][1] += info[k].product[1]; + } + + std::array total = zero2; + for (size_t k = 0; k < n; ++k) + { + total[0] += weights[n - 1 - k][0] * sum[k][0]; + total[1] += weights[n - 1 - k][1] * sum[k][1]; + } + + coefficients[i] = (total[1] - total[0]) / denominator; + } +} + +template +Real Integration::GaussianQuadrature(std::vector const& roots, + std::vectorconst & coefficients, Real a, Real b, + std::function const& integrand) +{ + Real const half = (Real)0.5; + Real radius = half * (b - a); + Real center = half * (b + a); + Real result = (Real)0; + for (size_t i = 0; i < roots.size(); ++i) + { + result += coefficients[i] * integrand(radius*roots[i] + center); + } + result *= radius; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkima1.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkima1.h new file mode 100644 index 000000000000..f9f977a2cc13 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkima1.h @@ -0,0 +1,181 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class IntpAkima1 +{ +protected: + // Construction (abstract base class). + IntpAkima1(int quantity, Real const* F); +public: + // Abstract base class. + virtual ~IntpAkima1(); + + // Member access. + inline int GetQuantity() const; + inline Real const* GetF() const; + virtual Real GetXMin() const = 0; + virtual Real GetXMax() const = 0; + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax. The first operator is for function + // evaluation. The second operator is for function or derivative + // evaluations. The 'order' argument is the order of the derivative or + // zero for the function itself. + Real operator()(Real x) const; + Real operator()(int order, Real x) const; + +protected: + class Polynomial + { + public: + // P(x) = c[0] + c[1]*x + c[2]*x^2 + c[3]*x^3 + inline Real& operator[](int i); + Real operator()(Real x) const; + Real operator()(int order, Real x) const; + + private: + Real mCoeff[4]; + }; + + Real ComputeDerivative(Real* slope) const; + virtual void Lookup(Real x, int& index, Real& dx) const = 0; + + int mQuantity; + Real const* mF; + std::vector mPoly; +}; + + +template +IntpAkima1::IntpAkima1(int quantity, Real const* F) + : + mQuantity(quantity), + mF(F) +{ + // At least three data points are needed to construct the estimates of + // the boundary derivatives. + LogAssert(mQuantity >= 3 && F, "Invalid input."); + + mPoly.resize(mQuantity - 1); +} + +template +IntpAkima1::~IntpAkima1() +{ +} + +template inline +int IntpAkima1::GetQuantity() const +{ + return mQuantity; +} + +template inline +Real const* IntpAkima1::GetF() const +{ + return mF; +} + +template +Real IntpAkima1::operator()(Real x) const +{ + x = std::min(std::max(x, GetXMin()), GetXMax()); + int index; + Real dx; + Lookup(x, index, dx); + return mPoly[index](dx); +} + +template +Real IntpAkima1::operator()(int order, Real x) const +{ + x = std::min(std::max(x, GetXMin()), GetXMax()); + int index; + Real dx; + Lookup(x, index, dx); + return mPoly[index](order, dx); +} + +template +Real IntpAkima1::ComputeDerivative(Real* slope) const +{ + if (slope[1] != slope[2]) + { + if (slope[0] != slope[1]) + { + if (slope[2] != slope[3]) + { + Real ad0 = std::abs(slope[3] - slope[2]); + Real ad1 = std::abs(slope[0] - slope[1]); + return (ad0 * slope[1] + ad1 * slope[2]) / (ad0 + ad1); + } + else + { + return slope[2]; + } + } + else + { + if (slope[2] != slope[3]) + { + return slope[1]; + } + else + { + return ((Real)0.5) * (slope[1] + slope[2]); + } + } + } + else + { + return slope[1]; + } +} + +template inline +Real& IntpAkima1::Polynomial::operator[](int i) +{ + return mCoeff[i]; +} + +template +Real IntpAkima1::Polynomial::operator()(Real x) const +{ + return mCoeff[0] + x * (mCoeff[1] + x * (mCoeff[2] + x * mCoeff[3])); +} + +template +Real IntpAkima1::Polynomial::operator()(int order, Real x) const +{ + switch (order) + { + case 0: + return mCoeff[0] + x * (mCoeff[1] + x * (mCoeff[2] + x * mCoeff[3])); + case 1: + return mCoeff[1] + x * (((Real)2)*mCoeff[2] + x * ((Real)3) * mCoeff[3]); + case 2: + return ((Real)2) * mCoeff[2] + x * ((Real)6) * mCoeff[3]; + case 3: + return ((Real)6) * mCoeff[3]; + } + + return (Real)0; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaNonuniform1.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaNonuniform1.h new file mode 100644 index 000000000000..65883f1b3a49 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaNonuniform1.h @@ -0,0 +1,128 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +template +class IntpAkimaNonuniform1 : public IntpAkima1 +{ +public: + // Construction. The interpolator is for arbitrarily spaced x-values. + // The input arrays must have 'quantity' elements and the X[] array + // must store increasing values: X[i + 1] > X[i] for all i. + IntpAkimaNonuniform1(int quantity, Real const* X, Real const* F); + + // Member access. + Real const* GetX() const; + virtual Real GetXMin() const override; + virtual Real GetXMax() const override; + +protected: + virtual void Lookup(Real x, int& index, Real& dx) const override; + + Real const* mX; +}; + +template +IntpAkimaNonuniform1::IntpAkimaNonuniform1(int quantity, Real const* X, + Real const* F) + : + IntpAkima1(quantity, F), + mX(X) +{ +#if !defined(GTE_NO_LOGGER) + LogAssert(X != nullptr, "Invalid input."); + for (int j0 = 0, j1 = 1; j1 < quantity; ++j0, ++j1) + { + LogAssert(X[j1] > X[j0], "Invalid input."); + } +#endif + + // Compute slopes. + std::vector slope(quantity + 3); + int i, ip1, ip2; + for (i = 0, ip1 = 1, ip2 = 2; i < quantity - 1; ++i, ++ip1, ++ip2) + { + Real dx = X[ip1] - X[i]; + Real df = F[ip1] - F[i]; + slope[ip2] = df / dx; + } + + slope[1] = ((Real)2) * slope[2] - slope[3]; + slope[0] = ((Real)2) * slope[1] - slope[2]; + slope[quantity + 1] = ((Real)2) * slope[quantity] - slope[quantity - 1]; + slope[quantity + 2] = ((Real)2) * slope[quantity + 1] - slope[quantity]; + + // Construct derivatives. + std::vector FDer(quantity); + for (i = 0; i < quantity; ++i) + { + FDer[i] = this->ComputeDerivative(&slope[i]); + } + + // Construct polynomials. + for (i = 0, ip1 = 1; i < quantity - 1; ++i, ++ip1) + { + auto& poly = this->mPoly[i]; + + Real F0 = F[i]; + Real F1 = F[ip1]; + Real FDer0 = FDer[i]; + Real FDer1 = FDer[ip1]; + Real df = F1 - F0; + Real dx = X[ip1] - X[i]; + Real dx2 = dx * dx; + Real dx3 = dx2 * dx; + + poly[0] = F0; + poly[1] = FDer0; + poly[2] = (((Real)3) * df - dx * (FDer1 + ((Real)2) * FDer0)) / dx2; + poly[3] = (dx * (FDer0 + FDer1) - ((Real)2) * df) / dx3; + } +} + +template +Real const* IntpAkimaNonuniform1::GetX() const +{ + return mX; +} + +template +Real IntpAkimaNonuniform1::GetXMin() const +{ + return mX[0]; +} + +template +Real IntpAkimaNonuniform1::GetXMax() const +{ + return mX[this->mQuantity - 1]; +} + +template +void IntpAkimaNonuniform1::Lookup(Real x, int& index, Real& dx) const +{ + // The caller has ensured that mXMin <= x <= mXMax. + for (index = 0; index + 1 < this->mQuantity; ++index) + { + if (x < mX[index + 1]) + { + dx = x - mX[index]; + return; + } + } + + --index; + dx = x - mX[index]; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform1.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform1.h new file mode 100644 index 000000000000..62ccae41511f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform1.h @@ -0,0 +1,130 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +template +class IntpAkimaUniform1 : public IntpAkima1 +{ +public: + // Construction and destruction. The interpolator is for uniformly + // spaced x-values. + virtual ~IntpAkimaUniform1(); + IntpAkimaUniform1(int quantity, Real xMin, Real xSpacing, Real const* F); + + // Member access. + inline virtual Real GetXMin() const override; + inline virtual Real GetXMax() const override; + inline Real GetXSpacing() const; + +protected: + virtual void Lookup(Real x, int& index, Real& dx) const override; + + Real mXMin, mXMax, mXSpacing; +}; + + +template +IntpAkimaUniform1::~IntpAkimaUniform1() +{ +} + +template +IntpAkimaUniform1::IntpAkimaUniform1(int quantity, Real xMin, + Real xSpacing, Real const* F) + : + IntpAkima1(quantity, F), + mXMin(xMin), + mXSpacing(xSpacing) +{ + LogAssert(mXSpacing > (Real)0, "Spacing must be positive."); + + mXMax = mXMin + mXSpacing * static_cast(quantity - 1); + + // Compute slopes. + Real invDX = ((Real)1) / mXSpacing; + std::vector slope(quantity + 3); + int i, ip1, ip2; + for (i = 0, ip1 = 1, ip2 = 2; i < quantity - 1; ++i, ++ip1, ++ip2) + { + slope[ip2] = (this->mF[ip1] - this->mF[i]) * invDX; + } + + slope[1] = ((Real)2) * slope[2] - slope[3]; + slope[0] = ((Real)2) * slope[1] - slope[2]; + slope[quantity + 1] = ((Real)2) * slope[quantity] - slope[quantity - 1]; + slope[quantity + 2] = ((Real)2) * slope[quantity + 1] - slope[quantity]; + + // Construct derivatives. + std::vector FDer(quantity); + for (i = 0; i < quantity; ++i) + { + FDer[i] = this->ComputeDerivative(&slope[i]); + } + + // Construct polynomials. + Real invDX2 = ((Real)1) / (mXSpacing * mXSpacing); + Real invDX3 = invDX2 / mXSpacing; + for (i = 0, ip1 = 1; i < quantity - 1; ++i, ++ip1) + { + auto& poly = this->mPoly[i]; + + Real F0 = F[i]; + Real F1 = F[ip1]; + Real df = F1 - F0; + Real FDer0 = FDer[i]; + Real FDer1 = FDer[ip1]; + + poly[0] = F0; + poly[1] = FDer0; + poly[2] = (((Real)3) * df - mXSpacing * (FDer1 + ((Real)2) * FDer0)) * invDX2; + poly[3] = (mXSpacing * (FDer0 + FDer1) - ((Real)2) * df) * invDX3; + } +} + +template inline +Real IntpAkimaUniform1::GetXMin() const +{ + return mXMin; +} + +template inline +Real IntpAkimaUniform1::GetXMax() const +{ + return mXMax; +} + +template inline +Real IntpAkimaUniform1::GetXSpacing() const +{ + return mXSpacing; +} + +template +void IntpAkimaUniform1::Lookup(Real x, int& index, Real& dx) const +{ + // The caller has ensured that mXMin <= x <= mXMax. + for (index = 0; index + 1 < this->mQuantity; ++index) + { + if (x < mXMin + mXSpacing * (index + 1)) + { + dx = x - (mXMin + mXSpacing * index); + return; + } + } + + --index; + dx = x - (mXMin + mXSpacing * index); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform2.h new file mode 100644 index 000000000000..ea380b2fee90 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform2.h @@ -0,0 +1,627 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/04) + +#pragma once + +#include +#include +#include +#include +#include + +// The interpolator is for uniformly spaced (x,y)-values. The input samples +// F must be stored in row-major order to represent f(x,y); that is, +// F[c + xBound*r] corresponds to f(x,y), where c is the index corresponding +// to x and r is the index corresponding to y. + +namespace gte +{ + +template +class IntpAkimaUniform2 +{ +public: + // Construction and destruction. + ~IntpAkimaUniform2(); + IntpAkimaUniform2(int xBound, int yBound, Real xMin, Real xSpacing, + Real yMin, Real ySpacing, Real const* F); + + // Member access. + inline int GetXBound() const; + inline int GetYBound() const; + inline int GetQuantity() const; + inline Real const* GetF() const; + inline Real GetXMin() const; + inline Real GetXMax() const; + inline Real GetXSpacing() const; + inline Real GetYMin() const; + inline Real GetYMax() const; + inline Real GetYSpacing() const; + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax and ymin <= y <= ymax. The first operator + // is for function evaluation. The second operator is for function or + // derivative evaluations. The xOrder argument is the order of the + // x-derivative and the yOrder argument is the order of the y-derivative. + // Both orders are zero to get the function value itself. + Real operator()(Real x, Real y) const; + Real operator()(int xOrder, int yOrder, Real x, Real y) const; + +private: + class Polynomial + { + public: + Polynomial(); + + // P(x,y) = (1,x,x^2,x^3)*A*(1,y,y^2,y^3). The matrix term A[ix][iy] + // corresponds to the polynomial term x^{ix} y^{iy}. + Real& A(int ix, int iy); + Real operator()(Real x, Real y) const; + Real operator()(int xOrder, int yOrder, Real x, Real y) const; + + private: + Real mCoeff[4][4]; + }; + + // Support for construction. + void GetFX(Array2 const& F, Array2& FX); + void GetFY(Array2 const& F, Array2& FY); + void GetFXY(Array2 const& F, Array2& FXY); + void GetPolynomials(Array2 const& F, Array2 const& FX, + Array2 const& FY, Array2 const& FXY); + Real ComputeDerivative(Real const* slope) const; + void Construct(Polynomial& poly, Real const F[2][2], Real const FX[2][2], + Real const FY[2][2], Real const FXY[2][2]); + + // Support for evaluation. + void XLookup(Real x, int& xIndex, Real& dx) const; + void YLookup(Real y, int& yIndex, Real& dy) const; + + int mXBound, mYBound, mQuantity; + Real mXMin, mXMax, mXSpacing; + Real mYMin, mYMax, mYSpacing; + Real const* mF; + Array2 mPoly; +}; + + +template +IntpAkimaUniform2::~IntpAkimaUniform2() +{ +} + +template +IntpAkimaUniform2::IntpAkimaUniform2(int xBound, int yBound, Real xMin, + Real xSpacing, Real yMin, Real ySpacing, Real const* F) + : + mXBound(xBound), + mYBound(yBound), + mQuantity(xBound * yBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mF(F), + mPoly(xBound - 1, mYBound - 1) +{ + // At least a 3x3 block of data points is needed to construct the + // estimates of the boundary derivatives. + LogAssert(mXBound >= 3 && mYBound >= 3 && mF, "Invalid input."); + LogAssert(mXSpacing > (Real)0 && mYSpacing > (Real)0, "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + + // Create a 2D wrapper for the 1D samples. + Array2 Fmap(mXBound, mYBound, const_cast(mF)); + + // Construct first-order derivatives. + Array2 FX(mXBound, mYBound), FY(mXBound, mYBound); + GetFX(Fmap, FX); + GetFY(Fmap, FY); + + // Construct second-order derivatives. + Array2 FXY(mXBound, mYBound); + GetFXY(Fmap, FXY); + + // Construct polynomials. + GetPolynomials(Fmap, FX, FY, FXY); +} + +template inline +int IntpAkimaUniform2::GetXBound() const +{ + return mXBound; +} + +template inline +int IntpAkimaUniform2::GetYBound() const +{ + return mYBound; +} + +template inline +int IntpAkimaUniform2::GetQuantity() const +{ + return mQuantity; +} + +template inline +Real const* IntpAkimaUniform2::GetF() const +{ + return mF; +} + +template inline +Real IntpAkimaUniform2::GetXMin() const +{ + return mXMin; +} + +template inline +Real IntpAkimaUniform2::GetXMax() const +{ + return mXMax; +} + +template inline +Real IntpAkimaUniform2::GetXSpacing() const +{ + return mXSpacing; +} + +template inline +Real IntpAkimaUniform2::GetYMin() const +{ + return mYMin; +} + +template inline +Real IntpAkimaUniform2::GetYMax() const +{ + return mYMax; +} + +template inline +Real IntpAkimaUniform2::GetYSpacing() const +{ + return mYSpacing; +} + +template +Real IntpAkimaUniform2::operator()(Real x, Real y) const +{ + x = std::min(std::max(x, mXMin), mXMax); + y = std::min(std::max(y, mYMin), mYMax); + int ix, iy; + Real dx, dy; + XLookup(x, ix, dx); + YLookup(y, iy, dy); + return mPoly[iy][ix](dx, dy); +} + +template +Real IntpAkimaUniform2::operator()(int xOrder, int yOrder, Real x, + Real y) const +{ + x = std::min(std::max(x, mXMin), mXMax); + y = std::min(std::max(y, mYMin), mYMax); + int ix, iy; + Real dx, dy; + XLookup(x, ix, dx); + YLookup(y, iy, dy); + return mPoly[iy][ix](xOrder, yOrder, dx, dy); +} + +template +void IntpAkimaUniform2::GetFX(Array2 const& F, Array2& FX) +{ + Array2 slope(mXBound + 3, mYBound); + Real invDX = ((Real)1) / mXSpacing; + int ix, iy; + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound - 1; ++ix) + { + slope[iy][ix + 2] = (F[iy][ix + 1] - F[iy][ix]) * invDX; + } + + slope[iy][1] = ((Real)2) * slope[iy][2] - slope[iy][3]; + slope[iy][0] = ((Real)2) * slope[iy][1] - slope[iy][2]; + slope[iy][mXBound + 1] = ((Real)2) * slope[iy][mXBound] - slope[iy][mXBound - 1]; + slope[iy][mXBound + 2] = ((Real)2) * slope[iy][mXBound + 1] - slope[iy][mXBound]; + } + + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound; ++ix) + { + FX[iy][ix] = ComputeDerivative(slope[iy] + ix); + } + } +} + +template +void IntpAkimaUniform2::GetFY(Array2 const& F, Array2& FY) +{ + Array2 slope(mYBound + 3, mXBound); + Real invDY = ((Real)1) / mYSpacing; + int ix, iy; + for (ix = 0; ix < mXBound; ++ix) + { + for (iy = 0; iy < mYBound - 1; ++iy) + { + slope[ix][iy + 2] = (F[iy + 1][ix] - F[iy][ix]) * invDY; + } + + slope[ix][1] = ((Real)2) * slope[ix][2] - slope[ix][3]; + slope[ix][0] = ((Real)2) * slope[ix][1] - slope[ix][2]; + slope[ix][mYBound + 1] = ((Real)2) * slope[ix][mYBound] - slope[ix][mYBound - 1]; + slope[ix][mYBound + 2] = ((Real)2) * slope[ix][mYBound + 1] - slope[ix][mYBound]; + } + + for (ix = 0; ix < mXBound; ++ix) + { + for (iy = 0; iy < mYBound; ++iy) + { + FY[iy][ix] = ComputeDerivative(slope[ix] + iy); + } + } +} + +template +void IntpAkimaUniform2::GetFXY(Array2 const& F, Array2& FXY) +{ + int xBoundM1 = mXBound - 1; + int yBoundM1 = mYBound - 1; + int ix0 = xBoundM1, ix1 = ix0 - 1, ix2 = ix1 - 1; + int iy0 = yBoundM1, iy1 = iy0 - 1, iy2 = iy1 - 1; + int ix, iy; + + Real invDXDY = ((Real)1) / (mXSpacing * mYSpacing); + + // corners + FXY[0][0] = ((Real)0.25)*invDXDY*( + ((Real)9)*F[0][0] + - ((Real)12)*F[0][1] + + ((Real)3)*F[0][2] + - ((Real)12)*F[1][0] + + ((Real)16)*F[1][1] + - ((Real)4)*F[1][2] + + ((Real)3)*F[2][0] + - ((Real)4)*F[2][1] + + F[2][2]); + + FXY[0][xBoundM1] = ((Real)0.25)*invDXDY*( + ((Real)9)*F[0][ix0] + - ((Real)12)*F[0][ix1] + + ((Real)3)*F[0][ix2] + - ((Real)12)*F[1][ix0] + + ((Real)16)*F[1][ix1] + - ((Real)4)*F[1][ix2] + + ((Real)3)*F[2][ix0] + - ((Real)4)*F[2][ix1] + + F[2][ix2]); + + FXY[yBoundM1][0] = ((Real)0.25)*invDXDY*( + ((Real)9)*F[iy0][0] + - ((Real)12)*F[iy0][1] + + ((Real)3)*F[iy0][2] + - ((Real)12)*F[iy1][0] + + ((Real)16)*F[iy1][1] + - ((Real)4)*F[iy1][2] + + ((Real)3)*F[iy2][0] + - ((Real)4)*F[iy2][1] + + F[iy2][2]); + + FXY[yBoundM1][xBoundM1] = ((Real)0.25)*invDXDY*( + ((Real)9)*F[iy0][ix0] + - ((Real)12)*F[iy0][ix1] + + ((Real)3)*F[iy0][ix2] + - ((Real)12)*F[iy1][ix0] + + ((Real)16)*F[iy1][ix1] + - ((Real)4)*F[iy1][ix2] + + ((Real)3)*F[iy2][ix0] + - ((Real)4)*F[iy2][ix1] + + F[iy2][ix2]); + + // x-edges + for (ix = 1; ix < xBoundM1; ++ix) + { + FXY[0][ix] = ((Real)0.25)*invDXDY*( + ((Real)3)*(F[0][ix - 1] - F[0][ix + 1]) + - ((Real)4)*(F[1][ix - 1] - F[1][ix + 1]) + + (F[2][ix - 1] - F[2][ix + 1])); + + FXY[yBoundM1][ix] = ((Real)0.25)*invDXDY*( + ((Real)3)*(F[iy0][ix - 1] - F[iy0][ix + 1]) + - ((Real)4)*(F[iy1][ix - 1] - F[iy1][ix + 1]) + + (F[iy2][ix - 1] - F[iy2][ix + 1])); + } + + // y-edges + for (iy = 1; iy < yBoundM1; ++iy) + { + FXY[iy][0] = ((Real)0.25)*invDXDY*( + ((Real)3)*(F[iy - 1][0] - F[iy + 1][0]) + - ((Real)4)*(F[iy - 1][1] - F[iy + 1][1]) + + (F[iy - 1][2] - F[iy + 1][2])); + + FXY[iy][xBoundM1] = ((Real)0.25)*invDXDY*( + ((Real)3)*(F[iy - 1][ix0] - F[iy + 1][ix0]) + - ((Real)4)*(F[iy - 1][ix1] - F[iy + 1][ix1]) + + (F[iy - 1][ix2] - F[iy + 1][ix2])); + } + + // interior + for (iy = 1; iy < yBoundM1; ++iy) + { + for (ix = 1; ix < xBoundM1; ++ix) + { + FXY[iy][ix] = ((Real)0.25)*invDXDY*(F[iy - 1][ix - 1] - + F[iy - 1][ix + 1] - F[iy + 1][ix - 1] + F[iy + 1][ix + 1]); + } + } +} + +template +void IntpAkimaUniform2::GetPolynomials(Array2 const& F, + Array2 const& FX, Array2 const& FY, Array2 const& FXY) +{ + int xBoundM1 = mXBound - 1; + int yBoundM1 = mYBound - 1; + for (int iy = 0; iy < yBoundM1; ++iy) + { + for (int ix = 0; ix < xBoundM1; ++ix) + { + // Note the 'transposing' of the 2x2 blocks (to match notation + // used in the polynomial definition). + Real G[2][2] = + { + { F[iy][ix], F[iy + 1][ix] }, + { F[iy][ix + 1], F[iy + 1][ix + 1] } + }; + + Real GX[2][2] = + { + { FX[iy][ix], FX[iy + 1][ix] }, + { FX[iy][ix + 1], FX[iy + 1][ix + 1] } + }; + + Real GY[2][2] = + { + { FY[iy][ix], FY[iy + 1][ix] }, + { FY[iy][ix + 1], FY[iy + 1][ix + 1] } + }; + + Real GXY[2][2] = + { + { FXY[iy][ix], FXY[iy + 1][ix] }, + { FXY[iy][ix + 1], FXY[iy + 1][ix + 1] } + }; + + Construct(mPoly[iy][ix], G, GX, GY, GXY); + } + } +} + +template +Real IntpAkimaUniform2::ComputeDerivative(Real const* slope) const +{ + if (slope[1] != slope[2]) + { + if (slope[0] != slope[1]) + { + if (slope[2] != slope[3]) + { + Real ad0 = std::abs(slope[3] - slope[2]); + Real ad1 = std::abs(slope[0] - slope[1]); + return (ad0 * slope[1] + ad1 * slope[2]) / (ad0 + ad1); + } + else + { + return slope[2]; + } + } + else + { + if (slope[2] != slope[3]) + { + return slope[1]; + } + else + { + return ((Real)0.5) * (slope[1] + slope[2]); + } + } + } + else + { + return slope[1]; + } +} + +template +void IntpAkimaUniform2::Construct(Polynomial& poly, Real const F[2][2], + Real const FX[2][2], Real const FY[2][2], Real const FXY[2][2]) +{ + Real dx = mXSpacing; + Real dy = mYSpacing; + Real invDX = ((Real)1) / dx, invDX2 = invDX*invDX; + Real invDY = ((Real)1) / dy, invDY2 = invDY*invDY; + Real b0, b1, b2, b3; + + poly.A(0, 0) = F[0][0]; + poly.A(1, 0) = FX[0][0]; + poly.A(0, 1) = FY[0][0]; + poly.A(1, 1) = FXY[0][0]; + + b0 = (F[1][0] - poly(0, 0, dx, (Real)0))*invDX2; + b1 = (FX[1][0] - poly(1, 0, dx, (Real)0))*invDX; + poly.A(2, 0) = ((Real)3)*b0 - b1; + poly.A(3, 0) = (-((Real)2)*b0 + b1)*invDX; + + b0 = (F[0][1] - poly(0, 0, (Real)0, dy))*invDY2; + b1 = (FY[0][1] - poly(0, 1, (Real)0, dy))*invDY; + poly.A(0, 2) = ((Real)3)*b0 - b1; + poly.A(0, 3) = (-((Real)2)*b0 + b1)*invDY; + + b0 = (FY[1][0] - poly(0, 1, dx, (Real)0))*invDX2; + b1 = (FXY[1][0] - poly(1, 1, dx, (Real)0))*invDX; + poly.A(2, 1) = ((Real)3)*b0 - b1; + poly.A(3, 1) = (-((Real)2)*b0 + b1)*invDX; + + b0 = (FX[0][1] - poly(1, 0, (Real)0, dy))*invDY2; + b1 = (FXY[0][1] - poly(1, 1, (Real)0, dy))*invDY; + poly.A(1, 2) = ((Real)3)*b0 - b1; + poly.A(1, 3) = (-((Real)2)*b0 + b1)*invDY; + + b0 = (F[1][1] - poly(0, 0, dx, dy))*invDX2*invDY2; + b1 = (FX[1][1] - poly(1, 0, dx, dy))*invDX*invDY2; + b2 = (FY[1][1] - poly(0, 1, dx, dy))*invDX2*invDY; + b3 = (FXY[1][1] - poly(1, 1, dx, dy))*invDX*invDY; + poly.A(2, 2) = ((Real)9)*b0 - ((Real)3)*b1 - ((Real)3)*b2 + b3; + poly.A(3, 2) = (-((Real)6)*b0 + ((Real)3)*b1 + ((Real)2)*b2 - b3)*invDX; + poly.A(2, 3) = (-((Real)6)*b0 + ((Real)2)*b1 + ((Real)3)*b2 - b3)*invDY; + poly.A(3, 3) = (((Real)4)*b0 - ((Real)2)*b1 - ((Real)2)*b2 + b3)*invDX*invDY; +} + +template +void IntpAkimaUniform2::XLookup(Real x, int& xIndex, Real& dx) const +{ + for (xIndex = 0; xIndex + 1 < mXBound; ++xIndex) + { + if (x < mXMin + mXSpacing*(xIndex + 1)) + { + dx = x - (mXMin + mXSpacing*xIndex); + return; + } + } + + --xIndex; + dx = x - (mXMin + mXSpacing*xIndex); +} + +template +void IntpAkimaUniform2::YLookup(Real y, int& yIndex, Real& dy) const +{ + for (yIndex = 0; yIndex + 1 < mYBound; ++yIndex) + { + if (y < mYMin + mYSpacing*(yIndex + 1)) + { + dy = y - (mYMin + mYSpacing*yIndex); + return; + } + } + + yIndex--; + dy = y - (mYMin + mYSpacing*yIndex); +} + +template +IntpAkimaUniform2::Polynomial::Polynomial() +{ + memset(&mCoeff[0][0], 0, 16 * sizeof(Real)); +} + +template +Real& IntpAkimaUniform2::Polynomial::A(int ix, int iy) +{ + return mCoeff[ix][iy]; +} + +template +Real IntpAkimaUniform2::Polynomial::operator()(Real x, Real y) const +{ + Real B[4]; + for (int i = 0; i <= 3; ++i) + { + B[i] = mCoeff[i][0] + y*(mCoeff[i][1] + y*(mCoeff[i][2] + y*mCoeff[i][3])); + } + + return B[0] + x*(B[1] + x*(B[2] + x*B[3])); +} + +template +Real IntpAkimaUniform2::Polynomial::operator()(int xOrder, int yOrder, + Real x, Real y) const +{ + Real xPow[4]; + switch (xOrder) + { + case 0: + xPow[0] = (Real)1; + xPow[1] = x; + xPow[2] = x * x; + xPow[3] = x * x * x; + break; + case 1: + xPow[0] = (Real)0; + xPow[1] = (Real)1; + xPow[2] = ((Real)2) * x; + xPow[3] = ((Real)3) * x * x; + break; + case 2: + xPow[0] = (Real)0; + xPow[1] = (Real)0; + xPow[2] = (Real)2; + xPow[3] = ((Real)6) * x; + break; + case 3: + xPow[0] = (Real)0; + xPow[1] = (Real)0; + xPow[2] = (Real)0; + xPow[3] = (Real)6; + break; + default: + return (Real)0; + } + + Real yPow[4]; + switch (yOrder) + { + case 0: + yPow[0] = (Real)1; + yPow[1] = y; + yPow[2] = y * y; + yPow[3] = y * y * y; + break; + case 1: + yPow[0] = (Real)0; + yPow[1] = (Real)1; + yPow[2] = ((Real)2) * y; + yPow[3] = ((Real)3) * y * y; + break; + case 2: + yPow[0] = (Real)0; + yPow[1] = (Real)0; + yPow[2] = (Real)2; + yPow[3] = ((Real)6) * y; + break; + case 3: + yPow[0] = (Real)0; + yPow[1] = (Real)0; + yPow[2] = (Real)0; + yPow[3] = (Real)6; + break; + default: + return (Real)0; + } + + Real p = (Real)0; + for (int iy = 0; iy <= 3; ++iy) + { + for (int ix = 0; ix <= 3; ++ix) + { + p += mCoeff[ix][iy] * xPow[ix] * yPow[iy]; + } + } + + return p; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform3.h new file mode 100644 index 000000000000..ab3517f49261 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform3.h @@ -0,0 +1,1503 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// The interpolator is for uniformly spaced(x,y z)-values. The input samples +// must be stored in lexicographical order to represent f(x,y,z); that is, +// F[c + xBound*(r + yBound*s)] corresponds to f(x,y,z), where c is the index +// corresponding to x, r is the index corresponding to y, and s is the index +// corresponding to z. + +namespace gte +{ + +template +class IntpAkimaUniform3 +{ +public: + // Construction and destruction. + ~IntpAkimaUniform3(); + IntpAkimaUniform3(int xBound, int yBound, int zBound, Real xMin, + Real xSpacing, Real yMin, Real ySpacing, Real zMin, Real zSpacing, + Real const* F); + + // Member access. + inline int GetXBound() const; + inline int GetYBound() const; + inline int GetZBound() const; + inline int GetQuantity() const; + inline Real const* GetF() const; + inline Real GetXMin() const; + inline Real GetXMax() const; + inline Real GetXSpacing() const; + inline Real GetYMin() const; + inline Real GetYMax() const; + inline Real GetYSpacing() const; + inline Real GetZMin() const; + inline Real GetZMax() const; + inline Real GetZSpacing() const; + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax, ymin <= y <= ymax, and zmin <= z <= zmax. + // The first operator is for function evaluation. The second operator is + // for function or derivative evaluations. The xOrder argument is the + // order of the x-derivative, the yOrder argument is the order of the + // y-derivative, and the zOrder argument is the order of the z-derivative. + // All orders are zero to get the function value itself. + Real operator()(Real x, Real y, Real z) const; + Real operator()(int xOrder, int yOrder, int zOrder, Real x, Real y, Real z) const; + +private: + class Polynomial + { + public: + Polynomial(); + + // P(x,y,z) = sum_{i=0}^3 sum_{j=0}^3 sum_{k=0}^3 a_{ijk} x^i y^j z^k. + // The tensor term A[ix][iy][iz] corresponds to the polynomial term + // x^{ix} y^{iy} z^{iz}. + Real& A(int ix, int iy, int iz); + Real operator()(Real x, Real y, Real z) const; + Real operator()(int xOrder, int yOrder, int zOrder, Real x, Real y, Real z) const; + + private: + Real mCoeff[4][4][4]; + }; + + // Support for construction. + void GetFX(Array3 const& F, Array3& FX); + void GetFY(Array3 const& F, Array3& FY); + void GetFZ(Array3 const& F, Array3& FZ); + void GetFXY(Array3 const& F, Array3& FXY); + void GetFXZ(Array3 const& F, Array3& FXZ); + void GetFYZ(Array3 const& F, Array3& FYZ); + void GetFXYZ(Array3 const& F, Array3& FXYZ); + void GetPolynomials(Array3 const& F, Array3 const& FX, + Array3 const& FY, Array3 const& FZ, Array3 const& FXY, + Array3 const& FXZ, Array3 const& FYZ, Array3 const& FXYZ); + + Real ComputeDerivative(Real const* slope) const; + void Construct(Polynomial& poly, Real const F[2][2][2], + Real const FX[2][2][2], Real const FY[2][2][2], + Real const FZ[2][2][2], Real const FXY[2][2][2], + Real const FXZ[2][2][2], Real const FYZ[2][2][2], + Real const FXYZ[2][2][2]); + + void XLookup(Real x, int& xIndex, Real& dx) const; + void YLookup(Real y, int& yIndex, Real& dy) const; + void ZLookup(Real z, int& zIndex, Real& dz) const; + + int mXBound, mYBound, mZBound, mQuantity; + Real mXMin, mXMax, mXSpacing; + Real mYMin, mYMax, mYSpacing; + Real mZMin, mZMax, mZSpacing; + Real const* mF; + Array3 mPoly; +}; + + +template +IntpAkimaUniform3::~IntpAkimaUniform3() +{ +} + +template +IntpAkimaUniform3::IntpAkimaUniform3(int xBound, int yBound, int zBound, + Real xMin, Real xSpacing, Real yMin, Real ySpacing, Real zMin, + Real zSpacing, Real const* F) + : + mXBound(xBound), + mYBound(yBound), + mZBound(zBound), + mQuantity(xBound * yBound * zBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mZMin(zMin), + mZSpacing(zSpacing), + mF(F), + mPoly(xBound - 1, yBound - 1, zBound - 1) +{ + // At least a 3x3x3 block of data points is needed to construct the + // estimates of the boundary derivatives. + LogAssert(mXBound >= 3 && mYBound >= 3 && mZBound >= 3 && mF, + "Invalid input."); + LogAssert(mXSpacing > (Real)0 && mYSpacing > (Real)0 && + mZSpacing > (Real)0, "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + mZMax = mZMin + mZSpacing * static_cast(mZBound - 1); + + // Create a 3D wrapper for the 1D samples. + Array3 Fmap(mXBound, mYBound, mZBound, const_cast(mF)); + + // Construct first-order derivatives. + Array3 FX(mXBound, mYBound, mZBound); + Array3 FY(mXBound, mYBound, mZBound); + Array3 FZ(mXBound, mYBound, mZBound); + GetFX(Fmap, FX); + GetFX(Fmap, FY); + GetFX(Fmap, FZ); + + // Construct second-order derivatives. + Array3 FXY(mXBound, mYBound, mZBound); + Array3 FXZ(mXBound, mYBound, mZBound); + Array3 FYZ(mXBound, mYBound, mZBound); + GetFX(Fmap, FXY); + GetFX(Fmap, FXZ); + GetFX(Fmap, FYZ); + + // Construct third-order derivatives. + Array3 FXYZ(mXBound, mYBound, mZBound); + GetFXYZ(Fmap, FXYZ); + + // Construct polynomials. + GetPolynomials(Fmap, FX, FY, FZ, FXY, FXZ, FYZ, FXYZ); +} + +template inline +int IntpAkimaUniform3::GetXBound() const +{ + return mXBound; +} + +template inline +int IntpAkimaUniform3::GetYBound() const +{ + return mYBound; +} + +template inline +int IntpAkimaUniform3::GetZBound() const +{ + return mZBound; +} + +template inline +int IntpAkimaUniform3::GetQuantity() const +{ + return mQuantity; +} + +template inline +Real const* IntpAkimaUniform3::GetF() const +{ + return mF; +} + +template inline +Real IntpAkimaUniform3::GetXMin() const +{ + return mXMin; +} + +template inline +Real IntpAkimaUniform3::GetXMax() const +{ + return mXMax; +} + +template inline +Real IntpAkimaUniform3::GetXSpacing() const +{ + return mXSpacing; +} + +template inline +Real IntpAkimaUniform3::GetYMin() const +{ + return mYMin; +} + +template inline +Real IntpAkimaUniform3::GetYMax() const +{ + return mYMax; +} + +template inline +Real IntpAkimaUniform3::GetYSpacing() const +{ + return mYSpacing; +} + +template inline +Real IntpAkimaUniform3::GetZMin() const +{ + return mZMin; +} + +template inline +Real IntpAkimaUniform3::GetZMax() const +{ + return mZMax; +} + +template inline +Real IntpAkimaUniform3::GetZSpacing() const +{ + return mZSpacing; +} + +template +Real IntpAkimaUniform3::operator()(Real x, Real y, Real z) const +{ + x = std::min(std::max(x, mXMin), mXMax); + y = std::min(std::max(y, mYMin), mYMax); + z = std::min(std::max(z, mZMin), mZMax); + int ix, iy, iz; + Real dx, dy, dz; + XLookup(x, ix, dx); + YLookup(y, iy, dy); + ZLookup(z, iz, dz); + return mPoly[iz][iy][ix](dx, dy, dz); +} + +template +Real IntpAkimaUniform3::operator()(int xOrder, int yOrder, int zOrder, + Real x, Real y, Real z) const +{ + x = std::min(std::max(x, mXMin), mXMax); + y = std::min(std::max(y, mYMin), mYMax); + z = std::min(std::max(z, mZMin), mZMax); + int ix, iy, iz; + Real dx, dy, dz; + XLookup(x, ix, dx); + YLookup(y, iy, dy); + ZLookup(z, iz, dz); + return mPoly[iz][iy][ix](xOrder, yOrder, zOrder, dx, dy, dz); +} + +template +void IntpAkimaUniform3::GetFX(Array3 const& F, Array3& FX) +{ + Array3 slope(mXBound + 3, mYBound, mZBound); + Real invDX = ((Real)1) / mXSpacing; + int ix, iy, iz; + for (iz = 0; iz < mZBound; ++iz) + { + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound - 1; ++ix) + { + slope[iz][iy][ix + 2] = (F[iz][iy][ix + 1] - F[iz][iy][ix]) * invDX; + } + + slope[iz][iy][1] = ((Real)2) * slope[iz][iy][2] - slope[iz][iy][3]; + slope[iz][iy][0] = ((Real)2) * slope[iz][iy][1] - slope[iz][iy][2]; + slope[iz][iy][mXBound + 1] = ((Real)2) * slope[iz][iy][mXBound] - slope[iz][iy][mXBound - 1]; + slope[iz][iy][mXBound + 2] = ((Real)2) * slope[iz][iy][mXBound + 1] - slope[iz][iy][mXBound]; + } + } + + for (iz = 0; iz < mZBound; ++iz) + { + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound; ++ix) + { + FX[iz][iy][ix] = ComputeDerivative(slope[iz][iy] + ix); + } + } + } +} + +template +void IntpAkimaUniform3::GetFY(Array3 const& F, Array3& FY) +{ + Array3 slope(mYBound + 3, mXBound, mZBound); + Real invDY = ((Real)1) / mYSpacing; + int ix, iy, iz; + for (iz = 0; iz < mZBound; ++iz) + { + for (ix = 0; ix < mXBound; ++ix) + { + for (iy = 0; iy < mYBound - 1; ++iy) + { + slope[iz][ix][iy + 2] = (F[iz][iy + 1][ix] - F[iz][iy][ix]) * invDY; + } + + slope[iz][ix][1] = ((Real)2) * slope[iz][ix][2] - slope[iz][ix][3]; + slope[iz][ix][0] = ((Real)2) * slope[iz][ix][1] - slope[iz][ix][2]; + slope[iz][ix][mYBound + 1] = ((Real)2) * slope[iz][ix][mYBound] - slope[iz][ix][mYBound - 1]; + slope[iz][ix][mYBound + 2] = ((Real)2) * slope[iz][ix][mYBound + 1] - slope[iz][ix][mYBound]; + } + } + + for (iz = 0; iz < mZBound; ++iz) + { + for (ix = 0; ix < mXBound; ++ix) + { + for (iy = 0; iy < mYBound; ++iy) + { + FY[iz][iy][ix] = ComputeDerivative(slope[iz][ix] + iy); + } + } + } +} + +template +void IntpAkimaUniform3::GetFZ(Array3 const& F, Array3& FZ) +{ + Array3 slope(mZBound + 3, mXBound, mYBound); + Real invDZ = ((Real)1) / mZSpacing; + int ix, iy, iz; + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound; ++ix) + { + for (iz = 0; iz < mZBound - 1; ++iz) + { + slope[iy][ix][iz + 2] = (F[iz + 1][iy][ix] - F[iz][iy][ix]) * invDZ; + } + + slope[iy][ix][1] = ((Real)2) * slope[iy][ix][2] - slope[iy][ix][3]; + slope[iy][ix][0] = ((Real)2) * slope[iy][ix][1] - slope[iy][ix][2]; + slope[iy][ix][mZBound + 1] = ((Real)2) * slope[iy][ix][mZBound] - slope[iy][ix][mZBound - 1]; + slope[iy][ix][mZBound + 2] = ((Real)2) * slope[iy][ix][mZBound + 1] - slope[iy][ix][mZBound]; + } + } + + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound; ++ix) + { + for (iz = 0; iz < mZBound; ++iz) + { + FZ[iz][iy][ix] = ComputeDerivative(slope[iy][ix] + iz); + } + } + } +} + +template +void IntpAkimaUniform3::GetFXY(Array3 const& F, Array3& FXY) +{ + int xBoundM1 = mXBound - 1; + int yBoundM1 = mYBound - 1; + int ix0 = xBoundM1, ix1 = ix0 - 1, ix2 = ix1 - 1; + int iy0 = yBoundM1, iy1 = iy0 - 1, iy2 = iy1 - 1; + int ix, iy, iz; + + Real invDXDY = ((Real)1) / (mXSpacing * mYSpacing); + for (iz = 0; iz < mZBound; ++iz) + { + // corners of z-slice + FXY[iz][0][0] = ((Real)0.25)*invDXDY*( + ((Real)9)*F[iz][0][0] + - ((Real)12)*F[iz][0][1] + + ((Real)3)*F[iz][0][2] + - ((Real)12)*F[iz][1][0] + + ((Real)16)*F[iz][1][1] + - ((Real)4)*F[iz][1][2] + + ((Real)3)*F[iz][2][0] + - ((Real)4)*F[iz][2][1] + + F[iz][2][2]); + + FXY[iz][0][xBoundM1] = ((Real)0.25)*invDXDY*( + ((Real)9)*F[iz][0][ix0] + - ((Real)12)*F[iz][0][ix1] + + ((Real)3)*F[iz][0][ix2] + - ((Real)12)*F[iz][1][ix0] + + ((Real)16)*F[iz][1][ix1] + - ((Real)4)*F[iz][1][ix2] + + ((Real)3)*F[iz][2][ix0] + - ((Real)4)*F[iz][2][ix1] + + F[iz][2][ix2]); + + FXY[iz][yBoundM1][0] = ((Real)0.25)*invDXDY*( + ((Real)9)*F[iz][iy0][0] + - ((Real)12)*F[iz][iy0][1] + + ((Real)3)*F[iz][iy0][2] + - ((Real)12)*F[iz][iy1][0] + + ((Real)16)*F[iz][iy1][1] + - ((Real)4)*F[iz][iy1][2] + + ((Real)3)*F[iz][iy2][0] + - ((Real)4)*F[iz][iy2][1] + + F[iz][iy2][2]); + + FXY[iz][yBoundM1][xBoundM1] = ((Real)0.25)*invDXDY*( + ((Real)9)*F[iz][iy0][ix0] + - ((Real)12)*F[iz][iy0][ix1] + + ((Real)3)*F[iz][iy0][ix2] + - ((Real)12)*F[iz][iy1][ix0] + + ((Real)16)*F[iz][iy1][ix1] + - ((Real)4)*F[iz][iy1][ix2] + + ((Real)3)*F[iz][iy2][ix0] + - ((Real)4)*F[iz][iy2][ix1] + + F[iz][iy2][ix2]); + + // x-edges of z-slice + for (ix = 1; ix < xBoundM1; ++ix) + { + FXY[iz][0][ix] = ((Real)0.25)*invDXDY*( + ((Real)3)*(F[iz][0][ix - 1] - F[iz][0][ix + 1]) - + ((Real)4)*(F[iz][1][ix - 1] - F[iz][1][ix + 1]) + + (F[iz][2][ix - 1] - F[iz][2][ix + 1])); + + FXY[iz][yBoundM1][ix] = ((Real)0.25)*invDXDY*( + ((Real)3)*(F[iz][iy0][ix - 1] - F[iz][iy0][ix + 1]) + - ((Real)4)*(F[iz][iy1][ix - 1] - F[iz][iy1][ix + 1]) + + (F[iz][iy2][ix - 1] - F[iz][iy2][ix + 1])); + } + + // y-edges of z-slice + for (iy = 1; iy < yBoundM1; ++iy) + { + FXY[iz][iy][0] = ((Real)0.25)*invDXDY*( + ((Real)3)*(F[iz][iy - 1][0] - F[iz][iy + 1][0]) - + ((Real)4)*(F[iz][iy - 1][1] - F[iz][iy + 1][1]) + + (F[iz][iy - 1][2] - F[iz][iy + 1][2])); + + FXY[iz][iy][xBoundM1] = ((Real)0.25)*invDXDY*( + ((Real)3)*(F[iz][iy - 1][ix0] - F[iz][iy + 1][ix0]) + - ((Real)4)*(F[iz][iy - 1][ix1] - F[iz][iy + 1][ix1]) + + (F[iz][iy - 1][ix2] - F[iz][iy + 1][ix2])); + } + + // interior of z-slice + for (iy = 1; iy < yBoundM1; ++iy) + { + for (ix = 1; ix < xBoundM1; ++ix) + { + FXY[iz][iy][ix] = ((Real)0.25)*invDXDY*( + F[iz][iy - 1][ix - 1] - F[iz][iy - 1][ix + 1] - + F[iz][iy + 1][ix - 1] + F[iz][iy + 1][ix + 1]); + } + } + } +} + +template +void IntpAkimaUniform3::GetFXZ(Array3 const& F, Array3& FXZ) +{ + int xBoundM1 = mXBound - 1; + int zBoundM1 = mZBound - 1; + int ix0 = xBoundM1, ix1 = ix0 - 1, ix2 = ix1 - 1; + int iz0 = zBoundM1, iz1 = iz0 - 1, iz2 = iz1 - 1; + int ix, iy, iz; + + Real invDXDZ = ((Real)1) / (mXSpacing * mZSpacing); + for (iy = 0; iy < mYBound; ++iy) + { + // corners of z-slice + FXZ[0][iy][0] = ((Real)0.25)*invDXDZ*( + ((Real)9)*F[0][iy][0] + - ((Real)12)*F[0][iy][1] + + ((Real)3)*F[0][iy][2] + - ((Real)12)*F[1][iy][0] + + ((Real)16)*F[1][iy][1] + - ((Real)4)*F[1][iy][2] + + ((Real)3)*F[2][iy][0] + - ((Real)4)*F[2][iy][1] + + F[2][iy][2]); + + FXZ[0][iy][xBoundM1] = ((Real)0.25)*invDXDZ*( + ((Real)9)*F[0][iy][ix0] + - ((Real)12)*F[0][iy][ix1] + + ((Real)3)*F[0][iy][ix2] + - ((Real)12)*F[1][iy][ix0] + + ((Real)16)*F[1][iy][ix1] + - ((Real)4)*F[1][iy][ix2] + + ((Real)3)*F[2][iy][ix0] + - ((Real)4)*F[2][iy][ix1] + + F[2][iy][ix2]); + + FXZ[zBoundM1][iy][0] = ((Real)0.25)*invDXDZ*( + ((Real)9)*F[iz0][iy][0] + - ((Real)12)*F[iz0][iy][1] + + ((Real)3)*F[iz0][iy][2] + - ((Real)12)*F[iz1][iy][0] + + ((Real)16)*F[iz1][iy][1] + - ((Real)4)*F[iz1][iy][2] + + ((Real)3)*F[iz2][iy][0] + - ((Real)4)*F[iz2][iy][1] + + F[iz2][iy][2]); + + FXZ[zBoundM1][iy][xBoundM1] = ((Real)0.25)*invDXDZ*( + ((Real)9)*F[iz0][iy][ix0] + - ((Real)12)*F[iz0][iy][ix1] + + ((Real)3)*F[iz0][iy][ix2] + - ((Real)12)*F[iz1][iy][ix0] + + ((Real)16)*F[iz1][iy][ix1] + - ((Real)4)*F[iz1][iy][ix2] + + ((Real)3)*F[iz2][iy][ix0] + - ((Real)4)*F[iz2][iy][ix1] + + F[iz2][iy][ix2]); + + // x-edges of y-slice + for (ix = 1; ix < xBoundM1; ++ix) + { + FXZ[0][iy][ix] = ((Real)0.25)*invDXDZ*( + ((Real)3)*(F[0][iy][ix - 1] - F[0][iy][ix + 1]) - + ((Real)4)*(F[1][iy][ix - 1] - F[1][iy][ix + 1]) + + (F[2][iy][ix - 1] - F[2][iy][ix + 1])); + + FXZ[zBoundM1][iy][ix] = ((Real)0.25)*invDXDZ*( + ((Real)3)*(F[iz0][iy][ix - 1] - F[iz0][iy][ix + 1]) + - ((Real)4)*(F[iz1][iy][ix - 1] - F[iz1][iy][ix + 1]) + + (F[iz2][iy][ix - 1] - F[iz2][iy][ix + 1])); + } + + // z-edges of y-slice + for (iz = 1; iz < zBoundM1; ++iz) + { + FXZ[iz][iy][0] = ((Real)0.25)*invDXDZ*( + ((Real)3)*(F[iz - 1][iy][0] - F[iz + 1][iy][0]) - + ((Real)4)*(F[iz - 1][iy][1] - F[iz + 1][iy][1]) + + (F[iz - 1][iy][2] - F[iz + 1][iy][2])); + + FXZ[iz][iy][xBoundM1] = ((Real)0.25)*invDXDZ*( + ((Real)3)*(F[iz - 1][iy][ix0] - F[iz + 1][iy][ix0]) + - ((Real)4)*(F[iz - 1][iy][ix1] - F[iz + 1][iy][ix1]) + + (F[iz - 1][iy][ix2] - F[iz + 1][iy][ix2])); + } + + // interior of y-slice + for (iz = 1; iz < zBoundM1; ++iz) + { + for (ix = 1; ix < xBoundM1; ++ix) + { + FXZ[iz][iy][ix] = ((Real)0.25)*invDXDZ*( + F[iz - 1][iy][ix - 1] - F[iz - 1][iy][ix + 1] - + F[iz + 1][iy][ix - 1] + F[iz + 1][iy][ix + 1]); + } + } + } +} + +template +void IntpAkimaUniform3::GetFYZ(Array3 const& F, Array3& FYZ) +{ + int yBoundM1 = mYBound - 1; + int zBoundM1 = mZBound - 1; + int iy0 = yBoundM1, iy1 = iy0 - 1, iy2 = iy1 - 1; + int iz0 = zBoundM1, iz1 = iz0 - 1, iz2 = iz1 - 1; + int ix, iy, iz; + + Real invDYDZ = ((Real)1) / (mYSpacing * mZSpacing); + for (ix = 0; ix < mXBound; ++ix) + { + // corners of x-slice + FYZ[0][0][ix] = ((Real)0.25)*invDYDZ*( + ((Real)9)*F[0][0][ix] + - ((Real)12)*F[0][1][ix] + + ((Real)3)*F[0][2][ix] + - ((Real)12)*F[1][0][ix] + + ((Real)16)*F[1][1][ix] + - ((Real)4)*F[1][2][ix] + + ((Real)3)*F[2][0][ix] + - ((Real)4)*F[2][1][ix] + + F[2][2][ix]); + + FYZ[0][yBoundM1][ix] = ((Real)0.25)*invDYDZ*( + ((Real)9)*F[0][iy0][ix] + - ((Real)12)*F[0][iy1][ix] + + ((Real)3)*F[0][iy2][ix] + - ((Real)12)*F[1][iy0][ix] + + ((Real)16)*F[1][iy1][ix] + - ((Real)4)*F[1][iy2][ix] + + ((Real)3)*F[2][iy0][ix] + - ((Real)4)*F[2][iy1][ix] + + F[2][iy2][ix]); + + FYZ[zBoundM1][0][ix] = ((Real)0.25)*invDYDZ*( + ((Real)9)*F[iz0][0][ix] + - ((Real)12)*F[iz0][1][ix] + + ((Real)3)*F[iz0][2][ix] + - ((Real)12)*F[iz1][0][ix] + + ((Real)16)*F[iz1][1][ix] + - ((Real)4)*F[iz1][2][ix] + + ((Real)3)*F[iz2][0][ix] + - ((Real)4)*F[iz2][1][ix] + + F[iz2][2][ix]); + + FYZ[zBoundM1][yBoundM1][ix] = ((Real)0.25)*invDYDZ*( + ((Real)9)*F[iz0][iy0][ix] + - ((Real)12)*F[iz0][iy1][ix] + + ((Real)3)*F[iz0][iy2][ix] + - ((Real)12)*F[iz1][iy0][ix] + + ((Real)16)*F[iz1][iy1][ix] + - ((Real)4)*F[iz1][iy2][ix] + + ((Real)3)*F[iz2][iy0][ix] + - ((Real)4)*F[iz2][iy1][ix] + + F[iz2][iy2][ix]); + + // y-edges of x-slice + for (iy = 1; iy < yBoundM1; ++iy) + { + FYZ[0][iy][ix] = ((Real)0.25)*invDYDZ*( + ((Real)3)*(F[0][iy - 1][ix] - F[0][iy + 1][ix]) - + ((Real)4)*(F[1][iy - 1][ix] - F[1][iy + 1][ix]) + + (F[2][iy - 1][ix] - F[2][iy + 1][ix])); + + FYZ[zBoundM1][iy][ix] = ((Real)0.25)*invDYDZ*( + ((Real)3)*(F[iz0][iy - 1][ix] - F[iz0][iy + 1][ix]) + - ((Real)4)*(F[iz1][iy - 1][ix] - F[iz1][iy + 1][ix]) + + (F[iz2][iy - 1][ix] - F[iz2][iy + 1][ix])); + } + + // z-edges of x-slice + for (iz = 1; iz < zBoundM1; ++iz) + { + FYZ[iz][0][ix] = ((Real)0.25)*invDYDZ*( + ((Real)3)*(F[iz - 1][0][ix] - F[iz + 1][0][ix]) - + ((Real)4)*(F[iz - 1][1][ix] - F[iz + 1][1][ix]) + + (F[iz - 1][2][ix] - F[iz + 1][2][ix])); + + FYZ[iz][yBoundM1][ix] = ((Real)0.25)*invDYDZ*( + ((Real)3)*(F[iz - 1][iy0][ix] - F[iz + 1][iy0][ix]) + - ((Real)4)*(F[iz - 1][iy1][ix] - F[iz + 1][iy1][ix]) + + (F[iz - 1][iy2][ix] - F[iz + 1][iy2][ix])); + } + + // interior of x-slice + for (iz = 1; iz < zBoundM1; ++iz) + { + for (iy = 1; iy < yBoundM1; ++iy) + { + FYZ[iz][iy][ix] = ((Real)0.25)*invDYDZ*( + F[iz - 1][iy - 1][ix] - F[iz - 1][iy + 1][ix] - + F[iz + 1][iy - 1][ix] + F[iz + 1][iy + 1][ix]); + } + } + } +} + +template +void IntpAkimaUniform3::GetFXYZ(Array3 const& F, Array3& FXYZ) +{ + int xBoundM1 = mXBound - 1; + int yBoundM1 = mYBound - 1; + int zBoundM1 = mZBound - 1; + int ix, iy, iz, ix0, iy0, iz0; + + Real invDXDYDZ = ((Real)1) / (mXSpacing * mYSpacing * mZSpacing); + + // convolution masks + // centered difference, O(h^2) + Real CDer[3] = { -(Real)0.5, (Real)0, (Real)0.5 }; + // one-sided difference, O(h^2) + Real ODer[3] = { -(Real)1.5, (Real)2, -(Real)0.5 }; + Real mask; + + // corners + FXYZ[0][0][0] = (Real)0; + FXYZ[0][0][xBoundM1] = (Real)0; + FXYZ[0][yBoundM1][0] = (Real)0; + FXYZ[0][yBoundM1][xBoundM1] = (Real)0; + FXYZ[zBoundM1][0][0] = (Real)0; + FXYZ[zBoundM1][0][xBoundM1] = (Real)0; + FXYZ[zBoundM1][yBoundM1][0] = (Real)0; + FXYZ[zBoundM1][yBoundM1][xBoundM1] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ*ODer[ix] * ODer[iy] * ODer[iz]; + FXYZ[0][0][0] += mask * F[iz][iy][ix]; + FXYZ[0][0][xBoundM1] += mask * F[iz][iy][xBoundM1 - ix]; + FXYZ[0][yBoundM1][0] += mask * F[iz][yBoundM1 - iy][ix]; + FXYZ[0][yBoundM1][xBoundM1] += mask * F[iz][yBoundM1 - iy][xBoundM1 - ix]; + FXYZ[zBoundM1][0][0] += mask * F[zBoundM1 - iz][iy][ix]; + FXYZ[zBoundM1][0][xBoundM1] += mask * F[zBoundM1 - iz][iy][xBoundM1 - ix]; + FXYZ[zBoundM1][yBoundM1][0] += mask * F[zBoundM1 - iz][yBoundM1 - iy][ix]; + FXYZ[zBoundM1][yBoundM1][xBoundM1] += mask * F[zBoundM1 - iz][yBoundM1 - iy][xBoundM1 - ix]; + } + } + } + + // x-edges + for (ix0 = 1; ix0 < xBoundM1; ++ix0) + { + FXYZ[0][0][ix0] = (Real)0; + FXYZ[0][yBoundM1][ix0] = (Real)0; + FXYZ[zBoundM1][0][ix0] = (Real)0; + FXYZ[zBoundM1][yBoundM1][ix0] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ*CDer[ix] * ODer[iy] * ODer[iz]; + FXYZ[0][0][ix0] += mask * F[iz][iy][ix0 + ix - 1]; + FXYZ[0][yBoundM1][ix0] += mask * F[iz][yBoundM1 - iy][ix0 + ix - 1]; + FXYZ[zBoundM1][0][ix0] += mask * F[zBoundM1 - iz][iy][ix0 + ix - 1]; + FXYZ[zBoundM1][yBoundM1][ix0] += mask * F[zBoundM1 - iz][yBoundM1 - iy][ix0 + ix - 1]; + } + } + } + } + + // y-edges + for (iy0 = 1; iy0 < yBoundM1; ++iy0) + { + FXYZ[0][iy0][0] = (Real)0; + FXYZ[0][iy0][xBoundM1] = (Real)0; + FXYZ[zBoundM1][iy0][0] = (Real)0; + FXYZ[zBoundM1][iy0][xBoundM1] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ*ODer[ix] * CDer[iy] * ODer[iz]; + FXYZ[0][iy0][0] += mask * F[iz][iy0 + iy - 1][ix]; + FXYZ[0][iy0][xBoundM1] += mask * F[iz][iy0 + iy - 1][xBoundM1 - ix]; + FXYZ[zBoundM1][iy0][0] += mask * F[zBoundM1 - iz][iy0 + iy - 1][ix]; + FXYZ[zBoundM1][iy0][xBoundM1] += mask * F[zBoundM1 - iz][iy0 + iy - 1][xBoundM1 - ix]; + } + } + } + } + + // z-edges + for (iz0 = 1; iz0 < zBoundM1; ++iz0) + { + FXYZ[iz0][0][0] = (Real)0; + FXYZ[iz0][0][xBoundM1] = (Real)0; + FXYZ[iz0][yBoundM1][0] = (Real)0; + FXYZ[iz0][yBoundM1][xBoundM1] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ*ODer[ix] * ODer[iy] * CDer[iz]; + FXYZ[iz0][0][0] += mask * F[iz0 + iz - 1][iy][ix]; + FXYZ[iz0][0][xBoundM1] += mask * F[iz0 + iz - 1][iy][xBoundM1 - ix]; + FXYZ[iz0][yBoundM1][0] += mask * F[iz0 + iz - 1][yBoundM1 - iy][ix]; + FXYZ[iz0][yBoundM1][xBoundM1] += mask * F[iz0 + iz - 1][yBoundM1 - iy][xBoundM1 - ix]; + } + } + } + } + + // xy-faces + for (iy0 = 1; iy0 < yBoundM1; ++iy0) + { + for (ix0 = 1; ix0 < xBoundM1; ++ix0) + { + FXYZ[0][iy0][ix0] = (Real)0; + FXYZ[zBoundM1][iy0][ix0] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ*CDer[ix] * CDer[iy] * ODer[iz]; + FXYZ[0][iy0][ix0] += mask * F[iz][iy0 + iy - 1][ix0 + ix - 1]; + FXYZ[zBoundM1][iy0][ix0] += mask * F[zBoundM1 - iz][iy0 + iy - 1][ix0 + ix - 1]; + } + } + } + } + } + + // xz-faces + for (iz0 = 1; iz0 < zBoundM1; ++iz0) + { + for (ix0 = 1; ix0 < xBoundM1; ++ix0) + { + FXYZ[iz0][0][ix0] = (Real)0; + FXYZ[iz0][yBoundM1][ix0] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ*CDer[ix] * ODer[iy] * CDer[iz]; + FXYZ[iz0][0][ix0] += mask * F[iz0 + iz - 1][iy][ix0 + ix - 1]; + FXYZ[iz0][yBoundM1][ix0] += mask * F[iz0 + iz - 1][yBoundM1 - iy][ix0 + ix - 1]; + } + } + } + } + } + + // yz-faces + for (iz0 = 1; iz0 < zBoundM1; ++iz0) + { + for (iy0 = 1; iy0 < yBoundM1; ++iy0) + { + FXYZ[iz0][iy0][0] = (Real)0; + FXYZ[iz0][iy0][xBoundM1] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ*ODer[ix] * CDer[iy] * CDer[iz]; + FXYZ[iz0][iy0][0] += mask * F[iz0 + iz - 1][iy0 + iy - 1][ix]; + FXYZ[iz0][iy0][xBoundM1] += mask * F[iz0 + iz - 1][iy0 + iy - 1][xBoundM1 - ix]; + } + } + } + } + } + + // interiors + for (iz0 = 1; iz0 < zBoundM1; ++iz0) + { + for (iy0 = 1; iy0 < yBoundM1; ++iy0) + { + for (ix0 = 1; ix0 < xBoundM1; ++ix0) + { + FXYZ[iz0][iy0][ix0] = (Real)0; + + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ*CDer[ix] * CDer[iy] * CDer[iz]; + FXYZ[iz0][iy0][ix0] += mask * F[iz0 + iz - 1][iy0 + iy - 1][ix0 + ix - 1]; + } + } + } + } + } + } +} + +template +void IntpAkimaUniform3::GetPolynomials(Array3 const& F, Array3 const& FX, + Array3 const& FY, Array3 const& FZ, Array3 const& FXY, + Array3 const& FXZ, Array3 const& FYZ, Array3 const& FXYZ) +{ + int xBoundM1 = mXBound - 1; + int yBoundM1 = mYBound - 1; + int zBoundM1 = mZBound - 1; + for (int iz = 0; iz < zBoundM1; ++iz) + { + for (int iy = 0; iy < yBoundM1; ++iy) + { + for (int ix = 0; ix < xBoundM1; ++ix) + { + // Note the 'transposing' of the 2x2x2 blocks (to match + // notation used in the polynomial definition). + Real G[2][2][2] = + { + { + { + F[iz][iy][ix], + F[iz + 1][iy][ix] + }, + { + F[iz][iy + 1][ix], + F[iz + 1][iy + 1][ix] + } + }, + { + { + F[iz][iy][ix + 1], + F[iz + 1][iy][ix + 1] + }, + { + F[iz][iy + 1][ix + 1], + F[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GX[2][2][2] = + { + { + { + FX[iz][iy][ix], + FX[iz + 1][iy][ix] + }, + { + FX[iz][iy + 1][ix], + FX[iz + 1][iy + 1][ix] + } + }, + { + { + FX[iz][iy][ix + 1], + FX[iz + 1][iy][ix + 1] + }, + { + FX[iz][iy + 1][ix + 1], + FX[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GY[2][2][2] = + { + { + { + FY[iz][iy][ix], + FY[iz + 1][iy][ix] + }, + { + FY[iz][iy + 1][ix], + FY[iz + 1][iy + 1][ix] + } + }, + { + { + FY[iz][iy][ix + 1], + FY[iz + 1][iy][ix + 1] + }, + { + FY[iz][iy + 1][ix + 1], + FY[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GZ[2][2][2] = + { + { + { + FZ[iz][iy][ix], + FZ[iz + 1][iy][ix] + }, + { + FZ[iz][iy + 1][ix], + FZ[iz + 1][iy + 1][ix] + } + }, + { + { + FZ[iz][iy][ix + 1], + FZ[iz + 1][iy][ix + 1] + }, + { + FZ[iz][iy + 1][ix + 1], + FZ[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GXY[2][2][2] = + { + { + { + FXY[iz][iy][ix], + FXY[iz + 1][iy][ix] + }, + { + FXY[iz][iy + 1][ix], + FXY[iz + 1][iy + 1][ix] + } + }, + { + { + FXY[iz][iy][ix + 1], + FXY[iz + 1][iy][ix + 1] + }, + { + FXY[iz][iy + 1][ix + 1], + FXY[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GXZ[2][2][2] = + { + { + { + FXZ[iz][iy][ix], + FXZ[iz + 1][iy][ix] + }, + { + FXZ[iz][iy + 1][ix], + FXZ[iz + 1][iy + 1][ix] + } + }, + { + { + FXZ[iz][iy][ix + 1], + FXZ[iz + 1][iy][ix + 1] + }, + { + FXZ[iz][iy + 1][ix + 1], + FXZ[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GYZ[2][2][2] = + { + { + { + FYZ[iz][iy][ix], + FYZ[iz + 1][iy][ix] + }, + { + FYZ[iz][iy + 1][ix], + FYZ[iz + 1][iy + 1][ix] + } + }, + { + { + FYZ[iz][iy][ix + 1], + FYZ[iz + 1][iy][ix + 1] + }, + { + FYZ[iz][iy + 1][ix + 1], + FYZ[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GXYZ[2][2][2] = + { + { + { + FXYZ[iz][iy][ix], + FXYZ[iz + 1][iy][ix] + }, + { + FXYZ[iz][iy + 1][ix], + FXYZ[iz + 1][iy + 1][ix] + } + }, + { + { + FXYZ[iz][iy][ix + 1], + FXYZ[iz + 1][iy][ix + 1] + }, + { + FXYZ[iz][iy + 1][ix + 1], + FXYZ[iz + 1][iy + 1][ix + 1] + } + } + }; + + Construct(mPoly[iz][iy][ix], G, GX, GY, GZ, GXY, GXZ, GYZ, GXYZ); + } + } + } +} + +template +Real IntpAkimaUniform3::ComputeDerivative(Real const* slope) const +{ + if (slope[1] != slope[2]) + { + if (slope[0] != slope[1]) + { + if (slope[2] != slope[3]) + { + Real ad0 = std::abs(slope[3] - slope[2]); + Real ad1 = std::abs(slope[0] - slope[1]); + return (ad0 * slope[1] + ad1 * slope[2]) / (ad0 + ad1); + } + else + { + return slope[2]; + } + } + else + { + if (slope[2] != slope[3]) + { + return slope[1]; + } + else + { + return ((Real)0.5) * (slope[1] + slope[2]); + } + } + } + else + { + return slope[1]; + } +} + +template +void IntpAkimaUniform3::Construct(Polynomial& poly, + Real const F[2][2][2], Real const FX[2][2][2], Real const FY[2][2][2], + Real const FZ[2][2][2], Real const FXY[2][2][2], Real const FXZ[2][2][2], + Real const FYZ[2][2][2], Real const FXYZ[2][2][2]) +{ + Real dx = mXSpacing, dy = mYSpacing, dz = mZSpacing; + Real invDX = ((Real)1) / dx, invDX2 = invDX * invDX; + Real invDY = ((Real)1) / dy, invDY2 = invDY * invDY; + Real invDZ = ((Real)1) / dz, invDZ2 = invDZ * invDZ; + Real b0, b1, b2, b3, b4, b5, b6, b7; + + poly.A(0, 0, 0) = F[0][0][0]; + poly.A(1, 0, 0) = FX[0][0][0]; + poly.A(0, 1, 0) = FY[0][0][0]; + poly.A(0, 0, 1) = FZ[0][0][0]; + poly.A(1, 1, 0) = FXY[0][0][0]; + poly.A(1, 0, 1) = FXZ[0][0][0]; + poly.A(0, 1, 1) = FYZ[0][0][0]; + poly.A(1, 1, 1) = FXYZ[0][0][0]; + + // solve for Aij0 + b0 = (F[1][0][0] - poly(0, 0, 0, dx, (Real)0, (Real)0))*invDX2; + b1 = (FX[1][0][0] - poly(1, 0, 0, dx, (Real)0, (Real)0))*invDX; + poly.A(2, 0, 0) = ((Real)3)*b0 - b1; + poly.A(3, 0, 0) = (-((Real)2)*b0 + b1)*invDX; + + b0 = (F[0][1][0] - poly(0, 0, 0, (Real)0, dy, (Real)0))*invDY2; + b1 = (FY[0][1][0] - poly(0, 1, 0, (Real)0, dy, (Real)0))*invDY; + poly.A(0, 2, 0) = ((Real)3)*b0 - b1; + poly.A(0, 3, 0) = (-((Real)2)*b0 + b1)*invDY; + + b0 = (FY[1][0][0] - poly(0, 1, 0, dx, (Real)0, (Real)0))*invDX2; + b1 = (FXY[1][0][0] - poly(1, 1, 0, dx, (Real)0, (Real)0))*invDX; + poly.A(2, 1, 0) = ((Real)3)*b0 - b1; + poly.A(3, 1, 0) = (-((Real)2)*b0 + b1)*invDX; + + b0 = (FX[0][1][0] - poly(1, 0, 0, (Real)0, dy, (Real)0))*invDY2; + b1 = (FXY[0][1][0] - poly(1, 1, 0, (Real)0, dy, (Real)0))*invDY; + poly.A(1, 2, 0) = ((Real)3)*b0 - b1; + poly.A(1, 3, 0) = (-((Real)2)*b0 + b1)*invDY; + + b0 = (F[1][1][0] - poly(0, 0, 0, dx, dy, (Real)0))*invDX2*invDY2; + b1 = (FX[1][1][0] - poly(1, 0, 0, dx, dy, (Real)0))*invDX*invDY2; + b2 = (FY[1][1][0] - poly(0, 1, 0, dx, dy, (Real)0))*invDX2*invDY; + b3 = (FXY[1][1][0] - poly(1, 1, 0, dx, dy, (Real)0))*invDX*invDY; + poly.A(2, 2, 0) = ((Real)9)*b0 - ((Real)3)*b1 - ((Real)3)*b2 + b3; + poly.A(3, 2, 0) = (-((Real)6)*b0 + ((Real)3)*b1 + ((Real)2)*b2 - b3)*invDX; + poly.A(2, 3, 0) = (-((Real)6)*b0 + ((Real)2)*b1 + ((Real)3)*b2 - b3)*invDY; + poly.A(3, 3, 0) = (((Real)4)*b0 - ((Real)2)*b1 - ((Real)2)*b2 + b3)*invDX*invDY; + + // solve for Ai0k + b0 = (F[0][0][1] - poly(0, 0, 0, (Real)0, (Real)0, dz))*invDZ2; + b1 = (FZ[0][0][1] - poly(0, 0, 1, (Real)0, (Real)0, dz))*invDZ; + poly.A(0, 0, 2) = ((Real)3)*b0 - b1; + poly.A(0, 0, 3) = (-((Real)2)*b0 + b1)*invDZ; + + b0 = (FZ[1][0][0] - poly(0, 0, 1, dx, (Real)0, (Real)0))*invDX2; + b1 = (FXZ[1][0][0] - poly(1, 0, 1, dx, (Real)0, (Real)0))*invDX; + poly.A(2, 0, 1) = ((Real)3)*b0 - b1; + poly.A(3, 0, 1) = (-((Real)2)*b0 + b1)*invDX; + + b0 = (FX[0][0][1] - poly(1, 0, 0, (Real)0, (Real)0, dz))*invDZ2; + b1 = (FXZ[0][0][1] - poly(1, 0, 1, (Real)0, (Real)0, dz))*invDZ; + poly.A(1, 0, 2) = ((Real)3)*b0 - b1; + poly.A(1, 0, 3) = (-((Real)2)*b0 + b1)*invDZ; + + b0 = (F[1][0][1] - poly(0, 0, 0, dx, (Real)0, dz))*invDX2*invDZ2; + b1 = (FX[1][0][1] - poly(1, 0, 0, dx, (Real)0, dz))*invDX*invDZ2; + b2 = (FZ[1][0][1] - poly(0, 0, 1, dx, (Real)0, dz))*invDX2*invDZ; + b3 = (FXZ[1][0][1] - poly(1, 0, 1, dx, (Real)0, dz))*invDX*invDZ; + poly.A(2, 0, 2) = ((Real)9)*b0 - ((Real)3)*b1 - ((Real)3)*b2 + b3; + poly.A(3, 0, 2) = (-((Real)6)*b0 + ((Real)3)*b1 + ((Real)2)*b2 - b3)*invDX; + poly.A(2, 0, 3) = (-((Real)6)*b0 + ((Real)2)*b1 + ((Real)3)*b2 - b3)*invDZ; + poly.A(3, 0, 3) = (((Real)4)*b0 - ((Real)2)*b1 - ((Real)2)*b2 + b3)*invDX*invDZ; + + // solve for A0jk + b0 = (FZ[0][1][0] - poly(0, 0, 1, (Real)0, dy, (Real)0))*invDY2; + b1 = (FYZ[0][1][0] - poly(0, 1, 1, (Real)0, dy, (Real)0))*invDY; + poly.A(0, 2, 1) = ((Real)3)*b0 - b1; + poly.A(0, 3, 1) = (-((Real)2)*b0 + b1)*invDY; + + b0 = (FY[0][0][1] - poly(0, 1, 0, (Real)0, (Real)0, dz))*invDZ2; + b1 = (FYZ[0][0][1] - poly(0, 1, 1, (Real)0, (Real)0, dz))*invDZ; + poly.A(0, 1, 2) = ((Real)3)*b0 - b1; + poly.A(0, 1, 3) = (-((Real)2)*b0 + b1)*invDZ; + + b0 = (F[0][1][1] - poly(0, 0, 0, (Real)0, dy, dz))*invDY2*invDZ2; + b1 = (FY[0][1][1] - poly(0, 1, 0, (Real)0, dy, dz))*invDY*invDZ2; + b2 = (FZ[0][1][1] - poly(0, 0, 1, (Real)0, dy, dz))*invDY2*invDZ; + b3 = (FYZ[0][1][1] - poly(0, 1, 1, (Real)0, dy, dz))*invDY*invDZ; + poly.A(0, 2, 2) = ((Real)9)*b0 - ((Real)3)*b1 - ((Real)3)*b2 + b3; + poly.A(0, 3, 2) = (-((Real)6)*b0 + ((Real)3)*b1 + ((Real)2)*b2 - b3)*invDY; + poly.A(0, 2, 3) = (-((Real)6)*b0 + ((Real)2)*b1 + ((Real)3)*b2 - b3)*invDZ; + poly.A(0, 3, 3) = (((Real)4)*b0 - ((Real)2)*b1 - ((Real)2)*b2 + b3)*invDY*invDZ; + + // solve for Aij1 + b0 = (FYZ[1][0][0] - poly(0, 1, 1, dx, (Real)0, (Real)0))*invDX2; + b1 = (FXYZ[1][0][0] - poly(1, 1, 1, dx, (Real)0, (Real)0))*invDX; + poly.A(2, 1, 1) = ((Real)3)*b0 - b1; + poly.A(3, 1, 1) = (-((Real)2)*b0 + b1)*invDX; + + b0 = (FXZ[0][1][0] - poly(1, 0, 1, (Real)0, dy, (Real)0))*invDY2; + b1 = (FXYZ[0][1][0] - poly(1, 1, 1, (Real)0, dy, (Real)0))*invDY; + poly.A(1, 2, 1) = ((Real)3)*b0 - b1; + poly.A(1, 3, 1) = (-((Real)2)*b0 + b1)*invDY; + + b0 = (FZ[1][1][0] - poly(0, 0, 1, dx, dy, (Real)0))*invDX2*invDY2; + b1 = (FXZ[1][1][0] - poly(1, 0, 1, dx, dy, (Real)0))*invDX*invDY2; + b2 = (FYZ[1][1][0] - poly(0, 1, 1, dx, dy, (Real)0))*invDX2*invDY; + b3 = (FXYZ[1][1][0] - poly(1, 1, 1, dx, dy, (Real)0))*invDX*invDY; + poly.A(2, 2, 1) = ((Real)9)*b0 - ((Real)3)*b1 - ((Real)3)*b2 + b3; + poly.A(3, 2, 1) = (-((Real)6)*b0 + ((Real)3)*b1 + ((Real)2)*b2 - b3)*invDX; + poly.A(2, 3, 1) = (-((Real)6)*b0 + ((Real)2)*b1 + ((Real)3)*b2 - b3)*invDY; + poly.A(3, 3, 1) = (((Real)4)*b0 - ((Real)2)*b1 - ((Real)2)*b2 + b3)*invDX*invDY; + + // solve for Ai1k + b0 = (FXY[0][0][1] - poly(1, 1, 0, (Real)0, (Real)0, dz))*invDZ2; + b1 = (FXYZ[0][0][1] - poly(1, 1, 1, (Real)0, (Real)0, dz))*invDZ; + poly.A(1, 1, 2) = ((Real)3)*b0 - b1; + poly.A(1, 1, 3) = (-((Real)2)*b0 + b1)*invDZ; + + b0 = (FY[1][0][1] - poly(0, 1, 0, dx, (Real)0, dz))*invDX2*invDZ2; + b1 = (FXY[1][0][1] - poly(1, 1, 0, dx, (Real)0, dz))*invDX*invDZ2; + b2 = (FYZ[1][0][1] - poly(0, 1, 1, dx, (Real)0, dz))*invDX2*invDZ; + b3 = (FXYZ[1][0][1] - poly(1, 1, 1, dx, (Real)0, dz))*invDX*invDZ; + poly.A(2, 1, 2) = ((Real)9)*b0 - ((Real)3)*b1 - ((Real)3)*b2 + b3; + poly.A(3, 1, 2) = (-((Real)6)*b0 + ((Real)3)*b1 + ((Real)2)*b2 - b3)*invDX; + poly.A(2, 1, 3) = (-((Real)6)*b0 + ((Real)2)*b1 + ((Real)3)*b2 - b3)*invDZ; + poly.A(3, 1, 3) = (((Real)4)*b0 - ((Real)2)*b1 - ((Real)2)*b2 + b3)*invDX*invDZ; + + // solve for A1jk + b0 = (FX[0][1][1] - poly(1, 0, 0, (Real)0, dy, dz))*invDY2*invDZ2; + b1 = (FXY[0][1][1] - poly(1, 1, 0, (Real)0, dy, dz))*invDY*invDZ2; + b2 = (FXZ[0][1][1] - poly(1, 0, 1, (Real)0, dy, dz))*invDY2*invDZ; + b3 = (FXYZ[0][1][1] - poly(1, 1, 1, (Real)0, dy, dz))*invDY*invDZ; + poly.A(1, 2, 2) = ((Real)9)*b0 - ((Real)3)*b1 - ((Real)3)*b2 + b3; + poly.A(1, 3, 2) = (-((Real)6)*b0 + ((Real)3)*b1 + ((Real)2)*b2 - b3)*invDY; + poly.A(1, 2, 3) = (-((Real)6)*b0 + ((Real)2)*b1 + ((Real)3)*b2 - b3)*invDZ; + poly.A(1, 3, 3) = (((Real)4)*b0 - ((Real)2)*b1 - ((Real)2)*b2 + b3)*invDY*invDZ; + + // solve for remaining Aijk with i >= 2, j >= 2, k >= 2 + b0 = (F[1][1][1] - poly(0, 0, 0, dx, dy, dz))*invDX2*invDY2*invDZ2; + b1 = (FX[1][1][1] - poly(1, 0, 0, dx, dy, dz))*invDX*invDY2*invDZ2; + b2 = (FY[1][1][1] - poly(0, 1, 0, dx, dy, dz))*invDX2*invDY*invDZ2; + b3 = (FZ[1][1][1] - poly(0, 0, 1, dx, dy, dz))*invDX2*invDY2*invDZ; + b4 = (FXY[1][1][1] - poly(1, 1, 0, dx, dy, dz))*invDX*invDY*invDZ2; + b5 = (FXZ[1][1][1] - poly(1, 0, 1, dx, dy, dz))*invDX*invDY2*invDZ; + b6 = (FYZ[1][1][1] - poly(0, 1, 1, dx, dy, dz))*invDX2*invDY*invDZ; + b7 = (FXYZ[1][1][1] - poly(1, 1, 1, dx, dy, dz))*invDX*invDY*invDZ; + poly.A(2, 2, 2) = ((Real)27)*b0 - ((Real)9)*b1 - ((Real)9)*b2 - + ((Real)9)*b3 + ((Real)3)*b4 + ((Real)3)*b5 + ((Real)3)*b6 - b7; + poly.A(3, 2, 2) = (((Real)-18)*b0 + ((Real)9)*b1 + ((Real)6)*b2 + + ((Real)6)*b3 - ((Real)3)*b4 - ((Real)3)*b5 - ((Real)2)*b6 + b7)*invDX; + poly.A(2, 3, 2) = (((Real)-18)*b0 + ((Real)6)*b1 + ((Real)9)*b2 + + ((Real)6)*b3 - ((Real)3)*b4 - ((Real)2)*b5 - ((Real)3)*b6 + b7)*invDY; + poly.A(2, 2, 3) = (((Real)-18)*b0 + ((Real)6)*b1 + ((Real)6)*b2 + + ((Real)9)*b3 - ((Real)2)*b4 - ((Real)3)*b5 - ((Real)3)*b6 + b7)*invDZ; + poly.A(3, 3, 2) = (((Real)12)*b0 - ((Real)6)*b1 - ((Real)6)*b2 - + ((Real)4)*b3 + ((Real)3)*b4 + ((Real)2)*b5 + ((Real)2)*b6 - b7)* + invDX*invDY; + poly.A(3, 2, 3) = (((Real)12)*b0 - ((Real)6)*b1 - ((Real)4)*b2 - + ((Real)6)*b3 + ((Real)2)*b4 + ((Real)3)*b5 + ((Real)2)*b6 - b7)* + invDX*invDZ; + poly.A(2, 3, 3) = (((Real)12)*b0 - ((Real)4)*b1 - ((Real)6)*b2 - + ((Real)6)*b3 + ((Real)2)*b4 + ((Real)2)*b5 + ((Real)3)*b6 - b7)* + invDY*invDZ; + poly.A(3, 3, 3) = (((Real)-8)*b0 + ((Real)4)*b1 + ((Real)4)*b2 + + ((Real)4)*b3 - ((Real)2)*b4 - ((Real)2)*b5 - ((Real)2)*b6 + b7)* + invDX*invDY*invDZ; +} + +template +void IntpAkimaUniform3::XLookup(Real x, int& xIndex, Real& dx) const +{ + for (xIndex = 0; xIndex + 1 < mXBound; ++xIndex) + { + if (x < mXMin + mXSpacing*(xIndex + 1)) + { + dx = x - (mXMin + mXSpacing*xIndex); + return; + } + } + + --xIndex; + dx = x - (mXMin + mXSpacing*xIndex); +} + +template +void IntpAkimaUniform3::YLookup(Real y, int& yIndex, Real& dy) const +{ + for (yIndex = 0; yIndex + 1 < mYBound; ++yIndex) + { + if (y < mYMin + mYSpacing*(yIndex + 1)) + { + dy = y - (mYMin + mYSpacing*yIndex); + return; + } + } + + --yIndex; + dy = y - (mYMin + mYSpacing*yIndex); +} + +template +void IntpAkimaUniform3::ZLookup(Real z, int& zIndex, Real& dz) const +{ + for (zIndex = 0; zIndex + 1 < mZBound; ++zIndex) + { + if (z < mZMin + mZSpacing*(zIndex + 1)) + { + dz = z - (mZMin + mZSpacing*zIndex); + return; + } + } + + --zIndex; + dz = z - (mZMin + mZSpacing*zIndex); +} + +template +IntpAkimaUniform3::Polynomial::Polynomial() +{ + memset(&mCoeff[0][0][0], 0, 64 * sizeof(Real)); +} + +template +Real& IntpAkimaUniform3::Polynomial::A(int ix, int iy, int iz) +{ + return mCoeff[ix][iy][iz]; +} + +template +Real IntpAkimaUniform3::Polynomial::operator()(Real x, Real y, Real z) +const +{ + Real xPow[4] = { (Real)1, x, x * x, x * x * x }; + Real yPow[4] = { (Real)1, y, y * y, y * y * y }; + Real zPow[4] = { (Real)1, z, z * z, z * z * z }; + + Real p = (Real)0; + for (int iz = 0; iz <= 3; ++iz) + { + for (int iy = 0; iy <= 3; ++iy) + { + for (int ix = 0; ix <= 3; ++ix) + { + p += mCoeff[ix][iy][iz] * xPow[ix] * yPow[iy] * zPow[iz]; + } + } + } + + return p; +} + +template +Real IntpAkimaUniform3::Polynomial::operator()(int xOrder, int yOrder, + int zOrder, Real x, Real y, Real z) const +{ + Real xPow[4]; + switch (xOrder) + { + case 0: + xPow[0] = (Real)1; + xPow[1] = x; + xPow[2] = x * x; + xPow[3] = x * x * x; + break; + case 1: + xPow[0] = (Real)0; + xPow[1] = (Real)1; + xPow[2] = ((Real)2) * x; + xPow[3] = ((Real)3) * x * x; + break; + case 2: + xPow[0] = (Real)0; + xPow[1] = (Real)0; + xPow[2] = (Real)2; + xPow[3] = ((Real)6) * x; + break; + case 3: + xPow[0] = (Real)0; + xPow[1] = (Real)0; + xPow[2] = (Real)0; + xPow[3] = (Real)6; + break; + default: + return (Real)0; + } + + Real yPow[4]; + switch (yOrder) + { + case 0: + yPow[0] = (Real)1; + yPow[1] = y; + yPow[2] = y * y; + yPow[3] = y * y * y; + break; + case 1: + yPow[0] = (Real)0; + yPow[1] = (Real)1; + yPow[2] = ((Real)2) * y; + yPow[3] = ((Real)3) * y * y; + break; + case 2: + yPow[0] = (Real)0; + yPow[1] = (Real)0; + yPow[2] = (Real)2; + yPow[3] = ((Real)6) * y; + break; + case 3: + yPow[0] = (Real)0; + yPow[1] = (Real)0; + yPow[2] = (Real)0; + yPow[3] = (Real)6; + break; + default: + return (Real)0; + } + + Real zPow[4]; + switch (zOrder) + { + case 0: + zPow[0] = (Real)1; + zPow[1] = z; + zPow[2] = z * z; + zPow[3] = z * z * z; + break; + case 1: + zPow[0] = (Real)0; + zPow[1] = (Real)1; + zPow[2] = ((Real)2) * z; + zPow[3] = ((Real)3) * z * z; + break; + case 2: + zPow[0] = (Real)0; + zPow[1] = (Real)0; + zPow[2] = (Real)2; + zPow[3] = ((Real)6) * z; + break; + case 3: + zPow[0] = (Real)0; + zPow[1] = (Real)0; + zPow[2] = (Real)0; + zPow[3] = (Real)6; + break; + default: + return (Real)0; + } + + Real p = (Real)0; + + for (int iz = 0; iz <= 3; ++iz) + { + for (int iy = 0; iy <= 3; ++iy) + { + for (int ix = 0; ix <= 3; ++ix) + { + p += mCoeff[ix][iy][iz] * xPow[ix] * yPow[iy] * zPow[iz]; + } + } + } + + return p; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBSplineUniform.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBSplineUniform.h new file mode 100644 index 000000000000..bc8204b961ac --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBSplineUniform.h @@ -0,0 +1,1455 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.13.2 (2018/10/05) + +#pragma once + +#include +#include +#include + +// IntpBSplineUniform is the class for B-spline interpolation of uniformly +// spaced N-dimensional data. The algorithm is described in +// https://www.geometrictools.com/Documentation/BSplineInterpolation.pdf +// A sample application for this topic is +// GeometricTools/GTEngine/Samples/Imagics/BSplineInterpolation +// +// The Controls adapter allows access to your control points without regard +// to how you organize your data. You can even defer the computation of a +// control point until it is needed via the operator()(...) calls that +// Controls must provide, and you can cache the points according to your own +// needs. The minimal interface for a Controls adapter is +// +// struct Controls +// { +// // The control_point_type is of your choosing. It must support +// // assignment, scalar multiplication and addition. Specifically, if +// // C0, C1 and C2 are control points and s is a scalar, the interpolator +// // needs to perform operations +// // C1 = C0; +// // C1 = C0 * s; +// // C2 = C0 + C1; +// typedef control_point_type Type; +// +// // The number of elements in the specified dimension. +// int GetSize(int dimension) const; +// +// // Get a control point based on an n-tuple lookup. The interpolator +// // does not need to know your organization; all it needs is the +// // desired control point. The 'tuple' input must have n elements. +// Type operator() (int const* tuple) const; +// +// // If you choose to use the specialized interpolators for dimensions +// // 1, 2 or 3, you must also provide the following accessor, where +// // the input is an n-tuple listed component-by-component (1, 2 or 3 +// // components). +// Type operator() (int i0, int i1, ..., int inm1) const; + +namespace gte +{ + template + class IntpBSplineUniformShared + { + protected: + // Abstract base class construction. A virtual destructor is not + // provided because there are no required side effects in the base + // class when destroying objects from the derived classes. + IntpBSplineUniformShared(int numDimensions, int const* degrees, + Controls const& controls, typename Controls::Type ctZero, int cacheMode) + : + mNumDimensions(numDimensions), + mDegree(numDimensions), + mControls(&controls), + mCTZero(ctZero), + mCacheMode(cacheMode), + mNumLocalControls(0), + mDegreeP1(numDimensions), + mNumControls(numDimensions), + mTMin(numDimensions), + mTMax(numDimensions), + mBlender(numDimensions), + mDCoefficient(numDimensions), + mLMax(numDimensions), + mPowerDSDT(numDimensions), + mITuple(numDimensions), + mJTuple(numDimensions), + mKTuple(numDimensions), + mLTuple(numDimensions), + mSumIJTuple(numDimensions), + mUTuple(numDimensions), + mPTuple(numDimensions), + mValid(IsValid(degrees)) + { + if (!mValid) + { + return; + } + + mNumLocalControls = 1; + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mDegree[dim] = degrees[dim]; + mDegreeP1[dim] = degrees[dim] + 1; + mNumLocalControls *= mDegreeP1[dim]; + mNumControls[dim] = controls.GetSize(dim); + mTMin[dim] = (Real)-0.5; + mTMax[dim] = static_cast(mNumControls[dim]) - (Real)0.5; + ComputeBlendingMatrix(mDegree[dim], mBlender[dim]); + ComputeDCoefficients(mDegree[dim], mDCoefficient[dim], mLMax[dim]); + ComputePowers(mDegree[dim], mNumControls[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim]); + } + + if (mCacheMode == NO_CACHING) + { + mPhi.resize(mNumDimensions); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mPhi[dim].resize(mDegreeP1[dim]); + } + } + else + { + InitializeTensors(); + } + } + +#if !defined(GTE_INTP_BSPLINE_UNIFORM_NO_SPECIALIZATION) + IntpBSplineUniformShared() + : + mNumDimensions(0), + mControls(nullptr), + mCTZero(), + mCacheMode(0), + mNumLocalControls(0) + { + } +#endif + + public: + // Support for caching the intermediate tensor product of control + // points with the blending matrices. A precached container has all + // elements precomputed before any Evaluate(...) calls. The 'bool' + // flags are all set to 'true'. A cached container fills the elements + // on demand. The 'bool' flags are initially 'false', indicating the + // EvalType component has not yet been computed. After it is computed + // and stored, the flag is set to 'true'. + enum + { + NO_CACHING, + PRE_CACHING, + ON_DEMAND_CACHING + }; + + // Member access. + inline int GetDegree(int dim) const { return mDegree[dim]; } + inline int GetNumControls(int dim) const { return mNumControls[dim]; } + inline Real GetTMin(int dim) const { return mTMin[dim]; } + inline Real GetTMax(int dim) const { return mTMax[dim]; } + inline int GetCacheMode() const { return mCacheMode; } + + protected: + // Disallow copying and moving. + IntpBSplineUniformShared(IntpBSplineUniformShared const&) = delete; + IntpBSplineUniformShared& operator=(IntpBSplineUniformShared const&) = delete; + IntpBSplineUniformShared(IntpBSplineUniformShared&&) = delete; + IntpBSplineUniformShared& operator=(IntpBSplineUniformShared&&) = delete; + + // Verify that the constraints for number of controls and degrees + // are satisfied. + bool IsValid(int const* degrees) + { + // The condition c+1 > d+1 is required so that when s = c+1-d, its + // maximum value, we have at least two s-knots (d and d + 1). + for (int dim = 0; dim < mNumDimensions; ++dim) + { + if (mControls->GetSize(dim) <= degrees[dim] + 1) + { + LogError("Incompatible degree and number of controls."); + return false; + } + } + return true; + } + + // ComputeBlendingMatrix, ComputeDCoefficients, ComputePowers and + // GetKey are used by the general-dimension derived classes and by + // the specializations for dimensions 1, 2 and 3. + + // Compute the blending matrix that combines the control points and + // the polynomial vector. The matrix A is stored in row-major order. + static void ComputeBlendingMatrix(int degree, std::vector& A) + { + int const degreeP1 = degree + 1; + A.resize(degreeP1 * degreeP1); + + if (degree == 0) + { + A[0] = (Real)1; + return; + } + + // P_{0,0}(s) + std::vector> P(degreeP1); + P[0][0] = (Real)1; + + // L0 = s/j + Polynomial1 L0(1); + L0[0] = (Real)0; + + // L1(s) = (j + 1 - s)/j + Polynomial1 L1(1); + + // s-1 is used in computing translated P_{j-1,k-1}(s-1) + Polynomial1 sm1 = { (Real)-1, (Real)1 }; + + // Compute + // P_{j,k}(s) = L0(s)*P_{j-1,k}(s) + L1(s)*P_{j-1,k-1}(s-1) + // for 0 <= k <= j where 1 <= j <= degree. When k = 0, + // P_{j-1,-1}(s) = 0, so P_{j,0}(s) = L0(s)*P_{j-1,0}(s). When + // k = j, P_{j-1,j}(s) = 0, so P_{j,j}(s) = L1(s)*P_{j-1,j-1}(s). + // The polynomials at level j-1 are currently stored in P[0] + // through P[j-1]. The polynomials at level j are computed and + // stored in P[0] through P[j]; that is, they are computed in + // place to reduce memory usage and copying. This requires + // computing P[k] (level j) from P[k] (level j-1) and P[k-1] + // (level j-1), which means we have to process k = j down to + // k = 0. + for (int j = 1; j <= degree; ++j) + { + Real invJ = (Real)1 / (Real)j; + L0[1] = invJ; + L1[0] = (Real)1 + invJ; + L1[1] = -invJ; + + for (int k = j; k >= 0; --k) + { + Polynomial1 result = { (Real)0 }; + + if (k > 0) + { + result += L1 * P[k - 1].GetTranslation((Real)1); + } + + if (k < j) + { + result += L0 * P[k]; + } + + P[k] = result; + } + } + + // Compute Q_{d,k}(s) = P_{d,k}(s + k). + std::vector> Q(degreeP1); + for (int k = 0; k <= degree; ++k) + { + Q[k] = P[k].GetTranslation(static_cast(-k)); + } + + // Extract the matrix A from the Q-polynomials. Row r of A + // contains the coefficients of Q_{d,d-r}(s). + for (int k = 0, row = degree; k <= degree; ++k, --row) + { + for (int col = 0; col <= degree; ++col) + { + A[col + degreeP1 * row] = Q[k][col]; + } + } + } + + // Compute the coefficients for the derivative polynomial terms. + static void ComputeDCoefficients(int degree, std::vector& dCoefficients, + std::vector& ellMax) + { + int numDCoefficients = (degree + 1) * (degree + 2) / 2; + dCoefficients.resize(numDCoefficients); + for (int i = 0; i < numDCoefficients; ++i) + { + dCoefficients[i] = 1; + } + + for (int order = 1, col0 = 0, col1 = degree + 1; order <= degree; ++order) + { + ++col0; + for (int c = order, m = 1; c <= degree; ++c, ++m, ++col0, ++col1) + { + dCoefficients[col1] = dCoefficients[col0] * m; + } + } + + ellMax.resize(degree + 1); + ellMax[0] = degree; + for (int i0 = 0, i1 = 1; i1 <= degree; i0 = i1++) + { + ellMax[i1] = ellMax[i0] + degree - i0; + } + } + + // Compute powers of ds/dt. + void ComputePowers(int degree, int numControls, Real tmin, Real tmax, + std::vector& powerDSDT) + { + Real dsdt = static_cast(numControls - degree) / (tmax - tmin); + powerDSDT.resize(degree + 1); + powerDSDT[0] = (Real)1; + powerDSDT[1] = dsdt; + for (int i = 2; i <= degree; ++i) + { + powerDSDT[i] = powerDSDT[i - 1] * dsdt; + } + } + + // Determine the interval [index,index+1) corresponding to the + // specified value of t and compute u in that interval. + static void GetKey(Real t, Real tmin, Real tmax, Real dsdt, int numControls, + int degree, int& index, Real& u) + { + // Compute s - d = ((c + 1 - d)/(c + 1))(t + 1/2), the index for + // which d + index <= s < d + index + 1. Let u = s - d - index so + // that 0 <= u < 1. + if (t > tmin) + { + if (t < tmax) + { + Real smd = dsdt * (t - tmin); + index = static_cast(std::floor(smd)); + u = smd - static_cast(index); + } + else + { + // In the evaluation, s = c + 1 - d and i = c - d. This + // causes s-d-i to be 1 in G_c(c+1-d). Effectively, the + // selection of i extends the s-domain [d,c+1) to its + // support [d,c+1]. + index = numControls - 1 - degree; + u = (Real)1; + } + } + else + { + index = 0; + u = (Real)0; + } + } + + // The remaining functions are used only by the general-dimension + // derived classes when caching is enabled. + + // For the multidimensional tensor Phi{iTuple, kTuple), compute the + // portion of the 1-dimensional index that corresponds to iTuple. + int GetRowIndex(std::vector const& i) const + { + int rowIndex = i[mNumDimensions - 1]; + int j1 = 2 * mNumDimensions - 2; + for (int j0 = mNumDimensions - 2; j0 >= 0; --j0, --j1) + { + rowIndex = mTBound[j1] * rowIndex + i[j0]; + } + rowIndex = mTBound[j1] * rowIndex; + return rowIndex; + } + + // For the multidimensional tensor Phi{iTuple, kTuple), combine the + // GetRowIndex(...) output with kTuple to produce the full + // 1-dimensional index. + int GetIndex(int rowIndex, std::vector const& k) const + { + int index = rowIndex + k[mNumDimensions - 1]; + for (int j = mNumDimensions - 2; j >= 0; --j) + { + index = mTBound[j] * index + k[j]; + } + return index; + } + + // Compute Phi(iTuple, kTuple). The 'index' value is an already + // computed 1-dimensional index for the tensor. + void ComputeTensor(int const* i, int const* k, int index) + { + auto element = mCTZero; + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mComputeJTuple[dim] = 0; + } + for (int iterate = 0; iterate < mNumLocalControls; ++iterate) + { + Real blend(1); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + blend *= mBlender[dim][k[dim] + mDegreeP1[dim] * mComputeJTuple[dim]]; + mComputeSumIJTuple[dim] = i[dim] + mComputeJTuple[dim]; + } + element = element + (*mControls)(mComputeSumIJTuple.data()) * blend; + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + if (++mComputeJTuple[dim] < mDegreeP1[dim]) + { + break; + } + mComputeJTuple[dim] = 0; + } + } + mTensor[index] = element; + } + + // Allocate the containers used for caching and fill in the tensor + // for precaching when that mode is selected. + void InitializeTensors() + { + mTBound.resize(2 * mNumDimensions); + mComputeJTuple.resize(mNumDimensions); + mComputeSumIJTuple.resize(mNumDimensions); + mDegreeMinusOrder.resize(mNumDimensions); + mTerm.resize(mNumDimensions); + + int current = 0; + int numCached = 1; + for (int dim = 0; dim < mNumDimensions; ++dim, ++current) + { + mTBound[current] = mDegreeP1[dim]; + numCached *= mTBound[current]; + } + for (int dim = 0; dim < mNumDimensions; ++dim, ++current) + { + mTBound[current] = mNumControls[dim] - mDegree[dim]; + numCached *= mTBound[current]; + } + mTensor.resize(numCached); + mCached.resize(numCached); + if (mCacheMode == PRE_CACHING) + { + std::vector tuple(2 * mNumDimensions, 0); + for (int index = 0; index < numCached; ++index) + { + ComputeTensor(&tuple[mNumDimensions], &tuple[0], index); + for (int i = 0; i < 2 * mNumDimensions; ++i) + { + if (++tuple[i] < mTBound[i]) + { + break; + } + tuple[i] = 0; + } + } + std::fill(mCached.begin(), mCached.end(), true); + } + else + { + std::fill(mCached.begin(), mCached.end(), false); + } + } + + // Evaluate the interpolator. Each element of 'order' indicates the + // order of the derivative you want to compute. For the function + // value itself, pass in 'order' that has all 0 elements. + typename Controls::Type EvaluateNoCaching(int const* order, Real const* t) + { + auto result = mCTZero; + if (!mValid) + { + return result; + } + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + if (order[dim] < 0 || order[dim] > mDegree[dim]) + { + return result; + } + } + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + GetKey(t[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim][1], + mNumControls[dim], mDegree[dim], mITuple[dim], mUTuple[dim]); + } + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + int jIndex = 0; + for (int j = 0; j <= mDegree[dim]; ++j) + { + int kjIndex = mDegree[dim] + jIndex; + int ell = mLMax[dim][order[dim]]; + mPhi[dim][j] = (Real)0; + for (int k = mDegree[dim]; k >= order[dim]; --k) + { + mPhi[dim][j] = mPhi[dim][j] * mUTuple[dim] + + mBlender[dim][kjIndex--] * mDCoefficient[dim][ell--]; + } + jIndex += mDegreeP1[dim]; + } + } + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mJTuple[dim] = 0; + mSumIJTuple[dim] = mITuple[dim]; + mPTuple[dim] = mPhi[dim][0]; + } + for (int iterate = 0; iterate < mNumLocalControls; ++iterate) + { + Real product(1); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + product *= mPTuple[dim]; + } + + result = result + (*mControls)(mSumIJTuple.data()) * product; + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + if (++mJTuple[dim] <= mDegree[dim]) + { + mSumIJTuple[dim] = mITuple[dim] + mJTuple[dim]; + mPTuple[dim] = mPhi[dim][mJTuple[dim]]; + break; + } + mJTuple[dim] = 0; + mSumIJTuple[dim] = mITuple[dim]; + mPTuple[dim] = mPhi[dim][0]; + } + } + + Real adjust(1); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + adjust *= mPowerDSDT[dim][order[dim]]; + } + result = result * adjust; + return result; + } + + typename Controls::Type EvaluateCaching(int const* order, Real const* t) + { + if (!mValid) + { + return mCTZero; + } + + int numIterates = 1; + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mDegreeMinusOrder[dim] = mDegree[dim] - order[dim]; + if (mDegreeMinusOrder[dim] < 0 || mDegreeMinusOrder[dim] > mDegree[dim]) + { + return mCTZero; + } + numIterates *= mDegreeMinusOrder[dim] + 1; + } + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + GetKey(t[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim][1], + mNumControls[dim], mDegree[dim], mITuple[dim], mUTuple[dim]); + } + + int rowIndex = GetRowIndex(mITuple); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mJTuple[dim] = 0; + mKTuple[dim] = mDegree[dim]; + mLTuple[dim] = mLMax[dim][order[dim]]; + mTerm[dim] = mCTZero; + } + for (int iterate = 0; iterate < numIterates; ++iterate) + { + int index = GetIndex(rowIndex, mKTuple); + if (mCacheMode == ON_DEMAND_CACHING && !mCached[index]) + { + ComputeTensor(mITuple.data(), mKTuple.data(), index); + mCached[index] = true; + } + mTerm[0] = mTerm[0] * mUTuple[0] + mTensor[index] * mDCoefficient[0][mLTuple[0]]; + for (int dim = 0; dim < mNumDimensions; ++dim) + { + if (++mJTuple[dim] <= mDegreeMinusOrder[dim]) + { + --mKTuple[dim]; + --mLTuple[dim]; + break; + } + int dimp1 = dim + 1; + if (dimp1 < mNumDimensions) + { + mTerm[dimp1] = mTerm[dimp1] * mUTuple[dimp1] + mTerm[dim] * mDCoefficient[dimp1][mLTuple[dimp1]]; + mTerm[dim] = mCTZero; + mJTuple[dim] = 0; + mKTuple[dim] = mDegree[dim]; + mLTuple[dim] = mLMax[dim][order[dim]]; + } + } + } + auto result = mTerm[mNumDimensions - 1]; + + Real adjust(1); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + adjust *= mPowerDSDT[dim][order[dim]]; + } + result = result * adjust; + return result; + } + + // Constructor inputs. + int const mNumDimensions; // N + std::vector mDegree; // degree[N] + Controls const* mControls; + typename Controls::Type const mCTZero; + int const mCacheMode; + + // Parameters for B-spline evaluation. All std::vector containers + // have N elements. + int mNumLocalControls; // product of (degree[]+1) + std::vector mDegreeP1; + std::vector mNumControls; + std::vector mTMin; + std::vector mTMax; + std::vector> mBlender; + std::vector> mDCoefficient; + std::vector> mLMax; + std::vector> mPowerDSDT; + std::vector mITuple; + std::vector mJTuple; + std::vector mKTuple; + std::vector mLTuple; + std::vector mSumIJTuple; + std::vector mUTuple; + std::vector mPTuple; + + // Support for no-cached B-spline evaluation. The std::vector + // container has N elements. + std::vector> mPhi; + + // Support for cached B-spline evaluation. + std::vector mTBound; // tbound[2*N] + std::vector mComputeJTuple; // computejtuple[N] + std::vector mComputeSumIJTuple; // computesumijtuple[N] + std::vector mDegreeMinusOrder; // degreeminusorder[N] + std::vector mTerm; // mTerm[N] + std::vector mTensor; // depends on numcontrols + std::vector mCached; // same size as mTensor + + bool mValid; + }; +} + +// Implementation for B-spline interpolation whose dimension is known at +// compile time. +namespace gte +{ + template + class IntpBSplineUniform : public IntpBSplineUniformShared + { + public: + // The caller is responsible for ensuring that the IntpBSplineUniform + // object persists as long as the input 'controls' exists. + IntpBSplineUniform(std::array const& degrees, Controls const& controls, + typename Controls::Type ctZero, int cacheMode) + : + IntpBSplineUniformShared(N, degrees.data(), controls, + ctZero, cacheMode) + { + if (!this->mValid) + { + return; + } + } + + // Disallow copying and moving. + IntpBSplineUniform(IntpBSplineUniform const&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; + IntpBSplineUniform(IntpBSplineUniform&&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; + + // Evaluate the interpolator. Each element of 'order' indicates the + // order of the derivative you want to compute. For the function + // value itself, pass in 'order' that has all 0 elements. + typename Controls::Type Evaluate(std::array const& order, + std::array const& t) + { + if (this->mValid) + { + if (this->mCacheMode == this->NO_CACHING) + { + return this->EvaluateNoCaching(order.data(), t.data()); + } + else + { + return this->EvaluateCaching(order.data(), t.data()); + } + } + else + { + return this->mCTZero; + } + } + }; +} + +// Implementation for B-spline interpolation whose dimension is known only +// at run time. +namespace gte +{ + template + class IntpBSplineUniform : public IntpBSplineUniformShared + { + public: + // The caller is responsible for ensuring that the IntpBSplineUniform + // object persists as long as the input 'controls' exists. + IntpBSplineUniform(std::vector const& degrees, Controls const& controls, + typename Controls::Type ctZero, int cacheMode) + : + IntpBSplineUniformShared(static_cast(degrees.size()), + degrees.data(), controls, ctZero, cacheMode) + { + if (!this->mValid) + { + return; + } + } + + // Disallow copying and moving. + IntpBSplineUniform(IntpBSplineUniform const&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; + IntpBSplineUniform(IntpBSplineUniform&&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; + + // Evaluate the interpolator. Each element of 'order' indicates the + // order of the derivative you want to compute. For the function + // value itself, pass in 'order' that has all 0 elements. + typename Controls::Type Evaluate(std::vector const& order, + std::vector const& t) + { + if (this->mValid + && static_cast(order.size()) >= this->mNumDimensions + && static_cast(t.size()) >= this->mNumDimensions) + { + if (this->mCacheMode == this->NO_CACHING) + { + return this->EvaluateNoCaching(order.data(), t.data()); + } + else + { + return this->EvaluateCaching(order.data(), t.data()); + } + } + else + { + return this->mCTZero; + } + } + }; +} + +// To use only the N-dimensional template code above, define the symbol +// GTE_INTP_BSPLINE_UNIFORM_NO_SPECIALIZATION to disable the specializations +// that occur below. Notice that the interfaces are different between the +// specializations and the general code. +#if !defined(GTE_INTP_BSPLINE_UNIFORM_NO_SPECIALIZATION) + +// Specialization for 1-dimensional data. +namespace gte +{ + template + class IntpBSplineUniform : public IntpBSplineUniformShared + { + public: + // The caller is responsible for ensuring that the IntpBSplineUniform + // object persists as long as the input 'controls' exists. + IntpBSplineUniform(int degree, Controls const& controls, + typename Controls::Type ctZero, int cacheMode) + : + IntpBSplineUniformShared(), + mDegree(degree), + mControls(&controls), + mCTZero(ctZero), + mCacheMode(cacheMode), + mValid(IsValid()) + { + if (!mValid) + { + return; + } + + mDegreeP1 = mDegree + 1; + mNumControls = mControls->GetSize(0); + mTMin = (Real)-0.5; + mTMax = static_cast(mNumControls) - (Real)0.5; + mNumTRows = 0; + mNumTCols = 0; + this->ComputeBlendingMatrix(mDegree, mBlender); + this->ComputeDCoefficients(mDegree, mDCoefficient, mLMax); + this->ComputePowers(mDegree, mNumControls, mTMin, mTMax, mPowerDSDT); + if (mCacheMode == this->NO_CACHING) + { + mPhi.resize(mDegreeP1); + } + else + { + InitializeTensors(); + } + } + + // Disallow copying and moving. + IntpBSplineUniform(IntpBSplineUniform const&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; + IntpBSplineUniform(IntpBSplineUniform&&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; + + // Member access. + inline int GetDegree(int) const { return mDegree; } + inline int GetNumControls(int) const { return mNumControls; } + inline Real GetTMin(int) const { return mTMin; } + inline Real GetTMax(int) const { return mTMax; } + inline int GetCacheMode() const { return mCacheMode; } + + // Evaluate the interpolator. The order is 0 when you want the B-spline + // function value itself. The order is 1 for the first derivative of the + // function, and so on. + typename Controls::Type Evaluate(std::array const& order, + std::array const& t) + { + auto result = mCTZero; + if (mValid + && 0 <= order[0] && order[0] <= mDegree) + { + int i; + Real u; + this->GetKey(t[0], mTMin, mTMax, mPowerDSDT[1], mNumControls, mDegree, i, u); + + if (mCacheMode == this->NO_CACHING) + { + int jIndex = 0; + for (int j = 0; j <= mDegree; ++j) + { + int kjIndex = mDegree + jIndex; + int ell = mLMax[order[0]]; + mPhi[j] = (Real)0; + for (int k = mDegree; k >= order[0]; --k) + { + mPhi[j] = mPhi[j] * u + mBlender[kjIndex--] * mDCoefficient[ell--]; + } + jIndex += mDegreeP1; + } + + for (int j = 0; j <= mDegree; ++j) + { + result = result + (*mControls)(i + j) * mPhi[j]; + } + } + else + { + int iIndex = mNumTCols * i; + int kiIndex = mDegree + iIndex; + int ell = mLMax[order[0]]; + for (int k = mDegree; k >= order[0]; --k) + { + if (mCacheMode == this->ON_DEMAND_CACHING && !mCached[kiIndex]) + { + ComputeTensor(i, k, kiIndex); + mCached[kiIndex] = true; + } + + result = result * u + mTensor[kiIndex--] * mDCoefficient[ell--]; + } + } + + result = result * mPowerDSDT[order[0]]; + } + return result; + } + + protected: + // Verify that the constraints for number of controls and degrees + // are satisfied. + bool IsValid() + { + // The condition c+1 > d+1 is required so that when s = c+1-d, its + // maximum value, we have at least two s-knots (d and d + 1). + if (mControls->GetSize(0) <= mDegree + 1) + { + LogError("Incompatible degree and number of controls."); + return false; + } + return true; + } + + void ComputeTensor(int r, int c, int index) + { + auto element = mCTZero; + for (int j = 0; j <= mDegree; ++j) + { + element = element + (*mControls)(r + j) * mBlender[c + mDegreeP1 * j]; + } + mTensor[index] = element; + } + + void InitializeTensors() + { + mNumTRows = mNumControls - mDegree; + mNumTCols = mDegreeP1; + int numCached = mNumTRows * mNumTCols; + mTensor.resize(numCached); + mCached.resize(numCached); + if (mCacheMode == this->PRE_CACHING) + { + for (int r = 0, index = 0; r < mNumTRows; ++r) + { + for (int c = 0; c < mNumTCols; ++c, ++index) + { + ComputeTensor(r, c, index); + } + } + std::fill(mCached.begin(), mCached.end(), true); + } + else + { + std::fill(mCached.begin(), mCached.end(), false); + } + } + + // Constructor inputs. + int mDegree; + Controls const* mControls; + typename Controls::Type mCTZero; + int mCacheMode; + + // Parameters for B-spline evaluation. + int mDegreeP1; + int mNumControls; + Real mTMin, mTMax; + std::vector mBlender; + std::vector mDCoefficient; + std::vector mLMax; + std::vector mPowerDSDT; + + // Support for no-cached B-spline evaluation. + std::vector mPhi; + + // Support for cached B-spline evaluation. + int mNumTRows, mNumTCols; + std::vector mTensor; + std::vector mCached; + + bool mValid; + }; +} + +// Specialization for 2-dimensional data. +namespace gte +{ + template + class IntpBSplineUniform : public IntpBSplineUniformShared + { + public: + // The caller is responsible for ensuring that the IntpBSplineUniform2 + // object persists as long as the input 'controls' exists. + IntpBSplineUniform(std::array const& degrees, Controls const& controls, + typename Controls::Type ctZero, int cacheMode) + : + IntpBSplineUniformShared(), + mDegree(degrees), + mControls(&controls), + mCTZero(ctZero), + mCacheMode(cacheMode), + mValid(IsValid()) + { + if (!mValid) + { + return; + } + + for (int dim = 0; dim < 2; ++dim) + { + mDegreeP1[dim] = mDegree[dim] + 1; + mNumControls[dim] = mControls->GetSize(dim); + mTMin[dim] = (Real)-0.5; + mTMax[dim] = static_cast(mNumControls[dim]) - (Real)0.5; + mNumTRows[dim] = 0; + mNumTCols[dim] = 0; + this->ComputeBlendingMatrix(mDegree[dim], mBlender[dim]); + this->ComputeDCoefficients(mDegree[dim], mDCoefficient[dim], mLMax[dim]); + this->ComputePowers(mDegree[dim], mNumControls[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim]); + } + + if (mCacheMode == this->NO_CACHING) + { + for (int dim = 0; dim < 2; ++dim) + { + mPhi[dim].resize(mDegreeP1[dim]); + } + } + else + { + InitializeTensors(); + } + } + + // Disallow copying and moving. + IntpBSplineUniform(IntpBSplineUniform const&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; + IntpBSplineUniform(IntpBSplineUniform&&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; + + // Member access. + inline int GetDegree(int dim) const { return mDegree[dim]; } + inline int GetNumControls(int dim) const { return mNumControls[dim]; } + inline Real GetTMin(int dim) const { return mTMin[dim]; } + inline Real GetTMax(int dim) const { return mTMax[dim]; } + inline int GetCacheMode() const { return mCacheMode; } + + // Evaluate the interpolator. The order is (0,0) when you want the + // B-spline function value itself. The order0 is 1 for the first + // derivative with respect to t0 and the order1 is 1 for the first + // derivative with respect to t1. Higher-order derivatives in other + // t-inputs are computed similarly. + typename Controls::Type Evaluate(std::array const& order, + std::array const& t) + { + auto result = mCTZero; + if (mValid + && 0 <= order[0] && order[0] <= mDegree[0] + && 0 <= order[1] && order[1] <= mDegree[1]) + { + std::array i; + std::array u; + for (int dim = 0; dim < 2; ++dim) + { + this->GetKey(t[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim][1], + mNumControls[dim], mDegree[dim], i[dim], u[dim]); + } + + if (mCacheMode == this->NO_CACHING) + { + for (int dim = 0; dim < 2; ++dim) + { + int jIndex = 0; + for (int j = 0; j <= mDegree[dim]; ++j) + { + int kjIndex = mDegree[dim] + jIndex; + int ell = mLMax[dim][order[dim]]; + mPhi[dim][j] = (Real)0; + for (int k = mDegree[dim]; k >= order[dim]; --k) + { + mPhi[dim][j] = mPhi[dim][j] * u[dim] + + mBlender[dim][kjIndex--] * mDCoefficient[dim][ell--]; + } + jIndex += mDegreeP1[dim]; + } + } + + for (int j1 = 0; j1 <= mDegree[1]; ++j1) + { + Real phi1 = mPhi[1][j1]; + for (int j0 = 0; j0 <= mDegree[0]; ++j0) + { + Real phi0 = mPhi[0][j0]; + Real phi01 = phi0 * phi1; + result = result + (*mControls)(i[0] + j0, i[1] + j1) * phi01; + } + } + } + else + { + int i0i1Index = mNumTCols[1] * (i[0] + mNumTRows[0] * i[1]); + int k1i0i1Index = mDegree[1] + i0i1Index; + int ell1 = mLMax[1][order[1]]; + for (int k1 = mDegree[1]; k1 >= order[1]; --k1) + { + int k0k1i0i1Index = mDegree[0] + mNumTCols[0] * k1i0i1Index; + int ell0 = mLMax[0][order[0]]; + auto term = mCTZero; + for (int k0 = mDegree[0]; k0 >= order[0]; --k0) + { + if (mCacheMode == this->ON_DEMAND_CACHING && !mCached[k0k1i0i1Index]) + { + ComputeTensor(i[0], i[1], k0, k1, k0k1i0i1Index); + mCached[k0k1i0i1Index] = true; + } + term = term * u[0] + mTensor[k0k1i0i1Index--] * mDCoefficient[0][ell0--]; + } + result = result * u[1] + term * mDCoefficient[1][ell1--]; + --k1i0i1Index; + } + } + + Real adjust(1); + for (int dim = 0; dim < 2; ++dim) + { + adjust *= mPowerDSDT[dim][order[dim]]; + } + result = result * adjust; + } + return result; + } + + // Verify that the constraints for number of controls and degrees + // are satisfied. + bool IsValid() + { + // The condition c+1 > d+1 is required so that when s = c+1-d, its + // maximum value, we have at least two s-knots (d and d + 1). + for (int dim = 0; dim < 2; ++dim) + { + if (mControls->GetSize(dim) <= mDegree[dim] + 1) + { + LogError("Incompatible degree and number of controls."); + return false; + } + } + return true; + } + + void ComputeTensor(int r0, int r1, int c0, int c1, int index) + { + auto element = mCTZero; + for (int j1 = 0; j1 <= mDegree[1]; ++j1) + { + Real blend1 = mBlender[1][c1 + mDegreeP1[1] * j1]; + for (int j0 = 0; j0 <= mDegree[0]; ++j0) + { + Real blend0 = mBlender[0][c0 + mDegreeP1[0] * j0]; + Real blend01 = blend0 * blend1; + element = element + (*mControls)(r0 + j0, r1 + j1) * blend01; + } + } + mTensor[index] = element; + } + + void InitializeTensors() + { + int numCached = 1; + for (int dim = 0; dim < 2; ++dim) + { + mNumTRows[dim] = mNumControls[dim] - mDegree[dim]; + mNumTCols[dim] = mDegreeP1[dim]; + numCached *= mNumTRows[dim] * mNumTCols[dim]; + } + mTensor.resize(numCached); + mCached.resize(numCached); + if (mCacheMode == this->PRE_CACHING) + { + for (int r1 = 0, index = 0; r1 < mNumTRows[1]; ++r1) + { + for (int r0 = 0; r0 < mNumTRows[0]; ++r0) + { + for (int c1 = 0; c1 < mNumTCols[1]; ++c1) + { + for (int c0 = 0; c0 < mNumTCols[0]; ++c0, ++index) + { + ComputeTensor(r0, r1, c0, c1, index); + } + } + } + } + std::fill(mCached.begin(), mCached.end(), true); + } + else + { + std::fill(mCached.begin(), mCached.end(), false); + } + } + + // Constructor inputs. + std::array mDegree; + Controls const* mControls; + typename Controls::Type mCTZero; + int mCacheMode; + + // Parameters for B-spline evaluation. + std::array mDegreeP1; + std::array mNumControls; + std::array mTMin, mTMax; + std::array, 2> mBlender; + std::array, 2> mDCoefficient; + std::array, 2> mLMax; + std::array, 2> mPowerDSDT; + + // Support for no-cached B-spline evaluation. + std::array, 2> mPhi; + + // Support for cached B-spline evaluation. + std::array mNumTRows, mNumTCols; + std::vector mTensor; + std::vector mCached; + + bool mValid; + }; +} + +// Specialization for 3-dimensional data. +namespace gte +{ + template + class IntpBSplineUniform : public IntpBSplineUniformShared + { + public: + // The caller is responsible for ensuring that the IntpBSplineUniform3 + // object persists as long as the input 'controls' exists. + IntpBSplineUniform(std::array const& degrees, Controls const& controls, + typename Controls::Type ctZero, int cacheMode) + : + IntpBSplineUniformShared(), + mDegree(degrees), + mControls(&controls), + mCTZero(ctZero), + mCacheMode(cacheMode), + mValid(IsValid()) + { + if (!mValid) + { + return; + } + + for (int dim = 0; dim < 3; ++dim) + { + mDegreeP1[dim] = mDegree[dim] + 1; + mNumControls[dim] = mControls->GetSize(dim); + mTMin[dim] = (Real)-0.5; + mTMax[dim] = static_cast(mNumControls[dim]) - (Real)0.5; + mNumTRows[dim] = 0; + mNumTCols[dim] = 0; + this->ComputeBlendingMatrix(mDegree[dim], mBlender[dim]); + this->ComputeDCoefficients(mDegree[dim], mDCoefficient[dim], mLMax[dim]); + this->ComputePowers(mDegree[dim], mNumControls[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim]); + } + + if (mCacheMode == this->NO_CACHING) + { + for (int dim = 0; dim < 3; ++dim) + { + mPhi[dim].resize(mDegreeP1[dim]); + } + } + else + { + InitializeTensors(); + } + + } + + // Disallow copying and moving. + IntpBSplineUniform(IntpBSplineUniform const&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; + IntpBSplineUniform(IntpBSplineUniform&&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; + + // Member access. The input i specifies the dimension (0, 1, 2). + inline int GetDegree(int dim) const { return mDegree[dim]; } + inline int GetNumControls(int dim) const { return mNumControls[dim]; } + inline Real GetTMin(int dim) const { return mTMin[dim]; } + inline Real GetTMax(int dim) const { return mTMax[dim]; } + + // Evaluate the interpolator. The order is (0,0,0) when you want the + // B-spline function value itself. The order0 is 1 for the first + // derivative with respect to t0, the order1 is 1 for the first + // derivative with respect to t1 or the order2 is 1 for the first + // derivative with respect to t2. Higher-order derivatives in other + // t-inputs are computed similarly. + typename Controls::Type Evaluate(std::array const& order, + std::array const& t) + { + auto result = mCTZero; + if (mValid + && 0 <= order[0] && order[0] <= mDegree[0] + && 0 <= order[1] && order[1] <= mDegree[1] + && 0 <= order[2] && order[2] <= mDegree[2]) + { + std::array i; + std::array u; + for (int dim = 0; dim < 3; ++dim) + { + this->GetKey(t[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim][1], + mNumControls[dim], mDegree[dim], i[dim], u[dim]); + } + + if (mCacheMode == this->NO_CACHING) + { + for (int dim = 0; dim < 3; ++dim) + { + int jIndex = 0; + for (int j = 0; j <= mDegree[dim]; ++j) + { + int kjIndex = mDegree[dim] + jIndex; + int ell = mLMax[dim][order[dim]]; + mPhi[dim][j] = (Real)0; + for (int k = mDegree[dim]; k >= order[dim]; --k) + { + mPhi[dim][j] = mPhi[dim][j] * u[dim] + + mBlender[dim][kjIndex--] * mDCoefficient[dim][ell--]; + } + jIndex += mDegreeP1[dim]; + } + } + + for (int j2 = 0; j2 <= mDegree[2]; ++j2) + { + Real phi2 = mPhi[2][j2]; + for (int j1 = 0; j1 <= mDegree[1]; ++j1) + { + Real phi1 = mPhi[1][j1]; + Real phi12 = phi1 * phi2; + for (int j0 = 0; j0 <= mDegree[0]; ++j0) + { + Real phi0 = mPhi[0][j0]; + Real phi012 = phi0 * phi12; + result = result + (*mControls)(i[0] + j0, i[1] + j1, i[2] + j2) * phi012; + } + } + } + } + else + { + int i0i1i2Index = mNumTCols[2] * (i[0] + mNumTRows[0] * (i[1] + mNumTRows[1] * i[2])); + int k2i0i1i2Index = mDegree[2] + i0i1i2Index; + int ell2 = mLMax[2][order[2]]; + for (int k2 = mDegree[2]; k2 >= order[2]; --k2) + { + int k1k2i0i1i2Index = mDegree[1] + mNumTCols[1] * k2i0i1i2Index; + int ell1 = mLMax[1][order[1]]; + auto term1 = mCTZero; + for (int k1 = mDegree[1]; k1 >= order[1]; --k1) + { + int k0k1k2i0i1i2Index = mDegree[0] + mNumTCols[0] * k1k2i0i1i2Index; + int ell0 = mLMax[0][order[0]]; + auto term0 = mCTZero; + for (int k0 = mDegree[0]; k0 >= order[0]; --k0) + { + if (mCacheMode == this->ON_DEMAND_CACHING && !mCached[k0k1k2i0i1i2Index]) + { + ComputeTensor(i[0], i[1], i[2], k0, k1, k2, k0k1k2i0i1i2Index); + mCached[k0k1k2i0i1i2Index] = true; + } + + term0 = term0 * u[0] + mTensor[k0k1k2i0i1i2Index--] * mDCoefficient[0][ell0--]; + } + term1 = term1 * u[1] + term0 * mDCoefficient[1][ell1--]; + --k1k2i0i1i2Index; + } + result = result * u[2] + term1 * mDCoefficient[2][ell2--]; + --k2i0i1i2Index; + } + } + + Real adjust(1); + for (int dim = 0; dim < 3; ++dim) + { + adjust *= mPowerDSDT[dim][order[dim]]; + } + result = result * adjust; + } + return result; + } + + protected: + // Verify that the constraints for number of controls and degrees + // are satisfied. + bool IsValid() + { + // The condition c+1 > d+1 is required so that when s = c+1-d, its + // maximum value, we have at least two s-knots (d and d + 1). + for (int dim = 0; dim < 3; ++dim) + { + if (mControls->GetSize(dim) <= mDegree[dim] + 1) + { + LogError("Incompatible degree and number of controls."); + return false; + } + } + return true; + } + + void ComputeTensor(int r0, int r1, int r2, int c0, int c1, int c2, int index) + { + auto element = mCTZero; + for (int j2 = 0; j2 <= mDegree[2]; ++j2) + { + Real blend2 = mBlender[2][c2 + mDegreeP1[2] * j2]; + for (int j1 = 0; j1 <= mDegree[1]; ++j1) + { + Real blend1 = mBlender[1][c1 + mDegreeP1[1] * j1]; + Real blend12 = blend1 * blend2; + for (int j0 = 0; j0 <= mDegree[0]; ++j0) + { + Real blend0 = mBlender[0][c0 + mDegreeP1[0] * j0]; + Real blend012 = blend0 * blend12; + element = element + (*mControls)(r0 + j0, r1 + j1, r2 + j2) * blend012; + } + } + } + mTensor[index] = element; + } + + void InitializeTensors() + { + int numCached = 1; + for (int dim = 0; dim < 3; ++dim) + { + mNumTRows[dim] = mNumControls[dim] - mDegree[dim]; + mNumTCols[dim] = mDegreeP1[dim]; + numCached *= mNumTRows[dim] * mNumTCols[dim]; + } + mTensor.resize(numCached); + mCached.resize(numCached); + if (mCacheMode == this->PRE_CACHING) + { + for (int r2 = 0, index = 0; r2 < mNumTRows[2]; ++r2) + { + for (int r1 = 0; r1 < mNumTRows[1]; ++r1) + { + for (int r0 = 0; r0 < mNumTRows[0]; ++r0) + { + for (int c2 = 0; c2 < mNumTCols[2]; ++c2) + { + for (int c1 = 0; c1 < mNumTCols[1]; ++c1) + { + for (int c0 = 0; c0 < mNumTCols[0]; ++c0, ++index) + { + ComputeTensor(r0, r1, r2, c0, c1, c2, index); + } + } + } + } + } + } + std::fill(mCached.begin(), mCached.end(), true); + } + else + { + std::fill(mCached.begin(), mCached.end(), false); + } + } + + // Constructor inputs. + std::array mDegree; + Controls const* mControls; + typename Controls::Type mCTZero; + int mCacheMode; + + // Parameters for B-spline evaluation. + std::array mDegreeP1; + std::array mNumControls; + std::array mTMin, mTMax; + std::array, 3> mBlender; + std::array, 3> mDCoefficient; + std::array, 3> mLMax; + std::array, 3> mPowerDSDT; + + // Support for no-cached B-spline evaluation. + std::array, 3> mPhi; + + // Support for cached B-spline evaluation. + std::array mNumTRows, mNumTCols; + std::vector mTensor; + std::vector mCached; + + bool mValid; + }; +} + +#endif diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBicubic2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBicubic2.h new file mode 100644 index 000000000000..e92425e14a0d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBicubic2.h @@ -0,0 +1,425 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The interpolator is for uniformly spaced (x,y)-values. The input samples +// F must be stored in row-major order to represent f(x,y); that is, +// F[c + xBound*r] corresponds to f(x,y), where c is the index corresponding +// to x and r is the index corresponding to y. Exact interpolation is +// achieved by setting catmullRom to 'true', giving you the Catmull-Rom +// blending matrix. If a smooth interpolation is desired, set catmullRom to +// 'false' to obtain B-spline blending. + +namespace gte +{ + +template +class IntpBicubic2 +{ +public: + // Construction. + IntpBicubic2(int xBound, int yBound, Real xMin, Real xSpacing, + Real yMin, Real ySpacing, Real const* F, bool catmullRom); + + // Member access. + inline int GetXBound() const; + inline int GetYBound() const; + inline int GetQuantity() const; + inline Real const* GetF() const; + inline Real GetXMin() const; + inline Real GetXMax() const; + inline Real GetXSpacing() const; + inline Real GetYMin() const; + inline Real GetYMax() const; + inline Real GetYSpacing() const; + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax and ymin <= y <= ymax. The first operator + // is for function evaluation. The second operator is for function or + // derivative evaluations. The xOrder argument is the order of the + // x-derivative and the yOrder argument is the order of the y-derivative. + // Both orders are zero to get the function value itself. + Real operator()(Real x, Real y) const; + Real operator()(int xOrder, int yOrder, Real x, Real y) const; + +private: + int mXBound, mYBound, mQuantity; + Real mXMin, mXMax, mXSpacing, mInvXSpacing; + Real mYMin, mYMax, mYSpacing, mInvYSpacing; + Real const* mF; + Real mBlend[4][4]; +}; + + +template +IntpBicubic2::IntpBicubic2(int xBound, int yBound, Real xMin, + Real xSpacing, Real yMin, Real ySpacing, Real const* F, bool catmullRom) + : + mXBound(xBound), + mYBound(yBound), + mQuantity(xBound * yBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mF(F) +{ + // At least a 3x3 block of data points are needed to construct the + // estimates of the boundary derivatives. + LogAssert(mXBound >= 3 && mYBound >= 3 && mF, "Invalid input."); + LogAssert(mXSpacing > (Real)0 && mYSpacing > (Real)0, "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mInvXSpacing = ((Real)1) / mXSpacing; + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + mInvYSpacing = ((Real)1) / mYSpacing; + + if (catmullRom) + { + mBlend[0][0] = (Real)0; + mBlend[0][1] = (Real)-0.5; + mBlend[0][2] = (Real)1; + mBlend[0][3] = (Real)-0.5; + mBlend[1][0] = (Real)1; + mBlend[1][1] = (Real)0; + mBlend[1][2] = (Real)-2.5; + mBlend[1][3] = (Real)1.5; + mBlend[2][0] = (Real)0; + mBlend[2][1] = (Real)0.5; + mBlend[2][2] = (Real)2; + mBlend[2][3] = (Real)-1.5; + mBlend[3][0] = (Real)0; + mBlend[3][1] = (Real)0; + mBlend[3][2] = (Real)-0.5; + mBlend[3][3] = (Real)0.5; + } + else + { + mBlend[0][0] = (Real)1 / (Real)6; + mBlend[0][1] = (Real)-3 / (Real)6; + mBlend[0][2] = (Real)3 / (Real)6; + mBlend[0][3] = (Real)-1 / (Real)6;; + mBlend[1][0] = (Real)4 / (Real)6; + mBlend[1][1] = (Real)0 / (Real)6; + mBlend[1][2] = (Real)-6 / (Real)6; + mBlend[1][3] = (Real)3 / (Real)6; + mBlend[2][0] = (Real)1 / (Real)6; + mBlend[2][1] = (Real)3 / (Real)6; + mBlend[2][2] = (Real)3 / (Real)6; + mBlend[2][3] = (Real)-3 / (Real)6; + mBlend[3][0] = (Real)0 / (Real)6; + mBlend[3][1] = (Real)0 / (Real)6; + mBlend[3][2] = (Real)0 / (Real)6; + mBlend[3][3] = (Real)1 / (Real)6; + } +} + +template inline +int IntpBicubic2::GetXBound() const +{ + return mXBound; +} + +template inline +int IntpBicubic2::GetYBound() const +{ + return mYBound; +} + +template inline +int IntpBicubic2::GetQuantity() const +{ + return mQuantity; +} + +template inline +Real const* IntpBicubic2::GetF() const +{ + return mF; +} + +template inline +Real IntpBicubic2::GetXMin() const +{ + return mXMin; +} + +template inline +Real IntpBicubic2::GetXMax() const +{ + return mXMax; +} + +template inline +Real IntpBicubic2::GetXSpacing() const +{ + return mXSpacing; +} + +template inline +Real IntpBicubic2::GetYMin() const +{ + return mYMin; +} + +template inline +Real IntpBicubic2::GetYMax() const +{ + return mYMax; +} + +template inline +Real IntpBicubic2::GetYSpacing() const +{ + return mYSpacing; +} + +template +Real IntpBicubic2::operator()(Real x, Real y) const +{ + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + Real U[4]; + U[0] = (Real)1; + U[1] = xIndex - ix; + U[2] = U[1] * U[1]; + U[3] = U[1] * U[2]; + + Real V[4]; + V[0] = (Real)1; + V[1] = yIndex - iy; + V[2] = V[1] * V[1]; + V[3] = V[1] * V[2]; + + // Compute P = M*U and Q = M*V. + Real P[4], Q[4]; + for (int row = 0; row < 4; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + for (int col = 0; col < 4; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + } + } + + // Compute (M*U)^t D (M*V) where D is the 4x4 subimage containing (x,y). + --ix; + --iy; + Real result = (Real)0; + for (int row = 0; row < 4; ++row) + { + int yClamp = iy + row; + if (yClamp < 0) + { + yClamp = 0; + } + else if (yClamp > mYBound - 1) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 4; ++col) + { + int xClamp = ix + col; + if (xClamp < 0) + { + xClamp = 0; + } + else if (xClamp > mXBound - 1) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * mF[xClamp + mXBound * yClamp]; + } + } + + return result; +} + +template +Real IntpBicubic2::operator()(int xOrder, int yOrder, Real x, Real y) +const +{ + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + Real U[4], dx, xMult; + switch (xOrder) + { + case 0: + dx = xIndex - ix; + U[0] = (Real)1; + U[1] = dx; + U[2] = dx * U[1]; + U[3] = dx * U[2]; + xMult = (Real)1; + break; + case 1: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)1; + U[2] = ((Real)2) * dx; + U[3] = ((Real)3) * dx * dx; + xMult = mInvXSpacing; + break; + case 2: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)0; + U[2] = (Real)2; + U[3] = (Real)6 * dx; + xMult = mInvXSpacing * mInvXSpacing; + break; + case 3: + U[0] = (Real)0; + U[1] = (Real)0; + U[2] = (Real)0; + U[3] = (Real)6; + xMult = mInvXSpacing * mInvXSpacing * mInvXSpacing; + break; + default: + return (Real)0; + } + + Real V[4], dy, yMult; + switch (yOrder) + { + case 0: + dy = yIndex - iy; + V[0] = (Real)1; + V[1] = dy; + V[2] = dy * V[1]; + V[3] = dy * V[2]; + yMult = (Real)1; + break; + case 1: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)1; + V[2] = ((Real)2) * dy; + V[3] = ((Real)3) * dy * dy; + yMult = mInvYSpacing; + break; + case 2: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)0; + V[2] = (Real)2; + V[3] = ((Real)6) * dy; + yMult = mInvYSpacing * mInvYSpacing; + break; + case 3: + V[0] = (Real)0; + V[1] = (Real)0; + V[2] = (Real)0; + V[3] = (Real)6; + yMult = mInvYSpacing * mInvYSpacing * mInvYSpacing; + break; + default: + return (Real)0; + } + + // Compute P = M*U and Q = M*V. + Real P[4], Q[4]; + for (int row = 0; row < 4; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + for (int col = 0; col < 4; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + } + } + + // Compute (M*U)^t D (M*V) where D is the 4x4 subimage containing (x,y). + --ix; + --iy; + Real result = (Real)0; + for (int row = 0; row < 4; ++row) + { + int yClamp = iy + row; + if (yClamp < 0) + { + yClamp = 0; + } + else if (yClamp > mYBound - 1) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 4; ++col) + { + int xClamp = ix + col; + if (xClamp < 0) + { + xClamp = 0; + } + else if (xClamp > mXBound - 1) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * mF[xClamp + mXBound * yClamp]; + } + } + result *= xMult * yMult; + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBilinear2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBilinear2.h new file mode 100644 index 000000000000..f982084b9374 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBilinear2.h @@ -0,0 +1,325 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The interpolator is for uniformly spaced (x,y)-values. The input samples +// F must be stored in row-major order to represent f(x,y); that is, +// F[c + xBound*r] corresponds to f(x,y), where c is the index corresponding +// to x and r is the index corresponding to y. + +namespace gte +{ + +template +class IntpBilinear2 +{ +public: + // Construction. + IntpBilinear2(int xBound, int yBound, Real xMin, Real xSpacing, + Real yMin, Real ySpacing, Real const* F); + + // Member access. + inline int GetXBound() const; + inline int GetYBound() const; + inline int GetQuantity() const; + inline Real const* GetF() const; + inline Real GetXMin() const; + inline Real GetXMax() const; + inline Real GetXSpacing() const; + inline Real GetYMin() const; + inline Real GetYMax() const; + inline Real GetYSpacing() const; + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax and ymin <= y <= ymax. The first operator + // is for function evaluation. The second operator is for function or + // derivative evaluations. The xOrder argument is the order of the + // x-derivative and the yOrder argument is the order of the y-derivative. + // Both orders are zero to get the function value itself. + Real operator()(Real x, Real y) const; + Real operator()(int xOrder, int yOrder, Real x, Real y) const; + +private: + int mXBound, mYBound, mQuantity; + Real mXMin, mXMax, mXSpacing, mInvXSpacing; + Real mYMin, mYMax, mYSpacing, mInvYSpacing; + Real const* mF; + Real mBlend[2][2]; +}; + + +template +IntpBilinear2::IntpBilinear2(int xBound, int yBound, Real xMin, + Real xSpacing, Real yMin, Real ySpacing, Real const* F) + : + mXBound(xBound), + mYBound(yBound), + mQuantity(xBound * yBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mF(F) +{ + // At least a 2x2 block of data points are needed. + LogAssert(mXBound >= 2 && mYBound >= 2 && mF, "Invalid input."); + LogAssert(mXSpacing > (Real)0 && mYSpacing > (Real)0, "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mInvXSpacing = ((Real)1) / mXSpacing; + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + mInvYSpacing = ((Real)1) / mYSpacing; + + mBlend[0][0] = (Real)1; + mBlend[0][1] = (Real)-1; + mBlend[1][0] = (Real)0; + mBlend[1][1] = (Real)1; +} + +template inline +int IntpBilinear2::GetXBound() const +{ + return mXBound; +} + +template inline +int IntpBilinear2::GetYBound() const +{ + return mYBound; +} + +template inline +int IntpBilinear2::GetQuantity() const +{ + return mQuantity; +} + +template inline +Real const* IntpBilinear2::GetF() const +{ + return mF; +} + +template inline +Real IntpBilinear2::GetXMin() const +{ + return mXMin; +} + +template inline +Real IntpBilinear2::GetXMax() const +{ + return mXMax; +} + +template inline +Real IntpBilinear2::GetXSpacing() const +{ + return mXSpacing; +} + +template inline +Real IntpBilinear2::GetYMin() const +{ + return mYMin; +} + +template inline +Real IntpBilinear2::GetYMax() const +{ + return mYMax; +} + +template inline +Real IntpBilinear2::GetYSpacing() const +{ + return mYSpacing; +} + +template +Real IntpBilinear2::operator()(Real x, Real y) const +{ + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + Real U[2]; + U[0] = (Real)1; + U[1] = xIndex - ix; + + Real V[2]; + V[0] = (Real)1.0; + V[1] = yIndex - iy; + + // Compute P = M*U and Q = M*V. + Real P[2], Q[2]; + for (int row = 0; row < 2; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + for (int col = 0; col < 2; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + } + } + + // Compute (M*U)^t D (M*V) where D is the 2x2 subimage containing (x,y). + Real result = (Real)0; + for (int row = 0; row < 2; ++row) + { + int yClamp = iy + row; + if (yClamp >= mYBound) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 2; ++col) + { + int xClamp = ix + col; + if (xClamp >= mXBound) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * mF[xClamp + mXBound * yClamp]; + } + } + + return result; +} + +template +Real IntpBilinear2::operator()(int xOrder, int yOrder, Real x, Real y) +const +{ + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + Real U[2], dx, xMult; + switch (xOrder) + { + case 0: + dx = xIndex - ix; + U[0] = (Real)1; + U[1] = dx; + xMult = (Real)1; + break; + case 1: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)1; + xMult = mInvXSpacing; + break; + default: + return (Real)0; + } + + Real V[2], dy, yMult; + switch (yOrder) + { + case 0: + dy = yIndex - iy; + V[0] = (Real)1; + V[1] = dy; + yMult = (Real)1; + break; + case 1: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)1; + yMult = mInvYSpacing; + break; + default: + return (Real)0; + } + + // Compute P = M*U and Q = M*V. + Real P[2], Q[2]; + for (int row = 0; row < 2; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + for (int col = 0; col < 2; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + } + } + + // Compute (M*U)^t D (M*V) where D is the 2x2 subimage containing (x,y). + Real result = (Real)0; + for (int row = 0; row < 2; ++row) + { + int yClamp = iy + row; + if (yClamp >= mYBound) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 2; ++col) + { + int xClamp = ix + col; + if (xClamp >= mXBound) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * mF[xClamp + mXBound * yClamp]; + } + } + result *= xMult * yMult; + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpLinearNonuniform2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpLinearNonuniform2.h new file mode 100644 index 000000000000..9f0c0afca2f0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpLinearNonuniform2.h @@ -0,0 +1,84 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +// Linear interpolation of a network of triangles whose vertices are of the +// form (x,y,f(x,y)). The function samples are F[i] and represent +// f(x[i],y[i]), where i is the index of the input vertex (x[i],y[i]) to +// Delaunay2. +// +// The TriangleMesh interface must support the following: +// bool GetIndices(int, std::array&) const; +// bool GetBarycentrics(int, Vector2 const&, +// std::array&) const; +// int GetContainingTriangle(Vector2 const&) const; + +#include +#include + +namespace gte +{ + +template +class IntpLinearNonuniform2 +{ +public: + // Construction. + IntpLinearNonuniform2(TriangleMesh const& mesh, Real const* F); + + // Linear interpolation. The return value is 'true' if and only if the + // input point P is in the convex hull of the input vertices, in which + // case the interpolation is valid. + bool operator()(Vector2 const& P, Real& F) const; + +private: + TriangleMesh const* mMesh; + Real const* mF; +}; + + +template +IntpLinearNonuniform2::IntpLinearNonuniform2( + TriangleMesh const& mesh, Real const* F) + : + mMesh(&mesh), + mF(F) +{ + LogAssert(mF != nullptr, "Invalid input."); +} + +template +bool IntpLinearNonuniform2::operator()( + Vector2 const& P, Real& F) const +{ + int t = mMesh->GetContainingTriangle(P); + if (t == -1) + { + // The point is outside the triangulation. + return false; + } + + // Get the barycentric coordinates of P with respect to the triangle, + // P = b0*V0 + b1*V1 + b2*V2, where b0 + b1 + b2 = 1. + std::array bary; + if (!mMesh->GetBarycentrics(t, P, bary)) + { + LogWarning("P is in a needle-like or degenerate triangle."); + return false; + } + + // The result is a barycentric combination of function values. + std::array indices; + mMesh->GetIndices(t, indices); + F = bary[0] * mF[indices[0]] + bary[1] * mF[indices[1]] + + bary[2] * mF[indices[2]]; + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpLinearNonuniform3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpLinearNonuniform3.h new file mode 100644 index 000000000000..d8af848c3535 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpLinearNonuniform3.h @@ -0,0 +1,83 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +// Linear interpolation of a network of triangles whose vertices are of the +// form (x,y,z,f(x,y,z)). The function samples are F[i] and represent +// f(x[i],y[i],z[i]), where i is the index of the input vertex +// (x[i],y[i],z[i]) to Delaunay3. +// +// The TetrahedronMesh interface must support the following: +// int GetContainingTetrahedron(Vector3 const&) const; +// bool GetIndices(int, std::array&) const; +// bool GetBarycentrics(int, Vector3 const&, Real[4]) const; + +#include +#include + +namespace gte +{ + +template +class IntpLinearNonuniform3 +{ +public: + // Construction. + IntpLinearNonuniform3(TetrahedronMesh const& mesh, Real const* F); + + // Linear interpolation. The return value is 'true' if and only if the + // input point is in the convex hull of the input vertices, in which case + // the interpolation is valid. + bool operator()(Vector3 const& P, Real& F) const; + +private: + TetrahedronMesh const* mMesh; + Real const* mF; +}; + + +template +IntpLinearNonuniform3::IntpLinearNonuniform3( + TetrahedronMesh const& mesh, Real const* F) + : + mMesh(&mesh), + mF(F) +{ + LogAssert(mF != nullptr, "Invalid input."); +} + +template +bool IntpLinearNonuniform3::operator()( + Vector3 const& P, Real& F) const +{ + int t = mMesh->GetContainingTetrahedron(P); + if (t == -1) + { + // The point is outside the tetrahedralization. + return false; + } + + // Get the barycentric coordinates of P with respect to the tetrahedron, + // P = b0*V0 + b1*V1 + b2*V2 + b3*V3, where b0 + b1 + b2 + b3 = 1. + std::array bary; + if (!mMesh->GetBarycentrics(t, P, bary)) + { + LogWarning("P is in a needle-like, flat, or degenerate tetrahedron."); + return false; + } + + // The result is a barycentric combination of function values. + std::array indices; + mMesh->GetIndices(t, indices); + F = bary[0] * mF[indices[0]] + bary[1] * mF[indices[1]] + + bary[2] * mF[indices[2]] + bary[3] * mF[indices[4]]; + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpQuadraticNonuniform2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpQuadraticNonuniform2.h new file mode 100644 index 000000000000..a2bf9c2390b9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpQuadraticNonuniform2.h @@ -0,0 +1,488 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// Quadratic interpolation of a network of triangles whose vertices are of +// the form (x,y,f(x,y)). This code is an implementation of the algorithm +// found in +// +// Zoltan J. Cendes and Steven H. Wong, +// C1 quadratic interpolation over arbitrary point sets, +// IEEE Computer Graphics & Applications, +// pp. 8-16, 1987 +// +// The TriangleMesh interface must support the following: +// int GetNumVertices() const; +// int GetNumTriangles() const; +// Vector2 const* GetVertices() const; +// int const* GetIndices() const; +// bool GetVertices(int, std::array, 3>&) const; +// bool GetIndices(int, std::array&) const; +// bool GetAdjacencies(int, std::array&) const; +// bool GetBarycentrics(int, Vector2 const&, +// std::array&) const; +// int GetContainingTriangle(Vector2 const&) const; + +namespace gte +{ + +template +class IntpQuadraticNonuniform2 +{ +public: + // Construction. + // + // The first constructor requires only F and a measure of the rate of + // change of the function values relative to changes in the spatial + // variables. The df/dx and df/dy values are estimated at the sample + // points using mesh normals and spatialDelta. + // + // The second constructor requires you to specify function values F and + // first-order partial derivative values df/dx and df/dy. + + IntpQuadraticNonuniform2(TriangleMesh const& mesh, Real const* F, + Real spatialDelta); + + IntpQuadraticNonuniform2(TriangleMesh const& mesh, Real const* F, + Real const* FX, Real const* FY); + + // Quadratic interpolation. The return value is 'true' if and only if the + // input point is in the convex hull of the input vertices, in which case + // the interpolation is valid. + bool operator()(Vector2 const & P, Real& F, Real& FX, Real& FY) + const; + +private: + void EstimateDerivatives(Real spatialDelta); + void ProcessTriangles(); + void ComputeCrossEdgeIntersections(int t); + void ComputeCoefficients(int t); + + class TriangleData + { + public: + Vector2 center; + Vector2 intersect[3]; + Real coeff[19]; + }; + + class Jet + { + public: + Real F, FX, FY; + }; + + TriangleMesh const* mMesh; + Real const* mF; + Real const* mFX; + Real const* mFY; + std::vector mFXStorage; + std::vector mFYStorage; + std::vector mTData; +}; + + +template +IntpQuadraticNonuniform2::IntpQuadraticNonuniform2( + TriangleMesh const& mesh, Real const* F, Real spatialDelta) + : + mMesh(&mesh), + mF(F), + mFX(nullptr), + mFY(nullptr) +{ + EstimateDerivatives(spatialDelta); + ProcessTriangles(); +} + +template +IntpQuadraticNonuniform2::IntpQuadraticNonuniform2( + TriangleMesh const& mesh, Real const* F, Real const* FX, Real const* FY) + : + mMesh(&mesh), + mF(F), + mFX(FX), + mFY(FY) +{ + ProcessTriangles(); +} + +template +bool IntpQuadraticNonuniform2::operator()( + Vector2 const& P, Real& F, Real& FX, Real& FY) const +{ + int t = mMesh->GetContainingTriangle(P); + if (t == -1) + { + // The point is outside the triangulation. + return false; + } + + // Get the vertices of the triangle. + std::array, 3> V; + mMesh->GetVertices(t, V); + + // Get the additional information for the triangle. + TriangleData const& tData = mTData[t]; + + // Determine which of the six subtriangles contains the target point. + // Theoretically, P must be in one of these subtriangles. + Vector2 sub0 = tData.center; + Vector2 sub1; + Vector2 sub2 = tData.intersect[2]; + Vector3 bary; + int index; + + Real const zero = (Real)0, one = (Real)1; + AlignedBox3 barybox({ zero, zero, zero }, { one, one, one }); + DCPQuery, AlignedBox3> pbQuery; + int minIndex = 0; + Real minDistance = (Real)-1; + Vector3 minBary; + Vector2 minSub0, minSub1, minSub2; + + for (index = 1; index <= 6; ++index) + { + sub1 = sub2; + if (index % 2) + { + sub2 = V[index / 2]; + } + else + { + sub2 = tData.intersect[index / 2 - 1]; + } + + bool valid = ComputeBarycentrics(P, sub0, sub1, sub2, &bary[0]); + if (valid + && zero <= bary[0] && bary[0] <= one + && zero <= bary[1] && bary[1] <= one + && zero <= bary[2] && bary[2] <= one) + { + // P is in triangle + break; + } + + // When computing with floating-point arithmetic, rounding errors + // can cause us to reach this code when, theoretically, the point + // is in the subtriangle. Keep track of the (b0,b1,b2) that is + // closest to the barycentric cube [0,1]^3 and choose the triangle + // corresponding to it when all 6 tests previously fail. + Real distance = pbQuery(bary, barybox).distance; + if (minIndex == 0 || distance < minDistance) + { + minDistance = distance; + minIndex = index; + minBary = bary; + minSub0 = sub0; + minSub1 = sub1; + minSub2 = sub2; + } + } + + // If the subtriangle was not found, rounding errors caused problems. + // Choose the barycentric point closest to the box. + if (index > 6) + { + index = minIndex; + bary = minBary; + sub0 = minSub0; + sub1 = minSub1; + sub2 = minSub2; + } + + // Fetch Bezier control points. + Real bez[6] = + { + tData.coeff[0], + tData.coeff[12 + index], + tData.coeff[13 + (index % 6)], + tData.coeff[index], + tData.coeff[6 + index], + tData.coeff[1 + (index % 6)] + }; + + // Evaluate Bezier quadratic. + F = bary[0] * (bez[0] * bary[0] + bez[1] * bary[1] + bez[2] * bary[2]) + + bary[1] * (bez[1] * bary[0] + bez[3] * bary[1] + bez[4] * bary[2]) + + bary[2] * (bez[2] * bary[0] + bez[4] * bary[1] + bez[5] * bary[2]); + + // Evaluate barycentric derivatives of F. + Real FU = ((Real)2)*(bez[0] * bary[0] + bez[1] * bary[1] + + bez[2] * bary[2]); + Real FV = ((Real)2)*(bez[1] * bary[0] + bez[3] * bary[1] + + bez[4] * bary[2]); + Real FW = ((Real)2)*(bez[2] * bary[0] + bez[4] * bary[1] + + bez[5] * bary[2]); + Real duw = FU - FW; + Real dvw = FV - FW; + + // Convert back to (x,y) coordinates. + Real m00 = sub0[0] - sub2[0]; + Real m10 = sub0[1] - sub2[1]; + Real m01 = sub1[0] - sub2[0]; + Real m11 = sub1[1] - sub2[1]; + Real inv = ((Real)1) / (m00*m11 - m10*m01); + + FX = inv*(m11*duw - m10*dvw); + FY = inv*(m00*dvw - m01*duw); + return true; +} + +template +void IntpQuadraticNonuniform2::EstimateDerivatives( + Real spatialDelta) +{ + int numVertices = mMesh->GetNumVertices(); + Vector2 const* vertices = mMesh->GetVertices(); + int numTriangles = mMesh->GetNumTriangles(); + int const* indices = mMesh->GetIndices(); + + mFXStorage.resize(numVertices); + mFYStorage.resize(numVertices); + std::vector FZ(numVertices); + std::fill(mFXStorage.begin(), mFXStorage.end(), (Real)0); + std::fill(mFYStorage.begin(), mFYStorage.end(), (Real)0); + std::fill(FZ.begin(), FZ.end(), (Real)0); + + mFX = &mFXStorage[0]; + mFY = &mFYStorage[0]; + + // Accumulate normals at spatial locations (averaging process). + for (int t = 0; t < numTriangles; ++t) + { + // Get three vertices of triangle. + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + + // Compute normal vector of triangle (with positive z-component). + Real dx1 = vertices[v1][0] - vertices[v0][0]; + Real dy1 = vertices[v1][1] - vertices[v0][1]; + Real dz1 = mF[v1] - mF[v0]; + Real dx2 = vertices[v2][0] - vertices[v0][0]; + Real dy2 = vertices[v2][1] - vertices[v0][1]; + Real dz2 = mF[v2] - mF[v0]; + Real nx = dy1*dz2 - dy2*dz1; + Real ny = dz1*dx2 - dz2*dx1; + Real nz = dx1*dy2 - dx2*dy1; + if (nz < (Real)0) + { + nx = -nx; + ny = -ny; + nz = -nz; + } + + mFXStorage[v0] += nx; mFYStorage[v0] += ny; FZ[v0] += nz; + mFXStorage[v1] += nx; mFYStorage[v1] += ny; FZ[v1] += nz; + mFXStorage[v2] += nx; mFYStorage[v2] += ny; FZ[v2] += nz; + } + + // Scale the normals to form (x,y,-1). + for (int i = 0; i < numVertices; ++i) + { + if (FZ[i] != (Real)0) + { + Real inv = -spatialDelta / FZ[i]; + mFXStorage[i] *= inv; + mFYStorage[i] *= inv; + } + else + { + mFXStorage[i] = (Real)0; + mFYStorage[i] = (Real)0; + } + } +} + +template +void IntpQuadraticNonuniform2::ProcessTriangles() +{ + // Add degenerate triangles to boundary triangles so that interpolation + // at the boundary can be treated in the same way as interpolation in + // the interior. + + // Compute centers of inscribed circles for triangles. + Vector2 const* vertices = mMesh->GetVertices(); + int numTriangles = mMesh->GetNumTriangles(); + int const* indices = mMesh->GetIndices(); + mTData.resize(numTriangles); + int t; + for (t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + Circle2 circle; + Inscribe(vertices[v0], vertices[v1], vertices[v2], circle); + mTData[t].center = circle.center; + } + + // Compute cross-edge intersections. + for (t = 0; t < numTriangles; ++t) + { + ComputeCrossEdgeIntersections(t); + } + + // Compute Bezier coefficients. + for (t = 0; t < numTriangles; ++t) + { + ComputeCoefficients(t); + } +} + +template +void IntpQuadraticNonuniform2:: +ComputeCrossEdgeIntersections(int t) +{ + // Get the vertices of the triangle. + std::array, 3> V; + mMesh->GetVertices(t, V); + + // Get the centers of adjacent triangles. + TriangleData& tData = mTData[t]; + std::array adjacencies; + mMesh->GetAdjacencies(t, adjacencies); + for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++) + { + int a = adjacencies[j0]; + if (a >= 0) + { + // Get center of adjacent triangle's inscribing circle. + Vector2 U = mTData[a].center; + Real m00 = V[j0][1] - V[j1][1]; + Real m01 = V[j1][0] - V[j0][0]; + Real m10 = tData.center[1] - U[1]; + Real m11 = U[0] - tData.center[0]; + Real r0 = m00*V[j0][0] + m01*V[j0][1]; + Real r1 = m10*tData.center[0] + m11*tData.center[1]; + Real invDet = ((Real)1) / (m00*m11 - m01*m10); + tData.intersect[j0][0] = (m11*r0 - m01*r1)*invDet; + tData.intersect[j0][1] = (m00*r1 - m10*r0)*invDet; + } + else + { + // No adjacent triangle, use center of edge. + tData.intersect[j0] = ((Real)0.5)*(V[j0] + V[j1]); + } + } +} + +template +void IntpQuadraticNonuniform2::ComputeCoefficients(int t) +{ + // Get the vertices of the triangle. + std::array, 3> V; + mMesh->GetVertices(t, V); + + // Get the additional information for the triangle. + TriangleData& tData = mTData[t]; + + // Get the sample data at main triangle vertices. + std::array indices; + mMesh->GetIndices(t, indices); + Jet jet[3]; + for (int j = 0; j < 3; ++j) + { + int k = indices[j]; + jet[j].F = mF[k]; + jet[j].FX = mFX[k]; + jet[j].FY = mFY[k]; + } + + // Get centers of adjacent triangles. + std::array adjacencies; + mMesh->GetAdjacencies(t, adjacencies); + Vector2 U[3]; + for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++) + { + int a = adjacencies[j0]; + if (a >= 0) + { + // Get center of adjacent triangle's circumscribing circle. + U[j0] = mTData[a].center; + } + else + { + // No adjacent triangle, use center of edge. + U[j0] = ((Real)0.5)*(V[j0] + V[j1]); + } + } + + // Compute intermediate terms. + std::array cenT, cen0, cen1, cen2; + mMesh->GetBarycentrics(t, tData.center, cenT); + mMesh->GetBarycentrics(t, U[0], cen0); + mMesh->GetBarycentrics(t, U[1], cen1); + mMesh->GetBarycentrics(t, U[2], cen2); + + Real alpha = (cenT[1] * cen1[0] - cenT[0] * cen1[1]) + / (cen1[0] - cenT[0]); + Real beta = (cenT[2] * cen2[1] - cenT[1] * cen2[2]) + / (cen2[1] - cenT[1]); + Real gamma = (cenT[0] * cen0[2] - cenT[2] * cen0[0]) + / (cen0[2] - cenT[2]); + Real oneMinusAlpha = (Real)1 - alpha; + Real oneMinusBeta = (Real)1 - beta; + Real oneMinusGamma = (Real)1 - gamma; + + Real tmp, A[9], B[9]; + + tmp = cenT[0] * V[0][0] + cenT[1] * V[1][0] + cenT[2] * V[2][0]; + A[0] = ((Real)0.5)*(tmp - V[0][0]); + A[1] = ((Real)0.5)*(tmp - V[1][0]); + A[2] = ((Real)0.5)*(tmp - V[2][0]); + A[3] = ((Real)0.5)*beta*(V[2][0] - V[0][0]); + A[4] = ((Real)0.5)*oneMinusGamma*(V[1][0] - V[0][0]); + A[5] = ((Real)0.5)*gamma*(V[0][0] - V[1][0]); + A[6] = ((Real)0.5)*oneMinusAlpha*(V[2][0] - V[1][0]); + A[7] = ((Real)0.5)*alpha*(V[1][0] - V[2][0]); + A[8] = ((Real)0.5)*oneMinusBeta*(V[0][0] - V[2][0]); + + tmp = cenT[0] * V[0][1] + cenT[1] * V[1][1] + cenT[2] * V[2][1]; + B[0] = ((Real)0.5)*(tmp - V[0][1]); + B[1] = ((Real)0.5)*(tmp - V[1][1]); + B[2] = ((Real)0.5)*(tmp - V[2][1]); + B[3] = ((Real)0.5)*beta*(V[2][1] - V[0][1]); + B[4] = ((Real)0.5)*oneMinusGamma*(V[1][1] - V[0][1]); + B[5] = ((Real)0.5)*gamma*(V[0][1] - V[1][1]); + B[6] = ((Real)0.5)*oneMinusAlpha*(V[2][1] - V[1][1]); + B[7] = ((Real)0.5)*alpha*(V[1][1] - V[2][1]); + B[8] = ((Real)0.5)*oneMinusBeta*(V[0][1] - V[2][1]); + + // Compute Bezier coefficients. + tData.coeff[2] = jet[0].F; + tData.coeff[4] = jet[1].F; + tData.coeff[6] = jet[2].F; + + tData.coeff[14] = jet[0].F + A[0] * jet[0].FX + B[0] * jet[0].FY; + tData.coeff[7] = jet[0].F + A[3] * jet[0].FX + B[3] * jet[0].FY; + tData.coeff[8] = jet[0].F + A[4] * jet[0].FX + B[4] * jet[0].FY; + tData.coeff[16] = jet[1].F + A[1] * jet[1].FX + B[1] * jet[1].FY; + tData.coeff[9] = jet[1].F + A[5] * jet[1].FX + B[5] * jet[1].FY; + tData.coeff[10] = jet[1].F + A[6] * jet[1].FX + B[6] * jet[1].FY; + tData.coeff[18] = jet[2].F + A[2] * jet[2].FX + B[2] * jet[2].FY; + tData.coeff[11] = jet[2].F + A[7] * jet[2].FX + B[7] * jet[2].FY; + tData.coeff[12] = jet[2].F + A[8] * jet[2].FX + B[8] * jet[2].FY; + + tData.coeff[5] = alpha*tData.coeff[10] + oneMinusAlpha*tData.coeff[11]; + tData.coeff[17] = alpha*tData.coeff[16] + oneMinusAlpha*tData.coeff[18]; + tData.coeff[1] = beta*tData.coeff[12] + oneMinusBeta*tData.coeff[7]; + tData.coeff[13] = beta*tData.coeff[18] + oneMinusBeta*tData.coeff[14]; + tData.coeff[3] = gamma*tData.coeff[8] + oneMinusGamma*tData.coeff[9]; + tData.coeff[15] = gamma*tData.coeff[14] + oneMinusGamma*tData.coeff[16]; + tData.coeff[0] = cenT[0] * tData.coeff[14] + cenT[1] * tData.coeff[16] + + cenT[2] * tData.coeff[18]; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpSphere2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpSphere2.h new file mode 100644 index 000000000000..a602f92fa2d0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpSphere2.h @@ -0,0 +1,137 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +// Interpolation of a scalar-valued function defined on a sphere. Although +// the sphere lives in 3D, the interpolation is a 2D method whose input +// points are angles (theta,phi) from spherical coordinates. The domains of +// the angles are -pi <= theta <= pi and 0 <= phi <= pi. + +namespace gte +{ + +template +class IntpSphere2 +{ +public: + // Construction and destruction. For complete spherical coverage, include + // the two antipodal (theta,phi) points (-pi,0,F(-pi,0)) and + // (-pi,pi,F(-pi,pi)) in the input data. These correspond to the sphere + // poles x = 0, y = 0, and |z| = 1. + ~IntpSphere2(); + IntpSphere2(int numPoints, InputType const* theta, InputType const* phi, + InputType const* F); + + // Spherical coordinates are + // x = cos(theta)*sin(phi) + // y = sin(theta)*sin(phi) + // z = cos(phi) + // for -pi <= theta <= pi, 0 <= phi <= pi. The application can use this + // function to convert unit length vectors (x,y,z) to (theta,phi). + static void GetSphericalCoordinates(InputType x, InputType y, InputType z, + InputType& theta, InputType& phi); + + // The return value is 'true' if and only if the input point is in the + // convex hull of the input (theta,pi) array, in which case the + // interpolation is valid. + bool operator()(InputType theta, InputType phi, InputType& F) const; + +private: + typedef Delaunay2Mesh TriangleMesh; + + std::vector> mWrapAngles; + Delaunay2 mDelaunay; + TriangleMesh mMesh; + std::vector mWrapF; + std::unique_ptr> mInterp; +}; + + +template +IntpSphere2::~IntpSphere2() +{ +} + +template +IntpSphere2::IntpSphere2(int numPoints, + InputType const* theta, InputType const* phi, InputType const* F) + : + mMesh(mDelaunay) +{ + // Copy the input data. The larger arrays are used to support wrap-around + // in the Delaunay triangulation for the interpolator. + int totalPoints = 3 * numPoints; + mWrapAngles.resize(totalPoints); + mWrapF.resize(totalPoints); + for (int i = 0; i < numPoints; ++i) + { + mWrapAngles[i][0] = theta[i]; + mWrapAngles[i][1] = phi[i]; + mWrapF[i] = F[i]; + } + + // Use periodicity to get wrap-around in the Delaunay triangulation. + int i0 = 0, i1 = numPoints, i2 = 2 * numPoints; + for (/**/; i0 < numPoints; ++i0, ++i1, ++i2) + { + mWrapAngles[i1][0] = mWrapAngles[i0][0] + (InputType)GTE_C_TWO_PI; + mWrapAngles[i2][0] = mWrapAngles[i0][0] - (InputType)GTE_C_TWO_PI; + mWrapAngles[i1][1] = mWrapAngles[i0][1]; + mWrapAngles[i2][1] = mWrapAngles[i0][1]; + mWrapF[i1] = mWrapF[i0]; + mWrapF[i2] = mWrapF[i0]; + } + + mDelaunay(totalPoints, &mWrapAngles[0], (ComputeType)0); + mInterp = std::make_unique>( + mMesh, &mWrapF[0], (InputType)1); +} + +template +void IntpSphere2:: +GetSphericalCoordinates(InputType x, InputType y, InputType z, +InputType& theta, InputType& phi) +{ + // Assumes (x,y,z) is unit length. Returns -pi <= theta <= pi and + // 0 <= phiAngle <= pi. + + if (z < (InputType)1) + { + if (z > -(InputType)1) + { + theta = std::atan2(y, x); + phi = std::acos(z); + } + else + { + theta = -(InputType)GTE_C_PI; + phi = (InputType)GTE_C_PI; + } + } + else + { + theta = -(InputType)GTE_C_PI; + phi = (InputType)0; + } +} + +template +bool IntpSphere2::operator()( + InputType theta, InputType phi, InputType& F) const +{ + Vector2 angles{ theta, phi }; + InputType thetaDeriv, phiDeriv; + return (*mInterp)(angles, F, thetaDeriv, phiDeriv); +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpThinPlateSpline2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpThinPlateSpline2.h new file mode 100644 index 000000000000..463b085a9e93 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpThinPlateSpline2.h @@ -0,0 +1,295 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +// WARNING. The implementation allows you to transform the inputs (x,y) to +// the unit square and perform the interpolation in that space. The idea is +// to keep the floating-point numbers to order 1 for numerical stability of +// the algorithm. The classical thin-plate spline algorithm does not include +// this transformation. The interpolation is invariant to translations and +// rotations of (x,y) but not to scaling. + +namespace gte +{ + +template +class IntpThinPlateSpline2 +{ +public: + // Construction. Data points are (x,y,f(x,y)). The smoothing parameter + // must be nonnegative. + IntpThinPlateSpline2(int numPoints, Real const* X, Real const* Y, + Real const* F, Real smooth, bool transformToUnitSquare); + + // Check this after the constructor call to see whether the thin plate + // spline coefficients were successfully computed. If so, then calls to + // operator()(Real,Real) will work properly. + inline bool IsInitialized() const; + + // Evaluate the interpolator. If IsInitialized() returns 'false', the + // operator will return std::numeric_limits::max(). + Real operator()(Real x, Real y) const; + + // Compute the functional value a^T*M*a when lambda is zero or + // lambda*w^T*(M+lambda*I)*w when lambda is positive. See the thin plate + // splines PDF for a description of these quantities. + Real ComputeFunctional() const; + +private: + // Kernel(t) = t^2 * log(t^2) + static Real Kernel(Real t); + + // Input data. + int mNumPoints; + std::vector mX; + std::vector mY; + Real mSmooth; + + // Thin plate spline coefficients. The A[] coefficients are associated + // with the Green's functions G(x,y,*) and the B[] coefficients are + // associated with the affine term B[0] + B[1]*x + B[2]*y. + std::vector mA; // mNumPoints elements + Real mB[3]; + + // Extent of input data. + Real mXMin, mXMax, mXInvRange; + Real mYMin, mYMax, mYInvRange; + + bool mInitialized; +}; + + +template +IntpThinPlateSpline2::IntpThinPlateSpline2(int numPoints, Real const* X, + Real const* Y, Real const* F, Real smooth, bool transformToUnitSquare) + : + mNumPoints(numPoints), + mX(numPoints), + mY(numPoints), + mSmooth(smooth), + mA(numPoints), + mInitialized(false) +{ + if (numPoints < 3 || !X || !Y || !F || smooth < (Real)0) + { + LogError("Invalid input."); + return; + } + + int i, row, col; + + if (transformToUnitSquare) + { + // Map input (x,y) to unit square. This is not part of the classical + // thin-plate spline algorithm because the interpolation is not + // invariant to scalings. + auto extreme = std::minmax_element(X, X + mNumPoints); + mXMin = *extreme.first; + mXMax = *extreme.second; + mXInvRange = ((Real)1) / (mXMax - mXMin); + for (i = 0; i < mNumPoints; ++i) + { + mX[i] = (X[i] - mXMin) * mXInvRange; + } + + extreme = std::minmax_element(Y, Y + mNumPoints); + mYMin = *extreme.first; + mYMax = *extreme.second; + mYInvRange = ((Real)1) / (mYMax - mYMin); + for (i = 0; i < mNumPoints; ++i) + { + mY[i] = (Y[i] - mYMin) * mYInvRange; + } + } + else + { + // The classical thin-plate spline uses the data as is. The values + // mXMax and mYMax are not used, but they are initialized anyway + // (to irrelevant numbers). + mXMin = (Real)0; + mXMax = (Real)1; + mXInvRange = (Real)1; + mYMin = (Real)0; + mYMax = (Real)1; + mYInvRange = (Real)1; + std::copy(X, X + mNumPoints, mX.begin()); + std::copy(Y, Y + mNumPoints, mY.begin()); + } + + // Compute matrix A = M + lambda*I [NxN matrix]. + GMatrix AMat(mNumPoints, mNumPoints); + for (row = 0; row < mNumPoints; ++row) + { + for (col = 0; col < mNumPoints; ++col) + { + if (row == col) + { + AMat(row, col) = mSmooth; + } + else + { + Real dx = mX[row] - mX[col]; + Real dy = mY[row] - mY[col]; + Real t = std::sqrt(dx*dx + dy*dy); + AMat(row, col) = Kernel(t); + } + } + } + + // Compute matrix B [Nx3 matrix]. + GMatrix BMat(mNumPoints, 3); + for (row = 0; row < mNumPoints; ++row) + { + BMat(row, 0) = (Real)1; + BMat(row, 1) = mX[row]; + BMat(row, 2) = mY[row]; + } + + // Compute A^{-1}. + bool invertible; + GMatrix invAMat = Inverse(AMat, &invertible); + if (!invertible) + { + return; + } + + // Compute P = B^T A^{-1} [3xN matrix]. + GMatrix PMat = MultiplyATB(BMat, invAMat); + + // Compute Q = P B = B^T A^{-1} B [3x3 matrix]. + GMatrix QMat = PMat * BMat; + + // Compute Q^{-1}. + GMatrix invQMat = Inverse(QMat, &invertible); + if (!invertible) + { + return; + } + + // Compute P*z. + Real prod[3]; + for (row = 0; row < 3; ++row) + { + prod[row] = (Real)0; + for (i = 0; i < mNumPoints; ++i) + { + prod[row] += PMat(row, i) * F[i]; + } + } + + // Compute 'b' vector for smooth thin plate spline. + for (row = 0; row < 3; ++row) + { + mB[row] = (Real)0; + for (i = 0; i < 3; ++i) + { + mB[row] += invQMat(row, i) * prod[i]; + } + } + + // Compute z-B*b. + std::vector tmp(mNumPoints); + for (row = 0; row < mNumPoints; ++row) + { + tmp[row] = F[row]; + for (i = 0; i < 3; ++i) + { + tmp[row] -= BMat(row, i) * mB[i]; + } + } + + // Compute 'a' vector for smooth thin plate spline. + for (row = 0; row < mNumPoints; ++row) + { + mA[row] = (Real)0; + for (i = 0; i < mNumPoints; ++i) + { + mA[row] += invAMat(row, i) * tmp[i]; + } + } + + mInitialized = true; +} + +template inline +bool IntpThinPlateSpline2::IsInitialized() const +{ + return mInitialized; +} + +template +Real IntpThinPlateSpline2::operator()(Real x, Real y) const +{ + if (mInitialized) + { + // Map (x,y) to the unit square. + x = (x - mXMin) * mXInvRange; + y = (y - mYMin) * mYInvRange; + + Real result = mB[0] + mB[1] * x + mB[2] * y; + for (int i = 0; i < mNumPoints; ++i) + { + Real dx = x - mX[i]; + Real dy = y - mY[i]; + Real t = std::sqrt(dx*dx + dy*dy); + result += mA[i] * Kernel(t); + } + return result; + } + + return std::numeric_limits::max(); +} + +template +Real IntpThinPlateSpline2::ComputeFunctional() const +{ + Real functional = (Real)0; + for (int row = 0; row < mNumPoints; ++row) + { + for (int col = 0; col < mNumPoints; ++col) + { + if (row == col) + { + functional += mSmooth * mA[row] * mA[col]; + } + else + { + Real dx = mX[row] - mX[col]; + Real dy = mY[row] - mY[col]; + Real t = std::sqrt(dx * dx + dy * dy); + functional += Kernel(t) * mA[row] * mA[col]; + } + } + } + + if (mSmooth > (Real)0) + { + functional *= mSmooth; + } + + return functional; +} + +template +Real IntpThinPlateSpline2::Kernel(Real t) +{ + if (t > (Real)0) + { + Real t2 = t * t; + return t2 * std::log(t2); + } + return (Real)0; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpThinPlateSpline3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpThinPlateSpline3.h new file mode 100644 index 000000000000..3c5e8adfc7ab --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpThinPlateSpline3.h @@ -0,0 +1,312 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// WARNING. The implementation allows you to transform the inputs (x,y,z) to +// the unit cube and perform the interpolation in that space. The idea is +// to keep the floating-point numbers to order 1 for numerical stability of +// the algorithm. The classical thin-plate spline algorithm does not include +// this transformation. The interpolation is invariant to translations and +// rotations of (x,y,z) but not to scaling. + +namespace gte +{ + +template +class IntpThinPlateSpline3 +{ +public: + // Construction. Data points are (x,y,z,f(x,y,z)). The smoothing parameter + // must be nonnegative + IntpThinPlateSpline3(int numPoints, Real const* X, Real const* Y, + Real const* Z, Real const* F, Real smooth, bool transformToUnitCube); + + // Check this after the constructor call to see whether the thin plate + // spline coefficients were successfully computed. If so, then calls to + // operator()(Real,Real,Real) will work properly. + inline bool IsInitialized() const; + + // Evaluate the interpolator. If IsInitialized()returns 'false', the + // operator will return std::numeric_limits::max(). + Real operator()(Real x, Real y, Real z) const; + + // Compute the functional value a^T*M*a when lambda is zero or + // lambda*w^T*(M+lambda*I)*w when lambda is positive. See the thin plate + // splines PDF for a description of these quantities. + Real ComputeFunctional() const; + +private: + // Kernel(t) = -|t| + static Real Kernel(Real t); + + // Input data. + int mNumPoints; + std::vector mX; + std::vector mY; + std::vector mZ; + Real mSmooth; + + // Thin plate spline coefficients. The A[] coefficients are associated + // with the Green's functions G(x,y,z,*) and the B[] coefficients are + // associated with the affine term B[0] + B[1]*x + B[2]*y + B[3]*z. + std::vector mA; // mNumPoints elements + Real mB[4]; + + // Extent of input data. + Real mXMin, mXMax, mXInvRange; + Real mYMin, mYMax, mYInvRange; + Real mZMin, mZMax, mZInvRange; + + bool mInitialized; +}; + + +template +IntpThinPlateSpline3::IntpThinPlateSpline3(int numPoints, Real const* X, + Real const* Y, Real const* Z, Real const* F, Real smooth, + bool transformToUnitCube) + : + mNumPoints(numPoints), + mX(numPoints), + mY(numPoints), + mZ(numPoints), + mSmooth(smooth), + mA(numPoints), + mInitialized(false) +{ + if (numPoints < 4 || !X || !Y || !Z || !F || smooth < (Real)0) + { + LogError("Invalid input."); + return; + } + + int i, row, col; + + if (transformToUnitCube) + { + // Map input (x,y,z) to unit cube. This is not part of the classical + // thin-plate spline algorithm, because the interpolation is not + // invariant to scalings. + auto extreme = std::minmax_element(X, X + mNumPoints); + mXMin = *extreme.first; + mXMax = *extreme.second; + mXInvRange = ((Real)1) / (mXMax - mXMin); + for (i = 0; i < mNumPoints; ++i) + { + mX[i] = (X[i] - mXMin) * mXInvRange; + } + + extreme = std::minmax_element(Y, Y + mNumPoints); + mYMin = *extreme.first; + mYMax = *extreme.second; + mYInvRange = ((Real)1) / (mYMax - mYMin); + for (i = 0; i < mNumPoints; ++i) + { + mY[i] = (Y[i] - mYMin) * mYInvRange; + } + + extreme = std::minmax_element(Z, Z + mNumPoints); + mZMin = *extreme.first; + mZMax = *extreme.second; + mZInvRange = ((Real)1) / (mZMax - mZMin); + for (i = 0; i < mNumPoints; ++i) + { + mZ[i] = (Z[i] - mZMin) * mZInvRange; + } + } + else + { + // The classical thin-plate spline uses the data as is. The values + // mXMax, mYMax, and mZMax are not used, but they are initialized + // anyway (to irrelevant numbers). + mXMin = (Real)0; + mXMax = (Real)1; + mXInvRange = (Real)1; + mYMin = (Real)0; + mYMax = (Real)1; + mYInvRange = (Real)1; + mZMin = (Real)0; + mZMax = (Real)1; + mZInvRange = (Real)1; + std::copy(X, X + mNumPoints, mX.begin()); + std::copy(Y, Y + mNumPoints, mY.begin()); + std::copy(Z, Z + mNumPoints, mZ.begin()); + } + + // Compute matrix A = M + lambda*I [NxN matrix]. + GMatrix AMat(mNumPoints, mNumPoints); + for (row = 0; row < mNumPoints; ++row) + { + for (col = 0; col < mNumPoints; ++col) + { + if (row == col) + { + AMat(row, col) = mSmooth; + } + else + { + Real dx = mX[row] - mX[col]; + Real dy = mY[row] - mY[col]; + Real dz = mZ[row] - mZ[col]; + Real t = std::sqrt(dx * dx + dy * dy + dz * dz); + AMat(row, col) = Kernel(t); + } + } + } + + // Compute matrix B [Nx4 matrix]. + GMatrix BMat(mNumPoints, 4); + for (row = 0; row < mNumPoints; ++row) + { + BMat(row, 0) = (Real)1; + BMat(row, 1) = mX[row]; + BMat(row, 2) = mY[row]; + BMat(row, 3) = mZ[row]; + } + + // Compute A^{-1}. + bool invertible; + GMatrix invAMat = Inverse(AMat, &invertible); + if (!invertible) + { + return; + } + + // Compute P = B^t A^{-1} [4xN matrix]. + GMatrix PMat = MultiplyATB(BMat, invAMat); + + // Compute Q = P B = B^t A^{-1} B [4x4 matrix]. + GMatrix QMat = PMat * BMat; + + // Compute Q^{-1}. + GMatrix invQMat = Inverse(QMat, &invertible); + if (!invertible) + { + return; + } + + // Compute P*w. + Real prod[4]; + for (row = 0; row < 4; ++row) + { + prod[row] = (Real)0; + for (i = 0; i < mNumPoints; ++i) + { + prod[row] += PMat(row, i) * F[i]; + } + } + + // Compute 'b' vector for smooth thin plate spline. + for (row = 0; row < 4; ++row) + { + mB[row] = (Real)0; + for (i = 0; i < 4; ++i) + { + mB[row] += invQMat(row, i) * prod[i]; + } + } + + // Compute w-B*b. + std::vector tmp(mNumPoints); + for (row = 0; row < mNumPoints; ++row) + { + tmp[row] = F[row]; + for (i = 0; i < 4; ++i) + { + tmp[row] -= BMat(row, i) * mB[i]; + } + } + + // Compute 'a' vector for smooth thin plate spline. + for (row = 0; row < mNumPoints; ++row) + { + mA[row] = (Real)0; + for (i = 0; i < mNumPoints; ++i) + { + mA[row] += invAMat(row, i) * tmp[i]; + } + } + + mInitialized = true; +} + +template +bool IntpThinPlateSpline3::IsInitialized() const +{ + return mInitialized; +} + +template +Real IntpThinPlateSpline3::operator()(Real x, Real y, Real z) const +{ + if (mInitialized) + { + // Map (x,y,z) to the unit cube. + x = (x - mXMin) * mXInvRange; + y = (y - mYMin) * mYInvRange; + z = (z - mZMin) * mZInvRange; + + Real result = mB[0] + mB[1] * x + mB[2] * y + mB[3] * z; + for (int i = 0; i < mNumPoints; ++i) + { + Real dx = x - mX[i]; + Real dy = y - mY[i]; + Real dz = z - mZ[i]; + Real t = std::sqrt(dx*dx + dy*dy + dz*dz); + result += mA[i] * Kernel(t); + } + return result; + } + + return std::numeric_limits::max(); +} + +template +Real IntpThinPlateSpline3::ComputeFunctional() const +{ + Real functional = (Real)0; + for (int row = 0; row < mNumPoints; ++row) + { + for (int col = 0; col < mNumPoints; ++col) + { + if (row == col) + { + functional += mSmooth * mA[row] * mA[col]; + } + else + { + Real dx = mX[row] - mX[col]; + Real dy = mY[row] - mY[col]; + Real dz = mZ[row] - mZ[col]; + Real t = std::sqrt(dx * dx + dy * dy + dz * dz); + functional += Kernel(t) * mA[row] * mA[col]; + } + } + } + + if (mSmooth > (Real)0) + { + functional *= mSmooth; + } + + return functional; +} + +template +Real IntpThinPlateSpline3::Kernel(Real t) +{ + return -std::abs(t); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpTricubic3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpTricubic3.h new file mode 100644 index 000000000000..05d7248472de --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpTricubic3.h @@ -0,0 +1,570 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The interpolator is for uniformly spaced(x,y z)-values. The input samples +// must be stored in lexicographical order to represent f(x,y,z); that is, +// F[c + xBound*(r + yBound*s)] corresponds to f(x,y,z), where c is the index +// corresponding to x, r is the index corresponding to y, and s is the index +// corresponding to z. Exact interpolation is achieved by setting catmullRom +// to 'true', giving you the Catmull-Rom blending matrix. If a smooth +// interpolation is desired, set catmullRom to 'false' to obtain B-spline +// blending. + +namespace gte +{ + +template +class IntpTricubic3 +{ +public: + // Construction. + IntpTricubic3(int xBound, int yBound, int zBound, Real xMin, + Real xSpacing, Real yMin, Real ySpacing, Real zMin, Real zSpacing, + Real const* F, bool catmullRom); + + // Member access. + inline int GetXBound() const; + inline int GetYBound() const; + inline int GetZBound() const; + inline int GetQuantity() const; + inline Real const* GetF() const; + inline Real GetXMin() const; + inline Real GetXMax() const; + inline Real GetXSpacing() const; + inline Real GetYMin() const; + inline Real GetYMax() const; + inline Real GetYSpacing() const; + inline Real GetZMin() const; + inline Real GetZMax() const; + inline Real GetZSpacing() const; + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax, ymin <= y <= ymax, and zmin <= z <= zmax. + // The first operator is for function evaluation. The second operator is + // for function or derivative evaluations. The xOrder argument is the + // order of the x-derivative, the yOrder argument is the order of the + // y-derivative, and the zOrder argument is the order of the z-derivative. + // All orders are zero to get the function value itself. + Real operator()(Real x, Real y, Real z) const; + Real operator()(int xOrder, int yOrder, int zOrder, Real x, Real y, + Real z) const; + +private: + int mXBound, mYBound, mZBound, mQuantity; + Real mXMin, mXMax, mXSpacing, mInvXSpacing; + Real mYMin, mYMax, mYSpacing, mInvYSpacing; + Real mZMin, mZMax, mZSpacing, mInvZSpacing; + Real const* mF; + Real mBlend[4][4]; +}; + + +template +IntpTricubic3::IntpTricubic3(int xBound, int yBound, int zBound, + Real xMin, Real xSpacing, Real yMin, Real ySpacing, Real zMin, + Real zSpacing, Real const* F, bool catmullRom) + : + mXBound(xBound), + mYBound(yBound), + mZBound(zBound), + mQuantity(xBound * yBound * zBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mZMin(zMin), + mZSpacing(zSpacing), + mF(F) +{ + // At least a 4x4x4 block of data points are needed to construct the + // tricubic interpolation. + LogAssert(mXBound >= 4 && mYBound >= 4 && mZBound >= 4 && mF, + "Invalid input."); + LogAssert(mXSpacing > (Real)0 && mYSpacing > (Real)0 && + mZSpacing > (Real)0, "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mInvXSpacing = ((Real)1) / mXSpacing; + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + mInvYSpacing = ((Real)1) / mYSpacing; + mZMax = mZMin + mZSpacing * static_cast(mZBound - 1); + mInvZSpacing = ((Real)1) / mZSpacing; + + if (catmullRom) + { + mBlend[0][0] = (Real)0; + mBlend[0][1] = (Real)-0.5; + mBlend[0][2] = (Real)1; + mBlend[0][3] = (Real)-0.5; + mBlend[1][0] = (Real)1; + mBlend[1][1] = (Real)0; + mBlend[1][2] = (Real)-2.5; + mBlend[1][3] = (Real)1.5; + mBlend[2][0] = (Real)0; + mBlend[2][1] = (Real)0.5; + mBlend[2][2] = (Real)2; + mBlend[2][3] = (Real)-1.5; + mBlend[3][0] = (Real)0; + mBlend[3][1] = (Real)0; + mBlend[3][2] = (Real)-0.5; + mBlend[3][3] = (Real)0.5; + } + else + { + mBlend[0][0] = (Real)1 / (Real)6; + mBlend[0][1] = (Real)-3 / (Real)6; + mBlend[0][2] = (Real)3 / (Real)6; + mBlend[0][3] = (Real)-1 / (Real)6;; + mBlend[1][0] = (Real)4 / (Real)6; + mBlend[1][1] = (Real)0 / (Real)6; + mBlend[1][2] = (Real)-6 / (Real)6; + mBlend[1][3] = (Real)3 / (Real)6; + mBlend[2][0] = (Real)1 / (Real)6; + mBlend[2][1] = (Real)3 / (Real)6; + mBlend[2][2] = (Real)3 / (Real)6; + mBlend[2][3] = (Real)-3 / (Real)6; + mBlend[3][0] = (Real)0 / (Real)6; + mBlend[3][1] = (Real)0 / (Real)6; + mBlend[3][2] = (Real)0 / (Real)6; + mBlend[3][3] = (Real)1 / (Real)6; + } +} + +template inline +int IntpTricubic3::GetXBound() const +{ + return mXBound; +} + +template inline +int IntpTricubic3::GetYBound() const +{ + return mYBound; +} + +template inline +int IntpTricubic3::GetZBound() const +{ + return mZBound; +} + +template inline +int IntpTricubic3::GetQuantity() const +{ + return mQuantity; +} + +template inline +Real const* IntpTricubic3::GetF() const +{ + return mF; +} + +template inline +Real IntpTricubic3::GetXMin() const +{ + return mXMin; +} + +template inline +Real IntpTricubic3::GetXMax() const +{ + return mXMax; +} + +template inline +Real IntpTricubic3::GetXSpacing() const +{ + return mXSpacing; +} + +template inline +Real IntpTricubic3::GetYMin() const +{ + return mYMin; +} + +template inline +Real IntpTricubic3::GetYMax() const +{ + return mYMax; +} + +template inline +Real IntpTricubic3::GetYSpacing() const +{ + return mYSpacing; +} + +template inline +Real IntpTricubic3::GetZMin() const +{ + return mZMin; +} + +template inline +Real IntpTricubic3::GetZMax() const +{ + return mZMax; +} + +template inline +Real IntpTricubic3::GetZSpacing() const +{ + return mZSpacing; +} + +template +Real IntpTricubic3::operator()(Real x, Real y, Real z) const +{ + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + // Compute z-index and clamp to image. + Real zIndex = (z - mZMin) * mInvZSpacing; + int iz = static_cast(zIndex); + if (iz < 0) + { + iz = 0; + } + else if (iz >= mZBound) + { + iz = mZBound - 1; + } + + Real U[4]; + U[0] = (Real)1; + U[1] = xIndex - ix; + U[2] = U[1] * U[1]; + U[3] = U[1] * U[2]; + + Real V[4]; + V[0] = (Real)1; + V[1] = yIndex - iy; + V[2] = V[1] * V[1]; + V[3] = V[1] * V[2]; + + Real W[4]; + W[0] = (Real)1; + W[1] = zIndex - iz; + W[2] = W[1] * W[1]; + W[3] = W[1] * W[2]; + + // Compute P = M*U, Q = M*V, R = M*W. + Real P[4], Q[4], R[4]; + for (int row = 0; row < 4; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + R[row] = (Real)0; + for (int col = 0; col < 4; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + R[row] += mBlend[row][col] * W[col]; + } + } + + // Compute the tensor product (M*U)(M*V)(M*W)*D where D is the 4x4x4 + // subimage containing (x,y,z). + --ix; + --iy; + --iz; + Real result = (Real)0; + for (int slice = 0; slice < 4; ++slice) + { + int zClamp = iz + slice; + if (zClamp < 0) + { + zClamp = 0; + } + else if (zClamp > mZBound - 1) + { + zClamp = mZBound - 1; + } + + for (int row = 0; row < 4; ++row) + { + int yClamp = iy + row; + if (yClamp < 0) + { + yClamp = 0; + } + else if (yClamp > mYBound - 1) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 4; ++col) + { + int xClamp = ix + col; + if (xClamp < 0) + { + xClamp = 0; + } + else if (xClamp > mXBound - 1) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * R[slice] * + mF[xClamp + mXBound * (yClamp + mYBound * zClamp)]; + } + } + } + + return result; +} + +template +Real IntpTricubic3::operator()(int xOrder, int yOrder, int zOrder, + Real x, Real y, Real z) const +{ + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + // Compute z-index and clamp to image. + Real zIndex = (z - mZMin) * mInvZSpacing; + int iz = static_cast(zIndex); + if (iz < 0) + { + iz = 0; + } + else if (iz >= mZBound) + { + iz = mZBound - 1; + } + + Real U[4], dx, xMult; + switch (xOrder) + { + case 0: + dx = xIndex - ix; + U[0] = (Real)1; + U[1] = dx; + U[2] = dx * U[1]; + U[3] = dx * U[2]; + xMult = (Real)1; + break; + case 1: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)1; + U[2] = ((Real)2) * dx; + U[3] = ((Real)3) * dx * dx; + xMult = mInvXSpacing; + break; + case 2: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)0; + U[2] = (Real)2; + U[3] = ((Real)6) * dx; + xMult = mInvXSpacing * mInvXSpacing; + break; + case 3: + U[0] = (Real)0; + U[1] = (Real)0; + U[2] = (Real)0; + U[3] = (Real)6; + xMult = mInvXSpacing * mInvXSpacing * mInvXSpacing; + break; + default: + return (Real)0; + } + + Real V[4], dy, yMult; + switch (yOrder) + { + case 0: + dy = yIndex - iy; + V[0] = (Real)1; + V[1] = dy; + V[2] = dy * V[1]; + V[3] = dy * V[2]; + yMult = (Real)1; + break; + case 1: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)1; + V[2] = ((Real)2) * dy; + V[3] = ((Real)3) * dy * dy; + yMult = mInvYSpacing; + break; + case 2: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)0; + V[2] = (Real)2; + V[3] = ((Real)6) * dy; + yMult = mInvYSpacing * mInvYSpacing; + break; + case 3: + V[0] = (Real)0; + V[1] = (Real)0; + V[2] = (Real)0; + V[3] = (Real)6; + yMult = mInvYSpacing * mInvYSpacing * mInvYSpacing; + break; + default: + return (Real)0; + } + + Real W[4], dz, zMult; + switch (zOrder) + { + case 0: + dz = zIndex - iz; + W[0] = (Real)1; + W[1] = dz; + W[2] = dz * W[1]; + W[3] = dz * W[2]; + zMult = (Real)1; + break; + case 1: + dz = zIndex - iz; + W[0] = (Real)0; + W[1] = (Real)1; + W[2] = ((Real)2) * dz; + W[3] = ((Real)3) * dz * dz; + zMult = mInvZSpacing; + break; + case 2: + dz = zIndex - iz; + W[0] = (Real)0; + W[1] = (Real)0; + W[2] = (Real)2; + W[3] = ((Real)6) * dz; + zMult = mInvZSpacing * mInvZSpacing; + break; + case 3: + W[0] = (Real)0; + W[1] = (Real)0; + W[2] = (Real)0; + W[3] = (Real)6; + zMult = mInvZSpacing * mInvZSpacing * mInvZSpacing; + break; + default: + return (Real)0; + } + + // Compute P = M*U, Q = M*V, and R = M*W. + Real P[4], Q[4], R[4]; + for (int row = 0; row < 4; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + R[row] = (Real)0; + for (int col = 0; col < 4; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + R[row] += mBlend[row][col] * W[col]; + } + } + + // Compute the tensor product (M*U)(M*V)(M*W)*D where D is the 4x4x4 + // subimage containing (x,y,z). + --ix; + --iy; + --iz; + Real result = (Real)0; + for (int slice = 0; slice < 4; ++slice) + { + int zClamp = iz + slice; + if (zClamp < 0) + { + zClamp = 0; + } + else if (zClamp > mZBound - 1) + { + zClamp = mZBound - 1; + } + + for (int row = 0; row < 4; ++row) + { + int yClamp = iy + row; + if (yClamp < 0) + { + yClamp = 0; + } + else if (yClamp > mYBound - 1) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 4; ++col) + { + int xClamp = ix + col; + if (xClamp < 0) + { + xClamp = 0; + } + else if (xClamp > mXBound - 1) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * R[slice] * + mF[xClamp + mXBound * (yClamp + mYBound * zClamp)]; + } + } + } + result *= xMult * yMult * zMult; + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpTrilinear3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpTrilinear3.h new file mode 100644 index 000000000000..505bbe854c9f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpTrilinear3.h @@ -0,0 +1,440 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The interpolator is for uniformly spaced(x,y z)-values. The input samples +// must be stored in lexicographical order to represent f(x,y,z); that is, +// F[c + xBound*(r + yBound*s)] corresponds to f(x,y,z), where c is the index +// corresponding to x, r is the index corresponding to y, and s is the index +// corresponding to z. + +namespace gte +{ + +template +class IntpTrilinear3 +{ +public: + // Construction. + IntpTrilinear3(int xBound, int yBound, int zBound, Real xMin, + Real xSpacing, Real yMin, Real ySpacing, Real zMin, Real zSpacing, + Real const* F); + + // Member access. + inline int GetXBound() const; + inline int GetYBound() const; + inline int GetZBound() const; + inline int GetQuantity() const; + inline Real const* GetF() const; + inline Real GetXMin() const; + inline Real GetXMax() const; + inline Real GetXSpacing() const; + inline Real GetYMin() const; + inline Real GetYMax() const; + inline Real GetYSpacing() const; + inline Real GetZMin() const; + inline Real GetZMax() const; + inline Real GetZSpacing() const; + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax, ymin <= y <= ymax, and zmin <= z <= zmax. + // The first operator is for function evaluation. The second operator is + // for function or derivative evaluations. The xOrder argument is the + // order of the x-derivative, the yOrder argument is the order of the + // y-derivative, and the zOrder argument is the order of the z-derivative. + // All orders are zero to get the function value itself. + Real operator()(Real x, Real y, Real z) const; + Real operator()(int xOrder, int yOrder, int zOrder, Real x, Real y, + Real z) const; + +private: + int mXBound, mYBound, mZBound, mQuantity; + Real mXMin, mXMax, mXSpacing, mInvXSpacing; + Real mYMin, mYMax, mYSpacing, mInvYSpacing; + Real mZMin, mZMax, mZSpacing, mInvZSpacing; + Real const* mF; + Real mBlend[2][2]; +}; + + +template +IntpTrilinear3::IntpTrilinear3(int xBound, int yBound, int zBound, + Real xMin, Real xSpacing, Real yMin, Real ySpacing, Real zMin, + Real zSpacing, Real const* F) + : + mXBound(xBound), + mYBound(yBound), + mZBound(zBound), + mQuantity(xBound * yBound * zBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mZMin(zMin), + mZSpacing(zSpacing), + mF(F) +{ + // At least a 2x2x2 block of data points are needed to construct the + // trilinear interpolation. + LogAssert(mXBound >= 2 && mYBound >= 2 && mZBound >= 2 && mF, + "Invalid input."); + LogAssert(mXSpacing > (Real)0 && mYSpacing > (Real)0 && + mZSpacing > (Real)0, "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mInvXSpacing = ((Real)1) / mXSpacing; + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + mInvYSpacing = ((Real)1) / mYSpacing; + mZMax = mZMin + mZSpacing * static_cast(mZBound - 1); + mInvZSpacing = ((Real)1) / mZSpacing; + + mBlend[0][0] = (Real)1; + mBlend[0][1] = (Real)-1; + mBlend[1][0] = (Real)0; + mBlend[1][1] = (Real)1; +} + +template inline +int IntpTrilinear3::GetXBound() const +{ + return mXBound; +} + +template inline +int IntpTrilinear3::GetYBound() const +{ + return mYBound; +} + +template inline +int IntpTrilinear3::GetZBound() const +{ + return mZBound; +} + +template inline +int IntpTrilinear3::GetQuantity() const +{ + return mQuantity; +} + +template inline +Real const* IntpTrilinear3::GetF() const +{ + return mF; +} + +template inline +Real IntpTrilinear3::GetXMin() const +{ + return mXMin; +} + +template inline +Real IntpTrilinear3::GetXMax() const +{ + return mXMax; +} + +template inline +Real IntpTrilinear3::GetXSpacing() const +{ + return mXSpacing; +} + +template inline +Real IntpTrilinear3::GetYMin() const +{ + return mYMin; +} + +template inline +Real IntpTrilinear3::GetYMax() const +{ + return mYMax; +} + +template inline +Real IntpTrilinear3::GetYSpacing() const +{ + return mYSpacing; +} + +template inline +Real IntpTrilinear3::GetZMin() const +{ + return mZMin; +} + +template inline +Real IntpTrilinear3::GetZMax() const +{ + return mZMax; +} + +template inline +Real IntpTrilinear3::GetZSpacing() const +{ + return mZSpacing; +} + +template +Real IntpTrilinear3::operator()(Real x, Real y, Real z) const +{ + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + // Compute z-index and clamp to image. + Real zIndex = (z - mZMin) * mInvZSpacing; + int iz = static_cast(zIndex); + if (iz < 0) + { + iz = 0; + } + else if (iz >= mZBound) + { + iz = mZBound - 1; + } + + Real U[2]; + U[0] = (Real)1; + U[1] = xIndex - ix; + + Real V[2]; + V[0] = (Real)1; + V[1] = yIndex - iy; + + Real W[2]; + W[0] = (Real)1; + W[1] = zIndex - iz; + + // Compute P = M*U, Q = M*V, R = M*W. + Real P[2], Q[2], R[2]; + for (int row = 0; row < 2; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + R[row] = (Real)0; + for (int col = 0; col < 2; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + R[row] += mBlend[row][col] * W[col]; + } + } + + // compute the tensor product (M*U)(M*V)(M*W)*D where D is the 2x2x2 + // subimage containing (x,y,z) + Real result = (Real)0; + for (int slice = 0; slice < 2; ++slice) + { + int zClamp = iz + slice; + if (zClamp >= mZBound) + { + zClamp = mZBound - 1; + } + + for (int row = 0; row < 2; ++row) + { + int yClamp = iy + row; + if (yClamp >= mYBound) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 2; ++col) + { + int xClamp = ix + col; + if (xClamp >= mXBound) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * R[slice] * + mF[xClamp + mXBound * (yClamp + mYBound * zClamp)]; + } + } + } + + return result; +} + +template +Real IntpTrilinear3::operator()(int xOrder, int yOrder, int zOrder, + Real x, Real y, Real z) const +{ + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + // Compute z-index and clamp to image. + Real zIndex = (z - mZMin) * mInvZSpacing; + int iz = static_cast(zIndex); + if (iz < 0) + { + iz = 0; + } + else if (iz >= mZBound) + { + iz = mZBound - 1; + } + + Real U[2], dx, xMult; + switch (xOrder) + { + case 0: + dx = xIndex - ix; + U[0] = (Real)1; + U[1] = dx; + xMult = (Real)1; + break; + case 1: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)1; + xMult = mInvXSpacing; + break; + default: + return (Real)0; + } + + Real V[2], dy, yMult; + switch (yOrder) + { + case 0: + dy = yIndex - iy; + V[0] = (Real)1; + V[1] = dy; + yMult = (Real)1; + break; + case 1: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)1; + yMult = mInvYSpacing; + break; + default: + return (Real)0; + } + + Real W[2], dz, zMult; + switch (zOrder) + { + case 0: + dz = zIndex - iz; + W[0] = (Real)1; + W[1] = dz; + zMult = (Real)1; + break; + case 1: + dz = zIndex - iz; + W[0] = (Real)0; + W[1] = (Real)1; + zMult = mInvZSpacing; + break; + default: + return (Real)0; + } + + // Compute P = M*U, Q = M*V, and R = M*W. + Real P[2], Q[2], R[2]; + for (int row = 0; row < 2; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + R[row] = (Real)0; + for (int col = 0; col < 2; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + R[row] += mBlend[row][col] * W[col]; + } + } + + // Compute the tensor product (M*U)(M*V)(M*W)*D where D is the 2x2x2 + // subimage containing (x,y,z). + Real result = (Real)0; + for (int slice = 0; slice < 2; ++slice) + { + int zClamp = iz + slice; + if (zClamp >= mZBound) + { + zClamp = mZBound - 1; + } + + for (int row = 0; row < 2; ++row) + { + int yClamp = iy + row; + if (yClamp >= mYBound) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 2; ++col) + { + int xClamp = ix + col; + if (xClamp >= mXBound) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * R[slice] * + mF[xClamp + mXBound * (yClamp + mYBound * zClamp)]; + } + } + } + result *= xMult * yMult * zMult; + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpVectorField2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpVectorField2.h new file mode 100644 index 000000000000..67d4c4e62634 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpVectorField2.h @@ -0,0 +1,93 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// Given points (x0[i],y0[i]) which are mapped to (x1[i],y1[i]) for +// 0 <= i < N, interpolate positions (xIn,yIn) to (xOut,yOut). + +namespace gte +{ + +template +class IntpVectorField2 +{ +public: + // Construction and destruction. + ~IntpVectorField2(); + IntpVectorField2(int numPoints, Vector2 const* domain, + Vector2 const* range); + + // The return value is 'true' if and only if (xIn,yIn) is in the convex + // hull of the input domain points, in which case the interpolation is + // valid. + bool operator()(Vector2 const& input, + Vector2& output) const; + +protected: + typedef Delaunay2Mesh TriangleMesh; + Delaunay2 mDelaunay; + TriangleMesh mMesh; + + std::vector mXRange; + std::vector mYRange; + std::unique_ptr> mXInterp; + std::unique_ptr> mYInterp; +}; + + +template +IntpVectorField2::~IntpVectorField2() +{ +} + +template +IntpVectorField2::IntpVectorField2( + int numPoints, Vector2 const* domain, + Vector2 const* range) + : + mMesh(mDelaunay) +{ + // Repackage the output vectors into individual components. This is + // required because of the format that the quadratic interpolator expects + // for its input data. + mXRange.resize(numPoints); + mYRange.resize(numPoints); + for (int i = 0; i < numPoints; ++i) + { + mXRange[i] = range[i][0]; + mYRange[i] = range[i][1]; + } + + // Common triangulator for the interpolators. + mDelaunay(numPoints, &domain[0], (ComputeType)0); + + // Create interpolator for x-coordinate of vector field. + mXInterp = std::make_unique>( + mMesh, &mXRange[0], (InputType)1); + + // Create interpolator for y-coordinate of vector field, but share the + // already created triangulation for the x-interpolator. + mYInterp = std::make_unique>( + mMesh, &mYRange[0], (InputType)1); +} + +template +bool IntpVectorField2::operator()( + Vector2 const& input, Vector2& output) const +{ + InputType xDeriv, yDeriv; + return (*mXInterp)(input, output[0], xDeriv, yDeriv) + && (*mYInterp)(input, output[1], xDeriv, yDeriv); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2AlignedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2AlignedBox2.h new file mode 100644 index 000000000000..71ed456b957d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2AlignedBox2.h @@ -0,0 +1,109 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The aligned-aligned queries use simple min-max comparisions. The +// interesection of aligned boxes is an aligned box, possibly degenerate, +// where min[d] == max[d] for at least one dimension d. + +namespace gte +{ + +template +class TIQuery, AlignedBox2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(AlignedBox2 const& box0, + AlignedBox2 const& box1); +}; + +template +class FIQuery, AlignedBox2> +{ +public: + struct Result + { + bool intersect; + AlignedBox2 box; + }; + + Result operator()(AlignedBox2 const& box0, + AlignedBox2 const& box1); +}; + + +template +typename TIQuery, AlignedBox2>::Result +TIQuery, AlignedBox2>::operator()( + AlignedBox2 const& box0, AlignedBox2 const& box1) +{ + Result result; + for (int i = 0; i < 2; i++) + { + if (box0.max[i] < box1.min[i] || box0.min[i] > box1.max[i]) + { + result.intersect = false; + return result; + } + } + result.intersect = true; + return result; +} + +template +typename FIQuery, AlignedBox2>::Result +FIQuery, AlignedBox2>::operator()( + AlignedBox2 const& box0, AlignedBox2 const& box1) +{ + Result result; + for (int i = 0; i < 2; i++) + { + if (box0.max[i] < box1.min[i] || box0.min[i] > box1.max[i]) + { + result.intersect = false; + return result; + } + } + + for (int i = 0; i < 2; i++) + { + if (box0.max[i] <= box1.max[i]) + { + result.box.max[i] = box0.max[i]; + } + else + { + result.box.max[i] = box1.max[i]; + } + + if (box0.min[i] <= box1.min[i]) + { + result.box.min[i] = box1.min[i]; + } + else + { + result.box.min[i] = box0.min[i]; + } + } + result.intersect = true; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2Circle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2Circle2.h new file mode 100644 index 000000000000..a32457376624 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2Circle2.h @@ -0,0 +1,348 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.15.3 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +// The find-intersection query is based on the document +// https://www.geometrictools.com/Documentation/IntersectionMovingCircleRectangle.pdf + +namespace gte +{ + template + class TIQuery, Circle2> + { + public: + // The intersection query considers the box and circle to be solids; + // that is, the circle object includes the region inside the circular + // boundary and the box object includes the region inside the + // rectangular boundary. If the circle object and box object + // overlap, the objects intersect. + struct Result + { + bool intersect; + }; + + Result operator()(AlignedBox2 const& box, Circle2 const& circle) + { + DCPQuery, AlignedBox2> pbQuery; + auto pbResult = pbQuery(circle.center, box); + Result result; + result.intersect = (pbResult.sqrDistance <= circle.radius * circle.radius); + return result; + } + }; + + template + class FIQuery, Circle2> + { + public: + // Currently, only a dynamic query is supported. A static query will + // need to compute the intersection set of (solid) box and circle. + struct Result + { + // The cases are + // 1. Objects initially overlapping. The contactPoint is only one + // of infinitely many points in the overlap. + // intersectionType = -1 + // contactTime = 0 + // contactPoint = circle.center + // 2. Objects initially separated but do not intersect later. The + // contactTime and contactPoint are invalid. + // intersectionType = 0 + // contactTime = 0 + // contactPoint = (0,0) + // 3. Objects initially separated but intersect later. + // intersectionType = +1 + // contactTime = first time T > 0 + // contactPoint = corresponding first contact + int intersectionType; + Real contactTime; + Vector2 contactPoint; + + // TODO: To support arbitrary precision for the contactTime, + // return q0, q1 and q2 where contactTime = (q0 - sqrt(q1)) / q2. + // The caller can compute contactTime to desired number of digits + // of precision. These are valid when intersectionType is +1 but + // are set to zero (invalid) in the other cases. Do the same for + // the contactPoint. + }; + + Result operator()(AlignedBox2 const& box, Vector2 const& boxVelocity, + Circle2 const& circle, Vector2 const& circleVelocity) + { + Result result = { 0, (Real)0, { (Real)0, (Real)0 } }; + + // Translate the circle and box so that the box center becomes + // the origin. Compute the velocity of the circle relative to + // the box. + Vector2 boxCenter = (box.max + box.min) * (Real)0.5; + Vector2 extent = (box.max - box.min) * (Real)0.5; + Vector2 C = circle.center - boxCenter; + Vector2 V = circleVelocity - boxVelocity; + + // Change signs on components, if necessary, to transform C to the + // first quadrant. Adjust the velocity accordingly. + Real sign[2]; + for (int i = 0; i < 2; ++i) + { + if (C[i] >= (Real)0) + { + sign[i] = (Real)1; + } + else + { + C[i] = -C[i]; + V[i] = -V[i]; + sign[i] = (Real)-1; + } + } + + DoQuery(extent, C, circle.radius, V, result); + + if (result.intersectionType != 0) + { + // Translate back to the original coordinate system. + for (int i = 0; i < 2; ++i) + { + if (sign[i] < (Real)0) + { + result.contactPoint[i] = -result.contactPoint[i]; + } + } + + result.contactPoint += boxCenter; + } + return result; + } + + protected: + void DoQuery(Vector2 const& K, Vector2 const& C, + Real radius, Vector2 const& V, Result& result) + { + Vector2 delta = C - K; + if (delta[1] <= radius) + { + if (delta[0] <= radius) + { + if (delta[1] <= (Real)0) + { + if (delta[0] <= (Real)0) + { + InteriorOverlap(C, result); + } + else + { + EdgeOverlap(0, 1, K, C, delta, radius, result); + } + } + else + { + if (delta[0] <= (Real)0) + { + EdgeOverlap(1, 0, K, C, delta, radius, result); + } + else + { + if (Dot(delta, delta) <= radius * radius) + { + VertexOverlap(K, delta, radius, result); + } + else + { + VertexSeparated(K, delta, V, radius, result); + } + } + + } + } + else + { + EdgeUnbounded(0, 1, K, C, radius, delta, V, result); + } + } + else + { + if (delta[0] <= radius) + { + EdgeUnbounded(1, 0, K, C, radius, delta, V, result); + } + else + { + VertexUnbounded(K, C, radius, delta, V, result); + } + } + } + + private: + void InteriorOverlap(Vector2 const& C, Result& result) + { + result.intersectionType = -1; + result.contactTime = (Real)0; + result.contactPoint = C; + } + + void EdgeOverlap(int i0, int i1, Vector2 const& K, Vector2 const& C, + Vector2 const& delta, Real radius, Result& result) + { + result.intersectionType = (delta[i0] < radius ? -1 : 1); + result.contactTime = (Real)0; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = C[i1]; + } + + void VertexOverlap(Vector2 const& K0, Vector2 const& delta, + Real radius, Result& result) + { + Real sqrDistance = delta[0] * delta[0] + delta[1] * delta[1]; + Real sqrRadius = radius * radius; + result.intersectionType = (sqrDistance < sqrRadius ? -1 : 1); + result.contactTime = (Real)0; + result.contactPoint = K0; + } + + void VertexSeparated(Vector2 const& K0, Vector2 const& delta0, + Vector2 const& V, Real radius, Result& result) + { + Real q0 = -Dot(V, delta0); + if (q0 > (Real)0) + { + Real dotVPerpD0 = Dot(V, Perp(delta0)); + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD0 * dotVPerpD0; + if (q1 >= (Real)0) + { + IntersectsVertex(0, 1, K0, q0, q1, q2, result); + } + } + } + + void EdgeUnbounded(int i0, int i1, Vector2 const& K0, Vector2 const& C, + Real radius, Vector2 const& delta0, Vector2 const& V, Result& result) + { + if (V[i0] < (Real)0) + { + Real dotVPerpD0 = V[i0] * delta0[i1] - V[i1] * delta0[i0]; + if (radius * V[i1] + dotVPerpD0 >= (Real)0) + { + Vector2 K1, delta1; + K1[i0] = K0[i0]; + K1[i1] = -K0[i1]; + delta1[i0] = C[i0] - K1[i0]; + delta1[i1] = C[i1] - K1[i1]; + Real dotVPerpD1 = V[i0] * delta1[i1] - V[i1] * delta1[i0]; + if (radius * V[i1] + dotVPerpD1 <= (Real)0) + { + IntersectsEdge(i0, i1, K0, C, radius, V, result); + } + else + { + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD1 * dotVPerpD1; + if (q1 >= (Real)0) + { + Real q0 = -(V[i0] * delta1[i0] + V[i1] * delta1[i1]); + IntersectsVertex(i0, i1, K1, q0, q1, q2, result); + } + } + } + else + { + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD0 * dotVPerpD0; + if (q1 >= (Real)0) + { + Real q0 = -(V[i0] * delta0[i0] + V[i1] * delta0[i1]); + IntersectsVertex(i0, i1, K0, q0, q1, q2, result); + } + } + } + } + + void VertexUnbounded(Vector2 const& K0, Vector2 const& C, Real radius, + Vector2 const& delta0, Vector2 const& V, Result& result) + { + if (V[0] < (Real)0 && V[1] < (Real)0) + { + Real dotVPerpD0 = Dot(V, Perp(delta0)); + if (radius * V[0] - dotVPerpD0 <= (Real)0) + { + if (-radius * V[1] - dotVPerpD0 >= (Real)0) + { + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD0 * dotVPerpD0; + Real q0 = -Dot(V, delta0); + IntersectsVertex(0, 1, K0, q0, q1, q2, result); + } + else + { + Vector2 K1{ K0[0], -K0[1] }; + Vector2 delta1 = C - K1; + Real dotVPerpD1 = Dot(V, Perp(delta1)); + if (-radius * V[1] - dotVPerpD1 >= (Real)0) + { + IntersectsEdge(0, 1, K0, C, radius, V, result); + } + else + { + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD1 * dotVPerpD1; + if (q1 >= (Real)0) + { + Real q0 = -Dot(V, delta1); + IntersectsVertex(0, 1, K1, q0, q1, q2, result); + } + } + } + } + else + { + Vector2 K2{ -K0[0], K0[1] }; + Vector2 delta2 = C - K2; + Real dotVPerpD2 = Dot(V, Perp(delta2)); + if (radius * V[0] - dotVPerpD2 <= (Real)0) + { + IntersectsEdge(1, 0, K0, C, radius, V, result); + } + else + { + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD2 * dotVPerpD2; + if (q1 >= (Real)0) + { + Real q0 = -Dot(V, delta2); + IntersectsVertex(1, 0, K2, q0, q1, q2, result); + } + } + } + } + } + + void IntersectsVertex(int i0, int i1, Vector2 const& K, + Real q0, Real q1, Real q2, Result& result) + { + result.intersectionType = +1; + result.contactTime = (q0 - std::sqrt(q1)) / q2; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = K[i1]; + } + + void IntersectsEdge(int i0, int i1, Vector2 const& K0, Vector2 const& C, + Real radius, Vector2 const& V, Result& result) + { + result.intersectionType = +1; + result.contactTime = (K0[i0] + radius - C[i0]) / V[i0]; + result.contactPoint[i0] = K0[i0]; + result.contactPoint[i1] = C[i1] + result.contactTime * V[i1]; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2OrientedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2OrientedBox2.h new file mode 100644 index 000000000000..69e84713aa6b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2OrientedBox2.h @@ -0,0 +1,110 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection query uses the method of separating axes. The set of +// potential separating directions includes the 2 edge normals of box0 and the +// 2 edge normals of box1. The integer 'separating' identifies the axis that +// reported separation; there may be more than one but only one is reported. +// The value is 0 when box0.axis[0] separates, 1 when box0.axis[1] separates, +// 2 when box1.axis[0] separates, or 3 when box1.axis[1] separates. + +namespace gte +{ + +template +class TIQuery, OrientedBox2> +{ +public: + struct Result + { + bool intersect; + int separating; + }; + + Result operator()(AlignedBox2 const& box0, + OrientedBox2 const& box1); +}; + + +template +typename TIQuery, OrientedBox2>::Result +TIQuery, OrientedBox2>::operator()( + AlignedBox2 const& box0, OrientedBox2 const& box1) +{ + Result result; + + // Get the centered form of the aligned box. The axes are implicitly + // A0[0] = (1,0) and A0[1] = (0,1). + Vector2 C0, E0; + box0.GetCenteredForm(C0, E0); + + // Convenience variables. + Vector2 const& C1 = box1.center; + Vector2 const* A1 = &box1.axis[0]; + Vector2 const& E1 = box1.extent; + + // Compute difference of box centers. + Vector2 D = C1 - C0; + + Real absDot01[2][2], rSum; + + // Test box0.axis[0] = (1,0). + absDot01[0][0] = std::abs(A1[0][0]); + absDot01[0][1] = std::abs(A1[1][0]); + rSum = E0[0] + E1[0] * absDot01[0][0] + E1[1] * absDot01[0][1]; + if (std::abs(D[0]) > rSum) + { + result.intersect = false; + result.separating = 0; + return result; + } + + // Test axis box0.axis[1] = (0,1). + absDot01[1][0] = std::abs(A1[0][1]); + absDot01[1][1] = std::abs(A1[1][1]); + rSum = E0[1] + E1[0] * absDot01[1][0] + E1[1] * absDot01[1][1]; + if (std::abs(D[1]) > rSum) + { + result.intersect = false; + result.separating = 1; + return result; + } + + // Test axis box1.axis[0]. + rSum = E1[0] + E0[0] * absDot01[0][0] + E0[1] * absDot01[1][0]; + if (std::abs(Dot(A1[0], D)) > rSum) + { + result.intersect = false; + result.separating = 2; + return result; + } + + // Test axis box1.axis[1]. + rSum = E1[1] + E0[0] * absDot01[0][1] + E0[1] * absDot01[1][1]; + if (std::abs(Dot(A1[1], D)) > rSum) + { + result.intersect = false; + result.separating = 3; + return result; + } + + result.intersect = true; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3AlignedBox3.h new file mode 100644 index 000000000000..5e5bd173c747 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3AlignedBox3.h @@ -0,0 +1,109 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The aligned-aligned queries use simple min-max comparisions. The +// interesection of aligned boxes is an aligned box, possibly degenerate, +// where min[d] == max[d] for at least one dimension d. + +namespace gte +{ + +template +class TIQuery, AlignedBox3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(AlignedBox3 const& box0, + AlignedBox3 const& box1); +}; + +template +class FIQuery, AlignedBox3> +{ +public: + struct Result + { + bool intersect; + AlignedBox3 box; + }; + + Result operator()(AlignedBox3 const& box0, + AlignedBox3 const& box1); +}; + + +template +typename TIQuery, AlignedBox3>::Result +TIQuery, AlignedBox3>::operator()( + AlignedBox3 const& box0, AlignedBox3 const& box1) +{ + Result result; + for (int i = 0; i < 3; i++) + { + if (box0.max[i] < box1.min[i] || box0.min[i] > box1.max[i]) + { + result.intersect = false; + return result; + } + } + result.intersect = true; + return result; +} + +template +typename FIQuery, AlignedBox3>::Result +FIQuery, AlignedBox3>::operator()( + AlignedBox3 const& box0, AlignedBox3 const& box1) +{ + Result result; + for (int i = 0; i < 3; i++) + { + if (box0.max[i] < box1.min[i] || box0.min[i] > box1.max[i]) + { + result.intersect = false; + return result; + } + } + + for (int i = 0; i < 3; i++) + { + if (box0.max[i] <= box1.max[i]) + { + result.box.max[i] = box0.max[i]; + } + else + { + result.box.max[i] = box1.max[i]; + } + + if (box0.min[i] <= box1.min[i]) + { + result.box.min[i] = box1.min[i]; + } + else + { + result.box.min[i] = box0.min[i]; + } + } + result.intersect = true; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Cone3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Cone3.h new file mode 100644 index 000000000000..5fac3a925b78 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Cone3.h @@ -0,0 +1,984 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/11/29) + +#pragma once + +#include +#include +#include +#include + +// Test for intersection of a box and a cone. The cone can be infinite +// 0 <= minHeight < maxHeight = std::numeric_limits::max() +// or finite (cone frustum) +// 0 <= minHeight < maxHeight < std::numeric_limits::max(). +// The algorithm is described in +// https://www.geometrictools.com/Documentation/IntersectionBoxCone.pdf +// and reports an intersection only when the intersection set has positive +// volume. For example, let the box be outside the cone. If the box is +// below the minHeight plane at the cone vertex and just touches the cone +// vertex, no intersection is reported. If the box is above the maxHeight +// plane and just touches the disk capping the cone, either at a single +// point, a line segment of points or a polygon of points, no intersection +// is reported. + +namespace gte +{ + template + class TIQuery, Cone<3, Real>> + { + public: + struct Result + { + bool intersect; + }; + + TIQuery() + : + mNumCandidateEdges(0) + { + // An edge is { v0, v1 }, where v0 and v1 are relative to mVertices + // with v0 < v1. + mEdges[ 0] = { 0, 1 }; + mEdges[ 1] = { 1, 3 }; + mEdges[ 2] = { 2, 3 }; + mEdges[ 3] = { 0, 2 }; + mEdges[ 4] = { 4, 5 }; + mEdges[ 5] = { 5, 7 }; + mEdges[ 6] = { 6, 7 }; + mEdges[ 7] = { 4, 6 }; + mEdges[ 8] = { 0, 4 }; + mEdges[ 9] = { 1, 5 }; + mEdges[10] = { 3, 7 }; + mEdges[11] = { 2, 6 }; + + // A face is { { v0, v1, v2, v3 }, { e0, e1, e2, e3 } }, where + // { v0, v1, v2, v3 } are relative to mVertices with + // v0 = min(v0,v1,v2,v3) and where { e0, e1, e2, e3 } are relative + // to mEdges. For example, mFaces[0] has vertices { 0, 4, 6, 2 }. + // The edge { 0, 4 } is mEdges[8], the edge { 4, 6 } is mEdges[7], + // the edge { 6, 2 } is mEdges[11] and the edge { 2, 0 } is + // mEdges[3]; thus, the edge indices are { 8, 7, 11, 3 }. + mFaces[0] = { { 0, 4, 6, 2 }, { 8, 7, 11, 3 } }; + mFaces[1] = { { 1, 3, 7, 5 }, { 1, 10, 5, 9 } }; + mFaces[2] = { { 0, 1, 5, 4 }, { 0, 9, 4, 8 } }; + mFaces[3] = { { 2, 6, 7, 3 }, { 11, 6, 10, 2 } }; + mFaces[4] = { { 0, 2, 3, 1 }, { 3, 2, 1, 0 } }; + mFaces[5] = { { 4, 5, 7, 6 }, { 4, 5, 6, 7 } }; + + // Clear the edges. + std::array ezero = {{ 0, 0 }}; + mCandidateEdges.fill(ezero); + for (size_t r = 0; r < MAX_VERTICES; ++r) + { + mAdjacencyMatrix[r].fill(0); + } + + mConfiguration[ 0] = &TIQuery::NNNN_0; + mConfiguration[ 1] = &TIQuery::NNNZ_1; + mConfiguration[ 2] = &TIQuery::NNNP_2; + mConfiguration[ 3] = &TIQuery::NNZN_3; + mConfiguration[ 4] = &TIQuery::NNZZ_4; + mConfiguration[ 5] = &TIQuery::NNZP_5; + mConfiguration[ 6] = &TIQuery::NNPN_6; + mConfiguration[ 7] = &TIQuery::NNPZ_7; + mConfiguration[ 8] = &TIQuery::NNPP_8; + mConfiguration[ 9] = &TIQuery::NZNN_9; + mConfiguration[10] = &TIQuery::NZNZ_10; + mConfiguration[11] = &TIQuery::NZNP_11; + mConfiguration[12] = &TIQuery::NZZN_12; + mConfiguration[13] = &TIQuery::NZZZ_13; + mConfiguration[14] = &TIQuery::NZZP_14; + mConfiguration[15] = &TIQuery::NZPN_15; + mConfiguration[16] = &TIQuery::NZPZ_16; + mConfiguration[17] = &TIQuery::NZPP_17; + mConfiguration[18] = &TIQuery::NPNN_18; + mConfiguration[19] = &TIQuery::NPNZ_19; + mConfiguration[20] = &TIQuery::NPNP_20; + mConfiguration[21] = &TIQuery::NPZN_21; + mConfiguration[22] = &TIQuery::NPZZ_22; + mConfiguration[23] = &TIQuery::NPZP_23; + mConfiguration[24] = &TIQuery::NPPN_24; + mConfiguration[25] = &TIQuery::NPPZ_25; + mConfiguration[26] = &TIQuery::NPPP_26; + mConfiguration[27] = &TIQuery::ZNNN_27; + mConfiguration[28] = &TIQuery::ZNNZ_28; + mConfiguration[29] = &TIQuery::ZNNP_29; + mConfiguration[30] = &TIQuery::ZNZN_30; + mConfiguration[31] = &TIQuery::ZNZZ_31; + mConfiguration[32] = &TIQuery::ZNZP_32; + mConfiguration[33] = &TIQuery::ZNPN_33; + mConfiguration[34] = &TIQuery::ZNPZ_34; + mConfiguration[35] = &TIQuery::ZNPP_35; + mConfiguration[36] = &TIQuery::ZZNN_36; + mConfiguration[37] = &TIQuery::ZZNZ_37; + mConfiguration[38] = &TIQuery::ZZNP_38; + mConfiguration[39] = &TIQuery::ZZZN_39; + mConfiguration[40] = &TIQuery::ZZZZ_40; + mConfiguration[41] = &TIQuery::ZZZP_41; + mConfiguration[42] = &TIQuery::ZZPN_42; + mConfiguration[43] = &TIQuery::ZZPZ_43; + mConfiguration[44] = &TIQuery::ZZPP_44; + mConfiguration[45] = &TIQuery::ZPNN_45; + mConfiguration[46] = &TIQuery::ZPNZ_46; + mConfiguration[47] = &TIQuery::ZPNP_47; + mConfiguration[48] = &TIQuery::ZPZN_48; + mConfiguration[49] = &TIQuery::ZPZZ_49; + mConfiguration[50] = &TIQuery::ZPZP_50; + mConfiguration[51] = &TIQuery::ZPPN_51; + mConfiguration[52] = &TIQuery::ZPPZ_52; + mConfiguration[53] = &TIQuery::ZPPP_53; + mConfiguration[54] = &TIQuery::PNNN_54; + mConfiguration[55] = &TIQuery::PNNZ_55; + mConfiguration[56] = &TIQuery::PNNP_56; + mConfiguration[57] = &TIQuery::PNZN_57; + mConfiguration[58] = &TIQuery::PNZZ_58; + mConfiguration[59] = &TIQuery::PNZP_59; + mConfiguration[60] = &TIQuery::PNPN_60; + mConfiguration[61] = &TIQuery::PNPZ_61; + mConfiguration[62] = &TIQuery::PNPP_62; + mConfiguration[63] = &TIQuery::PZNN_63; + mConfiguration[64] = &TIQuery::PZNZ_64; + mConfiguration[65] = &TIQuery::PZNP_65; + mConfiguration[66] = &TIQuery::PZZN_66; + mConfiguration[67] = &TIQuery::PZZZ_67; + mConfiguration[68] = &TIQuery::PZZP_68; + mConfiguration[69] = &TIQuery::PZPN_69; + mConfiguration[70] = &TIQuery::PZPZ_70; + mConfiguration[71] = &TIQuery::PZPP_71; + mConfiguration[72] = &TIQuery::PPNN_72; + mConfiguration[73] = &TIQuery::PPNZ_73; + mConfiguration[74] = &TIQuery::PPNP_74; + mConfiguration[75] = &TIQuery::PPZN_75; + mConfiguration[76] = &TIQuery::PPZZ_76; + mConfiguration[77] = &TIQuery::PPZP_77; + mConfiguration[78] = &TIQuery::PPPN_78; + mConfiguration[79] = &TIQuery::PPPZ_79; + mConfiguration[80] = &TIQuery::PPPP_80; + } + + Result operator()(AlignedBox<3, Real> const& box, Cone<3, Real> const& cone) + { + Result result; + + // Quick-rejectance test. Determine whether the box is outside + // the slab bounded by the minimum and maximum height planes. + // When outside the slab, the box vertices are not required by the + // cone-box intersection query, so the vertices are not yet + // computed. + Real boxMinHeight(0), boxMaxHeight(0); + ComputeBoxHeightInterval(box, cone, boxMinHeight, boxMaxHeight); + if (boxMaxHeight <= cone.minHeight || boxMinHeight >= cone.maxHeight) + { + // There is no volumetric overlap of the box and the cone. The + // box is clipped entirely. + result.intersect = false; + return result; + } + + // Quick-acceptance test. Determine whether the cone axis + // intersects the box. + if (ConeAxisIntersectsBox(box, cone)) + { + result.intersect = true; + return result; + } + + // Test for box fully inside the slab. When inside the slab, the + // box vertices are required by the cone-box intersection query, + // so they are computed here; they are also required in the + // remaining cases. Also when inside the slab, the box edges are + // the candidates, so they are copied to mCandidateEdges. + if (BoxFullyInConeSlab(box, boxMinHeight, boxMaxHeight, cone)) + { + result.intersect = CandidatesHavePointInsideCone(cone); + return result; + } + + // Clear the candidates array and adjacency matrix. + ClearCandidates(); + + // The box intersects at least one plane. Compute the box-plane + // edge-interior intersection points. Insert the box subedges into + // the array of candidate edges. + ComputeCandidatesOnBoxEdges(cone); + + // Insert any relevant box face-interior clipped edges into the array + // of candidate edges. + ComputeCandidatesOnBoxFaces(); + + result.intersect = CandidatesHavePointInsideCone(cone); + return result; + } + + protected: + // The constants here are described in the comments below. + enum + { + NUM_BOX_VERTICES = 8, + NUM_BOX_EDGES = 12, + NUM_BOX_FACES = 6, + MAX_VERTICES = 32, + VERTEX_MIN_BASE = 8, + VERTEX_MAX_BASE = 20, + MAX_CANDIDATE_EDGES = 496, + NUM_CONFIGURATIONS = 81 + }; + + // The box topology is that of a cube whose vertices have components + // in {0,1}. The cube vertices are indexed by + // 0: (0,0,0), 1: (1,0,0), 2: (1,1,0), 3: (0,1,0) + // 4: (0,0,1), 5: (1,0,1), 6: (1,1,1), 7: (0,1,1) + + // The first 8 vertices are the box corners, the next 12 vertices are + // reserved for hmin-edge points and the final 12 vertices are reserved + // for the hmax-edge points. The conservative upper bound of the number + // of vertices is 8 + 12 + 12 = 32. + std::array, MAX_VERTICES> mVertices; + + // The box has 12 edges stored in mEdges. An edge is mEdges[i] = + // { v0, v1 }, where the indices v0 and v1 are relative to mVertices + // with v0 < v1. + std::array, NUM_BOX_EDGES> mEdges; + + // The box has 6 faces stored in mFaces. A face is mFaces[i] = + // { { v0, v1, v2, v3 }, { e0, e1, e2, e3 } }, where the face corner + // vertices are { v0, v1, v2, v3 }. These indices are relative to + // mVertices. The indices { e0, e1, e2, e3 } are relative to mEdges. + // The index e0 refers to edge { v0, v1 }, the index e1 refers to edge + // { v1, v2 }, the index e2 refers to edge { v2, v3 } and the index e3 + // refers to edge { v3, v0 }. The ordering of vertices for the faces + // is/ counterclockwise when viewed from outside the box. The choice + // of initial vertex affects how you implement the graph data + // structure. In this implementation, the initial vertex has minimum + // index for all vertices of that face. The faces themselves are + // listed as -x face, +x face, -y face, +y face, -z face and +z face. + struct Face + { + std::array v, e; + }; + std::array mFaces; + + // Store the signed distances from the minimum and maximum height + // planes for the cone to the projection of the box vertices onto the + // cone axis. + std::array mProjectionMin, mProjectionMax; + + // The mCandidateEdges array stores the edges of the clipped box that + // are candidates for containing the optimizing point. The maximum + // number of candidate edges is 1 + 2 + ... + 31 = 496, which is a + // conservative bound because not all pairs of vertices form edges on + // box faces. The candidate edges are stored as (v0,v1) with v0 < v1. + // The implementation is designed so that during a single query, the + // number of candidate edges can only grow. + size_t mNumCandidateEdges; + std::array, MAX_CANDIDATE_EDGES> mCandidateEdges; + + // The mAdjancencyMatrix is a simple representation of edges in the + // graph G = (V,E) that represents the (wireframe) clipped box. An + // edge (r,c) does not exist when mAdjancencyMatrix[r][c] = 0. If an + // edge (r,c) does exist, it is appended to mCandidateEdges at index k + // and the adjacency matrix is set to mAdjacencyMatrix[r][c] = 1. + // This allows for a fast edge-in-graph test and a fast and efficient + // clear of mCandidateEdges and mAdjacencyMatrix. + std::array, MAX_VERTICES> mAdjacencyMatrix; + + typedef void (TIQuery::*ConfigurationFunction)(size_t, Face const&); + std::array mConfiguration; + + static void ComputeBoxHeightInterval(AlignedBox<3, Real> const& box, Cone<3, Real> const& cone, + Real& boxMinHeight, Real& boxMaxHeight) + { + Vector<3, Real> C, e; + box.GetCenteredForm(C, e); + Vector<3, Real> const& V = cone.ray.origin; + Vector<3, Real> const& U = cone.ray.direction; + Vector<3, Real> CmV = C - V; + Real DdCmV = Dot(U, CmV); + Real radius = e[0] * std::abs(U[0]) + e[1] * std::abs(U[1]) + e[2] * std::abs(U[2]); + boxMinHeight = DdCmV - radius; + boxMaxHeight = DdCmV + radius; + } + + static bool ConeAxisIntersectsBox(AlignedBox<3, Real> const& box, Cone<3, Real> const& cone) + { + if (cone.maxHeight < std::numeric_limits::max()) + { + Segment<3, Real> segment; + segment.p[0] = cone.ray.origin + cone.minHeight * cone.ray.direction; + segment.p[1] = cone.ray.origin + cone.maxHeight * cone.ray.direction; + auto sbResult = TIQuery, AlignedBox<3, Real>>()(segment, box); + if (sbResult.intersect) + { + return true; + } + } + else + { + Ray<3, Real> ray; + ray.origin = cone.ray.origin + cone.minHeight * cone.ray.direction; + ray.direction = cone.ray.direction; + auto rbResult = TIQuery, AlignedBox<3, Real>>()(ray, box); + if (rbResult.intersect) + { + return true; + } + } + return false; + } + + bool BoxFullyInConeSlab(AlignedBox<3, Real> const& box, Real boxMinHeight, Real boxMaxHeight, Cone<3, Real> const& cone) + { + // Compute the box vertices relative to cone vertex as origin. + mVertices[0] = { box.min[0], box.min[1], box.min[2] }; + mVertices[1] = { box.max[0], box.min[1], box.min[2] }; + mVertices[2] = { box.min[0], box.max[1], box.min[2] }; + mVertices[3] = { box.max[0], box.max[1], box.min[2] }; + mVertices[4] = { box.min[0], box.min[1], box.max[2] }; + mVertices[5] = { box.max[0], box.min[1], box.max[2] }; + mVertices[6] = { box.min[0], box.max[1], box.max[2] }; + mVertices[7] = { box.max[0], box.max[1], box.max[2] }; + for (int i = 0; i < NUM_BOX_VERTICES; ++i) + { + mVertices[i] -= cone.ray.origin; + } + + if (cone.minHeight <= boxMinHeight && boxMaxHeight <= cone.maxHeight) + { + // The box is fully inside, so no clipping is necessary. + std::copy(mEdges.begin(), mEdges.end(), mCandidateEdges.begin()); + mNumCandidateEdges = 12; + return true; + } + return false; + } + + static bool HasPointInsideCone(Vector<3, Real> const& P0, Vector<3, Real> const& P1, + Cone<3, Real> const& cone) + { + // Define F(X) = Dot(U,X - V)/|X - V|, where U is the unit-length + // cone axis direction and V is the cone vertex. The incoming + // points P0 and P1 are relative to V; that is, the original + // points are X0 = P0 + V and X1 = P1 + V. The segment + // and cone intersect when a segment point X is inside the cone; + // that is, when F(X) > cosAngle. The comparison is converted to + // an equivalent one that does not involve divisions in order to + // avoid a division by zero if a vertex or edge contain (0,0,0). + // The function is G(X) = Dot(U,X-V) - cosAngle*Length(X-V). + Vector<3, Real> const& U = cone.ray.direction; + + // Test whether P0 or P1 is inside the cone. + Real g = Dot(U, P0) - cone.cosAngle * Length(P0); + if (g > (Real)0) + { + // X0 = P0 + V is inside the cone. + return true; + } + + g = Dot(U, P1) - cone.cosAngle * Length(P1); + if (g > (Real)0) + { + // X1 = P1 + V is inside the cone. + return true; + } + + // Test whether an interior segment point is inside the cone. + Vector<3, Real> E = P1 - P0; + Vector<3, Real> crossP0U = Cross(P0, U); + Vector<3, Real> crossP0E = Cross(P0, E); + Real dphi0 = Dot(crossP0E, crossP0U); + if (dphi0 > (Real)0) + { + Vector3 crossP1U = Cross(P1, U); + Real dphi1 = Dot(crossP0E, crossP1U); + if (dphi1 < (Real)0) + { + Real t = dphi0 / (dphi0 - dphi1); + Vector<3, Real> PMax = P0 + t * E; + g = Dot(U, PMax) - cone.cosAngle * Length(PMax); + if (g > (Real)0) + { + // The edge point XMax = Pmax + V is inside the cone. + return true; + } + } + } + + return false; + } + + bool CandidatesHavePointInsideCone(Cone<3, Real> const& cone) const + { + for (size_t i = 0; i < mNumCandidateEdges; ++i) + { + auto const& edge = mCandidateEdges[i]; + Vector<3, Real> const& P0 = mVertices[edge[0]]; + Vector<3, Real> const& P1 = mVertices[edge[1]]; + if (HasPointInsideCone(P0, P1, cone)) + { + return true; + } + } + return false; + } + + void ComputeCandidatesOnBoxEdges(Cone<3, Real> const& cone) + { + for (size_t i = 0; i < NUM_BOX_VERTICES; ++i) + { + Real h = Dot(cone.ray.direction, mVertices[i]); + mProjectionMin[i] = cone.minHeight - h; + mProjectionMax[i] = h - cone.maxHeight; + } + + size_t v0 = VERTEX_MIN_BASE, v1 = VERTEX_MAX_BASE; + for (size_t i = 0; i < NUM_BOX_EDGES; ++i, ++v0, ++v1) + { + auto const& edge = mEdges[i]; + + // In the next blocks, the sign comparisons can be expressed + // instead as "s0 * s1 < 0". The multiplication could lead to + // floating-point underflow where the product becomes 0, so I + // avoid that approach. + + // Process the hmin-plane. + Real p0Min = mProjectionMin[edge[0]]; + Real p1Min = mProjectionMin[edge[1]]; + bool clipMin = (p0Min < (Real)0 && p1Min > (Real)0) || (p0Min > (Real)0 && p1Min < (Real)0); + if (clipMin) + { + mVertices[v0] = (p1Min * mVertices[edge[0]] - p0Min * mVertices[edge[1]]) / (p1Min - p0Min); + } + + // Process the hmax-plane. + Real p0Max = mProjectionMax[edge[0]]; + Real p1Max = mProjectionMax[edge[1]]; + bool clipMax = (p0Max < (Real)0 && p1Max > (Real)0) || (p0Max > (Real)0 && p1Max < (Real)0); + if (clipMax) + { + mVertices[v1] = (p1Max * mVertices[edge[0]] - p0Max * mVertices[edge[1]]) / (p1Max - p0Max); + } + + // Get the candidate edges that are contained by the box edges. + if (clipMin) + { + if (clipMax) + { + InsertEdge(v0, v1); + } + else + { + if (p0Min < (Real)0) + { + InsertEdge(edge[0], v0); + } + else // p1Min < 0 + { + InsertEdge(edge[1], v0); + } + } + } + else if (clipMax) + { + if (p0Max < (Real)0) + { + InsertEdge(edge[0], v1); + } + else // p1Max < 0 + { + InsertEdge(edge[1], v1); + } + } + else + { + // No clipping has occurred. If the edge is inside the box, + // it is a candidate edge. To be inside the box, the p*min + // and p*max values must be nonpositive. + if (p0Min <= (Real)0 && p1Min <= (Real)0 && p0Max <= (Real)0 && p1Max <= (Real)0) + { + InsertEdge(edge[0], edge[1]); + } + } + } + } + + void ComputeCandidatesOnBoxFaces() + { + Real p0, p1, p2, p3; + size_t b0, b1, b2, b3, index; + for (size_t i = 0; i < NUM_BOX_FACES; ++i) + { + auto const& face = mFaces[i]; + + // Process the hmin-plane. + p0 = mProjectionMin[face.v[0]]; + p1 = mProjectionMin[face.v[1]]; + p2 = mProjectionMin[face.v[2]]; + p3 = mProjectionMin[face.v[3]]; + b0 = (p0 < (Real)0 ? 0 : (p0 > (Real)0 ? 2 : 1)); + b1 = (p1 < (Real)0 ? 0 : (p1 > (Real)0 ? 2 : 1)); + b2 = (p2 < (Real)0 ? 0 : (p2 > (Real)0 ? 2 : 1)); + b3 = (p3 < (Real)0 ? 0 : (p3 > (Real)0 ? 2 : 1)); + index = b3 + 3 * (b2 + 3 * (b1 + 3 * b0)); + (this->*mConfiguration[index])(VERTEX_MIN_BASE, face); + + // Process the hmax-plane. + p0 = mProjectionMax[face.v[0]]; + p1 = mProjectionMax[face.v[1]]; + p2 = mProjectionMax[face.v[2]]; + p3 = mProjectionMax[face.v[3]]; + b0 = (p0 < (Real)0 ? 0 : (p0 > (Real)0 ? 2 : 1)); + b1 = (p1 < (Real)0 ? 0 : (p1 > (Real)0 ? 2 : 1)); + b2 = (p2 < (Real)0 ? 0 : (p2 > (Real)0 ? 2 : 1)); + b3 = (p3 < (Real)0 ? 0 : (p3 > (Real)0 ? 2 : 1)); + index = b3 + 3 * (b2 + 3 * (b1 + 3 * b0)); + (this->*mConfiguration[index])(VERTEX_MAX_BASE, face); + } + } + + void ClearCandidates() + { + for (size_t i = 0; i < mNumCandidateEdges; ++i) + { + auto const& edge = mCandidateEdges[i]; + mAdjacencyMatrix[edge[0]][edge[1]] = 0; + mAdjacencyMatrix[edge[1]][edge[0]] = 0; + } + mNumCandidateEdges = 0; + } + + void InsertEdge(size_t v0, size_t v1) + { + if (mAdjacencyMatrix[v0][v1] == 0) + { + mAdjacencyMatrix[v0][v1] = 1; + mAdjacencyMatrix[v1][v0] = 1; + mCandidateEdges[mNumCandidateEdges] = { v0, v1 }; + ++mNumCandidateEdges; + } + } + + // The 81 possible configurations for a box face. The N stands for a + // '-', the Z stands for '0' and the P stands for '+'. These are + // listed in the order that maps to the array mConfiguration. Thus, + // NNNN maps to mConfiguration[0], NNNZ maps to mConfiguration[1], and + // so on. + void NNNN_0(size_t, Face const&) + { + } + + void NNNZ_1(size_t, Face const&) + { + } + + void NNNP_2(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], base + face.e[3]); + } + + void NNZN_3(size_t, Face const&) + { + } + + void NNZZ_4(size_t, Face const&) + { + } + + void NNZP_5(size_t base, Face const& face) + { + InsertEdge(face.v[2], base + face.e[3]); + } + + void NNPN_6(size_t base, Face const& face) + { + InsertEdge(base + face.e[1], base + face.e[2]); + } + + void NNPZ_7(size_t base, Face const& face) + { + InsertEdge(base + face.e[1], face.v[3]); + } + + void NNPP_8(size_t base, Face const& face) + { + InsertEdge(base + face.e[1], base + face.e[3]); + } + + void NZNN_9(size_t, Face const&) + { + } + + void NZNZ_10(size_t, Face const&) + { + } + + void NZNP_11(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], face.v[3]); + InsertEdge(base + face.e[3], face.v[3]); + } + + void NZZN_12(size_t, Face const&) + { + } + + void NZZZ_13(size_t, Face const&) + { + } + + void NZZP_14(size_t base, Face const& face) + { + InsertEdge(face.v[2], face.v[3]); + InsertEdge(base + face.e[3], face.v[3]); + } + + void NZPN_15(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], face.v[1]); + } + + void NZPZ_16(size_t, Face const& face) + { + InsertEdge(face.v[1], face.v[3]); + } + + void NZPP_17(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[1]); + } + + void NPNN_18(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], base + face.e[1]); + } + + void NPNZ_19(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[1]); + InsertEdge(base + face.e[1], face.v[1]); + } + + void NPNP_20(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[1]); + InsertEdge(base + face.e[1], face.v[1]); + InsertEdge(base + face.e[2], face.v[3]); + InsertEdge(base + face.e[3], face.v[3]); + } + + void NPZN_21(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[2]); + } + + void NPZZ_22(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[1]); + InsertEdge(face.v[1], face.v[2]); + } + + void NPZP_23(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[1]); + InsertEdge(face.v[1], face.v[2]); + InsertEdge(base + face.e[3], face.v[2]); + InsertEdge(face.v[2], face.v[3]); + } + + void NPPN_24(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], base + face.e[2]); + } + + void NPPZ_25(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[3]); + } + + void NPPP_26(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], base + face.e[3]); + } + + void ZNNN_27(size_t, Face const&) + { + } + + void ZNNZ_28(size_t, Face const&) + { + } + + void ZNNP_29(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], face.v[0]); + } + + void ZNZN_30(size_t, Face const&) + { + } + + void ZNZZ_31(size_t, Face const&) + { + } + + void ZNZP_32(size_t, Face const& face) + { + InsertEdge(face.v[0], face.v[2]); + } + + void ZNPN_33(size_t base, Face const& face) + { + InsertEdge(base + face.e[1], face.v[2]); + InsertEdge(base + face.e[2], face.v[2]); + } + + void ZNPZ_34(size_t base, Face const& face) + { + InsertEdge(base + face.e[1], face.v[2]); + InsertEdge(face.v[2], face.v[3]); + } + + void ZNPP_35(size_t base, Face const& face) + { + InsertEdge(face.v[0], base + face.e[1]); + } + + void ZZNN_36(size_t, Face const&) + { + } + + void ZZNZ_37(size_t, Face const&) + { + } + + void ZZNP_38(size_t base, Face const& face) + { + InsertEdge(face.v[0], face.v[3]); + InsertEdge(face.v[3], base + face.e[2]); + } + + void ZZZN_39(size_t, Face const&) + { + } + + void ZZZZ_40(size_t, Face const&) + { + } + + void ZZZP_41(size_t, Face const& face) + { + InsertEdge(face.v[0], face.v[3]); + InsertEdge(face.v[3], face.v[2]); + } + + void ZZPN_42(size_t base, Face const& face) + { + InsertEdge(face.v[1], face.v[2]); + InsertEdge(face.v[2], base + face.e[2]); + } + + void ZZPZ_43(size_t, Face const& face) + { + InsertEdge(face.v[1], face.v[2]); + InsertEdge(face.v[2], face.v[3]); + } + + void ZZPP_44(size_t, Face const&) + { + } + + void ZPNN_45(size_t base, Face const& face) + { + InsertEdge(face.v[0], base + face.e[1]); + } + + void ZPNZ_46(size_t base, Face const& face) + { + InsertEdge(face.v[0], face.v[1]); + InsertEdge(face.v[1], base + face.e[1]); + } + + void ZPNP_47(size_t base, Face const& face) + { + InsertEdge(face.v[0], face.v[1]); + InsertEdge(face.v[1], base + face.e[1]); + InsertEdge(base + face.e[2], face.v[3]); + InsertEdge(face.v[3], face.v[0]); + } + + void ZPZN_48(size_t, Face const& face) + { + InsertEdge(face.v[0], face.v[2]); + } + + void ZPZZ_49(size_t, Face const& face) + { + InsertEdge(face.v[0], face.v[1]); + InsertEdge(face.v[1], face.v[2]); + } + + void ZPZP_50(size_t, Face const&) + { + } + + void ZPPN_51(size_t base, Face const& face) + { + InsertEdge(face.v[0], base + face.e[2]); + } + + void ZPPZ_52(size_t, Face const&) + { + } + + void ZPPP_53(size_t, Face const&) + { + } + + void PNNN_54(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], base + face.e[0]); + } + + void PNNZ_55(size_t base, Face const& face) + { + InsertEdge(face.v[3], base + face.e[0]); + } + + void PNNP_56(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], base + face.e[0]); + } + + void PNZN_57(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[0]); + InsertEdge(face.v[0], base + face.e[0]); + } + + void PNZZ_58(size_t base, Face const& face) + { + InsertEdge(face.v[3], face.v[0]); + InsertEdge(face.v[0], base + face.e[0]); + } + + void PNZP_59(size_t base, Face const& face) + { + InsertEdge(face.v[2], base + face.e[0]); + } + + void PNPN_60(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[0]); + InsertEdge(face.v[0], base + face.e[0]); + InsertEdge(base + face.e[1], face.v[2]); + InsertEdge(face.v[2], base + face.e[2]); + } + + void PNPZ_61(size_t base, Face const& face) + { + InsertEdge(face.v[3], face.v[0]); + InsertEdge(face.v[0], base + face.e[0]); + InsertEdge(base + face.e[1], face.v[2]); + InsertEdge(face.v[2], face.v[3]); + } + + void PNPP_62(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], base + face.e[1]); + } + + void PZNN_63(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[1]); + } + + void PZNZ_64(size_t, Face const& face) + { + InsertEdge(face.v[3], face.v[1]); + } + + void PZNP_65(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], face.v[1]); + } + + void PZZN_66(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[0]); + InsertEdge(face.v[0], face.v[1]); + } + + void PZZZ_67(size_t, Face const&) + { + } + + void PZZP_68(size_t, Face const&) + { + } + + void PZPN_69(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[0]); + InsertEdge(face.v[0], face.v[1]); + InsertEdge(face.v[1], face.v[2]); + InsertEdge(face.v[2], base + face.e[2]); + } + + void PZPZ_70(size_t, Face const&) + { + } + + void PZPP_71(size_t, Face const&) + { + } + + void PPNN_72(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], base + face.e[1]); + } + + void PPNZ_73(size_t base, Face const& face) + { + InsertEdge(face.v[3], base + face.e[1]); + } + + void PPNP_74(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], base + face.e[1]); + } + + void PPZN_75(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], face.v[2]); + } + + void PPZZ_76(size_t, Face const&) + { + } + + void PPZP_77(size_t, Face const&) + { + } + + void PPPN_78(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], base + face.e[2]); + } + + void PPPZ_79(size_t, Face const&) + { + } + + void PPPP_80(size_t, Face const&) + { + } + }; + + // Template alias for convenience. + template + using TIAlignedBox3Cone3 = TIQuery, Cone<3, Real>>; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Cylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Cylinder3.h new file mode 100644 index 000000000000..640623c8c4cd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Cylinder3.h @@ -0,0 +1,126 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.4.0 (2016/11/08) + +#pragma once + +#include +#include +#include +#include +#include + +// The query considers the cylinder and box to be solids. + +namespace gte +{ + +template +class TIQuery, Cylinder3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(AlignedBox3 const& box, Cylinder3 const& cylinder); + +private: +}; + + +template +typename TIQuery, Cylinder3>::Result +TIQuery, Cylinder3>::operator()( + AlignedBox3 const& box, Cylinder3 const& cylinder) +{ + Result result; + + // Translate the box and cylinder so that the box is in the first octant + // where all points in the box have nonnegative components. + Vector3 corner = box.max - box.min; + Vector3 origin = cylinder.axis.origin - box.min; + Vector3 direction = cylinder.axis.direction; + + // Compute quantities to initialize q and M in the LCP. + Real halfHeight = cylinder.height * (Real)0.5; + Matrix3x3 P = (Matrix3x3::Identity() - OuterProduct(direction, direction)); + Vector3 C = -(P * origin); + Real originDotDirection = Dot(origin, direction); + + Matrix<5, 3, Real> A; + A.SetRow(0, { (Real)-1, (Real) 0, (Real) 0 }); + A.SetRow(1, { (Real) 0, (Real)-1, (Real) 0 }); + A.SetRow(2, { (Real) 0, (Real) 0, (Real)-1 }); + A.SetRow(3, direction); + A.SetRow(4, -direction); + + Vector<5, Real> B = + { + -corner[0], + -corner[1], + -corner[2], + originDotDirection - halfHeight, + -originDotDirection - halfHeight + }; + + std::array, 8> M; + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + M[r][c] = P(r, c); + } + + for (int c = 3, i = 0; c < 8; ++c, ++i) + { + M[r][c] = -A(i, r); + } + } + + for (int r = 3, i = 0; r < 8; ++r, ++i) + { + for (int c = 0; c < 3; ++c) + { + M[r][c] = A(i, c); + } + + for (int c = 3; c < 8; ++c) + { + M[r][c] = (Real)0; + } + } + + std::array q; + for (int r = 0; r < 3; ++r) + { + q[r] = C[r]; + } + + for (int r = 3, i = 0; r < 8; ++r, ++i) + { + q[r] = -B[i]; + } + + std::array w, z; + LCPSolver LCP; + if (LCP.Solve(q, M, w, z)) + { + Vector3 zSolution{ z[0], z[1], z[2] }; + Vector3 diff = zSolution - origin; + Real qform = Dot(diff, P * diff); + result.intersect = (qform <= cylinder.radius * cylinder.radius); + } + else + { + result.intersect = false; + } + + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3OrientedBox3.h new file mode 100644 index 000000000000..7baeac7d6624 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3OrientedBox3.h @@ -0,0 +1,336 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection query uses the method of separating axes. The set of +// potential separating directions includes the 3 face normals of box0, the 3 +// face normals of box1, and 9 directions, each of which is the cross product +// of an edge of box0 and and an edge of box1. +// +// The separating axes involving cross products of edges has numerical +// robustness problems when the two edges are nearly parallel. The cross +// product of the edges is nearly the zero vector, so normalization of the +// cross product may produce unit-length directions that are not close to the +// true direction. Such a pair of edges occurs when a box0 face normal N0 and +// a box1 face normal N1 are nearly parallel. In this case, you may skip the +// edge-edge directions, which is equivalent to projecting the boxes onto the +// plane with normal N0 and applying a 2D separating axis test. The ability +// to do so involves choosing a small nonnegative epsilon . It is used to +// determine whether two face normals, one from each box, are nearly parallel: +// |Dot(N0,N1)| >= 1 - epsilon. If the input is negative, it is clamped to +// zero. +// +// The pair of integers 'separating', say, (i0,i1), identify the axis that +// reported separation; there may be more than one but only one is +// reported. If the separating axis is a face normal N[i0] of the aligned +// box0 in dimension i0, then (i0,-1) is returned. If the axis is a face +// normal box1.Axis[i1], then (-1,i1) is returned. If the axis is a cross +// product of edges, Cross(N[i0],box1.Axis[i1]), then (i0,i1) is returned. + +namespace gte +{ + +template +class TIQuery, OrientedBox3> +{ +public: + struct Result + { + // The 'epsilon' value must be nonnegative. + Result(Real inEpsilon = (Real)0); + + bool intersect; + Real epsilon; + int separating[2]; + }; + + Result operator()(AlignedBox3 const& box0, + OrientedBox3 const& box1); +}; + + +template +TIQuery, OrientedBox3>::Result:: +Result(Real inEpsilon) +{ + if (inEpsilon >= (Real)0) + { + epsilon = inEpsilon; + } + else + { + epsilon = (Real)0; + } +} + +template +typename TIQuery, OrientedBox3>::Result +TIQuery, OrientedBox3>::operator()( + AlignedBox3 const& box0, OrientedBox3 const& box1) +{ + Result result; + + // Get the centered form of the aligned box. The axes are implicitly + // A0[0] = (1,0,0), A0[1] = (0,1,0), and A0[2] = (0,0,1). + Vector3 C0, E0; + box0.GetCenteredForm(C0, E0); + + // Convenience variables. + Vector3 const& C1 = box1.center; + Vector3 const* A1 = &box1.axis[0]; + Vector3 const& E1 = box1.extent; + + Real const cutoff = (Real)1 - result.epsilon; + bool existsParallelPair = false; + + // Compute the difference of box centers. + Vector3 D = C1 - C0; + + Real dot01[3][3]; // dot01[i][j] = Dot(A0[i],A1[j]) = A1[j][i] + Real absDot01[3][3]; // |dot01[i][j]| + Real r0, r1, r; // interval radii and distance between centers + Real r01; // r0 + r1 + + // Test for separation on the axis C0 + t*A0[0]. + for (int i = 0; i < 3; ++i) + { + dot01[0][i] = A1[i][0]; + absDot01[0][i] = std::abs(A1[i][0]); + if (absDot01[0][i] >= cutoff) + { + existsParallelPair = true; + } + } + r = std::abs(D[0]); + r1 = E1[0] * absDot01[0][0] + E1[1] * absDot01[0][1] + E1[2] * absDot01[0][2]; + r01 = E0[0] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]. + for (int i = 0; i < 3; ++i) + { + dot01[1][i] = A1[i][1]; + absDot01[1][i] = std::abs(A1[i][1]); + if (absDot01[1][i] >= cutoff) + { + existsParallelPair = true; + } + } + r = std::abs(D[1]); + r1 = E1[0] * absDot01[1][0] + E1[1] * absDot01[1][1] + E1[2] * absDot01[1][2]; + r01 = E0[1] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]. + for (int i = 0; i < 3; ++i) + { + dot01[2][i] = A1[i][2]; + absDot01[2][i] = std::abs(A1[i][2]); + if (absDot01[2][i] >= cutoff) + { + existsParallelPair = true; + } + } + r = std::abs(D[2]); + r1 = E1[0] * absDot01[2][0] + E1[1] * absDot01[2][1] + E1[2] * absDot01[2][2]; + r01 = E0[2] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A1[0]. + r = std::abs(Dot(D, A1[0])); + r0 = E0[0] * absDot01[0][0] + E0[1] * absDot01[1][0] + E0[2] * absDot01[2][0]; + r01 = r0 + E1[0]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A1[1]. + r = std::abs(Dot(D, A1[1])); + r0 = E0[0] * absDot01[0][1] + E0[1] * absDot01[1][1] + E0[2] * absDot01[2][1]; + r01 = r0 + E1[1]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A1[2]. + r = std::abs(Dot(D, A1[2])); + r0 = E0[0] * absDot01[0][2] + E0[1] * absDot01[1][2] + E0[2] * absDot01[2][2]; + r01 = r0 + E1[2]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 2; + return result; + } + + // At least one pair of box axes was parallel, so the separation is + // effectively in 2D. The edge-edge axes do not need to be tested. + if (existsParallelPair) + { + result.intersect = true; + return result; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[0]. + r = std::abs(D[2] * dot01[1][0] - D[1] * dot01[2][0]); + r0 = E0[1] * absDot01[2][0] + E0[2] * absDot01[1][0]; + r1 = E1[1] * absDot01[0][2] + E1[2] * absDot01[0][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[1]. + r = std::abs(D[2] * dot01[1][1] - D[1] * dot01[2][1]); + r0 = E0[1] * absDot01[2][1] + E0[2] * absDot01[1][1]; + r1 = E1[0] * absDot01[0][2] + E1[2] * absDot01[0][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[2]. + r = std::abs(D[2] * dot01[1][2] - D[1] * dot01[2][2]); + r0 = E0[1] * absDot01[2][2] + E0[2] * absDot01[1][2]; + r1 = E1[0] * absDot01[0][1] + E1[1] * absDot01[0][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 2; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[0]. + r = std::abs(D[0] * dot01[2][0] - D[2] * dot01[0][0]); + r0 = E0[0] * absDot01[2][0] + E0[2] * absDot01[0][0]; + r1 = E1[1] * absDot01[1][2] + E1[2] * absDot01[1][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[1]. + r = std::abs(D[0] * dot01[2][1] - D[2] * dot01[0][1]); + r0 = E0[0] * absDot01[2][1] + E0[2] * absDot01[0][1]; + r1 = E1[0] * absDot01[1][2] + E1[2] * absDot01[1][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[2]. + r = std::abs(D[0] * dot01[2][2] - D[2] * dot01[0][2]); + r0 = E0[0] * absDot01[2][2] + E0[2] * absDot01[0][2]; + r1 = E1[0] * absDot01[1][1] + E1[1] * absDot01[1][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 2; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[0]. + r = std::abs(D[1] * dot01[0][0] - D[0] * dot01[1][0]); + r0 = E0[0] * absDot01[1][0] + E0[1] * absDot01[0][0]; + r1 = E1[1] * absDot01[2][2] + E1[2] * absDot01[2][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[1]. + r = std::abs(D[1] * dot01[0][1] - D[0] * dot01[1][1]); + r0 = E0[0] * absDot01[1][1] + E0[1] * absDot01[0][1]; + r1 = E1[0] * absDot01[2][2] + E1[2] * absDot01[2][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[2]. + r = std::abs(D[1] * dot01[0][2] - D[0] * dot01[1][2]); + r0 = E0[0] * absDot01[1][2] + E0[1] * absDot01[0][2]; + r1 = E1[0] * absDot01[2][1] + E1[1] * absDot01[2][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 2; + return result; + } + + result.intersect = true; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Sphere3.h new file mode 100644 index 000000000000..a9fd022924c6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Sphere3.h @@ -0,0 +1,595 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.4 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +// The find-intersection query is based on the document +// https://www.geometrictools.com/Documentation/IntersectionMovingSphereBox.pdf + +namespace gte +{ + template + class TIQuery, Sphere3> + { + public: + // The intersection query considers the box and sphere to be solids; + // that is, the sphere object includes the region inside the spherical + // boundary and the box object includes the region inside the cuboid + // boundary. If the sphere object and box object object overlap, the + // objects intersect. + struct Result + { + bool intersect; + }; + + Result operator()(AlignedBox3 const& box, Sphere3 const& sphere) + { + DCPQuery, AlignedBox3> pbQuery; + auto pbResult = pbQuery(sphere.center, box); + Result result; + result.intersect = (pbResult.sqrDistance <= sphere.radius * sphere.radius); + return result; + } + }; + + template + class FIQuery, Sphere3> + { + public: + // Currently, only a dynamic query is supported. A static query will + // need to compute the intersection set of (solid) box and sphere. + struct Result + { + // The cases are + // 1. Objects initially overlapping. The contactPoint is only one + // of infinitely many points in the overlap. + // intersectionType = -1 + // contactTime = 0 + // contactPoint = sphere.center + // 2. Objects initially separated but do not intersect later. The + // contactTime and contactPoint are invalid. + // intersectionType = 0 + // contactTime = 0 + // contactPoint = (0,0,0) + // 3. Objects initially separated but intersect later. + // intersectionType = +1 + // contactTime = first time T > 0 + // contactPoint = corresponding first contact + int intersectionType; + Real contactTime; + Vector3 contactPoint; + + // TODO: To support arbitrary precision for the contactTime, + // return q0, q1 and q2 where contactTime = (q0 - sqrt(q1)) / q2. + // The caller can compute contactTime to desired number of digits + // of precision. These are valid when intersectionType is +1 but + // are set to zero (invalid) in the other cases. Do the same for + // the contactPoint. + }; + + Result operator()(AlignedBox3 const& box, Vector3 const& boxVelocity, + Sphere3 const& sphere, Vector3 const& sphereVelocity) + { + Result result = { 0, (Real)0, { (Real)0, (Real)0, (Real)0 } }; + + // Translate the sphere and box so that the box center becomes + // the origin. Compute the velocity of the sphere relative to + // the box. + Vector3 boxCenter = (box.max + box.min) * (Real)0.5; + Vector3 extent = (box.max - box.min) * (Real)0.5; + Vector3 C = sphere.center - boxCenter; + Vector3 V = sphereVelocity - boxVelocity; + + // Test for no-intersection that leads to an early exit. The test + // is fast, using the method of separating axes. + AlignedBox3 superBox; + for (int i = 0; i < 3; ++i) + { + superBox.max[i] = extent[i] + sphere.radius; + superBox.min[i] = -superBox.max[i]; + } + TIQuery, AlignedBox3> rbQuery; + auto rbResult = rbQuery(Ray3(C, V), superBox); + if (!rbResult.intersect) + { + return result; + } + + // Change signs on components, if necessary, to transform C to the + // first quadrant. Adjust the velocity accordingly. + Real sign[3]; + for (int i = 0; i < 3; ++i) + { + if (C[i] >= (Real)0) + { + sign[i] = (Real)1; + } + else + { + C[i] = -C[i]; + V[i] = -V[i]; + sign[i] = (Real)-1; + } + } + + DoQuery(extent, C, sphere.radius, V, result); + + if (result.intersectionType != 0) + { + // Translate back to the original coordinate system. + for (int i = 0; i < 3; ++i) + { + if (sign[i] < (Real)0) + { + result.contactPoint[i] = -result.contactPoint[i]; + } + } + + result.contactPoint += boxCenter; + } + + return result; + } + + protected: + // The query assumes the box is axis-aligned with center at the + // origin. Callers need to convert the results back to the original + // coordinate system of the query. + void DoQuery(Vector3 const& K, Vector3 const& C, + Real radius, Vector3 const& V, Result& result) + { + Vector3 delta = C - K; + if (delta[2] <= radius) + { + if (delta[1] <= radius) + { + if (delta[0] <= radius) + { + if (delta[2] <= (Real)0) + { + if (delta[1] <= (Real)0) + { + if (delta[0] <= (Real)0) + { + InteriorOverlap(C, result); + } + else + { + // x-face + FaceOverlap(0, 1, 2, K, C, radius, delta, result); + } + } + else + { + if (delta[0] <= (Real)0) + { + // y-face + FaceOverlap(1, 2, 0, K, C, radius, delta, result); + } + else + { + // xy-edge + if (delta[0] * delta[0] + delta[1] * delta[1] <= radius * radius) + { + EdgeOverlap(0, 1, 2, K, C, radius, delta, result); + } + else + { + EdgeSeparated(0, 1, 2, K, C, radius, delta, V, result); + } + } + } + } + else + { + if (delta[1] <= (Real)0) + { + if (delta[0] <= (Real)0) + { + // z-face + FaceOverlap(2, 0, 1, K, C, radius, delta, result); + } + else + { + // xz-edge + if (delta[0] * delta[0] + delta[2] * delta[2] <= radius * radius) + { + EdgeOverlap(2, 0, 1, K, C, radius, delta, result); + } + else + { + EdgeSeparated(2, 0, 1, K, C, radius, delta, V, result); + } + } + } + else + { + if (delta[0] <= (Real)0) + { + // yz-edge + if (delta[1] * delta[1] + delta[2] * delta[2] <= radius * radius) + { + EdgeOverlap(1, 2, 0, K, C, radius, delta, result); + } + else + { + EdgeSeparated(1, 2, 0, K, C, radius, delta, V, result); + } + } + else + { + // xyz-vertex + if (Dot(delta, delta) <= radius * radius) + { + VertexOverlap(K, radius, delta, result); + } + else + { + VertexSeparated(K, radius, delta, V, result); + } + } + } + } + } + else + { + // x-face + FaceUnbounded(0, 1, 2, K, C, radius, delta, V, result); + } + } + else + { + if (delta[0] <= radius) + { + // y-face + FaceUnbounded(1, 2, 0, K, C, radius, delta, V, result); + } + else + { + // xy-edge + EdgeUnbounded(0, 1, 2, K, C, radius, delta, V, result); + } + } + } + else + { + if (delta[1] <= radius) + { + if (delta[0] <= radius) + { + // z-face + FaceUnbounded(2, 0, 1, K, C, radius, delta, V, result); + } + else + { + // xz-edge + EdgeUnbounded(2, 0, 1, K, C, radius, delta, V, result); + } + } + else + { + if (delta[0] <= radius) + { + // yz-edge + EdgeUnbounded(1, 2, 0, K, C, radius, delta, V, result); + } + else + { + // xyz-vertex + VertexUnbounded(K, C, radius, delta, V, result); + } + } + } + } + + private: + void InteriorOverlap(Vector3 const& C, Result& result) + { + result.intersectionType = -1; + result.contactTime = (Real)0; + result.contactPoint = C; + } + + void VertexOverlap(Vector3 const& K, Real radius, + Vector3 const& delta, Result& result) + { + result.intersectionType = (Dot(delta, delta) < radius * radius ? -1 : 1); + result.contactTime = (Real)0; + result.contactPoint = K; + } + + void EdgeOverlap(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Result& result) + { + result.intersectionType = (delta[i0] * delta[i0] + delta[i1] * delta[i1] < radius * radius ? -1 : 1); + result.contactTime = (Real)0; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = K[i1]; + result.contactPoint[i2] = C[i2]; + } + + void FaceOverlap(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Result& result) + { + result.intersectionType = (delta[i0] < radius ? -1 : 1); + result.contactTime = (Real)0; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = C[i1]; + result.contactPoint[i2] = C[i2]; + } + + void VertexSeparated(Vector3 const& K, Real radius, + Vector3 const& delta, Vector3 const& V, Result& result) + { + if (V[0] < (Real)0 || V[1] < (Real)0 || V[2] < (Real)0) + { + DoQueryRayRoundedVertex(K, radius, delta, V, result); + } + } + + void EdgeSeparated(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Vector3 const& V, Result& result) + { + if (V[i0] < (Real)0 || V[i1] < (Real)0) + { + DoQueryRayRoundedEdge(i0, i1, i2, K, C, radius, delta, V, result); + } + } + + void VertexUnbounded(Vector3 const& K, Vector3 const& C, Real radius, + Vector3 const& delta, Vector3 const& V, Result& result) + { + if (V[0] < (Real)0 && V[1] < (Real)0 && V[2] < (Real)0) + { + // Determine the face of the rounded box that is intersected + // by the ray C+T*V. + Real T = (radius - delta[0]) / V[0]; + int j0 = 0; + Real temp = (radius - delta[1]) / V[1]; + if (temp > T) + { + T = temp; + j0 = 1; + } + temp = (radius - delta[2]) / V[2]; + if (temp > T) + { + T = temp; + j0 = 2; + } + + // The j0-rounded face is the candidate for intersection. + int j1 = (j0 + 1) % 3; + int j2 = (j1 + 1) % 3; + DoQueryRayRoundedFace(j0, j1, j2, K, C, radius, delta, V, result); + } + } + + void EdgeUnbounded(int i0, int i1, int /* i2 */, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Vector3 const& V, Result& result) + { + if (V[i0] < (Real)0 && V[i1] < (Real)0) + { + // Determine the face of the rounded box that is intersected + // by the ray C+T*V. + Real T = (radius - delta[i0]) / V[i0]; + int j0 = i0; + Real temp = (radius - delta[i1]) / V[i1]; + if (temp > T) + { + T = temp; + j0 = i1; + } + + // The j0-rounded face is the candidate for intersection. + int j1 = (j0 + 1) % 3; + int j2 = (j1 + 1) % 3; + DoQueryRayRoundedFace(j0, j1, j2, K, C, radius, delta, V, result); + } + } + + void FaceUnbounded(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Vector3 const& V, Result& result) + { + if (V[i0] < (Real)0) + { + DoQueryRayRoundedFace(i0, i1, i2, K, C, radius, delta, V, result); + } + } + + void DoQueryRayRoundedVertex(Vector3 const& K, Real radius, + Vector3 const& delta, Vector3 const& V, Result& result) + { + Real a1 = Dot(V, delta); + if (a1 < (Real)0) + { + // The caller must ensure that a0 > 0 and a2 > 0. + Real a0 = Dot(delta, delta) - radius * radius; + Real a2 = Dot(V, V); + Real adiscr = a1 * a1 - a2 * a0; + if (adiscr >= (Real)0) + { + // The ray intersects the rounded vertex, so the sphere-box + // contact point is the vertex. + result.intersectionType = 1; + result.contactTime = -(a1 + std::sqrt(adiscr)) / a2; + result.contactPoint = K; + } + } + } + + void DoQueryRayRoundedEdge(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Vector3 const& V, Result& result) + { + Real b1 = V[i0] * delta[i0] + V[i1] * delta[i1]; + if (b1 < (Real)0) + { + // The caller must ensure that b0 > 0 and b2 > 0. + Real b0 = delta[i0] * delta[i0] + delta[i1] * delta[i1] - radius * radius; + Real b2 = V[i0] * V[i0] + V[i1] * V[i1]; + Real bdiscr = b1 * b1 - b2 * b0; + if (bdiscr >= (Real)0) + { + Real T = -(b1 + std::sqrt(bdiscr)) / b2; + Real p2 = C[i2] + T * V[i2]; + if (-K[i2] <= p2) + { + if (p2 <= K[i2]) + { + // The ray intersects the finite cylinder of the + // rounded edge, so the sphere-box contact point + // is on the corresponding box edge. + result.intersectionType = 1; + result.contactTime = T; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = K[i1]; + result.contactPoint[i2] = p2; + } + else + { + // The ray intersects the infinite cylinder but + // not the finite cylinder of the rounded edge. + // It is possible the ray intersects the rounded + // vertex for K. + DoQueryRayRoundedVertex(K, radius, delta, V, result); + } + } + else + { + // The ray intersects the infinite cylinder but + // not the finite cylinder of the rounded edge. + // It is possible the ray intersects the rounded + // vertex for otherK. + Vector3 otherK, otherDelta; + otherK[i0] = K[i0]; + otherK[i1] = K[i1]; + otherK[i2] = -K[i2]; + otherDelta[i0] = C[i0] - otherK[i0]; + otherDelta[i1] = C[i1] - otherK[i1]; + otherDelta[i2] = C[i2] - otherK[i2]; + DoQueryRayRoundedVertex(otherK, radius, otherDelta, V, result); + } + } + } + } + + void DoQueryRayRoundedFace(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Vector3 const& V, Result& result) + { + Vector3 otherK, otherDelta; + + Real T = (radius - delta[i0]) / V[i0]; + Real p1 = C[i1] + T * V[i1]; + Real p2 = C[i2] + T * V[i2]; + + if (p1 < -K[i1]) + { + // The ray potentially intersects the rounded (i0,i1)-edge + // whose top-most vertex is otherK. + otherK[i0] = K[i0]; + otherK[i1] = -K[i1]; + otherK[i2] = K[i2]; + otherDelta[i0] = C[i0] - otherK[i0]; + otherDelta[i1] = C[i1] - otherK[i1]; + otherDelta[i2] = C[i2] - otherK[i2]; + DoQueryRayRoundedEdge(i0, i1, i2, otherK, C, radius, otherDelta, V, result); + if (result.intersectionType == 0) + { + if (p2 < -K[i2]) + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is otherK. + otherK[i0] = K[i0]; + otherK[i1] = K[i1]; + otherK[i2] = -K[i2]; + otherDelta[i0] = C[i0] - otherK[i0]; + otherDelta[i1] = C[i1] - otherK[i1]; + otherDelta[i2] = C[i2] - otherK[i2]; + DoQueryRayRoundedEdge(i2, i0, i1, otherK, C, radius, otherDelta, V, result); + } + else if (p2 > K[i2]) + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is K. + DoQueryRayRoundedEdge(i2, i0, i1, K, C, radius, delta, V, result); + } + } + } + else if (p1 <= K[i1]) + { + if (p2 < -K[i2]) + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is otherK. + otherK[i0] = K[i0]; + otherK[i1] = K[i1]; + otherK[i2] = -K[i2]; + otherDelta[i0] = C[i0] - otherK[i0]; + otherDelta[i1] = C[i1] - otherK[i1]; + otherDelta[i2] = C[i2] - otherK[i2]; + DoQueryRayRoundedEdge(i2, i0, i1, otherK, C, radius, otherDelta, V, result); + } + else if (p2 <= K[i2]) + { + // The ray intersects the i0-face of the rounded box, so + // the sphere-box contact point is on the corresponding + // box face. + result.intersectionType = 1; + result.contactTime = T; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = p1; + result.contactPoint[i2] = p2; + } + else // p2 > K[i2] + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is K. + DoQueryRayRoundedEdge(i2, i0, i1, K, C, radius, delta, V, result); + } + } + else // p1 > K[i1] + { + // The ray potentially intersects the rounded (i0,i1)-edge + // whose top-most vertex is K. + DoQueryRayRoundedEdge(i0, i1, i2, K, C, radius, delta, V, result); + if (result.intersectionType == 0) + { + if (p2 < -K[i2]) + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is otherK. + otherK[i0] = K[i0]; + otherK[i1] = K[i1]; + otherK[i2] = -K[i2]; + otherDelta[i0] = C[i0] - otherK[i0]; + otherDelta[i1] = C[i1] - otherK[i1]; + otherDelta[i2] = C[i2] - otherK[i2]; + DoQueryRayRoundedEdge(i2, i0, i1, otherK, C, radius, otherDelta, V, result); + } + else if (p2 > K[i2]) + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is K. + DoQueryRayRoundedEdge(i2, i0, i1, K, C, radius, delta, V, result); + } + } + } + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrArc2Arc2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrArc2Arc2.h new file mode 100644 index 000000000000..160fcb29cb9f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrArc2Arc2.h @@ -0,0 +1,212 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2017/02/23) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class FIQuery, Arc2> +{ +public: + // The possible 'configuration' in Result are listed as an enumeration. + // The valid array elements are listed in the comments. + enum + { + NO_INTERSECTION, + NONCOCIRCULAR_ONE_POINT, // point[0] + NONCOCIRCULAR_TWO_POINTS, // point[0], point[1] + COCIRCULAR_ONE_POINT, // point[0] + COCIRCULAR_TWO_POINTS, // point[0], point[1] + COCIRCULAR_ONE_POINT_ONE_ARC, // point[0], arc[0] + COCIRCULAR_ONE_ARC, // arc[0] + COCIRCULAR_TWO_ARCS // arc[0], arc[1] + }; + + struct Result + { + bool intersect; // 'true' iff configuration != NO_INTERSECTION + int configuration; // one of the enumerations listed previously + Vector2 point[2]; + Arc2 arc[2]; + }; + + Result operator()(Arc2 const& arc0, Arc2 const& arc1); +}; + + +template +typename FIQuery, Arc2>::Result +FIQuery, Arc2>::operator()(Arc2 const& arc0, Arc2 const& arc1) +{ + // Assume initially there are no intersections. If we find at least one + // intersection, we will set result.intersect to 'true'. + Result result; + result.intersect = false; + + Circle2 circle0(arc0.center, arc0.radius); + Circle2 circle1(arc1.center, arc1.radius); + FIQuery, Circle2> ccQuery; + auto ccResult = ccQuery(circle0, circle1); + if (!ccResult.intersect) + { + // The arcs do not intersect. + result.configuration = NO_INTERSECTION; + return result; + } + + if (ccResult.numIntersections == std::numeric_limits::max()) + { + // The arcs are cocircular. Determine whether they overlap. Let + // arc0 be and arc1 be . The points are ordered + // counterclockwise around the circle of the arc. + if (arc1.Contains(arc0.end[0])) + { + result.intersect = true; + if (arc1.Contains(arc0.end[1])) + { + if (arc0.Contains(arc1.end[0]) && arc0.Contains(arc1.end[1])) + { + if (arc0.end[0] == arc1.end[0] && arc0.end[1] == arc1.end[1]) + { + // The arcs are the same. + result.configuration = COCIRCULAR_ONE_ARC; + result.arc[0] = arc0; + } + else + { + // arc0 and arc1 overlap in two disjoint subsets. + if (arc0.end[0] != arc1.end[1]) + { + if (arc1.end[0] != arc0.end[1]) + { + // The arcs overlap in two disjoint subarcs, each + // of positive subtended angle: , + result.configuration = COCIRCULAR_TWO_ARCS; + result.arc[0] = Arc2(arc0.center, arc0.radius, arc0.end[0], arc1.end[1]); + result.arc[1] = Arc2(arc0.center, arc0.radius, arc1.end[0], arc0.end[1]); + } + else // B0 = A1 + { + // The intersection is a point {A1} and an + // arc . + result.configuration = COCIRCULAR_ONE_POINT_ONE_ARC; + result.point[0] = arc0.end[1]; + result.arc[0] = Arc2(arc0.center, arc0.radius, arc0.end[0], arc1.end[1]); + } + } + else // A0 = B1 + { + if (arc1.end[0] != arc0.end[1]) + { + // The intersection is a point {A0} and an + // arc . + result.configuration = COCIRCULAR_ONE_POINT_ONE_ARC; + result.point[0] = arc0.end[0]; + result.arc[0] = Arc2(arc0.center, arc0.radius, arc1.end[0], arc0.end[1]); + } + else + { + // The arcs shared endpoints, so the union is a circle. + result.configuration = COCIRCULAR_TWO_POINTS; + result.point[0] = arc0.end[0]; + result.point[1] = arc0.end[1]; + } + } + } + } + else + { + // Arc0 inside arc1, . + result.configuration = COCIRCULAR_ONE_ARC; + result.arc[0] = arc0; + } + } + else + { + if (arc0.end[0] != arc1.end[1]) + { + // Arc0 and arc1 overlap, . + result.configuration = COCIRCULAR_ONE_ARC; + result.arc[0] = Arc2(arc0.center, arc0.radius, arc0.end[0], arc1.end[1]); + } + else + { + // Arc0 and arc1 share endpoint, , A0 = B1. + result.configuration = COCIRCULAR_ONE_POINT; + result.point[0] = arc0.end[0]; + } + } + return result; + } + + if (arc1.Contains(arc0.end[1])) + { + result.intersect = true; + if (arc0.end[1] != arc1.end[0]) + { + // Arc0 and arc1 overlap in a single arc, . + result.configuration = COCIRCULAR_ONE_ARC; + result.arc[0] = Arc2(arc0.center, arc0.radius, arc1.end[0], arc0.end[1]); + } + else + { + // Arc0 and arc1 share endpoint, , B0 = A1. + result.configuration = COCIRCULAR_ONE_POINT; + result.point[0] = arc1.end[0]; + } + return result; + } + + if (arc0.Contains(arc1.end[0])) + { + // Arc1 inside arc0, . + result.intersect = true; + result.configuration = COCIRCULAR_ONE_ARC; + result.arc[0] = arc1; + } + else + { + // Arcs do not overlap, . + result.configuration = NO_INTERSECTION; + } + return result; + } + + // Test whether circle-circle intersection points are on the arcs. + int numIntersections = 0; + for (int i = 0; i < ccResult.numIntersections; ++i) + { + if (arc0.Contains(ccResult.point[i]) && arc1.Contains(ccResult.point[i])) + { + result.point[numIntersections++] = ccResult.point[i]; + result.intersect = true; + } + } + + if (numIntersections == 2) + { + result.configuration = NONCOCIRCULAR_TWO_POINTS; + } + else if (numIntersections == 1) + { + result.configuration = NONCOCIRCULAR_ONE_POINT; + } + else + { + result.configuration = NO_INTERSECTION; + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCapsule3Capsule3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCapsule3Capsule3.h new file mode 100644 index 000000000000..02eda4900c29 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCapsule3Capsule3.h @@ -0,0 +1,45 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Capsule3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Capsule3 const& capsule0, + Capsule3 const& capsule1); +}; + + +template +typename TIQuery, Capsule3>::Result +TIQuery, Capsule3>::operator()( + Capsule3 const& capsule0, Capsule3 const& capsule1) +{ + Result result; + DCPQuery, Segment3> ssQuery; + auto ssResult = ssQuery(capsule0.segment, capsule1.segment); + Real rSum = capsule0.radius + capsule1.radius; + result.intersect = (ssResult.distance <= rSum); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCircle2Arc2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCircle2Arc2.h new file mode 100644 index 000000000000..1fed9c2a9e63 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCircle2Arc2.h @@ -0,0 +1,82 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class FIQuery, Arc2> +{ +public: + struct Result + { + bool intersect; + + // The number of intersections is 0, 1, 2, or maxInt = + // std::numeric_limits::max(). When 1, the arc and circle + // intersect in a single point. When 2, the arc is not on the circle + // and they intersect in two points. When maxInt, the arc is on the + // circle. + int numIntersections; + + // Valid only when numIntersections = 1 or 2. + Vector2 point[2]; + + // Valid only when numIntersections = maxInt. + Arc2 arc; + }; + + Result operator()(Circle2 const& circle, Arc2 const& arc); +}; + + +template +typename FIQuery, Arc2>::Result +FIQuery, Arc2>::operator()( + Circle2 const& circle, Arc2 const& arc) +{ + Result result; + + Circle2 circleOfArc(arc.center, arc.radius); + FIQuery, Circle2> ccQuery; + auto ccResult = ccQuery(circle, circleOfArc); + if (!ccResult.intersect) + { + result.intersect = false; + result.numIntersections = 0; + return result; + } + + if (ccResult.numIntersections == std::numeric_limits::max()) + { + // The arc is on the circle. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + result.arc = arc; + return result; + } + + // Test whether circle-circle intersection points are on the arc. + for (int i = 0; i < ccResult.numIntersections; ++i) + { + result.numIntersections = 0; + if (arc.Contains(ccResult.point[i])) + { + result.point[result.numIntersections++] = ccResult.point[i]; + result.intersect = true; + } + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCircle2Circle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCircle2Circle2.h new file mode 100644 index 000000000000..8babe879dbc2 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCircle2Circle2.h @@ -0,0 +1,172 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Circle2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Circle2 const& circle0, + Circle2 const& circle1); +}; + +template +class FIQuery, Circle2> +{ +public: + struct Result + { + bool intersect; + + // The number of intersections is 0, 1, 2, or maxInt = + // std::numeric_limits::max(). When 1, the circles are tangent + // and intersect in a single point. When 2, circles have two + // transverse intersection points. When maxInt, the circles are the + // same. + int numIntersections; + + // Valid only when numIntersections = 1 or 2. + Vector2 point[2]; + + // Valid only when numIntersections = maxInt. + Circle2 circle; + }; + + Result operator()(Circle2 const& circle0, + Circle2 const& circle1); +}; + + +template +typename TIQuery, Circle2>::Result +TIQuery, Circle2>::operator()( + Circle2 const& circle0, Circle2 const& circle1) +{ + Result result; + Vector2 diff = circle0.center - circle1.center; + result.intersect = (Length(diff) <= circle0.radius + circle1.radius); + return result; +} + +template +typename FIQuery, Circle2>::Result +FIQuery, Circle2>::operator()( + Circle2 const& circle0, Circle2 const& circle1) +{ + // The two circles are |X-C0| = R0 and |X-C1| = R1. Define U = C1 - C0 + // and V = Perp(U) where Perp(x,y) = (y,-x). Note that Dot(U,V) = 0 and + // |V|^2 = |U|^2. The intersection points X can be written in the form + // X = C0+s*U+t*V and X = C1+(s-1)*U+t*V. Squaring the circle equations + // and substituting these formulas into them yields + // R0^2 = (s^2 + t^2)*|U|^2 + // R1^2 = ((s-1)^2 + t^2)*|U|^2. + // Subtracting and solving for s yields + // s = ((R0^2-R1^2)/|U|^2 + 1)/2 + // Then replace in the first equation and solve for t^2 + // t^2 = (R0^2/|U|^2) - s^2. + // In order for there to be solutions, the right-hand side must be + // nonnegative. Some algebra leads to the condition for existence of + // solutions, + // (|U|^2 - (R0+R1)^2)*(|U|^2 - (R0-R1)^2) <= 0. + // This reduces to + // |R0-R1| <= |U| <= |R0+R1|. + // If |U| = |R0-R1|, then the circles are side-by-side and just tangent. + // If |U| = |R0+R1|, then the circles are nested and just tangent. + // If |R0-R1| < |U| < |R0+R1|, then the two circles to intersect in two + // points. + + Result result; + + Vector2 U = circle1.center - circle0.center; + Real USqrLen = Dot(U, U); + Real R0 = circle0.radius, R1 = circle1.radius; + Real R0mR1 = R0 - R1; + if (USqrLen == (Real)0 && R0mR1 == (Real)0) + { + // Circles are the same. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + result.circle = circle0; + return result; + } + + Real R0mR1Sqr = R0mR1*R0mR1; + if (USqrLen < R0mR1Sqr) + { + // The circles do not intersect. + result.intersect = false; + result.numIntersections = 0; + return result; + } + + Real R0pR1 = R0 + R1; + Real R0pR1Sqr = R0pR1*R0pR1; + if (USqrLen > R0pR1Sqr) + { + // The circles do not intersect. + result.intersect = false; + result.numIntersections = 0; + return result; + } + + if (USqrLen < R0pR1Sqr) + { + if (R0mR1Sqr < USqrLen) + { + Real invUSqrLen = ((Real)1) / USqrLen; + Real s = ((Real)0.5)*((R0*R0 - R1*R1)*invUSqrLen + (Real)1); + Vector2 tmp = circle0.center + s*U; + + // In theory, discr is nonnegative. However, numerical round-off + // errors can make it slightly negative. Clamp it to zero. + Real discr = R0*R0*invUSqrLen - s*s; + if (discr < (Real)0) + { + discr = (Real)0; + } + Real t = std::sqrt(discr); + Vector2 V{ U[1], -U[0] }; + result.point[0] = tmp - t*V; + result.point[1] = tmp + t*V; + result.numIntersections = (t >(Real)0 ? 2 : 1); + } + else + { + // |U| = |R0-R1|, circles are tangent. + result.numIntersections = 1; + result.point[0] = circle0.center + (R0 / R0mR1)*U; + } + } + else + { + // |U| = |R0+R1|, circles are tangent. + result.numIntersections = 1; + result.point[0] = circle0.center + (R0 / R0pR1)*U; + } + + // The circles intersect in 1 or 2 points. + result.intersect = true; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrConvexPolygonHyperplane.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrConvexPolygonHyperplane.h new file mode 100644 index 000000000000..eaa0933d4a36 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrConvexPolygonHyperplane.h @@ -0,0 +1,402 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2019/02/13) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// The intersection queries are based on the document +// https://www.geometrictools.com/Documentation/ClipConvexPolygonByHyperplane.pdf + +namespace gte +{ + template + class TIQuery>, Hyperplane> + { + public: + enum class Configuration + { + SPLIT, + POSITIVE_SIDE_VERTEX, + POSITIVE_SIDE_EDGE, + POSITIVE_SIDE_STRICT, + NEGATIVE_SIDE_VERTEX, + NEGATIVE_SIDE_EDGE, + NEGATIVE_SIDE_STRICT, + CONTAINED, + INVALID_POLYGON + }; + + struct Result + { + bool intersect; + Configuration configuration; + }; + + Result operator()(std::vector> const& polygon, Hyperplane const& hyperplane) + { + Result result; + + size_t const numVertices = polygon.size(); + if (numVertices < 3) + { + // The convex polygon must have at least 3 vertices. + result.intersect = false; + result.configuration = Configuration::INVALID_POLYGON; + return result; + } + + // Determine on which side of the hyperplane each vertex lies. + size_t numPositive = 0, numNegative = 0, numZero = 0; + for (size_t i = 0; i < numVertices; ++i) + { + Real h = Dot(hyperplane.normal, polygon[i]) - hyperplane.constant; + if (h > (Real)0) + { + ++numPositive; + } + else if (h < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + if (numPositive > 0) + { + if (numNegative > 0) + { + result.intersect = true; + result.configuration = Configuration::SPLIT; + } + else if (numZero == 0) + { + result.intersect = false; + result.configuration = Configuration::POSITIVE_SIDE_STRICT; + } + else if (numZero == 1) + { + result.intersect = true; + result.configuration = Configuration::POSITIVE_SIDE_VERTEX; + } + else // numZero > 1 + { + result.intersect = true; + result.configuration = Configuration::POSITIVE_SIDE_EDGE; + } + } + else if (numNegative > 0) + { + if (numZero == 0) + { + result.intersect = false; + result.configuration = Configuration::NEGATIVE_SIDE_STRICT; + } + else if (numZero == 1) + { + // The polygon touches the plane in a vertex or an edge. + result.intersect = true; + result.configuration = Configuration::NEGATIVE_SIDE_VERTEX; + } + else // numZero > 1 + { + result.intersect = true; + result.configuration = Configuration::NEGATIVE_SIDE_EDGE; + } + } + else // numZero == numVertices + { + result.intersect = true; + result.configuration = Configuration::CONTAINED; + } + + return result; + } + }; + + template + class FIQuery>, Hyperplane> + { + public: + enum class Configuration + { + SPLIT, + POSITIVE_SIDE_VERTEX, + POSITIVE_SIDE_EDGE, + POSITIVE_SIDE_STRICT, + NEGATIVE_SIDE_VERTEX, + NEGATIVE_SIDE_EDGE, + NEGATIVE_SIDE_STRICT, + CONTAINED, + INVALID_POLYGON + }; + + struct Result + { + // The intersection is either empty, a single vertex, a single + // edge or the polygon is contained by the hyperplane. + Configuration configuration; + std::vector> intersection; + + // If 'configuration' is POSITIVE_* or SPLIT, this polygon is the + // portion of the query input 'polygon' on the positive side of + // the hyperplane with possibly a vertex or edge on the hyperplane. + std::vector> positivePolygon; + + // If 'configuration' is NEGATIVE_* or SPLIT, this polygon is the + // portion of the query input 'polygon' on the negative side of + // the hyperplane with possibly a vertex or edge on the hyperplane. + std::vector> negativePolygon; + }; + + Result operator()(std::vector> const& polygon, Hyperplane const& hyperplane) + { + Result result; + + size_t const numVertices = polygon.size(); + if (numVertices < 3) + { + // The convex polygon must have at least 3 vertices. + result.configuration = Configuration::INVALID_POLYGON; + return result; + } + + // Determine on which side of the hyperplane the vertices live. + // The index maxPosIndex stores the index of the vertex on the + // positive side of the hyperplane that is farthest from the + // hyperplane. The index maxNegIndex stores the index of the + // vertex on the negative side of the hyperplane that is farthest + // from the hyperplane. If one or the other such vertex does not + // exist, the corresponding index will remain its initial value of + // max(size_t). + std::vector height(numVertices); + std::vector zeroHeightIndices; + zeroHeightIndices.reserve(numVertices); + size_t numPositive = 0, numNegative = 0; + Real maxPosHeight = -std::numeric_limits::max(); + Real maxNegHeight = std::numeric_limits::max(); + size_t maxPosIndex = std::numeric_limits::max(); + size_t maxNegIndex = std::numeric_limits::max(); + for (size_t i = 0; i < numVertices; ++i) + { + height[i] = Dot(hyperplane.normal, polygon[i]) - hyperplane.constant; + if (height[i] > (Real)0) + { + ++numPositive; + if (height[i] > maxPosHeight) + { + maxPosHeight = height[i]; + maxPosIndex = i; + } + } + else if (height[i] < (Real)0) + { + ++numNegative; + if (height[i] < maxNegHeight) + { + maxNegHeight = height[i]; + maxNegIndex = i; + } + } + else + { + zeroHeightIndices.push_back(i); + } + } + + if (numPositive > 0) + { + if (numNegative > 0) + { + result.configuration = Configuration::SPLIT; + + bool doSwap = (maxPosHeight < -maxNegHeight); + if (doSwap) + { + for (auto& h : height) + { + h = -h; + } + std::swap(maxPosIndex, maxNegIndex); + } + + SplitPolygon(polygon, height, maxPosIndex, result); + + if (doSwap) + { + std::swap(result.positivePolygon, result.negativePolygon); + } + } + else + { + size_t numZero = zeroHeightIndices.size(); + if (numZero == 0) + { + result.configuration = Configuration::POSITIVE_SIDE_STRICT; + } + else if (numZero == 1) + { + result.configuration = Configuration::POSITIVE_SIDE_VERTEX; + result.intersection = + { + polygon[zeroHeightIndices[0]] + }; + } + else // numZero > 1 + { + result.configuration = Configuration::POSITIVE_SIDE_EDGE; + result.intersection = + { + polygon[zeroHeightIndices[0]], + polygon[zeroHeightIndices[1]] + }; + } + result.positivePolygon = polygon; + } + } + else if (numNegative > 0) + { + size_t numZero = zeroHeightIndices.size(); + if (numZero == 0) + { + result.configuration = Configuration::NEGATIVE_SIDE_STRICT; + } + else if (numZero == 1) + { + result.configuration = Configuration::NEGATIVE_SIDE_VERTEX; + result.intersection = + { + polygon[zeroHeightIndices[0]] + }; + } + else // numZero > 1 + { + result.configuration = Configuration::NEGATIVE_SIDE_EDGE; + result.intersection = + { + polygon[zeroHeightIndices[0]], + polygon[zeroHeightIndices[1]] + }; + } + result.negativePolygon = polygon; + } + else // numZero == numVertices + { + result.configuration = Configuration::CONTAINED; + result.intersection = polygon; + } + + return result; + } + + protected: + void SplitPolygon(std::vector> const& polygon, + std::vector const& height, size_t maxPosIndex, Result& result) + { + // Find the largest contiguous subset of indices for which + // height[i] >= 0. + size_t const numVertices = polygon.size(); + std::list> positiveList; + positiveList.push_back(polygon[maxPosIndex]); + size_t end0 = maxPosIndex; + size_t end0prev = std::numeric_limits::max(); + for (size_t i = 0; i < numVertices; ++i) + { + end0prev = (end0 + numVertices - 1) % numVertices; + if (height[end0prev] >= (Real)0) + { + positiveList.push_front(polygon[end0prev]); + end0 = end0prev; + } + else + { + break; + } + } + + size_t end1 = maxPosIndex; + size_t end1next = std::numeric_limits::max(); + for (size_t i = 0; i < numVertices; ++i) + { + end1next = (end1 + 1) % numVertices; + if (height[end1next] >= (Real)0) + { + positiveList.push_back(polygon[end1next]); + end1 = end1next; + } + else + { + break; + } + } + + size_t index = end1next; + std::list> negativeList; + for (size_t i = 0; i < numVertices; ++i) + { + negativeList.push_back(polygon[index]); + index = (index + 1) % numVertices; + if (index == end0) + { + break; + } + } + + // Clip the polygon. + if (height[end0] > (Real)0) + { + Real t = -height[end0prev] / (height[end0] - height[end0prev]); + Real omt = (Real)1 - t; + Vector V = omt * polygon[end0prev] + t * polygon[end0]; + positiveList.push_front(V); + negativeList.push_back(V); + result.intersection.push_back(V); + } + else + { + negativeList.push_back(polygon[end0]); + result.intersection.push_back(polygon[end0]); + } + + if (height[end1] > (Real)0) + { + Real t = -height[end1next] / (height[end1] - height[end1next]); + Real omt = (Real)1 - t; + Vector V = omt * polygon[end1next] + t * polygon[end1]; + positiveList.push_back(V); + negativeList.push_front(V); + result.intersection.push_back(V); + } + else + { + negativeList.push_front(polygon[end1]); + result.intersection.push_back(polygon[end1]); + } + + result.positivePolygon.reserve(positiveList.size()); + for (auto const& p : positiveList) + { + result.positivePolygon.push_back(p); + } + + result.negativePolygon.reserve(negativeList.size()); + for (auto const& p : negativeList) + { + result.negativePolygon.push_back(p); + } + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrDisk2Sector2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrDisk2Sector2.h new file mode 100644 index 000000000000..6bd9aa1078f0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrDisk2Sector2.h @@ -0,0 +1,185 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +// The Circle2 object is considered to be a disk whose points X satisfy the +// constraint |X-C| <= R, where C is the disk center and R is the disk +// radius. The Sector2 object is also considered to be a solid. Also, +// the Sector2 object is required to be convex, so the sector angle must +// be in (0,pi/2], even though the Sector2 definition allows for angles +// larger than pi/2 (leading to nonconvex sectors). The sector vertex is +// V, the radius is L, the axis direction is D, and the angle is A. Sector +// points X satisfy |X-V| <= L and Dot(D,X-V) >= cos(A)|X-V| >= 0. +// +// A subproblem for the test-intersection query is to determine whether +// the disk intersects the cone of the sector. Although the query is in +// 2D, it is analogous to the 3D problem of determining whether a sphere +// and cone overlap. That algorithm is described in +// http://www.geometrictools.com/Documentation/IntersectionSphereCone.pdf +// The algorithm leads to coordinate-free pseudocod which applies to 2D +// as well as 3D. That function is the first SphereIntersectsCone on +// page 4 of the PDF. +// +// If the disk is outside the cone, there is no intersection. If the disk +// overlaps the cone, we then need to test whether the disk overlaps the +// disk of the sector. + +namespace gte +{ + +template +class TIQuery, Sector2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Circle2 const& disk, Sector2 const& sector); +}; + +template +typename TIQuery, Sector2>::Result +TIQuery, Sector2>::operator()( + Circle2 const& disk, Sector2 const& sector) +{ + Result result; + + // Test whether the disk and the disk of the sector overlap. + Vector2 CmV = disk.center - sector.vertex; + Real sqrLengthCmV = Dot(CmV, CmV); + Real lengthCmV = std::sqrt(sqrLengthCmV); + if (lengthCmV > disk.radius + sector.radius) + { + // The disk is outside the disk of the sector. + result.intersect = false; + return result; + } + + // Test whether the disk and cone of the sector overlap. The comments + // about K, K', and K" refer to the PDF mentioned previously. + Vector2 U = sector.vertex - (disk.radius / sector.sinAngle) * sector.direction; + Vector2 CmU = disk.center - U; + Real lengthCmU = Length(CmU); + if (Dot(sector.direction, CmU) < lengthCmU * sector.cosAngle) + { + // The disk center is outside K" (in the white or gray regions). + result.intersect = false; + return result; + } + // The disk center is inside K" (in the red, orange, blue, or green regions). + Real dotDirCmV = Dot(sector.direction, CmV); + if (-dotDirCmV >= lengthCmV * sector.sinAngle) + { + // The disk center is inside K" and inside K' (in the blue or green regions). + if (lengthCmV <= disk.radius) + { + // The disk center is in the blue region, in which case the disk + // contains the sector's vertex. + result.intersect = true; + } + else + { + // The disk center is in the green region. + result.intersect = false; + } + return result; + } + + // To reach here, we know that the disk overlaps the sector's disk and + // the sector's cone. The disk center is in the orange region or in + // in the red region (not including the segments that separate the red + // and blue regions). + + // Test whether the ray of the right boundary of the sector overlaps + // the disk. The ray direction U0 is a clockwise rotation of the + // cone axis by the cone angle. + Vector2 U0 + { + +sector.cosAngle * sector.direction[0] + sector.sinAngle * sector.direction[1], + -sector.sinAngle * sector.direction[0] + sector.cosAngle * sector.direction[1] + }; + Real dp0 = Dot(U0, CmV); + Real discr0 = disk.radius * disk.radius + dp0 * dp0 - sqrLengthCmV; + if (discr0 >= (Real)0) + { + // The ray intersects the disk. Now test whether the sector boundary + // segment contained by the ray overlaps the disk. The quadratic + // root tmin generates the ray-disk point of intersection closest to + // the sector vertex. + Real tmin = dp0 - std::sqrt(discr0); + if (sector.radius >= tmin) + { + // The segment overlaps the disk. + result.intersect = true; + return result; + } + else + { + // The segment does not overlap the disk. We know the disks + // overlap, so if the disk center is outside the sector cone + // or on the right-boundary ray, the overlap occurs outside the + // cone, which implies the disk and sector do not intersect. + if (dotDirCmV <= lengthCmV * sector.cosAngle) + { + // The disk center is not inside the sector cone. + result.intersect = false; + return result; + } + } + } + + // Test whether the ray of the left boundary of the sector overlaps + // the disk. The ray direction U1 is a counterclockwise rotation of the + // cone axis by the cone angle. + Vector2 U1 + { + +sector.cosAngle * sector.direction[0] - sector.sinAngle * sector.direction[1], + +sector.sinAngle * sector.direction[0] + sector.cosAngle * sector.direction[1] + }; + Real dp1 = Dot(U1, CmV); + Real discr1 = disk.radius * disk.radius + dp1 * dp1 - sqrLengthCmV; + if (discr1 >= (Real)0) + { + // The ray intersects the disk. Now test whether the sector boundary + // segment contained by the ray overlaps the disk. The quadratic + // root tmin generates the ray-disk point of intersection closest to + // the sector vertex. + Real tmin = dp1 - std::sqrt(discr1); + if (sector.radius >= tmin) + { + result.intersect = true; + return result; + } + else + { + // The segment does not overlap the disk. We know the disks + // overlap, so if the disk center is outside the sector cone + // or on the right-boundary ray, the overlap occurs outside the + // cone, which implies the disk and sector do not intersect. + if (dotDirCmV <= lengthCmV * sector.cosAngle) + { + // The disk center is not inside the sector cone. + result.intersect = false; + return result; + } + } + } + + // To reach here, a strict subset of the sector's arc boundary must + // intersect the disk. + result.intersect = true; + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrEllipse2Ellipse2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrEllipse2Ellipse2.h new file mode 100644 index 000000000000..291ffed9dba6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrEllipse2Ellipse2.h @@ -0,0 +1,1369 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.4 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +// The test-intersection and find-intersection queries implemented here are +// discussed in the document +// http://www.geometrictools.com/Documentation/IntersectionOfEllipses.pdf +// The Real type should support exact rational arithmetic in order for the +// polynomial root construction to be robust. The classification of the +// intersections depends on various sign tests of computed values. If these +// values are computed with floating-point arithmetic, the sign tests can +// lead to misclassification. +// +// The area-of-intersection query is discussed in the document +// http://www.geometrictools.com/Documentation/AreaIntersectingEllipses.pdf + +namespace gte +{ + +template +class TIQuery, Ellipse2> +{ +public: + // The query tests the relationship between the ellipses as solid + // objects. + enum + { + ELLIPSES_SEPARATED, + ELLIPSES_OVERLAP, + ELLIPSE0_OUTSIDE_ELLIPSE1_BUT_TANGENT, + ELLIPSE0_STRICTLY_CONTAINS_ELLIPSE1, + ELLIPSE0_CONTAINS_ELLIPSE1_BUT_TANGENT, + ELLIPSE1_STRICTLY_CONTAINS_ELLIPSE0, + ELLIPSE1_CONTAINS_ELLIPSE0_BUT_TANGENT, + ELLIPSES_EQUAL + }; + + // The ellipse axes are already normalized, which most likely introduced + // rounding errors. + int operator()(Ellipse2 const& ellipse0, + Ellipse2 const& ellipse1); + +private: + void GetRoots(Real d0, Real c0, int& numRoots, Real* roots); + void GetRoots(Real d0, Real d1, Real c0, Real c1, int& numRoots, + Real* roots); + + int Classify(Real minSqrDistance, Real maxSqrDistance, Real d0c0pd1c1); +}; + +template +class FIQuery, Ellipse2> +{ +public: + // The queries find the intersections (if any) of the ellipses treated as + // hollow objects. The implementations use the same concepts. + FIQuery(); + + struct Result + { + // This value is true when the ellipses intersect in at least one + // point. + bool intersect; + + // If the ellipses are not the same, numPoints is 0 through 4 and + // that number of elements of 'points' are valid. If the ellipses + // are the same, numPoints is set to maxInt and 'points' is invalid. + int numPoints; + std::array, 4> points; + std::array isTransverse; + }; + + // The ellipse axes are already normalized, which most likely introduced + // rounding errors. + Result operator()(Ellipse2 const& ellipse0, + Ellipse2 const& ellipse1); + + // The axis directions do not have to be unit length. The quadratic + // equations are constructed according to the details of the PDF document + // about the intersection of ellipses. + Result operator()(Vector2 const& center0, + Vector2 const axis0[2], Vector2 const& sqrExtent0, + Vector2 const& center1, Vector2 const axis1[2], + Vector2 const& sqrExtent1); + + + // Compute the area of intersection of ellipses. + struct AreaResult + { + // The configuration of the two ellipses. + enum + { + ELLIPSES_ARE_EQUAL, + ELLIPSES_ARE_SEPARATED, + E0_CONTAINS_E1, + E1_CONTAINS_E0, + ONE_CHORD_REGION, + FOUR_CHORD_REGION + }; + + // One of the enumerates, determined in the call to AreaDispatch. + int configuration; + + // Information about the ellipse-ellipse intersection points. + Result findResult; + + // The area of intersection of the ellipses. + Real area; + }; + + // The ellipse axes are already normalized, which most likely introduced + // rounding errors. + AreaResult AreaOfIntersection(Ellipse2 const& ellipse0, + Ellipse2 const& ellipse1); + + // The axis directions do not have to be unit length. The positive + // definite matrices are constructed according to the details of the PDF + // documentabout the intersection of ellipses. + AreaResult AreaOfIntersection(Vector2 const& center0, + Vector2 const axis0[2], Vector2 const& sqrExtent0, + Vector2 const& center1, Vector2 const axis1[2], + Vector2 const& sqrExtent1); + +private: + // Compute the coefficients of the quadratic equation but with the + // y^2-coefficient of 1. + void ToCoefficients(Vector2 const& center, + Vector2 const axis[2], Vector2 const& SqrExtent, + std::array& coeff); + + void DoRootFinding(Result& result); + void D4NotZeroEBarNotZero(Result& result); + void D4NotZeroEBarZero(Real const& xbar, Result& result); + void D4ZeroD2NotZeroE2NotZero(Result& result); + void D4ZeroD2NotZeroE2Zero(Result& result); + void D4ZeroD2Zero(Result& result); + + // Helper function for D4ZeroD2Zero. + void SpecialIntersection(Real const& x, bool transverse, Result& result); + + + // Helper functions for AreaOfIntersection. + struct EllipseInfo + { + Vector2 center; + std::array, 2> axis; + Vector2 extent, sqrExtent; + Matrix2x2 M; + Real AB; // extent[0] * extent[1] + Real halfAB; // extent[0] * extent[1] / 2 + Real BpA; // extent[1] + extent[0] + Real BmA; // extent[1] - extent[0] + }; + + // The axis, extent, and sqrExtent members of E must be set before this + // function is called. + void FinishEllipseInfo(EllipseInfo& E); + + void AreaDispatch(EllipseInfo const& E0, EllipseInfo const& E1, + AreaResult& ar); + + void AreaCS(EllipseInfo const& E0, EllipseInfo const& E1, + AreaResult& ar); + + void Area2(EllipseInfo const& E0, EllipseInfo const& E1, int i0, int i1, + AreaResult& ar); + + void Area4(EllipseInfo const& E0, EllipseInfo const& E1, + AreaResult& ar); + + Real ComputeAreaChordRegion(EllipseInfo const& E, + Vector2 const& P0mC, Vector2 const& P1mC); + + Real ComputeIntegral(EllipseInfo const& E, Real const& theta); + + + // Constants that are set up once (optimization for rational arithmetic). + Real mZero, mOne, mTwo, mPi, mTwoPi; + + // Various polynomial coefficients. The short names are meant to match + // the variable names used in the PDF documentation. + std::array mA, mB, mD, mF; + std::array mC, mE; + Real mA2Div2, mA4Div2; +}; + +// Template aliases for convenience. +template +using TIEllipses2 = TIQuery, Ellipse2>; + +template +using FIEllipses2 = FIQuery, Ellipse2>; + + +// TIQuery for intersections + +template +int TIQuery, Ellipse2>::operator()( + Ellipse2 const& ellipse0, Ellipse2 const& ellipse1) +{ + Real const zero = 0, one = 1; + + // Get the parameters of ellipe0. + Vector2 K0 = ellipse0.center; + Matrix2x2 R0; + R0.SetCol(0, ellipse0.axis[0]); + R0.SetCol(1, ellipse0.axis[1]); + Matrix2x2 D0{ + one / (ellipse0.extent[0] * ellipse0.extent[0]), zero, + zero, one / (ellipse0.extent[1] * ellipse0.extent[1]) }; + + // Get the parameters of ellipse1. + Vector2 K1 = ellipse1.center; + Matrix2x2 R1; + R1.SetCol(0, ellipse1.axis[0]); + R1.SetCol(1, ellipse1.axis[1]); + Matrix2x2 D1{ + one / (ellipse1.extent[0] * ellipse1.extent[0]), zero, + zero, one / (ellipse1.extent[1] * ellipse1.extent[1]) }; + + // Compute K2. + Matrix2x2 D0NegHalf{ + ellipse0.extent[0], zero, + zero, ellipse0.extent[1] }; + Matrix2x2 D0Half{ + one / ellipse0.extent[0], zero, + zero, one / ellipse0.extent[1] }; + Vector2 K2 = D0Half*((K1 - K0)*R0); + + // Compute M2. + Matrix2x2 R1TR0D0NegHalf = MultiplyATB(R1, R0 * D0NegHalf); + Matrix2x2 M2 = MultiplyATB(R1TR0D0NegHalf, D1) * R1TR0D0NegHalf; + + // Factor M2 = R*D*R^T. + SymmetricEigensolver2x2 es; + std::array D; + std::array, 2> evec; + es(M2(0, 0), M2(0, 1), M2(1, 1), +1, D, evec); + Matrix2x2 R; + R.SetCol(0, evec[0]); + R.SetCol(1, evec[1]); + + // Compute K = R^T*K2. + Vector2 K = K2 * R; + + // Transformed ellipse0 is Z^T*Z = 1 and transformed ellipse1 is + // (Z-K)^T*D*(Z-K) = 0. + + // The minimum and maximum squared distances from the origin of points on + // transformed ellipse1 are used to determine whether the ellipses + // intersect, are separated, or one contains the other. + Real minSqrDistance = std::numeric_limits::max(); + Real maxSqrDistance = zero; + + if (K == Vector2::Zero()) + { + // The special case of common centers must be handled separately. It + // is not possible for the ellipses to be separated. + for (int i = 0; i < 2; ++i) + { + Real invD = one / D[i]; + if (invD < minSqrDistance) + { + minSqrDistance = invD; + } + if (invD > maxSqrDistance) + { + maxSqrDistance = invD; + } + } + return Classify(minSqrDistance, maxSqrDistance, zero); + } + + // The closest point P0 and farthest point P1 are solutions to + // s0*D*(P0 - K) = P0 and s1*D1*(P1 - K) = P1 for some scalars s0 and s1 + // that are roots to the function + // f(s) = d0*k0^2/(d0*s-1)^2 + d1*k1^2/(d1*s-1)^2 - 1 + // where D = diagonal(d0,d1) and K = (k0,k1). + Real d0 = D[0], d1 = D[1]; + Real c0 = K[0] * K[0], c1 = K[1] * K[1]; + + // Sort the values so that d0 >= d1. This allows us to bound the roots of + // f(s), of which there are at most 4. + std::vector> param(2); + if (d0 >= d1) + { + param[0] = std::make_pair(d0, c0); + param[1] = std::make_pair(d1, c1); + } + else + { + param[0] = std::make_pair(d1, c1); + param[1] = std::make_pair(d0, c0); + } + + std::vector> valid; + valid.reserve(2); + if (param[0].first > param[1].first) + { + // d0 > d1 + for (int i = 0; i < 2; ++i) + { + if (param[i].second > zero) + { + valid.push_back(param[i]); + } + } + } + else + { + // d0 = d1 + param[0].second += param[1].second; + if (param[0].second > zero) + { + valid.push_back(param[0]); + } + } + + size_t numValid = valid.size(); + int numRoots = 0; + Real roots[4]; + if (numValid == 2) + { + GetRoots(valid[0].first, valid[1].first, valid[0].second, + valid[1].second, numRoots, roots); + } + else if (numValid == 1) + { + GetRoots(valid[0].first, valid[0].second, numRoots, roots); + } + // else: numValid cannot be zero because we already handled case K = 0 + + for (int i = 0; i < numRoots; ++i) + { + Real s = roots[i]; + Real p0 = d0 * K[0] * s / (d0 * s - (Real)1); + Real p1 = d1 * K[1] * s / (d1 * s - (Real)1); + Real sqrDistance = p0 * p0 + p1 * p1; + if (sqrDistance < minSqrDistance) + { + minSqrDistance = sqrDistance; + } + if (sqrDistance > maxSqrDistance) + { + maxSqrDistance = sqrDistance; + } + } + + return Classify(minSqrDistance, maxSqrDistance, d0 * c0 + d1 * c1); +} + +template +void TIQuery, Ellipse2>::GetRoots(Real d0, Real c0, + int& numRoots, Real* roots) +{ + // f(s) = d0*c0/(d0*s-1)^2 - 1 + Real const one = (Real)1; + Real temp = std::sqrt(d0*c0); + Real inv = one / d0; + numRoots = 2; + roots[0] = (one - temp) * inv; + roots[1] = (one + temp) * inv; +} + +template +void TIQuery, Ellipse2>::GetRoots(Real d0, Real d1, + Real c0, Real c1, int& numRoots, Real* roots) +{ + // f(s) = d0*c0/(d0*s-1)^2 + d1*c1/(d1*s-1)^2 - 1 + // with d0 > d1 + + Real const zero = 0, one = (Real)1; + Real d0c0 = d0 * c0; + Real d1c1 = d1 * c1; + Real sum = d0c0 + d1c1; + Real sqrtsum = std::sqrt(sum); + + std::function F = [&one, d0, d1, d0c0, d1c1](Real s) + { + Real invN0 = one / (d0*s - one); + Real invN1 = one / (d1*s - one); + Real term0 = d0c0 * invN0 * invN0; + Real term1 = d1c1 * invN1 * invN1; + Real f = term0 + term1 - one; + return f; + }; + + std::function DF = [&one, d0, d1, d0c0, d1c1](Real s) + { + Real const two = 2; + Real invN0 = one / (d0*s - one); + Real invN1 = one / (d1*s - one); + Real term0 = d0 * d0c0 * invN0 * invN0 * invN0; + Real term1 = d1 * d1c1 * invN1 * invN1 * invN1; + Real df = -two * (term0 + term1); + return df; + }; + + unsigned int const maxIterations = (unsigned int)( + 3 + std::numeric_limits::digits - + std::numeric_limits::min_exponent); + unsigned int iterations; + numRoots = 0; + + Real invD0 = one / d0; + Real invD1 = one / d1; + Real smin, smax, s, fval; + + // Compute root in (-infinity,1/d0). Obtain a lower bound for the root + // better than -std::numeric_limits::max(). + smax = invD0; + fval = sum - one; + if (fval > zero) + { + smin = (one - sqrtsum) * invD1; // < 0 + fval = F(smin); + LogAssert(fval <= zero, "Unexpected condition."); + } + else + { + smin = zero; + } + iterations = RootsBisection::Find(F, smin, smax, -one, one, + maxIterations, s); + fval = F(s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + + // Compute roots (if any) in (1/d0,1/d1). It is the case that + // F(1/d0) = +infinity, F'(1/d0) = -infinity + // F(1/d1) = +infinity, F'(1/d1) = +infinity + // F"(s) > 0 for all s in the domain of F + // Compute the unique root r of F'(s) on (1/d0,1/d1). The bisector needs + // only the signs at the endpoints, so we pass -1 and +1 instead of the + // infinite values. If F(r) < 0, F(s) has two roots in the interval. + // If F(r) = 0, F(s) has only one root in the interval. + Real const oneThird = (Real)(1.0 / 3.0); + Real rho = std::pow(d0 * d0c0 / (d1 * d1c1), oneThird); + Real smid = (one + rho) / (d0 + rho * d1); + Real fmid = F(smid); + if (fmid <= zero) + { + // Pass in signs rather than infinities, because the bisector cares + // only about the signs. + iterations = RootsBisection::Find(F, invD0, smid, one, -one, + maxIterations, s); + fval = F(s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + iterations = RootsBisection::Find(F, smid, invD1, -one, one, + maxIterations, s); + fval = F(s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + } + else if (fmid == zero) + { + roots[numRoots++] = smid; + } + + // Compute root in (1/d1,+infinity). Obtain an upper bound for the root + // better than std::numeric_limits::max(). + smin = invD1; + smax = (one + sqrtsum) * invD1; // > 1/d1 + fval = F(smax); + LogAssert(fval <= zero, "Unexpected condition."); + iterations = RootsBisection::Find(F, smin, smax, one, -one, + maxIterations, s); + fval = F(s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; +} + +template +int TIQuery, Ellipse2>::Classify( + Real minSqrDistance, Real maxSqrDistance, Real d0c0pd1c1) +{ + Real const one = (Real)1; + + if (maxSqrDistance < one) + { + return ELLIPSE0_STRICTLY_CONTAINS_ELLIPSE1; + } + else if (maxSqrDistance > one) + { + if (minSqrDistance < one) + { + return ELLIPSES_OVERLAP; + } + else if (minSqrDistance > one) + { + if (d0c0pd1c1 > one) + { + return ELLIPSES_SEPARATED; + } + else + { + return ELLIPSE1_STRICTLY_CONTAINS_ELLIPSE0; + } + } + else // minSqrDistance = 1 + { + if (d0c0pd1c1 > one) + { + return ELLIPSE0_OUTSIDE_ELLIPSE1_BUT_TANGENT; + } + else + { + return ELLIPSE1_CONTAINS_ELLIPSE0_BUT_TANGENT; + } + } + } + else // maxSqrDistance = 1 + { + if (minSqrDistance < one) + { + return ELLIPSE0_CONTAINS_ELLIPSE1_BUT_TANGENT; + } + else // minSqrDistance = 1 + { + return ELLIPSES_EQUAL; + } + } +} + + + +// FIQuery constructor for both intersection and area queries. + +template +FIQuery, Ellipse2>::FIQuery() + : + mZero((Real)0), + mOne((Real)1), + mTwo((Real)2), + mPi((Real)GTE_C_PI), + mTwoPi((Real)GTE_C_TWO_PI) +{ +} + + + +// FIQuery functions for ellipse-ellipse intersection points. + +template +typename FIQuery, Ellipse2>::Result +FIQuery, Ellipse2>::operator()( + Ellipse2 const& ellipse0, Ellipse2 const& ellipse1) +{ + Vector2 rCenter, rAxis[2], rSqrExtent; + + rCenter = { ellipse0.center[0], ellipse0.center[1] }; + rAxis[0] = { ellipse0.axis[0][0], ellipse0.axis[0][1] }; + rAxis[1] = { ellipse0.axis[1][0], ellipse0.axis[1][1] }; + rSqrExtent = + { + ellipse0.extent[0] * ellipse0.extent[0], + ellipse0.extent[1] * ellipse0.extent[1] + }; + ToCoefficients(rCenter, rAxis, rSqrExtent, mA); + + rCenter = { ellipse1.center[0], ellipse1.center[1] }; + rAxis[0] = { ellipse1.axis[0][0], ellipse1.axis[0][1] }; + rAxis[1] = { ellipse1.axis[1][0], ellipse1.axis[1][1] }; + rSqrExtent = + { + ellipse1.extent[0] * ellipse1.extent[0], + ellipse1.extent[1] * ellipse1.extent[1] + }; + ToCoefficients(rCenter, rAxis, rSqrExtent, mB); + + Result result; + DoRootFinding(result); + return result; +} + +template +typename FIQuery, Ellipse2>::Result +FIQuery, Ellipse2>::operator()( + Vector2 const& center0, Vector2 const axis0[2], + Vector2 const& sqrExtent0, Vector2 const& center1, + Vector2 const axis1[2], Vector2 const& sqrExtent1) +{ + Vector2 rCenter, rAxis[2], rSqrExtent; + + rCenter = { center0[0], center0[1] }; + rAxis[0] = { axis0[0][0], axis0[0][1] }; + rAxis[1] = { axis0[1][0], axis0[1][1] }; + rSqrExtent = { sqrExtent0[0], sqrExtent0[1] }; + ToCoefficients(rCenter, rAxis, rSqrExtent, mA); + + rCenter = { center1[0], center1[1] }; + rAxis[0] = { axis1[0][0], axis1[0][1] }; + rAxis[1] = { axis1[1][0], axis1[1][1] }; + rSqrExtent = { sqrExtent1[0], sqrExtent1[1] }; + ToCoefficients(rCenter, rAxis, rSqrExtent, mB); + + Result result; + DoRootFinding(result); + return result; +} + +template +void FIQuery, Ellipse2>::ToCoefficients( + Vector2 const& center, Vector2 const axis[2], + Vector2 const& sqrExtent, std::array& coeff) +{ + Real denom0 = Dot(axis[0], axis[0]) * sqrExtent[0]; + Real denom1 = Dot(axis[1], axis[1]) * sqrExtent[1]; + Matrix2x2 outer0 = OuterProduct(axis[0], axis[0]); + Matrix2x2 outer1 = OuterProduct(axis[1], axis[1]); + Matrix2x2 A = outer0 / denom0 + outer1 / denom1; + Vector2 product = A * center; + Vector2 B = -mTwo * product; + Real const& denom = A(1, 1); + coeff[0] = (Dot(center, product) - mOne) / denom; + coeff[1] = B[0] / denom; + coeff[2] = B[1] / denom; + coeff[3] = A(0, 0) / denom; + coeff[4] = mTwo * A(0, 1) / denom; + // coeff[5] = A(1, 1) / denom = 1; +} + +template +void FIQuery, Ellipse2>::DoRootFinding( + Result& result) +{ + bool allZero = true; + for (int i = 0; i < 5; ++i) + { + mD[i] = mA[i] - mB[i]; + if (mD[i] != mZero) + { + allZero = false; + } + } + if (allZero) + { + result.intersect = false; + result.numPoints = std::numeric_limits::max(); + return; + } + + result.numPoints = 0; + + mA2Div2 = mA[2] / mTwo; + mA4Div2 = mA[4] / mTwo; + mC[0] = mA[0] - mA2Div2 * mA2Div2; + mC[1] = mA[1] - mA2Div2 * mA[4]; + mC[2] = mA[3] - mA4Div2 * mA4Div2; // c[2] > 0 + mE[0] = mD[0] - mA2Div2 * mD[2]; + mE[1] = mD[1] - mA2Div2 * mD[4] - mA4Div2 * mD[2]; + mE[2] = mD[3] - mA4Div2 * mD[4]; + + if (mD[4] != mZero) + { + Real xbar = -mD[2] / mD[4]; + Real ebar = mE[0] + xbar * (mE[1] + xbar * mE[2]); + if (ebar != mZero) + { + D4NotZeroEBarNotZero(result); + } + else + { + D4NotZeroEBarZero(xbar, result); + } + } + else if (mD[2] != mZero) // d[4] = 0 + { + if (mE[2] != mZero) + { + D4ZeroD2NotZeroE2NotZero(result); + } + else + { + D4ZeroD2NotZeroE2Zero(result); + } + } + else // d[2] = d[4] = 0 + { + D4ZeroD2Zero(result); + } + + result.intersect = (result.numPoints > 0); +} + +template +void FIQuery, Ellipse2>::D4NotZeroEBarNotZero( + Result& result) +{ + // The graph of w = -e(x)/d(x) is a hyperbola. + Real d2d2 = mD[2] * mD[2], d2d4 = mD[2] * mD[4], d4d4 = mD[4] * mD[4]; + Real e0e0 = mE[0] * mE[0], e0e1 = mE[0] * mE[1], e0e2 = mE[0] * mE[2]; + Real e1e1 = mE[1] * mE[1], e1e2 = mE[1] * mE[2], e2e2 = mE[2] * mE[2]; + std::array f = + { + mC[0] * d2d2 + e0e0, + mC[1] * d2d2 + mTwo * (mC[0] * d2d4 + e0e1), + mC[2] * d2d2 + mC[0] * d4d4 + e1e1 + mTwo * (mC[1] * d2d4 + e0e2), + mC[1] * d4d4 + mTwo * (mC[2] * d2d4 + e1e2), + mC[2] * d4d4 + e2e2 // > 0 + }; + + std::map rmMap; + RootsPolynomial::template SolveQuartic(f[0], f[1], f[2], + f[3], f[4], rmMap); + + // xbar cannot be a root of f(x), so d(x) != 0 and we can solve + // directly for w = -e(x)/d(x). + for (auto const& rm : rmMap) + { + Real const& x = rm.first; + Real w = -(mE[0] + x * (mE[1] + x * mE[2])) / (mD[2] + mD[4] * x); + Real y = w - (mA2Div2 + x * mA4Div2); + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = (rm.second == 1); + } +} + +template +void FIQuery, Ellipse2>::D4NotZeroEBarZero( + Real const& xbar, Result& result) +{ + // Factor e(x) = (d2 + d4*x)*(h0 + h1*x). The w-equation has + // two solution components, x = xbar and w = -(h0 + h1*x). + Real translate, w, y; + + // Compute intersection of x = xbar with ellipse. + Real ncbar = -(mC[0] + xbar * (mC[1] + xbar * mC[2])); + if (ncbar >= mZero) + { + translate = mA2Div2 + xbar * mA4Div2; + w = std::sqrt(ncbar); + y = w - translate; + result.points[result.numPoints] = { xbar, y }; + if (w > mZero) + { + result.isTransverse[result.numPoints++] = true; + w = -w; + y = w - translate; + result.points[result.numPoints] = { xbar, y }; + result.isTransverse[result.numPoints++] = true; + } + else + { + result.isTransverse[result.numPoints++] = false; + } + } + + // Compute intersections of w = -(h0 + h1*x) with ellipse. + std::array h; + h[1] = mE[2] / mD[4]; + h[0] = (mE[1] - mD[2] * h[1]) / mD[4]; + std::array f = + { + mC[0] + h[0] * h[0], + mC[1] + mTwo * h[0] * h[1], + mC[2] + h[1] * h[1] // > 0 + }; + + std::map rmMap; + RootsPolynomial::template SolveQuadratic(f[0], f[1], f[2], + rmMap); + for (auto const& rm : rmMap) + { + Real const& x = rm.first; + translate = mA2Div2 + x * mA4Div2; + w = -(h[0] + x * h[1]); + y = w - translate; + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = (rm.second == 1); + } +} + +template +void FIQuery, Ellipse2>::D4ZeroD2NotZeroE2NotZero( + Result& result) +{ + Real d2d2 = mD[2] * mD[2]; + std::array f = + { + mC[0] * d2d2 + mE[0] * mE[0], + mC[1] * d2d2 + mTwo * mE[0] * mE[1], + mC[2] * d2d2 + mE[1] * mE[1] + mTwo * mE[0] * mE[2], + mTwo * mE[1] * mE[2], + mE[2] * mE[2] // > 0 + }; + + std::map rmMap; + RootsPolynomial::template SolveQuartic(f[0], f[1], f[2], f[3], + f[4], rmMap); + for (auto const& rm : rmMap) + { + Real const& x = rm.first; + Real translate = mA2Div2 + x * mA4Div2; + Real w = -(mE[0] + x * (mE[1] + x * mE[2])) / mD[2]; + Real y = w - translate; + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = (rm.second == 1); + } +} + +template +void FIQuery, Ellipse2>::D4ZeroD2NotZeroE2Zero( + Result& result) +{ + Real d2d2 = mD[2] * mD[2]; + std::array f = + { + mC[0] * d2d2 + mE[0] * mE[0], + mC[1] * d2d2 + mTwo * mE[0] * mE[1], + mC[2] * d2d2 + mE[1] * mE[1] + }; + + std::map rmMap; + RootsPolynomial::template SolveQuadratic(f[0], f[1], f[2], + rmMap); + for (auto const& rm : rmMap) + { + Real const& x = rm.first; + Real translate = mA2Div2 + x * mA4Div2; + Real w = -(mE[0] + x * mE[1]) / mD[2]; + Real y = w - translate; + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = (rm.second == 1); + } +} + +template +void FIQuery, Ellipse2>::D4ZeroD2Zero( + Result& result) +{ + // e(x) cannot be identically zero, because if it were, then all + // d[i] = 0. But we tested that case previously and exited. + + if (mE[2] != mZero) + { + // Make e(x) monic, f(x) = e(x)/e2 = x^2 + (e1/e2)*x + (e0/e2) + // = x^2 + f1*x + f0. + std::array f = { mE[0] / mE[2], mE[1] / mE[2] }; + + Real mid = -f[1] / mTwo; + Real discr = mid * mid - f[0]; + if (discr > mZero) + { + // The theoretical roots of e(x) are x = -f1/2 + s*sqrt(discr) + // where s in {-1,+1}. For each root, determine exactly the + // sign of c(x). We need c(x) <= 0 in order to solve for + // w^2 = -c(x). At the root, + // c(x) = c0 + c1*x + c2*x^2 = c0 + c1*x - c2*(f0 + f1*x) + // = (c0 - c2*f0) + (c1 - c2*f1)*x + // = g0 + g1*x + // We need g0 + g1*x <= 0. + Real sqrtDiscr = std::sqrt(discr); + std::array g = + { + mC[0] - mC[2] * f[0], + mC[1] - mC[2] * f[1] + }; + + if (g[1] > mZero) + { + // We need s*sqrt(discr) <= -g[0]/g[1] + f1/2. + Real r = -g[0] / g[1] - mid; + + // s = +1: + if (r >= mZero) + { + Real rsqr = r * r; + if (discr < rsqr) + { + SpecialIntersection(mid + sqrtDiscr, true, result); + } + else if (discr == rsqr) + { + SpecialIntersection(mid + sqrtDiscr, false, result); + } + } + + // s = -1: + if (r > mZero) + { + SpecialIntersection(mid - sqrtDiscr, true, result); + } + else + { + Real rsqr = r * r; + if (discr > rsqr) + { + SpecialIntersection(mid - sqrtDiscr, true, result); + } + else if (discr == rsqr) + { + SpecialIntersection(mid - sqrtDiscr, false, result); + } + } + } + else if (g[1] < mZero) + { + // We need s*sqrt(discr) >= -g[0]/g[1] + f1/2. + Real r = -g[0] / g[1] - mid; + + // s = -1: + if (r <= mZero) + { + Real rsqr = r * r; + if (discr < rsqr) + { + SpecialIntersection(mid - sqrtDiscr, true, result); + } + else + { + SpecialIntersection(mid - sqrtDiscr, false, result); + } + } + + // s = +1: + if (r < mZero) + { + SpecialIntersection(mid + sqrtDiscr, true, result); + } + else + { + Real rsqr = r * r; + if (discr > rsqr) + { + SpecialIntersection(mid + sqrtDiscr, true, result); + } + else if (discr == rsqr) + { + SpecialIntersection(mid + sqrtDiscr, false, result); + } + } + } + else // g[1] = 0 + { + // The graphs of c(x) and f(x) are parabolas of the same + // shape. One is a vertical translation of the other. + if (g[0] < mZero) + { + // The graph of f(x) is above that of c(x). + SpecialIntersection(mid - sqrtDiscr, true, result); + SpecialIntersection(mid + sqrtDiscr, true, result); + } + else if (g[0] == mZero) + { + // The graphs of c(x) and f(x) are the same parabola. + SpecialIntersection(mid - sqrtDiscr, false, result); + SpecialIntersection(mid + sqrtDiscr, false, result); + } + } + } + else if (discr == mZero) + { + // The theoretical root of f(x) is x = -f1/2. + Real nchat = -(mC[0] + mid * (mC[1] + mid * mC[2])); + if (nchat > mZero) + { + SpecialIntersection(mid, true, result); + } + else if (nchat == mZero) + { + SpecialIntersection(mid, false, result); + } + } + } + else if (mE[1] != mZero) + { + Real xhat = -mE[0] / mE[1]; + Real nchat = -(mC[0] + xhat * (mC[1] + xhat * mC[2])); + if (nchat > mZero) + { + SpecialIntersection(xhat, true, result); + } + else if (nchat == mZero) + { + SpecialIntersection(xhat, false, result); + } + } +} + +template +void FIQuery, Ellipse2>::SpecialIntersection( + Real const& x, bool transverse, Result& result) +{ + if (transverse) + { + Real translate = mA2Div2 + x * mA4Div2; + Real nc = -(mC[0] + x * (mC[1] + x * mC[2])); + if (nc < mZero) + { + // Clamp to eliminate the rounding error, but duplicate the point + // because we know that it is a transverse intersection. + nc = mZero; + } + + Real w = std::sqrt(nc); + Real y = w - translate; + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = true; + w = -w; + y = w - translate; + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = true; + } + else + { + // The vertical line at the root is tangent to the ellipse. + Real y = -(mA2Div2 + x * mA4Div2); // w = 0 + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = false; + } +} + + + +// FIQuery functions for area of intersection of ellipses. + +template +typename FIQuery, Ellipse2>::AreaResult +FIQuery, Ellipse2>::AreaOfIntersection( + Ellipse2 const& ellipse0, Ellipse2 const& ellipse1) +{ + EllipseInfo E0; + E0.center = ellipse0.center; + E0.axis = ellipse0.axis; + E0.extent = ellipse0.extent; + E0.sqrExtent = ellipse0.extent * ellipse0.extent; + FinishEllipseInfo(E0); + + EllipseInfo E1; + E1.center = ellipse1.center; + E1.axis = ellipse1.axis; + E1.extent = ellipse1.extent; + E1.sqrExtent = ellipse1.extent * ellipse0.extent; + FinishEllipseInfo(E1); + + AreaResult ar; + ar.configuration = 0; + ar.findResult = operator()(ellipse0, ellipse1); + ar.area = mZero; + AreaDispatch(E0, E1, ar); + return ar; +} + +template +typename FIQuery, Ellipse2>::AreaResult +FIQuery, Ellipse2>::AreaOfIntersection( + Vector2 const& center0, Vector2 const axis0[2], + Vector2 const& sqrExtent0, Vector2 const& center1, + Vector2 const axis1[2], Vector2 const& sqrExtent1) +{ + EllipseInfo E0; + E0.center = center0; + E0.axis = { axis0[0], axis0[1] }; + E0.extent = + { + std::sqrt(sqrExtent0[0]), + std::sqrt(sqrExtent0[1]) + }; + E0.sqrExtent = sqrExtent0; + FinishEllipseInfo(E0); + + EllipseInfo E1; + E1.center = center1; + E1.axis = { axis1[0], axis1[1] }; + E1.extent = + { + std::sqrt(sqrExtent1[0]), + std::sqrt(sqrExtent1[1]) + }; + E1.sqrExtent = sqrExtent1; + FinishEllipseInfo(E1); + + AreaResult ar; + ar.configuration = 0; + ar.findResult = operator()(center0, axis0, sqrExtent0, center1, axis1, + sqrExtent1); + ar.area = mZero; + AreaDispatch(E0, E1, ar); + return ar; +} + +template +void FIQuery, Ellipse2>::FinishEllipseInfo( + EllipseInfo& E) +{ + E.M = OuterProduct(E.axis[0], E.axis[0]) / + (E.sqrExtent[0] * Dot(E.axis[0], E.axis[0])); + E.M += OuterProduct(E.axis[1], E.axis[1]) / + (E.sqrExtent[1] * Dot(E.axis[1], E.axis[1])); + E.AB = E.extent[0] * E.extent[1]; + E.halfAB = E.AB / mTwo; + E.BpA = E.extent[1] + E.extent[0]; + E.BmA = E.extent[1] - E.extent[0]; +} + +template +void FIQuery, Ellipse2>::AreaDispatch( + EllipseInfo const& E0, EllipseInfo const& E1, AreaResult& ar) +{ + if (ar.findResult.intersect) + { + if (ar.findResult.numPoints == 1) + { + // Containment or separation. + AreaCS(E0, E1, ar); + } + else if (ar.findResult.numPoints == 2) + { + if (ar.findResult.isTransverse[0]) + { + // Both intersection points are transverse. + Area2(E0, E1, 0, 1, ar); + } + else + { + // Both intersection points are tangential, so one ellipse + // is contained in the other. + AreaCS(E0, E1, ar); + } + } + else if (ar.findResult.numPoints == 3) + { + // The tangential intersection is irrelevant in the area + // computation. + if (!ar.findResult.isTransverse[0]) + { + Area2(E0, E1, 1, 2, ar); + } + else if (!ar.findResult.isTransverse[1]) + { + Area2(E0, E1, 2, 0, ar); + } + else // ar.findResult.isTransverse[2] == false + { + Area2(E0, E1, 0, 1, ar); + } + } + else // ar.findResult.numPoints == 4 + { + Area4(E0, E1, ar); + } + } + else + { + // Containment, separation, or same ellipse. + AreaCS(E0, E1, ar); + } +} + +template +void FIQuery, Ellipse2>::AreaCS( + EllipseInfo const& E0, EllipseInfo const& E1, AreaResult& ar) +{ + if (ar.findResult.numPoints <= 1) + { + Vector2 diff = E0.center - E1.center; + Real qform0 = Dot(diff, E0.M * diff); + Real qform1 = Dot(diff, E1.M * diff); + if (qform0 > mOne && qform1 > mOne) + { + // Each ellipse center is outside the other ellipse, so the + // ellipses are separated (numPoints == 0) or outside each + // other and just touching (numPoints == 1). + ar.configuration = AreaResult::ELLIPSES_ARE_SEPARATED; + ar.area = mZero; + } + else + { + // One ellipse is inside the other. Determine this indirectly by + // comparing areas. + if (E0.AB < E1.AB) + { + ar.configuration = AreaResult::E1_CONTAINS_E0; + ar.area = mPi * E0.AB; + } + else + { + ar.configuration = AreaResult::E0_CONTAINS_E1; + ar.area = mPi * E1.AB; + } + } + } + else + { + ar.configuration = AreaResult::ELLIPSES_ARE_EQUAL; + ar.area = mPi * E0.AB; + } +} + +template +void FIQuery, Ellipse2>::Area2( + EllipseInfo const& E0, EllipseInfo const& E1, int i0, int i1, + AreaResult& ar) +{ + ar.configuration = AreaResult::ONE_CHORD_REGION; + + // The endpoints of the chord. + Vector2 const& P0 = ar.findResult.points[i0]; + Vector2 const& P1 = ar.findResult.points[i1]; + + // Compute locations relative to the ellipses. + Vector2 P0mC0 = P0 - E0.center, P0mC1 = P0 - E1.center; + Vector2 P1mC0 = P1 - E0.center, P1mC1 = P1 - E1.center; + + // Compute the ellipse normal vectors at endpoint P0. This is sufficient + // information to determine chord endpoint order. + Vector2 N0 = E0.M * P0mC0, N1 = E1.M * P0mC1; + Real dotperp = DotPerp(N1, N0); + + // Choose the endpoint order for the chord region associated with E0. + if (dotperp > mZero) + { + // The chord order for E0 is and for E1 is . + ar.area = + ComputeAreaChordRegion(E0, P0mC0, P1mC0) + + ComputeAreaChordRegion(E1, P1mC1, P0mC1); + } + else + { + // The chord order for E0 is and for E1 is . + ar.area = + ComputeAreaChordRegion(E0, P1mC0, P0mC0) + + ComputeAreaChordRegion(E1, P0mC1, P1mC1); + } +} + +template +void FIQuery, Ellipse2>::Area4( + EllipseInfo const& E0, EllipseInfo const& E1, AreaResult& ar) +{ + ar.configuration = AreaResult::FOUR_CHORD_REGION; + + // Select a counterclockwise ordering of the points of intersection. Use + // the polar coordinates for E0 to do this. The multimap is used in the + // event that computing the intersections involved numerical rounding + // errors that lead to a duplicate intersection, even though the + // intersections are all labeled as transverse. [See the comment in the + // function SpecialIntersection.] + std::multimap ordering; + int i; + for (i = 0; i < 4; ++i) + { + Vector2 PmC = ar.findResult.points[i] - E0.center; + Real x = Dot(E0.axis[0], PmC); + Real y = Dot(E0.axis[1], PmC); + Real theta = std::atan2(y, x); + ordering.insert(std::make_pair(theta, i)); + } + + std::array permute; + i = 0; + for (auto const& element : ordering) + { + permute[i++] = element.second; + } + + // Start with the area of the convex quadrilateral. + Vector2 diag20 = + ar.findResult.points[permute[2]] - ar.findResult.points[permute[0]]; + Vector2 diag31 = + ar.findResult.points[permute[3]] - ar.findResult.points[permute[1]]; + ar.area = std::abs(DotPerp(diag20, diag31)) / mTwo; + + // Visit each pair of consecutive points. The selection of ellipse for + // the chord-region area calculation uses the "most counterclockwise" + // tangent vector. + for (int i0 = 3, i1 = 0; i1 < 4; i0 = i1++) + { + // Get a pair of consecutive points. + Vector2 const& P0 = ar.findResult.points[permute[i0]]; + Vector2 const& P1 = ar.findResult.points[permute[i1]]; + + // Compute locations relative to the ellipses. + Vector2 P0mC0 = P0 - E0.center, P0mC1 = P0 - E1.center; + Vector2 P1mC0 = P1 - E0.center, P1mC1 = P1 - E1.center; + + // Compute the ellipse normal vectors at endpoint P0. + Vector2 N0 = E0.M * P0mC0, N1 = E1.M * P0mC1; + Real dotperp = DotPerp(N1, N0); + if (dotperp > mZero) + { + // The chord goes with ellipse E0. + ar.area += ComputeAreaChordRegion(E0, P0mC0, P1mC0); + } + else + { + // The chord goes with ellipse E1. + ar.area += ComputeAreaChordRegion(E1, P0mC1, P1mC1); + } + } +} + +template +Real FIQuery, Ellipse2>::ComputeAreaChordRegion( + EllipseInfo const& E, Vector2 const& P0mC, + Vector2 const& P1mC) +{ + // Compute polar coordinates for P0 and P1 on the ellipse. + Real x0 = Dot(E.axis[0], P0mC); + Real y0 = Dot(E.axis[1], P0mC); + Real theta0 = std::atan2(y0, x0); + Real x1 = Dot(E.axis[0], P1mC); + Real y1 = Dot(E.axis[1], P1mC); + Real theta1 = std::atan2(y1, x1); + + // The arc straddles the atan2 discontinuity on the negative x-axis. Wrap + // the second angle to be larger than the first angle. + if (theta1 < theta0) + { + theta1 += mTwoPi; + } + + // Compute the area portion of the sector due to the triangle. + Real triArea = std::abs(DotPerp(P0mC, P1mC)) / mTwo; + + // Compute the chord region area. + Real dtheta = theta1 - theta0; + Real F0, F1, sectorArea; + if (dtheta <= mPi) + { + // Use the area formula directly. + // area(theta0,theta1) = F(theta1) - F(theta0) - area(triangle) + F0 = ComputeIntegral(E, theta0); + F1 = ComputeIntegral(E, theta1); + sectorArea = F1 - F0; + return sectorArea - triArea; + } + else + { + // The angle of the elliptical sector is larger than pi radians. + // Use the area formula + // area(theta0,theta1) = pi*a*b - area(theta1,theta0) + theta0 += mTwoPi; // ensure theta0 > theta1 + F0 = ComputeIntegral(E, theta0); + F1 = ComputeIntegral(E, theta1); + sectorArea = F0 - F1; + return mPi * E.AB - (sectorArea - triArea); + } +} + +template +Real FIQuery, Ellipse2>::ComputeIntegral( + EllipseInfo const& E, Real const& theta) +{ + Real twoTheta = mTwo * theta; + Real sn = std::sin(twoTheta); + Real cs = std::cos(twoTheta); + Real arg = E.BmA * sn / (E.BpA + E.BmA * cs); + return E.halfAB * (theta - std::atan(arg)); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrEllipsoid3Ellipsoid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrEllipsoid3Ellipsoid3.h new file mode 100644 index 000000000000..4989c8a691ef --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrEllipsoid3Ellipsoid3.h @@ -0,0 +1,544 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Ellipsoid3> +{ +public: + enum + { + ELLIPSOIDS_SEPARATED, + ELLIPSOIDS_INTERSECTING, + ELLIPSOID0_CONTAINS_ELLIPSOID1, + ELLIPSOID1_CONTAINS_ELLIPSOID0 + }; + + struct Result + { + // As solids, the ellipsoids intersect as long as they are not + // separated. + bool intersect; + + // This is one of the four enumerations listed above. + int relationship; + }; + + Result operator()(Ellipsoid3 const& ellipsoid0, + Ellipsoid3 const& ellipsoid1); + +private: + void GetRoots(Real d0, Real c0, int& numRoots, Real* roots); + void GetRoots(Real d0, Real d1, Real c0, Real c1, int& numRoots, + Real* roots); + void GetRoots(Real d0, Real d1, Real d2, Real c0, Real c1, Real c2, + int& numRoots, Real* roots); +}; + + +template +typename TIQuery, Ellipsoid3>::Result +TIQuery, Ellipsoid3>::operator()( + Ellipsoid3 const& ellipsoid0, Ellipsoid3 const& ellipsoid1) +{ + Result result; + + Real const zero = (Real)0; + Real const one = (Real)1; + + // Get the parameters of ellipsoid0. + Vector3 K0 = ellipsoid0.center; + Matrix3x3 R0; + R0.SetCol(0, ellipsoid0.axis[0]); + R0.SetCol(1, ellipsoid0.axis[1]); + R0.SetCol(2, ellipsoid0.axis[2]); + Matrix3x3 D0{ + one / (ellipsoid0.extent[0] * ellipsoid0.extent[0]), zero, zero, + zero, one / (ellipsoid0.extent[1] * ellipsoid0.extent[1]), zero, + zero, zero, one / (ellipsoid0.extent[2] * ellipsoid0.extent[2]) }; + + // Get the parameters of ellipsoid1. + Vector3 K1 = ellipsoid1.center; + Matrix3x3 R1; + R1.SetCol(0, ellipsoid1.axis[0]); + R1.SetCol(1, ellipsoid1.axis[1]); + R1.SetCol(2, ellipsoid1.axis[2]); + Matrix3x3 D1{ + one / (ellipsoid1.extent[0] * ellipsoid1.extent[0]), zero, zero, + zero, one / (ellipsoid1.extent[1] * ellipsoid1.extent[1]), zero, + zero, zero, one / (ellipsoid1.extent[2] * ellipsoid1.extent[2]) }; + + // Compute K2. + Matrix3x3 D0NegHalf{ + ellipsoid0.extent[0], zero, zero, + zero, ellipsoid0.extent[1], zero, + zero, zero, ellipsoid0.extent[2] }; + Matrix3x3 D0Half{ + one / ellipsoid0.extent[0], zero, zero, + zero, one / ellipsoid0.extent[1], zero, + zero, zero, one / ellipsoid0.extent[2] }; + Vector3 K2 = D0Half*((K1 - K0)*R0); + + // Compute M2. + Matrix3x3 R1TR0D0NegHalf = MultiplyATB(R1, R0*D0NegHalf); + Matrix3x3 M2 = MultiplyATB(R1TR0D0NegHalf, D1)*R1TR0D0NegHalf; + + // Factor M2 = R*D*R^T. + SymmetricEigensolver3x3 es; + std::array D; + std::array, 3> evec; + es(M2(0, 0), M2(0, 1), M2(0, 2), M2(1, 1), M2(1, 2), M2(2, 2), false, +1, + D, evec); + Matrix3x3 R; + R.SetCol(0, evec[0]); + R.SetCol(1, evec[1]); + R.SetCol(2, evec[2]); + + // Compute K = R^T*K2. + Vector3 K = K2*R; + + // Transformed ellipsoid0 is Z^T*Z = 1 and transformed ellipsoid1 is + // (Z-K)^T*D*(Z-K) = 0. + + // The minimum and maximum squared distances from the origin of points on + // transformed ellipsoid1 are used to determine whether the ellipsoids + // intersect, are separated, or one contains the other. + Real minSqrDistance = std::numeric_limits::max(); + Real maxSqrDistance = zero; + int i; + + if (K == Vector3::Zero()) + { + // The special case of common centers must be handled separately. It + // is not possible for the ellipsoids to be separated. + for (i = 0; i < 3; ++i) + { + Real invD = one / D[i]; + if (invD < minSqrDistance) + { + minSqrDistance = invD; + } + if (invD > maxSqrDistance) + { + maxSqrDistance = invD; + } + } + + if (maxSqrDistance < one) + { + result.relationship = ELLIPSOID0_CONTAINS_ELLIPSOID1; + } + else if (minSqrDistance >(Real)1) + { + result.relationship = ELLIPSOID1_CONTAINS_ELLIPSOID0; + } + else + { + result.relationship = ELLIPSOIDS_INTERSECTING; + } + result.intersect = true; + return result; + } + + // The closest point P0 and farthest point P1 are solutions to + // s0*D*(P0 - K) = P0 and s1*D*(P1 - K) = P1 for some scalars s0 and s1 + // that are roots to the function + // f(s) = d0*k0^2/(d0*s-1)^2+d1*k1^2/(d1*s-1)^2+d2*k2^2/(d2*s-1)^2-1 + // where D = diagonal(d0,d1,d2) and K = (k0,k1,k2). + Real d0 = D[0], d1 = D[1], d2 = D[2]; + Real c0 = K[0] * K[0], c1 = K[1] * K[1], c2 = K[2] * K[2]; + + // Sort the values so that d0 >= d1 >= d2. This allows us to bound the + // roots of f(s), of which there are at most 6. + std::vector> param(3); + param[0] = std::make_pair(d0, c0); + param[1] = std::make_pair(d1, c1); + param[2] = std::make_pair(d2, c2); + std::sort(param.begin(), param.end(), + std::greater>()); + + std::vector> valid; + valid.reserve(3); + if (param[0].first > param[1].first) + { + if (param[1].first > param[2].first) + { + // d0 > d1 > d2 + for (i = 0; i < 3; ++i) + { + if (param[i].second >(Real)0) + { + valid.push_back(param[i]); + } + } + } + else + { + // d0 > d1 = d2 + if (param[0].second > (Real)0) + { + valid.push_back(param[0]); + } + param[1].second += param[0].second; + if (param[1].second > (Real)0) + { + valid.push_back(param[1]); + } + } + } + else + { + if (param[1].first > param[2].first) + { + // d0 = d1 > d2 + param[0].second += param[1].second; + if (param[0].second > (Real)0) + { + valid.push_back(param[0]); + } + if (param[2].second > (Real)0) + { + valid.push_back(param[2]); + } + } + else + { + // d0 = d1 = d2 + param[0].second += param[1].second + param[2].second; + if (param[0].second > (Real)0) + { + valid.push_back(param[0]); + } + } + } + + size_t numValid = valid.size(); + int numRoots; + Real roots[6]; + if (numValid == 3) + { + GetRoots(valid[0].first, valid[1].first, valid[2].first, + valid[0].second, valid[1].second, valid[2].second, numRoots, + roots); + } + else if (numValid == 2) + { + GetRoots(valid[0].first, valid[1].first, valid[0].second, + valid[1].second, numRoots, roots); + } + else if (numValid == 1) + { + GetRoots(valid[0].first, valid[0].second, numRoots, roots); + } + else + { + // numValid cannot be zero because we already handled case K = 0 + LogError("Unexpected condition."); + result.intersect = true; + result.relationship = ELLIPSOIDS_INTERSECTING; + return result; + } + + for (i = 0; i < numRoots; ++i) + { + Real s = roots[i]; + Real p0 = d0*K[0] * s / (d0 * s - (Real)1); + Real p1 = d1*K[1] * s / (d1 * s - (Real)1); + Real p2 = d2*K[2] * s / (d2 * s - (Real)1); + Real sqrDistance = p0 * p0 + p1 * p1 + p2 * p2; + if (sqrDistance < minSqrDistance) + { + minSqrDistance = sqrDistance; + } + if (sqrDistance > maxSqrDistance) + { + maxSqrDistance = sqrDistance; + } + } + + if (maxSqrDistance < one) + { + result.intersect = true; + result.relationship = ELLIPSOID0_CONTAINS_ELLIPSOID1; + } + else if (minSqrDistance >(Real)1) + { + if (d0 * c0 + d1 * c1 + d2 * c2 > one) + { + result.intersect = false; + result.relationship = ELLIPSOIDS_SEPARATED; + } + else + { + result.intersect = true; + result.relationship = ELLIPSOID1_CONTAINS_ELLIPSOID0; + } + } + else + { + result.intersect = true; + result.relationship = ELLIPSOIDS_INTERSECTING; + } + + return result; +} + +template +void TIQuery, Ellipsoid3>::GetRoots(Real d0, + Real c0, int& numRoots, Real* roots) +{ + // f(s) = d0*c0/(d0*s-1)^2 - 1 + Real const one = (Real)1; + Real temp = std::sqrt(d0*c0); + Real inv = one / d0; + numRoots = 2; + roots[0] = (one - temp) * inv; + roots[1] = (one + temp) * inv; +} + +template +void TIQuery, Ellipsoid3>::GetRoots(Real d0, + Real d1, Real c0, Real c1, int& numRoots, Real* roots) +{ + // f(s) = d0*c0/(d0*s-1)^2 + d1*c1/(d1*s-1)^2 - 1 + // with d0 > d1 + + Real const zero = (Real)0; + Real const one = (Real)1; + Real const two = (Real)2; + Real d0c0 = d0 * c0; + Real d1c1 = d1 * c1; + + std::function F = [&one, d0, d1, d0c0, d1c1](Real s) + { + Real invN0 = one / (d0*s - one); + Real invN1 = one / (d1*s - one); + Real term0 = d0c0 * invN0 * invN0; + Real term1 = d1c1 * invN1 * invN1; + Real f = term0 + term1 - one; + return f; + }; + + std::function DF = [&one, &two, d0, d1, d0c0, d1c1](Real s) + { + Real invN0 = one / (d0*s - one); + Real invN1 = one / (d1*s - one); + Real term0 = d0 * d0c0 * invN0 * invN0 * invN0; + Real term1 = d1 * d1c1 * invN1 * invN1 * invN1; + Real df = -two * (term0 + term1); + return df; + }; + + unsigned int const maxIterations = 1024; + unsigned int iterations; + numRoots = 0; + + // TODO: What role does epsilon play? + Real const epsilon = (Real)0.001; + Real multiplier0 = std::sqrt(two / (one - epsilon)); + Real multiplier1 = std::sqrt(one / (one + epsilon)); + Real sqrtd0c0 = std::sqrt(d0c0); + Real sqrtd1c1 = std::sqrt(d1c1); + Real invD0 = one / d0; + Real invD1 = one / d1; + Real temp0, temp1, smin, smax, s; + + // Compute root in (-infinity,1/d0). + temp0 = (one - multiplier0 * sqrtd0c0) * invD0; + temp1 = (one - multiplier0 * sqrtd1c1) * invD1; + smin = std::min(temp0, temp1); + LogAssert(F(smin) < zero, "Unexpected condition."); + smax = (one - multiplier1*sqrtd0c0)*invD0; + LogAssert(F(smax) > zero, "Unexpected condition."); + iterations = RootsBisection::Find(F, smin, smax, maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + + // Compute roots (if any) in (1/d0,1/d1). It is the case that + // F(1/d0) = +infinity, F'(1/d0) = -infinity + // F(1/d1) = +infinity, F'(1/d1) = +infinity + // F"(s) > 0 for all s in the domain of F + // Compute the unique root r of F'(s) on (1/d0,1/d1). The bisector needs + // only the signs at the endpoints, so we pass -1 and +1 instead of the + // infinite values. If F(r) < 0, F(s) has two roots in the interval. + // If F(r) = 0, F(s) has only one root in the interval. + Real smid; + iterations = RootsBisection::Find(DF, invD0, invD1, -one, one, + maxIterations, smid); + LogAssert(iterations > 0, "Unexpected condition."); + if (F(smid) < zero) + { + // Pass in signs rather than infinities, because the bisector cares + // only about the signs. + iterations = RootsBisection::Find(F, invD0, smid, one, -one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + iterations = RootsBisection::Find(F, smid, invD1, -one, one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + } + + // Compute root in (1/d1,+infinity). + temp0 = (one + multiplier0 * sqrtd0c0) * invD0; + temp1 = (one + multiplier0 * sqrtd1c1) * invD1; + smax = std::max(temp0, temp1); + LogAssert(F(smax) < zero, "Unexpected condition."); + smin = (one + multiplier1 * sqrtd1c1) * invD1; + LogAssert(F(smin) > zero, "Unexpected condition."); + iterations = RootsBisection::Find(F, smin, smax, maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; +} + +template +void TIQuery, Ellipsoid3>::GetRoots(Real d0, + Real d1, Real d2, Real c0, Real c1, Real c2, int& numRoots, Real* roots) +{ + // f(s) = d0*c0/(d0*s-1)^2 + d1*c1/(d1*s-1)^2 + d2*c2/(d2*s-1)^2 - 1 + // with d0 > d1 > d2 + + Real const zero = (Real)0; + Real const one = (Real)1; + Real const three = (Real)3; + Real d0c0 = d0 * c0; + Real d1c1 = d1 * c1; + Real d2c2 = d2 * c2; + + std::function F = [&one, d0, d1, d2, d0c0, d1c1, d2c2](Real s) + { + Real invN0 = one / (d0*s - one); + Real invN1 = one / (d1*s - one); + Real invN2 = one / (d2*s - one); + Real term0 = d0c0 * invN0 * invN0; + Real term1 = d1c1 * invN1 * invN1; + Real term2 = d2c2 * invN2 * invN2; + Real f = term0 + term1 + term2 - one; + return f; + }; + + std::function DF = [&one, d0, d1, d2, d0c0, d1c1, d2c2](Real s) + { + Real const two = (Real)2; + Real invN0 = one / (d0*s - one); + Real invN1 = one / (d1*s - one); + Real invN2 = one / (d2*s - one); + Real term0 = d0 * d0c0 * invN0 * invN0 * invN0; + Real term1 = d1 * d1c1 * invN1 * invN1 * invN1; + Real term2 = d2 * d2c2 * invN2 * invN2 * invN2; + Real df = -two * (term0 + term1 + term2); + return df; + }; + + unsigned int const maxIterations = 1024; + unsigned int iterations; + numRoots = 0; + + // TODO: What role does epsilon play? + Real epsilon = (Real)0.001; + Real multiplier0 = std::sqrt(three / (one - epsilon)); + Real multiplier1 = std::sqrt(one / (one + epsilon)); + Real sqrtd0c0 = std::sqrt(d0c0); + Real sqrtd1c1 = std::sqrt(d1c1); + Real sqrtd2c2 = std::sqrt(d2c2); + Real invD0 = one / d0; + Real invD1 = one / d1; + Real invD2 = one / d2; + Real temp0, temp1, temp2, smin, smax, s; + + // Compute root in (-infinity,1/d0). + temp0 = (one - multiplier0*sqrtd0c0)*invD0; + temp1 = (one - multiplier0*sqrtd1c1)*invD1; + temp2 = (one - multiplier0*sqrtd2c2)*invD2; + smin = std::min(std::min(temp0, temp1), temp2); + LogAssert(F(smin) < zero, "Unexpected condition."); + smax = (one - multiplier1*sqrtd0c0)*invD0; + LogAssert(F(smax) > zero, "Unexpected condition."); + iterations = RootsBisection::Find(F, smin, smax, maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + + // Compute roots (if any) in (1/d0,1/d1). It is the case that + // F(1/d0) = +infinity, F'(1/d0) = -infinity + // F(1/d1) = +infinity, F'(1/d1) = +infinity + // F"(s) > 0 for all s in the domain of F + // Compute the unique root r of F'(s) on (1/d0,1/d1). The bisector needs + // only the signs at the endpoints, so we pass -1 and +1 instead of the + // infinite values. If F(r) < 0, F(s) has two roots in the interval. + // If F(r) = 0, F(s) has only one root in the interval. + Real smid; + iterations = RootsBisection::Find(DF, invD0, invD1, -one, one, + maxIterations, smid); + LogAssert(iterations > 0, "Unexpected condition."); + if (F(smid) < zero) + { + // Pass in signs rather than infinities, because the bisector cares + // only about the signs. + iterations = RootsBisection::Find(F, invD0, smid, one, -one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + iterations = RootsBisection::Find(F, smid, invD1, -one, one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + } + + // Compute roots (if any) in (1/d1,1/d2). It is the case that + // F(1/d1) = +infinity, F'(1/d1) = -infinity + // F(1/d2) = +infinity, F'(1/d2) = +infinity + // F"(s) > 0 for all s in the domain of F + // Compute the unique root r of F'(s) on (1/d1,1/d2). The bisector needs + // only the signs at the endpoints, so we pass -1 and +1 instead of the + // infinite values. If F(r) < 0, F(s) has two roots in the interval. + // If F(r) = 0, F(s) has only one root in the interval. + iterations = RootsBisection::Find(DF, invD1, invD2, -one, one, + maxIterations, smid); + LogAssert(iterations > 0, "Unexpected condition."); + if (F(smid) < zero) + { + // Pass in signs rather than infinities, because the bisector cares + // only about the signs. + iterations = RootsBisection::Find(F, invD1, smid, one, -one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + iterations = RootsBisection::Find(F, smid, invD2, -one, one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + } + + // Compute root in (1/d2,+infinity). + temp0 = (one + multiplier0*sqrtd0c0)*invD0; + temp1 = (one + multiplier0*sqrtd1c1)*invD1; + temp2 = (one + multiplier0*sqrtd2c2)*invD2; + smax = std::max(std::max(temp0, temp1), temp2); + LogAssert(F(smax) < zero, "Unexpected condition."); + smin = (one + multiplier1*sqrtd2c2)*invD2; + LogAssert(F(smin) > zero, "Unexpected condition."); + iterations = RootsBisection::Find(F, smin, smax, maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace2Polygon2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace2Polygon2.h new file mode 100644 index 000000000000..af56d39cf7af --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace2Polygon2.h @@ -0,0 +1,167 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the box to be a solid and the polygon to be a +// convex solid. + +namespace gte +{ + +template +class FIQuery, std::vector>> +{ +public: + struct Result + { + bool intersect; + + // If 'intersect' is true, the halfspace and polygon intersect in a + // convex polygon. + std::vector> polygon; + }; + + Result operator()(Halfspace<2, Real> const& halfspace, + std::vector> const& polygon); +}; + + +template +typename FIQuery, std::vector>>::Result +FIQuery, std::vector>>::operator()( + Halfspace<2, Real> const& halfspace, std::vector> const& polygon) +{ + Result result; + + // Determine whether the polygon vertices are outside the halfspace, + // inside the halfspace, or on the halfspace boundary. + int const numVertices = static_cast(polygon.size()); + std::vector distance(numVertices); + int positive = 0, negative = 0, positiveIndex = -1; + for (int i = 0; i < numVertices; ++i) + { + distance[i] = Dot(halfspace.normal, polygon[i]) - halfspace.constant; + if (distance[i] > (Real)0) + { + ++positive; + if (positiveIndex == -1) + { + positiveIndex = i; + } + } + else if (distance[i] < (Real)0) + { + ++negative; + } + } + + if (positive == 0) + { + // The polygon is strictly outside the halfspace. + result.intersect = false; + return result; + } + + if (negative == 0) + { + // The polygon is contained in the closed halfspace, so it is + // fully visible and no clipping is necessary. + result.intersect = true; + return result; + } + + // The line transversely intersects the polygon. Clip the polygon. + Vector2 vertex; + int curr, prev; + Real t; + + if (positiveIndex > 0) + { + // Compute the first clip vertex on the line. + curr = positiveIndex; + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + result.polygon.push_back(vertex); + + // Include the vertices on the positive side of line. + while (curr < numVertices && distance[curr] >(Real)0) + { + result.polygon.push_back(polygon[curr++]); + } + + // Compute the kast clip vertex on the line. + if (curr < numVertices) + { + prev = curr - 1; + } + else + { + curr = 0; + prev = numVertices - 1; + } + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + result.polygon.push_back(vertex); + } + else // positiveIndex is 0 + { + // Include the vertices on the positive side of line. + curr = 0; + while (curr < numVertices && distance[curr] >(Real)0) + { + result.polygon.push_back(polygon[curr++]); + } + + // Compute the last clip vertex on the line. + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + result.polygon.push_back(vertex); + + // Skip the vertices on the negative side of the line. + while (curr < numVertices && distance[curr] <= (Real)0) + { + curr++; + } + + // Compute the first clip vertex on the line. + if (curr < numVertices) + { + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + result.polygon.push_back(vertex); + + // Keep the vertices on the positive side of the line. + while (curr < numVertices && distance[curr] >(Real)0) + { + result.polygon.push_back(polygon[curr++]); + } + } + else + { + curr = 0; + prev = numVertices - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + result.polygon.push_back(vertex); + } + } + + result.intersect = true; + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Capsule3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Capsule3.h new file mode 100644 index 000000000000..309405795f2c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Capsule3.h @@ -0,0 +1,56 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace gte +{ + +template +class TIQuery, Capsule3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, + Capsule3 const& capsule); +}; + + +template +typename TIQuery, Capsule3>::Result +TIQuery, Capsule3>::operator()( + Halfspace3 const& halfspace, Capsule3 const& capsule) +{ + Result result; + + // Project the capsule onto the normal line. The plane of the halfspace + // occurs at the origin (zero) of the normal line. + Real e0 = + Dot(halfspace.normal, capsule.segment.p[0]) - halfspace.constant; + Real e1 = + Dot(halfspace.normal, capsule.segment.p[1]) - halfspace.constant; + + // The capsule and halfspace intersect when the projection interval + // maximum is nonnegative. + result.intersect = (std::max(e0, e1) + capsule.radius >= (Real)0); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Cylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Cylinder3.h new file mode 100644 index 000000000000..dfcec0edd090 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Cylinder3.h @@ -0,0 +1,60 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace gte +{ + +template +class TIQuery, Cylinder3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, + Cylinder3 const& cylinder); +}; + + +template +typename TIQuery, Cylinder3>::Result +TIQuery, Cylinder3>::operator()( + Halfspace3 const& halfspace, Cylinder3 const& cylinder) +{ + Result result; + + // Compute extremes of signed distance Dot(N,X)-d for points on the + // cylinder. These are + // min = (Dot(N,C)-d) - r*sqrt(1-Dot(N,W)^2) - (h/2)*|Dot(N,W)| + // max = (Dot(N,C)-d) + r*sqrt(1-Dot(N,W)^2) + (h/2)*|Dot(N,W)| + Real center = Dot(halfspace.normal, cylinder.axis.origin) - + halfspace.constant; + Real absNdW = std::abs(Dot(halfspace.normal, cylinder.axis.direction)); + Real root = std::sqrt(std::max((Real)1, (Real)1 - absNdW * absNdW)); + Real tmax = center + cylinder.radius*root + + ((Real)0.5)*cylinder.height*absNdW; + + // The cylinder and halfspace intersect when the projection interval + // maximum is nonnegative. + result.intersect = (tmax >= (Real)0); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Ellipsoid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Ellipsoid3.h new file mode 100644 index 000000000000..50ef09f754b1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Ellipsoid3.h @@ -0,0 +1,58 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace gte +{ + +template +class TIQuery, Ellipsoid3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, + Ellipsoid3 const& ellipsoid); +}; + + +template +typename TIQuery, Ellipsoid3>::Result +TIQuery, Ellipsoid3>::operator()( + Halfspace3 const& halfspace, Ellipsoid3 const& ellipsoid) +{ + // Project the ellipsoid onto the normal line. The plane of the + // halfspace occurs at the origin (zero) of the normal line. + Result result; + Matrix3x3 MInverse; + ellipsoid.GetMInverse(MInverse); + Real discr = Dot(halfspace.normal, MInverse * halfspace.normal); + Real extent = std::sqrt(std::max(discr, (Real)0)); + Real center = Dot(halfspace.normal, ellipsoid.center) - halfspace.constant; + Real tmax = center + extent; + + // The ellipsoid and halfspace intersect when the projection interval + // maximum is nonnegative. + result.intersect = (tmax >= (Real)0); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3OrientedBox3.h new file mode 100644 index 000000000000..feae37b4eb07 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3OrientedBox3.h @@ -0,0 +1,59 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace gte +{ + +template +class TIQuery, OrientedBox3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, + OrientedBox3 const& box); +}; + + +template +typename TIQuery, OrientedBox3>::Result +TIQuery, OrientedBox3>::operator()( + Halfspace3 const& halfspace, OrientedBox3 const& box) +{ + Result result; + + // Project the box center onto the normal line. The plane of the + // halfspace occurs at the origin (zero) of the normal line. + Real center = Dot(halfspace.normal, box.center) - halfspace.constant; + + // Compute the radius of the interval of projection. + Real radius = + std::abs(box.extent[0] * Dot(halfspace.normal, box.axis[0])) + + std::abs(box.extent[1] * Dot(halfspace.normal, box.axis[1])) + + std::abs(box.extent[2] * Dot(halfspace.normal, box.axis[2])); + + // The box and halfspace intersect when the projection interval maximum + // is nonnegative. + result.intersect = (center + radius >= (Real)0); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Segment3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Segment3.h new file mode 100644 index 000000000000..c8c725032a0b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Segment3.h @@ -0,0 +1,157 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace gte +{ + +template +class TIQuery, Segment3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, + Segment3 const& segment); +}; + +template +class FIQuery, Segment3> +{ +public: + struct Result + { + bool intersect; + + // The segment is clipped against the plane defining the halfspace. + // The 'numPoints' is either 0 (no intersection), 1 (point), or 2 + // (segment). + int numPoints; + Vector3 point[2]; + }; + + Result operator()(Halfspace3 const& halfspace, + Segment3 const& segment); +}; + + +template +typename TIQuery, Segment3>::Result +TIQuery, Segment3>::operator()( + Halfspace3 const& halfspace, Segment3 const& segment) +{ + Result result; + + // Project the segment endpoints onto the normal line. The plane of the + // halfspace occurs at the origin (zero) of the normal line. + Real s[2]; + for (int i = 0; i < 2; ++i) + { + s[i] = Dot(halfspace.normal, segment.p[i]) - halfspace.constant; + } + + // The segment and halfspace intersect when the projection interval + // maximum is nonnegative. + result.intersect = (std::max(s[0], s[1]) >= (Real)0); + return result; +} + +template +typename FIQuery, Segment3>::Result +FIQuery, Segment3>::operator()( + Halfspace3 const& halfspace, Segment3 const& segment) +{ + // Determine on which side of the plane the endpoints lie. The table of + // possibilities is listed next with n = numNegative, p = numPositive, and + // z = numZero. + // + // n p z intersection + // ------------------------- + // 0 2 0 segment (original) + // 0 1 1 segment (original) + // 0 0 2 segment (original) + // 1 1 0 segment (clipped) + // 1 0 1 point (endpoint) + // 2 0 0 none + + Real s[3]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 2; ++i) + { + s[i] = Dot(halfspace.normal, segment.p[i]) - halfspace.constant; + if (s[i] >(Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + Result result; + + if (numNegative == 0) + { + // The segment is in the halfspace. + result.intersect = true; + result.numPoints = 2; + result.point[0] = segment.p[0]; + result.point[1] = segment.p[1]; + } + else if (numNegative == 1) + { + result.intersect = true; + result.numPoints = 1; + if (numPositive == 1) + { + // The segment is intersected at an interior point. + result.point[0] = segment.p[0] + + (s[0] / (s[0] - s[1]))*(segment.p[1] - segment.p[0]); + } + else // numZero = 1 + { + // One segment endpoint is on the plane. + if (s[0] == (Real)0) + { + result.point[0] = segment.p[0]; + } + else + { + result.point[0] = segment.p[1]; + } + } + } + else // numNegative == 2 + { + // The segment is outside the halfspace. (numNegative == 2) + result.intersect = false; + result.numPoints = 0; + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Sphere3.h new file mode 100644 index 000000000000..7194389a096f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Sphere3.h @@ -0,0 +1,53 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace gte +{ + +template +class TIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, + Sphere3 const& sphere); +}; + + +template +typename TIQuery, Sphere3>::Result +TIQuery, Sphere3>::operator()( + Halfspace3 const& halfspace, Sphere3 const& sphere) +{ + Result result; + + // Project the sphere center onto the normal line. The plane of the + // halfspace occurs at the origin (zero) of the normal line. + Real center = Dot(halfspace.normal, sphere.center) - halfspace.constant; + + // The sphere and halfspace intersect when the projection interval + // maximum is nonnegative. + result.intersect = (center + sphere.radius >= (Real)0); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Triangle3.h new file mode 100644 index 000000000000..74d613ffcc6f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Triangle3.h @@ -0,0 +1,242 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace gte +{ + +template +class TIQuery, Triangle3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, + Triangle3 const& triangle); +}; + +template +class FIQuery, Triangle3> +{ +public: + struct Result + { + bool intersect; + + // The triangle is clipped against the plane defining the halfspace. + // The 'numPoints' is either 0 (no intersection), 1 (point), 2 + // (segment), 3 (triangle), or 4 (quadrilateral). + int numPoints; + Vector3 point[4]; + }; + + Result operator()(Halfspace3 const& halfspace, + Triangle3 const& triangle); +}; + + +template +typename TIQuery, Triangle3>::Result +TIQuery, Triangle3>::operator()( + Halfspace3 const& halfspace, Triangle3 const& triangle) +{ + Result result; + + // Project the triangle vertices onto the normal line. The plane of the + // halfspace occurs at the origin (zero) of the normal line. + Real s[3]; + for (int i = 0; i < 3; ++i) + { + s[i] = Dot(halfspace.normal, triangle.v[i]) - halfspace.constant; + } + + // The triangle and halfspace intersect when the projection interval + // maximum is nonnegative. + result.intersect = (std::max(std::max(s[0], s[1]), s[2]) >= (Real)0); + return result; +} + +template +typename FIQuery, Triangle3>::Result +FIQuery, Triangle3>::operator()( + Halfspace3 const& halfspace, Triangle3 const& triangle) +{ + Result result; + + // Determine on which side of the plane the vertices lie. The table of + // possibilities is listed next with n = numNegative, p = numPositive, and + // z = numZero. + // + // n p z intersection + // --------------------------------- + // 0 3 0 triangle (original) + // 0 2 1 triangle (original) + // 0 1 2 triangle (original) + // 0 0 3 triangle (original) + // 1 2 0 quad (2 edges clipped) + // 1 1 1 triangle (1 edge clipped) + // 1 0 2 edge + // 2 1 0 triangle (2 edges clipped) + // 2 0 1 vertex + // 3 0 0 none + + Real s[3]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 3; ++i) + { + s[i] = Dot(halfspace.normal, triangle.v[i]) - halfspace.constant; + if (s[i] >(Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + if (numNegative == 0) + { + // The triangle is in the halfspace. + result.intersect = true; + result.numPoints = 3; + result.point[0] = triangle.v[0]; + result.point[1] = triangle.v[1]; + result.point[2] = triangle.v[2]; + } + else if (numNegative == 1) + { + result.intersect = true; + if (numPositive == 2) + { + // The portion of the triangle in the halfspace is a + // quadrilateral. + result.numPoints = 4; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] < (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.point[0] = triangle.v[i1]; + result.point[1] = triangle.v[i2]; + Real t2 = s[i2] / (s[i2] - s[i0]); + Real t0 = s[i0] / (s[i0] - s[i1]); + result.point[2] = triangle.v[i2] + t2 * + (triangle.v[i0] - triangle.v[i2]); + result.point[3] = triangle.v[i0] + t0 * + (triangle.v[i1] - triangle.v[i0]); + break; + } + } + } + else if (numPositive == 1) + { + // The portion of the triangle in the halfspace is a triangle + // with one vertex on the plane. + result.numPoints = 3; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] == (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.point[0] = triangle.v[i0]; + Real t1 = s[i1] / (s[i1] - s[i2]); + Vector3 p = triangle.v[i1] + t1 * + (triangle.v[i2] - triangle.v[i1]); + if (s[i1] >(Real)0) + { + result.point[1] = triangle.v[i1]; + result.point[2] = p; + } + else + { + result.point[1] = p; + result.point[2] = triangle.v[i2]; + } + break; + } + } + } + else + { + // Only an edge of the triangle is in the halfspace. + result.numPoints = 0; + for (int i = 0; i < 3; ++i) + { + if (s[i] == (Real)0) + { + result.point[result.numPoints++] = triangle.v[i]; + } + } + } + } + else if (numNegative == 2) + { + result.intersect = true; + if (numPositive == 1) + { + // The portion of the triangle in the halfspace is a triangle. + result.numPoints = 3; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] >(Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.point[0] = triangle.v[i0]; + Real t0 = s[i0] / (s[i0] - s[i1]); + Real t2 = s[i2] / (s[i2] - s[i0]); + result.point[1] = triangle.v[i0] + t0 * + (triangle.v[i1] - triangle.v[i0]); + result.point[2] = triangle.v[i2] + t2 * + (triangle.v[i0] - triangle.v[i2]); + break; + } + } + } + else + { + // Only a vertex of the triangle is in the halfspace. + result.numPoints = 1; + for (int i = 0; i < 3; ++i) + { + if (s[i] == (Real)0) + { + result.point[0] = triangle.v[i]; + break; + } + } + } + } + else // numNegative == 3 + { + // The triangle is outside the halfspace. (numNegative == 3) + result.intersect = false; + result.numPoints = 0; + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrIntervals.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrIntervals.h new file mode 100644 index 000000000000..a51791acf415 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrIntervals.h @@ -0,0 +1,331 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +// The intervals are [u0,u1] and [v0,v1], where u0 <= u1 and v0 <= v1, and +// where the endpoints are any finite floating-point numbers. Degenerate +// intervals are allowed (u0 = u1 or v0 = v1). The queries do not perform +// validation on the input intervals. In the comments, maxReal refers to +// std::numeric_limits::max(). + +template +class TIQuery, std::array> +{ +public: + // The query tests overlap, whether a single point or an entire interval. + struct Result + { + bool intersect; + + // Dynamic queries (intervals moving with constant speeds). If + // 'intersect' is true, the contact times are valid and + // 0 <= firstTime <= lastTime, firstTime <= maxTime + // If 'intersect' is false, there are two cases reported. If the + // intervals will intersect at firstTime > maxTime, the contact times + // are reported just as when 'intersect' is true. However, if the + // intervals will not intersect, then firstTime = maxReal and + // lastTime = -maxReal. + Real firstTime, lastTime; + }; + + // Static query. + Result operator()(std::array const& interval0, + std::array const& interval1); + + // Dynamic query. Current time is 0, maxTime > 0 is required. + Result operator()(Real maxTime, std::array const& interval0, + Real speed0, std::array const& interval1, Real speed1); +}; + +template +class FIQuery, std::array> +{ +public: + // The query finds overlap, whether a single point or an entire interval. + struct Result + { + bool intersect; + + // Static queries (no motion of intervals over time). The number of + // number of intersections is 0 (no overlap), 1 (intervals are just + // touching), or 2 (intervals overlap in an interval). If 'intersect' + // is false, numIntersections is 0 and 'overlap' is set to + // [maxReal,-maxReal]. If 'intersect' is true, numIntersections is + // 1 or 2. When 1, 'overlap' is set to [x,x], which is degenerate and + // represents the single intersection point x. When 2, 'overlap' is + // the interval of intersection. + int numIntersections; + std::array overlap; + + // Dynamic queries (intervals moving with constant speeds). If + // 'intersect' is true, the contact times are valid and + // 0 <= firstTime <= lastTime, firstTime <= maxTime + // If 'intersect' is false, there are two cases reported. If the + // intervals will intersect at firstTime > maxTime, the contact times + // are reported just as when 'intersect' is true. However, if the + // intervals will not intersect, then firstTime = maxReal and + // lastTime = -maxReal. + Real firstTime, lastTime; + }; + + // Static query. + Result operator()(std::array const& interval0, + std::array const& interval1); + + // Dynamic query. Current time is 0, maxTime > 0 is required. + Result operator()(Real maxTime, std::array const& interval0, + Real speed0, std::array const& interval1, Real speed1); +}; + +// Template aliases for convenience. +template +using TIIntervalInterval = +TIQuery, std::array>; + +template +using FIIntervalInterval = +FIQuery, std::array>; + + +template +typename TIQuery, std::array>::Result +TIQuery, std::array>::operator()( + std::array const& interval0, + std::array const& interval1) +{ + Result result; + result.intersect = + interval0[0] <= interval1[1] && interval0[1] >= interval1[0]; + return result; +} + +template +typename TIQuery, std::array>::Result +TIQuery, std::array>::operator()( + Real maxTime, std::array const& interval0, Real speed0, + std::array const& interval1, Real speed1) +{ + Result result; + + if (interval0[1] < interval1[0]) + { + // interval0 initially to the left of interval1. + Real diffSpeed = speed0 - speed1; + if (diffSpeed > (Real)0) + { + // The intervals must move towards each other. 'intersect' is + // true when the intervals will intersect by maxTime. + Real diffPos = interval1[0] - interval0[1]; + Real invDiffSpeed = ((Real)1) / diffSpeed; + result.intersect = (diffPos <= maxTime*diffSpeed); + result.firstTime = diffPos*invDiffSpeed; + result.lastTime = (interval1[1] - interval0[0])*invDiffSpeed; + return result; + } + } + else if (interval0[0] > interval1[1]) + { + // interval0 initially to the right of interval1. + Real diffSpeed = speed1 - speed0; + if (diffSpeed > (Real)0) + { + // The intervals must move towards each other. 'intersect' is + // true when the intervals will intersect by maxTime. + Real diffPos = interval0[0] - interval1[1]; + Real invDiffSpeed = ((Real)1) / diffSpeed; + result.intersect = (diffPos <= maxTime*diffSpeed); + result.firstTime = diffPos*invDiffSpeed; + result.lastTime = (interval0[1] - interval1[0])*invDiffSpeed; + return result; + } + } + else + { + // The intervals are initially intersecting. + result.intersect = true; + result.firstTime = (Real)0; + if (speed1 > speed0) + { + result.lastTime = (interval0[1] - interval1[0])/(speed1 - speed0); + } + else if (speed1 < speed0) + { + result.lastTime = (interval1[1] - interval0[0])/(speed0 - speed1); + } + else + { + result.lastTime = std::numeric_limits::max(); + } + return result; + } + + result.intersect = false; + result.firstTime = std::numeric_limits::max(); + result.lastTime = -std::numeric_limits::max(); + return result; +} + + + +template +typename FIQuery, std::array>::Result +FIQuery, std::array>::operator()( + std::array const& interval0, + std::array const& interval1) +{ + Result result; + result.firstTime = std::numeric_limits::max(); + result.lastTime = -std::numeric_limits::max(); + + if (interval0[1] < interval1[0] || interval0[0] > interval1[1]) + { + result.numIntersections = 0; + result.overlap[0] = std::numeric_limits::max(); + result.overlap[1] = -std::numeric_limits::max(); + } + else if (interval0[1] > interval1[0]) + { + if (interval0[0] < interval1[1]) + { + result.numIntersections = 2; + result.overlap[0] = + (interval0[0] < interval1[0] ? interval1[0] : interval0[0]); + result.overlap[1] = + (interval0[1] > interval1[1] ? interval1[1] : interval0[1]); + if (result.overlap[0] == result.overlap[1]) + { + result.numIntersections = 1; + } + } + else // interval0[0] == interval1[1] + { + result.numIntersections = 1; + result.overlap[0] = interval0[0]; + result.overlap[1] = result.overlap[0]; + } + } + else // interval0[1] == interval1[0] + { + result.numIntersections = 1; + result.overlap[0] = interval0[1]; + result.overlap[1] = result.overlap[0]; + } + + result.intersect = (result.numIntersections > 0); + return result; +} + +template +typename FIQuery, std::array>::Result +FIQuery, std::array>::operator()( + Real maxTime, std::array const& interval0, Real speed0, + std::array const& interval1, Real speed1) +{ + Result result; + + if (interval0[1] < interval1[0]) + { + // interval0 initially to the left of interval1. + Real diffSpeed = speed0 - speed1; + if (diffSpeed > (Real)0) + { + // The intervals must move towards each other. 'intersect' is + // true when the intervals will intersect by maxTime. + Real diffPos = interval1[0] - interval0[1]; + Real invDiffSpeed = ((Real)1) / diffSpeed; + result.intersect = (diffPos <= maxTime*diffSpeed); + result.numIntersections = 1; + result.firstTime = diffPos*invDiffSpeed; + result.lastTime = (interval1[1] - interval0[0])*invDiffSpeed; + result.overlap[0] = interval0[0] + result.firstTime*speed0; + result.overlap[1] = result.overlap[0]; + return result; + } + } + else if (interval0[0] > interval1[1]) + { + // interval0 initially to the right of interval1. + Real diffSpeed = speed1 - speed0; + if (diffSpeed > (Real)0) + { + // The intervals must move towards each other. 'intersect' is + // true when the intervals will intersect by maxTime. + Real diffPos = interval0[0] - interval1[1]; + Real invDiffSpeed = ((Real)1) / diffSpeed; + result.intersect = (diffPos <= maxTime*diffSpeed); + result.numIntersections = 1; + result.firstTime = diffPos*invDiffSpeed; + result.lastTime = (interval0[1] - interval1[0])*invDiffSpeed; + result.overlap[0] = interval1[1] + result.firstTime*speed1; + result.overlap[1] = result.overlap[0]; + return result; + } + } + else + { + // The intervals are initially intersecting. + result.intersect = true; + result.firstTime = (Real)0; + if (speed1 > speed0) + { + result.lastTime = (interval0[1] - interval1[0]) / (speed1 - speed0); + } + else if (speed1 < speed0) + { + result.lastTime = (interval1[1] - interval0[0]) / (speed0 - speed1); + } + else + { + result.lastTime = std::numeric_limits::max(); + } + + if (interval0[1] > interval1[0]) + { + if (interval0[0] < interval1[1]) + { + result.numIntersections = 2; + result.overlap[0] = (interval0[0] < interval1[0] ? + interval1[0] : interval0[0]); + result.overlap[1] = (interval0[1] > interval1[1] ? + interval1[1] : interval0[1]); + } + else // interval0[0] == interval1[1] + { + result.numIntersections = 1; + result.overlap[0] = interval0[0]; + result.overlap[1] = result.overlap[0]; + } + } + else // interval0[1] == interval1[0] + { + result.numIntersections = 1; + result.overlap[0] = interval0[1]; + result.overlap[1] = result.overlap[0]; + } + return result; + } + + result.intersect = false; + result.numIntersections = 0; + result.overlap[0] = std::numeric_limits::max(); + result.overlap[1] = -std::numeric_limits::max(); + result.firstTime = std::numeric_limits::max(); + result.lastTime = -std::numeric_limits::max(); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2AlignedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2AlignedBox2.h new file mode 100644 index 000000000000..c9860eeb0812 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2AlignedBox2.h @@ -0,0 +1,196 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the four edges of +// the box. + +namespace gte +{ + +template +class TIQuery, AlignedBox2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line2 const& line, AlignedBox2 const& box); + +protected: + void DoQuery(Vector2 const& lineOrigin, + Vector2 const& lineDirection, Vector2 const& boxExtent, + Result& result); +}; + +template +class FIQuery, AlignedBox2> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line2 const& line, AlignedBox2 const& box); + +protected: + void DoQuery(Vector2 const& lineOrigin, + Vector2 const& lineDirection, Vector2 const& boxExtent, + Result& result); + +private: + // Test whether the current clipped segment intersects the current test + // plane. If the return value is 'true', the segment does intersect the + // plane and is clipped; otherwise, the segment is culled (no intersection + // with box). + static bool Clip(Real denom, Real numer, Real& t0, Real& t1); +}; + + +template +typename TIQuery, AlignedBox2>::Result +TIQuery, AlignedBox2>::operator()( + Line2 const& line, AlignedBox2 const& box) +{ + // Get the centered form of the aligned box. The axes are implicitly + // Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the line to the aligned-box coordinate system. + Vector2 lineOrigin = line.origin - boxCenter; + + Result result; + DoQuery(lineOrigin, line.direction, boxExtent, result); + return result; +} + +template +void TIQuery, AlignedBox2>::DoQuery( + Vector2 const& lineOrigin, Vector2 const& lineDirection, + Vector2 const& boxExtent, Result& result) +{ + Real LHS = std::abs(DotPerp(lineDirection, lineOrigin)); + Real RHS = + boxExtent[0] * std::abs(lineDirection[1]) + + boxExtent[1] * std::abs(lineDirection[0]); + result.intersect = (LHS <= RHS); +} + +template +typename FIQuery, AlignedBox2>::Result +FIQuery, AlignedBox2>::operator()( + Line2 const& line, AlignedBox2 const& box) +{ + // Get the centered form of the aligned box. The axes are implicitly + // Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the line to the aligned-box coordinate system. + Vector2 lineOrigin = line.origin - boxCenter; + + Result result; + DoQuery(lineOrigin, line.direction, boxExtent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; +} + +template +void FIQuery, AlignedBox2>::DoQuery( + Vector2 const& lineOrigin, Vector2 const& lineDirection, + Vector2 const& boxExtent, Result& result) +{ + // The line t-values are in the interval (-infinity,+infinity). Clip the + // line against all four planes of an aligned box in centered form. The + // result.numPoints is + // 0, no intersection + // 1, intersect in a single point (t0 is line parameter of point) + // 2, intersect in a segment (line parameter interval is [t0,t1]) + Real t0 = -std::numeric_limits::max(); + Real t1 = std::numeric_limits::max(); + if (Clip(+lineDirection[0], -lineOrigin[0] - boxExtent[0], t0, t1) && + Clip(-lineDirection[0], +lineOrigin[0] - boxExtent[0], t0, t1) && + Clip(+lineDirection[1], -lineOrigin[1] - boxExtent[1], t0, t1) && + Clip(-lineDirection[1], +lineOrigin[1] - boxExtent[1], t0, t1)) + { + result.intersect = true; + if (t1 > t0) + { + result.numIntersections = 2; + result.parameter[0] = t0; + result.parameter[1] = t1; + } + else + { + result.numIntersections = 1; + result.parameter[0] = t0; + result.parameter[1] = t0; // Used by derived classes. + } + return; + } + + result.intersect = false; + result.numIntersections = 0; +} + +template +bool FIQuery, AlignedBox2>::Clip(Real denom, + Real numer, Real& t0, Real& t1) +{ + if (denom > (Real)0) + { + if (numer > denom*t1) + { + return false; + } + if (numer > denom*t0) + { + t0 = numer / denom; + } + return true; + } + else if (denom < (Real)0) + { + if (numer > denom*t0) + { + return false; + } + if (numer > denom*t1) + { + t1 = numer / denom; + } + return true; + } + else + { + return numer <= (Real)0; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Arc2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Arc2.h new file mode 100644 index 000000000000..8c8bf23c043a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Arc2.h @@ -0,0 +1,94 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The queries consider the arc to be a 1-dimensional object. + +namespace gte +{ + +template +class TIQuery, Arc2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line2 const& line, Arc2 const& arc); +}; + +template +class FIQuery, Arc2> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line2 const& line, Arc2 const& arc); +}; + + +template +typename TIQuery, Arc2>::Result +TIQuery, Arc2>::operator()( + Line2 const& line, Arc2 const& arc) +{ + Result result; + FIQuery, Arc2> laQuery; + auto laResult = laQuery(line, arc); + result.intersect = laResult.intersect; + return result; +} + +template +typename FIQuery, Arc2>::Result +FIQuery, Arc2>::operator()( + Line2 const& line, Arc2 const& arc) +{ + Result result; + + FIQuery, Circle2> lcQuery; + Circle2 circle(arc.center, arc.radius); + auto lcResult = lcQuery(line, circle); + if (lcResult.intersect) + { + // Test whether line-circle intersections are on the arc. + result.numIntersections = 0; + for (int i = 0; i < lcResult.numIntersections; ++i) + { + if (arc.Contains(lcResult.point[i])) + { + result.intersect = true; + result.parameter[result.numIntersections] + = lcResult.parameter[i]; + result.point[result.numIntersections++] + = lcResult.point[i]; + } + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Circle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Circle2.h new file mode 100644 index 000000000000..16f6465ba75a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Circle2.h @@ -0,0 +1,118 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the circle to be a solid (disk). + +namespace gte +{ + +template +class TIQuery, Circle2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line2 const& line, Circle2 const& circle); +}; + +template +class FIQuery, Circle2> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line2 const& line, Circle2 const& circle); + +protected: + void DoQuery(Vector2 const& lineOrigin, + Vector2 const& lineDirection, Circle2 const& circle, + Result& result); +}; + + +template +typename TIQuery, Circle2>::Result +TIQuery, Circle2>::operator()( + Line2 const& line, Circle2 const& circle) +{ + Result result; + DCPQuery, Line2> plQuery; + auto plResult = plQuery(circle.center, line); + result.intersect = (plResult.distance <= circle.radius); + return result; +} + +template +typename FIQuery, Circle2>::Result +FIQuery, Circle2>::operator()( + Line2 const& line, Circle2 const& circle) +{ + Result result; + DoQuery(line.origin, line.direction, circle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; +} + +template +void FIQuery, Circle2>::DoQuery( + Vector2 const& lineOrigin, Vector2 const& lineDirection, + Circle2 const& circle, Result& result) +{ + // Intersection of a the line P+t*D and the circle |X-C| = R. The line + // direction is unit length. The t-value is a real-valued root to the + // quadratic equation + // 0 = |t*D+P-C|^2 - R^2 + // = t^2 + 2*Dot(D,P-C)*t + |P-C|^2-R^2 + // = t^2 + 2*a1*t + a0 + // If there are two distinct roots, the order is t0 < t1. + Vector2 diff = lineOrigin - circle.center; + Real a0 = Dot(diff, diff) - circle.radius * circle.radius; + Real a1 = Dot(lineDirection, diff); + Real discr = a1 * a1 - a0; + if (discr > (Real)0) + { + Real root = std::sqrt(discr); + result.intersect = true; + result.numIntersections = 2; + result.parameter[0] = -a1 - root; + result.parameter[1] = -a1 + root; + } + else if (discr < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + else // discr == 0 + { + result.intersect = true; + result.numIntersections = 1; + result.parameter[0] = -a1; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Line2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Line2.h new file mode 100644 index 000000000000..5f54ebc56336 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Line2.h @@ -0,0 +1,166 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Line2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (lines intersect in a single + // point) or std::numeric_limits::max() (lines are the same). + int numIntersections; + }; + + Result operator()(Line2 const& line0, Line2 const& line1); +}; + +template +class FIQuery, Line2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (lines intersect in a single + // point) or std::numeric_limits::max() (lines are the same). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point = line0.origin + line0parameter[0] * line0.direction + // = line1.origin + line1parameter[0] * line1.direction + // If numIntersections is maxInt, point is not valid but the + // intervals are + // line0Parameter[] = { -maxReal, +maxReal } + // line1Parameter[] = { -maxReal, +maxReal } + Real line0Parameter[2], line1Parameter[2]; + Vector2 point; + }; + + Result operator()(Line2 const& line0, Line2 const& line1); +}; + + +template +typename TIQuery, Line2>::Result +TIQuery, Line2>::operator()( + Line2 const& line0, Line2 const& line1) +{ + Result result; + + // The intersection of two lines is a solution to P0 + s0*D0 = P1 + s1*D1. + // Rewrite this as s0*D0 - s1*D1 = P1 - P0 = Q. If DotPerp(D0, D1)) = 0, + // the lines are parallel. Additionally, if DotPerp(Q, D1)) = 0, the + // lines are the same. If Dotperp(D0, D1)) is not zero, then + // s0 = DotPerp(Q, D1))/DotPerp(D0, D1)) + // produces the point of intersection. Also, + // s1 = DotPerp(Q, D0))/DotPerp(D0, D1)) + + Vector2 diff = line1.origin - line0.origin; + Real D0DotPerpD1 = DotPerp(line0.direction, line1.direction); + if (D0DotPerpD1 != (Real)0) + { + // The lines are not parallel. + result.intersect = true; + result.numIntersections = 1; + } + else + { + // The lines are parallel. + Normalize(diff); + Real diffNDotPerpD1 = DotPerp(diff, line1.direction); + if (diffNDotPerpD1 != (Real)0) + { + // The lines are parallel but distinct. + result.intersect = false; + result.numIntersections = 0; + } + else + { + // The lines are the same. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + } + } + + return result; +} + +template +typename FIQuery, Line2>::Result +FIQuery, Line2>::operator()( + Line2 const& line0, Line2 const& line1) +{ + Result result; + + // The intersection of two lines is a solution to P0 + s0*D0 = P1 + s1*D1. + // Rewrite this as s0*D0 - s1*D1 = P1 - P0 = Q. If DotPerp(D0, D1)) = 0, + // the lines are parallel. Additionally, if DotPerp(Q, D1)) = 0, the + // lines are the same. If Dotperp(D0, D1)) is not zero, then + // s0 = DotPerp(Q, D1))/DotPerp(D0, D1)) + // produces the point of intersection. Also, + // s1 = DotPerp(Q, D0))/DotPerp(D0, D1)) + + Vector2 diff = line1.origin - line0.origin; + Real D0DotPerpD1 = DotPerp(line0.direction, line1.direction); + if (D0DotPerpD1 != (Real)0) + { + // The lines are not parallel. + result.intersect = true; + result.numIntersections = 1; + Real invD0DotPerpD1 = ((Real)1) / D0DotPerpD1; + Real diffDotPerpD0 = DotPerp(diff, line0.direction); + Real diffDotPerpD1 = DotPerp(diff, line1.direction); + Real s0 = diffDotPerpD1*invD0DotPerpD1; + Real s1 = diffDotPerpD0*invD0DotPerpD1; + result.line0Parameter[0] = s0; + result.line1Parameter[0] = s1; + result.point = line0.origin + s0 * line0.direction; + } + else + { + // The lines are parallel. + Normalize(diff); + Real diffNDotPerpD1 = DotPerp(diff, line1.direction); + if (std::abs(diffNDotPerpD1) != (Real)0) + { + // The lines are parallel but distinct. + result.intersect = false; + result.numIntersections = 0; + } + else + { + // The lines are the same. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + Real maxReal = std::numeric_limits::max(); + result.line0Parameter[0] = -maxReal; + result.line0Parameter[1] = +maxReal; + result.line1Parameter[0] = -maxReal; + result.line1Parameter[1] = +maxReal; + } + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2OrientedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2OrientedBox2.h new file mode 100644 index 000000000000..2a7e9bf23d98 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2OrientedBox2.h @@ -0,0 +1,106 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the four edges of +// the box. + +namespace gte +{ + +template +class TIQuery, OrientedBox2> + : + public TIQuery, AlignedBox2> +{ +public: + struct Result + : + public TIQuery, AlignedBox2>::Result + { + // No additional relevant information to compute. + }; + + Result operator()(Line2 const& line, OrientedBox2 const& box); +}; + +template +class FIQuery, OrientedBox2> + : + public FIQuery, AlignedBox2> +{ +public: + struct Result + : + public FIQuery, AlignedBox2>::Result + { + // No additional relevant information to compute. + }; + + Result operator()(Line2 const& line, OrientedBox2 const& box); +}; + + +template +typename TIQuery, OrientedBox2>::Result +TIQuery, OrientedBox2>::operator()( + Line2 const& line, OrientedBox2 const& box) +{ + // Transform the line to the oriented-box coordinate system. + Vector2 diff = line.origin - box.center; + Vector2 lineOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 lineDirection + { + Dot(line.direction, box.axis[0]), + Dot(line.direction, box.axis[1]) + }; + + Result result; + this->DoQuery(lineOrigin, lineDirection, box.extent, result); + return result; +} + +template +typename FIQuery, OrientedBox2>::Result +FIQuery, OrientedBox2>::operator()( + Line2 const& line, OrientedBox2 const& box) +{ + // Transform the line to the oriented-box coordinate system. + Vector2 diff = line.origin - box.center; + Vector2 lineOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 lineDirection + { + Dot(line.direction, box.axis[0]), + Dot(line.direction, box.axis[1]) + }; + + Result result; + this->DoQuery(lineOrigin, lineDirection, box.extent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Ray2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Ray2.h new file mode 100644 index 000000000000..a5227d712b95 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Ray2.h @@ -0,0 +1,135 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class TIQuery, Ray2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (line and ray intersect in a + // single point) or std::numeric_limits::max() (line and ray + // are collinear). + int numIntersections; + }; + + Result operator()(Line2 const& line, Ray2 const& ray); +}; + +template +class FIQuery, Ray2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (line and ray intersect in a + // single point) or std::numeric_limits::max() (line and ray + // are collinear). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point = line.origin + lineParameter[0] * line.direction + // = ray.origin + rayParameter[0] * ray.direction + // If numIntersections is maxInt, point is not valid but the + // intervals are + // lineParameter[] = { -maxReal, +maxReal } + // rayParameter[] = { 0, +maxReal } + Real lineParameter[2], rayParameter[2]; + Vector2 point; + }; + + Result operator()(Line2 const& line, Ray2 const& ray); +}; + + +template +typename TIQuery, Ray2>::Result +TIQuery, Ray2>::operator()( + Line2 const& line, Ray2 const& ray) +{ + Result result; + FIQuery, Line2> llQuery; + auto llResult = llQuery(line, Line2(ray.origin, ray.direction)); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the ray. + if (llResult.line1Parameter[0] >= (Real)0) + { + result.intersect = true; + result.numIntersections = 1; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else + { + result.intersect = llResult.intersect; + result.numIntersections = llResult.numIntersections; + } + return result; +} + +template +typename FIQuery, Ray2>::Result +FIQuery, Ray2>::operator()( + Line2 const& line, Ray2 const& ray) +{ + Result result; + FIQuery, Line2> llQuery; + auto llResult = llQuery(line, Line2(ray.origin, ray.direction)); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the ray. + if (llResult.line1Parameter[0] >= (Real)0) + { + result.intersect = true; + result.numIntersections = 1; + result.lineParameter[0] = llResult.line0Parameter[0]; + result.rayParameter[0] = llResult.line1Parameter[0]; + result.point = llResult.point; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + Real maxReal = std::numeric_limits::max(); + result.lineParameter[0] = -maxReal; + result.lineParameter[1] = +maxReal; + result.rayParameter[0] = (Real)0; + result.rayParameter[1] = +maxReal; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Segment2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Segment2.h new file mode 100644 index 000000000000..bd21e02c8176 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Segment2.h @@ -0,0 +1,143 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class TIQuery, Segment2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (line and segment intersect in + // a single point) or std::numeric_limits::max() (line and + // segment are collinear). + int numIntersections; + }; + + Result operator()(Line2 const& line, Segment2 const& segment); +}; + +template +class FIQuery, Segment2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (line and segment intersect in + // a single point) or std::numeric_limits::max() (line and + // segment are collinear). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point = line.origin + lineParameter[0] * line.direction + // = segment.origin + segmentParameter[0] * segment.direction + // If numIntersections is maxInt, point is not valid but the + // intervals are + // lineParameter[] = { -maxReal, +maxReal } + // segmentParameter[] = { -segmentExtent, segmentExtent } + Real lineParameter[2], segmentParameter[2]; + Vector2 point; + }; + + Result operator()(Line2 const& line, Segment2 const& segment); +}; + + +template +typename TIQuery, Segment2>::Result +TIQuery, Segment2>::operator()( + Line2 const& line, Segment2 const& segment) +{ + Result result; + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + FIQuery, Line2> llQuery; + auto llResult = llQuery(line, Line2(segOrigin, segDirection)); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the segment. + if (std::abs(llResult.line1Parameter[0]) <= segExtent) + { + result.intersect = true; + result.numIntersections = 1; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else + { + result.intersect = llResult.intersect; + result.numIntersections = llResult.numIntersections; + } + return result; +} + +template +typename FIQuery, Segment2>::Result +FIQuery, Segment2>::operator()( + Line2 const& line, Segment2 const& segment) +{ + Result result; + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + FIQuery, Line2> llQuery; + auto llResult = llQuery(line, Line2(segOrigin, segDirection)); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the ray. + if (std::abs(llResult.line1Parameter[0]) <= segExtent) + { + result.intersect = true; + result.numIntersections = 1; + result.lineParameter[0] = llResult.line0Parameter[0]; + result.segmentParameter[0] = llResult.line1Parameter[0]; + result.point = llResult.point; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + Real maxReal = std::numeric_limits::max(); + result.lineParameter[0] = -maxReal; + result.lineParameter[1] = +maxReal; + result.segmentParameter[0] = -segExtent; + result.segmentParameter[1] = +segExtent; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Triangle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Triangle2.h new file mode 100644 index 000000000000..0602a2f0de88 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Triangle2.h @@ -0,0 +1,243 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the triangle to be a solid. + +namespace gte +{ + +template +class TIQuery, Triangle2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line2 const& line, + Triangle2 const& triangle); +}; + +template +class FIQuery, Triangle2> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line2 const& line, + Triangle2 const& triangle); + +protected: + void DoQuery(Vector2 const& lineOrigin, + Vector2 const& lineDirection, Triangle2 const& triangle, + Result& result); +}; + + +template +typename TIQuery, Triangle2>::Result +TIQuery, Triangle2>::operator()( + Line2 const& line, Triangle2 const& triangle) +{ + Result result; + + // Determine on which side of the line the vertices lie. The table of + // possibilities is listed next with n = numNegative, p = numPositive, and + // z = numZero. + // + // n p z intersection + // ------------------------------------ + // 0 3 0 none + // 0 2 1 vertex + // 0 1 2 edge + // 0 0 3 none (degenerate triangle) + // 1 2 0 segment (2 edges clipped) + // 1 1 1 segment (1 edge clipped) + // 1 0 2 edge + // 2 1 0 segment (2 edges clipped) + // 2 0 1 vertex + // 3 0 0 none + + Real s[3]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 3; ++i) + { + Vector2 diff = triangle.v[i] - line.origin; + s[i] = DotPerp(line.direction, diff); + if (s[i] >(Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + result.intersect = + (numZero == 0 && (numPositive == 0 || numNegative == 0)) || + (numZero == 3); + + return result; +} + +template +typename FIQuery, Triangle2>::Result +FIQuery, Triangle2>::operator()( + Line2 const& line, Triangle2 const& triangle) +{ + Result result; + DoQuery(line.origin, line.direction, triangle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; +} + +template +void FIQuery, Triangle2>::DoQuery( + Vector2 const& lineOrigin, Vector2 const& lineDirection, + Triangle2 const& triangle, Result& result) +{ + // Determine on which side of the line the vertices lie. The table of + // possibilities is listed next with n = numNegative, p = numPositive, and + // z = numZero. + // + // n p z intersection + // ------------------------------------ + // 0 3 0 none + // 0 2 1 vertex + // 0 1 2 edge + // 0 0 3 none (degenerate triangle) + // 1 2 0 segment (2 edges clipped) + // 1 1 1 segment (1 edge clipped) + // 1 0 2 edge + // 2 1 0 segment (2 edges clipped) + // 2 0 1 vertex + // 3 0 0 none + + Real s[3]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 3; ++i) + { + Vector2 diff = triangle.v[i] - lineOrigin; + s[i] = DotPerp(lineDirection, diff); + if (s[i] >(Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + if (numZero == 0 && numPositive > 0 && numNegative > 0) + { + result.intersect = true; + result.numIntersections = 2; + Real sign = (Real)3 - numPositive * (Real)2; + for (int i0 = 0; i0 < 3; ++i0) + { + if (sign * s[i0] >(Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + Real s1 = s[i1] / (s[i1] - s[i0]); + Vector2 p1 = (triangle.v[i1] - lineOrigin) + + s1 * (triangle.v[i0] - triangle.v[i1]); + result.parameter[0] = Dot(lineDirection, p1); + Real s2 = s[i2] / (s[i2] - s[i0]); + Vector2 p2 = (triangle.v[i2] - lineOrigin) + + s2 * (triangle.v[i0] - triangle.v[i2]); + result.parameter[1] = Dot(lineDirection, p2); + break; + } + } + return; + } + + if (numZero == 1) + { + result.intersect = true; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] == (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.parameter[0] = + Dot(lineDirection, triangle.v[i0] - lineOrigin); + if (numPositive == 2 || numNegative == 2) + { + result.numIntersections = 1; + + // Used by derived classes. + result.parameter[1] = result.parameter[0]; + } + else + { + result.numIntersections = 2; + Real s1 = s[i1] / (s[i1] - s[i2]); + Vector2 p1 = (triangle.v[i1] - lineOrigin) + + s1 * (triangle.v[i2] - triangle.v[i1]); + result.parameter[1] = Dot(lineDirection, p1); + } + break; + } + } + return; + } + + if (numZero == 2) + { + result.intersect = true; + result.numIntersections = 2; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] != (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.parameter[0] = + Dot(lineDirection, triangle.v[i1] - lineOrigin); + result.parameter[1] = + Dot(lineDirection, triangle.v[i2] - lineOrigin); + break; + } + } + return; + } + + // (n,p,z) one of (3,0,0), (0,3,0), (0,0,3) + result.intersect = false; + result.numIntersections = 0; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3AlignedBox3.h new file mode 100644 index 000000000000..c3e54f630796 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3AlignedBox3.h @@ -0,0 +1,190 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/11/28) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the six faces of +// the box. The find-intersection queries use Liang-Barsky clipping. The +// queries consider the box to be a solid. The algorithms are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace gte +{ + template + class TIQuery, AlignedBox3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are implicitly + // Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the line to the aligned-box coordinate system. + Vector3 lineOrigin = line.origin - boxCenter; + + Result result; + DoQuery(lineOrigin, line.direction, boxExtent, result); + return result; + } + + protected: + void DoQuery(Vector3 const& lineOrigin, Vector3 const& lineDirection, + Vector3 const& boxExtent, Result& result) + { + Vector3 WxD = Cross(lineDirection, lineOrigin); + Real absWdU[3] = + { + std::abs(lineDirection[0]), + std::abs(lineDirection[1]), + std::abs(lineDirection[2]) + }; + + if (std::abs(WxD[0]) > boxExtent[1] * absWdU[2] + boxExtent[2] * absWdU[1]) + { + result.intersect = false; + return; + } + + if (std::abs(WxD[1]) > boxExtent[0] * absWdU[2] + boxExtent[2] * absWdU[0]) + { + result.intersect = false; + return; + } + + if (std::abs(WxD[2]) > boxExtent[0] * absWdU[1] + boxExtent[1] * absWdU[0]) + { + result.intersect = false; + return; + } + + result.intersect = true; + } + }; + + template + class FIQuery, AlignedBox3> + { + public: + struct Result + { + bool intersect; + int numPoints; + Real lineParameter[2]; + Vector3 point[2]; + }; + + Result operator()(Line3 const& line, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the line to the aligned-box coordinate system. + Vector3 lineOrigin = line.origin - boxCenter; + + Result result; + DoQuery(lineOrigin, line.direction, boxExtent, result); + for (int i = 0; i < result.numPoints; ++i) + { + result.point[i] = line.origin + result.lineParameter[i] * line.direction; + } + return result; + } + + protected: + void DoQuery(Vector3 const& lineOrigin, Vector3 const& lineDirection, + Vector3 const& boxExtent, Result& result) + { + // The line t-values are in the interval (-infinity,+infinity). + // Clip the line against all six planes of an aligned box in + // centered form. The result.numPoints is + // 0, no intersection + // 1, intersect in a single point (t0 is line parameter of point) + // 2, intersect in a segment (line parameter interval is [t0,t1]) + Real t0 = -std::numeric_limits::max(); + Real t1 = std::numeric_limits::max(); + if (Clip(+lineDirection[0], -lineOrigin[0] - boxExtent[0], t0, t1) && + Clip(-lineDirection[0], +lineOrigin[0] - boxExtent[0], t0, t1) && + Clip(+lineDirection[1], -lineOrigin[1] - boxExtent[1], t0, t1) && + Clip(-lineDirection[1], +lineOrigin[1] - boxExtent[1], t0, t1) && + Clip(+lineDirection[2], -lineOrigin[2] - boxExtent[2], t0, t1) && + Clip(-lineDirection[2], +lineOrigin[2] - boxExtent[2], t0, t1)) + { + result.intersect = true; + if (t1 > t0) + { + result.numPoints = 2; + result.lineParameter[0] = t0; + result.lineParameter[1] = t1; + } + else + { + result.numPoints = 1; + result.lineParameter[0] = t0; + result.lineParameter[1] = t0; // Used by derived classes. + } + return; + } + + result.intersect = false; + result.numPoints = 0; + } + + private: + // Test whether the current clipped segment intersects the current + // test plane. If the return value is 'true', the segment does + // intersect the plane and is clipped; otherwise, the segment is + // culled (no intersection with box). + static bool Clip(Real denom, Real numer, Real& t0, Real& t1) + { + if (denom > (Real)0) + { + if (numer > denom * t1) + { + return false; + } + if (numer > denom * t0) + { + t0 = numer / denom; + } + return true; + } + else if (denom < (Real)0) + { + if (numer > denom * t0) + { + return false; + } + if (numer > denom * t1) + { + t1 = numer / denom; + } + return true; + } + else + { + return numer <= (Real)0; + } + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Capsule3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Capsule3.h new file mode 100644 index 000000000000..80b654ff4a38 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Capsule3.h @@ -0,0 +1,341 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the capsule to be a solid. +// +// The test-intersection queries are based on distance computations. + +namespace gte +{ + +template +class TIQuery, Capsule3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, Capsule3 const& capsule); +}; + +template +class FIQuery, Capsule3> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line3 const& line, Capsule3 const& capsule); + +protected: + void DoQuery(Vector3 const& lineOrigin, + Vector3 const& lineDirection, Capsule3 const& capsule, + Result& result); +}; + + +template +typename TIQuery, Capsule3>::Result +TIQuery, Capsule3>::operator()( + Line3 const& line, Capsule3 const& capsule) +{ + Result result; + DCPQuery, Segment3> lsQuery; + auto lsResult = lsQuery(line, capsule.segment); + result.intersect = (lsResult.distance <= capsule.radius); + return result; +} + +template +typename FIQuery, Capsule3>::Result +FIQuery, Capsule3>::operator()( + Line3 const& line, Capsule3 const& capsule) +{ + Result result; + DoQuery(line.origin, line.direction, capsule, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; +} + +template +void FIQuery, Capsule3>::DoQuery( + Vector3 const& lineOrigin, Vector3 const& lineDirection, + Capsule3 const& capsule, Result& result) +{ + // Initialize the result as if there is no intersection. If we discover + // an intersection, these values will be modified accordingly. + result.intersect = false; + result.numIntersections = 0; + + // Create a coordinate system for the capsule. In this system, the + // capsule segment center C is the origin and the capsule axis direction + // W is the z-axis. U and V are the other coordinate axis directions. + // If P = x*U+y*V+z*W, the cylinder containing the capsule wall is + // x^2 + y^2 = r^2, where r is the capsule radius. The finite cylinder + // that makes up the capsule minus its hemispherical end caps has z-values + // |z| <= e, where e is the extent of the capsule segment. The top + // hemisphere cap is x^2+y^2+(z-e)^2 = r^2 for z >= e, and the bottom + // hemisphere cap is x^2+y^2+(z+e)^2 = r^2 for z <= -e. + + Vector3 segOrigin, segDirection; + Real segExtent; + capsule.segment.GetCenteredForm(segOrigin, segDirection, segExtent); + Vector3 basis[3]; // {W, U, V} + basis[0] = segDirection; + ComputeOrthogonalComplement(1, basis); + Real rSqr = capsule.radius * capsule.radius; + + // Convert incoming line origin to capsule coordinates. + Vector3 diff = lineOrigin - segOrigin; + Vector3 P{ Dot(basis[1], diff), Dot(basis[2], diff), + Dot(basis[0], diff) }; + + // Get the z-value, in capsule coordinates, of the incoming line's + // unit-length direction. + Real dz = Dot(basis[0], lineDirection); + if (std::abs(dz) == (Real)1) + { + // The line is parallel to the capsule axis. Determine whether the + // line intersects the capsule hemispheres. + Real radialSqrDist = rSqr - P[0] * P[0] - P[1] * P[1]; + if (radialSqrDist >= (Real)0) + { + // The line intersects the hemispherical caps. + result.intersect = true; + result.numIntersections = 2; + Real zOffset = std::sqrt(radialSqrDist) + segExtent; + if (dz > (Real)0) + { + result.parameter[0] = -P[2] - zOffset; + result.parameter[1] = -P[2] + zOffset; + } + else + { + result.parameter[0] = P[2] - zOffset; + result.parameter[1] = P[2] + zOffset; + } + } + // else: The line outside the capsule's cylinder, no intersection. + return; + } + + // Convert the incoming line unit-length direction to capsule coordinates. + Vector3 D{ Dot(basis[1], lineDirection), + Dot(basis[2], lineDirection), dz }; + + // Test intersection of line P+t*D with infinite cylinder x^2+y^2 = r^2. + // This reduces to computing the roots of a quadratic equation. If + // P = (px,py,pz) and D = (dx,dy,dz), then the quadratic equation is + // (dx^2+dy^2)*t^2 + 2*(px*dx+py*dy)*t + (px^2+py^2-r^2) = 0 + Real a0 = P[0] * P[0] + P[1] * P[1] - rSqr; + Real a1 = P[0] * D[0] + P[1] * D[1]; + Real a2 = D[0] * D[0] + D[1] * D[1]; + Real discr = a1 * a1 - a0 * a2; + if (discr < (Real)0) + { + // The line does not intersect the infinite cylinder, so it + // cannot intersect the capsule. + return; + } + + Real root, inv, tValue, zValue; + if (discr >(Real)0) + { + // The line intersects the infinite cylinder in two places. + root = std::sqrt(discr); + inv = ((Real)1) / a2; + tValue = (-a1 - root) * inv; + zValue = P[2] + tValue * D[2]; + if (std::abs(zValue) <= segExtent) + { + result.intersect = true; + result.parameter[result.numIntersections++] = tValue; + } + + tValue = (-a1 + root) * inv; + zValue = P[2] + tValue * D[2]; + if (std::abs(zValue) <= segExtent) + { + result.intersect = true; + result.parameter[result.numIntersections++] = tValue; + } + + if (result.numIntersections == 2) + { + // The line intersects the capsule wall in two places. + return; + } + } + else + { + // The line is tangent to the infinite cylinder but intersects the + // cylinder in a single point. + tValue = -a1 / a2; + zValue = P[2] + tValue * D[2]; + if (std::abs(zValue) <= segExtent) + { + result.intersect = true; + result.numIntersections = 1; + result.parameter[0] = tValue; + // Used by derived classes. + result.parameter[1] = result.parameter[0]; + return; + } + } + + // Test intersection with bottom hemisphere. The quadratic equation is + // t^2 + 2*(px*dx+py*dy+(pz+e)*dz)*t + (px^2+py^2+(pz+e)^2-r^2) = 0 + // Use the fact that currently a1 = px*dx+py*dy and a0 = px^2+py^2-r^2. + // The leading coefficient is a2 = 1, so no need to include in the + // construction. + Real PZpE = P[2] + segExtent; + a1 += PZpE * D[2]; + a0 += PZpE * PZpE; + discr = a1 * a1 - a0; + if (discr > (Real)0) + { + root = std::sqrt(discr); + tValue = -a1 - root; + zValue = P[2] + tValue * D[2]; + if (zValue <= -segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + + tValue = -a1 + root; + zValue = P[2] + tValue * D[2]; + if (zValue <= -segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + } + else if (discr == (Real)0) + { + tValue = -a1; + zValue = P[2] + tValue * D[2]; + if (zValue <= -segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + } + + // Test intersection with top hemisphere. The quadratic equation is + // t^2 + 2*(px*dx+py*dy+(pz-e)*dz)*t + (px^2+py^2+(pz-e)^2-r^2) = 0 + // Use the fact that currently a1 = px*dx+py*dy+(pz+e)*dz and + // a0 = px^2+py^2+(pz+e)^2-r^2. The leading coefficient is a2 = 1, so + // no need to include in the construction. + a1 -= ((Real)2) * segExtent * D[2]; + a0 -= ((Real)4) * segExtent * P[2]; + discr = a1 * a1 - a0; + if (discr > (Real)0) + { + root = std::sqrt(discr); + tValue = -a1 - root; + zValue = P[2] + tValue * D[2]; + if (zValue >= segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + + tValue = -a1 + root; + zValue = P[2] + tValue * D[2]; + if (zValue >= segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + } + else if (discr == (Real)0) + { + tValue = -a1; + zValue = P[2] + tValue * D[2]; + if (zValue >= segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + } + + if (result.numIntersections == 1) + { + // Used by derived classes. + result.parameter[1] = result.parameter[0]; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Cone3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Cone3.h new file mode 100644 index 000000000000..10c489d7a2f4 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Cone3.h @@ -0,0 +1,320 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/11/29) + +#pragma once + +#include +#include +#include +#include + +// The queries consider the cone to be single sided and solid. + +namespace gte +{ + +template +class FIQuery, Cone3> +{ +public: + struct Result + { + // Default construction. Set 'intersect' to false, 'type' to 0, + // 'parameter' to (0,0), and 'point' to (veczero,veczero). + Result(); + + bool intersect; + + // Because the intersection of line and cone with infinite height + // h > 0 can be a ray or a line, we use a 'type' value that allows + // you to decide how to interpret the parameter[] and point[] values. + // type intersect valid data + // 0 none none + // 1 point parameter[0] = parameter[1], finite + // point[0] = point[1] + // 2 segment parameter[0] < parameter[1], finite + // point[0,1] valid + // 3 ray parameter[0] finite, parameter[1] maxReal + // point[0] = rayOrigin, point[1] = lineDirection + // 4 ray parameter[0] -maxReal, parameter[1] finite + // point[0] = rayOrigin, point[1] = -lineDirection + // 5 line parameter[0] -maxReal, parameter[1] maxReal, + // point[0] = lineOrigin, point[1] = lineDirection + // If the cone height h is finite, only types 0, 1, or 2 can occur. + int type; + std::array parameter; // Relative to incoming line. + std::array, 2> point; + }; + + Result operator()(Line3 const& line, Cone3 const& cone); + +protected: + void DoQuery(Vector3 const& lineOrigin, + Vector3 const& lineDirection, Cone3 const& cone, + Result& result); +}; + + +template +FIQuery, Cone3>::Result::Result() + : + intersect(false), + type(0) +{ + parameter.fill((Real)0); + point.fill({ (Real)0, (Real)0, (Real)0 }); +} + +template +typename FIQuery, Cone3>::Result +FIQuery, Cone3>::operator()(Line3 const& line, + Cone3 const& cone) +{ + Result result; + DoQuery(line.origin, line.direction, cone, result); + switch (result.type) + { + case 1: // point + result.point[0] = line.origin + result.parameter[0] * line.direction; + result.point[1] = result.point[0]; + break; + case 2: // segment + result.point[0] = line.origin + result.parameter[0] * line.direction; + result.point[1] = line.origin + result.parameter[1] * line.direction; + break; + case 3: // ray + result.point[0] = line.origin + result.parameter[0] * line.direction; + result.point[1] = line.direction; + break; + case 4: // ray + result.point[0] = line.origin + result.parameter[1] * line.direction; + result.point[1] = -line.direction; + break; + case 5: // line + result.point[0] = line.origin; + result.point[1] = line.direction; + break; + default: // no intersection + break; + } + return result; +} + +template +void FIQuery, Cone3>::DoQuery( + Vector3 const& lineOrigin, Vector3 const& lineDirection, + Cone3 const& cone, Result& result) +{ + // The cone has vertex V, unit-length axis direction D, angle theta in + // (0,pi/2), and height h in (0,+infinity). The line is P + t*U, where U + // is a unit-length direction vector. Define g = cos(theta). The cone + // is represented by + // (X-V)^T * (D*D^T - g^2*I) * (X-V) = 0, 0 <= Dot(D,X-V) <= h + // The first equation defines a double-sided cone. The first inequality + // in the second equation limits this to a single-sided cone containing + // the ray V + s*D with s >= 0. We will call this the 'positive cone'. + // The single-sided cone containing ray V + s * t with s <= 0 is called + // the 'negative cone'. The double-sided cone is the union of the + // positive cone and negative cone. The second inequality in the second + // equation limits the single-sided cone to the region bounded by the + // height. Setting X(t) = P + t*U, the equations are + // c2*t^2 + 2*c1*t + c0 = 0, 0 <= Dot(D,U)*t + Dot(D,P-V) <= h + // where + // c2 = Dot(D,U)^2 - g^2 + // c1 = Dot(D,U)*Dot(D,P-V) - g^2*Dot(U,P-V) + // c0 = Dot(D,P-V)^2 - g^2*Dot(P-V,P-V) + // The following code computes the t-interval that satisfies the quadratic + // equation subject to the linear inequality constraints. + + Vector3 PmV = lineOrigin - cone.ray.origin; + Real DdU = Dot(cone.ray.direction, lineDirection); + Real DdPmV = Dot(cone.ray.direction, PmV); + Real UdPmV = Dot(lineDirection, PmV); + Real PmVdPmV = Dot(PmV, PmV); + Real cosAngleSqr = cone.cosAngle * cone.cosAngle; + Real c2 = DdU * DdU - cosAngleSqr; + Real c1 = DdU * DdPmV - cosAngleSqr * UdPmV; + Real c0 = DdPmV * DdPmV - cosAngleSqr * PmVdPmV; + Real t; + + if (c2 != (Real)0) + { + Real discr = c1 * c1 - c0 * c2; + if (discr < (Real)0) + { + // The quadratic has no real-valued roots. The line does not + // intersect the double-sided cone. + result.intersect = false; + result.type = 0; + return; + } + else if (discr > (Real)0) + { + // The quadratic has two distinct real-valued roots. However, one + // or both of them might intersect the negative cone. We are + // interested only in those intersections with the positive cone. + Real root = std::sqrt(discr); + Real invC2 = ((Real)1) / c2; + int numParameters = 0; + + t = (-c1 - root) * invC2; + if (DdU * t + DdPmV >= (Real)0) + { + result.parameter[numParameters++] = t; + } + + t = (-c1 + root) * invC2; + if (DdU * t + DdPmV >= (Real)0) + { + result.parameter[numParameters++] = t; + } + + if (numParameters == 2) + { + // The line intersects the positive cone in two distinct + // points. + result.intersect = true; + result.type = 2; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + } + else if (numParameters == 1) + { + // The line intersects the positive cone in a single point and + // the negative cone in a single point. We report only the + // intersection with the positive cone. + result.intersect = true; + if (DdU > (Real)0) + { + result.type = 3; + result.parameter[1] = std::numeric_limits::max(); + } + else + { + result.type = 4; + result.parameter[1] = result.parameter[0]; + result.parameter[0] = -std::numeric_limits::max(); + + } + } + else + { + // The line intersects the negative cone in two distinct + // points, but we are interested only in the intersections + // with the positive cone. + result.intersect = false; + result.type = 0; + return; + } + } + else // discr == 0 + { + // One repeated real root; the line is tangent to the double-sided + // cone at a single point. Report only the point if it is on the + // positive cone. + t = -c1 / c2; + if (DdU * t + DdPmV >= (Real)0) + { + result.intersect = true; + result.type = 1; + result.parameter[0] = t; + result.parameter[1] = t; + } + else + { + result.intersect = false; + result.type = 0; + return; + } + } + } + else if (c1 != (Real)0) + { + // c2 = 0, c1 != 0; U is a direction vector on the cone boundary + t = -((Real)0.5)*c0 / c1; + if (DdU * t + DdPmV >= (Real)0) + { + // The line intersects the positive cone and the ray of + // intersection is interior to the positive cone. + result.intersect = true; + if (DdU > (Real)0) + { + result.type = 3; + result.parameter[0] = t; + result.parameter[1] = std::numeric_limits::max(); + } + else + { + result.type = 4; + result.parameter[0] = -std::numeric_limits::max(); + result.parameter[1] = t; + } + } + else + { + // The line intersects the negative cone and the ray of + // intersection is interior to the positive cone. + result.intersect = false; + result.type = 0; + return; + } + } + else if (c0 != (Real)0) + { + // c2 = c1 = 0, c0 != 0. Cross(D,U) is perpendicular to Cross(P-V,U) + result.intersect = false; + result.type = 0; + return; + } + else + { + // c2 = c1 = c0 = 0; the line is on the cone boundary. + result.intersect = true; + result.type = 5; + result.parameter[0] = -std::numeric_limits::max(); + result.parameter[1] = +std::numeric_limits::max(); + } + + if (cone.maxHeight < std::numeric_limits::max()) + { + if (DdU != (Real)0) + { + // Clamp the intersection to the height of the cone. + Real invDdU = ((Real)1) / DdU; + std::array hInterval; + if (DdU >(Real)0) + { + hInterval[0] = -DdPmV * invDdU; + hInterval[1] = (cone.maxHeight - DdPmV) * invDdU; + } + else // (DdU < (Real)0) + { + hInterval[0] = (cone.maxHeight - DdPmV) * invDdU; + hInterval[1] = -DdPmV * invDdU; + } + + FIIntervalInterval iiQuery; + auto iiResult = iiQuery(result.parameter, hInterval); + result.intersect = (iiResult.numIntersections > 0); + result.type = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else if (result.intersect) + { + if (DdPmV > cone.maxHeight) + { + result.intersect = false; + result.type = 0; + } + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Cylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Cylinder3.h new file mode 100644 index 000000000000..f058b1c47dfa --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Cylinder3.h @@ -0,0 +1,264 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +// The queries consider the cylinder to be a solid. + +namespace gte +{ + +template +class FIQuery, Cylinder3> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line3 const& line, + Cylinder3 const& cylinder); + +protected: + void DoQuery(Vector3 const& lineOrigin, + Vector3 const& lineDirection, Cylinder3 const& cylinder, + Result& result); +}; + + +template +typename FIQuery, Cylinder3>::Result +FIQuery, Cylinder3>::operator()( + Line3 const& line, Cylinder3 const& cylinder) +{ + Result result; + DoQuery(line.origin, line.direction, cylinder, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; +} + +template +void FIQuery, Cylinder3>::DoQuery( + Vector3 const& lineOrigin, Vector3 const& lineDirection, + Cylinder3 const& cylinder, Result& result) +{ + // Initialize the result as if there is no intersection. If we discover + // an intersection, these values will be modified accordingly. + result.intersect = false; + result.numIntersections = 0; + + // Create a coordinate system for the cylinder. In this system, the + // cylinder segment center C is the origin and the cylinder axis direction + // W is the z-axis. U and V are the other coordinate axis directions. + // If P = x*U+y*V+z*W, the cylinder is x^2 + y^2 = r^2, where r is the + // cylinder radius. The end caps are |z| = h/2, where h is the cylinder + // height. + Vector3 basis[3]; // {W, U, V} + basis[0] = cylinder.axis.direction; + ComputeOrthogonalComplement(1, basis); + Real halfHeight = ((Real)0.5) * cylinder.height; + Real rSqr = cylinder.radius * cylinder.radius; + + // Convert incoming line origin to capsule coordinates. + Vector3 diff = lineOrigin - cylinder.axis.origin; + Vector3 P{ Dot(basis[1], diff), Dot(basis[2], diff), + Dot(basis[0], diff) }; + + // Get the z-value, in cylinder coordinates, of the incoming line's + // unit-length direction. + Real dz = Dot(basis[0], lineDirection); + if (std::abs(dz) == (Real)1) + { + // The line is parallel to the cylinder axis. Determine whether the + // line intersects the cylinder end disks. + Real radialSqrDist = rSqr - P[0] * P[0] - P[1] * P[1]; + if (radialSqrDist >= (Real)0) + { + // The line intersects the cylinder end disks. + result.intersect = true; + result.numIntersections = 2; + if (dz > (Real)0) + { + result.parameter[0] = -P[2] - halfHeight; + result.parameter[1] = -P[2] + halfHeight; + } + else + { + result.parameter[0] = P[2] - halfHeight; + result.parameter[1] = P[2] + halfHeight; + } + } + // else: The line is outside the cylinder, no intersection. + return; + } + + // Convert the incoming line unit-length direction to cylinder + // coordinates. + Vector3 D{ Dot(basis[1], lineDirection), + Dot(basis[2], lineDirection), dz }; + + Real a0, a1, a2, discr, root, inv, tValue; + + if (D[2] == (Real)0) + { + // The line is perpendicular to the cylinder axis. + if (std::abs(P[2]) <= halfHeight) + { + // Test intersection of line P+t*D with infinite cylinder + // x^2+y^2 = r^2. This reduces to computing the roots of a + // quadratic equation. If P = (px,py,pz) and D = (dx,dy,dz), + // then the quadratic equation is + // (dx^2+dy^2)*t^2 + 2*(px*dx+py*dy)*t + (px^2+py^2-r^2) = 0 + a0 = P[0] * P[0] + P[1] * P[1] - rSqr; + a1 = P[0] * D[0] + P[1] * D[1]; + a2 = D[0] * D[0] + D[1] * D[1]; + discr = a1 * a1 - a0 * a2; + if (discr > (Real)0) + { + // The line intersects the cylinder in two places. + result.intersect = true; + result.numIntersections = 2; + root = std::sqrt(discr); + inv = ((Real)1) / a2; + result.parameter[0] = (-a1 - root) * inv; + result.parameter[1] = (-a1 + root) * inv; + } + else if (discr == (Real)0) + { + // The line is tangent to the cylinder. + result.intersect = true; + result.numIntersections = 1; + result.parameter[0] = -a1 / a2; + // Used by derived classes. + result.parameter[1] = result.parameter[0]; + } + // else: The line does not intersect the cylinder. + } + // else: The line is outside the planes of the cylinder end disks. + return; + } + + // Test for intersections with the planes of the end disks. + inv = ((Real)1) / D[2]; + + Real t0 = (-halfHeight - P[2]) * inv; + Real xTmp = P[0] + t0 * D[0]; + Real yTmp = P[1] + t0 * D[1]; + if (xTmp * xTmp + yTmp * yTmp <= rSqr) + { + // Plane intersection inside the top cylinder end disk. + result.parameter[result.numIntersections++] = t0; + } + + Real t1 = (+halfHeight - P[2]) * inv; + xTmp = P[0] + t1 * D[0]; + yTmp = P[1] + t1 * D[1]; + if (xTmp * xTmp + yTmp * yTmp <= rSqr) + { + // Plane intersection inside the bottom cylinder end disk. + result.parameter[result.numIntersections++] = t1; + } + + if (result.numIntersections < 2) + { + // Test for intersection with the cylinder wall. + a0 = P[0] * P[0] + P[1] * P[1] - rSqr; + a1 = P[0] * D[0] + P[1] * D[1]; + a2 = D[0] * D[0] + D[1] * D[1]; + discr = a1 * a1 - a0 * a2; + if (discr >(Real)0) + { + root = std::sqrt(discr); + inv = ((Real)1) / a2; + tValue = (-a1 - root) * inv; + if (t0 <= t1) + { + if (t0 <= tValue && tValue <= t1) + { + result.parameter[result.numIntersections++] = tValue; + } + } + else + { + if (t1 <= tValue && tValue <= t0) + { + result.parameter[result.numIntersections++] = tValue; + } + } + + if (result.numIntersections < 2) + { + tValue = (-a1 + root) * inv; + if (t0 <= t1) + { + if (t0 <= tValue && tValue <= t1) + { + result.parameter[result.numIntersections++] = tValue; + } + } + else + { + if (t1 <= tValue && tValue <= t0) + { + result.parameter[result.numIntersections++] = tValue; + } + } + } + // else: Line intersects end disk and cylinder wall. + } + else if (discr == (Real)0) + { + tValue = -a1 / a2; + if (t0 <= t1) + { + if (t0 <= tValue && tValue <= t1) + { + result.parameter[result.numIntersections++] = tValue; + } + } + else + { + if (t1 <= tValue && tValue <= t0) + { + result.parameter[result.numIntersections++] = tValue; + } + } + } + // else: Line does not intersect cylinder wall. + } + // else: Line intersects both top and bottom cylinder end disks. + + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + } + else if (result.numIntersections == 1) + { + result.intersect = true; + // Used by derived classes. + result.parameter[1] = result.parameter[0]; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Ellipsoid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Ellipsoid3.h new file mode 100644 index 000000000000..a21ead986a81 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Ellipsoid3.h @@ -0,0 +1,141 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the ellipsoid to be a solid. + +namespace gte +{ + +template +class TIQuery, Ellipsoid3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, + Ellipsoid3 const& ellipsoid); +}; + +template +class FIQuery, Ellipsoid3> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line3 const& line, + Ellipsoid3 const& ellipsoid); + +protected: + void DoQuery(Vector3 const& lineOrigin, + Vector3 const& lineDirection, Ellipsoid3 const& ellipsoid, + Result& result); +}; + + +template +typename TIQuery, Ellipsoid3>::Result +TIQuery, Ellipsoid3>::operator()( + Line3 const& line, Ellipsoid3 const& ellipsoid) +{ + // The ellipsoid is (X-K)^T*M*(X-K)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the ellipsoid equation to obtain + // a quadratic equation Q(t) = a2*t^2 + 2*a1*t + a0 = 0, where + // a2 = D^T*M*D, a1 = D^T*M*(P-K), and a0 = (P-K)^T*M*(P-K)-1. + Result result; + + Matrix3x3 M; + ellipsoid.GetM(M); + + Vector3 diff = line.origin - ellipsoid.center; + Vector3 matDir = M*line.direction; + Vector3 matDiff = M*diff; + Real a2 = Dot(line.direction, matDir); + Real a1 = Dot(line.direction, matDiff); + Real a0 = Dot(diff, matDiff) - (Real)1; + + // Intersection occurs when Q(t) has real roots. + Real discr = a1*a1 - a0*a2; + result.intersect = (discr >= (Real)0); + return result; +} + +template +typename FIQuery, Ellipsoid3>::Result +FIQuery, Ellipsoid3>::operator()( + Line3 const& line, Ellipsoid3 const& ellipsoid) +{ + Result result; + DoQuery(line.origin, line.direction, ellipsoid, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; +} + +template +void FIQuery, Ellipsoid3>::DoQuery( + Vector3 const& lineOrigin, Vector3 const& lineDirection, + Ellipsoid3 const& ellipsoid, Result& result) +{ + // The ellipsoid is (X-K)^T*M*(X-K)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the ellipsoid equation to obtain + // a quadratic equation Q(t) = a2*t^2 + 2*a1*t + a0 = 0, where + // a2 = D^T*M*D, a1 = D^T*M*(P-K), and a0 = (P-K)^T*M*(P-K)-1. + Matrix3x3 M; + ellipsoid.GetM(M); + + Vector3 diff = lineOrigin - ellipsoid.center; + Vector3 matDir = M*lineDirection; + Vector3 matDiff = M*diff; + Real a2 = Dot(lineDirection, matDir); + Real a1 = Dot(lineDirection, matDiff); + Real a0 = Dot(diff, matDiff) - (Real)1; + + // Intersection occurs when Q(t) has real roots. + Real discr = a1*a1 - a0*a2; + if (discr > (Real)0) + { + result.intersect = true; + result.numIntersections = 2; + Real root = std::sqrt(discr); + Real inv = ((Real)1) / a2; + result.parameter[0] = (-a1 - root)*inv; + result.parameter[1] = (-a1 + root)*inv; + } + else if (discr < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + else + { + result.intersect = true; + result.numIntersections = 1; + result.parameter[0] = -a1 / a2; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3OrientedBox3.h new file mode 100644 index 000000000000..c49d340d296d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3OrientedBox3.h @@ -0,0 +1,97 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2016/11/28) + +#pragma once + +#include +#include + +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the six faces of +// the box. The find-intersection queries use Liang-Barsky clipping. The +// queries consider the box to be a solid. The algorithms are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace gte +{ + template + class TIQuery, OrientedBox3> + : + public TIQuery, AlignedBox3> + { + public: + struct Result + : + public TIQuery, AlignedBox3>::Result + { + // No additional relevant information to compute. + }; + + Result operator()(Line3 const& line, OrientedBox3 const& box) + { + // Transform the line to the oriented-box coordinate system. + Vector3 diff = line.origin - box.center; + Vector3 lineOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 lineDirection + { + Dot(line.direction, box.axis[0]), + Dot(line.direction, box.axis[1]), + Dot(line.direction, box.axis[2]) + }; + + Result result; + this->DoQuery(lineOrigin, lineDirection, box.extent, result); + return result; + } + }; + + template + class FIQuery, OrientedBox3> + : + public FIQuery, AlignedBox3> + { + public: + struct Result + : + public FIQuery, AlignedBox3>::Result + { + // No additional relevant information to compute. + }; + + Result operator()(Line3 const& line, OrientedBox3 const& box) + { + // Transform the line to the oriented-box coordinate system. + Vector3 diff = line.origin - box.center; + Vector3 lineOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 lineDirection + { + Dot(line.direction, box.axis[0]), + Dot(line.direction, box.axis[1]), + Dot(line.direction, box.axis[2]) + }; + + Result result; + this->DoQuery(lineOrigin, lineDirection, box.extent, result); + for (int i = 0; i < result.numPoints; ++i) + { + result.point[i] = + line.origin + result.lineParameter[i] * line.direction; + } + return result; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Plane3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Plane3.h new file mode 100644 index 000000000000..42cd93eac7bf --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Plane3.h @@ -0,0 +1,133 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Plane3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, Plane3 const& plane); +}; + +template +class FIQuery, Plane3> +{ +public: + struct Result + { + bool intersect; + + // The number of intersections is 0 (no intersection), 1 (linear + // component and plane intersect in a point), or + // std::numeric_limits::max() (linear component is on the plane). + // If the linear component is on the plane, 'point' component's + // origin and 'parameter' is zero. + int numIntersections; + Real parameter; + Vector3 point; + }; + + Result operator()(Line3 const& line, Plane3 const& plane); + +protected: + void DoQuery(Vector3 const& lineOrigin, + Vector3 const& lineDirection, Plane3 const& plane, + Result& result); +}; + + +template +typename TIQuery, Plane3>::Result +TIQuery, Plane3>::operator()( + Line3 const& line, Plane3 const& plane) +{ + Result result; + + Real DdN = Dot(line.direction, plane.normal); + if (DdN != (Real)0) + { + // The line is not parallel to the plane, so they must intersect. + result.intersect = true; + } + else + { + // The line and plane are parallel. + DCPQuery, Plane3> vpQuery; + result.intersect = (vpQuery(line.origin, plane).distance == (Real)0); + } + + return result; +} + +template +typename FIQuery, Plane3>::Result +FIQuery, Plane3>::operator()( + Line3 const& line, Plane3 const& plane) +{ + Result result; + DoQuery(line.origin, line.direction, plane, result); + if (result.intersect) + { + result.point = line.origin + result.parameter * line.direction; + } + return result; +} + +template +void FIQuery, Plane3>::DoQuery( + Vector3 const& lineOrigin, Vector3 const& lineDirection, + Plane3 const& plane, Result& result) +{ + Real DdN = Dot(lineDirection, plane.normal); + DCPQuery, Plane3> vpQuery; + auto vpResult = vpQuery(lineOrigin, plane); + + if (DdN != (Real)0) + { + // The line is not parallel to the plane, so they must intersect. + result.intersect = true; + result.numIntersections = 1; + result.parameter = -vpResult.signedDistance / DdN; + } + else + { + // The line and plane are parallel. Determine whether the line is on + // the plane. + if (vpResult.distance == (Real)0) + { + // The line is coincident with the plane, so choose t = 0 for the + // parameter. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + result.parameter = (Real)0; + } + else + { + // The line is not on the plane. + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Sphere3.h new file mode 100644 index 000000000000..ed3e62085ea1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Sphere3.h @@ -0,0 +1,124 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, Sphere3 const& sphere); +}; + +template +class FIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line3 const& line, Sphere3 const& sphere); + +protected: + void DoQuery(Vector3 const& lineOrigin, + Vector3 const& lineDirection, Sphere3 const& sphere, + Result& result); +}; + + +template +typename TIQuery, Sphere3>::Result +TIQuery, Sphere3>::operator()( + Line3 const& line, Sphere3 const& sphere) +{ + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to obtain a + // quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where a1 = D^T*(P-C), + // and a0 = (P-C)^T*(P-C)-1. + Result result; + + Vector3 diff = line.origin - sphere.center; + Real a0 = Dot(diff, diff) - sphere.radius * sphere.radius; + Real a1 = Dot(line.direction, diff); + + // Intersection occurs when Q(t) has real roots. + Real discr = a1*a1 - a0; + result.intersect = (discr >= (Real)0); + return result; +} + +template +typename FIQuery, Sphere3>::Result +FIQuery, Sphere3>::operator()( + Line3 const& line, Sphere3 const& sphere) +{ + Result result; + DoQuery(line.origin, line.direction, sphere, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; +} + +template +void FIQuery, Sphere3>::DoQuery( + Vector3 const& lineOrigin, Vector3 const& lineDirection, + Sphere3 const& sphere, Result& result) +{ + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to obtain a + // quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where a1 = D^T*(P-C), + // and a0 = (P-C)^T*(P-C)-1. + Vector3 diff = lineOrigin - sphere.center; + Real a0 = Dot(diff, diff) - sphere.radius * sphere.radius; + Real a1 = Dot(lineDirection, diff); + + // Intersection occurs when Q(t) has real roots. + Real discr = a1*a1 - a0; + if (discr > (Real)0) + { + result.intersect = true; + result.numIntersections = 2; + Real root = std::sqrt(discr); + result.parameter[0] = -a1 - root; + result.parameter[1] = -a1 + root; + } + else if (discr < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + else + { + result.intersect = true; + result.numIntersections = 1; + result.parameter[0] = -a1; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Triangle3.h new file mode 100644 index 000000000000..3d1fdcc7d43c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Triangle3.h @@ -0,0 +1,190 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery,Triangle3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, + Triangle3 const& triangle); +}; + +template +class FIQuery, Triangle3> +{ +public: + struct Result + { + Result(); + + bool intersect; + Real parameter; + Real triangleBary[3]; + Vector3 point; + }; + + Result operator()(Line3 const& line, + Triangle3 const& triangle); +}; + + +template +typename TIQuery, Triangle3>::Result +TIQuery, Triangle3>::operator()( + Line3 const& line, Triangle3 const& triangle) +{ + Result result; + + // Compute the offset origin, edges, and normal. + Vector3 diff = line.origin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = diff, D = line direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(line.direction, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Line and triangle are parallel, call it a "no intersection" + // even if the line and triangle are coplanar and intersecting. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign*DotCross(line.direction, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign*DotCross(line.direction, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle. + result.intersect = true; + return result; + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; +} + +template +FIQuery, Triangle3>::Result::Result() + : + parameter((Real)0) +{ + triangleBary[0] = (Real)0; + triangleBary[1] = (Real)0; + triangleBary[2] = (Real)0; +} + +template +typename FIQuery, Triangle3>::Result +FIQuery, Triangle3>::operator()( + Line3 const& line, Triangle3 const& triangle) +{ + Result result; + + // Compute the offset origin, edges, and normal. + Vector3 diff = line.origin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = diff, D = line direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(line.direction, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Line and triangle are parallel, call it a "no intersection" + // even if the line and triangle are coplanar and intersecting. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign*DotCross(line.direction, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign*DotCross(line.direction, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle. + Real QdN = -sign*Dot(diff, normal); + Real inv = ((Real)1) / DdN; + + result.intersect = true; + result.parameter = QdN*inv; + result.triangleBary[1] = DdQxE2*inv; + result.triangleBary[2] = DdE1xQ*inv; + result.triangleBary[0] = (Real)1 - result.triangleBary[1] + - result.triangleBary[2]; + result.point = line.origin + + result.parameter * line.direction; + return result; + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Circle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Circle2.h new file mode 100644 index 000000000000..aaf62aaebc07 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Circle2.h @@ -0,0 +1,96 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/08/01) + +#pragma once + +#include +#include + +// The find-intersection query is based on the document +// https://www.geometrictools.com/Documentation/IntersectionMovingCircleRectangle.pdf + +namespace gte +{ + template + class TIQuery, Circle2> + { + public: + // The intersection query considers the box and circle to be solids; + // that is, the circle object includes the region inside the circular + // boundary and the box object includes the region inside the + // rectangular boundary. If the circle object and rectangle object + // overlap, the objects intersect. + struct Result + { + bool intersect; + }; + + Result operator()(OrientedBox2 const& box, Circle2 const& circle) + { + DCPQuery, OrientedBox2> pbQuery; + auto pbResult = pbQuery(circle.center, box); + Result result; + result.intersect = (pbResult.sqrDistance <= circle.radius * circle.radius); + return result; + } + }; + + template + class FIQuery, Circle2> + : + public FIQuery, Circle2> + { + public: + // See the base class for the definition of 'struct Result'. + typename FIQuery, Circle2>::Result + operator()(OrientedBox2 const& box, Vector2 const& boxVelocity, + Circle2 const& circle, Vector2 const& circleVelocity) + { + // Transform the oriented box to an axis-aligned box centered at + // the origin and transform the circle accordingly. Compute the + // velocity of the circle relative to the box. + Real const zero(0), one(1), minusOne(-1); + Vector2 cdiff = circle.center - box.center; + Vector2 vdiff = circleVelocity - boxVelocity; + Vector2 C, V; + for (int i = 0; i < 2; ++i) + { + C[i] = Dot(cdiff, box.axis[i]); + V[i] = Dot(vdiff, box.axis[i]); + } + + // Change signs on components, if necessary, to transform C to the + // first quadrant. Adjust the velocity accordingly. + Real sign[2]; + for (int i = 0; i < 2; ++i) + { + if (C[i] >= zero) + { + sign[i] = one; + } + else + { + C[i] = -C[i]; + V[i] = -V[i]; + sign[i] = minusOne; + } + } + + typename FIQuery, Circle2>::Result result = { 0, zero, { zero, zero } }; + this->DoQuery(box.extent, C, circle.radius, V, result); + + if (result.intersectionType != 0) + { + // Transform back to the original coordinate system. + result.contactPoint = box.center + + (sign[0] * result.contactPoint[0]) * box.axis[0] + + (sign[1] * result.contactPoint[1]) * box.axis[1]; + } + return result; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Cone2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Cone2.h new file mode 100644 index 000000000000..425abbfb73a2 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Cone2.h @@ -0,0 +1,113 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The queries consider the box and cone to be solids. +// +// Define V = cone.ray.origin, D = cone.ray.direction, and cs = cone.cosAngle. +// Define C = box.center, U0 = box.axis[0], U1 = box.axis[1], +// e0 = box.extent[0], and e1 = box.extent[1]. A box point is +// P = C + x*U0 + y*U1 where |x| <= e0 and |y| <= e1. Define the function +// F(P) = Dot(D, (P-V)/Length(P-V)) = F(x,y) +// = Dot(D, (x*U0 + y*U1 + (C-V))/|x*U0 + y*U1 + (C-V)| +// = (a0*x + a1*y + a2)/(x^2 + y^2 + 2*b0*x + 2*b1*y + b2)^{1/2} +// The function has an essential singularity when P = V. The box intersects +// the cone (with positive-area overlap) when at least one of the four box +// corners is strictly inside the cone. It is necessary that the numerator +// of F(P) be positive at such a corner. The (interior of the) solid cone +// is defined by the quadratic inequality +// (Dot(D,P-V))^2 > |P-V|^2*(cone.cosAngle)^2 +// This inequality is inexpensive to compute. In summary, overlap occurs +// when there is a box corner P for which +// F(P) > 0 and (Dot(D,P-V))^2 > |P-V|^2*(cone.cosAngle)^2 + +namespace gte +{ + +template +class TIQuery, Cone<2, Real>> +{ +public: + struct Result + { + // The value of 'intersect' is true when there is a box point that + // is strictly inside the cone. If the box just touches the cone + // from the outside, an intersection is not reported, which supports + // the common operation of culling objects outside a cone. + bool intersect; + }; + + Result operator()(OrientedBox<2, Real> const& box, Cone<2, Real>& cone); +}; + + +template +typename TIQuery, Cone<2, Real>>::Result +TIQuery, Cone<2, Real>>::operator()( + OrientedBox<2, Real> const& box, Cone<2, Real>& cone) +{ + Result result; + + TIQuery, OrientedBox<2, Real>> rbQuery; + auto rbResult = rbQuery(cone.ray, box); + if (rbResult.intersect) + { + // The cone intersects the box. + result.intersect = true; + return result; + } + + // Define V = cone.ray.origin, D = cone.ray.direction, and + // cs = cone.cosAngle. Define C = box.center, U0 = box.axis[0], + // U1 = box.axis[1], e0 = box.extent[0], and e1 = box.extent[1]. + // A box point is P = C + x*U0 + y*U1 where |x| <= e0 and |y| <= e1. + // Define the function + // F(x,y) = Dot(D, (P-V)/Length(P-V)) + // = Dot(D, (x*U0 + y*U1 + (C-V))/|x*U0 + y*U1 + (C-V)| + // = (a0*x + a1*y + a2)/(x^2 + y^2 + 2*b0*x + 2*b1*y + b2)^{1/2} + // The function has an essential singularity when P = V. + Vector<2, Real> diff = box.center - cone.ray.origin; + Real a0 = Dot(cone.ray.direction, box.axis[0]); + Real a1 = Dot(cone.ray.direction, box.axis[1]); + Real a2 = Dot(cone.ray.direction, diff); + Real b0 = Dot(box.axis[0], diff); + Real b1 = Dot(box.axis[1], diff); + Real b2 = Dot(diff, diff); + Real csSqr = cone.cosAngle * cone.cosAngle; + + for (int i1 = 0; i1 < 2; ++i1) + { + Real sign1 = i1 * (Real)2 - (Real)1; + Real y = sign1 * box.extent[1]; + for (int i0 = 0; i0 < 2; ++i0) + { + Real sign0 = i0 * (Real)2 - (Real)1; + Real x = sign0 * box.extent[0]; + Real fNumerator = a0 * x + a1 * y + a2; + if (fNumerator >(Real)0) + { + Real dSqr = x*x + y*y + (b0*x + b1*y)*(Real)2 + b2; + Real nSqr = fNumerator*fNumerator; + if (nSqr > dSqr * csSqr) + { + result.intersect = true; + return result; + } + } + } + } + + result.intersect = false; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2OrientedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2OrientedBox2.h new file mode 100644 index 000000000000..217881ef9600 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2OrientedBox2.h @@ -0,0 +1,294 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection query uses the method of separating axes. The set of +// potential separating directions includes the 2 edge normals of box0 and the +// 2 edge normals of box1. The integer 'separating' identifies the axis that +// reported separation; there may be more than one but only one is reported. +// The value is 0 when box0.axis[0] separates, 1 when box0.axis[1] separates, +// 2 when box1.axis[0] separates, or 3 when box1.axis[1] separates. + +namespace gte +{ + +template +class TIQuery, OrientedBox2> +{ +public: + struct Result + { + bool intersect; + int separating; + }; + + Result operator()(OrientedBox2 const& box0, + OrientedBox2 const& box1); +}; + +template +class FIQuery, OrientedBox2> +{ +public: + struct Result + { + bool intersect; + + // If 'intersect' is true, the boxes intersect in a convex 'polygon'. + std::vector> polygon; + }; + + Result operator()(OrientedBox2 const& box0, + OrientedBox2 const& box1); + +private: + // The line normals are inner pointing. The function returns true + // when the incoming polygon is outside the line, in which case the + // boxes do not intersect. If the function returns false, the outgoing + // polygon is the incoming polygon intersected with the closed halfspace + // defined by the line. + bool Outside(Vector2 const& origin, Vector2 const& normal, + std::vector>& polygon); +}; + + +template +typename TIQuery, OrientedBox2>::Result +TIQuery, OrientedBox2>::operator()( + OrientedBox2 const& box0, OrientedBox2 const& box1) +{ + Result result; + + // Convenience variables. + Vector2 const* A0 = &box0.axis[0]; + Vector2 const* A1 = &box1.axis[0]; + Vector2 const& E0 = box0.extent; + Vector2 const& E1 = box1.extent; + + // Compute difference of box centers, D = C1-C0. + Vector2 D = box1.center - box0.center; + + Real absA0dA1[2][2], rSum; + + // Test box0.axis[0]. + absA0dA1[0][0] = std::abs(Dot(A0[0], A1[0])); + absA0dA1[0][1] = std::abs(Dot(A0[0], A1[1])); + rSum = E0[0] + E1[0] * absA0dA1[0][0] + E1[1] * absA0dA1[0][1]; + if (std::abs(Dot(A0[0], D)) > rSum) + { + result.intersect = false; + result.separating = 0; + return result; + } + + // Test axis box0.axis[1]. + absA0dA1[1][0] = std::abs(Dot(A0[1], A1[0])); + absA0dA1[1][1] = std::abs(Dot(A0[1], A1[1])); + rSum = E0[1] + E1[0] * absA0dA1[1][0] + E1[1] * absA0dA1[1][1]; + if (std::abs(Dot(A0[1], D)) > rSum) + { + result.intersect = false; + result.separating = 1; + return result; + } + + // Test axis box1.axis[0]. + rSum = E1[0] + E0[0] * absA0dA1[0][0] + E0[1] * absA0dA1[1][0]; + if (std::abs(Dot(A1[0], D)) > rSum) + { + result.intersect = false; + result.separating = 2; + return result; + } + + // Test axis box1.axis[1]. + rSum = E1[1] + E0[0] * absA0dA1[0][1] + E0[1] * absA0dA1[1][1]; + if (std::abs(Dot(A1[1], D)) > rSum) + { + result.intersect = false; + result.separating = 3; + return result; + } + + result.intersect = true; + return result; +} + +template +typename FIQuery, OrientedBox2>::Result +FIQuery, OrientedBox2>::operator()( + OrientedBox2 const& box0, OrientedBox2 const& box1) +{ + Result result; + result.intersect = true; + + // Initialize the intersection polygon to box0, listing the vertices + // in counterclockwise order. + std::array, 4> vertex; + box0.GetVertices(vertex); + result.polygon.push_back(vertex[0]); // C - e0 * U0 - e1 * U1 + result.polygon.push_back(vertex[1]); // C + e0 * U0 - e1 * U1 + result.polygon.push_back(vertex[3]); // C + e0 * U0 + e1 * U1 + result.polygon.push_back(vertex[2]); // C - e0 * U0 + e1 * U1 + + // Clip the polygon using the lines defining edges of box1. The + // line normal points inside box1. The line origin is the first + // vertex of the edge when traversing box1 counterclockwise. + box1.GetVertices(vertex); + std::array, 4> normal = + { + box1.axis[1], -box1.axis[0], box1.axis[0], -box1.axis[1] + }; + + for (int i = 0; i < 4; ++i) + { + if (Outside(vertex[i], normal[i], result.polygon)) + { + // The boxes are separated. + result.intersect = false; + result.polygon.clear(); + break; + } + } + + return result; +} + +template +bool FIQuery, OrientedBox2>::Outside( + Vector2 const& origin, Vector2 const& normal, + std::vector>& polygon) +{ + // Determine whether the polygon vertices are outside the polygon, inside + // the polygon, or on the polygon boundary. + int const numVertices = static_cast(polygon.size()); + std::vector distance(numVertices); + int positive = 0, negative = 0, positiveIndex = -1; + for (int i = 0; i < numVertices; ++i) + { + distance[i] = Dot(normal, polygon[i] - origin); + if (distance[i] > (Real)0) + { + ++positive; + if (positiveIndex == -1) + { + positiveIndex = i; + } + } + else if (distance[i] < (Real)0) + { + ++negative; + } + } + + if (positive == 0) + { + // The polygon is strictly outside the line. + return true; + } + + if (negative == 0) + { + // The polygon is contained in the closed halfspace whose boundary + // is the line. It is fully visible and no clipping is necessary. + return false; + } + + // The line transversely intersects the polygon. Clip the polygon. + std::vector> clipPolygon; + Vector2 vertex; + int curr, prev; + Real t; + + if (positiveIndex > 0) + { + // Compute the first clip vertex on the line. + curr = positiveIndex; + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + clipPolygon.push_back(vertex); + + // Include the vertices on the positive side of line. + while (curr < numVertices && distance[curr] > (Real)0) + { + clipPolygon.push_back(polygon[curr++]); + } + + // Compute the kast clip vertex on the line. + if (curr < numVertices) + { + prev = curr - 1; + } + else + { + curr = 0; + prev = numVertices - 1; + } + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + clipPolygon.push_back(vertex); + } + else // positiveIndex is 0 + { + // Include the vertices on the positive side of line. + curr = 0; + while (curr < numVertices && distance[curr] > (Real)0) + { + clipPolygon.push_back(polygon[curr++]); + } + + // Compute the last clip vertex on the line. + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + clipPolygon.push_back(vertex); + + // Skip the vertices on the negative side of the line. + while (curr < numVertices && distance[curr] <= (Real)0) + { + curr++; + } + + // Compute the first clip vertex on the line. + if (curr < numVertices) + { + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + clipPolygon.push_back(vertex); + + // Keep the vertices on the positive side of the line. + while (curr < numVertices && distance[curr] > (Real)0) + { + clipPolygon.push_back(polygon[curr++]); + } + } + else + { + curr = 0; + prev = numVertices - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + clipPolygon.push_back(vertex); + } + } + + polygon = clipPolygon; + return false; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Sector2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Sector2.h new file mode 100644 index 000000000000..20bd52cb88a6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Sector2.h @@ -0,0 +1,136 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/04) + +#pragma once + +#include +#include +#include +#include +#include + +// The OrientedBox2 object is considered to be a solid. + +namespace gte +{ + +template +class TIQuery, Sector2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(OrientedBox2 const& box, Sector2 const& sector); +}; + +template +typename TIQuery, Sector2>::Result +TIQuery, Sector2>::operator()( + OrientedBox2 const& box, Sector2 const& sector) +{ + Result result; + + // Determine whether the vertex is inside the box. + Vector2 CmV = box.center - sector.vertex; + Vector2 P{ Dot(box.axis[0], CmV), Dot(box.axis[1], CmV) }; + if (std::abs(P[0]) <= box.extent[0] && std::abs(P[1]) <= box.extent[1]) + { + // The vertex is inside the box. + result.intersect = true; + return result; + } + + // Test whether the box is outside the right ray boundary of the sector. + Vector2 U0 + { + +sector.cosAngle * sector.direction[0] + sector.sinAngle * sector.direction[1], + -sector.sinAngle * sector.direction[0] + sector.cosAngle * sector.direction[1] + }; + Vector2 N0 = Perp(U0); + Real prjcen0 = Dot(N0, CmV); + Real radius0 = box.extent[0] * std::abs(Dot(N0, box.axis[0])) + + box.extent[1] * std::abs(Dot(N0, box.axis[1])); + if (prjcen0 > radius0) + { + result.intersect = false; + return result; + } + + // Test whether the box is outside the ray of the left boundary of the + // sector. + Vector2 U1 + { + +sector.cosAngle * sector.direction[0] - sector.sinAngle * sector.direction[1], + +sector.sinAngle * sector.direction[0] + sector.cosAngle * sector.direction[1] + }; + Vector2 N1 = -Perp(U1); + Real prjcen1 = Dot(N1, CmV); + Real radius1 = box.extent[0] * std::abs(Dot(N1, box.axis[0])) + + box.extent[1] * std::abs(Dot(N1, box.axis[1])); + if (prjcen1 > radius1) + { + result.intersect = false; + return result; + } + + // Initialize the polygon of intersection to be the box. + Vector2 e0U0 = box.extent[0] * box.axis[0]; + Vector2 e1U1 = box.extent[1] * box.axis[1]; + std::vector> polygon; + polygon.reserve(8); + polygon.push_back(box.center - e0U0 - e1U1); + polygon.push_back(box.center + e0U0 - e1U1); + polygon.push_back(box.center + e0U0 + e1U1); + polygon.push_back(box.center - e0U0 + e1U1); + + FIQuery, std::vector>> hpQuery; + typename FIQuery, std::vector>>::Result hpResult; + Halfspace<2, Real> halfspace; + + // Clip the box against the right-ray sector boundary. + if (prjcen0 >= -radius0) + { + halfspace.normal = -N0; + halfspace.constant = Dot(halfspace.normal, sector.vertex); + hpResult = hpQuery(halfspace, polygon); + polygon = std::move(hpResult.polygon); + } + + // Clip the box against the left-ray sector boundary. + if (prjcen1 >= -radius1) + { + halfspace.normal = -N1; + halfspace.constant = Dot(halfspace.normal, sector.vertex); + hpResult = hpQuery(halfspace, polygon); + polygon = std::move(hpResult.polygon); + } + + DCPQuery, Segment2> psQuery; + typename DCPQuery, Segment2>::Result psResult; + int const numVertices = static_cast(polygon.size()); + if (numVertices >= 2) + { + for (int i0 = numVertices - 1, i1 = 0; i1 < numVertices; i0 = i1++) + { + Segment2 segment(polygon[i0], polygon[i1]); + psResult = psQuery(sector.vertex, segment); + if (psResult.distance <= sector.radius) + { + result.intersect = true; + return result; + } + } + } + + result.intersect = false; + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Cone3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Cone3.h new file mode 100644 index 000000000000..534d6593e390 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Cone3.h @@ -0,0 +1,79 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/11/29) + +#pragma once + +#include +#include +#include + +// Test for intersection of a box and a cone. The cone can be infinite +// 0 <= minHeight < maxHeight = std::numeric_limits::max() +// or finite (cone frustum) +// 0 <= minHeight < maxHeight < std::numeric_limits::max(). +// The algorithm is described in +// http://www.geometrictools.com/Documentation/IntersectionBoxCone.pdf +// and reports an intersection only when th intersection set has positive +// volume. For example, let the box be outside the cone. If the box is +// below the minHeight plane at the cone vertex and just touches the cone +// vertex, no intersection is reported. If the box is above the maxHeight +// plane and just touches the disk capping the cone, either at a single +// point, a line segment of points or a polygon of points, no intersection +// is reported. However, if the box straddles the minHeight plane (part of +// the box strictly above the plane and part of the box strictly below the +// plane) and just touches the cone vertex, an intersection is reported. + +namespace gte +{ + template + class TIQuery, Cone<3, Real>> + : + public TIQuery, Cone<3, Real>> + { + public: + struct Result + : + public TIQuery, Cone<3, Real>>::Result + { + // No additional information to compute. + }; + + Result operator()(OrientedBox<3, Real> const& box, Cone<3, Real> const& cone) + { + // Transform the cone and box so that the cone vertex is at the + // origin and the box is axis aligned. This allows us to call the + // base class operator()(...). + Vector<3, Real> diff = box.center - cone.ray.origin; + Vector<3, Real> xfrmBoxCenter + { + Dot(box.axis[0], diff), + Dot(box.axis[1], diff), + Dot(box.axis[2], diff) + }; + AlignedBox<3, Real> xfrmBox; + xfrmBox.min = xfrmBoxCenter - box.extent; + xfrmBox.max = xfrmBoxCenter + box.extent; + + Cone<3, Real> xfrmCone = cone; + for (int i = 0; i < 3; ++i) + { + xfrmCone.ray.origin[i] = (Real)0; + xfrmCone.ray.direction[i] = Dot(box.axis[i], cone.ray.direction); + } + + // Test for intersection between the aligned box and the cone. + auto bcResult = TIQuery, Cone<3, Real>>::operator()(xfrmBox, xfrmCone); + Result result; + result.intersect = bcResult.intersect; + return result; + } + }; + + // Template alias for convenience. + template + using TIOrientedBox3Cone3 = TIQuery, Cone<3, Real>>; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Cylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Cylinder3.h new file mode 100644 index 000000000000..ff0f6c153795 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Cylinder3.h @@ -0,0 +1,55 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.4.0 (2016/11/08) + +#pragma once + +#include +#include + +// The query considers the cylinder and box to be solids. + +namespace gte +{ + +template +class TIQuery, Cylinder3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(OrientedBox3 const& box, Cylinder3 const& cylinder); +}; + + +template +typename TIQuery, Cylinder3>::Result +TIQuery, Cylinder3>::operator()( + OrientedBox3 const& box, Cylinder3 const& cylinder) +{ + // Transform the box and cylinder so that the box is axis-aligned. + AlignedBox3 aabb(-box.extent, box.extent); + Vector3 diff = cylinder.axis.origin - box.center; + Cylinder3 transformedCylinder; + transformedCylinder.radius = cylinder.radius; + transformedCylinder.height = cylinder.height; + for (int i = 0; i < 3; ++i) + { + transformedCylinder.axis.origin[i] = Dot(box.axis[i], diff); + transformedCylinder.axis.direction[i] = Dot(box.axis[i], cylinder.axis.direction); + } + + TIQuery, Cylinder3> aabbCylinderQuery; + auto aabbCylinderResult = aabbCylinderQuery(aabb, transformedCylinder); + Result result; + result.intersect = aabbCylinderResult.intersect; + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Frustum3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Frustum3.h new file mode 100644 index 000000000000..6887725f644c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Frustum3.h @@ -0,0 +1,373 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +// The method of separating axes is used. The potential separating axes +// include the 3 box face normals, the 5 distinct frustum normals (near and +// far plane have the same normal), and cross products of normals, one from +// the box and one from the frustum. + +template +class TIQuery, Frustum3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(OrientedBox3 const& box, + Frustum3 const& frustum); +}; + + +template +typename TIQuery, Frustum3>::Result +TIQuery, Frustum3>::operator()( + OrientedBox3 const& box, Frustum3 const& frustum) +{ + Result result; + + // Convenience variables. + Vector3 const* axis = &box.axis[0]; + Vector3 const& extent = box.extent; + + Vector3 diff = box.center - frustum.origin; // C-E + + Real A[3]; // Dot(R,A[i]) + Real B[3]; // Dot(U,A[i]) + Real C[3]; // Dot(D,A[i]) + Real D[3]; // (Dot(R,C-E),Dot(U,C-E),Dot(D,C-E)) + Real NA[3]; // dmin*Dot(R,A[i]) + Real NB[3]; // dmin*Dot(U,A[i]) + Real NC[3]; // dmin*Dot(D,A[i]) + Real ND[3]; // dmin*(Dot(R,C-E),Dot(U,C-E),?) + Real RC[3]; // rmax*Dot(D,A[i]) + Real RD[3]; // rmax*(?,?,Dot(D,C-E)) + Real UC[3]; // umax*Dot(D,A[i]) + Real UD[3]; // umax*(?,?,Dot(D,C-E)) + Real NApRC[3]; // dmin*Dot(R,A[i]) + rmax*Dot(D,A[i]) + Real NAmRC[3]; // dmin*Dot(R,A[i]) - rmax*Dot(D,A[i]) + Real NBpUC[3]; // dmin*Dot(U,A[i]) + umax*Dot(D,A[i]) + Real NBmUC[3]; // dmin*Dot(U,A[i]) - umax*Dot(D,A[i]) + Real RBpUA[3]; // rmax*Dot(U,A[i]) + umax*Dot(R,A[i]) + Real RBmUA[3]; // rmax*Dot(U,A[i]) - umax*Dot(R,A[i]) + Real DdD, radius, p, fmin, fmax, MTwoUF, MTwoRF, tmp; + int i, j; + + // M = D + D[2] = Dot(diff, frustum.dVector); + for (i = 0; i < 3; ++i) + { + C[i] = Dot(axis[i], frustum.dVector); + } + radius = + extent[0] * std::abs(C[0]) + + extent[1] * std::abs(C[1]) + + extent[2] * std::abs(C[2]); + if (D[2] + radius < frustum.dMin + || D[2] - radius > frustum.dMax) + { + result.intersect = false; + return result; + } + + // M = n*R - r*D + for (i = 0; i < 3; ++i) + { + A[i] = Dot(axis[i], frustum.rVector); + RC[i] = frustum.rBound*C[i]; + NA[i] = frustum.dMin*A[i]; + NAmRC[i] = NA[i] - RC[i]; + } + D[0] = Dot(diff, frustum.rVector); + radius = + extent[0] * std::abs(NAmRC[0]) + + extent[1] * std::abs(NAmRC[1]) + + extent[2] * std::abs(NAmRC[2]); + ND[0] = frustum.dMin*D[0]; + RD[2] = frustum.rBound*D[2]; + DdD = ND[0] - RD[2]; + MTwoRF = frustum.GetMTwoRF(); + if (DdD + radius < MTwoRF || DdD > radius) + { + result.intersect = false; + return result; + } + + // M = -n*R - r*D + for (i = 0; i < 3; ++i) + { + NApRC[i] = NA[i] + RC[i]; + } + radius = + extent[0] * std::abs(NApRC[0]) + + extent[1] * std::abs(NApRC[1]) + + extent[2] * std::abs(NApRC[2]); + DdD = -(ND[0] + RD[2]); + if (DdD + radius < MTwoRF || DdD > radius) + { + result.intersect = false; + return result; + } + + // M = n*U - u*D + for (i = 0; i < 3; ++i) + { + B[i] = Dot(axis[i], frustum.uVector); + UC[i] = frustum.uBound*C[i]; + NB[i] = frustum.dMin*B[i]; + NBmUC[i] = NB[i] - UC[i]; + } + D[1] = Dot(diff, frustum.uVector); + radius = + extent[0] * std::abs(NBmUC[0]) + + extent[1] * std::abs(NBmUC[1]) + + extent[2] * std::abs(NBmUC[2]); + ND[1] = frustum.dMin*D[1]; + UD[2] = frustum.uBound*D[2]; + DdD = ND[1] - UD[2]; + MTwoUF = frustum.GetMTwoUF(); + if (DdD + radius < MTwoUF || DdD > radius) + { + result.intersect = false; + return result; + } + + // M = -n*U - u*D + for (i = 0; i < 3; ++i) + { + NBpUC[i] = NB[i] + UC[i]; + } + radius = + extent[0] * std::abs(NBpUC[0]) + + extent[1] * std::abs(NBpUC[1]) + + extent[2] * std::abs(NBpUC[2]); + DdD = -(ND[1] + UD[2]); + if (DdD + radius < MTwoUF || DdD > radius) + { + result.intersect = false; + return result; + } + + // M = A[i] + for (i = 0; i < 3; ++i) + { + p = frustum.rBound*std::abs(A[i]) + + frustum.uBound*std::abs(B[i]); + NC[i] = frustum.dMin*C[i]; + fmin = NC[i] - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = NC[i] + p; + if (fmax >(Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = A[i] * D[0] + B[i] * D[1] + C[i] * D[2]; + if (DdD + extent[i] < fmin || DdD - extent[i] > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(R,A[i]) + for (i = 0; i < 3; ++i) + { + p = frustum.uBound*std::abs(C[i]); + fmin = -NB[i] - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = -NB[i] + p; + if (fmax >(Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = C[i] * D[1] - B[i] * D[2]; + radius = + extent[0] * std::abs(B[i] * C[0] - B[0] * C[i]) + + extent[1] * std::abs(B[i] * C[1] - B[1] * C[i]) + + extent[2] * std::abs(B[i] * C[2] - B[2] * C[i]); + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(U,A[i]) + for (i = 0; i < 3; ++i) + { + p = frustum.rBound*std::abs(C[i]); + fmin = NA[i] - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = NA[i] + p; + if (fmax >(Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = -C[i] * D[0] + A[i] * D[2]; + radius = + extent[0] * std::abs(A[i] * C[0] - A[0] * C[i]) + + extent[1] * std::abs(A[i] * C[1] - A[1] * C[i]) + + extent[2] * std::abs(A[i] * C[2] - A[2] * C[i]); + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(n*D+r*R+u*U,A[i]) + for (i = 0; i < 3; ++i) + { + Real fRB = frustum.rBound*B[i]; + Real fUA = frustum.uBound*A[i]; + RBpUA[i] = fRB + fUA; + RBmUA[i] = fRB - fUA; + } + for (i = 0; i < 3; ++i) + { + p = frustum.rBound*std::abs(NBmUC[i]) + + frustum.uBound*std::abs(NAmRC[i]); + tmp = -frustum.dMin*RBmUA[i]; + fmin = tmp - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = tmp + p; + if (fmax >(Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = D[0] * NBmUC[i] - D[1] * NAmRC[i] - D[2] * RBmUA[i]; + radius = (Real)0; + for (j = 0; j < 3; j++) + { + radius += extent[j] * std::abs(A[j] * NBmUC[i] - + B[j] * NAmRC[i] - C[j] * RBmUA[i]); + } + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(n*D+r*R-u*U,A[i]) + for (i = 0; i < 3; ++i) + { + p = frustum.rBound*std::abs(NBpUC[i]) + + frustum.uBound*std::abs(NAmRC[i]); + tmp = -frustum.dMin*RBpUA[i]; + fmin = tmp - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = tmp + p; + if (fmax >(Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = D[0] * NBpUC[i] - D[1] * NAmRC[i] - D[2] * RBpUA[i]; + radius = (Real)0; + for (j = 0; j < 3; ++j) + { + radius += extent[j] * std::abs(A[j] * NBpUC[i] - + B[j] * NAmRC[i] - C[j] * RBpUA[i]); + } + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(n*D-r*R+u*U,A[i]) + for (i = 0; i < 3; ++i) + { + p = frustum.rBound*std::abs(NBmUC[i]) + + frustum.uBound*std::abs(NApRC[i]); + tmp = frustum.dMin*RBpUA[i]; + fmin = tmp - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = tmp + p; + if (fmax >(Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = D[0] * NBmUC[i] - D[1] * NApRC[i] + D[2] * RBpUA[i]; + radius = (Real)0; + for (j = 0; j < 3; ++j) + { + radius += extent[j] * std::abs(A[j] * NBmUC[i] - + B[j] * NApRC[i] + C[j] * RBpUA[i]); + } + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(n*D-r*R-u*U,A[i]) + for (i = 0; i < 3; ++i) + { + p = frustum.rBound*std::abs(NBpUC[i]) + + frustum.uBound*std::abs(NApRC[i]); + tmp = frustum.dMin*RBmUA[i]; + fmin = tmp - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = tmp + p; + if (fmax >(Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = D[0] * NBpUC[i] - D[1] * NApRC[i] + D[2] * RBmUA[i]; + radius = (Real)0; + for (j = 0; j < 3; ++j) + { + radius += extent[j] * std::abs(A[j] * NBpUC[i] - + B[j] * NApRC[i] + C[j] * RBmUA[i]); + } + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + result.intersect = true; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3OrientedBox3.h new file mode 100644 index 000000000000..656d8cd3a46f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3OrientedBox3.h @@ -0,0 +1,336 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection query uses the method of separating axes. The set of +// potential separating directions includes the 3 face normals of box0, the 3 +// face normals of box1, and 9 directions, each of which is the cross product +// of an edge of box0 and and an edge of box1. +// +// The separating axes involving cross products of edges has numerical +// robustness problems when the two edges are nearly parallel. The cross +// product of the edges is nearly the zero vector, so normalization of the +// cross product may produce unit-length directions that are not close to the +// true direction. Such a pair of edges occurs when a box0 face normal N0 and +// a box1 face normal N1 are nearly parallel. In this case, you may skip the +// edge-edge directions, which is equivalent to projecting the boxes onto the +// plane with normal N0 and applying a 2D separating axis test. The ability +// to do so involves choosing a small nonnegative epsilon . It is used to +// determine whether two face normals, one from each box, are nearly parallel: +// |Dot(N0,N1)| >= 1 - epsilon. If the input is negative, it is clamped to +// zero. +// +// The pair of integers 'separating', say, (i0,i1), identify the axis that +// reported separation; there may be more than one but only one is +// reported. If the separating axis is a face normal N[i0] of the aligned +// box0 in dimension i0, then (i0,-1) is returned. If the axis is a face +// normal box1.Axis[i1], then (-1,i1) is returned. If the axis is a cross +// product of edges, Cross(N[i0],box1.Axis[i1]), then (i0,i1) is returned. + +namespace gte +{ + +template +class TIQuery, OrientedBox3> +{ +public: + struct Result + { + // The 'epsilon' value must be nonnegative. + Result(Real inEpsilon = (Real)0); + + bool intersect; + Real epsilon; + int separating[2]; + }; + + Result operator()(OrientedBox3 const& box0, + OrientedBox3 const& box1); +}; + + +template +TIQuery, OrientedBox3>::Result:: +Result(Real inEpsilon) +{ + if (inEpsilon >= (Real)0) + { + epsilon = inEpsilon; + } + else + { + epsilon = (Real)0; + } +} + +template +typename TIQuery, OrientedBox3>::Result +TIQuery, OrientedBox3>::operator()( + OrientedBox3 const& box0, OrientedBox3 const& box1) +{ + Result result; + + // Convenience variables. + Vector3 const& C0 = box0.center; + Vector3 const* A0 = &box0.axis[0]; + Vector3 const& E0 = box0.extent; + Vector3 const& C1 = box1.center; + Vector3 const* A1 = &box1.axis[0]; + Vector3 const& E1 = box1.extent; + + const Real cutoff = (Real)1 - result.epsilon; + bool existsParallelPair = false; + + // Compute difference of box centers. + Vector3 D = C1 - C0; + + Real dot01[3][3]; // dot01[i][j] = Dot(A0[i],A1[j]) = A1[j][i] + Real absDot01[3][3]; // |dot01[i][j]| + Real dotDA0[3]; // Dot(D, A0[i]) + Real r0, r1, r; // interval radii and distance between centers + Real r01; // r0 + r1 + + // Test for separation on the axis C0 + t*A0[0]. + for (int i = 0; i < 3; ++i) + { + dot01[0][i] = Dot(A0[0], A1[i]); + absDot01[0][i] = std::abs(dot01[0][i]); + if (absDot01[0][i] > cutoff) + { + existsParallelPair = true; + } + } + dotDA0[0] = Dot(D, A0[0]); + r = std::abs(dotDA0[0]); + r1 = E1[0] * absDot01[0][0] + E1[1] * absDot01[0][1] + E1[2] * absDot01[0][2]; + r01 = E0[0] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]. + for (int i = 0; i < 3; ++i) + { + dot01[1][i] = Dot(A0[1], A1[i]); + absDot01[1][i] = std::abs(dot01[1][i]); + if (absDot01[1][i] > cutoff) + { + existsParallelPair = true; + } + } + dotDA0[1] = Dot(D, A0[1]); + r = std::abs(dotDA0[1]); + r1 = E1[0] * absDot01[1][0] + E1[1] * absDot01[1][1] + E1[2] * absDot01[1][2]; + r01 = E0[1] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]. + for (int i = 0; i < 3; ++i) + { + dot01[2][i] = Dot(A0[2], A1[i]); + absDot01[2][i] = std::abs(dot01[2][i]); + if (absDot01[2][i] > cutoff) + { + existsParallelPair = true; + } + } + dotDA0[2] = Dot(D, A0[2]); + r = std::abs(dotDA0[2]); + r1 = E1[0] * absDot01[2][0] + E1[1] * absDot01[2][1] + E1[2] * absDot01[2][2]; + r01 = E0[2] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A1[0]. + r = std::abs(Dot(D, A1[0])); + r0 = E0[0] * absDot01[0][0] + E0[1] * absDot01[1][0] + E0[2] * absDot01[2][0]; + r01 = r0 + E1[0]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A1[1]. + r = std::abs(Dot(D, A1[1])); + r0 = E0[0] * absDot01[0][1] + E0[1] * absDot01[1][1] + E0[2] * absDot01[2][1]; + r01 = r0 + E1[1]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A1[2]. + r = std::abs(Dot(D, A1[2])); + r0 = E0[0] * absDot01[0][2] + E0[1] * absDot01[1][2] + E0[2] * absDot01[2][2]; + r01 = r0 + E1[2]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 2; + return result; + } + + // At least one pair of box axes was parallel, so the separation is + // effectively in 2D. The edge-edge axes do not need to be tested. + if (existsParallelPair) + { + return true; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[0]. + r = std::abs(dotDA0[2] * dot01[1][0] - dotDA0[1] * dot01[2][0]); + r0 = E0[1] * absDot01[2][0] + E0[2] * absDot01[1][0]; + r1 = E1[1] * absDot01[0][2] + E1[2] * absDot01[0][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[1]. + r = std::abs(dotDA0[2] * dot01[1][1] - dotDA0[1] * dot01[2][1]); + r0 = E0[1] * absDot01[2][1] + E0[2] * absDot01[1][1]; + r1 = E1[0] * absDot01[0][2] + E1[2] * absDot01[0][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[2]. + r = std::abs(dotDA0[2] * dot01[1][2] - dotDA0[1] * dot01[2][2]); + r0 = E0[1] * absDot01[2][2] + E0[2] * absDot01[1][2]; + r1 = E1[0] * absDot01[0][1] + E1[1] * absDot01[0][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 2; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[0]. + r = std::abs(dotDA0[0] * dot01[2][0] - dotDA0[2] * dot01[0][0]); + r0 = E0[0] * absDot01[2][0] + E0[2] * absDot01[0][0]; + r1 = E1[1] * absDot01[1][2] + E1[2] * absDot01[1][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[1]. + r = std::abs(dotDA0[0] * dot01[2][1] - dotDA0[2] * dot01[0][1]); + r0 = E0[0] * absDot01[2][1] + E0[2] * absDot01[0][1]; + r1 = E1[0] * absDot01[1][2] + E1[2] * absDot01[1][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[2]. + r = std::abs(dotDA0[0] * dot01[2][2] - dotDA0[2] * dot01[0][2]); + r0 = E0[0] * absDot01[2][2] + E0[2] * absDot01[0][2]; + r1 = E1[0] * absDot01[1][1] + E1[1] * absDot01[1][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 2; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[0]. + r = std::abs(dotDA0[1] * dot01[0][0] - dotDA0[0] * dot01[1][0]); + r0 = E0[0] * absDot01[1][0] + E0[1] * absDot01[0][0]; + r1 = E1[1] * absDot01[2][2] + E1[2] * absDot01[2][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[1]. + r = std::abs(dotDA0[1] * dot01[0][1] - dotDA0[0] * dot01[1][1]); + r0 = E0[0] * absDot01[1][1] + E0[1] * absDot01[0][1]; + r1 = E1[0] * absDot01[2][2] + E1[2] * absDot01[2][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[2]. + r = std::abs(dotDA0[1] * dot01[0][2] - dotDA0[0] * dot01[1][2]); + r0 = E0[0] * absDot01[1][2] + E0[1] * absDot01[0][2]; + r1 = E1[0] * absDot01[2][1] + E1[1] * absDot01[2][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 2; + return result; + } + + result.intersect = true; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Sphere3.h new file mode 100644 index 000000000000..b30bb1d95419 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Sphere3.h @@ -0,0 +1,94 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/02) + +#pragma once + +#include +#include + +namespace gte +{ + template + class TIQuery, Sphere3> + : + public TIQuery, Sphere3> + { + public: + // The intersection query considers the box and sphere to be solids. + // For example, if the sphere is strictly inside the box (does not touch + // the box faces), the objects intersect. + struct Result + : + public TIQuery, Sphere3>::Result + { + // No additional information to compute. + }; + + Result operator()(OrientedBox3 const& box, Sphere3 const& sphere) + { + DCPQuery, OrientedBox3> pbQuery; + auto pbResult = pbQuery(sphere.center, box); + Result result; + result.intersect = (pbResult.sqrDistance <= sphere.radius * sphere.radius); + return result; + } + }; + + template + class FIQuery, Sphere3> + : + public FIQuery, Sphere3> + { + public: + // Currently, only a dynamic query is supported. The static query must + // compute the intersection set of (solid) box and sphere. + struct Result + : + public FIQuery, Sphere3>::Result + { + // No additional information to compute. + }; + + Result operator()(OrientedBox3 const& box, Vector3 const& boxVelocity, + Sphere3 const& sphere, Vector3 const& sphereVelocity) + { + Result result; + result.intersectionType = 0; + result.contactTime = (Real)0; + result.contactPoint = { (Real)0, (Real)0, (Real)0 }; + + // Transform the sphere and box so that the box center becomes + // the origin and the box is axis aligned. Compute the velocity + // of the sphere relative to the box. + Vector3 temp = sphere.center - box.center; + Vector3 C + { + Dot(temp, box.axis[0]), + Dot(temp, box.axis[1]), + Dot(temp, box.axis[2]) + }; + + temp = sphereVelocity - boxVelocity; + Vector3 V + { + Dot(temp, box.axis[0]), + Dot(temp, box.axis[1]), + Dot(temp, box.axis[2]) + }; + + this->DoQuery(box.extent, C, sphere.radius, V, result); + + // Transform back to the original coordinate system. + if (result.intersectionType != 0) + { + auto& P = result.contactPoint; + P = box.center + P[0] * box.axis[0] + P[1] * box.axis[1] + P[2] * box.axis[2]; + } + return result; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Capsule3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Capsule3.h new file mode 100644 index 000000000000..9a1d554de29e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Capsule3.h @@ -0,0 +1,58 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Capsule3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane, + Capsule3 const& capsule); +}; + + +template +typename TIQuery, Capsule3>::Result +TIQuery, Capsule3>::operator()( + Plane3 const& plane, Capsule3 const& capsule) +{ + Result result; + + DCPQuery, Plane3> vpQuery; + Real sdistance0 = vpQuery(capsule.segment.p[0], plane).signedDistance; + Real sdistance1 = vpQuery(capsule.segment.p[1], plane).signedDistance; + if (sdistance0 * sdistance1 <= (Real)0) + { + // A capsule segment endpoint is on the plane or the two endpoints + // are on opposite sides of the plane. + result.intersect = true; + return result; + } + + // The endpoints on same side of plane, but the endpoint spheres might + // intersect the plane. + result.intersect = + std::abs(sdistance0) <= capsule.radius || + std::abs(sdistance1) <= capsule.radius; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Circle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Circle3.h new file mode 100644 index 000000000000..2a9022a46d6a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Circle3.h @@ -0,0 +1,166 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class TIQuery, Circle3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane, Circle3 const& circle); +}; + +template +class FIQuery, Circle3> +{ +public: + struct Result + { + bool intersect; + + // If 'intersect' is true, the intersection is either 1 or 2 points + // or the entire circle. When points, 'numIntersections' and + // 'point' are valid. When a circle, 'circle' is set to the incoming + // circle. + bool isPoints; + int numIntersections; + Vector3 point[2]; + Circle3 circle; + }; + + Result operator()(Plane3 const& plane, Circle3 const& circle); +}; + + +template +typename TIQuery, Circle3>::Result +TIQuery, Circle3>::operator()( + Plane3 const& plane, Circle3 const& circle) +{ + Result result; + + // Construct the plane of the circle. + Plane3 cPlane(circle.normal, circle.center); + + // Compute the intersection of this plane with the input plane. + FIQuery, Plane3> ppQuery; + auto ppResult = ppQuery(plane, cPlane); + if (!ppResult.intersect) + { + // Planes are parallel and nonintersecting. + result.intersect = false; + return result; + } + + if (!ppResult.isLine) + { + // Planes are the same, the circle is the common intersection set. + result.intersect = true; + return result; + } + + // The planes intersect in a line. Locate one or two points that are on + // the circle and line. If the line is t*D+P, the circle center is C, + // and the circle radius is r, then + // r^2 = |t*D+P-C|^2 = |D|^2*t^2 + 2*Dot(D,P-C)*t + |P-C|^2 + // This is a quadratic equation of the form a2*t^2 + 2*a1*t + a0 = 0. + Vector3 diff = ppResult.line.origin - circle.center; + Real a2 = Dot(ppResult.line.direction, ppResult.line.direction); + Real a1 = Dot(diff, ppResult.line.direction); + Real a0 = Dot(diff, diff) - circle.radius*circle.radius; + + // Real-valued roots imply an intersection. + Real discr = a1*a1 - a0*a2; + result.intersect = (discr >= (Real)0); + return result; +} + + + +template +typename FIQuery, Circle3>::Result +FIQuery, Circle3>::operator()( + Plane3 const& plane, Circle3 const& circle) +{ + Result result; + + // Construct the plane of the circle. + Plane3 cPlane(circle.normal, circle.center); + + // Compute the intersection of this plane with the input plane. + FIQuery, Plane3> ppQuery; + auto ppResult = ppQuery(plane, cPlane); + if (!ppResult.intersect) + { + // Planes are parallel and nonintersecting. + result.intersect = false; + return result; + } + + if (!ppResult.isLine) + { + // Planes are the same, the circle is the common intersection set. + result.intersect = true; + result.isPoints = false; + result.circle = circle; + return result; + } + + // The planes intersect in a line. Locate one or two points that are on + // the circle and line. If the line is t*D+P, the circle center is C, + // and the circle radius is r, then + // r^2 = |t*D+P-C|^2 = |D|^2*t^2 + 2*Dot(D,P-C)*t + |P-C|^2 + // This is a quadratic equation of the form a2*t^2 + 2*a1*t + a0 = 0. + Vector3 diff = ppResult.line.origin - circle.center; + Real a2 = Dot(ppResult.line.direction, ppResult.line.direction); + Real a1 = Dot(diff, ppResult.line.direction); + Real a0 = Dot(diff, diff) - circle.radius*circle.radius; + + Real discr = a1*a1 - a0*a2; + if (discr < (Real)0) + { + // No real roots, the circle does not intersect the plane. + result.intersect = false; + return result; + } + + result.isPoints = true; + + Real inv = ((Real)1) / a2; + if (discr == (Real)0) + { + // One repeated root, the circle just touches the plane. + result.numIntersections = 1; + result.point[0] = + ppResult.line.origin - (a1*inv)*ppResult.line.direction; + return result; + } + + // Two distinct, real-valued roots, the circle intersects the plane in + // two points. + Real root = std::sqrt(discr); + result.numIntersections = 2; + result.point[0] = + ppResult.line.origin - ((a1 + root)*inv)*ppResult.line.direction; + result.point[1] = + ppResult.line.origin - ((a1 - root)*inv)*ppResult.line.direction; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Cylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Cylinder3.h new file mode 100644 index 000000000000..2f9da6637f76 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Cylinder3.h @@ -0,0 +1,165 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Cylinder3> +{ +public: + struct Result + { + bool intersect; + }; + + // The cylinder must have finite height. + Result operator()(Plane3 const& plane, + Cylinder3 const& cylinder); +}; + +template +class FIQuery, Cylinder3> +{ +public: + struct Result + { + bool intersect; + + // The type of intersection. + // 0: none + // 1: single line (cylinder is tangent to plane), line[0] valid + // 2: two parallel lines (plane cuts cylinder in two lines) + // 3: circle (cylinder axis perpendicular to plane) + // 4: ellipse (cylinder axis neither parallel nor perpendicular) + int type; + Line3 line[2]; + Circle3 circle; + Ellipse3 ellipse; + }; + + // The cylinder must have infinite height. + Result operator()(Plane3 const& plane, + Cylinder3 const& cylinder); +}; + + +template +typename TIQuery, Cylinder3>::Result +TIQuery, Cylinder3>::operator()( + Plane3 const& plane, Cylinder3 const& cylinder) +{ + LogAssert(cylinder.height != std::numeric_limits::max(), + "Cylinder height must be finite for TIQuery."); + + Result result; + + // Compute extremes of signed distance Dot(N,X)-d for points on the + // cylinder. These are + // min = (Dot(N,C)-d) - r*sqrt(1-Dot(N,W)^2) - (h/2)*|Dot(N,W)| + // max = (Dot(N,C)-d) + r*sqrt(1-Dot(N,W)^2) + (h/2)*|Dot(N,W)| + DCPQuery, Plane3> vpQuery; + Real distance = vpQuery(cylinder.axis.origin, plane).distance; + Real absNdW = std::abs(Dot(plane.normal, cylinder.axis.direction)); + Real root = std::sqrt(std::max((Real)1 - absNdW*absNdW, (Real)0)); + Real term = cylinder.radius*root + ((Real)0.5)*cylinder.height*absNdW; + + // Intersection occurs if and only if 0 is in the interval [min,max]. + result.intersect = (distance <= term); + return result; +} + +template +typename FIQuery, Cylinder3>::Result +FIQuery, Cylinder3>::operator()( + Plane3 const& plane, Cylinder3 const& cylinder) +{ + LogAssert(cylinder.height == std::numeric_limits::max(), + "Cylinder height must be infinite for FIQuery."); + + Result result; + + DCPQuery, Plane3> vpQuery; + Real sdistance = vpQuery(cylinder.axis.origin, plane).signedDistance; + Vector3 center = cylinder.axis.origin - sdistance*plane.normal; + Real cosTheta = Dot(cylinder.axis.direction, plane.normal); + Real absCosTheta = std::abs(cosTheta); + + if (absCosTheta > (Real)0) + { + // The cylinder axis intersects the plane in a unique point. + result.intersect = true; + if (absCosTheta < (Real)1) + { + result.type = 4; + result.ellipse.normal = plane.normal; + result.ellipse.center = cylinder.axis.origin - + (sdistance / cosTheta)*cylinder.axis.direction; + result.ellipse.axis[0] = cylinder.axis.direction - + cosTheta*plane.normal; + Normalize(result.ellipse.axis[0]); + result.ellipse.axis[1] = UnitCross(plane.normal, + result.ellipse.axis[0]); + result.ellipse.extent[0] = cylinder.radius / absCosTheta; + result.ellipse.extent[1] = cylinder.radius; + } + else + { + result.type = 3; + result.circle.normal = plane.normal; + result.circle.center = center; + result.circle.radius = cylinder.radius; + } + } + else + { + // The cylinder is parallel to the plane. + Real distance = std::abs(sdistance); + if (distance < cylinder.radius) + { + result.intersect = true; + result.type = 2; + + Vector3 offset = Cross(cylinder.axis.direction, + plane.normal); + Real extent = std::sqrt(cylinder.radius*cylinder.radius - sdistance*sdistance); + + result.line[0].origin = center - extent*offset; + result.line[0].direction = cylinder.axis.direction; + result.line[1].origin = center + extent*offset; + result.line[1].direction = cylinder.axis.direction; + } + else if (distance == cylinder.radius) + { + result.intersect = true; + result.type = 1; + result.line[0].origin = center; + result.line[0].direction = cylinder.axis.direction; + } + else + { + result.intersect = false; + result.type = 0; + } + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Ellipsoid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Ellipsoid3.h new file mode 100644 index 000000000000..c99065a1a7fe --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Ellipsoid3.h @@ -0,0 +1,49 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Ellipsoid3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane, + Ellipsoid3 const& ellipsoid); +}; + + +template +typename TIQuery, Ellipsoid3>::Result +TIQuery, Ellipsoid3>::operator()( + Plane3 const& plane, Ellipsoid3 const& ellipsoid) +{ + Result result; + Matrix3x3 MInverse; + ellipsoid.GetMInverse(MInverse); + Real discr = Dot(plane.normal, MInverse * plane.normal); + Real root = std::sqrt(std::max(discr, (Real)0)); + DCPQuery, Plane3> vpQuery; + Real distance = vpQuery(ellipsoid.center, plane).distance; + result.intersect = (distance <= root); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3OrientedBox3.h new file mode 100644 index 000000000000..67973eba030a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3OrientedBox3.h @@ -0,0 +1,51 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, OrientedBox3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane, + OrientedBox3 const& box); +}; + + +template +typename TIQuery, OrientedBox3>::Result +TIQuery, OrientedBox3>::operator()( + Plane3 const& plane, OrientedBox3 const& box) +{ + Result result; + + Real radius = + std::abs(box.extent[0] * Dot(plane.normal, box.axis[0])) + + std::abs(box.extent[1] * Dot(plane.normal, box.axis[1])) + + std::abs(box.extent[2] * Dot(plane.normal, box.axis[2])); + + DCPQuery, Plane3> ppQuery; + auto ppResult = ppQuery(box.center, plane); + result.intersect = (ppResult.distance <= radius); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Plane3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Plane3.h new file mode 100644 index 000000000000..08e5fff3441f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Plane3.h @@ -0,0 +1,155 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Plane3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane0, Plane3 const& plane1); +}; + +template +class FIQuery, Plane3> +{ +public: + struct Result + { + bool intersect; + + // If 'intersect' is true, the intersection is either a line or the + // planes are the same. When a line, 'line' is valid. When a plane, + // 'plane' is set to one of the planes. + bool isLine; + Line3 line; + Plane3 plane; + }; + + Result operator()(Plane3 const& plane0, Plane3 const& plane1); +}; + + +template +typename TIQuery, Plane3>::Result +TIQuery, Plane3>::operator()( + Plane3 const& plane0, Plane3 const& plane1) +{ + // If Cross(N0,N1) is zero, then either planes are parallel and separated + // or the same plane. In both cases, 'false' is returned. Otherwise, the + // planes intersect. To avoid subtle differences in reporting between + // Test() and Find(), the same parallel test is used. Mathematically, + // |Cross(N0,N1)|^2 = Dot(N0,N0)*Dot(N1,N1)-Dot(N0,N1)^2 + // = 1 - Dot(N0,N1)^2 + // The last equality is true since planes are required to have unit-length + // normal vectors. The test |Cross(N0,N1)| = 0 is the same as + // |Dot(N0,N1)| = 1. + + Result result; + Real dot = Dot(plane0.normal, plane1.normal); + if (std::abs(dot) < (Real)1) + { + result.intersect = true; + return result; + } + + // The planes are parallel. Check whether they are coplanar. + Real cDiff; + if (dot >= (Real)0) + { + // Normals are in same direction, need to look at c0-c1. + cDiff = plane0.constant - plane1.constant; + } + else + { + // Normals are in opposite directions, need to look at c0+c1. + cDiff = plane0.constant + plane1.constant; + } + + result.intersect = (std::abs(cDiff) == (Real)0); + return result; +} + + + +template +typename FIQuery, Plane3>::Result +FIQuery, Plane3>::operator()( + Plane3 const& plane0, Plane3 const& plane1) +{ + // If N0 and N1 are parallel, either the planes are parallel and separated + // or the same plane. In both cases, 'false' is returned. Otherwise, + // the intersection line is + // L(t) = t*Cross(N0,N1)/|Cross(N0,N1)| + c0*N0 + c1*N1 + // for some coefficients c0 and c1 and for t any real number (the line + // parameter). Taking dot products with the normals, + // d0 = Dot(N0,L) = c0*Dot(N0,N0) + c1*Dot(N0,N1) = c0 + c1*d + // d1 = Dot(N1,L) = c0*Dot(N0,N1) + c1*Dot(N1,N1) = c0*d + c1 + // where d = Dot(N0,N1). These are two equations in two unknowns. The + // solution is + // c0 = (d0 - d*d1)/det + // c1 = (d1 - d*d0)/det + // where det = 1 - d^2. + + Result result; + + Real dot = Dot(plane0.normal, plane1.normal); + if (std::abs(dot) >= (Real)1) + { + // The planes are parallel. Check if they are coplanar. + Real cDiff; + if (dot >= (Real)0) + { + // Normals are in same direction, need to look at c0-c1. + cDiff = plane0.constant - plane1.constant; + } + else + { + // Normals are in opposite directions, need to look at c0+c1. + cDiff = plane0.constant + plane1.constant; + } + + if (std::abs(cDiff) == (Real)0) + { + // The planes are coplanar. + result.intersect = true; + result.isLine = false; + result.plane = plane0; + return result; + } + + // The planes are parallel but distinct. + result.intersect = false; + return result; + } + + Real invDet = ((Real)1) / ((Real)1 - dot*dot); + Real c0 = (plane0.constant - dot*plane1.constant)*invDet; + Real c1 = (plane1.constant - dot*plane0.constant)*invDet; + result.intersect = true; + result.isLine = true; + result.line.origin = c0*plane0.normal + c1*plane1.normal; + result.line.direction = UnitCross(plane0.normal, plane1.normal); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Sphere3.h new file mode 100644 index 000000000000..129feb6eb0b7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Sphere3.h @@ -0,0 +1,98 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane, Sphere3 const& sphere); +}; + +template +class FIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + + // If 'intersect' is true, the intersection is either a point or a + // circle. When 'isCircle' is true, 'circle' is valid. When + // 'isCircle' is false, 'point' is valid. + bool isCircle; + Circle3 circle; + Vector3 point; + }; + + Result operator()(Plane3 const& plane, Sphere3 const& sphere); +}; + + +template +typename TIQuery, Sphere3>::Result +TIQuery, Sphere3>::operator()( + Plane3 const& plane, Sphere3 const& sphere) +{ + Result result; + DCPQuery, Plane3> ppQuery; + auto ppResult = ppQuery(sphere.center, plane); + result.intersect = (ppResult.distance <= sphere.radius); + return result; +} + + +template +typename FIQuery, Sphere3>::Result +FIQuery, Sphere3>::operator()( + Plane3 const& plane, Sphere3 const& sphere) +{ + Result result; + DCPQuery, Plane3> ppQuery; + auto ppResult = ppQuery(sphere.center, plane); + if (ppResult.distance < sphere.radius) + { + result.intersect = true; + result.isCircle = true; + result.circle.center = sphere.center - ppResult.signedDistance * plane.normal; + result.circle.normal = plane.normal; + Real sum = sphere.radius + ppResult.distance; // > 0 + Real dif = sphere.radius - ppResult.distance; // > 0 + Real arg = sum * dif; // sqr(sphere.radius) - sqr(ppResult.distance) + result.circle.radius = std::sqrt(arg); + return result; + } + else if (ppResult.distance == sphere.radius) + { + result.intersect = true; + result.isCircle = false; + result.point = sphere.center - ppResult.signedDistance * plane.normal; + return result; + } + else + { + result.intersect = false; + return result; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Triangle3.h new file mode 100644 index 000000000000..3f528e27acca --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Triangle3.h @@ -0,0 +1,289 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Triangle3> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (plane and triangle intersect + // at a single point [vertex]), 2 (plane and triangle intersect in a + // segment), or 3 (triangle is in the plane). When the number is 2, + // the segment is either interior to the triangle or is an edge of the + // triangle, the distinction stored in 'isInterior'. + int numIntersections; + bool isInterior; + }; + + Result operator()(Plane3 const& plane, + Triangle3 const& triangle); +}; + +template +class FIQuery, Triangle3> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (plane and triangle intersect + // at a single point [vertex]), 2 (plane and triangle intersect in a + // segment), or 3 (triangle is in the plane). When the number is 2, + // the segment is either interior to the triangle or is an edge of the + // triangle, the distinction stored in 'isInterior'. + int numIntersections; + bool isInterior; + Vector3 point[3]; + }; + + Result operator()(Plane3 const& plane, + Triangle3 const& triangle); +}; + + +template +typename TIQuery, Triangle3>::Result +TIQuery, Triangle3>::operator()( + Plane3 const& plane, Triangle3 const& triangle) +{ + Result result; + + // Determine on which side of the plane the vertices lie. The table of + // possibilities is listed next with n = numNegative, p = numPositive, and + // z = numZero. + // + // n p z intersection + // ------------------------------------ + // 0 3 0 none + // 0 2 1 vertex + // 0 1 2 edge + // 0 0 3 triangle in the plane + // 1 2 0 segment (2 edges clipped) + // 1 1 1 segment (1 edge clipped) + // 1 0 2 edge + // 2 1 0 segment (2 edges clipped) + // 2 0 1 vertex + // 3 0 0 none + + Real s[3]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 3; ++i) + { + s[i] = Dot(plane.normal, triangle.v[i]) - plane.constant; + if (s[i] >(Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + if (numZero == 0 && numPositive > 0 && numNegative > 0) + { + result.intersect = true; + result.numIntersections = 2; + result.isInterior = true; + return result; + } + + if (numZero == 1) + { + result.intersect = true; + for (int i = 0; i < 3; ++i) + { + if (s[i] == (Real)0) + { + if (numPositive == 2 || numNegative == 2) + { + result.numIntersections = 1; + } + else + { + result.numIntersections = 2; + result.isInterior = true; + } + break; + } + } + return result; + } + + if (numZero == 2) + { + result.intersect = true; + result.numIntersections = 2; + result.isInterior = false; + return result; + } + + if (numZero == 3) + { + result.intersect = true; + result.numIntersections = 3; + } + else + { + result.intersect = false; + result.numIntersections = 0; + + } + return result; +} + + + +template +typename FIQuery, Triangle3>::Result +FIQuery, Triangle3>::operator()( + Plane3 const& plane, Triangle3 const& triangle) +{ + Result result; + + // Determine on which side of the plane the vertices lie. The table of + // possibilities is listed next with n = numNegative, p = numPositive, and + // z = numZero. + // + // n p z intersection + // ------------------------------------ + // 0 3 0 none + // 0 2 1 vertex + // 0 1 2 edge + // 0 0 3 triangle in the plane + // 1 2 0 segment (2 edges clipped) + // 1 1 1 segment (1 edge clipped) + // 1 0 2 edge + // 2 1 0 segment (2 edges clipped) + // 2 0 1 vertex + // 3 0 0 none + + Real s[3]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 3; ++i) + { + s[i] = Dot(plane.normal, triangle.v[i]) - plane.constant; + if (s[i] >(Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + if (numZero == 0 && numPositive > 0 && numNegative > 0) + { + result.intersect = true; + result.numIntersections = 2; + result.isInterior = true; + Real sign = (Real)3 - numPositive * (Real)2; + for (int i0 = 0; i0 < 3; ++i0) + { + if (sign * s[i0] >(Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + Real t1 = s[i1] / (s[i1] - s[i0]); + Real t2 = s[i2] / (s[i2] - s[i0]); + result.point[0] = triangle.v[i1] + t1 * + (triangle.v[i0] - triangle.v[i1]); + result.point[1] = triangle.v[i2] + t2 * + (triangle.v[i0] - triangle.v[i2]); + break; + } + } + return result; + } + + if (numZero == 1) + { + result.intersect = true; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] == (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.point[0] = triangle.v[i0]; + if (numPositive == 2 || numNegative == 2) + { + result.numIntersections = 1; + } + else + { + result.numIntersections = 2; + result.isInterior = true; + Real t = s[i1] / (s[i1] - s[i2]); + result.point[1] = triangle.v[i1] + t * + (triangle.v[i2] - triangle.v[i1]); + } + break; + } + } + return result; + } + + if (numZero == 2) + { + result.intersect = true; + result.numIntersections = 2; + result.isInterior = false; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] != (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.point[0] = triangle.v[i1]; + result.point[1] = triangle.v[i2]; + break; + } + } + return result; + } + + if (numZero == 3) + { + result.intersect = true; + result.numIntersections = 3; + result.point[0] = triangle.v[0]; + result.point[1] = triangle.v[1]; + result.point[2] = triangle.v[2]; + } + else + { + result.intersect = false; + result.numIntersections = 0; + + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2AlignedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2AlignedBox2.h new file mode 100644 index 000000000000..cab4ffef1eb6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2AlignedBox2.h @@ -0,0 +1,150 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the four edges of +// the box. + +namespace gte +{ + +template +class TIQuery, AlignedBox2> + : + public TIQuery, AlignedBox2> +{ +public: + struct Result + : + public TIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, AlignedBox2 const& box); + +protected: + void DoQuery(Vector2 const& rayOrigin, + Vector2 const& rayDirection, Vector2 const& boxExtent, + Result& result); +}; + +template +class FIQuery, AlignedBox2> + : + public FIQuery, AlignedBox2> +{ +public: + struct Result + : + public FIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, AlignedBox2 const& box); + +protected: + void DoQuery(Vector2 const& rayOrigin, + Vector2 const& rayDirection, Vector2 const& boxExtent, + Result& result); +}; + + +template +typename TIQuery, AlignedBox2>::Result +TIQuery, AlignedBox2>::operator()( + Ray2 const& ray, AlignedBox2 const& box) +{ + // Get the centered form of the aligned box. The axes are implicitly + // Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the ray to the aligned-box coordinate system. + Vector2 rayOrigin = ray.origin - boxCenter; + + Result result; + DoQuery(rayOrigin, ray.direction, boxExtent, result); + return result; +} + +template +void TIQuery, AlignedBox2>::DoQuery( + Vector2 const& rayOrigin, Vector2 const& rayDirection, + Vector2 const& boxExtent, Result& result) +{ + for (int i = 0; i < 2; ++i) + { + if (std::abs(rayOrigin[i]) > boxExtent[i] + && rayOrigin[i] * rayDirection[i] >= (Real)0) + { + result.intersect = false; + return; + } + } + + TIQuery, AlignedBox2>::DoQuery(rayOrigin, + rayDirection, boxExtent, result); +} + +template +typename FIQuery, AlignedBox2>::Result +FIQuery, AlignedBox2>::operator()( + Ray2 const& ray, AlignedBox2 const& box) +{ + // Get the centered form of the aligned box. The axes are implicitly + // Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the ray to the aligned-box coordinate system. + Vector2 rayOrigin = ray.origin - boxCenter; + + Result result; + DoQuery(rayOrigin, ray.direction, boxExtent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; +} + +template +void FIQuery, AlignedBox2>::DoQuery( + Vector2 const& rayOrigin, Vector2 const& rayDirection, + Vector2 const& boxExtent, Result& result) +{ + FIQuery, AlignedBox2>::DoQuery(rayOrigin, + rayDirection, boxExtent, result); + + if (result.intersect) + { + // The line containing the ray intersects the box; the t-interval is + // [t0,t1]. The ray intersects the box as long as [t0,t1] overlaps + // the ray t-interval [0,+infinity). + std::array rayInterval = + { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + result.intersect = iiResult.intersect; + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Arc2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Arc2.h new file mode 100644 index 000000000000..03a499152541 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Arc2.h @@ -0,0 +1,94 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The queries consider the arc to be a 1-dimensional object. + +namespace gte +{ + +template +class TIQuery, Arc2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray2 const& ray, Arc2 const& arc); +}; + +template +class FIQuery, Arc2> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Ray2 const& ray, Arc2 const& arc); +}; + + +template +typename TIQuery, Arc2>::Result +TIQuery, Arc2>::operator()( + Ray2 const& ray, Arc2 const& arc) +{ + Result result; + FIQuery, Arc2> raQuery; + auto raResult = raQuery(ray, arc); + result.intersect = raResult.intersect; + return result; +} + +template +typename FIQuery, Arc2>::Result +FIQuery, Arc2>::operator()( + Ray2 const& ray, Arc2 const& arc) +{ + Result result; + + FIQuery, Circle2> rcQuery; + Circle2 circle(arc.center, arc.radius); + auto rcResult = rcQuery(ray, circle); + if (rcResult.intersect) + { + // Test whether ray-circle intersections are on the arc. + result.numIntersections = 0; + for (int i = 0; i < rcResult.numIntersections; ++i) + { + if (arc.Contains(rcResult.point[i])) + { + result.intersect = true; + result.parameter[result.numIntersections] + = rcResult.parameter[i]; + result.point[result.numIntersections++] + = rcResult.point[i]; + } + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Circle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Circle2.h new file mode 100644 index 000000000000..5bac060c4087 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Circle2.h @@ -0,0 +1,100 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the circle to be a solid (disk). + +namespace gte +{ + +template +class TIQuery, Circle2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray2 const& ray, Circle2 const& circle); +}; + +template +class FIQuery, Circle2> + : + public FIQuery, Circle2> +{ +public: + struct Result + : + public FIQuery, Circle2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, Circle2 const& circle); + +protected: + void DoQuery(Vector2 const& rayOrigin, + Vector2 const& rayDirection, Circle2 const& circle, + Result& result); +}; + + +template +typename TIQuery, Circle2>::Result +TIQuery, Circle2>::operator()( + Ray2 const& ray, Circle2 const& circle) +{ + Result result; + FIQuery, Circle2> rcQuery; + result.intersect = rcQuery(ray, circle).intersect; + return result; +} + +template +typename FIQuery, Circle2>::Result +FIQuery, Circle2>::operator()( + Ray2 const& ray, Circle2 const& circle) +{ + Result result; + DoQuery(ray.origin, ray.direction, circle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; +} + +template +void FIQuery, Circle2>::DoQuery( + Vector2 const& rayOrigin, Vector2 const& rayDirection, + Circle2 const& circle, Result& result) +{ + FIQuery, Circle2>::DoQuery(rayOrigin, + rayDirection, circle, result); + + if (result.intersect) + { + // The line containing the ray intersects the disk; the t-interval is + // [t0,t1]. The ray intersects the disk as long as [t0,t1] overlaps + // the ray t-interval [0,+infinity). + std::array rayInterval = {{ (Real)0, std::numeric_limits::max() }}; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + result.intersect = iiResult.intersect; + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2OrientedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2OrientedBox2.h new file mode 100644 index 000000000000..a13b679b5432 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2OrientedBox2.h @@ -0,0 +1,106 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the four edges of +// the box. + +namespace gte +{ + +template +class TIQuery, OrientedBox2> + : + public TIQuery, AlignedBox2> +{ +public: + struct Result + : + public TIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, OrientedBox2 const& box); +}; + +template +class FIQuery, OrientedBox2> + : + public FIQuery, AlignedBox2> +{ +public: + struct Result + : + public FIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, OrientedBox2 const& box); +}; + + +template +typename TIQuery, OrientedBox2>::Result +TIQuery, OrientedBox2>::operator()( + Ray2 const& ray, OrientedBox2 const& box) +{ + // Transform the ray to the oriented-box coordinate system. + Vector2 diff = ray.origin - box.center; + Vector2 rayOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 rayDirection = Vector2 + { + Dot(ray.direction, box.axis[0]), + Dot(ray.direction, box.axis[1]) + }; + + Result result; + this->DoQuery(rayOrigin, rayDirection, box.extent, result); + return result; +} + +template +typename FIQuery, OrientedBox2>::Result +FIQuery, OrientedBox2>::operator()( + Ray2 const& ray, OrientedBox2 const& box) +{ + // Transform the ray to the oriented-box coordinate system. + Vector2 diff = ray.origin - box.center; + Vector2 rayOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 rayDirection = Vector2 + { + Dot(ray.direction, box.axis[0]), + Dot(ray.direction, box.axis[1]) + }; + + Result result; + this->DoQuery(rayOrigin, rayDirection, box.extent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Ray2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Ray2.h new file mode 100644 index 000000000000..686ce518938b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Ray2.h @@ -0,0 +1,232 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class TIQuery, Ray2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (rays intersect in a + // single point), 2 (rays are collinear and intersect in a segment; + // ray directions are opposite of each other), or + // std::numeric_limits::max() (intersection is a ray; ray + // directions are the same). + int numIntersections; + }; + + Result operator()(Ray2 const& ray0, Ray2 const& ray1); +}; + +template +class FIQuery, Ray2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (rays intersect in a + // single point), 2 (rays are collinear and intersect in a segment; + // ray directions are opposite of each other), or + // std::numeric_limits::max() (intersection is a ray; ray + // directions are the same). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point[0] = ray0.origin + ray0Parameter[0] * ray0.direction + // = ray1.origin + ray1Parameter[0] * ray1.direction + // If numIntersections is 2, the segment of intersection is formed by + // the ray origins, + // ray0Parameter[0] = ray1Parameter[0] = 0 + // point[0] = ray0.origin + // = ray1.origin + ray1Parameter[1] * ray1.direction + // point[1] = ray1.origin + // = ray0.origin + ray0Parameter[1] * ray0.direction + // where ray0Parameter[1] >= 0 and ray1Parameter[1] >= 0. + // If numIntersections is maxInt, let + // ray1.origin = ray0.origin + t * ray0.direction + // then + // ray0Parameter[] = { max(t,0), +maxReal } + // ray1Parameter[] = { -min(t,0), +maxReal } + // point[0] = ray0.origin + ray0Parameter[0] * ray0.direction + Real ray0Parameter[2], ray1Parameter[2]; + Vector2 point[2]; + }; + + Result operator()(Ray2 const& ray0, Ray2 const& ray1); +}; + + +template +typename TIQuery, Ray2>::Result +TIQuery, Ray2>::operator()( + Ray2 const& ray0, Ray2 const& ray1) +{ + Result result; + FIQuery, Line2> llQuery; + Line2 line0(ray0.origin, ray0.direction); + Line2 line1(ray1.origin, ray1.direction); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the rays. + if (llResult.line0Parameter[0] >= (Real)0 + && llResult.line1Parameter[0] >= (Real)0) + { + result.intersect = true; + result.numIntersections = 1; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + if (Dot(ray0.direction, ray1.direction) > (Real)0) + { + // The rays are collinear and in the same direction, so they must + // overlap. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + } + else + { + // The rays are collinear but in opposite directions. Test + // whether they overlap. Ray0 has interval [0,+infinity) and + // ray1 has interval (-infinity,t] relative to ray0.direction. + Vector2 diff = ray1.origin - ray0.origin; + Real t = Dot(ray0.direction, diff); + if (t > (Real)0) + { + result.intersect = true; + result.numIntersections = 2; + } + else if (t < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + else // t == 0 + { + result.intersect = true; + result.numIntersections = 1; + } + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; +} + +template +typename FIQuery, Ray2>::Result +FIQuery, Ray2>::operator()( + Ray2 const& ray0, Ray2 const& ray1) +{ + Result result; + FIQuery, Line2> llQuery; + Line2 line0(ray0.origin, ray0.direction); + Line2 line1(ray1.origin, ray1.direction); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the rays. + if (llResult.line0Parameter[0] >= (Real)0 + && llResult.line1Parameter[0] >= (Real)0) + { + result.intersect = true; + result.numIntersections = 1; + result.ray0Parameter[0] = llResult.line0Parameter[0]; + result.ray1Parameter[0] = llResult.line1Parameter[0]; + result.point[0] = llResult.point; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + // Compute t for which ray1.origin = ray0.origin + t*ray0.direction. + Real maxReal = std::numeric_limits::max(); + Vector2 diff = ray1.origin - ray0.origin; + Real t = Dot(ray0.direction, diff); + if (Dot(ray0.direction, ray1.direction) > (Real)0) + { + // The rays are collinear and in the same direction, so they must + // overlap. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + if (t >= (Real)0) + { + result.ray0Parameter[0] = t; + result.ray0Parameter[1] = maxReal; + result.ray1Parameter[0] = (Real)0; + result.ray1Parameter[1] = maxReal; + result.point[0] = ray1.origin; + } + else + { + result.ray0Parameter[0] = (Real)0; + result.ray0Parameter[1] = maxReal; + result.ray1Parameter[0] = -t; + result.ray1Parameter[1] = maxReal; + result.point[0] = ray0.origin; + } + } + else + { + // The rays are collinear but in opposite directions. Test + // whether they overlap. Ray0 has interval [0,+infinity) and + // ray1 has interval (-infinity,t1] relative to ray0.direction. + if (t >= (Real)0) + { + result.intersect = true; + result.numIntersections = 2; + result.ray0Parameter[0] = (Real)0; + result.ray0Parameter[1] = t; + result.ray1Parameter[0] = (Real)0; + result.ray1Parameter[1] = t; + result.point[0] = ray0.origin; + result.point[1] = ray1.origin; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Segment2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Segment2.h new file mode 100644 index 000000000000..7959c560ae1c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Segment2.h @@ -0,0 +1,202 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Segment2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (ray and segment intersect in + // a single point), or 2 (ray and segment are collinear and intersect + // in a segment). + int numIntersections; + }; + + Result operator()(Ray2 const& ray, Segment2 const& segment); +}; + +template +class FIQuery, Segment2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (ray and segment intersect in + // a single point), or 2 (ray and segment are collinear and intersect + // in a segment). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point[0] = ray.origin + rayParameter[0] * ray.direction + // = segment.center + segmentParameter[0] * segment.direction + // If numIntersections is 2, the endpoints of the segment of + // intersection are + // point[i] = ray.origin + rayParameter[i] * ray.direction + // = segment.center + segmentParameter[i] * segment.direction + // with rayParameter[0] <= rayParameter[1] and + // segmentParameter[0] <= segmentParameter[1]. + Real rayParameter[2], segmentParameter[2]; + Vector2 point[2]; + }; + + Result operator()(Ray2 const& ray, Segment2 const& segment); +}; + + +template +typename TIQuery, Segment2>::Result +TIQuery, Segment2>::operator()( + Ray2 const& ray, Segment2 const& segment) +{ + Result result; + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + FIQuery, Line2> llQuery; + Line2 line0(ray.origin, ray.direction); + Line2 line1(segOrigin, segDirection); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the ray and segment. + if (llResult.line0Parameter[0] >= (Real)0 + && std::abs(llResult.line1Parameter[0]) <= segExtent) + { + result.intersect = true; + result.numIntersections = 1; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + // Compute the location of the right-most point of the segment + // relative to the ray direction. + Vector2 diff = segOrigin - ray.origin; + Real t = Dot(ray.direction, diff) + segExtent; + if (t > (Real)0) + { + result.intersect = true; + result.numIntersections = 2; + } + else if (t < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + else // t == 0 + { + result.intersect = true; + result.numIntersections = 1; + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; +} + +template +typename FIQuery, Segment2>::Result +FIQuery, Segment2>::operator()( + Ray2 const& ray, Segment2 const& segment) +{ + Result result; + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + FIQuery, Line2> llQuery; + Line2 line0(ray.origin, ray.direction); + Line2 line1(segOrigin, segDirection); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the ray and segment. + if (llResult.line0Parameter[0] >= (Real)0 + && std::abs(llResult.line1Parameter[0]) <= segExtent) + { + result.intersect = true; + result.numIntersections = 1; + result.rayParameter[0] = llResult.line0Parameter[0]; + result.segmentParameter[0] = llResult.line1Parameter[0]; + result.point[0] = llResult.point; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + // Compute t for which segment.origin = ray.origin + t*ray.direction. + Vector2 diff = segOrigin - ray.origin; + Real t = Dot(ray.direction, diff); + + // Get the ray interval. + std::array interval0 = + { + (Real)0, std::numeric_limits::max() + }; + + // Compute the location of the segment endpoints relative to the ray. + std::array interval1 = {{ t - segExtent, t + segExtent }}; + + // Compute the intersection of [0,+infinity) and [tmin,tmax]. + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(interval0, interval1); + if (iiResult.intersect) + { + result.intersect = true; + result.numIntersections = iiResult.numIntersections; + for (int i = 0; i < iiResult.numIntersections; ++i) + { + result.rayParameter[i] = iiResult.overlap[i]; + result.segmentParameter[i] = iiResult.overlap[i] - t; + result.point[i] = ray.origin + + result.rayParameter[i] * ray.direction; + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Triangle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Triangle2.h new file mode 100644 index 000000000000..68761c8249d9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Triangle2.h @@ -0,0 +1,99 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the triangle to be a solid. + +namespace gte +{ + +template +class TIQuery, Triangle2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray2 const& ray, Triangle2 const& triangle); +}; + +template +class FIQuery, Triangle2> + : + public FIQuery, Triangle2> +{ +public: + struct Result + : + public FIQuery, Triangle2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, Triangle2 const& triangle); + +protected: + void DoQuery(Vector2 const& rayOrigin, + Vector2 const& rayDirection, Triangle2 const& triangle, + Result& result); +}; + + +template +typename TIQuery, Triangle2>::Result +TIQuery, Triangle2>::operator()( + Ray2 const& ray, Triangle2 const& triangle) +{ + Result result; + FIQuery, Triangle2> rtQuery; + result.intersect = rtQuery(ray, triangle).intersect; + return result; +} + +template +typename FIQuery, Triangle2>::Result +FIQuery, Triangle2>::operator()( + Ray2 const& ray, Triangle2 const& triangle) +{ + Result result; + DoQuery(ray.origin, ray.direction, triangle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; +} + +template +void FIQuery, Triangle2>::DoQuery( + Vector2 const& rayOrigin, Vector2 const& rayDirection, + Triangle2 const& triangle, Result& result) +{ + FIQuery, Triangle2>::DoQuery(rayOrigin, + rayDirection, triangle, result); + + if (result.intersect) + { + // The line containing the ray intersects the disk; the t-interval is + // [t0,t1]. The ray intersects the disk as long as [t0,t1] overlaps + // the ray t-interval [0,+infinity). + std::array rayInterval = + { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + result.parameter = iiQuery(result.parameter, rayInterval).overlap; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3AlignedBox3.h new file mode 100644 index 000000000000..987603fe6243 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3AlignedBox3.h @@ -0,0 +1,123 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/11/28) + +#pragma once + +#include +#include + +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the six faces of +// the box. The find-intersection queries use Liang-Barsky clipping. The +// queries consider the box to be a solid. The algorithms are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace gte +{ + template + class TIQuery, AlignedBox3> + : + public TIQuery, AlignedBox3> + { + public: + struct Result + : + public TIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the ray to the aligned-box coordinate system. + Vector3 rayOrigin = ray.origin - boxCenter; + + Result result; + DoQuery(rayOrigin, ray.direction, boxExtent, result); + return result; + } + + protected: + void DoQuery(Vector3 const& rayOrigin, Vector3 const& rayDirection, + Vector3 const& boxExtent, Result& result) + { + for (int i = 0; i < 3; ++i) + { + if (std::abs(rayOrigin[i]) > boxExtent[i] && rayOrigin[i] * rayDirection[i] >= (Real)0) + { + result.intersect = false; + return; + } + } + + TIQuery, AlignedBox3>::DoQuery(rayOrigin, rayDirection, boxExtent, result); + } + }; + + template + class FIQuery, AlignedBox3> + : + public FIQuery, AlignedBox3> + { + public: + struct Result + : + public FIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the ray to the aligned-box coordinate system. + Vector3 rayOrigin = ray.origin - boxCenter; + + Result result; + DoQuery(rayOrigin, ray.direction, boxExtent, result); + for (int i = 0; i < result.numPoints; ++i) + { + result.point[i] = ray.origin + result.lineParameter[i] * ray.direction; + } + return result; + } + + protected: + void DoQuery(Vector3 const& rayOrigin, Vector3 const& rayDirection, + Vector3 const& boxExtent, Result& result) + { + FIQuery, AlignedBox3>::DoQuery(rayOrigin, rayDirection, boxExtent, result); + if (result.intersect) + { + // The line containing the ray intersects the box; the + // t-interval is [t0,t1]. The ray intersects the box as long + // as [t0,t1] overlaps the ray t-interval (0,+infinity). + if (result.lineParameter[1] >= (Real)0) + { + if (result.lineParameter[0] < (Real)0) + { + result.lineParameter[0] = (Real)0; + } + } + else + { + result.intersect = false; + result.numPoints = 0; + } + } + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Capsule3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Capsule3.h new file mode 100644 index 000000000000..a03e37d11ad9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Capsule3.h @@ -0,0 +1,112 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the capsule to be a solid. +// +// The test-intersection queries are based on distance computations. + +namespace gte +{ + +template +class TIQuery, Capsule3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray3 const& ray, Capsule3 const& capsule); +}; + +template +class FIQuery, Capsule3> + : + public FIQuery, Capsule3> +{ +public: + struct Result + : + public FIQuery, Capsule3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, Capsule3 const& capsule); + +protected: + void DoQuery(Vector3 const& rayOrigin, + Vector3 const& rayDirection, Capsule3 const& capsule, + Result& result); +}; + + +template +typename TIQuery, Capsule3>::Result +TIQuery, Capsule3>::operator()( + Ray3 const& ray, Capsule3 const& capsule) +{ + Result result; + DCPQuery, Segment3> rsQuery; + auto rsResult = rsQuery(ray, capsule.segment); + result.intersect = (rsResult.distance <= capsule.radius); + return result; +} + +template +typename FIQuery, Capsule3>::Result +FIQuery, Capsule3>::operator()( + Ray3 const& ray, Capsule3 const& capsule) +{ + Result result; + DoQuery(ray.origin, ray.direction, capsule, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; +} + +template +void FIQuery, Capsule3>::DoQuery( + Vector3 const& rayOrigin, Vector3 const& rayDirection, + Capsule3 const& capsule, Result& result) +{ + FIQuery, Capsule3>::DoQuery(rayOrigin, + rayDirection, capsule, result); + + if (result.intersect) + { + // The line containing the ray intersects the capsule; the t-interval + // is [t0,t1]. The ray intersects the capsule as long as [t0,t1] + // overlaps the ray t-interval [0,+infinity). + std::array rayInterval = + { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Cone3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Cone3.h new file mode 100644 index 000000000000..e64b0ff6272f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Cone3.h @@ -0,0 +1,105 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The queries consider the cone to be single sided and solid. + +namespace gte +{ + +template +class FIQuery, Cone3> + : + public FIQuery, Cone3> +{ +public: + struct Result + : + public FIQuery, Cone3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, Cone3 const& cone); + +protected: + void DoQuery(Vector3 const& rayOrigin, + Vector3 const& rayDirection, Cone3 const& cone, + Result& result); +}; + + +template +typename FIQuery, Cone3>::Result +FIQuery, Cone3>::operator()(Ray3 const& ray, + Cone3 const& cone) +{ + Result result; + DoQuery(ray.origin, ray.direction, cone, result); + switch (result.type) + { + case 1: // point + result.point[0] = ray.origin + result.parameter[0] * ray.direction; + result.point[1] = result.point[0]; + break; + case 2: // segment + result.point[0] = ray.origin + result.parameter[0] * ray.direction; + result.point[1] = ray.origin + result.parameter[1] * ray.direction; + break; + case 3: // ray + result.point[0] = ray.origin + result.parameter[0] * ray.direction; + result.point[1] = ray.direction; + break; + default: // no intersection + break; + } + return result; +} + +template +void FIQuery, Cone3>::DoQuery( + Vector3 const& rayOrigin, Vector3 const& rayDirection, + Cone3 const& cone, Result& result) +{ + FIQuery, Cone3>::DoQuery(rayOrigin, + rayDirection, cone, result); + + if (result.intersect) + { + // The line containing the ray intersects the cone; the t-interval + // is [t0,t1]. The ray intersects the cone as long as [t0,t1] + // overlaps the ray t-interval [0,+infinity). + std::array rayInterval = {{ + (Real)0, std::numeric_limits::max() }}; + FIIntervalInterval iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + if (iiResult.intersect) + { + result.parameter = iiResult.overlap; + if (result.parameter[1] < std::numeric_limits::max()) + { + result.type = iiResult.numIntersections; + } + else + { + result.type = 3; + } + } + else + { + result.intersect = false; + result.type = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Cylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Cylinder3.h new file mode 100644 index 000000000000..b44ff538052d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Cylinder3.h @@ -0,0 +1,86 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the cylinder to be a solid. + +namespace gte +{ + +template +class FIQuery, Cylinder3> + : + public FIQuery, Cylinder3> +{ +public: + struct Result + : + public FIQuery, Cylinder3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, Cylinder3 const& cylinder); + +protected: + void DoQuery(Vector3 const& rayOrigin, + Vector3 const& rayDirection, Cylinder3 const& cylinder, + Result& result); +}; + + +template +typename FIQuery, Cylinder3>::Result +FIQuery, Cylinder3>::operator()( + Ray3 const& ray, Cylinder3 const& cylinder) +{ + Result result; + DoQuery(ray.origin, ray.direction, cylinder, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; +} + +template +void FIQuery, Cylinder3>::DoQuery( + Vector3 const& rayOrigin, Vector3 const& rayDirection, + Cylinder3 const& cylinder, Result& result) +{ + FIQuery, Cylinder3>::DoQuery(rayOrigin, + rayDirection, cylinder, result); + + if (result.intersect) + { + // The line containing the ray intersects the cylinder; the t-interval + // is [t0,t1]. The ray intersects the cylinder as long as [t0,t1] + // overlaps the ray t-interval [0,+infinity). + std::array rayInterval = + { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Ellipsoid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Ellipsoid3.h new file mode 100644 index 000000000000..30652203ce68 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Ellipsoid3.h @@ -0,0 +1,148 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The queries consider the ellipsoid to be a solid. + +namespace gte +{ + +template +class TIQuery, Ellipsoid3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray3 const& ray, + Ellipsoid3 const& ellipsoid); +}; + +template +class FIQuery, Ellipsoid3> + : + public FIQuery, Ellipsoid3> +{ +public: + struct Result + : + public FIQuery, Ellipsoid3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, + Ellipsoid3 const& ellipsoid); + +protected: + void DoQuery(Vector3 const& rayOrigin, + Vector3 const& rayDirection, Ellipsoid3 const& ellipsoid, + Result& result); +}; + + +template +typename TIQuery, Ellipsoid3>::Result +TIQuery, Ellipsoid3>::operator()( + Ray3 const& ray, Ellipsoid3 const& ellipsoid) +{ + // The ellipsoid is (X-K)^T*M*(X-K)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the ellipsoid equation to obtain + // a quadratic equation Q(t) = a2*t^2 + 2*a1*t + a0 = 0, where + // a2 = D^T*M*D, a1 = D^T*M*(P-K), and a0 = (P-K)^T*M*(P-K)-1. + Result result; + + Matrix3x3 M; + ellipsoid.GetM(M); + + Vector3 diff = ray.origin - ellipsoid.center; + Vector3 matDir = M*ray.direction; + Vector3 matDiff = M*diff; + Real a2 = Dot(ray.direction, matDir); + Real a1 = Dot(ray.direction, matDiff); + Real a0 = Dot(diff, matDiff) - (Real)1; + + Real discr = a1*a1 - a0*a2; + if (discr >= (Real)0) + { + // Test whether ray origin is inside ellipsoid. + if (a0 <= (Real)0) + { + result.intersect = true; + } + else + { + // At this point, Q(0) = a0 > 0 and Q(t) has real roots. It is + // also the case that a2 > 0, since M is positive definite, + // implying that D^T*M*D > 0 for any nonzero vector D. Thus, + // an intersection occurs only when Q'(0) < 0. + result.intersect = (a1 < (Real)0); + } + } + else + { + // No intersection if Q(t) has no real roots. + result.intersect = false; + } + + return result; +} + +template +typename FIQuery, Ellipsoid3>::Result +FIQuery, Ellipsoid3>::operator()( + Ray3 const& ray, Ellipsoid3 const& ellipsoid) +{ + Result result; + DoQuery(ray.origin, ray.direction, ellipsoid, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; +} + +template +void FIQuery, Ellipsoid3>::DoQuery( + Vector3 const& rayOrigin, Vector3 const& rayDirection, + Ellipsoid3 const& ellipsoid, Result& result) +{ + FIQuery, Ellipsoid3>::DoQuery(rayOrigin, + rayDirection, ellipsoid, result); + + if (result.intersect) + { + // The line containing the ray intersects the ellipsoid; the + // t-interval is [t0,t1]. The ray intersects the capsule as long as + // [t0,t1] overlaps the ray t-interval [0,+infinity). + std::array rayInterval = + { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3OrientedBox3.h new file mode 100644 index 000000000000..f28f5bef9129 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3OrientedBox3.h @@ -0,0 +1,96 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/11/28) + +#pragma once + +#include +#include + +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the six faces of +// the box. The find-intersection queries use Liang-Barsky clipping. The +// queries consider the box to be a solid. The algorithms are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace gte +{ + template + class TIQuery, OrientedBox3> + : + public TIQuery, AlignedBox3> + { + public: + struct Result + : + public TIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, OrientedBox3 const& box) + { + // Transform the ray to the oriented-box coordinate system. + Vector3 diff = ray.origin - box.center; + Vector3 rayOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 rayDirection = Vector3 + { + Dot(ray.direction, box.axis[0]), + Dot(ray.direction, box.axis[1]), + Dot(ray.direction, box.axis[2]) + }; + + Result result; + this->DoQuery(rayOrigin, rayDirection, box.extent, result); + return result; + } + }; + + template + class FIQuery, OrientedBox3> + : + public FIQuery, AlignedBox3> + { + public: + struct Result + : + public FIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, OrientedBox3 const& box) + { + // Transform the ray to the oriented-box coordinate system. + Vector3 diff = ray.origin - box.center; + Vector3 rayOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 rayDirection = Vector3 + { + Dot(ray.direction, box.axis[0]), + Dot(ray.direction, box.axis[1]), + Dot(ray.direction, box.axis[2]) + }; + + Result result; + this->DoQuery(rayOrigin, rayDirection, box.extent, result); + for (int i = 0; i < result.numPoints; ++i) + { + result.point[i] = ray.origin + result.lineParameter[i] * ray.direction; + } + return result; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Plane3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Plane3.h new file mode 100644 index 000000000000..1e29e7c6b978 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Plane3.h @@ -0,0 +1,117 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class TIQuery, Plane3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray3 const& ray, Plane3 const& plane); +}; + +template +class FIQuery, Plane3> + : + public FIQuery, Plane3> +{ +public: + struct Result + : + public FIQuery, Plane3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, Plane3 const& plane); + +protected: + void DoQuery(Vector3 const& rayOrigin, + Vector3 const& rayDirection, Plane3 const& plane, + Result& result); +}; + + +template +typename TIQuery, Plane3>::Result +TIQuery, Plane3>::operator()( + Ray3 const& ray, Plane3 const& plane) +{ + Result result; + + // Compute the (signed) distance from the ray origin to the plane. + DCPQuery, Plane3> vpQuery; + auto vpResult = vpQuery(ray.origin, plane); + + Real DdN = Dot(ray.direction, plane.normal); + if (DdN > (Real)0) + { + // The ray is not parallel to the plane and is directed toward the + // +normal side of the plane. + result.intersect = (vpResult.signedDistance <= (Real)0); + } + else if (DdN < (Real)0) + { + // The ray is not parallel to the plane and is directed toward the + // -normal side of the plane. + result.intersect = (vpResult.signedDistance >= (Real)0); + } + else + { + // The ray and plane are parallel. + result.intersect = (vpResult.distance == (Real)0); + } + + return result; +} + +template +typename FIQuery, Plane3>::Result +FIQuery, Plane3>::operator()( + Ray3 const& ray, Plane3 const& plane) +{ + Result result; + DoQuery(ray.origin, ray.direction, plane, result); + if (result.intersect) + { + result.point = ray.origin + result.parameter * ray.direction; + } + return result; +} + +template +void FIQuery, Plane3>::DoQuery( + Vector3 const& rayOrigin, Vector3 const& rayDirection, + Plane3 const& plane, Result& result) +{ + FIQuery, Plane3>::DoQuery(rayOrigin, + rayDirection, plane, result); + if (result.intersect) + { + // The line intersects the plane in a point that might not be on the + // ray. + if (result.parameter < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Sphere3.h new file mode 100644 index 000000000000..1b1324d06a13 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Sphere3.h @@ -0,0 +1,130 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray3 const& ray, Sphere3 const& sphere); +}; + +template +class FIQuery, Sphere3> + : + public FIQuery, Sphere3> +{ +public: + struct Result + : + public FIQuery, Sphere3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, Sphere3 const& sphere); + +protected: + void DoQuery(Vector3 const& rayOrigin, + Vector3 const& rayDirection, Sphere3 const& sphere, + Result& result); +}; + + +template +typename TIQuery, Sphere3>::Result +TIQuery, Sphere3>::operator()( + Ray3 const& ray, Sphere3 const& sphere) +{ + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to obtain a + // quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where a1 = D^T*(P-C), + // and a0 = (P-C)^T*(P-C)-1. + Result result; + + Vector3 diff = ray.origin - sphere.center; + Real a0 = Dot(diff, diff) - sphere.radius * sphere.radius; + if (a0 <= (Real)0) + { + // P is inside the sphere. + result.intersect = true; + return result; + } + // else: P is outside the sphere + + Real a1 = Dot(ray.direction, diff); + if (a1 >= (Real)0) + { + result.intersect = false; + return result; + } + + // Intersection occurs when Q(t) has real roots. + Real discr = a1*a1 - a0; + result.intersect = (discr >= (Real)0); + return result; +} + +template +typename FIQuery, Sphere3>::Result +FIQuery, Sphere3>::operator()( + Ray3 const& ray, Sphere3 const& sphere) +{ + Result result; + DoQuery(ray.origin, ray.direction, sphere, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; +} + +template +void FIQuery, Sphere3>::DoQuery( + Vector3 const& rayOrigin, Vector3 const& rayDirection, + Sphere3 const& sphere, Result& result) +{ + FIQuery, Sphere3>::DoQuery(rayOrigin, + rayDirection, sphere, result); + + if (result.intersect) + { + // The line containing the ray intersects the sphere; the t-interval + // is [t0,t1]. The ray intersects the sphere as long as [t0,t1] + // overlaps the ray t-interval [0,+infinity). + std::array rayInterval = + { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Triangle3.h new file mode 100644 index 000000000000..83a31cc80bf5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Triangle3.h @@ -0,0 +1,198 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Triangle3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray3 const& ray, Triangle3 const& triangle); +}; + +template +class FIQuery, Triangle3> +{ +public: + struct Result + { + Result(); + + bool intersect; + Real parameter; + Real triangleBary[3]; + Vector3 point; + }; + + Result operator()(Ray3 const& ray, Triangle3 const& triangle); +}; + + +template +typename TIQuery, Triangle3>::Result +TIQuery, Triangle3>::operator()( + Ray3 const& ray, Triangle3 const& triangle) +{ + Result result; + + // Compute the offset origin, edges, and normal. + Vector3 diff = ray.origin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(ray.direction, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Ray and triangle are parallel, call it a "no intersection" + // even if the ray does intersect. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign*DotCross(ray.direction, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign*DotCross(ray.direction, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check whether ray does. + Real QdN = -sign*Dot(diff, normal); + if (QdN >= (Real)0) + { + // Ray intersects triangle. + result.intersect = true; + return result; + } + // else: t < 0, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; +} + +template +FIQuery, Triangle3>::Result::Result() + : + parameter((Real)0) +{ + triangleBary[0] = (Real)0; + triangleBary[1] = (Real)0; + triangleBary[2] = (Real)0; +} + +template +typename FIQuery, Triangle3>::Result +FIQuery, Triangle3>::operator()( + Ray3 const& ray, Triangle3 const& triangle) +{ + Result result; + + // Compute the offset origin, edges, and normal. + Vector3 diff = ray.origin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(ray.direction, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Ray and triangle are parallel, call it a "no intersection" + // even if the ray does intersect. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign*DotCross(ray.direction, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign*DotCross(ray.direction, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check whether ray does. + Real QdN = -sign*Dot(diff, normal); + if (QdN >= (Real)0) + { + // Ray intersects triangle. + result.intersect = true; + Real inv = ((Real)1) / DdN; + result.parameter = QdN*inv; + result.triangleBary[1] = DdQxE2*inv; + result.triangleBary[2] = DdE1xQ*inv; + result.triangleBary[0] = (Real)1 - result.triangleBary[1] + - result.triangleBary[2]; + result.point = ray.origin + + result.parameter * ray.direction; + return result; + } + // else: t < 0, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2AlignedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2AlignedBox2.h new file mode 100644 index 000000000000..0ffd55fa8d33 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2AlignedBox2.h @@ -0,0 +1,175 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/04) + +#pragma once + +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the four edges of +// the box. + +namespace gte +{ + + +template +class TIQuery, AlignedBox2> + : + public TIQuery, AlignedBox2> +{ +public: + struct Result + : + public TIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment2 const& segment, + AlignedBox2 const& box); + +protected: + void DoQuery(Vector2 const& segOrigin, + Vector2 const& segDirection, Real segExtent, + Vector2 const& boxExtent, Result& result); +}; + +template +class FIQuery, AlignedBox2> + : + public FIQuery, AlignedBox2> +{ +public: + struct Result + : + public FIQuery, AlignedBox2>::Result + { + // The base class parameter[] values are t-values for the + // segment parameterization (1-t)*p[0] + t*p[1], where t in [0,1]. + // The values in this class are s-values for the centered form + // C + s * D, where s in [-e,e] and e is the extent of the segment. + std::array cdeParameter; + }; + + Result operator()(Segment2 const& segment, + AlignedBox2 const& box); + +protected: + void DoQuery(Vector2 const& segOrigin, + Vector2 const& segDirection, Real segExtent, + Vector2 const& boxExtent, Result& result); +}; + + +template +typename TIQuery, AlignedBox2>::Result +TIQuery, AlignedBox2>::operator()( + Segment2 const& segment, AlignedBox2 const& box) +{ + // Get the centered form of the aligned box. The axes are implicitly + // Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the segment to a centered form in the aligned-box coordinate + // system. + Vector2 transformedP0 = segment.p[0] - boxCenter; + Vector2 transformedP1 = segment.p[1] - boxCenter; + Segment2 transformedSegment(transformedP0, transformedP1); + Vector2 segOrigin, segDirection; + Real segExtent; + transformedSegment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, boxExtent, result); + return result; +} + +template +void TIQuery, AlignedBox2>::DoQuery( + Vector2 const& segOrigin, Vector2 const& segDirection, + Real segExtent, Vector2 const& boxExtent, Result& result) +{ + for (int i = 0; i < 2; ++i) + { + Real lhs = std::abs(segOrigin[i]); + Real rhs = boxExtent[i] + segExtent * std::abs(segDirection[i]); + if (lhs > rhs) + { + result.intersect = false; + return; + } + } + + TIQuery, AlignedBox2>::DoQuery(segOrigin, + segDirection, boxExtent, result); +} + +template +typename FIQuery, AlignedBox2>::Result +FIQuery, AlignedBox2>::operator()( + Segment2 const& segment, AlignedBox2 const& box) +{ + // Get the centered form of the aligned box. The axes are implicitly + // Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the segment to a centered form in the aligned-box coordinate + // system. + Vector2 transformedP0 = segment.p[0] - boxCenter; + Vector2 transformedP1 = segment.p[1] - boxCenter; + Segment2 transformedSegment(transformedP0, transformedP1); + Vector2 segOrigin, segDirection; + Real segExtent; + transformedSegment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, boxExtent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + // Compute the segment in the aligned-box coordinate system and then + // translate it back to the original coordinates using the box cener. + result.point[i] = boxCenter + (segOrigin + result.parameter[i] * segDirection); + result.cdeParameter[i] = result.parameter[i]; + + // Convert the parameters from the centered form to the endpoint form. + result.parameter[i] = (result.parameter[i] / segExtent + (Real)1) * (Real)0.5; + } + return result; +} + +template +void FIQuery, AlignedBox2>::DoQuery( + Vector2 const& segOrigin, Vector2 const& segDirection, + Real segExtent, Vector2 const& boxExtent, Result& result) +{ + FIQuery, AlignedBox2>::DoQuery(segOrigin, + segDirection, boxExtent, result); + + if (result.intersect) + { + // The line containing the segment intersects the box; the t-interval + // is [t0,t1]. The segment intersects the box as long as [t0,t1] + // overlaps the segment t-interval [-segExtent,+segExtent]. + std::array segInterval = {{ -segExtent, segExtent }}; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + result.intersect = iiResult.intersect; + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Arc2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Arc2.h new file mode 100644 index 000000000000..fb8a0bc97398 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Arc2.h @@ -0,0 +1,89 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/06/22) + +#pragma once + +#include +#include + +// The queries consider the arc to be a 1-dimensional object. + +namespace gte +{ + +template +class TIQuery, Arc2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment2 const& segment, Arc2 const& arc); +}; + +template +class FIQuery, Arc2> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Segment2 const& segment, Arc2 const& arc); +}; + + +template +typename TIQuery, Arc2>::Result +TIQuery, Arc2>::operator()( + Segment2 const& segment, Arc2 const& arc) +{ + Result result; + FIQuery, Arc2> saQuery; + auto saResult = saQuery(segment, arc); + result.intersect = saResult.intersect; + return result; +} + +template +typename FIQuery, Arc2>::Result +FIQuery, Arc2>::operator()( + Segment2 const& segment, Arc2 const& arc) +{ + Result result; + result.intersect = false; + result.numIntersections = 0; + + FIQuery, Circle2> scQuery; + Circle2 circle(arc.center, arc.radius); + auto scResult = scQuery(segment, circle); + if (scResult.intersect) + { + // Test whether line-circle intersections are on the arc. + for (int i = 0; i < scResult.numIntersections; ++i) + { + if (arc.Contains(scResult.point[i])) + { + result.intersect = true; + result.parameter[result.numIntersections] + = scResult.parameter[i]; + result.point[result.numIntersections++] + = scResult.point[i]; + } + } + } + + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Circle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Circle2.h new file mode 100644 index 000000000000..d550caaaa3d1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Circle2.h @@ -0,0 +1,107 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the circle to be a solid (disk). + +namespace gte +{ + +template +class TIQuery, Circle2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment2 const& segment, + Circle2 const& circle); +}; + +template +class FIQuery, Circle2> + : + public FIQuery, Circle2> +{ +public: + struct Result + : + public FIQuery, Circle2>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment2 const& segment, + Circle2 const& circle); + +protected: + void DoQuery(Vector2 const& segyOrigin, + Vector2 const& segyDirection, Real segExtent, + Circle2 const& circle, Result& result); +}; + + +template +typename TIQuery, Circle2>::Result +TIQuery, Circle2>::operator()( + Segment2 const& segment, Circle2 const& circle) +{ + Result result; + FIQuery, Circle2> scQuery; + result.intersect = scQuery(segment, circle).intersect; + return result; +} + +template +typename FIQuery, Circle2>::Result +FIQuery, Circle2>::operator()( + Segment2 const& segment, Circle2 const& circle) +{ + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, circle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; +} + +template +void FIQuery, Circle2>::DoQuery( + Vector2 const& segOrigin, Vector2 const& segDirection, + Real segExtent, Circle2 const& circle, Result& result) +{ + FIQuery, Circle2>::DoQuery(segOrigin, + segDirection, circle, result); + + if (result.intersect) + { + // The line containing the segment intersects the disk; the t-interval + // is [t0,t1]. The segment intersects the disk as long as [t0,t1] + // overlaps the segment t-interval [-segExtent,+segExtent]. + std::array segInterval = {{ -segExtent, segExtent }}; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + result.intersect = iiResult.intersect; + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2OrientedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2OrientedBox2.h new file mode 100644 index 000000000000..d2ef12984d3e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2OrientedBox2.h @@ -0,0 +1,124 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the four edges of +// the box. + +namespace gte +{ + +template +class TIQuery, OrientedBox2> + : + public TIQuery, AlignedBox2> +{ +public: + struct Result + : + public TIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment2 const& segment, + OrientedBox2 const& box); +}; + +template +class FIQuery, OrientedBox2> + : + public FIQuery, AlignedBox2> +{ +public: + struct Result + : + public FIQuery, AlignedBox2>::Result + { + // The base class parameter[] values are t-values for the + // segment parameterization (1-t)*p[0] + t*p[1], where t in [0,1]. + // The values in this class are s-values for the centered form + // C + s * D, where s in [-e,e] and e is the extent of the segment. + std::array cdeParameter; + }; + + Result operator()(Segment2 const& segment, + OrientedBox2 const& box); +}; + + +template +typename TIQuery, OrientedBox2>::Result +TIQuery, OrientedBox2>::operator()( + Segment2 const& segment, OrientedBox2 const& box) +{ + // Transform the segment to the oriented-box coordinate system. + Vector2 tmpOrigin, tmpDirection; + Real segExtent; + segment.GetCenteredForm(tmpOrigin, tmpDirection, segExtent); + Vector2 diff = tmpOrigin - box.center; + Vector2 segOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 segDirection + { + Dot(tmpDirection, box.axis[0]), + Dot(tmpDirection, box.axis[1]) + }; + + Result result; + this->DoQuery(segOrigin, segDirection, segExtent, box.extent, result); + return result; +} + +template +typename FIQuery, OrientedBox2>::Result +FIQuery, OrientedBox2>::operator()( + Segment2 const& segment, OrientedBox2 const& box) +{ + // Transform the segment to the oriented-box coordinate system. + Vector2 tmpOrigin, tmpDirection; + Real segExtent; + segment.GetCenteredForm(tmpOrigin, tmpDirection, segExtent); + Vector2 diff = tmpOrigin - box.center; + Vector2 segOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 segDirection + { + Dot(tmpDirection, box.axis[0]), + Dot(tmpDirection, box.axis[1]) + }; + + Result result; + this->DoQuery(segOrigin, segDirection, segExtent, box.extent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + // Compute the segment in the aligned-box coordinate system and then + // translate it back to the original coordinates using the box cener. + result.point[i] = box.center + (segOrigin + result.parameter[i] * segDirection); + result.cdeParameter[i] = result.parameter[i]; + + // Convert the parameters from the centered form to the endpoint form. + result.parameter[i] = (result.parameter[i] / segExtent + (Real)1) * (Real)0.5; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Segment2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Segment2.h new file mode 100644 index 000000000000..53aea0e8297e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Segment2.h @@ -0,0 +1,197 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2019/01/11) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Segment2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (segments intersect in a + // single point), or 2 (segments are collinear and intersect in a + // segment). + int numIntersections; + }; + + Result operator()(Segment2 const& segment0, + Segment2 const& segment1); +}; + +template +class FIQuery, Segment2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (segments intersect in a + // a single point), or 2 (segments are collinear and intersect + // in a segment). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point[0] + // = segment0.origin + segment0Parameter[0] * segment0.direction + // = segment1.origin + segment1Parameter[0] * segment1.direction + // If numIntersections is 2, the endpoints of the segment of + // intersection are + // point[i] + // = segment0.origin + segment0Parameter[i] * segment0.direction + // = segment1.origin + segment1Parameter[i] * segment1.direction + // with segment0Parameter[0] <= segment0Parameter[1] and + // segment1Parameter[0] <= segment1Parameter[1]. + Real segment0Parameter[2], segment1Parameter[2]; + Vector2 point[2]; + }; + + Result operator()(Segment2 const& segment0, + Segment2 const& segment1); +}; + + +template +typename TIQuery, Segment2>::Result +TIQuery, Segment2>::operator()( + Segment2 const& segment0, Segment2 const& segment1) +{ + Result result; + Vector2 seg0Origin, seg0Direction, seg1Origin, seg1Direction; + Real seg0Extent, seg1Extent; + segment0.GetCenteredForm(seg0Origin, seg0Direction, seg0Extent); + segment1.GetCenteredForm(seg1Origin, seg1Direction, seg1Extent); + + FIQuery, Line2> llQuery; + Line2 line0(seg0Origin, seg0Direction); + Line2 line1(seg1Origin, seg1Direction); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the segments. + if (std::abs(llResult.line0Parameter[0]) <= seg0Extent + && std::abs(llResult.line1Parameter[0]) <= seg1Extent) + { + result.intersect = true; + result.numIntersections = 1; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + // Compute the location of segment1 endpoints relative to segment0. + Vector2 diff = seg1Origin - seg0Origin; + Real t = Dot(seg0Direction, diff); + + // Get the parameter intervals of the segments relative to segment0. + std::array interval0 = {{ -seg0Extent, seg0Extent }}; + std::array interval1 = {{ t - seg1Extent, t + seg1Extent }}; + + // Compute the intersection of the intervals. + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(interval0, interval1); + result.intersect = iiResult.intersect; + result.numIntersections = iiResult.numIntersections; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; +} + +template +typename FIQuery, Segment2>::Result +FIQuery, Segment2>::operator()( + Segment2 const& segment0, Segment2 const& segment1) +{ + Result result; + Vector2 seg0Origin, seg0Direction, seg1Origin, seg1Direction; + Real seg0Extent, seg1Extent; + segment0.GetCenteredForm(seg0Origin, seg0Direction, seg0Extent); + segment1.GetCenteredForm(seg1Origin, seg1Direction, seg1Extent); + + FIQuery, Line2> llQuery; + Line2 line0(seg0Origin, seg0Direction); + Line2 line1(seg1Origin, seg1Direction); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the segments. + if (std::abs(llResult.line0Parameter[0]) <= seg0Extent + && std::abs(llResult.line1Parameter[0]) <= seg1Extent) + { + result.intersect = true; + result.numIntersections = 1; + result.segment0Parameter[0] = llResult.line0Parameter[0]; + result.segment1Parameter[0] = llResult.line1Parameter[0]; + result.point[0] = llResult.point; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + // Compute the location of segment1 endpoints relative to segment0. + Vector2 diff = seg1Origin - seg0Origin; + Real t = Dot(seg0Direction, diff); + + // Get the parameter intervals of the segments relative to segment0. + std::array interval0 = {{ -seg0Extent, seg0Extent }}; + std::array interval1 = {{ t - seg1Extent, t + seg1Extent }}; + + // Compute the intersection of the intervals. + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(interval0, interval1); + if (iiResult.intersect) + { + result.intersect = true; + result.numIntersections = iiResult.numIntersections; + for (int i = 0; i < iiResult.numIntersections; ++i) + { + result.segment0Parameter[i] = iiResult.overlap[i]; + result.segment1Parameter[i] = iiResult.overlap[i] - t; + result.point[i] = seg0Origin + + result.segment0Parameter[i] * seg0Direction; + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Triangle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Triangle2.h new file mode 100644 index 000000000000..ab55bd991f17 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Triangle2.h @@ -0,0 +1,104 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the triangle to be a solid. + +namespace gte +{ + +template +class TIQuery, Triangle2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment2 const& segment, + Triangle2 const& triangle); +}; + +template +class FIQuery , Triangle2> + : + public FIQuery, Triangle2> +{ +public: + struct Result + : + public FIQuery, Triangle2>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment2 const& segment, + Triangle2 const& triangle); + +protected: + void DoQuery(Vector2 const& segOrigin, + Vector2 const& segDirection, Real segExtent, + Triangle2 const& triangle, Result& result); +}; + + +template +typename TIQuery, Triangle2>::Result +TIQuery, Triangle2>::operator()( + Segment2 const& segment, Triangle2 const& triangle) +{ + Result result; + FIQuery, Triangle2> stQuery; + result.intersect = stQuery(segment, triangle).intersect; + return result; +} + +template +typename FIQuery, Triangle2>::Result +FIQuery, Triangle2>::operator()( + Segment2 const& segment, Triangle2 const& triangle) +{ + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, triangle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; +} + +template +void FIQuery, Triangle2>::DoQuery( + Vector2 const& segOrigin, Vector2 const& segDirection, + Real segExtent, Triangle2 const& triangle, Result& result) +{ + FIQuery, Triangle2>::DoQuery(segOrigin, + segDirection, triangle, result); + + if (result.intersect) + { + // The line containing the segment intersects the disk; the t-interval + // is [t0,t1]. The segment intersects the disk as long as [t0,t1] + // overlaps the segment t-interval [-segExtent,+segExtent]. + std::array segInterval = {{ -segExtent, segExtent }}; + FIQuery, std::array> iiQuery; + result.parameter = iiQuery(result.parameter, segInterval).overlap; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3AlignedBox3.h new file mode 100644 index 000000000000..31c7a8795c51 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3AlignedBox3.h @@ -0,0 +1,155 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/11/28) + +#pragma once + +#include +#include +#include + +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the six faces of +// the box. The find-intersection queries use Liang-Barsky clipping. The +// queries consider the box to be a solid. The algorithms are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace gte +{ + template + class TIQuery, AlignedBox3> + : + public TIQuery, AlignedBox3> + { + public: + struct Result + : + public TIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the segment to a centered form in the aligned-box + // coordinate system. + Vector3 transformedP0 = segment.p[0] - boxCenter; + Vector3 transformedP1 = segment.p[1] - boxCenter; + Segment3 transformedSegment(transformedP0, transformedP1); + Vector3 segOrigin, segDirection; + Real segExtent; + transformedSegment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, boxExtent, result); + return result; + } + + protected: + void DoQuery(Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Vector3 const& boxExtent, Result& result) + { + for (int i = 0; i < 3; ++i) + { + if (std::abs(segOrigin[i]) > boxExtent[i] + segExtent * std::abs(segDirection[i])) + { + result.intersect = false; + return; + } + } + + TIQuery, AlignedBox3>::DoQuery(segOrigin, segDirection, boxExtent, result); + } + }; + + template + class FIQuery, AlignedBox3> + : + public FIQuery, AlignedBox3> + { + public: + struct Result + : + public FIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the segment to a centered form in the aligned-box + // coordinate system. + Vector3 transformedP0 = segment.p[0] - boxCenter; + Vector3 transformedP1 = segment.p[1] - boxCenter; + Segment3 transformedSegment(transformedP0, transformedP1); + Vector3 segOrigin, segDirection; + Real segExtent; + transformedSegment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, boxExtent, result); + + // The segment origin is in aligned-box coordinates. Transform it + // back to the original space. + segOrigin += boxCenter; + for (int i = 0; i < result.numPoints; ++i) + { + result.point[i] = segOrigin + result.lineParameter[i] * segDirection; + } + return result; + } + + protected: + void DoQuery(Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Vector3 const& boxExtent, Result& result) + { + FIQuery, AlignedBox3>::DoQuery(segOrigin, segDirection, boxExtent, result); + if (result.intersect) + { + // The line containing the segment intersects the box; the + // t-interval is [t0,t1]. The segment intersects the box as + // long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + FIQuery, std::array> iiQuery; + + std::array interval0 = + { + result.lineParameter[0], result.lineParameter[1] + }; + + std::array interval1 = + { + -segExtent, segExtent + }; + + auto iiResult = iiQuery(interval0, interval1); + if (iiResult.numIntersections > 0) + { + result.numPoints = iiResult.numIntersections; + for (int i = 0; i < result.numPoints; ++i) + { + result.lineParameter[i] = iiResult.overlap[i]; + } + } + else + { + result.intersect = false; + result.numPoints = 0; + } + } + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Capsule3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Capsule3.h new file mode 100644 index 000000000000..9a586e0b5815 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Capsule3.h @@ -0,0 +1,118 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the capsule to be a solid. +// +// The test-intersection queries are based on distance computations. + +namespace gte +{ + +template +class TIQuery, Capsule3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment3 const& segment, + Capsule3 const& capsule); +}; + +template +class FIQuery, Capsule3> + : + public FIQuery, Capsule3> +{ +public: + struct Result + : + public FIQuery, Capsule3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, + Capsule3 const& capsule); + +protected: + void DoQuery(Vector3 const& segOrigin, + Vector3 const& segDirection, Real segExtent, + Capsule3 const& capsule, Result& result); +}; + + +template +typename TIQuery, Capsule3>::Result +TIQuery, Capsule3>::operator()( + Segment3 const& segment, Capsule3 const& capsule) +{ + Result result; + DCPQuery, Segment3> ssQuery; + auto ssResult = ssQuery(segment, capsule.segment); + result.intersect = (ssResult.distance <= capsule.radius); + return result; +} + +template +typename FIQuery, Capsule3>::Result +FIQuery, Capsule3>::operator()( + Segment3 const& segment, Capsule3 const& capsule) +{ + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, capsule, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; +} + +template +void FIQuery, Capsule3>::DoQuery( + Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Capsule3 const& capsule, Result& result) +{ + FIQuery, Capsule3>::DoQuery(segOrigin, + segDirection, capsule, result); + + if (result.intersect) + { + // The line containing the segment intersects the capsule; the + // t-interval is [t0,t1]. The segment intersects the capsule as + // long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + std::array segInterval = {{ -segExtent, segExtent }}; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Cone3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Cone3.h new file mode 100644 index 000000000000..51d59f636552 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Cone3.h @@ -0,0 +1,98 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The queries consider the cone to be single sided and solid. + +namespace gte +{ + +template +class FIQuery, Cone3> + : + public FIQuery, Cone3> +{ +public: + struct Result + : + public FIQuery, Cone3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, Cone3 const& cone); + +protected: + void DoQuery(Vector3 const& segOrigin, + Vector3 const& segDirection, Real segExtent, + Cone3 const& cone, Result& result); +}; + + +template +typename FIQuery, Cone3>::Result +FIQuery, Cone3>::operator()( + Segment3 const& segment, Cone3 const& cone) +{ + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, cone, result); + switch (result.type) + { + case 1: // point + result.point[0] = segOrigin + result.parameter[0] * segDirection; + result.point[1] = result.point[0]; + break; + case 2: // segment + result.point[0] = segOrigin + result.parameter[0] * segDirection; + result.point[1] = segOrigin + result.parameter[1] * segDirection; + break; + default: // no intersection + break; + } + return result; +} + +template +void FIQuery, Cone3>::DoQuery( + Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Cone3 const& cone, Result& result) +{ + FIQuery, Cone3>::DoQuery(segOrigin, + segDirection, cone, result); + + if (result.intersect) + { + // The line containing the segment intersects the cone; the + // t-interval is [t0,t1]. The segment intersects the cone as + // long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + std::array segInterval = {{ -segExtent, segExtent }}; + FIIntervalInterval iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + if (iiResult.intersect) + { + result.parameter = iiResult.overlap; + result.type = iiResult.numIntersections; + } + else + { + result.intersect = false; + result.type = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Cylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Cylinder3.h new file mode 100644 index 000000000000..ad4a70db5d5e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Cylinder3.h @@ -0,0 +1,91 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the cylinder to be a solid. + +namespace gte +{ + +template +class FIQuery, Cylinder3> + : + public FIQuery, Cylinder3> +{ +public: + struct Result + : + public FIQuery, Cylinder3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, + Cylinder3 const& cylinder); + +protected: + void DoQuery(Vector3 const& segOrigin, + Vector3 const& segDirection, Real segExtent, + Cylinder3 const& cylinder, Result& result); +}; + + +template +typename FIQuery, Cylinder3>::Result +FIQuery, Cylinder3>::operator()( + Segment3 const& segment, Cylinder3 const& cylinder) +{ + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, cylinder, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; +} + +template +void FIQuery, Cylinder3>::DoQuery( + Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Cylinder3 const& cylinder, Result& result) +{ + FIQuery, Cylinder3>::DoQuery(segOrigin, + segDirection, cylinder, result); + + if (result.intersect) + { + // The line containing the segment intersects the cylinder; the + // t-interval is [t0,t1]. The segment intersects the cylinder as + // long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + std::array segInterval = { -segExtent, segExtent }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Ellipsoid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Ellipsoid3.h new file mode 100644 index 000000000000..e84be9995973 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Ellipsoid3.h @@ -0,0 +1,187 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The queries consider the ellipsoid to be a solid. + +namespace gte +{ + +template +class TIQuery, Ellipsoid3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment3 const& segment, + Ellipsoid3 const& ellipsoid); +}; + +template +class FIQuery, Ellipsoid3> + : + public FIQuery, Ellipsoid3> +{ +public: + struct Result + : + public FIQuery, Ellipsoid3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, + Ellipsoid3 const& ellipsoid); + +protected: + void DoQuery(Vector3 const& segOrigin, + Vector3 const& segDirection, Real segExtent, + Ellipsoid3 const& ellipsoid, Result& result); +}; + + +template +typename TIQuery, Ellipsoid3>::Result +TIQuery, Ellipsoid3>::operator()( + Segment3 const& segment, Ellipsoid3 const& ellipsoid) +{ + // The ellipsoid is (X-K)^T*M*(X-K)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the ellipsoid equation to obtain + // a quadratic equation Q(t) = a2*t^2 + 2*a1*t + a0 = 0, where + // a2 = D^T*M*D, a1 = D^T*M*(P-K), and a0 = (P-K)^T*M*(P-K)-1. + Result result; + + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Matrix3x3 M; + ellipsoid.GetM(M); + + Vector3 diff = segOrigin - ellipsoid.center; + Vector3 matDir = M*segDirection; + Vector3 matDiff = M*diff; + Real a2 = Dot(segDirection, matDir); + Real a1 = Dot(segDirection, matDiff); + Real a0 = Dot(diff, matDiff) - (Real)1; + + Real discr = a1*a1 - a0*a2; + if (discr >= (Real)0) + { + // Test whether ray origin is inside ellipsoid. + if (a0 <= (Real)0) + { + result.intersect = true; + } + else + { + // At this point, Q(0) = a0 > 0 and Q(t) has real roots. It is + // also the case that a2 > 0, since M is positive definite, + // implying that D^T*M*D > 0 for any nonzero vector D. + Real q, qder; + if (a1 >= (Real)0) + { + // Roots are possible only on [-e,0], e is the segment extent. + // At least one root occurs if Q(-e) <= 0 or if Q(-e) > 0 and + // Q'(-e) < 0. + q = a0 + segExtent*(((Real)-2)*a1 + a2*segExtent); + if (q <= (Real)0) + { + result.intersect = true; + } + else + { + qder = a1 - a2*segExtent; + result.intersect = (qder < (Real)0); + } + } + else + { + // Roots are only possible on [0,e], e is the segment extent. + // At least one root occurs if Q(e) <= 0 or if Q(e) > 0 and + // Q'(e) > 0. + q = a0 + segExtent*(((Real)2)*a1 + a2*segExtent); + if (q <= (Real)0.0) + { + result.intersect = true; + } + else + { + qder = a1 + a2*segExtent; + result.intersect = (qder < (Real)0); + } + } + } + } + else + { + // No intersection if Q(t) has no real roots. + result.intersect = false; + } + + return result; +} + +template +typename FIQuery, Ellipsoid3>::Result +FIQuery, Ellipsoid3>::operator()( + Segment3 const& segment, Ellipsoid3 const& ellipsoid) +{ + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, ellipsoid, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; +} + +template +void FIQuery, Ellipsoid3>::DoQuery( + Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Ellipsoid3 const& ellipsoid, Result& result) +{ + FIQuery, Ellipsoid3>::DoQuery(segOrigin, + segDirection, ellipsoid, result); + + if (result.intersect) + { + // The line containing the segment intersects the ellipsoid; the + // t-interval is [t0,t1]. The segment intersects the ellipsoid as + // long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + std::array segInterval = { -segExtent, segExtent }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3OrientedBox3.h new file mode 100644 index 000000000000..53f99456bcfb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3OrientedBox3.h @@ -0,0 +1,113 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/11/28) + +#pragma once + +#include +#include + +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the six faces of +// the box. The find-intersection queries use Liang-Barsky clipping. The +// queries consider the box to be a solid. The algorithms are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace gte +{ + template + class TIQuery, OrientedBox3> + : + public TIQuery, AlignedBox3> + { + public: + struct Result + : + public TIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, OrientedBox3 const& box) + { + // Transform the segment to the oriented-box coordinate system. + Vector3 tmpOrigin, tmpDirection; + Real segExtent; + segment.GetCenteredForm(tmpOrigin, tmpDirection, segExtent); + Vector3 diff = tmpOrigin - box.center; + Vector3 segOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 segDirection + { + Dot(tmpDirection, box.axis[0]), + Dot(tmpDirection, box.axis[1]), + Dot(tmpDirection, box.axis[2]) + }; + + Result result; + this->DoQuery(segOrigin, segDirection, segExtent, box.extent, result); + return result; + } + }; + + template + class FIQuery, OrientedBox3> + : + public FIQuery, AlignedBox3> + { + public: + struct Result + : + public FIQuery, AlignedBox3>::Result + { + // No additional relevant information to compute. + }; + + Result operator()(Segment3 const& segment, OrientedBox3 const& box) + { + // Transform the segment to the oriented-box coordinate system. + Vector3 tmpOrigin, tmpDirection; + Real segExtent; + segment.GetCenteredForm(tmpOrigin, tmpDirection, segExtent); + Vector3 diff = tmpOrigin - box.center; + Vector3 segOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 segDirection + { + Dot(tmpDirection, box.axis[0]), + Dot(tmpDirection, box.axis[1]), + Dot(tmpDirection, box.axis[2]) + }; + + Result result; + this->DoQuery(segOrigin, segDirection, segExtent, box.extent, result); + for (int i = 0; i < result.numPoints; ++i) + { + // Compute the intersection point in the oriented-box + // coordinate system. + Vector3 y = segOrigin + result.lineParameter[i] * segDirection; + + // Transform the intersection point to the original coordinate + // system. + result.point[i] = box.center; + for (int j = 0; j < 3; ++j) + { + result.point[i] += y[j] * box.axis[j]; + } + } + + return result; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Plane3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Plane3.h new file mode 100644 index 000000000000..cee46cdbcbc7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Plane3.h @@ -0,0 +1,120 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class TIQuery, Plane3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment3 const& segment, + Plane3 const& plane); +}; + +template +class FIQuery, Plane3> + : + public FIQuery, Plane3> +{ +public: + struct Result + : + public FIQuery, Plane3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, + Plane3 const& plane); + +protected: + void DoQuery(Vector3 const& segOrigin, + Vector3 const& segDirection, Real segExtent, + Plane3 const& plane, Result& result); +}; + + +template +typename TIQuery, Plane3>::Result +TIQuery, Plane3>::operator()( + Segment3 const& segment, Plane3 const& plane) +{ + Result result; + + // Compute the (signed) distance from the segment endpoints to the plane. + DCPQuery, Plane3> vpQuery; + Real sdistance0 = vpQuery(segment.p[0], plane).signedDistance; + if (sdistance0 == (Real)0) + { + // Endpoint p[0] is on the plane. + result.intersect = true; + return result; + } + + Real sdistance1 = vpQuery(segment.p[1], plane).signedDistance; + if (sdistance1 == (Real)0) + { + // Endpoint p[1] is on the plane. + result.intersect = true; + return result; + } + + // Test whether the segment transversely intersects the plane. + result.intersect = (sdistance0 * sdistance1 < (Real)0); + return result; +} + +template +typename FIQuery, Plane3>::Result +FIQuery, Plane3>::operator()( + Segment3 const& segment, Plane3 const& plane) +{ + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, plane, result); + if (result.intersect) + { + result.point = segOrigin + result.parameter * segDirection; + } + return result; +} + +template +void FIQuery, Plane3>::DoQuery( + Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Plane3 const& plane, Result& result) +{ + FIQuery, Plane3>::DoQuery(segOrigin, + segDirection, plane, result); + if (result.intersect) + { + // The line intersects the plane in a point that might not be on the + // segment. + if (std::abs(result.parameter) > segExtent) + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Sphere3.h new file mode 100644 index 000000000000..801f247e6633 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Sphere3.h @@ -0,0 +1,141 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment3 const& segment, + Sphere3 const& sphere); +}; + +template +class FIQuery, Sphere3> + : + public FIQuery, Sphere3> +{ +public: + struct Result + : + public FIQuery, Sphere3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, + Sphere3 const& sphere); + +protected: + void DoQuery(Vector3 const& segOrigin, + Vector3 const& segDirection, Real segExtent, + Sphere3 const& sphere, Result& result); +}; + + +template +typename TIQuery, Sphere3>::Result +TIQuery, Sphere3>::operator()( + Segment3 const& segment, Sphere3 const& sphere) +{ + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to obtain a + // quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where a1 = D^T*(P-C), + // and a0 = (P-C)^T*(P-C)-1. + Result result; + + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Vector3 diff = segOrigin - sphere.center; + Real a0 = Dot(diff, diff) - sphere.radius * sphere.radius; + Real a1 = Dot(segDirection, diff); + Real discr = a1*a1 - a0; + if (discr < (Real)0) + { + result.intersect = false; + return result; + } + + Real tmp0 = segExtent*segExtent + a0; + Real tmp1 = ((Real)2)*a1*segExtent; + Real qm = tmp0 - tmp1; + Real qp = tmp0 + tmp1; + if (qm*qp <= (Real)0) + { + result.intersect = true; + return result; + } + + result.intersect = (qm > (Real)0 && std::abs(a1) < segExtent); + return result; +} + +template +typename FIQuery, Sphere3>::Result +FIQuery, Sphere3>::operator()( + Segment3 const& segment, Sphere3 const& sphere) +{ + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, sphere, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; +} + +template +void FIQuery, Sphere3>::DoQuery( + Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Sphere3 const& sphere, Result& result) +{ + FIQuery, Sphere3>::DoQuery(segOrigin, + segDirection, sphere, result); + + if (result.intersect) + { + // The line containing the segment intersects the sphere; the + // t-interval is [t0,t1]. The segment intersects the sphere as + // long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + std::array segInterval = { -segExtent, segExtent }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Triangle3.h new file mode 100644 index 000000000000..714243fd0083 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Triangle3.h @@ -0,0 +1,210 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Triangle3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment3 const& segment, + Triangle3 const& triangle); +}; + +template +class FIQuery, Triangle3> +{ +public: + struct Result + { + Result(); + + bool intersect; + Real parameter; + Real triangleBary[3]; + Vector3 point; + }; + + Result operator()(Segment3 const& segment, + Triangle3 const& triangle); +}; + + +template +typename TIQuery, Triangle3>::Result +TIQuery, Triangle3>::operator()( + Segment3 const& segment, Triangle3 const& triangle) +{ + Result result; + + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + // Compute the offset origin, edges, and normal. + Vector3 diff = segOrigin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = diff, D = segment direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(segDirection, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Segment and triangle are parallel, call it a "no intersection" + // even if the segment does intersect. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign*DotCross(segDirection, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign*DotCross(segDirection, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check whether segment does. + Real QdN = -sign*Dot(diff, normal); + Real extDdN = segExtent*DdN; + if (-extDdN <= QdN && QdN <= extDdN) + { + // Segment intersects triangle. + result.intersect = true; + return result; + } + // else: |t| > extent, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; +} + +template +FIQuery, Triangle3>::Result::Result() + : + parameter((Real)0) +{ + triangleBary[0] = (Real)0; + triangleBary[1] = (Real)0; + triangleBary[2] = (Real)0; +} + +template +typename FIQuery, Triangle3>::Result +FIQuery, Triangle3>::operator()( + Segment3 const& segment, Triangle3 const& triangle) +{ + Result result; + + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + // Compute the offset origin, edges, and normal. + Vector3 diff = segOrigin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = diff, D = segment direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(segDirection, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Segment and triangle are parallel, call it a "no intersection" + // even if the segment does intersect. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign*DotCross(segDirection, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign*DotCross(segDirection, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check whether segment does. + Real QdN = -sign*Dot(diff, normal); + Real extDdN = segExtent*DdN; + if (-extDdN <= QdN && QdN <= extDdN) + { + // Segment intersects triangle. + result.intersect = true; + Real inv = ((Real)1) / DdN; + result.parameter = QdN*inv; + result.triangleBary[1] = DdQxE2*inv; + result.triangleBary[2] = DdE1xQ*inv; + result.triangleBary[0] = (Real)1 - result.triangleBary[1] + - result.triangleBary[2]; + result.point = segOrigin + + result.parameter * segDirection; + return result; + } + // else: |t| > extent, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Cone3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Cone3.h new file mode 100644 index 000000000000..a51839360e11 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Cone3.h @@ -0,0 +1,341 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2019/02/15) + +#pragma once + +#include +#include +#include +#include +#include + +// The test-intersection query is based on the document +// https://www.geometrictools.com/Documentation/IntersectionSphereCone.pdf +// +// The find-intersection returns a single point in the set of intersection +// when that intersection is not empty. + +namespace gte +{ + template + class TIQuery, Cone3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Sphere3 const& sphere, Cone3 const& cone) + { + Result result; + if (cone.minHeight > (Real)0) + { + if (cone.maxHeight < std::numeric_limits::max()) + { + result.intersect = DoQueryConeFrustum(sphere, cone); + } + else + { + result.intersect = DoQueryInfiniteTruncatedCone(sphere, cone); + } + } + else + { + if (cone.maxHeight < std::numeric_limits::max()) + { + result.intersect = DoQueryFiniteCone(sphere, cone); + } + else + { + result.intersect = DoQueryInfiniteCone(sphere, cone); + } + } + return result; + } + + private: + bool DoQueryInfiniteCone(Sphere3 const& sphere, Cone3 const& cone) + { + Vector3 U = cone.ray.origin - (sphere.radius * cone.invSinAngle) * cone.ray.direction; + Vector3 CmU = sphere.center - U; + Real AdCmU = Dot(cone.ray.direction, CmU); + if (AdCmU > (Real)0) + { + Real sqrLengthCmU = Dot(CmU, CmU); + if (AdCmU * AdCmU >= sqrLengthCmU * cone.cosAngleSqr) + { + Vector3 CmV = sphere.center - cone.ray.origin; + Real AdCmV = Dot(cone.ray.direction, CmV); + if (AdCmV < -sphere.radius) + { + return false; + } + + Real rSinAngle = sphere.radius * cone.sinAngle; + if (AdCmV >= -rSinAngle) + { + return true; + } + + Real sqrLengthCmV = Dot(CmV, CmV); + return sqrLengthCmV <= sphere.radius * sphere.radius; + } + } + + return false; + } + + bool DoQueryInfiniteTruncatedCone(Sphere3 const& sphere, Cone3 const& cone) + { + Vector3 U = cone.ray.origin - (sphere.radius * cone.invSinAngle) * cone.ray.direction; + Vector3 CmU = sphere.center - U; + Real AdCmU = Dot(cone.ray.direction, CmU); + if (AdCmU > (Real)0) + { + Real sqrLengthCmU = Dot(CmU, CmU); + if (AdCmU * AdCmU >= sqrLengthCmU * cone.cosAngleSqr) + { + Vector3 CmV = sphere.center - cone.ray.origin; + Real AdCmV = Dot(cone.ray.direction, CmV); + if (AdCmV < cone.minHeight - sphere.radius) + { + return false; + } + + Real rSinAngle = sphere.radius * cone.sinAngle; + if (AdCmV >= -rSinAngle) + { + return true; + } + + Vector3 D = CmV - cone.minHeight * cone.ray.direction; + Real lengthAxD = Length(Cross(cone.ray.direction, D)); + Real hminTanAngle = cone.minHeight * cone.tanAngle; + if (lengthAxD <= hminTanAngle) + { + return true; + } + + Real AdD = AdCmV - cone.minHeight; + Real diff = lengthAxD - hminTanAngle; + Real sqrLengthCmK = AdD * AdD + diff * diff; + return sqrLengthCmK <= sphere.radius * sphere.radius; + } + } + + return false; + } + + bool DoQueryFiniteCone(Sphere3 const& sphere, Cone3 const& cone) + { + Vector3 U = cone.ray.origin - (sphere.radius * cone.invSinAngle) * cone.ray.direction; + Vector3 CmU = sphere.center - U; + Real AdCmU = Dot(cone.ray.direction, CmU); + if (AdCmU > (Real)0) + { + Real sqrLengthCmU = Dot(CmU, CmU); + if (AdCmU * AdCmU >= sqrLengthCmU * cone.cosAngleSqr) + { + Vector3 CmV = sphere.center - cone.ray.origin; + Real AdCmV = Dot(cone.ray.direction, CmV); + if (AdCmV < -sphere.radius) + { + return false; + } + + if (AdCmV > cone.maxHeight + sphere.radius) + { + return false; + } + + Real rSinAngle = sphere.radius * cone.sinAngle; + if (AdCmV >= -rSinAngle) + { + if (AdCmV <= cone.maxHeight - rSinAngle) + { + return true; + } + else + { + Vector3 barD = CmV - cone.maxHeight * cone.ray.direction; + Real lengthAxBarD = Length(Cross(cone.ray.direction, barD)); + Real hmaxTanAngle = cone.maxHeight * cone.tanAngle; + if (lengthAxBarD <= hmaxTanAngle) + { + return true; + } + + Real AdBarD = AdCmV - cone.maxHeight; + Real diff = lengthAxBarD - hmaxTanAngle; + Real sqrLengthCmBarK = AdBarD * AdBarD + diff * diff; + return sqrLengthCmBarK <= sphere.radius * sphere.radius; + } + } + else + { + Real sqrLengthCmV = Dot(CmV, CmV); + return sqrLengthCmV <= sphere.radius * sphere.radius; + } + } + } + + return false; + } + + bool DoQueryConeFrustum(Sphere3 const& sphere, Cone3 const& cone) + { + Vector3 U = cone.ray.origin - (sphere.radius * cone.invSinAngle) * cone.ray.direction; + Vector3 CmU = sphere.center - U; + Real AdCmU = Dot(cone.ray.direction, CmU); + if (AdCmU > (Real)0) + { + Real sqrLengthCmU = Dot(CmU, CmU); + if (AdCmU * AdCmU >= sqrLengthCmU * cone.cosAngleSqr) + { + Vector3 CmV = sphere.center - cone.ray.origin; + Real AdCmV = Dot(cone.ray.direction, CmV); + if (AdCmV < cone.minHeight - sphere.radius) + { + return false; + } + + if (AdCmV > cone.maxHeight + sphere.radius) + { + return false; + } + + Real rSinAngle = sphere.radius * cone.sinAngle; + if (AdCmV >= cone.minHeight - rSinAngle) + { + if (AdCmV <= cone.maxHeight - rSinAngle) + { + return true; + } + else + { + Vector3 barD = CmV - cone.maxHeight * cone.ray.direction; + Real lengthAxBarD = Length(Cross(cone.ray.direction, barD)); + Real hmaxTanAngle = cone.maxHeight * cone.tanAngle; + if (lengthAxBarD <= hmaxTanAngle) + { + return true; + } + + Real AdBarD = AdCmV - cone.maxHeight; + Real diff = lengthAxBarD - hmaxTanAngle; + Real sqrLengthCmBarK = AdBarD * AdBarD + diff * diff; + return sqrLengthCmBarK <= sphere.radius * sphere.radius; + } + } + else + { + Vector3 D = CmV - cone.minHeight * cone.ray.direction; + Real lengthAxD = Length(Cross(cone.ray.direction, D)); + Real hminTanAngle = cone.minHeight * cone.tanAngle; + if (lengthAxD <= hminTanAngle) + { + return true; + } + + Real AdD = AdCmV - cone.minHeight; + Real diff = lengthAxD - hminTanAngle; + Real sqrLengthCmK = AdD * AdD + diff * diff; + return sqrLengthCmK <= sphere.radius * sphere.radius; + } + } + } + + return false; + } + }; + + template + class FIQuery, Cone3> + { + public: + struct Result + { + // If an intersection occurs, it is potentially an infinite set. + // If the cone vertex is inside the sphere, 'point' is set to the + // cone vertex. If the sphere center is inside the cone, 'point' + // is set to the sphere center. Otherwise, 'point' is set to the + // cone point that is closest to the cone vertex and inside the + // sphere. + bool intersect; + Vector3 point; + }; + + Result operator()(Sphere3 const& sphere, Cone3 const& cone) + { + Result result; + + // Test whether the cone vertex is inside the sphere. + Vector3 diff = sphere.center - cone.ray.origin; + Real rSqr = sphere.radius * sphere.radius; + Real lenSqr = Dot(diff, diff); + if (lenSqr <= rSqr) + { + // The cone vertex is inside the sphere, so the sphere and + // cone intersect. + result.intersect = true; + result.point = cone.ray.origin; + return result; + } + + // Test whether the sphere center is inside the cone. + Real dot = Dot(diff, cone.ray.direction); + Real dotSqr = dot * dot; + if (dotSqr >= lenSqr * cone.cosAngleSqr && dot > (Real)0) + { + // The sphere center is inside cone, so the sphere and cone + // intersect. + result.intersect = true; + result.point = sphere.center; + return result; + } + + // The sphere center is outside the cone. The problem now reduces + // to computing an intersection between the circle and the ray in + // the plane containing the cone vertex and spanned by the cone + // axis and vector from the cone vertex to the sphere center. + + // The ray is parameterized by t * D + V with t >= 0, |D| = 1 and + // dot(A,D) = cos(angle). Also, D = e * A + f * (C - V). + // Substituting the ray equation into the sphere equation yields + // R^2 = |t * D + V - C|^2, so the quadratic for intersections is + // t^2 - 2 * dot(D, C - V) * t + |C - V|^2 - R^2 = 0. An + // intersection occurs if and only if the discriminant is + // nonnegative. This test becomes + // dot(D, C - V)^2 >= dot(C - V, C - V) - R^2 + // Note that if the right-hand side is nonpositive, then the + // inequality is true (the sphere contains V). This is already + // ruled out in the first block of code in this function. + + Real uLen = std::sqrt(std::max(lenSqr - dotSqr, (Real)0)); + Real test = cone.cosAngle * dot + cone.sinAngle * uLen; + Real discr = test * test - lenSqr + rSqr; + + if (discr >= (Real)0 && test >= (Real)0) + { + // Compute the point of intersection closest to the cone + // vertex. + result.intersect = true; + Real t = test - std::sqrt(std::max(discr, (Real)0)); + Vector3 B = diff - dot * cone.ray.direction; + Real tmp = cone.sinAngle / uLen; + result.point = t * (cone.cosAngle * cone.ray.direction + tmp * B); + } + else + { + result.intersect = false; + } + + return result; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Frustum3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Frustum3.h new file mode 100644 index 000000000000..6a55e82ea368 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Frustum3.h @@ -0,0 +1,44 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Frustum3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Sphere3 const& sphere, + Frustum3 const& frustum); +}; + + +template +typename TIQuery, Frustum3>::Result +TIQuery, Frustum3>::operator()( + Sphere3 const& sphere, Frustum3 const& frustum) +{ + Result result; + DCPQuery, Frustum3> vfQuery; + Real distance = vfQuery(sphere.center, frustum).distance; + result.intersect = (distance <= sphere.radius); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Sphere3.h new file mode 100644 index 000000000000..bbc07cbef243 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Sphere3.h @@ -0,0 +1,152 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +// The queries consider the spheres to be solids. + +namespace gte +{ + +template +class TIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Sphere3 const& sphere0, + Sphere3 const& sphere1); +}; + +template +class FIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + + // The type of intersection. + // 0: spheres are disjoint and separated + // 1: spheres touch at point, each sphere outside the other + // 2: spheres intersect in a circle + // 3: sphere0 strictly contained in sphere1 + // 4: sphere0 contained in sphere1, share common point + // 5: sphere1 strictly contained in sphere0 + // 6: sphere1 contained in sphere0, share common point + int type; + Vector3 point; // types 1, 4, 6 + Circle3 circle; // type 2 + }; + + Result operator()(Sphere3 const& sphere0, + Sphere3 const& sphere1); +}; + + +template +typename TIQuery, Sphere3>::Result +TIQuery, Sphere3>::operator()( + Sphere3 const& sphere0, Sphere3 const& sphere1) +{ + Result result; + Vector3 diff = sphere1.center - sphere0.center; + Real rSum = sphere0.radius + sphere1.radius; + result.intersect = (Dot(diff, diff) <= rSum*rSum); + return result; +} + +template +typename FIQuery, Sphere3>::Result +FIQuery, Sphere3>::operator()( + Sphere3 const& sphere0, Sphere3 const& sphere1) +{ + Result result; + + // The plane of intersection must have C1-C0 as its normal direction. + Vector3 C1mC0 = sphere1.center - sphere0.center; + Real sqrLen = Dot(C1mC0, C1mC0); + Real r0 = sphere0.radius, r1 = sphere1.radius; + Real rSum = r0 + r1; + Real rSumSqr = rSum * rSum; + + if (sqrLen > rSumSqr) + { + // The spheres are disjoint/separated. + result.intersect = false; + result.type = 0; + return result; + } + + if (sqrLen == rSumSqr) + { + // The spheres are just touching with each sphere outside the other. + Normalize(C1mC0); + result.intersect = true; + result.type = 1; + result.point = sphere0.center + r0 * C1mC0; + return result; + } + + Real rDif = r0 - r1; + Real rDifSqr = rDif*rDif; + if (sqrLen < rDifSqr) + { + // One sphere is strictly contained in the other. Compute a point in + // the intersection set. + result.intersect = true; + result.type = (rDif <= (Real)0 ? 3 : 5); + result.point = ((Real)0.5)*(sphere0.center + sphere1.center); + return result; + } + if (sqrLen == rDifSqr) + { + // One sphere is contained in the other sphere but with a single point + // of contact. + Normalize(C1mC0); + result.intersect = true; + if (rDif <= (Real)0) + { + result.type = 4; + result.point = sphere1.center + r1 * C1mC0; + } + else + { + result.type = 6; + result.point = sphere0.center + r0 * C1mC0; + } + return result; + } + + // Compute t for which the circle of intersection has center + // K = C0 + t*(C1 - C0). + Real t = ((Real)0.5) * ((Real)1 + rDif * rSum / sqrLen); + + // Compute the center and radius of the circle of intersection. + result.circle.center = sphere0.center + t * C1mC0; + result.circle.radius = std::sqrt(std::max(r0*r0 - t*t*sqrLen, (Real)0)); + + // Compute the normal for the plane of the circle. + Normalize(C1mC0); + result.circle.normal = C1mC0; + + // The intersection is a circle. + result.intersect = true; + result.type = 2; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Triangle3.h new file mode 100644 index 000000000000..944b154717b7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Triangle3.h @@ -0,0 +1,591 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.21.0 (2019/01/21) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + // Currently, only a dynamic query is supported. A static query will + // need to compute the intersection set of triangle and sphere. + + template + class FIQuery, Triangle3> + { + public: + // The implementation for floating-point types. + struct Result + { + // The cases are + // 1. Objects initially overlapping. The contactPoint is only one + // of infinitely many points in the overlap. + // intersectionType = -1 + // contactTime = 0 + // contactPoint = triangle point closest to sphere.center + // 2. Objects initially separated but do not intersect later. The + // contactTime and contactPoint are invalid. + // intersectionType = 0 + // contactTime = 0 + // contactPoint = (0,0,0) + // 3. Objects initially separated but intersect later. + // intersectionType = +1 + // contactTime = first time T > 0 + // contactPoint = corresponding first contact + int intersectionType; + Real contactTime; + Vector3 contactPoint; + }; + + template + typename std::enable_if::value, Result>::type + operator()(Sphere3 const& sphere, Vector3 const& sphereVelocity, + Triangle3 const& triangle, Vector3 const& triangleVelocity) + { + Result result = { 0, (Real)0, { (Real)0, (Real)0, (Real)0 } }; + + // Test for initial overlap or contact. + DCPQuery, Triangle3> ptQuery; + auto ptResult = ptQuery(sphere.center, triangle); + Real rsqr = sphere.radius * sphere.radius; + if (ptResult.sqrDistance <= rsqr) + { + result.intersectionType = (ptResult.sqrDistance < rsqr ? -1 : +1); + result.contactTime = (Real)0; + result.contactPoint = ptResult.closest; + return result; + } + + // To reach here, the sphere and triangle are initially separated. + // Compute the velocity of the sphere relative to the triangle. + Vector3 V = sphereVelocity - triangleVelocity; + Real sqrLenV = Dot(V, V); + if (sqrLenV == (Real)0) + { + // The sphere and triangle are separated and the sphere is not + // moving relative to the triangle, so there is no contact. + // The 'result' is already set to the correct state for this + // case. + return result; + } + + // Compute the triangle edge directions E[], the vector U normal + // to the plane of the triangle, and compute the normals to the + // edges in the plane of the triangle. TODO: For a nondeforming + // triangle (or mesh of triangles), these quantities can all be + // precomputed to reduce the computational cost of the query. Add + // another operator()-query that accepts the precomputed values. + // TODO: When the triangle is deformable, these quantities must be + // computed, either by the caller or here. Optimize the code to + // compute the quantities on-demand (i.e. only when they are + // needed, but cache them for later use). + Vector3 E[3] = + { + triangle.v[1] - triangle.v[0], + triangle.v[2] - triangle.v[1], + triangle.v[0] - triangle.v[2] + }; + Real sqrLenE[3] = { Dot(E[0], E[0]), Dot(E[1], E[1]), Dot(E[2], E[2]) }; + Vector3 U = UnitCross(E[0], E[1]); + Vector3 ExU[3] = + { + Cross(E[0], U), + Cross(E[1], U), + Cross(E[2], U) + }; + + // Compute the vectors from the triangle vertices to the sphere + // center. + Vector3 Delta[3] = + { + sphere.center - triangle.v[0], + sphere.center - triangle.v[1], + sphere.center - triangle.v[2] + }; + + // Determine where the sphere center is located relative to the + // planes of the triangle offset faces of the sphere-swept volume. + Real dotUDelta0 = Dot(U, Delta[0]); + if (dotUDelta0 >= sphere.radius) + { + // The sphere is on the positive side of Dot(U,X-C) = r. If + // the sphere will contact the sphere-swept volume at a + // triangular face, it can do so only on the face of the + // aforementioned plane. + Real dotUV = Dot(U, V); + if (dotUV >= (Real)0) + { + // The sphere is moving away from, or parallel to, the + // plane of the triangle. The 'result' is already set to + // the correct state for this case. + return result; + } + + Real tbar = (sphere.radius - dotUDelta0) / dotUV; + bool foundContact = true; + for (int i = 0; i < 3; ++i) + { + Real phi = Dot(ExU[i], Delta[i]); + Real psi = Dot(ExU[i], V); + if (phi + psi * tbar > (Real)0) + { + foundContact = false; + break; + } + } + if (foundContact) + { + result.intersectionType = 1; + result.contactTime = tbar; + result.contactPoint = sphere.center + tbar * sphereVelocity; + return result; + } + } + else if (dotUDelta0 <= -sphere.radius) + { + // The sphere is on the positive side of Dot(-U,X-C) = r. If + // the sphere will contact the sphere-swept volume at a + // triangular face, it can do so only on the face of the + // aforementioned plane. + Real dotUV = Dot(U, V); + if (dotUV <= (Real)0) + { + // The sphere is moving away from, or parallel to, the + // plane of the triangle. The 'result' is already set to + // the correct state for this case. + return result; + } + + Real tbar = (-sphere.radius - dotUDelta0) / dotUV; + bool foundContact = true; + for (int i = 0; i < 3; ++i) + { + Real phi = Dot(ExU[i], Delta[i]); + Real psi = Dot(ExU[i], V); + if (phi + psi * tbar > (Real)0) + { + foundContact = false; + break; + } + } + if (foundContact) + { + result.intersectionType = 1; + result.contactTime = tbar; + result.contactPoint = sphere.center + tbar * sphereVelocity; + return result; + } + } + // else: The ray-sphere-swept-volume contact point (if any) cannot + // be on a triangular face of the sphere-swept-volume. + + // The sphere is moving towards the slab between the two planes + // of the sphere-swept volume triangular faces. Determine whether + // the ray intersects the half cylinders or sphere wedges of the + // sphere-swept volume. + + // Test for contact with half cylinders of the sphere-swept + // volume. First, precompute some dot products required in the + // computations. TODO: Optimize the code to compute the quantities + // on-demand (i.e. only when they are needed, but cache them for + // later use). + Real del[3], delp[3], nu[3]; + for (int im1 = 2, i = 0; i < 3; im1 = i++) + { + del[i] = Dot(E[i], Delta[i]); + delp[im1] = Dot(E[im1], Delta[i]); + nu[i] = Dot(E[i], V); + } + + for (int i = 2, ip1 = 0; ip1 < 3; i = ip1++) + { + Vector3 hatV = V - E[i] * nu[i] / sqrLenE[i]; + Real sqrLenHatV = Dot(hatV, hatV); + if (sqrLenHatV > (Real)0) + { + Vector3 hatDelta = Delta[i] - E[i] * del[i] / sqrLenE[i]; + Real alpha = -Dot(hatV, hatDelta); + if (alpha >= (Real)0) + { + Real sqrLenHatDelta = Dot(hatDelta, hatDelta); + Real beta = alpha * alpha - sqrLenHatV * (sqrLenHatDelta - rsqr); + if (beta >= (Real)0) + { + Real tbar = (alpha - std::sqrt(beta)) / sqrLenHatV; + + Real mu = Dot(ExU[i], Delta[i]); + Real omega = Dot(ExU[i], hatV); + if (mu + omega * tbar >= (Real)0) + { + if (del[i] + nu[i] * tbar >= (Real)0) + { + if (delp[i] + nu[i] * tbar <= (Real)0) + { + // The constraints are satisfied, so + // tbar is the first time of contact. + result.intersectionType = 1; + result.contactTime = tbar; + result.contactPoint = sphere.center + tbar * sphereVelocity; + return result; + } + } + } + } + } + } + } + + // Test for contact with sphere wedges of the sphere-swept + // volume. We know that |V|^2 > 0 because of a previous + // early-exit test. + for (int im1 = 2, i = 0; i < 3; im1 = i++) + { + Real alpha = -Dot(V, Delta[i]); + if (alpha >= (Real)0) + { + Real sqrLenDelta = Dot(Delta[i], Delta[i]); + Real beta = alpha * alpha - sqrLenV * (sqrLenDelta - rsqr); + if (beta >= (Real)0) + { + Real tbar = (alpha - std::sqrt(beta)) / sqrLenV; + if (delp[im1] + nu[im1] * tbar >= (Real)0) + { + if (del[i] + nu[i] * tbar <= (Real)0) + { + // The constraints are satisfied, so tbar + // is the first time of contact. + result.intersectionType = 1; + result.contactTime = tbar; + result.contactPoint = sphere.center + tbar * sphereVelocity; + return result; + } + } + } + } + } + + // The ray and sphere-swept volume do not intersect, so the sphere + // and triangle do not come into contact. The 'result' is already + // set to the correct state for this case. + return result; + } + + + // The implementation for arbitrary-precision types. + typedef typename QuadraticField::Element QFElement; + + struct ExactResult + { + // The cases are + // 1. Objects initially overlapping. The contactPoint is only one + // of infinitely many points in the overlap. + // intersectionType = -1 + // contactTime = 0 + // contactPoint = triangle point closest to sphere.center + // 2. Objects initially separated but do not intersect later. The + // contactTime and contactPoint are invalid. + // intersectionType = 0 + // contactTime = 0 + // contactPoint = (0,0,0) + // 3. Objects initially separated but intersect later. + // intersectionType = +1 + // contactTime = first time T > 0 + // contactPoint = corresponding first contact + int intersectionType; + + // The exact representation of the contact time and point. To + // convert to a floating-point type, use + // FloatType contactTime = field.Convert(result.contactTime); + // Vector3 contactPoint + // { + // field.Convert(result.contactPoint[0]), + // field.Convert(result.contactPoint[1]), + // field.Convert(result.contactPoint[2]) + // }; + QuadraticField field; + QFElement contactTime; + Vector3 contactPoint; + }; + + template + typename std::enable_if::value, ExactResult>::type + operator()(Sphere3 const& sphere, Vector3 const& sphereVelocity, + Triangle3 const& triangle, Vector3 const& triangleVelocity) + { + // The default constructors for the members of 'result' set their + // own members to zero. + ExactResult result; + + // Test for initial overlap or contact. + DCPQuery, Triangle3> ptQuery; + auto ptResult = ptQuery(sphere.center, triangle); + Real rsqr = sphere.radius * sphere.radius; + if (ptResult.sqrDistance <= rsqr) + { + // The values result.field, result.contactTime and + // result.contactPoint[] are all zero, so we need only set + // the result.contactPoint[].x values. + result.intersectionType = (ptResult.sqrDistance < rsqr ? -1 : +1); + for (int j = 0; j < 3; ++j) + { + result.contactPoint[j].x = ptResult.closest[j]; + } + return result; + } + + // To reach here, the sphere and triangle are initially separated. + // Compute the velocity of the sphere relative to the triangle. + Vector3 V = sphereVelocity - triangleVelocity; + Real sqrLenV = Dot(V, V); + if (sqrLenV == (Real)0) + { + // The sphere and triangle are separated and the sphere is not + // moving relative to the triangle, so there is no contact. + // The 'result' is already set to the correct state for this + // case. + return result; + } + + // Compute the triangle edge directions E[], the vector U normal + // to the plane of the triangle, and compute the normals to the + // edges in the plane of the triangle. TODO: For a nondeforming + // triangle (or mesh of triangles), these quantities can all be + // precomputed to reduce the computational cost of the query. Add + // another operator()-query that accepts the precomputed values. + // TODO: When the triangle is deformable, these quantities must be + // computed, either by the caller or here. Optimize the code to + // compute the quantities on-demand (i.e. only when they are + // needed, but cache them for later use). + Vector3 E[3] = + { + triangle.v[1] - triangle.v[0], + triangle.v[2] - triangle.v[1], + triangle.v[0] - triangle.v[2] + }; + Real sqrLenE[3] = { Dot(E[0], E[0]), Dot(E[1], E[1]), Dot(E[2], E[2]) }; + // Use an unnormalized U for the plane of the triangle. This + // allows us to use quadratic fields for the comparisons of the + // constraints. + Vector3 U = Cross(E[0], E[1]); + Real sqrLenU = Dot(U, U); + Vector3 ExU[3] = + { + Cross(E[0], U), + Cross(E[1], U), + Cross(E[2], U) + }; + + // Compute the vectors from the triangle vertices to the sphere + // center. + Vector3 Delta[3] = + { + sphere.center - triangle.v[0], + sphere.center - triangle.v[1], + sphere.center - triangle.v[2] + }; + + // Determine where the sphere center is located relative to the + // planes of the triangle offset faces of the sphere-swept volume. + QuadraticField ufield(sqrLenU); + QFElement element(Dot(U, Delta[0]), -sphere.radius); + if (ufield.GreaterThanOrEqualZero(element)) + { + // The sphere is on the positive side of Dot(U,X-C) = r|U|. + // If the sphere will contact the sphere-swept volume at a + // triangular face, it can do so only on the face of the + // aforementioned plane. + Real dotUV = Dot(U, V); + if (dotUV >= (Real)0) + { + // The sphere is moving away from, or parallel to, the + // plane of the triangle. The 'result' is already set + // to the correct state for this case. + return result; + } + + bool foundContact = true; + for (int i = 0; i < 3; ++i) + { + Real phi = Dot(ExU[i], Delta[i]); + Real psi = Dot(ExU[i], V); + QFElement arg(psi * element.x - phi * dotUV, psi * element.y); + if (ufield.GreaterThanZero(arg)) + { + foundContact = false; + break; + } + } + if (foundContact) + { + result.intersectionType = 1; + result.field = ufield; + result.contactTime.x = -element.x / dotUV; + result.contactTime.y = -element.y / dotUV; + for (int j = 0; j < 3; ++j) + { + result.contactPoint[j].x = sphere.center[j] + result.contactTime.x * sphereVelocity[j]; + result.contactPoint[j].y = result.contactTime.y * sphereVelocity[j]; + } + return result; + } + } + else + { + element.y = -element.y; + if (ufield.LessThanOrEqualZero(element)) + { + // The sphere is on the positive side of Dot(-U,X-C) = r|U|. + // If the sphere will contact the sphere-swept volume at a + // triangular face, it can do so only on the face of the + // aforementioned plane. + Real dotUV = Dot(U, V); + if (dotUV <= (Real)0) + { + // The sphere is moving away from, or parallel to, the + // plane of the triangle. The 'result' is already set + // to the correct state for this case. + return result; + } + + bool foundContact = true; + for (int i = 0; i < 3; ++i) + { + Real phi = Dot(ExU[i], Delta[i]); + Real psi = Dot(ExU[i], V); + QFElement arg(phi * dotUV - psi * element.x, -psi * element.y); + if (ufield.GreaterThanZero(arg)) + { + foundContact = false; + break; + } + } + if (foundContact) + { + result.intersectionType = 1; + result.field = ufield; + result.contactTime.x = -element.x / dotUV; + result.contactTime.y = -element.y / dotUV; + for (int j = 0; j < 3; ++j) + { + result.contactPoint[j].x = sphere.center[j] + result.contactTime.x * sphereVelocity[j]; + result.contactPoint[j].y = result.contactTime.y * sphereVelocity[j]; + } + return result; + } + } + // else: The ray-sphere-swept-volume contact point (if any) + // cannot be on a triangular face of the sphere-swept-volume. + } + + // The sphere is moving towards the slab between the two planes + // of the sphere-swept volume triangular faces. Determine whether + // the ray intersects the half cylinders or sphere wedges of the + // sphere-swept volume. + + // Test for contact with half cylinders of the sphere-swept + // volume. First, precompute some dot products required in the + // computations. TODO: Optimize the code to compute the quantities + // on-demand (i.e. only when they are needed, but cache them for + // later use). + Real del[3], delp[3], nu[3]; + for (int im1 = 2, i = 0; i < 3; im1 = i++) + { + del[i] = Dot(E[i], Delta[i]); + delp[im1] = Dot(E[im1], Delta[i]); + nu[i] = Dot(E[i], V); + } + + for (int i = 2, ip1 = 0; ip1 < 3; i = ip1++) + { + Vector3 hatV = V - E[i] * nu[i] / sqrLenE[i]; + Real sqrLenHatV = Dot(hatV, hatV); + if (sqrLenHatV > (Real)0) + { + Vector3 hatDelta = Delta[i] - E[i] * del[i] / sqrLenE[i]; + Real alpha = -Dot(hatV, hatDelta); + if (alpha >= (Real)0) + { + Real sqrLenHatDelta = Dot(hatDelta, hatDelta); + Real beta = alpha * alpha - sqrLenHatV * (sqrLenHatDelta - rsqr); + if (beta >= (Real)0) + { + QuadraticField bfield(beta); + Real mu = Dot(ExU[i], Delta[i]); + Real omega = Dot(ExU[i], hatV); + QFElement arg0(mu * sqrLenHatV + omega * alpha, -omega); + if (bfield.GreaterThanOrEqualZero(arg0)) + { + QFElement arg1(del[i] * sqrLenHatV + nu[i] * alpha, -nu[i]); + if (bfield.GreaterThanOrEqualZero(arg1)) + { + QFElement arg2(delp[i] * sqrLenHatV + nu[i] * alpha, -nu[i]); + if (bfield.LessThanOrEqualZero(arg2)) + { + result.intersectionType = 1; + result.field = bfield; + result.contactTime.x = alpha / sqrLenHatV; + result.contactTime.y = (Real)-1 / sqrLenHatV; + for (int j = 0; j < 3; ++j) + { + result.contactPoint[j].x = sphere.center[j] + result.contactTime.x * sphereVelocity[j]; + result.contactPoint[j].y = result.contactTime.y * sphereVelocity[j]; + } + return result; + } + } + } + } + } + } + } + + // Test for contact with sphere wedges of the sphere-swept + // volume. We know that |V|^2 > 0 because of a previous + // early-exit test. + for (int im1 = 2, i = 0; i < 3; im1 = i++) + { + Real alpha = -Dot(V, Delta[i]); + if (alpha >= (Real)0) + { + Real sqrLenDelta = Dot(Delta[i], Delta[i]); + Real beta = alpha * alpha - sqrLenV * (sqrLenDelta - rsqr); + if (beta >= (Real)0) + { + QuadraticField bfield(beta); + QFElement arg0(delp[im1] * sqrLenV + nu[im1] * alpha, -nu[im1]); + if (bfield.GreaterThanOrEqualZero(arg0)) + { + QFElement arg1(del[i] * sqrLenV + nu[i] * alpha, -nu[i]); + if (bfield.LessThanOrEqualZero(arg1)) + { + result.intersectionType = 1; + result.field = bfield; + result.contactTime.x = alpha / sqrLenV; + result.contactTime.y = (Real)-1 / sqrLenV; + for (int j = 0; j < 3; ++j) + { + result.contactPoint[j].x = sphere.center[j] + result.contactTime.x * sphereVelocity[j]; + result.contactPoint[j].y = result.contactTime.y * sphereVelocity[j]; + } + return result; + } + } + } + } + } + + // The ray and sphere-swept volume do not intersect, so the sphere + // and triangle do not come into contact. The 'result' is already + // set to the correct state for this case. + return result; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrTriangle3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrTriangle3OrientedBox3.h new file mode 100644 index 000000000000..e5db0debe5c7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrTriangle3OrientedBox3.h @@ -0,0 +1,207 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2019/02/13) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The test-intersection query is based on the document +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The find-intersection query clips the triangle against the faces of +// the oriented box. + +namespace gte +{ + template + class TIQuery, OrientedBox3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Triangle3 const& triangle, OrientedBox3 const& box) + { + Result result; + + Real min0, max0, min1, max1; + Vector3 D, edge[3]; + + // Test direction of triangle normal. + edge[0] = triangle.v[1] - triangle.v[0]; + edge[1] = triangle.v[2] - triangle.v[0]; + D = Cross(edge[0], edge[1]); + min0 = Dot(D, triangle.v[0]); + max0 = min0; + GetProjection(D, box, min1, max1); + if (max1 < min0 || max0 < min1) + { + result.intersect = false; + return result; + } + + // Test direction of box faces. + for (int i = 0; i < 3; ++i) + { + D = box.axis[i]; + GetProjection(D, triangle, min0, max0); + Real DdC = Dot(D, box.center); + min1 = DdC - box.extent[i]; + max1 = DdC + box.extent[i]; + if (max1 < min0 || max0 < min1) + { + result.intersect = false; + return result; + } + } + + // Test direction of triangle-box edge cross products. + edge[2] = edge[1] - edge[0]; + for (int i0 = 0; i0 < 3; ++i0) + { + for (int i1 = 0; i1 < 3; ++i1) + { + D = Cross(edge[i0], box.axis[i1]); + GetProjection(D, triangle, min0, max0); + GetProjection(D, box, min1, max1); + if (max1 < min0 || max0 < min1) + { + result.intersect = false; + return result; + } + } + } + + result.intersect = true; + return result; + } + + private: + void GetProjection(Vector3 const& axis, Triangle3 const& triangle, Real& imin, Real& imax) + { + Real dot[3] = + { + Dot(axis, triangle.v[0]), + Dot(axis, triangle.v[1]), + Dot(axis, triangle.v[2]) + }; + + imin = dot[0]; + imax = imin; + + if (dot[1] < imin) + { + imin = dot[1]; + } + else if (dot[1] > imax) + { + imax = dot[1]; + } + + if (dot[2] < imin) + { + imin = dot[2]; + } + else if (dot[2] > imax) + { + imax = dot[2]; + } + } + + void GetProjection(Vector3 const& axis, OrientedBox3 const& box, Real& imin, Real& imax) + { + Real origin = Dot(axis, box.center); + Real maximumExtent = + std::abs(box.extent[0] * Dot(axis, box.axis[0])) + + std::abs(box.extent[1] * Dot(axis, box.axis[1])) + + std::abs(box.extent[2] * Dot(axis, box.axis[2])); + + imin = origin - maximumExtent; + imax = origin + maximumExtent; + } + }; + + template + class FIQuery, OrientedBox3> + { + public: + struct Result + { + std::vector> insidePolygon; + std::vector>> outsidePolygons; + }; + + Result operator()(Triangle3 const& triangle, OrientedBox3 const& box) + { + Result result; + + // Start with the triangle and clip it against each face of the + // box. The largest number of vertices for the polygon of + // intersection is 7. + result.insidePolygon.resize(3); + for (int i = 0; i < 3; ++i) + { + result.insidePolygon[i] = triangle.v[i]; + } + + typedef FIQuery>, Hyperplane<3, Real>> PPQuery; + + Plane3 plane; + PPQuery ppQuery; + typename PPQuery::Result ppResult; + for (int dir = -1; dir <= 1; dir += 2) + { + for (int side = 0; side < 3; ++side) + { + // Create a plane for the box face that points inside the box. + plane.normal = ((Real)dir) * box.axis[side]; + plane.constant = Dot(plane.normal, box.center) - box.extent[side]; + + ppResult = ppQuery(result.insidePolygon, plane); + switch (ppResult.configuration) + { + case PPQuery::Configuration::SPLIT: + result.insidePolygon = ppResult.positivePolygon; + result.outsidePolygons.push_back(ppResult.negativePolygon); + break; + case PPQuery::Configuration::POSITIVE_SIDE_VERTEX: + case PPQuery::Configuration::POSITIVE_SIDE_EDGE: + case PPQuery::Configuration::POSITIVE_SIDE_STRICT: + // The result.insidePolygon is already + // ppResult.positivePolygon, but to make it clear, + // assign it here. + result.insidePolygon = ppResult.positivePolygon; + break; + case PPQuery::Configuration::NEGATIVE_SIDE_VERTEX: + case PPQuery::Configuration::NEGATIVE_SIDE_EDGE: + case PPQuery::Configuration::NEGATIVE_SIDE_STRICT: + result.insidePolygon.clear(); + result.outsidePolygons.push_back(ppResult.negativePolygon); + return result; + case PPQuery::Configuration::CONTAINED: + // A triangle coplanar with a box face will be + // processed as if it is inside the box. + result.insidePolygon = ppResult.intersection; + break; + default: + result.insidePolygon.clear(); + result.outsidePolygons.clear(); + break; + } + } + } + + return result; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteInvSqrtEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteInvSqrtEstimate.h new file mode 100644 index 000000000000..54bd5ff7be7f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteInvSqrtEstimate.h @@ -0,0 +1,193 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Minimax polynomial approximations to 1/sqrt(x). The polynomial p(x) of +// degree D minimizes the quantity maximum{|1/sqrt(x) - p(x)| : x in [1,2]} +// over all polynomials of degree D. + +namespace gte +{ + +template +class InvSqrtEstimate +{ +public: + // The input constraint is x in [1,2]. For example, + // float x; // in [1,2] + // float result = InvSqrtEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input constraint is x > 0. Range reduction is used to generate a + // value y in [1,2], call Evaluate(y), and combine the output with the + // proper exponent to obtain the approximation. For example, + // float x; // x > 0 + // float result = InvSqrtEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<1>, Real t); + inline static Real Evaluate(degree<2>, Real t); + inline static Real Evaluate(degree<3>, Real t); + inline static Real Evaluate(degree<4>, Real t); + inline static Real Evaluate(degree<5>, Real t); + inline static Real Evaluate(degree<6>, Real t); + inline static Real Evaluate(degree<7>, Real t); + inline static Real Evaluate(degree<8>, Real t); + + // Support for range reduction. + inline static void Reduce(Real x, Real& adj, Real& y, int& p); + inline static Real Combine(Real adj, Real y, int p); +}; + + +template +template +inline Real InvSqrtEstimate::Degree(Real x) +{ + Real t = x - (Real)1; // t in (0,1] + return Evaluate(degree(), t); +} + +template +template +inline Real InvSqrtEstimate::DegreeRR(Real x) +{ + Real adj, y; + int p; + Reduce(x, adj, y, p); + Real poly = Degree(y); + Real result = Combine(adj, poly, p); + return result; +} + +template +inline Real InvSqrtEstimate::Evaluate(degree<1>, Real t) +{ + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG1_C1; + poly = (Real)GTE_C_INVSQRT_DEG1_C0 + poly * t; + return poly; +} + +template +inline Real InvSqrtEstimate::Evaluate(degree<2>, Real t) +{ + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG2_C2; + poly = (Real)GTE_C_INVSQRT_DEG2_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG2_C0 + poly * t; + return poly; +} + +template +inline Real InvSqrtEstimate::Evaluate(degree<3>, Real t) +{ + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG3_C3; + poly = (Real)GTE_C_INVSQRT_DEG3_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG3_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG3_C0 + poly * t; + return poly; +} + +template +inline Real InvSqrtEstimate::Evaluate(degree<4>, Real t) +{ + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG4_C4; + poly = (Real)GTE_C_INVSQRT_DEG4_C3 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG4_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG4_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG4_C0 + poly * t; + return poly; +} + +template +inline Real InvSqrtEstimate::Evaluate(degree<5>, Real t) +{ + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG5_C5; + poly = (Real)GTE_C_INVSQRT_DEG5_C4 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG5_C3 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG5_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG5_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG5_C0 + poly * t; + return poly; +} + +template +inline Real InvSqrtEstimate::Evaluate(degree<6>, Real t) +{ + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG6_C6; + poly = (Real)GTE_C_INVSQRT_DEG6_C5 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG6_C4 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG6_C3 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG6_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG6_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG6_C0 + poly * t; + return poly; +} + +template +inline Real InvSqrtEstimate::Evaluate(degree<7>, Real t) +{ + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG7_C7; + poly = (Real)GTE_C_INVSQRT_DEG7_C6 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C5 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C4 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C3 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C0 + poly * t; + return poly; +} + +template +inline Real InvSqrtEstimate::Evaluate(degree<8>, Real t) +{ + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG8_C8; + poly = (Real)GTE_C_INVSQRT_DEG8_C7 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C6 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C5 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C4 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C3 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C0 + poly * t; + return poly; +} + +template +inline void InvSqrtEstimate::Reduce(Real x, Real& adj, Real& y, int& p) +{ + y = std::frexp(x, &p); // y in [1/2,1) + y = ((Real)2)*y; // y in [1,2) + --p; + adj = (1 & p)*(Real)GTE_C_INV_SQRT_2 + (1 & ~p)*(Real)1; + p = -(p >> 1); +} + +template +inline Real InvSqrtEstimate::Combine(Real adj, Real y, int p) +{ + return adj * std::ldexp(y, p); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIsPlanarGraph.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIsPlanarGraph.h new file mode 100644 index 000000000000..30aa2a576cbf --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIsPlanarGraph.h @@ -0,0 +1,526 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// Test whether an undirected graph is planar. The input positions must be +// unique and the input edges must be unique, so the number of positions is +// at least 2 and the number of edges is at least one. The elements of the +// edges array must be indices in {0,..,positions.size()-1}. +// +// A sort-and-sweep algorithm is used to determine edge-edge intersections. +// If none of the intersections occur at edge-interior points, the graph is +// planar. See Game Physics (2nd edition), Section 6.2.2: Culling with +// Axis-Aligned Bounding Boxes for such an algorithm. The operator() +// returns 'true' when the graph is planar. If it returns 'false', the +// 'invalidIntersections' set contains pairs of edges that intersect at an +// edge-interior point (that by definition is not a graph vertex). Each set +// element is a pair of indices into the 'edges' array; the indices are +// ordered so that element[0] < element[1]. The Real type must be chosen +// to guarantee exact computation of edge-edge intersections. + +namespace gte +{ + +template +class IsPlanarGraph +{ +public: + IsPlanarGraph(); + + enum + { + IPG_IS_PLANAR_GRAPH = 0, + IPG_INVALID_INPUT_SIZES = 1, + IPG_DUPLICATED_POSITIONS = 2, + IPG_DUPLICATED_EDGES = 4, + IPG_DEGENERATE_EDGES = 8, + IPG_EDGES_WITH_INVALID_VERTICES = 16, + IPG_INVALID_INTERSECTIONS = 32 + }; + + // The function returns a combination of the IPG_* flags listed in the + // previous enumeration. A combined value of 0 indicates the input + // forms a planar graph. If the combined value is not zero, you may + // examine the flags for the failure conditions and use the Get* member + // accessors to obtain specific information about the failure. If the + // positions.size() < 2 or edges.size() == 0, the IPG_INVALID_INPUT_SIZES + // flag is set. + + int operator()(std::vector> const& positions, + std::vector> const& edges); + + // A pair of indices (v0,v1) into the positions array is stored as + // (min(v0,v1), max(v0,v1)). This supports sorted containers of edges. + struct OrderedEdge + { + OrderedEdge(int v0 = -1, int v1 = -1); + bool operator<(OrderedEdge const& edge) const; + std::array V; + }; + + inline std::vector> const& GetDuplicatedPositions() const; + inline std::vector> const& GetDuplicatedEdges() const; + inline std::vector const& GetDegenerateEdges() const; + inline std::vector const& GetEdgesWithInvalidVertices() const; + inline std::vector const& GetInvalidIntersections() const; + +private: + class Endpoint + { + public: + Real value; // endpoint value + int type; // '0' if interval min, '1' if interval max. + int index; // index of interval containing this endpoint + + // Comparison operator for sorting. + bool operator<(Endpoint const& endpoint) const; + }; + + int IsValidTopology(std::vector> const& positions, + std::vector> const& edges); + + void ComputeOverlappingRectangles(std::vector> const& positions, + std::vector> const& edges, + std::set& overlappingRectangles) const; + + bool InvalidSegmentIntersection( + std::array const& p0, std::array const& p1, + std::array const& q0, std::array const& q1) const; + + std::vector> mDuplicatedPositions; + std::vector> mDuplicatedEdges; + std::vector mDegenerateEdges; + std::vector mEdgesWithInvalidVertices; + std::vector mInvalidIntersections; + Real mZero, mOne; +}; + + +template +IsPlanarGraph::IsPlanarGraph() + : + mZero(0), + mOne(1) +{ +} + +template +int IsPlanarGraph::operator()( + std::vector> const& positions, + std::vector> const& edges) +{ + mDuplicatedPositions.clear(); + mDuplicatedEdges.clear(); + mDegenerateEdges.clear(); + mEdgesWithInvalidVertices.clear(); + mInvalidIntersections.clear(); + + int flags = IsValidTopology(positions, edges); + if (flags == IPG_INVALID_INPUT_SIZES) + { + return flags; + } + + std::set overlappingRectangles; + ComputeOverlappingRectangles(positions, edges, overlappingRectangles); + for (auto key : overlappingRectangles) + { + // Get the endpoints of the line segments for the edges whose bounding + // rectangles overlapped. Determine whether the line segments + // intersect. If they do, determine how they intersect. + std::array e0 = edges[key.V[0]]; + std::array e1 = edges[key.V[1]]; + std::array const& p0 = positions[e0[0]]; + std::array const& p1 = positions[e0[1]]; + std::array const& q0 = positions[e1[0]]; + std::array const& q1 = positions[e1[1]]; + if (InvalidSegmentIntersection(p0, p1, q0, q1)) + { + mInvalidIntersections.push_back(key); + } + } + + if (mInvalidIntersections.size() > 0) + { + flags |= IPG_INVALID_INTERSECTIONS; + } + + return flags; +} + +template +inline std::vector> const& +IsPlanarGraph::GetDuplicatedPositions() const +{ + return mDuplicatedPositions; +} + +template +inline std::vector> const& +IsPlanarGraph::GetDuplicatedEdges() const +{ + return mDuplicatedEdges; +} + +template +inline std::vector const& +IsPlanarGraph::GetDegenerateEdges() const +{ + return mDegenerateEdges; +} + +template +inline std::vector const& +IsPlanarGraph::GetEdgesWithInvalidVertices() const +{ + return mEdgesWithInvalidVertices; +} + +template +inline std::vector::OrderedEdge> const& +IsPlanarGraph::GetInvalidIntersections() const +{ + return mInvalidIntersections; +} + +template +int IsPlanarGraph::IsValidTopology(std::vector> const& positions, + std::vector> const& edges) +{ + int const numPositions = static_cast(positions.size()); + int const numEdges = static_cast(edges.size()); + if (numPositions < 2 || numEdges == 0) + { + // The graph must have at least one edge. + return IPG_INVALID_INPUT_SIZES; + } + + // The positions must be unique. + int flags = IPG_IS_PLANAR_GRAPH; + std::map, std::vector> uniquePositions; + for (int i = 0; i < numPositions; ++i) + { + std::array p = positions[i]; + auto iter = uniquePositions.find(p); + if (iter == uniquePositions.end()) + { + std::vector indices; + indices.push_back(i); + uniquePositions.insert(std::make_pair(p, indices)); + } + else + { + iter->second.push_back(i); + } + } + if (uniquePositions.size() < positions.size()) + { + // At least two positions are duplicated. + for (auto const& element : uniquePositions) + { + if (element.second.size() > 1) + { + mDuplicatedPositions.push_back(element.second); + } + } + flags |= IPG_DUPLICATED_POSITIONS; + } + + // The edges must be unique. + std::map> uniqueEdges; + for (int i = 0; i < numEdges; ++i) + { + OrderedEdge key(edges[i][0], edges[i][1]); + auto iter = uniqueEdges.find(key); + if (iter == uniqueEdges.end()) + { + std::vector indices; + indices.push_back(i); + uniqueEdges.insert(std::make_pair(key, indices)); + } + else + { + iter->second.push_back(i); + } + } + if (uniqueEdges.size() < edges.size()) + { + // At least two edges are duplicated, possibly even a pair of + // edges (v0,v1) and (v1,v0) which is not allowed because the + // graph is undirected. + for (auto const& element : uniqueEdges) + { + if (element.second.size() > 1) + { + mDuplicatedEdges.push_back(element.second); + } + } + flags |= IPG_DUPLICATED_EDGES; + } + + // The edges are represented as pairs of indices into the 'positions' + // array. The indices for a single edge must be different (no edges + // allowed from a vertex to itself) and all indices must be valid. + // At the same time, keep track of unique edges. + for (int i = 0; i < numEdges; ++i) + { + std::array e = edges[i]; + if (e[0] == e[1]) + { + // The edge is degenerate, originating and terminating at the + // same vertex. + mDegenerateEdges.push_back(i); + flags |= IPG_DEGENERATE_EDGES; + } + + if (e[0] < 0 || e[0] >= numPositions || e[1] < 0 || e[1] >= numPositions) + { + // The edge has an index that references a non-existant vertex. + mEdgesWithInvalidVertices.push_back(i); + flags |= IPG_EDGES_WITH_INVALID_VERTICES; + } + } + + return flags; +} + +template +void IsPlanarGraph::ComputeOverlappingRectangles(std::vector> const& positions, + std::vector> const& edges, std::set& overlappingRectangles) const +{ + // Compute axis-aligned bounding rectangles for the edges. + int const numEdges = static_cast(edges.size()); + std::vector> emin(numEdges); + std::vector> emax(numEdges); + for (int i = 0; i < numEdges; ++i) + { + std::array e = edges[i]; + std::array const& p0 = positions[e[0]]; + std::array const& p1 = positions[e[1]]; + + for (int j = 0; j < 2; ++j) + { + if (p0[j] < p1[j]) + { + emin[i][j] = p0[j]; + emax[i][j] = p1[j]; + } + else + { + emin[i][j] = p1[j]; + emax[i][j] = p0[j]; + } + } + } + + // Get the rectangle endpoints. + int const numEndpoints = 2 * numEdges; + std::vector xEndpoints(numEndpoints); + std::vector yEndpoints(numEndpoints); + for (int i = 0, j = 0; i < numEdges; ++i) + { + xEndpoints[j].type = 0; + xEndpoints[j].value = emin[i][0]; + xEndpoints[j].index = i; + yEndpoints[j].type = 0; + yEndpoints[j].value = emin[i][1]; + yEndpoints[j].index = i; + ++j; + + xEndpoints[j].type = 1; + xEndpoints[j].value = emax[i][0]; + xEndpoints[j].index = i; + yEndpoints[j].type = 1; + yEndpoints[j].value = emax[i][1]; + yEndpoints[j].index = i; + ++j; + } + + // Sort the rectangle endpoints. + std::sort(xEndpoints.begin(), xEndpoints.end()); + std::sort(yEndpoints.begin(), yEndpoints.end()); + + // Sweep through the endpoints to determine overlapping x-intervals. Use + // an active set of rectangles to reduce the complexity of the algorithm. + std::set active; + for (int i = 0; i < numEndpoints; ++i) + { + Endpoint const& endpoint = xEndpoints[i]; + int index = endpoint.index; + if (endpoint.type == 0) // an interval 'begin' value + { + // In the 1D problem, the current interval overlaps with all the + // active intervals. In 2D this we also need to check for + // y-overlap. + for (auto activeIndex : active) + { + // Rectangles activeIndex and index overlap in the + // x-dimension. Test for overlap in the y-dimension. + std::array const& r0min = emin[activeIndex]; + std::array const& r0max = emax[activeIndex]; + std::array const& r1min = emin[index]; + std::array const& r1max = emax[index]; + if (r0max[1] >= r1min[1] && r0min[1] <= r1max[1]) + { + if (activeIndex < index) + { + overlappingRectangles.insert(OrderedEdge(activeIndex, index)); + } + else + { + overlappingRectangles.insert(OrderedEdge(index, activeIndex)); + } + } + } + active.insert(index); + } + else // an interval 'end' value + { + active.erase(index); + } + } +} + +template +bool IsPlanarGraph::InvalidSegmentIntersection( + std::array const& p0, std::array const& p1, + std::array const& q0, std::array const& q1) const +{ + // We must solve the two linear equations + // p0 + t0 * (p1 - p0) = q0 + t1 * (q1 - q0) + // for the unknown variables t0 and t1. These may be written as + // t0 * (p1 - p0) - t1 * (q1 - q0) = q0 - p0 + // If denom = Dot(p1 - p0, Perp(q1 - q0)) is not zero, then + // t0 = Dot(q0 - p0, Perp(q1 - q0)) / denom = numer0 / denom + // t1 = Dot(q0 - p0, Perp(p1 - p0)) / denom = numer1 / denom + // For an invalid intersection, we need (t0,t1) with: + // ((0 < t0 < 1) and (0 <= t1 <= 1)) or ((0 <= t0 <= 1) and (0 < t1 < 1). + // If denom is zero, then + + std::array p1mp0, q1mq0, q0mp0; + for (int j = 0; j < 2; ++j) + { + p1mp0[j] = p1[j] - p0[j]; + q1mq0[j] = q1[j] - q0[j]; + q0mp0[j] = q0[j] - p0[j]; + } + + Real denom = p1mp0[0] * q1mq0[1] - p1mp0[1] * q1mq0[0]; + Real numer0 = q0mp0[0] * q1mq0[1] - q0mp0[1] * q1mq0[0]; + Real numer1 = q0mp0[0] * p1mp0[1] - q0mp0[1] * p1mp0[0]; + + if (denom != mZero) + { + // The lines of the segments are not parallel. + if (denom > mZero) + { + if (mZero <= numer0 && numer0 <= denom && mZero <= numer1 && numer1 <= denom) + { + // The segments intersect. + return (numer0 != mZero && numer0 != denom) || (numer1 != mZero && numer1 != denom); + } + else + { + return false; + } + } + else // denom < mZero + { + if (mZero >= numer0 && numer0 >= denom && mZero >= numer1 && numer1 >= denom) + { + // The segments intersect. + return (numer0 != mZero && numer0 != denom) || (numer1 != mZero && numer1 != denom); + } + else + { + return false; + } + } + } + else + { + // The lines of the segments are parallel. + if (numer0 != mZero || numer1 != mZero) + { + // The lines of the segments are separated. + return false; + } + else + { + // The segments lie on the same line. Compute the parameter + // intervals for the segments in terms of the t0-parameter and + // determine their overlap (if any). + std::array q1mp0; + for (int j = 0; j < 2; ++j) + { + q1mp0[j] = q1[j] - p0[j]; + } + Real sqrLenP1mP0 = p1mp0[0] * p1mp0[0] + p1mp0[1] * p1mp0[1]; + Real value0 = q0mp0[0] * p1mp0[0] + q0mp0[1] * p1mp0[1]; + Real value1 = q1mp0[0] * p1mp0[0] + q1mp0[1] * p1mp0[1]; + if ((value0 >= sqrLenP1mP0 && value1 >= sqrLenP1mP0) + || (value0 <= mZero && value1 <= mZero)) + { + // If the segments intersect, they must do so at endpoints of + // the segments. + return false; + } + else + { + // The segments overlap in a positive-length interval. + return true; + } + } + } +} + +template +bool IsPlanarGraph::Endpoint::operator<(Endpoint const& endpoint) const +{ + if (value < endpoint.value) + { + return true; + } + if (value > endpoint.value) + { + return false; + } + return type < endpoint.type; +} + +template +IsPlanarGraph::OrderedEdge::OrderedEdge(int v0, int v1) +{ + if (v0 < v1) + { + // v0 is minimum + V[0] = v0; + V[1] = v1; + } + else + { + // v1 is minimum + V[0] = v1; + V[1] = v0; + } +} + +template +bool IsPlanarGraph::OrderedEdge::operator<(OrderedEdge const& edge) const +{ + // Lexicographical ordering used by std::array. + return V < edge.V; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLCPSolver.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLCPSolver.h new file mode 100644 index 000000000000..ada75319a9e7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLCPSolver.h @@ -0,0 +1,616 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.4.2 (2016/11/24) + +#pragma once + +#include +#include +#include + +// A class for solving the Linear Complementarity Problem (LCP) +// w = q + M * z, w^T * z = 0, w >= 0, z >= 0. The vectors q, w, and z are +// n-tuples and the matrix M is n-by-n. The inputs to Solve(...) are q and M. +// The outputs are w and z, which are valid when the returned bool is true but +// are invalid when the returned bool is false. +// +// The comments at the end of this file explain what the preprocessor symbol +// means regarding the LCP solver implementation. If the algorithm fails to +// converge within the specified maximum number of iterations, consider +// increasing the number and calling Solve(...) again. +//#define GTE_REPORT_FAILED_TO_CONVERGE + +namespace gte +{ + +// Support templates for number of dimensions known at compile time or known +// only at run time. +template +class LCPSolver {}; + + +template +class LCPSolverShared +{ +protected: + // Abstract base class construction. A virtual destructor is not provided + // because there are no required side effects when destroying objects from + // the derived classes. The member mMaxIterations is set by this call to + // the default value of n*n. + LCPSolverShared(int n); + +public: + // Theoretically, when there is a solution the algorithm must converge + // in a finite number of iterations. The number of iterations depends + // on the problem at hand, but we need to guard against an infinite loop + // by limiting the number. The implementation uses a maximum number of + // n*n (chosen arbitrarily). You can set the number yourself, perhaps + // when a call to Solve fails--increase the number of iterations and call + // Solve again. + inline void SetMaxIterations(int maxIterations); + inline int GetMaxIterations() const; + + // Access the actual number of iterations used in a call to Solve. + inline int GetNumIterations() const; + + enum Result + { + HAS_TRIVIAL_SOLUTION, + HAS_NONTRIVIAL_SOLUTION, + NO_SOLUTION, + FAILED_TO_CONVERGE, + INVALID_INPUT + }; + +protected: + // Bookkeeping of variables during the iterations of the solver. The name + // is either 'w' or 'z' and is used for human-readable debugging help. + // The 'index' is that for the original variables w[index] or z[index]. + // The 'complementary' index is the location of the complementary variable + // in mVarBasic[] or in mVarNonbasic[]. The 'tuple' is a pointer to + // &w[0] or &z[0], the choice based on name of 'w' or 'z', and is used to + // fill in the solution values (the variables are permuted during the + // pivoting algorithm). + struct Variable + { + char name; + int index; + int complementary; + Real* tuple; + }; + + // The augmented problem is w = q + M*z + z[n]*U = 0, where U is an + // n-tuple of 1-values. We manipulate the augmented matrix [M | U | p(t)] + // where p(t) is a column vector of polynomials of at most degree n. If + // p[r](t) is the polynomial for row r, then p[r](0) = q[r]. These are + // perturbations of q[r] designed so that the algorithm avoids degeneracies + // (a q-term becomes zero during the iterations). The basic variables are + // w[0] through w[n-1] and the nonbasic variables are z[0] through z[n]. + // The returned z consists only of z[0] through z[n-1]. + + // The derived classes ensure that the pointers point to the correct + // of elements for each array. The matrix M must be stored in row-major + // order. + bool Solve(Real const* q, Real const* M, Real* w, Real* z, Result* result); + + // Access mAugmented as a 2-dimensional array. + inline Real const& Augmented(int row, int col) const; + inline Real& Augmented(int row, int col); + + // Support for polynomials with n+1 coefficients and degree no larger + // than n. + void MakeZero(Real* poly); + void Copy(Real const* poly0, Real* poly1); + bool LessThan(Real const* poly0, Real const* poly1); + bool LessThanZero(Real const* poly); + void Multiply(Real const* poly, Real scalar, Real* product); + + int mDimension; + int mMaxIterations; + int mNumIterations; + + // These pointers are set by the derived-class constructors to arrays + // that have the correct number of elements. The arrays mVarBasic, + // mVarNonbasic, mQMin, mMinRatio, and mRatio each have n+1 elements. + // The mAugmented array has n rows and 2*(n+1) columns stored in + // row-major order in a 1-dimensional array. The array of pointers + // mPoly has n elements. + Variable* mVarBasic; + Variable* mVarNonbasic; + int mNumCols; + Real* mAugmented; + Real* mQMin; + Real* mMinRatio; + Real* mRatio; + Real** mPoly; +}; + + +template +class LCPSolver : public LCPSolverShared +{ +public: + // Construction. The member mMaxIterations is set by this call to the + // default value of n*n. + LCPSolver(); + + // If you want to know specifically why 'true' or 'false' was returned, + // pass the address of a Result variable as the last parameter. + bool Solve(std::array const& q, std::array, n> const& M, + std::array& w, std::array& z, + typename LCPSolverShared::Result* result = nullptr); + +private: + std::array::Variable, n + 1> mArrayVarBasic; + std::array::Variable, n + 1> mArrayVarNonbasic; + std::array mArrayAugmented; + std::array mArrayQMin; + std::array mArrayMinRatio; + std::array mArrayRatio; + std::array mArrayPoly; +}; + + +template +class LCPSolver : public LCPSolverShared +{ +public: + // Construction. The member mMaxIterations is set by this call to the + // default value of n*n. + LCPSolver(int n); + + // The input q must have n elements and the input M must be an n-by-n + // matrix stored in row-major order. The outputs w and z have n elements. + // If you want to know specifically why 'true' or 'false' was returned, + // pass the address of a Result variable as the last parameter. + bool Solve(std::vector const& q, std::vector const& M, + std::vector& w, std::vector& z, + typename LCPSolverShared::Result* result = nullptr); + +private: + std::vector::Variable> mVectorVarBasic; + std::vector::Variable> mVectorVarNonbasic; + std::vector mVectorAugmented; + std::vector mVectorQMin; + std::vector mVectorMinRatio; + std::vector mVectorRatio; + std::vector mVectorPoly; +}; + + +template +LCPSolverShared::LCPSolverShared(int n) + : + mNumIterations(0), + mVarBasic(nullptr), + mVarNonbasic(nullptr), + mNumCols(0), + mAugmented(nullptr), + mQMin(nullptr), + mMinRatio(nullptr), + mRatio(nullptr), + mPoly(nullptr) +{ + if (n > 0) + { + mDimension = n; + mMaxIterations = n * n; + } + else + { + mDimension = 0; + mMaxIterations = 0; + } +} + +template +inline void LCPSolverShared::SetMaxIterations(int maxIterations) +{ + mMaxIterations = (maxIterations > 0 ? maxIterations : mDimension * mDimension); +} + +template +inline int LCPSolverShared::GetMaxIterations() const +{ + return mMaxIterations; +} + +template +inline int LCPSolverShared::GetNumIterations() const +{ + return mNumIterations; +} + +template +bool LCPSolverShared::Solve(Real const* q, Real const* M, + Real* w, Real* z, Result* result) +{ + // Perturb the q[r] constants to be polynomials of degree r+1 represented + // as an array of n+1 coefficients. The coefficient with index r+1 is 1 + // and the coefficients with indices larger than r+1 are 0. + for (int r = 0; r < mDimension; ++r) + { + mPoly[r] = &Augmented(r, mDimension + 1); + MakeZero(mPoly[r]); + mPoly[r][0] = q[r]; + mPoly[r][r + 1] = (Real)1; + } + + // Determine whether there is the trivial solution w = z = 0. + Copy(mPoly[0], mQMin); + int basic = 0; + for (int r = 1; r < mDimension; ++r) + { + if (LessThan(mPoly[r], mQMin)) + { + Copy(mPoly[r], mQMin); + basic = r; + } + } + + if (!LessThanZero(mQMin)) + { + for (int r = 0; r < mDimension; ++r) + { + w[r] = q[r]; + z[r] = (Real)0; + } + + if (result) + { + *result = HAS_TRIVIAL_SOLUTION; + } + return true; + } + + // Initialize the remainder of the augmented matrix with M and U. + for (int r = 0; r < mDimension; ++r) + { + for (int c = 0; c < mDimension; ++c) + { + Augmented(r, c) = M[c + mDimension * r]; + } + Augmented(r, mDimension) = (Real)1; + } + + // Keep track of when the variables enter and exit the dictionary, + // including where complementary variables are relocated. + for (int i = 0; i <= mDimension; ++i) + { + mVarBasic[i].name = 'w'; + mVarBasic[i].index = i; + mVarBasic[i].complementary = i; + mVarBasic[i].tuple = w; + mVarNonbasic[i].name = 'z'; + mVarNonbasic[i].index = i; + mVarNonbasic[i].complementary = i; + mVarNonbasic[i].tuple = z; + } + + // The augmented variable z[n] is the initial driving variable for + // pivoting. The equation 'basic' is the one to solve for z[n] and + // pivoting with w[basic]. The last column of M remains all 1-values + // for this initial step, so no algebraic computations occur for M[r][n]. + int driving = mDimension; + for (int r = 0; r < mDimension; ++r) + { + if (r != basic) + { + for (int c = 0; c < mNumCols; ++c) + { + if (c != mDimension) + { + Augmented(r, c) -= Augmented(basic, c); + } + } + } + } + + for (int c = 0; c < mNumCols; ++c) + { + if (c != mDimension) + { + Augmented(basic, c) = -Augmented(basic, c); + } + } + + mNumIterations = 0; + for (int i = 0; i < mMaxIterations; ++i, ++mNumIterations) + { + // The basic variable of equation 'basic' exited the dictionary, so + // its complementary (nonbasic) variable must become the next driving + // variable in order for it to enter the dictionary. + int nextDriving = mVarBasic[basic].complementary; + mVarNonbasic[nextDriving].complementary = driving; + std::swap(mVarBasic[basic], mVarNonbasic[driving]); + if (mVarNonbasic[driving].index == mDimension) + { + // The algorithm has converged. + for (int r = 0; r < mDimension; ++r) + { + mVarBasic[r].tuple[mVarBasic[r].index] = mPoly[r][0]; + } + for (int c = 0; c <= mDimension; ++c) + { + int index = mVarNonbasic[c].index; + if (index < mDimension) + { + mVarNonbasic[c].tuple[index] = (Real)0; + } + } + if (result) + { + *result = HAS_NONTRIVIAL_SOLUTION; + } + return true; + } + + // Determine the 'basic' equation for which the ratio -q[r]/M(r,driving) + // is minimized among all equations r with M(r,driving) < 0. + driving = nextDriving; + basic = -1; + for (int r = 0; r < mDimension; ++r) + { + if (Augmented(r, driving) < (Real)0) + { + Real factor = (Real)-1 / Augmented(r, driving); + Multiply(mPoly[r], factor, mRatio); + if (basic == -1 || LessThan(mRatio, mMinRatio)) + { + Copy(mRatio, mMinRatio); + basic = r; + } + } + } + + if (basic == -1) + { + // The coefficients of z[driving] in all the equations are + // nonnegative, so the z[driving] variable cannot leave the + // dictionary. There is no solution to the LCP. + for (int r = 0; r < mDimension; ++r) + { + w[r] = (Real)0; + z[r] = (Real)0; + } + + if (result) + { + *result = NO_SOLUTION; + } + return false; + } + + // Solve the basic equation so that z[driving] enters the dictionary + // and w[basic] exits the dictionary. + Real invDenom = (Real)1 / Augmented(basic, driving); + for (int r = 0; r < mDimension; ++r) + { + if (r != basic && Augmented(r, driving) != (Real)0) + { + Real multiplier = Augmented(r, driving) * invDenom; + for (int c = 0; c < mNumCols; ++c) + { + if (c != driving) + { + Augmented(r, c) -= Augmented(basic, c) * multiplier; + } + else + { + Augmented(r, driving) = multiplier; + } + } + } + } + + for (int c = 0; c < mNumCols; ++c) + { + if (c != driving) + { + Augmented(basic, c) = -Augmented(basic, c) * invDenom; + } + else + { + Augmented(basic, driving) = invDenom; + } + } + } + + // Numerical round-off errors can cause the Lemke algorithm not to + // converge. In particular, the code above has a test + // if (mAugmented[r][driving] < (Real)0) { ... } + // to determine the 'basic' equation with which to pivot. It is + // possible that theoretically mAugmented[r][driving]is zero but + // rounding errors cause it to be slightly negative. If theoretically + // all mAugmented[r][driving] >= 0, there is no solution to the LCP. + // With the rounding errors, if the algorithm fails to converge within + // the specified number of iterations, NO_SOLUTION is returned, which + // is hopefully the correct result. It is also possible that the rounding + // errors lead to a NO_SOLUTION (returned from inside the loop) when in + // fact there is a solution. [When the LCP solver is used by intersection + // testing algorithms, the hope is that misclassifications occur only + // when the two objects are nearly in tangential contact.] + // + // To determine whether the rounding errors are the problem, you can + // execute the query using exact arithmetic with the following type + // used for 'Real' (replacing 'float' or 'double') of + // BSRational Rational. + // + // That said, if the algorithm fails to converge and you believe that + // the rounding errors are not causing this, please file a bug report + // and provide the input data to the solver. + +#if defined(GTE_REPORT_FAILED_TO_CONVERGE) + LogInformation("The algorithm failed to converge."); +#endif + + if (result) + { + *result = FAILED_TO_CONVERGE; + } + return false; +} + +template +inline Real const& LCPSolverShared::Augmented(int row, int col) const +{ + return mAugmented[col + mNumCols * row]; +} + +template +inline Real& LCPSolverShared::Augmented(int row, int col) +{ + return mAugmented[col + mNumCols * row]; +} + + +template +void LCPSolverShared::MakeZero(Real* poly) +{ + for (int i = 0; i <= mDimension; ++i) + { + poly[i] = (Real)0; + } +} + +template +void LCPSolverShared::Copy(Real const* poly0, Real* poly1) +{ + for (int i = 0; i <= mDimension; ++i) + { + poly1[i] = poly0[i]; + } +} + +template +bool LCPSolverShared::LessThan(Real const* poly0, Real const* poly1) +{ + for (int i = 0; i <= mDimension; ++i) + { + if (poly0[i] < poly1[i]) + { + return true; + } + + if (poly0[i] > poly1[i]) + { + return false; + } + } + + return false; +} + +template +bool LCPSolverShared::LessThanZero(Real const* poly) +{ + for (int i = 0; i <= mDimension; ++i) + { + if (poly[i] < (Real)0) + { + return true; + } + + if (poly[i] > (Real)0) + { + return false; + } + } + + return false; +} + +template +void LCPSolverShared::Multiply(Real const* poly, Real scalar, Real* product) +{ + for (int i = 0; i <= mDimension; ++i) + { + product[i] = poly[i] * scalar; + } +} + + +template +LCPSolver::LCPSolver() + : + LCPSolverShared(n) +{ + this->mVarBasic = mArrayVarBasic.data(); + this->mVarNonbasic = mArrayVarNonbasic.data(); + this->mNumCols = 2 * (n + 1); + this->mAugmented = mArrayAugmented.data(); + this->mQMin = mArrayQMin.data(); + this->mMinRatio = mArrayMinRatio.data(); + this->mRatio = mArrayRatio.data(); + this->mPoly = mArrayPoly.data(); +} + +template +bool LCPSolver::Solve( + std::array const& q, std::array, n> const& M, + std::array& w, std::array& z, + typename LCPSolverShared::Result* result) +{ + return LCPSolverShared::Solve(q.data(), M.front().data(), w.data(), z.data(), result); +} + + +template +LCPSolver::LCPSolver(int n) + : + LCPSolverShared(n) +{ + if (n > 0) + { + mVectorVarBasic.resize(n + 1); + mVectorVarNonbasic.resize(n + 1); + mVectorAugmented.resize(2 * (n + 1) * n); + mVectorQMin.resize(n + 1); + mVectorMinRatio.resize(n + 1); + mVectorRatio.resize(n + 1); + mVectorPoly.resize(n); + + this->mVarBasic = mVectorVarBasic.data(); + this->mVarNonbasic = mVectorVarNonbasic.data(); + this->mNumCols = 2 * (n + 1); + this->mAugmented = mVectorAugmented.data(); + this->mQMin = mVectorQMin.data(); + this->mMinRatio = mVectorMinRatio.data(); + this->mRatio = mVectorRatio.data(); + this->mPoly = mVectorPoly.data(); + } +} + +template +bool LCPSolver::Solve( + std::vector const& q, std::vector const& M, + std::vector& w, std::vector& z, + typename LCPSolverShared::Result* result) +{ + if (this->mDimension > static_cast(q.size()) + || this->mDimension * this->mDimension > static_cast(M.size())) + { + if (result) + { + *result = this->INVALID_INPUT; + } + return false; + } + + if (this->mDimension > static_cast(w.size())) + { + w.resize(this->mDimension); + } + + if (this->mDimension > static_cast(z.size())) + { + z.resize(this->mDimension); + } + + return LCPSolverShared::Solve(q.data(), M.data(), w.data(), z.data(), result); +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLevenbergMarquardtMinimizer.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLevenbergMarquardtMinimizer.h new file mode 100644 index 000000000000..f308c4662fd6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLevenbergMarquardtMinimizer.h @@ -0,0 +1,292 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.14.0 (2018/07/16) + +#pragma once + +#include +#include + +// See GteGaussNewtonMinimizer.h for a formulation of the minimization +// problem and how Levenberg-Marquardt relates to Gauss-Newton. + +namespace gte +{ + template + class LevenbergMarquardtMinimizer + { + public: + // Convenient types for the domain vectors, the range vectors, the + // function F and the Jacobian J. + typedef GVector DVector; // NumPDimensions elements + typedef GVector RVector; // NumFDImensions elements + typedef GMatrix JMatrix; // NumFDimensions-by-NumPDimensions elements + typedef GMatrix JTJMatrix; // NumPDimensions-by-NumPDimensions elements + typedef GVector JTFVector; // NumPDimensions elements + typedef std::function FFunction; + typedef std::function JFunction; + typedef std::function JPlusFunction; + + // NOTE: The C++ compiler for Microsoft Visual Studio 12.0.21005.1 REL + // (MSVS 2013) appears to have a bug regarding passing std::function + // arguments. The unit-test code for the first LevenbergMarquardtMinimizer + // constructor was passed std::function objects of type FFunction and + // JFunction. The compiler claimed that the constructor call is + // ambiguous because the JFunction portion of the first constructor's + // signature matches the JPlusFunction portion of the second + // constructor's signature, which is clearly not correct. When using + // MSVS 2013, you need to explicitly typecast using + // LevenbergMarquardtMinimizer minimizer( + // numPDimensions, numFDimensions, + // (LevenbergMarquardtMinimizer::FFunction const&) myFFunction, + // (LevenbergMarquardtMinimizer::JFunction const&) myJFunction); + // The same typecasting is also required for the second constructor. + + // Create the minimizer that computes F(p) and J(p) directly. + LevenbergMarquardtMinimizer(int numPDimensions, int numFDimensions, + FFunction const& inFFunction, JFunction const& inJFunction) + : + mNumPDimensions(numPDimensions), + mNumFDimensions(numFDimensions), + mFFunction(inFFunction), + mJFunction(inJFunction), + mF(mNumFDimensions), + mJ(mNumFDimensions, mNumPDimensions), + mJTJ(mNumPDimensions, mNumPDimensions), + mNegJTF(mNumPDimensions), + mDecomposer(mNumPDimensions), + mUseJFunction(true) + { + LogAssert(mNumPDimensions > 0 && mNumFDimensions > 0, "Invalid dimensions."); + } + + // Create the minimizer that computes J^T(p)*J(p) and -J(p)*F(p). + LevenbergMarquardtMinimizer(int numPDimensions, int numFDimensions, + FFunction const& inFFunction, JPlusFunction const& inJPlusFunction) + : + mNumPDimensions(numPDimensions), + mNumFDimensions(numFDimensions), + mFFunction(inFFunction), + mJPlusFunction(inJPlusFunction), + mF(mNumFDimensions), + mJ(mNumFDimensions, mNumPDimensions), + mJTJ(mNumPDimensions, mNumPDimensions), + mNegJTF(mNumPDimensions), + mDecomposer(mNumPDimensions), + mUseJFunction(false) + { + LogAssert(mNumPDimensions > 0 && mNumFDimensions > 0, "Invalid dimensions."); + } + + // Disallow copy, assignment and move semantics. + LevenbergMarquardtMinimizer(LevenbergMarquardtMinimizer const&) = delete; + LevenbergMarquardtMinimizer& operator=(LevenbergMarquardtMinimizer const&) = delete; + LevenbergMarquardtMinimizer(LevenbergMarquardtMinimizer&&) = delete; + LevenbergMarquardtMinimizer& operator=(LevenbergMarquardtMinimizer&&) = delete; + + inline int GetNumPDimensions() const { return mNumPDimensions; } + inline int GetNumFDimensions() const { return mNumFDimensions; } + + // The lambda is positive, the multiplier is positive, and the initial + // guess for the p-parameter is p0. Typical choices are lambda = + // 0.001 and multiplier = 10. TODO: Explain lambda in more detail, + // Multiview Geometry mentions lambda = 0.001*average(diagonal(JTJ)), + // but let's just expose the factor in front of the average. + + struct Result + { + DVector minLocation; + Real minError; + Real minErrorDifference; + Real minUpdateLength; + size_t numIterations; + size_t numAdjustments; + bool converged; + }; + + Result operator()(DVector const& p0, size_t maxIterations, + Real updateLengthTolerance, Real errorDifferenceTolerance, + Real lambdaFactor, Real lambdaAdjust, size_t maxAdjustments) + { + Result result; + result.minLocation = p0; + result.minError = std::numeric_limits::max(); + result.minErrorDifference = std::numeric_limits::max(); + result.minUpdateLength = (Real)0; + result.numIterations = 0; + result.numAdjustments = 0; + result.converged = false; + + // As a simple precaution, ensure that the lambda inputs are + // valid. If invalid, fall back to Gauss-Newton iteration. + if (lambdaFactor <= (Real)0 || lambdaAdjust <= (Real)0) + { + maxAdjustments = 1; + lambdaFactor = (Real)0; + lambdaAdjust = (Real)1; + } + + // As a simple precaution, ensure the tolerances are nonnegative. + updateLengthTolerance = std::max(updateLengthTolerance, (Real)0); + errorDifferenceTolerance = std::max(errorDifferenceTolerance, (Real)0); + + // Compute the initial error. + mFFunction(p0, mF); + result.minError = Dot(mF, mF); + + // Do the Levenberg-Marquart iterations. + auto pCurrent = p0; + for (result.numIterations = 1; result.numIterations <= maxIterations; ++result.numIterations) + { + std::pair status; + DVector pNext; + for (result.numAdjustments = 0; result.numAdjustments < maxAdjustments; ++result.numAdjustments) + { + status = DoIteration(pCurrent, lambdaFactor, updateLengthTolerance, + errorDifferenceTolerance, pNext, result); + if (status.first) + { + // Either the Cholesky decomposition failed or the + // iterates converged within tolerance. TODO: See the + // note in DoIteration about not failing on Cholesky + // decomposition. + return result; + } + + if (status.second) + { + // The error has been reduced but we have not yet + // converged within tolerance. + break; + } + + lambdaFactor *= lambdaAdjust; + } + + if (result.numAdjustments < maxAdjustments) + { + // The current value of lambda led us to an update that + // reduced the error, but the error is not yet small + // enough to conclude we converged. Reduce lambda for the + // next outer-loop iteration. + lambdaFactor /= lambdaAdjust; + } + else + { + // All lambdas tried during the inner-loop iteration did + // not lead to a reduced error. If we do nothing here, + // the next inner-loop iteration will continue to multiply + // lambda, risking eventual floating-point overflow. To + // avoid this, fall back to a Gauss-Newton iterate. + status = DoIteration(pCurrent, lambdaFactor, updateLengthTolerance, + errorDifferenceTolerance, pNext, result); + if (status.first) + { + // Either the Cholesky decomposition failed or the + // iterates converged within tolerance. TODO: See the + // note in DoIteration about not failing on Cholesky + // decomposition. + return result; + } + } + + pCurrent = pNext; + } + + return result; + } + + private: + void ComputeLinearSystemInputs(DVector const& pCurrent, Real lambda) + { + if (mUseJFunction) + { + mJFunction(pCurrent, mJ); + mJTJ = MultiplyATB(mJ, mJ); + mNegJTF = -(mF * mJ); + } + else + { + mJPlusFunction(pCurrent, mJTJ, mNegJTF); + } + + Real diagonalSum(0); + for (int i = 0; i < mNumPDimensions; ++i) + { + diagonalSum += mJTJ(i, i); + } + + Real diagonalAdjust = lambda * diagonalSum / static_cast(mNumPDimensions); + for (int i = 0; i < mNumPDimensions; ++i) + { + mJTJ(i, i) += diagonalAdjust; + } + } + + // The returned 'first' is true when the linear system cannot be + // solved (result.converged is false in this case) or when the + // error is reduced to within the tolerances specified by the caller + // (result.converged is true in this case). When the 'first' value + // is true, the 'second' value is true when the error is reduced or + // false when it is not. + std::pair DoIteration(DVector const& pCurrent, Real lambdaFactor, + Real updateLengthTolerance, Real errorDifferenceTolerance, DVector& pNext, + Result& result) + { + ComputeLinearSystemInputs(pCurrent, lambdaFactor); + if (!mDecomposer.Factor(mJTJ)) + { + // TODO: The matrix mJTJ is positive semi-definite, so the + // failure can occur when mJTJ has a zero eigenvalue in + // which case mJTJ is not invertible. Generate an iterate + // anyway, perhaps using gradient descent? + return std::make_pair(true, false); + } + mDecomposer.SolveLower(mJTJ, mNegJTF); + mDecomposer.SolveUpper(mJTJ, mNegJTF); + + pNext = pCurrent + mNegJTF; + mFFunction(pNext, mF); + Real error = Dot(mF, mF); + if (error < result.minError) + { + result.minErrorDifference = result.minError - error; + result.minUpdateLength = Length(mNegJTF); + result.minLocation = pNext; + result.minError = error; + if (result.minErrorDifference <= errorDifferenceTolerance + || result.minUpdateLength <= updateLengthTolerance) + { + result.converged = true; + return std::make_pair(true, true); + } + else + { + return std::make_pair(false, true); + } + } + else + { + return std::make_pair(false, false); + } + } + + int mNumPDimensions, mNumFDimensions; + FFunction mFFunction; + JFunction mJFunction; + JPlusFunction mJPlusFunction; + + // Storage for J^T(p)*J(p) and -J^T(p)*F(p) during the iterations. + RVector mF; + JMatrix mJ; + JTJMatrix mJTJ; + JTFVector mNegJTF; + + CholeskyDecomposition mDecomposer; + + bool mUseJFunction; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLine.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLine.h new file mode 100644 index 000000000000..b966126fec40 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLine.h @@ -0,0 +1,112 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The line is represented by P+t*D, where P is an origin point, D is a +// unit-length direction vector, and t is any real number. The user must +// ensure that D is unit length. + +namespace gte +{ + +template +class Line +{ +public: + // Construction and destruction. The default constructor sets the origin + // to (0,...,0) and the line direction to (1,0,...,0). + Line(); + Line(Vector const& inOrigin, Vector const& inDirection); + + // Public member access. The direction must be unit length. + Vector origin, direction; + +public: + // Comparisons to support sorted containers. + bool operator==(Line const& line) const; + bool operator!=(Line const& line) const; + bool operator< (Line const& line) const; + bool operator<=(Line const& line) const; + bool operator> (Line const& line) const; + bool operator>=(Line const& line) const; +}; + +// Template aliases for convenience. +template +using Line2 = Line<2, Real>; + +template +using Line3 = Line<3, Real>; + + +template +Line::Line() +{ + origin.MakeZero(); + direction.MakeUnit(0); +} + +template +Line::Line(Vector const& inOrigin, + Vector const& inDirection) + : + origin(inOrigin), + direction(inDirection) +{ +} + +template +bool Line::operator==(Line const& line) const +{ + return origin == line.origin && direction == line.direction; +} + +template +bool Line::operator!=(Line const& line) const +{ + return !operator==(line); +} + +template +bool Line::operator<(Line const& line) const +{ + if (origin < line.origin) + { + return true; + } + + if (origin > line.origin) + { + return false; + } + + return direction < line.direction; +} + +template +bool Line::operator<=(Line const& line) const +{ + return operator<(line) || operator==(line); +} + +template +bool Line::operator>(Line const& line) const +{ + return !operator<=(line); +} + +template +bool Line::operator>=(Line const& line) const +{ + return !operator<(line); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLinearSystem.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLinearSystem.h new file mode 100644 index 000000000000..ce204d82c7fa --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLinearSystem.h @@ -0,0 +1,401 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Solve linear systems of equations where the matrix A is NxN. The return +// value of a function is 'true' when A is invertible. In this case the +// solution X and the solution is valid. If the return value is 'false', A +// is not invertible and X and Y are invalid, so do not use them. When a +// matrix is passed as Real*, the storage order is assumed to be the one +// consistent with your choice of GTE_USE_ROW_MAJOR or GTE_USE_COL_MAJOR. +// +// The linear solvers that use the conjugate gradient algorithm are based +// on the discussion in "Matrix Computations, 2nd edition" by G. H. Golub +// and Charles F. Van Loan, The Johns Hopkins Press, Baltimore MD, Fourth +// Printing 1993. + +namespace gte +{ + +template +class LinearSystem +{ +public: + // Solve 2x2, 3x3, and 4x4 systems by inverting the matrix directly. This + // avoids the overhead of Gaussian elimination in small dimensions. + static bool Solve(Matrix2x2 const& A, Vector2 const& B, + Vector2& X); + + static bool Solve(Matrix3x3 const& A, Vector3 const& B, + Vector3& X); + + static bool Solve(Matrix4x4 const& A, Vector4 const& B, + Vector4& X); + + // Solve A*X = B, where B is Nx1 and the solution X is Nx1. + static bool Solve(int N, Real const* A, Real const* B, Real* X); + + // Solve A*X = B, where B is NxM and the solution X is NxM. + static bool Solve(int N, int M, Real const* A, Real const* B, Real* X); + + // Solve A*X = B, where A is tridiagonal. The function expects the + // subdiagonal, diagonal, and superdiagonal of A. The diagonal input + // must have N elements. The subdiagonal and superdiagonal inputs must + // have N-1 elements. + static bool SolveTridiagonal(int N, Real const* subdiagonal, + Real const* diagonal, Real const* superdiagonal, Real const* B, + Real* X); + + // Solve A*X = B, where A is tridiagonal. The function expects the + // subdiagonal, diagonal, and superdiagonal of A. Moreover, the + // subdiagonal elements are a constant, the diagonal elements are a + // constant, and the superdiagonal elements are a constant. + static bool SolveConstantTridiagonal(int N, Real subdiagonal, + Real diagonal, Real superdiagonal, Real const* B, Real* X); + + // Solve A*X = B using the conjugate gradient method, where A is + // symmetric. You must specify the maximum number of iterations and a + // tolerance for terminating the iterations. Reasonable choices for + // tolerance are 1e-06f for 'float' or 1e-08 for 'double'. + static unsigned int SolveSymmetricCG(int N, Real const* A, Real const* B, + Real* X, unsigned int maxIterations, Real tolerance); + + // Solve A*X = B using the conjugate gradient method, where A is sparse + // and symmetric. The nonzero entries of the symmetrix matrix A are + // stored in a map whose keys are pairs (i,j) and whose values are real + // numbers. The pair (i,j) is the location of the value in the array. + // Only one of (i,j) and (j,i) should be stored since A is symmetric. + // The column vector B is stored as an array of contiguous values. You + // must specify the maximum number of iterations and a tolerance for + // terminating the iterations. Reasonable choices for tolerance are + // 1e-06f for 'float' or 1e-08 for 'double'. + typedef std::map, Real> SparseMatrix; + static unsigned int SolveSymmetricCG(int N, SparseMatrix const& A, + Real const* B, Real* X, unsigned int maxIterations, Real tolerance); + +private: + // Support for the conjugate gradient method. + static Real Dot(int N, Real const* U, Real const* V); + static void Mul(int N, Real const* A, Real const* X, Real* P); + static void Mul(int N, SparseMatrix const& A, Real const* X, Real* P); + static void UpdateX(int N, Real* X, Real alpha, Real const* P); + static void UpdateR(int N, Real* R, Real alpha, Real const* W); + static void UpdateP(int N, Real* P, Real beta, Real const* R); +}; + + +template +bool LinearSystem::Solve(Matrix2x2 const& A, + Vector2 const& B, Vector2& X) +{ + bool invertible; + Matrix2x2 invA = Inverse(A, &invertible); + if (invertible) + { + X = invA * B; + } + else + { + X = Vector2::Zero(); + } + return invertible; +} + +template +bool LinearSystem::Solve(Matrix3x3 const& A, + Vector3 const& B, Vector3& X) +{ + bool invertible; + Matrix3x3 invA = Inverse(A, &invertible); + if (invertible) + { + X = invA * B; + } + else + { + X = Vector3::Zero(); + } + return invertible; +} + +template +bool LinearSystem::Solve(Matrix4x4 const& A, + Vector4 const& B, Vector4& X) +{ + bool invertible; + Matrix4x4 invA = Inverse(A, &invertible); + if (invertible) + { + X = invA * B; + } + else + { + X = Vector4::Zero(); + } + return invertible; +} + +template +bool LinearSystem::Solve(int N, Real const* A, Real const* B, Real* X) +{ + Real determinant; + return GaussianElimination()(N, A, nullptr, determinant, B, X, + nullptr, 0, nullptr); +} + +template +bool LinearSystem::Solve(int N, int M, Real const* A, Real const* B, + Real* X) +{ + Real determinant; + return GaussianElimination()(N, A, nullptr, determinant, nullptr, + nullptr, B, M, X); +} + +template +bool LinearSystem::SolveTridiagonal(int N, Real const* subdiagonal, + Real const* diagonal, Real const* superdiagonal, Real const* B, + Real* X) +{ + if (diagonal[0] == (Real)0) + { + return false; + } + + std::vector tmp(N - 1); + Real expr = diagonal[0]; + Real invExpr = ((Real)1) / expr; + X[0] = B[0] * invExpr; + + int i0, i1; + for (i0 = 0, i1 = 1; i1 < N; ++i0, ++i1) + { + tmp[i0] = superdiagonal[i0] * invExpr; + expr = diagonal[i1] - subdiagonal[i0] * tmp[i0]; + if (expr == (Real)0) + { + return false; + } + invExpr = ((Real)1) / expr; + X[i1] = (B[i1] - subdiagonal[i0] * X[i0]) * invExpr; + } + + for (i0 = N - 1, i1 = N - 2; i1 >= 0; --i0, --i1) + { + X[i1] -= tmp[i1] * X[i0]; + } + return true; +} + +template +bool LinearSystem::SolveConstantTridiagonal(int N, Real subdiagonal, + Real diagonal, Real superdiagonal, Real const* B, Real* X) +{ + if (diagonal == (Real)0) + { + return false; + } + + std::vector tmp(N - 1); + Real expr = diagonal; + Real invExpr = ((Real)1) / expr; + X[0] = B[0] * invExpr; + + int i0, i1; + for (i0 = 0, i1 = 1; i1 < N; ++i0, ++i1) + { + tmp[i0] = superdiagonal * invExpr; + expr = diagonal - subdiagonal * tmp[i0]; + if (expr == (Real)0) + { + return false; + } + invExpr = ((Real)1) / expr; + X[i1] = (B[i1] - subdiagonal * X[i0]) * invExpr; + } + + for (i0 = N - 1, i1 = N - 2; i1 >= 0; --i0, --i1) + { + X[i1] -= tmp[i1] * X[i0]; + } + return true; +} + +template +unsigned int LinearSystem::SolveSymmetricCG(int N, Real const* A, + Real const* B, Real* X, unsigned int maxIterations, Real tolerance) +{ + // The first iteration. + std::vector tmpR(N), tmpP(N), tmpW(N); + Real* R = &tmpR[0]; + Real* P = &tmpP[0]; + Real* W = &tmpW[0]; + size_t numBytes = N * sizeof(Real); + memset(X, 0, numBytes); + Memcpy(R, B, numBytes); + Real rho0 = Dot(N, R, R); + Memcpy(P, R, numBytes); + Mul(N, A, P, W); + Real alpha = rho0 / Dot(N, P, W); + UpdateX(N, X, alpha, P); + UpdateR(N, R, alpha, W); + Real rho1 = Dot(N, R, R); + + // The remaining iterations. + unsigned int iteration; + for (iteration = 1; iteration <= maxIterations; ++iteration) + { + Real root0 = std::sqrt(rho1); + Real norm = Dot(N, B, B); + Real root1 = std::sqrt(norm); + if (root0 <= tolerance*root1) + { + break; + } + + Real beta = rho1 / rho0; + UpdateP(N, P, beta, R); + Mul(N, A, P, W); + alpha = rho1 / Dot(N, P, W); + UpdateX(N, X, alpha, P); + UpdateR(N, R, alpha, W); + rho0 = rho1; + rho1 = Dot(N, R, R); + } + return iteration; +} + +template +unsigned int LinearSystem::SolveSymmetricCG(int N, + SparseMatrix const& A, Real const* B, Real* X, unsigned int maxIterations, + Real tolerance) +{ + // The first iteration. + std::vector tmpR(N), tmpP(N), tmpW(N); + Real* R = &tmpR[0]; + Real* P = &tmpP[0]; + Real* W = &tmpW[0]; + size_t numBytes = N * sizeof(Real); + memset(X, 0, numBytes); + Memcpy(R, B, numBytes); + Real rho0 = Dot(N, R, R); + Memcpy(P, R, numBytes); + Mul(N, A, P, W); + Real alpha = rho0 / Dot(N, P, W); + UpdateX(N, X, alpha, P); + UpdateR(N, R, alpha, W); + Real rho1 = Dot(N, R, R); + + // The remaining iterations. + unsigned int iteration; + for (iteration = 1; iteration <= maxIterations; ++iteration) + { + Real root0 = std::sqrt(rho1); + Real norm = Dot(N, B, B); + Real root1 = std::sqrt(norm); + if (root0 <= tolerance*root1) + { + break; + } + + Real beta = rho1 / rho0; + UpdateP(N, P, beta, R); + Mul(N, A, P, W); + alpha = rho1 / Dot(N, P, W); + UpdateX(N, X, alpha, P); + UpdateR(N, R, alpha, W); + rho0 = rho1; + rho1 = Dot(N, R, R); + } + return iteration; +} + +template +Real LinearSystem::Dot(int N, Real const* U, Real const* V) +{ + Real dot = (Real)0; + for (int i = 0; i < N; ++i) + { + dot += U[i] * V[i]; + } + return dot; +} + +template +void LinearSystem::Mul(int N, Real const* A, Real const* X, Real* P) +{ +#if defined(GTE_USE_ROW_MAJOR) + LexicoArray2 matA(N, N, const_cast(A)); +#else + LexicoArray2 matA(N, N, const_cast(A)); +#endif + + memset(P, 0, N * sizeof(Real)); + for (int row = 0; row < N; ++row) + { + for (int col = 0; col < N; ++col) + { + P[row] += matA(row, col) * X[col]; + } + } +} + +template +void LinearSystem::Mul(int N, SparseMatrix const& A, Real const* X, + Real* P) +{ + memset(P, 0, N * sizeof(Real)); + for (auto const& element : A) + { + int i = element.first[0]; + int j = element.first[1]; + Real value = element.second; + P[i] += value * X[j]; + if (i != j) + { + P[j] += value * X[i]; + } + } +} + +template +void LinearSystem::UpdateX(int N, Real* X, Real alpha, Real const* P) +{ + for (int i = 0; i < N; ++i) + { + X[i] += alpha*P[i]; + } +} + +template +void LinearSystem::UpdateR(int N, Real* R, Real alpha, Real const* W) +{ + for (int i = 0; i < N; ++i) + { + R[i] -= alpha*W[i]; + } +} + +template +void LinearSystem::UpdateP(int N, Real* P, Real beta, Real const* R) +{ + for (int i = 0; i < N; ++i) + { + P[i] = R[i] + beta*P[i]; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLog2Estimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLog2Estimate.h new file mode 100644 index 000000000000..45767f529211 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLog2Estimate.h @@ -0,0 +1,174 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Minimax polynomial approximations to log2(x). The polynomial p(x) of +// degree D minimizes the quantity maximum{|log2(x) - p(x)| : x in [1,2]} +// over all polynomials of degree D. + +namespace gte +{ + +template +class Log2Estimate +{ +public: + // The input constraint is x in [1,2]. For example, + // float x; // in [1,2] + // float result = Log2Estimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input constraint is x > 0. Range reduction is used to generate a + // value y in (0,1], call Degree(y), and add the exponent for the power + // of two in the binary scientific representation of x. For example, + // float x; // x > 0 + // float result = Log2Estimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<1>, Real t); + inline static Real Evaluate(degree<2>, Real t); + inline static Real Evaluate(degree<3>, Real t); + inline static Real Evaluate(degree<4>, Real t); + inline static Real Evaluate(degree<5>, Real t); + inline static Real Evaluate(degree<6>, Real t); + inline static Real Evaluate(degree<7>, Real t); + inline static Real Evaluate(degree<8>, Real t); +}; + + +template +template +inline Real Log2Estimate::Degree(Real x) +{ + Real t = x - (Real)1; // t in (0,1] + return Evaluate(degree(), t); +} + +template +template +inline Real Log2Estimate::DegreeRR(Real x) +{ + int p; + Real y = std::frexp(x, &p); // y in [1/2,1) + y = ((Real)2)*y; // y in [1,2) + --p; + Real poly = Degree(y); + Real result = poly + (Real)p; + return result; +} + +template +inline Real Log2Estimate::Evaluate(degree<1>, Real t) +{ + Real poly; + poly = (Real)GTE_C_LOG2_DEG1_C1; + poly = poly * t; + return poly; +} + +template +inline Real Log2Estimate::Evaluate(degree<2>, Real t) +{ + Real poly; + poly = (Real)GTE_C_LOG2_DEG2_C2; + poly = (Real)GTE_C_LOG2_DEG2_C1 + poly * t; + poly = poly * t; + return poly; +} + +template +inline Real Log2Estimate::Evaluate(degree<3>, Real t) +{ + Real poly; + poly = (Real)GTE_C_LOG2_DEG3_C3; + poly = (Real)GTE_C_LOG2_DEG3_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG3_C1 + poly * t; + poly = poly * t; + return poly; +} + +template +inline Real Log2Estimate::Evaluate(degree<4>, Real t) +{ + Real poly; + poly = (Real)GTE_C_LOG2_DEG4_C4; + poly = (Real)GTE_C_LOG2_DEG4_C3 + poly * t; + poly = (Real)GTE_C_LOG2_DEG4_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG4_C1 + poly * t; + poly = poly * t; + return poly; +} + +template +inline Real Log2Estimate::Evaluate(degree<5>, Real t) +{ + Real poly; + poly = (Real)GTE_C_LOG2_DEG5_C5; + poly = (Real)GTE_C_LOG2_DEG5_C4 + poly * t; + poly = (Real)GTE_C_LOG2_DEG5_C3 + poly * t; + poly = (Real)GTE_C_LOG2_DEG5_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG5_C1 + poly * t; + poly = poly * t; + return poly; +} + +template +inline Real Log2Estimate::Evaluate(degree<6>, Real t) +{ + Real poly; + poly = (Real)GTE_C_LOG2_DEG6_C6; + poly = (Real)GTE_C_LOG2_DEG6_C5 + poly * t; + poly = (Real)GTE_C_LOG2_DEG6_C4 + poly * t; + poly = (Real)GTE_C_LOG2_DEG6_C3 + poly * t; + poly = (Real)GTE_C_LOG2_DEG6_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG6_C1 + poly * t; + poly = poly * t; + return poly; +} + +template +inline Real Log2Estimate::Evaluate(degree<7>, Real t) +{ + Real poly; + poly = (Real)GTE_C_LOG2_DEG7_C7; + poly = (Real)GTE_C_LOG2_DEG7_C6 + poly * t; + poly = (Real)GTE_C_LOG2_DEG7_C5 + poly * t; + poly = (Real)GTE_C_LOG2_DEG7_C4 + poly * t; + poly = (Real)GTE_C_LOG2_DEG7_C3 + poly * t; + poly = (Real)GTE_C_LOG2_DEG7_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG7_C1 + poly * t; + poly = poly * t; + return poly; +} + +template +inline Real Log2Estimate::Evaluate(degree<8>, Real t) +{ + Real poly; + poly = (Real)GTE_C_LOG2_DEG8_C8; + poly = (Real)GTE_C_LOG2_DEG8_C7 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C6 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C5 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C4 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C3 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C1 + poly * t; + poly = poly * t; + return poly; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLogEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLogEstimate.h new file mode 100644 index 000000000000..562906421780 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLogEstimate.h @@ -0,0 +1,55 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Minimax polynomial approximations to log2(x). The polynomial p(x) of +// degree D minimizes the quantity maximum{|log2(x) - p(x)| : x in [1,2]} +// over all polynomials of degree D. The natural logarithm is computed +// using log(x) = log2(x)/log2(e) = log2(x)*log(2). + +namespace gte +{ + +template +class LogEstimate +{ +public: + // The input constraint is x in [1,2]. For example, + // float x; // in [1,2] + // float result = LogEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input constraint is x > 0. Range reduction is used to generate a + // value y in (0,1], call Degree(y), and add the exponent for the power + // of two in the binary scientific representation of x. For example, + // float x; // x > 0 + // float result = LogEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); +}; + + +template +template +inline Real LogEstimate::Degree(Real x) +{ + return Log2Estimate::Degree(x) * (Real)GTE_C_INV_LN_2; +} + +template +template +inline Real LogEstimate::DegreeRR(Real x) +{ + return Log2Estimate::DegreeRR(x) * (Real)GTE_C_INV_LN_2; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMath.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMath.h new file mode 100644 index 000000000000..39379617ce80 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMath.h @@ -0,0 +1,654 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.17.1 (2019/01/17) + +#pragma once + +// This file extends the support to include convenient constants and +// functions. The shared constants for CPU, Intel SSE and GPU lead to +// correctly rounded approximations of the constants when using 'float' or +// 'double'. The file also includes a type trait, is_arbitrary_precision, +// to support selecting between floating-point arithmetic (float, double, +//long double) or arbitrary-precision arithmetic (BSNumber, BSRational) +// in an implementation using std::enable_if. There is also a type trait, +// has_division_operator, to support selecting between numeric types that +// have a division operator (BSRational) and those that do not have a +// division operator (BSNumber). + +#include +#include +#include +#include + +// Maximum number of iterations for bisection before a subinterval +// degenerates to a single point. TODO: Verify these. I used the formula: +// 3 + std::numeric_limits::digits - std::numeric_limits::min_exponent. +// IEEEBinary16: digits = 11, min_exponent = -13 +// float: digits = 27, min_exponent = -125 +// double: digits = 53, min_exponent = -1021 +// BSNumber and BSRational use std::numeric_limits::max(), +// but maybe these should be set to a large number or be user configurable. +// The MAX_BISECTIONS_GENERIC is an arbitrary choice for now and is used +// in template code where Real is the template parameter. +#define GTE_C_MAX_BISECTIONS_FLOAT16 27u +#define GTE_C_MAX_BISECTIONS_FLOAT32 155u +#define GTE_C_MAX_BISECTIONS_FLOAT64 1077u +#define GTE_C_MAX_BISECTIONS_BSNUMBER 0xFFFFFFFFu +#define GTE_C_MAX_BISECTIONS_BSRATIONAL 0xFFFFFFFFu +#define GTE_C_MAX_BISECTIONS_GENERIC 2048u + +// Constants involving pi. +#define GTE_C_PI 3.1415926535897931 +#define GTE_C_HALF_PI 1.5707963267948966 +#define GTE_C_QUARTER_PI 0.7853981633974483 +#define GTE_C_TWO_PI 6.2831853071795862 +#define GTE_C_INV_PI 0.3183098861837907 +#define GTE_C_INV_TWO_PI 0.1591549430918953 +#define GTE_C_INV_HALF_PI 0.6366197723675813 + +// Conversions between degrees and radians. +#define GTE_C_DEG_TO_RAD 0.0174532925199433 +#define GTE_C_RAD_TO_DEG 57.295779513082321 + +// Common constants. +#define GTE_C_SQRT_2 1.4142135623730951 +#define GTE_C_INV_SQRT_2 0.7071067811865475 +#define GTE_C_LN_2 0.6931471805599453 +#define GTE_C_INV_LN_2 1.4426950408889634 +#define GTE_C_LN_10 2.3025850929940459 +#define GTE_C_INV_LN_10 0.43429448190325176 + +// Constants for minimax polynomial approximations to sqrt(x). +// The algorithm minimizes the maximum absolute error on [1,2]. +#define GTE_C_SQRT_DEG1_C0 +1.0 +#define GTE_C_SQRT_DEG1_C1 +4.1421356237309505e-01 +#define GTE_C_SQRT_DEG1_MAX_ERROR 1.7766952966368793e-2 + +#define GTE_C_SQRT_DEG2_C0 +1.0 +#define GTE_C_SQRT_DEG2_C1 +4.8563183076125260e-01 +#define GTE_C_SQRT_DEG2_C2 -7.1418268388157458e-02 +#define GTE_C_SQRT_DEG2_MAX_ERROR 1.1795695163108744e-3 + +#define GTE_C_SQRT_DEG3_C0 +1.0 +#define GTE_C_SQRT_DEG3_C1 +4.9750045320242231e-01 +#define GTE_C_SQRT_DEG3_C2 -1.0787308044477850e-01 +#define GTE_C_SQRT_DEG3_C3 +2.4586189615451115e-02 +#define GTE_C_SQRT_DEG3_MAX_ERROR 1.1309620116468910e-4 + +#define GTE_C_SQRT_DEG4_C0 +1.0 +#define GTE_C_SQRT_DEG4_C1 +4.9955939832918816e-01 +#define GTE_C_SQRT_DEG4_C2 -1.2024066151943025e-01 +#define GTE_C_SQRT_DEG4_C3 +4.5461507257698486e-02 +#define GTE_C_SQRT_DEG4_C4 -1.0566681694362146e-02 +#define GTE_C_SQRT_DEG4_MAX_ERROR 1.2741170151556180e-5 + +#define GTE_C_SQRT_DEG5_C0 +1.0 +#define GTE_C_SQRT_DEG5_C1 +4.9992197660031912e-01 +#define GTE_C_SQRT_DEG5_C2 -1.2378506719245053e-01 +#define GTE_C_SQRT_DEG5_C3 +5.6122776972699739e-02 +#define GTE_C_SQRT_DEG5_C4 -2.3128836281145482e-02 +#define GTE_C_SQRT_DEG5_C5 +5.0827122737047148e-03 +#define GTE_C_SQRT_DEG5_MAX_ERROR 1.5725568940708201e-6 + +#define GTE_C_SQRT_DEG6_C0 +1.0 +#define GTE_C_SQRT_DEG6_C1 +4.9998616695784914e-01 +#define GTE_C_SQRT_DEG6_C2 -1.2470733323278438e-01 +#define GTE_C_SQRT_DEG6_C3 +6.0388587356982271e-02 +#define GTE_C_SQRT_DEG6_C4 -3.1692053551807930e-02 +#define GTE_C_SQRT_DEG6_C5 +1.2856590305148075e-02 +#define GTE_C_SQRT_DEG6_C6 -2.6183954624343642e-03 +#define GTE_C_SQRT_DEG6_MAX_ERROR 2.0584155535630089e-7 + +#define GTE_C_SQRT_DEG7_C0 +1.0 +#define GTE_C_SQRT_DEG7_C1 +4.9999754817809228e-01 +#define GTE_C_SQRT_DEG7_C2 -1.2493243476353655e-01 +#define GTE_C_SQRT_DEG7_C3 +6.1859954146370910e-02 +#define GTE_C_SQRT_DEG7_C4 -3.6091595023208356e-02 +#define GTE_C_SQRT_DEG7_C5 +1.9483946523450868e-02 +#define GTE_C_SQRT_DEG7_C6 -7.5166134568007692e-03 +#define GTE_C_SQRT_DEG7_C7 +1.4127567687864939e-03 +#define GTE_C_SQRT_DEG7_MAX_ERROR 2.8072302919734948e-8 + +#define GTE_C_SQRT_DEG8_C0 +1.0 +#define GTE_C_SQRT_DEG8_C1 +4.9999956583056759e-01 +#define GTE_C_SQRT_DEG8_C2 -1.2498490369914350e-01 +#define GTE_C_SQRT_DEG8_C3 +6.2318494667579216e-02 +#define GTE_C_SQRT_DEG8_C4 -3.7982961896432244e-02 +#define GTE_C_SQRT_DEG8_C5 +2.3642612312869460e-02 +#define GTE_C_SQRT_DEG8_C6 -1.2529377587270574e-02 +#define GTE_C_SQRT_DEG8_C7 +4.5382426960713929e-03 +#define GTE_C_SQRT_DEG8_C8 -7.8810995273670414e-04 +#define GTE_C_SQRT_DEG8_MAX_ERROR 3.9460605685825989e-9 + +// Constants for minimax polynomial approximations to 1/sqrt(x). +// The algorithm minimizes the maximum absolute error on [1,2]. +#define GTE_C_INVSQRT_DEG1_C0 +1.0 +#define GTE_C_INVSQRT_DEG1_C1 -2.9289321881345254e-01 +#define GTE_C_INVSQRT_DEG1_MAX_ERROR 3.7814314552701983e-2 + +#define GTE_C_INVSQRT_DEG2_C0 +1.0 +#define GTE_C_INVSQRT_DEG2_C1 -4.4539812104566801e-01 +#define GTE_C_INVSQRT_DEG2_C2 +1.5250490223221547e-01 +#define GTE_C_INVSQRT_DEG2_MAX_ERROR 4.1953446330581234e-3 + +#define GTE_C_INVSQRT_DEG3_C0 +1.0 +#define GTE_C_INVSQRT_DEG3_C1 -4.8703230993068791e-01 +#define GTE_C_INVSQRT_DEG3_C2 +2.8163710486669835e-01 +#define GTE_C_INVSQRT_DEG3_C3 -8.7498013749463421e-02 +#define GTE_C_INVSQRT_DEG3_MAX_ERROR 5.6307702007266786e-4 + +#define GTE_C_INVSQRT_DEG4_C0 +1.0 +#define GTE_C_INVSQRT_DEG4_C1 -4.9710061558048779e-01 +#define GTE_C_INVSQRT_DEG4_C2 +3.4266247597676802e-01 +#define GTE_C_INVSQRT_DEG4_C3 -1.9106356536293490e-01 +#define GTE_C_INVSQRT_DEG4_C4 +5.2608486153198797e-02 +#define GTE_C_INVSQRT_DEG4_MAX_ERROR 8.1513919987605266e-5 + +#define GTE_C_INVSQRT_DEG5_C0 +1.0 +#define GTE_C_INVSQRT_DEG5_C1 -4.9937760586004143e-01 +#define GTE_C_INVSQRT_DEG5_C2 +3.6508741295133973e-01 +#define GTE_C_INVSQRT_DEG5_C3 -2.5884890281853501e-01 +#define GTE_C_INVSQRT_DEG5_C4 +1.3275782221320753e-01 +#define GTE_C_INVSQRT_DEG5_C5 -3.2511945299404488e-02 +#define GTE_C_INVSQRT_DEG5_MAX_ERROR 1.2289367475583346e-5 + +#define GTE_C_INVSQRT_DEG6_C0 +1.0 +#define GTE_C_INVSQRT_DEG6_C1 -4.9987029229547453e-01 +#define GTE_C_INVSQRT_DEG6_C2 +3.7220923604495226e-01 +#define GTE_C_INVSQRT_DEG6_C3 -2.9193067713256937e-01 +#define GTE_C_INVSQRT_DEG6_C4 +1.9937605991094642e-01 +#define GTE_C_INVSQRT_DEG6_C5 -9.3135712130901993e-02 +#define GTE_C_INVSQRT_DEG6_C6 +2.0458166789566690e-02 +#define GTE_C_INVSQRT_DEG6_MAX_ERROR 1.9001451223750465e-6 + +#define GTE_C_INVSQRT_DEG7_C0 +1.0 +#define GTE_C_INVSQRT_DEG7_C1 -4.9997357250704977e-01 +#define GTE_C_INVSQRT_DEG7_C2 +3.7426216884998809e-01 +#define GTE_C_INVSQRT_DEG7_C3 -3.0539882498248971e-01 +#define GTE_C_INVSQRT_DEG7_C4 +2.3976005607005391e-01 +#define GTE_C_INVSQRT_DEG7_C5 -1.5410326351684489e-01 +#define GTE_C_INVSQRT_DEG7_C6 +6.5598809723041995e-02 +#define GTE_C_INVSQRT_DEG7_C7 -1.3038592450470787e-02 +#define GTE_C_INVSQRT_DEG7_MAX_ERROR 2.9887724993168940e-7 + +#define GTE_C_INVSQRT_DEG8_C0 +1.0 +#define GTE_C_INVSQRT_DEG8_C1 -4.9999471066120371e-01 +#define GTE_C_INVSQRT_DEG8_C2 +3.7481415745794067e-01 +#define GTE_C_INVSQRT_DEG8_C3 -3.1023804387422160e-01 +#define GTE_C_INVSQRT_DEG8_C4 +2.5977002682930106e-01 +#define GTE_C_INVSQRT_DEG8_C5 -1.9818790717727097e-01 +#define GTE_C_INVSQRT_DEG8_C6 +1.1882414252613671e-01 +#define GTE_C_INVSQRT_DEG8_C7 -4.6270038088550791e-02 +#define GTE_C_INVSQRT_DEG8_C8 +8.3891541755747312e-03 +#define GTE_C_INVSQRT_DEG8_MAX_ERROR 4.7596926146947771e-8 + +// Constants for minimax polynomial approximations to sin(x). +// The algorithm minimizes the maximum absolute error on [-pi/2,pi/2]. +#define GTE_C_SIN_DEG3_C0 +1.0 +#define GTE_C_SIN_DEG3_C1 -1.4727245910375519e-01 +#define GTE_C_SIN_DEG3_MAX_ERROR 1.3481903639145865e-2 + +#define GTE_C_SIN_DEG5_C0 +1.0 +#define GTE_C_SIN_DEG5_C1 -1.6600599923812209e-01 +#define GTE_C_SIN_DEG5_C2 +7.5924178409012000e-03 +#define GTE_C_SIN_DEG5_MAX_ERROR 1.4001209384639779e-4 + +#define GTE_C_SIN_DEG7_C0 +1.0 +#define GTE_C_SIN_DEG7_C1 -1.6665578084732124e-01 +#define GTE_C_SIN_DEG7_C2 +8.3109378830028557e-03 +#define GTE_C_SIN_DEG7_C3 -1.8447486103462252e-04 +#define GTE_C_SIN_DEG7_MAX_ERROR 1.0205878936686563e-6 + +#define GTE_C_SIN_DEG9_C0 +1.0 +#define GTE_C_SIN_DEG9_C1 -1.6666656235308897e-01 +#define GTE_C_SIN_DEG9_C2 +8.3329962509886002e-03 +#define GTE_C_SIN_DEG9_C3 -1.9805100675274190e-04 +#define GTE_C_SIN_DEG9_C4 +2.5967200279475300e-06 +#define GTE_C_SIN_DEG9_MAX_ERROR 5.2010746265374053e-9 + +#define GTE_C_SIN_DEG11_C0 +1.0 +#define GTE_C_SIN_DEG11_C1 -1.6666666601721269e-01 +#define GTE_C_SIN_DEG11_C2 +8.3333303183525942e-03 +#define GTE_C_SIN_DEG11_C3 -1.9840782426250314e-04 +#define GTE_C_SIN_DEG11_C4 +2.7521557770526783e-06 +#define GTE_C_SIN_DEG11_C5 -2.3828544692960918e-08 +#define GTE_C_SIN_DEG11_MAX_ERROR 1.9295870457014530e-11 + +// Constants for minimax polynomial approximations to cos(x). +// The algorithm minimizes the maximum absolute error on [-pi/2,pi/2]. +#define GTE_C_COS_DEG2_C0 +1.0 +#define GTE_C_COS_DEG2_C1 -4.0528473456935105e-01 +#define GTE_C_COS_DEG2_MAX_ERROR 5.4870946878404048e-2 + +#define GTE_C_COS_DEG4_C0 +1.0 +#define GTE_C_COS_DEG4_C1 -4.9607181958647262e-01 +#define GTE_C_COS_DEG4_C2 +3.6794619653489236e-02 +#define GTE_C_COS_DEG4_MAX_ERROR 9.1879932449712154e-4 + +#define GTE_C_COS_DEG6_C0 +1.0 +#define GTE_C_COS_DEG6_C1 -4.9992746217057404e-01 +#define GTE_C_COS_DEG6_C2 +4.1493920348353308e-02 +#define GTE_C_COS_DEG6_C3 -1.2712435011987822e-03 +#define GTE_C_COS_DEG6_MAX_ERROR 9.2028470133065365e-6 + +#define GTE_C_COS_DEG8_C0 +1.0 +#define GTE_C_COS_DEG8_C1 -4.9999925121358291e-01 +#define GTE_C_COS_DEG8_C2 +4.1663780117805693e-02 +#define GTE_C_COS_DEG8_C3 -1.3854239405310942e-03 +#define GTE_C_COS_DEG8_C4 +2.3154171575501259e-05 +#define GTE_C_COS_DEG8_MAX_ERROR 5.9804533020235695e-8 + +#define GTE_C_COS_DEG10_C0 +1.0 +#define GTE_C_COS_DEG10_C1 -4.9999999508695869e-01 +#define GTE_C_COS_DEG10_C2 +4.1666638865338612e-02 +#define GTE_C_COS_DEG10_C3 -1.3888377661039897e-03 +#define GTE_C_COS_DEG10_C4 +2.4760495088926859e-05 +#define GTE_C_COS_DEG10_C5 -2.6051615464872668e-07 +#define GTE_C_COS_DEG10_MAX_ERROR 2.7006769043325107e-10 + +// Constants for minimax polynomial approximations to tan(x). +// The algorithm minimizes the maximum absolute error on [-pi/4,pi/4]. +#define GTE_C_TAN_DEG3_C0 1.0 +#define GTE_C_TAN_DEG3_C1 4.4295926544736286e-01 +#define GTE_C_TAN_DEG3_MAX_ERROR 1.1661892256204731e-2 + +#define GTE_C_TAN_DEG5_C0 1.0 +#define GTE_C_TAN_DEG5_C1 3.1401320403542421e-01 +#define GTE_C_TAN_DEG5_C2 2.0903948109240345e-01 +#define GTE_C_TAN_DEG5_MAX_ERROR 5.8431854390143118e-4 + +#define GTE_C_TAN_DEG7_C0 1.0 +#define GTE_C_TAN_DEG7_C1 3.3607213284422555e-01 +#define GTE_C_TAN_DEG7_C2 1.1261037305184907e-01 +#define GTE_C_TAN_DEG7_C3 9.8352099470524479e-02 +#define GTE_C_TAN_DEG7_MAX_ERROR 3.5418688397723108e-5 + +#define GTE_C_TAN_DEG9_C0 1.0 +#define GTE_C_TAN_DEG9_C1 3.3299232843941784e-01 +#define GTE_C_TAN_DEG9_C2 1.3747843432474838e-01 +#define GTE_C_TAN_DEG9_C3 3.7696344813028304e-02 +#define GTE_C_TAN_DEG9_C4 4.6097377279281204e-02 +#define GTE_C_TAN_DEG9_MAX_ERROR 2.2988173242199927e-6 + +#define GTE_C_TAN_DEG11_C0 1.0 +#define GTE_C_TAN_DEG11_C1 3.3337224456224224e-01 +#define GTE_C_TAN_DEG11_C2 1.3264516053824593e-01 +#define GTE_C_TAN_DEG11_C3 5.8145237645931047e-02 +#define GTE_C_TAN_DEG11_C4 1.0732193237572574e-02 +#define GTE_C_TAN_DEG11_C5 2.1558456793513869e-02 +#define GTE_C_TAN_DEG11_MAX_ERROR 1.5426257940140409e-7 + +#define GTE_C_TAN_DEG13_C0 1.0 +#define GTE_C_TAN_DEG13_C1 3.3332916426394554e-01 +#define GTE_C_TAN_DEG13_C2 1.3343404625112498e-01 +#define GTE_C_TAN_DEG13_C3 5.3104565343119248e-02 +#define GTE_C_TAN_DEG13_C4 2.5355038312682154e-02 +#define GTE_C_TAN_DEG13_C5 1.8253255966556026e-03 +#define GTE_C_TAN_DEG13_C6 1.0069407176615641e-02 +#define GTE_C_TAN_DEG13_MAX_ERROR 1.0550264249037378e-8 + +// Constants for minimax polynomial approximations to acos(x), where the +// approximation is of the form acos(x) = sqrt(1 - x)*p(x) with p(x) a +// polynomial. The algorithm minimizes the maximum error +// |acos(x)/sqrt(1-x) - p(x)| on [0,1]. At the same time we get an +// approximation for asin(x) = pi/2 - acos(x). +#define GTE_C_ACOS_DEG1_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG1_C1 -1.5658276442180141e-01 +#define GTE_C_ACOS_DEG1_MAX_ERROR 1.1659002803738105e-2 + +#define GTE_C_ACOS_DEG2_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG2_C1 -2.0347053865798365e-01 +#define GTE_C_ACOS_DEG2_C2 +4.6887774236182234e-02 +#define GTE_C_ACOS_DEG2_MAX_ERROR 9.0311602490029258e-4 + +#define GTE_C_ACOS_DEG3_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG3_C1 -2.1253291899190285e-01 +#define GTE_C_ACOS_DEG3_C2 +7.4773789639484223e-02 +#define GTE_C_ACOS_DEG3_C3 -1.8823635069382449e-02 +#define GTE_C_ACOS_DEG3_MAX_ERROR 9.3066396954288172e-5 + +#define GTE_C_ACOS_DEG4_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG4_C1 -2.1422258835275865e-01 +#define GTE_C_ACOS_DEG4_C2 +8.4936675142844198e-02 +#define GTE_C_ACOS_DEG4_C3 -3.5991475120957794e-02 +#define GTE_C_ACOS_DEG4_C4 +8.6946239090712751e-03 +#define GTE_C_ACOS_DEG4_MAX_ERROR 1.0930595804481413e-5 + +#define GTE_C_ACOS_DEG5_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG5_C1 -2.1453292139805524e-01 +#define GTE_C_ACOS_DEG5_C2 +8.7973089282889383e-02 +#define GTE_C_ACOS_DEG5_C3 -4.5130266382166440e-02 +#define GTE_C_ACOS_DEG5_C4 +1.9467466687281387e-02 +#define GTE_C_ACOS_DEG5_C5 -4.3601326117634898e-03 +#define GTE_C_ACOS_DEG5_MAX_ERROR 1.3861070257241426-6 + +#define GTE_C_ACOS_DEG6_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG6_C1 -2.1458939285677325e-01 +#define GTE_C_ACOS_DEG6_C2 +8.8784960563641491e-02 +#define GTE_C_ACOS_DEG6_C3 -4.8887131453156485e-02 +#define GTE_C_ACOS_DEG6_C4 +2.7011519960012720e-02 +#define GTE_C_ACOS_DEG6_C5 -1.1210537323478320e-02 +#define GTE_C_ACOS_DEG6_C6 +2.3078166879102469e-03 +#define GTE_C_ACOS_DEG6_MAX_ERROR 1.8491291330427484e-7 + +#define GTE_C_ACOS_DEG7_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG7_C1 -2.1459960076929829e-01 +#define GTE_C_ACOS_DEG7_C2 +8.8986946573346160e-02 +#define GTE_C_ACOS_DEG7_C3 -5.0207843052845647e-02 +#define GTE_C_ACOS_DEG7_C4 +3.0961594977611639e-02 +#define GTE_C_ACOS_DEG7_C5 -1.7162031184398074e-02 +#define GTE_C_ACOS_DEG7_C6 +6.7072304676685235e-03 +#define GTE_C_ACOS_DEG7_C7 -1.2690614339589956e-03 +#define GTE_C_ACOS_DEG7_MAX_ERROR 2.5574620927948377e-8 + +#define GTE_C_ACOS_DEG8_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG8_C1 -2.1460143648688035e-01 +#define GTE_C_ACOS_DEG8_C2 +8.9034700107934128e-02 +#define GTE_C_ACOS_DEG8_C3 -5.0625279962389413e-02 +#define GTE_C_ACOS_DEG8_C4 +3.2683762943179318e-02 +#define GTE_C_ACOS_DEG8_C5 -2.0949278766238422e-02 +#define GTE_C_ACOS_DEG8_C6 +1.1272900916992512e-02 +#define GTE_C_ACOS_DEG8_C7 -4.1160981058965262e-03 +#define GTE_C_ACOS_DEG8_C8 +7.1796493341480527e-04 +#define GTE_C_ACOS_DEG8_MAX_ERROR 3.6340015129032732e-9 + +// Constants for minimax polynomial approximations to atan(x). +// The algorithm minimizes the maximum absolute error on [-1,1]. +#define GTE_C_ATAN_DEG3_C0 +1.0 +#define GTE_C_ATAN_DEG3_C1 -2.1460183660255172e-01 +#define GTE_C_ATAN_DEG3_MAX_ERROR 1.5970326392614240e-2 + +#define GTE_C_ATAN_DEG5_C0 +1.0 +#define GTE_C_ATAN_DEG5_C1 -3.0189478312144946e-01 +#define GTE_C_ATAN_DEG5_C2 +8.7292946518897740e-02 +#define GTE_C_ATAN_DEG5_MAX_ERROR 1.3509832247372636e-3 + +#define GTE_C_ATAN_DEG7_C0 +1.0 +#define GTE_C_ATAN_DEG7_C1 -3.2570157599356531e-01 +#define GTE_C_ATAN_DEG7_C2 +1.5342994884206673e-01 +#define GTE_C_ATAN_DEG7_C3 -4.2330209451053591e-02 +#define GTE_C_ATAN_DEG7_MAX_ERROR 1.5051227215514412e-4 + +#define GTE_C_ATAN_DEG9_C0 +1.0 +#define GTE_C_ATAN_DEG9_C1 -3.3157878236439586e-01 +#define GTE_C_ATAN_DEG9_C2 +1.8383034738018011e-01 +#define GTE_C_ATAN_DEG9_C3 -8.9253037587244677e-02 +#define GTE_C_ATAN_DEG9_C4 +2.2399635968909593e-02 +#define GTE_C_ATAN_DEG9_MAX_ERROR 1.8921598624582064e-5 + +#define GTE_C_ATAN_DEG11_C0 +1.0 +#define GTE_C_ATAN_DEG11_C1 -3.3294527685374087e-01 +#define GTE_C_ATAN_DEG11_C2 +1.9498657165383548e-01 +#define GTE_C_ATAN_DEG11_C3 -1.1921576270475498e-01 +#define GTE_C_ATAN_DEG11_C4 +5.5063351366968050e-02 +#define GTE_C_ATAN_DEG11_C5 -1.2490720064867844e-02 +#define GTE_C_ATAN_DEG11_MAX_ERROR 2.5477724974187765e-6 + +#define GTE_C_ATAN_DEG13_C0 +1.0 +#define GTE_C_ATAN_DEG13_C1 -3.3324998579202170e-01 +#define GTE_C_ATAN_DEG13_C2 +1.9856563505717162e-01 +#define GTE_C_ATAN_DEG13_C3 -1.3374657325451267e-01 +#define GTE_C_ATAN_DEG13_C4 +8.1675882859940430e-02 +#define GTE_C_ATAN_DEG13_C5 -3.5059680836411644e-02 +#define GTE_C_ATAN_DEG13_C6 +7.2128853633444123e-03 +#define GTE_C_ATAN_DEG13_MAX_ERROR 3.5859104691865484e-7 + +// Constants for minimax polynomial approximations to exp2(x) = 2^x. +// The algorithm minimizes the maximum absolute error on [0,1]. +#define GTE_C_EXP2_DEG1_C0 1.0 +#define GTE_C_EXP2_DEG1_C1 1.0 +#define GTE_C_EXP2_DEG1_MAX_ERROR 8.6071332055934313e-2 + +#define GTE_C_EXP2_DEG2_C0 1.0 +#define GTE_C_EXP2_DEG2_C1 6.5571332605741528e-01 +#define GTE_C_EXP2_DEG2_C2 3.4428667394258472e-01 +#define GTE_C_EXP2_DEG2_MAX_ERROR 3.8132476831060358e-3 + +#define GTE_C_EXP2_DEG3_C0 1.0 +#define GTE_C_EXP2_DEG3_C1 6.9589012084456225e-01 +#define GTE_C_EXP2_DEG3_C2 2.2486494900110188e-01 +#define GTE_C_EXP2_DEG3_C3 7.9244930154334980e-02 +#define GTE_C_EXP2_DEG3_MAX_ERROR 1.4694877755186408e-4 + +#define GTE_C_EXP2_DEG4_C0 1.0 +#define GTE_C_EXP2_DEG4_C1 6.9300392358459195e-01 +#define GTE_C_EXP2_DEG4_C2 2.4154981722455560e-01 +#define GTE_C_EXP2_DEG4_C3 5.1744260331489045e-02 +#define GTE_C_EXP2_DEG4_C4 1.3701998859367848e-02 +#define GTE_C_EXP2_DEG4_MAX_ERROR 4.7617792624521371e-6 + +#define GTE_C_EXP2_DEG5_C0 1.0 +#define GTE_C_EXP2_DEG5_C1 6.9315298010274962e-01 +#define GTE_C_EXP2_DEG5_C2 2.4014712313022102e-01 +#define GTE_C_EXP2_DEG5_C3 5.5855296413199085e-02 +#define GTE_C_EXP2_DEG5_C4 8.9477503096873079e-03 +#define GTE_C_EXP2_DEG5_C5 1.8968500441332026e-03 +#define GTE_C_EXP2_DEG5_MAX_ERROR 1.3162098333463490e-7 + +#define GTE_C_EXP2_DEG6_C0 1.0 +#define GTE_C_EXP2_DEG6_C1 6.9314698914837525e-01 +#define GTE_C_EXP2_DEG6_C2 2.4023013440952923e-01 +#define GTE_C_EXP2_DEG6_C3 5.5481276898206033e-02 +#define GTE_C_EXP2_DEG6_C4 9.6838443037086108e-03 +#define GTE_C_EXP2_DEG6_C5 1.2388324048515642e-03 +#define GTE_C_EXP2_DEG6_C6 2.1892283501756538e-04 +#define GTE_C_EXP2_DEG6_MAX_ERROR 3.1589168225654163e-9 + +#define GTE_C_EXP2_DEG7_C0 1.0 +#define GTE_C_EXP2_DEG7_C1 6.9314718588750690e-01 +#define GTE_C_EXP2_DEG7_C2 2.4022637363165700e-01 +#define GTE_C_EXP2_DEG7_C3 5.5505235570535660e-02 +#define GTE_C_EXP2_DEG7_C4 9.6136265387940512e-03 +#define GTE_C_EXP2_DEG7_C5 1.3429234504656051e-03 +#define GTE_C_EXP2_DEG7_C6 1.4299202757683815e-04 +#define GTE_C_EXP2_DEG7_C7 2.1662892777385423e-05 +#define GTE_C_EXP2_DEG7_MAX_ERROR 6.6864513925679603e-11 + +// Constants for minimax polynomial approximations to log2(x). +// The algorithm minimizes the maximum absolute error on [1,2]. +// The polynomials all have constant term zero. +#define GTE_C_LOG2_DEG1_C1 +1.0 +#define GTE_C_LOG2_DEG1_MAX_ERROR 8.6071332055934202e-2 + +#define GTE_C_LOG2_DEG2_C1 +1.3465553856377803 +#define GTE_C_LOG2_DEG2_C2 -3.4655538563778032e-01 +#define GTE_C_LOG2_DEG2_MAX_ERROR 7.6362868906658110e-3 + +#define GTE_C_LOG2_DEG3_C1 +1.4228653756681227 +#define GTE_C_LOG2_DEG3_C2 -5.8208556916449616e-01 +#define GTE_C_LOG2_DEG3_C3 +1.5922019349637218e-01 +#define GTE_C_LOG2_DEG3_MAX_ERROR 8.7902902652883808e-4 + +#define GTE_C_LOG2_DEG4_C1 +1.4387257478171547 +#define GTE_C_LOG2_DEG4_C2 -6.7778401359918661e-01 +#define GTE_C_LOG2_DEG4_C3 +3.2118898377713379e-01 +#define GTE_C_LOG2_DEG4_C4 -8.2130717995088531e-02 +#define GTE_C_LOG2_DEG4_MAX_ERROR 1.1318551355360418e-4 + +#define GTE_C_LOG2_DEG5_C1 +1.4419170408633741 +#define GTE_C_LOG2_DEG5_C2 -7.0909645927612530e-01 +#define GTE_C_LOG2_DEG5_C3 +4.1560609399164150e-01 +#define GTE_C_LOG2_DEG5_C4 -1.9357573729558908e-01 +#define GTE_C_LOG2_DEG5_C5 +4.5149061716699634e-02 +#define GTE_C_LOG2_DEG5_MAX_ERROR 1.5521274478735858e-5 + +#define GTE_C_LOG2_DEG6_C1 +1.4425449435950917 +#define GTE_C_LOG2_DEG6_C2 -7.1814525675038965e-01 +#define GTE_C_LOG2_DEG6_C3 +4.5754919692564044e-01 +#define GTE_C_LOG2_DEG6_C4 -2.7790534462849337e-01 +#define GTE_C_LOG2_DEG6_C5 +1.2179791068763279e-01 +#define GTE_C_LOG2_DEG6_C6 -2.5841449829670182e-02 +#define GTE_C_LOG2_DEG6_MAX_ERROR 2.2162051216689793e-6 + +#define GTE_C_LOG2_DEG7_C1 +1.4426664401536078 +#define GTE_C_LOG2_DEG7_C2 -7.2055423726162360e-01 +#define GTE_C_LOG2_DEG7_C3 +4.7332419162501083e-01 +#define GTE_C_LOG2_DEG7_C4 -3.2514018752954144e-01 +#define GTE_C_LOG2_DEG7_C5 +1.9302965529095673e-01 +#define GTE_C_LOG2_DEG7_C6 -7.8534970641157997e-02 +#define GTE_C_LOG2_DEG7_C7 +1.5209108363023915e-02 +#define GTE_C_LOG2_DEG7_MAX_ERROR 3.2546531700261561e-7 + +#define GTE_C_LOG2_DEG8_C1 +1.4426896453621882 +#define GTE_C_LOG2_DEG8_C2 -7.2115893912535967e-01 +#define GTE_C_LOG2_DEG8_C3 +4.7861716616785088e-01 +#define GTE_C_LOG2_DEG8_C4 -3.4699935395019565e-01 +#define GTE_C_LOG2_DEG8_C5 +2.4114048765477492e-01 +#define GTE_C_LOG2_DEG8_C6 -1.3657398692885181e-01 +#define GTE_C_LOG2_DEG8_C7 +5.1421382871922106e-02 +#define GTE_C_LOG2_DEG8_C8 -9.1364020499895560e-03 +#define GTE_C_LOG2_DEG8_MAX_ERROR 4.8796219218050219e-8 + +// These functions are convenient for some applications. The classes +// BSNumber, BSRational and IEEEBinary16 have implementations that +// (for now) use typecasting to call the 'float' or 'double' versions. +namespace gte +{ + inline float atandivpi(float x) + { + return std::atan(x) * (float)GTE_C_INV_PI; + } + + inline float atan2divpi(float y, float x) + { + return std::atan2(y, x) * (float)GTE_C_INV_PI; + } + + inline float clamp(float x, float xmin, float xmax) + { + return (x <= xmin ? xmin : (x >= xmax ? xmax : x)); + } + + inline float cospi(float x) + { + return std::cos(x * (float)GTE_C_PI); + } + + inline float exp10(float x) + { + return std::exp(x * (float)GTE_C_LN_10); + } + + inline float invsqrt(float x) + { + return 1.0f / std::sqrt(x); + } + + inline int isign(float x) + { + return (x > 0.0f ? 1 : (x < 0.0f ? -1 : 0)); + } + + inline float saturate(float x) + { + return (x <= 0.0f ? 0.0f : (x >= 1.0f ? 1.0f : x)); + } + + inline float sign(float x) + { + return (x > 0.0f ? 1.0f : (x < 0.0f ? -1.0f : 0.0f)); + } + + inline float sinpi(float x) + { + return std::sin(x * (float)GTE_C_PI); + } + + inline float sqr(float x) + { + return x * x; + } + + + inline double atandivpi(double x) + { + return std::atan(x) * GTE_C_INV_PI; + } + + inline double atan2divpi(double y, double x) + { + return std::atan2(y, x) * GTE_C_INV_PI; + } + + inline double clamp(double x, double xmin, double xmax) + { + return (x <= xmin ? xmin : (x >= xmax ? xmax : x)); + } + + inline double cospi(double x) + { + return std::cos(x * GTE_C_PI); + } + + inline double exp10(double x) + { + return std::exp(x * GTE_C_LN_10); + } + + inline double invsqrt(double x) + { + return 1.0 / std::sqrt(x); + } + + inline double sign(double x) + { + return (x > 0.0 ? 1.0 : (x < 0.0 ? -1.0 : 0.0f)); + } + + inline int isign(double x) + { + return (x > 0.0 ? 1 : (x < 0.0 ? -1 : 0)); + } + + inline double saturate(double x) + { + return (x <= 0.0 ? 0.0 : (x >= 1.0 ? 1.0 : x)); + } + + inline double sinpi(double x) + { + return std::sin(x * GTE_C_PI); + } + + inline double sqr(double x) + { + return x * x; + } +} + +// Type traits to support std::enable_if conditional compilation for +// numerical computations. +namespace gte +{ + // The trait is_arbitrary_precision for type T of float, double or + // long double generates is_arbitrary_precision::value of false. The + // implementations for arbitrary-precision arithmetic are found in + // GteArbitraryPrecision.h. + template + struct is_arbitrary_precision_internal : std::false_type {}; + + template + struct is_arbitrary_precision : is_arbitrary_precision_internal>::type {}; + + // The trait has_division_operator for type T of float, double or + // long double generates has_division_operator::value of true. The + // implementations for arbitrary-precision arithmetic are found in + // GteArbitraryPrecision.h. + template + struct has_division_operator_internal : std::false_type {}; + + template + struct has_division_operator : has_division_operator_internal>::type {}; + + template <> + struct has_division_operator_internal : std::true_type {}; + + template <> + struct has_division_operator_internal : std::true_type {}; + + template <> + struct has_division_operator_internal : std::true_type {}; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix.h new file mode 100644 index 000000000000..c28c4cf958da --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix.h @@ -0,0 +1,957 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class Matrix +{ +public: + // The table is initialized to zero. + Matrix(); + + // The table is fully initialized by the inputs. The 'values' must be + // specified in row-major order, regardless of the active storage scheme + // (GTE_USE_ROW_MAJOR or GTE_USE_COL_MAJOR). + Matrix(std::array const& values); + + // At most NumRows*NumCols are copied from the initializer list, setting + // any remaining elements to zero. The 'values' must be specified in + // row-major order, regardless of the active storage scheme + // (GTE_USE_ROW_MAJOR or GTE_USE_COL_MAJOR). Create the zero matrix using + // the syntax + // Matrix zero{(Real)0}; + // WARNING: The C++ 11 specification states that + // Matrix zero{}; + // will lead to a call of the default constructor, not the initializer + // constructor! + Matrix(std::initializer_list values); + + // For 0 <= r < NumRows and 0 <= c < NumCols, element (r,c) is 1 and all + // others are 0. If either of r or c is invalid, the zero matrix is + // created. This is a convenience for creating the standard Euclidean + // basis matrices; see also MakeUnit(int,int) and Unit(int,int). + Matrix(int r, int c); + + // The copy constructor, destructor, and assignment operator are generated + // by the compiler. + + // Member access for which the storage representation is transparent. The + // matrix entry in row r and column c is A(r,c). The first operator() + // returns a const reference rather than a Real value. This supports + // writing via standard file operations that require a const pointer to + // data. + inline Real const& operator()(int r, int c) const; + inline Real& operator()(int r, int c); + + // Member access by rows or by columns. + void SetRow(int r, Vector const& vec); + void SetCol(int c, Vector const& vec); + Vector GetRow(int r) const; + Vector GetCol(int c) const; + + // Member access by 1-dimensional index. NOTE: These accessors are + // useful for the manipulation of matrix entries when it does not + // matter whether storage is row-major or column-major. Do not use + // constructs such as M[c+NumCols*r] or M[r+NumRows*c] that expose the + // storage convention. + inline Real const& operator[](int i) const; + inline Real& operator[](int i); + + // Comparisons for sorted containers and geometric ordering. + inline bool operator==(Matrix const& mat) const; + inline bool operator!=(Matrix const& mat) const; + inline bool operator< (Matrix const& mat) const; + inline bool operator<=(Matrix const& mat) const; + inline bool operator> (Matrix const& mat) const; + inline bool operator>=(Matrix const& mat) const; + + // Special matrices. + void MakeZero(); // All components are 0. + void MakeUnit(int r, int c); // Component (r,c) is 1, all others zero. + void MakeIdentity(); // Diagonal entries 1, others 0, even when nonsquare + static Matrix Zero(); + static Matrix Unit(int r, int c); + static Matrix Identity(); + +protected: + class Table + { + public: + // Storage-order-independent element access as 2D array. + inline Real const& operator()(int r, int c) const; + inline Real& operator()(int r, int c); + + // Element access as 1D array. Use this internally only when + // the 2D storage order is not relevant. + inline Real const& operator[](int i) const; + inline Real& operator[](int i); + +#if defined(GTE_USE_ROW_MAJOR) + std::array,NumRows> mStorage; +#else + std::array,NumCols> mStorage; +#endif + }; + + Table mTable; +}; + +// Unary operations. +template +Matrix +operator+(Matrix const& M); + +template +Matrix +operator-(Matrix const& M); + +// Linear-algebraic operations. +template +Matrix +operator+( + Matrix const& M0, + Matrix const& M1); + +template +Matrix +operator-( + Matrix const& M0, + Matrix const& M1); + +template +Matrix +operator*(Matrix const& M, Real scalar); + +template +Matrix +operator*(Real scalar, Matrix const& M); + +template +Matrix +operator/(Matrix const& M, Real scalar); + +template +Matrix& +operator+=( + Matrix& M0, + Matrix const& M1); + +template +Matrix& +operator-=( + Matrix& M0, + Matrix const& M1); + +template +Matrix& +operator*=(Matrix& M, Real scalar); + +template +Matrix& +operator/=(Matrix& M, Real scalar); + +// Geometric operations. +template +Real L1Norm(Matrix const& M); + +template +Real L2Norm(Matrix const& M); + +template +Real LInfinityNorm(Matrix const& M); + +template +Matrix Inverse(Matrix const& M, + bool* reportInvertibility = nullptr); + +template +Real Determinant(Matrix const& M); + +// M^T +template +Matrix +Transpose(Matrix const& M); + +// M*V +template +Vector +operator*( + Matrix const& M, + Vector const& V); + +// V^T*M +template +Vector +operator*( + Vector const& V, + Matrix const& M); + +// A*B +template +Matrix +operator*( + Matrix const& A, + Matrix const& B); + +template +Matrix +MultiplyAB( + Matrix const& A, + Matrix const& B); + +// A*B^T +template +Matrix +MultiplyABT( + Matrix const& A, + Matrix const& B); + +// A^T*B +template +Matrix +MultiplyATB( + Matrix const& A, + Matrix const& B); + +// A^T*B^T +template +Matrix +MultiplyATBT( + Matrix const& A, + Matrix const& B); + +// M*D, D is diagonal NumCols-by-NumCols +template +Matrix +MultiplyMD( + Matrix const& M, + Vector const& D); + +// D*M, D is diagonal NumRows-by-NumRows +template +Matrix +MultiplyDM( + Vector const& D, + Matrix const& M); + +// U*V^T, U is NumRows-by-1, V is Num-Cols-by-1, result is NumRows-by-NumCols. +template +Matrix +OuterProduct(Vector const& U, Vector const& V); + +// Initialization to a diagonal matrix whose diagonal entries are the +// components of D. +template +void MakeDiagonal(Vector const& D, Matrix& M); + +// Create an (N+1)-by-(N+1) matrix H by setting the upper N-by-N block to the +// input N-by-N matrix and all other entries to 0 except for the last row +// and last column entry which is set to 1. +template +Matrix HLift(Matrix const& M); + +// Extract the upper (N-1)-by-(N-1) block of the input N-by-N matrix. +template +Matrix HProject(Matrix const& M); + + +template +Matrix::Matrix() +{ + MakeZero(); +} + +template +Matrix::Matrix( + std::array const& values) +{ + for (int r = 0, i = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c, ++i) + { + mTable(r, c) = values[i]; + } + } +} + +template +Matrix::Matrix(std::initializer_list values) +{ + int const numValues = static_cast(values.size()); + auto iter = values.begin(); + int r, c, i; + for (r = 0, i = 0; r < NumRows; ++r) + { + for (c = 0; c < NumCols; ++c, ++i) + { + if (i < numValues) + { + mTable(r, c) = *iter++; + } + else + { + break; + } + } + + if (c < NumCols) + { + // Fill in the remaining columns of the current row with zeros. + for (/**/; c < NumCols; ++c) + { + mTable(r, c) = (Real)0; + } + ++r; + break; + } + } + + if (r < NumRows) + { + // Fill in the remain rows with zeros. + for (/**/; r < NumRows; ++r) + { + for (c = 0; c < NumCols; ++c) + { + mTable(r, c) = (Real)0; + } + } + } +} + +template +Matrix::Matrix(int r, int c) +{ + MakeUnit(r, c); +} + +template inline +Real const& Matrix::operator()(int r, int c) const +{ + return mTable(r, c); +} + +template inline +Real& Matrix::operator()(int r, int c) +{ + return mTable(r, c); +} + +template +void Matrix::SetRow(int r, + Vector const& vec) +{ + for (int c = 0; c < NumCols; ++c) + { + mTable(r, c) = vec[c]; + } +} + +template +void Matrix::SetCol(int c, + Vector const& vec) +{ + for (int r = 0; r < NumRows; ++r) + { + mTable(r, c) = vec[r]; + } +} + +template +Vector Matrix::GetRow(int r) const +{ + Vector vec; + for (int c = 0; c < NumCols; ++c) + { + vec[c] = mTable(r, c); + } + return vec; +} + +template +Vector Matrix::GetCol(int c) const +{ + Vector vec; + for (int r = 0; r < NumRows; ++r) + { + vec[r] = mTable(r, c); + } + return vec; +} + +template inline +Real const& Matrix::operator[](int i) const +{ + return mTable[i]; +} + +template inline +Real& Matrix::operator[](int i) +{ + return mTable[i]; +} + +template inline +bool Matrix::operator==(Matrix const& mat) const +{ + return mTable.mStorage == mat.mTable.mStorage; +} + +template inline +bool Matrix::operator!=(Matrix const& mat) const +{ + return mTable.mStorage != mat.mTable.mStorage; +} + +template inline +bool Matrix::operator<(Matrix const& mat) const +{ + return mTable.mStorage < mat.mTable.mStorage; +} + +template inline +bool Matrix::operator<=(Matrix const& mat) const +{ + return mTable.mStorage <= mat.mTable.mStorage; +} + +template inline +bool Matrix::operator>(Matrix const& mat) const +{ + return mTable.mStorage > mat.mTable.mStorage; +} + +template inline +bool Matrix::operator>=(Matrix const& mat) const +{ + return mTable.mStorage >= mat.mTable.mStorage; +} + +template +void Matrix::MakeZero() +{ + Real const zero = (Real)0; + for (int i = 0; i < NumRows * NumCols; ++i) + { + mTable[i] = zero; + } +} + +template +void Matrix::MakeUnit(int r, int c) +{ + MakeZero(); + if (0 <= r && r < NumRows && 0 <= c && c < NumCols) + { + mTable(r, c) = (Real)1; + } +} + +template +void Matrix::MakeIdentity() +{ + MakeZero(); + int const numDiagonal = (NumRows <= NumCols ? NumRows : NumCols); + for (int i = 0; i < numDiagonal; ++i) + { + mTable(i, i) = (Real)1; + } +} + +template +Matrix Matrix::Zero() +{ + Matrix M; + M.MakeZero(); + return M; +} + +template +Matrix Matrix::Unit(int r, + int c) +{ + Matrix M; + M.MakeUnit(r, c); + return M; +} + +template +Matrix Matrix::Identity() +{ + Matrix M; + M.MakeIdentity(); + return M; +} + + + +template +Matrix +operator+(Matrix const& M) +{ + return M; +} + +template +Matrix +operator-(Matrix const& M) +{ + Matrix result; + for (int i = 0; i < NumRows*NumCols; ++i) + { + result[i] = -M[i]; + } + return result; +} + +template +Matrix +operator+( + Matrix const& M0, + Matrix const& M1) +{ + Matrix result = M0; + return result += M1; +} + +template +Matrix +operator-( + Matrix const& M0, + Matrix const& M1) +{ + Matrix result = M0; + return result -= M1; +} + +template +Matrix +operator*(Matrix const& M, Real scalar) +{ + Matrix result = M; + return result *= scalar; +} + +template +Matrix +operator*(Real scalar, Matrix const& M) +{ + Matrix result = M; + return result *= scalar; +} + +template +Matrix +operator/(Matrix const& M, Real scalar) +{ + Matrix result = M; + return result /= scalar; +} + +template +Matrix& +operator+=( + Matrix& M0, + Matrix const& M1) +{ + for (int i = 0; i < NumRows*NumCols; ++i) + { + M0[i] += M1[i]; + } + return M0; +} + +template +Matrix& +operator-=( + Matrix& M0, + Matrix const& M1) +{ + for (int i = 0; i < NumRows*NumCols; ++i) + { + M0[i] -= M1[i]; + } + return M0; +} + +template +Matrix& +operator*=(Matrix& M, Real scalar) +{ + for (int i = 0; i < NumRows*NumCols; ++i) + { + M[i] *= scalar; + } + return M; +} + +template +Matrix& +operator/=(Matrix& M, Real scalar) +{ + if (scalar != (Real)0) + { + Real invScalar = ((Real)1) / scalar; + for (int i = 0; i < NumRows*NumCols; ++i) + { + M[i] *= invScalar; + } + } + else + { + for (int i = 0; i < NumRows*NumCols; ++i) + { + M[i] = (Real)0; + } + } + return M; +} + +template +Real L1Norm(Matrix const& M) +{ + Real sum = std::abs(M[0]); + for (int i = 1; i < NumRows*NumCols; ++i) + { + sum += std::abs(M[i]); + } + return sum; +} + +template +Real L2Norm(Matrix const& M) +{ + Real sum = M[0] * M[0]; + for (int i = 1; i < NumRows*NumCols; ++i) + { + sum += M[i] * M[i]; + } + return std::sqrt(sum); +} + +template +Real LInfinityNorm(Matrix const& M) +{ + Real maxAbsElement = M[0]; + for (int i = 1; i < NumRows*NumCols; ++i) + { + Real absElement = std::abs(M[i]); + if (absElement > maxAbsElement) + { + maxAbsElement = absElement; + } + } + return maxAbsElement; +} + +template +Matrix Inverse(Matrix const& M, + bool* reportInvertibility) +{ + Matrix invM; + Real determinant; + bool invertible = GaussianElimination()(N, &M[0], &invM[0], + determinant, nullptr, nullptr, nullptr, 0, nullptr); + if (reportInvertibility) + { + *reportInvertibility = invertible; + } + return invM; +} + +template +Real Determinant(Matrix const& M) +{ + Real determinant; + GaussianElimination()(N, &M[0], nullptr, determinant, nullptr, + nullptr, nullptr, 0, nullptr); + return determinant; +} + +template +Matrix +Transpose(Matrix const& M) +{ + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(c, r) = M(r, c); + } + } + return result; +} + +template +Vector +operator*( + Matrix const& M, + Vector const& V) +{ + Vector result; + for (int r = 0; r < NumRows; ++r) + { + result[r] = (Real)0; + for (int c = 0; c < NumCols; ++c) + { + result[r] += M(r, c) * V[c]; + } + } + return result; +} + +template +Vector operator*(Vector const& V, + Matrix const& M) +{ + Vector result; + for (int c = 0; c < NumCols; ++c) + { + result[c] = (Real)0; + for (int r = 0; r < NumRows; ++r) + { + result[c] += V[r] * M(r, c); + } + } + return result; +} + +template +Matrix +operator*( + Matrix const& A, + Matrix const& B) +{ + return MultiplyAB(A, B); +} + +template +Matrix +MultiplyAB( + Matrix const& A, + Matrix const& B) +{ + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < NumCommon; ++i) + { + result(r, c) += A(r, i) * B(i, c); + } + } + } + return result; +} + +template +Matrix +MultiplyABT( + Matrix const& A, + Matrix const& B) +{ + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < NumCommon; ++i) + { + result(r, c) += A(r, i) * B(c, i); + } + } + } + return result; +} + +template +Matrix +MultiplyATB( + Matrix const& A, + Matrix const& B) +{ + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < NumCommon; ++i) + { + result(r, c) += A(i, r) * B(i, c); + } + } + } + return result; +} + +template +Matrix +MultiplyATBT( + Matrix const& A, + Matrix const& B) +{ + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < NumCommon; ++i) + { + result(r, c) += A(i, r) * B(c, i); + } + } + } + return result; +} + +template +Matrix +MultiplyMD( + Matrix const& M, + Vector const& D) +{ + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = M(r, c) * D[c]; + } + } + return result; +} + +template +Matrix +MultiplyDM( + Vector const& D, + Matrix const& M) +{ + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = D[r] * M(r, c); + } + } + return result; +} + +template +Matrix +OuterProduct(Vector const& U, Vector const& V) +{ + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = U[r] * V[c]; + } + } + return result; +} + +template +void MakeDiagonal(Vector const& D, Matrix& M) +{ + for (int i = 0; i < N*N; ++i) + { + M[i] = (Real)0; + } + + for (int i = 0; i < N; ++i) + { + M(i, i) = D[i]; + } +} + +template +Matrix HLift(Matrix const& M) +{ + Matrix result; + result.MakeIdentity(); + for (int r = 0; r < N; ++r) + { + for (int c = 0; c < N; ++c) + { + result(r, c) = M(r, c); + } + } + return result; +} + +// Extract the upper (N-1)-by-(N-1) block of the input N-by-N matrix. +template +Matrix HProject(Matrix const& M) +{ + static_assert(N >= 2, "Invalid matrix dimension."); + Matrix result; + for (int r = 0; r < N - 1; ++r) + { + for (int c = 0; c < N - 1; ++c) + { + result(r, c) = M(r, c); + } + } + return result; +} + + +// Matrix::Table + +template inline +Real const& Matrix::Table::operator()(int r, int c) + const +{ +#if defined(GTE_USE_ROW_MAJOR) + return mStorage[r][c]; +#else + return mStorage[c][r]; +#endif +} + +template inline +Real& Matrix::Table::operator()(int r, int c) +{ +#if defined(GTE_USE_ROW_MAJOR) + return mStorage[r][c]; +#else + return mStorage[c][r]; +#endif +} + +template inline +Real const& Matrix::Table::operator[](int i) const +{ + Real const* elements = &mStorage[0][0]; + return elements[i]; +} + +template inline +Real& Matrix::Table::operator[](int i) +{ + Real* elements = &mStorage[0][0]; + return elements[i]; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix2x2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix2x2.h new file mode 100644 index 000000000000..0b66f0a6a41e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix2x2.h @@ -0,0 +1,132 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +// Template alias for convenience. +template +using Matrix2x2 = Matrix<2, 2, Real>; + +// Create a rotation matrix from an angle (in radians). The matrix is +// [GTE_USE_MAT_VEC] +// R(t) = {{c,-s},{s,c}} +// [GTE_USE_VEC_MAT] +// R(t) = {{c,s},{-s,c}} +// where c = cos(t), s = sin(t), and the inner-brace pairs are rows of the +// matrix. +template +void MakeRotation(Real angle, Matrix2x2& rotation); + +// Get the angle (radians) from a rotation matrix. The caller is +// responsible for ensuring the matrix is a rotation. +template +Real GetRotationAngle(Matrix2x2 const& rotation); + +// Geometric operations. +template +Matrix2x2 Inverse(Matrix2x2 const& M, + bool* reportInvertibility = nullptr); + +template +Matrix2x2 Adjoint(Matrix2x2 const& M); + +template +Real Determinant(Matrix2x2 const& M); + +template +Real Trace(Matrix2x2 const& M); + + +template +void MakeRotation(Real angle, Matrix2x2& rotation) +{ + Real cs = std::cos(angle); + Real sn = std::sin(angle); +#if defined(GTE_USE_MAT_VEC) + rotation(0, 0) = cs; + rotation(0, 1) = -sn; + rotation(1, 0) = sn; + rotation(1, 1) = cs; +#else + rotation(0, 0) = cs; + rotation(0, 1) = sn; + rotation(1, 0) = -sn; + rotation(1, 1) = cs; +#endif +} + +template +Real GetRotationAngle(Matrix2x2 const& rotation) +{ +#if defined(GTE_USE_MAT_VEC) + return std::atan2(rotation(1, 0), rotation(0, 0)); +#else + return std::atan2(rotation(0, 1), rotation(0, 0)); +#endif +} + +template +Matrix2x2 Inverse(Matrix2x2 const& M, bool* reportInvertibility) +{ + Matrix2x2 inverse; + bool invertible; + Real det = M(0, 0)*M(1, 1) - M(0, 1)*M(1, 0); + if (det != (Real)0) + { + Real invDet = ((Real)1) / det; + inverse = Matrix2x2 + { + M(1, 1)*invDet, -M(0, 1)*invDet, + -M(1, 0)*invDet, M(0, 0)*invDet + }; + invertible = true; + } + else + { + inverse.MakeZero(); + invertible = false; + } + + if (reportInvertibility) + { + *reportInvertibility = invertible; + } + return inverse; +} + +template +Matrix2x2 Adjoint(Matrix2x2 const& M) +{ + return Matrix2x2 + { + M(1, 1), -M(0, 1), + -M(1, 0), M(0, 0) + }; +} + +template +Real Determinant(Matrix2x2 const& M) +{ + Real det = M(0, 0)*M(1, 1) - M(0, 1)*M(1, 0); + return det; +} + +template +Real Trace(Matrix2x2 const& M) +{ + Real trace = M(0, 0) + M(1, 1); + return trace; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix3x3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix3x3.h new file mode 100644 index 000000000000..602a88e30ebb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix3x3.h @@ -0,0 +1,109 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +// Template alias for convenience. +template +using Matrix3x3 = Matrix<3, 3, Real>; + +// Geometric operations. +template +Matrix3x3 Inverse(Matrix3x3 const& M, + bool* reportInvertibility = nullptr); + +template +Matrix3x3 Adjoint(Matrix3x3 const& M); + +template +Real Determinant(Matrix3x3 const& M); + +template +Real Trace(Matrix3x3 const& M); + + +template +Matrix3x3 Inverse(Matrix3x3 const& M, bool* reportInvertibility) +{ + Matrix3x3 inverse; + bool invertible; + Real c00 = M(1, 1)*M(2, 2) - M(1, 2)*M(2, 1); + Real c10 = M(1, 2)*M(2, 0) - M(1, 0)*M(2, 2); + Real c20 = M(1, 0)*M(2, 1) - M(1, 1)*M(2, 0); + Real det = M(0, 0)*c00 + M(0, 1)*c10 + M(0, 2)*c20; + if (det != (Real)0) + { + Real invDet = ((Real)1) / det; + inverse = Matrix3x3 + { + c00*invDet, + (M(0, 2)*M(2, 1) - M(0, 1)*M(2, 2))*invDet, + (M(0, 1)*M(1, 2) - M(0, 2)*M(1, 1))*invDet, + c10*invDet, + (M(0, 0)*M(2, 2) - M(0, 2)*M(2, 0))*invDet, + (M(0, 2)*M(1, 0) - M(0, 0)*M(1, 2))*invDet, + c20*invDet, + (M(0, 1)*M(2, 0) - M(0, 0)*M(2, 1))*invDet, + (M(0, 0)*M(1, 1) - M(0, 1)*M(1, 0))*invDet + }; + invertible = true; + } + else + { + inverse.MakeZero(); + invertible = false; + } + + if (reportInvertibility) + { + *reportInvertibility = invertible; + } + return inverse; +} + +template +Matrix3x3 Adjoint(Matrix3x3 const& M) +{ + return Matrix3x3 + { + M(1, 1)*M(2, 2) - M(1, 2)*M(2, 1), + M(0, 2)*M(2, 1) - M(0, 1)*M(2, 2), + M(0, 1)*M(1, 2) - M(0, 2)*M(1, 1), + M(1, 2)*M(2, 0) - M(1, 0)*M(2, 2), + M(0, 0)*M(2, 2) - M(0, 2)*M(2, 0), + M(0, 2)*M(1, 0) - M(0, 0)*M(1, 2), + M(1, 0)*M(2, 1) - M(1, 1)*M(2, 0), + M(0, 1)*M(2, 0) - M(0, 0)*M(2, 1), + M(0, 0)*M(1, 1) - M(0, 1)*M(1, 0) + }; +} + +template +Real Determinant(Matrix3x3 const& M) +{ + Real c00 = M(1, 1)*M(2, 2) - M(1, 2)*M(2, 1); + Real c10 = M(1, 2)*M(2, 0) - M(1, 0)*M(2, 2); + Real c20 = M(1, 0)*M(2, 1) - M(1, 1)*M(2, 0); + Real det = M(0, 0)*c00 + M(0, 1)*c10 + M(0, 2)*c20; + return det; +} + +template +Real Trace(Matrix3x3 const& M) +{ + Real trace = M(0, 0) + M(1, 1) + M(2, 2); + return trace; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix4x4.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix4x4.h new file mode 100644 index 000000000000..e49cc4e32fe9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix4x4.h @@ -0,0 +1,349 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +// Template alias for convenience. +template +using Matrix4x4 = Matrix<4, 4, Real>; + +// Geometric operations. +template +Matrix4x4 Inverse(Matrix4x4 const& M, + bool* reportInvertibility = nullptr); + +template +Matrix4x4 Adjoint(Matrix4x4 const& M); + +template +Real Determinant(Matrix4x4 const& M); + +template +Real Trace(Matrix4x4 const& M); + +// Special matrices. In the comments, the matrices are shown using the +// GTE_USE_MAT_VEC multiplication convention. + +// The projection plane is Dot(N,X-P) = 0 where N is a 3-by-1 unit-length +// normal vector and P is a 3-by-1 point on the plane. The projection is +// oblique to the plane, in the direction of the 3-by-1 vector D. Necessarily +// Dot(N,D) is not zero for this projection to make sense. Given a 3-by-1 +// point U, compute the intersection of the line U+t*D with the plane to +// obtain t = -Dot(N,U-P)/Dot(N,D); then +// +// projection(U) = P + [I - D*N^T/Dot(N,D)]*(U-P) +// +// A 4-by-4 homogeneous transformation representing the projection is +// +// +- -+ +// M = | D*N^T - Dot(N,D)*I -Dot(N,P)D | +// | 0^T -Dot(N,D) | +// +- -+ +// +// where M applies to [U^T 1]^T by M*[U^T 1]^T. The matrix is chosen so +// that M[3][3] > 0 whenever Dot(N,D) < 0; the projection is onto the +// "positive side" of the plane. +template +Matrix4x4 MakeObliqueProjection(Vector4 const& origin, + Vector4 const& normal, Vector4 const& direction); + +// The perspective projection of a point onto a plane is +// +// +- -+ +// M = | Dot(N,E-P)*I - E*N^T -(Dot(N,E-P)*I - E*N^T)*E | +// | -N^t Dot(N,E) | +// +- -+ +// +// where E is the eye point, P is a point on the plane, and N is a +// unit-length plane normal. +template +Matrix4x4 MakePerspectiveProjection(Vector4 const& origin, + Vector4 const& normal, Vector4 const& eye); + +// The reflection of a point through a plane is +// +- -+ +// M = | I-2*N*N^T 2*Dot(N,P)*N | +// | 0^T 1 | +// +- -+ +// +// where P is a point on the plane and N is a unit-length plane normal. + +template +Matrix4x4 MakeReflection(Vector4 const& origin, + Vector4 const& normal); + + +template +Matrix4x4 Inverse(Matrix4x4 const& M, bool* reportInvertibility) +{ + Matrix4x4 inverse; + bool invertible; + Real a0 = M(0, 0) * M(1, 1) - M(0, 1) * M(1, 0); + Real a1 = M(0, 0) * M(1, 2) - M(0, 2) * M(1, 0); + Real a2 = M(0, 0) * M(1, 3) - M(0, 3) * M(1, 0); + Real a3 = M(0, 1) * M(1, 2) - M(0, 2) * M(1, 1); + Real a4 = M(0, 1) * M(1, 3) - M(0, 3) * M(1, 1); + Real a5 = M(0, 2) * M(1, 3) - M(0, 3) * M(1, 2); + Real b0 = M(2, 0) * M(3, 1) - M(2, 1) * M(3, 0); + Real b1 = M(2, 0) * M(3, 2) - M(2, 2) * M(3, 0); + Real b2 = M(2, 0) * M(3, 3) - M(2, 3) * M(3, 0); + Real b3 = M(2, 1) * M(3, 2) - M(2, 2) * M(3, 1); + Real b4 = M(2, 1) * M(3, 3) - M(2, 3) * M(3, 1); + Real b5 = M(2, 2) * M(3, 3) - M(2, 3) * M(3, 2); + Real det = a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0; + if (det != (Real)0) + { + Real invDet = ((Real)1) / det; + inverse = Matrix4x4 + { + (+M(1, 1) * b5 - M(1, 2) * b4 + M(1, 3) * b3) * invDet, + (-M(0, 1) * b5 + M(0, 2) * b4 - M(0, 3) * b3) * invDet, + (+M(3, 1) * a5 - M(3, 2) * a4 + M(3, 3) * a3) * invDet, + (-M(2, 1) * a5 + M(2, 2) * a4 - M(2, 3) * a3) * invDet, + (-M(1, 0) * b5 + M(1, 2) * b2 - M(1, 3) * b1) * invDet, + (+M(0, 0) * b5 - M(0, 2) * b2 + M(0, 3) * b1) * invDet, + (-M(3, 0) * a5 + M(3, 2) * a2 - M(3, 3) * a1) * invDet, + (+M(2, 0) * a5 - M(2, 2) * a2 + M(2, 3) * a1) * invDet, + (+M(1, 0) * b4 - M(1, 1) * b2 + M(1, 3) * b0) * invDet, + (-M(0, 0) * b4 + M(0, 1) * b2 - M(0, 3) * b0) * invDet, + (+M(3, 0) * a4 - M(3, 1) * a2 + M(3, 3) * a0) * invDet, + (-M(2, 0) * a4 + M(2, 1) * a2 - M(2, 3) * a0) * invDet, + (-M(1, 0) * b3 + M(1, 1) * b1 - M(1, 2) * b0) * invDet, + (+M(0, 0) * b3 - M(0, 1) * b1 + M(0, 2) * b0) * invDet, + (-M(3, 0) * a3 + M(3, 1) * a1 - M(3, 2) * a0) * invDet, + (+M(2, 0) * a3 - M(2, 1) * a1 + M(2, 2) * a0) * invDet + }; + invertible = true; + } + else + { + inverse.MakeZero(); + invertible = false; + } + + if (reportInvertibility) + { + *reportInvertibility = invertible; + } + return inverse; +} + +template +Matrix4x4 Adjoint(Matrix4x4 const& M) +{ + Real a0 = M(0, 0) * M(1, 1) - M(0, 1) * M(1, 0); + Real a1 = M(0, 0) * M(1, 2) - M(0, 2) * M(1, 0); + Real a2 = M(0, 0) * M(1, 3) - M(0, 3) * M(1, 0); + Real a3 = M(0, 1) * M(1, 2) - M(0, 2) * M(1, 1); + Real a4 = M(0, 1) * M(1, 3) - M(0, 3) * M(1, 1); + Real a5 = M(0, 2) * M(1, 3) - M(0, 3) * M(1, 2); + Real b0 = M(2, 0) * M(3, 1) - M(2, 1) * M(3, 0); + Real b1 = M(2, 0) * M(3, 2) - M(2, 2) * M(3, 0); + Real b2 = M(2, 0) * M(3, 3) - M(2, 3) * M(3, 0); + Real b3 = M(2, 1) * M(3, 2) - M(2, 2) * M(3, 1); + Real b4 = M(2, 1) * M(3, 3) - M(2, 3) * M(3, 1); + Real b5 = M(2, 2) * M(3, 3) - M(2, 3) * M(3, 2); + + return Matrix4x4 + { + +M(1, 1) * b5 - M(1, 2) * b4 + M(1, 3) * b3, + -M(0, 1) * b5 + M(0, 2) * b4 - M(0, 3) * b3, + +M(3, 1) * a5 - M(3, 2) * a4 + M(3, 3) * a3, + -M(2, 1) * a5 + M(2, 2) * a4 - M(2, 3) * a3, + -M(1, 0) * b5 + M(1, 2) * b2 - M(1, 3) * b1, + +M(0, 0) * b5 - M(0, 2) * b2 + M(0, 3) * b1, + -M(3, 0) * a5 + M(3, 2) * a2 - M(3, 3) * a1, + +M(2, 0) * a5 - M(2, 2) * a2 + M(2, 3) * a1, + +M(1, 0) * b4 - M(1, 1) * b2 + M(1, 3) * b0, + -M(0, 0) * b4 + M(0, 1) * b2 - M(0, 3) * b0, + +M(3, 0) * a4 - M(3, 1) * a2 + M(3, 3) * a0, + -M(2, 0) * a4 + M(2, 1) * a2 - M(2, 3) * a0, + -M(1, 0) * b3 + M(1, 1) * b1 - M(1, 2) * b0, + +M(0, 0) * b3 - M(0, 1) * b1 + M(0, 2) * b0, + -M(3, 0) * a3 + M(3, 1) * a1 - M(3, 2) * a0, + +M(2, 0) * a3 - M(2, 1) * a1 + M(2, 2) * a0 + }; +} + +template +Real Determinant(Matrix4x4 const& M) +{ + Real a0 = M(0, 0) * M(1, 1) - M(0, 1) * M(1, 0); + Real a1 = M(0, 0) * M(1, 2) - M(0, 2) * M(1, 0); + Real a2 = M(0, 0) * M(1, 3) - M(0, 3) * M(1, 0); + Real a3 = M(0, 1) * M(1, 2) - M(0, 2) * M(1, 1); + Real a4 = M(0, 1) * M(1, 3) - M(0, 3) * M(1, 1); + Real a5 = M(0, 2) * M(1, 3) - M(0, 3) * M(1, 2); + Real b0 = M(2, 0) * M(3, 1) - M(2, 1) * M(3, 0); + Real b1 = M(2, 0) * M(3, 2) - M(2, 2) * M(3, 0); + Real b2 = M(2, 0) * M(3, 3) - M(2, 3) * M(3, 0); + Real b3 = M(2, 1) * M(3, 2) - M(2, 2) * M(3, 1); + Real b4 = M(2, 1) * M(3, 3) - M(2, 3) * M(3, 1); + Real b5 = M(2, 2) * M(3, 3) - M(2, 3) * M(3, 2); + Real det = a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0; + return det; +} + +template +Real Trace(Matrix4x4 const& M) +{ + Real trace = M(0, 0) + M(1, 1) + M(2, 2) + M(3, 3); + return trace; +} + +template +Matrix4x4 MakeObliqueProjection(Vector4 const& origin, + Vector4 const& normal, Vector4 const& direction) +{ + Matrix4x4 M; + + Real const zero = (Real)0; + Real dotND = Dot(normal, direction); + Real dotNO = Dot(origin, normal); + +#if defined(GTE_USE_MAT_VEC) + M(0, 0) = direction[0] * normal[0] - dotND; + M(0, 1) = direction[0] * normal[1]; + M(0, 2) = direction[0] * normal[2]; + M(0, 3) = -dotNO * direction[0]; + M(1, 0) = direction[1] * normal[0]; + M(1, 1) = direction[1] * normal[1] - dotND; + M(1, 2) = direction[1] * normal[2]; + M(1, 3) = -dotNO * direction[1]; + M(2, 0) = direction[2] * normal[0]; + M(2, 1) = direction[2] * normal[1]; + M(2, 2) = direction[2] * normal[2] - dotND; + M(2, 3) = -dotNO * direction[2]; + M(3, 0) = zero; + M(3, 1) = zero; + M(3, 2) = zero; + M(3, 3) = -dotND; +#else + M(0, 0) = direction[0] * normal[0] - dotND; + M(1, 0) = direction[0] * normal[1]; + M(2, 0) = direction[0] * normal[2]; + M(3, 0) = -dotNO * direction[0]; + M(0, 1) = direction[1] * normal[0]; + M(1, 1) = direction[1] * normal[1] - dotND; + M(2, 1) = direction[1] * normal[2]; + M(3, 1) = -dotNO * direction[1]; + M(0, 2) = direction[2] * normal[0]; + M(1, 2) = direction[2] * normal[1]; + M(2, 2) = direction[2] * normal[2] - dotND; + M(3, 2) = -dotNO * direction[2]; + M(0, 2) = zero; + M(1, 3) = zero; + M(2, 3) = zero; + M(3, 3) = -dotND; +#endif + + return M; +} + +template +Matrix4x4 MakePerspectiveProjection(Vector4 const& origin, + Vector4 const& normal, Vector4 const& eye) +{ + Matrix4x4 M; + + Real dotND = Dot(normal, eye - origin); + +#if defined(GTE_USE_MAT_VEC) + M(0, 0) = dotND - eye[0] * normal[0]; + M(0, 1) = -eye[0] * normal[1]; + M(0, 2) = -eye[0] * normal[2]; + M(0, 3) = -(M(0, 0) * eye[0] + M(0, 1) * eye[1] + M(0, 2) * eye[2]); + M(1, 0) = -eye[1] * normal[0]; + M(1, 1) = dotND - eye[1] * normal[1]; + M(1, 2) = -eye[1] * normal[2]; + M(1, 3) = -(M(1, 0) * eye[0] + M(1, 1) * eye[1] + M(1, 2) * eye[2]); + M(2, 0) = -eye[2] * normal[0]; + M(2, 1) = -eye[2] * normal[1]; + M(2, 2) = dotND - eye[2] * normal[2]; + M(2, 3) = -(M(2, 0) * eye[0] + M(2, 1) * eye[1] + M(2, 2) * eye[2]); + M(3, 0) = -normal[0]; + M(3, 1) = -normal[1]; + M(3, 2) = -normal[2]; + M(3, 3) = Dot(eye, normal); +#else + M(0, 0) = dotND - eye[0] * normal[0]; + M(1, 0) = -eye[0] * normal[1]; + M(2, 0) = -eye[0] * normal[2]; + M(3, 0) = -(M(0, 0) * eye[0] + M(0, 1) * eye[1] + M(0, 2) * eye[2]); + M(0, 1) = -eye[1] * normal[0]; + M(1, 1) = dotND - eye[1] * normal[1]; + M(2, 1) = -eye[1] * normal[2]; + M(3, 1) = -(M(1, 0) * eye[0] + M(1, 1) * eye[1] + M(1, 2) * eye[2]); + M(0, 2) = -eye[2] * normal[0]; + M(1, 2) = -eye[2] * normal[1]; + M(2, 2) = dotND - eye[2] * normal[2]; + M(3, 2) = -(M(2, 0) * eye[0] + M(2, 1) * eye[1] + M(2, 2) * eye[2]); + M(0, 3) = -normal[0]; + M(1, 3) = -normal[1]; + M(2, 3) = -normal[2]; + M(3, 3) = Dot(eye, normal); +#endif + + return M; +} + +template +Matrix4x4 MakeReflection(Vector4 const& origin, + Vector4 const& normal) +{ + Matrix4x4 M; + + Real const zero = (Real)0, one = (Real)1, two = (Real)2; + Real twoDotNO = two * Dot(origin, normal); + +#if defined(GTE_USE_MAT_VEC) + M(0, 0) = one - two * normal[0] * normal[0]; + M(0, 1) = -two * normal[0] * normal[1]; + M(0, 2) = -two * normal[0] * normal[2]; + M(0, 3) = twoDotNO * normal[0]; + M(1, 0) = M(0, 1); + M(1, 1) = one - two * normal[1] * normal[1]; + M(1, 2) = -two * normal[1] * normal[2]; + M(1, 3) = twoDotNO * normal[1]; + M(2, 0) = M(0, 2); + M(2, 1) = M(1, 2); + M(2, 2) = one - two * normal[2] * normal[2]; + M(2, 3) = twoDotNO * normal[2]; + M(3, 0) = zero; + M(3, 1) = zero; + M(3, 2) = zero; + M(3, 3) = one; +#else + M(0, 0) = one - two * normal[0] * normal[0]; + M(1, 0) = -two * normal[0] * normal[1]; + M(2, 0) = -two * normal[0] * normal[2]; + M(3, 0) = twoDotNO * normal[0]; + M(0, 1) = M(1, 0); + M(1, 1) = one - two * normal[1] * normal[1]; + M(2, 1) = -two * normal[1] * normal[2]; + M(3, 1) = twoDotNO * normal[1]; + M(0, 2) = M(2, 0); + M(1, 2) = M(2, 1); + M(2, 2) = one - two * normal[2] * normal[2]; + M(3, 2) = twoDotNO * normal[2]; + M(0, 3) = zero; + M(1, 3) = zero; + M(2, 3) = zero; + M(3, 3) = one; +#endif + + return M; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMesh.h new file mode 100644 index 000000000000..011147767df9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMesh.h @@ -0,0 +1,747 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.3.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// The Mesh class is designed to support triangulations of surfaces of a small +// number of topologies. See the documents +// http://www.geometrictools.com/MeshDifferentialGeometry.pdf +// http://www.geometrictools.com/MeshFactory.pdf +// for details. +// +// You must set the vertex attribute sources before calling Update(). +// +// The semantic "position" is required and its source must be an array +// of Real with at least 3 channels so that positions are computed as +// Vector3. +// +// The positions are assumed to be parameterized by texture coordinates +// (u,v); the position is thought of as a function P(u,v). If texture +// coordinates are provided, the semantic must be "tcoord". If texture +// coordinates are not provided, default texture coordinates are computed +// internally as described in the mesh factory document. +// +// The frame for the tangent space is optional. All vectors in the frame +// must have sources that are arrays of Real with at least 3 channels per +// attribute. If normal vectors are provided, the semantic must be +// "normal". +// +// Two options are supported for tangent vectors. The first option is +// that the tangents are surface derivatives dP/du and dP/dv, which are +// not necessarily unit length or orthogonal. The semantics must be +// "dpdu" and "dpdv". The second option is that the tangents are unit +// length and orthogonal, with the infrequent possibility that a vertex +// is degenerate in that dP/du and dP/dv are linearly dependent. The +// semantics must be "tangent" and "bitangent". +// +// For each provided vertex attribute, a derived class can initialize +// that attribute by overriding one of the Initialize*() functions whose +// stubs are defined in this class. + +namespace gte +{ + +enum class MeshTopology +{ + ARBITRARY, + RECTANGLE, + CYLINDER, + TORUS, + DISK, + SPHERE +}; + +class MeshDescription +{ +public: + // Constructor for MeshTopology::ARBITRARY. The members topology, + // numVertices, and numTriangles are set in the obvious manner. The + // members numRows and numCols are set to zero. The remaining members + // must be set explicitly by the client. + inline MeshDescription(uint32_t inNumVertices, uint32_t inNumTriangles); + + // Constructor for topologies other than MeshTopology::ARBITRARY. + // Compute the number of vertices and triangles for the mesh based on the + // requested number of rows and columns. If the number of rows or columns + // is invalid for the specified topology, they are modified to be valid, + // in which case inNumRows/numRows and inNumCols/numCols can differ. If + // the input topology is MeshTopology::ARBITRARY, then inNumRows and + // inNumCols are assigned to numVertices and numTriangles, respectively, + // and numRows and numCols are set to zero. The remaining members must be + // set explicitly by the client. + inline MeshDescription(MeshTopology inTopology, uint32_t inNumRows, uint32_t inNumCols); + + MeshTopology topology; + uint32_t numVertices; + uint32_t numTriangles; + std::vector vertexAttributes; + IndexAttribute indexAttribute; + bool wantDynamicTangentSpaceUpdate; // default: false + bool wantCCW; // default: true + + // For internal use only. + bool hasTangentSpaceVectors; + bool allowUpdateFrame; + uint32_t numRows, numCols; + uint32_t rMax, cMax, rIncrement; + + // After an attempt to construct a Mesh or Mesh-derived object, examine + // this value to determine whether the construction was successful. + bool constructed; +}; + +template +class Mesh +{ +public: + // Construction and destruction. This constructor is for ARBITRARY topology. + // The vertices and indices must already be assigned by the client. Derived + // classes use the protected constructor, but assignment of vertices and + // indices occurs in the derived-class constructors. + Mesh(MeshDescription const& description, std::vector const& validTopologies); + + virtual ~Mesh(); + + // No copying or assignment is allowed. + Mesh(Mesh const&) = delete; + Mesh& operator=(Mesh const&) = delete; + + // Member accessors. + inline MeshDescription const& GetDescription() const; + + // If the underlying geometric data varies dynamically, call this function + // to update whatever vertex attributes are specified by the vertex pool. + // A derived class + void Update(); + +protected: + // Access the vertex attributes. + inline Vector3& Position(uint32_t i); + inline Vector3& Normal(uint32_t i); + inline Vector3& Tangent(uint32_t i); + inline Vector3& Bitangent(uint32_t i); + inline Vector3& DPDU(uint32_t i); + inline Vector3& DPDV(uint32_t i); + inline Vector2& TCoord(uint32_t i); + + // Compute the indices for non-arbitrary topologies. This function is + // called by derived classes. + void ComputeIndices(); + + // The Update() function allows derived classes to use algorithms + // different from least-squares fitting to compute the normals (when + // no tangent-space information is requested) or to compute the frame + // (normals and tangent space). The UpdatePositions() is a stub; the + // base-class has no knowledge about how positions should be modified. + // A derived class, however, might choose to use dynamic updating + // and override UpdatePositions(). The base-class UpdateNormals() + // computes vertex normals as averages of area-weighted triangle + // normals (nonparametric approach). The base-class UpdateFrame() + // uses a least-squares algorithm for estimating the tangent space + // (parametric approach). + virtual void UpdatePositions() {} + virtual void UpdateNormals(); + virtual void UpdateFrame(); + + // Constructor inputs. + // The client requests this via the constructor; however, if it is + // requested and the vertex attributes do not contain entries for + // "tangent", "bitangent", "dpdu", or "dpdv", then this member is + // set to false. + MeshDescription mDescription; + + // Copied from mVertexAttributes when available. + Vector3* mPositions; + Vector3* mNormals; + Vector3* mTangents; + Vector3* mBitangents; + Vector3* mDPDUs; + Vector3* mDPDVs; + Vector2* mTCoords; + size_t mPositionStride; + size_t mNormalStride; + size_t mTangentStride; + size_t mBitangentStride; + size_t mDPDUStride; + size_t mDPDVStride; + size_t mTCoordStride; + + // When dynamic tangent-space updates are requested, the update algorithm + // requires texture coordinates (user-specified or non-local). It is + // possible to create a vertex-adjacent set (with indices into the + // vertex array) for each mesh vertex; however, instead we rely on a + // triangle iteration and incrementally store the information needed for + // the estimation of the tangent space. Each vertex has associated + // matrices D and U, but we need to store only U^T*U and D^T*U. See the + // PDF for details. + std::vector> mUTU; + std::vector> mDTU; +}; + + +inline MeshDescription::MeshDescription(uint32_t inNumVertices, uint32_t inNumTriangles) + : + topology(MeshTopology::ARBITRARY), + numVertices(inNumVertices), + numTriangles(inNumTriangles), + wantDynamicTangentSpaceUpdate(false), + wantCCW(true), + hasTangentSpaceVectors(false), + allowUpdateFrame(false), + numRows(0), + numCols(0), + rMax(0), + cMax(0), + rIncrement(0), + constructed(false) +{ + LogAssert(numVertices >= 3, "Invalid number of vertices."); + LogAssert(numTriangles >= 1, "Invalid number of triangles."); +} + +inline MeshDescription::MeshDescription(MeshTopology inTopology, uint32_t inNumRows, uint32_t inNumCols) + : + topology(inTopology), + wantDynamicTangentSpaceUpdate(false), + wantCCW(true), + hasTangentSpaceVectors(false), + allowUpdateFrame(false), + constructed(false) +{ + switch (topology) + { + case MeshTopology::ARBITRARY: + numVertices = inNumRows; + numTriangles = inNumCols; + numRows = 0; + numCols = 0; + rMax = 0; + cMax = 0; + rIncrement = 0; + break; + + case MeshTopology::RECTANGLE: + numRows = std::max(inNumRows, 2u); + numCols = std::max(inNumCols, 2u); + rMax = numRows - 1; + cMax = numCols - 1; + rIncrement = numCols; + numVertices = (rMax + 1) * (cMax + 1); + numTriangles = 2 * rMax * cMax; + break; + + case MeshTopology::CYLINDER: + numRows = std::max(inNumRows, 2u); + numCols = std::max(inNumCols, 3u); + rMax = numRows - 1; + cMax = numCols; + rIncrement = numCols + 1; + numVertices = (rMax + 1) * (cMax + 1); + numTriangles = 2 * rMax * cMax; + break; + + case MeshTopology::TORUS: + numRows = std::max(inNumRows, 2u); + numCols = std::max(inNumCols, 3u); + rMax = numRows; + cMax = numCols; + rIncrement = numCols + 1; + numVertices = (rMax + 1) * (cMax + 1); + numTriangles = 2 * rMax * cMax; + break; + + case MeshTopology::DISK: + numRows = std::max(inNumRows, 1u); + numCols = std::max(inNumCols, 3u); + rMax = numRows - 1; + cMax = numCols; + rIncrement = numCols + 1; + numVertices = (rMax + 1) * (cMax + 1) + 1; + numTriangles = 2 * rMax * cMax + numCols; + break; + + case MeshTopology::SPHERE: + numRows = std::max(inNumRows, 1u); + numCols = std::max(inNumCols, 3u); + rMax = numRows - 1; + cMax = numCols; + rIncrement = numCols + 1; + numVertices = (rMax + 1) * (cMax + 1) + 2; + numTriangles = 2 * rMax * cMax + 2 * numCols; + break; + } +} + +template +Mesh::Mesh(MeshDescription const& description, std::vector const& validTopologies) + : + mDescription(description), + mPositions(nullptr), + mNormals(nullptr), + mTangents(nullptr), + mBitangents(nullptr), + mDPDUs(nullptr), + mDPDVs(nullptr), + mTCoords(nullptr), + mPositionStride(0), + mNormalStride(0), + mTangentStride(0), + mBitangentStride(0), + mDPDUStride(0), + mDPDVStride(0), + mTCoordStride(0) +{ + mDescription.constructed = false; + for (auto const& topology : validTopologies) + { + if (mDescription.topology == topology) + { + mDescription.constructed = true; + break; + } + } + + if (!mDescription.indexAttribute.source) + { + LogError("The mesh needs triangles/indices."); + mDescription.constructed = false; + return; + } + + // Set sources for the requested vertex attributes. + mDescription.hasTangentSpaceVectors = false; + mDescription.allowUpdateFrame = mDescription.wantDynamicTangentSpaceUpdate; + for (auto const& attribute : mDescription.vertexAttributes) + { + if (attribute.source != nullptr && attribute.stride > 0) + { + if (attribute.semantic == "position") + { + mPositions = reinterpret_cast*>(attribute.source); + mPositionStride = attribute.stride; + continue; + } + + if (attribute.semantic == "normal") + { + mNormals = reinterpret_cast*>(attribute.source); + mNormalStride = attribute.stride; + continue; + } + + if (attribute.semantic == "tangent") + { + mTangents = reinterpret_cast*>(attribute.source); + mTangentStride = attribute.stride; + mDescription.hasTangentSpaceVectors = true; + continue; + } + + if (attribute.semantic == "bitangent") + { + mBitangents = reinterpret_cast*>(attribute.source); + mBitangentStride = attribute.stride; + mDescription.hasTangentSpaceVectors = true; + continue; + } + + if (attribute.semantic == "dpdu") + { + mDPDUs = reinterpret_cast*>(attribute.source); + mDPDUStride = attribute.stride; + mDescription.hasTangentSpaceVectors = true; + continue; + } + + if (attribute.semantic == "dpdv") + { + mDPDVs = reinterpret_cast*>(attribute.source); + mDPDVStride = attribute.stride; + mDescription.hasTangentSpaceVectors = true; + continue; + } + + if (attribute.semantic == "tcoord") + { + mTCoords = reinterpret_cast*>(attribute.source); + mTCoordStride = attribute.stride; + continue; + } + } + } + + if (!mPositions) + { + LogError("The mesh needs positions."); + mPositions = nullptr; + mNormals = nullptr; + mTangents = nullptr; + mBitangents = nullptr; + mDPDUs = nullptr; + mDPDVs = nullptr; + mTCoords = nullptr; + mPositionStride = 0; + mNormalStride = 0; + mTangentStride = 0; + mBitangentStride = 0; + mDPDUStride = 0; + mDPDVStride = 0; + mTCoordStride = 0; + mDescription.constructed = false; + return; + } + + // The initial value of allowUpdateFrame is the client request about + // wanting dynamic tangent-space updates. If the vertex attributes do + // not include tangent-space vectors, then dynamic updates are not + // necessary. If tangent-space vectors are present, the update algorithm + // requires texture coordinates (mTCoords must be nonnull) or must compute + // local coordinates (mNormals must be nonnull). + if (mDescription.allowUpdateFrame) + { + if (!mDescription.hasTangentSpaceVectors) + { + mDescription.allowUpdateFrame = false; + } + + if (!mTCoords && !mNormals) + { + mDescription.allowUpdateFrame = false; + } + } + + if (mDescription.allowUpdateFrame) + { + mUTU.resize(mDescription.numVertices); + mDTU.resize(mDescription.numVertices); + } +} + +template +Mesh::~Mesh() +{ +} + +template +inline MeshDescription const& Mesh::GetDescription() const +{ + return mDescription; +} + +template +void Mesh::Update() +{ + if (!mDescription.constructed) + { + LogError("The Mesh object failed the construction."); + return; + } + + UpdatePositions(); + + if (mDescription.allowUpdateFrame) + { + UpdateFrame(); + } + else if (mNormals) + { + UpdateNormals(); + } + // else: The mesh has no frame data, so there is nothing to do. +} + +template +inline Vector3& Mesh::Position(uint32_t i) +{ + char* positions = reinterpret_cast(mPositions); + return *reinterpret_cast*>(positions + i * mPositionStride); +} + +template +inline Vector3& Mesh::Normal(uint32_t i) +{ + char* normals = reinterpret_cast(mNormals); + return *reinterpret_cast*>(normals + i * mNormalStride); +} + +template +inline Vector3& Mesh::Tangent(uint32_t i) +{ + char* tangents = reinterpret_cast(mTangents); + return *reinterpret_cast*>(tangents + i * mTangentStride); +} + +template +inline Vector3& Mesh::Bitangent(uint32_t i) +{ + char* bitangents = reinterpret_cast(mBitangents); + return *reinterpret_cast*>(bitangents + i * mBitangentStride); +} + +template +inline Vector3& Mesh::DPDU(uint32_t i) +{ + char* dpdus = reinterpret_cast(mDPDUs); + return *reinterpret_cast*>(dpdus + i * mDPDUStride); +} + +template +inline Vector3& Mesh::DPDV(uint32_t i) +{ + char* dpdvs = reinterpret_cast(mDPDVs); + return *reinterpret_cast*>(dpdvs + i * mDPDVStride); +} + +template +inline Vector2& Mesh::TCoord(uint32_t i) +{ + char* tcoords = reinterpret_cast(mTCoords); + return *reinterpret_cast*>(tcoords + i * mTCoordStride); +} + +template +void Mesh::ComputeIndices() +{ + uint32_t t = 0; + for (uint32_t r = 0, i = 0; r < mDescription.rMax; ++r) + { + uint32_t v0 = i, v1 = v0 + 1; + i += mDescription.rIncrement; + uint32_t v2 = i, v3 = v2 + 1; + for (uint32_t c = 0; c < mDescription.cMax; ++c, ++v0, ++v1, ++v2, ++v3) + { + if (mDescription.wantCCW) + { + mDescription.indexAttribute.SetTriangle(t++, v0, v1, v2); + mDescription.indexAttribute.SetTriangle(t++, v1, v3, v2); + } + else + { + mDescription.indexAttribute.SetTriangle(t++, v0, v2, v1); + mDescription.indexAttribute.SetTriangle(t++, v1, v2, v3); + } + } + } + + if (mDescription.topology == MeshTopology::DISK) + { + uint32_t v0 = 0, v1 = 1, v2 = mDescription.numVertices - 1; + for (unsigned int c = 0; c < mDescription.numCols; ++c, ++v0, ++v1) + { + if (mDescription.wantCCW) + { + mDescription.indexAttribute.SetTriangle(t++, v0, v2, v1); + } + else + { + mDescription.indexAttribute.SetTriangle(t++, v0, v1, v2); + } + } + } + else if (mDescription.topology == MeshTopology::SPHERE) + { + uint32_t v0 = 0, v1 = 1, v2 = mDescription.numVertices - 2; + for (uint32_t c = 0; c < mDescription.numCols; ++c, ++v0, ++v1) + { + if (mDescription.wantCCW) + { + mDescription.indexAttribute.SetTriangle(t++, v0, v2, v1); + } + else + { + mDescription.indexAttribute.SetTriangle(t++, v0, v1, v2); + } + } + + v0 = (mDescription.numRows - 1) * mDescription.numCols; + v1 = v0 + 1; + v2 = mDescription.numVertices - 1; + for (uint32_t c = 0; c < mDescription.numCols; ++c, ++v0, ++v1) + { + if (mDescription.wantCCW) + { + mDescription.indexAttribute.SetTriangle(t++, v0, v2, v1); + } + else + { + mDescription.indexAttribute.SetTriangle(t++, v0, v1, v2); + } + } + } +} + +template +void Mesh::UpdateNormals() +{ + // Compute normal vector as normalized weighted averages of triangle + // normal vectors. + + // Set the normals to zero to allow accumulation of triangle normals. + Vector3 zero{ (Real)0, (Real)0, (Real)0 }; + for (uint32_t i = 0; i < mDescription.numVertices; ++i) + { + Normal(i) = zero; + } + + // Accumulate the triangle normals. + for (uint32_t t = 0; t < mDescription.numTriangles; ++t) + { + // Get the positions for the triangle. + uint32_t v0, v1, v2; + mDescription.indexAttribute.GetTriangle(t, v0, v1, v2); + Vector3 P0 = Position(v0); + Vector3 P1 = Position(v1); + Vector3 P2 = Position(v2); + + // Get the edge vectors. + Vector3 E1 = P1 - P0; + Vector3 E2 = P2 - P0; + + // Compute a triangle normal show length is twice the area of the + // triangle. + Vector3 triangleNormal = Cross(E1, E2); + + // Accumulate the triangle normals. + Normal(v0) += triangleNormal; + Normal(v1) += triangleNormal; + Normal(v2) += triangleNormal; + } + + // Normalize the normals. + for (uint32_t i = 0; i < mDescription.numVertices; ++i) + { + Normalize(Normal(i), true); + } +} + +template +void Mesh::UpdateFrame() +{ + if (!mTCoords) + { + // We need to compute vertex normals first in order to compute + // local texture coordinates. The vertex normals are recomputed + // later based on estimated tangent vectors. + UpdateNormals(); + } + + // Use the least-squares algorithm to estimate the tangent-space vectors + // and, if requested, normal vectors. + Matrix<2, 2, Real> zero2x2; // initialized to zero + Matrix<3, 2, Real> zero3x2; // initialized to zero + std::fill(mUTU.begin(), mUTU.end(), zero2x2); + std::fill(mDTU.begin(), mDTU.end(), zero3x2); + for (uint32_t t = 0; t < mDescription.numTriangles; ++t) + { + // Get the positions and differences for the triangle. + uint32_t v0, v1, v2; + mDescription.indexAttribute.GetTriangle(t, v0, v1, v2); + Vector3 P0 = Position(v0); + Vector3 P1 = Position(v1); + Vector3 P2 = Position(v2); + Vector3 D10 = P1 - P0; + Vector3 D20 = P2 - P0; + Vector3 D21 = P2 - P1; + + if (mTCoords) + { + // Get the texture coordinates and differences for the triangle. + Vector2 C0 = TCoord(v0); + Vector2 C1 = TCoord(v1); + Vector2 C2 = TCoord(v2); + Vector2 U10 = C1 - C0; + Vector2 U20 = C2 - C0; + Vector2 U21 = C2 - C1; + + // Compute the outer products. + Matrix<2, 2, Real> outerU10 = OuterProduct(U10, U10); + Matrix<2, 2, Real> outerU20 = OuterProduct(U20, U20); + Matrix<2, 2, Real> outerU21 = OuterProduct(U21, U21); + Matrix<3, 2, Real> outerD10 = OuterProduct(D10, U10); + Matrix<3, 2, Real> outerD20 = OuterProduct(D20, U20); + Matrix<3, 2, Real> outerD21 = OuterProduct(D21, U21); + + // Keep a running sum of U^T*U and D^T*U. + mUTU[v0] += outerU10 + outerU20; + mUTU[v1] += outerU10 + outerU21; + mUTU[v2] += outerU20 + outerU21; + mDTU[v0] += outerD10 + outerD20; + mDTU[v1] += outerD10 + outerD21; + mDTU[v2] += outerD20 + outerD21; + } + else + { + // Compute local coordinates and differences for the triangle. + Vector3 basis[3]; + + basis[0] = Normal(v0); + ComputeOrthogonalComplement(1, basis, true); + Vector2 U10{ Dot(basis[1], D10), Dot(basis[2], D10) }; + Vector2 U20{ Dot(basis[1], D20), Dot(basis[2], D20) }; + mUTU[v0] += OuterProduct(U10, U10) + OuterProduct(U20, U20); + mDTU[v0] += OuterProduct(D10, U10) + OuterProduct(D20, U20); + + basis[0] = Normal(v1); + ComputeOrthogonalComplement(1, basis, true); + Vector2 U01{ Dot(basis[1], D10), Dot(basis[2], D10) }; + Vector2 U21{ Dot(basis[1], D21), Dot(basis[2], D21) }; + mUTU[v1] += OuterProduct(U01, U01) + OuterProduct(U21, U21); + mDTU[v1] += OuterProduct(D10, U01) + OuterProduct(D21, U21); + + basis[0] = Normal(v2); + ComputeOrthogonalComplement(1, basis, true); + Vector2 U02{ Dot(basis[1], D20), Dot(basis[2], D20) }; + Vector2 U12{ Dot(basis[1], D21), Dot(basis[2], D21) }; + mUTU[v2] += OuterProduct(U02, U02) + OuterProduct(U12, U12); + mDTU[v2] += OuterProduct(D20, U02) + OuterProduct(D21, U12); + } + + } + + for (uint32_t i = 0; i < mDescription.numVertices; ++i) + { + Matrix<3, 2, Real> jacobian = mDTU[i] * Inverse(mUTU[i]); + + Vector3 basis[3]; + basis[0] = { jacobian(0, 0), jacobian(1, 0), jacobian(2, 0) }; + basis[1] = { jacobian(0, 1), jacobian(1, 1), jacobian(2, 1) }; + + if (mDPDUs) + { + DPDU(i) = basis[0]; + } + if (mDPDVs) + { + DPDV(i) = basis[1]; + } + + ComputeOrthogonalComplement(2, basis, true); + + if (mNormals) + { + Normal(i) = basis[2]; + } + if (mTangents) + { + Tangent(i) = basis[0]; + } + if (mBitangents) + { + Bitangent(i) = basis[1]; + } + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimalCycleBasis.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimalCycleBasis.h new file mode 100644 index 000000000000..a45a763c6d10 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimalCycleBasis.h @@ -0,0 +1,709 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// Extract the minimal cycle basis for a planar graph. The input vertices and +// edges must form a graph for which edges intersect only at vertices; that is, +// no two edges must intersect at an interior point of one of the edges. The +// algorithm is described in +// http://www.geometrictools.com/Documentation/MinimalCycleBasis.pdf +// The graph might have filaments, which are polylines in the graph that are +// not shared by a cycle. These are also extracted by the implementation. +// Because the inputs to the constructor are vertices and edges of the graph, +// isolated vertices are ignored. +// +// The computations that determine which adjacent vertex to visit next during +// a filament or cycle traversal do not require division, so the exact +// arithmetic type BSNumber suffices for ComputeType when you +// want to ensure a correct output. (Floating-point rounding errors +// potentially can lead to an incorrect output.) + +namespace gte +{ + +template +class MinimalCycleBasis +{ +public: + struct Tree + { + std::vector cycle; + std::vector> children; + }; + + // The input positions and edges must form a planar graph for which edges + // intersect only at vertices; that is, no two edges must intersect at an + // interior point of one of the edges. + MinimalCycleBasis( + std::vector> const& positions, + std::vector> const& edges, + std::vector>& forest); + +private: + // No copy or assignment allowed. + MinimalCycleBasis(MinimalCycleBasis const&) = delete; + MinimalCycleBasis& operator=(MinimalCycleBasis const&) = delete; + + struct Vertex + { + Vertex(int inName, std::array const* inPosition); + bool operator< (Vertex const& vertex) const; + + // The index into the 'positions' input provided to the call to + // operator(). The index is used when reporting cycles to the + // caller of the constructor for MinimalCycleBasis. + int name; + + // Multiple vertices can share a position during processing of + // graph components. + std::array const* position; + + // The mVertexStorage member owns the Vertex objects and maintains + // the reference counts on those objects. The adjacent pointers + // are considered to be weak pointers; neither object ownership nor + // reference counting is required by 'adjacent'. + std::set adjacent; + + // Support for depth-first traversal of a graph. + int visited; + }; + + // The constructor uses GetComponents(...) and DepthFirstSearch(...) to + // get the connected components of the graph implied by the input 'edges'. + // Recursive processing uses only DepthFirstSearch(...) to collect + // vertices of the subgraphs of the original graph. + static void DepthFirstSearch(Vertex* vInitial, std::vector& component); + + // Support for traversing a simply connected component of the graph. + std::shared_ptr ExtractBasis(std::vector& component); + void RemoveFilaments(std::vector& component); + std::shared_ptr ExtractCycleFromComponent(std::vector& component); + std::shared_ptr ExtractCycleFromClosedWalk(std::vector& closedWalk); + std::vector ExtractCycle(std::vector& closedWalk); + + Vertex* GetClockwiseMost(Vertex* vPrev, Vertex* vCurr) const; + Vertex* GetCounterclockwiseMost(Vertex* vPrev, Vertex* vCurr) const; + + // Storage for referenced vertices of the original graph and for new + // vertices added during graph traversal. + std::vector> mVertexStorage; +}; + + +template +MinimalCycleBasis::MinimalCycleBasis(std::vector> const& positions, + std::vector> const& edges, std::vector>& forest) +{ + forest.clear(); + if (positions.size() == 0 || edges.size() == 0) + { + // The graph is empty, so there are no filaments or cycles. + return; + } + + // Determine the unique positions referenced by the edges. + std::map> unique; + for (auto const& edge : edges) + { + for (int i = 0; i < 2; ++i) + { + int name = edge[i]; + if (unique.find(name) == unique.end()) + { + std::shared_ptr vertex = + std::make_shared(name, &positions[name]); + unique.insert(std::make_pair(name, vertex)); + + } + } + } + + // Assign responsibility for ownership of the Vertex objects. + std::vector vertices; + mVertexStorage.reserve(unique.size()); + vertices.reserve(unique.size()); + for (auto const& element : unique) + { + mVertexStorage.push_back(element.second); + vertices.push_back(element.second.get()); + } + + // Determine the adjacencies from the edge information. + for (auto const& edge : edges) + { + auto iter0 = unique.find(edge[0]); + auto iter1 = unique.find(edge[1]); + iter0->second->adjacent.insert(iter1->second.get()); + iter1->second->adjacent.insert(iter0->second.get()); + } + + // Get the connected components of the graph. The 'visited' flags are + // 0 (unvisited), 1 (discovered), 2 (finished). The Vertex constructor + // sets all 'visited' flags to 0. + std::vector> components; + for (auto vInitial : mVertexStorage) + { + if (vInitial->visited == 0) + { + components.push_back(std::vector()); + DepthFirstSearch(vInitial.get(), components.back()); + } + } + + // The depth-first search is used later for collecting vertices for + // subgraphs that are detached from the main graph, so the 'visited' + // flags must be reset to zero after component finding. + for (auto vertex : mVertexStorage) + { + vertex->visited = 0; + } + + // Get the primitives for the components. + for (auto& component : components) + { + forest.push_back(ExtractBasis(component)); + } +} + +template +void MinimalCycleBasis::DepthFirstSearch(Vertex* vInitial, std::vector& component) +{ + std::stack vStack; + vStack.push(vInitial); + while (vStack.size() > 0) + { + Vertex* vertex = vStack.top(); + vertex->visited = 1; + size_t i = 0; + for (auto adjacent : vertex->adjacent) + { + if (adjacent && adjacent->visited == 0) + { + vStack.push(adjacent); + break; + } + ++i; + } + + if (i == vertex->adjacent.size()) + { + vertex->visited = 2; + component.push_back(vertex); + vStack.pop(); + } + } +} + +template +std::shared_ptr::Tree> + MinimalCycleBasis::ExtractBasis(std::vector& component) +{ + // The root will not have its 'cycle' member set. The children are + // the cycle trees extracted from the component. + std::shared_ptr tree = std::make_shared(); + while (component.size() > 0) + { + RemoveFilaments(component); + if (component.size() > 0) + { + tree->children.push_back(ExtractCycleFromComponent(component)); + } + } + + if (tree->cycle.size() == 0 && tree->children.size() == 1) + { + // Replace the parent by the child to avoid having two empty + // cycles in parent/child. + std::shared_ptr child = tree->children.back(); + tree->cycle = std::move(child->cycle); + tree->children = std::move(child->children); + } + return tree; +} + +template +void MinimalCycleBasis::RemoveFilaments(std::vector& component) +{ + // Locate all filament endpoints, which are vertices, each having exactly + // one adjacent vertex. + std::vector endpoints; + for (auto vertex : component) + { + if (vertex->adjacent.size() == 1) + { + endpoints.push_back(vertex); + } + } + + if (endpoints.size() > 0) + { + // Remove the filaments from the component. If a filament has two + // endpoints, each having one adjacent vertex, the adjacency set of + // the final visited vertex become empty. We must test for that + // condition before starting a new filament removal. + for (auto vertex : endpoints) + { + if (vertex->adjacent.size() == 1) + { + // Traverse the filament and remove the vertices. + while (vertex->adjacent.size() == 1) + { + // Break the connection between the two vertices. + Vertex* adjacent = *vertex->adjacent.begin(); + adjacent->adjacent.erase(vertex); + vertex->adjacent.erase(adjacent); + + // Traverse to the adjacent vertex. + vertex = adjacent; + } + } + } + + // At this time the component is either empty (it was a union of + // polylines) or it has no filaments and at least one cycle. Remove + // the isolated vertices generated by filament extraction. + std::vector remaining; + remaining.reserve(component.size()); + for (auto vertex : component) + { + if (vertex->adjacent.size() > 0) + { + remaining.push_back(vertex); + } + } + component = std::move(remaining); + } +} + +template +std::shared_ptr::Tree> + MinimalCycleBasis::ExtractCycleFromComponent(std::vector& component) +{ + // Search for the left-most vertex of the component. If two or more + // vertices attain minimum x-value, select the one that has minimum + // y-value. + Vertex* minVertex = component[0]; + for (auto vertex : component) + { + if (*vertex->position < *minVertex->position) + { + minVertex = vertex; + } + } + + // Traverse the closed walk, duplicating the starting vertex as the + // last vertex. + std::vector closedWalk; + Vertex* vCurr = minVertex; + Vertex* vStart = vCurr; + closedWalk.push_back(vStart); + Vertex* vAdj = GetClockwiseMost(nullptr, vStart); + while (vAdj != vStart) + { + closedWalk.push_back(vAdj); + Vertex* vNext = GetCounterclockwiseMost(vCurr, vAdj); + vCurr = vAdj; + vAdj = vNext; + } + closedWalk.push_back(vStart); + + // Recursively process the closed walk to extract cycles. + std::shared_ptr tree = ExtractCycleFromClosedWalk(closedWalk); + + // The isolated vertices generated by cycle removal are also removed from + // the component. + std::vector remaining; + remaining.reserve(component.size()); + for (auto vertex : component) + { + if (vertex->adjacent.size() > 0) + { + remaining.push_back(vertex); + } + } + component = std::move(remaining); + + return tree; +} + +template +std::shared_ptr::Tree> + MinimalCycleBasis::ExtractCycleFromClosedWalk(std::vector& closedWalk) +{ + std::shared_ptr tree = std::make_shared(); + + std::map duplicates; + std::set detachments; + int numClosedWalk = static_cast(closedWalk.size()); + for (int i = 1; i < numClosedWalk - 1; ++i) + { + auto diter = duplicates.find(closedWalk[i]); + if (diter == duplicates.end()) + { + // We have not yet visited this vertex. + duplicates.insert(std::make_pair(closedWalk[i], i)); + continue; + } + + // The vertex has been visited previously. Collapse the closed walk + // by removing the subwalk sharing this vertex. Note that the vertex + // is pointed to by closedWalk[diter->second] and closedWalk[i]. + int iMin = diter->second, iMax = i; + detachments.insert(iMin); + for (int j = iMin + 1; j < iMax; ++j) + { + Vertex* vertex = closedWalk[j]; + duplicates.erase(vertex); + detachments.erase(j); + } + closedWalk.erase(closedWalk.begin() + iMin + 1, closedWalk.begin() + iMax + 1); + numClosedWalk = static_cast(closedWalk.size()); + i = iMin; + } + + if (numClosedWalk > 3) + { + // We do not know whether closedWalk[0] is a detachment point. To + // determine this, we must test for any edges strictly contained by + // the wedge formed by the edges and + // . However, we must execute this test + // even for the known detachment points. The ensuing logic is designed + // to handle this and reduce the amount of code, so we insert + // closedWalk[0] into the detachment set and will ignore it later if + // it actually is not. + detachments.insert(0); + + // Detach subgraphs from the vertices of the cycle. + for (auto i : detachments) + { + Vertex* original = closedWalk[i]; + Vertex* maxVertex = closedWalk[i + 1]; + Vertex* minVertex = (i > 0 ? closedWalk[i - 1] : closedWalk[numClosedWalk - 2]); + + std::array dMin, dMax; + for (int j = 0; j < 2; ++j) + { + dMin[j] = (*minVertex->position)[j] - (*original->position)[j]; + dMax[j] = (*maxVertex->position)[j] - (*original->position)[j]; + } + + bool isConvex = (dMax[0] * dMin[1] >= dMax[1] * dMin[0]); (void)isConvex; + + std::set inWedge; + std::set adjacent = original->adjacent; + for (auto vertex : adjacent) + { + if (vertex->name == minVertex->name || vertex->name == maxVertex->name) + { + continue; + } + + std::array dVer; + for (int j = 0; j < 2; ++j) + { + dVer[j] = (*vertex->position)[j] - (*original->position)[j]; + } + + bool containsVertex; + if (isConvex) + { + containsVertex = + dVer[0] * dMin[1] > dVer[1] * dMin[0] && + dVer[0] * dMax[1] < dVer[1] * dMax[0]; + } + else + { + containsVertex = + (dVer[0] * dMin[1] > dVer[1] * dMin[0]) || + (dVer[0] * dMax[1] < dVer[1] * dMax[0]); + } + if (containsVertex) + { + inWedge.insert(vertex); + } + } + + if (inWedge.size() > 0) + { + // The clone will manage the adjacents for 'original' that lie + // inside the wedge defined by the first and last edges of the + // subgraph rooted at 'original'. The sorting is in the + // clockwise direction. + std::shared_ptr clone = + std::make_shared(original->name, original->position); + mVertexStorage.push_back(clone); + + // Detach the edges inside the wedge. + for (auto vertex : inWedge) + { + original->adjacent.erase(vertex); + vertex->adjacent.erase(original); + clone->adjacent.insert(vertex); + vertex->adjacent.insert(clone.get()); + } + + // Get the subgraph (it is a single connected component). + std::vector component; + DepthFirstSearch(clone.get(), component); + + // Extract the cycles of the subgraph. + tree->children.push_back(ExtractBasis(component)); + } + // else the candidate was closedWalk[0] and it has no subgraph + // to detach. + } + + tree->cycle = std::move(ExtractCycle(closedWalk)); + } + else + { + // Detach the subgraph from vertex closedWalk[0]; the subgraph + // is attached via a filament. + Vertex* original = closedWalk[0]; + Vertex* adjacent = closedWalk[1]; + + std::shared_ptr clone = + std::make_shared(original->name, original->position); + mVertexStorage.push_back(clone); + + original->adjacent.erase(adjacent); + adjacent->adjacent.erase(original); + clone->adjacent.insert(adjacent); + adjacent->adjacent.insert(clone.get()); + + // Get the subgraph (it is a single connected component). + std::vector component; + DepthFirstSearch(clone.get(), component); + + // Extract the cycles of the subgraph. + tree->children.push_back(ExtractBasis(component)); + if (tree->cycle.size() == 0 && tree->children.size() == 1) + { + // Replace the parent by the child to avoid having two empty + // cycles in parent/child. + std::shared_ptr child = tree->children.back(); + tree->cycle = std::move(child->cycle); + tree->children = std::move(child->children); + } + } + + return tree; +} + +template +std::vector MinimalCycleBasis::ExtractCycle(std::vector& closedWalk) +{ + // TODO: This logic was designed not to remove filaments after the + // cycle deletion is complete. Modify this to allow filament removal. + + // The closed walk is a cycle. + int const numVertices = static_cast(closedWalk.size()); + std::vector cycle(numVertices); + for (int i = 0; i < numVertices; ++i) + { + cycle[i] = closedWalk[i]->name; + } + + // The clockwise-most edge is always removable. + Vertex* v0 = closedWalk[0]; + Vertex* v1 = closedWalk[1]; + Vertex* vBranch = (v0->adjacent.size() > 2 ? v0 : nullptr); + v0->adjacent.erase(v1); + v1->adjacent.erase(v0); + + // Remove edges while traversing counterclockwise. + while (v1 != vBranch && v1->adjacent.size() == 1) + { + Vertex* adj = *v1->adjacent.begin(); + v1->adjacent.erase(adj); + adj->adjacent.erase(v1); + v1 = adj; + } + + if (v1 != v0) + { + // If v1 had exactly 3 adjacent vertices, removal of the CCW edge + // that shared v1 leads to v1 having 2 adjacent vertices. When + // the CW removal occurs and we reach v1, the edge deletion will + // lead to v1 having 1 adjacent vertex, making it a filament + // endpoints. We must ensure we do not delete v1 in this case, + // allowing the recursive algorithm to handle the filament later. + vBranch = v1; + + // Remove edges while traversing clockwise. + while (v0 != vBranch && v0->adjacent.size() == 1) + { + v1 = *v0->adjacent.begin(); + v0->adjacent.erase(v1); + v1->adjacent.erase(v0); + v0 = v1; + } + } + // else the cycle is its own connected component. + + return cycle; +} + +template +typename MinimalCycleBasis::Vertex* + MinimalCycleBasis::GetClockwiseMost(Vertex* vPrev, Vertex* vCurr) const +{ + Vertex* vNext = nullptr; + bool vCurrConvex = false; + std::array dCurr, dNext; + if (vPrev) + { + dCurr[0] = (*vCurr->position)[0] - (*vPrev->position)[0]; + dCurr[1] = (*vCurr->position)[1] - (*vPrev->position)[1]; + } + else + { + dCurr[0] = static_cast(0); + dCurr[1] = static_cast(-1); + } + + for (auto vAdj : vCurr->adjacent) + { + // vAdj is a vertex adjacent to vCurr. No backtracking is allowed. + if (vAdj == vPrev) + { + continue; + } + + // Compute the potential direction to move in. + std::array dAdj; + dAdj[0] = (*vAdj->position)[0] - (*vCurr->position)[0]; + dAdj[1] = (*vAdj->position)[1] - (*vCurr->position)[1]; + + // Select the first candidate. + if (!vNext) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]); + continue; + } + + // Update if the next candidate is clockwise of the current + // clockwise-most vertex. + if (vCurrConvex) + { + if (dCurr[0] * dAdj[1] < dCurr[1] * dAdj[0] + || dNext[0] * dAdj[1] < dNext[1] * dAdj[0]) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]); + } + } + else + { + if (dCurr[0] * dAdj[1] < dCurr[1] * dAdj[0] + && dNext[0] * dAdj[1] < dNext[1] * dAdj[0]) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] < dNext[1] * dCurr[0]); + } + } + } + + return vNext; +} + +template +typename MinimalCycleBasis::Vertex* + MinimalCycleBasis::GetCounterclockwiseMost(Vertex* vPrev, Vertex* vCurr) const +{ + Vertex* vNext = nullptr; + bool vCurrConvex = false; + std::array dCurr, dNext; + if (vPrev) + { + dCurr[0] = (*vCurr->position)[0] - (*vPrev->position)[0]; + dCurr[1] = (*vCurr->position)[1] - (*vPrev->position)[1]; + } + else + { + dCurr[0] = static_cast(0); + dCurr[1] = static_cast(-1); + } + + for (auto vAdj : vCurr->adjacent) + { + // vAdj is a vertex adjacent to vCurr. No backtracking is allowed. + if (vAdj == vPrev) + { + continue; + } + + // Compute the potential direction to move in. + std::array dAdj; + dAdj[0] = (*vAdj->position)[0] - (*vCurr->position)[0]; + dAdj[1] = (*vAdj->position)[1] - (*vCurr->position)[1]; + + // Select the first candidate. + if (!vNext) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]); + continue; + } + + // Select the next candidate if it is counterclockwise of the current + // counterclockwise-most vertex. + if (vCurrConvex) + { + if (dCurr[0] * dAdj[1] > dCurr[1] * dAdj[0] + && dNext[0] * dAdj[1] > dNext[1] * dAdj[0]) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]); + } + } + else + { + if (dCurr[0] * dAdj[1] > dCurr[1] * dAdj[0] + || dNext[0] * dAdj[1] > dNext[1] * dAdj[0]) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]); + } + } + } + + return vNext; +} + +template +MinimalCycleBasis::Vertex::Vertex(int inName, std::array const* inPosition) + : + name(inName), + position(inPosition), + visited(0) +{ +} + +template +bool MinimalCycleBasis::Vertex::operator< (Vertex const& vertex) const +{ + return name < vertex.name; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimize1.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimize1.h new file mode 100644 index 000000000000..2530b820f221 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimize1.h @@ -0,0 +1,336 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The interval [t0,t1] provided to GetMinimum(Real,Real,Real,Real&,Real&) +// is processed by examining subintervals. On each subinteral [a,b], the +// values f0 = F(a), f1 = F((a+b)/2), and f2 = F(b) are examined. If +// {f0,f1,f2} is monotonic, then [a,b] is subdivided and processed. The +// maximum depth of the recursion is limited by 'maxLevel'. If {f0,f1,f2} +// is not monotonic, then two cases arise. First, if f1 = min{f0,f1,f2}, +// then {f0,f1,f2} is said to "bracket a minimum" and GetBracketedMinimum(*) +// is called to locate the function minimum. The process uses a form of +// bisection called "parabolic interpolation" and the maximum number of +// bisection steps is 'maxBracket'. Second, if f1 = max{f0,f1,f2}, then +// {f0,f1,f2} brackets a maximum. The minimum search continues recursively +// as before on [a,(a+b)/2] and [(a+b)/2,b]. + +namespace gte +{ + +template +class Minimize1 +{ +public: + // Construction. + Minimize1(std::function const& F, int maxLevel, + int maxBracket); + + // Search for a minimum of 'function' on the interval [t0,t1] using an + // initial guess of 'tInitial'. The location of the minimum is 'tMin' and + // the value of the minimum is 'fMin'. + void GetMinimum(Real t0, Real t1, Real tInitial, Real& tMin, Real& fMin); + +private: + // This is called to start the search on [t0,tInitial] and [tInitial,t1]. + void GetMinimum(Real t0, Real f0, Real t1, Real f1, int level); + + // This is called recursively to search [a,(a+b)/2] and [(a+b)/2,b]. + void GetMinimum(Real t0, Real f0, Real tm, Real fm, Real t1, Real f1, + int level); + + // This is called when {f0,f1,f2} brackets a minimum. + void GetBracketedMinimum(Real t0, Real f0, Real tm, Real fm, Real t1, + Real f1, int level); + + std::function mFunction; + int mMaxLevel; + int mMaxBracket; + Real mTMin, mFMin; +}; + + +template +Minimize1::Minimize1(std::function const& F, int maxLevel, + int maxBracket) + : + mFunction(F), + mMaxLevel(maxLevel), + mMaxBracket(maxBracket) +{ +} + +template +void Minimize1::GetMinimum(Real t0, Real t1, Real tInitial, + Real& tMin, Real& fMin) +{ + LogAssert(t0 <= tInitial && tInitial <= t1, "Invalid initial t value."); + + mTMin = std::numeric_limits::max(); + mFMin = std::numeric_limits::max(); + + Real f0 = mFunction(t0); + if (f0 < mFMin) + { + mTMin = t0; + mFMin = f0; + } + + Real fInitial = mFunction(tInitial); + if (fInitial < mFMin) + { + mTMin = tInitial; + mFMin = fInitial; + } + + Real f1 = mFunction(t1); + if (f1 < mFMin) + { + mTMin = t1; + mFMin = f1; + } + + GetMinimum(t0, f0, tInitial, fInitial, t1, f1, mMaxLevel); + + tMin = mTMin; + fMin = mFMin; +} + +template +void Minimize1::GetMinimum(Real t0, Real f0, Real tm, Real fm, Real t1, + Real f1, int level) +{ + if (level-- == 0) + { + return; + } + + if ((t1 - tm)*(f0 - fm) > (tm - t0)*(fm - f1)) + { + // The quadratic fit has positive second derivative at the midpoint. + if (f1 > f0) + { + if (fm >= f0) + { + // Increasing, repeat on [t0,tm]. + GetMinimum(t0, f0, tm, fm, level); + } + else + { + // Not monotonic, have a bracket. + GetBracketedMinimum(t0, f0, tm, fm, t1, f1, level); + } + } + else if (f1 < f0) + { + if (fm >= f1) + { + // Decreasing, repeat on [tm,t1]. + GetMinimum(tm, fm, t1, f1, level); + } + else + { + // Not monotonic, have a bracket. + GetBracketedMinimum(t0, f0, tm, fm, t1, f1, level); + } + } + else + { + // Constant, repeat on [t0,tm] and [tm,t1]. + GetMinimum(t0, f0, tm, fm, level); + GetMinimum(tm, fm, t1, f1, level); + } + } + else + { + // The quadratic fit has a nonpositive second derivative at the + // midpoint. + if (f1 > f0) + { + // Repeat on [t0,tm]. + GetMinimum(t0, f0, tm, fm, level); + } + else if (f1 < f0) + { + // Repeat on [tm,t1]. + GetMinimum(tm, fm, t1, f1, level); + } + else + { + // Repeat on [t0,tm] and [tm,t1]. + GetMinimum(t0, f0, tm, fm, level); + GetMinimum(tm, fm, t1, f1, level); + } + } +} + +template +void Minimize1::GetMinimum(Real t0, Real f0, Real t1, Real f1, + int level) +{ + if (level-- == 0) + { + return; + } + + Real tm = ((Real)0.5)*(t0 + t1); + Real fm = mFunction(tm); + if (fm < mFMin) + { + mTMin = tm; + mFMin = fm; + } + + if (f0 - ((Real)2)*fm + f1 >(Real)0) + { + // The quadratic fit has positive second derivative at the midpoint. + if (f1 > f0) + { + if (fm >= f0) + { + // Increasing, repeat on [t0,tm]. + GetMinimum(t0, f0, tm, fm, level); + } + else + { + // Not monotonic, have a bracket. + GetBracketedMinimum(t0, f0, tm, fm, t1, f1, level); + } + } + else if (f1 < f0) + { + if (fm >= f1) + { + // Decreasing, repeat on [tm,t1]. + GetMinimum(tm, fm, t1, f1, level); + } + else + { + // Not monotonic, have a bracket. + GetBracketedMinimum(t0, f0, tm, fm, t1, f1, level); + } + } + else + { + // Constant, repeat on [t0,tm] and [tm,t1]. + GetMinimum(t0, f0, tm, fm, level); + GetMinimum(tm, fm, t1, f1, level); + } + } + else + { + // The quadratic fit has nonpositive second derivative at the + // midpoint. + if (f1 > f0) + { + // Repeat on [t0,tm]. + GetMinimum(t0, f0, tm, fm, level); + } + else if (f1 < f0) + { + // Repeat on [tm,t1]. + GetMinimum(tm, fm, t1, f1, level); + } + else + { + // Repeat on [t0,tm] and [tm,t1]. + GetMinimum(t0, f0, tm, fm, level); + GetMinimum(tm, fm, t1, f1, level); + } + } +} + +template +void Minimize1::GetBracketedMinimum(Real t0, Real f0, Real tm, + Real fm, Real t1, Real f1, int level) +{ + for (int i = 0; i < mMaxBracket; ++i) + { + // Update minimum value. + if (fm < mFMin) + { + mTMin = tm; + mFMin = fm; + } + + // Test for convergence. TODO: Expose the epsilon and tolerance + // parameters to the caller. + Real const epsilon = (Real)1e-08; + Real const tolerance = (Real)1e-04; + if (std::abs(t1 - t0) <= ((Real)2)*tolerance*std::abs(tm) + epsilon) + { + break; + } + + // Compute vertex of interpolating parabola. + Real dt0 = t0 - tm; + Real dt1 = t1 - tm; + Real df0 = f0 - fm; + Real df1 = f1 - fm; + Real tmp0 = dt0*df1; + Real tmp1 = dt1*df0; + Real denom = tmp1 - tmp0; + if (std::abs(denom) < epsilon) + { + return; + } + + Real tv = tm + ((Real)0.5)*(dt1*tmp1 - dt0*tmp0) / denom; + LogAssert(t0 <= tv && tv <= t1, "Vertex not in interval."); + Real fv = mFunction(tv); + if (fv < mFMin) + { + mTMin = tv; + mFMin = fv; + } + + if (tv < tm) + { + if (fv < fm) + { + t1 = tm; + f1 = fm; + tm = tv; + fm = fv; + } + else + { + t0 = tv; + f0 = fv; + } + } + else if (tv > tm) + { + if (fv < fm) + { + t0 = tm; + f0 = fm; + tm = tv; + fm = fv; + } + else + { + t1 = tv; + f1 = fv; + } + } + else + { + // The vertex of parabola is already at middle sample point. + GetMinimum(t0, f0, tm, fm, level); + GetMinimum(tm, fm, t1, f1, level); + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimizeN.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimizeN.h new file mode 100644 index 000000000000..a5b1f3187989 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimizeN.h @@ -0,0 +1,213 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The Cartesian-product domain provided to GetMinimum(*) has minimum values +// stored in t0[0..d-1] and maximum values stored in t1[0..d-1], where d is +// 'dimensions'. The domain is searched along lines through the current +// estimate of the minimum location. Each such line is searched for a minimum +// using a Minimize1 object. This is called "Powell's Direction Set +// Method". The parameters 'maxLevel' and 'maxBracket' are used by +// Minimize1, so read the documentation for that class (in its header +// file) to understand what these mean. The input 'maxIterations' is the +// number of iterations for the direction-set method. + +namespace gte +{ + +template +class MinimizeN +{ +public: + // Construction. + MinimizeN(int dimensions, std::function const& F, + int maxLevel, int maxBracket, int maxIterations); + + // Find the minimum on the Cartesian-product domain whose minimum values + // are stored in t0[0..d-1] and whose maximum values are stored in + // t1[0..d-1], where d is 'dimensions'. An initial guess is specified in + // tInitial[0..d-1]. The location of the minimum is tMin[0..d-1] and + // the value of the minimum is 'fMin'. + void GetMinimum(Real const* t0, Real const* t1, Real const* tInitial, + Real* tMin, Real& fMin); + +private: + // The current estimate of the minimum location is mTCurr[0..d-1]. The + // direction of the current line to search is mDCurr[0..d-1]. This line + // must be clipped against the Cartesian-product domain, a process + // implemented in this function. If the line is mTCurr+s*mDCurr, the + // clip result is the s-interval [ell0,ell1]. + void ComputeDomain(Real const* t0, Real const* t1, Real& ell0, + Real& ell1); + + int mDimensions; + std::function mFunction; + int mMaxIterations; + std::vector> mDirections; + int mDConjIndex; + int mDCurrIndex; + GVector mTCurr; + GVector mTSave; + Real mFCurr; + Minimize1 mMinimizer; +}; + + +template +MinimizeN::MinimizeN(int dimensions, + std::function const& F, int maxLevel, int maxBracket, + int maxIterations) + : + mDimensions(dimensions), + mFunction(F), + mMaxIterations(maxIterations), + mDirections(dimensions + 1), + mDConjIndex(dimensions), + mDCurrIndex(0), + mTCurr(dimensions), + mTSave(dimensions), + mMinimizer( + [this](Real t) +{ + return mFunction(&(mTCurr + t*mDirections[mDCurrIndex])[0]); +}, +maxLevel, maxBracket) +{ + for (auto& direction : mDirections) + { + direction.SetSize(dimensions); + } +} + +template +void MinimizeN::GetMinimum(Real const* t0, Real const* t1, + Real const* tInitial, Real* tMin, Real& fMin) +{ + // The initial guess. + size_t numBytes = mDimensions*sizeof(Real); + mFCurr = mFunction(tInitial); + Memcpy(&mTSave[0], tInitial, numBytes); + Memcpy(&mTCurr[0], tInitial, numBytes); + + // Initialize the direction set to the standard Euclidean basis. + for (int i = 0; i < mDimensions; ++i) + { + mDirections[i].MakeUnit(i); + } + + Real ell0, ell1, ellMin; + for (int iter = 0; iter < mMaxIterations; ++iter) + { + // Find minimum in each direction and update current location. + for (int i = 0; i < mDimensions; ++i) + { + mDCurrIndex = i; + ComputeDomain(t0, t1, ell0, ell1); + mMinimizer.GetMinimum(ell0, ell1, (Real)0, ellMin, mFCurr); + mTCurr += ellMin*mDirections[i]; + } + + // Estimate a unit-length conjugate direction. TODO: Expose + // epsilon to the caller. + mDirections[mDConjIndex] = mTCurr - mTSave; + Real length = Length(mDirections[mDConjIndex]); + Real const epsilon = (Real)1e-06; + if (length < epsilon) + { + // New position did not change significantly from old one. + // Should there be a better convergence criterion here? + break; + } + + mDirections[mDConjIndex] /= length; + + // Minimize in conjugate direction. + mDCurrIndex = mDConjIndex; + ComputeDomain(t0, t1, ell0, ell1); + mMinimizer.GetMinimum(ell0, ell1, (Real)0, ellMin, mFCurr); + mTCurr += ellMin*mDirections[mDCurrIndex]; + + // Cycle the directions and add conjugate direction to set. + mDConjIndex = 0; + for (int i = 0; i < mDimensions; ++i) + { + mDirections[i] = mDirections[i + 1]; + } + + // Set parameters for next pass. + mTSave = mTCurr; + } + + Memcpy(tMin, &mTCurr[0], numBytes); + fMin = mFCurr; +} + +template +void MinimizeN::ComputeDomain(Real const* t0, Real const* t1, + Real& ell0, Real& ell1) +{ + ell0 = -std::numeric_limits::max(); + ell1 = +std::numeric_limits::max(); + + for (int i = 0; i < mDimensions; ++i) + { + Real value = mDirections[mDCurrIndex][i]; + if (value != (Real)0) + { + Real b0 = t0[i] - mTCurr[i]; + Real b1 = t1[i] - mTCurr[i]; + Real inv = ((Real)1) / value; + if (value >(Real)0) + { + // The valid t-interval is [b0,b1]. + b0 *= inv; + if (b0 > ell0) + { + ell0 = b0; + } + b1 *= inv; + if (b1 < ell1) + { + ell1 = b1; + } + } + else + { + // The valid t-interval is [b1,b0]. + b0 *= inv; + if (b0 < ell1) + { + ell1 = b0; + } + b1 *= inv; + if (b1 > ell0) + { + ell0 = b1; + } + } + } + } + + // Correction if numerical errors lead to values nearly zero. + if (ell0 > (Real)0) + { + ell0 = (Real)0; + } + if (ell1 < (Real)0) + { + ell1 = (Real)0; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumAreaBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumAreaBox2.h new file mode 100644 index 000000000000..b4fde3629cf6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumAreaBox2.h @@ -0,0 +1,719 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include + +// Compute a minimum-area oriented box containing the specified points. The +// algorithm uses the rotating calipers method. +// http://www-cgrl.cs.mcgill.ca/~godfried/research/calipers.html +// http://cgm.cs.mcgill.ca/~orm/rotcal.html +// The box is supported by the convex hull of the points, so the algorithm +// is really about computing the minimum-area box containing a convex polygon. +// The rotating calipers approach is O(n) in time for n polygon edges. +// +// A detailed description of the algorithm and implementation is found in +// http://www.geometrictools.com/Documentation/MinimumAreaRectangle.pdf +// +// NOTE: This algorithm guarantees a correct output only when ComputeType is +// an exact arithmetic type that supports division. In GTEngine, one such +// type is BSRational (arbitrary precision). Another such type +// is BSRational> (fixed precision), where N is chosen large +// enough for your input data sets. If you choose ComputeType to be 'float' +// or 'double', the output is not guaranteed to be correct. +// +// See GeometricTools/GTEngine/Samples/Geometrics/MinimumAreaBox2 for an +// example of how to use the code. + +namespace gte +{ + +template +class MinimumAreaBox2 +{ +public: + // The class is a functor to support computing the minimum-area box of + // multiple data sets using the same class object. + MinimumAreaBox2(); + + // The points are arbitrary, so we must compute the convex hull from + // them in order to compute the minimum-area box. The input parameters + // are necessary for using ConvexHull2. NOTE: ConvexHull2 guarantees + // that the hull does not have three consecutive collinear points. + OrientedBox2 operator()(int numPoints, + Vector2 const* points, + bool useRotatingCalipers = + !std::is_floating_point::value); + + // The points already form a counterclockwise, nondegenerate convex + // polygon. If the points directly are the convex polygon, set + // numIndices to 0 and indices to nullptr. If the polygon vertices + // are a subset of the incoming points, that subset is identified by + // numIndices >= 3 and indices having numIndices elements. + OrientedBox2 operator()(int numPoints, + Vector2 const* points, int numIndices, int const* indices, + bool useRotatingCalipers = + !std::is_floating_point::value); + + // Member access. + inline int GetNumPoints() const; + inline Vector2 const* GetPoints() const; + inline std::vector const& GetHull() const; + inline std::array const& GetSupportIndices() const; + inline InputType GetArea() const; + +private: + // The box axes are U[i] and are usually not unit-length in order to allow + // exact arithmetic. The box is supported by mPoints[index[i]], where i + // is one of the enumerations above. The box axes are not necessarily unit + // length, but they have the same length. They need to be normalized for + // conversion back to InputType. + struct Box + { + Vector2 U[2]; + std::array index; // order: bottom, right, top, left + ComputeType sqrLenU0, area; + }; + + // The rotating calipers algorithm has a loop invariant that requires + // the convex polygon not to have collinear points. Any such points + // must be removed first. The code is also executed for the O(n^2) + // algorithm to reduce the number of process edges. + void RemoveCollinearPoints(std::vector>& vertices); + + // This is the slow O(n^2) search. + Box ComputeBoxForEdgeOrderNSqr( + std::vector> const& vertices); + + // The fast O(n) search. + Box ComputeBoxForEdgeOrderN( + std::vector> const& vertices); + + // Compute the smallest box for the polygon edge . + Box SmallestBox(int i0, int i1, + std::vector> const& vertices); + + // Compute (sin(angle))^2 for the polygon edges emanating from the support + // vertices of the box. The return value is 'true' if at least one angle + // is in [0,pi/2); otherwise, the return value is 'false' and the original + // polygon must be a rectangle. + bool ComputeAngles(std::vector> const& vertices, + Box const& box, std::array, 4>& A, + int& numA) const; + + // Sort the angles indirectly. The sorted indices are returned. This + // avoids swapping elements of A[], which can be expensive when + // ComputeType is an exact rational type. + std::array SortAngles( + std::array, 4> const& A, int numA) const; + + bool UpdateSupport(std::array, 4> const& A, + int numA, std::array const& sort, + std::vector> const& vertices, + std::vector& visited, Box& box); + + // Convert the ComputeType box to the InputType box. When the ComputeType + // is an exact rational type, the conversions are performed to avoid + // precision loss until necessary at the last step. + void ConvertTo(Box const& minBox, + std::vector> const& computePoints, + OrientedBox2& itMinBox); + + // The input points to be bound. + int mNumPoints; + Vector2 const* mPoints; + + // The indices into mPoints/mComputePoints for the convex hull vertices. + std::vector mHull; + + // The support indices for the minimum-area box. + std::array mSupportIndices; + + // The area of the minimum-area box. The ComputeType value is exact, + // so the only rounding errors occur in the conversion from ComputeType + // to InputType (default rounding mode is round-to-nearest-ties-to-even). + InputType mArea; + + // Convenient values that occur regularly in the code. When using + // rational ComputeType, we construct these numbers only once. + ComputeType mZero, mOne, mNegOne, mHalf; +}; + + +template +MinimumAreaBox2::MinimumAreaBox2() + : + mNumPoints(0), + mPoints(nullptr), + mArea((InputType)0), + mZero(0), + mOne(1), + mNegOne(-1), + mHalf((InputType)0.5) +{ + mSupportIndices = { 0, 0, 0, 0 }; +} + +template +OrientedBox2 MinimumAreaBox2::operator()( + int numPoints, Vector2 const* points, bool useRotatingCalipers) +{ + mNumPoints = numPoints; + mPoints = points; + mHull.clear(); + + // Get the convex hull of the points. + ConvexHull2 ch2; + ch2(mNumPoints, mPoints, (InputType)0); + int dimension = ch2.GetDimension(); + + OrientedBox2 minBox; + + if (dimension == 0) + { + // The points are all effectively the same (using fuzzy epsilon). + minBox.center = mPoints[0]; + minBox.axis[0] = Vector2::Unit(0); + minBox.axis[1] = Vector2::Unit(1); + minBox.extent[0] = (InputType)0; + minBox.extent[1] = (InputType)0; + mHull.resize(1); + mHull[0] = 0; + return minBox; + } + + if (dimension == 1) + { + // The points effectively lie on a line (using fuzzy epsilon). + // Determine the extreme t-values for the points represented as + // P = origin + t*direction. We know that 'origin' is an input + // vertex, so we can start both t-extremes at zero. + Line2 const& line = ch2.GetLine(); + InputType tmin = (InputType)0, tmax = (InputType)0; + int imin = 0, imax = 0; + for (int i = 0; i < mNumPoints; ++i) + { + Vector2 diff = mPoints[i] - line.origin; + InputType t = Dot(diff, line.direction); + if (t > tmax) + { + tmax = t; + imax = i; + } + else if (t < tmin) + { + tmin = t; + imin = i; + } + } + + minBox.center = line.origin + + ((InputType)0.5)*(tmin + tmax) * line.direction; + minBox.extent[0] = ((InputType)0.5)*(tmax - tmin); + minBox.extent[1] = (InputType)0; + minBox.axis[0] = line.direction; + minBox.axis[1] = -Perp(line.direction); + mHull.resize(2); + mHull[0] = imin; + mHull[1] = imax; + return minBox; + } + + mHull = ch2.GetHull(); + Vector2 const* queryPoints = ch2.GetQuery().GetVertices(); + std::vector> computePoints(mHull.size()); + for (size_t i = 0; i < mHull.size(); ++i) + { + computePoints[i] = queryPoints[mHull[i]]; + } + + RemoveCollinearPoints(computePoints); + + Box box; + if (useRotatingCalipers) + { + box = ComputeBoxForEdgeOrderN(computePoints); + } + else + { + box = ComputeBoxForEdgeOrderNSqr(computePoints); + } + + ConvertTo(box, computePoints, minBox); + return minBox; +} + +template +OrientedBox2 MinimumAreaBox2::operator()( + int numPoints, Vector2 const* points, int numIndices, + int const* indices, bool useRotatingCalipers) +{ + mHull.clear(); + + OrientedBox2 minBox; + + if (numPoints < 3 || !points || (indices && numIndices < 3)) + { + minBox.center = Vector2::Zero(); + minBox.axis[0] = Vector2::Unit(0); + minBox.axis[1] = Vector2::Unit(1); + minBox.extent = Vector2::Zero(); + return minBox; + } + + if (indices) + { + mHull.resize(numIndices); + std::copy(indices, indices + numIndices, mHull.begin()); + } + else + { + numIndices = numPoints; + mHull.resize(numIndices); + for (int i = 0; i < numIndices; ++i) + { + mHull[i] = i; + } + } + + std::vector> computePoints(numIndices); + for (int i = 0; i < numIndices; ++i) + { + int h = mHull[i]; + computePoints[i][0] = (ComputeType)points[h][0]; + computePoints[i][1] = (ComputeType)points[h][1]; + } + + RemoveCollinearPoints(computePoints); + + Box box; + if (useRotatingCalipers) + { + box = ComputeBoxForEdgeOrderN(computePoints); + } + else + { + box = ComputeBoxForEdgeOrderNSqr(computePoints); + } + + ConvertTo(box, computePoints, minBox); + return minBox; +} + +template inline +int MinimumAreaBox2::GetNumPoints() const +{ + return mNumPoints; +} + +template inline +Vector2 const* MinimumAreaBox2::GetPoints() + const +{ + return mPoints; +} + +template inline +std::vector const& +MinimumAreaBox2::GetHull() const +{ + return mHull; +} + +template inline +std::array const& +MinimumAreaBox2::GetSupportIndices() const +{ + return mSupportIndices; +} + +template inline +InputType MinimumAreaBox2::GetArea() const +{ + return mArea; +} + +template +void MinimumAreaBox2::RemoveCollinearPoints( + std::vector>& vertices) +{ + std::vector> tmpVertices = vertices; + + int const numVertices = static_cast(vertices.size()); + int numNoncollinear = 0; + Vector2 ePrev = tmpVertices[0] - tmpVertices.back(); + for (int i0 = 0, i1 = 1; i0 < numVertices; ++i0) + { + Vector2 eNext = tmpVertices[i1] - tmpVertices[i0]; + + ComputeType dp = DotPerp(ePrev, eNext); + if (dp != mZero) + { + vertices[numNoncollinear++] = tmpVertices[i0]; + } + + ePrev = eNext; + if (++i1 == numVertices) + { + i1 = 0; + } + } + + vertices.resize(numNoncollinear); +} + +template +typename MinimumAreaBox2::Box +MinimumAreaBox2::ComputeBoxForEdgeOrderNSqr( + std::vector> const& vertices) +{ + Box minBox; + minBox.area = mNegOne; + int const numIndices = static_cast(vertices.size()); + for (int i0 = numIndices - 1, i1 = 0; i1 < numIndices; i0 = i1++) + { + Box box = SmallestBox(i0, i1, vertices); + if (minBox.area == mNegOne || box.area < minBox.area) + { + minBox = box; + } + } + return minBox; +} + +template +typename MinimumAreaBox2::Box +MinimumAreaBox2::ComputeBoxForEdgeOrderN( + std::vector> const& vertices) +{ + // The inputs are assumed to be the vertices of a convex polygon that + // is counterclockwise ordered. The input points must not contain three + // consecutive collinear points. + + // When the bounding box corresponding to a polygon edge is computed, + // we mark the edge as visited. If the edge is encountered later, the + // algorithm terminates. + std::vector visited(vertices.size()); + std::fill(visited.begin(), visited.end(), false); + + // Start the minimum-area rectangle search with the edge from the last + // polygon vertex to the first. When updating the extremes, we want the + // bottom-most point on the left edge, the top-most point on the right + // edge, the left-most point on the top edge, and the right-most point + // on the bottom edge. The polygon edges starting at these points are + // then guaranteed not to coincide with a box edge except when an extreme + // point is shared by two box edges (at a corner). + Box minBox = SmallestBox((int)vertices.size() - 1, 0, vertices); + visited[minBox.index[0]] = true; + + // Execute the rotating calipers algorithm. + Box box = minBox; + for (size_t i = 0; i < vertices.size(); ++i) + { + std::array, 4> A; + int numA; + if (!ComputeAngles(vertices, box, A, numA)) + { + // The polygon is a rectangle, so the search is over. + break; + } + + // Indirectly sort the A-array. + std::array sort = SortAngles(A, numA); + + // Update the supporting indices (box.index[]) and the box axis + // directions (box.U[]). + if (!UpdateSupport(A, numA, sort, vertices, visited, box)) + { + // We have already processed the box polygon edge, so the search + // is over. + break; + } + + if (box.area < minBox.area) + { + minBox = box; + } + } + + return minBox; +} + +template +typename MinimumAreaBox2::Box +MinimumAreaBox2::SmallestBox(int i0, int i1, + std::vector> const& vertices) +{ + Box box; + box.U[0] = vertices[i1] - vertices[i0]; + box.U[1] = -Perp(box.U[0]); + box.index = { i1, i1, i1, i1 }; + box.sqrLenU0 = Dot(box.U[0], box.U[0]); + + Vector2 const& origin = vertices[i1]; + Vector2 support[4]; + for (int j = 0; j < 4; ++j) + { + support[j] = { mZero, mZero }; + } + + int i = 0; + for (auto const& vertex : vertices) + { + Vector2 diff = vertex - origin; + Vector2 v = { Dot(box.U[0], diff), Dot(box.U[1], diff) }; + + // The right-most vertex of the bottom edge is vertices[i1]. The + // assumption of no triple of collinear vertices guarantees that + // box.index[0] is i1, which is the initial value assigned at the + // beginning of this function. Therefore, there is no need to test + // for other vertices farther to the right than vertices[i1]. + + if (v[0] > support[1][0] || + (v[0] == support[1][0] && v[1] > support[1][1])) + { + // New right maximum OR same right maximum but closer to top. + box.index[1] = i; + support[1] = v; + } + + if (v[1] > support[2][1] || + (v[1] == support[2][1] && v[0] < support[2][0])) + { + // New top maximum OR same top maximum but closer to left. + box.index[2] = i; + support[2] = v; + } + + if (v[0] < support[3][0] || + (v[0] == support[3][0] && v[1] < support[3][1])) + { + // New left minimum OR same left minimum but closer to bottom. + box.index[3] = i; + support[3] = v; + } + + ++i; + } + + // The comment in the loop has the implication that support[0] = { 0, 0 }, + // so the scaled height (support[2][1] - support[0][1]) is simply + // support[2][1]. + ComputeType scaledWidth = support[1][0] - support[3][0]; + ComputeType scaledHeight = support[2][1]; + box.area = scaledWidth * scaledHeight / box.sqrLenU0; + return box; +} + +template +bool MinimumAreaBox2::ComputeAngles( + std::vector> const& vertices, Box const& box, + std::array, 4>& A, int& numA) const +{ + int const numVertices = static_cast(vertices.size()); + numA = 0; + for (int k0 = 3, k1 = 0; k1 < 4; k0 = k1++) + { + if (box.index[k0] != box.index[k1]) + { + // The box edges are ordered in k1 as U[0], U[1], -U[0], -U[1]. + Vector2 D = + ((k0 & 2) ? -box.U[k0 & 1] : box.U[k0 & 1]); + int j0 = box.index[k0], j1 = j0 + 1; + if (j1 == numVertices) + { + j1 = 0; + } + Vector2 E = vertices[j1] - vertices[j0]; + ComputeType dp = DotPerp(D, E); + ComputeType esqrlen = Dot(E, E); + ComputeType sinThetaSqr = (dp * dp) / esqrlen; + A[numA++] = std::make_pair(sinThetaSqr, k0); + } + } + return numA > 0; +} + +template +std::array MinimumAreaBox2::SortAngles( + std::array, 4> const& A, int numA) const +{ + std::array sort = {{ 0, 1, 2, 3 }}; + if (numA > 1) + { + if (numA == 2) + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + } + else if (numA == 3) + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + if (A[sort[0]].first > A[sort[2]].first) + { + std::swap(sort[0], sort[2]); + } + if (A[sort[1]].first > A[sort[2]].first) + { + std::swap(sort[1], sort[2]); + } + } + else // numA == 4 + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + if (A[sort[2]].first > A[sort[3]].first) + { + std::swap(sort[2], sort[3]); + } + if (A[sort[0]].first > A[sort[2]].first) + { + std::swap(sort[0], sort[2]); + } + if (A[sort[1]].first > A[sort[3]].first) + { + std::swap(sort[1], sort[3]); + } + if (A[sort[1]].first > A[sort[2]].first) + { + std::swap(sort[1], sort[2]); + } + } + } + return sort; +} + +template +bool MinimumAreaBox2::UpdateSupport( + std::array, 4> const& A, int numA, + std::array const& sort, + std::vector> const& vertices, + std::vector& visited, Box& box) +{ + // Replace the support vertices of those edges attaining minimum angle + // with the other endpoints of the edges. + int const numVertices = static_cast(vertices.size()); + auto const& amin = A[sort[0]]; + for (int k = 0; k < numA; ++k) + { + auto const& a = A[sort[k]]; + if (a.first == amin.first) + { + if (++box.index[a.second] == numVertices) + { + box.index[a.second] = 0; + } + } + else + { + break; + } + } + + int bottom = box.index[amin.second]; + if (visited[bottom]) + { + // We have already processed this polygon edge. + return false; + } + visited[bottom] = true; + + // Cycle the vertices so that the bottom support occurs first. + std::array nextIndex; + for (int k = 0; k < 4; ++k) + { + nextIndex[k] = box.index[(amin.second + k) % 4]; + } + box.index = nextIndex; + + // Compute the box axis directions. + int j1 = box.index[0], j0 = j1 - 1; + if (j0 < 0) + { + j0 = numVertices - 1; + } + box.U[0] = vertices[j1] - vertices[j0]; + box.U[1] = -Perp(box.U[0]); + box.sqrLenU0 = Dot(box.U[0], box.U[0]); + + // Compute the box area. + Vector2 diff[2] = + { + vertices[box.index[1]] - vertices[box.index[3]], + vertices[box.index[2]] - vertices[box.index[0]] + }; + box.area = Dot(box.U[0], diff[0]) * Dot(box.U[1], diff[1]) / box.sqrLenU0; + return true; +} + +template +void MinimumAreaBox2::ConvertTo(Box const& minBox, + std::vector> const& computePoints, + OrientedBox2& itMinBox) +{ + // The sum, difference, and center are all computed exactly. + Vector2 sum[2] = + { + computePoints[minBox.index[1]] + computePoints[minBox.index[3]], + computePoints[minBox.index[2]] + computePoints[minBox.index[0]] + }; + + Vector2 difference[2] = + { + computePoints[minBox.index[1]] - computePoints[minBox.index[3]], + computePoints[minBox.index[2]] - computePoints[minBox.index[0]] + }; + + Vector2 center = mHalf * ( + Dot(minBox.U[0], sum[0]) * minBox.U[0] + + Dot(minBox.U[1], sum[1]) * minBox.U[1]) / minBox.sqrLenU0; + + // Calculate the squared extent using ComputeType to avoid loss of + // precision before computing a squared root. + Vector2 sqrExtent; + for (int i = 0; i < 2; ++i) + { + sqrExtent[i] = mHalf * Dot(minBox.U[i], difference[i]); + sqrExtent[i] *= sqrExtent[i]; + sqrExtent[i] /= minBox.sqrLenU0; + } + + for (int i = 0; i < 2; ++i) + { + itMinBox.center[i] = (InputType)center[i]; + itMinBox.extent[i] = std::sqrt((InputType)sqrExtent[i]); + + // Before converting to floating-point, factor out the maximum + // component using ComputeType to generate rational numbers in a + // range that avoids loss of precision during the conversion and + // normalization. + Vector2 const& axis = minBox.U[i]; + ComputeType cmax = std::max(std::abs(axis[0]), std::abs(axis[1])); + ComputeType invCMax = mOne / cmax; + for (int j = 0; j < 2; ++j) + { + itMinBox.axis[i][j] = (InputType)(axis[j] * invCMax); + } + Normalize(itMinBox.axis[i]); + } + + mSupportIndices = minBox.index; + mArea = (InputType)minBox.area; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumAreaCircle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumAreaCircle2.h new file mode 100644 index 000000000000..1df9fdf190f2 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumAreaCircle2.h @@ -0,0 +1,445 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/28) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// Compute the minimum area circle containing the input set of points. The +// algorithm randomly permutes the input points so that the construction +// occurs in 'expected' O(N) time. All internal minimal circle calculations +// store the squared radius in the radius member of Circle2. Only at the +// end is a sqrt computed. +// +// The most robust choice for ComputeType is BSRational for exact rational +// arithmetic. As long as this code is a correct implementation of the theory +// (which I hope it is), you will obtain the minimum-area circle containing +// the points. +// +// Instead, if you choose ComputeType to be float or double, floating-point +// rounding errors can cause the UpdateSupport{2,3} functions to fail. +// The failure is trapped in those functions and a simple bounding circle is +// computed using GetContainer in file GteContCircle2.h. This circle is +// generally not the minimum-area circle containing the points. The +// minimum-area algorithm is terminated immediately. The circle is returned +// as well as a bool value of 'true' when the circle is minimum area or +// 'false' when the failure is trapped. When 'false' is returned, you can +// try another call to the operator()(...) function. The random shuffle +// that occurs is highly likely to be different from the previous shuffle, +// and there is a chance that the algorithm can succeed just because of the +// different ordering of points. + +namespace gte +{ + template + class MinimumAreaCircle2 + { + public: + bool operator()(int numPoints, Vector2 const* points, Circle2& minimal) + { + if (numPoints >= 1 && points) + { + // Function array to avoid switch statement in the main loop. + std::function update[4]; + update[1] = [this](int i) { return UpdateSupport1(i); }; + update[2] = [this](int i) { return UpdateSupport2(i); }; + update[3] = [this](int i) { return UpdateSupport3(i); }; + + // Process only the unique points. + std::vector permuted(numPoints); + for (int i = 0; i < numPoints; ++i) + { + permuted[i] = i; + } + std::sort(permuted.begin(), permuted.end(), + [points](int i0, int i1) { return points[i0] < points[i1]; }); + auto end = std::unique(permuted.begin(), permuted.end(), + [points](int i0, int i1) { return points[i0] == points[i1]; }); + permuted.erase(end, permuted.end()); + numPoints = static_cast(permuted.size()); + + // Create a random permutation of the points. + std::shuffle(permuted.begin(), permuted.end(), mDRE); + + // Convert to the compute type, which is a simple copy when + // ComputeType is the same as InputType. + mComputePoints.resize(numPoints); + for (int i = 0; i < numPoints; ++i) + { + for (int j = 0; j < 2; ++j) + { + mComputePoints[i][j] = points[permuted[i]][j]; + } + } + + // Start with the first point. + Circle2 ctMinimal = ExactCircle1(0); + mNumSupport = 1; + mSupport[0] = 0; + + // The loop restarts from the beginning of the point list each + // time the circle needs updating. Linus Kallberg (Computer + // Science at Malardalen University in Sweden) discovered that + // performance is better when the remaining points in the + // array are processed before restarting. The points + // processed before the point that caused the update are + // likely to be enclosed by the new circle (or near the circle + // boundary) because they were enclosed by the previous + // circle. The chances are better that points after the + // current one will cause growth of the bounding circle. + for (int i = 1 % numPoints, n = 0; i != n; i = (i + 1) % numPoints) + { + if (!SupportContains(i)) + { + if (!Contains(i, ctMinimal)) + { + auto result = update[mNumSupport](i); + if (result.second == true) + { + if (result.first.radius > ctMinimal.radius) + { + ctMinimal = result.first; + n = i; + } + } + else + { + // This case can happen when ComputeType is + // float or double. See the comments at the + // beginning of this file. + LogWarning("ComputeType is not exact and failure occurred. Returning non-minimal circle."); + GetContainer(numPoints, points, minimal); + mNumSupport = 0; + mSupport.fill(0); + return false; + } + } + } + } + + for (int j = 0; j < 2; ++j) + { + minimal.center[j] = static_cast(ctMinimal.center[j]); + } + minimal.radius = static_cast(ctMinimal.radius); + minimal.radius = std::sqrt(minimal.radius); + + for (int i = 0; i < mNumSupport; ++i) + { + mSupport[i] = permuted[mSupport[i]]; + } + return true; + } + else + { + LogError("Input must contain points."); + minimal.center = Vector2::Zero(); + minimal.radius = std::numeric_limits::max(); + return false; + } + } + + // Member access. + inline int GetNumSupport() const + { + return mNumSupport; + } + + inline std::array const& GetSupport() const + { + return mSupport; + } + + private: + // Test whether point P is inside circle C using squared distance and + // squared radius. + bool Contains(int i, Circle2 const& circle) const + { + // NOTE: In this algorithm, circle.radius is the *squared radius* + // until the function returns at which time a square root is + // applied. + Vector2 diff = mComputePoints[i] - circle.center; + return Dot(diff, diff) <= circle.radius; + } + + Circle2 ExactCircle1(int i0) const + { + Circle2 minimal; + minimal.center = mComputePoints[i0]; + minimal.radius = (ComputeType)0; + return minimal; + } + + Circle2 ExactCircle2(int i0, int i1) const + { + Vector2 const& P0 = mComputePoints[i0]; + Vector2 const& P1 = mComputePoints[i1]; + Vector2 diff = P1 - P0; + Circle2 minimal; + minimal.center = ((ComputeType)0.5)*(P0 + P1); + minimal.radius = ((ComputeType)0.25)*Dot(diff, diff); + return minimal; + } + + Circle2 ExactCircle3(int i0, int i1, int i2) const + { + // Compute the 2D circle containing P0, P1, and P2. The center in + // barycentric coordinates is C = x0*P0 + x1*P1 + x2*P2, where + // x0 + x1 + x2 = 1. The center is equidistant from the three + // points, so |C - P0| = |C - P1| = |C - P2| = R, where R is the + // radius of the circle. From these conditions, + // C - P0 = x0*E0 + x1*E1 - E0 + // C - P1 = x0*E0 + x1*E1 - E1 + // C - P2 = x0*E0 + x1*E1 + // where E0 = P0 - P2 and E1 = P1 - P2, which leads to + // r^2 = |x0*E0 + x1*E1|^2 - 2*Dot(E0, x0*E0 + x1*E1) + |E0|^2 + // r^2 = |x0*E0 + x1*E1|^2 - 2*Dot(E1, x0*E0 + x1*E1) + |E1|^2 + // r^2 = |x0*E0 + x1*E1|^2 + // Subtracting the last equation from the first two and writing + // the equations as a linear system, + // + // +- -++ -+ +- -+ + // | Dot(E0,E0) Dot(E0,E1) || x0 | = 0.5 | Dot(E0,E0) | + // | Dot(E1,E0) Dot(E1,E1) || x1 | | Dot(E1,E1) | + // +- -++ -+ +- -+ + // + // The following code solves this system for x0 and x1 and then + // evaluates the third equation in r^2 to obtain r. + + Vector2 const& P0 = mComputePoints[i0]; + Vector2 const& P1 = mComputePoints[i1]; + Vector2 const& P2 = mComputePoints[i2]; + + Vector2 E0 = P0 - P2; + Vector2 E1 = P1 - P2; + + Matrix2x2 A; + A(0, 0) = Dot(E0, E0); + A(0, 1) = Dot(E0, E1); + A(1, 0) = A(0, 1); + A(1, 1) = Dot(E1, E1); + + ComputeType const half = (ComputeType)0.5; + Vector2 B{ half * A(0, 0), half* A(1, 1) }; + + Circle2 minimal; + Vector2 X; + if (LinearSystem::Solve(A, B, X)) + { + ComputeType x2 = (ComputeType)1 - X[0] - X[1]; + minimal.center = X[0] * P0 + X[1] * P1 + x2 * P2; + Vector2 tmp = X[0] * E0 + X[1] * E1; + minimal.radius = Dot(tmp, tmp); + } + else + { + minimal.center = Vector2::Zero(); + minimal.radius = (ComputeType)std::numeric_limits::max(); + } + + return minimal; + } + + typedef std::pair, bool> UpdateResult; + + UpdateResult UpdateSupport1(int i) + { + Circle2 minimal = ExactCircle2(mSupport[0], i); + mNumSupport = 2; + mSupport[1] = i; + return std::make_pair(minimal, true); + } + + UpdateResult UpdateSupport2(int i) + { + // Permutations of type 2, used for calling ExactCircle2(...). + int const numType2 = 2; + int const type2[numType2][2] = + { + { 0, /*2*/ 1 }, + { 1, /*2*/ 0 } + }; + + // Permutations of type 3, used for calling ExactCircle3(...). + int const numType3 = 1; // {0, 1, 2} + + Circle2 circle[numType2 + numType3]; + ComputeType minRSqr = (ComputeType)std::numeric_limits::max(); + int iCircle = 0, iMinRSqr = -1; + int k0, k1; + + // Permutations of type 2. + for (int j = 0; j < numType2; ++j, ++iCircle) + { + k0 = mSupport[type2[j][0]]; + circle[iCircle] = ExactCircle2(k0, i); + if (circle[iCircle].radius < minRSqr) + { + k1 = mSupport[type2[j][1]]; + if (Contains(k1, circle[iCircle])) + { + minRSqr = circle[iCircle].radius; + iMinRSqr = iCircle; + } + } + } + + // Permutations of type 3. + k0 = mSupport[0]; + k1 = mSupport[1]; + circle[iCircle] = ExactCircle3(k0, k1, i); + if (circle[iCircle].radius < minRSqr) + { + minRSqr = circle[iCircle].radius; + iMinRSqr = iCircle; + } + + switch (iMinRSqr) + { + case 0: + mSupport[1] = i; + break; + case 1: + mSupport[0] = i; + break; + case 2: + mNumSupport = 3; + mSupport[2] = i; + break; + case -1: + // For exact arithmetic, iMinRSqr >= 0, but for floating-point + // arithmetic, round-off errors can lead to iMinRSqr == -1. + // When this happens, use a simple bounding circle for the + // result and terminate the minimum-area algorithm. + return std::make_pair(Circle2(), false); + } + + return std::make_pair(circle[iMinRSqr], true); + } + + UpdateResult UpdateSupport3(int i) + { + // Permutations of type 2, used for calling ExactCircle2(...). + int const numType2 = 3; + int const type2[numType2][3] = + { + { 0, /*3*/ 1, 2 }, + { 1, /*3*/ 0, 2 }, + { 2, /*3*/ 0, 1 } + }; + + // Permutations of type 2, used for calling ExactCircle3(...). + int const numType3 = 3; + int const type3[numType3][3] = + { + { 0, 1, /*3*/ 2 }, + { 0, 2, /*3*/ 1 }, + { 1, 2, /*3*/ 0 } + }; + + Circle2 circle[numType2 + numType3]; + ComputeType minRSqr = (ComputeType)std::numeric_limits::max(); + int iCircle = 0, iMinRSqr = -1; + int k0, k1, k2; + + // Permutations of type 2. + for (int j = 0; j < numType2; ++j, ++iCircle) + { + k0 = mSupport[type2[j][0]]; + circle[iCircle] = ExactCircle2(k0, i); + if (circle[iCircle].radius < minRSqr) + { + k1 = mSupport[type2[j][1]]; + k2 = mSupport[type2[j][2]]; + if (Contains(k1, circle[iCircle]) && Contains(k2, circle[iCircle])) + { + minRSqr = circle[iCircle].radius; + iMinRSqr = iCircle; + } + } + } + + // Permutations of type 3. + for (int j = 0; j < numType3; ++j, ++iCircle) + { + k0 = mSupport[type3[j][0]]; + k1 = mSupport[type3[j][1]]; + circle[iCircle] = ExactCircle3(k0, k1, i); + if (circle[iCircle].radius < minRSqr) + { + k2 = mSupport[type3[j][2]]; + if (Contains(k2, circle[iCircle])) + { + minRSqr = circle[iCircle].radius; + iMinRSqr = iCircle; + } + } + } + + switch (iMinRSqr) + { + case 0: + mNumSupport = 2; + mSupport[1] = i; + break; + case 1: + mNumSupport = 2; + mSupport[0] = i; + break; + case 2: + mNumSupport = 2; + mSupport[0] = mSupport[2]; + mSupport[1] = i; + break; + case 3: + mSupport[2] = i; + break; + case 4: + mSupport[1] = i; + break; + case 5: + mSupport[0] = i; + break; + case -1: + // For exact arithmetic, iMinRSqr >= 0, but for floating-point + // arithmetic, round-off errors can lead to iMinRSqr == -1. + // When this happens, use a simple bounding circle for the + // result and terminate the minimum-area algorithm. + return std::make_pair(Circle2(), false); + } + + return std::make_pair(circle[iMinRSqr], true); + } + + // Indices of points that support the current minimum area circle. + bool SupportContains(int j) const + { + for (int i = 0; i < mNumSupport; ++i) + { + if (j == mSupport[i]) + { + return true; + } + } + return false; + } + + int mNumSupport; + std::array mSupport; + + // Random permutation of the unique input points to produce expected + // linear time for the algorithm. + std::default_random_engine mDRE; + std::vector> mComputePoints; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumVolumeBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumVolumeBox3.h new file mode 100644 index 000000000000..a217f28df8f9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumVolumeBox3.h @@ -0,0 +1,1252 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Compute a minimum-volume oriented box containing the specified points. The +// algorithm is really about computing the minimum-volume box containing the +// convex hull of the points, so we must compute the convex hull or you must +// pass an already built hull to the code. +// +// The minimum-volume oriented box has a face coincident with a hull face +// or has three mutually orthogonal edges coincident with three hull edges +// that (of course) are mutually orthogonal. +// J.O'Rourke, "Finding minimal enclosing boxes", +// Internat. J. Comput. Inform. Sci., 14:183-199, 1985. +// +// A detailed description of the algorithm and implementation is found in +// the documents +// http://www.geometrictools.com/Documentation/MinimumVolumeBox.pdf +// http://www.geometrictools.com/Documentation/MinimumAreaRectangle.pdf +// +// NOTE: This algorithm guarantees a correct output only when ComputeType is +// an exact arithmetic type that supports division. In GTEngine, one such +// type is BSRational (arbitrary precision). Another such type +// is BSRational> (fixed precision), where N is chosen large +// enough for your input data sets. If you choose ComputeType to be 'float' +// or 'double', the output is not guaranteed to be correct. +// +// See GeometricTools/GTEngine/Samples/Geometrics/MinimumVolumeBox3 for an +// example of how to use the code. + +namespace gte +{ + +template +class MinimumVolumeBox3 +{ +public: + // The class is a functor to support computing the minimum-volume box of + // multiple data sets using the same class object. For multithreading + // in ProcessFaces, choose 'numThreads' subject to the constraints + // 1 <= numThreads <= std::thread::hardware_concurrency() + // To execute ProcessEdges in a thread separate from the main thrad, + // choose 'threadProcessEdges' to 'true'. + MinimumVolumeBox3(unsigned int numThreads = 1, bool threadProcessEdges = false); + + // The points are arbitrary, so we must compute the convex hull from + // them in order to compute the minimum-area box. The input parameters + // are necessary for using ConvexHull3. + OrientedBox3 operator()(int numPoints, Vector3 const* points, + bool useRotatingCalipers = !std::is_floating_point::value); + + // The points form a nondegenerate convex polyhedron. The indices input + // must be nonnull and specify the triangle faces. + OrientedBox3 operator()(int numPoints, Vector3 const* points, + int numIndices, int const* indices, + bool useRotatingCalipers = !std::is_floating_point::value); + + // Member access. + inline int GetNumPoints() const; + inline Vector3 const* GetPoints() const; + inline std::vector const& GetHull() const; + inline InputType GetVolume() const; + +private: + struct Box + { + Vector3 P, U[3]; + ComputeType sqrLenU[3], range[3][2], volume; + }; + + struct ExtrudeRectangle + { + Vector3 U[2]; + std::array index; + ComputeType sqrLenU[2], area; + }; + + // Compute the minimum-volume box relative to each hull face. + void ProcessFaces(ETManifoldMesh const& mesh, Box& minBox); + + // Compute the minimum-volume box for each triple of orthgonal hull edges. + void ProcessEdges(ETManifoldMesh const& mesh, Box& minBox); + + // Compute the minimum-volume box relative to a single hull face. + typedef ETManifoldMesh::Triangle Triangle; + void ProcessFace(std::shared_ptr const& supportTri, + std::vector> const& normal, + std::map, int> const& triNormalMap, + ETManifoldMesh::EMap const& emap, Box& localMinBox); + + // The rotating calipers algorithm has a loop invariant that requires + // the convex polygon not to have collinear points. Any such points + // must be removed first. The code is also executed for the O(n^2) + // algorithm to reduce the number of process edges. + void RemoveCollinearPoints(Vector3 const& N, std::vector& polyline); + + // This is the slow order O(n^2) search. + void ComputeBoxForFaceOrderNSqr(Vector3 const& N, std::vector const& polyline, Box& box); + + // This is the rotating calipers version, which is O(n). + void ComputeBoxForFaceOrderN(Vector3 const& N, std::vector const& polyline, Box& box); + + // Compute the smallest rectangle for the polyline edge . + ExtrudeRectangle SmallestRectangle(int i0, int i1, Vector3 const& N, std::vector const& polyline); + + // Compute (sin(angle))^2 for the polyline edges emanating from the + // support vertices of the rectangle. The return value is 'true' if at + // least one angle is in [0,pi/2); otherwise, the return value is 'false' + // and the original polyline must project to a rectangle. + bool ComputeAngles(Vector3 const& N, + std::vector const& polyline, ExtrudeRectangle const& rct, + std::array, 4>& A, int& numA) const; + + // Sort the angles indirectly. The sorted indices are returned. This + // avoids swapping elements of A[], which can be expensive when + // ComputeType is an exact rational type. + std::array SortAngles(std::array, 4> const& A, int numA) const; + + bool UpdateSupport(std::array, 4> const& A, int numA, + std::array const& sort, Vector3 const& N, std::vector const& polyline, + std::vector& visited, ExtrudeRectangle& rct); + + // Convert the extruded box to the minimum-volume box of InputType. When + // the ComputeType is an exact rational type, the conversions are + // performed to avoid precision loss until necessary at the last step. + void ConvertTo(Box const& minBox, OrientedBox3& itMinBox); + + // The code is multithreaded, both for convex hull computation and + // computing minimum-volume extruded boxes for the hull faces. The + // default value is 1, which implies a single-threaded computation (on + // the main thread). + unsigned int mNumThreads; + bool mThreadProcessEdges; + + // The input points to be bound. + int mNumPoints; + Vector3 const* mPoints; + + // The ComputeType conversions of the input points. Only points of the + // convex hull (vertices of a convex polyhedron) are converted for + // performance when ComputeType is rational. + Vector3 const* mComputePoints; + + // The indices into mPoints/mComputePoints for the convex hull vertices. + std::vector mHull; + + // The unique indices in mHull. This set allows us to compute only for + // the hull vertices and avoids redundant computations if the indices + // were to have repeated indices into mPoints/mComputePoints. This is + // a performance improvement for rational ComputeType. + std::set mUniqueIndices; + + // The caller can specify whether to use rotating calipers or the slower + // all-edge processing for computing an extruded bounding box. + bool mUseRotatingCalipers; + + // The volume of the minimum-volume box. The ComputeType value is exact, + // so the only rounding errors occur in the conversion from ComputeType + // to InputType (default rounding mode is round-to-nearest-ties-to-even). + InputType mVolume; + + // Convenient values that occur regularly in the code. When using + // rational ComputeType, we construct these numbers only once. + ComputeType mZero, mOne, mNegOne, mHalf; +}; + + +template +MinimumVolumeBox3::MinimumVolumeBox3(unsigned int numThreads, bool threadProcessEdges) + : + mNumThreads(numThreads), + mThreadProcessEdges(threadProcessEdges), + mNumPoints(0), + mPoints(nullptr), + mComputePoints(nullptr), + mUseRotatingCalipers(true), + mVolume((InputType)0), + mZero(0), + mOne(1), + mNegOne(-1), + mHalf((InputType)0.5) +{ +} + +template +OrientedBox3 MinimumVolumeBox3::operator()( + int numPoints, Vector3 const* points, bool useRotatingCalipers) +{ + mNumPoints = numPoints; + mPoints = points; + mUseRotatingCalipers = useRotatingCalipers; + mHull.clear(); + mUniqueIndices.clear(); + + // Get the convex hull of the points. + ConvexHull3 ch3; + ch3(mNumPoints, mPoints, (InputType)0); + int dimension = ch3.GetDimension(); + + OrientedBox3 itMinBox; + + if (dimension == 0) + { + // The points are all effectively the same (using fuzzy epsilon). + itMinBox.center = mPoints[0]; + itMinBox.axis[0] = Vector3::Unit(0); + itMinBox.axis[1] = Vector3::Unit(1); + itMinBox.axis[2] = Vector3::Unit(2); + itMinBox.extent[0] = (InputType)0; + itMinBox.extent[1] = (InputType)0; + itMinBox.extent[2] = (InputType)0; + mHull.resize(1); + mHull[0] = 0; + return itMinBox; + } + + if (dimension == 1) + { + // The points effectively lie on a line (using fuzzy epsilon). + // Determine the extreme t-values for the points represented as + // P = origin + t*direction. We know that 'origin' is an input + // vertex, so we can start both t-extremes at zero. + Line3 const& line = ch3.GetLine(); + InputType tmin = (InputType)0, tmax = (InputType)0; + int imin = 0, imax = 0; + for (int i = 0; i < mNumPoints; ++i) + { + Vector3 diff = mPoints[i] - line.origin; + InputType t = Dot(diff, line.direction); + if (t > tmax) + { + tmax = t; + imax = i; + } + else if (t < tmin) + { + tmin = t; + imin = i; + } + } + + itMinBox.center = line.origin + ((InputType)0.5)*(tmin + tmax) * line.direction; + itMinBox.extent[0] = ((InputType)0.5)*(tmax - tmin); + itMinBox.extent[1] = (InputType)0; + itMinBox.extent[2] = (InputType)0; + itMinBox.axis[0] = line.direction; + ComputeOrthogonalComplement(1, &itMinBox.axis[0]); + mHull.resize(2); + mHull[0] = imin; + mHull[1] = imax; + return itMinBox; + } + + if (dimension == 2) + { + // The points effectively line on a plane (using fuzzy epsilon). + // Project the points onto the plane and compute the minimum-area + // bounding box of them. + Plane3 const& plane = ch3.GetPlane(); + + // Get a coordinate system relative to the plane of the points. + // Choose the origin to be any of the input points. + Vector3 origin = mPoints[0]; + Vector3 basis[3]; + basis[0] = plane.normal; + ComputeOrthogonalComplement(1, basis); + + // Project the input points onto the plane. + std::vector> projection(mNumPoints); + for (int i = 0; i < mNumPoints; ++i) + { + Vector3 diff = mPoints[i] - origin; + projection[i][0] = Dot(basis[1], diff); + projection[i][1] = Dot(basis[2], diff); + } + + // Compute the minimum area box in 2D. + MinimumAreaBox2 mab2; + OrientedBox2 rectangle = mab2(mNumPoints, &projection[0]); + + // Lift the values into 3D. + itMinBox.center = origin + rectangle.center[0] * basis[1] + rectangle.center[1] * basis[2]; + itMinBox.axis[0] = rectangle.axis[0][0] * basis[1] + rectangle.axis[0][1] * basis[2]; + itMinBox.axis[1] = rectangle.axis[1][0] * basis[1] + rectangle.axis[1][1] * basis[2]; + itMinBox.axis[2] = basis[0]; + itMinBox.extent[0] = rectangle.extent[0]; + itMinBox.extent[1] = rectangle.extent[1]; + itMinBox.extent[2] = (InputType)0; + mHull = mab2.GetHull(); + return itMinBox; + } + + // Get the set of unique indices of the hull. This is used to project + // hull vertices onto lines. + ETManifoldMesh const& mesh = ch3.GetHullMesh(); + mHull.resize(3 * mesh.GetTriangles().size()); + int h = 0; + for (auto const& element : mesh.GetTriangles()) + { + for (int i = 0; i < 3; ++i, ++h) + { + int index = element.first.V[i]; + mHull[h] = index; + mUniqueIndices.insert(index); + } + } + + mComputePoints = ch3.GetQuery().GetVertices(); + + Box minBox, minBoxEdges; + minBox.volume = mNegOne; + minBoxEdges.volume = mNegOne; + + if (mThreadProcessEdges) + { + std::thread doEdges([this, &mesh, &minBoxEdges]() + { + ProcessEdges(mesh, minBoxEdges); + }); + ProcessFaces(mesh, minBox); + doEdges.join(); + } + else + { + ProcessEdges(mesh, minBoxEdges); + ProcessFaces(mesh, minBox); + } + + if (minBoxEdges.volume != mNegOne + && minBoxEdges.volume < minBox.volume) + { + minBox = minBoxEdges; + } + + ConvertTo(minBox, itMinBox); + mComputePoints = nullptr; + return itMinBox; +} + +template +OrientedBox3 MinimumVolumeBox3::operator()( + int numPoints, Vector3 const* points, int numIndices, + int const* indices, bool useRotatingCalipers) +{ + mNumPoints = numPoints; + mPoints = points; + mUseRotatingCalipers = useRotatingCalipers; + mUniqueIndices.clear(); + + // Build the mesh from the indices. The box construction uses the edge + // map of the mesh. + ETManifoldMesh mesh; + int numTriangles = numIndices / 3; + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + mesh.Insert(v0, v1, v2); + } + + // Get the set of unique indices of the hull. This is used to project + // hull vertices onto lines. + mHull.resize(3 * mesh.GetTriangles().size()); + int h = 0; + for (auto const& element : mesh.GetTriangles()) + { + for (int i = 0; i < 3; ++i, ++h) + { + int index = element.first.V[i]; + mHull[h] = index; + mUniqueIndices.insert(index); + } + } + + // Create the ComputeType points to be used downstream. + std::vector> computePoints(mNumPoints); + for (auto i : mUniqueIndices) + { + for (int j = 0; j < 3; ++j) + { + computePoints[i][j] = (ComputeType)mPoints[i][j]; + } + } + + OrientedBox3 itMinBox; + mComputePoints = &computePoints[0]; + + Box minBox, minBoxEdges; + minBox.volume = mNegOne; + minBoxEdges.volume = mNegOne; + + if (mThreadProcessEdges) + { + std::thread doEdges([this, &mesh, &minBoxEdges]() + { + ProcessEdges(mesh, minBoxEdges); + }); + ProcessFaces(mesh, minBox); + doEdges.join(); + } + else + { + ProcessEdges(mesh, minBoxEdges); + ProcessFaces(mesh, minBox); + } + + if (minBoxEdges.volume != mNegOne && minBoxEdges.volume < minBox.volume) + { + minBox = minBoxEdges; + } + + ConvertTo(minBox, itMinBox); + mComputePoints = nullptr; + return itMinBox; +} + +template inline +int MinimumVolumeBox3::GetNumPoints() const +{ + return mNumPoints; +} + +template inline +Vector3 const* +MinimumVolumeBox3::GetPoints() const +{ + return mPoints; +} + +template inline +std::vector const& MinimumVolumeBox3::GetHull() + const +{ + return mHull; +} + +template inline +InputType MinimumVolumeBox3::GetVolume() const +{ + return mVolume; +} + +template +void MinimumVolumeBox3::ProcessFaces(ETManifoldMesh const& mesh, Box& minBox) +{ + // Get the mesh data structures. + auto const& tmap = mesh.GetTriangles(); + auto const& emap = mesh.GetEdges(); + + // Compute inner-pointing face normals for searching boxes supported by + // a face and an extreme vertex. The indirection in triNormalMap, using + // an integer index instead of the normal/sqrlength pair itself, avoids + // expensive copies when using exact arithmetic. + std::vector> normal(tmap.size()); + std::map, int> triNormalMap; + int index = 0; + for (auto const& element : tmap) + { + auto tri = element.second; + Vector3 const& v0 = mComputePoints[tri->V[0]]; + Vector3 const& v1 = mComputePoints[tri->V[1]]; + Vector3 const& v2 = mComputePoints[tri->V[2]]; + Vector3 edge1 = v1 - v0; + Vector3 edge2 = v2 - v0; + normal[index] = Cross(edge2, edge1); // inner-pointing normal + triNormalMap[tri] = index++; + } + + // Process the triangle faces. For each face, compute the polyline of + // edges that supports the bounding box with a face coincident to the + // triangle face. The projection of the polyline onto the plane of the + // triangle face is a convex polygon, so we can use the method of rotating + // calipers to compute its minimum-area box efficiently. + unsigned int numFaces = static_cast(tmap.size()); + if (mNumThreads > 1 && numFaces >= mNumThreads) + { + // Repackage the triangle pointers to support the partitioning of + // faces for multithreaded face processing. + std::vector> triangles; + triangles.reserve(numFaces); + for (auto const& element : tmap) + { + triangles.push_back(element.second); + } + + // Partition the data for multiple threads. + unsigned int numFacesPerThread = numFaces / mNumThreads; + std::vector imin(mNumThreads), imax(mNumThreads); + std::vector localMinBox(mNumThreads); + for (unsigned int t = 0; t < mNumThreads; ++t) + { + imin[t] = t * numFacesPerThread; + imax[t] = imin[t] + numFacesPerThread - 1; + localMinBox[t].volume = mNegOne; + } + imax[mNumThreads - 1] = numFaces - 1; + + // Execute the face processing in multiple threads. + std::vector process(mNumThreads); + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t] = std::thread([this, t, &imin, &imax, &triangles, + &normal, &triNormalMap, &emap, &localMinBox]() + { + for (unsigned int i = imin[t]; i <= imax[t]; ++i) + { + auto const& supportTri = triangles[i]; + ProcessFace(supportTri, normal, triNormalMap, emap, localMinBox[t]); + } + }); + } + + // Wait for all threads to finish. + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t].join(); + + // Update the minimum-volume box candidate. + if (minBox.volume == mNegOne || localMinBox[t].volume < minBox.volume) + { + minBox = localMinBox[t]; + } + } + } + else + { + for (auto const& element : tmap) + { + auto const& supportTri = element.second; + ProcessFace(supportTri, normal, triNormalMap, emap, minBox); + } + } +} + +template +void MinimumVolumeBox3::ProcessEdges(ETManifoldMesh const& mesh, Box& minBox) +{ + // The minimum-volume box can also be supported by three mutually + // orthogonal edges of the convex hull. For each triple of orthogonal + // edges, compute the minimum-volume box for that coordinate frame by + // projecting the points onto the axes of the frame. Use a hull vertex + // as the origin. + int index = mesh.GetTriangles().begin()->first.V[0]; + Vector3 const& origin = mComputePoints[index]; + Vector3 U[3]; + std::array sqrLenU; + + auto const& emap = mesh.GetEdges(); + auto e2 = emap.begin(), end = emap.end(); + for (/**/; e2 != end; ++e2) + { + U[2] = mComputePoints[e2->first.V[1]] - mComputePoints[e2->first.V[0]]; + auto e1 = e2; + for (++e1; e1 != end; ++e1) + { + U[1] = mComputePoints[e1->first.V[1]] - mComputePoints[e1->first.V[0]]; + if (Dot(U[1], U[2]) != mZero) + { + continue; + } + sqrLenU[1] = Dot(U[1], U[1]); + + auto e0 = e1; + for (++e0; e0 != end; ++e0) + { + U[0] = mComputePoints[e0->first.V[1]] - mComputePoints[e0->first.V[0]]; + sqrLenU[0] = Dot(U[0], U[0]); + if (Dot(U[0], U[1]) != mZero || Dot(U[0], U[2]) != mZero) + { + continue; + } + + // The three edges are mutually orthogonal. To support exact + // rational arithmetic for volume computation, we replace U[2] + // by a parallel vector. + U[2] = Cross(U[0], U[1]); + sqrLenU[2] = sqrLenU[0] * sqrLenU[1]; + + // Project the vertices onto the lines containing the edges. + // Use vertex 0 as the origin. + std::array umin, umax; + for (int j = 0; j < 3; ++j) + { + umin[j] = mZero; + umax[j] = mZero; + } + + for (auto i : mUniqueIndices) + { + Vector3 diff = mComputePoints[i] - origin; + for (int j = 0; j < 3; ++j) + { + ComputeType dot = Dot(diff, U[j]); + if (dot < umin[j]) + { + umin[j] = dot; + } + else if (dot > umax[j]) + { + umax[j] = dot; + } + } + } + + ComputeType volume = (umax[0] - umin[0]) / sqrLenU[0]; + volume *= (umax[1] - umin[1]) / sqrLenU[1]; + volume *= (umax[2] - umin[2]); + + // Update current minimum-volume box (if necessary). + if (minBox.volume == mOne || volume < minBox.volume) + { + // The edge keys have unordered vertices, so it is + // possible that {U[0],U[1],U[2]} is a left-handed set. + // We need a right-handed set. + if (DotCross(U[0], U[1], U[2]) < mZero) + { + U[2] = -U[2]; + } + + minBox.P = origin; + for (int j = 0; j < 3; ++j) + { + minBox.U[j] = U[j]; + minBox.sqrLenU[j] = sqrLenU[j]; + for (int k = 0; k < 3; ++k) + { + minBox.range[k][0] = umin[k]; + minBox.range[k][1] = umax[k]; + } + } + minBox.volume = volume; + + } + } + } + } +} + +template +void MinimumVolumeBox3::ProcessFace(std::shared_ptr const& supportTri, + std::vector> const& normal, std::map, int> const& triNormalMap, + ETManifoldMesh::EMap const& emap, Box& localMinBox) +{ + // Get the supporting triangle information. + Vector3 const& supportNormal = normal[triNormalMap.find(supportTri)->second]; + + // Build the polyline of supporting edges. The pair (v,polyline[v]) + // represents an edge directed appropriately (see next set of + // comments). + std::vector polyline(mNumPoints); + int polylineStart = -1; + for (auto const& edgeElement : emap) + { + auto const& edge = *edgeElement.second; + auto const& tri0 = edge.T[0].lock(); + auto const& tri1 = edge.T[1].lock(); + auto const& normal0 = normal[triNormalMap.find(tri0)->second]; + auto const& normal1 = normal[triNormalMap.find(tri1)->second]; + ComputeType dot0 = Dot(supportNormal, normal0); + ComputeType dot1 = Dot(supportNormal, normal1); + + std::shared_ptr tri; + if (dot0 < mZero && dot1 >= mZero) + { + tri = tri0; + } + else if (dot1 < mZero && dot0 >= mZero) + { + tri = tri1; + } + + if (tri) + { + // The edge supports the bounding box. Insert the edge in the + // list using clockwise order relative to tri. This will lead + // to a polyline whose projection onto the plane of the hull + // face is a convex polygon that is counterclockwise oriented. + for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++) + { + if (tri->V[j1] == edge.V[0]) + { + if (tri->V[j0] == edge.V[1]) + { + polyline[edge.V[1]] = edge.V[0]; + } + else + { + polyline[edge.V[0]] = edge.V[1]; + } + polylineStart = edge.V[0]; + break; + } + } + } + } + + // Rearrange the edges to form a closed polyline. For M vertices, each + // ComputeBoxFor*() function starts with the edge from closedPolyline[M-1] + // to closedPolyline[0]. + std::vector closedPolyline(mNumPoints); + int numClosedPolyline = 0; + int v = polylineStart; + for (auto& cp : closedPolyline) + { + cp = v; + ++numClosedPolyline; + v = polyline[v]; + if (v == polylineStart) + { + break; + } + } + closedPolyline.resize(numClosedPolyline); + + // This avoids redundant face testing in the O(n^2) or O(n) algorithms + // and it simplifies the O(n) implementation. + RemoveCollinearPoints(supportNormal, closedPolyline); + + // Compute the box coincident to the hull triangle that has minimum + // area on the face coincident with the triangle. + Box faceBox; + if (mUseRotatingCalipers) + { + ComputeBoxForFaceOrderN(supportNormal, closedPolyline, faceBox); + } + else + { + ComputeBoxForFaceOrderNSqr(supportNormal, closedPolyline, faceBox); + } + + // Update the minimum-volume box candidate. + if (localMinBox.volume == mNegOne || faceBox.volume < localMinBox.volume) + { + localMinBox = faceBox; + } +} + +template +void MinimumVolumeBox3::RemoveCollinearPoints( + Vector3 const& N, std::vector& polyline) +{ + std::vector tmpPolyline = polyline; + + int const numPolyline = static_cast(polyline.size()); + int numNoncollinear = 0; + Vector3 ePrev = + mComputePoints[tmpPolyline[0]] - mComputePoints[tmpPolyline.back()]; + + for (int i0 = 0, i1 = 1; i0 < numPolyline; ++i0) + { + Vector3 eNext = + mComputePoints[tmpPolyline[i1]] - mComputePoints[tmpPolyline[i0]]; + + ComputeType tsp = DotCross(ePrev, eNext, N); + if (tsp != mZero) + { + polyline[numNoncollinear++] = tmpPolyline[i0]; + } + + ePrev = eNext; + if (++i1 == numPolyline) + { + i1 = 0; + } + } + + polyline.resize(numNoncollinear); +} + +template +void MinimumVolumeBox3::ComputeBoxForFaceOrderNSqr( + Vector3 const& N, std::vector const& polyline, + Box& box) +{ + // This code processes the polyline terminator associated with a convex + // hull face of inner-pointing normal N. The polyline is usually not + // contained by a plane, and projecting the polyline to a convex polygon + // in a plane perpendicular to N introduces floating-point rounding + // errors. The minimum-area box for the projected polyline is computed + // indirectly to support exact rational arithmetic. + + box.P = mComputePoints[polyline[0]]; + box.U[2] = N; + box.sqrLenU[2] = Dot(N, N); + box.range[1][0] = mZero; + box.volume = mNegOne; + int const numPolyline = static_cast(polyline.size()); + for (int i0 = numPolyline - 1, i1 = 0; i1 < numPolyline; i0 = i1++) + { + // Create a coordinate system for the plane perpendicular to the face + // normal and containing a polyline vertex. + Vector3 const& P = mComputePoints[polyline[i0]]; + Vector3 E = + mComputePoints[polyline[i1]] - mComputePoints[polyline[i0]]; + + Vector3 U1 = Cross(N, E); + Vector3 U0 = Cross(U1, N); + + // Compute the smallest rectangle containing the projected polyline. + ComputeType min0 = mZero, max0 = mZero, max1 = mZero; + for (int j = 0; j < numPolyline; ++j) + { + Vector3 diff = mComputePoints[polyline[j]] - P; + ComputeType dot = Dot(U0, diff); + if (dot < min0) + { + min0 = dot; + } + else if (dot > max0) + { + max0 = dot; + } + + dot = Dot(U1, diff); + if (dot > max1) + { + max1 = dot; + } + } + + // The true area is Area(rectangle)*Length(N). After the smallest + // scaled-area rectangle is computed and returned, the box.volume is + // updated to be the actual squared volume of the box. + ComputeType sqrLenU1 = Dot(U1, U1); + ComputeType volume = (max0 - min0) * max1 / sqrLenU1; + if (box.volume == mNegOne || volume < box.volume) + { + box.P = P; + box.U[0] = U0; + box.U[1] = U1; + box.sqrLenU[0] = sqrLenU1 * box.sqrLenU[2]; + box.sqrLenU[1] = sqrLenU1; + box.range[0][0] = min0; + box.range[0][1] = max0; + box.range[1][1] = max1; + box.volume = volume; + } + } + + // Compute the range of points in the support-normal direction. + box.range[2][0] = mZero; + box.range[2][1] = mZero; + for (auto i : mUniqueIndices) + { + Vector3 diff = mComputePoints[i] - box.P; + ComputeType height = Dot(box.U[2], diff); + if (height < box.range[2][0]) + { + box.range[2][0] = height; + } + else if (height > box.range[2][1]) + { + box.range[2][1] = height; + } + } + + // Compute the actual volume. + box.volume *= (box.range[2][1] - box.range[2][0]) / box.sqrLenU[2]; +} + +template +void MinimumVolumeBox3::ComputeBoxForFaceOrderN( + Vector3 const& N, std::vector const& polyline, Box& box) +{ + // This code processes the polyline terminator associated with a convex + // hull face of inner-pointing normal N. The polyline is usually not + // contained by a plane, and projecting the polyline to a convex polygon + // in a plane perpendicular to N introduces floating-point rounding + // errors. The minimum-area box for the projected polyline is computed + // indirectly to support exact rational arithmetic. + + // When the bounding box corresponding to a polyline edge is computed, + // we mark the edge as visited. If the edge is encountered later, the + // algorithm terminates. + std::vector visited(polyline.size()); + std::fill(visited.begin(), visited.end(), false); + + // Start the minimum-area rectangle search with the edge from the last + // polyline vertex to the first. When updating the extremes, we want the + // bottom-most point on the left edge, the top-most point on the right + // edge, the left-most point on the top edge, and the right-most point + // on the bottom edge. The polygon edges starting at these points are + // then guaranteed not to coincide with a box edge except when an extreme + // point is shared by two box edges (at a corner). + ExtrudeRectangle minRct = SmallestRectangle((int)polyline.size() - 1, 0, + N, polyline); + visited[minRct.index[0]] = true; + + // Execute the rotating calipers algorithm. + ExtrudeRectangle rct = minRct; + for (size_t i = 0; i < polyline.size(); ++i) + { + std::array, 4> A; + int numA; + if (!ComputeAngles(N, polyline, rct, A, numA)) + { + // The polyline projects to a rectangle, so the search is over. + break; + } + + // Indirectly sort the A-array. + std::array sort = SortAngles(A, numA); + + // Update the supporting indices (rct.index[]) and the rectangle axis + // directions (rct.U[]). + if (!UpdateSupport(A, numA, sort, N, polyline, visited, rct)) + { + // We have already processed the rectangle polygon edge, so the + // search is over. + break; + } + + if (rct.area < minRct.area) + { + minRct = rct; + } + } + + // Store relevant box information for computing volume and converting to + // an InputType bounding box. + box.P = mComputePoints[polyline[minRct.index[0]]]; + box.U[0] = minRct.U[0]; + box.U[1] = minRct.U[1]; + box.U[2] = N; + box.sqrLenU[0] = minRct.sqrLenU[0]; + box.sqrLenU[1] = minRct.sqrLenU[1]; + box.sqrLenU[2] = Dot(box.U[2], box.U[2]); + + // Compute the range of points in the plane perpendicular to the support + // normal. + box.range[0][0] = Dot(box.U[0], + mComputePoints[polyline[minRct.index[3]]] - box.P); + box.range[0][1] = Dot(box.U[0], + mComputePoints[polyline[minRct.index[1]]] - box.P); + box.range[1][0] = mZero; + box.range[1][1] = Dot(box.U[1], + mComputePoints[polyline[minRct.index[2]]] - box.P); + + // Compute the range of points in the support-normal direction. + box.range[2][0] = mZero; + box.range[2][1] = mZero; + for (auto i : mUniqueIndices) + { + Vector3 diff = mComputePoints[i] - box.P; + ComputeType height = Dot(box.U[2], diff); + if (height < box.range[2][0]) + { + box.range[2][0] = height; + } + else if (height > box.range[2][1]) + { + box.range[2][1] = height; + } + } + + // Compute the actual volume. + box.volume = + (box.range[0][1] - box.range[0][0]) * + ((box.range[1][1] - box.range[1][0]) / box.sqrLenU[1]) * + ((box.range[2][1] - box.range[2][0]) / box.sqrLenU[2]); +} + +template +typename MinimumVolumeBox3::ExtrudeRectangle +MinimumVolumeBox3::SmallestRectangle(int i0, int i1, + Vector3 const& N, std::vector const& polyline) +{ + ExtrudeRectangle rct; + Vector3 E = + mComputePoints[polyline[i1]] - mComputePoints[polyline[i0]]; + rct.U[1] = Cross(N, E); + rct.U[0] = Cross(rct.U[1], N); + rct.index = { i1, i1, i1, i1 }; + rct.sqrLenU[0] = Dot(rct.U[0], rct.U[0]); + rct.sqrLenU[1] = Dot(rct.U[1], rct.U[1]); + + Vector3 const& origin = mComputePoints[polyline[i1]]; + Vector2 support[4]; + for (int j = 0; j < 4; ++j) + { + support[j] = { mZero, mZero }; + } + + int i = 0; + for (auto p : polyline) + { + Vector3 diff = mComputePoints[p] - origin; + Vector2 v = { Dot(rct.U[0], diff), Dot(rct.U[1], diff) }; + + // The right-most vertex of the bottom edge is vertices[i1]. The + // assumption of no triple of collinear vertices guarantees that + // box.index[0] is i1, which is the initial value assigned at the + // beginning of this function. Therefore, there is no need to test + // for other vertices farther to the right than vertices[i1]. + + if (v[0] > support[1][0] || + (v[0] == support[1][0] && v[1] > support[1][1])) + { + // New right maximum OR same right maximum but closer to top. + rct.index[1] = i; + support[1] = v; + } + + if (v[1] > support[2][1] || + (v[1] == support[2][1] && v[0] < support[2][0])) + { + // New top maximum OR same top maximum but closer to left. + rct.index[2] = i; + support[2] = v; + } + + if (v[0] < support[3][0] || + (v[0] == support[3][0] && v[1] < support[3][1])) + { + // New left minimum OR same left minimum but closer to bottom. + rct.index[3] = i; + support[3] = v; + } + + ++i; + } + + // The comment in the loop has the implication that support[0] = { 0, 0 }, + // so the scaled height (support[2][1] - support[0][1]) is simply + // support[2][1]. + ComputeType scaledWidth = support[1][0] - support[3][0]; + ComputeType scaledHeight = support[2][1]; + rct.area = scaledWidth * scaledHeight / rct.sqrLenU[1]; + return rct; +} + +template +bool MinimumVolumeBox3::ComputeAngles( + Vector3 const& N, std::vector const& polyline, + ExtrudeRectangle const& rct, + std::array, 4>& A, int& numA) const +{ + int const numPolyline = static_cast(polyline.size()); + numA = 0; + for (int k0 = 3, k1 = 0; k1 < 4; k0 = k1++) + { + if (rct.index[k0] != rct.index[k1]) + { + // The rct edges are ordered in k1 as U[0], U[1], -U[0], -U[1]. + int lookup = (k0 & 1); + Vector3 D = + ((k0 & 2) ? -rct.U[lookup] : rct.U[lookup]); + int j0 = rct.index[k0], j1 = j0 + 1; + if (j1 == numPolyline) + { + j1 = 0; + } + Vector3 E = + mComputePoints[polyline[j1]] - mComputePoints[polyline[j0]]; + Vector3 Eperp = Cross(N, E); + E = Cross(Eperp, N); + Vector3 DxE = Cross(D, E); + ComputeType csqrlen = Dot(DxE, DxE); + ComputeType dsqrlen = rct.sqrLenU[lookup]; + ComputeType esqrlen = Dot(E, E); + ComputeType sinThetaSqr = csqrlen / (dsqrlen * esqrlen); + A[numA++] = std::make_pair(sinThetaSqr, k0); + } + } + return numA > 0; +} + +template +std::array MinimumVolumeBox3::SortAngles( + std::array, 4> const& A, int numA) const +{ + std::array sort = {{ 0, 1, 2, 3 }}; + if (numA > 1) + { + if (numA == 2) + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + } + else if (numA == 3) + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + if (A[sort[0]].first > A[sort[2]].first) + { + std::swap(sort[0], sort[2]); + } + if (A[sort[1]].first > A[sort[2]].first) + { + std::swap(sort[1], sort[2]); + } + } + else // numA == 4 + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + if (A[sort[2]].first > A[sort[3]].first) + { + std::swap(sort[2], sort[3]); + } + if (A[sort[0]].first > A[sort[2]].first) + { + std::swap(sort[0], sort[2]); + } + if (A[sort[1]].first > A[sort[3]].first) + { + std::swap(sort[1], sort[3]); + } + if (A[sort[1]].first > A[sort[2]].first) + { + std::swap(sort[1], sort[2]); + } + } + } + return sort; +} + +template +bool MinimumVolumeBox3::UpdateSupport( + std::array, 4> const& A, int numA, + std::array const& sort, Vector3 const& N, + std::vector const& polyline, std::vector& visited, + ExtrudeRectangle& rct) +{ + // Replace the support vertices of those edges attaining minimum angle + // with the other endpoints of the edges. + int const numPolyline = static_cast(polyline.size()); + auto const& amin = A[sort[0]]; + for (int k = 0; k < numA; ++k) + { + auto const& a = A[sort[k]]; + if (a.first == amin.first) + { + if (++rct.index[a.second] == numPolyline) + { + rct.index[a.second] = 0; + } + } + else + { + break; + } + } + + int bottom = rct.index[amin.second]; + if (visited[bottom]) + { + // We have already processed this polyline edge. + return false; + } + visited[bottom] = true; + + // Cycle the vertices so that the bottom support occurs first. + std::array nextIndex; + for (int k = 0; k < 4; ++k) + { + nextIndex[k] = rct.index[(amin.second + k) % 4]; + } + rct.index = nextIndex; + + // Compute the rectangle axis directions. + int j1 = rct.index[0], j0 = j1 - 1; + if (j1 < 0) + { + j1 = numPolyline - 1; + } + Vector3 E = + mComputePoints[polyline[j1]] - mComputePoints[polyline[j0]]; + rct.U[1] = Cross(N, E); + rct.U[0] = Cross(rct.U[1], N); + rct.sqrLenU[0] = Dot(rct.U[0], rct.U[0]); + rct.sqrLenU[1] = Dot(rct.U[1], rct.U[1]); + + // Compute the rectangle area. + Vector3 diff[2] = + { + mComputePoints[polyline[rct.index[1]]] + - mComputePoints[polyline[rct.index[3]]], + mComputePoints[polyline[rct.index[2]]] + - mComputePoints[polyline[rct.index[0]]] + }; + ComputeType scaledWidth = Dot(rct.U[0], diff[0]); + ComputeType scaledHeight = Dot(rct.U[1], diff[1]); + rct.area = scaledWidth * scaledHeight / rct.sqrLenU[1]; + return true; +} + +template +void MinimumVolumeBox3::ConvertTo(Box const& minBox, + OrientedBox3& itMinBox) +{ + Vector3 center = minBox.P; + for (int i = 0; i < 3; ++i) + { + ComputeType average = + mHalf * (minBox.range[i][0] + minBox.range[i][1]); + center += (average / minBox.sqrLenU[i]) * minBox.U[i]; + } + + // Calculate the squared extent using ComputeType to avoid loss of + // precision before computing a squared root. + Vector3 sqrExtent; + for (int i = 0; i < 3; ++i) + { + sqrExtent[i] = mHalf * (minBox.range[i][1] - minBox.range[i][0]); + sqrExtent[i] *= sqrExtent[i]; + sqrExtent[i] /= minBox.sqrLenU[i]; + } + + for (int i = 0; i < 3; ++i) + { + itMinBox.center[i] = (InputType)center[i]; + itMinBox.extent[i] = std::sqrt((InputType)sqrExtent[i]); + + // Before converting to floating-point, factor out the maximum + // component using ComputeType to generate rational numbers in a + // range that avoids loss of precision during the conversion and + // normalization. + Vector3 const& axis = minBox.U[i]; + ComputeType cmax = std::max(std::abs(axis[0]), std::abs(axis[1])); + cmax = std::max(cmax, std::abs(axis[2])); + ComputeType invCMax = mOne / cmax; + for (int j = 0; j < 3; ++j) + { + itMinBox.axis[i][j] = (InputType)(axis[j] * invCMax); + } + Normalize(itMinBox.axis[i]); + } + + mVolume = (InputType)minBox.volume; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumVolumeSphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumVolumeSphere3.h new file mode 100644 index 000000000000..c6fa29710934 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumVolumeSphere3.h @@ -0,0 +1,695 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/28) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Compute the minimum volume sphere containing the input set of points. The +// algorithm randomly permutes the input points so that the construction +// occurs in 'expected' O(N) time. All internal minimal sphere calculations +// store the squared radius in the radius member of Sphere3. Only at +// the end is a sqrt computed. +// +// The most robust choice for ComputeType is BSRational for exact rational +// arithmetic. As long as this code is a correct implementation of the theory +// (which I hope it is), you will obtain the minimum-volume sphere +// containing the points. +// +// Instead, if you choose ComputeType to be float or double, floating-point +// rounding errors can cause the UpdateSupport{2,3,4} functions to fail. +// The failure is trapped in those functions and a simple bounding sphere is +// computed using GetContainer in file GteContSphere3.h. This sphere is +// generally not the minimum-volume sphere containing the points. The +// minimum-volume algorithm is terminated immediately. The sphere is +// returned as well as a bool value of 'true' when the sphere is minimum +// volume or 'false' when the failure is trapped. When 'false' is returned, +// you can try another call to the operator()(...) function. The random +// shuffle that occurs is highly likely to be different from the previous +// shuffle, and there is a chance that the algorithm can succeed just because +// of the different ordering of points. + +namespace gte +{ + template + class MinimumVolumeSphere3 + { + public: + bool operator()(int numPoints, Vector3 const* points, Sphere3& minimal) + { + if (numPoints >= 1 && points) + { + // Function array to avoid switch statement in the main loop. + std::function update[5]; + update[1] = [this](int i) { return UpdateSupport1(i); }; + update[2] = [this](int i) { return UpdateSupport2(i); }; + update[3] = [this](int i) { return UpdateSupport3(i); }; + update[4] = [this](int i) { return UpdateSupport4(i); }; + + // Process only the unique points. + std::vector permuted(numPoints); + for (int i = 0; i < numPoints; ++i) + { + permuted[i] = i; + } + std::sort(permuted.begin(), permuted.end(), + [points](int i0, int i1) { return points[i0] < points[i1]; }); + auto end = std::unique(permuted.begin(), permuted.end(), + [points](int i0, int i1) { return points[i0] == points[i1]; }); + permuted.erase(end, permuted.end()); + numPoints = static_cast(permuted.size()); + + // Create a random permutation of the points. + std::shuffle(permuted.begin(), permuted.end(), mDRE); + + // Convert to the compute type, which is a simple copy when + // ComputeType is the same as InputType. + mComputePoints.resize(numPoints); + for (int i = 0; i < numPoints; ++i) + { + for (int j = 0; j < 3; ++j) + { + mComputePoints[i][j] = points[permuted[i]][j]; + } + } + + // Start with the first point. + Sphere3 ctMinimal = ExactSphere1(0); + mNumSupport = 1; + mSupport[0] = 0; + + // The loop restarts from the beginning of the point list each + // time the sphere needs updating. Linus Kallberg (Computer + // Science at Malardalen University in Sweden) discovered that + // performance is/ better when the remaining points in the + // array are processed before restarting. The points + // processed before the point that caused the/ update are + // likely to be enclosed by the new sphere (or near the sphere + // boundary) because they were enclosed by the previous + // sphere. The chances are better that points after the + // current one will cause growth of the bounding sphere. + for (int i = 1 % numPoints, n = 0; i != n; i = (i + 1) % numPoints) + { + if (!SupportContains(i)) + { + if (!Contains(i, ctMinimal)) + { + auto result = update[mNumSupport](i); + if (result.second == true) + { + if (result.first.radius > ctMinimal.radius) + { + ctMinimal = result.first; + n = i; + } + } + else + { + // This case can happen when ComputeType is + // float or double. See the comments at the + // beginning of this file. + LogWarning("ComputeType is not exact and failure occurred. Returning non-minimal sphere."); + GetContainer(numPoints, points, minimal); + mNumSupport = 0; + mSupport.fill(0); + return false; + } + } + } + } + + for (int j = 0; j < 3; ++j) + { + minimal.center[j] = static_cast(ctMinimal.center[j]); + } + minimal.radius = static_cast(ctMinimal.radius); + minimal.radius = std::sqrt(minimal.radius); + + for (int i = 0; i < mNumSupport; ++i) + { + mSupport[i] = permuted[mSupport[i]]; + } + return true; + } + else + { + LogError("Input must contain points."); + minimal.center = Vector3::Zero(); + minimal.radius = std::numeric_limits::max(); + return false; + } + } + + // Member access. + inline int GetNumSupport() const + { + return mNumSupport; + } + + inline std::array const& GetSupport() const + { + return mSupport; + } + + private: + // Test whether point P is inside sphere S using squared distance and + // squared radius. + bool Contains(int i, Sphere3 const& sphere) const + { + // NOTE: In this algorithm, sphere.radius is the *squared radius* + // until the function returns at which time a square root is + // applied. + Vector3 diff = mComputePoints[i] - sphere.center; + return Dot(diff, diff) <= sphere.radius; + } + + Sphere3 ExactSphere1(int i0) const + { + Sphere3 minimal; + minimal.center = mComputePoints[i0]; + minimal.radius = (ComputeType)0; + return minimal; + } + + Sphere3 ExactSphere2(int i0, int i1) const + { + Vector3 const& P0 = mComputePoints[i0]; + Vector3 const& P1 = mComputePoints[i1]; + Sphere3 minimal; + minimal.center = ((ComputeType)0.5)*(P0 + P1); + Vector3 diff = P1 - P0; + minimal.radius = ((ComputeType)0.25)*Dot(diff, diff); + return minimal; + } + + Sphere3 ExactSphere3(int i0, int i1, int i2) const + { + // Compute the 2D circle containing P0, P1, and P2. The center in + // barycentric coordinates is C = x0*P0 + x1*P1 + x2*P2, where + // x0 + x1 + x2 = 1. The center is equidistant from the three + // points, so |C - P0| = |C - P1| = |C - P2| = R, where R is the + // radius of the circle. From these conditions, + // C - P0 = x0*E0 + x1*E1 - E0 + // C - P1 = x0*E0 + x1*E1 - E1 + // C - P2 = x0*E0 + x1*E1 + // where E0 = P0 - P2 and E1 = P1 - P2, which leads to + // r^2 = |x0*E0 + x1*E1|^2 - 2*Dot(E0, x0*E0 + x1*E1) + |E0|^2 + // r^2 = |x0*E0 + x1*E1|^2 - 2*Dot(E1, x0*E0 + x1*E1) + |E1|^2 + // r^2 = |x0*E0 + x1*E1|^2 + // Subtracting the last equation from the first two and writing + // the equations as a linear system, + // + // +- -++ -+ +- -+ + // | Dot(E0,E0) Dot(E0,E1) || x0 | = 0.5 | Dot(E0,E0) | + // | Dot(E1,E0) Dot(E1,E1) || x1 | | Dot(E1,E1) | + // +- -++ -+ +- -+ + // + // The following code solves this system for x0 and x1 and then + // evaluates the third equation in r^2 to obtain r. + + Vector3 const& P0 = mComputePoints[i0]; + Vector3 const& P1 = mComputePoints[i1]; + Vector3 const& P2 = mComputePoints[i2]; + + Vector3 E0 = P0 - P2; + Vector3 E1 = P1 - P2; + + Matrix2x2 A; + A(0, 0) = Dot(E0, E0); + A(0, 1) = Dot(E0, E1); + A(1, 0) = A(0, 1); + A(1, 1) = Dot(E1, E1); + + ComputeType const half = (ComputeType)0.5; + Vector2 B{ half*A(0, 0), half*A(1, 1) }; + + Sphere3 minimal; + Vector2 X; + if (LinearSystem::Solve(A, B, X)) + { + ComputeType x2 = (ComputeType)1 - X[0] - X[1]; + minimal.center = X[0] * P0 + X[1] * P1 + x2 * P2; + Vector3 tmp = X[0] * E0 + X[1] * E1; + minimal.radius = Dot(tmp, tmp); + } + else + { + minimal.center = Vector3::Zero(); + minimal.radius = (ComputeType)std::numeric_limits::max(); + } + return minimal; + } + + Sphere3 ExactSphere4(int i0, int i1, int i2, int i3) const + { + // Compute the sphere containing P0, P1, P2, and P3. The center + // in barycentric coordinates is C = x0*P0 + x1*P1 + x2*P2 + x3*P3, + // where x0 + x1 + x2 + x3 = 1. The center is equidistant from + // the three points, so |C - P0| = |C - P1| = |C - P2| = |C - P3| + // = R, where R is the radius of the sphere. From these + // conditions, + // C - P0 = x0*E0 + x1*E1 + x2*E2 - E0 + // C - P1 = x0*E0 + x1*E1 + x2*E2 - E1 + // C - P2 = x0*E0 + x1*E1 + x2*E2 - E2 + // C - P3 = x0*E0 + x1*E1 + x2*E2 + // where E0 = P0 - P3, E1 = P1 - P3, and E2 = P2 - P3, which + // leads to + // r^2 = |x0*E0+x1*E1+x2*E2|^2 - 2*Dot(E0,x0*E0+x1*E1+x2*E2) + |E0|^2 + // r^2 = |x0*E0+x1*E1+x2*E2|^2 - 2*Dot(E1,x0*E0+x1*E1+x2*E2) + |E1|^2 + // r^2 = |x0*E0+x1*E1+x2*E2|^2 - 2*Dot(E2,x0*E0+x1*E1+x2*E2) + |E2|^2 + // r^2 = |x0*E0+x1*E1+x2*E2|^2 + // Subtracting the last equation from the first three and writing + // the equations as a linear system, + // + // +- -++ -+ +- -+ + // | Dot(E0,E0) Dot(E0,E1) Dot(E0,E2) || x0 | = 0.5 | Dot(E0,E0) | + // | Dot(E1,E0) Dot(E1,E1) Dot(E1,E2) || x1 | | Dot(E1,E1) | + // | Dot(E2,E0) Dot(E2,E1) Dot(E2,E2) || x2 | | Dot(E2,E2) | + // +- -++ -+ +- -+ + // + // The following code solves this system for x0, x1, and x2 and + // then evaluates the fourth equation in r^2 to obtain r. + + Vector3 const& P0 = mComputePoints[i0]; + Vector3 const& P1 = mComputePoints[i1]; + Vector3 const& P2 = mComputePoints[i2]; + Vector3 const& P3 = mComputePoints[i3]; + + Vector3 E0 = P0 - P3; + Vector3 E1 = P1 - P3; + Vector3 E2 = P2 - P3; + + Matrix3x3 A; + A(0, 0) = Dot(E0, E0); + A(0, 1) = Dot(E0, E1); + A(0, 2) = Dot(E0, E2); + A(1, 0) = A(0, 1); + A(1, 1) = Dot(E1, E1); + A(1, 2) = Dot(E1, E2); + A(2, 0) = A(0, 2); + A(2, 1) = A(1, 2); + A(2, 2) = Dot(E2, E2); + + ComputeType const half = (ComputeType)0.5; + Vector3 B{ half*A(0, 0), half*A(1, 1), half*A(2, 2) }; + + Sphere3 minimal; + Vector3 X; + if (LinearSystem::Solve(A, B, X)) + { + ComputeType x3 = (ComputeType)1 - X[0] - X[1] - X[2]; + minimal.center = X[0] * P0 + X[1] * P1 + X[2] * P2 + x3 * P3; + Vector3 tmp = X[0] * E0 + X[1] * E1 + X[2] * E2; + minimal.radius = Dot(tmp, tmp); + } + else + { + minimal.center = Vector3::Zero(); + minimal.radius = (ComputeType)std::numeric_limits::max(); + } + return minimal; + } + + typedef std::pair, bool> UpdateResult; + + UpdateResult UpdateSupport1(int i) + { + Sphere3 minimal = ExactSphere2(mSupport[0], i); + mNumSupport = 2; + mSupport[1] = i; + return std::make_pair(minimal, true); + } + + UpdateResult UpdateSupport2(int i) + { + // Permutations of type 2, used for calling ExactSphere2(...). + int const numType2 = 2; + int const type2[numType2][2] = + { + { 0, /*2*/ 1 }, + { 1, /*2*/ 0 } + }; + + // Permutations of type 3, used for calling ExactSphere3(...). + int const numType3 = 1; // {0, 1, 2} + + Sphere3 sphere[numType2 + numType3]; + ComputeType minRSqr = (ComputeType)std::numeric_limits::max(); + int iSphere = 0, iMinRSqr = -1; + int k0, k1; + + // Permutations of type 2. + for (int j = 0; j < numType2; ++j, ++iSphere) + { + k0 = mSupport[type2[j][0]]; + sphere[iSphere] = ExactSphere2(k0, i); + if (sphere[iSphere].radius < minRSqr) + { + k1 = mSupport[type2[j][1]]; + if (Contains(k1, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + // Permutations of type 3. + k0 = mSupport[0]; + k1 = mSupport[1]; + sphere[iSphere] = ExactSphere3(k0, k1, i); + if (sphere[iSphere].radius < minRSqr) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + + switch (iMinRSqr) + { + case 0: + mSupport[1] = i; + break; + case 1: + mSupport[0] = i; + break; + case 2: + mNumSupport = 3; + mSupport[2] = i; + break; + case -1: + // For exact arithmetic, iMinRSqr >= 0, but for floating-point + // arithmetic, round-off errors can lead to iMinRSqr == -1. + // When this happens, use a simple bounding sphere for the + // result and terminate the minimum-volume algorithm. + return std::make_pair(Sphere3(), false); + } + + return std::make_pair(sphere[iMinRSqr], true); + } + + UpdateResult UpdateSupport3(int i) + { + // Permutations of type 2, used for calling ExactSphere2(...). + int const numType2 = 3; + int const type2[numType2][3] = + { + { 0, /*3*/ 1, 2 }, + { 1, /*3*/ 0, 2 }, + { 2, /*3*/ 0, 1 } + }; + + // Permutations of type 3, used for calling ExactSphere3(...). + int const numType3 = 3; + int const type3[numType3][3] = + { + { 0, 1, /*3*/ 2 }, + { 0, 2, /*3*/ 1 }, + { 1, 2, /*3*/ 0 } + }; + + // Permutations of type 4, used for calling ExactSphere4(...). + int const numType4 = 1; // {0, 1, 2, 3} + + Sphere3 sphere[numType2 + numType3 + numType4]; + ComputeType minRSqr = (ComputeType)std::numeric_limits::max(); + int iSphere = 0, iMinRSqr = -1; + int k0, k1, k2; + + // Permutations of type 2. + for (int j = 0; j < numType2; ++j, ++iSphere) + { + k0 = mSupport[type2[j][0]]; + sphere[iSphere] = ExactSphere2(k0, i); + if (sphere[iSphere].radius < minRSqr) + { + k1 = mSupport[type2[j][1]]; + k2 = mSupport[type2[j][2]]; + if (Contains(k1, sphere[iSphere]) && Contains(k2, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + // Permutations of type 3. + for (int j = 0; j < numType3; ++j, ++iSphere) + { + k0 = mSupport[type3[j][0]]; + k1 = mSupport[type3[j][1]]; + sphere[iSphere] = ExactSphere3(k0, k1, i); + if (sphere[iSphere].radius < minRSqr) + { + k2 = mSupport[type3[j][2]]; + if (Contains(k2, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + // Permutations of type 4. + k0 = mSupport[0]; + k1 = mSupport[1]; + k2 = mSupport[2]; + sphere[iSphere] = ExactSphere4(k0, k1, k2, i); + if (sphere[iSphere].radius < minRSqr) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + + switch (iMinRSqr) + { + case 0: + mNumSupport = 2; + mSupport[1] = i; + break; + case 1: + mNumSupport = 2; + mSupport[0] = i; + break; + case 2: + mNumSupport = 2; + mSupport[0] = mSupport[2]; + mSupport[1] = i; + break; + case 3: + mSupport[2] = i; + break; + case 4: + mSupport[1] = i; + break; + case 5: + mSupport[0] = i; + break; + case 6: + mNumSupport = 4; + mSupport[3] = i; + break; + case -1: + // For exact arithmetic, iMinRSqr >= 0, but for floating-point + // arithmetic, round-off errors can lead to iMinRSqr == -1. + // When this happens, use a simple bounding sphere for the + // result and terminate the minimum-area algorithm. + return std::make_pair(Sphere3(), false); + } + + return std::make_pair(sphere[iMinRSqr], true); + } + + UpdateResult UpdateSupport4(int i) + { + // Permutations of type 2, used for calling ExactSphere2(...). + int const numType2 = 4; + int const type2[numType2][4] = + { + { 0, /*4*/ 1, 2, 3 }, + { 1, /*4*/ 0, 2, 3 }, + { 2, /*4*/ 0, 1, 3 }, + { 3, /*4*/ 0, 1, 2 } + }; + + // Permutations of type 3, used for calling ExactSphere3(...). + int const numType3 = 6; + int const type3[numType3][4] = + { + { 0, 1, /*4*/ 2, 3 }, + { 0, 2, /*4*/ 1, 3 }, + { 0, 3, /*4*/ 1, 2 }, + { 1, 2, /*4*/ 0, 3 }, + { 1, 3, /*4*/ 0, 2 }, + { 2, 3, /*4*/ 0, 1 } + }; + + // Permutations of type 4, used for calling ExactSphere4(...). + int const numType4 = 4; + int const type4[numType4][4] = + { + { 0, 1, 2, /*4*/ 3 }, + { 0, 1, 3, /*4*/ 2 }, + { 0, 2, 3, /*4*/ 1 }, + { 1, 2, 3, /*4*/ 0 } + }; + + Sphere3 sphere[numType2 + numType3 + numType4]; + ComputeType minRSqr = (ComputeType)std::numeric_limits::max(); + int iSphere = 0, iMinRSqr = -1; + int k0, k1, k2, k3; + + // Permutations of type 2. + for (int j = 0; j < numType2; ++j, ++iSphere) + { + k0 = mSupport[type2[j][0]]; + sphere[iSphere] = ExactSphere2(k0, i); + if (sphere[iSphere].radius < minRSqr) + { + k1 = mSupport[type2[j][1]]; + k2 = mSupport[type2[j][2]]; + k3 = mSupport[type2[j][3]]; + if (Contains(k1, sphere[iSphere]) && Contains(k2, sphere[iSphere]) && Contains(k3, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + // Permutations of type 3. + for (int j = 0; j < numType3; ++j, ++iSphere) + { + k0 = mSupport[type3[j][0]]; + k1 = mSupport[type3[j][1]]; + sphere[iSphere] = ExactSphere3(k0, k1, i); + if (sphere[iSphere].radius < minRSqr) + { + k2 = mSupport[type3[j][2]]; + k3 = mSupport[type3[j][3]]; + if (Contains(k2, sphere[iSphere]) && Contains(k3, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + // Permutations of type 4. + for (int j = 0; j < numType4; ++j, ++iSphere) + { + k0 = mSupport[type4[j][0]]; + k1 = mSupport[type4[j][1]]; + k2 = mSupport[type4[j][2]]; + sphere[iSphere] = ExactSphere4(k0, k1, k2, i); + if (sphere[iSphere].radius < minRSqr) + { + k3 = mSupport[type4[j][3]]; + if (Contains(k3, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + switch (iMinRSqr) + { + case 0: + mNumSupport = 2; + mSupport[1] = i; + break; + case 1: + mNumSupport = 2; + mSupport[0] = i; + break; + case 2: + mNumSupport = 2; + mSupport[0] = mSupport[2]; + mSupport[1] = i; + break; + case 3: + mNumSupport = 2; + mSupport[0] = mSupport[3]; + mSupport[1] = i; + break; + case 4: + mNumSupport = 3; + mSupport[2] = i; + break; + case 5: + mNumSupport = 3; + mSupport[1] = i; + break; + case 6: + mNumSupport = 3; + mSupport[1] = mSupport[3]; + mSupport[2] = i; + break; + case 7: + mNumSupport = 3; + mSupport[0] = i; + break; + case 8: + mNumSupport = 3; + mSupport[0] = mSupport[3]; + mSupport[2] = i; + break; + case 9: + mNumSupport = 3; + mSupport[0] = mSupport[3]; + mSupport[1] = i; + break; + case 10: + mSupport[3] = i; + break; + case 11: + mSupport[2] = i; + break; + case 12: + mSupport[1] = i; + break; + case 13: + mSupport[0] = i; + break; + case -1: + // For exact arithmetic, iMinRSqr >= 0, but for floating-point + // arithmetic, round-off errors can lead to iMinRSqr == -1. + // When this happens, use a simple bounding sphere for the + // result and terminate the minimum-area algorithm. + return std::make_pair(Sphere3(), false); + } + + return std::make_pair(sphere[iMinRSqr], true); + } + + // Indices of points that support current minimum volume sphere. + bool SupportContains(int j) const + { + for (int i = 0; i < mNumSupport; ++i) + { + if (j == mSupport[i]) + { + return true; + } + } + return false; + } + + int mNumSupport; + std::array mSupport; + + // Random permutation of the unique input points to produce expected + // linear time for the algorithm. + std::default_random_engine mDRE; + std::vector> mComputePoints; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSCircle.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSCircle.h new file mode 100644 index 000000000000..5e887ed3f345 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSCircle.h @@ -0,0 +1,143 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.18.0 (2018/10/28) + +#pragma once + +#include + +// The algorithm for representing a circle as a NURBS curve or a sphere as a +// NURBS surface is described in +// http://www.geometrictools.com/Documentation/NURBSCircleSphere.pdf +// The implementations are related to the documents as shown next. +// NURBSQuarterCircleDegree2 implements equation (9) +// NURBSQuarterCircleDegree4 implements equation (10) +// NURBSHalfCircleDegree3 implements equation (12) +// NURBSFullCircleDegree3 implements Section 2.3 + +namespace gte +{ + template + class NURBSQuarterCircleDegree2 : public NURBSCurve<2, Real> + { + public: + // Construction. The quarter circle is x^2 + y^2 = 1 for x >= 0 + // and y >= 0. The direction of traversal is counterclockwise as + // u increase from 0 to 1. + NURBSQuarterCircleDegree2() + : + NURBSCurve<2, Real>(BasisFunctionInput(3, 2), nullptr, nullptr) + { + Real const sqrt2 = std::sqrt((Real)2); + this->mWeights[0] = sqrt2; + this->mWeights[1] = (Real)1; + this->mWeights[2] = sqrt2; + + this->mControls[0] = { (Real)1, (Real)0 }; + this->mControls[1] = { (Real)1, (Real)1 }; + this->mControls[2] = { (Real)0, (Real)1 }; + } + }; + + template + class NURBSQuarterCircleDegree4 : public NURBSCurve<2, Real> + { + public: + // Construction. The quarter circle is x^2 + y^2 = 1 for x >= 0 + // and y >= 0. The direction of traversal is counterclockwise as + // u increases from 0 to 1. + NURBSQuarterCircleDegree4() + : + NURBSCurve<2, Real>(BasisFunctionInput(5, 4), nullptr, nullptr) + { + Real const sqrt2 = std::sqrt((Real)2); + this->mWeights[0] = (Real)1; + this->mWeights[1] = (Real)1; + this->mWeights[2] = (Real)2 * sqrt2 / (Real)3; + this->mWeights[3] = (Real)1; + this->mWeights[4] = (Real)1; + + Real const x1 = (Real)1; + Real const y1 = (Real)0.5 / sqrt2; + Real const x2 = (Real)1 - sqrt2 / (Real)8; + this->mControls[0] = { (Real)1, (Real)0 }; + this->mControls[1] = { x1, y1 }; + this->mControls[2] = { x2, x2 }; + this->mControls[3] = { y1, x1 }; + this->mControls[4] = { (Real)0, (Real)1 }; + } + }; + + template + class NURBSHalfCircleDegree3 : public NURBSCurve<2, Real> + { + public: + // Construction. The half circle is x^2 + y^2 = 1 for x >= 0. The + // direction of traversal is counterclockwise as u increases from + // 0 to 1. + NURBSHalfCircleDegree3() + : + NURBSCurve<2, Real>(BasisFunctionInput(4, 3), nullptr, nullptr) + { + Real const oneThird = (Real)1 / (Real)3; + this->mWeights[0] = (Real)1; + this->mWeights[1] = oneThird; + this->mWeights[2] = oneThird; + this->mWeights[3] = (Real)1; + + this->mControls[0] = { (Real)1, (Real)0 }; + this->mControls[1] = { (Real)1, (Real)2 }; + this->mControls[2] = { (Real)-1, (Real)2 }; + this->mControls[3] = { (Real)-1, (Real)0 }; + } + }; + + template + class NURBSFullCircleDegree3 : public NURBSCurve<2, Real> + { + public: + // Construction. The full circle is x^2 + y^2 = 1. The direction of + // traversal is counterclockwise as u increases from 0 to 1. + NURBSFullCircleDegree3() + : + NURBSCurve<2, Real>(CreateBasisFunctionInput(), nullptr, nullptr) + { + Real const oneThird = (Real)1 / (Real)3; + this->mWeights[0] = (Real)1; + this->mWeights[1] = oneThird; + this->mWeights[2] = oneThird; + this->mWeights[3] = (Real)1; + this->mWeights[4] = oneThird; + this->mWeights[5] = oneThird; + this->mWeights[6] = (Real)1; + + this->mControls[0] = { (Real)1, (Real)0 }; + this->mControls[1] = { (Real)1, (Real)2 }; + this->mControls[2] = { (Real)-1, (Real)2 }; + this->mControls[3] = { (Real)-1, (Real)0 }; + this->mControls[4] = { (Real)-1, (Real)-2 }; + this->mControls[5] = { (Real)1, (Real)-2 }; + this->mControls[6] = { (Real)1, (Real)0 }; + } + + private: + static BasisFunctionInput CreateBasisFunctionInput() + { + // We need knots (0,0,0,0,1/2,1/2,1/2,1,1,1,1). + BasisFunctionInput input; + input.numControls = 7; + input.degree = 3; + input.uniform = true; + input.periodic = false; + input.numUniqueKnots = 3; + input.uniqueKnots.resize(input.numUniqueKnots); + input.uniqueKnots[0] = { (Real)0, 4 }; + input.uniqueKnots[1] = { (Real)0.5, 3 }; + input.uniqueKnots[2] = { (Real)1, 4 }; + return input; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSCurve.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSCurve.h new file mode 100644 index 000000000000..d1fcda2439d3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSCurve.h @@ -0,0 +1,229 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/10/28) + +#pragma once + +#include +#include + +namespace gte +{ + template + class NURBSCurve : public ParametricCurve + { + public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points or weights, pass + // null pointers and later access the control points or weights via + // GetControls(), GetWeights(), SetControl(), or SetWeight() member + // functions. The domain is t in [t[d],t[n]], where t[d] and t[n] are + // knots with d the degree and n the number of control points. To + // validate construction, create an object as shown: + // NURBSCurve curve(parameters); + // if (!curve) { ; } + NURBSCurve(BasisFunctionInput const& input, + Vector const* controls, Real const* weights) + : + ParametricCurve((Real)0, (Real)1), + mBasisFunction(input) + { + if (!mBasisFunction) + { + // Errors were already generated during construction of the + // basis function. + return; + } + + // The mBasisFunction stores the domain but so does + // ParametricCurve. + this->mTime.front() = mBasisFunction.GetMinDomain(); + this->mTime.back() = mBasisFunction.GetMaxDomain(); + + // The replication of control points for periodic splines is + // avoided by wrapping the i-loop index in Evaluate. + mControls.resize(input.numControls); + mWeights.resize(input.numControls); + if (controls) + { + std::copy(controls, controls + input.numControls, mControls.begin()); + } + else + { + Vector zero{ (Real)0 }; + std::fill(mControls.begin(), mControls.end(), zero); + } + if (weights) + { + std::copy(weights, weights + input.numControls, mWeights.begin()); + } + else + { + std::fill(mWeights.begin(), mWeights.end(), (Real)0); + } + this->mConstructed = true; + } + + // Member access. + inline BasisFunction const& GetBasisFunction() const + { + return mBasisFunction; + } + + inline int GetNumControls() const + { + return static_cast(mControls.size()); + } + + inline Vector const* GetControls() const + { + return mControls.data(); + } + + inline Vector* GetControls() + { + return mControls.data(); + } + + inline Real const* GetWeights() const + { + return mWeights.data(); + } + + inline Real* GetWeights() + { + return mWeights.data(); + } + + void SetControl(int i, Vector const& control) + { + if (0 <= i && i < GetNumControls()) + { + mControls[i] = control; + } + } + + Vector const& GetControl(int i) const + { + if (0 <= i && i < GetNumControls()) + { + return mControls[i]; + } + else + { + // Invalid index, return something. + return mControls[0]; + } + } + + void SetWeight(int i, Real weight) + { + if (0 <= i && i < GetNumControls()) + { + mWeights[i] = weight; + } + } + + Real const& GetWeight(int i) const + { + if (0 <= i && i < GetNumControls()) + { + return mWeights[i]; + } + else + { + // Invalid index, return something. + return mWeights[0]; + } + } + + // Evaluation of the curve. The function supports derivative + // calculation through order 3; that is, maxOrder <= 3 is required. + // If you want only the position, pass in maxOrder of 0. If you + // want the position and first derivative, pass in maxOrder of 1, + // and so on. The output 'values' are ordered as: position, first + // derivative, second derivative, third derivative. + virtual void Evaluate(Real t, unsigned int maxOrder, Vector values[4]) const + { + if (!this->mConstructed) + { + // Errors were already generated during construction. + for (unsigned int order = 0; order < 4; ++order) + { + values[order].MakeZero(); + } + return; + } + + int imin, imax; + mBasisFunction.Evaluate(t, maxOrder, imin, imax); + + // Compute position. + Vector X; + Real w; + Compute(0, imin, imax, X, w); + Real invW = ((Real)1) / w; + values[0] = invW * X; + + if (maxOrder >= 1) + { + // Compute first derivative. + Vector XDer1; + Real wDer1; + Compute(1, imin, imax, XDer1, wDer1); + values[1] = invW * (XDer1 - wDer1 * values[0]); + + if (maxOrder >= 2) + { + // Compute second derivative. + Vector XDer2; + Real wDer2; + Compute(2, imin, imax, XDer2, wDer2); + values[2] = invW * (XDer2 - ((Real)2) * wDer1 * values[1] - + wDer2 * values[0]); + + if (maxOrder == 3) + { + // Compute third derivative. + Vector XDer3; + Real wDer3; + Compute(3, imin, imax, XDer3, wDer3); + values[3] = invW * (XDer3 - ((Real)3) * wDer1 * values[2] - + ((Real)3) * wDer2 * values[1] - wDer3 * values[0]); + } + else + { + values[3].MakeZero(); + } + } + } + } + + protected: + // Support for Evaluate(...). + void Compute(unsigned int order, int imin, int imax, Vector& X, Real& w) const + { + // The j-index introduces a tiny amount of overhead in order to + // handle both aperiodic and periodic splines. For aperiodic + // splines, j = i always. + + int numControls = GetNumControls(); + X.MakeZero(); + w = (Real)0; + for (int i = imin; i <= imax; ++i) + { + int j = (i >= numControls ? i - numControls : i); + Real tmp = mBasisFunction.GetValue(order, i) * mWeights[j]; + X += tmp * mControls[j]; + w += tmp; + } + } + + BasisFunction mBasisFunction; + std::vector> mControls; + std::vector mWeights; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSSphere.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSSphere.h new file mode 100644 index 000000000000..c7f2a846a8c7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSSphere.h @@ -0,0 +1,459 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.18.0 (2018/10/28) + +#pragma once + +#include +#include +#include + +// The algorithm for representing a circle as a NURBS curve or a sphere as a +// NURBS surface is described in +// http://www.geometrictools.com/Documentation/NURBSCircleSphere.pdf +// The implementations are related to the documents as shown next. +// NURBSEighthSphereDegree4 implements Section 3.1.2 (triangular domain) +// NURBSHalfSphereDegree3 implements Section 3.2 (rectangular domain) +// NURBSFullSphereDegree3 implements Section 2.3 (rectangular domain) +// TODO: The class NURBSSurface currently assumes a rectangular domain. +// Once support is added for triangular domains, make that new class a +// base class of the sphere-representing NURBS. This will allow sharing +// of the NURBS basis functions and evaluation framework. + +namespace gte +{ + template + class NURBSEighthSphereDegree4 + { + public: + // Construction. The eigth sphere is x^2 + y^2 + z^2 = 1 for x >= 0, + // y >= 0 and z >= 0. + NURBSEighthSphereDegree4() + { + Real const sqrt2 = std::sqrt((Real)2); + Real const sqrt3 = std::sqrt((Real)3); + Real const a0 = (sqrt3 - (Real)1) / sqrt3; + Real const a1 = (sqrt3 + (Real)1) / ((Real)2 * sqrt3); + Real const a2 = (Real)1 - ((Real)5 - sqrt2) * ((Real)7 - sqrt3) / (Real)46; + Real const b0 = (Real)4 * sqrt3 * (sqrt3 - (Real)1); + Real const b1 = (Real)3 * sqrt2; + Real const b2 = (Real)4; + Real const b3 = sqrt2 * ((Real)3 + (Real)2 * sqrt2 - sqrt3) / sqrt3; + + mControls[0][0] = { (Real)0, (Real)0, (Real)1 }; // P004 + mControls[0][1] = { (Real)0, a0, (Real)1 }; // P013 + mControls[0][2] = { (Real)0, a1, a1 }; // P022 + mControls[0][3] = { (Real)0, (Real)1, a0 }; // P031 + mControls[0][4] = { (Real)0, (Real)1, (Real)0 }; // P040 + + mControls[1][0] = { a0, (Real)0, (Real)1 }; // P103 + mControls[1][1] = { a2, a2, (Real)1 }; // P112 + mControls[1][2] = { a2, (Real)1, a2 }; // P121 + mControls[1][3] = { a0, (Real)1, (Real)0 }; // P130 + mControls[1][4] = { (Real)0, (Real)0, (Real)0 }; // unused + + mControls[2][0] = { a1, (Real)0, a1 }; // P202 + mControls[2][1] = { (Real)1, a2, a2 }; // P211 + mControls[2][2] = { a1, a1, (Real)0 }; // P220 + mControls[2][3] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[2][4] = { (Real)0, (Real)0, (Real)0 }; // unused + + mControls[3][0] = { (Real)1, (Real)0, a0 }; // P301 + mControls[3][1] = { (Real)1, a0, (Real)0 }; // P310 + mControls[3][2] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[3][3] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[3][4] = { (Real)0, (Real)0, (Real)0 }; // unused + + mControls[4][0] = { (Real)1, (Real)0, (Real)0 }; // P400 + mControls[4][1] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[4][2] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[4][3] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[4][4] = { (Real)0, (Real)0, (Real)0 }; // unused + + mWeights[0][0] = b0; // w004 + mWeights[0][1] = b1; // w013 + mWeights[0][2] = b2; // w022 + mWeights[0][3] = b1; // w031 + mWeights[0][4] = b0; // w040 + + mWeights[1][0] = b1; // w103 + mWeights[1][1] = b3; // w112 + mWeights[1][2] = b3; // w121 + mWeights[1][3] = b1; // w130 + mWeights[1][4] = (Real)0; // unused + + mWeights[2][0] = b2; // w202 + mWeights[2][1] = b3; // w211 + mWeights[2][2] = b2; // w220 + mWeights[2][3] = (Real)0; // unused + mWeights[2][4] = (Real)0; // unused + + mWeights[3][0] = b1; // w301 + mWeights[3][1] = b1; // w310 + mWeights[3][2] = (Real)0; // unused + mWeights[3][3] = (Real)0; // unused + mWeights[3][4] = (Real)0; // unused + + mWeights[4][0] = b0; // w400 + mWeights[4][1] = (Real)0; // unused + mWeights[4][2] = (Real)0; // unused + mWeights[4][3] = (Real)0; // unused + mWeights[4][4] = (Real)0; // unused + } + + // Evaluation of the surface. The function supports derivative + // calculation through order 2; that is, maxOrder <= 2 is required. + // If you want only the position, pass in maxOrder of 0. If you want + // the position and first-order derivatives, pass in maxOrder of 1, + // and so on. The output 'values' are ordered as: position X; + // first-order derivatives dX/du, dX/dv; second-order derivatives + // d2X/du2, d2X/dudv, d2X/dv2. + void Evaluate(Real u, Real v, unsigned int maxOrder, Vector<3, Real> values[6]) const + { + // TODO: Some of the polynomials are used in other polynomials. + // Optimize the code by eliminating the redundant computations. + + Real w = (Real)1 - u - v; + Real uu = u * u, uv = u * v, uw = u * w, vv = v * v, vw = v * w, ww = w * w; + + // Compute the order-0 polynomials. Only the elements to be used + // are filled in. The other terms are uninitialized but never + // used. + Real B[5][5]; + B[0][0] = ww * ww; + B[0][1] = (Real)4 * vw * ww; + B[0][2] = (Real)6 * vv * ww; + B[0][3] = (Real)4 * vv * vw; + B[0][4] = vv * vv; + B[1][0] = (Real)4 * uw * ww; + B[1][1] = (Real)12 * uv * ww; + B[1][2] = (Real)12 * uv * vw; + B[1][3] = (Real)4 * uv * vv; + B[2][0] = (Real)6 * uu * ww; + B[2][1] = (Real)12 * uu * vw; + B[2][2] = (Real)6 * uu * vv; + B[3][0] = (Real)4 * uu * uw; + B[3][1] = (Real)4 * uu * uv; + B[4][0] = uu * uu; + + // Compute the NURBS position. + Vector<3, Real> N{ (Real)0, (Real)0, (Real)0 }; + Real D(0); + for (int j1 = 0; j1 <= 4; ++j1) + { + for (int j0 = 0; j0 <= 4 - j1; ++j0) + { + Real product = mWeights[j1][j0] * B[j1][j0]; + N += product * mControls[j1][j0]; + D += product; + } + } + values[0] = N / D; + + if (maxOrder >= 1) + { + // Compute the order-1 polynomials. Only the elements to be + // used are filled in. The other terms are uninitialized but + // never used. + Real WmU = w - u; + Real WmTwoU = WmU - u; + Real WmThreeU = WmTwoU - u; + Real TwoWmU = w + WmU; + Real ThreeWmU = w + TwoWmU; + Real WmV = w - v; + Real WmTwoV = WmV - v; + Real WmThreeV = WmTwoV - v; + Real TwoWmV = w + WmV; + Real ThreeWmV = w + TwoWmV; + Real Dsqr = D * D; + + Real Bu[5][5]; + Bu[0][0] = (Real)-4 * ww * w; + Bu[0][1] = (Real)-12 * v * ww; + Bu[0][2] = (Real)-12 * vv * w; + Bu[0][3] = (Real)-4 * v * vv; + Bu[0][4] = (Real)0; + Bu[1][0] = (Real)4 * ww * WmThreeU; + Bu[1][1] = (Real)12 * vw * WmTwoU; + Bu[1][2] = (Real)12 * vv * WmU; + Bu[1][3] = (Real)4 * vv; + Bu[2][0] = (Real)12 * uw * WmU; + Bu[2][1] = (Real)12 * uv * TwoWmU; + Bu[2][2] = (Real)12 * u * vv; + Bu[3][0] = (Real)4 * uu * ThreeWmU; + Bu[3][1] = (Real)12 * uu * v; + Bu[4][0] = (Real)4 * uu * u; + + Real Bv[5][5]; + Bv[0][0] = (Real)-4 * ww * w; + Bv[0][1] = (Real)4 * ww * WmThreeV; + Bv[0][2] = (Real)12 * vw * WmV; + Bv[0][3] = (Real)4 * vv * ThreeWmV; + Bv[0][4] = (Real)4 * vv * v; + Bv[1][0] = (Real)-12 * u * ww; + Bv[1][1] = (Real)12 * uw * WmTwoV; + Bv[1][2] = (Real)12 * uv * TwoWmV; + Bv[1][3] = (Real)12 * u * vv; + Bv[2][0] = (Real)-12 * uu * w; + Bv[2][1] = (Real)12 * uu * WmV; + Bv[2][2] = (Real)12 * uu * v; + Bv[3][0] = (Real)-4 * uu * u; + Bv[3][1] = (Real)4 * uu * u; + Bv[4][0] = (Real)0; + + Vector<3, Real> Nu{ (Real)0, (Real)0, (Real)0 }; + Vector<3, Real> Nv{ (Real)0, (Real)0, (Real)0 }; + Real Du(0), Dv(0); + for (int j1 = 0; j1 <= 4; ++j1) + { + for (int j0 = 0; j0 <= 4 - j1; ++j0) + { + Real product = mWeights[j1][j0] * Bu[j1][j0]; + Nu += product * mControls[j1][j0]; + Du += product; + product = mWeights[j1][j0] * Bv[j1][j0]; + Nv += product * mControls[j1][j0]; + Dv += product; + } + } + Vector<3, Real> numerDU = D * Nu - Du * N; + Vector<3, Real> numerDV = D * Nv - Dv * N; + values[1] = numerDU / Dsqr; + values[2] = numerDV / Dsqr; + + if (maxOrder >= 2) + { + // Compute the order-2 polynomials. Only the elements to + // be used are filled in. The other terms are + // uninitialized but never used. + Real Dcub = Dsqr * D; + + Real Buu[5][5]; + Buu[0][0] = (Real)12 * ww; + Buu[0][1] = (Real)24 * vw; + Buu[0][2] = (Real)12 * vv; + Buu[0][3] = (Real)0; + Buu[0][4] = (Real)0; + Buu[1][0] = (Real)-24 * w * WmU; + Buu[1][1] = (Real)-24 * v * TwoWmU; + Buu[1][2] = (Real)-24 * vv; + Buu[1][3] = (Real)0; + Buu[2][0] = (Real)12 * (ww - (Real)4 * uw + uu); + Buu[2][1] = (Real)24 * v * WmTwoU; + Buu[2][2] = (Real)12 * vv; + Buu[3][0] = (Real)24 * u * WmU; + Buu[3][1] = (Real)24 * uv; + Buu[4][0] = (Real)12 * uu; + + Real Buv[5][5]; + Buv[0][0] = (Real)12 * ww; + Buv[0][1] = (Real)-12 * w * WmTwoV; + Buv[0][2] = (Real)-12 * v * TwoWmV; + Buv[0][3] = (Real)-12 * vv; + Buv[0][4] = (Real)0; + Buv[1][0] = (Real)-12 * w * WmTwoU; + Buv[1][1] = (Real)12 * (ww + (Real)2 * (uv - uw - vw)); + Buv[1][2] = (Real)12 * v * ((Real)2 * WmU - v); + Buv[1][3] = (Real)12 * vv; + Buv[2][0] = (Real)-12 * u * TwoWmU; + Buv[2][1] = (Real)12 * u * ((Real)2 * WmV - u); + Buv[2][2] = (Real)24 * uv; + Buv[3][0] = (Real)-12 * uu; + Buv[3][1] = (Real)12 * uu; + Buv[4][0] = (Real)0; + + Real Bvv[5][5]; + Bvv[0][0] = (Real)12 * ww; + Bvv[0][1] = (Real)-24 * w * WmV; + Bvv[0][2] = (Real)12 * (ww - (Real)4 * vw + vv); + Bvv[0][3] = (Real)24 * v * WmV; + Bvv[0][4] = (Real)12 * vv; + Bvv[1][0] = (Real)24 * uw; + Bvv[1][1] = (Real)-24 * u * TwoWmV; + Bvv[1][2] = (Real)24 * u * WmTwoV; + Bvv[1][3] = (Real)24 * uv; + Bvv[2][0] = (Real)12 * uu; + Bvv[2][1] = (Real)-24 * uu; + Bvv[2][2] = (Real)12 * uu; + Bvv[3][0] = (Real)0; + Bvv[3][1] = (Real)0; + Bvv[4][0] = (Real)0; + + Vector<3, Real> Nuu{ (Real)0, (Real)0, (Real)0 }; + Vector<3, Real> Nuv{ (Real)0, (Real)0, (Real)0 }; + Vector<3, Real> Nvv{ (Real)0, (Real)0, (Real)0 }; + Real Duu(0), Duv(0), Dvv(0); + for (int j1 = 0; j1 <= 4; ++j1) + { + for (int j0 = 0; j0 <= 4 - j1; ++j0) + { + Real product = mWeights[j1][j0] * Buu[j1][j0]; + Nuu += product * mControls[j1][j0]; + Duu += product; + product = mWeights[j1][j0] * Buv[j1][j0]; + Nuv += product * mControls[j1][j0]; + Duv += product; + product = mWeights[j1][j0] * Bvv[j1][j0]; + Nvv += product * mControls[j1][j0]; + Dvv += product; + } + } + Vector<3, Real> termDuu = D * (D * Nuu - Duu * N); + Vector<3, Real> termDuv = D * (D * Nuv - Duv * N - Du * Nv - Dv * Nu); + Vector<3, Real> termDvv = D * (D * Nvv - Dvv * N); + values[3] = (D * termDuu - (Real)2 * Du * numerDU) / Dcub; + values[4] = (D * termDuv + (Real)2 * Du * Dv * N) / Dcub; + values[5] = (D * termDvv - (Real)2 * Dv * numerDV) / Dcub; + } + } + } + + private: + // For simplicity of the implementation, 2-dimensional arrays + // of size 5-by-5 are used. Only array[r][c] is used where + // 0 <= r <= 4 and 0 <= c < 4 - r. + Vector3 mControls[5][5]; + Real mWeights[5][5]; + }; + + template + class NURBSHalfSphereDegree3 : public NURBSSurface<3, Real> + { + public: + NURBSHalfSphereDegree3() + : + NURBSSurface<3, Real>(BasisFunctionInput(4, 3), + BasisFunctionInput(4, 3), nullptr, nullptr) + { + // weight[j][i] is mWeights[i + 4 * j], 0 <= i < 4, 0 <= j < 4 + Real const oneThird = (Real)1 / (Real)3; + Real const oneNinth = (Real)1 / (Real)9; + this->mWeights[0] = (Real)1; + this->mWeights[1] = oneThird; + this->mWeights[2] = oneThird; + this->mWeights[3] = (Real)1; + this->mWeights[4] = oneThird; + this->mWeights[5] = oneNinth; + this->mWeights[6] = oneNinth; + this->mWeights[7] = oneThird; + this->mWeights[8] = oneThird; + this->mWeights[9] = oneNinth; + this->mWeights[10] = oneNinth; + this->mWeights[11] = oneThird; + this->mWeights[12] = (Real)1; + this->mWeights[13] = oneThird; + this->mWeights[14] = oneThird; + this->mWeights[15] = (Real)1; + + // control[j][i] is mControls[i + 4 * j], 0 <= i < 4, 0 <= j < 4 + this->mControls[0] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[1] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[2] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[3] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[4] = { (Real)2, (Real)0, (Real)1 }; + this->mControls[5] = { (Real)2, (Real)4, (Real)1 }; + this->mControls[6] = { (Real)-2, (Real)4, (Real)1 }; + this->mControls[7] = { (Real)-2, (Real)0, (Real)1 }; + this->mControls[8] = { (Real)2, (Real)0, (Real)-1 }; + this->mControls[9] = { (Real)2, (Real)4, (Real)-1 }; + this->mControls[10] = { (Real)-2, (Real)4, (Real)-1 }; + this->mControls[11] = { (Real)-2, (Real)0, (Real)-1 }; + this->mControls[12] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[13] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[14] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[15] = { (Real)0, (Real)0, (Real)-1 }; + } + }; + + template + class NURBSFullSphereDegree3 : public NURBSSurface<3, Real> + { + public: + NURBSFullSphereDegree3() + : + NURBSSurface<3, Real>(BasisFunctionInput(4, 3), + CreateBasisFunctionInputV(), nullptr, nullptr) + { + // weight[j][i] is mWeights[i + 4 * j], 0 <= i < 4, 0 <= j < 7 + Real const oneThird = (Real)1 / (Real)3; + Real const oneNinth = (Real)1 / (Real)9; + this->mWeights[0] = (Real)1; + this->mWeights[4] = oneThird; + this->mWeights[8] = oneThird; + this->mWeights[12] = (Real)1; + this->mWeights[16] = oneThird; + this->mWeights[20] = oneThird; + this->mWeights[24] = (Real)1; + this->mWeights[1] = oneThird; + this->mWeights[5] = oneNinth; + this->mWeights[9] = oneNinth; + this->mWeights[13] = oneThird; + this->mWeights[17] = oneNinth; + this->mWeights[21] = oneNinth; + this->mWeights[25] = oneThird; + this->mWeights[2] = oneThird; + this->mWeights[6] = oneNinth; + this->mWeights[10] = oneNinth; + this->mWeights[14] = oneThird; + this->mWeights[18] = oneNinth; + this->mWeights[22] = oneNinth; + this->mWeights[26] = oneThird; + this->mWeights[3] = (Real)1; + this->mWeights[7] = oneThird; + this->mWeights[11] = oneThird; + this->mWeights[15] = (Real)1; + this->mWeights[19] = oneThird; + this->mWeights[23] = oneThird; + this->mWeights[27] = (Real)1; + + // control[j][i] is mControls[i + 4 * j], 0 <= i < 4, 0 <= j < 7 + this->mControls[0] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[4] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[8] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[12] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[16] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[20] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[24] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[1] = { (Real)2, (Real)0, (Real)1 }; + this->mControls[5] = { (Real)2, (Real)4, (Real)1 }; + this->mControls[9] = { (Real)-2, (Real)4, (Real)1 }; + this->mControls[13] = { (Real)-2, (Real)0, (Real)1 }; + this->mControls[17] = { (Real)-2, (Real)-4, (Real)1 }; + this->mControls[21] = { (Real)2, (Real)-4, (Real)1 }; + this->mControls[25] = { (Real)2, (Real)0, (Real)1 }; + this->mControls[2] = { (Real)2, (Real)0, (Real)-1 }; + this->mControls[6] = { (Real)2, (Real)4, (Real)-1 }; + this->mControls[10] = { (Real)-2, (Real)4, (Real)-1 }; + this->mControls[14] = { (Real)-2, (Real)0, (Real)-1 }; + this->mControls[18] = { (Real)-2, (Real)-4, (Real)-1 }; + this->mControls[22] = { (Real)2, (Real)-4, (Real)-1 }; + this->mControls[26] = { (Real)2, (Real)0, (Real)-1 }; + this->mControls[3] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[7] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[11] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[15] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[19] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[23] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[27] = { (Real)0, (Real)0, (Real)-1 }; + } + + private: + static BasisFunctionInput CreateBasisFunctionInputV() + { + BasisFunctionInput input; + input.numControls = 7; + input.degree = 3; + input.uniform = true; + input.periodic = false; + input.numUniqueKnots = 3; + input.uniqueKnots.resize(input.numUniqueKnots); + input.uniqueKnots[0] = { (Real)0, 4 }; + input.uniqueKnots[1] = { (Real)0.5, 3 }; + input.uniqueKnots[2] = { (Real)1, 4 }; + return input; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSSurface.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSSurface.h new file mode 100644 index 000000000000..f42bb131b53b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSSurface.h @@ -0,0 +1,253 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/10/28) + +#pragma once + +#include +#include +#include + +namespace gte +{ + template + class NURBSSurface : public ParametricSurface + { + public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points or weights, pass + // null pointers and later access the control points or weights via + // GetControls(), GetWeights(), SetControl(), or SetWeight() member + // functions. The 'controls' and 'weights' must be stored in + // row-major order, attribute[i0 + numControls0*i1]. As a 2D array, + // this corresponds to attribute2D[i1][i0]. To validate construction, + // create an object as shown: + // NURBSSurface surface(parameters); + // if (!surface) { ; } + NURBSSurface(BasisFunctionInput const& input0, + BasisFunctionInput const& input1, + Vector const* controls, Real const* weights) + : + ParametricSurface((Real)0, (Real)1, (Real)0, (Real)1, true) + { + BasisFunctionInput const* input[2] = { &input0, &input1 }; + for (int i = 0; i < 2; ++i) + { + mNumControls[i] = input[i]->numControls; + mBasisFunction[i].Create(*input[i]); + if (!mBasisFunction[i]) + { + // Errors were already generated during construction of + // the basis functions. + return; + } + } + + // The mBasisFunction stores the domain but so does + // ParametricSurface. + this->mUMin = mBasisFunction[0].GetMinDomain(); + this->mUMax = mBasisFunction[0].GetMaxDomain(); + this->mVMin = mBasisFunction[1].GetMinDomain(); + this->mVMax = mBasisFunction[1].GetMaxDomain(); + + // The replication of control points for periodic splines is + // avoided by wrapping the i-loop index in Evaluate. + int numControls = mNumControls[0] * mNumControls[1]; + mControls.resize(numControls); + mWeights.resize(numControls); + if (controls) + { + std::copy(controls, controls + numControls, mControls.begin()); + } + else + { + memset(mControls.data(), 0, mControls.size() * sizeof(mControls[0])); + } + if (weights) + { + std::copy(weights, weights + numControls, mWeights.begin()); + } + else + { + memset(mWeights.data(), 0, mWeights.size() * sizeof(mWeights[0])); + } + this->mConstructed = true; + } + + // Member access. The index 'dim' must be in {0,1}. + inline BasisFunction const& GetBasisFunction(int dim) const + { + return mBasisFunction[dim]; + } + + inline int GetNumControls(int dim) const + { + return mNumControls[dim]; + } + + inline Vector const* GetControls() const + { + return mControls.data(); + } + + inline Vector* GetControls() + { + return mControls.data(); + } + + inline Real const* GetWeights() const + { + return mWeights.data(); + } + + inline Real* GetWeights() + { + return mWeights.data(); + } + + void SetControl(int i0, int i1, Vector const& control) + { + if (0 <= i0 && i0 < GetNumControls(0) && 0 <= i1 && i1 < GetNumControls(1)) + { + mControls[i0 + mNumControls[0] * i1] = control; + } + } + + Vector const& GetControl(int i0, int i1) const + { + if (0 <= i0 && i0 < GetNumControls(0) && 0 <= i1 && i1 < GetNumControls(1)) + { + return mControls[i0 + mNumControls[0] * i1]; + } + else + { + return mControls[0]; + } + } + + void SetWeight(int i0, int i1, Real weight) + { + if (0 <= i0 && i0 < GetNumControls(0) && 0 <= i1 && i1 < GetNumControls(1)) + { + mWeights[i0 + mNumControls[0] * i1] = weight; + } + } + + Real const& GetWeight(int i0, int i1) const + { + if (0 <= i0 && i0 < GetNumControls(0) && 0 <= i1 && i1 < GetNumControls(1)) + { + return mWeights[i0 + mNumControls[0] * i1]; + } + else + { + return mWeights[0]; + } + } + + // Evaluation of the surface. The function supports derivative + // calculation through order 2; that is, maxOrder <= 2 is required. + // If you want only the position, pass in maxOrder of 0. If you want + // the position and first-order derivatives, pass in maxOrder of 1, + // and so on. The output 'values' are ordered as: position X; + // first-order derivatives dX/du, dX/dv; second-order derivatives + // d2X/du2, d2X/dudv, d2X/dv2. + virtual void Evaluate(Real u, Real v, unsigned int maxOrder, Vector values[6]) const + { + if (!this->mConstructed) + { + // Errors were already generated during construction. + for (int i = 0; i < 6; ++i) + { + values[i].MakeZero(); + } + return; + } + + int iumin, iumax, ivmin, ivmax; + mBasisFunction[0].Evaluate(u, maxOrder, iumin, iumax); + mBasisFunction[1].Evaluate(v, maxOrder, ivmin, ivmax); + + // Compute position. + Vector X; + Real w; + Compute(0, 0, iumin, iumax, ivmin, ivmax, X, w); + Real invW = ((Real)1) / w; + values[0] = invW * X; + + if (maxOrder >= 1) + { + // Compute first-order derivatives. + Vector XDerU; + Real wDerU; + Compute(1, 0, iumin, iumax, ivmin, ivmax, XDerU, wDerU); + values[1] = invW * (XDerU - wDerU * values[0]); + + Vector XDerV; + Real wDerV; + Compute(0, 1, iumin, iumax, ivmin, ivmax, XDerV, wDerV); + values[2] = invW * (XDerV - wDerV * values[0]); + + if (maxOrder >= 2) + { + // Compute second-order derivatives. + Vector XDerUU; + Real wDerUU; + Compute(2, 0, iumin, iumax, ivmin, ivmax, XDerUU, wDerUU); + values[3] = invW * (XDerUU - ((Real)2) * wDerU * values[1] - + wDerUU * values[0]); + + Vector XDerUV; + Real wDerUV; + Compute(1, 1, iumin, iumax, ivmin, ivmax, XDerUV, wDerUV); + values[4] = invW * (XDerUV - wDerU * values[2] - wDerV * values[1] + - wDerUV * values[0]); + + Vector XDerVV; + Real wDerVV; + Compute(0, 2, iumin, iumax, ivmin, ivmax, XDerVV, wDerVV); + values[5] = invW * (XDerVV - ((Real)2) * wDerV * values[2] - + wDerVV * values[0]); + } + } + } + + protected: + // Support for Evaluate(...). + void Compute(unsigned int uOrder, unsigned int vOrder, int iumin, + int iumax, int ivmin, int ivmax, Vector& X, Real& w) const + { + // The j*-indices introduce a tiny amount of overhead in order to handle + // both aperiodic and periodic splines. For aperiodic splines, j* = i* + // always. + + int const numControls0 = mNumControls[0]; + int const numControls1 = mNumControls[1]; + X.MakeZero(); + w = (Real)0; + for (int iv = ivmin; iv <= ivmax; ++iv) + { + Real tmpv = mBasisFunction[1].GetValue(vOrder, iv); + int jv = (iv >= numControls1 ? iv - numControls1 : iv); + for (int iu = iumin; iu <= iumax; ++iu) + { + Real tmpu = mBasisFunction[0].GetValue(uOrder, iu); + int ju = (iu >= numControls0 ? iu - numControls0 : iu); + int index = ju + numControls0 * jv; + Real tmp = tmpu * tmpv * mWeights[index]; + X += tmp * mControls[index]; + w += tmp; + } + } + } + + std::array, 2> mBasisFunction; + std::array mNumControls; + std::vector> mControls; + std::vector mWeights; + }; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSVolume.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSVolume.h new file mode 100644 index 000000000000..94dfbf6e3897 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSVolume.h @@ -0,0 +1,264 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/10/22) + +#pragma once + +#include +#include + +namespace gte +{ + template + class NURBSVolume + { + public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points or weights, pass + // null pointers and later access the control points or weights via + // GetControls(), GetWeights(), SetControl(), or SetWeight() member + // functions. The 'controls' and 'weights' must be stored in + // lexicographical order, + // attribute[i0 + numControls0 * (i1 + numControls1 * i2)] + // As a 3D array, this corresponds to attribute3D[i2][i1][i0]. + NURBSVolume(BasisFunctionInput const& input0, + BasisFunctionInput const& input1, + BasisFunctionInput const& input2, + Vector const* controls, Real const* weights) + : + mConstructed(false) + { + BasisFunctionInput const* input[3] = { &input0, &input1, &input2 }; + for (int i = 0; i < 3; ++i) + { + mNumControls[i] = input[i]->numControls; + mBasisFunction[i].Create(*input[i]); + if (!mBasisFunction[i]) + { + // Errors were already generated during construction of + // the basis functions. + return; + } + } + + // The replication of control points for periodic splines is + // avoided by wrapping the i-loop index in Evaluate. + int numControls = mNumControls[0] * mNumControls[1] * mNumControls[2]; + mControls.resize(numControls); + mWeights.resize(numControls); + if (controls) + { + std::copy(controls, controls + numControls, mControls.begin()); + } + else + { + memset(mControls.data(), 0, mControls.size() * sizeof(mControls[0])); + } + if (weights) + { + std::copy(weights, weights + numControls, mWeights.begin()); + } + else + { + memset(mWeights.data(), 0, mWeights.size() * sizeof(mWeights[0])); + } + mConstructed = true; + } + + // To validate construction, create an object as shown: + // NURBSVolume volume(parameters); + // if (!volume) { ; } + inline operator bool() const + { + return mConstructed; + } + + // Member access. The index 'dim' must be in {0,1,2}. + inline BasisFunction const& GetBasisFunction(int dim) const + { + return mBasisFunction[dim]; + } + + inline Real GetMinDomain(int dim) const + { + return mBasisFunction[dim].GetMinDomain(); + } + + inline Real GetMaxDomain(int dim) const + { + return mBasisFunction[dim].GetMaxDomain(); + } + + inline int GetNumControls(int dim) const + { + return mNumControls[dim]; + } + + inline Vector const* GetControls() const + { + return mControls.data(); + } + + inline Vector* GetControls() + { + return mControls.data(); + } + + inline Real const* GetWeights() const + { + return mWeights.data(); + } + + inline Real* GetWeights() + { + return mWeights.data(); + } + + // Evaluation of the volume. The function supports derivative + // calculation through order 2; that is, maxOrder <= 2 is required. + // If you want only the position, pass in maxOrder of 0. If you want + // the position and first-order derivatives, pass in maxOrder of 1, + // and so on. The output 'values' are ordered as: position X; + // first-order derivatives dX/du, dX/dv, dX/dw; second-order + // derivatives d2X/du2, d2X/dv2, d2X/dw2, d2X/dudv, d2X/dudw, + // d2X/dvdw. + void Evaluate(Real u, Real v, Real w, unsigned int maxOrder, Vector values[10]) const + { + if (!mConstructed) + { + // Errors were already generated during construction. + for (int i = 0; i < 10; ++i) + { + values[i].MakeZero(); + } + return; + } + + int iumin, iumax, ivmin, ivmax, iwmin, iwmax; + mBasisFunction[0].Evaluate(u, maxOrder, iumin, iumax); + mBasisFunction[1].Evaluate(v, maxOrder, ivmin, ivmax); + mBasisFunction[2].Evaluate(w, maxOrder, iwmin, iwmax); + + // Compute position. + Vector X; + Real h; + Compute(0, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, X, h); + Real invH = ((Real)1) / h; + values[0] = invH * X; + + if (maxOrder >= 1) + { + // Compute first-order derivatives. + Vector XDerU; + Real hDerU; + Compute(1, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerU, hDerU); + values[1] = invH * (XDerU - hDerU * values[0]); + + Vector XDerV; + Real hDerV; + Compute(0, 1, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerV, hDerV); + values[2] = invH * (XDerV - hDerV * values[0]); + + Vector XDerW; + Real hDerW; + Compute(0, 0, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerW, hDerW); + values[3] = invH * (XDerW - hDerW * values[0]); + + if (maxOrder >= 2) + { + // Compute second-order derivatives. + Vector XDerUU; + Real hDerUU; + Compute(2, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerUU, hDerUU); + values[4] = invH * (XDerUU - ((Real)2) * hDerU * values[1] - + hDerUU * values[0]); + + Vector XDerVV; + Real hDerVV; + Compute(0, 2, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerVV, hDerVV); + values[5] = invH * (XDerVV - ((Real)2) * hDerV * values[2] - + hDerVV * values[0]); + + Vector XDerWW; + Real hDerWW; + Compute(0, 0, 2, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerWW, hDerWW); + values[6] = invH * (XDerWW - ((Real)2) * hDerW * values[3] - + hDerWW * values[0]); + + Vector XDerUV; + Real hDerUV; + Compute(1, 1, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerUV, hDerUV); + values[7] = invH * (XDerUV - hDerU * values[2] + - hDerV * values[1] - hDerUV * values[0]); + + Vector XDerUW; + Real hDerUW; + Compute(1, 0, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerUW, hDerUW); + values[8] = invH * (XDerUW - hDerU * values[3] + - hDerW * values[1] - hDerUW * values[0]); + + Vector XDerVW; + Real hDerVW; + Compute(0, 1, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerVW, hDerVW); + values[9] = invH * (XDerVW - hDerV * values[3] + - hDerW * values[2] - hDerVW * values[0]); + } + } + } + + private: + // Support for Evaluate(...). + void Compute(unsigned int uOrder, unsigned int vOrder, + unsigned int wOrder, int iumin, int iumax, int ivmin, int ivmax, + int iwmin, int iwmax, Vector& X, Real& h) const + { + // The j*-indices introduce a tiny amount of overhead in order to + // handle both aperiodic and periodic splines. For aperiodic + // splines, j* = i* always. + + int const numControls0 = mNumControls[0]; + int const numControls1 = mNumControls[1]; + int const numControls2 = mNumControls[2]; + X.MakeZero(); + h = (Real)0; + for (int iw = iwmin; iw <= iwmax; ++iw) + { + Real tmpw = mBasisFunction[2].GetValue(wOrder, iw); + int jw = (iw >= numControls2 ? iw - numControls2 : iw); + for (int iv = ivmin; iv <= ivmax; ++iv) + { + Real tmpv = mBasisFunction[1].GetValue(vOrder, iv); + Real tmpvw = tmpv * tmpw; + int jv = (iv >= numControls1 ? iv - numControls1 : iv); + for (int iu = iumin; iu <= iumax; ++iu) + { + Real tmpu = mBasisFunction[0].GetValue(uOrder, iu); + int ju = (iu >= numControls0 ? iu - numControls0 : iu); + int index = ju + numControls0 * (jv + numControls1 * jw); + Real tmp = (tmpu * tmpvw) * mWeights[index]; + X += tmp * mControls[index]; + h += tmp; + } + } + } + } + + std::array, 3> mBasisFunction; + std::array mNumControls; + std::vector> mControls; + std::vector mWeights; + bool mConstructed; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNaturalSplineCurve.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNaturalSplineCurve.h new file mode 100644 index 000000000000..d6a09c169d2d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNaturalSplineCurve.h @@ -0,0 +1,404 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/02/17) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class NaturalSplineCurve : public ParametricCurve +{ +public: + // Construction and destruction. The object copies the input arrays. The + // number of points M must be at least 2. The first constructor is for a + // spline with second derivatives zero at the endpoints (isFree = true) + // or a spline that is closed (isFree = false). The second constructor is + // for clamped splines, where you specify the first derivatives at the + // endpoints. Usually, derivative0 = points[1] - points[0] at the first + // point and derivative1 = points[M-1] - points[M-2]. To validate + // construction, create an object as shown: + // NaturalSplineCurve curve(parameters); + // if (!curve) { ; } + virtual ~NaturalSplineCurve(); + NaturalSplineCurve(bool isFree, int numPoints, + Vector const* points, Real const* times); + NaturalSplineCurve(int numPoints, Vector const* points, + Real const* times, Vector const& derivative0, + Vector const& derivative1); + + // Member access. + inline int GetNumPoints() const; + inline Vector const* GetPoints() const; + + // Evaluation of the curve. The function supports derivative calculation + // through order 3; that is, maxOrder <= 3 is required. If you want + // only the position, pass in maxOrder of 0. If you want the position and + // first derivative, pass in maxOrder of 1, and so on. The output + // 'values' are ordered as: position, first derivative, second derivative, + // third derivative. + virtual void Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const; + +protected: + // Support for construction. + void CreateFree(); + void CreateClosed(); + void CreateClamped(Vector const& derivative0, + Vector const& derivative1); + + // Determine the index i for which times[i] <= t < times[i+1]. + void GetKeyInfo(Real t, int& key, Real& dt) const; + + // Polynomial coefficients. mA are the points (constant coefficients of + // polynomials. mB are the degree 1 coefficients, mC are the degree 2 + // coefficients, and mD are the degree 3 coefficients. + std::vector> mA, mB, mC, mD; +}; + + +template +NaturalSplineCurve::~NaturalSplineCurve() +{ +} + +template +NaturalSplineCurve::NaturalSplineCurve(bool isFree, int numPoints, + Vector const* points, Real const* times) + : + ParametricCurve(numPoints - 1, times) +{ + if (numPoints < 2 || !points) + { + LogError("Invalid input."); + return; + } + + mA.resize(numPoints); + std::copy(points, points + numPoints, mA.begin()); + + if (isFree) + { + CreateFree(); + } + else + { + CreateClosed(); + } + + this->mConstructed = true; +} + +template +NaturalSplineCurve::NaturalSplineCurve(int numPoints, + Vector const* points, Real const* times, + Vector const& derivative0, Vector const& derivative1) + : + ParametricCurve(numPoints - 1, times) +{ + if (numPoints < 2 || !points) + { + LogError("Invalid input."); + return; + } + + mA.resize(numPoints); + std::copy(points, points + numPoints, mA.begin()); + + CreateClamped(derivative0, derivative1); + this->mConstructed = true; +} + +template inline +int NaturalSplineCurve::GetNumPoints() const +{ + return static_cast(mA.size()); +} + +template inline +Vector const* NaturalSplineCurve::GetPoints() const +{ + return &mA[0]; +} + +template +void NaturalSplineCurve::Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const +{ + if (!this->mConstructed) + { + // Errors were already generated during construction. + for (unsigned int order = 0; order < 4; ++order) + { + values[order].MakeZero(); + } + return; + } + + int key = 0; + Real dt = (Real)0; + GetKeyInfo(t, key, dt); + + // Compute position. + values[0] = mA[key] + dt * (mB[key] + dt * (mC[key] + dt * mD[key])); + if (maxOrder >= 1) + { + // Compute first derivative. + values[1] = mB[key] + dt * (((Real)2) * mC[key] + + ((Real)3) * dt * mD[key]); + if (maxOrder >= 2) + { + // Compute second derivative. + values[2] = ((Real)2) * mC[key] + ((Real)6) * dt * mD[key]; + if (maxOrder == 3) + { + values[3] = ((Real)6) * mD[key]; + } + else + { + values[3].MakeZero(); + } + } + } +} + +template +void NaturalSplineCurve::CreateFree() +{ + int numSegments = GetNumPoints() - 1; + std::vector dt(numSegments); + for (int i = 0; i < numSegments; ++i) + { + dt[i] = this->mTime[i + 1] - this->mTime[i]; + } + + std::vector d2t(numSegments); + for (int i = 1; i < numSegments; ++i) + { + d2t[i] = this->mTime[i + 1] - this->mTime[i - 1]; + } + + std::vector> alpha(numSegments); + for (int i = 1; i < numSegments; ++i) + { + Vector numer = ((Real)3)*(dt[i - 1] * mA[i + 1] - + d2t[i] * mA[i] + dt[i] * mA[i - 1]); + Real invDenom = ((Real)1) / (dt[i - 1] * dt[i]); + alpha[i] = invDenom * numer; + } + + std::vector ell(numSegments + 1); + std::vector mu(numSegments); + std::vector> z(numSegments + 1); + Real inv; + + ell[0] = (Real)1; + mu[0] = (Real)0; + z[0].MakeZero(); + for (int i = 1; i < numSegments; ++i) + { + ell[i] = ((Real)2) * d2t[i] - dt[i - 1] * mu[i - 1]; + inv = ((Real)1) / ell[i]; + mu[i] = inv * dt[i]; + z[i] = inv * (alpha[i] - dt[i - 1] * z[i - 1]); + } + ell[numSegments] = (Real)1; + z[numSegments].MakeZero(); + + mB.resize(numSegments); + mC.resize(numSegments + 1); + mD.resize(numSegments); + + Real oneThird = ((Real)1) / (Real)3; + mC[numSegments].MakeZero(); + for (int i = numSegments - 1; i >= 0; --i) + { + mC[i] = z[i] - mu[i] * mC[i + 1]; + inv = ((Real)1) / dt[i]; + mB[i] = inv * (mA[i + 1] - mA[i]) - oneThird * dt[i] * (mC[i + 1] + + ((Real)2) * mC[i]); + mD[i] = oneThird * inv * (mC[i + 1] - mC[i]); + } +} + +template +void NaturalSplineCurve::CreateClosed() +{ + // TODO: A general linear system solver is used here. The matrix + // corresponding to this case is actually "cyclic banded", so a faster + // linear solver can be used. The current linear system code does not + // have such a solver. + + int numSegments = GetNumPoints() - 1; + std::vector dt(numSegments); + for (int i = 0; i < numSegments; ++i) + { + dt[i] = this->mTime[i + 1] - this->mTime[i]; + } + + // Construct matrix of system. + GMatrix mat(numSegments + 1, numSegments + 1); + mat(0, 0) = (Real)1; + mat(0, numSegments) = (Real)-1; + for (int i = 1; i <= numSegments - 1; ++i) + { + mat(i, i - 1) = dt[i - 1]; + mat(i, i) = ((Real)2) * (dt[i - 1] + dt[i]); + mat(i, i + 1) = dt[i]; + } + mat(numSegments, numSegments - 1) = dt[numSegments - 1]; + mat(numSegments, 0) = ((Real)2) * (dt[numSegments - 1] + dt[0]); + mat(numSegments, 1) = dt[0]; + + // Construct right-hand side of system. + mC.resize(numSegments + 1); + mC[0].MakeZero(); + Real inv0, inv1; + for (int i = 1; i <= numSegments - 1; ++i) + { + inv0 = ((Real)1) / dt[i]; + inv1 = ((Real)1) / dt[i - 1]; + mC[i] = ((Real)3) * (inv0 * (mA[i + 1] - mA[i]) - + inv1*(mA[i] - mA[i - 1])); + } + inv0 = ((Real)1) / dt[0]; + inv1 = ((Real)1) / dt[numSegments - 1]; + mC[numSegments] = ((Real)3) * (inv0 * (mA[1] - mA[0]) - + inv1 * (mA[0] - mA[numSegments - 1])); + + // Solve the linear systems. + GMatrix invMat = Inverse(mat); + GVector input(numSegments + 1); + GVector output(numSegments + 1); + for (int j = 0; j < N; ++j) + { + for (int i = 0; i <= numSegments; ++i) + { + input[i] = mC[i][j]; + } + output = invMat * input; + for (int i = 0; i <= numSegments; ++i) + { + mC[i][j] = output[i]; + } + } + + Real oneThird = ((Real)1) / (Real)3; + mB.resize(numSegments); + mD.resize(numSegments); + for (int i = 0; i < numSegments; ++i) + { + inv0 = ((Real)1) / dt[i]; + mB[i] = inv0 * (mA[i + 1] - mA[i]) - oneThird * (mC[i + 1] + + ((Real)2) * mC[i]) * dt[i]; + mD[i] = oneThird * inv0 * (mC[i + 1] - mC[i]); + } +} + +template +void NaturalSplineCurve::CreateClamped( + Vector const& derivative0, Vector const& derivative1) +{ + int numSegments = GetNumPoints() - 1; + std::vector dt(numSegments); + for (int i = 0; i < numSegments; ++i) + { + dt[i] = this->mTime[i + 1] - this->mTime[i]; + } + + std::vector d2t(numSegments); + for (int i = 1; i < numSegments; ++i) + { + d2t[i] = this->mTime[i + 1] - this->mTime[i - 1]; + } + + std::vector> alpha(numSegments + 1); + Real inv = ((Real)1) / dt[0]; + alpha[0] = ((Real)3) * (inv * (mA[1] - mA[0]) - derivative0); + inv = ((Real)1) / dt[numSegments - 1]; + alpha[numSegments] = ((Real)3)*(derivative1 - + inv * (mA[numSegments] - mA[numSegments - 1])); + for (int i = 1; i < numSegments; ++i) + { + Vector numer = ((Real)3)*(dt[i - 1] * mA[i + 1] - + d2t[i] * mA[i] + dt[i] * mA[i - 1]); + Real invDenom = ((Real)1) / (dt[i - 1] * dt[i]); + alpha[i] = invDenom*numer; + } + + std::vector ell(numSegments + 1); + std::vector mu(numSegments); + std::vector> z(numSegments + 1); + + ell[0] = ((Real)2) * dt[0]; + mu[0] = (Real)0.5; + inv = ((Real)1) / ell[0]; + z[0] = inv * alpha[0]; + + for (int i = 1; i < numSegments; ++i) + { + ell[i] = ((Real)2) * d2t[i] - dt[i - 1] * mu[i - 1]; + inv = ((Real)1) / ell[i]; + mu[i] = inv * dt[i]; + z[i] = inv * (alpha[i] - dt[i - 1] * z[i - 1]); + } + ell[numSegments] = dt[numSegments - 1] * + (((Real)2) - mu[numSegments - 1]); + inv = ((Real)1) / ell[numSegments]; + z[numSegments] = inv * (alpha[numSegments] - dt[numSegments - 1] * + z[numSegments - 1]); + + mB.resize(numSegments); + mC.resize(numSegments + 1); + mD.resize(numSegments); + + Real oneThird = ((Real)1) / (Real)3; + mC[numSegments] = z[numSegments]; + for (int i = numSegments - 1; i >= 0; --i) + { + mC[i] = z[i] - mu[i] * mC[i + 1]; + inv = ((Real)1) / dt[i]; + mB[i] = inv * (mA[i + 1] - mA[i]) - oneThird*dt[i] * (mC[i + 1] + + ((Real)2) * mC[i]); + mD[i] = oneThird * inv * (mC[i + 1] - mC[i]); + } +} + +template +void NaturalSplineCurve::GetKeyInfo(Real t, int& key, Real& dt) const +{ + int numSegments = GetNumPoints() - 1; + if (t <= this->mTime[0]) + { + key = 0; + dt = (Real)0; + } + else if (t >= this->mTime[numSegments]) + { + key = numSegments - 1; + dt = this->mTime[numSegments] - this->mTime[numSegments - 1]; + } + else + { + for (int i = 0; i < numSegments; ++i) + { + if (t < this->mTime[i + 1]) + { + key = i; + dt = t - this->mTime[i]; + break; + } + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNearestNeighborQuery.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNearestNeighborQuery.h new file mode 100644 index 000000000000..5fd52b9bbf6a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNearestNeighborQuery.h @@ -0,0 +1,238 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2017/07/26) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +// Use a kd-tree for sorting used in a query for finding nearest neighbors +// of a point in a space of the specified dimension N. The split order is +// always 0,1,2,...,N-1. The number of sites at a leaf node is controlled +// by 'maxLeafSize' and the maximum level of the tree is controlled by +// 'maxLevels'. The points are of type Vector. The 'Site' is a +// structure of information that minimally implements the function +// 'Vector GetPosition () const'. The Site template parameter +// allows the query to be applied even when it has more local information +// than just point location. +template +class NearestNeighborQuery +{ +public: + // Construction. + NearestNeighborQuery(std::vector const& sites, int maxLeafSize, int maxLevel); + + // Member access. + inline int GetMaxLeafSize () const; + inline int GetMaxLevel () const; + + // Compute up to MaxNeighbors nearest neighbors within the specified + // radius of the point. The returned integer is the number of neighbors + // found, possibly zero. The neighbors array stores indices into the + // array passed to the constructor. + template + int FindNeighbors(Vector const& point, Real radius, + std::array& neighbors) const; + +private: + typedef std::pair, int> SortedPoint; + + struct Node + { + Real split; + int axis; + int numSites; + int siteOffset; + int left; + int right; + }; + + // Populate the node so that it contains the points split along the + // coordinate axes. + void Build(int numSites, int siteOffset, int nodeIndex, int level); + + int mMaxLeafSize; + int mMaxLevel; + std::vector mSortedPoints; + std::vector mNodes; +}; + + +template +NearestNeighborQuery::NearestNeighborQuery( + std::vector const& sites, int maxLeafSize, int maxLevel) + : + mMaxLeafSize(maxLeafSize), + mMaxLevel(maxLevel), + mSortedPoints(sites.size()) +{ + int const numSites = static_cast(sites.size()); + for (int i = 0; i < numSites; ++i) + { + mSortedPoints[i] = std::make_pair(sites[i].GetPosition(), i); + } + + mNodes.push_back(Node()); + Build(numSites, 0, 0, 0); +} + +template +inline int NearestNeighborQuery::GetMaxLeafSize() const +{ + return mMaxLeafSize; +} + +template +inline int NearestNeighborQuery::GetMaxLevel() const +{ + return mMaxLevel; +} + +template +template +int NearestNeighborQuery::FindNeighbors(Vector const& point, + Real radius, std::array& neighbors) const +{ + Real sqrRadius = radius * radius; + int numNeighbors = 0; + std::array localNeighbors; + std::array neighborSqrLength; + for (int i = 0; i <= MaxNeighbors; ++i) + { + localNeighbors[i] = -1; + neighborSqrLength[i] = std::numeric_limits::max(); + } + + // The kd-tree construction is recursive, simulated here by using a stack. + // The maximum depth is limited to 32, because the number of sites is + // limited to 2^{32} (the number of 32-bit integer indices). + std::array stack; + int top = 0; + stack[0] = 0; + + while (top >= 0) + { + Node node = mNodes[stack[top--]]; + + if (node.siteOffset != -1) + { + for (int i = 0, j = node.siteOffset; i < node.numSites; ++i, ++j) + { + Vector diff = mSortedPoints[j].first - point; + Real sqrLength = Dot(diff, diff); + if (sqrLength <= sqrRadius) + { + // Maintain the nearest neighbors. + int k; + for (k = 0; k < numNeighbors; ++k) + { + if (sqrLength <= neighborSqrLength[k]) + { + for (int n = numNeighbors; n > k; --n) + { + localNeighbors[n] = localNeighbors[n - 1]; + neighborSqrLength[n] = neighborSqrLength[n - 1]; + } + break; + } + } + if (k < MaxNeighbors) + { + localNeighbors[k] = mSortedPoints[j].second; + neighborSqrLength[k] = sqrLength; + } + if (numNeighbors < MaxNeighbors) + { + ++numNeighbors; + } + } + } + } + + if (node.left != -1 && point[node.axis] - radius <= node.split) + { + stack[++top] = node.left; + } + + if (node.right != -1 && point[node.axis] + radius >= node.split) + { + stack[++top] = node.right; + } + } + + for (int i = 0; i < numNeighbors; ++i) + { + neighbors[i] = localNeighbors[i]; + } + + return numNeighbors; +} + +template +void NearestNeighborQuery::Build(int numSites, int siteOffset, + int nodeIndex, int level) +{ + LogAssert(siteOffset != -1, "Invalid site offset."); + LogAssert(nodeIndex != -1, "Invalid node index."); + LogAssert(numSites > 0, "Empty point list."); + + Node& node = mNodes[nodeIndex]; + node.numSites = numSites; + + if (numSites > mMaxLeafSize && level <= mMaxLevel) + { + int halfNumSites = numSites / 2; + + // The point set is too large for a leaf node, so split it at the + // median. The O(m log m) sort is not needed; rather, we locate the + // median using an order statistic construction that is expected + // time O(m). + int const axis = level % N; + auto sorter = [axis](SortedPoint const& p0, SortedPoint const& p1) + { + return p0.first[axis] < p1.first[axis]; + }; + + auto begin = mSortedPoints.begin() + siteOffset; + auto mid = mSortedPoints.begin() + siteOffset + halfNumSites; + auto end = mSortedPoints.begin() + siteOffset + numSites; + std::nth_element(begin, mid, end, sorter); + + // Get the median position. + node.split = mSortedPoints[siteOffset + halfNumSites].first[axis]; + node.axis = axis; + node.siteOffset = -1; + + // Apply a divide-and-conquer step. + int left = (int)mNodes.size(), right = left + 1; + node.left = left; + node.right = right; + mNodes.push_back(Node()); + mNodes.push_back(Node()); + + int nextLevel = level + 1; + Build(halfNumSites, siteOffset, left, nextLevel); + Build(numSites - halfNumSites, siteOffset + halfNumSites, right, nextLevel); + } + else + { + // The number of points is small enough, so make this node a leaf. + node.split = std::numeric_limits::max(); + node.axis = -1; + node.siteOffset = siteOffset; + node.left = -1; + node.right = -1; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOBBTreeOfPoints.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOBBTreeOfPoints.h new file mode 100644 index 000000000000..fafd3900ca6e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOBBTreeOfPoints.h @@ -0,0 +1,359 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.10.1 (2017/09/16) + +#pragma once + +#include +#include + +// The depth of a node in a (nonempty) tree is the distance from the node to +// the root of the tree. The height is the maximum depth. A tree with a +// single node has height 0. The set of nodes of a tree with the same depth +// is refered to as a level of a tree (corresponding to that depth). A +// complete binary tree of height H has 2^{H+1}-1 nodes. The level +// corresponding to depth D has 2^D nodes, in which case the number of +// leaf nodes (depth H) is 2^H. + +namespace gte +{ + +template +class OBBTreeForPoints +{ +public: + struct Node + { + Node(); + OrientedBox3 box; + uint32_t depth; + uint32_t minIndex, maxIndex; + uint32_t leftChild, rightChild; + }; + + + // The 'points' array is a collection of vertices, each occupying a + // chunk of memory with 'stride' bytes. A vertex must start at the + // first byte of this chunk but does not necessarily fill it. The + // 'height' specifies the height of the tree and must be no larger + // than 31. If it is set to std::numeric_limits::max(), + // then the entire tree is built and the actual height is computed + // from 'numPoints'. + OBBTreeForPoints(uint32_t numPoints, char const* points, size_t stride, + uint32_t height = std::numeric_limits::max()); + + // Member access. + inline uint32_t GetNumPoints() const; + inline char const* GetPoints() const; + inline size_t GetStride() const; + inline std::vector const& GetTree() const; + inline uint32_t GetHeight() const; + inline std::vector const& GetPartition() const; + +private: + inline Vector3 GetPosition(uint32_t index) const; + void BuildTree(Node& node, uint32_t i0, uint32_t i1); + void ComputeOBB(uint32_t i0, uint32_t i1, OrientedBox3& box); + void SplitPoints(uint32_t i0, uint32_t i1, uint32_t& j0, uint32_t& j1, + Vector3 const& origin, Vector3 const& direction); + + struct ProjectionInfo + { + ProjectionInfo(); + bool operator< (ProjectionInfo const& info) const; + uint32_t pointIndex; + Real projection; + }; + + uint32_t mNumPoints; + char const* mPoints; + size_t mStride; + uint32_t mHeight; + std::vector mTree; + std::vector mPartition; +}; + +template +OBBTreeForPoints::OBBTreeForPoints(uint32_t numPoints, char const* points, size_t stride, uint32_t height) + : + mNumPoints(numPoints), + mPoints(points), + mStride(stride), + mHeight(height), + mPartition(numPoints) +{ + // The tree nodes are indexed by 32-bit unsigned integers, so + // the number of nodes can be at most 2^{32} - 1. This limits + // the height to 31. + + uint32_t numNodes; + if (mHeight == std::numeric_limits::max()) + { + uint32_t minPowerOfTwo = (uint32_t)RoundUpToPowerOfTwo(mNumPoints); + mHeight = Log2OfPowerOfTwo(minPowerOfTwo); + numNodes = 2 * mNumPoints - 1; + } + else + { + // The maximum level cannot exceed 30 because we are storing the + // indices into the node array as 32-bit unsigned integers. + if (mHeight < 32) + { + numNodes = (uint32_t)(1ULL << (mHeight + 1)) - 1; + } + else + { + // When the precondition is not met, return a tree of + // height 0 (a single node). + mHeight = 0; + numNodes = 1; + } + } + + // The tree is built recursively. A reference to a Node is passed + // to BuildTree and nodes are appended to a std::vector. Because the + // references are on the stack, we must guarantee no reallocations + // to avoid invalidating the references. TODO: This design can be + // modified to pass indices to the nodes so that reallocation is not + // a problem. + mTree.reserve(numNodes); + + // Build the tree recursively. The array mPartition stores the + // indices into the 'points' array so that at a node, the points + // represented by the node are those indexed by the range + // [node.minIndex, node.maxIndex]. + for (uint32_t i = 0; i < mNumPoints; ++i) + { + mPartition[i] = i; + } + mTree.push_back(Node()); + BuildTree(mTree.back(), 0, mNumPoints - 1); +} + +template +inline uint32_t OBBTreeForPoints::GetNumPoints() const +{ + return mNumPoints; +} + +template +inline char const* OBBTreeForPoints::GetPoints() const +{ + return mPoints; +} + +template +inline size_t OBBTreeForPoints::GetStride() const +{ + return mStride; +} + +template +inline std::vector::Node> const& OBBTreeForPoints::GetTree() const +{ + return mTree; +} + +template +inline uint32_t OBBTreeForPoints::GetHeight() const +{ + return mHeight; +} + +template +inline std::vector const& OBBTreeForPoints::GetPartition() const +{ + return mPartition; +} + +template +inline Vector3 OBBTreeForPoints::GetPosition(uint32_t index) const +{ + return *reinterpret_cast const*>(mPoints + index * mStride); +} + +template +void OBBTreeForPoints::BuildTree(Node& node, uint32_t i0, uint32_t i1) +{ + node.minIndex = i0; + node.maxIndex = i1; + + if (i0 == i1) + { + // We are at a leaf node. The left and right child indices were + // set to std::numeric_limits::max() during construction. + + // Create a degenerate box whose center is the point. + node.box.center = GetPosition(mPartition[i0]); + node.box.axis[0] = Vector3{ (Real)1, (Real)0, (Real)0 }; + node.box.axis[1] = Vector3{ (Real)0, (Real)1, (Real)0 }; + node.box.axis[2] = Vector3{ (Real)0, (Real)0, (Real)1 }; + node.box.extent = Vector3{ (Real)0, (Real)0, (Real)0 }; + } + else // i0 < i1 + { + // We are at an interior node. Compute an oriented bounding box. + ComputeOBB(i0, i1, node.box); + + if (node.depth == mHeight) + { + return; + } + + // Use the box axis corresponding to largest extent for the + // splitting axis. Partition the points into two subsets, one for + // the left child and one for the right child. The subsets have + // numbers of elements that differ by at most 1, so the tree is + // balanced. + Vector3 axis2 = node.box.axis[2]; + uint32_t j0, j1; + SplitPoints(i0, i1, j0, j1, node.box.center, axis2); + + node.leftChild = static_cast(mTree.size()); + node.rightChild = node.leftChild + 1; + mTree.push_back(Node()); + Node& leftTree = mTree.back(); + mTree.push_back(Node()); + Node& rightTree = mTree.back(); + leftTree.depth = node.depth + 1; + rightTree.depth = node.depth + 1; + BuildTree(leftTree, i0, j0); + BuildTree(rightTree, j1, i1); + } +} + +template +void OBBTreeForPoints::ComputeOBB(uint32_t i0, uint32_t i1, OrientedBox3& box) +{ + // Compute the mean of the points. + Vector3 zero{ (Real)0, (Real)0, (Real)0 }; + box.center = zero; + for (uint32_t i = i0; i <= i1; ++i) + { + box.center += GetPosition(mPartition[i]); + } + Real invSize = ((Real)1) / (Real)(i1 - i0 + 1); + box.center *= invSize; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (uint32_t i = i0; i <= i1; ++i) + { + Vector3 diff = GetPosition(mPartition[i]) - box.center; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + covar00 *= invSize; + covar01 *= invSize; + covar02 *= invSize; + covar11 *= invSize; + covar12 *= invSize; + covar22 *= invSize; + + // Solve the eigensystem and use the eigenvectors for the box axes. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, eval, evec); + for (int i = 0; i < 3; ++i) + { + box.axis[i] = evec[i]; + } + box.extent = eval; + + // Let C be the box center and let U0, U1, and U2 be the box axes. + // Each input point is of the form X = C + y0*U0 + y1*U1 + y2*U2. + // The following code computes min(y0), max(y0), min(y1), max(y1), + // min(y2), and max(y2). The box center is then adjusted to be + // C' = C + 0.5*(min(y0)+max(y0))*U0 + 0.5*(min(y1)+max(y1))*U1 + + // 0.5*(min(y2)+max(y2))*U2 + Vector3 pmin = zero, pmax = zero; + for (uint32_t i = i0; i <= i1; ++i) + { + Vector3 diff = GetPosition(mPartition[i]) - box.center; + for (int j = 0; j < 3; ++j) + { + Real dot = Dot(diff, box.axis[j]); + if (dot < pmin[j]) + { + pmin[j] = dot; + } + else if (dot > pmax[j]) + { + pmax[j] = dot; + } + } + } + + Real const half(0.5); + for (int j = 0; j < 3; ++j) + { + box.center += (half * (pmin[j] + pmax[j])) * box.axis[j]; + box.extent[j] = half * (pmax[j] - pmin[j]); + } +} + +template +void OBBTreeForPoints::SplitPoints(uint32_t i0, uint32_t i1, uint32_t& j0, uint32_t& j1, + Vector3 const& origin, Vector3 const& direction) +{ + // Project the points onto the splitting axis. + uint32_t numProjections = i1 - i0 + 1; + std::vector info(numProjections); + uint32_t i, k; + for (i = i0, k = 0; i <= i1; ++i, ++k) + { + Vector3 diff = GetPosition(mPartition[i]) - origin; + info[k].pointIndex = mPartition[i]; + info[k].projection = Dot(direction, diff); + } + + // Partition the projections by the median. + uint32_t medianIndex = (numProjections - 1) / 2; + std::nth_element(info.begin(), info.begin() + medianIndex, info.end()); + + // Partition the points by the median. + for (k = 0, j0 = i0 - 1; k <= medianIndex; ++k) + { + mPartition[++j0] = info[k].pointIndex; + } + for (j1 = i1 + 1; k < numProjections; ++k) + { + mPartition[--j1] = info[k].pointIndex; + } +} + +template +OBBTreeForPoints::Node::Node() + : + depth(0), + minIndex(std::numeric_limits::max()), + maxIndex(std::numeric_limits::max()), + leftChild(std::numeric_limits::max()), + rightChild(std::numeric_limits::max()) +{ +} + +template +OBBTreeForPoints::ProjectionInfo::ProjectionInfo() + : + pointIndex(0), + projection((Real)0) +{ +} + +template +bool OBBTreeForPoints::ProjectionInfo::operator< (ProjectionInfo const& info) const +{ + return projection < info.projection; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeEuler.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeEuler.h new file mode 100644 index 000000000000..eb832ef3b2ab --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeEuler.h @@ -0,0 +1,60 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The TVector template parameter allows you to create solvers with +// Vector when the dimension N is known at compile time or +// GVector when the dimension N is known at run time. Both classes +// have 'int GetSize() const' that allow OdeSolver-derived classes to query +// for the dimension. + +namespace gte +{ + +template +class OdeEuler : public OdeSolver +{ +public: + // Construction and destruction. + virtual ~OdeEuler(); + OdeEuler(Real tDelta, + std::function const& F); + + // Estimate x(t + tDelta) from x(t) using dx/dt = F(t,x). You may allow + // xIn and xOut to be the same object. + virtual void Update(Real tIn, TVector const& xIn, Real& tOut, + TVector& xOut); +}; + + +template +OdeEuler::~OdeEuler() +{ +} + +template +OdeEuler::OdeEuler(Real tDelta, + std::function const& F) + : + OdeSolver(tDelta, F) +{ +} + +template +void OdeEuler::Update(Real tIn, TVector const& xIn, + Real& tOut, TVector& xOut) +{ + TVector fVector = this->mFunction(tIn, xIn); + tOut = tIn + this->mTDelta; + xOut = xIn + this->mTDelta * fVector; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeImplicitEuler.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeImplicitEuler.h new file mode 100644 index 000000000000..c6cb571819ea --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeImplicitEuler.h @@ -0,0 +1,77 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The TVector template parameter allows you to create solvers with +// Vector when the dimension N is known at compile time or +// GVector when the dimension N is known at run time. Both classes +// have 'int GetSize() const' that allow OdeSolver-derived classes to query +// for the dimension. The TMatrix parameter must be either Matrix +// or GMatrix accordingly. +// +// The function F(t,x) has input t, a scalar, and input x, an N-vector. +// The first derivative matrix with respect to x is DF(t,x), an +// N-by-N matrix. Entry DF(r,c) is the derivative of F[r] with +// respect to x[c]. + +namespace gte +{ + +template +class OdeImplicitEuler : public OdeSolver +{ +public: + // Construction and destruction. + virtual ~OdeImplicitEuler(); + OdeImplicitEuler(Real tDelta, + std::function const& F, + std::function const& DF); + + // Estimate x(t + tDelta) from x(t) using dx/dt = F(t,x). You may allow + // xIn and xOut to be the same object. + virtual void Update(Real tIn, TVector const& xIn, Real& tOut, + TVector& xOut); + +private: + std::function mDerivativeFunction; +}; + + +template +OdeImplicitEuler::~OdeImplicitEuler() +{ +} + +template +OdeImplicitEuler::OdeImplicitEuler(Real tDelta, + std::function const& F, + std::function const& DF) + : + OdeSolver(tDelta, F), + mDerivativeFunction(DF) +{ +} + +template +void OdeImplicitEuler::Update(Real tIn, + TVector const& xIn, Real& tOut, TVector& xOut) +{ + TVector fVector = this->mFunction(tIn, xIn); + TMatrix dfMatrix = mDerivativeFunction(tIn, xIn); + TMatrix dgMatrix = TMatrix::Identity() - this->mTDelta * dfMatrix; + TMatrix dgInverse = Inverse(dgMatrix); + fVector = dgInverse * fVector; + tOut = tIn + this->mTDelta; + xOut = xIn + this->mTDelta * fVector; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeMidpoint.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeMidpoint.h new file mode 100644 index 000000000000..56357772c983 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeMidpoint.h @@ -0,0 +1,67 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The TVector template parameter allows you to create solvers with +// Vector when the dimension N is known at compile time or +// GVector when the dimension N is known at run time. Both classes +// have 'int GetSize() const' that allow OdeSolver-derived classes to query +// for the dimension. + +namespace gte +{ + +template +class OdeMidpoint : public OdeSolver +{ +public: + // Construction and destruction. + virtual ~OdeMidpoint(); + OdeMidpoint(Real tDelta, + std::function const& F); + + // Estimate x(t + tDelta) from x(t) using dx/dt = F(t,x). You may allow + // xIn and xOut to be the same object. + virtual void Update(Real tIn, TVector const& xIn, Real& tOut, + TVector& xOut); +}; + + +template +OdeMidpoint::~OdeMidpoint() +{ +} + +template +OdeMidpoint::OdeMidpoint(Real tDelta, + std::function const& F) + : + OdeSolver(tDelta, F) +{ +} + +template +void OdeMidpoint::Update(Real tIn, TVector const& xIn, + Real& tOut, TVector& xOut) +{ + // Compute the first step. + Real halfTDelta = ((Real)0.5) * this->mTDelta; + TVector fVector = this->mFunction(tIn, xIn); + TVector xTemp = xIn + halfTDelta * fVector; + + // Compute the second step. + Real halfT = tIn + halfTDelta; + fVector = this->mFunction(halfT, xTemp); + tOut = tIn + this->mTDelta; + xOut = xIn + this->mTDelta * fVector; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeRungeKutta4.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeRungeKutta4.h new file mode 100644 index 000000000000..100ee5a047c1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeRungeKutta4.h @@ -0,0 +1,77 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The TVector template parameter allows you to create solvers with +// Vector when the dimension N is known at compile time or +// GVector when the dimension N is known at run time. Both classes +// have 'int GetSize() const' that allow OdeSolver-derived classes to query +// for the dimension. + +namespace gte +{ + +template +class OdeRungeKutta4 : public OdeSolver +{ +public: + // Construction and destruction. + virtual ~OdeRungeKutta4(); + OdeRungeKutta4(Real tDelta, + std::function const& F); + + // Estimate x(t + tDelta) from x(t) using dx/dt = F(t,x). You may allow + // xIn and xOut to be the same object. + virtual void Update(Real tIn, TVector const& xIn, Real& tOut, + TVector& xOut); +}; + + +template +OdeRungeKutta4::~OdeRungeKutta4() +{ +} + +template +OdeRungeKutta4::OdeRungeKutta4(Real tDelta, + std::function const& F) + : + OdeSolver(tDelta, F) +{ +} + +template +void OdeRungeKutta4::Update(Real tIn, TVector const& xIn, + Real& tOut, TVector& xOut) +{ + // Compute the first step. + Real halfTDelta = ((Real)0.5) * this->mTDelta; + TVector fTemp1 = this->mFunction(tIn, xIn); + TVector xTemp = xIn + halfTDelta * fTemp1; + + // Compute the second step. + Real halfT = tIn + halfTDelta; + TVector fTemp2 = this->mFunction(halfT, xTemp); + xTemp = xIn + halfTDelta * fTemp2; + + // Compute the third step. + TVector fTemp3 = this->mFunction(halfT, xTemp); + xTemp = xIn + this->mTDelta * fTemp3; + + // Compute the fourth step. + Real sixthTDelta = this->mTDelta / (Real)6; + tOut = tIn + this->mTDelta; + TVector fTemp4 = this->mFunction(tOut, xTemp); + xOut = xIn + sixthTDelta * ( + fTemp1 + ((Real)2)*(fTemp2 + fTemp3) + fTemp4); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeSolver.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeSolver.h new file mode 100644 index 000000000000..6d12cd42bf70 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeSolver.h @@ -0,0 +1,77 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The differential equation is dx/dt = F(t,x). The TVector template +// parameter allows you to create solvers with Vector when the +// dimension N is known at compile time or GVector when the dimension +// N is known at run time. Both classes have 'int GetSize() const' that +// allow OdeSolver-derived classes to query for the dimension. + +namespace gte +{ + +template +class OdeSolver +{ +public: + // Abstract base class. +public: + virtual ~OdeSolver(); +protected: + OdeSolver(Real tDelta, + std::function const& F); + +public: + // Member access. + inline void SetTDelta(Real tDelta); + inline Real GetTDelta() const; + + // Estimate x(t + tDelta) from x(t) using dx/dt = F(t,x). The + // derived classes implement this so that it is possible for xIn and + // xOut to be the same object. + virtual void Update(Real tIn, TVector const& xIn, Real& tOut, + TVector& xOut) = 0; + +protected: + Real mTDelta; + std::function mFunction; +}; + + +template +OdeSolver::~OdeSolver() +{ +} + +template +OdeSolver::OdeSolver(Real tDelta, + std::function const& F) + : + mTDelta(tDelta), + mFunction(F) +{ +} + +template inline +void OdeSolver::SetTDelta(Real tDelta) +{ + mTDelta = tDelta; +} + +template inline +Real OdeSolver::GetTDelta() const +{ + return mTDelta; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOrientedBox.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOrientedBox.h new file mode 100644 index 000000000000..9ee2e8e83587 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOrientedBox.h @@ -0,0 +1,165 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// A box has center C, axis directions U[i], and extents e[i]. The set +// {U[0],...,U[N-1]} is orthonormal, which means the vectors are +// unit-length and mutually perpendicular. The extents are nonnegative; +// zero is allowed, meaning the box is degenerate in the corresponding +// direction. A point X is represented in box coordinates by +// X = C + y[0]*U[0] + y[1]*U[1]. This point is inside or on the +// box whenever |y[i]| <= e[i] for all i. + +namespace gte +{ + +template +class OrientedBox +{ +public: + // Construction and destruction. The default constructor sets the center + // to (0,...,0), axis d to Vector::Unit(d), and extent d to +1. + OrientedBox(); + OrientedBox(Vector const& inCenter, + std::array, N> const& inAxis, + Vector const& inExtent); + + // Compute the vertices of the box. If index i has the bit pattern + // i = b[N-1]...b[0], then + // vertex[i] = center + sum_{d=0}^{N-1} sign[d] * extent[d] * axis[d] + // where sign[d] = 2*b[d] - 1. + void GetVertices(std::array, (1 << N)>& vertex) const; + + // Public member access. It is required that extent[i] >= 0. + Vector center; + std::array, N> axis; + Vector extent; + +public: + // Comparisons to support sorted containers. + bool operator==(OrientedBox const& box) const; + bool operator!=(OrientedBox const& box) const; + bool operator< (OrientedBox const& box) const; + bool operator<=(OrientedBox const& box) const; + bool operator> (OrientedBox const& box) const; + bool operator>=(OrientedBox const& box) const; +}; + +// Template aliases for convenience. +template +using OrientedBox2 = OrientedBox<2, Real>; + +template +using OrientedBox3 = OrientedBox<3, Real>; + + +template +OrientedBox::OrientedBox() +{ + center.MakeZero(); + for (int i = 0; i < N; ++i) + { + axis[i].MakeUnit(i); + extent[i] = (Real)1; + } +} + +template +OrientedBox::OrientedBox(Vector const& inCenter, + std::array, N> const& inAxis, + Vector const& inExtent) + : + center(inCenter), + axis(inAxis), + extent(inExtent) +{ +} + +template +void OrientedBox::GetVertices( + std::array, (1 << N)>& vertex) const +{ + unsigned int const dsup = static_cast(N); + std::array, N> product; + for (unsigned int d = 0; d < dsup; ++d) + { + product[d] = extent[d] * axis[d]; + } + + int const imax = (1 << N); + for (int i = 0; i < imax; ++i) + { + vertex[i] = center; + for (unsigned int d = 0, mask = 1; d < dsup; ++d, mask <<= 1) + { + Real sign = (i & mask ? (Real)1 : (Real)-1); + vertex[i] += sign * product[d]; + } + } +} + +template +bool OrientedBox::operator==(OrientedBox const& box) const +{ + return center == box.center && axis == box.axis && extent == box.extent; +} + +template +bool OrientedBox::operator!=(OrientedBox const& box) const +{ + return !operator==(box); +} + +template +bool OrientedBox::operator<(OrientedBox const& box) const +{ + if (center < box.center) + { + return true; + } + + if (center > box.center) + { + return false; + } + + if (axis < box.axis) + { + return true; + } + + if (axis > box.axis) + { + return false; + } + + return extent < box.extent; +} + +template +bool OrientedBox::operator<=(OrientedBox const& box) const +{ + return operator<(box) || operator==(box); +} + +template +bool OrientedBox::operator>(OrientedBox const& box) const +{ + return !operator<=(box); +} + +template +bool OrientedBox::operator>=(OrientedBox const& box) const +{ + return !operator<(box); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteParametricCurve.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteParametricCurve.h new file mode 100644 index 000000000000..e05f567eb6cd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteParametricCurve.h @@ -0,0 +1,355 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class ParametricCurve +{ +protected: + // Abstract base class for a parameterized curve X(t), where t is the + // parameter in [tmin,tmax] and X is an N-tuple position. The first + // constructor is for single-segment curves. The second constructor is + // for multiple-segment curves. The times must be strictly increasing. + ParametricCurve(Real tmin, Real tmax); + ParametricCurve(int numSegments, Real const* times); +public: + virtual ~ParametricCurve(); + + // To validate construction, create an object as shown: + // DerivedClassCurve curve(parameters); + // if (!curve) { ; } + inline operator bool() const; + + // Member access. + inline Real GetTMin() const; + inline Real GetTMax() const; + inline int GetNumSegments() const; + Real const* GetTimes() const; + + // This function applies only when the first constructor is used (two + // times rather than a sequence of three or more times). + void SetTimeInterval(Real tmin, Real tmax); + + // Parameters used in GetLength(...), GetTotalLength(), and GetTime(...). + void SetRombergOrder(int order); // default = 8 + void SetMaxBisections(unsigned int maxBisections); // default = 1024 + + // Evaluation of the curve. The function supports derivative calculation + // through order 3; that is, maxOrder <= 3 is required. If you want + // only the position, pass in maxOrder of 0. If you want the position and + // first derivative, pass in maxOrder of 1, and so on. The output + // 'values' are ordered as: position, first derivative, second derivative, + // third derivative. + virtual void Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const = 0; + + // Differential geometric quantities. + Vector GetPosition(Real t) const; + Vector GetTangent(Real t) const; + Real GetSpeed(Real t) const; + Real GetLength(Real t0, Real t1) const; + Real GetTotalLength() const; + + // Inverse mapping of s = Length(t) given by t = Length^{-1}(s). The + // inverse length function is generally a function that cannot be written + // in closed form, in which case it is not directly computable. Instead, + // we can specify s and estimate the root t for F(t) = Length(t) - s. The + // derivative is F'(t) = Speed(t) >= 0, so F(t) is nondecreasing. To be + // robust, we use bisection to locate the root, although it is possible to + // use a hybrid of Newton's method and bisection. + Real GetTime(Real length) const; + + // Compute a subset of curve points according to the specified attribute. + // The input 'numPoints' must be two or larger. + void SubdivideByTime(int numPoints, Vector* points) const; + void SubdivideByLength(int numPoints, Vector* points) const; + +protected: + enum + { + DEFAULT_ROMBERG_ORDER = 8, + DEFAULT_MAX_BISECTIONS = 1024 + }; + + std::vector mTime; + mutable std::vector mSegmentLength; + mutable std::vector mAccumulatedLength; + int mRombergOrder; + unsigned int mMaxBisections; + + bool mConstructed; +}; + + +template +ParametricCurve::ParametricCurve(Real tmin, Real tmax) + : + mTime(2), + mSegmentLength(1), + mAccumulatedLength(1), + mRombergOrder(DEFAULT_ROMBERG_ORDER), + mMaxBisections(DEFAULT_MAX_BISECTIONS), + mConstructed(false) +{ + mTime[0] = tmin; + mTime[1] = tmax; + mSegmentLength[0] = (Real)0; + mAccumulatedLength[0] = (Real)0; +} + +template +ParametricCurve::ParametricCurve(int numSegments, Real const* times) + : + mTime(numSegments + 1), + mSegmentLength(numSegments), + mAccumulatedLength(numSegments), + mRombergOrder(DEFAULT_ROMBERG_ORDER), + mMaxBisections(DEFAULT_MAX_BISECTIONS), + mConstructed(false) +{ + std::copy(times, times + numSegments + 1, mTime.begin()); + mSegmentLength[0] = (Real)0; + mAccumulatedLength[0] = (Real)0; +} + +template +ParametricCurve::~ParametricCurve() +{ +} + +template inline +ParametricCurve::operator bool() const +{ + return mConstructed; +} + +template inline +Real ParametricCurve::GetTMin() const +{ + return mTime.front(); +} + +template inline +Real ParametricCurve::GetTMax() const +{ + return mTime.back(); +} + +template inline +int ParametricCurve::GetNumSegments() const +{ + return static_cast(mSegmentLength.size()); +} + +template inline +Real const* ParametricCurve::GetTimes() const +{ + return &mTime[0]; +} + +template +void ParametricCurve::SetTimeInterval(Real tmin, Real tmax) +{ + if (mTime.size() == 2) + { + mTime[0] = tmin; + mTime[1] = tmax; + } +} + +template +void ParametricCurve::SetRombergOrder(int order) +{ + mRombergOrder = std::max(order, 1); +} + +template +void ParametricCurve::SetMaxBisections(unsigned int maxBisections) +{ + mMaxBisections = std::max(maxBisections, 1u); +} + +template +Vector ParametricCurve::GetPosition(Real t) const +{ + Vector values[4]; + Evaluate(t, 0, values); + return values[0]; +} + +template +Vector ParametricCurve::GetTangent(Real t) const +{ + Vector values[4]; + Evaluate(t, 1, values); + Normalize(values[1]); + return values[1]; +} + +template +Real ParametricCurve::GetSpeed(Real t) const +{ + Vector values[4]; + Evaluate(t, 1, values); + return Length(values[1]); +} + +template +Real ParametricCurve::GetLength(Real t0, Real t1) const +{ + std::function speed = [this](Real t) + { + return GetSpeed(t); + }; + + if (mSegmentLength[0] == (Real)0) + { + // Lazy initialization of lengths of segments. + int const numSegments = static_cast(mSegmentLength.size()); + Real accumulated = (Real)0; + for (int i = 0; i < numSegments; ++i) + { + mSegmentLength[i] = Integration::Romberg(mRombergOrder, + mTime[i], mTime[i + 1], speed); + accumulated += mSegmentLength[i]; + mAccumulatedLength[i] = accumulated; + } + } + + t0 = std::max(t0, GetTMin()); + t1 = std::min(t1, GetTMax()); + auto iter0 = std::lower_bound(mTime.begin(), mTime.end(), t0); + int index0 = static_cast(iter0 - mTime.begin()); + auto iter1 = std::lower_bound(mTime.begin(), mTime.end(), t1); + int index1 = static_cast(iter1 - mTime.begin()); + + Real length; + if (index0 < index1) + { + length = (Real)0; + if (t0 < *iter0) + { + length += Integration::Romberg(mRombergOrder, t0, + mTime[index0], speed); + } + + int isup; + if (t1 < *iter1) + { + length += Integration::Romberg(mRombergOrder, + mTime[index1 - 1], t1, speed); + isup = index1 - 1; + } + else + { + isup = index1; + } + for (int i = index0; i < isup; ++i) + { + length += mSegmentLength[i]; + } + } + else + { + length = Integration::Romberg(mRombergOrder, t0, t1, speed); + } + return length; +} + +template +Real ParametricCurve::GetTotalLength() const +{ + if (mAccumulatedLength.back() == (Real)0) + { + // Lazy evaluation of the accumulated length array. + return GetLength(mTime.front(), mTime.back()); + } + + return mAccumulatedLength.back(); +} + +template +Real ParametricCurve::GetTime(Real length) const +{ + if (length > (Real)0) + { + if (length < GetTotalLength()) + { + std::function F = [this, &length](Real t) + { + return Integration::Romberg(mRombergOrder, + mTime.front(), t, [this](Real z){ return GetSpeed(z); }) + - length; + }; + + // We know that F(tmin) < 0 and F(tmax) > 0, which allows us to + // use bisection. Rather than bisect the entire interval, let's + // narrow it down with a reasonable initial guess. + Real ratio = length / GetTotalLength(); + Real omratio = (Real)1 - ratio; + Real tmid = omratio * mTime.front() + ratio * mTime.back(); + Real fmid = F(tmid); + if (fmid > (Real)0) + { + RootsBisection::Find(F, mTime.front(), tmid, (Real)-1, + (Real)1, mMaxBisections, tmid); + } + else if (fmid < (Real)0) + { + RootsBisection::Find(F, tmid, mTime.back(), (Real)-1, + (Real)1, mMaxBisections, tmid); + } + return tmid; + } + else + { + return mTime.back(); + } + } + else + { + return mTime.front(); + } +} + +template +void ParametricCurve::SubdivideByTime(int numPoints, + Vector* points) const +{ + Real delta = (mTime.back() - mTime.front()) / (Real)(numPoints - 1); + for (int i = 0; i < numPoints; ++i) + { + Real t = mTime.front() + delta * i; + points[i] = GetPosition(t); + } +} + +template +void ParametricCurve::SubdivideByLength(int numPoints, + Vector* points) const +{ + Real delta = GetTotalLength() / (Real)(numPoints - 1); + for (int i = 0; i < numPoints; ++i) + { + Real length = delta * i; + Real t = GetTime(length); + points[i] = GetPosition(t); + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteParametricSurface.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteParametricSurface.h new file mode 100644 index 000000000000..18c18e39dce9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteParametricSurface.h @@ -0,0 +1,144 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +template +class ParametricSurface +{ +protected: + // Abstract base class for a parameterized surface X(u,v). The parametric + // domain is either rectangular or triangular. Valid (u,v) values for a + // rectangular domain satisfy + // umin <= u <= umax, vmin <= v <= vmax + // and valid (u,v) values for a triangular domain satisfy + // umin <= u <= umax, vmin <= v <= vmax, + // (vmax-vmin)*(u-umin)+(umax-umin)*(v-vmax) <= 0 + ParametricSurface(Real umin, Real umax, Real vmin, Real vmax, + bool rectangular); +public: + virtual ~ParametricSurface(); + + // To validate construction, create an object as shown: + // DerivedClassSurface surface(parameters); + // if (!surface) { ; } + inline operator bool() const; + + // Member access. + inline Real GetUMin() const; + inline Real GetUMax() const; + inline Real GetVMin() const; + inline Real GetVMax() const; + inline bool IsRectangular() const; + + // Evaluation of the surface. The function supports derivative + // calculation through order 2; that is, maxOrder <= 2 is required. If + // you want only the position, pass in maxOrder of 0. If you want the + // position and first-order derivatives, pass in maxOrder of 1, and so on. + // The output 'values' are ordered as: position X; first-order derivatives + // dX/du, dX/dv; second-order derivatives d2X/du2, d2X/dudv, d2X/dv2. + virtual void Evaluate(Real u, Real v, unsigned int maxOrder, + Vector values[6]) const = 0; + + // Differential geometric quantities. + Vector GetPosition(Real u, Real v) const; + Vector GetUTangent(Real u, Real v) const; + Vector GetVTangent(Real u, Real v) const; + +protected: + Real mUMin, mUMax, mVMin, mVMax; + bool mRectangular; + bool mConstructed; +}; + + +template +ParametricSurface::ParametricSurface(Real umin, Real umax, + Real vmin, Real vmax, bool rectangular) + : + mUMin(umin), + mUMax(umax), + mVMin(vmin), + mVMax(vmax), + mRectangular(rectangular) +{ +} + +template +ParametricSurface::~ParametricSurface() +{ +} + +template +ParametricSurface::operator bool() const +{ + return mConstructed; +} + +template inline +Real ParametricSurface::GetUMin() const +{ + return mUMin; +} + +template inline +Real ParametricSurface::GetUMax() const +{ + return mUMax; +} + +template inline +Real ParametricSurface::GetVMin() const +{ + return mVMin; +} + +template inline +Real ParametricSurface::GetVMax() const +{ + return mVMax; +} + +template inline +bool ParametricSurface::IsRectangular() const +{ + return mRectangular; +} + +template inline +Vector ParametricSurface::GetPosition(Real u, Real v) const +{ + Vector values[6]; + Evaluate(u, v, 0, values); + return values[0]; +} + +template inline +Vector ParametricSurface::GetUTangent(Real u, Real v) const +{ + Vector values[6]; + Evaluate(u, v, 1, values); + Normalize(values[1]); + return values[1]; +} + +template inline +Vector ParametricSurface::GetVTangent(Real u, Real v) const +{ + Vector values[6]; + Evaluate(u, v, 1, values); + Normalize(values[2]); + return values[2]; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePlanarMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePlanarMesh.h new file mode 100644 index 000000000000..60525b22394e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePlanarMesh.h @@ -0,0 +1,500 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The planar mesh class is convenient for many applications involving +// searches for triangles containing a specified point. A couple of +// issues can show up in practice when the input data to the constructors +// is very large (number of triangles on the order of 10^5 or larger). +// +// The first constructor builds an ETManifoldMesh mesh that contains +// std::map objects. When such maps are large, the amount of time it +// takes to delete them is enormous. Although you can control the level +// of debug support in MSVS 2013 (see _ITERATOR_DEBUG_LEVEL), turning off +// checking might very well affect other classes for which you want +// iterator checking to be on. An alternative to reduce debugging time +// is to dynamically allocate the PlanarMesh object in the main thread but +// then launch another thread to delete the object and avoid stalling +// the main thread. For example, +// +// PlanarMesh* pmesh = +// new PlanarMesh(numV, vertices, numT, indices); +// ; +// std::thread deleter = [pmesh](){ delete pmesh; }; +// deleter.detach(); // Do not wait for the thread to finish. +// +// The second constructor has the mesh passed in, but mTriIndexMap is used +// in both constructors and can take time to delete. +// +// The input mesh should be consistently oriented, say, the triangles are +// counterclockwise ordered. The vertices should be consistent with this +// ordering. However, floating-point rounding errors in generating the +// vertices can cause apparent fold-over of the mesh; that is, theoretically +// the vertex geometry supports counterclockwise geometry but numerical +// errors cause an inconsistency. This can manifest in the mQuery.ToLine +// tests whereby cycles of triangles occur in the linear walk. When cycles +// occur, GetContainingTriangle(P,startTriangle) will iterate numTriangle +// times before reporting that the triangle cannot be found, which is a +// very slow process (in debug or release builds). The function +// GetContainingTriangle(P,startTriangle,visited) is provided to avoid the +// performance loss, trapping a cycle the first time and exiting, but +// again reporting that the triangle cannot be found. If you know that the +// query should be (theoretically) successful, use the second version of +// GetContainingTriangle. If it fails by returning -1, then perform an +// exhaustive search over the triangles. For example, +// +// int triangle = pmesh->GetContainingTriangle(P,startTriangle,visited); +// if (triangle >= 0) +// { +// ; +// } +// else +// { +// int numTriangles = pmesh->GetNumTriangles(); +// for (triangle = 0; triangle < numTriangles; ++triangle) +// { +// if (pmesh->Contains(triangle, P)) +// { +// ; +// break; +// } +// } +// if (triangle == numTriangles) +// { +// ; +// } +// } +// +// The PlanarMesh<*>::Contains function does not require the triangles to +// be ordered. + +namespace gte +{ + +template +class PlanarMesh +{ +public: + // Construction. The inputs must represent a manifold mesh of triangles + // in the plane. The index array must have 3*numTriangles elements, each + // triple of indices representing a triangle in the mesh. Each index is + // into the 'vertices' array. + PlanarMesh(int numVertices, Vector2 const* vertices, int numTriangles, int const* indices); + PlanarMesh(int numVertices, Vector2 const* vertices, ETManifoldMesh const& mesh); + + // Mesh information. + inline int GetNumVertices() const; + inline int GetNumTriangles() const; + inline Vector2 const* GetVertices() const; + inline int const* GetIndices() const; + inline int const* GetAdjacencies() const; + + // Containment queries. The function GetContainingTriangle works + // correctly when the planar mesh is a convex set. If the mesh is not + // convex, it is possible that the linear-walk search algorithm exits the + // mesh before finding a containing triangle. For example, a C-shaped + // mesh can contain a point in the top branch of the "C". A starting + // point in the bottom branch of the "C" will lead to the search exiting + // the bottom branch and having no path to walk to the top branch. If + // your mesh is not convex and you want a correct containment query, you + // will have to append "outside" triangles to your mesh to form a convex + // set. + int GetContainingTriangle(Vector2 const& P, int startTriangle = 0) const; + int GetContainingTriangle(Vector2 const& P, int startTriangle, std::set& visited) const; + bool GetVertices(int t, std::array, 3>& vertices) const; + bool GetIndices(int t, std::array& indices) const; + bool GetAdjacencies(int t, std::array& adjacencies) const; + bool GetBarycentrics(int t, Vector2 const& P, std::array& bary) const; + bool Contains(int triangle, Vector2 const& P) const; + +public: + void CreateVertices(int numVertices, Vector2 const* vertices); + + int mNumVertices; + Vector2 const* mVertices; + int mNumTriangles; + std::vector mIndices; + ETManifoldMesh mMesh; + std::map, int> mTriIndexMap; + std::vector mAdjacencies; + std::vector> mComputeVertices; + PrimalQuery2 mQuery; +}; + + +template +PlanarMesh::PlanarMesh(int numVertices, + Vector2 const* vertices, int numTriangles, int const* indices) + : + mNumVertices(0), + mVertices(nullptr), + mNumTriangles(0) +{ + if (numVertices < 3 || !vertices || numTriangles < 1 || !indices) + { + LogError("Invalid input."); + return; + } + + // Create a mesh in order to get adjacency information. + int const* current = indices; + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *current++; + int v1 = *current++; + int v2 = *current++; + if (!mMesh.Insert(v0, v1, v2)) + { + // The 'mesh' object will assert on nonmanifold inputs. + return; + } + } + + // We have a valid mesh. + CreateVertices(numVertices, vertices); + + // Build the adjacency graph using the triangle ordering implied by the + // indices, not the mesh triangle map, to preserve the triangle ordering + // of the input indices. + mNumTriangles = numTriangles; + int const numIndices = 3 * numTriangles; + mIndices.resize(numIndices); + + std::copy(indices, indices + numIndices, mIndices.begin()); + for (int t = 0, vIndex = 0; t < numTriangles; ++t) + { + int v0 = indices[vIndex++]; + int v1 = indices[vIndex++]; + int v2 = indices[vIndex++]; + TriangleKey key(v0, v1, v2); + mTriIndexMap.insert(std::make_pair(key, t)); + } + + mAdjacencies.resize(numIndices); + auto const& tmap = mMesh.GetTriangles(); + for (int t = 0, base = 0; t < numTriangles; ++t, base += 3) + { + int v0 = indices[base]; + int v1 = indices[base + 1]; + int v2 = indices[base + 2]; + TriangleKey key(v0, v1, v2); + auto element = tmap.find(key); + for (int i = 0; i < 3; ++i) + { + auto adj = element->second->T[i].lock(); + if (adj) + { + key = TriangleKey(adj->V[0], adj->V[1], adj->V[2]); + mAdjacencies[base + i] = mTriIndexMap.find(key)->second; + } + else + { + mAdjacencies[base + i] = -1; + } + } + } +} + +template +PlanarMesh::PlanarMesh(int numVertices, + Vector2 const* vertices, ETManifoldMesh const& mesh) + : + mNumVertices(0), + mVertices(nullptr), + mNumTriangles(0) +{ + if (numVertices < 3 || !vertices || mesh.GetTriangles().size() < 1) + { + LogError("Invalid input."); + return; + } + + // We have a valid mesh. + CreateVertices(numVertices, vertices); + + // Build the adjacency graph using the triangle ordering implied by the + // mesh triangle map. + auto const& tmap = mesh.GetTriangles(); + mNumTriangles = static_cast(tmap.size()); + mIndices.resize(3 * mNumTriangles); + + int tIndex = 0, vIndex = 0; + for (auto const& element : tmap) + { + mTriIndexMap.insert(std::make_pair(element.first, tIndex++)); + for (int i = 0; i < 3; ++i, ++vIndex) + { + mIndices[vIndex] = element.second->V[i]; + } + } + + mAdjacencies.resize(3 * mNumTriangles); + vIndex = 0; + for (auto const& element : tmap) + { + for (int i = 0; i < 3; ++i, ++vIndex) + { + auto adj = element.second->T[i].lock(); + if (adj) + { + TriangleKey key(adj->V[0], adj->V[1], adj->V[2]); + mAdjacencies[vIndex] = mTriIndexMap.find(key)->second; + } + else + { + mAdjacencies[vIndex] = -1; + } + } + } +} + +template +inline int PlanarMesh:: +GetNumVertices() const +{ + return mNumVertices; +} + +template +inline int PlanarMesh::GetNumTriangles() const +{ + return mNumTriangles; +} + +template +inline Vector2 const* PlanarMesh::GetVertices() const +{ + return mVertices; +} + +template +inline int const* PlanarMesh::GetIndices() const +{ + return &mIndices[0]; +} + +template +inline int const* PlanarMesh::GetAdjacencies() const +{ + return &mAdjacencies[0]; +} + +template +int PlanarMesh::GetContainingTriangle( + Vector2 const& P, int startTriangle) const +{ + Vector2 test{ P[0], P[1] }; + + // Use triangle edges as binary separating lines. + int triangle = startTriangle; + for (int i = 0; i < mNumTriangles; ++i) + { + int ibase = 3 * triangle; + int const* v = &mIndices[ibase]; + + if (mQuery.ToLine(test, v[0], v[1]) > 0) + { + triangle = mAdjacencies[ibase]; + if (triangle == -1) + { + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[1], v[2]) > 0) + { + triangle = mAdjacencies[ibase + 1]; + if (triangle == -1) + { + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[2], v[0]) > 0) + { + triangle = mAdjacencies[ibase + 2]; + if (triangle == -1) + { + return -1; + } + continue; + } + + return triangle; + } + + return -1; +} + +template +int PlanarMesh::GetContainingTriangle( + Vector2 const& P, int startTriangle, std::set& visited) const +{ + Vector2 test{ P[0], P[1] }; + visited.clear(); + + // Use triangle edges as binary separating lines. + int triangle = startTriangle; + for (int i = 0; i < mNumTriangles; ++i) + { + visited.insert(triangle); + int ibase = 3 * triangle; + int const* v = &mIndices[ibase]; + + if (mQuery.ToLine(test, v[0], v[1]) > 0) + { + triangle = mAdjacencies[ibase]; + if (triangle == -1 || visited.find(triangle) != visited.end()) + { + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[1], v[2]) > 0) + { + triangle = mAdjacencies[ibase + 1]; + if (triangle == -1 || visited.find(triangle) != visited.end()) + { + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[2], v[0]) > 0) + { + triangle = mAdjacencies[ibase + 2]; + if (triangle == -1 || visited.find(triangle) != visited.end()) + { + return -1; + } + continue; + } + + return triangle; + } + + return -1; +} + +template +bool PlanarMesh::GetVertices( + int t, std::array, 3>& vertices) const +{ + if (0 <= t && t < mNumTriangles) + { + for (int i = 0, vIndex = 3 * t; i < 3; ++i, ++vIndex) + { + vertices[i] = mVertices[mIndices[vIndex]]; + } + return true; + } + return false; +} + +template +bool PlanarMesh::GetIndices( + int t, std::array& indices) const +{ + if (0 <= t && t < mNumTriangles) + { + for (int i = 0, vIndex = 3 * t; i < 3; ++i, ++vIndex) + { + indices[i] = mIndices[vIndex]; + } + return true; + } + return false; +} + +template +bool PlanarMesh::GetAdjacencies( + int t, std::array& adjacencies) const +{ + if (0 <= t && t < mNumTriangles) + { + for (int i = 0, vIndex = 3 * t; i < 3; ++i, ++vIndex) + { + adjacencies[i] = mAdjacencies[vIndex]; + } + return true; + } + return false; +} + +template +bool PlanarMesh::GetBarycentrics( + int t, Vector2 const& P, std::array& bary) const +{ + std::array indices; + if (GetIndices(t, indices)) + { + Vector2 rtP{ P[0], P[1] }; + std::array, 3> rtV; + for (int i = 0; i < 3; ++i) + { + Vector2 const& V = mComputeVertices[indices[i]]; + for (int j = 0; j < 2; ++j) + { + rtV[i][j] = (RationalType)V[j]; + } + }; + + RationalType rtBary[3]; + if (ComputeBarycentrics(rtP, rtV[0], rtV[1], rtV[2], rtBary)) + { + for (int i = 0; i < 3; ++i) + { + bary[i] = (InputType)rtBary[i]; + } + return true; + } + } + return false; +} + +template +bool PlanarMesh::Contains( + int triangle, Vector2 const& P) const +{ + Vector2 test{ P[0], P[1] }; + Vector2 v[3]; + v[0] = mComputeVertices[mIndices[3 * triangle + 0]]; + v[1] = mComputeVertices[mIndices[3 * triangle + 1]]; + v[2] = mComputeVertices[mIndices[3 * triangle + 2]]; + PointInPolygon2 pip(3, v); + return pip.Contains(test); +} + +template +void PlanarMesh::CreateVertices( + int numVertices, Vector2 const* vertices) +{ + mNumVertices = numVertices; + mVertices = vertices; + mComputeVertices.resize(mNumVertices); + for (int i = 0; i < mNumVertices; ++i) + { + for (int j = 0; j < 2; ++j) + { + mComputeVertices[i][j] = (ComputeType)mVertices[i][j]; + } + } + mQuery.Set(mNumVertices, &mComputeVertices[0]); +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolygon2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolygon2.h new file mode 100644 index 000000000000..ce1d19f54813 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolygon2.h @@ -0,0 +1,308 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// The Polygon2 object represents a simple polygon: No duplicate vertices, +// closed (each vertex is shared by exactly two edges), and no +// self-intersections at interior edge points. The 'vertexPool' array can +// contain more points than needed to define the polygon, which allows the +// vertex pool to have multiple polygons associated with it. Thus, the +// programmer must ensure that the vertex pool persists as long as any +// Polygon2 objects exist that depend on the pool. The number of polygon +// vertices is 'numIndices' and must be 3 or larger. The 'indices' array +// refers to the points in 'vertexPool' that are part of the polygon and must +// have 'numIndices' unique elements. The edges of the polygon are pairs of +// indices into 'vertexPool', +// edge[0] = (indices[0], indices[1]) +// : +// edge[numIndices-2] = (indices[numIndices-2], indices[numIndices-1]) +// edge[numIndices-1] = (indices[numIndices-1], indices[0]) +// The programmer should ensure the polygon is simple. The geometric +// queries are valid regardless of whether the polygon is oriented clockwise +// or counterclockwise. +// +// NOTE: Comparison operators are not provided. The semantics of equal +// polygons is complicated and (at the moment) not useful. The vertex pools +// can be different and indices do not match, but the vertices they reference +// can match. Even with a shared vertex pool, the indices can be "rotated", +// leading to the same polygon abstractly but the data structures do not +// match. + +namespace gte +{ + +template +class Polygon2 +{ +public: + // Construction. The constructor succeeds when 'numIndices' >= 3 and + // 'vertexPool' and 'indices' are not null; we cannot test whether you + // have a valid number of elements in the input arrays. A copy is made + // of 'indices', but the 'vertexPool' is not copied. If the constructor + // fails, the internal vertex pointer is set to null, the index array + // has no elements, and the orientation is set to clockwise. + Polygon2(Vector2 const* vertexPool, int numIndices, + int const* indices, bool counterClockwise); + + // To validate construction, create an object as shown: + // Polygon2 polygon(parameters); + // if (!polygon) { ; } + inline operator bool() const; + + // Member access. + inline Vector2 const* GetVertexPool() const; + inline std::set const& GetVertices() const; + inline std::vector const& GetIndices() const; + inline bool CounterClockwise() const; + + // Geometric queries. + Vector2 ComputeVertexAverage() const; + Real ComputePerimeterLength() const; + Real ComputeArea() const; + + // Simple polygons have no self-intersections at interior points + // of edges. The test is an exhaustive all-pairs intersection + // test for edges, which is inefficient for polygons with a large + // number of vertices. TODO: Provide an efficient algorithm that + // uses the algorithm of class RectangleManager.h. + bool IsSimple() const; + + // Convex polygons are simple polygons where the angles between + // consecutive edges are less than or equal to pi radians. + bool IsConvex() const; + +private: + // These calls have preconditions that mVertexPool is not null and + // mIndices.size() > 3. The heart of the algorithms are implemented + // here. + bool IsSimpleInternal() const; + bool IsConvexInternal() const; + + Vector2 const* mVertexPool; + std::set mVertices; + std::vector mIndices; + bool mCounterClockwise; +}; + + +template +Polygon2::Polygon2(Vector2 const* vertexPool, int numIndices, + int const* indices, bool counterClockwise) + : + mVertexPool(vertexPool), + mCounterClockwise(counterClockwise) +{ + if (numIndices >= 3 && vertexPool && indices) + { + for (int i = 0; i < numIndices; ++i) + { + mVertices.insert(indices[i]); + } + + if (numIndices == static_cast(mVertices.size())) + { + mIndices.resize(numIndices); + std::copy(indices, indices + numIndices, mIndices.begin()); + return; + } + + // At least one duplicated vertex was encountered, so the polygon + // is not simple. Fail the constructor call. + mVertices.clear(); + } + + // Invalid input to the Polygon2 constructor. + mVertexPool = nullptr; + mCounterClockwise = false; +} + +template inline +Polygon2::operator bool() const +{ + return mVertexPool != nullptr; +} + +template inline +Vector2 const* Polygon2::GetVertexPool() const +{ + return mVertexPool; +} + +template inline +std::set const& Polygon2::GetVertices() const +{ + return mVertices; +} + +template inline +std::vector const& Polygon2::GetIndices() const +{ + return mIndices; +} + +template inline +bool Polygon2::CounterClockwise() const +{ + return mCounterClockwise; +} + +template +Vector2 Polygon2::ComputeVertexAverage() const +{ + Vector2 average = Vector2::Zero(); + if (mVertexPool) + { + for (int index : mVertices) + { + average += mVertexPool[index]; + } + average /= static_cast(mVertices.size()); + } + return average; +} + +template +Real Polygon2::ComputePerimeterLength() const +{ + Real length = (Real)0; + if (mVertexPool) + { + Vector2 v0 = mVertexPool[mIndices.back()]; + for (int index : mIndices) + { + Vector2 v1 = mVertexPool[index]; + length += Length(v1 - v0); + v0 = v1; + } + } + return length; +} + +template +Real Polygon2::ComputeArea() const +{ + Real area = (Real)0; + if (mVertexPool) + { + int const numIndices = static_cast(mIndices.size()); + Vector2 v0 = mVertexPool[mIndices[numIndices - 2]]; + Vector2 v1 = mVertexPool[mIndices[numIndices - 1]]; + for (int index : mIndices) + { + Vector2 v2 = mVertexPool[index]; + area += v1[0] * (v2[1] - v0[1]); + v0 = v1; + v1 = v2; + } + area *= (Real)0.5; + } + return std::abs(area); +} + +template +bool Polygon2::IsSimple() const +{ + if (!mVertexPool) + { + return false; + } + + // For mVertexPool to be nonnull, the number of indices is guaranteed + // to be at least 3. + int const numIndices = static_cast(mIndices.size()); + if (numIndices == 3) + { + // The polygon is a triangle. + return true; + } + + return IsSimpleInternal(); +} + +template +bool Polygon2::IsConvex() const +{ + if (!mVertexPool) + { + return false; + } + + // For mVertexPool to be nonnull, the number of indices is guaranteed + // to be at least 3. + int const numIndices = static_cast(mIndices.size()); + if (numIndices == 3) + { + // The polygon is a triangle. + return true; + } + + return IsSimpleInternal() && IsConvexInternal(); +} + +template +bool Polygon2::IsSimpleInternal() const +{ + Segment2 seg0, seg1; + TIQuery, Segment2> query; + typename TIQuery, Segment2>::Result result; + + int const numIndices = static_cast(mIndices.size()); + for (int i0 = 0; i0 < numIndices; ++i0) + { + int i0p1 = (i0 + 1) % numIndices; + seg0.p[0] = mVertexPool[mIndices[i0]]; + seg0.p[1] = mVertexPool[mIndices[i0p1]]; + + int i1min = (i0 + 2) % numIndices; + int i1max = (i0 - 2 + numIndices) % numIndices; + for (int i1 = i1min; i1 <= i1max; ++i1) + { + int i1p1 = (i1 + 1) % numIndices; + seg1.p[0] = mVertexPool[mIndices[i1]]; + seg1.p[1] = mVertexPool[mIndices[i1p1]]; + + result = query(seg0, seg1); + if (result.intersect) + { + return false; + } + } + } + return true; +} + +template +bool Polygon2::IsConvexInternal() const +{ + Real sign = (mCounterClockwise ? (Real)1 : (Real)-1); + int const numIndices = static_cast(mIndices.size()); + for (int i = 0; i < numIndices; ++i) + { + int iPrev = (i + numIndices - 1) % numIndices; + int iNext = (i + 1) % numIndices; + Vector2 vPrev = mVertexPool[mIndices[iPrev]]; + Vector2 vCurr = mVertexPool[mIndices[i]]; + Vector2 vNext = mVertexPool[mIndices[iNext]]; + Vector2 edge0 = vCurr - vPrev; + Vector2 edge1 = vNext - vCurr; + Real test = sign * DotPerp(edge0, edge1); + if (test < (Real)0) + { + return false; + } + } + return true; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolyhedron3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolyhedron3.h new file mode 100644 index 000000000000..5c9a3f438499 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolyhedron3.h @@ -0,0 +1,200 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/04) + +#pragma once + +#include +#include +#include +#include +#include + +// The Polyhedron3 object represents a simple polyhedron. The 'vertexPool' +// array can contain more points than needed to define the polyhedron, which +// allows the vertex pool to have multiple polyhedra associated with it. +// Thus, the programmer must ensure that the vertex pool persists as long as +// any Polyhedron3 objects exist that depend on the pool. The number of +// polyhedron indices is 'numIndices' and must be 6 or larger The 'indices' +// array refers to the points in 'vertexPool' that form the triangle faces, +// so 'numIndices' must be a multiple of 3. The number of vertices is +// the number of unique elements in 'indices' and is determined during +// construction. The programmer should ensure the polyhedron is simple. The +// geometric queries are valid regardless of whether the polyhedron triangles +// are oriented clockwise or counterclockwise. +// +// NOTE: Comparison operators are not provided. The semantics of equal +// polyhedra is complicated and (at the moment) not useful. The vertex pools +// can be different and indices do not match, but the vertices they reference +// can match. Even with a shared vertex pool, the indices can be permuted, +// leading to the same polyhedron abstractly but the data structures do not +// match. + +namespace gte +{ + +template +class Polyhedron3 +{ +public: + // Construction. The constructor succeeds when 'numIndices >= 12' (at + // least 4 triangles), and 'vertexPool' and 'indices' are not null; we + // cannot test whether you have a valid number of elements in the input + // arrays. A copy is made of 'indices', but the 'vertexPool' is not + // copied. If the constructor fails, the internal vertex pointer is set + // to null, the number of vertices is set to zero, the index array has no + // elements, and the triangle face orientation is set to clockwise. + Polyhedron3(std::shared_ptr>> const& vertexPool, + int numIndices, int const* indices, bool counterClockwise); + + // To validate construction, create an object as shown: + // Polyhedron3 polyhedron(parameters); + // if (!polyhedron) { ; } + inline operator bool() const; + + // Member access. + inline std::shared_ptr>> const& GetVertexPool() const; + inline std::vector> const& GetVertices() const; + inline std::set const& GetUniqueIndices() const; + inline std::vector const& GetIndices() const; + inline bool CounterClockwise() const; + + // Geometric queries. + Vector3 ComputeVertexAverage() const; + Real ComputeSurfaceArea() const; + Real ComputeVolume() const; + +private: + std::shared_ptr>> mVertexPool; + std::set mUniqueIndices; + std::vector mIndices; + bool mCounterClockwise; +}; + + +template +Polyhedron3::Polyhedron3(std::shared_ptr>> const& vertexPool, + int numIndices, int const* indices, bool counterClockwise) + : + mVertexPool(vertexPool), + mCounterClockwise(counterClockwise) +{ + if (vertexPool && indices && numIndices >= 12 && (numIndices % 3) == 0) + { + for (int i = 0; i < numIndices; ++i) + { + mUniqueIndices.insert(indices[i]); + } + + mIndices.resize(numIndices); + std::copy(indices, indices + numIndices, mIndices.begin()); + } + else + { + // Encountered an invalid input. + mVertexPool = nullptr; + mCounterClockwise = false; + } +} + +template inline +Polyhedron3::operator bool() const +{ + return mVertexPool != nullptr; +} + +template inline +std::shared_ptr>> const& Polyhedron3::GetVertexPool() const +{ + return mVertexPool; +} + +template inline +std::vector> const& Polyhedron3::GetVertices() const +{ + return *mVertexPool.get(); +} + +template inline +std::set const& Polyhedron3::GetUniqueIndices() const +{ + return mUniqueIndices; +} + +template inline +std::vector const& Polyhedron3::GetIndices() const +{ + return mIndices; +} + +template inline +bool Polyhedron3::CounterClockwise() const +{ + return mCounterClockwise; +} + +template +Vector3 Polyhedron3::ComputeVertexAverage() const +{ + Vector3 average = Vector3::Zero(); + if (mVertexPool) + { + auto vertexPool = GetVertices(); + for (int index : mUniqueIndices) + { + average += vertexPool[index]; + } + average /= static_cast(mUniqueIndices.size()); + } + return average; +} + +template +Real Polyhedron3::ComputeSurfaceArea() const +{ + Real surfaceArea = (Real)0; + if (mVertexPool) + { + auto vertexPool = GetVertices(); + int const numTriangles = static_cast(mIndices.size()) / 3; + int const* indices = mIndices.data(); + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + Vector3 edge0 = vertexPool[v1] - vertexPool[v0]; + Vector3 edge1 = vertexPool[v2] - vertexPool[v0]; + Vector3 cross = Cross(edge0, edge1); + surfaceArea += Length(cross); + } + surfaceArea *= (Real)0.5; + } + return surfaceArea; +} + +template +Real Polyhedron3::ComputeVolume() const +{ + Real volume = (Real)0; + if (mVertexPool) + { + auto vertexPool = GetVertices(); + int const numTriangles = static_cast(mIndices.size()) / 3; + int const* indices = mIndices.data(); + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + volume += DotCross(vertexPool[v0], vertexPool[v1], vertexPool[v2]); + } + volume /= (Real)6; + } + return std::abs(volume); +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolynomial1.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolynomial1.h new file mode 100644 index 000000000000..1e4735d67a98 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolynomial1.h @@ -0,0 +1,607 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/12/06) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + template + class Polynomial1 + { + public: + // Construction and destruction. The first constructor creates a + // polynomial of degree 0. The first and second constructors do not + // initialize their coefficients. In the third constructor, the + // degree is the number of initializers plus 1, but then adjusted to + // ensure coefficient[degree] is not zero. + Polynomial1() + : + mCoefficient(1) + { + // uninitialized + } + + Polynomial1(unsigned int degree) + : + mCoefficient(degree + 1) + { + // uninitialized + } + + Polynomial1(std::initializer_list values) + { + // C++ 11 will call the default constructor for + // Polynomial1 p{}, so it is guaranteed that + // values.size() > 0. + mCoefficient.resize(values.size()); + std::copy(values.begin(), values.end(), mCoefficient.begin()); + EliminateLeadingZeros(); + } + + // Support for partial construction, where the default constructor is + // used when the degree is not yet known. The coefficients are + // uninitialized. + void SetDegree(unsigned int degree) + { + mCoefficient.resize(degree + 1); + } + + // Set all coefficients to the specified value. + void SetCoefficients(Real value) + { + std::fill(mCoefficient.begin(), mCoefficient.end(), value); + } + + // Member access. + inline unsigned int GetDegree() const + { + // By design, mCoefficient.size() > 0. + return static_cast(mCoefficient.size() - 1); + } + + inline Real const& operator[](unsigned int i) const + { + return mCoefficient[i]; + } + + inline Real& operator[](unsigned int i) + { + return mCoefficient[i]; + } + + + // Comparisons. + inline bool operator==(Polynomial1 const& p) const + { + return mCoefficient == p.mCoefficient; + } + + inline bool operator!=(Polynomial1 const& p) const + { + return mCoefficient != p.mCoefficient; + } + + inline bool operator< (Polynomial1 const& p) const + { + return mCoefficient < p.mCoefficient; + } + + inline bool operator<=(Polynomial1 const& p) const + { + return mCoefficient <= p.mCoefficient; + } + + inline bool operator> (Polynomial1 const& p) const + { + return mCoefficient > p.mCoefficient; + } + + inline bool operator>=(Polynomial1 const& p) const + { + return mCoefficient >= p.mCoefficient; + } + + // Evaluate the polynomial. If the polynomial is invalid, the + // function returns zero. + Real operator()(Real t) const + { + int i = static_cast(mCoefficient.size()); + Real result = mCoefficient[--i]; + for (--i; i >= 0; --i) + { + result *= t; + result += mCoefficient[i]; + } + return result; + } + + // Compute the derivative of the polynomial. + Polynomial1 GetDerivative() const + { + unsigned int const degree = GetDegree(); + if (degree > 0) + { + Polynomial1 result(degree - 1); + for (unsigned int i0 = 0, i1 = 1; i0 < degree; ++i0, ++i1) + { + result.mCoefficient[i0] = mCoefficient[i1] * (Real)i1; + } + return result; + } + else + { + Polynomial1 result(0); + result[0] = (Real)0; + return result; + } + } + + // Inversion (invpoly[i] = poly[degree-i] for 0 <= i <= degree). + Polynomial1 GetInversion() const + { + unsigned int const degree = GetDegree(); + Polynomial1 result(degree); + for (unsigned int i = 0; i <= degree; ++i) + { + result.mCoefficient[i] = mCoefficient[degree - i]; + } + return result; + } + + // Tranlation. If 'this' is p(t}, return p(t-t0). + Polynomial1 GetTranslation(Real t0) const + { + Polynomial1 factor{ -t0, (Real)1 }; // f(t) = t - t0 + unsigned int const degree = GetDegree(); + Polynomial1 result{ mCoefficient[degree] }; + for (unsigned int i = 1, j = degree - 1; i <= degree; ++i, --j) + { + result = mCoefficient[j] + factor * result; + } + return result; + } + + // Eliminate any leading zeros in the polynomial, except in the case + // the degree is 0 and the coefficient is 0. The elimination is + // necessary when arithmetic operations cause a decrease in the degree + // of the result. For example, (1 + x + x^2) + (1 + 2*x - x^2) = + // (2 + 3*x). The inputs both have degree 2, so the result is created + // with degree 2. After the addition we find that the degree is in + // fact 1 and resize the array of coefficients. This function is + // called internally by the arithmetic operators, but it is exposed in + // the public interface in case you need it for your own purposes. + void EliminateLeadingZeros() + { + size_t size = mCoefficient.size(); + if (size > 1) + { + Real const zero = (Real)0; + int leading; + for (leading = static_cast(size) - 1; leading > 0; --leading) + { + if (mCoefficient[leading] != zero) + { + break; + } + } + + mCoefficient.resize(++leading); + } + } + + // If 'this' is P(t) and the divisor is D(t) with + // degree(P) >= degree(D), then P(t) = Q(t)*D(t)+R(t) where Q(t) is + // the quotient with degree(Q) = degree(P) - degree(D) and R(t) is the + // remainder with degree(R) < degree(D). If this routine is called + // with degree(P) < degree(D), then Q = 0 and R = P are returned. + void Divide(Polynomial1 const& divisor, Polynomial1& quotient, Polynomial1& remainder) const + { + Real const zero = (Real)0; + int divisorDegree = static_cast(divisor.GetDegree()); + int quotientDegree = static_cast(GetDegree()) - divisorDegree; + if (quotientDegree >= 0) + { + quotient.SetDegree(quotientDegree); + + // Temporary storage for the remainder. + Polynomial1 tmp = *this; + + // Do the division using the Euclidean algorithm. + Real inv = ((Real)1) / divisor[divisorDegree]; + for (int i = quotientDegree; i >= 0; --i) + { + int j = divisorDegree + i; + quotient[i] = inv * tmp[j]; + for (j--; j >= i; j--) + { + tmp[j] -= quotient[i] * divisor[j - i]; + } + } + + // Calculate the correct degree for the remainder. + if (divisorDegree >= 1) + { + int remainderDegree = divisorDegree - 1; + while (remainderDegree > 0 && tmp[remainderDegree] == zero) + { + --remainderDegree; + } + + remainder.SetDegree(remainderDegree); + for (int i = 0; i <= remainderDegree; ++i) + { + remainder[i] = tmp[i]; + } + } + else + { + remainder.SetDegree(0); + remainder[0] = zero; + } + } + else + { + quotient.SetDegree(0); + quotient[0] = zero; + remainder = *this; + } + } + + // Scale the polynomial so the highest-degree term has coefficient 1. + void MakeMonic() + { + EliminateLeadingZeros(); + Real const one(1); + if (mCoefficient.back() != one) + { + unsigned int degree = GetDegree(); + Real invLeading = one / mCoefficient.back(); + mCoefficient.back() = one; + for (unsigned int i = 0; i < degree; ++i) + { + mCoefficient[i] *= invLeading; + } + } + } + + protected: + // The class is designed so that mCoefficient.size() >= 1. + std::vector mCoefficient; + }; + + // Compute the greatest common divisor of two polynomials. The returned + // polynomial has leading coefficient 1 (except when zero-valued + // polynomials are passed to the function. + template + Polynomial1 GreatestCommonDivisor(Polynomial1 const& p0, Polynomial1 const& p1) + { + // The numerator should be the polynomial of larger degree. + Polynomial1 a, b; + if (p0.GetDegree() >= p1.GetDegree()) + { + a = p0; + b = p1; + } + else + { + a = p1; + b = p0; + } + + Polynomial1 const zero{ (Real)0 }; + if (a == zero || b == zero) + { + return (a != zero ? a : zero); + } + + // Make the polynomials monic to keep the coefficients reasonable size + // when computing with floating-point Real. + a.MakeMonic(); + b.MakeMonic(); + + Polynomial1 q, r; + for (;;) + { + a.Divide(b, q, r); + if (r != zero) + { + // a = q * b + r, so gcd(a,b) = gcd(b, r) + a = b; + b = r; + b.MakeMonic(); + } + else + { + b.MakeMonic(); + break; + } + } + + return b; + } + + // Factor f = factor[0]*factor[1]^2*factor[2]^3*...*factor[n-1]^n + // according to the square-free factorization algorithm + // https://en.wikipedia.org/wiki/Square-free_polynomial + template + void SquareFreeFactorization(Polynomial1 const& f, std::vector>& factors) + { + // In the call to Divide(...), we know that the divisor exactly + // divides the numerator, so r = 0 after all such calls. + Polynomial1 fder = f.GetDerivative(); + Polynomial1 a, b, c, d, q, r; + + a = GreatestCommonDivisor(f, fder); + f.Divide(a, b, r); // b = f / a + fder.Divide(a, c, r); // c = fder / a + d = c - b.GetDerivative(); + + do + { + a = GreatestCommonDivisor(b, d); + factors.emplace_back(a); + b.Divide(a, q, r); // q = b / a + b = std::move(q); + d.Divide(a, c, r); // c = d / a + d = c - b.GetDerivative(); + } while (b.GetDegree() > 0); + } + + // Unary operations. + template + Polynomial1 operator+(Polynomial1 const& p) + { + return p; + } + + template + Polynomial1 operator-(Polynomial1 const& p) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + for (unsigned int i = 0; i <= degree; ++i) + { + result[i] = -p[i]; + } + return result; + } + + // Linear-algebraic operations. + template + Polynomial1 operator+(Polynomial1 const& p0, Polynomial1 const& p1) + { + unsigned int const p0Degree = p0.GetDegree(), p1Degree = p1.GetDegree(); + unsigned int i; + if (p0Degree >= p1Degree) + { + Polynomial1 result(p0Degree); + for (i = 0; i <= p1Degree; ++i) + { + result[i] = p0[i] + p1[i]; + } + for (/**/; i <= p0Degree; ++i) + { + result[i] = p0[i]; + } + result.EliminateLeadingZeros(); + return result; + } + else + { + Polynomial1 result(p1Degree); + for (i = 0; i <= p0Degree; ++i) + { + result[i] = p0[i] + p1[i]; + } + for (/**/; i <= p1Degree; ++i) + { + result[i] = p1[i]; + } + result.EliminateLeadingZeros(); + return result; + } + } + + template + Polynomial1 operator-(Polynomial1 const& p0, Polynomial1 const& p1) + { + unsigned int const p0Degree = p0.GetDegree(), p1Degree = p1.GetDegree(); + unsigned int i; + if (p0Degree >= p1Degree) + { + Polynomial1 result(p0Degree); + for (i = 0; i <= p1Degree; ++i) + { + result[i] = p0[i] - p1[i]; + } + for (/**/; i <= p0Degree; ++i) + { + result[i] = p0[i]; + } + result.EliminateLeadingZeros(); + return result; + } + else + { + Polynomial1 result(p1Degree); + for (i = 0; i <= p0Degree; ++i) + { + result[i] = p0[i] - p1[i]; + } + for (/**/; i <= p1Degree; ++i) + { + result[i] = -p1[i]; + } + result.EliminateLeadingZeros(); + return result; + } + } + + template + Polynomial1 operator*(Polynomial1 const& p0, Polynomial1 const& p1) + { + unsigned int const p0Degree = p0.GetDegree(), p1Degree = p1.GetDegree(); + Polynomial1 result(p0Degree + p1Degree); + result.SetCoefficients((Real)0); + for (unsigned int i0 = 0; i0 <= p0Degree; ++i0) + { + for (unsigned int i1 = 0; i1 <= p1Degree; ++i1) + { + result[i0 + i1] += p0[i0] * p1[i1]; + } + } + return result; + } + + template + Polynomial1 operator+(Polynomial1 const& p, Real scalar) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + result[0] = p[0] + scalar; + for (unsigned int i = 1; i <= degree; ++i) + { + result[i] = p[i]; + } + return result; + } + + template + Polynomial1 operator+(Real scalar, Polynomial1 const& p) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + result[0] = p[0] + scalar; + for (unsigned int i = 1; i <= degree; ++i) + { + result[i] = p[i]; + } + return result; + } + + template + Polynomial1 operator-(Polynomial1 const& p, Real scalar) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + result[0] = p[0] - scalar; + for (unsigned int i = 1; i <= degree; ++i) + { + result[i] = p[i]; + } + return result; + } + + template + Polynomial1 operator-(Real scalar, Polynomial1 const& p) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + result[0] = scalar - p[0]; + for (unsigned int i = 1; i <= degree; ++i) + { + result[i] = -p[i]; + } + return result; + } + + template + Polynomial1 operator*(Polynomial1 const& p, Real scalar) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + for (unsigned int i = 0; i <= degree; ++i) + { + result[i] = scalar * p[i]; + } + return result; + } + + template + Polynomial1 operator*(Real scalar, Polynomial1 const& p) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + for (unsigned int i = 0; i <= degree; ++i) + { + result[i] = scalar * p[i]; + } + return result; + } + + template + Polynomial1 operator/(Polynomial1 const& p, Real scalar) + { + if (scalar == (Real)0) + { + LogWarning("Division by zero in operator/(p,scalar)."); + } + + unsigned int const degree = p.GetDegree(); + Real invScalar = ((Real)1) / scalar; + Polynomial1 result(degree); + for (unsigned int i = 0; i <= degree; ++i) + { + result[i] = invScalar * p[i]; + } + return result; + } + + template + Polynomial1& operator+=(Polynomial1& p0, Polynomial1 const& p1) + { + p0 = p0 + p1; + return p0; + } + + template + Polynomial1& operator-=(Polynomial1& p0, Polynomial1 const& p1) + { + p0 = p0 - p1; + return p0; + } + + template + Polynomial1& operator*=(Polynomial1& p0, Polynomial1 const& p1) + { + p0 = p0 * p1; + return p0; + } + + template + Polynomial1& operator+=(Polynomial1& p, Real scalar) + { + p[0] += scalar; + return p; + } + + template + Polynomial1& operator-=(Polynomial1& p, Real scalar) + { + p[0] -= scalar; + return p; + } + + template + Polynomial1& operator*=(Polynomial1& p, Real scalar) + { + p = p * scalar; + return p; + } + + template + Polynomial1 & operator/=(Polynomial1& p, Real scalar) + { + p = p / scalar; + return p; + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePrimalQuery2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePrimalQuery2.h new file mode 100644 index 000000000000..d5bca32e7b0f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePrimalQuery2.h @@ -0,0 +1,437 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Queries about the relation of a point to various geometric objects. The +// choices for N when using UIntegerFP32 for either BSNumber of BSRational +// are determined in GeometricTools/GTEngine/Tools/PrecisionCalculator. These +// N-values are worst case scenarios. Your specific input data might require +// much smaller N, in which case you can modify PrecisionCalculator to use the +// BSPrecision(int32_t,int32_t,int32_t,bool) constructors. + +namespace gte +{ + +template +class PrimalQuery2 +{ +public: + // The caller is responsible for ensuring that the array is not empty + // before calling queries and that the indices passed to the queries are + // valid. The class does no range checking. + PrimalQuery2(); + PrimalQuery2(int numVertices, Vector2 const* vertices); + + // Member access. + inline void Set(int numVertices, Vector2 const* vertices); + inline int GetNumVertices() const; + inline Vector2 const* GetVertices() const; + + // In the following, point P refers to vertices[i] or 'test' and Vi refers + // to vertices[vi]. + + // For a line with origin V0 and direction , ToLine returns + // +1, P on right of line + // -1, P on left of line + // 0, P on the line + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+----- + // float | BSNumber | 18 + // double | BSNumber | 132 + // float | BSRational | 214 + // double | BSRational | 1587 + int ToLine(int i, int v0, int v1) const; + int ToLine(Vector2 const& test, int v0, int v1) const; + + // For a line with origin V0 and direction , ToLine returns + // +1, P on right of line + // -1, P on left of line + // 0, P on the line + // The 'order' parameter is + // -3, points not collinear, P on left of line + // -2, P strictly left of V0 on the line + // -1, P = V0 + // 0, P interior to line segment [V0,V1] + // +1, P = V1 + // +2, P strictly right of V0 on the line + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+----- + // float | BSNumber | 18 + // double | BSNumber | 132 + // float | BSRational | 214 + // double | BSRational | 1587 + // This is the same as the first-listed ToLine calls because the worst-case + // path has the same computational complexity. + int ToLine(int i, int v0, int v1, int& order) const; + int ToLine(Vector2 const& test, int v0, int v1, int& order) const; + + // For a triangle with counterclockwise vertices V0, V1, and V2, + // ToTriangle returns + // +1, P outside triangle + // -1, P inside triangle + // 0, P on triangle + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+----- + // float | BSNumber | 18 + // double | BSNumber | 132 + // float | BSRational | 214 + // double | BSRational | 1587 + // The query involves three calls to ToLine, so the numbers match those + // of ToLine. + int ToTriangle(int i, int v0, int v1, int v2) const; + int ToTriangle(Vector2 const& test, int v0, int v1, int v2) const; + + // For a triangle with counterclockwise vertices V0, V1, and V2, + // ToCircumcircle returns + // +1, P outside circumcircle of triangle + // -1, P inside circumcircle of triangle + // 0, P on circumcircle of triangle + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+------ + // float | BSNumber | 35 + // double | BSNumber | 263 + // float | BSRational | 12302 + // double | BSRational | 92827 + // The query involves three calls of ToLine, so the numbers match those + // of ToLine. + int ToCircumcircle(int i, int v0, int v1, int v2) const; + int ToCircumcircle(Vector2 const& test, int v0, int v1, int v2) const; + + // An extended classification of the relationship of a point to a line + // segment. For noncollinear points, the return value is + // ORDER_POSITIVE when is a counterclockwise triangle + // ORDER_NEGATIVE when is a clockwise triangle + // For collinear points, the line direction is Q1-Q0. The return value is + // ORDER_COLLINEAR_LEFT when the line ordering is + // ORDER_COLLINEAR_RIGHT when the line ordering is + // ORDER_COLLINEAR_CONTAIN when the line ordering is + enum OrderType + { + ORDER_Q0_EQUALS_Q1, + ORDER_P_EQUALS_Q0, + ORDER_P_EQUALS_Q1, + ORDER_POSITIVE, + ORDER_NEGATIVE, + ORDER_COLLINEAR_LEFT, + ORDER_COLLINEAR_RIGHT, + ORDER_COLLINEAR_CONTAIN + }; + + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+----- + // float | BSNumber | 18 + // double | BSNumber | 132 + // float | BSRational | 214 + // double | BSRational | 1587 + // This is the same as the first-listed ToLine calls because the worst-case + // path has the same computational complexity. + OrderType ToLineExtended(Vector2 const& P, Vector2 const& Q0, Vector2 const& Q1) const; + +private: + int mNumVertices; + Vector2 const* mVertices; +}; + + +template +PrimalQuery2::PrimalQuery2() + : + mNumVertices(0), + mVertices(nullptr) +{ +} + +template +PrimalQuery2::PrimalQuery2(int numVertices, + Vector2 const* vertices) + : + mNumVertices(numVertices), + mVertices(vertices) +{ +} + +template inline +void PrimalQuery2::Set(int numVertices, Vector2 const* vertices) +{ + mNumVertices = numVertices; + mVertices = vertices; +} + +template inline +int PrimalQuery2::GetNumVertices() const +{ + return mNumVertices; +} + +template inline +Vector2 const* PrimalQuery2::GetVertices() const +{ + return mVertices; +} + +template +int PrimalQuery2::ToLine(int i, int v0, int v1) const +{ + return ToLine(mVertices[i], v0, v1); +} + +template +int PrimalQuery2::ToLine(Vector2 const& test, int v0, int v1) const +{ + Vector2 const& vec0 = mVertices[v0]; + Vector2 const& vec1 = mVertices[v1]; + + Real x0 = test[0] - vec0[0]; + Real y0 = test[1] - vec0[1]; + Real x1 = vec1[0] - vec0[0]; + Real y1 = vec1[1] - vec0[1]; + Real x0y1 = x0*y1; + Real x1y0 = x1*y0; + Real det = x0y1 - x1y0; + Real const zero(0); + + return (det > zero ? +1 : (det < zero ? -1 : 0)); +} + +template +int PrimalQuery2::ToLine(int i, int v0, int v1, int& order) const +{ + return ToLine(mVertices[i], v0, v1, order); +} + +template +int PrimalQuery2::ToLine(Vector2 const& test, int v0, int v1, int& order) const +{ + Vector2 const& vec0 = mVertices[v0]; + Vector2 const& vec1 = mVertices[v1]; + + Real x0 = test[0] - vec0[0]; + Real y0 = test[1] - vec0[1]; + Real x1 = vec1[0] - vec0[0]; + Real y1 = vec1[1] - vec0[1]; + Real x0y1 = x0*y1; + Real x1y0 = x1*y0; + Real det = x0y1 - x1y0; + Real const zero(0); + + if (det > zero) + { + order = +3; + return +1; + } + + if (det < zero) + { + order = -3; + return -1; + } + + Real x0x1 = x0*x1; + Real y0y1 = y0*y1; + Real dot = x0x1 + y0y1; + if (dot == zero) + { + order = -1; + } + else if (dot < zero) + { + order = -2; + } + else + { + Real x0x0 = x0*x0; + Real y0y0 = y0*y0; + Real sqrLength = x0x0 + y0y0; + if (dot == sqrLength) + { + order = +1; + } + else if (dot > sqrLength) + { + order = +2; + } + else + { + order = 0; + } + } + + return 0; +} + +template +int PrimalQuery2::ToTriangle(int i, int v0, int v1, int v2) const +{ + return ToTriangle(mVertices[i], v0, v1, v2); +} + +template +int PrimalQuery2::ToTriangle(Vector2 const& test, int v0, int v1, int v2) const +{ + int sign0 = ToLine(test, v1, v2); + if (sign0 > 0) + { + return +1; + } + + int sign1 = ToLine(test, v0, v2); + if (sign1 < 0) + { + return +1; + } + + int sign2 = ToLine(test, v0, v1); + if (sign2 > 0) + { + return +1; + } + + return ((sign0 && sign1 && sign2) ? -1 : 0); +} + +template +int PrimalQuery2::ToCircumcircle(int i, int v0, int v1, int v2) const +{ + return ToCircumcircle(mVertices[i], v0, v1, v2); +} + +template +int PrimalQuery2::ToCircumcircle(Vector2 const& test, int v0, int v1, int v2) const +{ + Vector2 const& vec0 = mVertices[v0]; + Vector2 const& vec1 = mVertices[v1]; + Vector2 const& vec2 = mVertices[v2]; + + Real x0 = vec0[0] - test[0]; + Real y0 = vec0[1] - test[1]; + Real s00 = vec0[0] + test[0]; + Real s01 = vec0[1] + test[1]; + Real t00 = s00*x0; + Real t01 = s01*y0; + Real z0 = t00 + t01; + + Real x1 = vec1[0] - test[0]; + Real y1 = vec1[1] - test[1]; + Real s10 = vec1[0] + test[0]; + Real s11 = vec1[1] + test[1]; + Real t10 = s10*x1; + Real t11 = s11*y1; + Real z1 = t10 + t11; + + Real x2 = vec2[0] - test[0]; + Real y2 = vec2[1] - test[1]; + Real s20 = vec2[0] + test[0]; + Real s21 = vec2[1] + test[1]; + Real t20 = s20*x2; + Real t21 = s21*y2; + Real z2 = t20 + t21; + + Real y0z1 = y0*z1; + Real y0z2 = y0*z2; + Real y1z0 = y1*z0; + Real y1z2 = y1*z2; + Real y2z0 = y2*z0; + Real y2z1 = y2*z1; + Real c0 = y1z2 - y2z1; + Real c1 = y2z0 - y0z2; + Real c2 = y0z1 - y1z0; + Real x0c0 = x0*c0; + Real x1c1 = x1*c1; + Real x2c2 = x2*c2; + Real term = x0c0 + x1c1; + Real det = term + x2c2; + Real const zero(0); + + return (det < zero ? 1 : (det > zero ? -1 : 0)); +} + +template +typename PrimalQuery2::OrderType PrimalQuery2::ToLineExtended( + Vector2 const& P, Vector2 const& Q0, Vector2 const& Q1) const +{ + Real const zero(0); + + Real x0 = Q1[0] - Q0[0]; + Real y0 = Q1[1] - Q0[1]; + if (x0 == zero && y0 == zero) + { + return ORDER_Q0_EQUALS_Q1; + } + + Real x1 = P[0] - Q0[0]; + Real y1 = P[1] - Q0[1]; + if (x1 == zero && y1 == zero) + { + return ORDER_P_EQUALS_Q0; + } + + Real x2 = P[0] - Q1[0]; + Real y2 = P[1] - Q1[1]; + if (x2 == zero && y2 == zero) + { + return ORDER_P_EQUALS_Q1; + } + + // The theoretical classification relies on computing exactly the sign of + // the determinant. Numerical roundoff errors can cause misclassification. + Real x0y1 = x0 * y1; + Real x1y0 = x1 * y0; + Real det = x0y1 - x1y0; + + if (det != zero) + { + if (det > zero) + { + // The points form a counterclockwise triangle . + return ORDER_POSITIVE; + } + else + { + // The points form a clockwise triangle . + return ORDER_NEGATIVE; + } + } + else + { + // The points are collinear; P is on the line through Q0 and Q1. + Real x0x1 = x0 * x1; + Real y0y1 = y0 * y1; + Real dot = x0x1 + y0y1; + if (dot < zero) + { + // The line ordering is . + return ORDER_COLLINEAR_LEFT; + } + + Real x0x0 = x0 * x0; + Real y0y0 = y0 * y0; + Real sqrLength = x0x0 + y0y0; + if (dot > sqrLength) + { + // The line ordering is . + return ORDER_COLLINEAR_RIGHT; + } + + // The line ordering is with P strictly between Q0 and Q1. + return ORDER_COLLINEAR_CONTAIN; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePrimalQuery3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePrimalQuery3.h new file mode 100644 index 000000000000..7e44f23145d9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePrimalQuery3.h @@ -0,0 +1,330 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Queries about the relation of a point to various geometric objects. The +// choices for N when using UIntegerFP32 for either BSNumber of BSRational +// are determined in GeometricTools/GTEngine/Tools/PrecisionCalculator. These +// N-values are worst case scenarios. Your specific input data might require +// much smaller N, in which case you can modify PrecisionCalculator to use the +// BSPrecision(int32_t,int32_t,int32_t,bool) constructors. + +namespace gte +{ + +template +class PrimalQuery3 +{ +public: + // The caller is responsible for ensuring that the array is not empty + // before calling queries and that the indices passed to the queries are + // valid. The class does no range checking. + PrimalQuery3(); + PrimalQuery3(int numVertices, Vector3 const* vertices); + + // Member access. + inline void Set(int numVertices, Vector3 const* vertices); + inline int GetNumVertices() const; + inline Vector3 const* GetVertices() const; + + // In the following, point P refers to vertices[i] or 'test' and Vi refers + // to vertices[vi]. + + // For a plane with origin V0 and normal N = Cross(V1-V0,V2-V0), ToPlane + // returns + // +1, P on positive side of plane (side to which N points) + // -1, P on negative side of plane (side to which -N points) + // 0, P on the plane + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+------ + // float | BSNumber | 27 + // double | BSNumber | 197 + // float | BSRational | 2882 + // double | BSRational | 21688 + int ToPlane(int i, int v0, int v1, int v2) const; + int ToPlane(Vector3 const& test, int v0, int v1, int v2) const; + + // For a tetrahedron with vertices ordered as described in the file + // GteTetrahedronKey.h, the function returns + // +1, P outside tetrahedron + // -1, P inside tetrahedron + // 0, P on tetrahedron + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+---- + // float | BSNumber | 27 + // double | BSNumber | 197 + // float | BSRational | 2882 + // double | BSRational | 21688 + // The query involves four calls of ToPlane, so the numbers match those + // of ToPlane. + int ToTetrahedron(int i, int v0, int v1, int v2, int v3) const; + int ToTetrahedron(Vector3 const& test, int v0, int v1, int v2, int v3) const; + + // For a tetrahedron with vertices ordered as described in the file + // GteTetrahedronKey.h, the function returns + // +1, P outside circumsphere of tetrahedron + // -1, P inside circumsphere of tetrahedron + // 0, P on circumsphere of tetrahedron + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+-------- + // float | BSNumber | 44 + // float | BSRational | 329 + // double | BSNumber | 298037 + // double | BSRational | 2254442 + int ToCircumsphere(int i, int v0, int v1, int v2, int v3) const; + int ToCircumsphere(Vector3 const& test, int v0, int v1, int v2, int v3) const; + +private: + int mNumVertices; + Vector3 const* mVertices; +}; + + +template +PrimalQuery3::PrimalQuery3() + : + mNumVertices(0), + mVertices(nullptr) +{ +} + +template +PrimalQuery3::PrimalQuery3(int numVertices, + Vector3 const* vertices) + : + mNumVertices(numVertices), + mVertices(vertices) +{ +} + +template inline +void PrimalQuery3::Set(int numVertices, Vector3 const* vertices) +{ + mNumVertices = numVertices; + mVertices = vertices; +} + +template inline +int PrimalQuery3::GetNumVertices() const +{ + return mNumVertices; +} + +template inline +Vector3 const* PrimalQuery3::GetVertices() const +{ + return mVertices; +} + +template +int PrimalQuery3::ToPlane(int i, int v0, int v1, int v2) const +{ + return ToPlane(mVertices[i], v0, v1, v2); +} + +template +int PrimalQuery3::ToPlane(Vector3 const& test, int v0, int v1, + int v2) const +{ + Vector3 const& vec0 = mVertices[v0]; + Vector3 const& vec1 = mVertices[v1]; + Vector3 const& vec2 = mVertices[v2]; + + Real x0 = test[0] - vec0[0]; + Real y0 = test[1] - vec0[1]; + Real z0 = test[2] - vec0[2]; + Real x1 = vec1[0] - vec0[0]; + Real y1 = vec1[1] - vec0[1]; + Real z1 = vec1[2] - vec0[2]; + Real x2 = vec2[0] - vec0[0]; + Real y2 = vec2[1] - vec0[1]; + Real z2 = vec2[2] - vec0[2]; + Real y1z2 = y1*z2; + Real y2z1 = y2*z1; + Real y2z0 = y2*z0; + Real y0z2 = y0*z2; + Real y0z1 = y0*z1; + Real y1z0 = y1*z0; + Real c0 = y1z2 - y2z1; + Real c1 = y2z0 - y0z2; + Real c2 = y0z1 - y1z0; + Real x0c0 = x0*c0; + Real x1c1 = x1*c1; + Real x2c2 = x2*c2; + Real term = x0c0 + x1c1; + Real det = term + x2c2; + Real const zero(0); + + return (det > zero ? +1 : (det < zero ? -1 : 0)); +} + +template +int PrimalQuery3::ToTetrahedron(int i, int v0, int v1, int v2, int v3) + const +{ + return ToTetrahedron(mVertices[i], v0, v1, v2, v3); +} + +template +int PrimalQuery3::ToTetrahedron(Vector3 const& test, int v0, + int v1, int v2, int v3) const +{ + int sign0 = ToPlane(test, v1, v2, v3); + if (sign0 > 0) + { + return +1; + } + + int sign1 = ToPlane(test, v0, v2, v3); + if (sign1 < 0) + { + return +1; + } + + int sign2 = ToPlane(test, v0, v1, v3); + if (sign2 > 0) + { + return +1; + } + + int sign3 = ToPlane(test, v0, v1, v2); + if (sign3 < 0) + { + return +1; + } + + return ((sign0 && sign1 && sign2 && sign3) ? -1 : 0); +} + +template +int PrimalQuery3::ToCircumsphere(int i, int v0, int v1, int v2, int v3) +const +{ + return ToCircumsphere(mVertices[i], v0, v1, v2, v3); +} + +template +int PrimalQuery3::ToCircumsphere(Vector3 const& test, int v0, + int v1, int v2, int v3) const +{ + Vector3 const& vec0 = mVertices[v0]; + Vector3 const& vec1 = mVertices[v1]; + Vector3 const& vec2 = mVertices[v2]; + Vector3 const& vec3 = mVertices[v3]; + + Real x0 = vec0[0] - test[0]; + Real y0 = vec0[1] - test[1]; + Real z0 = vec0[2] - test[2]; + Real s00 = vec0[0] + test[0]; + Real s01 = vec0[1] + test[1]; + Real s02 = vec0[2] + test[2]; + Real t00 = s00*x0; + Real t01 = s01*y0; + Real t02 = s02*z0; + Real t00pt01 = t00 + t01; + Real w0 = t00pt01 + t02; + + Real x1 = vec1[0] - test[0]; + Real y1 = vec1[1] - test[1]; + Real z1 = vec1[2] - test[2]; + Real s10 = vec1[0] + test[0]; + Real s11 = vec1[1] + test[1]; + Real s12 = vec1[2] + test[2]; + Real t10 = s10*x1; + Real t11 = s11*y1; + Real t12 = s12*z1; + Real t10pt11 = t10 + t11; + Real w1 = t10pt11 + t12; + + Real x2 = vec2[0] - test[0]; + Real y2 = vec2[1] - test[1]; + Real z2 = vec2[2] - test[2]; + Real s20 = vec2[0] + test[0]; + Real s21 = vec2[1] + test[1]; + Real s22 = vec2[2] + test[2]; + Real t20 = s20*x2; + Real t21 = s21*y2; + Real t22 = s22*z2; + Real t20pt21 = t20 + t21; + Real w2 = t20pt21 + t22; + + Real x3 = vec3[0] - test[0]; + Real y3 = vec3[1] - test[1]; + Real z3 = vec3[2] - test[2]; + Real s30 = vec3[0] + test[0]; + Real s31 = vec3[1] + test[1]; + Real s32 = vec3[2] + test[2]; + Real t30 = s30*x3; + Real t31 = s31*y3; + Real t32 = s32*z3; + Real t30pt31 = t30 + t31; + Real w3 = t30pt31 + t32; + + Real x0y1 = x0*y1; + Real x0y2 = x0*y2; + Real x0y3 = x0*y3; + Real x1y0 = x1*y0; + Real x1y2 = x1*y2; + Real x1y3 = x1*y3; + Real x2y0 = x2*y0; + Real x2y1 = x2*y1; + Real x2y3 = x2*y3; + Real x3y0 = x3*y0; + Real x3y1 = x3*y1; + Real x3y2 = x3*y2; + Real a0 = x0y1 - x1y0; + Real a1 = x0y2 - x2y0; + Real a2 = x0y3 - x3y0; + Real a3 = x1y2 - x2y1; + Real a4 = x1y3 - x3y1; + Real a5 = x2y3 - x3y2; + + Real z0w1 = z0*w1; + Real z0w2 = z0*w2; + Real z0w3 = z0*w3; + Real z1w0 = z1*w0; + Real z1w2 = z1*w2; + Real z1w3 = z1*w3; + Real z2w0 = z2*w0; + Real z2w1 = z2*w1; + Real z2w3 = z2*w3; + Real z3w0 = z3*w0; + Real z3w1 = z3*w1; + Real z3w2 = z3*w2; + Real b0 = z0w1 - z1w0; + Real b1 = z0w2 - z2w0; + Real b2 = z0w3 - z3w0; + Real b3 = z1w2 - z2w1; + Real b4 = z1w3 - z3w1; + Real b5 = z2w3 - z3w2; + Real a0b5 = a0*b5; + Real a1b4 = a1*b4; + Real a2b3 = a2*b3; + Real a3b2 = a3*b2; + Real a4b1 = a4*b1; + Real a5b0 = a5*b0; + Real term0 = a0b5 - a1b4; + Real term1 = term0 + a2b3; + Real term2 = term1 + a3b2; + Real term3 = term2 - a4b1; + Real det = term3 + a5b0; + Real const zero(0); + + return (det > zero ? 1 : (det < zero ? -1 : 0)); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteProjection.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteProjection.h new file mode 100644 index 000000000000..dcf078297840 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteProjection.h @@ -0,0 +1,71 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +// Project an ellipse onto a line. The projection interval is [smin,smax] +// and corresponds to the line segment P+s*D, where smin <= s <= smax. +template +void Project(Ellipse2 const& ellipse, Line2 const& line, + Real& smin, Real& smax); + +// Project an ellipsoid onto a line. The projection interval is [smin,smax] +// and corresponds to the line segment P+s*D, where smin <= s <= smax. +template +void Project(Ellipsoid3 const& ellipsoid, + Line3 const& line, Real& smin, Real& smax); + + +template +void Project(Ellipse2 const& ellipse, Line2 const& line, + Real& smin, Real& smax) +{ + // Center of projection interval. + Real center = Dot(line.direction, ellipse.center - line.origin); + + // Radius of projection interval. + Real tmp[2] = + { + ellipse.extent[0] * Dot(line.direction, ellipse.axis[0]), + ellipse.extent[1] * Dot(line.direction, ellipse.axis[1]) + }; + Real rSqr = tmp[0] * tmp[0] + tmp[1] * tmp[1]; + Real radius = std::sqrt(rSqr); + + smin = center - radius; + smax = center + radius; +} + +template +void Project(Ellipsoid3 const& ellipsoid, Line3 const& line, + Real& smin, Real& smax) +{ + // Center of projection interval. + Real center = Dot(line.direction, ellipsoid.center - line.origin); + + // Radius of projection interval. + Real tmp[3] = + { + ellipsoid.extent[0] * Dot(line.direction, ellipsoid.axis[0]), + ellipsoid.extent[1] * Dot(line.direction, ellipsoid.axis[1]), + ellipsoid.extent[2] * Dot(line.direction, ellipsoid.axis[2]) + }; + Real rSqr = tmp[0] * tmp[0] + tmp[1] * tmp[1] + tmp[2] * tmp[2]; + Real radius = std::sqrt(rSqr); + + smin = center - radius; + smax = center + radius; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuadraticField.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuadraticField.h new file mode 100644 index 000000000000..e3a6c5023847 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuadraticField.h @@ -0,0 +1,368 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.21.0 (2019/01/21) + +#pragma once + +#include + +// A real quadratic field has elements of the form z = x + y * sqrt(d), where +// x, y and d are rational numbers and where d > 0. In abstract algebra, +// it is require that d not be the square of a rational number. When this +// is the case, the representation of z is unique. For GTEngine, the +// square-free constraint is not essential, because we need only the +// arithmetic properties of the field (addition, subtraction, multiplication +// and division). We also need comparisons between elements of the quadratic +// field, but these can be reformulated to use only operations for rational +// arithmetic. + +namespace gte +{ + // The type T can be rational (for arbitrary precision arithmetic) or + // floating-point (the quadratic field operations are valid, although + // with floating-point rounding errors). + template + class QuadraticField + { + public: + // The quadratic field descriptor that simply stores d. + QuadraticField() + : + d(0) + { + } + + QuadraticField(T const& inD) + : + d(inD) + { + } + + T d; + + // Elements of the quadratic field. The arithmetic and comparisons + // are managed by the quadratic field itself to avoid having multiple + // copies of d (one copy per Element). + class Element + { + public: + Element() + : + x(0), + y(0) + { + } + + Element(T const& inX, T const& inY) + : + x(inX), + y(inY) + { + } + + T x, y; + }; + + T Convert(Element const& e) const + { + return e.x + e.y * std::sqrt(d); + } + + Element Negate(Element const& e) const + { + return Element(-e.x, -e.y); + } + + Element Add(Element const& e0, Element const& e1) const + { + T x = e0.x + e1.x; + T y = e0.y + e1.y; + return Element(x, y); + } + + Element Add(Element const& e, T const& s) const + { + T x = e.x + s; + T y = e.y; + return Element(x, y); + } + + Element Add(T const& s, Element const& e) const + { + T x = s + e.x; + T y = e.y; + return Element(x, y); + } + + Element Sub(Element const& e0, Element const& e1) const + { + T x = e0.x - e1.x; + T y = e0.y - e1.y; + return Element(x, y); + } + + Element Sub(Element const& e, T const& s) const + { + T x = e.x - s; + T y = e.y; + return Element(x, y); + } + + Element Sub(T const& s, Element const& e) const + { + T x = s - e.x; + T y = -e.y; + return Element(x, y); + } + + Element Mul(Element const& e0, Element const& e1) const + { + T x = e0.x * e1.x + e0.y * e1.y * d; + T y = e0.x * e1.y + e0.y * e1.x; + return Element(x, y); + } + + Element Mul(Element const& e, T const& s) + { + return Element(e.x * s, e.y * s); + } + + Element Mul(T const& s, Element const& e) + { + return Element(s * e.x, s * e.y); + } + + // Expose division only when T has a division operator. + template + typename std::enable_if::value, Element>::type + Div(Element const& e0, Element const& e1) const + { + T z = e1.x * e1.x - e1.y * e1.y * d; + T x = (e0.x * e1.x - e0.y * e1.y * d) / z; + T y = (e0.y * e1.x - e0.x * e1.y) / z; + return Element(x, y); + } + + template + typename std::enable_if::value, Element>::type + Div(Element const& e, T const& s) + { + return Element(e.x / s, e.y / s); + } + + // Comparison functions. + bool EqualZero(Element const& e) const + { + T const zero(0); + if (d == zero || e.y == zero) + { + return e.x == zero; + } + else if (e.y > zero) + { + if (e.x >= zero) + { + return false; + } + else + { + return e.x * e.x - e.y * e.y * d == zero; + } + } + else // e.y < zero + { + if (e.x <= zero) + { + return false; + } + else + { + return e.x * e.x - e.y * e.y * d == zero; + } + } + } + + bool NotEqualZero(Element const& e) const + { + T const zero(0); + if (d == zero || e.y == zero) + { + return e.x != zero; + } + else if (e.y > zero) + { + if (e.x >= zero) + { + return true; + } + else + { + return e.x * e.x - e.y * e.y * d != zero; + } + } + else // e.y < zero + { + if (e.x <= zero) + { + return true; + } + else + { + return e.x * e.x - e.y * e.y * d != zero; + } + } + } + + bool LessThanZero(Element const& e) const + { + T const zero(0); + if (d == zero || e.y == zero) + { + return e.x < zero; + } + else if (e.y > zero) + { + if (e.x >= zero) + { + return false; + } + else + { + return e.x * e.x - e.y * e.y * d > zero; + } + } + else // e.y < zero + { + if (e.x <= zero) + { + return true; + } + else + { + return e.x * e.x - e.y * e.y * d < zero; + } + } + } + + bool GreaterThanZero(Element const& e) const + { + T const zero(0); + if (d == zero || e.y == zero) + { + return e.x > zero; + } + else if (e.y > zero) + { + if (e.x >= zero) + { + return true; + } + else + { + return e.x * e.x - e.y * e.y * d < zero; + } + } + else // e.y < zero + { + if (e.x <= zero) + { + return false; + } + else + { + return e.x * e.x - e.y * e.y * d > zero; + } + } + } + + bool LessThanOrEqualZero(Element const& e) const + { + T const zero(0); + if (d == zero || e.y == zero) + { + return e.x <= zero; + } + else if (e.y > zero) + { + if (e.x >= zero) + { + return false; + } + else + { + return e.x * e.x - e.y * e.y * d >= zero; + } + } + else // e.y < zero + { + if (e.x <= zero) + { + return true; + } + else + { + return e.x * e.x - e.y * e.y * d <= zero; + } + } + } + + bool GreaterThanOrEqualZero(Element const& e) const + { + T const zero(0); + if (d == zero || e.y == zero) + { + return e.x >= zero; + } + else if (e.y > zero) + { + if (e.x >= zero) + { + return true; + } + else + { + return e.x * e.x - e.y * e.y * d <= zero; + } + } + else // e.y < zero + { + if (e.x <= zero) + { + return false; + } + else + { + return e.x * e.x - e.y * e.y * d >= zero; + } + } + } + + bool Equal(Element const& e0, Element const& e1) const + { + return EqualZero(Sub(e0, e1)); + } + + bool LessThan(Element const& e0, Element const& e1) const + { + return LessThanZero(Sub(e0, e1)); + } + + bool GreaterThan(Element const& e0, Element const& e1) const + { + return GreaterThanZero(Sub(e0, e1)); + } + + bool LessThanOrEqual(Element const& e0, Element const& e1) const + { + return LessThanOrEqualZero(Sub(e0, e1)); + } + + bool GreaterThanOrEqual(Element const& e0, Element const& e1) const + { + return GreaterThanOrEqualZero(Sub(e0, e1)); + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuarticRootsQR.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuarticRootsQR.h new file mode 100644 index 000000000000..f9fe78bebf4f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuarticRootsQR.h @@ -0,0 +1,305 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include "GteCubicRootsQR.h" + +// An implementation of the QR algorithm described in "Matrix Computations, +// 2nd edition" by G. H. Golub and C. F. Van Loan, The Johns Hopkins +// University Press, Baltimore MD, Fourth Printing 1993. In particular, +// the implementation is based on Chapter 7 (The Unsymmetric Eigenvalue +// Problem), Section 7.5 (The Practical QR Algorithm). The algorithm is +// specialized for the companion matrix associated with a quartic polynomial. + +namespace gte +{ + +template +class QuarticRootsQR +{ +public: + typedef std::array, 4> Matrix; + + // Solve p(x) = c0 + c1 * x + c2 * x^2 + c3 * x^3 + x^4 = 0. + uint32_t operator() (uint32_t maxIterations, Real c0, Real c1, Real c2, Real c3, + uint32_t& numRoots, std::array& roots); + + // Compute the real eigenvalues of the upper Hessenberg matrix A. The + // matrix is modified by in-place operations, so if you need to remember + // A, you must make your own copy before calling this function. + uint32_t operator() (uint32_t maxIterations, Matrix& A, + uint32_t& numRoots, std::array& roots); + +private: + void DoIteration(std::array const& V, Matrix& A); + + template + std::array House(std::array const& X); + + template + void RowHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A); + + template + void ColHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A); + + void GetQuadraticRoots(int i0, int i1, Matrix const& A, + uint32_t& numRoots, std::array& roots); +}; + + +template +uint32_t QuarticRootsQR::operator() (uint32_t maxIterations, Real c0, Real c1, Real c2, Real c3, + uint32_t& numRoots, std::array& roots) +{ + // Create the companion matrix for the polynomial. The matrix is in upper + // Hessenberg form. + Matrix A; + A[0][0] = (Real)0; + A[0][1] = (Real)0; + A[0][2] = (Real)0; + A[0][3] = -c0; + A[1][0] = (Real)1; + A[1][1] = (Real)0; + A[1][2] = (Real)0; + A[1][3] = -c1; + A[2][0] = (Real)0; + A[2][1] = (Real)1; + A[2][2] = (Real)0; + A[2][3] = -c2; + A[3][0] = (Real)0; + A[3][1] = (Real)0; + A[3][2] = (Real)1; + A[3][3] = -c3; + + // Avoid the QR-cycle when c1 = c2 = 0 and avoid the slow convergence + // when c1 and c2 are nearly zero. + std::array V{ + (Real)1, + (Real)0.36602540378443865, + (Real)0.36602540378443865 }; + DoIteration(V, A); + + return operator()(maxIterations, A, numRoots, roots); +} + +template +uint32_t QuarticRootsQR::operator() (uint32_t maxIterations, Matrix& A, + uint32_t& numRoots, std::array& roots) +{ + numRoots = 0; + std::fill(roots.begin(), roots.end(), (Real)0); + + for (uint32_t numIterations = 0; numIterations < maxIterations; ++numIterations) + { + // Apply a Francis QR iteration. + Real tr = A[2][2] + A[3][3]; + Real det = A[2][2] * A[3][3] - A[2][3] * A[3][2]; + std::array X{ + A[0][0] * A[0][0] + A[0][1] * A[1][0] - tr * A[0][0] + det, + A[1][0] * (A[0][0] + A[1][1] - tr), + A[1][0] * A[2][1] }; + std::array V = House<3>(X); + DoIteration(V, A); + + // Test for uncoupling of A. + Real tr12 = A[1][1] + A[2][2]; + if (tr12 + A[2][1] == tr12) + { + GetQuadraticRoots(0, 1, A, numRoots, roots); + GetQuadraticRoots(2, 3, A, numRoots, roots); + return numIterations; + } + + Real tr01 = A[0][0] + A[1][1]; + if (tr01 + A[1][0] == tr01) + { + numRoots = 1; + roots[0] = A[0][0]; + + // TODO: The cubic solver is not designed to process 3x3 submatrices + // of an NxN matrix, so the copy of a submatrix of A to B is a simple + // workaround for running the solver. Write general root-finding + // code that avoids such copying. + uint32_t subMaxIterations = maxIterations - numIterations; + typename CubicRootsQR::Matrix B; + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + B[r][c] = A[r + 1][c + 1]; + } + } + + uint32_t numSubroots = 0; + std::array subroots; + uint32_t numSubiterations = CubicRootsQR()(subMaxIterations, B, + numSubroots, subroots); + for (uint32_t i = 0; i < numSubroots; ++i) + { + roots[numRoots++] = subroots[i]; + } + return numIterations + numSubiterations; + } + + Real tr23 = A[2][2] + A[3][3]; + if (tr23 + A[3][2] == tr23) + { + numRoots = 1; + roots[0] = A[3][3]; + + // TODO: The cubic solver is not designed to process 3x3 submatrices + // of an NxN matrix, so the copy of a submatrix of A to B is a simple + // workaround for running the solver. Write general root-finding + // code that avoids such copying. + uint32_t subMaxIterations = maxIterations - numIterations; + typename CubicRootsQR::Matrix B; + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + B[r][c] = A[r][c]; + } + } + + uint32_t numSubroots = 0; + std::array subroots; + uint32_t numSubiterations = CubicRootsQR()(subMaxIterations, B, + numSubroots, subroots); + for (uint32_t i = 0; i < numSubroots; ++i) + { + roots[numRoots++] = subroots[i]; + } + return numIterations + numSubiterations; + } + } + return maxIterations; +} + +template +void QuarticRootsQR::DoIteration(std::array const& V, Matrix& A) +{ + Real multV = ((Real)-2) / (V[0] * V[0] + V[1] * V[1] + V[2] * V[2]); + std::array MV{ multV * V[0], multV * V[1], multV * V[2] }; + RowHouse<3>(0, 2, 0, 3, V, MV, A); + ColHouse<3>(0, 3, 0, 2, V, MV, A); + + std::array X{ A[1][0], A[2][0], A[3][0] }; + std::array locV = House<3>(X); + multV = ((Real)-2) / (locV[0] * locV[0] + locV[1] * locV[1] + locV[2] * locV[2]); + MV = { multV * locV[0], multV * locV[1], multV * locV[2] }; + RowHouse<3>(1, 3, 0, 3, locV, MV, A); + ColHouse<3>(0, 3, 1, 3, locV, MV, A); + + std::array Y{ A[2][1], A[3][1] }; + std::array W = House<2>(Y); + Real multW = ((Real)-2) / (W[0] * W[0] + W[1] * W[1]); + std::array MW = { multW * W[0], multW * W[1] }; + RowHouse<2>(2, 3, 0, 3, W, MW, A); + ColHouse<2>(0, 3, 2, 3, W, MW, A); +} + +template +template +std::array QuarticRootsQR::House(std::array const & X) +{ + std::array V; + Real length = (Real)0; + for (int i = 0; i < N; ++i) + { + length += X[i] * X[i]; + } + length = std::sqrt(length); + if (length != (Real)0) + { + Real sign = (X[0] >= (Real)0 ? (Real)1 : (Real)-1); + Real denom = X[0] + sign * length; + for (int i = 1; i < N; ++i) + { + V[i] = X[i] / denom; + } + } + V[0] = (Real)1; + return V; +} + +template +template +void QuarticRootsQR::RowHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A) +{ + std::array W; // only elements cmin through cmax are used + + for (int c = cmin; c <= cmax; ++c) + { + W[c] = (Real)0; + for (int r = rmin, k = 0; r <= rmax; ++r, ++k) + { + W[c] += V[k] * A[r][c]; + } + } + + for (int r = rmin, k = 0; r <= rmax; ++r, ++k) + { + for (int c = cmin; c <= cmax; ++c) + { + A[r][c] += MV[k] * W[c]; + } + } +} + +template +template +void QuarticRootsQR::ColHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A) +{ + std::array W; // only elements rmin through rmax are used + + for (int r = rmin; r <= rmax; ++r) + { + W[r] = (Real)0; + for (int c = cmin, k = 0; c <= cmax; ++c, ++k) + { + W[r] += V[k] * A[r][c]; + } + } + + for (int r = rmin; r <= rmax; ++r) + { + for (int c = cmin, k = 0; c <= cmax; ++c, ++k) + { + A[r][c] += W[r] * MV[k]; + } + } +} + +template +void QuarticRootsQR::GetQuadraticRoots(int i0, int i1, Matrix const& A, + uint32_t& numRoots, std::array& roots) +{ + // Solve x^2 - t * x + d = 0, where t is the trace and d is the + // determinant of the 2x2 matrix defined by indices i0 and i1. The + // discriminant is D = (t/2)^2 - d. When D >= 0, the roots are real + // values t/2 - sqrt(D) and t/2 + sqrt(D). To avoid potential numerical + // issues with subtractive cancellation, the roots are computed as + // r0 = t/2 + sign(t/2)*sqrt(D), r1 = trace - r0. + Real trace = A[i0][i0] + A[i1][i1]; + Real halfTrace = trace * (Real)0.5; + Real determinant = A[i0][i0] * A[i1][i1] - A[i0][i1] * A[i1][i0]; + Real discriminant = halfTrace * halfTrace - determinant; + if (discriminant >= (Real)0) + { + Real sign = (trace >= (Real)0 ? (Real)1 : (Real)-1); + Real root = halfTrace + sign * std::sqrt(discriminant); + roots[numRoots++] = root; + roots[numRoots++] = trace - root; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuaternion.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuaternion.h new file mode 100644 index 000000000000..9a12033d3438 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuaternion.h @@ -0,0 +1,455 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/12/11) + +#pragma once + +#include +#include +#include + +// A quaternion is of the form +// q = x * i + y * j + z * k + w * 1 = x * i + y * j + z * k + w +// where w, x, y, and z are real numbers. The scalar and vector parts are +// Vector(q) = x * i + y * j + z * k +// Scalar(q) = w +// q = Vector(q) + Scalar(q) +// I assume that you are familiar with the arithmetic and algebraic properties +// of quaternions. See +// https://www.geometrictools.com/Documentation/Quaternions.pdf + +namespace gte +{ + template + class Quaternion + { + public: + // The quaternions are of the form q = x*i + y*j + z*k + w. In tuple + // form, q = (x,y,z,w). + + // Construction. The default constructor does not initialize the + // members. + Quaternion() = default; + + Quaternion(Real x, Real y, Real z, Real w) + { + mTuple[0] = x; + mTuple[1] = y; + mTuple[2] = z; + mTuple[3] = w; + } + + // Member access. + inline Real const& operator[](int i) const + { + return mTuple[i]; + } + + inline Real& operator[](int i) + { + return mTuple[i]; + } + + // Comparisons. + inline bool operator==(Quaternion const& q) const + { + return mTuple == q.mTuple; + } + + inline bool operator!=(Quaternion const& q) const + { + return mTuple != q.mTuple; + } + + inline bool operator<(Quaternion const& q) const + { + return mTuple < q.mTuple; + } + + inline bool operator<=(Quaternion const& q) const + { + return mTuple <= q.mTuple; + } + + inline bool operator>(Quaternion const& q) const + { + return mTuple > q.mTuple; + } + + inline bool operator>=(Quaternion const& q) const + { + return mTuple >= q.mTuple; + } + + // Special quaternions. + + // z = 0*i + 0*j + 0*k + 0 + static Quaternion Zero() + { + return Quaternion((Real)0, (Real)0, (Real)0, (Real)0); + } + + // i = 1*i + 0*j + 0*k + 0 + static Quaternion I() + { + return Quaternion((Real)1, (Real)0, (Real)0, (Real)0); + } + + // j = 0*i + 1*j + 0*k + 0 + static Quaternion J() + { + return Quaternion((Real)0, (Real)1, (Real)0, (Real)0); + } + + // k = 0*i + 0*j + 1*k + 0 + static Quaternion K() + { + return Quaternion((Real)0, (Real)0, (Real)1, (Real)0); + } + + // 1 = 0*i + 0*j + 0*k + 1 + static Quaternion Identity() + { + return Quaternion((Real)0, (Real)0, (Real)0, (Real)1); + } + + protected: + std::array mTuple; + }; + + // Unary operations. + template + Quaternion operator+(Quaternion const& q) + { + return q; + } + + template + Quaternion operator-(Quaternion const& q) + { + Quaternion result; + for (int i = 0; i < 4; ++i) + { + result[i] = -q[i]; + } + return result; + } + + // Linear algebraic operations. + template + Quaternion operator+(Quaternion const& q0, Quaternion const& q1) + { + Quaternion result = q0; + return result += q1; + } + + template + Quaternion operator-(Quaternion const& q0, Quaternion const& q1) + { + Quaternion result = q0; + return result -= q1; + } + + template + Quaternion operator*(Quaternion const& q, Real scalar) + { + Quaternion result = q; + return result *= scalar; + } + + template + Quaternion operator*(Real scalar, Quaternion const& q) + { + Quaternion result = q; + return result *= scalar; + } + + template + Quaternion operator/(Quaternion const& q, Real scalar) + { + Quaternion result = q; + return result /= scalar; + } + + template + Quaternion& operator+=(Quaternion& q0, Quaternion const& q1) + { + for (int i = 0; i < 4; ++i) + { + q0[i] += q1[i]; + } + return q0; + } + + template + Quaternion& operator-=(Quaternion& q0, Quaternion const& q1) + { + for (int i = 0; i < 4; ++i) + { + q0[i] -= q1[i]; + } + return q0; + } + + template + Quaternion& operator*=(Quaternion& q, Real scalar) + { + for (int i = 0; i < 4; ++i) + { + q[i] *= scalar; + } + return q; + } + + template + Quaternion& operator/=(Quaternion& q, Real scalar) + { + if (scalar != (Real)0) + { + for (int i = 0; i < 4; ++i) + { + q[i] /= scalar; + } + } + else + { + for (int i = 0; i < 4; ++i) + { + q[i] = (Real)0; + } + } + return q; + } + + // Geometric operations. + template + Real Dot(Quaternion const& q0, Quaternion const& q1) + { + Real dot = q0[0] * q1[0]; + for (int i = 1; i < 4; ++i) + { + dot += q0[i] * q1[i]; + } + return dot; + } + + template + Real Length(Quaternion const& q) + { + return std::sqrt(Dot(q, q)); + } + + template + Real Normalize(Quaternion& q) + { + Real length = std::sqrt(Dot(q, q)); + if (length > (Real)0) + { + q /= length; + } + else + { + for (int i = 0; i < 4; ++i) + { + q[i] = (Real)0; + } + } + return length; + } + + // Multiplication of quaternions. This operation is not generally + // commutative; that is, q0*q1 and q1*q0 are not usually the same value. + // (x0*i + y0*j + z0*k + w0)*(x1*i + y1*j + z1*k + w1) + // = + // i*(+x0*w1 + y0*z1 - z0*y1 + w0*x1) + + // j*(-x0*z1 + y0*w1 + z0*x1 + w0*y1) + + // k*(+x0*y1 - y0*x1 + z0*w1 + w0*z1) + + // 1*(-x0*x1 - y0*y1 - z0*z1 + w0*w1) + template + Quaternion operator*(Quaternion const& q0, Quaternion const& q1) + { + // (x0*i + y0*j + z0*k + w0)*(x1*i + y1*j + z1*k + w1) + // = + // i*(+x0*w1 + y0*z1 - z0*y1 + w0*x1) + + // j*(-x0*z1 + y0*w1 + z0*x1 + w0*y1) + + // k*(+x0*y1 - y0*x1 + z0*w1 + w0*z1) + + // 1*(-x0*x1 - y0*y1 - z0*z1 + w0*w1) + + return Quaternion + ( + +q0[0] * q1[3] + q0[1] * q1[2] - q0[2] * q1[1] + q0[3] * q1[0], + -q0[0] * q1[2] + q0[1] * q1[3] + q0[2] * q1[0] + q0[3] * q1[1], + +q0[0] * q1[1] - q0[1] * q1[0] + q0[2] * q1[3] + q0[3] * q1[2], + -q0[0] * q1[0] - q0[1] * q1[1] - q0[2] * q1[2] + q0[3] * q1[3] + ); + } + + // For a nonzero quaternion q = (x,y,z,w), inv(q) = (-x,-y,-z,w)/|q|^2, where + // |q| is the length of the quaternion. When q is zero, the function returns + // zero, which is considered to be an improbable case. + template + Quaternion Inverse(Quaternion const& q) + { + Real sqrLen = Dot(q, q); + if (sqrLen > (Real)0) + { + Quaternion inverse = Conjugate(q) / sqrLen; + return inverse; + } + else + { + return Quaternion::Zero(); + } + } + + // The conjugate of q = (x,y,z,w) is conj(q) = (-x,-y,-z,w). + template + Quaternion Conjugate(Quaternion const& q) + { + return Quaternion(-q[0], -q[1], -q[2], +q[3]); + } + + // Rotate a vector using quaternion multiplication. The input quaternion must + // be unit length. + template + Vector<4, Real> Rotate(Quaternion const& q, Vector<4, Real> const& v) + { + Quaternion input(v[0], v[1], v[2], (Real)0); + Quaternion output = q * input * Conjugate(q); + Vector<4, Real> u{ output[0], output[1], output[2], (Real)0 }; + return u; + } + + // The spherical linear interpolation (slerp) of unit-length quaternions + // q0 and q1 for t in [0,1] is + // slerp(t,q0,q1) = [sin(t*theta)*q0 + sin((1-t)*theta)*q1]/sin(theta) + // where theta is the angle between q0 and q1 [cos(theta) = Dot(q0,q1)]. + // This function is a parameterization of the great spherical arc between + // q0 and q1 on the unit hypersphere. Moreover, the parameterization is + // one of normalized arclength--a particle traveling along the arc through + // time t does so with constant speed. + // + // When using slerp in animations involving sequences of quaternions, it is + // typical that the quaternions are preprocessed so that consecutive ones + // form an acute angle A in [0,pi/2]. Other preprocessing can help with + // performance. See the function comments below. + // + // See GteSlerpEstimate.{h,inl} for various approximations, including + // SLERP::EstimateRPH that gives good performance and accurate results + // for preprocessed quaternions. + + // The angle between q0 and q1 is in [0,pi). There are no angle restrictions + // restrictions and nothing is precomputed. + template + Quaternion Slerp(Real t, Quaternion const& q0, Quaternion const& q1) + { + Real cosA = Dot(q0, q1); + Real sign; + if (cosA >= (Real)0) + { + sign = (Real)1; + } + else + { + cosA = -cosA; + sign = (Real)-1; + } + + Real f0, f1; + ChebyshevRatio::Get(t, cosA, f0, f1); + return q0 * f0 + q1 * (sign * f1); + } + + // The angle between q0 and q1 must be in [0,pi/2]. The suffix R is for + // 'Restricted'. The preprocessing code is + // Quaternion q[n]; // assuming initialized + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cosA = Dot(q[i0], q[i1]); + // if (cosA < 0) + // { + // q[i1] = -q[i1]; // now Dot(q[i0], q[i]1) >= 0 + // } + // } + template + Quaternion SlerpR(Real t, Quaternion const& q0, Quaternion const& q1) + { + Real f0, f1; + ChebyshevRatio::Get(t, Dot(q0, q1), f0, f1); + return q0 * f0 + q1 * f1; + } + + // The angle between q0 and q1 must be in [0,pi/2]. The suffix R is for + // 'Restricted' and the suffix P is for 'Preprocessed'. The preprocessing + // code is + // Quaternion q[n]; // assuming initialized + // Real cosA[n-1], omcosA[n-1]; // to be precomputed + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cs = Dot(q[i0], q[i1]); + // if (cosA[i0] < 0) + // { + // q[i1] = -q[i1]; + // cs = -cs; + // } + // + // // for Quaterion::SlerpRP + // cosA[i0] = cs; + // + // // for SLERP::EstimateRP + // omcosA[i0] = 1 - cs; + // } + template + Quaternion SlerpRP(Real t, Quaternion const& q0, Quaternion const& q1, Real cosA) + { + Real f0, f1; + ChebyshevRatio::Get(t, cosA, f0, f1); + return q0 * f0 + q1 * f1; + } + + // The angle between q0 and q1 is A and must be in [0,pi/2]. The suffic R + // is for 'Restricted', the suffix P is for 'Preprocessed' and the suffix + // H is for 'Half' (the quaternion qh halfway between q0 and q1 is + // precomputed). Quaternion qh is slerp(1/2,q0,q1) = (q0+q1)/|q0+q1|, so + // the angle between q0 and qh is A/2 and the angle between qh and q1 is + // A/2. The preprocessing code is + // Quaternion q[n]; // assuming initialized + // Quaternion qh[n-1]; // to be precomputed + // Real omcosAH[n-1]; // to be precomputed + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cosA = Dot(q[i0], q[i1]); + // if (cosA < 0) + // { + // q[i1] = -q[i1]; + // cosA = -cosA; + // } + // + // // for Quaternion::SlerpRPH and SLERP::EstimateRPH + // cosAH[i0] = sqrt((1+cosA)/2); + // qh[i0] = (q0 + q1) / (2 * cosAH[i0]); + // + // // for SLERP::EstimateRPH + // omcosAH[i0] = 1 - cosAH[i0]; + // } + template + Quaternion SlerpRPH(Real t, Quaternion const& q0, Quaternion const& q1, + Quaternion const& qh, Real cosAH) + { + Real f0, f1; + Real twoT = static_cast(2) * t; + if (twoT <= static_cast(1)) + { + ChebyshevRatio::Get(twoT, cosAH, f0, f1); + return q0 * f0 + qh * f1; + } + else + { + ChebyshevRatio::Get(twoT - static_cast(1), cosAH, f0, f1); + return qh * f0 + q1 * f1; + } + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRay.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRay.h new file mode 100644 index 000000000000..446b03d6006b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRay.h @@ -0,0 +1,112 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The ray is represented as P+t*D, where P is the ray origin, D is a +// unit-length direction vector, and t >= 0. The user must ensure that D is +// unit length. + +namespace gte +{ + +template +class Ray +{ +public: + // Construction and destruction. The default constructor sets the origin + // to (0,...,0) and the ray direction to (1,0,...,0). + Ray(); + Ray(Vector const& inOrigin, Vector const& inDirection); + + // Public member access. The direction must be unit length. + Vector origin, direction; + +public: + // Comparisons to support sorted containers. + bool operator==(Ray const& ray) const; + bool operator!=(Ray const& ray) const; + bool operator< (Ray const& ray) const; + bool operator<=(Ray const& ray) const; + bool operator> (Ray const& ray) const; + bool operator>=(Ray const& ray) const; +}; + +// Template aliases for convenience. +template +using Ray2 = Ray<2, Real>; + +template +using Ray3 = Ray<3, Real>; + + +template +Ray::Ray() +{ + origin.MakeZero(); + direction.MakeUnit(0); +} + +template +Ray::Ray(Vector const& inOrigin, + Vector const& inDirection) + : + origin(inOrigin), + direction(inDirection) +{ +} + +template +bool Ray::operator==(Ray const& ray) const +{ + return origin == ray.origin && direction == ray.direction; +} + +template +bool Ray::operator!=(Ray const& ray) const +{ + return !operator==(ray); +} + +template +bool Ray::operator<(Ray const& ray) const +{ + if (origin < ray.origin) + { + return true; + } + + if (origin > ray.origin) + { + return false; + } + + return direction < ray.direction; +} + +template +bool Ray::operator<=(Ray const& ray) const +{ + return operator<(ray) || operator==(ray); +} + +template +bool Ray::operator>(Ray const& ray) const +{ + return !operator<=(ray); +} + +template +bool Ray::operator>=(Ray const& ray) const +{ + return !operator<(ray); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectangle.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectangle.h new file mode 100644 index 000000000000..bb993bc49b2f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectangle.h @@ -0,0 +1,172 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Points are R(s0,s1) = C + s0*A0 + s1*A1, where C is the center of the +// rectangle and A0 and A1 are unit-length and perpendicular axes. The +// parameters s0 and s1 are constrained by |s0| <= e0 and |s1| <= e1, +// where e0 > 0 and e1 > 0 are the extents of the rectangle. + +namespace gte +{ + +template +class Rectangle +{ +public: + // Construction and destruction. The default constructor sets the origin + // to (0,...,0), axis A0 to (1,0,...,0), axis A1 to (0,1,0,...0), and both + // extents to 1. + Rectangle(); + Rectangle(Vector const& inCenter, + std::array, 2> const& inAxis, + Vector<2, Real> const& inExtent); + + // Compute the vertices of the rectangle. If index i has the bit pattern + // i = b[1]b[0], then + // vertex[i] = center + sum_{d=0}^{1} sign[d] * extent[d] * axis[d] + // where sign[d] = 2*b[d] - 1. + void GetVertices(std::array, 4>& vertex) const; + + Vector center; + std::array, 2> axis; + Vector<2, Real> extent; + +public: + // Comparisons to support sorted containers. + bool operator==(Rectangle const& rectangle) const; + bool operator!=(Rectangle const& rectangle) const; + bool operator< (Rectangle const& rectangle) const; + bool operator<=(Rectangle const& rectangle) const; + bool operator> (Rectangle const& rectangle) const; + bool operator>=(Rectangle const& rectangle) const; +}; + +// Template alias for convenience. +template +using Rectangle3 = Rectangle<3, Real>; + + +template +Rectangle::Rectangle() +{ + center.MakeZero(); + for (int i = 0; i < 2; ++i) + { + axis[i].MakeUnit(i); + extent[i] = (Real)1; + } +} + +template +Rectangle::Rectangle(Vector const& inCenter, + std::array, 2> const& inAxis, + Vector<2, Real> const& inExtent) + : + center(inCenter), + axis(inAxis), + extent(inExtent) +{ +} + +template +void Rectangle::GetVertices( + std::array, 4>& vertex) const +{ + Vector product0 = extent[0] * axis[0]; + Vector product1 = extent[1] * axis[1]; + Vector sum = product0 + product1; + Vector dif = product0 - product1; + + vertex[0] = center - sum; + vertex[1] = center + dif; + vertex[2] = center - dif; + vertex[3] = center + sum; +} + +template +bool Rectangle::operator==(Rectangle const& rectangle) const +{ + if (center != rectangle.center) + { + return false; + } + + for (int i = 0; i < 2; ++i) + { + if (axis[i] != rectangle.axis[i]) + { + return false; + } + } + + for (int i = 0; i < 2; ++i) + { + if (extent[i] != rectangle.extent[i]) + { + return false; + } + } + + return true; +} + +template +bool Rectangle::operator!=(Rectangle const& rectangle) const +{ + return !operator==(rectangle); +} + +template +bool Rectangle::operator<(Rectangle const& rectangle) const +{ + if (center < rectangle.center) + { + return true; + } + + if (center > rectangle.center) + { + return false; + } + + if (axis < rectangle.axis) + { + return true; + } + + if (axis > rectangle.axis) + { + return false; + } + + return extent < rectangle.extent; +} + +template +bool Rectangle::operator<=(Rectangle const& rectangle) const +{ + return operator<(rectangle) || operator==(rectangle); +} + +template +bool Rectangle::operator>(Rectangle const& rectangle) const +{ + return !operator<=(rectangle); +} + +template +bool Rectangle::operator>=(Rectangle const& rectangle) const +{ + return !operator<(rectangle); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectangleMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectangleMesh.h new file mode 100644 index 000000000000..95a1ec63d4fa --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectangleMesh.h @@ -0,0 +1,170 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.4 (2016/08/29) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class RectangleMesh : public Mesh +{ +public: + // Create a mesh that tessellates a rectangle. + RectangleMesh(MeshDescription const& description, Rectangle<3, Real> const& rectangle); + + // Member access. + inline Rectangle<3, Real> const& GetRectangle() const; + +protected: + void InitializeTCoords(); + void InitializePositions(); + void InitializeNormals(); + void InitializeFrame(); + + Rectangle<3, Real> mRectangle; + + // If the client does not request texture coordinates, they will be + // computed internally for use in evaluation of the surface geometry. + std::vector> mDefaultTCoords; +}; + + +template +RectangleMesh::RectangleMesh(MeshDescription const& description, + Rectangle<3, Real> const& rectangle) + : + Mesh(description, { MeshTopology::RECTANGLE }), + mRectangle(rectangle) +{ + if (!this->mDescription.constructed) + { + // The logger system will report these errors in the Mesh constructor. + return; + } + + if (!this->mTCoords) + { + mDefaultTCoords.resize(this->mDescription.numVertices); + this->mTCoords = mDefaultTCoords.data(); + this->mTCoordStride = sizeof(Vector2); + + this->mDescription.allowUpdateFrame = this->mDescription.wantDynamicTangentSpaceUpdate; + if (this->mDescription.allowUpdateFrame) + { + if (!this->mDescription.hasTangentSpaceVectors) + { + this->mDescription.allowUpdateFrame = false; + } + + if (!this->mNormals) + { + this->mDescription.allowUpdateFrame = false; + } + } + } + + this->ComputeIndices(); + InitializeTCoords(); + InitializePositions(); + if (this->mDescription.allowUpdateFrame) + { + InitializeFrame(); + } + else if (this->mNormals) + { + InitializeNormals(); + } +} + +template +inline Rectangle<3, Real> const& RectangleMesh::GetRectangle() const +{ + return mRectangle; +} + +template +void RectangleMesh::InitializeTCoords() +{ + Vector2 tcoord; + for (uint32_t r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + tcoord[1] = (Real)r / (Real)(this->mDescription.numRows - 1); + for (uint32_t c = 0; c < this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = (Real)c / (Real)(this->mDescription.numCols - 1); + this->TCoord(i) = tcoord; + } + } +} + +template +void RectangleMesh::InitializePositions() +{ + for (uint32_t r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + for (uint32_t c = 0; c < this->mDescription.numCols; ++c, ++i) + { + Vector2 tcoord = this->TCoord(i); + Real w0 = ((Real)2 * tcoord[0] - (Real)1) * mRectangle.extent[0]; + Real w1 = ((Real)2 * tcoord[1] - (Real)1) * mRectangle.extent[1]; + this->Position(i) = mRectangle.center + w0 * mRectangle.axis[0] + w1 * mRectangle.axis[1]; + } + } +} + +template +void RectangleMesh::InitializeNormals() +{ + Vector3 normal = UnitCross(mRectangle.axis[0], mRectangle.axis[1]); + for (uint32_t i = 0; i < this->mDescription.numVertices; ++i) + { + this->Normal(i) = normal; + } +} + +template +void RectangleMesh::InitializeFrame() +{ + Vector3 normal = UnitCross(mRectangle.axis[0], mRectangle.axis[1]); + Vector3 tangent{ (Real)1, (Real)0, (Real)0 }; + Vector3 bitangent{ (Real)0, (Real)1, (Real)0 }; // = Cross(normal,tangent) + for (uint32_t i = 0; i < this->mDescription.numVertices; ++i) + { + if (this->mNormals) + { + this->Normal(i) = normal; + } + + if (this->mTangents) + { + this->Tangent(i) = tangent; + } + + if (this->mBitangents) + { + this->Bitangent(i) = bitangent; + } + + if (this->mDPDUs) + { + this->DPDU(i) = tangent; + } + + if (this->mDPDVs) + { + this->DPDV(i) = bitangent; + } + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectanglePatchMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectanglePatchMesh.h new file mode 100644 index 000000000000..925b821f581a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectanglePatchMesh.h @@ -0,0 +1,226 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.4 (2016/08/29) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class RectanglePatchMesh : public Mesh +{ +public: + // Create a mesh (x(u,v),y(u,v),z(u,v)) defined by the specified surface. + // It is required that surface->IsRectangular() return 'true'. + RectanglePatchMesh(MeshDescription const& description, + std::shared_ptr> const& surface); + + // Member access. + inline std::shared_ptr> const& GetSurface() const; + +protected: + void InitializeTCoords(); + void InitializePositions(); + void InitializeNormals(); + void InitializeFrame(); + virtual void UpdatePositions() override; + virtual void UpdateNormals() override; + virtual void UpdateFrame() override; + + std::shared_ptr> mSurface; + + // If the client does not request texture coordinates, they will be + // computed internally for use in evaluation of the surface geometry. + std::vector> mDefaultTCoords; +}; + + +template +RectanglePatchMesh::RectanglePatchMesh(MeshDescription const& description, + std::shared_ptr> const& surface) + : + Mesh(description, { MeshTopology::RECTANGLE }), + mSurface(surface) +{ + if (!this->mDescription.constructed) + { + // The logger system will report these errors in the Mesh constructor. + mSurface = nullptr; + return; + } + + if (!mSurface || !mSurface->IsRectangular()) + { + LogError("A nonnull rectangular surface is required."); + mSurface = nullptr; + this->mDescription.constructed = false; + return; + } + + if (!this->mTCoords) + { + mDefaultTCoords.resize(this->mDescription.numVertices); + this->mTCoords = mDefaultTCoords.data(); + this->mTCoordStride = sizeof(Vector2); + + this->mDescription.allowUpdateFrame = this->mDescription.wantDynamicTangentSpaceUpdate; + if (this->mDescription.allowUpdateFrame) + { + if (!this->mDescription.hasTangentSpaceVectors) + { + this->mDescription.allowUpdateFrame = false; + } + + if (!this->mNormals) + { + this->mDescription.allowUpdateFrame = false; + } + } + } + + this->ComputeIndices(); + InitializeTCoords(); + InitializePositions(); + if (this->mDescription.allowUpdateFrame) + { + InitializeFrame(); + } + else if (this->mNormals) + { + InitializeNormals(); + } +} + +template +inline std::shared_ptr> const& +RectanglePatchMesh::GetSurface() const +{ + return mSurface; +} + +template +void RectanglePatchMesh::InitializeTCoords() +{ + Real uMin = mSurface->GetUMin(); + Real uDelta = (mSurface->GetUMax() - uMin) / static_cast(this->mDescription.numCols - 1); + Real vMin = mSurface->GetVMin(); + Real vDelta = (mSurface->GetVMax() - vMin) / static_cast(this->mDescription.numRows - 1); + Vector2 tcoord; + for (uint32_t r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + tcoord[1] = vMin + vDelta * (Real)r; + for (uint32_t c = 0; c < this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = uMin + uDelta * (Real)c; + this->TCoord(i) = tcoord; + } + } +} + +template +void RectanglePatchMesh::InitializePositions() +{ + for (uint32_t r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + for (uint32_t c = 0; c < this->mDescription.numCols; ++c, ++i) + { + Vector2 tcoord = this->TCoord(i); + this->Position(i) = mSurface->GetPosition(tcoord[0], tcoord[1]); + } + } +} + +template +void RectanglePatchMesh::InitializeNormals() +{ + for (uint32_t r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + for (uint32_t c = 0; c < this->mDescription.numCols; ++c, ++i) + { + Vector2 tcoord = this->TCoord(i); + Vector3 values[6]; + mSurface->Evaluate(tcoord[0], tcoord[1], 1, values); + Normalize(values[1], true); + Normalize(values[2], true); + this->Normal(i) = UnitCross(values[1], values[2], true); + } + } +} + +template +void RectanglePatchMesh::InitializeFrame() +{ + Vector3 normal, tangent, bitangent; + for (unsigned int r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + for (unsigned int c = 0; c < this->mDescription.numCols; ++c, ++i) + { + Vector2 tcoord = this->TCoord(i); + Vector3 values[6]; + mSurface->Evaluate(tcoord[0], tcoord[1], 1, values); + Normalize(values[1], true); + Normalize(values[2], true); + + if (this->mDPDUs) + { + this->DPDU(i) = values[1]; + } + if (this->mDPDVs) + { + this->DPDV(i) = values[2]; + } + + ComputeOrthogonalComplement(2, &values[1], true); + + if (this->mNormals) + { + this->Normal(i) = values[3]; + } + if (this->mTangents) + { + this->Tangent(i) = values[1]; + } + if (this->mBitangents) + { + this->Bitangent(i) = values[2]; + } + } + } +} + +template +void RectanglePatchMesh::UpdatePositions() +{ + if (mSurface) + { + InitializePositions(); + } +} + +template +void RectanglePatchMesh::UpdateNormals() +{ + if (mSurface) + { + InitializeNormals(); + } +} + +template +void RectanglePatchMesh::UpdateFrame() +{ + if (mSurface) + { + InitializeFrame(); + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRevolutionMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRevolutionMesh.h new file mode 100644 index 000000000000..a53b2847ca4e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRevolutionMesh.h @@ -0,0 +1,347 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.4 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class RevolutionMesh : public Mesh +{ +public: + // The axis of revolution is the z-axis. The curve of revolution is + // p(t) = (x(t),z(t)), where t in [tmin,tmax], x(t) > 0 for t in + // (tmin,tmax), x(tmin) >= 0, and x(tmax) >= 0. The values tmin and tmax + // are those for the curve object passed to the constructor. The curve + // must be non-self-intersecting, except possibly at its endpoints. The + // curve is closed when p(tmin) = p(tmax), in which case the surface of + // revolution has torus topology. The curve is open when p(tmin) != + // p(tmax). For an open curve, define x0 = x(tmin) and x1 = x(tmax). The + // surface has cylinder topology when x0 > 0 and x1 > 0, disk topology + // when exactly one of x0 or x1 is zero, or sphere topology when x0 and + // x1 are both zero. However, to simplify the design, the mesh is always + // built using cylinder topology. The row samples correspond to curve + // points and the column samples correspond to the points on the circles + // of revolution. + RevolutionMesh(MeshDescription const& description, + std::shared_ptr> const& curve, + bool sampleByArcLength = false); + + // Member access. + inline std::shared_ptr> const& GetCurve() const; + inline bool IsSampleByArcLength() const; + +private: + void CreateSampler(); + void InitializeTCoords(); + virtual void UpdatePositions() override; + void UpdateCylinderPositions(); + void UpdateTorusPositions(); + void UpdateDiskPositions(); + void UpdateSpherePositions(); + + std::shared_ptr> mCurve; + bool mSampleByArcLength; + std::vector mCosAngle, mSinAngle; + std::function mTSampler; + std::vector> mSamples; + + // If the client does not request texture coordinates, they will be + // computed internally for use in evaluation of the surface geometry. + std::vector> mDefaultTCoords; +}; + + +template +RevolutionMesh::RevolutionMesh(MeshDescription const& description, + std::shared_ptr> const& curve, bool sampleByArcLength) + : + Mesh(description, + { MeshTopology::CYLINDER, MeshTopology::TORUS, MeshTopology::DISK, MeshTopology::SPHERE }), + mCurve(curve), + mSampleByArcLength(sampleByArcLength) +{ + if (!this->mDescription.constructed) + { + // The logger system will report these errors in the Mesh constructor. + mCurve = nullptr; + return; + } + + if (!mCurve) + { + LogWarning("A nonnull revolution curve is required."); + this->mDescription.constructed = false; + return; + } + + // The four supported topologies all wrap around in the column direction. + mCosAngle.resize(this->mDescription.numCols + 1); + mSinAngle.resize(this->mDescription.numCols + 1); + Real invRadialSamples = (Real)1 / (Real)this->mDescription.numCols; + for (unsigned int c = 0; c < this->mDescription.numCols; ++c) + { + Real angle = c * invRadialSamples * (Real)GTE_C_TWO_PI; + mCosAngle[c] = std::cos(angle); + mSinAngle[c] = std::sin(angle); + } + mCosAngle[this->mDescription.numCols] = mCosAngle[0]; + mSinAngle[this->mDescription.numCols] = mSinAngle[0]; + + CreateSampler(); + + if (!this->mTCoords) + { + mDefaultTCoords.resize(this->mDescription.numVertices); + this->mTCoords = mDefaultTCoords.data(); + this->mTCoordStride = sizeof(Vector2); + + this->mDescription.allowUpdateFrame = this->mDescription.wantDynamicTangentSpaceUpdate; + if (this->mDescription.allowUpdateFrame) + { + if (!this->mDescription.hasTangentSpaceVectors) + { + this->mDescription.allowUpdateFrame = false; + } + + if (!this->mNormals) + { + this->mDescription.allowUpdateFrame = false; + } + } + } + + this->ComputeIndices(); + InitializeTCoords(); + UpdatePositions(); + if (this->mDescription.allowUpdateFrame) + { + this->UpdateFrame(); + } + else if (this->mNormals) + { + this->UpdateNormals(); + } +} + +template +inline std::shared_ptr> const& RevolutionMesh::GetCurve() const +{ + return mCurve; +} + +template +inline bool RevolutionMesh::IsSampleByArcLength() const +{ + return mSampleByArcLength; +} + +template +void RevolutionMesh::CreateSampler() +{ + if (this->mDescription.topology == MeshTopology::CYLINDER + || this->mDescription.topology == MeshTopology::TORUS) + { + mSamples.resize(this->mDescription.rMax + 1); + } + else if (this->mDescription.topology == MeshTopology::DISK) + { + mSamples.resize(this->mDescription.rMax + 2); + } + else if (this->mDescription.topology == MeshTopology::SPHERE) + { + mSamples.resize(this->mDescription.rMax + 3); + } + + Real invDenom = ((Real)1) / (Real)(mSamples.size() - 1); + if (mSampleByArcLength) + { + Real factor = mCurve->GetTotalLength() * invDenom; + mTSampler = [this, factor](unsigned int i) + { + return mCurve->GetTime(i * factor); + }; + } + else + { + Real factor = (mCurve->GetTMax() - mCurve->GetTMin()) * invDenom; + mTSampler = [this, factor](unsigned int i) + { + return mCurve->GetTMin() + i * factor; + }; + } +} + +template +void RevolutionMesh::InitializeTCoords() +{ + Vector2tcoord; + + switch (this->mDescription.topology) + { + case MeshTopology::CYLINDER: + { + for (unsigned int r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + tcoord[1] = (Real)r / (Real)(this->mDescription.numRows - 1); + for (unsigned int c = 0; c <= this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = (Real)c / (Real)this->mDescription.numCols; + this->TCoord(i) = tcoord; + } + } + break; + } + case MeshTopology::TORUS: + { + for (unsigned int r = 0, i = 0; r <= this->mDescription.numRows; ++r) + { + tcoord[1] = (Real)r / (Real)this->mDescription.numRows; + for (unsigned int c = 0; c <= this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = (Real)c / (Real)this->mDescription.numCols; + this->TCoord(i) = tcoord; + } + } + break; + } + case MeshTopology::DISK: + { + Vector2 origin{ (Real)0.5, (Real)0.5 }; + unsigned int i = 0; + for (unsigned int r = 0; r < this->mDescription.numRows; ++r) + { + Real radius = (Real)(r + 1) / (Real)(2 * this->mDescription.numRows); + radius = std::min(radius, (Real)0.5); + for (unsigned int c = 0; c <= this->mDescription.numCols; ++c, ++i) + { + Real angle = (Real)GTE_C_TWO_PI * (Real)c / (Real)this->mDescription.numCols; + this->TCoord(i) = { radius * std::cos(angle), radius * std::sin(angle) }; + } + } + this->TCoord(i) = origin; + break; + } + case MeshTopology::SPHERE: + { + unsigned int i = 0; + for (unsigned int r = 0; r < this->mDescription.numRows; ++r) + { + tcoord[1] = (Real)r / (Real)(this->mDescription.numRows - 1); + for (unsigned int c = 0; c <= this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = (Real)c / (Real)this->mDescription.numCols; + this->TCoord(i) = tcoord; + } + } + this->TCoord(i++) = { (Real)0.5, (Real)0 }; + this->TCoord(i) = { (Real)0.5, (Real)1 }; + break; + } + default: + // Invalid topology is reported by the Mesh constructor, so there is + // no need to log a message here. + break; + } +} + +template +void RevolutionMesh::UpdatePositions() +{ + unsigned int const numSamples = static_cast(mSamples.size()); + for (unsigned int i = 0; i < numSamples; ++i) + { + Real t = mTSampler(i); + Vector2 position = mCurve->GetPosition(t); + mSamples[i][0] = position[0]; + mSamples[i][1] = (Real)0; + mSamples[i][2] = position[1]; + } + + switch (this->mDescription.topology) + { + case MeshTopology::CYLINDER: + UpdateCylinderPositions(); + break; + case MeshTopology::TORUS: + UpdateTorusPositions(); + break; + case MeshTopology::DISK: + UpdateDiskPositions(); + break; + case MeshTopology::SPHERE: + UpdateSpherePositions(); + break; + default: + break; + } +} + +template +void RevolutionMesh::UpdateCylinderPositions() +{ + for (unsigned int r = 0, i = 0; r <= this->mDescription.rMax; ++r) + { + Real radius = mSamples[r][0]; + for (unsigned int c = 0; c <= this->mDescription.cMax; ++c, ++i) + { + this->Position(i) = { radius * mCosAngle[c], radius * mSinAngle[c], mSamples[r][2] }; + } + } +} + +template +void RevolutionMesh::UpdateTorusPositions() +{ + for (unsigned int r = 0, i = 0; r <= this->mDescription.rMax; ++r) + { + Real radius = mSamples[r][0]; + for (unsigned int c = 0; c <= this->mDescription.cMax; ++c, ++i) + { + this->Position(i) = { radius * mCosAngle[c], radius * mSinAngle[c], mSamples[r][2] }; + } + } +} + +template +void RevolutionMesh::UpdateDiskPositions() +{ + for (unsigned int r = 0, rp1 = 1, i = 0; r <= this->mDescription.rMax; ++r, ++rp1) + { + Real radius = mSamples[rp1][0]; + for (unsigned int c = 0; c <= this->mDescription.cMax; ++c, ++i) + { + this->Position(i) = { radius * mCosAngle[c], radius * mSinAngle[c], mSamples[rp1][2] }; + } + } + + this->Position(this->mDescription.numVertices - 1) = { (Real)0, (Real)0, mSamples.front()[2] }; +} + +template +void RevolutionMesh::UpdateSpherePositions() +{ + for (unsigned int r = 0, rp1 = 1, i = 0; r <= this->mDescription.rMax; ++r, ++rp1) + { + Real radius = mSamples[rp1][0]; + for (unsigned int c = 0; c <= this->mDescription.cMax; ++c, ++i) + { + this->Position(i) = { radius * mCosAngle[c], radius * mSinAngle[c], mSamples[rp1][2] }; + } + } + + this->Position(this->mDescription.numVertices - 2) = { (Real)0, (Real)0, mSamples.front()[2] }; + this->Position(this->mDescription.numVertices - 1) = { (Real)0, (Real)0, mSamples.back()[2] }; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsBisection.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsBisection.h new file mode 100644 index 000000000000..c8114fb9c12a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsBisection.h @@ -0,0 +1,172 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// Compute a root of a function F(t) on an interval [t0, t1]. The caller +// specifies the maximum number of iterations, in case you want limited +// accuracy for the root. However, the function is designed for native types +// (Real = float/double). If you specify a sufficiently large number of +// iterations, the root finder bisects until either F(t) is identically zero +// [a condition dependent on how you structure F(t) for evaluation] or the +// midpoint (t0 + t1)/2 rounds numerically to tmin or tmax. Of course, it +// is required that t0 < t1. The return value of Find is: +// 0: F(t0)*F(t1) > 0, we cannot determine a root +// 1: F(t0) = 0 or F(t1) = 0 +// 2..maxIterations: the number of bisections plus one +// maxIterations+1: the loop executed without a break (no convergence) + +namespace gte +{ + +template +class RootsBisection +{ +public: + // Use this function when F(t0) and F(t1) are not already known. + static unsigned int Find(std::function const& F, Real t0, + Real t1, unsigned int maxIterations, Real& root); + + // If f0 = F(t0) and f1 = F(t1) are already known, pass them to the + // bisector. This is useful when |f0| or |f1| is infinite, and you can + // pass sign(f0) or sign(f1) rather than then infinity because the + // bisector cares only about the signs of f. + static unsigned int Find(std::function const& F, Real t0, + Real t1, Real f0, Real f1, unsigned int maxIterations, Real& root); +}; + + +template +unsigned int RootsBisection::Find(std::function const& F, + Real t0, Real t1, unsigned int maxIterations, Real& root) +{ + if (t0 < t1) + { + // Test the endpoints to see whether F(t) is zero. + Real f0 = F(t0); + if (f0 == (Real)0) + { + root = t0; + return 1; + } + + Real f1 = F(t1); + if (f1 == (Real)0) + { + root = t1; + return 1; + } + + if (f0*f1 > (Real)0) + { + // It is not known whether the interval bounds a root. + return 0; + } + + unsigned int i; + for (i = 2; i <= maxIterations; ++i) + { + root = ((Real)0.5) * (t0 + t1); + if (root == t0 || root == t1) + { + // The numbers t0 and t1 are consecutive floating-point + // numbers. + break; + } + + Real fm = F(root); + Real product = fm * f0; + if (product < (Real)0) + { + t1 = root; + f1 = fm; + } + else if (product > (Real)0) + { + t0 = root; + f0 = fm; + } + else + { + break; + } + } + return i; + } + else + { + return 0; + } +} + +template +unsigned int RootsBisection::Find(std::function const& F, + Real t0, Real t1, Real f0, Real f1, unsigned int maxIterations, + Real& root) +{ + if (t0 < t1) + { + // Test the endpoints to see whether F(t) is zero. + if (f0 == (Real)0) + { + root = t0; + return 1; + } + + if (f1 == (Real)0) + { + root = t1; + return 1; + } + + if (f0*f1 > (Real)0) + { + // It is not known whether the interval bounds a root. + return 0; + } + + unsigned int i; + for (i = 2; i <= maxIterations; ++i) + { + root = ((Real)0.5) * (t0 + t1); + if (root == t0 || root == t1) + { + // The numbers t0 and t1 are consecutive floating-point + // numbers. + break; + } + + Real fm = F(root); + Real product = fm * f0; + if (product < (Real)0) + { + t1 = root; + f1 = fm; + } + else if (product >(Real)0) + { + t0 = root; + f0 = fm; + } + else + { + break; + } + } + return i; + } + else + { + return 0; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsBrentsMethod.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsBrentsMethod.h new file mode 100644 index 000000000000..ef81656b4b2b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsBrentsMethod.h @@ -0,0 +1,232 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// This is an implementation of Brent's Method for computing a root of a +// function on an interval [t0,t1] for which F(t0)*F(t1) < 0. The method +// uses inverse quadratic interpolation to generate a root estimate but +// falls back to inverse linear interpolation (secant method) if +// necessary. Moreover, based on previous iterates, the method will fall +// back to bisection when it appears the interpolated estimate is not of +// sufficient quality. +// +// maxIterations: +// The maximum number of iterations used to locate a root. This +// should be positive. +// negFTolerance, posFTolerance: +// The root estimate t is accepted when the function value F(t) +// satisfies negFTolerance <= F(t) <= posFTolerance. The values +// must satisfy: negFTolerance <= 0, posFTolerance >= 0. +// stepTTolerance: +// Brent's Method requires additional tests before an interpolated +// t-value is accepted as the next root estimate. One of these +// tests compares the difference of consecutive iterates and +// requires it to be larger than a user-specified t-tolerance (to +// ensure progress is made). This parameter is that tolerance +// and should be nonnegative. +// convTTolerance: +// The root search is allowed to terminate when the current +// subinterval [tsub0,tsub1] is sufficiently small, say, +// |tsub1 - tsub0| <= tolerance. This parameter is that tolerance +// and should be nonnegative. + +namespace gte +{ + +template +class RootsBrentsMethod +{ +public: + // It is necessary that F(t0)*F(t1) <= 0, in which case the function + // returns 'true' and the 'root' is valid; otherwise, the function returns + // 'false' and 'root' is invalid (do not use it). When F(t0)*F(t1) > 0, + // the interval may very well contain a root but we cannot know that. The + // function also returns 'false' if t0 >= t1. + + static bool Find(std::function const& F, Real t0, Real t1, + unsigned int maxIterations, Real negFTolerance, Real posFTolerance, + Real stepTTolerance, Real convTTolerance, Real& root); +}; + + +template +bool RootsBrentsMethod::Find(std::function const& F, + Real t0, Real t1, unsigned int maxIterations, Real negFTolerance, + Real posFTolerance, Real stepTTolerance, Real convTTolerance, Real& root) +{ + // Parameter validation. + if (t1 <= t0 + || maxIterations == 0 + || negFTolerance > (Real)0 + || posFTolerance < (Real)0 + || stepTTolerance < (Real)0 + || convTTolerance < (Real)0) + { + // The input is invalid. + return false; + } + + Real f0 = F(t0); + if (negFTolerance <= f0 && f0 <= posFTolerance) + { + // This endpoint is an approximate root that satisfies the function + // tolerance. + root = t0; + return true; + } + + Real f1 = F(t1); + if (negFTolerance <= f1 && f1 <= posFTolerance) + { + // This endpoint is an approximate root that satisfies the function + // tolerance. + root = t1; + return true; + } + + if (f0*f1 > (Real)0) + { + // The input interval must bound a root. + return false; + } + + if (std::abs(f0) < std::abs(f1)) + { + // Swap t0 and t1 so that |F(t1)| <= |F(t0)|. The number t1 is + // considered to be the best estimate of the root. + std::swap(t0, t1); + std::swap(f0, f1); + } + + // Initialize values for the root search. + Real t2 = t0, t3 = t0, f2 = f0; + bool prevBisected = true; + + // The root search. + for (unsigned int i = 0; i < maxIterations; ++i) + { + Real fDiff01 = f0 - f1, fDiff02 = f0 - f2, fDiff12 = f1 - f2; + Real invFDiff01 = ((Real)1) / fDiff01; + Real s; + if (fDiff02 != (Real)0 && fDiff12 != (Real)0) + { + // Use inverse quadratic interpolation. + Real infFDiff02 = ((Real)1) / fDiff02; + Real invFDiff12 = ((Real)1) / fDiff12; + s = + t0 * f1 * f2 * invFDiff01 * infFDiff02 - + t1 * f0 * f2 * invFDiff01 * invFDiff12 + + t2 * f0 * f1 * infFDiff02 * invFDiff12; + } + else + { + // Use inverse linear interpolation (secant method). + s = (t1 * f0 - t0 * f1) * invFDiff01; + } + + // Compute values need in the accept-or-reject tests. + Real tDiffSAvr = s - ((Real)0.75) * t0 - ((Real)0.25) * t1; + Real tDiffS1 = s - t1; + Real absTDiffS1 = std::abs(tDiffS1); + Real absTDiff12 = std::abs(t1 - t2); + Real absTDiff23 = std::abs(t2 - t3); + + bool currBisected = false; + if (tDiffSAvr * tDiffS1 > (Real)0) + { + // The value s is not between 0.75*t0 + 0.25*t1 and t1. NOTE: + // The algorithm sometimes has t0 < t1 but sometimes t1 < t0, so + // the between-ness test does not use simple comparisons. + currBisected = true; + } + else if (prevBisected) + { + // The first of Brent's tests to determine whether to accept the + // interpolated s-value. + currBisected = + (absTDiffS1 >= ((Real)0.5) * absTDiff12) || + (absTDiff12 <= stepTTolerance); + } + else + { + // The second of Brent's tests to determine whether to accept the + // interpolated s-value. + currBisected = + (absTDiffS1 >= ((Real)0.5) * absTDiff23) || + (absTDiff23 <= stepTTolerance); + } + + if (currBisected) + { + // One of the additional tests failed, so reject the interpolated + // s-value and use bisection instead. + s = ((Real)0.5) * (t0 + t1); + if (s == t0 || s == t1) + { + // The numbers t0 and t1 are consecutive floating-point + // numbers. + root = s; + return true; + } + prevBisected = true; + } + else + { + prevBisected = false; + } + + // Evaluate the function at the new estimate and test for convergence. + Real fs = F(s); + if (negFTolerance <= fs && fs <= posFTolerance) + { + root = s; + return true; + } + + // Update the subinterval to include the new estimate as an endpoint. + t3 = t2; + t2 = t1; + f2 = f1; + if (f0 * fs < (Real)0) + { + t1 = s; + f1 = fs; + } + else + { + t0 = s; + f0 = fs; + } + + // Allow the algorithm to terminate when the subinterval is + // sufficiently small. + if (std::abs(t1 - t0) <= convTTolerance) + { + root = t1; + return true; + } + + // A loop invariant is that t1 is the root estimate, F(t0)*F(t1) < 0, + // and |F(t1)| <= |F(t0)|. + if (std::abs(f0) < std::abs(f1)) + { + std::swap(t0, t1); + std::swap(f0, f1); + } + } + + // Failed to converge in the specified number of iterations. + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsPolynomial.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsPolynomial.h new file mode 100644 index 000000000000..1fa708543e57 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsPolynomial.h @@ -0,0 +1,1158 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +// The Find functions return the number of roots, if any, and this number +// of elements of the outputs are valid. If the polynomial is identically +// zero, Find returns 1. +// +// Some root-bounding algorithms for real-valued roots are mentioned next for +// the polynomial p(t) = c[0] + c[1]*t + ... + c[d-1]*t^{d-1} + c[d]*t^d. +// +// 1. The roots must be contained by the interval [-M,M] where +// M = 1 + max{|c[0]|, ..., |c[d-1]|}/|c[d]| >= 1 +// is called the Cauchy bound. +// +// 2. You may search for roots in the interval [-1,1]. Define +// q(t) = t^d*p(1/t) = c[0]*t^d + c[1]*t^{d-1} + ... + c[d-1]*t + c[d] +// The roots of p(t) not in [-1,1] are the roots of q(t) in [-1,1]. +// +// 3. Between two consecutive roots of the derivative p'(t), say, r0 < r1, +// the function p(t) is strictly monotonic on the open interval (r0,r1). +// If additionally, p(r0) * p(r1) <= 0, then p(x) has a unique root on +// the closed interval [r0,r1]. Thus, one can compute the derivatives +// through order d for p(t), find roots for the derivative of order k+1, +// then use these to bound roots for the derivative of order k. +// +// 4. Sturm sequences of polynomials may be used to determine bounds on the +// roots. This is a more sophisticated approach to root bounding than item 3. +// Moreover, a Sturm sequence allows you to compute the number of real-valued +// roots on a specified interval. +// +// 5. For the low-degree Solve* functions, see +// http://www.geometrictools.com/Documentation/LowDegreePolynomialRoots.pdf + +namespace gte +{ + +template +class RootsPolynomial +{ +public: + // Low-degree root finders. These use exact rational arithmetic for + // theoretically correct root classification. The roots themselves are + // computed with mixed types (rational and floating-point arithmetic). + // The Rational type must support rational arithmetic (+, -, *, /); for + // example, BSRational suffices. The Rational class must + // have single-input constructors where the input is type Real. This + // ensures you can call the Solve* functions with floating-point inputs; + // they will be converted to Rational implicitly. The highest-order + // coefficients must be nonzero (p2 != 0 for quadratic, p3 != 0 for + // cubic, and p4 != 0 for quartic). + + template + static void SolveQuadratic(Rational const& p0, Rational const& p1, + Rational const& p2, std::map& rmMap); + + template + static void SolveCubic(Rational const& p0, Rational const& p1, + Rational const& p2, Rational const& p3, std::map& rmMap); + + template + static void SolveQuartic(Rational const& p0, Rational const& p1, + Rational const& p2, Rational const& p3, Rational const& p4, + std::map& rmMap); + + // Return only the number of real-valued roots and their multiplicities. + // info.size() is the number of real-valued roots and info[i] is the + // multiplicity of root corresponding to index i. + template + static void GetRootInfoQuadratic(Rational const& p0, Rational const& p1, + Rational const& p2, std::vector& info); + + template + static void GetRootInfoCubic(Rational const& p0, Rational const& p1, + Rational const& p2, Rational const& p3, std::vector& info); + + template + static void GetRootInfoQuartic(Rational const& p0, Rational const& p1, + Rational const& p2, Rational const& p3, Rational const& p4, + std::vector& info); + + + // General equations: sum_{i=0}^{d} c(i)*t^i = 0. The input array 'c' + // must have at least d+1 elements and the output array 'root' must have + // at least d elements. + + // Find the roots on (-infinity,+infinity). + static int Find(int degree, Real const* c, unsigned int maxIterations, + Real* roots); + + // If you know that p(tmin) * p(tmax) <= 0, then there must be at least + // one root in [tmin, tmax]. Compute it using bisection. + static bool Find(int degree, Real const* c, Real tmin, Real tmax, + unsigned int maxIterations, Real& root); + +private: + // Support for the Solve* functions. + template + static void SolveDepressedQuadratic(Rational const& c0, + std::map& rmMap); + + template + static void SolveDepressedCubic(Rational const& c0, Rational const& c1, + std::map& rmMap); + + template + static void SolveDepressedQuartic(Rational const& c0, Rational const& c1, + Rational const& c2, std::map& rmMap); + + template + static void SolveBiquadratic(Rational const& c0, Rational const& c2, + std::map& rmMap); + + // Support for the GetNumRoots* functions. + template + static void GetRootInfoDepressedQuadratic(Rational const& c0, + std::vector& info); + + template + static void GetRootInfoDepressedCubic(Rational const& c0, + Rational const& c1, std::vector& info); + + template + static void GetRootInfoDepressedQuartic(Rational const& c0, + Rational const& c1, Rational const& c2, std::vector& info); + + template + static void GetRootInfoBiquadratic(Rational const& c0, + Rational const& c2, std::vector& info); + + // Support for the Find functions. + static int FindRecursive(int degree, Real const* c, Real tmin, Real tmax, + unsigned int maxIterations, Real* roots); + + static Real Evaluate(int degree, Real const* c, Real t); +}; + +// FOR INTERNAL USE ONLY. Do not define GTE_ROOTS_LOW_DEGREE_UNIT_TEST in +// your own code. +#if defined(GTE_ROOTS_LOW_DEGREE_UNIT_TEST) +extern void RootsLowDegreeBlock(int); +#define GTE_ROOTS_LOW_DEGREE_BLOCK(block) RootsLowDegreeBlock(block) +#else +#define GTE_ROOTS_LOW_DEGREE_BLOCK(block) +#endif + + +template +template +void RootsPolynomial::SolveQuadratic(Rational const& p0, + Rational const& p1, Rational const& p2, std::map& rmMap) +{ + Rational const rat2 = 2; + Rational q0 = p0 / p2; + Rational q1 = p1 / p2; + Rational q1half = q1 / rat2; + Rational c0 = q0 - q1half * q1half; + + std::map rmLocalMap; + SolveDepressedQuadratic(c0, rmLocalMap); + + rmMap.clear(); + for (auto& rm : rmLocalMap) + { + Rational root = rm.first - q1half; + rmMap.insert(std::make_pair((Real)root, rm.second)); + } +} + +template +template +void RootsPolynomial::SolveCubic(Rational const& p0, + Rational const& p1, Rational const& p2, Rational const& p3, + std::map& rmMap) +{ + Rational const rat2 = 2, rat3 = 3; + Rational q0 = p0 / p3; + Rational q1 = p1 / p3; + Rational q2 = p2 / p3; + Rational q2third = q2 / rat3; + Rational c0 = q0 - q2third * (q1 - rat2 * q2third * q2third); + Rational c1 = q1 - q2 * q2third; + + std::map rmLocalMap; + SolveDepressedCubic(c0, c1, rmLocalMap); + + rmMap.clear(); + for (auto& rm : rmLocalMap) + { + Rational root = rm.first - q2third; + rmMap.insert(std::make_pair((Real)root, rm.second)); + } +} + +template +template +void RootsPolynomial::SolveQuartic(Rational const& p0, + Rational const& p1, Rational const& p2, Rational const& p3, + Rational const& p4, std::map& rmMap) +{ + Rational const rat2 = 2, rat3 = 3, rat4 = 4, rat6 = 6; + Rational q0 = p0 / p4; + Rational q1 = p1 / p4; + Rational q2 = p2 / p4; + Rational q3 = p3 / p4; + Rational q3fourth = q3 / rat4; + Rational q3fourthSqr = q3fourth * q3fourth; + Rational c0 = q0 - q3fourth * (q1 - q3fourth * (q2 - q3fourthSqr * rat3)); + Rational c1 = q1 - rat2 * q3fourth * (q2 - rat4 * q3fourthSqr); + Rational c2 = q2 - rat6 * q3fourthSqr; + + std::map rmLocalMap; + SolveDepressedQuartic(c0, c1, c2, rmLocalMap); + + rmMap.clear(); + for (auto& rm : rmLocalMap) + { + Rational root = rm.first - q3fourth; + rmMap.insert(std::make_pair((Real)root, rm.second)); + } +} + +template +template +void RootsPolynomial::GetRootInfoQuadratic(Rational const& p0, + Rational const& p1, Rational const& p2, std::vector& info) +{ + Rational const rat2 = 2; + Rational q0 = p0 / p2; + Rational q1 = p1 / p2; + Rational q1half = q1 / rat2; + Rational c0 = q0 - q1half * q1half; + + info.clear(); + info.reserve(2); + GetRootInfoDepressedQuadratic(c0, info); +} + +template +template +void RootsPolynomial::GetRootInfoCubic(Rational const& p0, + Rational const& p1, Rational const& p2, Rational const& p3, + std::vector& info) +{ + Rational const rat2 = 2, rat3 = 3; + Rational q0 = p0 / p3; + Rational q1 = p1 / p3; + Rational q2 = p2 / p3; + Rational q2third = q2 / rat3; + Rational c0 = q0 - q2third * (q1 - rat2 * q2third * q2third); + Rational c1 = q1 - q2 * q2third; + + info.clear(); + info.reserve(3); + GetRootInfoDepressedCubic(c0, c1, info); +} + +template +template +void RootsPolynomial::GetRootInfoQuartic(Rational const& p0, + Rational const& p1, Rational const& p2, Rational const& p3, + Rational const& p4, std::vector& info) +{ + Rational const rat2 = 2, rat3 = 3, rat4 = 4, rat6 = 6; + Rational q0 = p0 / p4; + Rational q1 = p1 / p4; + Rational q2 = p2 / p4; + Rational q3 = p3 / p4; + Rational q3fourth = q3 / rat4; + Rational q3fourthSqr = q3fourth * q3fourth; + Rational c0 = q0 - q3fourth * (q1 - q3fourth * (q2 - q3fourthSqr * rat3)); + Rational c1 = q1 - rat2 * q3fourth * (q2 - rat4 * q3fourthSqr); + Rational c2 = q2 - rat6 * q3fourthSqr; + + info.clear(); + info.reserve(4); + GetRootInfoDepressedQuartic(c0, c1, c2, info); +} + +template +int RootsPolynomial::Find(int degree, Real const* c, + unsigned int maxIterations, Real* roots) +{ + if (degree >= 0 && c) + { + Real const zero = (Real)0; + while (degree >= 0 && c[degree] == zero) + { + --degree; + } + + if (degree > 0) + { + // Compute the Cauchy bound. + Real const one = (Real)1; + Real invLeading = one / c[degree]; + Real maxValue = zero; + for (int i = 0; i < degree; ++i) + { + Real value = std::abs(c[i] * invLeading); + if (value > maxValue) + { + maxValue = value; + } + } + Real bound = one + maxValue; + + return FindRecursive(degree, c, -bound, bound, maxIterations, + roots); + } + else if (degree == 0) + { + // The polynomial is a nonzero constant. + return 0; + } + else + { + // The polynomial is identically zero. + roots[0] = zero; + return 1; + } + } + else + { + // Invalid degree or c. + return 0; + } +} + +template +bool RootsPolynomial::Find(int degree, Real const* c, Real tmin, + Real tmax, unsigned int maxIterations, Real& root) +{ + Real const zero = (Real)0; + Real pmin = Evaluate(degree, c, tmin); + if (pmin == zero) + { + root = tmin; + return true; + } + Real pmax = Evaluate(degree, c, tmax); + if (pmax == zero) + { + root = tmax; + return true; + } + + if (pmin*pmax > zero) + { + // It is not known whether the interval bounds a root. + return false; + } + + if (tmin >= tmax) + { + // Invalid ordering of interval endpoitns. + return false; + } + + for (unsigned int i = 1; i <= maxIterations; ++i) + { + root = ((Real)0.5) * (tmin + tmax); + + // This test is designed for 'float' or 'double' when tmin and tmax + // are consecutive floating-point numbers. + if (root == tmin || root == tmax) + { + break; + } + + Real p = Evaluate(degree, c, root); + Real product = p * pmin; + if (product < zero) + { + tmax = root; + pmax = p; + } + else if (product > zero) + { + tmin = root; + pmin = p; + } + else + { + break; + } + } + + return true; +} + +template +template +void RootsPolynomial::SolveDepressedQuadratic(Rational const& c0, + std::map& rmMap) +{ + Rational const zero = 0; + if (c0 < zero) + { + // Two simple roots. + Rational root1 = (Rational)std::sqrt(-(double)c0); + Rational root0 = -root1; + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(0); + } + else if (c0 == zero) + { + // One double root. + rmMap.insert(std::make_pair(zero, 2)); + GTE_ROOTS_LOW_DEGREE_BLOCK(1); + } + else // c0 > 0 + { + // A complex-conjugate pair of roots. + // Complex z0 = -q1/2 - i*sqrt(c0); + // Complex z0conj = -q1/2 + i*sqrt(c0); + GTE_ROOTS_LOW_DEGREE_BLOCK(2); + } +} + +template +template +void RootsPolynomial::SolveDepressedCubic(Rational const& c0, + Rational const& c1, std::map& rmMap) +{ + // Handle the special case of c0 = 0, in which case the polynomial + // reduces to a depressed quadratic. + Rational const zero = 0; + if (c0 == zero) + { + SolveDepressedQuadratic(c1, rmMap); + auto iter = rmMap.find(zero); + if (iter != rmMap.end()) + { + // The quadratic has a root of zero, so the multiplicity must be + // increased. + ++iter->second; + GTE_ROOTS_LOW_DEGREE_BLOCK(3); + } + else + { + // The quadratic does not have a root of zero. Insert the one + // for the cubic. + rmMap.insert(std::make_pair(zero, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(4); + } + return; + } + + // Handle the special case of c0 != 0 and c1 = 0. + double const oneThird = 1.0 / 3.0; + if (c1 == zero) + { + // One simple real root. + Rational root0; + if (c0 > zero) + { + root0 = (Rational)-std::pow((double)c0, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(5); + } + else + { + root0 = (Rational)std::pow(-(double)c0, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(6); + } + rmMap.insert(std::make_pair(root0, 1)); + + // One complex conjugate pair. + // Complex z0 = root0*(-1 - i*sqrt(3))/2; + // Complex z0conj = root0*(-1 + i*sqrt(3))/2; + return; + } + + // At this time, c0 != 0 and c1 != 0. + Rational const rat2 = 2, rat3 = 3, rat4 = 4, rat27 = 27, rat108 = 108; + Rational delta = -(rat4 * c1 * c1 * c1 + rat27 * c0 * c0); + if (delta > zero) + { + // Three simple roots. + Rational deltaDiv108 = delta / rat108; + Rational betaRe = -c0 / rat2; + Rational betaIm = std::sqrt(deltaDiv108); + Rational theta = std::atan2(betaIm, betaRe); + Rational thetaDiv3 = theta / rat3; + double angle = (double)thetaDiv3; + Rational cs = (Rational)std::cos(angle); + Rational sn = (Rational)std::sin(angle); + Rational rhoSqr = betaRe * betaRe + betaIm * betaIm; + Rational rhoPowThird = (Rational)std::pow((double)rhoSqr, 1.0 / 6.0); + Rational temp0 = rhoPowThird * cs; + Rational temp1 = rhoPowThird * sn * (Rational)std::sqrt(3.0); + Rational root0 = rat2 * temp0; + Rational root1 = -temp0 - temp1; + Rational root2 = -temp0 + temp1; + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + rmMap.insert(std::make_pair(root2, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(7); + } + else if (delta < zero) + { + // One simple root. + Rational deltaDiv108 = delta / rat108; + Rational temp0 = -c0 / rat2; + Rational temp1 = (Rational)std::sqrt(-(double)deltaDiv108); + Rational temp2 = temp0 - temp1; + Rational temp3 = temp0 + temp1; + if (temp2 >= zero) + { + temp2 = (Rational)std::pow((double)temp2, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(8); + } + else + { + temp2 = (Rational)-std::pow(-(double)temp2, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(9); + } + if (temp3 >= zero) + { + temp3 = (Rational)std::pow((double)temp3, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(10); + } + else + { + temp3 = (Rational)-std::pow(-(double)temp3, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(11); + } + Rational root0 = temp2 + temp3; + rmMap.insert(std::make_pair(root0, 1)); + + // One complex conjugate pair. + // Complex z0 = (-root0 - i*sqrt(3*root0*root0+4*c1))/2; + // Complex z0conj = (-root0 + i*sqrt(3*root0*root0+4*c1))/2; + } + else // delta = 0 + { + // One simple root and one double root. + Rational root0 = -rat3 * c0 / (rat2 * c1); + Rational root1 = -rat2 * root0; + rmMap.insert(std::make_pair(root0, 2)); + rmMap.insert(std::make_pair(root1, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(12); + } +} + +template +template +void RootsPolynomial::SolveDepressedQuartic(Rational const& c0, + Rational const& c1, Rational const& c2, std::map& rmMap) +{ + // Handle the special case of c0 = 0, in which case the polynomial + // reduces to a depressed cubic. + Rational const zero = 0; + if (c0 == zero) + { + SolveDepressedCubic(c1, c2, rmMap); + auto iter = rmMap.find(zero); + if (iter != rmMap.end()) + { + // The cubic has a root of zero, so the multiplicity must be + // increased. + ++iter->second; + GTE_ROOTS_LOW_DEGREE_BLOCK(13); + } + else + { + // The cubic does not have a root of zero. Insert the one + // for the quartic. + rmMap.insert(std::make_pair(zero, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(14); + } + return; + } + + // Handle the special case of c1 = 0, in which case the quartic is a + // biquadratic x^4 + c1*x^2 + c0 = (x^2 + c2/2)^2 + (c0 - c2^2/4). + if (c1 == zero) + { + SolveBiquadratic(c0, c2, rmMap); + return; + } + + // At this time, c0 != 0 and c1 != 0, which is a requirement for the + // general solver that must use a root of a special cubic polynomial. + Rational const rat2 = 2, rat4 = 4, rat8 = 8, rat12 = 12, rat16 = 16; + Rational const rat27 = 27, rat36 = 36; + Rational c0sqr = c0 * c0, c1sqr = c1 * c1, c2sqr = c2 * c2; + Rational delta = c1sqr * (-rat27 * c1sqr + rat4 * c2 * + (rat36 * c0 - c2sqr)) + rat16 * c0 * (c2sqr * (c2sqr - rat8 * c0) + + rat16 * c0sqr); + Rational a0 = rat12 * c0 + c2sqr; + Rational a1 = rat4 * c0 - c2sqr; + + if (delta > zero) + { + if (c2 < zero && a1 < zero) + { + // Four simple real roots. + std::map rmCubicMap; + SolveCubic(c1sqr - rat4 * c0 * c2, rat8 * c0, rat4 * c2, -rat8, + rmCubicMap); + Rational t = (Rational)rmCubicMap.rbegin()->first; + Rational alphaSqr = rat2 * t - c2; + Rational alpha = (Rational)std::sqrt((double)alphaSqr); + double sgnC1; + if (c1 > zero) + { + sgnC1 = 1.0; + GTE_ROOTS_LOW_DEGREE_BLOCK(15); + } + else + { + sgnC1 = -1.0; + GTE_ROOTS_LOW_DEGREE_BLOCK(16); + } + Rational arg = t * t - c0; + Rational beta = (Rational)(sgnC1 * std::sqrt(std::max((double)arg, 0.0))); + Rational D0 = alphaSqr - rat4 * (t + beta); + Rational sqrtD0 = (Rational)std::sqrt(std::max((double)D0, 0.0)); + Rational D1 = alphaSqr - rat4 * (t - beta); + Rational sqrtD1 = (Rational)std::sqrt(std::max((double)D1, 0.0)); + Rational root0 = (alpha - sqrtD0) / rat2; + Rational root1 = (alpha + sqrtD0) / rat2; + Rational root2 = (-alpha - sqrtD1) / rat2; + Rational root3 = (-alpha + sqrtD1) / rat2; + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + rmMap.insert(std::make_pair(root2, 1)); + rmMap.insert(std::make_pair(root3, 1)); + } + else // c2 >= 0 or a1 >= 0 + { + // Two complex-conjugate pairs. The values alpha, D0, and D1 are + // those of the if-block. + // Complex z0 = (alpha - i*sqrt(-D0))/2; + // Complex z0conj = (alpha + i*sqrt(-D0))/2; + // Complex z1 = (-alpha - i*sqrt(-D1))/2; + // Complex z1conj = (-alpha + i*sqrt(-D1))/2; + GTE_ROOTS_LOW_DEGREE_BLOCK(17); + } + } + else if (delta < zero) + { + // Two simple real roots, one complex-conjugate pair. + std::map rmCubicMap; + SolveCubic(c1sqr - rat4 * c0 * c2, rat8 * c0, rat4 * c2, -rat8, + rmCubicMap); + Rational t = (Rational)rmCubicMap.rbegin()->first; + Rational alphaSqr = rat2 * t - c2; + Rational alpha = (Rational)std::sqrt(std::max((double)alphaSqr, 0.0)); + double sgnC1; + if (c1 > zero) + { + sgnC1 = 1.0; // Leads to BLOCK(18) + } + else + { + sgnC1 = -1.0; // Leads to BLOCK(19) + } + Rational arg = t * t - c0; + Rational beta = (Rational)(sgnC1 * std::sqrt(std::max((double)arg, 0.0))); + Rational root0, root1; + if (sgnC1 > 0.0) + { + Rational D1 = alphaSqr - rat4 * (t - beta); + Rational sqrtD1 = (Rational)std::sqrt(std::max((double)D1, 0.0)); + root0 = (-alpha - sqrtD1) / rat2; + root1 = (-alpha + sqrtD1) / rat2; + + // One complex conjugate pair. + // Complex z0 = (alpha - i*sqrt(-D0))/2; + // Complex z0conj = (alpha + i*sqrt(-D0))/2; + GTE_ROOTS_LOW_DEGREE_BLOCK(18); + } + else + { + Rational D0 = alphaSqr - rat4 * (t + beta); + Rational sqrtD0 = (Rational)std::sqrt(std::max((double)D0, 0.0)); + root0 = (alpha - sqrtD0) / rat2; + root1 = (alpha + sqrtD0) / rat2; + + // One complex conjugate pair. + // Complex z0 = (-alpha - i*sqrt(-D1))/2; + // Complex z0conj = (-alpha + i*sqrt(-D1))/2; + GTE_ROOTS_LOW_DEGREE_BLOCK(19); + } + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + } + else // delta = 0 + { + if (a1 > zero || (c2 > zero && (a1 != zero || c1 != zero))) + { + // One double real root, one complex-conjugate pair. + Rational const rat9 = 9; + Rational root0 = -c1 * a0 / (rat9 * c1sqr - rat2 * c2 * a1); + rmMap.insert(std::make_pair(root0, 2)); + + // One complex conjugate pair. + // Complex z0 = -root0 - i*sqrt(c2 + root0^2); + // Complex z0conj = -root0 + i*sqrt(c2 + root0^2); + GTE_ROOTS_LOW_DEGREE_BLOCK(20); + } + else + { + Rational const rat3 = 3; + if (a0 != zero) + { + // One double real root, two simple real roots. + Rational const rat9 = 9; + Rational root0 = -c1 * a0 / (rat9 * c1sqr - rat2 * c2 * a1); + Rational alpha = rat2 * root0; + Rational beta = c2 + rat3 * root0 * root0; + Rational discr = alpha * alpha - rat4 * beta; + Rational temp1 = (Rational)std::sqrt((double)discr); + Rational root1 = (-alpha - temp1) / rat2; + Rational root2 = (-alpha + temp1) / rat2; + rmMap.insert(std::make_pair(root0, 2)); + rmMap.insert(std::make_pair(root1, 1)); + rmMap.insert(std::make_pair(root2, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(21); + } + else + { + // One triple real root, one simple real root. + Rational root0 = -rat3 * c1 / (rat4 * c2); + Rational root1 = -rat3 * root0; + rmMap.insert(std::make_pair(root0, 3)); + rmMap.insert(std::make_pair(root1, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(22); + } + } + } +} + +template +template +void RootsPolynomial::SolveBiquadratic(Rational const& c0, + Rational const& c2, std::map& rmMap) +{ + // Solve 0 = x^4 + c2*x^2 + c0 = (x^2 + c2/2)^2 + a1, where + // a1 = c0 - c2^2/2. We know that c0 != 0 at the time of the function + // call, so x = 0 is not a root. The condition c1 = 0 implies the quartic + // Delta = 256*c0*a1^2. + + Rational const zero = 0, rat2 = 2, rat256 = 256; + Rational c2Half = c2 / rat2; + Rational a1 = c0 - c2Half * c2Half; + Rational delta = rat256 * c0 * a1 * a1; + if (delta > zero) + { + if (c2 < zero) + { + if (a1 < zero) + { + // Four simple roots. + Rational temp0 = (Rational)std::sqrt(-(double)a1); + Rational temp1 = -c2Half - temp0; + Rational temp2 = -c2Half + temp0; + Rational root1 = (Rational)std::sqrt((double)temp1); + Rational root0 = -root1; + Rational root2 = (Rational)std::sqrt((double)temp2); + Rational root3 = -root2; + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + rmMap.insert(std::make_pair(root2, 1)); + rmMap.insert(std::make_pair(root3, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(23); + } + else // a1 > 0 + { + // Two simple complex conjugate pairs. + // double thetaDiv2 = atan2(sqrt(a1), -c2/2) / 2.0; + // double cs = cos(thetaDiv2), sn = sin(thetaDiv2); + // double length = pow(c0, 0.25); + // Complex z0 = length*(cs + i*sn); + // Complex z0conj = length*(cs - i*sn); + // Complex z1 = length*(-cs + i*sn); + // Complex z1conj = length*(-cs - i*sn); + GTE_ROOTS_LOW_DEGREE_BLOCK(24); + } + } + else // c2 >= 0 + { + // Two simple complex conjugate pairs. + // Complex z0 = -i*sqrt(c2/2 - sqrt(-a1)); + // Complex z0conj = +i*sqrt(c2/2 - sqrt(-a1)); + // Complex z1 = -i*sqrt(c2/2 + sqrt(-a1)); + // Complex z1conj = +i*sqrt(c2/2 + sqrt(-a1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(25); + } + } + else if (delta < zero) + { + // Two simple real roots. + Rational temp0 = (Rational)std::sqrt(-(double)a1); + Rational temp1 = -c2Half + temp0; + Rational root1 = (Rational)std::sqrt((double)temp1); + Rational root0 = -root1; + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + + // One complex conjugate pair. + // Complex z0 = -i*sqrt(c2/2 + sqrt(-a1)); + // Complex z0conj = +i*sqrt(c2/2 + sqrt(-a1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(26); + } + else // delta = 0 + { + if (c2 < zero) + { + // Two double real roots. + Rational root1 = (Rational)std::sqrt(-(double)c2Half); + Rational root0 = -root1; + rmMap.insert(std::make_pair(root0, 2)); + rmMap.insert(std::make_pair(root1, 2)); + GTE_ROOTS_LOW_DEGREE_BLOCK(27); + } + else // c2 > 0 + { + // Two double complex conjugate pairs. + // Complex z0 = -i*sqrt(c2/2); // multiplicity 2 + // Complex z0conj = +i*sqrt(c2/2); // multiplicity 2 + GTE_ROOTS_LOW_DEGREE_BLOCK(28); + } + } +} + +template +template +void RootsPolynomial::GetRootInfoDepressedQuadratic(Rational const& c0, + std::vector& info) +{ + Rational const zero = 0; + if (c0 < zero) + { + // Two simple roots. + info.push_back(1); + info.push_back(1); + } + else if (c0 == zero) + { + // One double root. + info.push_back(2); // root is zero + } + else // c0 > 0 + { + // A complex-conjugate pair of roots. + } +} + +template +template +void RootsPolynomial::GetRootInfoDepressedCubic(Rational const& c0, + Rational const& c1, std::vector& info) +{ + // Handle the special case of c0 = 0, in which case the polynomial + // reduces to a depressed quadratic. + Rational const zero = 0; + if (c0 == zero) + { + if (c1 == zero) + { + info.push_back(3); // triple root of zero + } + else + { + info.push_back(1); // simple root of zero + GetRootInfoDepressedQuadratic(c1, info); + } + return; + } + + Rational const rat4 = 4, rat27 = 27; + Rational delta = -(rat4 * c1 * c1 * c1 + rat27 * c0 * c0); + if (delta > zero) + { + // Three simple real roots. + info.push_back(1); + info.push_back(1); + info.push_back(1); + } + else if (delta < zero) + { + // One simple real root. + info.push_back(1); + } + else // delta = 0 + { + // One simple real root and one double real root. + info.push_back(1); + info.push_back(2); + } +} + +template +template +void RootsPolynomial::GetRootInfoDepressedQuartic(Rational const& c0, + Rational const& c1, Rational const& c2, std::vector& info) +{ + // Handle the special case of c0 = 0, in which case the polynomial + // reduces to a depressed cubic. + Rational const zero = 0; + if (c0 == zero) + { + if (c1 == zero) + { + if (c2 == zero) + { + info.push_back(4); // quadruple root of zero + } + else + { + info.push_back(2); // double root of zero + GetRootInfoDepressedQuadratic(c2, info); + } + } + else + { + info.push_back(1); // simple root of zero + GetRootInfoDepressedCubic(c1, c2, info); + } + return; + } + + // Handle the special case of c1 = 0, in which case the quartic is a + // biquadratic x^4 + c1*x^2 + c0 = (x^2 + c2/2)^2 + (c0 - c2^2/4). + if (c1 == zero) + { + GetRootInfoBiquadratic(c0, c2, info); + return; + } + + // At this time, c0 != 0 and c1 != 0, which is a requirement for the + // general solver that must use a root of a special cubic polynomial. + Rational const rat4 = 4, rat8 = 8, rat12 = 12, rat16 = 16; + Rational const rat27 = 27, rat36 = 36; + Rational c0sqr = c0 * c0, c1sqr = c1 * c1, c2sqr = c2 * c2; + Rational delta = c1sqr * (-rat27 * c1sqr + rat4 * c2 * + (rat36 * c0 - c2sqr)) + rat16 * c0 * (c2sqr * (c2sqr - rat8 * c0) + + rat16 * c0sqr); + Rational a0 = rat12 * c0 + c2sqr; + Rational a1 = rat4 * c0 - c2sqr; + + if (delta > zero) + { + if (c2 < zero && a1 < zero) + { + // Four simple real roots. + info.push_back(1); + info.push_back(1); + info.push_back(1); + info.push_back(1); + } + else // c2 >= 0 or a1 >= 0 + { + // Two complex-conjugate pairs. + } + } + else if (delta < zero) + { + // Two simple real roots, one complex-conjugate pair. + info.push_back(1); + info.push_back(1); + } + else // delta = 0 + { + if (a1 > zero || (c2 > zero && (a1 != zero || c1 != zero))) + { + // One double real root, one complex-conjugate pair. + info.push_back(2); + } + else + { + if (a0 != zero) + { + // One double real root, two simple real roots. + info.push_back(2); + info.push_back(1); + info.push_back(1); + } + else + { + // One triple real root, one simple real root. + info.push_back(3); + info.push_back(1); + } + } + } +} + +template +template +void RootsPolynomial::GetRootInfoBiquadratic(Rational const& c0, + Rational const& c2, std::vector& info) +{ + // Solve 0 = x^4 + c2*x^2 + c0 = (x^2 + c2/2)^2 + a1, where + // a1 = c0 - c2^2/2. We know that c0 != 0 at the time of the function + // call, so x = 0 is not a root. The condition c1 = 0 implies the quartic + // Delta = 256*c0*a1^2. + + Rational const zero = 0, rat2 = 2, rat256 = 256; + Rational c2Half = c2 / rat2; + Rational a1 = c0 - c2Half * c2Half; + Rational delta = rat256 * c0 * a1 * a1; + if (delta > zero) + { + if (c2 < zero) + { + if (a1 < zero) + { + // Four simple roots. + info.push_back(1); + info.push_back(1); + info.push_back(1); + info.push_back(1); + } + else // a1 > 0 + { + // Two simple complex conjugate pairs. + } + } + else // c2 >= 0 + { + // Two simple complex conjugate pairs. + } + } + else if (delta < zero) + { + // Two simple real roots, one complex conjugate pair. + info.push_back(1); + info.push_back(1); + } + else // delta = 0 + { + if (c2 < zero) + { + // Two double real roots. + info.push_back(2); + info.push_back(2); + } + else // c2 > 0 + { + // Two double complex conjugate pairs. + } + } +} + +template +int RootsPolynomial::FindRecursive(int degree, Real const* c, + Real tmin, Real tmax, unsigned int maxIterations, Real* roots) +{ + // The base of the recursion. + Real const zero = (Real)0; + Real root = zero; + if (degree == 1) + { + int numRoots; + if (c[1] != zero) + { + root = -c[0] / c[1]; + numRoots = 1; + } + else if (c[0] == zero) + { + root = zero; + numRoots = 1; + } + else + { + numRoots = 0; + } + + if (numRoots > 0 && tmin <= root && root <= tmax) + { + roots[0] = root; + return 1; + } + return 0; + } + + // Find the roots of the derivative polynomial scaled by 1/degree. The + // scaling avoids the factorial growth in the coefficients; for example, + // without the scaling, the high-order term x^d becomes (d!)*x through + // multiple differentiations. With the scaling we instead get x. This + // leads to better numerical behavior of the root finder. + int derivDegree = degree - 1; + std::vector derivCoeff(derivDegree + 1); + std::vector derivRoots(derivDegree); + for (int i = 0; i <= derivDegree; ++i) + { + derivCoeff[i] = c[i + 1] * (Real)(i + 1) / (Real)degree; + } + int numDerivRoots = FindRecursive(degree - 1, &derivCoeff[0], tmin, tmax, + maxIterations, &derivRoots[0]); + + int numRoots = 0; + if (numDerivRoots > 0) + { + // Find root on [tmin,derivRoots[0]]. + if (Find(degree, c, tmin, derivRoots[0], maxIterations, root)) + { + roots[numRoots++] = root; + } + + // Find root on [derivRoots[i],derivRoots[i+1]]. + for (int i = 0; i <= numDerivRoots - 2; ++i) + { + if (Find(degree, c, derivRoots[i], derivRoots[i + 1], + maxIterations, root)) + { + roots[numRoots++] = root; + } + } + + // Find root on [derivRoots[numDerivRoots-1],tmax]. + if (Find(degree, c, derivRoots[numDerivRoots - 1], tmax, + maxIterations, root)) + { + roots[numRoots++] = root; + } + } + else + { + // The polynomial is monotone on [tmin,tmax], so has at most one root. + if (Find(degree, c, tmin, tmax, maxIterations, root)) + { + roots[numRoots++] = root; + } + } + return numRoots; +} + +template +Real RootsPolynomial::Evaluate(int degree, Real const* c, Real t) +{ + int i = degree; + Real result = c[i]; + while (--i >= 0) + { + result = t * result + c[i]; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRotation.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRotation.h new file mode 100644 index 000000000000..e3cb76c1238e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRotation.h @@ -0,0 +1,892 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +// Conversions among various representations of rotations. The value of +// N must be 3 or 4. The latter case supports affine algebra when you use +// 4-tuple vectors (w-component is 1 for points and 0 for vector) and 4x4 +// matrices for affine transformations. Rotation axes must be unit length. +// The angles are in radians. The Euler angles are in world coordinates; +// we have not yet added support for body coordinates. + +template +class Rotation +{ +public: + // Create rotations from various representations. + Rotation(Matrix const& matrix); + Rotation(Quaternion const& quaternion); + Rotation(AxisAngle const& axisAngle); + Rotation(EulerAngles const& eulerAngles); + + // Convert one representation to another. + operator Matrix() const; + operator Quaternion() const; + operator AxisAngle() const; + EulerAngles const& operator()(int i0, int i1, int i2) const; + +private: + enum RepresentationType + { + IS_MATRIX, + IS_QUATERNION, + IS_AXIS_ANGLE, + IS_EULER_ANGLES + }; + + RepresentationType mType; + mutable Matrix mMatrix; + mutable Quaternion mQuaternion; + mutable AxisAngle mAxisAngle; + mutable EulerAngles mEulerAngles; + + // Convert a rotation matrix to a quaternion. + // + // x^2 = (+r00 - r11 - r22 + 1)/4 + // y^2 = (-r00 + r11 - r22 + 1)/4 + // z^2 = (-r00 - r11 + r22 + 1)/4 + // w^2 = (+r00 + r11 + r22 + 1)/4 + // x^2 + y^2 = (1 - r22)/2 + // z^2 + w^2 = (1 + r22)/2 + // y^2 - x^2 = (r11 - r00)/2 + // w^2 - z^2 = (r11 + r00)/2 + // x*y = (r01 + r10)/4 + // x*z = (r02 + r20)/4 + // y*z = (r12 + r21)/4 + // [GTE_USE_MAT_VEC] + // x*w = (r21 - r12)/4 + // y*w = (r02 - r20)/4 + // z*w = (r10 - r01)/4 + // [GTE_USE_VEC_MAT] + // x*w = (r12 - r21)/4 + // y*w = (r20 - r02)/4 + // z*w = (r01 - r10)/4 + // + // If Q is the 4x1 column vector (x,y,z,w), the previous equations give us + // +- -+ + // | x*x x*y x*z x*w | + // Q*Q^T = | y*x y*y y*z y*w | + // | z*x z*y z*z z*w | + // | w*x w*y w*z w*w | + // +- -+ + // The code extracts the row of maximum length, normalizing it to obtain + // the result q. + static void Convert(Matrix const& r, Quaternion& q); + + // Convert a quaterion q = x*i + y*j + z*k + w to a rotation matrix. + // [GTE_USE_MAT_VEC] + // +- -+ +- -+ + // R = | r00 r01 r02 | = | 1-2y^2-2z^2 2(xy-zw) 2(xz+yw) | + // | r10 r11 r12 | | 2(xy+zw) 1-2x^2-2z^2 2(yz-xw) | + // | r20 r21 r22 | | 2(xz-yw) 2(yz+xw) 1-2x^2-2y^2 | + // +- -+ +- -+ + // [GTE_USE_VEC_MAT] + // +- -+ +- -+ + // R = | r00 r01 r02 | = | 1-2y^2-2z^2 2(xy+zw) 2(xz-yw) | + // | r10 r11 r12 | | 2(xy-zw) 1-2x^2-2z^2 2(yz+xw) | + // | r20 r21 r22 | | 2(xz+yw) 2(yz-xw) 1-2x^2-2y^2 | + // +- -+ +- -+ + static void Convert(Quaternion const& q, Matrix& r); + + // Convert a rotation matrix to an axis-angle pair. Let (x0,x1,x2) be the + // axis let t be an angle of rotation. The rotation matrix is + // [GTE_USE_MAT_VEC] + // R = I + sin(t)*S + (1-cos(t))*S^2 + // or + // [GTE_USE_VEC_MAT] + // R = I - sin(t)*S + (1-cos(t))*S^2 + // where I is the identity and S = {{0,-x2,x1},{x2,0,-x0},{-x1,x0,0}} + // where the inner-brace triples are the rows of the matrix. If t > 0, + // R represents a counterclockwise rotation; see the comments for the + // constructor Matrix3x3(axis,angle). It may be shown that cos(t) = + // (trace(R)-1)/2 and R - Transpose(R) = 2*sin(t)*S. As long as sin(t) is + // not zero, we may solve for S in the second equation, which produces the + // axis direction U = (S21,S02,S10). When t = 0, the rotation is the + // identity, in which case any axis direction is valid; we choose (1,0,0). + // When t = pi, it must be that R - Transpose(R) = 0, which prevents us + // from extracting the axis. Instead, note that (R+I)/2 = I+S^2 = U*U^T, + // where U is a unit-length axis direction. + static void Convert(Matrix const& r, AxisAngle& a); + + // Convert an axis-angle pair to a rotation matrix. Assuming (x0,x1,x2) + // is for a right-handed world (x0 to right, x1 up, x2 out of plane of + // page), a positive angle corresponds to a counterclockwise rotation from + // the perspective of an observer looking at the origin of the plane of + // rotation and having view direction the negative of the axis direction. + // The coordinate-axis rotations are the following, where + // unit(0) = (1,0,0), unit(1) = (0,1,0), unit(2) = (0,0,1), + // [GTE_USE_MAT_VEC] + // R(unit(0),t) = {{ 1, 0, 0}, { 0, c,-s}, { 0, s, c}} + // R(unit(1),t) = {{ c, 0, s}, { 0, 1, 0}, {-s, 0, c}} + // R(unit(2),t) = {{ c,-s, 0}, { s, c, 0}, { 0, 0, 1}} + // or + // [GTE_USE_VEC_MAT] + // R(unit(0),t) = {{ 1, 0, 0}, { 0, c, s}, { 0,-s, c}} + // R(unit(1),t) = {{ c, 0,-s}, { 0, 1, 0}, { s, 0, c}} + // R(unit(2),t) = {{ c, s, 0}, {-s, c, 0}, { 0, 0, 1}} + // where c = cos(t), s = sin(t), and the inner-brace triples are rows of + // the matrix. The general matrix is + // [GTE_USE_MAT_VEC] + // +- -+ + // R = | (1-c)*x0^2 + c (1-c)*x0*x1 - s*x2 (1-c)*x0*x2 + s*x1 | + // | (1-c)*x0*x1 + s*x2 (1-c)*x1^2 + c (1-c)*x1*x2 - s*x0 | + // | (1-c)*x0*x2 - s*x1 (1-c)*x1*x2 + s*x0 (1-c)*x2^2 + c | + // +- -+ + // [GTE_USE_VEC_MAT] + // +- -+ + // R = | (1-c)*x0^2 + c (1-c)*x0*x1 + s*x2 (1-c)*x0*x2 - s*x1 | + // | (1-c)*x0*x1 - s*x2 (1-c)*x1^2 + c (1-c)*x1*x2 + s*x0 | + // | (1-c)*x0*x2 + s*x1 (1-c)*x1*x2 - s*x0 (1-c)*x2^2 + c | + // +- -+ + static void Convert(AxisAngle const& a, Matrix& r); + + // Convert a rotation matrix to Euler angles. Factorization into Euler + // angles is not necessarily unique. If the result is ER_NOT_UNIQUE_SUM, + // then the multiple solutions occur because angleN2+angleN0 is constant. + // If the result is ER_NOT_UNIQUE_DIF, then the multiple solutions occur + // because angleN2-angleN0 is constant. In either type of nonuniqueness, + // the function returns angleN0=0. + static void Convert(Matrix const& r, EulerAngles& e); + + // Convert Euler angles to a rotation matrix. The three integer inputs + // are in {0,1,2} and correspond to world directions unit(0) = (1,0,0), + // unit(1) = (0,1,0), or unit(2) = (0,0,1). The triples (N0,N1,N2) must + // be in the following set, + // {(0,1,2),(0,2,1),(1,0,2),(1,2,0),(2,0,1),(2,1,0), + // (0,1,0),(0,2,0),(1,0,1),(1,2,1),(2,0,2),(2,1,2)} + // The rotation matrix is + // [GTE_USE_MAT_VEC] + // R(unit(N2),angleN2)*R(unit(N1),angleN1)*R(unit(N0),angleN0) + // or + // [GTE_USE_VEC_MAT] + // R(unit(N0),angleN0)*R(unit(N1),angleN1)*R(unit(N2),angleN2) + // The conventions of constructor Matrix3(axis,angle) apply here as well. + // + // NOTE: The reversal of order is chosen so that a rotation matrix built + // with one multiplication convention is the transpose of the rotation + // matrix built with the other multiplication convention. Thus, + // [GTE_USE_MAT_VEC] + // Matrix3x3 R_mvconvention(N0,N1,N2,angleN0,angleN1,angleN2); + // Vector3 V(...); + // Vector3 U = R_mvconvention*V; // (u0,u1,u2) = R2*R1*R0*V + // [GTE_USE_VEC_MAT] + // Matrix3x3 R_vmconvention(N0,N1,N2,angleN0,angleN1,angleN2); + // Vector3 V(...); + // Vector3 U = R_mvconvention*V; // (u0,u1,u2) = V*R0*R1*R2 + // In either convention, you get the same 3-tuple U. + static void Convert(EulerAngles const& e, Matrix& r); + + // Convert a quaternion to an axis-angle pair, where + // q = sin(angle/2)*(axis[0]*i + axis[1]*j + axis[2]*k) + cos(angle/2) + static void Convert(Quaternion const& q, AxisAngle& a); + + // Convert an axis-angle pair to a quaternion, where + // q = sin(angle/2)*(axis[0]*i + axis[1]*j + axis[2]*k) + cos(angle/2) + static void Convert(AxisAngle const& a, Quaternion& q); + + // Convert a quaternion to Euler angles. The quaternion is converted to + // a matrix which is then converted to Euler angles. + static void Convert(Quaternion const& q, EulerAngles& e); + + // Convert Euler angles to a quaternion. The Euler angles are converted + // to a matrix which is then converted to a quaternion. + static void Convert(EulerAngles const& e, Quaternion& q); + + // Convert an axis-angle pair to Euler angles. The axis-angle pair is + // converted to a quaternion which is then converted to Euler angles. + static void Convert(AxisAngle const& a, EulerAngles& e); + + // Convert Euler angles to an axis-angle pair. The Euler angles are + // converted to a quaternion which is then converted to an axis-angle + // pair. + static void Convert(EulerAngles const& e, AxisAngle& a); +}; + + +template +Rotation::Rotation(Matrix const& matrix) + : + mType(IS_MATRIX), + mMatrix(matrix) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); +} + +template +Rotation::Rotation(Quaternion const& quaternion) + : + mType(IS_QUATERNION), + mQuaternion(quaternion) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); +} + +template +Rotation::Rotation(AxisAngle const& axisAngle) + : + mType(IS_AXIS_ANGLE), + mAxisAngle(axisAngle) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); +} + +template +Rotation::Rotation(EulerAngles const& eulerAngles) + : + mType(IS_EULER_ANGLES), + mEulerAngles(eulerAngles) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); +} + +template +Rotation::operator Matrix() const +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + switch (mType) + { + case IS_MATRIX: + break; + case IS_QUATERNION: + Convert(mQuaternion, mMatrix); + break; + case IS_AXIS_ANGLE: + Convert(mAxisAngle, mMatrix); + break; + case IS_EULER_ANGLES: + Convert(mEulerAngles, mMatrix); + break; + } + + return mMatrix; +} + +template +Rotation::operator Quaternion() const +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + switch (mType) + { + case IS_MATRIX: + Convert(mMatrix, mQuaternion); + break; + case IS_QUATERNION: + break; + case IS_AXIS_ANGLE: + Convert(mAxisAngle, mQuaternion); + break; + case IS_EULER_ANGLES: + Convert(mEulerAngles, mQuaternion); + break; + } + + return mQuaternion; +} + +template +Rotation::operator AxisAngle() const +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + switch (mType) + { + case IS_MATRIX: + Convert(mMatrix, mAxisAngle); + break; + case IS_QUATERNION: + Convert(mQuaternion, mAxisAngle); + break; + case IS_AXIS_ANGLE: + break; + case IS_EULER_ANGLES: + Convert(mEulerAngles, mAxisAngle); + break; + } + + return mAxisAngle; +} + +template +EulerAngles const& Rotation::operator()(int i0, int i1, + int i2) const +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + mEulerAngles.axis[0] = i0; + mEulerAngles.axis[1] = i1; + mEulerAngles.axis[2] = i2; + + switch (mType) + { + case IS_MATRIX: + Convert(mMatrix, mEulerAngles); + break; + case IS_QUATERNION: + Convert(mQuaternion, mEulerAngles); + break; + case IS_AXIS_ANGLE: + Convert(mAxisAngle, mEulerAngles); + break; + case IS_EULER_ANGLES: + break; + } + + return mEulerAngles; +} + +template +void Rotation::Convert(Matrix const& r, + Quaternion& q) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Real r22 = r(2, 2); + if (r22 <= (Real)0) // x^2 + y^2 >= z^2 + w^2 + { + Real dif10 = r(1, 1) - r(0, 0); + Real omr22 = (Real)1 - r22; + if (dif10 <= (Real)0) // x^2 >= y^2 + { + Real fourXSqr = omr22 - dif10; + Real inv4x = ((Real)0.5) / std::sqrt(fourXSqr); + q[0] = fourXSqr*inv4x; + q[1] = (r(0, 1) + r(1, 0))*inv4x; + q[2] = (r(0, 2) + r(2, 0))*inv4x; +#if defined(GTE_USE_MAT_VEC) + q[3] = (r(2, 1) - r(1, 2))*inv4x; +#else + q[3] = (r(1, 2) - r(2, 1))*inv4x; +#endif + } + else // y^2 >= x^2 + { + Real fourYSqr = omr22 + dif10; + Real inv4y = ((Real)0.5) / std::sqrt(fourYSqr); + q[0] = (r(0, 1) + r(1, 0))*inv4y; + q[1] = fourYSqr*inv4y; + q[2] = (r(1, 2) + r(2, 1))*inv4y; +#if defined(GTE_USE_MAT_VEC) + q[3] = (r(0, 2) - r(2, 0))*inv4y; +#else + q[3] = (r(2, 0) - r(0, 2))*inv4y; +#endif + } + } + else // z^2 + w^2 >= x^2 + y^2 + { + Real sum10 = r(1, 1) + r(0, 0); + Real opr22 = (Real)1 + r22; + if (sum10 <= (Real)0) // z^2 >= w^2 + { + Real fourZSqr = opr22 - sum10; + Real inv4z = ((Real)0.5) / std::sqrt(fourZSqr); + q[0] = (r(0, 2) + r(2, 0))*inv4z; + q[1] = (r(1, 2) + r(2, 1))*inv4z; + q[2] = fourZSqr*inv4z; +#if defined(GTE_USE_MAT_VEC) + q[3] = (r(1, 0) - r(0, 1))*inv4z; +#else + q[3] = (r(0, 1) - r(1, 0))*inv4z; +#endif + } + else // w^2 >= z^2 + { + Real fourWSqr = opr22 + sum10; + Real inv4w = ((Real)0.5) / std::sqrt(fourWSqr); +#if defined(GTE_USE_MAT_VEC) + q[0] = (r(2, 1) - r(1, 2))*inv4w; + q[1] = (r(0, 2) - r(2, 0))*inv4w; + q[2] = (r(1, 0) - r(0, 1))*inv4w; +#else + q[0] = (r(1, 2) - r(2, 1))*inv4w; + q[1] = (r(2, 0) - r(0, 2))*inv4w; + q[2] = (r(0, 1) - r(1, 0))*inv4w; +#endif + q[3] = fourWSqr*inv4w; + } + } +} + +template +void Rotation::Convert(Quaternion const& q, Matrix& r) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + r.MakeIdentity(); + + Real twoX = ((Real)2)*q[0]; + Real twoY = ((Real)2)*q[1]; + Real twoZ = ((Real)2)*q[2]; + Real twoXX = twoX*q[0]; + Real twoXY = twoX*q[1]; + Real twoXZ = twoX*q[2]; + Real twoXW = twoX*q[3]; + Real twoYY = twoY*q[1]; + Real twoYZ = twoY*q[2]; + Real twoYW = twoY*q[3]; + Real twoZZ = twoZ*q[2]; + Real twoZW = twoZ*q[3]; + +#if defined(GTE_USE_MAT_VEC) + r(0, 0) = (Real)1 - twoYY - twoZZ; + r(0, 1) = twoXY - twoZW; + r(0, 2) = twoXZ + twoYW; + r(1, 0) = twoXY + twoZW; + r(1, 1) = (Real)1 - twoXX - twoZZ; + r(1, 2) = twoYZ - twoXW; + r(2, 0) = twoXZ - twoYW; + r(2, 1) = twoYZ + twoXW; + r(2, 2) = (Real)1 - twoXX - twoYY; +#else + r(0, 0) = (Real)1 - twoYY - twoZZ; + r(1, 0) = twoXY - twoZW; + r(2, 0) = twoXZ + twoYW; + r(0, 1) = twoXY + twoZW; + r(1, 1) = (Real)1 - twoXX - twoZZ; + r(2, 1) = twoYZ - twoXW; + r(0, 2) = twoXZ - twoYW; + r(1, 2) = twoYZ + twoXW; + r(2, 2) = (Real)1 - twoXX - twoYY; +#endif +} + +template +void Rotation::Convert(Matrix const& r, + AxisAngle& a) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Real trace = r(0, 0) + r(1, 1) + r(2, 2); + Real half = (Real)0.5; + Real cs = half*(trace - (Real)1); + cs = std::max(std::min(cs, (Real)1), (Real)-1); + a.angle = std::acos(cs); // The angle is in [0,pi]. + a.axis.MakeZero(); + + if (a.angle > (Real)0) + { + if (a.angle < (Real)GTE_C_PI) + { + // The angle is in (0,pi). +#if defined(GTE_USE_MAT_VEC) + a.axis[0] = r(2, 1) - r(1, 2); + a.axis[1] = r(0, 2) - r(2, 0); + a.axis[2] = r(1, 0) - r(0, 1); + Normalize(a.axis); +#else + a.axis[0] = r(1, 2) - r(2, 1); + a.axis[1] = r(2, 0) - r(0, 2); + a.axis[2] = r(0, 1) - r(1, 0); + Normalize(a.axis); +#endif + } + else + { + // The angle is pi, in which case R is symmetric and + // R+I = 2*(I+S^2) = 2*U*U^T, where U = (u0,u1,u2) is the + // unit-length direction of the rotation axis. Determine the + // largest diagonal entry of R+I and normalize the + // corresponding row to produce U. It does not matter the + // sign on u[d] for chosen diagonal d, because R(U,pi) = R(-U,pi). + Real one = (Real)1; + if (r(0, 0) >= r(1, 1)) + { + if (r(0, 0) >= r(2, 2)) + { + // r00 is maximum diagonal term + a.axis[0] = r(0, 0) + one; + a.axis[1] = half*(r(0, 1) + r(1, 0)); + a.axis[2] = half*(r(0, 2) + r(2, 0)); + } + else + { + // r22 is maximum diagonal term + a.axis[0] = half*(r(2, 0) + r(0, 2)); + a.axis[1] = half*(r(2, 1) + r(1, 2)); + a.axis[2] = r(2, 2) + one; + } + } + else + { + if (r(1, 1) >= r(2, 2)) + { + // r11 is maximum diagonal term + a.axis[0] = half*(r(1, 0) + r(0, 1)); + a.axis[1] = r(1, 1) + one; + a.axis[2] = half*(r(1, 2) + r(2, 1)); + } + else + { + // r22 is maximum diagonal term + a.axis[0] = half*(r(2, 0) + r(0, 2)); + a.axis[1] = half*(r(2, 1) + r(1, 2)); + a.axis[2] = r(2, 2) + one; + } + } + Normalize(a.axis); + } + } + else + { + // The angle is 0 and the matrix is the identity. Any axis will + // work, so choose the Unit(0) axis. + a.axis[0] = (Real)1; + } +} + +template +void Rotation::Convert(AxisAngle const& a, + Matrix& r) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + r.MakeIdentity(); + + Real cs = std::cos(a.angle); + Real sn = std::sin(a.angle); + Real oneMinusCos = ((Real)1) - cs; + Real x0sqr = a.axis[0] * a.axis[0]; + Real x1sqr = a.axis[1] * a.axis[1]; + Real x2sqr = a.axis[2] * a.axis[2]; + Real x0x1m = a.axis[0] * a.axis[1] * oneMinusCos; + Real x0x2m = a.axis[0] * a.axis[2] * oneMinusCos; + Real x1x2m = a.axis[1] * a.axis[2] * oneMinusCos; + Real x0Sin = a.axis[0] * sn; + Real x1Sin = a.axis[1] * sn; + Real x2Sin = a.axis[2] * sn; + +#if defined(GTE_USE_MAT_VEC) + r(0, 0) = x0sqr*oneMinusCos + cs; + r(0, 1) = x0x1m - x2Sin; + r(0, 2) = x0x2m + x1Sin; + r(1, 0) = x0x1m + x2Sin; + r(1, 1) = x1sqr*oneMinusCos + cs; + r(1, 2) = x1x2m - x0Sin; + r(2, 0) = x0x2m - x1Sin; + r(2, 1) = x1x2m + x0Sin; + r(2, 2) = x2sqr*oneMinusCos + cs; +#else + r(0, 0) = x0sqr*oneMinusCos + cs; + r(1, 0) = x0x1m - x2Sin; + r(2, 0) = x0x2m + x1Sin; + r(0, 1) = x0x1m + x2Sin; + r(1, 1) = x1sqr*oneMinusCos + cs; + r(2, 1) = x1x2m - x0Sin; + r(0, 2) = x0x2m - x1Sin; + r(1, 2) = x1x2m + x0Sin; + r(2, 2) = x2sqr*oneMinusCos + cs; +#endif +} + +template +void Rotation::Convert(Matrix const& r, + EulerAngles& e) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + if (0 <= e.axis[0] && e.axis[0] < 3 + && 0 <= e.axis[1] && e.axis[1] < 3 + && 0 <= e.axis[2] && e.axis[2] < 3 + && e.axis[1] != e.axis[0] + && e.axis[1] != e.axis[2]) + { + if (e.axis[0] != e.axis[2]) + { +#if defined(GTE_USE_MAT_VEC) + // Map (0,1,2), (1,2,0), and (2,0,1) to +1. + // Map (0,2,1), (2,1,0), and (1,0,2) to -1. + int parity = (((e.axis[2] | (e.axis[1] << 2)) >> e.axis[0]) & 1); + Real const sgn = (parity & 1 ? (Real)-1 : (Real)+1); + + if (r(e.axis[2], e.axis[0]) < (Real)1) + { + if (r(e.axis[2], e.axis[0]) > (Real)-1) + { + e.angle[2] = std::atan2(sgn*r(e.axis[1], e.axis[0]), + r(e.axis[0], e.axis[0])); + e.angle[1] = std::asin(-sgn*r(e.axis[2], e.axis[0])); + e.angle[0] = std::atan2(sgn*r(e.axis[2], e.axis[1]), + r(e.axis[2], e.axis[2])); + e.result = ER_UNIQUE; + } + else + { + e.angle[2] = (Real)0; + e.angle[1] = sgn*(Real)GTE_C_HALF_PI; + e.angle[0] = std::atan2(-sgn*r(e.axis[1], e.axis[2]), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_DIF; + } + } + else + { + e.angle[2] = (Real)0; + e.angle[1] = -sgn*(Real)GTE_C_HALF_PI; + e.angle[0] = std::atan2(-sgn*r(e.axis[1], e.axis[2]), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_SUM; + } +#else + // Map (0,1,2), (1,2,0), and (2,0,1) to +1. + // Map (0,2,1), (2,1,0), and (1,0,2) to -1. + int parity = (((e.axis[0] | (e.axis[1] << 2)) >> e.axis[2]) & 1); + Real const sgn = (parity & 1 ? (Real)+1 : (Real)-1); + + if (r(e.axis[0], e.axis[2]) < (Real)1) + { + if (r(e.axis[0], e.axis[2]) > (Real)-1) + { + e.angle[0] = std::atan2(sgn*r(e.axis[1], e.axis[2]), + r(e.axis[2], e.axis[2])); + e.angle[1] = std::asin(-sgn*r(e.axis[0], e.axis[2])); + e.angle[2] = std::atan2(sgn*r(e.axis[0], e.axis[1]), + r(e.axis[0], e.axis[0])); + e.result = ER_UNIQUE; + } + else + { + e.angle[0] = (Real)0; + e.angle[1] = sgn*(Real)GTE_C_HALF_PI; + e.angle[2] = std::atan2(-sgn*r(e.axis[1], e.axis[0]), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_DIF; + } + } + else + { + e.angle[0] = (Real)0; + e.angle[1] = -sgn*(Real)GTE_C_HALF_PI; + e.angle[2] = std::atan2(-sgn*r(e.axis[1], e.axis[0]), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_SUM; + } +#endif + } + else + { +#if defined(GTE_USE_MAT_VEC) + // Map (0,2,0), (1,0,1), and (2,1,2) to +1. + // Map (0,1,0), (1,2,1), and (2,0,2) to -1. + int b0 = 3 - e.axis[1] - e.axis[2]; + int parity = (((b0 | (e.axis[1] << 2)) >> e.axis[2]) & 1); + Real const sgn = (parity & 1 ? (Real)+1 : (Real)-1); + + if (r(e.axis[2], e.axis[2]) < (Real)1) + { + if (r(e.axis[2], e.axis[2]) > (Real)-1) + { + e.angle[2] = std::atan2(r(e.axis[1], e.axis[2]), + sgn*r(b0, e.axis[2])); + e.angle[1] = std::acos(r(e.axis[2], e.axis[2])); + e.angle[0] = std::atan2(r(e.axis[2], e.axis[1]), + -sgn*r(e.axis[2], b0)); + e.result = ER_UNIQUE; + } + else + { + e.angle[2] = (Real)0; + e.angle[1] = (Real)GTE_C_PI; + e.angle[0] = std::atan2(sgn*r(e.axis[1], b0), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_DIF; + } + } + else + { + e.angle[2] = (Real)0; + e.angle[1] = (Real)0; + e.angle[0] = std::atan2(sgn*r(e.axis[1], b0), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_SUM; + } +#else + // Map (0,2,0), (1,0,1), and (2,1,2) to -1. + // Map (0,1,0), (1,2,1), and (2,0,2) to +1. + int b2 = 3 - e.axis[0] - e.axis[1]; + int parity = (((b2 | (e.axis[1] << 2)) >> e.axis[0]) & 1); + Real const sgn = (parity & 1 ? (Real)-1 : (Real)+1); + + if (r(e.axis[0], e.axis[0]) < (Real)1) + { + if (r(e.axis[0], e.axis[0]) > (Real)-1) + { + e.angle[0] = std::atan2(r(e.axis[1], e.axis[0]), + sgn*r(b2, e.axis[0])); + e.angle[1] = std::acos(r(e.axis[0], e.axis[0])); + e.angle[2] = std::atan2(r(e.axis[0], e.axis[1]), + -sgn*r(e.axis[0], b2)); + e.result = ER_UNIQUE; + } + else + { + e.angle[0] = (Real)0; + e.angle[1] = (Real)GTE_C_PI; + e.angle[2] = std::atan2(sgn*r(e.axis[1], b2), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_DIF; + } + } + else + { + e.angle[0] = (Real)0; + e.angle[1] = (Real)0; + e.angle[2] = std::atan2(sgn*r(e.axis[1], b2), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_SUM; + } +#endif + } + } + else + { + // Invalid angles. + e.angle[0] = (Real)0; + e.angle[1] = (Real)0; + e.angle[2] = (Real)0; + e.result = ER_INVALID; + } +} + +template +void Rotation::Convert(EulerAngles const& e, + Matrix& r) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + if (0 <= e.axis[0] && e.axis[0] < 3 + && 0 <= e.axis[1] && e.axis[1] < 3 + && 0 <= e.axis[2] && e.axis[2] < 3 + && e.axis[1] != e.axis[0] + && e.axis[1] != e.axis[2]) + { + Matrix r0, r1, r2; + Convert(AxisAngle(Vector::Unit(e.axis[0]), + e.angle[0]), r0); + Convert(AxisAngle(Vector::Unit(e.axis[1]), + e.angle[1]), r1); + Convert(AxisAngle(Vector::Unit(e.axis[2]), + e.angle[2]), r2); +#if defined(GTE_USE_MAT_VEC) + r = r2*r1*r0; +#else + r = r0*r1*r2; +#endif + } + else + { + // Invalid angles. + r.MakeIdentity(); + } +} + +template +void Rotation::Convert(Quaternion const& q, + AxisAngle& a) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + a.axis.MakeZero(); + + Real axisSqrLen = q[0] * q[0] + q[1] * q[1] + q[2] * q[2]; + if (axisSqrLen > (Real)0) + { +#if defined(GTE_USE_MAT_VEC) + Real adjust = ((Real)1) / std::sqrt(axisSqrLen); +#else + Real adjust = ((Real)-1) / std::sqrt(axisSqrLen); +#endif + a.axis[0] = q[0] * adjust; + a.axis[1] = q[1] * adjust; + a.axis[2] = q[2] * adjust; + Real cs = std::max(std::min(q[3], (Real)1), (Real)-1); + a.angle = (Real)2 * std::acos(cs); + } + else + { + // The angle is 0 (modulo 2*pi). Any axis will work, so choose the + // Unit(0) axis. + a.axis[0] = (Real)1; + a.angle = (Real)0; + } +} + +template +void Rotation::Convert(AxisAngle const& a, + Quaternion& q) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + +#if defined(GTE_USE_MAT_VEC) + Real halfAngle = ((Real)0.5)*a.angle; +#else + Real halfAngle = ((Real)-0.5)*a.angle; +#endif + Real sn = std::sin(halfAngle); + q[0] = sn*a.axis[0]; + q[1] = sn*a.axis[1]; + q[2] = sn*a.axis[2]; + q[3] = std::cos(halfAngle); +} + +template +void Rotation::Convert(Quaternion const& q, + EulerAngles& e) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Matrix r; + Convert(q, r); + Convert(r, e); +} + +template +void Rotation::Convert(EulerAngles const& e, + Quaternion& q) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Matrix r; + Convert(e, r); + Convert(r, q); +} + +template +void Rotation::Convert(AxisAngle const& a, + EulerAngles& e) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Quaternion q; + Convert(a, q); + Convert(q, e); +} + +template +void Rotation::Convert(EulerAngles const& e, + AxisAngle& a) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Quaternion q; + Convert(e, q); + Convert(q, a); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSector2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSector2.h new file mode 100644 index 000000000000..c467ec7bef8d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSector2.h @@ -0,0 +1,165 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +// A solid sector is the intersection of a disk and a 2D cone. The disk +// has center C, radius R, and contains points X for which |X-C| <= R. The +// 2D cone has vertex C, unit-length axis direction D, angle A in (0,pi) +// measured from D, and contains points X for which Dot(D,(X-C)/|X-C|) >= cos(A). +// Sector points X satisfy both inequality constraints. + +template +class Sector2 +{ +public: + // Construction and destruction. The default constructor sets the vertex + // to (0,0), radius to 1, axis direction to (1,0), and angle to pi, + // all of which define a disk. + Sector2(); + Sector2(Vector2 const& inVertex, Real inRadius, + Vector2 const& inDirection, Real inAngle); + + // Set the angle and cos(angle) simultaneously. + void SetAngle(Real inAngle); + + // Test whether P is in the sector. + bool Contains(Vector2 const& p) const; + + // The cosine and sine of the angle are used in queries, so all o + // angle, cos(angle), and sin(angle) are stored. If you set 'angle' + // via the public members, you must set all to be consistent. You + // can also call SetAngle(...) to ensure consistency. + Vector2 vertex; + Real radius; + Vector2 direction; + Real angle, cosAngle, sinAngle; + +public: + // Comparisons to support sorted containers. + bool operator==(Sector2 const& sector) const; + bool operator!=(Sector2 const& sector) const; + bool operator< (Sector2 const& sector) const; + bool operator<=(Sector2 const& sector) const; + bool operator> (Sector2 const& sector) const; + bool operator>=(Sector2 const& sector) const; +}; + + +template +Sector2::Sector2() + : + vertex(Vector2::Zero()), + radius((Real)1), + direction(Vector2::Unit(0)), + angle((Real)GTE_C_PI), + cosAngle((Real)-1), + sinAngle((Real)0) +{ +} + +template +Sector2::Sector2(Vector2 const& inCenter, Real inRadius, + Vector2 const& inDirection, Real inAngle) + : + vertex(inCenter), + radius(inRadius), + direction(inDirection) +{ + SetAngle(inAngle); +} + +template +void Sector2::SetAngle(Real inAngle) +{ + angle = inAngle; + cosAngle = std::cos(angle); + sinAngle = std::sin(angle); +} + +template +bool Sector2::Contains(Vector2 const& p) const +{ + Vector2 diff = p - vertex; + Real length = Length(diff); + return length <= radius && Dot(direction, diff) >= length * cosAngle; +} + +template +bool Sector2::operator==(Sector2 const& sector) const +{ + return vertex == sector.vertex && radius == sector.radius + && direction == sector.direction && angle == sector.angle; +} + +template +bool Sector2::operator!=(Sector2 const& sector) const +{ + return !operator==(sector); +} + +template +bool Sector2::operator<(Sector2 const& sector) const +{ + if (vertex < sector.vertex) + { + return true; + } + + if (vertex > sector.vertex) + { + return false; + } + + if (radius < sector.radius) + { + return true; + } + + if (radius > sector.radius) + { + return false; + } + + if (direction < sector.direction) + { + return true; + } + + if (direction > sector.direction) + { + return false; + } + + return angle < sector.angle; +} + +template +bool Sector2::operator<=(Sector2 const& sector) const +{ + return operator<(sector) || operator==(sector); +} + +template +bool Sector2::operator>(Sector2 const& sector) const +{ + return !operator<=(sector); +} + +template +bool Sector2::operator>=(Sector2 const& sector) const +{ + return !operator<(sector); +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSegment.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSegment.h new file mode 100644 index 000000000000..3fd213755d70 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSegment.h @@ -0,0 +1,148 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The segment is represented by (1-t)*P0 + t*P1, where P0 and P1 are the +// endpoints of the segment and 0 <= t <= 1. Some algorithms prefer a +// centered representation that is similar to how oriented bounding boxes are +// defined. This representation is C + s*D, where C = (P0 + P1)/2 is the +// center of the segment, D = (P1 - P0)/|P1 - P0| is a unit-length direction +// vector for the segment, and |t| <= e. The value e = |P1 - P0|/2 is the +// extent (or radius or half-length) of the segment. + +namespace gte +{ + +template +class Segment +{ +public: + // Construction and destruction. The default constructor sets p0 to + // (-1,0,...,0) and p1 to (1,0,...,0). NOTE: If you set p0 and p1; + // compute C, D, and e; and then recompute q0 = C-e*D and q1 = C+e*D, + // numerical round-off errors can lead to q0 not exactly equal to p0 + // and q1 not exactly equal to p1. + Segment(); + Segment(Vector const& p0, Vector const& p1); + Segment(std::array, 2> const& inP); + Segment(Vector const& center, Vector const& direction, + Real extent); + + // Manipulation via the centered form. + void SetCenteredForm(Vector const& center, + Vector const& direction, Real extent); + + void GetCenteredForm(Vector& center, Vector& direction, + Real& extent) const; + + // Public member access. + std::array, 2> p; + +public: + // Comparisons to support sorted containers. + bool operator==(Segment const& segment) const; + bool operator!=(Segment const& segment) const; + bool operator< (Segment const& segment) const; + bool operator<=(Segment const& segment) const; + bool operator> (Segment const& segment) const; + bool operator>=(Segment const& segment) const; +}; + +// Template aliases for convenience. +template +using Segment2 = Segment<2, Real>; + +template +using Segment3 = Segment<3, Real>; + + +template +Segment::Segment() +{ + p[1].MakeUnit(0); + p[0] = -p[1]; +} + +template +Segment::Segment(Vector const& p0, + Vector const& p1) +{ + p[0] = p0; + p[1] = p1; +} + +template +Segment::Segment(std::array, 2> const& inP) +{ + p = inP; +} + +template +Segment::Segment(Vector const& center, + Vector const& direction, Real extent) +{ + SetCenteredForm(center, direction, extent); +} + +template +void Segment::SetCenteredForm(Vector const& center, + Vector const& direction, Real extent) +{ + p[0] = center - extent * direction; + p[1] = center + extent * direction; +} + +template +void Segment::GetCenteredForm(Vector& center, + Vector& direction, Real& extent) const +{ + center = ((Real)0.5)*(p[0] + p[1]); + direction = p[1] - p[0]; + extent = ((Real)0.5)*Normalize(direction); +} + +template +bool Segment::operator==(Segment const& segment) const +{ + return p == segment.p; +} + +template +bool Segment::operator!=(Segment const& segment) const +{ + return !operator==(segment); +} + +template +bool Segment::operator<(Segment const& segment) const +{ + return p < segment.p; +} + +template +bool Segment::operator<=(Segment const& segment) const +{ + return operator<(segment) || operator==(segment); +} + +template +bool Segment::operator>(Segment const& segment) const +{ + return !operator<=(segment); +} + +template +bool Segment::operator>=(Segment const& segment) const +{ + return !operator<(segment); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSeparatePoints2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSeparatePoints2.h new file mode 100644 index 000000000000..3235dfef7b9e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSeparatePoints2.h @@ -0,0 +1,221 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Separate two point sets, if possible, by computing a line for which the +// point sets lie on opposite sides. The algorithm computes the convex hull +// of the point sets, then uses the method of separating axes to determine if +// the two convex polygons are disjoint. The ComputeType is for the +// ConvexHull2 class. + +namespace gte +{ + +template +class SeparatePoints2 +{ +public: + // The return value is 'true' if and only if there is a separation. If + // 'true', the returned line is a separating line. The code assumes that + // each point set has at least 3 noncollinear points. + bool operator()(int numPoints0, Vector2 const* points0, + int numPoints1, Vector2 const* points1, + Line2& separatingLine) const; + +private: + int OnSameSide(Vector2 const& lineNormal, Real lineConstant, + int numEdges, int const* edges, Vector2 const* points) const; + + int WhichSide(Vector2 const& lineNormal, Real lineConstant, + int numEdges, int const* edges, Vector2 const* points) const; +}; + + +template +bool SeparatePoints2::operator()(int numPoints0, + Vector2 const* points0, int numPoints1, + Vector2 const* points1, Line2& separatingLine) const +{ + // Construct convex hull of point set 0. + ConvexHull2 ch0; + ch0(numPoints0, points0, (Real)0); + if (ch0.GetDimension() != 2) + { + return false; + } + + // Construct convex hull of point set 1. + ConvexHull2 ch1; + ch1(numPoints1, points1, (Real)0); + if (ch1.GetDimension() != 2) + { + return false; + } + + int numEdges0 = static_cast(ch0.GetHull().size()); + int const* edges0 = &ch0.GetHull()[0]; + int numEdges1 = static_cast(ch1.GetHull().size()); + int const* edges1 = &ch1.GetHull()[0]; + + // Test edges of hull 0 for possible separation of points. + int j0, j1, i0, i1, side0, side1; + Vector2 lineNormal; + Real lineConstant; + for (j1 = 0, j0 = numEdges0 - 1; j1 < numEdges0; j0 = j1++) + { + // Look up edge (assert: i0 != i1 ). + i0 = edges0[j0]; + i1 = edges0[j1]; + + // Compute potential separating line (assert: (xNor,yNor) != (0,0)). + separatingLine.origin = points0[i0]; + separatingLine.direction = points0[i1] - points0[i0]; + Normalize(separatingLine.direction); + lineNormal = Perp(separatingLine.direction); + lineConstant = Dot(lineNormal, separatingLine.origin); + + // Determine whether hull 1 is on same side of line. + side1 = OnSameSide(lineNormal, lineConstant, numEdges1, edges1, + points1); + + if (side1) + { + // Determine on which side of line hull 0 lies. + side0 = WhichSide(lineNormal, lineConstant, numEdges0, + edges0, points0); + + if (side0*side1 <= 0) // Line separates hulls. + { + return true; + } + } + } + + // Test edges of hull 1 for possible separation of points. + for (j1 = 0, j0 = numEdges1 - 1; j1 < numEdges1; j0 = j1++) + { + // Look up edge (assert: i0 != i1 ). + i0 = edges1[j0]; + i1 = edges1[j1]; + + // Compute perpendicular to edge (assert: (xNor,yNor) != (0,0)). + separatingLine.origin = points1[i0]; + separatingLine.direction = points1[i1] - points1[i0]; + Normalize(separatingLine.direction); + lineNormal = Perp(separatingLine.direction); + lineConstant = Dot(lineNormal, separatingLine.origin); + + // Determine whether hull 0 is on same side of line. + side0 = OnSameSide(lineNormal, lineConstant, numEdges0, edges0, + points0); + + if (side0) + { + // Determine on which side of line hull 1 lies. + side1 = WhichSide(lineNormal, lineConstant, numEdges1, + edges1, points1); + + if (side0*side1 <= 0) // Line separates hulls. + { + return true; + } + } + } + + return false; +} + +template +int SeparatePoints2::OnSameSide( + Vector2 const& lineNormal, Real lineConstant, int numEdges, + int const* edges, Vector2 const* points) const +{ + // Test whether all points on same side of line Dot(N,X) = c. + Real c0; + int posSide = 0, negSide = 0; + + for (int i1 = 0, i0 = numEdges - 1; i1 < numEdges; i0 = i1++) + { + c0 = Dot(lineNormal, points[edges[i0]]); + if (c0 > lineConstant) + { + ++posSide; + } + else if (c0 < lineConstant) + { + ++negSide; + } + + if (posSide && negSide) + { + // Line splits point set. + return 0; + } + + c0 = Dot(lineNormal, points[edges[i1]]); + if (c0 > lineConstant) + { + ++posSide; + } + else if (c0 < lineConstant) + { + ++negSide; + } + + if (posSide && negSide) + { + // Line splits point set. + return 0; + } + } + + return (posSide ? +1 : -1); +} + +template +int SeparatePoints2::WhichSide( + Vector2 const& lineNormal, Real lineConstant, int numEdges, + int const* edges, Vector2 const* points) const +{ + // Establish which side of line hull is on. + Real c0; + for (int i1 = 0, i0 = numEdges - 1; i1 < numEdges; i0 = i1++) + { + c0 = Dot(lineNormal, points[edges[i0]]); + if (c0 > lineConstant) + { + // Hull on positive side. + return +1; + } + if (c0 < lineConstant) + { + // Hull on negative side. + return -1; + } + + c0 = Dot(lineNormal, points[edges[i1]]); + if (c0 > lineConstant) + { + // Hull on positive side. + return +1; + } + if (c0 < lineConstant) + { + // Hull on negative side. + return -1; + } + } + + // Hull is effectively collinear. + return 0; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSeparatePoints3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSeparatePoints3.h new file mode 100644 index 000000000000..1d07e575434b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSeparatePoints3.h @@ -0,0 +1,244 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Separate two point sets, if possible, by computing a plane for which the +// point sets lie on opposite sides. The algorithm computes the convex hull +// of the point sets, then uses the method of separating axes to determine if +// the two convex polyhedra are disjoint. The ComputeType is for the +// ConvexHull3 class. + +namespace gte +{ + +template +class SeparatePoints3 +{ +public: + // The return value is 'true' if and only if there is a separation. If + // 'true', the returned plane is a separating plane. The code assumes + // that each point set has at least 4 noncoplanar points. + bool operator()(int numPoints0, Vector3 const* points0, + int numPoints1, Vector3 const* points1, + Plane3& separatingPlane) const; + +private: + int OnSameSide(Plane3 const& plane, int numTriangles, + int const* indices, Vector3 const* points) const; + + int WhichSide(Plane3 const& plane, int numTriangles, + int const* indices, Vector3 const* points) const; +}; + + +template +bool SeparatePoints3::operator()(int numPoints0, + Vector3 const* points0, int numPoints1, + Vector3 const* points1, Plane3& separatingPlane) const +{ + // Construct convex hull of point set 0. + ConvexHull3 ch0; + ch0(numPoints0, points0, (Real)0); + if (ch0.GetDimension() != 3) + { + return false; + } + + // Construct convex hull of point set 1. + ConvexHull3 ch1; + ch1(numPoints1, points1, (Real)0); + if (ch1.GetDimension() != 3) + { + return false; + } + + auto const& hull0 = ch0.GetHullUnordered(); + auto const& hull1 = ch1.GetHullUnordered(); + int numTriangles0 = static_cast(hull0.size()); + int const* indices0 = reinterpret_cast(&hull0[0]); + int numTriangles1 = static_cast(hull1.size()); + int const* indices1 = reinterpret_cast(&hull1[0]); + + // Test faces of hull 0 for possible separation of points. + int i, i0, i1, i2, side0, side1; + Vector3 diff0, diff1; + for (i = 0; i < numTriangles0; ++i) + { + // Look up face (assert: i0 != i1 && i0 != i2 && i1 != i2). + i0 = indices0[3 * i]; + i1 = indices0[3 * i + 1]; + i2 = indices0[3 * i + 2]; + + // Compute potential separating plane (assert: normal != (0,0,0)). + separatingPlane = Plane3({ points0[i0], points0[i1], + points0[i2] }); + + // Determine whether hull 1 is on same side of plane. + side1 = OnSameSide(separatingPlane, numTriangles1, indices1, points1); + + if (side1) + { + // Determine on which side of plane hull 0 lies. + side0 = WhichSide(separatingPlane, numTriangles0, indices0, + points0); + if (side0*side1 <= 0) // Plane separates hulls. + { + return true; + } + } + } + + // Test faces of hull 1 for possible separation of points. + for (i = 0; i < numTriangles1; ++i) + { + // Look up edge (assert: i0 != i1 && i0 != i2 && i1 != i2). + i0 = indices1[3 * i]; + i1 = indices1[3 * i + 1]; + i2 = indices1[3 * i + 2]; + + // Compute perpendicular to face (assert: normal != (0,0,0)). + separatingPlane = Plane3({ points1[i0], points1[i1], + points1[i2] }); + + // Determine whether hull 0 is on same side of plane. + side0 = OnSameSide(separatingPlane, numTriangles0, indices0, points0); + if (side0) + { + // Determine on which side of plane hull 1 lies. + side1 = WhichSide(separatingPlane, numTriangles1, indices1, + points1); + if (side0*side1 <= 0) // Plane separates hulls. + { + return true; + } + } + } + + // Build edge set for hull 0. + std::set> edgeSet0; + for (i = 0; i < numTriangles0; ++i) + { + // Look up face (assert: i0 != i1 && i0 != i2 && i1 != i2). + i0 = indices0[3 * i]; + i1 = indices0[3 * i + 1]; + i2 = indices0[3 * i + 2]; + edgeSet0.insert(std::make_pair(i0, i1)); + edgeSet0.insert(std::make_pair(i0, i2)); + edgeSet0.insert(std::make_pair(i1, i2)); + } + + // Build edge list for hull 1. + std::set> edgeSet1; + for (i = 0; i < numTriangles1; ++i) + { + // Look up face (assert: i0 != i1 && i0 != i2 && i1 != i2). + i0 = indices1[3 * i]; + i1 = indices1[3 * i + 1]; + i2 = indices1[3 * i + 2]; + edgeSet1.insert(std::make_pair(i0, i1)); + edgeSet1.insert(std::make_pair(i0, i2)); + edgeSet1.insert(std::make_pair(i1, i2)); + } + + // Test planes whose normals are cross products of two edges, one from + // each hull. + for (auto const& e0 : edgeSet0) + { + // Get edge. + diff0 = points0[e0.second] - points0[e0.first]; + + for (auto const& e1 : edgeSet1) + { + diff1 = points1[e1.second] - points1[e1.first]; + + // Compute potential separating plane. + separatingPlane.normal = UnitCross(diff0, diff1); + separatingPlane.constant = Dot(separatingPlane.normal, + points0[e0.first]); + + // Determine if hull 0 is on same side of plane. + side0 = OnSameSide(separatingPlane, numTriangles0, indices0, + points0); + side1 = OnSameSide(separatingPlane, numTriangles1, indices1, + points1); + + if (side0*side1 < 0) // Plane separates hulls. + { + return true; + } + } + } + + return false; +} + +template +int SeparatePoints3::OnSameSide(Plane3 const& plane, + int numTriangles, int const* indices, Vector3 const* points) const +{ + // test if all points on same side of plane (nx,ny,nz)*(x,y,z) = c + int posSide = 0, negSide = 0; + + for (int t = 0; t < numTriangles; ++t) + { + for (int i = 0; i < 3; ++i) + { + int v = indices[3 * t + i]; + Real c0 = Dot(plane.normal, points[v]); + if (c0 > plane.constant) + { + ++posSide; + } + else if (c0 < plane.constant) + { + ++negSide; + } + + if (posSide && negSide) + { + // Plane splits point set. + return 0; + } + } + } + + return (posSide ? +1 : -1); +} + +template +int SeparatePoints3::WhichSide(Plane3 const& plane, + int numTriangles, int const* indices, Vector3 const* points) const +{ + // Establish which side of plane hull is on. + for (int t = 0; t < numTriangles; ++t) + { + for (int i = 0; i < 3; ++i) + { + int v = indices[3 * t + i]; + Real c0 = Dot(plane.normal, points[v]); + if (c0 > plane.constant) + { + // Positive side. + return +1; + } + if (c0 < plane.constant) + { + // Negative side. + return -1; + } + } + } + + // Hull is effectively collinear. + return 0; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSinEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSinEstimate.h new file mode 100644 index 000000000000..133e74ee6555 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSinEstimate.h @@ -0,0 +1,161 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Minimax polynomial approximations to sin(x). The polynomial p(x) of +// degree D has only odd-power terms, is required to have linear term x, +// and p(pi/2) = sin(pi/2) = 1. It minimizes the quantity +// maximum{|sin(x) - p(x)| : x in [-pi/2,pi/2]} over all polynomials of +// degree D subject to the constraints mentioned. + +namespace gte +{ + +template +class SinEstimate +{ +public: + // The input constraint is x in [-pi/2,pi/2]. For example, + // float x; // in [-pi/2,pi/2] + // float result = SinEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input x can be any real number. Range reduction is used to + // generate a value y in [-pi/2,pi/2] for which sin(y) = sin(x). + // For example, + // float x; // x any real number + // float result = SinEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<3>, Real x); + inline static Real Evaluate(degree<5>, Real x); + inline static Real Evaluate(degree<7>, Real x); + inline static Real Evaluate(degree<9>, Real x); + inline static Real Evaluate(degree<11>, Real x); + + // Support for range reduction. + inline static Real Reduce(Real x); +}; + + +template +template +inline Real SinEstimate::Degree(Real x) +{ + return Evaluate(degree(), x); +} + +template +template +inline Real SinEstimate::DegreeRR(Real x) +{ + return Degree(Reduce(x)); +} + +template +inline Real SinEstimate::Evaluate(degree<3>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_SIN_DEG3_C1; + poly = (Real)GTE_C_SIN_DEG3_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real SinEstimate::Evaluate(degree<5>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_SIN_DEG5_C2; + poly = (Real)GTE_C_SIN_DEG5_C1 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG5_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real SinEstimate::Evaluate(degree<7>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_SIN_DEG7_C3; + poly = (Real)GTE_C_SIN_DEG7_C2 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG7_C1 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG7_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real SinEstimate::Evaluate(degree<9>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_SIN_DEG9_C4; + poly = (Real)GTE_C_SIN_DEG9_C3 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG9_C2 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG9_C1 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG9_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real SinEstimate::Evaluate(degree<11>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_SIN_DEG11_C5; + poly = (Real)GTE_C_SIN_DEG11_C4 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG11_C3 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG11_C2 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG11_C1 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG11_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real SinEstimate::Reduce(Real x) +{ + // Map x to y in [-pi,pi], x = 2*pi*quotient + remainder. + Real quotient = (Real)GTE_C_INV_TWO_PI * x; + if (x >= (Real)0) + { + quotient = (Real)((int)(quotient + (Real)0.5)); + } + else + { + quotient = (Real)((int)(quotient - (Real)0.5)); + } + Real y = x - (Real)GTE_C_TWO_PI * quotient; + + // Map y to [-pi/2,pi/2] with sin(y) = sin(x). + if (y > (Real)GTE_C_HALF_PI) + { + y = (Real)GTE_C_PI - y; + } + else if (y < (Real)-GTE_C_HALF_PI) + { + y = (Real)-GTE_C_PI - y; + } + return y; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSingularValueDecomposition.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSingularValueDecomposition.h new file mode 100644 index 000000000000..726eba345137 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSingularValueDecomposition.h @@ -0,0 +1,1147 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The SingularValueDecomposition class is an implementation of Algorithm +// 8.3.2 (The SVD Algorithm) described in "Matrix Computations, 2nd +// edition" by G. H. Golub and Charles F. Van Loan, The Johns Hopkins +// Press, Baltimore MD, Fourth Printing 1993. Algorithm 5.4.2 (Householder +// Bidiagonalization) is used to reduce A to bidiagonal B. Algorithm 8.3.1 +// (Golub-Kahan SVD Step) is used for the iterative reduction from bidiagonal +// to diagonal. If A is the original matrix, S is the matrix whose diagonal +// entries are the singular values, and U and V are corresponding matrices, +// then theoretically U^T*A*V = S. Numerically, we have errors +// E = U^T*A*V - S. Algorithm 8.3.2 mentions that one expects |E| is +// approximately u*|A|, where |M| denotes the Frobenius norm of M and where +// u is the unit roundoff for the floating-point arithmetic: 2^{-23} for +// 'float', which is FLT_EPSILON = 1.192092896e-7f, and 2^{-52} for'double', +// which is DBL_EPSILON = 2.2204460492503131e-16. +// +// The condition |a(i,i+1)| <= epsilon*(|a(i,i) + a(i+1,i+1)|) used to +// determine when the reduction decouples to smaller problems is implemented +// as: sum = |a(i,i)| + |a(i+1,i+1)|; sum + |a(i,i+1)| == sum. The idea is +// that the superdiagonal term is small relative to its diagonal neighbors, +// and so it is effectively zero. The unit tests have shown that this +// interpretation of decoupling is effective. +// +// The condition |a(i,i)| <= epsilon*|B| used to determine when the +// reduction decouples (with a zero singular value) is implemented using +// the Frobenius norm of B and epsilon = multiplier*u, where for now the +// multiplier is hard-coded in Solve(...) as 8. +// +// The authors suggest that once you have the bidiagonal matrix, a practical +// implementation will store the diagonal and superdiagonal entries in linear +// arrays, ignoring the theoretically zero values not in the 2-band. This is +// good for cache coherence, and we have used the suggestion. The essential +// parts of the Householder u-vectors are stored in the lower-triangular +// portion of the matrix and the essential parts of the Householder v-vectors +// are stored in the upper-triangular portion of the matrix. To avoid having +// to recompute 2/Dot(u,u) and 2/Dot(v,v) when constructing orthogonal U and +// V, we store these quantities in additional memory during bidiagonalization. +// +// For matrices with randomly generated values in [0,1], the unit tests +// produce the following information for N-by-N matrices. +// +// N |A| |E| |E|/|A| iterations +// ------------------------------------------- +// 2 1.4831 4.1540e-16 2.8007e-16 1 +// 3 2.1065 3.5024e-16 1.6626e-16 4 +// 4 2.4979 7.4605e-16 2.9867e-16 6 +// 5 3.6591 1.8305e-15 5.0025e-16 9 +// 6 4.0572 2.0571e-15 5.0702e-16 10 +// 7 4.7745 2.9057e-15 6.0859e-16 12 +// 8 5.1964 2.7958e-15 5.3803e-16 13 +// 9 5.7599 3.3128e-15 5.7514e-16 16 +// 10 6.2700 3.7209e-15 5.9344e-16 16 +// 11 6.8220 5.0580e-15 7.4142e-16 18 +// 12 7.4540 5.2493e-15 7.0422e-16 21 +// 13 8.1225 5.6043e-15 6.8997e-16 24 +// 14 8.5883 5.8553e-15 6.8177e-16 26 +// 15 9.1337 6.9663e-15 7.6270e-16 27 +// 16 9.7884 9.1358e-15 9.3333e-16 29 +// 17 10.2407 8.2715e-15 8.0771e-16 34 +// 18 10.7147 8.9748e-15 8.3761e-16 33 +// 19 11.1887 1.0094e-14 9.0220e-16 32 +// 20 11.7739 9.7000e-15 8.2386e-16 35 +// 21 12.2822 1.1217e-14 9.1329e-16 36 +// 22 12.7649 1.1071e-14 8.6732e-16 37 +// 23 13.3366 1.1271e-14 8.4513e-16 41 +// 24 13.8505 1.2806e-14 9.2460e-16 43 +// 25 14.4332 1.3081e-14 9.0637e-16 43 +// 26 14.9301 1.4882e-14 9.9680e-16 46 +// 27 15.5214 1.5571e-14 1.0032e-15 48 +// 28 16.1029 1.7553e-14 1.0900e-15 49 +// 29 16.6407 1.6219e-14 9.7467e-16 53 +// 30 17.1891 1.8560e-14 1.0797e-15 55 +// 31 17.7773 1.8522e-14 1.0419e-15 56 +// +// The singularvalues and |E|/|A| values were compared to those generated by +// Mathematica Version 9.0; Wolfram Research, Inc., Champaign IL, 2012. +// The results were all comparable with singular values agreeing to a large +// number of decimal places. +// +// Timing on an Intel (R) Core (TM) i7-3930K CPU @ 3.20 GHZ (in seconds) +// for NxN matrices: +// +// N |E|/|A| iters bidiag QR U-and-V comperr +// ------------------------------------------------------- +// 512 3.8632e-15 848 0.341 0.016 1.844 2.203 +// 1024 5.6456e-15 1654 4.279 0.032 18.765 20.844 +// 2048 7.5499e-15 3250 40.421 0.141 186.607 213.216 +// +// where iters is the number of QR steps taken, bidiag is the computation +// of the Householder reflection vectors, U-and-V is the composition of +// Householder reflections and Givens rotations to obtain the orthogonal +// matrices of the decomposigion, and comperr is the computation E = +// U^T*A*V - S. + +namespace gte +{ + +template +class SingularValueDecomposition +{ +public: + // The solver processes MxN symmetric matrices, where M >= N > 1 + // ('numRows' is M and 'numCols' is N) and the matrix is stored in + // row-major order. The maximum number of iterations ('maxIterations') + // must be specified for the reduction of a bidiagonal matrix to a + // diagonal matrix. The goal is to compute MxM orthogonal U, NxN + // orthogonal V, and MxN matrix S for which U^T*A*V = S. The only + // nonzero entries of S are on the diagonal; the diagonal entries are + // the singular values of the original matrix. + SingularValueDecomposition(int numRows, int numCols, + unsigned int maxIterations); + + // A copy of the MxN input is made internally. The order of the singular + // values is specified by sortType: -1 (decreasing), 0 (no sorting), or +1 + // (increasing). When sorted, the columns of the orthogonal matrices + // are ordered accordingly. The return value is the number of iterations + // consumed when convergence occurred, 0xFFFFFFFF when convergence did not + // occur or 0 when N <= 1 or M < N was passed to the constructor. + unsigned int Solve(Real const* input, int sortType); + + // Get the singular values of the matrix passed to Solve(...). The input + // 'singularValues' must have N elements. + void GetSingularValues(Real* singularValues) const; + + // Accumulate the Householder reflections, the Givens rotations, and the + // diagonal fix-up matrix to compute the orthogonal matrices U and V for + // which U^T*A*V = S. The input uMatrix must be MxM and the input vMatrix + // must be NxN, both stored in row-major order. + void GetU(Real* uMatrix) const; + void GetV(Real* vMatrix) const; + + // Compute a single column of U or V. The reflections and rotations are + // applied incrementally. This is useful when you want only a small + // number of the singular values or vectors. + void GetUColumn(int index, Real* uColumn) const; + void GetVColumn(int index, Real* vColumn) const; + Real GetSingularValue(int index) const; + +private: + // Bidiagonalize using Householder reflections. On input, mMatrix is a + // copy of the input matrix and has one extra row. On output, the + // diagonal and superdiagonal contain the bidiagonalized results. The + // lower-triangular portion stores the essential parts of the Householder + // u vectors (the elements of u after the leading 1-valued component) and + // the upper-triangular portion stores the essential parts of the + // Householder v vectors. To avoid recomputing 2/Dot(u,u) and 2/Dot(v,v), + // these quantities are stored in mTwoInvUTU and mTwoInvVTV. + void Bidiagonalize(); + + // A helper for generating Givens rotation sine and cosine robustly. + void GetSinCos(Real u, Real v, Real& cs, Real& sn); + + // Test for (effectively) zero-valued diagonal entries (through all but + // the last). For each such entry, the B matrix decouples. Perform + // that decoupling. If there are no zero-valued entries, then the + // Golub-Kahan step must be performed. + bool DiagonalEntriesNonzero(int imin, int imax, Real threshold); + + // This is Algorithm 8.3.1 in "Matrix Computations, 2nd edition" by + // G. H. Golub and C. F. Van Loan. + void DoGolubKahanStep(int imin, int imax); + + // The diagonal entries are not guaranteed to be nonnegative during the + // construction. After convergence to a diagonal matrix S, test for + // negative entries and build a diagonal matrix that reverses the sign + // on the S-entry. + void EnsureNonnegativeDiagonal(); + + // Sort the singular values and compute the corresponding permutation of + // the indices of the array storing the singular values. The permutation + // is used for reordering the singular values and the corresponding + // columns of the orthogonal matrix in the calls to GetSingularValues(...) + // and GetOrthogonalMatrices(...). + void ComputePermutation(int sortType); + + // The number rows and columns of the matrices to be processed. + int mNumRows, mNumCols; + + // The maximum number of iterations for reducing the bidiagonal matrix + // to a diagonal matrix. + unsigned int mMaxIterations; + + // The internal copy of a matrix passed to the solver. See the comments + // about function Bidiagonalize() about what is stored in the matrix. + std::vector mMatrix; // MxN elements + + // After the initial bidiagonalization by Householder reflections, we no + // longer need the full mMatrix. Copy the diagonal and superdiagonal + // entries to linear arrays in order to be cache friendly. + std::vector mDiagonal; // N elements + std::vector mSuperdiagonal; // N-1 elements + + // The Givens rotations used to reduce the initial bidiagonal matrix to + // a diagonal matrix. A rotation is the identity with the following + // replacement entries: R(index0,index0) = cs, R(index0,index1) = sn, + // R(index1,index0) = -sn, and R(index1,index1) = cs. If N is the + // number of matrix columns and K is the maximum number of iterations, the + // maximum number of right or left Givens rotations is K*(N-1). The + // maximum amount of memory is allocated to store these. However, we also + // potentially need left rotations to decouple the matrix when a diagonal + // terms are zero. Worst case is a number of matrices quadratic in N, so + // for now we just use std::vector whose initial capacity is + // K*(N-1). + struct GivensRotation + { + GivensRotation(); + GivensRotation(int inIndex0, int inIndex1, Real inCs, Real inSn); + int index0, index1; + Real cs, sn; + }; + + std::vector mRGivens; + std::vector mLGivens; + + // The diagonal matrix that is used to convert S-entries to nonnegative. + std::vector mFixupDiagonal; // N elements + + // When sorting is requested, the permutation associated with the sort is + // stored in mPermutation. When sorting is not requested, mPermutation[0] + // is set to -1. mVisited is used for finding cycles in the permutation. + std::vector mPermutation; // N elements + mutable std::vector mVisited; // N elements + + // Temporary storage to compute Householder reflections and to support + // sorting of columns of the orthogonal matrices. + std::vector mTwoInvUTU; // N elements + std::vector mTwoInvVTV; // N-2 elements + mutable std::vector mUVector; // M elements + mutable std::vector mVVector; // N elements + mutable std::vector mWVector; // max(M,N) elements +}; + + +template +SingularValueDecomposition::SingularValueDecomposition(int numRows, + int numCols, unsigned int maxIterations) + : + mNumRows(0), + mNumCols(0), + mMaxIterations(0) +{ + if (numCols > 1 && numRows >= numCols && maxIterations > 0) + { + mNumRows = numRows; + mNumCols = numCols; + mMaxIterations = maxIterations; + mMatrix.resize(numRows * numCols); + mDiagonal.resize(numCols); + mSuperdiagonal.resize(numCols - 1); + mRGivens.reserve(maxIterations*(numCols - 1)); + mLGivens.reserve(maxIterations*(numCols - 1)); + mFixupDiagonal.resize(numCols); + mPermutation.resize(numCols); + mVisited.resize(numCols); + mTwoInvUTU.resize(numCols); + mTwoInvVTV.resize(numCols - 2); + mUVector.resize(numRows); + mVVector.resize(numCols); + mWVector.resize(numRows); + } +} + +template +unsigned int SingularValueDecomposition::Solve(Real const* input, + int sortType) +{ + if (mNumRows > 0) + { + int numElements = mNumRows * mNumCols; + std::copy(input, input + numElements, mMatrix.begin()); + Bidiagonalize(); + + // Compute 'threshold = multiplier*epsilon*|B|' as the threshold for + // diagonal entries effectively zero; that is, |d| <= |threshold| + // implies that d is (effectively) zero. TODO: Allow the caller to + // pass 'multiplier' to the constructor. + // + // We will use the L2-norm |B|, which is the length of the elements + // of B treated as an NM-tuple. The following code avoids overflow + // when accumulating the squares of the elements when those elements + // are large. + Real maxAbsComp = std::abs(input[0]); + for (int i = 1; i < numElements; ++i) + { + Real absComp = std::abs(input[i]); + if (absComp > maxAbsComp) + { + maxAbsComp = absComp; + } + } + + Real norm = (Real)0; + if (maxAbsComp > (Real)0) + { + Real invMaxAbsComp = ((Real)1) / maxAbsComp; + for (int i = 0; i < numElements; ++i) + { + Real ratio = input[i] * invMaxAbsComp; + norm += ratio * ratio; + } + norm = maxAbsComp * std::sqrt(norm); + } + + Real const multiplier = (Real)8; // TODO: Expose to caller. + Real const epsilon = std::numeric_limits::epsilon(); + Real const threshold = multiplier * epsilon * norm; + + mRGivens.clear(); + mLGivens.clear(); + for (unsigned int j = 0; j < mMaxIterations; ++j) + { + int imin = -1, imax = -1; + for (int i = mNumCols - 2; i >= 0; --i) + { + // When a01 is much smaller than its diagonal neighbors, it is + // effectively zero. + Real a00 = mDiagonal[i]; + Real a01 = mSuperdiagonal[i]; + Real a11 = mDiagonal[i + 1]; + Real sum = std::abs(a00) + std::abs(a11); + if (sum + std::abs(a01) != sum) + { + if (imax == -1) + { + imax = i; + } + imin = i; + } + else + { + // The superdiagonal term is effectively zero compared to + // the neighboring diagonal terms. + if (imin >= 0) + { + break; + } + } + } + + if (imax == -1) + { + // The algorithm has converged. + EnsureNonnegativeDiagonal(); + ComputePermutation(sortType); + return j; + } + + // We need to test diagonal entries of B for zero. For each zero + // diagonal entry, zero the superdiagonal. + if (DiagonalEntriesNonzero(imin, imax, threshold)) + { + // Process the lower-right-most unreduced bidiagonal block. + DoGolubKahanStep(imin, imax); + } + } + return 0xFFFFFFFF; + } + else + { + return 0; + } +} + +template +void SingularValueDecomposition::GetSingularValues( + Real* singularValues) const +{ + if (singularValues && mNumCols > 0) + { + if (mPermutation[0] >= 0) + { + // Sorting was requested. + for (int i = 0; i < mNumCols; ++i) + { + int p = mPermutation[i]; + singularValues[i] = mDiagonal[p]; + } + } + else + { + // Sorting was not requested. + size_t numBytes = mNumCols*sizeof(Real); + Memcpy(singularValues, &mDiagonal[0], numBytes); + } + } +} + +template +void SingularValueDecomposition::GetU(Real* uMatrix) const +{ + if (!uMatrix || mNumCols == 0) + { + // Invalid input or the constructor failed. + return; + } + + // Start with the identity matrix. + std::fill(uMatrix, uMatrix + mNumRows*mNumRows, (Real)0); + for (int d = 0; d < mNumRows; ++d) + { + uMatrix[d + mNumRows*d] = (Real)1; + } + + // Multiply the Householder reflections using backward accumulation. + int r, c; + for (int i0 = mNumCols - 1, i1 = i0 + 1; i0 >= 0; --i0, --i1) + { + // Copy the u vector and 2/Dot(u,u) from the matrix. + Real twoinvudu = mTwoInvUTU[i0]; + Real const* column = &mMatrix[i0]; + mUVector[i0] = (Real)1; + for (r = i1; r < mNumRows; ++r) + { + mUVector[r] = column[mNumCols*r]; + } + + // Compute the w vector. + mWVector[i0] = twoinvudu; + for (r = i1; r < mNumRows; ++r) + { + mWVector[r] = (Real)0; + for (c = i1; c < mNumRows; ++c) + { + mWVector[r] += mUVector[c] * uMatrix[r + mNumRows*c]; + } + mWVector[r] *= twoinvudu; + } + + // Update the matrix, U <- U - u*w^T. + for (r = i0; r < mNumRows; ++r) + { + for (c = i0; c < mNumRows; ++c) + { + uMatrix[c + mNumRows*r] -= mUVector[r] * mWVector[c]; + } + } + } + + // Multiply the Givens rotations. + for (auto const& givens : mLGivens) + { + int j0 = givens.index0; + int j1 = givens.index1; + for (r = 0; r < mNumRows; ++r, j0 += mNumRows, j1 += mNumRows) + { + Real& q0 = uMatrix[j0]; + Real& q1 = uMatrix[j1]; + Real prd0 = givens.cs * q0 - givens.sn * q1; + Real prd1 = givens.sn * q0 + givens.cs * q1; + q0 = prd0; + q1 = prd1; + } + } + + if (mPermutation[0] >= 0) + { + // Sorting was requested. + std::fill(mVisited.begin(), mVisited.end(), 0); + for (c = 0; c < mNumCols; ++c) + { + if (mVisited[c] == 0 && mPermutation[c] != c) + { + // The item starts a cycle with 2 or more elements. + int start = c, current = c, next; + for (r = 0; r < mNumRows; ++r) + { + mWVector[r] = uMatrix[c + mNumRows*r]; + } + while ((next = mPermutation[current]) != start) + { + mVisited[current] = 1; + for (r = 0; r < mNumRows; ++r) + { + uMatrix[current + mNumRows*r] = + uMatrix[next + mNumRows*r]; + } + current = next; + } + mVisited[current] = 1; + for (r = 0; r < mNumRows; ++r) + { + uMatrix[current + mNumRows*r] = mWVector[r]; + } + } + } + } +} + +template +void SingularValueDecomposition::GetV(Real* vMatrix) const +{ + if (!vMatrix || mNumCols == 0) + { + // Invalid input or the constructor failed. + return; + } + + // Start with the identity matrix. + std::fill(vMatrix, vMatrix + mNumCols*mNumCols, (Real)0); + for (int d = 0; d < mNumCols; ++d) + { + vMatrix[d + mNumCols*d] = (Real)1; + } + + // Multiply the Householder reflections using backward accumulation. + int i0 = mNumCols - 3; + int i1 = i0 + 1; + int i2 = i0 + 2; + int r, c; + for (/**/; i0 >= 0; --i0, --i1, --i2) + { + // Copy the v vector and 2/Dot(v,v) from the matrix. + Real twoinvvdv = mTwoInvVTV[i0]; + Real const* row = &mMatrix[mNumCols*i0]; + mVVector[i1] = (Real)1; + for (r = i2; r < mNumCols; ++r) + { + mVVector[r] = row[r]; + } + + // Compute the w vector. + mWVector[i1] = twoinvvdv; + for (r = i2; r < mNumCols; ++r) + { + mWVector[r] = (Real)0; + for (c = i2; c < mNumCols; ++c) + { + mWVector[r] += mVVector[c] * vMatrix[r + mNumCols*c]; + } + mWVector[r] *= twoinvvdv; + } + + // Update the matrix, V <- V - v*w^T. + for (r = i1; r < mNumCols; ++r) + { + for (c = i1; c < mNumCols; ++c) + { + vMatrix[c + mNumCols*r] -= mVVector[r] * mWVector[c]; + } + } + } + + // Multiply the Givens rotations. + for (auto const& givens : mRGivens) + { + int j0 = givens.index0; + int j1 = givens.index1; + for (c = 0; c < mNumCols; ++c, j0 += mNumCols, j1 += mNumCols) + { + Real& q0 = vMatrix[j0]; + Real& q1 = vMatrix[j1]; + Real prd0 = givens.cs * q0 - givens.sn * q1; + Real prd1 = givens.sn * q0 + givens.cs * q1; + q0 = prd0; + q1 = prd1; + } + } + + // Fix-up the diagonal. + for (r = 0; r < mNumCols; ++r) + { + for (c = 0; c < mNumCols; ++c) + { + vMatrix[c + mNumCols*r] *= mFixupDiagonal[c]; + } + } + + if (mPermutation[0] >= 0) + { + // Sorting was requested. + std::fill(mVisited.begin(), mVisited.end(), 0); + for (c = 0; c < mNumCols; ++c) + { + if (mVisited[c] == 0 && mPermutation[c] != c) + { + // The item starts a cycle with 2 or more elements. + int start = c, current = c, next; + for (r = 0; r < mNumCols; ++r) + { + mWVector[r] = vMatrix[c + mNumCols*r]; + } + while ((next = mPermutation[current]) != start) + { + mVisited[current] = 1; + for (r = 0; r < mNumCols; ++r) + { + vMatrix[current + mNumCols*r] = + vMatrix[next + mNumCols*r]; + } + current = next; + } + mVisited[current] = 1; + for (r = 0; r < mNumCols; ++r) + { + vMatrix[current + mNumCols*r] = mWVector[r]; + } + } + } + } +} + +template +void SingularValueDecomposition::GetUColumn(int index, + Real* uColumn) const +{ + if (0 <= index && index < mNumRows) + { + // y = H*x, then x and y are swapped for the next H + Real* x = uColumn; + Real* y = &mWVector[0]; + + // Start with the Euclidean basis vector. + memset(x, 0, mNumRows * sizeof(Real)); + if (index < mNumCols && mPermutation[0] >= 0) + { + // Sorting was requested. + x[mPermutation[index]] = (Real)1; + } + else + { + x[index] = (Real)1; + } + + // Apply the Givens rotations. + for (auto const& givens : gte::reverse(mLGivens)) + { + Real& xr0 = x[givens.index0]; + Real& xr1 = x[givens.index1]; + Real tmp0 = givens.cs * xr0 + givens.sn * xr1; + Real tmp1 = -givens.sn * xr0 + givens.cs * xr1; + xr0 = tmp0; + xr1 = tmp1; + } + + // Apply the Householder reflections. + for (int c = mNumCols - 1; c >= 0; --c) + { + // Get the Householder vector u. + int r; + for (r = 0; r < c; ++r) + { + y[r] = x[r]; + } + + // Compute s = Dot(x,u) * 2/u^T*u. + Real s = x[r]; // r = c, u[r] = 1 + for (int j = r + 1; j < mNumRows; ++j) + { + s += x[j] * mMatrix[c + mNumCols * j]; + } + s *= mTwoInvUTU[c]; + + // r = c, y[r] = x[r]*u[r] - s = x[r] - s because u[r] = 1 + y[r] = x[r] - s; + + // Compute the remaining components of y. + for (++r; r < mNumRows; ++r) + { + y[r] = x[r] - s * mMatrix[c + mNumCols * r]; + } + + std::swap(x, y); + } + + // The final product is stored in x. + if (x != uColumn) + { + size_t numBytes = mNumRows*sizeof(Real); + Memcpy(uColumn, x, numBytes); + } + } +} + +template +void SingularValueDecomposition::GetVColumn(int index, + Real* vColumn) const +{ + if (0 <= index && index < mNumCols) + { + // y = H*x, then x and y are swapped for the next H + Real* x = vColumn; + Real* y = &mWVector[0]; + + // Start with the Euclidean basis vector. + memset(x, 0, mNumCols * sizeof(Real)); + if (mPermutation[0] >= 0) + { + // Sorting was requested. + int p = mPermutation[index]; + x[p] = mFixupDiagonal[p]; + } + else + { + x[index] = mFixupDiagonal[index]; + } + + // Apply the Givens rotations. + for (auto const& givens : gte::reverse(mRGivens)) + { + Real& xr0 = x[givens.index0]; + Real& xr1 = x[givens.index1]; + Real tmp0 = givens.cs * xr0 + givens.sn * xr1; + Real tmp1 = -givens.sn * xr0 + givens.cs * xr1; + xr0 = tmp0; + xr1 = tmp1; + } + + // Apply the Householder reflections. + for (int r = mNumCols - 3; r >= 0; --r) + { + // Get the Householder vector v. + int c; + for (c = 0; c < r + 1; ++c) + { + y[c] = x[c]; + } + + // Compute s = Dot(x,v) * 2/v^T*v. + Real s = x[c]; // c = r+1, v[c] = 1 + for (int j = c + 1; j < mNumCols; ++j) + { + s += x[j] * mMatrix[j + mNumCols * r]; + } + s *= mTwoInvVTV[r]; + + // c = r+1, y[c] = x[c]*v[c] - s = x[c] - s because v[c] = 1 + y[c] = x[c] - s; + + // Compute the remaining components of y. + for (++c; c < mNumCols; ++c) + { + y[c] = x[c] - s * mMatrix[c + mNumCols * r]; + } + + std::swap(x, y); + } + + // The final product is stored in x. + if (x != vColumn) + { + size_t numBytes = mNumCols*sizeof(Real); + Memcpy(vColumn, x, numBytes); + } + } +} + +template +Real SingularValueDecomposition::GetSingularValue(int index) const +{ + if (0 <= index && index < mNumCols) + { + if (mPermutation[0] >= 0) + { + // Sorting was requested. + return mDiagonal[mPermutation[index]]; + } + else + { + // Sorting was not requested. + return mDiagonal[index]; + } + } + else + { + return (Real)0; + } +} + +template +void SingularValueDecomposition::Bidiagonalize() +{ + int r, c; + for (int i = 0, ip1 = 1; i < mNumCols; ++i, ++ip1) + { + // Compute the U-Householder vector. + Real length = (Real)0; + for (r = i; r < mNumRows; ++r) + { + Real ur = mMatrix[i + mNumCols*r]; + mUVector[r] = ur; + length += ur * ur; + } + Real udu = (Real)1; + length = std::sqrt(length); + if (length >(Real)0) + { + Real& u1 = mUVector[i]; + Real sgn = (u1 >= (Real)0 ? (Real)1 : (Real)-1); + Real invDenom = ((Real)1) / (u1 + sgn * length); + u1 = (Real)1; + for (r = ip1; r < mNumRows; ++r) + { + Real& ur = mUVector[r]; + ur *= invDenom; + udu += ur * ur; + } + } + + // Compute the rank-1 offset u*w^T. + Real invudu = (Real)1 / udu; + Real twoinvudu = invudu * (Real)2; + for (c = i; c < mNumCols; ++c) + { + mWVector[c] = (Real)0; + for (r = i; r < mNumRows; ++r) + { + mWVector[c] += mMatrix[c + mNumCols*r] * mUVector[r]; + } + mWVector[c] *= twoinvudu; + } + + // Update the input matrix. + for (r = i; r < mNumRows; ++r) + { + for (c = i; c < mNumCols; ++c) + { + mMatrix[c + mNumCols*r] -= mUVector[r] * mWVector[c]; + } + } + + if (i < mNumCols - 2) + { + // Compute the V-Householder vectors. + length = (Real)0; + for (c = ip1; c < mNumCols; ++c) + { + Real vc = mMatrix[c + mNumCols*i]; + mVVector[c] = vc; + length += vc * vc; + } + Real vdv = (Real)1; + length = std::sqrt(length); + if (length >(Real)0) + { + Real& v1 = mVVector[ip1]; + Real sgn = (v1 >= (Real)0 ? (Real)1 : (Real)-1); + Real invDenom = ((Real)1) / (v1 + sgn * length); + v1 = (Real)1; + for (c = ip1 + 1; c < mNumCols; ++c) + { + Real& vc = mVVector[c]; + vc *= invDenom; + vdv += vc * vc; + } + } + + // Compute the rank-1 offset w*v^T. + Real invvdv = (Real)1 / vdv; + Real twoinvvdv = invvdv * (Real)2; + for (r = i; r < mNumRows; ++r) + { + mWVector[r] = (Real)0; + for (c = ip1; c < mNumCols; ++c) + { + mWVector[r] += mMatrix[c + mNumCols*r] * mVVector[c]; + } + mWVector[r] *= twoinvvdv; + } + + // Update the input matrix. + for (r = i; r < mNumRows; ++r) + { + for (c = ip1; c < mNumCols; ++c) + { + mMatrix[c + mNumCols*r] -= mWVector[r] * mVVector[c]; + } + } + + mTwoInvVTV[i] = twoinvvdv; + for (c = i + 2; c < mNumCols; ++c) + { + mMatrix[c + mNumCols*i] = mVVector[c]; + } + } + + mTwoInvUTU[i] = twoinvudu; + for (r = ip1; r < mNumRows; ++r) + { + mMatrix[i + mNumCols*r] = mUVector[r]; + } + } + + // Copy the diagonal and subdiagonal for cache coherence in the + // Golub-Kahan iterations. + int k, ksup = mNumCols - 1, index = 0, delta = mNumCols + 1; + for (k = 0; k < ksup; ++k, index += delta) + { + mDiagonal[k] = mMatrix[index]; + mSuperdiagonal[k] = mMatrix[index + 1]; + } + mDiagonal[k] = mMatrix[index]; +} + +template +void SingularValueDecomposition::GetSinCos(Real x, Real y, Real& cs, + Real& sn) +{ + // Solves sn*x + cs*y = 0 robustly. + Real tau; + if (y != (Real)0) + { + if (std::abs(y) > std::abs(x)) + { + tau = -x / y; + sn = ((Real)1) / std::sqrt(((Real)1) + tau*tau); + cs = sn * tau; + } + else + { + tau = -y / x; + cs = ((Real)1) / std::sqrt(((Real)1) + tau*tau); + sn = cs * tau; + } + } + else + { + cs = (Real)1; + sn = (Real)0; + } +} + +template +bool SingularValueDecomposition::DiagonalEntriesNonzero(int imin, + int imax, Real threshold) +{ + for (int i = imin; i <= imax; ++i) + { + if (std::abs(mDiagonal[i]) <= threshold) + { + // Use planar rotations to case the superdiagonal entry out of + // the matrix, thus producing a row of zeros. + Real x, z, cs, sn; + Real y = mSuperdiagonal[i]; + mSuperdiagonal[i] = (Real)0; + for (int j = i + 1; j <= imax + 1; ++j) + { + x = mDiagonal[j]; + GetSinCos(x, y, cs, sn); + mLGivens.push_back(GivensRotation(i, j, cs, sn)); + mDiagonal[j] = cs*x - sn*y; + if (j <= imax) + { + z = mSuperdiagonal[j]; + mSuperdiagonal[j] = cs*z; + y = sn*z; + } + } + return false; + } + } + return true; +} + +template +void SingularValueDecomposition::DoGolubKahanStep(int imin, int imax) +{ + // The implicit shift. Compute the eigenvalue u of the lower-right 2x2 + // block of A = B^T*B that is closer to b11. + Real f0 = (imax >= (Real)1 ? mSuperdiagonal[imax - 1] : (Real)0); + Real d1 = mDiagonal[imax]; + Real f1 = mSuperdiagonal[imax]; + Real d2 = mDiagonal[imax + 1]; + Real a00 = d1*d1 + f0*f0; + Real a01 = d1*f1; + Real a11 = d2*d2 + f1*f1; + Real dif = (a00 - a11) * (Real)0.5; + Real sgn = (dif >= (Real)0 ? (Real)1 : (Real)-1); + Real a01sqr = a01 * a01; + Real u = a11 - a01sqr / (dif + sgn * std::sqrt(dif*dif + a01sqr)); + Real x = mDiagonal[imin] * mDiagonal[imin] - u; + Real y = mDiagonal[imin] * mSuperdiagonal[imin]; + + Real a12, a21, a22, a23, cs, sn; + Real a02 = (Real)0; + int i0 = imin - 1, i1 = imin, i2 = imin + 1; + for (/**/; i1 <= imax; ++i0, ++i1, ++i2) + { + // Compute the Givens rotation G and save it for use in computing + // V in U^T*A*V = S. + GetSinCos(x, y, cs, sn); + mRGivens.push_back(GivensRotation(i1, i2, cs, sn)); + + // Update B0 = B*G. + if (i1 > imin) + { + mSuperdiagonal[i0] = cs*mSuperdiagonal[i0] - sn*a02; + } + + a11 = mDiagonal[i1]; + a12 = mSuperdiagonal[i1]; + a22 = mDiagonal[i2]; + mDiagonal[i1] = cs*a11 - sn*a12; + mSuperdiagonal[i1] = sn*a11 + cs*a12; + mDiagonal[i2] = cs*a22; + a21 = -sn*a22; + + // Update the parameters for the next Givens rotations. + x = mDiagonal[i1]; + y = a21; + + // Compute the Givens rotation G and save it for use in computing + // U in U^T*A*V = S. + GetSinCos(x, y, cs, sn); + mLGivens.push_back(GivensRotation(i1, i2, cs, sn)); + + // Update B1 = G^T*B0. + a11 = mDiagonal[i1]; + a12 = mSuperdiagonal[i1]; + a22 = mDiagonal[i2]; + mDiagonal[i1] = cs*a11 - sn*a21; + mSuperdiagonal[i1] = cs*a12 - sn*a22; + mDiagonal[i2] = sn*a12 + cs*a22; + + if (i1 < imax) + { + a23 = mSuperdiagonal[i2]; + a02 = -sn*a23; + mSuperdiagonal[i2] = cs*a23; + + // Update the parameters for the next Givens rotations. + x = mSuperdiagonal[i1]; + y = a02; + } + } +} + +template +void SingularValueDecomposition::EnsureNonnegativeDiagonal() +{ + for (int i = 0; i < mNumCols; ++i) + { + if (mDiagonal[i] >= (Real)0) + { + mFixupDiagonal[i] = (Real)1; + } + else + { + mDiagonal[i] = -mDiagonal[i]; + mFixupDiagonal[i] = (Real)-1; + } + } +} + +template +void SingularValueDecomposition::ComputePermutation(int sortType) +{ + if (sortType == 0) + { + // Set a flag for GetSingularValues() and GetOrthogonalMatrices() to + // know that sorted output was not requested. + mPermutation[0] = -1; + return; + } + + // Compute the permutation induced by sorting. Initially, we start with + // the identity permutation I = (0,1,...,N-1). + struct SortItem + { + Real singularValue; + int index; + }; + + std::vector items(mNumCols); + int i; + for (i = 0; i < mNumCols; ++i) + { + items[i].singularValue = mDiagonal[i]; + items[i].index = i; + } + + if (sortType > 0) + { + std::sort(items.begin(), items.end(), + [](SortItem const& item0, SortItem const& item1) + { + return item0.singularValue < item1.singularValue; + } + ); + } + else + { + std::sort(items.begin(), items.end(), + [](SortItem const& item0, SortItem const& item1) + { + return item0.singularValue > item1.singularValue; + } + ); + } + + i = 0; + for (auto const& item : items) + { + mPermutation[i++] = item.index; + } + + // GetOrthogonalMatrices() has nontrivial code for computing the + // orthogonal U and V from the reflections and rotations. To avoid + // complicating the code further when sorting is requested, U and V are + // computed as in the unsorted case. We then need to swap columns of + // U and V to be consistent with the sorting of the singular values. To + // minimize copying due to column swaps, we use permutation P. The + // minimum number of transpositions to obtain P from I is N minus the + // number of cycles of P. Each cycle is reordered with a minimum number + // of transpositions; that is, the singular items are cyclically swapped, + // leading to a minimum amount of copying. For example, if there is a + // cycle i0 -> i1 -> i2 -> i3, then the copying is + // save = singularitem[i0]; + // singularitem[i1] = singularitem[i2]; + // singularitem[i2] = singularitem[i3]; + // singularitem[i3] = save; +} + +template +SingularValueDecomposition::GivensRotation::GivensRotation() +{ + // No default initialization for fast creation of std::vector of objects + // of this type. +} + +template +SingularValueDecomposition::GivensRotation::GivensRotation(int inIndex0, + int inIndex1, Real inCs, Real inSn) + : + index0(inIndex0), + index1(inIndex1), + cs(inCs), + sn(inSn) +{ +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSlerpEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSlerpEstimate.h new file mode 100644 index 000000000000..f74470b5da18 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSlerpEstimate.h @@ -0,0 +1,156 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/12/10) + +#pragma once + +#include + +// The spherical linear interpolation (slerp) of unit-length quaternions +// q0 and q1 for t in [0,1] and theta in (0,pi) is +// slerp(t,q0,q1) = [sin((1-t)*theta)*q0 + sin(theta)*q1]/sin(theta) +// where theta is the angle between q0 and q1 [cos(theta) = Dot(q0,q1)]. +// This function is a parameterization of the great spherical arc between +// q0 and q1 on the unit hypersphere. Moreover, the parameterization is +// one of normalized arclength--a particle traveling along the arc through +// time t does so with constant speed. +// +// Read the comments in GteChebyshevRatio.h regarding estimates for the +// ratio sin(t*theta)/sin(theta). +// +// When using slerp in animations involving sequences of quaternions, it is +// typical that the quaternions are preprocessed so that consecutive ones +// form an acute angle A in [0,pi/2]. Other preprocessing can help with +// performance. See the function comments in the SLERP class. + +namespace gte +{ + template + class SLERP + { + public: + // The angle between q0 and q1 is in [0,pi). There are no angle + // restrictions and nothing is precomputed. + template + inline static Quaternion Estimate(Real t, Quaternion const& q0, Quaternion const& q1) + { + static_assert(1 <= N && N <= 16, "Invalid degree."); + + Real cs = Dot(q0, q1); + Real sign; + if (cs >= (Real)0) + { + sign = (Real)1; + } + else + { + cs = -cs; + sign = (Real)-1; + } + + Real f0, f1; + ChebyshevRatio::template + GetEstimate(t, (Real)1 - cs, f0, f1); + return q0 * f0 + q1 * (sign * f1); + } + + + // The angle between q0 and q1 must be in [0,pi/2]. The suffix R is for + // 'Restricted'. The preprocessing code is + // Quaternion q[n]; // assuming initialized + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cosA = Dot(q[i0], q[i1]); + // if (cosA < 0) + // { + // q[i1] = -q[i1]; // now Dot(q[i0], q[i]1) >= 0 + // } + // } + template + inline static Quaternion EstimateR(Real t, Quaternion const& q0, Quaternion const& q1) + { + static_assert(1 <= N && N <= 16, "Invalid degree."); + + Real f0, f1; + ChebyshevRatio::template + GetEstimate(t, (Real)1 - Dot(q0, q1), f0, f1); + return q0 * f0 + q1 * f1; + } + + // The angle between q0 and q1 must be in [0,pi/2]. The suffix R is for + // 'Restricted' and the suffix P is for 'Preprocessed'. The preprocessing + // code is + // Quaternion q[n]; // assuming initialized + // Real cosA[n-1], omcosA[n-1]; // to be precomputed + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cs = Dot(q[i0], q[i1]); + // if (cosA[i0] < 0) + // { + // q[i1] = -q[i1]; + // cs = -cs; + // } + // + // // for Quaterion::SlerpRP + // cosA[i0] = cs; + // + // // for SLERP::EstimateRP + // omcosA[i0] = 1 - cs; + // } + template + inline static Quaternion EstimateRP(Real t, Quaternion const& q0, Quaternion const& q1, + Real omcosA) + { + static_assert(1 <= N && N <= 16, "Invalid degree."); + + Real f0, f1; + ChebyshevRatio::template + GetEstimate(t, omcosA, f0, f1); + return q0 * f0 + q1 * f1; + } + + // The angle between q0 and q1 is A and must be in [0,pi/2]. Quaternion + // qh is slerp(1/2,q0,q1) = (q0+q1)/|q0+q1|, so the angle between q0 and + // qh is A/2 and the angle between qh and q1 is A/2. The preprocessing + // code is + // Quaternion q[n]; // assuming initialized + // Quaternion qh[n-1]; // to be precomputed + // Real omcosAH[n-1]; // to be precomputed + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cosA = Dot(q[i0], q[i1]); + // if (cosA < 0) + // { + // q[i1] = -q[i1]; + // cosA = -cosA; + // } + // cosAH = sqrt((1 + cosA)/2); + // qh[i0] = (q0 + q1) / (2 * cosAH[i0]); + // omcosAH[i0] = 1 - cosAH; + // } + template + inline static Quaternion EstimateRPH(Real t, Quaternion const& q0, Quaternion const& q1, + Quaternion const& qh, Real omcosAH) + { + static_assert(1 <= N && N <= 16, "Invalid degree."); + + Real f0, f1; + Real twoT = t * (Real)2; + if (twoT <= (Real)1) + { + ChebyshevRatio::template + GetEstimate(twoT, omcosAH, f0, f1); + return q0 * f0 + qh * f1; + } + else + { + ChebyshevRatio::template + GetEstimate(twoT - (Real)1, omcosAH, f0, f1); + return qh * f0 + q1 * f1; + } + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSqrtEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSqrtEstimate.h new file mode 100644 index 000000000000..e538913276f6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSqrtEstimate.h @@ -0,0 +1,193 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Minimax polynomial approximations to sqrt(x). The polynomial p(x) of +// degree D minimizes the quantity maximum{|sqrt(x) - p(x)| : x in [1,2]} +// over all polynomials of degree D. + +namespace gte +{ + +template +class SqrtEstimate +{ +public: + // The input constraint is x in [1,2]. For example, + // float x; // in [1,2] + // float result = SqrtEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input constraint is x >= 0. Range reduction is used to generate a + // value y in [0,1], call Degree(y), and combine the output with the + // proper exponent to obtain the approximation. For example, + // float x; // x >= 0 + // float result = SqrtEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<1>, Real t); + inline static Real Evaluate(degree<2>, Real t); + inline static Real Evaluate(degree<3>, Real t); + inline static Real Evaluate(degree<4>, Real t); + inline static Real Evaluate(degree<5>, Real t); + inline static Real Evaluate(degree<6>, Real t); + inline static Real Evaluate(degree<7>, Real t); + inline static Real Evaluate(degree<8>, Real t); + + // Support for range reduction. + inline static void Reduce(Real x, Real& adj, Real& y, int& p); + inline static Real Combine(Real adj, Real y, int p); +}; + + +template +template +inline Real SqrtEstimate::Degree(Real x) +{ + Real t = x - (Real)1; // t in [0,1] + return Evaluate(degree(), t); +} + +template +template +inline Real SqrtEstimate::DegreeRR(Real x) +{ + Real adj, y; + int p; + Reduce(x, adj, y, p); + Real poly = Degree(y); + Real result = Combine(adj, poly, p); + return result; +} + +template +inline Real SqrtEstimate::Evaluate(degree<1>, Real t) +{ + Real poly; + poly = (Real)GTE_C_SQRT_DEG1_C1; + poly = (Real)GTE_C_SQRT_DEG1_C0 + poly * t; + return poly; +} + +template +inline Real SqrtEstimate::Evaluate(degree<2>, Real t) +{ + Real poly; + poly = (Real)GTE_C_SQRT_DEG2_C2; + poly = (Real)GTE_C_SQRT_DEG2_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG2_C0 + poly * t; + return poly; +} + +template +inline Real SqrtEstimate::Evaluate(degree<3>, Real t) +{ + Real poly; + poly = (Real)GTE_C_SQRT_DEG3_C3; + poly = (Real)GTE_C_SQRT_DEG3_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG3_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG3_C0 + poly * t; + return poly; +} + +template +inline Real SqrtEstimate::Evaluate(degree<4>, Real t) +{ + Real poly; + poly = (Real)GTE_C_SQRT_DEG4_C4; + poly = (Real)GTE_C_SQRT_DEG4_C3 + poly * t; + poly = (Real)GTE_C_SQRT_DEG4_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG4_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG4_C0 + poly * t; + return poly; +} + +template +inline Real SqrtEstimate::Evaluate(degree<5>, Real t) +{ + Real poly; + poly = (Real)GTE_C_SQRT_DEG5_C5; + poly = (Real)GTE_C_SQRT_DEG5_C4 + poly * t; + poly = (Real)GTE_C_SQRT_DEG5_C3 + poly * t; + poly = (Real)GTE_C_SQRT_DEG5_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG5_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG5_C0 + poly * t; + return poly; +} + +template +inline Real SqrtEstimate::Evaluate(degree<6>, Real t) +{ + Real poly; + poly = (Real)GTE_C_SQRT_DEG6_C6; + poly = (Real)GTE_C_SQRT_DEG6_C5 + poly * t; + poly = (Real)GTE_C_SQRT_DEG6_C4 + poly * t; + poly = (Real)GTE_C_SQRT_DEG6_C3 + poly * t; + poly = (Real)GTE_C_SQRT_DEG6_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG6_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG6_C0 + poly * t; + return poly; +} + +template +inline Real SqrtEstimate::Evaluate(degree<7>, Real t) +{ + Real poly; + poly = (Real)GTE_C_SQRT_DEG7_C7; + poly = (Real)GTE_C_SQRT_DEG7_C6 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C5 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C4 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C3 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C0 + poly * t; + return poly; +} + +template +inline Real SqrtEstimate::Evaluate(degree<8>, Real t) +{ + Real poly; + poly = (Real)GTE_C_SQRT_DEG8_C8; + poly = (Real)GTE_C_SQRT_DEG8_C7 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C6 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C5 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C4 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C3 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C0 + poly * t; + return poly; +} + +template +inline void SqrtEstimate::Reduce(Real x, Real& adj, Real& y, int& p) +{ + y = std::frexp(x, &p); // y in [1/2,1) + y = ((Real)2)*y; // y in [1,2) + --p; + adj = (1 & p)*(Real)GTE_C_SQRT_2 + (1 & ~p)*(Real)1; + p >>= 1; +} + +template +inline Real SqrtEstimate::Combine(Real adj, Real y, int p) +{ + return adj * std::ldexp(y, p); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver.h new file mode 100644 index 000000000000..5509b1803bef --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver.h @@ -0,0 +1,835 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The SymmetricEigensolver class is an implementation of Algorithm 8.2.3 +// (Symmetric QR Algorithm) described in "Matrix Computations, 2nd edition" +// by G. H. Golub and C. F. Van Loan, The Johns Hopkins University Press, +// Baltimore MD, Fourth Printing 1993. Algorithm 8.2.1 (Householder +// Tridiagonalization) is used to reduce matrix A to tridiagonal T. +// Algorithm 8.2.2 (Implicit Symmetric QR Step with Wilkinson Shift) is +// used for the iterative reduction from tridiagonal to diagonal. If A is +// the original matrix, D is the diagonal matrix of eigenvalues, and Q is +// the orthogonal matrix of eigenvectors, then theoretically Q^T*A*Q = D. +// Numerically, we have errors E = Q^T*A*Q - D. Algorithm 8.2.3 mentions +// that one expects |E| is approximately u*|A|, where |M| denotes the +// Frobenius norm of M and where u is the unit roundoff for the +// floating-point arithmetic: 2^{-23} for 'float', which is FLT_EPSILON +// = 1.192092896e-7f, and 2^{-52} for'double', which is DBL_EPSILON +// = 2.2204460492503131e-16. +// +// The condition |a(i,i+1)| <= epsilon*(|a(i,i) + a(i+1,i+1)|) used to +// determine when the reduction decouples to smaller problems is implemented +// as: sum = |a(i,i)| + |a(i+1,i+1)|; sum + |a(i,i+1)| == sum. The idea is +// that the superdiagonal term is small relative to its diagonal neighbors, +// and so it is effectively zero. The unit tests have shown that this +// interpretation of decoupling is effective. +// +// The authors suggest that once you have the tridiagonal matrix, a practical +// implementation will store the diagonal and superdiagonal entries in linear +// arrays, ignoring the theoretically zero values not in the 3-band. This is +// good for cache coherence. The authors also suggest storing the Householder +// vectors in the lower-triangular portion of the matrix to save memory. The +// implementation uses both suggestions. +// +// For matrices with randomly generated values in [0,1], the unit tests +// produce the following information for N-by-N matrices. +// +// N |A| |E| |E|/|A| iterations +// ------------------------------------------- +// 2 1.2332 5.5511e-17 4.5011e-17 1 +// 3 2.0024 1.1818e-15 5.9020e-16 5 +// 4 2.8708 9.9287e-16 3.4585e-16 7 +// 5 2.9040 2.5958e-15 8.9388e-16 9 +// 6 4.0427 2.2434e-15 5.5493e-16 12 +// 7 5.0276 3.2456e-15 6.4555e-16 15 +// 8 5.4468 6.5626e-15 1.2048e-15 15 +// 9 6.1510 4.0317e-15 6.5545e-16 17 +// 10 6.7523 4.9334e-15 7.3062e-16 21 +// 11 7.1322 7.1347e-15 1.0003e-15 22 +// 12 7.8324 5.6106e-15 7.1633e-16 24 +// 13 8.1073 5.1366e-15 6.3357e-16 25 +// 14 8.6257 8.3496e-15 9.6798e-16 29 +// 15 9.2603 6.9526e-15 7.5080e-16 31 +// 16 9.9853 6.5807e-15 6.5904e-16 32 +// 17 10.5388 8.0931e-15 7.6793e-16 35 +// 18 11.2377 1.1149e-14 9.9218e-16 38 +// 19 11.7105 1.0711e-14 9.1470e-16 42 +// 20 12.2227 1.7723e-14 1.4500e-15 42 +// 21 12.7411 1.2515e-14 9.8231e-16 47 +// 22 13.4462 1.2645e-14 9.4046e-16 50 +// 23 13.9541 1.2899e-14 9.2444e-16 47 +// 24 14.3298 1.6337e-14 1.1400e-15 53 +// 25 14.8050 1.4760e-14 9.9701e-16 54 +// 26 15.3914 1.7076e-14 1.1094e-15 57 +// 27 15.8430 1.9714e-14 1.2443e-15 60 +// 28 16.4771 1.7148e-14 1.0407e-15 60 +// 29 16.9909 1.7309e-14 1.0187e-15 60 +// 30 17.4456 2.1453e-14 1.2297e-15 64 +// 31 17.9909 2.2069e-14 1.2267e-15 68 +// +// The eigenvalues and |E|/|A| values were compared to those generated by +// Mathematica Version 9.0; Wolfram Research, Inc., Champaign IL, 2012. +// The results were all comparable with eigenvalues agreeing to a large +// number of decimal places. +// +// Timing on an Intel (R) Core (TM) i7-3930K CPU @ 3.20 GHZ (in seconds): +// +// N |E|/|A| iters tridiag QR evecs evec[N] comperr +// -------------------------------------------------------------- +// 512 4.4149e-15 1017 0.180 0.005 1.151 0.848 2.166 +// 1024 6.1691e-15 1990 1.775 0.031 11.894 12.759 21.179 +// 2048 8.5108e-15 3849 16.592 0.107 119.744 116.56 212.227 +// +// where iters is the number of QR steps taken, tridiag is the computation +// of the Householder reflection vectors, evecs is the composition of +// Householder reflections and Givens rotations to obtain the matrix of +// eigenvectors, evec[N] is N calls to get the eigenvectors separately, and +// comperr is the computation E = Q^T*A*Q - D. The construction of the full +// eigenvector matrix is, of course, quite expensive. If you need only a +// small number of eigenvectors, use function GetEigenvector(int,Real*). + +namespace gte +{ + +template +class SymmetricEigensolver +{ +public: + // The solver processes NxN symmetric matrices, where N > 1 ('size' is N) + // and the matrix is stored in row-major order. The maximum number of + // iterations ('maxIterations') must be specified for the reduction of a + // tridiagonal matrix to a diagonal matrix. The goal is to compute + // NxN orthogonal Q and NxN diagonal D for which Q^T*A*Q = D. + SymmetricEigensolver(int size, unsigned int maxIterations); + + // A copy of the NxN symmetric input is made internally. The order of + // the eigenvalues is specified by sortType: -1 (decreasing), 0 (no + // sorting), or +1 (increasing). When sorted, the eigenvectors are + // ordered accordingly. The return value is the number of iterations + // consumed when convergence occurred, 0xFFFFFFFF when convergence did + // not occur, or 0 when N <= 1 was passed to the constructor. + unsigned int Solve(Real const* input, int sortType); + + // Get the eigenvalues of the matrix passed to Solve(...). The input + // 'eigenvalues' must have N elements. + void GetEigenvalues(Real* eigenvalues) const; + + // Accumulate the Householder reflections and Givens rotations to produce + // the orthogonal matrix Q for which Q^T*A*Q = D. The input + // 'eigenvectors' must have N*N elements. The array is filled in as + // if the eigenvector matrix is stored in row-major order. The i-th + // eigenvector is + // (eigenvectors[i+size*0], ... eigenvectors[i+size*(size - 1)]) + // which is the i-th column of 'eigenvectors' as an NxN matrix stored + // in row-major order. + void GetEigenvectors(Real* eigenvectors) const; + + // The eigenvector matrix is a rotation (return +1) or a reflection + // (return 0). If the input 'size' to the constructor is 0 or the + // input 'eigenvectors' to GetEigenvectors is null, the returned value + // is -1. + int GetEigenvectorMatrixType() const; + + // Compute a single eigenvector, which amounts to computing column c + // of matrix Q. The reflections and rotations are applied incrementally. + // This is useful when you want only a small number of the eigenvectors. + void GetEigenvector(int c, Real* eigenvector) const; + Real GetEigenvalue(int c) const; + +private: + // Tridiagonalize using Householder reflections. On input, mMatrix is a + // copy of the input matrix. On output, the upper-triangular part of + // mMatrix including the diagonal stores the tridiagonalization. The + // lower-triangular part contains 2/Dot(v,v) that are used in computing + // eigenvectors and the part below the subdiagonal stores the essential + // parts of the Householder vectors v (the elements of v after the + // leading 1-valued component). + void Tridiagonalize(); + + // A helper for generating Givens rotation sine and cosine robustly. + void GetSinCos(Real u, Real v, Real& cs, Real& sn); + + // The QR step with implicit shift. Generally, the initial T is unreduced + // tridiagonal (all subdiagonal entries are nonzero). If a QR step causes + // a superdiagonal entry to become zero, the matrix decouples into a block + // diagonal matrix with two tridiagonal blocks. These blocks can be + // reduced independently of each other, which allows for parallelization + // of the algorithm. The inputs imin and imax identify the subblock of T + // to be processed. That block has upper-left element T(imin,imin) and + // lower-right element T(imax,imax). + void DoQRImplicitShift(int imin, int imax); + + // Sort the eigenvalues and compute the corresponding permutation of the + // indices of the array storing the eigenvalues. The permutation is used + // for reordering the eigenvalues and eigenvectors in the calls to + // GetEigenvalues(...) and GetEigenvectors(...). + void ComputePermutation(int sortType); + + // The number N of rows and columns of the matrices to be processed. + int mSize; + + // The maximum number of iterations for reducing the tridiagonal mtarix + // to a diagonal matrix. + unsigned int mMaxIterations; + + // The internal copy of a matrix passed to the solver. See the comments + // about function Tridiagonalize() about what is stored in the matrix. + std::vector mMatrix; // NxN elements + + // After the initial tridiagonalization by Householder reflections, we no + // longer need the full mMatrix. Copy the diagonal and superdiagonal + // entries to linear arrays in order to be cache friendly. + std::vector mDiagonal; // N elements + std::vector mSuperdiagonal; // N-1 elements + + // The Givens rotations used to reduce the initial tridiagonal matrix to + // a diagonal matrix. A rotation is the identity with the following + // replacement entries: R(index,index) = cs, R(index,index+1) = sn, + // R(index+1,index) = -sn, and R(index+1,index+1) = cs. If N is the + // matrix size and K is the maximum number of iterations, the maximum + // number of Givens rotations is K*(N-1). The maximum amount of memory + // is allocated to store these. + struct GivensRotation + { + GivensRotation(); + GivensRotation(int inIndex, Real inCs, Real inSn); + int index; + Real cs, sn; + }; + + std::vector mGivens; // K*(N-1) elements + + // When sorting is requested, the permutation associated with the sort is + // stored in mPermutation. When sorting is not requested, mPermutation[0] + // is set to -1. mVisited is used for finding cycles in the permutation. + // mEigenvectorMatrixType is +1 if GetEigenvectors returns a rotation + // matrix, 0 if GetEigenvectors returns a reflection matrix or -1 if an + // input to the constructor or to GetEigenvectors is invalid. + std::vector mPermutation; // N elements + mutable std::vector mVisited; // N elements + mutable int mEigenvectorMatrixType; + + // Temporary storage to compute Householder reflections and to support + // sorting of eigenvectors. + mutable std::vector mPVector; // N elements + mutable std::vector mVVector; // N elements + mutable std::vector mWVector; // N elements +}; + + +template +SymmetricEigensolver::SymmetricEigensolver(int size, + unsigned int maxIterations) + : + mSize(0), + mMaxIterations(0), + mEigenvectorMatrixType(-1) +{ + if (size > 1 && maxIterations > 0) + { + mSize = size; + mMaxIterations = maxIterations; + mMatrix.resize(size*size); + mDiagonal.resize(size); + mSuperdiagonal.resize(size - 1); + mGivens.reserve(maxIterations * (size - 1)); + mPermutation.resize(size); + mVisited.resize(size); + mPVector.resize(size); + mVVector.resize(size); + mWVector.resize(size); + } +} + +template +unsigned int SymmetricEigensolver::Solve(Real const* input, + int sortType) +{ + mEigenvectorMatrixType = -1; + + if (mSize > 0) + { + std::copy(input, input + mSize*mSize, mMatrix.begin()); + Tridiagonalize(); + + mGivens.clear(); + for (unsigned int j = 0; j < mMaxIterations; ++j) + { + int imin = -1, imax = -1; + for (int i = mSize - 2; i >= 0; --i) + { + // When a01 is much smaller than its diagonal neighbors, it is + // effectively zero. + Real a00 = mDiagonal[i]; + Real a01 = mSuperdiagonal[i]; + Real a11 = mDiagonal[i + 1]; + Real sum = std::abs(a00) + std::abs(a11); + if (sum + std::abs(a01) != sum) + { + if (imax == -1) + { + imax = i; + } + imin = i; + } + else + { + // The superdiagonal term is effectively zero compared to + // the neighboring diagonal terms. + if (imin >= 0) + { + break; + } + } + } + + if (imax == -1) + { + // The algorithm has converged. + ComputePermutation(sortType); + return j; + } + + // Process the lower-right-most unreduced tridiagonal block. + DoQRImplicitShift(imin, imax); + } + return 0xFFFFFFFF; + } + else + { + return 0; + } +} + +template +void SymmetricEigensolver::GetEigenvalues(Real* eigenvalues) const +{ + if (eigenvalues && mSize > 0) + { + if (mPermutation[0] >= 0) + { + // Sorting was requested. + for (int i = 0; i < mSize; ++i) + { + int p = mPermutation[i]; + eigenvalues[i] = mDiagonal[p]; + } + } + else + { + // Sorting was not requested. + size_t numBytes = mSize*sizeof(Real); + Memcpy(eigenvalues, &mDiagonal[0], numBytes); + } + } +} + +template +void SymmetricEigensolver::GetEigenvectors(Real* eigenvectors) const +{ + mEigenvectorMatrixType = -1; + + if (eigenvectors && mSize > 0) + { + // Start with the identity matrix. + std::fill(eigenvectors, eigenvectors + mSize*mSize, (Real)0); + for (int d = 0; d < mSize; ++d) + { + eigenvectors[d + mSize*d] = (Real)1; + } + + // Multiply the Householder reflections using backward accumulation. + int r, c; + for (int i = mSize - 3, rmin = i + 1; i >= 0; --i, --rmin) + { + // Copy the v vector and 2/Dot(v,v) from the matrix. + Real const* column = &mMatrix[i]; + Real twoinvvdv = column[mSize*(i + 1)]; + for (r = 0; r < i + 1; ++r) + { + mVVector[r] = (Real)0; + } + mVVector[r] = (Real)1; + for (++r; r < mSize; ++r) + { + mVVector[r] = column[mSize*r]; + } + + // Compute the w vector. + for (r = 0; r < mSize; ++r) + { + mWVector[r] = (Real)0; + for (c = rmin; c < mSize; ++c) + { + mWVector[r] += mVVector[c] * eigenvectors[r + mSize*c]; + } + mWVector[r] *= twoinvvdv; + } + + // Update the matrix, Q <- Q - v*w^T. + for (r = rmin; r < mSize; ++r) + { + for (c = 0; c < mSize; ++c) + { + eigenvectors[c + mSize*r] -= mVVector[r] * mWVector[c]; + } + } + } + + // Multiply the Givens rotations. + for (auto const& givens : mGivens) + { + for (r = 0; r < mSize; ++r) + { + int j = givens.index + mSize*r; + Real& q0 = eigenvectors[j]; + Real& q1 = eigenvectors[j + 1]; + Real prd0 = givens.cs * q0 - givens.sn * q1; + Real prd1 = givens.sn * q0 + givens.cs * q1; + q0 = prd0; + q1 = prd1; + } + } + + // The number of Householder reflections is H = mSize - 2. If H is + // even, the product of Householder reflections is a rotation; + // otherwise, H is odd and the product is a reflection. The number + // of Givens rotations does not influence the type of the product of + // Householder reflections. + mEigenvectorMatrixType = 1 - (mSize & 1); + + if (mPermutation[0] >= 0) + { + // Sorting was requested. + std::fill(mVisited.begin(), mVisited.end(), 0); + for (int i = 0; i < mSize; ++i) + { + if (mVisited[i] == 0 && mPermutation[i] != i) + { + // The item starts a cycle with 2 or more elements. + int start = i, current = i, j, next; + for (j = 0; j < mSize; ++j) + { + mPVector[j] = eigenvectors[i + mSize*j]; + } + while ((next = mPermutation[current]) != start) + { + mEigenvectorMatrixType = 1 - mEigenvectorMatrixType; + mVisited[current] = 1; + for (j = 0; j < mSize; ++j) + { + eigenvectors[current + mSize*j] = + eigenvectors[next + mSize*j]; + } + current = next; + } + mVisited[current] = 1; + for (j = 0; j < mSize; ++j) + { + eigenvectors[current + mSize*j] = mPVector[j]; + } + } + } + } + } +} + +template +int SymmetricEigensolver::GetEigenvectorMatrixType() const +{ + return mEigenvectorMatrixType; +} + +template +void SymmetricEigensolver::GetEigenvector(int c, Real* eigenvector) +const +{ + if (0 <= c && c < mSize) + { + // y = H*x, then x and y are swapped for the next H + Real* x = eigenvector; + Real* y = &mPVector[0]; + + // Start with the Euclidean basis vector. + memset(x, 0, mSize*sizeof(Real)); + if (mPermutation[0] >= 0) + { + // Sorting was requested. + x[mPermutation[c]] = (Real)1; + } + else + { + x[c] = (Real)1; + } + + // Apply the Givens rotations. + for (auto const& givens : gte::reverse(mGivens)) + { + Real& xr = x[givens.index]; + Real& xrp1 = x[givens.index + 1]; + Real tmp0 = givens.cs * xr + givens.sn * xrp1; + Real tmp1 = -givens.sn * xr + givens.cs * xrp1; + xr = tmp0; + xrp1 = tmp1; + } + + // Apply the Householder reflections. + for (int i = mSize - 3; i >= 0; --i) + { + // Get the Householder vector v. + Real const* column = &mMatrix[i]; + Real twoinvvdv = column[mSize*(i + 1)]; + int r; + for (r = 0; r < i + 1; ++r) + { + y[r] = x[r]; + } + + // Compute s = Dot(x,v) * 2/v^T*v. + Real s = x[r]; // r = i+1, v[i+1] = 1 + for (int j = r + 1; j < mSize; ++j) + { + s += x[j] * column[mSize*j]; + } + s *= twoinvvdv; + + y[r] = x[r] - s; // v[i+1] = 1 + + // Compute the remaining components of y. + for (++r; r < mSize; ++r) + { + y[r] = x[r] - s * column[mSize*r]; + } + + std::swap(x, y); + } + + // The final product is stored in x. + if (x != eigenvector) + { + size_t numBytes = mSize*sizeof(Real); + Memcpy(eigenvector, x, numBytes); + } + } +} + +template +Real SymmetricEigensolver::GetEigenvalue(int c) const +{ + if (mSize > 0) + { + if (mPermutation[0] >= 0) + { + // Sorting was requested. + return mDiagonal[mPermutation[c]]; + } + else + { + // Sorting was not requested. + return mDiagonal[c]; + } + } + else + { + return std::numeric_limits::max(); + } +} + +template +void SymmetricEigensolver::Tridiagonalize() +{ + int r, c; + for (int i = 0, ip1 = 1; i < mSize - 2; ++i, ++ip1) + { + // Compute the Householder vector. Read the initial vector from the + // row of the matrix. + Real length = (Real)0; + for (r = 0; r < ip1; ++r) + { + mVVector[r] = (Real)0; + } + for (r = ip1; r < mSize; ++r) + { + Real vr = mMatrix[r + mSize*i]; + mVVector[r] = vr; + length += vr * vr; + } + Real vdv = (Real)1; + length = std::sqrt(length); + if (length >(Real)0) + { + Real& v1 = mVVector[ip1]; + Real sgn = (v1 >= (Real)0 ? (Real)1 : (Real)-1); + Real invDenom = ((Real)1) / (v1 + sgn * length); + v1 = (Real)1; + for (r = ip1 + 1; r < mSize; ++r) + { + Real& vr = mVVector[r]; + vr *= invDenom; + vdv += vr * vr; + } + } + + // Compute the rank-1 offsets v*w^T and w*v^T. + Real invvdv = (Real)1 / vdv; + Real twoinvvdv = invvdv * (Real)2; + Real pdvtvdv = (Real)0; + for (r = i; r < mSize; ++r) + { + mPVector[r] = (Real)0; + for (c = i; c < r; ++c) + { + mPVector[r] += mMatrix[r + mSize*c] * mVVector[c]; + } + for (/**/; c < mSize; ++c) + { + mPVector[r] += mMatrix[c + mSize*r] * mVVector[c]; + } + mPVector[r] *= twoinvvdv; + pdvtvdv += mPVector[r] * mVVector[r]; + } + + pdvtvdv *= invvdv; + for (r = i; r < mSize; ++r) + { + mWVector[r] = mPVector[r] - pdvtvdv * mVVector[r]; + } + + // Update the input matrix. + for (r = i; r < mSize; ++r) + { + Real vr = mVVector[r]; + Real wr = mWVector[r]; + Real offset = vr * wr * (Real)2; + mMatrix[r + mSize*r] -= offset; + for (c = r + 1; c < mSize; ++c) + { + offset = vr * mWVector[c] + wr * mVVector[c]; + mMatrix[c + mSize*r] -= offset; + } + } + + // Copy the vector to column i of the matrix. The 0-valued components + // at indices 0 through i are not stored. The 1-valued component at + // index i+1 is also not stored; instead, the quantity 2/Dot(v,v) is + // stored for use in eigenvector construction. That construction must + // take into account the implied components that are not stored. + mMatrix[i + mSize*ip1] = twoinvvdv; + for (r = ip1 + 1; r < mSize; ++r) + { + mMatrix[i + mSize*r] = mVVector[r]; + } + } + + // Copy the diagonal and subdiagonal entries for cache coherence in + // the QR iterations. + int k, ksup = mSize - 1, index = 0, delta = mSize + 1; + for (k = 0; k < ksup; ++k, index += delta) + { + mDiagonal[k] = mMatrix[index]; + mSuperdiagonal[k] = mMatrix[index + 1]; + } + mDiagonal[k] = mMatrix[index]; +} + +template +void SymmetricEigensolver::GetSinCos(Real x, Real y, Real& cs, Real& sn) +{ + // Solves sn*x + cs*y = 0 robustly. + Real tau; + if (y != (Real)0) + { + if (std::abs(y) > std::abs(x)) + { + tau = -x / y; + sn = ((Real)1) / std::sqrt(((Real)1) + tau*tau); + cs = sn * tau; + } + else + { + tau = -y / x; + cs = ((Real)1) / std::sqrt(((Real)1) + tau*tau); + sn = cs * tau; + } + } + else + { + cs = (Real)1; + sn = (Real)0; + } +} + +template +void SymmetricEigensolver::DoQRImplicitShift(int imin, int imax) +{ + // The implicit shift. Compute the eigenvalue u of the lower-right 2x2 + // block that is closer to a11. + Real a00 = mDiagonal[imax]; + Real a01 = mSuperdiagonal[imax]; + Real a11 = mDiagonal[imax + 1]; + Real dif = (a00 - a11) * (Real)0.5; + Real sgn = (dif >= (Real)0 ? (Real)1 : (Real)-1); + Real a01sqr = a01 * a01; + Real u = a11 - a01sqr / (dif + sgn * std::sqrt(dif*dif + a01sqr)); + Real x = mDiagonal[imin] - u; + Real y = mSuperdiagonal[imin]; + + Real a12, a22, a23, tmp11, tmp12, tmp21, tmp22, cs, sn; + Real a02 = (Real)0; + int i0 = imin - 1, i1 = imin, i2 = imin + 1; + for (/**/; i1 <= imax; ++i0, ++i1, ++i2) + { + // Compute the Givens rotation and save it for use in computing the + // eigenvectors. + GetSinCos(x, y, cs, sn); + mGivens.push_back(GivensRotation(i1, cs, sn)); + + // Update the tridiagonal matrix. This amounts to updating a 4x4 + // subblock, + // b00 b01 b02 b03 + // b01 b11 b12 b13 + // b02 b12 b22 b23 + // b03 b13 b23 b33 + // The four corners (b00, b03, b33) do not change values. The + // The interior block {{b11,b12},{b12,b22}} is updated on each pass. + // For the first pass, the b0c values are out of range, so only + // the values (b13, b23) change. For the last pass, the br3 values + // are out of range, so only the values (b01, b02) change. For + // passes between first and last, the values (b01, b02, b13, b23) + // change. + if (i1 > imin) + { + mSuperdiagonal[i0] = cs*mSuperdiagonal[i0] - sn*a02; + } + + a11 = mDiagonal[i1]; + a12 = mSuperdiagonal[i1]; + a22 = mDiagonal[i2]; + tmp11 = cs*a11 - sn*a12; + tmp12 = cs*a12 - sn*a22; + tmp21 = sn*a11 + cs*a12; + tmp22 = sn*a12 + cs*a22; + mDiagonal[i1] = cs*tmp11 - sn*tmp12; + mSuperdiagonal[i1] = sn*tmp11 + cs*tmp12; + mDiagonal[i2] = sn*tmp21 + cs*tmp22; + + if (i1 < imax) + { + a23 = mSuperdiagonal[i2]; + a02 = -sn*a23; + mSuperdiagonal[i2] = cs*a23; + + // Update the parameters for the next Givens rotation. + x = mSuperdiagonal[i1]; + y = a02; + } + } +} + +template +void SymmetricEigensolver::ComputePermutation(int sortType) +{ + // The number of Householder reflections is H = mSize - 2. If H is + // even, the product of Householder reflections is a rotation; + // otherwise, H is odd and the product is a reflection. The number + // of Givens rotations does not influence the type of the product of + // Householder reflections. + mEigenvectorMatrixType = 1 - (mSize & 1); + + if (sortType == 0) + { + // Set a flag for GetEigenvalues() and GetEigenvectors() to know + // that sorted output was not requested. + mPermutation[0] = -1; + return; + } + + // Compute the permutation induced by sorting. Initially, we start with + // the identity permutation I = (0,1,...,N-1). + struct SortItem + { + Real eigenvalue; + int index; + }; + + std::vector items(mSize); + int i; + for (i = 0; i < mSize; ++i) + { + items[i].eigenvalue = mDiagonal[i]; + items[i].index = i; + } + + if (sortType > 0) + { + std::sort(items.begin(), items.end(), + [](SortItem const& item0, SortItem const& item1) + { + return item0.eigenvalue < item1.eigenvalue; + } + ); + } + else + { + std::sort(items.begin(), items.end(), + [](SortItem const& item0, SortItem const& item1) + { + return item0.eigenvalue > item1.eigenvalue; + } + ); + } + + i = 0; + for (auto const& item : items) + { + mPermutation[i++] = item.index; + } + + // GetEigenvectors() has nontrivial code for computing the orthogonal Q + // from the reflections and rotations. To avoid complicating the code + // further when sorting is requested, Q is computed as in the unsorted + // case. We then need to swap columns of Q to be consistent with the + // sorting of the eigenvalues. To minimize copying due to column swaps, + // we use permutation P. The minimum number of transpositions to obtain + // P from I is N minus the number of cycles of P. Each cycle is reordered + // with a minimum number of transpositions; that is, the eigenitems are + // cyclically swapped, leading to a minimum amount of copying. For + // example, if there is a cycle i0 -> i1 -> i2 -> i3, then the copying is + // save = eigenitem[i0]; + // eigenitem[i1] = eigenitem[i2]; + // eigenitem[i2] = eigenitem[i3]; + // eigenitem[i3] = save; +} + +template +SymmetricEigensolver::GivensRotation::GivensRotation() +{ + // No default initialization for fast creation of std::vector of objects + // of this type. +} + +template +SymmetricEigensolver::GivensRotation::GivensRotation(int inIndex, + Real inCs, Real inSn) + : + index(inIndex), + cs(inCs), + sn(inSn) +{ +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver2x2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver2x2.h new file mode 100644 index 000000000000..3398c8027906 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver2x2.h @@ -0,0 +1,94 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class SymmetricEigensolver2x2 +{ +public: + // The input matrix must be symmetric, so only the unique elements must + // be specified: a00, a01, and a11. + // + // The order of the eigenvalues is specified by sortType: -1 (decreasing), + // 0 (no sorting), or +1 (increasing). When sorted, the eigenvectors are + // ordered accordingly, and {evec[0], evec[1]} is guaranteed to be a + // right-handed orthonormal set. + + void operator()(Real a00, Real a01, Real a11, int sortType, + std::array& eval, std::array, 2>& evec) + const; +}; + + +template +void SymmetricEigensolver2x2::operator()(Real a00, Real a01, Real a11, + int sortType, std::array& eval, + std::array, 2>& evec) const +{ + // Normalize (c2,s2) robustly, avoiding floating-point overflow in the + // sqrt call. + Real const zero = (Real)0, one = (Real)1, half = (Real)0.5; + Real c2 = half * (a00 - a11), s2 = a01; + Real maxAbsComp = std::max(std::abs(c2), std::abs(s2)); + if (maxAbsComp > zero) + { + c2 /= maxAbsComp; // in [-1,1] + s2 /= maxAbsComp; // in [-1,1] + Real length = std::sqrt(c2 * c2 + s2 * s2); + c2 /= length; + s2 /= length; + if (c2 > zero) + { + c2 = -c2; + s2 = -s2; + } + } + else + { + c2 = -one; + s2 = zero; + } + + Real s = std::sqrt(half * (one - c2)); // >= 1/sqrt(2) + Real c = half * s2 / s; + + Real diagonal[2]; + Real csqr = c * c, ssqr = s * s, mid = s2 * a01; + diagonal[0] = csqr * a00 + mid + ssqr * a11; + diagonal[1] = csqr * a11 - mid + ssqr * a00; + + if (sortType == 0 || sortType * diagonal[0] <= sortType * diagonal[1]) + { + eval[0] = diagonal[0]; + eval[1] = diagonal[1]; + evec[0][0] = c; + evec[0][1] = s; + evec[1][0] = -s; + evec[1][1] = c; + } + else + { + eval[0] = diagonal[1]; + eval[1] = diagonal[0]; + evec[0][0] = s; + evec[0][1] = -c; + evec[1][0] = c; + evec[1][1] = s; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver3x3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver3x3.h new file mode 100644 index 000000000000..55d9325b308c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver3x3.h @@ -0,0 +1,777 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.5 (2018/10/09) + +#pragma once + +#include +#include +#include +#include + +// The document +// http://www.geometrictools.com/Documentation/RobustEigenSymmetric3x3.pdf +// describes algorithms for solving the eigensystem associated with a 3x3 +// symmetric real-valued matrix. The iterative algorithm is implemented +// by class SymmmetricEigensolver3x3. The noniterative algorithm is +// implemented by class NISymmetricEigensolver3x3. The code does not use +// GTEngine objects. + +namespace gte +{ + +template +class SymmetricEigensolver3x3 +{ +public: + // The input matrix must be symmetric, so only the unique elements must + // be specified: a00, a01, a02, a11, a12, and a22. + // + // If 'aggressive' is 'true', the iterations occur until a superdiagonal + // entry is exactly zero. If 'aggressive' is 'false', the iterations + // occur until a superdiagonal entry is effectively zero compared to the + // sum of magnitudes of its diagonal neighbors. Generally, the + // nonaggressive convergence is acceptable. + // + // The order of the eigenvalues is specified by sortType: -1 (decreasing), + // 0 (no sorting), or +1 (increasing). When sorted, the eigenvectors are + // ordered accordingly, and {evec[0], evec[1], evec[2]} is guaranteed to + // be a right-handed orthonormal set. The return value is the number of + // iterations used by the algorithm. + + int operator()(Real a00, Real a01, Real a02, Real a11, Real a12, Real a22, + bool aggressive, int sortType, std::array& eval, + std::array, 3>& evec) const; + +private: + // Update Q = Q*G in-place using G = {{c,0,-s},{s,0,c},{0,0,1}}. + void Update0(Real Q[3][3], Real c, Real s) const; + + // Update Q = Q*G in-place using G = {{0,1,0},{c,0,s},{-s,0,c}}. + void Update1(Real Q[3][3], Real c, Real s) const; + + // Update Q = Q*H in-place using H = {{c,s,0},{s,-c,0},{0,0,1}}. + void Update2(Real Q[3][3], Real c, Real s) const; + + // Update Q = Q*H in-place using H = {{1,0,0},{0,c,s},{0,s,-c}}. + void Update3(Real Q[3][3], Real c, Real s) const; + + // Normalize (u,v) robustly, avoiding floating-point overflow in the sqrt + // call. The normalized pair is (cs,sn) with cs <= 0. If (u,v) = (0,0), + // the function returns (cs,sn) = (-1,0). When used to generate a + // Householder reflection, it does not matter whether (cs,sn) or (-cs,-sn) + // is used. When generating a Givens reflection, cs = cos(2*theta) and + // sn = sin(2*theta). Having a negative cosine for the double-angle + // term ensures that the single-angle terms c = cos(theta) and + // s = sin(theta) satisfy |c| <= |s|. + void GetCosSin(Real u, Real v, Real& cs, Real& sn) const; + + // The convergence test. When 'aggressive' is 'true', the superdiagonal + // test is "bSuper == 0". When 'aggressive' is 'false', the superdiagonal + // test is "|bDiag0| + |bDiag1| + |bSuper| == |bDiag0| + |bDiag1|, which + // means bSuper is effectively zero compared to the sizes of the diagonal + // entries. + bool Converged(bool aggressive, Real bDiag0, Real bDiag1, + Real bSuper) const; + + // Support for sorting the eigenvalues and eigenvectors. The output + // (i0,i1,i2) is a permutation of (0,1,2) so that d[i0] <= d[i1] <= d[i2]. + // The 'bool' return indicates whether the permutation is odd. If it is + // not, the handedness of the Q matrix must be adjusted. + bool Sort(std::array const& d, int& i0, int& i1, int& i2) const; +}; + + +template +class NISymmetricEigensolver3x3 +{ +public: + // The input matrix must be symmetric, so only the unique elements must + // be specified: a00, a01, a02, a11, a12, and a22. The eigenvalues are + // sorted in ascending order: eval0 <= eval1 <= eval2. + + void operator()(Real a00, Real a01, Real a02, Real a11, Real a12, Real a22, + std::array& eval, std::array, 3>& evec) const; + +private: + static std::array Multiply(Real s, std::array const& U); + static std::array Subtract(std::array const& U, std::array const& V); + static std::array Divide(std::array const& U, Real s); + static Real Dot(std::array const& U, std::array const& V); + static std::array Cross(std::array const& U, std::array const& V); + + void ComputeOrthogonalComplement(std::array const& W, + std::array& U, std::array& V) const; + + void ComputeEigenvector0(Real a00, Real a01, Real a02, Real a11, Real a12, Real a22, + Real eval0, std::array& evec0) const; + + void ComputeEigenvector1(Real a00, Real a01, Real a02, Real a11, Real a12, Real a22, + std::array const& evec0, Real eval1, std::array& evec1) const; +}; + + +template +int SymmetricEigensolver3x3::operator()(Real a00, Real a01, Real a02, + Real a11, Real a12, Real a22, bool aggressive, int sortType, + std::array& eval, std::array, 3>& evec) const +{ + // Compute the Householder reflection H and B = H*A*H, where b02 = 0. + Real const zero = (Real)0, one = (Real)1, half = (Real)0.5; + bool isRotation = false; + Real c, s; + GetCosSin(a12, -a02, c, s); + Real Q[3][3] = { { c, s, zero }, { s, -c, zero }, { zero, zero, one } }; + Real term0 = c * a00 + s * a01; + Real term1 = c * a01 + s * a11; + Real b00 = c * term0 + s * term1; + Real b01 = s * term0 - c * term1; + term0 = s * a00 - c * a01; + term1 = s * a01 - c * a11; + Real b11 = s * term0 - c * term1; + Real b12 = s * a02 - c * a12; + Real b22 = a22; + + // Givens reflections, B' = G^T*B*G, preserve tridiagonal matrices. + int const maxIteration = 2 * (1 + std::numeric_limits::digits - + std::numeric_limits::min_exponent); + int iteration; + Real c2, s2; + + if (std::abs(b12) <= std::abs(b01)) + { + Real saveB00, saveB01, saveB11; + for (iteration = 0; iteration < maxIteration; ++iteration) + { + // Compute the Givens reflection. + GetCosSin(half * (b00 - b11), b01, c2, s2); + s = std::sqrt(half * (one - c2)); // >= 1/sqrt(2) + c = half * s2 / s; + + // Update Q by the Givens reflection. + Update0(Q, c, s); + isRotation = !isRotation; + + // Update B <- Q^T*B*Q, ensuring that b02 is zero and |b12| has + // strictly decreased. + saveB00 = b00; + saveB01 = b01; + saveB11 = b11; + term0 = c * saveB00 + s * saveB01; + term1 = c * saveB01 + s * saveB11; + b00 = c * term0 + s * term1; + b11 = b22; + term0 = c * saveB01 - s * saveB00; + term1 = c * saveB11 - s * saveB01; + b22 = c * term1 - s * term0; + b01 = s * b12; + b12 = c * b12; + + if (Converged(aggressive, b00, b11, b01)) + { + // Compute the Householder reflection. + GetCosSin(half * (b00 - b11), b01, c2, s2); + s = std::sqrt(half * (one - c2)); + c = half * s2 / s; // >= 1/sqrt(2) + + // Update Q by the Householder reflection. + Update2(Q, c, s); + isRotation = !isRotation; + + // Update D = Q^T*B*Q. + saveB00 = b00; + saveB01 = b01; + saveB11 = b11; + term0 = c * saveB00 + s * saveB01; + term1 = c * saveB01 + s * saveB11; + b00 = c * term0 + s * term1; + term0 = s * saveB00 - c * saveB01; + term1 = s * saveB01 - c * saveB11; + b11 = s * term0 - c * term1; + break; + } + } + } + else + { + Real saveB11, saveB12, saveB22; + for (iteration = 0; iteration < maxIteration; ++iteration) + { + // Compute the Givens reflection. + GetCosSin(half * (b22 - b11), b12, c2, s2); + s = std::sqrt(half * (one - c2)); // >= 1/sqrt(2) + c = half * s2 / s; + + // Update Q by the Givens reflection. + Update1(Q, c, s); + isRotation = !isRotation; + + // Update B <- Q^T*B*Q, ensuring that b02 is zero and |b12| has + // strictly decreased. MODIFY... + saveB11 = b11; + saveB12 = b12; + saveB22 = b22; + term0 = c * saveB22 + s * saveB12; + term1 = c * saveB12 + s * saveB11; + b22 = c * term0 + s * term1; + b11 = b00; + term0 = c * saveB12 - s * saveB22; + term1 = c * saveB11 - s * saveB12; + b00 = c * term1 - s * term0; + b12 = s * b01; + b01 = c * b01; + + if (Converged(aggressive, b11, b22, b12)) + { + // Compute the Householder reflection. + GetCosSin(half * (b11 - b22), b12, c2, s2); + s = std::sqrt(half * (one - c2)); + c = half * s2 / s; // >= 1/sqrt(2) + + // Update Q by the Householder reflection. + Update3(Q, c, s); + isRotation = !isRotation; + + // Update D = Q^T*B*Q. + saveB11 = b11; + saveB12 = b12; + saveB22 = b22; + term0 = c * saveB11 + s * saveB12; + term1 = c * saveB12 + s * saveB22; + b11 = c * term0 + s * term1; + term0 = s * saveB11 - c * saveB12; + term1 = s * saveB12 - c * saveB22; + b22 = s * term0 - c * term1; + break; + } + } + } + + std::array diagonal = { b00, b11, b22 }; + int i0, i1, i2; + if (sortType >= 1) + { + // diagonal[i0] <= diagonal[i1] <= diagonal[i2] + bool isOdd = Sort(diagonal, i0, i1, i2); + if (!isOdd) + { + isRotation = !isRotation; + } + } + else if (sortType <= -1) + { + // diagonal[i0] >= diagonal[i1] >= diagonal[i2] + bool isOdd = Sort(diagonal, i0, i1, i2); + std::swap(i0, i2); // (i0,i1,i2)->(i2,i1,i0) is odd + if (isOdd) + { + isRotation = !isRotation; + } + } + else + { + i0 = 0; + i1 = 1; + i2 = 2; + } + + eval[0] = diagonal[i0]; + eval[1] = diagonal[i1]; + eval[2] = diagonal[i2]; + evec[0][0] = Q[0][i0]; + evec[0][1] = Q[1][i0]; + evec[0][2] = Q[2][i0]; + evec[1][0] = Q[0][i1]; + evec[1][1] = Q[1][i1]; + evec[1][2] = Q[2][i1]; + evec[2][0] = Q[0][i2]; + evec[2][1] = Q[1][i2]; + evec[2][2] = Q[2][i2]; + + // Ensure the columns of Q form a right-handed set. + if (!isRotation) + { + for (int j = 0; j < 3; ++j) + { + evec[2][j] = -evec[2][j]; + } + } + + return iteration; +} + +template +void SymmetricEigensolver3x3::Update0(Real Q[3][3], Real c, Real s) +const +{ + for (int r = 0; r < 3; ++r) + { + Real tmp0 = c * Q[r][0] + s * Q[r][1]; + Real tmp1 = Q[r][2]; + Real tmp2 = c * Q[r][1] - s * Q[r][0]; + Q[r][0] = tmp0; + Q[r][1] = tmp1; + Q[r][2] = tmp2; + } +} + +template +void SymmetricEigensolver3x3::Update1(Real Q[3][3], Real c, Real s) +const +{ + for (int r = 0; r < 3; ++r) + { + Real tmp0 = c * Q[r][1] - s * Q[r][2]; + Real tmp1 = Q[r][0]; + Real tmp2 = c * Q[r][2] + s * Q[r][1]; + Q[r][0] = tmp0; + Q[r][1] = tmp1; + Q[r][2] = tmp2; + } +} + +template +void SymmetricEigensolver3x3::Update2(Real Q[3][3], Real c, Real s) +const +{ + for (int r = 0; r < 3; ++r) + { + Real tmp0 = c * Q[r][0] + s * Q[r][1]; + Real tmp1 = s * Q[r][0] - c * Q[r][1]; + Q[r][0] = tmp0; + Q[r][1] = tmp1; + } +} + +template +void SymmetricEigensolver3x3::Update3(Real Q[3][3], Real c, Real s) +const +{ + for (int r = 0; r < 3; ++r) + { + Real tmp0 = c * Q[r][1] + s * Q[r][2]; + Real tmp1 = s * Q[r][1] - c * Q[r][2]; + Q[r][1] = tmp0; + Q[r][2] = tmp1; + } +} + +template +void SymmetricEigensolver3x3::GetCosSin(Real u, Real v, Real& cs, + Real& sn) const +{ + Real maxAbsComp = std::max(std::abs(u), std::abs(v)); + if (maxAbsComp > (Real)0) + { + u /= maxAbsComp; // in [-1,1] + v /= maxAbsComp; // in [-1,1] + Real length = std::sqrt(u * u + v * v); + cs = u / length; + sn = v / length; + if (cs > (Real)0) + { + cs = -cs; + sn = -sn; + } + } + else + { + cs = (Real)-1; + sn = (Real)0; + } +} + +template +bool SymmetricEigensolver3x3::Converged(bool aggressive, Real bDiag0, + Real bDiag1, Real bSuper) const +{ + if (aggressive) + { + return bSuper == (Real)0; + } + else + { + Real sum = std::abs(bDiag0) + std::abs(bDiag1); + return sum + std::abs(bSuper) == sum; + } +} + +template +bool SymmetricEigensolver3x3::Sort(std::array const& d, + int& i0, int& i1, int& i2) const +{ + bool odd; + if (d[0] < d[1]) + { + if (d[2] < d[0]) + { + i0 = 2; i1 = 0; i2 = 1; odd = true; + } + else if (d[2] < d[1]) + { + i0 = 0; i1 = 2; i2 = 1; odd = false; + } + else + { + i0 = 0; i1 = 1; i2 = 2; odd = true; + } + } + else + { + if (d[2] < d[1]) + { + i0 = 2; i1 = 1; i2 = 0; odd = false; + } + else if (d[2] < d[0]) + { + i0 = 1; i1 = 2; i2 = 0; odd = true; + } + else + { + i0 = 1; i1 = 0; i2 = 2; odd = false; + } + } + return odd; +} + + +template +void NISymmetricEigensolver3x3::operator() (Real a00, Real a01, Real a02, + Real a11, Real a12, Real a22, std::array& eval, + std::array, 3>& evec) const +{ + // Precondition the matrix by factoring out the maximum absolute value + // of the components. This guards against floating-point overflow when + // computing the eigenvalues. + Real max0 = std::max(std::abs(a00), std::abs(a01)); + Real max1 = std::max(std::abs(a02), std::abs(a11)); + Real max2 = std::max(std::abs(a12), std::abs(a22)); + Real maxAbsElement = std::max(std::max(max0, max1), max2); + if (maxAbsElement == (Real)0) + { + // A is the zero matrix. + eval[0] = (Real)0; + eval[1] = (Real)0; + eval[2] = (Real)0; + evec[0] = { (Real)1, (Real)0, (Real)0 }; + evec[1] = { (Real)0, (Real)1, (Real)0 }; + evec[2] = { (Real)0, (Real)0, (Real)1 }; + return; + } + + Real invMaxAbsElement = (Real)1 / maxAbsElement; + a00 *= invMaxAbsElement; + a01 *= invMaxAbsElement; + a02 *= invMaxAbsElement; + a11 *= invMaxAbsElement; + a12 *= invMaxAbsElement; + a22 *= invMaxAbsElement; + + Real norm = a01 * a01 + a02 * a02 + a12 * a12; + if (norm > (Real)0) + { + // Compute the eigenvalues of A. + + // In the PDF mentioned previously, B = (A - q*I)/p, where q = tr(A)/3 + // with tr(A) the trace of A (sum of the diagonal entries of A) and where + // p = sqrt(tr((A - q*I)^2)/6). + Real q = (a00 + a11 + a22) / (Real)3; + + // The matrix A - q*I is represented by the following, where b00, b11 and + // b22 are computed after these comments, + // +- -+ + // | b00 a01 a02 | + // | a01 b11 a12 | + // | a02 a12 b22 | + // +- -+ + Real b00 = a00 - q; + Real b11 = a11 - q; + Real b22 = a22 - q; + + // The is the variable p mentioned in the PDF. + Real p = std::sqrt((b00 * b00 + b11 * b11 + b22 * b22 + norm * (Real)2) / (Real)6); + + // We need det(B) = det((A - q*I)/p) = det(A - q*I)/p^3. The value + // det(A - q*I) is computed using a cofactor expansion by the first + // row of A - q*I. The cofactors are c00, c01 and c02 and the + // determinant is b00*c00 - a01*c01 + a02*c02. The det(B) is then + // computed finally by the division with p^3. + Real c00 = b11 * b22 - a12 * a12; + Real c01 = a01 * b22 - a12 * a02; + Real c02 = a01 * a12 - b11 * a02; + Real det = (b00 * c00 - a01 * c01 + a02 * c02) / (p * p * p); + + // The halfDet value is cos(3*theta) mentioned in the PDF. The acos(z) + // function requires |z| <= 1, but will fail silently and return NaN + // if the input is larger than 1 in magnitude. To avoid this problem + // due to rounding errors, the halfDet/ value is clamped to [-1,1]. + Real halfDet = det * (Real)0.5; + halfDet = std::min(std::max(halfDet, (Real)-1), (Real)1); + + // The eigenvalues of B are ordered as beta0 <= beta1 <= beta2. The + // number of digits in twoThirdsPi is chosen so that, whether float or + // double, the floating-point number is the closest to theoretical 2*pi/3. + Real angle = std::acos(halfDet) / (Real)3; + Real const twoThirdsPi = (Real)2.09439510239319549; + Real beta2 = std::cos(angle) * (Real)2; + Real beta0 = std::cos(angle + twoThirdsPi) * (Real)2; + Real beta1 = -(beta0 + beta2); + + // The eigenvalues of A are ordered as alpha0 <= alpha1 <= alpha2. + eval[0] = q + p * beta0; + eval[1] = q + p * beta1; + eval[2] = q + p * beta2; + + // Compute the eigenvectors so that the set {evec[0], evec[1], evec[2]} + // is right handed and orthonormal. + if (halfDet >= (Real)0) + { + ComputeEigenvector0(a00, a01, a02, a11, a12, a22, eval[2], evec[2]); + ComputeEigenvector1(a00, a01, a02, a11, a12, a22, evec[2], eval[1], evec[1]); + evec[0] = Cross(evec[1], evec[2]); + } + else + { + ComputeEigenvector0(a00, a01, a02, a11, a12, a22, eval[0], evec[0]); + ComputeEigenvector1(a00, a01, a02, a11, a12, a22, evec[0], eval[1], evec[1]); + evec[2] = Cross(evec[0], evec[1]); + } + } + else + { + // The matrix is diagonal. + eval[0] = a00; + eval[1] = a11; + eval[2] = a22; + evec[0] = { (Real)1, (Real)0, (Real)0 }; + evec[1] = { (Real)0, (Real)1, (Real)0 }; + evec[2] = { (Real)0, (Real)0, (Real)1 }; + } + + // The preconditioning scaled the matrix A, which scales the eigenvalues. + // Revert the scaling. + eval[0] *= maxAbsElement; + eval[1] *= maxAbsElement; + eval[2] *= maxAbsElement; +} + +template +std::array NISymmetricEigensolver3x3::Multiply( + Real s, std::array const& U) +{ + std::array product = { s * U[0], s * U[1], s * U[2] }; + return product; +} + +template +std::array NISymmetricEigensolver3x3::Subtract( + std::array const& U, std::array const& V) +{ + std::array difference = { U[0] - V[0], U[1] - V[1], U[2] - V[2] }; + return difference; +} + +template +std::array NISymmetricEigensolver3x3::Divide( + std::array const& U, Real s) +{ + Real invS = (Real)1 / s; + std::array division = { U[0] * invS, U[1] * invS, U[2] * invS }; + return division; +} + +template +Real NISymmetricEigensolver3x3::Dot(std::array const& U, + std::array const& V) +{ + Real dot = U[0] * V[0] + U[1] * V[1] + U[2] * V[2]; + return dot; +} + +template +std::array NISymmetricEigensolver3x3::Cross(std::array const& U, + std::array const& V) +{ + std::array cross = + { + U[1] * V[2] - U[2] * V[1], + U[2] * V[0] - U[0] * V[2], + U[0] * V[1] - U[1] * V[0] + }; + return cross; +} + +template +void NISymmetricEigensolver3x3::ComputeOrthogonalComplement( + std::array const& W, std::array& U, std::array& V) const +{ + // Robustly compute a right-handed orthonormal set { U, V, W }. The + // vector W is guaranteed to be unit-length, in which case there is no + // need to worry about a division by zero when computing invLength. + Real invLength; + if (std::abs(W[0]) > std::abs(W[1])) + { + // The component of maximum absolute value is either W[0] or W[2]. + invLength = (Real)1 / std::sqrt(W[0] * W[0] + W[2] * W[2]); + U = { -W[2] * invLength, (Real)0, +W[0] * invLength }; + } + else + { + // The component of maximum absolute value is either W[1] or W[2]. + invLength = (Real)1 / std::sqrt(W[1] * W[1] + W[2] * W[2]); + U = { (Real)0, +W[2] * invLength, -W[1] * invLength }; + } + V = Cross(W, U); +} + +template +void NISymmetricEigensolver3x3::ComputeEigenvector0(Real a00, Real a01, + Real a02, Real a11, Real a12, Real a22, Real eval0, std::array& evec0) const +{ + // Compute a unit-length eigenvector for eigenvalue[i0]. The matrix is + // rank 2, so two of the rows are linearly independent. For a robust + // computation of the eigenvector, select the two rows whose cross product + // has largest length of all pairs of rows. + std::array row0 = { a00 - eval0, a01, a02 }; + std::array row1 = { a01, a11 - eval0, a12 }; + std::array row2 = { a02, a12, a22 - eval0 }; + std::array r0xr1 = Cross(row0, row1); + std::array r0xr2 = Cross(row0, row2); + std::array r1xr2 = Cross(row1, row2); + Real d0 = Dot(r0xr1, r0xr1); + Real d1 = Dot(r0xr2, r0xr2); + Real d2 = Dot(r1xr2, r1xr2); + + Real dmax = d0; + int imax = 0; + if (d1 > dmax) + { + dmax = d1; + imax = 1; + } + if (d2 > dmax) + { + imax = 2; + } + + if (imax == 0) + { + evec0 = Divide(r0xr1, std::sqrt(d0)); + } + else if (imax == 1) + { + evec0 = Divide(r0xr2, std::sqrt(d1)); + } + else + { + evec0 = Divide(r1xr2, std::sqrt(d2)); + } +} + +template +void NISymmetricEigensolver3x3::ComputeEigenvector1(Real a00, Real a01, + Real a02, Real a11, Real a12, Real a22, std::array const& evec0, + Real eval1, std::array& evec1) const +{ + // Robustly compute a right-handed orthonormal set { U, V, evec0 }. + std::array U, V; + ComputeOrthogonalComplement(evec0, U, V); + + // Let e be eval1 and let E be a corresponding eigenvector which is a + // solution to the linear system (A - e*I)*E = 0. The matrix (A - e*I) + // is 3x3, not invertible (so infinitely many solutions), and has rank 2 + // when eval1 and eval are different. It has rank 1 when eval1 and eval2 + // are equal. Numerically, it is difficult to compute robustly the rank + // of a matrix. Instead, the 3x3 linear system is reduced to a 2x2 system + // as follows. Define the 3x2 matrix J = [U V] whose columns are the U + // and V computed previously. Define the 2x1 vector X = J*E. The 2x2 + // system is 0 = M * X = (J^T * (A - e*I) * J) * X where J^T is the + // transpose of J and M = J^T * (A - e*I) * J is a 2x2 matrix. The system + // may be written as + // +- -++- -+ +- -+ + // | U^T*A*U - e U^T*A*V || x0 | = e * | x0 | + // | V^T*A*U V^T*A*V - e || x1 | | x1 | + // +- -++ -+ +- -+ + // where X has row entries x0 and x1. + + std::array AU = + { + a00 * U[0] + a01 * U[1] + a02 * U[2], + a01 * U[0] + a11 * U[1] + a12 * U[2], + a02 * U[0] + a12 * U[1] + a22 * U[2] + }; + + std::array AV = + { + a00 * V[0] + a01 * V[1] + a02 * V[2], + a01 * V[0] + a11 * V[1] + a12 * V[2], + a02 * V[0] + a12 * V[1] + a22 * V[2] + }; + + Real m00 = U[0] * AU[0] + U[1] * AU[1] + U[2] * AU[2] - eval1; + Real m01 = U[0] * AV[0] + U[1] * AV[1] + U[2] * AV[2]; + Real m11 = V[0] * AV[0] + V[1] * AV[1] + V[2] * AV[2] - eval1; + + // For robustness, choose the largest-length row of M to compute the + // eigenvector. The 2-tuple of coefficients of U and V in the + // assignments to eigenvector[1] lies on a circle, and U and V are + // unit length and perpendicular, so eigenvector[1] is unit length + // (within numerical tolerance). + Real absM00 = std::abs(m00); + Real absM01 = std::abs(m01); + Real absM11 = std::abs(m11); + Real maxAbsComp; + if (absM00 >= absM11) + { + maxAbsComp = std::max(absM00, absM01); + if (maxAbsComp > (Real)0) + { + if (absM00 >= absM01) + { + m01 /= m00; + m00 = (Real)1 / std::sqrt((Real)1 + m01 * m01); + m01 *= m00; + } + else + { + m00 /= m01; + m01 = (Real)1 / std::sqrt((Real)1 + m00 * m00); + m00 *= m01; + } + evec1 = Subtract(Multiply(m01, U), Multiply(m00, V)); + } + else + { + evec1 = U; + } + } + else + { + maxAbsComp = std::max(absM11, absM01); + if (maxAbsComp > (Real)0) + { + if (absM11 >= absM01) + { + m01 /= m11; + m11 = (Real)1 / std::sqrt((Real)1 + m01 * m01); + m01 *= m11; + } + else + { + m11 /= m01; + m01 = (Real)1 / std::sqrt((Real)1 + m11 * m11); + m11 *= m01; + } + evec1 = Subtract(Multiply(m11, U), Multiply(m01, V)); + } + else + { + evec1 = U; + } + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTCBSplineCurve.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTCBSplineCurve.h new file mode 100644 index 000000000000..73d3470dea59 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTCBSplineCurve.h @@ -0,0 +1,249 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class TCBSplineCurve : public ParametricCurve +{ +public: + // Construction and destruction. The object copies the input arrays. + // The number of points must be at least 2. To validate construction, + // create an object as shown: + // TCBSplineCurve curve(parameters); + // if (!curve) { ; } + virtual ~TCBSplineCurve(); + TCBSplineCurve(int numPoints, Vector const* points, + Real const* times, Real const* tension, Real const* continuity, + Real const* bias); + + // Member access. + inline int GetNumPoints() const; + inline Vector const* GetPoints() const; + inline Real const* GetTensions() const; + inline Real const* GetContinuities() const; + inline Real const* GetBiases() const; + + // Evaluation of the curve. The function supports derivative calculation + // through order 3; that is, maxOrder <= 3 is required. If you want + // only the position, pass in maxOrder of 0. If you want the position and + // first derivative, pass in maxOrder of 1, and so on. The output + // 'values' are ordered as: position, first derivative, second derivative, + // third derivative. + virtual void Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const; + +protected: + // Support for construction. + void ComputePoly(int i0, int i1, int i2, int i3); + + // Determine the index i for which times[i] <= t < times[i+1]. + void GetKeyInfo(Real t, int& key, Real& dt) const; + + std::vector> mPoints; + std::vector mTension, mContinuity, mBias; + + // Polynomial coefficients. mA are the degree 0 coefficients, mB are + // the degree 1 coefficients, mC are the degree 2 coefficients, and mD + // are the degree 3 coefficients. + std::vector> mA, mB, mC, mD; +}; + + +template +TCBSplineCurve::~TCBSplineCurve() +{ +} + +template +TCBSplineCurve::TCBSplineCurve(int numPoints, + Vector const* points, Real const* times, Real const* tension, + Real const* continuity, Real const* bias) + : + ParametricCurve(numPoints - 1, times) +{ + if (numPoints < 2 || !points) + { + LogError("Invalid input."); + return; + } + + mPoints.resize(numPoints); + mTension.resize(numPoints); + mContinuity.resize(numPoints); + mBias.resize(numPoints); + std::copy(points, points + numPoints, mPoints.begin()); + std::copy(tension, tension + numPoints, mTension.begin()); + std::copy(continuity, continuity + numPoints, mContinuity.begin()); + std::copy(bias, bias + numPoints, mBias.begin()); + + int numSegments = numPoints - 1; + mA.resize(numSegments); + mB.resize(numSegments); + mC.resize(numSegments); + mD.resize(numSegments); + + // For now, treat the first point as if it occurred twice. + ComputePoly(0, 0, 1, 2); + + for (int i = 1; i < numSegments - 1; ++i) + { + ComputePoly(i - 1, i, i + 1, i + 2); + } + + // For now, treat the last point as if it occurred twice. + ComputePoly(numSegments - 2, numSegments - 1, numSegments, numSegments); + + this->mConstructed = true; +} + +template inline +int TCBSplineCurve::GetNumPoints() const +{ + return static_cast(mPoints.size()); +} + +template inline +Vector const* TCBSplineCurve::GetPoints() const +{ + return &mPoints[0]; +} + +template inline +Real const* TCBSplineCurve::GetTensions() const +{ + return &mTension[0]; +} + +template inline +Real const* TCBSplineCurve::GetContinuities() const +{ + return &mContinuity[0]; +} + +template inline +Real const* TCBSplineCurve::GetBiases() const +{ + return &mBias[0]; +} + +template +void TCBSplineCurve::Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const +{ + if (!this->mConstructed) + { + // Errors were already generated during construction. + for (unsigned int order = 0; order < 4; ++order) + { + values[order].MakeZero(); + } + return; + } + + int key; + Real dt; + GetKeyInfo(t, key, dt); + dt /= (this->mTime[key + 1] - this->mTime[key]); + + // Compute position. + values[0] = mA[key] + dt * (mB[key] + dt * (mC[key] + dt * mD[key])); + if (maxOrder >= 1) + { + // Compute first derivative. + values[1] = mB[key] + dt * (mC[key] * ((Real)2) + mD[key] * + (((Real)3) * dt)); + if (maxOrder >= 2) + { + // Compute second derivative. + values[2] = mC[key] * ((Real)2) + mD[key] * (((Real)6) * dt); + if (maxOrder == 3) + { + values[3] = ((Real)6) * mD[key]; + } + else + { + values[3].MakeZero(); + } + } + } +} + +template +void TCBSplineCurve::ComputePoly(int i0, int i1, int i2, int i3) +{ + Vector diff = mPoints[i2] - mPoints[i1]; + Real dt = this->mTime[i2] - this->mTime[i1]; + + // Build multipliers at P1. + Real oneMinusT0 = (Real)1 - mTension[i1]; + Real oneMinusC0 = (Real)1 - mContinuity[i1]; + Real onePlusC0 = (Real)1 + mContinuity[i1]; + Real oneMinusB0 = (Real)1 - mBias[i1]; + Real onePlusB0 = (Real)1 + mBias[i1]; + Real adj0 = ((Real)2)*dt / (this->mTime[i2] - this->mTime[i0]); + Real out0 = ((Real)0.5)*adj0*oneMinusT0*onePlusC0*onePlusB0; + Real out1 = ((Real)0.5)*adj0*oneMinusT0*oneMinusC0*oneMinusB0; + + // Build outgoing tangent at P1. + Vector tOut = out1*diff + out0*(mPoints[i1] - mPoints[i0]); + + // Build multipliers at point P2. + Real oneMinusT1 = (Real)1 - mTension[i2]; + Real oneMinusC1 = (Real)1 - mContinuity[i2]; + Real onePlusC1 = (Real)1 + mContinuity[i2]; + Real oneMinusB1 = (Real)1 - mBias[i2]; + Real onePlusB1 = (Real)1 + mBias[i2]; + Real adj1 = ((Real)2)*dt / (this->mTime[i3] - this->mTime[i1]); + Real in0 = ((Real)0.5)*adj1*oneMinusT1*oneMinusC1*onePlusB1; + Real in1 = ((Real)0.5)*adj1*oneMinusT1*onePlusC1*oneMinusB1; + + // Build incoming tangent at P2. + Vector tIn = in1*(mPoints[i3] - mPoints[i2]) + in0*diff; + + mA[i1] = mPoints[i1]; + mB[i1] = tOut; + mC[i1] = ((Real)3)*diff - ((Real)2)*tOut - tIn; + mD[i1] = ((Real)-2)*diff + tOut + tIn; +} + +template +void TCBSplineCurve::GetKeyInfo(Real t, int& key, Real& dt) const +{ + int numSegments = static_cast(mA.size()); + if (t <= this->mTime[0]) + { + key = 0; + dt = (Real)0; + } + else if (t >= this->mTime[numSegments]) + { + key = numSegments - 1; + dt = this->mTime[numSegments] - this->mTime[numSegments - 1]; + } + else + { + for (int i = 0; i < numSegments; ++i) + { + if (t < this->mTime[i + 1]) + { + key = i; + dt = t - this->mTime[i]; + break; + } + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTIQuery.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTIQuery.h new file mode 100644 index 000000000000..c7afe55630a5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTIQuery.h @@ -0,0 +1,33 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include + +namespace gte +{ + +// Test-intersection queries. + +template +class TIQuery +{ +public: + struct Result + { + // A TIQuery-base class B must define a B::Result struct with member + // 'bool intersect'. A TIQuery-derived class D must also derive a + // D::Result from B:Result but may have no members. The member + // 'intersect' is 'true' iff the primitives intersect. The operator() + // is non-const to allow TIQuery to store and modify private state + // that supports the query. + }; + Result operator()(Type0 const& primitive0, Type1 const& primitive1); +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTSManifoldMesh.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTSManifoldMesh.cpp new file mode 100644 index 000000000000..6118f12545ab --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTSManifoldMesh.cpp @@ -0,0 +1,255 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +#include + +using namespace gte; + +TSManifoldMesh::~TSManifoldMesh() +{ +} + +TSManifoldMesh::TSManifoldMesh(TCreator tCreator, SCreator sCreator) + : + mTCreator(tCreator ? tCreator : CreateTriangle), + mSCreator(sCreator ? sCreator : CreateTetrahedron), + mAssertOnNonmanifoldInsertion(true) +{ +} + +TSManifoldMesh::TSManifoldMesh(TSManifoldMesh const& mesh) +{ + *this = mesh; +} + +TSManifoldMesh& TSManifoldMesh::operator=(TSManifoldMesh const& mesh) +{ + Clear(); + + mTCreator = mesh.mTCreator; + mSCreator = mesh.mSCreator; + mAssertOnNonmanifoldInsertion = mesh.mAssertOnNonmanifoldInsertion; + for (auto const& element : mesh.mSMap) + { + Insert(element.first.V[0], element.first.V[1], element.first.V[2], element.first.V[3]); + } + + return *this; +} + +TSManifoldMesh::TMap const& TSManifoldMesh::GetTriangles() const +{ + return mTMap; +} + +TSManifoldMesh::SMap const& TSManifoldMesh::GetTetrahedra() const +{ + return mSMap; +} + +std::shared_ptr TSManifoldMesh::CreateTriangle(int v0, int v1, int v2) +{ + return std::make_shared(v0, v1, v2); +} + +std::shared_ptr TSManifoldMesh::CreateTetrahedron(int v0, int v1, int v2, int v3) +{ + return std::make_shared(v0, v1, v2, v3); +} + +void TSManifoldMesh::AssertOnNonmanifoldInsertion(bool doAssert) +{ + mAssertOnNonmanifoldInsertion = doAssert; +} + +std::shared_ptr TSManifoldMesh::Insert(int v0, int v1, int v2, int v3) +{ + TetrahedronKey skey(v0, v1, v2, v3); + if (mSMap.find(skey) != mSMap.end()) + { + // The tetrahedron already exists. Return a null pointer as a signal + // to the caller that the insertion failed. + return nullptr; + } + + // Add the new tetrahedron. + std::shared_ptr tetra = mSCreator(v0, v1, v2, v3); + mSMap[skey] = tetra; + + // Add the faces to the mesh if they do not already exist. + for (int i = 0; i < 4; ++i) + { + auto opposite = TetrahedronKey::oppositeFace[i]; + TriangleKey tkey(tetra->V[opposite[0]], tetra->V[opposite[1]], tetra->V[opposite[2]]); + std::shared_ptr face; + auto titer = mTMap.find(tkey); + if (titer == mTMap.end()) + { + // This is the first time the face is encountered. + face = mTCreator(tetra->V[opposite[0]], tetra->V[opposite[1]], tetra->V[opposite[2]]); + mTMap[tkey] = face; + + // Update the face and tetrahedron. + face->T[0] = tetra; + tetra->T[i] = face; + } + else + { + // This is the second time the face is encountered. + face = titer->second; + if (!face) + { + LogError("Unexpected condition."); + return nullptr; + } + + // Update the face. + if (face->T[1].lock()) + { + if (mAssertOnNonmanifoldInsertion) + { + LogInformation("The mesh must be manifold."); + } + return nullptr; + } + face->T[1] = tetra; + + // Update the adjacent tetrahedra. + auto adjacent = face->T[0].lock(); + if (!adjacent) + { + LogError("Unexpected condition."); + return nullptr; + } + for (int j = 0; j < 4; ++j) + { + if (adjacent->T[j].lock() == face) + { + adjacent->S[j] = tetra; + break; + } + } + + // Update the tetrahedron. + tetra->T[i] = face; + tetra->S[i] = adjacent; + } + } + + return tetra; +} + +bool TSManifoldMesh::Remove(int v0, int v1, int v2, int v3) +{ + TetrahedronKey skey(v0, v1, v2, v3); + auto siter = mSMap.find(skey); + if (siter == mSMap.end()) + { + // The tetrahedron does not exist. + return false; + } + + // Get the tetrahedron. + std::shared_ptr tetra = siter->second; + + // Remove the faces and update adjacent tetrahedra if necessary. + for (int i = 0; i < 4; ++i) + { + // Inform the faces the tetrahedron is being deleted. + auto face = tetra->T[i].lock(); + if (!face) + { + // The triangle edge should be nonnull. + LogError("Unexpected condition."); + return false; + } + + if (face->T[0].lock() == tetra) + { + // One-tetrahedron faces always have pointer at index zero. + face->T[0] = face->T[1]; + face->T[1].reset(); + } + else if (face->T[1].lock() == tetra) + { + face->T[1].reset(); + } + else + { + LogError("Unexpected condition."); + return false; + } + + // Remove the face if you have the last reference to it. + if (!face->T[0].lock() && !face->T[1].lock()) + { + TriangleKey tkey(face->V[0], face->V[1], face->V[2]); + mTMap.erase(tkey); + } + + // Inform adjacent tetrahedra the tetrahedron is being deleted. + auto adjacent = tetra->S[i].lock(); + if (adjacent) + { + for (int j = 0; j < 4; ++j) + { + if (adjacent->S[j].lock() == tetra) + { + adjacent->S[j].reset(); + break; + } + } + } + } + + mSMap.erase(skey); + return true; +} + +void TSManifoldMesh::Clear() +{ + mTMap.clear(); + mSMap.clear(); +} + +bool TSManifoldMesh::IsClosed() const +{ + for (auto const& element : mSMap) + { + auto tri = element.second; + if (!tri->S[0].lock() || !tri->S[1].lock()) + { + return false; + } + } + return true; +} + +TSManifoldMesh::Triangle::~Triangle() +{ +} + +TSManifoldMesh::Triangle::Triangle(int v0, int v1, int v2) +{ + V[0] = v0; + V[1] = v1; + V[2] = v2; +} + +TSManifoldMesh::Tetrahedron::~Tetrahedron() +{ +} + +TSManifoldMesh::Tetrahedron::Tetrahedron(int v0, int v1, int v2, int v3) +{ + V[0] = v0; + V[1] = v1; + V[2] = v2; + V[3] = v3; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTSManifoldMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTSManifoldMesh.h new file mode 100644 index 000000000000..01a2ad4a66e3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTSManifoldMesh.h @@ -0,0 +1,120 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +class GTE_IMPEXP TSManifoldMesh +{ +public: + // Triangle data types. + class Triangle; + typedef std::shared_ptr (*TCreator)(int, int, int); + typedef std::map, std::shared_ptr> TMap; + + // Tetrahedron data types. + class Tetrahedron; + typedef std::shared_ptr (*SCreator)(int, int, int, int); + typedef std::map, std::shared_ptr> SMap; + + // Triangle object. + class GTE_IMPEXP Triangle + { + public: + virtual ~Triangle(); + Triangle(int v0, int v1, int v2); + + // Vertices of the face. + int V[3]; + + // Tetrahedra sharing the face. + std::weak_ptr T[2]; + }; + + // Tetrahedron object. + class GTE_IMPEXP Tetrahedron + { + public: + virtual ~Tetrahedron(); + Tetrahedron(int v0, int v1, int v2, int v3); + + // Vertices, listed in an order so that each face vertices in + // counterclockwise order when viewed from outside the tetrahedron. + int V[4]; + + // Adjacent faces. T[i] points to the triangle face opposite V[i]. + // T[0] points to face (V[1],V[2],V[3]) + // T[1] points to face (V[0],V[3],V[2]) + // T[2] points to face (V[0],V[1],V[3]) + // T[3] points to face (V[0],V[2],V[1]) + std::weak_ptr T[4]; + + // Adjacent tetrahedra. S[i] points to the adjacent tetrahedron + // sharing face T[i]. + std::weak_ptr S[4]; + }; + + + // Construction and destruction. + virtual ~TSManifoldMesh(); + TSManifoldMesh(TCreator tCreator = nullptr, SCreator sCreator = nullptr); + + // Support for a deep copy of the mesh. The mTMap and mSMap objects have + // dynamically allocated memory for triangles and tetrahedra. A shallow + // copy of the pointers to this memory is problematic. Allowing sharing, + // say, via std::shared_ptr, is an option but not really the intent of + // copying the mesh graph. + TSManifoldMesh(TSManifoldMesh const& mesh); + TSManifoldMesh& operator=(TSManifoldMesh const& mesh); + + // Member access. + TMap const& GetTriangles() const; + SMap const& GetTetrahedra() const; + + // If the insertion of a tetrahedron fails because the mesh would become + // nonmanifold, the default behavior is to trigger a LogError message. + // You can disable this behavior in situations where you want the Logger + // system on but you want to continue gracefully without an assertion. + void AssertOnNonmanifoldInsertion(bool doAssert); + + // If is not in the mesh, a Tetrahedron object is created + // and returned; otherwise, is in the mesh and nullptr is + // returned. If the insertion leads to a nonmanifold mesh, the call + // fails with a nullptr returned. + std::shared_ptr Insert(int v0, int v1, int v2, int v3); + + // If is in the mesh, it is removed and 'true' is returned; + // otherwise, is not in the mesh and 'false' is returned. + bool Remove(int v0, int v1, int v2, int v3); + + // Destroy the triangles and tetrahedra to obtain an empty mesh. + virtual void Clear(); + + // A manifold mesh is closed if each face is shared twice. + bool IsClosed() const; + +protected: + // The triangle data and default triangle creation. + static std::shared_ptr CreateTriangle(int v0, int v1, int v2); + TCreator mTCreator; + TMap mTMap; + + // The tetrahedron data and default tetrahedron creation. + static std::shared_ptr CreateTetrahedron(int v0, int v1, int v2, int v3); + SCreator mSCreator; + SMap mSMap; + bool mAssertOnNonmanifoldInsertion; // default: true +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTanEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTanEstimate.h new file mode 100644 index 000000000000..472d53ba8e5b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTanEstimate.h @@ -0,0 +1,187 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Minimax polynomial approximations to tan(x). The polynomial p(x) of +// degree D has only odd-power terms, is required to have linear term x, +// and p(pi/4) = tan(pi/4) = 1. It minimizes the quantity +// maximum{|tan(x) - p(x)| : x in [-pi/4,pi/4]} over all polynomials of +// degree D subject to the constraints mentioned. + +namespace gte +{ + +template +class TanEstimate +{ +public: + // The input constraint is x in [-pi/4,pi/4]. For example, + // float x; // in [-pi/4,pi/4] + // float result = TanEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input x can be any real number. Range reduction is used to + // generate a value y in [-pi/2,pi/2]. If |y| <= pi/4, then the + // polynomial is evaluated. If y in (pi/4,pi/2), set z = y - pi/4 + // and use the identity + // tan(y) = tan(z + pi/4) = [1 + tan(z)]/[1 - tan(z)] + // Be careful when evaluating at y nearly pi/2, because tan(y) + // becomes infinite. For example, + // float x; // x any real number + // float result = TanEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<3>, Real x); + inline static Real Evaluate(degree<5>, Real x); + inline static Real Evaluate(degree<7>, Real x); + inline static Real Evaluate(degree<9>, Real x); + inline static Real Evaluate(degree<11>, Real x); + inline static Real Evaluate(degree<13>, Real x); + + // Support for range reduction. + inline static void Reduce(Real x, Real& y); +}; + + +template +template +inline Real TanEstimate::Degree(Real x) +{ + return Evaluate(degree(), x); +} + +template +template +inline Real TanEstimate::DegreeRR(Real x) +{ + Real y; + Reduce(x, y); + if (std::abs(y) <= (Real)GTE_C_QUARTER_PI) + { + return Degree(y); + } + else if (y > (Real)GTE_C_QUARTER_PI) + { + Real poly = Degree(y - (Real)GTE_C_QUARTER_PI); + return ((Real)1 + poly) / ((Real)1 - poly); + } + else + { + Real poly = Degree(y + (Real)GTE_C_QUARTER_PI); + return -((Real)1 - poly) / ((Real)1 + poly); + } +} + +template +inline Real TanEstimate::Evaluate(degree<3>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG3_C1; + poly = (Real)GTE_C_TAN_DEG3_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real TanEstimate::Evaluate(degree<5>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG5_C2; + poly = (Real)GTE_C_TAN_DEG5_C1 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG5_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real TanEstimate::Evaluate(degree<7>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG7_C3; + poly = (Real)GTE_C_TAN_DEG7_C2 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG7_C1 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG7_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real TanEstimate::Evaluate(degree<9>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG9_C4; + poly = (Real)GTE_C_TAN_DEG9_C3 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG9_C2 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG9_C1 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG9_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real TanEstimate::Evaluate(degree<11>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG11_C5; + poly = (Real)GTE_C_TAN_DEG11_C4 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG11_C3 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG11_C2 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG11_C1 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG11_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real TanEstimate::Evaluate(degree<13>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG13_C6; + poly = (Real)GTE_C_TAN_DEG13_C5 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG13_C4 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG13_C3 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG13_C2 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG13_C1 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG13_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline void TanEstimate::Reduce(Real x, Real& y) +{ + // Map x to y in [-pi,pi], x = pi*quotient + remainder. + y = std::fmod(x, (Real)GTE_C_PI); + + // Map y to [-pi/2,pi/2] with tan(y) = tan(x). + if (y > (Real)GTE_C_HALF_PI) + { + y -= (Real)GTE_C_PI; + } + else if (y < (Real)-GTE_C_HALF_PI) + { + y += (Real)GTE_C_PI; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedron3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedron3.h new file mode 100644 index 000000000000..ac1aab871ca3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedron3.h @@ -0,0 +1,200 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The tetrahedron is represented as an array of four vertices: V0, V1, V2, +// and V3. The vertices are ordered so that the triangular faces are +// counterclockwise-ordered triangles when viewed by an observer outside the +// tetrahedron: +// face 0 = +// face 1 = +// face 2 = +// face 3 = + +namespace gte +{ + +template +class Tetrahedron3 +{ +public: + // Construction and destruction. The default constructor sets the + // vertices to (0,0,0), (1,0,0), (0,1,0), and (0,0,1). + Tetrahedron3(); + Tetrahedron3(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Vector3 const& v3); + Tetrahedron3(Vector3 const inV[4]); + + // Get the vertex indices for the specified face. The input 'face' must + // be in {0,1,2,3}. + void GetFaceIndices(int face, int index[3]) const; + + // Construct the planes of the faces. The planes have outer pointing + // normal vectors. The plane indexing is the same as the face indexing + // mentioned previously. + void GetPlanes(Plane3 plane[4]) const; + + // Public member access. + Vector3 v[4]; + +public: + // Comparisons to support sorted containers. + bool operator==(Tetrahedron3 const& tetrahedron) const; + bool operator!=(Tetrahedron3 const& tetrahedron) const; + bool operator< (Tetrahedron3 const& tetrahedron) const; + bool operator<=(Tetrahedron3 const& tetrahedron) const; + bool operator> (Tetrahedron3 const& tetrahedron) const; + bool operator>=(Tetrahedron3 const& tetrahedron) const; +}; + + +template +Tetrahedron3::Tetrahedron3() +{ + v[0] = Vector3::Zero(); + v[1] = Vector3::Unit(0); + v[2] = Vector3::Unit(1); + v[3] = Vector3::Unit(2); +} + +template +Tetrahedron3::Tetrahedron3(Vector3 const& v0, + Vector3 const& v1, Vector3 const& v2, Vector3 const& v3) +{ + v[0] = v0; + v[1] = v1; + v[2] = v2; + v[3] = v3; +} + +template +Tetrahedron3::Tetrahedron3(Vector3 const inV[4]) +{ + for (int i = 0; i < 4; ++i) + { + v[i] = inV[i]; + } +} + +template +void Tetrahedron3::GetFaceIndices(int face, int index[3]) +const +{ + if (face == 0) + { + index[0] = 0; + index[1] = 2; + index[2] = 1; + } + else if (face == 1) + { + index[0] = 0; + index[1] = 1; + index[2] = 3; + } + else if (face == 2) + { + index[0] = 0; + index[1] = 3; + index[2] = 2; + } + else // face == 3 (no index validation is performed) + { + index[0] = 1; + index[1] = 2; + index[2] = 3; + } +} + +template +void Tetrahedron3::GetPlanes(Plane3 plane[4]) const +{ + Vector3 edge10 = v[1] - v[0]; + Vector3 edge20 = v[2] - v[0]; + Vector3 edge30 = v[3] - v[0]; + Vector3 edge21 = v[2] - v[1]; + Vector3 edge31 = v[3] - v[1]; + + plane[0].normal = UnitCross(edge20, edge10); // + plane[1].normal = UnitCross(edge10, edge30); // + plane[2].normal = UnitCross(edge30, edge20); // + plane[3].normal = UnitCross(edge21, edge31); // + + Real det = Dot(edge10, plane[3].normal); + if (det < (Real)0) + { + // The normals are inner pointing, reverse their directions. + for (int i = 0; i < 4; ++i) + { + plane[i].normal = -plane[i].normal; + } + } + + for (int i = 0; i < 4; ++i) + { + plane[i].constant = Dot(v[i], plane[i].normal); + } +} + +template +bool Tetrahedron3::operator==(Tetrahedron3 const& tetrahedron) const +{ + return v[0] == tetrahedron.v[0] + && v[1] == tetrahedron.v[1] + && v[2] == tetrahedron.v[2] + && v[3] == tetrahedron.v[3]; +} + +template +bool Tetrahedron3::operator!=(Tetrahedron3 const& tetrahedron) const +{ + return !operator==(tetrahedron); +} + +template +bool Tetrahedron3::operator<(Tetrahedron3 const& tetrahedron) const +{ + for (int i = 0; i < 4; ++i) + { + if (v[i] < tetrahedron.v[i]) + { + return true; + } + + if (v[i] > tetrahedron.v[i]) + { + return false; + } + } + + return false; +} + +template +bool Tetrahedron3::operator<=(Tetrahedron3 const& tetrahedron) const +{ + return operator<(tetrahedron) || operator==(tetrahedron); +} + +template +bool Tetrahedron3::operator>(Tetrahedron3 const& tetrahedron) const +{ + return !operator<=(tetrahedron); +} + +template +bool Tetrahedron3::operator>=(Tetrahedron3 const& tetrahedron) const +{ + return !operator<(tetrahedron); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedronKey.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedronKey.cpp new file mode 100644 index 000000000000..efb008836d56 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedronKey.cpp @@ -0,0 +1,145 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/05/22) + +#include +#include +#include + +namespace +{ + void Permute(int u0, int u1, int u2, int V[4]) + { + // Once V[0] is determined, create a permutation (V[1],V[2],V[3]) so + // that (V[0],V[1],V[2],V[3]) is a positive permutation of + // (v0,v1,v2,v3). + if (u0 < u1) + { + if (u0 < u2) + { + // u0 is minimum + V[1] = u0; + V[2] = u1; + V[3] = u2; + } + else + { + // u2 is minimum + V[1] = u2; + V[2] = u0; + V[3] = u1; + } + } + else + { + if (u1 < u2) + { + // u1 is minimum + V[1] = u1; + V[2] = u2; + V[3] = u0; + } + else + { + // u2 is minimum + V[1] = u2; + V[2] = u0; + V[3] = u1; + } + } + } +} + +namespace gte +{ + // TetrahedronKey + + template<> std::array const TetrahedronKey::oppositeFace[4] = + { + {{ 1, 2, 3 }}, + {{ 0, 3, 2 }}, + {{ 0, 1, 3 }}, + {{ 0, 2, 1 }} + }; + + template<> + TetrahedronKey::TetrahedronKey() + { + V[0] = -1; + V[1] = -1; + V[2] = -1; + V[3] = -1; + } + + template<> + TetrahedronKey::TetrahedronKey(int v0, int v1, int v2, int v3) + { + int imin = 0; + V[0] = v0; + if (v1 < V[0]) + { + V[0] = v1; + imin = 1; + } + if (v2 < V[0]) + { + V[0] = v2; + imin = 2; + } + if (v3 < V[0]) + { + V[0] = v3; + imin = 3; + } + + if (imin == 0) + { + Permute(v1, v2, v3, V); + } + else if (imin == 1) + { + Permute(v0, v3, v2, V); + } + else if (imin == 2) + { + Permute(v0, v1, v3, V); + } + else // imin == 3 + { + Permute(v0, v2, v1, V); + } + } + + + // TetrahedronKey + + template<> std::array const TetrahedronKey::oppositeFace[4] = + { + {{ 1, 2, 3 }}, + {{ 0, 3, 2 }}, + {{ 0, 1, 3 }}, + {{ 0, 2, 1 }} + }; + + template<> + TetrahedronKey::TetrahedronKey() + { + V[0] = -1; + V[1] = -1; + V[2] = -1; + V[3] = -1; + } + + template<> + TetrahedronKey::TetrahedronKey(int v0, int v1, int v2, int v3) + { + V[0] = v0; + V[1] = v1; + V[2] = v2; + V[3] = v3; + std::sort(&V[0], &V[4]); + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedronKey.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedronKey.h new file mode 100644 index 000000000000..c36db9c77375 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedronKey.h @@ -0,0 +1,61 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/05/22) + +#pragma once + +#include +#include + +namespace gte +{ + template + class TetrahedronKey : public FeatureKey<4, Ordered> + { + public: + // An ordered tetrahedron has V[0] = min(v0,v1,v2,v3). Let {u1,u2,u3} + // be the set of inputs excluding the one assigned to V[0] and define + // V[1] = min(u1,u2,u3). Choose (V[1],V[2],V[3]) to be a permutation + // of (u1,u2,u3) so that the final storage is one of + // (v0,v1,v2,v3), (v0,v2,v3,v1), (v0,v3,v1,v2) + // (v1,v3,v2,v0), (v1,v2,v0,v3), (v1,v0,v3,v2) + // (v2,v3,v0,v1), (v2,v0,v1,v3), (v2,v1,v3,v0) + // (v3,v1,v0,v2), (v3,v0,v2,v1), (v3,v2,v1,v0) + // The idea is that if v0 corresponds to (1,0,0,0), v1 corresponds to + // (0,1,0,0), v2 corresponds to (0,0,1,0), and v3 corresponds to + // (0,0,0,1), the ordering (v0,v1,v2,v3) corresponds to the 4x4 + // identity matrix I; the rows are the specified 4-tuples. The + // permutation (V[0],V[1],V[2],V[3]) induces a permutation of the rows + // of the identity matrix to form a permutation matrix P with det(P) + // = 1 = det(I). + // + // An unordered tetrahedron stores a permutation of (v0,v1,v2,v3) so + // that V[0] < V[1] < V[2] < V[3]. + TetrahedronKey(); // creates key (-1,-1,-1,-1) + explicit TetrahedronKey(int v0, int v1, int v2, int v3); + + // Indexing for the vertices of the triangle opposite a vertex. The + // triangle opposite vertex j is + // + // and is listed in counterclockwise order when viewed from outside + // the tetrahedron. + static std::array const oppositeFace[4]; + }; + +#if !defined(__MSWINDOWS__) + // Apple LLVM 6.1.0 (clang-602.0.49) correctly requires these declarations + // to occur before the definition in GteTetrahedronKey.cpp. From the C++ + // specification: + // An explicit specialization of a static data member of a template is + // a definition if the declaration includes an initializer; otherwise, + // it is a declaration. + // If these declarations are exposed for MSVS 2013, the compiler generates + // error C2086, claiming that this is a definition (rather than a + // declaration) and the cpp file has a redefinition. + template<> std::array const TetrahedronKey::oppositeFace[4]; + template<> std::array const TetrahedronKey::oppositeFace[4]; +#endif +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTorus3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTorus3.h new file mode 100644 index 000000000000..a66146089319 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTorus3.h @@ -0,0 +1,254 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include + +// A torus with origin (0,0,0), outer radius r0 and inner radius r1 (with +// (r0 >= r1) is defined implicitly as follows. The point P0 = (x,y,z) is on +// the torus. Its projection onto the xy-plane is P1 = (x,y,0). The circular +// cross section of the torus that contains the projection has radius r0 and +// center P2 = r0*(x,y,0)/sqrt(x^2+y^2). The points triangle is a +// right triangle with right angle at P1. The hypotenuse has length +// r1, leg has length z and leg has length |r0 - sqrt(x^2+y^2)|. +// The Pythagorean theorem says z^2 + |r0 - sqrt(x^2+y^2)|^2 = r1^2. This can +// be algebraically manipulated to +// (x^2 + y^2 + z^2 + r0^2 - r1^2)^2 - 4 * r0^2 * (x^2 + y^2) = 0 +// +// A parametric form is +// x = (r0 + r1 * cos(v)) * cos(u) +// y = (r0 + r1 * cos(v)) * sin(u) +// z = r1 * sin(v) +// for u in [0,2*pi) and v in [0,2*pi). +// +// Generally, let the torus center be C with plane of symmetry containing C +// and having directions D0 and D1. The axis of symmetry is the line +// containing C and having direction N (the plane normal). The radius from +// the center of the torus is r0 and the radius of the tube of the torus is +// r1. A point P may be written as P = C + x*D0 + y*D1 + z*N, where matrix +// [D0 D1 N] is orthonormal and has determinant 1. Thus, x = Dot(D0,P-C), +// y = Dot(D1,P-C) and z = Dot(N,P-C). The implicit form is +// [|P-C|^2 + r0^2 - r1^2]^2 - 4*r0^2*[|P-C|^2 - (Dot(N,P-C))^2] = 0 +// Observe that D0 and D1 are not present in the equation, which is to be +// expected by the symmetry. The parametric form is +// P(u,v) = C + (r0 + r1*cos(v))*(cos(u)*D0 + sin(u)*D1) + r1*sin(v)*N +// for u in [0,2*pi) and v in [0,2*pi). +// +// In the class Torus3, the members are 'center' C, 'direction0' D0, +// 'direction1' D1, 'normal' N, 'radius0' r0 and 'radius1' r1. + +namespace gte +{ + +template +class Torus3 +{ +public: + // Construction and destruction. The default constructor sets center to + // (0,0,0), direction0 to (1,0,0), direction1 to (0,1,0), normal to + // (0,0,1), radius0 to 2, and radius1 to 1. + Torus3(); + Torus3(Vector3 const& inCenter, Vector3 const& inDirection0, + Vector3 const& inDirection1, Vector3 const& inNormal, + Real inRadius0, Real inRadius1); + + // Evaluation of the surface. The function supports derivative + // calculation through order 2; that is, maxOrder <= 2 is required. If + // you want only the position, pass in maxOrder of 0. If you want the + // position and first-order derivatives, pass in maxOrder of 1, and so on. + // The output 'values' are ordered as: position X; first-order derivatives + // dX/du, dX/dv; second-order derivatives d2X/du2, d2X/dudv, d2X/dv2. + void Evaluate(Real u, Real v, unsigned int maxOrder, Vector3 values[6]) const; + + // Reverse lookup of parameters from position. + void GetParameters(Vector3 const& X, Real& u, Real& v) const; + + Vector3 center, direction0, direction1, normal; + Real radius0, radius1; + +public: + // Comparisons to support sorted containers. + bool operator==(Torus3 const& torus) const; + bool operator!=(Torus3 const& torus) const; + bool operator< (Torus3 const& torus) const; + bool operator<=(Torus3 const& torus) const; + bool operator> (Torus3 const& torus) const; + bool operator>=(Torus3 const& torus) const; +}; + + +template +Torus3::Torus3() + : + center(Vector3::Zero()), + direction0(Vector3::Unit(0)), + direction1(Vector3::Unit(1)), + normal(Vector3::Unit(2)), + radius0((Real)2), + radius1((Real)1) +{ +} + +template +Torus3::Torus3(Vector3 const& inCenter, + Vector3 const& inDirection0, Vector3 const& inDirection1, + Vector3 const& inNormal, Real inRadius0, Real inRadius1) + : + center(inCenter), + direction0(inDirection0), + direction1(inDirection1), + normal(inNormal), + radius0(inRadius0), + radius1(inRadius1) +{ +} + +template +void Torus3::Evaluate(Real u, Real v, unsigned int maxOrder, + Vector3 values[6]) const +{ + // Compute position. + Real csu = std::cos(u), snu = std::sin(u), csv = std::cos(v), snv = std::sin(v); + Real r1csv = radius1 * csv; + Real r1snv = radius1 * snv; + Real r0pr1csv = radius0 + r1csv; + Vector3 combo0 = csu * direction0 + snu * direction1; + Vector3 r0pr1csvcombo0 = r0pr1csv * combo0; + Vector3 r1snvnormal = r1snv * normal; + values[0] = center + r0pr1csvcombo0 + r1snvnormal; + + if (maxOrder >= 1) + { + // Compute first-order derivatives. + Vector3 combo1 = -snu * direction0 + csu * direction1; + values[1] = r0pr1csv * combo1; + values[2] = -r1snv * combo0 + r1csv * normal; + + if (maxOrder >= 2) + { + // Compute second-order derivatives. + values[3] = -r0pr1csvcombo0; + values[4] = -r1snv * combo1; + values[5] = -r1csv * combo0 - r1snvnormal; + + if (maxOrder >= 3) + { + // These orders are not supported. + for (int i = 0; i < 6; ++i) + { + values[i] = Vector3::Zero(); + } + } + } + } +} + +template +void Torus3::GetParameters(Vector3 const& X, Real& u, Real& v) const +{ + Vector3 delta = X - center; + Real dot0 = Dot(direction0, delta); // (r0 + r1*cos(v))*cos(u) + Real dot1 = Dot(direction1, delta); // (r0 + r1*cos(v))*sin(u) + Real dot2 = Dot(normal, delta); // r1*sin(v) + Real r1csv = std::sqrt(dot0 * dot0 + dot1 * dot1) - radius0; // r1*cos(v) + u = std::atan2(dot1, dot0); + v = std::atan2(dot2, r1csv); +} + +template +bool Torus3::operator==(Torus3 const& torus) const +{ + return center == torus.center + && direction0 == torus.direction0 + && direction1 == torus.direction1 + && normal == torus.normal + && radius0 == torus.radius0 + && radius1 == torus.radius1; +} + +template +bool Torus3::operator!=(Torus3 const& torus) const +{ + return !operator==(torus); +} + +template +bool Torus3::operator<(Torus3 const& torus) const +{ + if (center < torus.center) + { + return true; + } + + if (center > torus.center) + { + return false; + } + + if (direction0 < torus.direction0) + { + return true; + } + + if (direction0 > torus.direction0) + { + return false; + } + + if (direction1 < torus.direction1) + { + return true; + } + + if (direction1 > torus.direction1) + { + return false; + } + + if (normal < torus.normal) + { + return true; + } + + if (normal > torus.normal) + { + return false; + } + + if (radius0 < torus.radius0) + { + return true; + } + + if (radius0 > torus.radius0) + { + return false; + } + + return radius1 < torus.radius1; +} + +template +bool Torus3::operator<=(Torus3 const& torus) const +{ + return operator<(torus) || operator==(torus); +} + +template +bool Torus3::operator>(Torus3 const& torus) const +{ + return !operator<=(torus); +} + +template +bool Torus3::operator>=(Torus3 const& torus) const +{ + return !operator<(torus); +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangle.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangle.h new file mode 100644 index 000000000000..07dbc707ef29 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangle.h @@ -0,0 +1,111 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The triangle is represented as an array of three vertices. The dimension +// N must be 2 or larger. + +namespace gte +{ + +template +class Triangle +{ +public: + // Construction and destruction. The default constructor sets the + // vertices to (0,..,0), (1,0,...,0), and (0,1,0,...,0). + Triangle(); + Triangle(Vector const& v0, Vector const& v1, + Vector const& v2); + Triangle(std::array, 3> const& inV); + + // Public member access. + std::array, 3> v; + +public: + // Comparisons to support sorted containers. + bool operator==(Triangle const& triangle) const; + bool operator!=(Triangle const& triangle) const; + bool operator< (Triangle const& triangle) const; + bool operator<=(Triangle const& triangle) const; + bool operator> (Triangle const& triangle) const; + bool operator>=(Triangle const& triangle) const; +}; + +// Template aliases for convenience. +template +using Triangle2 = Triangle<2, Real>; + +template +using Triangle3 = Triangle<3, Real>; + + +template +Triangle::Triangle() +{ + v[0].MakeZero(); + v[1].MakeUnit(0); + v[2].MakeUnit(1); +} + +template +Triangle::Triangle(Vector const& v0, + Vector const& v1, Vector const& v2) +{ + v[0] = v0; + v[1] = v1; + v[2] = v2; +} + +template +Triangle::Triangle(std::array, 3> const& inV) + : + v(inV) +{ +} + +template +bool Triangle::operator==(Triangle const& triangle) const +{ + return v == triangle.v; +} + +template +bool Triangle::operator!=(Triangle const& triangle) const +{ + return !operator==(triangle); +} + +template +bool Triangle::operator<(Triangle const& triangle) const +{ + return v < triangle.v; +} + +template +bool Triangle::operator<=(Triangle const& triangle) const +{ + return operator<(triangle) || operator==(triangle); +} + +template +bool Triangle::operator>(Triangle const& triangle) const +{ + return !operator<=(triangle); +} + +template +bool Triangle::operator>=(Triangle const& triangle) const +{ + return !operator<(triangle); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangleKey.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangleKey.h new file mode 100644 index 000000000000..b651cdd93a3b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangleKey.h @@ -0,0 +1,134 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/05/22) + +#pragma once + +#include + +#include + +namespace gte +{ + template + class TriangleKey : public FeatureKey<3, Ordered> + { + public: + // An ordered triangle has V[0] = min(v0,v1,v2). Choose + // (V[0],V[1],V[2]) to be a permutation of (v0,v1,v2) so that the + // final storage is one of + // (v0,v1,v2), (v1,v2,v0), (v2,v0,v1) + // The idea is that if v0 corresponds to (1,0,0), v1 corresponds to + // (0,1,0), and v2 corresponds to (0,0,1), the ordering (v0,v1,v2) + // corresponds to the 3x3 identity matrix I; the rows are the + // specified 3-tuples. The permutation (V[0],V[1],V[2]) induces a + // permutation of the rows of the identity matrix to form a + // permutation matrix P with det(P) = 1 = det(I). + // + // An unordered triangle stores a permutation of (v0,v1,v2) so that + // V[0] < V[1] < V[2]. + TriangleKey(); // creates key (-1,-1,-1) + explicit TriangleKey(int v0, int v1, int v2); + }; + + template<> + inline + TriangleKey::TriangleKey() + { + V[0] = -1; + V[1] = -1; + V[2] = -1; + } + + template<> + inline + TriangleKey::TriangleKey(int v0, int v1, int v2) + { + if (v0 < v1) + { + if (v0 < v2) + { + // v0 is minimum + V[0] = v0; + V[1] = v1; + V[2] = v2; + } + else + { + // v2 is minimum + V[0] = v2; + V[1] = v0; + V[2] = v1; + } + } + else + { + if (v1 < v2) + { + // v1 is minimum + V[0] = v1; + V[1] = v2; + V[2] = v0; + } + else + { + // v2 is minimum + V[0] = v2; + V[1] = v0; + V[2] = v1; + } + } + } + + template<> + inline + TriangleKey::TriangleKey() + { + V[0] = -1; + V[1] = -1; + V[2] = -1; + } + + template<> + inline + TriangleKey::TriangleKey(int v0, int v1, int v2) + { + if (v0 < v1) + { + if (v0 < v2) + { + // v0 is minimum + V[0] = v0; + V[1] = std::min(v1, v2); + V[2] = std::max(v1, v2); + } + else + { + // v2 is minimum + V[0] = v2; + V[1] = std::min(v0, v1); + V[2] = std::max(v0, v1); + } + } + else + { + if (v1 < v2) + { + // v1 is minimum + V[0] = v1; + V[1] = std::min(v2, v0); + V[2] = std::max(v2, v0); + } + else + { + // v2 is minimum + V[0] = v2; + V[1] = std::min(v0, v1); + V[2] = std::max(v0, v1); + } + } + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangulateCDT.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangulateCDT.h new file mode 100644 index 000000000000..1ed0f2904834 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangulateCDT.h @@ -0,0 +1,576 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// The triangulation is based on constrained Delaunay triangulations (CDT), +// which does not use divisions, so ComputeType may be chosen using BSNumber. +// The input constraints are relaxed compared to TriangulateEC; specifically, +// the inner polygons are allowed to share vertices with the outer polygons. +// The CDT produces a triangulation of the convex hull of the input, which +// includes triangles outside the top-level outer polygon and inside the +// inner polygons. Only the triangles relevant to the input are returned +// via the 'std::vector& triangles', but the other triangles are +// retained so that you can perform linear walks in search of points inside +// the original polygon (nested polygon, tree of nested polygons). This is +// useful, for example, when subsampling the polygon triangles for +// interpolation of function data specified at the vertices. A linear walk +// does not work for a mesh consisting only of the polygon triangles, but +// with the additional triangles, the walk can navigate through holes in +// the polygon to find the containing triangle of the specified point. + +namespace gte +{ + +template +class TriangulateCDT +{ +public: + // The class is a functor to support triangulating multiple polygons that + // share vertices in a collection of points. The interpretation of + // 'numPoints' and 'points' is described before each operator() function. + // Preconditions are numPoints >= 3 and points is a nonnull pointer to an + // array of at least numPoints elements. If the preconditions are + // satisfied, then operator() functions will return 'true'; otherwise, + // they return 'false'. + TriangulateCDT(int numPoints, Vector2 const* points); + TriangulateCDT(std::vector> const& points); + + // The triangles of the polygon triangulation. + inline std::vector> const& GetTriangles() const; + + // The triangles inside the convex hull of the points but outside the + // triangulation. + inline std::vector> const& GetOutsideTriangles() const; + + // The triangles of the convex hull of the inputs to the constructor. + inline std::vector> const& GetAllTriangles() const; + + // The classification of whether a triangle is part of the triangulation + // or outside the triangulation. These may be used in conjunction with + // the array returned by GetAllTriangles(). + inline std::vector const& GetIsInside() const; + inline bool IsInside(int triIndex) const; + inline bool IsOutside(int triIndex) const; + + // The outer polygons have counterclockwise ordered vertices. The inner + // polygons have clockwise ordered vertices. + typedef std::vector Polygon; + + // The input 'points' represents an array of vertices for a simple + // polygon. The vertices are points[0] through points[numPoints-1] and + // are listed in counterclockwise order. + bool operator()(); + + // The input 'points' represents an array of vertices that contains the + // vertices of a simple polygon. + bool operator()(Polygon const& polygon); + + // The input 'points' is a shared array of vertices that contains the + // vertices for two simple polygons, an outer polygon and an inner + // polygon. The inner polygon must be strictly inside the outer polygon. + bool operator()(Polygon const& outer, Polygon const& inner); + + // The input 'points' is a shared array of vertices that contains the + // vertices for multiple simple polygons, an outer polygon and one or more + // inner polygons. The inner polygons must be nonoverlapping and strictly + // inside the outer polygon. + bool operator()(Polygon const& outer, std::vector const& inners); + + // A tree of nested polygons. The root node corresponds to an outer + // polygon. The children of the root correspond to inner polygons, which + // are nonoverlapping polygons strictly contained in the outer polygon. + // Each inner polygon may itself contain an outer polygon, thus leading + // to a hierarchy of polygons. The outer polygons have vertices listed + // in counterclockwise order. The inner polygons have vertices listed in + // clockwise order. + class Tree + { + public: + Polygon polygon; + std::vector> child; + }; + + // The input 'positions' is a shared array of vertices that contains the + // vertices for multiple simple polygons in a tree of polygons. + bool operator()(std::shared_ptr const& tree); + +private: + // Triangulate the points referenced by an operator(...) query. The + // mAllTriangles and mIsInside are populated by this function, but the + // indices of mAllTriangles are relative to the packed 'points'. After + // the call, the indices need to be mapped back to the original set + // provided by the input arrays to operator(...). The mTriangles and + // mOutsideTriangles are generated after the call by examining + // mAllTriangles and mIsInside. + bool TriangulatePacked(int numPoints, Vector2 const* points, + std::shared_ptr const& tree, + std::map, int> const& offsets); + + int GetNumPointsAndOffsets(std::shared_ptr const& tree, + std::map, int>& offsets) const; + + void PackPoints(std::shared_ptr const& tree, + std::vector>& points); + + bool InsertEdges(std::shared_ptr const& tree); + + void LookupIndex(std::shared_ptr const& tree, int& v, + std::map, int> const& offsets) const; + + bool IsInside(std::shared_ptr const& tree, Vector2 const* points, + Vector2 const& test, + std::map, int> const& offsets) const; + + // The input polygon. + int mNumPoints; + Vector2 const* mPoints; + + // The output triangulation and those triangle inside the hull of the + // points but outside the triangulation. + std::vector> mTriangles; + std::vector> mOutsideTriangles; + std::vector> mAllTriangles; + std::vector mIsInside; + + ConstrainedDelaunay2 mConstrainedDelaunay; +}; + + +template +TriangulateCDT::TriangulateCDT(int numPoints, Vector2 const* points) + : + mNumPoints(numPoints), + mPoints(points) +{ + if (mNumPoints < 3 || !mPoints) + { + LogError("Invalid input."); + mNumPoints = 0; + mPoints = nullptr; + // The operator() functions will triangulate only when mPoints is + // not null. The test mNumPoints >= 3 is redundant because of the + // logic in the constructor. + } +} + +template +TriangulateCDT::TriangulateCDT(std::vector> const& points) + : + mNumPoints(static_cast(points.size())), + mPoints(points.data()) +{ + if (mNumPoints < 3 || !mPoints) + { + LogError("Invalid input."); + mNumPoints = 0; + mPoints = nullptr; + // The operator() functions will triangulate only when mPoints is + // not null. The test mNumPoints >= 3 is redundant because of the + // logic in the constructor. + } +} + +template inline +std::vector> const& TriangulateCDT::GetTriangles() const +{ + return mTriangles; +} + +template inline +std::vector> const& TriangulateCDT::GetOutsideTriangles() const +{ + return mOutsideTriangles; +} + +template inline +std::vector> const& TriangulateCDT::GetAllTriangles() const +{ + return mAllTriangles; +} + +template inline +std::vector const& TriangulateCDT::GetIsInside() const +{ + return mIsInside; +} + +template inline +bool TriangulateCDT::IsInside(int triIndex) const +{ + if (0 <= triIndex && triIndex < static_cast(mIsInside.size())) + { + return mIsInside[triIndex]; + } + else + { + return false; + } +} + +template inline +bool TriangulateCDT::IsOutside(int triIndex) const +{ + if (0 <= triIndex && triIndex < static_cast(mIsInside.size())) + { + return !mIsInside[triIndex]; + } + else + { + return false; + } +} + +template +bool TriangulateCDT::operator()() +{ + if (mPoints) + { + std::shared_ptr tree = std::make_shared(); + tree->polygon.resize(mNumPoints); + for (int i = 0; i < mNumPoints; ++i) + { + tree->polygon[i] = i; + } + + return operator()(tree); + } + return false; +} + +template +bool TriangulateCDT::operator()(Polygon const& polygon) +{ + if (mPoints) + { + std::shared_ptr tree = std::make_shared(); + tree->polygon = polygon; + + return operator()(tree); + } + return false; +} + +template +bool TriangulateCDT::operator()(Polygon const& outer, Polygon const& inner) +{ + if (mPoints) + { + std::shared_ptr otree = std::make_shared(); + otree->polygon = outer; + otree->child.resize(1); + + std::shared_ptr itree = std::make_shared(); + itree->polygon = inner; + otree->child[0] = itree; + + return operator()(otree); + } + return false; +} + +template +bool TriangulateCDT::operator()(Polygon const& outer, std::vector const& inners) +{ + if (mPoints) + { + std::shared_ptr otree = std::make_shared(); + otree->polygon = outer; + otree->child.resize(inners.size()); + + std::vector> itree(inners.size()); + for (size_t i = 0; i < inners.size(); ++i) + { + itree[i] = std::make_shared(); + itree[i]->polygon = inners[i]; + otree->child[i] = itree[i]; + } + + return operator()(otree); + } + return false; +} + +template +bool TriangulateCDT::operator()(std::shared_ptr const& tree) +{ + if (mPoints) + { + std::map, int> offsets; + int numPoints = GetNumPointsAndOffsets(tree, offsets); + std::vector> points(numPoints); + PackPoints(tree, points); + + if (TriangulatePacked(numPoints, &points[0], tree, offsets)) + { + int numTriangles = static_cast(mAllTriangles.size()); + for (int t = 0; t < numTriangles; ++t) + { + auto& tri = mAllTriangles[t]; + for (int j = 0; j < 3; ++j) + { + LookupIndex(tree, tri[j], offsets); + } + + if (mIsInside[t]) + { + mTriangles.push_back(tri); + } + else + { + mOutsideTriangles.push_back(tri); + } + } + return true; + } + } + + return false; +} + +template +bool TriangulateCDT::TriangulatePacked(int numPoints, + Vector2 const* points, std::shared_ptr const& tree, + std::map, int> const& offsets) +{ + mTriangles.clear(); + mOutsideTriangles.clear(); + mAllTriangles.clear(); + mIsInside.clear(); + + mConstrainedDelaunay(numPoints, points, static_cast(0)); + InsertEdges(tree); + + ComputeType half = static_cast(0.5); + ComputeType fourth = static_cast(0.25); + auto const& query = mConstrainedDelaunay.GetQuery(); + auto const* ctPoints = query.GetVertices(); + int numTriangles = mConstrainedDelaunay.GetNumTriangles(); + int const* indices = &mConstrainedDelaunay.GetIndices()[0]; + mIsInside.resize(numTriangles); + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + auto ctInside = fourth * ctPoints[v0] + half * ctPoints[v1] + fourth *ctPoints[v2]; + mIsInside[t] = IsInside(tree, ctPoints, ctInside, offsets); + mAllTriangles.push_back({ { v0, v1, v2 } }); + } + return true; +} + +template +int TriangulateCDT::GetNumPointsAndOffsets( + std::shared_ptr const& tree, std::map, int>& offsets) const +{ + int numPoints = 0; + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + offsets.insert(std::make_pair(outer, numPoints)); + numPoints += static_cast(outer->polygon.size()); + + int numChildren = static_cast(outer->child.size()); + for (int c = 0; c < numChildren; ++c) + { + std::shared_ptr inner = outer->child[c]; + offsets.insert(std::make_pair(inner, numPoints)); + numPoints += static_cast(inner->polygon.size()); + + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + } + return numPoints; +} + +template +void TriangulateCDT::PackPoints(std::shared_ptr const& tree, + std::vector>& points) +{ + int numPoints = 0; + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + int const numOuterIndices = static_cast(outer->polygon.size()); + int const* outerIndices = outer->polygon.data(); + for (int i = 0; i < numOuterIndices; ++i) + { + points[numPoints++] = mPoints[outerIndices[i]]; + } + + int numChildren = static_cast(outer->child.size()); + for (int c = 0; c < numChildren; ++c) + { + std::shared_ptr inner = outer->child[c]; + int const numInnerIndices = static_cast(inner->polygon.size()); + int const* innerIndices = inner->polygon.data(); + for (int i = 0; i < numInnerIndices; ++i) + { + points[numPoints++] = mPoints[innerIndices[i]]; + } + + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + } +} + +template +bool TriangulateCDT::InsertEdges(std::shared_ptr const& tree) +{ + int numPoints = 0; + std::array edge; + std::vector ignoreOutEdge; + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + int numOuter = static_cast(outer->polygon.size()); + for (int i0 = numOuter - 1, i1 = 0; i1 < numOuter; i0 = i1++) + { + edge[0] = numPoints + i0; + edge[1] = numPoints + i1; + if (!mConstrainedDelaunay.Insert(edge, ignoreOutEdge)) + { + return false; + } + } + numPoints += numOuter; + + int numChildren = static_cast(outer->child.size()); + for (int c = 0; c < numChildren; ++c) + { + std::shared_ptr inner = outer->child[c]; + int numInner = static_cast(inner->polygon.size()); + for (int i0 = numInner - 1, i1 = 0; i1 < numInner; i0 = i1++) + { + edge[0] = numPoints + i0; + edge[1] = numPoints + i1; + if (!mConstrainedDelaunay.Insert(edge, ignoreOutEdge)) + { + return false; + } + } + numPoints += numInner; + + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + } + return true; +} + +template +void TriangulateCDT::LookupIndex(std::shared_ptr const& tree, + int& v, std::map, int> const& offsets) const +{ + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + int const numOuterIndices = static_cast(outer->polygon.size()); + int const* outerIndices = outer->polygon.data(); + int offset = offsets.find(outer)->second; + if (v < offset + numOuterIndices) + { + v = outerIndices[v - offset]; + return; + } + + int numChildren = static_cast(outer->child.size()); + for (int c = 0; c < numChildren; ++c) + { + std::shared_ptr inner = outer->child[c]; + int const numInnerIndices = static_cast(inner->polygon.size()); + int const* innerIndices = inner->polygon.data(); + offset = offsets.find(inner)->second; + if (v < offset + numInnerIndices) + { + v = innerIndices[v - offset]; + return; + } + + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + } +} + +template +bool TriangulateCDT::IsInside(std::shared_ptr const& tree, + Vector2 const* points, Vector2 const& test, + std::map, int> const& offsets) const +{ + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + int const numOuterIndices = static_cast(outer->polygon.size()); + int offset = offsets.find(outer)->second; + PointInPolygon2 piOuter(numOuterIndices, points + offset); + if (piOuter.Contains(test)) + { + int numChildren = static_cast(outer->child.size()); + int c; + for (c = 0; c < numChildren; ++c) + { + std::shared_ptr inner = outer->child[c]; + int const numInnerIndices = static_cast(inner->polygon.size()); + offset = offsets.find(inner)->second; + PointInPolygon2 piInner(numInnerIndices, points + offset); + if (piInner.Contains(test)) + { + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + break; + } + } + if (c == numChildren) + { + return true; + } + } + } + return false; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangulateEC.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangulateEC.h new file mode 100644 index 000000000000..165239bfc7b2 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangulateEC.h @@ -0,0 +1,1319 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/02/17) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +// The algorithm for processing nested polygons involves a division, so the +// ComputeType must be rational-based, say, BSRational. If you process only +// triangles that are simple, you may use BSNumber for the ComputeType. + +namespace gte +{ + +template +class TriangulateEC +{ +public: + // The class is a functor to support triangulating multiple polygons that + // share vertices in a collection of points. The interpretation of + // 'numPoints' and 'points' is described before each operator() function. + // Preconditions are numPoints >= 3 and points is a nonnull pointer to an + // array of at least numPoints elements. If the preconditions are + // satisfied, then operator() functions will return 'true'; otherwise, + // they return 'false'. + TriangulateEC(int numPoints, Vector2 const* points); + TriangulateEC(std::vector> const& points); + + // Access the triangulation after each operator() call. + inline std::vector> const& GetTriangles() const; + + // The outer polygons have counterclockwise ordered vertices. The inner + // polygons have clockwise ordered vertices. + typedef std::vector Polygon; + + // The input 'points' represents an array of vertices for a simple + // polygon. The vertices are points[0] through points[numPoints-1] and + // are listed in counterclockwise order. + bool operator()(); + + // The input 'points' represents an array of vertices that contains the + // vertices of a simple polygon. + bool operator()(Polygon const& polygon); + + // The input 'points' is a shared array of vertices that contains the + // vertices for two simple polygons, an outer polygon and an inner + // polygon. The inner polygon must be strictly inside the outer polygon. + bool operator()(Polygon const& outer, Polygon const& inner); + + // The input 'points' is a shared array of vertices that contains the + // vertices for multiple simple polygons, an outer polygon and one or more + // inner polygons. The inner polygons must be nonoverlapping and strictly + // inside the outer polygon. + bool operator()(Polygon const& outer, std::vector const& inners); + + // A tree of nested polygons. The root node corresponds to an outer + // polygon. The children of the root correspond to inner polygons, which + // are nonoverlapping polygons strictly contained in the outer polygon. + // Each inner polygon may itself contain an outer polygon, thus leading + // to a hierarchy of polygons. The outer polygons have vertices listed + // in counterclockwise order. The inner polygons have vertices listed in + // clockwise order. + class Tree + { + public: + Polygon polygon; + std::vector> child; + }; + + // The input 'positions' is a shared array of vertices that contains the + // vertices for multiple simple polygons in a tree of polygons. + bool operator()(std::shared_ptr const& tree); + +private: + // Create the vertex objects that store the various lists required by the + // ear-clipping algorithm. + void InitializeVertices(int numVertices, int const* indices); + + // Apply ear clipping to the input polygon. Polygons with holes are + // preprocessed to obtain an index array that is nearly a simple polygon. + // This outer polygon has a pair of coincident edges per inner polygon. + void DoEarClipping(int numVertices, int const* indices); + + // Given an outer polygon that contains an inner polygon, this function + // determines a pair of visible vertices and inserts two coincident edges + // to generate a nearly simple polygon. + bool CombinePolygons(int nextElement, Polygon const& outer, + Polygon const& inner, std::map& indexMap, + std::vector& combined); + + // Given an outer polygon that contains a set of nonoverlapping inner + // polygons, this function determines pairs of visible vertices and + // inserts coincident edges to generate a nearly simple polygon. It + // repeatedly calls CombinePolygons for each inner polygon of the outer + // polygon. + bool ProcessOuterAndInners(int& nextElement, Polygon const& outer, + std::vector const& inners, std::map& indexMap, + std::vector& combined); + + // The insertion of coincident edges to obtain a nearly simple polygon + // requires duplication of vertices in order that the ear-clipping + // algorithm work correctly. After the triangulation, the indices of + // the duplicated vertices are converted to the original indices. + void RemapIndices(std::map const& indexMap); + + // Two extra elements are needed in the position array per outer-inners + // polygon. This function computes the total number of extra elements + // needed for the input tree and it converts InputType vertices to + // ComputeType values. + int InitializeFromTree(std::shared_ptr const& tree); + + // The input polygon. + int mNumPoints; + Vector2 const* mPoints; + + // The output triangulation. + std::vector> mTriangles; + + // The array of points used for geometric queries. If you want to be + // certain of a correct result, choose ComputeType to be BSNumber. The + // InputType points are convertex to ComputeType points on demand; the + // mIsConverted array keeps track of which input points have been + // converted. + std::vector> mComputePoints; + std::vector mIsConverted; + PrimalQuery2 mQuery; + + // Doubly linked lists for storing specially tagged vertices. + class Vertex + { + public: + Vertex(); + + int index; // index of vertex in position array + int vPrev, vNext; // vertex links for polygon + int sPrev, sNext; // convex/reflex vertex links (disjoint lists) + int ePrev, eNext; // ear links + bool isConvex, isEar; + }; + + inline Vertex& V(int i); + bool IsConvex(int i); + bool IsEar(int i); + void InsertAfterC(int i); // insert convex vertex + void InsertAfterR(int i); // insert reflex vertesx + void InsertEndE(int i); // insert ear at end of list + void InsertAfterE(int i); // insert ear after efirst + void InsertBeforeE(int i); // insert ear before efirst + void RemoveV(int i); // remove vertex + int RemoveE(int i); // remove ear at i + void RemoveR(int i); // remove reflex vertex + + // The doubly linked list. + std::vector mVertices; + int mCFirst, mCLast; // linear list of convex vertices + int mRFirst, mRLast; // linear list of reflex vertices + int mEFirst, mELast; // cyclical list of ears +}; + + +template +TriangulateEC::TriangulateEC(int numPoints, Vector2 const* points) + : + mNumPoints(numPoints), + mPoints(points) +{ + if (mNumPoints >= 3 && mPoints) + { + mComputePoints.resize(mNumPoints); + mIsConverted.resize(mNumPoints); + std::fill(mIsConverted.begin(), mIsConverted.end(), false); + mQuery.Set(mNumPoints, &mComputePoints[0]); + } + else + { + LogError("Invalid input."); + mNumPoints = 0; + mPoints = nullptr; + // The operator() functions will triangulate only when mPoints is + // not null. The test mNumPoints >= 3 is redundant because of the + // logic in the constructor. + } +} + +template +TriangulateEC::TriangulateEC(std::vector> const& points) + : + mNumPoints(static_cast(points.size())), + mPoints(points.data()) +{ + if (mNumPoints >= 3 && mPoints) + { + mComputePoints.resize(mNumPoints); + mIsConverted.resize(mNumPoints); + std::fill(mIsConverted.begin(), mIsConverted.end(), false); + mQuery.Set(mNumPoints, &mComputePoints[0]); + } + else + { + LogError("Invalid input."); + mNumPoints = 0; + mPoints = nullptr; + // The operator() functions will triangulate only when mPoints is + // not null. The test mNumPoints >= 3 is redundant because of the + // logic in the constructor. + } +} + +template inline +std::vector> const& TriangulateEC::GetTriangles() const +{ + return mTriangles; +} + +template +bool TriangulateEC::operator()() +{ + mTriangles.clear(); + if (mPoints) + { + // Compute the points for the queries. + for (int i = 0; i < mNumPoints; ++i) + { + if (!mIsConverted[i]) + { + mIsConverted[i] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[i][j] = mPoints[i][j]; + } + } + } + + // Triangulate the unindexed polygon. + InitializeVertices(mNumPoints, nullptr); + DoEarClipping(mNumPoints, nullptr); + return true; + } + else + { + return false; + } +} + +template +bool TriangulateEC::operator()(Polygon const& polygon) +{ + mTriangles.clear(); + if (mPoints) + { + // Compute the points for the queries. + int const numIndices = static_cast(polygon.size()); + int const* indices = polygon.data(); + for (int i = 0; i < numIndices; ++i) + { + int index = indices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + // Triangulate the indexed polygon. + InitializeVertices(numIndices, indices); + DoEarClipping(numIndices, indices); + return true; + } + else + { + return false; + } +} + +template +bool TriangulateEC::operator()(Polygon const& outer, Polygon const& inner) +{ + mTriangles.clear(); + if (mPoints) + { + // Two extra elements are needed to duplicate the endpoints of the + // edge introduced to combine outer and inner polygons. + int numPointsPlusExtras = mNumPoints + 2; + if (numPointsPlusExtras > static_cast(mComputePoints.size())) + { + mComputePoints.resize(numPointsPlusExtras); + mIsConverted.resize(numPointsPlusExtras); + mIsConverted[mNumPoints] = false; + mIsConverted[mNumPoints + 1] = false; + mQuery.Set(numPointsPlusExtras, &mComputePoints[0]); + } + + // Convert any points that have not been encountered in other + // triangulation calls. + int const numOuterIndices = static_cast(outer.size()); + int const* outerIndices = outer.data(); + for (int i = 0; i < numOuterIndices; ++i) + { + int index = outerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + int const numInnerIndices = static_cast(inner.size()); + int const* innerIndices = inner.data(); + for (int i = 0; i < numInnerIndices; ++i) + { + int index = innerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + // Combine the outer polygon and the inner polygon into a simple + // polygon by inserting two edges connecting mutually visible + // vertices, one from the outer polygon and one from the inner + // polygon. + int nextElement = mNumPoints; // The next available element. + std::map indexMap; + std::vector combined; + if (!CombinePolygons(nextElement, outer, inner, indexMap, combined)) + { + // An unexpected condition was encountered. + return false; + } + + // The combined polygon is now in the format of a simple polygon, + // albeit one with coincident edges. + int numVertices = static_cast(combined.size()); + int* const indices = &combined[0]; + InitializeVertices(numVertices, indices); + DoEarClipping(numVertices, indices); + + // Map the duplicate indices back to the original indices. + RemapIndices(indexMap); + return true; + } + else + { + return false; + } +} + +template +bool TriangulateEC::operator()(Polygon const& outer, std::vector const& inners) +{ + mTriangles.clear(); + if (mPoints) + { + // Two extra elements per inner polygon are needed to duplicate the + // endpoints of the edges introduced to combine outer and inner + // polygons. + int numPointsPlusExtras = mNumPoints + 2 * (int)inners.size(); + if (numPointsPlusExtras > static_cast(mComputePoints.size())) + { + mComputePoints.resize(numPointsPlusExtras); + mIsConverted.resize(numPointsPlusExtras); + for (int i = mNumPoints; i < numPointsPlusExtras; ++i) + { + mIsConverted[i] = false; + } + mQuery.Set(numPointsPlusExtras, &mComputePoints[0]); + } + + // Convert any points that have not been encountered in other + // triangulation calls. + int const numOuterIndices = static_cast(outer.size()); + int const* outerIndices = outer.data(); + for (int i = 0; i < numOuterIndices; ++i) + { + int index = outerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + for (auto const& inner : inners) + { + int const numInnerIndices = static_cast(inner.size()); + int const* innerIndices = inner.data(); + for (int i = 0; i < numInnerIndices; ++i) + { + int index = innerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + } + + // Combine the outer polygon and the inner polygons into a simple + // polygon by inserting two edges per inner polygon connecting + // mutually visible vertices. + int nextElement = mNumPoints; // The next available element. + std::map indexMap; + std::vector combined; + if (!ProcessOuterAndInners(nextElement, outer, inners, indexMap, combined)) + { + // An unexpected condition was encountered. + return false; + } + + // The combined polygon is now in the format of a simple polygon, albeit + // with coincident edges. + int numVertices = static_cast(combined.size()); + int* const indices = &combined[0]; + InitializeVertices(numVertices, indices); + DoEarClipping(numVertices, indices); + + // Map the duplicate indices back to the original indices. + RemapIndices(indexMap); + return true; + } + else + { + return false; + } +} + +template +bool TriangulateEC::operator()(std::shared_ptr const& tree) +{ + mTriangles.clear(); + if (mPoints) + { + // Two extra elements per inner polygon are needed to duplicate the + // endpoints of the edges introduced to combine outer and inner + // polygons. + int numPointsPlusExtras = mNumPoints + InitializeFromTree(tree); + if (numPointsPlusExtras > static_cast(mComputePoints.size())) + { + mComputePoints.resize(numPointsPlusExtras); + mIsConverted.resize(numPointsPlusExtras); + for (int i = mNumPoints; i < numPointsPlusExtras; ++i) + { + mIsConverted[i] = false; + } + mQuery.Set(numPointsPlusExtras, &mComputePoints[0]); + } + + int nextElement = mNumPoints; + std::map indexMap; + + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + + int numChildren = static_cast(outer->child.size()); + int numVertices; + int const* indices; + + if (numChildren == 0) + { + // The outer polygon is a simple polygon (no nested inner + // polygons). Triangulate the simple polygon. + numVertices = static_cast(outer->polygon.size()); + indices = outer->polygon.data(); + InitializeVertices(numVertices, indices); + DoEarClipping(numVertices, indices); + } + else + { + // Place the next level of outer polygon nodes on the queue for + // triangulation. + std::vector inners(numChildren); + for (int c = 0; c < numChildren; ++c) + { + std::shared_ptr inner = outer->child[c]; + inners[c] = inner->polygon; + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + + // Combine the outer polygon and the inner polygons into a + // simple polygon by inserting two edges per inner polygon + // connecting mutually visible vertices. + std::vector combined; + ProcessOuterAndInners(nextElement, outer->polygon, inners, indexMap, combined); + + // The combined polygon is now in the format of a simple + // polygon, albeit with coincident edges. + numVertices = static_cast(combined.size()); + indices = &combined[0]; + InitializeVertices(numVertices, indices); + DoEarClipping(numVertices, indices); + } + } + + // Map the duplicate indices back to the original indices. + RemapIndices(indexMap); + return true; + } + else + { + return false; + } +} + +template +void TriangulateEC::InitializeVertices(int numVertices, int const* indices) +{ + mVertices.clear(); + mVertices.resize(numVertices); + mCFirst = -1; + mCLast = -1; + mRFirst = -1; + mRLast = -1; + mEFirst = -1; + mELast = -1; + + // Create a circular list of the polygon vertices for dynamic removal of + // vertices. + int numVerticesM1 = numVertices - 1; + for (int i = 0; i <= numVerticesM1; ++i) + { + Vertex& vertex = V(i); + vertex.index = (indices ? indices[i] : i); + vertex.vPrev = (i > 0 ? i - 1 : numVerticesM1); + vertex.vNext = (i < numVerticesM1 ? i + 1 : 0); + } + + // Create a circular list of the polygon vertices for dynamic removal of + // vertices. Keep track of two linear sublists, one for the convex + // vertices and one for the reflex vertices. This is an O(N) process + // where N is the number of polygon vertices. + for (int i = 0; i <= numVerticesM1; ++i) + { + if (IsConvex(i)) + { + InsertAfterC(i); + } + else + { + InsertAfterR(i); + } + } +} + +template +void TriangulateEC::DoEarClipping(int numVertices, int const* indices) +{ + // If the polygon is convex, just create a triangle fan. + if (mRFirst == -1) + { + int numVerticesM1 = numVertices - 1; + if (indices) + { + for (int i = 1; i < numVerticesM1; ++i) + { + mTriangles.push_back({ { indices[0], indices[i], indices[i + 1] } }); + } + } + else + { + for (int i = 1; i < numVerticesM1; ++i) + { + mTriangles.push_back({ { 0, i, i + 1 } }); + } + } + return; + } + + // Identify the ears and build a circular list of them. Let V0, V1, and + // V2 be consecutive vertices forming a triangle T. The vertex V1 is an + // ear if no other vertices of the polygon lie inside T. Although it is + // enough to show that V1 is not an ear by finding at least one other + // vertex inside T, it is sufficient to search only the reflex vertices. + // This is an O(C*R) process, where C is the number of convex vertices and + // R is the number of reflex vertices with N = C+R. The order is O(N^2), + // for example when C = R = N/2. + for (int i = mCFirst; i != -1; i = V(i).sNext) + { + if (IsEar(i)) + { + InsertEndE(i); + } + } + V(mEFirst).ePrev = mELast; + V(mELast).eNext = mEFirst; + + // Remove the ears, one at a time. + bool bRemoveAnEar = true; + while (bRemoveAnEar) + { + // Add the triangle with the ear to the output list of triangles. + int iVPrev = V(mEFirst).vPrev; + int iVNext = V(mEFirst).vNext; + mTriangles.push_back({ { V(iVPrev).index, V(mEFirst).index, V(iVNext).index } }); + + // Remove the vertex corresponding to the ear. + RemoveV(mEFirst); + if (--numVertices == 3) + { + // Only one triangle remains, just remove the ear and copy it. + mEFirst = RemoveE(mEFirst); + iVPrev = V(mEFirst).vPrev; + iVNext = V(mEFirst).vNext; + mTriangles.push_back({ { V(iVPrev).index, V(mEFirst).index, V(iVNext).index } }); + bRemoveAnEar = false; + continue; + } + + // Removal of the ear can cause an adjacent vertex to become an ear + // or to stop being an ear. + Vertex& vPrev = V(iVPrev); + if (vPrev.isEar) + { + if (!IsEar(iVPrev)) + { + RemoveE(iVPrev); + } + } + else + { + bool wasReflex = !vPrev.isConvex; + if (IsConvex(iVPrev)) + { + if (wasReflex) + { + RemoveR(iVPrev); + } + + if (IsEar(iVPrev)) + { + InsertBeforeE(iVPrev); + } + } + } + + Vertex& vNext = V(iVNext); + if (vNext.isEar) + { + if (!IsEar(iVNext)) + { + RemoveE(iVNext); + } + } + else + { + bool wasReflex = !vNext.isConvex; + if (IsConvex(iVNext)) + { + if (wasReflex) + { + RemoveR(iVNext); + } + + if (IsEar(iVNext)) + { + InsertAfterE(iVNext); + } + } + } + + // Remove the ear. + mEFirst = RemoveE(mEFirst); + } +} + +template +bool TriangulateEC::CombinePolygons(int nextElement, + Polygon const& outer, Polygon const& inner, std::map& indexMap, + std::vector& combined) +{ + int const numOuterIndices = static_cast(outer.size()); + int const* outerIndices = outer.data(); + int const numInnerIndices = static_cast(inner.size()); + int const* innerIndices = inner.data(); + + // Locate the inner-polygon vertex of maximum x-value, call this vertex M. + ComputeType xmax = mComputePoints[innerIndices[0]][0]; + int xmaxIndex = 0; + for (int i = 1; i < numInnerIndices; ++i) + { + ComputeType x = mComputePoints[innerIndices[i]][0]; + if (x > xmax) + { + xmax = x; + xmaxIndex = i; + } + } + Vector2 M = mComputePoints[innerIndices[xmaxIndex]]; + + // Find the edge whose intersection Intr with the ray M+t*(1,0) minimizes + // the ray parameter t >= 0. + ComputeType const cmax = static_cast(std::numeric_limits::max()); + ComputeType const zero = static_cast(0); + Vector2 intr{ cmax, M[1] }; + int v0min = -1, v1min = -1, endMin = -1; + int i0, i1; + ComputeType s = cmax; + ComputeType t = cmax; + for (i0 = numOuterIndices - 1, i1 = 0; i1 < numOuterIndices; i0 = i1++) + { + // Consider only edges for which the first vertex is below (or on) the + // ray and the second vertex is above (or on) the ray. + Vector2 diff0 = mComputePoints[outerIndices[i0]] - M; + if (diff0[1] > zero) + { + continue; + } + Vector2 diff1 = mComputePoints[outerIndices[i1]] - M; + if (diff1[1] < zero) + { + continue; + } + + // At this time, diff0.y <= 0 and diff1.y >= 0. + int currentEndMin = -1; + if (diff0[1] < zero) + { + if (diff1[1] > zero) + { + // The intersection of the edge and ray occurs at an interior + // edge point. + s = diff0[1] / (diff0[1] - diff1[1]); + t = diff0[0] + s * (diff1[0] - diff0[0]); + } + else // diff1.y == 0 + { + // The vertex Outer[i1] is the intersection of the edge and + // the ray. + t = diff1[0]; + currentEndMin = i1; + } + } + else // diff0.y == 0 + { + if (diff1[1] > zero) + { + // The vertex Outer[i0] is the intersection of the edge and + // the ray; + t = diff0[0]; + currentEndMin = i0; + } + else // diff1.y == 0 + { + if (diff0[0] < diff1[0]) + { + t = diff0[0]; + currentEndMin = i0; + } + else + { + t = diff1[0]; + currentEndMin = i1; + } + } + } + + if (zero <= t && t < intr[0]) + { + intr[0] = t; + v0min = i0; + v1min = i1; + if (currentEndMin == -1) + { + // The current closest point is an edge-interior point. + endMin = -1; + } + else + { + // The current closest point is a vertex. + endMin = currentEndMin; + } + } + else if (t == intr[0]) + { + // The current closest point is a vertex shared by multiple edges; + // thus, endMin and currentMin refer to the same point. + if (endMin == -1 || currentEndMin == -1) + { + LogError("Unexpected condition."); + return false; + } + + // We need to select the edge closest to M. The previous closest + // edge is . The current candidate is + // . + Vector2 shared = mComputePoints[outerIndices[i1]]; + + // For the previous closest edge, endMin refers to a vertex of + // the edge. Get the index of the other vertex. + int other = (endMin == v0min ? v1min : v0min); + + // The new edge is closer if the other vertex of the old edge is + // left-of the new edge. + diff0 = mComputePoints[outerIndices[i0]] - shared; + diff1 = mComputePoints[outerIndices[other]] - shared; + ComputeType dotperp = DotPerp(diff0, diff1); + if (dotperp > zero) + { + // The new edge is closer to M. + v0min = i0; + v1min = i1; + endMin = currentEndMin; + } + } + } + + // The intersection intr[0] stored only the t-value of the ray. The + // actual point is (mx,my)+t*(1,0), so intr[0] must be adjusted. + intr[0] += M[0]; + + int maxCosIndex; + if (endMin == -1) + { + // If you reach this assert, there is a good chance that you have two + // inner polygons that share a vertex or an edge. + if (v0min < 0 || v1min < 0) + { + LogError("Is this an invalid nested polygon?"); + return false; + } + + // Select one of Outer[v0min] and Outer[v1min] that has an x-value + // larger than M.x, call this vertex P. The triangle must + // contain an outer-polygon vertex that is visible to M, which is + // possibly P itself. + Vector2 sTriangle[3]; // or + int pIndex; + if (mComputePoints[outerIndices[v0min]][0] > mComputePoints[outerIndices[v1min]][0]) + { + sTriangle[0] = mComputePoints[outerIndices[v0min]]; + sTriangle[1] = intr; + sTriangle[2] = M; + pIndex = v0min; + } + else + { + sTriangle[0] = mComputePoints[outerIndices[v1min]]; + sTriangle[1] = M; + sTriangle[2] = intr; + pIndex = v1min; + } + + // If any outer-polygon vertices other than P are inside the triangle + // , then at least one of these vertices must be a reflex + // vertex. It is sufficient to locate the reflex vertex R (if any) + // in that minimizes the angle between R-M and (1,0). The + // data member mQuery is used for the reflex query. + Vector2 diff = sTriangle[0] - M; + ComputeType maxSqrLen = Dot(diff, diff); + ComputeType maxCos = diff[0] * diff[0] / maxSqrLen; + PrimalQuery2 localQuery(3, sTriangle); + maxCosIndex = pIndex; + for (int i = 0; i < numOuterIndices; ++i) + { + if (i == pIndex) + { + continue; + } + + int curr = outerIndices[i]; + int prev = outerIndices[(i + numOuterIndices - 1) % numOuterIndices]; + int next = outerIndices[(i + 1) % numOuterIndices]; + if (mQuery.ToLine(curr, prev, next) <= 0 + && localQuery.ToTriangle(mComputePoints[curr], 0, 1, 2) <= 0) + { + // The vertex is reflex and inside the triangle. + diff = mComputePoints[curr] - M; + ComputeType sqrLen = Dot(diff, diff); + ComputeType cs = diff[0] * diff[0] / sqrLen; + if (cs > maxCos) + { + // The reflex vertex forms a smaller angle with the + // positive x-axis, so it becomes the new visible + // candidate. + maxSqrLen = sqrLen; + maxCos = cs; + maxCosIndex = i; + } + else if (cs == maxCos && sqrLen < maxSqrLen) + { + // The reflex vertex has angle equal to the current + // minimum but the length is smaller, so it becomes the + // new visible candidate. + maxSqrLen = sqrLen; + maxCosIndex = i; + } + } + } + } + else + { + maxCosIndex = endMin; + } + + // The visible vertices are Position[Inner[xmaxIndex]] and + // Position[Outer[maxCosIndex]]. Two coincident edges with these + // endpoints are inserted to connect the outer and inner polygons into a + // simple polygon. Each of the two Position[] values must be duplicated, + // because the original might be convex (or reflex) and the duplicate is + // reflex (or convex). The ear-clipping algorithm needs to distinguish + // between them. + combined.resize(numOuterIndices + numInnerIndices + 2); + int cIndex = 0; + for (int i = 0; i <= maxCosIndex; ++i, ++cIndex) + { + combined[cIndex] = outerIndices[i]; + } + + for (int i = 0; i < numInnerIndices; ++i, ++cIndex) + { + int j = (xmaxIndex + i) % numInnerIndices; + combined[cIndex] = innerIndices[j]; + } + + int innerIndex = innerIndices[xmaxIndex]; + mComputePoints[nextElement] = mComputePoints[innerIndex]; + combined[cIndex] = nextElement; + auto iter = indexMap.find(innerIndex); + if (iter != indexMap.end()) + { + innerIndex = iter->second; + } + indexMap[nextElement] = innerIndex; + ++cIndex; + ++nextElement; + + int outerIndex = outerIndices[maxCosIndex]; + mComputePoints[nextElement] = mComputePoints[outerIndex]; + combined[cIndex] = nextElement; + iter = indexMap.find(outerIndex); + if (iter != indexMap.end()) + { + outerIndex = iter->second; + } + indexMap[nextElement] = outerIndex; + ++cIndex; + ++nextElement; + + for (int i = maxCosIndex + 1; i < numOuterIndices; ++i, ++cIndex) + { + combined[cIndex] = outerIndices[i]; + } + return true; +} + +template +bool TriangulateEC::ProcessOuterAndInners(int& nextElement, + Polygon const& outer, std::vector const& inners, + std::map& indexMap, std::vector& combined) +{ + // Sort the inner polygons based on maximum x-values. + int numInners = static_cast(inners.size()); + std::vector> pairs(numInners); + for (int p = 0; p < numInners; ++p) + { + int numIndices = static_cast(inners[p].size()); + int const* indices = inners[p].data(); + ComputeType xmax = mComputePoints[indices[0]][0]; + for (int j = 1; j < numIndices; ++j) + { + ComputeType x = mComputePoints[indices[j]][0]; + if (x > xmax) + { + xmax = x; + } + } + pairs[p].first = xmax; + pairs[p].second = p; + } + std::sort(pairs.begin(), pairs.end()); + + // Merge the inner polygons with the outer polygon. + Polygon currentPolygon = outer; + for (int p = numInners - 1; p >= 0; --p) + { + Polygon const& polygon = inners[pairs[p].second]; + Polygon currentCombined; + if (!CombinePolygons(nextElement, currentPolygon, polygon, indexMap, currentCombined)) + { + return false; + } + currentPolygon = std::move(currentCombined); + nextElement += 2; + } + + for (auto index : currentPolygon) + { + combined.push_back(index); + } + return true; +} + +template +void TriangulateEC::RemapIndices(std::map const& indexMap) +{ + // The triangulation includes indices to the duplicated outer and inner + // vertices. These indices must be mapped back to the original ones. + for (auto& tri : mTriangles) + { + for (int i = 0; i < 3; ++i) + { + auto iter = indexMap.find(tri[i]); + if (iter != indexMap.end()) + { + tri[i] = iter->second; + } + } + } +} + +template +int TriangulateEC::InitializeFromTree(std::shared_ptr const& tree) +{ + // Use a breadth-first search to process the outer-inners pairs of the + // tree of nested polygons. + int numExtraPoints = 0; + + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + // The 'root' is an outer polygon. + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + + // Count number of extra points for this outer-inners pair. + int numChildren = static_cast(outer->child.size()); + numExtraPoints += 2 * numChildren; + + // Convert outer points from InputType to ComputeType. + int const numOuterIndices = static_cast(outer->polygon.size()); + int const* outerIndices = outer->polygon.data(); + for (int i = 0; i < numOuterIndices; ++i) + { + int index = outerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + // The grandchildren of the outer polygon are also outer polygons. + // Insert them into the queue for processing. + for (int c = 0; c < numChildren; ++c) + { + // The 'child' is an inner polygon. + std::shared_ptr inner = outer->child[c]; + + // Convert inner points from InputType to ComputeType. + int const numInnerIndices = static_cast(inner->polygon.size()); + int const* innerIndices = inner->polygon.data(); + for (int i = 0; i < numInnerIndices; ++i) + { + int index = innerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + } + + return numExtraPoints; +} + +template +TriangulateEC::Vertex::Vertex() + : + index(-1), + vPrev(-1), + vNext(-1), + sPrev(-1), + sNext(-1), + ePrev(-1), + eNext(-1), + isConvex(false), + isEar(false) +{ +} + +template inline +typename TriangulateEC::Vertex& TriangulateEC::V(int i) +{ + return mVertices[i]; +} + +template +bool TriangulateEC::IsConvex(int i) +{ + Vertex& vertex = V(i); + int curr = vertex.index; + int prev = V(vertex.vPrev).index; + int next = V(vertex.vNext).index; + vertex.isConvex = (mQuery.ToLine(curr, prev, next) > 0); + return vertex.isConvex; +} + +template +bool TriangulateEC::IsEar(int i) +{ + Vertex& vertex = V(i); + + if (mRFirst == -1) + { + // The remaining polygon is convex. + vertex.isEar = true; + return true; + } + + // Search the reflex vertices and test if any are in the triangle + // . + int prev = V(vertex.vPrev).index; + int curr = vertex.index; + int next = V(vertex.vNext).index; + vertex.isEar = true; + for (int j = mRFirst; j != -1; j = V(j).sNext) + { + // Check if the test vertex is already one of the triangle vertices. + if (j == vertex.vPrev || j == i || j == vertex.vNext) + { + continue; + } + + // V[j] has been ruled out as one of the original vertices of the + // triangle . When triangulating polygons + // with holes, V[j] might be a duplicated vertex, in which case it + // does not affect the earness of V[curr]. + int test = V(j).index; + if (mComputePoints[test] == mComputePoints[prev] + || mComputePoints[test] == mComputePoints[curr] + || mComputePoints[test] == mComputePoints[next]) + { + continue; + } + + // Test if the vertex is inside or on the triangle. When it is, it + // causes V[curr] not to be an ear. + if (mQuery.ToTriangle(test, prev, curr, next) <= 0) + { + vertex.isEar = false; + break; + } + } + + return vertex.isEar; +} + +template +void TriangulateEC::InsertAfterC(int i) +{ + if (mCFirst == -1) + { + // Add the first convex vertex. + mCFirst = i; + } + else + { + V(mCLast).sNext = i; + V(i).sPrev = mCLast; + } + mCLast = i; +} + +template +void TriangulateEC::InsertAfterR(int i) +{ + if (mRFirst == -1) + { + // Add the first reflex vertex. + mRFirst = i; + } + else + { + V(mRLast).sNext = i; + V(i).sPrev = mRLast; + } + mRLast = i; +} + +template +void TriangulateEC::InsertEndE(int i) +{ + if (mEFirst == -1) + { + // Add the first ear. + mEFirst = i; + mELast = i; + } + V(mELast).eNext = i; + V(i).ePrev = mELast; + mELast = i; +} + +template +void TriangulateEC::InsertAfterE(int i) +{ + Vertex& first = V(mEFirst); + int currENext = first.eNext; + Vertex& vertex = V(i); + vertex.ePrev = mEFirst; + vertex.eNext = currENext; + first.eNext = i; + V(currENext).ePrev = i; +} + +template +void TriangulateEC::InsertBeforeE(int i) +{ + Vertex& first = V(mEFirst); + int currEPrev = first.ePrev; + Vertex& vertex = V(i); + vertex.ePrev = currEPrev; + vertex.eNext = mEFirst; + first.ePrev = i; + V(currEPrev).eNext = i; +} + +template +void TriangulateEC::RemoveV(int i) +{ + int currVPrev = V(i).vPrev; + int currVNext = V(i).vNext; + V(currVPrev).vNext = currVNext; + V(currVNext).vPrev = currVPrev; +} + +template +int TriangulateEC::RemoveE(int i) +{ + int currEPrev = V(i).ePrev; + int currENext = V(i).eNext; + V(currEPrev).eNext = currENext; + V(currENext).ePrev = currEPrev; + return currENext; +} + +template +void TriangulateEC::RemoveR(int i) +{ + LogAssert(mRFirst != -1 && mRLast != -1, "Reflex vertices must exist."); + + if (i == mRFirst) + { + mRFirst = V(i).sNext; + if (mRFirst != -1) + { + V(mRFirst).sPrev = -1; + } + V(i).sNext = -1; + } + else if (i == mRLast) + { + mRLast = V(i).sPrev; + if (mRLast != -1) + { + V(mRLast).sNext = -1; + } + V(i).sPrev = -1; + } + else + { + int currSPrev = V(i).sPrev; + int currSNext = V(i).sNext; + V(currSPrev).sNext = currSNext; + V(currSNext).sPrev = currSPrev; + V(i).sNext = -1; + V(i).sPrev = -1; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTubeMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTubeMesh.h new file mode 100644 index 000000000000..0ef0f17d6133 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTubeMesh.h @@ -0,0 +1,259 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.6 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class TubeMesh : public Mesh +{ +public: + // Create a mesh (x(u,v),y(u,v),z(u,v)) defined by the specified medial + // curve and radial function. The mesh has torus topology when 'closed' + // is true and has cylinder topology when 'closed' is false. The client + // is responsible for setting the topology correctly in the 'description' + // input. The rows correspond to medial samples and the columns + // correspond to radial samples. The medial curve is sampled according + // to its natural t-parameter when 'sampleByArcLength' is false; + // otherwise, it is sampled uniformly in arclength. + TubeMesh(MeshDescription const& description, + std::shared_ptr> const& medial, + std::function const& radial, bool closed, + bool sampleByArcLength, Vector3 upVector); + + // Member access. + inline std::shared_ptr> const& GetMedial() const; + inline std::function const& GetRadial() const; + inline bool IsClosed() const; + inline bool IsSampleByArcLength() const; + inline Vector3 const& GetUpVector() const; + +private: + void InitializeTCoords(); + virtual void UpdatePositions() override; + + std::shared_ptr> mMedial; + std::function mRadial; + bool mClosed, mSampleByArcLength; + Vector3 mUpVector; + std::vector mCosAngle, mSinAngle; + std::function mTSampler; + std::function, 4>(Real)> mFSampler; + std::unique_ptr> mFrenet; + + // If the client does not request texture coordinates, they will be + // computed internally for use in evaluation of the surface geometry. + std::vector> mDefaultTCoords; +}; + + +template +TubeMesh::TubeMesh(MeshDescription const& description, + std::shared_ptr> const& medial, + std::function const& radial, bool closed, bool sampleByArcLength, + Vector3 upVector) + : + Mesh(description, { MeshTopology::CYLINDER }), // TODO: Allow TORUS and remove the 'closed' input + mMedial(medial), + mRadial(radial), + mClosed(closed), + mSampleByArcLength(sampleByArcLength), + mUpVector(upVector) +{ + if (!this->mDescription.constructed) + { + // The logger system will report these errors in the Mesh constructor. + mMedial = nullptr; + return; + } + + if (!mMedial) + { + LogWarning("A nonnull medial curve is required."); + this->mDescription.constructed = false; + return; + } + + mCosAngle.resize(this->mDescription.numCols); + mSinAngle.resize(this->mDescription.numCols); + Real invRadialSamples = (Real)1 / (Real)(this->mDescription.numCols - 1); + for (unsigned int i = 0; i < this->mDescription.numCols - 1; ++i) + { + Real angle = i * invRadialSamples * (Real)GTE_C_TWO_PI; + mCosAngle[i] = std::cos(angle); + mSinAngle[i] = std::sin(angle); + } + mCosAngle[this->mDescription.numCols - 1] = mCosAngle[0]; + mSinAngle[this->mDescription.numCols - 1] = mSinAngle[0]; + + Real invDenom; + if (mClosed) + { + invDenom = ((Real)1) / (Real)this->mDescription.numRows; + } + else + { + invDenom = ((Real)1) / (Real)(this->mDescription.numRows - 1); + } + + Real factor; + if (mSampleByArcLength) + { + factor = mMedial->GetTotalLength() * invDenom; + mTSampler = [this, factor](unsigned int row) + { + return mMedial->GetTime(row * factor); + }; + } + else + { + factor = (mMedial->GetTMax() - mMedial->GetTMin()) * invDenom; + mTSampler = [this, factor](unsigned int row) + { + return mMedial->GetTMin() + row * factor; + }; + } + + if (mUpVector != Vector3::Zero()) + { + mFSampler = [this](Real t) + { + std::array, 4> frame; + frame[0] = mMedial->GetPosition(t); + frame[1] = mMedial->GetTangent(t); + frame[3] = UnitCross(frame[1], mUpVector); + frame[2] = UnitCross(frame[3], frame[1]); + return frame; + }; + } + else + { + mFrenet = std::make_unique>(mMedial); + mFSampler = [this](Real t) + { + std::array, 4> frame; + (*mFrenet)(t, frame[0], frame[1], frame[2], frame[3]); + return frame; + }; + } + + if (!this->mTCoords) + { + mDefaultTCoords.resize(this->mDescription.numVertices); + this->mTCoords = mDefaultTCoords.data(); + this->mTCoordStride = sizeof(Vector2); + + this->mDescription.allowUpdateFrame = this->mDescription.wantDynamicTangentSpaceUpdate; + if (this->mDescription.allowUpdateFrame) + { + if (!this->mDescription.hasTangentSpaceVectors) + { + this->mDescription.allowUpdateFrame = false; + } + + if (!this->mNormals) + { + this->mDescription.allowUpdateFrame = false; + } + } + } + + this->ComputeIndices(); + InitializeTCoords(); + UpdatePositions(); + if (this->mDescription.allowUpdateFrame) + { + this->UpdateFrame(); + } + else if (this->mNormals) + { + this->UpdateNormals(); + } +} + +template +inline std::shared_ptr> const& TubeMesh::GetMedial() const +{ + return mMedial; +} + +template +inline std::function const& TubeMesh::GetRadial() const +{ + return mRadial; +} + +template +inline bool TubeMesh::IsClosed() const +{ + return mClosed; +} + +template +inline bool TubeMesh::IsSampleByArcLength() const +{ + return mSampleByArcLength; +} + +template +inline Vector3 const& TubeMesh::GetUpVector() const +{ + return mUpVector; +} + +template +void TubeMesh::InitializeTCoords() +{ + Vector2tcoord; + for (unsigned int r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + tcoord[1] = (Real)r / (Real)this->mDescription.rMax; + for (unsigned int c = 0; c <= this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = (Real)c / (Real)this->mDescription.numCols; + this->TCoord(i) = tcoord; + } + } +} + +template +void TubeMesh::UpdatePositions() +{ + uint32_t row, col, v, save; + for (row = 0, v = 0; row < this->mDescription.numRows; ++row, ++v) + { + Real t = mTSampler(row); + Real radius = mRadial(t); + // frame = (position, tangent, normal, binormal) + std::array, 4> frame = mFSampler(t); + for (col = 0, save = v; col < this->mDescription.numCols; ++col, ++v) + { + this->Position(v) = frame[0] + radius * (mCosAngle[col] * frame[2] + + mSinAngle[col] * frame[3]); + } + this->Position(v) = this->Position(save); + } + + if (mClosed) + { + for (col = 0; col < this->mDescription.numCols; ++col) + { + uint32_t i0 = col; + uint32_t i1 = col + this->mDescription.numCols * (this->mDescription.numRows - 1); + this->Position(i1) = this->Position(i0); + } + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerALU32.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerALU32.h new file mode 100644 index 000000000000..7e6909a80dae --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerALU32.h @@ -0,0 +1,562 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2017/07/04) + +#pragma once + +#include +#include + +// Support for unsigned integer arithmetic in BSNumber and BSRational. The +// Curiously Recurring Template Paradigm is used to allow the UInteger +// types to share code without introducing virtual functions. + +namespace gte +{ + +template +class UIntegerALU32 +{ +public: + // Comparisons. These are not generic. They rely on their being caled + // when the two BSNumber arguments to BSNumber::operatorX() are of the + // form 1.u*2^p and 1.v*2^p. The comparisons apply to 1.u and 1.v as + // unsigned integers with their leading 1-bits aligned. + bool operator==(UInteger const& number) const; + bool operator!=(UInteger const& number) const; + bool operator< (UInteger const& number) const; + bool operator<=(UInteger const& number) const; + bool operator> (UInteger const& number) const; + bool operator>=(UInteger const& number) const; + + // Arithmetic operations. These are performed in-place; that is, the + // result is stored in 'this' object. The goal is to reduce the number of + // object copies, much like the goal is for std::move. The Sub function + // requires the inputs to satisfy n0 > n1. + void Add(UInteger const& n0, UInteger const& n1); + void Sub(UInteger const& n0, UInteger const& n1); + void Mul(UInteger const& n0, UInteger const& n1); + + // The shift is performed in-place; that is, the result is stored in + // 'this' object. + void ShiftLeft(UInteger const& number, int32_t shift); + + // The 'number' is even and positive. It is shifted right to become an + // odd number and the return value is the amount shifted. The operation + // is performed in-place; that is, the result is stored in 'this' object. + int32_t ShiftRightToOdd(UInteger const& number); + + // Get a block of numRequested bits starting with the leading 1-bit of the + // nonzero number. The returned number has the prefix stored in the + // high-order bits. Additional bits are copied and used by the caller for + // rounding. This function supports conversions from 'float' and 'double'. + // The input 'numRequested' is smaller than 64. + uint64_t GetPrefix(int32_t numRequested) const; +}; + + +template +bool UIntegerALU32::operator==(UInteger const& number) const +{ + UInteger const& self = *(UInteger const*)this; + int32_t numBits = self.GetNumBits(); + if (numBits != number.GetNumBits()) + { + return false; + } + + if (numBits > 0) + { + auto const& bits = self.GetBits(); + auto const& nBits = number.GetBits(); + int32_t const last = self.GetSize() - 1; + for (int32_t i = last; i >= 0; --i) + { + if (bits[i] != nBits[i]) + { + return false; + } + } + } + return true; +} + +template +bool UIntegerALU32::operator!=(UInteger const& number) const +{ + return !operator==(number); +} + +template +bool UIntegerALU32::operator< (UInteger const& number) const +{ + UInteger const& self = *(UInteger const*)this; + int32_t nNumBits = number.GetNumBits(); + auto const& nBits = number.GetBits(); + + int32_t numBits = self.GetNumBits(); + if (numBits > 0 && nNumBits > 0) + { + // The numbers must be compared as if they are left-aligned with + // each other. We got here because we had self = 1.u * 2^p and + // number = 1.v * 2^p. Although they have the same exponent, it is + // possible that 'self < number' but 'numBits(1u) > numBits(1v)'. + // Compare the bits one 32-bit block at a time. + auto const& bits = self.GetBits(); + int bitIndex0 = numBits - 1; + int bitIndex1 = nNumBits - 1; + int block0 = bitIndex0 / 32; + int block1 = bitIndex1 / 32; + int numBlockBits0 = 1 + (bitIndex0 % 32); + int numBlockBits1 = 1 + (bitIndex1 % 32); + uint64_t n0shift = bits[block0]; + uint64_t n1shift = nBits[block1]; + while (block0 >= 0 && block1 >= 0) + { + // Shift the bits in the leading blocks to the high-order bit. + uint32_t value0 = (uint32_t)((n0shift << (32 - numBlockBits0)) & 0x00000000FFFFFFFFull); + uint32_t value1 = (uint32_t)((n1shift << (32 - numBlockBits1)) & 0x00000000FFFFFFFFull); + + // Shift bits in the next block (if any) to fill the current + // block. + if (--block0 >= 0) + { + n0shift = bits[block0]; + value0 |= (uint32_t)((n0shift >> numBlockBits0) & 0x00000000FFFFFFFFull); + } + if (--block1 >= 0) + { + n1shift = nBits[block1]; + value1 |= (uint32_t)((n1shift >> numBlockBits1) & 0x00000000FFFFFFFFull); + } + if (value0 < value1) + { + return true; + } + if (value0 > value1) + { + return false; + } + } + return block0 < block1; + } + else + { + // One or both numbers are negative. The only time 'less than' is + // 'true' is when 'number' is positive. + return (nNumBits > 0); + } +} + +template +bool UIntegerALU32::operator<=(UInteger const& number) const +{ + return operator<(number) || operator==(number); +} + +template +bool UIntegerALU32::operator> (UInteger const& number) const +{ + return !operator<=(number); +} + +template +bool UIntegerALU32::operator>=(UInteger const& number) const +{ + return !operator<(number); +} + +template +void UIntegerALU32::Add(UInteger const& n0, UInteger const& n1) +{ + UInteger& self = *(UInteger*)this; + int32_t n0NumBits = n0.GetNumBits(); + int32_t n1NumBits = n1.GetNumBits(); + + // Add the numbers considered as positive integers. Set the last block to + // zero in case no carry-out occurs. + int numBits = std::max(n0NumBits, n1NumBits) + 1; + self.SetNumBits(numBits); + self.SetBack(0); + + // Get the input array sizes. + int32_t numElements0 = n0.GetSize(); + int32_t numElements1 = n1.GetSize(); + + // Order the inputs so that the first has the most blocks. + auto const& u0 = + (numElements0 >= numElements1 ? n0.GetBits() : n1.GetBits()); + auto const& u1 = + (numElements0 >= numElements1 ? n1.GetBits() : n0.GetBits()); + auto numElements = std::minmax(numElements0, numElements1); + + // Add the u1-blocks to u0-blocks. + auto& bits = self.GetBits(); + uint64_t carry = 0, sum; + int32_t i; + for (i = 0; i < numElements.first; ++i) + { + sum = u0[i] + (u1[i] + carry); + bits[i] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + carry = (sum >> 32); + } + + // We have no more u1-blocks. Propagate the carry-out, if there is one, or + // copy the remaining blocks if there is not. + if (carry > 0) + { + for (/**/; i < numElements.second; ++i) + { + sum = u0[i] + carry; + bits[i] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + carry = (sum >> 32); + } + if (carry > 0) + { + bits[i] = (uint32_t)(carry & 0x00000000FFFFFFFFull); + } + } + else + { + for (/**/; i < numElements.second; ++i) + { + bits[i] = u0[i]; + } + } + + // Reduce the number of bits if there was not a carry-out. + uint32_t firstBitIndex = (numBits - 1) % 32; + uint32_t mask = (1 << firstBitIndex); + if ((mask & self.GetBack()) == 0) + { + self.SetNumBits(--numBits); + } +} + +template +void UIntegerALU32::Sub(UInteger const& n0, UInteger const& n1) +{ + UInteger& self = *(UInteger*)this; + int32_t n0NumBits = n0.GetNumBits(); + auto const& n0Bits = n0.GetBits(); + auto const& n1Bits = n1.GetBits(); + + // Subtract the numbers considered as positive integers. We know that + // n0 > n1, so create a number n2 that has the same number of bits as + // n0 and use two's-complement to generate -n2, and then add n0 and -n2. + // The result is nonnegative, so we do not need to apply two's complement + // to a negative result to extract the sign and absolute value. + + // Get the input array sizes. We know numElements0 >= numElements1. + int32_t numElements0 = n0.GetSize(); + int32_t numElements1 = n1.GetSize(); + + // Create the two's-complement number n2. We know n2.GetNumElements() is + // the same as numElements0. + UInteger n2(n0NumBits); + auto& n2Bits = n2.GetBits(); + int32_t i; + for (i = 0; i < numElements1; ++i) + { + n2Bits[i] = ~n1Bits[i]; + } + for (/**/; i < numElements0; ++i) + { + n2Bits[i] = ~0u; + } + + // Now add 1 to the bit-negated result to obtain -n1. + uint64_t carry = 1, sum; + for (i = 0; i < numElements0; ++i) + { + sum = n2Bits[i] + carry; + n2Bits[i] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + carry = (sum >> 32); + } + + // Add the numbers as positive integers. Set the last block to zero in + // case no carry-out occurs. + self.SetNumBits(n0NumBits + 1); + self.SetBack(0); + + // Add the n0-blocks to n2-blocks. + auto& bits = self.GetBits(); + for (i = 0, carry = 0; i < numElements0; ++i) + { + sum = n2Bits[i] + (n0Bits[i] + carry); + bits[i] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + carry = (sum >> 32); + } + + // Strip off the bits introduced by two's-complement. + int32_t block; + for (block = numElements0 - 1; block >= 0; --block) + { + if (bits[block] > 0) + { + break; + } + } + + // Added this condition to allow n0 == n1 and satisfy the static analyzer + if (block < 0) + { + self = 0; + } + else + { + self.SetNumBits(32 * block + GetLeadingBit(bits[block]) + 1); + } +} + +template +void UIntegerALU32::Mul(UInteger const& n0, UInteger const& n1) +{ + UInteger& self = *(UInteger*)this; + int32_t n0NumBits = n0.GetNumBits(); + int32_t n1NumBits = n1.GetNumBits(); + auto const& n0Bits = n0.GetBits(); + auto const& n1Bits = n1.GetBits(); + + // The number of bits is at most this, possibly one bit smaller. + int numBits = n0NumBits + n1NumBits; + self.SetNumBits(numBits); + auto& bits = self.GetBits(); + + // Product of a single-block number with a multiple-block number. + UInteger product(numBits); + auto& pBits = product.GetBits(); + + // Get the array sizes. + int32_t const numElements0 = n0.GetSize(); + int32_t const numElements1 = n1.GetSize(); + int32_t const numElements = self.GetSize(); + + // Compute the product v = u0*u1. + int32_t i0, i1, i2; + uint64_t term, sum; + + // The case i0 == 0 is handled separately to initialize the accumulator + // with u0[0]*v. This avoids having to fill the bytes of 'bits' with + // zeros outside the double loop, something that can be a performance + // issue when 'numBits' is large. + uint64_t block0 = n0Bits[0]; + uint64_t carry = 0; + for (i1 = 0; i1 < numElements1; ++i1) + { + term = block0 * n1Bits[i1] + carry; + bits[i1] = (uint32_t)(term & 0x00000000FFFFFFFFull); + carry = (term >> 32); + } + if (i1 < numElements) + { + bits[i1] = (uint32_t)(carry & 0x00000000FFFFFFFFull); + } + + for (i0 = 1; i0 < numElements0; ++i0) + { + // Compute the product p = u0[i0]*u1. + block0 = n0Bits[i0]; + carry = 0; + for (i1 = 0, i2 = i0; i1 < numElements1; ++i1, ++i2) + { + term = block0 * n1Bits[i1] + carry; + pBits[i2] = (uint32_t)(term & 0x00000000FFFFFFFFull); + carry = (term >> 32); + } + if (i2 < numElements) + { + pBits[i2] = (uint32_t)(carry & 0x00000000FFFFFFFFull); + } + + // Add p to the accumulator v. + carry = 0; + for (i1 = 0, i2 = i0; i1 < numElements1; ++i1, ++i2) + { + sum = pBits[i2] + (bits[i2] + carry); + bits[i2] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + carry = (sum >> 32); + } + if (i2 < numElements) + { + sum = pBits[i2] + carry; + bits[i2] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + } + } + + // Reduce the number of bits if there was not a carry-out. + uint32_t firstBitIndex = (numBits - 1) % 32; + uint32_t mask = (1 << firstBitIndex); + if ((mask & self.GetBack()) == 0) + { + self.SetNumBits(--numBits); + } +} + +template +void UIntegerALU32::ShiftLeft(UInteger const& number, int32_t shift) +{ + UInteger& self = *(UInteger*)this; + int32_t nNumBits = number.GetNumBits(); + auto const& nBits = number.GetBits(); + + // Shift the 'number' considered as an odd positive integer. + self.SetNumBits(nNumBits + shift); + + // Set the low-order bits to zero. + auto& bits = self.GetBits(); + int32_t const shiftBlock = shift / 32; + for (int32_t i = 0; i < shiftBlock; ++i) + { + bits[i] = 0; + } + + // Get the location of the low-order 1-bit within the result. + int32_t const numInElements = number.GetSize(); + int32_t const lshift = shift % 32; + int32_t i, j; + if (lshift > 0) + { + // The trailing 1-bits for source and target are at different + // relative indices. Each shifted source block straddles a boundary + // between two target blocks, so we must extract the subblocks and + // copy accordingly. + int32_t const rshift = 32 - lshift; + uint32_t prev = 0, curr; + for (i = shiftBlock, j = 0; j < numInElements; ++i, ++j) + { + curr = nBits[j]; + bits[i] = (curr << lshift) | (prev >> rshift); + prev = curr; + } + if (i < self.GetSize()) + { + // The leading 1-bit of the source is at a relative index such + // that when you add the shift amount, that bit occurs in a new + // block. + bits[i] = (prev >> rshift); + } + } + else + { + // The trailing 1-bits for source and target are at the same relative + // index. The shift reduces to a block copy. + for (i = shiftBlock, j = 0; j < numInElements; ++i, ++j) + { + bits[i] = nBits[j]; + } + } +} + +template +int32_t UIntegerALU32::ShiftRightToOdd(UInteger const& number) +{ + UInteger& self = *(UInteger*)this; + auto const& nBits = number.GetBits(); + + // Get the leading 1-bit. + int32_t const numElements = number.GetSize(); + int32_t const numM1 = numElements - 1; + int32_t firstBitIndex = 32 * numM1 + GetLeadingBit(nBits[numM1]); + + // Get the trailing 1-bit. + int32_t lastBitIndex = -1; + for (int32_t block = 0; block < numElements; ++block) + { + uint32_t value = nBits[block]; + if (value > 0) + { + lastBitIndex = 32 * block + GetTrailingBit(value); + break; + } + } + + // The right-shifted result. + self.SetNumBits(firstBitIndex - lastBitIndex + 1); + auto& bits = self.GetBits(); + int32_t const numBlocks = self.GetSize(); + + // Get the location of the low-order 1-bit within the result. + int32_t const shiftBlock = lastBitIndex / 32; + int32_t rshift = lastBitIndex % 32; + if (rshift > 0) + { + int32_t const lshift = 32 - rshift; + int32_t i, j = shiftBlock; + uint32_t curr = nBits[j++]; + for (i = 0; j < numElements; ++i, ++j) + { + uint32_t next = nBits[j]; + bits[i] = (curr >> rshift) | (next << lshift); + curr = next; + } + if (i < numBlocks) + { + bits[i] = (curr >> rshift); + } + } + else + { + for (int32_t i = 0, j = shiftBlock; i < numBlocks; ++i, ++j) + { + bits[i] = nBits[j]; + } + } + + return rshift + 32 * shiftBlock; +} + +template +uint64_t UIntegerALU32::GetPrefix(int32_t numRequested) const +{ + UInteger const& self = *(UInteger const*)this; + auto const& bits = self.GetBits(); + + // Copy to 'prefix' the leading 32-bit block that is nonzero. + int32_t bitIndex = self.GetNumBits() - 1; + int32_t blockIndex = bitIndex / 32; + uint64_t prefix = bits[blockIndex]; + + // Get the number of bits in the block starting with the leading 1-bit. + int32_t firstBitIndex = bitIndex % 32; + int32_t numBlockBits = firstBitIndex + 1; + + // Shift the leading 1-bit to bit-63 of prefix. We have consumed + // numBlockBits, which might not be the entire budget. + int32_t targetIndex = 63; + prefix <<= targetIndex - firstBitIndex; + numRequested -= numBlockBits; + targetIndex -= numBlockBits; + + if (numRequested > 0 && --blockIndex >= 0) + { + // More bits are available. Copy and shift the entire 32-bit next + // block and OR it into the 'prefix'. For 'float', we will have + // consumed the entire budget. For 'double', we might have to get + // bits from a third block. + uint64_t nextBlock = bits[blockIndex]; + nextBlock <<= targetIndex - 31; // Shift amount is positive. + prefix |= nextBlock; + numRequested -= 32; + targetIndex -= 32; + + if (numRequested > 0 && --blockIndex >= 0) + { + // We know that targetIndex > 0; only 'double' allows us to get + // here, so numRequested is at most 53. We also know that + // targetIndex < 32 because we started with 63 and subtracted + // at least 32 from it. Thus, the shift amount is positive. + nextBlock = bits[blockIndex]; + nextBlock >>= 31 - targetIndex; + prefix |= nextBlock; + } + } + + return prefix; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerAP32.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerAP32.cpp new file mode 100644 index 000000000000..342ad43e1a76 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerAP32.cpp @@ -0,0 +1,154 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2017/07/04) + +#include +#include +#include +#include +using namespace gte; + +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) +std::atomic UIntegerAP32::msMaxSize; +#endif + + +UIntegerAP32::UIntegerAP32() + : + mNumBits(0) +{ +} + +UIntegerAP32::UIntegerAP32(UIntegerAP32 const& number) +{ + *this = number; +} + +UIntegerAP32::UIntegerAP32(uint32_t number) +{ + if (number > 0) + { + int32_t first = GetLeadingBit(number); + int32_t last = GetTrailingBit(number); + mNumBits = first - last + 1; + mBits.resize(1); + mBits[0] = (number >> last); + } + else + { + mNumBits = 0; + } + +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) + AtomicMax(msMaxSize, mBits.size()); +#endif +} + +UIntegerAP32::UIntegerAP32(uint64_t number) +{ + if (number > 0) + { + int32_t first = GetLeadingBit(number); + int32_t last = GetTrailingBit(number); + number >>= last; + mNumBits = first - last + 1; + mBits.resize(1 + (mNumBits - 1) / 32); + mBits[0] = (uint32_t)(number & 0x00000000FFFFFFFFull); + if (mBits.size() > 1) + { + mBits[1] = (uint32_t)((number >> 32) & 0x00000000FFFFFFFFull); + } + } + else + { + mNumBits = 0; + } + +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) + AtomicMax(msMaxSize, mBits.size()); +#endif +} + +UIntegerAP32::UIntegerAP32(int numBits) + : + mNumBits(numBits), + mBits(1 + (numBits - 1) / 32) +{ +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) + AtomicMax(msMaxSize, mBits.size()); +#endif +} + +UIntegerAP32& UIntegerAP32::operator=(UIntegerAP32 const& number) +{ + mNumBits = number.mNumBits; + mBits = number.mBits; + return *this; +} + +UIntegerAP32::UIntegerAP32(UIntegerAP32&& number) +{ + *this = std::move(number); +} + +UIntegerAP32& UIntegerAP32::operator=(UIntegerAP32&& number) +{ + mNumBits = number.mNumBits; + mBits = std::move(number.mBits); + number.mNumBits = 0; + return *this; +} + +void UIntegerAP32::SetNumBits(uint32_t numBits) +{ + mNumBits = numBits; + if (mNumBits > 0) + { + mBits.resize(1 + (numBits - 1) / 32); + } + else + { + mBits.clear(); + } + +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) + AtomicMax(msMaxSize, mBits.size()); +#endif +} + +bool UIntegerAP32::Write(std::ofstream& output) const +{ + if (output.write((char const*)&mNumBits, sizeof(mNumBits)).bad()) + { + return false; + } + + std::size_t size = mBits.size(); + if (output.write((char const*)&size, sizeof(size)).bad()) + { + return false; + } + + return output.write((char const*)&mBits[0], size*sizeof(mBits[0])).good(); +} + +bool UIntegerAP32::Read(std::ifstream& input) +{ + if (input.read((char*)&mNumBits, sizeof(mNumBits)).bad()) + { + return false; + } + + std::size_t size; + if (input.read((char*)&size, sizeof(size)).bad()) + { + return false; + } + + mBits.resize(size); + return input.read((char*)&mBits[0], size*sizeof(mBits[0])).good(); +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerAP32.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerAP32.h new file mode 100644 index 000000000000..8a65e0225c6d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerAP32.h @@ -0,0 +1,118 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// Class UIntegerAP32 is designed to support arbitrary precision arithmetic +// using BSNumber and BSRational. It is not a general-purpose class for +// arithmetic of unsigned integers. + +// Uncomment this to collect statistics on how large the UIntegerAP32 storage +// becomes when using it for the UIntegerType of BSNumber. After a sequence +// of BSNumber operations, look at UIntegerAP32::msMaxSize in the debugger +// watch window. If the number is not too large, you might be safe in +// replacing UIntegerAP32 by UIntegerFP32, where N is the value of +// UIntegerAP32::msMaxSize. This leads to much faster code because you no +// you no longer have dynamic memory allocations and deallocations that occur +// regularly with std::vector during BSNumber operations. A safer +// choice is to argue mathematically that the maximum size is bounded by N. +// This requires an analysis of how many bits of precision you need for the +// types of computation you perform. See class BSPrecision for code that +// allows you to compute maximum N. +// +//#define GTE_COLLECT_UINTEGERAP32_STATISTICS + +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) +#include +#endif + +namespace gte +{ + +class UIntegerAP32 : public UIntegerALU32 +{ +public: + // Construction. + UIntegerAP32(); + UIntegerAP32(UIntegerAP32 const& number); + UIntegerAP32(uint32_t number); + UIntegerAP32(uint64_t number); + UIntegerAP32(int numBits); + + // Assignment. + UIntegerAP32& operator=(UIntegerAP32 const& number); + + // Support for std::move. + UIntegerAP32(UIntegerAP32&& number); + UIntegerAP32& operator=(UIntegerAP32&& number); + + // Member access. + void SetNumBits(uint32_t numBits); + inline int32_t GetNumBits() const; + inline std::vector const& GetBits() const; + inline std::vector& GetBits(); + inline void SetBack(uint32_t value); + inline uint32_t GetBack() const; + inline int32_t GetSize() const; + + // Disk input/output. The fstream objects should be created using + // std::ios::binary. The return value is 'true' iff the operation + // was successful. + bool Write(std::ofstream& output) const; + bool Read(std::ifstream& input); + +private: + int32_t mNumBits; + std::vector mBits; + + friend class UnitTestBSNumber; + +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) + static std::atomic msMaxSize; +public: + static void SetMaxSizeToZero() { msMaxSize = 0; } + static size_t GetMaxSize() { return msMaxSize; } +#endif +}; + + +inline int32_t UIntegerAP32::GetNumBits() const +{ + return mNumBits; +} + +inline std::vector const& UIntegerAP32::GetBits() const +{ + return mBits; +} + +inline std::vector& UIntegerAP32::GetBits() +{ + return mBits; +} + +inline void UIntegerAP32::SetBack(uint32_t value) +{ + mBits.back() = value; +} + +inline uint32_t UIntegerAP32::GetBack() const +{ + return mBits.back(); +} + +inline int32_t UIntegerAP32::GetSize() const +{ + return static_cast(mBits.size()); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerFP32.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerFP32.h new file mode 100644 index 000000000000..6743d8d218f0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerFP32.h @@ -0,0 +1,301 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2017/07/04) + +#pragma once + +#include +#include +#include +#include + +// Class UIntegerFP32 is designed to support fixed precision arithmetic +// using BSNumber and BSRational. It is not a general-purpose class for +// arithmetic of unsigned integers. The template parameter N is the +// number of 32-bit words required to store the precision for the desired +// computations (maximum number of bits is 32*N). + +// Uncomment this to trap when an attempt is made to create storage with +// more than N uint32_t items. +// +//#define GTE_ASSERT_ON_UINTEGERFP32_OUT_OF_RANGE + +// Uncomment this to collect statistics on how large the UIntegerFP32 storage +// becomes when using it for the UIntegerType of BSNumber. After a sequence +// of BSNumber operations, look at UIntegerFP32::msMaxSize in the debugger +// watch window. If the number is not too large, you might be safe in +// replacing the template parameter N by a smaller number. See class +// BSPrecision for code that allows you to compute maximum N. +// +// NOTE: Because UIntegerFP32::msMaxSize is a static member of a template +// class, if you expose this define, you must also declare in your code +// 'std::atomic gte::UIntegerFP32::msMaxSize;' +//#define GTE_COLLECT_UINTEGERFP32_STATISTICS + +#if defined(GTE_COLLECT_UINTEGERFP32_STATISTICS) +#include +#endif + +namespace gte +{ + +template +class UIntegerFP32 : public UIntegerALU32> +{ +public: + // Construction. + UIntegerFP32(); + UIntegerFP32(UIntegerFP32 const& number); + UIntegerFP32(uint32_t number); + UIntegerFP32(uint64_t number); + UIntegerFP32(int numBits); + + // Assignment. Only mSize elements are copied. + UIntegerFP32& operator=(UIntegerFP32 const& number); + + // Support for std::move. The interface is required by BSNumber, but the + // std::move of std::array is a copy (no pointer stealing). Moreover, + // a std::array object in this class typically uses smaller than N + // elements, the actual size stored in mSize, so we do not want to move + // everything. Therefore, the move operator only copies the bits BUT + // BUT 'number' is modified as if you have stolen the data (mNumBits and + // mSize set to zero). + UIntegerFP32(UIntegerFP32&& number); + UIntegerFP32& operator=(UIntegerFP32&& number); + + // Member access. + void SetNumBits(uint32_t numBits); + inline int32_t GetNumBits() const; + inline std::array const& GetBits() const; + inline std::array& GetBits(); + inline void SetBack(uint32_t value); + inline uint32_t GetBack() const; + inline int32_t GetSize() const; + + // Disk input/output. The fstream objects should be created using + // std::ios::binary. The return value is 'true' iff the operation + // was successful. + bool Write(std::ofstream& output) const; + bool Read(std::ifstream& input); + +private: + int32_t mNumBits, mSize; + std::array mBits; + + friend class UnitTestBSNumber; + +#if defined(GTE_COLLECT_UINTEGERFP32_STATISTICS) + static std::atomic msMaxSize; +public: + static void SetMaxSizeToZero() { msMaxSize = 0; } + static int32_t GetMaxSize() { return msMaxSize; } +#endif +}; + + +template +UIntegerFP32::UIntegerFP32() + : + mNumBits(0), + mSize(0) +{ + static_assert(N >= 1, "Invalid size N."); +} + +template +UIntegerFP32::UIntegerFP32(UIntegerFP32 const& number) +{ + static_assert(N >= 1, "Invalid size N."); + + *this = number; +} + +template +UIntegerFP32::UIntegerFP32(uint32_t number) +{ + static_assert(N >= 1, "Invalid size N."); + + if (number > 0) + { + int32_t first = GetLeadingBit(number); + int32_t last = GetTrailingBit(number); + mNumBits = first - last + 1; + mSize = 1; + mBits[0] = (number >> last); + } + else + { + mNumBits = 0; + mSize = 0; + } + +#if defined(GTE_COLLECT_UINTEGERFP32_STATISTICS) + AtomicMax(msMaxSize, mSize); +#endif +} + +template +UIntegerFP32::UIntegerFP32(uint64_t number) +{ + static_assert(N >= 2, "N not large enough to store 64-bit integers."); + + if (number > 0) + { + int32_t first = GetLeadingBit(number); + int32_t last = GetTrailingBit(number); + number >>= last; + mNumBits = first - last + 1; + mSize = 1 + (mNumBits - 1) / 32; + mBits[0] = (uint32_t)(number & 0x00000000FFFFFFFFull); + if (mSize > 1) + { + mBits[1] = (uint32_t)((number >> 32) & 0x00000000FFFFFFFFull); + } + } + else + { + mNumBits = 0; + mSize = 0; + } + +#if defined(GTE_COLLECT_UINTEGERFP32_STATISTICS) + AtomicMax(msMaxSize, mSize); +#endif +} + +template +UIntegerFP32::UIntegerFP32(int numBits) + : + mNumBits(numBits), + mSize(1 + (numBits - 1) / 32) +{ + static_assert(N >= 1, "Invalid size N."); + +#if defined(GTE_ASSERT_ON_UINTEGERFP32_OUT_OF_RANGE) + LogAssert(mSize <= N, "N not large enough to store number of bits."); +#endif + +#if defined(GTE_COLLECT_UINTEGERFP32_STATISTICS) + AtomicMax(msMaxSize, mSize); +#endif +} + +template +UIntegerFP32& UIntegerFP32::operator=(UIntegerFP32 const& number) +{ + static_assert(N >= 1, "Invalid size N."); + + mNumBits = number.mNumBits; + mSize = number.mSize; + std::copy(number.mBits.begin(), number.mBits.begin() + mSize, + mBits.begin()); + return *this; +} + +template +UIntegerFP32::UIntegerFP32(UIntegerFP32&& number) +{ + *this = std::move(number); +} + +template +UIntegerFP32& UIntegerFP32::operator=(UIntegerFP32&& number) +{ + mNumBits = number.mNumBits; + mSize = number.mSize; + std::copy(number.mBits.begin(), number.mBits.begin() + mSize, + mBits.begin()); + number.mNumBits = 0; + number.mSize = 0; + return *this; +} + +template +void UIntegerFP32::SetNumBits(uint32_t numBits) +{ + mNumBits = numBits; + mSize = 1 + (numBits - 1) / 32; + +#if defined(GTE_ASSERT_ON_UINTEGERFP32_OUT_OF_RANGE) + LogAssert(mSize <= N, "N not large enough to store number of bits."); +#endif + +#if defined(GTE_COLLECT_UINTEGERFP32_STATISTICS) + AtomicMax(msMaxSize, mSize); +#endif +} + +template inline +int32_t UIntegerFP32::GetNumBits() const +{ + return mNumBits; +} + +template inline +std::array const& UIntegerFP32::GetBits() const +{ + return mBits; +} + +template inline +std::array& UIntegerFP32::GetBits() +{ + return mBits; +} + +template inline +void UIntegerFP32::SetBack(uint32_t value) +{ + mBits[mSize - 1] = value; +} + +template inline +uint32_t UIntegerFP32::GetBack() const +{ + return mBits[mSize - 1]; +} + +template inline +int32_t UIntegerFP32::GetSize() const +{ + return mSize; +} + +template +bool UIntegerFP32::Write(std::ofstream& output) const +{ + if (output.write((char const*)&mNumBits, sizeof(mNumBits)).bad()) + { + return false; + } + + if (output.write((char const*)&mSize, sizeof(mSize)).bad()) + { + return false; + } + + return output.write((char const*)&mBits[0], + mSize*sizeof(mBits[0])).good(); +} + +template +bool UIntegerFP32::Read(std::ifstream& input) +{ + if (input.read((char*)&mNumBits, sizeof(mNumBits)).bad()) + { + return false; + } + + if (input.read((char*)&mSize, sizeof(mSize)).bad()) + { + return false; + } + + return input.read((char*)&mBits[0], mSize*sizeof(mBits[0])).good(); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUniqueVerticesTriangles.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUniqueVerticesTriangles.h new file mode 100644 index 000000000000..e4340dad9e21 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUniqueVerticesTriangles.h @@ -0,0 +1,154 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The VertexType must have an operator< member because it is used as the +// key in a std::map. For example, if VertexType is +// Vector3, the comparison operator already exist. Another example is +// that you have a vertex type used for vertex coloring, say, +// struct VertexType +// { +// Vector3 position; +// Vector4 color; +// bool operator< (VertexType const& v) const +// { +// return position < v.position; +// } +// } +// The comparision will guarantee unique vertex positions, although if you +// have two VertexType objects with the same position but different colors, +// there is no guarantee which color will occur in the final result. + +namespace gte +{ + +template +class UniqueVerticesTriangles +{ +public: + // Triangle soup. The input vertex array consists of triples of vertices, + // each triple representing a triangle. The array 'inVertices' must have + // a multiple of 3 elements. An array 'outVertices' of unique vertices + // and an array 'outIndices' of 'inVertices.size()/3' unique index triples + // are computed. The indices are relative to the array of unique vertices + // and each index triple represents a triangle. + UniqueVerticesTriangles( + std::vector const& inVertices, + std::vector& outVertices, + std::vector& outIndices); + + // Indexed triangles. The input vertex array consists of all vertices + // referenced by the input index array. The array 'inIndices' must have a + // multiple of 3 elements. An array 'outVertices' of unique vertices and + // an array 'outIndices' of 'indices.size()/3' unique index triples are + // computed. The indices are relative to the array of unique vertices and + // each index triple represents a triangle. + UniqueVerticesTriangles( + std::vector const& inVertices, + std::vector const& inIndices, + std::vector& outVertices, + std::vector& outIndices); + + // The input vertices have indices 0 <= i < VInNum. The output vertices + // have indices 0 <= j < VOutNum. The construction leads to a mapping of + // input indices i to output indices j. Duplicate vertices have different + // input indices but the same output index. The following function gives + // you access to the mapping. If the input index is invalid (i < 0 or + // i >= VINum), the return value is -1. + inline int GetOutputIndexFor(int index) const; + +private: + void ConstructUniqueVertices(std::vector const& inVertices, + std::vector& outVertices); + + int mNumInVertices, mNumOutVertices; + std::vector mInToOutMapping; +}; + + +template +UniqueVerticesTriangles::UniqueVerticesTriangles( + std::vector const& inVertices, + std::vector& outVertices, std::vector& outIndices) +{ + ConstructUniqueVertices(inVertices, outVertices); + + // The input index array is implicitly {<0,1,2>,<3,4,5>,...,} + // where n is the number of vertices. The output index array is the same + // as the mapping array. + outIndices.resize(inVertices.size()); + std::copy(mInToOutMapping.begin(), mInToOutMapping.end(), + outIndices.begin()); +} + +template +UniqueVerticesTriangles::UniqueVerticesTriangles( + std::vector const& inVertices, + std::vector const& inIndices, std::vector& outVertices, + std::vector& outIndices) +{ + ConstructUniqueVertices(inVertices, outVertices); + + // The input index array needs it indices mapped to the unique vertex + // indices. + outIndices.resize(inIndices.size()); + for (size_t i = 0; i < inIndices.size(); ++i) + { + outIndices[i] = mInToOutMapping[inIndices[i]]; + } +} + +template inline +int UniqueVerticesTriangles::GetOutputIndexFor(int index) const +{ + return mInToOutMapping[index]; +} + +template +void UniqueVerticesTriangles::ConstructUniqueVertices( + std::vector const& inVertices, + std::vector& outVertices) +{ + // Construct the unique vertices. + mNumInVertices = (int)inVertices.size(); + mInToOutMapping.resize(mNumInVertices); + std::map table; + mNumOutVertices = 0; + for (int i = 0; i < mNumInVertices; ++i) + { + auto const iter = table.find(inVertices[i]); + if (iter != table.end()) + { + // Vertex i is a duplicate of one inserted earlier into the + // table. Map vertex i to the first-found copy. + mInToOutMapping[i] = iter->second; + } + else + { + // Vertex i is the first occurrence of such a point. + table.insert(std::make_pair(inVertices[i], mNumOutVertices)); + mInToOutMapping[i] = mNumOutVertices; + ++mNumOutVertices; + } + } + + // Pack the unique vertices into an array in the correct order. + outVertices.resize(mNumOutVertices); + for (auto const& element : table) + { + outVertices[element.second] = element.first; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUnsymmetricEigenvalues.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUnsymmetricEigenvalues.h new file mode 100644 index 000000000000..cc4b7dec9114 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUnsymmetricEigenvalues.h @@ -0,0 +1,403 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// An implementation of the QR algorithm described in "Matrix Computations, +// 2nd edition" by G. H. Golub and C. F. Van Loan, The Johns Hopkins +// University Press, Baltimore MD, Fourth Printing 1993. In particular, +// the implementation is based on Chapter 7 (The Unsymmetric Eigenvalue +// Problem), Section 7.5 (The Practical QR Algorithm). + +namespace gte +{ + +template +class UnsymmetricEigenvalues +{ +public: + // The solver processes NxN matrices (not necessarily symmetric), where + // N >= 3 ('size' is N) and the matrix is stored in row-major order. The + // maximum number of iterations ('maxIterations') must be specified for + // reducing an upper Hessenberg matrix to an upper quasi-triangular + // matrix (upper triangular matrix of blocks where the diagonal blocks + // are 1x1 or 2x2). The goal is to compute the real-valued eigenvalues. + UnsymmetricEigenvalues(int32_t size, uint32_t maxIterations); + + // A copy of the NxN input is made internally. The order of the + // eigenvalues is specified by sortType: -1 (decreasing), 0 (no + // sorting), or +1 (increasing). When sorted, the eigenvectors are + // ordered accordingly. The return value is the number of iterations + // consumed when convergence occurred, 0xFFFFFFFF when convergence did + // not occur, or 0 when N <= 1 was passed to the constructor. + uint32_t Solve(Real const* input, int32_t sortType); + + // Get the real-valued eigenvalues of the matrix passed to Solve(...). + // The input 'eigenvalues' must have at least N elements. + void GetEigenvalues(uint32_t& numEigenvalues, Real* eigenvalues) const; + +private: + // 2D accessors to elements of mMatrix[]. + inline Real const& A(int r, int c) const; + inline Real& A(int r, int c); + + // Compute the Householder vector for (X[rmin],...,x[rmax]). The + // input vector is stored in mX in the index range [rmin,rmax]. The + // output vector V is stored in mV in the index range [rmin,rmax]. The + // scaled vector is S = (-2/Dot(V,V))*V and is stored in mScaledV in + // the index range [rmin,rmax]. + void House(int rmin, int rmax); + + // Support for replacing matrix A by P^T*A*P, where P is a Householder + // reflection computed using House(...). + void RowHouse(int rmin, int rmax, int cmin, int cmax); + void ColHouse(int rmin, int rmax, int cmin, int cmax); + + void ReduceToUpperHessenberg(); + void FrancisQRStep(int rmin, int rmax); + bool GetBlock(std::array& block); + + // The number N of rows and columns of the matrices to be processed. + int32_t mSize, mSizeM1; + + // The maximum number of iterations for reducing the tridiagonal mtarix + // to a diagonal matrix. + uint32_t mMaxIterations; + + // The internal copy of a matrix passed to the solver. + std::vector mMatrix; // NxN elements + + // Temporary storage to compute Householder reflections. + std::vector mX, mV, mScaledV, mW; // N elements + + // Flags about the zeroness of the subdiagonal entries. This is used + // to detect uncoupled submatrices and apply the QR algorithm to the + // corresponding subproblems. The storage is padded on both ends with + // zeros to avoid additional code logic when packing the eigenvalues + // for access by the caller. + std::vector mFlagStorage; + int* mSubdiagonalFlag; + + int mNumEigenvalues; + std::vector mEigenvalues; +}; + + +template +UnsymmetricEigenvalues::UnsymmetricEigenvalues(int32_t size, uint32_t maxIterations) + : + mSize(0), + mSizeM1(0), + mMaxIterations(0), + mNumEigenvalues(0) +{ + if (size >= 3 && maxIterations > 0) + { + mSize = size; + mSizeM1 = size - 1; + mMaxIterations = maxIterations; + mMatrix.resize(size * size); + mX.resize(size); + mV.resize(size); + mScaledV.resize(size); + mW.resize(size); + mFlagStorage.resize(size + 1); + std::fill(mFlagStorage.begin(), mFlagStorage.end(), 0); + mSubdiagonalFlag = &mFlagStorage[1]; + mEigenvalues.resize(mSize); + } +} + +template +uint32_t UnsymmetricEigenvalues::Solve(Real const* input, int32_t sortType) +{ + if (mSize > 0) + { + std::copy(input, input + mSize * mSize, mMatrix.begin()); + ReduceToUpperHessenberg(); + + std::array block; + bool found = GetBlock(block); + uint32_t numIterations; + for (numIterations = 0; numIterations < mMaxIterations; ++numIterations) + { + if (found) + { + // Solve the current subproblem. + FrancisQRStep(block[0], block[1] + 1); + + // Find another subproblem (if any). + found = GetBlock(block); + } + else + { + break; + } + } + + // The matrix is fully uncoupled, upper Hessenberg with 1x1 or + // 2x2 diagonal blocks. Golub and Van Loan call this "upper + // quasi-triangular". + mNumEigenvalues = 0; + std::fill(mEigenvalues.begin(), mEigenvalues.end(), (Real)0); + for (int i = 0; i < mSizeM1; ++i) + { + if (mSubdiagonalFlag[i] == 0) + { + if (mSubdiagonalFlag[i - 1] == 0) + { + // We have a 1x1 block with a real eigenvalue. + mEigenvalues[mNumEigenvalues++] = A(i, i); + } + } + else + { + if (mSubdiagonalFlag[i - 1] == 0 && mSubdiagonalFlag[i + 1] == 0) + { + // We have a 2x2 block that might have real eigenvalues. + Real a00 = A(i, i); + Real a01 = A(i, i + 1); + Real a10 = A(i + 1, i); + Real a11 = A(i + 1, i + 1); + Real tr = a00 + a11; + Real det = a00 * a11 - a01 * a10; + Real halfTr = tr * (Real)0.5; + Real discr = halfTr * halfTr - det; + if (discr >= (Real)0) + { + Real rootDiscr = std::sqrt(discr); + mEigenvalues[mNumEigenvalues++] = halfTr - rootDiscr; + mEigenvalues[mNumEigenvalues++] = halfTr + rootDiscr; + } + } + // else: + // The QR iteration failed to converge at this block. It + // must also be the case that numIterations == mMaxIterations. + // TODO: The caller will be aware of this when testing the + // returned numIterations. Is there a remedy for such a + // case? (This happened with root finding using the + // companion matrix of a polynomial.) + } + } + + if (sortType != 0 && mNumEigenvalues > 1) + { + if (sortType > 0) + { + std::sort(mEigenvalues.begin(), mEigenvalues.begin() + mNumEigenvalues, + std::less()); + } + else + { + std::sort(mEigenvalues.begin(), mEigenvalues.begin() + mNumEigenvalues, + std::greater()); + } + } + + return numIterations; + } + return 0; +} + +template +void UnsymmetricEigenvalues::GetEigenvalues(uint32_t& numEigenvalues, Real* eigenvalues) const +{ + if (mSize > 0) + { + numEigenvalues = mNumEigenvalues; + Memcpy(eigenvalues, mEigenvalues.data(), numEigenvalues * sizeof(Real)); + } + else + { + numEigenvalues = 0; + } +} + +template +inline Real const& UnsymmetricEigenvalues::A(int r, int c) const +{ + return mMatrix[c + r * mSize]; +} + +template +inline Real& UnsymmetricEigenvalues::A(int r, int c) +{ + return mMatrix[c + r * mSize]; +} + +template +void UnsymmetricEigenvalues::House(int rmin, int rmax) +{ + Real length = (Real)0; + for (int r = rmin; r <= rmax; ++r) + { + length += mX[r] * mX[r]; + } + length = std::sqrt(length); + if (length != (Real)0) + { + Real sign = (mX[rmin] >= (Real)0 ? (Real)1 : (Real)-1); + Real invDenom = ((Real)1) / (mX[rmin] + sign * length); + for (int r = rmin + 1; r <= rmax; ++r) + { + mV[r] = mX[r] * invDenom; + } + } + mV[rmin] = (Real)1; + + Real dot = (Real)1; + for (int r = rmin + 1; r <= rmax; ++r) + { + dot += mV[r] * mV[r]; + } + Real scale = ((Real)-2) / dot; + for (int r = rmin; r <= rmax; ++r) + { + mScaledV[r] = scale * mV[r]; + } +} + +template +void UnsymmetricEigenvalues::RowHouse(int rmin, int rmax, int cmin, int cmax) +{ + for (int c = cmin; c <= cmax; ++c) + { + mW[c] = (Real)0; + for (int r = rmin; r <= rmax; ++r) + { + mW[c] += mScaledV[r] * A(r, c); + } + } + + for (int r = rmin; r <= rmax; ++r) + { + for (int c = cmin; c <= cmax; ++c) + { + A(r, c) += mV[r] * mW[c]; + } + } +} + +template +void UnsymmetricEigenvalues::ColHouse(int rmin, int rmax, int cmin, int cmax) +{ + for (int r = rmin; r <= rmax; ++r) + { + mW[r] = (Real)0; + for (int c = cmin; c <= cmax; ++c) + { + mW[r] += mScaledV[c] * A(r, c); + } + } + + for (int r = rmin; r <= rmax; ++r) + { + for (int c = cmin; c <= cmax; ++c) + { + A(r, c) += mW[r] * mV[c]; + } + } +} + +template +void UnsymmetricEigenvalues::ReduceToUpperHessenberg() +{ + for (int c = 0, cp1 = 1; c <= mSize - 3; ++c, ++cp1) + { + for (int r = cp1; r <= mSizeM1; ++r) + { + mX[r] = A(r, c); + } + + House(cp1, mSizeM1); + RowHouse(cp1, mSizeM1, c, mSizeM1); + ColHouse(0, mSizeM1, cp1, mSizeM1); + } +} + +template +void UnsymmetricEigenvalues::FrancisQRStep(int rmin, int rmax) +{ + // Apply the double implicit shift step. + int const i0 = rmax - 1, i1 = rmax; + Real a00 = A(i0, i0); + Real a01 = A(i0, i1); + Real a10 = A(i1, i0); + Real a11 = A(i1, i1); + Real tr = a00 + a11; + Real det = a00 * a11 - a01 * a10; + + int const j0 = rmin, j1 = j0 + 1, j2 = j1 + 1; + Real b00 = A(j0, j0); + Real b01 = A(j0, j1); + Real b10 = A(j1, j0); + Real b11 = A(j1, j1); + Real b21 = A(j2, j1); + mX[rmin] = b00 * (b00 - tr) + b01 * b10 + det; + mX[rmin + 1] = b10 * (b00 + b11 - tr); + mX[rmin + 2] = b10 * b21; + + House(rmin, rmin + 2); + RowHouse(rmin, rmin + 2, rmin, rmax); + ColHouse(rmin, std::min(rmax, rmin + 3), rmin, rmin + 2); + + // Apply Householder reflections to restore the matrix to upper + // Hessenberg form. + for (int c = 0, cp1 = 1; c <= mSize - 3; ++c, ++cp1) + { + int kmax = std::min(cp1 + 2, mSizeM1); + for (int r = cp1; r <= kmax; ++r) + { + mX[r] = A(r, c); + } + + House(cp1, kmax); + RowHouse(cp1, kmax, c, mSizeM1); + ColHouse(0, mSizeM1, cp1, kmax); + } +} + +template +bool UnsymmetricEigenvalues::GetBlock(std::array& block) +{ + for (int i = 0; i < mSizeM1; ++i) + { + Real a00 = A(i, i); + Real a11 = A(i + 1, i + 1); + Real a21 = A(i + 1, i); + Real sum0 = a00 + a11; + Real sum1 = sum0 + a21; + mSubdiagonalFlag[i] = (sum1 != sum0 ? 1 : 0); + } + + for (int i = 0; i < mSizeM1; ++i) + { + if (mSubdiagonalFlag[i] == 1) + { + block = {{ i, -1 }}; + while (i < mSizeM1 && mSubdiagonalFlag[i] == 1) + { + block[1] = i++; + } + if (block[1] != block[0]) + { + return true; + } + } + } + return false; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVEManifoldMesh.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVEManifoldMesh.cpp new file mode 100644 index 000000000000..74d271c52ea5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVEManifoldMesh.cpp @@ -0,0 +1,228 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +#include +using namespace gte; + +VEManifoldMesh::~VEManifoldMesh() +{ +} + +VEManifoldMesh::VEManifoldMesh(VCreator vCreator, ECreator eCreator) + : + mVCreator(vCreator ? vCreator : CreateVertex), + mECreator(eCreator ? eCreator : CreateEdge), + mAssertOnNonmanifoldInsertion(true) +{ +} + +VEManifoldMesh::VMap const& VEManifoldMesh::GetVertices() const +{ + return mVMap; +} + +VEManifoldMesh::EMap const& VEManifoldMesh::GetEdges() const +{ + return mEMap; +} + +std::shared_ptr VEManifoldMesh::CreateVertex(int v) +{ + return std::make_shared(v); +} + +std::shared_ptr VEManifoldMesh::CreateEdge(int v0, int v1) +{ + return std::make_shared(v0, v1); +} + +void VEManifoldMesh::AssertOnNonmanifoldInsertion(bool doAssert) +{ + mAssertOnNonmanifoldInsertion = doAssert; +} + +std::shared_ptr VEManifoldMesh::Insert(int v0, int v1) +{ + std::pair ekey(v0, v1); + if (mEMap.find(ekey) != mEMap.end()) + { + // The edge already exists. Return a null pointer as a signal to + // the caller that the insertion failed. + return nullptr; + } + + // Add the new edge. + std::shared_ptr edge = mECreator(v0, v1); + mEMap[ekey] = edge; + + // Add the vertices if they do not already exist. + for (int i = 0; i < 2; ++i) + { + int v = edge->V[i]; + std::shared_ptr vertex; + auto viter = mVMap.find(v); + if (viter == mVMap.end()) + { + // This is the first time the vertex is encountered. + vertex = mVCreator(v); + mVMap[v] = vertex; + + // Update the vertex. + vertex->E[0] = edge; + } + else + { + // This is the second time the vertex is encountered. + vertex = viter->second; + if (!vertex) + { + LogError("Unexpected condition."); + return nullptr; + } + + // Update the vertex. + if (vertex->E[1].lock()) + { + if (mAssertOnNonmanifoldInsertion) + { + LogInformation("The mesh must be manifold."); + } + return nullptr; + } + vertex->E[1] = edge; + + // Update the adjacent edge. + auto adjacent = vertex->E[0].lock(); + if (!adjacent) + { + LogError("Unexpected condition."); + return nullptr; + } + for (int j = 0; j < 2; ++j) + { + if (adjacent->V[j] == v) + { + adjacent->E[j] = edge; + break; + } + } + + // Update the edge. + edge->E[i] = adjacent; + } + } + + return edge; +} + +bool VEManifoldMesh::Remove(int v0, int v1) +{ + std::pair ekey(v0, v1); + auto eiter = mEMap.find(ekey); + if (eiter == mEMap.end()) + { + // The edge does not exist. + return false; + } + + // Get the edge. + std::shared_ptr edge = eiter->second; + + // Remove the vertices if necessary (when they are not shared). + for (int i = 0; i < 2; ++i) + { + // Inform the vertices the edge is being deleted. + auto viter = mVMap.find(edge->V[i]); + if (viter == mVMap.end()) + { + // The edge vertices should be in the map. + LogError("Unexpected condition."); + return false; + } + + std::shared_ptr vertex = viter->second; + if (!vertex) + { + // Any in the map must have a dynamically allocated + // Vertex object. + LogError("Unexpected condition."); + return false; + } + if (vertex->E[0].lock() == edge) + { + // One-edge vertices always have pointer at index zero. + vertex->E[0] = vertex->E[1]; + vertex->E[1].reset(); + } + else if (vertex->E[1].lock() == edge) + { + vertex->E[1].reset(); + } + else + { + LogError("Unexpected condition."); + return false; + } + + // Remove the vertex if you have the last reference to it. + if (!vertex->E[0].lock() && !vertex->E[1].lock()) + { + mVMap.erase(vertex->V); + } + + // Inform adjacent edges the edge is being deleted. + auto adjacent = edge->E[i].lock(); + if (adjacent) + { + for (int j = 0; j < 2; ++j) + { + if (adjacent->E[j].lock() == edge) + { + adjacent->E[j].reset(); + break; + } + } + } + } + + mEMap.erase(ekey); + return true; +} + +bool VEManifoldMesh::IsClosed() const +{ + for (auto const& element : mVMap) + { + auto vertex = element.second; + if (!vertex->E[0].lock() || !vertex->E[1].lock()) + { + return false; + } + } + return true; +} + +VEManifoldMesh::Vertex::~Vertex() +{ +} + +VEManifoldMesh::Vertex::Vertex(int v) +{ + V = v; +} + +VEManifoldMesh::Edge::~Edge() +{ +} + +VEManifoldMesh::Edge::Edge(int v0, int v1) +{ + V[0] = v0; + V[1] = v1; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVEManifoldMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVEManifoldMesh.h new file mode 100644 index 000000000000..7e969660feb0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVEManifoldMesh.h @@ -0,0 +1,99 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +class GTE_IMPEXP VEManifoldMesh +{ +public: + // Vertex data types. + class Vertex; + typedef std::shared_ptr (*VCreator)(int); + typedef std::map> VMap; + + // Edge data types. + class Edge; + typedef std::shared_ptr (*ECreator)(int, int); + typedef std::map, std::shared_ptr> EMap; + + // Vertex object. + class GTE_IMPEXP Vertex + { + public: + virtual ~Vertex(); + Vertex(int v); + + // The unique vertex index. + int V; + + // The edges (if any) sharing the vertex. + std::weak_ptr E[2]; + }; + + // Edge object. + class GTE_IMPEXP Edge + { + public: + virtual ~Edge(); + Edge(int v0, int v1); + + // Vertices, listed as a directed edge . + int V[2]; + + // Adjacent edges. E[i] points to edge sharing V[i]. + std::weak_ptr E[2]; + }; + + + // Construction and destruction. + virtual ~VEManifoldMesh(); + VEManifoldMesh(VCreator vCreator = nullptr, ECreator eCreator = nullptr); + + // Member access. + VMap const& GetVertices() const; + EMap const& GetEdges() const; + + // If the insertion of an edge fails because the mesh would become + // nonmanifold, the default behavior is to trigger a LogError message. + // You can disable this behavior in situations where you want the Logger + // system on but you want to continue gracefully without an assertion. + void AssertOnNonmanifoldInsertion(bool doAssert); + + // If is not in the mesh, an Edge object is created and returned; + // otherwise, is in the mesh and nullptr is returned. If the + // insertion leads to a nonmanifold mesh, the call fails with a nullptr + // returned. + std::shared_ptr Insert(int v0, int v1); + + // If is in the mesh, it is removed and 'true' is returned; + // otherwise, is not in the mesh and 'false' is returned. + bool Remove(int v0, int v1); + + // A manifold mesh is closed if each vertex is shared twice. + bool IsClosed() const; + +protected: + // The vertex data and default vertex creation. + static std::shared_ptr CreateVertex(int v0); + VCreator mVCreator; + VMap mVMap; + + // The edge data and default edge creation. + static std::shared_ptr CreateEdge(int v0, int v1); + ECreator mECreator; + EMap mEMap; + bool mAssertOnNonmanifoldInsertion; // default: true +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETManifoldMesh.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETManifoldMesh.cpp new file mode 100644 index 000000000000..29285c18d4e1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETManifoldMesh.cpp @@ -0,0 +1,175 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +#include +using namespace gte; + +VETManifoldMesh::~VETManifoldMesh() +{ +} + +VETManifoldMesh::VETManifoldMesh(VCreator vCreator, ECreator eCreator, TCreator tCreator) + : + ETManifoldMesh(eCreator, tCreator), + mVCreator(vCreator ? vCreator : CreateVertex) +{ +} + +VETManifoldMesh::VETManifoldMesh(VETManifoldMesh const& mesh) +{ + *this = mesh; +} + +VETManifoldMesh& VETManifoldMesh::operator=(VETManifoldMesh const& mesh) +{ + Clear(); + mVCreator = mesh.mVCreator; + ETManifoldMesh::operator=(mesh); + return *this; +} + +VETManifoldMesh::VMap const& VETManifoldMesh::GetVertices() const +{ + return mVMap; +} + +std::shared_ptr VETManifoldMesh::Insert(int v0, int v1, int v2) +{ + std::shared_ptr tri = ETManifoldMesh::Insert(v0, v1, v2); + if (!tri) + { + return nullptr; + } + + for (int i = 0; i < 3; ++i) + { + int vIndex = tri->V[i]; + auto vItem = mVMap.find(vIndex); + std::shared_ptr vertex; + if (vItem == mVMap.end()) + { + vertex = mVCreator(vIndex); + mVMap[vIndex] = vertex; + } + else + { + vertex = vItem->second; + } + + vertex->TAdjacent.insert(tri); + + for (int j = 0; j < 3; ++j) + { + auto edge = tri->E[j].lock(); + if (edge) + { + if (edge->V[0] == vIndex) + { + vertex->VAdjacent.insert(edge->V[1]); + vertex->EAdjacent.insert(edge); + } + else if (edge->V[1] == vIndex) + { + vertex->VAdjacent.insert(edge->V[0]); + vertex->EAdjacent.insert(edge); + } + } + else + { + LogError("Malformed mesh: Triangle edges must not be null."); + return nullptr; + } + } + } + + return tri; +} + +bool VETManifoldMesh::Remove(int v0, int v1, int v2) +{ + auto tItem = mTMap.find(TriangleKey(v0, v1, v2)); + if (tItem == mTMap.end()) + { + return false; + } + + std::shared_ptr tri = tItem->second; + for (int i = 0; i < 3; ++i) + { + int vIndex = tri->V[i]; + auto vItem = mVMap.find(vIndex); + if (vItem != mVMap.end()) + { + std::shared_ptr vertex = vItem->second; + for (int j = 0; j < 3; ++j) + { + auto edge = tri->E[j].lock(); + if (edge) + { + if (edge->T[0].lock() && !edge->T[1].lock()) + { + if (edge->V[0] == vIndex) + { + vertex->VAdjacent.erase(edge->V[1]); + vertex->EAdjacent.erase(edge); + } + else if (edge->V[1] == vIndex) + { + vertex->VAdjacent.erase(edge->V[0]); + vertex->EAdjacent.erase(edge); + } + } + } + else + { + LogError("Malformed mesh: Triangle edges must not be null."); + return false; + } + } + + vertex->TAdjacent.erase(tri); + + if (vertex->TAdjacent.size() == 0) + { + LogAssert(vertex->VAdjacent.size() == 0 && vertex->EAdjacent.size() == 0, + "Malformed mesh: Inconsistent vertex adjacency information."); + + mVMap.erase(vItem); + } + } + else + { + LogError("Malformed mesh: Vertex must exist in the mesh."); + return false; + } + } + + return ETManifoldMesh::Remove(v0, v1, v2); +} + +void VETManifoldMesh::Clear() +{ + mVMap.clear(); + ETManifoldMesh::Clear(); +} + +std::shared_ptr VETManifoldMesh::CreateVertex(int vIndex) +{ + return std::make_shared(vIndex); +} + +VETManifoldMesh::Vertex::~Vertex() +{ +} + +VETManifoldMesh::Vertex::Vertex(int vIndex) + : + V(vIndex) +{ +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETManifoldMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETManifoldMesh.h new file mode 100644 index 000000000000..9a997ceb582b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETManifoldMesh.h @@ -0,0 +1,79 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The VETManifoldMesh class represents an edge-triangle manifold mesh +// but additionally stores vertex adjacency information. + +namespace gte +{ + +class GTE_IMPEXP VETManifoldMesh : public ETManifoldMesh +{ +public: + // Vertex data types. + class Vertex; + typedef std::shared_ptr (*VCreator)(int); + typedef std::map> VMap; + + // Vertex object. + class GTE_IMPEXP Vertex + { + public: + virtual ~Vertex(); + Vertex(int vIndex); + + // The index into the vertex pool of the mesh. + int V; + + // Adjacent objects. + std::set VAdjacent; + std::set> EAdjacent; + std::set> TAdjacent; + }; + + + // Construction and destruction. + virtual ~VETManifoldMesh(); + VETManifoldMesh(VCreator vCreator = nullptr, ECreator eCreator = nullptr, TCreator tCreator = nullptr); + + // Support for a deep copy of the mesh. The mVMap, mEMap, and mTMap + // objects have dynamically allocated memory for vertices, edges, and + // triangles. A shallow copy of the pointers to this memory is + // problematic. Allowing sharing, say, via std::shared_ptr, is an + // option but not really the intent of copying the mesh graph. + VETManifoldMesh(VETManifoldMesh const& mesh); + VETManifoldMesh& operator=(VETManifoldMesh const& mesh); + + // Member access. + VMap const& GetVertices() const; + + // If is not in the mesh, a Triangle object is created and + // returned; otherwise, is in the mesh and nullptr is returned. + // If the insertion leads to a nonmanifold mesh, the call fails with a + // nullptr returned. + virtual std::shared_ptr Insert(int v0, int v1, int v2) override; + + // If is in the mesh, it is removed and 'true' is returned; + // otherwise, is not in the mesh and 'false' is returned. + virtual bool Remove(int v0, int v1, int v2) override; + + // Destroy the vertices, edges, and triangles to obtain an empty mesh. + virtual void Clear() override; + +protected: + // The vertex data and default vertex creation. + static std::shared_ptr CreateVertex(int vIndex); + VCreator mVCreator; + VMap mVMap; +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETNonmanifoldMesh.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETNonmanifoldMesh.cpp new file mode 100644 index 000000000000..9be4b4fc91c3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETNonmanifoldMesh.cpp @@ -0,0 +1,182 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2018 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.20.0 (2019/01/08) + +#include +#include +#include +using namespace gte; + +VETNonmanifoldMesh::~VETNonmanifoldMesh() +{ +} + +VETNonmanifoldMesh::VETNonmanifoldMesh(VCreator vCreator, ECreator eCreator, TCreator tCreator) + : + ETNonmanifoldMesh(eCreator, tCreator), + mVCreator(vCreator ? vCreator : CreateVertex) +{ +} + +VETNonmanifoldMesh::VETNonmanifoldMesh(VETNonmanifoldMesh const& mesh) +{ + *this = mesh; +} + +VETNonmanifoldMesh& VETNonmanifoldMesh::operator=(VETNonmanifoldMesh const& mesh) +{ + Clear(); + mVCreator = mesh.mVCreator; + ETNonmanifoldMesh::operator=(mesh); + return *this; +} + +VETNonmanifoldMesh::VMap const& VETNonmanifoldMesh::GetVertices() const +{ + return mVMap; +} + +std::shared_ptr VETNonmanifoldMesh::Insert(int v0, int v1, int v2) +{ + std::shared_ptr tri = ETNonmanifoldMesh::Insert(v0, v1, v2); + if (!tri) + { + return nullptr; + } + + for (int i = 0; i < 3; ++i) + { + int vIndex = tri->V[i]; + auto vItem = mVMap.find(vIndex); + std::shared_ptr vertex; + if (vItem == mVMap.end()) + { + vertex = mVCreator(vIndex); + mVMap[vIndex] = vertex; + } + else + { + vertex = vItem->second; + } + + vertex->TAdjacent.insert(tri); + + for (int j = 0; j < 3; ++j) + { + auto edge = tri->E[j].lock(); + if (!edge) + { + LogError("Unexpected condition."); + return nullptr; + } + + if (edge->V[0] == vIndex) + { + vertex->VAdjacent.insert(edge->V[1]); + vertex->EAdjacent.insert(edge); + } + else if (edge->V[1] == vIndex) + { + vertex->VAdjacent.insert(edge->V[0]); + vertex->EAdjacent.insert(edge); + } + } + } + + return tri; +} + +bool VETNonmanifoldMesh::Remove(int v0, int v1, int v2) +{ + auto tItem = mTMap.find(TriangleKey(v0, v1, v2)); + if (tItem == mTMap.end()) + { + return false; + } + + std::shared_ptr tri = tItem->second; + for (int i = 0; i < 3; ++i) + { + int vIndex = tri->V[i]; + auto vItem = mVMap.find(vIndex); + if (vItem == mVMap.end()) + { + LogError("Unexpected condition."); + return false; + } + + std::shared_ptr vertex = vItem->second; + for (int j = 0; j < 3; ++j) + { + auto edge = tri->E[j].lock(); + if (!edge) + { + LogError("Unexpected condition."); + return false; + } + + // If the edge will be removed by ETNonmanifoldMesh::Remove, + // remove the vertex references to it. + if (edge->T.size() == 1) + { + for (auto const& adjw : edge->T) + { + auto adj = adjw.lock(); + if (!adj) + { + LogError("Unexpected condition."); + return false; + } + + if (edge->V[0] == vIndex) + { + vertex->VAdjacent.erase(edge->V[1]); + vertex->EAdjacent.erase(edge); + } + else if (edge->V[1] == vIndex) + { + vertex->VAdjacent.erase(edge->V[0]); + vertex->EAdjacent.erase(edge); + } + } + } + } + + vertex->TAdjacent.erase(tri); + + // If the vertex is no longer shared by any triangle, remove it. + if (vertex->TAdjacent.size() == 0) + { + LogAssert(vertex->VAdjacent.size() == 0 && vertex->EAdjacent.size() == 0, + "Malformed mesh: Inconsistent vertex adjacency information."); + + mVMap.erase(vItem); + } + } + + return ETNonmanifoldMesh::Remove(v0, v1, v2); +} + +void VETNonmanifoldMesh::Clear() +{ + mVMap.clear(); + ETNonmanifoldMesh::Clear(); +} + +std::shared_ptr VETNonmanifoldMesh::CreateVertex(int vIndex) +{ + return std::make_shared(vIndex); +} + +VETNonmanifoldMesh::Vertex::~Vertex() +{ +} + +VETNonmanifoldMesh::Vertex::Vertex(int vIndex) + : + V(vIndex) +{ +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETNonmanifoldMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETNonmanifoldMesh.h new file mode 100644 index 000000000000..7174cbf729ab --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETNonmanifoldMesh.h @@ -0,0 +1,80 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2018 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.20.0 (2019/01/09) + +#pragma once + +#include +#include + +// The VETNonmanifoldMesh class represents an edge-triangle nonmanifold mesh +// but additionally stores vertex adjacency information. + +namespace gte +{ + class GTE_IMPEXP VETNonmanifoldMesh : public ETNonmanifoldMesh + { + public: + // Vertex data types. + class Vertex; + typedef std::shared_ptr(*VCreator)(int); + typedef std::map> VMap; + + // Vertex object. + class GTE_IMPEXP Vertex + { + public: + virtual ~Vertex(); + Vertex(int vIndex); + + // The index into the vertex pool of the mesh. + int V; + + bool operator<(Vertex const& other) const + { + return V < other.V; + } + + // Adjacent objects. + std::set VAdjacent; + std::set, SharedPtrLT> EAdjacent; + std::set, SharedPtrLT> TAdjacent; + }; + + + // Construction and destruction. + virtual ~VETNonmanifoldMesh(); + VETNonmanifoldMesh(VCreator vCreator = nullptr, ECreator eCreator = nullptr, TCreator tCreator = nullptr); + + // Support for a deep copy of the mesh. The mVMap, mEMap, and mTMap + // objects have dynamically allocated memory for vertices, edges, and + // triangles. A shallow copy of the pointers to this memory is + // problematic. Allowing sharing, say, via std::shared_ptr, is an + // option but not really the intent of copying the mesh graph. + VETNonmanifoldMesh(VETNonmanifoldMesh const& mesh); + VETNonmanifoldMesh& operator=(VETNonmanifoldMesh const& mesh); + + // Member access. + VMap const& GetVertices() const; + + // If is not in the mesh, a Triangle object is created and + // returned; otherwise, is in the mesh and nullptr is returned. + virtual std::shared_ptr Insert(int v0, int v1, int v2) override; + + // If is in the mesh, it is removed and 'true' is returned; + // otherwise, is not in the mesh and 'false' is returned. + virtual bool Remove(int v0, int v1, int v2) override; + + // Destroy the vertices, edges, and triangles to obtain an empty mesh. + virtual void Clear() override; + + protected: + // The vertex data and default vertex creation. + static std::shared_ptr CreateVertex(int vIndex); + VCreator mVCreator; + VMap mVMap; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector.h new file mode 100644 index 000000000000..022039ddafa6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector.h @@ -0,0 +1,692 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class Vector +{ +public: + // The tuple is uninitialized. + Vector(); + + // The tuple is fully initialized by the inputs. + Vector(std::array const& values); + + // At most N elements are copied from the initializer list, setting any + // remaining elements to zero. Create the zero vector using the syntax + // Vector zero{(Real)0}; + // WARNING: The C++ 11 specification states that + // Vector zero{}; + // will lead to a call of the default constructor, not the initializer + // constructor! + Vector(std::initializer_list values); + + // For 0 <= d < N, element d is 1 and all others are 0. If d is invalid, + // the zero vector is created. This is a convenience for creating the + // standard Euclidean basis vectors; see also MakeUnit(int) and Unit(int). + Vector(int d); + + // The copy constructor, destructor, and assignment operator are generated + // by the compiler. + + // Member access. The first operator[] returns a const reference rather + // than a Real value. This supports writing via standard file operations + // that require a const pointer to data. + inline int GetSize() const; // returns N + inline Real const& operator[](int i) const; + inline Real& operator[](int i); + + // Comparisons for sorted containers and geometric ordering. + inline bool operator==(Vector const& vec) const; + inline bool operator!=(Vector const& vec) const; + inline bool operator< (Vector const& vec) const; + inline bool operator<=(Vector const& vec) const; + inline bool operator> (Vector const& vec) const; + inline bool operator>=(Vector const& vec) const; + + // Special vectors. + void MakeZero(); // All components are 0. + void MakeUnit(int d); // Component d is 1, all others are zero. + static Vector Zero(); + static Vector Unit(int d); + +protected: + // This data structure takes advantage of the built-in operator[], + // range checking, and visualizers in MSVS. + std::array mTuple; +}; + +// Unary operations. +template +Vector operator+(Vector const& v); + +template +Vector operator-(Vector const& v); + +// Linear-algebraic operations. +template +Vector +operator+(Vector const& v0, Vector const& v1); + +template +Vector +operator-(Vector const& v0, Vector const& v1); + +template +Vector operator*(Vector const& v, Real scalar); + +template +Vector operator*(Real scalar, Vector const& v); + +template +Vector operator/(Vector const& v, Real scalar); + +template +Vector& operator+=(Vector& v0, Vector const& v1); + +template +Vector& operator-=(Vector& v0, Vector const& v1); + +template +Vector& operator*=(Vector& v, Real scalar); + +template +Vector& operator/=(Vector& v, Real scalar); + +// Componentwise algebraic operations. +template +Vector +operator*(Vector const& v0, Vector const& v1); + +template +Vector +operator/(Vector const& v0, Vector const& v1); + +template +Vector& operator*=(Vector& v0, Vector const& v1); + +template +Vector& operator/=(Vector& v0, Vector const& v1); + +// Geometric operations. The functions with 'robust' set to 'false' use the +// standard algorithm for normalizing a vector by computing the length as a +// square root of the squared length and dividing by it. The results can be +// infinite (or NaN) if the length is zero. When 'robust' is set to 'true', +// the algorithm is designed to avoid floating-point overflow and sets the +// normalized vector to zero when the length is zero. +template +Real Dot(Vector const& v0, Vector const& v1); + +template +Real Length(Vector const& v, bool robust = false); + +template +Real Normalize(Vector& v, bool robust = false); + +// Gram-Schmidt orthonormalization to generate orthonormal vectors from the +// linearly independent inputs. The function returns the smallest length of +// the unnormalized vectors computed during the process. If this value is +// nearly zero, it is possible that the inputs are linearly dependent (within +// numerical round-off errors). On input, 1 <= numElements <= N and v[0] +// through v[numElements-1] must be initialized. On output, the vectors +// v[0] through v[numElements-1] form an orthonormal set. +template +Real Orthonormalize(int numElements, Vector* v, bool robust = false); + +// Construct a single vector orthogonal to the nonzero input vector. If the +// maximum absolute component occurs at index i, then the orthogonal vector +// U has u[i] = v[i+1], u[i+1] = -v[i], and all other components zero. The +// index addition i+1 is computed modulo N. +template +Vector GetOrthogonal(Vector const& v, bool unitLength); + +// Compute the axis-aligned bounding box of the vectors. The return value is +// 'true' iff the inputs are valid, in which case vmin and vmax have valid +// values. +template +bool ComputeExtremes(int numVectors, Vector const* v, + Vector& vmin, Vector& vmax); + +// Lift n-tuple v to homogeneous (n+1)-tuple (v,last). +template +Vector HLift(Vector const& v, Real last); + +// Project homogeneous n-tuple v = (u,v[n-1]) to (n-1)-tuple u. +template +Vector HProject(Vector const& v); + +// Lift n-tuple v = (w0,w1) to (n+1)-tuple u = (w0,u[inject],w1). By +// inference, w0 is a (inject)-tuple [nonexistent when inject=0] and w1 is a +// (n-inject)-tuple [nonexistent when inject=n]. +template +Vector Lift(Vector const& v, int inject, Real value); + +// Project n-tuple v = (w0,v[reject],w1) to (n-1)-tuple u = (w0,w1). By +// inference, w0 is a (reject)-tuple [nonexistent when reject=0] and w1 is a +// (n-1-reject)-tuple [nonexistent when reject=n-1]. +template +Vector Project(Vector const& v, int reject); + + +template +Vector::Vector() +{ + // Uninitialized. +} + +template +Vector::Vector(std::array const& values) + : + mTuple(values) +{ +} +template +Vector::Vector(std::initializer_list values) +{ + int const numValues = static_cast(values.size()); + if (N == numValues) + { + std::copy(values.begin(), values.end(), mTuple.begin()); + } + else if (N > numValues) + { + std::copy(values.begin(), values.end(), mTuple.begin()); + std::fill(mTuple.begin() + numValues, mTuple.end(), (Real)0); + } + else // N < numValues + { + std::copy(values.begin(), values.begin() + N, mTuple.begin()); + } +} + +template +Vector::Vector(int d) +{ + MakeUnit(d); +} + +template inline +int Vector::GetSize() const +{ + return N; +} + +template inline +Real const& Vector::operator[](int i) const +{ + return mTuple[i]; +} + +template inline +Real& Vector::operator[](int i) +{ + return mTuple[i]; +} + +template inline +bool Vector::operator==(Vector const& vec) const +{ + return mTuple == vec.mTuple; +} + +template inline +bool Vector::operator!=(Vector const& vec) const +{ + return mTuple != vec.mTuple; +} + +template inline +bool Vector::operator<(Vector const& vec) const +{ + return mTuple < vec.mTuple; +} + +template inline +bool Vector::operator<=(Vector const& vec) const +{ + return mTuple <= vec.mTuple; +} + +template inline +bool Vector::operator>(Vector const& vec) const +{ + return mTuple > vec.mTuple; +} + +template inline +bool Vector::operator>=(Vector const& vec) const +{ + return mTuple >= vec.mTuple; +} + +template +void Vector::MakeZero() +{ + std::fill(mTuple.begin(), mTuple.end(), (Real)0); +} + +template +void Vector::MakeUnit(int d) +{ + std::fill(mTuple.begin(), mTuple.end(), (Real)0); + if (0 <= d && d < N) + { + mTuple[d] = (Real)1; + } +} + +template +Vector Vector::Zero() +{ + Vector v; + v.MakeZero(); + return v; +} + +template +Vector Vector::Unit(int d) +{ + Vector v; + v.MakeUnit(d); + return v; +} + + + +template +Vector operator+(Vector const& v) +{ + return v; +} + +template +Vector operator-(Vector const& v) +{ + Vector result; + for (int i = 0; i < N; ++i) + { + result[i] = -v[i]; + } + return result; +} + +template +Vector operator+(Vector const& v0, + Vector const& v1) +{ + Vector result = v0; + return result += v1; +} + +template +Vector operator-(Vector const& v0, + Vector const& v1) +{ + Vector result = v0; + return result -= v1; +} + +template +Vector operator*(Vector const& v, Real scalar) +{ + Vector result = v; + return result *= scalar; +} + +template +Vector operator*(Real scalar, Vector const& v) +{ + Vector result = v; + return result *= scalar; +} + +template +Vector operator/(Vector const& v, Real scalar) +{ + Vector result = v; + return result /= scalar; +} + +template +Vector& operator+=(Vector& v0, Vector const& v1) +{ + for (int i = 0; i < N; ++i) + { + v0[i] += v1[i]; + } + return v0; +} + +template +Vector& operator-=(Vector& v0, Vector const& v1) +{ + for (int i = 0; i < N; ++i) + { + v0[i] -= v1[i]; + } + return v0; +} + +template +Vector& operator*=(Vector& v, Real scalar) +{ + for (int i = 0; i < N; ++i) + { + v[i] *= scalar; + } + return v; +} + +template +Vector& operator/=(Vector& v, Real scalar) +{ + if (scalar != (Real)0) + { + Real invScalar = ((Real)1) / scalar; + for (int i = 0; i < N; ++i) + { + v[i] *= invScalar; + } + } + else + { + for (int i = 0; i < N; ++i) + { + v[i] = (Real)0; + } + } + return v; +} + +template +Vector operator*(Vector const& v0, + Vector const& v1) +{ + Vector result = v0; + return result *= v1; +} + +template +Vector operator/(Vector const& v0, + Vector const& v1) +{ + Vector result = v0; + return result /= v1; +} + +template +Vector& operator*=(Vector& v0, Vector const& v1) +{ + for (int i = 0; i < N; ++i) + { + v0[i] *= v1[i]; + } + return v0; +} + +template +Vector& operator/=(Vector& v0, Vector const& v1) +{ + for (int i = 0; i < N; ++i) + { + v0[i] /= v1[i]; + } + return v0; +} + +template +Real Dot(Vector const& v0, Vector const& v1) +{ + Real dot = v0[0] * v1[0]; + for (int i = 1; i < N; ++i) + { + dot += v0[i] * v1[i]; + } + return dot; +} + +template +Real Length(Vector const& v, bool robust) +{ + if (robust) + { + Real maxAbsComp = std::abs(v[0]); + for (int i = 1; i < N; ++i) + { + Real absComp = std::abs(v[i]); + if (absComp > maxAbsComp) + { + maxAbsComp = absComp; + } + } + + Real length; + if (maxAbsComp > (Real)0) + { + Vector scaled = v / maxAbsComp; + length = maxAbsComp * std::sqrt(Dot(scaled, scaled)); + } + else + { + length = (Real)0; + } + return length; + } + else + { + return std::sqrt(Dot(v, v)); + } +} + +template +Real Normalize(Vector& v, bool robust) +{ + if (robust) + { + Real maxAbsComp = std::abs(v[0]); + for (int i = 1; i < N; ++i) + { + Real absComp = std::abs(v[i]); + if (absComp > maxAbsComp) + { + maxAbsComp = absComp; + } + } + + Real length; + if (maxAbsComp > (Real)0) + { + v /= maxAbsComp; + length = std::sqrt(Dot(v, v)); + v /= length; + length *= maxAbsComp; + } + else + { + length = (Real)0; + for (int i = 0; i < N; ++i) + { + v[i] = (Real)0; + } + } + return length; + } + else + { + Real length = std::sqrt(Dot(v, v)); + if (length > (Real)0) + { + v /= length; + } + else + { + for (int i = 0; i < N; ++i) + { + v[i] = (Real)0; + } + } + return length; + } +} + +template +Real Orthonormalize(int numInputs, Vector* v, bool robust) +{ + if (v && 1 <= numInputs && numInputs <= N) + { + Real minLength = Normalize(v[0], robust); + for (int i = 1; i < numInputs; ++i) + { + for (int j = 0; j < i; ++j) + { + Real dot = Dot(v[i], v[j]); + v[i] -= v[j] * dot; + } + Real length = Normalize(v[i], robust); + if (length < minLength) + { + minLength = length; + } + } + return minLength; + } + + return (Real)0; +} + +template +Vector GetOrthogonal(Vector const& v, bool unitLength) +{ + Real cmax = std::abs(v[0]); + int imax = 0; + for (int i = 1; i < N; ++i) + { + Real c = std::abs(v[i]); + if (c > cmax) + { + cmax = c; + imax = i; + } + } + + Vector result; + result.MakeZero(); + int inext = imax + 1; + if (inext == N) + { + inext = 0; + } + result[imax] = v[inext]; + result[inext] = -v[imax]; + if (unitLength) + { + Real sqrDistance = result[imax] * result[imax] + result[inext] * result[inext]; + Real invLength = ((Real)1) / std::sqrt(sqrDistance); + result[imax] *= invLength; + result[inext] *= invLength; + } + return result; +} + +template +bool ComputeExtremes(int numVectors, Vector const* v, + Vector& vmin, Vector& vmax) +{ + if (v && numVectors > 0) + { + vmin = v[0]; + vmax = vmin; + for (int j = 1; j < numVectors; ++j) + { + Vector const& vec = v[j]; + for (int i = 0; i < N; ++i) + { + if (vec[i] < vmin[i]) + { + vmin[i] = vec[i]; + } + else if (vec[i] > vmax[i]) + { + vmax[i] = vec[i]; + } + } + } + return true; + } + + return false; +} + +template +Vector HLift(Vector const& v, Real last) +{ + Vector result; + for (int i = 0; i < N; ++i) + { + result[i] = v[i]; + } + result[N] = last; + return result; +} + +template +Vector HProject(Vector const& v) +{ + static_assert(N >= 2, "Invalid dimension."); + Vector result; + for (int i = 0; i < N - 1; ++i) + { + result[i] = v[i]; + } + return result; +} + +template +Vector Lift(Vector const& v, int inject, Real value) +{ + Vector result; + int i; + for (i = 0; i < inject; ++i) + { + result[i] = v[i]; + } + result[i] = value; + int j = i; + for (++j; i < N; ++i, ++j) + { + result[j] = v[i]; + } + return result; +} + +template +Vector Project(Vector const& v, int reject) +{ + static_assert(N >= 2, "Invalid dimension."); + Vector result; + for (int i = 0, j = 0; i < N - 1; ++i, ++j) + { + if (j == reject) + { + ++j; + } + result[i] = v[j]; + } + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector2.h new file mode 100644 index 000000000000..407142aaf39e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector2.h @@ -0,0 +1,276 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/04) + +#pragma once + +#include + +namespace gte +{ + +// Template alias for convenience. +template +using Vector2 = Vector<2, Real>; + +// Compute the perpendicular using the formal determinant, +// perp = det{{e0,e1},{x0,x1}} = (x1,-x0) +// where e0 = (1,0), e1 = (0,1), and v = (x0,x1). +template +Vector2 Perp(Vector2 const& v); + +// Compute the normalized perpendicular. +template +Vector2 UnitPerp(Vector2 const& v, bool robust = false); + +// Compute Dot((x0,x1),Perp(y0,y1)) = x0*y1 - x1*y0, where v0 = (x0,x1) +// and v1 = (y0,y1). +template +Real DotPerp(Vector2 const& v0, Vector2 const& v1); + +// Compute a right-handed orthonormal basis for the orthogonal complement +// of the input vectors. The function returns the smallest length of the +// unnormalized vectors computed during the process. If this value is nearly +// zero, it is possible that the inputs are linearly dependent (within +// numerical round-off errors). On input, numInputs must be 1 and v[0] +// must be initialized. On output, the vectors v[0] and v[1] form an +// orthonormal set. +template +Real ComputeOrthogonalComplement(int numInputs, Vector2* v, bool robust = false); + +// Compute the barycentric coordinates of the point P with respect to the +// triangle , P = b0*V0 + b1*V1 + b2*V2, where b0 + b1 + b2 = 1. +// The return value is 'true' iff {V0,V1,V2} is a linearly independent set. +// Numerically, this is measured by |det[V0 V1 V2]| <= epsilon. The values +// bary[] are valid only when the return value is 'true' but set to zero when +// the return value is 'false'. +template +bool ComputeBarycentrics(Vector2 const& p, Vector2 const& v0, + Vector2 const& v1, Vector2 const& v2, Real bary[3], + Real epsilon = (Real)0); + +// Get intrinsic information about the input array of vectors. The return +// value is 'true' iff the inputs are valid (numVectors > 0, v is not null, +// and epsilon >= 0), in which case the class members are valid. +template +class IntrinsicsVector2 +{ +public: + // The constructor sets the class members based on the input set. + IntrinsicsVector2(int numVectors, Vector2 const* v, Real inEpsilon); + + // A nonnegative tolerance that is used to determine the intrinsic + // dimension of the set. + Real epsilon; + + // The intrinsic dimension of the input set, computed based on the + // nonnegative tolerance mEpsilon. + int dimension; + + // Axis-aligned bounding box of the input set. The maximum range is + // the larger of max[0]-min[0] and max[1]-min[1]. + Real min[2], max[2]; + Real maxRange; + + // Coordinate system. The origin is valid for any dimension d. The + // unit-length direction vector is valid only for 0 <= i < d. The + // extreme index is relative to the array of input points, and is also + // valid only for 0 <= i < d. If d = 0, all points are effectively + // the same, but the use of an epsilon may lead to an extreme index + // that is not zero. If d = 1, all points effectively lie on a line + // segment. If d = 2, the points are not collinear. + Vector2 origin; + Vector2 direction[2]; + + // The indices that define the maximum dimensional extents. The + // values extreme[0] and extreme[1] are the indices for the points + // that define the largest extent in one of the coordinate axis + // directions. If the dimension is 2, then extreme[2] is the index + // for the point that generates the largest extent in the direction + // perpendicular to the line through the points corresponding to + // extreme[0] and extreme[1]. The triangle formed by the points + // V[extreme[0]], V[extreme[1]], and V[extreme[2]] is clockwise or + // counterclockwise, the condition stored in extremeCCW. + int extreme[3]; + bool extremeCCW; +}; + + +template +Vector2 Perp(Vector2 const& v) +{ + return Vector2{ v[1], -v[0] }; +} + +template +Vector2 UnitPerp(Vector2 const& v, bool robust) +{ + Vector2 unitPerp{ v[1], -v[0] }; + Normalize(unitPerp, robust); + return unitPerp; +} + +template +Real DotPerp(Vector2 const& v0, Vector2 const& v1) +{ + return Dot(v0, Perp(v1)); +} + +template +Real ComputeOrthogonalComplement(int numInputs, Vector2* v, bool robust) +{ + if (numInputs == 1) + { + v[1] = -Perp(v[0]); + return Orthonormalize<2, Real>(2, v, robust); + } + + return (Real)0; +} + +template +bool ComputeBarycentrics(Vector2 const& p, Vector2 const& v0, + Vector2 const& v1, Vector2 const& v2, Real bary[3], + Real epsilon) +{ + // Compute the vectors relative to V2 of the triangle. + Vector2 diff[3] = { v0 - v2, v1 - v2, p - v2 }; + + Real det = DotPerp(diff[0], diff[1]); + if (det < -epsilon || det > epsilon) + { + Real invDet = ((Real)1) / det; + bary[0] = DotPerp(diff[2], diff[1])*invDet; + bary[1] = DotPerp(diff[0], diff[2])*invDet; + bary[2] = (Real)1 - bary[0] - bary[1]; + return true; + } + + for (int i = 0; i < 3; ++i) + { + bary[i] = (Real)0; + } + return false; +} + +template +IntrinsicsVector2::IntrinsicsVector2(int numVectors, + Vector2 const* v, Real inEpsilon) + : + epsilon(inEpsilon), + dimension(0), + maxRange((Real)0), + origin({ (Real)0, (Real)0 }), + extremeCCW(false) +{ + min[0] = (Real)0; + min[1] = (Real)0; + direction[0] = { (Real)0, (Real)0 }; + direction[1] = { (Real)0, (Real)0 }; + extreme[0] = 0; + extreme[1] = 0; + extreme[2] = 0; + + if (numVectors > 0 && v && epsilon >= (Real)0) + { + // Compute the axis-aligned bounding box for the input vectors. Keep + // track of the indices into 'vectors' for the current min and max. + int j, indexMin[2], indexMax[2]; + for (j = 0; j < 2; ++j) + { + min[j] = v[0][j]; + max[j] = min[j]; + indexMin[j] = 0; + indexMax[j] = 0; + } + + int i; + for (i = 1; i < numVectors; ++i) + { + for (j = 0; j < 2; ++j) + { + if (v[i][j] < min[j]) + { + min[j] = v[i][j]; + indexMin[j] = i; + } + else if (v[i][j] > max[j]) + { + max[j] = v[i][j]; + indexMax[j] = i; + } + } + } + + // Determine the maximum range for the bounding box. + maxRange = max[0] - min[0]; + extreme[0] = indexMin[0]; + extreme[1] = indexMax[0]; + Real range = max[1] - min[1]; + if (range > maxRange) + { + maxRange = range; + extreme[0] = indexMin[1]; + extreme[1] = indexMax[1]; + } + + // The origin is either the vector of minimum x0-value or vector of + // minimum x1-value. + origin = v[extreme[0]]; + + // Test whether the vector set is (nearly) a vector. + if (maxRange <= epsilon) + { + dimension = 0; + for (j = 0; j < 2; ++j) + { + extreme[j + 1] = extreme[0]; + } + return; + } + + // Test whether the vector set is (nearly) a line segment. We need + // direction[1] to span the orthogonal complement of direction[0]. + direction[0] = v[extreme[1]] - origin; + Normalize(direction[0], false); + direction[1] = -Perp(direction[0]); + + // Compute the maximum distance of the points from the line + // origin+t*direction[0]. + Real maxDistance = (Real)0; + Real maxSign = (Real)0; + extreme[2] = extreme[0]; + for (i = 0; i < numVectors; ++i) + { + Vector2 diff = v[i] - origin; + Real distance = Dot(direction[1], diff); + Real sign = (distance >(Real)0 ? (Real)1 : + (distance < (Real)0 ? (Real)-1 : (Real)0)); + distance = std::abs(distance); + if (distance > maxDistance) + { + maxDistance = distance; + maxSign = sign; + extreme[2] = i; + } + } + + if (maxDistance <= epsilon * maxRange) + { + // The points are (nearly) on the line origin+t*direction[0]. + dimension = 1; + extreme[2] = extreme[1]; + return; + } + + dimension = 2; + extremeCCW = (maxSign > (Real)0); + return; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector3.h new file mode 100644 index 000000000000..614c60e8fc2d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector3.h @@ -0,0 +1,382 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/04) + +#pragma once + +#include + +namespace gte +{ + +// Template alias for convenience. +template +using Vector3 = Vector<3, Real>; + +// Cross, UnitCross, and DotCross have a template parameter N that should +// be 3 or 4. The latter case supports affine vectors in 4D (last component +// w = 0) when you want to use 4-tuples and 4x4 matrices for affine algebra. + +// Compute the cross product using the formal determinant: +// cross = det{{e0,e1,e2},{x0,x1,x2},{y0,y1,y2}} +// = (x1*y2-x2*y1, x2*y0-x0*y2, x0*y1-x1*y0) +// where e0 = (1,0,0), e1 = (0,1,0), e2 = (0,0,1), v0 = (x0,x1,x2), and +// v1 = (y0,y1,y2). +template +Vector Cross(Vector const& v0, Vector const& v1); + +// Compute the normalized cross product. +template +Vector UnitCross(Vector const& v0, Vector const& v1, + bool robust = false); + +// Compute Dot((x0,x1,x2),Cross((y0,y1,y2),(z0,z1,z2)), the triple scalar +// product of three vectors, where v0 = (x0,x1,x2), v1 = (y0,y1,y2), and +// v2 is (z0,z1,z2). +template +Real DotCross(Vector const& v0, Vector const& v1, + Vector const& v2); + +// Compute a right-handed orthonormal basis for the orthogonal complement +// of the input vectors. The function returns the smallest length of the +// unnormalized vectors computed during the process. If this value is nearly +// zero, it is possible that the inputs are linearly dependent (within +// numerical round-off errors). On input, numInputs must be 1 or 2 and +// v[0] through v[numInputs-1] must be initialized. On output, the +// vectors v[0] through v[2] form an orthonormal set. +template +Real ComputeOrthogonalComplement(int numInputs, Vector3* v, bool robust = false); + +// Compute the barycentric coordinates of the point P with respect to the +// tetrahedron , P = b0*V0 + b1*V1 + b2*V2 + b3*V3, where +// b0 + b1 + b2 + b3 = 1. The return value is 'true' iff {V0,V1,V2,V3} is +// a linearly independent set. Numerically, this is measured by +// |det[V0 V1 V2 V3]| <= epsilon. The values bary[] are valid only when the +// return value is 'true' but set to zero when the return value is 'false'. +template +bool ComputeBarycentrics(Vector3 const& p, Vector3 const& v0, + Vector3 const& v1, Vector3 const& v2, Vector3 const& v3, + Real bary[4], Real epsilon = (Real)0); + +// Get intrinsic information about the input array of vectors. The return +// value is 'true' iff the inputs are valid (numVectors > 0, v is not null, +// and epsilon >= 0), in which case the class members are valid. +template +class IntrinsicsVector3 +{ +public: + // The constructor sets the class members based on the input set. + IntrinsicsVector3(int numVectors, Vector3 const* v, Real inEpsilon); + + // A nonnegative tolerance that is used to determine the intrinsic + // dimension of the set. + Real epsilon; + + // The intrinsic dimension of the input set, computed based on the + // nonnegative tolerance mEpsilon. + int dimension; + + // Axis-aligned bounding box of the input set. The maximum range is + // the larger of max[0]-min[0], max[1]-min[1], and max[2]-min[2]. + Real min[3], max[3]; + Real maxRange; + + // Coordinate system. The origin is valid for any dimension d. The + // unit-length direction vector is valid only for 0 <= i < d. The + // extreme index is relative to the array of input points, and is also + // valid only for 0 <= i < d. If d = 0, all points are effectively + // the same, but the use of an epsilon may lead to an extreme index + // that is not zero. If d = 1, all points effectively lie on a line + // segment. If d = 2, all points effectively line on a plane. If + // d = 3, the points are not coplanar. + Vector3 origin; + Vector3 direction[3]; + + // The indices that define the maximum dimensional extents. The + // values extreme[0] and extreme[1] are the indices for the points + // that define the largest extent in one of the coordinate axis + // directions. If the dimension is 2, then extreme[2] is the index + // for the point that generates the largest extent in the direction + // perpendicular to the line through the points corresponding to + // extreme[0] and extreme[1]. Furthermore, if the dimension is 3, + // then extreme[3] is the index for the point that generates the + // largest extent in the direction perpendicular to the triangle + // defined by the other extreme points. The tetrahedron formed by the + // points V[extreme[0]], V[extreme[1]], V[extreme[2]], and + // V[extreme[3]] is clockwise or counterclockwise, the condition + // stored in extremeCCW. + int extreme[4]; + bool extremeCCW; +}; + + +template +Vector Cross(Vector const& v0, Vector const& v1) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Vector result; + result.MakeZero(); + result[0] = v0[1] * v1[2] - v0[2] * v1[1]; + result[1] = v0[2] * v1[0] - v0[0] * v1[2]; + result[2] = v0[0] * v1[1] - v0[1] * v1[0]; + return result; +} + +template +Vector UnitCross(Vector const& v0, Vector const& v1, bool robust) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Vector unitCross = Cross(v0, v1); + Normalize(unitCross, robust); + return unitCross; +} + +template +Real DotCross(Vector const& v0, Vector const& v1, + Vector const& v2) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + return Dot(v0, Cross(v1, v2)); +} + +template +Real ComputeOrthogonalComplement(int numInputs, Vector3* v, bool robust) +{ + if (numInputs == 1) + { + if (std::abs(v[0][0]) > std::abs(v[0][1])) + { + v[1] = { -v[0][2], (Real)0, +v[0][0] }; + } + else + { + v[1] = { (Real)0, +v[0][2], -v[0][1] }; + } + numInputs = 2; + } + + if (numInputs == 2) + { + v[2] = Cross(v[0], v[1]); + return Orthonormalize<3, Real>(3, v, robust); + } + + return (Real)0; +} + +template +bool ComputeBarycentrics(Vector3 const& p, Vector3 const& v0, + Vector3 const& v1, Vector3 const& v2, Vector3 const& v3, + Real bary[4], Real epsilon) +{ + // Compute the vectors relative to V3 of the tetrahedron. + Vector3 diff[4] = { v0 - v3, v1 - v3, v2 - v3, p - v3 }; + + Real det = DotCross(diff[0], diff[1], diff[2]); + if (det < -epsilon || det > epsilon) + { + Real invDet = ((Real)1) / det; + bary[0] = DotCross(diff[3], diff[1], diff[2]) * invDet; + bary[1] = DotCross(diff[3], diff[2], diff[0]) * invDet; + bary[2] = DotCross(diff[3], diff[0], diff[1]) * invDet; + bary[3] = (Real)1 - bary[0] - bary[1] - bary[2]; + return true; + } + + for (int i = 0; i < 4; ++i) + { + bary[i] = (Real)0; + } + return false; +} + +template +IntrinsicsVector3::IntrinsicsVector3(int numVectors, + Vector3 const* v, Real inEpsilon) + : + epsilon(inEpsilon), + dimension(0), + maxRange((Real)0), + origin({ (Real)0, (Real)0, (Real)0 }), + extremeCCW(false) +{ + min[0] = (Real)0; + min[1] = (Real)0; + min[2] = (Real)0; + direction[0] = { (Real)0, (Real)0, (Real)0 }; + direction[1] = { (Real)0, (Real)0, (Real)0 }; + direction[2] = { (Real)0, (Real)0, (Real)0 }; + extreme[0] = 0; + extreme[1] = 0; + extreme[2] = 0; + extreme[3] = 0; + + if (numVectors > 0 && v && epsilon >= (Real)0) + { + // Compute the axis-aligned bounding box for the input vectors. Keep + // track of the indices into 'vectors' for the current min and max. + int j, indexMin[3], indexMax[3]; + for (j = 0; j < 3; ++j) + { + min[j] = v[0][j]; + max[j] = min[j]; + indexMin[j] = 0; + indexMax[j] = 0; + } + + int i; + for (i = 1; i < numVectors; ++i) + { + for (j = 0; j < 3; ++j) + { + if (v[i][j] < min[j]) + { + min[j] = v[i][j]; + indexMin[j] = i; + } + else if (v[i][j] > max[j]) + { + max[j] = v[i][j]; + indexMax[j] = i; + } + } + } + + // Determine the maximum range for the bounding box. + maxRange = max[0] - min[0]; + extreme[0] = indexMin[0]; + extreme[1] = indexMax[0]; + Real range = max[1] - min[1]; + if (range > maxRange) + { + maxRange = range; + extreme[0] = indexMin[1]; + extreme[1] = indexMax[1]; + } + range = max[2] - min[2]; + if (range > maxRange) + { + maxRange = range; + extreme[0] = indexMin[2]; + extreme[1] = indexMax[2]; + } + + // The origin is either the vector of minimum x0-value, vector of + // minimum x1-value, or vector of minimum x2-value. + origin = v[extreme[0]]; + + // Test whether the vector set is (nearly) a vector. + if (maxRange <= epsilon) + { + dimension = 0; + for (j = 0; j < 3; ++j) + { + extreme[j + 1] = extreme[0]; + } + return; + } + + // Test whether the vector set is (nearly) a line segment. We need + // {direction[2],direction[3]} to span the orthogonal complement of + // direction[0]. + direction[0] = v[extreme[1]] - origin; + Normalize(direction[0], false); + if (std::abs(direction[0][0]) > std::abs(direction[0][1])) + { + direction[1][0] = -direction[0][2]; + direction[1][1] = (Real)0; + direction[1][2] = +direction[0][0]; + } + else + { + direction[1][0] = (Real)0; + direction[1][1] = +direction[0][2]; + direction[1][2] = -direction[0][1]; + } + Normalize(direction[1], false); + direction[2] = Cross(direction[0], direction[1]); + + // Compute the maximum distance of the points from the line + // origin+t*direction[0]. + Real maxDistance = (Real)0; + Real distance, dot; + extreme[2] = extreme[0]; + for (i = 0; i < numVectors; ++i) + { + Vector3 diff = v[i] - origin; + dot = Dot(direction[0], diff); + Vector3 proj = diff - dot * direction[0]; + distance = Length(proj, false); + if (distance > maxDistance) + { + maxDistance = distance; + extreme[2] = i; + } + } + + if (maxDistance <= epsilon*maxRange) + { + // The points are (nearly) on the line origin+t*direction[0]. + dimension = 1; + extreme[2] = extreme[1]; + extreme[3] = extreme[1]; + return; + } + + // Test whether the vector set is (nearly) a planar polygon. The + // point v[extreme[2]] is farthest from the line: origin + + // t*direction[0]. The vector v[extreme[2]]-origin is not + // necessarily perpendicular to direction[0], so project out the + // direction[0] component so that the result is perpendicular to + // direction[0]. + direction[1] = v[extreme[2]] - origin; + dot = Dot(direction[0], direction[1]); + direction[1] -= dot * direction[0]; + Normalize(direction[1], false); + + // We need direction[2] to span the orthogonal complement of + // {direction[0],direction[1]}. + direction[2] = Cross(direction[0], direction[1]); + + // Compute the maximum distance of the points from the plane + // origin+t0*direction[0]+t1*direction[1]. + maxDistance = (Real)0; + Real maxSign = (Real)0; + extreme[3] = extreme[0]; + for (i = 0; i < numVectors; ++i) + { + Vector3 diff = v[i] - origin; + distance = Dot(direction[2], diff); + Real sign = (distance >(Real)0 ? (Real)1 : + (distance < (Real)0 ? (Real)-1 : (Real)0)); + distance = std::abs(distance); + if (distance > maxDistance) + { + maxDistance = distance; + maxSign = sign; + extreme[3] = i; + } + } + + if (maxDistance <= epsilon * maxRange) + { + // The points are (nearly) on the plane origin+t0*direction[0] + // +t1*direction[1]. + dimension = 2; + extreme[3] = extreme[2]; + return; + } + + dimension = 3; + extremeCCW = (maxSign > (Real)0); + return; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector4.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector4.h new file mode 100644 index 000000000000..f44c8f2b8468 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector4.h @@ -0,0 +1,186 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/04) + +#pragma once + +#include + +namespace gte +{ + +// Template alias for convenience. +template +using Vector4 = Vector<4, Real>; + +// In GteVector3.h, the Vector3 Cross, UnitCross, and DotCross have a template +// parameter N that should be 3 or 4. The latter case supports affine vectors +// in 4D (last component w = 0) when you want to use 4-tuples and 4x4 matrices +// for affine algebra. Thus, you may use those template functions for Vector4. + +// Compute the hypercross product using the formal determinant: +// hcross = det{{e0,e1,e2,e3},{x0,x1,x2,x3},{y0,y1,y2,y3},{z0,z1,z2,z3}} +// where e0 = (1,0,0,0), e1 = (0,1,0,0), e2 = (0,0,1,0), e3 = (0,0,0,1), +// v0 = (x0,x1,x2,x3), v1 = (y0,y1,y2,y3), and v2 = (z0,z1,z2,z3). +template +Vector4 HyperCross(Vector4 const& v0, Vector4 const& v1, + Vector4 const& v2); + +// Compute the normalized hypercross product. +template +Vector4 UnitHyperCross(Vector4 const& v0, + Vector4 const& v1, Vector4 const& v2, bool robust = false); + +// Compute Dot(HyperCross((x0,x1,x2,x3),(y0,y1,y2,y3),(z0,z1,z2,z3)), +// (w0,w1,w2,w3)), where v0 = (x0,x1,x2,x3), v1 = (y0,y1,y2,y3), +// v2 = (z0,z1,z2,z3), and v3 = (w0,w1,w2,w3). +template +Real DotHyperCross(Vector4 const& v0, Vector4 const& v1, + Vector4 const& v2, Vector4 const& v3); + +// Compute a right-handed orthonormal basis for the orthogonal complement +// of the input vectors. The function returns the smallest length of the +// unnormalized vectors computed during the process. If this value is nearly +// zero, it is possible that the inputs are linearly dependent (within +// numerical round-off errors). On input, numInputs must be 1, 2, or 3 and +// v[0] through v[numInputs-1] must be initialized. On output, the vectors +// v[0] through v[3] form an orthonormal set. +template +Real ComputeOrthogonalComplement(int numInputs, Vector4* v, + bool robust = false); + + +template +Vector4 HyperCross(Vector4 const& v0, Vector4 const& v1, + Vector4 const& v2) +{ + Real m01 = v0[0] * v1[1] - v0[1] * v1[0]; // x0*y1 - y0*x1 + Real m02 = v0[0] * v1[2] - v0[2] * v1[0]; // x0*z1 - z0*x1 + Real m03 = v0[0] * v1[3] - v0[3] * v1[0]; // x0*w1 - w0*x1 + Real m12 = v0[1] * v1[2] - v0[2] * v1[1]; // y0*z1 - z0*y1 + Real m13 = v0[1] * v1[3] - v0[3] * v1[1]; // y0*w1 - w0*y1 + Real m23 = v0[2] * v1[3] - v0[3] * v1[2]; // z0*w1 - w0*z1 + return Vector4 + { + +m23*v2[1] - m13*v2[2] + m12*v2[3], // +m23*y2 - m13*z2 + m12*w2 + -m23*v2[0] + m03*v2[2] - m02*v2[3], // -m23*x2 + m03*z2 - m02*w2 + +m13*v2[0] - m03*v2[1] + m01*v2[3], // +m13*x2 - m03*y2 + m01*w2 + -m12*v2[0] + m02*v2[1] - m01*v2[2] // -m12*x2 + m02*y2 - m01*z2 + }; +} + +template +Vector4 UnitHyperCross(Vector4 const& v0, + Vector4 const& v1, Vector4 const& v2, bool robust) +{ + Vector4 unitHyperCross = HyperCross(v0, v1, v2); + Normalize(unitHyperCross, robust); + return unitHyperCross; +} + +template +Real DotHyperCross(Vector4 const& v0, Vector4 const& v1, + Vector4 const& v2, Vector4 const& v3) +{ + return Dot(HyperCross(v0, v1, v2), v3); +} + +template +Real ComputeOrthogonalComplement(int numInputs, Vector4* v, bool robust) +{ + if (numInputs == 1) + { + int maxIndex = 0; + Real maxAbsValue = std::abs(v[0][0]); + for (int i = 1; i < 4; ++i) + { + Real absValue = std::abs(v[0][i]); + if (absValue > maxAbsValue) + { + maxIndex = i; + maxAbsValue = absValue; + } + } + + if (maxIndex < 2) + { + v[1][0] = -v[0][1]; + v[1][1] = +v[0][0]; + v[1][2] = (Real)0; + v[1][3] = (Real)0; + } + else if (maxIndex == 3) + { + // Generally, you can skip this clause and swap the last two + // components. However, by swapping 2 and 3 in this case, we + // allow the function to work properly when the inputs are 3D + // vectors represented as 4D affine vectors (w = 0). + v[1][0] = (Real)0; + v[1][1] = +v[0][2]; + v[1][2] = -v[0][1]; + v[1][3] = (Real)0; + } + else + { + v[1][0] = (Real)0; + v[1][1] = (Real)0; + v[1][2] = -v[0][3]; + v[1][3] = +v[0][2]; + } + + numInputs = 2; + } + + if (numInputs == 2) + { + Real det[6] = + { + v[0][0] * v[1][1] - v[1][0] * v[0][1], + v[0][0] * v[1][2] - v[1][0] * v[0][2], + v[0][0] * v[1][3] - v[1][0] * v[0][3], + v[0][1] * v[1][2] - v[1][1] * v[0][2], + v[0][1] * v[1][3] - v[1][1] * v[0][3], + v[0][2] * v[1][3] - v[1][2] * v[0][3] + }; + + int maxIndex = 0; + Real maxAbsValue = std::abs(det[0]); + for (int i = 1; i < 6; ++i) + { + Real absValue = std::abs(det[i]); + if (absValue > maxAbsValue) + { + maxIndex = i; + maxAbsValue = absValue; + } + } + + if (maxIndex == 0) + { + v[2] = { -det[4], +det[2], (Real)0, -det[0] }; + } + else if (maxIndex <= 2) + { + v[2] = { +det[5], (Real)0, -det[2], +det[1] }; + } + else + { + v[2] = { (Real)0, -det[5], +det[4], -det[3] }; + } + + numInputs = 3; + } + + if (numInputs == 3) + { + v[3] = HyperCross(v[0], v[1], v[2]); + return Orthonormalize<4, Real>(4, v, robust); + } + + return (Real)0; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVertexAttribute.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVertexAttribute.h new file mode 100644 index 000000000000..b6e61b2eb201 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVertexAttribute.h @@ -0,0 +1,56 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.3.0 (2016/08/29) + +#pragma once + +#include +#include + +namespace gte +{ + +struct VertexAttribute +{ + inline VertexAttribute(); + inline VertexAttribute(std::string const& inSemantic, void* inSource, size_t inStride); + + // The 'semantic' string allows you to query for a specific vertex + // attribute and use the 'source' and 'stride' to access the data + // of the attribute. For example, you might use the semantics + // "position" (px,py,pz), "normal" (nx,ny,nz), "tcoord" (texture + // coordinates (u,v)), "dpdu" (derivative of position with respect + // to u), or "dpdv" (derivative of position with respect to v) for + // mesh vertices. + // + // The source pointer must be 4-byte aligned. The stride must be + // positive and a multiple of 4. The pointer alignment constraint is + // guaranteed on 32-bit and 64-bit architectures. The stride constraint + // is reasonable given that (usually) geometric attributes are usually + // arrays of 'float' or 'double'. + + std::string semantic; + void* source; + size_t stride; +}; + +inline VertexAttribute::VertexAttribute() + : + semantic(""), + source(nullptr), + stride(0) +{ +} + +inline VertexAttribute::VertexAttribute(std::string const& inSemantic, void* inSource, size_t inStride) + : + semantic(inSemantic), + source(inSource), + stride(inStride) +{ +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVertexCollapseMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVertexCollapseMesh.h new file mode 100644 index 000000000000..19211f8c4f8a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVertexCollapseMesh.h @@ -0,0 +1,546 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/04) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class VertexCollapseMesh +{ +public: + // Construction. + VertexCollapseMesh(int numPositions, Vector3 const* positions, + int numIndices, int const* indices); + + // Decimate the mesh using vertex collapses + struct Record + { + // The index of the interior vertex that is removed from the mesh. + // The triangles adjacent to the vertex are 'removed' from the mesh. + // The polygon boundary of the adjacent triangles is triangulated and + // the new triangles are 'inserted' into the mesh. + int vertex; + std::vector> removed; + std::vector> inserted; + }; + + // Return 'true' when a vertex collapse occurs. Once the function returns + // 'false', no more vertex collapses are allowed so you may then stop + // calling the function. The implementation has several consistency tests + // that should not fail with a theoretically correct implementation. If a + // test fails, the function returns 'false' and the record.vertex is set to + // the invalid integer 0x80000000. When the Logger system is enabled, the + // failed tests are reported to any Logger listeners. + bool DoCollapse(Record& record); + + // Access the current state of the mesh, whether the original built in the + // constructor or a decimated mesh during DoCollapse calls. + inline ETManifoldMesh const& GetMesh() const; + +private: + struct VCVertex : public VETManifoldMesh::Vertex + { + VCVertex(int v); + static std::shared_ptr Create(int v); + + // The weight depends on the area of the triangles sharing the vertex + // and the lengths of the projections of the adjacent vertices onto + // the vertex normal line. A side effect of the call is that the + // vertex normal is computed and stored. + Real ComputeWeight(Vector3 const* positions); + + Vector3 normal; + bool isBoundary; + }; + + // The functions TriangulateLink and Collapsed return one of the + // enumerates described next. + // + // VCM_NO_MORE_ALLOWED: + // Either the mesh has no more interior vertices or a collapse + // will lead to a mesh fold-over or to a nonmanifold mesh. The + // returned value 'v' is invalid (0x80000000) and 'removed' and + // 'inserted' are empty. + // + // VCM_ALLOWED: + // An interior vertex v has been removed. This is allowed using the + // following algorithm. The vertex normal is the weighted average + // of non-unit-length normals of triangles sharing v. The weights + // are the triangle areas. The adjacent vertices are projected onto + // a plane containing v and having normal equal to the vertex normal. + // If the projection is a simple polygon in the plane, the collapse + // is allowed. The triangles sharing v are 'removed', the polygon is + // triangulated, and the new triangles are 'inserted' into the mesh. + // + // VCM_DEFERRED: + // If the projection polygon described in the previous case is not + // simple (at least one pair of edges overlaps at some edge-interior + // point), the collapse would produce a fold-over in the mesh. We + // do not collapse in this case. It is possible that such a vertex + // occurs in a later collapse as its neighbors are adjusted by + // collapses. When this case occurs, v is valid (even though the + // collapse was not allowed) but 'removed' and 'inserted' are empty. + // + // VCM_UNEXPECTED_ERROR: + // The code has several tests for conditions that are not expected + // to occur for a theoretically correct implementation. If you + // receive this error, file a bug report and provide a data set + // that caused the error. + enum + { + VCM_NO_MORE_ALLOWED, + VCM_ALLOWED, + VCM_DEFERRED, + VCM_UNEXPECTED_ERROR + }; + + int TriangulateLink(std::shared_ptr const& vertex, std::vector>& removed, + std::vector>& inserted, std::vector& linkVertices) const; + + int Collapsed(std::vector> const& removed, + std::vector> const& inserted, std::vector const& linkVertices); + + int mNumPositions; + Vector3 const* mPositions; + VETManifoldMesh mMesh; + + MinHeap mMinHeap; + std::map::Record*> mHeapRecords; +}; + + +template +VertexCollapseMesh::VertexCollapseMesh(int numPositions, + Vector3 const* positions, int numIndices, int const* indices) + : + mNumPositions(numPositions), + mPositions(positions), + mMesh(VCVertex::Create) +{ + if (numPositions <= 0 || !positions || numIndices < 3 || !indices) + { + mNumPositions = 0; + mPositions = nullptr; + return; + } + + // Build the manifold mesh from the inputs. + int numTriangles = numIndices / 3; + int const* current = indices; + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *current++; + int v1 = *current++; + int v2 = *current++; + mMesh.Insert(v0, v1, v2); + } + + // Locate the vertices (if any) on the mesh boundary. + auto const& vmap = mMesh.GetVertices(); + for (auto const& eelement : mMesh.GetEdges()) + { + auto edge = eelement.second; + if (!edge->T[1].lock()) + { + for (int i = 0; i < 2; ++i) + { + auto velement = vmap.find(edge->V[i]); + auto vertex = std::static_pointer_cast(velement->second); + vertex->isBoundary = true; + } + } + } + + // Build the priority queue of weights for the interior vertices. + mMinHeap.Reset((int)vmap.size()); + for (auto const& velement : vmap) + { + auto vertex = std::static_pointer_cast(velement.second); + + Real weight; + if (vertex->isBoundary) + { + weight = std::numeric_limits::max(); + } + else + { + weight = vertex->ComputeWeight(mPositions); + } + + auto record = mMinHeap.Insert(velement.first, weight); + mHeapRecords.insert(std::make_pair(velement.first, record)); + } +} + +template +bool VertexCollapseMesh::DoCollapse(Record& record) +{ + record.vertex = 0x80000000; + record.removed.clear(); + record.inserted.clear(); + + if (mNumPositions == 0) + { + // The constructor failed, so there is nothing to collapse. + return false; + } + + while (mMinHeap.GetNumElements() > 0) + { + int v = -1; + Real weight = std::numeric_limits::max(); + mMinHeap.GetMinimum(v, weight); + if (weight == std::numeric_limits::max()) + { + // There are no more interior vertices to collapse. + return false; + } + + auto const& vmap = mMesh.GetVertices(); + auto velement = vmap.find(v); + if (velement == vmap.end()) + { + LogError("Unexpected condition."); + return false; + } + + auto vertex = std::static_pointer_cast(velement->second); + std::vector> removed, inserted; + std::vector linkVertices; + int result = TriangulateLink(vertex, removed, inserted, linkVertices); + if (result == VCM_UNEXPECTED_ERROR) + { + return false; + } + + if (result == VCM_ALLOWED) + { + result = Collapsed(removed, inserted, linkVertices); + if (result == VCM_UNEXPECTED_ERROR) + { + return false; + } + + if (result == VCM_ALLOWED) + { + // Remove the vertex and associated weight. + mMinHeap.Remove(v, weight); + mHeapRecords.erase(v); + + // Update the weights of the link vertices. + for (auto vlink : linkVertices) + { + velement = vmap.find(vlink); + if (velement == vmap.end()) + { + LogError("Unexpected condition."); + return false; + } + + vertex = std::static_pointer_cast(velement->second); + if (!vertex->isBoundary) + { + auto iter = mHeapRecords.find(vlink); + if (iter == mHeapRecords.end()) + { + LogError("Unexpected condition."); + return false; + } + + weight = vertex->ComputeWeight(mPositions); + mMinHeap.Update(iter->second, weight); + } + } + + record.vertex = v; + record.removed = std::move(removed); + record.inserted = std::move(inserted); + return true; + } + // else: result == VCM_DEFERRED + } + + // To get here, result must be VCM_DEFERRED. The vertex collapse + // would cause mesh fold-over. Temporarily set the edge weight to + // infinity. After removal of other triangles, the vertex weight + // will be updated to a finite value and the vertex possibly can be + // removed at that time. + auto iter = mHeapRecords.find(v); + if (iter == mHeapRecords.end()) + { + LogError("Unexpected condition."); + return false; + } + mMinHeap.Update(iter->second, std::numeric_limits::max()); + } + + // We do not expect to reach this line of code, even for a closed mesh. + // However, the compiler does not know this, yet requires a return value. + return false; +} + +template inline +ETManifoldMesh const& VertexCollapseMesh::GetMesh() const +{ + return mMesh; +} + +template +int VertexCollapseMesh::TriangulateLink(std::shared_ptr const& vertex, + std::vector>& removed, std::vector>& inserted, + std::vector& linkVertices) const +{ + // Create the (CCW) polygon boundary of the link of the vertex. The + // incoming vertex is interior, so the number of triangles sharing the + // vertex is equal to the number of vertices of the polygon. A + // precondition of the function call is that the vertex normal has already + // been computed. + + // Get the edges of the link that are opposite the incoming vertex. + int const numVertices = static_cast(vertex->TAdjacent.size()); + removed.resize(numVertices); + int j = 0; + std::map edgeMap; + for (auto tri : vertex->TAdjacent) + { + for (int i = 0; i < 3; ++i) + { + if (tri->V[i] == vertex->V) + { + edgeMap.insert(std::make_pair(tri->V[(i + 1) % 3], tri->V[(i + 2) % 3])); + break; + } + } + removed[j++] = TriangleKey(tri->V[0], tri->V[1], tri->V[2]); + } + if (edgeMap.size() != vertex->TAdjacent.size()) + { + LogError("Unexpected condition."); + return VCM_UNEXPECTED_ERROR; + } + + // Connect the edges into a polygon. + linkVertices.resize(numVertices); + auto iter = edgeMap.begin(); + for (int i = 0; i < numVertices; ++i) + { + linkVertices[i] = iter->first; + iter = edgeMap.find(iter->second); + if (iter == edgeMap.end()) + { + LogError("Unexpected condition."); + return VCM_UNEXPECTED_ERROR; + } + } + if (iter->first != linkVertices[0]) + { + LogError("Unexpected condition."); + return VCM_UNEXPECTED_ERROR; + } + + // Project the polygon onto the plane containing the incoming vertex and + // having the vertex normal. The projected polygon is computed so that + // the incoming vertex is projected to (0,0). + Vector3 center = mPositions[vertex->V]; + Vector3 basis[3]; + basis[0] = vertex->normal; + ComputeOrthogonalComplement(1, basis); + std::vector> projected(numVertices); + std::vector indices(numVertices); + for (int i = 0; i < numVertices; ++i) + { + Vector3 diff = mPositions[linkVertices[i]] - center; + projected[i][0] = Dot(basis[1], diff); + projected[i][1] = Dot(basis[2], diff); + indices[i] = i; + } + + // The polygon must be simple in order to triangulate it. + Polygon2 polygon(projected.data(), numVertices, indices.data(), true); + if (polygon.IsSimple()) + { + TriangulateEC triangulator(numVertices, projected.data()); + triangulator(); + auto const& triangles = triangulator.GetTriangles(); + if (triangles.size() == 0) + { + LogError("Unexpected condition."); + return VCM_UNEXPECTED_ERROR; + } + + int const numTriangles = static_cast(triangles.size()); + inserted.resize(numTriangles); + for (int t = 0; t < numTriangles; ++t) + { + inserted[t] = TriangleKey( + linkVertices[triangles[t][0]], + linkVertices[triangles[t][1]], + linkVertices[triangles[t][2]]); + } + return VCM_ALLOWED; + } + else + { + return VCM_DEFERRED; + } +} + +template +int VertexCollapseMesh::Collapsed(std::vector> const& removed, + std::vector> const& inserted, std::vector const& linkVertices) +{ + // The triangles that were disconnected from the link edges are guaranteed + // to allow manifold reconnection to 'inserted' triangles. On the + // insertion, each diagonal of the link becomes a mesh edge and shares two + // (link) triangles. It is possible that the mesh already contains the + // (diagonal) edge, which will lead to a nonmanifold connection, which we + // cannot allow. The following code traps this condition and restores the + // mesh to its state before the 'Remove(...)' call. + bool isCollapsible = true; + auto const& emap = mMesh.GetEdges(); + std::set> edges; + for (auto const& tri : inserted) + { + for (int k0 = 2, k1 = 0; k1 < 3; k0 = k1++) + { + EdgeKey edge(tri.V[k0], tri.V[k1]); + if (edges.find(edge) == edges.end()) + { + edges.insert(edge); + } + else + { + // The edge has been visited twice, so it is a diagonal of + // the link. + + auto eelement = emap.find(edge); + if (eelement != emap.end()) + { + if (eelement->second->T[1].lock()) + { + // The edge will not allow a manifold connection. + isCollapsible = false; + break; + } + } + + edges.erase(edge); + } + }; + + if (!isCollapsible) + { + return VCM_DEFERRED; + } + } + + // Remove the old triangle neighborhood, which will lead to the vertex + // itself being removed from the mesh. + for (auto tri : removed) + { + mMesh.Remove(tri.V[0], tri.V[1], tri.V[2]); + } + + // Insert the new triangulation. + for (auto const& tri : inserted) + { + mMesh.Insert(tri.V[0], tri.V[1], tri.V[2]); + } + + // If the Remove(...) calls remove a boundary vertex that is in the link + // vertices, the Insert(...) calls will insert the boundary vertex again. + // We must re-tag those boundary vertices. + auto const& vmap = mMesh.GetVertices(); + size_t const numVertices = linkVertices.size(); + for (size_t i0 = numVertices - 1, i1 = 0; i1 < numVertices; i0 = i1++) + { + EdgeKey ekey(linkVertices[i0], linkVertices[i1]); + auto eelement = emap.find(ekey); + if (eelement == emap.end()) + { + LogError("Unexpected condition."); + return VCM_UNEXPECTED_ERROR; + } + + auto edge = eelement->second; + if (!edge) + { + LogError("Unexpected condition."); + return VCM_UNEXPECTED_ERROR; + } + + if (edge->T[0].lock() && !edge->T[1].lock()) + { + for (int k = 0; k < 2; ++k) + { + auto velement = vmap.find(edge->V[k]); + if (velement == vmap.end()) + { + LogError("Unexpected condition."); + return VCM_UNEXPECTED_ERROR; + } + + auto vertex = std::static_pointer_cast(velement->second); + vertex->isBoundary = true; + } + } + } + + return VCM_ALLOWED; +} + +template +VertexCollapseMesh::VCVertex::VCVertex(int v) + : + VETManifoldMesh::Vertex(v), + normal(Vector3::Zero()), + isBoundary(false) +{ +} + +template +std::shared_ptr VertexCollapseMesh::VCVertex::Create(int v) +{ + return std::make_shared(v); +} + +template +Real VertexCollapseMesh::VCVertex::ComputeWeight(Vector3 const* positions) +{ + Real weight = (Real)0; + + normal = { (Real)0, (Real)0, (Real)0 }; + for (auto const& tri : TAdjacent) + { + Vector3 E0 = positions[tri->V[1]] - positions[tri->V[0]]; + Vector3 E1 = positions[tri->V[2]] - positions[tri->V[0]]; + Vector3 N = Cross(E0, E1); + normal += N; + weight += Length(N); + } + Normalize(normal); + + for (int index : VAdjacent) + { + Vector3 diff = positions[index] - positions[V]; + weight += std::abs(Dot(normal, diff)); + } + + return weight; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/Arrangement2d.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/Arrangement2d.h new file mode 100644 index 000000000000..7b322570fc26 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/Arrangement2d.h @@ -0,0 +1,515 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp Arrangement2d + +#pragma once + +#include "BoxTypes.h" +#include "Curve/DynamicGraph2.h" +#include "Intersection/IntrSegment2Segment2.h" +#include "Polygon2.h" +#include "Spatial/PointHashGrid2.h" +#include "Util/GridIndexing2.h" + +#include "CoreMinimal.h" + +/** + * Arrangement2d constructs a planar arrangement of a set of 2D line segments. + * When a segment is inserted, existing edges are split, and the inserted + * segment becomes multiple graph edges. So, the resulting FDynamicGraph2d should + * not have any edges that intersect. + * + * Calculations are performed in double-precision, so there is no guarantee + * of correctness. + * + * + * [TODO] multi-level segment has to accelerate find_intersecting_edges() + * [TODO] maybe smarter handling + * + */ +struct FArrangement2d +{ + // graph of arrangement + FDynamicGraph2d Graph; + + // PointHash for vertices of graph + TPointHashGrid2d PointHash; + + // points within this tolerance are merged + double VertexSnapTol = 0.00001; + + FArrangement2d(const FAxisAlignedBox2d& BoundsHint) + : PointHash(BoundsHint.MaxDim() / 64, -1) + { + } + + FArrangement2d(double PointHashCellSize) + : PointHash(PointHashCellSize, -1) + { + } + + /** + * Attempts to triangulates the arrangement with a constrained delaunay triangulation + * NOTE: Not robust; generates invalid triangulations and fails to insert edges sometimes, even if the arrangement has no self-intersections + * But should always do *something* that at least covers the point set, if the point set is not degenerate + * TODO: Make a robust triangulation algo + * + * Triangles: Output triangles (as indices into Graph vertices) + * SkippedEdges: Output indices of edges that the algorithm failed to insert + * BoundaryEdgeGroupID: ID of edges corresponding to a boundary; if we have a closed loop of these boundary edges on output triangulation, will discard triangles outside this + * return: false if triangulation algo knows it failed (NOTE: triangulation may still be invalid when function returns true, as function is not robust) + */ + bool GEOMETRYALGORITHMS_API AttemptTriangulate(TArray& Triangles, TArray& SkippedEdges, int32 BoundaryEdgeGroupID); + + /** + * Check if current Graph has self-intersections; not optimized, only for debugging + */ + bool HasSelfIntersections() + { + for (const FDynamicGraph::FEdge& e : Graph.Edges()) + { + TArray Hits; + int HitCount = find_intersecting_edges(Graph.GetVertex(e.A), Graph.GetVertex(e.B), Hits, 0.0); + for (const FIntersection& Intersect : Hits) + { + FDynamicGraph::FEdge o = Graph.GetEdge(Intersect.EID); + if (o.A != e.A && o.A != e.B && o.B != e.A && o.B != e.B) + { + return true; + } + } + } + return false; + } + + /** + * Subdivide edge at a given position + */ + FIndex2i SplitEdgeAtPoint(int EdgeID, FVector2d Point) + { + FDynamicGraph2d::FEdgeSplitInfo splitInfo; + EMeshResult result = Graph.SplitEdge(EdgeID, splitInfo); + ensureMsgf(result == EMeshResult::Ok, TEXT("SplitEdgeAtPoint: edge split failed?")); + Graph.SetVertex(splitInfo.VNew, Point); + PointHash.InsertPointUnsafe(splitInfo.VNew, Point); + return FIndex2i(splitInfo.VNew, splitInfo.ENewBN); + } + + /** + * Check if vertex exists in region + */ + bool HasVertexNear(FVector2d Point, double SearchRadius) + { + return find_nearest_vertex(Point, SearchRadius) > -1; + } + + /** + * Insert isolated point P into the arrangement + */ + int Insert(const FVector2d& Pt) + { + return insert_point(Pt, VertexSnapTol); + } + + + /** + * insert segment [A,B] into the arrangement + */ + void Insert(const FVector2d& A, const FVector2d& B, int GID = -1) + { + insert_segment(A, B, GID, VertexSnapTol); + } + + /** + * insert segment into the arrangement + */ + void Insert(const FSegment2d& Segment, int GID = -1) + { + insert_segment(Segment.StartPoint(), Segment.EndPoint(), GID, VertexSnapTol); + } + + ///** + // * sequentially insert segments of polyline + // */ + //void Insert(PolyLine2d pline, int GID = -1) + //{ + // int N = pline.VertexCount - 1; + // for (int i = 0; i < N; ++i) { + // FVector2d A = pline[i]; + // FVector2d B = pline[i + 1]; + // insert_segment(A, B, GID); + // } + //} + + ///** + // * sequentially insert segments of polygon + // */ + void Insert(const FPolygon2d& Poly, int GID = -1) + { + int N = Poly.VertexCount(); + for (int i = 0; i < N; ++i) + { + insert_segment(Poly[i], Poly[(i + 1) % N], GID, VertexSnapTol); + } + } + + /* + * Graph improvement + */ + + /** + * connect open boundary vertices within DistThresh, by inserting new segments + */ + void ConnectOpenBoundaries(double DistThresh) + { + int max_vid = Graph.MaxVertexID(); + for (int VID = 0; VID < max_vid; ++VID) + { + if (Graph.IsBoundaryVertex(VID) == false) + { + continue; + } + + FVector2d v = Graph.GetVertex(VID); + int snap_with = find_nearest_boundary_vertex(v, DistThresh, VID); + if (snap_with != -1) + { + FVector2d v2 = Graph.GetVertex(snap_with); + Insert(v, v2); + } + } + } + +protected: + struct FSegmentPoint + { + double T; + int VID; + }; + + /** + * insert pt P into the arrangement, splitting existing edges as necessary + */ + int insert_point(const FVector2d &P, double Tol = 0) + { + int PIdx = find_existing_vertex(P); + if (PIdx > -1) + { + return -1; + } + + // TODO: currently this tries to add the vertex on the closest edge below tolerance; we should instead insert at *every* edge below tolerance! ... but that is more inconvenient to write + FVector2d x = FVector2d::Zero(), y = FVector2d::Zero(); + double ClosestDistSq = Tol*Tol; + int FoundEdgeToSplit = -1; + for (int EID = 0, ExistingEdgeMax = Graph.MaxEdgeID(); EID < ExistingEdgeMax; EID++) + { + if (!Graph.IsEdge(EID)) + { + continue; + } + + Graph.GetEdgeV(EID, x, y); + FSegment2d Seg(x, y); + double DistSq = Seg.DistanceSquared(P); + if (DistSq < ClosestDistSq) + { + ClosestDistSq = DistSq; + FoundEdgeToSplit = EID; + } + } + if (FoundEdgeToSplit > -1) + { + FDynamicGraph2d::FEdgeSplitInfo splitInfo; + EMeshResult result = Graph.SplitEdge(FoundEdgeToSplit, splitInfo); + ensureMsgf(result == EMeshResult::Ok, TEXT("insert_into_segment: edge split failed?")); + Graph.SetVertex(splitInfo.VNew, P); + PointHash.InsertPointUnsafe(splitInfo.VNew, P); + return splitInfo.VNew; + } + + int VID = Graph.AppendVertex(P); + PointHash.InsertPointUnsafe(VID, P); + return VID; + } + + /** + * insert edge [A,B] into the arrangement, splitting existing edges as necessary + */ + bool insert_segment(FVector2d A, FVector2d B, int GID = -1, double Tol = 0) + { + // handle degenerate edges + int a_idx = find_existing_vertex(A); + int b_idx = find_existing_vertex(B); + if (a_idx == b_idx && a_idx >= 0) + { + return false; + } + // snap input vertices + if (a_idx >= 0) + { + A = Graph.GetVertex(a_idx); + } + if (b_idx >= 0) + { + B = Graph.GetVertex(b_idx); + } + + // handle tiny-segment case + double SegLenSq = A.DistanceSquared(B); + if (SegLenSq <= VertexSnapTol*VertexSnapTol) + { + // seg is too short and was already on an existing vertex; just consider that vertex to be the inserted segment + if (a_idx >= 0 || b_idx >= 0) + { + return false; + } + // seg is too short and wasn't on an existing vertex; add it as an isolated vertex + return insert_point(A, Tol) != -1; + } + + // ok find all intersections + TArray Hits; + find_intersecting_edges(A, B, Hits, Tol); + + // we are going to construct a list of values along segment AB + TArray points; + FSegment2d segAB = FSegment2d(A, B); + + find_intersecting_floating_vertices(segAB, a_idx, b_idx, points, Tol); + + // insert intersections into existing segments + for (int i = 0, N = Hits.Num(); i < N; ++i) + { + FIntersection Intr = Hits[i]; + int EID = Intr.EID; + double t0 = Intr.Intr.Parameter0, t1 = Intr.Intr.Parameter1; + + // insert first point at t0 + int new_eid = -1; + if (Intr.Intr.Type == EIntersectionType::Point || Intr.Intr.Type == EIntersectionType::Segment) + { + FIndex2i new_info = split_segment_at_t(EID, t0, VertexSnapTol); + new_eid = new_info.B; + FVector2d v = Graph.GetVertex(new_info.A); + points.Add(FSegmentPoint{segAB.Project(v), new_info.A}); + } + + // if intersection was on-segment, then we have a second point at t1 + if (Intr.Intr.Type == EIntersectionType::Segment) + { + if (new_eid == -1) + { + // did not actually split edge for t0, so we can still use EID + FIndex2i new_info = split_segment_at_t(EID, t1, VertexSnapTol); + FVector2d v = Graph.GetVertex(new_info.A); + points.Add(FSegmentPoint{segAB.Project(v), new_info.A}); + } + else + { + // find t1 was in EID, rebuild in new_eid + FSegment2d new_seg = Graph.GetEdgeSegment(new_eid); + FVector2d p1 = Intr.Intr.GetSegment1().PointAt(t1); + double new_t1 = new_seg.Project(p1); + check(new_t1 <= FMath::Abs(new_seg.Extent)); + + FIndex2i new_info = split_segment_at_t(new_eid, new_t1, VertexSnapTol); + FVector2d v = Graph.GetVertex(new_info.A); + points.Add(FSegmentPoint{segAB.Project(v), new_info.A}); + } + } + } + + // find or create start and end points + if (a_idx == -1) + { + a_idx = find_existing_vertex(A); + } + if (a_idx == -1) + { + a_idx = Graph.AppendVertex(A); + PointHash.InsertPointUnsafe(a_idx, A); + } + if (b_idx == -1) + { + b_idx = find_existing_vertex(B); + } + if (b_idx == -1) + { + b_idx = Graph.AppendVertex(B); + PointHash.InsertPointUnsafe(b_idx, B); + } + + // add start/end to points list. These may be duplicates but we will sort that out after + points.Add(FSegmentPoint{-segAB.Extent, a_idx}); + points.Add(FSegmentPoint{segAB.Extent, b_idx}); + // sort by T + points.Sort([](const FSegmentPoint& pa, const FSegmentPoint& pb) { return pa.T < pb.T; }); + + // connect sequential points, as long as they aren't the same point, + // and the segment doesn't already exist + for (int k = 0; k < points.Num() - 1; ++k) + { + int v0 = points[k].VID; + int v1 = points[k + 1].VID; + if (v0 == v1) + { + continue; + } + + // sanity check + ensureMsgf(FMath::Abs(points[k].T - points[k + 1].T) >= std::numeric_limits::epsilon(), TEXT("insert_segment: different points have same T??")); + + if (Graph.FindEdge(v0, v1) == FDynamicGraph2d::InvalidID) + { + Graph.AppendEdge(v0, v1, GID); + } + } + + return true; + } + + /** + * insert new point into segment EID at parameter value T + * If T is within Tol of endpoint of segment, we use that instead. + */ + FIndex2i split_segment_at_t(int EID, double T, double Tol) + { + FVector2d V1, V2; + FIndex2i ev = Graph.GetEdgeV(EID); + FSegment2d seg = FSegment2d(Graph.GetVertex(ev.A), Graph.GetVertex(ev.B)); + + int use_vid = -1; + int new_eid = -1; + if (T < -(seg.Extent - Tol)) + { + use_vid = ev.A; + } + else if (T > (seg.Extent - Tol)) + { + use_vid = ev.B; + } + else + { + FVector2d Pt = seg.PointAt(T); + // ensure(find_existing_vertex(Pt) == -1); // TODO: handle this case!? + FDynamicGraph2d::FEdgeSplitInfo splitInfo; + EMeshResult result = Graph.SplitEdge(EID, splitInfo); + ensureMsgf(result == EMeshResult::Ok, TEXT("insert_into_segment: edge split failed?")); + use_vid = splitInfo.VNew; + new_eid = splitInfo.ENewBN; + Graph.SetVertex(use_vid, Pt); + PointHash.InsertPointUnsafe(splitInfo.VNew, Pt); + } + return FIndex2i(use_vid, new_eid); + } + + /** + * find existing vertex at point, if it exists + */ + int find_existing_vertex(FVector2d Pt) + { + return find_nearest_vertex(Pt, VertexSnapTol); + } + /** + * find closest vertex, within SearchRadius + */ + int find_nearest_vertex(FVector2d Pt, double SearchRadius, int IgnoreVID = -1) + { + auto FuncDistSq = [&](int B) { return Pt.DistanceSquared(Graph.GetVertex(B)); }; + auto FuncIgnore = [&](int VID) { return VID == IgnoreVID; }; + TPair found = (IgnoreVID == -1) ? PointHash.FindNearestInRadius(Pt, SearchRadius, FuncDistSq) + : PointHash.FindNearestInRadius(Pt, SearchRadius, FuncDistSq, FuncIgnore); + if (found.Key == PointHash.InvalidValue()) + { + return -1; + } + return found.Key; + } + + /** + * find nearest boundary vertex, within SearchRadius + */ + int find_nearest_boundary_vertex(FVector2d Pt, double SearchRadius, int IgnoreVID = -1) + { + auto FuncDistSq = [&](int B) { return Pt.DistanceSquared(Graph.GetVertex(B)); }; + auto FuncIgnore = [&](int VID) { return Graph.IsBoundaryVertex(VID) == false || VID == IgnoreVID; }; + TPair found = + PointHash.FindNearestInRadius(Pt, SearchRadius, FuncDistSq, FuncIgnore); + if (found.Key == PointHash.InvalidValue()) + { + return -1; + } + return found.Key; + } + + struct FIntersection + { + int EID; + int SideX; + int SideY; + FIntrSegment2Segment2d Intr; + }; + + /** + * find set of edges in graph that intersect with edge [A,B] + */ + bool find_intersecting_edges(FVector2d A, FVector2d B, TArray& Hits, double Tol = 0) + { + int num_hits = 0; + FVector2d x = FVector2d::Zero(), y = FVector2d::Zero(); + FVector2d EPerp = (B - A).Perp(); + EPerp.Normalize(); + for (int EID : Graph.EdgeIndices()) + { + Graph.GetEdgeV(EID, x, y); + // inlined version of WhichSide with pre-normalized EPerp, to ensure Tolerance is consistent for different edge lengths + double SignX = EPerp.Dot(x - A); + double SignY = EPerp.Dot(y - A); + int SideX = (SignX > Tol ? +1 : (SignX < -Tol ? -1 : 0)); + int SideY = (SignY > Tol ? +1 : (SignY < -Tol ? -1 : 0)); + if (SideX == SideY && SideX != 0) + { + continue; // both pts on same side + } + + FIntrSegment2Segment2d Intr(FSegment2d(x, y), FSegment2d(A, B)); + Intr.SetIntervalThreshold(Tol); + // set a loose DotThreshold as well so almost-parallel segments are treated as parallel; + // otherwise we're more likely to hit later problems when an edge intersects near-overlapping edges at almost the same point + // (TODO: detect + handle that case!) + Intr.SetDotThreshold(1e-4); + if (Intr.Find()) + { + Hits.Add(FIntersection{EID, SideX, SideY, Intr}); + num_hits++; + } + } + + return (num_hits > 0); + } + + bool find_intersecting_floating_vertices(const FSegment2d &SegAB, int32 AID, int32 BID, TArray& Hits, double Tol = 0) + { + int num_hits = 0; + + for (int VID : Graph.VertexIndices()) + { + if (Graph.GetVtxEdgeCount(VID) > 0 || VID == AID || VID == BID) // if it's an existing edge or on the currently added edge, it's not floating so skip it + { + continue; + } + + FVector2d V = Graph.GetVertex(VID); + double T; + double DSQ = SegAB.DistanceSquared(V, T); + if (DSQ < Tol*Tol) + { + Hits.Add(FSegmentPoint{ T, VID }); + num_hits++; + } + } + + return num_hits > 0; + } +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/GeometryAlgorithmsModule.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/GeometryAlgorithmsModule.h new file mode 100644 index 000000000000..21bd24fa5f4f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/GeometryAlgorithmsModule.h @@ -0,0 +1,15 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FGeometryAlgorithmsModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/MeshConversion.Build.cs b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/MeshConversion.Build.cs new file mode 100644 index 000000000000..3b8573a2163a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/MeshConversion.Build.cs @@ -0,0 +1,28 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class MeshConversion : ModuleRules +{ + public MeshConversion(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange( + new string[] { + "Core", + "MeshDescription", + "GeometricObjects", + "DynamicMesh" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine" + } + ); + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/DynamicMeshToMeshDescription.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/DynamicMeshToMeshDescription.cpp new file mode 100644 index 000000000000..4701ddcd07ee --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/DynamicMeshToMeshDescription.cpp @@ -0,0 +1,246 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DynamicMeshToMeshDescription.h" +#include "MeshAttributes.h" +#include "DynamicMeshAttributeSet.h" +#include "DynamicMeshOverlay.h" +#include "MeshDescriptionBuilder.h" +#include "MeshNormals.h" + + + +void FDynamicMeshToMeshDescription::Update(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut) +{ + FMeshDescriptionBuilder Builder; + Builder.SetMeshDescription(&MeshOut); + + check(MeshIn->IsCompactV()); + check(MeshIn->VertexCount() == MeshOut.Vertices().Num()); + + // update positions + for (int VertID : MeshIn->VertexIndicesItr()) + { + Builder.SetPosition(FVertexID(VertID), MeshIn->GetVertex(VertID)); + } + + // can't trust these yet... + //const FDynamicMeshUVOverlay* UVOverlay = MeshIn->HasAttributes() ? MeshIn->Attributes()->PrimaryUV() : nullptr; + //const FDynamicMeshNormalOverlay* NormalOverlay = MeshIn->HasAttributes() ? MeshIn->Attributes()->PrimaryNormals() : nullptr; + + Builder.RecalculateInstanceNormals(); +} + + + + + + + + +void FDynamicMeshToMeshDescription::Convert(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut) +{ + if (MeshIn->HasAttributes()) + { + Convert_SharedInstances(MeshIn, MeshOut); + } + else + { + Convert_NoAttributes(MeshIn, MeshOut); + } +} + + +void FDynamicMeshToMeshDescription::Convert_NoAttributes(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut) +{ + MeshOut.Empty(); + + FMeshDescriptionBuilder Builder; + Builder.SetMeshDescription(&MeshOut); + + bool bCopyGroupToPolyGroup = false; + if (bSetPolyGroups && MeshIn->HasTriangleGroups()) + { + Builder.EnablePolyGroups(); + bCopyGroupToPolyGroup = true; + } + + // create vertices + TArray MapV; + MapV.SetNum(MeshIn->MaxVertexID()); + for (int VertID : MeshIn->VertexIndicesItr()) + { + MapV[VertID] = Builder.AppendVertex(MeshIn->GetVertex(VertID)); + } + + FMeshNormals VertexNormals(MeshIn); + VertexNormals.ComputeVertexNormals(); + + FPolygonGroupID AllGroupID = Builder.AppendPolygonGroup(); + + // create new instances when seen + TMap InstanceList; + for (int TriID : MeshIn->TriangleIndicesItr()) + { + FIndex3i Triangle = MeshIn->GetTriangle(TriID); + FIndex3i UVTriangle(-1, -1, -1); + FIndex3i NormalTriangle = Triangle; + FVertexInstanceID InstanceTri[3]; + for (int j = 0; j < 3; ++j) + { + FIndex3i InstanceElem(Triangle[j], UVTriangle[j], NormalTriangle[j]); + if (InstanceList.Contains(InstanceElem) == false) + { + FVertexInstanceID NewInstanceID = Builder.AppendInstance(MapV[Triangle[j]]); + InstanceList.Add(InstanceElem, NewInstanceID); + Builder.SetInstance(NewInstanceID, FVector2f::Zero(), VertexNormals[Triangle[j]]); + } + InstanceTri[j] = InstanceList[InstanceElem]; + } + + FPolygonID NewPolygonID = Builder.AppendTriangle(InstanceTri[0], InstanceTri[1], InstanceTri[2], AllGroupID); + if (bCopyGroupToPolyGroup) + { + Builder.SetPolyGroupID(NewPolygonID, MeshIn->GetTriangleGroup(TriID)); + } + } + + Builder.RecalculateInstanceNormals(); +} + + + + + + + +void FDynamicMeshToMeshDescription::Convert_SharedInstances(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut) +{ + const FDynamicMeshUVOverlay* UVOverlay = MeshIn->HasAttributes() ? MeshIn->Attributes()->PrimaryUV() : nullptr; + const FDynamicMeshNormalOverlay* NormalOverlay = MeshIn->HasAttributes() ? MeshIn->Attributes()->PrimaryNormals() : nullptr; + + MeshOut.Empty(); + + FMeshDescriptionBuilder Builder; + Builder.SetMeshDescription(&MeshOut); + + bool bCopyGroupToPolyGroup = false; + if (bSetPolyGroups && MeshIn->HasTriangleGroups()) + { + Builder.EnablePolyGroups(); + bCopyGroupToPolyGroup = true; + } + + // create vertices + TArray MapV; MapV.SetNum(MeshIn->MaxVertexID()); + for (int VertID : MeshIn->VertexIndicesItr()) + { + MapV[VertID] = Builder.AppendVertex(MeshIn->GetVertex(VertID)); + } + + + FPolygonGroupID AllGroupID = Builder.AppendPolygonGroup(); + + + // create new instances when seen + TMap InstanceList; + for (int TriID : MeshIn->TriangleIndicesItr()) + { + // @todo support additional overlays (requires IndexNi...) + FIndex3i Triangle = MeshIn->GetTriangle(TriID); + FIndex3i UVTriangle = (UVOverlay != nullptr) ? UVOverlay->GetTriangle(TriID) : FIndex3i(-1, -1, -1); + FIndex3i NormalTriangle = (NormalOverlay != nullptr) ? NormalOverlay->GetTriangle(TriID) : FIndex3i(-1, -1, -1); + FVertexInstanceID InstanceTri[3]; + for (int j = 0; j < 3; ++j) + { + FIndex3i InstanceElem(Triangle[j], UVTriangle[j], NormalTriangle[j]); + if (InstanceList.Contains(InstanceElem) == false) + { + FVertexInstanceID NewInstanceID = Builder.AppendInstance(MapV[Triangle[j]]); + InstanceList.Add(InstanceElem, NewInstanceID); + Builder.SetInstance(NewInstanceID, + (UVTriangle[j] == -1 || UVOverlay == nullptr) ? FVector2f::Zero() : UVOverlay->GetElement(UVTriangle[j]), + (NormalTriangle[j] == -1 || NormalOverlay == nullptr) ? FVector3f::UnitY() : NormalOverlay->GetElement(NormalTriangle[j])); + } + InstanceTri[j] = InstanceList[InstanceElem]; + } + + FPolygonID NewPolygonID = Builder.AppendTriangle(InstanceTri[0], InstanceTri[1], InstanceTri[2], AllGroupID); + + if (bCopyGroupToPolyGroup) + { + Builder.SetPolyGroupID(NewPolygonID, MeshIn->GetTriangleGroup(TriID)); + } + } +} + + + + + +void FDynamicMeshToMeshDescription::Convert_NoSharedInstances(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut) +{ + const FDynamicMeshUVOverlay* UVOverlay = MeshIn->HasAttributes() ? MeshIn->Attributes()->PrimaryUV() : nullptr; + const FDynamicMeshNormalOverlay* NormalOverlay = MeshIn->HasAttributes() ? MeshIn->Attributes()->PrimaryNormals() : nullptr; + + MeshOut.Empty(); + + FMeshDescriptionBuilder Builder; + Builder.SetMeshDescription(&MeshOut); + + bool bCopyGroupToPolyGroup = false; + if (bSetPolyGroups && MeshIn->HasTriangleGroups()) + { + Builder.EnablePolyGroups(); + bCopyGroupToPolyGroup = true; + } + + TArray MapV; MapV.SetNum(MeshIn->MaxVertexID()); + for (int VertID : MeshIn->VertexIndicesItr()) + { + MapV[VertID] = Builder.AppendVertex(MeshIn->GetVertex(VertID)); + } + + FPolygonGroupID AllGroupID = Builder.AppendPolygonGroup(); + + FVertexID TriVertices[3]; + FVector2D TriUVs[3]; + FVector TriNormals[3]; + for (int TriID : MeshIn->TriangleIndicesItr()) + { + FIndex3i Triangle = MeshIn->GetTriangle(TriID); + + FVector2D* UseUVs = nullptr; + if (UVOverlay != nullptr) + { + FIndex3i UVTriangle = UVOverlay->GetTriangle(TriID); + TriUVs[0] = UVOverlay->GetElement(UVTriangle[0]); + TriUVs[1] = UVOverlay->GetElement(UVTriangle[1]); + TriUVs[2] = UVOverlay->GetElement(UVTriangle[2]); + UseUVs = TriUVs; + } + + FVector* UseNormals = nullptr; + if (NormalOverlay != nullptr) + { + FIndex3i NormalTriangle = NormalOverlay->GetTriangle(TriID); + TriNormals[0] = NormalOverlay->GetElement(NormalTriangle[0]); + TriNormals[1] = NormalOverlay->GetElement(NormalTriangle[1]); + TriNormals[2] = NormalOverlay->GetElement(NormalTriangle[2]); + UseNormals = TriNormals; + } + + TriVertices[0] = MapV[Triangle[0]]; + TriVertices[1] = MapV[Triangle[1]]; + TriVertices[2] = MapV[Triangle[2]]; + + FPolygonID NewPolygonID = Builder.AppendTriangle(TriVertices, AllGroupID, UseUVs, UseNormals); + + if (bCopyGroupToPolyGroup) + { + Builder.SetPolyGroupID(NewPolygonID, MeshIn->GetTriangleGroup(TriID)); + } + } + + // set to hard edges + //PolyMeshIn->SetAllEdgesHardness(true); +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshConversionModule.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshConversionModule.cpp new file mode 100644 index 000000000000..5400834b1d3e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshConversionModule.cpp @@ -0,0 +1,20 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshConversionModule.h" + +#define LOCTEXT_NAMESPACE "FMeshConversionModule" + +void FMeshConversionModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FMeshConversionModule::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(FMeshConversionModule, MeshConversion) \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshDescriptionBuilder.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshDescriptionBuilder.cpp new file mode 100644 index 000000000000..51ab3d061f2f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshDescriptionBuilder.cpp @@ -0,0 +1,379 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshDescriptionBuilder.h" +#include "MeshAttributes.h" +#include "VectorTypes.h" +#include "BoxTypes.h" + +#include "DynamicMesh3.h" +#include "DynamicMeshAttributeSet.h" + +namespace ExtendedMeshAttribute +{ + const FName PolyTriGroups("PolyTriGroups"); +} + + + +void FMeshDescriptionBuilder::SetMeshDescription(FMeshDescription* Description) +{ + this->MeshDescription = Description; + this->VertexPositions = + MeshDescription->VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); + this->InstanceUVs = + MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); + this->InstanceNormals = + MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); + this->InstanceColors = + MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); +} + + +void FMeshDescriptionBuilder::EnablePolyGroups() +{ + PolyGroups = + MeshDescription->PolygonAttributes().GetAttributesRef(ExtendedMeshAttribute::PolyTriGroups); + if (PolyGroups.IsValid() == false) + { + MeshDescription->PolygonAttributes().RegisterAttribute( + ExtendedMeshAttribute::PolyTriGroups, 1, 0, EMeshAttributeFlags::AutoGenerated); + PolyGroups = + MeshDescription->PolygonAttributes().GetAttributesRef(ExtendedMeshAttribute::PolyTriGroups); + check(PolyGroups.IsValid()); + } +} + + +FVertexID FMeshDescriptionBuilder::AppendVertex(const FVector& Position) +{ + FVertexID VertexID = MeshDescription->CreateVertex(); + VertexPositions.Set(VertexID, Position); + return VertexID; +} + + +FPolygonGroupID FMeshDescriptionBuilder::AppendPolygonGroup() +{ + return MeshDescription->CreatePolygonGroup(); +} + + + +FPolygonID FMeshDescriptionBuilder::AppendTriangle(const FVertexID& Vertex0, const FVertexID& Vertex1, const FVertexID& Vertex2, const FPolygonGroupID& PolygonGroup) +{ + TempBuffer.SetNum(3, false); + TempBuffer[0] = Vertex0; + TempBuffer[1] = Vertex1; + TempBuffer[2] = Vertex2; + return AppendPolygon(TempBuffer, PolygonGroup); +} + + +FVertexInstanceID FMeshDescriptionBuilder::AppendInstance(const FVertexID& VertexID) +{ + return MeshDescription->CreateVertexInstance(VertexID); +} + + + +void FMeshDescriptionBuilder::SetPosition(const FVertexID& VertexID, const FVector& NewPosition) +{ + VertexPositions.Set(VertexID, 0, NewPosition); +} + +FVector FMeshDescriptionBuilder::GetPosition(const FVertexID& VertexID) +{ + return VertexPositions.Get(VertexID, 0); +} + +FVector FMeshDescriptionBuilder::GetPosition(const FVertexInstanceID& InstanceID) +{ + return VertexPositions.Get(MeshDescription->GetVertexInstance(InstanceID).VertexID, 0); +} + + +void FMeshDescriptionBuilder::SetInstance(const FVertexInstanceID& InstanceID, const FVector2D& InstanceUV, const FVector& InstanceNormal) +{ + if (InstanceUVs.IsValid()) + { + InstanceUVs.Set(InstanceID, InstanceUV); + } + if (InstanceNormals.IsValid()) + { + InstanceNormals.Set(InstanceID, InstanceNormal); + } +} + + +void FMeshDescriptionBuilder::SetInstanceNormal(const FVertexInstanceID& InstanceID, const FVector& Normal) +{ + if (InstanceNormals.IsValid()) + { + InstanceNormals.Set(InstanceID, Normal); + } +} + + +void FMeshDescriptionBuilder::SetInstanceColor(const FVertexInstanceID& InstanceID, const FVector4& Color) +{ + if (InstanceColors.IsValid()) + { + InstanceColors.Set(InstanceID, Color); + } +} + + +FPolygonID FMeshDescriptionBuilder::AppendTriangle(const FVertexID* Triangle, const FPolygonGroupID& PolygonGroup, + const FVector2D * VertexUVs, const FVector* VertexNormals) +{ + TempBuffer.SetNum(3, false); + TempBuffer[0] = Triangle[0]; + TempBuffer[1] = Triangle[1]; + TempBuffer[2] = Triangle[2]; + + const TArray * UseUVs = nullptr; + if (VertexUVs != nullptr) + { + UVBuffer.SetNum(3, false); + UVBuffer[0] = VertexUVs[0]; + UVBuffer[1] = VertexUVs[1]; + UVBuffer[2] = VertexUVs[2]; + UseUVs = &UVBuffer; + } + + const TArray * UseNormals = nullptr; + if (VertexNormals != nullptr) + { + NormalBuffer.SetNum(3, false); + NormalBuffer[0] = VertexNormals[0]; + NormalBuffer[1] = VertexNormals[1]; + NormalBuffer[2] = VertexNormals[2]; + UseNormals = &NormalBuffer; + } + + return AppendPolygon(TempBuffer, PolygonGroup, UseUVs, UseNormals); +} + + +FPolygonID FMeshDescriptionBuilder::AppendPolygon(const TArray& Vertices, const FPolygonGroupID& PolygonGroup, + const TArray * VertexUVs, const TArray* VertexNormals) +{ + int NumVertices = Vertices.Num(); + TArray Polygon; + Polygon.Reserve(NumVertices); + for (int j = 0; j < NumVertices; ++j) + { + FVertexInstanceID VertexInstance = MeshDescription->CreateVertexInstance(Vertices[j]); + Polygon.Add(VertexInstance); + + if (VertexUVs != nullptr) + { + InstanceUVs.Set(VertexInstance, (*VertexUVs)[j]); + } + if (VertexNormals != nullptr) + { + InstanceNormals.Set(VertexInstance, (*VertexNormals)[j]); + } + + } + + const FPolygonID NewPolygonID = MeshDescription->CreatePolygon(PolygonGroup, Polygon); + + // compute triangulation + FMeshPolygon& NewPolygon = MeshDescription->GetPolygon(NewPolygonID); + MeshDescription->ComputePolygonTriangulation(NewPolygonID, NewPolygon.Triangles); + + return NewPolygonID; +} + + + + +FPolygonID FMeshDescriptionBuilder::AppendTriangle(const FVertexInstanceID& Instance0, const FVertexInstanceID& Instance1, const FVertexInstanceID& Instance2, const FPolygonGroupID& PolygonGroup) +{ + TArray Polygon; + Polygon.Add(Instance0); + Polygon.Add(Instance1); + Polygon.Add(Instance2); + + const FPolygonID NewPolygonID = MeshDescription->CreatePolygon(PolygonGroup, Polygon); + + // compute triangulation + FMeshPolygon& NewPolygon = MeshDescription->GetPolygon(NewPolygonID); + MeshDescription->ComputePolygonTriangulation(NewPolygonID, NewPolygon.Triangles); + + return NewPolygonID; +} + + + + + +void FMeshDescriptionBuilder::SetPolyGroupID(const FPolygonID& PolygonID, int GroupID) +{ + PolyGroups.Set(PolygonID, 0, GroupID); +} + + + + + +void FMeshDescriptionBuilder::AppendMesh(const FDynamicMesh3* Mesh, bool bSetPolyGroups) +{ + // create vertices + TArray MapV; + MapV.SetNum(Mesh->MaxVertexID()); + for (int VertID : Mesh->VertexIndicesItr()) + { + FVector3d v = Mesh->GetVertex(VertID); + MapV[VertID] = AppendVertex(v); + } + + bool bDoTransferPolyGroups = false; + if (bSetPolyGroups) + { + EnablePolyGroups(); + bDoTransferPolyGroups = Mesh->HasTriangleGroups(); + } + + FPolygonGroupID AllGroupID = AppendPolygonGroup(); + + bool bHasVertexColors = Mesh->HasVertexColors(); + bool bHasVertexNormals = Mesh->HasVertexNormals(); + const FDynamicMeshUVOverlay* UVOverlay = Mesh->HasAttributes() ? Mesh->Attributes()->PrimaryUV() : nullptr; + const FDynamicMeshNormalOverlay* NormalOverlay = Mesh->HasAttributes() ? Mesh->Attributes()->PrimaryNormals() : nullptr; + //FDynamicMeshUVOverlay* UVOverlay = nullptr; + //FDynamicMeshNormalOverlay* NormalOverlay = nullptr; + + + // create new instances when seen + TMap InstanceList; + for (int TriID : Mesh->TriangleIndicesItr()) + { + // @todo support additional overlays (requires IndexNi...) + FIndex3i Triangle = Mesh->GetTriangle(TriID); + FIndex3i UVTriangle = (UVOverlay != nullptr) ? UVOverlay->GetTriangle(TriID) : FIndex3i(-1, -1, -1); + FIndex3i NormalTriangle = (NormalOverlay != nullptr) ? NormalOverlay->GetTriangle(TriID) : FIndex3i(-1, -1, -1); + FVertexInstanceID InstanceTri[3]; + for (int j = 0; j < 3; ++j) + { + FIndex3i InstanceElem(Triangle[j], UVTriangle[j], NormalTriangle[j]); + if (InstanceList.Contains(InstanceElem) == false) + { + FVertexInstanceID NewInstanceID = AppendInstance(MapV[Triangle[j]]); + InstanceList.Add(InstanceElem, NewInstanceID); + SetInstance(NewInstanceID, + (UVTriangle[j] == -1 || UVOverlay == nullptr) ? FVector2f::Zero() : UVOverlay->GetElement(UVTriangle[j]), + (NormalTriangle[j] == -1 || NormalOverlay == nullptr) ? FVector3f::UnitY() : NormalOverlay->GetElement(NormalTriangle[j])); + } + InstanceTri[j] = InstanceList[InstanceElem]; + } + + FPolygonID NewPolyID = AppendTriangle(InstanceTri[0], InstanceTri[1], InstanceTri[2], AllGroupID); + if (bDoTransferPolyGroups) + { + SetPolyGroupID(NewPolyID, Mesh->GetTriangleGroup(TriID)); + } + + if (bHasVertexColors) + { + for (int j = 0; j < 3; ++j) + { + FVector3f Color = Mesh->GetVertexColor(Triangle[j]); + FVector4 Color4(Color.X, Color.Y, Color.Z, 1.0); + SetInstanceColor(InstanceTri[j], Color4); + } + } + if (NormalOverlay == nullptr && bHasVertexNormals) + { + SetInstanceNormal(InstanceTri[0], Mesh->GetVertexNormal(Triangle[0])); + SetInstanceNormal(InstanceTri[1], Mesh->GetVertexNormal(Triangle[1])); + SetInstanceNormal(InstanceTri[2], Mesh->GetVertexNormal(Triangle[2])); + } + } +} + + + + + +void FMeshDescriptionBuilder::Translate(const FVector& Translation) +{ + for (FVertexID VertexID : MeshDescription->Vertices().GetElementIDs()) + { + FVector Position = VertexPositions.Get(VertexID); + Position += Translation; + VertexPositions.Set(VertexID, Position); + } +} + + + + +void FMeshDescriptionBuilder::SetAllEdgesHardness(bool bHard) +{ + TEdgeAttributesRef EdgeHardness = + MeshDescription->EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::IsHard); + for (FEdgeID EdgeID : MeshDescription->Edges().GetElementIDs()) + { + EdgeHardness.Set(EdgeID, 0, bHard); + } +} + + + +void FMeshDescriptionBuilder::RecalculateInstanceNormals() +{ + for (int k = 0; k < InstanceNormals.GetNumElements(); ++k) + { + InstanceNormals.Set(FVertexInstanceID(k), 0, FVector::ZeroVector); + } + + const FPolygonArray& Polygons = MeshDescription->Polygons(); + for (const FPolygonID PolygonID : Polygons.GetElementIDs()) + { + const TArray& Triangles = MeshDescription->GetPolygonTriangles(PolygonID); + for (FMeshTriangle Triangle : Triangles) + { + FVector3d A = GetPosition(Triangle.VertexInstanceID0); + FVector3d B = GetPosition(Triangle.VertexInstanceID1); + FVector3d C = GetPosition(Triangle.VertexInstanceID2); + double Area = 1.0; + FVector FaceNormal = VectorUtil::FastNormalArea(A, B, C, Area); + if (Area > FMathf::ZeroTolerance) + { + FaceNormal *= Area; + InstanceNormals.Set(Triangle.VertexInstanceID0, + InstanceNormals.Get(Triangle.VertexInstanceID0, 0) + FaceNormal); + InstanceNormals.Set(Triangle.VertexInstanceID1, + InstanceNormals.Get(Triangle.VertexInstanceID1, 0) + FaceNormal); + InstanceNormals.Set(Triangle.VertexInstanceID2, + InstanceNormals.Get(Triangle.VertexInstanceID2, 0) + FaceNormal); + } + } + } + + + for (int k = 0; k < InstanceNormals.GetNumElements(); ++k) + { + FVector SumNormal = InstanceNormals.Get(FVertexInstanceID(k), 0); + SumNormal.Normalize(); + if ( SumNormal.Size() < 0.99 ) + { + SumNormal = FVector(1, 0, 0); + } + InstanceNormals.Set(FVertexInstanceID(k), 0, SumNormal); + } +} + + + +FBox FMeshDescriptionBuilder::ComputeBoundingBox() const +{ + FAxisAlignedBox3f bounds = FAxisAlignedBox3f::Empty(); + for ( FVertexID VertexID : MeshDescription->Vertices().GetElementIDs() ) + { + bounds.Contain(VertexPositions.Get(VertexID)); + } + return bounds; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshDescriptionToDynamicMesh.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshDescriptionToDynamicMesh.cpp new file mode 100644 index 000000000000..fd209deb390f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshDescriptionToDynamicMesh.cpp @@ -0,0 +1,343 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshDescriptionToDynamicMesh.h" +#include "MeshAttributes.h" +#include "DynamicMeshAttributeSet.h" +#include "DynamicMeshOverlay.h" +#include "MeshDescriptionBuilder.h" + + +struct FVertexUV +{ + int vid; + float x; + float y; + bool operator==(const FVertexUV & o) const + { + return vid == o.vid && x == o.x && y == o.y; + } +}; +FORCEINLINE uint32 GetTypeHash(const FVertexUV& Vector) +{ + // ugh copied from FVector clearly should not be using CRC for hash!! + return FCrc::MemCrc32(&Vector, sizeof(Vector)); +} + + +class FUVWelder +{ +public: + TMap UniqueVertexUVs; + FDynamicMeshUVOverlay* UVOverlay; + + FUVWelder(FDynamicMeshUVOverlay* UVOverlayIn) + { + check(UVOverlayIn); + UVOverlay = UVOverlayIn; + } + + int FindOrAddUnique(const FVector2D & UV, int VertexID) + { + FVertexUV VertUV = { VertexID, UV.X, UV.Y }; + + int NewIndex = -1; + if (UniqueVertexUVs.Contains(VertUV)) + { + NewIndex = UniqueVertexUVs[VertUV]; + } + else + { + NewIndex = UVOverlay->AppendElement(UV, VertexID); + UniqueVertexUVs.Add(VertUV, NewIndex); + } + return NewIndex; + } +}; + + + + +struct FVertexNormal +{ + int vid; + float x; + float y; + float z; + bool operator==(const FVertexNormal & o) const + { + return vid == o.vid && x == o.x && y == o.y && z == o.z; + } +}; +FORCEINLINE uint32 GetTypeHash(const FVertexNormal& Vector) +{ + // ugh copied from FVector clearly should not be using CRC for hash!! + return FCrc::MemCrc32(&Vector, sizeof(Vector)); +} + + +class FNormalWelder +{ +public: + TMap UniqueVertexNormals; + FDynamicMeshNormalOverlay* NormalOverlay; + + FNormalWelder(FDynamicMeshNormalOverlay* NormalOverlayIn) + { + check(NormalOverlayIn); + NormalOverlay = NormalOverlayIn; + } + + int FindOrAddUnique(const FVector & Normal, int VertexID) + { + FVertexNormal VertNormal = { VertexID, Normal.X, Normal.Y, Normal.Z }; + + int NewIndex = -1; + if (UniqueVertexNormals.Contains(VertNormal)) + { + NewIndex = UniqueVertexNormals[VertNormal]; + } + else + { + NewIndex = NormalOverlay->AppendElement(Normal, VertexID); + UniqueVertexNormals.Add(VertNormal, NewIndex); + } + return NewIndex; + } +}; + + + + + + + + + + + + + +void FMeshDescriptionToDynamicMesh::Convert(const FMeshDescription* MeshIn, FDynamicMesh3& MeshOut) +{ + // look up vertex positions + const FVertexArray& VertexIDs = MeshIn->Vertices(); + TVertexAttributesConstRef VertexPositions = + MeshIn->VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); + + // copy vertex positions + for (const FVertexID VertexID : VertexIDs.GetElementIDs()) + { + const FVector Position = VertexPositions.Get(VertexID); + int NewVertIdx = MeshOut.AppendVertex(Position); + check(NewVertIdx == VertexID.GetValue()); + } + + // look up vertex-instance UVs and normals + // @todo: does the MeshDescription always have UVs and Normals? + TVertexInstanceAttributesConstRef InstanceUVs = + MeshIn->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); + TVertexInstanceAttributesConstRef InstanceNormals = + MeshIn->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); + + // enable attributes on output mesh + MeshOut.EnableAttributes(); + FDynamicMeshUVOverlay* UVOverlay = MeshOut.Attributes()->PrimaryUV(); + FDynamicMeshNormalOverlay* NormalOverlay = MeshOut.Attributes()->PrimaryNormals(); + + TPolygonAttributesConstRef PolyGroups = + MeshIn->PolygonAttributes().GetAttributesRef(ExtendedMeshAttribute::PolyTriGroups); + + // base triangle groups will track polygons + if (bEnableOutputGroups) + { + MeshOut.EnableTriangleGroups(0); + } + + int NumVertices = MeshIn->Vertices().Num(); + int NumPolygons = MeshIn->Polygons().Num(); + int NumVtxInstances = MeshIn->VertexInstances().Num(); + if (bPrintDebugMessages) + { + UE_LOG(LogTemp, Warning, TEXT("FMeshDescriptionToDynamicMesh: MeshDescription verts %d polys %d instances %d"), NumVertices, NumPolygons, NumVtxInstances); + } + + // reserve space in MeshOut? + + // used to merge coincident elements so that we get actual topology + FUVWelder UVWelder(UVOverlay); + FNormalWelder NormalWelder(NormalOverlay); + + const FPolygonArray& Polygons = MeshIn->Polygons(); + for (const FPolygonID PolygonID : Polygons.GetElementIDs()) + { + FPolygonGroupID PolygonGroupID = MeshIn->GetPolygonPolygonGroup(PolygonID); + + const TArray& Triangles = MeshIn->GetPolygonTriangles(PolygonID); + int NumTriangles = Triangles.Num(); + for ( int TriIdx = 0; TriIdx < NumTriangles; ++TriIdx) + { + const FMeshTriangle& Triangle = Triangles[TriIdx]; + + FVertexInstanceID InstanceTri[3]; + InstanceTri[0] = Triangle.VertexInstanceID0; + InstanceTri[1] = Triangle.VertexInstanceID1; + InstanceTri[2] = Triangle.VertexInstanceID2; + + int GroupID = 0; + if (GroupMode == EPrimaryGroupMode::SetToPolyGroup) + { + if (PolyGroups.IsValid()) + { + GroupID = PolyGroups.Get(PolygonID, 0); + } + } + else if (GroupMode == EPrimaryGroupMode::SetToPolygonID) + { + GroupID = PolygonID.GetValue(); + } + else if (GroupMode == EPrimaryGroupMode::SetToPolygonGroupID) + { + GroupID = PolygonGroupID.GetValue(); + } + + + // append triangle + int VertexID0 = MeshIn->GetVertexInstance(Triangle.VertexInstanceID0).VertexID.GetValue(); + int VertexID1 = MeshIn->GetVertexInstance(Triangle.VertexInstanceID1).VertexID.GetValue(); + int VertexID2 = MeshIn->GetVertexInstance(Triangle.VertexInstanceID2).VertexID.GetValue(); + int NewTriangleID = MeshOut.AppendTriangle(VertexID0, VertexID1, VertexID2, GroupID); + + // if append failed due to non-manifold, duplicate verts + if (NewTriangleID == FDynamicMesh3::NonManifoldID) + { + int e0 = MeshOut.FindEdge(VertexID0, VertexID1); + int e1 = MeshOut.FindEdge(VertexID1, VertexID2); + int e2 = MeshOut.FindEdge(VertexID2, VertexID0); + + // determine which verts need to be duplicated + bool bDuplicate[3] = { false, false, false }; + if (e0 != FDynamicMesh3::InvalidID && MeshOut.IsBoundaryEdge(e0) == false) + { + bDuplicate[0] = true; + bDuplicate[1] = true; + } + if (e1 != FDynamicMesh3::InvalidID && MeshOut.IsBoundaryEdge(e1) == false) + { + bDuplicate[1] = true; + bDuplicate[2] = true; + } + if (e2 != FDynamicMesh3::InvalidID && MeshOut.IsBoundaryEdge(e2) == false) + { + bDuplicate[2] = true; + bDuplicate[0] = true; + } + if (bDuplicate[0]) + { + FVertexID VertexID = MeshIn->GetVertexInstance(Triangle.VertexInstanceID0).VertexID; + const FVector Position = VertexPositions.Get(VertexID); + int NewVertIdx = MeshOut.AppendVertex(Position); + VertexID0 = NewVertIdx; + } + if (bDuplicate[1]) + { + FVertexID VertexID = MeshIn->GetVertexInstance(Triangle.VertexInstanceID1).VertexID; + const FVector Position = VertexPositions.Get(VertexID); + int NewVertIdx = MeshOut.AppendVertex(Position); + VertexID1 = NewVertIdx; + } + if (bDuplicate[2]) + { + FVertexID VertexID = MeshIn->GetVertexInstance(Triangle.VertexInstanceID2).VertexID; + const FVector Position = VertexPositions.Get(VertexID); + int NewVertIdx = MeshOut.AppendVertex(Position); + VertexID2 = NewVertIdx; + } + + NewTriangleID = MeshOut.AppendTriangle(VertexID0, VertexID1, VertexID2, GroupID); + checkSlow(NewTriangleID != FDynamicMesh3::NonManifoldID); + } + + FIndex3i Tri(VertexID0, VertexID1, VertexID2); + + if (bCalculateMaps) + { + TriToPolyTriMap.Insert(FIndex2i(PolygonID.GetValue(), TriIdx), NewTriangleID); + } + + if (UVOverlay != nullptr) + { + FIndex3i TriUV; + for (int j = 0; j < 3; ++j) + { + FVector2D UV = InstanceUVs.Get(InstanceTri[j]); + TriUV[j] = UVWelder.FindOrAddUnique(UV, Tri[j]); + } + UVOverlay->SetTriangle(NewTriangleID, TriUV); + } + + if (NormalOverlay != nullptr) + { + FIndex3i TriNormals; + for (int j = 0; j < 3; ++j) + { + FVector Normal = InstanceNormals.Get(InstanceTri[j]); + TriNormals[j] = NormalWelder.FindOrAddUnique(Normal, Tri[j]); + } + NormalOverlay->SetTriangle(NewTriangleID, TriNormals); + } + + } + } + + if (bPrintDebugMessages) + { + int NumUVs = (UVOverlay != nullptr) ? UVOverlay->MaxElementID() : 0; + int NumNormals = (NormalOverlay != nullptr) ? NormalOverlay->MaxElementID() : 0; + UE_LOG(LogTemp, Warning, TEXT("FMeshDescriptionToDynamicMesh: FDynamicMesh verts %d triangles %d uvs %d normals %d"), MeshOut.MaxVertexID(), MeshOut.MaxTriangleID(), NumUVs, NumNormals); + } + +} + + + + + +void FMeshDescriptionToDynamicMesh::CopyTangents(const FMeshDescription* SourceMesh, const FDynamicMesh3* TargetMesh, FMeshTangentsf& TangentsOut) +{ + check(bCalculateMaps == true); + check(TriToPolyTriMap.Num() == TargetMesh->TriangleCount()); + + TVertexInstanceAttributesConstRef InstanceNormals = + SourceMesh->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); + TVertexInstanceAttributesConstRef InstanceTangents = + SourceMesh->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); + TVertexInstanceAttributesConstRef InstanceSigns = + SourceMesh->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); + check(InstanceNormals.IsValid()); + check(InstanceTangents.IsValid()); + check(InstanceSigns.IsValid()); + + TangentsOut.SetMesh(TargetMesh); + TangentsOut.InitializePerTriangleTangents(false); + + for (int TriID : TargetMesh->TriangleIndicesItr()) + { + FIndex2i PolyTriIdx = TriToPolyTriMap[TriID]; + const TArray& Triangles = SourceMesh->GetPolygonTriangles( FPolygonID(PolyTriIdx.A) ); + const FMeshTriangle& Triangle = Triangles[PolyTriIdx.B]; + FVertexInstanceID InstanceTri[3]; + InstanceTri[0] = Triangle.VertexInstanceID0; + InstanceTri[1] = Triangle.VertexInstanceID1; + InstanceTri[2] = Triangle.VertexInstanceID2; + for (int j = 0; j < 3; ++j) + { + FVector Normal = InstanceNormals.Get(InstanceTri[j], 0); + FVector Tangent = InstanceTangents.Get(InstanceTri[j], 0); + float BinormalSign = InstanceSigns.Get(InstanceTri[j], 0); + FVector3f Bitangent = VectorUtil::Binormal((FVector3f)Normal, (FVector3f)Tangent, (float)BinormalSign); + Tangent.Normalize(); Bitangent.Normalize(); + TangentsOut.SetPerTriangleTangent(TriID, j, Tangent, Bitangent); + } + } + +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/DynamicMeshToMeshDescription.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/DynamicMeshToMeshDescription.h new file mode 100644 index 000000000000..d5527a81ad64 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/DynamicMeshToMeshDescription.h @@ -0,0 +1,56 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DynamicMesh3.h" +#include "MeshDescription.h" + +/** + * Convert FDynamicMesh3 to FMeshDescription + * + */ +class MESHCONVERSION_API FDynamicMeshToMeshDescription +{ +public: + /** If true, will print some possibly-helpful debugging spew to output log */ + bool bPrintDebugMessages = false; + + /** Should DynamicMesh triangle groups be transfered to MeshDescription via custom PolyTriGroups attribute */ + bool bSetPolyGroups = true; + + /** + * Default conversion of DynamicMesh to MeshDescription. Calls functions below depending on mesh state + */ + void Convert(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut); + + + /** + * Update existing MeshDescription based on DynamicMesh. Assumes mesh topology has not changed. + * Copies positions, recalculates MeshDescription normals. + */ + void Update(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut); + + + // + // Internal functions that you can also call directly + // + + /** + * Ignore any Attributes on input Mesh, calculate per-vertex normals and have MeshDescription compute tangents. + * One VertexInstance per input vertex is generated + */ + void Convert_NoAttributes(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut); + + /** + * Convert while minimizing VertexInstance count, IE new VertexInstances are only created + * if a unique UV or Normal is required. + */ + void Convert_SharedInstances(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut); + + /** + * Convert with no shared VertexInstances. A new VertexInstance is created for + * each triangle vertex (ie corner). However vertex positions are shared. + */ + void Convert_NoSharedInstances(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut); +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshConversionModule.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshConversionModule.h new file mode 100644 index 000000000000..5f7d52d66178 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshConversionModule.h @@ -0,0 +1,15 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FMeshConversionModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshDescriptionBuilder.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshDescriptionBuilder.h new file mode 100644 index 000000000000..4f4e8a704772 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshDescriptionBuilder.h @@ -0,0 +1,117 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "MeshDescription.h" + +class FDynamicMesh3; + + +/** + * These attributes are used to store custom modeling tools data on a MeshDescription + */ +namespace ExtendedMeshAttribute +{ + extern MESHCONVERSION_API const FName PolyTriGroups; +} + + +/** + * Utility class to construct MeshDescription instances + */ +class MESHCONVERSION_API FMeshDescriptionBuilder +{ +public: + void SetMeshDescription(FMeshDescription* Description); + + /** Append vertex and return new vertex ID */ + FVertexID AppendVertex(const FVector& Position); + + /** Return position of vertex */ + FVector GetPosition(const FVertexID& VertexID); + + /** Return position of vertex parent of instance */ + FVector GetPosition(const FVertexInstanceID& InstanceID); + + /** Set the position of a vertex */ + void SetPosition(const FVertexID& VertexID, const FVector& NewPosition); + + + + /** Append new vertex instance and return ID */ + FVertexInstanceID AppendInstance(const FVertexID& VertexID); + + /** Set the UV and Normal of a vertex instance*/ + void SetInstance(const FVertexInstanceID& InstanceID, const FVector2D& InstanceUV, const FVector& InstanceNormal); + + /** Set the Normal of a vertex instance*/ + void SetInstanceNormal(const FVertexInstanceID& InstanceID, const FVector& Normal); + + /** Set the Color of a vertex instance*/ + void SetInstanceColor(const FVertexInstanceID& InstanceID, const FVector4& Color); + + /** Enable per-triangle integer attribute named PolyTriGroups */ + void EnablePolyGroups(); + + /** Create a new polygon group and return it's ID */ + FPolygonGroupID AppendPolygonGroup(); + + /** Set the PolyTriGroups attribute value to a specific GroupID for a Polygon */ + void SetPolyGroupID(const FPolygonID& PolygonID, int GroupID); + + + + /** Append a triangle to the mesh with the given PolygonGroup ID */ + FPolygonID AppendTriangle(const FVertexID& Vertex0, const FVertexID& Vertex1, const FVertexID& Vertex2, const FPolygonGroupID& PolygonGroup); + + /** Append a triangle to the mesh with the given PolygonGroup ID, and optionally with triangle-vertex UVs and Normals */ + FPolygonID AppendTriangle(const FVertexID* Triangle, const FPolygonGroupID& PolygonGroup, + const FVector2D* VertexUVs = nullptr, const FVector* VertexNormals = nullptr); + + /** + * Append an arbitrary polygon to the mesh with the given PolygonGroup ID, and optionally with polygon-vertex UVs and Normals + * Unique Vertex instances will be created for each polygon-vertex. + */ + FPolygonID AppendPolygon(const TArray& Vertices, const FPolygonGroupID& PolygonGroup, + const TArray* VertexUVs = nullptr, const TArray* VertexNormals = nullptr); + + /** + * Append a triangle to the mesh using the given vertex instances and PolygonGroup ID + */ + FPolygonID AppendTriangle(const FVertexInstanceID& Instance0, const FVertexInstanceID& Instance1, const FVertexInstanceID& Instance2, const FPolygonGroupID& PolygonGroup); + + + /** + * Append an entire mesh to this mesh. This will create a minimal set of shared vertex instances. + * @param bSetPolyGroups if true, we transfer the input mesh triangle groups to the PolyTriGroups attribute + */ + void AppendMesh(const FDynamicMesh3* Mesh, bool bSetPolyGroups); + + + + /** Set MeshAttribute::Edge::IsHard to true for all edges */ + void SetAllEdgesHardness(bool bHard); + + /** Translate the MeshDescription vertex positions */ + void Translate(const FVector& Translation); + + /** Calculate normals on the MeshDescription vertex instances, using area-weighted face average */ + void RecalculateInstanceNormals(); + + /** Return the current bounding box of the mesh */ + FBox ComputeBoundingBox() const; + +protected: + FMeshDescription* MeshDescription; + + TVertexAttributesRef VertexPositions; + TVertexInstanceAttributesRef InstanceUVs; + TVertexInstanceAttributesRef InstanceNormals; + TVertexInstanceAttributesRef InstanceColors; + TArray TempBuffer; + TArray UVBuffer; + TArray NormalBuffer; + + TPolygonAttributesRef PolyGroups; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshDescriptionToDynamicMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshDescriptionToDynamicMesh.h new file mode 100644 index 000000000000..6b501aca37c7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshDescriptionToDynamicMesh.h @@ -0,0 +1,60 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DynamicMesh3.h" +#include "MeshTangents.h" +#include "MeshDescription.h" + +/** + * Convert FMeshDescription to FDynamicMesh3 + * + * @todo handle missing UV/normals on MD? + * @todo be able to ignore UV/Normals + * @todo handle additional UV layers on MD + * @todo option to disable UV/Normal welding + */ +class MESHCONVERSION_API FMeshDescriptionToDynamicMesh +{ +public: + /** If true, will print some possibly-helpful debugging spew to output log */ + bool bPrintDebugMessages = false; + + /** Should we initialize triangle groups on output mesh */ + bool bEnableOutputGroups = true; + + /** Should we calculate conversion index maps */ + bool bCalculateMaps = true; + + /** map from triangle ID to (polygon,triangle) pair */ + TArray TriToPolyTriMap; + + + /** + * Various modes can be used to create output triangle groups + */ + enum class EPrimaryGroupMode + { + SetToZero, + SetToPolygonID, + SetToPolygonGroupID, + SetToPolyGroup + }; + /** + * Which mode to use to create groups on output mesh. Ignored if bEnableOutputGroups = false. + */ + EPrimaryGroupMode GroupMode = EPrimaryGroupMode::SetToPolyGroup; + + + /** + * Default conversion of MeshDescription to DynamicMesh + */ + void Convert(const FMeshDescription* MeshIn, FDynamicMesh3& MeshOut); + + /** + * Copy tangents from MeshDescription to a FMeshTangents instance. + * @warning Convert() must have been used to create the TargetMesh before calling this function + */ + void CopyTangents(const FMeshDescription* SourceMesh, const FDynamicMesh3* TargetMesh, FMeshTangentsf& TangentsOut); +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/MeshSolverUtilities.Build.cs b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/MeshSolverUtilities.Build.cs new file mode 100644 index 000000000000..a9b684b3bbe1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/MeshSolverUtilities.Build.cs @@ -0,0 +1,34 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class MeshSolverUtilities : ModuleRules +{ + public MeshSolverUtilities(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + //PCHUsage = ModuleRules.PCHUsageMode.NoSharedPCHs; + + PublicDependencyModuleNames.AddRange( + new string[] { + "Core", + "Eigen" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "GeometricObjects", + "DynamicMesh" + //"GeometricObjects", + //"DynamicMesh" + //"Slate", + //"SlateCore", + // ... add private dependencies that you statically link with here ... + } + ); + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/ConstrainedPoissonSolver.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/ConstrainedPoissonSolver.h new file mode 100644 index 000000000000..4c9c6b73b38e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/ConstrainedPoissonSolver.h @@ -0,0 +1,348 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DynamicMesh3.h" +#include "FSOAPositions.h" +#include "FSparseMatrixD.h" + +#include "MatrixSolver.h" + + + + + +#include "ProfilingDebugging/ScopedTimers.h" +#include "Async/ParallelFor.h" + + + +/** +* Least Squares constrained poisson solver. +* +* Where L is a Laplacian type matrix +* {lambda_i, cvec_i} are k weights (lambda) and positional constraints. +* +* Solves in the lease squares sense the equation +* L p_vec = d_vec +* lambda_i * (p_vec)_i = lambda_i (c_vec)_i for some constraints. +* +* this amounts to +* ( Transpose(L) * L + (0 0 ) ) p_vec = source_vec + ( 0 ) +* ( (0 lambda^2) ) ( lambda^2 c_vec ) +* +* where source_vec = Transpose(L)*L p_vec +* +* C.F. http://sites.fas.harvard.edu/~cs277/papers/deformation_survey.pdf section V, subsection C +* +* @todo consider reworking to hold TUniquePtr<> to the various objects. +*/ + +class FConstrainedSolver +{ +public: + + typedef typename FSparseMatrixD::Scalar ScalarType; + typedef typename FSOAPositions::VectorType VectorType; + + + + struct FConstraintPosition + { + FConstraintPosition(const FVector3d& P, bool b) : Position(P), bPostFix(b) {} + + FVector3d Position; + bool bPostFix; + }; + + + FConstrainedSolver(TUniquePtr& SymmatrixMatrixOperator, const EMatrixSolverType MatrixSolverType) + : ConstraintPositions(SymmatrixMatrixOperator->cols()) + { + SymmetricMatrixPtr.Reset(SymmatrixMatrixOperator.Release()); + + bMatrixSolverDirty = true; + MatrixSolver = ContructMatrixSolver(MatrixSolverType); + } + + + // Updates the diagonal weights matrix. + void SetConstraintWeights(const TMap& WeightMap) + { + typedef FSparseMatrixD::Scalar ScalarT; + typedef Eigen::Triplet MatrixTripletT; + + ClearWeights(); + + std::vector MatrixTripelList; + MatrixTripelList.reserve(WeightMap.Num()); + + for (const auto& WeightPair : WeightMap) + { + const int32 i = WeightPair.Key; // row id + double Weight = WeightPair.Value; + + checkSlow(i < SymmetricMatrixPtr->cols()); + + // the soft constrained system uses the square of the weight. + Weight *= Weight; + + //const int32 i = ToIndex[VertId]; + MatrixTripelList.push_back(MatrixTripletT(i, i, Weight)); + + } + + // Construct matrix with weights on the diagonal for the constrained verts ( and zero everywhere else) + WeightsSqrdMatrix.setFromTriplets(MatrixTripelList.begin(), MatrixTripelList.end()); + WeightsSqrdMatrix.makeCompressed(); + + // The solver matrix will have to be updated and re-factored + UpdateSolverWithContraints(); + } + + // Updates the positional source term + void SetContraintPositions(const TMap& PositionMap) + { + ClearConstraintPositions(); + + for (const auto& PositionPair : PositionMap) + { + const int32 i = PositionPair.Key; // row id + const FVector3d& Pos = PositionPair.Value.Position; + + checkSlow(i < SymmetricMatrixPtr->cols()); + + + // constrained source vector + ConstraintPositions.XVector[i] = Pos.X; + ConstraintPositions.YVector[i] = Pos.Y; + ConstraintPositions.ZVector[i] = Pos.Z; + + } + } + + ScalarType GetWeightSqrd(int32 VtxIndex) const + { + return WeightsSqrdMatrix.coeff(VtxIndex, VtxIndex); + } + + /** + * Access to the underlying solver + */ + IMatrixSolverBase* GetMatrixSolverBase() { return MatrixSolver.Get(); } + + const IMatrixSolverBase* GetMatrixSolverBase() const { return MatrixSolver.Get(); } + + /** + * Access to the underlying solver in iterative form - will return null if the solver is not iterative. + */ + IIterativeMatrixSolverBase* GetMatrixSolverIterativeBase() + { + IIterativeMatrixSolverBase* IterativeBasePtr = NULL; + if (MatrixSolver->bIsIterative()) + { + IterativeBasePtr = (IIterativeMatrixSolverBase*)MatrixSolver.Get(); + } + return IterativeBasePtr; + } + const IIterativeMatrixSolverBase* GetMatrixSolverIterativeBase() const + { + const IIterativeMatrixSolverBase* IterativeBasePtr = NULL; + if (MatrixSolver->bIsIterative()) + { + IterativeBasePtr = (const IIterativeMatrixSolverBase*)MatrixSolver.Get(); + } + return IterativeBasePtr; + } + + /** + * @param SourceVector - Required that each component vector has size equal to the the number of columns in the laplacian + * @param SolutionVector - the resulting solution to the least squares problem + */ + bool Solve(const FSOAPositions& SourceVector, FSOAPositions& SolutionVector) const + { + checkSlow(bMatrixSolverDirty == false); + FScopedDurationTimeLogger Timmer(TEXT("Post-setup solve time")); + + FSparseMatrixD& SymmetricMatrix = *SymmetricMatrixPtr; + // Set up the source vector + FSOAPositions RHSVector(SymmetricMatrix.cols()); + for (int32 Dir = 0; Dir < 3; ++Dir) + { + RHSVector.Array(Dir) = SourceVector.Array(Dir) + WeightsSqrdMatrix * ConstraintPositions.Array(Dir); + } + + MatrixSolver->Solve(RHSVector, SolutionVector); + + bool bSuccess = MatrixSolver->bSucceeded(); + + return bSuccess; + } + + + /** + * Special case when the source vector is identically zero. + * NB: this is used for region smoothing. + */ + bool Solve(FSOAPositions& SolutionVector) const + { + checkSlow(bMatrixSolverDirty == false); + + FScopedDurationTimeLogger Timmer(TEXT("Post-setup solve time")); + + FSparseMatrixD& SymmetricMatrix = *SymmetricMatrixPtr; + FSOAPositions RHSVector(SymmetricMatrix.cols()); + for (int32 Dir = 0; Dir < 3; ++Dir) + { + RHSVector.Array(Dir) = WeightsSqrdMatrix * ConstraintPositions.Array(Dir); + } + + MatrixSolver->Solve(RHSVector, SolutionVector); + + bool bSuccess = MatrixSolver->bSucceeded(); + return bSuccess; + } + + /* + * For use with iterative solvers. + * + * Reverts to Solve(SolutionVector); + * if the matrix solver type is direct. + */ + bool SolveWithGuess(const FSOAPositions& Guess, FSOAPositions& SolutionVector) const + { + checkSlow(bMatrixSolverDirty == false); + + if (MatrixSolver->bIsIterative()) + { + const IIterativeMatrixSolverBase* IterativeSolver = (IIterativeMatrixSolverBase*)MatrixSolver.Get(); + + FScopedDurationTimeLogger Timmer(TEXT("Post-setup solve time")); + + FSparseMatrixD& SymmetricMatrix = *SymmetricMatrixPtr; + FSOAPositions RHSVector(SymmetricMatrix.cols()); + for (int32 Dir = 0; Dir < 3; ++Dir) + { + RHSVector.Array(Dir) = WeightsSqrdMatrix * ConstraintPositions.Array(Dir); + } + + IterativeSolver->SolveWithGuess(Guess, RHSVector, SolutionVector); + + bool bSuccess = IterativeSolver->bSucceeded(); + return bSuccess; + } + else + { + return Solve(SolutionVector); + } + } + + /** + * For use with iterative solvers. + * + * Reverts to Solve(SourceVector, SolutionVector); + * if the matrix solver type is direct + */ + bool SolveWithGuess(const FSOAPositions& Guess, const FSOAPositions& SourceVector, FSOAPositions& SolutionVector) const + { + checkSlow(bMatrixSolverDirty == false); + + if (MatrixSolver->bIsIterative()) + { + const IIterativeMatrixSolverBase* IterativeSolver = (IIterativeMatrixSolverBase*)MatrixSolver.Get(); + + FScopedDurationTimeLogger Timmer(TEXT("Post-setup solve time")); + + FSparseMatrixD& SymmetricMatrix = *SymmetricMatrixPtr; + // Set up the source vector + FSOAPositions RHSVector(SymmetricMatrix.cols()); + for (int32 Dir = 0; Dir < 3; ++Dir) + { + RHSVector.Array(Dir) = SourceVector.Array(Dir) + WeightsSqrdMatrix * ConstraintPositions.Array(Dir); + } + + IterativeSolver->SolveWithGuess(Guess, RHSVector, SolutionVector); + + bool bSuccess = IterativeSolver->bSucceeded(); + + return bSuccess; + } + else + { + return Solve(SourceVector, SolutionVector); + } + } + + + const FSparseMatrixD& Biharmonic() const { return *SymmetricMatrixPtr; } + + +protected: + + // Zero the diagonal matrix that holds constraints + void ClearConstraints() + { + ClearConstraintPositions(); + ClearWeights(); + } + + void ClearConstraintPositions() + { + const FSparseMatrixD& SymmetricMatrix = *SymmetricMatrixPtr; + const int32 NumColumns = SymmetricMatrix.cols(); + + ConstraintPositions.SetZero(NumColumns); + + } + void ClearWeights() + { + WeightsSqrdMatrix.setZero(); + + WeightsSqrdMatrix.resize(SymmetricMatrixPtr->rows(), SymmetricMatrixPtr->cols()); + + // The constraints are part of the matrix + bMatrixSolverDirty = true; + } + + // Note: If constraints haven't been set, or if constraints have been cleared, this must be called prior to solve. + void UpdateSolverWithContraints() + { + FScopedDurationTimeLogger Timmer(TEXT("Matrix setup time")); + + // The constrained verts are part of the matrix + + FSparseMatrixD& SymmetricMatrix = *SymmetricMatrixPtr; + + + LHSMatrix = SymmetricMatrix + WeightsSqrdMatrix; + + LHSMatrix.makeCompressed(); + // This matrix by construction is symmetric + const bool bIsSymmetric = true; + + + MatrixSolver->Reset(); + + MatrixSolver->SetUp(LHSMatrix, bIsSymmetric); + + bMatrixSolverDirty = false; + } + +private: + + + // Positional Constraint Arrays + FSOAPositions ConstraintPositions; + + TUniquePtr SymmetricMatrixPtr; // Transpose(Laplacian) * Laplacain + FSparseMatrixD WeightsSqrdMatrix; // Weights Squared Diagonal Matrix. + + FSparseMatrixD LHSMatrix; + + bool bMatrixSolverDirty; + TUniquePtr MatrixSolver; + + +}; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/FSOAPositions.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/FSOAPositions.h new file mode 100644 index 000000000000..7f0742cd21a6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/FSOAPositions.h @@ -0,0 +1,79 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#if defined(_MSC_VER) && USING_CODE_ANALYSIS +#pragma warning(push) +#pragma warning(disable : 6011) +#pragma warning(disable : 6387) +#pragma warning(disable : 6313) +#pragma warning(disable : 6294) +#endif +THIRD_PARTY_INCLUDES_START +#include +THIRD_PARTY_INCLUDES_END +#if defined(_MSC_VER) && USING_CODE_ANALYSIS +#pragma warning(pop) +#endif + +#include "FSparseMatrixD.h" + +/** +* A struct of arrays representation used to hold vertex positions +* in three vectors that can interface with the eigen library +*/ +class FSOAPositions +{ +public: + typedef typename FSparseMatrixD::Scalar ScalarType; + typedef Eigen::Matrix VectorType; + + + FSOAPositions(int32 Size) + : XVector(Size) + , YVector(Size) + , ZVector(Size) + {} + + FSOAPositions() + {} + + VectorType& Array(int32 i) + { + return (i == 0) ? XVector : (i == 1) ? YVector : ZVector; + } + const VectorType& Array(int32 i) const + { + return (i == 0) ? XVector : (i == 1) ? YVector : ZVector; + } + + VectorType XVector; + VectorType YVector; + VectorType ZVector; + + void SetZero(int32 NumElements) + { + XVector.setZero(NumElements); + YVector.setZero(NumElements); + ZVector.setZero(NumElements); + } + + // Test that all the arrays have the same given size. + bool bHasSize(int32 Size) const + { + return (XVector.rows() == Size && YVector.rows() == Size && ZVector.rows() == Size); + } + + int32 Num() const + { + int32 Size = XVector.rows(); + if (!bHasSize(Size)) + { + Size = -1; + } + return Size; + } + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/FSparseMatrixD.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/FSparseMatrixD.h new file mode 100644 index 000000000000..aed12d855b17 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/FSparseMatrixD.h @@ -0,0 +1,33 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +// According to http://eigen.tuxfamily.org/index.php?title=Main_Page +// SimplicialCholesky, AMD ordering, and constrained_cg are disabled. + +#ifndef EIGEN_MPL2_ONLY +#define EIGEN_MPL2_ONLY +#endif + +#if defined(_MSC_VER) && USING_CODE_ANALYSIS +#pragma warning(push) +#pragma warning(disable : 6011) +#pragma warning(disable : 6387) +#pragma warning(disable : 6313) +#pragma warning(disable : 6294) +#endif +THIRD_PARTY_INCLUDES_START +#include +THIRD_PARTY_INCLUDES_END +#if defined(_MSC_VER) && USING_CODE_ANALYSIS +#pragma warning(pop) +#endif + + +// NB: The LU solver likes ColMajor but the CG sovler likes RowMajor +// Also, to change everything to float / double just change the scalar type here + +typedef Eigen::SparseMatrix FSparseMatrixD; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianMeshSmoother.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianMeshSmoother.cpp new file mode 100644 index 000000000000..36e518781318 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianMeshSmoother.cpp @@ -0,0 +1,695 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "LaplacianMeshSmoother.h" + +#include "FSOAPositions.h" + +#include "LaplacianOperators.h" +#include "MatrixSolver.h" +#include "MeshSmoothingUtilities.h" + +#include "ProfilingDebugging/ScopedTimers.h" +#include "Async/ParallelFor.h" + + +DEFINE_LOG_CATEGORY_STATIC(LogMeshSmoother, Log, All); + + +double ComputeDistSqrd(const FSOAPositions& VecA, const FSOAPositions& VecB) +{ + const int32 NumA = VecA.Num(); + const int32 NumB = VecB.Num(); + checkSlow(NumA == NumB); + +#if 0 + // the eigne way? + double Tmp = (VecA.Array(0) - VecB.Array(0)).dot(VecA.Array(0) - VecB.Array(0)) + + (VecA.Array(1) - VecB.Array(1)).dot(VecA.Array(1) - VecB.Array(1)) + + (VecA.Array(2) - VecB.Array(2)).dot(VecA.Array(2) - VecB.Array(2)); + +#endif + + // doing it by hand so we can break when the error is large + double DistSqrd = 0.; + { + const auto& AX = VecA.Array(0); + const auto& AY = VecA.Array(1); + const auto& AZ = VecA.Array(2); + + const auto& BX = VecB.Array(0); + const auto& BY = VecB.Array(1); + const auto& BZ = VecB.Array(2); + + + for (int32 i = 0; i < NumA; ++i) + { + double TmpX = AX(i)-BX(i); + double TmpY = AY(i)-BY(i); + double TmpZ = AZ(i)-BZ(i); + + TmpX *= TmpX; + TmpY *= TmpY; + TmpZ *= TmpZ; + double TmpT = TmpX + TmpY + TmpZ; + DistSqrd += TmpT; + } + } + return DistSqrd; +} + + + +FConstrainedMeshOperator::FConstrainedMeshOperator(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme, const EMatrixSolverType MatrixSolverType) + : VertexCount(DynamicMesh.VertexCount()) +{ + + Laplacian = ConstructLaplacian(Scheme, DynamicMesh, VtxLinearization, &EdgeVerts); + FSparseMatrixD& LMatrix = *(Laplacian); + + TUniquePtr LTL(new FSparseMatrixD(Laplacian->rows(), Laplacian->cols())); + FSparseMatrixD& LTLMatrix = *(LTL); + + bool bIsLaplacianSymmetric = (Scheme == ELaplacianWeightScheme::Valence || Scheme == ELaplacianWeightScheme::Uniform); + + if (bIsLaplacianSymmetric) + { + // Laplacian is symmetric, i.e. equal to its transpose + LTLMatrix = LMatrix * LMatrix; + + ConstrainedSolver.Reset(new FConstrainedSolver(LTL, MatrixSolverType)); + } + else + { + // the laplacian + LTLMatrix = LMatrix.transpose() * LMatrix; + ConstrainedSolver.Reset(new FConstrainedSolver(LTL, MatrixSolverType)); + } + +} + +void FConstrainedMeshOperator::AddConstraint(const int32 VtxId, const double Weight, const FVector3d& Pos, const bool bPostFix) +{ + + bConstraintPositionsDirty = true; + bConstraintWeightsDirty = true; + int32 Index = VtxLinearization.ToIndex()[VtxId]; + + ConstraintPositionMap.Add(TTuple(Index, FConstraintPosition(Pos, bPostFix))); + ConstraintWeightMap.Add(TTuple(Index, Weight)); + +} + + +bool FConstrainedMeshOperator::UpdateConstraintPosition(const int32 VtxId, const FVector3d& Pos, const bool bPostFix) +{ + bConstraintPositionsDirty = true; + int32 Index = VtxLinearization.ToIndex()[VtxId]; + // Add should over-write any existing value for this key + ConstraintPositionMap.Add(TTuple(Index, FConstraintPosition(Pos, bPostFix))); + + return ConstraintWeightMap.Contains(VtxId); +} + +bool FConstrainedMeshOperator::UpdateConstraintWeight(const int32 VtxId, const double Weight) +{ + + bConstraintWeightsDirty = true; + int32 Index = VtxLinearization.ToIndex()[VtxId]; + // Add should over-write any existing value for this key + ConstraintWeightMap.Add(TTuple(Index, Weight)); + + return ConstraintPositionMap.Contains(VtxId); +} + + +bool FConstrainedMeshOperator::IsConstrained(const int32 VtxId) const +{ + if (VtxId > VtxLinearization.ToIndex().Num()) return false; + + int32 Index = VtxLinearization.ToIndex()[VtxId]; + + return ConstraintWeightMap.Contains(Index); +} + +void FConstrainedMeshOperator::UpdateSolverConstraints() +{ + if (bConstraintWeightsDirty) + { + ConstrainedSolver->SetConstraintWeights(ConstraintWeightMap); + bConstraintWeightsDirty = false; + } + + if (bConstraintPositionsDirty) + { + ConstrainedSolver->SetContraintPositions(ConstraintPositionMap); + bConstraintPositionsDirty = false; + } +} + +bool FConstrainedMeshOperator::Linearize(const FSOAPositions& PositionalVector, TArray& LinearArray) const +{ + // Number of positions + + const int32 Num = PositionalVector.XVector.rows(); + + // early out if the x,y,z arrays in the PositionalVector have different lengths + if (!PositionalVector.bHasSize(Num)) + { + return false; + } + + // + const auto& IndexToVtxId = VtxLinearization.ToId(); + const int32 MaxVtxId = IndexToVtxId.Num(); // NB: this is really max_used + 1 in the mesh. See FDynamicMesh3::MaxVertexID() + + + + LinearArray.Empty(MaxVtxId); + LinearArray.AddUninitialized(MaxVtxId); + + for (int32 i = 0; i < Num; ++i) + { + const int32 VtxId = IndexToVtxId[i]; + + LinearArray[VtxId] = FVector3d(PositionalVector.XVector.coeff(i), PositionalVector.YVector.coeff(i), PositionalVector.ZVector.coeff(i)); + } + + return true; +} + +void FConstrainedMeshOperator::ExtractVertexPositions(const FDynamicMesh3& DynamicMesh, FSOAPositions& VertexPositions) const +{ + VertexPositions.SetZero(VertexCount); + + + const TArray& ToIndex = VtxLinearization.ToIndex(); + + + for (int32 VtxId : DynamicMesh.VertexIndicesItr()) + { + const int32 i = ToIndex[VtxId]; + + checkSlow(i != FDynamicMesh3::InvalidID); + + const FVector3d& Vertex = DynamicMesh.GetVertex(VtxId); + // coeffRef - coeff access without range checking + VertexPositions.XVector.coeffRef(i) = Vertex.X; + VertexPositions.YVector.coeffRef(i) = Vertex.Y; + VertexPositions.ZVector.coeffRef(i) = Vertex.Z; + } +} + +void FConstrainedMeshOperator::UpdateWithPostFixConstraints(FSOAPositions& PositionVector) const +{ + for (const auto& ConstraintPosition : ConstraintPositionMap) + { + const int32 Index = ConstraintPosition.Key; + const FConstraintPosition& Constraint = ConstraintPosition.Value; + + // we only care about post-fix constraints + + if (Constraint.bPostFix) + { + const FVector3d& Pos = Constraint.Position; + PositionVector.XVector[Index] = Pos.X; + PositionVector.YVector[Index] = Pos.Y; + PositionVector.ZVector[Index] = Pos.Z; + } + } +} + + + +FConstrainedMeshDeformer::FConstrainedMeshDeformer(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme LaplacianType) + : FConstrainedMeshOperator(DynamicMesh, LaplacianType, EMatrixSolverType::LU) + , LaplacianVectors(FConstrainedMeshOperator::VertexCount) +{ + + // The current vertex positions + + // Note: the OriginalVertexPositions are being stored as member data + // for use if the solver is iterative. + // FSOAPositions OriginalVertexPositions; + ExtractVertexPositions(DynamicMesh, OriginalVertexPositions); + + + // The biharmonic part of the constrained solver + // Biharmonic := Laplacian^{T} * Laplacian + + const auto& Biharmonic = ConstrainedSolver->Biharmonic(); + + // Compute the Laplacian Vectors + // := Biharmonic * VertexPostion + // In the case of the cotangent laplacian this can be identified as the mean curvature * normal. + for (int32 i = 0; i < 3; ++i) + { + LaplacianVectors.Array(i) = Biharmonic * OriginalVertexPositions.Array(i); + } +} + +bool FConstrainedMeshDeformer::Deform(TArray& PositionBuffer) +{ + + // Update constraints. This only trigger solver rebuild if the weights were updated. + UpdateSolverConstraints(); + + // Allocate space for the result as a struct of arrays + FSOAPositions SolutionVector(VertexCount); + + // Solve the linear system + // NB: the original positions will only be used if the underlying solver type is iterative + bool bSuccess = ConstrainedSolver->SolveWithGuess(OriginalVertexPositions, LaplacianVectors, SolutionVector); + + // Move any vertices to match bPostFix constraints + + UpdateWithPostFixConstraints(SolutionVector); + + // Copy the result into the array of stucts form. + // NB: this re-indexes so the results can be looked up using VtxId + + Linearize(SolutionVector, PositionBuffer); + + // the matrix solve state + return bSuccess; + +} + + +bool FBiHarmonicMeshSmoother::ComputeSmoothedMeshPositions(TArray& UpdatedPositions) +{ + + UpdateSolverConstraints(); + + // Solves the constrained system and updates the mesh + + FSOAPositions SolutionVector(VertexCount); + + bool bSuccess = ConstrainedSolver->Solve(SolutionVector); + + + // Move any vertices to match bPostFix constraints + + UpdateWithPostFixConstraints(SolutionVector); + + // Move vertices to solution positions + + Linearize(SolutionVector, UpdatedPositions); + + return bSuccess; +} + +bool FCGBiHarmonicMeshSmoother::ComputeSmoothedMeshPositions(TArray& UpdatedPositions) +{ + + UpdateSolverConstraints(); + + // Solves the constrained system and updates the mesh + + // Solves the constrained system + + FSOAPositions SolutionVector(VertexCount); + + bool bSuccess = ConstrainedSolver->Solve(SolutionVector); + + + // Move any vertices to match bPostFix constraints + + UpdateWithPostFixConstraints(SolutionVector); + + // Move vertices to solution positions + + Linearize(SolutionVector, UpdatedPositions); + + return bSuccess; +} + + + +FDiffusionIntegrator::FDiffusionIntegrator(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme) +{ + Id = 0; + bIsSymmetric = false; + MinDiagonalValue = 0; + + VertexCount = DynamicMesh.VertexCount(); + + // Allocate the buffers. + Tmp[0].SetZero(VertexCount); + Tmp[1].SetZero(VertexCount); + + +} + +void FDiffusionIntegrator::Initialize(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme) +{ + // Construct the laplacian, and extract the mapping for vertices (VtxLinearization) + DiffusionOperator = ConstructDiffusionOperator(Scheme, DynamicMesh, bIsSymmetric, VtxLinearization, &EdgeVerts); + + const auto& ToVertId = VtxLinearization.ToId(); + + // Extract current positions. + for (int32 i = 0; i < VertexCount; ++i) + { + int32 VtxId = ToVertId[i]; + + const FVector3d Pos = DynamicMesh.GetVertex(VtxId); + Tmp[0].XVector[i] = Pos.X; + Tmp[0].YVector[i] = Pos.Y; + Tmp[0].ZVector[i] = Pos.Z; + } + + const FSparseMatrixD& M = *DiffusionOperator; + + // Find the min diagonal entry (all should be negative). + int32 Rank = M.rows(); + MinDiagonalValue = FSparseMatrixD::Scalar(0); + for (int32 i = 0; i < Rank; ++i) + { + auto Diag = M.coeff(i, i); + MinDiagonalValue = FMath::Min(Diag, MinDiagonalValue); + } + + +#if 0 + // testing - how to print the matrix to debug output + + std::stringstream ss; + ss << Eigen::MatrixXd(M) << std::endl; + + FString Foo = ss.str().c_str(); + FPlatformMisc::LowLevelOutputDebugStringf(*Foo); +#endif + +} + + + +void FDiffusionIntegrator::Integrate_ForwardEuler(int32 NumSteps, double Alpha, double) +{ + Alpha = FMath::Clamp(Alpha, 0., 1.); + + const FSparseMatrixD& M = *DiffusionOperator; + FSparseMatrixD::Scalar TimeStep = -Alpha / MinDiagonalValue; + Id = 0; + for (int32 s = 0; s < NumSteps; ++s) + { + + int32 SrcBuffer = Id; + Id = 1 - Id; + Tmp[Id].XVector = Tmp[SrcBuffer].XVector + TimeStep * M * Tmp[SrcBuffer].XVector; + Tmp[Id].YVector = Tmp[SrcBuffer].YVector + TimeStep * M * Tmp[SrcBuffer].YVector; + Tmp[Id].ZVector = Tmp[SrcBuffer].ZVector + TimeStep * M * Tmp[SrcBuffer].ZVector; + + } + +} + + + +void FDiffusionIntegrator::Integrate_BackwardEuler(const EMatrixSolverType MatrixSolverType, int32 NumSteps, double Alpha, double Intensity) +{ + + //typedef typename TMatrixSolverTrait::MatrixSolverType MatrixSolverType; + + // We solve + // p^{n+1} - dt * L[p^{n+1}] = p^{n} + // + // i.e. + // [I - dt * L ] p^{n+1} = p^{n} + // + // NB: in the case of the cotangent laplacian this would be better if we broke the L int + // L = (A^{-1}) H where A is the "area matrix" (think "mass matrix"), then this would + // become + // [A - dt * H] p^{n+1} = Ap^{n} + // + // A - dt * H would be symmetric + // + + + const FSparseMatrixD& L = *DiffusionOperator; + // Identity matrix + FSparseMatrixD Ident(L.rows(), L.cols()); + Ident.setIdentity(); + + FSparseMatrixD::Scalar TimeStep = Alpha * FMath::Min(Intensity, 1.e6); + + FSparseMatrixD M = Ident -TimeStep * L; + + + M.makeCompressed(); + + TUniquePtr MatrixSolver = ContructMatrixSolver(MatrixSolverType); + + MatrixSolver->SetUp(M, bIsSymmetric); + + if (MatrixSolver->bIsIterative()) + { + IIterativeMatrixSolverBase* IterativeSolver = (IIterativeMatrixSolverBase*)MatrixSolver.Get(); + + bool bForceSingleThreaded = false; + Id = 0; + for (int32 s = 0; s < NumSteps; ++s) + { + + int32 SrcBuffer = Id; + Id = 1 - Id; + + // Old solution is the guess. + IterativeSolver->SolveWithGuess(Tmp[SrcBuffer], Tmp[SrcBuffer], Tmp[Id]); + + } + } + else + { + bool bForceSingleThreaded = false; + Id = 0; + for (int32 s = 0; s < NumSteps; ++s) + { + + int32 SrcBuffer = Id; + Id = 1 - Id; + + MatrixSolver->Solve(Tmp[SrcBuffer], Tmp[Id]); + } + } + + +} + +void FDiffusionIntegrator::GetPositions(TArray& PositionArray) const +{ + Linearize(Tmp[Id], PositionArray); +} + +bool FDiffusionIntegrator::Linearize(const FSOAPositions& PositionalVector, TArray& LinearArray) const +{ + // Number of positions + + const int32 Num = PositionalVector.XVector.rows(); + + // early out if the x,y,z arrays in the PositionalVector have different lengths + if (!PositionalVector.bHasSize(Num)) + { + return false; + } + + // + const auto& IndexToVtxId = VtxLinearization.ToId(); + const int32 MaxVtxId = IndexToVtxId.Num(); // NB: this is really max_used + 1 in the mesh. See FDynamicMesh3::MaxVertexID() + + + + LinearArray.Empty(MaxVtxId); + LinearArray.AddUninitialized(MaxVtxId); + + for (int32 i = 0; i < Num; ++i) + { + const int32 VtxId = IndexToVtxId[i]; + + LinearArray[VtxId] = FVector3d(PositionalVector.XVector.coeff(i), PositionalVector.YVector.coeff(i), PositionalVector.ZVector.coeff(i)); + } + + return true; +} + + +TUniquePtr FLaplacianDiffusionMeshSmoother::ConstructDiffusionOperator( const ELaplacianWeightScheme Scheme, + const FDynamicMesh3& DynamicMesh, + bool& bIsOperatorSymmetric, + FVertexLinearization& Linearization, + TArray* EdgeVtx ) +{ + bIsOperatorSymmetric = bIsSymmetricLaplacian(Scheme); + + + // Construct the laplacian, and extract the mapping for vertices (VtxLinearization) + TUniquePtr Laplacian = ConstructLaplacian(Scheme, DynamicMesh, VtxLinearization, &EdgeVerts); + Laplacian->makeCompressed(); + return Laplacian; +}; + +TUniquePtr FBiHarmonicDiffusionMeshSmoother::ConstructDiffusionOperator( const ELaplacianWeightScheme Scheme, + const FDynamicMesh3& DynamicMesh, + bool& bIsOperatorSymmetric, + FVertexLinearization& Linearization, + TArray* EdgeVtx ) +{ + bIsOperatorSymmetric = true; + + // Construct the laplacian, and extract the mapping for vertices (VtxLinearization) + TUniquePtr Laplacian = ConstructLaplacian(Scheme, DynamicMesh, VtxLinearization, &EdgeVerts); + const FSparseMatrixD& L = *Laplacian; + TUniquePtr MatrixOperator(new FSparseMatrixD()); + + bool bIsLaplacianSymmetric = bIsSymmetricLaplacian(Scheme); + + if (bIsLaplacianSymmetric) + { + + *MatrixOperator = -1. * L * L; + } + else + { + *MatrixOperator = -1. * L.transpose() * L; + } + + MatrixOperator->makeCompressed(); + return MatrixOperator; +}; + + +void MeshSmoothingOperators::ComputeSmoothing_BiHarmonic(const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& OriginalMesh, + const double Speed, const double Intensity, const int32 NumIterations, TArray& PositionArray) +{ + + + + // This is equivalent to taking a single backward Euler time step of bi-harmonic diffusion + // where L is the Laplacian (Del^2) , and L^T L is an approximation of the Del^4. + // + // dp/dt = - k*k L^T L[p] + // with + // weight = 1 / (k * Sqrt[dt] ) + // + // p^{n+1} + dt * k * k L^TL [p^{n+1}] = p^{n} + // + // re-write as + // L^TL[p^{n+1}] + weight * weight p^{n+1} = weight * weight p^{n} + +#ifndef EIGEN_MPL2_ONLY + const EMatrixSolverType MatrixSolverType = EMatrixSolverType::LTL; +#else + // const EMatrixSolverType MatrixSolverType = EMatrixSolverType::LU; + // const EMatrixSolverType MatrixSolverType = EMatrixSolverType::PCG; + + // The Symmetric Laplacians are SPD, and so are the LtL Operators + const EMatrixSolverType MatrixSolverType = (bIsSymmetricLaplacian(WeightScheme))? EMatrixSolverType::PCG : EMatrixSolverType::LU; + +#endif + + + FString DebugLogString = FString::Printf(TEXT("Biharmonic Smoothing of mesh with %d verts "), OriginalMesh.VertexCount()) + LaplacianSchemeName(WeightScheme) + MatrixSolverName(MatrixSolverType); + + FScopedDurationTimeLogger Timmer(DebugLogString); + + FBiHarmonicDiffusionMeshSmoother BiHarmonicDiffusionSmoother(OriginalMesh, WeightScheme); + + BiHarmonicDiffusionSmoother.Integrate_BackwardEuler(MatrixSolverType, NumIterations, Speed, Intensity); + + BiHarmonicDiffusionSmoother.GetPositions(PositionArray); + + +} + +void MeshSmoothingOperators::ComputeSmoothing_ImplicitBiHarmonicPCG( const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& OriginalMesh, + const double Speed, const double Weight, const int32 MaxIterations, TArray& PositionArray) +{ + + // This is equivalent to taking a single backward Euler time step of bi-harmonic diffusion + // where L is the Laplacian (Del^2) , and L^T L is an approximation of the Del^4. + // + // dp/dt = - k*k L^T L[p] + // with + // weight = 1 / (k * Sqrt[dt] ) + // + // p^{n+1} + dt * k * k L^TL [p^{n+1}] = p^{n} + // + // re-write as + // L^TL[p^{n+1}] + weight * weight p^{n+1} = weight * weight p^{n} + + FString DebugLogString = FString::Printf(TEXT("PCG Biharmonic Smoothing of mesh with %d verts "), OriginalMesh.VertexCount()) + LaplacianSchemeName(WeightScheme); + + FScopedDurationTimeLogger Timmer(DebugLogString); + + if (MaxIterations < 1) return; + + FCGBiHarmonicMeshSmoother Smoother(OriginalMesh, WeightScheme); + + // Treat all vertices as constraints with the same weight + const bool bPostFix = false; + + for (int32 VertId : OriginalMesh.VertexIndicesItr()) + { + FVector3d Pos = OriginalMesh.GetVertex(VertId); + + Smoother.AddConstraint(VertId, Weight, Pos, bPostFix); + } + + Smoother.SetMaxIterations(MaxIterations); + Smoother.SetTolerance(1.e-4); + + bool bSuccess = Smoother.ComputeSmoothedMeshPositions(PositionArray); + +} + +void MeshSmoothingOperators::ComputeSmoothing_Diffusion( const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& OriginalMesh, bool bForwardEuler, + const double Speed, const double Intensity, const int32 IterationCount, TArray& PositionArray) +{ + +#ifndef EIGEN_MPL2_ONLY + const EMatrixSolverType MatrixSolverType = EMatrixSolverType::LTL; +#else + const EMatrixSolverType MatrixSolverType = EMatrixSolverType::LU; + //const EMatrixSolverType MatrixSolverType = EMatrixSolverType::PCG; + //const EMatrixSolverType MatrixSolverType = EMatrixSolverType::BICGSTAB; +#endif + + FString DebugLogString = FString::Printf(TEXT("Diffusion Smoothing of mesh with %d verts"), OriginalMesh.VertexCount()); + if (!bForwardEuler) + { + DebugLogString += MatrixSolverName(MatrixSolverType); + } + + FScopedDurationTimeLogger Timmer(DebugLogString); + + if (IterationCount < 1) return; + + FLaplacianDiffusionMeshSmoother Smoother(OriginalMesh, WeightScheme); + + if (bForwardEuler) + { + Smoother.Integrate_ForwardEuler(IterationCount, Speed, Intensity); + } + else + { + Smoother.Integrate_BackwardEuler(MatrixSolverType, IterationCount, Speed, Intensity); + } + + Smoother.GetPositions(PositionArray); +}; + + +TUniquePtr MeshDeformingOperators::ConstructConstrainedMeshDeformer(const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& DynamicMesh) +{ + TUniquePtr Deformer(new FConstrainedMeshDeformer(DynamicMesh, WeightScheme)); + + return Deformer; +} + + +TUniquePtr MeshDeformingOperators::ConstructConstrainedMeshSmoother(const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& DynamicMesh) +{ + TUniquePtr Deformer(new FBiHarmonicMeshSmoother(DynamicMesh, WeightScheme)); + + return Deformer; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianMeshSmoother.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianMeshSmoother.h new file mode 100644 index 000000000000..627be22a695a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianMeshSmoother.h @@ -0,0 +1,249 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DynamicMesh3.h" +#include "ConstrainedPoissonSolver.h" +#include "FSOAPositions.h" +#include "MatrixSolver.h" +#include "MeshSmoothingUtilities.h" +#include "MeshElementLinearizations.h" + + + +class FConstrainedMeshOperator : public MeshDeformingOperators::IConstrainedMeshOperator +{ +public: + typedef FConstrainedSolver::FConstraintPosition FConstraintPosition; + + + FConstrainedMeshOperator(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme, const EMatrixSolverType MatrixSolverType); + virtual ~FConstrainedMeshOperator() override {} + + // Add constraint associated with given vertex id. + void AddConstraint(const int32 VtxId, const double Weight, const FVector3d& Pos, const bool bPostFix) override; + + + bool UpdateConstraintPosition(const int32 VtxId, const FVector3d& Position, const bool bPostFix) override; + + // The underlying solver will have to refactor the matrix if this is done + bool UpdateConstraintWeight(const int32 VtxId, const double Weight) override; + + // Clear all constraints associated with this smoother + void ClearConstraints() override { FConstrainedMeshOperator::ClearConstraintPositions(); FConstrainedMeshOperator::ClearConstraintWeights(); } + + void ClearConstraintWeights() override { ConstraintWeightMap.Empty(); bConstraintWeightsDirty = true; } + + void ClearConstraintPositions() override { ConstraintPositionMap.Empty(); bConstraintPositionsDirty = true; } + + + // Test if for constraint associated with given vertex id. + bool IsConstrained(const int32 VtxId) const override; + + + + virtual bool Deform(TArray& PositionBuffer) override { return false; } + +protected: + + // Sync constraints with internal solver. + void UpdateSolverConstraints(); + + + void ExtractVertexPositions(const FDynamicMesh3& DynamicMesh, FSOAPositions& Positions) const ; + + //void UpdateMeshWithConstraints(); + // Respect any bPostFix constraints by moving those vertices to position defined by said constraint. + void UpdateWithPostFixConstraints(FSOAPositions& PositionalVector) const; + + + // converts the positionalvector to a TArray where the offset in the array is implicitly the + // VtxId in the mesh, and not ness the matrix row id + // NB: the resulting array is treated as sparse and may have un-initialized elements. + bool Linearize(const FSOAPositions& PositionalVector, TArray& LinearArray) const; + + +protected: + + // The Key (int32) here is the vertex index not vertex ID + // making it the same as the matrix row + TMap ConstraintPositionMap; + TMap ConstraintWeightMap; + + bool bConstraintPositionsDirty = true; + bool bConstraintWeightsDirty = true; + + // Cache the vertex count. + int32 VertexCount; + + // Used to map between VtxId and vertex Index in linear vector.. + FVertexLinearization VtxLinearization; + + // I don't know if we want to keep this after the constructor + TUniquePtr Laplacian; + TArray EdgeVerts; + + // Actual solver that manages the various linear algebra bits. + TUniquePtr ConstrainedSolver; +}; + + +class FConstrainedMeshDeformer : public FConstrainedMeshOperator +{ +public: + FConstrainedMeshDeformer(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme LaplacianType); + ~FConstrainedMeshDeformer() override {} + + bool Deform(TArray& PositionBuffer) override; + +private: + + FSOAPositions LaplacianVectors; + FSOAPositions OriginalVertexPositions; +}; + + +class FBiHarmonicMeshSmoother : public FConstrainedMeshOperator +{ +public: + typedef FConstrainedMeshOperator MyBaseType; + + FBiHarmonicMeshSmoother(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme) : + MyBaseType(DynamicMesh, Scheme, EMatrixSolverType::LU) + {} + + bool Deform(TArray& UpdatedPositions) override + { + return ComputeSmoothedMeshPositions(UpdatedPositions); + } + + // (Direct) Solve the constrained system and populate the UpdatedPositions with the result + bool ComputeSmoothedMeshPositions(TArray& UpdatedPositions); + +}; + + +// NB: This conjugate gradient solver could be updated to use solveWithGuess() method on the iterative solver +class FCGBiHarmonicMeshSmoother : public FConstrainedMeshOperator +{ +public: + typedef FConstrainedMeshOperator MyBaseType; + + FCGBiHarmonicMeshSmoother(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme) : + MyBaseType(DynamicMesh, Scheme, EMatrixSolverType::BICGSTAB) + {} + + bool Deform(TArray& UpdatedPositions) override + { + return ComputeSmoothedMeshPositions(UpdatedPositions); + } + + void SetMaxIterations(int32 MaxIterations) + { + IIterativeMatrixSolverBase* Solver = ConstrainedSolver->GetMatrixSolverIterativeBase(); + if (Solver) + { + Solver->SetIterations(MaxIterations); + } + } + + void SetTolerance(double Tol) + { + IIterativeMatrixSolverBase* Solver = ConstrainedSolver->GetMatrixSolverIterativeBase(); + if (Solver) + { + Solver->SetTolerance(Tol); + } + } + + // (Iterative) Solve the constrained system and populate the UpdatedPositions with the result + bool ComputeSmoothedMeshPositions(TArray& UpdatedPositions); + + +}; + + +class FDiffusionIntegrator +{ +public: + + FDiffusionIntegrator(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme); + virtual ~FDiffusionIntegrator() {} + + void Integrate_ForwardEuler(int32 NumSteps, double Speed, double); + + // Note: + void Integrate_BackwardEuler(const EMatrixSolverType MatrixSolverType, int32 NumSteps, double Speed, double Intensity); + + void GetPositions(TArray& PositionArray) const; + +protected: + + // The derived class has to implement this. + virtual TUniquePtr ConstructDiffusionOperator(const ELaplacianWeightScheme Scheme, + const FDynamicMesh3& Mesh, + bool& bIsOperatorSymmetric, + FVertexLinearization& Linearization, + TArray* EdgeVtx) = 0 ; + + void Initialize(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme); + +protected: + bool Linearize(const FSOAPositions& PositionalVector, TArray& LinearArray) const; + + // Cache the vertex count. + int32 VertexCount; + + // Used to map vertex ID to entry in linear vector.. + FVertexLinearization VtxLinearization; + + // I don't know if we want to keep this after the constructor + bool bIsSymmetric; + TUniquePtr DiffusionOperator; + TArray EdgeVerts; + FSparseMatrixD::Scalar MinDiagonalValue; + + FSOAPositions Tmp[2]; + int32 Id; // double buffer id +}; + + +class FLaplacianDiffusionMeshSmoother : public FDiffusionIntegrator +{ +public: + + FLaplacianDiffusionMeshSmoother(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme) + : FDiffusionIntegrator(DynamicMesh, Scheme) + { + Initialize(DynamicMesh, Scheme); + } + +protected: + TUniquePtr ConstructDiffusionOperator( const ELaplacianWeightScheme Scheme, + const FDynamicMesh3& Mesh, + bool& bIsOperatorSymmetric, + FVertexLinearization& Linearization, + TArray* EdgeVtx) override ; + + +}; + +class FBiHarmonicDiffusionMeshSmoother : public FDiffusionIntegrator +{ +public: + + FBiHarmonicDiffusionMeshSmoother(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme) + : FDiffusionIntegrator(DynamicMesh, Scheme) + { + Initialize(DynamicMesh, Scheme); + } + +protected: + TUniquePtr ConstructDiffusionOperator( const ELaplacianWeightScheme Scheme, + const FDynamicMesh3& Mesh, + bool& bIsOperatorSymmetric, + FVertexLinearization& Linearization, + TArray* EdgeVtx) override; +}; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianOperators.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianOperators.cpp new file mode 100644 index 000000000000..324efb955f04 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianOperators.cpp @@ -0,0 +1,1164 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "LaplacianOperators.h" + +#include // double version of sqrt +#include // used by eigen to initialize sparse matrix + +#if defined(_MSC_VER) && USING_CODE_ANALYSIS +#pragma warning(push) +#pragma warning(disable : 6011) +#pragma warning(disable : 6387) +#pragma warning(disable : 6313) +#pragma warning(disable : 6294) +#endif +THIRD_PARTY_INCLUDES_START +#include // for Matrix4d in testing +THIRD_PARTY_INCLUDES_END +#if defined(_MSC_VER) && USING_CODE_ANALYSIS +#pragma warning(pop) +#endif + + +#define LAPLACIAN_SKIP_BOUNDARY 0 + + + +FString LaplacianSchemeName(const ELaplacianWeightScheme Scheme) +{ + FString LaplacianName; + switch (Scheme) + { + case ELaplacianWeightScheme::ClampedCotangent: + LaplacianName = FString(TEXT("Clamped Cotangent Laplacian")); + break; + case ELaplacianWeightScheme::Cotangent: + LaplacianName = FString(TEXT("Cotangent Laplacian")); + break; + case ELaplacianWeightScheme::Umbrella: + LaplacianName = FString(TEXT("Umbrella Laplacian")); + break; + case ELaplacianWeightScheme::MeanValue: + LaplacianName = FString(TEXT("MeanValue Laplacian")); + break; + case ELaplacianWeightScheme::Uniform: + LaplacianName = FString(TEXT("Uniform Laplacian")); + break; + case ELaplacianWeightScheme::Valence: + LaplacianName = FString(TEXT("Valence Laplacian")); + break; + default: + check(0 && "Unknown Laplacian Weight Scheme Enum"); + } + + return LaplacianName; +} + + +// Utility to compute the number of elements in the sparse laplacian matrix +int32 ComputeNumMatrixElements(const FDynamicMesh3& DynamicMesh, const TArray& ToVtxId) +{ + const int32 NumVerts = ToVtxId.Num(); + TArray OneRingSize; + { + OneRingSize.SetNumUninitialized(NumVerts); + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 VertId = ToVtxId[i]; + OneRingSize[i] = DynamicMesh.GetVtxEdgeCount(VertId); + } + } + + // Compute the total number of entries in the sparse matrix + int32 NumMatrixEntries = 0; + { + for (int32 i = 0; i < NumVerts; ++i) + { + NumMatrixEntries += 1 + OneRingSize[i]; // myself plus my neighbors + } + + } + + return NumMatrixEntries; +} + +TUniquePtr ConstructUniformLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts) +{ + + + typedef FSparseMatrixD::Scalar ScalarT; + typedef Eigen::Triplet MatrixTripletT; + + if (BoundaryVerts) + { + // empty + BoundaryVerts->Empty(); + } + + // Sync the mapping between the mesh vertex ids and their offsets in a nomimal linear array. + VertexMap.Reset(DynamicMesh); + + const TArray& ToMeshV = VertexMap.ToId(); + const TArray& ToIndex = VertexMap.ToIndex(); + const int32 NumVerts = VertexMap.NumVerts(); + + // Eigen constructs a sparse matrix from a linearized array of matrix entries. + + std::vector MatrixTripelList; + { + int32 NumMatrixEntries = ComputeNumMatrixElements(DynamicMesh, ToMeshV); + MatrixTripelList.reserve(NumMatrixEntries); + } + + // Construct Laplacian Matrix: loop over verts constructing the corresponding matrix row. + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 VertId = ToMeshV[i]; + + if (DynamicMesh.IsBoundaryVertex(VertId)) + { + if (BoundaryVerts) + { + BoundaryVerts->Add(VertId); + } +#if LAPLACIAN_SKIP_BOUNDARY == 1 + MatrixTripelList.push_back(MatrixTripletT(i, i, 0.)); + continue; +#endif + } + + + ScalarT CenterWeight = ScalarT(0); // equal and opposite the sum of the neighbor weights + for (int NeighborVertId : DynamicMesh.VtxVerticesItr(VertId)) + { + const int32 j = ToIndex[NeighborVertId]; + + + ScalarT NeighborWeight = ScalarT(1); + CenterWeight += NeighborWeight; + + // add the neighbor + MatrixTripelList.push_back(MatrixTripletT(i, j, NeighborWeight)); + + } + // add the center + MatrixTripelList.push_back(MatrixTripletT(i, i, -CenterWeight)); + + } + + // SparseMatrix doesn't have (SparseMatrix&& ) constructor + + TUniquePtr LaplacianMatrix(new FSparseMatrixD(NumVerts, NumVerts)); + LaplacianMatrix->setFromTriplets(MatrixTripelList.begin(), MatrixTripelList.end()); + + LaplacianMatrix->makeCompressed(); + + return LaplacianMatrix; + +} + +TUniquePtr ConstructUmbrellaLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts) +{ + + + typedef FSparseMatrixD::Scalar ScalarT; + typedef Eigen::Triplet MatrixTripletT; + + if (BoundaryVerts) + { + // empty + BoundaryVerts->Empty(); + } + + // Sync the mapping between the mesh vertex ids and their offsets in a nomimal linear array. + VertexMap.Reset(DynamicMesh); + + const TArray& ToMeshV = VertexMap.ToId(); + const TArray& ToIndex = VertexMap.ToIndex(); + const int32 NumVerts = VertexMap.NumVerts(); + + // Eigen constructs a sparse matrix from a linearized array of matrix entries. + + std::vector MatrixTripelList; + { + int32 NumMatrixEntries = ComputeNumMatrixElements(DynamicMesh, ToMeshV); + MatrixTripelList.reserve(NumMatrixEntries); + } + + // Cache valency of each vertex. + // Number of non-zero elements in the i'th row = 1 + OneRingSize(i) + TArray OneRingSize; + { + OneRingSize.SetNumUninitialized(NumVerts); + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 VertId = ToMeshV[i]; + OneRingSize[i] = DynamicMesh.GetVtxEdgeCount(VertId); + } + } + + // Construct Laplacian Matrix: loop over verts constructing the corresponding matrix row. + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 VertId = ToMeshV[i]; + const int32 Valence = OneRingSize[i]; + double InvValence = (Valence != 0) ? 1. / double(Valence) : 0.; + + if (DynamicMesh.IsBoundaryVertex(VertId)) + { + if (BoundaryVerts) + { + BoundaryVerts->Add(VertId); + } +#if LAPLACIAN_SKIP_BOUNDARY == 1 + MatrixTripelList.push_back(MatrixTripletT(i, i, 0.)); + continue; +#endif + } + + for (int NeighborVertId : DynamicMesh.VtxVerticesItr(VertId)) + { + const int32 j = ToIndex[NeighborVertId]; + + // add the neighbor + MatrixTripelList.push_back(MatrixTripletT(i, j, InvValence)); + + } + // add the center + MatrixTripelList.push_back(MatrixTripletT(i, i, -ScalarT(1))); + + } + + // SparseMatrix doesn't have (SparseMatrix&& ) constructor + + TUniquePtr LaplacianMatrix(new FSparseMatrixD(NumVerts, NumVerts)); + LaplacianMatrix->setFromTriplets(MatrixTripelList.begin(), MatrixTripelList.end()); + + LaplacianMatrix->makeCompressed(); + + return LaplacianMatrix; + +} + +TUniquePtr ConstructValenceWeightedLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts) +{ + + + typedef FSparseMatrixD::Scalar ScalarT; + typedef Eigen::Triplet MatrixTripletT; + + if (BoundaryVerts) + { + // empty + BoundaryVerts->Empty(); + } + + // Sync the mapping between the mesh vertex ids and their offsets in a nomimal linear array. + VertexMap.Reset(DynamicMesh); + + const TArray& ToMeshV = VertexMap.ToId(); + const TArray& ToIndex = VertexMap.ToIndex(); + const int32 NumVerts = VertexMap.NumVerts(); + + // Cache valency of each vertex. + // Number of non-zero elements in the i'th row = 1 + OneRingSize(i) + TArray OneRingSize; + { + OneRingSize.SetNumUninitialized(NumVerts); + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 VertId = ToMeshV[i]; + OneRingSize[i] = DynamicMesh.GetVtxEdgeCount(VertId); + } + } + + + // Compute the total number of entries in the sparse matrix + int32 NumMatrixEntries = 0; + { + for (int32 i = 0; i < NumVerts; ++i) + { + NumMatrixEntries += 1 + OneRingSize[i]; // myself plus my neighbors + } + + } + std::vector MatrixTripelList; + MatrixTripelList.reserve(NumMatrixEntries); + + + + // Construct Laplacian Matrix: loop over verts constructing the corresponding matrix row. + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 VertId = ToMeshV[i]; + const int32 IOneRingSize = OneRingSize[i]; + + if (DynamicMesh.IsBoundaryVertex(VertId)) + { + if (BoundaryVerts) + { + BoundaryVerts->Add(VertId); + } +#if LAPLACIAN_SKIP_BOUNDARY == 1 + MatrixTripelList.push_back(MatrixTripletT(i, i, 0.)); + continue; +#endif + } + + + ScalarT CenterWeight = ScalarT(0); // equal and opposite the sum of the neighbor weights + for (int NeighborVertId : DynamicMesh.VtxVerticesItr(VertId)) + { + const int32 j = ToIndex[NeighborVertId]; + const int32 JOneRingSize = OneRingSize[j]; + + + ScalarT NeighborWeight = ScalarT(1) / std::sqrt(IOneRingSize + JOneRingSize); + CenterWeight += NeighborWeight; + + // add the neighbor + MatrixTripelList.push_back(MatrixTripletT(i, j, NeighborWeight)); + + } + // add the center + MatrixTripelList.push_back(MatrixTripletT(i, i, -CenterWeight)); + + } + + // SparseMatrix doesn't have (SparseMatrix&& ) constructor + + TUniquePtr LaplacianMatrix(new FSparseMatrixD(NumVerts, NumVerts)); + LaplacianMatrix->setFromTriplets(MatrixTripelList.begin(), MatrixTripelList.end()); + + LaplacianMatrix->makeCompressed(); + + return LaplacianMatrix; + +} + + +/** +* The per-triangle data used in constructing the cotangent weighted laplacian. +* +*/ +class CotanTriangleData +{ +public: + + typedef FVector3d TriangleVertices[3]; + + CotanTriangleData() = default; + + CotanTriangleData(const CotanTriangleData& Other) + { + + Cotangent[0] = Other.Cotangent[0]; + Cotangent[1] = Other.Cotangent[1]; + Cotangent[2] = Other.Cotangent[2]; + + VoronoiArea[0] = Other.VoronoiArea[0]; + VoronoiArea[1] = Other.VoronoiArea[1]; + VoronoiArea[2] = Other.VoronoiArea[2]; + + OppositeEdge[0] = Other.OppositeEdge[0]; + OppositeEdge[1] = Other.OppositeEdge[1]; + OppositeEdge[2] = Other.OppositeEdge[2]; + + } + + + CotanTriangleData(const FDynamicMesh3& DynamicMesh, int32 TriId) + { + Initialize(DynamicMesh, TriId); + } + + void Initialize(const FDynamicMesh3& DynamicMesh, int32 SrcTriId) + { + TriId = SrcTriId; + + // edges: ab, bc, ca + FIndex3i EdgeIds = DynamicMesh.GetTriEdges(TriId); + + FVector3d VertA, VertB, VertC; + DynamicMesh.GetTriVertices(TriId, VertA, VertB, VertC); + + const FVector3d EdgeAB(VertB - VertA); + const FVector3d EdgeAC(VertC - VertA); + const FVector3d EdgeBC(VertC - VertB); + + + OppositeEdge[0] = EdgeIds[1]; // EdgeBC is opposite vert A + OppositeEdge[1] = EdgeIds[2]; // EdgeAC is opposite vert B + OppositeEdge[2] = EdgeIds[0]; // EdgeAB is opposite vert C + + + // NB: didn't use VectorUtil::Area() so we can re-use the Edges. + // also this formulation of area is always positive. + + const double TwiceArea = EdgeAB.Cross(EdgeAC).Length(); + + // NB: Area = 1/2 || EdgeA X EdgeB || where EdgeA and EdgeB are any two edges in the triangle. + + // Compute the Voronoi areas + // + // From Discrete Differential-Geometry Operators for Triangulated 2-Manifolds (Meyer, Desbrun, Schroder, Barr) + // http://www.geometry.caltech.edu/pubs/DMSB_III.pdf + // Given triangle P,Q, R the voronoi area at P is given by + // Area = (1/8) * ( |PR|**2 Cot 2. * SmallTriangleArea) + { + + + + // Compute the cotangent of the angle between V1 and V2 + // as the ratio V1.Dot.V2 / || V1 Cross V2 || + + // Cotangent[i] is cos(theta)/sin(theta) at the i'th vertex. + + Cotangent[0] = EdgeAB.Dot(EdgeAC) / TwiceArea; + Cotangent[1] = -EdgeAB.Dot(EdgeBC) / TwiceArea; + Cotangent[2] = EdgeAC.Dot(EdgeBC) / TwiceArea; + + if (bIsObtuse()) + { + const double Area = 0.5 * TwiceArea; + + // Voronoi inappropriate case. Instead use Area(T)/2 at obtuse corner + // and Area(T) / 4 at the other corners. + + VoronoiArea[0] = 0.25 * Area; + VoronoiArea[1] = 0.25 * Area; + VoronoiArea[2] = 0.25 * Area; + + for (int i = 0; i < 3; ++i) + { + if (Cotangent[i] < 0.) + { + VoronoiArea[i] = 0.5 * Area; + } + } + } + else + { + // If T is non-obtuse. + + const double EdgeABSqLength = EdgeAB.SquaredLength(); + const double EdgeACSqLength = EdgeAC.SquaredLength(); + const double EdgeBCSqLength = EdgeBC.SquaredLength(); + + VoronoiArea[0] = EdgeABSqLength * Cotangent[1] + EdgeACSqLength * Cotangent[2]; + VoronoiArea[1] = EdgeABSqLength * Cotangent[0] + EdgeBCSqLength * Cotangent[2]; + VoronoiArea[2] = EdgeACSqLength * Cotangent[0] + EdgeBCSqLength * Cotangent[1]; + + const double Inv8 = .125; // 1/8 + + VoronoiArea[0] *= Inv8; + VoronoiArea[1] *= Inv8; + VoronoiArea[2] *= Inv8; + } + } + else + { + // default small triangle - equilateral + double CotOf60 = 1. / FMath::Sqrt(3.f); + Cotangent[0] = CotOf60; + Cotangent[1] = CotOf60; + Cotangent[2] = CotOf60; + + VoronoiArea[0] = SmallTriangleArea / 3.; + VoronoiArea[1] = SmallTriangleArea / 3.; + VoronoiArea[2] = SmallTriangleArea / 3.; + } + } + + int32 GetLocalEdgeIdx(const int32 DynamicsMeshEdgeId) const + { + int32 Result = -1; + if (DynamicsMeshEdgeId == OppositeEdge[0]) + { + Result = 0; + } + else if (DynamicsMeshEdgeId == OppositeEdge[1]) + { + Result = 1; + } + else if (DynamicsMeshEdgeId == OppositeEdge[2]) + { + Result = 2; + } + return Result; + } + + /** helper to return the cotangent of the angle opposite the given edge + * + * @param DynamicsMeshEdgeId is the id used by FDynamicMesh3 for this edge. + * @param bValid will false on return if the requested edge is not part of this triangle + * @return Cotangent of the opposite angle. + */ + double GetOpposingCotangent(const int32 DynamicsMeshEdgeId, bool& bValid) const + { + + double OpposingCotangent = -1.; + bValid = false; + int32 LocalEdgeIdx = GetLocalEdgeIdx(DynamicsMeshEdgeId); + if (LocalEdgeIdx > -1) + { + OpposingCotangent = Cotangent[LocalEdgeIdx]; + bValid = true; + } + + return OpposingCotangent; + } + + double GetOpposingCotangent(const int32 DynamicsMeshEdgeId) const + { + bool bValid; + double OpposingCotangent = GetOpposingCotangent(DynamicsMeshEdgeId, bValid); + checkSlow(bValid); + return OpposingCotangent; + } + + bool bIsObtuse() const + { + return (Cotangent[0] < 0.f || Cotangent[1] < 0.f || Cotangent[2] < 0.f); + } + + + // The "floor" for triangle area. + // NB: the cotan laplacian has terms ~ 1/TriArea + // and the deformation matrix has terms ~ 1/TriArea**2 + + static constexpr double SmallTriangleArea = 1.e-4; + + // testing + int32 TriId = -1; + + /** Total byte count: 6 double + 3 int32 = 60 bytes. */ + + // Cotangent[i] is cos(theta)/sin(theta) at the i'th vertex. + + double Cotangent[3] = { 0. }; + + // VoronoiArea[i] is the voronoi area about the i'th vertex in this triangle. + + double VoronoiArea[3] = { 0. }; + + // OppositeEdge[i] = Corresponding DynamicsMesh3::EdgeId for the edge that is opposite + // the i'th vertex in this triangle + + int32 OppositeEdge[3] = { -1 }; + +}; + + +/** +* The per-triangle data used in constructing the mean-value weighted laplacian. +* +*/ +class MeanValueTriangleData +{ + +public: + + MeanValueTriangleData(const MeanValueTriangleData& Other) + : TriId(Other.TriId) + , TriVtxIds(Other.TriVtxIds) + , TriEdgeIds(Other.TriEdgeIds) + , bDegenerate(Other.bDegenerate) + { + EdgeLength[0] = Other.EdgeLength[0]; + EdgeLength[1] = Other.EdgeLength[1]; + EdgeLength[2] = Other.EdgeLength[2]; + + TanHalfAngle[0] = Other.TanHalfAngle[0]; + TanHalfAngle[1] = Other.TanHalfAngle[1]; + TanHalfAngle[2] = Other.TanHalfAngle[2]; + } + + void Initialize(const FDynamicMesh3& DynamicMesh, int32 SrcTriId) + { + TriId = SrcTriId; + + // VertAId, VertBId, VertCId + TriVtxIds = DynamicMesh.GetTriangle(TriId); + TriEdgeIds = DynamicMesh.GetTriEdges(TriId); + + FVector3d VertA, VertB, VertC; + DynamicMesh.GetTriVertices(TriId, VertA, VertB, VertC); + + const FVector3d EdgeAB(VertB - VertA); + const FVector3d EdgeAC(VertC - VertA); + const FVector3d EdgeBC(VertC - VertB); + + EdgeLength[0] = EdgeAB.Length(); + EdgeLength[1] = EdgeAC.Length(); + EdgeLength[2] = EdgeBC.Length(); + + constexpr double SmallEdge = 1e-4; + + bDegenerate = (EdgeLength[0] < SmallEdge || EdgeLength[1] < SmallEdge || EdgeLength[2] < SmallEdge); + + // Compute tan(angle/2) = Sqrt[ (1-cos) / (1 + cos)] + + const double ABdotAC = EdgeAB.Dot(EdgeAC); + const double BCdotBA = -EdgeBC.Dot(EdgeAB); + const double CAdotCB = EdgeAC.Dot(EdgeBC); + + + const double RegularizingConst = 1.e-6; // keeps us from dividing by zero when making tan[180/2] = sin[90]/cos[90] = inf + TanHalfAngle[0] = (EdgeLength[0] * EdgeLength[1] - ABdotAC) / (EdgeLength[0] * EdgeLength[1] + ABdotAC + RegularizingConst); + TanHalfAngle[1] = (EdgeLength[0] * EdgeLength[2] - BCdotBA) / (EdgeLength[0] * EdgeLength[2] + BCdotBA + RegularizingConst); + TanHalfAngle[2] = (EdgeLength[1] * EdgeLength[2] - CAdotCB) / (EdgeLength[1] * EdgeLength[2] + CAdotCB + RegularizingConst); + + // The ABS is just a precaution.. mathematically these should all be positive, but very small angles may result in negative values. + + TanHalfAngle[0] = std::sqrt(FMath::Abs(TanHalfAngle[0])); // at vertA + TanHalfAngle[1] = std::sqrt(FMath::Abs(TanHalfAngle[1])); // at vertB + TanHalfAngle[2] = std::sqrt(FMath::Abs(TanHalfAngle[2])); // at vertC +#if 0 + // testing + double totalAngle = 2. * (std::atan(TanHalfAngle[0]) + std::atan(TanHalfAngle[1]) + std::atan(TanHalfAngle[2])); + + double angleError = M_PI - totalAngle; +#endif + } + + // return Tan(angle / 2) for the corner indicated by this vert id. + double GetTanHalfAngle(int32 VtxId) const + { + int32 Offset = 0; + + while (VtxId != TriVtxIds[Offset]) + { + Offset++; + checkSlow(Offset < 3); + } + + return TanHalfAngle[Offset]; + } + + // return the length of the indicated edge + double GetEdgeLenght(int32 EdgeId) const + { + int32 Offset = 0; + + while (EdgeId != TriEdgeIds[Offset]) + { + Offset++; + checkSlow(Offset < 3); + } + + return EdgeLength[Offset]; + } + + + int32 TriId = -1; + FIndex3i TriVtxIds; + FIndex3i TriEdgeIds; + + bool bDegenerate = true; + + double EdgeLength[3] = { 0. }; + double TanHalfAngle[3] = { 0. }; + + +}; + +/** +* Return and array in triangle order that holds the per-triangle derived data needed +*/ +template +TArray ConstructTriangleDataArray(const FDynamicMesh3& DynamicMesh, const FTriangleLinearization& TriangleLinearization) +{ + TArray< TriangleDataType > TriangleDataArray; + + const int32 NumTris = TriangleLinearization.NumTris(); + TriangleDataArray.SetNumUninitialized(NumTris); + + const auto& ToTriIdx = TriangleLinearization.ToIndex(); + + for (int32 i = 0; i < NumTris; ++i) + { + // Current triangle + + const int32 TriIdx = ToTriIdx[i]; + + // Compute all the geometric data needed for this triangle. + + TriangleDataArray[i].Initialize(DynamicMesh, TriIdx); + } + + return TriangleDataArray; +} + + +TUniquePtr ConstructCotangentLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, FSparseMatrixD& AreaMatrix, TArray* BoundaryVerts) +{ + + if (BoundaryVerts) + { + // empty + BoundaryVerts->Empty(); + } + + typedef FSparseMatrixD::Scalar ScalarT; + typedef Eigen::Triplet MatrixTripletT; + + + // Create a mapping between the ordering of the vertex indices in the Dynamic Mesh + // and the actual storage. + VertexMap.Reset(DynamicMesh); + + const TArray& ToMeshV = VertexMap.ToId(); + const TArray& ToIndex = VertexMap.ToIndex(); + const int32 NumVerts = VertexMap.NumVerts(); + + + // Create the mapping of triangles + + FTriangleLinearization TriangleMap(DynamicMesh); + + const TArray& ToMeshTri = TriangleMap.ToId(); + const TArray& ToTriIdx = TriangleMap.ToIndex(); + const int32 NumTris = TriangleMap.NumTris(); + + + // Clear space for the areas + std::vector DiagonalTriplets; + DiagonalTriplets.reserve(NumVerts); + + FSparseMatrixD Diagonals; + Diagonals.reserve(NumVerts); + + + // Create an array that holds all the geometric information we need for each triangle. + + TArray CotangentTriangleDataArray = ConstructTriangleDataArray(DynamicMesh, TriangleMap); + + + // Eigen constructs a sparse matrix from a linearized array of matrix entries. + + std::vector MatrixTripelList; + { + int32 NumMatrixEntries = ComputeNumMatrixElements(DynamicMesh, ToMeshV); + MatrixTripelList.reserve(NumMatrixEntries); + } + + + // Construct Laplacian Matrix: loop over verts constructing the corresponding matrix row. + // store the id of the boundary verts for later use. + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 IVertId = ToMeshV[i]; // I - the row + + + if (DynamicMesh.IsBoundaryVertex(IVertId)) + { + if (BoundaryVerts) + { + BoundaryVerts->Add(IVertId); + } +#if LAPLACIAN_SKIP_BOUNDARY == 1 + MatrixTripelList.push_back(MatrixTripletT(i, i, 0.)); + DiagonalTriplets.push_back(MatrixTripletT(i, i, 1.)); + continue; +#endif + } + + + // Compute the Voronoi area for this vertex. + double WeightArea = 0.; + for (int32 TriId : DynamicMesh.VtxTrianglesItr(IVertId)) + { + const int32 TriIdx = ToTriIdx[TriId]; + const CotanTriangleData& TriData = CotangentTriangleDataArray[TriIdx]; + + + // The three VertIds for this triangle. + const FIndex3i TriVertIds = DynamicMesh.GetTriangle(TriId); + + // Which of the corners is IVertId? + int32 Offset = 0; + while (TriVertIds[Offset] != IVertId) + { + Offset++; + checkSlow(Offset < 3); + } + + WeightArea += TriData.VoronoiArea[Offset]; + } + + + double WeightII = 0.; // accumulate to equal and opposite the sum of the neighbor weights + + // for each connecting edge + + for (int32 EdgeId : DynamicMesh.VtxEdgesItr(IVertId)) + { + // [v0, v1, t0, t1]: NB: both t0 & t1 exist since IVert isn't a boundary vert. + FIndex4i Edge = DynamicMesh.GetEdge(EdgeId); + + + // the other vert in the edge - identifies the matrix column + const int32 JVertId = (Edge[0] == IVertId) ? Edge[1] : Edge[0]; // J - the column + + checkSlow(JVertId != IVertId); + + // Get the cotangents for this edge. + + const int32 Tri0Idx = ToTriIdx[Edge[2]]; + const CotanTriangleData& Tri0Data = CotangentTriangleDataArray[Tri0Idx]; + const double CotanAlpha = Tri0Data.GetOpposingCotangent(EdgeId); + + + // The second triangle will be invalid if this is an edge! + + const double CotanBeta = (Edge[3] != FDynamicMesh3::InvalidID) ? CotangentTriangleDataArray[ToTriIdx[Edge[3]]].GetOpposingCotangent(EdgeId) : 0.; + + double WeightIJ = 0.5 * (CotanAlpha + CotanBeta); + WeightII += WeightIJ; + + const int32 j = ToIndex[JVertId]; + + MatrixTripelList.push_back(MatrixTripletT(i, j, WeightIJ)); + + + } + + MatrixTripelList.push_back(MatrixTripletT(i, i, -WeightII)); + + DiagonalTriplets.push_back(MatrixTripletT(i, i, WeightArea)); + + + } + + Diagonals.setFromTriplets(DiagonalTriplets.begin(), DiagonalTriplets.end()); + AreaMatrix.swap(Diagonals); + AreaMatrix.makeCompressed(); + + TUniquePtr LaplacianMatrix(new FSparseMatrixD(NumVerts, NumVerts)); + LaplacianMatrix->setFromTriplets(MatrixTripelList.begin(), MatrixTripelList.end()); + LaplacianMatrix->makeCompressed(); + + + return LaplacianMatrix; + +} + +TUniquePtr ConstructCotangentLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, const bool bClampWeights, TArray* BoundaryVerts) +{ + + if (BoundaryVerts) + { + // empty + BoundaryVerts->Empty(); + } + + typedef FSparseMatrixD::Scalar ScalarT; + typedef Eigen::Triplet MatrixTripletT; + + + // Create a mapping between the ordering of the vertex indices in the Dynamic Mesh + // and the actual storage. + VertexMap.Reset(DynamicMesh); + + const TArray& ToMeshV = VertexMap.ToId(); + const TArray& ToIndex = VertexMap.ToIndex(); + const int32 NumVerts = VertexMap.NumVerts(); + + + // Map the triangles. + + FTriangleLinearization TriangleMap(DynamicMesh); + + const TArray& ToMeshTri = TriangleMap.ToId(); + const TArray& ToTriIdx = TriangleMap.ToIndex(); + const int32 NumTris = TriangleMap.NumTris(); + + // Create an array that holds all the geometric information we need for each triangle. + + TArray CotangentTriangleDataArray = ConstructTriangleDataArray(DynamicMesh, TriangleMap); + + // Eigen constructs a sparse matrix from a linearized array of matrix entries. + + std::vector MatrixTripelList; + { + int32 NumMatrixEntries = ComputeNumMatrixElements(DynamicMesh, ToMeshV); + MatrixTripelList.reserve(NumMatrixEntries); + } + + + // Construct Laplacian Matrix: loop over verts constructing the corresponding matrix row. + // skipping the boundary verts for later use. + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 IVertId = ToMeshV[i]; // I - the row + + + if (DynamicMesh.IsBoundaryVertex(IVertId)) + { + if (BoundaryVerts) + { + BoundaryVerts->Add(IVertId); + } +#if LAPLACIAN_SKIP_BOUNDARY == 1 + MatrixTripelList.push_back(MatrixTripletT(i, i, 0.)); + continue; +#endif + } + + + // Compute the Voronoi area for this vertex. + double WeightArea = 0.; + for (int32 TriId : DynamicMesh.VtxTrianglesItr(IVertId)) + { + const int32 TriIdx = ToTriIdx[TriId]; + const CotanTriangleData& TriData = CotangentTriangleDataArray[TriIdx]; + + + // The three VertIds for this triangle. + const FIndex3i TriVertIds = DynamicMesh.GetTriangle(TriId); + + // Which of the corners is IVertId? + int32 Offset = 0; + while (TriVertIds[Offset] != IVertId) + { + Offset++; + checkSlow(Offset < 3); + } + + WeightArea += TriData.VoronoiArea[Offset]; + } + + + double WeightII = 0.; // accumulate to equal and opposite the sum of the neighbor weights + + // for each connecting edge + + for (int32 EdgeId : DynamicMesh.VtxEdgesItr(IVertId)) + { + // [v0, v1, t0, t1]: NB: both t0 & t1 exist since IVert isn't a boundary vert. + FIndex4i Edge = DynamicMesh.GetEdge(EdgeId); + + + // the other vert in the edge - identifies the matrix column + const int32 JVertId = (Edge[0] == IVertId) ? Edge[1] : Edge[0]; // J - the column + + checkSlow(JVertId != IVertId); + + // Get the cotangents for this edge. + + const int32 Tri0Idx = ToTriIdx[Edge[2]]; + const CotanTriangleData& Tri0Data = CotangentTriangleDataArray[Tri0Idx]; + const double CotanAlpha = Tri0Data.GetOpposingCotangent(EdgeId); + + // The second triangle will be invalid if this is an edge! + + const double CotanBeta = (Edge[3] != FDynamicMesh3::InvalidID) ? CotangentTriangleDataArray[ToTriIdx[Edge[3]]].GetOpposingCotangent(EdgeId) : 0.; + + double WeightIJ = 0.5 * (CotanAlpha + CotanBeta); + + // clamp the weight + if (bClampWeights) + { + WeightIJ = FMath::Clamp(WeightIJ, -1.e5 * WeightArea, 1.e5 * WeightArea); + } + + WeightII += WeightIJ; + + const int32 j = ToIndex[JVertId]; + + MatrixTripelList.push_back(MatrixTripletT(i, j, WeightIJ / WeightArea)); + + } + + MatrixTripelList.push_back(MatrixTripletT(i, i, -WeightII / WeightArea)); + + } + +#if 0 + // used in degubing to insepct the matrix for testing + std::vector Tmp = MatrixTripelList; + + auto SortFunctor = [](const MatrixTripletT& A, const MatrixTripletT& B)->bool + { + return (A.value() < B.value()); + }; + + std::sort(Tmp.begin(), Tmp.end(), SortFunctor); + + auto ReverseSortFunctor = [](const MatrixTripletT& A, const MatrixTripletT& B)->bool + { + return (A.value() > B.value()); + }; + + std::sort(Tmp.begin(), Tmp.end(), ReverseSortFunctor); +#endif + + TUniquePtr LaplacianMatrix(new FSparseMatrixD(NumVerts, NumVerts)); + LaplacianMatrix->setFromTriplets(MatrixTripelList.begin(), MatrixTripelList.end()); + LaplacianMatrix->makeCompressed(); + + return LaplacianMatrix; + +} + + + + + + + +TUniquePtr ConstructMeanValueWeightLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts) +{ + + if (BoundaryVerts) + { + // empty + BoundaryVerts->Empty(); + } + + typedef FSparseMatrixD::Scalar ScalarT; + typedef Eigen::Triplet MatrixTripletT; + + + // Create a mapping between the ordering of the vertex indices in the Dynamic Mesh + // and the actual storage. + VertexMap.Reset(DynamicMesh); + + const TArray& ToMeshV = VertexMap.ToId(); + const TArray& ToIndex = VertexMap.ToIndex(); + const int32 NumVerts = VertexMap.NumVerts(); + + + // Map the triangles. + + FTriangleLinearization TriangleMap(DynamicMesh); + + const TArray& ToMeshTri = TriangleMap.ToId(); + const TArray& ToTriIdx = TriangleMap.ToIndex(); + const int32 NumTris = TriangleMap.NumTris(); + + + // Create an array that holds all the geometric information we need for each triangle. + + TArray TriangleDataArray = ConstructTriangleDataArray(DynamicMesh, TriangleMap); + + + // Eigen constructs a sparse matrix from a linearized array of matrix entries. + + std::vector MatrixTripelList; + { + int32 NumMatrixEntries = ComputeNumMatrixElements(DynamicMesh, ToMeshV); + MatrixTripelList.reserve(NumMatrixEntries); + } + + + // Construct Laplacian Matrix: loop over verts constructing the corresponding matrix row. + // skipping the boundary verts for later use. + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 IVertId = ToMeshV[i]; // I - the row + + + if (DynamicMesh.IsBoundaryVertex(IVertId)) + { + if (BoundaryVerts) + { + BoundaryVerts->Add(IVertId); + } +#if LAPLACIAN_SKIP_BOUNDARY == 1 + MatrixTripelList.push_back(MatrixTripletT(i, i, 0.)); + continue; +#endif + } + + + + double WeightII = 0.; // accumulate to equal and opposite the sum of the neighbor weights + + // for each connecting edge + + for (int32 EdgeId : DynamicMesh.VtxEdgesItr(IVertId)) + { + // [v0, v1, t0, t1]: NB: both t0 & t1 exist since IVert isn't a boundary vert. + FIndex4i Edge = DynamicMesh.GetEdge(EdgeId); + + // the other vert in the edge - identifies the matrix column + const int32 JVertId = (Edge[0] == IVertId) ? Edge[1] : Edge[0]; // J - the column + + // Get the cotangents for this edge. + + const int32 Tri0Idx = ToTriIdx[Edge[2]]; + const auto& Tri0Data = TriangleDataArray[Tri0Idx]; + double TanHalfAngleSum = Tri0Data.GetTanHalfAngle(IVertId); + double EdgeLength = FMath::Max(1.e-5, Tri0Data.GetEdgeLenght(EdgeId)); // Clamp the length + + // The second triangle will be invalid if this is an edge! + + TanHalfAngleSum += (Edge[3] != FDynamicMesh3::InvalidID) ? TriangleDataArray[ToTriIdx[Edge[3]]].GetTanHalfAngle(IVertId) : 0.; + + double WeightIJ = TanHalfAngleSum / EdgeLength; + WeightII += WeightIJ; + + const int32 j = ToIndex[JVertId]; + + MatrixTripelList.push_back(MatrixTripletT(i, j, WeightIJ)); + } + + MatrixTripelList.push_back(MatrixTripletT(i, i, -WeightII)); + + + } + + TUniquePtr LaplacianMatrix(new FSparseMatrixD(NumVerts, NumVerts)); + LaplacianMatrix->setFromTriplets(MatrixTripelList.begin(), MatrixTripelList.end()); + LaplacianMatrix->makeCompressed(); + + return LaplacianMatrix; + +} + +TUniquePtr ConstructLaplacian(const ELaplacianWeightScheme Scheme, const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts) +{ + + switch (Scheme) + { + default: + case ELaplacianWeightScheme::Uniform: + return ConstructUniformLaplacian(DynamicMesh, VertexMap, BoundaryVerts); + break; + case ELaplacianWeightScheme::Umbrella: + return ConstructUmbrellaLaplacian(DynamicMesh, VertexMap, BoundaryVerts); + break; + case ELaplacianWeightScheme::Valence: + return ConstructValenceWeightedLaplacian(DynamicMesh, VertexMap, BoundaryVerts); + break; + case ELaplacianWeightScheme::Cotangent: + { + bool bClampWeights = false; + return ConstructCotangentLaplacian(DynamicMesh, VertexMap, bClampWeights, BoundaryVerts); + break; + } + case ELaplacianWeightScheme::ClampedCotangent: + { + bool bClampWeights = true; + return ConstructCotangentLaplacian(DynamicMesh, VertexMap, bClampWeights, BoundaryVerts); + break; + } + case ELaplacianWeightScheme::MeanValue: + return ConstructMeanValueWeightLaplacian(DynamicMesh, VertexMap, BoundaryVerts); + break; + } +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianOperators.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianOperators.h new file mode 100644 index 000000000000..1d9b6ba1fb66 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianOperators.h @@ -0,0 +1,156 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DynamicMesh3.h" + +#include "MeshElementLinearizations.h" + +#include "FSparseMatrixD.h" + +#include "MeshSmoothingUtilities.h" // for ELaplacianWeightScheme + + +/** +* Construct a sparse matrix representation of a uniform weighted Laplacian. +* The uniform weighted Laplacian is defined solely in terms of the connectivity +* of the mesh. Note, by construction this should be a symmetric matrix. +* +* Row i represents the Laplacian at vert_i, the non-zero entries correspond to +* the incident one-ring vertices vert_j. +* +* L_{ij} = 1 if vert_j is in the one-ring of vert_i +* L_{ii} = -Sum{ L_{ij}, j != i} +* +* +* @param DynamicMesh3 The triangle mesh +* @param VertexMap Additional arrays used to map between vertexID and offset in a linear array (i.e. the row) +* @param BoundaryVerts Optionally capture the verts that are on mesh edges. +*/ +TUniquePtr ConstructUniformLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts = NULL); + +/** +* Construct a sparse matrix representation of an umbrella weighted Laplacian. +* This Laplacian is defined solely in terms of the connectivity +* of the mesh. Note, there is no expectation that the resulting matrix will be symmetric. +* +* Row i represents the Laplacian at vert_i, the non-zero entries correspond to +* the incident one-ring vertices vert_j. +* +* L_{ij} = 1 / valence(of i) if vert_j is in the one-ring of vert_i +* L_{ii} = -Sum{ L_{ij}, j != i} = -1 +* +* +* @param DynamicMesh3 The triangle mesh +* @param VertexMap Additional arrays used to map between vertexID and offset in a linear array (i.e. the row) +* @param BoundaryVerts Optionally capture the verts that are on mesh edges. +*/ +TUniquePtr ConstructUmbrellaLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts = NULL); + +/** +* Construct a sparse matrix representation of a valence-weighted Laplacian. +* The valence weighted Laplacian is defined solely in terms of the connectivity +* of the mesh. Note, by construction this should be a symmetric matrix. +* +* Row i represents the Laplacian at vert_i, the non-zero entries correspond to +* the incident one-ring vertices vert_j. +* +* L_{ij} = 1/\sqrt(valence(i) + valence(j)) if vert_j is in the one-ring of vert_i +* L_{ii} = -Sum{ L_{ij}, j != i} +* +* +* @param DynamicMesh3 The triangle mesh +* @param VertexMap Additional arrays used to map between vertexID and offset in a linear array (i.e. the row) +* @param BoundaryVerts Optionally capture the verts that are on mesh edges. +*/ +TUniquePtr ConstructValenceWeightedLaplacian(const FDynamicMesh3& DynamicMesh3, FVertexLinearization& VertexMap, TArray* BoundaryVerts = NULL); + +/** +* Construct a sparse matrix representation using a cotangent-weighted Laplacian. +* NB: there is no reason to expect this to be a symmetric matrix. +* +* @param DynamicMesh3 The triangle mesh +* @param VertexMap Additional arrays used to map between vertexID and offset in a linear array (i.e. the row) +* @param bClampWeights Indicates if the off-diagonal weights should be clamped on construction. +* in practice this is desirable when creating the biharmonic operator, but not the mean curvature flow operator +* @param BoundaryVerts Optionally capture the verts that are on mesh edges. +*/ + +TUniquePtr ConstructCotangentLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, const bool bClampWeights = false, TArray* BoundaryVerts = NULL); + +/** +* Construct a sparse matrix representation using a cotangent-weighted Laplacian. +* but returns the result in two symmetric parts. +* +* (AreaMatrix^{-1}) * L_hat = Cotangent weighted Laplacian. +* +* @return L_hat = Laplacian Without Area weighting. - this will be symmetric +* +* @param On return - diagonal matrix holding the voronoi areas for each vertex. Area 1 is assigned to any edge vertex +* +* +* @param DynamicMesh3 The triangle mesh +* @param VertexMap Additional arrays used to map between vertexID and offset in a linear array (i.e. the row) +* @param BoundaryVerts Optionally capture the verts that are on mesh edges. +*/ + +TUniquePtr ConstructCotangentLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, FSparseMatrixD& AreaMatrix, TArray* BoundaryVerts = NULL); + + + +/** +* Construct a sparse matrix representation using a meanvalue-weighted Laplacian. +* NB: there is no reason to expect this to be a symmetric matrix. +* +* @param DynamicMesh3 The triangle mesh +* @param VertexMap Additional arrays used to map between vertexID and offset in a linear array (i.e. the row) +* @param bClampWeights Indicates if the off-diagonal weights should be clamped on construction. +* in practice this is desirable when creating the biharmonic operator, but not the mean curvature flow operator +* @param BoundaryVerts Optionally capture the verts that are on mesh edges. +*/ + +TUniquePtr ConstructMeanValueWeightLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts = NULL); + +TUniquePtr ConstructLaplacian(const ELaplacianWeightScheme Scheme, const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts = NULL); + + +/** +* Utility to map the enum names for debuging etc. +*/ +FString LaplacianSchemeName(const ELaplacianWeightScheme Scheme); + + +/** +* Only certian laplacian operators are symmetric... +*/ +static bool bIsSymmetricLaplacian(const ELaplacianWeightScheme Scheme) +{ + bool bSymmetric = false; + switch (Scheme) + { + case ELaplacianWeightScheme::ClampedCotangent: + bSymmetric = false; + break; + case ELaplacianWeightScheme::Cotangent: + bSymmetric = false; + break; + case ELaplacianWeightScheme::Umbrella: + bSymmetric = false; + break; + case ELaplacianWeightScheme::MeanValue: + bSymmetric = false; + break; + case ELaplacianWeightScheme::Uniform: + bSymmetric = true; + break; + case ELaplacianWeightScheme::Valence: + bSymmetric = true; + break; + default: + check(0); + } + return bSymmetric; +} + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MatrixSolver.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MatrixSolver.cpp new file mode 100644 index 000000000000..a9091e8b1c30 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MatrixSolver.cpp @@ -0,0 +1,36 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MatrixSolver.h" + +IMatrixSolverBase::~IMatrixSolverBase() {}; +IIterativeMatrixSolverBase::~IIterativeMatrixSolverBase() {}; + +TUniquePtr ContructMatrixSolver(const EMatrixSolverType& MatrixSolverType) +{ + TUniquePtr ResultPtr; + + switch (MatrixSolverType) + { + default: + + case EMatrixSolverType::LU: + ResultPtr.Reset(new FLUMatrixSolver()); + break; + case EMatrixSolverType::QR: + ResultPtr.Reset(new FQRMatrixSolver()); + break; + case EMatrixSolverType::PCG: + ResultPtr.Reset(new FPCGMatrixSolver()); + break; + case EMatrixSolverType::BICGSTAB: + ResultPtr.Reset(new FBiCGMatrixSolver()); + break; +#ifndef EIGEN_MPL2_ONLY + case EMatrixSolverType::LDLT: + ResultPtr.Reset(new FLDLTMatrixSolver()); + break; +#endif + } + + return ResultPtr; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MatrixSolver.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MatrixSolver.h new file mode 100644 index 000000000000..eba5ad5c0efb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MatrixSolver.h @@ -0,0 +1,304 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "FSparseMatrixD.h" +#include "Async/ParallelFor.h" + +#if defined(_MSC_VER) && USING_CODE_ANALYSIS +#pragma warning(push) +#pragma warning(disable : 6011) +#pragma warning(disable : 6387) +#pragma warning(disable : 6313) +#pragma warning(disable : 6294) +#endif +THIRD_PARTY_INCLUDES_START +#include +#include +#include +#include +#include +#include // BiCGSTAB & IncompleteLUT +#ifndef EIGEN_MPL2_ONLY +#include +#endif +#include +THIRD_PARTY_INCLUDES_END +#if defined(_MSC_VER) && USING_CODE_ANALYSIS +#pragma warning(pop) +#endif + +#include "FSOAPositions.h" + + +#include "ProfilingDebugging/ScopedTimers.h" + + +enum class EMatrixSolverType +{ + LU /* SparseLU*/, + QR /* SparseQR */, + BICGSTAB /* Iterative Bi-conjugate gradient */, + PCG /* Pre-conditioned conjugate gradient - requires symmetric positive def.*/, +#ifndef EIGEN_MPL2_ONLY + LDLT /**Not included due to MPL2 */ +#endif +}; + +static FString MatrixSolverName(const EMatrixSolverType SolverType) +{ + FString String; + switch (SolverType) + { + case EMatrixSolverType::LU: + String = FString(TEXT(" Direct LU ")); + break; + case EMatrixSolverType::QR: + String = FString(TEXT(" Direct QR ")); + break; + case EMatrixSolverType::BICGSTAB: + String = FString(TEXT(" Iterative BiConjugate Gradient ")); + break; + case EMatrixSolverType::PCG: + String = FString(TEXT(" Iterative Preconditioned Conjugate Gradient ")); + break; +#ifndef EIGEN_MPL2_ONLY + case EMatrixSolverType::PCG: + String = FString(TEXT(" Direct Cholesky ")); + break; +#endif + default: + check(0); + } + + return String; +} + +class FMatrixSolverSettings +{ +public: + EMatrixSolverType MatrixSolverType; + + // Used by iterative solvers + int32 MaxIterations = 600; + double Tolerance = 1e-4; +}; + + + +// Pure Virtual Base Class used to wrap Eigen Solvers. +class IMatrixSolverBase +{ +public: + typedef typename FSparseMatrixD::Scalar ScalarType; + typedef typename FSOAPositions::VectorType VectorType; + + IMatrixSolverBase() {} + virtual ~IMatrixSolverBase() = 0; + virtual bool bIsIterative() const = 0; + virtual void Solve(const VectorType& BVector, VectorType& SolVector) const = 0; + virtual void Solve(const FSOAPositions& BVectors, FSOAPositions& SolVectors) const = 0; + + virtual void SetUp(const FSparseMatrixD& Matrix, bool bIsSymmetric) = 0; + //virtual void SetIterations(int32 MaxIterations) = 0; + virtual void Reset() = 0; + virtual bool bSucceeded() const = 0; +}; + +// Additional methods particular to the iterative solvers +class IIterativeMatrixSolverBase : public IMatrixSolverBase +{ +public: + IIterativeMatrixSolverBase() {} + + virtual ~IIterativeMatrixSolverBase() = 0; + virtual void SetIterations(int32 MaxIterations) = 0; + virtual void SetTolerance(double Tol) = 0; + virtual void SolveWithGuess(const VectorType& GuessVector, const VectorType& BVector, VectorType& SolVector) const = 0; + virtual void SolveWithGuess(const FSOAPositions& GuessVector, const FSOAPositions& BVector, FSOAPositions& SolVector) const = 0; + +}; + +// Matrix Solver Factory +TUniquePtr ContructMatrixSolver(const EMatrixSolverType& MatrixSolverType); + +template +class TMatrixSolver : public IMatrixSolverBase +{ +public: + typedef DirectSolverType SolverType; + TMatrixSolver() {} + ~TMatrixSolver() override {}; + + bool bIsIterative() const override { return false; } + + void Solve(const VectorType& BVector, VectorType& SolVector) const override + { + checkSlow(bSetup); + SolVector = MatrixSolver.solve(BVector); + } + + void Solve(const FSOAPositions& BVectors, FSOAPositions& SolVectors) const override + { + const bool bForceSingleThreaded = false; + ParallelFor(3, [&](int Dir) + { SolVectors.Array(Dir) = MatrixSolver.solve(BVectors.Array(Dir)); }, bForceSingleThreaded); + } + + void SetUp(const FSparseMatrixD& SparseMatrix, bool bIsSymmetric) override + { + SetSymmetry(bIsSymmetric); + SetUp(SparseMatrix); + } + + void SetSymmetry(bool bIsSymmetric); + + //void SetIterations(int32 MaxIterations) override {} + + void Reset() override + { + //MatrixSolver = FMatrixSolver(); + bSetup = false; + } + + bool bSucceeded() const override + { + return (MatrixSolver.info() == Eigen::ComputationInfo::Success); + } + + +private: + + void SetUp(const FSparseMatrixD& SparseMatrix) + { + // The analyzePattern could be done just once if + // the sparsity pattern of the matrix is fixed. + // But testing indicates that takes little time compared + // with factorizing: e.g. dim 14508 matrix. Analyze 0.145s, Factorize 0.935s + + MatrixSolver.analyzePattern(SparseMatrix); + MatrixSolver.factorize(SparseMatrix); + + bSetup = true; + } +private: + + bool bSetup = false; + DirectSolverType MatrixSolver; +}; + + +/** +* Define wrappers for Direct Matrix Solver Types +*/ +// Timing Tests: +// 7k Tri 3.6k Verts. 10 sets of 3 backsolves 0.17s +// 29k tri 14.5k Verts 1.3s +// 100k tri 51.5k Verts 5.39s +// 127k tri 63.4k Verts 3.0s +typedef TMatrixSolver>> FLUMatrixSolver; +typedef TMatrixSolver>> FQRMatrixSolver; + +#ifndef EIGEN_MPL2_ONLY +// Not included due to MPL2. But in general much faster than standard LU + +// Timing info: +// 29k tris 14.5k verts 10 sets of 3 backsolves - 0.42s Analyze 0.03s Factorize 0.28s +// 45k tris 23k verts with 10 sets of 3 backsolves - 0.8s Analyze 0.08s Factorize 0.54s +// 49k verts with 10 sets of 3 backsolves - 3.35s Analyze 0.17s Factorize 2.67s +// 101k tris 50k verts with 10 sets of 3 backsolves - 1.34s Analyze 0.12s Factorize 0.84s +// 126k tris 63k verts with 10 sets of 3 backsolves - 0.8s Analyze 0.17s Factorize 0.22s +// 205k tris 102k verts with 10 sets of 3 backsolves - 3.5s Analyze 0.41s Factorize 2.2s +typedef TMatrixSolver< Eigen::SimplicialLDLT > FLDLTMatrixSolver +template<> +void TMatrixSolver::SetSymmetry(bool bIsSymmetric) { check(bIsSymmetric); }; + +#endif // EIGEN_MPL2_ONLY + +template<> +inline void TMatrixSolver::SetSymmetry(bool bIsSymmetric) { MatrixSolver.isSymmetric(bIsSymmetric); }; + +template<> +inline void TMatrixSolver::SetSymmetry(bool bIsSymmetric) {}; + + +template +class TIterativeMatrixSolver : public IIterativeMatrixSolverBase +{ +public: + typedef IterativeMatrixSolverType SolverType; + + TIterativeMatrixSolver() { MatrixSolver.setMaxIterations(1000); MatrixSolver.setTolerance(1e-4); } + ~TIterativeMatrixSolver() override {}; + + bool bIsIterative() const override { return true; } + + void Solve(const VectorType& BVector, VectorType& SolVector) const override + { + checkSlow(bSetup); + SolVector = MatrixSolver.solve(BVector); + } + void Solve(const FSOAPositions& BVectors, FSOAPositions& SolVectors) const override + { + const bool bForceSingleThreaded = false; + ParallelFor(3, [&](int Dir) + { SolVectors.Array(Dir) = MatrixSolver.solve(BVectors.Array(Dir)); }, bForceSingleThreaded); + } + + void SolveWithGuess(const VectorType& GuessVector, const VectorType& BVector, VectorType& SolVector) const override + { + + checkSlow(bSetup); + SolVector = MatrixSolver.solveWithGuess(BVector, GuessVector); + } + + void SolveWithGuess(const FSOAPositions& GuessVectors, const FSOAPositions& BVectors, FSOAPositions& SolVectors) const override + { + const bool bForceSingleThreaded = false; + ParallelFor(3, [&](int Dir) + { SolVectors.Array(Dir) = MatrixSolver.solveWithGuess(BVectors.Array(Dir), GuessVectors.Array(Dir)); }, bForceSingleThreaded); + } + void SetUp(const FSparseMatrixD& SparseMatrix, bool bIsSymmetric) override + { + SetUp(SparseMatrix); + } + + void SetIterations(int32 MaxIterations) override { MatrixSolver.setMaxIterations(MaxIterations); } + void SetTolerance(double Tol) override { MatrixSolver.setTolerance(Tol); } + + void Reset() override + { + //MatrixSolver = FMatrixSolver(); + bSetup = false; + } + + bool bSucceeded() const override + { + return (MatrixSolver.info() == Eigen::ComputationInfo::Success); + } + +private: + void SetUp(const FSparseMatrixD& SparseMatrix) + { + MatrixSolver.analyzePattern(SparseMatrix); + MatrixSolver.factorize(SparseMatrix); + + bSetup = true; + + int32 n = Eigen::nbThreads(); + } + +private: + bool bSetup = false; + + IterativeMatrixSolverType MatrixSolver; +}; + +/** +* Define wrappers for Iterative Matrix Solver Types +*/ +//typedef Eigen::IncompleteCholesky< double, Eigen::Lower | Eigen::Upper> FICPreConditioner; +typedef TIterativeMatrixSolver> FPCGMatrixSolver; +typedef TIterativeMatrixSolver >> FBiCGMatrixSolver; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MeshElementLinearizations.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MeshElementLinearizations.h new file mode 100644 index 000000000000..91edb43c8ad7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MeshElementLinearizations.h @@ -0,0 +1,113 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DynamicMesh3.h" + + +/** +* Used linearize the VtxIds in a mesh as a single array and allow mapping from array offset to mesh VtxId. +* Generally, the array offset will correspond to a matrix row when forming a Laplacian +*/ +class FMeshElementLinearization +{ +public: + + FMeshElementLinearization() = default; + + // Lookup ToVtxId(Index) = VtxId; + const TArray& ToId() const { return ToIdMap; } + + // Lookup ToIndex(VtxId) = Index; may return FDynamicMesh3::InvalidID + const TArray& ToIndex() const { return ToIndexMap; } + + int32 NumIds() const { return ToIdMap.Num(); } + + // Following the FDynamicMesh3 convention this is really MaxId + 1 + int32 MaxId() const { return ToIndexMap.Num(); } + + void Empty() { ToIdMap.Empty(); ToIndexMap.Empty(); } + + void Populate(const int32 MaxId, const int32 Count, FRefCountVector::IndexEnumerable ElementItr) + { + ToIndexMap.SetNumUninitialized(MaxId); + ToIdMap.SetNumUninitialized(Count); + + for (int32 i = 0; i < MaxId; ++i) + { + ToIndexMap[i] = FDynamicMesh3::InvalidID; + } + + // create the mapping + { + int32 N = 0; + for (int32 Id : ElementItr) + { + ToIdMap[N] = Id; + ToIndexMap[Id] = N; + N++; + } + } + } + +protected: + TArray ToIdMap; + TArray ToIndexMap; + +private: + FMeshElementLinearization(const FMeshElementLinearization&); +}; + + +/** +* Used linearize the VtxIds in a mesh as a single array and allow mapping from array offset to mesh VtxId. +* Generally, the array offset will correspond to a matrix row when forming a Laplacian +*/ +class FVertexLinearization : public FMeshElementLinearization +{ +public: + FVertexLinearization() = default; + FVertexLinearization(const FDynamicMesh3& DynamicMesh) + { + Reset(DynamicMesh); + } + + void Reset(const FDynamicMesh3& DynamicMesh) + { + Empty(); + FMeshElementLinearization::Populate(DynamicMesh.MaxVertexID(), DynamicMesh.VertexCount(), DynamicMesh.VertexIndicesItr()); + } + + int32 NumVerts() const { return FMeshElementLinearization::NumIds(); } + +private: + FVertexLinearization(const FVertexLinearization&); +}; + +/** +* Used linearize the TriIds in a mesh as a single array and allow mapping from array offset to mesh TriId. +* +*/ +class FTriangleLinearization : public FMeshElementLinearization +{ +public: + FTriangleLinearization() = default; + + FTriangleLinearization(const FDynamicMesh3& DynamicMesh) + { + Reset(DynamicMesh); + } + + void Reset(const FDynamicMesh3& DynamicMesh) + { + Empty(); + FMeshElementLinearization::Populate(DynamicMesh.MaxTriangleID(), DynamicMesh.TriangleCount(), DynamicMesh.TriangleIndicesItr()); + } + + int32 NumTris() const { return FMeshElementLinearization::NumIds(); } + + +private: + FTriangleLinearization(const FTriangleLinearization&); +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MeshSolverUtilitiesModule.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MeshSolverUtilitiesModule.cpp new file mode 100644 index 000000000000..32b7fb258ab6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MeshSolverUtilitiesModule.cpp @@ -0,0 +1,19 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshSolverUtilitiesModule.h" + +#define LOCTEXT_NAMESPACE "FMeshSolverUtilitiesModule" + +void FMeshSolverUtilitiesModule::StartupModule() +{ +} + +void FMeshSolverUtilitiesModule::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(FMeshSolverUtilitiesModule, MeshSolverUtilities) \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Public/MeshSmoothingUtilities.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Public/MeshSmoothingUtilities.h new file mode 100644 index 000000000000..f7c80bb81b9b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Public/MeshSmoothingUtilities.h @@ -0,0 +1,162 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DynamicMesh3.h" + + + +enum class ELaplacianWeightScheme +{ + Uniform, + Umbrella, + Valence, + MeanValue, + Cotangent, + ClampedCotangent +}; + +namespace MeshSmoothingOperators +{ + /** + * + * Note: for discussion of implicit / explicit integration of diffusion and biharmonic equations + * see "Implicit Fairing of Irregular Meshes using Diffusion and Curvature Flow" - M Desbrun 99. + * although the following suggests an additional source term could be included in the implicit solve for better accuracy. + * or "Generalized Surface Flows for Mesh Processing" Eckstein et al. 2007 + */ + + /** + * This is equivalent to taking a single backward Euler time step of bi-harmonic diffusion + * where L is the Laplacian (Del^2) , and L^T L is an approximation of the Del^4. + * + * dp/dt = - k*k L^T L[p] + * with + * weight = 1 / (k * Sqrt[dt] ) + * + * p^{n+1} + dt * k * k L^TL [p^{n+1}] = p^{n} + * + * re-write as + * L^TL[p^{n+1}] + weight * weight p^{n+1} = weight * weight p^{n} + + * + * The result is returned in the PositionArray + */ + void MESHSOLVERUTILITIES_API ComputeSmoothing_BiHarmonic(const ELaplacianWeightScheme WeightingScheme, const FDynamicMesh3& OriginalMesh, + const double Speed, const double Weight, const int32 NumIterations, TArray& PositionArray); + + void MESHSOLVERUTILITIES_API ComputeSmoothing_ImplicitBiHarmonicPCG(const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& OriginalMesh, + const double Speed, const double Weight, const int32 MaxIterations, TArray& PositionArray); + + /** + * This is equivalent to forward or backward Euler time steps of the diffusion equation + * + * dp/dt = L[p] + * + * p^{n+1} = p^{n} + dt L[p^{n}] + * + * with dt = Speed / Max(|w_ii|) + * + * here w_ii are the diagonal values of L + */ + void MESHSOLVERUTILITIES_API ComputeSmoothing_Diffusion(const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& OriginalMesh, bool bForwardEuler, + const double Speed, double Weight, const int32 NumIterations, TArray& PositionArray); + +} + +namespace MeshDeformingOperators +{ + class MESHSOLVERUTILITIES_API IConstrainedMeshOperator + { + public: + + virtual ~IConstrainedMeshOperator() {}; + + // Add or update a constraint associated with VtxId + virtual void AddConstraint(const int32 VtxId, const double Weight, const FVector3d& Position, const bool bPostFix) = 0; + + // Update or create a constraint position associated with @param VtxId + // @return true if a constraint weight is associated with @param VtxId + virtual bool UpdateConstraintPosition(const int32 VtxId, const FVector3d& Position, const bool bPostFix) = 0; + + // Update or create a constraint weight associated with @param VtxId + // @return true if a constraint position is associated with @param VtxId + virtual bool UpdateConstraintWeight(const int32 VtxId, const double Weight) = 0; + + // Clear all constraints (Positions and Weights) + virtual void ClearConstraints() = 0; + + // Clear all Constraint Weights + virtual void ClearConstraintWeights() = 0; + + // Clear all Constraint Positions + virtual void ClearConstraintPositions() = 0; + + + // Test if a non-zero weighted constraint is associated with VtxId + virtual bool IsConstrained(const int32 VtxId) const = 0; + + // Returns the vertex locations of the deformed mesh. + // Note the array may have empty elements as the index matches the Mesh based VtxId. PositionBuffer[VtxId] = Pos. + virtual bool Deform(TArray& PositionBuffer) = 0; + }; + + /** + * Solves the linear system for p_vec + * + * ( Transpose(L) * L + (0 0 ) ) p_vec = source_vec + ( 0 ) + * ( (0 lambda^2) ) ( lambda^2 c_vec ) + * + * where: L := laplacian for the mesh, + * source_vec := Transpose(L)*L mesh_vertex_positions + * lambda := weights + * c_vec := constrained positions + * + * Expected Use: + * + * // Create Deformation Solver from Mesh + * TUniquePtr MeshDeformer = ConstructConstrainedMeshDeformer(ELaplacianWeightScheme::ClampedCotangent, DynamicMesh); + * + * // Add constraints. + * for.. + * { + * int32 VtxId = ..; double Weight = ..; FVector3d TargetPos = ..; bool bPostFix = ...; + * MeshDeformer->AddConstraint(VtxId, Weight, TargetPos, bPostFix); + * } + * + * // Solve for new mesh vertex locations + * TArray PositionBuffer; + * MeshDeformer->Deform(PositionBuffer); + * + * // Update Mesh? for (int32 VtxId : DynamicMesh.VertexIndices()) DynamicMesh.SetVertex(VtxId, PositionBuffer[VtxId]); + * ... + * + * // Update constraint positions. + * for .. + * { + * int32 VtxId = ..; FVector3d TargetPos = ..; bool bPostFix = ...; + * MeshDeformer->UpdateConstraintPosition(VtxId, TargetPos, bPostFix); + * } + * + * // Solve for new vertex locations. + * MeshDeformer->Deform(PositionBuffer); + * // Update Mesh? + */ + TUniquePtr MESHSOLVERUTILITIES_API ConstructConstrainedMeshDeformer(const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& DynamicMesh); + + /** + * Solves the linear system for p_vec + * + * ( Transpose(L) * L + (0 0 ) ) p_vec = ( 0 ) + * ( (0 lambda^2) ) ( lambda^2 c_vec ) + * + * where: L := laplacian for the mesh, + * lambda := weights + * c_vec := constrained positions + * + * Expected Use: same as the ConstrainedMeshDeformer above. + * + */ + TUniquePtr MESHSOLVERUTILITIES_API ConstructConstrainedMeshSmoother(const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& DynamicMesh); +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Public/MeshSolverUtilitiesModule.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Public/MeshSolverUtilitiesModule.h new file mode 100644 index 000000000000..1c7e5dfa4f94 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Public/MeshSolverUtilitiesModule.h @@ -0,0 +1,15 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FMeshSolverUtilitiesModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/WildMagic.tps b/Engine/Plugins/Experimental/GeometryProcessing/WildMagic.tps new file mode 100644 index 000000000000..197752188e99 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/WildMagic.tps @@ -0,0 +1,13 @@ + + + WildMagic 5.17 + /Engine/Plugins/Experimental/GeometryProcessing + 2D/3D geometry processing and geometric modeling. + https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt + + Licensees + Git + P4 + + /Engine/Source/ThirdParty/Licenses/WildMagic_License.txt + \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/geometry3Sharp.tps b/Engine/Plugins/Experimental/GeometryProcessing/geometry3Sharp.tps new file mode 100644 index 000000000000..d01869f33319 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/geometry3Sharp.tps @@ -0,0 +1,13 @@ + + + geometry3Sharp + /Engine/Plugins/Experimental/GeometryProcessing + 2D/3D geometry processing and geometric modeling. + https://github.com/gradientspace/geometry3Sharp/blob/master/LICENSE + + Licensees + Git + P4 + + /Engine/Source/ThirdParty/Licenses/geometry3Sharp_License.txt + \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/geometry3cpp.tps b/Engine/Plugins/Experimental/GeometryProcessing/geometry3cpp.tps new file mode 100644 index 000000000000..18e59c0597c6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/geometry3cpp.tps @@ -0,0 +1,13 @@ + + + geometry3cpp + /Engine/Plugins/Experimental/GeometryProcessing + 2D/3D geometry processing and geometric modeling. + https://github.com/gradientspace/geometry3cpp/blob/master/LICENSE + + Licensees + Git + P4 + + /Engine/Source/ThirdParty/Licenses/geometry3cpp_License.txt + \ No newline at end of file diff --git a/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Classes/WebSocketConnection.h b/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Classes/WebSocketConnection.h index 53f6a37aa5d9..05a4a1bb4e36 100644 --- a/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Classes/WebSocketConnection.h +++ b/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Classes/WebSocketConnection.h @@ -20,10 +20,6 @@ class HTML5NETWORKING_API UWebSocketConnection : public UNetConnection virtual void LowLevelSend(void* Data, int32 CountBits, FOutPacketTraits& Traits) override; FString LowLevelGetRemoteAddress(bool bAppendPort = false) override; FString LowLevelDescribe() override; - virtual int32 GetAddrAsInt(void) override; - virtual int32 GetAddrPort(void) override; - virtual TSharedPtr GetInternetAddr() override; - virtual FString RemoteAddressToString() override; virtual void Tick(); virtual void FinishDestroy(); virtual void ReceivedRawPacket(void* Data,int32 Count); diff --git a/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Classes/WebSocketNetDriver.h b/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Classes/WebSocketNetDriver.h index ae259efb475c..d3b805727710 100644 --- a/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Classes/WebSocketNetDriver.h +++ b/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Classes/WebSocketNetDriver.h @@ -27,7 +27,7 @@ class HTML5NETWORKING_API UWebSocketNetDriver : public UNetDriver virtual bool InitConnect(FNetworkNotify* InNotify, const FURL& ConnectURL, FString& Error) override; virtual bool InitListen(FNetworkNotify* InNotify, FURL& LocalURL, bool bReuseAddressAndPort, FString& Error) override; virtual void TickDispatch(float DeltaTime) override; - virtual void LowLevelSend(FString Address, void* Data, int32 CountBits, FOutPacketTraits& Traits) override; + virtual void LowLevelSend(TSharedPtr Address, void* Data, int32 CountBits, FOutPacketTraits& Traits) override; virtual FString LowLevelGetNetworkNumber() override; virtual void LowLevelDestroy() override; virtual bool IsNetResourceValid(void) override; diff --git a/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Private/WebSocket.cpp b/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Private/WebSocket.cpp index 38a7f403b1f9..de8c095cc5e2 100644 --- a/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Private/WebSocket.cpp +++ b/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Private/WebSocket.cpp @@ -90,7 +90,7 @@ FWebSocket::FWebSocket( SockFd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (SockFd == -1) { - UE_LOG(LogHTML5Networking, Error, TEXT("Socket creationg failed ")); + UE_LOG(LogHTML5Networking, Error, TEXT("Socket creation failed ")); } else { @@ -101,9 +101,10 @@ FWebSocket::FWebSocket( #endif + memset(&RemoteAddr, 0, sizeof(RemoteAddr)); + // Windows XP does not have support for inet_pton #if PLATFORM_WINDOWS && _WIN32_WINNT <= 0x0502 - memset(&RemoteAddr, 0, sizeof(RemoteAddr)); int32 SizeOfRemoteAddr = sizeof(RemoteAddr); // Force ServerAddress into non-const array. API doesn't modify contents but old API still requires non-const string @@ -112,24 +113,20 @@ FWebSocket::FWebSocket( UE_LOG(LogHTML5Networking, Warning, TEXT("WSAStringToAddress failed ")); return; } - - RemoteAddr.sin_family = AF_INET; - RemoteAddr.sin_port = htons(ServerAddress.GetPort()); #else - memset(&RemoteAddr, 0, sizeof(RemoteAddr)); - RemoteAddr.sin_family = AF_INET; - RemoteAddr.sin_port = htons(ServerAddress.GetPort()); - if (inet_pton(AF_INET, TCHAR_TO_ANSI(*ServerAddress.ToString(false)), &RemoteAddr.sin_addr) != 1) { - UE_LOG(LogHTML5Networking, Warning, TEXT("inet_pton failed ")); + UE_LOG(LogHTML5Networking, Warning, TEXT("inet_pton failed to %s"), *ServerAddress.ToString(false)); return; } #endif + RemoteAddr.sin_family = AF_INET; + RemoteAddr.sin_port = htons(ServerAddress.GetPort()); + #if !USE_LIBWEBSOCKET // HTML5 uses BSD network API int Ret = connect(SockFd, (struct sockaddr *)&RemoteAddr, sizeof(RemoteAddr)); - UE_LOG(LogHTML5Networking, Warning, TEXT(" Connect socket returned %d"), Ret); + UE_LOG(LogHTML5Networking, Warning, TEXT(" Connect socket returned %d to %s. Error Code: %d"), Ret, *ServerAddress.ToString(false), ((Ret != 0) ? errno : 0)); #endif } @@ -194,11 +191,6 @@ FString FWebSocket::RemoteEndPoint(bool bAppendPort) return remote; } -struct sockaddr_in* FWebSocket::GetRemoteAddr() -{ - return &RemoteAddr; -} - FString FWebSocket::LocalEndPoint(bool bAppendPort) { #if USE_LIBWEBSOCKET @@ -305,6 +297,19 @@ void FWebSocket::Flush() }; } +TArray FWebSocket::GetRawRemoteAddr(int32& OutPort) +{ + OutPort = ntohs(RemoteAddr.sin_port); + TArray RawBuffer; + uint32 IntAddr = RemoteAddr.sin_addr.s_addr; + RawBuffer.Add((IntAddr >> 0) & 0xFF); + RawBuffer.Add((IntAddr >> 8) & 0xFF); + RawBuffer.Add((IntAddr >> 16) & 0xFF); + RawBuffer.Add((IntAddr >> 24) & 0xFF); + + return RawBuffer; +} + void FWebSocket::SetConnectedCallBack(FWebsocketInfoCallBack CallBack) { ConnectedCallBack = CallBack; diff --git a/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Private/WebSocket.h b/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Private/WebSocket.h index abf2647c43ba..ac82705a925a 100644 --- a/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Private/WebSocket.h +++ b/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Private/WebSocket.h @@ -44,9 +44,10 @@ public: void Flush(); /** Helper functions to describe end points. */ + TArray GetRawRemoteAddr(int32& OutPort); FString RemoteEndPoint(bool bAppendPort); FString LocalEndPoint(bool bAppendPort); - struct sockaddr_in* GetRemoteAddr(); + struct sockaddr_in* GetRemoteAddr() { return &RemoteAddr; } // this was made public because of cross-platform build issues public: diff --git a/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Private/WebSocketNetDriver.cpp b/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Private/WebSocketNetDriver.cpp index b71df38d8bca..3ac7e92cc98a 100644 --- a/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Private/WebSocketNetDriver.cpp +++ b/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Private/WebSocketNetDriver.cpp @@ -106,6 +106,8 @@ bool UWebSocketNetDriver::InitListen(FNetworkNotify* InNotify, FURL& LocalURL, b WebSocketServer->Tick(); LocalURL.Port = WebSocketPort; + LocalAddr = GetSocketSubsystem()->GetLocalBindAddr(*GLog); + LocalAddr->SetPort(WebSocketPort); UE_LOG(LogHTML5Networking, Log, TEXT("%s WebSocketNetDriver listening on port %i"), *GetDescription(), LocalURL.Port); // server has no server connection. @@ -121,17 +123,9 @@ void UWebSocketNetDriver::TickDispatch(float DeltaTime) WebSocketServer->Tick(); } -void UWebSocketNetDriver::LowLevelSend(FString Address, void* Data, int32 CountBits, FOutPacketTraits& Traits) +void UWebSocketNetDriver::LowLevelSend(TSharedPtr Address, void* Data, int32 CountBits, FOutPacketTraits& Traits) { - bool bValidAddress = !Address.IsEmpty(); - TSharedRef RemoteAddr = GetSocketSubsystem()->CreateInternetAddr(); - - if (bValidAddress) - { - RemoteAddr->SetIp(*Address, bValidAddress); - } - - if (bValidAddress) + if (Address.IsValid() && Address->IsValid()) { const uint8* DataToSend = reinterpret_cast(Data); @@ -159,7 +153,7 @@ void UWebSocketNetDriver::LowLevelSend(FString Address, void* Data, int32 CountB for (int32 i = 0; iLowLevelGetRemoteAddress(true) == Address ) ) + if (Connection && ( Connection->LowLevelGetRemoteAddress(true) == Address->ToString(true) ) ) { Connection->GetWebSocket()->Send((uint8*)DataToSend, FMath::DivideAndRoundUp(CountBits, 8)); break; @@ -169,13 +163,13 @@ void UWebSocketNetDriver::LowLevelSend(FString Address, void* Data, int32 CountB } else { - UE_LOG(LogNet, Warning, TEXT("UWebSocketNetDriver::LowLevelSend: Invalid send address '%s'"), *Address); + UE_LOG(LogNet, Warning, TEXT("UWebSocketNetDriver::LowLevelSend: Invalid send address '%s'"), *Address->ToString(false)); } } FString UWebSocketNetDriver::LowLevelGetNetworkNumber() { - return WebSocketServer->Info(); + return WebSocketServer != nullptr ? WebSocketServer->Info() : FString(TEXT("")); } void UWebSocketNetDriver::LowLevelDestroy() @@ -225,9 +219,9 @@ void UWebSocketNetDriver::OnWebSocketClientConnected(FWebSocket* ClientWebSocket check(Connection); TSharedRef InternetAddr = GetSocketSubsystem()->CreateInternetAddr(); - bool Ok; + int32 AddressPort = 0; - InternetAddr->SetIp(*(ClientWebSocket->RemoteEndPoint(false)),Ok); + InternetAddr->SetRawIp(ClientWebSocket->GetRawRemoteAddr(AddressPort)); InternetAddr->SetPort(0); Connection->SetWebSocket(ClientWebSocket); Connection->InitRemoteConnection(this, NULL, FURL(), *InternetAddr, USOCK_Open); diff --git a/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Private/WebsocketConnection.cpp b/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Private/WebsocketConnection.cpp index 01443b315732..fc7daf307600 100644 --- a/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Private/WebsocketConnection.cpp +++ b/Engine/Plugins/Experimental/HTML5Networking/Source/HTML5Networking/Private/WebsocketConnection.cpp @@ -171,19 +171,24 @@ void UWebSocketConnection::ReceivedRawPacket(void* Data,int32 Count) if ( bChallengeHandshake ) { // Process all incoming packets. - if (Driver->ConnectionlessHandler.IsValid() && Driver->StatelessConnectComponent.IsValid()) + if (Driver->ConnectionlessHandler.IsValid() && Driver->StatelessConnectComponent.IsValid() && Driver->GetSocketSubsystem() != nullptr) { + int32 Port; + TSharedPtr IncomingAddress = Driver->GetSocketSubsystem()->CreateInternetAddr(); + IncomingAddress->SetRawIp(WebSocket->GetRawRemoteAddr(Port)); + IncomingAddress->SetPort(Port); + const ProcessedPacket UnProcessedPacket = - Driver->ConnectionlessHandler->IncomingConnectionless(LowLevelGetRemoteAddress(true), DataRef, Count); + Driver->ConnectionlessHandler->IncomingConnectionless(IncomingAddress, DataRef, Count); TSharedPtr StatelessConnect = Driver->StatelessConnectComponent.Pin(); bool bRestartedHandshake = false; - if (!UnProcessedPacket.bError && StatelessConnect->HasPassedChallenge(LowLevelGetRemoteAddress(true), bRestartedHandshake) && + if (!UnProcessedPacket.bError && StatelessConnect->HasPassedChallenge(IncomingAddress, bRestartedHandshake) && !bRestartedHandshake) { - UE_LOG(LogNet, Log, TEXT("Server accepting post-challenge connection from: %s"), *LowLevelGetRemoteAddress(true)); + UE_LOG(LogNet, Log, TEXT("Server accepting post-challenge connection from: %s"), *(IncomingAddress->ToString(false))); // Set the initial packet sequence from the handshake data if (StatelessConnectComponent.IsValid()) { @@ -220,30 +225,3 @@ void UWebSocketConnection::ReceivedRawPacket(void* Data,int32 Count) UNetConnection::ReceivedRawPacket(DataRef,Count); } - -int32 UWebSocketConnection::GetAddrAsInt() -{ - // Get the host byte order ip addr - struct sockaddr_in* sock = WebSocket->GetRemoteAddr(); - return (int32)ntohl(sock->sin_addr.s_addr); -} - -int32 UWebSocketConnection::GetAddrPort() -{ - // Get the host byte order ip port - struct sockaddr_in* sock = WebSocket->GetRemoteAddr(); - return (int32)ntohs(sock->sin_port); -} - -TSharedPtr UWebSocketConnection::GetInternetAddr() -{ - struct sockaddr_in* sock = WebSocket->GetRemoteAddr(); - - // @todo #JIRA UENET-883: This should be based on NetConnection.RemoteAddr, when moved down from IPConnection - return ISocketSubsystem::Get()->CreateInternetAddr((int32)ntohl(sock->sin_addr.s_addr), (int32)ntohs(sock->sin_port)); -} - -FString UWebSocketConnection::RemoteAddressToString() -{ - return WebSocket->RemoteEndPoint(true); -} diff --git a/Engine/Plugins/Experimental/PlatformCrypto/PlatformCrypto.uplugin b/Engine/Plugins/Experimental/PlatformCrypto/PlatformCrypto.uplugin index 7b516b4bb508..066d19bc24d3 100644 --- a/Engine/Plugins/Experimental/PlatformCrypto/PlatformCrypto.uplugin +++ b/Engine/Plugins/Experimental/PlatformCrypto/PlatformCrypto.uplugin @@ -1,82 +1,83 @@ { - "FileVersion" : 3, - "Version" : 1, - "VersionName" : "1.0", - "FriendlyName" : "Platform Cryptography Plugin", - "Description" : "Exposes a unified API for cryptography functionality provided by the platform, if available. Otherwise, interfaces with OpenSSL.", - "Category" : "Misc", - "CreatedBy" : "Epic Games, Inc.", - "CreatedByURL" : "http://epicgames.com", - "DocsURL" : "", - "MarketplaceURL" : "", - "SupportURL" : "", - "EnabledByDefault" : false, - "CanContainContent" : false, - "IsBetaVersion" : false, - "Installed" : false, - "Modules" : - [ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "Platform Cryptography Plugin", + "Description": "Exposes a unified API for cryptography functionality provided by the platform, if available. Otherwise, interfaces with OpenSSL.", + "Category": "Misc", + "CreatedBy": "Epic Games, Inc.", + "CreatedByURL": "http://epicgames.com", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "EnabledByDefault": true, + "CanContainContent": false, + "IsBetaVersion": false, + "Installed": false, + "SupportedPrograms": [ "UnrealPak" ], + "Modules": [ { "Name": "PlatformCrypto", - "Type": "Runtime", - "LoadingPhase": "Default", - "WhitelistPlatforms": [ - "Android", - "IOS", - "Mac", - "Win32", - "Win64", - "Linux", - "PS4", - "XboxOne", - "Switch" - ], - "BlacklistPlatforms": [ - "TVOS" - ] + "Type": "RuntimeAndProgram", + "LoadingPhase": "EarliestPossible", + "WhitelistPlatforms": [ + "Android", + "IOS", + "Mac", + "Win32", + "Win64", + "Linux", + "PS4", + "XboxOne", + "Switch" + ], + "BlacklistPlatforms": [ + "TVOS" + ], + "WhitelistPrograms": [ "UnrealPak" ] }, { "Name": "PlatformCryptoOpenSSL", - "Type": "Runtime", - "LoadingPhase": "Default", - "WhitelistPlatforms": [ - "Android", - "IOS", - "Mac", - "Win32", - "Win64", - "Linux", - "PS4" - ], - "BlacklistPlatforms": [ - "TVOS" - ] + "Type": "RuntimeAndProgram", + "LoadingPhase": "EarliestPossible", + "WhitelistPlatforms": [ + "Android", + "IOS", + "Mac", + "Win32", + "Win64", + "Linux", + "PS4" + ], + "BlacklistPlatforms": [ + "TVOS" + ] }, { - "Name" : "PlatformCryptoBCrypt", - "Type" : "Runtime", - "LoadingPhase" : "Default", - "WhitelistPlatforms": [ - "XboxOne" - ], - "BlacklistPlatforms": [ - "Android", - "IOS", - "TVOS" - ] + "Name": "PlatformCryptoBCrypt", + "Type": "RuntimeAndProgram", + "LoadingPhase": "EarliestPossible", + "WhitelistPlatforms": [ + "XboxOne" + ], + "BlacklistPlatforms": [ + "Android", + "IOS", + "TVOS" + ] }, { "Name": "PlatformCryptoSwitch", - "Type": "Runtime", - "LoadingPhase": "Default", - "WhitelistPlatforms": [ - "Switch" - ], - "BlacklistPlatforms": [ - "Android", - "IOS", - "TVOS" - ] + "Type": "RuntimeAndProgram", + "LoadingPhase": "EarliestPossible", + "WhitelistPlatforms": [ + "Switch" + ], + "BlacklistPlatforms": [ + "Android", + "IOS", + "TVOS" + ] } ] } \ No newline at end of file diff --git a/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCrypto/Private/PlatformCrypto.cpp b/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCrypto/Private/PlatformCrypto.cpp index ea09fe5d5ec8..19df48be71c6 100644 --- a/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCrypto/Private/PlatformCrypto.cpp +++ b/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCrypto/Private/PlatformCrypto.cpp @@ -3,30 +3,87 @@ #include "CoreMinimal.h" #include "Modules/ModuleManager.h" #include "IPlatformCrypto.h" - #include "PlatformCryptoIncludes.h" +#include "Features/IModularFeatures.h" +#include "Misc/IEngineCrypto.h" -class FPlatformCrypto : public IPlatformCrypto +class FPlatformCryptoModularFeature : public IEngineCrypto { - /** IModuleInterface implementation */ - virtual void StartupModule() override; - virtual void ShutdownModule() override; +public: + + FPlatformCryptoModularFeature() + { + IModularFeatures::Get().RegisterModularFeature(IEngineCrypto::GetFeatureName(), this); + } + + virtual ~FPlatformCryptoModularFeature() + { + Context.Reset(); + IModularFeatures::Get().UnregisterModularFeature(IEngineCrypto::GetFeatureName(), this); + } + + /** IEngineCrypto implementation */ + virtual FRSAKeyHandle CreateRSAKey(const TArrayView InPublicExponent, const TArrayView InPrivateExponent, const TArrayView InModulus) override + { + return GetContext()->CreateKey_RSA(InPublicExponent, InPrivateExponent, InModulus); + } + + virtual void DestroyRSAKey(FRSAKeyHandle InKey) override + { + GetContext()->DestroyKey_RSA(InKey); + } + + virtual int32 GetKeySize(FRSAKeyHandle InKey) override + { + return GetContext()->GetKeySize_RSA(InKey); + } + + virtual int32 GetMaxDataSize(FRSAKeyHandle InKey) override + { + return GetContext()->GetMaxDataSize_RSA(InKey); + } + + virtual int32 EncryptPublic(const TArrayView InSource, TArray& OutDestination, FRSAKeyHandle InKey) override + { + return GetContext()->EncryptPublic_RSA(InSource, OutDestination, InKey); + } + + virtual int32 EncryptPrivate(const TArrayView InSource, TArray& OutDestination, FRSAKeyHandle InKey) override + { + return GetContext()->EncryptPrivate_RSA(InSource, OutDestination, InKey); + } + + virtual int32 DecryptPublic(const TArrayView InSource, TArray& OutDestination, FRSAKeyHandle InKey) override + { + return GetContext()->DecryptPublic_RSA(InSource, OutDestination, InKey); + } + + virtual int32 DecryptPrivate(const TArrayView InSource, TArray& OutDestination, FRSAKeyHandle InKey) override + { + return GetContext()->DecryptPrivate_RSA(InSource, OutDestination, InKey); + } + +private: + + TUniquePtr& GetContext() + { + if (!Context.IsValid()) + { + Context = IPlatformCrypto::Get().CreateContext(); + } + + return Context; + } + + /** Content used by IEngineCrypto implementation */ + TUniquePtr Context; }; -IMPLEMENT_MODULE( FPlatformCrypto, PlatformCrypto ) +FPlatformCryptoModularFeature GPlatformCryptoModularFeature; - - -void FPlatformCrypto::StartupModule() -{ -} - - -void FPlatformCrypto::ShutdownModule() -{ -} +IMPLEMENT_MODULE(FDefaultModuleImpl, PlatformCrypto) TUniquePtr IPlatformCrypto::CreateContext() { return MakeUnique(); -} +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCrypto/Public/PlatformCryptoTypes.h b/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCrypto/Public/PlatformCryptoTypes.h index 97e039236868..94d53fd8cd6f 100644 --- a/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCrypto/Public/PlatformCryptoTypes.h +++ b/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCrypto/Public/PlatformCryptoTypes.h @@ -4,6 +4,7 @@ #include "CoreMinimal.h" #include "Stats/Stats.h" +#include "Misc/IEngineCrypto.h" /** * Generic result type for cryptographic functions. @@ -14,11 +15,6 @@ enum class EPlatformCryptoResult Failure }; -/** - * Generic handle to an RSA key - */ -typedef void* TPlatformCryptoRSAKey; - /** * Stat group for implementations to use. */ diff --git a/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCryptoOpenSSL/Private/EncryptionContextOpenSSL.cpp b/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCryptoOpenSSL/Private/EncryptionContextOpenSSL.cpp index bbfcf6187f9c..f3c0fa7b5e0c 100644 --- a/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCryptoOpenSSL/Private/EncryptionContextOpenSSL.cpp +++ b/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCryptoOpenSSL/Private/EncryptionContextOpenSSL.cpp @@ -4,11 +4,14 @@ #include #include +#include DEFINE_LOG_CATEGORY_STATIC(LogPlatformCryptoOpenSSL, Warning, All); static const int32 AES256_KeySizeInBytes = 32; static const int32 AES256_BlockSizeInBytes = 16; +static const int32 AES256_IVSizeInBytes = 12; +static const int32 AES256_AuthTagSizeInBytes = 16; class FScopedEVPContext { @@ -131,6 +134,157 @@ TArray FEncryptionContextOpenSSL::Decrypt_AES_256_ECB(const TArrayView FEncryptionContextOpenSSL::Encrypt_AES_256_GCM(const TArrayView Plaintext, const TArrayView Key, const TArrayView IV, TArray& OutAuthTag, EPlatformCryptoResult& OutResult) +{ + DECLARE_SCOPE_CYCLE_COUNTER(TEXT("OpenSSL AES256GCM Encrypt"), STAT_OpenSSL_AES_GCM_Encrypt, STATGROUP_PlatformCrypto); + + OutResult = EPlatformCryptoResult::Failure; + + if (Key.Num() != AES256_KeySizeInBytes) + { + UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FEncryptionContextOpenSSL::Encrypt_AES_256_GCM: Key size %d is not the expected size %d."), Key.Num(), AES256_KeySizeInBytes); + return TArray(); + } + + FScopedEVPContext Context; + if (Context.Get() == nullptr) + { + UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FEncryptionContextOpenSSL::Encrypt_AES_256_GCM: failed to create EVP context.")); + return TArray(); + } + + const int InitResult = EVP_EncryptInit_ex(Context.Get(), EVP_aes_256_gcm(), nullptr, Key.GetData(), IV.GetData()); + if (InitResult != 1) + { + UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FEncryptionContextOpenSSL::Encrypt_AES_256_GCM: EVP_EncryptInit_ex failed.")); + return TArray(); + } + + TArray Ciphertext; + Ciphertext.SetNumUninitialized(Plaintext.Num() + AES256_BlockSizeInBytes); // Allow for up to a block of padding. + + int OutLength = 0; + const int UpdateResult = EVP_EncryptUpdate(Context.Get(), Ciphertext.GetData(), &OutLength, Plaintext.GetData(), Plaintext.Num()); + if (UpdateResult != 1) + { + UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FEncryptionContextOpenSSL::Encrypt_AES_256_GCM: EVP_EncryptUpdate failed.")); + return TArray(); + } + + int FinalizeLength = 0; + const int FinalizeResult = EVP_EncryptFinal_ex(Context.Get(), Ciphertext.GetData() + OutLength, &FinalizeLength); + if (FinalizeResult != 1) + { + UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FEncryptionContextOpenSSL::Encrypt_AES_256_GCM: EVP_EncryptFinal_ex failed.")); + return TArray(); + } + + OutAuthTag.Reset(); + OutAuthTag.AddUninitialized(AES256_AuthTagSizeInBytes); + + const int GetTagResult = EVP_CIPHER_CTX_ctrl(Context.Get(), EVP_CTRL_GCM_GET_TAG, OutAuthTag.Num(), OutAuthTag.GetData()); + if (GetTagResult != 1) + { + UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FEncryptionContextOpenSSL::Encrypt_AES_256_GCM: EVP_CIPHER_CTX_ctrl failed.")); + return TArray(); + } + + Ciphertext.SetNum(OutLength + FinalizeLength); + + OutResult = EPlatformCryptoResult::Success; + return Ciphertext; +} + +TArray FEncryptionContextOpenSSL::Decrypt_AES_256_GCM(const TArrayView Ciphertext, const TArrayView Key, const TArrayView IV, const TArrayView AuthTag, EPlatformCryptoResult& OutResult) +{ + DECLARE_SCOPE_CYCLE_COUNTER(TEXT("OpenSSL AES256GCM Decrypt"), STAT_OpenSSL_AES_GCM_Decrypt, STATGROUP_PlatformCrypto); + + OutResult = EPlatformCryptoResult::Failure; + + if (Key.Num() != AES256_KeySizeInBytes) + { + UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FEncryptionContextOpenSSL::Decrypt_AES_256_GCM: Key size %d is not the expected size %d."), Key.Num(), AES256_KeySizeInBytes); + return TArray(); + } + + if (IV.Num() != AES256_IVSizeInBytes) + { + UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FEncryptionContextOpenSSL::Decrypt_AES_256_GCM: IV size %d is not the expected size %d."), IV.Num(), AES256_IVSizeInBytes); + return TArray(); + } + + if (AuthTag.Num() != AES256_AuthTagSizeInBytes) + { + UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FEncryptionContextOpenSSL::Decrypt_AES_256_GCM: Auth tag size %d is not the expected size %d."), IV.Num(), AES256_AuthTagSizeInBytes); + return TArray(); + } + + FScopedEVPContext Context; + if (Context.Get() == nullptr) + { + UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FEncryptionContextOpenSSL::Decrypt_AES_256_GCM: failed to create EVP context.")); + return TArray(); + } + + const int InitResult = EVP_DecryptInit_ex(Context.Get(), EVP_aes_256_gcm(), nullptr, Key.GetData(), IV.GetData()); + if (InitResult != 1) + { + UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FEncryptionContextOpenSSL::Decrypt_AES_256_GCM: EVP_DecryptInit_ex failed.")); + return TArray(); + } + + TArray Plaintext; + Plaintext.SetNumUninitialized(Ciphertext.Num()); + + int OutLength = 0; + const int UpdateResult = EVP_DecryptUpdate(Context.Get(), Plaintext.GetData(), &OutLength, Ciphertext.GetData(), Ciphertext.Num()); + if (UpdateResult != 1) + { + UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FEncryptionContextOpenSSL::Decrypt_AES_256_GCM: EVP_DecryptUpdate failed.")); + return TArray(); + } + + const int SetTagResult = EVP_CIPHER_CTX_ctrl(Context.Get(), EVP_CTRL_GCM_SET_TAG, AuthTag.Num(), const_cast(AuthTag.GetData())); + if (SetTagResult != 1) + { + UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FEncryptionContextOpenSSL::Decrypt_AES_256_GCM: EVP_CIPHER_CTX_ctrl failed.")); + return TArray(); + } + + int FinalizeLength = 0; + const int FinalizeResult = EVP_DecryptFinal_ex(Context.Get(), Plaintext.GetData() + OutLength, &FinalizeLength); + if (FinalizeResult != 1) + { + UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FEncryptionContextOpenSSL::Decrypt_AES_256_GCM: EVP_DecryptFinal_ex failed.")); + return TArray(); + } + + Plaintext.SetNum(OutLength + FinalizeLength); + + OutResult = EPlatformCryptoResult::Success; + return Plaintext; +} + +TArray FEncryptionContextOpenSSL::GetRandomBytes(uint32 NumBytes, EPlatformCryptoResult& OutResult) +{ + DECLARE_SCOPE_CYCLE_COUNTER(TEXT("OpenSSL GetRandomBytes"), STAT_OpenSSL_GetRandomBytes, STATGROUP_PlatformCrypto); + + OutResult = EPlatformCryptoResult::Failure; + + TArray RandomBytes; + RandomBytes.AddUninitialized(NumBytes); + + const int RandResult = RAND_bytes(RandomBytes.GetData(), RandomBytes.Num()); + if (RandResult != 1) + { + UE_LOG(LogPlatformCryptoOpenSSL, Warning, TEXT("FEncryptionContextOpenSSL::GetRandomBytes: RAND_bytes failed.")); + return TArray(); + } + + OutResult = EPlatformCryptoResult::Success; + return RandomBytes; +} + class FScopedEVPMDContext { public: @@ -193,7 +347,7 @@ static void LoadBinaryIntoBigNum(const uint8* InData, int64 InDataSize, BIGNUM* #endif } -TPlatformCryptoRSAKey FEncryptionContextOpenSSL::CreateKey_RSA(const TArrayView PublicExponent, const TArrayView PrivateExponent, const TArrayView Modulus) +FRSAKeyHandle FEncryptionContextOpenSSL::CreateKey_RSA(const TArrayView PublicExponent, const TArrayView PrivateExponent, const TArrayView Modulus) { RSA* NewKey = RSA_new(); @@ -223,22 +377,22 @@ TPlatformCryptoRSAKey FEncryptionContextOpenSSL::CreateKey_RSA(const TArrayView< return NewKey; } -void FEncryptionContextOpenSSL::DestroyKey_RSA(TPlatformCryptoRSAKey Key) +void FEncryptionContextOpenSSL::DestroyKey_RSA(FRSAKeyHandle Key) { RSA_free((RSA*)Key); } -int32 FEncryptionContextOpenSSL::GetKeySize_RSA(TPlatformCryptoRSAKey Key) +int32 FEncryptionContextOpenSSL::GetKeySize_RSA(FRSAKeyHandle Key) { return RSA_size((RSA*)Key); } -int32 FEncryptionContextOpenSSL::GetMaxDataSize_RSA(TPlatformCryptoRSAKey Key) +int32 FEncryptionContextOpenSSL::GetMaxDataSize_RSA(FRSAKeyHandle Key) { return (GetKeySize_RSA(Key)) - RSA_PKCS1_PADDING_SIZE; } -int32 FEncryptionContextOpenSSL::EncryptPublic_RSA(TArrayView Source, TArray& Dest, TPlatformCryptoRSAKey Key) +int32 FEncryptionContextOpenSSL::EncryptPublic_RSA(TArrayView Source, TArray& Dest, FRSAKeyHandle Key) { Dest.SetNum(GetKeySize_RSA(Key)); int NumEncryptedBytes = RSA_public_encrypt(Source.Num(), Source.GetData(), Dest.GetData(), (RSA*)Key, RSA_PKCS1_PADDING); @@ -249,7 +403,7 @@ int32 FEncryptionContextOpenSSL::EncryptPublic_RSA(TArrayView Sourc return NumEncryptedBytes; } -int32 FEncryptionContextOpenSSL::EncryptPrivate_RSA(TArrayView Source, TArray& Dest, TPlatformCryptoRSAKey Key) +int32 FEncryptionContextOpenSSL::EncryptPrivate_RSA(TArrayView Source, TArray& Dest, FRSAKeyHandle Key) { Dest.SetNum(GetKeySize_RSA(Key)); int NumEncryptedBytes = RSA_private_encrypt(Source.Num(), Source.GetData(), Dest.GetData(), (RSA*)Key, RSA_PKCS1_PADDING); @@ -260,7 +414,7 @@ int32 FEncryptionContextOpenSSL::EncryptPrivate_RSA(TArrayView Sour return NumEncryptedBytes; } -int32 FEncryptionContextOpenSSL::DecryptPublic_RSA(TArrayView Source, TArray& Dest, TPlatformCryptoRSAKey Key) +int32 FEncryptionContextOpenSSL::DecryptPublic_RSA(TArrayView Source, TArray& Dest, FRSAKeyHandle Key) { Dest.SetNum(GetKeySize_RSA(Key) - RSA_PKCS1_PADDING_SIZE); int NumDecryptedBytes = RSA_public_decrypt(Source.Num(), Source.GetData(), Dest.GetData(), (RSA*)Key, RSA_PKCS1_PADDING); @@ -275,7 +429,7 @@ int32 FEncryptionContextOpenSSL::DecryptPublic_RSA(TArrayView Sourc return NumDecryptedBytes; } -int32 FEncryptionContextOpenSSL::DecryptPrivate_RSA(TArrayView Source, TArray& Dest, TPlatformCryptoRSAKey Key) +int32 FEncryptionContextOpenSSL::DecryptPrivate_RSA(TArrayView Source, TArray& Dest, FRSAKeyHandle Key) { Dest.SetNum(GetKeySize_RSA(Key) - RSA_PKCS1_PADDING_SIZE); int NumDecryptedBytes = RSA_private_decrypt(Source.Num(), Source.GetData(), Dest.GetData(), (RSA*)Key, RSA_PKCS1_PADDING); diff --git a/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCryptoOpenSSL/Public/EncryptionContextOpenSSL.h b/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCryptoOpenSSL/Public/EncryptionContextOpenSSL.h index 75458b5502f1..18cc0122f4e5 100644 --- a/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCryptoOpenSSL/Public/EncryptionContextOpenSSL.h +++ b/Engine/Plugins/Experimental/PlatformCrypto/Source/PlatformCryptoOpenSSL/Public/EncryptionContextOpenSSL.h @@ -15,19 +15,23 @@ class PLATFORMCRYPTOOPENSSL_API FEncryptionContextOpenSSL public: TArray Encrypt_AES_256_ECB(const TArrayView Plaintext, const TArrayView Key, EPlatformCryptoResult& OutResult); - TArray Decrypt_AES_256_ECB(const TArrayView Ciphertext, const TArrayView Key, EPlatformCryptoResult& OutResult); + TArray Encrypt_AES_256_GCM(const TArrayView Plaintext, const TArrayView Key, const TArrayView IV, TArray& OutAuthTag, EPlatformCryptoResult& OutResult); + TArray Decrypt_AES_256_GCM(const TArrayView Ciphertext, const TArrayView Key, const TArrayView IV, const TArrayView AuthTag, EPlatformCryptoResult& OutResult); + + TArray GetRandomBytes(uint32 NumBytes, EPlatformCryptoResult& OutResult); + bool DigestVerify_PS256(const TArrayView Message, const TArrayView Signature, const TArrayView PKCS1Key); - TPlatformCryptoRSAKey CreateKey_RSA(const TArrayView PublicExponent, const TArrayView PrivateExponent, const TArrayView Modulus); - void DestroyKey_RSA(TPlatformCryptoRSAKey Key); - int32 GetKeySize_RSA(TPlatformCryptoRSAKey Key); - int32 GetMaxDataSize_RSA(TPlatformCryptoRSAKey Key); - int32 EncryptPublic_RSA(TArrayView Source, TArray& Dest, TPlatformCryptoRSAKey Key); - int32 EncryptPrivate_RSA(TArrayView Source, TArray& Dest, TPlatformCryptoRSAKey Key); - int32 DecryptPublic_RSA(TArrayView Source, TArray& Dest, TPlatformCryptoRSAKey Key); - int32 DecryptPrivate_RSA(TArrayView Source, TArray& Dest, TPlatformCryptoRSAKey Key); + FRSAKeyHandle CreateKey_RSA(const TArrayView PublicExponent, const TArrayView PrivateExponent, const TArrayView Modulus); + void DestroyKey_RSA(FRSAKeyHandle Key); + int32 GetKeySize_RSA(FRSAKeyHandle Key); + int32 GetMaxDataSize_RSA(FRSAKeyHandle Key); + int32 EncryptPublic_RSA(TArrayView Source, TArray& Dest, FRSAKeyHandle Key); + int32 EncryptPrivate_RSA(TArrayView Source, TArray& Dest, FRSAKeyHandle Key); + int32 DecryptPublic_RSA(TArrayView Source, TArray& Dest, FRSAKeyHandle Key); + int32 DecryptPrivate_RSA(TArrayView Source, TArray& Dest, FRSAKeyHandle Key); }; typedef FEncryptionContextOpenSSL FEncryptionContext; diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshConvertUtils.h b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshConvertUtils.h index 0bc045923fe6..f871c8b33f15 100644 --- a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshConvertUtils.h +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshConvertUtils.h @@ -39,9 +39,10 @@ namespace ProxyLOD * * @param InMesh Mesh with a mixture of quads and triangles. * @param OutMesh Triangle Mesh. + * @param bClockWise how to order the verts in a triangle */ template - void MixedPolyMeshToAOSMesh(const FMixedPolyMesh& InMesh, TAOSMesh& OutMesh); + void MixedPolyMeshToAOSMesh(const FMixedPolyMesh& InMesh, TAOSMesh& OutMesh, const bool bClockWise = true); /** @@ -103,8 +104,6 @@ namespace ProxyLOD #define PROXYLOD_CLOCKWISE_TRIANGLES 1 #endif - - static FVector ComputeNormal(const FVector(&Tri)[3]) { @@ -119,7 +118,7 @@ static FVector ComputeNormal(const FVector(&Tri)[3]) // Convert MixedPolyMesh to AOS Mesh. This requires splitting quads to produce triangles. template -void ProxyLOD::MixedPolyMeshToAOSMesh(const FMixedPolyMesh& MixedPolyMesh, TAOSMesh& DstAOSMesh) +void ProxyLOD::MixedPolyMeshToAOSMesh(const FMixedPolyMesh& MixedPolyMesh, TAOSMesh& DstAOSMesh, bool bClockWise) { // Splitting a quad doesn't introduce any new verts. @@ -160,7 +159,7 @@ void ProxyLOD::MixedPolyMeshToAOSMesh(const FMixedPolyMesh& MixedPolyMesh, TAOSM // NB: The Quads are ordered in clockwise fashion. ProxyLOD::Parallel_For(ProxyLOD::FUIntRange(0, NumQuads), - [&MixedPolyMesh, &DstAOSMesh](const ProxyLOD::FUIntRange& Range) + [&MixedPolyMesh, &DstAOSMesh, bClockWise](const ProxyLOD::FUIntRange& Range) { uint32* Indices = DstAOSMesh.Indexes; for (uint32 q = Range.begin(), Q = Range.end(); q < Q; ++q) @@ -168,33 +167,36 @@ void ProxyLOD::MixedPolyMeshToAOSMesh(const FMixedPolyMesh& MixedPolyMesh, TAOSM const uint32 Offset = q * 6; const openvdb::Vec4I& Quad = MixedPolyMesh.Quads[q]; // add as two triangles -#if (PROXYLOD_CLOCKWISE_TRIANGLES == 1) - // first triangle - Indices[Offset] = Quad[0]; - Indices[Offset + 1] = Quad[1]; - Indices[Offset + 2] = Quad[2]; - // second triangle - Indices[Offset + 3] = Quad[2]; - Indices[Offset + 4] = Quad[3]; - Indices[Offset + 5] = Quad[0]; -#else - // first triangle - Indices[Offset] = Quad[0]; - Indices[Offset + 1] = Quad[3]; - Indices[Offset + 2] = Quad[2]; + if (bClockWise) + { + // first triangle + Indices[Offset] = Quad[0]; + Indices[Offset + 1] = Quad[1]; + Indices[Offset + 2] = Quad[2]; + // second triangle + Indices[Offset + 3] = Quad[2]; + Indices[Offset + 4] = Quad[3]; + Indices[Offset + 5] = Quad[0]; + } + else + { + // first triangle + Indices[Offset] = Quad[0]; + Indices[Offset + 1] = Quad[3]; + Indices[Offset + 2] = Quad[2]; - // second triangle - Indices[Offset + 3] = Quad[2]; - Indices[Offset + 4] = Quad[1]; - Indices[Offset + 5] = Quad[0]; -#endif + // second triangle + Indices[Offset + 3] = Quad[2]; + Indices[Offset + 4] = Quad[1]; + Indices[Offset + 5] = Quad[0]; + } } }); // add the MixedPolyMesh triangles. ProxyLOD::Parallel_For(ProxyLOD::FUIntRange(0, MixedPolyMesh.Triangles.size()), - [&MixedPolyMesh, &DstAOSMesh, NumQuads](const ProxyLOD::FUIntRange& Range) + [&MixedPolyMesh, &DstAOSMesh, NumQuads, bClockWise](const ProxyLOD::FUIntRange& Range) { uint32* Indices = DstAOSMesh.Indexes; for (uint32 t = Range.begin(), EndT = Range.end(); t < EndT; ++t) @@ -202,15 +204,18 @@ void ProxyLOD::MixedPolyMeshToAOSMesh(const FMixedPolyMesh& MixedPolyMesh, TAOSM const uint32 Offset = NumQuads * 6 + t * 3; const openvdb::Vec3I& Tri = MixedPolyMesh.Triangles[t]; // add the triangle -#if (PROXYLOD_CLOCKWISE_TRIANGLES == 1) - Indices[Offset] = Tri[0]; - Indices[Offset + 1] = Tri[1]; - Indices[Offset + 2] = Tri[2]; -#else - Indices[Offset] = Tri[2]; - Indices[Offset + 1] = Tri[1]; - Indices[Offset + 2] = Tri[0]; -#endif + if (bClockWise) + { + Indices[Offset] = Tri[0]; + Indices[Offset + 1] = Tri[1]; + Indices[Offset + 2] = Tri[2]; + } + else + { + Indices[Offset] = Tri[2]; + Indices[Offset + 1] = Tri[1]; + Indices[Offset + 2] = Tri[0]; + } } }); } diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshParameterization.cpp b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshParameterization.cpp index f23aa8de05a9..b487385b7beb 100644 --- a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshParameterization.cpp +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshParameterization.cpp @@ -11,8 +11,31 @@ THIRD_PARTY_INCLUDES_END #include "ProxyLODThreadedWrappers.h" #include "ProxyLODMeshUtilities.h" - bool ProxyLOD::GenerateUVs(FVertexDataMesh& InOutMesh, const FTextureAtlasDesc& TextureAtlasDesc, const bool VertexColorParts) +{ + // desired parameters for ISO-Chart method + + // MaxChartNum = 0 will allow any number of charts to be generated. + + const size_t MaxChartNumber = 0; + + // Let the polys in the partitions stretch some.. 1.f will let it stretch freely + + const float MaxStretch = 0.125f; // Question. Does this override the pIMT? + + // Note: I tried constructing this from the normals, but that resulted in some large planar regions being really compressed in the + // UV chart. + const bool bComputeIMTFromVertexNormal = false; + + // No Op + auto NoOpCallBack = [](float percent)->HRESULT {return S_OK; }; + + return GenerateUVs(InOutMesh, TextureAtlasDesc, VertexColorParts, MaxStretch, MaxChartNumber, bComputeIMTFromVertexNormal, NoOpCallBack); +} + +bool ProxyLOD::GenerateUVs(FVertexDataMesh& InOutMesh, const FTextureAtlasDesc& TextureAtlasDesc, const bool VertexColorParts, + const float MaxStretch, const size_t MaxChartNumber, const bool bComputeIMTFromVertexNormal, + std::function StatusCallBack, float* MaxStretchOut, size_t* NumChartsOut) { std::vector DirextXAdjacency; @@ -22,11 +45,6 @@ bool ProxyLOD::GenerateUVs(FVertexDataMesh& InOutMesh, const FTextureAtlasDesc& if (!bValidMesh) return false; - // No Op - auto NoOpCallBack = [](float percent)->HRESULT {return S_OK; }; - - // Gather data for UVAtlas Create - // Data from the existing mesh const DirectX::XMFLOAT3* Pos = (DirectX::XMFLOAT3*) (InOutMesh.Points.GetData()); @@ -40,16 +58,6 @@ bool ProxyLOD::GenerateUVs(FVertexDataMesh& InOutMesh, const FTextureAtlasDesc& const uint32* adjacency = DirextXAdjacency.data(); - // desired parameters for ISO-Chart method - - // MaxChartNum = 0 will allow any number of charts to be generated. - - const size_t MaxChartNumber = 0; - - // Let the polys in the partitions stretch some.. 1.f will let it stretch freely - - const float MaxStretch = 0.125f; // Question. Does this override the pIMT? - // Size of the texture atlas @@ -66,21 +74,36 @@ bool ProxyLOD::GenerateUVs(FVertexDataMesh& InOutMesh, const FTextureAtlasDesc& std::vector facePartitioning; // Capture stats about the result. - float maxStretchOut = 0.f; - size_t numChartsOut = 0; + float maxStretchUsed = 0.f; + size_t numChartsUsed = 0; - // per-triangle IMT from per-vertex data(normal). Note: I tried constructing this from - // the normals, but that resulted in some large planar regions being really compressed in the - // UV chart. + float * pIMTArray = new float[NumFaces * 3]; - for (int32 f = 0; f < NumFaces; ++f) + if (!bComputeIMTFromVertexNormal) { - int32 offset = 3 * f; + for (int32 f = 0; f < NumFaces; ++f) { - pIMTArray[offset ] = 1.f; - pIMTArray[offset + 1] = 0.f; - pIMTArray[offset + 2] = 1.f; + int32 offset = 3 * f; + { + pIMTArray[offset] = 1.f; + pIMTArray[offset + 1] = 0.f; + pIMTArray[offset + 2] = 1.f; + } + } + } + else + { + // per-triangle IMT from per-vertex data(normal). + + const TArray& Normals = InOutMesh.Normal; + const float* PerVertSignal = (float*)Normals.GetData(); + size_t SignalStride = 3 * sizeof(float); + HRESULT IMTResult = UVAtlasComputeIMTFromPerVertexSignal(Pos, NumVerts, indices, DXGI_FORMAT_R32_UINT, NumFaces, PerVertSignal, 3, SignalStride, StatusCallBack, pIMTArray); + + if (FAILED(IMTResult)) + { + return false; } } @@ -89,10 +112,18 @@ bool ProxyLOD::GenerateUVs(FVertexDataMesh& InOutMesh, const FTextureAtlasDesc& MaxChartNumber, MaxStretch, width, height, gutter, adjacency, NULL /*false adj*/, pIMTArray /*IMTArray*/, - NoOpCallBack, DirectX::UVATLAS_DEFAULT_CALLBACK_FREQUENCY, + StatusCallBack, DirectX::UVATLAS_DEFAULT_CALLBACK_FREQUENCY, DirectX::UVATLAS_DEFAULT, vb, ib, - &facePartitioning, &vertexRemapArray, &maxStretchOut, &numChartsOut); + &facePartitioning, &vertexRemapArray, &maxStretchUsed, &numChartsUsed); + if (MaxStretchOut) + { + *MaxStretchOut = maxStretchUsed; + } + if (NumChartsOut) + { + *NumChartsOut = numChartsUsed; + } if (FAILED(hr)) return false; if (pIMTArray) delete[] pIMTArray; diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshParameterization.h b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshParameterization.h index 688b22ece5e8..caffefb299f0 100644 --- a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshParameterization.h +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshParameterization.h @@ -5,7 +5,7 @@ #include "ProxyLODMeshTypes.h" // FVertexDataMesh #include "ProxyLODGrid2d.h" // FTextureAtlasDesc -#include +#include // Note: should convert std::function to TFunction /** * In mesh segmentation (charts and atlases) and parameterization (generating UVs ) we rely @@ -33,6 +33,34 @@ namespace ProxyLOD */ bool GenerateUVs( FVertexDataMesh& InOutMesh, const FTextureAtlasDesc& InTextureAtlasDesc, const bool bVertexColorParts = false); + + + /** + * Lower-level entry point: + * Method that generates new UVs on the FVertexDataMesh according to the parameters specified in the FTextureAtlasDesc + * The underlying code uses Isometeric approach (Iso-Charts) in UV generation. + * + * As a debugging option, the updated InOutMesh can have vertex colors that distinguish the various UV charts. + * + * NB: The mesh vertex count may change as vertices are split on UV seams. + * + * @param InOutMesh Mesh that will be updated by this function, adding UVs + * @param InTextureAtlasDesc Description of the texel resolution of the desired texture atlas. + * @param bVertColorParts Option to add vertex colors according the the chart in the texture atlas, used for debugging. + * @param MaxStretch The maximum amount of stretch between (0 - none and 1 - any) + * @param MaxChartNumber Maximum number of charts required for the atlas. If this is 0, will be parameterized solely on stretch. + * Note, not a hard limit - isochart will stop when a valid charting is found that is greater or equal to this number. + * @param MaxStretchOut Actual max stretch used in computing uvs + * @param NumChartsOut Number of charts actually generated + * + * @return 'true' if the UV generation succeeded, 'false' if it failed. + */ + bool GenerateUVs(FVertexDataMesh& InOutMesh, const FTextureAtlasDesc& TextureAtlasDesc, const bool VertexColorParts, + const float MaxStretch, const size_t MaxChartNumber, + const bool bComputeIMTFromVertexNormal, + std::function StatusCallBack, + float* MaxStretchOut = NULL, size_t* NumChartsOut = NULL); + /** * Generate adjacency data needed for the mesh, additionally this may alter the mesh in attempting to * remove mesh degeneracy problems. This method is primarily called within GenerateUVs diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshTypes.cpp b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshTypes.cpp index b8046c66d080..3b4fa80cfa52 100644 --- a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshTypes.cpp +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshTypes.cpp @@ -6,7 +6,7 @@ // --- FMeshDescriptionAdapter ---- FMeshDescriptionAdapter::FMeshDescriptionAdapter(const FMeshDescription& InRawMesh, const openvdb::math::Transform& InTransform) : - RawMesh(&InRawMesh), Transform(&InTransform) + RawMesh(&InRawMesh), Transform(InTransform) { InitializeCacheData(); } @@ -25,6 +25,21 @@ void FMeshDescriptionAdapter::InitializeCacheData() { TriangleCount += RawMesh->GetPolygonTriangles(PolygonID).Num(); } + + IndexBuffer.Reserve(TriangleCount * 3); + + for (const FPolygonID& PolygonID : RawMesh->Polygons().GetElementIDs()) + { + const auto& Triangles = RawMesh->GetPolygonTriangles(PolygonID); + for (const FMeshTriangle& Tri : Triangles) + { + IndexBuffer.Add(Tri.VertexInstanceID0); + IndexBuffer.Add(Tri.VertexInstanceID1); + IndexBuffer.Add(Tri.VertexInstanceID2); + } + } + + } size_t FMeshDescriptionAdapter::polygonCount() const @@ -40,10 +55,13 @@ size_t FMeshDescriptionAdapter::pointCount() const void FMeshDescriptionAdapter::getIndexSpacePoint(size_t FaceNumber, size_t CornerNumber, openvdb::Vec3d& pos) const { // Get the vertex position in local space. - const FVertexInstanceID VertexInstanceID = FVertexInstanceID(FaceNumber * 3 + CornerNumber); + const FVertexInstanceID VertexInstanceID = IndexBuffer[FaceNumber * 3 + CornerNumber]; // float3 position - FVector Position = VertexPositions[RawMesh->GetVertexInstanceVertex(VertexInstanceID)]; - pos = Transform->worldToIndex(openvdb::Vec3d(Position.X, Position.Y, Position.Z)); + const FVertexID VertexID = RawMesh->GetVertexInstanceVertex(VertexInstanceID); + + + FVector Position = VertexPositions[VertexID]; + pos = Transform.worldToIndex(openvdb::Vec3d(Position.X, Position.Y, Position.Z)); }; @@ -63,11 +81,30 @@ FMeshDescriptionArrayAdapter::FMeshDescriptionArrayAdapter(const TArrayRawMesh; PointCount += size_t(RawMesh->Vertices().Num()); + // Sum up all the polys in this mesh. + int32 MeshPolyCount = 0; for (const FPolygonID& PolygonID : RawMesh->Polygons().GetElementIDs()) { - PolyCount += RawMesh->GetPolygonTriangles(PolygonID).Num(); + MeshPolyCount += RawMesh->GetPolygonTriangles(PolygonID).Num(); } + // Construct local index buffer for this mesh + IndexBufferArray.push_back(std::vector()); + std::vector& IndexBuffer = IndexBufferArray[MeshIdx]; + IndexBuffer.reserve(MeshPolyCount * 3); + for (const FPolygonID& PolygonID : RawMesh->Polygons().GetElementIDs()) + { + const auto& Triangles = RawMesh->GetPolygonTriangles(PolygonID); + for (const FMeshTriangle& Tri : Triangles) + { + IndexBuffer.push_back(Tri.VertexInstanceID0); + IndexBuffer.push_back(Tri.VertexInstanceID1); + IndexBuffer.push_back(Tri.VertexInstanceID2); + } + } + + + PolyCount += MeshPolyCount; PolyOffsetArray.push_back(PolyCount); RawMeshArray.push_back(RawMesh); RawMeshArrayData.push_back(FMeshDescriptionAttributesGetter(RawMesh)); @@ -93,10 +130,29 @@ FMeshDescriptionArrayAdapter::FMeshDescriptionArrayAdapter(const TArrayRawMesh; PointCount += size_t(RawMesh->Vertices().Num()); + + // Sum up all the polys in this mesh. + int32 MeshPolyCount = 0; for (const FPolygonID& PolygonID : RawMesh->Polygons().GetElementIDs()) { - PolyCount += RawMesh->GetPolygonTriangles(PolygonID).Num(); + MeshPolyCount += RawMesh->GetPolygonTriangles(PolygonID).Num(); } + PolyCount += MeshPolyCount; + // Construct local index buffer for this mesh + IndexBufferArray.push_back(std::vector()); + std::vector& IndexBuffer = IndexBufferArray[MeshIdx]; + IndexBuffer.reserve(MeshPolyCount * 3); + for (const FPolygonID& PolygonID : RawMesh->Polygons().GetElementIDs()) + { + const auto& Triangles = RawMesh->GetPolygonTriangles(PolygonID); + for (const FMeshTriangle& Tri : Triangles) + { + IndexBuffer.push_back(Tri.VertexInstanceID0); + IndexBuffer.push_back(Tri.VertexInstanceID1); + IndexBuffer.push_back(Tri.VertexInstanceID2); + } + } + PolyOffsetArray.push_back(PolyCount); RawMeshArray.push_back(RawMesh); @@ -121,11 +177,30 @@ FMeshDescriptionArrayAdapter::FMeshDescriptionArrayAdapter(const TArrayRawMesh; PointCount += size_t(RawMesh->Vertices().Num()); + + // Sum up all the polys in this mesh. + int32 MeshPolyCount = 0; for (const FPolygonID& PolygonID : RawMesh->Polygons().GetElementIDs()) { - PolyCount += RawMesh->GetPolygonTriangles(PolygonID).Num(); + MeshPolyCount += RawMesh->GetPolygonTriangles(PolygonID).Num(); } + // Construct local index buffer for this mesh + IndexBufferArray.push_back(std::vector()); + std::vector& IndexBuffer = IndexBufferArray[MeshIdx]; + IndexBuffer.reserve(MeshPolyCount * 3); + for (const FPolygonID& PolygonID : RawMesh->Polygons().GetElementIDs()) + { + const auto& Triangles = RawMesh->GetPolygonTriangles(PolygonID); + for (const FMeshTriangle& Tri : Triangles) + { + IndexBuffer.push_back(Tri.VertexInstanceID0); + IndexBuffer.push_back(Tri.VertexInstanceID1); + IndexBuffer.push_back(Tri.VertexInstanceID2); + } + } + + PolyCount += MeshPolyCount; PolyOffsetArray.push_back(PolyCount); RawMeshArray.push_back(RawMesh); RawMeshArrayData.push_back(FMeshDescriptionAttributesGetter(RawMesh)); @@ -147,6 +222,13 @@ FMeshDescriptionArrayAdapter::FMeshDescriptionArrayAdapter(const FMeshDescriptio { RawMeshArrayData.push_back(FMeshDescriptionAttributesGetter(RawMesh)); } + + IndexBufferArray.reserve(other.IndexBufferArray.size()); + for (const auto& IndexBuffer : other.IndexBufferArray) + { + IndexBufferArray.push_back(IndexBuffer); + } + } FMeshDescriptionArrayAdapter::~FMeshDescriptionArrayAdapter() @@ -166,8 +248,9 @@ void FMeshDescriptionArrayAdapter::getWorldSpacePoint(size_t FaceNumber, size_t const FMeshDescription& RawMesh = GetRawMesh(FaceNumber, MeshIdx, LocalFaceNumber, &AttributesGetter); check(AttributesGetter); + const auto& IndexBuffer = IndexBufferArray[MeshIdx]; // Get the vertex position in local space. - const FVertexInstanceID VertexInstanceID = FVertexInstanceID(3 * LocalFaceNumber + int32(CornerNumber)); + const FVertexInstanceID VertexInstanceID = IndexBuffer[3 * LocalFaceNumber + int32(CornerNumber)]; // float3 position FVector Position = AttributesGetter->VertexPositions[RawMesh.GetVertexInstanceVertex(VertexInstanceID)]; pos = openvdb::Vec3d(Position.X, Position.Y, Position.Z); diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshTypes.h b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshTypes.h index bbf8b2c5544b..262110f28e86 100644 --- a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshTypes.h +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshTypes.h @@ -219,7 +219,7 @@ public: */ const openvdb::math::Transform& GetTransform() const { - return *Transform; + return Transform; } private: @@ -227,14 +227,20 @@ private: // Pointer to the raw mesh we are wrapping const FMeshDescription* RawMesh; + + ////////////////////////////////////////////////////////////////////////// //Cache data void InitializeCacheData(); uint32 TriangleCount; TVertexAttributesConstRef VertexPositions; + // Local version of the index array. The FMeshDescription doesn't really have one. + TArray IndexBuffer; + + // Transform used to convert the mesh space into the index space used by voxelization - const openvdb::math::Transform* Transform; + const openvdb::math::Transform Transform; }; namespace ProxyLOD @@ -421,6 +427,9 @@ protected: std::vector MergeDataArray; + // Need to build local index buffers for each mesh because the FMeshDescription doesn't natively have the construct. + std::vector> IndexBufferArray; + }; /** diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODParameterization.cpp b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODParameterization.cpp new file mode 100644 index 000000000000..d27b0cd7e64e --- /dev/null +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODParameterization.cpp @@ -0,0 +1,92 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "ProxyLODParameterization.h" + +#include "ProxyLODMeshConvertUtils.h" +#include "ProxyLODMeshUtilities.h" +#include "ProxyLODMeshParameterization.h" + + + + +class FProxyLODParameterizationImpl : public IProxyLODParameterization +{ +public: + FProxyLODParameterizationImpl() {} + + virtual ~FProxyLODParameterizationImpl() {}; + + + virtual bool ParameterizeMeshDescription(FMeshDescription& MeshDescription, int32 Width, int32 Height, float GutterSpace, float Stretch, int32 ChartNum, bool bUseNormals, bool bRecomputeTangentSpace, bool bPrintDebugMessages) const override + { + const float MaxStretch = Stretch; + const size_t MaxChartNumber = ChartNum; + const bool bUseNormalsInUV = bUseNormals; + + // No Op + auto NoOpCallBack = [](float percent)->HRESULT {return S_OK; }; + + bool bComputeNormals = bRecomputeTangentSpace; + bool bSplitHardAngles = false; + bool bComputeTangentSpace = bRecomputeTangentSpace; + + float HardAngleDegrees = 80.f; + FVertexDataMesh VertexDataMesh; + ProxyLOD::ConvertMesh(MeshDescription, VertexDataMesh); + + if (bSplitHardAngles) + { + ProxyLOD::SplitHardAngles(HardAngleDegrees, VertexDataMesh); + } + + if (bComputeNormals) + { + ProxyLOD::ComputeVertexNormals(VertexDataMesh, ProxyLOD::ENormalComputationMethod::AngleWeighted); + } + + + FIntPoint UVSize(Height, Width); + ProxyLOD::FTextureAtlasDesc TextureAtlasDesc(UVSize, GutterSpace); + + float MaxStretchUsed = 0.f; + size_t NumChartsMade = 0; + bool bUVGenerationSucess = ProxyLOD::GenerateUVs(VertexDataMesh, TextureAtlasDesc, false, MaxStretch, MaxChartNumber, bUseNormalsInUV, NoOpCallBack, &MaxStretchUsed, &NumChartsMade); + + if (bUVGenerationSucess) + { + if (bPrintDebugMessages) + { + UE_LOG(LogTemp, Warning, TEXT("UV Generation: generated %d charts, using max stretch %f "), NumChartsMade, MaxStretchUsed); + } + + if (bComputeTangentSpace) + { + ProxyLOD::ComputeTangentSpace(VertexDataMesh, false); + } + // Reset the result mesh description + MeshDescription.Empty(); + UStaticMesh::RegisterMeshAttributes(MeshDescription); + + // convert to fill the mesh description + ProxyLOD::ConvertMesh(VertexDataMesh, MeshDescription); + } + + + return bUVGenerationSucess; + }; +}; + + + +TUniquePtr IProxyLODParameterization::CreateTool() +{ + TUniquePtr Tool = MakeUnique(); + + if (Tool == nullptr) + { + return nullptr; + } + + return Tool; + +} diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODVolume.cpp b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODVolume.cpp index 3898a1d81199..8d089b88c731 100644 --- a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODVolume.cpp +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODVolume.cpp @@ -15,6 +15,7 @@ THIRD_PARTY_INCLUDES_START #include // for Spatial Query #include // for MeshToVolume #include // for VolumeToMesh +#include // for CSG operations #pragma warning(pop) THIRD_PARTY_INCLUDES_END @@ -149,3 +150,345 @@ TUniquePtr IProxyLODVolume::CreateSDFVolumeFromMeshArray(const return Volume; } + + +class FPolygonSoup +{ +public: + FPolygonSoup(const TArray& AdapterArray, double VoxelSize); + + // Total number of polygons + size_t polygonCount() const { return NumPolys; } + + // Total number of points (vertex locations) + size_t pointCount() const { return NumVerts; } + + // Vertex count for polygon n: currently FMeshDescription is just triangles. + size_t vertexCount(size_t n) const { return 3; } + + // Return position pos in local grid index space for polygon n and vertex v + void getIndexSpacePoint(size_t FaceNumber, size_t CornerNumber, openvdb::Vec3d& pos) const + { + int32 AdapterIdx = PolyIdxToAdapter[FaceNumber]; + int32 Offset = AdapterToOffset[AdapterIdx]; + + Adapters[AdapterIdx].getIndexSpacePoint(FaceNumber - Offset, CornerNumber, pos); + } + + const OpenVDBTransform& Transform() const { return *Xform; } + +private: + + FPolygonSoup(); + + OpenVDBTransform::Ptr Xform; + + TArray Adapters; + TArray AdapterToOffset; + TArray PolyIdxToAdapter; + size_t NumPolys; + size_t NumVerts; + +}; + +FPolygonSoup::FPolygonSoup(const TArray& AdapterArray, double VoxelSize) +{ + + Xform = OpenVDBTransform::createLinearTransform(VoxelSize); + + int32 NumMeshes = AdapterArray.Num(); + Adapters.Reserve(NumMeshes); + + NumPolys = 0; + NumVerts = 0; + for (auto& Adapter : AdapterArray) + { + Adapters.Add(Adapter); + NumPolys += Adapter.polygonCount(); + NumVerts += Adapter.pointCount(); + } + + AdapterToOffset.AddZeroed(NumMeshes + 1); + for (int32 i = 1; i < NumMeshes + 1; ++i) + { + AdapterToOffset[i] = AdapterToOffset[i-1] + AdapterArray[i - 1].polygonCount(); + } + + PolyIdxToAdapter.Reserve(NumPolys); + for (int32 i = 0; i < NumMeshes; ++i) + { + int32 np = AdapterArray[i].polygonCount(); + for (int32 j = 0; j < np; ++j) + { + PolyIdxToAdapter.Add(i); + } + } +} + + + + + +class FVoxelBasedCSGImpl : public IVoxelBasedCSG +{ +public: + FVoxelBasedCSGImpl() + { + openvdb::initialize(); + + double VoxelSize = 0.1; + XForm = OpenVDBTransform::createLinearTransform(VoxelSize); + } + + FVoxelBasedCSGImpl(double VoxelSize) + { + openvdb::initialize(); + + XForm = OpenVDBTransform::createLinearTransform(VoxelSize); + } + + virtual ~FVoxelBasedCSGImpl() + { + XForm.reset(); + } + + double GetVoxelSize() const override + { + return XForm->voxelSize()[0]; + } + + void SetVoxelSize(double VoxelSize) override + { + XForm = OpenVDBTransform::createLinearTransform(VoxelSize); + } + + // Note this will become very slow if the isosurface is more than 3 voxel widths from 0. + // the tool that calls this should clamp the isosurface. + FVector ComputeUnion(const TArray& PlacedMeshArray, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface ) const override + { + + + // convert IsoSurface units to voxels + + const double VoxelSize = GetVoxelSize(); + + const double IsoSurfaceInVoxels = IsoSurface / VoxelSize; + + const double ExteriorVoxelWidth = FMath::Max( 2., IsoSurfaceInVoxels + 1.); + const double InteriorVoxelWidth = -FMath::Min(-2., IsoSurfaceInVoxels - 1); + + + const int32 NumMeshes = PlacedMeshArray.Num(); + if (NumMeshes == 0) + { + return FVector(0.f, 0.f, 0.f); + } + + // Find the average translation of all the meshes. + + FVector AverageTranslation(0.f, 0.f, 0.f); + for (int32 i = 0; i < NumMeshes; ++i) + { + AverageTranslation += PlacedMeshArray[i].Transform.GetTranslation(); + } + AverageTranslation /= (float)NumMeshes; + + + // Target grid to hold the union in SDF form. + //openvdb::FloatGrid::Ptr SDFUnionVolume = openvdb::FloatGrid::create(ExteriorVoxelWidth /*background value */); + //SDFUnionVolume->setTransform(XForm); + + // Fill the SDFUnionVolume + FMatrix LocalToVoxel = FMatrix::Identity; + LocalToVoxel.M[0][0] = VoxelSize; + LocalToVoxel.M[1][1] = VoxelSize; + LocalToVoxel.M[2][2] = VoxelSize; + + TArray Adapters; + for (int32 i = 0, I = PlacedMeshArray.Num(); i < I; ++i) + { + const FPlacedMesh& PlacedMesh = PlacedMeshArray[i]; + const FMeshDescription* MeshPtr = PlacedMesh.Mesh; + if (MeshPtr) + { + + // Get the transform relative to the average + FTransform MeshTransform = PlacedMesh.Transform; + MeshTransform.AddToTranslation(-AverageTranslation); + FMatrix TransformMatrix = MeshTransform.ToMatrixWithScale().Inverse(); + + + TransformMatrix = LocalToVoxel * TransformMatrix; + float* data = &TransformMatrix.M[0][0]; + openvdb::math::Mat4 VDBMatFloat(data); + + openvdb::Mat4R VDBMatDouble(VDBMatFloat); + OpenVDBTransform::Ptr LocalXForm = OpenVDBTransform::createLinearTransform(VDBMatDouble); + + // Create a wrapper with OpenVDB semantics. + + FMeshDescriptionAdapter MeshAdapter(*MeshPtr, *LocalXForm); + + Adapters.Add(MeshAdapter); + + } + } + + FPolygonSoup PolySoup(Adapters, VoxelSize); + openvdb::FloatGrid::Ptr SDFUnionVolume = openvdb::tools::meshToVolume(PolySoup, PolySoup.Transform(), ExteriorVoxelWidth, InteriorVoxelWidth); + + + // Convert the SDFUnionVolume to a mesh + + ConvertSDFToMesh(SDFUnionVolume, Adaptivity, IsoSurface, ResultMesh); + + return AverageTranslation; + } + + FVector ComputeDifference(const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface) const override + { + + // make SDFs + openvdb::FloatGrid::Ptr SDFVolumeA; + openvdb::FloatGrid::Ptr SDFVolumeB; + + FVector AverageTranslation = GenerateVolumes(IsoSurface, PlacedMeshA, PlacedMeshB, SDFVolumeA, SDFVolumeB); + + // create the difference - result stored in volume A + openvdb::tools::csgDifference(*SDFVolumeA, *SDFVolumeB); + + // convert the result + ConvertSDFToMesh(SDFVolumeA, Adaptivity, IsoSurface, ResultMesh); + + return AverageTranslation; + } + + FVector ComputeIntersection(const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface) const override + { + + // make SDFs + openvdb::FloatGrid::Ptr SDFVolumeA; + openvdb::FloatGrid::Ptr SDFVolumeB; + + FVector AverageTranslation = GenerateVolumes(IsoSurface, PlacedMeshA, PlacedMeshB, SDFVolumeA, SDFVolumeB); + + + // create the difference - result stored in volume A + openvdb::tools::csgIntersection(*SDFVolumeA, *SDFVolumeB); + + // convert the result + ConvertSDFToMesh(SDFVolumeA, Adaptivity, IsoSurface, ResultMesh); + + return AverageTranslation; + } + + FVector ComputeUnion(const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface) const override + { + + // make SDFs + openvdb::FloatGrid::Ptr SDFVolumeA; + openvdb::FloatGrid::Ptr SDFVolumeB; + + FVector AverageTranslation = GenerateVolumes(IsoSurface, PlacedMeshA, PlacedMeshB, SDFVolumeA, SDFVolumeB); + + + // create the difference - result stored in volume A + openvdb::tools::csgUnion(*SDFVolumeA, *SDFVolumeB); + + // convert the result + ConvertSDFToMesh(SDFVolumeA, Adaptivity, IsoSurface, ResultMesh); + + return AverageTranslation; + } + +private: + + void ConvertSDFToMesh(openvdb::FloatGrid::Ptr SDFVolume, double Adaptivity, double IsoSurface, FMeshDescription& ResultMesh) const + { + // Convert the SDFUnionVolume to a mesh + FAOSMesh AOSMeshedVolume; + ProxyLOD::ExtractIsosurfaceWithNormals(SDFVolume, IsoSurface, Adaptivity, AOSMeshedVolume); // QUestion should IsoSurface be worldspace? + + + // Evidently this conversion code makes certain assumptions about the FMeshDescription. that were introduced when it was converted from FRawMesh... + + ProxyLOD::ConvertMesh(AOSMeshedVolume, ResultMesh); + } + + FVector GenerateVolumes(const double IsoSurface, const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, openvdb::FloatGrid::Ptr& VolumeA, openvdb::FloatGrid::Ptr& VolumeB) const + { + const FMeshDescription& MeshA = *PlacedMeshA.Mesh; + const FMeshDescription& MeshB = *PlacedMeshB.Mesh; + + const double VoxelSize = GetVoxelSize(); + + const double IsoSurfaceInVoxels = IsoSurface / VoxelSize; + + + const double ExteriorVoxelWidth = FMath::Max(2., IsoSurfaceInVoxels + 1.); + const double InteriorVoxelWidth = -FMath::Min(-2., IsoSurfaceInVoxels - 1); + + // The average translation of the two meshes. + FVector AverageTranslation = PlacedMeshA.Transform.GetTranslation() + PlacedMeshB.Transform.GetTranslation(); + AverageTranslation *= 0.5f; + + // Fill the SDFUnionVolume + FMatrix LocalToVoxel = FMatrix::Identity; + LocalToVoxel.M[0][0] = VoxelSize; + LocalToVoxel.M[1][1] = VoxelSize; + LocalToVoxel.M[2][2] = VoxelSize; + + auto TransformGenerator = [&LocalToVoxel, &AverageTranslation](const FPlacedMesh& PlacedMesh)->openvdb::Mat4R + { + FTransform XForm = PlacedMesh.Transform; + XForm.AddToTranslation(-AverageTranslation); + FMatrix TransformMatrix = XForm.ToMatrixWithScale().Inverse(); + + TransformMatrix = LocalToVoxel * TransformMatrix; + float* data = &TransformMatrix.M[0][0]; + openvdb::math::Mat4 VDBMatFloat(data); + return openvdb::Mat4R(VDBMatFloat); + }; + + openvdb::Mat4R XFormA = TransformGenerator(PlacedMeshA); + OpenVDBTransform::Ptr VDBXFormA = OpenVDBTransform::createLinearTransform(XFormA); + + openvdb::Mat4R XFormB = TransformGenerator(PlacedMeshB); + OpenVDBTransform::Ptr VDBXFormB = OpenVDBTransform::createLinearTransform(XFormB); + + // Create adapters that understand the openVDB semantics. + FMeshDescriptionAdapter AdapterA(MeshA, *VDBXFormA); + FMeshDescriptionAdapter AdapterB(MeshB, *VDBXFormB); + + // target transform + OpenVDBTransform::Ptr TargetXForm = OpenVDBTransform::createLinearTransform(VoxelSize); + + // make SDFs + openvdb::FloatGrid::Ptr SDFVolumeA = openvdb::tools::meshToVolume(AdapterA, *TargetXForm, ExteriorVoxelWidth, InteriorVoxelWidth); + openvdb::FloatGrid::Ptr SDFVolumeB = openvdb::tools::meshToVolume(AdapterB, *TargetXForm, ExteriorVoxelWidth, InteriorVoxelWidth); + + VolumeA = SDFVolumeA; + VolumeB = SDFVolumeB; + + return AverageTranslation; + + } + + OpenVDBTransform::Ptr XForm; +}; + +TUniquePtr IVoxelBasedCSG::CreateCSGTool(float VoxelSize) +{ + TUniquePtr CSGTool = MakeUnique(); + + if (CSGTool == nullptr) + { + return nullptr; + } + else + { + CSGTool->SetVoxelSize(VoxelSize); + } + + return CSGTool; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Public/ProxyLODParameterization.h b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Public/ProxyLODParameterization.h new file mode 100644 index 000000000000..86caf2fe5763 --- /dev/null +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Public/ProxyLODParameterization.h @@ -0,0 +1,18 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +struct FMeshDescription; + + +class PROXYLODMESHREDUCTION_API IProxyLODParameterization +{ +public: + static TUniquePtr CreateTool(); + virtual ~IProxyLODParameterization(){} + virtual bool ParameterizeMeshDescription(FMeshDescription& MeshDescription, int32 Width, int32 Height, + float GutterSpace, float Stretch, int32 ChartNum, + bool bUseNormals, bool bRecomputeTangentSpace, bool bPrintDebugMessages) const = 0; +}; diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Public/ProxyLODVolume.h b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Public/ProxyLODVolume.h index 27f9fa5f2127..03b4acf73952 100644 --- a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Public/ProxyLODVolume.h +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Public/ProxyLODVolume.h @@ -56,3 +56,48 @@ public: */ virtual float QueryDistance(const FVector& Point) const = 0; }; + + + + +class PROXYLODMESHREDUCTION_API IVoxelBasedCSG +{ +public : + static TUniquePtr CreateCSGTool(float VoxelSize); + + virtual ~IVoxelBasedCSG() {} + + class FPlacedMesh + { + public: + FPlacedMesh() + : Mesh(nullptr) + {} + + FPlacedMesh(const FPlacedMesh& other) + : Mesh(other.Mesh) + , Transform(other.Transform) + {} + + FPlacedMesh& operator=(const FPlacedMesh& other) + { + Mesh = other.Mesh; + Transform = other.Transform; + return *this; + } + + const FMeshDescription* Mesh; + FTransform Transform; + }; + + virtual double GetVoxelSize() const = 0; + virtual void SetVoxelSize(double VolexSize) = 0; + + // Will destroy UVs and other attributes Returns the average location of the input meshes + virtual FVector ComputeUnion(const TArray& MeshArray, FMeshDescription& ResultMesh, double Adaptivity = 0.1, double IsoSurfcae = 0.) const = 0; + + // We could make this keep the UVs and other attributes from the AMesh.. + virtual FVector ComputeDifference(const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface) const = 0; + virtual FVector ComputeIntersection(const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface) const = 0; + virtual FVector ComputeUnion(const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface) const = 0; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyReferenceCollector.cpp b/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyReferenceCollector.cpp index e25e386e56a8..2ec825ea6634 100644 --- a/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyReferenceCollector.cpp +++ b/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyReferenceCollector.cpp @@ -43,6 +43,11 @@ void FPyReferenceCollector::AddReferencedObjects(FReferenceCollector& InCollecto FPyWrapperTypeReinstancer::Get().AddReferencedObjects(InCollector); } +FString FPyReferenceCollector::GetReferencerName() const +{ + return TEXT("FPyReferenceCollector"); +} + void FPyReferenceCollector::PurgeUnrealObjectReferences(const UObject* InObject, const bool bIncludeInnerObjects) { PurgeUnrealObjectReferences(TArrayView(&InObject, 1), bIncludeInnerObjects); diff --git a/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyReferenceCollector.h b/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyReferenceCollector.h index 7772a42f2055..b4623d1c23e7 100644 --- a/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyReferenceCollector.h +++ b/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyReferenceCollector.h @@ -49,6 +49,7 @@ public: //~ FGCObject interface virtual void AddReferencedObjects(FReferenceCollector& InCollector) override; + virtual FString GetReferencerName() const override; /** Utility function to ARO a delegate instance */ static void AddReferencedObjectsFromDelegate(FReferenceCollector& InCollector, FScriptDelegate& InDelegate); diff --git a/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperObject.cpp b/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperObject.cpp index 778cfa350743..ef0367ce1ada 100644 --- a/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperObject.cpp +++ b/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperObject.cpp @@ -1687,6 +1687,7 @@ void UPythonGeneratedClass::PostInitInstance(UObject* InObj) } } + void UPythonGeneratedClass::ReleasePythonResources() { PyType.Reset(); @@ -1696,6 +1697,12 @@ void UPythonGeneratedClass::ReleasePythonResources() PyMetaData = FPyWrapperObjectMetaData(); } +bool UPythonGeneratedClass::IsFunctionImplementedInScript(FName InFunctionName) const +{ + UFunction* Function = FindFunctionByName(InFunctionName); + return Function && Function->GetOuter() && Function->GetOuter()->IsA(UPythonGeneratedClass::StaticClass()); +} + UPythonGeneratedClass* UPythonGeneratedClass::GenerateClass(PyTypeObject* InPyType) { // Get the correct super class from the parent type in Python diff --git a/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperObject.h b/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperObject.h index c40fb1eb9d4a..338ede363bf3 100644 --- a/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperObject.h +++ b/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperObject.h @@ -196,6 +196,8 @@ public: //~ IPythonResourceOwner interface virtual void ReleasePythonResources() override; + virtual bool IsFunctionImplementedInScript(FName InFunctionName) const override; + /** Generate an Unreal class from the given Python type */ static UPythonGeneratedClass* GenerateClass(PyTypeObject* InPyType); diff --git a/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperTypeRegistry.cpp b/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperTypeRegistry.cpp index d651b3e82763..6fc7c88aed27 100644 --- a/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperTypeRegistry.cpp +++ b/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperTypeRegistry.cpp @@ -720,10 +720,10 @@ PyTypeObject* FPyWrapperTypeRegistry::GenerateWrappedClassType(const UClass* InC } const FString PythonStructMethodName = PyGenUtil::GetScriptMethodPythonName(InFunc); - TArray> DynamicMethodDefs; + TArray, TInlineAllocator<4>> DynamicMethodDefs; // Copy the basic wrapped method as we need to adjust some parts of it below - PyGenUtil::FGeneratedWrappedDynamicMethod& GeneratedWrappedDynamicMethod = DynamicMethodDefs.AddDefaulted_GetRef(); + PyGenUtil::FGeneratedWrappedDynamicMethod& GeneratedWrappedDynamicMethod = DynamicMethodDefs.AddDefaulted_GetRef().Get(); static_cast(GeneratedWrappedDynamicMethod) = InTypeMethod; GeneratedWrappedDynamicMethod.SelfParam = SelfParam; @@ -814,7 +814,7 @@ PyTypeObject* FPyWrapperTypeRegistry::GenerateWrappedClassType(const UClass* InC { FString DeprecationMessage = FString::Printf(TEXT("'%s' was renamed to '%s'."), *DeprecatedPythonStructMethodName, *PythonStructMethodName); - PyGenUtil::FGeneratedWrappedDynamicMethod& DeprecatedGeneratedWrappedMethod = DynamicMethodDefs.AddDefaulted_GetRef(); + PyGenUtil::FGeneratedWrappedDynamicMethod& DeprecatedGeneratedWrappedMethod = DynamicMethodDefs.AddDefaulted_GetRef().Get(); DeprecatedGeneratedWrappedMethod = GeneratedWrappedDynamicMethod; DeprecatedGeneratedWrappedMethod.MethodName = PyGenUtil::TCHARToUTF8Buffer(*DeprecatedPythonStructMethodName); DeprecatedGeneratedWrappedMethod.MethodDoc = PyGenUtil::TCHARToUTF8Buffer(*FString::Printf(TEXT("deprecated: %s"), *DeprecationMessage)); @@ -836,10 +836,10 @@ PyTypeObject* FPyWrapperTypeRegistry::GenerateWrappedClassType(const UClass* InC check(HostedClassGeneratedWrappedType.IsValid()); // Add the dynamic methods to the class - for (PyGenUtil::FGeneratedWrappedDynamicMethod& GeneratedWrappedDynamicMethodToAdd : DynamicMethodDefs) + for (TUniqueObj& GeneratedWrappedDynamicMethodToAdd : DynamicMethodDefs) { - HostedClassGeneratedWrappedType->FieldTracker.RegisterPythonFieldName(UTF8_TO_TCHAR(GeneratedWrappedDynamicMethodToAdd.MethodName.GetData()), InFunc); - HostedClassGeneratedWrappedType->AddDynamicMethod(MoveTemp(GeneratedWrappedDynamicMethodToAdd)); + HostedClassGeneratedWrappedType->FieldTracker.RegisterPythonFieldName(UTF8_TO_TCHAR(GeneratedWrappedDynamicMethodToAdd->MethodName.GetData()), InFunc); + HostedClassGeneratedWrappedType->AddDynamicMethod(MoveTemp(GeneratedWrappedDynamicMethodToAdd.Get())); } } else if (SelfParam.ParamProp->IsA()) @@ -856,10 +856,10 @@ PyTypeObject* FPyWrapperTypeRegistry::GenerateWrappedClassType(const UClass* InC check(HostedStructGeneratedWrappedType.IsValid()); // Add the dynamic methods to the struct - for (PyGenUtil::FGeneratedWrappedDynamicMethod& GeneratedWrappedDynamicMethodToAdd : DynamicMethodDefs) + for (TUniqueObj& GeneratedWrappedDynamicMethodToAdd : DynamicMethodDefs) { - HostedStructGeneratedWrappedType->FieldTracker.RegisterPythonFieldName(UTF8_TO_TCHAR(GeneratedWrappedDynamicMethodToAdd.MethodName.GetData()), InFunc); - HostedStructGeneratedWrappedType->AddDynamicMethod(MoveTemp(GeneratedWrappedDynamicMethodToAdd)); + HostedStructGeneratedWrappedType->FieldTracker.RegisterPythonFieldName(UTF8_TO_TCHAR(GeneratedWrappedDynamicMethodToAdd->MethodName.GetData()), InFunc); + HostedStructGeneratedWrappedType->AddDynamicMethod(MoveTemp(GeneratedWrappedDynamicMethodToAdd.Get())); } } else @@ -960,10 +960,10 @@ PyTypeObject* FPyWrapperTypeRegistry::GenerateWrappedClassType(const UClass* InC } const FString PythonConstantName = PyGenUtil::GetScriptConstantPythonName(InFunc); - TArray> ConstantDefs; + TArray, TInlineAllocator<4>> ConstantDefs; // Build the constant definition - PyGenUtil::FGeneratedWrappedConstant& GeneratedWrappedConstant = ConstantDefs.AddDefaulted_GetRef(); + PyGenUtil::FGeneratedWrappedConstant& GeneratedWrappedConstant = ConstantDefs.AddDefaulted_GetRef().Get(); GeneratedWrappedConstant.ConstantName = PyGenUtil::TCHARToUTF8Buffer(*PythonConstantName); GeneratedWrappedConstant.ConstantDoc = PyGenUtil::TCHARToUTF8Buffer(*FString::Printf(TEXT("(%s): %s"), *PyGenUtil::GetPropertyPythonType(ConstantFunc.OutputParams[0].ParamProp), *PyGenUtil::GetFieldTooltip(InFunc))); GeneratedWrappedConstant.ConstantFunc = ConstantFunc; @@ -974,7 +974,7 @@ PyTypeObject* FPyWrapperTypeRegistry::GenerateWrappedClassType(const UClass* InC { FString DeprecationMessage = FString::Printf(TEXT("'%s' was renamed to '%s'."), *DeprecatedPythonConstantName, *PythonConstantName); - PyGenUtil::FGeneratedWrappedConstant& DeprecatedGeneratedWrappedConstant = ConstantDefs.AddDefaulted_GetRef(); + PyGenUtil::FGeneratedWrappedConstant& DeprecatedGeneratedWrappedConstant = ConstantDefs.AddDefaulted_GetRef().Get(); DeprecatedGeneratedWrappedConstant = GeneratedWrappedConstant; DeprecatedGeneratedWrappedConstant.ConstantName = PyGenUtil::TCHARToUTF8Buffer(*DeprecatedPythonConstantName); DeprecatedGeneratedWrappedConstant.ConstantDoc = PyGenUtil::TCHARToUTF8Buffer(*FString::Printf(TEXT("deprecated: %s"), *DeprecationMessage)); @@ -998,10 +998,10 @@ PyTypeObject* FPyWrapperTypeRegistry::GenerateWrappedClassType(const UClass* InC check(HostedClassGeneratedWrappedType.IsValid()); // Add the dynamic constants to the struct - for (PyGenUtil::FGeneratedWrappedConstant& GeneratedWrappedConstantToAdd : ConstantDefs) + for (TUniqueObj& GeneratedWrappedConstantToAdd : ConstantDefs) { - HostedClassGeneratedWrappedType->FieldTracker.RegisterPythonFieldName(UTF8_TO_TCHAR(GeneratedWrappedConstantToAdd.ConstantName.GetData()), InFunc); - HostedClassGeneratedWrappedType->AddDynamicConstant(MoveTemp(GeneratedWrappedConstantToAdd)); + HostedClassGeneratedWrappedType->FieldTracker.RegisterPythonFieldName(UTF8_TO_TCHAR(GeneratedWrappedConstantToAdd->ConstantName.GetData()), InFunc); + HostedClassGeneratedWrappedType->AddDynamicConstant(MoveTemp(GeneratedWrappedConstantToAdd.Get())); } } else if (HostType->IsA()) @@ -1019,10 +1019,10 @@ PyTypeObject* FPyWrapperTypeRegistry::GenerateWrappedClassType(const UClass* InC check(HostedStructGeneratedWrappedType.IsValid()); // Add the dynamic constants to the struct - for (PyGenUtil::FGeneratedWrappedConstant& GeneratedWrappedConstantToAdd : ConstantDefs) + for (TUniqueObj& GeneratedWrappedConstantToAdd : ConstantDefs) { - HostedStructGeneratedWrappedType->FieldTracker.RegisterPythonFieldName(UTF8_TO_TCHAR(GeneratedWrappedConstantToAdd.ConstantName.GetData()), InFunc); - HostedStructGeneratedWrappedType->AddDynamicConstant(MoveTemp(GeneratedWrappedConstantToAdd)); + HostedStructGeneratedWrappedType->FieldTracker.RegisterPythonFieldName(UTF8_TO_TCHAR(GeneratedWrappedConstantToAdd->ConstantName.GetData()), InFunc); + HostedStructGeneratedWrappedType->AddDynamicConstant(MoveTemp(GeneratedWrappedConstantToAdd.Get())); } } else @@ -1033,10 +1033,10 @@ PyTypeObject* FPyWrapperTypeRegistry::GenerateWrappedClassType(const UClass* InC else { // Add the static constants to this type - for (PyGenUtil::FGeneratedWrappedConstant& GeneratedWrappedConstantToAdd : ConstantDefs) + for (TUniqueObj& GeneratedWrappedConstantToAdd : ConstantDefs) { - GeneratedWrappedType->FieldTracker.RegisterPythonFieldName(UTF8_TO_TCHAR(GeneratedWrappedConstantToAdd.ConstantName.GetData()), InFunc); - GeneratedWrappedType->Constants.TypeConstants.Add(MoveTemp(GeneratedWrappedConstantToAdd)); + GeneratedWrappedType->FieldTracker.RegisterPythonFieldName(UTF8_TO_TCHAR(GeneratedWrappedConstantToAdd->ConstantName.GetData()), InFunc); + GeneratedWrappedType->Constants.TypeConstants.Add(MoveTemp(GeneratedWrappedConstantToAdd.Get())); } } }; @@ -1092,6 +1092,11 @@ PyTypeObject* FPyWrapperTypeRegistry::GenerateWrappedClassType(const UClass* InC GeneratedWrappedMethod.MethodCallback = GeneratedWrappedMethod.MethodFunc.InputParams.Num() > 0 ? PyCFunctionWithClosureCast(&FPyWrapperObject::CallMethodWithArgs_Impl) : PyCFunctionWithClosureCast(&FPyWrapperObject::CallMethodNoArgs_Impl); } + // We must create a copy here because otherwise the reference will get invalidated by + // subsequent modifications + + const PyGenUtil::FGeneratedWrappedMethod GeneratedWrappedMethodCopy = GeneratedWrappedMethod; + const TArray DeprecatedPythonFuncNames = PyGenUtil::GetDeprecatedFunctionPythonNames(InFunc); for (const FString& DeprecatedPythonFuncName : DeprecatedPythonFuncNames) { @@ -1099,7 +1104,7 @@ PyTypeObject* FPyWrapperTypeRegistry::GenerateWrappedClassType(const UClass* InC PythonMethods.Add(*DeprecatedPythonFuncName, InFunc->GetFName()); PythonDeprecatedMethods.Add(*DeprecatedPythonFuncName, DeprecationMessage); - PyGenUtil::FGeneratedWrappedMethod DeprecatedGeneratedWrappedMethod = GeneratedWrappedMethod; + PyGenUtil::FGeneratedWrappedMethod DeprecatedGeneratedWrappedMethod = GeneratedWrappedMethodCopy; DeprecatedGeneratedWrappedMethod.MethodName = PyGenUtil::TCHARToUTF8Buffer(*DeprecatedPythonFuncName); DeprecatedGeneratedWrappedMethod.MethodDoc = PyGenUtil::TCHARToUTF8Buffer(*FString::Printf(TEXT("deprecated: %s"), *DeprecationMessage)); DeprecatedGeneratedWrappedMethod.MethodFunc.DeprecationMessage = MoveTemp(DeprecationMessage); @@ -1111,11 +1116,11 @@ PyTypeObject* FPyWrapperTypeRegistry::GenerateWrappedClassType(const UClass* InC // Should this function also be hoisted as a struct method or operator? if (InFunc->HasMetaData(PyGenUtil::ScriptMethodMetaDataKey)) { - GenerateWrappedDynamicMethod(InFunc, GeneratedWrappedMethod); + GenerateWrappedDynamicMethod(InFunc, GeneratedWrappedMethodCopy); } if (InFunc->HasMetaData(PyGenUtil::ScriptOperatorMetaDataKey)) { - GenerateWrappedOperator(InFunc, GeneratedWrappedMethod); + GenerateWrappedOperator(InFunc, GeneratedWrappedMethodCopy); } }; @@ -2003,7 +2008,7 @@ void FPyWrapperTypeRegistry::GenerateStubCodeForWrappedTypes(const EPyOnlineDocs { TArray ModuleNames; GeneratedWrappedTypesForModule.GetKeys(ModuleNames); - ModuleNames.Sort(); + ModuleNames.Sort(FNameLexicalLess()); bool bExportedImports = false; for (const FName ModuleName : ModuleNames) diff --git a/Engine/Plugins/Experimental/RawInput/Source/RawInput/Private/Windows/RawInputWindows.cpp b/Engine/Plugins/Experimental/RawInput/Source/RawInput/Private/Windows/RawInputWindows.cpp index 57723887de10..e9d720cc2c02 100644 --- a/Engine/Plugins/Experimental/RawInput/Source/RawInput/Private/Windows/RawInputWindows.cpp +++ b/Engine/Plugins/Experimental/RawInput/Source/RawInput/Private/Windows/RawInputWindows.cpp @@ -94,16 +94,19 @@ FRawInputWindows::FRawInputWindows(const TSharedRefAddMessageHandler(*this); QueryConnectedDevices(); - // Register a default device. - const uint32 Flags = 0; - const int32 PageID = 0x01; - int32 DeviceID = 0x04; - DefaultDeviceHandle = RegisterInputDevice(RIM_TYPEHID, Flags, DeviceID, PageID); - - if(DefaultDeviceHandle==INDEX_NONE) + // Register a default device if desired + if (GetDefault()->bRegisterDefaultDevice) { - DeviceID = 0x05; + const uint32 Flags = 0; + const int32 PageID = 0x01; + int32 DeviceID = 0x04; DefaultDeviceHandle = RegisterInputDevice(RIM_TYPEHID, Flags, DeviceID, PageID); + + if (DefaultDeviceHandle == INDEX_NONE) + { + DeviceID = 0x05; + DefaultDeviceHandle = RegisterInputDevice(RIM_TYPEHID, Flags, DeviceID, PageID); + } } AHUD::OnShowDebugInfo.AddRaw(this, &FRawInputWindows::ShowDebugInfo); diff --git a/Engine/Plugins/Experimental/RawInput/Source/RawInput/Public/RawInputSettings.h b/Engine/Plugins/Experimental/RawInput/Source/RawInput/Public/RawInputSettings.h index 8910124dae9d..78efc216eb81 100644 --- a/Engine/Plugins/Experimental/RawInput/Source/RawInput/Public/RawInputSettings.h +++ b/Engine/Plugins/Experimental/RawInput/Source/RawInput/Public/RawInputSettings.h @@ -100,5 +100,8 @@ public: UPROPERTY(config, EditAnywhere, Category="Device Configurations") TArray DeviceConfigurations; + + UPROPERTY(config, EditAnywhere, Category="Device Configurations") + bool bRegisterDefaultDevice = true; }; diff --git a/Engine/Plugins/Experimental/RemoteSession/Source/RemoteSession/Private/MessageHandler/RecordingMessageHandler.cpp b/Engine/Plugins/Experimental/RemoteSession/Source/RemoteSession/Private/MessageHandler/RecordingMessageHandler.cpp index 5e0cb37a2943..4313d0bdec29 100644 --- a/Engine/Plugins/Experimental/RemoteSession/Source/RemoteSession/Private/MessageHandler/RecordingMessageHandler.cpp +++ b/Engine/Plugins/Experimental/RemoteSession/Source/RemoteSession/Private/MessageHandler/RecordingMessageHandler.cpp @@ -134,7 +134,7 @@ FVector2D FRecordingMessageHandler::ConvertFromNormalizedScreenLocation(const FV FWidgetPath WidgetPath(GameWindow.ToSharedRef(), JustWindow); if (WidgetPath.ExtendPathTo(FWidgetMatcher(ViewportWidget.ToSharedRef()), EVisibility::Visible)) { - FArrangedWidget ArrangedWidget = WidgetPath.FindArrangedWidget(ViewportWidget.ToSharedRef()).Get(FArrangedWidget::NullWidget); + FArrangedWidget ArrangedWidget = WidgetPath.FindArrangedWidget(ViewportWidget.ToSharedRef()).Get(FArrangedWidget::GetNullWidget()); FVector2D WindowClientOffset = ArrangedWidget.Geometry.GetAbsolutePosition(); FVector2D WindowClientSize = ArrangedWidget.Geometry.GetAbsoluteSize(); diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/SampleToolsEditorMode.uplugin b/Engine/Plugins/Experimental/SampleToolsEditorMode/SampleToolsEditorMode.uplugin new file mode 100644 index 000000000000..b8783284a86f --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/SampleToolsEditorMode.uplugin @@ -0,0 +1,24 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "0.1", + "FriendlyName": "Sample Tools Mode", + "Description": "Adds Sample Tools Editor Mode Tab", + "Category": "Other", + "CreatedBy": "Epic Games, Inc.", + "CreatedByURL": "http://epicgames.com", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": false, + "IsBetaVersion": true, + "Installed": false, + "Modules": [ + { + "Name": "SampleToolsEditorMode", + "Type": "Editor", + "LoadingPhase": "Default" + } + ], + "Plugins": [] +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/CreateActorSampleTool.cpp b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/CreateActorSampleTool.cpp new file mode 100644 index 000000000000..de44fb0cf9cd --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/CreateActorSampleTool.cpp @@ -0,0 +1,114 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "CreateActorSampleTool.h" +#include "InteractiveToolManager.h" +#include "ToolBuilderUtil.h" +#include "CollisionQueryParams.h" +#include "Engine/World.h" + +// localization namespace +#define LOCTEXT_NAMESPACE "UCreateActorSampleTool" + +/* + * ToolBuilder + */ + + +bool UCreateActorSampleToolBuilder::CanBuildTool(const FToolBuilderState & SceneState) const +{ + return (this->AssetAPI != nullptr); +} + +UInteractiveTool* UCreateActorSampleToolBuilder::BuildTool(const FToolBuilderState & SceneState) const +{ + UCreateActorSampleTool* NewTool = NewObject(SceneState.ToolManager); + NewTool->SetWorld(SceneState.World); + NewTool->SetAssetAPI(AssetAPI); + return NewTool; +} + + + +/* + * Tool + */ + + +UCreateActorSampleToolProperties::UCreateActorSampleToolProperties() +{ + PlaceOnObjects = true; + GroundHeight = 0.0f; +} + + +UCreateActorSampleTool::UCreateActorSampleTool() +{ +} + + +void UCreateActorSampleTool::SetWorld(UWorld* World) +{ + this->TargetWorld = World; +} + +void UCreateActorSampleTool::SetAssetAPI(IToolsContextAssetAPI* AssetAPIIn) +{ + this->AssetAPI = AssetAPIIn; +} + + +void UCreateActorSampleTool::Setup() +{ + USingleClickTool::Setup(); + + Properties = NewObject(this); + AddToolPropertySource(Properties); +} + + + + +void UCreateActorSampleTool::OnClicked(const FInputDeviceRay& ClickPos) +{ + // we will create actor at this position + FVector NewActorPos = FVector::ZeroVector; + + // cast ray into world to find hit position + FVector RayStart = ClickPos.WorldRay.Origin; + FVector RayEnd = ClickPos.WorldRay.PointAt(999999); + FCollisionObjectQueryParams QueryParams(FCollisionObjectQueryParams::AllObjects); + FHitResult Result; + bool bHitWorld = TargetWorld->LineTraceSingleByObjectType(Result, RayStart, RayEnd, QueryParams); + if (Properties->PlaceOnObjects && bHitWorld) + { + NewActorPos = Result.ImpactPoint; + } + else + { + // hit the ground plane + FPlane GroundPlane(FVector(0,0,Properties->GroundHeight) , FVector(0, 0, 1)); + NewActorPos = FMath::RayPlaneIntersection(ClickPos.WorldRay.Origin, ClickPos.WorldRay.Direction, GroundPlane); + } + + // create new actor + FRotator Rotation(0.0f, 0.0f, 0.0f); + FActorSpawnParameters SpawnInfo; + AActor* NewActor = TargetWorld->SpawnActor(FVector::ZeroVector, Rotation, SpawnInfo); + + // create root component + USceneComponent* RootComponent = NewObject(NewActor, USceneComponent::GetDefaultSceneRootVariableName(), RF_Transactional); + RootComponent->Mobility = EComponentMobility::Movable; + RootComponent->bVisualizeComponent = true; + RootComponent->SetWorldTransform(FTransform(NewActorPos)); + + NewActor->SetRootComponent(RootComponent); + NewActor->AddInstanceComponent(RootComponent); + RootComponent->RegisterComponent(); + +} + + + + + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/CreateActorSampleTool.h b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/CreateActorSampleTool.h new file mode 100644 index 000000000000..d06ac0f88c8e --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/CreateActorSampleTool.h @@ -0,0 +1,89 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "InteractiveToolBuilder.h" +#include "BaseTools/SingleClickTool.h" +#include "CreateActorSampleTool.generated.h" + + +/** + * Builder for UCreateActorSampleTool + */ +UCLASS() +class SAMPLETOOLSEDITORMODE_API UCreateActorSampleToolBuilder : public UInteractiveToolBuilder +{ + GENERATED_BODY() + +public: + IToolsContextAssetAPI* AssetAPI; + + UCreateActorSampleToolBuilder() + { + AssetAPI = nullptr; + } + + virtual bool CanBuildTool(const FToolBuilderState& SceneState) const override; + virtual UInteractiveTool* BuildTool(const FToolBuilderState& SceneState) const override; +}; + + + +/** + * Settings UObject for UCreateActorSampleTool. This UClass inherits from UInteractiveToolPropertySet, + * which provides an OnModified delegate that the Tool will listen to for changes in property values. + */ +UCLASS(Transient) +class SAMPLETOOLSEDITORMODE_API UCreateActorSampleToolProperties : public UInteractiveToolPropertySet +{ + GENERATED_BODY() +public: + UCreateActorSampleToolProperties(); + + /** Place actors on existing objects */ + UPROPERTY(EditAnywhere, Category = Options, meta = (DisplayName = "Place On Objects")) + bool PlaceOnObjects; + + /** Height of ground plane */ + UPROPERTY(EditAnywhere, Category = Options, meta = (DisplayName = "Ground Height", UIMin = "-1000.0", UIMax = "1000.0", ClampMin = "-1000000", ClampMax = "1000000.0")) + float GroundHeight; +}; + + + + +/** + * UCreateActorSampleTool is an example Tool that drops an empty Actor at each position the user + * clicks left mouse button. The Actors are placed at the first ray intersection in the scene, + * or on a ground plane if no scene objects are hit. All the action is in the ::OnClicked handler. + */ +UCLASS() +class SAMPLETOOLSEDITORMODE_API UCreateActorSampleTool : public USingleClickTool +{ + GENERATED_BODY() + +public: + UCreateActorSampleTool(); + + virtual void SetWorld(UWorld* World); + virtual void SetAssetAPI(IToolsContextAssetAPI* AssetAPI); + + virtual void Setup() override; + + virtual void OnClicked(const FInputDeviceRay& ClickPos); + + +protected: + UPROPERTY() + UCreateActorSampleToolProperties* Properties; + + +protected: + /** target World we will raycast into and create Actor in */ + UWorld* TargetWorld; + + /** Access to the ToolContext's Asset Creation API. This is not currently used, but can be used to (eg) add Components, etc*/ + IToolsContextAssetAPI* AssetAPI; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/DrawCurveOnMeshSampleTool.cpp b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/DrawCurveOnMeshSampleTool.cpp new file mode 100644 index 000000000000..cb34f2aca202 --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/DrawCurveOnMeshSampleTool.cpp @@ -0,0 +1,108 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DrawCurveOnMeshSampleTool.h" +#include "InteractiveToolManager.h" +#include "ToolBuilderUtil.h" +#include "SceneManagement.h" // FPrimitiveDrawInterface + +// localization namespace +#define LOCTEXT_NAMESPACE "UDrawCurveOnMeshSampleTool" + +/* + * ToolBuilder + */ + + +UMeshSurfacePointTool* UDrawCurveOnMeshSampleToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const +{ + UDrawCurveOnMeshSampleTool* NewTool = NewObject(SceneState.ToolManager); + return NewTool; +} + + + +/* + * Tool + */ + +UDrawCurveOnMeshSampleToolProperties::UDrawCurveOnMeshSampleToolProperties() +{ + Thickness = 4.0f; + DepthBias = 0.0f; + MinSpacing = 1.0; + NormalOffset = 0.25; + Color = FLinearColor(255, 0, 0); + bScreenSpace = true; +} + + +UDrawCurveOnMeshSampleTool::UDrawCurveOnMeshSampleTool() +{ +} + + +void UDrawCurveOnMeshSampleTool::Setup() +{ + UMeshSurfacePointTool::Setup(); + + // add settings object + Settings = NewObject(this, TEXT("Settings")); + AddToolPropertySource(Settings); +} + + + +void UDrawCurveOnMeshSampleTool::Render(IToolsContextRenderAPI* RenderAPI) +{ + FPrimitiveDrawInterface* PDI = RenderAPI->GetPrimitiveDrawInterface(); + + int NumPts = Positions.Num(); + for (int i = 0; i < NumPts - 1; ++i) + { + FVector A = Positions[i] + Settings->NormalOffset*Normals[i]; + FVector B = Positions[i+1] + Settings->NormalOffset*Normals[i+1]; + PDI->DrawLine(A, B, Settings->Color, 0, Settings->Thickness, Settings->DepthBias, Settings->bScreenSpace); + } +} + + + + +void UDrawCurveOnMeshSampleTool::OnBeginDrag(const FRay& Ray) +{ + Positions.Reset(); + Normals.Reset(); + + FHitResult OutHit; + if (HitTest(Ray, OutHit)) + { + Positions.Add(OutHit.ImpactPoint); + Normals.Add(OutHit.ImpactNormal); + } + +} + +void UDrawCurveOnMeshSampleTool::OnUpdateDrag(const FRay& Ray) +{ + FHitResult OutHit; + if (HitTest(Ray, OutHit)) + { + if ( FVector::Dist(OutHit.ImpactPoint, Positions[Positions.Num()-1]) > Settings->MinSpacing) + { + Positions.Add(OutHit.ImpactPoint); + Normals.Add(OutHit.ImpactNormal); + } + } +} + +void UDrawCurveOnMeshSampleTool::OnEndDrag(const FRay& Ray) +{ + +} + + + + + + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/DrawCurveOnMeshSampleTool.h b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/DrawCurveOnMeshSampleTool.h new file mode 100644 index 000000000000..a5dfd58723bb --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/DrawCurveOnMeshSampleTool.h @@ -0,0 +1,91 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "InteractiveToolBuilder.h" +#include "BaseTools/MeshSurfacePointTool.h" +#include "DrawCurveOnMeshSampleTool.generated.h" + + +/** + * UMeshSurfacePointToolBuilder override for UDrawCurveOnMeshSampleTool + */ +UCLASS(Transient) +class SAMPLETOOLSEDITORMODE_API UDrawCurveOnMeshSampleToolBuilder : public UMeshSurfacePointToolBuilder +{ + GENERATED_BODY() + +public: + virtual UMeshSurfacePointTool* CreateNewTool(const FToolBuilderState& SceneState) const override; +}; + + +/** + * Settings UObject for UDrawCurveOnMeshSampleTool. This UClass inherits from UInteractiveToolPropertySet, + * which provides an OnModified delegate that the Tool will listen to for changes in property values. + */ +UCLASS(Transient) +class SAMPLETOOLSEDITORMODE_API UDrawCurveOnMeshSampleToolProperties : public UInteractiveToolPropertySet +{ + GENERATED_BODY() +public: + UDrawCurveOnMeshSampleToolProperties(); + + UPROPERTY(EditAnywhere, Category = Options) + FLinearColor Color; + + UPROPERTY(EditAnywhere, Category = Options, meta = (DisplayName = "Thickness", UIMin = "0.25", UIMax = "10.0", ClampMin = "0.01", ClampMax = "1000.0")) + float Thickness; + + UPROPERTY(EditAnywhere, Category = Options, meta = (DisplayName = "Min Spacing", UIMin = "0.01", UIMax = "10.0")) + float MinSpacing; + + UPROPERTY(EditAnywhere, Category = Options, meta = (DisplayName = "Offset", UIMin = "0.0", UIMax = "10.0", ClampMin = "-1000.0", ClampMax = "1000.0")) + float NormalOffset; + + UPROPERTY(EditAnywhere, Category = Options, meta = (DisplayName = "Depth Bias", UIMin = "-10.0", UIMax = "10.0")) + float DepthBias; + + UPROPERTY(EditAnywhere, Category = Options) + bool bScreenSpace; +}; + + + +/** + * UDrawCurveOnMeshSampleTool is a sample Tool that allows the user to draw curves on the surface of + * a selected Mesh Component. The various rendering properties of the polycurve are exposed and can be tweaked. + * Nothing is done with the curve, it is just drawn by ::Render() and discarded when the Tool exits. + */ +UCLASS(Transient) +class SAMPLETOOLSEDITORMODE_API UDrawCurveOnMeshSampleTool : public UMeshSurfacePointTool +{ + GENERATED_BODY() + +public: + UDrawCurveOnMeshSampleTool(); + + // UInteractiveTool API + + virtual void Setup() override; + + virtual void Render(IToolsContextRenderAPI* RenderAPI) override; + + // UMeshSurfacePointTool API + virtual void OnBeginDrag(const FRay& Ray) override; + virtual void OnUpdateDrag(const FRay& Ray) override; + virtual void OnEndDrag(const FRay& Ray) override; + +protected: + UPROPERTY() + UDrawCurveOnMeshSampleToolProperties* Settings; + + UPROPERTY() + TArray Positions; + + UPROPERTY() + TArray Normals; + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/MeasureDistanceSampleTool.cpp b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/MeasureDistanceSampleTool.cpp new file mode 100644 index 000000000000..491f78a288f8 --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/MeasureDistanceSampleTool.cpp @@ -0,0 +1,167 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeasureDistanceSampleTool.h" +#include "InteractiveToolManager.h" +#include "ToolBuilderUtil.h" +#include "BaseBehaviors/ClickDragBehavior.h" + +// for raycast into World +#include "CollisionQueryParams.h" +#include "Engine/World.h" + +#include "SceneManagement.h" + +// localization namespace +#define LOCTEXT_NAMESPACE "UMeasureDistanceSampleTool" + +/* + * ToolBuilder + */ + + +bool UMeasureDistanceSampleToolBuilder::CanBuildTool(const FToolBuilderState & SceneState) const +{ + return true; +} + +UInteractiveTool* UMeasureDistanceSampleToolBuilder::BuildTool(const FToolBuilderState & SceneState) const +{ + UMeasureDistanceSampleTool* NewTool = NewObject(SceneState.ToolManager); + NewTool->SetWorld(SceneState.World); + return NewTool; +} + + + +/* + * Tool + */ + +UMeasureDistanceProperties::UMeasureDistanceProperties() +{ + // initialize the points and distance to reasonable values + StartPoint = FVector(0,0,0); + EndPoint = FVector(0,0,100); + Distance = 100; +} + + +UMeasureDistanceSampleTool::UMeasureDistanceSampleTool() +{ +} + + +void UMeasureDistanceSampleTool::SetWorld(UWorld* World) +{ + check(World); + this->TargetWorld = World; +} + + + + +void UMeasureDistanceSampleTool::Setup() +{ + UInteractiveTool::Setup(); + + // Add default mouse input behavior + UClickDragInputBehavior* MouseBehavior = NewObject(); + // We will use the shift key to indicate that we should move the second point. + // This call tells the Behavior to call our OnUpdateModifierState() function on mouse-down and mouse-move + MouseBehavior->Modifiers.RegisterModifier(MoveSecondPointModifierID, FInputDeviceState::IsShiftKeyDown); + MouseBehavior->Initialize(this); + AddInputBehavior(MouseBehavior); + + // Create the property set and register it with the Tool + Properties = NewObject(this, "Measurement"); + AddToolPropertySource(Properties); + + bSecondPointModifierDown = false; + bMoveSecondPoint = false; +} + + +void UMeasureDistanceSampleTool::OnUpdateModifierState(int ModifierID, bool bIsOn) +{ + // keep track of the "second point" modifier (shift key for mouse input) + if (ModifierID == MoveSecondPointModifierID) + { + bSecondPointModifierDown = bIsOn; + } +} + + + +bool UMeasureDistanceSampleTool::CanBeginClickDragSequence(const FInputDeviceRay& PressPos) +{ + // we only start drag if press-down is on top of something we can raycast + FVector Temp; + return FindRayHit(PressPos.WorldRay, Temp); +} + +void UMeasureDistanceSampleTool::OnClickPress(const FInputDeviceRay& PressPos) +{ + // determine whether we are moving first or second point for the drag sequence + bMoveSecondPoint = bSecondPointModifierDown; + UpdatePosition(PressPos.WorldRay); +} + +void UMeasureDistanceSampleTool::OnClickDrag(const FInputDeviceRay& DragPos) +{ + UpdatePosition(DragPos.WorldRay); +} + + + +bool UMeasureDistanceSampleTool::FindRayHit(const FRay& WorldRay, FVector& HitPos) +{ + // trace a ray into the World + FCollisionObjectQueryParams QueryParams(FCollisionObjectQueryParams::AllObjects); + FHitResult Result; + bool bHitWorld = TargetWorld->LineTraceSingleByObjectType(Result, WorldRay.Origin, WorldRay.PointAt(999999), QueryParams); + if (bHitWorld) + { + HitPos = Result.ImpactPoint; + return true; + } + return false; +} + + +void UMeasureDistanceSampleTool::UpdatePosition(const FRay& WorldRay) +{ + if (FindRayHit(WorldRay, (bMoveSecondPoint) ? Properties->EndPoint : Properties->StartPoint)) + { + UpdateDistance(); + } +} + + +void UMeasureDistanceSampleTool::UpdateDistance() +{ + Properties->Distance = FVector::Distance(Properties->StartPoint, Properties->EndPoint); +} + + +void UMeasureDistanceSampleTool::OnPropertyModified(UObject* PropertySet, UProperty* Property) +{ + // if the user updated any of the property fields, update the distance + UpdateDistance(); +} + + + +void UMeasureDistanceSampleTool::Render(IToolsContextRenderAPI* RenderAPI) +{ + FPrimitiveDrawInterface* PDI = RenderAPI->GetPrimitiveDrawInterface(); + // draw a thin line that shows through objects + PDI->DrawLine(Properties->StartPoint, Properties->EndPoint, + FColor(240, 16, 16), SDPG_Foreground, 2.0f, 0.0f, true); + // draw a thicker line that is depth-tested + PDI->DrawLine(Properties->StartPoint, Properties->EndPoint, + FColor(240, 16, 16), SDPG_World, 4.0f, 0.0f, true); +} + + + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/MeasureDistanceSampleTool.h b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/MeasureDistanceSampleTool.h new file mode 100644 index 000000000000..311b49d1ff73 --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/MeasureDistanceSampleTool.h @@ -0,0 +1,102 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "InteractiveToolBuilder.h" +#include "BaseTools/ClickDragTool.h" +#include "MeasureDistanceSampleTool.generated.h" + + +/** + * Builder for UMeasureDistanceSampleTool + */ +UCLASS() +class SAMPLETOOLSEDITORMODE_API UMeasureDistanceSampleToolBuilder : public UInteractiveToolBuilder +{ + GENERATED_BODY() + +public: + virtual bool CanBuildTool(const FToolBuilderState& SceneState) const override; + virtual UInteractiveTool* BuildTool(const FToolBuilderState& SceneState) const override; +}; + + +/** + * Property set for the UMeasureDistanceSampleTool + */ +UCLASS(Transient) +class SAMPLETOOLSEDITORMODE_API UMeasureDistanceProperties : public UInteractiveToolPropertySet +{ + GENERATED_BODY() + +public: + UMeasureDistanceProperties(); + + /** First point of measurement */ + UPROPERTY(EditAnywhere, Category = Options) + FVector StartPoint; + + /** Second point of measurement */ + UPROPERTY(EditAnywhere, Category = Options) + FVector EndPoint; + + /** Current distance measurement */ + UPROPERTY(EditAnywhere, Category = Options) + float Distance; +}; + + + +/** + * UMeasureDistanceSampleTool is an example Tool that allows the user to measure the + * distance between two points. The first point is set by click-dragging the mouse, and + * the second point is set by shift-click-dragging the mouse. + */ +UCLASS() +class SAMPLETOOLSEDITORMODE_API UMeasureDistanceSampleTool : public UInteractiveTool, public IClickDragBehaviorTarget +{ + GENERATED_BODY() + +public: + UMeasureDistanceSampleTool(); + + virtual void SetWorld(UWorld* World); + + // UInteractiveTool overrides + + virtual void Setup() override; + virtual void Render(IToolsContextRenderAPI* RenderAPI) override; + virtual void OnPropertyModified(UObject* PropertySet, UProperty* Property) override; + + // IClickDragBehaviorTarget implementation + + virtual bool CanBeginClickDragSequence(const FInputDeviceRay& PressPos) override; + virtual void OnClickPress(const FInputDeviceRay& PressPos) override; + virtual void OnClickDrag(const FInputDeviceRay& DragPos) override; + // these are not used in this Tool + virtual void OnClickRelease(const FInputDeviceRay& ReleasePos) override {} + virtual void OnTerminateDragSequence() override {} + + // IModifierToggleBehaviorTarget implementation (inherited via IClickDragBehaviorTarget) + + virtual void OnUpdateModifierState(int ModifierID, bool bIsOn) override; + + +protected: + /** Properties of the tool are stored here */ + UPROPERTY() + UMeasureDistanceProperties* Properties; + + +protected: + UWorld* TargetWorld = nullptr; // target World we will raycast into + + static const int MoveSecondPointModifierID = 1; // identifier we associate with the shift key + bool bSecondPointModifierDown = false; // flag we use to keep track of modifier state + bool bMoveSecondPoint = false; // flag we use to keep track of which point we are moving during a press-drag + + bool FindRayHit(const FRay& WorldRay, FVector& HitPos); // raycasts into World + void UpdatePosition(const FRay& WorldRay); // updates first or second point based on raycast + void UpdateDistance(); // updates distance +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorMode.cpp b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorMode.cpp new file mode 100644 index 000000000000..4acbcc7c7ca1 --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorMode.cpp @@ -0,0 +1,236 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SampleToolsEditorMode.h" +#include "SampleToolsEditorModeToolkit.h" +#include "Toolkits/ToolkitManager.h" +#include "EditorViewportClient.h" +#include "EditorModeManager.h" + + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +// AddYourTool Step 1 - include the header file for your Tool here +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +#include "BaseTools/SingleClickTool.h" +#include "BaseTools/MeshSurfacePointTool.h" +#include "SampleTools/CreateActorSampleTool.h" +#include "SampleTools/DrawCurveOnMeshSampleTool.h" +#include "SampleTools/MeasureDistanceSampleTool.h" + +// step 2: register a ToolBuilder in FSampleToolsEditorMode::Enter() +// step 3: add a button in FSampleToolsEditorModeToolkit::Init() + + +#define LOCTEXT_NAMESPACE "FSampleToolsEditorMode" + +const FEditorModeID FSampleToolsEditorMode::EM_SampleToolsEditorModeId = TEXT("EM_SampleToolsEditorMode"); + + +FSampleToolsEditorMode::FSampleToolsEditorMode() +{ + ToolsContext = nullptr; +} + + +FSampleToolsEditorMode::~FSampleToolsEditorMode() +{ + // this should have happend already in ::Exit() + if (ToolsContext != nullptr) + { + ToolsContext->ShutdownContext(); + ToolsContext = nullptr; + } +} + + +void FSampleToolsEditorMode::ActorSelectionChangeNotify() +{ + // @todo support selection change +} + + + +void FSampleToolsEditorMode::Tick(FEditorViewportClient* ViewportClient, float DeltaTime) +{ + FEdMode::Tick(ViewportClient, DeltaTime); + + // give ToolsContext a chance to tick + if (ToolsContext != nullptr) + { + ToolsContext->Tick(ViewportClient, DeltaTime); + } +} + + + + +void FSampleToolsEditorMode::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) +{ + FEdMode::Render(View, Viewport, PDI); + + // give ToolsContext a chance to render + if (ToolsContext != nullptr) + { + ToolsContext->Render(View, Viewport, PDI); + } +} + + + + + +// +// Input device event tracking. We forward input events to the ToolsContext adapter for handling. +// + +bool FSampleToolsEditorMode::InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) +{ + bool bHandled = FEdMode::InputKey(ViewportClient, Viewport, Key, Event); + bHandled |= ToolsContext->InputKey(ViewportClient, Viewport, Key, Event); + return bHandled; +} + +bool FSampleToolsEditorMode::InputAxis(FEditorViewportClient* InViewportClient, FViewport* Viewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime) +{ + // mouse axes: EKeys::MouseX, EKeys::MouseY, EKeys::MouseWheelAxis + return FEdMode::InputAxis(InViewportClient, Viewport, ControllerId, Key, Delta, DeltaTime); +} + + +bool FSampleToolsEditorMode::StartTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) +{ + bool bHandled = FEdMode::StartTracking(InViewportClient, InViewport); + bHandled |= ToolsContext->StartTracking(InViewportClient, InViewport); + return bHandled; +} + +bool FSampleToolsEditorMode::CapturedMouseMove(FEditorViewportClient* InViewportClient, FViewport* InViewport, int32 InMouseX, int32 InMouseY) +{ + bool bHandled = ToolsContext->CapturedMouseMove(InViewportClient, InViewport, InMouseX, InMouseY); + return bHandled; +} + +bool FSampleToolsEditorMode::EndTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) +{ + bool bHandled = ToolsContext->EndTracking(InViewportClient, InViewport); + return bHandled; +} + + + +bool FSampleToolsEditorMode::MouseEnter(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) +{ + bool bHandled = ToolsContext->MouseEnter(ViewportClient, Viewport, x, y); + return bHandled; +} + +bool FSampleToolsEditorMode::MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) +{ + bool bHandled = ToolsContext->MouseMove(ViewportClient, Viewport, x, y); + return bHandled; +} + +bool FSampleToolsEditorMode::MouseLeave(FEditorViewportClient* ViewportClient, FViewport* Viewport) +{ + bool bHandled = ToolsContext->MouseLeave(ViewportClient, Viewport); + return bHandled; +} + + + +bool FSampleToolsEditorMode::ReceivedFocus(FEditorViewportClient* ViewportClient, FViewport* Viewport) +{ + return FEdMode::ReceivedFocus(ViewportClient, Viewport); +} + + +bool FSampleToolsEditorMode::LostFocus(FEditorViewportClient* ViewportClient, FViewport* Viewport) +{ + return FEdMode::LostFocus(ViewportClient, Viewport); +} + + + + + + +void FSampleToolsEditorMode::Enter() +{ + FEdMode::Enter(); + + if (!Toolkit.IsValid() && UsesToolkits()) + { + Toolkit = MakeShareable(new FSampleToolsEditorModeToolkit); + Toolkit->Init(Owner->GetToolkitHost()); + } + + // initialize the adapter that attaches the ToolsContext to this FEdMode + ToolsContext = NewObject(GetTransientPackage(), TEXT("ToolsContext"), RF_Transient); + ToolsContext->InitializeContextFromEdMode(this); + + + ////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + // AddYourTool Step 2 - register a ToolBuilder for your Tool here. + // The string name you pass to the ToolManager is used to select/activate your ToolBuilder later. + ////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + + + auto CreateActorSampleToolBuilder = NewObject(); + CreateActorSampleToolBuilder->AssetAPI = ToolsContext->GetAssetAPI(); + ToolsContext->ToolManager->RegisterToolType(TEXT("CreateActorSampleTool"), CreateActorSampleToolBuilder); + + auto DrawCurveOnMeshSampleToolBuilder = NewObject(); + ToolsContext->ToolManager->RegisterToolType(TEXT("DrawCurveOnMeshSampleTool"), DrawCurveOnMeshSampleToolBuilder); + + auto MeasureDistanceSampleToolBuilder = NewObject(); + ToolsContext->ToolManager->RegisterToolType(TEXT("MeasureDistanceSampleTool"), MeasureDistanceSampleToolBuilder); + + auto SurfacePointToolBuilder = NewObject(); + ToolsContext->ToolManager->RegisterToolType(TEXT("SurfacePointTool"), SurfacePointToolBuilder); + + // active tool type is not relevant here, we just set to default + ToolsContext->ToolManager->SelectActiveToolType(EToolSide::Left, TEXT("SurfacePointTool")); +} + + + + +void FSampleToolsEditorMode::Exit() +{ + // shutdown and clean up the ToolsContext + ToolsContext->ShutdownContext(); + ToolsContext = nullptr; + + if (Toolkit.IsValid()) + { + FToolkitManager::Get().CloseToolkit(Toolkit.ToSharedRef()); + Toolkit.Reset(); + } + + // Call base Exit method to ensure proper cleanup + FEdMode::Exit(); +} + +bool FSampleToolsEditorMode::UsesToolkits() const +{ + return true; +} + + +void FSampleToolsEditorMode::AddReferencedObjects(FReferenceCollector& Collector) +{ + Collector.AddReferencedObject(ToolsContext); +} + + + + + + + + + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorModeModule.cpp b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorModeModule.cpp new file mode 100644 index 000000000000..8403aa98d5c7 --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorModeModule.cpp @@ -0,0 +1,23 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SampleToolsEditorModeModule.h" +#include "SampleToolsEditorMode.h" + +#define LOCTEXT_NAMESPACE "FSampleToolsEditorModeModule" + +void FSampleToolsEditorModeModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + FEditorModeRegistry::Get().RegisterMode(FSampleToolsEditorMode::EM_SampleToolsEditorModeId, LOCTEXT("SampleToolsEditorModeName", "SampleToolsEditorMode"), FSlateIcon(), true); +} + +void FSampleToolsEditorModeModule::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. + FEditorModeRegistry::Get().UnregisterMode(FSampleToolsEditorMode::EM_SampleToolsEditorModeId); +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FSampleToolsEditorModeModule, SampleToolsEditorMode) \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorModeToolkit.cpp b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorModeToolkit.cpp new file mode 100644 index 000000000000..356f0027292b --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorModeToolkit.cpp @@ -0,0 +1,222 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SampleToolsEditorModeToolkit.h" +#include "SampleToolsEditorMode.h" +#include "Engine/Selection.h" + +#include "Modules/ModuleManager.h" +#include "PropertyEditorModule.h" +#include "IDetailsView.h" + +#include "Widgets/Input/SButton.h" +#include "Widgets/Text/STextBlock.h" + + + +#include "EditorModeManager.h" + +#define LOCTEXT_NAMESPACE "FSampleToolsEditorModeToolkit" + +FSampleToolsEditorModeToolkit::FSampleToolsEditorModeToolkit() +{ +} + +void FSampleToolsEditorModeToolkit::Init(const TSharedPtr& InitToolkitHost) +{ + + FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked("PropertyEditor"); + + FDetailsViewArgs DetailsViewArgs( + /*bUpdateFromSelection=*/ false, + /*bLockable=*/ false, + /*bAllowSearch=*/ false, + FDetailsViewArgs::HideNameArea, + /*bHideSelectionTip=*/ true, + /*InNotifyHook=*/ nullptr, + /*InSearchInitialKeyFocus=*/ false, + /*InViewIdentifier=*/ NAME_None); + DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Automatic; + DetailsViewArgs.bShowOptions = false; + DetailsViewArgs.bAllowMultipleTopLevelObjects = true; + + DetailsView = PropertyEditorModule.CreateDetailView(DetailsViewArgs); + + + ////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + // AddYourTool Step 3 - add a button to initialize your Tool + ////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + + + SAssignNew(ToolkitWidget, SBorder) + .HAlign(HAlign_Center) + .Padding(25) + [ + SNew(SVerticalBox) + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Center) + .Padding(50) + [ + SNew(STextBlock) + .AutoWrapText(true) + .Text(LOCTEXT("HeaderLabel", "Sample Tools")) + ] + + + SVerticalBox::Slot() + .HAlign(HAlign_Center) + .AutoHeight() + [ + SNew(SButton).Text(LOCTEXT("CreateActorSampleToolLabel", "Create Actor on Click")) + .OnClicked_Lambda([this]() { return this->StartTool(TEXT("CreateActorSampleTool")); }) + .IsEnabled_Lambda([this]() { return this->CanStartTool(TEXT("CreateActorSampleTool")); }) + ] + + + SVerticalBox::Slot() + .HAlign(HAlign_Center) + .AutoHeight() + [ + SNew(SButton).Text(LOCTEXT("MeasureDistanceSampleToolLabel", "Measure Distance")) + .OnClicked_Lambda([this]() { return this->StartTool(TEXT("MeasureDistanceSampleTool")); }) + .IsEnabled_Lambda([this]() { return this->CanStartTool(TEXT("MeasureDistanceSampleTool")); }) + ] + + + SVerticalBox::Slot() + .HAlign(HAlign_Center) + .AutoHeight() + [ + SNew(SButton).Text(LOCTEXT("DrawCurveOnMeshSampleToolLabel", "Draw Curve On Mesh")) + .OnClicked_Lambda([this]() { return this->StartTool(TEXT("DrawCurveOnMeshSampleTool")); }) + .IsEnabled_Lambda([this]() { return this->CanStartTool(TEXT("DrawCurveOnMeshSampleTool")); }) + ] + + + SVerticalBox::Slot() + .HAlign(HAlign_Center) + .AutoHeight() + [ + SNew(SButton).Text(LOCTEXT("SurfacePointToolLabel", "Surface Point Tool")) + .OnClicked_Lambda([this]() { return this->StartTool(TEXT("SurfacePointTool")); }) + .IsEnabled_Lambda([this]() { return this->CanStartTool(TEXT("SurfacePointTool")); }) + ] + + + + SVerticalBox::Slot() + .HAlign(HAlign_Center) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton).Text(LOCTEXT("AcceptToolButtonLabel", "Accept")) + .OnClicked_Lambda([this]() { return this->EndTool(EToolShutdownType::Accept); }) + .IsEnabled_Lambda([this]() { return this->CanAcceptActiveTool(); }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton).Text(LOCTEXT("CancelToolButtonLabel", "Cancel")) + .OnClicked_Lambda([this]() { return this->EndTool(EToolShutdownType::Cancel); }) + .IsEnabled_Lambda([this]() { return this->CanCancelActiveTool(); }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton).Text(LOCTEXT("CompletedToolButtonLabel", "Complete")) + .OnClicked_Lambda([this]() { return this->EndTool(EToolShutdownType::Completed); }) + .IsEnabled_Lambda([this]() { return this->CanCompleteActiveTool(); }) + ] + ] + + + SVerticalBox::Slot() + .HAlign(HAlign_Center) + .Padding(2) + .AutoHeight() + .MaxHeight(500.0f) + [ + DetailsView->AsShared() + ] + + ]; + + FModeToolkit::Init(InitToolkitHost); +} + + +bool FSampleToolsEditorModeToolkit::CanStartTool(const FString& ToolTypeIdentifier) +{ + UInteractiveToolManager* Manager = GetToolsEditorMode()->GetToolManager(); + + return (Manager->HasActiveTool(EToolSide::Left) == false) && + (Manager->CanActivateTool(EToolSide::Left, ToolTypeIdentifier) == true); +} + +bool FSampleToolsEditorModeToolkit::CanAcceptActiveTool() +{ + return GetToolsEditorMode()->GetToolManager()->CanAcceptActiveTool(EToolSide::Left); +} + +bool FSampleToolsEditorModeToolkit::CanCancelActiveTool() +{ + return GetToolsEditorMode()->GetToolManager()->CanCancelActiveTool(EToolSide::Left); +} + +bool FSampleToolsEditorModeToolkit::CanCompleteActiveTool() +{ + return GetToolsEditorMode()->GetToolManager()->HasActiveTool(EToolSide::Left) && CanCancelActiveTool() == false; +} + + +FReply FSampleToolsEditorModeToolkit::StartTool(const FString& ToolTypeIdentifier) +{ + if (GetToolsEditorMode()->GetToolManager()->SelectActiveToolType(EToolSide::Left, ToolTypeIdentifier) == false) + { + UE_LOG(LogTemp, Warning, TEXT("ToolManager: Unknown Tool Type %s"), *ToolTypeIdentifier); + } + else + { + UE_LOG(LogTemp, Warning, TEXT("ToolManager: Starting Tool Type %s"), *ToolTypeIdentifier); + GetToolsEditorMode()->GetToolManager()->ActivateTool(EToolSide::Left); + + // Update properties panel + UInteractiveTool* CurTool = GetToolsEditorMode()->GetToolManager()->GetActiveTool(EToolSide::Left); + DetailsView->SetObjects(CurTool->GetToolProperties()); + } + return FReply::Handled(); +} + +FReply FSampleToolsEditorModeToolkit::EndTool(EToolShutdownType ShutdownType) +{ + UE_LOG(LogTemp, Warning, TEXT("ENDING TOOL")); + + GetToolsEditorMode()->GetToolManager()->DeactivateTool(EToolSide::Left, ShutdownType); + + DetailsView->SetObject(nullptr); + + return FReply::Handled(); +} + + +FName FSampleToolsEditorModeToolkit::GetToolkitFName() const +{ + return FName("SampleToolsEditorMode"); +} + +FText FSampleToolsEditorModeToolkit::GetBaseToolkitName() const +{ + return NSLOCTEXT("SampleToolsEditorModeToolkit", "DisplayName", "SampleToolsEditorMode Tool"); +} + +class FEdMode* FSampleToolsEditorModeToolkit::GetEditorMode() const +{ + return GLevelEditorModeTools().GetActiveMode(FSampleToolsEditorMode::EM_SampleToolsEditorModeId); +} + +FSampleToolsEditorMode* FSampleToolsEditorModeToolkit::GetToolsEditorMode() const +{ + return (FSampleToolsEditorMode*)GetEditorMode(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorMode.h b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorMode.h new file mode 100644 index 000000000000..5a7af362ac1d --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorMode.h @@ -0,0 +1,157 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "EdMode.h" + +#include "InputState.h" +#include "InteractiveToolManager.h" +#include "EdModeInteractiveToolsContext.h" + + +class FEditorComponentSourceFactory; +class FEditorToolAssetAPI; + + +/** + * This class provides an example of how to connect an FEdMode to the + * InteractiveTools framework. The various FEdMode input event handlers + * forward events to a UEdModeInteractiveToolsContext instance, which + * has all the logic for interacting with the InputRouter, ToolManager, etc. + * The functions here are basically just forwarders. + */ +class FSampleToolsEditorMode : public FEdMode +{ +public: + const static FEditorModeID EM_SampleToolsEditorModeId; +public: + FSampleToolsEditorMode(); + virtual ~FSampleToolsEditorMode(); + + //////////////// + // FEdMode interface + //////////////// + + virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) override; + virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) override; + virtual void ActorSelectionChangeNotify() override; + virtual bool UsesToolkits() const override; + + virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + + // these disable the standard gizmo, which is probably want we want in + // these tools as we can't hit-test the standard gizmo... + virtual bool AllowWidgetMove() override { return false; } + virtual bool ShouldDrawWidget() const override { return false; } + virtual bool UsesTransformWidget() const override { return false; } + + + /* + * focus events + */ + + // called when we "start" this editor mode (ie switch to this tab) + virtual void Enter() override; + + // called when we "end" this editor mode (ie switch to another tab) + virtual void Exit() override; + + // called when viewport window is focused + virtual bool ReceivedFocus(FEditorViewportClient* ViewportClient, FViewport* Viewport) override; + + // called when viewport window loses focus (ie when some other window is focused) + // *not* called when editor is backgrounded, but is called when editor is minimized + virtual bool LostFocus(FEditorViewportClient* ViewportClient, FViewport* Viewport) override; + + + /* + * Mouse position events - These are called when no mouse button is down + */ + + // called when mouse moves over viewport window + virtual bool MouseEnter(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) override; + + // called when mouse leaves viewport window + virtual bool MouseLeave(FEditorViewportClient* ViewportClient, FViewport* Viewport) override; + + // called on any mouse-move event. *not* called during tracking/capturing, eg if any button is down + virtual bool MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) override; + + + /* + * Input Button/Axis Events & Mouse Capture + * + * event sequence for left mouse down/capture/up is: + * - InputKey( EKeys::LeftMouseButton, IE_PRESSED ) + * - StartTracking() + * - CapturedMouseMove() and InputAxis() (repeated for each mouse-move) + * - Inputkey( EKeys::LeftMouseButton, IE_RELEASED ) + * - EndTracking() + * + * for doubleclick, we get one of the above sequence, then a second + * where instead of the first IE_PRESSED we get a IE_DoubleClick + * + * for mouse wheel, we get following sequence + * - InputKey ( EKeys::MouseScrollUp / Down, IE_PRESSED ) + * - InputAxis() for wheel move + * - InputKey ( EKeys::MouseScrollUp / Down, IE_RELEASED ) + * it appears that there will only ever be *one* InputAxis() between the pressed/released sequenece + * + * Note that this wheel event sequence can happen *during* a + * middle-mouse tracking sequence on a wheel-button (nice!) + */ + + + // This is not just called for keyboard keys, it is also called for mouse down/up events! + // Eg for left-press we get Key = EKeys::LeftMouseButton and Event = IE_Pressed + // Return value indicates "handled'. If we return true for mouse press events then + // the StartTracking/CapturedMouseMove/EndTracking sequence is *not* called (but we + // also don't get MouseMove(), so the mouse-movement events appear to be lost?) + virtual bool InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) override; + + + // Called for 1D Axis movements - EKeys::MouseX, EKeys::MouseY, and EKeys::MouseWheelAxis + // Called even if we return true from InputKey which otherwise blocks tracking. + // Return value indicates "handled" but has no effect on CapturedMouseMove events (ie we still get them) + virtual bool InputAxis(FEditorViewportClient* InViewportClient, FViewport* Viewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime); + + + // called on mouse-down. return value indicates whether this was "handled" but + // does not mean we get exclusive capture events! + virtual bool StartTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) override; + + // called during mouse-down mouse-move. Always called, return value is used to indicate whether + // we "handled" this mouse move (see FEditorModeTools::CapturedMouseMove) but does not pre-empt + // other editor modes from seeing this move event... + virtual bool CapturedMouseMove(FEditorViewportClient* InViewportClient, FViewport* InViewport, int32 InMouseX, int32 InMouseY) override; + + // always called on mouse-up + virtual bool EndTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) override; + + + ////////////////// + // End of FEdMode interface + ////////////////// + + +public: + + /** + * @return active ToolManager + */ + virtual UInteractiveToolManager* GetToolManager() const + { + return ToolsContext->ToolManager; + } + +protected: + + /** + * This is the ToolsContext adapter that attaches to the FEdMode. + * We forward FEdMode event handler functions to this implementation. + * This UObject is explicitly kept alive by adding it to the Collector in ::AddReferencedObjects() + */ + UEdModeInteractiveToolsContext* ToolsContext; + +}; diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorModeModule.h b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorModeModule.h new file mode 100644 index 000000000000..8018f2a976f6 --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorModeModule.h @@ -0,0 +1,15 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FSampleToolsEditorModeModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorModeToolkit.h b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorModeToolkit.h new file mode 100644 index 000000000000..aaa422310a80 --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorModeToolkit.h @@ -0,0 +1,46 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Toolkits/BaseToolkit.h" +#include "InteractiveTool.h" +#include "SampleToolsEditorMode.h" + +class IDetailsView; + +/** + * This FModeToolkit just creates a basic UI panel that allows various InteractiveTools to + * be initialized, and a DetailsView used to show properties of the active Tool. + */ +class FSampleToolsEditorModeToolkit : public FModeToolkit +{ +public: + FSampleToolsEditorModeToolkit(); + + // FModeToolkit interface + virtual void Init(const TSharedPtr& InitToolkitHost) override; + + // IToolkit interface + virtual FName GetToolkitFName() const override; + virtual FText GetBaseToolkitName() const override; + virtual class FEdMode* GetEditorMode() const override; + virtual TSharedPtr GetInlineContent() const override { return ToolkitWidget; } + + virtual FSampleToolsEditorMode* GetToolsEditorMode() const; + +private: + + TSharedPtr ToolkitWidget; + TSharedPtr DetailsView; + + // these functions just forward calls to the ToolsContext / ToolManager + + bool CanStartTool(const FString& ToolTypeIdentifier); + bool CanAcceptActiveTool(); + bool CanCancelActiveTool(); + bool CanCompleteActiveTool(); + + FReply StartTool(const FString& ToolTypeIdentifier); + FReply EndTool(EToolShutdownType ShutdownType); +}; diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/SampleToolsEditorMode.Build.cs b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/SampleToolsEditorMode.Build.cs new file mode 100644 index 000000000000..775928f751d1 --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/SampleToolsEditorMode.Build.cs @@ -0,0 +1,60 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class SampleToolsEditorMode : ModuleRules +{ + public SampleToolsEditorMode(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + //"ContentBrowser" + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core" + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "InputCore", + "UnrealEd", + "ContentBrowser", + "LevelEditor", + "InteractiveToolsFramework", + "EditorInteractiveToolsFramework" + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalMeshReductionPlugin.cpp b/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalMeshReductionPlugin.cpp index 33df3791ba64..01fb905a89e5 100644 --- a/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalMeshReductionPlugin.cpp +++ b/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalMeshReductionPlugin.cpp @@ -1772,7 +1772,34 @@ void FQuadricSkeletalMeshReduction::ReduceSkeletalMesh(USkeletalMesh& SkeletalMe UE_LOG(LogSkeletalMeshReduction, Warning, TEXT("Building LOD %d - Invalid Base LOD entered. Using Base LOD 0 instead"), LODIndex); } } - + + //Store the sections flags + struct FSectionData + { + uint16 MaterialIndex; + bool bCastShadow; + bool bRecomputeTangent; + }; + + TMap BackupSectionIndexToSectionData; + //Store the section data. Store the source LOD model in case we add a LOD, or the target LOD model in case the LOD already exist + FSkeletalMeshLODModel& BackupSectionLODModel = bLODModelAdded ? SkeletalMeshResource.LODModels[Settings.BaseLOD] : SkeletalMeshResource.LODModels[LODIndex]; + BackupSectionIndexToSectionData.Reserve(BackupSectionLODModel.Sections.Num()); + + for (int32 SectionIndex = 0; SectionIndex < BackupSectionLODModel.Sections.Num(); ++SectionIndex) + { + //Skip disable and not generated section + int32 MaxGeneratedLODIndex = BackupSectionLODModel.Sections[SectionIndex].GenerateUpToLodIndex; + if (BackupSectionLODModel.Sections[SectionIndex].bDisabled || (MaxGeneratedLODIndex != -1 && MaxGeneratedLODIndex < LODIndex)) + { + continue; + } + FSectionData& SectionData = BackupSectionIndexToSectionData.FindOrAdd(SectionIndex); + SectionData.MaterialIndex = BackupSectionLODModel.Sections[SectionIndex].MaterialIndex; + SectionData.bCastShadow = BackupSectionLODModel.Sections[SectionIndex].bCastShadow; + SectionData.bRecomputeTangent = BackupSectionLODModel.Sections[SectionIndex].bRecomputeTangent; + } + auto FillClothingData = [&SkeletalMeshResource, &LODIndex, bLODModelAdded](int32 &EnableSectionNumber, TArray &SectionStatus) { EnableSectionNumber = 0; @@ -1961,6 +1988,31 @@ void FQuadricSkeletalMeshReduction::ReduceSkeletalMesh(USkeletalMesh& SkeletalMe // Flag this LOD as having been simplified. SkeletalMesh.GetLODInfo(LODIndex)->bHasBeenSimplified = true; SkeletalMesh.bHasBeenSimplified = true; + //Restore section data + FSkeletalMeshLODModel& ImportedModelLOD = SkeletalMesh.GetImportedModel()->LODModels[LODIndex]; + TArray SectionMatched; + SectionMatched.AddZeroed(ImportedModelLOD.Sections.Num()); + for (auto Kvp : BackupSectionIndexToSectionData) + { + const int32 SourceSectionIndex = Kvp.Key; + + const FSectionData& SectionData = Kvp.Value; + int32 MaxSectionIndex = FMath::Min(ImportedModelLOD.Sections.Num(), SourceSectionIndex+1); + for (int32 SectionIndex = 0; SectionIndex < MaxSectionIndex; ++SectionIndex) + { + if (SectionMatched[SectionIndex]) + { + continue; + } + if (SectionData.MaterialIndex == ImportedModelLOD.Sections[SectionIndex].MaterialIndex) + { + ImportedModelLOD.Sections[SectionIndex].bCastShadow = SectionData.bCastShadow; + ImportedModelLOD.Sections[SectionIndex].bRecomputeTangent = SectionData.bRecomputeTangent; + SectionMatched[SectionIndex] = true; + break; + } + } + } } else { diff --git a/Engine/Plugins/FX/Niagara/Niagara.uplugin b/Engine/Plugins/FX/Niagara/Niagara.uplugin index b0c3753b3748..16ccd2e5fd71 100644 --- a/Engine/Plugins/FX/Niagara/Niagara.uplugin +++ b/Engine/Plugins/FX/Niagara/Niagara.uplugin @@ -26,12 +26,16 @@ "Type": "Runtime", "LoadingPhase": "PreDefault" }, + { + "Name" : "NiagaraAnimNotifies", + "Type" : "Runtime", + "LoadingPhase" : "PreDefault" + }, { "Name" : "NiagaraShader", "Type" : "Runtime", "LoadingPhase" : "PostConfigInit" }, - { "Name" : "NiagaraVertexFactories", "Type" : "Runtime", diff --git a/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraDataInterfaceSkeletalMesh.ush b/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraDataInterfaceSkeletalMesh.ush new file mode 100644 index 000000000000..9dd8e5215446 --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraDataInterfaceSkeletalMesh.ush @@ -0,0 +1,501 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#ifndef DISKELMESH_USE_EXTRA_INFLUENCES +#define DISKELMESH_USE_EXTRA_INFLUENCES 0 // Default is 4 bone, this enables support for 4 & 8 bones +#endif + +struct FDISkelMeshContext +{ + Buffer MeshIndexBuffer; + Buffer MeshVertexBuffer; + Buffer MeshSkinWeightBuffer; + Buffer MeshCurrBonesBuffer; + Buffer MeshPrevBonesBuffer; + Buffer MeshCurrSamplingBonesBuffer; + Buffer MeshPrevSamplingBonesBuffer; + Buffer MeshTangentBuffer; + Buffer MeshTexCoordBuffer; + Buffer MeshColorBuffer; + Buffer MeshTriangleSamplerProbaBuffer; + Buffer MeshTriangleSamplerAliasBuffer; + Buffer MeshTriangleMatricesOffsetBuffer; + uint MeshTriangleCount; + uint MeshVertexCount; + uint MeshWeightStride; + uint MeshNumTexCoord; + uint MeshNumWeights; + uint NumSpecificBones; + Buffer SpecificBones; + uint NumSpecificSocketBones; + Buffer SpecificSocketBones; + float4x4 InstanceTransform; + float4x4 InstancePrevTransform; + float InstanceInvDeltaTime; + bool UniformTriangleSamplingEnable; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Triangle Sampling + +struct FDISkelMeshSkinnedVertex +{ + float3 Position; + float3 PrevPosition; + float3 TangentX; + float3 TangentY; + float3 TangentZ; +}; + +float3x4 DISkelMesh_GetPrevBoneSkinningMatrix(in FDISkelMeshContext DIContext, uint Bone) +{ + return float3x4(DIContext.MeshPrevBonesBuffer[Bone * 3], DIContext.MeshPrevBonesBuffer[Bone * 3 + 1], DIContext.MeshPrevBonesBuffer[Bone * 3 + 2]); +} + +float3x4 DISkelMesh_GetPrevSkinningMatrix(in FDISkelMeshContext DIContext, uint VertexIndex, int4 BlendIndices, float4 BlendWeights) +{ + // Get the matrix offset for each vertex because BlendIndices are stored relatively to each section start vertex. + uint MatrixOffset = DIContext.MeshTriangleMatricesOffsetBuffer[VertexIndex]; + + float3x4 Result; + Result = DISkelMesh_GetPrevBoneSkinningMatrix(DIContext, MatrixOffset + BlendIndices.x) * BlendWeights.x; + Result += DISkelMesh_GetPrevBoneSkinningMatrix(DIContext, MatrixOffset + BlendIndices.y) * BlendWeights.y; + Result += DISkelMesh_GetPrevBoneSkinningMatrix(DIContext, MatrixOffset + BlendIndices.z) * BlendWeights.z; + Result += DISkelMesh_GetPrevBoneSkinningMatrix(DIContext, MatrixOffset + BlendIndices.w) * BlendWeights.w; + return Result; +} + +float3x4 DISkelMesh_GetCurrBoneSkinningMatrix(in FDISkelMeshContext DIContext, uint Bone) +{ + return float3x4(DIContext.MeshCurrBonesBuffer[Bone * 3], DIContext.MeshCurrBonesBuffer[Bone * 3 + 1], DIContext.MeshCurrBonesBuffer[Bone * 3 + 2]); +} + +float3x4 DISkelMesh_GetCurrSkinningMatrix(in FDISkelMeshContext DIContext, uint VertexIndex, int4 BlendIndices, float4 BlendWeights) +{ + // Get the matrix offset for each vertex because BlendIndices are stored relatively to each section start vertex. + uint MatrixOffset = DIContext.MeshTriangleMatricesOffsetBuffer[VertexIndex]; + + float3x4 Result; + Result = DISkelMesh_GetCurrBoneSkinningMatrix(DIContext, MatrixOffset + BlendIndices.x) * BlendWeights.x; + Result += DISkelMesh_GetCurrBoneSkinningMatrix(DIContext, MatrixOffset + BlendIndices.y) * BlendWeights.y; + Result += DISkelMesh_GetCurrBoneSkinningMatrix(DIContext, MatrixOffset + BlendIndices.z) * BlendWeights.z; + Result += DISkelMesh_GetCurrBoneSkinningMatrix(DIContext, MatrixOffset + BlendIndices.w) * BlendWeights.w; + return Result; +} + +int4 DISkelMesh_UnpackIndices4(uint Packed) +{ + return int4(Packed & 0xff, Packed >> 8 & 0xff, Packed >> 16 & 0xff, Packed >> 24 & 0xff); +} + +float4 DISkelMesh_UnpackWeights4(uint Packed) +{ + return float4(Packed & 0xff, Packed >> 8 & 0xff, Packed >> 16 & 0xff, Packed >> 24 & 0xff) / 255.0f; +} + +FDISkelMeshSkinnedVertex DISkelMesh_GetSkinnedVertex(in FDISkelMeshContext DIContext, uint VertexIndex) +{ + //-TODO: Use a R32G32B32 format + float3 CurrPosition = float3(DIContext.MeshVertexBuffer[VertexIndex * 3], DIContext.MeshVertexBuffer[VertexIndex * 3 + 1], DIContext.MeshVertexBuffer[VertexIndex * 3 + 2]); + float3 PrevPosition = CurrPosition; + + float3 TangentX = TangentBias(DIContext.MeshTangentBuffer[VertexIndex * 2 ].xyz); + float4 TangentZ = TangentBias(DIContext.MeshTangentBuffer[VertexIndex * 2 + 1].xyzw); + + if ( DIContext.MeshNumWeights > 0 ) + { + float3x4 PrevBoneMatrix; + float3x4 CurrBoneMatrix; + +#if DISKELMESH_USE_EXTRA_INFLUENCES + uint WeightOffset = DIContext.MeshNumWeights == 8 ? 2 : 1; + + int4 BlendIndices = DISkelMesh_UnpackIndices4(DIContext.MeshSkinWeightBuffer[VertexIndex * DIContext.MeshWeightStride]); + float4 BlendWeights = DISkelMesh_UnpackWeights4(DIContext.MeshSkinWeightBuffer[VertexIndex * DIContext.MeshWeightStride + WeightOffset]); + CurrBoneMatrix = DISkelMesh_GetCurrSkinningMatrix(DIContext, VertexIndex, BlendIndices, BlendWeights); + PrevBoneMatrix = DISkelMesh_GetPrevSkinningMatrix(DIContext, VertexIndex, BlendIndices, BlendWeights); + if (DIContext.MeshNumWeights == 8) + { + BlendIndices = DISkelMesh_UnpackIndices4(DIContext.MeshSkinWeightBuffer[VertexIndex * DIContext.MeshWeightStride + 2]); + BlendWeights = DISkelMesh_UnpackWeights4(DIContext.MeshSkinWeightBuffer[VertexIndex * DIContext.MeshWeightStride + WeightOffset + 1]); + CurrBoneMatrix += DISkelMesh_GetCurrSkinningMatrix(DIContext, VertexIndex, BlendIndices, BlendWeights); + PrevBoneMatrix += DISkelMesh_GetPrevSkinningMatrix(DIContext, VertexIndex, BlendIndices, BlendWeights); + } +#else + int4 BlendIndices = DISkelMesh_UnpackIndices4(DIContext.MeshSkinWeightBuffer[VertexIndex * DIContext.MeshWeightStride]); + float4 BlendWeights = DISkelMesh_UnpackWeights4(DIContext.MeshSkinWeightBuffer[VertexIndex * DIContext.MeshWeightStride + 1]); + CurrBoneMatrix = DISkelMesh_GetCurrSkinningMatrix(DIContext, VertexIndex, BlendIndices, BlendWeights); + PrevBoneMatrix = DISkelMesh_GetPrevSkinningMatrix(DIContext, VertexIndex, BlendIndices, BlendWeights); +#endif + + CurrPosition = mul(CurrBoneMatrix, float4(CurrPosition, 1.0f)).xyz; + PrevPosition = mul(PrevBoneMatrix, float4(PrevPosition, 1.0f)).xyz; + + // Not using InverseTranspose of matrices so assuming uniform scaling only (same as SkinCache) + TangentX.xyz = mul(CurrBoneMatrix, float4(TangentX.xyz, 0.0f)).xyz; + TangentZ.xyz = mul(CurrBoneMatrix, float4(TangentZ.xyz, 0.0f)).xyz; + } + + FDISkelMeshSkinnedVertex SkinnedVertex; + SkinnedVertex.Position = CurrPosition; + SkinnedVertex.PrevPosition = PrevPosition; + SkinnedVertex.TangentX = TangentX; + SkinnedVertex.TangentY = cross(TangentZ.xyz, TangentX.xyz); + SkinnedVertex.TangentZ = TangentZ.xyz; + return SkinnedVertex; +} + +FDISkelMeshSkinnedVertex DISkelMesh_GetSkinnedPointOnTriangle(in FDISkelMeshContext DIContext, uint TriangleIndex, float3 BaryCoord) +{ + uint IndexBufferOffset = TriangleIndex * 3; + uint VertexIndex0 = DIContext.MeshIndexBuffer[IndexBufferOffset]; + uint VertexIndex1 = DIContext.MeshIndexBuffer[IndexBufferOffset + 1]; + uint VertexIndex2 = DIContext.MeshIndexBuffer[IndexBufferOffset + 2]; + + FDISkelMeshSkinnedVertex SkinnedVertex0 = DISkelMesh_GetSkinnedVertex(DIContext, VertexIndex0); + FDISkelMeshSkinnedVertex SkinnedVertex1 = DISkelMesh_GetSkinnedVertex(DIContext, VertexIndex1); + FDISkelMeshSkinnedVertex SkinnedVertex2 = DISkelMesh_GetSkinnedVertex(DIContext, VertexIndex2); + + FDISkelMeshSkinnedVertex FinalVertex; + FinalVertex.Position = (SkinnedVertex0.Position * BaryCoord.x) + (SkinnedVertex1.Position * BaryCoord.y) + (SkinnedVertex2.Position * BaryCoord.z); + FinalVertex.PrevPosition = (SkinnedVertex0.PrevPosition * BaryCoord.x) + (SkinnedVertex1.PrevPosition * BaryCoord.y) + (SkinnedVertex2.PrevPosition * BaryCoord.z); + FinalVertex.TangentX = (SkinnedVertex0.TangentX * BaryCoord.x) + (SkinnedVertex1.TangentX * BaryCoord.y) + (SkinnedVertex2.TangentX * BaryCoord.z); + FinalVertex.TangentY = (SkinnedVertex0.TangentY * BaryCoord.x) + (SkinnedVertex1.TangentY * BaryCoord.y) + (SkinnedVertex2.TangentY * BaryCoord.z); + FinalVertex.TangentZ = (SkinnedVertex0.TangentZ * BaryCoord.x) + (SkinnedVertex1.TangentZ * BaryCoord.y) + (SkinnedVertex2.TangentZ * BaryCoord.z); + + return FinalVertex; +} + +FDISkelMeshSkinnedVertex DISkelMesh_GetSkinnedPointOnTriangleWS(in FDISkelMeshContext DIContext, uint TriangleIndex, float3 BaryCoord) +{ + FDISkelMeshSkinnedVertex FinalVertex = DISkelMesh_GetSkinnedPointOnTriangle(DIContext, TriangleIndex, BaryCoord); + FinalVertex.Position = mul(float4(FinalVertex.Position, 1.0f), DIContext.InstanceTransform).xyz; + FinalVertex.PrevPosition = mul(float4(FinalVertex.PrevPosition, 1.0f), DIContext.InstancePrevTransform).xyz; + FinalVertex.TangentX = mul(float4(FinalVertex.TangentX, 0.0f), DIContext.InstanceTransform).xyz; + FinalVertex.TangentY = mul(float4(FinalVertex.TangentY, 0.0f), DIContext.InstanceTransform).xyz; + FinalVertex.TangentZ = mul(float4(FinalVertex.TangentZ, 0.0f), DIContext.InstanceTransform).xyz; + + return FinalVertex; +} + +void DISKelMesh_RandomTriCoord(in FDISkelMeshContext DIContext, out uint OutTriangle, out float3 OutBaryCoord) +{ + float RandT0 = NiagaraInternalNoise(1, 2, 3); + + [branch] + if ( !DIContext.UniformTriangleSamplingEnable ) + { + // Uniform triangle id selection + OutTriangle = min(uint(RandT0 * float(DIContext.MeshTriangleCount)), DIContext.MeshTriangleCount - 1); // avoid % by using mul/min to Tri = MeshTriangleCountName + } + else + { + // Uniform area weighted position selection (using alias method from Alias method from FWeightedRandomSampler) + uint TriangleIndex = min(uint(RandT0*float(DIContext.MeshTriangleCount)), DIContext.MeshTriangleCount - 1); + float TriangleProbability = DIContext.MeshTriangleSamplerProbaBuffer[TriangleIndex]; + + // Alias check + float RandT1 = NiagaraInternalNoise(1, 2, 3); + if( RandT1 > TriangleProbability ) + { + TriangleIndex = DIContext.MeshTriangleSamplerAliasBuffer[TriangleIndex]; + } + OutTriangle = TriangleIndex; + } + + float r0 = NiagaraInternalNoise(1, 2, 3); + float r1 = NiagaraInternalNoise(1, 2, 3); + float sqrt0 = sqrt(r0); + float sqrt1 = sqrt(r1); + OutBaryCoord = float3(1.0f - sqrt0, sqrt0 * (1.0 - r1), r1 * sqrt0); +} + +void DISKelMesh_GetSkinnedTriangleDataWS(in FDISkelMeshContext DIContext, in uint TriangleIndex, in float3 BaryCoord, out float3 OutPosition, out float3 OutVelocity, out float3 OutNormal, out float3 OutBinormal, out float3 OutTangent) +{ + FDISkelMeshSkinnedVertex SkinnedVertex = DISkelMesh_GetSkinnedPointOnTriangleWS(DIContext, TriangleIndex, BaryCoord); + OutPosition = SkinnedVertex.Position; + OutVelocity = (SkinnedVertex.Position - SkinnedVertex.PrevPosition) * DIContext.InstanceInvDeltaTime; + OutNormal = normalize(SkinnedVertex.TangentZ); + OutBinormal = normalize(SkinnedVertex.TangentY); + OutTangent = normalize(SkinnedVertex.TangentX); +} + +void DISKelMesh_GetSkinnedTriangleData(in FDISkelMeshContext DIContext, in uint TriangleIndex, in float3 BaryCoord, out float3 OutPosition, out float3 OutVelocity, out float3 OutNormal, out float3 OutBinormal, out float3 OutTangent) +{ + FDISkelMeshSkinnedVertex SkinnedVertex = DISkelMesh_GetSkinnedPointOnTriangle(DIContext, TriangleIndex, BaryCoord); + OutPosition = SkinnedVertex.Position; + OutVelocity = (SkinnedVertex.Position - SkinnedVertex.PrevPosition) * DIContext.InstanceInvDeltaTime; + OutNormal = normalize(SkinnedVertex.TangentZ); + OutBinormal = normalize(SkinnedVertex.TangentY); + OutTangent = normalize(SkinnedVertex.TangentX); +} + +void DISKelMesh_GetSkinnedTriangleDataInterpolatedWS(in FDISkelMeshContext DIContext, in uint TriangleIndex, in float3 BaryCoord, in float Interp, out float3 OutPosition, out float3 OutVelocity, out float3 OutNormal, out float3 OutBinormal, out float3 OutTangent) +{ + FDISkelMeshSkinnedVertex SkinnedVertex = DISkelMesh_GetSkinnedPointOnTriangleWS(DIContext, TriangleIndex, BaryCoord); + OutPosition = lerp(SkinnedVertex.PrevPosition, SkinnedVertex.Position, Interp); + OutVelocity = (SkinnedVertex.Position - SkinnedVertex.PrevPosition) * DIContext.InstanceInvDeltaTime; + OutNormal = normalize(SkinnedVertex.TangentZ); + OutBinormal = normalize(SkinnedVertex.TangentY); + OutTangent = normalize(SkinnedVertex.TangentX); +} + +void DISKelMesh_GetSkinnedTriangleDataInterpolated(in FDISkelMeshContext DIContext, in uint TriangleIndex, in float3 BaryCoord, in float Interp, out float3 OutPosition, out float3 OutVelocity, out float3 OutNormal, out float3 OutBinormal, out float3 OutTangent) +{ + FDISkelMeshSkinnedVertex SkinnedVertex = DISkelMesh_GetSkinnedPointOnTriangle(DIContext, TriangleIndex, BaryCoord); + OutPosition = lerp(SkinnedVertex.PrevPosition, SkinnedVertex.Position, Interp); + OutVelocity = (SkinnedVertex.Position - SkinnedVertex.PrevPosition) * DIContext.InstanceInvDeltaTime; + OutNormal = normalize(SkinnedVertex.TangentZ); + OutBinormal = normalize(SkinnedVertex.TangentY); + OutTangent = normalize(SkinnedVertex.TangentX); +} + +void DISKelMesh_GetTriUV(in FDISkelMeshContext DIContext, in uint TriangleIndex, in float3 BaryCoord, in int UVSet, out float2 OutUV) +{ + if (DIContext.MeshNumTexCoord > 0) + { + uint IndexBufferOffset = TriangleIndex * 3; + uint VertexIndex0 = DIContext.MeshIndexBuffer[IndexBufferOffset ]; + uint VertexIndex1 = DIContext.MeshIndexBuffer[IndexBufferOffset+1]; + uint VertexIndex2 = DIContext.MeshIndexBuffer[IndexBufferOffset+2]; + + uint Stride = DIContext.MeshNumTexCoord; + uint SelectedUVSet = clamp((uint) UVSet, 0, DIContext.MeshNumTexCoord - 1); + float2 UV0 = DIContext.MeshTexCoordBuffer[VertexIndex0 * Stride + SelectedUVSet]; + float2 UV1 = DIContext.MeshTexCoordBuffer[VertexIndex1 * Stride + SelectedUVSet]; + float2 UV2 = DIContext.MeshTexCoordBuffer[VertexIndex2 * Stride + SelectedUVSet]; + + OutUV = UV0 * BaryCoord.x + UV1 * BaryCoord.y + UV2 * BaryCoord.z; + } + else + { + OutUV = 0.0f; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Bone Sampling + +float4 QuatSlerp(float4 Quat1, float4 Quat2, float Slerp) +{ + const float RawCosom = dot(Quat1, Quat2); + const float Cosom = abs(RawCosom); + + float Scale0, Scale1; + if (Cosom < 0.9999f) + { + const float Omega = acos(Cosom); + const float InvSin = 1.f / sin(Omega); + Scale0 = sin((1.f - Slerp) * Omega) * InvSin; + Scale1 = sin(Slerp * Omega) * InvSin; + } + else + { + Scale0 = 1.0f - Slerp; + Scale1 = Slerp; + } + + // In keeping with our flipped Cosom: + Scale1 = RawCosom >= 0.0f ? Scale1 : -Scale1; + + return (Scale0 * Quat1) + (Scale1 * Quat2); +} + +void DISkelMesh_GetSkinnedBoneCommon(in FDISkelMeshContext DIContext, in int Bone, in float Interp, in bool bWorldSpace, out float3 OutPosition, out float4 OutRotation, out float3 OutVelocity) +{ + float3 PrevPosition = DIContext.MeshPrevSamplingBonesBuffer[Bone * 2].xyz; + float4 PrevRotation = DIContext.MeshPrevSamplingBonesBuffer[Bone * 2 + 1]; + float3 CurrPosition = DIContext.MeshCurrSamplingBonesBuffer[Bone * 2].xyz; + float4 CurrRotation = DIContext.MeshCurrSamplingBonesBuffer[Bone * 2 + 1]; + + if (bWorldSpace) + { + PrevPosition = mul(float4(PrevPosition, 1), DIContext.InstancePrevTransform).xyz; + CurrPosition = mul(float4(CurrPosition, 1), DIContext.InstanceTransform).xyz; + } + CurrPosition = lerp(PrevPosition, CurrPosition, Interp); + + OutPosition = CurrPosition; + OutRotation = QuatSlerp(PrevRotation, CurrRotation, Interp); + OutVelocity = (CurrPosition - PrevPosition) * DIContext.InstanceInvDeltaTime; +} + +void DISkelMesh_GetSkinnedBone(in FDISkelMeshContext DIContext, in int Bone, out float3 OutPosition, out float4 OutRotation, out float3 OutVelocity) +{ + DISkelMesh_GetSkinnedBoneCommon(DIContext, Bone, 1.0f, false, OutPosition, OutRotation, OutVelocity); +} + +void DISkelMesh_GetSkinnedBoneInterpolated(in FDISkelMeshContext DIContext, in int Bone, in float Interp, out float3 OutPosition, out float4 OutRotation, out float3 OutVelocity) +{ + DISkelMesh_GetSkinnedBoneCommon(DIContext, Bone, Interp, false, OutPosition, OutRotation, OutVelocity); +} + +void DISkelMesh_GetSkinnedBoneWS(in FDISkelMeshContext DIContext, in int Bone, out float3 OutPosition, out float4 OutRotation, out float3 OutVelocity) +{ + DISkelMesh_GetSkinnedBoneCommon(DIContext, Bone, 1.0f, true, OutPosition, OutRotation, OutVelocity); +} + +void DISkelMesh_GetSkinnedBoneInterpolatedWS(in FDISkelMeshContext DIContext, in int Bone, in float Interp, out float3 OutPosition, out float4 OutRotation, out float3 OutVelocity) +{ + DISkelMesh_GetSkinnedBoneCommon(DIContext, Bone, Interp, true, OutPosition, OutRotation, OutVelocity); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Vertex Sampling + +void DISkelMesh_GetTriVertices(in FDISkelMeshContext DIContext, in uint TriangleIndex, out int VertexIndex0, out int VertexIndex1, out int VertexIndex2) +{ + uint IndexBufferOffset = TriangleIndex * 3; + VertexIndex0 = DIContext.MeshIndexBuffer[IndexBufferOffset]; + VertexIndex1 = DIContext.MeshIndexBuffer[IndexBufferOffset + 1]; + VertexIndex2 = DIContext.MeshIndexBuffer[IndexBufferOffset + 2]; +} + +void DISkelMesh_GetTriColor(in FDISkelMeshContext DIContext, in uint TriangleIndex, in float3 BaryCoord, out float4 OutColor) +{ + uint IndexBufferOffset = TriangleIndex * 3; + uint VertexIndex0 = DIContext.MeshIndexBuffer[IndexBufferOffset]; + uint VertexIndex1 = DIContext.MeshIndexBuffer[IndexBufferOffset + 1]; + uint VertexIndex2 = DIContext.MeshIndexBuffer[IndexBufferOffset + 2]; + OutColor = (DIContext.MeshColorBuffer[VertexIndex0] * BaryCoord.x) + (DIContext.MeshColorBuffer[VertexIndex1] * BaryCoord.y) + (DIContext.MeshColorBuffer[VertexIndex2] * BaryCoord.z); +} + +void DISkelMesh_GetRandomVertex(in FDISkelMeshContext DIContext, out int OutVertex) +{ + float RandT0 = NiagaraInternalNoise(1, 2, 3); + OutVertex = min(uint(RandT0 * float(DIContext.MeshVertexCount)), DIContext.MeshVertexCount - 1); // avoid % by using mul/min to Tri = MeshVertexCount +} + +void DISkelMesh_IsValidVertex(in FDISkelMeshContext DIContext, in int Vertex, out bool IsValid) +{ + IsValid = Vertex < (int)DIContext.MeshVertexCount; +} + +void DISkelMesh_GetSkinnedVertex(in FDISkelMeshContext DIContext, in int VertexIndex, out float3 OutPosition, out float3 OutVelocity) +{ + FDISkelMeshSkinnedVertex SkinnedVertex = DISkelMesh_GetSkinnedVertex(DIContext, VertexIndex); + OutPosition = SkinnedVertex.Position; + OutVelocity = (SkinnedVertex.Position - SkinnedVertex.PrevPosition) * DIContext.InstanceInvDeltaTime; +} + +void DISkelMesh_GetSkinnedVertexWS(in FDISkelMeshContext DIContext, in int VertexIndex, out float3 OutPosition, out float3 OutVelocity) +{ + FDISkelMeshSkinnedVertex SkinnedVertex = DISkelMesh_GetSkinnedVertex(DIContext, VertexIndex); + OutPosition = mul(float4(SkinnedVertex.Position, 1.0f), DIContext.InstanceTransform).xyz; + float3 PrevPosition = mul(float4(SkinnedVertex.PrevPosition, 1.0f), DIContext.InstancePrevTransform).xyz; + OutVelocity = (OutPosition - PrevPosition) * DIContext.InstanceInvDeltaTime; +} + +void DISkelMesh_GetVertexColor(in FDISkelMeshContext DIContext, in int VertexIndex, out float4 OutColor) +{ + OutColor = DIContext.MeshColorBuffer[VertexIndex]; +} + +void DISkelMesh_GetVertexUV(in FDISkelMeshContext DIContext, in int VertexIndex, in int UVSet, out float2 OutUV) +{ + if (DIContext.MeshNumTexCoord > 0) + { + uint Stride = DIContext.MeshNumTexCoord; + uint SelectedUVSet = clamp((uint) UVSet, 0, DIContext.MeshNumTexCoord - 1); + OutUV = DIContext.MeshTexCoordBuffer[VertexIndex * Stride + SelectedUVSet]; + } + else + { + OutUV = 0.0f; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Specific Bones / Sockets +void DISkelMesh_GetSpecificBoneCount(in FDISkelMeshContext DIContext, out int Count) +{ + Count = DIContext.NumSpecificBones; +} + +void DISkelMesh_GetSpecificBoneAt(in FDISkelMeshContext DIContext, in int InBoneIndex, out int Bone) +{ + Bone = (int)DIContext.SpecificBones[InBoneIndex]; +} + +void DISkelMesh_RandomSpecificBone(in FDISkelMeshContext DIContext, out int Bone) +{ + float RandT0 = NiagaraInternalNoise(1, 2, 3); + Bone = min(uint(RandT0 * float(DIContext.NumSpecificBones)), DIContext.NumSpecificBones - 1); // avoid % by using mul/min to Tri = MeshVertexCount +} + +void DISkelMesh_GetSpecificSocketCount(in FDISkelMeshContext DIContext, out int Count) +{ + Count = DIContext.NumSpecificSocketBones; +} + +void DISkelMesh_GetSpecificSocketBoneAt(in FDISkelMeshContext DIContext, in int InSocketIndex, out int Bone) +{ + Bone = (int)DIContext.SpecificSocketBones[InSocketIndex]; +} + +void DISkelMesh_RandomSpecificSocketBone(in FDISkelMeshContext DIContext, out int SocketBone) +{ + float RandT0 = NiagaraInternalNoise(1, 2, 3); + SocketBone = min(uint(RandT0 * float(DIContext.NumSpecificSocketBones)), DIContext.NumSpecificSocketBones - 1); // avoid % by using mul/min to Tri = MeshVertexCount +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#define DISKELMESH_DECLARE_CONSTANTS(NAME) \ + Buffer MeshIndexBuffer_ ## NAME; \ + Buffer MeshVertexBuffer_ ## NAME; \ + Buffer MeshSkinWeightBuffer_ ## NAME; \ + Buffer MeshCurrBonesBuffer_ ## NAME; \ + Buffer MeshPrevBonesBuffer_ ## NAME; \ + Buffer MeshCurrSamplingBonesBuffer_ ## NAME; \ + Buffer MeshPrevSamplingBonesBuffer_ ## NAME; \ + Buffer MeshTangentBuffer_ ## NAME; \ + Buffer MeshTexCoordBuffer_ ## NAME; \ + Buffer MeshColorBuffer_ ## NAME; \ + Buffer MeshTriangleSamplerProbaBuffer_ ## NAME; \ + Buffer MeshTriangleSamplerAliasBuffer_ ## NAME; \ + Buffer MeshTriangleMatricesOffsetBuffer_ ## NAME; \ + uint MeshTriangleCount_ ## NAME; \ + uint MeshVertexCount_ ## NAME; \ + uint MeshWeightStride_ ## NAME; \ + uint MeshNumTexCoord_ ## NAME; \ + uint MeshNumWeights_ ## NAME; \ + uint NumSpecificBones_ ## NAME; \ + Buffer SpecificBones_ ## NAME; \ + uint NumSpecificSocketBones_ ## NAME; \ + Buffer SpecificSocketBones_ ## NAME; \ + float4x4 InstanceTransform_ ## NAME; \ + float4x4 InstancePrevTransform_ ## NAME; \ + float InstanceInvDeltaTime_ ## NAME; \ + uint EnabledFeatures_ ## NAME; \ + +#define DISKELMESH_MAKE_CONTEXT(NAME) \ + FDISkelMeshContext DIContext; \ + DIContext.MeshIndexBuffer = MeshIndexBuffer_ ## NAME; \ + DIContext.MeshVertexBuffer = MeshVertexBuffer_ ## NAME; \ + DIContext.MeshSkinWeightBuffer = MeshSkinWeightBuffer_ ## NAME; \ + DIContext.MeshCurrBonesBuffer = MeshCurrBonesBuffer_ ## NAME; \ + DIContext.MeshPrevBonesBuffer = MeshPrevBonesBuffer_ ## NAME; \ + DIContext.MeshCurrSamplingBonesBuffer = MeshCurrSamplingBonesBuffer_ ## NAME; \ + DIContext.MeshPrevSamplingBonesBuffer = MeshPrevSamplingBonesBuffer_ ## NAME; \ + DIContext.MeshTangentBuffer = MeshTangentBuffer_ ## NAME; \ + DIContext.MeshTexCoordBuffer = MeshTexCoordBuffer_ ## NAME; \ + DIContext.MeshColorBuffer = MeshColorBuffer_ ## NAME; \ + DIContext.MeshTriangleSamplerProbaBuffer = MeshTriangleSamplerProbaBuffer_ ## NAME; \ + DIContext.MeshTriangleSamplerAliasBuffer = MeshTriangleSamplerAliasBuffer_ ## NAME; \ + DIContext.MeshTriangleMatricesOffsetBuffer = MeshTriangleMatricesOffsetBuffer_ ## NAME; \ + DIContext.MeshTriangleCount = MeshTriangleCount_ ## NAME; \ + DIContext.MeshVertexCount = MeshVertexCount_ ## NAME; \ + DIContext.MeshNumTexCoord = MeshNumTexCoord_ ## NAME; \ + DIContext.MeshNumWeights = MeshNumWeights_ ## NAME; \ + DIContext.NumSpecificBones = NumSpecificBones_ ## NAME; \ + DIContext.SpecificBones = SpecificBones_ ## NAME; \ + DIContext.NumSpecificSocketBones = NumSpecificSocketBones_ ## NAME; \ + DIContext.SpecificSocketBones = SpecificSocketBones_ ## NAME; \ + DIContext.MeshWeightStride = MeshWeightStride_ ## NAME; \ + DIContext.InstanceTransform = InstanceTransform_ ## NAME; \ + DIContext.InstancePrevTransform = InstancePrevTransform_ ## NAME; \ + DIContext.InstanceInvDeltaTime = InstanceInvDeltaTime_ ## NAME; \ + DIContext.UniformTriangleSamplingEnable = EnabledFeatures_ ## NAME & 0x1; \ + diff --git a/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraEmitterInstanceShader.usf b/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraEmitterInstanceShader.usf index 74fc9b76feaa..7803b1be6fc2 100644 --- a/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraEmitterInstanceShader.usf +++ b/Engine/Plugins/FX/Niagara/Shaders/Private/NiagaraEmitterInstanceShader.usf @@ -56,6 +56,18 @@ float Reciprocal(float x){ return 1.0f/x; } float4 HomogeneousWorldPosition = mul(float4(ScreenPosition * SceneDepth, SceneDepth, 1), View.ScreenToWorld); return HomogeneousWorldPosition.xyz / HomogeneousWorldPosition.w; } + + // MASSIVE HACK - Tracked in JIRA UE-69298 + // Hardcoded random function accessible from inner part of node implementation. + // It works for now at least and avoids exposing every random needed in the UI. + // Temporary solution, it will be replaced when a design is validated. + float NiagaraInternalNoise(uint u, uint v, uint s) + { + static uint RandomSeedOffset = 0; + uint Seed = (u * 1664525u + v) + s + RandomSeedOffset; + RandomSeedOffset += Seed; + return float(Rand3DPCG32(int3(u,v,Seed)).x) / 4294967296.0f; + } #endif /* ---------------------------------------------------------------------------- diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterface.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterface.h index c5177c835c45..f143791df9e1 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterface.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterface.h @@ -257,6 +257,11 @@ public: /** Determines if this type definition matches to a known data interface type.*/ static bool IsDataInterfaceType(const FNiagaraTypeDefinition& TypeDef); + /** Allows data interfaces to provide common functionality that will be shared across interfaces on that type. */ + virtual void GetCommonHLSL(FString& OutHLSL) + { + } + virtual bool GetFunctionHLSL(const FName& DefinitionFunctionName, FString InstanceFunctionName, FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) { // checkf(false, TEXT("Unefined HLSL in data interface. Interfaces need to be able to return HLSL for each function they define in GetFunctions.")); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceSkeletalMesh.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceSkeletalMesh.h index a976fde8d7eb..9ad0c5ff41d1 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceSkeletalMesh.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceSkeletalMesh.h @@ -238,7 +238,7 @@ public: virtual ~FSkeletalMeshGpuSpawnStaticBuffers(); FORCEINLINE_DEBUGGABLE void Initialise(const FSkeletalMeshLODRenderData& SkeletalMeshLODRenderData, - bool bIsGpuUniformlyDistributedSampling, const FSkeletalMeshSamplingLODBuiltData& SkeletalMeshSamplingLODBuiltData); + bool bIsGpuUniformlyDistributedSampling, const FSkeletalMeshSamplingLODBuiltData& SkeletalMeshSamplingLODBuiltData, const TArray& SpecificBones, const TArray& SpecificSocketBones); virtual void InitRHI() override; virtual void ReleaseRHI() override; @@ -249,13 +249,22 @@ public: FShaderResourceViewRHIRef GetBufferTriangleUniformSamplerAliasSRV() const { return BufferTriangleUniformSamplerAliasSRV; } FShaderResourceViewRHIRef GetBufferTriangleMatricesOffsetSRV() const { return BufferTriangleMatricesOffsetSRV; } uint32 GetTriangleCount() const { return TriangleCount; } + uint32 GetVertexCount() const { return VertexCount; } FRHIShaderResourceView* GetBufferPositionSRV() const { return MeshVertexBufferSrv; } FRHIShaderResourceView* GetBufferIndexSRV() const { return MeshIndexBufferSrv; } FRHIShaderResourceView* GetBufferTangentSRV() const { return MeshTangentBufferSRV; } FRHIShaderResourceView* GetBufferTexCoordSRV() const { return MeshTexCoordBufferSrv; } + FRHIShaderResourceView* GetBufferColorSRV() const { return MeshColorBufferSrv; } uint32 GetNumTexCoord() const { return NumTexCoord; } + uint32 GetNumWeights() const { return NumWeights; } + + uint32 GetNumSpecificBones() const { return NumSpecificBones; } + FShaderResourceViewRHIParamRef GetSpecificBonesSRV() const { return SpecificBonesSRV; } + + uint32 GetNumSpecificSocketBones() const { return NumSpecificSocketBones; } + FShaderResourceViewRHIParamRef GetSpecificSocketBonesSRV() const { return SpecificSocketBonesSRV; } protected: @@ -266,20 +275,33 @@ protected: FVertexBufferRHIRef BufferTriangleMatricesOffsetRHI = nullptr; FShaderResourceViewRHIRef BufferTriangleMatricesOffsetSRV = nullptr; + uint32 NumSpecificBones = 0; + TResourceArray SpecificBonesArray; + FVertexBufferRHIRef SpecificBonesBuffer; + FShaderResourceViewRHIRef SpecificBonesSRV; + + uint32 NumSpecificSocketBones = 0; + TResourceArray SpecificSocketBonesArray; + FVertexBufferRHIRef SpecificSocketBonesBuffer; + FShaderResourceViewRHIRef SpecificSocketBonesSRV; + /** Cached SRV to gpu buffers of the mesh we spawn from */ FRHIShaderResourceView* MeshVertexBufferSrv; FRHIShaderResourceView* MeshIndexBufferSrv; FRHIShaderResourceView* MeshTangentBufferSRV; FRHIShaderResourceView* MeshTexCoordBufferSrv; + FRHIShaderResourceView* MeshColorBufferSrv; - uint32 NumTexCoord; + uint32 NumTexCoord = 0; + uint32 NumWeights = 0; // Cached data for resource creation on RenderThread const FSkeletalMeshLODRenderData* LODRenderData = nullptr; const FSkeletalMeshSamplingLODBuiltData* SkeletalMeshSamplingLODBuiltData = nullptr; - uint32 TriangleCount; - uint32 InputWeightStride; - bool bUseGpuUniformlyDistributedSampling; + uint32 TriangleCount = 0; + uint32 VertexCount = 0; + uint32 InputWeightStride = 0; + bool bUseGpuUniformlyDistributedSampling = false; }; /** @@ -294,7 +316,7 @@ public: FSkeletalMeshGpuDynamicBufferProxy(); virtual ~FSkeletalMeshGpuDynamicBufferProxy(); - void Initialise(const FSkeletalMeshLODRenderData& SkeletalMeshLODRenderData); + void Initialise(const FReferenceSkeleton& RefSkel, const FSkeletalMeshLODRenderData& SkeletalMeshLODRenderData); virtual void InitRHI() override; virtual void ReleaseRHI() override; @@ -306,16 +328,19 @@ public: /** Encapsulates a GPU read / CPU write buffer for bone data */ struct FSkeletalBuffer { - FVertexBufferRHIRef Buffer; - FShaderResourceViewRHIRef SRV; + FVertexBufferRHIRef SectionBuffer; + FShaderResourceViewRHIRef SectionSRV; + + FVertexBufferRHIRef SamplingBuffer; + FShaderResourceViewRHIRef SamplingSRV; }; FSkeletalBuffer& GetRWBufferBone() { return RWBufferBones[CurrentBoneBufferId % 2]; } FSkeletalBuffer& GetRWBufferPrevBone() { return bPrevBoneGpuBufferValid ? RWBufferBones[(CurrentBoneBufferId + 1) % 2] : GetRWBufferBone(); } private: - - uint32 BoneCount = 0; + uint32 SamplingBoneCount = 0; + uint32 SectionBoneCount = 0; enum { BufferBoneCount = 2 }; FSkeletalBuffer RWBufferBones[BufferBoneCount]; @@ -482,6 +507,7 @@ public: static USkeletalMesh* GetSkeletalMeshHelper(UNiagaraDataInterfaceSkeletalMesh* Interface, class UNiagaraComponent* OwningComponent, TWeakObjectPtr& SceneComponent, USkeletalMeshComponent*& FoundSkelComp); + virtual void GetCommonHLSL(FString& OutHLSL) override; virtual bool GetFunctionHLSL(const FName& DefinitionFunctionName, FString InstanceFunctionName, FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) override; virtual void GetParameterDefinitionHLSL(FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) override; virtual FNiagaraDataInterfaceParametersCS* ConstructComputeParameters() const override; @@ -493,20 +519,27 @@ public: static const FString MeshSkinWeightBufferName; static const FString MeshCurrBonesBufferName; static const FString MeshPrevBonesBufferName; + static const FString MeshCurrSamplingBonesBufferName; + static const FString MeshPrevSamplingBonesBufferName; static const FString MeshTangentBufferName; static const FString MeshTexCoordBufferName; + static const FString MeshColorBufferName; static const FString MeshTriangleSamplerProbaBufferName; static const FString MeshTriangleSamplerAliasBufferName; static const FString MeshTriangleMatricesOffsetBufferName; static const FString MeshTriangleCountName; - static const FString MeshWeightStrideByteName; + static const FString MeshVertexCountName; + static const FString MeshWeightStrideName; + static const FString MeshNumTexCoordName; + static const FString MeshNumWeightsName; + static const FString NumSpecificBonesName; + static const FString SpecificBonesName; + static const FString NumSpecificSocketBonesName; + static const FString SpecificSocketBonesName; static const FString InstanceTransformName; static const FString InstancePrevTransformName; static const FString InstanceInvDeltaTimeName; static const FString EnabledFeaturesName; - static const FString InputWeightStrideName; - static const FString NumTexCoordName; - protected: virtual bool CopyToInternal(UNiagaraDataInterface* Destination) const override; @@ -633,6 +666,7 @@ public: class FSkeletalMeshInterfaceHelper { public: + // Triangle Sampling static const FName RandomTriCoordName; static const FName IsValidTriCoordName; static const FName GetSkinnedTriangleDataName; @@ -644,6 +678,29 @@ public: static const FName GetTriangleCountName; static const FName GetTriangleAtName; static const FName GetTriCoordVerticesName; + + // Bone Sampling + static const FName GetSkinnedBoneDataName; + static const FName GetSkinnedBoneDataWSName; + static const FName GetSkinnedBoneDataInterpolatedName; + static const FName GetSkinnedBoneDataWSInterpolatedName; + static const FName RandomSpecificBoneName; + static const FName IsValidBoneName; + static const FName GetSpecificBoneCountName; + static const FName GetSpecificBoneAtName; + static const FName RandomSpecificSocketBoneName; + static const FName GetSpecificSocketCountName; + static const FName GetSpecificSocketBoneAtName; + + // Vertex Sampling + static const FName IsValidVertexName; + static const FName RandomVertexName; + static const FName GetSkinnedVertexDataName; + static const FName GetSkinnedVertexDataWSName; + static const FName GetVertexColorName; + static const FName GetVertexUVName; + static const FName GetVertexCountName; + static const FName GetVertexAtName; }; struct FNiagaraDISkeletalMeshPassedDataToRT diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataSet.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataSet.h index f72892fab7af..b23db631c533 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataSet.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataSet.h @@ -99,7 +99,7 @@ public: void KillInstance(uint32 InstanceIdx); void CopyTo(FNiagaraDataBuffer& DestBuffer, int32 StartIdx, int32 NumInstances)const; void CopyTo(FNiagaraDataBuffer& DestBuffer)const; - void GPUCopyFrom(float* GPUReadBackFloat, int* GPUReadBackInt, int32 StartIdx, int32 NumInstances); + void GPUCopyFrom(float* GPUReadBackFloat, int* GPUReadBackInt, int32 StartIdx, int32 NumInstances, uint32 InSrcFloatStride, uint32 InSrcIntStride); void Dump(int32 StartIndex, int32 NumInstances, const FString& Label)const; bool AppendToRegisterTable(uint8** Registers, int32& NumRegisters, int32 StartInstance); @@ -255,7 +255,7 @@ public: void CopyTo(FNiagaraDataSet& Other, int32 StartIdx = 0, int32 NumInstances = INDEX_NONE)const; - void CopyFromGPUReadback(float* GPUReadBackFloat, int* GPUReadBackInt, int32 StartIdx = 0, int32 NumInstances = INDEX_NONE); + void CopyFromGPUReadback(float* GPUReadBackFloat, int* GPUReadBackInt, int32 StartIdx = 0, int32 NumInstances = INDEX_NONE, uint32 FloatStride = 0, uint32 IntStride = 0); void CheckForNaNs()const; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitter.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitter.h index 23c5c6126828..558329e6a968 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitter.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitter.h @@ -8,6 +8,7 @@ #include "UObject/Object.h" #include "NiagaraScript.h" #include "NiagaraCollision.h" +#include "INiagaraMergeManager.h" #include "NiagaraEmitter.generated.h" class UMaterial; @@ -172,6 +173,8 @@ class UNiagaraEmitter : public UObject { GENERATED_UCLASS_BODY() + friend struct FNiagaraEmitterHandle; + public: #if WITH_EDITOR DECLARE_MULTICAST_DELEGATE(FOnPropertiesChanged); @@ -184,10 +187,15 @@ public: #endif public: - //Begin UObject Interface #if WITH_EDITOR - virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; + /** Creates a new emitter with the supplied emitter as a parent emitter and the supplied system as it's owner. */ + NIAGARA_API static UNiagaraEmitter* CreateWithParentAndOwner(UNiagaraEmitter& InParentEmitter, UObject* InOwner, FName InName, EObjectFlags FlagMask); + /** Creates a new emitter by duplicating an existing emitter. The new emitter will reference the same parent emitter if one is available. */ + static UNiagaraEmitter* CreateAsDuplicate(const UNiagaraEmitter& InEmitterToDuplicate, FName InDuplicateName, UNiagaraSystem& InDuplicateOwnerSystem); + + //Begin UObject Interface + virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; NIAGARA_API FOnPropertiesChanged& OnPropertiesChanged(); #endif void Serialize(FArchive& Ar)override; @@ -207,15 +215,6 @@ public: UPROPERTY(EditAnywhere, Category = "Emitter", meta = (EditCondition = "bDeterminism")) int32 RandomSeed; - //UPROPERTY(EditAnywhere, Category = "Emitter") - //float StartTime; - //UPROPERTY(EditAnywhere, Category = "Emitter") - //float EndTime; - //UPROPERTY(EditAnywhere, Category = "Emitter") - //int32 NumLoops - //UPROPERTY(EditAnywhere, Category = "Emitter") - //ENiagaraCollisionMode CollisionMode; - UPROPERTY() FNiagaraEmitterScriptProperties UpdateScriptProps; @@ -314,6 +313,15 @@ public: NIAGARA_API FOnEmitterCompiled& OnEmitterVMCompiled(); NIAGARA_API static bool GetForceCompileOnLoad(); + + /** Whether or not this emitter is synchronized with its parent emitter. */ + NIAGARA_API bool IsSynchronizedWithParent() const; + + /** Merges in any changes from the parent emitter into this emitter. */ + NIAGARA_API INiagaraMergeManager::FMergeEmitterResults MergeChangesFromParent(); + + /** Whether or not this emitter uses the supplied emitter */ + bool UsesEmitter(const UNiagaraEmitter& InEmitter) const; #endif /** Is this emitter allowed to be enabled by the current system detail level. */ @@ -353,11 +361,17 @@ public: TStatId GetStatID(bool bGameThread, bool bConcurrent)const; + NIAGARA_API UNiagaraEmitter* GetParent() const; + + NIAGARA_API void RemoveParent(); + protected: virtual void BeginDestroy() override; #if WITH_EDITORONLY_DATA private: + void UpdateFromMergedCopy(const INiagaraMergeManager& MergeManager, UNiagaraEmitter* MergedEmitter); + void SyncEmitterAlias(const FString& InOldName, const FString& InNewName); void UpdateChangeId(); @@ -392,6 +406,12 @@ private: UPROPERTY() TArray SharedEventGeneratorIds; + UPROPERTY() + UNiagaraEmitter* Parent; + + UPROPERTY() + UNiagaraEmitter* ParentAtLastMerge; + #if WITH_EDITOR FOnPropertiesChanged OnPropertiesChangedDelegate; #endif diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterHandle.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterHandle.h index ade88523ed71..21a2268be382 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterHandle.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterHandle.h @@ -13,8 +13,7 @@ class UNiagaraSystem; class UNiagaraEmitter; /** - * Stores a references to a source emitter asset, and a copy of that emitter for editing within an System. Also - * stores whether or not this emitter is enabled, and it's name within the editor. + * Stores emitter information within the context of a System. */ USTRUCT() struct NIAGARA_API FNiagaraEmitterHandle @@ -25,13 +24,10 @@ public: FNiagaraEmitterHandle(); #if WITH_EDITORONLY_DATA - /** Creates a new emitter handle from an emitter and an owning System. */ - FNiagaraEmitterHandle(UNiagaraEmitter& InSourceEmitter, FName InName, UNiagaraSystem& InOuterSystem); - - /** Creates a new emitter handle by duplicating an existing handle. The new emitter handle will reference the same source emitter - but will have it's own copy of the emitter made from the one in the supplied handle and will have it's own Id. */ - FNiagaraEmitterHandle(const FNiagaraEmitterHandle& InHandleToDuplicate, FName InDuplicateName, UNiagaraSystem& InDuplicateOwnerSystem); + /** Creates a new emitter handle from an emitter. */ + FNiagaraEmitterHandle(UNiagaraEmitter& InEmitter); #endif + /** Whether or not this is a valid emitter handle. */ bool IsValid() const; @@ -55,9 +51,6 @@ public: void SetIsEnabled(bool bInIsEnabled); #if WITH_EDITORONLY_DATA - /** Gets the source emitter this emitter handle was built from. */ - const UNiagaraEmitter* GetSource() const; - bool IsIsolated() const { return bIsolated; } void SetIsolated(bool bInIsolated) { bIsolated = bInIsolated; } #endif @@ -69,26 +62,14 @@ public: FString GetUniqueInstanceName()const; #if WITH_EDITORONLY_DATA - /** Determine whether or not the Source and Instance refer to the same Emitter ChangeId.*/ - bool IsSynchronizedWithSource() const; - /** Determine whether or not the Instance script is in synch with its graph.*/ bool NeedsRecompile() const; /** Calls conditional post load on all sub-objects this handle references. */ - void ConditionalPostLoad(); + void ConditionalPostLoad(int32 NiagaraCustomVersion); - /** Merges in any changes from the source emitter into the instanced emitter. */ - INiagaraModule::FMergeEmitterResults MergeSourceChanges(); - - /** Gets an object which contains the history of modifications to the emitter instance. */ - UObject* GetEmitterModificationHistory(); - - /** Sets an object which will contain the history of modifications to the emitter instance. */ - void SetEmitterModificationHistory(); - - /** Removes the source system from the this emitter handle which will prevent inheriting any further changes. */ - void RemoveSource(); + /** Whether or not this handle uses the supplied emitter. */ + bool UsesEmitter(const UNiagaraEmitter& InEmitter) const; #endif public: @@ -116,11 +97,11 @@ private: #if WITH_EDITORONLY_DATA /** The source emitter this emitter handle was built from. */ UPROPERTY() - UNiagaraEmitter* Source; + UNiagaraEmitter* Source_DEPRECATED; /** An unmodified copy of the emitter this handle references for use when merging change from the source emitter. */ UPROPERTY() - UNiagaraEmitter* LastMergedSource; + UNiagaraEmitter* LastMergedSource_DEPRECATED; bool bIsolated; #endif diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScript.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScript.h index afcbcca153ce..92d8558d3631 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScript.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScript.h @@ -454,6 +454,7 @@ public: //~ Begin UObject interface void Serialize(FArchive& Ar)override; virtual void PostLoad() override; + virtual bool IsDestructionThreadSafe() const override { return false; } #if WITH_EDITOR virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; #endif diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScriptExecutionContext.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScriptExecutionContext.h index c16bbe1546a1..29ced511a057 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScriptExecutionContext.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScriptExecutionContext.h @@ -186,6 +186,7 @@ public: //Most current buffer that can be used for rendering. FNiagaraDataBuffer* DataToRender; + bool ResetSinceLastReadbackIssued = false; FRHIGPUBufferReadback *GPUDataReadback; uint32 AccumulatedSpawnRate; uint32 NumIndicesPerInstance; // how many vtx indices per instance the renderer is going to have for its draw call @@ -199,6 +200,8 @@ public: mutable FRHIGPUMemoryReadback *GPUDebugDataReadbackCounts; mutable uint32 GPUDebugDataFloatSize; mutable uint32 GPUDebugDataIntSize; + mutable uint32 GPUDebugDataFloatStride; + mutable uint32 GPUDebugDataIntStride; mutable TSharedPtr DebugInfo; #endif @@ -275,4 +278,5 @@ public: FNiagaraDataInterfaceInstanceData* DIInstanceData; uint8* InstanceData_ParamData_Packed; bool bRequiredDistanceFieldData = false; + bool bNeedsReset = false; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraSystem.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraSystem.h index 381e47456fd2..8297500e305e 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraSystem.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraSystem.h @@ -139,15 +139,9 @@ public: FORCEINLINE float GetWarmupTickDelta()const { return WarmupTickDelta; } #if WITH_EDITORONLY_DATA - /** Called to query whether or not this emitter is referenced as the source to any emitter handles for this System.*/ - bool ReferencesSourceEmitter(UNiagaraEmitter& Emitter); - /** Determines if this system has the supplied emitter as an editable and simulating emitter instance. */ bool ReferencesInstanceEmitter(UNiagaraEmitter& Emitter); - /** Updates all handles which use this emitter as their source. */ - void UpdateFromEmitterChanges(UNiagaraEmitter& ChangedSourceEmitter, bool bRecompileOnChange = true); - /** Updates the system's rapid iteration parameters from a specific emitter. */ void RefreshSystemParametersFromEmitter(const FNiagaraEmitterHandle& EmitterHandle); @@ -238,7 +232,6 @@ public: private: #if WITH_EDITORONLY_DATA - INiagaraModule::FMergeEmitterResults MergeChangesForEmitterHandle(FNiagaraEmitterHandle& EmitterHandle); bool QueryCompileComplete(bool bWait, bool bDoPost, bool bDoNotApply = false); #endif diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NDISkeletalMesh_BoneSampling.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NDISkeletalMesh_BoneSampling.cpp index b6c4b56d510f..c024378b8915 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NDISkeletalMesh_BoneSampling.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NDISkeletalMesh_BoneSampling.cpp @@ -20,20 +20,17 @@ DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceSkeletalMesh, GetSpecificBone DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceSkeletalMesh, GetSpecificSocketBoneAt) -static const FName RandomSpecificBoneName("RandomSpecificBone"); -static const FName IsValidBoneName("IsValidBoneName"); -static const FName GetSkinnedBoneDataName("GetSkinnedBoneData"); -static const FName GetSkinnedBoneDataWSName("GetSkinnedBoneDataWS"); - -static const FName GetSkinnedBoneDataInterpolatedName("GetSkinnedBoneDataInterpolated"); -static const FName GetSkinnedBoneDataWSInterpolatedName("GetSkinnedBoneDataWSInterpolated"); - -static const FName GetSpecificBoneCountName("GetSpecificBoneCount"); -static const FName GetSpecificBoneAtName("GetSpecificBone"); - -static const FName RandomSpecificSocketBoneName("RandomSpecificSocketBone"); -static const FName GetSpecificSocketCountName("GetSpecificSocketCount"); -static const FName GetSpecificSocketBoneAtName("GetSpecificSocketBone"); +const FName FSkeletalMeshInterfaceHelper::GetSkinnedBoneDataName("GetSkinnedBoneData"); +const FName FSkeletalMeshInterfaceHelper::GetSkinnedBoneDataWSName("GetSkinnedBoneDataWS"); +const FName FSkeletalMeshInterfaceHelper::GetSkinnedBoneDataInterpolatedName("GetSkinnedBoneDataInterpolated"); +const FName FSkeletalMeshInterfaceHelper::GetSkinnedBoneDataWSInterpolatedName("GetSkinnedBoneDataWSInterpolated"); +const FName FSkeletalMeshInterfaceHelper::RandomSpecificBoneName("RandomSpecificBone"); +const FName FSkeletalMeshInterfaceHelper::IsValidBoneName("IsValidBoneName"); +const FName FSkeletalMeshInterfaceHelper::GetSpecificBoneCountName("GetSpecificBoneCount"); +const FName FSkeletalMeshInterfaceHelper::GetSpecificBoneAtName("GetSpecificBone"); +const FName FSkeletalMeshInterfaceHelper::RandomSpecificSocketBoneName("RandomSpecificSocketBone"); +const FName FSkeletalMeshInterfaceHelper::GetSpecificSocketCountName("GetSpecificSocketCount"); +const FName FSkeletalMeshInterfaceHelper::GetSpecificSocketBoneAtName("GetSpecificSocketBone"); void UNiagaraDataInterfaceSkeletalMesh::GetSkeletonSamplingFunctions(TArray& OutFunctions) { @@ -42,7 +39,7 @@ void UNiagaraDataInterfaceSkeletalMesh::GetSkeletonSamplingFunctions(TArrayRandomSpecificBone(Context); }; OutFunc = FVMExternalFunction::CreateLambda(Lambda); } - else if (BindingInfo.Name == IsValidBoneName) + else if (BindingInfo.Name == FSkeletalMeshInterfaceHelper::IsValidBoneName) { check(BindingInfo.GetNumInputs() == 2 && BindingInfo.GetNumOutputs() == 1); NDI_FUNC_BINDER(UNiagaraDataInterfaceSkeletalMesh, IsValidBone)::Bind(this, OutFunc); } - else if (BindingInfo.Name == GetSkinnedBoneDataName) + else if (BindingInfo.Name == FSkeletalMeshInterfaceHelper::GetSkinnedBoneDataName) { ensure(BindingInfo.GetNumInputs() == 2 && BindingInfo.GetNumOutputs() == 10); TSkinningModeBinder, NDI_FUNC_BINDER(UNiagaraDataInterfaceSkeletalMesh, GetSkinnedBoneData)>>>::Bind(this, BindingInfo, InstanceData, OutFunc); } - else if (BindingInfo.Name == GetSkinnedBoneDataWSName) + else if (BindingInfo.Name == FSkeletalMeshInterfaceHelper::GetSkinnedBoneDataWSName) { ensure(BindingInfo.GetNumInputs() == 2 && BindingInfo.GetNumOutputs() == 10); TSkinningModeBinder, NDI_FUNC_BINDER(UNiagaraDataInterfaceSkeletalMesh, GetSkinnedBoneData)>>>::Bind(this, BindingInfo, InstanceData, OutFunc); } - else if (BindingInfo.Name == GetSkinnedBoneDataInterpolatedName) + else if (BindingInfo.Name == FSkeletalMeshInterfaceHelper::GetSkinnedBoneDataInterpolatedName) { ensure(BindingInfo.GetNumInputs() == 3 && BindingInfo.GetNumOutputs() == 10); TSkinningModeBinder, NDI_FUNC_BINDER(UNiagaraDataInterfaceSkeletalMesh, GetSkinnedBoneData)>>>::Bind(this, BindingInfo, InstanceData, OutFunc); } - else if (BindingInfo.Name == GetSkinnedBoneDataWSInterpolatedName) + else if (BindingInfo.Name == FSkeletalMeshInterfaceHelper::GetSkinnedBoneDataWSInterpolatedName) { ensure(BindingInfo.GetNumInputs() == 3 && BindingInfo.GetNumOutputs() == 10); TSkinningModeBinder, NDI_FUNC_BINDER(UNiagaraDataInterfaceSkeletalMesh, GetSkinnedBoneData)>>>::Bind(this, BindingInfo, InstanceData, OutFunc); } - else if (BindingInfo.Name == GetSpecificBoneCountName) + else if (BindingInfo.Name == FSkeletalMeshInterfaceHelper::GetSpecificBoneCountName) { check(BindingInfo.GetNumInputs() == 1 && BindingInfo.GetNumOutputs() == 1); auto Lambda = [this](FVectorVMContext& Context) { this->GetSpecificBoneCount(Context); }; OutFunc = FVMExternalFunction::CreateLambda(Lambda); } - else if (BindingInfo.Name == GetSpecificBoneAtName) + else if (BindingInfo.Name == FSkeletalMeshInterfaceHelper::GetSpecificBoneAtName) { check(BindingInfo.GetNumInputs() == 2 && BindingInfo.GetNumOutputs() == 1); NDI_FUNC_BINDER(UNiagaraDataInterfaceSkeletalMesh, GetSpecificBoneAt)::Bind(this, OutFunc); } //Socket Functions - else if (BindingInfo.Name == RandomSpecificSocketBoneName) + else if (BindingInfo.Name == FSkeletalMeshInterfaceHelper::RandomSpecificSocketBoneName) { check(BindingInfo.GetNumInputs() == 1 && BindingInfo.GetNumOutputs() == 1); auto Lambda = [this](FVectorVMContext& Context) { this->RandomSpecificSocketBone(Context); }; OutFunc = FVMExternalFunction::CreateLambda(Lambda); } - else if (BindingInfo.Name == GetSpecificSocketCountName) + else if (BindingInfo.Name == FSkeletalMeshInterfaceHelper::GetSpecificSocketCountName) { check(BindingInfo.GetNumInputs() == 1 && BindingInfo.GetNumOutputs() == 1); auto Lambda = [this](FVectorVMContext& Context) { this->GetSpecificSocketCount(Context); }; OutFunc = FVMExternalFunction::CreateLambda(Lambda); } - else if (BindingInfo.Name == GetSpecificSocketBoneAtName) + else if (BindingInfo.Name == FSkeletalMeshInterfaceHelper::GetSpecificSocketBoneAtName) { check(BindingInfo.GetNumInputs() == 2 && BindingInfo.GetNumOutputs() == 1); NDI_FUNC_BINDER(UNiagaraDataInterfaceSkeletalMesh, GetSpecificSocketBoneAt)::Bind(this, OutFunc); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NDISkeletalMesh_VertexSampling.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NDISkeletalMesh_VertexSampling.cpp index 78866b6a6717..5e61e75601b1 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NDISkeletalMesh_VertexSampling.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NDISkeletalMesh_VertexSampling.cpp @@ -23,20 +23,20 @@ DEFINE_NDI_FUNC_BINDER(UNiagaraDataInterfaceSkeletalMesh, IsValidVertex); DEFINE_NDI_FUNC_BINDER(UNiagaraDataInterfaceSkeletalMesh, GetFilteredVertexCount); DEFINE_NDI_FUNC_BINDER(UNiagaraDataInterfaceSkeletalMesh, GetFilteredVertexAt); -static const FName RandomVertexName("RandomVertex"); -static const FName IsValidVertexName("IsValidVertex"); -static const FName GetSkinnedVertexDataName("GetSkinnedVertexData"); -static const FName GetSkinnedVertexDataWSName("GetSkinnedVertexDataWS"); -static const FName GetVertexColorName("GetVertexColor"); -static const FName GetVertexUVName("GetVertexUV"); -static const FName GetVertexCountName("GetFilteredVertexCount"); -static const FName GetVertexAtName("GetFilteredVertex"); +const FName FSkeletalMeshInterfaceHelper::IsValidVertexName("IsValidVertex"); +const FName FSkeletalMeshInterfaceHelper::RandomVertexName("RandomVertex"); +const FName FSkeletalMeshInterfaceHelper::GetSkinnedVertexDataName("GetSkinnedVertexData"); +const FName FSkeletalMeshInterfaceHelper::GetSkinnedVertexDataWSName("GetSkinnedVertexDataWS"); +const FName FSkeletalMeshInterfaceHelper::GetVertexColorName("GetVertexColor"); +const FName FSkeletalMeshInterfaceHelper::GetVertexUVName("GetVertexUV"); +const FName FSkeletalMeshInterfaceHelper::GetVertexCountName("GetFilteredVertexCount"); +const FName FSkeletalMeshInterfaceHelper::GetVertexAtName("GetFilteredVertex"); void UNiagaraDataInterfaceSkeletalMesh::GetVertexSamplingFunctions(TArray& OutFunctions) { { FNiagaraFunctionSignature Sig; - Sig.Name = RandomVertexName; + Sig.Name = FSkeletalMeshInterfaceHelper::RandomVertexName; Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("SkeletalMesh"))); Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Vertex"))); Sig.bMemberFunction = true; @@ -46,7 +46,7 @@ void UNiagaraDataInterfaceSkeletalMesh::GetVertexSamplingFunctions(TArray::Bind(this, BindingInfo, InstanceData, OutFunc); } - else if (BindingInfo.Name == IsValidVertexName) + else if (BindingInfo.Name == FSkeletalMeshInterfaceHelper::IsValidVertexName) { check(BindingInfo.GetNumInputs() == 2 && BindingInfo.GetNumOutputs() == 1); TFilterModeBinder::Bind(this, BindingInfo, InstanceData, OutFunc); } - else if (BindingInfo.Name == GetSkinnedVertexDataName) + else if (BindingInfo.Name == FSkeletalMeshInterfaceHelper::GetSkinnedVertexDataName) { check(BindingInfo.GetNumInputs() == 2 && BindingInfo.GetNumOutputs() == 6); TSkinningModeBinder>>::Bind(this, BindingInfo, InstanceData, OutFunc); } - else if (BindingInfo.Name == GetSkinnedVertexDataWSName) + else if (BindingInfo.Name == FSkeletalMeshInterfaceHelper::GetSkinnedVertexDataWSName) { check(BindingInfo.GetNumInputs() == 2 && BindingInfo.GetNumOutputs() == 6); TSkinningModeBinder>>::Bind(this, BindingInfo, InstanceData, OutFunc); } - else if (BindingInfo.Name == GetVertexColorName) + else if (BindingInfo.Name == FSkeletalMeshInterfaceHelper::GetVertexColorName) { check(BindingInfo.GetNumInputs() == 2 && BindingInfo.GetNumOutputs() == 4); if (InstanceData->HasColorData()) @@ -169,17 +169,17 @@ void UNiagaraDataInterfaceSkeletalMesh::BindVertexSamplingFunction(const FVMExte NDI_FUNC_BINDER(UNiagaraDataInterfaceSkeletalMesh, GetVertexColorFallback)::Bind(this, OutFunc); } } - else if (BindingInfo.Name == GetVertexUVName) + else if (BindingInfo.Name == FSkeletalMeshInterfaceHelper::GetVertexUVName) { check(BindingInfo.GetNumInputs() == 3 && BindingInfo.GetNumOutputs() == 2); TVertexAccessorBinder::Bind(this, BindingInfo, InstanceData, OutFunc); } - else if (BindingInfo.Name == GetVertexCountName) + else if (BindingInfo.Name == FSkeletalMeshInterfaceHelper::GetVertexCountName) { check(BindingInfo.GetNumInputs() == 1 && BindingInfo.GetNumOutputs() == 1); TFilterModeBinder::Bind(this, BindingInfo, InstanceData, OutFunc); } - else if (BindingInfo.Name == GetVertexAtName) + else if (BindingInfo.Name == FSkeletalMeshInterfaceHelper::GetVertexAtName) { check(BindingInfo.GetNumInputs() == 2 && BindingInfo.GetNumOutputs() == 1); TFilterModeBinder::Bind(this, BindingInfo, InstanceData, OutFunc); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCollisionQuery.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCollisionQuery.cpp index 7c6a91b4abc4..11080a4905ce 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCollisionQuery.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCollisionQuery.cpp @@ -17,9 +17,9 @@ FCriticalSection UNiagaraDataInterfaceCollisionQuery::CriticalSection; UNiagaraDataInterfaceCollisionQuery::UNiagaraDataInterfaceCollisionQuery(FObjectInitializer const& ObjectInitializer) : Super(ObjectInitializer) { - TraceChannelEnum = FindObject(ANY_PACKAGE, TEXT("ECollisionChannel"), true); + TraceChannelEnum = StaticEnum(); - Proxy = MakeShared(); + Proxy = MakeShared(); } bool UNiagaraDataInterfaceCollisionQuery::InitPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* InSystemInstance) diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceSkeletalMesh.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceSkeletalMesh.cpp index 7a723d7e0f57..ac6eaf1877c9 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceSkeletalMesh.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceSkeletalMesh.cpp @@ -289,15 +289,40 @@ FSkeletalMeshGpuSpawnStaticBuffers::~FSkeletalMeshGpuSpawnStaticBuffers() //ValidSections.Empty(); } -void FSkeletalMeshGpuSpawnStaticBuffers::Initialise(const FSkeletalMeshLODRenderData& SkeletalMeshLODRenderData, - bool bIsGpuUniformlyDistributedSampling, const FSkeletalMeshSamplingLODBuiltData& MeshSamplingLODBuiltData) +void FSkeletalMeshGpuSpawnStaticBuffers::Initialise(const FSkeletalMeshLODRenderData& SkeletalMeshLODRenderData, bool bIsGpuUniformlyDistributedSampling, const FSkeletalMeshSamplingLODBuiltData& MeshSamplingLODBuiltData, const TArray& InSpecificBones, const TArray& InSpecificSocketBones) { SkeletalMeshSamplingLODBuiltData = &MeshSamplingLODBuiltData; bUseGpuUniformlyDistributedSampling = bIsGpuUniformlyDistributedSampling; LODRenderData = &SkeletalMeshLODRenderData; TriangleCount = SkeletalMeshLODRenderData.MultiSizeIndexContainer.GetIndexBuffer()->Num() / 3; + VertexCount = SkeletalMeshLODRenderData.GetNumVertices(); check(TriangleCount > 0); + check(VertexCount > 0); + + // Copy Specific Bones / Socket data into arrays that the renderer will use to create read buffers + //-TODO: Exclude setting up these arrays if we don't sample from them + NumSpecificBones = InSpecificBones.Num(); + if (NumSpecificBones > 0) + { + SpecificBonesArray.Reserve(NumSpecificBones); + for ( int32 v : InSpecificBones ) + { + check(v <= 65535); + SpecificBonesArray.Add(v); + } + } + + NumSpecificSocketBones = InSpecificSocketBones.Num(); + if (NumSpecificSocketBones > 0) + { + SpecificSocketBonesArray.Reserve(NumSpecificSocketBones); + for (int32 v : InSpecificSocketBones) + { + check(v <= 65535); + SpecificSocketBonesArray.Add(v); + } + } } void FSkeletalMeshGpuSpawnStaticBuffers::InitRHI() @@ -308,6 +333,11 @@ void FSkeletalMeshGpuSpawnStaticBuffers::InitRHI() const FMultiSizeIndexContainer& IndexBuffer = LODRenderData->MultiSizeIndexContainer; MeshIndexBufferSrv = IndexBuffer.GetIndexBuffer()->GetSRV(); + if (!MeshIndexBufferSrv.IsValid()) + { + UE_LOG(LogNiagara, Warning, TEXT("Skeletal Mesh does not have an SRV for the index buffer, if you are using triangle sampling it will not work.")); + } + MeshVertexBufferSrv = LODRenderData->StaticVertexBuffers.PositionVertexBuffer.GetSRV(); MeshTangentBufferSRV = LODRenderData->StaticVertexBuffers.StaticMeshVertexBuffer.GetTangentsSRV(); @@ -316,7 +346,10 @@ void FSkeletalMeshGpuSpawnStaticBuffers::InitRHI() MeshTexCoordBufferSrv = LODRenderData->StaticVertexBuffers.StaticMeshVertexBuffer.GetTexCoordsSRV(); NumTexCoord = LODRenderData->StaticVertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords(); - uint32 VertexCount = LODRenderData->StaticVertexBuffers.StaticMeshVertexBuffer.GetNumVertices(); + MeshColorBufferSrv = LODRenderData->StaticVertexBuffers.ColorVertexBuffer.GetColorComponentsSRV(); + + NumWeights = LODRenderData->SkinWeightVertexBuffer.HasExtraBoneInfluences() ? 8 : 4; + uint32 SectionCount = LODRenderData->RenderSections.Num(); if (bUseGpuUniformlyDistributedSampling) @@ -343,32 +376,65 @@ void FSkeletalMeshGpuSpawnStaticBuffers::InitRHI() // Prepare the vertex matrix lookup offset for each of the sections. This is needed because per vertex BlendIndicies are stored relatively to each Section used matrices. // And these offset per section need to point to the correct matrix according to each section BoneMap. // There is not section selection/culling in the interface so technically we could compute that array in the pipeline. - FRHIResourceCreateInfo CreateInfo; - void* BufferData = nullptr; - BufferTriangleMatricesOffsetRHI = RHICreateAndLockVertexBuffer(VertexCount * sizeof(uint32), BUF_Static | BUF_ShaderResource, CreateInfo, BufferData); - uint32* MatricesOffsets = (uint32*)BufferData; - uint32 AccumulatedMatrixOffset = 0; - for (uint32 s = 0; s < SectionCount; ++s) { - const FSkelMeshRenderSection& Section = LODRenderData->RenderSections[s]; - const uint32 SectionBaseVertexIndex = Section.BaseVertexIndex; - const uint32 SectionNumVertices = Section.NumVertices; - for (uint32 SectionVertex = 0; SectionVertex < SectionNumVertices; ++SectionVertex) + FRHIResourceCreateInfo CreateInfo; + void* BufferData = nullptr; + BufferTriangleMatricesOffsetRHI = RHICreateAndLockVertexBuffer(VertexCount * sizeof(uint32), BUF_Static | BUF_ShaderResource, CreateInfo, BufferData); + uint32* MatricesOffsets = (uint32*)BufferData; + uint32 AccumulatedMatrixOffset = 0; + for (uint32 s = 0; s < SectionCount; ++s) { - MatricesOffsets[SectionBaseVertexIndex + SectionVertex] = AccumulatedMatrixOffset; + const FSkelMeshRenderSection& Section = LODRenderData->RenderSections[s]; + const uint32 SectionBaseVertexIndex = Section.BaseVertexIndex; + const uint32 SectionNumVertices = Section.NumVertices; + for (uint32 SectionVertex = 0; SectionVertex < SectionNumVertices; ++SectionVertex) + { + MatricesOffsets[SectionBaseVertexIndex + SectionVertex] = AccumulatedMatrixOffset; + } + AccumulatedMatrixOffset += Section.BoneMap.Num(); } - AccumulatedMatrixOffset += Section.BoneMap.Num(); + RHIUnlockVertexBuffer(BufferTriangleMatricesOffsetRHI); + BufferTriangleMatricesOffsetSRV = RHICreateShaderResourceView(BufferTriangleMatricesOffsetRHI, sizeof(uint32), PF_R32_UINT); + } + + // Create arrays for specific bones / sockets + if ( NumSpecificBones > 0 ) + { + FRHIResourceCreateInfo CreateInfo; + CreateInfo.ResourceArray = &SpecificBonesArray; + + SpecificBonesBuffer = RHICreateVertexBuffer(NumSpecificBones * sizeof(uint16), BUF_Static | BUF_ShaderResource, CreateInfo); + SpecificBonesSRV = RHICreateShaderResourceView(SpecificBonesBuffer, sizeof(uint16), PF_R16_UINT); + } + + if (NumSpecificSocketBones > 0) + { + FRHIResourceCreateInfo CreateInfo; + CreateInfo.ResourceArray = &SpecificSocketBonesArray; + + SpecificSocketBonesBuffer = RHICreateVertexBuffer(NumSpecificSocketBones * sizeof(uint16), BUF_Static | BUF_ShaderResource, CreateInfo); + SpecificSocketBonesSRV = RHICreateShaderResourceView(SpecificSocketBonesBuffer, sizeof(uint16), PF_R16_UINT); } - RHIUnlockVertexBuffer(BufferTriangleMatricesOffsetRHI); - BufferTriangleMatricesOffsetSRV = RHICreateShaderResourceView(BufferTriangleMatricesOffsetRHI, sizeof(uint32), PF_R32_UINT); } void FSkeletalMeshGpuSpawnStaticBuffers::ReleaseRHI() { + SpecificBonesBuffer.SafeRelease(); + SpecificBonesSRV.SafeRelease(); + + SpecificSocketBonesBuffer.SafeRelease(); + SpecificSocketBonesSRV.SafeRelease(); + BufferTriangleUniformSamplerProbaRHI.SafeRelease(); BufferTriangleUniformSamplerProbaSRV.SafeRelease(); BufferTriangleUniformSamplerAliasRHI.SafeRelease(); BufferTriangleUniformSamplerAliasSRV.SafeRelease(); + + MeshVertexBufferSrv.SafeRelease(); + MeshIndexBufferSrv.SafeRelease(); + MeshTangentBufferSRV.SafeRelease(); + MeshTexCoordBufferSrv.SafeRelease(); + MeshColorBufferSrv.SafeRelease(); } ////////////////////////////////////////////////////////////////////////// @@ -384,13 +450,15 @@ FSkeletalMeshGpuDynamicBufferProxy::~FSkeletalMeshGpuDynamicBufferProxy() // } -void FSkeletalMeshGpuDynamicBufferProxy::Initialise(const FSkeletalMeshLODRenderData& SkeletalMeshLODRenderData) +void FSkeletalMeshGpuDynamicBufferProxy::Initialise(const FReferenceSkeleton& RefSkel, const FSkeletalMeshLODRenderData& SkeletalMeshLODRenderData) { - BoneCount = 0; - for (const FSkelMeshRenderSection& section : SkeletalMeshLODRenderData.RenderSections) + SectionBoneCount = 0; + for (const FSkelMeshRenderSection& Section : SkeletalMeshLODRenderData.RenderSections) { - BoneCount += section.BoneMap.Num(); + SectionBoneCount += Section.BoneMap.Num(); } + + SamplingBoneCount = RefSkel.GetNum(); } void FSkeletalMeshGpuDynamicBufferProxy::InitRHI() @@ -399,8 +467,11 @@ void FSkeletalMeshGpuDynamicBufferProxy::InitRHI() { FRHIResourceCreateInfo CreateInfo; CreateInfo.DebugName = TEXT("SkeletalMeshGpuDynamicBuffer"); - Buffer.Buffer = RHICreateVertexBuffer(sizeof(FVector4) * 3 * BoneCount, BUF_ShaderResource | BUF_Dynamic, CreateInfo); - Buffer.SRV = RHICreateShaderResourceView(Buffer.Buffer, sizeof(FVector4), PF_A32B32G32R32F); + Buffer.SectionBuffer = RHICreateVertexBuffer(sizeof(FVector4) * 3 * SectionBoneCount, BUF_ShaderResource | BUF_Dynamic, CreateInfo); + Buffer.SectionSRV = RHICreateShaderResourceView(Buffer.SectionBuffer, sizeof(FVector4), PF_A32B32G32R32F); + + Buffer.SamplingBuffer = RHICreateVertexBuffer(sizeof(FVector4) * 2 * SamplingBoneCount, BUF_ShaderResource | BUF_Dynamic, CreateInfo); + Buffer.SamplingSRV = RHICreateShaderResourceView(Buffer.SamplingBuffer, sizeof(FVector4), PF_A32B32G32R32F); } } @@ -408,8 +479,11 @@ void FSkeletalMeshGpuDynamicBufferProxy::ReleaseRHI() { for (FSkeletalBuffer& Buffer : RWBufferBones) { - Buffer.Buffer.SafeRelease(); - Buffer.SRV.SafeRelease(); + Buffer.SectionBuffer.SafeRelease(); + Buffer.SectionSRV.SafeRelease(); + + Buffer.SamplingBuffer.SafeRelease(); + Buffer.SamplingSRV.SafeRelease(); } } @@ -432,44 +506,70 @@ void FSkeletalMeshGpuDynamicBufferProxy::NewFrame(const FNDISkeletalMesh_Instanc // Count number of matrices we want before appending all of them according to the per section mapping from BoneMap uint32 Float4Count = 0; - for ( const FSkelMeshRenderSection& section : Sections ) + for ( const FSkelMeshRenderSection& Section : Sections ) { - Float4Count += section.BoneMap.Num() * 3; + Float4Count += Section.BoneMap.Num() * 3; } - check(Float4Count == 3 * BoneCount); + check(Float4Count == 3 * SectionBoneCount); AllSectionsRefToLocalMatrices.AddUninitialized(Float4Count); Float4Count = 0; - for ( const FSkelMeshRenderSection& section : Sections ) + for ( const FSkelMeshRenderSection& Section : Sections ) { - const uint32 MatrixCount = section.BoneMap.Num(); + const uint32 MatrixCount = Section.BoneMap.Num(); for (uint32 m = 0; m < MatrixCount; ++m) { - RefToLocalMatrices[section.BoneMap[m]].To3x4MatrixTranspose(&AllSectionsRefToLocalMatrices[Float4Count].X); + RefToLocalMatrices[Section.BoneMap[m]].To3x4MatrixTranspose(&AllSectionsRefToLocalMatrices[Float4Count].X); Float4Count += 3; } } + // Generate information for bone sampling + TArray BoneSamplingData; + { + const TArray& ComponentTransforms = SkelComp->GetComponentSpaceTransforms(); + check(ComponentTransforms.Num() == SamplingBoneCount); + + BoneSamplingData.Reserve(ComponentTransforms.Num() * 2); + + for (const FTransform& boneMat : ComponentTransforms) + { + const FQuat Rotation = boneMat.GetRotation(); + BoneSamplingData.Add(boneMat.GetLocation()); + BoneSamplingData.Add(FVector4(Rotation.X, Rotation.Y, Rotation.Z, Rotation.W)); + } + } + FSkeletalMeshGpuDynamicBufferProxy* ThisProxy = this; ENQUEUE_RENDER_COMMAND(UpdateSpawnInfoForSkinnedMesh)( - [ThisProxy, AllSectionsRefToLocalMatrices = MoveTemp(AllSectionsRefToLocalMatrices)](FRHICommandListImmediate& RHICmdList) mutable - { - ThisProxy->CurrentBoneBufferId = (ThisProxy->CurrentBoneBufferId + 1) % BufferBoneCount; - ThisProxy->bPrevBoneGpuBufferValid = ThisProxy->bBoneGpuBufferValid; - ThisProxy->bBoneGpuBufferValid = true; + [ThisProxy, AllSectionsRefToLocalMatrices = MoveTemp(AllSectionsRefToLocalMatrices), BoneSamplingData = MoveTemp(BoneSamplingData)](FRHICommandListImmediate& RHICmdList) mutable + { + ThisProxy->CurrentBoneBufferId = (ThisProxy->CurrentBoneBufferId + 1) % BufferBoneCount; + ThisProxy->bPrevBoneGpuBufferValid = ThisProxy->bBoneGpuBufferValid; + ThisProxy->bBoneGpuBufferValid = true; - const uint32 NumBytes = AllSectionsRefToLocalMatrices.Num() * sizeof(FVector4); + // Copy bone remap data matrices + { + const uint32 NumBytes = AllSectionsRefToLocalMatrices.Num() * sizeof(FVector4); + void* DstData = RHILockVertexBuffer(ThisProxy->GetRWBufferBone().SectionBuffer, 0, NumBytes, RLM_WriteOnly); + FMemory::Memcpy(DstData, AllSectionsRefToLocalMatrices.GetData(), NumBytes); + RHIUnlockVertexBuffer(ThisProxy->GetRWBufferBone().SectionBuffer); + } - void* DstData = RHILockVertexBuffer(ThisProxy->GetRWBufferBone().Buffer, 0, NumBytes, RLM_WriteOnly); - FMemory::Memcpy(DstData, AllSectionsRefToLocalMatrices.GetData(), NumBytes); - RHIUnlockVertexBuffer(ThisProxy->GetRWBufferBone().Buffer); - }); + // Copy bone sampling data + { + const uint32 NumBytes = BoneSamplingData.Num() * sizeof(FVector4); + FVector4* DstData = reinterpret_cast(RHILockVertexBuffer(ThisProxy->GetRWBufferBone().SamplingBuffer, 0, NumBytes, RLM_WriteOnly)); + FMemory::Memcpy(DstData, BoneSamplingData.GetData(), NumBytes); + RHIUnlockVertexBuffer(ThisProxy->GetRWBufferBone().SamplingBuffer); + } + } + ); } } ////////////////////////////////////////////////////////////////////////// //FNiagaraDataInterfaceParametersCS_SkeletalMesh - struct FNDISkeletalMeshParametersName { FString MeshIndexBufferName; @@ -477,20 +577,29 @@ struct FNDISkeletalMeshParametersName FString MeshSkinWeightBufferName; FString MeshCurrBonesBufferName; FString MeshPrevBonesBufferName; + FString MeshCurrSamplingBonesBufferName; + FString MeshPrevSamplingBonesBufferName; FString MeshTangentBufferName; FString MeshTexCoordBufferName; + FString MeshColorBufferName; FString MeshTriangleSamplerProbaBufferName; FString MeshTriangleSamplerAliasBufferName; FString MeshTriangleMatricesOffsetBufferName; FString MeshTriangleCountName; - FString MeshWeightStrideByteName; + FString MeshVertexCountName; + FString MeshWeightStrideName; + FString MeshNumTexCoordName; + FString MeshNumWeightsName; + FString NumSpecificBonesName; + FString SpecificBonesName; + FString NumSpecificSocketBonesName; + FString SpecificSocketBonesName; FString InstanceTransformName; FString InstancePrevTransformName; FString InstanceInvDeltaTimeName; FString EnabledFeaturesName; - FString InputWeightStrideName; - FString NumTexCoordName; }; + static void GetNiagaraDataInterfaceParametersName(FNDISkeletalMeshParametersName& Names, const FString& Suffix) { Names.MeshIndexBufferName = UNiagaraDataInterfaceSkeletalMesh::MeshIndexBufferName + Suffix; @@ -498,19 +607,27 @@ static void GetNiagaraDataInterfaceParametersName(FNDISkeletalMeshParametersName Names.MeshSkinWeightBufferName = UNiagaraDataInterfaceSkeletalMesh::MeshSkinWeightBufferName + Suffix; Names.MeshCurrBonesBufferName = UNiagaraDataInterfaceSkeletalMesh::MeshCurrBonesBufferName + Suffix; Names.MeshPrevBonesBufferName = UNiagaraDataInterfaceSkeletalMesh::MeshPrevBonesBufferName + Suffix; + Names.MeshCurrSamplingBonesBufferName = UNiagaraDataInterfaceSkeletalMesh::MeshCurrSamplingBonesBufferName + Suffix; + Names.MeshPrevSamplingBonesBufferName = UNiagaraDataInterfaceSkeletalMesh::MeshPrevSamplingBonesBufferName + Suffix; Names.MeshTangentBufferName = UNiagaraDataInterfaceSkeletalMesh::MeshTangentBufferName + Suffix; Names.MeshTexCoordBufferName = UNiagaraDataInterfaceSkeletalMesh::MeshTexCoordBufferName + Suffix; + Names.MeshColorBufferName = UNiagaraDataInterfaceSkeletalMesh::MeshColorBufferName + Suffix; Names.MeshTriangleSamplerProbaBufferName = UNiagaraDataInterfaceSkeletalMesh::MeshTriangleSamplerProbaBufferName + Suffix; Names.MeshTriangleSamplerAliasBufferName = UNiagaraDataInterfaceSkeletalMesh::MeshTriangleSamplerAliasBufferName + Suffix; Names.MeshTriangleMatricesOffsetBufferName = UNiagaraDataInterfaceSkeletalMesh::MeshTriangleMatricesOffsetBufferName + Suffix; Names.MeshTriangleCountName = UNiagaraDataInterfaceSkeletalMesh::MeshTriangleCountName + Suffix; - Names.MeshWeightStrideByteName = UNiagaraDataInterfaceSkeletalMesh::MeshWeightStrideByteName + Suffix; + Names.MeshVertexCountName = UNiagaraDataInterfaceSkeletalMesh::MeshVertexCountName + Suffix; + Names.MeshWeightStrideName = UNiagaraDataInterfaceSkeletalMesh::MeshWeightStrideName + Suffix; + Names.MeshNumTexCoordName = UNiagaraDataInterfaceSkeletalMesh::MeshNumTexCoordName + Suffix; + Names.MeshNumWeightsName = UNiagaraDataInterfaceSkeletalMesh::MeshNumWeightsName + Suffix; + Names.NumSpecificBonesName = UNiagaraDataInterfaceSkeletalMesh::NumSpecificBonesName + Suffix; + Names.SpecificBonesName = UNiagaraDataInterfaceSkeletalMesh::SpecificBonesName + Suffix; + Names.NumSpecificSocketBonesName = UNiagaraDataInterfaceSkeletalMesh::NumSpecificSocketBonesName + Suffix; + Names.SpecificSocketBonesName = UNiagaraDataInterfaceSkeletalMesh::SpecificSocketBonesName + Suffix; Names.InstanceTransformName = UNiagaraDataInterfaceSkeletalMesh::InstanceTransformName + Suffix; Names.InstancePrevTransformName = UNiagaraDataInterfaceSkeletalMesh::InstancePrevTransformName + Suffix; Names.InstanceInvDeltaTimeName = UNiagaraDataInterfaceSkeletalMesh::InstanceInvDeltaTimeName + Suffix; Names.EnabledFeaturesName = UNiagaraDataInterfaceSkeletalMesh::EnabledFeaturesName + Suffix; - Names.InputWeightStrideName = UNiagaraDataInterfaceSkeletalMesh::InputWeightStrideName + Suffix; - Names.NumTexCoordName = UNiagaraDataInterfaceSkeletalMesh::NumTexCoordName + Suffix; } struct FNiagaraDataInterfaceParametersCS_SkeletalMesh : public FNiagaraDataInterfaceParametersCS @@ -525,44 +642,27 @@ struct FNiagaraDataInterfaceParametersCS_SkeletalMesh : public FNiagaraDataInter MeshSkinWeightBuffer.Bind(ParameterMap, *ParamNames.MeshSkinWeightBufferName); MeshCurrBonesBuffer.Bind(ParameterMap, *ParamNames.MeshCurrBonesBufferName); MeshPrevBonesBuffer.Bind(ParameterMap, *ParamNames.MeshPrevBonesBufferName); + MeshCurrSamplingBonesBuffer.Bind(ParameterMap, *ParamNames.MeshCurrSamplingBonesBufferName); + MeshPrevSamplingBonesBuffer.Bind(ParameterMap, *ParamNames.MeshPrevSamplingBonesBufferName); MeshTangentBuffer.Bind(ParameterMap, *ParamNames.MeshTangentBufferName); MeshTexCoordBuffer.Bind(ParameterMap, *ParamNames.MeshTexCoordBufferName); + MeshColorBuffer.Bind(ParameterMap, *ParamNames.MeshColorBufferName); MeshTriangleSamplerProbaBuffer.Bind(ParameterMap, *ParamNames.MeshTriangleSamplerProbaBufferName); MeshTriangleSamplerAliasBuffer.Bind(ParameterMap, *ParamNames.MeshTriangleSamplerAliasBufferName); MeshTriangleMatricesOffsetBuffer.Bind(ParameterMap, *ParamNames.MeshTriangleMatricesOffsetBufferName); MeshTriangleCount.Bind(ParameterMap, *ParamNames.MeshTriangleCountName); - MeshWeightStrideByte.Bind(ParameterMap, *ParamNames.MeshWeightStrideByteName); + MeshVertexCount.Bind(ParameterMap, *ParamNames.MeshVertexCountName); + MeshWeightStride.Bind(ParameterMap, *ParamNames.MeshWeightStrideName); + MeshNumTexCoord.Bind(ParameterMap, *ParamNames.MeshNumTexCoordName); + MeshNumWeights.Bind(ParameterMap, *ParamNames.MeshNumWeightsName); + NumSpecificBones.Bind(ParameterMap, *ParamNames.NumSpecificBonesName); + SpecificBones.Bind(ParameterMap, *ParamNames.SpecificBonesName); + NumSpecificSocketBones.Bind(ParameterMap, *ParamNames.NumSpecificSocketBonesName); + SpecificSocketBones.Bind(ParameterMap, *ParamNames.SpecificSocketBonesName); InstanceTransform.Bind(ParameterMap, *ParamNames.InstanceTransformName); InstancePrevTransform.Bind(ParameterMap, *ParamNames.InstancePrevTransformName); InstanceInvDeltaTime.Bind(ParameterMap, *ParamNames.InstanceInvDeltaTimeName); EnabledFeatures.Bind(ParameterMap, *ParamNames.EnabledFeaturesName); - InputWeightStride.Bind(ParameterMap, *ParamNames.InputWeightStrideName); - NumTexCoord.Bind(ParameterMap, *ParamNames.NumTexCoordName); - - if (!MeshIndexBuffer.IsBound()) - { - UE_LOG(LogNiagara, Warning, TEXT("Binding failed for FNiagaraDataInterfaceParametersCS_StaticMesh Texture %s. Was it optimized out?"), *ParamNames.MeshIndexBufferName) - } - if (!MeshVertexBuffer.IsBound()) - { - UE_LOG(LogNiagara, Warning, TEXT("Binding failed for FNiagaraDataInterfaceParametersCS_StaticMesh Sampler %s. Was it optimized out?"), *ParamNames.MeshVertexBufferName) - } - if (!MeshSkinWeightBuffer.IsBound()) - { - UE_LOG(LogNiagara, Warning, TEXT("Binding failed for FNiagaraDataInterfaceParametersCS_StaticMesh Sampler %s. Was it optimized out?"), *ParamNames.MeshSkinWeightBufferName) - } - if (!MeshCurrBonesBuffer.IsBound()) - { - UE_LOG(LogNiagara, Warning, TEXT("Binding failed for FNiagaraDataInterfaceParametersCS_StaticMesh Sampler %s. Was it optimized out?"), *ParamNames.MeshCurrBonesBufferName) - } - if (!MeshTangentBuffer.IsBound()) - { - UE_LOG(LogNiagara, Warning, TEXT("Binding failed for FNiagaraDataInterfaceParametersCS_StaticMesh Sampler %s. Was it optimized out?"), *ParamNames.MeshTangentBufferName) - } - if (!MeshTriangleMatricesOffsetBuffer.IsBound()) - { - UE_LOG(LogNiagara, Warning, TEXT("Binding failed for FNiagaraDataInterfaceParametersCS_StaticMesh Sampler %s. Was it optimized out?"), *ParamNames.MeshTriangleMatricesOffsetBufferName) - } } virtual void Serialize(FArchive& Ar)override @@ -572,19 +672,27 @@ struct FNiagaraDataInterfaceParametersCS_SkeletalMesh : public FNiagaraDataInter Ar << MeshSkinWeightBuffer; Ar << MeshCurrBonesBuffer; Ar << MeshPrevBonesBuffer; + Ar << MeshCurrSamplingBonesBuffer; + Ar << MeshPrevSamplingBonesBuffer; Ar << MeshTangentBuffer; Ar << MeshTexCoordBuffer; + Ar << MeshColorBuffer; Ar << MeshTriangleSamplerProbaBuffer; Ar << MeshTriangleSamplerAliasBuffer; Ar << MeshTriangleMatricesOffsetBuffer; Ar << MeshTriangleCount; - Ar << MeshWeightStrideByte; + Ar << MeshVertexCount; + Ar << MeshWeightStride; + Ar << MeshNumTexCoord; + Ar << MeshNumWeights; + Ar << NumSpecificBones; + Ar << SpecificBones; + Ar << NumSpecificSocketBones; + Ar << SpecificSocketBones; Ar << InstanceTransform; Ar << InstancePrevTransform; Ar << InstanceInvDeltaTime; Ar << EnabledFeatures; - Ar << InputWeightStride; - Ar << NumTexCoord; } virtual void Set(FRHICommandList& RHICmdList, const FNiagaraDataInterfaceSetArgs& Context) const override @@ -602,7 +710,7 @@ struct FNiagaraDataInterfaceParametersCS_SkeletalMesh : public FNiagaraDataInter SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshIndexBuffer, StaticBuffers->GetBufferIndexSRV()); SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshTangentBuffer, StaticBuffers->GetBufferTangentSRV()); - SetShaderValue(RHICmdList, ComputeShaderRHI, NumTexCoord, StaticBuffers->GetNumTexCoord()); + SetShaderValue(RHICmdList, ComputeShaderRHI, MeshNumTexCoord, StaticBuffers->GetNumTexCoord()); if (StaticBuffers->GetNumTexCoord() > 0) { SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshTexCoordBuffer, StaticBuffers->GetBufferTexCoordSRV()); @@ -611,7 +719,9 @@ struct FNiagaraDataInterfaceParametersCS_SkeletalMesh : public FNiagaraDataInter { SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshTexCoordBuffer, FNiagaraRenderer::GetDummyFloatBuffer().SRV); } + SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshColorBuffer, StaticBuffers->GetBufferColorSRV()); SetShaderValue(RHICmdList, ComputeShaderRHI, MeshTriangleCount, StaticBuffers->GetTriangleCount()); + SetShaderValue(RHICmdList, ComputeShaderRHI, MeshVertexCount, StaticBuffers->GetVertexCount()); if (InstanceData->bIsGpuUniformlyDistributedSampling) { SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshTriangleSamplerProbaBuffer, StaticBuffers->GetBufferTriangleUniformSamplerProbaSRV().GetReference()); @@ -625,11 +735,7 @@ struct FNiagaraDataInterfaceParametersCS_SkeletalMesh : public FNiagaraDataInter SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshSkinWeightBuffer, InstanceData->MeshSkinWeightBufferSrv); - SetShaderValue(RHICmdList, ComputeShaderRHI, MeshWeightStrideByte, InstanceData->MeshWeightStrideByte); - SetShaderValue(RHICmdList, ComputeShaderRHI, InstanceTransform, InstanceData->Transform); - SetShaderValue(RHICmdList, ComputeShaderRHI, InstancePrevTransform, InstanceData->PrevTransform); - SetShaderValue(RHICmdList, ComputeShaderRHI, InstanceInvDeltaTime, 1.0f / InstanceData->DeltaSeconds); - SetShaderValue(RHICmdList, ComputeShaderRHI, InputWeightStride, InstanceData->MeshWeightStrideByte/4); + SetShaderValue(RHICmdList, ComputeShaderRHI, MeshWeightStride, InstanceData->MeshWeightStrideByte/4); uint32 EnabledFeaturesBits = InstanceData->bIsGpuUniformlyDistributedSampling ? 1 : 0; @@ -637,19 +743,36 @@ struct FNiagaraDataInterfaceParametersCS_SkeletalMesh : public FNiagaraDataInter check(DynamicBuffers); if(DynamicBuffers->DoesBoneDataExist()) { - EnabledFeaturesBits |= 2; // Enable the skinning feature - SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshCurrBonesBuffer, DynamicBuffers->GetRWBufferBone().SRV); - SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshPrevBonesBuffer, DynamicBuffers->GetRWBufferPrevBone().SRV); + SetShaderValue(RHICmdList, ComputeShaderRHI, MeshNumWeights, StaticBuffers->GetNumWeights()); + SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshCurrBonesBuffer, DynamicBuffers->GetRWBufferBone().SectionSRV); + SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshPrevBonesBuffer, DynamicBuffers->GetRWBufferPrevBone().SectionSRV); + SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshCurrSamplingBonesBuffer, DynamicBuffers->GetRWBufferBone().SamplingSRV); + SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshPrevSamplingBonesBuffer, DynamicBuffers->GetRWBufferPrevBone().SamplingSRV); SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshTriangleMatricesOffsetBuffer, StaticBuffers->GetBufferTriangleMatricesOffsetSRV()); } // Bind dummy data for validation purposes only. Code will not execute due to "EnabledFeatures" bits but validation can not determine that. else { + SetShaderValue(RHICmdList, ComputeShaderRHI, MeshNumWeights, 0); SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshCurrBonesBuffer, FNiagaraRenderer::GetDummyFloat4Buffer().SRV); SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshPrevBonesBuffer, FNiagaraRenderer::GetDummyFloat4Buffer().SRV); + SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshCurrSamplingBonesBuffer, FNiagaraRenderer::GetDummyFloat4Buffer().SRV); + SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshPrevSamplingBonesBuffer, FNiagaraRenderer::GetDummyFloat4Buffer().SRV); SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshTriangleMatricesOffsetBuffer, FNiagaraRenderer::GetDummyUIntBuffer().SRV); } + FShaderResourceViewRHIParamRef SpecificBonesSRV = StaticBuffers->GetNumSpecificBones() > 0 ? StaticBuffers->GetSpecificBonesSRV() : FNiagaraRenderer::GetDummyUIntBuffer().SRV.GetReference(); + SetShaderValue(RHICmdList, ComputeShaderRHI, NumSpecificBones, StaticBuffers->GetNumSpecificBones()); + SetSRVParameter(RHICmdList, ComputeShaderRHI, SpecificBones, SpecificBonesSRV); + + FShaderResourceViewRHIParamRef SpecificSocketBonesSRV = StaticBuffers->GetNumSpecificSocketBones() > 0 ? StaticBuffers->GetSpecificSocketBonesSRV() : FNiagaraRenderer::GetDummyUIntBuffer().SRV.GetReference(); + SetShaderValue(RHICmdList, ComputeShaderRHI, NumSpecificSocketBones, StaticBuffers->GetNumSpecificSocketBones()); + SetSRVParameter(RHICmdList, ComputeShaderRHI, SpecificSocketBones, SpecificSocketBonesSRV); + + SetShaderValue(RHICmdList, ComputeShaderRHI, InstanceTransform, InstanceData->Transform); + SetShaderValue(RHICmdList, ComputeShaderRHI, InstancePrevTransform, InstanceData->PrevTransform); + SetShaderValue(RHICmdList, ComputeShaderRHI, InstanceInvDeltaTime, 1.0f / InstanceData->DeltaSeconds); + SetShaderValue(RHICmdList, ComputeShaderRHI, EnabledFeatures, EnabledFeaturesBits); } else @@ -661,24 +784,34 @@ struct FNiagaraDataInterfaceParametersCS_SkeletalMesh : public FNiagaraDataInter SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshIndexBuffer, FNiagaraRenderer::GetDummyUIntBuffer().SRV); SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshTangentBuffer, FNiagaraRenderer::GetDummyFloatBuffer().SRV); - SetShaderValue(RHICmdList, ComputeShaderRHI, NumTexCoord, 0); + SetShaderValue(RHICmdList, ComputeShaderRHI, MeshNumTexCoord, 0); SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshTexCoordBuffer, FNiagaraRenderer::GetDummyFloatBuffer().SRV); + SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshColorBuffer, FNiagaraRenderer::GetDummyFloatBuffer().SRV); SetShaderValue(RHICmdList, ComputeShaderRHI, MeshTriangleCount, 0); + SetShaderValue(RHICmdList, ComputeShaderRHI, MeshVertexCount, 0); SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshTriangleSamplerProbaBuffer, FNiagaraRenderer::GetDummyUIntBuffer().SRV); SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshTriangleSamplerAliasBuffer, FNiagaraRenderer::GetDummyUIntBuffer().SRV); SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshSkinWeightBuffer, FNiagaraRenderer::GetDummyUIntBuffer().SRV); - SetShaderValue(RHICmdList, ComputeShaderRHI, MeshWeightStrideByte, 0); - SetShaderValue(RHICmdList, ComputeShaderRHI, InstanceTransform, FMatrix::Identity); - SetShaderValue(RHICmdList, ComputeShaderRHI, InstancePrevTransform, FMatrix::Identity); - SetShaderValue(RHICmdList, ComputeShaderRHI, InstanceInvDeltaTime, 0.0f); - SetShaderValue(RHICmdList, ComputeShaderRHI, InputWeightStride, 0); + SetShaderValue(RHICmdList, ComputeShaderRHI, MeshWeightStride, 0); + SetShaderValue(RHICmdList, ComputeShaderRHI, MeshNumWeights, 0); SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshCurrBonesBuffer, FNiagaraRenderer::GetDummyFloat4Buffer().SRV); SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshPrevBonesBuffer, FNiagaraRenderer::GetDummyFloat4Buffer().SRV); + SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshCurrSamplingBonesBuffer, FNiagaraRenderer::GetDummyFloat4Buffer().SRV); + SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshPrevSamplingBonesBuffer, FNiagaraRenderer::GetDummyFloat4Buffer().SRV); SetSRVParameter(RHICmdList, ComputeShaderRHI, MeshTriangleMatricesOffsetBuffer, FNiagaraRenderer::GetDummyUIntBuffer().SRV); + SetShaderValue(RHICmdList, ComputeShaderRHI, NumSpecificBones, 0); + SetSRVParameter(RHICmdList, ComputeShaderRHI, SpecificBones, FNiagaraRenderer::GetDummyUIntBuffer().SRV); + SetShaderValue(RHICmdList, ComputeShaderRHI, NumSpecificSocketBones, 0); + SetSRVParameter(RHICmdList, ComputeShaderRHI, SpecificBones, FNiagaraRenderer::GetDummyUIntBuffer().SRV); + + SetShaderValue(RHICmdList, ComputeShaderRHI, InstanceTransform, FMatrix::Identity); + SetShaderValue(RHICmdList, ComputeShaderRHI, InstancePrevTransform, FMatrix::Identity); + SetShaderValue(RHICmdList, ComputeShaderRHI, InstanceInvDeltaTime, 0.0f); + SetShaderValue(RHICmdList, ComputeShaderRHI, EnabledFeatures, 0); } } @@ -691,19 +824,27 @@ private: FShaderResourceParameter MeshSkinWeightBuffer; FShaderResourceParameter MeshCurrBonesBuffer; FShaderResourceParameter MeshPrevBonesBuffer; + FShaderResourceParameter MeshCurrSamplingBonesBuffer; + FShaderResourceParameter MeshPrevSamplingBonesBuffer; FShaderResourceParameter MeshTangentBuffer; FShaderResourceParameter MeshTexCoordBuffer; + FShaderResourceParameter MeshColorBuffer; FShaderResourceParameter MeshTriangleSamplerProbaBuffer; FShaderResourceParameter MeshTriangleSamplerAliasBuffer; FShaderResourceParameter MeshTriangleMatricesOffsetBuffer; FShaderParameter MeshTriangleCount; - FShaderParameter MeshWeightStrideByte; + FShaderParameter MeshVertexCount; + FShaderParameter MeshWeightStride; + FShaderParameter MeshNumTexCoord; + FShaderParameter MeshNumWeights; + FShaderParameter NumSpecificBones; + FShaderResourceParameter SpecificBones; + FShaderParameter NumSpecificSocketBones; + FShaderResourceParameter SpecificSocketBones; FShaderParameter InstanceTransform; FShaderParameter InstancePrevTransform; FShaderParameter InstanceInvDeltaTime; FShaderParameter EnabledFeatures; - FShaderParameter InputWeightStride; - FShaderParameter NumTexCoord; }; ////////////////////////////////////////////////////////////////////////// @@ -834,29 +975,27 @@ USkeletalMesh* UNiagaraDataInterfaceSkeletalMesh::GetSkeletalMeshHelper(UNiagara bool FNDISkeletalMesh_InstanceData::Init(UNiagaraDataInterfaceSkeletalMesh* Interface, FNiagaraSystemInstance* SystemInstance) { check(SystemInstance); - ChangeId = Interface->ChangeId; - USkeletalMesh* PrevMesh = Mesh; + + // Initialize members Component = nullptr; Mesh = nullptr; + MeshSafe = nullptr; Transform = FMatrix::Identity; TransformInverseTransposed = FMatrix::Identity; PrevTransform = FMatrix::Identity; PrevTransformInverseTransposed = FMatrix::Identity; DeltaSeconds = 0.0f; + ChangeId = Interface->ChangeId; + bIsGpuUniformlyDistributedSampling = false; + MeshWeightStrideByte = 0; + MeshGpuSpawnStaticBuffers = nullptr; + MeshGpuSpawnDynamicBuffers = nullptr; + // Get skel mesh and confirm have valid data USkeletalMeshComponent* NewSkelComp = nullptr; Mesh = UNiagaraDataInterfaceSkeletalMesh::GetSkeletalMeshHelper(Interface, SystemInstance->GetComponent(), Component, NewSkelComp); - MeshSafe = Mesh; - if (Component.IsValid() && Mesh) - { - PrevTransform = Transform; - PrevTransformInverseTransposed = TransformInverseTransposed; - Transform = Component->GetComponentToWorld().ToMatrixWithScale(); - TransformInverseTransposed = Transform.InverseFast().GetTransposed(); - } - if (!Mesh) { /*USceneComponent* Comp = Component.Get(); @@ -867,23 +1006,27 @@ bool FNDISkeletalMesh_InstanceData::Init(UNiagaraDataInterfaceSkeletalMesh* Inte return false; } + if (!Component.IsValid()) + { + UE_LOG(LogNiagara, Log, TEXT("SkeletalMesh data interface has no valid component. Failed InitPerInstanceData - %s"), *Interface->GetFullName()); + return false; + } + + Transform = Component->GetComponentToWorld().ToMatrixWithScale(); + TransformInverseTransposed = Transform.InverseFast().GetTransposed(); + PrevTransform = Transform; + PrevTransformInverseTransposed = TransformInverseTransposed; + #if WITH_EDITOR MeshSafe->GetOnMeshChanged().AddUObject(SystemInstance->GetComponent(), &UNiagaraComponent::ReinitializeSystem); #endif - // if (!Mesh->bAllowCPUAccess) // { // UE_LOG(LogNiagara, Log, TEXT("SkeletalMesh data interface using a mesh that does not allow CPU access. Failed InitPerInstanceData - Mesh: %s"), *Mesh->GetFullName()); // return false; // } - if (!Component.IsValid()) - { - UE_LOG(LogNiagara, Log, TEXT("SkeletalMesh data interface has no valid component. Failed InitPerInstanceData - %s"), *Interface->GetFullName()); - return false; - } - //Setup where to spawn from SamplingRegionIndices.Empty(); bool bAllRegionsAreAreaWeighting = true; @@ -1012,40 +1155,42 @@ bool FNDISkeletalMesh_InstanceData::Init(UNiagaraDataInterfaceSkeletalMesh* Inte FSkeletalMeshLODRenderData& LODData = GetLODRenderDataAndSkinWeights(SkinWeightBuffer); //Check for the validity of the Mesh's cpu data. - - bool LODDataNumVerticesCorrect = LODData.GetNumVertices() > 0; - bool LODDataPositonNumVerticesCorrect = LODData.StaticVertexBuffers.PositionVertexBuffer.GetNumVertices() > 0; - bool bSkinWeightBuffer = SkinWeightBuffer != nullptr; - bool SkinWeightBufferNumVerticesCorrect = bSkinWeightBuffer && (SkinWeightBuffer->GetNumVertices() > 0); - bool bIndexBufferValid = LODData.MultiSizeIndexContainer.IsIndexBufferValid(); - bool bIndexBufferFound = bIndexBufferValid && (LODData.MultiSizeIndexContainer.GetIndexBuffer() != nullptr); - bool bIndexBufferNumCorrect = bIndexBufferFound && (LODData.MultiSizeIndexContainer.GetIndexBuffer()->Num() > 0); - - bool bMeshCPUDataValid = LODDataNumVerticesCorrect && - LODDataPositonNumVerticesCorrect && - bSkinWeightBuffer && - SkinWeightBufferNumVerticesCorrect && - bIndexBufferValid && - bIndexBufferFound && - bIndexBufferNumCorrect; - - if (!bMeshCPUDataValid) + if ( Interface->bUseTriangleSampling || Interface->bUseVertexSampling ) { - UE_LOG(LogNiagara, Warning, TEXT("Skeletal Mesh Data Interface is trying to sample from a mesh with missing CPU vertex or index data.\nInterface: %s\nMesh: %s\nLOD: %d\n" - "LODDataNumVerticesCorrect: %d LODDataPositonNumVerticesCorrect : %d bSkinWeightBuffer : %d SkinWeightBufferNumVerticesCorrect : %d bIndexBufferValid : %d bIndexBufferFound : %d bIndexBufferNumCorrect : %d"), - *Interface->GetFullName(), - *Mesh->GetFullName(), - LODIndex, - LODDataNumVerticesCorrect ? 1 : 0, - LODDataPositonNumVerticesCorrect ? 1 : 0, - bSkinWeightBuffer ? 1 : 0, - SkinWeightBufferNumVerticesCorrect ? 1 : 0, - bIndexBufferValid ? 1 : 0, - bIndexBufferFound ? 1 : 0, - bIndexBufferNumCorrect ? 1 : 0 + bool LODDataNumVerticesCorrect = LODData.GetNumVertices() > 0; + bool LODDataPositonNumVerticesCorrect = LODData.StaticVertexBuffers.PositionVertexBuffer.GetNumVertices() > 0; + bool bSkinWeightBuffer = SkinWeightBuffer != nullptr; + bool SkinWeightBufferNumVerticesCorrect = bSkinWeightBuffer && (SkinWeightBuffer->GetNumVertices() > 0); + bool bIndexBufferValid = LODData.MultiSizeIndexContainer.IsIndexBufferValid(); + bool bIndexBufferFound = bIndexBufferValid && (LODData.MultiSizeIndexContainer.GetIndexBuffer() != nullptr); + bool bIndexBufferNumCorrect = bIndexBufferFound && (LODData.MultiSizeIndexContainer.GetIndexBuffer()->Num() > 0); + + bool bMeshCPUDataValid = LODDataNumVerticesCorrect && + LODDataPositonNumVerticesCorrect && + bSkinWeightBuffer && + SkinWeightBufferNumVerticesCorrect && + bIndexBufferValid && + bIndexBufferFound && + bIndexBufferNumCorrect; + + if (!bMeshCPUDataValid) + { + UE_LOG(LogNiagara, Warning, TEXT("Skeletal Mesh Data Interface is trying to sample from a mesh with missing CPU vertex or index data.\nInterface: %s\nMesh: %s\nLOD: %d\n" + "LODDataNumVerticesCorrect: %d LODDataPositonNumVerticesCorrect : %d bSkinWeightBuffer : %d SkinWeightBufferNumVerticesCorrect : %d bIndexBufferValid : %d bIndexBufferFound : %d bIndexBufferNumCorrect : %d"), + *Interface->GetFullName(), + *Mesh->GetFullName(), + LODIndex, + LODDataNumVerticesCorrect ? 1 : 0, + LODDataPositonNumVerticesCorrect ? 1 : 0, + bSkinWeightBuffer ? 1 : 0, + SkinWeightBufferNumVerticesCorrect ? 1 : 0, + bIndexBufferValid ? 1 : 0, + bIndexBufferFound ? 1 : 0, + bIndexBufferNumCorrect ? 1 : 0 ); - return false; + return false; + } } FReferenceSkeleton& RefSkel = Mesh->RefSkeleton; @@ -1106,6 +1251,8 @@ bool FNDISkeletalMesh_InstanceData::Init(UNiagaraDataInterfaceSkeletalMesh* Inte } } + //-TODO: We should find out if this DI is connected to a GPU emitter or not rather than a blanket accross the system + if ( SystemInstance->HasGPUEmitters() ) { MeshWeightStrideByte = SkinWeightBuffer->GetStride(); MeshSkinWeightBufferSrv = SkinWeightBuffer->GetSRV(); @@ -1124,11 +1271,11 @@ bool FNDISkeletalMesh_InstanceData::Init(UNiagaraDataInterfaceSkeletalMesh* Inte } MeshGpuSpawnStaticBuffers = new FSkeletalMeshGpuSpawnStaticBuffers(); - MeshGpuSpawnStaticBuffers->Initialise(LODData, bIsGpuUniformlyDistributedSampling, SamplingInfo.GetBuiltData().WholeMeshBuiltData[LODIndex]); + MeshGpuSpawnStaticBuffers->Initialise(LODData, bIsGpuUniformlyDistributedSampling, SamplingInfo.GetBuiltData().WholeMeshBuiltData[LODIndex], SpecificBones, SpecificSocketBones); BeginInitResource(MeshGpuSpawnStaticBuffers); MeshGpuSpawnDynamicBuffers = new FSkeletalMeshGpuDynamicBufferProxy(); - MeshGpuSpawnDynamicBuffers->Initialise(LODData); + MeshGpuSpawnDynamicBuffers->Initialise(RefSkel, LODData); BeginInitResource(MeshGpuSpawnDynamicBuffers); } @@ -1611,313 +1758,186 @@ void UNiagaraDataInterfaceSkeletalMesh::ValidateFunction(const FNiagaraFunctionS #endif -const FString UNiagaraDataInterfaceSkeletalMesh::MeshIndexBufferName(TEXT("IndexBuffer_")); -const FString UNiagaraDataInterfaceSkeletalMesh::MeshVertexBufferName(TEXT("VertexBuffer_")); -const FString UNiagaraDataInterfaceSkeletalMesh::MeshSkinWeightBufferName(TEXT("VertexSkinWeightBuffer_")); +const FString UNiagaraDataInterfaceSkeletalMesh::MeshIndexBufferName(TEXT("MeshIndexBuffer_")); +const FString UNiagaraDataInterfaceSkeletalMesh::MeshVertexBufferName(TEXT("MeshVertexBuffer_")); +const FString UNiagaraDataInterfaceSkeletalMesh::MeshSkinWeightBufferName(TEXT("MeshSkinWeightBuffer_")); const FString UNiagaraDataInterfaceSkeletalMesh::MeshCurrBonesBufferName(TEXT("MeshCurrBonesBuffer_")); const FString UNiagaraDataInterfaceSkeletalMesh::MeshPrevBonesBufferName(TEXT("MeshPrevBonesBuffer_")); +const FString UNiagaraDataInterfaceSkeletalMesh::MeshCurrSamplingBonesBufferName(TEXT("MeshCurrSamplingBonesBuffer_")); +const FString UNiagaraDataInterfaceSkeletalMesh::MeshPrevSamplingBonesBufferName(TEXT("MeshPrevSamplingBonesBuffer_")); const FString UNiagaraDataInterfaceSkeletalMesh::MeshTangentBufferName(TEXT("MeshTangentBuffer_")); -const FString UNiagaraDataInterfaceSkeletalMesh::MeshTexCoordBufferName(TEXT("TexCoordBuffer_")); +const FString UNiagaraDataInterfaceSkeletalMesh::MeshTexCoordBufferName(TEXT("MeshTexCoordBuffer_")); +const FString UNiagaraDataInterfaceSkeletalMesh::MeshColorBufferName(TEXT("MeshColorBuffer_")); const FString UNiagaraDataInterfaceSkeletalMesh::MeshTriangleSamplerProbaBufferName(TEXT("MeshTriangleSamplerProbaBuffer_")); const FString UNiagaraDataInterfaceSkeletalMesh::MeshTriangleSamplerAliasBufferName(TEXT("MeshTriangleSamplerAliasBuffer_")); const FString UNiagaraDataInterfaceSkeletalMesh::MeshTriangleMatricesOffsetBufferName(TEXT("MeshTriangleMatricesOffsetBuffer_")); +const FString UNiagaraDataInterfaceSkeletalMesh::MeshTriangleCountName(TEXT("MeshTriangleCount_")); +const FString UNiagaraDataInterfaceSkeletalMesh::MeshVertexCountName(TEXT("MeshVertexCount_")); +const FString UNiagaraDataInterfaceSkeletalMesh::MeshWeightStrideName(TEXT("MeshWeightStride_")); +const FString UNiagaraDataInterfaceSkeletalMesh::MeshNumTexCoordName(TEXT("MeshNumTexCoord_")); +const FString UNiagaraDataInterfaceSkeletalMesh::MeshNumWeightsName(TEXT("MeshNumWeights_")); +const FString UNiagaraDataInterfaceSkeletalMesh::NumSpecificBonesName(TEXT("NumSpecificBones_")); +const FString UNiagaraDataInterfaceSkeletalMesh::SpecificBonesName(TEXT("SpecificBones_")); +const FString UNiagaraDataInterfaceSkeletalMesh::NumSpecificSocketBonesName(TEXT("NumSpecificSocketBones_")); +const FString UNiagaraDataInterfaceSkeletalMesh::SpecificSocketBonesName(TEXT("SpecificSocketBones_")); const FString UNiagaraDataInterfaceSkeletalMesh::InstanceTransformName(TEXT("InstanceTransform_")); const FString UNiagaraDataInterfaceSkeletalMesh::InstancePrevTransformName(TEXT("InstancePrevTransform_")); const FString UNiagaraDataInterfaceSkeletalMesh::InstanceInvDeltaTimeName(TEXT("InstanceInvDeltaTime_")); -const FString UNiagaraDataInterfaceSkeletalMesh::MeshWeightStrideByteName(TEXT("MeshWeightStrideByte_")); -const FString UNiagaraDataInterfaceSkeletalMesh::MeshTriangleCountName(TEXT("MeshTriangleCount_")); const FString UNiagaraDataInterfaceSkeletalMesh::EnabledFeaturesName(TEXT("EnabledFeatures_")); -const FString UNiagaraDataInterfaceSkeletalMesh::InputWeightStrideName(TEXT("InputWeightStride_")); -const FString UNiagaraDataInterfaceSkeletalMesh::NumTexCoordName(TEXT("NumTexCoordName_")); -bool UNiagaraDataInterfaceSkeletalMesh::GetFunctionHLSL(const FName& DefinitionFunctionName, FString InstanceFunctionName, FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) +void UNiagaraDataInterfaceSkeletalMesh::GetCommonHLSL(FString& OutHLSL) +{ + OutHLSL += TEXT("#include \"/Plugin/FX/Niagara/Private/NiagaraDataInterfaceSkeletalMesh.ush\"\n"); +} + +bool UNiagaraDataInterfaceSkeletalMesh::GetFunctionHLSL(const FName& DefinitionFunctionName, FString InstanceFunctionName, FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) { FNDISkeletalMeshParametersName ParamNames; GetNiagaraDataInterfaceParametersName(ParamNames, ParamInfo.DataInterfaceHLSLSymbol); - FString MeshTriCoordinateStructName = "MeshTriCoordinate"; - - static const TCHAR* FormatCommonFunctions = TEXT(R"( - void {InstanceFunctionName}_GetIndicesAndWeights(uint VertexIndex, out int4 BlendIndices, out float4 BlendWeights) - { - uint PackedBlendIndices = {MeshSkinWeightBufferName}[VertexIndex * ({InputWeightStrideName}) ]; - uint PackedBlendWeights = {MeshSkinWeightBufferName}[VertexIndex * ({InputWeightStrideName}) + 1]; - BlendIndices.x = PackedBlendIndices & 0xff; - BlendIndices.y = PackedBlendIndices >> 8 & 0xff; - BlendIndices.z = PackedBlendIndices >> 16 & 0xff; - BlendIndices.w = PackedBlendIndices >> 24 & 0xff; - BlendWeights.x = float(PackedBlendWeights & 0xff) / 255.0f; - BlendWeights.y = float(PackedBlendWeights >> 8 & 0xff) / 255.0f; - BlendWeights.z = float(PackedBlendWeights >> 16 & 0xff) / 255.0f; - BlendWeights.w = float(PackedBlendWeights >> 24 & 0xff) / 255.0f; - } - - float3x4 {InstanceFunctionName}_GetPrevBoneMatrix(uint Bone) - { - return float3x4({MeshPrevBonesBufferName}[Bone * 3], {MeshPrevBonesBufferName}[Bone * 3 + 1], {MeshPrevBonesBufferName}[Bone * 3 + 2]); - } - - float3x4 {InstanceFunctionName}_GetPrevSkinningMatrix(uint VertexIndex, int4 BlendIndices, float4 BlendWeights) - { - // Get the matrix offset for each vertex because BlendIndices are stored relatively to each section start vertex. - uint MatrixOffset = {MeshTriangleMatricesOffsetBufferName}[VertexIndex]; - - float3x4 Result; - Result = {InstanceFunctionName}_GetPrevBoneMatrix(MatrixOffset + BlendIndices.x) * BlendWeights.x; - Result += {InstanceFunctionName}_GetPrevBoneMatrix(MatrixOffset + BlendIndices.y) * BlendWeights.y; - Result += {InstanceFunctionName}_GetPrevBoneMatrix(MatrixOffset + BlendIndices.z) * BlendWeights.z; - Result += {InstanceFunctionName}_GetPrevBoneMatrix(MatrixOffset + BlendIndices.w) * BlendWeights.w; - return Result; - } - - float3x4 {InstanceFunctionName}_GetCurrBoneMatrix(uint Bone) - { - return float3x4({MeshCurrBonesBufferName}[Bone * 3], {MeshCurrBonesBufferName}[Bone * 3 + 1], {MeshCurrBonesBufferName}[Bone * 3 + 2]); - } - - float3x4 {InstanceFunctionName}_GetCurrSkinningMatrix(uint VertexIndex, int4 BlendIndices, float4 BlendWeights) - { - // Get the matrix offset for each vertex because BlendIndices are stored relatively to each section start vertex. - uint MatrixOffset = {MeshTriangleMatricesOffsetBufferName}[VertexIndex]; - - float3x4 Result; - Result = {InstanceFunctionName}_GetCurrBoneMatrix(MatrixOffset + BlendIndices.x) * BlendWeights.x; - Result += {InstanceFunctionName}_GetCurrBoneMatrix(MatrixOffset + BlendIndices.y) * BlendWeights.y; - Result += {InstanceFunctionName}_GetCurrBoneMatrix(MatrixOffset + BlendIndices.z) * BlendWeights.z; - Result += {InstanceFunctionName}_GetCurrBoneMatrix(MatrixOffset + BlendIndices.w) * BlendWeights.w; - return Result; - } - )"); - - static const TCHAR* FormatSampleSkinnedTriangleDataWSHeader = TEXT(R"( - void {InstanceFunctionName} (in {MeshTriCoordinateStructName} In_Coord, out float3 Out_Position, out float3 Out_Velocity, out float3 Out_Normal, out float3 Out_Binormal, out float3 Out_Tangent) - { - const float In_Interp = 1.0f; - )"); - - static const TCHAR* FormatSampleSkinnedTriangleDataWSInterpolatedHeader = TEXT(R"( - void {InstanceFunctionName} (in {MeshTriCoordinateStructName} In_Coord, float In_Interp, out float3 Out_Position, out float3 Out_Velocity, out float3 Out_Normal, out float3 Out_Binormal, out float3 Out_Tangent) - { - )"); - - static const TCHAR* FormatSampleSkinnedTriangleDataWSPart0 = TEXT(R"( - const bool SkinningEnabled = {EnabledFeaturesName} & 0x0002; - - uint TriangleIndex = In_Coord.Tri * 3; - uint VertexIndex0 = {MeshIndexBufferName}[TriangleIndex ]; - uint VertexIndex1 = {MeshIndexBufferName}[TriangleIndex+1]; - uint VertexIndex2 = {MeshIndexBufferName}[TriangleIndex+2]; - - // I could not find a R32G32B32f format to create an SRV on that buffer. So float load it is for now... - float3 Vertex0 = float3({MeshVertexBufferName}[VertexIndex0*3], {MeshVertexBufferName}[VertexIndex0*3+1], {MeshVertexBufferName}[VertexIndex0*3+2]); - float3 Vertex1 = float3({MeshVertexBufferName}[VertexIndex1*3], {MeshVertexBufferName}[VertexIndex1*3+1], {MeshVertexBufferName}[VertexIndex1*3+2]); - float3 Vertex2 = float3({MeshVertexBufferName}[VertexIndex2*3], {MeshVertexBufferName}[VertexIndex2*3+1], {MeshVertexBufferName}[VertexIndex2*3+2]); - float3 PrevVertex0 = Vertex0; - float3 PrevVertex1 = Vertex1; - float3 PrevVertex2 = Vertex2; - - float3 TangentX0 = TangentBias({MeshTangentBufferName}[VertexIndex0*2 ].xyz); - float4 TangentZ0 = TangentBias({MeshTangentBufferName}[VertexIndex0*2+1].xyzw); - float3 TangentX1 = TangentBias({MeshTangentBufferName}[VertexIndex1*2 ].xyz); - float4 TangentZ1 = TangentBias({MeshTangentBufferName}[VertexIndex1*2+1].xyzw); - float3 TangentX2 = TangentBias({MeshTangentBufferName}[VertexIndex2*2 ].xyz); - float4 TangentZ2 = TangentBias({MeshTangentBufferName}[VertexIndex2*2+1].xyzw); - - if(SkinningEnabled) - { - int4 BlendIndices0; - int4 BlendIndices1; - int4 BlendIndices2; - float4 BlendWeights0; - float4 BlendWeights1; - float4 BlendWeights2; - - {InstanceFunctionName}_GetIndicesAndWeights(VertexIndex0, BlendIndices0, BlendWeights0); - {InstanceFunctionName}_GetIndicesAndWeights(VertexIndex1, BlendIndices1, BlendWeights1); - {InstanceFunctionName}_GetIndicesAndWeights(VertexIndex2, BlendIndices2, BlendWeights2); - - float3x4 PrevBoneMatrix0 = {InstanceFunctionName}_GetPrevSkinningMatrix(VertexIndex0, BlendIndices0, BlendWeights0); - float3x4 PrevBoneMatrix1 = {InstanceFunctionName}_GetPrevSkinningMatrix(VertexIndex1, BlendIndices1, BlendWeights1); - float3x4 PrevBoneMatrix2 = {InstanceFunctionName}_GetPrevSkinningMatrix(VertexIndex2, BlendIndices2, BlendWeights2); - PrevVertex0 = mul( PrevBoneMatrix0, float4(Vertex0, 1.0f) ).xyz; - PrevVertex1 = mul( PrevBoneMatrix1, float4(Vertex1, 1.0f) ).xyz; - PrevVertex2 = mul( PrevBoneMatrix2, float4(Vertex2, 1.0f) ).xyz; - - float3x4 CurrBoneMatrix0 = {InstanceFunctionName}_GetCurrSkinningMatrix(VertexIndex0, BlendIndices0, BlendWeights0); - float3x4 CurrBoneMatrix1 = {InstanceFunctionName}_GetCurrSkinningMatrix(VertexIndex1, BlendIndices1, BlendWeights1); - float3x4 CurrBoneMatrix2 = {InstanceFunctionName}_GetCurrSkinningMatrix(VertexIndex2, BlendIndices2, BlendWeights2); - Vertex0 = mul( CurrBoneMatrix0, float4(Vertex0, 1.0f) ).xyz; - Vertex1 = mul( CurrBoneMatrix1, float4(Vertex1, 1.0f) ).xyz; - Vertex2 = mul( CurrBoneMatrix2, float4(Vertex2, 1.0f) ).xyz; - - // Not using InverseTranspose of matrices so assuming uniform scaling only (same as SkinCache) - TangentX0.xyz = mul( CurrBoneMatrix0, float4(TangentX0.xyz, 0.0f) ).xyz; - TangentZ0.xyz = mul( CurrBoneMatrix0, float4(TangentZ0.xyz, 0.0f) ).xyz; - TangentX1.xyz = mul( CurrBoneMatrix1, float4(TangentX1.xyz, 0.0f) ).xyz; - TangentZ1.xyz = mul( CurrBoneMatrix1, float4(TangentZ1.xyz, 0.0f) ).xyz; - TangentX2.xyz = mul( CurrBoneMatrix2, float4(TangentX2.xyz, 0.0f) ).xyz; - TangentZ2.xyz = mul( CurrBoneMatrix2, float4(TangentZ2.xyz, 0.0f) ).xyz; - } - - // Evaluate current and previous world position - float3 WSPos = Vertex0 * In_Coord.BaryCoord.x + Vertex1 * In_Coord.BaryCoord.y + Vertex2 * In_Coord.BaryCoord.z; - WSPos = mul(float4(WSPos,1.0), {InstanceTransformName}).xyz; - float3 PrevWSPos = PrevVertex0 * In_Coord.BaryCoord.x + PrevVertex1 * In_Coord.BaryCoord.y + PrevVertex2 * In_Coord.BaryCoord.z; - PrevWSPos = mul(float4(PrevWSPos,1.0), {InstancePrevTransformName}).xyz; - - // Not using InverseTranspose of matrices so assuming uniform scaling only (same as SkinCache) - float3 Binormal0 = cross(TangentZ0.xyz, TangentX0.xyz) * TangentZ0.w; - float3 Binormal1 = cross(TangentZ1.xyz, TangentX1.xyz) * TangentZ1.w; - float3 Binormal2 = cross(TangentZ2.xyz, TangentX2.xyz) * TangentZ2.w; - float3 Normal = TangentZ0.xyz * In_Coord.BaryCoord.x + TangentZ1.xyz * In_Coord.BaryCoord.y + TangentZ2.xyz * In_Coord.BaryCoord.z; // Normal is TangentZ - float3 Tangent = TangentX0.xyz * In_Coord.BaryCoord.x + TangentX1.xyz * In_Coord.BaryCoord.y + TangentX2.xyz * In_Coord.BaryCoord.z; - float3 Binormal = Binormal0.xyz * In_Coord.BaryCoord.x + Binormal1.xyz * In_Coord.BaryCoord.y + Binormal2.xyz * In_Coord.BaryCoord.z; - float3 NormalWorld = mul(float4(Normal , 0.0), {InstanceTransformName}).xyz; - float3 TangentWorld = mul(float4(Tangent , 0.0), {InstanceTransformName}).xyz; - float3 BinormalWorld = mul(float4(Binormal, 0.0), {InstanceTransformName}).xyz; - - Out_Position = lerp(PrevWSPos, WSPos, float3(In_Interp,In_Interp,In_Interp)); - Out_Velocity = (WSPos - PrevWSPos) * {InstanceInvDeltaTimeName}; // Velocity is unafected by spawn interpolation. That would require another set of previous data. - Out_Normal = normalize(NormalWorld); - Out_Tangent = normalize(TangentWorld); - Out_Binormal = normalize(BinormalWorld); - } - )"); - - TMap ArgsSample = { {TEXT("InstanceFunctionName"), InstanceFunctionName}, - {TEXT("MeshTriCoordinateStructName"), MeshTriCoordinateStructName}, - {TEXT("MeshIndexBufferName"), ParamNames.MeshIndexBufferName}, - {TEXT("MeshVertexBufferName"), ParamNames.MeshVertexBufferName}, - {TEXT("MeshSkinWeightBufferName"), ParamNames.MeshSkinWeightBufferName}, - {TEXT("MeshCurrBonesBufferName"), ParamNames.MeshCurrBonesBufferName}, - {TEXT("MeshPrevBonesBufferName"), ParamNames.MeshPrevBonesBufferName}, - {TEXT("MeshTangentBufferName"), ParamNames.MeshTangentBufferName}, - {TEXT("MeshTexCoordBufferName"), ParamNames.MeshTexCoordBufferName}, - {TEXT("MeshTriangleSamplerProbaBufferName"), ParamNames.MeshTriangleSamplerProbaBufferName}, - {TEXT("MeshTriangleSamplerAliasBufferName"), ParamNames.MeshTriangleSamplerAliasBufferName}, - {TEXT("MeshTriangleMatricesOffsetBufferName"), ParamNames.MeshTriangleMatricesOffsetBufferName}, - {TEXT("MeshTriangleCountName"), ParamNames.MeshTriangleCountName}, - {TEXT("InstanceTransformName"), ParamNames.InstanceTransformName}, - {TEXT("InstancePrevTransformName"), ParamNames.InstancePrevTransformName}, - {TEXT("InstanceInvDeltaTimeName"), ParamNames.InstanceInvDeltaTimeName}, - {TEXT("EnabledFeaturesName"), ParamNames.EnabledFeaturesName}, - {TEXT("InputWeightStrideName"), ParamNames.InputWeightStrideName}, - {TEXT("NumTexCoordName"), ParamNames.NumTexCoordName}, + {TEXT("MeshTriCoordinateStructName"), TEXT("MeshTriCoordinate")}, + {TEXT("MeshTriangleCount"), ParamNames.MeshTriangleCountName}, + {TEXT("MeshVertexCount"), ParamNames.MeshVertexCountName}, + {TEXT("GetDISkelMeshContextName"), TEXT("DISKELMESH_MAKE_CONTEXT(") + ParamInfo.DataInterfaceHLSLSymbol + TEXT(")")}, }; + // Triangle Sampling if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::RandomTriCoordName) { - static const TCHAR *FormatSample = TEXT(R"( - void {InstanceFunctionName} (out {MeshTriCoordinateStructName} Out_Coord) - { - const bool UniformTriangleSamplingEnable = {EnabledFeaturesName} & 0x0001; - - float RandT0 = NiagaraInternalNoise(1, 2, 3); - [branch] - if (!UniformTriangleSamplingEnable) - { - // Uniform triangle id selection - Out_Coord.Tri = min(uint(RandT0*float({MeshTriangleCountName})), {MeshTriangleCountName}-1); // avoid % by using mul/min to Tri = MeshTriangleCountName - } - else - { - // Uniform area weighted position selection (using alias method from Alias method from FWeightedRandomSampler) - uint TriangleIndex = min(uint(RandT0*float({MeshTriangleCountName})), {MeshTriangleCountName}-1); - float TriangleProbability = {MeshTriangleSamplerProbaBufferName}[TriangleIndex]; - - // Alias check - float RandT1 = NiagaraInternalNoise(1, 2, 3); - if( RandT1 > TriangleProbability ) - { - TriangleIndex = {MeshTriangleSamplerAliasBufferName}[TriangleIndex]; - } - Out_Coord.Tri = TriangleIndex; - } - - float r0 = NiagaraInternalNoise(1, 2, 3); - float r1 = NiagaraInternalNoise(1, 2, 3); - float sqrt0 = sqrt(r0); - float sqrt1 = sqrt(r1); - Out_Coord.BaryCoord = float3(1.0f - sqrt0, sqrt0 * (1.0 - r1), r1 * sqrt0); - // Out_Coord.BaryCoord = float3(1.0f, 0.0f, 0.0f); - } - )"); + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (out {MeshTriCoordinateStructName} OutCoord) { {GetDISkelMeshContextName} DISKelMesh_RandomTriCoord(DIContext, OutCoord.Tri, OutCoord.BaryCoord); }"); OutHLSL += FString::Format(FormatSample, ArgsSample); } else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetSkinnedTriangleDataWSName) { - OutHLSL += FString::Format(FormatCommonFunctions, ArgsSample); - OutHLSL += FString::Format(FormatSampleSkinnedTriangleDataWSHeader, ArgsSample); - OutHLSL += FString::Format(FormatSampleSkinnedTriangleDataWSPart0, ArgsSample); + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in {MeshTriCoordinateStructName} InCoord, out float3 OutPosition, out float3 OutVelocity, out float3 OutNormal, out float3 OutBinormal, out float3 OutTangent) { {GetDISkelMeshContextName} DISKelMesh_GetSkinnedTriangleDataWS(DIContext, InCoord.Tri, InCoord.BaryCoord, OutPosition, OutVelocity, OutNormal, OutBinormal, OutTangent); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); } else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetSkinnedTriangleDataWSInterpName) { - OutHLSL += FString::Format(FormatCommonFunctions, ArgsSample); - OutHLSL += FString::Format(FormatSampleSkinnedTriangleDataWSInterpolatedHeader, ArgsSample); - OutHLSL += FString::Format(FormatSampleSkinnedTriangleDataWSPart0, ArgsSample); + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in {MeshTriCoordinateStructName} InCoord, in float InInterp, out float3 OutPosition, out float3 OutVelocity, out float3 OutNormal, out float3 OutBinormal, out float3 OutTangent) { {GetDISkelMeshContextName} DISKelMesh_GetSkinnedTriangleDataInterpolatedWS(DIContext, InCoord.Tri, InCoord.BaryCoord, InInterp, OutPosition, OutVelocity, OutNormal, OutBinormal, OutTangent); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); } - else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetTriColorName) + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetSkinnedTriangleDataName) { - static const TCHAR *FormatSample = TEXT(R"( - void {InstanceFunctionName} (in {MeshTriCoordinateStructName} In_Coord, out float4 Out_Color) - { - Out_Color = 0.0f; - } - )"); + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in {MeshTriCoordinateStructName} InCoord, out float3 OutPosition, out float3 OutVelocity, out float3 OutNormal, out float3 OutBinormal, out float3 OutTangent) { {GetDISkelMeshContextName} DISKelMesh_GetSkinnedTriangleData(DIContext, InCoord.Tri, InCoord.BaryCoord, OutPosition, OutVelocity, OutNormal, OutBinormal, OutTangent); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetSkinnedTriangleDataInterpName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in {MeshTriCoordinateStructName} InCoord, in float InInterp, out float3 OutPosition, out float3 OutVelocity, out float3 OutNormal, out float3 OutBinormal, out float3 OutTangent) { {GetDISkelMeshContextName} DISKelMesh_GetSkinnedTriangleDataInterpolated(DIContext, InCoord.Tri, InCoord.BaryCoord, InInterp, OutPosition, OutVelocity, OutNormal, OutBinormal, OutTangent); }"); OutHLSL += FString::Format(FormatSample, ArgsSample); } else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetTriUVName) { - static const TCHAR *FormatSample = TEXT(R"( - void {InstanceFunctionName} (in {MeshTriCoordinateStructName} In_Coord, in int In_UVSet, out float2 Out_UV) - { - if({NumTexCoordName}>0) - { - uint TriangleIndex = In_Coord.Tri * 3; - uint VertexIndex0 = {MeshIndexBufferName}[TriangleIndex ]; - uint VertexIndex1 = {MeshIndexBufferName}[TriangleIndex+1]; - uint VertexIndex2 = {MeshIndexBufferName}[TriangleIndex+2]; - - uint stride = {NumTexCoordName}; - uint SelectedUVSet = clamp(In_UVSet, 0, {NumTexCoordName}-1); - float2 UV0 = {MeshTexCoordBufferName}[VertexIndex0 * stride + SelectedUVSet]; - float2 UV1 = {MeshTexCoordBufferName}[VertexIndex1 * stride + SelectedUVSet]; - float2 UV2 = {MeshTexCoordBufferName}[VertexIndex2 * stride + SelectedUVSet]; - - Out_UV = UV0 * In_Coord.BaryCoord.x + UV1 * In_Coord.BaryCoord.y + UV2 * In_Coord.BaryCoord.z; - } - else - { - Out_UV = 0.0f; - } - } - )"); + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in {MeshTriCoordinateStructName} InCoord, in int InUVSet, out float2 OutUV) { {GetDISkelMeshContextName} DISKelMesh_GetTriUV(DIContext, InCoord.Tri, InCoord.BaryCoord, InUVSet, OutUV); }"); OutHLSL += FString::Format(FormatSample, ArgsSample); } - //else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::IsValidTriCoordName) - //{ - // // unimplemented(); - //} - //else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetSkinnedTriangleDataName) - //{ - // // unimplemented(); - //} - //else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetSkinnedTriangleDataInterpName) - //{ - // // unimplemented(); - //} - //else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetTriangleCountName) - //{ - // // unimplemented(); - //} - //else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetTriangleAtName) - //{ - // // unimplemented(); - //} - //else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetTriCoordVerticesName) - //{ - //} + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetTriColorName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in {MeshTriCoordinateStructName} InCoord, out float4 OutColor) { {GetDISkelMeshContextName} DISkelMesh_GetTriColor(DIContext, InCoord.Tri, InCoord.BaryCoord, OutColor); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::IsValidTriCoordName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in {MeshTriCoordinateStructName} InCoord, out bool IsValid) { {GetDISkelMeshContextName} IsValid = InCoord.Tri < DIContext.MeshTriangleCount; }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetTriCoordVerticesName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in int TriangleIndex, out int OutVertexIndex0, out int OutVertexIndex1, out int OutVertexIndex2) { {GetDISkelMeshContextName} DISkelMesh_GetTriVertices(DIContext, TriangleIndex, OutVertexIndex0, OutVertexIndex1, OutVertexIndex2); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + // Bone Sampling + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetSkinnedBoneDataName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in int InBone, out float3 OutPosition, out float4 OutRotation, out float3 OutVelocity) { {GetDISkelMeshContextName} DISkelMesh_GetSkinnedBone(DIContext, InBone, OutPosition, OutRotation, OutVelocity); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetSkinnedBoneDataInterpolatedName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in int InBone, in float Interp, out float3 OutPosition, out float4 OutRotation, out float3 OutVelocity) { {GetDISkelMeshContextName} DISkelMesh_GetSkinnedBoneInterpolated(DIContext, InBone, Interp, OutPosition, OutRotation, OutVelocity); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetSkinnedBoneDataWSName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in int InBone, out float3 OutPosition, out float4 OutRotation, out float3 OutVelocity) { {GetDISkelMeshContextName} DISkelMesh_GetSkinnedBoneWS(DIContext, InBone, OutPosition, OutRotation, OutVelocity); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetSkinnedBoneDataWSInterpolatedName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in int InBone, in float Interp, out float3 OutPosition, out float4 OutRotation, out float3 OutVelocity) { {GetDISkelMeshContextName} DISkelMesh_GetSkinnedBoneInterpolatedWS(DIContext, InBone, Interp, OutPosition, OutRotation, OutVelocity); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + // Vertex Sampling + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::RandomVertexName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName}(out int OutVertex) { {GetDISkelMeshContextName} DISkelMesh_GetRandomVertex(DIContext, OutVertex); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::IsValidVertexName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in int Vertex, out bool IsValid) { {GetDISkelMeshContextName} DISkelMesh_IsValidVertex(DIContext, Vertex, IsValid); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetSkinnedVertexDataName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in int Vertex, out float3 OutPosition, out float3 OutVelocity) { {GetDISkelMeshContextName} DISkelMesh_GetSkinnedVertex(DIContext, Vertex, OutPosition, OutVelocity); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetSkinnedVertexDataWSName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in int Vertex, out float3 OutPosition, out float3 OutVelocity) { {GetDISkelMeshContextName} DISkelMesh_GetSkinnedVertexWS(DIContext, Vertex, OutPosition, OutVelocity); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetVertexColorName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in int Vertex, out float4 OutColor) { {GetDISkelMeshContextName} DISkelMesh_GetVertexColor(DIContext, Vertex, OutColor); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetVertexUVName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in int Vertex, in int UVSet, out float2 OutUV) { {GetDISkelMeshContextName} DISkelMesh_GetVertexUV(DIContext, Vertex, UVSet, OutUV); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + // Specific Bone + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetSpecificBoneCountName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (out int Count) { {GetDISkelMeshContextName} DISkelMesh_GetSpecificBoneCount(DIContext, Count); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetSpecificBoneAtName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in int BoneIndex, out int Bone) { {GetDISkelMeshContextName} DISkelMesh_GetSpecificBoneAt(DIContext, BoneIndex, Bone); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::RandomSpecificBoneName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (out int Bone) { {GetDISkelMeshContextName} DISkelMesh_RandomSpecificBone(DIContext, Bone); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + // Specific Socket + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetSpecificSocketCountName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (out int Count) { {GetDISkelMeshContextName} DISkelMesh_GetSpecificSocketCount(DIContext, Count); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetSpecificSocketBoneAtName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (in int SocketIndex, out int Bone) { {GetDISkelMeshContextName} DISkelMesh_GetSpecificSocketBoneAt(DIContext, SocketIndex, Bone); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::RandomSpecificSocketBoneName) + { + static const TCHAR* FormatSample = TEXT("void {InstanceFunctionName} (out int SocketBone) { {GetDISkelMeshContextName} DISkelMesh_RandomSpecificSocketBone(DIContext, SocketBone); }"); + OutHLSL += FString::Format(FormatSample, ArgsSample); + } + // Unsupported functionality + //else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetVertexCountName) // void GetFilteredVertexCount(out int VertexCount) + //else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetVertexAtName) // void GetFilteredVertex(in int FilterdIndex, out int VertexIndex) + //else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetTriangleCountName) // void GetFilteredTriangleCount(out int OutTriangleCount) + //else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::GetTriangleAtName) // void GetFilteredTriangle(in int Triangle, out {MeshTriCoordinateStructName} OutCoord) + //else if (DefinitionFunctionName == FSkeletalMeshInterfaceHelper::IsValidBoneName) // void IsValidBoneName(in int BoneIndex, out bool IsValid) else { // This function is not support @@ -1926,31 +1946,13 @@ bool UNiagaraDataInterfaceSkeletalMesh::GetFunctionHLSL(const FName& Definition OutHLSL += TEXT("\n"); return true; - } + void UNiagaraDataInterfaceSkeletalMesh::GetParameterDefinitionHLSL(FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) { - FNDISkeletalMeshParametersName ParamNames; - GetNiagaraDataInterfaceParametersName(ParamNames, ParamInfo.DataInterfaceHLSLSymbol); - - OutHLSL += TEXT("Buffer ") + ParamNames.MeshIndexBufferName + TEXT(";\n"); - OutHLSL += TEXT("Buffer ") + ParamNames.MeshVertexBufferName + TEXT(";\n"); - OutHLSL += TEXT("Buffer ") + ParamNames.MeshSkinWeightBufferName + TEXT(";\n"); - OutHLSL += TEXT("Buffer ") + ParamNames.MeshCurrBonesBufferName + TEXT(";\n"); - OutHLSL += TEXT("Buffer ") + ParamNames.MeshPrevBonesBufferName + TEXT(";\n"); - OutHLSL += TEXT("Buffer ") + ParamNames.MeshTangentBufferName + TEXT(";\n"); - OutHLSL += TEXT("Buffer ") + ParamNames.MeshTexCoordBufferName + TEXT(";\n"); - OutHLSL += TEXT("Buffer ") + ParamNames.MeshTriangleSamplerProbaBufferName + TEXT(";\n"); - OutHLSL += TEXT("Buffer ") + ParamNames.MeshTriangleSamplerAliasBufferName + TEXT(";\n"); - OutHLSL += TEXT("Buffer ") + ParamNames.MeshTriangleMatricesOffsetBufferName + TEXT(";\n"); - OutHLSL += TEXT("uint ") + ParamNames.MeshTriangleCountName + TEXT(";\n"); - OutHLSL += TEXT("float4x4 ") + ParamNames.InstanceTransformName + TEXT(";\n"); - OutHLSL += TEXT("float4x4 ") + ParamNames.InstancePrevTransformName + TEXT(";\n"); - OutHLSL += TEXT("float ") + ParamNames.InstanceInvDeltaTimeName + TEXT(";\n"); - OutHLSL += TEXT("uint ") + ParamNames.EnabledFeaturesName + TEXT(";\n"); - OutHLSL += TEXT("uint ") + ParamNames.InputWeightStrideName + TEXT(";\n"); - OutHLSL += TEXT("uint ") + ParamNames.NumTexCoordName + TEXT(";\n"); + OutHLSL += TEXT("DISKELMESH_DECLARE_CONSTANTS(") + ParamInfo.DataInterfaceHLSLSymbol + TEXT(")\n"); } + FNiagaraDataInterfaceParametersCS* UNiagaraDataInterfaceSkeletalMesh::ConstructComputeParameters() const { return new FNiagaraDataInterfaceParametersCS_SkeletalMesh(); @@ -2018,5 +2020,4 @@ void FSkeletalMeshAccessorHelper::Init< SamplingRegionBuiltData = &SamplingInfo.GetRegionBuiltData(InstData->SamplingRegionIndices[0]); } - #undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceStaticMesh.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceStaticMesh.cpp index bf0517c90f50..7043a6800001 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceStaticMesh.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceStaticMesh.cpp @@ -465,15 +465,6 @@ struct FNiagaraDataInterfaceParametersCS_StaticMesh : public FNiagaraDataInterfa InstanceWorldVelocity.Bind(ParameterMap, *ParamNames.InstanceWorldVelocityName); AreaWeightedSampling.Bind(ParameterMap, *ParamNames.AreaWeightedSamplingName); NumTexCoord.Bind(ParameterMap, *ParamNames.NumTexCoordName); - - if (!MeshIndexBuffer.IsBound()) - { - UE_LOG(LogNiagara, Warning, TEXT("Binding failed for FNiagaraDataInterfaceParametersCS_StaticMesh Texture %s. Was it optimized out?"), *ParamNames.MeshIndexBufferName) - } - if (!MeshVertexBuffer.IsBound()) - { - UE_LOG(LogNiagara, Warning, TEXT("Binding failed for FNiagaraDataInterfaceParametersCS_StaticMesh Sampler %s. Was it optimized out?"), *ParamNames.MeshVertexBufferName) - } } virtual void Serialize(FArchive& Ar)override @@ -2030,7 +2021,7 @@ bool UNiagaraDataInterfaceStaticMesh::GetFunctionHLSL(const FName& DefinitionFu static const TCHAR *FormatSample = TEXT(R"( void {InstanceFunctionName} (in int In_Section, out {MeshTriCoordinateStructName} Out_Coord) { - int Section = clamp(In_Section, 0, {SectionCountName} - 1); + int Section = clamp(In_Section, 0, (int)({SectionCountName} - 1)); uint4 SectionData = {MeshSectionBufferName}[Section]; uint SectionFirstTriangle = SectionData.x; @@ -2245,7 +2236,7 @@ bool UNiagaraDataInterfaceStaticMesh::GetFunctionHLSL(const FName& DefinitionFu uint VertexIndex2 = {MeshIndexBufferName}[TriangleIndex+2]; uint stride = {NumTexCoordName}; - uint SelectedUVSet = clamp(In_UVSet, 0, {NumTexCoordName}-1); + uint SelectedUVSet = clamp((uint)In_UVSet, 0, {NumTexCoordName}-1); float2 UV0 = {MeshTexCoordBufferName}[VertexIndex0 * stride + SelectedUVSet]; float2 UV1 = {MeshTexCoordBufferName}[VertexIndex1 * stride + SelectedUVSet]; float2 UV2 = {MeshTexCoordBufferName}[VertexIndex2 * stride + SelectedUVSet]; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataSet.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataSet.cpp index b0f74a54592a..d2bb14e5a709 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataSet.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataSet.cpp @@ -432,13 +432,13 @@ void FNiagaraDataSet::CopyTo(FNiagaraDataSet& Other, int32 StartIdx, int32 NumIn Other.EndSimulate(); } -void FNiagaraDataSet::CopyFromGPUReadback(float* GPUReadBackFloat, int* GPUReadBackInt, int32 StartIdx /* = 0 */, int32 NumInstances /* = INDEX_NONE */) +void FNiagaraDataSet::CopyFromGPUReadback(float* GPUReadBackFloat, int* GPUReadBackInt, int32 StartIdx /* = 0 */, int32 NumInstances /* = INDEX_NONE */, uint32 FloatStride, uint32 IntStride) { check(IsInRenderingThread()); check(bFinalized);//We should be finalized with proper layout information already. FNiagaraDataBuffer& DestBuffer = BeginSimulate(); - DestBuffer.GPUCopyFrom(GPUReadBackFloat, GPUReadBackInt, StartIdx, NumInstances); + DestBuffer.GPUCopyFrom(GPUReadBackFloat, GPUReadBackInt, StartIdx, NumInstances, FloatStride, IntStride); EndSimulate(); } @@ -466,7 +466,7 @@ void FNiagaraDataBuffer::CheckUsage(bool bReadOnly)const if (Owner->SimTarget == ENiagaraSimTarget::CPUSim) { //We can read on the RT but any modifications must be GT (or GT Task). - check(bReadOnly || !IsInRenderingThread()); + check(IsInGameThread() || (bReadOnly || !IsInRenderingThread())); } else { @@ -744,7 +744,7 @@ void FNiagaraDataBuffer::CopyTo(FNiagaraDataBuffer& DestBuffer, int32 InStartIdx } } -void FNiagaraDataBuffer::GPUCopyFrom(float* GPUReadBackFloat, int* GPUReadBackInt, int32 InStartIdx, int32 InNumInstances) +void FNiagaraDataBuffer::GPUCopyFrom(float* GPUReadBackFloat, int* GPUReadBackInt, int32 InStartIdx, int32 InNumInstances, uint32 InSrcFloatStride, uint32 InSrcIntStride) { //CheckUsage(false); //Have to disable this as in this specific case we write to a "CPUSim" from the RT. @@ -760,8 +760,10 @@ void FNiagaraDataBuffer::GPUCopyFrom(float* GPUReadBackFloat, int* GPUReadBackIn { for (uint32 CompIdx = 0; CompIdx < Owner->TotalFloatComponents; ++CompIdx) { - const float* SrcStart = GetInstancePtrFloat(GPUReadBackFloat, CompIdx, InStartIdx); - const float* SrcEnd = GetInstancePtrFloat(GPUReadBackFloat, CompIdx, InStartIdx + InNumInstances); + // We have to reimplement the logic from GetInstancePtrFloat here because the incoming stride may be different than this + // data buffer's stride. + const float* SrcStart = (const float*)((uint8*)GPUReadBackFloat + InSrcFloatStride * CompIdx) + InStartIdx; + const float* SrcEnd = (const float*)((uint8*)GPUReadBackFloat + InSrcFloatStride * CompIdx) + InStartIdx + InNumInstances; float* Dst = GetInstancePtrFloat(CompIdx, 0); size_t Count = SrcEnd - SrcStart; FMemory::Memcpy(Dst, SrcStart, Count * sizeof(float)); @@ -779,8 +781,10 @@ void FNiagaraDataBuffer::GPUCopyFrom(float* GPUReadBackFloat, int* GPUReadBackIn { for (uint32 CompIdx = 0; CompIdx < Owner->TotalInt32Components; ++CompIdx) { - const int32* SrcStart = GetInstancePtrInt32(GPUReadBackInt, CompIdx, InStartIdx); - const int32* SrcEnd = GetInstancePtrInt32(GPUReadBackInt, CompIdx, InStartIdx + InNumInstances); + // We have to reimplement the logic from GetInstancePtrInt here because the incoming stride may be different than this + // data buffer's stride. + const int32* SrcStart = (const int32*)((uint8*)GPUReadBackInt + InSrcIntStride * CompIdx) + InStartIdx; + const int32* SrcEnd = (const int32*)((uint8*)GPUReadBackInt + InSrcIntStride * CompIdx) + InStartIdx + InNumInstances; int32* Dst = GetInstancePtrInt32(CompIdx, 0); size_t Count = SrcEnd - SrcStart; FMemory::Memcpy(Dst, SrcStart, Count * sizeof(int32)); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitter.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitter.cpp index 520119e71102..d4f015943785 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitter.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitter.cpp @@ -11,9 +11,12 @@ #include "NiagaraModule.h" #include "NiagaraSystem.h" #include "NiagaraStats.h" +#include "Modules/ModuleManager.h" #if WITH_EDITOR const FName UNiagaraEmitter::PrivateMemberNames::EventHandlerScriptProps = GET_MEMBER_NAME_CHECKED(UNiagaraEmitter, EventHandlerScriptProps); + +const FString InitialNotSynchronizedReason("Emitter created"); #endif static int32 GbForceNiagaraCompileOnLoad = 0; @@ -24,6 +27,14 @@ static FAutoConsoleVariableRef CVarForceNiagaraCompileOnLoad( ECVF_Default ); +static int32 GbForceNiagaraMergeOnLoad = 0; +static FAutoConsoleVariableRef CVarForceNiagaraMergeOnLoad( + TEXT("fx.ForceMergeOnLoad"), + GbForceNiagaraMergeOnLoad, + TEXT("If > 0 emitters will be forced to merge on load. \n"), + ECVF_Default +); + static int32 GbForceNiagaraFailToCompile = 0; static FAutoConsoleVariableRef CVarForceNiagaraCompileToFail( TEXT("fx.ForceNiagaraCompileToFail"), @@ -126,6 +137,84 @@ bool UNiagaraEmitter::GetForceCompileOnLoad() { return GbForceNiagaraCompileOnLoad > 0; } + +bool UNiagaraEmitter::IsSynchronizedWithParent() const +{ + if (Parent == nullptr) + { + // If the emitter has no parent than it is synchronized by default. + return true; + } + + if (ParentAtLastMerge == nullptr) + { + // If the parent was valid but the parent at last merge isn't, they we don't know if it's up to date so we say it's not, and let + // the actual merge code print an appropriate message to the log. + return false; + } + + if (Parent->GetChangeId().IsValid() == false || + ParentAtLastMerge->GetChangeId().IsValid() == false) + { + // If any of the change Ids aren't valid then we assume we're out of sync. + return false; + } + + // Otherwise check the change ids, and the force flag. + return Parent->GetChangeId() == ParentAtLastMerge->GetChangeId() && GbForceNiagaraMergeOnLoad <= 0; +} + +INiagaraMergeManager::FMergeEmitterResults UNiagaraEmitter::MergeChangesFromParent() +{ + UE_LOG(LogNiagara, Log, TEXT("Emitter %s-%s is merging changes from parent %s because its Change ID was updated."), *GetPathName(), *GetName(), + Parent != nullptr ? *Parent->GetPathName() : TEXT("(null)")); + + if (Parent == nullptr) + { + // If we don't have a copy of the parent emitter, this emitter can't safely be merged. + INiagaraMergeManager::FMergeEmitterResults MergeResults; + MergeResults.bSucceeded = false; + MergeResults.bModifiedGraph = false; + MergeResults.ErrorMessages.Add(NSLOCTEXT("NiagaraEmitter", "NoParentErrorMessage", "This emitter has no 'Parent' so changes can't be merged in.")); + return MergeResults; + } + + if (ParentAtLastMerge == nullptr) + { + // If we don't have a copy of the last merged parent emitter, this emitter can't safely be + // merged. + INiagaraMergeManager::FMergeEmitterResults MergeResults; + MergeResults.bSucceeded = false; + MergeResults.bModifiedGraph = false; + MergeResults.ErrorMessages.Add(NSLOCTEXT("NiagaraEmitter", "NoLastMergedParentErrorMessage", "This emitter has no 'ParentAtLastMerge' so changes can't be merged in.")); + return MergeResults; + } + + INiagaraModule& NiagaraModule = FModuleManager::Get().GetModuleChecked("Niagara"); + const INiagaraMergeManager& MergeManager = NiagaraModule.GetMergeManager(); + INiagaraMergeManager::FMergeEmitterResults MergeResults = MergeManager.MergeEmitter(*Parent, *ParentAtLastMerge, *this); + if (MergeResults.bSucceeded) + { + UpdateFromMergedCopy(MergeManager, MergeResults.MergedInstance); + + // Update the last merged source and clear it's stand alone and public flags since it's not an asset. + ParentAtLastMerge = CastChecked(StaticDuplicateObject(Parent, this)); + ParentAtLastMerge->ClearFlags(RF_Standalone | RF_Public); + } + else + { + UE_LOG(LogNiagara, Warning, TEXT("Failed to merge changes for parent emitter. Emitter: %s Parent Emitter: %s Error Message: %s"), + *GetPathName(), Parent != nullptr ? *Parent->GetPathName() : TEXT("(null)"), *MergeResults.GetErrorMessagesString()); + } + + return MergeResults; +} + +bool UNiagaraEmitter::UsesEmitter(const UNiagaraEmitter& InEmitter) const +{ + return Parent == &InEmitter || (Parent != nullptr && Parent->UsesEmitter(InEmitter)); +} + #endif void UNiagaraEmitter::Serialize(FArchive& Ar) @@ -203,8 +292,14 @@ void UNiagaraEmitter::PostLoad() Script->ConditionalPostLoad(); } - // Reset scripts if recompile is forced. #if WITH_EDITORONLY_DATA + + if (IsSynchronizedWithParent() == false) + { + MergeChangesFromParent(); + } + + // Reset scripts if recompile is forced. bool bGenerateNewChangeId = false; if (GetForceCompileOnLoad()) { @@ -283,6 +378,35 @@ void UNiagaraEmitter::PostLoad() } #if WITH_EDITOR +/** Creates a new emitter with the supplied emitter as a parent emitter and the supplied system as it's owner. */ +UNiagaraEmitter* UNiagaraEmitter::CreateWithParentAndOwner(UNiagaraEmitter& InParentEmitter, UObject* InOwner, FName InName, EObjectFlags FlagMask) +{ + UNiagaraEmitter* NewEmitter = Cast(StaticDuplicateObject(&InParentEmitter, InOwner, InName, FlagMask)); + NewEmitter->Parent = &InParentEmitter; + NewEmitter->ParentAtLastMerge = Cast(StaticDuplicateObject(&InParentEmitter, NewEmitter)); + NewEmitter->ParentAtLastMerge->ClearFlags(RF_Standalone | RF_Public); + NewEmitter->SetUniqueEmitterName(InName.ToString()); + NewEmitter->GraphSource->MarkNotSynchronized(InitialNotSynchronizedReason); + return NewEmitter; +} + +/** Creates a new emitter by duplicating an existing emitter. The new emitter will reference the same parent emitter if one is available. */ +UNiagaraEmitter* UNiagaraEmitter::CreateAsDuplicate(const UNiagaraEmitter& InEmitterToDuplicate, FName InDuplicateName, UNiagaraSystem& InDuplicateOwnerSystem) +{ + UNiagaraEmitter* NewEmitter = Cast(StaticDuplicateObject(&InEmitterToDuplicate, &InDuplicateOwnerSystem)); + NewEmitter->ClearFlags(RF_Standalone | RF_Public); + NewEmitter->Parent = InEmitterToDuplicate.Parent; + if (InEmitterToDuplicate.ParentAtLastMerge != nullptr) + { + NewEmitter->ParentAtLastMerge = InEmitterToDuplicate.ParentAtLastMerge; + NewEmitter->ParentAtLastMerge->ClearFlags(RF_Standalone | RF_Public); + } + NewEmitter->SetUniqueEmitterName(InDuplicateName.ToString()); + NewEmitter->GraphSource->MarkNotSynchronized(InitialNotSynchronizedReason); + + return NewEmitter; +} + void UNiagaraEmitter::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); @@ -668,11 +792,6 @@ bool UNiagaraEmitter::UsesScript(const UNiagaraScript* Script)const return false; } -//TODO -// bool UNiagaraEmitter::UsesDataInterface(UNiagaraDataInterface* Interface) -//{ -//} - bool UNiagaraEmitter::UsesCollection(const class UNiagaraParameterCollection* Collection)const { if (SpawnScriptProps.Script && SpawnScriptProps.Script->UsesCollection(Collection)) @@ -699,6 +818,90 @@ FString UNiagaraEmitter::GetUniqueEmitterName()const } #if WITH_EDITORONLY_DATA + +void UNiagaraEmitter::UpdateFromMergedCopy(const INiagaraMergeManager& MergeManager, UNiagaraEmitter* MergedEmitter) +{ + auto ReouterMergedObject = [](UObject* NewOuter, UObject* TargetObject) + { + FName MergedObjectUniqueName = MakeUniqueObjectName(NewOuter, TargetObject->GetClass(), TargetObject->GetFName()); + TargetObject->Rename(*MergedObjectUniqueName.ToString(), NewOuter, REN_ForceNoResetLoaders); + }; + + // The merged copy was based on the parent emitter so its name might be wrong, check and fix that first, + // otherwise the rapid iteration parameter names will be wrong from the copied scripts. + if (MergedEmitter->GetUniqueEmitterName() != UniqueEmitterName) + { + MergedEmitter->SetUniqueEmitterName(UniqueEmitterName); + } + + // Copy base editable emitter properties. + TArray DifferentProperties; + MergeManager.DiffEditableProperties(this, MergedEmitter, *UNiagaraEmitter::StaticClass(), DifferentProperties); + MergeManager.CopyPropertiesToBase(this, MergedEmitter, DifferentProperties); + + // Copy source and scripts + ReouterMergedObject(this, MergedEmitter->GraphSource); + GraphSource->OnChanged().RemoveAll(this); + GraphSource = MergedEmitter->GraphSource; + GraphSource->OnChanged().AddUObject(this, &UNiagaraEmitter::GraphSourceChanged); + + ReouterMergedObject(this, MergedEmitter->SpawnScriptProps.Script); + SpawnScriptProps.Script->RapidIterationParameters.RemoveAllOnChangedHandlers(this); + SpawnScriptProps.Script = MergedEmitter->SpawnScriptProps.Script; + SpawnScriptProps.Script->RapidIterationParameters.AddOnChangedHandler( + FNiagaraParameterStore::FOnChanged::FDelegate::CreateUObject(this, &UNiagaraEmitter::ScriptRapidIterationParameterChanged)); + + ReouterMergedObject(this, MergedEmitter->UpdateScriptProps.Script); + UpdateScriptProps.Script->RapidIterationParameters.RemoveAllOnChangedHandlers(this); + UpdateScriptProps.Script = MergedEmitter->UpdateScriptProps.Script; + UpdateScriptProps.Script->RapidIterationParameters.AddOnChangedHandler( + FNiagaraParameterStore::FOnChanged::FDelegate::CreateUObject(this, &UNiagaraEmitter::ScriptRapidIterationParameterChanged)); + + ReouterMergedObject(this, MergedEmitter->EmitterSpawnScriptProps.Script); + EmitterSpawnScriptProps.Script->RapidIterationParameters.RemoveAllOnChangedHandlers(this); + EmitterSpawnScriptProps.Script = MergedEmitter->EmitterSpawnScriptProps.Script; + EmitterSpawnScriptProps.Script->RapidIterationParameters.AddOnChangedHandler( + FNiagaraParameterStore::FOnChanged::FDelegate::CreateUObject(this, &UNiagaraEmitter::ScriptRapidIterationParameterChanged)); + + ReouterMergedObject(this, MergedEmitter->EmitterUpdateScriptProps.Script); + EmitterUpdateScriptProps.Script->RapidIterationParameters.RemoveAllOnChangedHandlers(this); + EmitterUpdateScriptProps.Script = MergedEmitter->EmitterUpdateScriptProps.Script; + EmitterUpdateScriptProps.Script->RapidIterationParameters.AddOnChangedHandler( + FNiagaraParameterStore::FOnChanged::FDelegate::CreateUObject(this, &UNiagaraEmitter::ScriptRapidIterationParameterChanged)); + + // Copy event handlers + for (FNiagaraEventScriptProperties& EventScriptProperties : EventHandlerScriptProps) + { + EventScriptProperties.Script->RapidIterationParameters.RemoveAllOnChangedHandlers(this); + } + EventHandlerScriptProps.Empty(); + + for (FNiagaraEventScriptProperties& MergedEventScriptProperties : MergedEmitter->EventHandlerScriptProps) + { + EventHandlerScriptProps.Add(MergedEventScriptProperties); + ReouterMergedObject(this, MergedEventScriptProperties.Script); + MergedEventScriptProperties.Script->RapidIterationParameters.AddOnChangedHandler( + FNiagaraParameterStore::FOnChanged::FDelegate::CreateUObject(this, &UNiagaraEmitter::ScriptRapidIterationParameterChanged)); + } + + // Copy renderers + for (UNiagaraRendererProperties* Renderer : RendererProperties) + { + Renderer->OnChanged().RemoveAll(this); + } + RendererProperties.Empty(); + + for (UNiagaraRendererProperties* MergedRenderer : MergedEmitter->RendererProperties) + { + ReouterMergedObject(this, MergedRenderer); + RendererProperties.Add(MergedRenderer); + MergedRenderer->OnChanged().AddUObject(this, &UNiagaraEmitter::RendererChanged); + } + + // Update the change id since we don't know what's changed. + ChangeId = FGuid::NewGuid(); +} + void UNiagaraEmitter::SyncEmitterAlias(const FString& InOldName, const FString& InNewName) { TMap RenameMap; @@ -724,6 +927,13 @@ bool UNiagaraEmitter::SetUniqueEmitterName(const FString& InName) FString OldName = UniqueEmitterName; UniqueEmitterName = InName; + if (GetName() != InName) + { + // Also rename the underlying uobject to keep things consistent. + FName UniqueObjectName = MakeUniqueObjectName(GetOuter(), UNiagaraEmitter::StaticClass(), *InName); + Rename(*UniqueObjectName.ToString()); + } + #if WITH_EDITORONLY_DATA SyncEmitterAlias(OldName, UniqueEmitterName); #endif @@ -880,3 +1090,14 @@ void UNiagaraEmitter::GenerateStatID() StatID_RT_CNC = FDynamicStats::CreateStatId(GetName() + TEXT("[RT_CNC]")); #endif } + +UNiagaraEmitter* UNiagaraEmitter::GetParent() const +{ + return Parent; +} + +void UNiagaraEmitter::RemoveParent() +{ + Parent = nullptr; + ParentAtLastMerge = nullptr; +} diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterHandle.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterHandle.cpp index 8b30c481dda5..5c3d44bbd48b 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterHandle.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterHandle.cpp @@ -11,54 +11,29 @@ #include "Modules/ModuleManager.h" const FNiagaraEmitterHandle FNiagaraEmitterHandle::InvalidHandle; -const FString InitialNotSynchronizedReason("Emitter handle constructed"); -FNiagaraEmitterHandle::FNiagaraEmitterHandle() : - bIsEnabled(true), +FNiagaraEmitterHandle::FNiagaraEmitterHandle() + : bIsEnabled(true) #if WITH_EDITORONLY_DATA - Source(nullptr), - LastMergedSource(nullptr), - bIsolated(false), + , Source_DEPRECATED(nullptr) + , LastMergedSource_DEPRECATED(nullptr) + , bIsolated(false) #endif - Instance(nullptr) + , Instance(nullptr) { } #if WITH_EDITORONLY_DATA -FNiagaraEmitterHandle::FNiagaraEmitterHandle(UNiagaraEmitter& InSourceEmitter, FName InName, UNiagaraSystem& InOuterSystem) +FNiagaraEmitterHandle::FNiagaraEmitterHandle(UNiagaraEmitter& InEmitter) : Id(FGuid::NewGuid()) , IdName(*Id.ToString()) , bIsEnabled(true) - , Name(InName) - , Source(&InSourceEmitter) - , LastMergedSource(Cast(StaticDuplicateObject(Source, &InOuterSystem))) + , Name(*InEmitter.GetUniqueEmitterName()) + , Source_DEPRECATED(nullptr) + , LastMergedSource_DEPRECATED(nullptr) , bIsolated(false) - , Instance(Cast(StaticDuplicateObject(Source, &InOuterSystem))) + , Instance(&InEmitter) { - Instance->ClearFlags(RF_Standalone | RF_Public); - Instance->SetUniqueEmitterName(Name.ToString()); - Instance->GraphSource->MarkNotSynchronized(InitialNotSynchronizedReason); - LastMergedSource->ClearFlags(RF_Standalone | RF_Public); -} - -FNiagaraEmitterHandle::FNiagaraEmitterHandle(const FNiagaraEmitterHandle& InHandleToDuplicate, FName InDuplicateName, UNiagaraSystem& InDuplicateOwnerSystem) - : Id(FGuid::NewGuid()) - , IdName(*Id.ToString()) - , bIsEnabled(InHandleToDuplicate.bIsEnabled) - , Name(InDuplicateName) - , Source(InHandleToDuplicate.Source) - , LastMergedSource(InHandleToDuplicate.LastMergedSource != nullptr ? Cast(StaticDuplicateObject(InHandleToDuplicate.LastMergedSource, &InDuplicateOwnerSystem)) : nullptr) - , bIsolated(false) - , Instance(Cast(StaticDuplicateObject(InHandleToDuplicate.Instance, &InDuplicateOwnerSystem))) -{ - // Clear stand alone and public flags from the referenced emitters since they are not assets. - Instance->ClearFlags(RF_Standalone | RF_Public); - Instance->SetUniqueEmitterName(Name.ToString()); - Instance->GraphSource->MarkNotSynchronized(InitialNotSynchronizedReason); - if (LastMergedSource != nullptr) - { - LastMergedSource->ClearFlags(RF_Standalone | RF_Public); - } } #endif @@ -129,14 +104,6 @@ void FNiagaraEmitterHandle::SetIsEnabled(bool bInIsEnabled) bIsEnabled = bInIsEnabled; } -#if WITH_EDITORONLY_DATA -const UNiagaraEmitter* FNiagaraEmitterHandle::GetSource() const -{ - return Source; -} - -#endif - UNiagaraEmitter* FNiagaraEmitterHandle::GetInstance() const { return Instance; @@ -150,31 +117,6 @@ FString FNiagaraEmitterHandle::GetUniqueInstanceName()const #if WITH_EDITORONLY_DATA -bool FNiagaraEmitterHandle::IsSynchronizedWithSource() const -{ - if (Source == nullptr && LastMergedSource == nullptr) - { - // If the emitter has no source and no last merged source than it is synchronized by default. - return true; - } - - if (Source == nullptr || LastMergedSource == nullptr) - { - // If either only the source or only the last merged sources is missing, then we're not synchronized. The - // merge logic will detect this and print an appropriate message to the log. - return false; - } - - if (Source->GetChangeId().IsValid() == false || - LastMergedSource->GetChangeId().IsValid() == false) - { - // If any of the change Ids aren't valid then we assume we're out of sync. - return false; - } - - return Source->GetChangeId() == LastMergedSource->GetChangeId(); -} - bool FNiagaraEmitterHandle::NeedsRecompile() const { TArray Scripts; @@ -190,69 +132,41 @@ bool FNiagaraEmitterHandle::NeedsRecompile() const return false; } -void FNiagaraEmitterHandle::ConditionalPostLoad() +void FNiagaraEmitterHandle::ConditionalPostLoad(int32 NiagaraCustomVersion) { - if (Source != nullptr) + if (Instance != nullptr) { - Source->ConditionalPostLoad(); + Instance->ConditionalPostLoad(); + if (NiagaraCustomVersion < FNiagaraCustomVersion::MoveInheritanceDataFromTheEmitterHandleToTheEmitter) + { + if (Source_DEPRECATED != nullptr) + { + Source_DEPRECATED->ConditionalPostLoad(); + Instance->Parent = Source_DEPRECATED; + Instance->Parent->Rename(nullptr, Instance, REN_ForceNoResetLoaders); + Source_DEPRECATED = nullptr; + } + if (LastMergedSource_DEPRECATED != nullptr) + { + LastMergedSource_DEPRECATED->ConditionalPostLoad(); + Instance->ParentAtLastMerge = LastMergedSource_DEPRECATED; + Instance->ParentAtLastMerge->Rename(nullptr, Instance, REN_ForceNoResetLoaders); + LastMergedSource_DEPRECATED = nullptr; + } + + // Since we've previously post loaded the emitter it wouldn't have merged on load since it didn't have + // the parent information so we check this again here, now that the parent information has been set. + if (Instance->IsSynchronizedWithParent() == false) + { + Instance->MergeChangesFromParent(); + } + } } - if (LastMergedSource != nullptr) - { - LastMergedSource->ConditionalPostLoad(); - } - Instance->ConditionalPostLoad(); } -INiagaraModule::FMergeEmitterResults FNiagaraEmitterHandle::MergeSourceChanges() +bool FNiagaraEmitterHandle::UsesEmitter(const UNiagaraEmitter& InEmitter) const { - UE_LOG(LogNiagara, Log, TEXT("Emitter %s-%s is merging changes from source %s because its Change ID was updated."), *Instance->GetPathName(), *Name.ToString(), - Source != nullptr ? *Source->GetPathName() : TEXT("(null)")); - - if (Source == nullptr) - { - // If we don't have a copy of the source emitter, this emitter can't safely be merged. - INiagaraModule::FMergeEmitterResults MergeResults; - MergeResults.bSucceeded = false; - MergeResults.bModifiedGraph = false; - MergeResults.ErrorMessages.Add(NSLOCTEXT("NiagaraEmitterHandle", "NoSourceErrorMessage", "This emitter has no 'Source' so changes can't be merged in.")); - return MergeResults; - } - - if (LastMergedSource == nullptr) - { - // If we don't have a copy of the last merged source emitter, this emitter can't safely be - // merged. - INiagaraModule::FMergeEmitterResults MergeResults; - MergeResults.bSucceeded = false; - MergeResults.bModifiedGraph = false; - MergeResults.ErrorMessages.Add(NSLOCTEXT("NiagaraEmitterHandle", "NoLastMergedSourceErrorMessage", "This emitter has no 'LastMergedSource' so changes can't be merged in.")); - return MergeResults; - } - - INiagaraModule& NiagaraModule = FModuleManager::Get().GetModuleChecked("Niagara"); - INiagaraModule::FMergeEmitterResults MergeResults = NiagaraModule.MergeEmitter(*Source, *LastMergedSource, *Instance); - if (MergeResults.bSucceeded) - { - UObject* Outer = Instance->GetOuter(); - Instance = MergeResults.MergedInstance; - - // Rename the merged instance into this package with the correct handle name and then clear it's stand alone and public flags since it's not a root asset. - FName NewInstanceName = MakeUniqueObjectName(Outer, UNiagaraEmitter::StaticClass(), Name); - Instance->Rename(*NewInstanceName.ToString(), Outer, REN_ForceNoResetLoaders); - Instance->ClearFlags(RF_Standalone | RF_Public); - Instance->SetUniqueEmitterName(Name.ToString()); - - // Update the last merged source and clear it's stand alone and public flags since it's not an asset. - LastMergedSource = CastChecked(StaticDuplicateObject(Source, Outer)); - LastMergedSource->ClearFlags(RF_Standalone | RF_Public); - } - return MergeResults; -} - -void FNiagaraEmitterHandle::RemoveSource() -{ - Source = nullptr; - LastMergedSource = nullptr; + return Instance == &InEmitter || (Instance != nullptr && Instance->UsesEmitter(InEmitter)); } #endif \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstanceBatcher.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstanceBatcher.cpp index 23f762f27016..d1d1c438b07b 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstanceBatcher.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstanceBatcher.cpp @@ -454,7 +454,7 @@ void NiagaraEmitterInstanceBatcher::SimStepClearAndSetup(const FNiagaraComputeIn // SCOPED_GPU_EVENTF(RHICmdList, NiagaraIndexBufferClear, TEXT("Niagara index buffer clear")); SCOPED_DRAW_EVENT(RHICmdList, NiagaraIndexBufferClear); SCOPED_GPU_STAT(RHICmdList, NiagaraIndexBufferClear); - + //UE_LOG(LogNiagara, Log, TEXT("Clearing UAV %p"), (FRHIVertexBuffer *)DatasetIndexBufferWrite.Buffer); ClearUAV(RHICmdList, DatasetIndexBufferWrite, 0); #endif } @@ -513,7 +513,7 @@ void NiagaraEmitterInstanceBatcher::TickSingle(const FNiagaraGPUSystemTick& Tick FNiagaraDataBuffer& CurrentData = *Instance->CurrentData; FNiagaraDataBuffer& DestinationData = *Instance->DestinationData; - uint32 PrevNumInstances = CurrentData.GetNumInstances(); + uint32 PrevNumInstances = Tick.bNeedsReset ? 0 : CurrentData.GetNumInstances(); uint32 NewNumInstances = Instance->SpawnRateInstances + Instance->EventSpawnTotal + PrevNumInstances; //We must assume all particles survive when allocating here. @@ -813,13 +813,16 @@ void NiagaraEmitterInstanceBatcher::ResolveDatasetWrites(FRHICommandList &RHICmd int32 *NumInstancesAfterSim = static_cast(Context->GPUDataReadback->Lock(64 * sizeof(int32))); if (NumInstancesAfterSim) { - int32 NewExistingDataCount = NumInstancesAfterSim[1] + Context->AccumulatedSpawnRate; // index 1 is always the count + int32 NewExistingDataCount = (Context->ResetSinceLastReadbackIssued ? 0 : NumInstancesAfterSim[1]) + Context->AccumulatedSpawnRate; // index 1 is always the count DestinationData.SetNumInstances(NewExistingDataCount); - //UE_LOG(LogNiagara, Log, TEXT("GPU Syncup %s : Was(%d) Now(%d)"), *Context->DebugSimName, ExistingDataCount, NewExistingDataCount ); + //UE_LOG(LogNiagara, Log, TEXT("GPU Syncup %s : Was(%d) Now(%d) Accumulated(%d) NumInstancesAfterSim[1](%d)"), *Context->DebugSimName, ExistingDataCount, NewExistingDataCount , Context->AccumulatedSpawnRate, NumInstancesAfterSim[1]); + //UE_LOG(LogNiagara, Log, TEXT("Reading UAV %p"), (FRHIVertexBuffer *)DatasetIndexBufferWrite.Buffer); + INC_DWORD_STAT_BY(STAT_NiagaraGPUParticles, NewExistingDataCount); SET_DWORD_STAT(STAT_NiagaraReadbackLatency, 0); Context->AccumulatedSpawnRate = 0; + Context->ResetSinceLastReadbackIssued = false; bSuccessfullyRead = true; } else @@ -876,7 +879,7 @@ void NiagaraEmitterInstanceBatcher::ProcessDebugInfo(FRHICommandList &RHICmdList IntDataBuffer = static_cast(Context->GPUDebugDataReadbackInt->Lock(Context->GPUDebugDataIntSize)); } - Context->DebugInfo->Frame.CopyFromGPUReadback(FloatDataBuffer, IntDataBuffer, 0, NewExistingDataCount); + Context->DebugInfo->Frame.CopyFromGPUReadback(FloatDataBuffer, IntDataBuffer, 0, NewExistingDataCount, Context->GPUDebugDataFloatStride, Context->GPUDebugDataIntStride); Context->DebugInfo->bWritten = true; @@ -907,6 +910,8 @@ void NiagaraEmitterInstanceBatcher::ProcessDebugInfo(FRHICommandList &RHICmdList Context->GPUDebugDataReadbackCounts = nullptr; Context->GPUDebugDataFloatSize = 0; Context->GPUDebugDataIntSize = 0; + Context->GPUDebugDataFloatStride = 0; + Context->GPUDebugDataIntStride = 0; } // We've updated the debug info directly, now we need to no longer keep asking and querying because this frame is done! @@ -1018,6 +1023,13 @@ void NiagaraEmitterInstanceBatcher::Run(const FNiagaraGPUSystemTick& Tick, const InitIndexBuffer[1] = 0; // number of instances ReadIndexBuffer.Initialize(sizeof(int32), 64, EPixelFormat::PF_R32_UINT, BUF_DrawIndirect | BUF_Static, nullptr, &InitIndexBuffer); } + else if (Tick.bNeedsReset) + { + //UE_LOG(LogNiagara, Log, TEXT("Clearing UAV due to reset!")); + ClearUAV(RHICmdList, ReadIndexBuffer, 0); + Context->AccumulatedSpawnRate = 0; + Context->ResetSinceLastReadbackIssued = true; + } RHICmdList.SetComputeShader(Shader->GetComputeShader()); @@ -1098,6 +1110,8 @@ void NiagaraEmitterInstanceBatcher::Run(const FNiagaraGPUSystemTick& Tick, const Context->GPUDebugDataFloatSize = 0; Context->GPUDebugDataIntSize = 0; + Context->GPUDebugDataFloatStride = 0; + Context->GPUDebugDataIntStride = 0; if (DestinationData.GetGPUBufferFloat().NumBytes > 0) { @@ -1105,6 +1119,7 @@ void NiagaraEmitterInstanceBatcher::Run(const FNiagaraGPUSystemTick& Tick, const Context->GPUDebugDataReadbackFloat = new FRHIGPUBufferReadback(ReadbackFloatName); Context->GPUDebugDataReadbackFloat->EnqueueCopy(RHICmdList, DestinationData.GetGPUBufferFloat().Buffer); Context->GPUDebugDataFloatSize = DestinationData.GetGPUBufferFloat().NumBytes; + Context->GPUDebugDataFloatStride = DestinationData.GetFloatStride(); } if (DestinationData.GetGPUBufferInt().NumBytes > 0) @@ -1113,6 +1128,7 @@ void NiagaraEmitterInstanceBatcher::Run(const FNiagaraGPUSystemTick& Tick, const Context->GPUDebugDataReadbackInt = new FRHIGPUBufferReadback(ReadbackIntName); Context->GPUDebugDataReadbackInt->EnqueueCopy(RHICmdList, DestinationData.GetGPUBufferInt().Buffer); Context->GPUDebugDataIntSize = DestinationData.GetGPUBufferInt().NumBytes; + Context->GPUDebugDataIntStride = DestinationData.GetInt32Stride(); } static const FName ReadbackCountsName(TEXT("Niagara GPU Emitter Readback")); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraModule.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraModule.cpp index a332ca87df33..b07042f87efa 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraModule.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraModule.cpp @@ -352,30 +352,23 @@ void INiagaraModule::TickWorld(UWorld* World, ELevelTick TickType, float DeltaSe } #if WITH_EDITOR -INiagaraModule::FMergeEmitterResults INiagaraModule::MergeEmitter(UNiagaraEmitter& Source, UNiagaraEmitter& LastMergedSource, UNiagaraEmitter& Instance) +const INiagaraMergeManager& INiagaraModule::GetMergeManager() { - if (OnMergeEmitterDelegate.IsBound()) - { - return OnMergeEmitterDelegate.Execute(Source, LastMergedSource, Instance); - } - FMergeEmitterResults Results; - Results.bSucceeded = false; - Results.ErrorMessages.Add(FText::Format(LOCTEXT("MergeDelegateNotRegisteredFormat", "Failed to merge emitter {0}. Merge delegate not registered."), FText::FromString(Instance.GetPathName()))); - return Results; + checkf(MergeManager.IsValid(), TEXT("Merge manager was never registered, or was unregistered.")); + return *MergeManager.Get(); } -FDelegateHandle INiagaraModule::RegisterOnMergeEmitter(FOnMergeEmitter OnMergeEmitter) +void INiagaraModule::RegisterMergeManager(TSharedRef InMergeManager) { - checkf(OnMergeEmitterDelegate.IsBound() == false, TEXT("Only one handler is allowed for the OnMergeEmitter delegate")); - OnMergeEmitterDelegate = OnMergeEmitter; - return OnMergeEmitterDelegate.GetHandle(); + checkf(MergeManager.IsValid() == false, TEXT("Only one merge manager can be registered at a time.")); + MergeManager = InMergeManager; } -void INiagaraModule::UnregisterOnMergeEmitter(FDelegateHandle DelegateHandle) +void INiagaraModule::UnregisterMergeManager(TSharedRef InMergeManager) { - checkf(OnMergeEmitterDelegate.IsBound(), TEXT("OnMergeEmitter is not registered")); - checkf(OnMergeEmitterDelegate.GetHandle() == DelegateHandle, TEXT("Can only unregister the OnMergeEmitter delegate with the handle it was registered with.")); - OnMergeEmitterDelegate.Unbind(); + checkf(MergeManager.IsValid(), TEXT("MergeManager is not registered")); + checkf(MergeManager == InMergeManager, TEXT("Can only unregister the merge manager which was previously registered.")); + MergeManager.Reset(); } UNiagaraScriptSourceBase* INiagaraModule::CreateDefaultScriptSource(UObject* Outer) diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptDerivedData.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptDerivedData.h index a542ae5110ba..2b2e7a3760bf 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptDerivedData.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptDerivedData.h @@ -54,7 +54,7 @@ public: // This is a version string that mimics the old versioning scheme. If you // want to bump this version, generate a new guid using VS->Tools->Create GUID and // return it here. Ex. - return TEXT("FB23DB34150247469E8B70EBE84A30C9"); + return TEXT("B575C44C549E4AE5A86049B662D2EFFA"); } virtual FString GetPluginSpecificCacheKeySuffix() const override; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptExecutionContext.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptExecutionContext.cpp index 4917ec25b185..3f5c1f6697f1 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptExecutionContext.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptExecutionContext.cpp @@ -323,6 +323,11 @@ void FNiagaraGPUSystemTick::Init(FNiagaraSystemInstance* InSystemInstance) FNiagaraComputeInstanceData* Instances = (FNiagaraComputeInstanceData*)(InstanceData_ParamData_Packed); uint8* ParamDataBufferPtr = InstanceData_ParamData_Packed + PackedDispatchesSizeAligned; + int32 TickCount = InSystemInstance->GetTickCount(); + check(TickCount > 0); + bNeedsReset = ( TickCount == 1); + + // Now we will generate instance data for every GPU simulation we want to run on the render thread. // This is spawn rate as well as DataInterface per instance data and the ParameterData for the emitter. // @todo Ideally we would only update DataInterface and ParameterData bits if they have changed. @@ -399,6 +404,8 @@ FNiagaraComputeExecutionContext::FNiagaraComputeExecutionContext() , GPUDebugDataReadbackCounts(nullptr) , GPUDebugDataFloatSize(0) , GPUDebugDataIntSize(0) + , GPUDebugDataFloatStride(0) + , GPUDebugDataIntStride(0) #endif { } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystem.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystem.cpp index 72c2913a45f4..d6ae6514cb64 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystem.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystem.cpp @@ -130,7 +130,7 @@ bool UNiagaraSystem::UsesScript(const UNiagaraScript* Script)const for (FNiagaraEmitterHandle EmitterHandle : GetEmitterHandles()) { - if ((EmitterHandle.GetSource() && EmitterHandle.GetSource()->UsesScript(Script)) || (EmitterHandle.GetInstance() && EmitterHandle.GetInstance()->UsesScript(Script))) + if (EmitterHandle.GetInstance() && EmitterHandle.GetInstance()->UsesScript(Script)) { return true; } @@ -141,11 +141,14 @@ bool UNiagaraSystem::UsesScript(const UNiagaraScript* Script)const bool UNiagaraSystem::UsesEmitter(const UNiagaraEmitter* Emitter) const { - for (FNiagaraEmitterHandle EmitterHandle : GetEmitterHandles()) + if (Emitter != nullptr) { - if (Emitter == EmitterHandle.GetSource() || Emitter == EmitterHandle.GetInstance()) + for (const FNiagaraEmitterHandle& EmitterHandle : GetEmitterHandles()) { - return true; + if (EmitterHandle.UsesEmitter(*Emitter)) + { + return true; + } } } return false; @@ -290,7 +293,6 @@ void UNiagaraSystem::PostLoad() bSystemScriptsAreSynchronized &= SystemScript->AreScriptAndSourceSynchronized(); } - bool bEmitterGraphChangedFromMerge = false; bool bEmitterScriptsAreSynchronized = true; #if 0 @@ -313,21 +315,11 @@ void UNiagaraSystem::PostLoad() for (FNiagaraEmitterHandle& EmitterHandle : EmitterHandles) { - EmitterHandle.ConditionalPostLoad(); - if (EmitterHandle.IsSynchronizedWithSource() == false) + EmitterHandle.ConditionalPostLoad(NiagaraVer); + if (!EmitterHandle.GetInstance()->AreAllScriptAndSourcesSynchronized()) { - INiagaraModule::FMergeEmitterResults Results = MergeChangesForEmitterHandle(EmitterHandle); - if (Results.bSucceeded) - { - bEmitterGraphChangedFromMerge |= Results.bModifiedGraph; - } - } - if (bEmitterScriptsAreSynchronized) - { - if (!EmitterHandle.GetInstance()->AreAllScriptAndSourcesSynchronized()) - { - bEmitterScriptsAreSynchronized = false; - } + bEmitterScriptsAreSynchronized = false; + break; } } @@ -352,11 +344,6 @@ void UNiagaraSystem::PostLoad() UE_LOG(LogNiagara, Log, TEXT("System %s being compiled because there were changes to an emitter script Change ID."), *GetPathName()); } - if (bEmitterGraphChangedFromMerge) - { - UE_LOG(LogNiagara, Log, TEXT("System %s being compiled because graph changes were merged for a base emitter."), *GetPathName()); - } - #if 0 UE_LOG(LogNiagara, Log, TEXT("Before")); for (FNiagaraEmitterHandle& EmitterHandle : EmitterHandles) @@ -375,7 +362,7 @@ void UNiagaraSystem::PostLoad() } #endif - if (bSystemScriptsAreSynchronized == false || bEmitterScriptsAreSynchronized == false || bEmitterGraphChangedFromMerge) + if (bSystemScriptsAreSynchronized == false || bEmitterScriptsAreSynchronized == false) { RequestCompile(false); } @@ -417,39 +404,6 @@ void UNiagaraSystem::SetEditorData(UNiagaraEditorDataBase* InEditorData) EditorData = InEditorData; } -INiagaraModule::FMergeEmitterResults UNiagaraSystem::MergeChangesForEmitterHandle(FNiagaraEmitterHandle& EmitterHandle) -{ - INiagaraModule::FMergeEmitterResults Results = EmitterHandle.MergeSourceChanges(); - if (Results.bSucceeded) - { - UNiagaraEmitter* Instance = EmitterHandle.GetInstance(); - RefreshSystemParametersFromEmitter(EmitterHandle); - if (Instance->bInterpolatedSpawning) - { - Instance->UpdateScriptProps.Script->RapidIterationParameters.CopyParametersTo( - Instance->SpawnScriptProps.Script->RapidIterationParameters, false, FNiagaraParameterStore::EDataInterfaceCopyMethod::None); - } - } - else - { - UE_LOG(LogNiagara, Warning, TEXT("Failed to merge changes for base emitter. System: %s Emitter: %s Error Message: %s"), - *GetPathName(), *EmitterHandle.GetName().ToString(), *Results.GetErrorMessagesString()); - } - return Results; -} - -bool UNiagaraSystem::ReferencesSourceEmitter(UNiagaraEmitter& Emitter) -{ - for (FNiagaraEmitterHandle& Handle : EmitterHandles) - { - if (&Emitter == Handle.GetSource()) - { - return true; - } - } - return false; -} - bool UNiagaraSystem::ReferencesInstanceEmitter(UNiagaraEmitter& Emitter) { for (FNiagaraEmitterHandle& Handle : EmitterHandles) @@ -462,24 +416,6 @@ bool UNiagaraSystem::ReferencesInstanceEmitter(UNiagaraEmitter& Emitter) return false; } -void UNiagaraSystem::UpdateFromEmitterChanges(UNiagaraEmitter& ChangedSourceEmitter, bool bRecompileOnChange) -{ - bool bNeedsCompile = false; - for(FNiagaraEmitterHandle& EmitterHandle : EmitterHandles) - { - if (EmitterHandle.GetSource() == &ChangedSourceEmitter) - { - INiagaraModule::FMergeEmitterResults Results = MergeChangesForEmitterHandle(EmitterHandle); - bNeedsCompile |= Results.bSucceeded && Results.bModifiedGraph; - } - } - - if (bNeedsCompile && bRecompileOnChange) - { - RequestCompile(false); - } -} - void UNiagaraSystem::RefreshSystemParametersFromEmitter(const FNiagaraEmitterHandle& EmitterHandle) { InitEmitterSpawnAttributes(); @@ -618,12 +554,15 @@ bool UNiagaraSystem::IsValid()const } #if WITH_EDITORONLY_DATA -FNiagaraEmitterHandle UNiagaraSystem::AddEmitterHandle(UNiagaraEmitter& SourceEmitter, FName EmitterName) +FNiagaraEmitterHandle UNiagaraSystem::AddEmitterHandle(UNiagaraEmitter& InEmitter, FName EmitterName) { - FNiagaraEmitterHandle EmitterHandle(SourceEmitter, EmitterName, *this); - if (SourceEmitter.bIsTemplateAsset) + UNiagaraEmitter* NewEmitter = UNiagaraEmitter::CreateWithParentAndOwner(InEmitter, this, EmitterName, ~(RF_Public | RF_Standalone)); + FNiagaraEmitterHandle EmitterHandle(*NewEmitter); + if (InEmitter.bIsTemplateAsset) { - EmitterHandle.RemoveSource(); + NewEmitter->bIsTemplateAsset = false; + NewEmitter->TemplateAssetDescription = FText(); + NewEmitter->RemoveParent(); } EmitterHandles.Add(EmitterHandle); RefreshSystemParametersFromEmitter(EmitterHandle); @@ -632,7 +571,9 @@ FNiagaraEmitterHandle UNiagaraSystem::AddEmitterHandle(UNiagaraEmitter& SourceEm FNiagaraEmitterHandle UNiagaraSystem::DuplicateEmitterHandle(const FNiagaraEmitterHandle& EmitterHandleToDuplicate, FName EmitterName) { - FNiagaraEmitterHandle EmitterHandle(EmitterHandleToDuplicate, EmitterName, *this); + UNiagaraEmitter* DuplicateEmitter = UNiagaraEmitter::CreateAsDuplicate(*EmitterHandleToDuplicate.GetInstance(), EmitterName, *this); + FNiagaraEmitterHandle EmitterHandle(*DuplicateEmitter); + EmitterHandle.SetIsEnabled(EmitterHandleToDuplicate.GetIsEnabled()); EmitterHandles.Add(EmitterHandle); RefreshSystemParametersFromEmitter(EmitterHandle); return EmitterHandle; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemInstance.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemInstance.cpp index e8fff6604e00..441b1f5d7ee9 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemInstance.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemInstance.cpp @@ -864,6 +864,8 @@ void FNiagaraSystemInstance::BindParameters() } Component->GetOverrideParameters().Bind(&InstanceParameters); + InstanceParameters.Bind(&SystemSimulation->GetSpawnExecutionContext().Parameters); + InstanceParameters.Bind(&SystemSimulation->GetUpdateExecutionContext().Parameters); if (SystemSimulation->GetIsSolo()) { @@ -896,6 +898,8 @@ void FNiagaraSystemInstance::UnbindParameters() if (SystemSimulation.IsValid()) { + InstanceParameters.Unbind(&SystemSimulation->GetSpawnExecutionContext().Parameters); + InstanceParameters.Unbind(&SystemSimulation->GetUpdateExecutionContext().Parameters); if (SystemSimulation->GetIsSolo()) { if (Component) @@ -1257,7 +1261,7 @@ bool FNiagaraSystemInstance::UsesScript(const UNiagaraScript* Script)const { for (FNiagaraEmitterHandle EmitterHandle : GetSystem()->GetEmitterHandles()) { - if ((EmitterHandle.GetSource() && EmitterHandle.GetSource()->UsesScript(Script)) || (EmitterHandle.GetInstance() && EmitterHandle.GetInstance()->UsesScript(Script))) + if (EmitterHandle.GetInstance() && EmitterHandle.GetInstance()->UsesScript(Script)) { return true; } @@ -1293,6 +1297,8 @@ void FNiagaraSystemInstance::InitEmitters() Component->MarkRenderStateDirty(); } + bHasGPUEmitters = false; + Emitters.Empty(); UNiagaraSystem* System = GetSystem(); if (System != nullptr) diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraWorldManager.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraWorldManager.cpp index 85798167a8a0..d774df31d3b5 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraWorldManager.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraWorldManager.cpp @@ -74,6 +74,11 @@ void FNiagaraWorldManager::AddReferencedObjects(FReferenceCollector& Collector) Collector.AddReferencedObjects(ParameterCollections); } +FString FNiagaraWorldManager::GetReferencerName() const +{ + return TEXT("FNiagaraWorldManager"); +} + UNiagaraParameterCollectionInstance* FNiagaraWorldManager::GetParameterCollection(UNiagaraParameterCollection* Collection) { if (!Collection) diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/INiagaraMergeManager.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/INiagaraMergeManager.h new file mode 100644 index 000000000000..6579074d84b9 --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/INiagaraMergeManager.h @@ -0,0 +1,39 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +class INiagaraMergeManager +{ +public: + struct FMergeEmitterResults + { + FMergeEmitterResults() + : bSucceeded(true) + , bModifiedGraph(false) + { + } + + bool bSucceeded; + TArray ErrorMessages; + bool bModifiedGraph; + UNiagaraEmitter* MergedInstance; + + FString GetErrorMessagesString() const + { + TArray ErrorMessageStrings; + for (FText ErrorMessage : ErrorMessages) + { + ErrorMessageStrings.Add(ErrorMessage.ToString()); + } + return FString::Join(ErrorMessageStrings, TEXT("\n")); + } + }; + + virtual FMergeEmitterResults MergeEmitter(UNiagaraEmitter& Parent, UNiagaraEmitter& ParentAtLastMerge, UNiagaraEmitter& Instance) const = 0; + + virtual void DiffEditableProperties(const void* BaseDataAddress, const void* OtherDataAddress, UStruct& Struct, TArray& OutDifferentProperties) const = 0; + + virtual void CopyPropertiesToBase(void* BaseDataAddress, const void* OtherDataAddress, TArray PropertiesToCopy) const = 0; + + virtual ~INiagaraMergeManager() { } +}; \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponent.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponent.h index dac01f571aac..89db64eef3a3 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponent.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponent.h @@ -47,6 +47,7 @@ public: void SetVectorParameter(FName ParameterName, FVector Param) override; void SetColorParameter(FName ParameterName, FLinearColor Param) override; void SetActorParameter(FName ParameterName, class AActor* Param) override; + virtual UFXSystemAsset* GetFXSystemAsset() const override { return Asset; }; /********* UFXSystemComponent *********/ private: diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraModule.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraModule.h index 3fb2cd22f29c..f080bdd964a7 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraModule.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraModule.h @@ -15,6 +15,7 @@ struct FNiagaraVMExecutableData; class UNiagaraScript; class FNiagaraCompileOptions; class FNiagaraCompileRequestDataBase; +class INiagaraMergeManager; extern NIAGARA_API int32 GEnableVerboseNiagaraChangeIdLogging; @@ -23,38 +24,9 @@ extern NIAGARA_API int32 GEnableVerboseNiagaraChangeIdLogging; */ class NIAGARA_API INiagaraModule : public IModuleInterface { -public: -#if WITH_EDITOR - struct FMergeEmitterResults - { - FMergeEmitterResults() - : bSucceeded(true) - , bModifiedGraph(false) - { - } - - bool bSucceeded; - TArray ErrorMessages; - bool bModifiedGraph; - UNiagaraEmitter* MergedInstance; - - FString GetErrorMessagesString() const - { - TArray ErrorMessageStrings; - for (FText ErrorMessage : ErrorMessages) - { - ErrorMessageStrings.Add(ErrorMessage.ToString()); - } - return FString::Join(ErrorMessageStrings, TEXT("\n")); - } - }; - -#endif - public: #if WITH_EDITOR typedef TSharedPtr CompileRequestPtr; - DECLARE_DELEGATE_RetVal_ThreeParams(FMergeEmitterResults, FOnMergeEmitter, UNiagaraEmitter&, UNiagaraEmitter&, UNiagaraEmitter&); DECLARE_DELEGATE_RetVal_OneParam(class UNiagaraScriptSourceBase*, FOnCreateDefaultScriptSource, UObject*); DECLARE_DELEGATE_RetVal_TwoParams(TSharedPtr, FScriptCompiler,const FNiagaraCompileRequestDataBase*, const FNiagaraCompileOptions&); DECLARE_DELEGATE_RetVal_OneParam(CompileRequestPtr, FOnPrecompile, UObject*); @@ -87,11 +59,11 @@ public: void TickWorld(UWorld* World, ELevelTick TickType, float DeltaSeconds); #if WITH_EDITOR - FMergeEmitterResults MergeEmitter(UNiagaraEmitter& Source, UNiagaraEmitter& LastMergedSource, UNiagaraEmitter& Instance); + const INiagaraMergeManager& GetMergeManager(); - FDelegateHandle RegisterOnMergeEmitter(FOnMergeEmitter OnMergeEmitter); + void RegisterMergeManager(TSharedRef InMergeManager); - void UnregisterOnMergeEmitter(FDelegateHandle DelegateHandle); + void UnregisterMergeManager(TSharedRef InMergeManager); UNiagaraScriptSourceBase* CreateDefaultScriptSource(UObject* Outer); @@ -200,7 +172,7 @@ private: FOnProcessQueue OnProcessQueue; #if WITH_EDITORONLY_DATA - FOnMergeEmitter OnMergeEmitterDelegate; + TSharedPtr MergeManager; FOnCreateDefaultScriptSource OnCreateDefaultScriptSourceDelegate; FScriptCompiler ScriptCompilerDelegate; FOnPrecompile ObjectPrecompilerDelegate; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemInstance.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemInstance.h index c6ccad81384e..1f20510b525f 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemInstance.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemInstance.h @@ -159,6 +159,7 @@ public: FORCEINLINE void SetPendingSpawn(bool bInValue) { bPendingSpawn = bInValue; } FORCEINLINE float GetAge()const { return Age; } + FORCEINLINE int32 GetTickCount() const { return TickCount; } FORCEINLINE TSharedPtr GetSystemSimulation()const { @@ -210,8 +211,8 @@ public: static bool AllocateSystemInstance(class UNiagaraComponent* InComponent, TUniquePtr< FNiagaraSystemInstance >& OutSystemInstanceAllocation); static bool DeallocateSystemInstance(TUniquePtr< FNiagaraSystemInstance >& SystemInstanceAllocation); - /*void SetHasGPUEmitters(bool bInHasGPUEmitters) { bHasGPUEmitters = bInHasGPUEmitters; } - bool HasGPUEmitters() { return bHasGPUEmitters; }*/ + /*void SetHasGPUEmitters(bool bInHasGPUEmitters) { bHasGPUEmitters = bInHasGPUEmitters; }*/ + bool HasGPUEmitters() { return bHasGPUEmitters; } int32 GetDetailLevel()const; private: diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraTypes.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraTypes.h index ffab566a5102..f4bf8cf3bc2e 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraTypes.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraTypes.h @@ -322,6 +322,7 @@ public: , EditorSortPriority(0) , bInlineEditConditionToggle(false) , bIsStaticSwitch(false) + , StaticSwitchDefaultValue(0) { } public: @@ -340,7 +341,7 @@ public: /** Declares the associated input is used as an inline edit condition toggle, so it should be hidden and edited as a checkbox inline with the input which was designated as its edit condition. */ - UPROPERTY(EditAnywhere, Category = "Variable", meta = (EditCondition = "!bIsStaticSwitch")) + UPROPERTY(EditAnywhere, Category = "Variable") bool bInlineEditConditionToggle; /** Declares the associated input should be conditionally editable based on the value of another input. */ @@ -356,6 +357,10 @@ public: UPROPERTY(AdvancedDisplay, VisibleAnywhere, Category = "Variable") bool bIsStaticSwitch; + + /** The default value to use when creating new pins or stack entries for a static switch parameter */ + UPROPERTY() + int32 StaticSwitchDefaultValue; }; USTRUCT() diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraWorldManager.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraWorldManager.h index e010ed90a132..58a8df4082ee 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraWorldManager.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraWorldManager.h @@ -67,7 +67,8 @@ public: static FNiagaraWorldManager* Get(UWorld* World); //~ GCObject Interface - void AddReferencedObjects(FReferenceCollector& Collector); + virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + virtual FString GetReferencerName() const override; //~ GCObject Interface UNiagaraParameterCollectionInstance* GetParameterCollection(UNiagaraParameterCollection* Collection); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/NiagaraAnimNotifies.Build.cs b/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/NiagaraAnimNotifies.Build.cs new file mode 100644 index 000000000000..6eeca3f78aa2 --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/NiagaraAnimNotifies.Build.cs @@ -0,0 +1,16 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class NiagaraAnimNotifies : ModuleRules +{ + public NiagaraAnimNotifies(ReadOnlyTargetRules Target) : base(Target) + { + PrivateIncludePaths.AddRange( + new string[] { + "NiagaraAnimNotifies/Private", + }); + + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "Niagara" }); + } +} diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Private/AnimNotifyState_TimedNiagaraEffect.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Private/AnimNotifyState_TimedNiagaraEffect.cpp new file mode 100644 index 000000000000..804884e097ed --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Private/AnimNotifyState_TimedNiagaraEffect.cpp @@ -0,0 +1,131 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "AnimNotifyState_TimedNiagaraEffect.h" + +#include "Components/SkeletalMeshComponent.h" +#include "Kismet/GameplayStatics.h" + +#include "NiagaraComponent.h" +#include "NiagaraSystem.h" +#include "NiagaraFunctionLibrary.h" + +UAnimNotifyState_TimedNiagaraEffect::UAnimNotifyState_TimedNiagaraEffect(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + Template = nullptr; + LocationOffset.Set(0.0f, 0.0f, 0.0f); + RotationOffset = FRotator(0.0f, 0.0f, 0.0f); +} + +UFXSystemComponent* UAnimNotifyState_TimedNiagaraEffect::SpawnEffect(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) +{ + // Only spawn if we've got valid params + if (ValidateParameters(MeshComp)) + { + UFXSystemComponent* NewComponent = UNiagaraFunctionLibrary::SpawnSystemAttached(Template, MeshComp, SocketName, LocationOffset, RotationOffset, EAttachLocation::KeepRelativeOffset, !bDestroyAtEnd); + return NewComponent; + } + return nullptr; +} + +void UAnimNotifyState_TimedNiagaraEffect::NotifyBegin(USkeletalMeshComponent * MeshComp, class UAnimSequenceBase * Animation, float TotalDuration) +{ + SpawnEffect(MeshComp, Animation); + Received_NotifyBegin(MeshComp, Animation, TotalDuration); +} + +void UAnimNotifyState_TimedNiagaraEffect::NotifyTick(USkeletalMeshComponent * MeshComp, class UAnimSequenceBase * Animation, float FrameDeltaTime) +{ + Received_NotifyTick(MeshComp, Animation, FrameDeltaTime); +} + +void UAnimNotifyState_TimedNiagaraEffect::NotifyEnd(USkeletalMeshComponent * MeshComp, class UAnimSequenceBase * Animation) +{ + TArray Children; + MeshComp->GetChildrenComponents(false, Children); + + for (USceneComponent* Component : Children) + { + if (UFXSystemComponent* FXSystemComponent = Cast(Component)) + { + bool bSocketMatch = FXSystemComponent->GetAttachSocketName() == SocketName; + bool bTemplateMatch = FXSystemComponent->GetFXSystemAsset() == Template; + +#if WITH_EDITORONLY_DATA + // In editor someone might have changed our parameters while we're ticking; so check + // previous known parameters too. + bSocketMatch |= PreviousSocketNames.Contains(FXSystemComponent->GetAttachSocketName()); + bTemplateMatch |= PreviousTemplates.Contains(FXSystemComponent->GetFXSystemAsset()); +#endif + + if (bSocketMatch && bTemplateMatch && FXSystemComponent->bIsActive) + { + // Either destroy the component or deactivate it to have it's active FXSystems finish. + // The component will auto destroy once all FXSystem are gone. + if (bDestroyAtEnd) + { + FXSystemComponent->DestroyComponent(); + } + else + { + FXSystemComponent->Deactivate(); + } + +#if WITH_EDITORONLY_DATA + // No longer need to track previous values as we've found our component + // and removed it. + PreviousTemplates.Empty(); + PreviousSocketNames.Empty(); +#endif + // Removed a component, no need to continue + break; + } + } + } + + Received_NotifyEnd(MeshComp, Animation); +} + +bool UAnimNotifyState_TimedNiagaraEffect::ValidateParameters(USkeletalMeshComponent* MeshComp) +{ + bool bValid = true; + + if (!Template) + { + bValid = false; + } + else if (!MeshComp->DoesSocketExist(SocketName) && MeshComp->GetBoneIndex(SocketName) == INDEX_NONE) + { + bValid = false; + } + + return bValid; +} + +FString UAnimNotifyState_TimedNiagaraEffect::GetNotifyName_Implementation() const +{ + if (Template) + { + return Template->GetName(); + } + + return UAnimNotifyState::GetNotifyName_Implementation(); +} + +#if WITH_EDITOR +void UAnimNotifyState_TimedNiagaraEffect::PreEditChange(UProperty* PropertyAboutToChange) +{ + if (PropertyAboutToChange) + { + if (PropertyAboutToChange->GetName() == GET_MEMBER_NAME_STRING_CHECKED(UAnimNotifyState_TimedNiagaraEffect, Template) && Template != NULL) + { + PreviousTemplates.Add(Template); + } + + if (PropertyAboutToChange->GetName() == GET_MEMBER_NAME_STRING_CHECKED(UAnimNotifyState_TimedNiagaraEffect, SocketName) && SocketName != FName(TEXT("None"))) + { + PreviousSocketNames.Add(SocketName); + } + } +} +#endif diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Private/AnimNotify_PlayNiagaraEffect.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Private/AnimNotify_PlayNiagaraEffect.cpp new file mode 100644 index 000000000000..9a8b7be45034 --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Private/AnimNotify_PlayNiagaraEffect.cpp @@ -0,0 +1,119 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "AnimNotify_PlayNiagaraEffect.h" + +#include "Components/SkeletalMeshComponent.h" +#include "Kismet/GameplayStatics.h" +#include "Animation/AnimSequenceBase.h" + +#if WITH_EDITOR +#include "Logging/MessageLog.h" +#include "Misc/UObjectToken.h" +#endif + +#include "NiagaraComponent.h" +#include "NiagaraSystem.h" +#include "NiagaraFunctionLibrary.h" + +///////////////////////////////////////////////////// +// UAnimNotify_PlayNiagaraEffect + +UAnimNotify_PlayNiagaraEffect::UAnimNotify_PlayNiagaraEffect() + : Super() +{ + Attached = true; + Scale = FVector(1.f); + +#if WITH_EDITORONLY_DATA + NotifyColor = FColor(192, 255, 99, 255); +#endif // WITH_EDITORONLY_DATA +} + +void UAnimNotify_PlayNiagaraEffect::PostLoad() +{ + Super::PostLoad(); + + RotationOffsetQuat = FQuat(RotationOffset); +} + +#if WITH_EDITOR +void UAnimNotify_PlayNiagaraEffect::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + if (PropertyChangedEvent.MemberProperty && PropertyChangedEvent.MemberProperty->GetFName() == GET_MEMBER_NAME_CHECKED(UAnimNotify_PlayNiagaraEffect, RotationOffset)) + { + RotationOffsetQuat = FQuat(RotationOffset); + } +} + +void UAnimNotify_PlayNiagaraEffect::ValidateAssociatedAssets() +{ + static const FName NAME_AssetCheck("AssetCheck"); + + if ((Template != nullptr) && (Template->IsLooping())) + { + UObject* ContainingAsset = GetContainingAsset(); + + FMessageLog AssetCheckLog(NAME_AssetCheck); + + const FText MessageLooping = FText::Format( + NSLOCTEXT("AnimNotify", "NiagaraSystem_ShouldNotLoop", "Niagara system {0} used in anim notify for asset {1} is set to looping, but the slot is a one-shot (it won't be played to avoid leaking a component per notify)."), + FText::AsCultureInvariant(Template->GetPathName()), + FText::AsCultureInvariant(ContainingAsset->GetPathName())); + AssetCheckLog.Warning() + ->AddToken(FUObjectToken::Create(ContainingAsset)) + ->AddToken(FTextToken::Create(MessageLooping)); + + if (GIsEditor) + { + AssetCheckLog.Notify(MessageLooping, EMessageSeverity::Warning, /*bForce=*/ true); + } + } +} +#endif + +void UAnimNotify_PlayNiagaraEffect::Notify(class USkeletalMeshComponent* MeshComp, class UAnimSequenceBase* Animation) +{ + // Don't call super to avoid unnecessary call in to blueprints + SpawnEffect(MeshComp, Animation); +} + +FString UAnimNotify_PlayNiagaraEffect::GetNotifyName_Implementation() const +{ + if (Template) + { + return Template->GetName(); + } + else + { + return Super::GetNotifyName_Implementation(); + } +} + +UFXSystemComponent* UAnimNotify_PlayNiagaraEffect::SpawnEffect(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) +{ + UFXSystemComponent* ReturnComp = nullptr; + + if (Template) + { + if (Template->IsLooping()) + { + return ReturnComp; + } + + if (Attached) + { + ReturnComp = UNiagaraFunctionLibrary::SpawnSystemAttached(Template, MeshComp, SocketName, LocationOffset, RotationOffset, EAttachLocation::KeepRelativeOffset, true); + } + else + { + const FTransform MeshTransform = MeshComp->GetSocketTransform(SocketName); + ReturnComp = UNiagaraFunctionLibrary::SpawnSystemAtLocation(MeshComp->GetWorld(), Template, MeshTransform.TransformPosition(LocationOffset), (MeshTransform.GetRotation() * RotationOffsetQuat).Rotator(), true); + } + + ReturnComp->RelativeScale3D = Scale; + } + + return ReturnComp; +} diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Private/NiagaraAnimNotifiesModule.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Private/NiagaraAnimNotifiesModule.cpp new file mode 100644 index 000000000000..678836529ee9 --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Private/NiagaraAnimNotifiesModule.cpp @@ -0,0 +1,6 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "NiagaraAnimNotifiesModule.h" +#include "Modules/ModuleManager.h" + +IMPLEMENT_MODULE(INiagaraAnimNotifiesModule, NiagaraAnimNotifies); \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Public/AnimNotifyState_TimedNiagaraEffect.h b/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Public/AnimNotifyState_TimedNiagaraEffect.h new file mode 100644 index 000000000000..5f951b7305cc --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Public/AnimNotifyState_TimedNiagaraEffect.h @@ -0,0 +1,74 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Animation/AnimNotifies/AnimNotifyState.h" +#include "AnimNotifyState_TimedNiagaraEffect.generated.h" + +class UNiagaraSystem; +class USkeletalMeshComponent; +class UFXSystemAsset; +class UFXSystemComponent; + +// Timed Niagara Effect Notify +// Allows a looping Niagara effect to be played in an animation that will activate +// at the beginning of the notify and deactivate at the end. +UCLASS(Blueprintable, meta = (DisplayName = "Timed Niagara Effect")) +class NIAGARAANIMNOTIFIES_API UAnimNotifyState_TimedNiagaraEffect : public UAnimNotifyState +{ + GENERATED_UCLASS_BODY() + + // The niagara system template to use when spawning the niagara component + UPROPERTY(EditAnywhere, Category = NiagaraSystem, meta = (ToolTip = "The niagara system to spawn for the notify state")) + UNiagaraSystem* Template; + + // The socket within our mesh component to attach to when we spawn the Niagara component + UPROPERTY(EditAnywhere, Category = NiagaraSystem, meta = (ToolTip = "The socket or bone to attach the system to", AnimNotifyBoneName = "true")) + FName SocketName; + + // Offset from the socket / bone location + UPROPERTY(EditAnywhere, Category = NiagaraSystem, meta = (ToolTip = "Offset from the socket or bone to place the Niagara system")) + FVector LocationOffset; + + // Offset from the socket / bone rotation + UPROPERTY(EditAnywhere, Category = NiagaraSystem, meta = (ToolTip = "Rotation offset from the socket or bone for the Niagara system")) + FRotator RotationOffset; + + // Whether or not we destroy the component at the end of the notify or instead just stop + // the emitters. + UPROPERTY(EditAnywhere, Category = NiagaraSystem, meta = (DisplayName = "Destroy Immediately", ToolTip = "Whether the Niagara system should be immediately destroyed at the end of the notify state or be allowed to finish")) + bool bDestroyAtEnd; + +#if WITH_EDITORONLY_DATA + // The following arrays are used to handle property changes during a state. Because we can't + // store any stateful data here we can't know which emitter is ours. The best metric we have + // is an emitter on our Mesh Component with the same template and socket name we have defined. + // Because these can change at any time we need to track previous versions when we are in an + // editor build. Refactor when stateful data is possible, tracking our component instead. + UPROPERTY(transient) + TArray PreviousTemplates; + + UPROPERTY(transient) + TArray PreviousSocketNames; + +#endif + +#if WITH_EDITOR + virtual void PreEditChange(UProperty* PropertyAboutToChange) override; +#endif + + virtual void NotifyBegin(class USkeletalMeshComponent * MeshComp, class UAnimSequenceBase * Animation, float TotalDuration) override; + virtual void NotifyTick(class USkeletalMeshComponent * MeshComp, class UAnimSequenceBase * Animation, float FrameDeltaTime) override; + virtual void NotifyEnd(class USkeletalMeshComponent * MeshComp, class UAnimSequenceBase * Animation) override; + + // Overridden from UAnimNotifyState to provide custom notify name. + FString GetNotifyName_Implementation() const override; + +protected: + // Spawns the NiagaraSystemComponent. Called from Notify. + virtual UFXSystemComponent* SpawnEffect(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation); +private: + bool ValidateParameters(USkeletalMeshComponent* MeshComp); +}; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Public/AnimNotify_PlayNiagaraEffect.h b/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Public/AnimNotify_PlayNiagaraEffect.h new file mode 100644 index 000000000000..62e2e5c2665e --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Public/AnimNotify_PlayNiagaraEffect.h @@ -0,0 +1,77 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Animation/AnimNotifies/AnimNotify.h" +#include "AnimNotify_PlayNiagaraEffect.generated.h" + +class UAnimSequenceBase; +class UNiagaraSystem; +class USkeletalMeshComponent; +class UNiagaraSystemComponent; + +class UNiagaraSystem; +class UFXSystemComponent; + +UCLASS(const, hidecategories = Object, collapsecategories, meta = (DisplayName = "Play Niagara Particle Effect")) +class NIAGARAANIMNOTIFIES_API UAnimNotify_PlayNiagaraEffect : public UAnimNotify +{ + GENERATED_BODY() + +public: + + UAnimNotify_PlayNiagaraEffect(); + + // Begin UObject interface + virtual void PostLoad() override; +#if WITH_EDITOR + virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + // End UObject interface + + // Begin UAnimNotify interface + virtual FString GetNotifyName_Implementation() const override; + virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override; +#if WITH_EDITOR + virtual void ValidateAssociatedAssets() override; +#endif + // End UAnimNotify interface + + // Niagara System to Spawn + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AnimNotify", meta = (DisplayName = "Niagara System")) + UNiagaraSystem* Template; + + // Location offset from the socket + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AnimNotify") + FVector LocationOffset; + + // Rotation offset from socket + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AnimNotify") + FRotator RotationOffset; + + // Scale to spawn the Niagara system at + UPROPERTY(EditAnywhere, Category = "AnimNotify") + FVector Scale; + +protected: + // Cached version of the Rotation Offset already in Quat form + FQuat RotationOffsetQuat; + + // Spawns the NiagaraSystemComponent. Called from Notify. + virtual UFXSystemComponent* SpawnEffect(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation); + +public: + + // Should attach to the bone/socket + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AnimNotify") + uint32 Attached : 1; //~ Does not follow coding standard due to redirection from BP + + // SocketName to attach to + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AnimNotify", meta = (AnimNotifyBoneName = "true")) + FName SocketName; +}; + + + diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Public/NiagaraAnimNotifiesModule.h b/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Public/NiagaraAnimNotifiesModule.h new file mode 100644 index 000000000000..95da74b8d82a --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraAnimNotifies/Public/NiagaraAnimNotifiesModule.h @@ -0,0 +1,14 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" + +/** +* Niagara AnimNotifies module interface +*/ +class INiagaraAnimNotifiesModule : public IModuleInterface +{ +}; + diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Private/NiagaraCustomVersion.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Private/NiagaraCustomVersion.cpp index 117d3d805d1a..76f6e82e211a 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Private/NiagaraCustomVersion.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Private/NiagaraCustomVersion.cpp @@ -8,4 +8,4 @@ const FGuid FNiagaraCustomVersion::GUID(0xFCF57AFA, 0x50764283, 0xB9A9E658, 0xFF // Register the custom version with core FCustomVersionRegistration GRegisterNiagaraCustomVersion(FNiagaraCustomVersion::GUID, FNiagaraCustomVersion::LatestVersion, TEXT("NiagaraVer")); -const FGuid FNiagaraCustomVersion::LatestScriptCompileVersion(0x4182078E, 0x4D784761, 0xB4205380, 0x84A48D4E); +const FGuid FNiagaraCustomVersion::LatestScriptCompileVersion(0x8D38659E, 0xFF5CC945, 0xA718F14F, 0x52E92EBA); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Public/NiagaraCustomVersion.h b/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Public/NiagaraCustomVersion.h index 420539e93030..a1fa4e6c7a0b 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Public/NiagaraCustomVersion.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Public/NiagaraCustomVersion.h @@ -119,6 +119,8 @@ struct FNiagaraCustomVersion UseHashesToIdentifyCompileStateOfTopLevelScripts, // Move to using the traversed graph hash and the base script id for the FNiagaraVMExecutableDataId instead of the change id guid to prevent invalidating the DDC. MetaDataAndParametersUpdate, // Reworked how the metadata is stored in NiagaraGraph from storing a Map of FNiagaraVariableMetaData to storing a map of UNiagaraScriptVariable* to be used with the Details panel. + + MoveInheritanceDataFromTheEmitterHandleToTheEmitter, // Moved the emitter inheritance data from the emitter handle to the emitter to allow for chained emitter inheritance. // ------------------------------------------------------ VersionPlusOne, diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraEventScriptPropertiesCustomization.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraEventScriptPropertiesCustomization.cpp index f590439e23e2..081f65763846 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraEventScriptPropertiesCustomization.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraEventScriptPropertiesCustomization.cpp @@ -339,9 +339,9 @@ TSharedRef FNiagaraEventScriptPropertiesCustomization::OnGetMenuContent SNew(SBox) [ SNew(SGraphActionMenu) - .OnActionSelected(this, &FNiagaraEventScriptPropertiesCustomization::OnActionSelected) - .OnCreateWidgetForAction(SGraphActionMenu::FOnCreateWidgetForAction::CreateSP(this, &FNiagaraEventScriptPropertiesCustomization::OnCreateWidgetForAction)) - .OnCollectAllActions(this, &FNiagaraEventScriptPropertiesCustomization::CollectAllActions) + .OnActionSelected(const_cast(this), &FNiagaraEventScriptPropertiesCustomization::OnActionSelected) + .OnCreateWidgetForAction(SGraphActionMenu::FOnCreateWidgetForAction::CreateSP(const_cast(this), &FNiagaraEventScriptPropertiesCustomization::OnCreateWidgetForAction)) + .OnCollectAllActions(const_cast(this), &FNiagaraEventScriptPropertiesCustomization::CollectAllActions) .AutoExpandActionMenu(false) .ShowFilterTextBox(true) ] diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraMetaDataCustomNodeBuilder.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraMetaDataCustomNodeBuilder.cpp index de06d0a0df5a..a05b664d4498 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraMetaDataCustomNodeBuilder.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraMetaDataCustomNodeBuilder.cpp @@ -86,7 +86,7 @@ void FNiagaraMetaDataCustomNodeBuilder::GenerateChildContent(IDetailChildrenBuil { const FNiagaraVariable& ParameterVariableA = MetaDataRowDataA.Get<0>(); const FNiagaraVariable& ParameterVariableB = MetaDataRowDataB.Get<0>(); - return ParameterVariableA.GetName() < ParameterVariableB.GetName(); + return FNameLexicalLess()(ParameterVariableA.GetName(), ParameterVariableB.GetName()); } }); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraStaticSwitchNodeDetails.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraStaticSwitchNodeDetails.cpp index 4d5c7f49ab82..56e225186d0d 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraStaticSwitchNodeDetails.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraStaticSwitchNodeDetails.cpp @@ -12,6 +12,8 @@ #include "Widgets/Input/SComboBox.h" #include "Widgets/Input/SNumericEntryBox.h" #include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Layout/SWidgetSwitcher.h" #define LOCTEXT_NAMESPACE "NiagaraStaticSwitchNodeDetails" @@ -48,6 +50,9 @@ void FNiagaraStaticSwitchNodeDetails::CustomizeDetails(IDetailLayoutBuilder& Det IDetailCategoryBuilder& CategoryBuilder = DetailBuilder.EditCategory(SwitchCategoryName); FDetailWidgetRow& NameWidget = CategoryBuilder.AddCustomRow(LOCTEXT("NiagaraSwitchNodeNameFilterText", "Input parameter name")); + FDetailWidgetRow& DropdownWidget = CategoryBuilder.AddCustomRow(LOCTEXT("NiagaraSwitchNodeTypeFilterText", "Input parameter type")); + FDetailWidgetRow& IntValueOption = CategoryBuilder.AddCustomRow(LOCTEXT("NiagaraSwitchNodeIntFilterText", "Max integer value")); + FDetailWidgetRow& DefaultValueOption = CategoryBuilder.AddCustomRow(LOCTEXT("NiagaraSwitchNodeDefaultFilterText", "Default value")); NameWidget .NameContent() @@ -74,8 +79,6 @@ void FNiagaraStaticSwitchNodeDetails::CustomizeDetails(IDetailLayoutBuilder& Det ] ]; - FDetailWidgetRow& DropdownWidget = CategoryBuilder.AddCustomRow(LOCTEXT("NiagaraSwitchNodeTypeFilterText", "Input parameter type")); - DropdownWidget .NameContent() [ @@ -108,8 +111,6 @@ void FNiagaraStaticSwitchNodeDetails::CustomizeDetails(IDetailLayoutBuilder& Det ] ]; - FDetailWidgetRow& IntValueOption = CategoryBuilder.AddCustomRow(LOCTEXT("NiagaraSwitchNodeIntFilterText", "Max integer value")); - IntValueOption .NameContent() [ @@ -136,6 +137,96 @@ void FNiagaraStaticSwitchNodeDetails::CustomizeDetails(IDetailLayoutBuilder& Det .OnValueCommitted(this, &FNiagaraStaticSwitchNodeDetails::IntOptionValueCommitted) ] ]; + + DefaultValueOption + .NameContent() + [ + SNew(SBox) + .Padding(FMargin(0.0f, 2.0f)) + [ + SNew(STextBlock) + .TextStyle(FNiagaraEditorStyle::Get(), "NiagaraEditor.ParameterText") + .Text(LOCTEXT("NiagaraSwitchNodeDefaultOptionText", "Default value")) + ] + ] + .ValueContent() + [ + SNew(SBox) + .Padding(FMargin(0.0f, 2.0f)) + [ + SNew(SWidgetSwitcher) + .WidgetIndex(this, &FNiagaraStaticSwitchNodeDetails::GetDefaultWidgetIndex) + + + SWidgetSwitcher::Slot() + [ + SNew(SCheckBox) + .IsChecked_Lambda([this]() { return GetSwitchDefaultValue().Get(0) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) + .OnCheckStateChanged(this, &FNiagaraStaticSwitchNodeDetails::DefaultBoolValueCommitted) + ] + + + SWidgetSwitcher::Slot() + [ + SNew(SNumericEntryBox) + .AllowSpin(false) + .MinValue(0) + .MaxValue(99) + .Value(this, &FNiagaraStaticSwitchNodeDetails::GetSwitchDefaultValue) + .OnValueCommitted(this, &FNiagaraStaticSwitchNodeDetails::DefaultIntValueCommitted) + ] + + + SWidgetSwitcher::Slot() + [ + SNew(SComboBox>) + .OptionsSource(&DefaultEnumDropdownOptions) + .OnSelectionChanged(this, &FNiagaraStaticSwitchNodeDetails::OnSelectionChanged) + .OnGenerateWidget(this, &FNiagaraStaticSwitchNodeDetails::CreateWidgetForDropdownOption) + .InitiallySelectedItem(SelectedDefaultValue) + [ + SNew(STextBlock) + .Margin(FMargin(0.0f, 2.0f)) + .Text(this, &FNiagaraStaticSwitchNodeDetails::GetDefaultSelectionItemLabel) + ] + ] + ] + ]; + + RefreshDefaultDropdownValues(); + } +} + +int32 FNiagaraStaticSwitchNodeDetails::GetDefaultWidgetIndex() const +{ + if (!Node.IsValid()) + { + return 0; + } + ENiagaraStaticSwitchType Type = Node->SwitchTypeData.SwitchType; + return Type == ENiagaraStaticSwitchType::Bool ? 0 : (Type == ENiagaraStaticSwitchType::Integer ? 1 : 2); +} + +TOptional FNiagaraStaticSwitchNodeDetails::GetSwitchDefaultValue() const +{ + TOptional MetaData = GetSwitchParameterMetadata(); + return MetaData.IsSet() ? TOptional(MetaData->StaticSwitchDefaultValue) : TOptional(); +} + +void FNiagaraStaticSwitchNodeDetails::DefaultIntValueCommitted(int32 Value, ETextCommit::Type CommitInfo) +{ + TOptional MetaData = GetSwitchParameterMetadata(); + if (MetaData.IsSet()) + { + MetaData->StaticSwitchDefaultValue = Value; + SetSwitchParameterMetadata(MetaData.GetValue()); + } +} + +void FNiagaraStaticSwitchNodeDetails::DefaultBoolValueCommitted(ECheckBoxState NewState) +{ + TOptional MetaData = GetSwitchParameterMetadata(); + if (MetaData.IsSet()) + { + MetaData->StaticSwitchDefaultValue = (NewState == ECheckBoxState::Checked) ? 1 : 0; + SetSwitchParameterMetadata(MetaData.GetValue()); } } @@ -144,6 +235,30 @@ TSharedRef FNiagaraStaticSwitchNodeDetails::CreateWidgetForDropdownOpti return SNew(STextBlock).Text(FText::FromString(*InOption->Name)); } +TSharedRef FNiagaraStaticSwitchNodeDetails::CreateWidgetForDropdownOption(TSharedPtr InOption) +{ + return SNew(STextBlock).Text(InOption->DisplayName); +} + +void FNiagaraStaticSwitchNodeDetails::OnSelectionChanged(TSharedPtr NewValue, ESelectInfo::Type) +{ + SelectedDefaultValue = NewValue; + TOptional MetaData = GetSwitchParameterMetadata(); + if (!SelectedDefaultValue.IsValid() || !MetaData.IsSet()) + { + return; + } + + UEnum* Enum = Node->SwitchTypeData.Enum; + if (!Enum) + { + return; + } + + MetaData->StaticSwitchDefaultValue = SelectedDefaultValue->EnumIndex; + SetSwitchParameterMetadata(MetaData.GetValue()); +} + void FNiagaraStaticSwitchNodeDetails::OnSelectionChanged(TSharedPtr NewValue, ESelectInfo::Type) { SelectedDropdownItem = NewValue; @@ -156,10 +271,12 @@ void FNiagaraStaticSwitchNodeDetails::OnSelectionChanged(TSharedPtrSwitchTypeData.SwitchType = ENiagaraStaticSwitchType::Bool; + Node->SwitchTypeData.Enum = nullptr; } else if (SelectedDropdownItem == DropdownOptions[1]) { Node->SwitchTypeData.SwitchType = ENiagaraStaticSwitchType::Integer; + Node->SwitchTypeData.Enum = nullptr; } else { @@ -167,6 +284,7 @@ void FNiagaraStaticSwitchNodeDetails::OnSelectionChanged(TSharedPtrSwitchTypeData.Enum = SelectedDropdownItem->Enum; } Node->OnSwitchParameterTypeChanged(OldType); + RefreshDefaultDropdownValues(); } FText FNiagaraStaticSwitchNodeDetails::GetDropdownItemLabel() const @@ -179,6 +297,65 @@ FText FNiagaraStaticSwitchNodeDetails::GetDropdownItemLabel() const return LOCTEXT("InvalidNiagaraStaticSwitchNodeComboEntryText", ""); } +FText FNiagaraStaticSwitchNodeDetails::GetDefaultSelectionItemLabel() const +{ + if (SelectedDefaultValue.IsValid()) + { + return SelectedDefaultValue->DisplayName; + } + + return LOCTEXT("InvalidNiagaraStaticSwitchNodeComboEntryText", ""); +} + +void FNiagaraStaticSwitchNodeDetails::RefreshDefaultDropdownValues() +{ + if (!Node.IsValid() || Node->SwitchTypeData.SwitchType != ENiagaraStaticSwitchType::Enum) + { + return; + } + TOptional MetaData = GetSwitchParameterMetadata(); + + DefaultEnumDropdownOptions.Empty(); + UEnum* Enum = Node->SwitchTypeData.Enum; + if (Enum) + { + SelectedDefaultValue.Reset(); + for (int i = 0; i < Enum->GetMaxEnumValue() - 1; i++) + { + FText DisplayName = Enum->GetDisplayNameTextByIndex(i); + DefaultEnumDropdownOptions.Add(MakeShared(DisplayName, i)); + + if (MetaData.IsSet() && i == MetaData->StaticSwitchDefaultValue) + { + SelectedDefaultValue = DefaultEnumDropdownOptions[i]; + } + } + if (!SelectedDefaultValue.IsValid() && DefaultEnumDropdownOptions.Num() > 0) + { + SelectedDefaultValue = DefaultEnumDropdownOptions[0]; + } + } +} + +TOptional FNiagaraStaticSwitchNodeDetails::GetSwitchParameterMetadata() const +{ + if (!Node.IsValid() || !Node->GetNiagaraGraph()) + { + TOptional Empty; + return Empty; + } + return Node->GetNiagaraGraph()->GetMetaData(FNiagaraVariable(Node->GetInputType(), Node->InputParameterName)); +} + +void FNiagaraStaticSwitchNodeDetails::SetSwitchParameterMetadata(const FNiagaraVariableMetaData& MetaData) +{ + if (!Node.IsValid() || !Node->GetNiagaraGraph()) + { + return; + } + Node->GetNiagaraGraph()->SetMetaData(FNiagaraVariable(Node->GetInputType(), Node->InputParameterName), MetaData); +} + void FNiagaraStaticSwitchNodeDetails::UpdateSelectionFromNode() { SelectedDropdownItem.Reset(); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraStaticSwitchNodeDetails.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraStaticSwitchNodeDetails.h index f379be5abe23..c64e2ead0d40 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraStaticSwitchNodeDetails.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraStaticSwitchNodeDetails.h @@ -6,6 +6,7 @@ #include "IDetailCustomization.h" #include "DetailLayoutBuilder.h" #include "Misc/Optional.h" +#include "NiagaraTypes.h" // This data structure is used internally by the dropdown to keep track of the user's choice struct SwitchDropdownOption @@ -20,6 +21,21 @@ struct SwitchDropdownOption {} }; +// This data structure is used internally by the default enum dropdown to keep track of the user's choice +struct DefaultEnumOption +{ + FText DisplayName; + int32 EnumIndex; + + DefaultEnumOption(FText DisplayName) : DisplayName(DisplayName), EnumIndex(0) + { + } + + DefaultEnumOption(FText DisplayName, int32 EnumIndex) : DisplayName(DisplayName), EnumIndex(EnumIndex) + { + } +}; + /** This customization sets up a custom details panel for the static switch node in the niagara module graph. */ class FNiagaraStaticSwitchNodeDetails : public IDetailCustomization { @@ -34,6 +50,7 @@ public: virtual void CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) override; private: + // enum dropdown functions TSharedRef CreateWidgetForDropdownOption(TSharedPtr InOption); void OnSelectionChanged(TSharedPtr NewValue, ESelectInfo::Type); FText GetDropdownItemLabel() const; @@ -48,7 +65,22 @@ private: FText GetParameterNameText() const; void OnParameterNameCommited(const FText& InText, ETextCommit::Type InCommitType); + // default value option functions + int32 GetDefaultWidgetIndex() const; + TOptional GetSwitchDefaultValue() const; + void DefaultIntValueCommitted(int32 Value, ETextCommit::Type CommitInfo); + void DefaultBoolValueCommitted(ECheckBoxState NewState); + TSharedRef CreateWidgetForDropdownOption(TSharedPtr InOption); + void OnSelectionChanged(TSharedPtr NewValue, ESelectInfo::Type); + FText GetDefaultSelectionItemLabel() const; + void RefreshDefaultDropdownValues(); + + TOptional GetSwitchParameterMetadata() const; + void SetSwitchParameterMetadata(const FNiagaraVariableMetaData& MetaData); + TWeakObjectPtr Node; TArray> DropdownOptions; TSharedPtr SelectedDropdownItem; + TArray> DefaultEnumDropdownOptions; + TSharedPtr SelectedDefaultValue; }; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraTypeCustomizations.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraTypeCustomizations.cpp index 6f0c3a88ff4e..28fd67e19c1d 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraTypeCustomizations.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraTypeCustomizations.cpp @@ -142,9 +142,9 @@ TSharedRef FNiagaraVariableAttributeBindingCustomization::OnGetMenuCont SNew(SBox) [ SNew(SGraphActionMenu) - .OnActionSelected(this, &FNiagaraVariableAttributeBindingCustomization::OnActionSelected) - .OnCreateWidgetForAction(SGraphActionMenu::FOnCreateWidgetForAction::CreateSP(this, &FNiagaraVariableAttributeBindingCustomization::OnCreateWidgetForAction)) - .OnCollectAllActions(this, &FNiagaraVariableAttributeBindingCustomization::CollectAllActions) + .OnActionSelected(const_cast(this), &FNiagaraVariableAttributeBindingCustomization::OnActionSelected) + .OnCreateWidgetForAction(SGraphActionMenu::FOnCreateWidgetForAction::CreateSP(const_cast(this), &FNiagaraVariableAttributeBindingCustomization::OnCreateWidgetForAction)) + .OnCollectAllActions(const_cast(this), &FNiagaraVariableAttributeBindingCustomization::CollectAllActions) .AutoExpandActionMenu(false) .ShowFilterTextBox(true) ] diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorModule.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorModule.cpp index f639b44cf1a0..02450f074eaf 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorModule.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorModule.cpp @@ -277,6 +277,34 @@ void DumpRapidIterationParamersForAsset(const TArray& Arguments) } } +void CompileEmitterStandAlone(UNiagaraEmitter* Emitter, TSet& InOutCompiledEmitters) +{ + if (InOutCompiledEmitters.Contains(Emitter) == false) + { + if (Emitter->GetParent() != nullptr) + { + // If the emitter has a parent emitter make sure to compile that one first. + CompileEmitterStandAlone(Emitter->GetParent(), InOutCompiledEmitters); + + if (Emitter->IsSynchronizedWithParent() == false) + { + // If compiling the parent caused it to become out of sync with the current emitter merge in changes before compiling. + Emitter->MergeChangesFromParent(); + } + } + + Emitter->MarkPackageDirty(); + UNiagaraSystem* TransientSystem = NewObject(GetTransientPackage(), NAME_None, RF_Transient); + UNiagaraSystemFactoryNew::InitializeSystem(TransientSystem, true); + TransientSystem->AddEmitterHandle(*Emitter, TEXT("Emitter")); + FNiagaraStackGraphUtilities::RebuildEmitterNodes(*TransientSystem); + TransientSystem->RequestCompile(false); + TransientSystem->WaitForCompilationComplete(); + + InOutCompiledEmitters.Add(Emitter); + } +} + void PreventSystemRecompile(FAssetData SystemAsset, TSet& InOutCompiledEmitters) { UNiagaraSystem* System = Cast(SystemAsset.GetAsset()); @@ -284,22 +312,7 @@ void PreventSystemRecompile(FAssetData SystemAsset, TSet& InOu { for (const FNiagaraEmitterHandle& EmitterHandle : System->GetEmitterHandles()) { - UNiagaraEmitter* SourceEmitter = const_cast(EmitterHandle.GetSource()); - if (SourceEmitter != nullptr) - { - if (InOutCompiledEmitters.Contains(SourceEmitter) == false) - { - SourceEmitter->MarkPackageDirty(); - UNiagaraSystem* TransientSystem = NewObject(GetTransientPackage(), NAME_None, RF_Transient); - UNiagaraSystemFactoryNew::InitializeSystem(TransientSystem, true); - TransientSystem->AddEmitterHandle(*SourceEmitter, TEXT("Emitter")); // TODO Frank.Fella, resolve this properly... - FNiagaraStackGraphUtilities::RebuildEmitterNodes(*TransientSystem); - TransientSystem->RequestCompile(false); - TransientSystem->WaitForCompilationComplete(); - InOutCompiledEmitters.Add(SourceEmitter); - } - System->UpdateFromEmitterChanges(*SourceEmitter, false); - } + CompileEmitterStandAlone(EmitterHandle.GetInstance(), InOutCompiledEmitters); } System->MarkPackageDirty(); @@ -544,7 +557,7 @@ void FNiagaraEditorModule::StartupModule() // Register the emitter merge handler. ScriptMergeManager = MakeShared(); - MergeEmitterHandle = NiagaraModule.RegisterOnMergeEmitter(INiagaraModule::FOnMergeEmitter::CreateSP(ScriptMergeManager.ToSharedRef(), &FNiagaraScriptMergeManager::MergeEmitter)); + NiagaraModule.RegisterMergeManager(ScriptMergeManager.ToSharedRef()); // Register the script compiler ScriptCompilerHandle = NiagaraModule.RegisterScriptCompiler(INiagaraModule::FScriptCompiler::CreateLambda([this](const FNiagaraCompileRequestDataBase* CompileRequest, const FNiagaraCompileOptions& Options) @@ -645,7 +658,7 @@ void FNiagaraEditorModule::ShutdownModule() INiagaraModule* NiagaraModule = FModuleManager::GetModulePtr("Niagara"); if (NiagaraModule != nullptr) { - NiagaraModule->UnregisterOnMergeEmitter(MergeEmitterHandle); + NiagaraModule->UnregisterMergeManager(ScriptMergeManager.ToSharedRef()); NiagaraModule->UnregisterOnCreateDefaultScriptSource(CreateDefaultScriptSourceHandle); NiagaraModule->UnregisterScriptCompiler(ScriptCompilerHandle); NiagaraModule->UnregisterPrecompiler(PrecompilerHandle); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorUtilities.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorUtilities.cpp index a220ea827eb2..988557d7a80c 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorUtilities.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorUtilities.cpp @@ -862,6 +862,11 @@ const TMap& FNiagar // We clone the graph to prevent any destructive changes to the source UNiagaraGraph* NodeGraphDeepCopy = CastChecked(FEdGraphUtilities::CloneGraph(Graph, GetTransientPackage(), 0, true)); + + // We copy some transient data over here because the copied graph loses some data. Since the result of this function is + // also used to build the ui, the user will otherwise lose changes to yet unused parameters (e.g. in the metadata panel). + CastChecked(Graph)->CopyCachedReferencesMap(NodeGraphDeepCopy); + TArray Nodes; NodeGraphDeepCopy->GetNodesOfClass(Nodes); for (int32 i = 0; i < Nodes.Num(); i++) @@ -874,7 +879,10 @@ const TMap& FNiagar // We append all function nodes we find to the end of the queue, basically traversing the graph breadth-first TArray SubFunctionNodes; FunctionGraph->GetNodesOfClass(SubFunctionNodes); - Nodes.Append(SubFunctionNodes); + for (UNiagaraNodeFunctionCall* SubNode : SubFunctionNodes) + { + Nodes.AddUnique(SubNode); + } // We set the static switch values based on the function call inputs to prepare the parameter map history generation TArray CallInputs; @@ -1068,7 +1076,7 @@ TArray FNiagaraEditorUtilities::GetComponentsThatReferenceSy { for (auto EmitterHandle : ReferencedSystemViewModel.GetSystem().GetEmitterHandles()) { - if (Component->GetAsset()->UsesEmitter(EmitterHandle.GetSource())) + if (Component->GetAsset()->UsesEmitter(EmitterHandle.GetInstance()->GetParent())) { ReferencingComponents.Add(Component); } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEmitterFactoryNew.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEmitterFactoryNew.cpp index c3992e62346b..83a7c3616878 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEmitterFactoryNew.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEmitterFactoryNew.cpp @@ -31,6 +31,7 @@ UNiagaraEmitterFactoryNew::UNiagaraEmitterFactoryNew(const FObjectInitializer& O bEditAfterNew = true; bCreateNew = true; EmitterToCopy = nullptr; + bUseInheritance = false; } bool UNiagaraEmitterFactoryNew::ConfigureProperties() @@ -51,6 +52,7 @@ bool UNiagaraEmitterFactoryNew::ConfigureProperties() if (SelectedEmitterAsset.IsSet()) { EmitterToCopy = Cast(SelectedEmitterAsset->GetAsset()); + bUseInheritance = NewEmitterDialog->GetUseInheritance(); if (EmitterToCopy == nullptr) { FText Title = LOCTEXT("FailedToLoadTitle", "Create Default?"); @@ -120,7 +122,16 @@ UObject* UNiagaraEmitterFactoryNew::FactoryCreateNew(UClass* Class, UObject* InP if (EmitterToCopy != nullptr) { - NewEmitter = Cast(StaticDuplicateObject(EmitterToCopy, InParent, Name, Flags, Class)); + if (bUseInheritance && EmitterToCopy->bIsTemplateAsset == false) + { + NewEmitter = UNiagaraEmitter::CreateWithParentAndOwner(*EmitterToCopy, InParent, Name, Flags); + } + else + { + NewEmitter = Cast(StaticDuplicateObject(EmitterToCopy, InParent, Name, Flags, Class)); + NewEmitter->SetUniqueEmitterName(Name.ToString()); + } + NewEmitter->bIsTemplateAsset = false; NewEmitter->TemplateAssetDescription = FText(); } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraGraph.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraGraph.cpp index 448209bb6415..8150a99e7d75 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraGraph.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraGraph.cpp @@ -158,7 +158,7 @@ void UNiagaraGraph::PostLoad() if (bAllZeroes && UniqueNames.Num() > 1) { // Just do the lexicographic sort and assign the call order to their ordered index value. - UniqueNames.Sort(); + UniqueNames.Sort(FNameLexicalLess()); for (UNiagaraNodeInput* InputNode : InputNodes) { if (InputNode->Usage == ENiagaraInputNodeUsage::Parameter) @@ -642,7 +642,7 @@ TArray UNiagaraGraph::FindStaticSwitchInputs() const } Result.Sort([](const FNiagaraVariable& Left, const FNiagaraVariable& Right) { - return Left.GetName() < Right.GetName(); + return Left.GetName().LexicalLess(Right.GetName()); }); return Result; } @@ -1115,6 +1115,11 @@ void UNiagaraGraph::RebuildCachedCompileIds(bool bForce) RebuildNumericCache(); } +void UNiagaraGraph::CopyCachedReferencesMap(UNiagaraGraph* TargetGraph) +{ + TargetGraph->ParameterToReferencesMap = ParameterToReferencesMap; +} + const class UEdGraphSchema_Niagara* UNiagaraGraph::GetNiagaraSchema() const { return Cast(GetSchema()); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.cpp index e998bcf782da..4648594b4fc1 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.cpp @@ -409,10 +409,17 @@ FString FHlslNiagaraTranslator::GetFunctionDefinitions() // data interface functions that should be defined differently. } + // Check to see if we have interpolated spawn enabled, for the GPU we need to look for the additional defines + bool bHasInterpolatedSpawn = CompileOptions.TargetUsage == ENiagaraScriptUsage::ParticleSpawnScriptInterpolated; + if ( CompileOptions.TargetUsage == ENiagaraScriptUsage::ParticleGPUComputeScript ) + { + bHasInterpolatedSpawn = CompileOptions.AdditionalDefines.Contains(TEXT("InterpolatedSpawn")); + } + //Add a few hard coded helper functions in. FwdDeclString += TEXT("float GetSpawnInterpolation();"); //Add helper function to get the interpolation factor. - if (CompileOptions.TargetUsage == ENiagaraScriptUsage::ParticleSpawnScriptInterpolated) + if ( bHasInterpolatedSpawn ) { DefinitionsString += TEXT("float GetSpawnInterpolation()\n{\n"); DefinitionsString += TEXT("\treturn HackSpawnInterp;\n"); @@ -996,23 +1003,6 @@ const FNiagaraTranslateResults &FHlslNiagaraTranslator::Translate(const FNiagara if (FunctionCtx()) return TranslateResults; - // MASSIVE HACK - Tracked in JIRA UE-69298 - // Hardcoded random function accessible from inner part of node implementation. - // It works for now at least and avoids exposing every random needed in the UI. - // Temporary solution, it will be replaced when a design is validated. - if (CompilationTarget == ENiagaraSimTarget::GPUComputeSim) - { - HlslOutput += TEXT(R"( - float NiagaraInternalNoise(uint u, uint v, uint s) - { - static uint RandomSeedOffset = 0; - uint Seed = (u * 1664525u + v) + s + RandomSeedOffset; - RandomSeedOffset += Seed; - return float(Rand3DPCG32(int3(u,v,Seed)).x) / 4294967296.0f; - } - )"); - } - //Now evaluate all the code chunks to generate the shader code. //FString HlslOutput; if (TranslateResults.bHLSLGenSucceeded) @@ -1127,7 +1117,7 @@ const FNiagaraTranslateResults &FHlslNiagaraTranslator::Translate(const FNiagara // We sort the variables so that they end up in the same ordering between Spawn & Update... SystemEngineReadVars.Sort([&](const FNiagaraVariable& A, const FNiagaraVariable& B) { - return A.GetName() < B.GetName(); + return A.GetName().LexicalLess(B.GetName()); }); { @@ -1176,12 +1166,12 @@ const FNiagaraTranslateResults &FHlslNiagaraTranslator::Translate(const FNiagara // We sort the variables so that they end up in the same ordering between Spawn & Update... InstanceReadVars.Sort([&](const FNiagaraVariable& A, const FNiagaraVariable& B) { - return A.GetName() < B.GetName(); + return A.GetName().LexicalLess(B.GetName()); }); // We sort the variables so that they end up in the same ordering between Spawn & Update... InstanceWriteVars.Sort([&](const FNiagaraVariable& A, const FNiagaraVariable& B) { - return A.GetName() < B.GetName(); + return A.GetName().LexicalLess(B.GetName()); }); //Define the simulation context. Which is a helper struct containing all the input, result and intermediate data needed for a single simulation. //Allows us to reuse the same simulate function but provide different wrappers for final IO between GPU and CPU sims. @@ -1654,13 +1644,13 @@ void FHlslNiagaraTranslator::DefineDataSetWriteFunction(FString &HlslOutputStrin HlslOutput += TEXT("}\n\n"); } - - void FHlslNiagaraTranslator::DefineDataInterfaceHLSL(FString &InHlslOutput) { + FString InterfaceCommonHLSL; FString InterfaceUniformHLSL; FString InterfaceFunctionHLSL; TArray BufferParamNames; + TSet InterfaceClasses; for (uint32 i = 0; i < 32; i++) { BufferParamNames.Add(TEXT("DataInterfaceBuffer_") + FString::FromInt(i)); @@ -1676,6 +1666,12 @@ void FHlslNiagaraTranslator::DefineDataInterfaceHLSL(FString &InHlslOutput) UNiagaraDataInterface* CDO = Cast(*FoundCDO); if (CDO && CDO->CanExecuteOnTarget(ENiagaraSimTarget::GPUComputeSim)) { + if ( !InterfaceClasses.Contains(Info.Type.GetFName()) ) + { + CDO->GetCommonHLSL(InterfaceCommonHLSL); + InterfaceClasses.Add(Info.Type.GetFName()); + } + FString OwnerIDString = Info.Name.ToString(); FString SanitizedOwnerIDString = GetSanitizedSymbolName(OwnerIDString, true); @@ -1712,7 +1708,7 @@ void FHlslNiagaraTranslator::DefineDataInterfaceHLSL(FString &InHlslOutput) , nullptr, nullptr); } } - InHlslOutput += InterfaceUniformHLSL + InterfaceFunctionHLSL; + InHlslOutput += InterfaceCommonHLSL + InterfaceUniformHLSL + InterfaceFunctionHLSL; } @@ -4307,6 +4303,28 @@ void FHlslNiagaraTranslator::FunctionCall(UNiagaraNodeFunctionCall* FunctionNode // We need the generated string to generate the proper signature for now. ActiveHistoryForFunctionCalls.EnterFunction(FunctionNode->GetFunctionName(), FunctionNode->FunctionScript, FunctionNode); + // Check if there are static switch parameters being set directly by a set node from the stack UI. + // This can happen if a module was changed and the original parameter was replaced by a static switch with the same name, but the emitter was not yet updated. + const FString* ModuleAlias = ActiveHistoryForFunctionCalls.GetModuleAlias(); + if (ModuleAlias) + { + for (int32 i = 0; i < ParamMapHistories.Num(); i++) + { + for (int32 j = 0; j < ParamMapHistories[i].VariablesWithOriginalAliasesIntact.Num(); j++) + { + const FNiagaraVariable Var = ParamMapHistories[i].VariablesWithOriginalAliasesIntact[j]; + FString VarStr = Var.GetName().ToString(); + if (VarStr.StartsWith(*ModuleAlias)) + { + VarStr = VarStr.Mid(ModuleAlias->Len() + 1); + if (FunctionNode->FindStaticSwitchInputPin(*VarStr)) + { + Error(FText::Format(LOCTEXT("SwitchPinFoundForSetPin", "A switch node pin exists but is being set directly using Set node! Please use the stack UI to resolve the conflict. Output Pin: {0}"), FText::FromName(Var.GetName())), FunctionNode, nullptr); + } + } + } + } + } // Remove input add pin if it exists for (int32 i = 0; i < CallOutputs.Num(); i++) @@ -5475,7 +5493,7 @@ int32 FHlslNiagaraTranslator::CompileOutputPin(const UEdGraphPin* InPin) return Ret; } -void FHlslNiagaraTranslator::Error(FText ErrorText, const UNiagaraNode* Node, const UEdGraphPin* Pin) //@todo(message manager) rearrange syntax to fit with FNiagaraMessageManager +void FHlslNiagaraTranslator::Error(FText ErrorText, const UNiagaraNode* Node, const UEdGraphPin* Pin) { FString NodePinStr = TEXT(""); FString NodePinPrefix = TEXT(" - "); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraMessageManager.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraMessageManager.cpp index 23abac0e09db..6eb09fa6bf45 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraMessageManager.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraMessageManager.cpp @@ -265,7 +265,7 @@ TSharedRef FNiagaraMessageCompileEvent::GenerateTokenizedMess FNiagaraMessageJobCompileEvent::FNiagaraMessageJobCompileEvent( const FNiagaraCompileEvent& InCompileEvent - , const TWeakObjectPtr& InOriginatingScriptWeakObjPtr + , const TWeakObjectPtr& InOriginatingScriptWeakObjPtr , const TOptional& InOwningScriptNameString , const TOptional& InSourceScriptAssetPath ) @@ -282,11 +282,11 @@ TSharedRef FNiagaraMessageJobCompileEvent::GenerateNiagar if (OriginatingScriptWeakObjPtr.IsValid()) { - UNiagaraScriptSourceBase* FunctionScriptSourceBase = OriginatingScriptWeakObjPtr->GetSource(); + const UNiagaraScriptSourceBase* FunctionScriptSourceBase = OriginatingScriptWeakObjPtr->GetSource(); checkf(FunctionScriptSourceBase->IsA(), TEXT("Script source for function call node is not assigned or is not of type UNiagaraScriptSource!")) - UNiagaraScriptSource* FunctionScriptSource = Cast(FunctionScriptSourceBase); + const UNiagaraScriptSource* FunctionScriptSource = Cast(FunctionScriptSourceBase); checkf(FunctionScriptSource, TEXT("Script source base was somehow not a derived type!")); - UNiagaraGraph* ScriptGraph = FunctionScriptSource->NodeGraph; + const UNiagaraGraph* ScriptGraph = FunctionScriptSource->NodeGraph; checkf(ScriptGraph, TEXT("Function Script does not have a UNiagaraGraph!")); TArray ContextScriptNamesAndPaths; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeFunctionCall.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeFunctionCall.cpp index ce1e6d57f5b1..001808ffea4d 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeFunctionCall.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeFunctionCall.cpp @@ -155,18 +155,32 @@ void UNiagaraNodeFunctionCall::AllocateDefaultPins() } TArray SwitchNodeInputs = Graph->FindStaticSwitchInputs(); - for (const FNiagaraVariable& Input : SwitchNodeInputs) + for (FNiagaraVariable& Input : SwitchNodeInputs) { UEdGraphPin* NewPin = CreatePin(EGPD_Input, Schema->TypeDefinitionToPinType(Input.GetType()), Input.GetName()); - NewPin->bDefaultValueIsIgnored = false; NewPin->bNotConnectable = true; + NewPin->bDefaultValueIsIgnored = FindPropagatedVariable(Input) != nullptr; FString PinDefaultValue; - if (Schema->TryGetPinDefaultValueFromNiagaraVariable(Input, PinDefaultValue)) + TOptional MetaData = Graph->GetMetaData(Input); + if (MetaData.IsSet()) { - NewPin->DefaultValue = PinDefaultValue; + int32 DefaultValue = MetaData->StaticSwitchDefaultValue; + Input.AllocateData(); + Input.SetValue({ DefaultValue }); + + if (Schema->TryGetPinDefaultValueFromNiagaraVariable(Input, PinDefaultValue)) + { + NewPin->DefaultValue = PinDefaultValue; + } + } + else + { + if (Schema->TryGetPinDefaultValueFromNiagaraVariable(Input, PinDefaultValue)) + { + NewPin->DefaultValue = PinDefaultValue; + } } - NewPin->bDefaultValueIsIgnored = FindPropagatedVariable(Input) != nullptr; } for (FNiagaraVariable& Output : Outputs) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeOutput.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeOutput.cpp index c95cd289ed35..341d18e78ab0 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeOutput.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeOutput.cpp @@ -90,7 +90,7 @@ void UNiagaraNodeOutput::GetContextMenuActions(const FGraphNodeContextMenuBuilde [ SNew(SEditableTextBox) .Text_UObject(this, &UNiagaraNodeOutput::GetPinNameText, Pin) - .OnTextCommitted_UObject(this, &UNiagaraNodeOutput::PinNameTextCommitted, Pin) + .OnTextCommitted_UObject(const_cast(this), &UNiagaraNodeOutput::PinNameTextCommitted, Pin) ]; Context.MenuBuilder->AddWidget(RenameWidget, LOCTEXT("NameMenuItem", "Name")); } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapGet.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapGet.cpp index 082593b4fc51..3ed80bf1dbda 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapGet.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapGet.cpp @@ -515,7 +515,7 @@ void UNiagaraNodeParameterMapGet::GetContextMenuActions(const FGraphNodeContextM [ SNew(SEditableTextBox) .Text_UObject(this, &UNiagaraNodeParameterMapBase::GetPinDescriptionText, Pin) - .OnTextCommitted_UObject(this, &UNiagaraNodeParameterMapBase::PinDescriptionTextCommitted, Pin) + .OnTextCommitted_UObject(const_cast(this), &UNiagaraNodeParameterMapBase::PinDescriptionTextCommitted, Pin) ]; Context.MenuBuilder->AddWidget(RenameWidget, LOCTEXT("DescMenuItem", "Description")); Context.MenuBuilder->EndSection(); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapSet.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapSet.cpp index e897cb4346dc..26d46ac506de 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapSet.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapSet.cpp @@ -271,7 +271,7 @@ void UNiagaraNodeParameterMapSet::GetContextMenuActions(const FGraphNodeContextM [ SNew(SEditableTextBox) .Text_UObject(this, &UNiagaraNodeParameterMapBase::GetPinDescriptionText, Pin) - .OnTextCommitted_UObject(this, &UNiagaraNodeParameterMapBase::PinDescriptionTextCommitted, Pin) + .OnTextCommitted_UObject(const_cast(this), &UNiagaraNodeParameterMapBase::PinDescriptionTextCommitted, Pin) ]; Context.MenuBuilder->AddWidget(RenameWidget, LOCTEXT("DescMenuItem", "Description")); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeStaticSwitch.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeStaticSwitch.cpp index 70d84750d2fc..68b7bccae913 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeStaticSwitch.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeStaticSwitch.cpp @@ -55,6 +55,9 @@ void UNiagaraNodeStaticSwitch::RemoveUnusedGraphParameter(const FNiagaraVariable { GetNiagaraGraph()->NotifyGraphChanged(); } + + // force the graph to refresh the metadata + GetNiagaraGraph()->GetParameterReferenceMap(); } void UNiagaraNodeStaticSwitch::AllocateDefaultPins() @@ -111,6 +114,9 @@ void UNiagaraNodeStaticSwitch::AllocateDefaultPins() } CreateAddPin(EGPD_Output); + + // force the graph to refresh the metadata + GetNiagaraGraph()->GetParameterReferenceMap(); } void UNiagaraNodeStaticSwitch::InsertInputPinsFor(const FNiagaraVariable& Var) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeUsageSelector.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeUsageSelector.cpp index 1d8382859252..f4a67f6a408d 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeUsageSelector.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeUsageSelector.cpp @@ -35,7 +35,7 @@ bool UNiagaraNodeUsageSelector::AllowNiagaraTypeForAddPin(const FNiagaraTypeDefi void UNiagaraNodeUsageSelector::InsertInputPinsFor(const FNiagaraVariable& Var) { const UEdGraphSchema_Niagara* Schema = GetDefault(); - UEnum* ENiagaraScriptGroupEnum = FindObject(ANY_PACKAGE, TEXT("ENiagaraScriptGroup"), true); + UEnum* ENiagaraScriptGroupEnum = StaticEnum(); int64 GroupCount = (int64)ENiagaraScriptGroup::Max; TArray OldPins(Pins); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeWithDynamicPins.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeWithDynamicPins.cpp index 53ddf65aec0f..768be9cc5e12 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeWithDynamicPins.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeWithDynamicPins.cpp @@ -177,7 +177,7 @@ void UNiagaraNodeWithDynamicPins::GetContextMenuActions(const FGraphNodeContextM [ SNew(SEditableTextBox) .Text_UObject(this, &UNiagaraNodeWithDynamicPins::GetPinNameText, Pin) - .OnTextCommitted_UObject(this, &UNiagaraNodeWithDynamicPins::PinNameTextCommitted, Pin) + .OnTextCommitted_UObject(const_cast(this), &UNiagaraNodeWithDynamicPins::PinNameTextCommitted, Pin) ]; Context.MenuBuilder->AddWidget(RenameWidget, LOCTEXT("NameMenuItem", "Name")); } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptMergeManager.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptMergeManager.cpp index 1855b7198588..fd288a550ed4 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptMergeManager.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptMergeManager.cpp @@ -656,7 +656,7 @@ TSharedRef FNiagaraScriptMergeManager::Get() return NiagaraEditorModule.GetScriptMergeManager(); } -void FNiagaraScriptMergeManager::DiffChangeIds(const TMap& InSourceChangeIds, const TMap& InLastMergedChangeIds, const TMap& InInstanceChangeIds, TMap& OutChangeIdsToKeepOnInstance) +void FNiagaraScriptMergeManager::DiffChangeIds(const TMap& InSourceChangeIds, const TMap& InLastMergedChangeIds, const TMap& InInstanceChangeIds, TMap& OutChangeIdsToKeepOnInstance) const { auto It = InInstanceChangeIds.CreateConstIterator(); while (It) @@ -698,7 +698,7 @@ void FNiagaraScriptMergeManager::DiffChangeIds(const TMap& InSourc } } -FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ResolveChangeIds(TSharedRef MergedInstanceAdapter, UNiagaraEmitter& OriginalEmitterInstance, const TMap& ChangeIdsThatNeedToBeReset) +FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ResolveChangeIds(TSharedRef MergedInstanceAdapter, UNiagaraEmitter& OriginalEmitterInstance, const TMap& ChangeIdsThatNeedToBeReset) const { FApplyDiffResults DiffResults; @@ -807,24 +807,24 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::Resolv return DiffResults; } -INiagaraModule::FMergeEmitterResults FNiagaraScriptMergeManager::MergeEmitter(UNiagaraEmitter& Source, UNiagaraEmitter& LastMergedSource, UNiagaraEmitter& Instance) +INiagaraMergeManager::FMergeEmitterResults FNiagaraScriptMergeManager::MergeEmitter(UNiagaraEmitter& Parent, UNiagaraEmitter& ParentAtLastMerge, UNiagaraEmitter& Instance) const { SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_ScriptMergeManager_MergeEmitter); - INiagaraModule::FMergeEmitterResults MergeResults; + INiagaraMergeManager::FMergeEmitterResults MergeResults; - FNiagaraEmitterDiffResults DiffResults = DiffEmitters(LastMergedSource, Instance); + FNiagaraEmitterDiffResults DiffResults = DiffEmitters(ParentAtLastMerge, Instance); if (DiffResults.IsValid()) { - UNiagaraEmitter* MergedInstance = CastChecked(StaticDuplicateObject(&Source, (UObject*)GetTransientPackage())); + UNiagaraEmitter* MergedInstance = CastChecked(StaticDuplicateObject(&Parent, (UObject*)GetTransientPackage())); TSharedRef MergedInstanceAdapter = MakeShared(*MergedInstance); TMap SourceChangeIds; TMap PreviousSourceChangeIds; TMap LastChangeIds; TMap ChangeIdsThatNeedToBeReset; - FNiagaraEditorUtilities::GatherChangeIds(Source, SourceChangeIds, TEXT("Source")); - FNiagaraEditorUtilities::GatherChangeIds(LastMergedSource, PreviousSourceChangeIds, TEXT("MergeLast")); + FNiagaraEditorUtilities::GatherChangeIds(Parent, SourceChangeIds, TEXT("Source")); + FNiagaraEditorUtilities::GatherChangeIds(ParentAtLastMerge, PreviousSourceChangeIds, TEXT("MergeLast")); FNiagaraEditorUtilities::GatherChangeIds(Instance, LastChangeIds, TEXT("Instance")); DiffChangeIds(SourceChangeIds, PreviousSourceChangeIds, LastChangeIds, ChangeIdsThatNeedToBeReset); @@ -1154,7 +1154,7 @@ void FNiagaraScriptMergeManager::ResetEmitterEditablePropertySetToBase(UNiagaraE Emitter.PostEditChange(); } -FNiagaraEmitterDiffResults FNiagaraScriptMergeManager::DiffEmitters(UNiagaraEmitter& BaseEmitter, UNiagaraEmitter& OtherEmitter) +FNiagaraEmitterDiffResults FNiagaraScriptMergeManager::DiffEmitters(UNiagaraEmitter& BaseEmitter, UNiagaraEmitter& OtherEmitter) const { SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_ScriptMergeManager_DiffEmitters); @@ -1283,7 +1283,7 @@ FListDiffResults DiffLists(const TArray& BaseList, const T return DiffResults; } -void FNiagaraScriptMergeManager::DiffEventHandlers(const TArray>& BaseEventHandlers, const TArray>& OtherEventHandlers, FNiagaraEmitterDiffResults& DiffResults) +void FNiagaraScriptMergeManager::DiffEventHandlers(const TArray>& BaseEventHandlers, const TArray>& OtherEventHandlers, FNiagaraEmitterDiffResults& DiffResults) const { FListDiffResults> EventHandlerListDiffResults = DiffLists, FGuid>( BaseEventHandlers, @@ -1334,7 +1334,7 @@ void FNiagaraScriptMergeManager::DiffEventHandlers(const TArray>& BaseRenderers, const TArray>& OtherRenderers, FNiagaraEmitterDiffResults& DiffResults) +void FNiagaraScriptMergeManager::DiffRenderers(const TArray>& BaseRenderers, const TArray>& OtherRenderers, FNiagaraEmitterDiffResults& DiffResults) const { FListDiffResults> RendererListDiffResults = DiffLists, FGuid>( BaseRenderers, @@ -1354,7 +1354,7 @@ void FNiagaraScriptMergeManager::DiffRenderers(const TArray BaseScriptStackAdapter, TSharedRef OtherScriptStackAdapter, FNiagaraScriptStackDiffResults& DiffResults) +void FNiagaraScriptMergeManager::DiffScriptStacks(TSharedRef BaseScriptStackAdapter, TSharedRef OtherScriptStackAdapter, FNiagaraScriptStackDiffResults& DiffResults) const { // Diff the module lists. FListDiffResults> ModuleListDiffResults = DiffLists, FGuid>( @@ -1408,7 +1408,7 @@ void FNiagaraScriptMergeManager::DiffScriptStacks(TSharedRef BaseFunctionAdapter, TSharedRef OtherFunctionAdapter, FNiagaraScriptStackDiffResults& DiffResults) +void FNiagaraScriptMergeManager::DiffFunctionInputs(TSharedRef BaseFunctionAdapter, TSharedRef OtherFunctionAdapter, FNiagaraScriptStackDiffResults& DiffResults) const { FListDiffResults> ListDiffResults = DiffLists, FString>( BaseFunctionAdapter->GetInputOverrides(), @@ -1438,7 +1438,7 @@ void FNiagaraScriptMergeManager::DiffFunctionInputs(TSharedRef& OutDifferentProperties) +void FNiagaraScriptMergeManager::DiffEditableProperties(const void* BaseDataAddress, const void* OtherDataAddress, UStruct& Struct, TArray& OutDifferentProperties) const { for (TFieldIterator PropertyIterator(&Struct); PropertyIterator; ++PropertyIterator) { @@ -1454,7 +1454,7 @@ void FNiagaraScriptMergeManager::DiffEditableProperties(const void* BaseDataAddr } } -TOptional FNiagaraScriptMergeManager::DoFunctionInputOverridesMatch(TSharedRef BaseFunctionInputAdapter, TSharedRef OtherFunctionInputAdapter) +TOptional FNiagaraScriptMergeManager::DoFunctionInputOverridesMatch(TSharedRef BaseFunctionInputAdapter, TSharedRef OtherFunctionInputAdapter) const { // Local String Value. if ((BaseFunctionInputAdapter->GetLocalValueString().IsSet() && OtherFunctionInputAdapter->GetLocalValueString().IsSet() == false) || @@ -1546,7 +1546,7 @@ TOptional FNiagaraScriptMergeManager::DoFunctionInputOverridesMatch(TShare return TOptional(); } -FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::AddModule(FString UniqueEmitterName, UNiagaraScript& OwningScript, UNiagaraNodeOutput& TargetOutputNode, TSharedRef AddModule) +FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::AddModule(FString UniqueEmitterName, UNiagaraScript& OwningScript, UNiagaraNodeOutput& TargetOutputNode, TSharedRef AddModule) const { FApplyDiffResults Results; @@ -1600,7 +1600,7 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::AddMod return Results; } -FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::RemoveInputOverride(UNiagaraScript& OwningScript, TSharedRef OverrideToRemove) +FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::RemoveInputOverride(UNiagaraScript& OwningScript, TSharedRef OverrideToRemove) const { FApplyDiffResults Results; if (OverrideToRemove->GetOverridePin() != nullptr && OverrideToRemove->GetOverrideNode() != nullptr) @@ -1626,7 +1626,7 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::Remove return Results; } -FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::AddInputOverride(FString UniqueEmitterName, UNiagaraScript& OwningScript, UNiagaraNodeFunctionCall& TargetFunctionCall, TSharedRef OverrideToAdd) +FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::AddInputOverride(FString UniqueEmitterName, UNiagaraScript& OwningScript, UNiagaraNodeFunctionCall& TargetFunctionCall, TSharedRef OverrideToAdd) const { FApplyDiffResults Results; @@ -1758,7 +1758,7 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::AddInp return Results; } -void FNiagaraScriptMergeManager::CopyPropertiesToBase(void* BaseDataAddress, const void* OtherDataAddress, TArray PropertiesToCopy) +void FNiagaraScriptMergeManager::CopyPropertiesToBase(void* BaseDataAddress, const void* OtherDataAddress, TArray PropertiesToCopy) const { for (UProperty* PropertyToCopy : PropertiesToCopy) { @@ -1766,7 +1766,7 @@ void FNiagaraScriptMergeManager::CopyPropertiesToBase(void* BaseDataAddress, con } } -FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyScriptStackDiff(TSharedRef BaseScriptStackAdapter, const FNiagaraScriptStackDiffResults& DiffResults) +FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyScriptStackDiff(TSharedRef BaseScriptStackAdapter, const FNiagaraScriptStackDiffResults& DiffResults) const { FApplyDiffResults Results; @@ -1926,7 +1926,7 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyS return Results; } -FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyEventHandlerDiff(TSharedRef BaseEmitterAdapter, const FNiagaraEmitterDiffResults& DiffResults) +FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyEventHandlerDiff(TSharedRef BaseEmitterAdapter, const FNiagaraEmitterDiffResults& DiffResults) const { FApplyDiffResults Results; if (DiffResults.RemovedBaseEventHandlers.Num() > 0) @@ -2018,7 +2018,7 @@ FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyE return Results; } -FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyRendererDiff(UNiagaraEmitter& BaseEmitter, const FNiagaraEmitterDiffResults& DiffResults) +FNiagaraScriptMergeManager::FApplyDiffResults FNiagaraScriptMergeManager::ApplyRendererDiff(UNiagaraEmitter& BaseEmitter, const FNiagaraEmitterDiffResults& DiffResults) const { TArray RenderersToRemove; TArray RenderersToAdd; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptMergeManager.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptMergeManager.h index de3300f32f9c..83c9dfad5458 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptMergeManager.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptMergeManager.h @@ -4,6 +4,7 @@ #include "ViewModels/Stack/NiagaraParameterHandle.h" #include "NiagaraModule.h" +#include "INiagaraMergeManager.h" #include "NiagaraTypes.h" #include "NiagaraCommon.h" @@ -272,7 +273,7 @@ private: TArray ErrorMessages; }; -class FNiagaraScriptMergeManager +class FNiagaraScriptMergeManager : public INiagaraMergeManager { public: struct FApplyDiffResults @@ -289,11 +290,11 @@ public: }; public: - INiagaraModule::FMergeEmitterResults MergeEmitter(UNiagaraEmitter& Source, UNiagaraEmitter& LastMergedSource, UNiagaraEmitter& Instance); + virtual INiagaraMergeManager::FMergeEmitterResults MergeEmitter(UNiagaraEmitter& Parent, UNiagaraEmitter& ParentAtLastMerge, UNiagaraEmitter& Instance) const override; static TSharedRef Get(); - FNiagaraEmitterDiffResults DiffEmitters(UNiagaraEmitter& BaseEmitter, UNiagaraEmitter& OtherEmitter); + FNiagaraEmitterDiffResults DiffEmitters(UNiagaraEmitter& BaseEmitter, UNiagaraEmitter& OtherEmitter) const; bool IsMergeableScriptUsage(ENiagaraScriptUsage ScriptUsage) const; @@ -319,40 +320,40 @@ public: void ResetEmitterEditablePropertySetToBase(UNiagaraEmitter& Emitter, const UNiagaraEmitter& BaseEmitter); - void DiffEventHandlers(const TArray>& BaseEventHandlers, const TArray>& OtherEventHandlers, FNiagaraEmitterDiffResults& DiffResults); + void DiffEventHandlers(const TArray>& BaseEventHandlers, const TArray>& OtherEventHandlers, FNiagaraEmitterDiffResults& DiffResults) const; - void DiffRenderers(const TArray>& BaseRenderers, const TArray>& OtherRenderers, FNiagaraEmitterDiffResults& DiffResults); + void DiffRenderers(const TArray>& BaseRenderers, const TArray>& OtherRenderers, FNiagaraEmitterDiffResults& DiffResults) const; - void DiffScriptStacks(TSharedRef BaseScriptStackAdapter, TSharedRef OtherScriptStackAdapter, FNiagaraScriptStackDiffResults& DiffResults); + void DiffScriptStacks(TSharedRef BaseScriptStackAdapter, TSharedRef OtherScriptStackAdapter, FNiagaraScriptStackDiffResults& DiffResults) const; - void DiffFunctionInputs(TSharedRef BaseFunctionAdapter, TSharedRef OtherFunctionAdapter, FNiagaraScriptStackDiffResults& DiffResults); + void DiffFunctionInputs(TSharedRef BaseFunctionAdapter, TSharedRef OtherFunctionAdapter, FNiagaraScriptStackDiffResults& DiffResults) const; - void DiffEditableProperties(const void* BaseDataAddress, const void* OtherDataAddress, UStruct& Struct, TArray& OutDifferentProperties); + virtual void DiffEditableProperties(const void* BaseDataAddress, const void* OtherDataAddress, UStruct& Struct, TArray& OutDifferentProperties) const override; + + virtual void CopyPropertiesToBase(void* BaseDataAddress, const void* OtherDataAddress, TArray PropertiesToCopy) const override; private: - TOptional DoFunctionInputOverridesMatch(TSharedRef BaseFunctionInputAdapter, TSharedRef OtherFunctionInputAdapter); + TOptional DoFunctionInputOverridesMatch(TSharedRef BaseFunctionInputAdapter, TSharedRef OtherFunctionInputAdapter) const; - FApplyDiffResults ApplyScriptStackDiff(TSharedRef BaseScriptStackAdapter, const FNiagaraScriptStackDiffResults& DiffResults); + FApplyDiffResults ApplyScriptStackDiff(TSharedRef BaseScriptStackAdapter, const FNiagaraScriptStackDiffResults& DiffResults) const; - FApplyDiffResults ApplyEventHandlerDiff(TSharedRef BaseEmitterAdapter, const FNiagaraEmitterDiffResults& DiffResults); + FApplyDiffResults ApplyEventHandlerDiff(TSharedRef BaseEmitterAdapter, const FNiagaraEmitterDiffResults& DiffResults) const; - FApplyDiffResults ApplyRendererDiff(UNiagaraEmitter& BaseEmitter, const FNiagaraEmitterDiffResults& DiffResults); + FApplyDiffResults ApplyRendererDiff(UNiagaraEmitter& BaseEmitter, const FNiagaraEmitterDiffResults& DiffResults) const; - FApplyDiffResults AddModule(FString UniqueEmitterName, UNiagaraScript& OwningScript, UNiagaraNodeOutput& TargetOutputNode, TSharedRef AddModule); + FApplyDiffResults AddModule(FString UniqueEmitterName, UNiagaraScript& OwningScript, UNiagaraNodeOutput& TargetOutputNode, TSharedRef AddModule) const; - FApplyDiffResults RemoveInputOverride(UNiagaraScript& OwningScript, TSharedRef OverrideToRemove); + FApplyDiffResults RemoveInputOverride(UNiagaraScript& OwningScript, TSharedRef OverrideToRemove) const; - FApplyDiffResults AddInputOverride(FString UniqueEmitterName, UNiagaraScript& OwningScript, UNiagaraNodeFunctionCall& TargetFunctionCall, TSharedRef OverrideToAdd); - - void CopyPropertiesToBase(void* BaseDataAddress, const void* OtherDataAddress, TArray PropertiesToCopy); + FApplyDiffResults AddInputOverride(FString UniqueEmitterName, UNiagaraScript& OwningScript, UNiagaraNodeFunctionCall& TargetFunctionCall, TSharedRef OverrideToAdd) const; private: TSharedRef GetEmitterMergeAdapterUsingCache(const UNiagaraEmitter& Emitter); TSharedRef GetEmitterMergeAdapterUsingCache(UNiagaraEmitter& Emitter); - void DiffChangeIds(const TMap& InSourceChangeIds, const TMap& InLastMergedChangeIds, const TMap& InInstanceChangeIds, TMap& OutChangeIdsToKeepOnInstance); - FApplyDiffResults ResolveChangeIds(TSharedRef MergedInstanceAdapter, UNiagaraEmitter& OriginalEmitterInstance, const TMap& ChangeIdsThatNeedToBeReset); + void DiffChangeIds(const TMap& InSourceChangeIds, const TMap& InLastMergedChangeIds, const TMap& InInstanceChangeIds, TMap& OutChangeIdsToKeepOnInstance) const; + FApplyDiffResults ResolveChangeIds(TSharedRef MergedInstanceAdapter, UNiagaraEmitter& OriginalEmitterInstance, const TMap& ChangeIdsThatNeedToBeReset) const; private: diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNewEmitterDialog.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNewEmitterDialog.cpp index 417839e87e12..dde2f8db986d 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNewEmitterDialog.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNewEmitterDialog.cpp @@ -33,17 +33,26 @@ void SNewEmitterDialog::Construct(const FArguments& InArgs) LOCTEXT("CreateFromTemplateLabel", "Create a new emitter from an emitter template"), LOCTEXT("TemplatesPickerHeader", "Select a Template Emitter"), SNiagaraNewAssetDialog::FOnGetSelectedAssetsFromPicker::CreateSP(this, &SNewEmitterDialog::GetSelectedEmitterTemplateAssets), + SNiagaraNewAssetDialog::FOnSelectionConfirmed(), SAssignNew(TemplateAssetPicker, SNiagaraTemplateAssetPicker, UNiagaraEmitter::StaticClass()) .OnTemplateAssetActivated(this, &SNewEmitterDialog::OnTemplateAssetActivated)), SNiagaraNewAssetDialog::FNiagaraNewAssetDialogOption( LOCTEXT("CreateFromOtherEmitterLabel", "Copy an existing emitter from your project content"), LOCTEXT("ProjectEmitterPickerHeader", "Select a Project Emitter"), SNiagaraNewAssetDialog::FOnGetSelectedAssetsFromPicker::CreateSP(this, &SNewEmitterDialog::GetSelectedProjectEmiterAssets), + SNiagaraNewAssetDialog::FOnSelectionConfirmed(), + AssetPicker), + SNiagaraNewAssetDialog::FNiagaraNewAssetDialogOption( + LOCTEXT("InheritFromOtherEmitterLabel", "Inherit from an existing emitter in your project content"), + LOCTEXT("InheritProjectEmitterPickerHeader", "Select a Parent Project Emitter"), + SNiagaraNewAssetDialog::FOnGetSelectedAssetsFromPicker::CreateSP(this, &SNewEmitterDialog::GetSelectedProjectEmiterAssets), + SNiagaraNewAssetDialog::FOnSelectionConfirmed::CreateSP(this, &SNewEmitterDialog::InheritanceOptionConfirmed), AssetPicker), SNiagaraNewAssetDialog::FNiagaraNewAssetDialogOption( LOCTEXT("CreateEmptyLabel", "Create an empty emitter with no modules or renderers (Advanced)"), LOCTEXT("EmptyLabel", "Empty Emitter"), SNiagaraNewAssetDialog::FOnGetSelectedAssetsFromPicker(), + SNiagaraNewAssetDialog::FOnSelectionConfirmed(), SNew(SBox) .HAlign(HAlign_Center) .VAlign(VAlign_Center) @@ -64,6 +73,11 @@ TOptional SNewEmitterDialog::GetSelectedEmitterAsset() return TOptional(); } +bool SNewEmitterDialog::GetUseInheritance() const +{ + return bUseInheritance; +} + void SNewEmitterDialog::GetSelectedEmitterTemplateAssets(TArray& OutSelectedAssets) { OutSelectedAssets.Append(TemplateAssetPicker->GetSelectedAssets()); @@ -101,4 +115,9 @@ void SNewEmitterDialog::OnEmitterAssetsActivated(const TArray& Activ } } +void SNewEmitterDialog::InheritanceOptionConfirmed() +{ + bUseInheritance = true; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNewEmitterDialog.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNewEmitterDialog.h index 5332599bdfa0..b4bd41499bd2 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNewEmitterDialog.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNewEmitterDialog.h @@ -23,6 +23,8 @@ public: TOptional GetSelectedEmitterAsset(); + bool GetUseInheritance() const; + private: void GetSelectedEmitterTemplateAssets(TArray& OutSelectedAssets); @@ -32,6 +34,8 @@ private: void OnEmitterAssetsActivated(const TArray& ActivatedAssets, EAssetTypeActivationMethod::Type ActivationMethod); + void InheritanceOptionConfirmed(); + private: TSharedPtr TemplateAssetPicker; @@ -40,4 +44,6 @@ private: FAssetData ActivatedTemplateAsset; FAssetData ActivatedProjectAsset; + + bool bUseInheritance; }; \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNewSystemDialog.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNewSystemDialog.cpp index 08f66449fb1c..d0c13e6101fd 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNewSystemDialog.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNewSystemDialog.cpp @@ -48,12 +48,14 @@ void SNewSystemDialog::Construct(const FArguments& InArgs) LOCTEXT("CreateFromTemplateLabel", "Create a new system from a system template"), LOCTEXT("TemplateLabel", "Select a System Template"), SNiagaraNewAssetDialog::FOnGetSelectedAssetsFromPicker::CreateSP(this, &SNewSystemDialog::GetSelectedSystemTemplateAssets), + SNiagaraNewAssetDialog::FOnSelectionConfirmed(), SAssignNew(TemplateAssetPicker, SNiagaraTemplateAssetPicker, UNiagaraSystem::StaticClass()) .OnTemplateAssetActivated(this, &SNewSystemDialog::OnTemplateAssetActivated)), SNiagaraNewAssetDialog::FNiagaraNewAssetDialogOption( LOCTEXT("CreateFromSelectedEmittersLabel", "Create a new system from a set of selected emitters"), LOCTEXT("ProjectEmittersLabel", "Select Emitters to Add"), SNiagaraNewAssetDialog::FOnGetSelectedAssetsFromPicker::CreateSP(this, &SNewSystemDialog::GetSelectedProjectEmiterAssets), + SNiagaraNewAssetDialog::FOnSelectionConfirmed(), SNew(SVerticalBox) + SVerticalBox::Slot() .Padding(0, 0, 0, 10) @@ -109,11 +111,13 @@ void SNewSystemDialog::Construct(const FArguments& InArgs) LOCTEXT("CreateFromOtherSystemLabel", "Copy an existing system from your project content"), LOCTEXT("ProjectSystemsLabel", "Select a Project System"), SNiagaraNewAssetDialog::FOnGetSelectedAssetsFromPicker::CreateSP(this, &SNewSystemDialog::GetSelectedProjectSystemAssets), + SNiagaraNewAssetDialog::FOnSelectionConfirmed(), SystemAssetPicker), SNiagaraNewAssetDialog::FNiagaraNewAssetDialogOption( LOCTEXT("CreateEmptyLabel", "Create an empty system with no emitters"), LOCTEXT("EmptyLabel", "Empty System"), SNiagaraNewAssetDialog::FOnGetSelectedAssetsFromPicker(), + SNiagaraNewAssetDialog::FOnSelectionConfirmed(), SNew(SBox) .HAlign(HAlign_Center) .VAlign(VAlign_Center) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNiagaraNewAssetDialog.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNiagaraNewAssetDialog.cpp index 59bac5f5927a..d3c8fcbdc5a4 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNiagaraNewAssetDialog.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNiagaraNewAssetDialog.cpp @@ -197,6 +197,7 @@ void SNiagaraNewAssetDialog::ConfirmSelection() SelectedOption.OnGetSelectedAssetsFromPicker.Execute(SelectedAssets); ensureMsgf(SelectedAssets.Num() > 0, TEXT("No assets selected when dialog was confirmed.")); } + SelectedOption.OnSelectionConfirmed.ExecuteIfBound(); bUserConfirmedSelection = true; RequestDestroyWindow(); } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNiagaraNewAssetDialog.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNiagaraNewAssetDialog.h index 7e8f35052b17..5cb06d12f6f6 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNiagaraNewAssetDialog.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/SNiagaraNewAssetDialog.h @@ -14,6 +14,7 @@ class SNiagaraNewAssetDialog : public SWindow { public: DECLARE_DELEGATE_OneParam(FOnGetSelectedAssetsFromPicker, TArray& /* OutSelectedAssets */); + DECLARE_DELEGATE(FOnSelectionConfirmed); public: class FNiagaraNewAssetDialogOption @@ -23,12 +24,14 @@ public: FText AssetPickerHeader; TSharedRef AssetPicker; FOnGetSelectedAssetsFromPicker OnGetSelectedAssetsFromPicker; + FOnSelectionConfirmed OnSelectionConfirmed; - FNiagaraNewAssetDialogOption(FText InOptionText, FText InAssetPickerHeader, FOnGetSelectedAssetsFromPicker InOnGetSelectedAssetsFromPicker, TSharedRef InAssetPicker) + FNiagaraNewAssetDialogOption(FText InOptionText, FText InAssetPickerHeader, FOnGetSelectedAssetsFromPicker InOnGetSelectedAssetsFromPicker, FOnSelectionConfirmed InOnSelecitonConfirmed, TSharedRef InAssetPicker) : OptionText(InOptionText) , AssetPickerHeader(InAssetPickerHeader) , AssetPicker(InAssetPicker) , OnGetSelectedAssetsFromPicker(InOnGetSelectedAssetsFromPicker) + , OnSelectionConfirmed(InOnSelecitonConfirmed) { } }; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraScriptToolkit.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraScriptToolkit.cpp index 922e4a40c141..761948fac626 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraScriptToolkit.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraScriptToolkit.cpp @@ -143,15 +143,17 @@ void FNiagaraScriptToolkit::Initialize( const EToolkitMode::Type Mode, const TSh { DisplayName = FAssetTypeActions_NiagaraScriptDynamicInputs::GetFormattedName(); } - ScriptViewModel = MakeShareable(new FNiagaraScriptViewModel(EditedNiagaraScript, DisplayName, ENiagaraParameterEditMode::EditAll)); + + const FGuid MessageLogGuidKey = FGuid::NewGuid(); + NiagaraMessageLogViewModel = MakeShared(GetNiagaraScriptMessageLogName(EditedNiagaraScript), MessageLogGuidKey, NiagaraMessageLog); + + ScriptViewModel = MakeShareable(new FNiagaraStandaloneScriptViewModel(EditedNiagaraScript, DisplayName, ENiagaraParameterEditMode::EditAll, NiagaraMessageLogViewModel, OriginalNiagaraScript, MessageLogGuidKey)); OnEditedScriptGraphChangedHandle = ScriptViewModel->GetGraphViewModel()->GetGraph()->AddOnGraphNeedsRecompileHandler( FOnGraphChanged::FDelegate::CreateRaw(this, &FNiagaraScriptToolkit::OnEditedScriptGraphChanged)); DetailsScriptSelection = MakeShareable(new FNiagaraObjectSelection()); DetailsScriptSelection->SetSelectedObject(EditedNiagaraScript); - - const FGuid MessageLogGuidKey = FGuid::NewGuid(); FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked("MessageLog"); //@todo(message manager) remove stats listing FMessageLogInitializationOptions LogOptions; @@ -163,8 +165,6 @@ void FNiagaraScriptToolkit::Initialize( const EToolkitMode::Type Mode, const TSh StatsListing = MessageLogModule.CreateLogListing("MaterialEditorStats", LogOptions); Stats = MessageLogModule.CreateLogListingWidget(StatsListing.ToSharedRef()); - NiagaraMessageLogViewModel = MakeShared(GetNiagaraScriptMessageLogName(EditedNiagaraScript), MessageLogGuidKey, NiagaraMessageLog); - TSharedRef StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_Niagara_Layout_v8") ->AddArea ( @@ -685,7 +685,7 @@ void FNiagaraScriptToolkit::UpdateOriginalNiagaraScript() for (int32 i = 0; i < System->GetNumEmitters(); i++) { AffectedEmitters.AddUnique(System->GetEmitterHandle(i).GetInstance()); - AffectedEmitters.AddUnique((UNiagaraEmitter*)System->GetEmitterHandle(i).GetSource()); + AffectedEmitters.AddUnique((UNiagaraEmitter*)System->GetEmitterHandle(i).GetInstance()->GetParent()); } } } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraScriptToolkit.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraScriptToolkit.h index ad49a033d7b4..4d4eaf024f25 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraScriptToolkit.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraScriptToolkit.h @@ -21,6 +21,7 @@ class FNiagaraScriptViewModel; class FNiagaraObjectSelection; struct FEdGraphEditAction; class FNiagaraMessageLogViewModel; +class FNiagaraStandaloneScriptViewModel; /** Viewer/editor for a DataTable */ class FNiagaraScriptToolkit : public FAssetEditorToolkit, public FGCObject @@ -122,7 +123,7 @@ private: private: /** The Script being edited */ - TSharedPtr ScriptViewModel; + TSharedPtr ScriptViewModel; /** The selection displayed by the details tab. */ TSharedPtr DetailsScriptSelection; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraSystemToolkit.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraSystemToolkit.cpp index 0fd242d30025..f472c75ce776 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraSystemToolkit.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraSystemToolkit.cpp @@ -258,9 +258,6 @@ void FNiagaraSystemToolkit::InitializeWithEmitter(const EToolkitMode::Type Mode, SystemViewModel->SetToolkitCommands(GetToolkitCommands()); SystemViewModel->AddEmitter(*Emitter); - // Always remove the source information from the emitter handle when editing an emitter asset because inheritance is not valid in this case. - System->GetEmitterHandle(0).RemoveSource(); - // Adding the emitter to the system has made a copy of it and we set this to the copy's change id here instead of the original emitter's change // id because the copy's change id may have been updated from the original as part of post load and we use this id to detect if the editable // emitter has been changed. @@ -1053,10 +1050,7 @@ void FNiagaraSystemToolkit::UpdateOriginalEmitter() LastSyncedEmitterChangeId = EditableEmitter->GetChangeId(); bEmitterThumbnailUpdated = false; - TArray AffectedEmitters; - AffectedEmitters.Add(Emitter); UpdateExistingEmitters(); - GWarn->EndSlowTask(); } else if(bEmitterThumbnailUpdated) @@ -1068,28 +1062,76 @@ void FNiagaraSystemToolkit::UpdateOriginalEmitter() } } +void MergeEmittersRecursively(UNiagaraEmitter* ChangedEmitter, const TMap>& EmitterToReferencingEmittersMap, TSet& OutMergedEmitters) +{ + const TArray* ReferencingEmitters = EmitterToReferencingEmittersMap.Find(ChangedEmitter); + if (ReferencingEmitters != nullptr) + { + for (UNiagaraEmitter* ReferencingEmitter : (*ReferencingEmitters)) + { + if (ReferencingEmitter->IsSynchronizedWithParent() == false) + { + ReferencingEmitter->MergeChangesFromParent(); + OutMergedEmitters.Add(ReferencingEmitter); + MergeEmittersRecursively(ReferencingEmitter, EmitterToReferencingEmittersMap, OutMergedEmitters); + } + } + } +} + void FNiagaraSystemToolkit::UpdateExistingEmitters() { + // Build a tree of references from the currently loaded emitters so that we can efficiently find all emitters that reference the modified emitter. + TMap> EmitterToReferencingEmittersMap; + UNiagaraEmitter* EditableCopy = System->GetEmitterHandles()[0].GetInstance(); + for (TObjectIterator EmitterIterator; EmitterIterator; ++EmitterIterator) + { + UNiagaraEmitter* LoadedEmitter = *EmitterIterator; + if (LoadedEmitter != EditableCopy && LoadedEmitter->GetParent() != nullptr) + { + TArray& ReferencingEmitters = EmitterToReferencingEmittersMap.FindOrAdd(LoadedEmitter->GetParent()); + ReferencingEmitters.Add(LoadedEmitter); + } + } + + // Recursively merge emitters by traversing the reference chains. + TSet MergedEmitters; + MergeEmittersRecursively(Emitter, EmitterToReferencingEmittersMap, MergedEmitters); + + // find referencing systems, aside from the system being edited by this toolkit and request that they recompile, + // also refresh their view models, and reinitialize their components. for (TObjectIterator SystemIterator; SystemIterator; ++SystemIterator) { UNiagaraSystem* LoadedSystem = *SystemIterator; - if (LoadedSystem != System && LoadedSystem->IsPendingKill() == false && - LoadedSystem->HasAnyFlags(RF_ClassDefaultObject) == false && - LoadedSystem->ReferencesSourceEmitter(*Emitter)) + LoadedSystem->HasAnyFlags(RF_ClassDefaultObject) == false) { - LoadedSystem->UpdateFromEmitterChanges(*Emitter); - TArray> ReferencingSystemViewModels; - FNiagaraSystemViewModel::GetAllViewModelsForObject(LoadedSystem, ReferencingSystemViewModels); - - for (TSharedPtr ReferencingSystemViewModel : ReferencingSystemViewModels) + bool bUsesMergedEmitterDirectly = false; + for (const FNiagaraEmitterHandle& EmitterHandle : LoadedSystem->GetEmitterHandles()) { - ReferencingSystemViewModel->RefreshAll(); + if (MergedEmitters.Contains(EmitterHandle.GetInstance())) + { + bUsesMergedEmitterDirectly = true; + break; + } } - if (ReferencingSystemViewModels.Num() == 0) + if (bUsesMergedEmitterDirectly) { + // Request that the system recompile. + bool bForce = false; + LoadedSystem->RequestCompile(bForce); + + // Invalidate any view models. + TArray> ReferencingSystemViewModels; + FNiagaraSystemViewModel::GetAllViewModelsForObject(LoadedSystem, ReferencingSystemViewModels); + for (TSharedPtr ReferencingSystemViewModel : ReferencingSystemViewModels) + { + ReferencingSystemViewModel->RefreshAll(); + } + + // Reinit any running components for (TObjectIterator ComponentIterator; ComponentIterator; ++ComponentIterator) { UNiagaraComponent* Component = *ComponentIterator; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraDataInterfaceCurveTypeEditorUtilities.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraDataInterfaceCurveTypeEditorUtilities.cpp index 9771d7b7afa6..103a19d59a9f 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraDataInterfaceCurveTypeEditorUtilities.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/TypeEditorUtilities/NiagaraDataInterfaceCurveTypeEditorUtilities.cpp @@ -36,7 +36,7 @@ TSharedPtr FNiagaraDataInterfaceCurveTypeEditorUtilitiesBase::CreateDat return SNew(SComboButton) .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") .ForegroundColor(FSlateColor::UseForeground()) - .OnGetMenuContent_Raw(this, &FNiagaraDataInterfaceCurveTypeEditorUtilitiesBase::GetImportMenuContent, MakeWeakObjectPtr(CurveDataInterface), DataInterfaceChangedHandler) + .OnGetMenuContent_Raw(const_cast(this), &FNiagaraDataInterfaceCurveTypeEditorUtilitiesBase::GetImportMenuContent, MakeWeakObjectPtr(CurveDataInterface), DataInterfaceChangedHandler) .ButtonContent() [ SNew(STextBlock) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraEmitterHandleViewModel.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraEmitterHandleViewModel.cpp index f9542041ef99..ff9b276a1122 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraEmitterHandleViewModel.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraEmitterHandleViewModel.cpp @@ -162,24 +162,6 @@ FText FNiagaraEmitterHandleViewModel::GetNameText() const return FText(); } -FText FNiagaraEmitterHandleViewModel::GetSourceNameText() const -{ - if (EmitterHandle && EmitterHandle->GetSource()) - { - return FText::FromString(EmitterHandle->GetSource()->GetName()); - } - return FText(); -} - -FText FNiagaraEmitterHandleViewModel::GetSourcePathNameText() const -{ - if (EmitterHandle && EmitterHandle->GetSource()) - { - return FText::FromString(EmitterHandle->GetSource()->GetPathName()); - } - return FText(); -} - void FNiagaraEmitterHandleViewModel::OnNameTextComitted(const FText& InText, ETextCommit::Type CommitInfo) { SetName(*InText.ToString()); @@ -240,19 +222,6 @@ TSharedPtr FNiagaraEmitterHandleViewModel::GetEmitterV return EmitterViewModel; } - -void FNiagaraEmitterHandleViewModel::OpenSourceEmitter() -{ - if (EmitterHandle) - { - UNiagaraEmitter* EmitterSource = const_cast(EmitterHandle->GetSource()); - if (EmitterSource) - { - FAssetEditorManager::Get().OpenEditorForAsset(EmitterSource); - } - } -} - FNiagaraEmitterHandleViewModel::FOnPropertyChanged& FNiagaraEmitterHandleViewModel::OnPropertyChanged() { return OnPropertyChangedDelegate; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraEmitterViewModel.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraEmitterViewModel.cpp index c0b8ef911428..a0f3d20d9c94 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraEmitterViewModel.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraEmitterViewModel.cpp @@ -116,6 +116,28 @@ UNiagaraEmitter* FNiagaraEmitterViewModel::GetEmitter() return Emitter.Get(); } +const UNiagaraEmitter* FNiagaraEmitterViewModel::GetParentEmitter() const +{ + return Emitter.IsValid() ? Emitter->GetParent() : nullptr; +} + +FText FNiagaraEmitterViewModel::GetParentNameText() const +{ + if (Emitter.IsValid() && Emitter->GetParent() != nullptr) + { + return FText::FromString(Emitter->GetParent()->GetName()); + } + return FText(); +} + +FText FNiagaraEmitterViewModel::GetParentPathNameText() const +{ + if (Emitter.IsValid() && Emitter->GetParent() != nullptr) + { + return FText::FromString(Emitter->GetParent()->GetPathName()); + } + return FText(); +} FText FNiagaraEmitterViewModel::GetStatsText() const { diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraSystemViewModel.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraSystemViewModel.cpp index faaad571e984..963c8f8a0b51 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraSystemViewModel.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraSystemViewModel.cpp @@ -310,9 +310,17 @@ void FNiagaraSystemViewModel::AddEmitter(UNiagaraEmitter& Emitter) if (EditMode == ENiagaraSystemViewModelEditMode::SystemAsset) { System.Modify(); + EmitterHandle = System.AddEmitterHandle(Emitter, FNiagaraUtilities::GetUniqueName(Emitter.GetFName(), EmitterHandleNames)); } - EmitterHandle = System.AddEmitterHandle(Emitter, FNiagaraUtilities::GetUniqueName(Emitter.GetFName(), EmitterHandleNames)); - + else if (EditMode == ENiagaraSystemViewModelEditMode::EmitterAsset) + { + // When editing an emitter asset we add the emitter as a duplicate so that the parent emitter is duplicated, but it's parent emitter + // information is maintained. + checkf(System.GetNumEmitters() == 0, TEXT("Can not add multiple emitters to a system being edited in emitter asset mode.")); + FNiagaraEmitterHandle TemporaryEmitterHandle(Emitter); + EmitterHandle = System.DuplicateEmitterHandle(TemporaryEmitterHandle, *Emitter.GetUniqueEmitterName()); + } + check(SystemScriptViewModel.IsValid()); FNiagaraStackGraphUtilities::RebuildEmitterNodes(System); @@ -678,7 +686,7 @@ void FNiagaraSystemViewModel::SendLastCompileMessageJobs() const WarningCount++; } - JobBatchToQueue.Add(MakeShared(CompileEvent, TWeakObjectPtr(ScriptInfo.Script), ScriptInfo.OwningScriptNameString)); + JobBatchToQueue.Add(MakeShared(CompileEvent, MakeWeakObjectPtr(const_cast(ScriptInfo.Script)), ScriptInfo.OwningScriptNameString)); } } JobBatchToQueue.Insert(MakeShared(ErrorCount, WarningCount, GetLatestCompileStatus(), FText::FromString("System")), 0); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackEmitterSpawnScriptItemGroup.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackEmitterSpawnScriptItemGroup.cpp index 7e069aa9bde2..78f9ad15cd48 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackEmitterSpawnScriptItemGroup.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackEmitterSpawnScriptItemGroup.cpp @@ -38,7 +38,7 @@ bool UNiagaraStackEmitterPropertiesItem::CanResetToBase() const { if (bCanResetToBaseCache.IsSet() == false) { - const UNiagaraEmitter* BaseEmitter = FNiagaraStackGraphUtilities::GetBaseEmitter(*Emitter.Get(), GetSystemViewModel()->GetSystem()); + const UNiagaraEmitter* BaseEmitter = GetEmitterViewModel()->GetEmitter()->GetParent(); if (BaseEmitter != nullptr && Emitter != BaseEmitter) { TSharedRef MergeManager = FNiagaraScriptMergeManager::Get(); @@ -56,7 +56,7 @@ void UNiagaraStackEmitterPropertiesItem::ResetToBase() { if (CanResetToBase()) { - const UNiagaraEmitter* BaseEmitter = FNiagaraStackGraphUtilities::GetBaseEmitter(*Emitter.Get(), GetSystemViewModel()->GetSystem()); + const UNiagaraEmitter* BaseEmitter = GetEmitterViewModel()->GetEmitter()->GetParent(); TSharedRef MergeManager = FNiagaraScriptMergeManager::Get(); MergeManager->ResetEmitterEditablePropertySetToBase(*Emitter, *BaseEmitter); } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackEntry.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackEntry.cpp index 1e14f11b6bfd..a50376db3683 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackEntry.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackEntry.cpp @@ -391,7 +391,7 @@ bool UNiagaraStackEntry::HasBaseEmitter() const if (bHasBaseEmitterCache.IsSet() == false) { TSharedRef MergeManager = FNiagaraScriptMergeManager::Get(); - const UNiagaraEmitter* BaseEmitter = FNiagaraStackGraphUtilities::GetBaseEmitter(*GetEmitterViewModel()->GetEmitter(), GetSystemViewModel()->GetSystem()); + const UNiagaraEmitter* BaseEmitter = GetEmitterViewModel()->GetParentEmitter(); bHasBaseEmitterCache = BaseEmitter != nullptr; } return bHasBaseEmitterCache.GetValue(); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackEventScriptItemGroup.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackEventScriptItemGroup.cpp index 3fff1a50498f..9ba613bd7939 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackEventScriptItemGroup.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackEventScriptItemGroup.cpp @@ -57,7 +57,7 @@ bool UNiagaraStackEventHandlerPropertiesItem::CanResetToBase() const { if (HasBaseEventHandler()) { - const UNiagaraEmitter* BaseEmitter = FNiagaraStackGraphUtilities::GetBaseEmitter(*Emitter.Get(), GetSystemViewModel()->GetSystem()); + const UNiagaraEmitter* BaseEmitter = GetEmitterViewModel()->GetEmitter()->GetParent(); if (BaseEmitter != nullptr && Emitter != BaseEmitter) { TSharedRef MergeManager = FNiagaraScriptMergeManager::Get(); @@ -80,7 +80,7 @@ void UNiagaraStackEventHandlerPropertiesItem::ResetToBase() { if (CanResetToBase()) { - const UNiagaraEmitter* BaseEmitter = FNiagaraStackGraphUtilities::GetBaseEmitter(*Emitter.Get(), GetSystemViewModel()->GetSystem()); + const UNiagaraEmitter* BaseEmitter = GetEmitterViewModel()->GetEmitter()->GetParent(); TSharedRef MergeManager = FNiagaraScriptMergeManager::Get(); MergeManager->ResetEventHandlerPropertySetToBase(*Emitter, *BaseEmitter, EventScriptUsageId); RefreshChildren(); @@ -173,7 +173,7 @@ bool UNiagaraStackEventHandlerPropertiesItem::HasBaseEventHandler() const { if (bHasBaseEventHandlerCache.IsSet() == false) { - const UNiagaraEmitter* BaseEmitter = FNiagaraStackGraphUtilities::GetBaseEmitter(*Emitter.Get(), GetSystemViewModel()->GetSystem()); + const UNiagaraEmitter* BaseEmitter = GetEmitterViewModel()->GetEmitter()->GetParent(); if (BaseEmitter != nullptr && Emitter != BaseEmitter) { TSharedRef MergeManager = FNiagaraScriptMergeManager::Get(); @@ -287,7 +287,7 @@ bool UNiagaraStackEventScriptItemGroup::HasBaseEventHandler() const { if (bHasBaseEventHandlerCache.IsSet() == false) { - const UNiagaraEmitter* BaseEmitter = FNiagaraStackGraphUtilities::GetBaseEmitter(*GetEmitterViewModel()->GetEmitter(), GetSystemViewModel()->GetSystem()); + const UNiagaraEmitter* BaseEmitter = GetEmitterViewModel()->GetEmitter()->GetParent(); bHasBaseEventHandlerCache = BaseEmitter != nullptr && FNiagaraScriptMergeManager::Get()->HasBaseEventHandler(*BaseEmitter, GetScriptUsageId()); } return bHasBaseEventHandlerCache.GetValue(); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInput.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInput.cpp index 43fb1a8d0c4e..d20d121e1c06 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInput.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInput.cpp @@ -1277,7 +1277,7 @@ bool UNiagaraStackFunctionInput::CanResetToBase() const UNiagaraNodeOutput* OutputNode = FNiagaraStackGraphUtilities::GetEmitterOutputNodeForStackNode(*OwningFunctionCallNode.Get()); if(MergeManager->IsMergeableScriptUsage(OutputNode->GetUsage())) { - const UNiagaraEmitter* BaseEmitter = FNiagaraStackGraphUtilities::GetBaseEmitter(*GetEmitterViewModel()->GetEmitter(), GetSystemViewModel()->GetSystem()); + const UNiagaraEmitter* BaseEmitter = GetEmitterViewModel()->GetParentEmitter(); bCanResetToBaseCache = BaseEmitter != nullptr && MergeManager->IsModuleInputDifferentFromBase( *GetEmitterViewModel()->GetEmitter(), @@ -1308,17 +1308,7 @@ void UNiagaraStackFunctionInput::ResetToBase() { TSharedRef MergeManager = FNiagaraScriptMergeManager::Get(); - TSharedPtr ThisEmitterHandleViewModel; - for (TSharedRef EmitterHandleViewModel : GetSystemViewModel()->GetEmitterHandleViewModels()) - { - if (EmitterHandleViewModel->GetEmitterViewModel() == GetEmitterViewModel()) - { - ThisEmitterHandleViewModel = EmitterHandleViewModel; - break; - } - } - - const UNiagaraEmitter* BaseEmitter = ThisEmitterHandleViewModel->GetEmitterHandle()->GetSource(); + const UNiagaraEmitter* BaseEmitter = GetEmitterViewModel()->GetEmitter()->GetParent(); UNiagaraNodeOutput* OutputNode = FNiagaraStackGraphUtilities::GetEmitterOutputNodeForStackNode(*OwningFunctionCallNode.Get()); FScopedTransaction ScopedTransaction(LOCTEXT("ResetInputToBaseTransaction", "Reset this input to match the parent emitter.")); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInputCollection.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInputCollection.cpp index 8e3eb231f833..1f82ffd898f7 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInputCollection.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInputCollection.cpp @@ -85,6 +85,7 @@ void UNiagaraStackFunctionInputCollection::RefreshChildrenInternal(const TArray< TArray ProcessedInputNames; TArray DuplicateInputNames; TArray ValidAliasedInputNames; + TMap StaticSwitchInputs; TArray PinsWithInvalidTypes; FText UncategorizedName = LOCTEXT("Uncategorized", "Uncategorized"); @@ -129,9 +130,9 @@ void UNiagaraStackFunctionInputCollection::RefreshChildrenInternal(const TArray< } // Gather static switch parameters - InputPins.Reset(); - FNiagaraStackGraphUtilities::GetStackFunctionStaticSwitchPins(*InputFunctionCallNode, InputPins); - for (const UEdGraphPin* InputPin : InputPins) + TArray SwitchPins; + FNiagaraStackGraphUtilities::GetStackFunctionStaticSwitchPins(*InputFunctionCallNode, SwitchPins); + for (UEdGraphPin* InputPin : SwitchPins) { // The static switch pin names to not contain the module namespace, as they are not part of the parameter maps. // We add it here only to check for name clashes with actual module parameters. @@ -152,7 +153,9 @@ void UNiagaraStackFunctionInputCollection::RefreshChildrenInternal(const TArray< PinsWithInvalidTypes.Add(InputPin); continue; } - ValidAliasedInputNames.Add(FNiagaraParameterHandle(*InputFunctionCallNode->GetFunctionName(), InputPin->PinName).GetParameterHandleString()); + + FName AliasedName = FNiagaraParameterHandle(*InputFunctionCallNode->GetFunctionName(), InputPin->PinName).GetParameterHandleString(); + StaticSwitchInputs.Add(AliasedName, InputPin); TOptional InputMetaData; if (InputFunctionGraph != nullptr) @@ -185,7 +188,7 @@ void UNiagaraStackFunctionInputCollection::RefreshChildrenInternal(const TArray< } else { - return A.Pin->PinName < B.Pin->PinName; + return A.Pin->PinName.LexicalLess(B.Pin->PinName); } }); @@ -221,23 +224,49 @@ void UNiagaraStackFunctionInputCollection::RefreshChildrenInternal(const TArray< } InputCategory->AddInput(InputData.Pin->PinName, InputData.Type, InputData.bIsStatic ? EStackParameterBehavior::Static : EStackParameterBehavior::Dynamic); } - RefreshIssues(DuplicateInputNames, ValidAliasedInputNames, PinsWithInvalidTypes, NewIssues); + RefreshIssues(DuplicateInputNames, ValidAliasedInputNames, PinsWithInvalidTypes, StaticSwitchInputs, NewIssues); } -void UNiagaraStackFunctionInputCollection::RefreshIssues(TArray DuplicateInputNames, TArray ValidAliasedInputNames, TArray PinsWithInvalidTypes, TArray& NewIssues) +UNiagaraStackEntry::FStackIssueFix UNiagaraStackFunctionInputCollection::GetNodeRemovalFix(UEdGraphPin* PinToRemove, FText FixDescription) +{ + return FStackIssueFix( + FixDescription, + UNiagaraStackEntry::FStackIssueFixDelegate::CreateLambda([=]() + { + FScopedTransaction ScopedTransaction(FixDescription); + TArray> RemovedDataObjects; + FNiagaraStackGraphUtilities::RemoveNodesForStackFunctionInputOverridePin(*PinToRemove, RemovedDataObjects); + for (TWeakObjectPtr RemovedDataObject : RemovedDataObjects) + { + if (RemovedDataObject.IsValid()) + { + OnDataObjectModified().Broadcast(RemovedDataObject.Get()); + } + } + PinToRemove->GetOwningNode()->RemovePin(PinToRemove); + })); +} + +void UNiagaraStackFunctionInputCollection::RefreshIssues(TArray DuplicateInputNames, TArray ValidAliasedInputNames, TArray PinsWithInvalidTypes, TMap StaticSwitchInputs, TArray& NewIssues) { if (!GetIsEnabled()) { NewIssues.Empty(); return; } - // Try to find function input overrides which are no longer valid so we can generate errors for them. + + // Gather override nodes to find candidates that were replaced by static switches and are no longer valid + TArray OverridePins; UNiagaraNodeParameterMapSet* OverrideNode = FNiagaraStackGraphUtilities::GetStackFunctionOverrideNode(*InputFunctionCallNode); if (OverrideNode != nullptr) { - TArray OverridePins; OverrideNode->GetInputPins(OverridePins); - for (UEdGraphPin* OverridePin : OverridePins) + } + for (UEdGraphPin* OverridePin : OverridePins) + { + // Try to find function input overrides which are no longer valid so we can generate errors for them. + UEdGraphPin** PinReference = StaticSwitchInputs.Find(OverridePin->PinName); + if (PinReference == nullptr) { // If the pin isn't in the misc category for the add pin, and not the parameter map pin, and it's for this function call, // check to see if it's in the list of valid input names, and if not generate an error. @@ -246,12 +275,39 @@ void UNiagaraStackFunctionInputCollection::RefreshIssues(TArray Duplicate FNiagaraParameterHandle(OverridePin->PinName).GetNamespace().ToString() == InputFunctionCallNode->GetFunctionName() && ValidAliasedInputNames.Contains(OverridePin->PinName) == false) { - FText FixDescription = LOCTEXT("RemoveInvalidInputTransaction", "Remove invalid input override."); - FStackIssueFix RemoveInputOverrideFix( - FixDescription, + FStackIssue InvalidInputOverrideError( + EStackIssueSeverity::Error, + FText::Format(LOCTEXT("InvalidInputSummaryFormat", "Invalid Input Override: {0}"), FText::FromString(OverridePin->PinName.ToString())), + FText::Format(LOCTEXT("InvalidInputFormat", "The input {0} was previously overriden but is no longer exposed by the function {1}.\nPress the fix button to remove this unused override data,\nor check the function definition to see why this input is no longer exposed."), + FText::FromString(OverridePin->PinName.ToString()), FText::FromString(InputFunctionCallNode->GetFunctionName())), + GetStackEditorDataKey(), + false, + GetNodeRemovalFix(OverridePin, LOCTEXT("RemoveInvalidInputTransaction", "Remove input override"))); + + NewIssues.Add(InvalidInputOverrideError); + } + } + else + { + // If we have an override pin that is no longer valid, but has the same name and type as a static switch parameter, then it is safe to assume + // that the parameter was replaced by the static switch. So we ask the user to copy over its value or remove the override. + UEdGraphPin* SwitchPin = *PinReference; + bool bIsSameType = OverridePin->PinType.PinCategory == SwitchPin->PinType.PinCategory && + OverridePin->PinType.PinSubCategoryObject == SwitchPin->PinType.PinSubCategoryObject; + if (bIsSameType && !ValidAliasedInputNames.Contains(OverridePin->PinName)) + { + TArray Fixes; + + // first possible fix: convert the value over to the static switch + FText ConversionFixDescription = LOCTEXT("ConvertInputToStaticSwitchTransaction", "Copy value to static switch parameter"); + FStackIssueFix ConvertInputOverrideFix( + ConversionFixDescription, UNiagaraStackEntry::FStackIssueFixDelegate::CreateLambda([=]() { - FScopedTransaction ScopedTransaction(FixDescription); + FScopedTransaction ScopedTransaction(ConversionFixDescription); + SwitchPin->Modify(); + SwitchPin->DefaultValue = OverridePin->DefaultValue; + TArray> RemovedDataObjects; FNiagaraStackGraphUtilities::RemoveNodesForStackFunctionInputOverridePin(*OverridePin, RemovedDataObjects); for (TWeakObjectPtr RemovedDataObject : RemovedDataObjects) @@ -263,19 +319,25 @@ void UNiagaraStackFunctionInputCollection::RefreshIssues(TArray Duplicate } OverridePin->GetOwningNode()->RemovePin(OverridePin); })); + Fixes.Add(ConvertInputOverrideFix); - FStackIssue InvalidInputOverrideError( + // second possible fix: remove the override completely + Fixes.Add(GetNodeRemovalFix(OverridePin, LOCTEXT("RemoveInvalidInputTransaction", "Remove input override (WARNING: this could result in different behavior!)"))); + + FStackIssue DeprecatedInputOverrideError( EStackIssueSeverity::Error, - FText::Format(LOCTEXT("InvalidInputSummaryFormat", "Invalid Input Override: {0}"), FText::FromString(OverridePin->PinName.ToString())), - FText::Format(LOCTEXT("InvalidInputFormat", "The input {0} was previously overriden but is no longer exposed by the function {1}.\nPress the fix button to remove this unused override data,\nor check the function definition to see why this input is no longer exposed."), + FText::Format(LOCTEXT("DeprecatedInputSummaryFormat", "Deprecated Input Override: {0}"), FText::FromString(OverridePin->PinName.ToString())), + FText::Format(LOCTEXT("DeprecatedInputFormat", "The input {0} is no longer exposed by the function {1}, but there exists a static switch parameter with the same name instead.\nYou can choose to copy the previously entered data over to the new parameter or remove the override to discard it."), FText::FromString(OverridePin->PinName.ToString()), FText::FromString(InputFunctionCallNode->GetFunctionName())), GetStackEditorDataKey(), false, - RemoveInputOverrideFix); + Fixes); - NewIssues.Add(InvalidInputOverrideError); + NewIssues.Add(DeprecatedInputOverrideError); + break; } } + } // Generate issues for duplicate input names. diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackGraphUtilities.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackGraphUtilities.cpp index 8037eb7c2b80..2e7a7950ec87 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackGraphUtilities.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackGraphUtilities.cpp @@ -526,7 +526,7 @@ void FNiagaraStackGraphUtilities::GetStackFunctionInputPins(UNiagaraNodeFunction } } -void FNiagaraStackGraphUtilities::GetStackFunctionStaticSwitchPins(UNiagaraNodeFunctionCall& FunctionCallNode, TArray& OutInputPins) +void FNiagaraStackGraphUtilities::GetStackFunctionStaticSwitchPins(UNiagaraNodeFunctionCall& FunctionCallNode, TArray& OutInputPins) { const UEdGraphSchema_Niagara* Schema = CastChecked(FunctionCallNode.GetSchema()); UNiagaraGraph* FunctionCallGraph = FunctionCallNode.GetCalledGraph(); @@ -596,6 +596,11 @@ UNiagaraNodeParameterMapSet& FNiagaraStackGraphUtilities::GetOrCreateStackFuncti UEdGraphPin* FNiagaraStackGraphUtilities::GetStackFunctionInputOverridePin(UNiagaraNodeFunctionCall& StackFunctionCall, FNiagaraParameterHandle AliasedInputParameterHandle) { + if (UEdGraphPin* SwitchPin = StackFunctionCall.FindStaticSwitchInputPin(AliasedInputParameterHandle.GetName())) + { + return SwitchPin; + } + UNiagaraNodeParameterMapSet* OverrideNode = GetStackFunctionOverrideNode(StackFunctionCall); if (OverrideNode != nullptr) { @@ -1149,27 +1154,6 @@ UNiagaraNodeOutput* FNiagaraStackGraphUtilities::ResetGraphForOutput(UNiagaraGra return OutputNode; } -const UNiagaraEmitter* FNiagaraStackGraphUtilities::GetBaseEmitter(UNiagaraEmitter& Emitter, UNiagaraSystem& OwningSystem) -{ - for(const FNiagaraEmitterHandle& Handle : OwningSystem.GetEmitterHandles()) - { - if (Handle.GetInstance() == &Emitter) - { - if (Handle.GetSource() != nullptr && Handle.GetSource() != &Emitter) - { - return Handle.GetSource(); - } - else - { - // If the source is null then it was deleted and if the source is the same as the emitter the owning - // system is transient and the emitter doesn't have base. - return nullptr; - } - } - } - return nullptr; -} - void GetFunctionNamesRecursive(UNiagaraNode* CurrentNode, TArray& VisitedNodes, TArray& FunctionNames) { if (VisitedNodes.Contains(CurrentNode) == false) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackModuleItem.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackModuleItem.cpp index cdc741e83c9a..9d7c89cf9e0a 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackModuleItem.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackModuleItem.cpp @@ -699,7 +699,7 @@ bool UNiagaraStackModuleItem::CanMoveAndDelete() const // When editing systems only non-base modules can be moved and deleted. TSharedRef MergeManager = FNiagaraScriptMergeManager::Get(); - const UNiagaraEmitter* BaseEmitter = FNiagaraStackGraphUtilities::GetBaseEmitter(*GetEmitterViewModel()->GetEmitter(), GetSystemViewModel()->GetSystem()); + const UNiagaraEmitter* BaseEmitter = GetEmitterViewModel()->GetEmitter()->GetParent(); bool bIsMergeable = MergeManager->IsMergeableScriptUsage(OutputNode->GetUsage()); bool bHasBaseModule = bIsMergeable && BaseEmitter != nullptr && MergeManager->HasBaseModule(*BaseEmitter, OutputNode->GetUsage(), OutputNode->GetUsageId(), FunctionCallNode->NodeGuid); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackPropertyRow.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackPropertyRow.cpp index 6d570f215a6d..1680ca9855d2 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackPropertyRow.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackPropertyRow.cpp @@ -30,6 +30,12 @@ bool UNiagaraStackPropertyRow::GetIsEnabled() const return OwningNiagaraNode == nullptr || OwningNiagaraNode->GetDesiredEnabledState() == ENodeEnabledState::Enabled; } +void UNiagaraStackPropertyRow::FinalizeInternal() +{ + Super::FinalizeInternal(); + DetailTreeNode.Reset(); +} + void UNiagaraStackPropertyRow::RefreshChildrenInternal(const TArray& CurrentChildren, TArray& NewChildren, TArray& NewIssues) { TArray> NodeChildren; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackRendererItem.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackRendererItem.cpp index 6380a1522329..c16b4efb6976 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackRendererItem.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackRendererItem.cpp @@ -168,7 +168,7 @@ bool UNiagaraStackRendererItem::HasBaseRenderer() const if (bHasBaseRendererCache.IsSet() == false) { TSharedRef MergeManager = FNiagaraScriptMergeManager::Get(); - const UNiagaraEmitter* BaseEmitter = FNiagaraStackGraphUtilities::GetBaseEmitter(*GetEmitterViewModel()->GetEmitter(), GetSystemViewModel()->GetSystem()); + const UNiagaraEmitter* BaseEmitter = GetEmitterViewModel()->GetEmitter()->GetParent(); bHasBaseRendererCache = BaseEmitter != nullptr && MergeManager->HasBaseRenderer(*BaseEmitter, RendererProperties->GetMergeId()); } return bHasBaseRendererCache.GetValue(); @@ -183,7 +183,7 @@ bool UNiagaraStackRendererItem::CanResetToBase() const if (bCanResetToBaseCache.IsSet() == false) { TSharedRef MergeManager = FNiagaraScriptMergeManager::Get(); - const UNiagaraEmitter* BaseEmitter = FNiagaraStackGraphUtilities::GetBaseEmitter(*GetEmitterViewModel()->GetEmitter(), GetSystemViewModel()->GetSystem()); + const UNiagaraEmitter* BaseEmitter = GetEmitterViewModel()->GetEmitter()->GetParent(); bCanResetToBaseCache = BaseEmitter != nullptr && MergeManager->IsRendererDifferentFromBase(*GetEmitterViewModel()->GetEmitter(), *BaseEmitter, RendererProperties->GetMergeId()); } return bCanResetToBaseCache.GetValue(); @@ -196,7 +196,7 @@ void UNiagaraStackRendererItem::ResetToBase() if (CanResetToBase()) { TSharedRef MergeManager = FNiagaraScriptMergeManager::Get(); - const UNiagaraEmitter* BaseEmitter = FNiagaraStackGraphUtilities::GetBaseEmitter(*GetEmitterViewModel()->GetEmitter(), GetSystemViewModel()->GetSystem()); + const UNiagaraEmitter* BaseEmitter = GetEmitterViewModel()->GetEmitter()->GetParent(); MergeManager->ResetRendererToBase(*GetEmitterViewModel()->GetEmitter(), *BaseEmitter, RendererProperties->GetMergeId()); ModifiedGroupItemsDelegate.ExecuteIfBound(); } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackViewModel.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackViewModel.cpp index 33b478fbe334..b932b88f3b13 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackViewModel.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackViewModel.cpp @@ -182,17 +182,18 @@ bool UNiagaraStackViewModel::HasDismissedStackIssues() || GetEmitterHandleViewModel()->GetEmitterViewModel()->GetEditorData().GetStackEditorData().GetDismissedStackIssueIds().Num() > 0; } -bool UNiagaraStackViewModel::HasEmitterSource() const +bool UNiagaraStackViewModel::HasParentEmitter() const { - return EmitterHandleViewModel->GetEmitterHandle()->GetSource() != nullptr; + return EmitterHandleViewModel->GetEmitterViewModel()->GetParentEmitter() != nullptr; } void UNiagaraStackViewModel::RemoveEmitterSource() { { - FScopedTransaction ScopedTransaction(LOCTEXT("RemoveEmitterSourceTransaction", "Remove Emitter Source")); - SystemViewModel->GetSystem().Modify(); - EmitterHandleViewModel->GetEmitterHandle()->RemoveSource(); + FScopedTransaction ScopedTransaction(LOCTEXT("RemoveParentEmitterTransaction", "Remove Parent Emitter")); + UNiagaraEmitter* Emitter = EmitterHandleViewModel->GetEmitterViewModel()->GetEmitter(); + Emitter->Modify(); + Emitter->RemoveParent(); } RootEntry->RefreshChildren(); } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraGeneratedCodeView.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraGeneratedCodeView.cpp index 78c6f9f74b5f..d562c5645f3c 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraGeneratedCodeView.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraGeneratedCodeView.cpp @@ -476,14 +476,14 @@ void SNiagaraGeneratedCodeView::UpdateUI() { GeneratedCode[i].HorizontalScrollBar = SNew(SScrollBar) .Orientation(Orient_Horizontal) - .Thickness(FVector2D(8.0f, 8.0f)); + .Thickness(FVector2D(12.0f, 12.0f)); } if (!GeneratedCode[i].VerticalScrollBar.IsValid()) { GeneratedCode[i].VerticalScrollBar = SNew(SScrollBar) .Orientation(Orient_Vertical) - .Thickness(FVector2D(8.0f, 8.0f)); + .Thickness(FVector2D(12.0f, 12.0f)); } if (!GeneratedCode[i].Container.IsValid()) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterMapView.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterMapView.cpp index 3688d713eead..7dbbc5a9bae4 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterMapView.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterMapView.cpp @@ -332,8 +332,7 @@ void SNiagaraParameterMapView::CollectAllActions(FGraphActionListBuilderBase& Ou for (auto& GraphWeakPtr : Graphs) { UNiagaraGraph* Graph = GraphWeakPtr.Get(); - for (const auto& ParameterElement : Graph->GetParameterReferenceMap()) - // for (const auto& ParameterElement : FNiagaraEditorUtilities::GetCompiledGraphParameterMapReferences(Graph)) // TODO(mv): This makes unused module parameters not show up. See CL 6182457. + for (const auto& ParameterElement : FNiagaraEditorUtilities::GetCompiledGraphParameterMapReferences(Graph)) { TArray* Found = ParameterEntries.Find(ParameterElement.Key); if (Found) @@ -368,7 +367,7 @@ void SNiagaraParameterMapView::CollectAllActions(FGraphActionListBuilderBase& Ou } } - ParameterEntries.KeySort([](const FNiagaraVariable& A, const FNiagaraVariable& B) { return (A.GetName() < B.GetName()); }); + ParameterEntries.KeySort([](const FNiagaraVariable& A, const FNiagaraVariable& B) { return A.GetName().LexicalLess(B.GetName()); }); const FText TooltipFormat = LOCTEXT("Parameters", "Name: {0} \nType: {1}"); for (const auto& ParameterEntry : ParameterEntries) @@ -977,7 +976,7 @@ void SNiagaraAddParameterMenu::CollectAllActions(FGraphActionListBuilderBase& Ou for (TWeakObjectPtr& Graph : Graphs) { TMap ParameterEntries = Graph.Get()->GetParameterReferenceMap(); - ParameterEntries.KeySort([](const FNiagaraVariable& A, const FNiagaraVariable& B) { return (A.GetName() < B.GetName()); }); + ParameterEntries.KeySort([](const FNiagaraVariable& A, const FNiagaraVariable& B) { return A.GetName().LexicalLess(B.GetName()); }); for (const auto& ParameterEntry : ParameterEntries) { @@ -1034,7 +1033,7 @@ void SNiagaraAddParameterMenu::AddParameterGroup(FGraphActionListBuilderBase& Ou { if (bSort) { - Variables.Sort([](const FNiagaraVariable& A, const FNiagaraVariable& B) { return (A.GetName() < B.GetName()); }); + Variables.Sort([](const FNiagaraVariable& A, const FNiagaraVariable& B) { return A.GetName().LexicalLess(B.GetName()); }); } for (FNiagaraVariable& Variable : Variables) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.cpp index 63266854dd71..8c420c3e2a62 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.cpp @@ -285,19 +285,19 @@ void SNiagaraSpreadsheetView::Construct(const FArguments& InArgs, TSharedRef >) .IsEnabled(this, &SNiagaraSpreadsheetView::IsPausedAtRightTimeOnRightHandle) @@ -691,7 +691,7 @@ TSharedRef SNiagaraSpreadsheetView::OnGetTargetMenuContent() const ComponentName, ComponentTooltip, FSlateIcon(), - FUIAction(FExecuteAction::CreateRaw(this, &SNiagaraSpreadsheetView::SetTarget, *It))); + FUIAction(FExecuteAction::CreateRaw(const_cast(this), &SNiagaraSpreadsheetView::SetTarget, *It))); } } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorModule.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorModule.h index 1167e9df2018..17e8cb1ece17 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorModule.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorModule.h @@ -119,7 +119,6 @@ private: FDelegateHandle CreateVectorParameterTrackEditorHandle; FDelegateHandle CreateColorParameterTrackEditorHandle; - FDelegateHandle MergeEmitterHandle; FDelegateHandle CreateDefaultScriptSourceHandle; FDelegateHandle ScriptCompilerHandle; FDelegateHandle PrecompilerHandle; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEmitterFactoryNew.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEmitterFactoryNew.h index 76d62d060e46..b2e76b757fcf 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEmitterFactoryNew.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEmitterFactoryNew.h @@ -14,6 +14,7 @@ class UNiagaraEmitterFactoryNew : public UFactory GENERATED_UCLASS_BODY() UNiagaraEmitter* EmitterToCopy; + bool bUseInheritance; bool bAddDefaultModulesAndRenderersToEmptyEmitter; //~ Begin UFactory Interface diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraGraph.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraGraph.h index 37319108d992..9965eaa58b40 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraGraph.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraGraph.h @@ -265,6 +265,8 @@ class UNiagaraGraph : public UEdGraph void RebuildCachedCompileIds(bool bForce = false); + void CopyCachedReferencesMap(UNiagaraGraph* TargetGraph); + protected: void RebuildNumericCache(); bool bNeedNumericCacheRebuilt; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraMessageManager.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraMessageManager.h index 78b42b545dbc..db81f22df96c 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraMessageManager.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraMessageManager.h @@ -67,7 +67,7 @@ class FNiagaraMessageJobCompileEvent : public INiagaraMessageJob public: FNiagaraMessageJobCompileEvent( const FNiagaraCompileEvent& InCompileEvent - , const TWeakObjectPtr& InOriginatingScriptWeakObjPtr + , const TWeakObjectPtr& InOriginatingScriptWeakObjPtr , const TOptional& InOwningScriptNameString = TOptional() , const TOptional& InSourceScriptAssetPath = TOptional() ); @@ -87,7 +87,7 @@ private: ) const; const FNiagaraCompileEvent CompileEvent; - const TWeakObjectPtr OriginatingScriptWeakObjPtr; + const TWeakObjectPtr OriginatingScriptWeakObjPtr; TOptional OwningScriptNameString; TOptional SourceScriptAssetPath; }; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/NiagaraEmitterHandleViewModel.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/NiagaraEmitterHandleViewModel.h index 453b5d79bd53..a9204d01f11f 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/NiagaraEmitterHandleViewModel.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/NiagaraEmitterHandleViewModel.h @@ -47,12 +47,6 @@ public: /** Gets the text representation of the emitter handle name. */ NIAGARAEDITOR_API FText GetNameText() const; - /** Gets the text representation of the source emitter handle name. */ - NIAGARAEDITOR_API FText GetSourceNameText() const; - - /** Gets the text representation of the emitter handle name. */ - NIAGARAEDITOR_API FText GetSourcePathNameText() const; - /** Called when the contents of the name text control is committed. */ NIAGARAEDITOR_API void OnNameTextComitted(const FText& InText, ETextCommit::Type CommitInfo); @@ -85,10 +79,6 @@ public: /** Gets the view model for the emitter this handle references. */ NIAGARAEDITOR_API TSharedPtr GetEmitterViewModel(); - - /** Opens the source emitter in a stand alone asset editor. */ - void OpenSourceEmitter(); - /** Gets a multicast delegate which is called any time a property on the handle changes. */ FOnPropertyChanged& OnPropertyChanged(); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/NiagaraEmitterViewModel.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/NiagaraEmitterViewModel.h index 97c765e8f092..c81cafd6e92c 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/NiagaraEmitterViewModel.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/NiagaraEmitterViewModel.h @@ -43,6 +43,15 @@ public: /** Gets the emitter represented by this view model. */ NIAGARAEDITOR_API UNiagaraEmitter* GetEmitter(); + /** Gets the parent emitter for the emitter represented by this view model, if it has one. */ + NIAGARAEDITOR_API const UNiagaraEmitter* GetParentEmitter() const; + + /** Gets the text representation of the parent emitter name. */ + NIAGARAEDITOR_API FText GetParentNameText() const; + + /** Gets the text representation of the parent emitter path. */ + NIAGARAEDITOR_API FText GetParentPathNameText() const; + /** Gets text representing stats for the emitter. */ //~ TODO: Instead of a single string here, we should probably have separate controls with tooltips etc. NIAGARAEDITOR_API FText GetStatsText() const; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackFunctionInputCollection.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackFunctionInputCollection.h index 26cdbf7be944..e5143d561559 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackFunctionInputCollection.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackFunctionInputCollection.h @@ -41,11 +41,12 @@ protected: virtual void RefreshChildrenInternal(const TArray& CurrentChildren, TArray& NewChildren, TArray& NewIssues) override; private: - void RefreshIssues(TArray DuplicateInputNames, TArray ValidAliasedInputNames, TArray PinsWithInvalidTypes, TArray& NewIssues); + void RefreshIssues(TArray DuplicateInputNames, TArray ValidAliasedInputNames, TArray PinsWithInvalidTypes, TMap StaticSwitchInputs, TArray& NewIssues); void OnFunctionInputsChanged(); -private: + UNiagaraStackEntry::FStackIssueFix GetNodeRemovalFix(UEdGraphPin* PinToRemove, FText FixDescription); + struct FInputData { const UEdGraphPin* Pin; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackGraphUtilities.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackGraphUtilities.h index 70d393b955d1..15558c145c74 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackGraphUtilities.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackGraphUtilities.h @@ -76,7 +76,7 @@ namespace FNiagaraStackGraphUtilities void GetStackFunctionInputPins(UNiagaraNodeFunctionCall& FunctionCallNode, TArray& OutInputPins, ENiagaraGetStackFunctionInputPinsOptions Options = ENiagaraGetStackFunctionInputPinsOptions::AllInputs, bool bIgnoreDisabled = false); - void GetStackFunctionStaticSwitchPins(UNiagaraNodeFunctionCall& FunctionCallNode, TArray& OutInputPins); + void GetStackFunctionStaticSwitchPins(UNiagaraNodeFunctionCall& FunctionCallNode, TArray& OutInputPins); UNiagaraNodeParameterMapSet* GetStackFunctionOverrideNode(UNiagaraNodeFunctionCall& FunctionCallNode); @@ -120,8 +120,6 @@ namespace FNiagaraStackGraphUtilities UNiagaraNodeOutput* ResetGraphForOutput(UNiagaraGraph& NiagaraGraph, ENiagaraScriptUsage ScriptUsage, FGuid ScriptUsageId, const FGuid& PreferredOutputNodeGuid = FGuid(), const FGuid& PreferredInputNodeGuid = FGuid()); - const UNiagaraEmitter* GetBaseEmitter(UNiagaraEmitter& Emitter, UNiagaraSystem& OwningSystem); - bool IsRapidIterationType(const FNiagaraTypeDefinition& InputType); FNiagaraVariable CreateRapidIterationParameter(const FString& UniqueEmitterName, ENiagaraScriptUsage ScriptUsage, const FName& AliasedInputName, const FNiagaraTypeDefinition& InputType); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackPropertyRow.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackPropertyRow.h index f6f9aa437b6d..c110a7995819 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackPropertyRow.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackPropertyRow.h @@ -21,6 +21,8 @@ public: virtual bool GetIsEnabled() const override; protected: + virtual void FinalizeInternal() override; + virtual void RefreshChildrenInternal(const TArray& CurrentChildren, TArray& NewChildren, TArray& NewIssues) override; virtual void GetSearchItems(TArray& SearchItems) const override; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackViewModel.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackViewModel.h index 9914996d485d..b529ec4a9e06 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackViewModel.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackViewModel.h @@ -83,7 +83,7 @@ public: bool HasDismissedStackIssues(); - bool HasEmitterSource() const; + bool HasParentEmitter() const; void RemoveEmitterSource(); private: diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/SNiagaraStack.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/SNiagaraStack.cpp index 59b648f29e19..9b52b096f90a 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/SNiagaraStack.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/SNiagaraStack.cpp @@ -162,7 +162,7 @@ void SNiagaraStack::Construct(const FArguments& InArgs, UNiagaraStackViewModel* FSlateIcon(), FUIAction( FExecuteAction::CreateUObject(StackViewModel, &UNiagaraStackViewModel::RemoveEmitterSource), - FCanExecuteAction::CreateUObject(StackViewModel, &UNiagaraStackViewModel::HasEmitterSource))); + FCanExecuteAction::CreateUObject(StackViewModel, &UNiagaraStackViewModel::HasParentEmitter))); MenuBuilder.AddMenuEntry( LOCTEXT("ShowEmitterInContentBrowser", "Show in Content Browser"), @@ -339,11 +339,11 @@ void SNiagaraStack::ConstructHeaderWidget() [ SNew(SButton) .IsFocusable(false) - .ToolTipText(LOCTEXT("OpenAndFocusSourceEmitterToolTip", "Open and Focus Source Emitter")) + .ToolTipText(LOCTEXT("OpenAndFocusParentEmitterToolTip", "Open and Focus Parent Emitter")) .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") .ForegroundColor(this, &SNiagaraStack::GetPinColor) .ContentPadding(2) - .OnClicked(this, &SNiagaraStack::OpenSourceEmitter) + .OnClicked(this, &SNiagaraStack::OpenParentEmitter) .Visibility(this, &SNiagaraStack::GetOpenSourceEmitterVisibility) // GoToSource icon is 30x30px so we scale it down to stay in line with other 12x12px UI .DesiredSizeScale(FVector2D(0.55f, 0.55f)) @@ -530,14 +530,14 @@ bool SNiagaraStack::IsEntryFocusedInSearch(UNiagaraStackEntry* Entry) const return false; } -FReply SNiagaraStack::OpenSourceEmitter() +FReply SNiagaraStack::OpenParentEmitter() { - if (StackViewModel && StackViewModel->GetEmitterHandleViewModel().IsValid() && StackViewModel->GetEmitterHandleViewModel()->GetEmitterHandle()) + if (StackViewModel && StackViewModel->GetEmitterHandleViewModel().IsValid() && StackViewModel->GetEmitterHandleViewModel()->GetEmitterViewModel().IsValid()) { - UNiagaraEmitter* Emitter = const_cast(StackViewModel->GetEmitterHandleViewModel()->GetEmitterHandle()->GetSource()); - if (Emitter != nullptr) + UNiagaraEmitter* ParentEmitter = const_cast(StackViewModel->GetEmitterHandleViewModel()->GetEmitterViewModel()->GetParentEmitter()); + if (ParentEmitter != nullptr) { - FAssetEditorManager::Get().OpenEditorForAsset(Emitter); + FAssetEditorManager::Get().OpenEditorForAsset(ParentEmitter); } } return FReply::Handled(); @@ -555,7 +555,7 @@ EVisibility SNiagaraStack::GetPinEmitterVisibility() const EVisibility SNiagaraStack::GetOpenSourceEmitterVisibility() const { - return CanOpenSourceEmitter() ? EVisibility::Visible : EVisibility::Collapsed; + return StackViewModel->HasParentEmitter() ? EVisibility::Visible : EVisibility::Collapsed; } bool SNiagaraStack::GetEmitterNameIsReadOnly() const @@ -567,17 +567,6 @@ bool SNiagaraStack::GetEmitterNameIsReadOnly() const return true; } -bool SNiagaraStack::CanOpenSourceEmitter() const -{ - if (StackViewModel && StackViewModel->GetEmitterHandleViewModel().IsValid() && StackViewModel->GetEmitterHandleViewModel()->GetEmitterHandle() - && StackViewModel->GetEmitterHandleViewModel()->GetEmitterHandle()->GetSource()) - { - return true; - } - - return false; -} - void SNiagaraStack::SetEmitterEnabled(bool bIsEnabled) { StackViewModel->GetEmitterHandleViewModel()->SetIsEnabled(bIsEnabled); @@ -592,8 +581,11 @@ void SNiagaraStack::ShowEmitterInContentBrowser() { FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked(TEXT("ContentBrowser")); TArray Assets; - Assets.Add(FAssetData(StackViewModel->GetEmitterHandleViewModel()->GetEmitterHandle()->GetSource())); - ContentBrowserModule.Get().SyncBrowserToAssets(Assets); + if (StackViewModel->HasParentEmitter()) + { + Assets.Add(FAssetData(StackViewModel->GetEmitterHandleViewModel()->GetEmitterViewModel()->GetParentEmitter())); + ContentBrowserModule.Get().SyncBrowserToAssets(Assets); + } } void SNiagaraStack::NavigateTo(UNiagaraStackEntry* Item) @@ -1067,16 +1059,16 @@ void SNiagaraStack::StackStructureChanged() FText SNiagaraStack::GetSourceEmitterNameText() const { - return StackViewModel->GetEmitterHandleViewModel()->GetSourceNameText(); + return StackViewModel->GetEmitterHandleViewModel()->GetEmitterViewModel()->GetParentNameText(); } FText SNiagaraStack::GetEmitterNameToolTip() const { - if (CanOpenSourceEmitter()) + if (StackViewModel->HasParentEmitter()) { // We are looking at this Emitter in a System Asset and it has a valid parent Emitter TSharedPtr ThisViewModel = StackViewModel->GetEmitterHandleViewModel(); - return FText::Format(LOCTEXT("EmitterNameAndPath", "{0}\nParent: {1}"), ThisViewModel->GetNameText(), ThisViewModel->GetSourcePathNameText()); + return FText::Format(LOCTEXT("EmitterNameAndPath", "{0}\nParent: {1}"), ThisViewModel->GetNameText(), ThisViewModel->GetEmitterViewModel()->GetParentPathNameText()); } else { @@ -1092,13 +1084,13 @@ void SNiagaraStack::OnStackViewNameTextCommitted(const FText& InText, ETextCommi EVisibility SNiagaraStack::GetSourceEmitterNameVisibility() const { - return CanOpenSourceEmitter() && GetIsEmitterRenamed() ? EVisibility::Visible : EVisibility::Collapsed; + return StackViewModel->HasParentEmitter() && GetIsEmitterRenamed() ? EVisibility::Visible : EVisibility::Collapsed; } bool SNiagaraStack::GetIsEmitterRenamed() const { const FText CurrentNameText = StackViewModel->GetEmitterHandleViewModel()->GetNameText(); - const FText SourceNameText = StackViewModel->GetEmitterHandleViewModel()->GetSourceNameText(); + const FText SourceNameText = StackViewModel->GetEmitterHandleViewModel()->GetEmitterViewModel()->GetParentNameText(); return !CurrentNameText.EqualTo(SourceNameText); } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/SNiagaraStack.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/SNiagaraStack.h index 7d56fee76215..7e072574bf7a 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/SNiagaraStack.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/SNiagaraStack.h @@ -72,7 +72,7 @@ private: void ConstructHeaderWidget(); FSlateColor GetPinColor() const; FReply PinButtonPressed(); - FReply OpenSourceEmitter(); + FReply OpenParentEmitter(); EVisibility GetEnableCheckboxVisibility() const; EVisibility GetPinEmitterVisibility() const; EVisibility GetOpenSourceEmitterVisibility() const; @@ -100,7 +100,6 @@ private: bool IsEntryFocusedInSearch(UNiagaraStackEntry* Entry) const; // Inline menu commands - bool CanOpenSourceEmitter() const; void SetEmitterEnabled(bool bIsEnabled); bool CheckEmitterEnabledStatus(bool bIsEnabled); void ShowEmitterInContentBrowser(); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderDerivedDataVersion.h b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderDerivedDataVersion.h index f61802dde1cd..7905df65856f 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderDerivedDataVersion.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderDerivedDataVersion.h @@ -11,4 +11,4 @@ NiagaraShaderDerivedDataVersion.h: Shader derived data version for Niagara. // In case of merge conflicts with DDC versions, you MUST generate a new GUID and set this new // guid as version -#define NIAGARASHADERMAP_DERIVEDDATA_VER TEXT("DE7107EEE3F66B4EA8F73C17D107D3E6") +#define NIAGARASHADERMAP_DERIVEDDATA_VER TEXT("068D842C69C776419BF530A17E1A388B") diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShared.h b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShared.h index 9f3e7cf3eb19..84a9fc655dc3 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShared.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShared.h @@ -513,7 +513,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnNiagaraScriptCompilationComplete); /** * FNiagaraShaderScript represents a Niagara script to the shader compilation process */ -class FNiagaraShaderScript +class NIAGARASHADER_VTABLE FNiagaraShaderScript { public: diff --git a/Engine/Plugins/Importers/USDImporter/Source/ThirdParty/USD/include/boost/config/compiler/visualc.hpp b/Engine/Plugins/Importers/USDImporter/Source/ThirdParty/USD/include/boost/config/compiler/visualc.hpp index baaab589efac..88ef94a4fba2 100644 --- a/Engine/Plugins/Importers/USDImporter/Source/ThirdParty/USD/include/boost/config/compiler/visualc.hpp +++ b/Engine/Plugins/Importers/USDImporter/Source/ThirdParty/USD/include/boost/config/compiler/visualc.hpp @@ -291,8 +291,14 @@ // last known and checked version is 19.00.23026 (VC++ 2015 RTM): #if (_MSC_VER > 1900) # if defined(BOOST_ASSERT_CONFIG) -# error "Unknown compiler version - please run the configure tests and report the results" -# else -# pragma message("Unknown compiler version - please run the configure tests and report the results") +# error "Boost.Config is older than your current compiler version." +# elif !defined(BOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE) + // Disabled this warning, as newer versions of boost already disable the message + // + // Newer versions of boost have comment says: + // + // Disabled as of March 2018 - the pace of VS releases is hard to keep up with + // and in any case, we have relatively few defect macros defined now. + // BOOST_PRAGMA_MESSAGE("Info: Boost.Config is older than your compiler version - probably nothing bad will happen - but you may wish to look for an updated Boost version. Define BOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE to suppress this message.") # endif #endif diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/ImgMedia.Build.cs b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/ImgMedia.Build.cs index 2e0d4fe072d5..dc4bbbc69b1f 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/ImgMedia.Build.cs +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/ImgMedia.Build.cs @@ -32,6 +32,7 @@ namespace UnrealBuildTool.Rules new string[] { "ImgMedia/Private", "ImgMedia/Private/Assets", + "ImgMedia/Private/GlobalCache", "ImgMedia/Private/Loader", "ImgMedia/Private/Player", "ImgMedia/Private/Readers", diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/GlobalCache/ImgMediaGlobalCache.cpp b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/GlobalCache/ImgMediaGlobalCache.cpp new file mode 100644 index 000000000000..6f8172295195 --- /dev/null +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/GlobalCache/ImgMediaGlobalCache.cpp @@ -0,0 +1,223 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "ImgMediaGlobalCache.h" +#include "IImgMediaReader.h" +#include "ImgMediaPrivate.h" +#include "ImgMediaSettings.h" + + +FImgMediaGlobalCache::FImgMediaGlobalCache() + : LeastRecent(nullptr) + , MostRecent(nullptr) + , CurrentSize(0) + , MaxSize(0) +{ +} + +FImgMediaGlobalCache::~FImgMediaGlobalCache() +{ + Shutdown(); +} + +void FImgMediaGlobalCache::Initialize() +{ + auto Settings = GetDefault(); + MaxSize = Settings->GlobalCacheSizeGB * 1024 * 1024 * 1024; +} + +void FImgMediaGlobalCache::Shutdown() +{ + FScopeLock Lock(&CriticalSection); + Empty(); +} + +void FImgMediaGlobalCache::AddFrame(const FName& Sequence, int32 Index, const TSharedPtr& Frame) +{ + FScopeLock Lock(&CriticalSection); + + // Make sure we have enough space in the cache to add this new frame. + SIZE_T FrameSize = Frame->Info.UncompressedSize; + if (FrameSize <= MaxSize) + { + // Empty cache until we have enough space. + while (CurrentSize + FrameSize > MaxSize) + { + FName* RemoveSequencePtr = MapLeastRecentToSequence.Find(LeastRecent); + FName RemoveSequence = RemoveSequencePtr != nullptr ? *RemoveSequencePtr : FName(); + Remove(RemoveSequence, *LeastRecent); + } + + // Create new entry. + FImgMediaGlobalCacheEntry* NewEntry = new FImgMediaGlobalCacheEntry(Index, Frame); + MapFrameToEntry.Emplace(TPair(Sequence, Index), NewEntry); + + MarkAsRecent(Sequence, *NewEntry); + + CurrentSize += FrameSize; + } + else + { + UE_LOG(LogImgMedia, Warning, TEXT("Global cache size %d is smaller than frame size %d."), MaxSize, FrameSize); + } +} + +TSharedPtr* FImgMediaGlobalCache::FindAndTouch(const FName& Sequence, int32 Index) +{ + FScopeLock Lock(&CriticalSection); + + TSharedPtr* Frame = nullptr; + + FImgMediaGlobalCacheEntry** Entry = MapFrameToEntry.Find(TPair(Sequence, Index)); + + if ((Entry != nullptr) && (*Entry != nullptr)) + { + Frame = &((*Entry)->Frame); + + // Mark this as the most recent. + Unlink(Sequence, **Entry); + MarkAsRecent(Sequence, **Entry); + } + + return Frame; +} + +void FImgMediaGlobalCache::GetIndices(const FName& Sequence, TArray& OutIndices) const +{ + FScopeLock Lock(&CriticalSection); + + // Get most recent entry in this sequence. + FImgMediaGlobalCacheEntry* const* CurrentPtr = MapSequenceToMostRecentEntry.Find(Sequence); + const FImgMediaGlobalCacheEntry* Current = CurrentPtr != nullptr ? *CurrentPtr : nullptr; + + // Loop over all entries in the sequence. + while (Current != nullptr) + { + OutIndices.Add(Current->Index); + Current = Current->LessRecentSequence; + } +} + +void FImgMediaGlobalCache::Remove(const FName& Sequence, FImgMediaGlobalCacheEntry& Entry) +{ + // Remove from cache. + Unlink(Sequence, Entry); + + // Update current cache size. + SIZE_T FrameSize = Entry.Frame->Info.UncompressedSize; + CurrentSize -= FrameSize; + + // Delete entry. + MapFrameToEntry.Remove(TPair(Sequence, Entry.Index)); + delete &Entry; +} + +void FImgMediaGlobalCache::MarkAsRecent(const FName& Sequence, FImgMediaGlobalCacheEntry& Entry) +{ + // Mark most recent. + Entry.LessRecent = MostRecent; + if (MostRecent != nullptr) + { + MostRecent->MoreRecent = &Entry; + } + MostRecent = &Entry; + + // Mark most recent in sequence. + FImgMediaGlobalCacheEntry** SequenceMostRecentPtr = MapSequenceToMostRecentEntry.Find(Sequence); + FImgMediaGlobalCacheEntry* SequenceMostRecent = (SequenceMostRecentPtr != nullptr) ? (*SequenceMostRecentPtr) : nullptr; + Entry.LessRecentSequence = SequenceMostRecent; + if (SequenceMostRecent != nullptr) + { + SequenceMostRecent->MoreRecentSequence = &Entry; + } + else + { + // If we did not have a most recent one, then this is the first in the sequence. + MapLeastRecentToSequence.Emplace(&Entry, Sequence); + } + MapSequenceToMostRecentEntry.Emplace(Sequence, &Entry); + + // If LeastRecent is null, then set it now. + if (LeastRecent == nullptr) + { + LeastRecent = &Entry; + } +} + +void FImgMediaGlobalCache::Unlink(const FName& Sequence, FImgMediaGlobalCacheEntry& Entry) +{ + // Remove from link. + if (Entry.LessRecent != nullptr) + { + Entry.LessRecent->MoreRecent = Entry.MoreRecent; + } + else if (LeastRecent == &Entry) + { + LeastRecent = Entry.MoreRecent; + } + + if (Entry.MoreRecent != nullptr) + { + Entry.MoreRecent->LessRecent = Entry.LessRecent; + } + else if (MostRecent == &Entry) + { + MostRecent = Entry.LessRecent; + } + + Entry.LessRecent = nullptr; + Entry.MoreRecent = nullptr; + + // Remove from sequence link. + if (Entry.LessRecentSequence != nullptr) + { + Entry.LessRecentSequence->MoreRecentSequence = Entry.MoreRecentSequence; + } + else + { + MapLeastRecentToSequence.Remove(&Entry); + if (Entry.MoreRecentSequence != nullptr) + { + MapLeastRecentToSequence.Emplace(Entry.MoreRecentSequence, Sequence); + } + } + + if (Entry.MoreRecentSequence != nullptr) + { + Entry.MoreRecentSequence->LessRecentSequence = Entry.LessRecentSequence; + } + else + { + // Update most recent in sequence. + FImgMediaGlobalCacheEntry** MostRecentSequence = MapSequenceToMostRecentEntry.Find(Sequence); + if (MostRecentSequence != nullptr) + { + if (*MostRecentSequence == &Entry) + { + MapSequenceToMostRecentEntry.Emplace(Sequence, Entry.LessRecentSequence); + } + } + } + + Entry.LessRecent = nullptr; + Entry.LessRecentSequence = nullptr; + Entry.MoreRecent = nullptr; + Entry.MoreRecentSequence = nullptr; +} + +void FImgMediaGlobalCache::Empty() +{ + while (LeastRecent != nullptr) + { + FImgMediaGlobalCacheEntry *Entry = LeastRecent; + LeastRecent = Entry->MoreRecent; + + delete Entry; + } + + MostRecent = nullptr; + CurrentSize = 0; + + MapSequenceToMostRecentEntry.Empty(); + MapLeastRecentToSequence.Empty(); + MapFrameToEntry.Empty(); +} diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/GlobalCache/ImgMediaGlobalCache.h b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/GlobalCache/ImgMediaGlobalCache.h new file mode 100644 index 000000000000..25077c1b121a --- /dev/null +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/GlobalCache/ImgMediaGlobalCache.h @@ -0,0 +1,145 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "Containers/Map.h" +#include "Templates/SharedPointer.h" + +struct FImgMediaFrame; + +/** + * A global cache for all ImgMedia players. + * + * Uses Least Recently Used (LRU). + */ +class FImgMediaGlobalCache +{ +public: + /** + * Constructor. + */ + FImgMediaGlobalCache(); + + /** + * Desctructor. + */ + ~FImgMediaGlobalCache(); + + /** + * Initialize the cache. + */ + void Initialize(); + + /** + * Shut down the cache. + */ + void Shutdown(); + + /** + * Adds a frame to the cache. + * + * @param Sequence Indentifying name of this sequence. + * @param Index Index of frame to add. + * @param Frame Actual frame to add. + */ + void AddFrame(const FName& Sequence, int32 Index, const TSharedPtr& Frame); + + /** + * Find the entry with the specified sequence and index and mark it as the most recently used. + * + * @param Sequence Indentifying name of this sequence. + * @param Index Index of frame to touch. + */ + TSharedPtr* FindAndTouch(const FName& Sequence, int32 Index); + + /** + * Find the indices of all cached entries of a particular sequence. + * + * @param Sequence Indentifying name of this sequence. + * @param OutIndices Will contain the collection of indices. + */ + void GetIndices(const FName& Sequence, TArray& OutIndices) const; + +private: + + /** An entry in the cache. */ + struct FImgMediaGlobalCacheEntry + { + /** Frame index. */ + int32 Index; + /** Actual frame. */ + TSharedPtr Frame; + + /** Previous entry in the cache. */ + FImgMediaGlobalCacheEntry* LessRecent; + /** Previous entry in the cache that is part of the same sequence. */ + FImgMediaGlobalCacheEntry* LessRecentSequence; + /** Next entry in the cache. */ + FImgMediaGlobalCacheEntry* MoreRecent; + /** Next entry in the cache that is part of the same sequence. */ + FImgMediaGlobalCacheEntry* MoreRecentSequence; + + /** Constructor. */ + FImgMediaGlobalCacheEntry(int32 InIndex, const TSharedPtr& InFrame) + : Index(InIndex) + , Frame(InFrame) + , LessRecent(nullptr) + , LessRecentSequence(nullptr) + , MoreRecent(nullptr) + , MoreRecentSequence(nullptr) + { + } + }; + + /** Entry that was used first. */ + FImgMediaGlobalCacheEntry* LeastRecent; + /** Entry that was used last. */ + FImgMediaGlobalCacheEntry* MostRecent; + + /** Maps a sequence name to the most recent cache entry of that sequence. */ + TMap MapSequenceToMostRecentEntry; + /** Maps a cache entry that is the least recent entry to the name of its sequence. */ + TMap MapLeastRecentToSequence; + /** Maps a sequence name and frame index to an entry in the cache. */ + TMap, FImgMediaGlobalCacheEntry*> MapFrameToEntry; + + /** Current size of the cache in bytes. */ + SIZE_T CurrentSize; + /** Maximum size of the cache in bytes. */ + SIZE_T MaxSize; + + /** Critical section for synchronizing access to Frames. */ + mutable FCriticalSection CriticalSection; + + /** + * Removes an entry from the cache and deletes the entry. + * + * @param Sequence Indentifying name of this sequence. + * @param Entry Entry to remove. + */ + void Remove(const FName& Sequence, FImgMediaGlobalCacheEntry& Entry); + + /** + * Inserts an entry into the cache as the most recent entry. + * Assumes the entry is not already in the cache. + * + * @param Sequence Indentifying name of this sequence. + * @param Entry Entry to mark as recent. + */ + void MarkAsRecent(const FName& Sequence, FImgMediaGlobalCacheEntry& Entry); + + /** + * Removes an entry from the cache but does not delete the entry. + * + * @param Sequence Indentifying name of this sequence. + * @param Entry Entry to remove. + */ + void Unlink(const FName& Sequence, FImgMediaGlobalCacheEntry& Entry); + + /** + * Empties the cache. + */ + void Empty(); +}; + diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/ImgMediaModule.cpp b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/ImgMediaModule.cpp index 3b9b779f7903..d23e7d336080 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/ImgMediaModule.cpp +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/ImgMediaModule.cpp @@ -7,6 +7,7 @@ #include "Misc/QueuedThreadPool.h" #include "Modules/ModuleManager.h" +#include "ImgMediaGlobalCache.h" #include "ImgMediaPlayer.h" #include "ImgMediaScheduler.h" #include "IImgMediaModule.h" @@ -103,8 +104,12 @@ public: { InitScheduler(); } + if (!GlobalCache.IsValid()) + { + InitGlobalCache(); + } - return MakeShared(EventSink, Scheduler.ToSharedRef()); + return MakeShared(EventSink, Scheduler.ToSharedRef(), GlobalCache.ToSharedRef()); } public: @@ -119,6 +124,7 @@ public: virtual void ShutdownModule() override { Scheduler.Reset(); + GlobalCache.Reset(); #if USE_IMGMEDIA_DEALLOC_POOL ImgMediaThreadPool.Reset(); @@ -141,7 +147,15 @@ private: } } + void InitGlobalCache() + { + // Initialize global cache. + GlobalCache = MakeShared(); + GlobalCache->Initialize(); + } + TSharedPtr Scheduler; + TSharedPtr GlobalCache; }; diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Loader/ImgMediaLoader.cpp b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Loader/ImgMediaLoader.cpp index b6dfb099fab5..b115bbbe6ab3 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Loader/ImgMediaLoader.cpp +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Loader/ImgMediaLoader.cpp @@ -1,6 +1,7 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ImgMediaLoader.h" +#include "ImgMediaGlobalCache.h" #include "ImgMediaPrivate.h" #include "Algo/Reverse.h" @@ -35,17 +36,20 @@ DECLARE_CYCLE_STAT(TEXT("ImgMedia Loader Release Cache"), STAT_ImgMedia_LoaderRe /* FImgMediaLoader structors *****************************************************************************/ -FImgMediaLoader::FImgMediaLoader(const TSharedRef& InScheduler) +FImgMediaLoader::FImgMediaLoader(const TSharedRef& InScheduler, + const TSharedRef& InGlobalCache) : Frames(1) , ImageWrapperModule(FModuleManager::LoadModuleChecked("ImageWrapper")) , Initialized(false) , NumLoadAhead(0) , NumLoadBehind(0) , Scheduler(InScheduler) + , GlobalCache(InGlobalCache) , SequenceDim(FIntPoint::ZeroValue) , SequenceDuration(FTimespan::Zero()) , SequenceFrameRate(0, 0) , LastRequestedFrame(INDEX_NONE) + , UseGlobalCache(false) { UE_LOG(LogImgMedia, Verbose, TEXT("Loader %p: Created"), this); } @@ -95,7 +99,14 @@ void FImgMediaLoader::GetCompletedTimeRanges(TRangeSet& OutRangeSet) FScopeLock Lock(&CriticalSection); TArray CompletedFrames; - Frames.GetKeys(CompletedFrames); + if (UseGlobalCache) + { + GlobalCache->GetIndices(SequenceName, CompletedFrames); + } + else + { + Frames.GetKeys(CompletedFrames); + } FrameNumbersToTimeRanges(CompletedFrames, OutRangeSet); } @@ -111,7 +122,16 @@ TSharedPtr FImgMediaLoader::GetFram FScopeLock ScopeLock(&CriticalSection); - const TSharedPtr* Frame = Frames.FindAndTouch(FrameIndex); + const TSharedPtr* Frame; + if (UseGlobalCache) + { + Frame = GlobalCache->FindAndTouch(SequenceName, FrameIndex); + } + else + { + Frame = Frames.FindAndTouch(FrameIndex); + } + if (Frame == nullptr) { @@ -307,6 +327,7 @@ void FImgMediaLoader::LoadSequence(const FString& SequencePath, const FFrameRate // initialize loader auto Settings = GetDefault(); + UseGlobalCache = Settings->UseGlobalCache; const FPlatformMemoryStats Stats = FPlatformMemory::GetStats(); const SIZE_T DesiredCacheSize = Settings->CacheSizeGB * 1024 * 1024 * 1024; @@ -320,6 +341,9 @@ void FImgMediaLoader::LoadSequence(const FString& SequencePath, const FFrameRate NumLoadAhead = NumFramesToLoad - NumLoadBehind; Frames.Empty(NumFramesToLoad); + + SequenceName = FName(*SequencePath); + Update(0, 0.0f, Loop); // update info @@ -453,7 +477,17 @@ void FImgMediaLoader::Update(int32 PlayHeadFrame, float PlayRate, bool Loop) for (int32 FrameNumber : FramesToLoad) { - if ((Frames.FindAndTouch(FrameNumber) == nullptr) && !QueuedFrameNumbers.Contains(FrameNumber)) + bool NeedFrame = false; + if (UseGlobalCache) + { + NeedFrame = GlobalCache->FindAndTouch(SequenceName, FrameNumber) == nullptr; + } + else + { + NeedFrame = Frames.FindAndTouch(FrameNumber) == nullptr; + } + + if ((NeedFrame) && !QueuedFrameNumbers.Contains(FrameNumber)) { PendingFrameNumbers.Add(FrameNumber); } @@ -476,7 +510,14 @@ void FImgMediaLoader::NotifyWorkComplete(FImgMediaLoaderWork& CompletedWork, int if (Frame.IsValid()) { UE_LOG(LogImgMedia, VeryVerbose, TEXT("Loader %p: Loaded frame %i"), this, FrameNumber); - Frames.Add(FrameNumber, Frame); + if (UseGlobalCache) + { + GlobalCache->AddFrame(SequenceName, FrameNumber, Frame); + } + else + { + Frames.Add(FrameNumber, Frame); + } } } diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Loader/ImgMediaLoader.h b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Loader/ImgMediaLoader.h index f9aedcdd744b..7d44280c04b2 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Loader/ImgMediaLoader.h +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Loader/ImgMediaLoader.h @@ -8,6 +8,7 @@ #include "Misc/FrameRate.h" #include "Templates/SharedPointer.h" +class FImgMediaGlobalCache; class FImgMediaLoaderWork; class FImgMediaScheduler; class FImgMediaTextureSample; @@ -31,7 +32,8 @@ public: * * @param InScheduler The scheduler for image loading. */ - FImgMediaLoader(const TSharedRef& InScheduler); + FImgMediaLoader(const TSharedRef& InScheduler, + const TSharedRef& InGlobalCache); /** Virtual destructor. */ virtual ~FImgMediaLoader(); @@ -270,6 +272,9 @@ private: /** The scheduler for image loading. */ TSharedPtr Scheduler; + /** The scheduler for image loading. */ + TSharedPtr GlobalCache; + /** Width and height of the image sequence (in pixels) .*/ FIntPoint SequenceDim; @@ -279,6 +284,9 @@ private: /** Frame rate of the currently loaded sequence. */ FFrameRate SequenceFrameRate; + /** Identifying name of sequence files. */ + FName SequenceName; + private: /** Index of the previously requested frame. */ @@ -292,4 +300,7 @@ private: /** Object pool for reusable work items. */ TArray WorkPool; + + /** True if we are using the global cache, false to use the local cache. */ + bool UseGlobalCache; }; diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Player/ImgMediaPlayer.cpp b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Player/ImgMediaPlayer.cpp index 826b244991c2..28bd6eb5eb35 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Player/ImgMediaPlayer.cpp +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Player/ImgMediaPlayer.cpp @@ -32,7 +32,8 @@ const FTimespan HackDeltaTimeOffset(1); /* FImgMediaPlayer structors *****************************************************************************/ -FImgMediaPlayer::FImgMediaPlayer(IMediaEventSink& InEventSink, const TSharedRef& InScheduler) +FImgMediaPlayer::FImgMediaPlayer(IMediaEventSink& InEventSink, const TSharedRef& InScheduler, + const TSharedRef& InGlobalCache) : CurrentDuration(FTimespan::Zero()) , CurrentRate(0.0f) , CurrentState(EMediaState::Closed) @@ -44,6 +45,7 @@ FImgMediaPlayer::FImgMediaPlayer(IMediaEventSink& InEventSink, const TSharedRef< , Scheduler(InScheduler) , SelectedVideoTrack(INDEX_NONE) , ShouldLoop(false) + , GlobalCache(InGlobalCache) { } @@ -179,7 +181,7 @@ bool FImgMediaPlayer::Open(const FString& Url, const IMediaOptions* Options) } // initialize image loader on a separate thread - Loader = MakeShared(Scheduler.ToSharedRef()); + Loader = MakeShared(Scheduler.ToSharedRef(), GlobalCache.ToSharedRef()); Scheduler->RegisterLoader(Loader.ToSharedRef()); const FString SequencePath = Url.RightChop(6); diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Player/ImgMediaPlayer.h b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Player/ImgMediaPlayer.h index 318119eb3415..d24d848735d1 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Player/ImgMediaPlayer.h +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Player/ImgMediaPlayer.h @@ -15,6 +15,7 @@ class FImgMediaScheduler; class IImgMediaReader; class IMediaEventSink; class IMediaTextureSample; +class FImgMediaGlobalCache; /** @@ -36,7 +37,8 @@ public: * @param InEventSink The object that receives media events from this player. * @param InScheduler The image loading scheduler to use. */ - FImgMediaPlayer(IMediaEventSink& InEventSink, const TSharedRef& InScheduler); + FImgMediaPlayer(IMediaEventSink& InEventSink, const TSharedRef& InScheduler, + const TSharedRef& InGlobalCache); /** Virtual destructor. */ virtual ~FImgMediaPlayer(); @@ -153,4 +155,7 @@ private: /** Should the video loop to the beginning at completion */ bool ShouldLoop; + + /** The global cache to use. */ + TSharedPtr GlobalCache; }; diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMediaFactory/Private/ImgMediaSettings.cpp b/Engine/Plugins/Media/ImgMedia/Source/ImgMediaFactory/Private/ImgMediaSettings.cpp index 7e7abcc82594..8859d0342479 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/ImgMediaFactory/Private/ImgMediaSettings.cpp +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMediaFactory/Private/ImgMediaSettings.cpp @@ -12,6 +12,8 @@ UImgMediaSettings::UImgMediaSettings() , CacheSizeGB(1.0f) , CacheThreads(8) , CacheThreadStackSizeKB(128) + , GlobalCacheSizeGB(1.0f) + , UseGlobalCache(true) , ExrDecoderThreads(0) , DefaultProxy(TEXT("proxy")) , UseDefaultProxy(false) diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMediaFactory/Public/ImgMediaSettings.h b/Engine/Plugins/Media/ImgMedia/Source/ImgMediaFactory/Public/ImgMediaSettings.h index 110905299eb3..2524b343d57a 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/ImgMediaFactory/Public/ImgMediaSettings.h +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMediaFactory/Public/ImgMediaSettings.h @@ -44,6 +44,14 @@ public: UPROPERTY(config, EditAnywhere, Category=Caching, meta=(ClampMin=128), AdvancedDisplay) int32 CacheThreadStackSizeKB; + /** Maximum size of the global look-ahead cache (in GB; default = 1 GB). */ + UPROPERTY(config, EditAnywhere, Category = Caching, meta = (ClampMin = 0)) + float GlobalCacheSizeGB; + + /** Whether to use the global cache or not. */ + UPROPERTY(config, EditAnywhere, Category = Caching) + bool UseGlobalCache; + public: /** Number of worker threads to use when decoding EXR images (0 = auto). */ diff --git a/Engine/Plugins/Media/ImgMedia/Source/OpenExrWrapper/Private/OpenExrWrapper.cpp b/Engine/Plugins/Media/ImgMedia/Source/OpenExrWrapper/Private/OpenExrWrapper.cpp index cf10df17f311..8290cd9aff7a 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/OpenExrWrapper/Private/OpenExrWrapper.cpp +++ b/Engine/Plugins/Media/ImgMedia/Source/OpenExrWrapper/Private/OpenExrWrapper.cpp @@ -5,6 +5,7 @@ #include "Containers/UnrealString.h" #include "Modules/ModuleManager.h" +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include "ImathBox.h" #include "ImfHeader.h" @@ -12,6 +13,7 @@ THIRD_PARTY_INCLUDES_START #include "ImfCompressionAttribute.h" #include "ImfStandardAttributes.h" THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END /* FOpenExr diff --git a/Engine/Plugins/Media/MediaIOFramework/Source/MediaIOEditor/Public/Widgets/SMediaPermutationsSelector.inl b/Engine/Plugins/Media/MediaIOFramework/Source/MediaIOEditor/Public/Widgets/SMediaPermutationsSelector.inl index 6ced19d1b188..6d198b0b6308 100644 --- a/Engine/Plugins/Media/MediaIOFramework/Source/MediaIOEditor/Public/Widgets/SMediaPermutationsSelector.inl +++ b/Engine/Plugins/Media/MediaIOFramework/Source/MediaIOEditor/Public/Widgets/SMediaPermutationsSelector.inl @@ -22,7 +22,8 @@ ItemType SMediaPermutationsSelector::GetSelectedItem() co template void SMediaPermutationsSelector::Construct(const FArguments& InArgs) { - SWidget::Construct(InArgs._ToolTipText, InArgs._ToolTip, InArgs._Cursor, InArgs._IsEnabled, InArgs._Visibility, InArgs._RenderOpacity, InArgs._RenderTransform, InArgs._RenderTransformPivot, InArgs._Tag, InArgs._ForceVolatile, InArgs._Clipping, InArgs._FlowDirectionPreference, InArgs.MetaData); + SWidget::Construct(InArgs._ToolTipText, InArgs._ToolTip, InArgs._Cursor, InArgs._IsEnabled, InArgs._Visibility, InArgs._RenderOpacity, InArgs._RenderTransform, InArgs._RenderTransformPivot, InArgs._Tag, InArgs._ForceVolatile, InArgs._Clipping, + InArgs._FlowDirectionPreference, InArgs._AccessibleParams, InArgs.MetaData); PermutationsSource = InArgs._PermutationsSource; SelectedPermutationIndex = INDEX_NONE; diff --git a/Engine/Plugins/MovieScene/ActorSequence/Source/ActorSequenceEditor/Private/ActorSequenceComponentCustomization.cpp b/Engine/Plugins/MovieScene/ActorSequence/Source/ActorSequenceEditor/Private/ActorSequenceComponentCustomization.cpp index 4a95a8090c18..4f0aaf131c99 100644 --- a/Engine/Plugins/MovieScene/ActorSequence/Source/ActorSequenceEditor/Private/ActorSequenceComponentCustomization.cpp +++ b/Engine/Plugins/MovieScene/ActorSequence/Source/ActorSequenceEditor/Private/ActorSequenceComponentCustomization.cpp @@ -100,7 +100,7 @@ void FActorSequenceComponentCustomization::CustomizeDetails(IDetailLayoutBuilder bool bIsExternalTabAlreadyOpened = false; - if (HostTabManager.IsValid() && HostTabManager->CanSpawnTab(SequenceTabId)) + if (HostTabManager.IsValid() && HostTabManager->HasTabSpawner(SequenceTabId)) { WeakTabManager = HostTabManager; @@ -151,7 +151,7 @@ void FActorSequenceComponentCustomization::CustomizeDetails(IDetailLayoutBuilder FReply FActorSequenceComponentCustomization::InvokeSequencer() { TSharedPtr TabManager = WeakTabManager.Pin(); - if (TabManager.IsValid() && TabManager->CanSpawnTab(SequenceTabId)) + if (TabManager.IsValid() && TabManager->HasTabSpawner(SequenceTabId)) { TSharedRef Tab = TabManager->InvokeTab(SequenceTabId); diff --git a/Engine/Plugins/MovieScene/ActorSequence/Source/ActorSequenceEditor/Private/ActorSequenceEditorTabSummoner.cpp b/Engine/Plugins/MovieScene/ActorSequence/Source/ActorSequenceEditor/Private/ActorSequenceEditorTabSummoner.cpp index 1f9a03335008..0c8c97d656c5 100644 --- a/Engine/Plugins/MovieScene/ActorSequence/Source/ActorSequenceEditor/Private/ActorSequenceEditorTabSummoner.cpp +++ b/Engine/Plugins/MovieScene/ActorSequence/Source/ActorSequenceEditor/Private/ActorSequenceEditorTabSummoner.cpp @@ -386,7 +386,7 @@ public: UBlueprint* Blueprint = BlueprintEditor->GetBlueprintObj(); if (Blueprint) { - EditingComponent = SelectedNode->GetEditableComponentTemplate(Blueprint); + EditingComponent = SelectedNode->GetOrCreateEditableComponentTemplate(Blueprint); } } else if (AActor* Actor = GetPreviewActor()) diff --git a/Engine/Plugins/NetcodeUnitTest/NetcodeUnitTest/Source/NetcodeUnitTest/Classes/UnitTestManager.h b/Engine/Plugins/NetcodeUnitTest/NetcodeUnitTest/Source/NetcodeUnitTest/Classes/UnitTestManager.h index 76cbd909c8f6..c445e7f6574c 100644 --- a/Engine/Plugins/NetcodeUnitTest/NetcodeUnitTest/Source/NetcodeUnitTest/Classes/UnitTestManager.h +++ b/Engine/Plugins/NetcodeUnitTest/NetcodeUnitTest/Source/NetcodeUnitTest/Classes/UnitTestManager.h @@ -297,7 +297,6 @@ public: */ bool Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar); - /** * FOutputDevice methods */ diff --git a/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/Party/PartyMember.cpp b/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/Party/PartyMember.cpp index 72cdc7c615b3..66e5d4c0cfeb 100644 --- a/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/Party/PartyMember.cpp +++ b/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/Party/PartyMember.cpp @@ -31,7 +31,6 @@ void FPartyMemberRepData::CompareAgainst(const FOnlinePartyRepDataBase& OldData) ComparePlatformUniqueId(TypedOldData); ComparePlatformSessionId(TypedOldData); CompareCrossplayPreference(TypedOldData); - CompareVoiceConnectionId(TypedOldData); } const USocialParty* FPartyMemberRepData::GetOwnerParty() const diff --git a/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/SocialToolkit.cpp b/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/SocialToolkit.cpp index 023365cf90ec..483d95f03e23 100644 --- a/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/SocialToolkit.cpp +++ b/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/SocialToolkit.cpp @@ -247,7 +247,7 @@ void USocialToolkit::TrySendFriendInvite(const FString& DisplayNameOrEmail) cons IOnlineUserPtr UserInterface = PrimaryOSS ? PrimaryOSS->GetUserInterface() : nullptr; if (UserInterface.IsValid()) { - IOnlineUser::FOnQueryUserMappingComplete QueryCompleteDelegate = IOnlineUser::FOnQueryUserMappingComplete::CreateUObject(this, &USocialToolkit::HandleQueryPrimaryUserIdMappingComplete); + IOnlineUser::FOnQueryUserMappingComplete QueryCompleteDelegate = IOnlineUser::FOnQueryUserMappingComplete::CreateUObject(const_cast(this), &USocialToolkit::HandleQueryPrimaryUserIdMappingComplete); UserInterface->QueryUserIdMapping(*GetLocalUserNetId(ESocialSubsystem::Primary), DisplayNameOrEmail, QueryCompleteDelegate); } } @@ -415,7 +415,7 @@ bool USocialToolkit::TrySendFriendInvite(USocialUser& SocialUser, ESocialSubsyst if (FriendsInterface && SubsystemId.IsValid() && !bIsFriendshipRestricted) { - return FriendsInterface->SendInvite(GetLocalUserNum(), *SubsystemId, FriendListToQuery, FOnSendInviteComplete::CreateUObject(this, &USocialToolkit::HandleFriendInviteSent, SubsystemType, SocialUser.GetDisplayName())); + return FriendsInterface->SendInvite(GetLocalUserNum(), *SubsystemId, FriendListToQuery, FOnSendInviteComplete::CreateUObject(const_cast(this), &USocialToolkit::HandleFriendInviteSent, SubsystemType, SocialUser.GetDisplayName())); } } return false; diff --git a/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/Party/PartyMember.h b/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/Party/PartyMember.h index 8b23c48fbf71..da1d59aed7d0 100644 --- a/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/Party/PartyMember.h +++ b/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/Party/PartyMember.h @@ -54,11 +54,6 @@ private: UPROPERTY() ECrossplayPreference CrossplayPreference = ECrossplayPreference::NoSelection; EXPOSE_REP_DATA_PROPERTY(FPartyMemberRepData, ECrossplayPreference, CrossplayPreference); - - /** The voice device selected by the user. Can be blank if the user has not pinned a device */ - UPROPERTY() - FString VoiceConnectionId; - EXPOSE_REP_DATA_PROPERTY(FPartyMemberRepData, FString, VoiceConnectionId); }; using FPartyMemberDataReplicator = TPartyDataReplicator; diff --git a/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/Party/PartyTypes.h b/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/Party/PartyTypes.h index 4bbbe9e3e8a8..3f4c896edcea 100644 --- a/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/Party/PartyTypes.h +++ b/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/Party/PartyTypes.h @@ -273,8 +273,8 @@ public: UPROPERTY() bool bOnlyLeaderFriendsCanJoin = true; - bool operator==(const FPartyPrivacySettings& Other) const; - bool operator!=(const FPartyPrivacySettings& Other) const { return !operator==(Other); } + bool PARTY_API operator==(const FPartyPrivacySettings& Other) const; + bool PARTY_API operator!=(const FPartyPrivacySettings& Other) const { return !operator==(Other); } FPartyPrivacySettings() {} }; diff --git a/Engine/Plugins/Online/OnlineSubsystem/Source/Private/LANBeacon.cpp b/Engine/Plugins/Online/OnlineSubsystem/Source/Private/LANBeacon.cpp index 25c142c25bb5..3b4e9e551238 100644 --- a/Engine/Plugins/Online/OnlineSubsystem/Source/Private/LANBeacon.cpp +++ b/Engine/Plugins/Online/OnlineSubsystem/Source/Private/LANBeacon.cpp @@ -39,17 +39,22 @@ bool FLanBeacon::Init(int32 Port) { ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); bool bSuccess = false; + // Now the listen address + ListenAddr = SocketSubsystem->GetLocalBindAddr(*GWarn); + ListenAddr->SetPort(Port); // Set our broadcast address BroadcastAddr = SocketSubsystem->CreateInternetAddr(); BroadcastAddr->SetBroadcastAddress(); BroadcastAddr->SetPort(Port); - // Now the listen address - ListenAddr = SocketSubsystem->GetLocalBindAddr(*GWarn); - ListenAddr->SetPort(Port); // A temporary "received from" address SockAddr = SocketSubsystem->CreateInternetAddr(); + // Now create and set up our sockets (no VDP) - ListenSocket = SocketSubsystem->CreateSocket(NAME_DGram, TEXT("LAN beacon"), true); + // TODO: @jleonard - for the time being this will be fine, but on hybrid stacks this will be an issue as + // we have no guarantee that the address protocol from GetLocalBindAddr and BroadcastAddr will match. + PRAGMA_DISABLE_DEPRECATION_WARNINGS + ListenSocket = SocketSubsystem->CreateSocket(NAME_DGram, TEXT("LAN beacon"), ListenAddr->GetProtocolType()); + PRAGMA_ENABLE_DEPRECATION_WARNINGS if (ListenSocket != NULL) { ListenSocket->SetReuseAddr(); diff --git a/Engine/Plugins/Online/OnlineSubsystem/Source/Public/OnlineSubsystemModule.h b/Engine/Plugins/Online/OnlineSubsystem/Source/Public/OnlineSubsystemModule.h index 824cb7fa0bfe..c1af1c941ac3 100644 --- a/Engine/Plugins/Online/OnlineSubsystem/Source/Public/OnlineSubsystemModule.h +++ b/Engine/Plugins/Online/OnlineSubsystem/Source/Public/OnlineSubsystemModule.h @@ -144,7 +144,7 @@ public: * * @return true if the instance exists, false otherwise */ - bool DoesInstanceExist(const FName InSubsystemName) const; + ONLINESUBSYSTEM_API bool DoesInstanceExist(const FName InSubsystemName) const; /** * Determine if a subsystem is loaded by the OSS module @@ -182,7 +182,7 @@ public: * **NOTE** This is intended for editor use only, attempting to use this at the wrong time can result * in unexpected crashes/behavior */ - void ReloadDefaultSubsystem(); + void ONLINESUBSYSTEM_API ReloadDefaultSubsystem(); // IModuleInterface diff --git a/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Classes/OculusNetConnection.h b/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Classes/OculusNetConnection.h index 7f925907348c..e02600cc3a27 100644 --- a/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Classes/OculusNetConnection.h +++ b/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Classes/OculusNetConnection.h @@ -30,7 +30,6 @@ public: FString LowLevelDescribe() override; virtual void FinishDestroy() override; - virtual TSharedPtr GetInternetAddr() override; virtual FString RemoteAddressToString() override; // End NetConnection Interface diff --git a/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Classes/OculusNetDriver.h b/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Classes/OculusNetDriver.h index 7b4874a11560..680a7a40b5bf 100644 --- a/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Classes/OculusNetDriver.h +++ b/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Classes/OculusNetDriver.h @@ -5,6 +5,7 @@ #include "CoreMinimal.h" #include "OculusNetConnection.h" #include "IpNetDriver.h" +#include "IPAddress.h" #include "OculusNetDriver.generated.h" /** @@ -35,7 +36,7 @@ public: virtual bool InitConnect(FNetworkNotify* InNotify, const FURL& ConnectURL, FString& Error) override; virtual bool InitListen(FNetworkNotify* InNotify, FURL& LocalURL, bool bReuseAddressAndPort, FString& Error) override; virtual void TickDispatch(float DeltaTime) override; - virtual void LowLevelSend(FString Address, void* Data, int32 CountBits, FOutPacketTraits& Traits) override; + virtual void LowLevelSend(TSharedPtr Address, void* Data, int32 CountBits, FOutPacketTraits& Traits) override; virtual void Shutdown() override; virtual bool IsNetResourceValid() override; diff --git a/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/IPAddressOculus.h b/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/IPAddressOculus.h index 1a14b01ccbe2..7dc6b347de29 100644 --- a/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/IPAddressOculus.h +++ b/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/IPAddressOculus.h @@ -6,6 +6,7 @@ #include "IPAddress.h" #include "Algo/Reverse.h" #include "Engine/EngineBaseTypes.h" +#include "OnlineSubsystemOculus.h" #include "OnlineSubsystemOculusTypes.h" #include "OnlineSubsystemOculusPackage.h" @@ -206,6 +207,11 @@ public: return ::GetTypeHash((uint64)GetID()); } + virtual FName GetProtocolType() const override + { + return FNetworkProtocolTypes::Oculus; + } + /** * Is this a well formed internet address * diff --git a/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/OculusNetConnection.cpp b/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/OculusNetConnection.cpp index 471ca3a3feae..b51ec1625063 100644 --- a/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/OculusNetConnection.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/OculusNetConnection.cpp @@ -62,8 +62,8 @@ void UOculusNetConnection::InitRemoteConnection(UNetDriver* InDriver, class FSoc InMaxPacket == 0 ? MAX_PACKET_SIZE : InMaxPacket, 0); - auto OculusAddr = static_cast(InRemoteAddr); - PeerID = OculusAddr.GetID(); + RemoteAddr = InRemoteAddr.Clone(); + PeerID = StaticCastSharedPtr(RemoteAddr)->GetID(); // This is for a client that needs to log in, setup ClientLoginState and ExpectedClientLoginMsgType to reflect that SetClientLoginState(EClientLoginState::LoggingIn); @@ -156,12 +156,6 @@ void UOculusNetConnection::FinishDestroy() } } -TSharedPtr UOculusNetConnection::GetInternetAddr() -{ - // @todo #JIRA UENET-883: This should be based on NetConnection.RemoteAddr, when moved down from IPConnection - return MakeShareable(new FInternetAddrOculus(PeerID)); -} - FString UOculusNetConnection::RemoteAddressToString() { if (bIsPassThrough) diff --git a/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/OculusNetDriver.cpp b/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/OculusNetDriver.cpp index 3d4751cfa193..4d1518f3e644 100644 --- a/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/OculusNetDriver.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/OculusNetDriver.cpp @@ -199,14 +199,14 @@ void UOculusNetDriver::TickDispatch(float DeltaTime) } UE_LOG(LogNet, Verbose, TEXT("Checking challenge from: %llu"), PeerID); + TSharedPtr OculusAddr = MakeShareable(new FInternetAddrOculus(PeerID)); StatelessConnect = StatelessConnectComponent.Pin(); - FString IncomingAddress = FString::Printf(TEXT("%llu.oculus"), PeerID); const ProcessedPacket UnProcessedPacket = - ConnectionlessHandler->IncomingConnectionless(IncomingAddress, Data, PacketSize); + ConnectionlessHandler->IncomingConnectionless(OculusAddr, Data, PacketSize); bool bRestartedHandshake = false; - bPassedChallenge = !UnProcessedPacket.bError && StatelessConnect->HasPassedChallenge(IncomingAddress, bRestartedHandshake); + bPassedChallenge = !UnProcessedPacket.bError && StatelessConnect->HasPassedChallenge(OculusAddr, bRestartedHandshake); if (bPassedChallenge) { @@ -224,9 +224,7 @@ void UOculusNetDriver::TickDispatch(float DeltaTime) UOculusNetConnection* Connection = NewObject(NetConnectionClass); check(Connection); - auto OculusAddr = new FInternetAddrOculus(PeerID); - TSharedRef InternetAddr = MakeShareable(OculusAddr); - Connection->InitRemoteConnection(this, nullptr, FURL(), *InternetAddr, ovr_Net_IsConnected(PeerID) ? USOCK_Open : USOCK_Pending); + Connection->InitRemoteConnection(this, nullptr, FURL(), *OculusAddr, ovr_Net_IsConnected(PeerID) ? USOCK_Open : USOCK_Pending); AddClientConnection(Connection); @@ -287,13 +285,13 @@ void UOculusNetDriver::TickDispatch(float DeltaTime) } } -void UOculusNetDriver::LowLevelSend(FString Address, void* Data, int32 CountBits, FOutPacketTraits& Traits) +void UOculusNetDriver::LowLevelSend(TSharedPtr Address, void* Data, int32 CountBits, FOutPacketTraits& Traits) { if (bIsPassthrough) { return UIpNetDriver::LowLevelSend(Address, Data, CountBits, Traits); } - FInternetAddrOculus OculusAddr(FURL(nullptr, *Address, ETravelType::TRAVEL_Absolute)); + FInternetAddrOculus OculusAddr(FURL(nullptr, *Address->ToString(false), ETravelType::TRAVEL_Absolute)); ovrID PeerID = OculusAddr.GetID(); if (ovr_Net_IsConnected(PeerID)) { diff --git a/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/OnlineSubsystemOculus.cpp b/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/OnlineSubsystemOculus.cpp index ce70de0ac2b9..870903a05b33 100644 --- a/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/OnlineSubsystemOculus.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/OnlineSubsystemOculus.cpp @@ -19,6 +19,11 @@ OVRPL_PUBLIC_FUNCTION(void) ovr_ResetInitAndContext(); #endif +namespace FNetworkProtocolTypes +{ + const FName Oculus(TEXT("Oculus")); +} + IOnlineSessionPtr FOnlineSubsystemOculus::GetSessionInterface() const { return SessionInterface; diff --git a/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Public/OnlineSubsystemOculus.h b/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Public/OnlineSubsystemOculus.h index 6e8b2d9704d3..bcac768c3986 100644 --- a/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Public/OnlineSubsystemOculus.h +++ b/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Public/OnlineSubsystemOculus.h @@ -129,4 +129,9 @@ private: FOnlineMessageTaskManagerOculusPtr MessageTaskManager; }; +namespace FNetworkProtocolTypes +{ + ONLINESUBSYSTEMOCULUS_API extern const FName Oculus; +} + typedef TSharedPtr FOnlineSubsystemOculusPtr; diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/OnlineSubsystemSteam.Build.cs b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/OnlineSubsystemSteam.Build.cs index 6460f60c5d52..166fe764eb41 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/OnlineSubsystemSteam.Build.cs +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/OnlineSubsystemSteam.Build.cs @@ -7,7 +7,7 @@ public class OnlineSubsystemSteam : ModuleRules { public OnlineSubsystemSteam(ReadOnlyTargetRules Target) : base(Target) { - string SteamVersion = "Steamv139"; + string SteamVersion = "Steamv142"; bool bSteamSDKFound = Directory.Exists(Target.UEThirdPartySourceDirectory + "Steamworks/" + SteamVersion) == true; PublicDefinitions.Add("STEAMSDK_FOUND=" + (bSteamSDKFound ? "1" : "0")); diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/IPAddressSteam.h b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/IPAddressSteam.h index 27cbe2f086d6..5a8984ece883 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/IPAddressSteam.h +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/IPAddressSteam.h @@ -4,6 +4,7 @@ #include "CoreMinimal.h" #include "IPAddress.h" +#include "OnlineSubsystemSteam.h" #include "OnlineSubsystemSteamTypes.h" #include "OnlineSubsystemSteamPackage.h" @@ -164,18 +165,13 @@ public: } virtual uint32 GetTypeHash() const override - { - return GetConstTypeHash(); - } - - uint32 GetConstTypeHash() const { return ::GetTypeHash(ToString(true)); } friend uint32 GetTypeHash(const FInternetAddrSteam& A) { - return A.GetConstTypeHash(); + return A.GetTypeHash(); } /** @@ -188,6 +184,11 @@ public: return SteamId.IsValid(); } + virtual FName GetProtocolType() const override + { + return FNetworkProtocolTypes::Steam; + } + virtual TSharedRef Clone() const override { TSharedRef NewAddress = MakeShareable(new FInternetAddrSteam); diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineAuthHandlerSteam.cpp b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineAuthHandlerSteam.cpp index 167e64dcb772..f214fd460e1b 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineAuthHandlerSteam.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineAuthHandlerSteam.cpp @@ -83,6 +83,7 @@ FString ReusableKey; FSteamAuthHandlerComponent::FSteamAuthHandlerComponent() : AuthInterface(nullptr), SteamUserPtr(SteamUser()), + State(ESteamAuthHandlerState::Uninitialized), bIsEnabled(true), LastTimestamp(0.0f), TicketHandle(k_HAuthTicketInvalid), diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineAuthHandlerSteam.h b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineAuthHandlerSteam.h index 0921b2bb5a5e..83d54f1b19ab 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineAuthHandlerSteam.h +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineAuthHandlerSteam.h @@ -7,6 +7,7 @@ #include "OnlineSubsystemSteamTypes.h" #include "PacketHandler.h" #include "HandlerComponentFactory.h" +#include "IPAddress.h" #include "OnlineAuthHandlerSteam.generated.h" class FSteamAuthHandlerComponent : public HandlerComponent @@ -23,8 +24,8 @@ public: virtual void Incoming(FBitReader& Packet) override; virtual void Outgoing(FBitWriter& Packet, FOutPacketTraits& Traits) override; - virtual void IncomingConnectionless(const FString& Address, FBitReader& Packet) override {} - virtual void OutgoingConnectionless(const FString& Address, FBitWriter& Packet, FOutPacketTraits& Traits) override {} + virtual void IncomingConnectionless(const TSharedPtr& Address, FBitReader& Packet) override {} + virtual void OutgoingConnectionless(const TSharedPtr& Address, FBitWriter& Packet, FOutPacketTraits& Traits) override {} virtual void Tick(float DeltaTime) override; diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineExternalUIInterfaceSteam.cpp b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineExternalUIInterfaceSteam.cpp index 1f430651536f..b1b98a7d0164 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineExternalUIInterfaceSteam.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineExternalUIInterfaceSteam.cpp @@ -2,6 +2,7 @@ #include "OnlineExternalUIInterfaceSteam.h" #include "Interfaces/OnlineSessionInterface.h" +#include "OnlineSessionSettings.h" #include "OnlineSubsystemSteamTypes.h" // Other external UI possibilities in Steam @@ -49,9 +50,26 @@ bool FOnlineExternalUISteam::ShowFriendsUI(int32 LocalUserNum) bool FOnlineExternalUISteam::ShowInviteUI(int32 LocalUserNum, FName SessionName) { IOnlineSessionPtr SessionInt = SteamSubsystem->GetSessionInterface(); - if (SessionInt.IsValid() && SessionInt->HasPresenceSession()) + if (!SessionInt.IsValid()) { - SteamFriends()->ActivateGameOverlay("LobbyInvite"); + return false; + } + + const FNamedOnlineSession* const Session = SessionInt->GetNamedSession(SessionName); + if (Session && Session->SessionInfo.IsValid()) + { + const FOnlineSessionInfoSteam* const SessionInfo = (FOnlineSessionInfoSteam*)(Session->SessionInfo.Get()); + if (SessionInfo->SessionType == ESteamSession::LobbySession && SessionInfo->SessionId.IsValid()) + { + // This can only invite to lobbies, does not work for dedicated servers. + SteamFriends()->ActivateGameOverlayInviteDialog(SessionInfo->SessionId); + } + else if(SessionInfo->SessionType == ESteamSession::AdvertisedSessionHost || SessionInfo->SessionType == ESteamSession::AdvertisedSessionClient) + { + // Invite people to start this game. + // To invite someone directly into the game, use SendSessionInviteToFriend + SteamFriends()->ActivateGameOverlay("LobbyInvite"); + } return true; } diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSessionAsyncLobbySteam.cpp b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSessionAsyncLobbySteam.cpp index 4ad45acd3cf0..419b483e3942 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSessionAsyncLobbySteam.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSessionAsyncLobbySteam.cpp @@ -604,12 +604,13 @@ void FOnlineAsyncTaskSteamUpdateLobby::Tick() if (SteamMatchmakingPtr->SetLobbyType(SessionInfo->SessionId, LobbyType)) { int32 LobbyMemberCount = SteamMatchmakingPtr->GetNumLobbyMembers(SessionInfo->SessionId); - int32 MaxLobbyMembers = SteamMatchmakingPtr->GetLobbyMemberLimit(SessionInfo->SessionId); - bool bLobbyJoinable = Session->SessionSettings.bAllowJoinInProgress && (LobbyMemberCount < MaxLobbyMembers); - if (SteamMatchmakingPtr->SetLobbyJoinable(SessionInfo->SessionId, bLobbyJoinable)) + int32 NumConnections = Session->SessionSettings.NumPrivateConnections + Session->SessionSettings.NumPublicConnections; + + if (SteamMatchmakingPtr->SetLobbyMemberLimit(SessionInfo->SessionId, NumConnections)) { - int32 NumConnections = Session->SessionSettings.NumPrivateConnections + Session->SessionSettings.NumPublicConnections; - if (SteamMatchmakingPtr->SetLobbyMemberLimit(SessionInfo->SessionId, NumConnections)) + int32 MaxLobbyMembers = SteamMatchmakingPtr->GetLobbyMemberLimit(SessionInfo->SessionId); + bool bLobbyJoinable = Session->SessionSettings.bAllowJoinInProgress && (LobbyMemberCount < MaxLobbyMembers) && (MaxLobbyMembers != 0); + if (SteamMatchmakingPtr->SetLobbyJoinable(SessionInfo->SessionId, bLobbyJoinable)) { bWasSuccessful = true; diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSessionAsyncServerSteam.cpp b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSessionAsyncServerSteam.cpp index 561aeaf91234..9930b55d7916 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSessionAsyncServerSteam.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSessionAsyncServerSteam.cpp @@ -406,7 +406,9 @@ void FOnlineAsyncTaskSteamCreateServer::Finalize() UE_LOG_ONLINE_SESSION(Verbose, TEXT("Server SteamP2P IP: %s"), *NewSessionInfo->SteamP2PAddr->ToString(true)); // Create the proper ip address for this server - NewSessionInfo->HostAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr(SteamGameServerPtr->GetPublicIP(), Subsystem->GetGameServerGamePort()); + NewSessionInfo->HostAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr(); + NewSessionInfo->HostAddr->SetIp(SteamGameServerPtr->GetPublicIP()); + NewSessionInfo->HostAddr->SetPort(Subsystem->GetGameServerGamePort()); UE_LOG_ONLINE_SESSION(Verbose, TEXT("Server IP: %s"), *NewSessionInfo->HostAddr->ToString(true)); if (!Session->OwningUserId.IsValid()) @@ -675,7 +677,8 @@ bool FPendingSearchResultSteam::FillSessionFromServerRules() } // Verify success with all required keys found - if (bSuccess && (KeysFound == STEAMKEY_NUMREQUIREDSERVERKEYS) && (SteamAddrKeysFound == 2)) + // If the user has SteamNetworking off, then we should just check if their host addressing is correct. + if (bSuccess && (KeysFound == STEAMKEY_NUMREQUIREDSERVERKEYS) && (SteamAddrKeysFound == 2 || (HostAddr->IsValid() && HostAddr.IsValid()))) { SessionInfo->HostAddr = HostAddr; SessionInfo->SteamP2PAddr = SteamP2PAddr; @@ -1273,10 +1276,8 @@ void FOnlineAsyncEventSteamInviteAccepted::Finalize() Port = (Port > 0) ? Port : Subsystem->GetGameServerGamePort(); // Parse the address - bool bIsValid; - TSharedRef IpAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr(); - IpAddr->SetIp(ParsedURL, bIsValid); - if (bIsValid) + TSharedPtr IpAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetAddressFromString(ParsedURL); + if (IpAddr.IsValid()) { SessionInt->CurrentSessionSearch->QuerySettings.Set(FName(SEARCH_STEAM_HOSTIP), IpAddr->ToString(false), EOnlineComparisonOp::Equals); FOnlineAsyncTaskSteamFindServerForInviteSession* NewTask = new FOnlineAsyncTaskSteamFindServerForInviteSession(Subsystem, SearchSettings, LocalUserNum, SessionInt->OnSessionUserInviteAcceptedDelegates); diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSessionInterfaceSteam.cpp b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSessionInterfaceSteam.cpp index d0fc01d6997b..fe9b73973c56 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSessionInterfaceSteam.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSessionInterfaceSteam.cpp @@ -444,18 +444,24 @@ bool FOnlineSessionSteam::UpdateSession(FName SessionName, FOnlineSessionSetting if (!Session->SessionSettings.bIsLANMatch) { FOnlineSessionInfoSteam* SessionInfo = (FOnlineSessionInfoSteam*)(Session->SessionInfo.Get()); - - if (SessionInfo->SessionType == ESteamSession::LobbySession && SessionInfo->SessionId.IsValid()) + if (SessionInfo) { - // Lobby update - FOnlineAsyncTaskSteamUpdateLobby* NewTask = new FOnlineAsyncTaskSteamUpdateLobby(SteamSubsystem, SessionName, bShouldRefreshOnlineData, UpdatedSessionSettings); - SteamSubsystem->QueueAsyncTask(NewTask); + if (SessionInfo->SessionType == ESteamSession::LobbySession && SessionInfo->SessionId.IsValid()) + { + // Lobby update + FOnlineAsyncTaskSteamUpdateLobby* NewTask = new FOnlineAsyncTaskSteamUpdateLobby(SteamSubsystem, SessionName, bShouldRefreshOnlineData, UpdatedSessionSettings); + SteamSubsystem->QueueAsyncTask(NewTask); + } + else if (SessionInfo->SessionType == ESteamSession::AdvertisedSessionHost) + { + // Gameserver update + FOnlineAsyncTaskSteamUpdateServer* NewTask = new FOnlineAsyncTaskSteamUpdateServer(SteamSubsystem, SessionName, bShouldRefreshOnlineData, UpdatedSessionSettings); + SteamSubsystem->QueueAsyncTask(NewTask); + } } - else if (SessionInfo->SessionType == ESteamSession::AdvertisedSessionHost) + else { - // Gameserver update - FOnlineAsyncTaskSteamUpdateServer* NewTask = new FOnlineAsyncTaskSteamUpdateServer(SteamSubsystem, SessionName, bShouldRefreshOnlineData, UpdatedSessionSettings); - SteamSubsystem->QueueAsyncTask(NewTask); + bWasSuccessful = false; } } else @@ -999,9 +1005,7 @@ uint32 FOnlineSessionSteam::JoinLANSession(int32 PlayerNum, FNamedOnlineSession* const FOnlineSessionInfoSteam* SearchSessionInfo = (const FOnlineSessionInfoSteam*)SearchSession->SessionInfo.Get(); FOnlineSessionInfoSteam* SessionInfo = (FOnlineSessionInfoSteam*)Session->SessionInfo.Get(); - uint32 IpAddr; - SearchSessionInfo->HostAddr->GetIp(IpAddr); - SessionInfo->HostAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr(IpAddr, SearchSessionInfo->HostAddr->GetPort()); + SessionInfo->HostAddr = SearchSessionInfo->HostAddr->Clone(); Result = ONLINE_SUCCESS; } @@ -1038,7 +1042,9 @@ bool FOnlineSessionSteam::FindFriendSession(int32 LocalUserNum, const FUniqueNet else { // Search for the session via host ip - TSharedRef IpAddr = ISocketSubsystem::Get()->CreateInternetAddr(FriendGameInfo.m_unGameIP, FriendGameInfo.m_usGamePort); + TSharedRef IpAddr = ISocketSubsystem::Get()->CreateInternetAddr(); + IpAddr->SetIp(FriendGameInfo.m_unGameIP); + IpAddr->SetPort(FriendGameInfo.m_usGamePort); CurrentSessionSearch->QuerySettings.Set(FName(SEARCH_STEAM_HOSTIP), IpAddr->ToString(true), EOnlineComparisonOp::Equals); FOnlineAsyncTaskSteamFindServerForFriendSession* NewTask = new FOnlineAsyncTaskSteamFindServerForFriendSession(SteamSubsystem, CurrentSessionSearch, LocalUserNum, OnFindFriendSessionCompleteDelegates[LocalUserNum]); @@ -1222,35 +1228,27 @@ static bool GetConnectStringFromSessionInfo(TSharedPtr& if (SessionInfo.IsValid()) { - if (SessionInfo->SessionType == ESteamSession::LobbySession || - SessionInfo->SessionType == ESteamSession::AdvertisedSessionHost || - SessionInfo->SessionType == ESteamSession::AdvertisedSessionClient) + if (SessionInfo->SteamP2PAddr.IsValid() && SessionInfo->SteamP2PAddr->IsValid()) { - if (SessionInfo->SteamP2PAddr.IsValid() && SessionInfo->SteamP2PAddr->IsValid()) + int32 SteamPort = SessionInfo->SteamP2PAddr->GetPort(); + if (PortOverride > 0) { - int32 SteamPort = SessionInfo->SteamP2PAddr->GetPort(); - if (PortOverride > 0) - { - SteamPort = PortOverride; - } - - ConnectInfo = FString::Printf(STEAM_URL_PREFIX TEXT("%s:%d"), *SessionInfo->SteamP2PAddr->ToString(false), SteamPort); - bSuccess = true; + SteamPort = PortOverride; } + + ConnectInfo = FString::Printf(STEAM_URL_PREFIX TEXT("%s:%d"), *SessionInfo->SteamP2PAddr->ToString(false), SteamPort); + bSuccess = true; } - else + else if (SessionInfo->HostAddr.IsValid() && SessionInfo->HostAddr->IsValid()) { - if (SessionInfo->HostAddr.IsValid() && SessionInfo->HostAddr->IsValid()) + int32 HostPort = SessionInfo->HostAddr->GetPort(); + if (PortOverride > 0) { - int32 HostPort = SessionInfo->HostAddr->GetPort(); - if (PortOverride > 0) - { - HostPort = PortOverride; - } - - ConnectInfo = FString::Printf(TEXT("%s:%d"), *SessionInfo->HostAddr->ToString(false), HostPort); - bSuccess = true; + HostPort = PortOverride; } + + ConnectInfo = FString::Printf(TEXT("%s:%d"), *SessionInfo->HostAddr->ToString(false), HostPort); + bSuccess = true; } } @@ -1335,6 +1333,15 @@ FString FOnlineSessionSteam::GetCustomDedicatedServerName() const return TEXT(""); } +TSharedPtr FOnlineSessionSteam::CreateSessionIdFromString(const FString& SessionIdStr) +{ + if (!SessionIdStr.IsEmpty()) + { + return MakeShared(SessionIdStr); + } + return nullptr; +} + FOnlineSessionSettings* FOnlineSessionSteam::GetSessionSettings(FName SessionName) { FNamedOnlineSession* Session = GetNamedSession(SessionName); diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSessionInterfaceSteam.h b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSessionInterfaceSteam.h index 10461288f65b..440ef29a05a0 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSessionInterfaceSteam.h +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSessionInterfaceSteam.h @@ -447,12 +447,7 @@ public: virtual ~FOnlineSessionSteam() {} - virtual TSharedPtr CreateSessionIdFromString(const FString& SessionIdStr) override - { - /* NYI */ - TSharedPtr SessionId; - return SessionId; - } + virtual TSharedPtr CreateSessionIdFromString(const FString& SessionIdStr) override; FNamedOnlineSession* GetNamedSession(FName SessionName) override { diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSubsystemModuleSteam.cpp b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSubsystemModuleSteam.cpp index ae17436ed4bb..fd3928170acd 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSubsystemModuleSteam.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSubsystemModuleSteam.cpp @@ -107,6 +107,8 @@ static FString GetSteamModulePath() return FPaths::EngineDir() / STEAM_SDK_ROOT_PATH / STEAM_SDK_VER_PATH / TEXT("i686-unknown-linux-gnu/"); #endif //PLATFORM_64BITS +#elif PLATFORM_MAC + return FPaths::EngineDir() / STEAM_SDK_ROOT_PATH / STEAM_SDK_VER_PATH / TEXT("Mac/"); #else return FString(); @@ -124,46 +126,57 @@ void FOnlineSubsystemSteamModule::LoadSteamModules() FString Suffix("64"); #else FString Suffix; -#endif +#endif // PLATFORM_64BITS FString RootSteamPath = GetSteamModulePath(); FPlatformProcess::PushDllDirectory(*RootSteamPath); SteamDLLHandle = FPlatformProcess::GetDllHandle(*(RootSteamPath + "steam_api" + Suffix + ".dll")); if (IsRunningDedicatedServer() && FCommandLine::IsInitialized() && FParse::Param(FCommandLine::Get(), TEXT("force_steamclient_link"))) { - UE_LOG_ONLINE(Log, TEXT("Force linking the steam client dlls.")); + FString SteamClientDLL("steamclient" + Suffix + ".dll"), + SteamTierDLL("tier0_s" + Suffix + ".dll"), + SteamVSTDDLL("vstdlib_s" + Suffix + ".dll"); + + UE_LOG_ONLINE(Log, TEXT("Attempting to force linking the steam client dlls.")); bForceLoadSteamClientDll = true; - SteamServerDLLHandle = FPlatformProcess::GetDllHandle(*(RootSteamPath + "steamclient" + Suffix + ".dll")); + SteamServerDLLHandle = FPlatformProcess::GetDllHandle(*(RootSteamPath + SteamClientDLL)); + if(!SteamServerDLLHandle) + { + UE_LOG_ONLINE(Error, TEXT("Could not find the %s, %s and %s DLLs, make sure they are all located at %s! These dlls can be located in your Steam install directory."), + *SteamClientDLL, *SteamTierDLL, *SteamVSTDDLL, *RootSteamPath); + } } FPlatformProcess::PopDllDirectory(*RootSteamPath); -#elif PLATFORM_MAC - SteamDLLHandle = FPlatformProcess::GetDllHandle(TEXT("libsteam_api.dylib")); -#elif PLATFORM_LINUX +#elif PLATFORM_MAC || (PLATFORM_LINUX && LOADING_STEAM_LIBRARIES_DYNAMICALLY) -#if LOADING_STEAM_LIBRARIES_DYNAMICALLY - UE_LOG_ONLINE(Log, TEXT("Loading system libsteam_api.so.")); - SteamDLLHandle = FPlatformProcess::GetDllHandle(TEXT("libsteam_api.so")); +#if PLATFORM_MAC + FString SteamModuleFileName("libsteam_api.dylib"); +#else + FString SteamModuleFileName("libsteam_api.so"); +#endif // PLATFORM_MAC + + SteamDLLHandle = FPlatformProcess::GetDllHandle(*SteamModuleFileName); if (SteamDLLHandle == nullptr) { // try bundled one - UE_LOG_ONLINE(Warning, TEXT("Could not find system one, loading bundled libsteam_api.so.")); + UE_LOG_ONLINE(Warning, TEXT("Could not find system one, loading bundled %s."), *SteamModuleFileName); FString RootSteamPath = GetSteamModulePath(); - SteamDLLHandle = FPlatformProcess::GetDllHandle(*(RootSteamPath + "libsteam_api.so")); + SteamDLLHandle = FPlatformProcess::GetDllHandle(*(RootSteamPath + SteamModuleFileName)); } if (SteamDLLHandle) { - UE_LOG_ONLINE(Display, TEXT("Loaded libsteam_api.so at %p"), SteamDLLHandle); + UE_LOG_ONLINE(Display, TEXT("Loaded %s at %p"), *SteamModuleFileName, SteamDLLHandle); } else { - UE_LOG_ONLINE(Warning, TEXT("Unable to load libsteam_api.so, Steam functionality will not work")); + UE_LOG_ONLINE(Warning, TEXT("Unable to load %s, Steam functionality will not work"), *SteamModuleFileName); } -#else - UE_LOG_ONLINE(Log, TEXT("libsteam_api.so is linked explicitly and should be already loaded.")); -#endif // LOADING_STEAM_LIBRARIES_DYNAMICALLY -#endif //PLATFORM_WINDOWS + +#elif PLATFORM_LINUX + UE_LOG_ONLINE(Log, TEXT("libsteam_api.so is linked explicitly and should be already loaded.")); +#endif // PLATFORM_WINDOWS } void FOnlineSubsystemSteamModule::UnloadSteamModules() diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSubsystemSteam.cpp b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSubsystemSteam.cpp index 8cb034fec1a9..d11bcc471c10 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSubsystemSteam.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSubsystemSteam.cpp @@ -40,6 +40,11 @@ #define UE4_PROJECT_STEAMSHIPPINGID 0 #endif +namespace FNetworkProtocolTypes +{ + const FName Steam(TEXT("Steam")); +} + #if !UE_BUILD_SHIPPING namespace OSSConsoleVariables { @@ -124,7 +129,7 @@ inline FString GetSteamAppIdFilename() * Write out the steam app id to the steam_appid.txt file before initializing the API * @param SteamAppId id assigned to the application by Steam */ -static void WriteSteamAppIdToDisk(int32 SteamAppId) +static bool WriteSteamAppIdToDisk(int32 SteamAppId) { if (SteamAppId > 0) { @@ -136,7 +141,8 @@ static void WriteSteamAppIdToDisk(int32 SteamAppId) IFileHandle* Handle = IPlatformFile::GetPlatformPhysical().OpenWrite(*SteamAppIdFilename, false, false); if (!Handle) { - UE_LOG_ONLINE(Fatal, TEXT("Failed to create file: %s"), *SteamAppIdFilename); + UE_LOG_ONLINE(Error, TEXT("Failed to create file: %s"), *SteamAppIdFilename); + return false; } else { @@ -148,8 +154,13 @@ static void WriteSteamAppIdToDisk(int32 SteamAppId) Handle->Write(Archive.GetData(), Archive.Num()); delete Handle; Handle = nullptr; + + return true; } } + + UE_LOG_ONLINE(Warning, TEXT("Steam App Id provided (%d) is invalid, must be greater than 0!"), SteamAppId); + return false; } /** @@ -173,18 +184,25 @@ static void DeleteSteamAppIdFromDisk() * * @param RequireRelaunch enforce the Steam client running precondition * @param RelaunchAppId appid to launch when the Steam client is loaded + * + * @return if this sequence completed without any serious errors */ -void ConfigureSteamInitDevOptions(bool& RequireRelaunch, int32& RelaunchAppId) +bool ConfigureSteamInitDevOptions(bool& RequireRelaunch, int32& RelaunchAppId) { #if !UE_BUILD_SHIPPING && !UE_BUILD_SHIPPING_WITH_EDITOR // Write out the steam_appid.txt file before launching if (!GConfig->GetInt(TEXT("OnlineSubsystemSteam"), TEXT("SteamDevAppId"), RelaunchAppId, GEngineIni)) { UE_LOG_ONLINE(Warning, TEXT("Missing SteamDevAppId key in OnlineSubsystemSteam of DefaultEngine.ini")); + return false; } else { - WriteSteamAppIdToDisk(RelaunchAppId); + if (!WriteSteamAppIdToDisk(RelaunchAppId)) + { + UE_LOG_ONLINE(Warning, TEXT("Could not create/update the steam_appid.txt file! Make sure the directory is writable and there isn't another instance using this file")); + return false; + } } // Should the game force a relaunch in Steam if the client isn't already loaded @@ -198,6 +216,8 @@ void ConfigureSteamInitDevOptions(bool& RequireRelaunch, int32& RelaunchAppId) RequireRelaunch = true; RelaunchAppId = UE4_PROJECT_STEAMSHIPPINGID; #endif + + return true; } FOnlineAuthSteamPtr FOnlineSubsystemSteam::GetAuthInterface() const @@ -381,7 +401,12 @@ bool FOnlineSubsystemSteam::Init() { bool bRelaunchInSteam = false; int RelaunchAppId = 0; - ConfigureSteamInitDevOptions(bRelaunchInSteam, RelaunchAppId); + + if (!ConfigureSteamInitDevOptions(bRelaunchInSteam, RelaunchAppId)) + { + UE_LOG_ONLINE(Warning, TEXT("Could not set up the steam environment! Falling back to another OSS.")); + return false; + } const bool bIsServer = IsRunningDedicatedServer(); bool bAttemptServerInit = true; diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSubsystemSteamTypes.h b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSubsystemSteamTypes.h index 2a5c8329cd7f..a3a03110a6b9 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSubsystemSteamTypes.h +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/OnlineSubsystemSteamTypes.h @@ -322,8 +322,7 @@ public: return SteamP2PAddr.IsValid() && SteamP2PAddr->IsValid() && SessionId.IsValid(); case ESteamSession::AdvertisedSessionHost: case ESteamSession::AdvertisedSessionClient: - // Could/should check that the HostAddr is valid here also - return SteamP2PAddr.IsValid() && SteamP2PAddr->IsValid() && SessionId.IsValid(); + return ((SteamP2PAddr.IsValid() && SteamP2PAddr->IsValid()) || (HostAddr.IsValid() && HostAddr->IsValid())) && SessionId.IsValid(); case ESteamSession::LANSession: default: // LAN case diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/SocketSubsystemSteam.cpp b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/SocketSubsystemSteam.cpp index 35f81e7056f1..ce51c8bfc2c3 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/SocketSubsystemSteam.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/SocketSubsystemSteam.cpp @@ -165,7 +165,7 @@ void FSocketSubsystemSteam::Shutdown() * * @return the new socket or NULL if failed */ -FSocket* FSocketSubsystemSteam::CreateSocket(const FName& SocketType, const FString& SocketDescription, ESocketProtocolFamily ProtocolType) +FSocket* FSocketSubsystemSteam::CreateSocket(const FName& SocketType, const FString& SocketDescription, const FName& ProtocolType) { FSocket* NewSocket = nullptr; if (SocketType == FName("SteamClientSocket")) @@ -174,7 +174,7 @@ FSocket* FSocketSubsystemSteam::CreateSocket(const FName& SocketType, const FStr if (SteamUserPtr != nullptr) { FUniqueNetIdSteam ClientId(SteamUserPtr->GetSteamID()); - NewSocket = new FSocketSteam(SteamNetworking(), ClientId, SocketDescription, ProtocolType); + NewSocket = new FSocketSteam(SteamNetworking(), ClientId, SocketDescription, FNetworkProtocolTypes::Steam); if (NewSocket) { @@ -191,12 +191,12 @@ FSocket* FSocketSubsystemSteam::CreateSocket(const FName& SocketType, const FStr // If the GameServer connection hasn't been created yet, mark the socket as invalid for now if (SessionInt->bSteamworksGameServerConnected && SessionInt->GameServerSteamId->IsValid() && SessionInt->bPolicyResponseReceived) { - NewSocket = new FSocketSteam(SteamGameServerNetworking(), *SessionInt->GameServerSteamId, SocketDescription, ProtocolType); + NewSocket = new FSocketSteam(SteamGameServerNetworking(), *SessionInt->GameServerSteamId, SocketDescription, FNetworkProtocolTypes::Steam); } else { FUniqueNetIdSteam InvalidId(uint64(0)); - NewSocket = new FSocketSteam(SteamGameServerNetworking(), InvalidId, SocketDescription, ProtocolType); + NewSocket = new FSocketSteam(SteamGameServerNetworking(), InvalidId, SocketDescription, FNetworkProtocolTypes::Steam); } if (NewSocket) @@ -263,10 +263,10 @@ void FSocketSubsystemSteam::RegisterConnection(USteamNetConnection* Connection) FWeakObjectPtr ObjectPtr = Connection; SteamConnections.Add(ObjectPtr); - if (Connection->GetInternetAddr().IsValid() && Connection->Socket) + if (Connection->GetRemoteAddr().IsValid() && Connection->Socket) { FSocketSteam* SteamSocket = (FSocketSteam*)Connection->Socket; - TSharedPtr SteamAddr = StaticCastSharedPtr(Connection->GetInternetAddr()); + TSharedPtr SteamAddr = StaticCastSharedPtr(Connection->GetRemoteAddr()); UE_LOG_ONLINE(Log, TEXT("Adding user %s from RegisterConnection"), *SteamAddr->ToString(true)); P2PTouch(SteamSocket->SteamNetworkingPtr, SteamAddr->SteamId, SteamAddr->SteamChannel); @@ -289,9 +289,9 @@ void FSocketSubsystemSteam::UnregisterConnection(USteamNetConnection* Connection // is garbage collected. It's possible that the player who left rejoined before garbage // collection runs (their connection object will be different), so P2PRemove would kick // them from the session when it shouldn't. - if (SteamConnections.RemoveSingleSwap(ObjectPtr) == 1 && Connection->GetInternetAddr().IsValid()) + if (SteamConnections.RemoveSingleSwap(ObjectPtr) == 1 && Connection->GetRemoteAddr().IsValid()) { - TSharedPtr SteamAddr = StaticCastSharedPtr(Connection->GetInternetAddr()); + TSharedPtr SteamAddr = StaticCastSharedPtr(Connection->GetRemoteAddr()); P2PRemove(SteamAddr->SteamId, SteamAddr->SteamChannel); } } @@ -311,7 +311,7 @@ void FSocketSubsystemSteam::ConnectFailure(const FUniqueNetIdSteam& RemoteId) for (int32 ConnIdx=0; ConnIdx(SteamConnections[ConnIdx].Get()); - TSharedPtr RemoteAddrSteam = StaticCastSharedPtr(SteamConn->GetInternetAddr()); + TSharedPtr RemoteAddrSteam = StaticCastSharedPtr(SteamConn->GetRemoteAddr()); // Only checking Id here because its a complete failure (channel doesn't matter) if (RemoteAddrSteam->SteamId == RemoteId) @@ -339,21 +339,82 @@ void FSocketSubsystemSteam::ConnectFailure(const FUniqueNetIdSteam& RemoteId) * @return the array of results from GetAddrInfo */ FAddressInfoResult FSocketSubsystemSteam::GetAddressInfo(const TCHAR* HostName, const TCHAR* ServiceName, - EAddressInfoFlags QueryFlags, ESocketProtocolFamily ProtocolType, ESocketType SocketType) + EAddressInfoFlags QueryFlags, const FName ProtocolTypeName, ESocketType SocketType) { - UE_LOG_ONLINE(Warning, TEXT("GetAddressInfo is not supported on Steam Sockets")); - return FAddressInfoResult(HostName, ServiceName); + int32 UnusedIndex; + FString RawAddress(HostName); + // Remove steam prefixes if they exist + RawAddress.RemoveFromStart(STEAM_URL_PREFIX); + + if (!RawAddress.FindChar(TEXT(':'), UnusedIndex) && !RawAddress.FindChar(TEXT('.'), UnusedIndex)) + { + FAddressInfoResult SteamResult(HostName, ServiceName); + + // This is an Steam address + uint64 Id = FCString::Atoi64(*RawAddress); + if (Id != 0) + { + FString PortString(ServiceName); + SteamResult.ReturnCode = SE_NO_ERROR; + TSharedRef SteamIdAddress = StaticCastSharedRef(CreateInternetAddr()); + SteamIdAddress->SteamId = FUniqueNetIdSteam(Id); + if (PortString.IsNumeric()) + { + SteamIdAddress->SetPort(FCString::Atoi(*PortString)); + } + + SteamResult.Results.Add(FAddressInfoResultData(SteamIdAddress, 0, FNetworkProtocolTypes::Steam, SOCKTYPE_Unknown)); + return SteamResult; + } + else + { + UE_LOG(LogSockets, Warning, TEXT("GetAddressInfo: Could not serialize %s into a SteamID, the ID was invalid."), *RawAddress); + SteamResult.ReturnCode = SE_HOST_NOT_FOUND; + return SteamResult; + } + } + + return ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetAddressInfo(HostName, ServiceName, QueryFlags, ProtocolTypeName, SocketType); } /** - * Does a DNS look up of a host name + * Serializes a string that only contains an address. * - * @param HostName the name of the host to look up - * @param OutAddr the address to copy the IP address to + * On Steam, this will take SteamIDs and serialize them into FInternetAddrSteam if it is determined + * the input string is an ID. Otherwise, this will give you back a FInternetAddrBSD. + * + * This is a what you see is what you get, there is no DNS resolution of the input string, + * so only use this if you know you already have a valid ip address. + * Otherwise, feed the address to GetAddressInfo for guaranteed results. + * + * @param InAddress the address to serialize + * + * @return The FInternetAddr of the given string address. This will point to nullptr on failure. */ -ESocketErrors FSocketSubsystemSteam::GetHostByName(const ANSICHAR* HostName, FInternetAddr& OutAddr) +TSharedPtr FSocketSubsystemSteam::GetAddressFromString(const FString& InAddress) { - return SE_EADDRNOTAVAIL; + int32 UnusedIndex; + FString RawAddress = InAddress; + // Remove steam prefixes if they exist + RawAddress.RemoveFromStart(STEAM_URL_PREFIX); + if (!RawAddress.FindChar(TEXT(':'), UnusedIndex) && !RawAddress.FindChar(TEXT('.'), UnusedIndex)) + { + // This is a Steam Address + uint64 Id = FCString::Atoi64(*RawAddress); + if (Id != 0) + { + TSharedRef ReturnAddress = StaticCastSharedRef(CreateInternetAddr()); + ReturnAddress->SteamId = FUniqueNetIdSteam(Id); + return ReturnAddress; + } + else + { + UE_LOG(LogSockets, Warning, TEXT("Could not serialize %s into a SteamID, the ID was invalid."), *RawAddress); + return nullptr; + } + } + + return ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetAddressFromString(InAddress); } /** @@ -370,13 +431,10 @@ bool FSocketSubsystemSteam::GetHostName(FString& HostName) /** * Create a proper FInternetAddr representation - * @param Address host address - * @param Port host port */ -TSharedRef FSocketSubsystemSteam::CreateInternetAddr(uint32 Address, uint32 Port) +TSharedRef FSocketSubsystemSteam::CreateInternetAddr() { - FInternetAddrSteam* SteamAddr = new FInternetAddrSteam(); - return MakeShareable(SteamAddr); + return MakeShareable(new FInternetAddrSteam()); } /** @@ -727,7 +785,15 @@ void FSocketSubsystemSteam::CleanupDeadConnections(bool bSkipLinger) */ void FSocketSubsystemSteam::DumpSteamP2PSessionInfo(P2PSessionState_t& SessionInfo) { - TSharedRef IpAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr(SessionInfo.m_nRemoteIP, SessionInfo.m_nRemotePort); + FOnlineSubsystemSteam* SteamSubsystem = static_cast(IOnlineSubsystem::Get(STEAM_SUBSYSTEM)); + if (SteamSubsystem == nullptr) + { + return; + } + + TSharedRef IpAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr(); + IpAddr->SetIp(SessionInfo.m_nRemoteIP); + IpAddr->SetPort(SessionInfo.m_nRemotePort); UE_LOG_ONLINE(Verbose, TEXT("- Detailed P2P session info:")); UE_LOG_ONLINE(Verbose, TEXT("-- IPAddress: %s"), *IpAddr->ToString(true)); UE_LOG_ONLINE(Verbose, TEXT("-- ConnectionActive: %i, Connecting: %i, SessionError: %i, UsingRelay: %i"), diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/SocketSubsystemSteam.h b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/SocketSubsystemSteam.h index a1f4d544f63e..22ab4076c589 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/SocketSubsystemSteam.h +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/SocketSubsystemSteam.h @@ -271,7 +271,7 @@ public: * * @return the new socket or NULL if failed */ - virtual class FSocket* CreateSocket(const FName& SocketType, const FString& SocketDescription, ESocketProtocolFamily ProtocolType) override; + virtual class FSocket* CreateSocket(const FName& SocketType, const FString& SocketDescription, const FName& ProtocolType) override; /** * Cleans up a socket class @@ -297,16 +297,25 @@ public: */ virtual FAddressInfoResult GetAddressInfo(const TCHAR* HostName, const TCHAR* ServiceName = nullptr, EAddressInfoFlags QueryFlags = EAddressInfoFlags::Default, - ESocketProtocolFamily ProtocolType = ESocketProtocolFamily::None, + const FName ProtocolTypeName = NAME_None, ESocketType SocketType = ESocketType::SOCKTYPE_Unknown) override; + /** - * Does a DNS look up of a host name + * Serializes a string that only contains an address. + * + * On Steam, this will take SteamIDs and serialize them into FInternetAddrSteam if it is determined + * the input string is an ID. Otherwise, this will give you back a FInternetAddrBSD. * - * @param HostName the name of the host to look up - * @param OutAddr the address to copy the IP address to + * This is a what you see is what you get, there is no DNS resolution of the input string, + * so only use this if you know you already have a valid ip address. + * Otherwise, feed the address to GetAddressInfo for guaranteed results. + * + * @param IPAddress the ip address to serialize + * + * @return The FInternetAddr of the given string address. This will point to nullptr on failure. */ - virtual ESocketErrors GetHostByName(const ANSICHAR* HostName, FInternetAddr& OutAddr) override; + virtual TSharedPtr GetAddressFromString(const FString& IPAddress) override; /** * Some platforms require chat data (voice, text, etc.) to be placed into @@ -338,10 +347,8 @@ public: /** * Create a proper FInternetAddr representation - * @param Address host address - * @param Port host port */ - virtual TSharedRef CreateInternetAddr(uint32 Address=0, uint32 Port=0) override; + virtual TSharedRef CreateInternetAddr() override; /** * @return Whether the machine has a properly configured network device or not diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/SocketsSteam.h b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/SocketsSteam.h index d3ead6fb76eb..864be0efdb4c 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/SocketsSteam.h +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/SocketsSteam.h @@ -55,7 +55,7 @@ public: * @param InSocketDescription the debug description of the socket * @param InSocketProtocol the protocol used to create this socket. */ - FSocketSteam(ISteamNetworking* InSteamNetworkingPtr, FUniqueNetIdSteam& InLocalSteamId, const FString& InSocketDescription, ESocketProtocolFamily InSocketProtocol) : + FSocketSteam(ISteamNetworking* InSteamNetworkingPtr, FUniqueNetIdSteam& InLocalSteamId, const FString& InSocketDescription, const FName& InSocketProtocol) : FSocket(SOCKTYPE_Datagram, InSocketDescription, InSocketProtocol), LocalSteamId(InLocalSteamId), SteamChannel(0), diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/SteamNetDriver.cpp b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/SteamNetDriver.cpp index d003c34b2666..6b006e90f438 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/SteamNetDriver.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/SteamNetDriver.cpp @@ -2,8 +2,8 @@ #include "SteamNetDriver.h" #include "OnlineSubsystemNames.h" -#include "OnlineSubsystem.h" #include "SocketSubsystem.h" +#include "OnlineSubsystemSteam.h" #include "OnlineSubsystemSteamPrivate.h" #include "SocketsSteam.h" #include "SteamNetConnection.h" @@ -89,7 +89,7 @@ bool USteamNetDriver::InitConnect(FNetworkNotify* InNotify, const FURL& ConnectU // If we are opening a Steam URL, create a Steam client socket if (ConnectURL.Host.StartsWith(STEAM_URL_PREFIX)) { - Socket = SteamSockets->CreateSocket(FName(TEXT("SteamClientSocket")), TEXT("Unreal client (Steam)")); + Socket = SteamSockets->CreateSocket(FName(TEXT("SteamClientSocket")), TEXT("Unreal client (Steam)"), FNetworkProtocolTypes::Steam); } else { @@ -106,7 +106,7 @@ bool USteamNetDriver::InitListen(FNetworkNotify* InNotify, FURL& ListenURL, bool if (SteamSockets && !ListenURL.HasOption(TEXT("bIsLanMatch"))) { FName SocketTypeName = IsRunningDedicatedServer() ? FName(TEXT("SteamServerSocket")) : FName(TEXT("SteamClientSocket")); - Socket = SteamSockets->CreateSocket(SocketTypeName, TEXT("Unreal server (Steam)")); + Socket = SteamSockets->CreateSocket(SocketTypeName, TEXT("Unreal server (Steam)"), FNetworkProtocolTypes::Steam); } else { diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/VoiceEngineSteam.cpp b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/VoiceEngineSteam.cpp index 08da7ea0745a..1cecf2e734a8 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/VoiceEngineSteam.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/VoiceEngineSteam.cpp @@ -1,195 +1,42 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "VoiceEngineSteam.h" -#include "Components/AudioComponent.h" -#include "GameFramework/GameSession.h" #include "OnlineSubsystemSteam.h" #include "OnlineSubsystemUtils.h" -#include "Sound/SoundWaveProcedural.h" #include "VoiceModule.h" -#include "Voice.h" #include "SteamUtilities.h" -/** Largest size allowed to carry over into next buffer */ -#define MAX_VOICE_REMAINDER_SIZE 4 * 1024 - -FRemoteTalkerDataSteam::FRemoteTalkerDataSteam() : - MaxUncompressedDataSize(0), - MaxUncompressedDataQueueSize(0), - CurrentUncompressedDataQueueSize(0), - LastSeen(0.0), - NumFramesStarved(0), - VoipSynthComponent(nullptr), - VoiceDecoder(nullptr) -{ - int32 SampleRate = UVOIPStatics::GetVoiceSampleRate(); - int32 NumChannels = DEFAULT_NUM_VOICE_CHANNELS; - VoiceDecoder = FVoiceModule::Get().CreateVoiceDecoder(SampleRate, NumChannels); - check(VoiceDecoder.IsValid()); - - // Approx 1 sec worth of data for a stereo microphone - MaxUncompressedDataSize = UVOIPStatics::GetMaxUncompressedVoiceDataSizePerChannel() * 2; - MaxUncompressedDataQueueSize = MaxUncompressedDataSize * 5; - { - FScopeLock ScopeLock(&QueueLock); - UncompressedDataQueue.Empty(MaxUncompressedDataQueueSize); - } -} - -FRemoteTalkerDataSteam::FRemoteTalkerDataSteam(FRemoteTalkerDataSteam&& Other) -{ - LastSeen = Other.LastSeen; - Other.LastSeen = 0.0; - - NumFramesStarved = Other.NumFramesStarved; - Other.NumFramesStarved = 0; - - VoipSynthComponent = Other.VoipSynthComponent; - Other.VoipSynthComponent = nullptr; - - VoiceDecoder = MoveTemp(Other.VoiceDecoder); - Other.VoiceDecoder = nullptr; - - MaxUncompressedDataSize = Other.MaxUncompressedDataSize; - Other.MaxUncompressedDataSize = 0; - - MaxUncompressedDataQueueSize = Other.MaxUncompressedDataQueueSize; - Other.MaxUncompressedDataQueueSize = 0; - - CurrentUncompressedDataQueueSize = Other.CurrentUncompressedDataQueueSize; - Other.CurrentUncompressedDataQueueSize = 0; - - { - FScopeLock ScopeLock(&Other.QueueLock); - UncompressedDataQueue = MoveTemp(Other.UncompressedDataQueue); - } -} - -FRemoteTalkerDataSteam::~FRemoteTalkerDataSteam() -{ - VoiceDecoder = nullptr; - - CurrentUncompressedDataQueueSize = 0; - - { - FScopeLock ScopeLock(&QueueLock); - UncompressedDataQueue.Empty(); - } -} - -void FRemoteTalkerDataSteam::Reset() -{ - // Set to large number so TickTalkers doesn't come in here - LastSeen = MAX_FLT; - NumFramesStarved = 0; - - if (VoipSynthComponent) - { - VoipSynthComponent->Stop(); - bIsActive = false; - } - - CurrentUncompressedDataQueueSize = 0; - - { - FScopeLock ScopeLock(&QueueLock); - UncompressedDataQueue.Empty(); - } -} - -void FRemoteTalkerDataSteam::Cleanup() -{ - if (VoipSynthComponent) - { - VoipSynthComponent->Stop(); - bIsActive = false; - } - - VoipSynthComponent = nullptr; -} - FVoiceEngineSteam::FVoiceEngineSteam(IOnlineSubsystem* InSubsystem) : - OnlineSubsystem(InSubsystem), - VoiceCapture(nullptr), - VoiceEncoder(nullptr), - OwningUserIndex(INVALID_INDEX), - UncompressedBytesAvailable(0), - CompressedBytesAvailable(0), - AvailableVoiceResult(EVoiceCaptureState::UnInitialized), - bPendingFinalCapture(false), - bIsCapturing(false), - SerializeHelper(nullptr) + FVoiceEngineImpl(InSubsystem), + SteamUserPtr(SteamUser()), + SteamFriendsPtr(SteamFriends()) { - SteamUserPtr = SteamUser(); - SteamFriendsPtr = SteamFriends(); } FVoiceEngineSteam::~FVoiceEngineSteam() { - if (bIsCapturing) + if (IsRecording()) { - VoiceCapture->Stop(); SteamFriendsPtr->SetInGameVoiceSpeaking(SteamUserPtr->GetSteamID(), false); } - - VoiceCapture = nullptr; - VoiceEncoder = nullptr; - - delete SerializeHelper; -} - -void FVoiceEngineSteam::VoiceCaptureUpdate() const -{ - if (bPendingFinalCapture) - { - uint32 CompressedSize; - const EVoiceCaptureState::Type RecordingState = VoiceCapture->GetCaptureState(CompressedSize); - - // If no data is available, we have finished capture the last (post-StopRecording) half-second of voice data - if (RecordingState == EVoiceCaptureState::NotCapturing) - { - UE_LOG_ONLINE_VOICEENGINE(Log, TEXT("Internal voice capture complete.")); - - bPendingFinalCapture = false; - - // If a new recording session has begun since the call to 'StopRecording', kick that off - if (bIsCapturing) - { - StartRecording(); - } - else - { - // Marks that recording has successfully stopped - StoppedRecording(); - } - } - } } void FVoiceEngineSteam::StartRecording() const { UE_LOG_ONLINE_VOICEENGINE(VeryVerbose, TEXT("VOIP StartRecording")); - if (VoiceCapture.IsValid()) + if (GetVoiceCapture().IsValid()) { - if (!VoiceCapture->Start()) + if (!GetVoiceCapture()->Start()) { - UE_LOG(LogVoiceEngine, Warning, TEXT("Failed to start voice recording")); + UE_LOG_ONLINE_VOICEENGINE(Warning, TEXT("Failed to start voice recording")); } - else if (SteamFriendsPtr) { + else if (SteamFriendsPtr) + { SteamFriendsPtr->SetInGameVoiceSpeaking(SteamUserPtr->GetSteamID(), true); } } } -void FVoiceEngineSteam::StopRecording() const -{ - UE_LOG_ONLINE_VOICEENGINE(VeryVerbose, TEXT("VOIP StopRecording")); - if (VoiceCapture.IsValid()) - { - VoiceCapture->Stop(); - } -} - void FVoiceEngineSteam::StoppedRecording() const { UE_LOG_ONLINE_VOICEENGINE(VeryVerbose, TEXT("VOIP StoppedRecording")); @@ -199,500 +46,3 @@ void FVoiceEngineSteam::StoppedRecording() const } } -bool FVoiceEngineSteam::Init(int32 MaxLocalTalkers, int32 MaxRemoteTalkers) -{ - bool bSuccess = false; - - if (!OnlineSubsystem->IsDedicated()) - { - FVoiceModule& VoiceModule = FVoiceModule::Get(); - if (VoiceModule.IsVoiceEnabled()) - { - VoiceCapture = VoiceModule.CreateVoiceCapture(); - VoiceEncoder = VoiceModule.CreateVoiceEncoder(); - - bSuccess = VoiceCapture.IsValid() && VoiceEncoder.IsValid(); - if (bSuccess) - { - CompressedVoiceBuffer.Empty(UVOIPStatics::GetMaxCompressedVoiceDataSize()); - DecompressedVoiceBuffer.Empty(UVOIPStatics::GetMaxUncompressedVoiceDataSizePerChannel()); - - for (int32 TalkerIdx = 0; TalkerIdx < MaxLocalTalkers; TalkerIdx++) - { - PlayerVoiceData[TalkerIdx].VoiceRemainderSize = 0; - PlayerVoiceData[TalkerIdx].VoiceRemainder.Empty(MAX_VOICE_REMAINDER_SIZE); - } - } - else - { - UE_LOG(LogVoice, Warning, TEXT("Voice capture initialization failed!")); - } - } - else - { - UE_LOG(LogVoice, Log, TEXT("Voice module disabled by config [Voice].bEnabled")); - } - } - - return bSuccess; -} - -uint32 FVoiceEngineSteam::StartLocalVoiceProcessing(uint32 LocalUserNum) -{ - uint32 Return = ONLINE_FAIL; - if (IsOwningUser(LocalUserNum)) - { - if (!bIsCapturing) - { - // Update the current recording state, if VOIP data was still being read - VoiceCaptureUpdate(); - - if (!IsRecording()) - { - StartRecording(); - } - - bIsCapturing = true; - } - - Return = ONLINE_SUCCESS; - } - else - { - UE_LOG_ONLINE_VOICEENGINE(Error, TEXT("StartLocalVoiceProcessing(): Device is currently owned by another user")); - } - - return Return; -} - -uint32 FVoiceEngineSteam::StopLocalVoiceProcessing(uint32 LocalUserNum) -{ - uint32 Return = ONLINE_FAIL; - if (IsOwningUser(LocalUserNum)) - { - if (bIsCapturing) - { - bIsCapturing = false; - bPendingFinalCapture = true; - - // Make a call to begin stopping the current VOIP recording session - StopRecording(); - - // Now check/update the status of the recording session - VoiceCaptureUpdate(); - } - - Return = ONLINE_SUCCESS; - } - else - { - UE_LOG_ONLINE_VOICEENGINE(Error, TEXT("StopLocalVoiceProcessing: Ignoring stop request for non-owning user")); - } - - return Return; -} - -uint32 FVoiceEngineSteam::UnregisterRemoteTalker(const FUniqueNetId& UniqueId) -{ - FRemoteTalkerDataSteam* RemoteData = RemoteTalkerBuffers.Find(FUniqueNetIdWrapper(UniqueId.AsShared())); - if (RemoteData != nullptr) - { - // Dump the whole talker - RemoteData->Cleanup(); - RemoteTalkerBuffers.Remove(FUniqueNetIdWrapper(UniqueId.AsShared())); - } - - return ONLINE_SUCCESS; -} - -uint32 FVoiceEngineSteam::GetVoiceDataReadyFlags() const -{ - // First check and update the internal state of VOIP recording - VoiceCaptureUpdate(); - if (OwningUserIndex != INVALID_INDEX && IsRecording()) - { - // Check if there is new data available via the Voice API - if (AvailableVoiceResult == EVoiceCaptureState::Ok && UncompressedBytesAvailable > 0) - { - return 1 << OwningUserIndex; - } - } - - return 0; -} - -uint32 FVoiceEngineSteam::ReadLocalVoiceData(uint32 LocalUserNum, uint8* Data, uint32* Size, uint64* OutSampleCount) -{ - check(*Size > 0); - - // Before doing anything, check/update the current recording state - VoiceCaptureUpdate(); - - // Return data even if not capturing, possibly have data during stopping - if (IsOwningUser(LocalUserNum) && IsRecording()) - { - DecompressedVoiceBuffer.Empty(UVOIPStatics::GetMaxUncompressedVoiceDataSizePerChannel()); - CompressedVoiceBuffer.Empty(UVOIPStatics::GetMaxCompressedVoiceDataSize()); - - uint32 NewVoiceDataBytes = 0; - EVoiceCaptureState::Type VoiceResult = VoiceCapture->GetCaptureState(NewVoiceDataBytes); - if (VoiceResult != EVoiceCaptureState::Ok && VoiceResult != EVoiceCaptureState::NoData) - { - UE_LOG_ONLINE_VOICEENGINE(Warning, TEXT("ReadLocalVoiceData: GetAvailableVoice failure: VoiceResult: %s"), EVoiceCaptureState::ToString(VoiceResult)); - return ONLINE_FAIL; - } - - if (NewVoiceDataBytes == 0) - { - UE_LOG_ONLINE_VOICEENGINE(VeryVerbose, TEXT("ReadLocalVoiceData: No Data: VoiceResult: %s"), EVoiceCaptureState::ToString(VoiceResult)); - *Size = 0; - return ONLINE_SUCCESS; - } - - // Make space for new and any previously remaining data - - // Add the number of new bytes (since last time this function was called) and the number of bytes remaining that wasn't consumed last time this was called - // This is how many bytes we would like to return - uint32 TotalVoiceBytes = NewVoiceDataBytes + PlayerVoiceData[LocalUserNum].VoiceRemainderSize; - - // But we have a max amount we can return so clamp it to that max value if we're asking for more bytes than we're allowed - if (TotalVoiceBytes > UVOIPStatics::GetMaxUncompressedVoiceDataSizePerChannel()) - { - UE_LOG(LogVoiceEngine, Warning, TEXT("Exceeded uncompressed voice buffer size, clamping")) - TotalVoiceBytes = UVOIPStatics::GetMaxUncompressedVoiceDataSizePerChannel(); - } - - DecompressedVoiceBuffer.AddUninitialized(TotalVoiceBytes); - - // If there's still audio left from a previous ReadLocalData call that didn't get output, copy that first into the decompressed voice buffer - if (PlayerVoiceData[LocalUserNum].VoiceRemainderSize > 0) - { - FMemory::Memcpy(DecompressedVoiceBuffer.GetData(), PlayerVoiceData[LocalUserNum].VoiceRemainder.GetData(), PlayerVoiceData[LocalUserNum].VoiceRemainderSize); - } - - // Get new uncompressed data - uint8* RemainingDecompressedBufferPtr = DecompressedVoiceBuffer.GetData() + PlayerVoiceData[LocalUserNum].VoiceRemainderSize; - uint32 ByteWritten = 0; - uint64 NewSampleCount = 0; - VoiceResult = VoiceCapture->GetVoiceData(DecompressedVoiceBuffer.GetData() + PlayerVoiceData[LocalUserNum].VoiceRemainderSize, NewVoiceDataBytes, ByteWritten, NewSampleCount); - - TotalVoiceBytes = ByteWritten + PlayerVoiceData[LocalUserNum].VoiceRemainderSize; - - if ((VoiceResult == EVoiceCaptureState::Ok || VoiceResult == EVoiceCaptureState::NoData) && TotalVoiceBytes > 0) - { - if (OutSampleCount != nullptr) - { - *OutSampleCount = NewSampleCount; - } - - // Prepare the encoded buffer (e.g. opus) - CompressedBytesAvailable = UVOIPStatics::GetMaxCompressedVoiceDataSize(); - CompressedVoiceBuffer.AddUninitialized(UVOIPStatics::GetMaxCompressedVoiceDataSize()); - - check(((uint32)CompressedVoiceBuffer.Num()) <= UVOIPStatics::GetMaxCompressedVoiceDataSize()); - - // Run the uncompressed audio through the opus decoder, note that it may not encode all data, which results in some remaining data - PlayerVoiceData[LocalUserNum].VoiceRemainderSize = - VoiceEncoder->Encode(DecompressedVoiceBuffer.GetData(), TotalVoiceBytes, CompressedVoiceBuffer.GetData(), CompressedBytesAvailable); - - // Save off any unencoded remainder - if (PlayerVoiceData[LocalUserNum].VoiceRemainderSize > 0) - { - if (PlayerVoiceData[LocalUserNum].VoiceRemainderSize > MAX_VOICE_REMAINDER_SIZE) - { - UE_LOG(LogVoiceEngine, Warning, TEXT("Exceeded voice remainder buffer size, clamping")); - PlayerVoiceData[LocalUserNum].VoiceRemainderSize = MAX_VOICE_REMAINDER_SIZE; - } - - PlayerVoiceData[LocalUserNum].VoiceRemainder.AddUninitialized(MAX_VOICE_REMAINDER_SIZE); - FMemory::Memcpy(PlayerVoiceData[LocalUserNum].VoiceRemainder.GetData(), DecompressedVoiceBuffer.GetData() + (TotalVoiceBytes - PlayerVoiceData[LocalUserNum].VoiceRemainderSize), PlayerVoiceData[LocalUserNum].VoiceRemainderSize); - } - - static double LastGetVoiceCallTime = 0.0; - double CurTime = FPlatformTime::Seconds(); - double TimeSinceLastCall = (LastGetVoiceCallTime > 0) ? (CurTime - LastGetVoiceCallTime) : 0.0; - LastGetVoiceCallTime = CurTime; - - UE_LOG(LogVoiceEngine, Log, TEXT("ReadLocalVoiceData: GetVoice: Result: %s, Available: %i, LastCall: %0.3f ms"), EVoiceCaptureState::ToString(VoiceResult), CompressedBytesAvailable, TimeSinceLastCall * 1000.0); - if (CompressedBytesAvailable > 0) - { - *Size = FMath::Min(*Size, CompressedBytesAvailable); - FMemory::Memcpy(Data, CompressedVoiceBuffer.GetData(), *Size); - - UE_LOG(LogVoiceEngine, VeryVerbose, TEXT("ReadLocalVoiceData: Size: %d"), *Size); - return ONLINE_SUCCESS; - } - else - { - *Size = 0; - CompressedVoiceBuffer.Empty(UVOIPStatics::GetMaxCompressedVoiceDataSize()); - - UE_LOG_ONLINE_VOICEENGINE(Warning, TEXT("ReadLocalVoiceData: GetVoice failure: VoiceResult: %s"), EVoiceCaptureState::ToString(VoiceResult)); - return ONLINE_FAIL; - } - } - } - - return ONLINE_FAIL; -} - -uint32 FVoiceEngineSteam::SubmitRemoteVoiceData(const FUniqueNetIdWrapper& RemoteTalkerId, uint8* Data, uint32* Size, uint64& InSampleCount) -{ - UE_LOG_ONLINE_VOICEENGINE(VeryVerbose, TEXT("SubmitRemoteVoiceData(%s) Size: %d received!"), *RemoteTalkerId.ToDebugString(), *Size); - - FRemoteTalkerDataSteam& QueuedData = RemoteTalkerBuffers.FindOrAdd(RemoteTalkerId); - - // new voice packet. - QueuedData.LastSeen = FPlatformTime::Seconds(); - - uint32 BytesWritten = UVOIPStatics::GetMaxUncompressedVoiceDataSizePerChannel(); - - DecompressedVoiceBuffer.Empty(UVOIPStatics::GetMaxUncompressedVoiceDataSizePerChannel()); - DecompressedVoiceBuffer.AddUninitialized(UVOIPStatics::GetMaxUncompressedVoiceDataSizePerChannel()); - QueuedData.VoiceDecoder->Decode(Data, *Size, DecompressedVoiceBuffer.GetData(), BytesWritten); - - // If there is no data, return - if (BytesWritten <= 0) - { - *Size = 0; - return ONLINE_SUCCESS; - } - - bool bAudioComponentCreated = false; - // Generate a streaming wave audio component for voice playback - if (QueuedData.VoipSynthComponent == nullptr || QueuedData.VoipSynthComponent->IsPendingKill()) - { - if (SerializeHelper == nullptr) - { - SerializeHelper = new FVoiceSerializeHelper(this); - } - - QueuedData.VoipSynthComponent = CreateVoiceSynthComponent(UVOIPStatics::GetVoiceSampleRate()); - if (QueuedData.VoipSynthComponent) - { - //TODO, make buffer size and buffering delay runtime-controllable parameters. - QueuedData.bIsActive = false; - QueuedData.VoipSynthComponent->OpenPacketStream(InSampleCount, UVOIPStatics::GetNumBufferedPackets(), UVOIPStatics::GetBufferingDelay()); - QueuedData.bIsEnvelopeBound = false; - } - } - - if (QueuedData.VoipSynthComponent != nullptr) - { - if (!QueuedData.bIsActive) - { - QueuedData.bIsActive = true; - FVoiceSettings InSettings; - UVOIPTalker* OwningTalker = nullptr; - - OwningTalker = UVOIPStatics::GetVOIPTalkerForPlayer(RemoteTalkerId, InSettings); - - ApplyVoiceSettings(QueuedData.VoipSynthComponent, InSettings); - - QueuedData.VoipSynthComponent->ResetBuffer(InSampleCount, UVOIPStatics::GetBufferingDelay()); - QueuedData.VoipSynthComponent->Start(); - QueuedData.CachedTalkerPtr = OwningTalker; - - if (OwningTalker) - { - if (!QueuedData.bIsEnvelopeBound) - { - QueuedData.VoipSynthComponent->OnAudioEnvelopeValueNative.AddUObject(OwningTalker, &UVOIPTalker::OnAudioComponentEnvelopeValue); - QueuedData.bIsEnvelopeBound = true; - } - - OwningTalker->OnTalkingBegin(QueuedData.VoipSynthComponent->GetAudioComponent()); - } - } - - QueuedData.VoipSynthComponent->SubmitPacket((float*)DecompressedVoiceBuffer.GetData(), BytesWritten, InSampleCount, EVoipStreamDataFormat::Int16); - } - - return ONLINE_SUCCESS; -} - -void FVoiceEngineSteam::TickTalkers(float DeltaTime) -{ - // Remove users that are done talking. - const double CurTime = FPlatformTime::Seconds(); - for (FRemoteTalkerData::TIterator It(RemoteTalkerBuffers); It; ++It) - { - FRemoteTalkerDataSteam& RemoteData = It.Value(); - double TimeSince = CurTime - RemoteData.LastSeen; - - if (RemoteData.VoipSynthComponent->IsIdling() && RemoteData.bIsActive) - { - RemoteData.VoipSynthComponent->Stop(); - - UAudioComponent* AudioComponent = RemoteData.VoipSynthComponent->GetAudioComponent(); - if (AudioComponent->IsRegistered()) - { - AudioComponent->UnregisterComponent(); - } - - //If the UVOIPTalker associated with this is still alive, notify it that this player is done talking. - if (UVOIPStatics::IsVOIPTalkerStillAlive(RemoteData.CachedTalkerPtr)) - { - RemoteData.CachedTalkerPtr->OnTalkingEnd(); - } - RemoteData.bIsActive = false; - - RemoteData.Reset(); - } - else if (TimeSince >= UVOIPStatics::GetRemoteTalkerTimeoutDuration()) - { - // Dump the whole talker - RemoteData.Reset(); - } - } -} - -void FVoiceEngineSteam::Tick(float DeltaTime) -{ - // Check available voice once a frame, this value changes after calling GetVoiceData() - AvailableVoiceResult = VoiceCapture->GetCaptureState(UncompressedBytesAvailable); - - TickTalkers(DeltaTime); -} - -void FVoiceEngineSteam::GenerateVoiceData(USoundWaveProcedural* InProceduralWave, int32 SamplesRequired, const FUniqueNetId& TalkerId) -{ - FRemoteTalkerDataSteam* QueuedData = RemoteTalkerBuffers.Find(FUniqueNetIdWrapper(TalkerId.AsShared())); - if (QueuedData) - { - const int32 SampleSize = sizeof(uint16) * DEFAULT_NUM_VOICE_CHANNELS; - - { - FScopeLock ScopeLock(&QueuedData->QueueLock); - QueuedData->CurrentUncompressedDataQueueSize = QueuedData->UncompressedDataQueue.Num(); - const int32 AvailableSamples = QueuedData->CurrentUncompressedDataQueueSize / SampleSize; - if (AvailableSamples >= SamplesRequired) - { - UE_LOG_ONLINE_VOICEENGINE(Verbose, TEXT("GenerateVoiceData %d / %d"), AvailableSamples, SamplesRequired); - const int32 SamplesBytesTaken = AvailableSamples * SampleSize; - InProceduralWave->QueueAudio(QueuedData->UncompressedDataQueue.GetData(), SamplesBytesTaken); - QueuedData->UncompressedDataQueue.RemoveAt(0, SamplesBytesTaken, false); - QueuedData->CurrentUncompressedDataQueueSize -= (SamplesBytesTaken); - } - else - { - UE_LOG_ONLINE_VOICEENGINE(Verbose, TEXT("Voice underflow")); - } - } - } -} - -void FVoiceEngineSteam::OnAudioFinished() -{ - for (FRemoteTalkerData::TIterator It(RemoteTalkerBuffers); It; ++It) - { - FRemoteTalkerDataSteam& RemoteData = It.Value(); - if (RemoteData.VoipSynthComponent->IsIdling()) - { - UE_LOG_ONLINE_VOICEENGINE( Log, TEXT("Removing VOIP AudioComponent for Id: %s"), *It.Key().ToDebugString()); - RemoteData.VoipSynthComponent->Stop(); - RemoteData.bIsActive = false; - break; - } - } - UE_LOG(LogVoiceEngine, Verbose, TEXT("Audio Finished")); -} - -FString FVoiceEngineSteam::GetVoiceDebugState() const -{ - FString Output; - Output = FString::Printf(TEXT("IsRecording: %d\n DataReady: 0x%08x State:%s\n UncompressedBytes: %d\n CompressedBytes: %d\n"), - IsRecording(), - GetVoiceDataReadyFlags(), - EVoiceCaptureState::ToString(AvailableVoiceResult), - UncompressedBytesAvailable, - CompressedBytesAvailable - ); - - // Add remainder size - for (int32 Idx = 0; Idx < MAX_SPLITSCREEN_TALKERS; Idx++) - { - Output += FString::Printf(TEXT("Remainder[%d] %d\n"), Idx, PlayerVoiceData[Idx].VoiceRemainderSize); - } - - return Output; -} - -bool FVoiceEngineSteam::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) -{ - bool bWasHandled = false; - - if (FParse::Command(&Cmd, TEXT("vcvbr"))) - { - // vcvbr - FString VBRStr = FParse::Token(Cmd, false); - int32 ShouldVBR = FPlatformString::Atoi(*VBRStr); - bool bVBR = ShouldVBR != 0; - if (VoiceEncoder.IsValid()) - { - if (!VoiceEncoder->SetVBR(bVBR)) - { - UE_LOG(LogVoice, Warning, TEXT("Failed to set VBR %d"), bVBR); - } - } - - bWasHandled = true; - } - else if (FParse::Command(&Cmd, TEXT("vcbitrate"))) - { - // vcbitrate - FString BitrateStr = FParse::Token(Cmd, false); - int32 NewBitrate = !BitrateStr.IsEmpty() ? FPlatformString::Atoi(*BitrateStr) : 0; - if (VoiceEncoder.IsValid() && NewBitrate > 0) - { - if (!VoiceEncoder->SetBitrate(NewBitrate)) - { - UE_LOG(LogVoice, Warning, TEXT("Failed to set bitrate %d"), NewBitrate); - } - } - - bWasHandled = true; - } - else if (FParse::Command(&Cmd, TEXT("vccomplexity"))) - { - // vccomplexity - FString ComplexityStr = FParse::Token(Cmd, false); - int32 NewComplexity = !ComplexityStr.IsEmpty() ? FPlatformString::Atoi(*ComplexityStr) : -1; - if (VoiceEncoder.IsValid() && NewComplexity >= 0) - { - if (!VoiceEncoder->SetComplexity(NewComplexity)) - { - UE_LOG(LogVoice, Warning, TEXT("Failed to set complexity %d"), NewComplexity); - } - } - - bWasHandled = true; - } - else if (FParse::Command(&Cmd, TEXT("vcdump"))) - { - if (VoiceCapture.IsValid()) - { - VoiceCapture->DumpState(); - } - - if (VoiceEncoder.IsValid()) - { - VoiceEncoder->DumpState(); - } - - for (FRemoteTalkerData::TIterator It(RemoteTalkerBuffers); It; ++It) - { - FRemoteTalkerDataSteam& RemoteData = It.Value(); - if (RemoteData.VoiceDecoder.IsValid()) - { - RemoteData.VoiceDecoder->DumpState(); - } - } - - bWasHandled = true; - } - - return bWasHandled; -} - - diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/VoiceEngineSteam.h b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/VoiceEngineSteam.h index 44b0e15c74c2..d4ed38ddbef4 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/VoiceEngineSteam.h +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Private/VoiceEngineSteam.h @@ -3,319 +3,43 @@ #pragma once #include "CoreMinimal.h" -#include "UObject/GCObject.h" #include "OnlineSubsystemTypes.h" #include "OnlineSubsystemSteamTypes.h" -#include "Interfaces/VoiceInterface.h" -#include "Net/VoiceDataCommon.h" -#include "Interfaces/VoiceCapture.h" -#include "Interfaces/VoiceCodec.h" #include "OnlineSubsystemUtilsPackage.h" -#include "VoipListenerSynthComponent.h" +#include "VoiceEngineImpl.h" class IOnlineSubsystem; class FUniqueNetIdSteam; -class IVoiceDecoder; -class IVoiceEncoder; -class IVoiceCapture; #define INVALID_INDEX -1 -/** -* Container for unprocessed voice data -*/ -struct FLocalVoiceDataSteam -{ - FLocalVoiceDataSteam() : - VoiceRemainderSize(0) - { - } - - /** Amount of voice data not encoded last time */ - uint32 VoiceRemainderSize; - /** Voice sample data not encoded last time */ - TArray VoiceRemainder; -}; - -/** -* Remote voice data playing on a single client -*/ -class FRemoteTalkerDataSteam -{ -public: - - FRemoteTalkerDataSteam(); - /** Required for TMap FindOrAdd() */ - FRemoteTalkerDataSteam(FRemoteTalkerDataSteam&& Other); - ~FRemoteTalkerDataSteam(); - - /** Reset the talker after long periods of silence */ - void Reset(); - /** Cleanup the talker before unregistration */ - void Cleanup(); - - /** Maximum size of a single decoded packet */ - int32 MaxUncompressedDataSize; - /** Maximum size of the outgoing playback queue */ - int32 MaxUncompressedDataQueueSize; - /** Amount of data currently in the outgoing playback queue */ - int32 CurrentUncompressedDataQueueSize; - - /** Receive side timestamp since last voice packet fragment */ - double LastSeen; - /** Number of frames starved of audio */ - int32 NumFramesStarved; - /** Synth component playing this buffer (only valid on remote instances) */ - UVoipListenerSynthComponent* VoipSynthComponent; - /** Cached Talker Ptr. Is checked against map before use to ensure it has not been destroyed. */ - UVOIPTalker* CachedTalkerPtr; - /** Boolean used to ensure that we only bind the VOIP talker to the SynthComponent's corresponding envelope delegate once. */ - bool bIsEnvelopeBound; - /** Boolean flag used to tell whether this synth component is currently consuming incoming voice packets. */ - bool bIsActive; - /** Buffer for outgoing audio intended for procedural streaming */ - mutable FCriticalSection QueueLock; - TArray UncompressedDataQueue; - /** Per remote talker voice decoding state */ - TSharedPtr VoiceDecoder; -}; - /** * Generic implementation of voice engine, using Voice module for capture/codec */ -class FVoiceEngineSteam : public IVoiceEngine, public FSelfRegisteringExec +class FVoiceEngineSteam : public FVoiceEngineImpl { - class FVoiceSerializeHelper : public FGCObject - { - /** Reference to audio components */ - FVoiceEngineSteam* VoiceEngine; - FVoiceSerializeHelper() : - VoiceEngine(nullptr) - {} - - public: - - FVoiceSerializeHelper(FVoiceEngineSteam* InVoiceEngine) : - VoiceEngine(InVoiceEngine) - {} - ~FVoiceSerializeHelper() {} - - /** FGCObject interface */ - virtual void AddReferencedObjects(FReferenceCollector& Collector) override - { - // Prevent garbage collection of audio components - for (FRemoteTalkerData::TIterator It(VoiceEngine->RemoteTalkerBuffers); It; ++It) - { - FRemoteTalkerDataSteam& RemoteData = It.Value(); - if (RemoteData.VoipSynthComponent) - { - Collector.AddReferencedObject(RemoteData.VoipSynthComponent); - } - } - } - }; - - friend class FVoiceSerializeHelper; - - /** Mapping of UniqueIds to the incoming voice data and their audio component */ - typedef TMap FRemoteTalkerData; - - /** Reference to the main online subsystem */ - IOnlineSubsystem* OnlineSubsystem; - /** Steam User interface */ class ISteamUser* SteamUserPtr; /** Steam Friends interface */ class ISteamFriends* SteamFriendsPtr; - FLocalVoiceDataSteam PlayerVoiceData[MAX_SPLITSCREEN_TALKERS]; - /** Reference to voice capture device */ - TSharedPtr VoiceCapture; - /** Reference to voice encoding object */ - TSharedPtr VoiceEncoder; - - /** User index currently holding onto the voice interface */ - int32 OwningUserIndex; - /** Amount of uncompressed data available this frame */ - uint32 UncompressedBytesAvailable; - /** Amount of compressed data available this frame */ - uint32 CompressedBytesAvailable; - /** Current frame state of voice capture */ - EVoiceCaptureState::Type AvailableVoiceResult; - /** Have we stopped capturing voice but are waiting for its completion */ - mutable bool bPendingFinalCapture; - /** State of voice recording */ - bool bIsCapturing; - - /** Data from voice codec, waiting to send to network. */ - TArray CompressedVoiceBuffer; - /** Data from network playing on an audio component. */ - FRemoteTalkerData RemoteTalkerBuffers; - /** Voice decompression buffer, shared by all talkers, valid during SubmitRemoteVoiceData */ - TArray DecompressedVoiceBuffer; - /** Serialization helper */ - FVoiceSerializeHelper* SerializeHelper; - - /** - * Determines if the specified index is the owner or not - * - * @param InIndex the index being tested - * - * @return true if this is the owner, false otherwise - */ - FORCEINLINE bool IsOwningUser(uint32 UserIndex) - { - return UserIndex >= 0 && UserIndex < MAX_SPLITSCREEN_TALKERS && OwningUserIndex == UserIndex; - } - - /** - * Update the internal state of the voice capturing state - * Handles possible continuation waiting for capture stop event - */ - void VoiceCaptureUpdate() const; - /** Start capturing voice data */ - void StartRecording() const; - - /** Stop capturing voice data */ - void StopRecording() const; + virtual void StartRecording() const override; /** Called when "last half second" is over */ - void StoppedRecording() const; - - /** @return is active recording occurring at the moment */ - bool IsRecording() const { return bIsCapturing || bPendingFinalCapture; } - - /** - * Callback from streaming audio when data is requested for playback - * - * @param InProceduralWave SoundWave requesting more data - * @param SamplesRequired number of samples needed for immediate playback - * @param TalkerId id of the remote talker to allocate voice data for - */ - void GenerateVoiceData(USoundWaveProcedural* InProceduralWave, int32 SamplesRequired, const FUniqueNetId& TalkerId); + virtual void StoppedRecording() const override; PACKAGE_SCOPE: /** Constructor */ FVoiceEngineSteam() : - OnlineSubsystem(nullptr), + FVoiceEngineImpl(), SteamUserPtr(nullptr), - SteamFriendsPtr(nullptr), - VoiceCapture(nullptr), - VoiceEncoder(nullptr), - OwningUserIndex(INVALID_INDEX), - UncompressedBytesAvailable(0), - CompressedBytesAvailable(0), - AvailableVoiceResult(EVoiceCaptureState::UnInitialized), - bPendingFinalCapture(false), - bIsCapturing(false), - SerializeHelper(nullptr) + SteamFriendsPtr(nullptr) {}; - // IVoiceEngine - virtual bool Init(int32 MaxLocalTalkers, int32 MaxRemoteTalkers) override; - public: FVoiceEngineSteam(IOnlineSubsystem* InSubsystem); virtual ~FVoiceEngineSteam(); - - // IVoiceEngine - virtual uint32 StartLocalVoiceProcessing(uint32 LocalUserNum) override; - virtual uint32 StopLocalVoiceProcessing(uint32 LocalUserNum) override; - - virtual uint32 StartRemoteVoiceProcessing(const FUniqueNetId& UniqueId) override - { - // Not needed - return ONLINE_SUCCESS; - } - - virtual uint32 StopRemoteVoiceProcessing(const FUniqueNetId& UniqueId) override - { - // Not needed - return ONLINE_SUCCESS; - } - - virtual uint32 RegisterLocalTalker(uint32 LocalUserNum) override - { - if (OwningUserIndex == INVALID_INDEX) - { - OwningUserIndex = LocalUserNum; - return ONLINE_SUCCESS; - } - - return ONLINE_FAIL; - } - - virtual uint32 UnregisterLocalTalker(uint32 LocalUserNum) override - { - if (IsOwningUser(LocalUserNum)) - { - OwningUserIndex = INVALID_INDEX; - return ONLINE_SUCCESS; - } - - return ONLINE_FAIL; - } - - virtual uint32 RegisterRemoteTalker(const FUniqueNetId& UniqueId) override - { - // Not needed - return ONLINE_SUCCESS; - } - - virtual uint32 UnregisterRemoteTalker(const FUniqueNetId& UniqueId) override; - - virtual bool IsHeadsetPresent(uint32 LocalUserNum) override - { - return IsOwningUser(LocalUserNum) ? true : false; - } - - virtual bool IsLocalPlayerTalking(uint32 LocalUserNum) override - { - return (GetVoiceDataReadyFlags() & (LocalUserNum << 1)) != 0; - } - - virtual bool IsRemotePlayerTalking(const FUniqueNetId& UniqueId) override - { - return RemoteTalkerBuffers.Find(FUniqueNetIdWrapper(UniqueId.AsShared())) != nullptr; - } - - virtual uint32 GetVoiceDataReadyFlags() const override; - virtual uint32 SetPlaybackPriority(uint32 LocalUserNum, const FUniqueNetId& RemoteTalkerId, uint32 Priority) override - { - // Not supported - return ONLINE_SUCCESS; - } - - virtual uint32 ReadLocalVoiceData(uint32 LocalUserNum, uint8* Data, uint32* Size) override { return ReadLocalVoiceData(LocalUserNum, Data, Size, nullptr); } - virtual uint32 ReadLocalVoiceData(uint32 LocalUserNum, uint8* Data, uint32* Size, uint64* OutSampleCount) override; - - virtual uint32 SubmitRemoteVoiceData(const FUniqueNetId& RemoteTalkerId, uint8* Data, uint32* Size) - { - checkf(false, TEXT("Please use the following function signature instead: SubmitRemoteVoiceData(const FUniqueNetIdWrapper& RemoteTalkerId, uint8* Data, uint32* Size, uint64& InSampleCount)")); - return 0; - } - virtual uint32 SubmitRemoteVoiceData(const FUniqueNetIdWrapper& RemoteTalkerId, uint8* Data, uint32* Size, uint64& InSampleCount) override; - - virtual void Tick(float DeltaTime) override; - FString GetVoiceDebugState() const override; - - // FSelfRegisteringExec - virtual bool Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) override; - -private: - - /** - * Update the state of all remote talkers, possibly dropping data or the talker entirely - */ - void TickTalkers(float DeltaTime); - - /** - * Delegate triggered when an audio component Stop() function is called - */ - void OnAudioFinished(); }; - -typedef TSharedPtr FVoiceEngineImplPtr; diff --git a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Public/OnlineSubsystemSteam.h b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Public/OnlineSubsystemSteam.h index d8bc7ba2385b..10e98219be1c 100644 --- a/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Public/OnlineSubsystemSteam.h +++ b/Engine/Plugins/Online/OnlineSubsystemSteam/Source/Public/OnlineSubsystemSteam.h @@ -306,5 +306,10 @@ public: } }; +namespace FNetworkProtocolTypes +{ + ONLINESUBSYSTEMSTEAM_API extern const FName Steam; +} + typedef TSharedPtr FOnlineSubsystemSteamPtr; diff --git a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/IpConnection.h b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/IpConnection.h index 381a4aadabca..a201333e3451 100644 --- a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/IpConnection.h +++ b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/IpConnection.h @@ -22,10 +22,6 @@ class ONLINESUBSYSTEMUTILS_API UIpConnection : public UNetConnection GENERATED_UCLASS_BODY() // Variables. - // @todo #JIRA UENET-883: This should be moved down to UNetConnection, now that GetInternetAddr is a thing. - // A lot of platforms reinvent the wheel in their own inefficient way here, despite having their own FInternetAddr type - TSharedPtr RemoteAddr; - class FSocket* Socket; class FResolveInfo* ResolveInfo; @@ -36,10 +32,6 @@ class ONLINESUBSYSTEMUTILS_API UIpConnection : public UNetConnection virtual void LowLevelSend(void* Data, int32 CountBits, FOutPacketTraits& Traits) override; FString LowLevelGetRemoteAddress(bool bAppendPort=false) override; FString LowLevelDescribe() override; - virtual int32 GetAddrAsInt(void) override; - virtual int32 GetAddrPort(void) override; - virtual TSharedPtr GetInternetAddr() override; - virtual FString RemoteAddressToString() override; virtual void Tick() override; virtual void CleanUp() override; virtual void ReceivedRawPacket(void* Data, int32 Count) override; diff --git a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/IpNetDriver.h b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/IpNetDriver.h index cc9dc2f4f33b..c74fb83fca3b 100644 --- a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/IpNetDriver.h +++ b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Classes/IpNetDriver.h @@ -39,9 +39,6 @@ class ONLINESUBSYSTEMUTILS_API UIpNetDriver : public UNetDriver UPROPERTY(Config) uint32 MaxPortCountToTry; - /** Local address this net driver is associated with */ - TSharedPtr LocalAddr; - /** Underlying socket communication */ FSocket* Socket; @@ -54,7 +51,7 @@ class ONLINESUBSYSTEMUTILS_API UIpNetDriver : public UNetDriver virtual bool InitConnect( FNetworkNotify* InNotify, const FURL& ConnectURL, FString& Error ) override; virtual bool InitListen( FNetworkNotify* InNotify, FURL& LocalURL, bool bReuseAddressAndPort, FString& Error ) override; virtual void TickDispatch( float DeltaTime ) override; - virtual void LowLevelSend(FString Address, void* Data, int32 CountBits, FOutPacketTraits& Traits) override; + virtual void LowLevelSend(TSharedPtr Address, void* Data, int32 CountBits, FOutPacketTraits& Traits) override; virtual FString LowLevelGetNetworkNumber() override; virtual void LowLevelDestroy() override; virtual class ISocketSubsystem* GetSocketSubsystem() override; diff --git a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/IpConnection.cpp b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/IpConnection.cpp index 6a147985032e..61cb9e12a2a3 100644 --- a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/IpConnection.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/IpConnection.cpp @@ -37,7 +37,6 @@ TAutoConsoleVariable CVarNetIpConnectionUseSendTasks( UIpConnection::UIpConnection(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer), - RemoteAddr(NULL), Socket(NULL), ResolveInfo(NULL), SocketErrorDisconnectDelay(5.f), @@ -97,13 +96,7 @@ void UIpConnection::InitRemoteConnection(UNetDriver* InDriver, class FSocket* In (InMaxPacket == 0 || InMaxPacket > MAX_PACKET_SIZE) ? MAX_PACKET_SIZE : InMaxPacket, InPacketOverhead == 0 ? UDP_HEADER_SIZE : InPacketOverhead); - // Copy the remote IPAddress passed in - bool bIsValid = false; - FString IpAddrStr = InRemoteAddr.ToString(false); - RemoteAddr = InDriver->GetSocketSubsystem()->CreateInternetAddr(); - RemoteAddr->SetIp(*IpAddrStr, bIsValid); - RemoteAddr->SetPort(InRemoteAddr.GetPort()); - + RemoteAddr = InRemoteAddr.Clone(); URL.Host = RemoteAddr->ToString(false); // Initialize our send bunch @@ -410,26 +403,3 @@ FString UIpConnection::LowLevelDescribe() : TEXT("Invalid") ); } - -int32 UIpConnection::GetAddrAsInt(void) -{ - uint32 OutAddr = 0; - // Get the host byte order ip addr - RemoteAddr->GetIp(OutAddr); - return (int32)OutAddr; -} - -int32 UIpConnection::GetAddrPort(void) -{ - return RemoteAddr->GetPort(); -} - -TSharedPtr UIpConnection::GetInternetAddr() -{ - return RemoteAddr; -} - -FString UIpConnection::RemoteAddressToString() -{ - return RemoteAddr->ToString(true); -} diff --git a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/IpNetDriver.cpp b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/IpNetDriver.cpp index 109c81c0f995..93dda74fd753 100644 --- a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/IpNetDriver.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/IpNetDriver.cpp @@ -359,7 +359,7 @@ FSocket * UIpNetDriver::CreateSocket() return NULL; } - return SocketSubsystem->CreateSocket( NAME_DGram, TEXT( "Unreal" ) ); + return SocketSubsystem->CreateSocket(NAME_DGram, TEXT("Unreal"), ( LocalAddr.IsValid() ? LocalAddr->GetProtocolType() : NAME_None) ); } int UIpNetDriver::GetClientPort() @@ -375,18 +375,32 @@ bool UIpNetDriver::InitBase( bool bInitAsClient, FNetworkNotify* InNotify, const } ISocketSubsystem* SocketSubsystem = GetSocketSubsystem(); - if (SocketSubsystem == NULL) + if (SocketSubsystem == nullptr) { UE_LOG(LogNet, Warning, TEXT("Unable to find socket subsystem")); return false; } // Derived types may have already allocated a socket + const TCHAR* MultiHomeBindAddr = URL.GetOption(TEXT("multihome="), nullptr); + if (MultiHomeBindAddr != nullptr) + { + LocalAddr = SocketSubsystem->GetAddressFromString(MultiHomeBindAddr); + if (!LocalAddr.IsValid()) + { + UE_LOG(LogNet, Warning, TEXT("Failed to created valid address from multihome address: %s"), MultiHomeBindAddr); + } + } + + if (!LocalAddr.IsValid()) + { + LocalAddr = SocketSubsystem->GetLocalBindAddr(*GLog); + } // Create the socket that we will use to communicate with Socket = CreateSocket(); - if( Socket == NULL ) + if(Socket == nullptr) { Socket = 0; Error = FString::Printf( TEXT("%s: socket failed (%i)"), SocketSubsystem->GetSocketAPIName(), (int32)SocketSubsystem->GetLastErrorCode() ); @@ -420,27 +434,6 @@ bool UIpNetDriver::InitBase( bool bInitAsClient, FNetworkNotify* InNotify, const UE_LOG(LogInit, Log, TEXT("%s: Socket queue. Rx: %i (config %i) Tx: %i (config %i)"), SocketSubsystem->GetSocketAPIName(), ActualRecvSize, DesiredRecvSize, ActualSendSize, DesiredSendSize); - // allow multihome as a url option - const TCHAR* MultiHomeBindAddr = URL.GetOption(TEXT("multihome="), nullptr); - if (MultiHomeBindAddr != nullptr) - { - LocalAddr = SocketSubsystem->CreateInternetAddr(); - - bool bIsValid = false; - LocalAddr->SetIp(MultiHomeBindAddr, bIsValid); - - if (!bIsValid) - { - LocalAddr = nullptr; - UE_LOG(LogNet, Warning, TEXT("Failed to created valid address from multihome address: %s"), MultiHomeBindAddr); - } - } - - if (!LocalAddr.IsValid()) - { - LocalAddr = SocketSubsystem->GetLocalBindAddr(*GLog); - } - // Bind socket to our port. LocalAddr->SetPort(bInitAsClient ? GetClientPort() : URL.Port); @@ -695,7 +688,8 @@ void UIpNetDriver::TickDispatch(float DeltaTime) if (Connection == nullptr) { - UNetConnection** Result = MappedClientConnections.Find(FromAddr); + const TSharedRef ConstFromAddr = FromAddr; + UNetConnection** Result = MappedClientConnections.Find(ConstFromAddr); if (Result != nullptr) { @@ -828,11 +822,11 @@ UNetConnection* UIpNetDriver::ProcessConnectionlessPacket(const TSharedRefIncomingConnectionless(IncomingAddress, Data, CountBytesRef); + const ProcessedPacket UnProcessedPacket = ConnectionlessHandler->IncomingConnectionless(Address, Data, CountBytesRef); if (!UnProcessedPacket.bError) { - bPassedChallenge = StatelessConnect->HasPassedChallenge(IncomingAddress, bRestartedHandshake); + bPassedChallenge = StatelessConnect->HasPassedChallenge(Address, bRestartedHandshake); if (bPassedChallenge) { @@ -977,17 +971,9 @@ UNetConnection* UIpNetDriver::ProcessConnectionlessPacket(const TSharedRef Address, void* Data, int32 CountBits, FOutPacketTraits& Traits) { - bool bValidAddress = !Address.IsEmpty(); - TSharedRef RemoteAddr = GetSocketSubsystem()->CreateInternetAddr(); - - if (bValidAddress) - { - RemoteAddr->SetIp(*Address, bValidAddress); - } - - if (bValidAddress) + if (Address.IsValid() && Address->IsValid()) { const uint8* DataToSend = reinterpret_cast(Data); @@ -1013,7 +999,7 @@ void UIpNetDriver::LowLevelSend(FString Address, void* Data, int32 CountBits, FO if (CountBits > 0) { CLOCK_CYCLES(SendCycles); - Socket->SendTo(DataToSend, FMath::DivideAndRoundUp(CountBits, 8), BytesSent, *RemoteAddr); + Socket->SendTo(DataToSend, FMath::DivideAndRoundUp(CountBits, 8), BytesSent, *Address); UNCLOCK_CYCLES(SendCycles); } @@ -1025,7 +1011,7 @@ void UIpNetDriver::LowLevelSend(FString Address, void* Data, int32 CountBits, FO } else { - UE_LOG(LogNet, Warning, TEXT("UIpNetDriver::LowLevelSend: Invalid send address '%s'"), *Address); + UE_LOG(LogNet, Warning, TEXT("UIpNetDriver::LowLevelSend: Invalid send address '%s'"), *Address->ToString(true)); } } @@ -1033,7 +1019,7 @@ void UIpNetDriver::LowLevelSend(FString Address, void* Data, int32 CountBits, FO FString UIpNetDriver::LowLevelGetNetworkNumber() { - return LocalAddr->ToString(true); + return LocalAddr.IsValid() ? LocalAddr->ToString(true) : FString(TEXT("")); } void UIpNetDriver::LowLevelDestroy() diff --git a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/OnlineSubsystemUtils.cpp b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/OnlineSubsystemUtils.cpp index 2960c194d49a..4860977678b6 100644 --- a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/OnlineSubsystemUtils.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Private/OnlineSubsystemUtils.cpp @@ -218,7 +218,9 @@ int32 GetClientPeerIp(FName InstanceName, const FUniqueNetId& UserId) if (ClientConnection && ClientConnection->PlayerId.ToString() == UserId.ToString()) { + PRAGMA_DISABLE_DEPRECATION_WARNINGS PeerIp = ClientConnection->GetAddrAsInt(); + PRAGMA_ENABLE_DEPRECATION_WARNINGS break; } } diff --git a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Public/VoiceEngineImpl.h b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Public/VoiceEngineImpl.h index 1a0b3d7ffe43..8284be2ae27b 100644 --- a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Public/VoiceEngineImpl.h +++ b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Public/VoiceEngineImpl.h @@ -161,6 +161,7 @@ class ONLINESUBSYSTEMUTILS_API FVoiceEngineImpl : public IVoiceEngine, public FS const double DeviceChangeDelay = 2.0f; #endif +protected: /** * Determines if the specified index is the owner or not * @@ -168,29 +169,30 @@ class ONLINESUBSYSTEMUTILS_API FVoiceEngineImpl : public IVoiceEngine, public FS * * @return true if this is the owner, false otherwise */ - FORCEINLINE bool IsOwningUser(uint32 UserIndex) + FORCEINLINE virtual bool IsOwningUser(uint32 UserIndex) { return UserIndex >= 0 && UserIndex < MAX_SPLITSCREEN_TALKERS && OwningUserIndex == UserIndex; } - /** - * Update the internal state of the voice capturing state + /** Start capturing voice data */ + virtual void StartRecording() const; + + /** Stop capturing voice data */ + virtual void StopRecording() const; + + /** Called when "last half second" is over */ + virtual void StoppedRecording() const; + + /** @return is active recording occurring at the moment */ + virtual bool IsRecording() const { return bIsCapturing || bPendingFinalCapture; } + +private: + /** + * Update the internal state of the voice capturing state * Handles possible continuation waiting for capture stop event */ void VoiceCaptureUpdate() const; - /** Start capturing voice data */ - void StartRecording() const; - - /** Stop capturing voice data */ - void StopRecording() const; - - /** Called when "last half second" is over */ - void StoppedRecording() const; - - /** @return is active recording occurring at the moment */ - bool IsRecording() const { return bIsCapturing || bPendingFinalCapture; } - /** * Callback from streaming audio when data is requested for playback * @@ -298,15 +300,17 @@ private: void OnPostLoadMap(UWorld*); protected: - IOnlineSubsystem* GetOnlineSubSystem() { return OnlineSubsystem; } - TSharedPtr& GetVoiceCapture() { return VoiceCapture; } - TSharedPtr& GetVoiceEncoder() { return VoiceEncoder; } - FRemoteTalkerData& GetRemoteTalkerBuffers() { return RemoteTalkerBuffers; } - TArray& GetCompressedVoiceBuffer() { return CompressedVoiceBuffer; } - TArray& GetDecompressedVoiceBuffer() { return DecompressedVoiceBuffer; } - FLocalVoiceData* GetLocalPlayerVoiceData() { return PlayerVoiceData; } - int32 GetMaxVoiceRemainderSize(); - void CreateSerializeHelper(); + virtual IOnlineSubsystem* GetOnlineSubSystem() { return OnlineSubsystem; } + virtual const TSharedPtr& GetVoiceCapture() const { return VoiceCapture; } + virtual TSharedPtr& GetVoiceCapture() { return VoiceCapture; } + virtual const TSharedPtr& GetVoiceEncoder() const { return VoiceEncoder; } + virtual TSharedPtr& GetVoiceEncoder() { return VoiceEncoder; } + virtual FRemoteTalkerData& GetRemoteTalkerBuffers() { return RemoteTalkerBuffers; } + virtual TArray& GetCompressedVoiceBuffer() { return CompressedVoiceBuffer; } + virtual TArray& GetDecompressedVoiceBuffer() { return DecompressedVoiceBuffer; } + virtual FLocalVoiceData* GetLocalPlayerVoiceData() { return PlayerVoiceData; } + virtual int32 GetMaxVoiceRemainderSize(); + virtual void CreateSerializeHelper(); // Get Audio Device Changes on Windows #if PLATFORM_WINDOWS diff --git a/Engine/Plugins/Online/VoiceChat/VivoxVoiceChat/Source/Private/VivoxVoiceChat.cpp b/Engine/Plugins/Online/VoiceChat/VivoxVoiceChat/Source/Private/VivoxVoiceChat.cpp index 4e99c852a622..ed844dae4d6f 100644 --- a/Engine/Plugins/Online/VoiceChat/VivoxVoiceChat/Source/Private/VivoxVoiceChat.cpp +++ b/Engine/Plugins/Online/VoiceChat/VivoxVoiceChat/Source/Private/VivoxVoiceChat.cpp @@ -15,6 +15,8 @@ DEFINE_LOG_CATEGORY(LogVivoxVoiceChat); +FVivoxDelegates::FOnAudioUnitCaptureDeviceStatusChanged FVivoxDelegates::OnAudioUnitCaptureDeviceStatusChanged; + namespace { static const FVoiceChatResult ResultSuccess = { true, 0, TEXT("") }; diff --git a/Engine/Plugins/Online/VoiceChat/VivoxVoiceChat/Source/Public/VivoxVoiceChat.h b/Engine/Plugins/Online/VoiceChat/VivoxVoiceChat/Source/Public/VivoxVoiceChat.h index c51a4e5f77a9..9929ae6909b7 100644 --- a/Engine/Plugins/Online/VoiceChat/VivoxVoiceChat/Source/Public/VivoxVoiceChat.h +++ b/Engine/Plugins/Online/VoiceChat/VivoxVoiceChat/Source/Public/VivoxVoiceChat.h @@ -23,6 +23,14 @@ DECLARE_LOG_CATEGORY_EXTERN(LogVivoxVoiceChat, Log, All); DECLARE_STATS_GROUP(TEXT("Vivox"), STATGROUP_Vivox, STATCAT_Advanced); +VIVOXVOICECHAT_API class FVivoxDelegates +{ +public: + /** Delegate called when the status of the audio device has changed. Triggered on platforms that the vivox sdk provides this event for */ + DECLARE_MULTICAST_DELEGATE_OneParam(FOnAudioUnitCaptureDeviceStatusChanged, int); + static FOnAudioUnitCaptureDeviceStatusChanged OnAudioUnitCaptureDeviceStatusChanged; +}; + class FVivoxVoiceChat : public FSelfRegisteringExec, public IVoiceChat, protected VivoxClientApi::DebugClientApiEventHandler { public: diff --git a/Engine/Plugins/Runtime/AR/Apple/AppleARKitFaceSupport/Source/AppleARKitFaceSupport/Private/AppleARKitLiveLinkSource.cpp b/Engine/Plugins/Runtime/AR/Apple/AppleARKitFaceSupport/Source/AppleARKitFaceSupport/Private/AppleARKitLiveLinkSource.cpp index 18b507dcc991..73eda8e56eb2 100644 --- a/Engine/Plugins/Runtime/AR/Apple/AppleARKitFaceSupport/Source/AppleARKitFaceSupport/Private/AppleARKitLiveLinkSource.cpp +++ b/Engine/Plugins/Runtime/AR/Apple/AppleARKitFaceSupport/Source/AppleARKitFaceSupport/Private/AppleARKitLiveLinkSource.cpp @@ -248,7 +248,7 @@ bool FAppleARKitLiveLinkRemotePublisher::InitSendSocket() { ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(); // Allocate our socket for sending - SendSocket = SocketSubsystem->CreateSocket(NAME_DGram, TEXT("FAppleARKitLiveLinkRemotePublisher socket"), true); + SendSocket = SocketSubsystem->CreateSocket(NAME_DGram, TEXT("FAppleARKitLiveLinkRemotePublisher socket"), Addr->GetProtocolType()); SendSocket->SetReuseAddr(); SendSocket->SetNonBlocking(); UE_LOG(LogAppleARKitFace, Log, TEXT("Sending LiveLink face AR data to address (%s)"), *Addr->ToString(true)); @@ -339,7 +339,7 @@ bool FAppleARKitLiveLinkRemoteListener::InitReceiveSocket() GConfig->GetInt(TEXT("/Script/AppleARKit.AppleARKitSettings"), TEXT("LiveLinkPublishingPort"), LiveLinkPort, GEngineIni); Addr->SetPort(LiveLinkPort); - RecvSocket = SocketSubsystem->CreateSocket(NAME_DGram, TEXT("FAppleARKitLiveLinkRemoteListener socket")); + RecvSocket = SocketSubsystem->CreateSocket(NAME_DGram, TEXT("FAppleARKitLiveLinkRemoteListener socket"), Addr->GetProtocolType()); if (RecvSocket != nullptr) { RecvSocket->SetReuseAddr(); @@ -512,7 +512,7 @@ FAppleARKitLiveLinkFileWriterCsv::FAppleARKitLiveLinkFileWriterCsv() check(IsInGameThread()); CsvFrameHeader = TEXT("Timecode, FrameRate"); - const UEnum *EnumPtr = FindObject(ANY_PACKAGE, TEXT("EARFaceBlendShape"), true); + const UEnum *EnumPtr = StaticEnum(); if (EnumPtr != nullptr) { // Iterate through all of the enum values generating strings for them for CSV/JSON generation @@ -560,7 +560,7 @@ FAppleARKitLiveLinkFileWriterJson::FAppleARKitLiveLinkFileWriterJson() // Touching UObjects, so needs to be game thread check(IsInGameThread()); - const UEnum *EnumPtr = FindObject(ANY_PACKAGE, TEXT("EARFaceBlendShape"), true); + const UEnum *EnumPtr = StaticEnum(); if (EnumPtr != nullptr) { // Iterate through all of the enum values generating strings for them for CSV/JSON generation diff --git a/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/SkeletalMeshComponentBudgeted.cpp b/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/SkeletalMeshComponentBudgeted.cpp index 8c87aacb5087..ded74ab05ba7 100644 --- a/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/SkeletalMeshComponentBudgeted.cpp +++ b/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/SkeletalMeshComponentBudgeted.cpp @@ -41,6 +41,30 @@ void USkeletalMeshComponentBudgeted::EndPlay(const EEndPlayReason::Type EndPlayR Super::EndPlay(EndPlayReason); } +void USkeletalMeshComponentBudgeted::SetComponentTickEnabled(bool bEnabled) +{ + if (AnimationBudgetAllocator) + { + AnimationBudgetAllocator->SetComponentTickEnabled(this, bEnabled); + } + else + { + Super::SetComponentTickEnabled(bEnabled); + } +} + +void USkeletalMeshComponentBudgeted::SetComponentSignificance(float Significance, bool bNeverSkip, bool bTickEvenIfNotRendered, bool bAllowReducedWork, bool bForceInterpolate) +{ + if (AnimationBudgetAllocator) + { + AnimationBudgetAllocator->SetComponentSignificance(this, Significance, bNeverSkip, bTickEvenIfNotRendered, bAllowReducedWork, bForceInterpolate); + } + else + { + UE_LOG(LogSkeletalMesh, Warning, TEXT("SetComponentSignificance called on [%s] before registering with budget allocator"), *GetName()); + } +} + void USkeletalMeshComponentBudgeted::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { #if !UE_BUILD_SHIPPING diff --git a/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Public/SkeletalMeshComponentBudgeted.h b/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Public/SkeletalMeshComponentBudgeted.h index 394a0fcd179f..2c5558da76a6 100644 --- a/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Public/SkeletalMeshComponentBudgeted.h +++ b/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Public/SkeletalMeshComponentBudgeted.h @@ -25,6 +25,12 @@ class ANIMATIONBUDGETALLOCATOR_API USkeletalMeshComponentBudgeted : public USkel public: USkeletalMeshComponentBudgeted(const FObjectInitializer& ObjectInitializer); + // UActorComponent interface + virtual void SetComponentTickEnabled(bool bEnabled) override; + + /** Updates significance budget if this component has been registered with a AnimationBudgetAllocator */ + void SetComponentSignificance(float Significance, bool bNeverSkip = false, bool bTickEvenIfNotRendered = false, bool bAllowReducedWork = true, bool bForceInterpolate = false); + /** Set this component to automatically register with the budget allocator */ UFUNCTION(BlueprintSetter) void SetAutoRegisterWithBudgetAllocator(bool bInAutoRegisterWithBudgetAllocator) { bAutoRegisterWithBudgetAllocator = bInAutoRegisterWithBudgetAllocator; } diff --git a/Engine/Plugins/Runtime/AudioCapture/Source/AudioCapture/Public/AudioCapture.h b/Engine/Plugins/Runtime/AudioCapture/Source/AudioCapture/Public/AudioCapture.h index 3871ef313f86..20c9e448dc8c 100644 --- a/Engine/Plugins/Runtime/AudioCapture/Source/AudioCapture/Public/AudioCapture.h +++ b/Engine/Plugins/Runtime/AudioCapture/Source/AudioCapture/Public/AudioCapture.h @@ -79,7 +79,7 @@ namespace Audio { public: FAudioCaptureSynth(); - virtual ~FAudioCaptureSynth(); + AUDIOCAPTURE_API virtual ~FAudioCaptureSynth(); // Gets the default capture device info bool GetDefaultCaptureDeviceInfo(FCaptureDeviceInfo& OutInfo); @@ -204,4 +204,4 @@ public: UFUNCTION(BlueprintCallable, Category = "Audio Capture") static class UAudioCapture* CreateAudioCapture(); -}; \ No newline at end of file +}; diff --git a/Engine/Plugins/Runtime/Database/RemoteDatabaseSupport/Source/RemoteDatabaseSupport/Private/RemoteDatabaseConnection.cpp b/Engine/Plugins/Runtime/Database/RemoteDatabaseSupport/Source/RemoteDatabaseSupport/Private/RemoteDatabaseConnection.cpp index 2dcc7e866a44..b1ff8447a950 100644 --- a/Engine/Plugins/Runtime/Database/RemoteDatabaseSupport/Source/RemoteDatabaseSupport/Private/RemoteDatabaseConnection.cpp +++ b/Engine/Plugins/Runtime/Database/RemoteDatabaseSupport/Source/RemoteDatabaseSupport/Private/RemoteDatabaseConnection.cpp @@ -35,16 +35,8 @@ bool ExecuteDBProxyCommand(FSocket *Socket, const FString& Cmd) * Constructor. */ FRemoteDatabaseConnection::FRemoteDatabaseConnection() -: Socket(NULL) +: Socket(nullptr) { - ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); - check(SocketSubsystem); - - // The socket won't work if secure connections are enabled, so don't try - if (SocketSubsystem->RequiresEncryptedPackets() == false) - { - Socket = SocketSubsystem->CreateSocket(NAME_Stream, TEXT("remote database connection")); - } } /** @@ -58,7 +50,7 @@ FRemoteDatabaseConnection::~FRemoteDatabaseConnection() if (Socket) { SocketSubsystem->DestroySocket(Socket); - Socket = NULL; + Socket = nullptr; } } @@ -74,23 +66,30 @@ FRemoteDatabaseConnection::~FRemoteDatabaseConnection() bool FRemoteDatabaseConnection::Open( const TCHAR* ConnectionString, const TCHAR* RemoteConnectionIP, const TCHAR* RemoteConnectionStringOverride ) { bool bIsValid = false; - if ( Socket ) + ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); + check(SocketSubsystem); + + TSharedRef Address = SocketSubsystem->CreateInternetAddr(); + Address->SetIp(RemoteConnectionIP, bIsValid); + Address->SetPort(10500); + + // The socket won't work if secure connections are enabled, so don't try + if (SocketSubsystem->RequiresEncryptedPackets() == false && Socket == nullptr) { - ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); - check(SocketSubsystem); + Socket = SocketSubsystem->CreateSocket(NAME_Stream, TEXT("remote database connection"), Address->GetProtocolType()); + } + else + { + bIsValid = false; + } - TSharedRef Address = SocketSubsystem->CreateInternetAddr(); - Address->SetIp(RemoteConnectionIP, bIsValid); - Address->SetPort(10500); + if (bIsValid && Socket) + { + bIsValid = Socket->Connect(*Address); - if(bIsValid) + if (bIsValid && RemoteConnectionStringOverride) { - bIsValid = Socket->Connect(*Address); - - if(bIsValid && RemoteConnectionStringOverride) - { - SetConnectionString(RemoteConnectionStringOverride); - } + SetConnectionString(RemoteConnectionStringOverride); } } return bIsValid; @@ -274,7 +273,7 @@ float FRemoteDataBaseRecordSet::GetFloat( const TCHAR* Column ) const } /** Constructor. */ -FRemoteDataBaseRecordSet::FRemoteDataBaseRecordSet(int32 ResultSetID, FSocket *Connection) : Socket(NULL) +FRemoteDataBaseRecordSet::FRemoteDataBaseRecordSet(int32 ResultSetID, FSocket *Connection) : Socket(nullptr) { check(ResultSetID >= 0); check(Connection); diff --git a/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableMesh.cpp b/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableMesh.cpp index 43ae9823bf0c..8a4d9f6e6c2b 100644 --- a/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableMesh.cpp +++ b/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableMesh.cpp @@ -427,7 +427,7 @@ static void InvertRemapTable( TSparseArray& InvertedRemapTable, const TSp } -class FCompactChange : public FChange +class FCompactChange : public FSwapChange { public: @@ -459,7 +459,7 @@ struct FUncompactChangeInput }; -class FUncompactChange : public FChange +class FUncompactChange : public FSwapChange { public: @@ -2621,7 +2621,8 @@ void UEditableMesh::SetSubdivisionCount( const int32 NewSubdivisionCount ) void UEditableMesh::MoveVertices( const TArray& VerticesToMove ) { - EM_ENTER( TEXT( "MoveVertices: %s" ), *LogHelpers::ArrayToString( VerticesToMove ) ); + //EM_ENTER( TEXT( "MoveVertices: %s" ), *LogHelpers::ArrayToString( VerticesToMove ) ); + EM_ENTER(TEXT("MoveVertices: [redacted]")); static TSet< FPolygonID > VertexConnectedPolygons; VertexConnectedPolygons.Reset(); @@ -4415,7 +4416,8 @@ void UEditableMesh::DeletePolygonGroups( const TArray& PolygonG void UEditableMesh::SetVerticesAttributes( const TArray& AttributesForVertices ) { - EM_ENTER( TEXT( "SetVerticesAttributes: %s" ), *LogHelpers::ArrayToString( AttributesForVertices ) ); + //EM_ENTER( TEXT( "SetVerticesAttributes: %s" ), *LogHelpers::ArrayToString( AttributesForVertices ) ); + EM_ENTER(TEXT("SetVerticesAttributes: [redacted]")); FSetVerticesAttributesChangeInput RevertInput; diff --git a/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableMeshChanges.h b/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableMeshChanges.h index 2cc55ff86c27..87a29c889992 100644 --- a/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableMeshChanges.h +++ b/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableMeshChanges.h @@ -22,7 +22,7 @@ struct FDeleteOrphanVerticesChangeInput }; -class FDeleteOrphanVerticesChange : public FChange +class FDeleteOrphanVerticesChange : public FSwapChange { public: @@ -67,7 +67,7 @@ struct FDeleteVertexInstancesChangeInput }; -class FDeleteVertexInstancesChange : public FChange +class FDeleteVertexInstancesChange : public FSwapChange { public: @@ -111,7 +111,7 @@ struct FDeleteEdgesChangeInput }; -class FDeleteEdgesChange : public FChange +class FDeleteEdgesChange : public FSwapChange { public: @@ -153,7 +153,7 @@ struct FCreateVerticesChangeInput }; -class FCreateVerticesChange : public FChange +class FCreateVerticesChange : public FSwapChange { public: @@ -195,7 +195,7 @@ struct FCreateVertexInstancesChangeInput }; -class FCreateVertexInstancesChange : public FChange +class FCreateVertexInstancesChange : public FSwapChange { public: @@ -237,7 +237,7 @@ struct FCreateEdgesChangeInput }; -class FCreateEdgesChange : public FChange +class FCreateEdgesChange : public FSwapChange { public: @@ -279,7 +279,7 @@ struct FCreatePolygonsChangeInput }; -class FCreatePolygonsChange : public FChange +class FCreatePolygonsChange : public FSwapChange { public: @@ -337,7 +337,7 @@ struct FDeletePolygonsChangeInput }; -class FDeletePolygonsChange : public FChange +class FDeletePolygonsChange : public FSwapChange { public: @@ -376,7 +376,7 @@ struct FFlipPolygonsChangeInput } }; -class FFlipPolygonsChange : public FChange +class FFlipPolygonsChange : public FSwapChange { public: FFlipPolygonsChange( const FFlipPolygonsChangeInput& InitInput ) @@ -400,7 +400,7 @@ struct FSetVerticesAttributesChangeInput }; -class FSetVerticesAttributesChange : public FChange +class FSetVerticesAttributesChange : public FSwapChange { public: @@ -435,7 +435,7 @@ struct FSetVertexInstancesAttributesChangeInput }; -class FSetVertexInstancesAttributesChange : public FChange +class FSetVertexInstancesAttributesChange : public FSwapChange { public: @@ -477,7 +477,7 @@ struct FSetEdgesAttributesChangeInput }; -class FSetEdgesAttributesChange : public FChange +class FSetEdgesAttributesChange : public FSwapChange { public: @@ -519,7 +519,7 @@ struct FSetPolygonsVertexAttributesChangeInput }; -class FSetPolygonsVertexAttributesChange : public FChange +class FSetPolygonsVertexAttributesChange : public FSwapChange { public: @@ -561,7 +561,7 @@ struct FChangePolygonsVertexInstancesChangeInput }; -class FChangePolygonsVertexInstancesChange : public FChange +class FChangePolygonsVertexInstancesChange : public FSwapChange { public: @@ -603,7 +603,7 @@ struct FSetEdgesVerticesChangeInput }; -class FSetEdgesVerticesChange : public FChange +class FSetEdgesVerticesChange : public FSwapChange { public: @@ -654,7 +654,7 @@ struct FInsertPolygonPerimeterVerticesChangeInput }; -class FInsertPolygonPerimeterVerticesChange : public FChange +class FInsertPolygonPerimeterVerticesChange : public FSwapChange { public: @@ -709,7 +709,7 @@ struct FRemovePolygonPerimeterVerticesChangeInput }; -class FRemovePolygonPerimeterVerticesChange : public FChange +class FRemovePolygonPerimeterVerticesChange : public FSwapChange { public: @@ -755,7 +755,7 @@ struct FStartOrEndModificationChangeInput }; -class FStartOrEndModificationChange : public FChange +class FStartOrEndModificationChange : public FSwapChange { public: @@ -792,7 +792,7 @@ struct FSetSubdivisionCountChangeInput }; -class FSetSubdivisionCountChange : public FChange +class FSetSubdivisionCountChange : public FSwapChange { public: @@ -829,7 +829,7 @@ struct FCreatePolygonGroupsChangeInput }; -class FCreatePolygonGroupsChange : public FChange +class FCreatePolygonGroupsChange : public FSwapChange { public: @@ -870,7 +870,7 @@ struct FDeletePolygonGroupsChangeInput }; -class FDeletePolygonGroupsChange : public FChange +class FDeletePolygonGroupsChange : public FSwapChange { public: @@ -915,7 +915,7 @@ struct FAssignPolygonsToPolygonGroupChangeInput }; -class FAssignPolygonsToPolygonGroupChange : public FChange +class FAssignPolygonsToPolygonGroupChange : public FSwapChange { public: diff --git a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/Abilities/Tasks/AbilityTask_WaitTargetData.cpp b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/Abilities/Tasks/AbilityTask_WaitTargetData.cpp index 4f2a54fc9737..2d3efbfe0cc2 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/Abilities/Tasks/AbilityTask_WaitTargetData.cpp +++ b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/Abilities/Tasks/AbilityTask_WaitTargetData.cpp @@ -139,8 +139,8 @@ void UAbilityTask_WaitTargetData::InitializeTargetActor(AGameplayAbilityTargetAc SpawnedActor->MasterPC = Ability->GetCurrentActorInfo()->PlayerController.Get(); // If we spawned the target actor, always register the callbacks for when the data is ready. - SpawnedActor->TargetDataReadyDelegate.AddUObject(this, &UAbilityTask_WaitTargetData::OnTargetDataReadyCallback); - SpawnedActor->CanceledDelegate.AddUObject(this, &UAbilityTask_WaitTargetData::OnTargetDataCancelledCallback); + SpawnedActor->TargetDataReadyDelegate.AddUObject(const_cast(this), &UAbilityTask_WaitTargetData::OnTargetDataReadyCallback); + SpawnedActor->CanceledDelegate.AddUObject(const_cast(this), &UAbilityTask_WaitTargetData::OnTargetDataCancelledCallback); } void UAbilityTask_WaitTargetData::FinalizeTargetActor(AGameplayAbilityTargetActor* SpawnedActor) const diff --git a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent.cpp b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent.cpp index 63196a5a1015..a3b66284f7ac 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent.cpp +++ b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent.cpp @@ -1987,11 +1987,6 @@ FAutoConsoleCommandWithWorld AbilitySystemPrevDebugTargetCmd( FConsoleCommandWithWorldDelegate::CreateStatic(AbilitySystemCycleDebugTarget, false) ); -static void AbilitySystemDebugNextCategory(UWorld* InWorld, bool Next) -{ - CycleDebugTarget( GetDebugTargetInfo(InWorld), Next ); -} - FAutoConsoleCommandWithWorld AbilitySystemDebugNextCategoryCmd( TEXT("AbilitySystem.Debug.NextCategory"), TEXT("Targets previous AbilitySystemComponent in ShowDebug AbilitySystem"), diff --git a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayAbilityTargetTypes.cpp b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayAbilityTargetTypes.cpp index 9c110d8ca634..86311b9d4673 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayAbilityTargetTypes.cpp +++ b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayAbilityTargetTypes.cpp @@ -169,7 +169,7 @@ bool FGameplayAbilityTargetDataHandle::NetSerialize(FArchive& Ar, class UPackage // only reallocating when necessary check(!Data[i].IsValid()); - FGameplayAbilityTargetData * NewData = (FGameplayAbilityTargetData*)FMemory::Malloc(ScriptStruct->GetCppStructOps()->GetSize()); + FGameplayAbilityTargetData * NewData = (FGameplayAbilityTargetData*)FMemory::Malloc(ScriptStruct->GetStructureSize()); ScriptStruct->InitializeStruct(NewData); Data[i] = TSharedPtr(NewData); diff --git a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayEffect.cpp b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayEffect.cpp index 82a0d9db12ff..e9772a63dce3 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayEffect.cpp +++ b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayEffect.cpp @@ -2875,7 +2875,7 @@ FActiveGameplayEffect* FActiveGameplayEffectsContainer::ApplyGameplayEffectSpec( const bool HasModifiedAttributes = AppliedEffectSpec.ModifiedAttributes.Num() > 0; const bool HasDurationAndNoPeriod = AppliedEffectSpec.Def->DurationPolicy == EGameplayEffectDurationType::HasDuration && AppliedEffectSpec.GetPeriod() == UGameplayEffect::NO_PERIOD; const bool HasPeriodAndNoDuration = AppliedEffectSpec.Def->DurationPolicy == EGameplayEffectDurationType::Instant && - AppliedEffectSpec.GetPeriod() != UGameplayEffect::NO_PERIOD; + AppliedEffectSpec.GetPeriod() > UGameplayEffect::NO_PERIOD; const bool ShouldBuildModifiedAttributeList = !HasModifiedAttributes && (HasDurationAndNoPeriod || HasPeriodAndNoDuration); if (ShouldBuildModifiedAttributeList) { @@ -2954,7 +2954,7 @@ FActiveGameplayEffect* FActiveGameplayEffectsContainer::ApplyGameplayEffectSpec( } // Register period callbacks with the timer manager - if (bSetPeriod && Owner && (AppliedEffectSpec.GetPeriod() != UGameplayEffect::NO_PERIOD)) + if (bSetPeriod && Owner && (AppliedEffectSpec.GetPeriod() > UGameplayEffect::NO_PERIOD)) { FTimerManager& TimerManager = Owner->GetWorld()->GetTimerManager(); FTimerDelegate Delegate = FTimerDelegate::CreateUObject(Owner, &UAbilitySystemComponent::ExecutePeriodicEffect, AppliedActiveGE->Handle); diff --git a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/AbilitySystemTestAttributeSet.h b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/AbilitySystemTestAttributeSet.h index 06af4d8bb64d..65e674a98915 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/AbilitySystemTestAttributeSet.h +++ b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/AbilitySystemTestAttributeSet.h @@ -34,7 +34,7 @@ class GAMEPLAYABILITIES_API UAbilitySystemTestAttributeSet : public UAttributeSe mutable float MaxMana; /** This Damage is just used for applying negative health mods. Its not a 'persistent' attribute. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AttributeTest", meta = (HideFromLevelInfos)) // You can't make a GameplayEffect 'powered' by Damage (Its transient) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AttributeTest") // You can't make a GameplayEffect 'powered' by Damage (Its transient) mutable float Damage; /** This Attribute is the actual spell damage for this actor. It will power spell based GameplayEffects */ diff --git a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/ActiveGameplayEffectIterator.h b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/ActiveGameplayEffectIterator.h index 0d065c188804..756e057bd79b 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/ActiveGameplayEffectIterator.h +++ b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/ActiveGameplayEffectIterator.h @@ -48,23 +48,33 @@ public: Next(); } - ElementType& operator*() + ElementType& operator*() const { check(Current); return *Current; } - ElementType& operator->() + ElementType& operator->() const { check(Current); return *Current; } - operator bool() + explicit operator bool() const { return (Current != nullptr); } + friend bool operator==(const FActiveGameplayEffectIterator& Lhs, const FActiveGameplayEffectIterator& Rhs) + { + return Lhs.Current == Rhs.Current; + } + + friend bool operator!=(const FActiveGameplayEffectIterator& Lhs, const FActiveGameplayEffectIterator& Rhs) + { + return Lhs.Current != Rhs.Current; + } + private: FORCEINLINE ElementType* AdvancePending(ElementType** Next) diff --git a/Engine/Plugins/Runtime/GoogleVR/GoogleVRController/GoogleVRController.uplugin b/Engine/Plugins/Runtime/GoogleVR/GoogleVRController/GoogleVRController.uplugin index 0da68e6c1146..79bd9a97fcc6 100644 --- a/Engine/Plugins/Runtime/GoogleVR/GoogleVRController/GoogleVRController.uplugin +++ b/Engine/Plugins/Runtime/GoogleVR/GoogleVRController/GoogleVRController.uplugin @@ -20,7 +20,7 @@ "Name": "GoogleVRController", "Type": "Runtime", "LoadingPhase": "PostConfigInit", - "WhitelistPlatforms": [ "Android", "Mac", "Win32", "Win64", "Linux" ] + "WhitelistPlatforms": [ "Android", "Mac", "Win32", "Win64" ] } ], "Plugins": [ diff --git a/Engine/Plugins/Runtime/GoogleVR/GoogleVRHMD/GoogleVRHMD.uplugin b/Engine/Plugins/Runtime/GoogleVR/GoogleVRHMD/GoogleVRHMD.uplugin index 3e6294f0a9ac..0e45e3a8eaee 100644 --- a/Engine/Plugins/Runtime/GoogleVR/GoogleVRHMD/GoogleVRHMD.uplugin +++ b/Engine/Plugins/Runtime/GoogleVR/GoogleVRHMD/GoogleVRHMD.uplugin @@ -20,7 +20,7 @@ "Name": "GoogleVRHMD", "Type": "Runtime", "LoadingPhase": "PostConfigInit", - "WhitelistPlatforms": [ "Win64", "Win32", "Mac", "Linux", "Android" ] + "WhitelistPlatforms": [ "Win64", "Win32", "Mac", "Android" ] } ] } diff --git a/Engine/Plugins/Runtime/Oculus/OculusVR/Source/OculusInput/Private/OculusInput.cpp b/Engine/Plugins/Runtime/Oculus/OculusVR/Source/OculusInput/Private/OculusInput.cpp index 9e6fee29b04f..5c2900f93069 100644 --- a/Engine/Plugins/Runtime/Oculus/OculusVR/Source/OculusInput/Private/OculusInput.cpp +++ b/Engine/Plugins/Runtime/Oculus/OculusVR/Source/OculusInput/Private/OculusInput.cpp @@ -6,6 +6,7 @@ #include "OculusHMD.h" #include "Misc/CoreDelegates.h" #include "Features/IModularFeatures.h" +#include "Misc/ConfigCacheIni.h" #define OVR_DEBUG_LOGGING 0 @@ -78,6 +79,9 @@ float FOculusInput::TriggerThreshold = 0.8f; /** Are Remote keys mapped to gamepad or not. */ bool FOculusInput::bRemoteKeysMappedToGamepad = true; +float FOculusInput::InitialButtonRepeatDelay = 0.2f; +float FOculusInput::ButtonRepeatDelay = 0.1f; + FOculusInput::FOculusInput( const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler ) : OVRPluginHandle(nullptr) , MessageHandler( InMessageHandler ) @@ -169,6 +173,9 @@ void FOculusInput::LoadConfig() { bRemoteKeysMappedToGamepad = b; } + + GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("InitialButtonRepeatDelay"), InitialButtonRepeatDelay, GInputIni); + GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("ButtonRepeatDelay"), ButtonRepeatDelay, GInputIni); } void FOculusInput::Tick( float DeltaTime ) @@ -180,10 +187,6 @@ void FOculusInput::Tick( float DeltaTime ) void FOculusInput::SendControllerEvents() { const double CurrentTime = FPlatformTime::Seconds(); - - // @todo: Should be made configurable and unified with other controllers handling of repeat - const float InitialButtonRepeatDelay = 0.2f; - const float ButtonRepeatDelay = 0.1f; const float AnalogButtonPressThreshold = TriggerThreshold; if(IOculusHMDModule::IsAvailable() && ovrp_GetInitialized() && FApp::HasVRFocus()) diff --git a/Engine/Plugins/Runtime/Oculus/OculusVR/Source/OculusInput/Private/OculusInput.h b/Engine/Plugins/Runtime/Oculus/OculusVR/Source/OculusInput/Private/OculusInput.h index b17124de8748..087e7a43c727 100644 --- a/Engine/Plugins/Runtime/Oculus/OculusVR/Source/OculusInput/Private/OculusInput.h +++ b/Engine/Plugins/Runtime/Oculus/OculusVR/Source/OculusInput/Private/OculusInput.h @@ -95,6 +95,10 @@ private: /** Are Remote keys mapped to gamepad or not. */ static bool bRemoteKeysMappedToGamepad; + /** Repeat key delays, loaded from config */ + static float InitialButtonRepeatDelay; + static float ButtonRepeatDelay; + ovrpHapticsDesc OvrpHapticsDesc; }; diff --git a/Engine/Plugins/Runtime/PacketHandlers/AESGCMHandlerComponent/AESGCMHandlerComponent.uplugin b/Engine/Plugins/Runtime/PacketHandlers/AESGCMHandlerComponent/AESGCMHandlerComponent.uplugin new file mode 100644 index 000000000000..fa5ba1233d9e --- /dev/null +++ b/Engine/Plugins/Runtime/PacketHandlers/AESGCMHandlerComponent/AESGCMHandlerComponent.uplugin @@ -0,0 +1,56 @@ +{ + "FileVersion" : 3, + "Version" : 1, + "VersionName" : "1.0", + "FriendlyName" : "AES GCM network packet handler", + "Description" : "Provides a packet handler component to do AES GCM encryption and decryption.", + "Category" : "Misc", + "CreatedBy" : "Epic Games, Inc.", + "CreatedByURL" : "http://epicgames.com", + "DocsURL" : "", + "MarketplaceURL" : "", + "SupportURL" : "", + "EnabledByDefault" : false, + "CanContainContent" : false, + "IsBetaVersion" : true, + "Installed" : false, + "Modules" : + [ + { + "Name" : "AESGCMHandlerComponent", + "Type" : "Runtime", + "LoadingPhase" : "Default", + "WhitelistPlatforms": + [ + "Android", + "IOS", + "Mac", + "Win32", + "Win64", + "Linux", + "PS4", + "XboxOne", + "Switch" + ] + } + ], + "Plugins": + [ + { + "Name": "PlatformCrypto", + "Enabled": true, + "WhitelistPlatforms": + [ + "Android", + "IOS", + "Mac", + "Win32", + "Win64", + "Linux", + "PS4", + "XboxOne", + "Switch" + ] + } + ] +} \ No newline at end of file diff --git a/Engine/Plugins/Runtime/PacketHandlers/AESGCMHandlerComponent/Source/AESGCMHandlerComponent.Build.cs b/Engine/Plugins/Runtime/PacketHandlers/AESGCMHandlerComponent/Source/AESGCMHandlerComponent.Build.cs new file mode 100644 index 000000000000..b4e791c739d5 --- /dev/null +++ b/Engine/Plugins/Runtime/PacketHandlers/AESGCMHandlerComponent/Source/AESGCMHandlerComponent.Build.cs @@ -0,0 +1,59 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.IO; + +public class AESGCMHandlerComponent : ModuleRules +{ + public AESGCMHandlerComponent(ReadOnlyTargetRules Target) : base(Target) + { + PrivateIncludePaths.AddRange( + new string[] { + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "PacketHandler", + "PlatformCrypto", + } + ); + + PublicIncludePathModuleNames.AddRange( + new string[] + { + "PlatformCrypto" + } + ); + + if (Target.Platform == UnrealTargetPlatform.XboxOne) + { + PublicDependencyModuleNames.AddRange( + new string[] + { + "PlatformCryptoBCrypt", + } + ); + } + else if (Target.Platform == UnrealTargetPlatform.Switch) + { + PublicDependencyModuleNames.AddRange( + new string[] + { + "PlatformCryptoSwitch", + } + ); + } + else + { + PublicDependencyModuleNames.AddRange( + new string[] + { + "PlatformCryptoOpenSSL", + } + ); + } + } +} \ No newline at end of file diff --git a/Engine/Plugins/Runtime/PacketHandlers/AESGCMHandlerComponent/Source/Private/AESGCMHandlerComponent.cpp b/Engine/Plugins/Runtime/PacketHandlers/AESGCMHandlerComponent/Source/Private/AESGCMHandlerComponent.cpp new file mode 100644 index 000000000000..cfdc6a4cb9a1 --- /dev/null +++ b/Engine/Plugins/Runtime/PacketHandlers/AESGCMHandlerComponent/Source/Private/AESGCMHandlerComponent.cpp @@ -0,0 +1,240 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "AESGCMHandlerComponent.h" + +IMPLEMENT_MODULE( FAESGCMHandlerComponentModule, AESGCMHandlerComponent ) + +TSharedPtr FAESGCMHandlerComponentModule::CreateComponentInstance(FString& Options) +{ + return MakeShared(); +} + + +FAESGCMHandlerComponent::FAESGCMHandlerComponent() + : FEncryptionComponent(FName(TEXT("AESGCMHandlerComponent"))) + , bEncryptionEnabled(false) +{ + EncryptionContext = IPlatformCrypto::Get().CreateContext(); +} + +void FAESGCMHandlerComponent::SetEncryptionKey(TArrayView NewKey) +{ + if (NewKey.Num() != KeySizeInBytes) + { + UE_LOG(PacketHandlerLog, Log, TEXT("FAESGCMHandlerComponent::SetEncryptionKey. NewKey is not %d bytes long, ignoring."), KeySizeInBytes); + return; + } + + Key.Reset(KeySizeInBytes); + Key.Append(NewKey.GetData(), NewKey.Num()); +} + +void FAESGCMHandlerComponent::EnableEncryption() +{ + bEncryptionEnabled = true; +} + +void FAESGCMHandlerComponent::DisableEncryption() +{ + bEncryptionEnabled = false; +} + +bool FAESGCMHandlerComponent::IsEncryptionEnabled() const +{ + return bEncryptionEnabled; +} + +void FAESGCMHandlerComponent::Initialize() +{ + SetActive(true); + SetState(Handler::Component::State::Initialized); + Initialized(); +} + +bool FAESGCMHandlerComponent::IsValid() const +{ + return true; +} + +void FAESGCMHandlerComponent::Incoming(FBitReader& Packet) +{ + DECLARE_SCOPE_CYCLE_COUNTER(TEXT("PacketHandler AESGCM Decrypt"), STAT_PacketHandler_AESGCM_Decrypt, STATGROUP_Net); + + // Handle this packet + if (IsValid() && Packet.GetNumBytes() > 0) + { + // Check first bit to see whether payload is encrypted + if (Packet.ReadBit() != 0) + { + // If the key hasn't been set yet, we can't decrypt, so ignore this packet. We don't set an error in this case because it may just be an out-of-order packet. + if (Key.Num() == 0) + { + UE_LOG(PacketHandlerLog, Log, TEXT("FAESGCMHandlerComponent::Incoming: received encrypted packet before key was set, ignoring.")); + Packet.SetData(nullptr, 0); + return; + } + + IV.Reset(); + IV.AddUninitialized(IVSizeInBytes); + Packet.SerializeBits(IV.GetData(), IV.Num() * 8); + + AuthTag.Reset(); + AuthTag.AddUninitialized(AuthTagSizeInBytes); + Packet.SerializeBits(AuthTag.GetData(), AuthTag.Num() * 8); + + // Copy remaining bits to a TArray so that they are byte-aligned. + Ciphertext.Reset(); + Ciphertext.AddUninitialized(Packet.GetBytesLeft()); + Ciphertext[Ciphertext.Num() - 1] = 0; + + Packet.SerializeBits(Ciphertext.GetData(), Packet.GetBitsLeft()); + + UE_LOG(PacketHandlerLog, VeryVerbose, TEXT("AESGCM packet handler received %ld bytes before decryption."), Ciphertext.Num()); + + EPlatformCryptoResult DecryptResult = EPlatformCryptoResult::Failure; + TArray Plaintext = EncryptionContext->Decrypt_AES_256_GCM(Ciphertext, Key, IV, AuthTag, DecryptResult); + + if (DecryptResult == EPlatformCryptoResult::Failure) + { + UE_LOG(PacketHandlerLog, Log, TEXT("FAESGCMHandlerComponent::Incoming: failed to decrypt packet.")); + Packet.SetError(); + return; + } + + if (Plaintext.Num() == 0) + { + Packet.SetData(nullptr, 0); + return; + } + + // Look for the termination bit that was written in Outgoing() to determine the exact bit size. + uint8 LastByte = Plaintext.Last(); + + if (LastByte != 0) + { + int32 BitSize = (Plaintext.Num() * 8) - 1; + + // Bit streaming, starts at the Least Significant Bit, and ends at the MSB. + while (!(LastByte & 0x80)) + { + LastByte *= 2; + BitSize--; + } + + UE_LOG(PacketHandlerLog, VeryVerbose, TEXT(" Have %d bits after decryption."), BitSize); + + Packet.SetData(MoveTemp(Plaintext), BitSize); + } + else + { + UE_LOG(PacketHandlerLog, Log, TEXT("FAESGCMHandlerComponent::Incoming: malformed packet, last byte was 0.")); + Packet.SetError(); + } + } + } +} + +void FAESGCMHandlerComponent::Outgoing(FBitWriter& Packet, FOutPacketTraits& Traits) +{ + DECLARE_SCOPE_CYCLE_COUNTER(TEXT("PacketHandler AESGCM Encrypt"), STAT_PacketHandler_AESGCM_Encrypt, STATGROUP_Net); + + // Handle this packet + if (IsValid() && Packet.GetNumBytes() > 0) + { + // Allow for encryption enabled bit and termination bit. Allow resizing to account for encryption padding. + FBitWriter NewPacket(Packet.GetNumBits() + 2, true); + NewPacket.WriteBit(bEncryptionEnabled ? 1 : 0); + + if (NewPacket.IsError()) + { + UE_LOG(PacketHandlerLog, Log, TEXT("FAESGCMHandlerComponent::Outgoing: failed to write encryption bit.")); + Packet.SetError(); + return; + } + + if (bEncryptionEnabled) + { + UE_LOG(PacketHandlerLog, VeryVerbose, TEXT("AESGCM packet handler sending %ld bits before encryption."), Packet.GetNumBits()); + + // Write a termination bit so that the receiving side can calculate the exact number of bits sent. + // Same technique used in UNetConnection. + Packet.WriteBit(1); + + if (Packet.IsError()) + { + UE_LOG(PacketHandlerLog, Log, TEXT("FAESGCMHandlerComponent::Outgoing: failed to write termination bit.")); + return; + } + + EPlatformCryptoResult RandResult = EPlatformCryptoResult::Failure; + TArray OutIV = EncryptionContext->GetRandomBytes(IVSizeInBytes, RandResult); + if (RandResult == EPlatformCryptoResult::Failure) + { + UE_LOG(PacketHandlerLog, Log, TEXT("FAESGCMHandlerComponent::Outgoing: failed to generate IV.")); + Packet.SetError(); + return; + } + + TArray OutAuthTag; + + EPlatformCryptoResult EncryptResult = EPlatformCryptoResult::Failure; + TArray OutCiphertext = EncryptionContext->Encrypt_AES_256_GCM(TArrayView(Packet.GetData(), Packet.GetNumBytes()), Key, OutIV, OutAuthTag, EncryptResult); + + if (EncryptResult == EPlatformCryptoResult::Failure) + { + UE_LOG(PacketHandlerLog, Log, TEXT("FAESGCMHandlerComponent::Outgoing: failed to encrypt packet.")); + Packet.SetError(); + return; + } + else + { + NewPacket.Serialize(OutIV.GetData(), OutIV.Num()); + NewPacket.Serialize(OutAuthTag.GetData(), OutAuthTag.Num()); + NewPacket.Serialize(OutCiphertext.GetData(), OutCiphertext.Num()); + + if (NewPacket.IsError()) + { + UE_LOG(PacketHandlerLog, Log, TEXT("FAESGCMHandlerComponent::Outgoing: failed to write ciphertext to packet.")); + Packet.SetError(); + return; + } + + UE_LOG(PacketHandlerLog, VeryVerbose, TEXT(" AESGCM packet handler sending %d bytes after encryption."), NewPacket.GetNumBytes()); + } + } + else + { + NewPacket.SerializeBits(Packet.GetData(), Packet.GetNumBits()); + } + + Packet = MoveTemp(NewPacket); + } +} + +int32 FAESGCMHandlerComponent::GetReservedPacketBits() const +{ + // Worst case includes the encryption enabled bit, the termination bit, padding up to the next whole byte, and a block of padding. + // Now also includes initialization vector and auth tag + return 2 + 7 + ((BlockSizeInBytes + IVSizeInBytes + AuthTagSizeInBytes) * 8); +} + +void FAESGCMHandlerComponent::CountBytes(FArchive& Ar) const +{ + FEncryptionComponent::CountBytes(Ar); + + const SIZE_T SizeOfThis = sizeof(*this) - sizeof(FEncryptionComponent); + Ar.CountBytes(SizeOfThis, SizeOfThis); + + /* + Note, as of now, EncryptionContext is just typedef'd, but none of the base + types actually allocated memory directly in their classes (although there may be + global state). + if (FEncryptionContext const * const LocalContext = EncrpytionContext.Get()) + { + LocalContext->CountBytes(Ar); + } + */ + + Key.CountBytes(Ar); + Ciphertext.CountBytes(Ar); +} diff --git a/Engine/Plugins/Runtime/PacketHandlers/AESGCMHandlerComponent/Source/Public/AESGCMHandlerComponent.h b/Engine/Plugins/Runtime/PacketHandlers/AESGCMHandlerComponent/Source/Public/AESGCMHandlerComponent.h new file mode 100644 index 000000000000..faca3d808ae5 --- /dev/null +++ b/Engine/Plugins/Runtime/PacketHandlers/AESGCMHandlerComponent/Source/Public/AESGCMHandlerComponent.h @@ -0,0 +1,76 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "PacketHandler.h" +#include "IPlatformCrypto.h" +#include "EncryptionComponent.h" + +/* +* AES256 GCM block encryption component. +*/ +class AESGCMHANDLERCOMPONENT_API FAESGCMHandlerComponent : public FEncryptionComponent +{ +public: + /** + * Default constructor that leaves the Key empty, and encryption disabled. + * You must set the key before enabling encryption, or before receiving encrypted + * packets, or those operations will fail. + */ + FAESGCMHandlerComponent(); + + // This handler uses AES256, which has 32-byte keys. + static const int32 KeySizeInBytes = 32; + + // This handler uses AES256, which has 32-byte keys. + static const int32 BlockSizeInBytes = 16; + + static const int32 IVSizeInBytes = 12; + static const int32 AuthTagSizeInBytes = 16; + + // Replace the key used for encryption with NewKey if NewKey is exactly KeySizeInBytes long. + virtual void SetEncryptionKey(TArrayView NewKey) override; + + // After calling this, future outgoing packets will be encrypted (until a call to DisableEncryption). + virtual void EnableEncryption() override; + + // After calling this, future outgoing packets will not be encrypted (until a call to DisableEncryption). + virtual void DisableEncryption() override; + + // Returns true if encryption is currently enabled. + virtual bool IsEncryptionEnabled() const override; + + // HandlerComponent interface + virtual void Initialize() override; + virtual bool IsValid() const override; + virtual void Incoming(FBitReader& Packet) override; + virtual void Outgoing(FBitWriter& Packet, FOutPacketTraits& Traits) override; + virtual void IncomingConnectionless(const TSharedPtr& Address, FBitReader& Packet) override {} + virtual void OutgoingConnectionless(const TSharedPtr& Address, FBitWriter& Packet, FOutPacketTraits& Traits) override {} + virtual int32 GetReservedPacketBits() const override; + virtual void CountBytes(FArchive& Ar) const override; + +private: + TUniquePtr EncryptionContext; + + TArray Key; + + // Avoid per packet allocations + TArray Ciphertext; + TArray IV; + TArray AuthTag; + + bool bEncryptionEnabled; +}; + + +/** + * The public interface to this module. + */ +class FAESGCMHandlerComponentModule : public FPacketHandlerComponentModuleInterface +{ +public: + /* Creates an instance of this component */ + virtual TSharedPtr CreateComponentInstance(FString& Options) override; +}; diff --git a/Engine/Plugins/Runtime/PacketHandlers/AESHandlerComponent/Source/Private/AESHandlerComponent.cpp b/Engine/Plugins/Runtime/PacketHandlers/AESHandlerComponent/Source/Private/AESHandlerComponent.cpp index f7a562617447..a684d46c5453 100644 --- a/Engine/Plugins/Runtime/PacketHandlers/AESHandlerComponent/Source/Private/AESHandlerComponent.cpp +++ b/Engine/Plugins/Runtime/PacketHandlers/AESHandlerComponent/Source/Private/AESHandlerComponent.cpp @@ -81,7 +81,7 @@ void FAESHandlerComponent::Incoming(FBitReader& Packet) // Copy remaining bits to a TArray so that they are byte-aligned. Ciphertext.Reset(); Ciphertext.AddUninitialized(Packet.GetBytesLeft()); - Ciphertext[Ciphertext.Num()-1] = 0; + Ciphertext[Ciphertext.Num() - 1] = 0; Packet.SerializeBits(Ciphertext.GetData(), Packet.GetBitsLeft()); @@ -194,14 +194,6 @@ void FAESHandlerComponent::Outgoing(FBitWriter& Packet, FOutPacketTraits& Traits } } -void FAESHandlerComponent::IncomingConnectionless(const FString& Address, FBitReader& Packet) -{ -} - -void FAESHandlerComponent::OutgoingConnectionless(const FString& Address, FBitWriter& Packet, FOutPacketTraits& Traits) -{ -} - int32 FAESHandlerComponent::GetReservedPacketBits() const { // Worst case includes the encryption enabled bit, the termination bit, padding up to the next whole byte, and a block of padding. diff --git a/Engine/Plugins/Runtime/PacketHandlers/AESHandlerComponent/Source/Public/AESHandlerComponent.h b/Engine/Plugins/Runtime/PacketHandlers/AESHandlerComponent/Source/Public/AESHandlerComponent.h index a958477d69e8..8a4d705017bf 100644 --- a/Engine/Plugins/Runtime/PacketHandlers/AESHandlerComponent/Source/Public/AESHandlerComponent.h +++ b/Engine/Plugins/Runtime/PacketHandlers/AESHandlerComponent/Source/Public/AESHandlerComponent.h @@ -44,8 +44,8 @@ public: virtual bool IsValid() const override; virtual void Incoming(FBitReader& Packet) override; virtual void Outgoing(FBitWriter& Packet, FOutPacketTraits& Traits) override; - virtual void IncomingConnectionless(const FString& Address, FBitReader& Packet) override; - virtual void OutgoingConnectionless(const FString& Address, FBitWriter& Packet, FOutPacketTraits& Traits) override; + virtual void IncomingConnectionless(const TSharedPtr& Address, FBitReader& Packet) override {} + virtual void OutgoingConnectionless(const TSharedPtr& Address, FBitWriter& Packet, FOutPacketTraits& Traits) override {} virtual int32 GetReservedPacketBits() const override; virtual void CountBytes(FArchive& Ar) const override; diff --git a/Engine/Plugins/Runtime/PacketHandlers/CompressionComponents/Oodle/Source/OodleHandlerComponent/Private/OodleHandlerComponent.cpp b/Engine/Plugins/Runtime/PacketHandlers/CompressionComponents/Oodle/Source/OodleHandlerComponent/Private/OodleHandlerComponent.cpp index 63fd0dd850ca..70860f9eb409 100644 --- a/Engine/Plugins/Runtime/PacketHandlers/CompressionComponents/Oodle/Source/OodleHandlerComponent/Private/OodleHandlerComponent.cpp +++ b/Engine/Plugins/Runtime/PacketHandlers/CompressionComponents/Oodle/Source/OodleHandlerComponent/Private/OodleHandlerComponent.cpp @@ -364,7 +364,7 @@ void OodleHandlerComponent::Initialize() } FString CurEnableModeStr; - UEnum* EnableModeEnum = FindObject(ANY_PACKAGE, TEXT("EOodleEnableMode"), true); + UEnum* EnableModeEnum = StaticEnum(); if (GConfig->GetString(OODLE_INI_SECTION, TEXT("ServerEnableMode"), CurEnableModeStr, GEngineIni)) { diff --git a/Engine/Plugins/Runtime/PacketHandlers/CompressionComponents/Oodle/Source/OodleHandlerComponent/Public/OodleHandlerComponent.h b/Engine/Plugins/Runtime/PacketHandlers/CompressionComponents/Oodle/Source/OodleHandlerComponent/Public/OodleHandlerComponent.h index dfdc29b1439e..4751a0dc7a46 100644 --- a/Engine/Plugins/Runtime/PacketHandlers/CompressionComponents/Oodle/Source/OodleHandlerComponent/Public/OodleHandlerComponent.h +++ b/Engine/Plugins/Runtime/PacketHandlers/CompressionComponents/Oodle/Source/OodleHandlerComponent/Public/OodleHandlerComponent.h @@ -362,11 +362,11 @@ public: virtual void Outgoing(FBitWriter& Packet, FOutPacketTraits& Traits) override; - virtual void IncomingConnectionless(const FString& Address, FBitReader& Packet) override + virtual void IncomingConnectionless(const TSharedPtr& Address, FBitReader& Packet) override { } - virtual void OutgoingConnectionless(const FString& Address, FBitWriter& Packet, FOutPacketTraits& Traits) override + virtual void OutgoingConnectionless(const TSharedPtr& Address, FBitWriter& Packet, FOutPacketTraits& Traits) override { } diff --git a/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraph.h b/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraph.h index d7fadc69216a..afe4f483b868 100644 --- a/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraph.h +++ b/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraph.h @@ -368,6 +368,7 @@ protected: virtual void GatherActors_DistanceOnly(const FActorRepListRefView& RepList, FGlobalActorReplicationInfoMap& GlobalMap, FPerConnectionActorInfoMap& ConnectionMap, const FConnectionGatherActorListParameters& Params); void CalcFrequencyForActor(AActor* Actor, UReplicationGraph* RepGraph, UNetConnection* NetConnection, FGlobalActorReplicationInfo& GlobalInfo, FConnectionReplicationActorInfo& ConnectionInfo, FSettings& MySettings, const FNetViewerArray& Viewers, const uint32 FrameNum, int32 ExistingItemIndex); + UE_DEPRECATED(4.23, "Use the other function to allow for multiple viewers") void CalcFrequencyForActor(AActor* Actor, UReplicationGraph* RepGraph, UNetConnection* NetConnection, FGlobalActorReplicationInfo& GlobalInfo, FConnectionReplicationActorInfo& ConnectionInfo, FSettings& MySettings, const FVector& ConnectionViewLocation, const FVector& ConnectionViewDir, const uint32 FrameNum, int32 ExistingItemIndex); }; @@ -712,9 +713,11 @@ public: UPROPERTY() TArray PastRelevantActors; + UE_DEPRECATED(4.23, "ViewTargets are now handled inside the PastRelevantActorMap") UPROPERTY() AActor* LastViewer = nullptr; + UE_DEPRECATED(4.23, "ViewTargets are now handled inside the PastRelevantActorMap") UPROPERTY() AActor* LastViewTarget = nullptr; }; @@ -948,10 +951,12 @@ protected: /** Default Replication Path */ void ReplicateActorListsForConnections_Default(UNetReplicationGraphConnection* ConnectionManager, FGatheredReplicationActorLists& GatheredReplicationListsForConnection, FNetViewerArray& Viewers); + UE_DEPRECATED(4.23, "Use the array format to support subconnections as well") void ReplicateActorListsForConnection_Default(UNetReplicationGraphConnection* ConnectionManager, FGatheredReplicationActorLists& GatheredReplicationListsForConnection, FNetViewer& Viewer); /** "FastShared" Replication Path */ void ReplicateActorListsForConnections_FastShared(UNetReplicationGraphConnection* ConnectionManager, FGatheredReplicationActorLists& GatheredReplicationListsForConnection, FNetViewerArray& Viewers); + UE_DEPRECATED(4.23, "Use the array format to support subconnections as well") void ReplicateActorListsForConnection_FastShared(UNetReplicationGraphConnection* ConnectionManager, FGatheredReplicationActorLists& GatheredReplicationListsForConnection, FNetViewer& Viewer); /** Connections needing a FlushNet in PostTickDispatch */ @@ -1039,6 +1044,7 @@ public: // ID that is assigned by the replication graph. Will be reassigned/compacted as clients disconnect. Useful for spacing out connection operations. E.g., not stable but always compact. int32 ConnectionId; + UE_DEPRECATED(4.23, "Use the LastGatherLocations to have support for subconnection lookups") FVector LastGatherLocation; UPROPERTY() diff --git a/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraphTypes.h b/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraphTypes.h index 269594bf99c9..8733b97561e2 100644 --- a/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraphTypes.h +++ b/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraphTypes.h @@ -31,7 +31,7 @@ DECLARE_LOG_CATEGORY_EXTERN( LogReplicationGraph, Log, All ); #define repCheck(x) check(x) #define repCheckf(expr, format, ...) checkf(expr, format, ##__VA_ARGS__ ) #define RG_QUICK_SCOPE_CYCLE_COUNTER(x) QUICK_SCOPE_CYCLE_COUNTER(x) - extern int32 CVar_RepGraph_Verify; + REPLICATIONGRAPH_API extern int32 CVar_RepGraph_Verify; #else #define REPGRAPH_DETAILS 0 #define DO_REPGRAPH_DETAILS(X) 0 @@ -1167,6 +1167,7 @@ struct FConnectionGatherActorListParameters /** In: The Data the nodes have to work with */ + UE_DEPRECATED(4.23, "Use the viewer arrays for support for subconnections") FNetViewer& Viewer; FNetViewerArray Viewers; @@ -1680,4 +1681,4 @@ struct FActorConnectionPair }; // Generic/global pair that can be set by debug commands etc for extra logging/debugging functionality -extern FActorConnectionPair DebugActorConnectionPair; \ No newline at end of file +extern FActorConnectionPair DebugActorConnectionPair; diff --git a/Engine/Plugins/Runtime/SignificanceManager/Source/SignificanceManager/Public/OrderedBudget.h b/Engine/Plugins/Runtime/SignificanceManager/Source/SignificanceManager/Public/OrderedBudget.h new file mode 100644 index 000000000000..cdd6c9f8c951 --- /dev/null +++ b/Engine/Plugins/Runtime/SignificanceManager/Source/SignificanceManager/Public/OrderedBudget.h @@ -0,0 +1,75 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +// Manages how to allocate a budget of levels to an ordered list of items (e.g., how to distribute minimum LOD levels to the N closest characters) +struct FOrderedBudget +{ +public: + FOrderedBudget() + : ValueForOutOfBounds(0) + { + } + + // Returns the budget number for the Index-th closest character + FORCEINLINE int32 GetBudgetForIndex(int32 Index) const + { + return (Index < BudgetValues.Num()) ? BudgetValues[Index] : ValueForOutOfBounds; + } + + // Returns the number of items specified in the budget + FORCEINLINE int32 GetBudgetLength() const + { + return BudgetValues.Num(); + } + + // Recreates the budget given a specification string. For example, the specification "0,2,3,5" would correspond to the following budget: + // Level0: 0 + // Level1: 2 + // Level2: 3 + // Level3: 5 + // Creating a table that contains: + // 1,1,2,2,2,3,3,3,3,3 (out of bounds = 4) + // Returns true if the budget was modified, or false if the existing budget already matched + bool RecreateBudget(const FString& Specification) + { + const bool bSpecificationDiffers = Specification != BudgetString; + + if (bSpecificationDiffers) + { + BudgetValues.Reset(); + BudgetString = Specification; + + TArray BudgetStrings; + BudgetString.ParseIntoArray(/*out*/ BudgetStrings, TEXT(","), /*bCullEmpty=*/ false); + + int32 LevelIndex = 0; + for (const FString& BudgetItem : BudgetStrings) + { + const int32 CountForThisLevel = FCString::Atoi(*BudgetItem); + check(CountForThisLevel >= 0); + + for (int32 ThisLevelIndex = 0; ThisLevelIndex < CountForThisLevel; ++ThisLevelIndex) + { + BudgetValues.Add(LevelIndex); + } + + ++LevelIndex; + } + + ValueForOutOfBounds = LevelIndex; + } + + return bSpecificationDiffers; + } + +private: + // This is the budget value for the i-th closest character (e.g., there will be be 10 entries if the budgets ranges sum to cover 10) + TArray BudgetValues; + + // This is the budget value for things with an index further away than BudgetByIndex.Num() + int32 ValueForOutOfBounds; + + // My budget string + FString BudgetString; +}; diff --git a/Engine/Plugins/Runtime/SoundMod/Source/SoundMod/Private/SoundMod.cpp b/Engine/Plugins/Runtime/SoundMod/Source/SoundMod/Private/SoundMod.cpp index af27a9c13475..f205ed5b71b1 100644 --- a/Engine/Plugins/Runtime/SoundMod/Source/SoundMod/Private/SoundMod.cpp +++ b/Engine/Plugins/Runtime/SoundMod/Source/SoundMod/Private/SoundMod.cpp @@ -22,12 +22,12 @@ void USoundMod::Parse(class FAudioDevice* AudioDevice, const UPTRINT NodeWaveIns FWaveInstance* WaveInstance = ActiveSound.FindWaveInstance(NodeWaveInstanceHash); // Create a new WaveInstance if this SoundWave doesn't already have one associated with it. - if (WaveInstance == NULL) + if (!WaveInstance) { const int32 SampleRate = 44100; // Create a new wave instance and associate with the ActiveSound - WaveInstance = new FWaveInstance(NodeWaveInstanceHash, ActiveSound); + WaveInstance = &ActiveSound.AddWaveInstance(NodeWaveInstanceHash); // Create streaming wave object USoundModWave* ModWave = NewObject(); @@ -36,7 +36,7 @@ void USoundMod::Parse(class FAudioDevice* AudioDevice, const UPTRINT NodeWaveIns ModWave->Duration = INDEFINITELY_LOOPING_DURATION; ModWave->bLooping = bLooping; - if (ResourceData == NULL) + if (!ResourceData) { RawData.GetCopy((void**)&ResourceData, true); } diff --git a/Engine/Plugins/Runtime/Steam/SteamAudio/Source/SteamAudio/Private/PhononProbeVolume.h b/Engine/Plugins/Runtime/Steam/SteamAudio/Source/SteamAudio/Private/PhononProbeVolume.h index de6aa39c64de..243a74e26f1c 100644 --- a/Engine/Plugins/Runtime/Steam/SteamAudio/Source/SteamAudio/Private/PhononProbeVolume.h +++ b/Engine/Plugins/Runtime/Steam/SteamAudio/Source/SteamAudio/Private/PhononProbeVolume.h @@ -38,14 +38,14 @@ struct FBakedDataInfo int32 Size; }; -inline bool operator==(const FBakedDataInfo& lhs, const FBakedDataInfo& rhs) +inline bool operator==(const FBakedDataInfo& Lhs, const FBakedDataInfo& Rhs) { - return lhs.Name == rhs.Name && lhs.Size == rhs.Size; + return Lhs.Name == Rhs.Name && Lhs.Size == Rhs.Size; } -inline bool operator<(const FBakedDataInfo& lhs, const FBakedDataInfo& rhs) +inline bool operator<(const FBakedDataInfo& Lhs, const FBakedDataInfo& Rhs) { - return lhs.Name < rhs.Name; + return Lhs.Name.LexicalLess(Rhs.Name); } /** diff --git a/Engine/Plugins/Runtime/Steam/SteamController/Source/SteamController/Private/SteamController.cpp b/Engine/Plugins/Runtime/Steam/SteamController/Source/SteamController/Private/SteamController.cpp index 23576b5f9084..4fa194c42b1f 100644 --- a/Engine/Plugins/Runtime/Steam/SteamController/Source/SteamController/Private/SteamController.cpp +++ b/Engine/Plugins/Runtime/Steam/SteamController/Source/SteamController/Private/SteamController.cpp @@ -4,6 +4,7 @@ #include "HAL/PlatformTime.h" #include "HAL/PlatformProcess.h" #include "Misc/Paths.h" +#include "Misc/ConfigCacheIni.h" #include "GenericPlatform/GenericApplicationMessageHandler.h" #include "Modules/ModuleManager.h" #include "GenericPlatform/IInputInterface.h" @@ -74,6 +75,9 @@ public: return; } + GConfig->GetDouble(TEXT("/Script/Engine.InputSettings"), TEXT("InitialButtonRepeatDelay"), InitialButtonRepeatDelay, GInputIni); + GConfig->GetDouble(TEXT("/Script/Engine.InputSettings"), TEXT("ButtonRepeatDelay"), ButtonRepeatDelay, GInputIni); + // Initialize the API, so we can start calling SteamController functions bSteamAPIInitialized = SteamAPI_Init(); diff --git a/Engine/Plugins/Runtime/Steam/SteamController/Source/SteamController/SteamController.Build.cs b/Engine/Plugins/Runtime/Steam/SteamController/Source/SteamController/SteamController.Build.cs index 606b44ca4036..b1d526113960 100644 --- a/Engine/Plugins/Runtime/Steam/SteamController/Source/SteamController/SteamController.Build.cs +++ b/Engine/Plugins/Runtime/Steam/SteamController/Source/SteamController/SteamController.Build.cs @@ -7,7 +7,7 @@ public class SteamController : ModuleRules { public SteamController(ReadOnlyTargetRules Target) : base(Target) { - string SteamVersion = "Steamv139"; + string SteamVersion = "Steamv142"; bool bSteamSDKFound = Directory.Exists(Target.UEThirdPartySourceDirectory + "Steamworks/" + SteamVersion) == true; PublicDefinitions.Add("STEAMSDK_FOUND=" + (bSteamSDKFound ? "1" : "0")); diff --git a/Engine/Plugins/Runtime/Steam/SteamVR/Source/SteamVRController/Private/SteamVRController.cpp b/Engine/Plugins/Runtime/Steam/SteamVR/Source/SteamVRController/Private/SteamVRController.cpp index a6f51b12a51d..3778566c01f7 100644 --- a/Engine/Plugins/Runtime/Steam/SteamVR/Source/SteamVRController/Private/SteamVRController.cpp +++ b/Engine/Plugins/Runtime/Steam/SteamVR/Source/SteamVRController/Private/SteamVRController.cpp @@ -21,6 +21,7 @@ #include "GameFramework/PlayerInput.h" #include "Misc/FileHelper.h" +#include "Misc/ConfigCacheIni.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" @@ -183,6 +184,9 @@ public: InitialButtonRepeatDelay = 0.2f; ButtonRepeatDelay = 0.1f; + GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("InitialButtonRepeatDelay"), InitialButtonRepeatDelay, GInputIni); + GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("ButtonRepeatDelay"), ButtonRepeatDelay, GInputIni); + InitControllerMappings(); InitLegacyControllerKeys(); BuildActionManifest(); diff --git a/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Private/SynthComponentMonoWaveTable.cpp b/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Private/SynthComponentMonoWaveTable.cpp index 6ddc35ac72e1..a31943213f33 100644 --- a/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Private/SynthComponentMonoWaveTable.cpp +++ b/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Private/SynthComponentMonoWaveTable.cpp @@ -1,4 +1,4 @@ -// Copyrigxht 1998-2019 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "SynthComponents/SynthComponentMonoWaveTable.h" diff --git a/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Private/UI/SSynth2DSlider.cpp b/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Private/UI/SSynth2DSlider.cpp index 67ac105791eb..07d578b16233 100644 --- a/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Private/UI/SSynth2DSlider.cpp +++ b/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Private/UI/SSynth2DSlider.cpp @@ -2,6 +2,7 @@ #include "UI/SSynth2DSlider.h" #include "Rendering/DrawElements.h" +#include "Framework/Application/SlateApplication.h" void SSynth2DSlider::Construct(const SSynth2DSlider::FArguments& InDeclaration) { @@ -125,7 +126,7 @@ FReply SSynth2DSlider::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& I // The controller's bottom face button must be pressed once to begin manipulating the slider's value. // Navigation away from the widget is prevented until the button has been pressed again or focus is lost. // The value can be manipulated by using the game pad's directional arrows ( relative to slider orientation ). - if (KeyPressed == EKeys::Enter || KeyPressed == EKeys::SpaceBar || KeyPressed == EKeys::Virtual_Accept) + if (FSlateApplication::Get().GetNavigationActionForKey(KeyPressed) == EUINavigationAction::Accept) { if (bControllerInputCaptured == false) { diff --git a/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Private/UI/SSynthKnob.cpp b/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Private/UI/SSynthKnob.cpp index e199c09a0ead..7f224ec2488e 100644 --- a/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Private/UI/SSynthKnob.cpp +++ b/Engine/Plugins/Runtime/Synthesis/Source/Synthesis/Private/UI/SSynthKnob.cpp @@ -2,6 +2,7 @@ #include "UI/SSynthKnob.h" #include "Rendering/DrawElements.h" +#include "Framework/Application/SlateApplication.h" #define LOCTEXT_NAMESPACE "SynthKnob" @@ -122,7 +123,7 @@ FReply SSynthKnob::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKey // The controller's bottom face button must be pressed once to begin manipulating the slider's value. // Navigation away from the widget is prevented until the button has been pressed again or focus is lost. // The value can be manipulated by using the game pad's directional arrows ( relative to slider orientation ). - if (KeyPressed == EKeys::Enter || KeyPressed == EKeys::SpaceBar || KeyPressed == EKeys::Virtual_Accept) + if (FSlateApplication::Get().GetNavigationActionForKey(KeyPressed) == EUINavigationAction::Accept) { if (bControllerInputCaptured == false) { diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterInput/Private/Blueprints/DisplayClusterInputBlueprintAPIImpl.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterInput/Private/Blueprints/DisplayClusterInputBlueprintAPIImpl.cpp index b3270cb062c4..ab4c0d19966a 100644 --- a/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterInput/Private/Blueprints/DisplayClusterInputBlueprintAPIImpl.cpp +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterInput/Private/Blueprints/DisplayClusterInputBlueprintAPIImpl.cpp @@ -64,14 +64,6 @@ bool UDisplayClusterInputBlueprintAPIImpl::BindVrpnTracker(const FString& VrpnDe { DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterInputBP); - const FString ObjName(TEXT("EControllerHand")); - const UEnum* const EnumPtr = FindObject(ANY_PACKAGE, *ObjName, true); - if (!EnumPtr) - { - UE_LOG(LogDisplayClusterInputBP, Error, TEXT("Couldn't find %s object"), *ObjName); - return false; - } - - FName TargetName = EnumPtr->GetNameByValue((int64)Target); - return IDisplayClusterInputModule::Get().BindVrpnChannel(VrpnDeviceId, VrpnChannel, TargetName.ToString()); + const FString TargetString = UEnum::GetValueAsString(Target); + return IDisplayClusterInputModule::Get().BindVrpnChannel(VrpnDeviceId, VrpnChannel, TargetString); } diff --git a/Engine/Plugins/Tests/RuntimeTests/Source/RuntimeTests/Private/Slate/RichTextMarkupProcessingTest.cpp b/Engine/Plugins/Tests/RuntimeTests/Source/RuntimeTests/Private/Slate/RichTextMarkupProcessingTest.cpp index 67a504a38179..e71f0eced14b 100644 --- a/Engine/Plugins/Tests/RuntimeTests/Source/RuntimeTests/Private/Slate/RichTextMarkupProcessingTest.cpp +++ b/Engine/Plugins/Tests/RuntimeTests/Source/RuntimeTests/Private/Slate/RichTextMarkupProcessingTest.cpp @@ -2,7 +2,6 @@ #include "CoreMinimal.h" #include "Misc/AutomationTest.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/ITextDecorator.h" #include "Framework/Text/RichTextMarkupProcessing.h" diff --git a/Engine/Source/Developer/AssetTools/Private/AssetRenameManager.cpp b/Engine/Source/Developer/AssetTools/Private/AssetRenameManager.cpp index 9790bc51efb1..ff81ea459fbc 100644 --- a/Engine/Source/Developer/AssetTools/Private/AssetRenameManager.cpp +++ b/Engine/Source/Developer/AssetTools/Private/AssetRenameManager.cpp @@ -1043,7 +1043,7 @@ bool FAssetRenameManager::CheckPackageForSoftObjectReferences(UPackage* Package, // Bind to dirty callback if we aren't already if (!DirtyDelegateHandle.IsValid()) { - DirtyDelegateHandle = UPackage::PackageMarkedDirtyEvent.AddSP(this, &FAssetRenameManager::OnMarkPackageDirty); + DirtyDelegateHandle = UPackage::PackageMarkedDirtyEvent.AddSP(const_cast(this), &FAssetRenameManager::OnMarkPackageDirty); } CachedReferences = &CachedSoftReferences.Add(Package->GetFName()); diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTools.h b/Engine/Source/Developer/AssetTools/Private/AssetTools.h index d32a1b2d875b..3153c3dce875 100644 --- a/Engine/Source/Developer/AssetTools/Private/AssetTools.h +++ b/Engine/Source/Developer/AssetTools/Private/AssetTools.h @@ -55,6 +55,9 @@ class UAssetToolsImpl : public UObject, public IAssetTools public: UAssetToolsImpl(const FObjectInitializer& ObjectInitializer); + // UObject implementation + virtual bool IsDestructionThreadSafe() const override { return false; } + // IAssetTools implementation virtual void RegisterAssetTypeActions(const TSharedRef& NewActions) override; virtual void UnregisterAssetTypeActions(const TSharedRef& ActionsToRemove) override; diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_DataTable.cpp b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_DataTable.cpp index f37bd1ccd64b..c26ce5091f97 100644 --- a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_DataTable.cpp +++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_DataTable.cpp @@ -91,7 +91,7 @@ void FAssetTypeActions_DataTable::ExecuteExportAsCSV(TArray< TWeakObjectPtr 0) { - FFileHelper::SaveStringToFile(DataTable->GetTableAsCSV(EDataTableExportFlags::UsePrettyPropertyNames | EDataTableExportFlags::UsePrettyEnumNames), *OutFilenames[0]); + FFileHelper::SaveStringToFile(DataTable->GetTableAsCSV(), *OutFilenames[0]); } } } @@ -125,7 +125,7 @@ void FAssetTypeActions_DataTable::ExecuteExportAsJSON(TArray< TWeakObjectPtr 0) { - FFileHelper::SaveStringToFile(DataTable->GetTableAsJSON(EDataTableExportFlags::UsePrettyPropertyNames | EDataTableExportFlags::UsePrettyEnumNames | EDataTableExportFlags::UseJsonObjectsForStructs), *OutFilenames[0]); + FFileHelper::SaveStringToFile(DataTable->GetTableAsJSON(EDataTableExportFlags::UseJsonObjectsForStructs), *OutFilenames[0]); } } } @@ -209,8 +209,8 @@ void FAssetTypeActions_DataTable::PerformAssetDiff(UObject* OldAsset, UObject* N FString AbsoluteNewTempFileName = FPaths::ConvertRelativePathToFull(RelNewTempFileName); // save temp files - bool OldResult = FFileHelper::SaveStringToFile(OldDataTable->GetTableAsCSV(EDataTableExportFlags::UsePrettyPropertyNames | EDataTableExportFlags::UsePrettyEnumNames), *AbsoluteOldTempFileName); - bool NewResult = FFileHelper::SaveStringToFile(NewDataTable->GetTableAsCSV(EDataTableExportFlags::UsePrettyPropertyNames | EDataTableExportFlags::UsePrettyEnumNames), *AbsoluteNewTempFileName); + bool OldResult = FFileHelper::SaveStringToFile(OldDataTable->GetTableAsCSV(), *AbsoluteOldTempFileName); + bool NewResult = FFileHelper::SaveStringToFile(NewDataTable->GetTableAsCSV(), *AbsoluteNewTempFileName); if (OldResult && NewResult) { diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_SkeletalMesh.cpp b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_SkeletalMesh.cpp index bf7556eb7241..3bdc076585a6 100644 --- a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_SkeletalMesh.cpp +++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_SkeletalMesh.cpp @@ -487,7 +487,7 @@ void FAssetTypeActions_SkeletalMesh::FillCreateMenu(FMenuBuilder& MenuBuilder, T MenuBuilder.AddSubMenu( LOCTEXT("SkeletalMesh_NewPhysicsAssetMenu", "Physics Asset"), LOCTEXT("SkeletalMesh_NewPhysicsAssetMenu_ToolTip", "Options for creating new physics assets from the selected meshes."), - FNewMenuDelegate::CreateSP(this, &FAssetTypeActions_SkeletalMesh::GetPhysicsAssetMenu, Meshes)); + FNewMenuDelegate::CreateSP(const_cast(this), &FAssetTypeActions_SkeletalMesh::GetPhysicsAssetMenu, Meshes)); } MenuBuilder.EndSection(); @@ -620,9 +620,9 @@ TSharedPtr FAssetTypeActions_SkeletalMesh::GetThumbnailOverlay(const FA return SNew(SBorder) .BorderImage(FEditorStyle::GetNoBrush()) .Visibility(this, &FAssetTypeActions_SkeletalMesh::GetThumbnailSkinningOverlayVisibility, AssetData) - .Padding(FMargin(0.0f, 3.0f, 3.0f, 0.0f)) + .Padding(FMargin(0.0f, 0.0f, 3.0f, 3.0f)) .HAlign(HAlign_Right) - .VAlign(VAlign_Top) + .VAlign(VAlign_Bottom) [ SNew(SImage) .ToolTipText(LOCTEXT("FAssetTypeActions_SkeletalMesh_NeedSkinning_ToolTip", "Asset geometry was imported, the skinning need to be validate")) @@ -662,7 +662,7 @@ void FAssetTypeActions_SkeletalMesh::GetLODMenu(class FMenuBuilder& MenuBuilder, int32 LODMax = SkeletalMesh->GetLODNum(); for(int32 LOD = 1; LOD <= LODMax; ++LOD) { - const FText Description = FText::Format( LOCTEXT("LODLevel", "LOD {0}"), FText::AsNumber( LOD ) ); + const FText Description = (LOD == LODMax) ? FText::Format(LOCTEXT("AddLODLevel", "Add LOD {0}"), FText::AsNumber(LOD)) : FText::Format( LOCTEXT("LODLevel", "Reimport LOD {0}"), FText::AsNumber( LOD ) ); const FText ToolTip = ( LOD == LODMax ) ? LOCTEXT("NewImportTip", "Import new LOD") : LOCTEXT("ReimportTip", "Reimport over existing LOD"); MenuBuilder.AddMenuEntry( Description, ToolTip, FSlateIcon(), @@ -836,7 +836,7 @@ void FAssetTypeActions_SkeletalMesh::FillSkeletonMenu(FMenuBuilder& MenuBuilder, LOCTEXT("SkeletalMesh_NewSkeletonTooltip", "Creates a new skeleton for each of the selected meshes."), FSlateIcon(FEditorStyle::GetStyleSetName(), "AssetIcons.Skeleton"), FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_SkeletalMesh::ExecuteNewSkeleton, Meshes), + FExecuteAction::CreateSP(const_cast(this), &FAssetTypeActions_SkeletalMesh::ExecuteNewSkeleton, Meshes), FCanExecuteAction() ) ); @@ -846,7 +846,7 @@ void FAssetTypeActions_SkeletalMesh::FillSkeletonMenu(FMenuBuilder& MenuBuilder, LOCTEXT("SkeletalMesh_AssignSkeletonTooltip", "Assigns a skeleton to the selected meshes."), FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.AssetActions.AssignSkeleton"), FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_SkeletalMesh::ExecuteAssignSkeleton, Meshes), + FExecuteAction::CreateSP(const_cast(this), &FAssetTypeActions_SkeletalMesh::ExecuteAssignSkeleton, Meshes), FCanExecuteAction() ) ); @@ -856,7 +856,7 @@ void FAssetTypeActions_SkeletalMesh::FillSkeletonMenu(FMenuBuilder& MenuBuilder, LOCTEXT("SkeletalMesh_FindSkeletonTooltip", "Finds the skeleton used by the selected meshes in the content browser."), FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.AssetActions.FindSkeleton"), FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_SkeletalMesh::ExecuteFindSkeleton, Meshes), + FExecuteAction::CreateSP(const_cast(this), &FAssetTypeActions_SkeletalMesh::ExecuteFindSkeleton, Meshes), FCanExecuteAction() ) ); diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_Skeleton.cpp b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_Skeleton.cpp index 463cc8efc0d9..2cb9f9d928df 100644 --- a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_Skeleton.cpp +++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_Skeleton.cpp @@ -478,7 +478,7 @@ void FAssetTypeActions_Skeleton::FillCreateMenu(FMenuBuilder& MenuBuilder, TArra LOCTEXT("Skeleton_CreateRigTooltip", "Create Rig from this skeleton."), FSlateIcon(), FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_Skeleton::ExecuteCreateRig, Skeletons), + FExecuteAction::CreateSP(const_cast(this), &FAssetTypeActions_Skeleton::ExecuteCreateRig, Skeletons), FCanExecuteAction() ) ); @@ -488,7 +488,7 @@ void FAssetTypeActions_Skeleton::FillCreateMenu(FMenuBuilder& MenuBuilder, TArra TArray> Objects; Algo::Transform(Skeletons, Objects, [](const TWeakObjectPtr& Skeleton) { return Skeleton; }); - AnimationEditorUtils::FillCreateAssetMenu(MenuBuilder, Objects, FAnimAssetCreated::CreateSP(this, &FAssetTypeActions_Skeleton::OnAssetCreated)); + AnimationEditorUtils::FillCreateAssetMenu(MenuBuilder, Objects, FAnimAssetCreated::CreateSP(const_cast(this), &FAssetTypeActions_Skeleton::OnAssetCreated)); } void FAssetTypeActions_Skeleton::OpenAssetEditor( const TArray& InObjects, TSharedPtr EditWithinLevelEditor ) diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_StaticMesh.cpp b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_StaticMesh.cpp index 8be05042a0af..09874c4068b6 100644 --- a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_StaticMesh.cpp +++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_StaticMesh.cpp @@ -111,7 +111,7 @@ void FAssetTypeActions_StaticMesh::GetImportLODMenu(class FMenuBuilder& MenuBuil FText ToolTip = NSLOCTEXT("AssetTypeActions_StaticMesh", "ReimportTip", "Reimport over existing LOD"); if(LOD == First->GetNumLODs()) { - Description = FText::Format( NSLOCTEXT("AssetTypeActions_StaticMesh", "LOD (number)", "LOD {0}"), LODText ); + Description = FText::Format( NSLOCTEXT("AssetTypeActions_StaticMesh", "LOD (number)", "Add LOD {0}"), LODText ); ToolTip = NSLOCTEXT("AssetTypeActions_StaticMesh", "NewImportTip", "Import new LOD"); } diff --git a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendBase.cpp b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendBase.cpp index 65d7c38b1cf8..6e70ad4d7317 100644 --- a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendBase.cpp +++ b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendBase.cpp @@ -74,7 +74,7 @@ void FBlueprintCompilerCppBackendBase::EmitStructProperties(FEmitterLocalContext check(Property); FString PropertyMacro(TEXT("UPROPERTY(")); { - TArray Tags = FEmitHelper::ProperyFlagsToTags(Property->PropertyFlags, nullptr != Cast(SourceClass)); + TArray Tags = FEmitHelper::PropertyFlagsToTags(Property->PropertyFlags, nullptr != Cast(SourceClass)); Tags.Emplace(FEmitHelper::HandleRepNotifyFunc(Property)); Tags.Emplace(FEmitHelper::HandleMetaData(Property, false)); Tags.Remove(FString()); diff --git a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendUtils.cpp b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendUtils.cpp index 1cb960676134..c715c3411948 100644 --- a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendUtils.cpp +++ b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendUtils.cpp @@ -658,7 +658,7 @@ FString FEmitHelper::HandleMetaData(const UField* Field, bool AddCategory, const #endif #define HANDLE_CPF_TAG(TagName, CheckedFlags) if (HasAllFlags(Flags, (CheckedFlags))) { Tags.Emplace(TagName); } -TArray FEmitHelper::ProperyFlagsToTags(uint64 Flags, bool bIsClassProperty) +TArray FEmitHelper::PropertyFlagsToTags(uint64 Flags, bool bIsClassProperty) { TArray Tags; diff --git a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendUtils.h b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendUtils.h index a58313089ed8..7046430f7c16 100644 --- a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendUtils.h +++ b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendUtils.h @@ -267,7 +267,7 @@ struct FEmitHelper static FString HandleMetaData(const UField* Field, bool AddCategory = true, const TArray* AdditinalMetaData = nullptr); - static TArray ProperyFlagsToTags(uint64 Flags, bool bIsClassProperty); + static TArray PropertyFlagsToTags(uint64 Flags, bool bIsClassProperty); static TArray FunctionFlagsToTags(uint64 Flags); diff --git a/Engine/Source/Developer/CollectionManager/Private/Collection.cpp b/Engine/Source/Developer/CollectionManager/Private/Collection.cpp index 955ef55c1582..76b27a382579 100644 --- a/Engine/Source/Developer/CollectionManager/Private/Collection.cpp +++ b/Engine/Source/Developer/CollectionManager/Private/Collection.cpp @@ -122,13 +122,13 @@ bool FCollection::Load(FText& OutError) if (StorageMode == ECollectionStorageMode::Static) { // Static collection, a flat list of asset paths - for (FString Line : FileContents) + for (FString& Line : FileContents) { Line.TrimStartAndEndInline(); - if ( Line.Len() ) + if ( int32 Len = Line.Len() ) { - AddObjectToCollection(FName(*Line)); + AddObjectToCollection(FName(Len, *Line)); } } } @@ -194,7 +194,7 @@ bool FCollection::Save(const TArray& AdditionalChangelistText, FText& Out { // Write out the set as a sorted array to keep things in a known order for diffing TArray ObjectList = ObjectSet.Array(); - ObjectList.Sort(); + ObjectList.Sort(FNameLexicalLess()); // Static collection. Save a flat list of all objects in the collection. for (const FName& ObjectName : ObjectList) @@ -398,10 +398,11 @@ void FCollection::Empty() bool FCollection::AddObjectToCollection(FName ObjectPath) { - if (StorageMode == ECollectionStorageMode::Static && !ObjectSet.Contains(ObjectPath)) + if (StorageMode == ECollectionStorageMode::Static) { - ObjectSet.Add(ObjectPath); - return true; + bool bAlreadyInSet = false; + ObjectSet.Add(ObjectPath, &bAlreadyInSet); + return !bAlreadyInSet; } return false; @@ -580,7 +581,7 @@ void FCollection::PrintCollection() const // Print the set as a sorted array to keep things in a sane order TArray ObjectList = ObjectSet.Array(); - ObjectList.Sort(); + ObjectList.Sort(FNameLexicalLess()); for (const FName& ObjectName : ObjectList) { @@ -902,8 +903,8 @@ bool FCollection::CheckinCollection(const TArray& AdditionalChangelistTex TArray ObjectsRemoved; GetObjectDifferencesFromDisk(ObjectsAdded, ObjectsRemoved); - ObjectsAdded.Sort(); - ObjectsRemoved.Sort(); + ObjectsAdded.Sort(FNameLexicalLess()); + ObjectsRemoved.Sort(FNameLexicalLess()); // Report added files FFormatNamedArguments Args; diff --git a/Engine/Source/Developer/DerivedDataCache/Private/DDCCleanup.cpp b/Engine/Source/Developer/DerivedDataCache/Private/DDCCleanup.cpp index ba943bf4d82b..446e33cbf611 100644 --- a/Engine/Source/Developer/DerivedDataCache/Private/DDCCleanup.cpp +++ b/Engine/Source/Developer/DerivedDataCache/Private/DDCCleanup.cpp @@ -57,6 +57,7 @@ FDDCCleanup* FDDCCleanup::Runnable = NULL; FDDCCleanup::FDDCCleanup() : Thread(NULL) , StopTaskCounter(0) + , bDontWaitBetweenDeletes(false) { // Don't delete the runnable automatically. It's going to be manually deleted in FDDCCleanup::Shutdown. Thread = FRunnableThread::Create(this, TEXT("FDDCCleanup"), 0, TPri_BelowNormal, FPlatformAffinity::GetPoolThreadMask()); @@ -71,9 +72,9 @@ void FDDCCleanup::Wait( const float InSeconds, const float InSleepTime ) { // Instead of waiting the given amount of seconds doing nothing // check periodically if there's been any Stop requests. - for( float TimeToWait = InSeconds; TimeToWait > 0.0f && ShouldStop() == false; TimeToWait -= InSleepTime ) + for( float TimeToWait = InSeconds; TimeToWait > 0.0f && ShouldStop() == false && !bDontWaitBetweenDeletes; TimeToWait -= InSleepTime ) { - FPlatformProcess::Sleep( FMath::Min(InSleepTime, TimeToWait) ); + FPlatformProcess::SleepNoStats( FMath::Min(InSleepTime, TimeToWait) ); } } @@ -108,7 +109,7 @@ uint32 FDDCCleanup::Run() } Wait( 5.0f ); } - while( ShouldStop() == false ); + while(ShouldStop() == false && CleanupList.Num() > 0); return 0; } @@ -134,7 +135,7 @@ bool FDDCCleanup::CleanupFilesystemDirectory( TSharedPtr< FFilesystemInfo > File CleanupList.Remove( FilesystemInfo ); FilesystemInfo.Reset(); } - else if( ++FilesystemInfo->FoldersChecked >= FilesystemInfo->MaxNumFoldersToCheck && FilesystemInfo->MaxNumFoldersToCheck > 0 ) + else if( !bDontWaitBetweenDeletes && ++FilesystemInfo->FoldersChecked >= FilesystemInfo->MaxNumFoldersToCheck && FilesystemInfo->MaxNumFoldersToCheck > 0 ) { // Remove the filesystem but keep checking the current folder FScopeLock ScopeLock( &DataLock ); @@ -158,11 +159,19 @@ bool FDDCCleanup::CleanupFilesystemDirectory( TSharedPtr< FFilesystemInfo > File if( TimeSinceLastAccess >= FilesystemInfo->UnusedFileTime && TimeSinceLastModification >= FilesystemInfo->UnusedFileTime ) { // Delete the file - bool Result = IFileManager::Get().Delete( *FileNames[ FileIndex ], false, true, true ); + bool bResult = IFileManager::Get().Delete( *FileNames[ FileIndex ], false, true, true ); + if (bResult) + { + UE_LOG(LogDerivedDataCache, VeryVerbose, TEXT("Deleted %s"), *FileNames[FileIndex]); + } + else + { + UE_LOG(LogDerivedDataCache, VeryVerbose, TEXT("Failed to delete %s"), *FileNames[FileIndex]); + } } } - if( ++NumFilesChecked >= FilesystemInfo->MaxContinuousFileChecks && FilesystemInfo->MaxContinuousFileChecks > 0 && ShouldStop() == false ) + if( !bDontWaitBetweenDeletes && ++NumFilesChecked >= FilesystemInfo->MaxContinuousFileChecks && FilesystemInfo->MaxContinuousFileChecks > 0 && ShouldStop() == false ) { NumFilesChecked = 0; Wait( 1.0f ); @@ -177,7 +186,7 @@ bool FDDCCleanup::CleanupFilesystemDirectory( TSharedPtr< FFilesystemInfo > File bCleanedUp = true; } - UE_LOG(LogDerivedDataCache, VeryVerbose, TEXT("DDC Folder Cleanup (%s) took %.4lfs."), *FilesystemInfo->CachePath, FPlatformTime::Seconds() - StartTime); + UE_CLOG(FilesystemInfo.IsValid(), LogDerivedDataCache, VeryVerbose, TEXT("DDC Folder Cleanup (%s) took %.4lfs."), *FilesystemInfo->CachePath, FPlatformTime::Seconds() - StartTime); return bCleanedUp; } @@ -218,7 +227,7 @@ void FDDCCleanup::Shutdown() { Runnable->EnsureCompletion(); delete Runnable; - Runnable = NULL; + Runnable = nullptr; } } diff --git a/Engine/Source/Developer/DerivedDataCache/Private/DerivedDataCache.cpp b/Engine/Source/Developer/DerivedDataCache/Private/DerivedDataCache.cpp index 5d06ff578390..a964b276a4df 100644 --- a/Engine/Source/Developer/DerivedDataCache/Private/DerivedDataCache.cpp +++ b/Engine/Source/Developer/DerivedDataCache/Private/DerivedDataCache.cpp @@ -29,6 +29,10 @@ DEFINE_STAT(STAT_DDC_PutTime); DEFINE_STAT(STAT_DDC_SyncBuildTime); DEFINE_STAT(STAT_DDC_ExistTime); +//#define DDC_SCOPE_CYCLE_COUNTER(x) QUICK_SCOPE_CYCLE_COUNTER(STAT_ ## x) +#define DDC_SCOPE_CYCLE_COUNTER(x) TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT(#x)); + + #if ENABLE_COOK_STATS #include "DerivedDataCacheUsageStats.h" namespace DerivedDataCacheCookStats @@ -148,9 +152,13 @@ class FDerivedDataCache : public FDerivedDataCacheInterface /** Async worker that checks the cache backend and if that fails, calls the deriver to build the data and then puts the results to the cache **/ void DoWork() { + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("DDC_DoWork")); + const int32 NumBeforeDDC = Data.Num(); bool bGetResult; { + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("DDC_Get")); + INC_DWORD_STAT(STAT_DDC_NumGets); STAT(double ThisTime = 0); { @@ -203,6 +211,8 @@ class FDerivedDataCache : public FDerivedDataCacheInterface else if (DataDeriver) { { + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("DDC_Build")); + INC_DWORD_STAT(STAT_DDC_NumBuilds); STAT(double ThisTime = 0); { @@ -217,6 +227,9 @@ class FDerivedDataCache : public FDerivedDataCacheInterface if (bSuccess) { check(Data.Num()); + + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("DDC_Put")); + INC_DWORD_STAT(STAT_DDC_NumPuts); STAT(double ThisTime = 0); { @@ -277,7 +290,7 @@ public: virtual bool GetSynchronous(FDerivedDataPluginInterface* DataDeriver, TArray& OutData, bool* bDataWasBuilt = nullptr) override { - QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_GetSynchronous); + DDC_SCOPE_CYCLE_COUNTER(DDC_GetSynchronous); check(DataDeriver); FString CacheKey = FDerivedDataCache::BuildCacheKey(DataDeriver); UE_LOG(LogDerivedDataCache, Verbose, TEXT("GetSynchronous %s"), *CacheKey); @@ -294,7 +307,7 @@ public: virtual uint32 GetAsynchronous(FDerivedDataPluginInterface* DataDeriver) override { - QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_GetAsynchronous); + DDC_SCOPE_CYCLE_COUNTER(DDC_GetAsynchronous); FScopeLock ScopeLock(&SynchronizationObject); uint32 Handle = NextHandle(); FString CacheKey = FDerivedDataCache::BuildCacheKey(DataDeriver); @@ -323,7 +336,7 @@ public: virtual bool PollAsynchronousCompletion(uint32 Handle) override { - QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_PollAsynchronousCompletion); + DDC_SCOPE_CYCLE_COUNTER(DDC_PollAsynchronousCompletion); FAsyncTask* AsyncTask = NULL; { FScopeLock ScopeLock(&SynchronizationObject); @@ -335,7 +348,7 @@ public: virtual void WaitAsynchronousCompletion(uint32 Handle) override { - QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_WaitAsynchronousCompletion); + DDC_SCOPE_CYCLE_COUNTER(DDC_WaitAsynchronousCompletion); STAT(double ThisTime = 0); { SCOPE_SECONDS_COUNTER(ThisTime); @@ -352,7 +365,7 @@ public: virtual bool GetAsynchronousResults(uint32 Handle, TArray& OutData, bool* bDataWasBuilt = nullptr) override { - QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_GetAsynchronousResults); + DDC_SCOPE_CYCLE_COUNTER(DDC_GetAsynchronousResults); FAsyncTask* AsyncTask = NULL; { FScopeLock ScopeLock(&SynchronizationObject); @@ -386,7 +399,7 @@ public: virtual bool GetSynchronous(const TCHAR* CacheKey, TArray& OutData) override { - QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_GetSynchronous_Data); + DDC_SCOPE_CYCLE_COUNTER(DDC_GetSynchronous_Data); UE_LOG(LogDerivedDataCache, Verbose, TEXT("GetSynchronous %s"), CacheKey); FAsyncTask PendingTask((FDerivedDataPluginInterface*)NULL, CacheKey, true); AddToAsyncCompletionCounter(1); @@ -397,7 +410,7 @@ public: virtual uint32 GetAsynchronous(const TCHAR* CacheKey, IDerivedDataRollup* Rollup = NULL) override { - QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_GetAsynchronous_Handle); + DDC_SCOPE_CYCLE_COUNTER(DDC_GetAsynchronous_Handle); check(!Rollup); // this needs to be handled by someone else, if rollups are disabled, then it should be NULL FScopeLock ScopeLock(&SynchronizationObject); UE_LOG(LogDerivedDataCache, Verbose, TEXT("GetAsynchronous %s"), CacheKey); @@ -418,7 +431,7 @@ public: **/ void GetAsynchronousForRollup(const TCHAR* CacheKey, uint32 Handle) { - QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_GetAsynchronousForRollup); + DDC_SCOPE_CYCLE_COUNTER(DDC_GetAsynchronousForRollup); FScopeLock ScopeLock(&SynchronizationObject); UE_LOG(LogDerivedDataCache, Verbose, TEXT("GetAsynchronous(handle) %s"), CacheKey); FAsyncTask* AsyncTask = new FAsyncTask((FDerivedDataPluginInterface*)NULL, CacheKey, false); @@ -430,7 +443,7 @@ public: virtual void Put(const TCHAR* CacheKey, TArray& Data, bool bPutEvenIfExists = false) override { - QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_Put); + DDC_SCOPE_CYCLE_COUNTER(DDC_Put); STAT(double ThisTime = 0); { SCOPE_SECONDS_COUNTER(ThisTime); @@ -447,7 +460,7 @@ public: virtual bool CachedDataProbablyExists(const TCHAR* CacheKey) override { - QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_CachedDataProbablyExists); + DDC_SCOPE_CYCLE_COUNTER(DDC_CachedDataProbablyExists); bool bResult; INC_DWORD_STAT(STAT_DDC_NumExist); STAT(double ThisTime = 0); @@ -461,7 +474,7 @@ public: void NotifyBootComplete() override { - QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_NotifyBootComplete); + DDC_SCOPE_CYCLE_COUNTER(DDC_NotifyBootComplete); FDerivedDataBackend::Get().NotifyBootComplete(); } @@ -472,7 +485,7 @@ public: void WaitForQuiescence(bool bShutdown) override { - QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_WaitForQuiescence); + DDC_SCOPE_CYCLE_COUNTER(DDC_WaitForQuiescence); FDerivedDataBackend::Get().WaitForQuiescence(bShutdown); } diff --git a/Engine/Source/Developer/DerivedDataCache/Private/DDCCleanup.h b/Engine/Source/Developer/DerivedDataCache/Public/DDCCleanup.h similarity index 79% rename from Engine/Source/Developer/DerivedDataCache/Private/DDCCleanup.h rename to Engine/Source/Developer/DerivedDataCache/Public/DDCCleanup.h index 4ab97d31344d..998ec2a01829 100644 --- a/Engine/Source/Developer/DerivedDataCache/Private/DDCCleanup.h +++ b/Engine/Source/Developer/DerivedDataCache/Public/DDCCleanup.h @@ -11,7 +11,7 @@ struct FFilesystemInfo; /** * DDC Filesystem Cache cleanup thread. */ -class FDDCCleanup : public FRunnable +class DERIVEDDATACACHE_API FDDCCleanup : public FRunnable { /** Singleton instance */ static FDDCCleanup* Runnable; @@ -24,6 +24,8 @@ class FDDCCleanup : public FRunnable TArray< TSharedPtr< struct FFilesystemInfo > > CleanupList; /** Synchronization object */ FCriticalSection DataLock; + /** If true, work without giving up time to other threads */ + bool bDontWaitBetweenDeletes; /** Constructor */ FDDCCleanup(); @@ -74,9 +76,27 @@ public: */ void AddFilesystem( FString& InCachePath, int32 InDaysToDelete, int32 InMaxNumFoldersToCheck, int32 InMaxContinuousFileChecks ); - /** Gets DDC Cleanup singleton instance */ + /** Gets DDC Cleanup singleton instance, crates one if it doesn't exist already */ static FDDCCleanup* Get(); + /** Gets DDC Cleanup singleton instance */ + static FDDCCleanup* GetNoInit() + { + return Runnable; + } + /** Shuts down DDC Cleanup thread. */ static void Shutdown(); + + /** Sets whether cleanup should give up time to other threads between deletes */ + void WaitBetweenDeletes(bool bWait) + { + bDontWaitBetweenDeletes = !bWait; + } + + /** Checks if the cleanup thread is done deleting files */ + bool IsFinished() const + { + return !CleanupList.Num(); + } }; diff --git a/Engine/Source/Developer/FunctionalTesting/Private/ScreenshotFunctionalTest.cpp b/Engine/Source/Developer/FunctionalTesting/Private/ScreenshotFunctionalTest.cpp index b25440cc6748..bd6c410b6a1c 100644 --- a/Engine/Source/Developer/FunctionalTesting/Private/ScreenshotFunctionalTest.cpp +++ b/Engine/Source/Developer/FunctionalTesting/Private/ScreenshotFunctionalTest.cpp @@ -47,7 +47,7 @@ void AScreenshotFunctionalTest::PrepareTest() if (PlayerController && PlayerController->PlayerCameraManager) { - PlayerController->PlayerCameraManager->bGameCameraCutThisFrame = true; + PlayerController->PlayerCameraManager->SetGameCameraCutThisFrame(); if (ScreenshotCamera) { ScreenshotCamera->NotifyCameraCut(); diff --git a/Engine/Source/Developer/GameplayDebugger/Private/GameplayDebuggerAddonManager.cpp b/Engine/Source/Developer/GameplayDebugger/Private/GameplayDebuggerAddonManager.cpp index d131cd16ca1e..55f9f9db2db1 100644 --- a/Engine/Source/Developer/GameplayDebugger/Private/GameplayDebuggerAddonManager.cpp +++ b/Engine/Source/Developer/GameplayDebugger/Private/GameplayDebuggerAddonManager.cpp @@ -45,7 +45,7 @@ void FGameplayDebuggerAddonManager::NotifyCategoriesChanged() int32 CategoryId; int32 SlotIdx; - bool operator<(const FSlotInfo& Other) const { return (SlotIdx == Other.SlotIdx) ? (CategoryName < Other.CategoryName) : (SlotIdx < Other.SlotIdx); } + bool operator<(const FSlotInfo& Other) const { return (SlotIdx == Other.SlotIdx) ? CategoryName.LexicalLess(Other.CategoryName) : (SlotIdx < Other.SlotIdx); } }; TArray AssignList; diff --git a/Engine/Source/Developer/HotReload/Private/HotReloadClassReinstancer.cpp b/Engine/Source/Developer/HotReload/Private/HotReloadClassReinstancer.cpp index fea40681bfc2..7b575bea8f15 100644 --- a/Engine/Source/Developer/HotReload/Private/HotReloadClassReinstancer.cpp +++ b/Engine/Source/Developer/HotReload/Private/HotReloadClassReinstancer.cpp @@ -481,9 +481,15 @@ void FHotReloadClassReinstancer::UpdateDefaultProperties() TArray CurrentValueSerializedData; // Update properties on all existing instances of the class + const UPackage* TransientPackage = GetTransientPackage(); for (FObjectIterator It(NewClass); It; ++It) { UObject* ObjectPtr = *It; + if (ObjectPtr->IsPendingKill() || ObjectPtr->GetOutermost() == TransientPackage) + { + continue; + } + DefaultSubobjectArray.Empty(DefaultSubobjectArrayCapacity); ObjectPtr->CollectDefaultSubobjects(DefaultSubobjectArray); diff --git a/Engine/Source/Developer/IOS/IOSTargetPlatform/Private/IOSTargetDevice.cpp b/Engine/Source/Developer/IOS/IOSTargetPlatform/Private/IOSTargetDevice.cpp index ec7aaad1785c..0b7f4c0b0299 100644 --- a/Engine/Source/Developer/IOS/IOSTargetPlatform/Private/IOSTargetDevice.cpp +++ b/Engine/Source/Developer/IOS/IOSTargetPlatform/Private/IOSTargetDevice.cpp @@ -73,9 +73,10 @@ bool FTcpDSCommander::Init() { return false; } - TSharedRef Addr = SSS->CreateInternetAddr(0, DEFAULT_DS_COMMANDER_PORT); + TSharedRef Addr = SSS->CreateInternetAddr(); bool bIsValid; Addr->SetIp(TEXT("127.0.0.1"), bIsValid); + Addr->SetPort(DEFAULT_DS_COMMANDER_PORT); #if PLATFORM_WINDOWS // using the mutex to detect if the DeploymentServer is running diff --git a/Engine/Source/Developer/Localization/Public/LocTextHelper.h b/Engine/Source/Developer/Localization/Public/LocTextHelper.h index 127112116087..463f0bd6b480 100644 --- a/Engine/Source/Developer/Localization/Public/LocTextHelper.h +++ b/Engine/Source/Developer/Localization/Public/LocTextHelper.h @@ -129,6 +129,10 @@ public: */ FString GetConflictReport() const; + /** Publicly movable */ + FLocTextConflicts(FLocTextConflicts&&) = default; + FLocTextConflicts& operator=(FLocTextConflicts&&) = default; + private: FLocTextConflicts(const FLocTextConflicts&) = delete; FLocTextConflicts& operator=(const FLocTextConflicts&) = delete; diff --git a/Engine/Source/Developer/LogVisualizer/Private/SVisualLogger.cpp b/Engine/Source/Developer/LogVisualizer/Private/SVisualLogger.cpp index effd44ec505f..636145b1671b 100644 --- a/Engine/Source/Developer/LogVisualizer/Private/SVisualLogger.cpp +++ b/Engine/Source/Developer/LogVisualizer/Private/SVisualLogger.cpp @@ -388,7 +388,7 @@ TSharedRef SVisualLogger::HandleTabManagerSpawnTab(const FSpawnTabArgs } else if (TabIdentifier == MainViewTabId) { - TabWidget = SAssignNew(MainView, SVisualLoggerView, CommandList).OnFiltersSearchChanged(this, &SVisualLogger::OnFiltersSearchChanged); + TabWidget = SAssignNew(MainView, SVisualLoggerView, CommandList).OnFiltersSearchChanged(const_cast(this), &SVisualLogger::OnFiltersSearchChanged); AutoSizeTab = false; } else if (TabIdentifier == LogsListTabId) diff --git a/Engine/Source/Developer/LogVisualizer/Private/SVisualLoggerFilters.cpp b/Engine/Source/Developer/LogVisualizer/Private/SVisualLoggerFilters.cpp index 06d807c8d91f..96ea33becd25 100644 --- a/Engine/Source/Developer/LogVisualizer/Private/SVisualLoggerFilters.cpp +++ b/Engine/Source/Developer/LogVisualizer/Private/SVisualLoggerFilters.cpp @@ -249,7 +249,7 @@ void SVisualLoggerFilters::CreateFiltersMenuCategoryForGraph(FMenuBuilder& MenuB FText::Format(LOCTEXT("FilterByTooltipPrefix", "Filter by {0}"), LabelText), FSlateIcon(), FUIAction( - FExecuteAction::CreateSP(this, &SVisualLoggerFilters::FilterByTypeClicked, GraphName, DataName), + FExecuteAction::CreateSP(const_cast(this), &SVisualLoggerFilters::FilterByTypeClicked, GraphName, DataName), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SVisualLoggerFilters::IsAssetTypeActionsInUse, GraphName, DataName), FIsActionButtonVisible::CreateLambda([this, LabelText]()->bool{return this->GraphsSearchString.Len() == 0 || LabelText.ToString().Find(this->GraphsSearchString) != INDEX_NONE; })), diff --git a/Engine/Source/Developer/LogVisualizer/Private/SVisualLoggerView.cpp b/Engine/Source/Developer/LogVisualizer/Private/SVisualLoggerView.cpp index adc9917d970a..81179b9455a3 100644 --- a/Engine/Source/Developer/LogVisualizer/Private/SVisualLoggerView.cpp +++ b/Engine/Source/Developer/LogVisualizer/Private/SVisualLoggerView.cpp @@ -79,7 +79,7 @@ void SVisualLoggerView::Construct(const FArguments& InArgs, const TSharedRef ZoomScrollBar = SNew(SScrollBar) .Orientation(EOrientation::Orient_Horizontal) - .Thickness(FVector2D(2.0f, 2.0f)); + .Thickness(FVector2D(6.0f, 6.0f)); ZoomScrollBar->SetState(0.0f, 1.0f); FLogVisualizer::Get().GetTimeSliderController()->SetExternalScrollbar(ZoomScrollBar); @@ -90,7 +90,7 @@ void SVisualLoggerView::Construct(const FArguments& InArgs, const TSharedRef ScrollBar = SNew(SScrollBar) - .Thickness(FVector2D(2.0f, 2.0f)); + .Thickness(FVector2D(6.0f, 6.0f)); ULogVisualizerSettings* Settings = ULogVisualizerSettings::StaticClass()->GetDefaultObject(); diff --git a/Engine/Source/Developer/LogVisualizer/Private/VisualLoggerCanvasRenderer.cpp b/Engine/Source/Developer/LogVisualizer/Private/VisualLoggerCanvasRenderer.cpp index 130dfee90230..b6d240629290 100644 --- a/Engine/Source/Developer/LogVisualizer/Private/VisualLoggerCanvasRenderer.cpp +++ b/Engine/Source/Developer/LogVisualizer/Private/VisualLoggerCanvasRenderer.cpp @@ -254,7 +254,7 @@ void FVisualLoggerCanvasRenderer::DrawHistogramGraphs(class UCanvas* Canvas, cla auto& CategoriesForGraph = UsedGraphCategories.FindOrAdd(It->Key.ToString()); - It->Value.GraphLines.KeySort(TLess()); + It->Value.GraphLines.KeySort(FNameLexicalLess()); for (auto LinesIt(It->Value.GraphLines.CreateConstIterator()); LinesIt; ++LinesIt) { const FString DataName = LinesIt->Value.DataName.ToString(); diff --git a/Engine/Source/Developer/MaterialBaking/Private/MaterialOptionsCustomization.cpp b/Engine/Source/Developer/MaterialBaking/Private/MaterialOptionsCustomization.cpp index e7003d3a72ff..48f351075d3b 100644 --- a/Engine/Source/Developer/MaterialBaking/Private/MaterialOptionsCustomization.cpp +++ b/Engine/Source/Developer/MaterialBaking/Private/MaterialOptionsCustomization.cpp @@ -200,14 +200,14 @@ void AddTextureSizeClamping(TSharedPtr TextureSizeProperty) const FString MaxTextureResolutionString = FString::FromInt(GetMax2DTextureDimension()); TextureSizeProperty->GetProperty()->SetMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); TextureSizeProperty->GetProperty()->SetMetaData(TEXT("UIMax"), *MaxTextureResolutionString); - PropertyX->GetProperty()->SetMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); - PropertyX->GetProperty()->SetMetaData(TEXT("UIMax"), *MaxTextureResolutionString); - PropertyY->GetProperty()->SetMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); - PropertyY->GetProperty()->SetMetaData(TEXT("UIMax"), *MaxTextureResolutionString); + PropertyX->SetInstanceMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); + PropertyX->SetInstanceMetaData(TEXT("UIMax"), *MaxTextureResolutionString); + PropertyY->SetInstanceMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); + PropertyY->SetInstanceMetaData(TEXT("UIMax"), *MaxTextureResolutionString); const FString MinTextureResolutionString("1"); - PropertyX->GetProperty()->SetMetaData(TEXT("ClampMin"), *MinTextureResolutionString); - PropertyX->GetProperty()->SetMetaData(TEXT("UIMin"), *MinTextureResolutionString); - PropertyY->GetProperty()->SetMetaData(TEXT("ClampMin"), *MinTextureResolutionString); - PropertyY->GetProperty()->SetMetaData(TEXT("UIMin"), *MinTextureResolutionString); + PropertyX->SetInstanceMetaData(TEXT("ClampMin"), *MinTextureResolutionString); + PropertyX->SetInstanceMetaData(TEXT("UIMin"), *MinTextureResolutionString); + PropertyY->SetInstanceMetaData(TEXT("ClampMin"), *MinTextureResolutionString); + PropertyY->SetInstanceMetaData(TEXT("UIMin"), *MinTextureResolutionString); } diff --git a/Engine/Source/Developer/Merge/Private/MergeUtils.cpp b/Engine/Source/Developer/Merge/Private/MergeUtils.cpp index c83c9cf5e594..66ae3d4df4c1 100644 --- a/Engine/Source/Developer/Merge/Private/MergeUtils.cpp +++ b/Engine/Source/Developer/Merge/Private/MergeUtils.cpp @@ -43,7 +43,7 @@ UObject const* FMergeToolUtils::LoadRevision(const FString& AssetName, const ISo if (DesiredRevision.Get(TempFileName)) { // Try and load that package - UPackage* TempPackage = LoadPackage(NULL, *TempFileName, LOAD_DisableCompileOnLoad); + UPackage* TempPackage = LoadPackage(NULL, *TempFileName, LOAD_ForDiff|LOAD_DisableCompileOnLoad); if (TempPackage != NULL) { // Grab the old asset from that old package diff --git a/Engine/Source/Developer/Merge/Private/SMergeDetailsView.cpp b/Engine/Source/Developer/Merge/Private/SMergeDetailsView.cpp index 391ba0cc9dba..cf0c1191ffea 100644 --- a/Engine/Source/Developer/Merge/Private/SMergeDetailsView.cpp +++ b/Engine/Source/Developer/Merge/Private/SMergeDetailsView.cpp @@ -242,7 +242,11 @@ void SMergeDetailsView::Construct(const FArguments InArgs InSelectionCallback.ExecuteIfBound(); }; - TSharedPtr Category = FBlueprintDifferenceTreeEntry::CreateDefaultsCategoryEntryForMerge(FOnDiffEntryFocused::CreateStatic(ForwardSelection, SelectionCallback), Children, RemoteDifferences.Num() != 0, LocalDifferences.Num() != 0, bAnyConflict); + TSharedPtr Category = FBlueprintDifferenceTreeEntry::CreateCategoryEntryForMerge( + NSLOCTEXT("FBlueprintDifferenceTreeEntry", "DefaultsLabel", "Defaults"), + NSLOCTEXT("FBlueprintDifferenceTreeEntry", "DefaultsTooltip", "The list of changes made in the Defaults panel"), + FOnDiffEntryFocused::CreateStatic(ForwardSelection, SelectionCallback), + Children, RemoteDifferences.Num() != 0, LocalDifferences.Num() != 0, bAnyConflict); OutTreeEntries.Push(Category); CurrentDifference = -1; diff --git a/Engine/Source/Developer/Merge/Private/SMergeGraphView.cpp b/Engine/Source/Developer/Merge/Private/SMergeGraphView.cpp index dd7e319bbf46..cbb6b8d454d7 100644 --- a/Engine/Source/Developer/Merge/Private/SMergeGraphView.cpp +++ b/Engine/Source/Developer/Merge/Private/SMergeGraphView.cpp @@ -27,24 +27,27 @@ struct FBlueprintRevPair const FRevisionInfo& RevData; };; -static UEdGraph* FindGraphByName(UBlueprint const& FromBlueprint, const FName& GraphName) +static UEdGraph* FindGraphByPath(UBlueprint const& FromBlueprint, const FString& GraphPath) { TArray Graphs; FromBlueprint.GetAllGraphs(Graphs); - UEdGraph* Ret = nullptr; - if (UEdGraph** Result = Graphs.FindByPredicate(FMatchFName(GraphName))) + for (UEdGraph* Graph : Graphs) { - Ret = *Result; + FString SearchGraphPath = FGraphDiffControl::GetGraphPath(Graph); + if (SearchGraphPath.Equals(GraphPath)) + { + return Graph; + } } - return Ret; + return nullptr; } struct FMergeGraphRowEntry { FText Label; - FName GraphName; + FString GraphPath; UEdGraphNode* LocalNode; UEdGraphNode* BaseNode; @@ -61,7 +64,7 @@ struct FMergeGraphRowEntry struct FMergeGraphEntry { - FName GraphName; + FString GraphPath; TArray Changes; bool bAnyConflics; @@ -77,22 +80,24 @@ static TArray< FMergeGraphEntry > GenerateDiffListItems(const FBlueprintRevPair& { // Index all the graphs by name, we use the name of the graph as the // basis of comparison between the various versions of the blueprint. - TMap< FName, UEdGraph* > RemoteGraphMap, BaseGraphMap, LocalGraphMap; + TMap< FString, UEdGraph* > RemoteGraphMap, BaseGraphMap, LocalGraphMap; // We also want the set of all graph names in these blueprints, so that we // can iterate over every graph. - TSet< FName > AllGraphNames; + TSet< FString > AllGraphPaths; { TArray GraphsRemote, GraphsBase, GraphsLocal; RemoteBlueprint.Blueprint->GetAllGraphs(GraphsRemote); BaseBlueprint.Blueprint->GetAllGraphs(GraphsBase); LocalBlueprint.Blueprint->GetAllGraphs(GraphsLocal); - const auto ToMap = [&AllGraphNames](const TArray& InList, TMap& OutMap) + const auto ToMap = [&AllGraphPaths](const TArray& InList, TMap& OutMap) { - for (auto Graph : InList) + for (UEdGraph* Graph : InList) { - OutMap.Add(Graph->GetFName(), Graph); - AllGraphNames.Add(Graph->GetFName()); + FString GraphPath = FGraphDiffControl::GetGraphPath(Graph); + + OutMap.Add(GraphPath, Graph); + AllGraphPaths.Add(GraphPath); } }; ToMap(GraphsRemote, RemoteGraphMap); @@ -118,18 +123,18 @@ static TArray< FMergeGraphEntry > GenerateDiffListItems(const FBlueprintRevPair& return Results; }; - for (const auto& GraphName : AllGraphNames) + for (const FString& GraphPath : AllGraphPaths) { TArray< FDiffSingleResult > RemoteDifferences; TArray< FDiffSingleResult > LocalDifferences; bool bExistsInRemote, bExistsInBase, bExistsInLocal; FMergeGraphEntry GraphEntry; - GraphEntry.GraphName = GraphName; + GraphEntry.GraphPath = GraphPath; { - UEdGraph** RemoteGraph = RemoteGraphMap.Find(GraphName); - UEdGraph** BaseGraph = BaseGraphMap.Find(GraphName); - UEdGraph** LocalGraph = LocalGraphMap.Find(GraphName); + UEdGraph** RemoteGraph = RemoteGraphMap.Find(GraphPath); + UEdGraph** BaseGraph = BaseGraphMap.Find(GraphPath); + UEdGraph** LocalGraph = LocalGraphMap.Find(GraphPath); GraphEntry.bAnyConflics = false; GraphEntry.bExistsInRemote = RemoteGraph != nullptr; @@ -209,7 +214,7 @@ static TArray< FMergeGraphEntry > GenerateDiffListItems(const FBlueprintRevPair& FMergeGraphRowEntry NewEntry = { Label - , Difference.OwningGraph + , Difference.OwningObjectPath , ConflictingDifference ? (*ConflictingDifference)->Node2 : nullptr /*UEdGraphNode* LocalNode*/ , Difference.Node1 /*UEdGraphNode* BaseNode*/ , Difference.Node2 /*UEdGraphNode* RemoteNode*/ @@ -234,7 +239,7 @@ static TArray< FMergeGraphEntry > GenerateDiffListItems(const FBlueprintRevPair& { FMergeGraphRowEntry NewEntry = { Difference.DisplayString - , Difference.OwningGraph + , Difference.OwningObjectPath , Difference.Node2 /*UEdGraphNode* LocalNode*/ , Difference.Node1 /*UEdGraphNode* BaseNode*/ , nullptr @@ -453,22 +458,29 @@ void SMergeGraphView::Construct(const FArguments InArgs FLinearColor LocalColor = ComputeColor(InDifference->bAnyConflics, InDifference->bLocalDifferences); FLinearColor TextColor = ComputeColor(InDifference->bAnyConflics, InDifference->bLocalDifferences || InDifference->bRemoteDifferences); + FString DisplayString = InDifference->GraphPath; + int32 PeriodIndex = INDEX_NONE; + if (DisplayString.FindLastChar('.', PeriodIndex)) + { + DisplayString = DisplayString.Mid(PeriodIndex + 1); + } + return SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SNew(STextBlock) .ColorAndOpacity( TextColor ) - .Text(FText::FromString(InDifference->GraphName.GetPlainNameString())) + .Text(FText::FromString(DisplayString)) ] + DiffViewUtils::Box(InDifference->bExistsInRemote, RemoteColor) + DiffViewUtils::Box(InDifference->bExistsInBase, BaseColor) + DiffViewUtils::Box(InDifference->bExistsInLocal, LocalColor); }; - const auto FocusGraph = [](FOnMergeNodeSelected InSelectionCallback, SMergeGraphView* Parent, FName GraphName) + const auto FocusGraph = [](FOnMergeNodeSelected InSelectionCallback, SMergeGraphView* Parent, FString GraphPath) { InSelectionCallback.ExecuteIfBound(); - Parent->FocusGraph( GraphName ); + Parent->FocusGraph( GraphPath ); }; if( Children.Num() == 0 ) @@ -478,7 +490,7 @@ void SMergeGraphView::Construct(const FArguments InArgs OutTreeEntries.Push( TSharedPtr(new FBlueprintDifferenceTreeEntry( - FOnDiffEntryFocused::CreateStatic(FocusGraph, SelectionCallback, this, Difference.GraphName) + FOnDiffEntryFocused::CreateStatic(FocusGraph, SelectionCallback, this, Difference.GraphPath) , FGenerateDiffEntryWidget::CreateStatic(Widget, &Difference) , Children )) @@ -508,11 +520,11 @@ void SMergeGraphView::Construct(const FArguments InArgs ]; } -void SMergeGraphView::FocusGraph(FName GraphName) +void SMergeGraphView::FocusGraph(const FString& GraphPath) { - UEdGraph* GraphRemote = FindGraphByName(*GetRemotePanel().Blueprint, GraphName); - UEdGraph* GraphBase = FindGraphByName(*GetBasePanel().Blueprint, GraphName); - UEdGraph* GraphLocal = FindGraphByName(*GetLocalPanel().Blueprint, GraphName); + UEdGraph* GraphRemote = FindGraphByPath(*GetRemotePanel().Blueprint, GraphPath); + UEdGraph* GraphBase = FindGraphByPath(*GetBasePanel().Blueprint, GraphPath); + UEdGraph* GraphLocal = FindGraphByPath(*GetLocalPanel().Blueprint, GraphPath); GetBasePanel().GeneratePanel(GraphBase, nullptr); GetRemotePanel().GeneratePanel(GraphRemote, GraphBase); @@ -523,7 +535,7 @@ void SMergeGraphView::FocusGraph(FName GraphName) void SMergeGraphView::HighlightEntry(const struct FMergeGraphRowEntry& Conflict) { - FocusGraph(Conflict.GraphName); + FocusGraph(Conflict.GraphPath); const auto FocusPinOrNode = [this]( UEdGraphPin* Pin, UEdGraphNode* Node ) { @@ -551,7 +563,7 @@ TSharedRef SMergeGraphView::CreateGraphDiffViews(const FSpawnTabArgs& { PanelContainer->AddSlot() [ - SAssignNew(Panel.GraphEditorBorder, SBox) + SAssignNew(Panel.GraphEditorBox, SBox) .VAlign(VAlign_Fill) [ SBlueprintDiff::DefaultEmptyPanel() @@ -572,7 +584,7 @@ TSharedRef SMergeGraphView::CreateMyBlueprintsViews(const FSpawnTabArg { PanelContainer->AddSlot() [ - Panel.GenerateMyBlueprintPanel() + Panel.GenerateMyBlueprintWidget() ]; } diff --git a/Engine/Source/Developer/Merge/Private/SMergeGraphView.h b/Engine/Source/Developer/Merge/Private/SMergeGraphView.h index 736d81c3f2e7..4c183a89dde2 100644 --- a/Engine/Source/Developer/Merge/Private/SMergeGraphView.h +++ b/Engine/Source/Developer/Merge/Private/SMergeGraphView.h @@ -25,7 +25,7 @@ public: , TArray< TSharedPtr >& OutConflicts); private: /** Helper functions and event handlers: */ - void FocusGraph(FName GraphName); + void FocusGraph(const FString& GraphPath); void HighlightEntry(const struct FMergeGraphRowEntry& Conflict); bool HasNoDifferences() const; diff --git a/Engine/Source/Developer/Merge/Private/SMergeTreeView.cpp b/Engine/Source/Developer/Merge/Private/SMergeTreeView.cpp index e25b7f66d538..a650a14ef484 100644 --- a/Engine/Source/Developer/Merge/Private/SMergeTreeView.cpp +++ b/Engine/Source/Developer/Merge/Private/SMergeTreeView.cpp @@ -243,12 +243,14 @@ void SMergeTreeView::Construct(const FArguments InArgs Children.Push( FBlueprintDifferenceTreeEntry::NoDifferencesEntry() ); } - TSharedPtr Category = FBlueprintDifferenceTreeEntry::CreateComponentsCategoryEntryForMerge( - FOnDiffEntryFocused::CreateStatic(ForwardSelection, SelectionCallback) - , Children - , RemoteDifferingProperties.Entries.Num() != 0 - , LocalDifferingProperties.Entries.Num() != 0 - , bAnyConflict); + TSharedPtr Category = FBlueprintDifferenceTreeEntry::CreateCategoryEntryForMerge( + NSLOCTEXT("FBlueprintDifferenceTreeEntry", "SCSLabel", "Components"), + NSLOCTEXT("FBlueprintDifferenceTreeEntry", "SCSTooltip", "The list of changes made in the Components panel"), + FOnDiffEntryFocused::CreateStatic(ForwardSelection, SelectionCallback), + Children, + RemoteDifferingProperties.Entries.Num() != 0, + LocalDifferingProperties.Entries.Num() != 0, + bAnyConflict); OutTreeEntries.Push(Category); ChildSlot[ diff --git a/Engine/Source/Developer/MeshBuilder/MeshBuilder.Build.cs b/Engine/Source/Developer/MeshBuilder/MeshBuilder.Build.cs index 096579d699b9..c44a978a78dc 100644 --- a/Engine/Source/Developer/MeshBuilder/MeshBuilder.Build.cs +++ b/Engine/Source/Developer/MeshBuilder/MeshBuilder.Build.cs @@ -28,6 +28,11 @@ namespace UnrealBuildTool.Rules AddEngineThirdPartyPrivateStaticDependencies(Target, "ForsythTriOptimizer"); AddEngineThirdPartyPrivateStaticDependencies(Target, "nvTessLib"); AddEngineThirdPartyPrivateStaticDependencies(Target, "QuadricMeshReduction"); - } + + if (Target.IsInPlatformGroup(UnrealPlatformGroup.Unix)) + { + PublicAdditionalLibraries.Add("stdc++"); // can be fixed, see UE-70769 + } + } } } diff --git a/Engine/Source/Developer/MeshBuilder/Private/StaticMeshBuilder.cpp b/Engine/Source/Developer/MeshBuilder/Private/StaticMeshBuilder.cpp index 5a370a9778e6..85fc7b1c8f5c 100644 --- a/Engine/Source/Developer/MeshBuilder/Private/StaticMeshBuilder.cpp +++ b/Engine/Source/Developer/MeshBuilder/Private/StaticMeshBuilder.cpp @@ -199,7 +199,7 @@ bool FStaticMeshBuilder::Build(FStaticMeshRenderData& StaticMeshRenderData, USta } //If the reduce did not output the same number of section use the base LOD sectionInfoMap - bool bIsOldMappingInvalid = OldSectionInfoMapCount != UniqueMaterialIndex.Num(); + bool bIsOldMappingInvalid = OldSectionInfoMapCount != MeshDescriptions[LodIndex].PolygonGroups().Num(); bool bValidBaseSectionInfoMap = BeforeBuildSectionInfoMap.GetSectionNumber(BaseReduceLodIndex) > 0; //All used material represent a different section diff --git a/Engine/Source/Developer/MeshMergeUtilities/Private/MeshMergeHelpers.cpp b/Engine/Source/Developer/MeshMergeUtilities/Private/MeshMergeHelpers.cpp index 5de69ebc534e..cab4f89ac7f4 100644 --- a/Engine/Source/Developer/MeshMergeUtilities/Private/MeshMergeHelpers.cpp +++ b/Engine/Source/Developer/MeshMergeUtilities/Private/MeshMergeHelpers.cpp @@ -170,11 +170,21 @@ void FMeshMergeHelpers::ExpandInstances(const UInstancedStaticMeshComponent* InI { FMeshDescription CombinedRawMesh; + bool bFirstMesh = true; for(const FInstancedStaticMeshInstanceData& InstanceData : InInstancedStaticMeshComponent->PerInstanceSMData) { FMeshDescription InstanceRawMesh = InOutRawMesh; FMeshMergeHelpers::TransformRawMeshVertexData(FTransform(InstanceData.Transform), InstanceRawMesh); - FMeshMergeHelpers::AppendRawMesh(CombinedRawMesh, InstanceRawMesh); + + if(bFirstMesh) + { + CombinedRawMesh = InstanceRawMesh; + bFirstMesh = false; + } + else + { + FMeshMergeHelpers::AppendRawMesh(CombinedRawMesh, InstanceRawMesh); + } } InOutRawMesh = CombinedRawMesh; @@ -1176,7 +1186,7 @@ void FMeshMergeHelpers::AppendRawMesh(FMeshDescription& InTarget, const FMeshDes InTarget.ReserveNewVertices(InSource.Vertices().Num()); InTarget.ReserveNewVertexInstances(InSource.VertexInstances().Num()); InTarget.ReserveNewEdges(InSource.Edges().Num()); - InTarget.ReserveNewPolygons(InSource.Vertices().Num()); + InTarget.ReserveNewPolygons(InSource.Polygons().Num()); //Append PolygonGroup for (const FPolygonGroupID& SourcePolygonGroupID : InSource.PolygonGroups().GetElementIDs()) @@ -1189,12 +1199,14 @@ void FMeshMergeHelpers::AppendRawMesh(FMeshDescription& InTarget, const FMeshDes bool bUnique = true; do { + bUnique = true; for (const FPolygonGroupID PolygonGroupID : InTarget.PolygonGroups().GetElementIDs()) { if (TargetPolygonGroupImportedMaterialSlotNames[PolygonGroupID] == CurrentTestName) { CurrentTestName = FName(*(BaseName.ToString() + FString::FromInt(UniqueID++))); bUnique = false; + break; } } } while (!bUnique); diff --git a/Engine/Source/Developer/MeshUtilities/Private/MeshUtilities.cpp b/Engine/Source/Developer/MeshUtilities/Private/MeshUtilities.cpp index 02e014755d09..562373885350 100644 --- a/Engine/Source/Developer/MeshUtilities/Private/MeshUtilities.cpp +++ b/Engine/Source/Developer/MeshUtilities/Private/MeshUtilities.cpp @@ -266,7 +266,7 @@ static void SkinnedMeshToRawMeshes(USkinnedMeshComponent* InSkinnedMeshComponent const FVector TangentX = InComponentToWorld.TransformVector(SkinnedVertex.TangentX.ToFVector()); const FVector TangentZ = InComponentToWorld.TransformVector(SkinnedVertex.TangentZ.ToFVector()); const FVector4 UnpackedTangentZ = SkinnedVertex.TangentZ.ToFVector4(); - const FVector TangentY = (TangentX ^ TangentZ).GetSafeNormal() * UnpackedTangentZ.W; + const FVector TangentY = (TangentZ ^ TangentX).GetSafeNormal() * UnpackedTangentZ.W; RawMesh.WedgeTangentX.Add(TangentX); RawMesh.WedgeTangentY.Add(TangentY); @@ -747,9 +747,9 @@ void FMeshUtilities::BuildSkeletalModelFromChunks(FSkeletalMeshLODModel& LODMode FSoftSkinVertex NewVertex; NewVertex.Position = SoftVertex.Position; - NewVertex.TangentX = SoftVertex.TangentX.ToFVector(); - NewVertex.TangentY = SoftVertex.TangentY.ToFVector(); - NewVertex.TangentZ = SoftVertex.TangentZ.ToFVector(); + NewVertex.TangentX = SoftVertex.TangentX; + NewVertex.TangentY = SoftVertex.TangentY; + NewVertex.TangentZ = SoftVertex.TangentZ; FMemory::Memcpy(NewVertex.UVs, SoftVertex.UVs, sizeof(FVector2D)*MAX_TEXCOORDS); NewVertex.Color = SoftVertex.Color; for (int32 i = 0; i < MAX_TOTAL_INFLUENCES; ++i) @@ -3302,6 +3302,93 @@ public: } } + //This function add every triangles connected to the triangle queue. + //A connected triangle pair must share at least 1 vertex between the two triangles. + //If bConnectByEdge is true, the connected triangle must share at least one edge (two vertex index) + void AddAdjacentFace(IMeshBuildData* BuildData, TBitArray<>& FaceAdded, TMap>& VertexIndexToAdjacentFaces, int32 FaceIndex, TArray& TriangleQueue, const bool bConnectByEdge) + { + int32 NumFaces = (int32)BuildData->GetNumFaces(); + check(FaceAdded.Num() == NumFaces); + + TMap AdjacentFaceCommonVertices; + for (int32 Corner = 0; Corner < 3; Corner++) + { + int32 VertexIndex = BuildData->GetVertexIndex(FaceIndex, Corner); + TArray& AdjacentFaces = VertexIndexToAdjacentFaces.FindChecked(VertexIndex); + for (int32 AdjacentFaceArrayIndex = 0; AdjacentFaceArrayIndex < AdjacentFaces.Num(); ++AdjacentFaceArrayIndex) + { + int32 AdjacentFaceIndex = AdjacentFaces[AdjacentFaceArrayIndex]; + if (!FaceAdded[AdjacentFaceIndex] && AdjacentFaceIndex != FaceIndex) + { + bool bAddConnected = !bConnectByEdge; + if (bConnectByEdge) + { + int32& AdjacentFaceCommonVerticeCount = AdjacentFaceCommonVertices.FindOrAdd(AdjacentFaceIndex); + AdjacentFaceCommonVerticeCount++; + //Is the connected triangles share 2 vertex index (one edge) not only one vertex + bAddConnected = AdjacentFaceCommonVerticeCount > 1; + } + + if (bAddConnected) + { + TriangleQueue.Add(AdjacentFaceIndex); + //Add the face only once by marking the face has computed + FaceAdded[AdjacentFaceIndex] = true; + } + } + } + } + } + + //Fill FaceIndexToPatchIndex so every triangle know is unique island patch index. + //We need to respect the island when we use the smooth group to compute the normals. + //Each island patch have its own smoothgroup data, there is no triangle connectivity possible between island patch. + //@Param bConnectByEdge: If true we need at least 2 vertex index (one edge) to connect 2 triangle. If false we just need one vertex index (bowtie) + void Skeletal_FillPolygonPatch(IMeshBuildData* BuildData, TArray& FaceIndexToPatchIndex, const bool bConnectByEdge) + { + int32 NumTriangles = BuildData->GetNumFaces(); + check(FaceIndexToPatchIndex.Num() == NumTriangles); + + int32 PatchIndex = 0; + + TMap> VertexIndexToAdjacentFaces; + VertexIndexToAdjacentFaces.Reserve(BuildData->GetNumFaces()*2); + for (int32 FaceIndex = 0; FaceIndex < NumTriangles; ++FaceIndex) + { + int32 WedgeOffset = FaceIndex * 3; + for (int32 Corner = 0; Corner < 3; Corner++) + { + int32 VertexIndex = BuildData->GetVertexIndex(FaceIndex, Corner); + TArray& AdjacentFaces = VertexIndexToAdjacentFaces.FindOrAdd(VertexIndex); + AdjacentFaces.AddUnique(FaceIndex); + } + } + + //Mark added face so we do not add them more then once + TBitArray<> FaceAdded; + FaceAdded.Init(false, NumTriangles); + + TArray TriangleQueue; + TriangleQueue.Reserve(100); + for (int32 FaceIndex = 0; FaceIndex < NumTriangles; ++FaceIndex) + { + if (FaceAdded[FaceIndex]) + { + continue; + } + TriangleQueue.Reset(); + TriangleQueue.Add(FaceIndex); //Use a queue to avoid recursive function + FaceAdded[FaceIndex] = true; + while (TriangleQueue.Num() > 0) + { + int32 CurrentTriangleIndex = TriangleQueue.Pop(false); + FaceIndexToPatchIndex[CurrentTriangleIndex] = PatchIndex; + AddAdjacentFace(BuildData, FaceAdded, VertexIndexToAdjacentFaces, CurrentTriangleIndex, TriangleQueue, bConnectByEdge); + } + PatchIndex++; + } + } + void Skeletal_ComputeTangents( IMeshBuildData* BuildData, const FOverlappingCorners& OverlappingCorners @@ -3665,7 +3752,11 @@ public: { bool bBlendOverlappingNormals = true; bool bIgnoreDegenerateTriangles = BuildData->BuildOptions.bRemoveDegenerateTriangles; - + + int32 NumFaces = BuildData->GetNumFaces(); + int32 NumWedges = BuildData->GetNumWedges(); + check(NumFaces * 3 == NumWedges); + // Compute per-triangle tangents. TArray TriangleTangentX; TArray TriangleTangentY; @@ -3679,6 +3770,12 @@ public: bIgnoreDegenerateTriangles ? SMALL_NUMBER : 0.0f ); + TArray FaceIndexToPatchIndex; + FaceIndexToPatchIndex.AddZeroed(NumFaces); + //Since we use triangle normals to compute the vertex normal, we need a full edge connected (2 vertex component per triangle) + const bool bConnectByEdge = true; + Skeletal_FillPolygonPatch(BuildData, FaceIndexToPatchIndex, bConnectByEdge); + TArray& WedgeTangentX = BuildData->GetTangentArray(0); TArray& WedgeTangentY = BuildData->GetTangentArray(1); TArray& WedgeTangentZ = BuildData->GetTangentArray(2); @@ -3687,10 +3784,6 @@ public: TArray RelevantFacesForCorner[3]; TArray AdjacentFaces; - int32 NumFaces = BuildData->GetNumFaces(); - int32 NumWedges = BuildData->GetNumWedges(); - check(NumFaces * 3 == NumWedges); - bool bWedgeTSpace = false; if (WedgeTangentX.Num() > 0 && WedgeTangentY.Num() > 0) @@ -3715,6 +3808,7 @@ public: for (int32 FaceIndex = 0; FaceIndex < NumFaces; FaceIndex++) { + int32 PatchIndex = FaceIndexToPatchIndex[FaceIndex]; int32 WedgeOffset = FaceIndex * 3; FVector CornerPositions[3]; FVector CornerNormal[3]; @@ -3758,8 +3852,9 @@ public: for (int32 k = 0; k < DupVerts.Num(); k++) { int32 PotentialTriangleIndex = DupVerts[k] / 3; - //Do not add mirror triangle to the adjacentFaces - if (!IsTriangleMirror(BuildData, TriangleTangentZ, FaceIndex, PotentialTriangleIndex)) + + //Do not add mirror triangle to the adjacentFaces. Also make sure adjacent triangle is in the same connected triangle patch. + if (!IsTriangleMirror(BuildData, TriangleTangentZ, FaceIndex, PotentialTriangleIndex) && PatchIndex == FaceIndexToPatchIndex[PotentialTriangleIndex]) { AdjacentFaces.AddUnique(PotentialTriangleIndex); } @@ -5257,8 +5352,8 @@ private: if (StaticMeshReductionInterface) { FUIAction UIAction; - UIAction.ExecuteAction.BindSP(this, &FMeshSimplifcationSettingsCustomization::OnMeshSimplificationModuleChosen, ModuleName); - UIAction.GetActionCheckState.BindSP(this, &FMeshSimplifcationSettingsCustomization::IsMeshSimplificationModuleChosen, ModuleName); + UIAction.ExecuteAction.BindSP(const_cast(this), &FMeshSimplifcationSettingsCustomization::OnMeshSimplificationModuleChosen, ModuleName); + UIAction.GetActionCheckState.BindSP(const_cast(this), &FMeshSimplifcationSettingsCustomization::IsMeshSimplificationModuleChosen, ModuleName); MenuBuilder.AddMenuEntry(FText::FromName(ModuleName), FText::GetEmpty(), FSlateIcon(), UIAction, NAME_None, EUserInterfaceActionType::RadioButton); } @@ -5269,7 +5364,7 @@ private: FUIAction OpenMarketplaceAction; - OpenMarketplaceAction.ExecuteAction.BindSP(this, &FMeshSimplifcationSettingsCustomization::OnFindReductionPluginsClicked); + OpenMarketplaceAction.ExecuteAction.BindSP(const_cast(this), &FMeshSimplifcationSettingsCustomization::OnFindReductionPluginsClicked); FSlateIcon Icon = FSlateIcon(FEditorStyle::Get().GetStyleSetName(), "LevelEditor.OpenMarketplace.Menu"); MenuBuilder.AddMenuEntry( LOCTEXT("FindMoreReductionPluginsLink", "Search the Marketplace"), LOCTEXT("FindMoreReductionPluginsLink_Tooltip", "Opens the Marketplace to find more mesh reduction plugins"), Icon, OpenMarketplaceAction); return MenuBuilder.MakeWidget(); @@ -5377,8 +5472,8 @@ private: if (SkeletalMeshReductionInterface) { FUIAction UIAction; - UIAction.ExecuteAction.BindSP(this, &FSkeletalMeshSimplificationSettingsCustomization::OnSkeletalMeshSimplificationModuleChosen, ModuleName); - UIAction.GetActionCheckState.BindSP(this, &FSkeletalMeshSimplificationSettingsCustomization::IsSkeletalMeshSimplificationModuleChosen, ModuleName); + UIAction.ExecuteAction.BindSP(const_cast(this), &FSkeletalMeshSimplificationSettingsCustomization::OnSkeletalMeshSimplificationModuleChosen, ModuleName); + UIAction.GetActionCheckState.BindSP(const_cast(this), &FSkeletalMeshSimplificationSettingsCustomization::IsSkeletalMeshSimplificationModuleChosen, ModuleName); MenuBuilder.AddMenuEntry(FText::FromName(ModuleName), FText::GetEmpty(), FSlateIcon(), UIAction, NAME_None, EUserInterfaceActionType::RadioButton); } @@ -5389,7 +5484,7 @@ private: FUIAction OpenMarketplaceAction; - OpenMarketplaceAction.ExecuteAction.BindSP(this, &FSkeletalMeshSimplificationSettingsCustomization::OnFindReductionPluginsClicked); + OpenMarketplaceAction.ExecuteAction.BindSP(const_cast(this), &FSkeletalMeshSimplificationSettingsCustomization::OnFindReductionPluginsClicked); FSlateIcon Icon = FSlateIcon(FEditorStyle::Get().GetStyleSetName(), "LevelEditor.OpenMarketplace.Menu"); MenuBuilder.AddMenuEntry(LOCTEXT("FindMoreReductionPluginsLink", "Search the Marketplace"), LOCTEXT("FindMoreReductionPluginsLink_Tooltip", "Opens the Marketplace to find more mesh reduction plugins"), Icon, OpenMarketplaceAction); return MenuBuilder.MakeWidget(); @@ -5497,8 +5592,8 @@ private: if (MeshMergingInterface) { FUIAction UIAction; - UIAction.ExecuteAction.BindSP(this, &FProxyLODMeshSimplificationSettingsCustomization::OnProxyLODMeshSimplificationModuleChosen, ModuleName); - UIAction.GetActionCheckState.BindSP(this, &FProxyLODMeshSimplificationSettingsCustomization::IsProxyLODMeshSimplificationModuleChosen, ModuleName); + UIAction.ExecuteAction.BindSP(const_cast(this), &FProxyLODMeshSimplificationSettingsCustomization::OnProxyLODMeshSimplificationModuleChosen, ModuleName); + UIAction.GetActionCheckState.BindSP(const_cast(this), &FProxyLODMeshSimplificationSettingsCustomization::IsProxyLODMeshSimplificationModuleChosen, ModuleName); MenuBuilder.AddMenuEntry(FText::FromName(ModuleName), FText::GetEmpty(), FSlateIcon(), UIAction, NAME_None, EUserInterfaceActionType::RadioButton); } @@ -5509,7 +5604,7 @@ private: FUIAction OpenMarketplaceAction; - OpenMarketplaceAction.ExecuteAction.BindSP(this, &FProxyLODMeshSimplificationSettingsCustomization::OnFindReductionPluginsClicked); + OpenMarketplaceAction.ExecuteAction.BindSP(const_cast(this), &FProxyLODMeshSimplificationSettingsCustomization::OnFindReductionPluginsClicked); FSlateIcon Icon = FSlateIcon(FEditorStyle::Get().GetStyleSetName(), "LevelEditor.OpenMarketplace.Menu"); MenuBuilder.AddMenuEntry(LOCTEXT("FindMoreReductionPluginsLink", "Search the Marketplace"), LOCTEXT("FindMoreReductionPluginsLink_Tooltip", "Opens the Marketplace to find more mesh reduction plugins"), Icon, OpenMarketplaceAction); return MenuBuilder.MakeWidget(); diff --git a/Engine/Source/Developer/MeshUtilities/Private/SkeletalMeshTools.cpp b/Engine/Source/Developer/MeshUtilities/Private/SkeletalMeshTools.cpp index e91f128d7047..3a6a9f44054b 100644 --- a/Engine/Source/Developer/MeshUtilities/Private/SkeletalMeshTools.cpp +++ b/Engine/Source/Developer/MeshUtilities/Private/SkeletalMeshTools.cpp @@ -24,17 +24,17 @@ namespace SkeletalMeshTools } } - if(!NormalsEqual(V1.TangentX.ToFVector(), V2.TangentX.ToFVector(), OverlappingThresholds)) + if(!NormalsEqual(V1.TangentX, V2.TangentX, OverlappingThresholds)) { return false; } - if(!NormalsEqual(V1.TangentY.ToFVector(), V2.TangentY.ToFVector(), OverlappingThresholds)) + if(!NormalsEqual(V1.TangentY, V2.TangentY, OverlappingThresholds)) { return false; } - if(!NormalsEqual(V1.TangentZ.ToFVector(), V2.TangentZ.ToFVector(), OverlappingThresholds)) + if(!NormalsEqual(V1.TangentZ, V2.TangentZ, OverlappingThresholds)) { return false; } diff --git a/Engine/Source/Developer/MeshUtilities/Private/SkeletalMeshTools.h b/Engine/Source/Developer/MeshUtilities/Private/SkeletalMeshTools.h index 6ed0fbca7254..7cda66c6461c 100644 --- a/Engine/Source/Developer/MeshUtilities/Private/SkeletalMeshTools.h +++ b/Engine/Source/Developer/MeshUtilities/Private/SkeletalMeshTools.h @@ -4,7 +4,6 @@ #include "CoreMinimal.h" #include "Containers/IndirectArray.h" -#include "PackedNormal.h" #include "GPUSkinPublicDefs.h" #include "Components.h" #include "BoneIndices.h" @@ -26,7 +25,7 @@ struct FSkeletalMeshVertIndexAndZ struct FSoftSkinBuildVertex { FVector Position; - FPackedNormal TangentX, // Tangent, U-direction + FVector TangentX, // Tangent, U-direction TangentY, // Binormal, V-direction TangentZ; // Normal FVector2D UVs[MAX_TEXCOORDS]; // UVs diff --git a/Engine/Source/Developer/MessageLog/Private/UserInterface/SMessageLogListing.cpp b/Engine/Source/Developer/MessageLog/Private/UserInterface/SMessageLogListing.cpp index cf25f746b7fb..3d606efb93ca 100644 --- a/Engine/Source/Developer/MessageLog/Private/UserInterface/SMessageLogListing.cpp +++ b/Engine/Source/Developer/MessageLog/Private/UserInterface/SMessageLogListing.cpp @@ -419,7 +419,7 @@ TSharedRef SMessageLogListing::OnGetPageMenuContent() const MessageLogListingViewModel->GetPageTitle(PageIndex), FText::Format(LOCTEXT("PageMenuEntry_Tooltip", "View page: {PageName}"), Arguments), FSlateIcon(), - FExecuteAction::CreateSP(this, &SMessageLogListing::OnPageSelected, PageIndex)); + FExecuteAction::CreateSP(const_cast(this), &SMessageLogListing::OnPageSelected, PageIndex)); } return MenuBuilder.MakeWidget(); diff --git a/Engine/Source/Developer/ModuleUI/Private/SModuleUI.cpp b/Engine/Source/Developer/ModuleUI/Private/SModuleUI.cpp index 0c2f13deb905..a7d075d41c67 100644 --- a/Engine/Source/Developer/ModuleUI/Private/SModuleUI.cpp +++ b/Engine/Source/Developer/ModuleUI/Private/SModuleUI.cpp @@ -202,7 +202,7 @@ void SModuleUI::UpdateModuleListItems() { FORCEINLINE bool operator()( const TSharedPtr& A, const TSharedPtr& B ) const { - return A->ModuleName < B->ModuleName; + return A->ModuleName.LexicalLess(B->ModuleName); } }; ModuleListItems.Sort( FModuleSorter() ); diff --git a/Engine/Source/Developer/OutputLog/Private/SOutputLog.cpp b/Engine/Source/Developer/OutputLog/Private/SOutputLog.cpp index f48a3a0e3d8c..a21157ff06ee 100644 --- a/Engine/Source/Developer/OutputLog/Private/SOutputLog.cpp +++ b/Engine/Source/Developer/OutputLog/Private/SOutputLog.cpp @@ -1,7 +1,6 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "SOutputLog.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/IRun.h" #include "Framework/Text/TextLayout.h" #include "Misc/ConfigCacheIni.h" diff --git a/Engine/Source/Developer/PakFileUtilities/Private/PakFileUtilities.cpp b/Engine/Source/Developer/PakFileUtilities/Private/PakFileUtilities.cpp index 01da0950bc3d..d0d12e50f429 100644 --- a/Engine/Source/Developer/PakFileUtilities/Private/PakFileUtilities.cpp +++ b/Engine/Source/Developer/PakFileUtilities/Private/PakFileUtilities.cpp @@ -41,7 +41,7 @@ struct FNamedAESKey struct FKeyChain { - FRSA::TKeyPtr SigningKey; + FRSAKeyHandle SigningKey = InvalidRSAKeyHandle; TMap EncryptionKeys; const FNamedAESKey* MasterEncryptionKey = nullptr; }; @@ -1258,7 +1258,7 @@ TEncryptionInt ParseEncryptionIntFromJson(TSharedPtr InObj, const T } } -FRSA::TKeyPtr ParseRSAKeyFromJson(TSharedPtr InObj) +FRSAKeyHandle ParseRSAKeyFromJson(TSharedPtr InObj) { TSharedPtr PublicKey = InObj->GetObjectField(TEXT("PublicKey")); TSharedPtr PrivateKey = InObj->GetObjectField(TEXT("PrivateKey")); @@ -1347,7 +1347,7 @@ void LoadKeyChainFromFile(const FString& InFilename, FKeyChain& OutCryptoSetting void LoadKeyChain(const TCHAR* CmdLine, FKeyChain& OutCryptoSettings) { - OutCryptoSettings.SigningKey.Reset(); + OutCryptoSettings.SigningKey = InvalidRSAKeyHandle; OutCryptoSettings.EncryptionKeys.Empty(); // First, try and parse the keys from a supplied crypto key cache file @@ -1602,7 +1602,7 @@ bool CreatePakFile(const TCHAR* Filename, TArray& FilesToAdd, con if (InKeyChain.MasterEncryptionKey) { - UE_LOG(LogPakFile, Display, TEXT("Encrypting using key '%s' [%s]"), *InKeyChain.MasterEncryptionKey->Name, *InKeyChain.MasterEncryptionKey->Guid.ToString()); + UE_LOG(LogPakFile, Display, TEXT("Using encryption key '%s' [%s]"), *InKeyChain.MasterEncryptionKey->Name, *InKeyChain.MasterEncryptionKey->Guid.ToString()); } TArray Index; @@ -3824,7 +3824,7 @@ bool Repack(const FString& InputPakFile, const FString& OutputPakFile, const FPa FString TempOutputPakFile = FPaths::CreateTempFilename(*FPaths::GetPath(OutputPakFile), *FPaths::GetCleanFilename(OutputPakFile)); FPakCommandLineParameters ModifiedCmdLineParameters = CmdLineParameters; - ModifiedCmdLineParameters.bSign = bAnySigned && InKeyChain.SigningKey.IsValid(); + ModifiedCmdLineParameters.bSign = bAnySigned && (InKeyChain.SigningKey != InvalidRSAKeyHandle); FKeyChain ModifiedKeyChain = InKeyChain; ModifiedKeyChain.MasterEncryptionKey = InKeyChain.EncryptionKeys.Find(EncryptionKeys.Num() ? EncryptionKeys[0] : FGuid()); diff --git a/Engine/Source/Developer/PakFileUtilities/Private/SignedArchiveWriter.cpp b/Engine/Source/Developer/PakFileUtilities/Private/SignedArchiveWriter.cpp index 3d18994c901e..c7e3ccfe0d57 100644 --- a/Engine/Source/Developer/PakFileUtilities/Private/SignedArchiveWriter.cpp +++ b/Engine/Source/Developer/PakFileUtilities/Private/SignedArchiveWriter.cpp @@ -5,7 +5,7 @@ #include "Misc/SecureHash.h" #include "HAL/FileManager.h" -FSignedArchiveWriter::FSignedArchiveWriter(FArchive& InPak, const FString& InPakFilename, FRSA::TKeyPtr InSigningKey) +FSignedArchiveWriter::FSignedArchiveWriter(FArchive& InPak, const FString& InPakFilename, const FRSAKeyHandle InSigningKey) : BufferArchive(Buffer) , PakWriter(InPak) , PakSignaturesFilename(FPaths::ChangeExtension(InPakFilename, TEXT("sig"))) diff --git a/Engine/Source/Developer/PakFileUtilities/Private/SignedArchiveWriter.h b/Engine/Source/Developer/PakFileUtilities/Private/SignedArchiveWriter.h index 278402225d45..4e57219c0807 100644 --- a/Engine/Source/Developer/PakFileUtilities/Private/SignedArchiveWriter.h +++ b/Engine/Source/Developer/PakFileUtilities/Private/SignedArchiveWriter.h @@ -25,7 +25,7 @@ class FSignedArchiveWriter : public FArchive /** Data size (excluding signatures) */ int64 PakSize; /** Signing key */ - FRSA::TKeyPtr SigningKey; + const FRSAKeyHandle SigningKey; /** Hashes */ TArray ChunkHashes; @@ -36,7 +36,7 @@ class FSignedArchiveWriter : public FArchive public: - FSignedArchiveWriter(FArchive& InPak, const FString& InPakFilename, FRSA::TKeyPtr InSigningKey); + FSignedArchiveWriter(FArchive& InPak, const FString& InPakFilename, const FRSAKeyHandle InSigningKey); virtual ~FSignedArchiveWriter(); // FArchive interface diff --git a/Engine/Source/Developer/Profiler/Private/ProfilerCommands.cpp b/Engine/Source/Developer/Profiler/Private/ProfilerCommands.cpp index 483e90c32c76..a6db9144d46e 100644 --- a/Engine/Source/Developer/Profiler/Private/ProfilerCommands.cpp +++ b/Engine/Source/Developer/Profiler/Private/ProfilerCommands.cpp @@ -103,7 +103,7 @@ void FProfilerActionManager::Map_ToggleDataPreview_Global() const FUIAction FProfilerActionManager::ToggleDataPreview_Custom( const FGuid SessionInstanceID ) const { FUIAction UIAction; - UIAction.ExecuteAction = FExecuteAction::CreateRaw( this, &FProfilerActionManager::ToggleDataPreview_Execute, SessionInstanceID ); + UIAction.ExecuteAction = FExecuteAction::CreateRaw( const_cast(this), &FProfilerActionManager::ToggleDataPreview_Execute, SessionInstanceID ); UIAction.CanExecuteAction = FCanExecuteAction::CreateRaw( this, &FProfilerActionManager::ToggleDataPreview_CanExecute, SessionInstanceID ); UIAction.GetActionCheckState = FGetActionCheckState::CreateRaw( this, &FProfilerActionManager::ToggleDataPreview_GetCheckState, SessionInstanceID ); return UIAction; @@ -284,7 +284,7 @@ void FProfilerActionManager::Map_ToggleDataCapture_Global() const FUIAction FProfilerActionManager::ToggleDataCapture_Custom( const FGuid SessionInstanceID ) const { FUIAction UIAction; - UIAction.ExecuteAction = FExecuteAction::CreateRaw( this, &FProfilerActionManager::ToggleDataCapture_Execute, SessionInstanceID ); + UIAction.ExecuteAction = FExecuteAction::CreateRaw( const_cast(this), &FProfilerActionManager::ToggleDataCapture_Execute, SessionInstanceID ); UIAction.CanExecuteAction = FCanExecuteAction::CreateRaw( this, &FProfilerActionManager::ToggleDataCapture_CanExecute, SessionInstanceID ); UIAction.GetActionCheckState = FGetActionCheckState::CreateRaw( this, &FProfilerActionManager::ToggleDataCapture_GetCheckState, SessionInstanceID ); return UIAction; @@ -302,7 +302,7 @@ void FProfilerActionManager::ToggleDataCapture_Execute( const FGuid SessionInsta EAppReturnType::Type Result = FPlatformMisc::MessageBoxExt ( EAppMsgType::YesNo, - *LOCTEXT("TransferServiceSideCaptureQuestion", "Would like to transfer the captured stats file(s) to this machine? This may take some time.").ToString(), + *LOCTEXT("TransferServiceSideCaptureQuestion", "Would you like to transfer the captured stats file(s) to this machine? This may take some time.").ToString(), *LOCTEXT("Question", "Question").ToString() ); @@ -338,7 +338,7 @@ void FProfilerActionManager::Map_OpenSettings_Global() const FUIAction FProfilerActionManager::OpenSettings_Custom() const { FUIAction UIAction; - UIAction.ExecuteAction = FExecuteAction::CreateRaw( this, &FProfilerActionManager::OpenSettings_Execute ); + UIAction.ExecuteAction = FExecuteAction::CreateRaw( const_cast(this), &FProfilerActionManager::OpenSettings_Execute ); UIAction.CanExecuteAction = FCanExecuteAction::CreateRaw( this, &FProfilerActionManager::OpenSettings_CanExecute ); return UIAction; } diff --git a/Engine/Source/Developer/Profiler/Private/ProfilerRawStatsForMemory.cpp b/Engine/Source/Developer/Profiler/Private/ProfilerRawStatsForMemory.cpp index 407f9a799c99..e4e0651c430b 100644 --- a/Engine/Source/Developer/Profiler/Private/ProfilerRawStatsForMemory.cpp +++ b/Engine/Source/Developer/Profiler/Private/ProfilerRawStatsForMemory.cpp @@ -61,7 +61,7 @@ struct FStatsCallstack FString Result; for (const auto& Name : Callstack) { - Result += TTypeToString::ToString( (int32)Name.GetComparisonIndex() ); + Result += TTypeToString::ToString(Name.GetComparisonIndex().ToUnstableInt() ); Result += CallstackSeparator; } return Result; @@ -76,9 +76,10 @@ struct FStatsCallstack // Convert back to FNames for (const auto& It : DecodedCallstack) { - NAME_INDEX NameIndex = 0; - TTypeFromString::FromString( NameIndex, *It ); - const FName LongName = FName( NameIndex, NameIndex, 0 ); + uint32 NameInt = 0; + TTypeFromString::FromString(NameInt, *It ); + FNameEntryId Id = FNameEntryId::FromUnstableInt(NameInt); + const FName LongName = FName(Id, Id, 0 ); out_DecodedCallstack.Add( LongName ); } diff --git a/Engine/Source/Developer/Profiler/Private/ProfilerRawStatsForThreadView.cpp b/Engine/Source/Developer/Profiler/Private/ProfilerRawStatsForThreadView.cpp index ebd3bfe27a95..0f97e763759a 100644 --- a/Engine/Source/Developer/Profiler/Private/ProfilerRawStatsForThreadView.cpp +++ b/Engine/Source/Developer/Profiler/Private/ProfilerRawStatsForThreadView.cpp @@ -169,11 +169,6 @@ void FRawProfilerSession::PrepareLoading() Stream.ReadFNamesAndMetadataMessages( *FileReader, MetadataMessages ); StatsThreadStats.ProcessMetaDataOnly( MetadataMessages ); - const FName F00245 = FName(245, 245, 0); - - const FName F11602 = FName(11602, 11602, 0); - const FName F06394 = FName(6394, 6394, 0); - const int64 CurrentFilePos = FileReader->Tell(); // Update profiler's metadata. diff --git a/Engine/Source/Developer/Profiler/Private/Widgets/SEventGraph.cpp b/Engine/Source/Developer/Profiler/Private/Widgets/SEventGraph.cpp index b41d4de24638..5e8644a4dae0 100644 --- a/Engine/Source/Developer/Profiler/Private/Widgets/SEventGraph.cpp +++ b/Engine/Source/Developer/Profiler/Private/Widgets/SEventGraph.cpp @@ -1051,7 +1051,7 @@ void SEventGraph::FillThreadFilterOptions() // Sort the thread names alphabetically ThreadNamesForCombo.Sort([]( const TSharedPtr Lhs, const TSharedPtr Rhs ) { - return Lhs->IsNone() || ( !Rhs->IsNone() && *Lhs < *Rhs ); + return Lhs->IsNone() || ( !Rhs->IsNone() && Lhs->LexicalLess(*Rhs) ); }); // Refresh the combo box @@ -1735,7 +1735,7 @@ TSharedPtr SEventGraph::EventGraph_GetMenuContent() const FUIAction Action_ExpandHotPath ( - FExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_ExpandHotPath_Execute ), + FExecuteAction::CreateSP( const_cast(this), &SEventGraph::ContextMenu_ExpandHotPath_Execute ), FCanExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_ExpandHotPath_CanExecute ) ); MenuBuilder.AddMenuEntry @@ -1812,7 +1812,7 @@ TSharedPtr SEventGraph::EventGraph_GetMenuContent() const { FUIAction Action_CopySelectedToClipboard ( - FExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_CopySelectedToClipboard_Execute ), + FExecuteAction::CreateSP( const_cast(this), &SEventGraph::ContextMenu_CopySelectedToClipboard_Execute ), FCanExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_CopySelectedToClipboard_CanExecute ) ); MenuBuilder.AddMenuEntry @@ -1826,7 +1826,7 @@ TSharedPtr SEventGraph::EventGraph_GetMenuContent() const FUIAction Action_SelectStack ( - FExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_SelectStack_Execute ), + FExecuteAction::CreateSP( const_cast(this), &SEventGraph::ContextMenu_SelectStack_Execute ), FCanExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_SelectStack_CanExecute ) ); MenuBuilder.AddMenuEntry @@ -1840,7 +1840,7 @@ TSharedPtr SEventGraph::EventGraph_GetMenuContent() const ( LOCTEXT("ContextMenu_Header_Misc_Sort", "Sort By"), LOCTEXT("ContextMenu_Header_Misc_Sort_Desc", "Sort by column"), - FNewMenuDelegate::CreateSP( this, &SEventGraph::EventGraph_BuildSortByMenu ), + FNewMenuDelegate::CreateSP( const_cast(this), &SEventGraph::EventGraph_BuildSortByMenu ), false, FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortBy") ); @@ -1853,14 +1853,14 @@ TSharedPtr SEventGraph::EventGraph_GetMenuContent() const ( LOCTEXT("ContextMenu_Header_Columns_View", "View Column"), LOCTEXT("ContextMenu_Header_Columns_View_Desc", "Hides or shows columns"), - FNewMenuDelegate::CreateSP( this, &SEventGraph::EventGraph_BuildViewColumnMenu ), + FNewMenuDelegate::CreateSP( const_cast(this), &SEventGraph::EventGraph_BuildViewColumnMenu ), false, FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ViewColumn") ); FUIAction Action_ResetColumns ( - FExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_ResetColumns_Execute ), + FExecuteAction::CreateSP( const_cast(this), &SEventGraph::ContextMenu_ResetColumns_Execute ), FCanExecuteAction::CreateSP( this, &SEventGraph::ContextMenu_ResetColumns_CanExecute ) ); MenuBuilder.AddMenuEntry diff --git a/Engine/Source/Developer/Profiler/Private/Widgets/SEventGraph.h b/Engine/Source/Developer/Profiler/Private/Widgets/SEventGraph.h index 1006f4e86676..89175446d34a 100644 --- a/Engine/Source/Developer/Profiler/Private/Widgets/SEventGraph.h +++ b/Engine/Source/Developer/Profiler/Private/Widgets/SEventGraph.h @@ -318,7 +318,7 @@ public: const FUIAction SelectAllFrames_Custom() const { FUIAction UIAction; - UIAction.ExecuteAction = FExecuteAction::CreateSP( this, &SEventGraph::SelectAllFrames_Execute ); + UIAction.ExecuteAction = FExecuteAction::CreateSP( const_cast(this), &SEventGraph::SelectAllFrames_Execute ); UIAction.CanExecuteAction = FCanExecuteAction::CreateSP( this, &SEventGraph::SelectAllFrames_CanExecute ); return UIAction; } @@ -384,7 +384,7 @@ public: const FUIAction SetRoot_Custom() const { FUIAction UIAction; - UIAction.ExecuteAction = FExecuteAction::CreateSP( this, &SEventGraph::SetRoot_Execute ); + UIAction.ExecuteAction = FExecuteAction::CreateSP( const_cast(this), &SEventGraph::SetRoot_Execute ); UIAction.CanExecuteAction = FCanExecuteAction::CreateSP( this, &SEventGraph::SetRoot_CanExecute ); return UIAction; } @@ -405,7 +405,7 @@ public: const FUIAction ClearHistory_Custom() const { FUIAction UIAction; - UIAction.ExecuteAction = FExecuteAction::CreateSP( this, &SEventGraph::ClearHistory_Execute ); + UIAction.ExecuteAction = FExecuteAction::CreateSP( const_cast(this), &SEventGraph::ClearHistory_Execute ); UIAction.CanExecuteAction = FCanExecuteAction::CreateSP( this, &SEventGraph::ClearHistory_CanExecute ); return UIAction; } @@ -425,7 +425,7 @@ public: const FUIAction ShowSelectedEventsInViewMode_Custom( EEventGraphViewModes::Type NewViewMode ) const { FUIAction UIAction; - UIAction.ExecuteAction = FExecuteAction::CreateSP( this, &SEventGraph::ShowSelectedEventsInViewMode_Execute, NewViewMode ); + UIAction.ExecuteAction = FExecuteAction::CreateSP( const_cast(this), &SEventGraph::ShowSelectedEventsInViewMode_Execute, NewViewMode ); UIAction.CanExecuteAction = FCanExecuteAction::CreateSP( this, &SEventGraph::ShowSelectedEventsInViewMode_CanExecute, NewViewMode ); UIAction.GetActionCheckState = FGetActionCheckState::CreateSP( this, &SEventGraph::ShowSelectedEventsInViewMode_GetCheckState, NewViewMode ); return UIAction; @@ -448,7 +448,7 @@ public: const FUIAction FilterOutByProperty_Custom( const FEventGraphSamplePtr EventPtr, const FName PropertyName, const bool bReset ) const { FUIAction UIAction; - UIAction.ExecuteAction = FExecuteAction::CreateSP( this, &SEventGraph::FilterOutByProperty_Execute, EventPtr, PropertyName, bReset ); + UIAction.ExecuteAction = FExecuteAction::CreateSP( const_cast(this), &SEventGraph::FilterOutByProperty_Execute, EventPtr, PropertyName, bReset ); UIAction.CanExecuteAction = FCanExecuteAction::CreateSP( this, &SEventGraph::FilterOutByProperty_CanExecute, EventPtr, PropertyName, bReset ); return UIAction; } @@ -469,7 +469,7 @@ public: const FUIAction CullByProperty_Custom( const FEventGraphSamplePtr EventPtr, const FName PropertyName, const bool bReset ) const { FUIAction UIAction; - UIAction.ExecuteAction = FExecuteAction::CreateSP( this, &SEventGraph::CullByProperty_Execute, EventPtr, PropertyName, bReset ); + UIAction.ExecuteAction = FExecuteAction::CreateSP( const_cast(this), &SEventGraph::CullByProperty_Execute, EventPtr, PropertyName, bReset ); UIAction.CanExecuteAction = FCanExecuteAction::CreateSP( this, &SEventGraph::CullByProperty_CanExecute, EventPtr, PropertyName, bReset ); return UIAction; } @@ -489,7 +489,7 @@ public: const FUIAction HistoryList_GoTo_Custom( int32 StateIndex ) const { FUIAction UIAction; - UIAction.ExecuteAction = FExecuteAction::CreateSP( this, &SEventGraph::HistoryList_GoTo_Execute, StateIndex ); + UIAction.ExecuteAction = FExecuteAction::CreateSP( const_cast(this), &SEventGraph::HistoryList_GoTo_Execute, StateIndex ); UIAction.CanExecuteAction = FCanExecuteAction(); UIAction.GetActionCheckState = FGetActionCheckState::CreateSP( this, &SEventGraph::HistoryList_GoTo_GetCheckState, StateIndex ); return UIAction; @@ -522,7 +522,7 @@ public: const FUIAction SetExpansionForEvents_Custom( const ESelectedEventTypes::Type SelectedEventType, bool bShouldExpand ) const { FUIAction UIAction; - UIAction.ExecuteAction = FExecuteAction::CreateSP( this, &SEventGraph::SetExpansionForEvents_Execute, SelectedEventType, bShouldExpand ); + UIAction.ExecuteAction = FExecuteAction::CreateSP( const_cast(this), &SEventGraph::SetExpansionForEvents_Execute, SelectedEventType, bShouldExpand ); UIAction.CanExecuteAction = FCanExecuteAction::CreateSP( this, &SEventGraph::SetExpansionForEvents_CanExecute, SelectedEventType, bShouldExpand ); return UIAction; } diff --git a/Engine/Source/Developer/Profiler/Private/Widgets/SFiltersAndPresets.h b/Engine/Source/Developer/Profiler/Private/Widgets/SFiltersAndPresets.h index 8e9364fb2e84..6930ca365bc4 100644 --- a/Engine/Source/Developer/Profiler/Private/Widgets/SFiltersAndPresets.h +++ b/Engine/Source/Developer/Profiler/Private/Widgets/SFiltersAndPresets.h @@ -241,7 +241,7 @@ struct FGroupAndStatSorting { FORCEINLINE_DEBUGGABLE bool operator()( const FGroupOrStatNodePtr& A, const FGroupOrStatNodePtr& B ) const { - return A->GetName() < B->GetName(); + return A->GetName().LexicalLess(B->GetName()); } }; @@ -250,7 +250,7 @@ struct FGroupAndStatSorting { FORCEINLINE_DEBUGGABLE bool operator()( const FGroupOrStatNodePtr& A, const FGroupOrStatNodePtr& B ) const { - return A->GetGroupName() < B->GetGroupName(); + return A->GetGroupName().LexicalLess(B->GetGroupName()); } }; @@ -265,7 +265,7 @@ struct FGroupAndStatSorting if( TypeA == TypeB ) { // Sort by stat name. - return A->GetName() < B->GetName(); + return A->GetName().LexicalLess(B->GetName()); } else { diff --git a/Engine/Source/Developer/Profiler/Private/Widgets/SProfilerGraphPanel.cpp b/Engine/Source/Developer/Profiler/Private/Widgets/SProfilerGraphPanel.cpp index 283eeb2b1ea4..72f222e465d7 100644 --- a/Engine/Source/Developer/Profiler/Private/Widgets/SProfilerGraphPanel.cpp +++ b/Engine/Source/Developer/Profiler/Private/Widgets/SProfilerGraphPanel.cpp @@ -82,7 +82,7 @@ void SProfilerGraphPanel::Construct(const FArguments& InArgs) .Orientation(Orient_Horizontal) .AlwaysShowScrollbar(true) .Visibility(EVisibility::Visible) - .Thickness(FVector2D(8.0f, 8.0f)) + .Thickness(FVector2D(12.0f, 12.0f)) .OnUserScrolled(this, &SProfilerGraphPanel::HorizontalScrollBar_OnUserScrolled) ] ] @@ -94,7 +94,7 @@ void SProfilerGraphPanel::Construct(const FArguments& InArgs) .Orientation(Orient_Vertical) .AlwaysShowScrollbar(true) .Visibility(EVisibility::Visible) - .Thickness(FVector2D(8.0f, 8.0f)) + .Thickness(FVector2D(12.0f, 12.0f)) .OnUserScrolled(this, &SProfilerGraphPanel::VerticalScrollBar_OnUserScrolled) ] ] diff --git a/Engine/Source/Developer/SessionFrontend/Private/Widgets/SSessionFrontend.cpp b/Engine/Source/Developer/SessionFrontend/Private/Widgets/SSessionFrontend.cpp index 349b378361ea..48abd08e7602 100644 --- a/Engine/Source/Developer/SessionFrontend/Private/Widgets/SSessionFrontend.cpp +++ b/Engine/Source/Developer/SessionFrontend/Private/Widgets/SSessionFrontend.cpp @@ -192,14 +192,14 @@ TSharedRef SSessionFrontend::HandleTabManagerSpawnTab( const FSpawnTab IAutomationControllerManagerPtr AutomationController = AutomationControllerModule.GetAutomationController(); IAutomationWindowModule& AutomationWindowModule = FModuleManager::LoadModuleChecked("AutomationWindow"); - AutomationController->OnShutdown().AddSP(this, &SSessionFrontend::HandleAutomationModuleShutdown); + AutomationController->OnShutdown().AddSP(const_cast(this), &SSessionFrontend::HandleAutomationModuleShutdown); TabWidget = AutomationWindowModule.CreateAutomationWindow( AutomationController.ToSharedRef(), SessionManager.ToSharedRef() ); - AutomationWindowModule.OnShutdown().BindSP(this, &SSessionFrontend::HandleAutomationModuleShutdown); + AutomationWindowModule.OnShutdown().BindSP(const_cast(this), &SSessionFrontend::HandleAutomationModuleShutdown); } else if (TabIdentifier == ProfilerTabId) { diff --git a/Engine/Source/Developer/ShaderCompilerCommon/Private/HlslUtils.h b/Engine/Source/Developer/ShaderCompilerCommon/Private/HlslUtils.h index 3fd703badb1c..6cebc83bde3d 100644 --- a/Engine/Source/Developer/ShaderCompilerCommon/Private/HlslUtils.h +++ b/Engine/Source/Developer/ShaderCompilerCommon/Private/HlslUtils.h @@ -109,6 +109,8 @@ namespace CrossCompiler class FLinearAllocatorPolicy { public: + using SizeType = int32; + // Unreal allocator magic enum { NeedsElementType = false }; enum { RequireRangeCheck = true }; @@ -129,7 +131,7 @@ namespace CrossCompiler { return Data; } - void ResizeAllocation(int32 PreviousNumElements, int32 NumElements, int32 NumBytesPerElement) + void ResizeAllocation(SizeType PreviousNumElements, SizeType NumElements, SIZE_T NumBytesPerElement) { void* OldData = Data; if (NumElements) @@ -142,30 +144,30 @@ namespace CrossCompiler // If the container previously held elements, copy them into the new allocation. if (OldData && PreviousNumElements) { - const int32 NumCopiedElements = FMath::Min(NumElements, PreviousNumElements); + const SizeType NumCopiedElements = FMath::Min(NumElements, PreviousNumElements); FMemory::Memcpy(Data, OldData, NumCopiedElements * NumBytesPerElement); } } } - int32 CalculateSlackReserve(int32 NumElements, int32 NumBytesPerElement) const + SizeType CalculateSlackReserve(SizeType NumElements, SIZE_T NumBytesPerElement) const { return DefaultCalculateSlackReserve(NumElements, NumBytesPerElement, false); } - int32 CalculateSlackShrink(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const + SizeType CalculateSlackShrink(SizeType NumElements, SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { return DefaultCalculateSlackShrink(NumElements, NumAllocatedElements, NumBytesPerElement, false); } - int32 CalculateSlackGrow(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const + SizeType CalculateSlackGrow(SizeType NumElements, SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { return DefaultCalculateSlackGrow(NumElements, NumAllocatedElements, NumBytesPerElement, false); } - int32 GetAllocatedSize(int32 NumAllocatedElements, int32 NumBytesPerElement) const + SIZE_T GetAllocatedSize(SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { return NumAllocatedElements * NumBytesPerElement; } - bool HasAllocation() + bool HasAllocation() const { return !!Data; } diff --git a/Engine/Source/Developer/ShaderFormatOpenGL/Private/OpenGLShaderCompiler.cpp b/Engine/Source/Developer/ShaderFormatOpenGL/Private/OpenGLShaderCompiler.cpp index 2c2fe5f0bbd9..e53cb8b280b0 100644 --- a/Engine/Source/Developer/ShaderFormatOpenGL/Private/OpenGLShaderCompiler.cpp +++ b/Engine/Source/Developer/ShaderFormatOpenGL/Private/OpenGLShaderCompiler.cpp @@ -29,12 +29,9 @@ #include #include "Windows/HideWindowsPlatformTypes.h" #elif PLATFORM_LINUX - #define GL_GLEXT_PROTOTYPES 1 #include #include #include "SDL.h" - GLAPI GLuint APIENTRY glCreateShader (GLenum type); - GLAPI void APIENTRY glShaderSource (GLuint shader, GLsizei count, const GLchar* const *string, const GLint *length); typedef SDL_Window* SDL_HWindow; typedef SDL_GLContext SDL_HGLContext; struct FPlatformOpenGLContext @@ -281,6 +278,27 @@ static void PlatformReleaseOpenGL(void* ContextPtr, void* PrevContextPtr) wglMakeCurrent((HDC)ContextPtr, (HGLRC)PrevContextPtr); } #elif PLATFORM_LINUX +/** List all OpenGL entry points needed for shader compilation. */ +#define ENUM_GL_ENTRYPOINTS(EnumMacro) \ + EnumMacro(PFNGLCOMPILESHADERPROC,glCompileShader) \ + EnumMacro(PFNGLCREATESHADERPROC,glCreateShader) \ + EnumMacro(PFNGLDELETESHADERPROC,glDeleteShader) \ + EnumMacro(PFNGLGETSHADERIVPROC,glGetShaderiv) \ + EnumMacro(PFNGLGETSHADERINFOLOGPROC,glGetShaderInfoLog) \ + EnumMacro(PFNGLSHADERSOURCEPROC,glShaderSource) \ + EnumMacro(PFNGLDELETEBUFFERSPROC,glDeleteBuffers) + +/** Define all GL functions. */ +// We need to make pointer names different from GL functions otherwise we may end up getting +// addresses of those symbols when looking for extensions. +namespace GLFuncPointers +{ + #define DEFINE_GL_ENTRYPOINTS(Type,Func) static Type Func = NULL; + ENUM_GL_ENTRYPOINTS(DEFINE_GL_ENTRYPOINTS); +}; + +using namespace GLFuncPointers; + static void _PlatformCreateDummyGLWindow(FPlatformOpenGLContext *OutContext) { static bool bInitializedWindowClass = false; @@ -332,6 +350,19 @@ static void PlatformInitOpenGL(void*& ContextPtr, void*& PrevContextPtr, int InM UE_LOG(LogOpenGLShaderCompiler, Fatal, TEXT("Unable to dynamically load libGL: %s"), ANSI_TO_TCHAR(SDL_GetError())); } + if (glCreateShader == nullptr) + { + // Initialize all entry points. + #define GET_GL_ENTRYPOINTS(Type,Func) GLFuncPointers::Func = reinterpret_cast(SDL_GL_GetProcAddress(#Func)); + ENUM_GL_ENTRYPOINTS(GET_GL_ENTRYPOINTS); + + // Check that all of the entry points have been initialized. + bool bFoundAllEntryPoints = true; + #define CHECK_GL_ENTRYPOINTS(Type,Func) if (Func == nullptr) { bFoundAllEntryPoints = false; UE_LOG(LogOpenGLShaderCompiler, Warning, TEXT("Failed to find entry point for %s"), TEXT(#Func)); } + ENUM_GL_ENTRYPOINTS(CHECK_GL_ENTRYPOINTS); + checkf(bFoundAllEntryPoints, TEXT("Failed to find all OpenGL entry points.")); + } + if (SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, InMajorVersion)) { UE_LOG(LogOpenGLShaderCompiler, Fatal, TEXT("Failed to set GL major version: %s"), ANSI_TO_TCHAR(SDL_GetError())); diff --git a/Engine/Source/Developer/SlateFileDialogs/Private/SlateFileDlgWindow.cpp b/Engine/Source/Developer/SlateFileDialogs/Private/SlateFileDlgWindow.cpp index beb246ebc09c..f8ed0f18043e 100644 --- a/Engine/Source/Developer/SlateFileDialogs/Private/SlateFileDlgWindow.cpp +++ b/Engine/Source/Developer/SlateFileDialogs/Private/SlateFileDlgWindow.cpp @@ -755,7 +755,7 @@ void SSlateFileOpenDlg::Construct(const FArguments& InArgs) .Padding(FMargin(0.0f)) [ SNew(STextBlock) - .Text(LOCTEXT("FilterLabel", "Filter:")) + .Text(bSaveFile ? LOCTEXT("SaveTypeLabel", "Save as type:") : LOCTEXT("FilterLabel", "Filter:")) .Font(StyleSet->GetFontStyle("SlateFileDialogs.Dialog")) .Justification(ETextJustify::Left) ] @@ -1020,7 +1020,7 @@ TSharedPtr SSlateFileOpenDlg::OnGetCrumbDelimiterContent(const FString& FText::FromString(Drive), FText::GetEmpty(), FSlateIcon(), - FUIAction(FExecuteAction::CreateSP(this, &SSlateFileOpenDlg::OnPathMenuItemClicked, Drive + TEXT("/")))); + FUIAction(FExecuteAction::CreateSP(const_cast(this), &SSlateFileOpenDlg::OnPathMenuItemClicked, Drive + TEXT("/")))); } DrivesMask >>= 1; @@ -1051,7 +1051,7 @@ TSharedPtr SSlateFileOpenDlg::OnGetCrumbDelimiterContent(const FString& FText::FromString(SubDir), FText::GetEmpty(), FSlateIcon(), - FUIAction(FExecuteAction::CreateSP(this, &SSlateFileOpenDlg::OnPathMenuItemClicked, CrumbData + SubDir + TEXT("/")))); + FUIAction(FExecuteAction::CreateSP(const_cast(this), &SSlateFileOpenDlg::OnPathMenuItemClicked, CrumbData + SubDir + TEXT("/")))); } Widget = @@ -1304,7 +1304,10 @@ void SSlateFileOpenDlg::OnItemDoubleClicked(TSharedPtr Item) { if (Item->bIsDirectory) { - SetDefaultFile(FString("")); + if (!bSaveFile) + { + SetDefaultFile(FString("")); + } CurrentPath = CurrentPath + Item->Label + TEXT("/"); bNeedsBuilding = true; @@ -1392,6 +1395,19 @@ void SSlateFileOpenDlg::ParseTextField(TArray &FilenameArray, FString F } else { + FString Extension; + + // get current filter extension + if (!bDirectoriesOnly && GetFilterExtension(Extension)) + { + // append extension to filename if user left it off + if (!SaveFilename.EndsWith(Extension, ESearchCase::CaseSensitive) && + !IsWildcardExtension(Extension)) + { + Files = Files + Extension; + } + } + FilenameArray.Add(Files); } } @@ -1433,20 +1449,7 @@ void SSlateFileOpenDlg::OnFileNameCommitted(const FText& InText, ETextCommit::Ty // update edit box unless user choose to escape out if (InCommitType != ETextCommit::OnCleared) { - FString Extension; SaveFilename = InText.ToString(); - - // get current filter extension - if (!bDirectoriesOnly && GetFilterExtension(Extension)) - { - // append extension to filename if user left it off - if (!SaveFilename.EndsWith(Extension, ESearchCase::CaseSensitive) && - !IsWildcardExtension(Extension)) - { - SaveFilename = SaveFilename + Extension; - } - } - ListView->ClearSelection(); SetDefaultFile(SaveFilename); @@ -1540,6 +1543,12 @@ bool SSlateFileOpenDlg::GetFilterExtension(FString &OutString) return false; } + // We have attempted to get the filter extension before parsing them + if (FilterNameArray.Num() == 0) + { + ParseFilters(); + } + // make a copy of filter string that we can modify TCHAR Temp[MAX_FILTER_LENGTH] = {0}; FCString::Strcpy(Temp, ARRAY_COUNT(Temp), *(*FilterNameArray[FilterIndex].Get())); @@ -1645,7 +1654,10 @@ FReply SSlateFileOpenDlg::OnGoForwardClick() { if ((HistoryIndex+1) < History.Num()) { - SetDefaultFile(FString("")); + if (!bSaveFile) + { + SetDefaultFile(FString("")); + } HistoryIndex++; CurrentPath = History[HistoryIndex]; @@ -1662,7 +1674,10 @@ FReply SSlateFileOpenDlg::OnGoBackClick() { if (HistoryIndex > 0) { - SetDefaultFile(FString("")); + if (!bSaveFile) + { + SetDefaultFile(FString("")); + } HistoryIndex--; CurrentPath = History[HistoryIndex]; diff --git a/Engine/Source/Developer/SourceControl/Public/ISourceControlProvider.h b/Engine/Source/Developer/SourceControl/Public/ISourceControlProvider.h index e6f61985dfda..943227d7ec35 100644 --- a/Engine/Source/Developer/SourceControl/Public/ISourceControlProvider.h +++ b/Engine/Source/Developer/SourceControl/Public/ISourceControlProvider.h @@ -71,7 +71,7 @@ DECLARE_MULTICAST_DELEGATE(FSourceControlStateChanged); /** * Interface to talking with source control providers. */ -class ISourceControlProvider : public IModularFeature +class SOURCECONTROL_VTABLE ISourceControlProvider : public IModularFeature { public: /** diff --git a/Engine/Source/Developer/TargetPlatform/Public/Common/TargetPlatformBase.h b/Engine/Source/Developer/TargetPlatform/Public/Common/TargetPlatformBase.h index c11777683fd8..7458445bf01a 100644 --- a/Engine/Source/Developer/TargetPlatform/Public/Common/TargetPlatformBase.h +++ b/Engine/Source/Developer/TargetPlatform/Public/Common/TargetPlatformBase.h @@ -9,7 +9,7 @@ /** * Base class for target platforms. */ -class FTargetPlatformBase +class TARGETPLATFORM_VTABLE FTargetPlatformBase : public ITargetPlatform { public: diff --git a/Engine/Source/Developer/TraceAnalysis/Private/Analysis/Context.cpp b/Engine/Source/Developer/TraceAnalysis/Private/Analysis/Context.cpp new file mode 100644 index 000000000000..0c74f64c8a2c --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/Private/Analysis/Context.cpp @@ -0,0 +1,28 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Engine.h" +#include "Trace/Analysis.h" + +namespace Trace +{ + +//////////////////////////////////////////////////////////////////////////////// +void FAnalysisContext::AddAnalyzer(IAnalyzer& Analyzer) +{ + Analyzers.Add(&Analyzer); +} + +//////////////////////////////////////////////////////////////////////////////// +FAnalysisProcessor FAnalysisContext::Process() +{ + FAnalysisProcessor Processor; + + if (Analyzers.Num()) + { + Processor.Impl = new FAnalysisEngine(MoveTemp(Analyzers)); + } + + return MoveTemp(Processor); +} + +} // namespace Trace diff --git a/Engine/Source/Developer/TraceAnalysis/Private/Analysis/Engine.cpp b/Engine/Source/Developer/TraceAnalysis/Private/Analysis/Engine.cpp new file mode 100644 index 000000000000..ad016c8691f5 --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/Private/Analysis/Engine.cpp @@ -0,0 +1,612 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Engine.h" +#include "Containers/ArrayView.h" +#include "CoreGlobals.h" +#include "HAL/UnrealMemory.h" +#include "Trace/Analysis.h" +#include "Trace/Analyzer.h" +#if 0 +#include "Trace/Private/DataDecoder.h" +#endif // 0 +#include "Trace/Private/Event.h" + +namespace Trace +{ + +//////////////////////////////////////////////////////////////////////////////// +class FFnv1aHash +{ +public: + FFnv1aHash() = default; + FFnv1aHash(uint32 PrevResult) { Result = PrevResult; } + void Add(const ANSICHAR* String) { for (; *String; ++String) { Add(*String); } } + const uint8* Add(const uint8* Data, uint32 Size) { for (uint32 i = 0; i < Size; ++Data, ++i) { Add(*Data); } return Data; } + void Add(uint8 Value) { Result ^= Value; Result *= 0x01000193; } + uint32 Get() const { return Result; } + +private: + uint32 Result = 0x811c9dc5; + // uint32: bias=0x811c9dc5 prime=0x01000193 + // uint64: bias=0xcbf29ce484222325 prime=0x00000100000001b3; +}; + + + +//////////////////////////////////////////////////////////////////////////////// +class FTransportReader +{ +public: + void SetSource(FStreamReader::FData& InSource); + template + RetType const* GetPointer(); + template + RetType const* GetPointer(uint32 BlockSize); + virtual void Advance(uint32 BlockSize); + +protected: + virtual const uint8* GetPointerImpl(uint32 BlockSize); + FStreamReader::FData* Source; +}; + +//////////////////////////////////////////////////////////////////////////////// +void FTransportReader::SetSource(FStreamReader::FData& InSource) +{ + Source = &InSource; +} + +//////////////////////////////////////////////////////////////////////////////// +template +RetType const* FTransportReader::GetPointer() +{ + return GetPointer(sizeof(RetType)); +} + +//////////////////////////////////////////////////////////////////////////////// +template +RetType const* FTransportReader::GetPointer(uint32 BlockSize) +{ + return (RetType const*)GetPointerImpl(BlockSize); +} + +//////////////////////////////////////////////////////////////////////////////// +void FTransportReader::Advance(uint32 BlockSize) +{ + Source->Advance(BlockSize); +} + +//////////////////////////////////////////////////////////////////////////////// +const uint8* FTransportReader::GetPointerImpl(uint32 BlockSize) +{ + return Source->GetPointer(BlockSize); +} + + + +//////////////////////////////////////////////////////////////////////////////// +#if 0 +class FLz4TransportReader + : public FTransportReader +{ + enum : uint32 { MaxChunks = 4 }; + +public: + FLz4TransportReader(uint32 InChunkPow2); + virtual ~FLz4TransportReader(); + +private: + virtual void Advance(uint32 BlockSize) override; + virtual const uint8* GetPointerImpl(uint32 BlockSize) override; + bool NextChunk(); + FDataDecoder Decoder; + uint8* Buffer; + const uint8* Cursor; + const uint8* End; + uint32 Index = 0; + const uint32 ChunkPow2; +}; + +//////////////////////////////////////////////////////////////////////////////// +FLz4TransportReader::FLz4TransportReader(uint32 InChunkPow2) +: ChunkPow2(InChunkPow2) +{ + uint32 ChunkSize = 1 << ChunkPow2; + Buffer = new uint8[ChunkSize * (MaxChunks + 1)]; + Cursor = End = Buffer + (ChunkSize * 2); +} + +//////////////////////////////////////////////////////////////////////////////// +FLz4TransportReader::~FLz4TransportReader() +{ + delete[] Buffer; +} + +//////////////////////////////////////////////////////////////////////////////// +void FLz4TransportReader::Advance(uint32 BlockSize) +{ + Cursor += BlockSize; +} + +//////////////////////////////////////////////////////////////////////////////// +const uint8* FLz4TransportReader::GetPointerImpl(uint32 BlockSize) +{ + while (true) + { + uint32 Remaining = uint32(UPTRINT(End - Cursor)); + if (Remaining >= BlockSize) + { + break; + } + + if (!NextChunk()) + { + return nullptr; + } + } + + return Cursor; +} + +//////////////////////////////////////////////////////////////////////////////// +bool FLz4TransportReader::NextChunk() +{ + const struct { + uint32 Size; + uint8 Data[]; + }* Block; + + Block = decltype(Block)(Source->GetPointer(sizeof(*Block))); + if (Block == nullptr) + { + return false; + } + + int32 BlockSize = Block->Size; + if (BlockSize < 0) + { + BlockSize = ~BlockSize; + } + + Block = decltype(Block)(Source->GetPointer(BlockSize)); + if (Block == nullptr) + { + return false; + } + + Index = (Index + 1) & (MaxChunks - 1); + if (!Index) + { + uint32 Remaining = uint32(UPTRINT(End - Cursor)); + uint32 OverflowChunks = Remaining >> ChunkPow2; + if (OverflowChunks >= MaxChunks - 1) + { + return false; + } + + char* Dest = (char*)(Buffer + (1ull << ChunkPow2)); + Dest -= Remaining - OverflowChunks; + memcpy(Dest, Cursor, Remaining); + Cursor = (uint8*)Dest; + End = Cursor + Remaining; + } + + uint32 SrcSize = BlockSize - sizeof(*Block); + uint32 DestSize = 1 << ChunkPow2; + int DecodedSize = Decoder.Decode(Block->Data, End, SrcSize, DestSize); + if (DecodedSize < 0) + { + return false; + } + + End += DecodedSize; + + if (BlockSize != Block->Size) + { + Decoder.Reset(); + } + + Source->Advance(BlockSize); + return true; +} +#endif // 0 + + + +//////////////////////////////////////////////////////////////////////////////// +struct FAnalysisEngine::FEventDataImpl + : public FEventData +{ + virtual ~FEventDataImpl() = default; + virtual const FValue& GetValue(const ANSICHAR* FieldName) const override; + virtual const FArray& GetArray(const ANSICHAR* FieldName) const override; + virtual const uint8* GetData() const override; + virtual const uint8* GetAttachment() const override; + virtual uint16 GetAttachmentSize() const override; + virtual uint16 GetTotalSize() const override; + const FDispatch* Dispatch; + const uint8* Ptr; + uint16 Size; +}; + +//////////////////////////////////////////////////////////////////////////////// +const IAnalyzer::FValue& FAnalysisEngine::FEventDataImpl::GetValue(const ANSICHAR* FieldName) const +{ + UPTRINT Ret = ~0ull; + + FFnv1aHash Hash; + Hash.Add(FieldName); + uint32 NameHash = Hash.Get(); + + for (int i = 0, n = Dispatch->FieldCount; i < n; ++i) + { + const auto& Field = Dispatch->Fields[i]; + if (Field.Hash == NameHash) + { + Ret = UPTRINT(Ptr + Field.Offset); + Ret |= UPTRINT(Field.TypeInfo) << 48; + break; + } + } + + return *(FValue*)Ret; +} + +//////////////////////////////////////////////////////////////////////////////// +const IAnalyzer::FArray& FAnalysisEngine::FEventDataImpl::GetArray(const ANSICHAR* FieldName) const +{ + return *(FArray*)0x493; +} + +//////////////////////////////////////////////////////////////////////////////// +const uint8* FAnalysisEngine::FEventDataImpl::GetData() const +{ + return Ptr; +} + +//////////////////////////////////////////////////////////////////////////////// +const uint8* FAnalysisEngine::FEventDataImpl::GetAttachment() const +{ + return Ptr + Dispatch->EventSize; +} + +//////////////////////////////////////////////////////////////////////////////// +uint16 FAnalysisEngine::FEventDataImpl::GetAttachmentSize() const +{ + return Size - Dispatch->EventSize; +} + +//////////////////////////////////////////////////////////////////////////////// +uint16 FAnalysisEngine::FEventDataImpl::GetTotalSize() const +{ + return Size; +} + + + +//////////////////////////////////////////////////////////////////////////////// +enum ERouteId : uint16 +{ + RouteId_NewEvent, + RouteId_NewTrace, + RouteId_Timing, +}; + +//////////////////////////////////////////////////////////////////////////////// +FAnalysisEngine::FAnalysisEngine(TArray&& InAnalyzers) +: Analyzers(MoveTemp(InAnalyzers)) +{ + EventDataImpl = new FEventDataImpl(); + + uint16 SelfIndex = Analyzers.Num(); + Analyzers.Add(this); + + // Manually add event routing for known events, and those we don't quite know + // yet but are expecting. + FDispatch& NewEventDispatch = AddDispatch(uint16(FNewEventEvent::Uid), 0); + NewEventDispatch.FirstRoute = 0; + AddRoute(SelfIndex, RouteId_NewEvent, 0); + AddRoute(SelfIndex, RouteId_NewTrace, "$Trace", "NewTrace"); + AddRoute(SelfIndex, RouteId_Timing, "$Trace", "Timing"); +} + +//////////////////////////////////////////////////////////////////////////////// +FAnalysisEngine::~FAnalysisEngine() +{ + for (IAnalyzer* Analyzer : Analyzers) + { + Analyzer->OnAnalysisEnd(); + } + + for (FDispatch* Dispatch : Dispatches) + { + FMemory::Free(Dispatch); + } + + delete EventDataImpl; +} + +//////////////////////////////////////////////////////////////////////////////// +void FAnalysisEngine::OnAnalysisBegin(const FOnAnalysisContext& Context) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +void FAnalysisEngine::OnAnalysisEnd() +{ +} + +//////////////////////////////////////////////////////////////////////////////// +void FAnalysisEngine::OnEvent(uint16 RouteId, const FOnEventContext& Context) +{ + switch (RouteId) + { + case RouteId_NewEvent: return OnNewEvent(Context); + case RouteId_NewTrace: return OnNewTrace(Context); + case RouteId_Timing: return OnTiming(Context); + } +} + +//////////////////////////////////////////////////////////////////////////////// +void FAnalysisEngine::AddRoute( + uint16 AnalyzerIndex, + uint16 Id, + const ANSICHAR* Logger, + const ANSICHAR* Event) +{ + FFnv1aHash Hash; + Hash.Add(Logger); + Hash.Add(Event); + AddRoute(AnalyzerIndex, Id, Hash.Get()); +} + +//////////////////////////////////////////////////////////////////////////////// +void FAnalysisEngine::AddRoute( + uint16 AnalyzerIndex, + uint16 Id, + uint32 Hash) +{ + check(AnalyzerIndex < Analyzers.Num()); + + uint16 HashIndex = 0; + for (uint16 n = Hashes.Num(); HashIndex < n; ++HashIndex) + { + if (Hashes[HashIndex] == Hash) + { + break; + } + } + + if (HashIndex == Hashes.Num()) + { + Hashes.Add(Hash); + } + + FRoute& Route = Routes.Emplace_GetRef(); + Route.Id = Id; + Route.HashIndex = HashIndex; + Route.Count = 1; + Route.AnalyzerIndex = AnalyzerIndex; +} + +//////////////////////////////////////////////////////////////////////////////// +void FAnalysisEngine::OnNewTrace(const FOnEventContext& Context) +{ + struct : IAnalyzer::FInterfaceBuilder + { + virtual void RouteEvent(uint16 RouteId, const ANSICHAR* Logger, const ANSICHAR* Event) override + { + Self->AddRoute(AnalyzerIndex, RouteId, Logger, Event); + } + + FAnalysisEngine* Self; + uint16 AnalyzerIndex; + } Builder; + Builder.Self = this; + + FOnAnalysisContext OnAnalysisContext = { { SessionContext }, Builder }; + for (uint16 i = 0, n = Analyzers.Num(); i < n; ++i) + { + Builder.AnalyzerIndex = i; + Analyzers[i]->OnAnalysisBegin(OnAnalysisContext); + } + + Algo::SortBy(Routes, [] (const FRoute& Route) { return Route.HashIndex; }); + + FRoute* Cursor = Routes.GetData(); + Cursor->Count = 1; + + for (uint16 i = 1, n = Routes.Num(); i < n; ++i) + { + if (Routes[i].HashIndex == Cursor->HashIndex) + { + Cursor->Count++; + } + else + { + Cursor = Routes.GetData() + i; + Cursor->Count = 1; + } + } + + // Add a terminal route for events that aren't subscribed to + FRoute& Route = Routes.Emplace_GetRef(); + Route.HashIndex = Hashes.Num(); + Route.Count = 0; +} + +//////////////////////////////////////////////////////////////////////////////// +void FAnalysisEngine::OnTiming(const FOnEventContext& Context) +{ + SessionContext.StartCycle = Context.EventData.GetValue("StartCycle").As(); + SessionContext.CycleFrequency = Context.EventData.GetValue("CycleFrequency").As(); +} + +//////////////////////////////////////////////////////////////////////////////// +FAnalysisEngine::FDispatch& FAnalysisEngine::AddDispatch(uint16 Uid, uint16 FieldCount) +{ + // Allocate a block of memory to hold the dispatch + uint32 Size = sizeof(FDispatch) + (sizeof(FDispatch::FField) * FieldCount); + auto* Dispatch = (FDispatch*)FMemory::Malloc(Size); + Dispatch->FieldCount = FieldCount; + Dispatch->EventSize = 0; + Dispatch->FirstRoute = -1; + + // Add the new dispatch in the dispatch table + if (Uid >= Dispatches.Num()) + { + Dispatches.SetNum(Uid + 1); + } + check(Dispatches[Uid] == nullptr); + Dispatches[Uid] = Dispatch; + + return *Dispatch; +} + +//////////////////////////////////////////////////////////////////////////////// +void FAnalysisEngine::OnNewEvent(const FOnEventContext& Context) +{ + const auto& NewEvent = *(FNewEventEvent*)Context.EventData.GetData(); + + FDispatch& Dispatch = AddDispatch(NewEvent.EventUid, NewEvent.FieldCount); + + if (NewEvent.FieldCount) + { + auto& LastField = NewEvent.Fields[NewEvent.FieldCount - 1]; + Dispatch.EventSize = LastField.Offset + LastField.Size; + } + + const uint8* NameCursor = (uint8*)(NewEvent.Fields + NewEvent.FieldCount); + + // Calculate this dispatch's hash. + FFnv1aHash DispatchHash; + NameCursor = DispatchHash.Add(NameCursor, NewEvent.LoggerNameSize); + NameCursor = DispatchHash.Add(NameCursor, NewEvent.EventNameSize); + + // Fill out the fields + for (int i = 0, n = NewEvent.FieldCount; i < n; ++i) + { + const auto& In = NewEvent.Fields[i]; + auto& Out = Dispatch.Fields[i]; + + FFnv1aHash FieldHash; + NameCursor = FieldHash.Add(NameCursor, In.NameSize); + + Out.Hash = FieldHash.Get(); + Out.Offset = In.Offset; + Out.Size = In.Size; + Out.TypeInfo = In.TypeInfo; + } + + TArrayView Fields(Dispatch.Fields, Dispatch.FieldCount); + Algo::SortBy(Fields, [] (const auto& Field) { return Field.Hash; }); + + // Find routes that have subscribed to this event. + uint16 HashIndex = 0; + for (uint16 n = Hashes.Num(); HashIndex < n; ++HashIndex) + { + if (Hashes[HashIndex] == DispatchHash.Get()) + { + break; + } + } + + uint16 RouteIndex = 0; + for (uint16 n = Routes.Num(); RouteIndex < n; ++RouteIndex) + { + if (Routes[RouteIndex].HashIndex == HashIndex) + { + break; + } + } + + check(RouteIndex < Routes.Num()); + Dispatch.FirstRoute = RouteIndex; +} + +//////////////////////////////////////////////////////////////////////////////// +bool FAnalysisEngine::EstablishTransport(FStreamReader::FData& Data) +{ + const struct { + uint8 Format; + uint8 Parameter; + }* Header = decltype(Header)(Data.GetPointer(sizeof(*Header))); + if (Header == nullptr) + { + return false; + } + + Data.Advance(sizeof(*Header)); + + switch (Header->Format) + { + case 1: Transport = new FTransportReader(); break; +#if 0 + case 4: Transport = new FLz4TransportReader(Header->Parameter); break; +#endif // 0 + default: return false; + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +bool FAnalysisEngine::OnData(FStreamReader::FData& Data) +{ + if (Transport == nullptr) + { + if (!EstablishTransport(Data)) + { + return false; + } + } + + struct FEventHeader + { + uint16 Uid; + uint16 Size; + uint8 EventData[]; + }; + + Transport->SetSource(Data); + + while (true) + { + const auto* Header = Transport->GetPointer(); + if (Header == nullptr) + { + break; + } + + uint32 BlockSize = Header->Size + sizeof(FEventHeader); + Header = Transport->GetPointer(BlockSize); + if (Header == nullptr) + { + break; + } + + uint16 Uid = uint16(Header->Uid & ((1 << 14) - 1)); + if (Uid >= Dispatches.Num()) + { + return false; + } + + Transport->Advance(BlockSize); + + const FDispatch* Dispatch = Dispatches[Uid]; + + EventDataImpl->Dispatch = Dispatch; + EventDataImpl->Ptr = Header->EventData; + EventDataImpl->Size = Header->Size; + + const FRoute* Route = Routes.GetData() + Dispatch->FirstRoute; + for (uint32 n = Route->Count; n--; ++Route) + { + IAnalyzer* Analyzer = Analyzers[Route->AnalyzerIndex]; + Analyzer->OnEvent(Route->Id, { SessionContext, *EventDataImpl }); + } + } + + return true; +} + +} // namespace Trace diff --git a/Engine/Source/Developer/TraceAnalysis/Private/Analysis/Engine.h b/Engine/Source/Developer/TraceAnalysis/Private/Analysis/Engine.h new file mode 100644 index 000000000000..83595bb316fa --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/Private/Analysis/Engine.h @@ -0,0 +1,78 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Containers/Array.h" +#include "DataStream.h" +#include "Trace/Analysis.h" +#include "Trace/Analyzer.h" + +class IFileHandle; + +namespace Trace +{ + +//////////////////////////////////////////////////////////////////////////////// +struct FNewEventEvent; +class FTransportReader; + +//////////////////////////////////////////////////////////////////////////////// +class FAnalysisEngine + : public IAnalyzer +{ +public: + FAnalysisEngine(TArray&& InAnalyzers); + ~FAnalysisEngine(); + bool OnData(FStreamReader::FData& Data); + +private: + struct FDispatch + { + struct FField + { + uint32 Hash; + uint16 Offset; + uint16 Size; + uint16 _Unused0; + uint8 TypeInfo; + uint8 _Unused1; + }; + + uint16 FirstRoute; + uint16 FieldCount; + uint16 EventSize; + uint16 _Unused0; + FField Fields[]; + }; + + struct FRoute + { + uint16 HashIndex; + int16 Count; + uint16 Id; + uint16 AnalyzerIndex; + }; + + struct FEventDataImpl; + + virtual void OnAnalysisBegin(const FOnAnalysisContext& Context) override; + virtual void OnAnalysisEnd() override; + virtual void OnEvent(uint16 RouteId, const FOnEventContext& Context) override; + + bool EstablishTransport(FStreamReader::FData& Data); + FDispatch& AddDispatch(uint16 Uid, uint16 FieldCount); + void AddRoute(uint16 AnalyzerIndex, uint16 Id, const ANSICHAR* Logger, const ANSICHAR* Event); + void AddRoute(uint16 AnalyzerIndex, uint16 Id, uint32 Hash); + void OnNewTrace(const FOnEventContext& Context); + void OnTiming(const FOnEventContext& Context); + void OnNewEvent(const FOnEventContext& Context); + FSessionContext SessionContext; + TArray Hashes; + TArray Routes; + TArray Analyzers; + TArray Dispatches; + FTransportReader* Transport = nullptr; + FEventDataImpl* EventDataImpl; +}; + +} // namespace Trace diff --git a/Engine/Source/Developer/TraceAnalysis/Private/Analysis/Processor.cpp b/Engine/Source/Developer/TraceAnalysis/Private/Analysis/Processor.cpp new file mode 100644 index 000000000000..16290cc85924 --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/Private/Analysis/Processor.cpp @@ -0,0 +1,30 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Engine.h" +#include "Trace/Analysis.h" + +namespace Trace +{ + +//////////////////////////////////////////////////////////////////////////////// +void FAnalysisProcessor::Start(IInDataStream& DataStream) +{ + FAnalysisEngine* Engine = (FAnalysisEngine*)Impl; + if (Engine == nullptr) + { + return; + } + + FStreamReader Reader(DataStream); + while (FStreamReader::FData* Data = Reader.Read()) + { + if (!Engine->OnData(*Data)) + { + break; + } + } + delete Engine; + Engine = nullptr; +} + +} // namespace Trace diff --git a/Engine/Source/Developer/TraceAnalysis/Private/Common.h b/Engine/Source/Developer/TraceAnalysis/Private/Common.h new file mode 100644 index 000000000000..e32201442e34 --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/Private/Common.h @@ -0,0 +1,16 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if 0 +#if PLATFORM_WINDOWS +# include "Windows/AllowWindowsPlatformTypes.h" +# define _WINSOCK_DEPRECATED_NO_WARNINGS +# include +# include +# include "Windows/HideWindowsPlatformTypes.h" +# pragma comment(lib, "ws2_32.lib") +#endif + +#define TRACE_LOG(Format, ...) UE_LOG(LogCore, Log, TEXT(Format), __VA_ARGS__) +#endif // 0 diff --git a/Engine/Source/Developer/TraceAnalysis/Private/ControlClient.cpp b/Engine/Source/Developer/TraceAnalysis/Private/ControlClient.cpp new file mode 100644 index 000000000000..ae8d16c041ce --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/Private/ControlClient.cpp @@ -0,0 +1,164 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Trace/ControlClient.h" +#include "IPAddress.h" +#include "Misc/CString.h" +#include "Sockets.h" +#include "SocketSubsystem.h" +#include "AddressInfoTypes.h" + +namespace Trace +{ + +//////////////////////////////////////////////////////////////////////////////// +FControlClient::~FControlClient() +{ + Disconnect(); +} + +//////////////////////////////////////////////////////////////////////////////// +bool FControlClient::Connect(const TCHAR* Host, uint16 Port) +{ + if (IsConnected()) + { + return false; + } + + ISocketSubsystem* Sockets = ISocketSubsystem::Get(); + if (Sockets == nullptr) + { + return false; + } + + TSharedPtr Addr = Sockets->GetAddressFromString(Host); + if (!Addr.IsValid() || !Addr->IsValid()) + { + FAddressInfoResult GAIRequest = Sockets->GetAddressInfo(Host, nullptr, EAddressInfoFlags::Default, NAME_None); + if (GAIRequest.ReturnCode != SE_NO_ERROR || GAIRequest.Results.Num() == 0) + { + return false; + } + + Addr = GAIRequest.Results[0].Address; + } + Addr->SetPort(Port); + return Connect(*Addr); +} + +//////////////////////////////////////////////////////////////////////////////// +bool FControlClient::Connect(const FInternetAddr& Address) +{ + if (IsConnected()) + { + return false; + } + + ISocketSubsystem* Sockets = ISocketSubsystem::Get(); + if (Sockets == nullptr) + { + return false; + } + + FSocket* ClientSocket = Sockets->CreateSocket(NAME_Stream, TEXT("TraceControlClient"), Address.GetProtocolType()); + if (ClientSocket == nullptr) + { + return false; + } + + if (!ClientSocket->Connect(Address)) + { + Sockets->DestroySocket(ClientSocket); + return false; + } + + ClientSocket->SetLinger(); + + Socket = ClientSocket; + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +void FControlClient::Disconnect() +{ + if (!IsConnected()) + { + return; + } + + Socket->Shutdown(ESocketShutdownMode::ReadWrite); + Socket->Close(); + + ISocketSubsystem& Sockets = *(ISocketSubsystem::Get()); + Sockets.DestroySocket(Socket); + Socket = nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +bool FControlClient::IsConnected() const +{ + return (Socket != nullptr); +} + +//////////////////////////////////////////////////////////////////////////////// +void FControlClient::SendConnect(const TCHAR* Path) +{ + if (!IsConnected()) + { + return; + } + + FormatAndSend(TEXT("Connect %s"), Path); +} + +//////////////////////////////////////////////////////////////////////////////// +void FControlClient::SendToggleEvent(const TCHAR* EventMask, bool bState) +{ + FormatAndSend(TEXT("ToggleEvent %s %d"), EventMask, bState); +} + +//////////////////////////////////////////////////////////////////////////////// +void FControlClient::Send(const TCHAR* Command) +{ + int Length = FCString::Strlen(Command); + Send((const uint8*)TCHAR_TO_ANSI(Command), Length); +} + +//////////////////////////////////////////////////////////////////////////////// +void FControlClient::FormatAndSend(const TCHAR* Format, ...) +{ + if (!IsConnected()) + { + return; + } + + TCHAR Buffer[512]; + va_list Args; + va_start(Args, Format); + int Length = FCString::GetVarArgs(Buffer, ARRAY_COUNT(Buffer), Format, Args); + if (Length > sizeof(Buffer)) + { + Length = sizeof(Buffer); + } + va_end(Args); + + Send((const uint8*)TCHAR_TO_ANSI(Buffer), Length); +} + +//////////////////////////////////////////////////////////////////////////////// +void FControlClient::Send(const uint8* Data, int Length) +{ + int32 SentBytes = 0; + if (!Socket->Send(Data, Length, SentBytes) || SentBytes != Length) + { + Disconnect(); + return; + } + + if (!Socket->Send((const uint8*)"\n", 1, SentBytes) || SentBytes != 1) + { + Disconnect(); + return; + } +} + +} // namesapce Trace diff --git a/Engine/Source/Developer/TraceAnalysis/Private/DataStream.cpp b/Engine/Source/Developer/TraceAnalysis/Private/DataStream.cpp new file mode 100644 index 000000000000..c9b4606d196f --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/Private/DataStream.cpp @@ -0,0 +1,85 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DataStream.h" +#include "GenericPlatform/GenericPlatformFile.h" + +namespace Trace +{ + +FFileStream::FFileStream(const TCHAR* InFilePath) + : FilePath(InFilePath) +{ + OpenFileInternal(); +} + +FFileStream::~FFileStream() +{ + delete Inner; +} + +int32 FFileStream::Read(void* Data, uint32 Size) +{ + if (!Inner) + { + return 0; + } + uint64 Remaining = End - Cursor; + if (Remaining == 0) + { + return 0; + } + + uint64 Size64 = Size; + Size64 = (Remaining < Size64) ? Remaining : Size64; + + if (!Inner->Read((uint8*)Data, Size64)) + { + return 0; + } + + Cursor += Size64; + return int32(Size64); +} + +void FFileStream::UpdateFileSize() +{ + delete Inner; + OpenFileInternal(); + if (Inner) + { + Inner->Seek(Cursor); + } +} + +void FFileStream::OpenFileInternal() +{ + IPlatformFile& FileSystem = IPlatformFile::GetPlatformPhysical(); + Inner = FileSystem.OpenRead(*FilePath, true); + if (Inner) + { + End = Inner->Size(); + } +} + +FStreamReader::FStreamReader(IInDataStream& InDataStream) + : DataStream(InDataStream) +{ + Buffer = new uint8[BufferSize]; +} + +FStreamReader::~FStreamReader() +{ + delete[] Buffer; +} + +IInDataStream* DataStream_ReadFile(const TCHAR* FilePath) +{ + IPlatformFile& FileSystem = IPlatformFile::GetPlatformPhysical(); + if (!FileSystem.FileExists(FilePath)) + { + return nullptr; + } + return new FFileStream(FilePath); +} + +} diff --git a/Engine/Source/Developer/TraceAnalysis/Private/DataStream.h b/Engine/Source/Developer/TraceAnalysis/Private/DataStream.h new file mode 100644 index 000000000000..c40c0435cb7e --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/Private/DataStream.h @@ -0,0 +1,99 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "Logging/LogMacros.h" +#include "Containers/UnrealString.h" +#include "Trace/DataStream.h" + +#include + +namespace Trace +{ + +//////////////////////////////////////////////////////////////////////////////// +class FFileStream + : public IInDataStream +{ +public: + FFileStream(const TCHAR* FilePath); + virtual ~FFileStream(); + virtual int32 Read(void* Data, uint32 Size) override; + void UpdateFileSize(); + +private: + void OpenFileInternal(); + + FString FilePath; + IFileHandle* Inner = nullptr; + uint64 Cursor = 0; + uint64 End = 0; +}; + +//////////////////////////////////////////////////////////////////////////////// +class FStreamReader +{ +public: + class FData + { + friend FStreamReader; + const uint8* Cursor = nullptr; + const uint8* End = nullptr; + + public: + const uint8* GetPointer(uint32 Size) const; + void Advance(uint32 Size); + }; + + FStreamReader(IInDataStream& InDataStream); + ~FStreamReader(); + FData* Read(); + +private: + static const uint32 BufferSize = 1 << 18; + FData Data; + IInDataStream& DataStream; + uint8* Buffer; +}; + +//////////////////////////////////////////////////////////////////////////////// +inline FStreamReader::FData* FStreamReader::Read() +{ + int32 Remaining = int32(UPTRINT(Data.End - Data.Cursor)); + if (Remaining != 0) + { + memcpy(Buffer, Data.Cursor, Remaining); + } + + uint8* Dest = Buffer + Remaining; + int32 ReadSize = DataStream.Read(Dest, BufferSize - Remaining); + if (ReadSize <= 0) + { + return nullptr; + } + + Data.Cursor = Buffer; + Data.End = Dest + ReadSize; + return &Data; +} + +//////////////////////////////////////////////////////////////////////////////// +inline const uint8* FStreamReader::FData::GetPointer(uint32 Size) const +{ + if (Cursor + Size > End) + { + return nullptr; + } + + return Cursor; +} + +//////////////////////////////////////////////////////////////////////////////// +inline void FStreamReader::FData::Advance(uint32 Size) +{ + Cursor += Size; + check(Cursor <= End); +} + +} // namespace Trace diff --git a/Engine/Source/Developer/TraceAnalysis/Private/Recorder/Recorder.cpp b/Engine/Source/Developer/TraceAnalysis/Private/Recorder/Recorder.cpp new file mode 100644 index 000000000000..bb074da4dd9c --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/Private/Recorder/Recorder.cpp @@ -0,0 +1,351 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Trace/Recorder.h" + +#include "Containers/Array.h" +#include "HAL/Runnable.h" +#include "HAL/RunnableThread.h" +#include "IPAddress.h" +#include "Misc/ScopeLock.h" +#include "Sockets.h" +#include "SocketSubsystem.h" +#include "Trace/DataStream.h" +#include "Trace/Store.h" +#include "Trace/ControlClient.h" + +namespace Trace +{ + +//////////////////////////////////////////////////////////////////////////////// +class FRecorder + : public IRecorder + , public FRunnable +{ +public: + FRecorder(TSharedRef InStore); + virtual ~FRecorder(); + +private: + struct FSession; + virtual uint32 Run() override; + virtual bool IsRunning() const override; + virtual bool StartRecording() override; + virtual void StopRecording() override; + virtual uint32 GetSessionCount() const override; + virtual void GetActiveSessions(TArray& OutSessions) const override; + virtual bool ToggleEvent(FRecorderSessionHandle RecordingHandle, const TCHAR* LoggerWildcard, bool bState) override; + FSession* AcceptSession(FSocket& Socket); + void CloseSession(FSession& Session); + void ReapDeadSessions(); + TSharedRef Store; + mutable FCriticalSection SessionsCS; + TMap Sessions; + FRunnableThread* Thread = nullptr; + FSocket* ListenSocket = nullptr; + FRecorderSessionHandle NextSessionHandle = 1; + volatile bool bStopRequested; +}; + + + +//////////////////////////////////////////////////////////////////////////////// +struct FRecorder::FSession + : public FRunnable +{ + virtual uint32 Run() override; + FSocket* Socket; + FStoreSessionHandle StoreSessionHandle; + IOutDataStream* StoreSessionStream; + FRunnableThread* Thread; + TSharedPtr ControlClientAddress; + volatile bool bDead = false; +}; + +//////////////////////////////////////////////////////////////////////////////// +uint32 FRecorder::FSession::Run() +{ + static const uint32 BufferSize = 1 * 1024 * 1024; + uint8* Buffer = new uint8[BufferSize]; + + do + { + int32 RecvSize; + if (!Socket->Recv(Buffer, BufferSize, RecvSize)) + { + bDead = true; + break; + } + + if (!StoreSessionStream->Write(Buffer, RecvSize)) + { + bDead = true; + break; + } + } + while (!bDead); + + delete[] Buffer; + return 0; +} + + + +//////////////////////////////////////////////////////////////////////////////// +FRecorder::FRecorder(TSharedRef InStore) +: Store(InStore) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +FRecorder::~FRecorder() +{ + StopRecording(); +} + +//////////////////////////////////////////////////////////////////////////////// +FRecorder::FSession* FRecorder::AcceptSession(FSocket& Socket) +{ + bool bAcceptable = false; + if (Socket.Wait(ESocketWaitConditions::WaitForRead, FTimespan(ETimespan::TicksPerSecond / 3))) + { + uint32 Magic; + int32 RecvSize; + if (Socket.Recv((uint8*)&Magic, sizeof(Magic), RecvSize)) + { + bAcceptable = (RecvSize == sizeof(Magic)) & (Magic == 'TRCE'); + } + } + + if (!bAcceptable) + { + return nullptr; + } + + TTuple StoreSession = Store->CreateNewSession(); + if (!StoreSession.Get<1>()) + { + return nullptr; + } + FSession* Session = new FSession(); + Session->ControlClientAddress = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr(); + Socket.GetPeerAddress(*Session->ControlClientAddress); + Session->ControlClientAddress->SetPort(1985); + Session->Socket = &Socket; + Session->StoreSessionHandle = StoreSession.Get<0>(); + Session->StoreSessionStream = StoreSession.Get<1>(); + Session->Thread = FRunnableThread::Create(Session, TEXT("TraceRecSession")); + return Session; +} + +//////////////////////////////////////////////////////////////////////////////// +void FRecorder::CloseSession(FSession& Session) +{ + Session.bDead = true; + Session.Socket->Close(); + + Session.Thread->Kill(true); + + ISocketSubsystem& Sockets = *(ISocketSubsystem::Get()); + Sockets.DestroySocket(Session.Socket); + + delete Session.Thread; + delete Session.StoreSessionStream; + delete &Session; +} + +//////////////////////////////////////////////////////////////////////////////// +void FRecorder::ReapDeadSessions() +{ + // Reap dead sessions + TArray SessionsToClose; + { + FScopeLock Lock(&SessionsCS); + for (auto It = Sessions.CreateIterator(); It; ++It) + { + FSession* Session = It->Value; + if (Session->bDead) + { + SessionsToClose.Add(Session); + It.RemoveCurrent(); + } + } + } + + for (FSession* Session : SessionsToClose) + { + CloseSession(*Session); + } +} + +//////////////////////////////////////////////////////////////////////////////// +uint32 FRecorder::Run() +{ + ISocketSubsystem& Sockets = *(ISocketSubsystem::Get()); + + FTimespan WaitTimespan = FTimespan((ETimespan::TicksPerSecond * 3) / 7); + while (!bStopRequested) + { + bool bPending; + if (!ListenSocket->WaitForPendingConnection(bPending, WaitTimespan)) + { + break; + } + + if (bPending) + { + FSocket* Socket = ListenSocket->Accept(TEXT("TraceRecClient")); + if (Socket == nullptr) + { + break; + } + + if (FSession* Session = AcceptSession(*Socket)) + { + FScopeLock Lock(&SessionsCS); + Sessions.Add(NextSessionHandle++, Session); + } + else + { + Sockets.DestroySocket(Socket); + } + } + + ReapDeadSessions(); + } + + TArray SessionsToClose; + { + FScopeLock Lock(&SessionsCS); + for (auto& KV : Sessions) + { + SessionsToClose.Add(KV.Value); + } + Sessions.Empty(); + } + for (FSession* Session : SessionsToClose) + { + CloseSession(*Session); + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +bool FRecorder::IsRunning() const +{ + return (ListenSocket != nullptr); +} + +//////////////////////////////////////////////////////////////////////////////// +bool FRecorder::StartRecording() +{ + if (IsRunning()) + { + return true; + } + + StopRecording(); + + ISocketSubsystem& Sockets = *(ISocketSubsystem::Get()); + + // Create a socket to use for listening for inbound connections + FSocket* Socket = Sockets.CreateSocket(NAME_Stream, TEXT("TraceRecListen")); + if (Socket == nullptr) + { + return false; + } + + // Bind it to the trace-recording port + bool bBound = false; + TSharedPtr Addr = Sockets.CreateInternetAddr(); + Addr->SetPort(1980); + if (Socket->Bind(*Addr)) + { + bBound = true; + } + + // Set the socket listening for connections to accept + if (!bBound || !Socket->Listen(32)) + { + Sockets.DestroySocket(Socket); + return false; + } + + bStopRequested = false; + ListenSocket = Socket; + Thread = FRunnableThread::Create(this, TEXT("TraceRec")); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +void FRecorder::StopRecording() +{ + if (Thread == nullptr || bStopRequested) + { + return; + } + + ListenSocket->Close(); + + bStopRequested = true; + + Thread->Kill(true); + delete Thread; + Thread = nullptr; + + ISocketSubsystem& Sockets = *(ISocketSubsystem::Get()); + Sockets.DestroySocket(ListenSocket); + ListenSocket = nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +uint32 FRecorder::GetSessionCount() const +{ + FScopeLock Lock(&SessionsCS); + return Sessions.Num(); +} + + +//////////////////////////////////////////////////////////////////////////////// +void FRecorder::GetActiveSessions(TArray& OutSessions) const +{ + FScopeLock Lock(&SessionsCS); + OutSessions.Reserve(OutSessions.Num() + Sessions.Num()); + for (const auto& KV : Sessions) + { + FRecorderSessionInfo& SessionInfo = OutSessions.AddDefaulted_GetRef(); + SessionInfo.Handle = KV.Key; + SessionInfo.StoreSessionHandle = KV.Value->StoreSessionHandle; + } +} + +//////////////////////////////////////////////////////////////////////////////// +bool FRecorder::ToggleEvent(FRecorderSessionHandle SessionHandle, const TCHAR* LoggerWildcard, bool bState) +{ + TSharedPtr ControlClientAddress; + { + FScopeLock Lock(&SessionsCS); + FSession** FindIt = Sessions.Find(SessionHandle); + if (!FindIt) + { + return false; + } + ControlClientAddress = (*FindIt)->ControlClientAddress; + } + FControlClient ControlClient; + if (!ControlClient.Connect(*ControlClientAddress)) + { + return false; + } + ControlClient.SendToggleEvent(LoggerWildcard, bState); + ControlClient.Disconnect(); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +TSharedPtr Recorder_Create(TSharedRef Store) +{ + return MakeShared(Store); +} + +} // namespace Trace diff --git a/Engine/Source/Developer/TraceAnalysis/Private/Store/FileStore.cpp b/Engine/Source/Developer/TraceAnalysis/Private/Store/FileStore.cpp new file mode 100644 index 000000000000..2da7931e720b --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/Private/Store/FileStore.cpp @@ -0,0 +1,347 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Trace/Store.h" +#include "DataStream.h" +#include "HAL/PlatformFile.h" +#include "HAL/PlatformProcess.h" +#include "Misc/ScopeLock.h" +#include "Templates/UniquePtr.h" +#include "Misc/Paths.h" +#include "Modules/ModuleManager.h" +#include "DirectoryWatcherModule.h" +#include "IDirectoryWatcher.h" +#include "Containers/Ticker.h" + +namespace Trace +{ + +class FFileStore; + +class FFileStoreInDataStream + : public IInDataStream +{ +public: + FFileStoreInDataStream(FFileStore* Owner, FStoreSessionHandle SessionHandle, FFileStream* File); + virtual int32 Read(void* Data, uint32 Size) override; + +private: + FFileStore* Owner; + FStoreSessionHandle SessionHandle; + TUniquePtr Inner; +}; + +class FFileStoreOutDataStream + : public IOutDataStream +{ +public: + FFileStoreOutDataStream(FFileStore* Owner, FStoreSessionHandle SessionHandle, IFileHandle* File); + virtual ~FFileStoreOutDataStream(); + virtual bool Write(const void* Data, uint32 Size) override; + +private: + FFileStore* Owner; + FStoreSessionHandle SessionHandle; + TUniquePtr Inner; +}; + +class FFileStore + : public IStore +{ +public: + FFileStore(const TCHAR* StoreDir); + ~FFileStore(); + + virtual void GetAvailableSessions(TArray& OutSessions) const override; + virtual TTuple CreateNewSession() override; + virtual IInDataStream* OpenSessionStream(FStoreSessionHandle Handle) override; + +private: + friend class FFileStoreInDataStream; + friend class FFileStoreOutDataStream; + + struct FSessionInfoInternal + { + FStoreSessionHandle Handle; + FString Name; + FString Path; + bool bIsLive; + bool bIsValid; + }; + + FSessionInfoInternal* AddSession(const FString& Path); + void RemoveSession(FStoreSessionHandle Handle); + FSessionInfoInternal* GetSession(FStoreSessionHandle Handle) const; + bool Tick(float DeltaTime); + void OnDirectoryChanged(const TArray& FileChanges); + bool IsSessionLive(FStoreSessionHandle Handle) const; + void CloseSessionStream(FStoreSessionHandle Handle); + + mutable FCriticalSection SessionsCS; + FString StoreDir; + IDirectoryWatcher* DirectoryWatcher; + FDelegateHandle DirectoryWatcherHandle; + FDelegateHandle TickHandle; + FStoreSessionHandle NextSessionHandle = 1; + TArray Sessions; + TMap SessionsByPathMap; +}; + +FFileStoreOutDataStream::FFileStoreOutDataStream(FFileStore* InOwner, FStoreSessionHandle InSessionHandle, IFileHandle* InFile) + : Owner(InOwner) + , SessionHandle(InSessionHandle) +{ + check(InFile); + Inner.Reset(InFile); +} + +FFileStoreOutDataStream::~FFileStoreOutDataStream() +{ + Inner.Reset(nullptr); + Owner->CloseSessionStream(SessionHandle); +} + +bool FFileStoreOutDataStream::Write(const void* Data, uint32 Size) +{ + return Inner->Write((const uint8*)Data, Size); +} + +FFileStoreInDataStream::FFileStoreInDataStream(FFileStore* InOwner, FStoreSessionHandle InSessionHandle, FFileStream* InFileStream) + : Owner(InOwner) + , SessionHandle(InSessionHandle) + , Inner(InFileStream) +{ +} + +int32 FFileStoreInDataStream::Read(void* Data, uint32 Size) +{ + do + { + int32 InnerResult = Inner->Read(Data, Size); + if (InnerResult > 0) + { + return InnerResult; + } + Inner->UpdateFileSize(); + if (Owner->IsSessionLive(SessionHandle)) + { + FPlatformProcess::Sleep(0.5); + continue; + } + else + { + return 0; + } + } while (true); +} + +FFileStore::FFileStore(const TCHAR* InStoreDir) + : StoreDir(InStoreDir) +{ + IPlatformFile& FileSystem = IPlatformFile::GetPlatformPhysical(); + FileSystem.CreateDirectory(*StoreDir); + + int32 HighestTraceId = -1; + FileSystem.IterateDirectory(*StoreDir, [&HighestTraceId, this](const TCHAR* FileName, bool IsDirectory) + { + if (!IsDirectory && FPaths::GetExtension(FileName) == TEXT("utrace")) + { + AddSession(FileName); + } + return true; + }); + + FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::LoadModuleChecked(TEXT("DirectoryWatcher")); + DirectoryWatcher = DirectoryWatcherModule.Get(); + if (DirectoryWatcher) + { + DirectoryWatcher->RegisterDirectoryChangedCallback_Handle(StoreDir, IDirectoryWatcher::FDirectoryChanged::CreateRaw(this, &FFileStore::OnDirectoryChanged), DirectoryWatcherHandle, IDirectoryWatcher::WatchOptions::IncludeDirectoryChanges); + FTickerDelegate TickDelegate = FTickerDelegate::CreateRaw(this, &FFileStore::Tick); + TickHandle = FTicker::GetCoreTicker().AddTicker(TickDelegate, 0.5f); + } +} + +FFileStore::~FFileStore() +{ + FDirectoryWatcherModule* DirectoryWatcherModule = FModuleManager::GetModulePtr(TEXT("DirectoryWatcher")); + if (DirectoryWatcherModule) + { + IDirectoryWatcher* LocalDirectoryWatcher = DirectoryWatcherModule->Get(); + if (DirectoryWatcher) + { + LocalDirectoryWatcher->UnregisterDirectoryChangedCallback_Handle(StoreDir, DirectoryWatcherHandle); + } + } + if (TickHandle.IsValid()) + { + FTicker::GetCoreTicker().RemoveTicker(TickHandle); + TickHandle.Reset(); + } + for (FSessionInfoInternal* Session : Sessions) + { + delete Session; + } +} + +FFileStore::FSessionInfoInternal* FFileStore::AddSession(const FString& Path) +{ + FScopeLock Lock(&SessionsCS); + + FSessionInfoInternal* Session = new FSessionInfoInternal(); + Session->Handle = NextSessionHandle++; + Session->Name = FPaths::GetBaseFilename(Path); + Session->Path = Path; + Session->bIsValid = true; + Session->bIsLive = false; + Sessions.Add(Session); + SessionsByPathMap.Add(Path, Session); + + return Session; +} + +void FFileStore::RemoveSession(FStoreSessionHandle Handle) +{ + FScopeLock Lock(&SessionsCS); + + FSessionInfoInternal* Session = Sessions[Handle - 1]; + SessionsByPathMap.Remove(Session->Path); + Sessions[Handle - 1]->bIsValid = false; +} + +FFileStore::FSessionInfoInternal* FFileStore::GetSession(FStoreSessionHandle Handle) const +{ + FScopeLock Lock(&SessionsCS); + if (Handle - 1 >= Sessions.Num()) + { + return nullptr; + } + FSessionInfoInternal* Session = Sessions[Handle - 1]; + if (!Session->bIsValid) + { + return nullptr; + } + return Session; +} + +void FFileStore::GetAvailableSessions(TArray& OutSessions) const +{ + FScopeLock Lock(&SessionsCS); + OutSessions.Reserve(OutSessions.Num() + Sessions.Num()); + for (const FSessionInfoInternal* Session : Sessions) + { + if (Session->bIsValid) + { + FStoreSessionInfo& SessionInfo = OutSessions.AddDefaulted_GetRef(); + SessionInfo.Handle = Session->Handle; + SessionInfo.Uri = *Session->Path; + SessionInfo.Name = *Session->Name; + SessionInfo.bIsLive = Session->bIsLive; + } + } +} + +TTuple FFileStore::CreateNewSession() +{ + FScopeLock Lock(&SessionsCS); + + IPlatformFile& FileSystem = IPlatformFile::GetPlatformPhysical(); + + FString TracePath = StoreDir / FString::Printf(TEXT("%s.utrace"), *FDateTime::Now().ToString(TEXT("%Y%m%d_%H%M%S"))); + uint64 Index = 1; + while (FileSystem.FileExists(*TracePath)) + { + TracePath = StoreDir / FString::Printf(TEXT("%s_%d.utrace"), *FDateTime::Now().ToString(TEXT("%Y%m%d_%H%M%S")), Index); + } + + IFileHandle* File = FileSystem.OpenWrite(*TracePath, true, true); + if (!File) + { + return MakeTuple(FStoreSessionHandle(0), (IOutDataStream*)nullptr); + } + + FSessionInfoInternal* Session = AddSession(TracePath); + Session->bIsLive = true; + return MakeTuple(Session->Handle, static_cast(new FFileStoreOutDataStream(this, Session->Handle, File))); +} + +IInDataStream* FFileStore::OpenSessionStream(FStoreSessionHandle Handle) +{ + FScopeLock Lock(&SessionsCS); + + FSessionInfoInternal* Session = GetSession(Handle); + if (!Session) + { + return nullptr; + } + + IPlatformFile& FileSystem = IPlatformFile::GetPlatformPhysical(); + if (!FileSystem.FileExists(*Session->Path)) + { + return nullptr; + } + + FFileStream* FileStream = new FFileStream(*Session->Path); + return new FFileStoreInDataStream(this, Handle, FileStream); +} + +bool FFileStore::Tick(float DeltaTime) +{ + DirectoryWatcher->Tick(DeltaTime); + return true; +} + +void FFileStore::OnDirectoryChanged(const TArray& FileChanges) +{ + FScopeLock Lock(&SessionsCS); + + for (const FFileChangeData& FileChange : FileChanges) + { + FSessionInfoInternal** FindIt = SessionsByPathMap.Find(FileChange.Filename.ToUpper()); + switch (FileChange.Action) + { + case FFileChangeData::FCA_Removed: + if (FindIt) + { + RemoveSession((*FindIt)->Handle); + } + break; + case FFileChangeData::FCA_Added: + if (!FindIt && FPaths::GetExtension(FileChange.Filename) == TEXT("utrace")) + { + AddSession(FileChange.Filename); + } + break; + } + } +} + +bool FFileStore::IsSessionLive(FStoreSessionHandle Handle) const +{ + FScopeLock Lock(&SessionsCS); + + FSessionInfoInternal* Session = GetSession(Handle); + if (!Session) + { + return false; + } + return Session->bIsLive; +} + +void FFileStore::CloseSessionStream(FStoreSessionHandle Handle) +{ + FScopeLock Lock(&SessionsCS); + + FSessionInfoInternal* Session = GetSession(Handle); + if (Session) + { + Session->bIsLive = false; + } +} + +TSharedPtr Store_Create(const TCHAR* StoreDir) +{ + TSharedRef FileStore = MakeShared(StoreDir); + return FileStore; +} + +} + diff --git a/Engine/Source/Developer/TraceAnalysis/Private/TraceAnalysisModule.cpp b/Engine/Source/Developer/TraceAnalysis/Private/TraceAnalysisModule.cpp new file mode 100644 index 000000000000..409bb255c29d --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/Private/TraceAnalysisModule.cpp @@ -0,0 +1,5 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Modules/ModuleManager.h" + +IMPLEMENT_MODULE(FDefaultModuleImpl, TraceAnalysis); diff --git a/Engine/Source/Developer/TraceAnalysis/Public/Trace/Analysis.h b/Engine/Source/Developer/TraceAnalysis/Public/Trace/Analysis.h new file mode 100644 index 000000000000..dfa4bf2a5036 --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/Public/Trace/Analysis.h @@ -0,0 +1,41 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Containers/Array.h" + +namespace Trace +{ + +class IAnalyzer; +class IInDataStream; + +//////////////////////////////////////////////////////////////////////////////// +class TRACEANALYSIS_API FAnalysisProcessor +{ +public: + explicit operator bool () const; + void Start(IInDataStream& DataStream); + void Stop(); + void Wait(); + void Pause(bool State); + +private: + friend class FAnalysisContext; + void* Impl = nullptr; +}; + + + +//////////////////////////////////////////////////////////////////////////////// +class TRACEANALYSIS_API FAnalysisContext +{ +public: + void AddAnalyzer(IAnalyzer& Analyzer); + FAnalysisProcessor Process(); + +private: + TArray Analyzers; +}; + +} // namespace Trace diff --git a/Engine/Source/Developer/TraceAnalysis/Public/Trace/Analyzer.h b/Engine/Source/Developer/TraceAnalysis/Public/Trace/Analyzer.h new file mode 100644 index 000000000000..ada5452de870 --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/Public/Trace/Analyzer.h @@ -0,0 +1,97 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "Logging/LogMacros.h" +#include "Trace/Private/Field.h" + +#include + +namespace Trace +{ + +//////////////////////////////////////////////////////////////////////////////// +class TRACEANALYSIS_API IAnalyzer +{ +public: + struct FSessionContext + { + uint64 StartCycle; + uint64 CycleFrequency; + + double TimestampFromCycle(uint64 Cycle) const + { + return double(Cycle - StartCycle) / double(CycleFrequency); + } + + double DurationFromCycleCount(uint64 CycleCount) const + { + return double(CycleCount) / double(CycleFrequency); + } + }; + + struct FInterfaceBuilder + { + virtual void RouteEvent(uint16 RouteId, const ANSICHAR* Logger, const ANSICHAR* Event) = 0; + }; + + struct FOnAnalysisContext + { + const FSessionContext& SessionContext; + FInterfaceBuilder& InterfaceBuilder; + }; + + struct FValue + { + template + AsType As() const; + }; + + struct FArray + { + }; + + struct FEventData + { + virtual const FValue& GetValue(const ANSICHAR* FieldName) const = 0; + virtual const FArray& GetArray(const ANSICHAR* FieldName) const = 0; + virtual const uint8* GetData() const = 0; + virtual const uint8* GetAttachment() const = 0; + virtual uint16 GetAttachmentSize() const = 0; + virtual uint16 GetTotalSize() const = 0; + }; + + struct FOnEventContext + { + const FSessionContext& SessionContext; + const FEventData& EventData; + }; + + virtual ~IAnalyzer() = default; + virtual void OnAnalysisBegin(const FOnAnalysisContext& Context) = 0; + virtual void OnAnalysisEnd() = 0; + virtual void OnEvent(uint16 RouteId, const FOnEventContext& Context) = 0; +}; + +//////////////////////////////////////////////////////////////////////////////// +template +AsType IAnalyzer::FValue::As() const +{ + uint16 FieldType = uint16(UPTRINT(this) >> 48); + if (FieldType == 0xffff) + { + return AsType(0); + } + + check((FieldType & _Field_Float) == 0); + + const void* Addr = (void*)(UPTRINT(this) & ((1ull << 48) - 1)); + UPTRINT Value = 0; + uint32 Size = 1 << (FieldType & _Field_Pow2SizeMask); + memcpy(&Value, Addr, Size); + + return AsType(Value); +} + +} // namespace Trace diff --git a/Engine/Source/Developer/TraceAnalysis/Public/Trace/ControlClient.h b/Engine/Source/Developer/TraceAnalysis/Public/Trace/ControlClient.h new file mode 100644 index 000000000000..eab7b2e5a237 --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/Public/Trace/ControlClient.h @@ -0,0 +1,32 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" + +class FSocket; +class FInternetAddr; + +namespace Trace +{ + +//////////////////////////////////////////////////////////////////////////////// +class TRACEANALYSIS_API FControlClient +{ +public: + ~FControlClient(); + bool Connect(const TCHAR* Host, uint16 Port=1985); + bool Connect(const FInternetAddr& Address); + void Disconnect(); + bool IsConnected() const; + void SendConnect(const TCHAR* Path); + void SendToggleEvent(const TCHAR* Logger, bool bState=true); + void Send(const TCHAR* Command); + +private: + void FormatAndSend(const TCHAR* Format, ...); + void Send(const uint8* Data, int Length); + FSocket* Socket = nullptr; +}; + +} // namespace Trace diff --git a/Engine/Source/Developer/TraceAnalysis/Public/Trace/DataStream.h b/Engine/Source/Developer/TraceAnalysis/Public/Trace/DataStream.h new file mode 100644 index 000000000000..531136958c15 --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/Public/Trace/DataStream.h @@ -0,0 +1,26 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" + +namespace Trace +{ + +class IInDataStream +{ +public: + virtual ~IInDataStream() = default; + virtual int32 Read(void* Data, uint32 Size) = 0; +}; + +class IOutDataStream +{ +public: + virtual ~IOutDataStream() = default; + virtual bool Write(const void* Data, uint32 Size) = 0; +}; + +TRACEANALYSIS_API IInDataStream* DataStream_ReadFile(const TCHAR* FilePath); + +} diff --git a/Engine/Source/Developer/TraceAnalysis/Public/Trace/Recorder.h b/Engine/Source/Developer/TraceAnalysis/Public/Trace/Recorder.h new file mode 100644 index 000000000000..335ee3cc6785 --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/Public/Trace/Recorder.h @@ -0,0 +1,47 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Templates/SharedPointer.h" + +namespace Trace +{ + +class IStore; +typedef uint64 FStoreSessionHandle; +typedef uint64 FRecorderSessionHandle; + +struct FRecorderSessionInfo +{ + FRecorderSessionHandle Handle; + FStoreSessionHandle StoreSessionHandle; +}; + +//////////////////////////////////////////////////////////////////////////////// +class IRecorder +{ +public: + virtual ~IRecorder() = default; + + /** Checks if the recorder is actively recording, return false if it is not. */ + virtual bool IsRunning() const = 0; + + /** Starts recording, returning true if it was started successfully or if was already running. */ + virtual bool StartRecording() = 0; + + /** Stops a running recorder dead in its tracks. */ + virtual void StopRecording() = 0; + + /** Returns the number of current in-bound sessions. */ + virtual uint32 GetSessionCount() const = 0; + + /** Return information about the current in-bound sessions */ + virtual void GetActiveSessions(TArray& OutSessions) const = 0; + + /** Toggles an event logger widlcard on or off for an active recording session */ + virtual bool ToggleEvent(FRecorderSessionHandle RecordingHandle, const TCHAR* LoggerWildcard, bool bState) = 0; +}; + +TRACEANALYSIS_API TSharedPtr Recorder_Create(TSharedRef Store); + +} // namespace Trace diff --git a/Engine/Source/Developer/TraceAnalysis/Public/Trace/Store.h b/Engine/Source/Developer/TraceAnalysis/Public/Trace/Store.h new file mode 100644 index 000000000000..91aa9952382d --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/Public/Trace/Store.h @@ -0,0 +1,35 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "Containers/UnrealString.h" +#include "Templates/SharedPointer.h" + +namespace Trace +{ + +class IInDataStream; +class IOutDataStream; +typedef uint64 FStoreSessionHandle; + +struct FStoreSessionInfo +{ + FStoreSessionHandle Handle; + const TCHAR* Uri; + const TCHAR* Name; + bool bIsLive; +}; + +class IStore +{ +public: + virtual ~IStore() = default; + virtual void GetAvailableSessions(TArray& OutSessions) const = 0; + virtual TTuple CreateNewSession() = 0; + virtual IInDataStream* OpenSessionStream(FStoreSessionHandle Handle) = 0; +}; + +TRACEANALYSIS_API TSharedPtr Store_Create(const TCHAR* StoreDir); + +} diff --git a/Engine/Source/Developer/TraceAnalysis/TraceAnalysis.Build.cs b/Engine/Source/Developer/TraceAnalysis/TraceAnalysis.Build.cs new file mode 100644 index 000000000000..2542246717a7 --- /dev/null +++ b/Engine/Source/Developer/TraceAnalysis/TraceAnalysis.Build.cs @@ -0,0 +1,13 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class TraceAnalysis : ModuleRules +{ + public TraceAnalysis(ReadOnlyTargetRules Target) : base(Target) + { + PrivateDependencyModuleNames.Add("Core"); + PrivateDependencyModuleNames.Add("Sockets"); + PrivateDependencyModuleNames.Add("DirectoryWatcher"); + } +} diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Common/FixedCircularBuffer.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Common/FixedCircularBuffer.h new file mode 100644 index 000000000000..123d5cfb2048 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Common/FixedCircularBuffer.h @@ -0,0 +1,50 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +template +struct TFixedCircularBuffer +{ + T Buffer[Size]; + int32 BufferStartOffset = 0; + int32 BufferLength = 0; + + void Reset() + { + BufferStartOffset = 0; + BufferLength = 0; + } + + void AddValue(const T Value) + { + Buffer[BufferStartOffset] = Value; + BufferStartOffset++; + if (BufferStartOffset == Size) + { + BufferStartOffset = 0; + } + if (BufferLength < Size) + { + BufferLength++; + } + } + + const T GetValue(const int32 RecentIndex) const + { + return Buffer[(BufferStartOffset + Size - RecentIndex - 1) % Size]; + } + + const T ComputeAverage() const + { + if (BufferLength == 0) + { + return 0; + } + T Total = 0; + for (int32 RecentIndex = 0; RecentIndex < BufferLength; ++RecentIndex) + { + Total += Buffer[(BufferStartOffset + Size - RecentIndex - 1) % Size]; + } + return Total / BufferLength; + } +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Common/PaintUtils.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Common/PaintUtils.h new file mode 100644 index 000000000000..0ddb4202d50c --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Common/PaintUtils.h @@ -0,0 +1,87 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Rendering/DrawElements.h" + +#define MAKE_PAINT_GEOMETRY_PT(Geometry, X, Y) Geometry.ToPaintGeometry(FSlateLayoutTransform(1.0f, FVector2D(X, Y))) +#define MAKE_PAINT_GEOMETRY_RC(Geometry, X, Y, W, H) Geometry.ToPaintGeometry(FVector2D(W, H), FSlateLayoutTransform(1.0f, FVector2D(X, Y))) + +struct FGeometry; +class FSlateRect; +class WidgetStyle; +enum class ESlateDrawEffect : uint8; +class FSlateWindowElementList; + +/** + * Holds current state provided by OnPaint function, used to simplify drawing. + */ +struct FDrawContext +{ + FDrawContext(const FGeometry& InGeometry, + const FSlateRect& InCullingRect, + const FWidgetStyle& InWidgetStyle, + const ESlateDrawEffect InDrawEffects, + FSlateWindowElementList& InOutElementList, + int32& InOutLayerId) + : Geometry(InGeometry) + , CullingRect(InCullingRect) + , WidgetStyle(InWidgetStyle) + , DrawEffects(InDrawEffects) + , ElementList(InOutElementList) + , LayerId(InOutLayerId) + {} + + /** + * Non-copyable + */ + FDrawContext(const FDrawContext&) = delete; + FDrawContext& operator=(const FDrawContext&) = delete; + + inline void DrawBox(const float X, const float Y, const float W, const float H, const FSlateBrush* Brush, const FLinearColor& Color) const + { + FSlateDrawElement::MakeBox(ElementList, LayerId, MAKE_PAINT_GEOMETRY_RC(Geometry, X, Y, W, H), Brush, DrawEffects, Color); + } + + inline void DrawBox(const int32 InLayer, const float X, const float Y, const float W, const float H, const FSlateBrush* Brush, const FLinearColor& Color) const + { + FSlateDrawElement::MakeBox(ElementList, InLayer, MAKE_PAINT_GEOMETRY_RC(Geometry, X, Y, W, H), Brush, DrawEffects, Color); + } + + inline void DrawRotatedBox(const float X, const float Y, const float W, const float H, const FSlateBrush* Brush, const FLinearColor& Color, float Angle, TOptional RotationPoint) const + { + FSlateDrawElement::MakeRotatedBox(ElementList, LayerId, MAKE_PAINT_GEOMETRY_RC(Geometry, X, Y, W, H), Brush, DrawEffects, Angle, RotationPoint, FSlateDrawElement::RelativeToElement, Color); + } + + inline void DrawText(const float X, const float Y, const FString& Text, const FSlateFontInfo& Font, const FLinearColor& Color) const + { + FSlateDrawElement::MakeText(ElementList, LayerId, MAKE_PAINT_GEOMETRY_PT(Geometry, X, Y), Text, Font, DrawEffects, Color); + } + + inline void DrawText(const int32 InLayer, const float X, const float Y, const FString& Text, const FSlateFontInfo& Font, const FLinearColor& Color) const + { + FSlateDrawElement::MakeText(ElementList, InLayer, MAKE_PAINT_GEOMETRY_PT(Geometry, X, Y), Text, Font, DrawEffects, Color); + } + + inline void DrawText(const float X, const float Y, const FString& Text, const int32 StartIndex, const int32 EndIndex, const FSlateFontInfo& Font, const FLinearColor& Color) const + { + FSlateDrawElement::MakeText(ElementList, LayerId, MAKE_PAINT_GEOMETRY_PT(Geometry, X, Y), Text, StartIndex, EndIndex, Font, DrawEffects, Color); + } + + inline void DrawText(const int32 InLayer, const float X, const float Y, const FString& Text, const int32 StartIndex, const int32 EndIndex, const FSlateFontInfo& Font, const FLinearColor& Color) const + { + FSlateDrawElement::MakeText(ElementList, InLayer, MAKE_PAINT_GEOMETRY_PT(Geometry, X, Y), Text, StartIndex, EndIndex, Font, DrawEffects, Color); + } + + //inline void IncrementLayer() const { ++LayerId; } + + // Accessors + const FGeometry& Geometry; + const FSlateRect& CullingRect; + const FWidgetStyle& WidgetStyle; + const ESlateDrawEffect DrawEffects; + + FSlateWindowElementList& ElementList; + int32& LayerId; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Common/Stopwatch.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Common/Stopwatch.h new file mode 100644 index 000000000000..9a6479bd7133 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Common/Stopwatch.h @@ -0,0 +1,77 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "HAL/PlatformTime.h" + +// Simple stopwatch. +struct FStopwatch +{ + uint64 AccumulatedTime = 0; + uint64 StartTime = 0; + bool bIsStarted = false; + + void Start() + { + if (!bIsStarted) + { + bIsStarted = true; + StartTime = FPlatformTime::Cycles64(); + } + } + + void Stop() + { + if (bIsStarted) + { + bIsStarted = false; + AccumulatedTime += FPlatformTime::Cycles64() - StartTime; + } + } + + void Update() + { + if (bIsStarted) + { + uint64 CrtTime = FPlatformTime::Cycles64(); + AccumulatedTime += CrtTime - StartTime; + StartTime = CrtTime; + } + } + + void Restart() + { + AccumulatedTime = 0; + bIsStarted = true; + StartTime = FPlatformTime::Cycles64(); + } + + void Reset() + { + AccumulatedTime = 0; + StartTime = 0; + bIsStarted = false; + } + + double GetAccumulatedTime() const + { + return FStopwatch::Cycles64ToSeconds(AccumulatedTime); + } + + uint64 GetAccumulatedTimeMs() const + { + return FStopwatch::Cycles64ToMilliseconds(AccumulatedTime); + } + + static double Cycles64ToSeconds(const uint64 Cycles64) + { + return static_cast(Cycles64) * FPlatformTime::GetSecondsPerCycle64(); + } + + static uint64 Cycles64ToMilliseconds(const uint64 Cycles64) + { + const double Milliseconds = FMath::RoundToDouble(static_cast(Cycles64 * 1000) * FPlatformTime::GetSecondsPerCycle64()); + return static_cast(Milliseconds); + } +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Common/TimeUtils.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Common/TimeUtils.cpp new file mode 100644 index 000000000000..0d4b5a2e4c50 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Common/TimeUtils.cpp @@ -0,0 +1,778 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TimeUtils.h" + +#include +//#include + +//#include "TimingProfilerCommon.h" // for UE_LOG(TimingProfiler, ... + +namespace TimeUtils { + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FString FormatTimeAuto(const double InDuration) +{ + //TestTimeAutoFormatting(); + + double Duration = InDuration; + + if (Duration == 0.0) + { + return TEXT("0"); + } + + FString Str; + + if (FMath::IsNegativeDouble(Duration)) + { + Str = TEXT("-"); + Duration = -Duration; + } + + if (Duration == DBL_MAX || Duration == std::numeric_limits::infinity()) + { + Str += TEXT("∞"); + } + else if (Duration < TimeUtils::Picosecond) + { + // (0 .. 1ps) + Str = TEXT("~0"); + } + else if (Duration < TimeUtils::Nanosecond) + { + // [1ps .. 1ns) + if (Duration >= 999.5 * TimeUtils::Picosecond) + { + Str += TEXT("1 ns"); + } + else + { + const double Picoseconds = Duration * 1000000000000.0 + 0.5; + const int32 IntPicoseconds = static_cast(Picoseconds); + ensure(IntPicoseconds <= 999); + //if (IntPicoseconds > 999) + //{ + // Str += TEXT("1 ns"); + //} + //else + { + Str += FString::Printf(TEXT("%d ps"), IntPicoseconds); + } + } + } + else if (Duration < TimeUtils::Microsecond) + { + // [1ns .. 1µs) + if (Duration >= 999.5 * TimeUtils::Nanosecond) + { + Str += TEXT("1 µs"); + } + else + { + const double Nanoseconds = Duration * 1000000000.0 + 0.5; + const int32 IntNanoseconds = static_cast(Nanoseconds); + ensure(IntNanoseconds <= 999); + //if (IntNanoseconds > 999) + //{ + // Str += TEXT("1 µs"); + //} + //else + { + Str += FString::Printf(TEXT("%d ns"), IntNanoseconds); + } + } + } + else if (Duration < TimeUtils::Milisecond) + { + // [1µs .. 1ms) + const double Microseconds = Duration * 1000000.0; + if (Microseconds >= 999.95) + { + Str += TEXT("1 ms"); + } + else + { + FString S = FString::Printf(TEXT("%.1f"), Microseconds); + S.RemoveFromEnd(TEXT(".0")); + Str += S; + Str += TEXT(" µs"); + } + } + else if (Duration < TimeUtils::Second) + { + // [1ms .. 1s) + const double Miliseconds = Duration * 1000.0; + if (Miliseconds >= 999.95) + { + Str += TEXT("1s"); + } + else + { + FString S = FString::Printf(TEXT("%.1f"), Miliseconds); + S.RemoveFromEnd(TEXT(".0")); + Str += S; + Str += TEXT(" ms"); + } + } + else if (Duration < TimeUtils::Minute) + { + // [1s .. 1m) + if (Duration >= 59.95) + { + Str += TEXT("1m"); + } + else + { + FString S = FString::Printf(TEXT("%.1f"), Duration); + S.RemoveFromEnd(TEXT(".0")); + Str += S; + Str += TEXT("s"); + } + } + else if (Duration < TimeUtils::Hour) + { + // [1m .. 1h) + const double Minutes = FMath::FloorToDouble(Duration / TimeUtils::Minute); + Str += FString::Printf(TEXT("%dm"), static_cast(Minutes)); + Duration -= Minutes * TimeUtils::Minute; + const double Seconds = FMath::FloorToDouble(Duration / TimeUtils::Second); + if (Seconds > 0.5) + { + Str += FString::Printf(TEXT(" %ds"), static_cast(Seconds)); + } + } + else if (Duration < TimeUtils::Day) + { + // [1h .. 1d) + const double Hours = FMath::FloorToDouble(Duration / TimeUtils::Hour); + Str += FString::Printf(TEXT("%dh"), static_cast(Hours)); + Duration -= Hours * TimeUtils::Hour; + const double Minutes = FMath::FloorToDouble(Duration / TimeUtils::Minute); + if (Minutes > 0.5) + { + Str += FString::Printf(TEXT(" %dm"), static_cast(Minutes)); + } + } + else + { + // [1d .. ∞) + const double Days = FMath::FloorToDouble(Duration / TimeUtils::Day); + Str += FString::Printf(TEXT("%dd"), static_cast(Days)); + Duration -= Days * TimeUtils::Day; + const double Hours = FMath::FloorToDouble(Duration / TimeUtils::Hour); + if (Hours > 0.5) + { + Str += FString::Printf(TEXT(" %dh"), static_cast(Hours)); + } + } + + return Str; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FString FormatTimeMs(const double InDuration, const int32 NumDigits, bool bAddTimeUnit) +{ + if (FMath::IsNearlyZero(InDuration, TimeUtils::Picosecond)) + { + return TEXT("0"); + } + + double Duration = InDuration; + FString Str; + + if (FMath::IsNegativeDouble(Duration)) + { + Duration = -Duration; + Str = TEXT("-"); + } + + if (Duration == DBL_MAX || Duration == std::numeric_limits::infinity()) + { + Str += TEXT("∞"); + } + else + { + Str += FString::Printf(TEXT("%.*f"), NumDigits, Duration * 1000.0); + + if (bAddTimeUnit) + { + Str += TEXT(" ms"); + } + } + + return Str; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FString FormatTime(const double InTime, const double Precision) +{ + if (FMath::IsNearlyZero(InTime, FMath::Max(TimeUtils::Picosecond, Precision / 10.0f))) + { + return TEXT("0"); + } + + double Time = InTime; + FString Str; + + if (FMath::IsNegativeDouble(Time)) + { + Time = -Time; + Str = TEXT("-"); + } + + if (Time == DBL_MAX || Time == std::numeric_limits::infinity()) + { + Str += TEXT("∞"); + return Str; + } + + bool bIsSpaceNeeded = false; + + int32 Days = static_cast(Time / TimeUtils::Day); + if (Days > 0) + { + Str += FString::Printf(TEXT("%dd"), Days); + bIsSpaceNeeded = true; + Time -= static_cast(Days) * TimeUtils::Day; + } + + if (Precision >= TimeUtils::Day) + { + if (!bIsSpaceNeeded) + { + Str = TEXT("~0"); + } + return Str; + } + + int32 Hours = static_cast(Time / TimeUtils::Hour); + if (Hours > 0) + { + if (bIsSpaceNeeded) + { + Str += TEXT(" "); + } + Str += FString::Printf(TEXT("%dh"), Hours); + bIsSpaceNeeded = true; + Time -= static_cast(Hours) * TimeUtils::Hour; + } + + if (Precision >= TimeUtils::Hour) + { + if (!bIsSpaceNeeded) + { + Str = TEXT("~0"); + } + return Str; + } + + int32 Minutes = static_cast(Time / TimeUtils::Minute); + if (Minutes > 0) + { + if (bIsSpaceNeeded) + { + Str += TEXT(" "); + } + Str += FString::Printf(TEXT("%dm"), Minutes); + bIsSpaceNeeded = true; + Time -= static_cast(Minutes) * TimeUtils::Minute; + } + + if (Precision >= TimeUtils::Minute) + { + if (!bIsSpaceNeeded) + { + Str = TEXT("~0"); + } + return Str; + } + + //TestOptimizationIssue(); + + ////float Log10 = -FMath::LogX(10.0f, static_cast(Precision)); + //double Log10 = -log10(Precision); + //int32 Digits = (Log10 > 0) ? FMath::CeilToInt(Log10) : 0; + + static const double DigitThresholds[] = + { + TimeUtils::Second, // 0 digits + TimeUtils::Milisecond * 100, // 1 digit + TimeUtils::Milisecond * 10, // 2 digits + TimeUtils::Milisecond, // 3 digits + TimeUtils::Microsecond * 100, // 4 digits + TimeUtils::Microsecond * 10, // 5 digits + TimeUtils::Microsecond, // 6 digits + TimeUtils::Nanosecond * 100, // 7 digits + TimeUtils::Nanosecond * 10, // 8 digits + TimeUtils::Nanosecond, // 9 digits + TimeUtils::Picosecond * 100, // 10 digits + TimeUtils::Picosecond * 10, // 11 digits + TimeUtils::Picosecond, // 12 digits + 0 // 13 digits + }; + int32 Digits = 0; + while (Precision < DigitThresholds[Digits]) + { + Digits++; + } + + if (Digits == 0) + { + int32 Seconds = static_cast(Time / TimeUtils::Second + 0.5); + if (Seconds > 0) + { + if (bIsSpaceNeeded) + { + Str += TEXT(" "); + } + Str += FString::Printf(TEXT("%ds"), Seconds); + } + else if (!bIsSpaceNeeded) + { + Str += TEXT("~0"); + } + } + //else if (Digits <= 9) + //{ + // if (bIsSpaceNeeded) + // { + // Str += TEXT(" "); + // } + // int32 Seconds = static_cast(Time / TimeUtils::Second); + // Time -= static_cast(Seconds) * TimeUtils::Second; + // int64 SubSeconds = static_cast(Time * FMath::Pow(10.0f, Digits) + 0.5); + // Str += FString::Printf(TEXT("%d.%0*llds"), Seconds, Digits, SubSeconds); + //} + else + { + if (bIsSpaceNeeded) + { + Str += TEXT(" "); + } + Str += FString::Printf(TEXT("%.*fs"), Digits, Time); + } + + return Str; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FString FormatTimeHMS(const double Time, const double Precision) +{ + //TODO: DD:HH:MM:SS.mmm.uuu.nnn.ppp + return FormatTime(Time, Precision); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SplitTime(const double InTime, FTimeSplit& OutTimeSplit) +{ + double Time = InTime; + + if (FMath::IsNearlyZero(Time, TimeUtils::Picosecond)) + { + OutTimeSplit.bIsZero = true; + OutTimeSplit.bIsNegative = false; + OutTimeSplit.bIsInfinite = false; + return; + } + + if (FMath::IsNegativeDouble(Time)) + { + Time = -Time; + OutTimeSplit.bIsNegative = true; + } + else + { + OutTimeSplit.bIsNegative = false; + } + + if (Time == DBL_MAX || Time == std::numeric_limits::infinity()) + { + OutTimeSplit.bIsZero = false; + OutTimeSplit.bIsInfinite = true; + for (int32 Index = 0; Index < 8; ++Index) + { + OutTimeSplit.Units[Index] = 0; + } + return; + } + + OutTimeSplit.bIsInfinite = false; + bool bIsZero = true; // assume true and see if any split unit is != 0 + + const double Days = FMath::FloorToDouble(Time / TimeUtils::Day); + OutTimeSplit.Days = static_cast(Days); + if (OutTimeSplit.Days > 0) + { + Time -= Days * TimeUtils::Day; + bIsZero = false; + } + + const double Hours = FMath::FloorToDouble(Time / TimeUtils::Hour); + OutTimeSplit.Hours = static_cast(Hours); + if (OutTimeSplit.Hours > 0) + { + Time -= Hours * TimeUtils::Hour; + } + + const double Minutes = FMath::FloorToDouble(Time / TimeUtils::Minute); + OutTimeSplit.Minutes = static_cast(Minutes); + if (OutTimeSplit.Minutes > 0) + { + Time -= Minutes * TimeUtils::Minute; + bIsZero = false; + } + + const double Seconds = FMath::FloorToDouble(Time / TimeUtils::Second); + OutTimeSplit.Seconds = static_cast(Seconds); + if (OutTimeSplit.Seconds > 0) + { + Time -= Seconds * TimeUtils::Second; + bIsZero = false; + } + + const double Miliseconds = FMath::FloorToDouble(Time / TimeUtils::Milisecond); + OutTimeSplit.Miliseconds = static_cast(Miliseconds); + if (OutTimeSplit.Miliseconds > 0) + { + Time -= Miliseconds * TimeUtils::Milisecond; + bIsZero = false; + } + + const double Microseconds = FMath::FloorToDouble(Time / TimeUtils::Microsecond); + OutTimeSplit.Microseconds = static_cast(Microseconds); + if (OutTimeSplit.Microseconds > 0) + { + Time -= Microseconds * TimeUtils::Microsecond; + bIsZero = false; + } + + const double Nanoseconds = FMath::FloorToDouble(Time / TimeUtils::Nanosecond); + OutTimeSplit.Nanoseconds = static_cast(Nanoseconds); + if (OutTimeSplit.Nanoseconds > 0) + { + Time -= Nanoseconds * TimeUtils::Nanosecond; + bIsZero = false; + } + + const double Picoseconds = FMath::FloorToDouble(Time / TimeUtils::Picosecond); + OutTimeSplit.Picoseconds = static_cast(Picoseconds); + if (OutTimeSplit.Picoseconds > 0) + { + Time -= Picoseconds * TimeUtils::Picosecond; + bIsZero = false; + } + + OutTimeSplit.bIsZero = bIsZero; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FString FormatTimeSplit(const FTimeSplit& InTimeSplit, const double Precision) +{ + FString Str; + + if (InTimeSplit.bIsZero) + { + Str = TEXT("0"); + return Str; + } + + if (InTimeSplit.bIsNegative) + { + Str = TEXT("-"); + } + + if (InTimeSplit.bIsInfinite) + { + Str += TEXT("∞"); + return Str; + } + + bool bIsSpaceNeeded = false; + + if (InTimeSplit.Days > 0) + { + Str += FString::Printf(TEXT("%dd"), InTimeSplit.Days); + bIsSpaceNeeded = true; + } + + if (Precision >= TimeUtils::Day) + { + if (!bIsSpaceNeeded) + { + Str = TEXT("~0"); + } + return Str; + } + + if (InTimeSplit.Hours > 0) + { + if (bIsSpaceNeeded) + { + Str += TEXT(" "); + } + Str += FString::Printf(TEXT("%dh"), InTimeSplit.Hours); + bIsSpaceNeeded = true; + } + + if (Precision >= TimeUtils::Hour) + { + if (!bIsSpaceNeeded) + { + Str = TEXT("~0"); + } + return Str; + } + + if (InTimeSplit.Minutes > 0) + { + if (bIsSpaceNeeded) + { + Str += TEXT(" "); + } + Str += FString::Printf(TEXT("%dm"), InTimeSplit.Minutes); + bIsSpaceNeeded = true; + } + + if (Precision >= TimeUtils::Minute) + { + if (!bIsSpaceNeeded) + { + Str = TEXT("~0"); + } + return Str; + } + + if (InTimeSplit.Seconds > 0) + { + if (bIsSpaceNeeded) + { + Str += TEXT(" "); + } + Str += FString::Printf(TEXT("%ds"), InTimeSplit.Seconds); + bIsSpaceNeeded = true; + } + + if (Precision >= TimeUtils::Second) + { + if (!bIsSpaceNeeded) + { + Str = TEXT("~0"); + } + return Str; + } + + if (InTimeSplit.Miliseconds > 0) + { + if (bIsSpaceNeeded) + { + Str += TEXT(" "); + } + Str += FString::Printf(TEXT("%dms"), InTimeSplit.Miliseconds); + bIsSpaceNeeded = true; + } + + if (Precision >= TimeUtils::Milisecond) + { + if (!bIsSpaceNeeded) + { + Str = TEXT("~0"); + } + return Str; + } + + if (InTimeSplit.Microseconds > 0) + { + if (bIsSpaceNeeded) + { + Str += TEXT(" "); + } + Str += FString::Printf(TEXT("%dµs"), InTimeSplit.Microseconds); + bIsSpaceNeeded = true; + } + + if (Precision >= TimeUtils::Microsecond) + { + if (!bIsSpaceNeeded) + { + Str = TEXT("~0"); + } + return Str; + } + + if (InTimeSplit.Nanoseconds > 0) + { + if (bIsSpaceNeeded) + { + Str += TEXT(" "); + } + Str += FString::Printf(TEXT("%dns"), InTimeSplit.Nanoseconds); + bIsSpaceNeeded = true; + } + + if (Precision >= TimeUtils::Nanosecond) + { + if (!bIsSpaceNeeded) + { + Str = TEXT("~0"); + } + return Str; + } + + if (InTimeSplit.Picoseconds > 0) + { + if (bIsSpaceNeeded) + { + Str += TEXT(" "); + } + Str += FString::Printf(TEXT("%dps"), InTimeSplit.Picoseconds); + bIsSpaceNeeded = true; + } + + if (!bIsSpaceNeeded) + { + Str = TEXT("~0"); + } + + return Str; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FString FormatTimeSplit(const double Time, const double Precision) +{ + FTimeSplit TimeSplit; + SplitTime(Time, TimeSplit); + return FormatTimeSplit(TimeSplit, Precision); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void TestTimeFormatting() +{ + double T1 = 1 * TimeUtils::Day + 2 * TimeUtils::Hour + 3 * TimeUtils::Minute + 4.567890123456789; + FString S1 = FormatTime(T1, TimeUtils::Day); + //UE_LOG(TimingProfiler, Log, TEXT("D-T: %s"), *S1); + FString S2 = FormatTime(T1, TimeUtils::Hour); + //UE_LOG(TimingProfiler, Log, TEXT("H-T: %s"), *S2); + FString S3 = FormatTime(T1, TimeUtils::Minute); + //UE_LOG(TimingProfiler, Log, TEXT("M-T: %s"), *S3); + for (double P = 10.0; P >= TimeUtils::Picosecond; P /= 10.0) + { + FString SP = FormatTime(T1, P); + //UE_LOG(TimingProfiler, Log, TEXT("P:%g T: %s"), P, *SP); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void TestTimeAutoFormatting() +{ + static bool Once = false; + if (Once) return; + Once = true; + + struct FTestTimeAutoData + { + double T; + const TCHAR* Msg; + }; + + static const FTestTimeAutoData Data[] = + { + { TimeUtils::Minute, TEXT("1m") }, + { TimeUtils::Second * 59.99, TEXT("59.99s") }, + { TimeUtils::Second * 59.95, TEXT("59.95s") }, + { 0, TEXT("[threshold 1m / 59.9s]") }, + { TimeUtils::Second * 59.94, TEXT("59.94s") }, + { TimeUtils::Second * 59.9, TEXT("59.9s") }, + { TimeUtils::Second * 10, TEXT("10s") }, + { TimeUtils::Second, TEXT("1s") }, + { TimeUtils::Milisecond * 999.99, TEXT("999.99ms") }, + { TimeUtils::Milisecond * 999.95, TEXT("999.95ms") }, + { 0, TEXT("[threshold 1s / 999.9 ms]") }, + { TimeUtils::Milisecond * 999.94, TEXT("999.94ms") }, + { TimeUtils::Milisecond * 999.9, TEXT("999.9ms") }, + { TimeUtils::Milisecond * 999, TEXT("999ms") }, + { TimeUtils::Milisecond * 100, TEXT("100ms") }, + { TimeUtils::Milisecond * 10, TEXT("10ms") }, + { TimeUtils::Milisecond * 1.55, TEXT("1.55ms") }, + { TimeUtils::Milisecond * 1.5, TEXT("1.5ms") }, + { TimeUtils::Milisecond * 1.05, TEXT("1.05ms") }, + { TimeUtils::Milisecond, TEXT("1ms") }, + { TimeUtils::Microsecond * 999.99, TEXT("999.99µs") }, + { TimeUtils::Microsecond * 999.95, TEXT("999.95µs") }, + { 0, TEXT("[threshold 1 ms / 999.9 µs]") }, + { TimeUtils::Microsecond * 999.94, TEXT("999.94µs") }, + { TimeUtils::Microsecond * 999.9, TEXT("999.9µs") }, + { TimeUtils::Microsecond * 999, TEXT("999µs") }, + { TimeUtils::Microsecond * 100, TEXT("100µs") }, + { TimeUtils::Microsecond * 10, TEXT("10µs") }, + { TimeUtils::Microsecond, TEXT("1µs") }, + { TimeUtils::Nanosecond * 999.9, TEXT("999.9ns") }, + { TimeUtils::Nanosecond * 999.5, TEXT("999.5ns") }, + { 0, TEXT("[threshold 1 µs / 999 ns]") }, + { TimeUtils::Nanosecond * 999.4, TEXT("999.4ns") }, + { TimeUtils::Nanosecond * 999, TEXT("999ns") }, + { TimeUtils::Nanosecond * 100, TEXT("100ns") }, + { TimeUtils::Nanosecond * 10, TEXT("10ns") }, + { TimeUtils::Nanosecond, TEXT("1ns") }, + { TimeUtils::Picosecond * 999.9, TEXT("999.9ps") }, + { TimeUtils::Picosecond * 999.5, TEXT("999.5ps") }, + { 0, TEXT("[threshold 1 ns / 999 ps]") }, + { TimeUtils::Picosecond * 999.4, TEXT("999.4ps") }, + { TimeUtils::Picosecond * 999, TEXT("999ps") }, + { TimeUtils::Picosecond * 100, TEXT("100ps") }, + { TimeUtils::Picosecond * 10, TEXT("10ps") }, + { TimeUtils::Picosecond, TEXT("1ps") }, + { TimeUtils::Picosecond * 0.1, TEXT("0.1ps") }, + }; + const int32 DataCount = sizeof(Data) / sizeof(FTestTimeAutoData); + + for (int32 Index = 0; Index < DataCount; ++Index) + { + FString Str = FormatTimeAuto(Data[Index].T); + //UE_LOG(TimingProfiler, Log, TEXT("%s : %s"), Data[Index].Msg, *Str); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +int32 GetNumDigits(const double Precision) +{ + //float Log10 = -log10(Precision); + float Log10 = -FMath::LogX(10.0f, static_cast(Precision)); + int32 D = (Log10 > 0) ? FMath::CeilToInt(Log10) : 0; + return D; +} + +PRAGMA_DISABLE_OPTIMIZATION +int32 GetNumDigitsOptDisabled(const double Precision) +{ + //double Log10 = -log10(Precision); + float Log10 = -FMath::LogX(10.0f, static_cast(Precision)); + int32 D = (Log10 > 0) ? FMath::CeilToInt(Log10) : 0; + return D; +} +PRAGMA_ENABLE_OPTIMIZATION + +PRAGMA_DISABLE_OPTIMIZATION +void TestOptimizationIssue() +{ + constexpr double Ns = 0.000000001; + int32 D1 = GetNumDigits(Ns); + int32 D2 = GetNumDigitsOptDisabled(Ns); + //UE_LOG(TimingProfiler, Log, TEXT("D1 = %d, D2 = %d"), D1, D2); + ensure(D1 == 9); // 10 ? + ensure(D1 == D2); +} +PRAGMA_ENABLE_OPTIMIZATION + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +} // namespace TimeUtils diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Common/TimeUtils.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Common/TimeUtils.h new file mode 100644 index 000000000000..5ccb87ff7dc8 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Common/TimeUtils.h @@ -0,0 +1,68 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Containers/UnrealString.h" + +/* +double is a 64 bit IEEE 754 double precision Floating Point Number +1 bit for the sign, 11 bits for the exponent, and 52* bits for the value +15.5 decimal digits of precision (max value*: ‭4`503`599`627`370`495) +|------------------------------------------------------------------------------ +| 900`000.000`000`001 --> up to 10 days with 1 ns precision +| 90`000.000`000`000`1 --> up to 25 hours with 0.1 ns precision (100 ps) +| 9`000.000`000`000`01 --> up to 2.5 hours with 0.01 ns precision (10 ps) +| 900.000`000`000`001 --> up to 15 min with 1 ps precision +|------------------------------------------------------------------------------ +| 3`600.000`000`000`001 --> 1 ps precision at 1 hour : NOT OK (16 digits)!!! +| 86`400.000`000`000`001 --> 1 ps precision at 1 day : NOT OK (17 digits)!!! +|------------------------------------------------------------------------------ +*/ + +namespace TimeUtils +{ + static constexpr double Picosecond = 0.000000000001; + static constexpr double Nanosecond = 0.000000001; + static constexpr double Microsecond = 0.000001; + static constexpr double Milisecond = 0.001; + static constexpr double Second = 1.0; + static constexpr double Minute = 60.0; + static constexpr double Hour = 3600.0; + static constexpr double Day = 86400.0; + static constexpr double Week = 604800.0; + + struct FTimeSplit + { + union + { + int32 Units[8]; // Units[0] = Days, Units[1] = Hours, ..., Units[7] = Picoseconds + struct + { + int32 Days; + int32 Hours; + int32 Minutes; + int32 Seconds; + int32 Miliseconds; + int32 Microseconds; + int32 Nanoseconds; + int32 Picoseconds; + }; + }; + bool bIsZero; + bool bIsNegative; + bool bIsInfinite; + }; + + FString FormatTimeAuto(const double Duration); + FString FormatTimeMs(const double Duration, const int32 NumDigits = 2, bool bAddTimeUnit = false); + FString FormatTime(const double Time, const double Precision = 0.0); + FString FormatTimeHMS(const double Time, const double Precision = 0.0); + void SplitTime(const double Time, FTimeSplit& OutTimeSplit); + FString FormatTimeSplit(const FTimeSplit& TimeSplit, const double Precision = 0.0); + FString FormatTimeSplit(const double Time, const double Precision = 0.0); + + void TestTimeFormatting(); + void TestTimeAutoFormatting(); + void TestOptimizationIssue(); +} diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsCommands.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsCommands.cpp new file mode 100644 index 000000000000..30aecfc57913 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsCommands.cpp @@ -0,0 +1,205 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Insights/InsightsCommands.h" + +#include "DesktopPlatformModule.h" +#include "EditorStyleSet.h" +#include "Framework/Application/SlateApplication.h" +//#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "HAL/FileManagerGeneric.h" +#include "Misc/Paths.h" + +// Insights +#include "Insights/InsightsManager.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define LOCTEXT_NAMESPACE "FInsightsCommands" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FInsightsCommands +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FInsightsCommands::FInsightsCommands() + : TCommands( + TEXT("InsightsCommands"), // Context name for fast lookup + NSLOCTEXT("Contexts", "InsightsCommand", "Insights Command"), // Localized context name for displaying + NAME_None, // Parent + FEditorStyle::GetStyleSetName() // Icon Style Set + ) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// UI_COMMAND takes long for the compile to optimize +PRAGMA_DISABLE_OPTIMIZATION +void FInsightsCommands::RegisterCommands() +{ + UI_COMMAND(InsightsManager_Live, "Live", "Loads profiler data from a live or last trace session", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(InsightsManager_Load, "Load...", "Loads profiler data from a trace file", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control, EKeys::L)); + UI_COMMAND(ToggleDebugInfo, "Debug", "Toggles the display of debug info", EUserInterfaceActionType::ToggleButton, FInputChord(EModifierKey::Control, EKeys::D)); + UI_COMMAND(OpenSettings, "Settings", "Opens the Unreal Insights settings", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control, EKeys::O)); +} +PRAGMA_ENABLE_OPTIMIZATION + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// ToggleDebugInfo +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FInsightsActionManager::Map_ToggleDebugInfo_Global() +{ + This->CommandList->MapAction(This->GetCommands().ToggleDebugInfo, ToggleDebugInfo_Custom()); +} + +const FUIAction FInsightsActionManager::ToggleDebugInfo_Custom() +{ + FUIAction UIAction; + UIAction.ExecuteAction = FExecuteAction::CreateRaw(this, &FInsightsActionManager::ToggleDebugInfo_Execute); + UIAction.CanExecuteAction = FCanExecuteAction::CreateRaw(this, &FInsightsActionManager::ToggleDebugInfo_CanExecute); + UIAction.GetActionCheckState = FGetActionCheckState::CreateRaw(this, &FInsightsActionManager::ToggleDebugInfo_GetCheckState); + return UIAction; +} + +void FInsightsActionManager::ToggleDebugInfo_Execute() +{ + const bool bIsDebugInfoEnabled = !This->IsDebugInfoEnabled(); + This->SetDebugInfo(bIsDebugInfoEnabled); +} + +bool FInsightsActionManager::ToggleDebugInfo_CanExecute() const +{ + return FInsightsManager::Get()->GetSession().IsValid(); +} + +ECheckBoxState FInsightsActionManager::ToggleDebugInfo_GetCheckState() const +{ + const bool bIsDebugInfoEnabled = This->IsDebugInfoEnabled(); + return bIsDebugInfoEnabled ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// InsightsManager_Live +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FInsightsActionManager::Map_InsightsManager_Live() +{ + FUIAction UIAction; + UIAction.ExecuteAction = FExecuteAction::CreateRaw(this, &FInsightsActionManager::InsightsManager_Live_Execute); + UIAction.CanExecuteAction = FCanExecuteAction::CreateRaw(this, &FInsightsActionManager::InsightsManager_Live_CanExecute); + + This->CommandList->MapAction(This->GetCommands().InsightsManager_Live, UIAction); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FInsightsActionManager::InsightsManager_Live_Execute() +{ + This->LoadLastLiveSession(); + if (!This->GetSession().IsValid()) + { + This->LoadLastSession(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool FInsightsActionManager::InsightsManager_Live_CanExecute() const +{ + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// InsightsManager_Load +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FInsightsActionManager::Map_InsightsManager_Load() +{ + FUIAction UIAction; + UIAction.ExecuteAction = FExecuteAction::CreateRaw(this, &FInsightsActionManager::InsightsManager_Load_Execute); + UIAction.CanExecuteAction = FCanExecuteAction::CreateRaw(this, &FInsightsActionManager::InsightsManager_Load_CanExecute); + + This->CommandList->MapAction(This->GetCommands().InsightsManager_Load, UIAction); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FInsightsActionManager::InsightsManager_Load_Execute() +{ + //const FString ProfilingDirectory(FPaths::ConvertRelativePathToFull(*FPaths::ProfilingDir())); + TSharedRef SessionService = This->GetSessionService(); + const FString ProfilingDirectory(FPaths::ConvertRelativePathToFull(SessionService->GetLocalSessionDirectory())); + + TArray OutFiles; + + bool bOpened = false; + + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + if (DesktopPlatform != nullptr) + { + FSlateApplication::Get().CloseToolTip(); + + bOpened = DesktopPlatform->OpenFileDialog + ( + FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), + LOCTEXT("LoadTrace_FileDesc", "Open trace file...").ToString(), + ProfilingDirectory, + TEXT(""), + LOCTEXT("LoadTrace_FileFilter", "Trace files (*.utrace)|*.utrace|All files (*.*)|*.*").ToString(), + EFileDialogFlags::None, + OutFiles + ); + } + + if (bOpened == true) + { + if (OutFiles.Num() == 1) + { + This->LoadTraceFile(OutFiles[0]); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool FInsightsActionManager::InsightsManager_Load_CanExecute() const +{ + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// OpenSettings +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FInsightsActionManager::Map_OpenSettings_Global() +{ + This->CommandList->MapAction(This->GetCommands().OpenSettings, OpenSettings_Custom()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FUIAction FInsightsActionManager::OpenSettings_Custom() +{ + FUIAction UIAction; + UIAction.ExecuteAction = FExecuteAction::CreateRaw(this, &FInsightsActionManager::OpenSettings_Execute); + UIAction.CanExecuteAction = FCanExecuteAction::CreateRaw(this, &FInsightsActionManager::OpenSettings_CanExecute); + return UIAction; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FInsightsActionManager::OpenSettings_Execute() +{ + This->OpenSettings(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool FInsightsActionManager::OpenSettings_CanExecute() const +{ + return !This->Settings.IsEditing(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsCommands.h b/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsCommands.h new file mode 100644 index 000000000000..7b9872cfe7a7 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsCommands.h @@ -0,0 +1,102 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Commands/UIAction.h" +#include "Framework/Commands/Commands.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * Class that holds all profiler commands. + */ +class FInsightsCommands : public TCommands +{ +public: + /** Default constructor. */ + FInsightsCommands(); + + /** Initialize commands. */ + virtual void RegisterCommands() override; + +public: + ////////////////////////////////////////////////// + // Global commands need to implement following method: + // void Map__Global(); + // Custom commands needs to implement also the following method: + // const FUIAction _Custom(...) const; + ////////////////////////////////////////////////// + + /** Load profiler data from a live trace session. Global version. */ + TSharedPtr InsightsManager_Live; + + /** Load profiler data from a trace file. Global version. */ + TSharedPtr InsightsManager_Load; + + /** Toggles the debug info. Global and custom command. */ + TSharedPtr ToggleDebugInfo; + + /** Open settings for the profiler manager. */ + TSharedPtr OpenSettings; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * Class that provides helper functions for the commands to avoid cluttering manager with many small functions. + * Can't contain any variables. Directly operates on the manager instance. + */ +class FInsightsActionManager +{ + friend class FInsightsManager; + +private: + /** Private constructor. */ + FInsightsActionManager(class FInsightsManager* Instance) + : This(Instance) + {} + + ////////////////////////////////////////////////// + // InsightsManager_Live + +public: + void Map_InsightsManager_Live(); /**< Maps UI command info InsightsManager_Live with the specified UI command list. */ +protected: + void InsightsManager_Live_Execute(); /**< Handles FExecuteAction for InsightsManager_Live. */ + bool InsightsManager_Live_CanExecute() const; /**< Handles FCanExecuteAction for InsightsManager_Live. */ + + ////////////////////////////////////////////////// + // InsightsManager_Load + +public: + void Map_InsightsManager_Load(); /**< Maps UI command info InsightsManager_Load with the specified UI command list. */ +protected: + void InsightsManager_Load_Execute(); /**< Handles FExecuteAction for InsightsManager_Load. */ + bool InsightsManager_Load_CanExecute() const; /**< Handles FCanExecuteAction for InsightsManager_Load. */ + + ////////////////////////////////////////////////// + // ToggleDebugInfo + +public: + void Map_ToggleDebugInfo_Global(); /**< Maps UI command info ToggleDebugInfo with the specified UI command list. */ + const FUIAction ToggleDebugInfo_Custom(); /**< UI action for ToggleDebugInfo command. */ +protected: + void ToggleDebugInfo_Execute(); /**< Handles FExecuteAction for ToggleDebugInfo. */ + bool ToggleDebugInfo_CanExecute() const; /**< Handles FCanExecuteAction for ToggleDebugInfo. */ + ECheckBoxState ToggleDebugInfo_GetCheckState() const; /**< Handles FGetActionCheckState for ToggleDebugInfo. */ + + ////////////////////////////////////////////////// + // OpenSettings + +public: + void Map_OpenSettings_Global(); /**< Maps UI command info OpenSettings with the specified UI command list. */ + const FUIAction OpenSettings_Custom(); /**< UI action for OpenSettings command. */ +protected: + void OpenSettings_Execute(); /**< Handles FExecuteAction for OpenSettings. */ + bool OpenSettings_CanExecute() const; /**< Handles FCanExecuteAction for OpenSettings. */ + + ////////////////////////////////////////////////// + +protected: + /** Reference to the global instance of the Insights manager. */ + class FInsightsManager* This; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsManager.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsManager.cpp new file mode 100644 index 000000000000..556be683f507 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsManager.cpp @@ -0,0 +1,299 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "InsightsManager.h" + +#include "Framework/Application/SlateApplication.h" +#include "Modules/ModuleManager.h" +#include "Templates/ScopedPointer.h" +#include "Templates/UniquePtr.h" + +// Insights +#include "Insights/TimingProfilerManager.h" +#include "Insights/Widgets/SStartPageWindow.h" +#include "Insights/Widgets/STimingProfilerWindow.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define LOCTEXT_NAMESPACE "InsightsManager" + +const FName FInsightsManagerTabs::StartPageTabId(TEXT("StartPage")); +const FName FInsightsManagerTabs::TimingProfilerTabId(TEXT("TimingProfiler")); +const FName FInsightsManagerTabs::IoProfilerTabId(TEXT("IoProfiler")); + +TSharedPtr FInsightsManager::Instance = nullptr; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FInsightsManager::FInsightsManager(TSharedRef InTraceAnalysisService, + TSharedRef InTraceSessionService, + TSharedRef InTraceModuleService) + : AnalysisService(InTraceAnalysisService) + , SessionService(InTraceSessionService) + , ModuleService(InTraceModuleService) + , CommandList(new FUICommandList()) + , ActionManager(this) + , Settings() + , bIsDebugInfoEnabled(false) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FInsightsManager::PostConstructor() +{ + // Register tick functions. + //OnTick = FTickerDelegate::CreateSP(this, &FInsightsManager::Tick); + //OnTickHandle = FTicker::GetCoreTicker().AddTicker(OnTick, 1.0f); + + FInsightsCommands::Register(); + BindCommands(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FInsightsManager::BindCommands() +{ + ActionManager.Map_InsightsManager_Live(); + ActionManager.Map_InsightsManager_Load(); + ActionManager.Map_ToggleDebugInfo_Global(); + ActionManager.Map_OpenSettings_Global(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FInsightsManager::~FInsightsManager() +{ + FInsightsCommands::Unregister(); + + // Unregister tick function. + //FTicker::GetCoreTicker().RemoveTicker(OnTickHandle); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedPtr FInsightsManager::Get() +{ + return FInsightsManager::Instance; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedPtr FInsightsManager::GetSession() const +{ + return Session; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const TSharedRef FInsightsManager::GetCommandList() const +{ + return CommandList; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FInsightsCommands& FInsightsManager::GetCommands() +{ + return FInsightsCommands::Get(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FInsightsActionManager& FInsightsManager::GetActionManager() +{ + return FInsightsManager::Instance->ActionManager; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FInsightsSettings& FInsightsManager::GetSettings() +{ + return FInsightsManager::Instance->Settings; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool FInsightsManager::Tick(float DeltaTime) +{ + //SCOPE_CYCLE_COUNTER(STAT_IM_Tick); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FInsightsManager::ResetSession() +{ + if (Session.IsValid()) + { + Session.Reset(); + OnSessionChanged(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FInsightsManager::OnSessionChanged() +{ + if (TSharedPtr TimingProfilerManager = FTimingProfilerManager::Get()) + { + // FIXME: make TimingProfilerManager to register to SessionChangedEvent instead + TimingProfilerManager->OnSessionChanged(); + } + + SessionChangedEvent.Broadcast(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FInsightsManager::SpawnAndActivateTabs() +{ + // Open tabs for profilers. + if (FGlobalTabmanager::Get()->HasTabSpawner(FInsightsManagerTabs::TimingProfilerTabId)) + { + FGlobalTabmanager::Get()->InvokeTab(FInsightsManagerTabs::TimingProfilerTabId); + } + if (FGlobalTabmanager::Get()->HasTabSpawner(FInsightsManagerTabs::IoProfilerTabId)) + { + FGlobalTabmanager::Get()->InvokeTab(FInsightsManagerTabs::IoProfilerTabId); + } + + // Ensure Timing Insights / Timing View is the active tab / view. + if (TSharedPtr TimingInsightsTab = FGlobalTabmanager::Get()->FindExistingLiveTab(FInsightsManagerTabs::TimingProfilerTabId)) + { + TimingInsightsTab->ActivateInParent(ETabActivationCause::SetDirectly); + + //TOOD: FTimingProfilerManager::Get()->SpawnAndActivateTabs(); + TSharedPtr Wnd = FTimingProfilerManager::Get()->GetProfilerWindow(); + if (Wnd) + { + TSharedPtr TabManager = Wnd->GetTabManager(); + + if (TSharedPtr TimingViewTab = TabManager->FindExistingLiveTab(FTimingProfilerTabs::TimingViewID)) + { + TimingViewTab->ActivateInParent(ETabActivationCause::SetDirectly); + FSlateApplication::Get().SetKeyboardFocus(TimingViewTab->GetContent()); + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool FInsightsManager::IsAnyLiveSessionAvailable(Trace::FSessionHandle& OutLastLiveSessionHandle) const +{ + TArray LiveSessions; + SessionService->GetLiveSessions(LiveSessions); + + if (LiveSessions.Num() > 0) + { + OutLastLiveSessionHandle = LiveSessions.Last(); + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool FInsightsManager::IsAnySessionAvailable(Trace::FSessionHandle& OutLastSessionHandle) const +{ + TArray AvailableSessions; + SessionService->GetAvailableSessions(AvailableSessions); + + if (AvailableSessions.Num() > 0) + { + OutLastSessionHandle = AvailableSessions.Last(); + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FInsightsManager::LoadLastLiveSession() +{ + ResetSession(); + + TArray AvailableSessions; + SessionService->GetAvailableSessions(AvailableSessions); + + // Iterate in reverse order as we want the most recent live session first. + for (int32 SessionIndex = AvailableSessions.Num() - 1; SessionIndex >= 0; --SessionIndex) + { + Trace::FSessionHandle SessionHandle = AvailableSessions[SessionIndex]; + + Trace::FSessionInfo SessionInfo; + SessionService->GetSessionInfo(SessionHandle, SessionInfo); + + if (SessionInfo.bIsLive) + { + LoadSession(SessionHandle); + break; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FInsightsManager::LoadLastSession() +{ + ResetSession(); + + TArray AvailableSessions; + SessionService->GetAvailableSessions(AvailableSessions); + + if (AvailableSessions.Num() > 0) + { + LoadSession(AvailableSessions.Last()); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FInsightsManager::LoadSession(Trace::FSessionHandle SessionHandle) +{ + ResetSession(); + + Trace::FSessionInfo SessionInfo; + SessionService->GetSessionInfo(SessionHandle, SessionInfo); + + TUniquePtr DataStream(SessionService->OpenSessionStream(SessionHandle)); + if (DataStream) + { + Session = AnalysisService->StartAnalysis(SessionInfo.Name, MoveTemp(DataStream)); + SpawnAndActivateTabs(); + OnSessionChanged(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FInsightsManager::LoadTraceFile(const FString& TraceFilepath) +{ + ResetSession(); + + TUniquePtr DataStream(SessionService->OpenSessionFromFile(*TraceFilepath)); + if (DataStream) + { + Session = AnalysisService->StartAnalysis(*TraceFilepath, MoveTemp(DataStream)); + SpawnAndActivateTabs(); + OnSessionChanged(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FInsightsManager::OpenSettings() +{ + TSharedPtr Wnd = GetStartPageWindow(); + if (Wnd.IsValid()) + { + Wnd->OpenSettings(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsManager.h b/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsManager.h new file mode 100644 index 000000000000..209a2c329d3c --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsManager.h @@ -0,0 +1,232 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Containers/Ticker.h" +#include "CoreMinimal.h" +#include "Framework/Commands/UICommandList.h" +#include "TraceServices/AnalysisService.h" +#include "TraceServices/ModuleService.h" +#include "TraceServices/SessionService.h" + +// Insights +#include "Insights/InsightsCommands.h" +#include "Insights/InsightsSettings.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class SStartPageWindow; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct FInsightsManagerTabs +{ + static const FName StartPageTabId; + static const FName TimingProfilerTabId; + static const FName IoProfilerTabId; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/** Enumerates loading a trace file progress states. */ + +enum class ELoadingProgressState +{ + Started, + InProgress, + Loaded, + Failed, + Cancelled, + + /** Invalid enum type, may be used as a number of enumerations. */ + InvalidOrMax, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +enum class EInsightsNotificationType +{ + LoadingTraceFile, + + /** Invalid enum type, may be used as a number of enumerations. */ + InvalidOrMax, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * This class manages following areas: + * Connecting/disconnecting to source trace + * Lifetime of all other managers (specific profilers) + * Global Unreal Insights application state and settings + */ +class FInsightsManager + : public TSharedFromThis +{ + friend class FInsightsActionManager; + +public: + /** Creates the main manager, only one instance can exist. */ + FInsightsManager(TSharedRef TraceAnalysisService, + TSharedRef SessionService, + TSharedRef TraceModuleService); + + /** Virtual destructor. */ + virtual ~FInsightsManager(); + + /** + * Creates an instance of the main manager and initializes global instance with the previously created instance of the manager. + * @param TraceAnalysisService The trace analysis service + * @param TraceSessionService The trace session service + * @param TraceModuleService The trace module service + */ + static TSharedPtr Initialize(TSharedRef TraceAnalysisService, + TSharedRef TraceSessionService, + TSharedRef TraceModuleService) + { + if (FInsightsManager::Instance.IsValid()) + { + FInsightsManager::Instance.Reset(); + } + + FInsightsManager::Instance = MakeShareable(new FInsightsManager(TraceAnalysisService, TraceSessionService, TraceModuleService)); + FInsightsManager::Instance->PostConstructor(); + + return FInsightsManager::Instance; + } + + /** Shutdowns the main manager. */ + void Shutdown() + { + FInsightsManager::Instance.Reset(); + } + + /** @return the global instance of the main manager (FInsightsManager). */ + static TSharedPtr Get(); + + TSharedRef GetAnalysisService() const { return AnalysisService; } + TSharedRef GetSessionService() const { return SessionService; } + TSharedRef GetModuleService() const { return ModuleService; } + + /** @return an instance of the trace analysis session. */ + TSharedPtr GetSession() const; + + /** @returns UI command list for the main manager. */ + const TSharedRef GetCommandList() const; + + /** @return an instance of the main commands. */ + static const FInsightsCommands& GetCommands(); + + /** @return an instance of the main action manager. */ + static FInsightsActionManager& GetActionManager(); + + /** @return an instance of the main settings. */ + static FInsightsSettings& GetSettings(); + + void AssignStartPageWindow(const TSharedRef& InStartPageWindow) + { + StartPageWindow = InStartPageWindow; + } + + /** + * Converts profiler window weak pointer to a shared pointer and returns it. + * Make sure the returned pointer is valid before trying to dereference it. + */ + TSharedPtr GetStartPageWindow() const + { + return StartPageWindow.Pin(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Getters and setters used by Toggle Commands. + + /** @return true, if UI is allowed to display debug info. */ + const bool IsDebugInfoEnabled() const { return bIsDebugInfoEnabled; } + void SetDebugInfo(const bool bDebugInfoEnabledState) { bIsDebugInfoEnabled = bDebugInfoEnabledState; } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + + bool IsAnyLiveSessionAvailable(Trace::FSessionHandle& OutLastLiveSessionHandle) const; + bool IsAnySessionAvailable(Trace::FSessionHandle& OutLastSessionHandle) const; + + /** Creates a new analysis session instance and loads the latest available trace session that is live. */ + void LoadLastLiveSession(); + + /** Creates a new analysis session instance and loads the latest available trace session. */ + void LoadLastSession(); + + /** + * Creates a new analysis session instance using specified session handle. + * @param SessionHandle - The handle for session to analyze + */ + void LoadSession(Trace::FSessionHandle SessionHandle); + + /** + * Creates a new analysis session instance and loads a trace file from the specified location. + * @param TraceFilepath - The path to the trace file + */ + void LoadTraceFile(const FString& TraceFilepath); + + /** Opens the Settings dialog. */ + void OpenSettings(); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // SessionChangedEvent + +public: + /** The event to execute when the session has changed. */ + DECLARE_EVENT(FTimingProfilerManager, FSessionChangedEvent); + FSessionChangedEvent& GetSessionChangedEvent() { return SessionChangedEvent; } +private: + /** The event to execute when the session has changed. */ + FSessionChangedEvent SessionChangedEvent; + + //////////////////////////////////////////////////////////////////////////////////////////////////// + +private: + /** Finishes initialization of the profiler manager. */ + void PostConstructor(); + + /** Binds our UI commands to delegates. */ + void BindCommands(); + + /** Updates this manager, done through FCoreTicker. */ + bool Tick(float DeltaTime); + + /** Resets (closes) current session instance. */ + void ResetSession(); + + void OnSessionChanged(); + + void SpawnAndActivateTabs(); + +private: + /** The delegate to be invoked when this manager ticks. */ + FTickerDelegate OnTick; + + /** Handle to the registered OnTick. */ + FDelegateHandle OnTickHandle; + + TSharedRef AnalysisService; + TSharedRef SessionService; + TSharedRef ModuleService; + + /** The trace analysis session. */ + TSharedPtr Session; + + /** List of UI commands for this manager. This will be filled by this and corresponding classes. */ + TSharedRef CommandList; + + /** An instance of the main action manager. */ + FInsightsActionManager ActionManager; + + /** An instance of the main settings. */ + FInsightsSettings Settings; + + /** A weak pointer to the Start Page window. */ + TWeakPtr StartPageWindow; + + /** If enabled, UI can display additional info for debugging purposes. */ + bool bIsDebugInfoEnabled; + + /** A shared pointer to the global instance of the main manager. */ + static TSharedPtr Instance; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsSettings.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsSettings.cpp new file mode 100644 index 000000000000..9ee6c48020db --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsSettings.cpp @@ -0,0 +1,5 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "InsightsSettings.h" + +FInsightsSettings FInsightsSettings::Defaults(true); diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsSettings.h b/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsSettings.h new file mode 100644 index 000000000000..d0ac93bb10bb --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsSettings.h @@ -0,0 +1,89 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/ConfigCacheIni.h" + +/** Contains all settings for the Unreal Insights, accessible through the main manager. */ +class FInsightsSettings +{ +public: + FInsightsSettings(bool bInIsDefault = false) + : bIsEditing(false) + , bIsDefault(bInIsDefault) + , bShowEmptyTracksByDefault(false) + { + if (!bIsDefault) + { + LoadFromConfig(); + } + } + + ~FInsightsSettings() + { + if (!bIsDefault) + { + SaveToConfig(); + } + } + + void LoadFromConfig() + { + FConfigCacheIni::LoadGlobalIniFile(SettingsIni, TEXT("UnrealInsightsSettings")); + + GConfig->GetBool(TEXT("Insights.TimingProfiler"), TEXT("bShowEmptyTracksByDefault"), bShowEmptyTracksByDefault, SettingsIni); + } + + void SaveToConfig() + { + GConfig->SetBool(TEXT("Insights.TimingProfiler"), TEXT("bShowEmptyTracksByDefault"), bShowEmptyTracksByDefault, SettingsIni); + + GConfig->Flush(false, SettingsIni); + } + + void EnterEditMode() + { + bIsEditing = true; + } + + void ExitEditMode() + { + bIsEditing = false; + } + + const bool IsEditing() const + { + return bIsEditing; + } + + const FInsightsSettings& GetDefaults() const + { + return Defaults; + } + + void ResetToDefaults() + { + bShowEmptyTracksByDefault = Defaults.bShowEmptyTracksByDefault; + } + +//protected: +public: + /** Contains default settings. */ + static FInsightsSettings Defaults; + + /** Setting filename ini. */ + FString SettingsIni; + + /** Whether profiler settings is in edit mode. */ + bool bIsEditing; + + /** Whether this instance contains defaults. */ + bool bIsDefault; + + ////////////////////////////////////////////////// + // Actual settings. + + /** If True, the Timing View will show empty tracks by default. */ + bool bShowEmptyTracksByDefault; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsStyle.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsStyle.cpp new file mode 100644 index 000000000000..74994475aa8f --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsStyle.cpp @@ -0,0 +1,99 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "InsightsStyle.h" +#include "Styling/SlateStyleRegistry.h" + +#define IMAGE_BRUSH(RelativePath, ...) FSlateImageBrush (FPaths::EngineContentDir() / "Editor/Slate"/ RelativePath + TEXT(".png"), __VA_ARGS__) +#define BOX_BRUSH(RelativePath, ...) FSlateBoxBrush (FPaths::EngineContentDir() / "Editor/Slate"/ RelativePath + TEXT(".png"), __VA_ARGS__) +#define BORDER_BRUSH(RelativePath, ...) FSlateBorderBrush(FPaths::EngineContentDir() / "Editor/Slate"/ RelativePath + TEXT(".png"), __VA_ARGS__) + +TSharedPtr FInsightsStyle::StyleInstance = nullptr; + +void FInsightsStyle::Initialize() +{ + if (!StyleInstance.IsValid()) + { + StyleInstance = Create(); + FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance); + } +} + +void FInsightsStyle::Shutdown() +{ + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance); + ensure(StyleInstance.IsUnique()); + StyleInstance.Reset(); +} + +FName FInsightsStyle::GetStyleSetName() +{ + static FName StyleSetName(TEXT("InsightsStyle")); + return StyleSetName; +} + +TSharedRef FInsightsStyle::Create() +{ + TSharedRef StyleRef = MakeShareable(new FSlateStyleSet(FInsightsStyle::GetStyleSetName())); + StyleRef->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate")); + StyleRef->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate")); + + FSlateStyleSet& Style = StyleRef.Get(); + + ////////////////////////////////////////////////// + // Icons for major components + + Style.Set("StartPage.Icon.Large", new IMAGE_BRUSH("/Icons/icon_tab_Tools_16x", FVector2D(32.0f, 32.0f))); + Style.Set("StartPage.Icon.Small", new IMAGE_BRUSH("/Icons/icon_tab_Tools_16x", FVector2D(16.0f, 16.0f))); + + Style.Set("TimingInsights.Icon.Large", new IMAGE_BRUSH("Icons/Profiler/profiler_stats_40x", FVector2D(32.0f, 32.0f))); + Style.Set("TimingInsights.Icon.Small", new IMAGE_BRUSH("Icons/Profiler/profiler_stats_40x", FVector2D(16.0f, 16.0f))); + + Style.Set("AssetLoadingInsights.Icon.Large", new IMAGE_BRUSH("Icons/Profiler/profiler_stats_40x", FVector2D(32.0f, 32.0f))); + Style.Set("AssetLoadingInsights.Icon.Small", new IMAGE_BRUSH("Icons/Profiler/profiler_stats_40x", FVector2D(16.0f, 16.0f))); + + Style.Set("Toolbar.Icon.Large", new IMAGE_BRUSH("/Icons/icon_tab_Tools_16x", FVector2D(32.0f, 32.0f))); + Style.Set("Toolbar.Icon.Small", new IMAGE_BRUSH("/Icons/icon_tab_Tools_16x", FVector2D(16.0f, 16.0f))); + + ////////////////////////////////////////////////// + // Timing Insights + + Style.Set("FramesTrack.Icon.Large", new IMAGE_BRUSH("Icons/Profiler/profiler_stats_40x", FVector2D(32.0f, 32.0f))); + Style.Set("FramesTrack.Icon.Small", new IMAGE_BRUSH("Icons/Profiler/profiler_stats_40x", FVector2D(16.0f, 16.0f))); + + Style.Set("GraphTrack.Icon.Large", new IMAGE_BRUSH("Icons/Profiler/profiler_stats_40x", FVector2D(32.0f, 32.0f))); + Style.Set("GraphTrack.Icon.Small", new IMAGE_BRUSH("Icons/Profiler/profiler_stats_40x", FVector2D(16.0f, 16.0f))); + + Style.Set("TimingView.Icon.Large", new IMAGE_BRUSH("Icons/Profiler/profiler_stats_40x", FVector2D(32.0f, 32.0f))); + Style.Set("TimingView.Icon.Small", new IMAGE_BRUSH("Icons/Profiler/profiler_stats_40x", FVector2D(16.0f, 16.0f))); + + Style.Set("TimersView.Icon.Large", new IMAGE_BRUSH("Icons/Profiler/Profiler_Data_Capture_40x", FVector2D(32.0f, 32.0f))); + Style.Set("TimersView.Icon.Small", new IMAGE_BRUSH("Icons/Profiler/Profiler_Data_Capture_40x", FVector2D(16.0f, 16.0f))); + + Style.Set("StatsCountersView.Icon.Large", new IMAGE_BRUSH("Icons/Profiler/Profiler_Data_Capture_40x", FVector2D(32.0f, 32.0f))); + Style.Set("StatsCountersView.Icon.Small", new IMAGE_BRUSH("Icons/Profiler/Profiler_Data_Capture_40x", FVector2D(16.0f, 16.0f))); + + Style.Set("LogView.Icon.Large", new IMAGE_BRUSH("Icons/Profiler/profiler_CopyToClipboard_32x", FVector2D(32.0f, 32.0f))); + Style.Set("LogView.Icon.Small", new IMAGE_BRUSH("Icons/Profiler/profiler_CopyToClipboard_32x", FVector2D(16.0f, 16.0f))); + + ////////////////////////////////////////////////// + // Start Page buttons + + Style.Set("Live.Icon.Large", new IMAGE_BRUSH("Icons/Profiler/Profiler_LoadMultiple_Profiler_40x", FVector2D(32.0f, 32.0f))); + Style.Set("Live.Icon.Small", new IMAGE_BRUSH("Icons/Profiler/Profiler_Load_Profiler_40x", FVector2D(16.0f, 16.0f))); + + Style.Set("Load.Icon.Large", new IMAGE_BRUSH("Icons/LV_Load", FVector2D(32.0f, 32.0f))); + Style.Set("Load.Icon.Small", new IMAGE_BRUSH("Icons/LV_Load", FVector2D(16.0f, 16.0f))); + + ////////////////////////////////////////////////// + + return StyleRef; +} + +#undef IMAGE_BRUSH +#undef BOX_BRUSH +#undef BORDER_BRUSH + +const ISlateStyle& FInsightsStyle::Get() +{ + return *StyleInstance; +} diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsStyle.h b/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsStyle.h new file mode 100644 index 000000000000..6749d3f2296b --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/InsightsStyle.h @@ -0,0 +1,32 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Styling/SlateStyle.h" + +/** Style data for Insights tools */ +class FInsightsStyle +{ +public: + static void Initialize(); + static void Shutdown(); + static const ISlateStyle& Get(); + static FName GetStyleSetName(); + + static const FLinearColor& GetColor(FName PropertyName, const ANSICHAR* Specifier = NULL) + { + return StyleInstance->GetColor(PropertyName, Specifier); + } + + static const FSlateBrush* GetBrush(FName PropertyName, const ANSICHAR* Specifier = NULL) + { + return StyleInstance->GetBrush(PropertyName, Specifier); + } + +private: + static TSharedRef Create(); + +private: + static TSharedPtr StyleInstance; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/IoProfilerCommands.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/IoProfilerCommands.cpp new file mode 100644 index 000000000000..5e009d14ba1c --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/IoProfilerCommands.cpp @@ -0,0 +1,102 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Insights/IoProfilerCommands.h" + +#include "DesktopPlatformModule.h" +#include "EditorStyleSet.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" + +// Insights +#include "Insights/InsightsManager.h" +#include "Insights/IoProfilerManager.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define LOCTEXT_NAMESPACE "FIoProfilerCommands" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FIoProfilerMenuBuilder +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FIoProfilerMenuBuilder::AddMenuEntry(FMenuBuilder& MenuBuilder, const TSharedPtr< FUICommandInfo >& UICommandInfo, const FUIAction& UIAction) +{ + MenuBuilder.AddMenuEntry + ( + UICommandInfo->GetLabel(), + UICommandInfo->GetDescription(), + UICommandInfo->GetIcon(), + UIAction, + NAME_None, + UICommandInfo->GetUserInterfaceType() + ); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FIoProfilerCommands +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FIoProfilerCommands::FIoProfilerCommands() + : TCommands( + TEXT("IoProfilerCommand"), // Context name for fast lookup + NSLOCTEXT("Contexts", "IoProfilerCommand", "Asset Loading Insights Command"), // Localized context name for displaying + NAME_None, // Parent + FEditorStyle::GetStyleSetName() // Icon Style Set + ) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// UI_COMMAND takes long for the compiler to optimize +PRAGMA_DISABLE_OPTIMIZATION +void FIoProfilerCommands::RegisterCommands() +{ + UI_COMMAND(ToggleTimingViewVisibility, "Timing", "Toggles the visibility of the main Timing view", EUserInterfaceActionType::ToggleButton, FInputChord(EModifierKey::Control, EKeys::T)); +} +PRAGMA_ENABLE_OPTIMIZATION + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Toggle Commands +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define IMPLEMENT_TOGGLE_COMMAND(CmdName, IsEnabled, SetIsEnabled) \ + \ + void FIoProfilerActionManager::Map_##CmdName##_Global()\ + {\ + This->CommandList->MapAction(This->GetCommands().CmdName, CmdName##_Custom());\ + }\ + \ + const FUIAction FIoProfilerActionManager::CmdName##_Custom() \ + {\ + FUIAction UIAction;\ + UIAction.ExecuteAction = FExecuteAction::CreateRaw(this, &FIoProfilerActionManager::CmdName##_Execute);\ + UIAction.CanExecuteAction = FCanExecuteAction::CreateRaw(this, &FIoProfilerActionManager::CmdName##_CanExecute);\ + UIAction.GetActionCheckState = FGetActionCheckState::CreateRaw(this, &FIoProfilerActionManager::CmdName##_GetCheckState);\ + return UIAction;\ + }\ + \ + void FIoProfilerActionManager::CmdName##_Execute()\ + {\ + const bool b##IsEnabled = !This->IsEnabled();\ + This->SetIsEnabled(b##IsEnabled);\ + }\ + \ + bool FIoProfilerActionManager::CmdName##_CanExecute() const\ + {\ + return FInsightsManager::Get()->GetSession().IsValid();\ + }\ + \ + ECheckBoxState FIoProfilerActionManager::CmdName##_GetCheckState() const\ + {\ + const bool b##IsEnabled = This->IsEnabled();\ + return b##IsEnabled ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;\ + } + +IMPLEMENT_TOGGLE_COMMAND(ToggleTimingViewVisibility, IsTimingViewVisible, SetTimingViewVisible) + +#undef IMPLEMENT_TOGGLE_COMMAND + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/IoProfilerCommands.h b/Engine/Source/Developer/TraceInsights/Private/Insights/IoProfilerCommands.h new file mode 100644 index 000000000000..9fb44ca0c886 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/IoProfilerCommands.h @@ -0,0 +1,93 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Commands/UIAction.h" +#include "Framework/Commands/Commands.h" + +class FMenuBuilder; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * Class that holds all profiler commands. + */ +class FIoProfilerCommands : public TCommands +{ +public: + /** Default constructor. */ + FIoProfilerCommands(); + + /** Initialize commands. */ + virtual void RegisterCommands() override; + +public: + ////////////////////////////////////////////////// + // Global commands need to implement following method: + // void Map__Global(); + // Custom commands needs to implement also the following method: + // const FUIAction _Custom(...) const; + ////////////////////////////////////////////////// + + /** Toggles visibility for the Timing View. Global and custom command. */ + TSharedPtr ToggleTimingViewVisibility; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * Menu builder. Helper class for adding a customized menu entry using the global UI command info. + */ +class FIoProfilerMenuBuilder +{ +public: + /** + * Helper method for adding a customized menu entry using the global UI command info. + * FUICommandInfo cannot be executed with custom parameters, so we need to create a custom FUIAction, + * but sometime we have global and local version for the UI command, so reuse data from the global UI command info. + * Ex: + * SessionInstance_ToggleCapture - Global version will toggle capture process for all active session instances + * SessionInstance_ToggleCapture_OneParam - Local version will toggle capture process only for the specified session instance + * + * @param MenuBuilder The menu to add items to + * @param FUICommandInfo A shared pointer to the UI command info + * @param UIAction Customized version of the UI command info stored in an UI action + */ + static void AddMenuEntry(FMenuBuilder& MenuBuilder, const TSharedPtr& UICommandInfo, const FUIAction& UIAction); +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * Class that provides helper functions for the commands to avoid cluttering profiler manager with many small functions. + * Can't contain any variables. Directly operates on the profiler manager instance. + */ +class FIoProfilerActionManager +{ + friend class FIoProfilerManager; + +private: + /** Private constructor. */ + FIoProfilerActionManager(class FIoProfilerManager* Instance) + : This(Instance) + {} + + ////////////////////////////////////////////////// + // Toggle Commands + +#define DECLARE_TOGGLE_COMMAND(CmdName)\ +public:\ + void Map_##CmdName##_Global(); /**< Maps UI command info CmdName with the specified UI command list. */\ + const FUIAction CmdName##_Custom(); /**< UI action for CmdName command. */\ +protected:\ + void CmdName##_Execute(); /**< Handles FExecuteAction for CmdName. */\ + bool CmdName##_CanExecute() const; /**< Handles FCanExecuteAction for CmdName. */\ + ECheckBoxState CmdName##_GetCheckState() const; /**< Handles FGetActionCheckState for CmdName. */ + + DECLARE_TOGGLE_COMMAND(ToggleTimingViewVisibility) +#undef DECLARE_TOGGLE_COMMAND + + ////////////////////////////////////////////////// + +protected: + /** Reference to the global instance of the profiler manager. */ + class FIoProfilerManager* This; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/IoProfilerManager.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/IoProfilerManager.cpp new file mode 100644 index 000000000000..2c9bbbd7215e --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/IoProfilerManager.cpp @@ -0,0 +1,139 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "IoProfilerManager.h" + +#include "Modules/ModuleManager.h" +#include "Templates/ScopedPointer.h" +#include "Templates/UniquePtr.h" +#include "TraceServices/AnalysisService.h" +#include "TraceServices/SessionService.h" + +// Insights +#include "Insights/InsightsManager.h" +//#include "Insights/IoProfilerCommon.h" +#include "Insights/Widgets/STimingView.h" +#include "Insights/Widgets/SIoProfilerWindow.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define LOCTEXT_NAMESPACE "IoProfilerManager" + +//DEFINE_LOG_CATEGORY(IoProfiler); +// +//DEFINE_STAT(STAT_FT_OnPaint); +//DEFINE_STAT(STAT_GT_OnPaint); +//DEFINE_STAT(STAT_TT_OnPaint); +//DEFINE_STAT(STAT_IOPM_Tick); + +TSharedPtr FIoProfilerManager::Instance = nullptr; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FIoProfilerManager::FIoProfilerManager(TSharedRef InCommandList) + : CommandList(InCommandList) + , ActionManager(this) + , ProfilerWindow(nullptr) + , bIsTimingViewVisible(true) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FIoProfilerManager::PostConstructor() +{ + // Register tick functions. + //OnTick = FTickerDelegate::CreateSP(this, &FIoProfilerManager::Tick); + //OnTickHandle = FTicker::GetCoreTicker().AddTicker(OnTick, 1.0f); + + FIoProfilerCommands::Register(); + BindCommands(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FIoProfilerManager::BindCommands() +{ + ActionManager.Map_ToggleTimingViewVisibility_Global(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FIoProfilerManager::~FIoProfilerManager() +{ + FIoProfilerCommands::Unregister(); + + // Unregister tick function. + //FTicker::GetCoreTicker().RemoveTicker(OnTickHandle); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedPtr FIoProfilerManager::Get() +{ + return FIoProfilerManager::Instance; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const TSharedRef FIoProfilerManager::GetCommandList() const +{ + return CommandList; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FIoProfilerCommands& FIoProfilerManager::GetCommands() +{ + return FIoProfilerCommands::Get(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FIoProfilerActionManager& FIoProfilerManager::GetActionManager() +{ + return FIoProfilerManager::Instance->ActionManager; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool FIoProfilerManager::Tick(float DeltaTime) +{ + //SCOPE_CYCLE_COUNTER(STAT_IOPM_Tick); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FIoProfilerManager::OnSessionChanged() +{ + TSharedPtr Wnd = GetProfilerWindow(); + if (Wnd.IsValid()) + { + TSharedPtr TimingView = Wnd->GetTimingView(); + if (TimingView) + { + TimingView->Reset(); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FIoProfilerManager::SetTimingViewVisible(const bool bTimingViewVisibleState) +{ + //bool bWasTimingViewVisible = bIsTimingViewVisible; + + bIsTimingViewVisible = bTimingViewVisibleState; + + // TODO + //TSharedPtr Wnd = GetProfilerWindow(); + //if (Wnd.IsValid()) + //{ + // Wnd->ShowHideTab(FIoProfilerTabs::TimingViewID, bIsTimingViewVisible); + //} +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/IoProfilerManager.h b/Engine/Source/Developer/TraceInsights/Private/Insights/IoProfilerManager.h new file mode 100644 index 000000000000..1e109baa16bd --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/IoProfilerManager.h @@ -0,0 +1,133 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Containers/Ticker.h" +#include "CoreMinimal.h" +#include "Framework/Commands/UICommandList.h" + +// Insights +#include "Insights/InsightsManager.h" +#include "Insights/IoProfilerCommands.h" + +class SIoProfilerWindow; + +namespace Trace +{ + class ISessionService; + class IAnalysisService; + class IAnalysisSession; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * This class manages the Timing Profiler state and settings. + */ +class FIoProfilerManager : public TSharedFromThis +{ + friend class FIoProfilerActionManager; + +public: + /** Creates the Timing Profiler manager, only one instance can exist. */ + FIoProfilerManager(TSharedRef InCommandList); + + /** Virtual destructor. */ + virtual ~FIoProfilerManager(); + + /** Creates an instance of the profiler manager. */ + static TSharedPtr Initialize() + { + if (FIoProfilerManager::Instance.IsValid()) + { + FIoProfilerManager::Instance.Reset(); + } + + FIoProfilerManager::Instance = MakeShareable(new FIoProfilerManager(FInsightsManager::Get()->GetCommandList())); + FIoProfilerManager::Instance->PostConstructor(); + + return FIoProfilerManager::Instance; + } + + /** Shutdowns the Timing Profiler manager. */ + void Shutdown() + { + FIoProfilerManager::Instance.Reset(); + } + +protected: + /** Finishes initialization of the profiler manager. */ + void PostConstructor(); + + /** Binds our UI commands to delegates. */ + void BindCommands(); + +public: + /** + * @return the global instance of the Timing Profiler manager (FIoProfilerManager). + * This is an internal singleton and cannot be used outside ProfilerModule. + * For external use: + * IProfilerModule& ProfilerModule = FModuleManager::Get().LoadModuleChecked("Profiler"); + * ProfilerModule.GetProfilerManager(); + */ + static TSharedPtr Get(); + + /** @returns UI command list for the Timing Profiler manager. */ + const TSharedRef GetCommandList() const; + + /** @return an instance of the Timing Profiler commands. */ + static const FIoProfilerCommands& GetCommands(); + + /** @return an instance of the Timing Profiler action manager. */ + static FIoProfilerActionManager& GetActionManager(); + + void AssignProfilerWindow(const TSharedRef& InProfilerWindow) + { + ProfilerWindow = InProfilerWindow; + } + + /** + * Converts profiler window weak pointer to a shared pointer and returns it. + * Make sure the returned pointer is valid before trying to dereference it. + */ + TSharedPtr GetProfilerWindow() const + { + return ProfilerWindow.Pin(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Getters and setters used by Toggle Commands. + + /** @return true, if Timing View is visible */ + const bool IsTimingViewVisible() const { return bIsTimingViewVisible; } + void SetTimingViewVisible(const bool bTimingViewVisibleState); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + + void OnSessionChanged(); + +protected: + /** Updates this manager, done through FCoreTicker. */ + bool Tick(float DeltaTime); + +protected: + /** The delegate to be invoked when this manager ticks. */ + FTickerDelegate OnTick; + + /** Handle to the registered OnTick. */ + FDelegateHandle OnTickHandle; + + /** List of UI commands for this manager. This will be filled by this and corresponding classes. */ + TSharedRef CommandList; + + /** An instance of the Timing Manager action manager. */ + FIoProfilerActionManager ActionManager; + + /** A weak pointer to the Timing Profiler window. */ + TWeakPtr ProfilerWindow; + + /** If the Timing View is visible or hidden. */ + bool bIsTimingViewVisible; + + /** A shared pointer to the global instance of the profiler manager. */ + static TSharedPtr Instance; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/TimingProfilerCommands.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/TimingProfilerCommands.cpp new file mode 100644 index 000000000000..3369ede4a8a8 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/TimingProfilerCommands.cpp @@ -0,0 +1,112 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Insights/TimingProfilerCommands.h" + +#include "DesktopPlatformModule.h" +#include "EditorStyleSet.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" + +// Insights +#include "Insights/InsightsManager.h" +#include "Insights/TimingProfilerManager.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define LOCTEXT_NAMESPACE "FTimingProfilerCommands" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FTimingProfilerMenuBuilder +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingProfilerMenuBuilder::AddMenuEntry(FMenuBuilder& MenuBuilder, const TSharedPtr< FUICommandInfo >& UICommandInfo, const FUIAction& UIAction) +{ + MenuBuilder.AddMenuEntry + ( + UICommandInfo->GetLabel(), + UICommandInfo->GetDescription(), + UICommandInfo->GetIcon(), + UIAction, + NAME_None, + UICommandInfo->GetUserInterfaceType() + ); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FTimingProfilerCommands +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FTimingProfilerCommands::FTimingProfilerCommands() + : TCommands( + TEXT("TimingProfilerCommand"), // Context name for fast lookup + NSLOCTEXT("Contexts", "TimingProfilerCommand", "Timing Profiler Command"), // Localized context name for displaying + NAME_None, // Parent + FEditorStyle::GetStyleSetName() // Icon Style Set + ) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// UI_COMMAND takes long for the compiler to optimize +PRAGMA_DISABLE_OPTIMIZATION +void FTimingProfilerCommands::RegisterCommands() +{ + UI_COMMAND(ToggleFramesTrackVisibility, "Frames", "Toggles the visibility of the Frames track", EUserInterfaceActionType::ToggleButton, FInputChord(EModifierKey::Control, EKeys::F)); + UI_COMMAND(ToggleGraphTrackVisibility, "Graph", "Toggles the visibility of the Overview Graph track", EUserInterfaceActionType::ToggleButton, FInputChord(EModifierKey::Control, EKeys::G)); + UI_COMMAND(ToggleTimingViewVisibility, "Timing", "Toggles the visibility of the main Timing view", EUserInterfaceActionType::ToggleButton, FInputChord(EModifierKey::Control, EKeys::T)); + UI_COMMAND(ToggleTimersViewVisibility, "Timers", "Toggles the visibility of the Timers view", EUserInterfaceActionType::ToggleButton, FInputChord()); + UI_COMMAND(ToggleStatsCountersViewVisibility, "Stats", "Toggles the visibility of the Stats Counters view", EUserInterfaceActionType::ToggleButton, FInputChord()); + UI_COMMAND(ToggleLogViewVisibility, "Log", "Toggles the visibility of the Log view", EUserInterfaceActionType::ToggleButton, FInputChord(EKeys::L)); +} +PRAGMA_ENABLE_OPTIMIZATION + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Toggle Commands +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define IMPLEMENT_TOGGLE_COMMAND(CmdName, IsEnabled, SetIsEnabled) \ + \ + void FTimingProfilerActionManager::Map_##CmdName##_Global()\ + {\ + This->CommandList->MapAction(This->GetCommands().CmdName, CmdName##_Custom());\ + }\ + \ + const FUIAction FTimingProfilerActionManager::CmdName##_Custom() \ + {\ + FUIAction UIAction;\ + UIAction.ExecuteAction = FExecuteAction::CreateRaw(this, &FTimingProfilerActionManager::CmdName##_Execute);\ + UIAction.CanExecuteAction = FCanExecuteAction::CreateRaw(this, &FTimingProfilerActionManager::CmdName##_CanExecute);\ + UIAction.GetActionCheckState = FGetActionCheckState::CreateRaw(this, &FTimingProfilerActionManager::CmdName##_GetCheckState);\ + return UIAction;\ + }\ + \ + void FTimingProfilerActionManager::CmdName##_Execute()\ + {\ + const bool b##IsEnabled = !This->IsEnabled();\ + This->SetIsEnabled(b##IsEnabled);\ + }\ + \ + bool FTimingProfilerActionManager::CmdName##_CanExecute() const\ + {\ + return FInsightsManager::Get()->GetSession().IsValid();\ + }\ + \ + ECheckBoxState FTimingProfilerActionManager::CmdName##_GetCheckState() const\ + {\ + const bool b##IsEnabled = This->IsEnabled();\ + return b##IsEnabled ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;\ + } + +IMPLEMENT_TOGGLE_COMMAND(ToggleFramesTrackVisibility, IsFramesTrackVisible, ShowHideFramesTrack) +IMPLEMENT_TOGGLE_COMMAND(ToggleGraphTrackVisibility, IsGraphTrackVisible, ShowHideGraphTrack) +IMPLEMENT_TOGGLE_COMMAND(ToggleTimingViewVisibility, IsTimingViewVisible, ShowHideTimingView) +IMPLEMENT_TOGGLE_COMMAND(ToggleTimersViewVisibility, IsTimersViewVisible, ShowHideTimersView) +IMPLEMENT_TOGGLE_COMMAND(ToggleStatsCountersViewVisibility, IsStatsCountersViewVisible, ShowHideStatsCountersView) +IMPLEMENT_TOGGLE_COMMAND(ToggleLogViewVisibility, IsLogViewVisible, ShowHideLogView) + +#undef IMPLEMENT_TOGGLE_COMMAND + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/TimingProfilerCommands.h b/Engine/Source/Developer/TraceInsights/Private/Insights/TimingProfilerCommands.h new file mode 100644 index 000000000000..020375e9b496 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/TimingProfilerCommands.h @@ -0,0 +1,123 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Commands/UIAction.h" +#include "Framework/Commands/Commands.h" + +class FMenuBuilder; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * Class that holds all profiler commands. + */ +class FTimingProfilerCommands : public TCommands +{ +public: + /** Default constructor. */ + FTimingProfilerCommands(); + + /** Initialize commands. */ + virtual void RegisterCommands() override; + +public: + ////////////////////////////////////////////////// + // Global commands need to implement following method: + // void Map__Global(); + // Custom commands needs to implement also the following method: + // const FUIAction _Custom(...) const; + ////////////////////////////////////////////////// + + /** Toggles visibility for the Frames Track. Global and custom command. */ + TSharedPtr ToggleFramesTrackVisibility; + + /** Toggles visibility for the Graph Track. Global and custom command. */ + TSharedPtr ToggleGraphTrackVisibility; + + /** Toggles visibility for the Timing View. Global and custom command. */ + TSharedPtr ToggleTimingViewVisibility; + + /** Toggles visibility for the Timers View. Global and custom command. */ + TSharedPtr ToggleTimersViewVisibility; + + /** Toggles visibility for the Stats Counters View. Global and custom command. */ + TSharedPtr ToggleStatsCountersViewVisibility; + + /** Toggles visibility for the Log View. Global and custom command. */ + TSharedPtr ToggleLogViewVisibility; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * Menu builder. Helper class for adding a customized menu entry using the global UI command info. + */ +class FTimingProfilerMenuBuilder +{ +public: + /** + * Helper method for adding a customized menu entry using the global UI command info. + * FUICommandInfo cannot be executed with custom parameters, so we need to create a custom FUIAction, + * but sometime we have global and local version for the UI command, so reuse data from the global UI command info. + * Ex: + * SessionInstance_ToggleCapture - Global version will toggle capture process for all active session instances + * SessionInstance_ToggleCapture_OneParam - Local version will toggle capture process only for the specified session instance + * + * @param MenuBuilder The menu to add items to + * @param FUICommandInfo A shared pointer to the UI command info + * @param UIAction Customized version of the UI command info stored in an UI action + */ + static void AddMenuEntry(FMenuBuilder& MenuBuilder, const TSharedPtr& UICommandInfo, const FUIAction& UIAction); +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * Class that provides helper functions for the commands to avoid cluttering profiler manager with many small functions. + * Can't contain any variables. Directly operates on the profiler manager instance. + */ +class FTimingProfilerActionManager +{ + friend class FTimingProfilerManager; + +private: + /** Private constructor. */ + FTimingProfilerActionManager(class FTimingProfilerManager* Instance) + : This(Instance) + {} + + ////////////////////////////////////////////////// + // Toggle Commands + +#define DECLARE_TOGGLE_COMMAND(CmdName)\ +public:\ + void Map_##CmdName##_Global(); /**< Maps UI command info CmdName with the specified UI command list. */\ + const FUIAction CmdName##_Custom(); /**< UI action for CmdName command. */\ +protected:\ + void CmdName##_Execute(); /**< Handles FExecuteAction for CmdName. */\ + bool CmdName##_CanExecute() const; /**< Handles FCanExecuteAction for CmdName. */\ + ECheckBoxState CmdName##_GetCheckState() const; /**< Handles FGetActionCheckState for CmdName. */ + + DECLARE_TOGGLE_COMMAND(ToggleFramesTrackVisibility) + DECLARE_TOGGLE_COMMAND(ToggleGraphTrackVisibility) + DECLARE_TOGGLE_COMMAND(ToggleTimingViewVisibility) + DECLARE_TOGGLE_COMMAND(ToggleTimersViewVisibility) + DECLARE_TOGGLE_COMMAND(ToggleStatsCountersViewVisibility) + DECLARE_TOGGLE_COMMAND(ToggleLogViewVisibility) +#undef DECLARE_TOGGLE_COMMAND + + ////////////////////////////////////////////////// + // OpenSettings + +public: + void Map_OpenSettings_Global(); /**< Maps UI command info OpenSettings with the specified UI command list. */ + const FUIAction OpenSettings_Custom() const; /**< UI action for OpenSettings command. */ +protected: + void OpenSettings_Execute(); /**< Handles FExecuteAction for OpenSettings. */ + bool OpenSettings_CanExecute() const; /**< Handles FCanExecuteAction for OpenSettings. */ + + ////////////////////////////////////////////////// + +protected: + /** Reference to the global instance of the profiler manager. */ + class FTimingProfilerManager* This; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/TimingProfilerManager.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/TimingProfilerManager.cpp new file mode 100644 index 000000000000..e5f34eb47ee8 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/TimingProfilerManager.cpp @@ -0,0 +1,247 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TimingProfilerManager.h" + +#include "Modules/ModuleManager.h" +#include "Templates/ScopedPointer.h" +#include "Templates/UniquePtr.h" +#include "TraceServices/AnalysisService.h" +#include "TraceServices/SessionService.h" + +// Insights +#include "Insights/InsightsManager.h" +#include "Insights/TimingProfilerCommon.h" +#include "Insights/Widgets/SFrameTrack.h" +#include "Insights/Widgets/SGraphTrack.h" +#include "Insights/Widgets/SLogView.h" +#include "Insights/Widgets/SStatsView.h" +#include "Insights/Widgets/STimersView.h" +#include "Insights/Widgets/STimingView.h" +#include "Insights/Widgets/STimingProfilerWindow.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define LOCTEXT_NAMESPACE "TimingProfilerManager" + +DEFINE_LOG_CATEGORY(TimingProfiler); + +DEFINE_STAT(STAT_FT_OnPaint); +DEFINE_STAT(STAT_GT_OnPaint); +DEFINE_STAT(STAT_TT_OnPaint); +DEFINE_STAT(STAT_TPM_Tick); + +TSharedPtr FTimingProfilerManager::Instance = nullptr; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FTimingProfilerManager::FTimingProfilerManager(TSharedRef InCommandList) + : CommandList(InCommandList) + , ActionManager(this) + , ProfilerWindow(nullptr) + , bIsFramesTrackVisible(true) + , bIsGraphTrackVisible(false) + , bIsTimingViewVisible(true) + , bIsTimersViewVisible(true) + , bIsStatsCountersViewVisible(true) + , bIsLogViewVisible(true) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingProfilerManager::PostConstructor() +{ + // Register tick functions. + //OnTick = FTickerDelegate::CreateSP(this, &FTimingProfilerManager::Tick); + //OnTickHandle = FTicker::GetCoreTicker().AddTicker(OnTick, 1.0f); + + FTimingProfilerCommands::Register(); + BindCommands(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingProfilerManager::BindCommands() +{ + ActionManager.Map_ToggleFramesTrackVisibility_Global(); + ActionManager.Map_ToggleGraphTrackVisibility_Global(); + ActionManager.Map_ToggleTimingViewVisibility_Global(); + ActionManager.Map_ToggleTimersViewVisibility_Global(); + ActionManager.Map_ToggleStatsCountersViewVisibility_Global(); + ActionManager.Map_ToggleLogViewVisibility_Global(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FTimingProfilerManager::~FTimingProfilerManager() +{ + FTimingProfilerCommands::Unregister(); + + // Unregister tick function. + //FTicker::GetCoreTicker().RemoveTicker(OnTickHandle); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedPtr FTimingProfilerManager::Get() +{ + return FTimingProfilerManager::Instance; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const TSharedRef FTimingProfilerManager::GetCommandList() const +{ + return CommandList; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FTimingProfilerCommands& FTimingProfilerManager::GetCommands() +{ + return FTimingProfilerCommands::Get(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FTimingProfilerActionManager& FTimingProfilerManager::GetActionManager() +{ + return FTimingProfilerManager::Instance->ActionManager; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool FTimingProfilerManager::Tick(float DeltaTime) +{ + SCOPE_CYCLE_COUNTER(STAT_TPM_Tick); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingProfilerManager::OnSessionChanged() +{ + TSharedPtr Wnd = GetProfilerWindow(); + if (Wnd.IsValid()) + { + if (Wnd->FrameTrack) + { + Wnd->FrameTrack->Reset(); + } + if (Wnd->GraphTrack) + { + Wnd->GraphTrack->Reset(); + } + if (Wnd->TimingView) + { + Wnd->TimingView->Reset(); + } + if (Wnd->TimersView) + { + Wnd->TimersView->RebuildTree(); + } + if (Wnd->StatsView) + { + Wnd->StatsView->RebuildTree(); + } + if (Wnd->LogView) + { + Wnd->LogView->Reset(); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingProfilerManager::ShowHideFramesTrack(const bool bFramesTrackVisibleState) +{ + bool bWasFramesTrackVisible = bIsFramesTrackVisible; + + bIsFramesTrackVisible = bFramesTrackVisibleState; + + TSharedPtr Wnd = GetProfilerWindow(); + if (Wnd.IsValid()) + { + Wnd->ShowHideTab(FTimingProfilerTabs::FramesTrackID, bIsFramesTrackVisible); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingProfilerManager::ShowHideGraphTrack(const bool bGraphTrackVisibleState) +{ + bool bWasGraphTrackVisible = bIsGraphTrackVisible; + + bIsGraphTrackVisible = bGraphTrackVisibleState; + + TSharedPtr Wnd = GetProfilerWindow(); + if (Wnd.IsValid()) + { + Wnd->ShowHideTab(FTimingProfilerTabs::GraphTrackID, bIsGraphTrackVisible); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingProfilerManager::ShowHideTimingView(const bool bTimingViewVisibleState) +{ + bool bWasTimingViewVisible = bIsTimingViewVisible; + + bIsTimingViewVisible = bTimingViewVisibleState; + + TSharedPtr Wnd = GetProfilerWindow(); + if (Wnd.IsValid()) + { + Wnd->ShowHideTab(FTimingProfilerTabs::TimingViewID, bIsTimingViewVisible); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingProfilerManager::ShowHideTimersView(const bool bTimersViewVisibleState) +{ + bool bWasTimersViewVisible = bIsTimersViewVisible; + + bIsTimersViewVisible = bTimersViewVisibleState; + + TSharedPtr Wnd = GetProfilerWindow(); + if (Wnd.IsValid()) + { + Wnd->ShowHideTab(FTimingProfilerTabs::TimersID, bIsTimersViewVisible); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingProfilerManager::ShowHideStatsCountersView(const bool bStatsCountersViewVisibleState) +{ + bool bWasStatsCountersViewVisible = bIsStatsCountersViewVisible; + + bIsStatsCountersViewVisible = bStatsCountersViewVisibleState; + + TSharedPtr Wnd = GetProfilerWindow(); + if (Wnd.IsValid()) + { + Wnd->ShowHideTab(FTimingProfilerTabs::StatsCountersID, bIsStatsCountersViewVisible); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingProfilerManager::ShowHideLogView(const bool bLogViewVisibleState) +{ + bool bWasLogViewVisible = bIsLogViewVisible; + + bIsLogViewVisible = bLogViewVisibleState; + + TSharedPtr Wnd = GetProfilerWindow(); + if (Wnd.IsValid()) + { + Wnd->ShowHideTab(FTimingProfilerTabs::LogViewID, bIsLogViewVisible); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/TimingProfilerManager.h b/Engine/Source/Developer/TraceInsights/Private/Insights/TimingProfilerManager.h new file mode 100644 index 000000000000..c9b35fc4aa4d --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/TimingProfilerManager.h @@ -0,0 +1,174 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Containers/Ticker.h" +#include "CoreMinimal.h" +#include "Framework/Commands/UICommandList.h" + +// Insights +#include "Insights/InsightsManager.h" +#include "Insights/TimingProfilerCommands.h" + +class STimingProfilerWindow; + +namespace Trace +{ + class ISessionService; + class IAnalysisService; + class IAnalysisSession; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * This class manages the Timing Profiler state and settings. + */ +class FTimingProfilerManager : public TSharedFromThis +{ + friend class FTimingProfilerActionManager; + +public: + /** Creates the Timing Profiler manager, only one instance can exist. */ + FTimingProfilerManager(TSharedRef InCommandList); + + /** Virtual destructor. */ + virtual ~FTimingProfilerManager(); + + /** Creates an instance of the profiler manager. */ + static TSharedPtr Initialize() + { + if (FTimingProfilerManager::Instance.IsValid()) + { + FTimingProfilerManager::Instance.Reset(); + } + + FTimingProfilerManager::Instance = MakeShareable(new FTimingProfilerManager(FInsightsManager::Get()->GetCommandList())); + FTimingProfilerManager::Instance->PostConstructor(); + + return FTimingProfilerManager::Instance; + } + + /** Shutdowns the Timing Profiler manager. */ + void Shutdown() + { + FTimingProfilerManager::Instance.Reset(); + } + +protected: + /** Finishes initialization of the profiler manager. */ + void PostConstructor(); + + /** Binds our UI commands to delegates. */ + void BindCommands(); + +public: + /** + * @return the global instance of the Timing Profiler manager (FTimingProfilerManager). + * This is an internal singleton and cannot be used outside ProfilerModule. + * For external use: + * IProfilerModule& ProfilerModule = FModuleManager::Get().LoadModuleChecked("Profiler"); + * ProfilerModule.GetProfilerManager(); + */ + static TSharedPtr Get(); + + /** @returns UI command list for the Timing Profiler manager. */ + const TSharedRef GetCommandList() const; + + /** @return an instance of the Timing Profiler commands. */ + static const FTimingProfilerCommands& GetCommands(); + + /** @return an instance of the Timing Profiler action manager. */ + static FTimingProfilerActionManager& GetActionManager(); + + void AssignProfilerWindow(const TSharedRef& InProfilerWindow) + { + ProfilerWindow = InProfilerWindow; + } + + /** + * Converts profiler window weak pointer to a shared pointer and returns it. + * Make sure the returned pointer is valid before trying to dereference it. + */ + TSharedPtr GetProfilerWindow() const + { + return ProfilerWindow.Pin(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Getters and setters used by Toggle Commands. + + /** @return true, if Frames Track is visible */ + const bool IsFramesTrackVisible() const { return bIsFramesTrackVisible; } + void SetFramesTrackVisible(const bool bFramesTrackVisibleState) { bIsFramesTrackVisible = bFramesTrackVisibleState; } + void ShowHideFramesTrack(const bool bFramesTrackVisibleState); + + /** @return true, if Graph Track is visible */ + const bool IsGraphTrackVisible() const { return bIsGraphTrackVisible; } + void SetGraphTrackVisible(const bool bGraphTrackVisibleState) { bIsGraphTrackVisible = bGraphTrackVisibleState; } + void ShowHideGraphTrack(const bool bGraphTrackVisibleState); + + /** @return true, if Timing View is visible */ + const bool IsTimingViewVisible() const { return bIsTimingViewVisible; } + void SetTimingViewVisible(const bool bTimingViewVisibleState) { bIsTimingViewVisible = bTimingViewVisibleState; } + void ShowHideTimingView(const bool bTimingViewVisibleState); + + /** @return true, if Timers View is visible */ + const bool IsTimersViewVisible() const { return bIsTimersViewVisible; } + void SetTimersViewVisible(const bool bTimersViewVisibleState) { bIsTimersViewVisible = bTimersViewVisibleState; } + void ShowHideTimersView(const bool bTimersViewVisibleState); + + /** @return true, if Stats Counters View is visible */ + const bool IsStatsCountersViewVisible() const { return bIsStatsCountersViewVisible; } + void SetStatsCountersViewVisible(const bool bStatsCountersViewVisibleState) { bIsStatsCountersViewVisible = bStatsCountersViewVisibleState; } + void ShowHideStatsCountersView(const bool bStatsCountersViewVisibleState); + + /** @return true, if Log View is visible */ + const bool IsLogViewVisible() const { return bIsLogViewVisible; } + void SetLogViewVisible(const bool bLogViewVisibleState) { bIsLogViewVisible = bLogViewVisibleState; } + void ShowHideLogView(const bool bLogViewVisibleState); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + + void OnSessionChanged(); + +protected: + /** Updates this manager, done through FCoreTicker. */ + bool Tick(float DeltaTime); + +protected: + /** The delegate to be invoked when this manager ticks. */ + FTickerDelegate OnTick; + + /** Handle to the registered OnTick. */ + FDelegateHandle OnTickHandle; + + /** List of UI commands for this manager. This will be filled by this and corresponding classes. */ + TSharedRef CommandList; + + /** An instance of the Timing Manager action manager. */ + FTimingProfilerActionManager ActionManager; + + /** A weak pointer to the Timing Profiler window. */ + TWeakPtr ProfilerWindow; + + /** If the Frames Track is visible or hidden. */ + bool bIsFramesTrackVisible; + + /** If the Graph Track is visible or hidden. */ + bool bIsGraphTrackVisible; + + /** If the Timing View is visible or hidden. */ + bool bIsTimingViewVisible; + + /** If the Timers View is visible or hidden. */ + bool bIsTimersViewVisible; + + /** If the Stats Counters View is visible or hidden. */ + bool bIsStatsCountersViewVisible; + + /** If the Log View is visible or hidden. */ + bool bIsLogViewVisible; + + /** A shared pointer to the global instance of the profiler manager. */ + static TSharedPtr Instance; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/TraceInsightsModule.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/TraceInsightsModule.cpp new file mode 100644 index 000000000000..2065d7eef3e4 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/TraceInsightsModule.cpp @@ -0,0 +1,247 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "CoreMinimal.h" +#include "EditorStyleSet.h" +#include "HAL/PlatformApplicationMisc.h" +#include "Modules/ModuleManager.h" +#include "TraceServices/ITraceServicesModule.h" +#include "TraceServices/SessionService.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/Docking/SDockTab.h" +#include "Widgets/SWidget.h" +#include "Widgets/Text/STextBlock.h" +//#include "WorkspaceMenuStructure.h" +//#include "WorkspaceMenuStructureModule.h" + +// Insights +#include "Insights/InsightsManager.h" +#include "Insights/InsightsStyle.h" +#include "Insights/IoProfilerManager.h" +#include "Insights/IUnrealInsightsModule.h" +#include "Insights/TimingProfilerManager.h" +#include "Insights/Widgets/SIoProfilerWindow.h" +#include "Insights/Widgets/SStartPageWindow.h" +#include "Insights/Widgets/STimingProfilerWindow.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * Implements the Trace Insights module (TimingProfiler, IoProfiler, etc.). + */ +class FTraceInsightsModule : public IUnrealInsightsModule +{ +public: + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + virtual bool SupportsDynamicReloading() override + { + return false; + } + + virtual void OnNewLayout(TSharedRef NewLayout) override; + virtual void OnLayoutRestored(TSharedPtr TabManager) override; + +protected: + /** Callback called when a major tab is closed. */ + void OnTabBeingClosed(TSharedRef TabBeingClosed); + + /** Callback called when the Timing Profiler major tab is closed. */ + void OnTimingProfilerTabBeingClosed(TSharedRef TabBeingClosed); + + /** Callback called when the Io Profiler major tab is closed. */ + void OnIoProfilerTabBeingClosed(TSharedRef TabBeingClosed); + + /** Start Page */ + TSharedRef SpawnStartPageTab(const FSpawnTabArgs& Args); + + /** Timing Profiler */ + TSharedRef SpawnTimingProfilerTab(const FSpawnTabArgs& Args); + + /** I/O Profiler */ + TSharedRef SpawnIoProfilerTab(const FSpawnTabArgs& Args); + +protected: + TSharedPtr TraceAnalysisService; + TSharedPtr TraceSessionService; + TSharedPtr TraceModuleService; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +IMPLEMENT_MODULE(FTraceInsightsModule, TraceInsights); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FTraceInsightsModule +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTraceInsightsModule::StartupModule() +{ + ITraceServicesModule& TraceServicesModule = FModuleManager::LoadModuleChecked("TraceServices"); + TraceAnalysisService = TraceServicesModule.GetAnalysisService(); + TraceSessionService = TraceServicesModule.GetSessionService(); + TraceModuleService = TraceServicesModule.GetModuleService(); + + // Starts the Trace Recorder service. + TraceSessionService->StartRecorderServer(); + + FInsightsStyle::Initialize(); + + FInsightsManager::Initialize(TraceAnalysisService.ToSharedRef(), TraceSessionService.ToSharedRef(), TraceModuleService.ToSharedRef()); + FTimingProfilerManager::Initialize(); + FIoProfilerManager::Initialize(); + + // Register tab spawner for the Start Page. + auto& StartPageTabSpawnerEntry = FGlobalTabmanager::Get()->RegisterTabSpawner(FInsightsManagerTabs::StartPageTabId, + FOnSpawnTab::CreateRaw(this, &FTraceInsightsModule::SpawnStartPageTab)) + .SetDisplayName(NSLOCTEXT("FTraceInsightsModule", "StartPageTabTitle", "Unreal Insights")) + .SetTooltipText(NSLOCTEXT("FTraceInsightsModule", "StartPageTooltipText", "Open the start page for Unreal Insights.")) + .SetIcon(FSlateIcon(FInsightsStyle::GetStyleSetName(), "StartPage.Icon.Small")); + + // Register tab spawner for the Timing Insights. + auto& TimingProfilerTabSpawnerEntry = FGlobalTabmanager::Get()->RegisterTabSpawner(FInsightsManagerTabs::TimingProfilerTabId, + FOnSpawnTab::CreateRaw(this, &FTraceInsightsModule::SpawnTimingProfilerTab)) + .SetDisplayName(NSLOCTEXT("FTraceInsightsModule", "TimingProfilerTabTitle", "Timing Insights")) + .SetTooltipText(NSLOCTEXT("FTraceInsightsModule", "TimingProfilerTooltipText", "Open the Timing Insights tab.")) + .SetIcon(FSlateIcon(FInsightsStyle::GetStyleSetName(), "TimingInsights.Icon.Small")); + +//#if WITH_EDITOR +// TimingProfilerTabSpawnerEntry.SetGroup(WorkspaceMenu::GetMenuStructure().GetDeveloperToolsMiscCategory()); +//#else +// TimingProfilerTabSpawnerEntry.SetGroup(WorkspaceMenu::GetMenuStructure().GetToolsCategory()); +//#endif + + // Register tab spawner for the Asset Loading Insights. + auto& IoProfilerTabSpawnerEntry = FGlobalTabmanager::Get()->RegisterTabSpawner(FInsightsManagerTabs::IoProfilerTabId, + FOnSpawnTab::CreateRaw(this, &FTraceInsightsModule::SpawnIoProfilerTab)) + .SetDisplayName(NSLOCTEXT("FTraceInsightsModule", "IoProfilerTabTitle", "Asset Loading Insights")) + .SetTooltipText(NSLOCTEXT("FTraceInsightsModule", "IoProfilerTooltipText", "Open the Asset Loading Insights tab.")) + .SetIcon(FSlateIcon(FInsightsStyle::GetStyleSetName(), "AssetLoadingInsights.Icon.Small")); + +//#if WITH_EDITOR +// IoProfilerTabSpawnerEntry.SetGroup(WorkspaceMenu::GetMenuStructure().GetDeveloperToolsMiscCategory()); +//#else +// IoProfilerTabSpawnerEntry.SetGroup(WorkspaceMenu::GetMenuStructure().GetToolsCategory()); +//#endif +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTraceInsightsModule::ShutdownModule() +{ + TraceSessionService->StopRecorderServer(); + + FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(FInsightsManagerTabs::StartPageTabId); + FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(FInsightsManagerTabs::TimingProfilerTabId); + FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(FInsightsManagerTabs::IoProfilerTabId); + + if (FTimingProfilerManager::Get().IsValid()) + { + // Shutdown the Timing Profiler manager. + FTimingProfilerManager::Get()->Shutdown(); + } + + if (FIoProfilerManager::Get().IsValid()) + { + // Shutdown the I/O Profiler manager. + FIoProfilerManager::Get()->Shutdown(); + } + + if (FInsightsManager::Get().IsValid()) + { + // Shutdown the main manager. + FInsightsManager::Get()->Shutdown(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTraceInsightsModule::OnNewLayout(TSharedRef NewLayout) +{ + const float DPIScaleFactor = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(10.0f, 10.0f); + NewLayout->AddArea + ( + FTabManager::NewArea(1280.f * DPIScaleFactor, 720.0f * DPIScaleFactor) + ->Split + ( + FTabManager::NewStack() + ->AddTab(FInsightsManagerTabs::StartPageTabId, ETabState::OpenedTab) + ->AddTab(FInsightsManagerTabs::TimingProfilerTabId, ETabState::ClosedTab) + ->AddTab(FInsightsManagerTabs::IoProfilerTabId, ETabState::ClosedTab) + ->SetForegroundTab(FTabId(FInsightsManagerTabs::StartPageTabId)) + ) + ); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTraceInsightsModule::OnLayoutRestored(TSharedPtr TabManager) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef FTraceInsightsModule::SpawnStartPageTab(const FSpawnTabArgs& Args) +{ + const TSharedRef DockTab = SNew(SDockTab) + .TabRole(ETabRole::NomadTab); + + // Create the Start Page widget. + TSharedRef Window = SNew(SStartPageWindow); + DockTab->SetContent(Window); + + return DockTab; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef FTraceInsightsModule::SpawnTimingProfilerTab(const FSpawnTabArgs& Args) +{ + const TSharedRef DockTab = SNew(SDockTab) + .TabRole(ETabRole::NomadTab); + + // Register OnTabClosed to handle Timing profiler manager shutdown. + DockTab->SetOnTabClosed(SDockTab::FOnTabClosedCallback::CreateRaw(this, &FTraceInsightsModule::OnTimingProfilerTabBeingClosed)); + + // Create the STimingProfilerWindow widget. + TSharedRef Window = SNew(STimingProfilerWindow, DockTab, Args.GetOwnerWindow()); + FTimingProfilerManager::Get()->AssignProfilerWindow(Window); + DockTab->SetContent(Window); + + return DockTab; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTraceInsightsModule::OnTimingProfilerTabBeingClosed(TSharedRef TabBeingClosed) +{ + // Disable TabClosed delegate. + TabBeingClosed->SetOnTabClosed(SDockTab::FOnTabClosedCallback()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef FTraceInsightsModule::SpawnIoProfilerTab(const FSpawnTabArgs& Args) +{ + const TSharedRef DockTab = SNew(SDockTab) + .TabRole(ETabRole::NomadTab); + + // Register OnTabClosed to handle I/O profiler manager shutdown. + DockTab->SetOnTabClosed(SDockTab::FOnTabClosedCallback::CreateRaw(this, &FTraceInsightsModule::OnIoProfilerTabBeingClosed)); + + // Create the SIoProfilerWindow widget. + TSharedRef Window = SNew(SIoProfilerWindow); + FIoProfilerManager::Get()->AssignProfilerWindow(Window); + DockTab->SetContent(Window); + + return DockTab; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTraceInsightsModule::OnIoProfilerTabBeingClosed(TSharedRef TabBeingClosed) +{ + // Disable TabClosed delegate. + TabBeingClosed->SetOnTabClosed(SDockTab::FOnTabClosedCallback()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/BaseTimingTrack.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/BaseTimingTrack.h new file mode 100644 index 000000000000..ca02cc373280 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/BaseTimingTrack.h @@ -0,0 +1,94 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/EnumClassFlags.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FTimingTrackViewport; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +enum class ETimingTrackFlags : uint32 +{ + None = 0, + IsVisible = (1 << 0), + IsSelected = (1 << 1), + IsHovered = (1 << 2), + IsHeaderHovered = (1 << 3), +}; +ENUM_CLASS_FLAGS(ETimingTrackFlags); + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FBaseTimingTrack +{ + friend class FTimingViewDrawHelper; + +protected: + FBaseTimingTrack(uint64 InId) + : Id(InId) + , PosY(0.0f) + , Height(0.0f) + , Flags(ETimingTrackFlags::IsVisible) + {} + + virtual ~FBaseTimingTrack() + {} + +public: + virtual void Reset() + { + Id = 0; + PosY = 0.0f; + Height = 0.0f; + Flags = ETimingTrackFlags::IsVisible; + } + + uint64 GetId() const { return Id; } + + float GetPosY() const { return PosY; } + virtual void SetPosY(float InPosY) { PosY = InPosY; } + + float GetHeight() const { return Height; } + virtual void SetHeight(float InHeight) { Height = InHeight; } + + bool IsVisible() const { return EnumHasAnyFlags(Flags, ETimingTrackFlags::IsVisible); } + void Show() { Flags |= ETimingTrackFlags::IsVisible; OnVisibilityChanged(); } + void Hide() { Flags &= ~ETimingTrackFlags::IsVisible; OnVisibilityChanged(); } + void ToggleVisibility() { Flags ^= ETimingTrackFlags::IsVisible; OnVisibilityChanged(); } + void SetVisibilityFlag(bool bIsVisible) { bIsVisible ? Show() : Hide(); } + virtual void OnVisibilityChanged() {} + + bool IsSelected() const { return EnumHasAnyFlags(Flags, ETimingTrackFlags::IsSelected); } + void Select() { Flags |= ETimingTrackFlags::IsSelected; OnSelectedFlagChanged(); } + void Unselect() { Flags &= ~ETimingTrackFlags::IsSelected; OnSelectedFlagChanged(); } + void ToggleSelectedFlag() { Flags ^= ETimingTrackFlags::IsSelected; OnSelectedFlagChanged(); } + void SetSelectedFlag(bool bIsSelected) { bIsSelected ? Select() : Unselect(); } + virtual void OnSelectedFlagChanged() {} + + bool IsHovered() const { return EnumHasAnyFlags(Flags, ETimingTrackFlags::IsHovered); } + void SetHoveredState(bool bIsHovered) { bIsHovered ? Flags |= ETimingTrackFlags::IsHovered : Flags &= ~ETimingTrackFlags::IsHovered; } + bool IsHeaderHovered() const { return EnumHasAllFlags(Flags, ETimingTrackFlags::IsHovered | ETimingTrackFlags::IsHeaderHovered); } + void SetHeaderHoveredState(bool bIsHeaderHovered) { bIsHeaderHovered ? Flags |= ETimingTrackFlags::IsHeaderHovered : Flags &= ~ETimingTrackFlags::IsHeaderHovered; } + virtual void UpdateHoveredState(float MouseX, float MouseY, const FTimingTrackViewport& Viewport) = 0; + + virtual void Tick(const double CurrentTime, const float DeltaTime) {} + virtual void Update(const FTimingTrackViewport& Viewport) {} + + static uint64 GenerateId() { return IdGenerator++; } + +private: + uint64 Id; + + float PosY; // y position, in Slate units + float Height; // height, in Slate units + + ETimingTrackFlags Flags; + + static uint64 IdGenerator; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/FrameTrackHelper.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/FrameTrackHelper.cpp new file mode 100644 index 000000000000..dc73f57b4bf1 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/FrameTrackHelper.cpp @@ -0,0 +1,322 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "FrameTrackHelper.h" + +#include "Brushes/SlateBorderBrush.h" +#include "Brushes/SlateColorBrush.h" +#include "EditorStyleSet.h" +#include "Fonts/FontMeasure.h" +#include "Fonts/SlateFontInfo.h" +#include "Framework/Application/SlateApplication.h" +#include "Rendering/DrawElements.h" +#include "TraceServices/AnalysisService.h" + +// Insights +#include "Insights/Common/PaintUtils.h" +#include "Insights/ViewModels/FrameTrackViewport.h" + +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FFrameTrackTimelineBuilder +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FFrameTrackTimelineBuilder::FFrameTrackTimelineBuilder(FFrameTrackTimeline& InTimeline, const FFrameTrackViewport& InViewport) + : Timeline(InTimeline) + , Viewport(InViewport) + , NumAddedFrames(0) +{ + SampleW = Viewport.GetSampleWidth(); + FramesPerSample = Viewport.GetNumFramesPerSample(); + NumSamples = FMath::Max(0, FMath::CeilToInt(Viewport.Width / SampleW)); + FirstFrameIndex = Viewport.GetFirstFrameIndex(); + + Timeline.NumAggregatedFrames = 0; + Timeline.Samples.Reset(); + Timeline.Samples.AddDefaulted(NumSamples); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FFrameTrackTimelineBuilder::AddFrame(const Trace::FFrame& Frame) +{ + NumAddedFrames++; + + const int32 FrameIndex = Frame.Index; + + int32 SampleIndex = (FrameIndex - FirstFrameIndex) / FramesPerSample; + if (SampleIndex >= 0 && SampleIndex < NumSamples) + { + FFrameTrackSample& Sample = Timeline.Samples[SampleIndex]; + Sample.NumFrames++; + + double Duration = Frame.EndTime - Frame.StartTime; + Sample.TotalDuration += Duration; + if (Frame.StartTime < Sample.StartTime) + { + Sample.StartTime = Frame.StartTime; + } + if (Frame.EndTime > Sample.EndTime) + { + Sample.EndTime = Frame.EndTime; + } + if (Duration > Sample.LargestFrameDuration) + { + Sample.LargestFrameIndex = FrameIndex; + Sample.LargestFrameStartTime = Frame.StartTime; + Sample.LargestFrameDuration = Duration; + } + + Timeline.NumAggregatedFrames++; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FFrameTrackDrawHelper +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FFrameTrackDrawHelper::FFrameTrackDrawHelper(const FDrawContext& InDrawContext, const FFrameTrackViewport& InViewport) + : DrawContext(InDrawContext) + , Viewport(InViewport) + , WhiteBrush(FCoreStyle::Get().GetBrush("WhiteBrush")) + , BorderBrush(FEditorStyle::GetBrush("PlainBorder")) + , NumFrames(0) + , NumDrawSamples(0) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FFrameTrackDrawHelper::DrawBackground() const +{ + const FSlateBrush* AreaBrush = WhiteBrush; + const FLinearColor ValidAreaColor(0.07f, 0.07f, 0.07f, 1.0f); + const FLinearColor InvalidAreaColor(0.1f, 0.07f, 0.07f, 1.0f); + + float X0 = Viewport.MinX - Viewport.PosX; + float X1 = Viewport.MaxX - Viewport.PosX; + const float W = FMath::CeilToFloat(Viewport.Width); + const float H = FMath::CeilToFloat(Viewport.Height); + + if (X0 >= W || X1 <= 0.0f) + { + // Draw invalid area (entire view). + DrawContext.DrawBox(0.0f, 0.0f, W, H, AreaBrush, InvalidAreaColor); + } + else // X0 < W && X1 > 0 + { + if (X0 > 0.0f) + { + // Draw invalid area (left). + DrawContext.DrawBox(0.0f, 0.0f, X0, H, AreaBrush, InvalidAreaColor); + } + + if (X1 < W) + { + // Draw invalid area (right). + DrawContext.DrawBox(X1, 0.0f, W - X1, H, AreaBrush, InvalidAreaColor); + } + + X0 = FMath::Max(X0, 0.0f); + X1 = FMath::Min(X1, W); + + if (X1 > X0) + { + // Draw valid area. + DrawContext.DrawBox(X0, 0.0f, X1 - X0, H, AreaBrush, ValidAreaColor); + } + } + + DrawContext.LayerId++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FLinearColor FFrameTrackDrawHelper::GetColorById(int32 Id) const +{ + const float Alpha = 0.9; + switch (Id) + { + case 0: // Game Frames + return FLinearColor(0.75, 1.0, 1.0, Alpha); + + case 1: // Rendering Frames + return FLinearColor(1.0, 0.75, 0.75, Alpha); + + case 2: + return FLinearColor(0.75, 0.75, 1.0, Alpha); + + default: + return FLinearColor(1.0, 1.0, 1.0, Alpha); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FFrameTrackDrawHelper::DrawCached(const FFrameTrackTimeline& Timeline) const +{ + if (Timeline.NumAggregatedFrames == 0) + { + return; + } + + NumFrames += Timeline.NumAggregatedFrames; + + FLinearColor TimelineColor = GetColorById(static_cast(Timeline.Id)); + + const float SampleW = Viewport.GetSampleWidth(); + const int32 NumSamples = Timeline.Samples.Num(); + + const float Y1 = FMath::RoundToFloat(Viewport.GetViewportYForValue(0.0)); + + for (int32 SampleIndex = 0; SampleIndex < NumSamples; SampleIndex++) + { + const FFrameTrackSample& Sample = Timeline.Samples[SampleIndex]; + if (Sample.NumFrames == 0) + { + continue; + } + + NumDrawSamples++; + + const float X = SampleIndex * SampleW; + float Y2; + + FLinearColor ColorFill = TimelineColor; + + if (Sample.LargestFrameDuration == std::numeric_limits::infinity()) + { + Y2 = Viewport.Height; + ColorFill.R = 0.0f; + ColorFill.G = 0.0f; + ColorFill.B = 0.0f; + } + else + { + Y2 = FMath::RoundToFloat(Viewport.GetViewportYForValue(Sample.LargestFrameDuration)); + if (Sample.LargestFrameDuration > 1.0 / 30.0) + { + ColorFill.G *= 0.5f; + ColorFill.B *= 0.5f; + } + else if (Sample.LargestFrameDuration > 1.0 / 60.0) + { + ColorFill.B *= 0.5f; + } + } + + const float Y = FMath::RoundToFloat(Viewport.Height) - Y2; + const float H = Y2 - Y1; + + const FLinearColor ColorBorder(ColorFill.R * 0.75f, ColorFill.G * 0.75f, ColorFill.B * 0.75f, 1.0); + + if (SampleW > 2.0f) + { + DrawContext.DrawBox(X + 1.0f, Y + 1.0f, SampleW - 2.0f, H - 2.0f, WhiteBrush, ColorFill); + + // Draw border. + DrawContext.DrawBox(X, Y, 1.0, H, WhiteBrush, ColorBorder); + DrawContext.DrawBox(X + SampleW - 1.0f, Y, 1.0, H, WhiteBrush, ColorBorder); + DrawContext.DrawBox(X + 1.0f, Y, SampleW - 2.0f, 1.0f, WhiteBrush, ColorBorder); + DrawContext.DrawBox(X + 1.0f, Y + H - 1.0f, SampleW - 2.0f, 1.0f, WhiteBrush, ColorBorder); + } + else + { + DrawContext.DrawBox(X, Y, SampleW, H, WhiteBrush, ColorBorder); + } + } + + DrawContext.LayerId++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FFrameTrackDrawHelper::DrawHoveredSample(const FFrameTrackSample& Sample) const +{ + const float SampleW = Viewport.GetSampleWidth(); + const int32 FramesPerSample = Viewport.GetNumFramesPerSample(); + const int32 FirstFrameIndex = Viewport.GetFirstFrameIndex(); + const int32 SampleIndex = (Sample.LargestFrameIndex - FirstFrameIndex) / FramesPerSample; + const float X = SampleIndex * SampleW; + + const float Y1 = FMath::RoundToFloat(Viewport.GetViewportYForValue(0.0)); + float Y2; + if (Sample.LargestFrameDuration == std::numeric_limits::infinity()) + { + Y2 = Viewport.Height; + } + else + { + Y2 = FMath::RoundToFloat(Viewport.GetViewportYForValue(Sample.LargestFrameDuration)); + } + const float Y = FMath::RoundToFloat(Viewport.Height) - Y2; + const float H = Y2 - Y1; + + const FLinearColor ColorBorder(1.0f, 1.0f, 0.0f, 1.0); + DrawContext.DrawBox(X - 1.0f, Y - 1.0f, SampleW + 2.0f, H + 2.0f, BorderBrush, ColorBorder); + DrawContext.LayerId++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FFrameTrackDrawHelper::DrawHighlightedInterval(const FFrameTrackTimeline& Timeline, const double StartTime, const double EndTime) const +{ + const int32 NumSamples = Timeline.Samples.Num(); + + //TODO: binary search + int32 Index1 = 0; + int32 Index2 = NumSamples - 1; + while (Index1 < NumSamples && Timeline.Samples[Index1].EndTime < StartTime) + { + Index1++; + } + while (Index2 >= Index1 && Timeline.Samples[Index2].StartTime > EndTime) + { + Index2--; + } + + if (Index1 <= Index2) + { + const float SampleW = Viewport.GetSampleWidth(); + float X1 = Index1 * SampleW; + float X2 = Index2 * SampleW; + + constexpr float Y1 = 12.0f; // allows 12px for the horizontal scrollbar (one displayed on top of the track) + const float Y2 = Viewport.Height; + constexpr float D = 2.0f; // line thickness (for both horizontal and vertical lines) + constexpr float H = 10.0f; // height of corner lines + + const FLinearColor Color(1.0f, 1.0f, 1.0f, 1.0f); + + if (X1 >= 0.0f && X1 < Viewport.Width - 2.0f) + { + // Draw left side vertical lines. + DrawContext.DrawBox(X1 - D, Y1, D, H, WhiteBrush, Color); + DrawContext.DrawBox(X1 - D, Y2 - H, D, H, WhiteBrush, Color); + } + + if (X2 >= -2.0f && X2 < Viewport.Width) + { + // Draw right side vertical lines. + DrawContext.DrawBox(X2, Y1, D, H, WhiteBrush, Color); + DrawContext.DrawBox(X2, Y2 - H, D, H, WhiteBrush, Color); + } + + if (X1 < 0) + { + X1 = 0.0f; + } + if (X2 > Viewport.Width) + { + X2 = Viewport.Width; + } + if (X1 < X2) + { + // Draw horizontal lines. + DrawContext.DrawBox(X1, Y1, X2 - X1, D, WhiteBrush, Color); + DrawContext.DrawBox(X1, Y2 - D, X2 - X1, D, WhiteBrush, Color); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/FrameTrackHelper.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/FrameTrackHelper.h new file mode 100644 index 000000000000..807f29508995 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/FrameTrackHelper.h @@ -0,0 +1,137 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Styling/WidgetStyle.h" + +enum class ESlateDrawEffect : uint8; + +struct FDrawContext; +struct FGeometry; +struct FSlateBrush; + +class FFrameTrackViewport; +class FSlateWindowElementList; + +namespace Trace +{ + struct FFrame; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +enum class EFrameType +{ + Unknown = -1, + + Game, + Render +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct FFrameTrackSample +{ + int32 NumFrames; + double TotalDuration; // sum of durations of all frames in this sample + double StartTime; // min start time of all frames in this sample + double EndTime; // max end time of all frames in this sample + double LargestFrameIndex; // index of the largest frame + double LargestFrameStartTime; // start time of the largest frame + double LargestFrameDuration; // duration of the largest frame + + FFrameTrackSample() + : NumFrames(0) + , TotalDuration(0.0) + , StartTime(DBL_MAX) + , EndTime(-DBL_MAX) + , LargestFrameIndex(0) + , LargestFrameStartTime(0.0) + , LargestFrameDuration(0.0) + {} +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct FFrameTrackTimeline +{ + uint64 Id; + //TODO: EFrameType Type; + + //TODO: double MinValue; // min value + //TODO: double MaxValue; // max value + + //TODO: float SampleW; // width of a sample, in Slate units + //TODO: int32 FramesPerSample; // number of frames in a sample + //TODO: int32 FirstFrameIndex; // index of first frame in first sample; can be negative + //TODO: int32 NumSamples; // total number of samples + int32 NumAggregatedFrames; // total number of frames aggregated in samples; ie. sum of all Sample.NumFrames + + TArray Samples; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FFrameTrackTimelineBuilder +{ +public: + FFrameTrackTimelineBuilder(FFrameTrackTimeline& InTimeline, const FFrameTrackViewport& InViewport); + + /** + * Non-copyable + */ + FFrameTrackTimelineBuilder(const FFrameTrackTimelineBuilder&) = delete; + FFrameTrackTimelineBuilder& operator=(const FFrameTrackTimelineBuilder&) = delete; + + void AddFrame(const Trace::FFrame& Frame); + + int32 GetNumAddedFrames() const { return NumAddedFrames; } + +private: + FFrameTrackTimeline& Timeline; + const FFrameTrackViewport& Viewport; + + float SampleW; // width of a sample, in Slate units + int32 FramesPerSample; // number of frames in a sample + int32 FirstFrameIndex; // index of first frame in first sample; can be negative + int32 NumSamples; // total number of samples + + // Debug stats. + int32 NumAddedFrames; // counts total number of added frame events +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FFrameTrackDrawHelper +{ +public: + FFrameTrackDrawHelper(const FDrawContext& InDrawContext, const FFrameTrackViewport& InViewport); + + /** + * Non-copyable + */ + FFrameTrackDrawHelper(const FFrameTrackDrawHelper&) = delete; + FFrameTrackDrawHelper& operator=(const FFrameTrackDrawHelper&) = delete; + + void DrawBackground() const; + void DrawCached(const FFrameTrackTimeline& Timeline) const; + void DrawHoveredSample(const FFrameTrackSample& Sample) const; + void DrawHighlightedInterval(const FFrameTrackTimeline& Timeline, const double StartTime, const double EndTime) const; + + FLinearColor GetColorById(int32 Id) const; + + int32 GetNumFrames() const { return NumFrames; } + int32 GetNumDrawSamples() const { return NumDrawSamples; } + +private: + const FDrawContext& DrawContext; + const FFrameTrackViewport& Viewport; + + const FSlateBrush* WhiteBrush; + const FSlateBrush* BorderBrush; + + // Debug stats. + mutable int32 NumFrames; + mutable int32 NumDrawSamples; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/FrameTrackViewport.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/FrameTrackViewport.cpp new file mode 100644 index 000000000000..c282a21fc2a4 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/FrameTrackViewport.cpp @@ -0,0 +1,3 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "FrameTrackViewport.h" diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/FrameTrackViewport.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/FrameTrackViewport.h new file mode 100644 index 000000000000..77752f60b707 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/FrameTrackViewport.h @@ -0,0 +1,228 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +class FFrameTrackViewport +{ +private: + const float SLATE_UNITS_TOLERANCE = 0.1f; + +public: + FFrameTrackViewport() + { + Reset(); + } + + void Reset() + { + Width = 0.0f; + Height = 0.0f; + + MinIndex = 0; + MaxIndex = 0; + + MinX = 0.0f; + MaxX = 0.0f; + PosX = 0.0f; + MinScaleX = 0.0001f; + MaxScaleX = 16.0f; + ScaleX = MaxScaleX; + + MinValue = 0.0; + MaxValue = 1.0f; + + MinY = 0.0f; + MaxY = 1.0f; + PosY = 0.0f; + MinScaleY = 0.001f; + MaxScaleY = 1000000.0f; + ScaleY = 1000.0f; + + LastOnPaintMaxValue = 0.0; + } + + void SetSize(const float InWidth, const float InHeight) + { + if (!FMath::IsNearlyEqual(Width, InWidth, SLATE_UNITS_TOLERANCE) || + !FMath::IsNearlyEqual(Height, InHeight, SLATE_UNITS_TOLERANCE)) + { + Width = InWidth; + Height = InHeight; + OnSizeChanged(); + } + } + + void SetMinMaxIndexInterval(const int32 InMinIndex, const int32 InMaxIndex) + { + MinIndex = InMinIndex; + MaxIndex = InMaxIndex; + UpdateMinMaxX(); + } + + int32 GetIndexAtPosX(const float X) const + { + return FMath::RoundToInt(X / ScaleX); + } + + int32 GetIndexAtViewportX(const float VX) const + { + return FMath::RoundToInt((PosX + VX) / ScaleX); + } + + float GetPosXForIndex(const int32 Index) const + { + return static_cast(Index) * ScaleX; + } + + float GetViewportXForIndex(const int32 Index) const + { + return static_cast(Index) * ScaleX - PosX; + } + + float GetRoundedViewportXForIndex(const int32 Index) const + { + return FMath::RoundToFloat(static_cast(Index) * ScaleX - PosX); + } + + void ScrollAtPosX(const float InPosX) + { + PosX = InPosX; + OnPosXChanged(); + } + + void ScrollAtIndex(const int32 Index) + { + PosX = static_cast(Index) * ScaleX; + OnPosXChanged(); + } + + void CenterOnIndex(const int32 Index) + { + float W = FMath::Max(1.0f, ScaleX); + if (W > Width) + { + ScrollAtIndex(Index); + } + else + { + float VX = (Width - W) / 2.0f; + PosX = static_cast(Index) * ScaleX - VX; + OnPosXChanged(); + } + } + + void CenterOnIndexInterval(const int32 IntervalStartIndex, const int32 IntervalEndIndex) + { + float W = FMath::Max(1.0f, ScaleX * static_cast(IntervalEndIndex - IntervalStartIndex)); + if (W > Width) + { + ScrollAtIndex(IntervalStartIndex); + } + else + { + float VX = (Width - W) / 2.0f; + PosX = static_cast(IntervalStartIndex) * ScaleX - VX; + OnPosXChanged(); + } + } + + void ZoomWithFixedViewportX(const float InNewScaleX, const float VX) + { + float LocalNewScaleX = FMath::Clamp(InNewScaleX, MinScaleX, MaxScaleX); + if (LocalNewScaleX != ScaleX) + { + // Index at viewport X should remain the same. So we resolve equation: + // (PosX + VX) / ScaleX = (NewPosX + VX) / NewScaleX + // ==> NewPosX = (PosX + VX) / ScaleX * NewScaleX - VX + PosX = (PosX + VX) * (LocalNewScaleX / ScaleX) - VX; + ScaleX = LocalNewScaleX; + + OnPosXChanged(); + OnScaleXChanged(); + } + } + + float GetSampleWidth() const { return FMath::Max(1.0f, FMath::RoundToFloat(ScaleX)); } + int32 GetNumFramesPerSample() const { return FMath::Max(1, FMath::RoundToInt(1.0f / ScaleX)); } + + int32 GetFirstFrameIndex() const { return GetIndexAtViewportX(0.0f); } + + float GetPosYForValue(const double Value) const + { + return static_cast(Value) * ScaleY; + } + + float GetViewportYForValue(const double Value) const + { + return static_cast(Value) * ScaleY - PosY; + } + + void SetScaleY(const float InNewScaleY) + { + float LocalNewScaleY = FMath::Clamp(InNewScaleY, MinScaleY, MaxScaleY); + if (LocalNewScaleY != ScaleY) + { + ScaleY = LocalNewScaleY; + OnScaleYChanged(); + } + } + +private: + void OnSizeChanged() + { + } + + void OnPosXChanged() + { + } + + void OnScaleXChanged() + { + UpdateMinMaxX(); + } + + void OnScaleYChanged() + { + } + + void UpdateMinMaxX() + { + MinX = static_cast(MinIndex) * ScaleX; + MaxX = static_cast(MaxIndex) * ScaleX; + + //float NewPosX = FMath::Clamp(PosX, MinX, MaxX); + //if (FMath::IsNearlyEqual(NewPosX, PosX, SLATE_UNITS_TOLERANCE)) + //{ + // PosX = NewPosX; + // OnPosXChanged(); + //} + } + +public: + float Width; // width of viewport, in Slate units + float Height; // height of viewport, in Slate units + + int32 MinIndex; // minimum index + int32 MaxIndex; // maximum index (exclusive) + + float MinX; // minimum horizontal position, in Slate units + float MaxX; // maximum horizontal position, in Slate units + float PosX; + float MinScaleX; // minimum horizontal scale factor + float MaxScaleX; // maximum horizontal scale factor + float ScaleX; // scale factor between frame index and Slate units: if > 1, it represents number of Slate units for one frame index, otherwise it represents number of frames in one Slate unit + + double MinValue; + double MaxValue; + + float MinY; // minimum vertical scroll position, in Slate units + float MaxY; // maximum vertical scroll position, in Slate units + float PosY; // current vertical scroll position, in Slate units; origin at bottom + float MinScaleY; // minimum vertical scale factor + float MaxScaleY; // maximum vertical scale factor + float ScaleY; // scale factor between Value units and Slate units + + mutable double LastOnPaintMaxValue; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/GraphTrack.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/GraphTrack.cpp new file mode 100644 index 000000000000..e662d9acafbe --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/GraphTrack.cpp @@ -0,0 +1,675 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "GraphTrack.h" + +#include "Brushes/SlateColorBrush.h" +#include "Brushes/SlateBorderBrush.h" +#include "EditorStyleSet.h" +#include "Rendering/DrawElements.h" +#include "Styling/CoreStyle.h" +#include "TraceServices/AnalysisService.h" +#include "TraceServices/SessionService.h" + +// Insights +#include "Insights/Common/PaintUtils.h" +#include "Insights/InsightsManager.h" +#include "Insights/TimingProfilerManager.h" +#include "Insights/ViewModels/TimingTrackViewport.h" + +#define LOCTEXT_NAMESPACE "GraphTrack" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FGraphTrackSeries +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FGraphTrackSeries::FGraphTrackSeries() + : Color(0.0f, 0.5f, 1.0f, 1.0f) + , BorderColor(0.3f, 0.8f, 1.0f, 1.0f) + //, Points() + //, LinePoints() + //, Boxes() +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FGraphTrackSeries::~FGraphTrackSeries() +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FGraphTrack +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FGraphTrack::FGraphTrack(uint64 InTrackId) + : FBaseTimingTrack(InTrackId) + //, AllSeries() + , WhiteBrush(FCoreStyle::Get().GetBrush("WhiteBrush")) + , PointBrush(FEditorStyle::GetBrush("Graph.ExecutionBubble")) + , BorderBrush(new FSlateBorderBrush(NAME_None, FMargin(1.0f))) + //, BorderBrush(FEditorStyle::GetBrush("PlainBorder")) + , Font(FCoreStyle::GetDefaultFontStyle("Regular", 8)) + , bDrawPoints(true) + , bDrawPointsWithBorder(true) + , bDrawLines(true) + , bDrawLinesWithDuration(false) + , bDrawBoxes(true) + , BaselineY(0.0f) + , ScaleY(1.0) + , NumAddedEvents(0) + , NumDrawPoints(0) + , NumDrawLines(0) + , NumDrawBoxes(0) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FGraphTrack::~FGraphTrack() +{ + delete BorderBrush; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FGraphTrack::UpdateHoveredState(float MouseX, float MouseY, const FTimingTrackViewport& Viewport) +{ + constexpr float HeaderWidth = 100.0f; + constexpr float HeaderHeight = 14.0f; + + if (MouseY >= GetPosY() && MouseY < GetPosY() + GetHeight()) + { + SetHoveredState(true); + SetHeaderHoveredState(MouseX < HeaderWidth && MouseY < GetPosY() + HeaderHeight); + } + else + { + SetHoveredState(false); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FGraphTrack::UpdateStats() +{ + NumDrawPoints = 0; + NumDrawLines = 0; + NumDrawBoxes = 0; + + for (const FGraphTrackSeries& Series : AllSeries) + { + NumDrawPoints += Series.Points.Num(); + NumDrawLines += Series.LinePoints.Num() / 2; + NumDrawBoxes += Series.Boxes.Num(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FGraphTrack::Draw(FDrawContext& DrawContext, const FTimingTrackViewport& Viewport) const +{ + DrawContext.DrawBox(0.0f, GetPosY(), Viewport.Width, GetHeight(), WhiteBrush, FLinearColor(0.1f, 0.1f, 0.1f, 1.0f)); + DrawContext.LayerId++; + + //TODO: Set clipping to (0, GetPosY(), Viewport.Width, GetHeight())! + + for (const FGraphTrackSeries& Series : AllSeries) + { + DrawSeries(Series, DrawContext, Viewport); + } + + // Draw baseline (Value == 0). + DrawContext.DrawBox(0.0f, GetPosY() + BaselineY - 1.0f, Viewport.Width, 1.0f, WhiteBrush, FLinearColor(0.05f, 0.05f, 0.05f, 1.0f)); + DrawContext.LayerId++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FGraphTrack::DrawSeries(const FGraphTrackSeries& Series, FDrawContext& DrawContext, const FTimingTrackViewport& Viewport) const +{ + if (bDrawBoxes) + { + int32 NumBoxes = Series.Boxes.Num(); + for (int32 Index = 0; Index < NumBoxes; ++Index) + { + const FGraphBox& Box = Series.Boxes[Index]; + DrawContext.DrawBox(Box.X, GetPosY() + Box.Y, Box.W, BaselineY - Box.Y, WhiteBrush, Series.Color); + } + DrawContext.LayerId++; + } + + if (bDrawLines) + { + FPaintGeometry Geo = DrawContext.Geometry.ToPaintGeometry(); + Geo.AppendTransform(FSlateLayoutTransform(FVector2D(0, GetPosY()))); + FSlateDrawElement::MakeLines(DrawContext.ElementList, DrawContext.LayerId, Geo, Series.LinePoints, DrawContext.DrawEffects, Series.Color, false, 1.0f); + DrawContext.LayerId++; + } + + if (bDrawPoints) + { + int32 NumPoints = Series.Points.Num(); + +#define INSIGHTS_GRAPH_TRACK_DRAW_POINTS_AS_RECTANGLES 0 +#if !INSIGHTS_GRAPH_TRACK_DRAW_POINTS_AS_RECTANGLES + + if (bDrawPointsWithBorder) + { + // Draw points (border). + for (int32 Index = 0; Index < NumPoints; ++Index) + { + const FVector2D& Pt = Series.Points[Index]; + const float PtX = Pt.X - PointVisualSize / 2.0f - 1.0f; + const float PtY = GetPosY() + Pt.Y - PointVisualSize / 2.0f - 1.0f; + DrawContext.DrawBox(PtX, PtY, PointVisualSize + 2.0f, PointVisualSize + 2.0f, PointBrush, Series.BorderColor); + } + DrawContext.LayerId++; + } + + // Draw points (interior). + for (int32 Index = 0; Index < NumPoints; ++Index) + { + const FVector2D& Pt = Series.Points[Index]; + const float PtX = Pt.X - PointVisualSize / 2.0f; + const float PtY = GetPosY() + Pt.Y - PointVisualSize / 2.0f; + DrawContext.DrawBox(PtX, PtY, PointVisualSize, PointVisualSize, PointBrush, Series.Color); + } + DrawContext.LayerId++; + +#else // Alternative way of drawing points; kept here for debugging purposes. + + //const float Angle = FMath::DegreesToRadians(45.0f); + + if (bDrawPointsWithBorder) + { + // Draw borders. + const float BorderPtSize = PointVisualSize; + //FVector2D BorderRotationPoint(BorderPtSize / 2.0f, BorderPtSize / 2.0f); + for (int32 Index = 0; Index < NumPoints; ++Index) + { + const FVector2D& Pt = Series.Points[Index]; + const float PtX = Pt.X - BorderPtSize / 2.0f; + const float PtY = GetPosY() + Pt.Y - BorderPtSize / 2.0f; + DrawContext.DrawBox(PtX, PtY, BorderPtSize, BorderPtSize, BorderBrush, Series.BorderColor); + //DrawContext.DrawRotatedBox(PtX, PtY, BorderPtSize, BorderPtSize, BorderBrush, Series.BorderColor, Angle, BorderRotationPoint); + } + DrawContext.LayerId++; + } + + // Draw points as rectangles. + const float PtSize = PointVisualSize - 2.0f; + //FVector2D RotationPoint(PtSize / 2.0f, PtSize / 2.0f); + for (int32 Index = 0; Index < NumPoints; ++Index) + { + const FVector2D& Pt = Series.Points[Index]; + const float PtX = Pt.X - PtSize / 2.0f; + const float PtY = GetPosY() + Pt.Y - PtSize / 2.0f; + DrawContext.DrawBox(PtX, PtY, PtSize, PtSize, PointBrush, Series.Color); + //DrawContext.DrawRotatedBox(PtX, PtY, PtSize, PtSize, WhiteBrush, Series.Color, Angle, RotationPoint); + } + DrawContext.LayerId++; + +#endif + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FRandomGraphTrack +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FRandomGraphTrack::FRandomGraphTrack(uint64 InTrackId) + : FGraphTrack(InTrackId) +{ + bDrawPoints = true; + bDrawPointsWithBorder = true; + bDrawLines = true; + bDrawLinesWithDuration = false; + bDrawBoxes = false; + + AllSeries.AddDefaulted(); + FGraphTrackSeries& Series = AllSeries[0]; + Series.SetColor(FLinearColor(0.0f, 0.5f, 1.0f, 1.0f), FLinearColor(0.3f, 0.8f, 1.0f, 1.0f)); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FRandomGraphTrack::~FRandomGraphTrack() +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FRandomGraphTrack::Update(const FTimingTrackViewport& Viewport) +{ + NumAddedEvents = 0; + + // TODO: vertical panning and zooming + BaselineY = GetHeight(); + ScaleY = 1.0; + + ensure(AllSeries.Num() == 1); + GenerateSeries(AllSeries[0], Viewport, 1000000); + + UpdateStats(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FRandomGraphTrack::GenerateSeries(FGraphTrackSeries& Series, const FTimingTrackViewport& Viewport, const int32 EventCount) +{ + ////////////////////////////////////////////////// + // Generate random events. + + constexpr double MinDeltaTime = 0.0000001; // 100ns + constexpr double MaxDeltaTime = 0.01; // 100ms + const float MinValue = 0; + const float MaxValue = GetHeight(); + + struct FGraphEvent + { + double Time; + double Duration; + double Value; + }; + + TArray Events; + Events.Reserve(EventCount); + + FRandomStream RandomStream(0); + double NextT = 0.0; + for (int32 Index = 0; Index < EventCount; ++Index) + { + FGraphEvent Ev; + Ev.Time = NextT; + const double TimeAdvance = RandomStream.GetFraction() * (MaxDeltaTime - MinDeltaTime); + NextT += MinDeltaTime + TimeAdvance; + Ev.Duration = MinDeltaTime + RandomStream.GetFraction() * TimeAdvance; + Ev.Value = MinValue + RandomStream.GetFraction() * (MaxValue - MinValue); + Events.Add(Ev); + } + + ////////////////////////////////////////////////// + // Optimize and build draw lists. + { + FGraphTrackBuilder Builder(*this, Series, Viewport); + + int32 Index = 0; + while (Index < EventCount && Events[Index].Time < Viewport.StartTime) + { + ++Index; + } + if (Index > 0) + { + Index--; // one point outside screen (left side) + } + while (Index < EventCount) + { + const FGraphEvent& Ev = Events[Index]; + Builder.AddEvent(Ev.Time, Ev.Duration, Ev.Value); + + if (Ev.Time > Viewport.EndTime) + { + // one point outside screen (right side) + break; + } + + ++Index; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FFramesGraphTrack +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FFramesGraphTrack::FFramesGraphTrack(uint64 InTrackId) + : FGraphTrack(InTrackId) +{ + bDrawPoints = true; + bDrawPointsWithBorder = true; + bDrawLines = true; + bDrawLinesWithDuration = true; + bDrawBoxes = false; + + AllSeries.AddDefaulted(2); + + FGraphTrackSeries& GameFramesSeries = AllSeries[0]; + GameFramesSeries.SetColor(FLinearColor(0.3f, 0.3f, 1.0f, 1.0f), FLinearColor(1.0f, 1.0f, 1.0f, 1.0f)); + + FGraphTrackSeries& RenderFramesSeries = AllSeries[1]; + RenderFramesSeries.SetColor(FLinearColor(1.0f, 0.3f, 0.3f, 1.0f), FLinearColor(1.0f, 1.0f, 1.0f, 1.0f)); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FFramesGraphTrack::~FFramesGraphTrack() +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FFramesGraphTrack::Update(const FTimingTrackViewport& Viewport) +{ + NumAddedEvents = 0; + + // TODO: Vertical panning and zooming needs to be moved out in a Viewport like controller. + BaselineY = GetHeight(); + ScaleY = 200.0 / 0.1; // 200px = 100ms + + ensure(AllSeries.Num() == 2); + UpdateSeries(AllSeries[0], Viewport, TraceFrameType_Game); + UpdateSeries(AllSeries[1], Viewport, TraceFrameType_Rendering); + + UpdateStats(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FFramesGraphTrack::UpdateSeries(FGraphTrackSeries& Series, const FTimingTrackViewport& Viewport, ETraceFrameType FrameType) +{ + FGraphTrackBuilder Builder(*this, Series, Viewport); + + TSharedPtr Session = FInsightsManager::Get()->GetSession(); + if (Session.IsValid()) + { + Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); + const Trace::IFrameProvider& FramesProvider = ReadFrameProvider(*Session.Get()); + uint64 FrameCount = FramesProvider.GetFrameCount(FrameType); + FramesProvider.EnumerateFrames(FrameType, 0, FrameCount - 1, [&Builder](const Trace::FFrame& Frame) + { + const double Duration = Frame.EndTime - Frame.StartTime; + Builder.AddEvent(Frame.StartTime, Duration, Duration); + }); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FGraphTrackBuilder +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FGraphTrackBuilder::FGraphTrackBuilder(FGraphTrack& InTrack, FGraphTrackSeries& InSeries, const FTimingTrackViewport& InViewport) + : Track(InTrack) + , Series(InSeries) + , Viewport(InViewport) +{ + BeginPoints(); + BeginConnectedLines(); + BeginBoxes(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FGraphTrackBuilder::~FGraphTrackBuilder() +{ + EndPoints(); + EndConnectedLines(); + EndBoxes(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FGraphTrackBuilder::AddEvent(double Time, double Duration, double Value) +{ + Track.NumAddedEvents++; + + if (Track.bDrawPoints) + { + AddPoint(Time, Value); + } + + if (Track.bDrawLines) + { + AddConnectedLine(Time, Value); + + if (Track.bDrawLinesWithDuration) + { + AddConnectedLine(Time + Duration, Value); + } + } + + if (Track.bDrawBoxes) + { + AddBox(Time, Duration, Value); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FGraphTrackBuilder - Points +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FGraphTrackBuilder::BeginPoints() +{ + Series.Points.Reset(); + + PointsCurrentX = -DBL_MAX; + + PointsAtCurrentX.Reset(); + int32 MaxPointsPerLineScan = FMath::CeilToInt(Track.GetHeight() / FGraphTrack::PointSizeY); + if (MaxPointsPerLineScan > 0) + { + PointsAtCurrentX.AddDefaulted(MaxPointsPerLineScan); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FGraphTrackBuilder::AddPoint(double Time, double Value) +{ + const float X = Viewport.TimeToSlateUnitsRounded(Time); + if (X < -FGraphTrack::PointVisualSize / 2.0f || X >= Viewport.Width + FGraphTrack::PointVisualSize / 2.0f) + { + return; + } + + // Align the X with a grid of GraphTrackPointDX pixels in size, in the global space (i.e. scroll independent). + const double AlignedX = FMath::RoundToDouble(Time * Viewport.ScaleX / FGraphTrack::PointSizeX) * FGraphTrack::PointSizeX; + + if (AlignedX > PointsCurrentX + FGraphTrack::PointSizeX - 0.5) + { + FlushPoints(); + PointsCurrentX = AlignedX; + } + + const float Y = Track.GetYForValue(Value); + + int32 Index = FMath::RoundToInt(Y / FGraphTrack::PointSizeY); + if (Index >= 0 && Index < PointsAtCurrentX.Num()) + { + FPointInfo& Pt = PointsAtCurrentX[Index]; + Pt.bValid = true; + Pt.X = X; + Pt.Y = Y; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FGraphTrackBuilder::FlushPoints() +{ + for (int32 Index = 0; Index < PointsAtCurrentX.Num(); ++Index) + { + FPointInfo& Pt = PointsAtCurrentX[Index]; + if (Pt.bValid) + { + Pt.bValid = false; + Series.Points.Add(FVector2D(Pt.X, Pt.Y)); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FGraphTrackBuilder::EndPoints() +{ + FlushPoints(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FGraphTrackBuilder - Connected Lines +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FGraphTrackBuilder::BeginConnectedLines() +{ + Series.LinePoints.Reset(); + + LinesCurrentX = -FLT_MAX; + LinesMinY = FLT_MAX; + LinesMaxY = -FLT_MAX; + LinesFirstY = FLT_MAX; + LinesLastY = FLT_MAX; + bIsLastLineAdded = false; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FGraphTrackBuilder::AddConnectedLine(double Time, double Value) +{ + if (bIsLastLineAdded) + { + return; + } + + const float Y = Track.GetYForValue(Value); + + const float X = Viewport.TimeToSlateUnitsRounded(Viewport.RestrictEndTime(Time)); + + ensure(X >= LinesCurrentX); // we are assuming events are already sorted by Time + + if (X < 0) + { + LinesCurrentX = X; + LinesLastY = Y; + return; + } + + if (X >= Viewport.Width) + { + if (!bIsLastLineAdded) + { + bIsLastLineAdded = true; + + if (LinesLastY != FLT_MAX) + { + FlushConnectedLine(); + + Series.LinePoints.Add(FVector2D(LinesCurrentX, LinesLastY)); + Series.LinePoints.Add(FVector2D(X, Y)); + } + + // Reset the "reduction line" so last FlushConnectedLine() call will do nothing. + LinesMinY = Y; + LinesMaxY = Y; + } + return; + } + + if (X > LinesCurrentX) + { + if (LinesLastY != FLT_MAX) + { + FlushConnectedLine(); + + Series.LinePoints.Add(FVector2D(LinesCurrentX, LinesLastY)); + Series.LinePoints.Add(FVector2D(X, Y)); + } + + // Advance the "reduction line". + LinesCurrentX = X; + LinesMinY = Y; + LinesMaxY = Y; + LinesFirstY = Y; + LinesLastY = Y; + } + else + { + // Merge current line with the "reduction line". + if (Y < LinesMinY) + { + LinesMinY = Y; + } + if (Y > LinesMaxY) + { + LinesMaxY = Y; + } + LinesLastY = Y; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FGraphTrackBuilder::FlushConnectedLine() +{ + if (LinesCurrentX >= 0.0f && LinesMinY != LinesMaxY) + { + Series.LinePoints.Add(FVector2D(LinesCurrentX, LinesMaxY)); + Series.LinePoints.Add(FVector2D(LinesCurrentX, LinesMinY)); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FGraphTrackBuilder::EndConnectedLines() +{ + FlushConnectedLine(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FGraphTrackBuilder - Boxes +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FGraphTrackBuilder::BeginBoxes() +{ + Series.Boxes.Reset(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FGraphTrackBuilder::AddBox(double Time, double Duration, double Value) +{ + float X1 = Viewport.TimeToSlateUnitsRounded(Time); + if (X1 > Viewport.Width) + { + return; + } + + double EndTime = Viewport.RestrictEndTime(Time + Duration); + float X2 = Viewport.TimeToSlateUnitsRounded(EndTime); + if (X2 < 0) + { + return; + } + + float W = X2 - X1; + ensure(W >= 0); // we expect events to be sorted + + // Timing events are displayed with minimum 1px (including empty ones). + if (W == 0) + { + W = 1.0f; + } + + // TODO: reduction algorithm + FGraphBox Box; + Box.X = X1; + Box.W = W; + Box.Y = Track.GetYForValue(Value); + Series.Boxes.Add(Box); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FGraphTrackBuilder::FlushBox() +{ + // TODO: reduction algorithm +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FGraphTrackBuilder::EndBoxes() +{ + FlushBox(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/GraphTrack.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/GraphTrack.h new file mode 100644 index 000000000000..d2ac7388bff0 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/GraphTrack.h @@ -0,0 +1,214 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Fonts/SlateFontInfo.h" + +// Insights +#include "Insights/ViewModels/BaseTimingTrack.h" + +struct FDrawContext; +struct FSlateBrush; +class FTimingTrackViewport; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct FGraphBox +{ + float X; + float W; + float Y; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FGraphTrackSeries +{ + friend class FGraphTrack; + friend class FGraphTrackBuilder; + +public: + FGraphTrackSeries(); + ~FGraphTrackSeries(); + + void SetColor(FLinearColor InColor, FLinearColor InBorderColor) + { + Color = InColor; + BorderColor = InBorderColor; + } + +protected: + //FText Name; + //FText Description; + + FLinearColor Color; + FLinearColor BorderColor; + + TArray Points; + TArray LinePoints; + TArray Boxes; + + //bool bIsVisible; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FGraphTrack : public FBaseTimingTrack +{ + friend class FGraphTrackBuilder; + +private: + // Visual size of points (in pixels). + static constexpr float PointVisualSize = 7.0f; + + // Size of points (in pixels) used in reduction algorithm. + static constexpr double PointSizeX = 3.0f; + static constexpr float PointSizeY = 3.0f; + +public: + FGraphTrack(uint64 InTrackId); + virtual ~FGraphTrack(); + + virtual void UpdateHoveredState(float MouseX, float MouseY, const FTimingTrackViewport& Viewport); + + virtual void Update(const FTimingTrackViewport& Viewport) override = 0; + + void Draw(FDrawContext& DrawContext, const FTimingTrackViewport& Viewport) const; + + int32 GetNumAddedEvents() const { return NumAddedEvents; } + int32 GetNumDrawPoints() const { return NumDrawPoints; } + int32 GetNumDrawLines() const { return NumDrawLines; } + int32 GetNumDrawBoxes() const { return NumDrawBoxes; } + +protected: + void UpdateStats(); + + void DrawSeries(const FGraphTrackSeries& Series, FDrawContext& DrawContext, const FTimingTrackViewport& Viewport) const; + + float GetYForValue(double Value) const + { + return BaselineY - static_cast(Value * ScaleY); // TODO: vertical zooming and panning + } + +protected: + TArray AllSeries; + + // Slate resources + const FSlateBrush* WhiteBrush; + const FSlateBrush* PointBrush; + const FSlateBrush* BorderBrush; + const FSlateFontInfo Font; + + bool bDrawPoints; + bool bDrawPointsWithBorder; + bool bDrawLines; + bool bDrawLinesWithDuration; + bool bDrawBoxes; + + float BaselineY; // Y position (in viewport local space) of the baseline (with Value == 0); in pixels (Slate units) + double ScaleY; // scale between Value units and viewport units; in pixels (Slate units) / Value unit + + // Stats + int32 NumAddedEvents; + int32 NumDrawPoints; + int32 NumDrawLines; + int32 NumDrawBoxes; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FRandomGraphTrack : public FGraphTrack +{ +public: + FRandomGraphTrack(uint64 InTrackId); + virtual ~FRandomGraphTrack(); + + virtual void Update(const FTimingTrackViewport& Viewport) override; + +protected: + void GenerateSeries(FGraphTrackSeries& Series, const FTimingTrackViewport& Viewport, const int32 EventCount); +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FFramesGraphTrack : public FGraphTrack +{ +public: + FFramesGraphTrack(uint64 InTrackId); + virtual ~FFramesGraphTrack(); + + virtual void Update(const FTimingTrackViewport& Viewport) override; + +protected: + void UpdateSeries(FGraphTrackSeries& Series, const FTimingTrackViewport& Viewport, ETraceFrameType FrameType); +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FGraphTrackBuilder +{ +private: + struct FPointInfo + { + bool bValid; + float X; + float Y; + + FPointInfo() : bValid(false) {} + }; + +public: + FGraphTrackBuilder(FGraphTrack& InTrack, FGraphTrackSeries& InSeries, const FTimingTrackViewport& InViewport); + ~FGraphTrackBuilder(); + + /** + * Non-copyable + */ + FGraphTrackBuilder(const FGraphTrackBuilder&) = delete; + FGraphTrackBuilder& operator=(const FGraphTrackBuilder&) = delete; + + FGraphTrack& GetTrack() const { return Track; } + FGraphTrackSeries& GetSeries() const { return Series; } + const FTimingTrackViewport& GetViewport() const { return Viewport; } + + void AddEvent(double Time, double Duration, double Value); + +private: + void BeginPoints(); + void AddPoint(double Time, double Value); + void FlushPoints(); + void EndPoints(); + + void BeginConnectedLines(); + void AddConnectedLine(double Time, double Value); + void FlushConnectedLine(); + void EndConnectedLines(); + + void BeginBoxes(); + void AddBox(double Time, double Duration, double Value); + void FlushBox(); + void EndBoxes(); + +private: + FGraphTrack& Track; + FGraphTrackSeries& Series; + const FTimingTrackViewport& Viewport; + + // Used by the point reduction algorithm. + double PointsCurrentX; + TArray PointsAtCurrentX; + + // Used by the line reduction algorithm. + float LinesCurrentX; + float LinesMinY; + float LinesMaxY; + float LinesFirstY; + float LinesLastY; + bool bIsLastLineAdded; + + // Used by the box reduction algorithm. + //... +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/LogFilter.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/LogFilter.cpp new file mode 100644 index 000000000000..5033b0a3799f --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/LogFilter.cpp @@ -0,0 +1,306 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "LogFilter.h" + +#include "Async/AsyncWork.h" + +// Insights +#include "Insights/TimingProfilerCommon.h" // for UE_LOG +#include "Insights/Widgets/SLogView.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * Expression context to test the given messages against the current text filter. + */ +class FLogFilter_TextFilterExpressionContext : public ITextFilterExpressionContext +{ +public: + explicit FLogFilter_TextFilterExpressionContext(const FLogMessageRecord& InMessage) : Message(&InMessage) {} + + /** Test the given value against the strings extracted from the current item */ + virtual bool TestBasicStringExpression(const FTextFilterString& InValue, const ETextFilterTextComparisonMode InTextComparisonMode) const override + { + return TextFilterUtils::TestBasicStringExpression(Message->Message.ToString(), InValue, InTextComparisonMode); + } + + /** + * Perform a complex expression test for the current item + * No complex expressions in this case - always returns false + */ + virtual bool TestComplexExpression(const FName& InKey, const FTextFilterString& InValue, const ETextFilterComparisonOperation InComparisonOperation, const ETextFilterTextComparisonMode InTextComparisonMode) const override + { + return false; + } + +private: + /** Message that is being filtered */ + const FLogMessageRecord* Message; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FLogFilter +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FLogFilter::FLogFilter() + : ChangeNumber(0) + , VerbosityThreshold(ELogVerbosity::All) + , bIsFilterSetByVerbosity(false) + , bIsFilterSetByCategory(false) + , bIsFilterSetByText(false) + , bShowAllCategories(true) + , AvailableLogCategories() + , EnabledLogCategories() + , TextFilterExpressionEvaluator(ETextFilterExpressionEvaluatorMode::BasicString) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FLogFilter::FLogFilter(const FLogFilter& Other) + : ChangeNumber(Other.ChangeNumber) + , VerbosityThreshold(Other.VerbosityThreshold) + , bIsFilterSetByVerbosity(Other.bIsFilterSetByVerbosity) + , bIsFilterSetByCategory(Other.bIsFilterSetByCategory) + , bIsFilterSetByText(Other.bIsFilterSetByText) + , bShowAllCategories(Other.bShowAllCategories) + //, AvailableLogCategories(Other.AvailableLogCategories) -- not needed in FLogFilter copies + , EnabledLogCategories(Other.EnabledLogCategories) + , TextFilterExpressionEvaluator(Other.TextFilterExpressionEvaluator) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FLogFilter& FLogFilter::operator=(const FLogFilter& Other) +{ + ChangeNumber = Other.ChangeNumber; + VerbosityThreshold = Other.VerbosityThreshold; + bIsFilterSetByVerbosity = Other.bIsFilterSetByVerbosity; + bIsFilterSetByCategory = Other.bIsFilterSetByCategory; + bIsFilterSetByText = Other.bIsFilterSetByText; + bShowAllCategories = Other.bShowAllCategories; + //AvailableLogCategories = Other.AvailableLogCategories; -- not needed in FLogFilter copies + EnabledLogCategories = Other.EnabledLogCategories; + TextFilterExpressionEvaluator = Other.TextFilterExpressionEvaluator; + return *this; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FLogFilter::Reset() +{ + ChangeNumber = 0; + VerbosityThreshold = ELogVerbosity::All; + bIsFilterSetByVerbosity = false; + bIsFilterSetByCategory = false; + bIsFilterSetByText = false; + bShowAllCategories = true; + AvailableLogCategories.Reset(); + EnabledLogCategories.Reset(); + TextFilterExpressionEvaluator.SetFilterText(FText::GetEmpty()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool FLogFilter::IsMessageAllowed(const FLogMessageRecord& Message) +{ + // Filter by Verbosity + if (bIsFilterSetByVerbosity) + { + if (Message.Verbosity > VerbosityThreshold) + { + return false; + } + } + + // Filter by Category + if (bIsFilterSetByCategory) + { + if (!EnabledLogCategories.Contains(FName(*Message.Category.ToString()))) + { + return false; + } + } + + // Filter by Message text + if (bIsFilterSetByText) + { + //FString Msg = Message.Message.ToString(); + //if (!Msg.Contains(FilterText, bIgnoreCase ? ESearchCase::IgnoreCase : ESearchCase::CaseSensitive)) + //{ + // return true; + //} + + if (!TextFilterExpressionEvaluator.TestTextFilter(FLogFilter_TextFilterExpressionContext(Message))) + { + return false; + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FLogFilter::SyncAvailableCategories(const TSet Categories) +{ + // Remove obsolete categories. + int32 OldNumAvailableLogCategories = AvailableLogCategories.Num(); + for (int32 Index = OldNumAvailableLogCategories - 1; Index >= 0; --Index) + { + if (!Categories.Contains(AvailableLogCategories[Index])) + { + EnabledLogCategories.Remove(AvailableLogCategories[Index]); + AvailableLogCategories.RemoveAt(Index); + } + } + if (AvailableLogCategories.Num() != OldNumAvailableLogCategories) + { + OnFilterByCategoryChanged(); + } + + // Add new categories (if any; duplicates will be ignored). + for (const FName& Category : Categories) + { + AddAvailableLogCategory(Category); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FLogFilter::AddAvailableLogCategory(const FName& LogCategory) +{ + // Use an insert-sort to keep AvailableLogCategories alphabetically sorted. + int32 InsertIndex = 0; + for (InsertIndex = AvailableLogCategories.Num() - 1; InsertIndex >= 0; --InsertIndex) + { + FName CheckCategory = AvailableLogCategories[InsertIndex]; + // No duplicates + if (CheckCategory == LogCategory) + { + return; + } + else if (CheckCategory.Compare(LogCategory) < 0) + { + break; + } + } + AvailableLogCategories.Insert(LogCategory, InsertIndex + 1); + + if (bShowAllCategories) + { + EnabledLogCategories.Add(LogCategory); + OnFilterByCategoryChanged(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FLogFilter::ToggleShowAllCategories() +{ + bShowAllCategories = !bShowAllCategories; + + EnabledLogCategories.Reset(); + + if (bShowAllCategories) + { + for (const auto& LogCategory : AvailableLogCategories) + { + EnabledLogCategories.Add(LogCategory); + } + } + + OnFilterByCategoryChanged(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FLogFilter::EnableLogCategory(const FName& LogCategory) +{ + bool bAlreadyEnabled = false; + EnabledLogCategories.Add(LogCategory, &bAlreadyEnabled); + if (!bAlreadyEnabled) + { + OnFilterByCategoryChanged(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FLogFilter::DisableLogCategory(const FName& LogCategory) +{ + if (EnabledLogCategories.Remove(LogCategory) > 0) + { + OnFilterByCategoryChanged(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FLogFilter::ToggleLogCategory(const FName& LogCategory) +{ + if (EnabledLogCategories.Remove(LogCategory) == 0) + { + EnabledLogCategories.Add(LogCategory); + } + OnFilterByCategoryChanged(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FLogFilter::EnableOnlyCategory(const FName& LogCategory) +{ + EnabledLogCategories.Reset(); + EnabledLogCategories.Add(LogCategory); + OnFilterByCategoryChanged(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool FLogFilter::IsLogCategoryEnabled(const FName& LogCategory) const +{ + return EnabledLogCategories.Contains(LogCategory); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FLogFilteringAsyncTask +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FLogFilteringAsyncTask::DoWork() +{ + bool bCanceled = false; + FilteredMessages.Reset(); + + if (Filter.IsFilterSetByText()) + { + UE_LOG(TimingProfiler, Log, TEXT("[LogView] FLogFilteringAsyncTask::DoWork [%d to %d] by Text (\"%s\")"), StartIndex, EndIndex, *Filter.GetFilterText().ToString()); + } + else + { + UE_LOG(TimingProfiler, Log, TEXT("[LogView] FLogFilteringAsyncTask::DoWork [%d to %d]"), StartIndex, EndIndex); + } + + for (int32 Index = StartIndex; Index < EndIndex && !bCanceled; ++Index) + { + TSharedPtr RecordPtr = LogView->GetCache().GetUncached(Index); + + if (RecordPtr.IsValid() && Filter.IsMessageAllowed(*RecordPtr)) + { + FilteredMessages.Add(Index); + } + + bCanceled = LogView->IsFilteringAsyncTaskCancelRequested(); + } + + if (bCanceled) + { + FilteredMessages.Reset(); + UE_LOG(TimingProfiler, Log, TEXT("[LogView] FLogFilteringAsyncTask::DoWork CANCELED")); + } + else + { + UE_LOG(TimingProfiler, Log, TEXT("[LogView] FLogFilteringAsyncTask::DoWork DONE (%d filtered messages)"), FilteredMessages.Num()); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/LogFilter.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/LogFilter.h new file mode 100644 index 000000000000..8dac252f0f37 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/LogFilter.h @@ -0,0 +1,199 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Async/AsyncWork.h" +#include "CoreMinimal.h" +#include "Logging/LogVerbosity.h" +#include "Misc/TextFilterExpressionEvaluator.h" +#include "Stats/Stats.h" + +// Insights +#include "Insights/ViewModels/LogMessage.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +template class FAsyncTask; +class SLogView; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Holds information about Log filters. + */ +class FLogFilter +{ +public: + FLogFilter(); + FLogFilter(const FLogFilter& Other); + FLogFilter& operator=(const FLogFilter& Other); + + void Reset(); + + uint64 GetChangeNumber() const { return ChangeNumber; } + + /** Returns true if any messages should be filtered out. */ + bool IsFilterSet() const { return bIsFilterSetByVerbosity || bIsFilterSetByCategory || bIsFilterSetByText; } + + /** + * Checks the given message against set filters. + * @return true if the log message passes filter test and false if log message is filtered out + */ + bool IsMessageAllowed(const FLogMessageRecord& LogMessage); + + ////////////////////////////////////////////////// + // Filtering by Verbosity + + ELogVerbosity::Type GetVerbosityThreshold() const { return VerbosityThreshold; } + void SetVerbosityThreshold(ELogVerbosity::Type InVerbosityThreshold) + { + VerbosityThreshold = InVerbosityThreshold; + bIsFilterSetByVerbosity = VerbosityThreshold != ELogVerbosity::All; + ++ChangeNumber; + } + +public: + bool IsFilterSetByVerbosity() const { return bIsFilterSetByVerbosity; } + + ////////////////////////////////////////////////// + // Available Log Categories + + const TArray& GetAvailableLogCategories() const { return AvailableLogCategories; } + + /** Sync list of available categories with a new one. */ + void SyncAvailableCategories(const TSet Categories); + + /** Adds a Log Category to the list of available categories, if it isn't already present. */ + void AddAvailableLogCategory(const FName& LogCategory); + + ////////////////////////////////////////////////// + // Filtering by Log Categories + + bool IsShowAllCategoriesEnabled() const { return bShowAllCategories; } + + /** Toggles the ShowAllCategories switch. */ + void ToggleShowAllCategories(); + + /** Enables a Log Category in the filter. */ + void EnableLogCategory(const FName& LogCategory); + + /** Disables a Log Category in the filter. */ + void DisableLogCategory(const FName& LogCategory); + + /** Enables or disables a Log Category in the filter. */ + void ToggleLogCategory(const FName& LogCategory); + + /** Enables only the specified Log Category in the filter (disables all other). */ + void EnableOnlyCategory(const FName& LogCategory); + + /** Returns true if the specified log category is enabled. */ + bool IsLogCategoryEnabled(const FName& LogCategory) const; + +private: + void OnFilterByCategoryChanged() + { + bIsFilterSetByCategory = (EnabledLogCategories.Num() < AvailableLogCategories.Num()); + ++ChangeNumber; + } + +public: + bool IsFilterSetByCategory() const { return bIsFilterSetByCategory; } + + ////////////////////////////////////////////////// + // Filtering by Message Text + + /** Set the Text to be used to filter by message text. */ + void SetFilterText(const FText& InFilterText) + { + //FilterText = InFilterText; + //bIsFilterSetByText = (FilterText.Len() > 0); + + TextFilterExpressionEvaluator.SetFilterText(InFilterText); + bIsFilterSetByText = TextFilterExpressionEvaluator.GetFilterType() != ETextFilterExpressionType::Empty || !TextFilterExpressionEvaluator.GetFilterText().IsEmpty(); + + ++ChangeNumber; + } + + /** Get the Text currently being used to filter by message text. */ + const FText GetFilterText() const { return TextFilterExpressionEvaluator.GetFilterText(); } + + /** Returns Evaluator syntax errors (if any). */ + FText GetSyntaxErrors() { return TextFilterExpressionEvaluator.GetFilterErrorText(); } + + bool IsFilterSetByText() const { return bIsFilterSetByText; } + + ////////////////////////////////////////////////// + +private: + /** A number incremented each time the filter changes. */ + uint64 ChangeNumber; + + ELogVerbosity::Type VerbosityThreshold; + + bool bIsFilterSetByVerbosity; + bool bIsFilterSetByCategory; + bool bIsFilterSetByText; + + /** true to allow all Log categories */ + bool bShowAllCategories; + + /** Array of Log Categories which are available for filter -- i.e. have been used in a log this session */ + TArray AvailableLogCategories; + + /** Array of Log Categories which are being used in the filter */ + TSet EnabledLogCategories; + + //FText FilterText; + //bool bIgnoreCase; + //bool bMatchWholeWord; + //bool bUseRegEx; + //bool bComplex; + + /** Expression evaluator that can be used to perform complex text filter queries */ + FTextFilterExpressionEvaluator TextFilterExpressionEvaluator; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FAsyncLogFilterProxy; + +class FLogFilteringAsyncTask : public FNonAbandonableTask +{ +public: + FLogFilteringAsyncTask(int32 InStartIndex, int32 InEndIndex, const FLogFilter& InFilter, TSharedPtr InLogView) + : StartIndex(InStartIndex) + , EndIndex(InEndIndex) + , Filter(InFilter) + , LogView(InLogView) + { + } + + void DoWork(); + + int32 GetStartIndex() const { return StartIndex; } + int32 GetEndIndex() const { return EndIndex; } + const FLogFilter& GetFilter() const { return Filter; } + + const TArray& GetFilteredMessages() const { return FilteredMessages; } + + FORCEINLINE TStatId GetStatId() const + { + RETURN_QUICK_DECLARE_CYCLE_STAT(FLogFilteringAsyncTask, STATGROUP_ThreadPoolAsyncTasks); + } + +private: + // [StartIndex, EndIndex) is the range of log messages to filter; accessible from worker thread. + int32 StartIndex; + int32 EndIndex; + + /** A copy of the filter settings. */ + FLogFilter Filter; + + /** Shared pointer to parent LogView widget. Used for accesing the cache and to check if cancel is requested. */ + TSharedPtr LogView; + + /** The output filtered messages. */ + TArray FilteredMessages; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/LogMessage.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/LogMessage.cpp new file mode 100644 index 000000000000..6488d2c07fab --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/LogMessage.cpp @@ -0,0 +1,188 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "LogMessage.h" + +#include "Misc/OutputDeviceHelper.h" +#include "Misc/ScopeLock.h" +#include "TraceServices/AnalysisService.h" +#include "TraceServices/SessionService.h" + +// Insights +#include "Insights/Common/TimeUtils.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FLogMessageRecord +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FLogMessageRecord::FLogMessageRecord() + : Index(0) + , Time(0) + , Verbosity(ELogVerbosity::Type::NoLogging) + , Category() + , Message() + , File() + , Line(0) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FLogMessageRecord::FLogMessageRecord(const Trace::FLogMessage& TraceLogMessage) + : Index(static_cast(TraceLogMessage.Index)) + , Time(TraceLogMessage.Time) + , Verbosity(TraceLogMessage.Verbosity) + //, Category(FText::FromString(TraceLogMessage.Category)) + //, Message(FText::FromString(TraceLogMessage.Message)) + , File(FText::FromString(TraceLogMessage.File)) + , Line(TraceLogMessage.Line) +{ + // Strip the "Log" prefix. + FString CategoryStr(TraceLogMessage.Category->Name); + if (CategoryStr.StartsWith(TEXT("Log"))) + { + CategoryStr = CategoryStr.RightChop(3); + } + Category = FText::FromString(CategoryStr); + + // Strip the trailing whitespaces (ex. some messages ends with "\n" and we do not want the LogView rows to have an unnecessary increased height). + FString MessageStr(TraceLogMessage.Message); + MessageStr.TrimEndInline(); + Message = FText::FromString(MessageStr); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText FLogMessageRecord::GetIndexAsText() const +{ + return FText::AsNumber(Index); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText FLogMessageRecord::GetTimeAsText() const +{ + return FText::FromString(TimeUtils::FormatTimeHMS(Time, TimeUtils::Microsecond)); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText FLogMessageRecord::GetVerbosityAsText() const +{ + return FText::FromString(FString(FOutputDeviceHelper::VerbosityToString(Verbosity))); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText FLogMessageRecord::GetCategoryAsText() const +{ + return Category; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText FLogMessageRecord::GetMessageAsText() const +{ + return Message; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText FLogMessageRecord::GetFileAsText() const +{ + return File; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText FLogMessageRecord::GetLineAsText() const +{ + return FText::AsNumber(Line); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText FLogMessageRecord::ToDisplayString() const +{ + FTextBuilder TextBuilder; + TextBuilder.AppendLine(GetTimeAsText()); + TextBuilder.AppendLineFormat(NSLOCTEXT("SLogView", "CategoryLine", "Category: {0}"), Category); + TextBuilder.AppendLineFormat(NSLOCTEXT("SLogView", "MessageLine", "Message: {0}"), Message); + return TextBuilder.ToText(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FLogMessageCache +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FLogMessageCache::FLogMessageCache() +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FLogMessageCache::Reset() +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FLogMessageRecord& FLogMessageCache::Get(uint64 Index) +{ + { + FScopeLock Lock(&CriticalSection); + if (Map.Contains(Index)) + { + return Map[Index]; + } + + // Just an arbitrary limit. Will purge the cache after this limit, to avoid using too much memory. + if (Map.Num() > 10000) + { + Map.Reset(); + } + } + + if (Session.IsValid()) + { + Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); + const Trace::ILogProvider& LogProvider = Trace::ReadLogProvider(*Session.Get()); + LogProvider.ReadMessage(Index, [this, Index](const Trace::FLogMessage& Message) + { + FScopeLock Lock(&CriticalSection); + FLogMessageRecord Entry(Message); + Map.Add(Index, Entry); + }); + } + + { + FScopeLock Lock(&CriticalSection); + if (Map.Contains(Index)) + { + return Map[Index]; + } + } + + return InvalidEntry; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedPtr FLogMessageCache::GetUncached(uint64 Index) const +{ + TSharedPtr EntryPtr; + + if (Session.IsValid()) + { + Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); + const Trace::ILogProvider& LogProvider = Trace::ReadLogProvider(*Session.Get()); + LogProvider.ReadMessage(Index, [&EntryPtr](const Trace::FLogMessage& Message) + { + EntryPtr = MakeShareable(new FLogMessageRecord(Message)); + }); + } + + return EntryPtr; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/LogMessage.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/LogMessage.h new file mode 100644 index 000000000000..468bb8e16df1 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/LogMessage.h @@ -0,0 +1,85 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace Trace +{ + struct FLogMessage; + class IAnalysisSession; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FLogMessageRecord +{ +public: + FLogMessageRecord(); + FLogMessageRecord(const Trace::FLogMessage& Message); + + FText GetIndexAsText() const; + FText GetTimeAsText() const; + FText GetVerbosityAsText() const; + FText GetCategoryAsText() const; + FText GetMessageAsText() const; + FText GetFileAsText() const; + FText GetLineAsText() const; + + FText ToDisplayString() const; + +public: + int32 Index; + double Time; + ELogVerbosity::Type Verbosity; + FText Category; + FText Message; + FText File; + uint32 Line; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FLogMessageCache +{ +public: + FLogMessageCache(); + + void SetSession(TSharedPtr InSession) { Session = InSession; } + void Reset(); + + FLogMessageRecord& Get(uint64 Index); + TSharedPtr GetUncached(uint64 Index) const; + +private: + FCriticalSection CriticalSection; + TSharedPtr Session; + TMap Map; + FLogMessageRecord InvalidEntry; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FLogMessageCategory +{ +public: + FName Name; + bool bIsVisible; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FLogMessage +{ +public: + FLogMessage(const int32 InIndex) : Index(InIndex) {} + + int32 GetIndex() const { return Index; } + +private: + int32 Index; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/MarkersTimingTrack.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/MarkersTimingTrack.cpp new file mode 100644 index 000000000000..7fdb319fe757 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/MarkersTimingTrack.cpp @@ -0,0 +1,526 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MarkersTimingTrack.h" + +#include "Brushes/SlateColorBrush.h" +#include "Fonts/FontMeasure.h" +#include "Framework/Application/SlateApplication.h" +#include "Styling/CoreStyle.h" +#include "TraceServices/AnalysisService.h" + +// Insights +#include "Insights/Common/PaintUtils.h" +#include "Insights/InsightsManager.h" +#include "Insights/TimingProfilerManager.h" +#include "Insights/ViewModels/TimingTrackViewport.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define LOCTEXT_NAMESPACE "MarkersTimingTrack" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FMarkersTimingTrack +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FMarkersTimingTrack::FMarkersTimingTrack(uint64 InTrackId) + : FBaseTimingTrack(InTrackId) + //, TimeMarkerBoxes() + //, TimeMarkerTexts() + , bIsCollapsed(false) + , bUseOnlyBookmarks(true) + , bIsDirty(true) + , TargetHoveredAnimPercent(0.0f) + , CurrentHoveredAnimPercent(0.0f) + , NumLogMessages(0) + , NumDrawBoxes(0) + , NumDrawTexts(0) + , WhiteBrush(FCoreStyle::Get().GetBrush("WhiteBrush")) + , Font(FCoreStyle::GetDefaultFontStyle("Regular", 8)) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FMarkersTimingTrack::~FMarkersTimingTrack() +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FMarkersTimingTrack::Reset() +{ + FBaseTimingTrack::Reset(); + + TimeMarkerBoxes.Reset(); + TimeMarkerTexts.Reset(); + + bIsCollapsed = false; + bUseOnlyBookmarks = true; + bIsDirty = true; + + TargetHoveredAnimPercent = 0.0f; + CurrentHoveredAnimPercent = 0.0f; + + NumLogMessages = 0; + NumDrawBoxes = 0; + NumDrawTexts = 0; + + UpdateHeight(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FMarkersTimingTrack::UpdateHeight() +{ + constexpr float BookmarksTrackHeight = 14.0f; + constexpr float TimeMarkersTrackHeight = 28.0f; + + if (bUseOnlyBookmarks) + { + SetHeight(BookmarksTrackHeight); + } + else + { + SetHeight(TimeMarkersTrackHeight); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FMarkersTimingTrack::UpdateHoveredState(float MouseX, float MouseY, const FTimingTrackViewport& Viewport) +{ + constexpr float HeaderWidth = 80.0f; + constexpr float HeaderHeight = 14.0f; + + if (MouseY >= GetPosY() && MouseY < GetPosY() + GetHeight()) + { + SetHoveredState(true); + SetHeaderHoveredState(MouseX < HeaderWidth && MouseY < GetPosY() + HeaderHeight); + TargetHoveredAnimPercent = 1.0f; + } + else + { + SetHoveredState(false); + TargetHoveredAnimPercent = 0.0f; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FMarkersTimingTrack::Tick(const double InCurrentTime, const float InDeltaTime) +{ + if (CurrentHoveredAnimPercent != TargetHoveredAnimPercent) + { + if (CurrentHoveredAnimPercent < TargetHoveredAnimPercent) + { + const float ShowAnimSpeed = 2 * InDeltaTime; + CurrentHoveredAnimPercent += ShowAnimSpeed; + if (CurrentHoveredAnimPercent > TargetHoveredAnimPercent) + { + CurrentHoveredAnimPercent = TargetHoveredAnimPercent; + } + } + else + { + const float HideAnimSpeed = 3 * InDeltaTime; + CurrentHoveredAnimPercent -= HideAnimSpeed; + if (CurrentHoveredAnimPercent < TargetHoveredAnimPercent) + { + CurrentHoveredAnimPercent = TargetHoveredAnimPercent; + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FMarkersTimingTrack::Update(const FTimingTrackViewport& InViewport) +{ + FTimeMarkerTrackBuilder Builder(*this, InViewport); + + TSharedPtr Session = FInsightsManager::Get()->GetSession(); + if (Session.IsValid()) + { + Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); + + const Trace::ILogProvider& LogProvider = Trace::ReadLogProvider(*Session.Get()); + Builder.BeginLog(LogProvider); + + LogProvider.EnumerateMessages( + Builder.GetViewport().StartTime, + Builder.GetViewport().EndTime, + [&Builder](const Trace::FLogMessage& Message) { Builder.AddLogMessage(Message); }); + + Builder.EndLog(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FMarkersTimingTrack::Draw(FDrawContext& DrawContext, const FTimingTrackViewport& Viewport) const +{ + // Draw background. + DrawContext.DrawBox(0.0f, GetPosY(), Viewport.Width, GetHeight(), WhiteBrush, FLinearColor(0.04f, 0.04f, 0.04f, 1.0f)); + DrawContext.LayerId++; + + // Draw the track's header, in background. + if (!IsHovered() || CurrentHoveredAnimPercent < 1.0) + { + DrawHeader(DrawContext, true); + } + + ////////////////////////////////////////////////// + // Draw vertical lines. + // Multiple adjacent vertical lines with same color are merged into a single box. + + float BoxY, BoxH; + if (IsCollapsed()) + { + BoxY = GetPosY(); + BoxH = GetHeight(); + } + else + { + BoxY = 0.0f; + BoxH = Viewport.Height; + } + + int32 NumBoxes = TimeMarkerBoxes.Num(); + for (int32 BoxIndex = 0; BoxIndex < NumBoxes; BoxIndex++) + { + const FTimeMarkerBoxInfo& Box = TimeMarkerBoxes[BoxIndex]; + DrawContext.DrawBox(Box.X, BoxY, Box.W, BoxH, WhiteBrush, Box.Color); + } + DrawContext.LayerId++; + NumDrawBoxes = NumBoxes; + + ////////////////////////////////////////////////// + // Draw texts (strings are already truncated). + + const float CategoryY = GetPosY() + 2.0f; + const float MessageY = GetPosY() + (IsBookmarksTrack() ? 1.0f : 14.0f); + + int32 NumTexts = TimeMarkerTexts.Num(); + for (int32 TextIndex = 0; TextIndex < NumTexts; TextIndex++) + { + const FTimeMarkerTextInfo& TextInfo = TimeMarkerTexts[TextIndex]; + + if (!IsBookmarksTrack() && TextInfo.Category.Len() > 0) + { + DrawContext.DrawText(TextInfo.X, CategoryY, TextInfo.Category, Font, TextInfo.Color); + NumDrawTexts++; + } + + if (TextInfo.Message.Len() > 0) + { + DrawContext.DrawText(TextInfo.X, MessageY, TextInfo.Message, Font, TextInfo.Color); + NumDrawTexts++; + } + } + DrawContext.LayerId++; + + ////////////////////////////////////////////////// + + // When hovered, the track's header is draw on top. + if (IsHovered() || CurrentHoveredAnimPercent > 0.0) + { + DrawHeader(DrawContext, false); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FMarkersTimingTrack::DrawHeader(FDrawContext& DrawContext, bool bFirstDraw) const +{ + const FString Name = IsBookmarksTrack() ? TEXT("Bookmarks") : TEXT("Logs"); + //const float ArrowSize = 6.0f; + const float ArrowSizeX = 4.0f; + const float ArrowSizeY = 8.0f; + const float ArrowX = IsBookmarksTrack() ? 64.0f : 31.0f; + const float ArrowY = GetPosY() + 3.0f; + const float HeaderW = ArrowX + ArrowSizeX + 4.0f; + const float HeaderH = 14.0f; + + if (!bFirstDraw) + { + DrawContext.DrawBox(0.0f, GetPosY(), HeaderW, HeaderH, WhiteBrush, FLinearColor(0.04f, 0.04f, 0.04f, CurrentHoveredAnimPercent)); + DrawContext.LayerId++; + } + + FLinearColor Color; + if (bFirstDraw || CurrentHoveredAnimPercent == 0.0) + { + Color = FLinearColor(0.07f, 0.07f, 0.07f, 1.0f); + } + else if (IsHeaderHovered()) + { + Color = FLinearColor(1.0f, 1.0f, 0.0f, CurrentHoveredAnimPercent); + } + else + { + Color = FLinearColor(1.0f, 1.0f, 1.0f, CurrentHoveredAnimPercent); + } + + // Draw "Bookmarks" or "Logs" text. + DrawContext.DrawText(2.0f, GetPosY() + 1.0f, Name, Font, Color); + + if (IsCollapsed()) + { + // Draw "right empty arrow". + TArray Points = + { + FVector2D(ArrowX, ArrowY), + FVector2D(ArrowX, ArrowY + ArrowSizeY), + FVector2D(ArrowX + ArrowSizeX, ArrowY + ArrowSizeY / 2.0f), + FVector2D(ArrowX, ArrowY) + }; + FSlateDrawElement::MakeLines(DrawContext.ElementList, DrawContext.LayerId, DrawContext.Geometry.ToPaintGeometry(), Points, DrawContext.DrawEffects, Color, false, 1.0f); + } + else + { + // Draw "down-right filled arrow". + for (float A = 1.0f; A < ArrowSizeY; A += 1.0f) + { + DrawContext.DrawBox(ArrowX - 3.0 + ArrowSizeY - A, ArrowY + A - 1.0f, A, 1.0f, WhiteBrush, Color); + } + } + + DrawContext.LayerId++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FTimeMarkerTrackBuilder +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FTimeMarkerTrackBuilder::FTimeMarkerTrackBuilder(FMarkersTimingTrack& InTrack, const FTimingTrackViewport& InViewport) + : Track(InTrack) + , Viewport(InViewport) + , FontMeasureService(FSlateApplication::Get().GetRenderer()->GetFontMeasureService()) + , Font(FCoreStyle::GetDefaultFontStyle("Regular", 8)) +{ + Track.ResetCache(); + Track.NumLogMessages = 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimeMarkerTrackBuilder::BeginLog(const Trace::ILogProvider& LogProvider) +{ + LogProviderPtr = &LogProvider; + + LastX1 = -1000.0f; + LastX2 = -1000.0f; + LastLogIndex = 0; + LastVerbosity = ELogVerbosity::NoLogging; + LastCategory = nullptr; + LastMessage = nullptr; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimeMarkerTrackBuilder::AddLogMessage(const Trace::FLogMessage& Message) +{ + Track.NumLogMessages++; + + // Add also the log message imediately on the left of the screen (if any). + if (Track.NumLogMessages == 1 && Message.Index > 0) + { + // Note: Reading message at Index-1 will not work as expected when using filter! + //TODO: Search API like: LogProviderPtr->SearchMessage(StartIndex, ESearchDirection::Backward, LambdaPredicate, bResolveFormatString); + LogProviderPtr->ReadMessage( + Message.Index - 1, + [this](const Trace::FLogMessage& Message) { AddLogMessage(Message); }); + } + + if (!Track.bUseOnlyBookmarks || FCString::Strcmp(Message.Category->Name, TEXT("LogBookmark")) == 0) + { + float X = Viewport.TimeToSlateUnitsRounded(Message.Time); + if (X < 0) + { + X = -1.0f; + } + AddTimeMarker(X, Message.Index, Message.Verbosity, Message.Category->Name, Message.Message); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FLinearColor FTimeMarkerTrackBuilder::GetColorByCategory(const TCHAR* const Category) +{ + // Strip the "Log" prefix. + FString CategoryStr(Category); + if (CategoryStr.StartsWith(TEXT("Log"))) + { + CategoryStr = CategoryStr.RightChop(3); + } + + uint32 Hash = 0; + for (const TCHAR* c = *CategoryStr; *c; ++c) + { + Hash = (Hash + *c) * 0x2c2c57ed; + } + + // Divided by 128.0 in order to force bright colors. + return FLinearColor(((Hash >> 16) & 0xFF) / 128.0f, ((Hash >> 8) & 0xFF) / 128.0f, (Hash & 0xFF) / 128.0f, 1.0f); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FLinearColor FTimeMarkerTrackBuilder::GetColorByVerbosity(const ELogVerbosity::Type Verbosity) +{ + static FLinearColor Colors[] = + { + FLinearColor(0.0, 0.0, 0.0, 1.0), // NoLogging + FLinearColor(1.0, 0.0, 0.0, 1.0), // Fatal + FLinearColor(1.0, 0.3, 0.0, 1.0), // Error + FLinearColor(0.7, 0.5, 0.0, 1.0), // Warning + FLinearColor(0.0, 0.7, 0.0, 1.0), // Display + FLinearColor(0.0, 0.7, 1.0, 1.0), // Log + FLinearColor(0.7, 0.7, 0.7, 1.0), // Verbose + FLinearColor(1.0, 1.0, 1.0, 1.0), // VeryVerbose + }; + static_assert(sizeof(Colors) / sizeof(FLinearColor) == (int)ELogVerbosity::Type::All + 1, "ELogVerbosity::Type has changed!?"); + //return Colors[Verbosity & ELogVerbosity::VerbosityMask]; + return Colors[Verbosity & 7]; // using 7 instead of ELogVerbosity::VerbosityMask (15) to make static analyzer happy +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimeMarkerTrackBuilder::Flush(float AvailableTextW) +{ + // Is last marker valid? + if (LastCategory != nullptr) + { + const FLinearColor Color = GetColorByCategory(LastCategory); + + bool bAddNewBox = true; + if (Track.TimeMarkerBoxes.Num() > 0) + { + FTimeMarkerBoxInfo& PrevBox = Track.TimeMarkerBoxes[Track.TimeMarkerBoxes.Num() - 1]; + if (PrevBox.X + PrevBox.W == LastX1 && + PrevBox.Color.R == Color.R && + PrevBox.Color.G == Color.G && + PrevBox.Color.B == Color.B) + { + // Extend previous box instead. + PrevBox.W += LastX2 - LastX1; + bAddNewBox = false; + } + } + + if (bAddNewBox) + { + // Add new Box info to cache. + FTimeMarkerBoxInfo& Box = Track.TimeMarkerBoxes[Track.TimeMarkerBoxes.AddDefaulted()]; + Box.X = LastX1; + Box.W = LastX2 - LastX1; + Box.Color = Color; + Box.Color.A = 0.25f; + } + + if (AvailableTextW > 6.0f) + { + // Strip the "Log" prefix. + FString CategoryStr(LastCategory); + if (CategoryStr.StartsWith(TEXT("Log"))) + { + CategoryStr = CategoryStr.RightChop(3); + } + + const int32 LastWholeCharacterIndexCategory = FontMeasureService->FindLastWholeCharacterIndexBeforeOffset(CategoryStr, Font, FMath::RoundToInt(AvailableTextW - 2.0f)); + const int32 LastWholeCharacterIndexMessage = FontMeasureService->FindLastWholeCharacterIndexBeforeOffset(LastMessage, Font, FMath::RoundToInt(AvailableTextW - 2.0f)); + + if (LastWholeCharacterIndexCategory >= 0 || + LastWholeCharacterIndexMessage >= 0) + { + // Add new Text info to cache. + FTimeMarkerTextInfo& TextInfo = Track.TimeMarkerTexts[Track.TimeMarkerTexts.AddDefaulted()]; + TextInfo.X = LastX2 + 2.0f; + TextInfo.Color = Color; + if (LastWholeCharacterIndexCategory >= 0) + { + TextInfo.Category = CategoryStr.Left(LastWholeCharacterIndexCategory + 1); + } + if (LastWholeCharacterIndexMessage >= 0) + { + TextInfo.Message.AppendChars(LastMessage, LastWholeCharacterIndexMessage + 1); + } + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimeMarkerTrackBuilder::AddTimeMarker(const float X, const uint64 LogIndex, const ELogVerbosity::Type Verbosity, const TCHAR* const Category, const TCHAR* Message) +{ + float W = X - LastX2; + + if (W > 0.0f) // There is at least 1px from previous box? + { + // Flush previous marker (if any). + Flush(W); + + // Begin new marker info. + LastX1 = X; + LastX2 = X + 1.0f; + } + else if (W == 0.0f) // Adjacent to previous box? + { + // Same color as previous marker? + if (Category == LastCategory) + { + // Extend previous box. + LastX2++; + } + else + { + // Flush previous marker (if any). + Flush(0.0f); + + // Begin new box. + LastX1 = X; + LastX2 = X + 1.0f; + } + } + else // Overlaps previous box? + { + // Same color as previous marker? + if (Category == LastCategory) + { + // Keep previous box. + } + else + { + // Shrink previous box. + LastX2--; + + if (LastX2 > LastX1) + { + // Flush previous marker (if any). + Flush(0.0f); + } + + // Begin new box. + LastX1 = X; + LastX2 = X + 1.0f; + } + } + + // Save marker. + LastCategory = Category; + LastVerbosity = Verbosity; + LastLogIndex = LogIndex; + LastMessage = Message; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimeMarkerTrackBuilder::EndLog() +{ + Flush(Viewport.Width - LastX2); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/MarkersTimingTrack.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/MarkersTimingTrack.h new file mode 100644 index 000000000000..0c6aac86f98b --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/MarkersTimingTrack.h @@ -0,0 +1,152 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Fonts/SlateFontInfo.h" + +// Insights +#include "Insights/ViewModels/BaseTimingTrack.h" + +namespace Trace +{ + struct FLogMessage; + class ILogProvider; +} + +struct FDrawContext; +struct FSlateBrush; +class FTimingTrackViewport; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct FTimeMarkerBoxInfo +{ + float X; + float W; + FLinearColor Color; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct FTimeMarkerTextInfo +{ + float X; + FLinearColor Color; + FString Category; // truncated Category string + FString Message; // truncated Message string +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FMarkersTimingTrack : public FBaseTimingTrack +{ + friend class FTimeMarkerTrackBuilder; + +public: + FMarkersTimingTrack(uint64 InTrackId); + virtual ~FMarkersTimingTrack(); + + virtual void Reset() override; + + bool IsCollapsed() const { return bIsCollapsed; } + void ToggleCollapsed() { bIsCollapsed = !bIsCollapsed; } + + bool IsBookmarksTrack() const { return bUseOnlyBookmarks; } + void SetBookmarksTrackFlag(bool bInUseOnlyBookmarks) + { + bUseOnlyBookmarks = bInUseOnlyBookmarks; + UpdateHeight(); + } + + bool IsDirty() const { return bIsDirty; } + void SetDirtyFlag() { bIsDirty = true; } + void ClearDirtyFlag() { bIsDirty = false; } + + // Stats + int32 GetNumLogMessages() const { return NumLogMessages; } + int32 GetNumBoxes() const { return TimeMarkerBoxes.Num(); } + int32 GetNumTexts() const { return TimeMarkerTexts.Num(); } + + virtual void UpdateHoveredState(float MouseX, float MouseY, const FTimingTrackViewport& Viewport) override; + virtual void Tick(const double CurrentTime, const float DeltaTime) override; + virtual void Update(const FTimingTrackViewport& Viewport) override; + + void Draw(FDrawContext& DrawContext, const FTimingTrackViewport& Viewport) const; + +private: + void ResetCache() + { + TimeMarkerBoxes.Reset(); + TimeMarkerTexts.Reset(); + } + + void UpdateHeight(); + + void DrawHeader(FDrawContext& DrawContext, bool bFirstDraw) const; + +private: + TArray TimeMarkerBoxes; + TArray TimeMarkerTexts; + + bool bIsCollapsed; // If false, the vertical lines will extend to entire viewport height; otherwise will be limited to this track's height. + bool bUseOnlyBookmarks; // If true, uses only bookmarks; otherwise it uses all log messages. + bool bIsDirty; // Source list of bookmarks/markers has changed. Cached Boxes and Texts are dirty. + + float TargetHoveredAnimPercent; // [0.0 .. 1.0], 0.0 = hidden, 1.0 = visible + float CurrentHoveredAnimPercent; + + // Stats + int32 NumLogMessages; + mutable int32 NumDrawBoxes; + mutable int32 NumDrawTexts; + + // Slate resources + const FSlateBrush* WhiteBrush; + const FSlateFontInfo Font; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FTimeMarkerTrackBuilder +{ +public: + FTimeMarkerTrackBuilder(FMarkersTimingTrack& InTrack, const FTimingTrackViewport& InViewport); + + /** + * Non-copyable + */ + FTimeMarkerTrackBuilder(const FTimeMarkerTrackBuilder&) = delete; + FTimeMarkerTrackBuilder& operator=(const FTimeMarkerTrackBuilder&) = delete; + + const FTimingTrackViewport& GetViewport() { return Viewport; } + + void BeginLog(const Trace::ILogProvider& LogProvider); + void AddLogMessage(const Trace::FLogMessage& Message); + void EndLog(); + + static FLinearColor GetColorByCategory(const TCHAR* const Category); + static FLinearColor GetColorByVerbosity(const ELogVerbosity::Type Verbosity); + +private: + void Flush(float AvailableTextW); + void AddTimeMarker(const float X, const uint64 LogIndex, const ELogVerbosity::Type Verbosity, const TCHAR* const Category, const TCHAR* Message); + +private: + FMarkersTimingTrack& Track; + const FTimingTrackViewport& Viewport; + + const TSharedRef FontMeasureService; + const FSlateFontInfo Font; + + const Trace::ILogProvider* LogProviderPtr; // valid only between BeginLog() and EndLog() + + float LastX1; + float LastX2; + uint64 LastLogIndex; + ELogVerbosity::Type LastVerbosity; + const TCHAR* LastCategory; + const TCHAR* LastMessage; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsNode.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsNode.cpp new file mode 100644 index 000000000000..629c57df97b2 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsNode.cpp @@ -0,0 +1,191 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "StatsNode.h" + +// Insights +#include "Insights/Common/TimeUtils.h" + +#define LOCTEXT_NAMESPACE "StatsNode" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FStatsNode::ResetAggregatedStats() +{ + AggregatedStats.Reset(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FStatsNode::SetAggregatedStats(FAggregatedStats& InAggregatedStats) +{ + AggregatedStats = InAggregatedStats; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FStatsNode::ResetAggregatedIntegerStats() +{ + AggregatedIntegerStats.Reset(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FStatsNode::SetAggregatedIntegerStats(FAggregatedIntegerStats& InAggregatedIntegerStats) +{ + AggregatedIntegerStats = InAggregatedIntegerStats; + + // Sorting and display of Count value uses the "float" stats. + AggregatedStats.Count = AggregatedIntegerStats.Count; + + AggregatedStats.Sum = static_cast(AggregatedIntegerStats.Sum); + AggregatedStats.Min = static_cast(AggregatedIntegerStats.Min); + AggregatedStats.Max = static_cast(AggregatedIntegerStats.Max); + AggregatedStats.Average = static_cast(AggregatedIntegerStats.Average); + AggregatedStats.Median = static_cast(AggregatedIntegerStats.Median); + AggregatedStats.LowerQuartile = static_cast(AggregatedIntegerStats.LowerQuartile); + AggregatedStats.UpperQuartile = static_cast(AggregatedIntegerStats.UpperQuartile); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FText FStatsNode::FormatAggregatedStatsValue(double ValueDbl, int64 ValueInt) const +{ + if (GetType() == EStatsNodeType::Float) + { + if (AggregatedStats.Count == 0) + { + return LOCTEXT("AggregatedStatsNA", "N/A"); + } + else + { + //TODO: if (GetDisplayHint() == FName(TEXT("Seconds"))) + if (GetMetaGroupName() == FName(TEXT("Time"))) + { + return FText::FromString(TimeUtils::FormatTimeAuto(ValueDbl)); + } + else + { + return FText::AsNumber(ValueDbl); + } + } + } + else + { + if (AggregatedIntegerStats.Count == 0) + { + return LOCTEXT("AggregatedStatsNA", "N/A"); + } + else + { + //TODO: if (GetDisplayHint() == FName(TEXT("Bytes"))) + if (GetMetaGroupName() == FName(TEXT("Memory"))) + { + if (ValueInt > 0) + { + return FText::AsMemory(ValueInt); + } + else if (ValueInt == 0) + { + return FText::FromString(TEXT("0")); + } + else + { + return FText::FromString(FString::Printf(TEXT("-%s"), *FText::AsMemory(-ValueInt).ToString())); + } + } + else + { + return FText::AsNumber(ValueInt); + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FText FStatsNode::GetTextForAggregatedStatsSum() const +{ + return FormatAggregatedStatsValue(AggregatedStats.Sum, AggregatedIntegerStats.Sum); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FText FStatsNode::GetTextForAggregatedStatsMin() const +{ + return FormatAggregatedStatsValue(AggregatedStats.Min, AggregatedIntegerStats.Min); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FText FStatsNode::GetTextForAggregatedStatsMax() const +{ + return FormatAggregatedStatsValue(AggregatedStats.Max, AggregatedIntegerStats.Max); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FText FStatsNode::GetTextForAggregatedStatsAverage() const +{ + return FormatAggregatedStatsValue(AggregatedStats.Average, AggregatedIntegerStats.Average); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FText FStatsNode::GetTextForAggregatedStatsMedian() const +{ + return FormatAggregatedStatsValue(AggregatedStats.Median, AggregatedIntegerStats.Median); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FText FStatsNode::GetTextForAggregatedStatsLowerQuartile() const +{ + return FormatAggregatedStatsValue(AggregatedStats.LowerQuartile, AggregatedIntegerStats.LowerQuartile); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FText FStatsNode::GetTextForAggregatedStatsUpperQuartile() const +{ + return FormatAggregatedStatsValue(AggregatedStats.UpperQuartile, AggregatedIntegerStats.UpperQuartile); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FText FStatsNode::GetNameEx() const +{ + FText Text = FText::GetEmpty(); + + if (IsGroup()) + { + const int32 NumChildren = Children.Num(); + const int32 NumFilteredChildren = FilteredChildren.Num(); + + if (NumFilteredChildren == NumChildren) + { + Text = FText::Format(LOCTEXT("StatsNodeGroupTextFmt1", "{0} ({1})"), FText::FromName(Name), FText::AsNumber(NumChildren)); + } + else + { + Text = FText::Format(LOCTEXT("StatsNodeGroupTextFmt2", "{0} ({1} / {2})"), FText::FromName(Name), FText::AsNumber(NumFilteredChildren), FText::AsNumber(NumChildren)); + } + } + else + { + //const bool bIsStatTracked = FProfilerManager::Get()->IsStatTracked(GroupOrStatNode->GetStatID()); + //if (bIsStatTracked) + //{ + // Text = FText::Format(LOCTEXT("StatsNodeTrackedTextFmt", "{0}*"), FText::FromName(Name)); + //} + //else + { + Text = FText::FromName(Name); + } + } + + return Text; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsNode.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsNode.h new file mode 100644 index 000000000000..e1deb05a0639 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsNode.h @@ -0,0 +1,292 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +class FStatsNode; + +/** Type definition for shared pointers to instances of FStatsNode. */ +typedef TSharedPtr FStatsNodePtr; + +/** Type definition for shared references to instances of FStatsNode. */ +typedef TSharedRef FStatsNodeRef; + +/** Type definition for shared references to const instances of FStatsNode. */ +typedef TSharedRef FStatsNodeRefConst; + +/** Type definition for weak references to instances of FStatsNode. */ +typedef TWeakPtr FStatsNodeWeak; + +enum class EStatsNodeType +{ + /** The StatsNode is a floating number stats. */ + Float, + + /** The StatsNode is an integer number stats. */ + Int64, + + /** The StatsNode is a group node. */ + Group, + + /** Invalid enum type, may be used as a number of enumerations. */ + InvalidOrMax, +}; + +template +struct TAggregatedStats +{ + uint64 Count; /** Number of values. */ + + Type Sum; /** Sum of all values. */ + Type Min; /** Min value. */ + Type Max; /** Max value. */ + Type Average; /** Average value. */ + Type Median; /** Median value. */ + Type LowerQuartile; /** Lower Quartile value. */ + Type UpperQuartile; /** Upper Quartile value. */ + + TAggregatedStats() + : Count(0) + , Sum(0) + , Min(0) + , Max(0) + , Average(0) + , Median(0) + , LowerQuartile(0) + , UpperQuartile(0) + { + } + + void Reset() + { + Count = 0; + Sum = 0; + Min = 0; + Max = 0; + Average = 0; + Median = 0; + LowerQuartile = 0; + UpperQuartile = 0; + } +}; + +typedef TAggregatedStats FAggregatedStats; +typedef TAggregatedStats FAggregatedIntegerStats; + +/** + * Class used to store information about a timer node (used in timers' tree view). + */ +class FStatsNode : public TSharedFromThis +{ +public: + static const uint64 InvalidId = -1; + +public: + /** Initialization constructor for the timer node. */ + FStatsNode(uint64 InId, const FName InName, const FName InMetaGroupName, EStatsNodeType InType) + : Id(InId) + , Name(InName) + , MetaGroupName(InMetaGroupName) + , Type(InType) + , bForceExpandGroupNode(false) + { + ResetAggregatedStats(); + } + + /** Initialization constructor for the group node. */ + FStatsNode(const FName InGroupName) + : Id(0) + , Name(InGroupName) + , Type(EStatsNodeType::Group) + , bForceExpandGroupNode(false) + { + ResetAggregatedStats(); + } + + /** + * @return an Id of this timer, valid only for timer nodes. + */ + const uint64 GetId() const + { + return Id; + } + + /** + * @return a name of this node, group or timer. + */ + const FName& GetName() const + { + return Name; + } + + /** + * @return a name of this node, group or timer + addditional info, to display in Stats tree view. + */ + const FText GetNameEx() const; + + /** + * @return a name of the group that this timer node belongs to, taken from the metadata. + */ + const FName& GetMetaGroupName() const + { + return MetaGroupName; + } + + /** + * @return a type of this timer or EStatsNodeType::Group for group nodes. + */ + const EStatsNodeType& GetType() const + { + return Type; + } + + /** + * @return true, if this node is a group node. + */ + bool IsGroup() const + { + return Type == EStatsNodeType::Group; + } + + /** + * @return the aggregated stats of this counter (if counter is a "float number" type). + */ + const FAggregatedStats& GetAggregatedStats() const + { + return AggregatedStats; + } + + void ResetAggregatedStats(); + + void SetAggregatedStats(FAggregatedStats& AggregatedStats); + + /** + * @return the aggregated stats of this counter (if counter is an "integer number" type). + */ + const FAggregatedIntegerStats& GetAggregatedIntegerStats() const + { + return AggregatedIntegerStats; + } + + void ResetAggregatedIntegerStats(); + + void SetAggregatedIntegerStats(FAggregatedIntegerStats& AggregatedIntegerStats); + + const FText FormatValue(double Value) const; + const FText FormatValue(int64 Value) const; + + const FText FormatAggregatedStatsValue(double ValueDbl, int64 ValueInt) const; + + const FText GetTextForAggregatedStatsSum() const; + const FText GetTextForAggregatedStatsMin() const; + const FText GetTextForAggregatedStatsMax() const; + const FText GetTextForAggregatedStatsAverage() const; + const FText GetTextForAggregatedStatsMedian() const; + const FText GetTextForAggregatedStatsLowerQuartile() const; + const FText GetTextForAggregatedStatsUpperQuartile() const; + + /** + * @return a const reference to the child nodes of this group. + */ + FORCEINLINE_DEBUGGABLE const TArray& GetChildren() const + { + return Children; + } + + /** + * @return a const reference to the child nodes that should be visible to the UI based on filtering. + */ + FORCEINLINE_DEBUGGABLE const TArray& GetFilteredChildren() const + { + return FilteredChildren; + } + + /** + * @return a weak reference to the group of this timer node, may be invalid. + */ + FStatsNodeWeak GetGroupPtr() const + { + return GroupPtr; + } + + /** + * @return a name of the fake group that this timer node belongs to. + */ + const FName& GetGroupName() const + { + return GroupPtr.Pin()->Name; + } + + /** + * @return a name of the fake group that this timer node belongs to. + */ + //const FName& GetGroupNameSafe() const + //{ + // return GroupPtr.IsValid() ? GroupPtr.Pin()->Name : NAME_None; + //} + + bool IsFiltered() const + { + return false; // TODO + } + +public: + /** Sorts children using the specified class instance. */ + template + void SortChildren(const TSortingClass& Instance) + { + Children.Sort(Instance); + } + + /** Adds specified child to the children and sets group for it. */ + FORCEINLINE_DEBUGGABLE void AddChildAndSetGroupPtr(const FStatsNodePtr& ChildPtr) + { + ChildPtr->GroupPtr = AsShared(); + Children.Add(ChildPtr); + } + + /** Adds specified child to the filtered children. */ + FORCEINLINE_DEBUGGABLE void AddFilteredChild(const FStatsNodePtr& ChildPtr) + { + FilteredChildren.Add(ChildPtr); + } + + /** Clears filtered children. */ + void ClearFilteredChildren() + { + FilteredChildren.Reset(); + } + +protected: + /** The Id of this timer or group. */ + const uint64 Id; + + /** The name of this timer or group. */ + const FName Name; + + /** The name of the group that this timer belongs to, based on the timer's metadata; only valid for timer nodes. */ + const FName MetaGroupName; + + /** Holds the type of this timer; for the group, this is Group. */ + const EStatsNodeType Type; + + /** Aggregated stats (double). */ + FAggregatedStats AggregatedStats; + + /** Aggregated stats (int64). */ + FAggregatedIntegerStats AggregatedIntegerStats; + + /** Children of this node. */ + TArray Children; + + /** Filtered children of this node. */ + TArray FilteredChildren; + + /** A weak pointer to the group/parent of this node. */ + FStatsNodeWeak GroupPtr; + +public: + /** Whether this group node should be expanded when the text filtering is enabled. */ + bool bForceExpandGroupNode; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsNodeHelper.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsNodeHelper.cpp new file mode 100644 index 000000000000..b0618fd277cb --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsNodeHelper.cpp @@ -0,0 +1,123 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "StatsNodeHelper.h" +#include "EditorStyleSet.h" + +#define LOCTEXT_NAMESPACE "StatsNode" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// StatsNode Type Helper +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText StatsNodeTypeHelper::ToName(const EStatsNodeType NodeType) +{ + static_assert(static_cast(EStatsNodeType::InvalidOrMax) == 3, "Not all cases are handled in switch below!?"); + switch (NodeType) + { + case EStatsNodeType::Float: return LOCTEXT("Stats_Name_Float", "Float"); + case EStatsNodeType::Int64: return LOCTEXT("Stats_Name_Int64", "Integer"); + case EStatsNodeType::Group: return LOCTEXT("Stats_Name_Group", "Group"); + default: return LOCTEXT("InvalidOrMax", "InvalidOrMax"); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText StatsNodeTypeHelper::ToDescription(const EStatsNodeType NodeType) +{ + static_assert(static_cast(EStatsNodeType::InvalidOrMax) == 3, "Not all cases are handled in switch below!?"); + switch (NodeType) + { + case EStatsNodeType::Float: return LOCTEXT("Stats_Desc_Float", "Float number stats"); + case EStatsNodeType::Int64: return LOCTEXT("Stats_Desc_Int64", "Integer number stats"); + case EStatsNodeType::Group: return LOCTEXT("Stats_Desc_Group", "Group stats node"); + default: return LOCTEXT("InvalidOrMax", "InvalidOrMax"); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FName StatsNodeTypeHelper::ToBrushName(const EStatsNodeType NodeType) +{ + static_assert(static_cast(EStatsNodeType::InvalidOrMax) == 3, "Not all cases are handled in switch below!?"); + switch (NodeType) + { + case EStatsNodeType::Float: return TEXT("Profiler.FiltersAndPresets.StatTypeIcon"); //TODO: "Icons.StatsType.Float" + case EStatsNodeType::Int64: return TEXT("Profiler.FiltersAndPresets.StatTypeIcon"); //TODO: "Icons.StatsType.Int64" + case EStatsNodeType::Group: return TEXT("Profiler.Misc.GenericGroup"); //TODO: "Icons.GenericGroup" + default: return NAME_None; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FSlateBrush* StatsNodeTypeHelper::GetIconForGroup() +{ + return FEditorStyle::GetBrush(TEXT("Profiler.Misc.GenericGroup")); //TODO: FInsightsStyle::GetBrush(TEXT("Icons.GenericGroup")); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FSlateBrush* StatsNodeTypeHelper::GetIconForStatsNodeType(const EStatsNodeType NodeType) +{ + static_assert(static_cast(EStatsNodeType::InvalidOrMax) == 3, "Not all cases are handled in switch below!?"); + switch (NodeType) + { + case EStatsNodeType::Float: return FEditorStyle::GetBrush(TEXT("Profiler.Type.NumberFloat")); //TODO: FInsightsStyle::GetBrush(TEXT("Icons.StatsType.Float")); + case EStatsNodeType::Int64: return FEditorStyle::GetBrush(TEXT("Profiler.Type.NumberInt")); //TODO: FInsightsStyle::GetBrush(TEXT("Icons.StatsType.Int64")); + case EStatsNodeType::Group: return FEditorStyle::GetBrush(TEXT("Profiler.Misc.GenericGroup")); //TODO: FInsightsStyle::GetBrush(TEXT("Icons.GenericGroup")); + default: return nullptr; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// StatsNode Grouping Helper +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText StatsNodeGroupingHelper::ToName(const EStatsGroupingMode GroupingMode) +{ + static_assert(static_cast(EStatsGroupingMode::InvalidOrMax) == 4, "Not all cases are handled in switch below!?"); + switch (GroupingMode) + { + case EStatsGroupingMode::Flat: return LOCTEXT("Grouping_Name_Flat", "Flat"); + case EStatsGroupingMode::ByName: return LOCTEXT("Grouping_Name_ByName", "Stats Name"); + case EStatsGroupingMode::ByMetaGroupName: return LOCTEXT("Grouping_Name_MetaGroupName", "Meta Group Name"); + case EStatsGroupingMode::ByType: return LOCTEXT("Grouping_Name_Type", "Stats Type"); + default: return LOCTEXT("InvalidOrMax", "InvalidOrMax"); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText StatsNodeGroupingHelper::ToDescription(const EStatsGroupingMode GroupingMode) +{ + static_assert(static_cast(EStatsGroupingMode::InvalidOrMax) == 4, "Not all cases are handled in switch below!?"); + switch (GroupingMode) + { + case EStatsGroupingMode::Flat: return LOCTEXT("Grouping_Desc_Flat", "Creates a single group. Includes all stats."); + case EStatsGroupingMode::ByName: return LOCTEXT("Grouping_Desc_ByName", "Creates one group for one letter."); + case EStatsGroupingMode::ByMetaGroupName: return LOCTEXT("Grouping_Desc_MetaGroupName", "Creates groups based on metadata group names of stats."); + case EStatsGroupingMode::ByType: return LOCTEXT("Grouping_Desc_Type", "Creates one group for each stats type."); + default: return LOCTEXT("InvalidOrMax", "InvalidOrMax"); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FName StatsNodeGroupingHelper::ToBrushName(const EStatsGroupingMode GroupingMode) +{ + static_assert(static_cast(EStatsGroupingMode::InvalidOrMax) == 4, "Not all cases are handled in switch below!?"); + switch (GroupingMode) + { + //TODO: "UnrealProfiler.*Icon" + case EStatsGroupingMode::Flat: return TEXT("Profiler.FiltersAndPresets.GroupNameIcon"); //TODO: "Icons.Grouping.Flat" + case EStatsGroupingMode::ByName: return TEXT("Profiler.FiltersAndPresets.Group1NameIcon"); //TODO: "Icons.Grouping.ByName" + case EStatsGroupingMode::ByMetaGroupName: return TEXT("Profiler.FiltersAndPresets.StatNameIcon"); //TODO + case EStatsGroupingMode::ByType: return TEXT("Profiler.FiltersAndPresets.StatTypeIcon"); //TODO + default: return NAME_None; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsNodeHelper.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsNodeHelper.h new file mode 100644 index 000000000000..84049e1af0b2 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsNodeHelper.h @@ -0,0 +1,259 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Styling/SlateBrush.h" + +// Insights +#include "Insights/ViewModels/StatsNode.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** Enumerates types of grouping or sorting for the stats nodes. */ +enum class EStatsGroupingMode +{ + /** Creates a single group for all timers. */ + Flat, + + /** Creates one group for one letter. */ + ByName, + + /** Creates groups based on stats metadata group names. */ + ByMetaGroupName, + + /** Creates one group for each stats type. */ + ByType, + + /** Invalid enum type, may be used as a number of enumerations. */ + InvalidOrMax, +}; + +/** Type definition for shared pointers to instances of EStatsGroupingMode. */ +typedef TSharedPtr EStatsGroupingModePtr; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** Helper struct that contains helper functions and classes for EStatsNodeType enum. */ +struct StatsNodeTypeHelper +{ + /** + * @param Type - The value to get the text for. + * + * @return text representation of the specified EStatsNodeType value. + */ + static FText ToName(const EStatsNodeType Type); + + /** + * @param Type - The value to get the text for. + * + * @return text representation with more detailed explanation of the specified EStatsNodeType value. + */ + static FText ToDescription(const EStatsNodeType Type); + + /** + * @param Type - The value to get the brush name for. + * + * @return brush name of the specified EStatsNodeType value. + */ + static FName ToBrushName(const EStatsNodeType Type); + + static const FSlateBrush* GetIconForGroup(); + static const FSlateBrush* GetIconForStatsNodeType(const EStatsNodeType Type); +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** Helper struct that contains grouping static functions and classes. */ +struct StatsNodeGroupingHelper +{ + /** + * @param StatsGroupingMode - The value to get the text for. + * + * @return text representation of the specified EStatsGroupingMode value. + */ + static FText ToName(const EStatsGroupingMode StatsGroupingMode); + + /** + * @param StatsGroupingMode - The value to get the text for. + * + * @return text representation with more detailed explanation of the specified EStatsGroupingMode value. + */ + static FText ToDescription(const EStatsGroupingMode StatsGroupingMode); + + /** + * @param StatsGroupingMode - The value to get the brush name for. + * + * @return brush name of the specified EStatsGroupingMode value. + */ + static FName ToBrushName(const EStatsGroupingMode StatsGroupingMode); +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** Helper struct that contains sorting static functions and classes. */ +struct StatsNodeSortingHelper +{ + ////////////////////////////////////////////////// + // Sorting by stats name. + + struct ByNameAscending + { + FORCEINLINE_DEBUGGABLE bool operator()(const FStatsNodePtr& A, const FStatsNodePtr& B) const + { + return A->GetName().LexicalLess(B->GetName()); + } + }; + + struct ByNameDescending + { + FORCEINLINE_DEBUGGABLE bool operator()(const FStatsNodePtr& A, const FStatsNodePtr& B) const + { + return B->GetName().LexicalLess(A->GetName()); + } + }; + + ////////////////////////////////////////////////// + // Sorting by stats meta group name. + // If meta group names are the same then sort by name. + + struct ByMetaGroupNameAscending + { + FORCEINLINE_DEBUGGABLE bool operator()(const FStatsNodePtr& A, const FStatsNodePtr& B) const + { + if (A->GetMetaGroupName() == B->GetMetaGroupName()) + { + // Sort by stats name (ascending). + return A->GetName().LexicalLess(B->GetName()); + } + else + { + return A->GetMetaGroupName().LexicalLess(B->GetMetaGroupName()); + } + } + }; + + struct ByMetaGroupNameDescending + { + FORCEINLINE_DEBUGGABLE bool operator()(const FStatsNodePtr& A, const FStatsNodePtr& B) const + { + if (A->GetMetaGroupName() == B->GetMetaGroupName()) + { + // Sort by stats name (ascending). + return A->GetName().LexicalLess(B->GetName()); + } + else + { + return B->GetMetaGroupName().LexicalLess(A->GetMetaGroupName()); + } + } + }; + + ////////////////////////////////////////////////// + // Sorting by stats type. + // If types are the same then sort by name. + + struct ByTypeAscending + { + FORCEINLINE_DEBUGGABLE bool operator()(const FStatsNodePtr& A, const FStatsNodePtr& B) const + { + const EStatsNodeType& TypeA = A->GetType(); + const EStatsNodeType& TypeB = B->GetType(); + + if (TypeA == TypeB) + { + // Sort by stats name (ascending). + return A->GetName().LexicalLess(B->GetName()); + } + else + { + return TypeA < TypeB; + } + } + }; + + struct ByTypeDescending + { + FORCEINLINE_DEBUGGABLE bool operator()(const FStatsNodePtr& A, const FStatsNodePtr& B) const + { + const EStatsNodeType& TypeA = A->GetType(); + const EStatsNodeType& TypeB = B->GetType(); + + if (TypeA == TypeB) + { + // Sort by stats name (ascending). + return A->GetName().LexicalLess(B->GetName()); + } + else + { + return TypeA > TypeB; + } + } + }; + + ////////////////////////////////////////////////// + // Sorting by an aggregated stats value. + // If aggregated stats values are the same then sort by name. + + #define SORT_BY_STATS_ASCENDING(Type, SortName, AggregatedStatsMember) \ + struct SortName \ + { \ + FORCEINLINE_DEBUGGABLE bool operator()(const FStatsNodePtr& A, const FStatsNodePtr& B) const \ + { \ + const Type ValueA = A->GetAggregatedStats().AggregatedStatsMember; \ + const Type ValueB = B->GetAggregatedStats().AggregatedStatsMember; \ + \ + if (ValueA == ValueB) \ + { \ + /* Sort by name (ascending). */ \ + return A->GetName().LexicalLess(B->GetName()); \ + } \ + else \ + { \ + return ValueA < ValueB; \ + } \ + } \ + }; + + #define SORT_BY_STATS_DESCENDING(Type, SortName, AggregatedStatsMember) \ + struct SortName \ + { \ + FORCEINLINE_DEBUGGABLE bool operator()(const FStatsNodePtr& A, const FStatsNodePtr& B) const \ + { \ + const Type ValueA = A->GetAggregatedStats().AggregatedStatsMember; \ + const Type ValueB = B->GetAggregatedStats().AggregatedStatsMember; \ + \ + if (ValueA == ValueB) \ + { \ + /* Sort by name (ascending). */ \ + return A->GetName().LexicalLess(B->GetName()); \ + } \ + else \ + { \ + return ValueA > ValueB; \ + } \ + } \ + }; + + #define SORT_BY_STATS(Type, SortName, AggregatedStatsMember) \ + SORT_BY_STATS_ASCENDING(Type, SortName##Ascending, AggregatedStatsMember) \ + SORT_BY_STATS_DESCENDING(Type, SortName##Descending, AggregatedStatsMember) + + SORT_BY_STATS(uint64, ByCount, Count); + SORT_BY_STATS(double, BySum, Sum); + SORT_BY_STATS(double, ByMin, Min); + SORT_BY_STATS(double, ByMax, Max); + SORT_BY_STATS(double, ByAverage, Average); + SORT_BY_STATS(double, ByMedian, Median); + SORT_BY_STATS(double, ByLowerQuartile, LowerQuartile); + SORT_BY_STATS(double, ByUpperQuartile, UpperQuartile); + + + #undef SORT_BY_STATS_ASCENDING + #undef SORT_BY_STATS_DESCENDING + #undef SORT_BY_STATS + + ////////////////////////////////////////////////// +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsViewColumn.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsViewColumn.h new file mode 100644 index 000000000000..00e1a9d0ec01 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsViewColumn.h @@ -0,0 +1,124 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Types/SlateEnums.h" +#include "Misc/EnumClassFlags.h" + +class FStatsNode; // TODO: IInsightsTreeNode; generic formatter; see also TimersViewColumn.h + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +enum class EStatsViewColumnFlags : uint32 +{ + None = 0, + + CanBeHidden = (1 << 0), + CanBeSorted = (1 << 1), + CanBeFiltered = (1 << 2), +}; +ENUM_CLASS_FLAGS(EStatsViewColumnFlags); + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** Holds information about a column in the Stats Counters view widget. */ +class FStatsViewColumn +{ + friend struct FStatsViewColumnFactory; + +public: + typedef TFunction FGetFormattedValueFn; + +public: + /** Whether this column can be hidden. */ + bool bCanBeHidden() const { return EnumHasAnyFlags(Flags, EStatsViewColumnFlags::CanBeHidden); } + + /** Whether this column cab be used for sorting. */ + bool bCanBeSorted() const { return EnumHasAnyFlags(Flags, EStatsViewColumnFlags::CanBeSorted); } + + /** Where this column can be used to filtering displayed results. */ + bool bCanBeFiltered() const { return EnumHasAnyFlags(Flags, EStatsViewColumnFlags::CanBeFiltered); } + + /** If MinColumnWidth == MaxColumnWidth, this column has fixed width and cannot be resized. */ + bool bIsFixedColumnWidth() const { return MinColumnWidth == MaxColumnWidth; } + + FText GetFormattedValue(const FStatsNode& StatsNode) const + { + return GetFormattedValueFn(*this, StatsNode); + } + +protected: + /** No default constructor. */ + FStatsViewColumn() = delete; + + /** Initialization constructor, only used in FStatsViewColumnFactory. */ + FStatsViewColumn + ( + int32 InOrder, + const FName InId, + const FName InSearchId, + FText InShortName, + FText InTitleName, + FText InDescription, + bool bInIsVisible, + const EStatsViewColumnFlags InFlags, + const EHorizontalAlignment InHorizontalAlignment, + const float InInitialColumnWidth, + const float InMinColumnWidth, + const float InMaxColumnWidth, + const FGetFormattedValueFn InGetFormattedValueFn + ) + : Order(InOrder) + , Id(InId) + , SearchId(InSearchId) + , ShortName(MoveTemp(InShortName)) + , TitleName(MoveTemp(InTitleName)) + , Description(MoveTemp(InDescription)) + , bIsVisible(bInIsVisible) + , Flags(InFlags) + , HorizontalAlignment(InHorizontalAlignment) + , InitialColumnWidth(InInitialColumnWidth) + , MinColumnWidth(InMinColumnWidth) + , MaxColumnWidth(InMaxColumnWidth) + , GetFormattedValueFn(InGetFormattedValueFn) + { + } + +public: + /** Order value, to sort columns in the tree view. */ + int32 Order; + + /** Name of the column, name of the property. */ + FName Id; + + /** Name of the column used by the searching system. */ + FName SearchId; + + /** Short name of the column, displayed in the column header. */ + FText ShortName; + + /** Title name of the column, displayed as title in the column tooltip. */ + FText TitleName; + + /** Long name of the column, displayed in the column tooltip. */ + FText Description; + + /** Is this column visible? */ + bool bIsVisible; + + /** On/off switches. */ + EStatsViewColumnFlags Flags; + + /** Horizontal alignment of the content in this column. */ + EHorizontalAlignment HorizontalAlignment; + + float InitialColumnWidth; /**< Initial column width. */ + float MinColumnWidth; /**< Minimum column width. */ + float MaxColumnWidth; /**< Maximum column width. */ + + /** Custom function used to format (as an FText) the value of the stats counter node to be displayed by this column. */ + FGetFormattedValueFn GetFormattedValueFn; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsViewColumnFactory.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsViewColumnFactory.cpp new file mode 100644 index 000000000000..4c21e0276771 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsViewColumnFactory.cpp @@ -0,0 +1,287 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "StatsViewColumnFactory.h" + +// Insights +#include "Insights/Common/TimeUtils.h" +#include "Insights/ViewModels/StatsNodeHelper.h" +#include "Insights/ViewModels/StatsViewColumn.h" + +#define LOCTEXT_NAMESPACE "SStatsView" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// Column identifiers +const FName FStatsViewColumns::NameColumnID(TEXT("Name")); +const FName FStatsViewColumns::MetaGroupNameColumnID(TEXT("MetaGroupName")); +const FName FStatsViewColumns::TypeColumnID(TEXT("Type")); +const FName FStatsViewColumns::CountColumnID(TEXT("Count")); +const FName FStatsViewColumns::SumColumnID(TEXT("Sum")); +const FName FStatsViewColumns::MaxColumnID(TEXT("Max")); +const FName FStatsViewColumns::UpperQuartileColumnID(TEXT("UpperQuartile")); +const FName FStatsViewColumns::AverageColumnID(TEXT("Average")); +const FName FStatsViewColumns::MedianColumnID(TEXT("Median")); +const FName FStatsViewColumns::LowerQuartileColumnID(TEXT("LowerQuartile")); +const FName FStatsViewColumns::MinColumnID(TEXT("Min")); + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FStatsViewColumnFactory::FStatsViewColumnFactory() +{ + uint32 Index = 0; + + // FStatsViewColumn + // ( + // Order, + // Id, + // SearchId, + // ShortName, + // TitleName, + // Description, + // bIsVisible, + // Flags, + // HorizontalAlignment, + // InitialWidth, MinWidth, MaxWidth, + // GetFormattedValueFn + // ) + + ////////////////////////////////////////////////// + // Name + + const EStatsViewColumnFlags NameColumnFlags = EStatsViewColumnFlags::CanBeSorted | + EStatsViewColumnFlags::CanBeFiltered; + + Collection.Add(new FStatsViewColumn + ( + Index++, + FStatsViewColumns::NameColumnID, + TEXT("name"), + LOCTEXT("StatsNameColumnName", "Name"), + LOCTEXT("StatsNameColumnTitle", "Stats or Group Name"), + LOCTEXT("StatsNameColumnDesc", "Name of the stats or group"), + true, + NameColumnFlags, + HAlign_Left, + 206.0f, 10.0f, FLT_MAX, + [](const FStatsViewColumn& Column, const FStatsNode& StatsNode) -> FText + { return FText::FromName(StatsNode.GetName()); } + )); + + ////////////////////////////////////////////////// + // Meta Group Name + + const EStatsViewColumnFlags MetaGroupNameColumnFlags = EStatsViewColumnFlags::CanBeHidden | + EStatsViewColumnFlags::CanBeSorted | + EStatsViewColumnFlags::CanBeFiltered; + + Collection.Add(new FStatsViewColumn + ( + Index++, + FStatsViewColumns::MetaGroupNameColumnID, + TEXT("metagroupname"), + LOCTEXT("StatsMetaGroupNameColumnName", "Meta Group"), + LOCTEXT("StatsMetaGroupNameColumnTitle", "Meta Group Name"), + LOCTEXT("StatsMetaGroupNameColumnDesc", "Name of the meta group"), + false, + MetaGroupNameColumnFlags, + HAlign_Left, + 100.0f, 10.0f, FLT_MAX, + [](const FStatsViewColumn& Column, const FStatsNode& StatsNode) -> FText + { return FText::FromName(StatsNode.GetMetaGroupName()); } + )); + + ////////////////////////////////////////////////// + // Type + + const EStatsViewColumnFlags TypeColumnFlags = EStatsViewColumnFlags::CanBeHidden | + EStatsViewColumnFlags::CanBeSorted | + EStatsViewColumnFlags::CanBeFiltered; + + Collection.Add(new FStatsViewColumn + ( + Index++, + FStatsViewColumns::TypeColumnID, + TEXT("type"), + LOCTEXT("StatsTypeColumnName", "Type"), + LOCTEXT("StatsTypeColumnTitle", "Type"), + LOCTEXT("StatsTypeColumnDesc", "Type of the stats or group"), + false, + TypeColumnFlags, + HAlign_Left, + 60.0f, 10.0f, FLT_MAX, + [](const FStatsViewColumn& Column, const FStatsNode& StatsNode) -> FText + { return StatsNodeTypeHelper::ToName(StatsNode.GetType()); } + )); + + ////////////////////////////////////////////////// + + const EStatsViewColumnFlags AggregatedStatsColumnFlags = EStatsViewColumnFlags::CanBeHidden | + EStatsViewColumnFlags::CanBeSorted | + EStatsViewColumnFlags::CanBeFiltered; + + ////////////////////////////////////////////////// + // Count + + Collection.Add(new FStatsViewColumn + ( + Index++, + FStatsViewColumns::CountColumnID, + TEXT("count"), + LOCTEXT("CountName", "Count"), + LOCTEXT("CountTitle", "Count"), + LOCTEXT("CountDesc", "Number of selected values"), + true, + AggregatedStatsColumnFlags, + HAlign_Right, + 60.0f, 0.0f, FLT_MAX, + [](const FStatsViewColumn& Column, const FStatsNode& StatsNode) -> FText + { return FText::AsNumber(StatsNode.GetAggregatedStats().Count); } + )); + + ////////////////////////////////////////////////// + // Aggregated stats + + const float StatsInitialColumnWidth = 80.0f; + + Collection.Add(new FStatsViewColumn + ( + Index++, + FStatsViewColumns::SumColumnID, + TEXT("sum"), + LOCTEXT("SumName", "Sum"), + LOCTEXT("SumTitle", "Sum"), + LOCTEXT("SumDesc", "Sum of selected values"), + false, + AggregatedStatsColumnFlags, + HAlign_Right, + StatsInitialColumnWidth, 4.0f, FLT_MAX, + [](const FStatsViewColumn& Column, const FStatsNode& StatsNode) -> FText + { return StatsNode.GetTextForAggregatedStatsSum(); } + )); + + Collection.Add(new FStatsViewColumn + ( + Index++, + FStatsViewColumns::MaxColumnID, + TEXT("max"), + LOCTEXT("MaxName", "Max"), + LOCTEXT("MaxTitle", "Maximum"), + LOCTEXT("MaxDesc", "Maximum of selected values"), + true, + AggregatedStatsColumnFlags, + HAlign_Right, + StatsInitialColumnWidth, 4.0f, FLT_MAX, + [](const FStatsViewColumn& Column, const FStatsNode& StatsNode) -> FText + { return StatsNode.GetTextForAggregatedStatsMax(); } + )); + + Collection.Add(new FStatsViewColumn + ( + Index++, + FStatsViewColumns::UpperQuartileColumnID, + TEXT("upperquartile"), + LOCTEXT("UpperQuartileName", "Upper"), + LOCTEXT("UpperQuartileTitle", "Upper Quartile"), + LOCTEXT("UpperQuartileDesc", "Upper quartile (Q3; third quartile; 75th percentile) of selected values"), + false, + AggregatedStatsColumnFlags, + HAlign_Right, + StatsInitialColumnWidth, 4.0f, FLT_MAX, + [](const FStatsViewColumn& Column, const FStatsNode& StatsNode) -> FText + { return StatsNode.GetTextForAggregatedStatsUpperQuartile(); } + )); + + Collection.Add(new FStatsViewColumn + ( + Index++, + FStatsViewColumns::AverageColumnID, + TEXT("avg"), + LOCTEXT("AverageName", "Avg."), + LOCTEXT("AverageTitle", "Average"), + LOCTEXT("AverageDesc", "Average (arithmetic mean) of selected values"), + false, + AggregatedStatsColumnFlags, + HAlign_Right, + StatsInitialColumnWidth, 4.0f, FLT_MAX, + [](const FStatsViewColumn& Column, const FStatsNode& StatsNode) -> FText + { return StatsNode.GetTextForAggregatedStatsAverage(); } + )); + + Collection.Add(new FStatsViewColumn + ( + Index++, + FStatsViewColumns::MedianColumnID, + TEXT("med"), + LOCTEXT("MedianName", "Med."), + LOCTEXT("MedianTitle", "Median"), + LOCTEXT("MedianDesc", "Median (Q2; second quartile; 50th percentile) of selected values"), + false, + AggregatedStatsColumnFlags, + HAlign_Right, + StatsInitialColumnWidth, 4.0f, FLT_MAX, + [](const FStatsViewColumn& Column, const FStatsNode& StatsNode) -> FText + { return StatsNode.GetTextForAggregatedStatsMedian(); } + )); + + Collection.Add(new FStatsViewColumn + ( + Index++, + FStatsViewColumns::LowerQuartileColumnID, + TEXT("lowerquartile"), + LOCTEXT("LowerQuartileName", "Lower"), + LOCTEXT("LowerQuartileTitle", "Lower Quartile"), + LOCTEXT("LowerQuartileDesc", "Lower quartile (Q1; first quartile; 25th percentile) of selected values"), + false, + AggregatedStatsColumnFlags, + HAlign_Right, + StatsInitialColumnWidth, 4.0f, FLT_MAX, + [](const FStatsViewColumn& Column, const FStatsNode& StatsNode) -> FText + { return StatsNode.GetTextForAggregatedStatsLowerQuartile(); } + )); + + Collection.Add(new FStatsViewColumn + ( + Index++, + FStatsViewColumns::MinColumnID, + TEXT("min"), + LOCTEXT("MinName", "Min"), + LOCTEXT("MinTitle", "Minimum"), + LOCTEXT("MinDesc", "Minimum of selected values"), + true, + AggregatedStatsColumnFlags, + HAlign_Right, + StatsInitialColumnWidth, 4.0f, FLT_MAX, + [](const FStatsViewColumn& Column, const FStatsNode& StatsNode) -> FText + { return StatsNode.GetTextForAggregatedStatsMin(); } + )); + + ////////////////////////////////////////////////// + + for (int32 MapIndex = 0; MapIndex < Collection.Num(); MapIndex++) + { + ColumnIdToPtrMapping.Add(Collection[MapIndex]->Id, Collection[MapIndex]); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FStatsViewColumnFactory::~FStatsViewColumnFactory() +{ + for (const FStatsViewColumn* Column : Collection) + { + delete Column; + } + Collection.Reset(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FStatsViewColumnFactory& FStatsViewColumnFactory::Get() +{ + static FStatsViewColumnFactory Instance; + return Instance; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsViewColumnFactory.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsViewColumnFactory.h new file mode 100644 index 000000000000..0f8dfe861546 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/StatsViewColumnFactory.h @@ -0,0 +1,49 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "Insights/ViewModels/StatsViewColumn.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct FStatsViewColumns +{ + // Column identifiers + static const FName NameColumnID; + static const FName MetaGroupNameColumnID; + static const FName TypeColumnID; + static const FName CountColumnID; + static const FName SumColumnID; + static const FName MaxColumnID; + static const FName UpperQuartileColumnID; + static const FName AverageColumnID; + static const FName MedianColumnID; + static const FName LowerQuartileColumnID; + static const FName MinColumnID; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct FStatsViewColumnFactory +{ +private: + /** Default constructor. */ + FStatsViewColumnFactory(); + + /** Destructor. */ + ~FStatsViewColumnFactory(); + +public: + /** Contains basic information about columns used in the Stats Counters view widget. Names should be localized. */ + TArray Collection; + + /** Mapping between column IDs and FStatsViewColumn pointers. */ + TMap ColumnIdToPtrMapping; + + /** Singleton */ + static const FStatsViewColumnFactory& Get(); +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimeRulerTrack.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimeRulerTrack.cpp new file mode 100644 index 000000000000..55f4aa7ba6e2 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimeRulerTrack.cpp @@ -0,0 +1,232 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TimeRulerTrack.h" + +#include "Brushes/SlateColorBrush.h" +#include "Fonts/FontMeasure.h" +#include "Fonts/SlateFontInfo.h" +#include "Framework/Application/SlateApplication.h" +#include "Rendering/DrawElements.h" + +// Insights +#include "Insights/Common/PaintUtils.h" +#include "Insights/Common/TimeUtils.h" +#include "Insights/ViewModels/TimingTrackViewport.h" + +#define LOCTEXT_NAMESPACE "TimeRulerTrack" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FTimeRulerTrack::FTimeRulerTrack(uint64 InTrackId) + : FBaseTimingTrack(InTrackId) + , WhiteBrush(FCoreStyle::Get().GetBrush("WhiteBrush")) + , Font(FCoreStyle::GetDefaultFontStyle("Regular", 8)) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FTimeRulerTrack::~FTimeRulerTrack() +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimeRulerTrack::Reset() +{ + FBaseTimingTrack::Reset(); + + constexpr float TimeRulerHeight = 22.0f; + SetHeight(TimeRulerHeight); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimeRulerTrack::UpdateHoveredState(float MouseX, float MouseY, const FTimingTrackViewport& Viewport) +{ + //constexpr float HeaderWidth = 100.0f; + //constexpr float HeaderHeight = 14.0f; + + if (MouseY >= GetPosY() && MouseY < GetPosY() + GetHeight()) + { + SetHoveredState(true); + //SetHeaderHoveredState(MouseX < HeaderWidth && MouseY < GetPosY() + HeaderHeight); + } + else + { + SetHoveredState(false); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimeRulerTrack::DrawBackground(FDrawContext& DrawContext, const FTimingTrackViewport& Viewport) const +{ + const float X0 = Viewport.TimeToSlateUnitsRounded(0.0); + const float X1 = Viewport.TimeToSlateUnitsRounded(Viewport.MaxValidTime); + const float W = FMath::CeilToFloat(Viewport.Width); + const float H = GetHeight(); + + const FLinearColor InvalidAreaColor(0.08f, 0.07f, 0.07f, 1.0f); + const FLinearColor ValidAreaColor(0.09f, 0.09f, 0.09f, 1.0f); + + if (X0 >= W || X1 <= 0.0f) + { + // Draw invalid area (entire view). + DrawContext.DrawBox(0.0f, 0.0f, W, H, WhiteBrush, InvalidAreaColor); + } + else // X0 < W && X1 > 0 + { + if (X0 > 0.0f) + { + // Draw invalid area (left). + DrawContext.DrawBox(0.0f, 0.0f, X0, H, WhiteBrush, InvalidAreaColor); + } + + if (X1 < W) + { + // Draw invalid area (right). + DrawContext.DrawBox(X1, 0.0f, W - X1, H, WhiteBrush, InvalidAreaColor); + } + + float ValidX0 = FMath::Max(X0, 0.0f); + float ValidX1 = FMath::Min(X1, W); + + if (ValidX1 > ValidX0) + { + // Draw valid area. + DrawContext.DrawBox(ValidX0, 0.0f, ValidX1 - ValidX0, H, WhiteBrush, ValidAreaColor); + } + } + + DrawContext.LayerId++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimeRulerTrack::Draw(FDrawContext& DrawContext, const FTimingTrackViewport& Viewport, const FVector2D& MousePosition, const bool bIsSelecting, const double SelectionStartTime, const double SelectionEndTime) const +{ + const float MinorTickMark = 5.0f; + const float MajorTickMark = 20 * MinorTickMark; + + const float MinorTickMarkHeight = 5.0f; + const float MajorTickMarkHeight = 9.0f; + + const float TextY = GetPosY() + MajorTickMarkHeight; + + double MinorTickMarkTime = Viewport.GetDurationForViewportDX(MinorTickMark); + double MajorTickMarkTime = Viewport.GetDurationForViewportDX(MajorTickMark); + + double VX = Viewport.StartTime * Viewport.ScaleX; + double MinorN = FMath::FloorToDouble(VX / static_cast(MinorTickMark)); + double MajorN = FMath::FloorToDouble(VX / static_cast(MajorTickMark)); + float MinorOX = static_cast(FMath::RoundToDouble(MinorN * static_cast(MinorTickMark) - VX)); + float MajorOX = static_cast(FMath::RoundToDouble(MajorN * static_cast(MajorTickMark) - VX)); + + // Draw the time ruler's background. + DrawBackground(DrawContext, Viewport); + + // Draw the minor tick marks. + for (float X = MinorOX; X < Viewport.Width; X += MinorTickMark) + { + const bool bIsTenth = ((int32)(((X - MajorOX) / MinorTickMark) + 0.4f) % 2 == 0); + const float MinorTickH = bIsTenth ? MinorTickMarkHeight : MinorTickMarkHeight - 1.0f; + DrawContext.DrawBox(X, GetPosY(), 1.0f, MinorTickH, WhiteBrush, + bIsTenth ? FLinearColor(0.3f, 0.3f, 0.3f, 1.0f) : FLinearColor(0.25f, 0.25f, 0.25f, 1.0f)); + } + // Draw the major tick marks. + for (float X = MajorOX; X < Viewport.Width; X += MajorTickMark) + { + DrawContext.DrawBox(X, GetPosY(), 1.0f, MajorTickMarkHeight, WhiteBrush, FLinearColor(0.4f, 0.4f, 0.4f, 1.0f)); + } + DrawContext.LayerId++; + + const double DT = static_cast(MajorTickMark) / Viewport.ScaleX; + const double Precision = FMath::Max(DT / 10.0, TimeUtils::Nanosecond); + + const TSharedRef FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService(); + + // Draw the time at major tick marks. + for (float X = MajorOX; X < Viewport.Width + MajorTickMark; X += MajorTickMark) + { + const double T = Viewport.SlateUnitsToTime(X); + FString Text = TimeUtils::FormatTime(T, Precision); + const float TextWidth = FontMeasureService->Measure(Text, Font).X; + DrawContext.DrawText(X - TextWidth / 2, TextY, Text, Font, + (T < 0 || T >= Viewport.MaxValidTime) ? FLinearColor(0.7f, 0.5f, 0.5f, 1.0f) : FLinearColor(0.8f, 0.8f, 0.8f, 1.0f)); + } + DrawContext.LayerId++; + + bool bShowMousePos = !MousePosition.IsZero(); + if (bShowMousePos) + { + const FLinearColor MousePosLineColor(0.9f, 0.9f, 0.9f, 0.1f); + const FLinearColor MousePosTextBackgroundColor(0.9f, 0.9f, 0.9f, 1.0f); + FLinearColor MousePosTextForegroundColor(0.1f, 0.1f, 0.1f, 1.0f); + + // Time at current mouse position. + FString MousePosText; + + const double MousePosTime = Viewport.SlateUnitsToTime(MousePosition.X); + const double MousePosPrecision = FMath::Max(DT / 100.0, TimeUtils::Nanosecond); + if (MousePosition.Y >= GetPosY() && MousePosition.Y < GetPosY() + GetHeight()) + { + // If mouse is hovering the time ruller, format time with a better precision (split seconds in ms, us, ns and ps). + MousePosText = TimeUtils::FormatTimeSplit(MousePosTime, MousePosPrecision); + } + else + { + // Format current time with one more digit than the time at major tick marks. + MousePosText = TimeUtils::FormatTime(MousePosTime, MousePosPrecision); + } + + const float MousePosTextWidth = FMath::RoundToFloat(FontMeasureService->Measure(MousePosText, Font).X); + static float CrtMousePosTextWidth = 0.0f; + + if (!FMath::IsNearlyEqual(CrtMousePosTextWidth, MousePosTextWidth)) + { + // Animate the box's width (to avoid flickering). + CrtMousePosTextWidth = CrtMousePosTextWidth * 0.6f + MousePosTextWidth * 0.4f; + } + + float X = MousePosition.X; + float W = CrtMousePosTextWidth + 4.0f; + if (bIsSelecting && SelectionStartTime < SelectionEndTime) + { + float SelectionX1 = Viewport.TimeToSlateUnitsRounded(SelectionStartTime); + float SelectionX2 = Viewport.TimeToSlateUnitsRounded(SelectionEndTime); + if (X - SelectionX1 > 1.0f) + { + X = SelectionX2 + W / 2; + } + if (SelectionX2 - X > 1.0f) + { + X = SelectionX1 - W / 2; + } + MousePosTextForegroundColor = FLinearColor(FColor(32, 64, 128, 255)); + } + else + { + // Draw horizontal line at mouse position. + //DrawContext.DrawBox(0.0f, MousePosition.Y, Viewport.Width, 1.0f, WhiteBrush, MousePosLineColor); + + // Draw vertical line at mouse position. + DrawContext.DrawBox(MousePosition.X, 0.0f, 1.0f, Viewport.Height, WhiteBrush, MousePosLineColor); + + // Stroke the vertical line above current time box. + DrawContext.DrawBox(MousePosition.X, 0.0f, 1.0f, TextY, WhiteBrush, MousePosTextBackgroundColor); + } + + // Fill the current time box. + DrawContext.DrawBox(X - W / 2, TextY, W, 12.0f, WhiteBrush, MousePosTextBackgroundColor); + DrawContext.LayerId++; + + // Draw current time text. + DrawContext.DrawText(X - MousePosTextWidth / 2, TextY, MousePosText, Font, MousePosTextForegroundColor); + DrawContext.LayerId++; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimeRulerTrack.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimeRulerTrack.h new file mode 100644 index 000000000000..a4b41d7cd084 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimeRulerTrack.h @@ -0,0 +1,41 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Fonts/SlateFontInfo.h" + +// Insights +#include "Insights/ViewModels/BaseTimingTrack.h" + +struct FDrawContext; +struct FSlateBrush; +class FTimingTrackViewport; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FTimeRulerTrack : public FBaseTimingTrack +{ +public: + FTimeRulerTrack(uint64 InTrackId); + virtual ~FTimeRulerTrack(); + + virtual void Reset() override; + virtual void UpdateHoveredState(float MouseX, float MouseY, const FTimingTrackViewport& Viewport); + + void Draw(FDrawContext& DrawContext, const FTimingTrackViewport& Viewport, + const FVector2D& MousePosition = FVector2D(0.0f, 0.0f), + const bool bIsSelecting = false, + const double SelectionStartTime = 0.0, + const double SelectionEndTime = 0.0) const; + +private: + void DrawBackground(FDrawContext& DrawContext, const FTimingTrackViewport& Viewport) const; + +public: + // Slate resources + const FSlateBrush* WhiteBrush; + const FSlateFontInfo Font; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerGroupingAndSorting.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerGroupingAndSorting.cpp new file mode 100644 index 000000000000..51cb096cb4ad --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerGroupingAndSorting.cpp @@ -0,0 +1,9 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TimerGroupingAndSorting.h" + + +#define LOCTEXT_NAMESPACE "TimerNode" + + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerGroupingAndSorting.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerGroupingAndSorting.h new file mode 100644 index 000000000000..dbe828379d55 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerGroupingAndSorting.h @@ -0,0 +1,39 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Insights/ViewModels/TimerNode.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Grouping + +/** Enumerates types of grouping or sorting for the timer nodes. */ +enum class ETimerGroupingMode +{ + /** Creates a single group for all timers. */ + Flat, + + /** Creates one group for one letter. */ + ByName, + + /** Creates groups based on timer metadata group names. */ + ByMetaGroupName, + + /** Creates one group for each timer type. */ + ByType, + + ByTotalInclusiveTime, + + ByTotalExclusiveTime, + + ByInstanceCount, + + /** Invalid enum type, may be used as a number of enumerations. */ + InvalidOrMax, +}; + +/** Type definition for shared pointers to instances of ETimerGroupingMode. */ +typedef TSharedPtr ETimerGroupingModePtr; + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerNode.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerNode.cpp new file mode 100644 index 000000000000..72d6c400a42d --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerNode.cpp @@ -0,0 +1,59 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TimerNode.h" + +#define LOCTEXT_NAMESPACE "TimerNode" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimerNode::ResetAggregatedStats() +{ + AggregatedStats = Trace::FAggregatedTimingStats(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimerNode::SetAggregatedStats(const Trace::FAggregatedTimingStats& InAggregatedStats) +{ + AggregatedStats = InAggregatedStats; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FText FTimerNode::GetNameEx() const +{ + FText Text = FText::GetEmpty(); + + if (IsGroup()) + { + const int32 NumChildren = Children.Num(); + const int32 NumFilteredChildren = FilteredChildren.Num(); + + if (NumFilteredChildren == NumChildren) + { + Text = FText::Format(LOCTEXT("TimerNodeGroupTextFmt1", "{0} ({1})"), FText::FromName(Name), FText::AsNumber(NumChildren)); + } + else + { + Text = FText::Format(LOCTEXT("TimerNodeGroupTextFmt2", "{0} ({1} / {2})"), FText::FromName(Name), FText::AsNumber(NumFilteredChildren), FText::AsNumber(NumChildren)); + } + } + else + { + //const bool bIsStatTracked = FProfilerManager::Get()->IsStatTracked(GroupOrStatNode->GetStatID()); + //if (bIsStatTracked) + //{ + // Text = FText::Format(LOCTEXT("TimerNodeTrackedTextFmt", "{0}*"), FText::FromName(Name)); + //} + //else + { + Text = FText::FromName(Name); + } + } + + return Text; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerNode.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerNode.h new file mode 100644 index 000000000000..96eed175c7ca --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerNode.h @@ -0,0 +1,227 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "TraceServices/AnalysisService.h" + +class FTimerNode; + +/** Type definition for shared pointers to instances of FTimerNode. */ +typedef TSharedPtr FTimerNodePtr; + +/** Type definition for shared references to instances of FTimerNode. */ +typedef TSharedRef FTimerNodeRef; + +/** Type definition for shared references to const instances of FTimerNode. */ +typedef TSharedRef FTimerNodeRefConst; + +/** Type definition for weak references to instances of FTimerNode. */ +typedef TWeakPtr FTimerNodeWeak; + +enum class ETimerNodeType +{ + /** The TimerNode is a CPU Scope timer. */ + CpuScope, + + /** The TimerNode is a GPU Scope timer. */ + GpuScope, + + /** The TimerNode is a Compute Scope timer. */ + ComputeScope, + + /** The TimerNode is a group node. */ + Group, + + /** Invalid enum type, may be used as a number of enumerations. */ + InvalidOrMax, +}; + +/** + * Class used to store information about a timer node (used in timers' tree view). + */ +class FTimerNode : public TSharedFromThis +{ +public: + static const uint64 InvalidId = -1; + +public: + /** Initialization constructor for the timer node. */ + FTimerNode(uint64 InId, const FName InName, const FName InMetaGroupName, ETimerNodeType InType) + : Id(InId) + , Name(InName) + , MetaGroupName(InMetaGroupName) + , Type(InType) + , bForceExpandGroupNode(false) + { + ResetAggregatedStats(); + } + + /** Initialization constructor for the group node. */ + FTimerNode(const FName InGroupName) + : Id(0) + , Name(InGroupName) + , Type(ETimerNodeType::Group) + , bForceExpandGroupNode(false) + { + ResetAggregatedStats(); + } + + /** + * @return an Id of this timer, valid only for timer nodes. + */ + const uint64 GetId() const + { + return Id; + } + + /** + * @return a name of this node, group or timer. + */ + const FName& GetName() const + { + return Name; + } + + /** + * @return a name of this node, group or timer + addditional info, to display in Timers tree view. + */ + const FText GetNameEx() const; + + /** + * @return a name of the group that this timer node belongs to, taken from the metadata. + */ + const FName& GetMetaGroupName() const + { + return MetaGroupName; + } + + /** + * @return a type of this timer or ETimerNodeType::Group for group nodes. + */ + const ETimerNodeType& GetType() const + { + return Type; + } + + /** + * @return true, if this node is a group node. + */ + bool IsGroup() const + { + return Type == ETimerNodeType::Group; + } + + /** + * @return the aggregated stats for this timer. + */ + const Trace::FAggregatedTimingStats& GetAggregatedStats() const + { + return AggregatedStats; + } + + void ResetAggregatedStats(); + + void SetAggregatedStats(const Trace::FAggregatedTimingStats& AggregatedStats); + + /** + * @return a const reference to the child nodes of this group. + */ + FORCEINLINE_DEBUGGABLE const TArray& GetChildren() const + { + return Children; + } + + /** + * @return a const reference to the child nodes that should be visible to the UI based on filtering. + */ + FORCEINLINE_DEBUGGABLE const TArray& GetFilteredChildren() const + { + return FilteredChildren; + } + + /** + * @return a weak reference to the group of this timer node, may be invalid. + */ + FTimerNodeWeak GetGroupPtr() const + { + return GroupPtr; + } + + /** + * @return a name of the fake group that this timer node belongs to. + */ + const FName& GetGroupName() const + { + return GroupPtr.Pin()->Name; + } + + /** + * @return a name of the fake group that this timer node belongs to. + */ + //const FName& GetGroupNameSafe() const + //{ + // return GroupPtr.IsValid() ? GroupPtr.Pin()->Name : NAME_None; + //} + + bool IsFiltered() const + { + return false; // TODO + } + +public: + /** Sorts children using the specified class instance. */ + template + void SortChildren(const TSortingClass& Instance) + { + Children.Sort(Instance); + } + + /** Adds specified child to the children and sets group for it. */ + FORCEINLINE_DEBUGGABLE void AddChildAndSetGroupPtr(const FTimerNodePtr& ChildPtr) + { + ChildPtr->GroupPtr = AsShared(); + Children.Add(ChildPtr); + } + + /** Adds specified child to the filtered children. */ + FORCEINLINE_DEBUGGABLE void AddFilteredChild(const FTimerNodePtr& ChildPtr) + { + FilteredChildren.Add(ChildPtr); + } + + /** Clears filtered children. */ + void ClearFilteredChildren() + { + FilteredChildren.Reset(); + } + +protected: + /** The Id of this timer or group. */ + const uint64 Id; + + /** The name of this timer or group. */ + const FName Name; + + /** The name of the group that this timer belongs to, based on the timer's metadata; only valid for timer nodes. */ + const FName MetaGroupName; + + /** Holds the type of this timer; for the group, this is Group. */ + const ETimerNodeType Type; + + /** Aggregated stats. */ + Trace::FAggregatedTimingStats AggregatedStats; + + /** Children of this node. */ + TArray Children; + + /** Filtered children of this node. */ + TArray FilteredChildren; + + /** A weak pointer to the group/parent of this node. */ + FTimerNodeWeak GroupPtr; + +public: + /** Whether this group node should be expanded when the text filtering is enabled. */ + bool bForceExpandGroupNode; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerNodeHelper.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerNodeHelper.cpp new file mode 100644 index 000000000000..7992c1104a99 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerNodeHelper.cpp @@ -0,0 +1,135 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TimerNodeHelper.h" +#include "EditorStyleSet.h" + +#define LOCTEXT_NAMESPACE "TimerNode" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// TimerNode Type Helper +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText TimerNodeTypeHelper::ToName(const ETimerNodeType NodeType) +{ + static_assert(static_cast(ETimerNodeType::InvalidOrMax) == 4, "Not all cases are handled in switch below!?"); + switch (NodeType) + { + case ETimerNodeType::GpuScope: return LOCTEXT("Timer_Name_Gpu", "GPU"); + case ETimerNodeType::ComputeScope: return LOCTEXT("Timer_Name_Compute", "Compute"); + case ETimerNodeType::CpuScope: return LOCTEXT("Timer_Name_Cpu", "CPU"); + case ETimerNodeType::Group: return LOCTEXT("Timer_Name_Group", "Group"); + default: return LOCTEXT("InvalidOrMax", "InvalidOrMax"); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText TimerNodeTypeHelper::ToDescription(const ETimerNodeType NodeType) +{ + static_assert(static_cast(ETimerNodeType::InvalidOrMax) == 4, "Not all cases are handled in switch below!?"); + switch (NodeType) + { + case ETimerNodeType::GpuScope: return LOCTEXT("Timer_Desc_Gpu", "GPU scope timer"); + case ETimerNodeType::ComputeScope: return LOCTEXT("Timer_Desc_Compute", "Compute scope timer"); + case ETimerNodeType::CpuScope: return LOCTEXT("Timer_Desc_Cpu", "CPU scope timer"); + case ETimerNodeType::Group: return LOCTEXT("Timer_Desc_Group", "Group timer node"); + default: return LOCTEXT("InvalidOrMax", "InvalidOrMax"); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FName TimerNodeTypeHelper::ToBrushName(const ETimerNodeType NodeType) +{ + static_assert(static_cast(ETimerNodeType::InvalidOrMax) == 4, "Not all cases are handled in switch below!?"); + switch (NodeType) + { + case ETimerNodeType::GpuScope: return TEXT("Profiler.FiltersAndPresets.StatTypeIcon"); //TODO: "Icons.GpuTimer" + case ETimerNodeType::ComputeScope: return TEXT("Profiler.FiltersAndPresets.StatTypeIcon"); //TODO: "Icons.ComputeTimer" + case ETimerNodeType::CpuScope: return TEXT("Profiler.FiltersAndPresets.StatTypeIcon"); //TODO: "Icons.CpuTimer" + case ETimerNodeType::Group: return TEXT("Profiler.Misc.GenericGroup"); //TODO: "Icons.GenericGroup" + default: return NAME_None; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FSlateBrush* TimerNodeTypeHelper::GetIconForGroup() +{ + return FEditorStyle::GetBrush(TEXT("Profiler.Misc.GenericGroup")); //TODO: FInsightsStyle::GetBrush(TEXT("Icons.GenericGroup")); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FSlateBrush* TimerNodeTypeHelper::GetIconForTimerNodeType(const ETimerNodeType NodeType) +{ + static_assert(static_cast(ETimerNodeType::InvalidOrMax) == 4, "Not all cases are handled in switch below!?"); + switch (NodeType) + { + case ETimerNodeType::GpuScope: return FEditorStyle::GetBrush(TEXT("Profiler.Type.NumberFloat")); //TODO: FInsightsStyle::GetBrush(TEXT("Icons.GpuTimer")); + case ETimerNodeType::ComputeScope: return FEditorStyle::GetBrush(TEXT("Profiler.Type.NumberFloat")); //TODO: FInsightsStyle::GetBrush(TEXT("Icons.ComputeTimer")); + case ETimerNodeType::CpuScope: return FEditorStyle::GetBrush(TEXT("Profiler.Type.NumberFloat")); //TODO: FInsightsStyle::GetBrush(TEXT("Icons.CpuTimer")); + case ETimerNodeType::Group: return FEditorStyle::GetBrush(TEXT("Profiler.Misc.GenericGroup")); //TODO: FInsightsStyle::GetBrush(TEXT("Icons.GenericGroup")); + default: return nullptr; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// TimerNode Grouping Helper +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText TimerNodeGroupingHelper::ToName(const ETimerGroupingMode GroupingMode) +{ + static_assert(static_cast(ETimerGroupingMode::InvalidOrMax) == 7, "Not all cases are handled in switch below!?"); + switch (GroupingMode) + { + case ETimerGroupingMode::Flat: return LOCTEXT("Grouping_Name_Flat", "Flat"); + case ETimerGroupingMode::ByName: return LOCTEXT("Grouping_Name_ByName", "Timer Name"); + case ETimerGroupingMode::ByMetaGroupName: return LOCTEXT("Grouping_Name_MetaGroupName", "Meta Group Name"); + case ETimerGroupingMode::ByType: return LOCTEXT("Grouping_Name_Type", "Timer Type"); + case ETimerGroupingMode::ByTotalInclusiveTime: return LOCTEXT("Grouping_Name_TotalInclusiveTime", "Total Inclusive Time"); + case ETimerGroupingMode::ByTotalExclusiveTime: return LOCTEXT("Grouping_Name_TotalExclusiveTime", "Total Exclusive Time"); + case ETimerGroupingMode::ByInstanceCount: return LOCTEXT("Grouping_Name_InstanceCount", "Instance Count"); + default: return LOCTEXT("InvalidOrMax", "InvalidOrMax"); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText TimerNodeGroupingHelper::ToDescription(const ETimerGroupingMode GroupingMode) +{ + static_assert(static_cast(ETimerGroupingMode::InvalidOrMax) == 7, "Not all cases are handled in switch below!?"); + switch (GroupingMode) + { + case ETimerGroupingMode::Flat: return LOCTEXT("Grouping_Desc_Flat", "Creates a single group. Includes all timers."); + case ETimerGroupingMode::ByName: return LOCTEXT("Grouping_Desc_ByName", "Creates one group for one letter."); + case ETimerGroupingMode::ByMetaGroupName: return LOCTEXT("Grouping_Desc_MetaGroupName", "Creates groups based on metadata group names of timers."); + case ETimerGroupingMode::ByType: return LOCTEXT("Grouping_Desc_Type", "Creates one group for each timer type."); + case ETimerGroupingMode::ByTotalInclusiveTime: return LOCTEXT("Grouping_Desc_TotalInclusiveTime", "Creates one group for each logarithmic range ie. 0.001 - 0.01, 0.01 - 0.1, 0.1 - 1.0, 1.0 - 10.0 etc"); + case ETimerGroupingMode::ByTotalExclusiveTime: return LOCTEXT("Grouping_Desc_TotalExclusiveTime", "Creates one group for each logarithmic range ie. 0.001 - 0.01, 0.01 - 0.1, 0.1 - 1.0, 1.0 - 10.0 etc"); + case ETimerGroupingMode::ByInstanceCount: return LOCTEXT("Grouping_Desc_InstanceCount", "Creates one group for each logarithmic range ie. 0, 1 - 10, 10 - 100, 100 - 1000, etc"); + default: return LOCTEXT("InvalidOrMax", "InvalidOrMax"); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FName TimerNodeGroupingHelper::ToBrushName(const ETimerGroupingMode GroupingMode) +{ + static_assert(static_cast(ETimerGroupingMode::InvalidOrMax) == 7, "Not all cases are handled in switch below!?"); + switch (GroupingMode) + { + case ETimerGroupingMode::Flat: return TEXT("Profiler.FiltersAndPresets.GroupNameIcon"); //TODO: "Icons.Grouping.Flat" + case ETimerGroupingMode::ByName: return TEXT("Profiler.FiltersAndPresets.GroupNameIcon"); //TODO: "Icons.Grouping.ByName" + case ETimerGroupingMode::ByMetaGroupName : return TEXT("Profiler.FiltersAndPresets.StatNameIcon"); //TODO + case ETimerGroupingMode::ByType: return TEXT("Profiler.FiltersAndPresets.StatTypeIcon"); //TODO + case ETimerGroupingMode::ByTotalInclusiveTime: return TEXT("Profiler.FiltersAndPresets.StatValueIcon"); //TODO + case ETimerGroupingMode::ByTotalExclusiveTime: return TEXT("Profiler.FiltersAndPresets.StatValueIcon"); //TODO + case ETimerGroupingMode::ByInstanceCount: return TEXT("Profiler.FiltersAndPresets.StatValueIcon"); //TODO + default: return NAME_None; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerNodeHelper.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerNodeHelper.h new file mode 100644 index 000000000000..8bbfdebb139b --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimerNodeHelper.h @@ -0,0 +1,240 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Styling/SlateBrush.h" + +// Insights +#include "Insights/ViewModels/TimerNode.h" +#include "Insights/ViewModels/TimerGroupingAndSorting.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** Helper struct that contains helper functions and classes for ETimerNodeType enum. */ +struct TimerNodeTypeHelper +{ + /** + * @param Type - The value to get the text for. + * + * @return text representation of the specified ETimerNodeType value. + */ + static FText ToName(const ETimerNodeType Type); + + /** + * @param Type - The value to get the text for. + * + * @return text representation with more detailed explanation of the specified ETimerNodeType value. + */ + static FText ToDescription(const ETimerNodeType Type); + + /** + * @param Type - The value to get the brush name for. + * + * @return brush name of the specified ETimerNodeType value. + */ + static FName ToBrushName(const ETimerNodeType Type); + + static const FSlateBrush* GetIconForGroup(); + static const FSlateBrush* GetIconForTimerNodeType(const ETimerNodeType Type); +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** Helper struct that contains grouping static functions and classes. */ +struct TimerNodeGroupingHelper +{ + /** + * @param TimerGroupingMode - The value to get the text for. + * + * @return text representation of the specified ETimerGroupingMode value. + */ + static FText ToName(const ETimerGroupingMode TimerGroupingMode); + + /** + * @param TimerGroupingMode - The value to get the text for. + * + * @return text representation with more detailed explanation of the specified ETimerGroupingMode value. + */ + static FText ToDescription(const ETimerGroupingMode TimerGroupingMode); + + /** + * @param TimerGroupingMode - The value to get the brush name for. + * + * @return brush name of the specified ETimerGroupingMode value. + */ + static FName ToBrushName(const ETimerGroupingMode TimerGroupingMode); +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** Helper struct that contains sorting static functions and classes. */ +struct TimerNodeSortingHelper +{ + ////////////////////////////////////////////////// + // Sorting by timer's name. + + struct ByNameAscending + { + FORCEINLINE_DEBUGGABLE bool operator()(const FTimerNodePtr& A, const FTimerNodePtr& B) const + { + return A->GetName().LexicalLess(B->GetName()); + } + }; + + struct ByNameDescending + { + FORCEINLINE_DEBUGGABLE bool operator()(const FTimerNodePtr& A, const FTimerNodePtr& B) const + { + return B->GetName().LexicalLess(A->GetName()); + } + }; + + ////////////////////////////////////////////////// + // Sorting by timer's meta group name. + // If meta group names are the same then sort by name. + + struct ByMetaGroupNameAscending + { + FORCEINLINE_DEBUGGABLE bool operator()(const FTimerNodePtr& A, const FTimerNodePtr& B) const + { + if (A->GetMetaGroupName() == B->GetMetaGroupName()) + { + // Sort by timer name (ascending). + return A->GetName().LexicalLess(B->GetName()); + } + else + { + return A->GetMetaGroupName().LexicalLess(B->GetMetaGroupName()); + } + } + }; + + struct ByMetaGroupNameDescending + { + FORCEINLINE_DEBUGGABLE bool operator()(const FTimerNodePtr& A, const FTimerNodePtr& B) const + { + if (A->GetMetaGroupName() == B->GetMetaGroupName()) + { + // Sort by timer name (ascending). + return A->GetName().LexicalLess(B->GetName()); + } + else + { + return B->GetMetaGroupName().LexicalLess(A->GetMetaGroupName()); + } + } + }; + + ////////////////////////////////////////////////// + // Sorting by timer's type. + // If types are the same then sort by name. + + struct ByTypeAscending + { + FORCEINLINE_DEBUGGABLE bool operator()(const FTimerNodePtr& A, const FTimerNodePtr& B) const + { + const ETimerNodeType& TypeA = A->GetType(); + const ETimerNodeType& TypeB = B->GetType(); + + if (TypeA == TypeB) + { + // Sort by timer name (ascending). + return A->GetName().LexicalLess(B->GetName()); + } + else + { + return TypeA < TypeB; + } + } + }; + + struct ByTypeDescending + { + FORCEINLINE_DEBUGGABLE bool operator()(const FTimerNodePtr& A, const FTimerNodePtr& B) const + { + const ETimerNodeType& TypeA = A->GetType(); + const ETimerNodeType& TypeB = B->GetType(); + + if (TypeA == TypeB) + { + // Sort by timer name (ascending). + return A->GetName().LexicalLess(B->GetName()); + } + else + { + return TypeA > TypeB; + } + } + }; + + ////////////////////////////////////////////////// + // Sorting by an aggregated stats value. + // If aggregated stats values are the same then sort by name. + + #define SORT_BY_STATS_ASCENDING(Type, SortName, AggregatedStatsMember) \ + struct SortName \ + { \ + FORCEINLINE_DEBUGGABLE bool operator()(const FTimerNodePtr& A, const FTimerNodePtr& B) const \ + { \ + const Type ValueA = A->GetAggregatedStats().AggregatedStatsMember; \ + const Type ValueB = B->GetAggregatedStats().AggregatedStatsMember; \ + \ + if (ValueA == ValueB) \ + { \ + /* Sort by name (ascending). */ \ + return A->GetName().LexicalLess(B->GetName()); \ + } \ + else \ + { \ + return ValueA < ValueB; \ + } \ + } \ + }; + + #define SORT_BY_STATS_DESCENDING(Type, SortName, AggregatedStatsMember) \ + struct SortName \ + { \ + FORCEINLINE_DEBUGGABLE bool operator()(const FTimerNodePtr& A, const FTimerNodePtr& B) const \ + { \ + const Type ValueA = A->GetAggregatedStats().AggregatedStatsMember; \ + const Type ValueB = B->GetAggregatedStats().AggregatedStatsMember; \ + \ + if (ValueA == ValueB) \ + { \ + /* Sort by name (ascending). */ \ + return A->GetName().LexicalLess(B->GetName()); \ + } \ + else \ + { \ + return ValueA > ValueB; \ + } \ + } \ + }; + + #define SORT_BY_STATS(Type, SortName, AggregatedStatsMember) \ + SORT_BY_STATS_ASCENDING(Type, SortName##Ascending, AggregatedStatsMember) \ + SORT_BY_STATS_DESCENDING(Type, SortName##Descending, AggregatedStatsMember) + + SORT_BY_STATS(uint64, ByInstanceCount, InstanceCount); + + SORT_BY_STATS(double, ByTotalInclusiveTime, TotalInclusiveTime); + SORT_BY_STATS(double, ByMinInclusiveTime, MinInclusiveTime); + SORT_BY_STATS(double, ByMaxInclusiveTime, MaxInclusiveTime); + SORT_BY_STATS(double, ByAverageInclusiveTime, AverageInclusiveTime); + SORT_BY_STATS(double, ByMedianInclusiveTime, MedianInclusiveTime); + + SORT_BY_STATS(double, ByTotalExclusiveTime, TotalExclusiveTime); + SORT_BY_STATS(double, ByMinExclusiveTime, MinExclusiveTime); + SORT_BY_STATS(double, ByMaxExclusiveTime, MaxExclusiveTime); + SORT_BY_STATS(double, ByAverageExclusiveTime, AverageExclusiveTime); + SORT_BY_STATS(double, ByMedianExclusiveTime, MedianExclusiveTime); + + #undef SORT_BY_STATS_ASCENDING + #undef SORT_BY_STATS_DESCENDING + #undef SORT_BY_STATS + + ////////////////////////////////////////////////// +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimersViewColumn.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimersViewColumn.h new file mode 100644 index 000000000000..d8d502377792 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimersViewColumn.h @@ -0,0 +1,118 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Types/SlateEnums.h" +#include "Misc/EnumClassFlags.h" + +class FTimerNode; // TODO: IInsightsTreeNode; generic formatter; see also StatsViewColumn.h + +enum class ETimersViewColumnFlags : uint32 +{ + None = 0, + + CanBeHidden = (1 << 0), + CanBeSorted = (1 << 1), + CanBeFiltered = (1 << 2), +}; +ENUM_CLASS_FLAGS(ETimersViewColumnFlags); + +/** Holds information about a column in the timer view widget. */ +class FTimersViewColumn +{ + friend struct FTimersViewColumnFactory; + +public: + typedef TFunction FGetFormattedValueFn; + +public: + /** Whether this column can be hidden. */ + bool bCanBeHidden() const { return EnumHasAnyFlags(Flags, ETimersViewColumnFlags::CanBeHidden); } + + /** Whether this column cab be used for sorting. */ + bool bCanBeSorted() const { return EnumHasAnyFlags(Flags, ETimersViewColumnFlags::CanBeSorted); } + + /** Where this column can be used to filtering displayed results. */ + bool bCanBeFiltered() const { return EnumHasAnyFlags(Flags, ETimersViewColumnFlags::CanBeFiltered); } + + /** If MinColumnWidth == MaxColumnWidth, this column has fixed width and cannot be resized. */ + bool bIsFixedColumnWidth() const { return MinColumnWidth == MaxColumnWidth; } + + FText GetFormattedValue(const FTimerNode& TimerNode) const + { + return GetFormattedValueFn(*this, TimerNode); + } + +protected: + /** No default constructor. */ + FTimersViewColumn() = delete; + + /** Initialization constructor, only used in FStatsViewColumnFactory. */ + FTimersViewColumn + ( + int32 InOrder, + const FName InId, + const FName InSearchId, + FText InShortName, + FText InTitleName, + FText InDescription, + bool bInIsVisible, + const ETimersViewColumnFlags InFlags, + const EHorizontalAlignment InHorizontalAlignment, + const float InInitialColumnWidth, + const float InMinColumnWidth, + const float InMaxColumnWidth, + const FGetFormattedValueFn InGetFormattedValueFn + ) + : Order(InOrder) + , Id(InId) + , SearchId(InSearchId) + , ShortName(MoveTemp(InShortName)) + , TitleName(MoveTemp(InTitleName)) + , Description(MoveTemp(InDescription)) + , bIsVisible(bInIsVisible) + , Flags(InFlags) + , HorizontalAlignment(InHorizontalAlignment) + , InitialColumnWidth(InInitialColumnWidth) + , MinColumnWidth(InMinColumnWidth) + , MaxColumnWidth(InMaxColumnWidth) + , GetFormattedValueFn(InGetFormattedValueFn) + { + } + +public: + /** Order value, to sort columns in the tree view. */ + int32 Order; + + /** Name of the column, name of the property. */ + FName Id; + + /** Name of the column used by the searching system. */ + FName SearchId; + + /** Short name of the column, displayed in the column header. */ + FText ShortName; + + /** Title name of the column, displayed as title in the column tooltip. */ + FText TitleName; + + /** Long name of the column, displayed in the column tooltip. */ + FText Description; + + /** Is this column visible? */ + bool bIsVisible; + + /** On/off switches. */ + ETimersViewColumnFlags Flags; + + /** Horizontal alignment of the content in this column. */ + EHorizontalAlignment HorizontalAlignment; + + float InitialColumnWidth; /**< Initial column width. */ + float MinColumnWidth; /**< Minimum column width. */ + float MaxColumnWidth; /**< Maximum column width. */ + + /** Custom function used to format (as an FText) the value of the timer node to be displayed by this column. */ + FGetFormattedValueFn GetFormattedValueFn; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimersViewColumnFactory.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimersViewColumnFactory.cpp new file mode 100644 index 000000000000..40fb18a822f8 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimersViewColumnFactory.cpp @@ -0,0 +1,356 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TimersViewColumnFactory.h" + +// Insights +#include "Insights/Common/TimeUtils.h" +#include "Insights/ViewModels/TimerNodeHelper.h" + +#define LOCTEXT_NAMESPACE "STimerView" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Column identifiers + +const FName FTimersViewColumns::NameColumnID(TEXT("Name")); +const FName FTimersViewColumns::MetaGroupNameColumnID(TEXT("MetaGroupName")); +const FName FTimersViewColumns::TypeColumnID(TEXT("Type")); +const FName FTimersViewColumns::InstanceCountColumnID(TEXT("Count")); + +// Inclusive Time columns +const FName FTimersViewColumns::TotalInclusiveTimeColumnID(TEXT("TotalInclTime")); +const FName FTimersViewColumns::MaxInclusiveTimeColumnID(TEXT("MaxInclTime")); +const FName FTimersViewColumns::UpperQuartileInclusiveTimeColumnID(TEXT("UpperQuartileInclTime")); +const FName FTimersViewColumns::AverageInclusiveTimeColumnID(TEXT("AverageInclTime")); +const FName FTimersViewColumns::MedianInclusiveTimeColumnID(TEXT("MedianInclTime")); +const FName FTimersViewColumns::LowerQuartileInclusiveTimeColumnID(TEXT("LowerQuartileInclTime")); +const FName FTimersViewColumns::MinInclusiveTimeColumnID(TEXT("MinInclTime")); + +// Exclusive Time columns +const FName FTimersViewColumns::TotalExclusiveTimeColumnID(TEXT("TotalExclTime")); +const FName FTimersViewColumns::MaxExclusiveTimeColumnID(TEXT("MaxExclTime")); +const FName FTimersViewColumns::UpperQuartileExclusiveTimeColumnID(TEXT("UpperQuartileExclTime")); +const FName FTimersViewColumns::AverageExclusiveTimeColumnID(TEXT("AverageExclTime")); +const FName FTimersViewColumns::MedianExclusiveTimeColumnID(TEXT("MedianExclTime")); +const FName FTimersViewColumns::LowerQuartileExclusiveTimeColumnID(TEXT("LowerQuartileExclTime")); +const FName FTimersViewColumns::MinExclusiveTimeColumnID(TEXT("MinExclTime")); + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FTimersViewColumnFactory::FTimersViewColumnFactory() +{ + uint32 Index = 0; + + // FTimerViewColumn + // ( + // Order, + // Id, + // SearchId, + // ShortName, + // TitleName, + // Description, + // bIsVisible, + // Flags, + // HorizontalAlignment, + // InitialWidth, MinWidth, MaxWidth, + // GetFormattedValueFn + // ) + + ////////////////////////////////////////////////// + // Name + + const ETimersViewColumnFlags NameColumnFlags = ETimersViewColumnFlags::CanBeSorted | + ETimersViewColumnFlags::CanBeFiltered; + + Collection.Add(new FTimersViewColumn + ( + Index++, + FTimersViewColumns::NameColumnID, + TEXT("name"), + LOCTEXT("TimerNameColumnName", "Name"), + LOCTEXT("TimerNameColumnTitle", "Timer or Group Name"), + LOCTEXT("TimerNameColumnDesc", "Name of the timer or group"), + true, + NameColumnFlags, + HAlign_Left, + 246.0f, 10.0f, FLT_MAX, + [](const FTimersViewColumn& Column, const FTimerNode& TimerNode) -> FText + { return FText::FromName(TimerNode.GetName()); } + )); + + ////////////////////////////////////////////////// + // Meta Group Name + + const ETimersViewColumnFlags MetaGroupNameColumnFlags = ETimersViewColumnFlags::CanBeHidden | + ETimersViewColumnFlags::CanBeSorted | + ETimersViewColumnFlags::CanBeFiltered; + + Collection.Add(new FTimersViewColumn + ( + Index++, + FTimersViewColumns::MetaGroupNameColumnID, + TEXT("metagroupname"), + LOCTEXT("TimerMetaGroupNameColumnName", "Meta Group"), + LOCTEXT("TimerMetaGroupNameColumnTitle", "Meta Group Name"), + LOCTEXT("TimerMetaGroupNameColumnDesc", "Name of the meta group"), + false, + MetaGroupNameColumnFlags, + HAlign_Left, + 100.0f, 10.0f, FLT_MAX, + [](const FTimersViewColumn& Column, const FTimerNode& TimerNode) -> FText + { return FText::FromName(TimerNode.GetMetaGroupName()); } + )); + + ////////////////////////////////////////////////// + // Type + + const ETimersViewColumnFlags TypeColumnFlags = ETimersViewColumnFlags::CanBeHidden | + ETimersViewColumnFlags::CanBeSorted | + ETimersViewColumnFlags::CanBeFiltered; + + Collection.Add(new FTimersViewColumn + ( + Index++, + FTimersViewColumns::TypeColumnID, + TEXT("type"), + LOCTEXT("TimerTypeColumnName", "Type"), + LOCTEXT("TimerTypeColumnTitle", "Type"), + LOCTEXT("TimerTypeColumnDesc", "Type of timer or group"), + false, + TypeColumnFlags, + HAlign_Left, + 60.0f, 10.0f, FLT_MAX, + [](const FTimersViewColumn& Column, const FTimerNode& TimerNode) -> FText + { return TimerNodeTypeHelper::ToName(TimerNode.GetType()); } + )); + + ////////////////////////////////////////////////// + + const ETimersViewColumnFlags AggregatedStatsColumnFlags = ETimersViewColumnFlags::CanBeHidden | + ETimersViewColumnFlags::CanBeSorted | + ETimersViewColumnFlags::CanBeFiltered; + + ////////////////////////////////////////////////// + // Instance Count + + Collection.Add(new FTimersViewColumn + ( + Index++, + FTimersViewColumns::InstanceCountColumnID, + TEXT("count"), + LOCTEXT("InstanceCountName", "Count"), + LOCTEXT("InstanceCountTitle", "Instance Count"), + LOCTEXT("InstanceCountDesc", "Number of timer's instances"), + true, + AggregatedStatsColumnFlags, + HAlign_Right, + 60.0f, 0.0f, FLT_MAX, + [](const FTimersViewColumn& Column, const FTimerNode& TimerNode) -> FText + { return FText::AsNumber(TimerNode.GetAggregatedStats().InstanceCount); } + )); + + ////////////////////////////////////////////////// + + constexpr float TotalTimeColumnInitialWidth = 60.0f; + constexpr float TotalTimeColumnMinWidth = 4.0f; + constexpr float TotalTimeColumnMaxWidth = FLT_MAX; + + constexpr float TimeMsColumnInitialWidth = 50.0f; + constexpr float TimeMsColumnMinWidth = 4.0f; + constexpr float TimeMsColumnMaxWidth = FLT_MAX; + + ////////////////////////////////////////////////// + // Inclusive Time stats + + Collection.Add(new FTimersViewColumn + ( + Index++, + FTimersViewColumns::TotalInclusiveTimeColumnID, + TEXT("inc"), + LOCTEXT("TotalInclusiveTimeName", "Incl"), + LOCTEXT("TotalInclusiveTimeTitle", "Total Inclusive Time"), + LOCTEXT("TotalInclusiveTimeDesc", "Total inclusive duration of selected timer's instances"), + true, + AggregatedStatsColumnFlags, + HAlign_Right, + TotalTimeColumnInitialWidth, TotalTimeColumnMinWidth, TotalTimeColumnMaxWidth, + [](const FTimersViewColumn& Column, const FTimerNode& TimerNode) -> FText + { return FText::FromString(TimeUtils::FormatTimeAuto(TimerNode.GetAggregatedStats().TotalInclusiveTime)); } + )); + + Collection.Add(new FTimersViewColumn + ( + Index++, + FTimersViewColumns::MaxInclusiveTimeColumnID, + TEXT("maxinc"), + LOCTEXT("MaxInclusiveTimeName", "I.Max"), + LOCTEXT("MaxInclusiveTimeTitle", "Max Inclusive Time (ms)"), + LOCTEXT("MaxInclusiveTimeDesc", "Maximum inclusive duration of selected timer's instances, in milliseconds"), + false, + AggregatedStatsColumnFlags, + HAlign_Right, + TimeMsColumnInitialWidth, TimeMsColumnMinWidth, TimeMsColumnMaxWidth, + [](const FTimersViewColumn& Column, const FTimerNode& TimerNode) -> FText + { return FText::FromString(TimeUtils::FormatTimeMs(TimerNode.GetAggregatedStats().MaxInclusiveTime)); } + )); + + Collection.Add(new FTimersViewColumn + ( + Index++, + FTimersViewColumns::AverageInclusiveTimeColumnID, + TEXT("avginc"), + LOCTEXT("AvgInclusiveTimeName", "I.Avg"), + LOCTEXT("AvgInclusiveTimeTitle", "Average Inclusive Time (ms)"), + LOCTEXT("AvgInclusiveTimeDesc", "Average inclusive duration of selected timer's instances, in milliseconds"), + false, + AggregatedStatsColumnFlags, + HAlign_Right, + TimeMsColumnInitialWidth, TimeMsColumnMinWidth, TimeMsColumnMaxWidth, + [](const FTimersViewColumn& Column, const FTimerNode& TimerNode) -> FText + { return FText::FromString(TimeUtils::FormatTimeMs(TimerNode.GetAggregatedStats().AverageInclusiveTime)); } + )); + + Collection.Add(new FTimersViewColumn + ( + Index++, + FTimersViewColumns::MedianInclusiveTimeColumnID, + TEXT("medinc"), + LOCTEXT("MedInclusiveTimeName", "I.Med"), + LOCTEXT("MedInclusiveTimeTitle", "Median Inclusive Time (ms)"), + LOCTEXT("MedInclusiveTimeDesc", "Median inclusive duration of selected timer's instances, in milliseconds"), + false, + AggregatedStatsColumnFlags, + HAlign_Right, + TimeMsColumnInitialWidth, TimeMsColumnMinWidth, TimeMsColumnMaxWidth, + [](const FTimersViewColumn& Column, const FTimerNode& TimerNode) -> FText + { return FText::FromString(TimeUtils::FormatTimeMs(TimerNode.GetAggregatedStats().MedianInclusiveTime)); } + )); + + Collection.Add(new FTimersViewColumn + ( + Index++, + FTimersViewColumns::MinInclusiveTimeColumnID, + TEXT("mininc"), + LOCTEXT("MinInclusiveTimeName", "I.Min"), + LOCTEXT("MinInclusiveTimeTitle", "Min Inclusive Time (ms)"), + LOCTEXT("MinInclusiveTimeDesc", "Minimum inclusive duration of selected timer's instances, in milliseconds"), + false, + AggregatedStatsColumnFlags, + HAlign_Right, + TimeMsColumnInitialWidth, TimeMsColumnMinWidth, TimeMsColumnMaxWidth, + [](const FTimersViewColumn& Column, const FTimerNode& TimerNode) -> FText + { return FText::FromString(TimeUtils::FormatTimeMs(TimerNode.GetAggregatedStats().MinInclusiveTime)); } + )); + + ////////////////////////////////////////////////// + // Exclusive Time stats + + Collection.Add(new FTimersViewColumn + ( + Index++, + FTimersViewColumns::TotalExclusiveTimeColumnID, + TEXT("exc"), + LOCTEXT("TotalExclusiveTimeName", "Excl"), + LOCTEXT("TotalExclusiveTimeTitle", "Total Exclusive Time"), + LOCTEXT("TotalExclusiveTimeDesc", "Total exclusive duration of selected timer's instances"), + true, + AggregatedStatsColumnFlags, + HAlign_Right, + TotalTimeColumnInitialWidth, TotalTimeColumnMinWidth, TotalTimeColumnMaxWidth, + [](const FTimersViewColumn& Column, const FTimerNode& TimerNode) -> FText + { return FText::FromString(TimeUtils::FormatTimeAuto(TimerNode.GetAggregatedStats().TotalExclusiveTime)); } + )); + + Collection.Add(new FTimersViewColumn + ( + Index++, + FTimersViewColumns::MaxExclusiveTimeColumnID, + TEXT("maxexc"), + LOCTEXT("MaxExclusiveTimeName", "E.Max"), + LOCTEXT("MaxExclusiveTimeTitle", "Max Exclusive Time (ms)"), + LOCTEXT("MaxExclusiveTimeDesc", "Maximum exclusive duration of selected timer's instances, in milliseconds"), + false, + AggregatedStatsColumnFlags, + HAlign_Right, + TimeMsColumnInitialWidth, TimeMsColumnMinWidth, TimeMsColumnMaxWidth, + [](const FTimersViewColumn& Column, const FTimerNode& TimerNode) -> FText + { return FText::FromString(TimeUtils::FormatTimeMs(TimerNode.GetAggregatedStats().MaxExclusiveTime)); } + )); + + Collection.Add(new FTimersViewColumn + ( + Index++, + FTimersViewColumns::AverageExclusiveTimeColumnID, + TEXT("avgexc"), + LOCTEXT("AvgExclusiveTimeName", "E.Avg"), + LOCTEXT("AvgExclusiveTimeTitle", "Average Exclusive Time (ms)"), + LOCTEXT("AvgExclusiveTimeDesc", "Average exclusive duration of selected timer's instances, in milliseconds"), + false, + AggregatedStatsColumnFlags, + HAlign_Right, + TimeMsColumnInitialWidth, TimeMsColumnMinWidth, TimeMsColumnMaxWidth, + [](const FTimersViewColumn& Column, const FTimerNode& TimerNode) -> FText + { return FText::FromString(TimeUtils::FormatTimeMs(TimerNode.GetAggregatedStats().AverageExclusiveTime)); } + )); + + Collection.Add(new FTimersViewColumn + ( + Index++, + FTimersViewColumns::MedianExclusiveTimeColumnID, + TEXT("medexc"), + LOCTEXT("MedExclusiveTimeName", "E.Med"), + LOCTEXT("MedExclusiveTimeTitle", "Median Exclusive Time (ms)"), + LOCTEXT("MedExclusiveTimeDesc", "Median exclusive duration of timer's instances, in milliseconds"), + false, + AggregatedStatsColumnFlags, + HAlign_Right, + TimeMsColumnInitialWidth, TimeMsColumnMinWidth, TimeMsColumnMaxWidth, + [](const FTimersViewColumn& Column, const FTimerNode& TimerNode) -> FText + { return FText::FromString(TimeUtils::FormatTimeMs(TimerNode.GetAggregatedStats().MedianExclusiveTime)); } + )); + + Collection.Add(new FTimersViewColumn + ( + Index++, + FTimersViewColumns::MinExclusiveTimeColumnID, + TEXT("minexc"), + LOCTEXT("MinExclusiveTimeName", "E.Min"), + LOCTEXT("MinExclusiveTimeTitle", "Min Exclusive Time (ms)"), + LOCTEXT("MinExclusiveTimeDesc", "Minimum exclusive duration of selected timer's instances, in milliseconds"), + false, + AggregatedStatsColumnFlags, + HAlign_Right, + TimeMsColumnInitialWidth, TimeMsColumnMinWidth, TimeMsColumnMaxWidth, + [](const FTimersViewColumn& Column, const FTimerNode& TimerNode) -> FText + { return FText::FromString(TimeUtils::FormatTimeMs(TimerNode.GetAggregatedStats().MinExclusiveTime)); } + )); + + ////////////////////////////////////////////////// + + for (int32 MapIndex = 0; MapIndex < Collection.Num(); MapIndex++) + { + ColumnIdToPtrMapping.Add(Collection[MapIndex]->Id, Collection[MapIndex]); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FTimersViewColumnFactory::~FTimersViewColumnFactory() +{ + for (const FTimersViewColumn* Column : Collection) + { + delete Column; + } + Collection.Reset(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FTimersViewColumnFactory& FTimersViewColumnFactory::Get() +{ + static FTimersViewColumnFactory Instance; + return Instance; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimersViewColumnFactory.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimersViewColumnFactory.h new file mode 100644 index 000000000000..0c9fe76c0ba7 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimersViewColumnFactory.h @@ -0,0 +1,62 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "Insights/ViewModels/TimersViewColumn.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct FTimersViewColumns +{ + ////////////////////////////////////////////////// + // Column identifiers + + static const FName NameColumnID; + static const FName MetaGroupNameColumnID; + static const FName TypeColumnID; + static const FName InstanceCountColumnID; + + // Inclusive Time columns + static const FName TotalInclusiveTimeColumnID; + static const FName MaxInclusiveTimeColumnID; + static const FName UpperQuartileInclusiveTimeColumnID; + static const FName AverageInclusiveTimeColumnID; + static const FName MedianInclusiveTimeColumnID; + static const FName LowerQuartileInclusiveTimeColumnID; + static const FName MinInclusiveTimeColumnID; + + // Exclusive Time columns + static const FName TotalExclusiveTimeColumnID; + static const FName MaxExclusiveTimeColumnID; + static const FName UpperQuartileExclusiveTimeColumnID; + static const FName AverageExclusiveTimeColumnID; + static const FName MedianExclusiveTimeColumnID; + static const FName LowerQuartileExclusiveTimeColumnID; + static const FName MinExclusiveTimeColumnID; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct FTimersViewColumnFactory +{ +private: + /** Default constructor. */ + FTimersViewColumnFactory(); + + /** Destructor. */ + ~FTimersViewColumnFactory(); + +public: + /** Contains basic information about columns used in the Timers view widget. Names should be localized. */ + TArray Collection; + + /** Mapping between column IDs and FTimersViewColumn pointers. */ + TMap ColumnIdToPtrMapping; + + /** Singleton */ + static const FTimersViewColumnFactory& Get(); +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingEvent.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingEvent.h new file mode 100644 index 000000000000..f348ce6cef88 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingEvent.h @@ -0,0 +1,57 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "TraceServices/Model/LoadTimeProfiler.h" + + +// Insights +#include "Insights/ViewModels/TimerNode.h" + +class FTimingEventsTrack; + +struct FTimingEvent +{ + const FTimingEventsTrack* Track; + uint64 TypeId; + uint32 Depth; + double StartTime; + double EndTime; + double ExclusiveTime; + Trace::FLoadTimeProfilerCpuEvent LoadingInfo; + + void Reset() + { + Track = nullptr; + TypeId = FTimerNode::InvalidId; + Depth = 0; + StartTime = 0.0; + EndTime = 0.0; + ExclusiveTime = 0.0; + LoadingInfo.Package = nullptr; + LoadingInfo.Export = nullptr; + LoadingInfo.PackageEventType = LoadTimeProfilerPackageEventType_None; + LoadingInfo.ExportEventType = LoadTimeProfilerObjectEventType_None; + } + + bool IsValidTrack() const { return Track != nullptr; } + bool IsValid() const { return Track != nullptr && TypeId != FTimerNode::InvalidId; } + + double Duration() const { return EndTime - StartTime; } + + bool Equals(const FTimingEvent& Other) const + { + return Track == Other.Track + && TypeId == Other.TypeId + && Depth == Other.Depth + && StartTime == Other.StartTime + && EndTime == Other.EndTime; + //&& ExclusiveTime == Other.ExclusiveTime; + } + + static bool AreEquals(const FTimingEvent& A, const FTimingEvent& B) + { + return A.Equals(B); + } +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingEventsTrack.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingEventsTrack.cpp new file mode 100644 index 000000000000..4f1a314068f8 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingEventsTrack.cpp @@ -0,0 +1,132 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TimingEventsTrack.h" + +#define LOCTEXT_NAMESPACE "TimingEventsTrack" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FTimingEventsTrackLayout +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingEventsTrackLayout::ForceNormalMode() +{ + bIsCompactMode = false; + EventH = NormalLayoutEventH; + EventDY = NormalLayoutEventDY; + TimelineDY = NormalLayoutTimelineDY; + MinTimelineH = NormalLayoutMinTimelineH; + TargetMinTimelineH = NormalLayoutMinTimelineH; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingEventsTrackLayout::ForceCompactMode() +{ + bIsCompactMode = true; + EventH = CompactLayoutEventH; + EventDY = CompactLayoutEventDY; + TimelineDY = CompactLayoutTimelineDY; + MinTimelineH = CompactLayoutMinTimelineH; + TargetMinTimelineH = CompactLayoutMinTimelineH; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingEventsTrackLayout::Update() +{ + constexpr float LayoutTransitionSpeed = 0.25f; + + if (bIsCompactMode) + { + if (EventH > CompactLayoutEventH) + { + EventH -= LayoutTransitionSpeed; + } + if (EventDY > CompactLayoutEventDY) + { + EventDY -= LayoutTransitionSpeed; + } + if (TimelineDY > CompactLayoutTimelineDY) + { + TimelineDY -= LayoutTransitionSpeed; + } + } + else + { + if (EventH < NormalLayoutEventH) + { + EventH += LayoutTransitionSpeed; + } + if (EventDY < NormalLayoutEventDY) + { + EventDY += LayoutTransitionSpeed; + } + if (TimelineDY < NormalLayoutTimelineDY) + { + TimelineDY += LayoutTransitionSpeed; + } + } + + if (MinTimelineH > TargetMinTimelineH) + { + MinTimelineH -= LayoutTransitionSpeed; + } + else if (MinTimelineH < TargetMinTimelineH) + { + MinTimelineH += LayoutTransitionSpeed; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FTimingEventsTrack +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FTimingEventsTrack::FTimingEventsTrack(uint64 InTrackId, ETimingEventsTrackType InType, const FString& InName, const TCHAR* InGroupName) + : FBaseTimingTrack(InTrackId) + , Type(InType) + , Name(InName) + , GroupName(InGroupName) + , ThreadId(0) + , Order(0) + , Depth(-1) + , bIsCollapsed(false) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FTimingEventsTrack::~FTimingEventsTrack() +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingEventsTrack::Reset() +{ + FBaseTimingTrack::Reset(); + + Depth = -1; + bIsCollapsed = false; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingEventsTrack::UpdateHoveredState(float MouseX, float MouseY, const FTimingTrackViewport& Viewport) +{ + constexpr float HeaderWidth = 100.0f; + constexpr float HeaderHeight = 14.0f; + + if (MouseY >= GetPosY() && MouseY < GetPosY() + GetHeight()) + { + SetHoveredState(true); + SetHeaderHoveredState(MouseX < HeaderWidth && MouseY < GetPosY() + HeaderHeight); + } + else + { + SetHoveredState(false); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingEventsTrack.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingEventsTrack.h new file mode 100644 index 000000000000..9ffeb4b099c1 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingEventsTrack.h @@ -0,0 +1,93 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "Insights/ViewModels/BaseTimingTrack.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +static const float RealMinTimelineH = 13.0f; + +static const float NormalLayoutEventH = 14.0f; +static const float NormalLayoutEventDY = 2.0f; +static const float NormalLayoutTimelineDY = 14.0f; +static const float NormalLayoutMinTimelineH = 0.0f; + +static const float CompactLayoutEventH = 2.0f; +static const float CompactLayoutEventDY = 1.0f; +static const float CompactLayoutTimelineDY = 3.0f; +static const float CompactLayoutMinTimelineH = 0.0f; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct FTimingEventsTrackLayout +{ + bool bIsCompactMode; + + float EventH; // height of a timing event, in Slate units + float EventDY; // vertical distance between two timing event sub-tracks, in Slate units + float TimelineDY; // space at top and bottom of each timeline, in Slate units + float MinTimelineH; + float TargetMinTimelineH; + + float GetLaneY(uint32 Depth) const { return 1.0f + TimelineDY + Depth * (EventDY + EventH); } + + void ForceNormalMode(); + void ForceCompactMode(); + void Update(); +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +enum class ETimingEventsTrackType +{ + Cpu, + Gpu, + Loading, + Io, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FTimingEventsTrack : public FBaseTimingTrack +{ +public: + FTimingEventsTrack(uint64 InTrackId, ETimingEventsTrackType InType, const FString& InName, const TCHAR* InGroupName); + virtual ~FTimingEventsTrack(); + + ETimingEventsTrackType GetType() const { return Type; } + const FString& GetName() const { return Name; } + + const TCHAR* GetGroupName() const { return GroupName; }; + + void SetThreadId(uint32 InThreadId) { ThreadId = InThreadId; } + uint32 GetThreadId() const { return ThreadId; } + + void SetOrder(int32 InOrder) { Order = InOrder; } + int32 GetOrder() const { return Order; } + + int32 GetDepth() const { return Depth; } + int32 GetNumLanes() const { return Depth + 1; } + + virtual void Reset() override; + virtual void UpdateHoveredState(float MX, float MY, const FTimingTrackViewport& Viewport); + +public: + ETimingEventsTrackType Type; + FString Name; + const TCHAR* GroupName; + uint32 ThreadId; + int32 Order; + int32 Depth; // number of lanes == Depth + 1 + bool bIsCollapsed; + + // Cached OnPaint state. + //TArray Boxes; + //TArray MergedBorderBoxes; + //TArray Borders; + //TArray Texts; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingTrackViewport.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingTrackViewport.cpp new file mode 100644 index 000000000000..2d64899b026f --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingTrackViewport.cpp @@ -0,0 +1,146 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TimingTrackViewport.h" + +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool FTimingTrackViewport::ScrollAtTime(const double Time) +{ + const double NewStartTime = AlignTimeToPixel(Time); + if (NewStartTime != StartTime) + { + StartTime = NewStartTime; + EndTime = SlateUnitsToTime(Width); + return true; + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool FTimingTrackViewport::CenterOnTimeInterval(const double Time, const double Duration) +{ + double NewStartTime = Time; + const double ViewportDuration = static_cast(Width) / ScaleX; + if (Duration < ViewportDuration) + { + NewStartTime -= (ViewportDuration - Duration) / 2.0; + } + return ScrollAtTime(NewStartTime); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool FTimingTrackViewport::ZoomOnTimeInterval(const double Time, const double Duration) +{ + const double NewScaleX = FMath::Clamp(static_cast(Width) / Duration, MinScaleX, MaxScaleX); + double NewStartTime = Time; + const double NewViewportDuration = static_cast(Width) / NewScaleX; + if (Duration < NewViewportDuration) + { + NewStartTime -= (NewViewportDuration - Duration) / 2; + } + NewStartTime = AlignTimeToPixel(NewStartTime, NewScaleX); + if (NewStartTime != StartTime || NewScaleX != ScaleX) + { + StartTime = NewStartTime; + ScaleX = NewScaleX; + EndTime = SlateUnitsToTime(Width); + return true; + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool FTimingTrackViewport::ZoomWithFixedX(const double NewScaleX, const float X) +{ + const double LocalNewScaleX = FMath::Clamp(NewScaleX, MinScaleX, MaxScaleX); + if (LocalNewScaleX != ScaleX) + { + // Time at local position X should remain the same. So we resolve equation: + // StartTime + X / ScaleX == NewStartTime + X / NewScaleX + // ==> NewStartTime = StartTime + X / ScaleX - X / NewScaleX + // ==> NewStartTime = StartTime + X * (1 / ScaleX - 1 / NewScaleX) + StartTime += static_cast(X) * (1.0 / ScaleX - 1.0 / LocalNewScaleX); + ScaleX = LocalNewScaleX; + EndTime = SlateUnitsToTime(Width); + return true; + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +double FTimingTrackViewport::RestrictEndTime(const double InEndTime) const +{ + if (InEndTime == DBL_MAX || InEndTime == std::numeric_limits::infinity()) + { + return MaxValidTime; + } + else + { + return InEndTime; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +double FTimingTrackViewport::RestrictDuration(const double InStartTime, const double InEndTime) const +{ + if (InEndTime == DBL_MAX || InEndTime == std::numeric_limits::infinity()) + { + return MaxValidTime - InStartTime; + } + else + { + return InEndTime - InStartTime; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingTrackViewport::GetHorizontalScrollLimits(double& OutMinT, double& OutMaxT) +{ + const double ViewportDuration = static_cast(Width) / ScaleX; + if (MaxValidTime < ViewportDuration) + { + OutMinT = MaxValidTime - ViewportDuration; + OutMaxT = MinValidTime; + } + else + { + OutMinT = MinValidTime - 0.25 * ViewportDuration; + OutMaxT = MaxValidTime - 0.75 * ViewportDuration; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool FTimingTrackViewport::EnforceHorizontalScrollLimits(const double U) +{ + double MinT, MaxT; + GetHorizontalScrollLimits(MinT, MaxT); + + double NewStartTime = StartTime; + if (NewStartTime < MinT) + { + NewStartTime = (1.0 - U) * NewStartTime + U * MinT; + if (FMath::IsNearlyEqual(NewStartTime, MinT, 1.0 / ScaleX)) + NewStartTime = MinT; + } + else if (NewStartTime > MaxT) + { + NewStartTime = (1.0 - U) * NewStartTime + U * MaxT; + if (FMath::IsNearlyEqual(NewStartTime, MaxT, 1.0 / ScaleX)) + NewStartTime = MaxT; + if (NewStartTime < MinT) + NewStartTime = MinT; + } + + return ScrollAtTime(NewStartTime); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingTrackViewport.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingTrackViewport.h new file mode 100644 index 000000000000..dd0183fe5f69 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingTrackViewport.h @@ -0,0 +1,109 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +class FTimingTrackViewport +{ +public: + float Width; // width of viewport, in Slate units + float Height; // height of viewport, in Slate units + + double MinValidTime; // min session time, in seconds + double MaxValidTime; // max session time, in seconds + double StartTime; // time of viewport's left side, in seconds + double EndTime; // time of viewport's right side, in seconds; computed when StartTime or Width changes + double MinScaleX; + double MaxScaleX; + double ScaleX; // scale factor between seconds and pixels (Slate units), in pixels (Slate units) per second + + float TopOffset; // top offset (to allow the time ruller to be visible), in pixels (Slate units) + float ScrollHeight; // height of the vertical scrollable area, in pixels (Slate units) + float ScrollPosY; // current vertical scroll position, in pixels (Slate units) + +public: + FTimingTrackViewport() + { + Reset(); + } + + void Reset() + { + Width = 0.0f; + Height = 0.0f; + + MinValidTime = 0.0; + MaxValidTime = 0.0; + StartTime = 0.0; + EndTime = 0.0; + MinScaleX = (5 * 20) / 3600.0; // 1h between major tick marks + MaxScaleX = 1.0E10; // 10ns between major tick marks + ScaleX = (5 * 20) / 5.0; // 5s between major tick marks + + TopOffset = 0.0f; + ScrollHeight = 1.0f; + ScrollPosY = 0.0f; + } + + float TimeToSlateUnits(const double Time) const + { + return static_cast((Time - StartTime) * ScaleX); + } + + float TimeToSlateUnitsRounded(const double Time) const + { + return static_cast(FMath::RoundToDouble((Time - StartTime) * ScaleX)); + } + + float GetViewportDXForDuration(const double DT) const + { + return static_cast(DT * ScaleX); + } + + double GetDurationForViewportDX(const float DX) const + { + return static_cast(DX) / ScaleX; + } + + double SlateUnitsToTime(const float X) const + { + return StartTime + static_cast(X) / ScaleX; + } + + bool UpdateSize(const float InWidth, const float InHeight) + { + if (Width != InWidth || Height != InHeight) + { + Width = InWidth; + Height = InHeight; + EndTime = SlateUnitsToTime(Width); + return true; + } + return false; + } + + double AlignTimeToPixel(const double InTime, const double InScaleX) const + { + return FMath::RoundToDouble(InTime * InScaleX) / InScaleX; + } + + double AlignTimeToPixel(const double Time) const + { + return AlignTimeToPixel(Time, ScaleX); + } + + bool ScrollAtTime(const double Time); + bool CenterOnTimeInterval(const double Time, const double Duration); + + bool ZoomOnTimeInterval(const double Time, const double Duration); + bool ZoomWithFixedX(const double NewScaleX, const float X); + + double RestrictEndTime(const double InEndTime) const; + double RestrictDuration(const double InStartTime, const double InEndTime) const; + + void GetHorizontalScrollLimits(double& OutMinT, double& OutMaxT); + bool EnforceHorizontalScrollLimits(const double U); + + float GetViewportY(const float Y) const { return TopOffset + Y - ScrollPosY; } +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingViewDrawHelper.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingViewDrawHelper.cpp new file mode 100644 index 000000000000..24c9a78731c1 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingViewDrawHelper.cpp @@ -0,0 +1,571 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TimingViewDrawHelper.h" + +#include "Brushes/SlateBorderBrush.h" +#include "Brushes/SlateColorBrush.h" +#include "EditorStyleSet.h" +#include "Fonts/FontMeasure.h" +#include "Fonts/SlateFontInfo.h" +#include "Framework/Application/SlateApplication.h" +#include "Rendering/DrawElements.h" +#include "Styling/CoreStyle.h" +#include "TraceServices/AnalysisService.h" + +// Insights +#include "Insights/Common/PaintUtils.h" +#include "Insights/Common/TimeUtils.h" +#include "Insights/ViewModels/MarkersTimingTrack.h" +#include "Insights/ViewModels/TimingEventsTrack.h" +#include "Insights/ViewModels/TimingTrackViewport.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FTimingViewDrawHelper::FTimingViewDrawHelper(const FDrawContext& InDC, const FTimingTrackViewport& InViewport, const FTimingEventsTrackLayout& InLayout) + : DrawContext(InDC) + , Viewport(InViewport) + , Layout(InLayout) + , WhiteBrush(FCoreStyle::Get().GetBrush("WhiteBrush")) + , BorderBrush(FEditorStyle::GetBrush("PlainBorder")) + , EventsBorderBrush(new FSlateBorderBrush(NAME_None, FMargin(1.0f))) + , BackgroundAreaBrush(WhiteBrush) + , ValidAreaColor(0.07f, 0.07f, 0.07f, 1.0f) + , InvalidAreaColor(0.1f, 0.07f, 0.07f, 1.0f) + , EventFont(FCoreStyle::GetDefaultFontStyle("Regular", 8)) + , Stats() +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FTimingViewDrawHelper::~FTimingViewDrawHelper() +{ + delete EventsBorderBrush; //im: is it safe to delete brush (i.e in OnPaint, imediately after use)? +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingViewDrawHelper::DrawBackground() const +{ + const float X0 = Viewport.TimeToSlateUnitsRounded(0.0); + const float X1 = Viewport.TimeToSlateUnitsRounded(Viewport.MaxValidTime); + const float W = FMath::CeilToFloat(Viewport.Width); + const float H = FMath::CeilToFloat(Viewport.Height); + + if (X0 >= W || X1 <= 0.0f) + { + ValidX0 = 0.0f; + ValidX1 = W; + + // Draw invalid area (entire view). + DrawContext.DrawBox(0.0f, 0.0f, W, H, BackgroundAreaBrush, InvalidAreaColor); + } + else // X0 < W && X1 > 0 + { + if (X0 > 0.0f) + { + // Draw invalid area (left). + DrawContext.DrawBox(0.0f, 0.0f, X0, H, BackgroundAreaBrush, InvalidAreaColor); + } + + if (X1 < W) + { + // Draw invalid area (right). + DrawContext.DrawBox(X1, 0.0f, W - X1, H, BackgroundAreaBrush, InvalidAreaColor); + } + + ValidX0 = FMath::Max(X0, 0.0f); + ValidX1 = FMath::Min(X1, W); + + if (ValidX1 > ValidX0) + { + // Draw valid area. + DrawContext.DrawBox(ValidX0, 0.0f, ValidX1 - ValidX0, H, BackgroundAreaBrush, ValidAreaColor); + } + } + + DrawContext.LayerId++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingViewDrawHelper::BeginTimelines() +{ + TimelineIndex = -1; + TimelineTopY = -Viewport.ScrollPosY; + + float Y = TimelineTopY + Viewport.TopOffset; + + if (Y > 0.0f && ValidX1 > ValidX0) + { + Y = FMath::Min(Y, Viewport.Height); + + // Draw invalid area (top). + DrawContext.DrawBox(DrawContext.LayerId + ToInt32(EDrawLayer::TimelineHeader), ValidX0, 0.0f, ValidX1 - ValidX0, Y, BackgroundAreaBrush, InvalidAreaColor); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool FTimingViewDrawHelper::BeginTimeline(FTimingEventsTrack& Track) +{ + TimelineIndex++; + + Track.SetPosY(TimelineTopY + Viewport.ScrollPosY); + + if (Track.GetHeight() < Layout.MinTimelineH) + Track.SetHeight(Layout.MinTimelineH); + + if (TimelineTopY + Track.GetHeight() < -1.0f) + { + TimelineTopY += Track.GetHeight(); + return false; + } + + if (TimelineTopY > Viewport.Height - Viewport.TopOffset) + { + return false; + } + + MaxDepth = -1; + + // +1.0f is for horizontal line between timelines + TimelineY = Viewport.TopOffset + TimelineTopY + 1.0f + Layout.TimelineDY; + + // Reset "last event X2" for all depths. + LastEventX2.Reset(); + + // Reset "last box" for all depths. + LastBox.Reset(); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingViewDrawHelper::EndTimeline(FTimingEventsTrack& Track) +{ + // Flush merged boxes. + for (int32 Depth = 0; Depth <= MaxDepth; ++Depth) + { + const FBoxData& Box = LastBox[Depth]; + if (Box.X1 < Box.X2) + { + const float EventY = TimelineY + (Layout.EventH + Layout.EventDY) * Depth; + DrawBox(Box, EventY, Layout.EventH); + } + } + + Track.Depth = MaxDepth; + + float NewH; + if (MaxDepth < 0) + { + NewH = Layout.MinTimelineH; + } + else //if (MaxDepth >= 0) + { + // 1.0f is for horizontal line between timelines + NewH = 1.0f + Layout.TimelineDY + Layout.EventH * (MaxDepth + 1) + Layout.EventDY * MaxDepth + Layout.TimelineDY; + + if (NewH < RealMinTimelineH) + { + NewH = RealMinTimelineH; + } + } + + if (Track.GetHeight() < NewH) + { + float NewTrackHeight; + if (Layout.bIsCompactMode) + { + NewTrackHeight = FMath::CeilToFloat(Track.GetHeight() * 0.5f + NewH * 0.5f); + } + else + { + NewTrackHeight = FMath::CeilToFloat(Track.GetHeight() * 0.9f + NewH * 0.1f); + } + Track.SetHeight(NewTrackHeight); + } + else if (Track.GetHeight() > NewH) + { + float NewTrackHeight; + if (Layout.bIsCompactMode) + { + NewTrackHeight = FMath::FloorToFloat(Track.GetHeight() * 0.5f + NewH * 0.5f); + } + else + { + NewTrackHeight = FMath::FloorToFloat(Track.GetHeight() * 0.9f + NewH * 0.1f); + } + Track.SetHeight(NewTrackHeight); + } + + if (Track.GetHeight() > 0) + { + const float Y = Viewport.TopOffset + TimelineTopY; + + FLinearColor Color(0.05f, 0.05f, 0.05f, 1.0f); + FLinearColor TextColor(1.0f, 1.0f, 1.0f, 1.0f); + + if (Track.IsSelected()) + { + if (Track.IsHovered()) + { + TextColor = FLinearColor(1.0f, 1.0f, 0.0f, 1.0f); + } + else + { + TextColor = FLinearColor(1.0f, 1.0f, 0.5f, 1.0f); + } + } + else if (Track.IsHovered()) + { + TextColor = FLinearColor(1.0f, 1.0f, 0.0f, 1.0f); + } + + // Draw a horizontal line between timelines. + DrawContext.DrawBox(DrawContext.LayerId + ToInt32(EDrawLayer::TimelineHeader), 0, Y, Viewport.Width, 1.0f, WhiteBrush, Color); + + // Draw name of timeline. + //const FString Name = FString::Printf(TEXT("%d Y: %g, H: %g, VO: %g, VY: %g"), TimelineIndex, Track.Y, Track.H, Viewport.TopOffset, Viewport.ScrollPosY); //debug + const TSharedRef FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService(); + float NameWidth = FontMeasureService->Measure(Track.GetName(), EventFont).X; + DrawContext.DrawBox(DrawContext.LayerId + ToInt32(EDrawLayer::TimelineHeader), 0.0f, Y + 1.0f, NameWidth + 4.0f, 12.0f, WhiteBrush, Color); + DrawContext.DrawText(DrawContext.LayerId + ToInt32(EDrawLayer::TimelineText), 2.0f, Y, Track.GetName(), EventFont, TextColor); + } + + TimelineTopY += Track.GetHeight(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingViewDrawHelper::AddEvent(double EventStartTime, double EventEndTime, uint32 EventDepth, const TCHAR* EventName, uint32 Color) +{ + Stats.NumEvents++; + + float EventX1 = Viewport.TimeToSlateUnitsRounded(EventStartTime); + if (EventX1 > Viewport.Width) + { + return; + } + + double RestrictedEndTime = Viewport.RestrictEndTime(EventEndTime); + float EventX2 = Viewport.TimeToSlateUnitsRounded(RestrictedEndTime); + if (EventX2 < 0) + { + return; + } + + // Timing events are displayed with minimum 1px (including empty ones). + if (EventX1 == EventX2) + { + EventX2 = EventX1 + 1.0f; + } + + const int32 Depth = static_cast(EventDepth); + if (Depth > MaxDepth) + { + MaxDepth = Depth; + } + + // Ensure we have enough slots in array. See LastBox[Depth] usage. + if (LastBox.Num() <= Depth) + { + LastBox.AddDefaulted(Depth + 1 - LastBox.Num()); + } + + float EventY = TimelineY + (Layout.EventH + Layout.EventDY) * Depth; + if (EventY < -Layout.EventH || EventY > Viewport.Height) + { + return; + } + + // Ensure we have enough slots in array. See LastEventX2[Depth] usage. + while (LastEventX2.Num() <= Depth) + { + LastEventX2.Add(-1.0f); + } + + // Limit event width on the viewport's left side. + // This also makes the text to be displayed in viewport, for very long events. + const float MinX = -2.0f; // -2 allows event border to remain outside screen + if (EventX1 < MinX) + { + EventX1 = MinX; + } + + // Limit event width on the viewport's right side. + const float MaxX = Viewport.Width + 2.0f; // +2 allows event border to remain outside screen + if (EventX2 > MaxX) + { + EventX2 = MaxX; + } + + float EventW = EventX2 - EventX1; + + // Optimization... + if (EventW == 1.0f && EventX1 == LastEventX2[Depth] - 1.0f) + { + // Do no draw 1 pixel event if the last event was ended on that pixel. + return; + } + + ////////////////////////////////////////////////// + // Coloring + + FLinearColor EventColorFill; + + //im:TODO: EventColorFill = GetEventColorFn(Event); + if (Color == 0) + { + uint32 NameHash = 0; + for (const TCHAR* c = EventName; *c; ++c) + { + NameHash = (NameHash + *c) * 0x2c2c57ed; + } + + Color = NameHash | 0xFF000000; + + // Increase brightness. + //Color = ((NameHash | ((NameHash & 0x00808080) >> 1) | ((NameHash & 0x00808080) >> 2) | ((NameHash & 0x00808080) >> 3)) << 1) | 0xFF000000; + + // Increase brightness. + //EventColorFill.R = ((NameHash >> 16) & 0xFF) / 128.0f; + //EventColorFill.G = ((NameHash >> 8) & 0xFF) / 128.0f; + //EventColorFill.B = (NameHash & 0xFF) / 128.0f; + //EventColorFill.A = 1.0f; + } + //else + { + EventColorFill.R = ((Color >> 16) & 0xFF) / 255.0f; + EventColorFill.G = ((Color >> 8) & 0xFF) / 255.0f; + EventColorFill.B = ((Color ) & 0xFF) / 255.0f; + EventColorFill.A = ((Color >> 24) & 0xFF) / 255.0f; + } + + // Maps hash to color directly. + //EventColorFill.R = ((Color >> 16) & 0xFF) / 255.0f; + //EventColorFill.G = ((Color >> 8) & 0xFF) / 255.0f; + //EventColorFill.B = (Color & 0xFF) / 255.0f; + //EventColorFill.A = 1.0f; + + // Maps hash to color (sRGB to linear). + //const FColor ColorFill(Color); + //EventColorFill = ColorFill; + + // Invereted color. + //EventColorFill.R = 1.0f - ((Color >> 16) & 0xFF) / 255.0f; + //EventColorFill.G = 1.0f - ((Color >> 8) & 0xFF) / 255.0f; + //EventColorFill.B = 1.0f - (Color & 0xFF) / 255.0f; + //EventColorFill.A = 1.0f; + + // Bright color. + //EventColorFill.R = ((Color >> 16) & 0xFF) / 128.0f; + //EventColorFill.G = ((Color >> 8) & 0xFF) / 128.0f; + //EventColorFill.B = (Color & 0xFF) / 128.0f; + //EventColorFill.A = 1.0f; + + // Bright color (adjusted R, G, B). + //const float BrightnessFactor = 0.59f / 255.0f; // [0 .. 0.59f / 255.0f] + //EventColorFill.R = ((Color >> 16) & 0xFF) * (BrightnessFactor / 0.30f); + //EventColorFill.G = ((Color >> 8) & 0xFF) * (BrightnessFactor / 0.59f); + //EventColorFill.B = ((Color ) & 0xFF) * (BrightnessFactor / 0.11f); + //EventColorFill.A = 1.0f; + + // Bright desaturated color. + //const float MinL = 0.3f; + //const float MulL = (1.0f - MinL) / 255.0f; + //EventColorFill.R = MinL + ((Color >> 16) & 0xFF) * MulL; + //EventColorFill.G = MinL + ((Color >> 8) & 0xFF) * MulL; + //EventColorFill.B = MinL + (Color & 0xFF) * MulL; + //EventColorFill.A = 1.0f; + + // Dark desaturated color. + //const float MaxL = 0.5f; + //const float MulL = MaxL / 255.0f; + //EventColorFill.R = (((Color >> 16) & 0xFF) * MulL; + //EventColorFill.G = ((Color >> 8) & 0xFF) * MulL; + //EventColorFill.B = (Color & 0xFF) * MulL; + //EventColorFill.A = 1.0f; + + //constexpr float BorderColorFactor = 0.75f; // darker border + constexpr float BorderColorFactor = 1.25f; // brighter border + + ////////////////////////////////////////////////// + + if (EventW > 2.0f && Layout.EventH > 2.0f) + { + // Fill inside of the timing event box. + DrawContext.DrawBox(DrawContext.LayerId + ToInt32(EDrawLayer::EventFill), EventX1 + 1.0f, EventY + 1.0f, EventW - 2.0f, Layout.EventH - 2.0f, WhiteBrush, EventColorFill); + Stats.NumDrawBoxes++; + } + + // Save X2, for current depth. + LastEventX2[Depth] = EventX2; + + // Draw border around the timing event box. + if (EventW > 2.0f) + { + FBoxData& Box = LastBox[Depth]; + if (Box.X1 < Box.X2) + { + DrawBox(Box, EventY, Layout.EventH); + Box.Reset(); + } + + const FLinearColor EventColorBorder(EventColorFill.R * BorderColorFactor, EventColorFill.G * BorderColorFactor, EventColorFill.B * BorderColorFactor, 1.0f); + DrawContext.DrawBox(DrawContext.LayerId + ToInt32(EDrawLayer::EventBorder), EventX1, EventY, EventW, Layout.EventH, EventsBorderBrush, EventColorBorder); + Stats.NumDrawBorders++; + } + else // 1px or 2px boxes + { + FBoxData& Box = LastBox[Depth]; + + // Check if we can merge this box with previous one, if any. + // Note: We are assuming events are processed in sorted order by X1. + if (Color == Box.Color && // same color + EventX1 <= Box.X2) // overlapping or adjacent + { + // Merge it with previous box. + Box.X2 = EventX2; + Stats.NumMergedBoxes++; + } + else + { + // Flush previous box, if any. + if (Box.X1 < Box.X2) + { + DrawBox(Box, EventY, Layout.EventH); + } + + // Start new "merge box". + Box.X1 = EventX1; + Box.X2 = EventX2; + Box.Color = Color; + Box.LinearColor = FLinearColor(EventColorFill.R * BorderColorFactor, EventColorFill.G * BorderColorFactor, EventColorFill.B * BorderColorFactor, 1.0f); + } + } + + // Draw the name of the timing event. + if (EventW > 8.0f && Layout.EventH > 10.0f) + { + FString Name = EventName;// +TEXT(" [") + FText::AsNumber(Event.Type->Id).ToString() + TEXT("]"); + if (EventW > Name.Len() * 2.0f + 48.0f) + { + const double Duration = EventEndTime - EventStartTime; + Name += TEXT(" ("); + Name += TimeUtils::FormatTimeAuto(Duration); + Name += TEXT(")"); + } + + const TSharedRef FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService(); + const int32 LastWholeCharacterIndex = FontMeasureService->FindLastWholeCharacterIndexBeforeOffset(Name, EventFont, FMath::RoundToInt(EventW - 2.0f)); + + if (LastWholeCharacterIndex >= 0) + { + // Grey threshold is shifted toward black (0.4 instead of 0.5 in test below) due to "area rule": + // a large gray surface (background of a timing event in this case) is perceived lighter than a smaller area (text pixels). + // Ref: https://books.google.ro/books?id=0pVr7dhmdWYC + const bool bIsDarkColor = (EventColorFill.ComputeLuminance() < 0.4f); + + DrawContext.DrawText(DrawContext.LayerId + ToInt32(EDrawLayer::EventText), EventX1 + 2.0f, EventY + 1.0f, Name, 0, LastWholeCharacterIndex + 1, EventFont, bIsDarkColor ? FLinearColor::White : FLinearColor::Black); + Stats.NumDrawTexts++; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingViewDrawHelper::DrawBox(const FBoxData& Box, const float EventY, const float EventH) +{ + DrawContext.DrawBox(DrawContext.LayerId + ToInt32(EDrawLayer::EventBorder), Box.X1, EventY, Box.X2 - Box.X1, EventH, WhiteBrush, Box.LinearColor); + Stats.NumDrawBoxes++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingViewDrawHelper::EndTimelines() +{ + float Y = Viewport.TopOffset + TimelineTopY; + + // Draw a last horizontal line. + DrawContext.DrawBox(DrawContext.LayerId + ToInt32(EDrawLayer::TimelineHeader), 0, Y, Viewport.Width, 1.0f, WhiteBrush, FLinearColor(0.05f, 0.05f, 0.05f, 1.0f)); + Y += 1.0f; + + if (Y < Viewport.Height && ValidX1 > ValidX0) + { + Y = FMath::Max(Y, 0.0f); + + // Draw invalid area (bottom). + DrawContext.DrawBox(DrawContext.LayerId + ToInt32(EDrawLayer::TimelineHeader), ValidX0, Y, ValidX1 - ValidX0, Viewport.Height - Y, BackgroundAreaBrush, InvalidAreaColor); + } + + DrawContext.LayerId += ToInt32(EDrawLayer::Count); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FTimingViewDrawHelper::DrawTimingEventHighlight(double StartTime, double EndTime, float Y, EHighlightMode Mode) +{ + float EventX1 = Viewport.TimeToSlateUnitsRounded(StartTime); + if (EventX1 > Viewport.Width) + { + return; + } + + EndTime = Viewport.RestrictEndTime(EndTime); + float EventX2 = Viewport.TimeToSlateUnitsRounded(EndTime); + if (EventX2 < 0) + { + return; + } + + if (EventX1 == EventX2) + { + EventX2 = EventX1 + 1.0f; + } + + // Limit event width on the viewport's left side. + const float MinX = -2.0f; // -2 allows event border to remain outside screen + if (EventX1 < MinX) + { + EventX1 = MinX; + } + + // Limit event width on the viewport's right side. + const float MaxX = Viewport.Width + 2.0f; // +2 allows event border to remain outside screen + if (EventX2 > MaxX) + { + EventX2 = MaxX; + } + + float EventW = EventX2 - EventX1; + + if (Mode == EHighlightMode::Hovered) + { + const FSlateBrush* HighlightBrush = BorderBrush; + + const FLinearColor Color(1.0f, 1.0f, 0.0f, 1.0f); // yellow + + // Draw border around the timing event box. + DrawContext.DrawBox(EventX1 - 2.0f, Y - 2.0f, EventW + 4.0f, Layout.EventH + 4.0f, HighlightBrush, Color); + } + else // EHighlightMode::Selected or EHighlightMode::SelectedAndHovered + { + const FSlateBrush* SelectedBrush = BorderBrush; + + // Animate color from white (if selected and hovered) or yellow (if only selected) to black, using a squared sine function. + const double Time = static_cast(FPlatformTime::Cycles64()) * FPlatformTime::GetSecondsPerCycle64(); + float S = FMath::Sin(2.0 * Time); + S = S * S; // squared, to ensure only positive [0 - 1] values + const float Blue = (Mode == EHighlightMode::SelectedAndHovered) ? 0.0f : S; + const FLinearColor Color(S, S, Blue, 1.0f); + + // Draw border around the timing event box. + DrawContext.DrawBox(EventX1 - 2.0f, Y - 2.0f, EventW + 4.0f, Layout.EventH + 4.0f, SelectedBrush, Color); + } + DrawContext.LayerId++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingViewDrawHelper.h b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingViewDrawHelper.h new file mode 100644 index 000000000000..0f910aa74564 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/ViewModels/TimingViewDrawHelper.h @@ -0,0 +1,157 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Fonts/SlateFontInfo.h" +#include "Math/Color.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct FSlateBrush; +struct FDrawContext; + +class FTimingEventsTrack; +struct FTimingEventsTrackLayout; +class FTimingTrackViewport; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FTimingViewDrawHelper +{ +public: + enum class EHighlightMode + { + Hovered, + Selected, + SelectedAndHovered + }; + +private: + enum class EDrawLayer : int32 + { + EventBorder, + EventFill, + EventText, + TimelineHeader, + TimelineText, + + Count, + }; + static int32 ToInt32(EDrawLayer Layer) { return static_cast(Layer); } + + struct FBoxData + { + float X1; + float X2; + uint32 Color; + FLinearColor LinearColor; + + FBoxData() : X1(0.0f), X2(0.0f), Color(0) {} + void Reset() { X1 = 0.0f; X2 = 0.0f; Color = 0; } + }; + + //struct FEventBoxInfo + //{ + // float X1; + // float X2; + // int32 Depth; + // uint32 Color; + //} + + //struct FEventTextInfo + //{ + // float X; + // float Y; + // FString Text; + // bool bUseDarkTextColor; // true if text needs to be displayed in Black, otherwise will be displayed in White + //}; + + struct FStats + { + int32 NumEvents; + int32 NumDrawBoxes; + int32 NumMergedBoxes; + int32 NumDrawBorders; + int32 NumDrawTexts; + int32 NumDrawTimeMarkerBoxes; + int32 NumDrawTimeMarkerTexts; + + FStats() + : NumEvents(0) + , NumDrawBoxes(0) + , NumMergedBoxes(0) + , NumDrawBorders(0) + , NumDrawTexts(0) + , NumDrawTimeMarkerBoxes(0) + , NumDrawTimeMarkerTexts(0) + {} + }; + +public: + FTimingViewDrawHelper(const FDrawContext& InDrawContext, const FTimingTrackViewport& InViewport, const FTimingEventsTrackLayout& InLayout); + ~FTimingViewDrawHelper(); + + /** + * Non-copyable + */ + FTimingViewDrawHelper(const FTimingViewDrawHelper&) = delete; + FTimingViewDrawHelper& operator=(const FTimingViewDrawHelper&) = delete; + + const FDrawContext& GetDrawContext() const { return DrawContext; } + const FTimingTrackViewport& GetViewport() const { return Viewport; } + const FTimingEventsTrackLayout& GetLayout() const { return Layout; } + + int32 GetNumEvents() const { return Stats.NumEvents; } + int32 GetNumDrawBoxes() const { return Stats.NumDrawBoxes; } + int32 GetNumMergedBoxes() const { return Stats.NumMergedBoxes; } + int32 GetNumDrawBorders() const { return Stats.NumDrawBorders; } + int32 GetNumDrawTexts() const { return Stats.NumDrawTexts; } + int32 GetNumDrawTimeMarkerBoxes() const { return Stats.NumDrawTimeMarkerBoxes; } + int32 GetNumDrawTimeMarkerTexts() const { return Stats.NumDrawTimeMarkerTexts; } + + void DrawBackground() const; + void DrawTimingEventHighlight(double StartTime, double EndTime, float Y, EHighlightMode Mode); + + //TODO: move the following in a Builder class + void BeginTimelines(); + bool BeginTimeline(FTimingEventsTrack& Track); + void AddEvent(double StartTime, double EndTime, uint32 Depth, const TCHAR* EventName, uint32 Color = 0); + void EndTimeline(FTimingEventsTrack& Track); + void EndTimelines(); + +private: + void DrawBox(const FBoxData& Box, const float EventY, const float EventH); + +private: + const FDrawContext& DrawContext; + const FTimingTrackViewport& Viewport; + const FTimingEventsTrackLayout& Layout; + + const FSlateBrush* WhiteBrush; + const FSlateBrush* BorderBrush; + const FSlateBrush* EventsBorderBrush; + const FSlateBrush* BackgroundAreaBrush; + const FLinearColor ValidAreaColor; + const FLinearColor InvalidAreaColor; + const FSlateFontInfo EventFont; + + //static const FSlateColorBrush SolidWhiteBrush = FSlateColorBrush(FColorList::White); + //static const FSlateBrush BorderBrush = FSlateBorderBrush(NAME_None, FMargin(1.0f)); + + mutable float ValidX0; + mutable float ValidX1; + + float TimelineTopY; + float TimelineY; + int32 MaxDepth; + int32 TimelineIndex; + + TArray LastEventX2; // X2 value for last event on each depth, for current timeline + TArray LastBox; + + /** Debug stats */ + mutable FStats Stats; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SFrameTrack.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SFrameTrack.cpp new file mode 100644 index 000000000000..6d39f7a0bcda --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SFrameTrack.cpp @@ -0,0 +1,882 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SFrameTrack.h" + +#include "Brushes/SlateBorderBrush.h" +#include "Brushes/SlateColorBrush.h" +#include "EditorStyleSet.h" +#include "Fonts/FontMeasure.h" +#include "Fonts/SlateFontInfo.h" +#include "Framework/Application/SlateApplication.h" +#include "HAL/PlatformTime.h" +#include "Rendering/DrawElements.h" +#include "TraceServices/AnalysisService.h" +#include "TraceServices/SessionService.h" +#include "Widgets/Layout/SScrollBar.h" + +// Insights +#include "Insights/Common/PaintUtils.h" +#include "Insights/Common/Stopwatch.h" +#include "Insights/Common/TimeUtils.h" +#include "Insights/InsightsManager.h" +#include "Insights/TimingProfilerCommon.h" +#include "Insights/TimingProfilerManager.h" +#include "Insights/ViewModels/FrameTrackHelper.h" +#include "Insights/Widgets/STimingProfilerWindow.h" +#include "Insights/Widgets/STimingView.h" + +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define LOCTEXT_NAMESPACE "SFrameTrack" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +SFrameTrack::SFrameTrack() + : HoveredSample(nullptr, nullptr) +{ + Reset(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +SFrameTrack::~SFrameTrack() +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SFrameTrack::Reset() +{ + Viewport.Reset(); + bIsViewportDirty = true; + + CachedTimelines.Reset(); + TimelinesOrder.Reset(); + TimelinesOrder.Add(0); + TimelinesOrder.Add(1); + TimelinesOrder.Add(2); + bIsStateDirty = true; + + AnalysisSyncNextTimestamp = 0; + + MousePosition = FVector2D::ZeroVector; + + MousePositionOnButtonDown = FVector2D::ZeroVector; + ViewportPosXOnButtonDown = 0.0f; + ViewportPosYOnButtonDown = 0.0f; + + MousePositionOnButtonUp = FVector2D::ZeroVector; + + bIsLMB_Pressed = false; + bIsRMB_Pressed = false; + + bIsRMB_Scrolling = false; + + SelectionStartFrameIndex = 0; + SelectionEndFrameIndex = 0; + + HoveredSample.Timeline = nullptr; + HoveredSample.Sample = nullptr; + + //ThisGeometry + + CursorType = EFrameTrackCursor::Default; + + NumUpdatedFrames = 0; + UpdateDurationHistory.Reset(); + DrawDurationHistory.Reset(); + OnPaintDurationHistory.Reset(); + LastOnPaintTime = FPlatformTime::Cycles64(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SFrameTrack::Construct(const FArguments& InArgs) +{ + ChildSlot + [ + SNew(SOverlay) + .Visibility(EVisibility::SelfHitTestInvisible) + + + SOverlay::Slot() + .VAlign(VAlign_Top) + .Padding(FMargin(0, 0, 0, 0)) + [ + SAssignNew(HorizontalScrollBar, SScrollBar) + .Orientation(Orient_Horizontal) + .AlwaysShowScrollbar(false) + .Visibility(EVisibility::Visible) + .Thickness(FVector2D(5.0f, 5.0f)) + .RenderOpacity(0.75) + .OnUserScrolled(this, &SFrameTrack::HorizontalScrollBar_OnUserScrolled) + ] + ]; + + UpdateHorizontalScrollBar(); + + BindCommands(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SFrameTrack::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) +{ + if (ThisGeometry != AllottedGeometry || bIsViewportDirty) + { + bIsViewportDirty = false; + const float ViewWidth = AllottedGeometry.GetLocalSize().X; + const float ViewHeight = AllottedGeometry.GetLocalSize().Y; + Viewport.SetSize(ViewWidth, ViewHeight); + bIsStateDirty = true; + } + + ThisGeometry = AllottedGeometry; + + if (!bIsRMB_Scrolling) + { + // Elastic snap to horizontal limits (+15%). + + float MinX, MaxX; + if (Viewport.MaxX - Viewport.MinX < Viewport.Width) + { + MinX = Viewport.MaxX - Viewport.Width; + MaxX = Viewport.MinX; + } + else + { + MinX = Viewport.MinX - 0.15f * Viewport.Width; + MaxX = Viewport.MaxX - 0.85f * Viewport.Width; + } + const float U = 0.5f; + + float PosX = Viewport.PosX; + if (PosX < MinX) + { + PosX = PosX * U + (1.0f - U) * MinX; + if (FMath::IsNearlyEqual(PosX, MinX, 1.0f)) + PosX = MinX; + } + else if (PosX > MaxX) + { + PosX = PosX * U + (1.0f - U) * MaxX; + if (FMath::IsNearlyEqual(PosX, MaxX, 1.0f)) + PosX = MaxX; + if (PosX < MinX) + PosX = MinX; + } + + if (Viewport.PosX != PosX) + { + //Viewport.ScrollAtPosX(PosX); + Viewport.ScrollAtIndex(Viewport.GetIndexAtPosX(PosX)); + bIsStateDirty = true; + } + } + + uint64 Time = FPlatformTime::Cycles64(); + if (Time > AnalysisSyncNextTimestamp) + { + const uint64 WaitTime = static_cast(0.1 / FPlatformTime::GetSecondsPerCycle64()); // 100ms + AnalysisSyncNextTimestamp = Time + WaitTime; + + TSharedPtr Session = FInsightsManager::Get()->GetSession(); + if (Session.IsValid()) + { + Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); + + const Trace::IFrameProvider& FramesProvider = Trace::ReadFrameProvider(*Session.Get()); + + for (int32 FrameType = 0; FrameType < TraceFrameType_Count; ++FrameType) + { + FFrameTrackTimeline& CachedTimeline = CachedTimelines.FindOrAdd(FrameType); + CachedTimeline.Id = FrameType; + + int32 NumFrames = FramesProvider.GetFrameCount(static_cast(FrameType)); + if (NumFrames > Viewport.MaxIndex) + { + Viewport.SetMinMaxIndexInterval(0, NumFrames); + UpdateHorizontalScrollBar(); + bIsStateDirty = true; + + // Auto zoom out. + float SampleW = Viewport.GetSampleWidth(); + int32 NumSamples = FMath::CeilToInt(Viewport.Width / SampleW); + if (NumFrames > NumSamples * Viewport.GetNumFramesPerSample()) + { + ZoomHorizontally(-0.1f, 0.0f); + Viewport.ScrollAtPosX(0.0f); + } + } + } + } + } + + if (bIsStateDirty) + { + bIsStateDirty = false; + UpdateState(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SFrameTrack::UpdateState() +{ + FStopwatch Stopwatch; + Stopwatch.Start(); + + // Reset stats. + for (TPair& KeyValuePair : CachedTimelines) + { + FFrameTrackTimeline& Timeline = KeyValuePair.Value; + Timeline.NumAggregatedFrames = 0; + } + NumUpdatedFrames = 0; + + TSharedPtr Session = FInsightsManager::Get()->GetSession(); + if (Session.IsValid()) + { + Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); + + const Trace::IFrameProvider& FramesProvider = Trace::ReadFrameProvider(*Session.Get()); + + for (int32 FrameType = 0; FrameType < TraceFrameType_Count; ++FrameType) + { + FFrameTrackTimeline& Timeline = CachedTimelines.FindOrAdd(FrameType); + Timeline.Id = FrameType; + + FFrameTrackTimelineBuilder Builder(Timeline, Viewport); + + uint64 StartIndex = static_cast(FMath::Max(0, Viewport.GetIndexAtViewportX(0.0f))); + uint64 EndIndex = static_cast(Viewport.GetIndexAtViewportX(Viewport.Width)); + + FramesProvider.EnumerateFrames(static_cast(FrameType), StartIndex, EndIndex, [&Builder](const Trace::FFrame& Frame) + { + Builder.AddFrame(Frame); + }); + + NumUpdatedFrames += Builder.GetNumAddedFrames(); + } + } + + Stopwatch.Stop(); + UpdateDurationHistory.AddValue(Stopwatch.AccumulatedTime); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FSampleRef SFrameTrack::GetSampleAtMousePosition(float X, float Y) +{ + float SampleW = Viewport.GetSampleWidth(); + int32 SampleIndex = FMath::FloorToInt(X / SampleW); + if (SampleIndex >= 0) + { + const float ViewportHeight = FMath::RoundToFloat(Viewport.Height); + const float SampleY2 = ViewportHeight - FMath::RoundToFloat(Viewport.GetViewportYForValue(0.0)); + + // Search in reverse paint order. + for (int32 TimelineIndex = TimelinesOrder.Num() - 1; TimelineIndex >= 0; --TimelineIndex) + { + int32 TimelineId = TimelinesOrder[TimelineIndex]; + if (CachedTimelines.Contains(TimelineId)) + { + const FFrameTrackTimeline& CachedTimeline = CachedTimelines[TimelineId]; + if (CachedTimeline.NumAggregatedFrames > 0 && + SampleIndex < CachedTimeline.Samples.Num()) + { + const FFrameTrackSample& Sample = CachedTimeline.Samples[SampleIndex]; + if (Sample.NumFrames > 0) + { + float SampleY1; + if (Sample.LargestFrameDuration == std::numeric_limits::infinity()) + { + SampleY1 = 0.0; + } + else + { + SampleY1 = ViewportHeight - FMath::RoundToFloat(Viewport.GetViewportYForValue(Sample.LargestFrameDuration)); + } + if (Y >= SampleY1 && Y <= SampleY2) + { + return FSampleRef(&CachedTimeline, &Sample); + } + } + } + } + } + } + return FSampleRef(nullptr, nullptr); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SFrameTrack::SelectFrameAtMousePosition(float X, float Y) +{ + FSampleRef SampleRef = GetSampleAtMousePosition(X, Y); + if (!SampleRef.IsValid()) + { + SampleRef = GetSampleAtMousePosition(X - 1.0f, Y); + } + if (!SampleRef.IsValid()) + { + SampleRef = GetSampleAtMousePosition(X + 1.0f, Y); + } + + if (SampleRef.IsValid()) + { + TSharedPtr Window = FTimingProfilerManager::Get()->GetProfilerWindow(); + if (Window.IsValid()) + { + const double StartTime = SampleRef.Sample->LargestFrameStartTime; + const double Duration = SampleRef.Sample->LargestFrameDuration; + Window->TimingView->CenterOnTimeInterval(StartTime, Duration); + Window->TimingView->SelectTimeInterval(StartTime, Duration); + FSlateApplication::Get().SetKeyboardFocus(Window->TimingView); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +int32 SFrameTrack::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const +{ + const bool bEnabled = ShouldBeEnabled(bParentEnabled); + const ESlateDrawEffect DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect; + FDrawContext DrawContext(AllottedGeometry, MyCullingRect, InWidgetStyle, DrawEffects, OutDrawElements, LayerId); + + const TSharedRef FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService(); + + const FSlateBrush* WhiteBrush = FCoreStyle::Get().GetBrush("WhiteBrush"); + + const float ViewWidth = AllottedGeometry.Size.X; + const float ViewHeight = AllottedGeometry.Size.Y; + + int32 NumDrawSamples = 0; + + { + FStopwatch Stopwatch; + Stopwatch.Start(); + + FFrameTrackDrawHelper Helper(DrawContext, Viewport); + + Helper.DrawBackground(); + + // Draw frames, for each visible timeline. + for (const TPair& KeyValuePair : CachedTimelines) + { + const FFrameTrackTimeline& Timeline = KeyValuePair.Value; + Helper.DrawCached(Timeline); + } + NumDrawSamples = Helper.GetNumDrawSamples(); + + // Highlight the mouse hovered sample. + if (HoveredSample.IsValid()) + { + Helper.DrawHoveredSample(*HoveredSample.Sample); + + FString Text = FString::Format(TEXT("{0} frame {1} ({2})"), + { + (HoveredSample.Timeline->Id == 0) ? TEXT("Game") : + (HoveredSample.Timeline->Id == 1) ? TEXT("Render") : TEXT("Misc"), + FText::AsNumber(HoveredSample.Sample->LargestFrameIndex).ToString(), + TimeUtils::FormatTimeAuto(HoveredSample.Sample->LargestFrameDuration) + }); + + FSlateFontInfo SummaryFont = FCoreStyle::GetDefaultFontStyle("Regular", 8); + FVector2D TextSize = FontMeasureService->Measure(Text, SummaryFont); + + const float DX = 2.0f; + const float W2 = TextSize.X / 2 + DX; + + float X1 = Viewport.GetViewportXForIndex(HoveredSample.Sample->LargestFrameIndex); + float CX = X1 + FMath::RoundToFloat(Viewport.GetSampleWidth() / 2); + if (CX + W2 > Viewport.Width) + { + CX = FMath::RoundToFloat(Viewport.Width - W2); + } + if (CX - W2 < 0) + { + CX = W2; + } + + const float Y = 10.0f; + const float H = 14.0f; + DrawContext.DrawBox(CX - W2, Y, 2 * W2, H, WhiteBrush, FLinearColor(0.7, 0.7, 0.7, 0.9)); + DrawContext.DrawText(CX - W2 + DX, Y + 1.0f, Text, SummaryFont, FLinearColor(0.0, 0.0, 0.0, 0.9)); + } + + TSharedPtr Window = FTimingProfilerManager::Get()->GetProfilerWindow(); + if (Window && Window->TimingView && CachedTimelines.Num() > 0) + { + // Highlight the area corresponding to viewport of Timing View. + const double StartTime = Window->TimingView->GetViewport().StartTime; + const double EndTime = Window->TimingView->GetViewport().EndTime; + Helper.DrawHighlightedInterval(CachedTimelines[0], StartTime, EndTime); + } + + Stopwatch.Stop(); + DrawDurationHistory.AddValue(Stopwatch.AccumulatedTime); + } + + ////////////////////////////////////////////////// + + const bool bShouldDisplayDebugInfo = FInsightsManager::Get()->IsDebugInfoEnabled(); + if (bShouldDisplayDebugInfo) + { + FSlateFontInfo SummaryFont = FCoreStyle::GetDefaultFontStyle("Regular", 8); + const float MaxFontCharHeight = FontMeasureService->Measure(TEXT("!"), SummaryFont).Y; + const float DbgDY = MaxFontCharHeight; + + const float DbgW = 240.0f; + const float DbgH = DbgDY * 4 + 3.0f; + const float DbgX = ViewWidth - DbgW - 20.0f; + float DbgY = 7.0f; + + ++LayerId; + FSlateDrawElement::MakeBox( + OutDrawElements, + LayerId, + MAKE_PAINT_GEOMETRY_RC(AllottedGeometry, DbgX - 2.0f, DbgY - 2.0f, DbgW, DbgH), + WhiteBrush, + DrawEffects, + FLinearColor(1.0, 1.0, 1.0, 0.9) + ); + + ++LayerId; + FLinearColor DbgTextColor(0.0, 0.0, 0.0, 0.9); + + // Time interval since last OnPaint call. + const uint64 CurrentTime = FPlatformTime::Cycles64(); + const uint64 OnPaintDuration = CurrentTime - LastOnPaintTime; + LastOnPaintTime = CurrentTime; + OnPaintDurationHistory.AddValue(OnPaintDuration); // saved for last 32 OnPaint calls + const uint64 AvgOnPaintDuration = OnPaintDurationHistory.ComputeAverage(); + const uint64 AvgOnPaintDurationMs = FStopwatch::Cycles64ToMilliseconds(AvgOnPaintDuration); + const double AvgOnPaintFps = AvgOnPaintDurationMs != 0 ? 1.0 / FStopwatch::Cycles64ToSeconds(AvgOnPaintDuration) : 0.0; + + const uint64 AvgUpdateDurationMs = FStopwatch::Cycles64ToMilliseconds(UpdateDurationHistory.ComputeAverage()); + const uint64 AvgDrawDurationMs = FStopwatch::Cycles64ToMilliseconds(DrawDurationHistory.ComputeAverage()); + + // Draw performance info. + FSlateDrawElement::MakeText + ( + OutDrawElements, + LayerId, + MAKE_PAINT_GEOMETRY_PT(AllottedGeometry, DbgX, DbgY), + FString::Printf(TEXT("U: %llu ms, D: %llu ms + %llu ms = %llu ms (%d fps)"), + AvgUpdateDurationMs, // caching time + AvgDrawDurationMs, // drawing time + AvgOnPaintDurationMs - AvgDrawDurationMs, // other overhead to OnPaint calls + AvgOnPaintDurationMs, // average time between two OnPaint calls + FMath::RoundToInt(AvgOnPaintFps)), // framerate of OnPaint calls + SummaryFont, + DrawEffects, + DbgTextColor + ); + DbgY += DbgDY; + + // Draw number of draw calls. + FSlateDrawElement::MakeText + ( + OutDrawElements, + LayerId, + MAKE_PAINT_GEOMETRY_PT(AllottedGeometry, DbgX, DbgY), + FString::Format(TEXT("U: {0} frames, D: {1} samples"), + { + FText::AsNumber(NumUpdatedFrames).ToString(), + FText::AsNumber(NumDrawSamples).ToString() + }), + SummaryFont, + DrawEffects, + DbgTextColor + ); + DbgY += DbgDY; + + // Draw viewport's horizontal info. + FSlateDrawElement::MakeText + ( + OutDrawElements, + LayerId, + MAKE_PAINT_GEOMETRY_PT(AllottedGeometry, DbgX, DbgY), + FString::Printf(TEXT("SX: %.3f (%d %s), X: %.2f"), + Viewport.ScaleX, + (Viewport.ScaleX >= 1.0f) ? FMath::RoundToInt(Viewport.ScaleX) : FMath::RoundToInt(1.0f / Viewport.ScaleX), + (Viewport.ScaleX >= 1.0f) ? TEXT("px/frame") : TEXT("frames/px"), + Viewport.PosX), + SummaryFont, + DrawEffects, + DbgTextColor + ); + DbgY += DbgDY; + + // Draw viewport's vertical info. + FSlateDrawElement::MakeText + ( + OutDrawElements, + LayerId, + MAKE_PAINT_GEOMETRY_PT(AllottedGeometry, DbgX, DbgY), + FString::Printf(TEXT("SY: %g, Y: %.2f"), Viewport.ScaleY, Viewport.PosY), + SummaryFont, + DrawEffects, + DbgTextColor + ); + DbgY += DbgDY; + } + + return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled && IsEnabled()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SFrameTrack::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + FReply Reply = FReply::Unhandled(); + + if (!IsReady()) + { + return Reply; + } + + MousePositionOnButtonDown = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()); + ViewportPosXOnButtonDown = Viewport.PosX; + ViewportPosYOnButtonDown = Viewport.PosY; + + if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) + { + bIsLMB_Pressed = true; + + // Capture mouse. + Reply = FReply::Handled().CaptureMouse(SharedThis(this)); + } + else if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton) + { + bIsRMB_Pressed = true; + + // Capture mouse, so we can scroll outside this widget. + Reply = FReply::Handled().CaptureMouse(SharedThis(this)); + } + + return Reply; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SFrameTrack::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + FReply Reply = FReply::Unhandled(); + + if (!IsReady()) + { + return Reply; + } + + MousePositionOnButtonUp = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()); + + const bool bIsValidForMouseClick = MousePositionOnButtonUp.Equals(MousePositionOnButtonDown, MOUSE_SNAP_DISTANCE); + + if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) + { + if (bIsLMB_Pressed) + { + // Release the mouse. + Reply = FReply::Handled().ReleaseMouseCapture(); + + if (bIsValidForMouseClick) + { + SelectFrameAtMousePosition(MousePositionOnButtonUp.X, MousePositionOnButtonUp.Y); + } + + bIsLMB_Pressed = false; + } + } + else if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton) + { + if (bIsRMB_Pressed) + { + // Release mouse as we no longer scroll. + Reply = FReply::Handled().ReleaseMouseCapture(); + + if (bIsRMB_Scrolling) + { + CursorType = EFrameTrackCursor::Default; + bIsRMB_Scrolling = false; + } + else if (bIsValidForMouseClick) + { + ShowContextMenu(MouseEvent.GetScreenSpacePosition()); + Reply = FReply::Handled(); + } + + bIsRMB_Pressed = false; + } + } + + return Reply; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SFrameTrack::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + FReply Reply = FReply::Unhandled(); + + if (!IsReady()) + { + return Reply; + } + + MousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()); + + if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) + { + if (HasMouseCapture() && !MouseEvent.GetCursorDelta().IsZero()) + { + // Inform other widgets that we have moved the selection box. + //SelectionBoxChangedEvent.Broadcast(SelectionBoxFrameStart, SelectionBoxFrameEnd); + + Reply = FReply::Handled(); + } + } + else if (MouseEvent.IsMouseButtonDown(EKeys::RightMouseButton)) + { + if (HasMouseCapture() && !MouseEvent.GetCursorDelta().IsZero()) + { + bIsRMB_Scrolling = true; + CursorType = EFrameTrackCursor::Hand; + + HoveredSample.Reset(); + + float PosX = ViewportPosXOnButtonDown + (MousePositionOnButtonDown.X - MousePosition.X); + Viewport.ScrollAtIndex(Viewport.GetIndexAtPosX(PosX)); + UpdateHorizontalScrollBar(); + bIsStateDirty = true; + + Reply = FReply::Handled(); + } + } + else + { + HoveredSample = GetSampleAtMousePosition(MousePosition.X, MousePosition.Y); + if (!HoveredSample.IsValid()) + HoveredSample = GetSampleAtMousePosition(MousePosition.X - 1.0f, MousePosition.Y); + if (!HoveredSample.IsValid()) + HoveredSample = GetSampleAtMousePosition(MousePosition.X + 1.0f, MousePosition.Y); + } + + return Reply; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SFrameTrack::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SFrameTrack::OnMouseLeave(const FPointerEvent& MouseEvent) +{ + if (!HasMouseCapture()) + { + bIsLMB_Pressed = false; + bIsRMB_Pressed = false; + + HoveredSample.Reset(); + + CursorType = EFrameTrackCursor::Default; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SFrameTrack::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + MousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()); + + if (MouseEvent.GetModifierKeys().IsShiftDown()) + { + // Zoom in/out vertically. + const float Delta = MouseEvent.GetWheelDelta(); + constexpr float ZoomStep = 0.25f; // as percent + float ScaleY; + + if (Delta > 0) + { + ScaleY = Viewport.ScaleY * FMath::Pow(1.0f + ZoomStep, Delta); + } + else + { + ScaleY = Viewport.ScaleY * FMath::Pow(1.0f / (1.0f + ZoomStep), -Delta); + } + + Viewport.SetScaleY(ScaleY); + //UpdateVerticalScrollBar(); + } + else //if (MouseEvent.GetModifierKeys().IsControlDown()) + { + // Zoom in/out horizontally. + const float Delta = MouseEvent.GetWheelDelta(); + ZoomHorizontally(Delta, MousePosition.X); + } + + return FReply::Handled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SFrameTrack::ZoomHorizontally(const float Delta, const float X) +{ + constexpr float ZoomStep = 0.25f; // as percent + float ScaleX; + + if (Delta > 0) + { + ScaleX = Viewport.ScaleX * FMath::Pow(1.0f + ZoomStep, Delta); + } + else + { + ScaleX = Viewport.ScaleX * FMath::Pow(1.0f / (1.0f + ZoomStep), -Delta); + } + + // Snap to integer value of either: "number of frames per pixel (Slate unit)" or "number of pixels per frame". + if (ScaleX < 1.0f) + { + // frames per Slate unit + if (Delta > 0) + ScaleX = 1.0f / FMath::FloorToFloat(1.0f / ScaleX); + else + ScaleX = 1.0f / FMath::CeilToFloat(1.0f / ScaleX); + } + else + { + // Slate units per frame + if (Delta > 0) + ScaleX = FMath::CeilToFloat(ScaleX); + else + ScaleX = FMath::FloorToFloat(ScaleX); + } + + //UE_LOG(TimingProfiler, Log, TEXT("%.2f, %.2f, %.2f"), Delta, Viewport.ScaleX, ScaleX); + Viewport.ZoomWithFixedViewportX(ScaleX, MousePosition.X); + UpdateHorizontalScrollBar(); + bIsStateDirty = true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SFrameTrack::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + return FReply::Unhandled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FCursorReply SFrameTrack::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const +{ + FCursorReply CursorReply = FCursorReply::Unhandled(); + + if (CursorType == EFrameTrackCursor::Arrow) + { + CursorReply = FCursorReply::Cursor(EMouseCursor::ResizeLeftRight); + } + else if (CursorType == EFrameTrackCursor::Hand) + { + CursorReply = FCursorReply::Cursor(EMouseCursor::GrabHand); + } + + return CursorReply; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SFrameTrack::ShowContextMenu(const FVector2D& ScreenSpacePosition) +{ + +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SFrameTrack::BindCommands() +{ + +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SFrameTrack::HorizontalScrollBar_OnUserScrolled(float ScrollOffset) +{ + const float SX = 1.0 / (Viewport.MaxX - Viewport.MinX); + const float ThumbSizeFraction = FMath::Clamp(Viewport.Width * SX, 0.0f, 1.0f); + const float OffsetFraction = FMath::Clamp(ScrollOffset, 0.0f, 1.0f - ThumbSizeFraction); + + const float PosX = Viewport.MinX + OffsetFraction * (Viewport.MaxX - Viewport.MinX); + Viewport.ScrollAtPosX(PosX); + bIsStateDirty = true; + + HorizontalScrollBar->SetState(OffsetFraction, ThumbSizeFraction); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SFrameTrack::UpdateHorizontalScrollBar() +{ + const float SX = 1.0 / (Viewport.MaxX - Viewport.MinX); + const float ThumbSizeFraction = FMath::Clamp(Viewport.Width * SX, 0.0f, 1.0f); + const float ScrollOffset = (Viewport.PosX - Viewport.MinX) * SX; + const float OffsetFraction = FMath::Clamp(ScrollOffset, 0.0f, 1.0f - ThumbSizeFraction); + + HorizontalScrollBar->SetState(OffsetFraction, ThumbSizeFraction); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/* +void SFrameTrack::AddThreadTime(int32 InFrameIndex, const TMap& InThreadMS, const TSharedRef& InStatMetaData) +{ + FFrameThreadTimes FrameThreadTimes; + FrameThreadTimes.FrameNumber = InFrameIndex; + FrameThreadTimes.ThreadTimes = InThreadMS; + RecentlyAddedFrames.Add(FrameThreadTimes); + + if (!bIsActiveTimerRegistered) + { + bIsActiveTimerRegistered = true; + RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SFrameTrack::EnsureDataUpdateDuringPreview)); + } + + StatMetadata = InStatMetaData; +} +*/ +//////////////////////////////////////////////////////////////////////////////////////////////////// +/* +EActiveTimerReturnType SFrameTrack::EnsureDataUpdateDuringPreview(double InCurrentTime, float InDeltaTime) +{ + //if (RecentlyAddedFrames.Num() > 0) + //{ + // bUpdateData = true; + // return EActiveTimerReturnType::Continue; + //} + + bIsActiveTimerRegistered = false; + return EActiveTimerReturnType::Stop; +} +*/ +//////////////////////////////////////////////////////////////////////////////////////////////////// +/* +void SFrameTrack::MoveSelectionBox(int32 FrameIndex) +{ + const int32 SelectionBoxSize = SelectionBoxFrameEnd - SelectionBoxFrameStart; + const int32 SelectionBoxHalfSize = SelectionBoxSize / 2; + const int32 CenterFrameIndex = FMath::Clamp(FrameIndex - SelectionBoxHalfSize, 0, Frames.Num() - 1 - SelectionBoxSize); + + // Inform other widgets that we have moved the selection box. + SelectionBoxFrameStart = CenterFrameIndex; + SelectionBoxFrameEnd = CenterFrameIndex + SelectionBoxSize; + SelectionBoxChangedEvent.Broadcast(SelectionBoxFrameStart, SelectionBoxFrameEnd); +} +*/ +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SFrameTrack.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SFrameTrack.h new file mode 100644 index 000000000000..6e332dae2956 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SFrameTrack.h @@ -0,0 +1,190 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Input/CursorReply.h" +#include "Input/Reply.h" +#include "Layout/Geometry.h" +#include "Rendering/RenderingCommon.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" + +// Insights +#include "Insights/Common/FixedCircularBuffer.h" +#include "Insights/ViewModels/FrameTrackHelper.h" +#include "Insights/ViewModels/FrameTrackViewport.h" + +class SScrollBar; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct FSampleRef +{ + const FFrameTrackTimeline* Timeline; + const FFrameTrackSample* Sample; + + FSampleRef(const FFrameTrackTimeline* InTimeline, const FFrameTrackSample* InSample) + : Timeline(InTimeline), Sample(InSample) + { + } + + void Reset() { Timeline = nullptr; Sample = nullptr; } + bool IsValid() const { return Timeline != nullptr && Sample != nullptr; } +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * Widget used to present frames data in a track. + */ +class SFrameTrack : public SCompoundWidget +{ + enum + { + /** Number of pixels. */ + MOUSE_SNAP_DISTANCE = 4, + }; + + enum class EFrameTrackCursor + { + Default, + Arrow, + Hand, + }; + +public: + /** Default constructor. */ + SFrameTrack(); + + /** Virtual destructor. */ + virtual ~SFrameTrack(); + + /** Resets internal widget's data to the default one. */ + void Reset(); + + SLATE_BEGIN_ARGS(SFrameTrack) + { + _Clipping = EWidgetClipping::ClipToBounds; + } + SLATE_END_ARGS() + + /** + * Construct this widget + * @param InArgs The declaration data for this widget + */ + void Construct(const FArguments& InArgs); + + /** + * Ticks this widget. Override in derived classes, but always call the parent implementation. + * @param AllottedGeometry The space allotted for this widget + * @param InCurrentTime Current absolute real time + * @param InDeltaTime Real time passed since last tick + */ + virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; + + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; + + virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + virtual void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override; + virtual FReply OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + virtual FReply OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + virtual FCursorReply OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const override; + +protected: + bool IsReady() { return true; } + + void UpdateState(); + + FSampleRef GetSampleAtMousePosition(float X, float Y); + void SelectFrameAtMousePosition(float X, float Y); + + void ShowContextMenu(const FVector2D& ScreenSpacePosition); + + /** Binds our UI commands to delegates. */ + void BindCommands(); + + /** + * Called when the user scrolls the horizontal scrollbar. + * @param ScrollOffset Scroll offset as a fraction between 0 and 1. + */ + void HorizontalScrollBar_OnUserScrolled(float ScrollOffset); + void UpdateHorizontalScrollBar(); + + void ZoomHorizontally(const float Delta, const float X); + + ////////////////////////////////////////////////// + // SelectionBoxChanged Event + +public: + /** The event to execute when the selection box has been changed. */ + DECLARE_EVENT_TwoParams(SFrameTrack, FSelectionBoxChangedEvent, int32 /*FrameStart*/, int32 /*FrameEnd*/); + FSelectionBoxChangedEvent& OnSelectionBoxChanged() { return SelectionBoxChangedEvent; } +protected: + /** The event to execute when the selection box has been changed. */ + FSelectionBoxChangedEvent SelectionBoxChangedEvent; + + ////////////////////////////////////////////////// + +protected: + /** The track's viewport. Encapsulates info about position and scale. */ + FFrameTrackViewport Viewport; + bool bIsViewportDirty; + + /** Cached info for timelines. */ + TMap CachedTimelines; + TArray TimelinesOrder; + + bool bIsStateDirty; + + uint64 AnalysisSyncNextTimestamp; + + ////////////////////////////////////////////////// + + TSharedPtr HorizontalScrollBar; + + ////////////////////////////////////////////////// + // Panning, Zooming and Selection behaviors + + /** The current mouse position. */ + FVector2D MousePosition; + + /** Mouse position during the call on mouse button down. */ + FVector2D MousePositionOnButtonDown; + float ViewportPosXOnButtonDown; + float ViewportPosYOnButtonDown; + + /** Mouse position during the call on mouse button up. */ + FVector2D MousePositionOnButtonUp; + + bool bIsLMB_Pressed; + bool bIsRMB_Pressed; + + /** True, if the user is currently interactively scrolling the view by holding the right mouse button and dragging. */ + bool bIsRMB_Scrolling; + + ////////////////////////////////////////////////// + + int32 SelectionStartFrameIndex; + int32 SelectionEndFrameIndex; + + FSampleRef HoveredSample; + + ////////////////////////////////////////////////// + // Misc + + FGeometry ThisGeometry; + + /** Cursor type. */ + EFrameTrackCursor CursorType; + + // Debug stats + int32 NumUpdatedFrames; + TFixedCircularBuffer UpdateDurationHistory; + mutable TFixedCircularBuffer DrawDurationHistory; + mutable TFixedCircularBuffer OnPaintDurationHistory; + mutable uint64 LastOnPaintTime; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SGraphTrack.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SGraphTrack.cpp new file mode 100644 index 000000000000..bc3060741f5a --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SGraphTrack.cpp @@ -0,0 +1,220 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SGraphTrack.h" + +#include "Brushes/SlateBorderBrush.h" +#include "Brushes/SlateBoxBrush.h" +#include "Brushes/SlateColorBrush.h" +#include "Containers/ArrayBuilder.h" +#include "Containers/MapBuilder.h" +#include "EditorStyleSet.h" +#include "Fonts/FontMeasure.h" +#include "Fonts/SlateFontInfo.h" +#include "Framework/Application/MenuStack.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Layout/WidgetPath.h" +#include "Misc/Paths.h" +#include "Rendering/DrawElements.h" +#include "SlateOptMacros.h" +#include "Styling/CoreStyle.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Layout/SSpacer.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/SOverlay.h" +#include "Widgets/Text/STextBlock.h" + +// Insights +#include "Insights/Common/PaintUtils.h" +#include "Insights/TimingProfilerManager.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define LOCTEXT_NAMESPACE "SGraphTrack" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// SGraphTrack +//////////////////////////////////////////////////////////////////////////////////////////////////// + +SGraphTrack::SGraphTrack() + : TimeRulerTrack(0) + , GraphTrack(1) +{ + Reset(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +SGraphTrack::~SGraphTrack() +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SGraphTrack::Reset() +{ + TimeRulerTrack.Reset(); + GraphTrack.Reset(); + Viewport.ScaleX = (5 * 20) / 0.1; // 100ms between major tick marks +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SGraphTrack::Construct(const FArguments& InArgs) +{ + //TODO: Add toggle buttons for Points/Lines/Bars. + //TODO: Add "show list of series" expand button + toggle buttons for each available graph series. +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SGraphTrack::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) +{ + //ThisGeometry = AllottedGeometry; + + float TrackWidth = AllottedGeometry.GetAbsoluteSize().X; + float TrackHeight = AllottedGeometry.GetAbsoluteSize().Y; + + bool bIsGraphDirty = false; + + if (Viewport.UpdateSize(TrackWidth, TrackHeight)) + { + bIsGraphDirty = true; + } + + if (GraphTrack.GetHeight() != TrackHeight) + { + bIsGraphDirty = true; + } + + if (bIsGraphDirty) + { + bIsGraphDirty = false; + + GraphTrack.SetPosY(TimeRulerTrack.GetHeight()); + GraphTrack.SetHeight(Viewport.Height - TimeRulerTrack.GetHeight()); + GraphTrack.Update(Viewport); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +int32 SGraphTrack::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const +{ + const bool bEnabled = ShouldBeEnabled(bParentEnabled); + const ESlateDrawEffect DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect; + FDrawContext DrawContext(AllottedGeometry, MyCullingRect, InWidgetStyle, DrawEffects, OutDrawElements, LayerId); + + GraphTrack.Draw(DrawContext, Viewport); + TimeRulerTrack.Draw(DrawContext, Viewport); + + return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled && IsEnabled()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SGraphTrack::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + //... + + return FReply::Unhandled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SGraphTrack::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + //... + + return FReply::Unhandled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SGraphTrack::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + //... + + return FReply::Unhandled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SGraphTrack::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + //... +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SGraphTrack::OnMouseLeave(const FPointerEvent& MouseEvent) +{ + //... +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SGraphTrack::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + //... + + return FReply::Handled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SGraphTrack::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + //... + + return FReply::Unhandled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SGraphTrack::OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) +{ + SCompoundWidget::OnDragEnter(MyGeometry, DragDropEvent); + + //... +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SGraphTrack::OnDragLeave(const FDragDropEvent& DragDropEvent) +{ + SCompoundWidget::OnDragLeave(DragDropEvent); + + //... +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SGraphTrack::OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) +{ + //... + + return SCompoundWidget::OnDragOver(MyGeometry,DragDropEvent); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SGraphTrack::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) +{ + //... + + return SCompoundWidget::OnDrop(MyGeometry,DragDropEvent); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FCursorReply SGraphTrack::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const +{ + //... + + return FCursorReply::Unhandled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SGraphTrack.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SGraphTrack.h new file mode 100644 index 000000000000..e0b794884481 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SGraphTrack.h @@ -0,0 +1,187 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Layout/Geometry.h" +#include "Input/CursorReply.h" +#include "Input/Reply.h" +#include "Widgets/SWidget.h" +#include "Widgets/SCompoundWidget.h" + +// Insights +#include "Insights/ViewModels/GraphTrack.h" +#include "Insights/ViewModels/TimeRulerTrack.h" +#include "Insights/ViewModels/TimingTrackViewport.h" + +class FPaintArgs; +class FSlateWindowElementList; + +/** A custom widget used to display graphs. */ +class SGraphTrack : public SCompoundWidget +{ +public: + /** Default constructor. */ + SGraphTrack(); + + /** Virtual destructor. */ + virtual ~SGraphTrack(); + + /** Resets internal widget's data to the default one. */ + void Reset(); + + SLATE_BEGIN_ARGS(SGraphTrack) + { + _Clipping = EWidgetClipping::ClipToBounds; + } + SLATE_END_ARGS() + + /** + * Construct this widget + * + * @param InArgs The declaration data for this widget + */ + void Construct(const FArguments& InArgs); + + /** + * Ticks this widget. Override in derived classes, but always call the parent implementation. + * + * @param AllottedGeometry The space allotted for this widget + * @param InCurrentTime Current absolute real time + * @param InDeltaTime Real time passed since last tick + */ + virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; + + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; + + /** + * The system calls this method to notify the widget that a mouse button was pressed within it. This event is bubbled. + * + * @param MyGeometry The Geometry of the widget receiving the event + * @param MouseEvent Information about the input event + * + * @return Whether the event was handled along with possible requests for the system to take action. + */ + virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + /** + * The system calls this method to notify the widget that a mouse button was release within it. This event is bubbled. + * + * @param MyGeometry The Geometry of the widget receiving the event + * @param MouseEvent Information about the input event + * + * @return Whether the event was handled along with possible requests for the system to take action. + */ + virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + /** + * The system calls this method to notify the widget that a mouse moved within it. This event is bubbled. + * + * @param MyGeometry The Geometry of the widget receiving the event + * @param MouseEvent Information about the input event + * + * @return Whether the event was handled along with possible requests for the system to take action. + */ + virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + /** + * The system will use this event to notify a widget that the cursor has entered it. This event is NOT bubbled. + * + * @param MyGeometry The Geometry of the widget receiving the event + * @param MouseEvent Information about the input event + */ + virtual void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + /** + * The system will use this event to notify a widget that the cursor has left it. This event is NOT bubbled. + * + * @param MouseEvent Information about the input event + */ + virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override; + + /** + * Called when the mouse wheel is spun. This event is bubbled. + * + * @param MouseEvent Mouse event + * + * @return Returns whether the event was handled, along with other possible actions + */ + virtual FReply OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + /** + * Called when a mouse button is double clicked. Override this in derived classes. + * + * @param InMyGeometry Widget geometry + * @param InMouseEvent Mouse button event + * + * @return Returns whether the event was handled, along with other possible actions + */ + virtual FReply OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + /** + * Called when the user is dropping something onto a widget; terminates drag and drop. + * + * @param MyGeometry The geometry of the widget receiving the event. + * @param DragDropEvent The drag and drop event. + * + * @return A reply that indicated whether this event was handled. + */ + virtual FReply OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override; + + /** + * Called during drag and drop when the drag enters a widget. + * + * Enter/Leave events in slate are meant as lightweight notifications. + * So we do not want to capture mouse or set focus in response to these. + * However, OnDragEnter must also support external APIs (e.g. OLE Drag/Drop) + * Those require that we let them know whether we can handle the content + * being dragged OnDragEnter. + * + * The concession is to return a can_handled/cannot_handle + * boolean rather than a full FReply. + * + * @param MyGeometry The geometry of the widget receiving the event. + * @param DragDropEvent The drag and drop event. + * + * @return A reply that indicated whether the contents of the DragDropEvent can potentially be processed by this widget. + */ + virtual void OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override; + + /** + * Called during drag and drop when the drag leaves a widget. + * + * @param DragDropEvent The drag and drop event. + */ + virtual void OnDragLeave(const FDragDropEvent& DragDropEvent) override; + + /** + * Called during drag and drop when the the mouse is being dragged over a widget. + * + * @param MyGeometry The geometry of the widget receiving the event. + * @param DragDropEvent The drag and drop event. + * + * @return A reply that indicated whether this event was handled. + */ + virtual FReply OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override; + + /** + * Called when the system wants to know which cursor to display for this Widget. This event is bubbled. + * + * @return The cursor requested (can be None.) + */ + virtual FCursorReply OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const override; + +protected: + virtual FVector2D ComputeDesiredSize(float) const override + { + return FVector2D(16.0f, 16.0f); + } + + void UpdateGraphTrack(); + +protected: + FTimeRulerTrack TimeRulerTrack; + FRandomGraphTrack GraphTrack; + FTimingTrackViewport Viewport; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SInsightsSettings.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SInsightsSettings.cpp new file mode 100644 index 000000000000..ce2133de1fb6 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SInsightsSettings.cpp @@ -0,0 +1,282 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SInsightsSettings.h" + +#include "EditorStyleSet.h" +#include "Fonts/SlateFontInfo.h" +#include "Misc/Paths.h" +#include "SlateOptMacros.h" +#include "Styling/CoreStyle.h" +#include "Styling/SlateTypes.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Layout/SGridPanel.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Text/STextBlock.h" + +// Insights +#include "Insights/InsightsManager.h" +#include "Insights/InsightsSettings.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define LOCTEXT_NAMESPACE "SInsightsSettings" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + +void SInsightsSettings::Construct(const FArguments& InArgs) +{ + OnClose = InArgs._OnClose; + SettingPtr = InArgs._SettingPtr; + + const TSharedRef SettingsGrid = SNew(SGridPanel); + int32 CurrentRowPos = 0; + + AddTitle(LOCTEXT("SettingsTitle","Unreal Insights - Settings"), SettingsGrid, CurrentRowPos); + AddSeparator(SettingsGrid, CurrentRowPos); + AddHeader(LOCTEXT("TimingProfilerTitle","TimingProfiler"), SettingsGrid, CurrentRowPos); + AddOption + ( + LOCTEXT("bShowEmptyTracksByDefault_T","Show empty CPU/GPU tracks in Timing View, by default"), + LOCTEXT("bShowEmptyTracksByDefault_TT","If True, empty CPU/GPU tracks will be visible in Timing View. Applies when session starts."), + SettingPtr->bShowEmptyTracksByDefault, + SettingPtr->GetDefaults().bShowEmptyTracksByDefault, + SettingsGrid, + CurrentRowPos + ); + AddSeparator(SettingsGrid, CurrentRowPos); + AddFooter(SettingsGrid, CurrentRowPos); + + ChildSlot + [ + SettingsGrid + ]; + + SettingPtr->EnterEditMode(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SInsightsSettings::AddTitle(const FText& TitleText, const TSharedRef& Grid, int32& RowPos) +{ + Grid->AddSlot(0, RowPos++) + .Padding(2.0f) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 18)) + .Text(TitleText) + ]; + RowPos++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SInsightsSettings::AddSeparator(const TSharedRef& Grid, int32& RowPos) +{ + Grid->AddSlot(0, RowPos++) + .Padding(2.0f) + .ColumnSpan(2) + [ + SNew(SSeparator) + .Orientation(Orient_Horizontal) + ]; + RowPos++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SInsightsSettings::AddHeader(const FText& HeaderText, const TSharedRef& Grid, int32& RowPos) +{ + Grid->AddSlot(0, RowPos++) + .Padding(2.0f) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 14)) + .Text(HeaderText) + ]; + RowPos++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SInsightsSettings::AddOption(const FText& OptionName, const FText& OptionDesc, bool& Value, const bool& Default, const TSharedRef& Grid, int32& RowPos) +{ + Grid->AddSlot(0, RowPos) + .Padding(2.0f) + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(OptionName) + .ToolTipText(OptionDesc) + ]; + + Grid->AddSlot(1, RowPos) + .Padding(2.0f) + .HAlign(HAlign_Center) + .VAlign(VAlign_Fill) + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(SCheckBox) + .IsChecked(this, &SInsightsSettings::OptionValue_IsChecked, (const bool*)&Value) + .OnCheckStateChanged(this, &SInsightsSettings::OptionValue_OnCheckStateChanged, (bool*)&Value) + ] + + +SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("ResetToDefaultToolTip", "Reset to default")) + .ButtonStyle(FEditorStyle::Get(), TEXT("NoBorder")) + .ContentPadding(0.0f) + .Visibility(this, &SInsightsSettings::OptionDefault_GetDiffersFromDefaultAsVisibility, (const bool*)&Value, (const bool*)&Default) + .OnClicked(this, &SInsightsSettings::OptionDefault_OnClicked, (bool*)&Value, (const bool*)&Default) + .Content() + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + RowPos++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SInsightsSettings::AddFooter(const TSharedRef& Grid, int32& RowPos) +{ + Grid->AddSlot(0, RowPos) + .Padding(2.0f) + .ColumnSpan(2) + .HAlign(HAlign_Right) + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .AutoWidth() + .MaxWidth(132) + .HAlign(HAlign_Center) + [ + SNew(SButton) + .OnClicked(this, &SInsightsSettings::SaveAndClose_OnClicked) + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("Profiler.Misc.Save16")) + ] + + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("SaveAndCloseTitle","Save and close")) + ] + ] + ] + + +SHorizontalBox::Slot() + .AutoWidth() + .MaxWidth(132) + .HAlign(HAlign_Center) + [ + SNew(SButton) + .OnClicked(this, &SInsightsSettings::ResetToDefaults_OnClicked) + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("Profiler.Misc.Reset16")) + ] + + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("ResetToDefaultsTitle","Reset to defaults")) + ] + ] + ] + ]; + + RowPos++; +} + +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SInsightsSettings::SaveAndClose_OnClicked() +{ + OnClose.ExecuteIfBound(); + SettingPtr->ExitEditMode(); + SettingPtr->SaveToConfig(); + + return FReply::Handled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SInsightsSettings::ResetToDefaults_OnClicked() +{ + SettingPtr->ResetToDefaults(); + + return FReply::Handled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SInsightsSettings::OptionValue_OnCheckStateChanged(ECheckBoxState CheckBoxState, bool* ValuePtr) +{ + *ValuePtr = CheckBoxState == ECheckBoxState::Checked ? true : false; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +ECheckBoxState SInsightsSettings::OptionValue_IsChecked(const bool* ValuePtr) const +{ + return *ValuePtr ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +EVisibility SInsightsSettings::OptionDefault_GetDiffersFromDefaultAsVisibility(const bool* ValuePtr, const bool* DefaultPtr) const +{ + return *ValuePtr != *DefaultPtr ? EVisibility::Visible : EVisibility::Hidden; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SInsightsSettings::OptionDefault_OnClicked(bool* ValuePtr, const bool* DefaultPtr) +{ + *ValuePtr = *DefaultPtr; + + return FReply::Handled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SInsightsSettings.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SInsightsSettings.h new file mode 100644 index 000000000000..201d729d5abc --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SInsightsSettings.h @@ -0,0 +1,53 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Input/Reply.h" +#include "Layout/Visibility.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" + +class FInsightsSettings; +class SGridPanel; +enum class ECheckBoxState : uint8; + +/** Widget used to modify settings for the profiler, created on demand and destroyed on close. */ +class SInsightsSettings : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SInsightsSettings) {} + SLATE_EVENT(FSimpleDelegate, OnClose) + SLATE_ARGUMENT(FInsightsSettings*, SettingPtr) + SLATE_END_ARGS() + + /** + * Construct this widget + * + * @param InArgs The declaration data for this widget. + */ + void Construct(const FArguments& InArgs); + +private: + void AddTitle(const FText& TitleText, const TSharedRef& Grid, int32& RowPos); + void AddSeparator(const TSharedRef& Grid, int32& RowPos); + void AddHeader(const FText& HeaderText, const TSharedRef& Grid, int32& RowPos); + void AddOption(const FText& OptionName, const FText& OptionDesc, bool& Value, const bool& Default, const TSharedRef& Grid, int32& RowPos); + void AddFooter(const TSharedRef& Grid, int32& RowPos); + + FReply SaveAndClose_OnClicked(); + FReply ResetToDefaults_OnClicked(); + + EVisibility OptionDefault_GetDiffersFromDefaultAsVisibility(const bool* ValuePtr, const bool* DefaultPtr) const; + FReply OptionDefault_OnClicked(bool* ValuePtr, const bool* DefaultPtr); + + void OptionValue_OnCheckStateChanged(ECheckBoxState CheckBoxState, bool* ValuePtr); + ECheckBoxState OptionValue_IsChecked(const bool* ValuePtr) const; + +private: + /** Delegate to call when this profiler settings is closed. */ + FSimpleDelegate OnClose; + + /** Pointer to the profiler settings. */ + FInsightsSettings* SettingPtr; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SIoProfilerWindow.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SIoProfilerWindow.cpp new file mode 100644 index 000000000000..d97177c9dba6 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SIoProfilerWindow.cpp @@ -0,0 +1,240 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SIoProfilerWindow.h" + +#include "EditorStyleSet.h" +#include "SlateOptMacros.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Layout/SSpacer.h" +#include "Widgets/Layout/SSplitter.h" +#include "Widgets/Notifications/SNotificationList.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/SOverlay.h" +#include "Widgets/Text/STextBlock.h" + +#if WITH_EDITOR + #include "EngineAnalytics.h" + #include "Runtime/Analytics/Analytics/Public/AnalyticsEventAttribute.h" + #include "Runtime/Analytics/Analytics/Public/Interfaces/IAnalyticsProvider.h" +#endif // WITH_EDITOR + +// Insights +#include "Insights/Version.h" +#include "Insights/InsightsManager.h" +#include "Insights/IoProfilerManager.h" +#include "Insights/Widgets/STimingView.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define LOCTEXT_NAMESPACE "SIoProfilerWindow" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +SIoProfilerWindow::SIoProfilerWindow() + : DurationActive(0.0f) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +SIoProfilerWindow::~SIoProfilerWindow() +{ +#if WITH_EDITOR + if (DurationActive > 0.0f && FEngineAnalytics::IsAvailable()) + { + FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.Profiler"), FAnalyticsEventAttribute(TEXT("Duration"), DurationActive)); + } +#endif // WITH_EDITOR +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SIoProfilerWindow::Construct(const FArguments& InArgs) +{ + ChildSlot + [ + SNew(SOverlay) + + // Version + + SOverlay::Slot() + .HAlign(HAlign_Right) + .VAlign(VAlign_Top) + .Padding(0.0f, -16.0f, 0.0f, 0.0f) + [ + SNew(STextBlock) + .Clipping(EWidgetClipping::ClipToBoundsWithoutIntersecting) + .Text(LOCTEXT("UnrealInsightsVersion", UNREAL_INSIGHTS_VERSION_STRING_EX)) + .ColorAndOpacity(FLinearColor(0.15f, 0.15f, 0.15f, 1.0f)) + ] + + // Overlay slot for the main profiler window area + + SOverlay::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + [ + SNew(SBox) + .Visibility(this, &SIoProfilerWindow::IsTimingViewVisible) + .IsEnabled(this, &SIoProfilerWindow::IsProfilerEnabled) + [ + SAssignNew(TimingView, STimingView) + ] + ] + + // Session hint overlay + + SOverlay::Slot() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(SBorder) + .Visibility(this, &SIoProfilerWindow::IsSessionOverlayVisible) + .BorderImage(FEditorStyle::GetBrush("NotificationList.ItemBackground")) + .Padding(8.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("SelectTraceOverlayText", "Please select a trace.")) + ] + ] + ]; + + TimingView->EnableAssetLoadingMode(); +} + +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +EVisibility SIoProfilerWindow::IsTimingViewVisible() const +{ + if (FInsightsManager::Get()->GetSession().IsValid()) + { + return EVisibility::Visible; + } + else + { + return EVisibility::Collapsed; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +EVisibility SIoProfilerWindow::IsSessionOverlayVisible() const +{ + if (FInsightsManager::Get()->GetSession().IsValid()) + { + return EVisibility::Hidden; + } + else + { + return EVisibility::Visible; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SIoProfilerWindow::IsProfilerEnabled() const +{ + return FInsightsManager::Get()->GetSession().IsValid(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +EActiveTimerReturnType SIoProfilerWindow::UpdateActiveDuration(double InCurrentTime, float InDeltaTime) +{ + DurationActive += InDeltaTime; + + // The profiler window will explicitly unregister this active timer when the mouse leaves. + return EActiveTimerReturnType::Continue; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SIoProfilerWindow::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + SCompoundWidget::OnMouseEnter(MyGeometry, MouseEvent); + + if (!ActiveTimerHandle.IsValid()) + { + ActiveTimerHandle = RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SIoProfilerWindow::UpdateActiveDuration)); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SIoProfilerWindow::OnMouseLeave(const FPointerEvent& MouseEvent) +{ + SCompoundWidget::OnMouseLeave(MouseEvent); + + auto PinnedActiveTimerHandle = ActiveTimerHandle.Pin(); + if (PinnedActiveTimerHandle.IsValid()) + { + UnRegisterActiveTimer(PinnedActiveTimerHandle.ToSharedRef()); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SIoProfilerWindow::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + return FIoProfilerManager::Get()->GetCommandList()->ProcessCommandBindings(InKeyEvent) ? FReply::Handled() : FReply::Unhandled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SIoProfilerWindow::OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) +{ + TSharedPtr DragDropOp = DragDropEvent.GetOperationAs(); + if (DragDropOp.IsValid()) + { + if (DragDropOp->HasFiles()) + { + const TArray& Files = DragDropOp->GetFiles(); + if (Files.Num() == 1) + { + const FString DraggedFileExtension = FPaths::GetExtension(Files[0], true); + if (DraggedFileExtension == TEXT(".utrace")) + { + return FReply::Handled(); + } + } + } + } + + return SCompoundWidget::OnDragOver(MyGeometry,DragDropEvent); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SIoProfilerWindow::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) +{ + TSharedPtr DragDropOp = DragDropEvent.GetOperationAs(); + if (DragDropOp.IsValid()) + { + if (DragDropOp->HasFiles()) + { + // For now, only allow a single file. + const TArray& Files = DragDropOp->GetFiles(); + if (Files.Num() == 1) + { + const FString DraggedFileExtension = FPaths::GetExtension(Files[0], true); + if (DraggedFileExtension == TEXT(".utrace")) + { + // Enqueue load operation. + FInsightsManager::Get()->LoadTraceFile(Files[0]); + return FReply::Handled(); + } + } + } + } + + return SCompoundWidget::OnDrop(MyGeometry,DragDropEvent); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SIoProfilerWindow.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SIoProfilerWindow.h new file mode 100644 index 000000000000..392f2537516a --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SIoProfilerWindow.h @@ -0,0 +1,113 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Input/Reply.h" +#include "Layout/Visibility.h" +#include "Misc/Guid.h" +#include "SlateFwd.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" + +// Insights +#include "Insights/InsightsManager.h" + +class FActiveTimerHandle; +class STimingView; + +/** Implements the timing profiler window. */ +class SIoProfilerWindow : public SCompoundWidget +{ +public: + /** Default constructor. */ + SIoProfilerWindow(); + + /** Virtual destructor. */ + virtual ~SIoProfilerWindow(); + + SLATE_BEGIN_ARGS(SIoProfilerWindow){} + SLATE_END_ARGS() + + /** Constructs this widget. */ + void Construct(const FArguments& InArgs); + + TSharedPtr GetTimingView() const { return TimingView; } + +private: + /** Callback for determining the visibility of the Timing view. */ + EVisibility IsTimingViewVisible() const; + + /** Callback for determining the visibility of the 'Select a session' overlay. */ + EVisibility IsSessionOverlayVisible() const; + + /** Callback for getting the enabled state of the profiler window. */ + bool IsProfilerEnabled() const; + + /** Updates the amount of time the profiler has been active. */ + EActiveTimerReturnType UpdateActiveDuration(double InCurrentTime, float InDeltaTime); + + /** + * Ticks this widget. Override in derived classes, but always call the parent implementation. + * + * @param AllottedGeometry The space allotted for this widget + * @param InCurrentTime Current absolute real time + * @param InDeltaTime Real time passed since last tick + */ + //virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; + + /** + * The system will use this event to notify a widget that the cursor has entered it. This event is NOT bubbled. + * + * @param MyGeometry The Geometry of the widget receiving the event + * @param MouseEvent Information about the input event + */ + virtual void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + /** + * The system will use this event to notify a widget that the cursor has left it. This event is NOT bubbled. + * + * @param MouseEvent Information about the input event + */ + virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override; + + /** + * Called after a key is pressed when this widget has focus + * + * @param MyGeometry The Geometry of the widget receiving the event + * @param InKeyEvent Key event + * + * @return Returns whether the event was handled, along with other possible actions + */ + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; + + /** + * Called when the user is dropping something onto a widget; terminates drag and drop. + * + * @param MyGeometry The geometry of the widget receiving the event. + * @param DragDropEvent The drag and drop event. + * + * @return A reply that indicated whether this event was handled. + */ + virtual FReply OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override; + + /** + * Called during drag and drop when the the mouse is being dragged over a widget. + * + * @param MyGeometry The geometry of the widget receiving the event. + * @param DragDropEvent The drag and drop event. + * + * @return A reply that indicated whether this event was handled. + */ + virtual FReply OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override; + +private: + /** Widget for the timing track */ + TSharedPtr TimingView; + + /** The number of seconds the profiler has been active */ + float DurationActive; + + /** The handle to the active update duration tick */ + TWeakPtr ActiveTimerHandle; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SLogView.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SLogView.cpp new file mode 100644 index 000000000000..b88d367604a7 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SLogView.cpp @@ -0,0 +1,1275 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SLogView.h" + +#include "Algo/BinarySearch.h" +#include "Async/AsyncWork.h" +#include "EditorStyleSet.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "SlateOptMacros.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SComboButton.h" +#include "Widgets/Layout/SScrollBox.h" +#include "Widgets/Text/STextBlock.h" + +// Insights +#include "Insights/Common/TimeUtils.h" +#include "Insights/InsightsManager.h" +#include "Insights/TimingProfilerCommon.h" +#include "Insights/TimingProfilerManager.h" +#include "Insights/ViewModels/TimingViewDrawHelper.h" +#include "Insights/Widgets/STimingProfilerWindow.h" +#include "Insights/Widgets/STimingView.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define LOCTEXT_NAMESPACE "SLogView" + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// SLogMessageRow +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class SLogMessageRow : public SMultiColumnTableRow> +{ + SLATE_BEGIN_ARGS(SLogMessageRow) {} + SLATE_END_ARGS() + +public: + /** + * Constructs the widget. + * + * @param InArgs The construction arguments. + * @param InLogMessage The log message displayed by this row. + * @param InOwnerTableView The table to which the row must be added. + */ + void Construct(const FArguments& InArgs, TSharedPtr InLogMessage, TSharedRef InParentWidget, const TSharedRef& InOwnerTableView) + { + WeakLogMessage = MoveTemp(InLogMessage); + WeakParentWidget = InParentWidget; + + SMultiColumnTableRow>::Construct(FSuperRowType::FArguments(), InOwnerTableView); + + TSharedRef Row = ChildSlot.GetChildAt(0); + + ChildSlot + [ + SNew(SBorder) + .BorderImage(FCoreStyle::Get().GetBrush("WhiteBrush")) + .BorderBackgroundColor(this, &SLogMessageRow::GetBackgroundColor) + [ + Row + ] + ]; + } + +public: + virtual TSharedRef GenerateWidgetForColumn(const FName& ColumnName) override + { + if (ColumnName == LogViewColumns::IdColumnName) + { + return SNew(SBox) + .Padding(FMargin(4.0, 0.0)) + [ + SNew(STextBlock) + .Text(this, &SLogMessageRow::GetIndex) + ]; + } + else if (ColumnName == LogViewColumns::TimeColumnName) + { + return SNew(SBox) + .Padding(FMargin(4.0, 0.0)) + [ + SNew(STextBlock) + .Text(this, &SLogMessageRow::GetTime) + .ColorAndOpacity(this, &SLogMessageRow::GetColorAndOpacity) + ]; + } + else if (ColumnName == LogViewColumns::VerbosityColumnName) + { + return SNew(SBox) + .Padding(FMargin(4.0, 0.0)) + [ + SNew(STextBlock) + .Text(this, &SLogMessageRow::GetVerbosity) + .ColorAndOpacity(this, &SLogMessageRow::GetColorByVerbosity) + ]; + } + else if (ColumnName == LogViewColumns::CategoryColumnName) + { + return SNew(SBox) + .Padding(FMargin(4.0, 0.0)) + [ + SNew(STextBlock) + .Text(this, &SLogMessageRow::GetCategory) + .ColorAndOpacity(this, &SLogMessageRow::GetColorByCategory) + ]; + } + else if (ColumnName == LogViewColumns::MessageColumnName) + { + return SNew(SBox) + .Padding(FMargin(4.0, 0.0)) + [ + SNew(STextBlock) + .Text(this, &SLogMessageRow::GetMessage) + .HighlightText(this, &SLogMessageRow::GetMessageHighlightText) + ]; + } + else if (ColumnName == LogViewColumns::FileColumnName) + { + return SNew(SBox) + .Padding(FMargin(4.0, 0.0)) + [ + SNew(STextBlock) + .Text(this, &SLogMessageRow::GetFile) + ]; + } + else if (ColumnName == LogViewColumns::LineColumnName) + { + return SNew(SBox) + .Padding(FMargin(4.0, 0.0)) + [ + SNew(STextBlock) + .Text(this, &SLogMessageRow::GetLine) + ]; + } + else + { + return SNew(STextBlock).Text(LOCTEXT("UnknownColumn", "Unknown Column")); + } + } + + FSlateColor GetBackgroundColor() const + { + TSharedPtr ParentWidgetPin = WeakParentWidget.Pin(); + TSharedPtr LogMessagePin = WeakLogMessage.Pin(); + if (ParentWidgetPin.IsValid() && LogMessagePin.IsValid()) + { + TSharedPtr SelectedLogMessage = ParentWidgetPin->GetSelectedLogMessage(); + if (!SelectedLogMessage || SelectedLogMessage->GetIndex() != LogMessagePin->GetIndex()) // if row is not selected + { + FLogMessageRecord& CacheEntry = ParentWidgetPin->GetCache().Get(LogMessagePin->GetIndex()); + const double Time = CacheEntry.Time; + + TSharedPtr Window = FTimingProfilerManager::Get()->GetProfilerWindow(); + if (Window && Window->TimingView) + { + if (Window->TimingView->IsTimeSelectedInclusive(Time)) + { + return FSlateColor(FLinearColor(0.25f, 0.5f, 1.0f, 0.25f)); + } + } + } + } + + return FSlateColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.0f)); + } + + FSlateColor GetColorAndOpacity() const + { + bool IsSelected = false; + + TSharedPtr ParentWidgetPin = WeakParentWidget.Pin(); + TSharedPtr LogMessagePin = WeakLogMessage.Pin(); + if (ParentWidgetPin.IsValid() && LogMessagePin.IsValid()) + { + TSharedPtr SelectedLogMessage = ParentWidgetPin->GetSelectedLogMessage(); + if (SelectedLogMessage && SelectedLogMessage->GetIndex() == LogMessagePin->GetIndex()) + { + IsSelected = true; + } + + FLogMessageRecord& CacheEntry = ParentWidgetPin->GetCache().Get(LogMessagePin->GetIndex()); + const double Time = CacheEntry.Time; + + TSharedPtr Window = FTimingProfilerManager::Get()->GetProfilerWindow(); + if (Window && Window->TimingView) + { + if (Window->TimingView->IsTimeSelectedInclusive(Time)) + { + if (IsSelected) + { + //return FSlateColor(FLinearColor(0.0f, 0.1f, 0.5f, 1.0f)); + return FSlateColor(FLinearColor(0.0f, 0.05f, 0.2f, 1.0f)); + } + else + { + //return FSlateColor(FLinearColor(0.2f, 0.4f, 0.8f, 1.0f)); + return FSlateColor(FLinearColor(0.4f, 0.8f, 1.6f, 1.0f)); + } + } + } + } + + if (IsSelected) + { + return FSlateColor(FLinearColor::Black); + } + else + { + return FSlateColor(FLinearColor::White); + } + } + + FText GetIndex() const + { + TSharedPtr ParentWidgetPin = WeakParentWidget.Pin(); + TSharedPtr LogMessagePin = WeakLogMessage.Pin(); + if (ParentWidgetPin.IsValid() && LogMessagePin.IsValid()) + { + FLogMessageRecord& CacheEntry = ParentWidgetPin->GetCache().Get(LogMessagePin->GetIndex()); + return CacheEntry.GetIndexAsText(); + } + else + { + return FText(); + } + } + + FText GetTime() const + { + TSharedPtr ParentWidgetPin = WeakParentWidget.Pin(); + TSharedPtr LogMessagePin = WeakLogMessage.Pin(); + if (ParentWidgetPin.IsValid() && LogMessagePin.IsValid()) + { + FLogMessageRecord& CacheEntry = ParentWidgetPin->GetCache().Get(LogMessagePin->GetIndex()); + return CacheEntry.GetTimeAsText(); + } + else + { + return FText(); + } + } + + FText GetVerbosity() const + { + TSharedPtr ParentWidgetPin = WeakParentWidget.Pin(); + TSharedPtr LogMessagePin = WeakLogMessage.Pin(); + if (ParentWidgetPin.IsValid() && LogMessagePin.IsValid()) + { + FLogMessageRecord& CacheEntry = ParentWidgetPin->GetCache().Get(LogMessagePin->GetIndex()); + return CacheEntry.GetVerbosityAsText(); + } + else + { + return FText(); + } + } + + FSlateColor GetColorByVerbosity() const + { + if (IsSelected()) + return FSlateColor(FLinearColor::Black); + + TSharedPtr ParentWidgetPin = WeakParentWidget.Pin(); + TSharedPtr LogMessagePin = WeakLogMessage.Pin(); + if (ParentWidgetPin.IsValid() && LogMessagePin.IsValid()) + { + FLogMessageRecord& CacheEntry = ParentWidgetPin->GetCache().Get(LogMessagePin->GetIndex()); + return FSlateColor(FTimeMarkerTrackBuilder::GetColorByVerbosity(CacheEntry.Verbosity)); + } + else + { + return FSlateColor(FLinearColor::White); + } + } + + FText GetCategory() const + { + TSharedPtr ParentWidgetPin = WeakParentWidget.Pin(); + TSharedPtr LogMessagePin = WeakLogMessage.Pin(); + if (ParentWidgetPin.IsValid() && LogMessagePin.IsValid()) + { + FLogMessageRecord& CacheEntry = ParentWidgetPin->GetCache().Get(LogMessagePin->GetIndex()); + return CacheEntry.GetCategoryAsText(); + } + else + { + return FText(); + } + } + + FSlateColor GetColorByCategory() const + { + if (IsSelected()) + return FSlateColor(FLinearColor::Black); + + TSharedPtr ParentWidgetPin = WeakParentWidget.Pin(); + TSharedPtr LogMessagePin = WeakLogMessage.Pin(); + if (ParentWidgetPin.IsValid() && LogMessagePin.IsValid()) + { + FLogMessageRecord& CacheEntry = ParentWidgetPin->GetCache().Get(LogMessagePin->GetIndex()); + return FSlateColor(FTimeMarkerTrackBuilder::GetColorByCategory(*CacheEntry.Category.ToString())); + } + else + { + return FSlateColor(FLinearColor::White); + } + } + + FText GetMessage() const + { + TSharedPtr ParentWidgetPin = WeakParentWidget.Pin(); + TSharedPtr LogMessagePin = WeakLogMessage.Pin(); + if (ParentWidgetPin.IsValid() && LogMessagePin.IsValid()) + { + FLogMessageRecord& CacheEntry = ParentWidgetPin->GetCache().Get(LogMessagePin->GetIndex()); + return CacheEntry.GetMessageAsText(); + } + else + { + return FText(); + } + } + + FText GetMessageHighlightText() const + { + TSharedPtr ParentWidgetPin = WeakParentWidget.Pin(); + if (ParentWidgetPin.IsValid()) + { + return ParentWidgetPin->GetFilterText(); + } + else + { + return FText(); + } + } + + FText GetFile() const + { + TSharedPtr ParentWidgetPin = WeakParentWidget.Pin(); + TSharedPtr LogMessagePin = WeakLogMessage.Pin(); + if (ParentWidgetPin.IsValid() && LogMessagePin.IsValid()) + { + FLogMessageRecord& CacheEntry = ParentWidgetPin->GetCache().Get(LogMessagePin->GetIndex()); + return CacheEntry.GetFileAsText(); + } + else + { + return FText(); + } + } + + FText GetLine() const + { + TSharedPtr ParentWidgetPin = WeakParentWidget.Pin(); + TSharedPtr LogMessagePin = WeakLogMessage.Pin(); + if (ParentWidgetPin.IsValid() && LogMessagePin.IsValid()) + { + FLogMessageRecord& CacheEntry = ParentWidgetPin->GetCache().Get(LogMessagePin->GetIndex()); + return CacheEntry.GetLineAsText(); + } + else + { + return FText(); + } + } + + FText GetRowToolTip() const + { + TSharedPtr ParentWidgetPin = WeakParentWidget.Pin(); + TSharedPtr LogMessagePin = WeakLogMessage.Pin(); + if (ParentWidgetPin.IsValid() && LogMessagePin.IsValid()) + { + FLogMessageRecord& CacheEntry = ParentWidgetPin->GetCache().Get(LogMessagePin->GetIndex()); + return CacheEntry.ToDisplayString(); + } + else + { + return FText(); + } + } + +private: + TWeakPtr WeakLogMessage; + TWeakPtr WeakParentWidget; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// SLogView +//////////////////////////////////////////////////////////////////////////////////////////////////// + +SLogView::SLogView() + : FilteringStartIndex(0) + , FilteringEndIndex(0) + , FilteringChangeNumber(0) + , bIsFilteringAsyncTaskCancelRequested(false) + , TotalNumCategories(0) + , TotalNumMessages(0) + , bIsDirty(false) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +SLogView::~SLogView() +{ + // Remove ourselves from the profiler manager. + //if (FTimingProfilerManager::Get().IsValid()) + //{ + // //TODO: FTimimgProfilerManager::Get()->OnRequestLogViewUpdate().RemoveAll(this); + //} + + Reset(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SLogView::Reset() +{ + //ListView + //ExternalScrollbar + FilterTextBox->SetText(FText::GetEmpty()); + + Filter.Reset(); + FilterChangeNumber = 0; + + FilteringStartIndex = 0; + FilteringEndIndex = 0; + FilteringChangeNumber = 0; + + // Clean up our async task if we're deleted before it is completed. + if (FilteringAsyncTask.IsValid()) + { + if (!FilteringAsyncTask->Cancel()) + { + bIsFilteringAsyncTaskCancelRequested = true; + FilteringAsyncTask->EnsureCompletion(); + } + FilteringAsyncTask.Reset(); + } + + //bIsFilteringAsyncTaskCancelRequested = false; + //FilteringStopwatch.Stop(); + + TotalNumCategories = 0; + TotalNumMessages = 0; + + bIsDirty = false; + DirtyStopwatch.Stop(); + + StatsText = FText::GetEmpty(); + + Cache.Reset(); + + Messages.Reset(); + + ListView->RebuildList(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION +void SLogView::Construct(const FArguments& InArgs) +{ + SAssignNew(ExternalScrollbar, SScrollBar) + .AlwaysShowScrollbar(true); + + ChildSlot + [ + SNew(SVerticalBox) + + // Toolbar + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + // Icon + //+ SHorizontalBox::Slot() + //.VAlign(VAlign_Center) + //.AutoWidth() + //[ + // SNew(SImage) + // .Image(FEditorStyle::GetBrush(TEXT("Log.TabIcon"))) + //] + // + //// Label + //+ SHorizontalBox::Slot() + //.VAlign(VAlign_Center) + //.Padding(2.0f, 0.0f) + //.AutoWidth() + //[ + // SNew(STextBlock) + // .Text(LOCTEXT("LogViewLabel", "Log View")) + //] + + // Verbosity Threshold Filter + +SHorizontalBox::Slot() + //.Padding(2.0f, 0.0f, 0.0f, 0.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SComboButton) + .ComboButtonStyle(FEditorStyle::Get(), "GenericFilters.ComboButtonStyle") + .ForegroundColor(FLinearColor::White) + .ContentPadding(0) + .ToolTipText(LOCTEXT("VerbosityThresholdFilterToolTip", "Filter log messages by verbosity threshold.")) + .OnGetMenuContent(this, &SLogView::MakeVerbosityThresholdMenu) + .HasDownArrow(true) + .ContentPadding(FMargin(1.0f, 1.0f, 1.0f, 0.0f)) + .ButtonContent() + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .Padding(0.0f) + .AutoWidth() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "GenericFilters.TextStyle") + .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.9")) + .Text(FText::FromString(FString(TEXT("\xf0b0"))) /*fa-filter*/) + ] + + +SHorizontalBox::Slot() + .Padding(2.0f, 0.0f, 0.0f, 0.0f) + .AutoWidth() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "GenericFilters.TextStyle") + .Text(LOCTEXT("VerbosityThresholdFilter", "Verbosity Threshold")) + ] + ] + ] + + // Category Filter + +SHorizontalBox::Slot() + .Padding(2.0f, 0.0f, 0.0f, 0.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SComboButton) + .ComboButtonStyle(FEditorStyle::Get(), "GenericFilters.ComboButtonStyle") + .ForegroundColor(FLinearColor::White) + .ContentPadding(0) + .ToolTipText(LOCTEXT("CategoryFilterToolTip", "Filter log messages by category.")) + .OnGetMenuContent(this, &SLogView::MakeCategoryFilterMenu) + .HasDownArrow(true) + .ContentPadding(FMargin(1.0f, 1.0f, 1.0f, 0.0f)) + .ButtonContent() + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .Padding(0.0f) + .AutoWidth() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "GenericFilters.TextStyle") + .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.9")) + .Text(FText::FromString(FString(TEXT("\xf0b0"))) /*fa-filter*/) + ] + + +SHorizontalBox::Slot() + .Padding(2.0f, 0.0f, 0.0f, 0.0f) + .AutoWidth() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "GenericFilters.TextStyle") + .Text(LOCTEXT("CategoryFilter", "Category Filter")) + ] + ] + ] + + // Text Filter (Search Box) + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .Padding(2.0f, 0.0f) + .AutoWidth() + [ + SAssignNew(FilterTextBox, SSearchBox) + .HintText(LOCTEXT("FilterTextBoxHint", "Search log messages")) + .ToolTipText(LOCTEXT("FilterTextBoxToolTip", "Type here to filter the list of log messages.")) + .OnTextChanged(this, &SLogView::FilterTextBox_OnTextChanged) + ] + + // Stats Text + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .Padding(2.0f, 0.0f) + .AutoWidth() + [ + SNew(STextBlock) + .Text(this, &SLogView::GetStatsText) + .ColorAndOpacity(this, &SLogView::GetStatsTextColor) + ] + ] + + + SVerticalBox::Slot() + .FillHeight(1.0f) + [ + SNew(SBox) + .VAlign(VAlign_Fill) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(0.0f) + .VAlign(VAlign_Fill) + [ + SNew(SScrollBox) + .Orientation(Orient_Horizontal) + + + SScrollBox::Slot() + .VAlign(VAlign_Fill) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(0.0f) + [ + SAssignNew(ListView, SListView>) + .ExternalScrollbar(ExternalScrollbar) + .ItemHeight(20.0f) + .SelectionMode(ESelectionMode::Single) + .OnSelectionChanged(this, &SLogView::OnSelectionChanged) + .ListItemsSource(&Messages) + .OnGenerateRow(this, &SLogView::OnGenerateRow) + .ConsumeMouseWheel(EConsumeMouseWheel::Always) + .OnContextMenuOpening(FOnContextMenuOpening::CreateSP(this, &SLogView::ListView_GetContextMenu)) + .HeaderRow + ( + SNew(SHeaderRow) + + + SHeaderRow::Column(LogViewColumns::IdColumnName) + .ManualWidth(60.0f) + .DefaultLabel(LOCTEXT("IdColumn", "Index")) + + + SHeaderRow::Column(LogViewColumns::TimeColumnName) + .ManualWidth(94.0f) + .DefaultLabel(LOCTEXT("TimeColumn", "Time")) + + + SHeaderRow::Column(LogViewColumns::VerbosityColumnName) + .ManualWidth(80.0f) + .DefaultLabel(LOCTEXT("VerbosityColumn", "Verbosity")) + + + SHeaderRow::Column(LogViewColumns::CategoryColumnName) + .ManualWidth(120.0f) + .DefaultLabel(LOCTEXT("CategoryColumn", "Category")) + + + SHeaderRow::Column(LogViewColumns::MessageColumnName) + .ManualWidth(880.0f) + .DefaultLabel(LOCTEXT("MessageColumn", "Message")) + + + SHeaderRow::Column(LogViewColumns::FileColumnName) + .ManualWidth(600.0f) + .DefaultLabel(LOCTEXT("FileColumn", "File")) + + + SHeaderRow::Column(LogViewColumns::LineColumnName) + .ManualWidth(60.0f) + .DefaultLabel(LOCTEXT("LineColumn", "Line")) + ) + ] + ] + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f) + [ + SNew(SBox) + .WidthOverride(FOptionalSize(13.0f)) + [ + ExternalScrollbar.ToSharedRef() + ] + ] + ] + ] + ]; + + // Register ourselves with the profiler manager. + //TODO: FTimingProfilerManager::Get()->OnRequestLogViewUpdate().AddSP(this, &SLogView::ProfilerManager_OnRequestLogViewUpdate); +} +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef SLogView::OnGenerateRow(TSharedPtr InLogMessage, const TSharedRef& OwnerTable) +{ + // Generate a row for the log message corresponding to InLogMessage. + return SNew(SLogMessageRow, InLogMessage, SharedThis(this), OwnerTable); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SLogView::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) +{ + int32 NewMessageCount = 0; + + TSharedPtr Session = FInsightsManager::Get()->GetSession(); + + Cache.SetSession(Session); + + if (Session.IsValid()) + { + Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); + const Trace::ILogProvider& LogProvider = Trace::ReadLogProvider(*Session.Get()); + + NewMessageCount = static_cast(LogProvider.GetMessageCount()); + + //TODO: show only categories that are used in current trace + //TODO: cause of duplicates: a) runtime, b) case insensitive, c) stripped "Log" prefix + //TODO: FString vs. FText vs. FName ? + + //TODO: int32 NumCategories = static_cast(LogProvider.GetCategoriesCount()); + int32 NumCategories = 0; + LogProvider.EnumerateCategories([&NumCategories](const Trace::FLogCategory& Category) + { + NumCategories++; + }); + + if (NumCategories != TotalNumCategories) + { + TotalNumCategories = NumCategories; + UE_LOG(TimingProfiler, Log, TEXT("[LogView] Total Log Categories: %d"), TotalNumCategories); + + TSet Categories; + LogProvider.EnumerateCategories([&Categories](const Trace::FLogCategory& Category) + { + FString CategoryStr(Category.Name); + if (CategoryStr.StartsWith(TEXT("Log"))) + { + CategoryStr = CategoryStr.RightChop(3); + } + if (Categories.Contains(FName(*CategoryStr))) + { + UE_LOG(TimingProfiler, Log, TEXT("[LogView] Duplicated Log Category: \"%s\""), Category.Name); + } + Categories.Add(FName(*CategoryStr)); + }); + Filter.SyncAvailableCategories(Categories); + UE_LOG(TimingProfiler, Log, TEXT("[LogView] Unique Log Categories: %d"), Filter.GetAvailableLogCategories().Num()); + + //Cache.Reset(); + Messages.Reset(); + TotalNumMessages = 0; + //ListView->RebuildList(); + bIsDirty = true; + DirtyStopwatch.Start(); + } + } + + if (NewMessageCount != TotalNumMessages) + { + bIsDirty = true; + DirtyStopwatch.Start(); + + if (NewMessageCount > TotalNumMessages) + { + if (Filter.IsFilterSet()) + { + // Filter messages async. + if (!FilteringAsyncTask.IsValid()) + { + FilteringStopwatch.Restart(); + + bIsFilteringAsyncTaskCancelRequested = false; + FilteringStartIndex = TotalNumMessages; + FilteringEndIndex = NewMessageCount; + FilteringChangeNumber = Filter.GetChangeNumber(); + FilteringAsyncTask = MakeUnique>(FilteringStartIndex, FilteringEndIndex, Filter, SharedThis(this)); + UE_LOG(TimingProfiler, Log, TEXT("[LogView] Start async task for filtering by%s%s%s (\"%s\") (%d to %d)"), + Filter.IsFilterSetByVerbosity() ? TEXT(" Verbosity,") : TEXT(""), + Filter.IsFilterSetByCategory() ? TEXT(" Category,") : TEXT(""), + Filter.IsFilterSetByText() ? TEXT(" Text") : TEXT(""), + *Filter.GetFilterText().ToString(), + FilteringStartIndex, FilteringEndIndex); + FilteringAsyncTask->StartBackgroundTask(); + } + else + { + // A task is already in progress. + if (FilteringStartIndex == TotalNumMessages && + FilteringEndIndex <= NewMessageCount && + FilteringChangeNumber == Filter.GetChangeNumber()) + { + // The filter is still valid. Just wait. + } + else + { + // The filter used by running task is obsolete. Cancel the task. + bIsFilteringAsyncTaskCancelRequested = true; + } + } + } + else // no filtering + { + FilteringStopwatch.Restart(); + + for (int32 Index = TotalNumMessages; Index < NewMessageCount; Index++) + { + FLogMessage LogMessage(Index); + Messages.Add(MakeShared(MoveTemp(LogMessage))); + } + + const int32 NumAddedMessages = NewMessageCount - TotalNumMessages; + TotalNumMessages = NewMessageCount; + TSharedPtr SelectedLogMessage = GetSelectedLogMessage(); + ListView->RebuildList(); + if (SelectedLogMessage.IsValid()) + { + SelectedLogMessageByLogIndex(SelectedLogMessage->GetIndex()); + } + bIsDirty = false; + DirtyStopwatch.Reset(); + UpdateStatsText(); + + FilteringStopwatch.Stop(); + uint64 DurationMs = FilteringStopwatch.GetAccumulatedTimeMs(); + if (DurationMs > 10) // avoids spams + { + UE_LOG(TimingProfiler, Log, TEXT("[LogView] Updated (no filter; %d added / %d total messages) in %llu ms."), + NumAddedMessages, NewMessageCount, DurationMs); + } + } + } + else // if (NewMessageCount < TotalNumMessages) + { + // Just reset. On next Tick() the list will grow if needed. + UE_LOG(TimingProfiler, Log, TEXT("[LogView] RESET")); + Cache.Reset(); + Messages.Reset(); + TotalNumMessages = 0; + ListView->RebuildList(); + bIsDirty = (NewMessageCount != 0); + if (bIsDirty) + { + DirtyStopwatch.Start(); + } + else + { + DirtyStopwatch.Reset(); + } + UpdateStatsText(); + } + } + + if (FilteringAsyncTask.IsValid() && + FilteringAsyncTask->IsDone()) + { + // A filtering async task has completed. Check if filter used is still valid. + if (!bIsFilteringAsyncTaskCancelRequested && + FilteringStartIndex == TotalNumMessages && + FilteringEndIndex <= NewMessageCount && + FilteringChangeNumber == Filter.GetChangeNumber()) + { + FLogFilteringAsyncTask& Task = FilteringAsyncTask->GetTask(); + const TArray& FilteredMessages = Task.GetFilteredMessages(); + + // Add filtered messages to current Messages array. + const int32 NumFilteredMessages = FilteredMessages.Num(); + for (int32 Index = 0; Index < NumFilteredMessages; Index++) + { + FLogMessage LogMessage(FilteredMessages[Index]); + Messages.Add(MakeShared(MoveTemp(LogMessage))); + } + + TotalNumMessages = Task.GetEndIndex(); + TSharedPtr SelectedLogMessage = GetSelectedLogMessage(); + ListView->RebuildList(); + if (SelectedLogMessage.IsValid()) + { + SelectedLogMessageByLogIndex(SelectedLogMessage->GetIndex()); + } + bIsDirty = false; + DirtyStopwatch.Reset(); + UpdateStatsText(); + + FilteringStopwatch.Stop(); + uint64 DurationMs = FilteringStopwatch.GetAccumulatedTimeMs(); + if (DurationMs > 10) // avoids spams + { + int32 NumAsyncFilteredMessages = Task.GetEndIndex() - Task.GetStartIndex(); + double Speed = static_cast(NumAsyncFilteredMessages) / FilteringStopwatch.GetAccumulatedTime(); + UE_LOG(TimingProfiler, Log, TEXT("[LogView] Updated (%d added / %d async filtered / %d total messages) in %llu ms (%.2f messages/second)."), + NumFilteredMessages, NumAsyncFilteredMessages, TotalNumMessages, DurationMs, Speed); + } + } + + FilteringAsyncTask.Reset(); + } + + //SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedPtr SLogView::GetSelectedLogMessage() const +{ + TArray> SelectedItems = ListView->GetSelectedItems(); + return (SelectedItems.Num() == 1) ? SelectedItems[0] : nullptr; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SLogView::SelectedLogMessageByLogIndex(int32 LogIndex) +{ + // We are assuming the Messages list is sorted by log index... + int32 MessageIndex = Algo::BinarySearchBy(Messages, LogIndex, &FLogMessage::GetIndex); + if (MessageIndex != INDEX_NONE) + { + ListView->SetItemSelection(Messages[MessageIndex], true); + ListView->RequestScrollIntoView(Messages[MessageIndex]); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SLogView::OnSelectionChanged(TSharedPtr LogMessage, ESelectInfo::Type SelectInfo) +{ + TSharedPtr Window = FTimingProfilerManager::Get()->GetProfilerWindow(); + if (Window && Window->TimingView) + { + // Single item selection. + if (LogMessage.IsValid()) + { + const double Time = Cache.Get(LogMessage->GetIndex()).Time; + + if (FSlateApplication::Get().GetModifierKeys().IsShiftDown()) + { + Window->TimingView->SelectToTimeMarker(Time); + } + else + { + Window->TimingView->SetAndCenterOnTimeMarker(Time); + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SLogView::FilterTextBox_OnTextChanged(const FText& InFilterText) +{ + if (Filter.GetFilterText().ToString().Equals(InFilterText.ToString(), ESearchCase::CaseSensitive)) + { + // nothing to do + return; + } + + // Set filter phrases. + Filter.SetFilterText(InFilterText); + + // Report possible syntax errors back to the user. + FilterTextBox->SetError(Filter.GetSyntaxErrors()); + + OnFilterChanged(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SLogView::OnFilterChanged() +{ + const FString FilterText = FilterTextBox->GetText().ToString(); + UE_LOG(TimingProfiler, Log, TEXT("[LogView] OnFilterChanged: \"%s\""), *FilterText); + Cache.Reset(); + Messages.Reset(); + TotalNumMessages = 0; + bIsDirty = true; + DirtyStopwatch.Start(); + // The next Tick() will update the filtered list of messages. +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SLogView::UpdateStatsText() +{ + if (Messages.Num() == TotalNumMessages) + { + StatsText = FText::Format(LOCTEXT("StatsText1", "{0} logs"), FText::AsNumber(TotalNumMessages)); + } + else + { + StatsText = FText::Format(LOCTEXT("StatsText2", "{0} / {1} logs"), FText::AsNumber(Messages.Num()), FText::AsNumber(TotalNumMessages)); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText SLogView::GetStatsText() const +{ + if (bIsDirty) + { + DirtyStopwatch.Update(); + double DT = DirtyStopwatch.GetAccumulatedTime(); + if (DT > 1.0) + { + return FText::Format(LOCTEXT("StatsTextEx", "{0} (filtering... please wait... {1}s)"), StatsText, FText::AsNumber(FMath::RoundToInt(DT))); + } + } + + return StatsText; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FSlateColor SLogView::GetStatsTextColor() const +{ + if (bIsDirty) + { + return FSlateColor(FLinearColor(1.0f, 0.5f, 0.5f, 1.0f)); + } + else + { + return FSlateColor(FLinearColor(1.0f, 1.0f, 1.0f, 1.0f)); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedPtr SLogView::ListView_GetContextMenu() +{ + TSharedPtr SelectedLogMessage = GetSelectedLogMessage(); + + FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr); + + MenuBuilder.BeginSection("LogViewContextMenu"); + { + if (SelectedLogMessage.IsValid()) + { + FLogMessageRecord& Record = Cache.Get(SelectedLogMessage->GetIndex()); + FName CategoryName(*Record.Category.ToString()); + + MenuBuilder.AddMenuEntry( + FText::Format(LOCTEXT("HideCategory", "Hide \"{0}\" Category"), Record.Category), + FText::Format(LOCTEXT("HideCategory_Tooltip", "Hide the \"{0}\" log category."), Record.Category), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &SLogView::ToggleCategory_Execute, CategoryName)), + NAME_None, + EUserInterfaceActionType::Button + ); + + MenuBuilder.AddMenuEntry( + FText::Format(LOCTEXT("ShowOnlyCategory", "Show Only \"{0}\" Category"), Record.Category), + FText::Format(LOCTEXT("ShowOnlyCategory_Tooltip", "Show only the \"{0}\" log category (hide all other log categories)."), Record.Category), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &SLogView::ShowOnlyCategory_Execute, CategoryName)), + NAME_None, + EUserInterfaceActionType::Button + ); + } + + MenuBuilder.AddMenuEntry( + LOCTEXT("ShowAllCategoriesCtxMenu", "Show All Categories"), + LOCTEXT("ShowAllCategoriesCtxMenu_Tooltip", "Reset category filter (show all log categories)."), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &SLogView::ShowAllCategories_Execute)), + NAME_None, + EUserInterfaceActionType::Button + ); + } + MenuBuilder.EndSection(); + + return MenuBuilder.MakeWidget(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef SLogView::MakeVerbosityThresholdMenu() +{ + FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr); + + MenuBuilder.BeginSection("LogViewVerbosityThreshold"/*, LOCTEXT("VerbosityThresholdHeading", "Verbosity Threshold")*/); + CreateVerbosityThresholdMenuSection(MenuBuilder); + MenuBuilder.EndSection(); + + return MenuBuilder.MakeWidget(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SLogView::CreateVerbosityThresholdMenuSection(FMenuBuilder& MenuBuilder) +{ + struct FVerbosityThresholdInfo + { + ELogVerbosity::Type Verbosity; + FText Label; + FText ToolTip; + }; + + FVerbosityThresholdInfo VerbosityThresholds[] = + { + { ELogVerbosity::VeryVerbose, LOCTEXT("VerbosityThresholdVeryVerbose", "VeryVerbose"), LOCTEXT("VerbosityThresholdVeryVerbose_Tooltip", "Show all log messages (any verbosity level)."), }, + { ELogVerbosity::Verbose, LOCTEXT("VerbosityThresholdVerbose", "Verbose"), LOCTEXT("VerbosityThresholdVerbose_Tooltip", "Show Verbose, Log, Display, Warning, Error and Fatal log messages (i.e. hide VeryVerbose log messages)."), }, + { ELogVerbosity::Log, LOCTEXT("VerbosityThresholdLog", "Log"), LOCTEXT("VerbosityThresholdLog_Tooltip", "Show Log, Display, Warning, Error and Fatal log messages (i.e. hide Verbose and VeryVerbose log messages)."), }, + { ELogVerbosity::Display, LOCTEXT("VerbosityThresholdDisplay", "Display"), LOCTEXT("VerbosityThresholdDisplay_Tooltip", "Show Display, Warning, Error and Fatal log messages (i.e. hide Log, Verbose and VeryVerbose log messages)."), }, + { ELogVerbosity::Warning, LOCTEXT("VerbosityThresholdWarning", "Warning"), LOCTEXT("VerbosityThresholdWarning_Tooltip", "Show only Warning, Error and Fatal log messages."), }, + { ELogVerbosity::Error, LOCTEXT("VerbosityThresholdError", "Error"), LOCTEXT("VerbosityThresholdError_Tooltip", "Show only Error and Fatal log messages."), }, + { ELogVerbosity::Fatal, LOCTEXT("VerbosityThresholdFatal", "Fatal"), LOCTEXT("VerbosityThresholdFatal_Tooltip", "Show only Fatal log messages."), }, + }; + + for (int32 Index = 0; Index < sizeof(VerbosityThresholds) / sizeof(FVerbosityThresholdInfo); ++Index) + { + const FVerbosityThresholdInfo& Threshold = VerbosityThresholds[Index]; + + /* + MenuBuilder.AddMenuEntry( + Threshold.Label, + Threshold.ToolTip, + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &SLogView::VerbosityThreshold_Execute, Threshold.Verbosity), + FCanExecuteAction::CreateLambda([] { return true; }), + FIsActionChecked::CreateSP(this, &SLogView::VerbosityThreshold_IsChecked, Threshold.Verbosity)), + NAME_None, + EUserInterfaceActionType::ToggleButton + ); + */ + + const TSharedRef TextBlock = SNew(STextBlock) + .Text(Threshold.Label) + .ShadowColorAndOpacity(FLinearColor(0.05f, 0.05f, 0.05f, 1.0f)) + .ShadowOffset(FVector2D(1.0f, 1.0f)) + .ColorAndOpacity(FSlateColor(FTimeMarkerTrackBuilder::GetColorByVerbosity(Threshold.Verbosity))); + + MenuBuilder.AddMenuEntry( + FUIAction(FExecuteAction::CreateSP(this, &SLogView::VerbosityThreshold_Execute, Threshold.Verbosity), + FCanExecuteAction::CreateLambda([] { return true; }), + FIsActionChecked::CreateSP(this, &SLogView::VerbosityThreshold_IsChecked, Threshold.Verbosity)), + TextBlock, + NAME_None, + Threshold.ToolTip, + EUserInterfaceActionType::ToggleButton + ); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef SLogView::MakeCategoryFilterMenu() +{ + FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr); + + MenuBuilder.BeginSection("LogViewShowAllCategories", LOCTEXT("QuickFilterHeading", "Quick Filter")); + { + MenuBuilder.AddMenuEntry( + LOCTEXT("ShowAllCategories", "Show/Hide All"), + LOCTEXT("ShowAllCategories_Tooltip", "Change filtering to show/hide all categories"), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &SLogView::ShowHideAllCategories_Execute), + FCanExecuteAction::CreateLambda([] { return true; }), + FIsActionChecked::CreateSP(this, &SLogView::ShowHideAllCategories_IsChecked)), + NAME_None, + EUserInterfaceActionType::ToggleButton + ); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("LogViewCategoriesEntries", LOCTEXT("CategoriesHeading", "Categories")); + CreateCategoriesFilterMenuSection(MenuBuilder); + MenuBuilder.EndSection(); + + return MenuBuilder.MakeWidget(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SLogView::CreateCategoriesFilterMenuSection(FMenuBuilder& MenuBuilder) +{ + for (const FName CategoryName : Filter.GetAvailableLogCategories()) + { + const FString CategoryString = CategoryName.ToString(); + const FText CategoryText(FText::AsCultureInvariant(CategoryString)); + + //MenuBuilder.AddMenuEntry( + // FText::AsCultureInvariant(CategoryString), + // FText::Format(LOCTEXT("Category_Tooltip", "Filter the Log View to show/hide category: {0}"), CategoryText), + // FSlateIcon(), + // FUIAction(FExecuteAction::CreateSP(this, &SLogView::ToggleCategory_Execute, CategoryName), + // FCanExecuteAction::CreateLambda([] { return true; }), + // FIsActionChecked::CreateSP(this, &SLogView::ToggleCategory_IsChecked, CategoryName)), + // NAME_None, + // EUserInterfaceActionType::ToggleButton + //); + + const TSharedRef TextBlock = SNew(STextBlock) + .Text(FText::AsCultureInvariant(CategoryString)) + .ShadowColorAndOpacity(FLinearColor(0.05f, 0.05f, 0.05f, 1.0f)) + .ShadowOffset(FVector2D(1.0f, 1.0f)) + .ColorAndOpacity(FSlateColor(FTimeMarkerTrackBuilder::GetColorByCategory(*CategoryString))); + + MenuBuilder.AddMenuEntry( + FUIAction(FExecuteAction::CreateSP(this, &SLogView::ToggleCategory_Execute, CategoryName), + FCanExecuteAction::CreateLambda([] { return true; }), + FIsActionChecked::CreateSP(this, &SLogView::ToggleCategory_IsChecked, CategoryName)), + TextBlock, + NAME_None, + FText::Format(LOCTEXT("Category_Tooltip", "Filter the Log View to show/hide category: {0}"), CategoryText), + EUserInterfaceActionType::ToggleButton + ); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SLogView::VerbosityThreshold_IsChecked(ELogVerbosity::Type Verbosity) const +{ + return Verbosity <= Filter.GetVerbosityThreshold(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SLogView::VerbosityThreshold_Execute(ELogVerbosity::Type Verbosity) +{ + Filter.SetVerbosityThreshold(Verbosity); + OnFilterChanged(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SLogView::ShowHideAllCategories_IsChecked() const +{ + return Filter.IsShowAllCategoriesEnabled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SLogView::ShowHideAllCategories_Execute() +{ + Filter.ToggleShowAllCategories(); + OnFilterChanged(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SLogView::ShowAllCategories_Execute() +{ + if (Filter.IsShowAllCategoriesEnabled()) + { + Filter.ToggleShowAllCategories(); // hide + Filter.ToggleShowAllCategories(); // show + } + else + { + Filter.ToggleShowAllCategories(); // show + } + OnFilterChanged(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SLogView::ShowOnlyCategory_Execute(FName InName) +{ + Filter.EnableOnlyCategory(InName); + OnFilterChanged(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SLogView::ToggleCategory_IsChecked(FName InName) const +{ + return Filter.IsLogCategoryEnabled(InName); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SLogView::ToggleCategory_Execute(FName InName) +{ + Filter.ToggleLogCategory(InName); + OnFilterChanged(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SLogView.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SLogView.h new file mode 100644 index 000000000000..b75a64c97b60 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SLogView.h @@ -0,0 +1,150 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Logging/LogVerbosity.h" +#include "Misc/ScopeLock.h" +#include "SlateFwd.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/Input/SSearchBox.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/Views/SListView.h" + +// Insights +#include "Insights/Common/Stopwatch.h" +#include "Insights/ViewModels/LogFilter.h" +#include "Insights/ViewModels/LogMessage.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace LogViewColumns +{ + static const FName IdColumnName(TEXT("Id")); + static const FName TimeColumnName(TEXT("Time")); + static const FName VerbosityColumnName(TEXT("Verbosity")); + static const FName CategoryColumnName(TEXT("Category")); + static const FName MessageColumnName(TEXT("Message")); + static const FName FileColumnName(TEXT("File")); + static const FName LineColumnName(TEXT("Line")); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Trace log window. + */ +class SLogView : public SCompoundWidget +{ +public: + /** Default constructor. */ + SLogView(); + + /** Virtual destructor. */ + virtual ~SLogView(); + + void Reset(); + + SLATE_BEGIN_ARGS(SLogView){} + SLATE_END_ARGS() + + /** + * Construct this widget. + * @param InArgs - The declaration data for this widget + */ + void Construct(const FArguments& InArgs); + + /** + * Ticks this widget. Override in derived classes, but always call the parent implementation. + * + * @param AllottedGeometry - The space allotted for this widget + * @param InCurrentTime - Current absolute real time + * @param InDeltaTime - Real time passed since last tick + */ + virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; + + FLogMessageCache& GetCache() { return Cache; } + + TSharedPtr GetSelectedLogMessage() const; + void SelectedLogMessageByLogIndex(int32 LogIndex); + + FText GetFilterText() const { return FilterTextBox->GetText(); } + + bool IsFilteringAsyncTaskCancelRequested() const { return bIsFilteringAsyncTaskCancelRequested; } + +protected: + /** Generate a new list view row. */ + TSharedRef OnGenerateRow(TSharedPtr InLogMessage, const TSharedRef& OwnerTable); + + void OnSelectionChanged(TSharedPtr LogMessage, ESelectInfo::Type SelectInfo); + + void FilterTextBox_OnTextChanged(const FText& InFilterText); + void OnFilterChanged(); + + void UpdateStatsText(); + FText GetStatsText() const; + FSlateColor GetStatsTextColor() const; + + TSharedPtr ListView_GetContextMenu(); + TSharedRef MakeVerbosityThresholdMenu(); + void CreateVerbosityThresholdMenuSection(FMenuBuilder& MenuBuilder); + TSharedRef MakeCategoryFilterMenu(); + void CreateCategoriesFilterMenuSection(FMenuBuilder& MenuBuilder); + + bool VerbosityThreshold_IsChecked(ELogVerbosity::Type Verbosity) const; + void VerbosityThreshold_Execute(ELogVerbosity::Type Verbosity); + + bool ShowHideAllCategories_IsChecked() const; + void ShowHideAllCategories_Execute(); + + void ShowAllCategories_Execute(); + + void ShowOnlyCategory_Execute(FName InName); + + bool ToggleCategory_IsChecked(FName InName) const; + void ToggleCategory_Execute(FName InName); + +protected: + /** The list view widget. */ + TSharedPtr>> ListView; + + /** External scrollbar used to synchronize tree view position. */ + TSharedPtr ExternalScrollbar; + + /** The search box widget used to filter logs by message text. */ + TSharedPtr FilterTextBox; + + FLogFilter Filter; + uint64 FilterChangeNumber; + + int32 FilteringStartIndex; // Start index (of the range of log messages to filter) currenly used by the async task + int32 FilteringEndIndex; // End index (of the range of log messages to filter) currenly used by the async task + uint64 FilteringChangeNumber; // Change number of the filter currenly used by the async task + TUniquePtr> FilteringAsyncTask; // The async task to filter log messages on a worker thread + mutable volatile bool bIsFilteringAsyncTaskCancelRequested; // true if we want the async task to finish asap + + /** Stopwatch used to measure how long it takes to filter the message list. */ + mutable FStopwatch FilteringStopwatch; + + int32 TotalNumCategories; + + /** Total number of log messages processed, from the source Trace session. Used to detect when new log messages are added in the source Trace session. */ + int32 TotalNumMessages; + + /** true if the list of messages is not yet updated (the filter has changed and/or the source trace messages have changed) */ + bool bIsDirty; + + /** Stopwatch used to measure the time since the list of messages has become dirty. */ + mutable FStopwatch DirtyStopwatch; + + /** Stats */ + FText StatsText; + + /** Cached log messages. */ + FLogMessageCache Cache; + + /** List of trace log messages to show in list view (i.e. filtered). */ + TArray> Messages; // TODO: this needs virtualisation (an a new SListView) +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStartPageWindow.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStartPageWindow.cpp new file mode 100644 index 000000000000..757ce1b235f1 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStartPageWindow.cpp @@ -0,0 +1,1370 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SStartPageWindow.h" + +#include "DesktopPlatformModule.h" +#include "EditorStyleSet.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/Docking/WorkspaceItem.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "HAL/FileManagerGeneric.h" +#include "SlateOptMacros.h" +#include "Widgets/Docking/SDockTab.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Layout/SSpacer.h" +#include "Widgets/Notifications/SNotificationList.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SComboButton.h" +//#include "WorkspaceMenuStructure.h" +//#include "WorkspaceMenuStructureModule.h" + +#if WITH_EDITOR + #include "EngineAnalytics.h" + #include "Runtime/Analytics/Analytics/Public/AnalyticsEventAttribute.h" + #include "Runtime/Analytics/Analytics/Public/Interfaces/IAnalyticsProvider.h" +#endif // WITH_EDITOR + +// Insights +#include "Insights/InsightsManager.h" +#include "Insights/TimingProfilerManager.h" +#include "Insights/InsightsStyle.h" +#include "Insights/Version.h" +#include "Insights/Widgets/SInsightsSettings.h" +#include "Insights/Widgets/STimingProfilerWindow.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define LOCTEXT_NAMESPACE "SStartPageWindow" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +static FText GetTextForNotification(const EInsightsNotificationType NotificatonType, const ELoadingProgressState ProgressState, const FString& Filename, const float ProgressPercent = 0.0f) +{ + FText Result; + + if (NotificatonType == EInsightsNotificationType::LoadingTraceFile) + { + if (ProgressState == ELoadingProgressState::Started) + { + FFormatNamedArguments Args; + Args.Add(TEXT("Filename"), FText::FromString(Filename)); + Result = FText::Format(LOCTEXT("DescF_OfflineCapture_Started", "Started loading a file {Filename}"), Args); + } + else if (ProgressState == ELoadingProgressState::InProgress) + { + FFormatNamedArguments Args; + Args.Add(TEXT("Filename"), FText::FromString(Filename)); + Args.Add(TEXT("LoadingProgressPercent"), FText::AsPercent(ProgressPercent)); + Result = FText::Format(LOCTEXT("DescF_OfflineCapture_InProgress", "Loading a file {Filename} {LoadingProgressPercent}"), Args); + } + else if (ProgressState == ELoadingProgressState::Loaded) + { + FFormatNamedArguments Args; + Args.Add(TEXT("Filename"), FText::FromString(Filename)); + Result = FText::Format(LOCTEXT("DescF_OfflineCapture_Loaded", "File {Filename} has been successfully loaded"), Args); + } + else if (ProgressState == ELoadingProgressState::Failed) + { + FFormatNamedArguments Args; + Args.Add(TEXT("Filename"), FText::FromString(Filename)); + Result = FText::Format(LOCTEXT("DescF_OfflineCapture_Failed", "Failed to load file {Filename}"), Args); + } + } + + return Result; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// SConnectionRow +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class SConnectionRow : public SMultiColumnTableRow> +{ + SLATE_BEGIN_ARGS(SConnectionRow) {} + SLATE_END_ARGS() + +public: + /** + * Constructs the widget. + * + * @param InArgs The construction arguments. + * @param InConnection The connection displayed by this row. + * @param InOwnerTableView The table to which the row must be added. + */ + void Construct(const FArguments& InArgs, TSharedPtr InConnection, TSharedRef InParentWidget, const TSharedRef& InOwnerTableView) + { + WeakConnection = MoveTemp(InConnection); + WeakParentWidget = InParentWidget; + + SMultiColumnTableRow>::Construct(FSuperRowType::FArguments(), InOwnerTableView); + } + +public: + virtual TSharedRef GenerateWidgetForColumn(const FName& ColumnName) override + { + if (ColumnName == FName(TEXT("Name"))) + { + return SNew(SBox) + .Padding(FMargin(4.0, 0.0)) + [ + SNew(STextBlock) + .Text(this, &SConnectionRow::GetConnectionName) + .ToolTipText(this, &SConnectionRow::GetConnectionName) + ]; + } + else if (ColumnName == FName(TEXT("Uri"))) + { + return SNew(SBox) + .Padding(FMargin(4.0, 0.0)) + [ + SNew(STextBlock) + .Text(this, &SConnectionRow::GetConnectionUri) + .ToolTipText(this, &SConnectionRow::GetConnectionUri) + ]; + } + else + { + return SNew(STextBlock).Text(LOCTEXT("UnknownColumn", "Unknown Column")); + } + } + + FText GetConnectionName() const + { + TSharedPtr ConnectionPin = WeakConnection.Pin(); + if (ConnectionPin.IsValid()) + { + return ConnectionPin->Name; + } + else + { + return FText(); + } + } + + FText GetConnectionUri() const + { + TSharedPtr ConnectionPin = WeakConnection.Pin(); + if (ConnectionPin.IsValid()) + { + return ConnectionPin->Uri; + } + else + { + return FText(); + } + } + + FText GetRowToolTip() const + { + TSharedPtr ConnectionPin = WeakConnection.Pin(); + if (ConnectionPin.IsValid()) + { + return ConnectionPin->Uri; + } + else + { + return FText(); + } + } + +private: + TWeakPtr WeakConnection; + TWeakPtr WeakParentWidget; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// SStartPageWindow +//////////////////////////////////////////////////////////////////////////////////////////////////// + +SStartPageWindow::SStartPageWindow() + : DurationActive(0.0f) + , bIsAnyLiveSessionAvailable(false) + , LastLiveSessionHandle(static_cast(0)) + , bIsAnySessionAvailable(false) + , LastSessionHandle(static_cast(0)) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +SStartPageWindow::~SStartPageWindow() +{ +#if WITH_EDITOR + if (DurationActive > 0.0f && FEngineAnalytics::IsAvailable()) + { + FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.Profiler"), FAnalyticsEventAttribute(TEXT("Duration"), DurationActive)); + } +#endif // WITH_EDITOR +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + +void SStartPageWindow::Construct(const FArguments& InArgs) +{ + ChildSlot + [ + SNew(SOverlay) + + // Version + + SOverlay::Slot() + .HAlign(HAlign_Right) + .VAlign(VAlign_Top) + .Padding(0.0f, -16.0f, 0.0f, 0.0f) + [ + SNew(STextBlock) + .Clipping(EWidgetClipping::ClipToBoundsWithoutIntersecting) + .Text(LOCTEXT("UnrealInsightsVersion", UNREAL_INSIGHTS_VERSION_STRING_EX)) + .ColorAndOpacity(FLinearColor(0.15f, 0.15f, 0.15f, 1.0f)) + ] + + // Overlay slot for the main window area + + SOverlay::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Center) + .Padding(3.0f, 3.0f) + [ + SNew(SBorder) + .Visibility(this, &SStartPageWindow::IsSessionOverlayVisible) + .BorderImage(FEditorStyle::GetBrush("NotificationList.ItemBackground")) + .Padding(8.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("SelectTraceOverlayText", "Please select a trace...")) + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Center) + .Padding(3.0f, 3.0f) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("NotificationList.ItemBackground")) + .Padding(8.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("SelectTraceHint", "\ +- Use \"Live\" button to start analysis for the latest available session that is live, if any.\n\ +- Use \"Latest\" button to start analysis for the latest available session, if any.\n\ +- Use \"Load\" button to load a trace file (*.utrace).\n\ +- Use the combo box to choose from available sessions.\n\ +- Drag and drop a *.utrace file over this window.")) + //.Justification(ETextJustify::Center) + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Center) + .Padding(3.0f, 3.0f) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(3.0f, 0.0f) + [ + SNew(SButton) + .OnClicked(this, &SStartPageWindow::Live_OnClicked) + .IsEnabled(this, &SStartPageWindow::Live_IsEnabled) + .ToolTipText(LOCTEXT("LiveButtonTooltip", "Start analysis for the latest available session that is live, if any.")) + .ContentPadding(8.0f) + .Content() + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SImage) + .Image(FInsightsStyle::GetBrush("Live.Icon.Large")) + ] + + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SBox) + .WidthOverride(100.0f) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 18)) + .Text(LOCTEXT("LiveButtonText", "Live")) + ] + ] + ] + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(3.0f, 0.0f) + [ + SNew(SButton) + .OnClicked(this, &SStartPageWindow::Last_OnClicked) + .IsEnabled(this, &SStartPageWindow::Last_IsEnabled) + .ToolTipText(LOCTEXT("LastButtonTooltip", "Start analysis for the latest available session, if any.")) + .ContentPadding(8.0f) + .Content() + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SImage) + .Image(FInsightsStyle::GetBrush("Live.Icon.Large")) + ] + + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SBox) + .WidthOverride(100.0f) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 18)) + .Text(LOCTEXT("LastButtonText", "Latest")) + ] + ] + ] + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(3.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(SButton) + .OnClicked(this, &SStartPageWindow::Load_OnClicked) + .ToolTipText(LOCTEXT("LoadButtonTooltip", "Load a trace file.")) + .ContentPadding(8.0f) + .Content() + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SImage) + .Image(FInsightsStyle::GetBrush("Load.Icon.Large")) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SBox) + .WidthOverride(100.0f) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 18)) + .Text(LOCTEXT("LoadButtonText", "Load...")) + ] + ] + ] + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(SComboButton) + .ToolTipText(LOCTEXT("SessionList_Tooltip", "Choose from Most Recently Used Sessions or from Available Sessions")) + .OnGetMenuContent(this, &SStartPageWindow::MakeSessionListMenu) + .HasDownArrow(true) + .ContentPadding(FMargin(1.0f, 1.0f, 1.0f, 1.0f)) + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Center) + .Padding(3.0f, 3.0f) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("NotificationList.ItemBackground")) + .Padding(8.0f) + [ + SNew(SVerticalBox) + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Center) + .Padding(0.0, 2.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("LocalSessionDirectoryText", "Local Session Directory:")) + .ColorAndOpacity(FLinearColor::Gray) + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Center) + .Padding(0.0, 2.0f) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(this, &SStartPageWindow::GetLocalSessionDirectory) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .Text(LOCTEXT("ExploreLocalSessionDirButton", "...")) + .ToolTipText(LOCTEXT("ExploreLocalSessionDirButtonToolTip", "Explore the Local Session Directory")) + .OnClicked(this, &SStartPageWindow::ExploreLocalSessionDirectory_OnClicked) + ] + ] + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Center) + .Padding(3.0f, 3.0f) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("NotificationList.ItemBackground")) + .Padding(8.0f) + [ + ConstructRecoderPanel() + ] + ] + ] + + // Notification area overlay + + SOverlay::Slot() + .HAlign(HAlign_Right) + .VAlign(VAlign_Bottom) + .Padding(16.0f) + [ + SAssignNew(NotificationList, SNotificationList) + ] + + // Settings dialog overlay + + SOverlay::Slot() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .Expose(OverlaySettingsSlot) + ]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef SStartPageWindow::ConstructRecoderPanel() +{ + TSharedRef Widget = SNew(SVerticalBox) + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Center) + .Padding(0.0, 2.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("RecorderPanelTitle", "Trace Recorder")) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 11)) + .ColorAndOpacity(FLinearColor::Gray) + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Left) + .Padding(0.0, 2.0f) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("RecorderStatusTitle", "Status:")) + .ColorAndOpacity(FLinearColor::Gray) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(this, &SStartPageWindow::GetRecorderStatusText) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .Text(LOCTEXT("StartRecorder", "Start")) + .ToolTipText(LOCTEXT("StartRecorderToolTip", "Start the Trace Recorder")) + .OnClicked(this, &SStartPageWindow::StartTraceRecorder_OnClicked) + .Visibility(this, &SStartPageWindow::StartTraceRecorder_Visibility) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .Text(LOCTEXT("StopRecorder", "Stop")) + .ToolTipText(LOCTEXT("StopRecorderToolTip", "Stop the Trace Recorder")) + .OnClicked(this, &SStartPageWindow::StopTraceRecorder_OnClicked) + .Visibility(this, &SStartPageWindow::StopTraceRecorder_Visibility) + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Fill) + .Padding(0.0, 2.0f) + [ + SNew(SHorizontalBox) + .Visibility(this, &SStartPageWindow::StopTraceRecorder_Visibility) + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("HostTitle", "Host:")) + .ColorAndOpacity(FLinearColor::Gray) + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0) + .VAlign(VAlign_Center) + [ + SAssignNew(HostTextBox, SEditableTextBox) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .Text(LOCTEXT("Connect", "Connect")) + .ToolTipText(LOCTEXT("ConnectToolTip", "Try connecting to host.")) + .OnClicked(this, &SStartPageWindow::Connect_OnClicked) + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Left) + .Padding(0.0, 2.0f, 0.0f, 1.0f) + [ + SNew(SHorizontalBox) + .Visibility(this, &SStartPageWindow::StopTraceRecorder_Visibility) + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text_Lambda([this]() -> FText + { + return FText::Format(LOCTEXT("ConnectionListTitle", "Connections (live sessions): {0}"), FText::AsNumber(Connections.Num())); + }) + .ColorAndOpacity(FLinearColor::Gray) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .Text(LOCTEXT("Refresh", "Refresh")) + .ToolTipText(LOCTEXT("RefreshToolTip", "Refresh the connection list (live sessions).")) + .OnClicked(this, &SStartPageWindow::RefreshConnections_OnClicked) + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Left) + .Padding(0.0, 1.0f, 0.0f, 2.0f) + [ + SNew(SBox) + .WidthOverride(640.0f) + .MaxDesiredHeight(78.0f) + .Visibility(this, &SStartPageWindow::Connections_Visibility) + [ + SAssignNew(ConnectionsListView, SListView>) + .ItemHeight(20.0f) + .SelectionMode(ESelectionMode::Single) + .OnSelectionChanged(this, &SStartPageWindow::Connections_OnSelectionChanged) + .ListItemsSource(&Connections) + .OnGenerateRow(this, &SStartPageWindow::Connections_OnGenerateRow) + .ConsumeMouseWheel(EConsumeMouseWheel::Always) + //.OnContextMenuOpening(FOnContextMenuOpening::CreateSP(this, &SStartPageWindow::Connections_GetContextMenu)) + .HeaderRow + ( + SNew(SHeaderRow) + + + SHeaderRow::Column(FName(TEXT("Name"))) + .FillWidth(0.2f) + .DefaultLabel(LOCTEXT("NameColumn", "Name")) + + + SHeaderRow::Column(FName(TEXT("Uri"))) + .FillWidth(0.8f) + .DefaultLabel(LOCTEXT("UriColumn", "URI")) + ) + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Left) + .Padding(0.0, 2.0f, 0.0f, 1.0f) + [ + SNew(STextBlock) + .Visibility(this, &SStartPageWindow::Modules_Visibility) + .Text(LOCTEXT("ModulesListTitle", "Modules for selected connection:")) + .ColorAndOpacity(FLinearColor::Gray) + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Left) + .Padding(0.0, 1.0f, 0.0f, 2.0f) + [ + ConstructModuleList() + ] + ; + + RefreshConnectionList(); + + return Widget; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef SStartPageWindow::ConstructModuleList() +{ + TSharedRef VerticalBox = SNew(SVerticalBox) + .Visibility(this, &SStartPageWindow::Modules_Visibility); + + TSharedRef ModuleService = FInsightsManager::Get()->GetModuleService(); + ModuleService->GetAvailableModules(AvailableModules); + + constexpr bool bDefaultModuleEnableState = false; + AvailableModulesEnabledState.Reset(); + + for (int32 ModuleIndex = 0; ModuleIndex < AvailableModules.Num(); ++ModuleIndex) + { + const Trace::FModuleInfo& Module = AvailableModules[ModuleIndex]; + + AvailableModulesEnabledState.Add(bDefaultModuleEnableState); + ModuleService->SetModuleEnabled(Module.Name, bDefaultModuleEnableState); + + VerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 1.0f) + [ + SNew(SCheckBox) + .IsChecked(this, &SStartPageWindow::Module_IsChecked, ModuleIndex) + .OnCheckStateChanged(this, &SStartPageWindow::Module_OnCheckStateChanged, ModuleIndex) + [ + SNew(STextBlock) + .Text(FText::FromString(Module.DisplayName)) + ] + ]; + } + + return VerticalBox; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef SStartPageWindow::Connections_OnGenerateRow(TSharedPtr InConnection, const TSharedRef& OwnerTable) +{ + return SNew(SConnectionRow, InConnection, SharedThis(this), OwnerTable); +} + +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +EVisibility SStartPageWindow::Modules_Visibility() const +{ + TSharedRef SessionService = FInsightsManager::Get()->GetSessionService(); + return (SessionService->IsRecorderServerRunning() && Connections.Num() > 0 && SelectedConnection.IsValid()) ? EVisibility::Visible : EVisibility::Collapsed; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +ECheckBoxState SStartPageWindow::Module_IsChecked(int32 ModuleIndex) const +{ + if (ModuleIndex >= 0 && ModuleIndex < AvailableModulesEnabledState.Num()) + { + const bool bIsEnabled = AvailableModulesEnabledState[ModuleIndex]; + return bIsEnabled ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + } + else + { + return ECheckBoxState::Undetermined; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStartPageWindow::Module_OnCheckStateChanged(ECheckBoxState NewRadioState, int32 ModuleIndex) +{ + ensure(AvailableModulesEnabledState.Num() == AvailableModules.Num()); + + if (SelectedConnection.IsValid() && + ModuleIndex >= 0 && ModuleIndex < AvailableModules.Num()) + { + bool bIsEnabled = NewRadioState == ECheckBoxState::Checked; + + TSharedRef SessionService = FInsightsManager::Get()->GetSessionService(); + + SessionService->SetModuleEnabled(SelectedConnection->SessionHandle, AvailableModules[ModuleIndex].Name, bIsEnabled); + //AvailableModulesEnabledState[ModuleIndex] = bIsEnabled; + AvailableModulesEnabledState[ModuleIndex] = SessionService->IsModuleEnabled(SelectedConnection->SessionHandle, AvailableModules[ModuleIndex].Name); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SStartPageWindow::RefreshConnections_OnClicked() +{ + RefreshConnectionList(); + return FReply::Handled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SStartPageWindow::Connect_OnClicked() +{ + TSharedRef SessionService = FInsightsManager::Get()->GetSessionService(); + + FText HostText = HostTextBox->GetText(); + if (HostText.IsEmptyOrWhitespace()) + { + //... + } + else if (SessionService->ConnectSession(*HostText.ToString())) + { + FNotificationInfo NotificationInfo(FText::Format(LOCTEXT("ConnectSuccess", "Successfully connected to \"{0}\"!"), HostText)); + NotificationInfo.bFireAndForget = false; + NotificationInfo.bUseLargeFont = false; + NotificationInfo.bUseSuccessFailIcons = true; + NotificationInfo.ExpireDuration = 10.0f; + SNotificationItemWeak NotificationItem = NotificationList->AddNotification(NotificationInfo); + NotificationItem.Pin()->SetCompletionState(SNotificationItem::CS_Success); + NotificationItem.Pin()->ExpireAndFadeout(); + ActiveNotifications.Add(TEXT("ConnectSuccess"), NotificationItem); + } + else + { + FNotificationInfo NotificationInfo(FText::Format(LOCTEXT("ConnectFailed", "Failed to connect to \"{0}\"!"), HostText)); + NotificationInfo.bFireAndForget = false; + NotificationInfo.bUseLargeFont = false; + NotificationInfo.bUseSuccessFailIcons = true; + NotificationInfo.ExpireDuration = 10.0f; + SNotificationItemWeak NotificationItem = NotificationList->AddNotification(NotificationInfo); + NotificationItem.Pin()->SetCompletionState(SNotificationItem::CS_Fail); + NotificationItem.Pin()->ExpireAndFadeout(); + ActiveNotifications.Add(TEXT("ConnectFailed"), NotificationItem); + } + + RefreshConnectionList(); + return FReply::Handled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStartPageWindow::RefreshConnectionList() +{ + TSharedRef SessionService = FInsightsManager::Get()->GetSessionService(); + + TArray LiveSessions; + SessionService->GetLiveSessions(LiveSessions); + + TSharedPtr NewSelectedConnection; + + Connections.Reset(); + + for (Trace::FSessionHandle SessionHandle : LiveSessions) + { + Trace::FSessionInfo SessionInfo; + SessionService->GetSessionInfo(SessionHandle, SessionInfo); + + Connections.Add(MakeShareable(new FRecorderConnection(SessionHandle, SessionInfo))); + + if (SelectedConnection && SelectedConnection->SessionHandle == SessionHandle) + { + NewSelectedConnection = Connections.Last(); + } + } + + ConnectionsListView->RebuildList(); + + if (NewSelectedConnection.IsValid()) + { + ConnectionsListView->SetItemSelection(NewSelectedConnection, true); + ConnectionsListView->RequestScrollIntoView(NewSelectedConnection); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStartPageWindow::Connections_OnSelectionChanged(TSharedPtr Connection, ESelectInfo::Type SelectInfo) +{ + SelectedConnection = Connection; + + if (Connection.IsValid()) + { + TSharedRef SessionService = FInsightsManager::Get()->GetSessionService(); + for (int32 Index = 0; Index < AvailableModulesEnabledState.Num(); ++Index) + { + AvailableModulesEnabledState[Index] = SessionService->IsModuleEnabled(SelectedConnection->SessionHandle, AvailableModules[Index].Name); + } + } + else + { + for (int32 Index = 0; Index < AvailableModulesEnabledState.Num(); ++Index) + { + AvailableModulesEnabledState[Index] = false; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +EVisibility SStartPageWindow::Connections_Visibility() const +{ + TSharedRef SessionService = FInsightsManager::Get()->GetSessionService(); + return (SessionService->IsRecorderServerRunning() && Connections.Num() > 0) ? EVisibility::Visible : EVisibility::Collapsed; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStartPageWindow::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) +{ + // We need to check if there is any available session, but not too often. + static uint64 NextTimestamp = 0; + uint64 Time = FPlatformTime::Cycles64(); + if (Time > NextTimestamp) + { + const uint64 WaitTime = static_cast(0.5 / FPlatformTime::GetSecondsPerCycle64()); // 500ms + NextTimestamp = Time + WaitTime; + + bIsAnySessionAvailable = FInsightsManager::Get()->IsAnySessionAvailable(LastSessionHandle); + //bIsAnyLiveSessionAvailable = FInsightsManager::Get()->IsAnyLiveSessionAvailable(LastLiveSessionHandle); + + TSharedRef SessionService = FInsightsManager::Get()->GetSessionService(); + TArray LiveSessions; + SessionService->GetLiveSessions(LiveSessions); + + if (LiveSessions.Num() > 0) + { + LastLiveSessionHandle = LiveSessions.Last(); + bIsAnyLiveSessionAvailable = true; + } + else + { + bIsAnyLiveSessionAvailable = false; + } + + if (LiveSessions.Num() != Connections.Num()) + { + RefreshConnectionList(); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +EVisibility SStartPageWindow::IsSessionOverlayVisible() const +{ + if (FInsightsManager::Get()->GetSession().IsValid()) + { + return EVisibility::Hidden; + } + + return EVisibility::Visible; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStartPageWindow::IsSessionValid() const +{ + const bool bIsActive = FInsightsManager::Get()->GetSession().IsValid(); + return bIsActive; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStartPageWindow::ManageLoadingProgressNotificationState(const FString& Filename, const EInsightsNotificationType NotificatonType, const ELoadingProgressState ProgressState, const float LoadingProgress) +{ + const FString BaseFilename = FPaths::GetBaseFilename(Filename); + + if (ProgressState == ELoadingProgressState::Started) + { + const bool bContains = ActiveNotifications.Contains(Filename); + if (!bContains) + { + FNotificationInfo NotificationInfo(GetTextForNotification(NotificatonType, ProgressState, BaseFilename)); + NotificationInfo.bFireAndForget = false; + NotificationInfo.bUseLargeFont = false; + + // Add two buttons, one for cancel, one for loading the received file. + if (NotificatonType == EInsightsNotificationType::LoadingTraceFile) + { + const FText CancelButtonText = LOCTEXT("CancelButton_Text", "Cancel"); + const FText CancelButtonTT = LOCTEXT("CancelButton_TTText", "Hides this notification"); + const FText LoadButtonText = LOCTEXT("LoadButton_Text", "Load file"); + const FText LoadButtonTT = LOCTEXT("LoadButton_TTText", "Loads the received file and hides this notification"); + + NotificationInfo.ButtonDetails.Add(FNotificationButtonInfo(CancelButtonText, CancelButtonTT, + FSimpleDelegate::CreateSP(this, &SStartPageWindow::SendingServiceSideCapture_Cancel, Filename), SNotificationItem::CS_Success)); + NotificationInfo.ButtonDetails.Add(FNotificationButtonInfo(LoadButtonText, LoadButtonTT, + FSimpleDelegate::CreateSP(this, &SStartPageWindow::SendingServiceSideCapture_Load, Filename), SNotificationItem::CS_Success)); + } + + SNotificationItemWeak NotificationItem = NotificationList->AddNotification(NotificationInfo); + NotificationItem.Pin()->SetCompletionState(SNotificationItem::CS_Pending); + ActiveNotifications.Add(Filename, NotificationItem); + } + } + else if (ProgressState == ELoadingProgressState::InProgress) + { + const SNotificationItemWeak* LoadingProgressPtr = ActiveNotifications.Find(Filename); + if (LoadingProgressPtr) + { + TSharedPtr LoadingProcessPinned = LoadingProgressPtr->Pin(); + LoadingProcessPinned->SetText(GetTextForNotification(NotificatonType, ProgressState, BaseFilename, LoadingProgress)); + LoadingProcessPinned->SetCompletionState(SNotificationItem::CS_Pending); + } + } + else if (ProgressState == ELoadingProgressState::Loaded) + { + const SNotificationItemWeak* LoadingProgressPtr = ActiveNotifications.Find(Filename); + if (LoadingProgressPtr) + { + TSharedPtr LoadingProcessPinned = LoadingProgressPtr->Pin(); + LoadingProcessPinned->SetText(GetTextForNotification(NotificatonType, ProgressState, BaseFilename)); + LoadingProcessPinned->SetCompletionState(SNotificationItem::CS_Success); + + // Notifications for received files are handled by the user. + if (NotificatonType == EInsightsNotificationType::LoadingTraceFile) + { + LoadingProcessPinned->ExpireAndFadeout(); + ActiveNotifications.Remove(Filename); + } + } + } + else if (ProgressState == ELoadingProgressState::Failed) + { + const SNotificationItemWeak* LoadingProgressPtr = ActiveNotifications.Find(Filename); + if (LoadingProgressPtr) + { + TSharedPtr LoadingProcessPinned = LoadingProgressPtr->Pin(); + LoadingProcessPinned->SetText(GetTextForNotification(NotificatonType, ProgressState, BaseFilename)); + LoadingProcessPinned->SetCompletionState(SNotificationItem::CS_Fail); + + LoadingProcessPinned->ExpireAndFadeout(); + ActiveNotifications.Remove(Filename); + } + } + else if (ProgressState == ELoadingProgressState::Cancelled) + { + const SNotificationItemWeak* LoadingProgressPtr = ActiveNotifications.Find(Filename); + if (LoadingProgressPtr) + { + TSharedPtr LoadingProcessPinned = LoadingProgressPtr->Pin(); + LoadingProcessPinned->ExpireAndFadeout(); + ActiveNotifications.Remove(Filename); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStartPageWindow::SendingServiceSideCapture_Cancel(const FString Filename) +{ + const SNotificationItemWeak* LoadingProgressPtr = ActiveNotifications.Find(Filename); + if (LoadingProgressPtr) + { + TSharedPtr LoadingProcessPinned = LoadingProgressPtr->Pin(); + LoadingProcessPinned->ExpireAndFadeout(); + ActiveNotifications.Remove(Filename); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStartPageWindow::SendingServiceSideCapture_Load(const FString Filename) +{ + const SNotificationItemWeak* LoadingProgressPtr = ActiveNotifications.Find(Filename); + if (LoadingProgressPtr) + { + TSharedPtr LoadingProcessPinned = LoadingProgressPtr->Pin(); + LoadingProcessPinned->ExpireAndFadeout(); + ActiveNotifications.Remove(Filename); + + const FString PathName = FPaths::ProfilingDir() + TEXT("UnrealStats/Received/"); + const FString TraceFilepath = PathName + Filename; + FInsightsManager::Get()->LoadTraceFile(TraceFilepath); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +EActiveTimerReturnType SStartPageWindow::UpdateActiveDuration(double InCurrentTime, float InDeltaTime) +{ + DurationActive += InDeltaTime; + + // The window will explicitly unregister this active timer when the mouse leaves. + return EActiveTimerReturnType::Continue; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStartPageWindow::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + SCompoundWidget::OnMouseEnter(MyGeometry, MouseEvent); + + if (!ActiveTimerHandle.IsValid()) + { + ActiveTimerHandle = RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SStartPageWindow::UpdateActiveDuration)); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStartPageWindow::OnMouseLeave(const FPointerEvent& MouseEvent) +{ + SCompoundWidget::OnMouseLeave(MouseEvent); + + auto PinnedActiveTimerHandle = ActiveTimerHandle.Pin(); + if (PinnedActiveTimerHandle.IsValid()) + { + UnRegisterActiveTimer(PinnedActiveTimerHandle.ToSharedRef()); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SStartPageWindow::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + return FReply::Unhandled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SStartPageWindow::OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) +{ + TSharedPtr DragDropOp = DragDropEvent.GetOperationAs(); + if (DragDropOp.IsValid()) + { + if (DragDropOp->HasFiles()) + { + const TArray& Files = DragDropOp->GetFiles(); + if (Files.Num() == 1) + { + const FString DraggedFileExtension = FPaths::GetExtension(Files[0], true); + if (DraggedFileExtension == TEXT(".utrace")) + { + return FReply::Handled(); + } + } + } + } + + return SCompoundWidget::OnDragOver(MyGeometry, DragDropEvent); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SStartPageWindow::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) +{ + TSharedPtr DragDropOp = DragDropEvent.GetOperationAs(); + if (DragDropOp.IsValid()) + { + if (DragDropOp->HasFiles()) + { + // For now, only allow a single file. + const TArray& Files = DragDropOp->GetFiles(); + if (Files.Num() == 1) + { + const FString DraggedFileExtension = FPaths::GetExtension(Files[0], true); + if (DraggedFileExtension == TEXT(".utrace")) + { + FInsightsManager::Get()->LoadTraceFile(Files[0]); + return FReply::Handled(); + } + } + } + } + + return SCompoundWidget::OnDrop(MyGeometry, DragDropEvent); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStartPageWindow::Live_IsEnabled() const +{ + return bIsAnyLiveSessionAvailable; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStartPageWindow::Last_IsEnabled() const +{ + return bIsAnySessionAvailable; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SStartPageWindow::Live_OnClicked() +{ + FInsightsManager::Get()->LoadLastLiveSession(); + return FReply::Handled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SStartPageWindow::Last_OnClicked() +{ + FInsightsManager::Get()->LoadLastSession(); + return FReply::Handled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SStartPageWindow::Load_OnClicked() +{ + //const FString ProfilingDirectory(FPaths::ConvertRelativePathToFull(*FPaths::ProfilingDir())); + TSharedRef SessionService = FInsightsManager::Get()->GetSessionService(); + const FString ProfilingDirectory(FPaths::ConvertRelativePathToFull(SessionService->GetLocalSessionDirectory())); + + TArray OutFiles; + bool bOpened = false; + + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + if (DesktopPlatform != nullptr) + { + FSlateApplication::Get().CloseToolTip(); + + bOpened = DesktopPlatform->OpenFileDialog + ( + FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), + LOCTEXT("LoadTrace_FileDesc", "Open trace file...").ToString(), + ProfilingDirectory, + TEXT(""), + LOCTEXT("LoadTrace_FileFilter", "Trace files (*.utrace)|*.utrace|All files (*.*)|*.*").ToString(), + EFileDialogFlags::None, + OutFiles + ); + } + + if (bOpened == true) + { + if (OutFiles.Num() == 1) + { + FInsightsManager::Get()->LoadTraceFile(OutFiles[0]); + } + } + + return FReply::Handled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStartPageWindow::LoadTraceFile(const TCHAR* TraceFile) +{ + FInsightsManager::Get()->LoadTraceFile(FString(TraceFile)); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStartPageWindow::LoadSession(Trace::FSessionHandle SessionHandle) +{ + FInsightsManager::Get()->LoadSession(SessionHandle); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef SStartPageWindow::MakeSessionListMenu() +{ + RefreshConnectionList(); + + FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr); + + MenuBuilder.BeginSection("MostRecentlyUsedSessions", LOCTEXT("MostRecentlyUsedSessionsHeading", "Most Recently Used Sessions")); + { + //TODO: MRU + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("AvailableSessions", LOCTEXT("AvailableSessionsHeading", "Available Sessions")); + { + TSharedRef SessionService = FInsightsManager::Get()->GetSessionService(); + + TArray AvailableSessions; + SessionService->GetAvailableSessions(AvailableSessions); + + // Iterate in reverse order as we want most recent sessions first. + for (int32 SessionIndex = AvailableSessions.Num() - 1; SessionIndex >= 0; --SessionIndex) + { + Trace::FSessionHandle SessionHandle = AvailableSessions[SessionIndex]; + + Trace::FSessionInfo SessionInfo; + SessionService->GetSessionInfo(SessionHandle, SessionInfo); + + FText Label = FText::FromString(SessionInfo.Name); + if (SessionInfo.bIsLive) + { + Label = FText::Format(LOCTEXT("LiveSessionTextFmt", "{0} (LIVE!)"), Label); + } + + MenuBuilder.AddMenuEntry( + Label, + TAttribute(), // no tooltip + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &SStartPageWindow::LoadSession, SessionHandle)), + NAME_None, + EUserInterfaceActionType::Button + ); + } + } + MenuBuilder.EndSection(); + + return MenuBuilder.MakeWidget(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText SStartPageWindow::GetLocalSessionDirectory() const +{ + TSharedRef SessionService = FInsightsManager::Get()->GetSessionService(); + return FText::FromString(FPaths::ConvertRelativePathToFull(SessionService->GetLocalSessionDirectory())); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SStartPageWindow::ExploreLocalSessionDirectory_OnClicked() +{ + TSharedRef SessionService = FInsightsManager::Get()->GetSessionService(); + FString FullPath(FPaths::ConvertRelativePathToFull(SessionService->GetLocalSessionDirectory())); + FPlatformProcess::ExploreFolder(*FullPath); + return FReply::Handled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText SStartPageWindow::GetRecorderStatusText() const +{ + TSharedRef SessionService = FInsightsManager::Get()->GetSessionService(); + if (SessionService->IsRecorderServerRunning()) + { + return FText(LOCTEXT("RecorderServerRunning", "Running")); + } + else + { + return FText(LOCTEXT("RecorderServerRunning", "Stopped")); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +EVisibility SStartPageWindow::StartTraceRecorder_Visibility() const +{ + TSharedRef SessionService = FInsightsManager::Get()->GetSessionService(); + return SessionService->IsRecorderServerRunning() ? EVisibility::Collapsed : EVisibility::Visible; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +EVisibility SStartPageWindow::StopTraceRecorder_Visibility() const +{ + TSharedRef SessionService = FInsightsManager::Get()->GetSessionService(); + return SessionService->IsRecorderServerRunning() ? EVisibility::Visible : EVisibility::Collapsed; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SStartPageWindow::StartTraceRecorder_OnClicked() +{ + TSharedRef SessionService = FInsightsManager::Get()->GetSessionService(); + SessionService->StartRecorderServer(); + RefreshConnectionList(); + return FReply::Handled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply SStartPageWindow::StopTraceRecorder_OnClicked() +{ + TSharedRef SessionService = FInsightsManager::Get()->GetSessionService(); + SessionService->StopRecorderServer(); + RefreshConnectionList(); + return FReply::Handled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStartPageWindow::OpenSettings() +{ + MainContentPanel->SetEnabled(false); + (*OverlaySettingsSlot) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("NotificationList.ItemBackground")) + .Padding(8.0f) + [ + SNew(SInsightsSettings) + .OnClose(this, &SStartPageWindow::CloseSettings) + .SettingPtr(&FInsightsManager::GetSettings()) + ] + ]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStartPageWindow::CloseSettings() +{ + // Close the profiler settings by simply replacing widget with a null one. + (*OverlaySettingsSlot) + [ + SNullWidget::NullWidget + ]; + MainContentPanel->SetEnabled(true); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStartPageWindow.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStartPageWindow.h new file mode 100644 index 000000000000..91bc128b047c --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStartPageWindow.h @@ -0,0 +1,211 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Docking/TabManager.h" +#include "Input/Reply.h" +#include "Layout/Visibility.h" +#include "Misc/Guid.h" +#include "SlateFwd.h" +#include "TraceServices/ModuleService.h" +#include "TraceServices/SessionService.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/Docking/SDockTab.h" +#include "Widgets/Layout/SSplitter.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/SOverlay.h" +#include "Widgets/Views/SListView.h" + +// Insights +#include "Insights/InsightsManager.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FActiveTimerHandle; +class SVerticalBox; +class SEditableTextBox; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** Type definition for shared pointers to instances of SNotificationItem. */ +typedef TSharedPtr SNotificationItemPtr; + +/** Type definition for shared references to instances of SNotificationItem. */ +typedef TSharedRef SNotificationItemRef; + +/** Type definition for weak references to instances of SNotificationItem. */ +typedef TWeakPtr SNotificationItemWeak; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct FRecorderConnection +{ + Trace::FSessionHandle SessionHandle; + FText Name; + FText Uri; + + FRecorderConnection(const Trace::FSessionHandle InSessionHandle, const Trace::FSessionInfo& InSessionInfo) + : SessionHandle(InSessionHandle) + , Name(FText::FromString(InSessionInfo.Name)) + , Uri(FText::FromString(InSessionInfo.Uri)) + {} +}; + +/** Implements the Start Page window. */ +class SStartPageWindow : public SCompoundWidget +{ +public: + /** Default constructor. */ + SStartPageWindow(); + + /** Virtual destructor. */ + virtual ~SStartPageWindow(); + + SLATE_BEGIN_ARGS(SStartPageWindow){} + SLATE_END_ARGS() + + /** Constructs this widget. */ + void Construct(const FArguments& InArgs); + + void ManageLoadingProgressNotificationState(const FString& Filename, const EInsightsNotificationType NotificatonType, const ELoadingProgressState ProgressState, const float LoadingProgress); + + void OpenSettings(); + void CloseSettings(); + +private: + TSharedRef ConstructRecoderPanel(); + TSharedRef ConstructModuleList(); + + /** Generate a new row for the Connections list view. */ + TSharedRef Connections_OnGenerateRow(TSharedPtr InConnection, const TSharedRef& OwnerTable); + + /** Callback for determining the visibility of the 'Please select a trace' overlay. */ + EVisibility IsSessionOverlayVisible() const; + + bool IsSessionValid() const; + + void SendingServiceSideCapture_Cancel(const FString Filename); + void SendingServiceSideCapture_Load(const FString Filename); + + bool Live_IsEnabled() const; + bool Last_IsEnabled() const; + + FReply Live_OnClicked(); + FReply Last_OnClicked(); + FReply Load_OnClicked(); + + void LoadTraceFile(const TCHAR* TraceFile); + void LoadSession(Trace::FSessionHandle SessionHandle); + + TSharedRef MakeSessionListMenu(); + + FText GetLocalSessionDirectory() const; + FReply ExploreLocalSessionDirectory_OnClicked(); + + FText GetRecorderStatusText() const; + EVisibility StartTraceRecorder_Visibility() const; + EVisibility StopTraceRecorder_Visibility() const; + FReply StartTraceRecorder_OnClicked(); + FReply StopTraceRecorder_OnClicked(); + + EVisibility Modules_Visibility() const; + ECheckBoxState Module_IsChecked(int32 ModuleIndex) const; + void Module_OnCheckStateChanged(ECheckBoxState NewRadioState, int32 ModuleIndex); + + FReply RefreshConnections_OnClicked(); + FReply Connect_OnClicked(); + void RefreshConnectionList(); + void Connections_OnSelectionChanged(TSharedPtr Connection, ESelectInfo::Type SelectInfo); + EVisibility Connections_Visibility() const; + + /** Updates the amount of time the profiler has been active. */ + EActiveTimerReturnType UpdateActiveDuration(double InCurrentTime, float InDeltaTime); + + /** + * Ticks this widget. Override in derived classes, but always call the parent implementation. + * + * @param AllottedGeometry The space allotted for this widget + * @param InCurrentTime Current absolute real time + * @param InDeltaTime Real time passed since last tick + */ + virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; + + /** + * The system will use this event to notify a widget that the cursor has entered it. This event is NOT bubbled. + * + * @param MyGeometry The Geometry of the widget receiving the event + * @param MouseEvent Information about the input event + */ + virtual void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + /** + * The system will use this event to notify a widget that the cursor has left it. This event is NOT bubbled. + * + * @param MouseEvent Information about the input event + */ + virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override; + + /** + * Called after a key is pressed when this widget has focus + * + * @param MyGeometry The Geometry of the widget receiving the event + * @param InKeyEvent Key event + * + * @return Returns whether the event was handled, along with other possible actions + */ + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; + + /** + * Called when the user is dropping something onto a widget; terminates drag and drop. + * + * @param MyGeometry The geometry of the widget receiving the event. + * @param DragDropEvent The drag and drop event. + * + * @return A reply that indicated whether this event was handled. + */ + virtual FReply OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override; + + /** + * Called during drag and drop when the the mouse is being dragged over a widget. + * + * @param MyGeometry The geometry of the widget receiving the event. + * @param DragDropEvent The drag and drop event. + * + * @return A reply that indicated whether this event was handled. + */ + virtual FReply OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override; + +public: + /** Widget for the non-intrusive notifications. */ + TSharedPtr NotificationList; + + /** Holds all active and visible notifications, stored as FGuid -> SNotificationItemWeak. */ + TMap ActiveNotifications; + + /** Overlay slot which contains the profiler settings widget. */ + SOverlay::FOverlaySlot* OverlaySettingsSlot; + + /** The number of seconds the profiler has been active */ + float DurationActive; + +private: + /** The handle to the active update duration tick */ + TWeakPtr ActiveTimerHandle; + + /** Holds all widgets for the profiler window like menu bar, toolbar and tabs. */ + TSharedPtr MainContentPanel; + + bool bIsAnyLiveSessionAvailable; + Trace::FSessionHandle LastLiveSessionHandle; + + bool bIsAnySessionAvailable; + Trace::FSessionHandle LastSessionHandle; + + TSharedPtr>> ConnectionsListView; + TArray> Connections; + TSharedPtr HostTextBox; + TSharedPtr SelectedConnection; + TArray AvailableModules; + TArray AvailableModulesEnabledState; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsTableCell.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsTableCell.cpp new file mode 100644 index 000000000000..a1e82198b7ea --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsTableCell.cpp @@ -0,0 +1,146 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SStatsTableCell.h" + +#include "EditorStyleSet.h" +#include "SlateOptMacros.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Views/SExpanderArrow.h" + +// Insights +#include "Insights/ViewModels/StatsViewColumn.h" +#include "Insights/ViewModels/StatsViewColumnFactory.h" +#include "Insights/Widgets/SStatsViewTooltip.h" + +#define LOCTEXT_NAMESPACE "SStatsView" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + +void SStatsTableCell::Construct(const FArguments& InArgs, const TSharedRef& TableRow) +{ + SetHoveredTableCellDelegate = InArgs._OnSetHoveredTableCell; + StatsNodePtr = InArgs._StatsNodePtr; + ColumnId = InArgs._ColumnId; + //HighlightText = InArgs._HighlightText; + + ChildSlot + [ + GenerateWidgetForColumn(InArgs, TableRow) + ]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef SStatsTableCell::GenerateWidgetForColumn(const FArguments& InArgs, const TSharedRef& TableRow) +{ + if (InArgs._IsStatsNameColumn) + { + return GenerateWidgetForNameColumn(InArgs, TableRow); + } + else + { + return GenerateWidgetForStatsColumn(InArgs, TableRow); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef SStatsTableCell::GenerateWidgetForNameColumn(const FArguments& InArgs, const TSharedRef& TableRow) +{ + const FStatsViewColumn& Column = *FStatsViewColumnFactory::Get().ColumnIdToPtrMapping.FindChecked(ColumnId); + + return + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + .VAlign(VAlign_Center) + [ + SNew(SExpanderArrow, TableRow) + ] + + // Event info icon + tooltip. + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(SImage) + .Visibility(this, &SStatsTableCell::GetHintIconVisibility) + .Image(FEditorStyle::GetBrush("Profiler.Tooltip.HintIcon10")) + .ToolTip(SStatsViewTooltip::GetTableCellTooltip(StatsNodePtr)) + ] + + // Name + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .HAlign(Column.HorizontalAlignment) + .Padding(FMargin(2.0f, 0.0f)) + [ + SNew(STextBlock) + //.Text(StatsNodePtr->GetNameEx()) + .Text(this, &SStatsTableCell::GetNameEx) + .HighlightText(InArgs._HighlightText) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + .ColorAndOpacity(this, &SStatsTableCell::GetColorAndOpacity) + .ShadowColorAndOpacity(this, &SStatsTableCell::GetShadowColorAndOpacity) + ] + + /* + // Culled children warning icon + tooltip. + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), TEXT("HoverHintOnly")) + .ContentPadding(0.0f) + .IsFocusable(false) + //.OnClicked(this, &SStatsTableCell::ExpandCulledEvents_OnClicked) + [ + SNew(SImage) + .Visibility(this, &SStatsTableCell::GetCulledEventsIconVisibility) + .Image(FEditorStyle::GetBrush("Profiler.EventGraph.HasCulledEventsSmall")) + .ToolTipText(LOCTEXT("HasCulledEvents_TT", "This event contains culled children, if you want to see all children, please disable culling or use function details, or press this icon")) + ] + ]*/ + ; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef SStatsTableCell::GenerateWidgetForStatsColumn(const FArguments& InArgs, const TSharedRef& TableRow) +{ + const FStatsViewColumn& Column = *FStatsViewColumnFactory::Get().ColumnIdToPtrMapping.FindChecked(ColumnId); + const FText FormattedValue = Column.GetFormattedValue(*StatsNodePtr); + + return + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(Column.HorizontalAlignment) + .Padding(FMargin(2.0f, 0.0f)) + [ + SNew(STextBlock) + .Text(FormattedValue) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + .ColorAndOpacity(this, &SStatsTableCell::GetStatsColorAndOpacity) + .ShadowColorAndOpacity(this, &SStatsTableCell::GetShadowColorAndOpacity) + ]; +} + +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsTableCell.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsTableCell.h new file mode 100644 index 000000000000..7cff65b2541f --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsTableCell.h @@ -0,0 +1,140 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" + +// Insights +#include "Insights/ViewModels/StatsNode.h" + +DECLARE_DELEGATE_TwoParams(FSetHoveredStatsTableCell, const FName /*ColumnId*/, const FStatsNodePtr /*SamplePtr*/); +DECLARE_DELEGATE_RetVal_OneParam(bool, FIsColumnVisibleDelegate, const FName /*ColumnId*/); +DECLARE_DELEGATE_RetVal_OneParam(EHorizontalAlignment, FGetColumnOutlineHAlignmentDelegate, const FName /*ColumnId*/); + +class SStatsTableCell : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SStatsTableCell) {} + SLATE_EVENT(FSetHoveredStatsTableCell, OnSetHoveredTableCell) + SLATE_ATTRIBUTE(FText, HighlightText) + SLATE_ARGUMENT(FStatsNodePtr, StatsNodePtr) + SLATE_ARGUMENT(FName, ColumnId) + SLATE_ARGUMENT(bool, IsStatsNameColumn) + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs, const TSharedRef& TableRow); + +protected: + TSharedRef GenerateWidgetForColumn(const FArguments& InArgs, const TSharedRef& TableRow); + TSharedRef GenerateWidgetForNameColumn(const FArguments& InArgs, const TSharedRef& TableRow); + TSharedRef GenerateWidgetForStatsColumn(const FArguments& InArgs, const TSharedRef& TableRow); + + /** + * The system will use this event to notify a widget that the cursor has entered it. This event is NOT bubbled. + * + * @param MyGeometry The Geometry of the widget receiving the event + * @param MouseEvent Information about the input event + */ + virtual void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override + { + SCompoundWidget::OnMouseEnter(MyGeometry, MouseEvent); + SetHoveredTableCellDelegate.ExecuteIfBound(ColumnId, StatsNodePtr); + } + + /** + * The system will use this event to notify a widget that the cursor has left it. This event is NOT bubbled. + * + * @param MouseEvent Information about the input event + */ + virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override + { + SCompoundWidget::OnMouseLeave(MouseEvent); + SetHoveredTableCellDelegate.ExecuteIfBound(NAME_None, nullptr); + } + + /** + * Called during drag and drop when the drag enters a widget. + * + * Enter/Leave events in slate are meant as lightweight notifications. + * So we do not want to capture mouse or set focus in response to these. + * However, OnDragEnter must also support external APIs (e.g. OLE Drag/Drop) + * Those require that we let them know whether we can handle the content + * being dragged OnDragEnter. + * + * The concession is to return a can_handled/cannot_handle + * boolean rather than a full FReply. + * + * @param MyGeometry The geometry of the widget receiving the event. + * @param DragDropEvent The drag and drop event. + * + * @return A reply that indicated whether the contents of the DragDropEvent can potentially be processed by this widget. + */ + virtual void OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override + { + SCompoundWidget::OnDragEnter(MyGeometry, DragDropEvent); + SetHoveredTableCellDelegate.ExecuteIfBound(ColumnId, StatsNodePtr); + } + + /** + * Called during drag and drop when the drag leaves a widget. + * + * @param DragDropEvent The drag and drop event. + */ + virtual void OnDragLeave(const FDragDropEvent& DragDropEvent) override + { + SCompoundWidget::OnDragLeave(DragDropEvent); + SetHoveredTableCellDelegate.ExecuteIfBound(NAME_None, nullptr); + } + + EVisibility GetHintIconVisibility() const + { + return IsHovered() ? EVisibility::Visible : EVisibility::Hidden; + } + + EVisibility GetCulledEventsIconVisibility() const + { + //return StatsNodePtr->HasCulledChildren() ? EVisibility::Visible : EVisibility::Collapsed; + return EVisibility::Collapsed; + } + + FText GetNameEx() const + { + return StatsNodePtr->GetNameEx(); + } + + FSlateColor GetColorAndOpacity() const + { + const FLinearColor TextColor = + StatsNodePtr->IsFiltered() ? FLinearColor(1.0f, 1.0f, 1.0f, 0.5f) : + FLinearColor::White; + return TextColor; + } + + FSlateColor GetStatsColorAndOpacity() const + { + const FLinearColor TextColor = + StatsNodePtr->IsFiltered() ? FLinearColor(1.0f, 1.0f, 1.0f, 0.5f) : + StatsNodePtr->GetAggregatedStats().Count == 0 ? FLinearColor(1.0f, 1.0f, 1.0f, 0.6f) : + FLinearColor::White; + return TextColor; + } + + FLinearColor GetShadowColorAndOpacity() const + { + const FLinearColor ShadowColor = + StatsNodePtr->IsFiltered() ? FLinearColor(0.f, 0.f, 0.f, 0.25f) : + FLinearColor(0.0f, 0.0f, 0.0f, 0.5f); + return ShadowColor; + } + +protected: + /** A shared pointer to the stats node. */ + FStatsNodePtr StatsNodePtr; + + /** The Id of the column where this stats belongs. */ + FName ColumnId; + + FSetHoveredStatsTableCell SetHoveredTableCellDelegate; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsTableRow.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsTableRow.cpp new file mode 100644 index 000000000000..b3a5624810f6 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsTableRow.cpp @@ -0,0 +1,273 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SStatsTableRow.h" + +#include "Widgets/SOverlay.h" +#include "Widgets/Images/SImage.h" + +// Insights +#include "Insights/Common/TimeUtils.h" +#include "Insights/ViewModels/StatsViewColumnFactory.h" +#include "Insights/Widgets/SStatsViewTooltip.h" +#include "Insights/Widgets/SStatsTableCell.h" + +#define LOCTEXT_NAMESPACE "SStatsView" + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + +void SStatsTableRow::Construct(const FArguments& InArgs, const TSharedRef& InOwnerTableView) +{ + OnShouldBeEnabled = InArgs._OnShouldBeEnabled; + IsColumnVisibleDelegate = InArgs._OnIsColumnVisible; + SetHoveredTableCellDelegate = InArgs._OnSetHoveredTableCell; + GetColumnOutlineHAlignmentDelegate = InArgs._OnGetColumnOutlineHAlignmentDelegate; + + HighlightText = InArgs._HighlightText; + HighlightedStatsName = InArgs._HighlightedStatsName; + + StatsNodePtr = InArgs._StatsNodePtr; + + SetEnabled(TAttribute(this, &SStatsTableRow::HandleShouldBeEnabled)); + + SMultiColumnTableRow::Construct(SMultiColumnTableRow::FArguments(), InOwnerTableView); + + /* + const FSlateBrush* const NodeIcon = StatsNodeTypeHelper::GetIconForStatsNodeType(InStatsNode->GetType()); + const TSharedRef Tooltip = InStatsNode->IsGroup() ? SNew(SToolTip) : SStatsViewTooltip(InStatsNode->GetId()).GetTooltip(); + + ChildSlot + [ + SNew(SHorizontalBox) + + // Expander arrow. + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + .VAlign(VAlign_Center) + [ + SNew(SExpanderArrow, SharedThis(this)) + ] + + // Icon to visualize group or timer type. + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f, 0.0f, 8.0f, 0.0f) + [ + SNew(SImage) + .Image(NodeIcon) + .ToolTip(Tooltip) + ] + + // Description text. + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + .Padding(FMargin(2.0f, 0.0f)) + [ + SNew(STextBlock) + .Text(this, &SStatsTableRow::GetText) + .HighlightText(InArgs._HighlightText) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + .ColorAndOpacity(this, &SStatsTableRow::GetColorAndOpacity) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Center) + .VAlign(VAlign_Top) + .Padding(0.0f, 1.0f, 0.0f, 0.0f) + [ + SNew(SImage) + .Visibility(!InStatsNode->IsGroup() ? EVisibility::Visible : EVisibility::Collapsed) + .Image(FEditorStyle::GetBrush("Profiler.Tooltip.HintIcon10")) + .ToolTip(Tooltip) + ] + ]; + + STableRow::ConstructInternal(STableRow::FArguments().ShowSelection(true), InOwnerTableView); + */ +} + +TSharedRef SStatsTableRow::GenerateWidgetForColumn(const FName& ColumnId) +{ + return + + SNew(SOverlay) + .Visibility(EVisibility::SelfHitTestInvisible) + + +SOverlay::Slot() + .Padding(0.0f) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("Profiler.LineGraphArea")) + .ColorAndOpacity(this, &SStatsTableRow::GetBackgroundColorAndOpacity) + ] + + +SOverlay::Slot() + .Padding(0.0f) + [ + SNew(SImage) + .Image(this, &SStatsTableRow::GetOutlineBrush, ColumnId) + .ColorAndOpacity(this, &SStatsTableRow::GetOutlineColorAndOpacity) + ] + + +SOverlay::Slot() + [ + SNew(SStatsTableCell, SharedThis(this)) + .Visibility(this, &SStatsTableRow::IsColumnVisible, ColumnId) + .StatsNodePtr(StatsNodePtr) + .ColumnId(ColumnId) + .HighlightText(HighlightText) + .IsStatsNameColumn(ColumnId == FStatsViewColumnFactory::Get().Collection[0]->Id) // name column + .OnSetHoveredTableCell(this, &SStatsTableRow::OnSetHoveredTableCell) + ]; +} + +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +FReply SStatsTableRow::OnDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + //im:TODO + //if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) + //{ + // if (StatsNode->IsGroup()) + // { + // // Add all timer Ids for the group. + // TArray StatsIds; + // const TArray& FilteredChildren = StatsNode->GetFilteredChildren(); + // const int32 NumFilteredChildren = FilteredChildren.Num(); + // + // StatsIds.Reserve(NumFilteredChildren); + // for (int32 Nx = 0; Nx < NumFilteredChildren; ++Nx) + // { + // StatsIds.Add(FilteredChildren[Nx]->GetId()); + // } + // + // return FReply::Handled().BeginDragDrop(FStatIDDragDropOp::NewGroup(StatsIds, StatsNode->GetName().GetPlainNameString())); + // } + // else + // { + // return FReply::Handled().BeginDragDrop(FStatIDDragDropOp::NewSingle(StatsNode->GetId(), StatsNode->GetName().GetPlainNameString())); + // } + //} + + return SMultiColumnTableRow::OnDragDetected(MyGeometry, MouseEvent); +} + +FText SStatsTableRow::GetText() const +{ + FText Text = FText::GetEmpty(); + + if (StatsNodePtr->IsGroup()) + { + Text = FText::Format(LOCTEXT("StatsNode_GroupNodeTextFmt", "{0} ({1})"), FText::FromName(StatsNodePtr->GetName()), FText::AsNumber(StatsNodePtr->GetChildren().Num())); + } + else + { + Text = FText::FromName(StatsNodePtr->GetName()); + } + + return Text; +} + +FSlateFontInfo SStatsTableRow::GetFont() const +{ + const bool bIsStatTracked = false;//im:TODO: FTimingProfilerManager::Get()->IsStatTracked(StatsNodePtr->GetStatsId()); + const FSlateFontInfo FontInfo = bIsStatTracked ? FEditorStyle::GetFontStyle("BoldFont") : FEditorStyle::GetFontStyle("NormalFont"); + return FontInfo; +} + +FSlateColor SStatsTableRow::GetColorAndOpacity() const +{ + //const bool bIsStatTracked = FTimingProfilerManager::Get()->IsStatTracked(StatsNodePtr->GetId()); + //const FSlateColor Color = bIsStatTracked ? FTimingProfilerManager::Get()->GetColorForStatsId(StatsNodePtr->GetId()) : FLinearColor::White; + //return Color; + //FTimingProfilerManager::GetSettings().GetColorForStat(StatName) + return FLinearColor::White; +} + +FSlateColor SStatsTableRow::GetBackgroundColorAndOpacity() const +{ + //return GetBackgroundColorAndOpacity(StatsNodePtr->GetAggregatedStats().Sum); + return FLinearColor(0.0f, 0.0f, 0.0f, 1.0f); +} + +FSlateColor SStatsTableRow::GetBackgroundColorAndOpacity(double Time) const +{ + const FLinearColor Color = Time > TimeUtils::Second ? FLinearColor(0.3f, 0.0f, 0.0f, 1.0f) : + Time > TimeUtils::Milisecond ? FLinearColor(0.3f, 0.1f, 0.0f, 1.0f) : + Time > TimeUtils::Microsecond ? FLinearColor(0.0f, 0.1f, 0.0f, 1.0f) : + FLinearColor(0.0f, 0.0f, 0.0f, 1.0f); + return Color; +} + +FSlateColor SStatsTableRow::GetOutlineColorAndOpacity() const +{ + const FLinearColor NoColor(0.0f, 0.0f, 0.0f, 0.0f); + const bool bShouldBeHighlighted = StatsNodePtr->GetName() == HighlightedStatsName.Get(); + const FLinearColor OutlineColorAndOpacity = bShouldBeHighlighted ? FLinearColor(FColorList::SlateBlue) : NoColor; + return OutlineColorAndOpacity; +} + +const FSlateBrush* SStatsTableRow::GetOutlineBrush(const FName ColumnId) const +{ + EHorizontalAlignment Result = HAlign_Center; + if (IsColumnVisibleDelegate.IsBound()) + { + Result = GetColumnOutlineHAlignmentDelegate.Execute(ColumnId); + } + + const FSlateBrush* Brush = nullptr; + if (Result == HAlign_Left) + { + Brush = FEditorStyle::GetBrush("Profiler.EventGraph.Border.L"); + } + else if(Result == HAlign_Right) + { + Brush = FEditorStyle::GetBrush("Profiler.EventGraph.Border.R"); + } + else + { + Brush = FEditorStyle::GetBrush("Profiler.EventGraph.Border.TB"); + } + return Brush; +} + +bool SStatsTableRow::HandleShouldBeEnabled() const +{ + bool bResult = false; + + if (StatsNodePtr->IsGroup()) + { + bResult = true; + } + else + { + if (OnShouldBeEnabled.IsBound()) + { + bResult = OnShouldBeEnabled.Execute(StatsNodePtr->GetId()); + } + } + + return bResult; +} + +EVisibility SStatsTableRow::IsColumnVisible(const FName ColumnId) const +{ + EVisibility Result = EVisibility::Collapsed; + + if (IsColumnVisibleDelegate.IsBound()) + { + Result = IsColumnVisibleDelegate.Execute(ColumnId) ? EVisibility::Visible : EVisibility::Collapsed; + } + + return Result; +} + +void SStatsTableRow::OnSetHoveredTableCell(const FName InColumnId, const FStatsNodePtr InSamplePtr) +{ + SetHoveredTableCellDelegate.ExecuteIfBound(InColumnId, InSamplePtr); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsTableRow.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsTableRow.h new file mode 100644 index 000000000000..e834e339e1a9 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsTableRow.h @@ -0,0 +1,93 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/Views/SHeaderRow.h" +#include "Widgets/Views/STableViewBase.h" +#include "Widgets/Views/STableRow.h" +#include "Widgets/Views/STreeView.h" + +// Insights +#include "Insights/ViewModels/StatsNodeHelper.h" + +DECLARE_DELEGATE_RetVal_OneParam(bool, FShouldBeEnabledDelegate, const uint32 /*StatsId*/); +DECLARE_DELEGATE_RetVal_OneParam(bool, FIsColumnVisibleDelegate, const FName /*ColumnId*/); +DECLARE_DELEGATE_TwoParams(FSetHoveredStatsTableCell, const FName /*ColumnId*/, const FStatsNodePtr /*StatsNodePtr*/); +DECLARE_DELEGATE_RetVal_OneParam(EHorizontalAlignment, FGetColumnOutlineHAlignmentDelegate, const FName /*ColumnId*/); + +/** Widget that represents a table row in the stats' tree control. Generates widgets for each column on demand. */ +class SStatsTableRow : public SMultiColumnTableRow +{ +public: + SLATE_BEGIN_ARGS(SStatsTableRow) {} + SLATE_EVENT(FShouldBeEnabledDelegate, OnShouldBeEnabled) + SLATE_EVENT(FIsColumnVisibleDelegate, OnIsColumnVisible) + SLATE_EVENT(FSetHoveredStatsTableCell, OnSetHoveredTableCell) + SLATE_EVENT(FGetColumnOutlineHAlignmentDelegate, OnGetColumnOutlineHAlignmentDelegate) + SLATE_ATTRIBUTE(FText, HighlightText) + SLATE_ATTRIBUTE(FName, HighlightedStatsName) + SLATE_ARGUMENT(FStatsNodePtr, StatsNodePtr) + SLATE_END_ARGS() + +public: + void Construct(const FArguments& InArgs, const TSharedRef& InOwnerTableView); + + virtual TSharedRef GenerateWidgetForColumn(const FName& ColumnId) override; + + /** + * Called when Slate detects that a widget started to be dragged. + * Usage: + * A widget can ask Slate to detect a drag. + * OnMouseDown() reply with FReply::Handled().DetectDrag(SharedThis(this)). + * Slate will either send an OnDragDetected() event or do nothing. + * If the user releases a mouse button or leaves the widget before + * a drag is triggered (maybe user started at the very edge) then no event will be + * sent. + * + * @param InMyGeometry Widget geometry + * @param InMouseEvent MouseMove that triggered the drag + * + */ + virtual FReply OnDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + +protected: + /** + * @return a text which describes this table row, refers to both groups and stats + */ + FText GetText() const; + + /** + * @return a font style which is used to draw this table row, refers to both groups and stats + */ + FSlateFontInfo GetFont() const; + + /** + * @return a color and opacity value used to draw this table row, refers to both groups and stats + */ + FSlateColor GetColorAndOpacity() const; + + FSlateColor GetBackgroundColorAndOpacity() const; + FSlateColor GetBackgroundColorAndOpacity(double Time) const; + FSlateColor GetOutlineColorAndOpacity() const; + const FSlateBrush* GetOutlineBrush(const FName ColumnId) const; + bool HandleShouldBeEnabled() const; + EVisibility IsColumnVisible(const FName ColumnId) const; + void OnSetHoveredTableCell(const FName ColumnId, const FStatsNodePtr SamplePtr); + +protected: + /** Data context for this table row. */ + FStatsNodePtr StatsNodePtr; + + FShouldBeEnabledDelegate OnShouldBeEnabled; + FIsColumnVisibleDelegate IsColumnVisibleDelegate; + FSetHoveredStatsTableCell SetHoveredTableCellDelegate; + FGetColumnOutlineHAlignmentDelegate GetColumnOutlineHAlignmentDelegate; + + /** Text to be highlighted on stats name. */ + TAttribute HighlightText; + + /** Name of the stats that should be drawn as highlighted. */ + TAttribute HighlightedStatsName; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsView.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsView.cpp new file mode 100644 index 000000000000..2e201364acee --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsView.cpp @@ -0,0 +1,1794 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SStatsView.h" + +#include "EditorStyleSet.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "SlateOptMacros.h" +#include "TraceServices/AnalysisService.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Layout/SScrollBox.h" +#include "Widgets/Input/SSearchBox.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SGridPanel.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/SToolTip.h" +#include "Widgets/Views/STableViewBase.h" + +// Insights +#include "Insights/TimingProfilerCommon.h" +#include "Insights/TimingProfilerManager.h" +#include "Insights/ViewModels/StatsNodeHelper.h" +#include "Insights/ViewModels/StatsViewColumnFactory.h" +#include "Insights/Widgets/SStatsViewTooltip.h" +#include "Insights/Widgets/SStatsTableRow.h" + +#define LOCTEXT_NAMESPACE "SStatsView" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const EColumnSortMode::Type SStatsView::DefaultColumnSortMode(EColumnSortMode::Ascending); +const FName SStatsView::DefaultColumnBeingSorted(FStatsViewColumns::NameColumnID); + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +SStatsView::SStatsView() + : bExpansionSaved(false) + , GroupingMode(EStatsGroupingMode::Flat) + , ColumnSortMode(DefaultColumnSortMode) + , ColumnBeingSorted(DefaultColumnBeingSorted) +{ + FMemory::Memset(bStatsNodeIsVisible, 1); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +SStatsView::~SStatsView() +{ + // Remove ourselves from the Insights manager. + if (FInsightsManager::Get().IsValid()) + { + FInsightsManager::Get()->GetSessionChangedEvent().RemoveAll(this); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION +void SStatsView::Construct(const FArguments& InArgs) +{ + SAssignNew(ExternalScrollbar, SScrollBar) + .AlwaysShowScrollbar(true); + + ChildSlot + [ + SNew(SVerticalBox) + + +SVerticalBox::Slot() + .VAlign(VAlign_Center) + .AutoHeight() + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(2.0f) + [ + SNew(SVerticalBox) + + // Search box + +SVerticalBox::Slot() + .VAlign(VAlign_Center) + .Padding(2.0f) + .AutoHeight() + [ + SAssignNew(SearchBox, SSearchBox) + .HintText(LOCTEXT("SearchBoxHint", "Search timers or groups")) + .OnTextChanged(this, &SStatsView::SearchBox_OnTextChanged) + .IsEnabled(this, &SStatsView::SearchBox_IsEnabled) + .ToolTipText(LOCTEXT("FilterSearchHint", "Type here to search timer or group")) + ] + + // Group by + +SVerticalBox::Slot() + .VAlign(VAlign_Center) + .Padding(2.0f) + .AutoHeight() + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("GroupByText", "Group by")) + ] + + +SHorizontalBox::Slot() + .FillWidth(2.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(GroupByComboBox, SComboBox>) + .ToolTipText(this, &SStatsView::GroupBy_GetSelectedTooltipText) + .OptionsSource(&GroupByOptionsSource) + .OnSelectionChanged(this, &SStatsView::GroupBy_OnSelectionChanged) + .OnGenerateWidget(this, &SStatsView::GroupBy_OnGenerateWidget) + [ + SNew(STextBlock) + .Text(this, &SStatsView::GroupBy_GetSelectedText) + ] + ] + ] + + // Check boxes for: GpuScope, ComputeScope, CpuScope + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .Padding(2.0f) + .AutoHeight() + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .Padding(FMargin(0.0f,0.0f,1.0f,0.0f)) + .FillWidth(1.0f) + [ + GetToggleButtonForStatsType(EStatsNodeType::Int64) + ] + + +SHorizontalBox::Slot() + .Padding(FMargin(1.0f,0.0f,1.0f,0.0f)) + .FillWidth(1.0f) + [ + GetToggleButtonForStatsType(EStatsNodeType::Float) + ] + ] + ] + ] + + // Tree view + +SVerticalBox::Slot() + .FillHeight(1.0f) + .Padding(0.0f, 6.0f, 0.0f, 0.0f) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(0.0f) + [ + SNew(SScrollBox) + .Orientation(Orient_Horizontal) + + + SScrollBox::Slot() + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(0.0f) + [ + SAssignNew(TreeView, STreeView) + .ExternalScrollbar(ExternalScrollbar) + .SelectionMode(ESelectionMode::Multi) + .TreeItemsSource(&FilteredGroupNodes) + .OnGetChildren(this, &SStatsView::TreeView_OnGetChildren) + .OnGenerateRow(this, &SStatsView::TreeView_OnGenerateRow) + .OnSelectionChanged(this, &SStatsView::TreeView_OnSelectionChanged) + .OnMouseButtonDoubleClick(this, &SStatsView::TreeView_OnMouseButtonDoubleClick) + .OnContextMenuOpening(FOnContextMenuOpening::CreateSP(this, &SStatsView::TreeView_GetMenuContent)) + .ItemHeight(12.0f) + .HeaderRow + ( + SAssignNew(TreeViewHeaderRow, SHeaderRow) + .Visibility(EVisibility::Visible) + ) + ] + ] + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f) + [ + SNew(SBox) + .WidthOverride(FOptionalSize(13.0f)) + [ + ExternalScrollbar.ToSharedRef() + ] + ] + ] + ]; + + InitializeAndShowHeaderColumns(); + //BindCommands(); + + // Create the search filters: text based, type based etc. + TextFilter = MakeShareable(new FStatsNodeTextFilter(FStatsNodeTextFilter::FItemToStringArray::CreateSP(this, &SStatsView::HandleItemToStringArray))); + Filters = MakeShareable(new FStatsNodeFilterCollection()); + Filters->Add(TextFilter); + + CreateGroupByOptionsSources(); + + // Register ourselves with the Insights manager. + FInsightsManager::Get()->GetSessionChangedEvent().AddSP(this, &SStatsView::InsightsManager_OnSessionChanged); +} +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedPtr SStatsView::TreeView_GetMenuContent() +{ + const TArray SelectedStatsNodes = TreeView->GetSelectedItems(); + const int32 NumSelectedStatsNodes = SelectedStatsNodes.Num(); + FStatsNodePtr SelectedStatsNode = NumSelectedStatsNodes ? SelectedStatsNodes[0] : nullptr; + + const FStatsViewColumn* const * ColumnPtrPtr = FStatsViewColumnFactory::Get().ColumnIdToPtrMapping.Find(HoveredColumnId); + const FStatsViewColumn* const ColumnPtr = (ColumnPtrPtr != nullptr) ? *ColumnPtrPtr : nullptr; + + FText SelectionStr; + FText PropertyName; + FText PropertyValue; + + if (NumSelectedStatsNodes == 0) + { + SelectionStr = LOCTEXT("NothingSelected", "Nothing selected"); + } + else if (NumSelectedStatsNodes == 1) + { + if (ColumnPtr != nullptr) + { + PropertyName = ColumnPtr->ShortName; + PropertyValue = ColumnPtr->GetFormattedValue(*SelectedStatsNode); + } + SelectionStr = FText::FromName(SelectedStatsNode->GetName()); + } + else + { + SelectionStr = LOCTEXT("MultipleSelection", "Multiple selection"); + } + + const bool bShouldCloseWindowAfterMenuSelection = true; + FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, NULL); + + // Selection menu + MenuBuilder.BeginSection("Selection", LOCTEXT("ContextMenu_Header_Selection", "Selection")); + { + struct FLocal + { + static bool ReturnFalse() + { + return false; + } + }; + + FUIAction DummyUIAction; + DummyUIAction.CanExecuteAction = FCanExecuteAction::CreateStatic(&FLocal::ReturnFalse); + MenuBuilder.AddMenuEntry + ( + SelectionStr, + LOCTEXT("ContextMenu_Selection", "Currently selected items"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "@missing.icon"), DummyUIAction, NAME_None, EUserInterfaceActionType::Button + ); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("Misc", LOCTEXT("ContextMenu_Header_Misc", "Miscellaneous")); + { + /*TODO + FUIAction Action_CopySelectedToClipboard + ( + FExecuteAction::CreateSP(this, &SStatsView::ContextMenu_CopySelectedToClipboard_Execute), + FCanExecuteAction::CreateSP(this, &SStatsView::ContextMenu_CopySelectedToClipboard_CanExecute) + ); + MenuBuilder.AddMenuEntry + ( + LOCTEXT("ContextMenu_Header_Misc_CopySelectedToClipboard", "Copy To Clipboard"), + LOCTEXT("ContextMenu_Header_Misc_CopySelectedToClipboard_Desc", "Copies selection to clipboard"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.CopyToClipboard"), Action_CopySelectedToClipboard, NAME_None, EUserInterfaceActionType::Button + ); + */ + + MenuBuilder.AddSubMenu + ( + LOCTEXT("ContextMenu_Header_Misc_Sort", "Sort By"), + LOCTEXT("ContextMenu_Header_Misc_Sort_Desc", "Sort by column"), + FNewMenuDelegate::CreateSP(this, &SStatsView::TreeView_BuildSortByMenu), + false, + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortBy") + ); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("Columns", LOCTEXT("ContextMenu_Header_Columns", "Columns")); + { + MenuBuilder.AddSubMenu + ( + LOCTEXT("ContextMenu_Header_Columns_View", "View Column"), + LOCTEXT("ContextMenu_Header_Columns_View_Desc", "Hides or shows columns"), + FNewMenuDelegate::CreateSP(this, &SStatsView::TreeView_BuildViewColumnMenu), + false, + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ViewColumn") + ); + + FUIAction Action_ShowAllColumns + ( + FExecuteAction::CreateSP(this, &SStatsView::ContextMenu_ShowAllColumns_Execute), + FCanExecuteAction::CreateSP(this, &SStatsView::ContextMenu_ShowAllColumns_CanExecute) + ); + MenuBuilder.AddMenuEntry + ( + LOCTEXT("ContextMenu_Header_Columns_ShowAllColumns", "Show All Columns"), + LOCTEXT("ContextMenu_Header_Columns_ShowAllColumns_Desc", "Resets tree view to show all columns"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ResetColumn"), Action_ShowAllColumns, NAME_None, EUserInterfaceActionType::Button + ); + + FUIAction Action_ShowMinMaxMedColumns + ( + FExecuteAction::CreateSP(this, &SStatsView::ContextMenu_ShowMinMaxMedColumns_Execute), + FCanExecuteAction::CreateSP(this, &SStatsView::ContextMenu_ShowMinMaxMedColumns_CanExecute) + ); + MenuBuilder.AddMenuEntry + ( + LOCTEXT("ContextMenu_Header_Columns_ShowMinMaxMedColumns", "Reset Columns to Min/Max/Median Preset"), + LOCTEXT("ContextMenu_Header_Columns_ShowMinMaxMedColumns_Desc", "Resets columns to Min/Max/Median preset"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ResetColumn"), Action_ShowMinMaxMedColumns, NAME_None, EUserInterfaceActionType::Button + ); + + FUIAction Action_ResetColumns + ( + FExecuteAction::CreateSP(this, &SStatsView::ContextMenu_ResetColumns_Execute), + FCanExecuteAction::CreateSP(this, &SStatsView::ContextMenu_ResetColumns_CanExecute) + ); + MenuBuilder.AddMenuEntry + ( + LOCTEXT("ContextMenu_Header_Columns_ResetColumns", "Reset Columns to Default"), + LOCTEXT("ContextMenu_Header_Columns_ResetColumns_Desc", "Resets columns to default"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ResetColumn"), Action_ResetColumns, NAME_None, EUserInterfaceActionType::Button + ); + } + MenuBuilder.EndSection(); + + return MenuBuilder.MakeWidget(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::TreeView_BuildSortByMenu(FMenuBuilder& MenuBuilder) +{ + // TODO: Refactor later @see TSharedPtr SCascadePreviewViewportToolBar::GenerateViewMenu() const + + MenuBuilder.BeginSection("ColumnName", LOCTEXT("ContextMenu_Header_Misc_ColumnName", "Column Name")); + + for (auto It = TreeViewHeaderColumns.CreateConstIterator(); It; ++It) + { + const FStatsViewColumn& Column = It.Value(); + + if (Column.bIsVisible && Column.bCanBeSorted()) + { + FUIAction Action_SortByColumn + ( + FExecuteAction::CreateSP(this, &SStatsView::ContextMenu_SortByColumn_Execute, Column.Id), + FCanExecuteAction::CreateSP(this, &SStatsView::ContextMenu_SortByColumn_CanExecute, Column.Id), + FIsActionChecked::CreateSP(this, &SStatsView::ContextMenu_SortByColumn_IsChecked, Column.Id) + ); + MenuBuilder.AddMenuEntry + ( + Column.TitleName, + Column.Description, + FSlateIcon(), Action_SortByColumn, NAME_None, EUserInterfaceActionType::RadioButton + ); + } + } + + MenuBuilder.EndSection(); + + //----------------------------------------------------------------------------- + + MenuBuilder.BeginSection("SortMode", LOCTEXT("ContextMenu_Header_Misc_Sort_SortMode", "Sort Mode")); + { + FUIAction Action_SortAscending + ( + FExecuteAction::CreateSP(this, &SStatsView::ContextMenu_SortMode_Execute, EColumnSortMode::Ascending), + FCanExecuteAction::CreateSP(this, &SStatsView::ContextMenu_SortMode_CanExecute, EColumnSortMode::Ascending), + FIsActionChecked::CreateSP(this, &SStatsView::ContextMenu_SortMode_IsChecked, EColumnSortMode::Ascending) + ); + MenuBuilder.AddMenuEntry + ( + LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending", "Sort Ascending"), + LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending_Desc", "Sorts ascending"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortAscending"), Action_SortAscending, NAME_None, EUserInterfaceActionType::RadioButton + ); + + FUIAction Action_SortDescending + ( + FExecuteAction::CreateSP(this, &SStatsView::ContextMenu_SortMode_Execute, EColumnSortMode::Descending), + FCanExecuteAction::CreateSP(this, &SStatsView::ContextMenu_SortMode_CanExecute, EColumnSortMode::Descending), + FIsActionChecked::CreateSP(this, &SStatsView::ContextMenu_SortMode_IsChecked, EColumnSortMode::Descending) + ); + MenuBuilder.AddMenuEntry + ( + LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending", "Sort Descending"), + LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending_Desc", "Sorts descending"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortDescending"), Action_SortDescending, NAME_None, EUserInterfaceActionType::RadioButton + ); + } + MenuBuilder.EndSection(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::TreeView_BuildViewColumnMenu(FMenuBuilder& MenuBuilder) +{ + MenuBuilder.BeginSection("ViewColumn", LOCTEXT("ContextMenu_Header_Columns_View", "View Column")); + + for (auto It = TreeViewHeaderColumns.CreateConstIterator(); It; ++It) + { + const FStatsViewColumn& Column = It.Value(); + + FUIAction Action_ToggleColumn + ( + FExecuteAction::CreateSP(this, &SStatsView::ContextMenu_ToggleColumn_Execute, Column.Id), + FCanExecuteAction::CreateSP(this, &SStatsView::ContextMenu_ToggleColumn_CanExecute, Column.Id), + FIsActionChecked::CreateSP(this, &SStatsView::ContextMenu_ToggleColumn_IsChecked, Column.Id) + ); + MenuBuilder.AddMenuEntry + ( + Column.TitleName, + Column.Description, + FSlateIcon(), Action_ToggleColumn, NAME_None, EUserInterfaceActionType::ToggleButton + ); + } + + MenuBuilder.EndSection(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::InitializeAndShowHeaderColumns() +{ + const int32 NumColumns = FStatsViewColumnFactory::Get().Collection.Num(); + for (int32 ColumnIndex = 0; ColumnIndex < NumColumns; ColumnIndex++) + { + TreeViewHeaderRow_CreateColumnArgs(ColumnIndex); + } + + for (auto It = TreeViewHeaderColumns.CreateConstIterator(); It; ++It) + { + const FStatsViewColumn& Column = It.Value(); + + if (Column.bIsVisible) + { + TreeViewHeaderRow_ShowColumn(Column.Id); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::TreeViewHeaderRow_CreateColumnArgs(const int32 ColumnIndex) +{ + const FStatsViewColumn& Column = *FStatsViewColumnFactory::Get().Collection[ColumnIndex]; + SHeaderRow::FColumn::FArguments ColumnArgs; + + ColumnArgs + .ColumnId(Column.Id) + .DefaultLabel(Column.ShortName) + .SortMode(EColumnSortMode::None) + .HAlignHeader(HAlign_Fill) + .VAlignHeader(VAlign_Fill) + .HeaderContentPadding(TOptional(2.0f)) + .HAlignCell(HAlign_Fill) + .VAlignCell(VAlign_Fill) + .SortMode(this, &SStatsView::GetSortModeForColumn, Column.Id) + .OnSort(this, &SStatsView::OnSortModeChanged) + .ManualWidth(Column.InitialColumnWidth) + .FixedWidth(Column.bIsFixedColumnWidth() ? Column.InitialColumnWidth : TOptional()) + .HeaderContent() + [ + SNew(SHorizontalBox) + .ToolTip(SStatsViewTooltip::GetColumnTooltip(Column)) + + + SHorizontalBox::Slot() + .HAlign(Column.HorizontalAlignment) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(Column.ShortName) + ] + ] + .MenuContent() + [ + TreeViewHeaderRow_GenerateColumnMenu(Column) + ]; + + TreeViewHeaderColumnArgs.Add(Column.Id, ColumnArgs); + TreeViewHeaderColumns.Add(Column.Id, Column); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::TreeViewHeaderRow_ShowColumn(const FName ColumnId) +{ + FStatsViewColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnId); + Column.bIsVisible = true; + SHeaderRow::FColumn::FArguments& ColumnArgs = TreeViewHeaderColumnArgs.FindChecked(ColumnId); + + const int32 NumColumns = TreeViewHeaderRow->GetColumns().Num(); + int32 ColumnIndex = 0; + for (; ColumnIndex < NumColumns; ColumnIndex++) + { + const SHeaderRow::FColumn& CurrentColumn = TreeViewHeaderRow->GetColumns()[ColumnIndex]; + const FStatsViewColumn& CurrentStatsViewColumn = TreeViewHeaderColumns.FindChecked(CurrentColumn.ColumnId); + if (Column.Order < CurrentStatsViewColumn.Order) + break; + } + + TreeViewHeaderRow->InsertColumn(ColumnArgs, ColumnIndex); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef SStatsView::TreeViewHeaderRow_GenerateColumnMenu(const FStatsViewColumn& Column) +{ + bool bIsMenuVisible = false; + + const bool bShouldCloseWindowAfterMenuSelection = true; + FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, NULL); + { + if (Column.bCanBeHidden()) + { + MenuBuilder.BeginSection("Column", LOCTEXT("TreeViewHeaderRow_Header_Column", "Column")); + + FUIAction Action_HideColumn + ( + FExecuteAction::CreateSP(this, &SStatsView::HeaderMenu_HideColumn_Execute, Column.Id), + FCanExecuteAction::CreateSP(this, &SStatsView::HeaderMenu_HideColumn_CanExecute, Column.Id) + ); + + MenuBuilder.AddMenuEntry + ( + LOCTEXT("TreeViewHeaderRow_HideColumn", "Hide"), + LOCTEXT("TreeViewHeaderRow_HideColumn_Desc", "Hides the selected column"), + FSlateIcon(), Action_HideColumn, NAME_None, EUserInterfaceActionType::Button + ); + bIsMenuVisible = true; + + MenuBuilder.EndSection(); + } + + if (Column.bCanBeSorted()) + { + MenuBuilder.BeginSection("SortMode", LOCTEXT("ContextMenu_Header_Misc_Sort_SortMode", "Sort Mode")); + + FUIAction Action_SortAscending + ( + FExecuteAction::CreateSP(this, &SStatsView::HeaderMenu_SortMode_Execute, Column.Id, EColumnSortMode::Ascending), + FCanExecuteAction::CreateSP(this, &SStatsView::HeaderMenu_SortMode_CanExecute, Column.Id, EColumnSortMode::Ascending), + FIsActionChecked::CreateSP(this, &SStatsView::HeaderMenu_SortMode_IsChecked, Column.Id, EColumnSortMode::Ascending) + ); + MenuBuilder.AddMenuEntry + ( + LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending", "Sort Ascending"), + LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending_Desc", "Sorts ascending"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortAscending"), Action_SortAscending, NAME_None, EUserInterfaceActionType::RadioButton + ); + + FUIAction Action_SortDescending + ( + FExecuteAction::CreateSP(this, &SStatsView::HeaderMenu_SortMode_Execute, Column.Id, EColumnSortMode::Descending), + FCanExecuteAction::CreateSP(this, &SStatsView::HeaderMenu_SortMode_CanExecute, Column.Id, EColumnSortMode::Descending), + FIsActionChecked::CreateSP(this, &SStatsView::HeaderMenu_SortMode_IsChecked, Column.Id, EColumnSortMode::Descending) + ); + MenuBuilder.AddMenuEntry + ( + LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending", "Sort Descending"), + LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending_Desc", "Sorts descending"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortDescending"), Action_SortDescending, NAME_None, EUserInterfaceActionType::RadioButton + ); + bIsMenuVisible = true; + + MenuBuilder.EndSection(); + } + + if (Column.bCanBeFiltered()) + { + MenuBuilder.BeginSection("FilterMode", LOCTEXT("ContextMenu_Header_Misc_Filter_FilterMode", "Filter Mode")); + bIsMenuVisible = true; + MenuBuilder.EndSection(); + } + } + + /* + TODO: + - Show top ten + - Show top bottom + - Filter by list (avg, median, 10%, 90%, etc.) + - Text box for filtering for each column instead of one text box used for filtering + - Grouping button for flat view modes (show at most X groups, show all groups for names) + */ + + return bIsMenuVisible ? MenuBuilder.MakeWidget() : (TSharedRef)SNullWidget::NullWidget; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::InsightsManager_OnSessionChanged() +{ + TSharedPtr NewSession = FInsightsManager::Get()->GetSession(); + + if (NewSession != Session) + { + Session = NewSession; + RebuildTree(); + } + else + { + UpdateTree(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::UpdateTree() +{ + // Create groups, sort timers within the group and apply filtering. + CreateGroups(); + SortStats(); + ApplyFiltering(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::ApplyFiltering() +{ + FilteredGroupNodes.Reset(); + + // Apply filter to all groups and its children. + const int32 NumGroups = GroupNodes.Num(); + for (int32 GroupIndex = 0; GroupIndex < NumGroups; ++GroupIndex) + { + FStatsNodePtr& GroupPtr = GroupNodes[GroupIndex]; + GroupPtr->ClearFilteredChildren(); + const bool bIsGroupVisible = Filters->PassesAllFilters(GroupPtr); + + const TArray& GroupChildren = GroupPtr->GetChildren(); + const int32 NumChildren = GroupChildren.Num(); + int32 NumVisibleChildren = 0; + for (int32 Cx = 0; Cx < NumChildren; ++Cx) + { + // Add a child. + const FStatsNodePtr& NodePtr = GroupChildren[Cx]; + const bool bIsChildVisible = Filters->PassesAllFilters(NodePtr) && bStatsNodeIsVisible[static_cast(NodePtr->GetType())]; + if (bIsChildVisible) + { + GroupPtr->AddFilteredChild(NodePtr); + NumVisibleChildren++; + } + } + + if (bIsGroupVisible || NumVisibleChildren > 0) + { + // Add a group. + FilteredGroupNodes.Add(GroupPtr); + GroupPtr->bForceExpandGroupNode = true; + } + else + { + GroupPtr->bForceExpandGroupNode = false; + } + } + + // Only expand timer nodes if we have a text filter. + const bool bNonEmptyTextFilter = !TextFilter->GetRawFilterText().IsEmpty(); + if (bNonEmptyTextFilter) + { + if (!bExpansionSaved) + { + ExpandedNodes.Empty(); + TreeView->GetExpandedItems(ExpandedNodes); + bExpansionSaved = true; + } + + for (int32 Fx = 0; Fx < FilteredGroupNodes.Num(); Fx++) + { + const FStatsNodePtr& GroupPtr = FilteredGroupNodes[Fx]; + TreeView->SetItemExpansion(GroupPtr, GroupPtr->bForceExpandGroupNode); + } + } + else + { + if (bExpansionSaved) + { + // Restore previously expanded nodes when the text filter is disabled. + TreeView->ClearExpandedItems(); + for (auto It = ExpandedNodes.CreateConstIterator(); It; ++It) + { + TreeView->SetItemExpansion(*It, true); + } + bExpansionSaved = false; + } + } + + // Request tree refresh + TreeView->RequestTreeRefresh(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::HandleItemToStringArray(const FStatsNodePtr& FStatsNodePtr, TArray& OutSearchStrings) const +{ + OutSearchStrings.Add(FStatsNodePtr->GetName().GetPlainNameString()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef SStatsView::GetToggleButtonForStatsType(const EStatsNodeType NodeType) +{ + return SNew(SCheckBox) + .Style(FEditorStyle::Get(), "ToggleButtonCheckbox") + .HAlign(HAlign_Center) + .Padding(2.0f) + .OnCheckStateChanged(this, &SStatsView::FilterByStatsType_OnCheckStateChanged, NodeType) + .IsChecked(this, &SStatsView::FilterByStatsType_IsChecked, NodeType) + .ToolTipText(StatsNodeTypeHelper::ToDescription(NodeType)) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SImage) + .Image(StatsNodeTypeHelper::GetIconForStatsNodeType(NodeType)) + ] + + +SHorizontalBox::Slot() + .Padding(2.0f, 0.0f, 0.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(StatsNodeTypeHelper::ToName(NodeType)) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Caption")) + ] + ]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::FilterByStatsType_OnCheckStateChanged(ECheckBoxState NewRadioState, const EStatsNodeType InStatType) +{ + bStatsNodeIsVisible[static_cast(InStatType)] = (NewRadioState == ECheckBoxState::Checked); + ApplyFiltering(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +ECheckBoxState SStatsView::FilterByStatsType_IsChecked(const EStatsNodeType InStatType) const +{ + return bStatsNodeIsVisible[static_cast(InStatType)] ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// TreeView +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::TreeView_Refresh() +{ + if (TreeView.IsValid()) + { + TreeView->RequestTreeRefresh(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::TreeView_OnSelectionChanged(FStatsNodePtr SelectedItem, ESelectInfo::Type SelectInfo) +{ + if (SelectInfo != ESelectInfo::Direct) + { + TArray SelectedItems = TreeView->GetSelectedItems(); + if (SelectedItems.Num() == 1) + { + //HighlightedStatsName = SelectedItems[0]->GetName(); + } + else + { + //HighlightedStatsName = NAME_None; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::TreeView_OnGetChildren(FStatsNodePtr InParent, TArray& OutChildren) +{ + OutChildren = InParent->GetFilteredChildren(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::TreeView_OnMouseButtonDoubleClick(FStatsNodePtr StatsNode) +{ + if (!StatsNode->IsGroup()) + { + //im:TODO: const bool bIsStatTracked = FTimingProfilerManager::Get()->IsStatTracked(StatsNode->GetId()); + // if (!bIsStatTracked) + // { + // // Add a new graph. + // FTimingProfilerManager::Get()->TrackStat(StatsNode->GetId()); + // } + // else + // { + // // Remove a graph + // FTimingProfilerManager::Get()->UntrackStat(StatsNode->GetId()); + // } + } + else + { + const bool bIsGroupExpanded = TreeView->IsItemExpanded(StatsNode); + TreeView->SetItemExpansion(StatsNode, !bIsGroupExpanded); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Tree View's Table Row +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef SStatsView::TreeView_OnGenerateRow(FStatsNodePtr StatsNodePtr, const TSharedRef& OwnerTable) +{ + TSharedRef TableRow = + SNew(SStatsTableRow, OwnerTable) + .OnShouldBeEnabled(this, &SStatsView::TableRow_ShouldBeEnabled) + .OnIsColumnVisible(this, &SStatsView::TableRow_IsColumnVisible) + .OnSetHoveredTableCell(this, &SStatsView::TableRow_SetHoveredTableCell) + .OnGetColumnOutlineHAlignmentDelegate(this, &SStatsView::TableRow_GetColumnOutlineHAlignment) + .HighlightText(this, &SStatsView::TableRow_GetHighlightText) + .HighlightedStatsName(this, &SStatsView::TableRow_GetHighlightedStatsName) + .StatsNodePtr(StatsNodePtr); + + return TableRow; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStatsView::TableRow_IsColumnVisible(const FName ColumnId) const +{ + bool bResult = false; + const FStatsViewColumn& ColumnPtr = TreeViewHeaderColumns.FindChecked(ColumnId); + return ColumnPtr.bIsVisible; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::TableRow_SetHoveredTableCell(const FName ColumnId, const FStatsNodePtr StatsNodePtr) +{ + HoveredColumnId = ColumnId; + + const bool bIsAnyMenusVisible = FSlateApplication::Get().AnyMenusVisible(); + if (!HasMouseCapture() && !bIsAnyMenusVisible) + { + HoveredStatsNodePtr = StatsNodePtr; + } + + //UE_LOG(TimingProfiler, Log, TEXT("%s -> %s"), *HoveredColumnId.GetPlainNameString(), StatsNodePtr.IsValid() ? *StatsNodePtr->GetName().GetPlainNameString() : TEXT("nullptr")); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +EHorizontalAlignment SStatsView::TableRow_GetColumnOutlineHAlignment(const FName ColumnId) const +{ + const TIndirectArray& Columns = TreeViewHeaderRow->GetColumns(); + const int32 LastColumnIdx = Columns.Num() - 1; + + // First column + if (Columns[0].ColumnId == ColumnId) + { + return HAlign_Left; + } + // Last column + else if (Columns[LastColumnIdx].ColumnId == ColumnId) + { + return HAlign_Right; + } + // Middle columns + { + return HAlign_Center; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText SStatsView::TableRow_GetHighlightText() const +{ + return SearchBox->GetText(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FName SStatsView::TableRow_GetHighlightedStatsName() const +{ + return HighlightedStatsName; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStatsView::TableRow_ShouldBeEnabled(const uint32 StatsId) const +{ + return true;//im:TODO: Session->GetAggregatedStat(StatsId) != nullptr; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// SearchBox +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::SearchBox_OnTextChanged(const FText& InFilterText) +{ + TextFilter->SetRawFilterText(InFilterText); + SearchBox->SetError(TextFilter->GetFilterErrorText()); + ApplyFiltering(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStatsView::SearchBox_IsEnabled() const +{ + return StatsNodes.Num() > 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// GroupBy +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::CreateGroups() +{ + TMap GroupNodeSet; + + if (GroupingMode == EStatsGroupingMode::Flat) + { + const FName GroupName(TEXT("All")); + FStatsNodePtr* GroupPtr = GroupNodeSet.Find(GroupName); + if (!GroupPtr) + { + GroupPtr = &GroupNodeSet.Add(GroupName, MakeShareable(new FStatsNode(GroupName))); + } + + for (const FStatsNodePtr& StatsNodePtr : StatsNodes) + { + (*GroupPtr)->AddChildAndSetGroupPtr(StatsNodePtr); + } + + TreeView->SetItemExpansion(*GroupPtr, true); + } + // Creates groups based on stat metadata groups. + else if (GroupingMode == EStatsGroupingMode::ByMetaGroupName) + { + for (const FStatsNodePtr& StatsNodePtr : StatsNodes) + { + const FName GroupName = StatsNodePtr->GetMetaGroupName(); + + FStatsNodePtr* GroupPtr = GroupNodeSet.Find(GroupName); + if (!GroupPtr) + { + GroupPtr = &GroupNodeSet.Add(GroupName, MakeShareable(new FStatsNode(GroupName))); + } + + (*GroupPtr)->AddChildAndSetGroupPtr(StatsNodePtr); + TreeView->SetItemExpansion(*GroupPtr, true); + } + } + // Creates one group for each stat type. + else if (GroupingMode == EStatsGroupingMode::ByType) + { + for (const FStatsNodePtr& StatsNodePtr : StatsNodes) + { + const FName GroupName = *StatsNodeTypeHelper::ToName(StatsNodePtr->GetType()).ToString(); + + FStatsNodePtr* GroupPtr = GroupNodeSet.Find(GroupName); + if (!GroupPtr) + { + GroupPtr = &GroupNodeSet.Add(GroupName, MakeShareable(new FStatsNode(GroupName))); + } + + (*GroupPtr)->AddChildAndSetGroupPtr(StatsNodePtr); + TreeView->SetItemExpansion(*GroupPtr, true); + } + } + // Creates one group for one letter. + else if (GroupingMode == EStatsGroupingMode::ByName) + { + for (const FStatsNodePtr& StatsNodePtr : StatsNodes) + { + const FName GroupName = *StatsNodePtr->GetName().GetPlainNameString().Left(1); + + FStatsNodePtr* GroupPtr = GroupNodeSet.Find(GroupName); + if (!GroupPtr) + { + GroupPtr = &GroupNodeSet.Add(GroupName, MakeShareable(new FStatsNode(GroupName))); + } + + (*GroupPtr)->AddChildAndSetGroupPtr(StatsNodePtr); + } + } + + GroupNodeSet.GenerateValueArray(GroupNodes); + + // Sort by a fake group name. + GroupNodes.Sort(StatsNodeSortingHelper::ByNameAscending()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::CreateGroupByOptionsSources() +{ + GroupByOptionsSource.Reset(3); + + // Must be added in order of elements in the EStatsGroupingMode. + GroupByOptionsSource.Add(MakeShareable(new EStatsGroupingMode(EStatsGroupingMode::Flat))); + GroupByOptionsSource.Add(MakeShareable(new EStatsGroupingMode(EStatsGroupingMode::ByName))); + GroupByOptionsSource.Add(MakeShareable(new EStatsGroupingMode(EStatsGroupingMode::ByMetaGroupName))); + GroupByOptionsSource.Add(MakeShareable(new EStatsGroupingMode(EStatsGroupingMode::ByType))); + + EStatsGroupingModePtr* GroupingModePtrPtr = GroupByOptionsSource.FindByPredicate([&](const EStatsGroupingModePtr InGroupingModePtr) { return *InGroupingModePtr == GroupingMode; }); + if (GroupingModePtrPtr != nullptr) + { + GroupByComboBox->SetSelectedItem(*GroupingModePtrPtr); + } + + GroupByComboBox->RefreshOptions(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::GroupBy_OnSelectionChanged(TSharedPtr NewGroupingMode, ESelectInfo::Type SelectInfo) +{ + if (SelectInfo != ESelectInfo::Direct) + { + GroupingMode = *NewGroupingMode; + + // Create groups, sort timers within the group and apply filtering. + CreateGroups(); + SortStats(); + ApplyFiltering(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef SStatsView::GroupBy_OnGenerateWidget(TSharedPtr InGroupingMode) const +{ + return SNew(STextBlock) + .Text(StatsNodeGroupingHelper::ToName(*InGroupingMode)) + .ToolTipText(StatsNodeGroupingHelper::ToDescription(*InGroupingMode)); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText SStatsView::GroupBy_GetSelectedText() const +{ + return StatsNodeGroupingHelper::ToName(GroupingMode); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText SStatsView::GroupBy_GetSelectedTooltipText() const +{ + return StatsNodeGroupingHelper::ToDescription(GroupingMode); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// SortBy +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::SortStats() +{ + const int32 NumGroups = GroupNodes.Num(); + + #define CHECK_AND_SORT_COLUMN(ColumnId, SortTypeName) \ + if (ColumnBeingSorted == ColumnId) \ + { \ + if (ColumnSortMode == EColumnSortMode::Type::Descending) \ + { \ + for (int32 ID = 0; ID < NumGroups; ++ID) \ + { \ + GroupNodes[ID]->SortChildren(StatsNodeSortingHelper::SortTypeName##Descending()); \ + } \ + } \ + else /*if (ColumnSortMode == EColumnSortMode::Type::Ascending)*/ \ + { \ + for (int32 ID = 0; ID < NumGroups; ++ID) \ + { \ + GroupNodes[ID]->SortChildren(StatsNodeSortingHelper::SortTypeName##Ascending()); \ + } \ + } \ + } + + CHECK_AND_SORT_COLUMN(FStatsViewColumns::NameColumnID, ByName) + else CHECK_AND_SORT_COLUMN(FStatsViewColumns::MetaGroupNameColumnID, ByMetaGroupName) + else CHECK_AND_SORT_COLUMN(FStatsViewColumns::TypeColumnID, ByType) + else CHECK_AND_SORT_COLUMN(FStatsViewColumns::CountColumnID, ByCount) + else CHECK_AND_SORT_COLUMN(FStatsViewColumns::SumColumnID, BySum) + else CHECK_AND_SORT_COLUMN(FStatsViewColumns::MaxColumnID, ByMax) + else CHECK_AND_SORT_COLUMN(FStatsViewColumns::UpperQuartileColumnID, ByUpperQuartile) + else CHECK_AND_SORT_COLUMN(FStatsViewColumns::AverageColumnID, ByAverage) + else CHECK_AND_SORT_COLUMN(FStatsViewColumns::MedianColumnID, ByMedian) + else CHECK_AND_SORT_COLUMN(FStatsViewColumns::LowerQuartileColumnID, ByLowerQuartile) + else CHECK_AND_SORT_COLUMN(FStatsViewColumns::MinColumnID, ByMin) + + #undef CHECK_AND_SORT_COLUMN +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +EColumnSortMode::Type SStatsView::GetSortModeForColumn(const FName ColumnId) const +{ + if (ColumnBeingSorted != ColumnId) + { + return EColumnSortMode::None; + } + + return ColumnSortMode; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::SetSortModeForColumn(const FName& ColumnId, const EColumnSortMode::Type SortMode) +{ + ColumnBeingSorted = ColumnId; + ColumnSortMode = SortMode; + + // Sort timers and apply filtering. + SortStats(); + ApplyFiltering(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::OnSortModeChanged(const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type SortMode) +{ + SetSortModeForColumn(ColumnId, SortMode); + TreeView_Refresh(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// SortMode action (HeaderMenu) +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStatsView::HeaderMenu_SortMode_IsChecked(const FName ColumnId, const EColumnSortMode::Type InSortMode) +{ + return ColumnBeingSorted == ColumnId && ColumnSortMode == InSortMode; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStatsView::HeaderMenu_SortMode_CanExecute(const FName ColumnId, const EColumnSortMode::Type InSortMode) const +{ + const FStatsViewColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnId); + const bool bIsValid = Column.bCanBeSorted(); + + bool bCanExecute = ColumnBeingSorted != ColumnId ? true : ColumnSortMode != InSortMode; + bCanExecute = bCanExecute && bIsValid; + + return bCanExecute; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::HeaderMenu_SortMode_Execute(const FName ColumnId, const EColumnSortMode::Type InSortMode) +{ + SetSortModeForColumn(ColumnId, InSortMode); + TreeView_Refresh(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// SortMode action (ContextMenu) +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStatsView::ContextMenu_SortMode_IsChecked(const EColumnSortMode::Type InSortMode) +{ + return ColumnSortMode == InSortMode; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStatsView::ContextMenu_SortMode_CanExecute(const EColumnSortMode::Type InSortMode) const +{ + return ColumnSortMode != InSortMode; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::ContextMenu_SortMode_Execute(const EColumnSortMode::Type InSortMode) +{ + SetSortModeForColumn(ColumnBeingSorted, InSortMode); + TreeView_Refresh(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// SortByColumn action (ContextMenu) +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStatsView::ContextMenu_SortByColumn_IsChecked(const FName ColumnId) +{ + return ColumnId == ColumnBeingSorted; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStatsView::ContextMenu_SortByColumn_CanExecute(const FName ColumnId) const +{ + return ColumnId != ColumnBeingSorted; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::ContextMenu_SortByColumn_Execute(const FName ColumnId) +{ + SetSortModeForColumn(ColumnId, EColumnSortMode::Descending); + TreeView_Refresh(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// HideColumn action (HeaderMenu) +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStatsView::HeaderMenu_HideColumn_CanExecute(const FName ColumnId) const +{ + const FStatsViewColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnId); + return Column.bCanBeHidden(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::HeaderMenu_HideColumn_Execute(const FName ColumnId) +{ + FStatsViewColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnId); + Column.bIsVisible = false; + TreeViewHeaderRow->RemoveColumn(ColumnId); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// ToggleColumn action (ContextMenu) +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStatsView::ContextMenu_ToggleColumn_IsChecked(const FName ColumnId) +{ + const FStatsViewColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnId); + return Column.bIsVisible; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStatsView::ContextMenu_ToggleColumn_CanExecute(const FName ColumnId) const +{ + const FStatsViewColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnId); + return Column.bCanBeHidden(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::ContextMenu_ToggleColumn_Execute(const FName ColumnId) +{ + FStatsViewColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnId); + if (Column.bIsVisible) + { + HeaderMenu_HideColumn_Execute(ColumnId); + } + else + { + TreeViewHeaderRow_ShowColumn(ColumnId); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// "Show All Columns" action (ContextMenu) +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStatsView::ContextMenu_ShowAllColumns_CanExecute() const +{ + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::ContextMenu_ShowAllColumns_Execute() +{ + ColumnSortMode = DefaultColumnSortMode; + ColumnBeingSorted = DefaultColumnBeingSorted; + + const int32 NumColumns = FStatsViewColumnFactory::Get().Collection.Num(); + for (int32 ColumnIndex = 0; ColumnIndex < NumColumns; ColumnIndex++) + { + const FStatsViewColumn& DefaultColumn = *FStatsViewColumnFactory::Get().Collection[ColumnIndex]; + const FStatsViewColumn& CurrentColumn = TreeViewHeaderColumns.FindChecked(DefaultColumn.Id); + + if (!CurrentColumn.bIsVisible) + { + TreeViewHeaderRow_ShowColumn(DefaultColumn.Id); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// "Show Min/Max/Median Columns" action (ContextMenu) +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStatsView::ContextMenu_ShowMinMaxMedColumns_CanExecute() const +{ + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::ContextMenu_ShowMinMaxMedColumns_Execute() +{ + TSet Preset = + { + FStatsViewColumns::NameColumnID, + //FStatsViewColumns::MetaGroupNameColumnID, + //FStatsViewColumns::TypeColumnID, + FStatsViewColumns::CountColumnID, + FStatsViewColumns::SumColumnID, + FStatsViewColumns::MaxColumnID, + FStatsViewColumns::UpperQuartileColumnID, + //FStatsViewColumns::AverageColumnID, + FStatsViewColumns::MedianColumnID, + FStatsViewColumns::LowerQuartileColumnID, + FStatsViewColumns::MinColumnID, + }; + + ColumnSortMode = EColumnSortMode::Descending; + ColumnBeingSorted = FStatsViewColumns::CountColumnID; + + const int32 NumColumns = FStatsViewColumnFactory::Get().Collection.Num(); + for (int32 ColumnIndex = 0; ColumnIndex < NumColumns; ColumnIndex++) + { + const FStatsViewColumn& DefaultColumn = *FStatsViewColumnFactory::Get().Collection[ColumnIndex]; + const FStatsViewColumn& CurrentColumn = TreeViewHeaderColumns.FindChecked(DefaultColumn.Id); + + bool bIsVisible = Preset.Contains(DefaultColumn.Id); + if (bIsVisible && !CurrentColumn.bIsVisible) + { + TreeViewHeaderRow_ShowColumn(DefaultColumn.Id); + } + else if (!bIsVisible && CurrentColumn.bIsVisible) + { + HeaderMenu_HideColumn_Execute(DefaultColumn.Id); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// ResetColumns action (ContextMenu) +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SStatsView::ContextMenu_ResetColumns_CanExecute() const +{ + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::ContextMenu_ResetColumns_Execute() +{ + ColumnSortMode = DefaultColumnSortMode; + ColumnBeingSorted = DefaultColumnBeingSorted; + + const int32 NumColumns = FStatsViewColumnFactory::Get().Collection.Num(); + for (int32 ColumnIndex = 0; ColumnIndex < NumColumns; ColumnIndex++) + { + const FStatsViewColumn& DefaultColumn = *FStatsViewColumnFactory::Get().Collection[ColumnIndex]; + const FStatsViewColumn& CurrentColumn = TreeViewHeaderColumns.FindChecked(DefaultColumn.Id); + + if (DefaultColumn.bIsVisible && !CurrentColumn.bIsVisible) + { + TreeViewHeaderRow_ShowColumn(DefaultColumn.Id); + } + else if (!DefaultColumn.bIsVisible && CurrentColumn.bIsVisible) + { + HeaderMenu_HideColumn_Execute(DefaultColumn.Id); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::RebuildTree(bool bResync) +{ + bool bListHasChanged = false; + + if (bResync) + { + StatsNodes.Empty(StatsNodes.Num()); + //StatsNodesMap.Empty(StatsNodesMap.Num()); + StatsNodesIdMap.Empty(StatsNodesIdMap.Num()); + bListHasChanged = true; + } + + if (Session.IsValid() && Trace::ReadCounterProvider(*Session.Get())) + { + Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); + + const Trace::ICounterProvider& CountersProvider = *Trace::ReadCounterProvider(*Session.Get()); + + if (!bResync) + { + bResync = (CountersProvider.GetCounterCount() != StatsNodes.Num()); + } + + if (bResync) + { + StatsNodes.Empty(StatsNodes.Num()); + //StatsNodesMap.Empty(StatsNodesMap.Num()); + StatsNodesIdMap.Empty(StatsNodesIdMap.Num()); + bListHasChanged = true; + + CountersProvider.EnumerateCounters([this](const Trace::ICounter& Counter) + { + FName Name(Counter.GetName()); + FName Group(Counter.GetDisplayHint() == Trace::CounterDisplayHint_Memory ? TEXT("Memory") : + Counter.GetDisplayHint() == Trace::CounterDisplayHint_FloatingPoint ? TEXT("float") : TEXT("int64")); + EStatsNodeType Type = Counter.GetDisplayHint() == Trace::CounterDisplayHint_FloatingPoint ? EStatsNodeType::Float : EStatsNodeType::Int64; + FStatsNodePtr StatsNodePtr = MakeShareable(new FStatsNode(Counter.GetId(), Name, Group, Type)); + StatsNodes.Add(StatsNodePtr); + //StatsNodesMap.Add(Name, StatsNodePtr); + StatsNodesIdMap.Add(Counter.GetId(), StatsNodePtr); + }); + } + } + + if (bListHasChanged) + { + UpdateTree(); + UpdateStats(StatsStartTime, StatsEndTime); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +template +class TTimeCalculationHelper +{ +public: + TTimeCalculationHelper(double InIntervalStartTime, double InIntervalEndTime) + : IntervalStartTime(InIntervalStartTime) + , IntervalEndTime(InIntervalEndTime) + { + } + + void Update(const Trace::ICounter& Counter) + { + EnumerateValues(Counter, UpdateMinMax); + } + + void PrecomputeHistograms(); + + void UpdateHistograms(const Trace::ICounter& Counter) + { + EnumerateValues(Counter, UpdateHistogram); + } + + void PostProcess(TMap& StatsNodesIdMap, bool bComputeMedian); + +private: + template + void EnumerateValues(const Trace::ICounter& Counter, CallbackType Callback); + + static void UpdateMinMax(TAggregatedStatsEx& Stats, Type Value); + static void UpdateHistogram(TAggregatedStatsEx& StatsEx, Type Value); + static void PostProcess(TAggregatedStatsEx& StatsEx, bool bComputeMedian); + + double IntervalStartTime; + double IntervalEndTime; + TMap> StatsMap; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// specialization for Type = double + +template<> +template +void TTimeCalculationHelper::EnumerateValues(const Trace::ICounter& Counter, CallbackType Callback) +{ + TAggregatedStatsEx* StatsExPtr = StatsMap.Find(Counter.GetId()); + if (!StatsExPtr) + { + StatsExPtr = &StatsMap.Add(Counter.GetId()); + StatsExPtr->BaseStats.Min = +MAX_dbl; + StatsExPtr->BaseStats.Max = -MAX_dbl; + } + TAggregatedStatsEx& StatsEx = *StatsExPtr; + + Counter.EnumerateFloatValues(IntervalStartTime, IntervalEndTime, [this, &StatsEx, Callback](double Time, double Value) + { + Callback(StatsEx, Value); + }); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// specialization for Type = int64 + +template<> +template +void TTimeCalculationHelper::EnumerateValues(const Trace::ICounter& Counter, CallbackType Callback) +{ + TAggregatedStatsEx* StatsExPtr = StatsMap.Find(Counter.GetId()); + if (!StatsExPtr) + { + StatsExPtr = &StatsMap.Add(Counter.GetId()); + StatsExPtr->BaseStats.Min = +MAX_int64; + StatsExPtr->BaseStats.Max = -MAX_int64; + } + TAggregatedStatsEx& StatsEx = *StatsExPtr; + + Counter.EnumerateValues(IntervalStartTime, IntervalEndTime, [this, &StatsEx, Callback](double Time, int64 Value) + { + Callback(StatsEx, Value); + }); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +template +void TTimeCalculationHelper::UpdateMinMax(TAggregatedStatsEx& StatsEx, Type Value) +{ + TAggregatedStats& Stats = StatsEx.BaseStats; + + Stats.Sum += Value; + + if (Value < Stats.Min) + { + Stats.Min = Value; + } + + if (Value > Stats.Max) + { + Stats.Max = Value; + } + + Stats.Count++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// specialization for Type = double + +template<> +void TTimeCalculationHelper::PrecomputeHistograms() +{ + for (auto& KV : StatsMap) + { + TAggregatedStatsEx& StatsEx = KV.Value; + const TAggregatedStats& Stats = StatsEx.BaseStats; + + // Each bucket (Histogram[i]) will be centered on a value. + // I.e. First bucket (bucket 0) is centered on Min value: [Min-DT/2, Min+DT/2) + // and last bucket (bucket N-1) is centered on Max value: [Max-DT/2, Max+DT/2). + + if (Stats.Max == Stats.Min) + { + StatsEx.DT = 1.0; // single large bucket + } + else + { + StatsEx.DT = (Stats.Max - Stats.Min) / (TAggregatedStatsEx::HistogramLen - 1); + if (StatsEx.DT == 0.0) + { + StatsEx.DT = 1.0; + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// specialization for Type = int64 + +template<> +void TTimeCalculationHelper::PrecomputeHistograms() +{ + for (auto& KV : StatsMap) + { + TAggregatedStatsEx& StatsEx = KV.Value; + const TAggregatedStats& Stats = StatsEx.BaseStats; + + // Each bucket (Histogram[i]) will be centered on a value. + // I.e. First bucket (bucket 0) is centered on Min value: [Min-DT/2, Min+DT/2) + // and last bucket (bucket N-1) is centered on Max value: [Max-DT/2, Max+DT/2). + + if (Stats.Max == Stats.Min) + { + StatsEx.DT = 1; // single bucket + } + else + { + // DT = Ceil[(Max - Min) / (N - 1)] + StatsEx.DT = (Stats.Max - Stats.Min + TAggregatedStatsEx::HistogramLen - 2) / (TAggregatedStatsEx::HistogramLen - 1); + if (StatsEx.DT == 0) + { + StatsEx.DT = 1; + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +template +void TTimeCalculationHelper::UpdateHistogram(TAggregatedStatsEx& StatsEx, Type Value) +{ + const TAggregatedStats& Stats = StatsEx.BaseStats; + + // Index = (Value - Min + DT/2) / DT + int32 Index = static_cast((Value - Stats.Min + StatsEx.DT/2) / StatsEx.DT); + ensure(Index >= 0); + if (Index < 0) + { + Index = 0; + } + ensure(Index < TAggregatedStatsEx::HistogramLen); + if (Index >= TAggregatedStatsEx::HistogramLen) + { + Index = TAggregatedStatsEx::HistogramLen - 1; + } + StatsEx.Histogram[Index]++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +template +void TTimeCalculationHelper::PostProcess(TAggregatedStatsEx& StatsEx, bool bComputeMedian) +{ + TAggregatedStats& Stats = StatsEx.BaseStats; + + // Compute average value. + if (Stats.Count > 0) + { + Stats.Average = Stats.Sum / static_cast(Stats.Count); + + if (bComputeMedian) + { + const int32 HalfCount = Stats.Count / 2; + + // Compute median value. + int32 Count = 0; + for (int32 HistogramIndex = 0; HistogramIndex < TAggregatedStatsEx::HistogramLen; HistogramIndex++) + { + Count += StatsEx.Histogram[HistogramIndex]; + if (Count > HalfCount) + { + Stats.Median = Stats.Min + HistogramIndex * StatsEx.DT; + + if (HistogramIndex > 0 && + Stats.Count % 2 == 0 && + Count - StatsEx.Histogram[HistogramIndex] == HalfCount) + { + const Type PrevMedian = Stats.Min + (HistogramIndex - 1) * StatsEx.DT; + Stats.Median = (Stats.Median + PrevMedian) / 2; + } + + break; + } + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// specialization for Type = double + +template<> +void TTimeCalculationHelper::PostProcess(TMap& StatsNodesIdMap, bool bComputeMedian) +{ + for (auto& KV : StatsMap) + { + PostProcess(KV.Value, bComputeMedian); + + // Update the stats node. + FStatsNodePtr* StatsNodePtrPtr = StatsNodesIdMap.Find(KV.Key); + if (StatsNodePtrPtr != nullptr) + { + (*StatsNodePtrPtr)->SetAggregatedStats(KV.Value.BaseStats); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// specialization for Type = int64 + +template<> +void TTimeCalculationHelper::PostProcess(TMap& StatsNodesIdMap, bool bComputeMedian) +{ + for (auto& KV : StatsMap) + { + PostProcess(KV.Value, bComputeMedian); + + // Update the stats node. + FStatsNodePtr* StatsNodePtrPtr = StatsNodesIdMap.Find(KV.Key); + if (StatsNodePtrPtr != nullptr) + { + (*StatsNodePtrPtr)->SetAggregatedIntegerStats(KV.Value.BaseStats); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::UpdateStats(double StartTime, double EndTime) +{ + for (const FStatsNodePtr& StatsNodePtr : StatsNodes) + { + StatsNodePtr->ResetAggregatedStats(); + } + + if (Session.IsValid() && StartTime < EndTime && Trace::ReadCounterProvider(*Session.Get())) + { + const bool bComputeMedian = true; + + TTimeCalculationHelper CalculationHelperDbl(StartTime, EndTime); + TTimeCalculationHelper CalculationHelperInt(StartTime, EndTime); + + { + Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); + + const Trace::ICounterProvider& CountersProvider = *Trace::ReadCounterProvider(*Session.Get()); + + // Compute instance count and total/min/max inclusive/exclusive times for each counter. + // Iterate through all counters. + CountersProvider.EnumerateCounters([&CalculationHelperDbl, &CalculationHelperInt](const Trace::ICounter& Counter) + { + if (Counter.GetDisplayHint() == Trace::CounterDisplayHint_FloatingPoint) + { + CalculationHelperDbl.Update(Counter); + } + else + { + CalculationHelperInt.Update(Counter); + } + }); + + // Now, as we know min/max inclusive/exclusive times for counter, we can compute histogram and median values. + if (bComputeMedian) + { + // Update bucket size (DT) for computing histogram. + CalculationHelperDbl.PrecomputeHistograms(); + CalculationHelperInt.PrecomputeHistograms(); + + // Compute histogram. + // Iterate again through all counters. + CountersProvider.EnumerateCounters([&CalculationHelperDbl, &CalculationHelperInt](const Trace::ICounter& Counter) + { + if (Counter.GetDisplayHint() == Trace::CounterDisplayHint_FloatingPoint) + { + CalculationHelperDbl.UpdateHistograms(Counter); + } + else + { + CalculationHelperInt.UpdateHistograms(Counter); + } + }); + } + } + + // Compute average and median inclusive/exclusive times. + CalculationHelperDbl.PostProcess(StatsNodesIdMap, bComputeMedian); + CalculationHelperInt.PostProcess(StatsNodesIdMap, bComputeMedian); + + StatsStartTime = StartTime; + StatsEndTime = EndTime; + + UpdateTree(); + TreeView->RebuildList(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsView::SelectStatsNode(uint64 Id) +{ + FStatsNodePtr* StatsNodePtrPtr = StatsNodesIdMap.Find(Id); + if (StatsNodePtrPtr != nullptr) + { + //UE_LOG(TimingProfiler, Log, TEXT("Select and RequestScrollIntoView %s"), *(*StatsNodePtrPtr)->GetName().ToString()); + TreeView->SetSelection(*StatsNodePtrPtr); + TreeView->RequestScrollIntoView(*StatsNodePtrPtr); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsView.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsView.h new file mode 100644 index 000000000000..31aa2da142af --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsView.h @@ -0,0 +1,341 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/FilterCollection.h" +#include "Misc/TextFilter.h" +#include "SlateFwd.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/SWidget.h" +#include "Widgets/Views/SHeaderRow.h" +#include "Widgets/Views/STableRow.h" +#include "Widgets/Views/STableViewBase.h" +#include "Widgets/Views/STreeView.h" + +// Insights +#include "Insights/ViewModels/StatsNode.h" +#include "Insights/ViewModels/StatsNodeHelper.h" +#include "Insights/ViewModels/StatsViewColumn.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FMenuBuilder; + +namespace Trace +{ + class IAnalysisSession; + struct FTimelineEvent; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +template +struct TAggregatedStatsEx +{ + static constexpr int32 HistogramLen = 100; // number of buckets per histogram + + TAggregatedStats BaseStats; + + // Histogram for computing median and lower/upper quartiles. + int32 Histogram[HistogramLen]; + Type DT; // bucket size + + TAggregatedStatsEx() + { + FMemory::Memzero(Histogram, sizeof(int32) * HistogramLen); + DT = Type(1); + } +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** The filter collection - used for updating the list of stats nodes. */ +typedef TFilterCollection FStatsNodeFilterCollection; + +/** The text based filter - used for updating the list of stats nodes. */ +typedef TTextFilter FStatsNodeTextFilter; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * A custom widget used to display the list of stats. + */ +class SStatsView : public SCompoundWidget +{ +public: + /** Default constructor. */ + SStatsView(); + + /** Virtual destructor. */ + virtual ~SStatsView(); + + SLATE_BEGIN_ARGS(SStatsView){} + SLATE_END_ARGS() + + /** + * Construct this widget + * @param InArgs - The declaration data for this widget + */ + void Construct(const FArguments& InArgs); + + /** + * Rebuilds the tree (if necessary). + * @param bResync - If true, it forces a resync with list of stats counters from Analysis, even if the list did not changed since last sync. + */ + void RebuildTree(bool bResync = true); + void UpdateStats(double StartTime, double EndTime); + + void SelectStatsNode(uint64 Id); + + //const TSet& GetStatsNodes() const { return StatsNodes; } + //const TMap GetStatsNodesIdMap() const { return StatsNodesIdMap; } + const FStatsNodePtr* GetStatsNode(uint64 Id) const { return StatsNodesIdMap.Find(Id); } + +protected: + + void UpdateTree(); + + /** Called when the analysis session has changed. */ + void InsightsManager_OnSessionChanged(); + + /** + * Populates OutSearchStrings with the strings that should be used in searching. + * + * @param GroupOrStatNodePtr - the group and stat node to get a text description from. + * @param OutSearchStrings - an array of strings to use in searching. + * + */ + void HandleItemToStringArray(const FStatsNodePtr& GroupOrStatNodePtr, TArray& OutSearchStrings) const; + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Tree View - Context Menu + + TSharedPtr TreeView_GetMenuContent(); + void TreeView_BuildSortByMenu(FMenuBuilder& MenuBuilder); + void TreeView_BuildViewColumnMenu(FMenuBuilder& MenuBuilder); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Tree View - Columns' Header + + void InitializeAndShowHeaderColumns(); + void TreeViewHeaderRow_CreateColumnArgs(const int32 ColumnIndex); + void TreeViewHeaderRow_ShowColumn(const FName ColumnId); + TSharedRef TreeViewHeaderRow_GenerateColumnMenu(const FStatsViewColumn& Column); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Tree View - Misc + + void TreeView_Refresh(); + + /** + * Called by STreeView to retrieves the children for the specified parent item. + * @param InParent - The parent node to retrieve the children from. + * @param OutChildren - List of children for the parent node. + */ + void TreeView_OnGetChildren(FStatsNodePtr InParent, TArray& OutChildren); + + /** Called by STreeView when selection has changed. */ + void TreeView_OnSelectionChanged(FStatsNodePtr SelectedItem, ESelectInfo::Type SelectInfo); + + /** Called by STreeView when a tree item is double clicked. */ + void TreeView_OnMouseButtonDoubleClick(FStatsNodePtr TreeNode); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Tree View - Table Row + + /** Called by STreeView to generate a table row for the specified item. */ + TSharedRef TreeView_OnGenerateRow(FStatsNodePtr TreeNode, const TSharedRef& OwnerTable); + + bool TableRow_IsColumnVisible(const FName ColumnId) const; + void TableRow_SetHoveredTableCell(const FName ColumnId, const FStatsNodePtr StatsNodePtr); + EHorizontalAlignment TableRow_GetColumnOutlineHAlignment(const FName ColumnId) const; + + FText TableRow_GetHighlightText() const; + FName TableRow_GetHighlightedStatsName() const; + + bool TableRow_ShouldBeEnabled(const uint32 StatsId) const; + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Filtering + + /** Populates the group and stat tree with items based on the current data. */ + void ApplyFiltering(); + + TSharedRef GetToggleButtonForStatsType(const EStatsNodeType StatType); + void FilterByStatsType_OnCheckStateChanged(ECheckBoxState NewRadioState, const EStatsNodeType InStatType); + ECheckBoxState FilterByStatsType_IsChecked(const EStatsNodeType InStatType) const; + + bool SearchBox_IsEnabled() const; + void SearchBox_OnTextChanged(const FText& InFilterText); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // GroupBy + + void CreateGroups(); + void CreateGroupByOptionsSources(); + + void GroupBy_OnSelectionChanged(TSharedPtr NewGroupingMode, ESelectInfo::Type SelectInfo); + + TSharedRef GroupBy_OnGenerateWidget(TSharedPtr InGroupingMode) const; + + FText GroupBy_GetSelectedText() const; + + FText GroupBy_GetSelectedTooltipText() const; + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Sorting + + void SortStats(); + + EColumnSortMode::Type GetSortModeForColumn(const FName ColumnId) const; + void SetSortModeForColumn(const FName& ColumnId, EColumnSortMode::Type SortMode); + void OnSortModeChanged(const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type SortMode); + + //void TreeView_BuildSortByMenu(FMenuBuilder& MenuBuilder); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Sorting actions + + // SortMode (HeaderMenu) + bool HeaderMenu_SortMode_IsChecked(const FName ColumnId, const EColumnSortMode::Type InSortMode); + bool HeaderMenu_SortMode_CanExecute(const FName ColumnId, const EColumnSortMode::Type InSortMode) const; + void HeaderMenu_SortMode_Execute(const FName ColumnId, const EColumnSortMode::Type InSortMode); + + // SortMode (ContextMenu) + bool ContextMenu_SortMode_IsChecked(const EColumnSortMode::Type InSortMode); + bool ContextMenu_SortMode_CanExecute(const EColumnSortMode::Type InSortMode) const; + void ContextMenu_SortMode_Execute(const EColumnSortMode::Type InSortMode); + + // SortByColumn (ContextMenu) + bool ContextMenu_SortByColumn_IsChecked(const FName ColumnId); + bool ContextMenu_SortByColumn_CanExecute(const FName ColumnId) const; + void ContextMenu_SortByColumn_Execute(const FName ColumnId); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Column visibility actions + + // HideColumn (HeaderMenu) + bool HeaderMenu_HideColumn_CanExecute(const FName ColumnId) const; + void HeaderMenu_HideColumn_Execute(const FName ColumnId); + + // ToggleColumn (ContextMenu) + bool ContextMenu_ToggleColumn_IsChecked(const FName ColumnId); + bool ContextMenu_ToggleColumn_CanExecute(const FName ColumnId) const; + void ContextMenu_ToggleColumn_Execute(const FName ColumnId); + + // ShowAllColumns (ContextMenu) + bool ContextMenu_ShowAllColumns_CanExecute() const; + void ContextMenu_ShowAllColumns_Execute(); + + // MinMaxMedColumns (ContextMenu) + bool ContextMenu_ShowMinMaxMedColumns_CanExecute() const; + void ContextMenu_ShowMinMaxMedColumns_Execute(); + + // ResetColumns (ContextMenu) + bool ContextMenu_ResetColumns_CanExecute() const; + void ContextMenu_ResetColumns_Execute(); + +protected: + /** A weak pointer to the profiler session used to populate this widget. */ + TSharedPtr/*Weak*/ Session; + + ////////////////////////////////////////////////// + // Tree View, Columns + + /** The tree widget which holds the list of groups and stats corresponding with each group. */ + TSharedPtr> TreeView; + + /** Column metadata used to initialize column arguments, stored as PropertyName -> FEventGraphColumn. */ + TMap TreeViewHeaderColumns; + + /** Column arguments used to initialize a new header column in the tree view, stored as column name to column arguments mapping. */ + TMap TreeViewHeaderColumnArgs; + + /** Holds the tree view header row widget which display all columns in the tree view. */ + TSharedPtr TreeViewHeaderRow; + + /** External scrollbar used to synchronize tree view position. */ + TSharedPtr ExternalScrollbar; + + ////////////////////////////////////////////////// + // Hovered Column, Hovered Stats Node + + /** Name of the column currently being hovered by the mouse. */ + FName HoveredColumnId; + + /** A shared pointer to the stats node currently being hovered by the mouse. */ + FStatsNodePtr HoveredStatsNodePtr; + + /** Name of the stats that should be drawn as highlighted. */ + FName HighlightedStatsName; + + ////////////////////////////////////////////////// + // Stats Nodes + + /** An array of group and stats nodes generated from the metadata. */ + TArray GroupNodes; + + /** A filtered array of group and stats nodes to be displayed in the tree widget. */ + TArray FilteredGroupNodes; + + /** All stats nodes. */ + TSet StatsNodes; + + /** All stats nodes, stored as StatsName -> FStatsNodePtr. */ + //TMap StatsNodesMap; + + /** All stats nodes, stored as StatsId -> FStatsNodePtr. */ + TMap StatsNodesIdMap; + + /** Currently expanded group nodes. */ + TSet ExpandedNodes; + + /** If true, the expanded nodes have been saved before applying a text filter. */ + bool bExpansionSaved; + + ////////////////////////////////////////////////// + // Search box and filters + + /** The search box widget used to filter items displayed in the stats and groups tree. */ + TSharedPtr SearchBox; + + /** The text based filter. */ + TSharedPtr TextFilter; + + /** The filter collection. */ + TSharedPtr Filters; + + /** Holds the visibility of each stats type. */ + bool bStatsNodeIsVisible[static_cast(EStatsNodeType::InvalidOrMax)]; + + ////////////////////////////////////////////////// + // Grouping + + TArray> GroupByOptionsSource; + + TSharedPtr>> GroupByComboBox; + + /** How we group the stats? */ + EStatsGroupingMode GroupingMode; + + ////////////////////////////////////////////////// + // Sorting + + /** How we sort the stats? */ + EColumnSortMode::Type ColumnSortMode; + + /** Name of the column currently being sorted, NAME_None if sorting is disabled. */ + FName ColumnBeingSorted; + + static const EColumnSortMode::Type DefaultColumnSortMode; + static const FName DefaultColumnBeingSorted; + + ////////////////////////////////////////////////// + + double StatsStartTime; + double StatsEndTime; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsViewTooltip.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsViewTooltip.cpp new file mode 100644 index 000000000000..7bfcebab93b7 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsViewTooltip.cpp @@ -0,0 +1,526 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SStatsViewTooltip.h" + +#include "EditorStyleSet.h" +#include "SlateOptMacros.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SGridPanel.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/SToolTip.h" +#include "Widgets/Text/STextBlock.h" + +// Insights +#include "Insights/Common/TimeUtils.h" +#include "Insights/ViewModels/StatsNode.h" +#include "Insights/ViewModels/StatsViewColumn.h" +#include "Insights/ViewModels/StatsNodeHelper.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define LOCTEXT_NAMESPACE "SStatsViewTooltip" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + +TSharedPtr SStatsViewTooltip::GetColumnTooltip(const FStatsViewColumn& Column) +{ + TSharedPtr ColumnTooltip = + SNew(SToolTip) + [ + SNew(SVerticalBox) + + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(Column.TitleName) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + ] + + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(Column.Description) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + ] + ]; + + return ColumnTooltip; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedPtr SStatsViewTooltip::GetTableCellTooltip(const TSharedPtr StatsNodePtr) +{ + //const FSlateFontInfo TitleFont = FCoreStyle::GetDefaultFontStyle("Bold", 10); + //const FSlateFontInfo DescriptionFont = FCoreStyle::GetDefaultFontStyle("Regular", 8); + //const FSlateFontInfo DescriptionFontB = FCoreStyle::GetDefaultFontStyle("Bold", 8); + + const FLinearColor DefaultColor(1.0f,1.0f,1.0f,1.0f); + const FLinearColor ThreadColor(5.0f, 0.0f, 0.0f, 1.0f); + const float Alpha = 0.0f;//StatsNodePtr->_FramePct * 0.01f; + const FLinearColor ColorAndOpacity = FMath::Lerp(DefaultColor, ThreadColor,Alpha); + + const FText InstanceCountText = FText::AsNumber(StatsNodePtr->GetAggregatedStats().Count); + + FText SumText = StatsNodePtr->GetTextForAggregatedStatsSum(); + FText MinText = StatsNodePtr->GetTextForAggregatedStatsMin(); + FText MaxText = StatsNodePtr->GetTextForAggregatedStatsMax(); + FText AvgText = StatsNodePtr->GetTextForAggregatedStatsAverage(); + FText MedText = StatsNodePtr->GetTextForAggregatedStatsMedian(); + FText LowText = StatsNodePtr->GetTextForAggregatedStatsLowerQuartile(); + FText UppText = StatsNodePtr->GetTextForAggregatedStatsUpperQuartile(); + + //TSharedPtr HBoxCaption; + TSharedPtr GridPanel; + TSharedPtr HBox; + + TSharedPtr TableCellTooltip = + SNew(SToolTip) + [ + SAssignNew(HBox, SHorizontalBox) + + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SVerticalBox) + + //+SVerticalBox::Slot() + //.AutoHeight() + //.Padding(2.0f) + //[ + // SAssignNew(HBoxCaption, SHorizontalBox) + //] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SSeparator) + .Orientation(Orient_Horizontal) + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SGridPanel) + + // Id: [Id] + +SGridPanel::Slot(0, 0) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("TT_Id", "Id:")) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + ] + +SGridPanel::Slot(1, 0) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(FText::AsNumber(StatsNodePtr->GetId())) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + ] + + // Name: [Name] + +SGridPanel::Slot(0, 1) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("TT_Name", "Name:")) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + ] + +SGridPanel::Slot(1, 1) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(FText::FromName(StatsNodePtr->GetName())) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + ] + + //// Group: [MetaGroupName] + //+SGridPanel::Slot(0, 2) + //.Padding(2.0f) + //[ + // SNew(STextBlock) + // .Text(LOCTEXT("TT_Group", "Group:")) + // .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + //] + //+SGridPanel::Slot(1, 2) + //.Padding(2.0f) + //[ + // SNew(STextBlock) + // .Text(FText::FromName(StatsNodePtr->GetMetaGroupName())) + // .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + //] + + // Type: [Type] + + SGridPanel::Slot(0, 3) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("TT_Type", "Type:")) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + ] + + SGridPanel::Slot(1, 3) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(StatsNodeTypeHelper::ToName(StatsNodePtr->GetType())) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + ] + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SSeparator) + .Orientation(Orient_Horizontal) + ] + + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SGridPanel) + + + SGridPanel::Slot(0, 0) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("TT_NumInstances", "Num Instances:")) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + ] + + SGridPanel::Slot(1, 0) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(InstanceCountText) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + ] + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SSeparator) + .Orientation(Orient_Horizontal) + ] + + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SAssignNew(GridPanel, SGridPanel) + + // Aggregated stats are added here. + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SSeparator) + .Orientation(Orient_Horizontal) + ] + ] + ]; + + int32 Row = 1; + AddAggregatedStatsRow(GridPanel, Row, LOCTEXT("TT_Sum", "Sum:"), SumText); + AddAggregatedStatsRow(GridPanel, Row, LOCTEXT("TT_Max", "Max:"), MaxText); + AddAggregatedStatsRow(GridPanel, Row, LOCTEXT("TT_UpperQ", "Upper Quartile:"), UppText); + AddAggregatedStatsRow(GridPanel, Row, LOCTEXT("TT_Average", "Average:"), AvgText); + AddAggregatedStatsRow(GridPanel, Row, LOCTEXT("TT_Median", "Median:"), MedText); + AddAggregatedStatsRow(GridPanel, Row, LOCTEXT("TT_LowerQ", "Lower Quartile:"), LowText); + AddAggregatedStatsRow(GridPanel, Row, LOCTEXT("TT_Min", "Min:"), MinText); + + /* + //TODO: We need Stats hierarchy (not the grouping hierarchy)! + const bool bHasParent = StatsNodePtr->GetStats()->GetParent().IsValid(); + const bool bHasChildren = StatsNodePtr->GetStats()->GetChildren().Num() > 0; + + if (bHasParent) + { + const FText ParentName = FText::FromName(StatsNodePtr->GetGroupPtr()->GetName()); + HBoxCaption->AddSlot() + .AutoWidth() + [ + SNew(STextBlock) + .Text(ParentName) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Caption")) + ]; + + HBoxCaption->AddSlot() + .AutoWidth() + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("BreadcrumbTrail.Delimiter")) + ]; + } + + const FText StatsName = FText::FromName(StatsNodePtr->GetName()); + HBoxCaption->AddSlot() + .AutoWidth() + [ + SNew(STextBlock) + .Text(StatsName) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.CaptionBold")) + ]; + + if (bHasChildren) + { + typedef TKeyValuePair FEventNameAndPct; + TArray MinimalChildren; + + const TArray& Children = StatsNodePtr->GetChildren(); + for(int32 ChildIndex = 0; ChildIndex < Children.Num(); ChildIndex++) + { + const FStatsNodePtr Child = Children[ChildIndex]; + float Percent = static_cast(Child->GetStats().TotalInclusiveTime / StatsNodePtr->GetStats().TotalInclusiveTime * 100.0); + MinimalChildren.Add(FEventNameAndPct(Child->GetStats().TotalInclusiveTime, Child->GetName())); + } + + struct FCompareByFloatDescending + { + FORCEINLINE bool operator()(const FEventNameAndPct& A, const FEventNameAndPct& B) const + { + return A.Key > B.Key; + } + }; + MinimalChildren.Sort(FCompareByFloatDescending()); + + FString ChildrenNames; + const int32 NumChildrenToDisplay = FMath::Min(MinimalChildren.Num(), 3); + for(int32 SortedChildIndex = 0; SortedChildIndex < NumChildrenToDisplay; SortedChildIndex++) + { + const FEventNameAndPct& MinimalChild = MinimalChildren[SortedChildIndex]; + ChildrenNames += FString::Printf(TEXT("%s (%.1f %%)"), *MinimalChild.Value.ToString(), MinimalChild.Key); + + const bool bAddDelimiter = SortedChildIndex < NumChildrenToDisplay - 1; + if (bAddDelimiter) + { + ChildrenNames += TEXT(", "); + } + } + + HBoxCaption->AddSlot() + .AutoWidth() + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("BreadcrumbTrail.Delimiter")) + ]; + + HBoxCaption->AddSlot() + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString(ChildrenNames)) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Caption")) + ]; + } + */ + + return TableCellTooltip; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef SStatsViewTooltip::GetTooltip() +{ + if (Session.IsValid()) + { + const TSharedRef ToolTipGrid = SNew(SGridPanel); + int32 CurrentRowPos = 0; + + AddHeader(ToolTipGrid, CurrentRowPos); + AddDescription(ToolTipGrid, CurrentRowPos); + + AddNoDataInformation(ToolTipGrid, CurrentRowPos); + + return SNew(SToolTip) + [ + ToolTipGrid + ]; + } + else + { + return SNew(SToolTip) + .Text(LOCTEXT("NotImplemented", "Tooltip for multiple profiler instances has not been implemented yet")); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsViewTooltip::AddNoDataInformation(const TSharedRef& Grid, int32& RowPos) +{ + Grid->AddSlot(0, RowPos) + .Padding(2.0f) + .ColumnSpan(3) + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + .Text(LOCTEXT("NoDataAvailable", "N/A")) + ]; + RowPos++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsViewTooltip::AddHeader(const TSharedRef& Grid, int32& RowPos) +{ + const FString InstanceName = Session->GetName(); + + Grid->AddSlot(0, RowPos++) + .Padding(2.0f) + .ColumnSpan(3) + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + .Text(LOCTEXT("StatInstance", "Stat information for profiler instance")) + ]; + + Grid->AddSlot(0, RowPos++) + .Padding(2.0f) + .ColumnSpan(3) + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + .Text(FText::FromString(InstanceName)) + ]; + + AddSeparator(Grid, RowPos); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsViewTooltip::AddDescription(const TSharedRef& Grid, int32& RowPos) +{ + /* + const FProfilerStat& ProfilerStat = Session->GetMetaData()->GetStatsById(StatsId); + const EStatsNodeType SampleType = Session->GetMetaData()->GetSampleTypeForStatsId(StatsId); + const FSlateBrush* const StatIcon = SStatsViewHelper::GetIconForStatType(SampleType); + + Grid->AddSlot(0, RowPos) + .Padding(2.0f) + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + .Text(LOCTEXT("GroupDesc","Group:")) + ]; + + Grid->AddSlot(1, RowPos) + .Padding(2.0f) + .ColumnSpan(2) + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + .Text(FText::FromName(ProfilerStat.OwningGroup().Name())) + ]; + RowPos++; + + Grid->AddSlot(0, RowPos) + .Padding(2.0f) + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + .Text(LOCTEXT("NameDesc","Name:")) + ]; + + Grid->AddSlot(1, RowPos) + .Padding(2.0f) + .ColumnSpan(2) + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + .Text(FText::FromName(ProfilerStat.Name())) + ]; + RowPos++; + + Grid->AddSlot(0, RowPos) + .Padding(2.0f) + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + .Text(LOCTEXT("TypeDesc","Type:")) + ]; + + Grid->AddSlot(1, RowPos) + .Padding(2.0f) + .ColumnSpan(2) + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SImage) + .Image(StatIcon) + ] + + +SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString(EStatsNodeType::ToDescription(SampleType))) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + ] + ]; + RowPos++; + + AddSeparator(Grid, RowPos); + */ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsViewTooltip::AddSeparator(const TSharedRef& Grid, int32& RowPos) +{ + Grid->AddSlot(0, RowPos++) + .Padding(2.0f) + .ColumnSpan(3) + [ + SNew(SSeparator) + .Orientation(Orient_Horizontal) + ]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void SStatsViewTooltip::AddAggregatedStatsRow(TSharedPtr Grid, int32& Row, const FText& Name, const FText& Value) +{ + Grid->AddSlot(0, Row) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(Name) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + ]; + + Grid->AddSlot(1, Row) + .Padding(2.0f) + .HAlign(HAlign_Right) + [ + SNew(STextBlock) + .Text(Value) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + ]; + + Row++; +} + +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsViewTooltip.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsViewTooltip.h new file mode 100644 index 000000000000..b7880c2bdd56 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/SStatsViewTooltip.h @@ -0,0 +1,53 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "EditorStyleSet.h" +#include "SlateOptMacros.h" +#include "TraceServices/AnalysisService.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SGridPanel.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/SToolTip.h" +#include "Widgets/Text/STextBlock.h" + +// Insights +#include "Insights/InsightsManager.h" + +// Insights +class FStatsNode; +class FStatsViewColumn; + +#define LOCTEXT_NAMESPACE "SStatsView" + +/** Stats View Tooltip */ +class SStatsViewTooltip +{ + const uint32 StatsId; + TSharedPtr Session; + +public: + SStatsViewTooltip(const uint32 InStatsId) + : StatsId(InStatsId) + { + Session = FInsightsManager::Get()->GetSession(); + } + + TSharedRef GetTooltip(); + +protected: + void AddNoDataInformation(const TSharedRef& Grid, int32& RowPos); + void AddHeader(const TSharedRef& Grid, int32& RowPos); + void AddDescription(const TSharedRef& Grid, int32& RowPos); + void AddSeparator(const TSharedRef& Grid, int32& RowPos); + +public: + static TSharedPtr GetColumnTooltip(const FStatsViewColumn& Column); + static TSharedPtr GetTableCellTooltip(const TSharedPtr StatsNodePtr); + +protected: + static void AddAggregatedStatsRow(TSharedPtr Grid, int32& Row, const FText& Name, const FText& Value); +}; + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimerTableCell.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimerTableCell.cpp new file mode 100644 index 000000000000..9c01efe9a16c --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimerTableCell.cpp @@ -0,0 +1,146 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "STimerTableCell.h" + +#include "EditorStyleSet.h" +#include "SlateOptMacros.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Views/SExpanderArrow.h" + +// Insights +#include "Insights/ViewModels/TimersViewColumn.h" +#include "Insights/ViewModels/TimersViewColumnFactory.h" +#include "Insights/Widgets/STimersViewTooltip.h" + +#define LOCTEXT_NAMESPACE "STimersView" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + +void STimerTableCell::Construct(const FArguments& InArgs, const TSharedRef& TableRow) +{ + SetHoveredTableCellDelegate = InArgs._OnSetHoveredTableCell; + TimerNodePtr = InArgs._TimerNodePtr; + ColumnId = InArgs._ColumnId; + //HighlightText = InArgs._HighlightText; + + ChildSlot + [ + GenerateWidgetForColumn(InArgs, TableRow) + ]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef STimerTableCell::GenerateWidgetForColumn(const FArguments& InArgs, const TSharedRef& TableRow) +{ + if (InArgs._IsTimerNameColumn) + { + return GenerateWidgetForNameColumn(InArgs, TableRow); + } + else + { + return GenerateWidgetForStatsColumn(InArgs, TableRow); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef STimerTableCell::GenerateWidgetForNameColumn(const FArguments& InArgs, const TSharedRef& TableRow) +{ + const FTimersViewColumn& Column = *FTimersViewColumnFactory::Get().ColumnIdToPtrMapping.FindChecked(ColumnId); + + return + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + .VAlign(VAlign_Center) + [ + SNew(SExpanderArrow, TableRow) + ] + + // Event info icon + tooltip. + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(SImage) + .Visibility(this, &STimerTableCell::GetHintIconVisibility) + .Image(FEditorStyle::GetBrush("Profiler.Tooltip.HintIcon10")) + .ToolTip(STimersViewTooltip::GetTableCellTooltip(TimerNodePtr)) + ] + + // Name + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .HAlign(Column.HorizontalAlignment) + .Padding(FMargin(2.0f, 0.0f)) + [ + SNew(STextBlock) + //.Text(TimerNodePtr->GetNameEx()) + .Text(this, &STimerTableCell::GetNameEx) + .HighlightText(InArgs._HighlightText) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + .ColorAndOpacity(this, &STimerTableCell::GetColorAndOpacity) + .ShadowColorAndOpacity(this, &STimerTableCell::GetShadowColorAndOpacity) + ] + + /* + // Culled children warning icon + tooltip. + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), TEXT("HoverHintOnly")) + .ContentPadding(0.0f) + .IsFocusable(false) + //.OnClicked(this, &STimerTableCell::ExpandCulledEvents_OnClicked) + [ + SNew(SImage) + .Visibility(this, &STimerTableCell::GetCulledEventsIconVisibility) + .Image(FEditorStyle::GetBrush("Profiler.EventGraph.HasCulledEventsSmall")) + .ToolTipText(LOCTEXT("HasCulledEvents_TT", "This event contains culled children, if you want to see all children, please disable culling or use function details, or press this icon")) + ] + ]*/ + ; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef STimerTableCell::GenerateWidgetForStatsColumn(const FArguments& InArgs, const TSharedRef& TableRow) +{ + const FTimersViewColumn& Column = *FTimersViewColumnFactory::Get().ColumnIdToPtrMapping.FindChecked(ColumnId); + const FText FormattedValue = Column.GetFormattedValue(*TimerNodePtr); + + return + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(Column.HorizontalAlignment) + .Padding(FMargin(2.0f, 0.0f)) + [ + SNew(STextBlock) + .Text(FormattedValue) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + .ColorAndOpacity(this, &STimerTableCell::GetStatsColorAndOpacity) + .ShadowColorAndOpacity(this, &STimerTableCell::GetShadowColorAndOpacity) + ]; +} + +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimerTableCell.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimerTableCell.h new file mode 100644 index 000000000000..c72fcc9cc2c7 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimerTableCell.h @@ -0,0 +1,140 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" + +// Insights +#include "Insights/ViewModels/TimerNode.h" + +DECLARE_DELEGATE_TwoParams(FSetHoveredTimerTableCell, const FName /*ColumnId*/, const FTimerNodePtr /*SamplePtr*/); +DECLARE_DELEGATE_RetVal_OneParam(bool, FIsColumnVisibleDelegate, const FName /*ColumnId*/); +DECLARE_DELEGATE_RetVal_OneParam(EHorizontalAlignment, FGetColumnOutlineHAlignmentDelegate, const FName /*ColumnId*/); + +class STimerTableCell : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(STimerTableCell) {} + SLATE_EVENT(FSetHoveredTimerTableCell, OnSetHoveredTableCell) + SLATE_ATTRIBUTE(FText, HighlightText) + SLATE_ARGUMENT(FTimerNodePtr, TimerNodePtr) + SLATE_ARGUMENT(FName, ColumnId) + SLATE_ARGUMENT(bool, IsTimerNameColumn) + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs, const TSharedRef& TableRow); + +protected: + TSharedRef GenerateWidgetForColumn(const FArguments& InArgs, const TSharedRef& TableRow); + TSharedRef GenerateWidgetForNameColumn(const FArguments& InArgs, const TSharedRef& TableRow); + TSharedRef GenerateWidgetForStatsColumn(const FArguments& InArgs, const TSharedRef& TableRow); + + /** + * The system will use this event to notify a widget that the cursor has entered it. This event is NOT bubbled. + * + * @param MyGeometry The Geometry of the widget receiving the event + * @param MouseEvent Information about the input event + */ + virtual void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override + { + SCompoundWidget::OnMouseEnter(MyGeometry, MouseEvent); + SetHoveredTableCellDelegate.ExecuteIfBound(ColumnId, TimerNodePtr); + } + + /** + * The system will use this event to notify a widget that the cursor has left it. This event is NOT bubbled. + * + * @param MouseEvent Information about the input event + */ + virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override + { + SCompoundWidget::OnMouseLeave(MouseEvent); + SetHoveredTableCellDelegate.ExecuteIfBound(NAME_None, nullptr); + } + + /** + * Called during drag and drop when the drag enters a widget. + * + * Enter/Leave events in slate are meant as lightweight notifications. + * So we do not want to capture mouse or set focus in response to these. + * However, OnDragEnter must also support external APIs (e.g. OLE Drag/Drop) + * Those require that we let them know whether we can handle the content + * being dragged OnDragEnter. + * + * The concession is to return a can_handled/cannot_handle + * boolean rather than a full FReply. + * + * @param MyGeometry The geometry of the widget receiving the event. + * @param DragDropEvent The drag and drop event. + * + * @return A reply that indicated whether the contents of the DragDropEvent can potentially be processed by this widget. + */ + virtual void OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override + { + SCompoundWidget::OnDragEnter(MyGeometry, DragDropEvent); + SetHoveredTableCellDelegate.ExecuteIfBound(ColumnId, TimerNodePtr); + } + + /** + * Called during drag and drop when the drag leaves a widget. + * + * @param DragDropEvent The drag and drop event. + */ + virtual void OnDragLeave(const FDragDropEvent& DragDropEvent) override + { + SCompoundWidget::OnDragLeave(DragDropEvent); + SetHoveredTableCellDelegate.ExecuteIfBound(NAME_None, nullptr); + } + + EVisibility GetHintIconVisibility() const + { + return IsHovered() ? EVisibility::Visible : EVisibility::Hidden; + } + + EVisibility GetCulledEventsIconVisibility() const + { + //return TimerNodePtr->HasCulledChildren() ? EVisibility::Visible : EVisibility::Collapsed; + return EVisibility::Collapsed; + } + + FText GetNameEx() const + { + return TimerNodePtr->GetNameEx(); + } + + FSlateColor GetColorAndOpacity() const + { + const FLinearColor TextColor = + TimerNodePtr->IsFiltered() ? FLinearColor(1.0f, 1.0f, 1.0f, 0.5f) : + FLinearColor::White; + return TextColor; + } + + FSlateColor GetStatsColorAndOpacity() const + { + const FLinearColor TextColor = + TimerNodePtr->IsFiltered() ? FLinearColor(1.0f, 1.0f, 1.0f, 0.5f) : + TimerNodePtr->GetAggregatedStats().InstanceCount == 0 ? FLinearColor(1.0f, 1.0f, 1.0f, 0.6f) : + FLinearColor::White; + return TextColor; + } + + FLinearColor GetShadowColorAndOpacity() const + { + const FLinearColor ShadowColor = + TimerNodePtr->IsFiltered() ? FLinearColor(0.f, 0.f, 0.f, 0.25f) : + FLinearColor(0.0f, 0.0f, 0.0f, 0.5f); + return ShadowColor; + } + +protected: + /** A shared pointer to the timer node. */ + FTimerNodePtr TimerNodePtr; + + /** The Id of the column where this timer belongs. */ + FName ColumnId; + + FSetHoveredTimerTableCell SetHoveredTableCellDelegate; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimerTableRow.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimerTableRow.cpp new file mode 100644 index 000000000000..466b9d3a0371 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimerTableRow.cpp @@ -0,0 +1,273 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "STimerTableRow.h" + +#include "Widgets/SOverlay.h" +#include "Widgets/Images/SImage.h" + +// Insights +#include "Insights/Common/TimeUtils.h" +#include "Insights/ViewModels/TimersViewColumnFactory.h" +#include "Insights/Widgets/STimersViewTooltip.h" +#include "Insights/Widgets/STimerTableCell.h" + +#define LOCTEXT_NAMESPACE "STimersView" + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + +void STimerTableRow::Construct(const FArguments& InArgs, const TSharedRef& InOwnerTableView) +{ + OnShouldBeEnabled = InArgs._OnShouldBeEnabled; + IsColumnVisibleDelegate = InArgs._OnIsColumnVisible; + SetHoveredTableCellDelegate = InArgs._OnSetHoveredTableCell; + GetColumnOutlineHAlignmentDelegate = InArgs._OnGetColumnOutlineHAlignmentDelegate; + + HighlightText = InArgs._HighlightText; + HighlightedTimerName = InArgs._HighlightedTimerName; + + TimerNodePtr = InArgs._TimerNodePtr; + + SetEnabled(TAttribute(this, &STimerTableRow::HandleShouldBeEnabled)); + + SMultiColumnTableRow::Construct(SMultiColumnTableRow::FArguments(), InOwnerTableView); + + /* + const FSlateBrush* const NodeIcon = TimerNodeTypeHelper::GetIconForTimerNodeType(InTimerNode->GetType()); + const TSharedRef Tooltip = InTimerNode->IsGroup() ? SNew(SToolTip) : STimersViewTooltip(InTimerNode->GetId()).GetTooltip(); + + ChildSlot + [ + SNew(SHorizontalBox) + + // Expander arrow. + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + .VAlign(VAlign_Center) + [ + SNew(SExpanderArrow, SharedThis(this)) + ] + + // Icon to visualize group or timer type. + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f, 0.0f, 8.0f, 0.0f) + [ + SNew(SImage) + .Image(NodeIcon) + .ToolTip(Tooltip) + ] + + // Description text. + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + .Padding(FMargin(2.0f, 0.0f)) + [ + SNew(STextBlock) + .Text(this, &STimerTableRow::GetText) + .HighlightText(InArgs._HighlightText) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + .ColorAndOpacity(this, &STimerTableRow::GetColorAndOpacity) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Center) + .VAlign(VAlign_Top) + .Padding(0.0f, 1.0f, 0.0f, 0.0f) + [ + SNew(SImage) + .Visibility(!InTimerNode->IsGroup() ? EVisibility::Visible : EVisibility::Collapsed) + .Image(FEditorStyle::GetBrush("Profiler.Tooltip.HintIcon10")) + .ToolTip(Tooltip) + ] + ]; + + STableRow::ConstructInternal(STableRow::FArguments().ShowSelection(true), InOwnerTableView); + */ +} + +TSharedRef STimerTableRow::GenerateWidgetForColumn(const FName& ColumnId) +{ + return + + SNew(SOverlay) + .Visibility(EVisibility::SelfHitTestInvisible) + + +SOverlay::Slot() + .Padding(0.0f) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("Profiler.LineGraphArea")) + .ColorAndOpacity(this, &STimerTableRow::GetBackgroundColorAndOpacity) + ] + + +SOverlay::Slot() + .Padding(0.0f) + [ + SNew(SImage) + .Image(this, &STimerTableRow::GetOutlineBrush, ColumnId) + .ColorAndOpacity(this, &STimerTableRow::GetOutlineColorAndOpacity) + ] + + +SOverlay::Slot() + [ + SNew(STimerTableCell, SharedThis(this)) + .Visibility(this, &STimerTableRow::IsColumnVisible, ColumnId) + .TimerNodePtr(TimerNodePtr) + .ColumnId(ColumnId) + .HighlightText(HighlightText) + .IsTimerNameColumn(ColumnId == FTimersViewColumnFactory::Get().Collection[0]->Id) // name column + .OnSetHoveredTableCell(this, &STimerTableRow::OnSetHoveredTableCell) + ]; +} + +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +FReply STimerTableRow::OnDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + //im:TODO + //if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) + //{ + // if (TimerNode->IsGroup()) + // { + // // Add all timer Ids for the group. + // TArray TimerIds; + // const TArray& FilteredChildren = TimerNode->GetFilteredChildren(); + // const int32 NumFilteredChildren = FilteredChildren.Num(); + // + // TimerIds.Reserve(NumFilteredChildren); + // for (int32 Nx = 0; Nx < NumFilteredChildren; ++Nx) + // { + // TimerIds.Add(FilteredChildren[Nx]->GetId()); + // } + // + // return FReply::Handled().BeginDragDrop(FStatIDDragDropOp::NewGroup(TimerIds, TimerNode->GetName().GetPlainNameString())); + // } + // else + // { + // return FReply::Handled().BeginDragDrop(FStatIDDragDropOp::NewSingle(TimerNode->GetId(), TimerNode->GetName().GetPlainNameString())); + // } + //} + + return SMultiColumnTableRow::OnDragDetected(MyGeometry, MouseEvent); +} + +FText STimerTableRow::GetText() const +{ + FText Text = FText::GetEmpty(); + + if (TimerNodePtr->IsGroup()) + { + Text = FText::Format(LOCTEXT("TimerNode_GroupNodeTextFmt", "{0} ({1})"), FText::FromName(TimerNodePtr->GetName()), FText::AsNumber(TimerNodePtr->GetChildren().Num())); + } + else + { + Text = FText::FromName(TimerNodePtr->GetName()); + } + + return Text; +} + +FSlateFontInfo STimerTableRow::GetFont() const +{ + const bool bIsStatTracked = false;//im:TODO: FTimingProfilerManager::Get()->IsStatTracked(TimerNodePtr->GetTimerId()); + const FSlateFontInfo FontInfo = bIsStatTracked ? FEditorStyle::GetFontStyle("BoldFont") : FEditorStyle::GetFontStyle("NormalFont"); + return FontInfo; +} + +FSlateColor STimerTableRow::GetColorAndOpacity() const +{ + //const bool bIsStatTracked = FTimingProfilerManager::Get()->IsStatTracked(TimerNodePtr->GetId()); + //const FSlateColor Color = bIsStatTracked ? FTimingProfilerManager::Get()->GetColorForTimerId(TimerNodePtr->GetId()) : FLinearColor::White; + //return Color; + //FTimingProfilerManager::GetSettings().GetColorForStat(StatName) + return FLinearColor::White; +} + +FSlateColor STimerTableRow::GetBackgroundColorAndOpacity() const +{ + return GetBackgroundColorAndOpacity(TimerNodePtr->GetAggregatedStats().TotalInclusiveTime); + //return FLinearColor(0.0f, 0.0f, 0.0f, 1.0f) +} + +FSlateColor STimerTableRow::GetBackgroundColorAndOpacity(double Time) const +{ + const FLinearColor Color = Time > TimeUtils::Second ? FLinearColor(0.3f, 0.0f, 0.0f, 1.0f) : + Time > TimeUtils::Milisecond ? FLinearColor(0.3f, 0.1f, 0.0f, 1.0f) : + Time > TimeUtils::Microsecond ? FLinearColor(0.0f, 0.1f, 0.0f, 1.0f) : + FLinearColor(0.0f, 0.0f, 0.0f, 1.0f); + return Color; +} + +FSlateColor STimerTableRow::GetOutlineColorAndOpacity() const +{ + const FLinearColor NoColor(0.0f, 0.0f, 0.0f, 0.0f); + const bool bShouldBeHighlighted = TimerNodePtr->GetName() == HighlightedTimerName.Get(); + const FLinearColor OutlineColorAndOpacity = bShouldBeHighlighted ? FLinearColor(FColorList::SlateBlue) : NoColor; + return OutlineColorAndOpacity; +} + +const FSlateBrush* STimerTableRow::GetOutlineBrush(const FName ColumnId) const +{ + EHorizontalAlignment Result = HAlign_Center; + if (IsColumnVisibleDelegate.IsBound()) + { + Result = GetColumnOutlineHAlignmentDelegate.Execute(ColumnId); + } + + const FSlateBrush* Brush = nullptr; + if (Result == HAlign_Left) + { + Brush = FEditorStyle::GetBrush("Profiler.EventGraph.Border.L"); + } + else if(Result == HAlign_Right) + { + Brush = FEditorStyle::GetBrush("Profiler.EventGraph.Border.R"); + } + else + { + Brush = FEditorStyle::GetBrush("Profiler.EventGraph.Border.TB"); + } + return Brush; +} + +bool STimerTableRow::HandleShouldBeEnabled() const +{ + bool bResult = false; + + if (TimerNodePtr->IsGroup()) + { + bResult = true; + } + else + { + if (OnShouldBeEnabled.IsBound()) + { + bResult = OnShouldBeEnabled.Execute(TimerNodePtr->GetId()); + } + } + + return bResult; +} + +EVisibility STimerTableRow::IsColumnVisible(const FName ColumnId) const +{ + EVisibility Result = EVisibility::Collapsed; + + if (IsColumnVisibleDelegate.IsBound()) + { + Result = IsColumnVisibleDelegate.Execute(ColumnId) ? EVisibility::Visible : EVisibility::Collapsed; + } + + return Result; +} + +void STimerTableRow::OnSetHoveredTableCell(const FName InColumnId, const FTimerNodePtr InSamplePtr) +{ + SetHoveredTableCellDelegate.ExecuteIfBound(InColumnId, InSamplePtr); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimerTableRow.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimerTableRow.h new file mode 100644 index 000000000000..d56ba1b14577 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimerTableRow.h @@ -0,0 +1,93 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/Views/SHeaderRow.h" +#include "Widgets/Views/STableViewBase.h" +#include "Widgets/Views/STableRow.h" +#include "Widgets/Views/STreeView.h" + +// Insights +#include "Insights/ViewModels/TimerNodeHelper.h" + +DECLARE_DELEGATE_RetVal_OneParam(bool, FShouldBeEnabledDelegate, const uint32 /*TimerId*/); +DECLARE_DELEGATE_RetVal_OneParam(bool, FIsColumnVisibleDelegate, const FName /*ColumnId*/); +DECLARE_DELEGATE_TwoParams(FSetHoveredTimerTableCell, const FName /*ColumnId*/, const FTimerNodePtr /*TimerNodePtr*/); +DECLARE_DELEGATE_RetVal_OneParam(EHorizontalAlignment, FGetColumnOutlineHAlignmentDelegate, const FName /*ColumnId*/); + +/** Widget that represents a table row in the timers' tree control. Generates widgets for each column on demand. */ +class STimerTableRow : public SMultiColumnTableRow +{ +public: + SLATE_BEGIN_ARGS(STimerTableRow) {} + SLATE_EVENT(FShouldBeEnabledDelegate, OnShouldBeEnabled) + SLATE_EVENT(FIsColumnVisibleDelegate, OnIsColumnVisible) + SLATE_EVENT(FSetHoveredTimerTableCell, OnSetHoveredTableCell) + SLATE_EVENT(FGetColumnOutlineHAlignmentDelegate, OnGetColumnOutlineHAlignmentDelegate) + SLATE_ATTRIBUTE(FText, HighlightText) + SLATE_ATTRIBUTE(FName, HighlightedTimerName) + SLATE_ARGUMENT(FTimerNodePtr, TimerNodePtr) + SLATE_END_ARGS() + +public: + void Construct(const FArguments& InArgs, const TSharedRef& InOwnerTableView); + + virtual TSharedRef GenerateWidgetForColumn(const FName& ColumnId) override; + + /** + * Called when Slate detects that a widget started to be dragged. + * Usage: + * A widget can ask Slate to detect a drag. + * OnMouseDown() reply with FReply::Handled().DetectDrag(SharedThis(this)). + * Slate will either send an OnDragDetected() event or do nothing. + * If the user releases a mouse button or leaves the widget before + * a drag is triggered (maybe user started at the very edge) then no event will be + * sent. + * + * @param InMyGeometry Widget geometry + * @param InMouseEvent MouseMove that triggered the drag + * + */ + virtual FReply OnDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + +protected: + /** + * @return a text which describes this table row, refers to both groups and timers + */ + FText GetText() const; + + /** + * @return a font style which is used to draw this table row, refers to both groups and timers + */ + FSlateFontInfo GetFont() const; + + /** + * @return a color and opacity value used to draw this table row, refers to both groups and timers + */ + FSlateColor GetColorAndOpacity() const; + + FSlateColor GetBackgroundColorAndOpacity() const; + FSlateColor GetBackgroundColorAndOpacity(double Time) const; + FSlateColor GetOutlineColorAndOpacity() const; + const FSlateBrush* GetOutlineBrush(const FName ColumnId) const; + bool HandleShouldBeEnabled() const; + EVisibility IsColumnVisible(const FName ColumnId) const; + void OnSetHoveredTableCell(const FName ColumnId, const FTimerNodePtr SamplePtr); + +protected: + /** Data context for this table row. */ + FTimerNodePtr TimerNodePtr; + + FShouldBeEnabledDelegate OnShouldBeEnabled; + FIsColumnVisibleDelegate IsColumnVisibleDelegate; + FSetHoveredTimerTableCell SetHoveredTableCellDelegate; + FGetColumnOutlineHAlignmentDelegate GetColumnOutlineHAlignmentDelegate; + + /** Text to be highlighted on timer name. */ + TAttribute HighlightText; + + /** Name of the timer that should be drawn as highlighted. */ + TAttribute HighlightedTimerName; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimersView.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimersView.cpp new file mode 100644 index 000000000000..972c840069c6 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimersView.cpp @@ -0,0 +1,1542 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "STimersView.h" + +#include "EditorStyleSet.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "SlateOptMacros.h" +#include "TraceServices/AnalysisService.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Layout/SScrollBox.h" +#include "Widgets/Input/SSearchBox.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SGridPanel.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/SToolTip.h" +#include "Widgets/Views/STableViewBase.h" + +// Insights +#include "Insights/TimingProfilerCommon.h" +#include "Insights/TimingProfilerManager.h" +#include "Insights/ViewModels/TimerNodeHelper.h" +#include "Insights/ViewModels/TimersViewColumnFactory.h" +#include "Insights/Widgets/STimersViewTooltip.h" +#include "Insights/Widgets/STimerTableRow.h" +#include "Insights/Widgets/STimingProfilerWindow.h" +#include "Insights/Widgets/STimingView.h" + +#define LOCTEXT_NAMESPACE "STimersView" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const EColumnSortMode::Type STimersView::DefaultColumnSortMode(EColumnSortMode::Descending); +const FName STimersView::DefaultColumnBeingSorted(FTimersViewColumns::TotalInclusiveTimeColumnID); + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +STimersView::STimersView() + : bExpansionSaved(false) + , GroupingMode(ETimerGroupingMode::ByType) + , ColumnSortMode(DefaultColumnSortMode) + , ColumnBeingSorted(DefaultColumnBeingSorted) +{ + FMemory::Memset(bTimerNodeIsVisible, 1); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +STimersView::~STimersView() +{ + // Remove ourselves from the Insights manager. + if (FInsightsManager::Get().IsValid()) + { + FInsightsManager::Get()->GetSessionChangedEvent().RemoveAll(this); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION +void STimersView::Construct(const FArguments& InArgs) +{ + SAssignNew(ExternalScrollbar, SScrollBar) + .AlwaysShowScrollbar(true); + + ChildSlot + [ + SNew(SVerticalBox) + + +SVerticalBox::Slot() + .VAlign(VAlign_Center) + .AutoHeight() + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(2.0f) + [ + SNew(SVerticalBox) + + // Search box + +SVerticalBox::Slot() + .VAlign(VAlign_Center) + .Padding(2.0f) + .AutoHeight() + [ + SAssignNew(SearchBox, SSearchBox) + .HintText(LOCTEXT("SearchBoxHint", "Search timers or groups")) + .OnTextChanged(this, &STimersView::SearchBox_OnTextChanged) + .IsEnabled(this, &STimersView::SearchBox_IsEnabled) + .ToolTipText(LOCTEXT("FilterSearchHint", "Type here to search timer or group")) + ] + + // Group by + +SVerticalBox::Slot() + .VAlign(VAlign_Center) + .Padding(2.0f) + .AutoHeight() + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("GroupByText", "Group by")) + ] + + +SHorizontalBox::Slot() + .FillWidth(2.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(GroupByComboBox, SComboBox>) + .ToolTipText(this, &STimersView::GroupBy_GetSelectedTooltipText) + .OptionsSource(&GroupByOptionsSource) + .OnSelectionChanged(this, &STimersView::GroupBy_OnSelectionChanged) + .OnGenerateWidget(this, &STimersView::GroupBy_OnGenerateWidget) + [ + SNew(STextBlock) + .Text(this, &STimersView::GroupBy_GetSelectedText) + ] + ] + ] + + // Check boxes for: GpuScope, ComputeScope, CpuScope + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .Padding(2.0f) + .AutoHeight() + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .Padding(FMargin(0.0f,0.0f,1.0f,0.0f)) + .FillWidth(1.0f) + [ + GetToggleButtonForTimerType(ETimerNodeType::GpuScope) + ] + + //+SHorizontalBox::Slot() + //.Padding(FMargin(1.0f,0.0f,1.0f,0.0f)) + //.FillWidth(1.0f) + //[ + // GetToggleButtonForTimerType(ETimerNodeType::ComputeScope) + //] + + +SHorizontalBox::Slot() + .Padding(FMargin(1.0f,0.0f,1.0f,0.0f)) + .FillWidth(1.0f) + [ + GetToggleButtonForTimerType(ETimerNodeType::CpuScope) + ] + ] + ] + ] + + // Tree view + +SVerticalBox::Slot() + .FillHeight(1.0f) + .Padding(0.0f, 6.0f, 0.0f, 0.0f) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(0.0f) + [ + SNew(SScrollBox) + .Orientation(Orient_Horizontal) + + + SScrollBox::Slot() + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(0.0f) + [ + SAssignNew(TreeView, STreeView) + .ExternalScrollbar(ExternalScrollbar) + .SelectionMode(ESelectionMode::Multi) + .TreeItemsSource(&FilteredGroupNodes) + .OnGetChildren(this, &STimersView::TreeView_OnGetChildren) + .OnGenerateRow(this, &STimersView::TreeView_OnGenerateRow) + .OnSelectionChanged(this, &STimersView::TreeView_OnSelectionChanged) + .OnMouseButtonDoubleClick(this, &STimersView::TreeView_OnMouseButtonDoubleClick) + .OnContextMenuOpening(FOnContextMenuOpening::CreateSP(this, &STimersView::TreeView_GetMenuContent)) + .ItemHeight(12.0f) + .HeaderRow + ( + SAssignNew(TreeViewHeaderRow, SHeaderRow) + .Visibility(EVisibility::Visible) + ) + ] + ] + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f) + [ + SNew(SBox) + .WidthOverride(FOptionalSize(13.0f)) + [ + ExternalScrollbar.ToSharedRef() + ] + ] + ] + ]; + + InitializeAndShowHeaderColumns(); + //BindCommands(); + + // Create the search filters: text based, type based etc. + TextFilter = MakeShareable(new FTimerNodeTextFilter(FTimerNodeTextFilter::FItemToStringArray::CreateSP(this, &STimersView::HandleItemToStringArray))); + Filters = MakeShareable(new FTimerNodeFilterCollection()); + Filters->Add(TextFilter); + + CreateGroupByOptionsSources(); + + // Register ourselves with the Insights manager. + FInsightsManager::Get()->GetSessionChangedEvent().AddSP(this, &STimersView::InsightsManager_OnSessionChanged); +} +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedPtr STimersView::TreeView_GetMenuContent() +{ + const TArray SelectedTimerNodes = TreeView->GetSelectedItems(); + const int32 NumSelectedTimerNodes = SelectedTimerNodes.Num(); + FTimerNodePtr SelectedTimerNode = NumSelectedTimerNodes ? SelectedTimerNodes[0] : nullptr; + + const FTimersViewColumn* const * ColumnPtrPtr = FTimersViewColumnFactory::Get().ColumnIdToPtrMapping.Find(HoveredColumnId); + const FTimersViewColumn* const ColumnPtr = (ColumnPtrPtr != nullptr) ? *ColumnPtrPtr : nullptr; + + FText SelectionStr; + FText PropertyName; + FText PropertyValue; + + if (NumSelectedTimerNodes == 0) + { + SelectionStr = LOCTEXT("NothingSelected", "Nothing selected"); + } + else if (NumSelectedTimerNodes == 1) + { + if (ColumnPtr != nullptr) + { + PropertyName = ColumnPtr->ShortName; + PropertyValue = ColumnPtr->GetFormattedValue(*SelectedTimerNode); + } + SelectionStr = FText::FromName(SelectedTimerNode->GetName()); + } + else + { + SelectionStr = LOCTEXT("MultipleSelection", "Multiple selection"); + } + + const bool bShouldCloseWindowAfterMenuSelection = true; + FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, NULL); + + // Selection menu + MenuBuilder.BeginSection("Selection", LOCTEXT("ContextMenu_Header_Selection", "Selection")); + { + struct FLocal + { + static bool ReturnFalse() + { + return false; + } + }; + + FUIAction DummyUIAction; + DummyUIAction.CanExecuteAction = FCanExecuteAction::CreateStatic(&FLocal::ReturnFalse); + MenuBuilder.AddMenuEntry + ( + SelectionStr, + LOCTEXT("ContextMenu_Selection", "Currently selected items"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "@missing.icon"), DummyUIAction, NAME_None, EUserInterfaceActionType::Button + ); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("Misc", LOCTEXT("ContextMenu_Header_Misc", "Miscellaneous")); + { + /*TODO + FUIAction Action_CopySelectedToClipboard + ( + FExecuteAction::CreateSP(this, &STimersView::ContextMenu_CopySelectedToClipboard_Execute), + FCanExecuteAction::CreateSP(this, &STimersView::ContextMenu_CopySelectedToClipboard_CanExecute) + ); + MenuBuilder.AddMenuEntry + ( + LOCTEXT("ContextMenu_Header_Misc_CopySelectedToClipboard", "Copy To Clipboard"), + LOCTEXT("ContextMenu_Header_Misc_CopySelectedToClipboard_Desc", "Copies selection to clipboard"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.CopyToClipboard"), Action_CopySelectedToClipboard, NAME_None, EUserInterfaceActionType::Button + ); + */ + + MenuBuilder.AddSubMenu + ( + LOCTEXT("ContextMenu_Header_Misc_Sort", "Sort By"), + LOCTEXT("ContextMenu_Header_Misc_Sort_Desc", "Sort by column"), + FNewMenuDelegate::CreateSP(this, &STimersView::TreeView_BuildSortByMenu), + false, + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortBy") + ); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("Columns", LOCTEXT("ContextMenu_Header_Columns", "Columns")); + { + MenuBuilder.AddSubMenu + ( + LOCTEXT("ContextMenu_Header_Columns_View", "View Column"), + LOCTEXT("ContextMenu_Header_Columns_View_Desc", "Hides or shows columns"), + FNewMenuDelegate::CreateSP(this, &STimersView::TreeView_BuildViewColumnMenu), + false, + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ViewColumn") + ); + + FUIAction Action_ShowAllColumns + ( + FExecuteAction::CreateSP(this, &STimersView::ContextMenu_ShowAllColumns_Execute), + FCanExecuteAction::CreateSP(this, &STimersView::ContextMenu_ShowAllColumns_CanExecute) + ); + MenuBuilder.AddMenuEntry + ( + LOCTEXT("ContextMenu_Header_Columns_ShowAllColumns", "Show All Columns"), + LOCTEXT("ContextMenu_Header_Columns_ShowAllColumns_Desc", "Resets tree view to show all columns"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ResetColumn"), Action_ShowAllColumns, NAME_None, EUserInterfaceActionType::Button + ); + + FUIAction Action_ShowMinMaxMedColumns + ( + FExecuteAction::CreateSP(this, &STimersView::ContextMenu_ShowMinMaxMedColumns_Execute), + FCanExecuteAction::CreateSP(this, &STimersView::ContextMenu_ShowMinMaxMedColumns_CanExecute) + ); + MenuBuilder.AddMenuEntry + ( + LOCTEXT("ContextMenu_Header_Columns_ShowMinMaxMedColumns", "Reset Columns to Min/Max/Median Preset"), + LOCTEXT("ContextMenu_Header_Columns_ShowMinMaxMedColumns_Desc", "Resets columns to Min/Max/Median preset"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ResetColumn"), Action_ShowMinMaxMedColumns, NAME_None, EUserInterfaceActionType::Button + ); + + FUIAction Action_ResetColumns + ( + FExecuteAction::CreateSP(this, &STimersView::ContextMenu_ResetColumns_Execute), + FCanExecuteAction::CreateSP(this, &STimersView::ContextMenu_ResetColumns_CanExecute) + ); + MenuBuilder.AddMenuEntry + ( + LOCTEXT("ContextMenu_Header_Columns_ResetColumns", "Reset Columns to Default"), + LOCTEXT("ContextMenu_Header_Columns_ResetColumns_Desc", "Resets columns to default"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ResetColumn"), Action_ResetColumns, NAME_None, EUserInterfaceActionType::Button + ); + } + MenuBuilder.EndSection(); + + return MenuBuilder.MakeWidget(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::TreeView_BuildSortByMenu(FMenuBuilder& MenuBuilder) +{ + // TODO: Refactor later @see TSharedPtr SCascadePreviewViewportToolBar::GenerateViewMenu() const + + MenuBuilder.BeginSection("ColumnName", LOCTEXT("ContextMenu_Header_Misc_ColumnName", "Column Name")); + + for (auto It = TreeViewHeaderColumns.CreateConstIterator(); It; ++It) + { + const FTimersViewColumn& Column = It.Value(); + + if (Column.bIsVisible && Column.bCanBeSorted()) + { + FUIAction Action_SortByColumn + ( + FExecuteAction::CreateSP(this, &STimersView::ContextMenu_SortByColumn_Execute, Column.Id), + FCanExecuteAction::CreateSP(this, &STimersView::ContextMenu_SortByColumn_CanExecute, Column.Id), + FIsActionChecked::CreateSP(this, &STimersView::ContextMenu_SortByColumn_IsChecked, Column.Id) + ); + MenuBuilder.AddMenuEntry + ( + Column.TitleName, + Column.Description, + FSlateIcon(), Action_SortByColumn, NAME_None, EUserInterfaceActionType::RadioButton + ); + } + } + + MenuBuilder.EndSection(); + + //----------------------------------------------------------------------------- + + MenuBuilder.BeginSection("SortMode", LOCTEXT("ContextMenu_Header_Misc_Sort_SortMode", "Sort Mode")); + { + FUIAction Action_SortAscending + ( + FExecuteAction::CreateSP(this, &STimersView::ContextMenu_SortMode_Execute, EColumnSortMode::Ascending), + FCanExecuteAction::CreateSP(this, &STimersView::ContextMenu_SortMode_CanExecute, EColumnSortMode::Ascending), + FIsActionChecked::CreateSP(this, &STimersView::ContextMenu_SortMode_IsChecked, EColumnSortMode::Ascending) + ); + MenuBuilder.AddMenuEntry + ( + LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending", "Sort Ascending"), + LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending_Desc", "Sorts ascending"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortAscending"), Action_SortAscending, NAME_None, EUserInterfaceActionType::RadioButton + ); + + FUIAction Action_SortDescending + ( + FExecuteAction::CreateSP(this, &STimersView::ContextMenu_SortMode_Execute, EColumnSortMode::Descending), + FCanExecuteAction::CreateSP(this, &STimersView::ContextMenu_SortMode_CanExecute, EColumnSortMode::Descending), + FIsActionChecked::CreateSP(this, &STimersView::ContextMenu_SortMode_IsChecked, EColumnSortMode::Descending) + ); + MenuBuilder.AddMenuEntry + ( + LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending", "Sort Descending"), + LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending_Desc", "Sorts descending"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortDescending"), Action_SortDescending, NAME_None, EUserInterfaceActionType::RadioButton + ); + } + MenuBuilder.EndSection(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::TreeView_BuildViewColumnMenu(FMenuBuilder& MenuBuilder) +{ + MenuBuilder.BeginSection("ViewColumn", LOCTEXT("ContextMenu_Header_Columns_View", "View Column")); + + for (auto It = TreeViewHeaderColumns.CreateConstIterator(); It; ++It) + { + const FTimersViewColumn& Column = It.Value(); + + FUIAction Action_ToggleColumn + ( + FExecuteAction::CreateSP(this, &STimersView::ContextMenu_ToggleColumn_Execute, Column.Id), + FCanExecuteAction::CreateSP(this, &STimersView::ContextMenu_ToggleColumn_CanExecute, Column.Id), + FIsActionChecked::CreateSP(this, &STimersView::ContextMenu_ToggleColumn_IsChecked, Column.Id) + ); + MenuBuilder.AddMenuEntry + ( + Column.TitleName, + Column.Description, + FSlateIcon(), Action_ToggleColumn, NAME_None, EUserInterfaceActionType::ToggleButton + ); + } + + MenuBuilder.EndSection(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::InitializeAndShowHeaderColumns() +{ + const int32 NumColumns = FTimersViewColumnFactory::Get().Collection.Num(); + for (int32 ColumnIndex = 0; ColumnIndex < NumColumns; ColumnIndex++) + { + TreeViewHeaderRow_CreateColumnArgs(ColumnIndex); + } + + for (auto It = TreeViewHeaderColumns.CreateConstIterator(); It; ++It) + { + const FTimersViewColumn& Column = It.Value(); + + if (Column.bIsVisible) + { + TreeViewHeaderRow_ShowColumn(Column.Id); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::TreeViewHeaderRow_CreateColumnArgs(const int32 ColumnIndex) +{ + const FTimersViewColumn& Column = *FTimersViewColumnFactory::Get().Collection[ColumnIndex]; + SHeaderRow::FColumn::FArguments ColumnArgs; + + ColumnArgs + .ColumnId(Column.Id) + .DefaultLabel(Column.ShortName) + .SortMode(EColumnSortMode::None) + .HAlignHeader(HAlign_Fill) + .VAlignHeader(VAlign_Fill) + .HeaderContentPadding(TOptional(2.0f)) + .HAlignCell(HAlign_Fill) + .VAlignCell(VAlign_Fill) + .SortMode(this, &STimersView::GetSortModeForColumn, Column.Id) + .OnSort(this, &STimersView::OnSortModeChanged) + .ManualWidth(Column.InitialColumnWidth) + .FixedWidth(Column.bIsFixedColumnWidth() ? Column.InitialColumnWidth : TOptional()) + .HeaderContent() + [ + SNew(SHorizontalBox) + .ToolTip(STimersViewTooltip::GetColumnTooltip(Column)) + + + SHorizontalBox::Slot() + .HAlign(Column.HorizontalAlignment) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(Column.ShortName) + ] + ] + .MenuContent() + [ + TreeViewHeaderRow_GenerateColumnMenu(Column) + ]; + + TreeViewHeaderColumnArgs.Add(Column.Id, ColumnArgs); + TreeViewHeaderColumns.Add(Column.Id, Column); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::TreeViewHeaderRow_ShowColumn(const FName ColumnId) +{ + FTimersViewColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnId); + Column.bIsVisible = true; + SHeaderRow::FColumn::FArguments& ColumnArgs = TreeViewHeaderColumnArgs.FindChecked(ColumnId); + + const int32 NumColumns = TreeViewHeaderRow->GetColumns().Num(); + int32 ColumnIndex = 0; + for (; ColumnIndex < NumColumns; ColumnIndex++) + { + const SHeaderRow::FColumn& CurrentColumn = TreeViewHeaderRow->GetColumns()[ColumnIndex]; + const FTimersViewColumn& CurrentTimersViewColumn = TreeViewHeaderColumns.FindChecked(CurrentColumn.ColumnId); + if (Column.Order < CurrentTimersViewColumn.Order) + break; + } + + TreeViewHeaderRow->InsertColumn(ColumnArgs, ColumnIndex); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef STimersView::TreeViewHeaderRow_GenerateColumnMenu(const FTimersViewColumn& Column) +{ + bool bIsMenuVisible = false; + + const bool bShouldCloseWindowAfterMenuSelection = true; + FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, NULL); + { + if (Column.bCanBeHidden()) + { + MenuBuilder.BeginSection("Column", LOCTEXT("TreeViewHeaderRow_Header_Column", "Column")); + + FUIAction Action_HideColumn + ( + FExecuteAction::CreateSP(this, &STimersView::HeaderMenu_HideColumn_Execute, Column.Id), + FCanExecuteAction::CreateSP(this, &STimersView::HeaderMenu_HideColumn_CanExecute, Column.Id) + ); + + MenuBuilder.AddMenuEntry + ( + LOCTEXT("TreeViewHeaderRow_HideColumn", "Hide"), + LOCTEXT("TreeViewHeaderRow_HideColumn_Desc", "Hides the selected column"), + FSlateIcon(), Action_HideColumn, NAME_None, EUserInterfaceActionType::Button + ); + bIsMenuVisible = true; + + MenuBuilder.EndSection(); + } + + if (Column.bCanBeSorted()) + { + MenuBuilder.BeginSection("SortMode", LOCTEXT("ContextMenu_Header_Misc_Sort_SortMode", "Sort Mode")); + + FUIAction Action_SortAscending + ( + FExecuteAction::CreateSP(this, &STimersView::HeaderMenu_SortMode_Execute, Column.Id, EColumnSortMode::Ascending), + FCanExecuteAction::CreateSP(this, &STimersView::HeaderMenu_SortMode_CanExecute, Column.Id, EColumnSortMode::Ascending), + FIsActionChecked::CreateSP(this, &STimersView::HeaderMenu_SortMode_IsChecked, Column.Id, EColumnSortMode::Ascending) + ); + MenuBuilder.AddMenuEntry + ( + LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending", "Sort Ascending"), + LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending_Desc", "Sorts ascending"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortAscending"), Action_SortAscending, NAME_None, EUserInterfaceActionType::RadioButton + ); + + FUIAction Action_SortDescending + ( + FExecuteAction::CreateSP(this, &STimersView::HeaderMenu_SortMode_Execute, Column.Id, EColumnSortMode::Descending), + FCanExecuteAction::CreateSP(this, &STimersView::HeaderMenu_SortMode_CanExecute, Column.Id, EColumnSortMode::Descending), + FIsActionChecked::CreateSP(this, &STimersView::HeaderMenu_SortMode_IsChecked, Column.Id, EColumnSortMode::Descending) + ); + MenuBuilder.AddMenuEntry + ( + LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending", "Sort Descending"), + LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending_Desc", "Sorts descending"), + FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortDescending"), Action_SortDescending, NAME_None, EUserInterfaceActionType::RadioButton + ); + bIsMenuVisible = true; + + MenuBuilder.EndSection(); + } + + if (Column.bCanBeFiltered()) + { + MenuBuilder.BeginSection("FilterMode", LOCTEXT("ContextMenu_Header_Misc_Filter_FilterMode", "Filter Mode")); + bIsMenuVisible = true; + MenuBuilder.EndSection(); + } + } + + /* + TODO: + - Show top ten + - Show top bottom + - Filter by list (avg, median, 10%, 90%, etc.) + - Text box for filtering for each column instead of one text box used for filtering + - Grouping button for flat view modes (show at most X groups, show all groups for names) + */ + + return bIsMenuVisible ? MenuBuilder.MakeWidget() : (TSharedRef)SNullWidget::NullWidget; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::InsightsManager_OnSessionChanged() +{ + TSharedPtr NewSession = FInsightsManager::Get()->GetSession(); + + if (NewSession != Session) + { + Session = NewSession; + RebuildTree(); + } + else + { + UpdateTree(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::UpdateTree() +{ + // Create groups, sort timers within the group and apply filtering. + CreateGroups(); + SortTimers(); + ApplyFiltering(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::ApplyFiltering() +{ + FilteredGroupNodes.Reset(); + + // Apply filter to all groups and its children. + const int32 NumGroups = GroupNodes.Num(); + for (int32 GroupIndex = 0; GroupIndex < NumGroups; ++GroupIndex) + { + FTimerNodePtr& GroupPtr = GroupNodes[GroupIndex]; + GroupPtr->ClearFilteredChildren(); + const bool bIsGroupVisible = Filters->PassesAllFilters(GroupPtr); + + const TArray& GroupChildren = GroupPtr->GetChildren(); + const int32 NumChildren = GroupChildren.Num(); + int32 NumVisibleChildren = 0; + for (int32 Cx = 0; Cx < NumChildren; ++Cx) + { + // Add a child. + const FTimerNodePtr& NodePtr = GroupChildren[Cx]; + const bool bIsChildVisible = Filters->PassesAllFilters(NodePtr) && bTimerNodeIsVisible[static_cast(NodePtr->GetType())]; + if (bIsChildVisible) + { + GroupPtr->AddFilteredChild(NodePtr); + NumVisibleChildren++; + } + } + + if (bIsGroupVisible || NumVisibleChildren > 0) + { + // Add a group. + FilteredGroupNodes.Add(GroupPtr); + GroupPtr->bForceExpandGroupNode = true; + } + else + { + GroupPtr->bForceExpandGroupNode = false; + } + } + + // Only expand timer nodes if we have a text filter. + const bool bNonEmptyTextFilter = !TextFilter->GetRawFilterText().IsEmpty(); + if (bNonEmptyTextFilter) + { + if (!bExpansionSaved) + { + ExpandedNodes.Empty(); + TreeView->GetExpandedItems(ExpandedNodes); + bExpansionSaved = true; + } + + for (int32 Fx = 0; Fx < FilteredGroupNodes.Num(); Fx++) + { + const FTimerNodePtr& GroupPtr = FilteredGroupNodes[Fx]; + TreeView->SetItemExpansion(GroupPtr, GroupPtr->bForceExpandGroupNode); + } + } + else + { + if (bExpansionSaved) + { + // Restore previously expanded nodes when the text filter is disabled. + TreeView->ClearExpandedItems(); + for (auto It = ExpandedNodes.CreateConstIterator(); It; ++It) + { + TreeView->SetItemExpansion(*It, true); + } + bExpansionSaved = false; + } + } + + // Request tree refresh + TreeView->RequestTreeRefresh(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::HandleItemToStringArray(const FTimerNodePtr& FTimerNodePtr, TArray& OutSearchStrings) const +{ + OutSearchStrings.Add(FTimerNodePtr->GetName().GetPlainNameString()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef STimersView::GetToggleButtonForTimerType(const ETimerNodeType NodeType) +{ + return SNew(SCheckBox) + .Style(FEditorStyle::Get(), "ToggleButtonCheckbox") + .HAlign(HAlign_Center) + .Padding(2.0f) + .OnCheckStateChanged(this, &STimersView::FilterByTimerType_OnCheckStateChanged, NodeType) + .IsChecked(this, &STimersView::FilterByTimerType_IsChecked, NodeType) + .ToolTipText(TimerNodeTypeHelper::ToDescription(NodeType)) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SImage) + .Image(TimerNodeTypeHelper::GetIconForTimerNodeType(NodeType)) + ] + + +SHorizontalBox::Slot() + .Padding(2.0f, 0.0f, 0.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(TimerNodeTypeHelper::ToName(NodeType)) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Caption")) + ] + ]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::FilterByTimerType_OnCheckStateChanged(ECheckBoxState NewRadioState, const ETimerNodeType InStatType) +{ + bTimerNodeIsVisible[static_cast(InStatType)] = (NewRadioState == ECheckBoxState::Checked); + ApplyFiltering(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +ECheckBoxState STimersView::FilterByTimerType_IsChecked(const ETimerNodeType InStatType) const +{ + return bTimerNodeIsVisible[static_cast(InStatType)] ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// TreeView +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::TreeView_Refresh() +{ + if (TreeView.IsValid()) + { + TreeView->RequestTreeRefresh(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::TreeView_OnSelectionChanged(FTimerNodePtr SelectedItem, ESelectInfo::Type SelectInfo) +{ + if (SelectInfo != ESelectInfo::Direct) + { + TArray SelectedItems = TreeView->GetSelectedItems(); + if (SelectedItems.Num() == 1) + { + //HighlightedTimerName = SelectedItems[0]->GetName(); + } + else + { + //HighlightedTimerName = NAME_None; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::TreeView_OnGetChildren(FTimerNodePtr InParent, TArray& OutChildren) +{ + OutChildren = InParent->GetFilteredChildren(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::TreeView_OnMouseButtonDoubleClick(FTimerNodePtr TimerNode) +{ + if (!TimerNode->IsGroup()) + { + //im:TODO: const bool bIsStatTracked = FTimingProfilerManager::Get()->IsStatTracked(TimerNode->GetId()); + // if (!bIsStatTracked) + // { + // // Add a new graph. + // FTimingProfilerManager::Get()->TrackStat(TimerNode->GetId()); + // } + // else + // { + // // Remove a graph + // FTimingProfilerManager::Get()->UntrackStat(TimerNode->GetId()); + // } + } + else + { + const bool bIsGroupExpanded = TreeView->IsItemExpanded(TimerNode); + TreeView->SetItemExpansion(TimerNode, !bIsGroupExpanded); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Tree View's Table Row +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef STimersView::TreeView_OnGenerateRow(FTimerNodePtr TimerNodePtr, const TSharedRef& OwnerTable) +{ + TSharedRef TableRow = + SNew(STimerTableRow, OwnerTable) + .OnShouldBeEnabled(this, &STimersView::TableRow_ShouldBeEnabled) + .OnIsColumnVisible(this, &STimersView::TableRow_IsColumnVisible) + .OnSetHoveredTableCell(this, &STimersView::TableRow_SetHoveredTableCell) + .OnGetColumnOutlineHAlignmentDelegate(this, &STimersView::TableRow_GetColumnOutlineHAlignment) + .HighlightText(this, &STimersView::TableRow_GetHighlightText) + .HighlightedTimerName(this, &STimersView::TableRow_GetHighlightedTimerName) + .TimerNodePtr(TimerNodePtr); + + return TableRow; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimersView::TableRow_IsColumnVisible(const FName ColumnId) const +{ + bool bResult = false; + const FTimersViewColumn& ColumnPtr = TreeViewHeaderColumns.FindChecked(ColumnId); + return ColumnPtr.bIsVisible; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::TableRow_SetHoveredTableCell(const FName ColumnId, const FTimerNodePtr TimerNodePtr) +{ + HoveredColumnId = ColumnId; + + const bool bIsAnyMenusVisible = FSlateApplication::Get().AnyMenusVisible(); + if (!HasMouseCapture() && !bIsAnyMenusVisible) + { + HoveredTimerNodePtr = TimerNodePtr; + } + + //UE_LOG(TimingProfiler, Log, TEXT("%s -> %s"), *HoveredColumnId.GetPlainNameString(), TimerNodePtr.IsValid() ? *TimerNodePtr->GetName().GetPlainNameString() : TEXT("nullptr")); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +EHorizontalAlignment STimersView::TableRow_GetColumnOutlineHAlignment(const FName ColumnId) const +{ + const TIndirectArray& Columns = TreeViewHeaderRow->GetColumns(); + const int32 LastColumnIdx = Columns.Num() - 1; + + // First column + if (Columns[0].ColumnId == ColumnId) + { + return HAlign_Left; + } + // Last column + else if (Columns[LastColumnIdx].ColumnId == ColumnId) + { + return HAlign_Right; + } + // Middle columns + { + return HAlign_Center; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText STimersView::TableRow_GetHighlightText() const +{ + return SearchBox->GetText(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FName STimersView::TableRow_GetHighlightedTimerName() const +{ + return HighlightedTimerName; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimersView::TableRow_ShouldBeEnabled(const uint32 TimerId) const +{ + return true;//im:TODO: Session->GetAggregatedStat(TimerId) != nullptr; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// SearchBox +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::SearchBox_OnTextChanged(const FText& InFilterText) +{ + TextFilter->SetRawFilterText(InFilterText); + SearchBox->SetError(TextFilter->GetFilterErrorText()); + ApplyFiltering(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimersView::SearchBox_IsEnabled() const +{ + return TimerNodes.Num() > 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// GroupBy +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::CreateGroups() +{ + TMap GroupNodeSet; + + if (GroupingMode == ETimerGroupingMode::Flat) + { + const FName GroupName(TEXT("All")); + FTimerNodePtr* GroupPtr = GroupNodeSet.Find(GroupName); + if (!GroupPtr) + { + GroupPtr = &GroupNodeSet.Add(GroupName, MakeShareable(new FTimerNode(GroupName))); + } + + for (const FTimerNodePtr& TimerNodePtr : TimerNodes) + { + (*GroupPtr)->AddChildAndSetGroupPtr(TimerNodePtr); + } + + TreeView->SetItemExpansion(*GroupPtr, true); + } + // Creates groups based on stat metadata groups. + else if (GroupingMode == ETimerGroupingMode::ByMetaGroupName) + { + for (const FTimerNodePtr& TimerNodePtr : TimerNodes) + { + const FName GroupName = TimerNodePtr->GetMetaGroupName(); + + FTimerNodePtr* GroupPtr = GroupNodeSet.Find(GroupName); + if (!GroupPtr) + { + GroupPtr = &GroupNodeSet.Add(GroupName, MakeShareable(new FTimerNode(GroupName))); + } + + (*GroupPtr)->AddChildAndSetGroupPtr(TimerNodePtr); + TreeView->SetItemExpansion(*GroupPtr, true); + } + } + // Creates one group for each stat type. + else if (GroupingMode == ETimerGroupingMode::ByType) + { + for (const FTimerNodePtr& TimerNodePtr : TimerNodes) + { + const FName GroupName = *TimerNodeTypeHelper::ToName(TimerNodePtr->GetType()).ToString(); + + FTimerNodePtr* GroupPtr = GroupNodeSet.Find(GroupName); + if (!GroupPtr) + { + GroupPtr = &GroupNodeSet.Add(GroupName, MakeShareable(new FTimerNode(GroupName))); + } + + (*GroupPtr)->AddChildAndSetGroupPtr(TimerNodePtr); + TreeView->SetItemExpansion(*GroupPtr, true); + } + } + // Creates one group for one letter. + else if (GroupingMode == ETimerGroupingMode::ByName) + { + for (const FTimerNodePtr& TimerNodePtr : TimerNodes) + { + const FName GroupName = *TimerNodePtr->GetName().GetPlainNameString().Left(1); + + FTimerNodePtr* GroupPtr = GroupNodeSet.Find(GroupName); + if (!GroupPtr) + { + GroupPtr = &GroupNodeSet.Add(GroupName, MakeShareable(new FTimerNode(GroupName))); + } + + (*GroupPtr)->AddChildAndSetGroupPtr(TimerNodePtr); + } + } + // Creates one group for each logarithmic range ie. 0.001 - 0.01, 0.01 - 0.1, 0.1 - 1.0, 1.0 - 10.0, etc. + else if (GroupingMode == ETimerGroupingMode::ByTotalInclusiveTime) + { + //im:TODO: + } + // Creates one group for each logarithmic range ie. 0.001 - 0.01, 0.01 - 0.1, 0.1 - 1.0, 1.0 - 10.0, etc. + else if (GroupingMode == ETimerGroupingMode::ByTotalExclusiveTime) + { + //im:TODO: + } + // Creates one group for each logarithmic range ie. 0, 1 - 10, 10 - 100, 100 - 1000, etc. + else if (GroupingMode == ETimerGroupingMode::ByInstanceCount) + { + //im:TODO: + } + + GroupNodeSet.GenerateValueArray(GroupNodes); + + // Sort by a fake group name. + GroupNodes.Sort(TimerNodeSortingHelper::ByNameAscending()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::CreateGroupByOptionsSources() +{ + GroupByOptionsSource.Reset(3); + + // Must be added in order of elements in the ETimerGroupingMode. + GroupByOptionsSource.Add(MakeShareable(new ETimerGroupingMode(ETimerGroupingMode::Flat))); + GroupByOptionsSource.Add(MakeShareable(new ETimerGroupingMode(ETimerGroupingMode::ByName))); + //GroupByOptionsSource.Add(MakeShareable(new ETimerGroupingMode(ETimerGroupingMode::ByMetaGroupName))); + GroupByOptionsSource.Add(MakeShareable(new ETimerGroupingMode(ETimerGroupingMode::ByType))); + + ETimerGroupingModePtr* GroupingModePtrPtr = GroupByOptionsSource.FindByPredicate([&](const ETimerGroupingModePtr InGroupingModePtr) { return *InGroupingModePtr == GroupingMode; }); + if (GroupingModePtrPtr != nullptr) + { + GroupByComboBox->SetSelectedItem(*GroupingModePtrPtr); + } + + GroupByComboBox->RefreshOptions(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::GroupBy_OnSelectionChanged(TSharedPtr NewGroupingMode, ESelectInfo::Type SelectInfo) +{ + if (SelectInfo != ESelectInfo::Direct) + { + GroupingMode = *NewGroupingMode; + + // Create groups, sort timers within the group and apply filtering. + CreateGroups(); + SortTimers(); + ApplyFiltering(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef STimersView::GroupBy_OnGenerateWidget(TSharedPtr InGroupingMode) const +{ + return SNew(STextBlock) + .Text(TimerNodeGroupingHelper::ToName(*InGroupingMode)) + .ToolTipText(TimerNodeGroupingHelper::ToDescription(*InGroupingMode)); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText STimersView::GroupBy_GetSelectedText() const +{ + return TimerNodeGroupingHelper::ToName(GroupingMode); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FText STimersView::GroupBy_GetSelectedTooltipText() const +{ + return TimerNodeGroupingHelper::ToDescription(GroupingMode); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// SortBy +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::SortTimers() +{ + const int32 NumGroups = GroupNodes.Num(); + + #define CHECK_AND_SORT_COLUMN(ColumnId, SortTypeName) \ + if (ColumnBeingSorted == ColumnId) \ + { \ + if (ColumnSortMode == EColumnSortMode::Type::Descending) \ + { \ + for (int32 ID = 0; ID < NumGroups; ++ID) \ + { \ + GroupNodes[ID]->SortChildren(TimerNodeSortingHelper::SortTypeName##Descending()); \ + } \ + } \ + else /*if (ColumnSortMode == EColumnSortMode::Type::Ascending)*/ \ + { \ + for (int32 ID = 0; ID < NumGroups; ++ID) \ + { \ + GroupNodes[ID]->SortChildren(TimerNodeSortingHelper::SortTypeName##Ascending()); \ + } \ + } \ + } + + CHECK_AND_SORT_COLUMN(FTimersViewColumns::NameColumnID, ByName) + else CHECK_AND_SORT_COLUMN(FTimersViewColumns::MetaGroupNameColumnID, ByMetaGroupName) + else CHECK_AND_SORT_COLUMN(FTimersViewColumns::TypeColumnID, ByType) + else CHECK_AND_SORT_COLUMN(FTimersViewColumns::InstanceCountColumnID, ByInstanceCount) + // Inclusive Time columns + else CHECK_AND_SORT_COLUMN(FTimersViewColumns::TotalInclusiveTimeColumnID, ByTotalInclusiveTime) + else CHECK_AND_SORT_COLUMN(FTimersViewColumns::MaxInclusiveTimeColumnID, ByMaxInclusiveTime) + else CHECK_AND_SORT_COLUMN(FTimersViewColumns::AverageInclusiveTimeColumnID, ByAverageInclusiveTime) + else CHECK_AND_SORT_COLUMN(FTimersViewColumns::MedianInclusiveTimeColumnID, ByMedianInclusiveTime) + else CHECK_AND_SORT_COLUMN(FTimersViewColumns::MinInclusiveTimeColumnID, ByMinInclusiveTime) + // Exclusive Time columns + else CHECK_AND_SORT_COLUMN(FTimersViewColumns::TotalExclusiveTimeColumnID, ByTotalExclusiveTime) + else CHECK_AND_SORT_COLUMN(FTimersViewColumns::MaxExclusiveTimeColumnID, ByMaxExclusiveTime) + else CHECK_AND_SORT_COLUMN(FTimersViewColumns::AverageExclusiveTimeColumnID, ByAverageExclusiveTime) + else CHECK_AND_SORT_COLUMN(FTimersViewColumns::MedianExclusiveTimeColumnID, ByMedianExclusiveTime) + else CHECK_AND_SORT_COLUMN(FTimersViewColumns::MinExclusiveTimeColumnID, ByMinExclusiveTime) + + #undef CHECK_AND_SORT_COLUMN +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +EColumnSortMode::Type STimersView::GetSortModeForColumn(const FName ColumnId) const +{ + if (ColumnBeingSorted != ColumnId) + { + return EColumnSortMode::None; + } + + return ColumnSortMode; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::SetSortModeForColumn(const FName& ColumnId, const EColumnSortMode::Type SortMode) +{ + ColumnBeingSorted = ColumnId; + ColumnSortMode = SortMode; + + // Sort timers and apply filtering. + SortTimers(); + ApplyFiltering(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::OnSortModeChanged(const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type SortMode) +{ + SetSortModeForColumn(ColumnId, SortMode); + TreeView_Refresh(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// SortMode action (HeaderMenu) +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimersView::HeaderMenu_SortMode_IsChecked(const FName ColumnId, const EColumnSortMode::Type InSortMode) +{ + return ColumnBeingSorted == ColumnId && ColumnSortMode == InSortMode; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimersView::HeaderMenu_SortMode_CanExecute(const FName ColumnId, const EColumnSortMode::Type InSortMode) const +{ + const FTimersViewColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnId); + const bool bIsValid = Column.bCanBeSorted(); + + bool bCanExecute = ColumnBeingSorted != ColumnId ? true : ColumnSortMode != InSortMode; + bCanExecute = bCanExecute && bIsValid; + + return bCanExecute; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::HeaderMenu_SortMode_Execute(const FName ColumnId, const EColumnSortMode::Type InSortMode) +{ + SetSortModeForColumn(ColumnId, InSortMode); + TreeView_Refresh(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// SortMode action (ContextMenu) +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimersView::ContextMenu_SortMode_IsChecked(const EColumnSortMode::Type InSortMode) +{ + return ColumnSortMode == InSortMode; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimersView::ContextMenu_SortMode_CanExecute(const EColumnSortMode::Type InSortMode) const +{ + return ColumnSortMode != InSortMode; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::ContextMenu_SortMode_Execute(const EColumnSortMode::Type InSortMode) +{ + SetSortModeForColumn(ColumnBeingSorted, InSortMode); + TreeView_Refresh(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// SortByColumn action (ContextMenu) +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimersView::ContextMenu_SortByColumn_IsChecked(const FName ColumnId) +{ + return ColumnId == ColumnBeingSorted; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimersView::ContextMenu_SortByColumn_CanExecute(const FName ColumnId) const +{ + return ColumnId != ColumnBeingSorted; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::ContextMenu_SortByColumn_Execute(const FName ColumnId) +{ + SetSortModeForColumn(ColumnId, EColumnSortMode::Descending); + TreeView_Refresh(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// HideColumn action (HeaderMenu) +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimersView::HeaderMenu_HideColumn_CanExecute(const FName ColumnId) const +{ + const FTimersViewColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnId); + return Column.bCanBeHidden(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::HeaderMenu_HideColumn_Execute(const FName ColumnId) +{ + FTimersViewColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnId); + Column.bIsVisible = false; + TreeViewHeaderRow->RemoveColumn(ColumnId); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// ToggleColumn action (ContextMenu) +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimersView::ContextMenu_ToggleColumn_IsChecked(const FName ColumnId) +{ + const FTimersViewColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnId); + return Column.bIsVisible; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimersView::ContextMenu_ToggleColumn_CanExecute(const FName ColumnId) const +{ + const FTimersViewColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnId); + return Column.bCanBeHidden(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::ContextMenu_ToggleColumn_Execute(const FName ColumnId) +{ + FTimersViewColumn& Column = TreeViewHeaderColumns.FindChecked(ColumnId); + if (Column.bIsVisible) + { + HeaderMenu_HideColumn_Execute(ColumnId); + } + else + { + TreeViewHeaderRow_ShowColumn(ColumnId); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// "Show All Columns" action (ContextMenu) +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimersView::ContextMenu_ShowAllColumns_CanExecute() const +{ + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::ContextMenu_ShowAllColumns_Execute() +{ + ColumnSortMode = DefaultColumnSortMode; + ColumnBeingSorted = DefaultColumnBeingSorted; + + const int32 NumColumns = FTimersViewColumnFactory::Get().Collection.Num(); + for (int32 ColumnIndex = 0; ColumnIndex < NumColumns; ColumnIndex++) + { + const FTimersViewColumn& DefaultColumn = *FTimersViewColumnFactory::Get().Collection[ColumnIndex]; + const FTimersViewColumn& CurrentColumn = TreeViewHeaderColumns.FindChecked(DefaultColumn.Id); + + if (!CurrentColumn.bIsVisible) + { + TreeViewHeaderRow_ShowColumn(DefaultColumn.Id); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// "Show Min/Max/Median Columns" action (ContextMenu) +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimersView::ContextMenu_ShowMinMaxMedColumns_CanExecute() const +{ + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::ContextMenu_ShowMinMaxMedColumns_Execute() +{ + TSet Preset = + { + FTimersViewColumns::NameColumnID, + //FTimersViewColumns::MetaGroupNameColumnID, + //FTimersViewColumns::TypeColumnID, + FTimersViewColumns::InstanceCountColumnID, + + FTimersViewColumns::TotalInclusiveTimeColumnID, + FTimersViewColumns::MaxInclusiveTimeColumnID, + FTimersViewColumns::MedianInclusiveTimeColumnID, + FTimersViewColumns::MinInclusiveTimeColumnID, + + FTimersViewColumns::TotalExclusiveTimeColumnID, + FTimersViewColumns::MaxExclusiveTimeColumnID, + FTimersViewColumns::MedianExclusiveTimeColumnID, + FTimersViewColumns::MinExclusiveTimeColumnID, + }; + + ColumnSortMode = EColumnSortMode::Descending; + ColumnBeingSorted = FTimersViewColumns::TotalInclusiveTimeColumnID; + + const int32 NumColumns = FTimersViewColumnFactory::Get().Collection.Num(); + for (int32 ColumnIndex = 0; ColumnIndex < NumColumns; ColumnIndex++) + { + const FTimersViewColumn& DefaultColumn = *FTimersViewColumnFactory::Get().Collection[ColumnIndex]; + const FTimersViewColumn& CurrentColumn = TreeViewHeaderColumns.FindChecked(DefaultColumn.Id); + + bool bIsVisible = Preset.Contains(DefaultColumn.Id); + if (bIsVisible && !CurrentColumn.bIsVisible) + { + TreeViewHeaderRow_ShowColumn(DefaultColumn.Id); + } + else if (!bIsVisible && CurrentColumn.bIsVisible) + { + HeaderMenu_HideColumn_Execute(DefaultColumn.Id); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// ResetColumns action (ContextMenu) +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimersView::ContextMenu_ResetColumns_CanExecute() const +{ + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::ContextMenu_ResetColumns_Execute() +{ + ColumnSortMode = DefaultColumnSortMode; + ColumnBeingSorted = DefaultColumnBeingSorted; + + const int32 NumColumns = FTimersViewColumnFactory::Get().Collection.Num(); + for (int32 ColumnIndex = 0; ColumnIndex < NumColumns; ColumnIndex++) + { + const FTimersViewColumn& DefaultColumn = *FTimersViewColumnFactory::Get().Collection[ColumnIndex]; + const FTimersViewColumn& CurrentColumn = TreeViewHeaderColumns.FindChecked(DefaultColumn.Id); + + if (DefaultColumn.bIsVisible && !CurrentColumn.bIsVisible) + { + TreeViewHeaderRow_ShowColumn(DefaultColumn.Id); + } + else if (!DefaultColumn.bIsVisible && CurrentColumn.bIsVisible) + { + HeaderMenu_HideColumn_Execute(DefaultColumn.Id); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::RebuildTree(bool bResync) +{ + bool bListHasChanged = false; + + if (bResync) + { + TimerNodes.Empty(TimerNodes.Num()); + //TimerNodesMap.Empty(TimerNodesMap.Num()); + TimerNodesIdMap.Empty(TimerNodesIdMap.Num()); + bListHasChanged = true; + } + + if (Session.IsValid() && Trace::ReadTimingProfilerProvider(*Session.Get())) + { + Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); + + const Trace::ITimingProfilerProvider& TimingProfilerProvider = *Trace::ReadTimingProfilerProvider(*Session.Get()); + + TimingProfilerProvider.ReadTimers([this, &bResync, &bListHasChanged](const Trace::FTimingProfilerTimer* Timers, uint64 TimersCount) + { + if (!bResync) + { + bResync = (TimersCount != TimerNodes.Num()); + } + + if (bResync) + { + TimerNodes.Empty(TimerNodes.Num()); + //TimerNodesMap.Empty(TimerNodesMap.Num()); + TimerNodesIdMap.Empty(TimerNodesIdMap.Num()); + bListHasChanged = true; + + for (uint64 TimerIndex = 0; TimerIndex < TimersCount; ++TimerIndex) + { + const Trace::FTimingProfilerTimer& Timer = Timers[TimerIndex]; + FName Name(Timer.Name);// +TEXT(" [GPU]"))); + FName Group(Timer.IsGpuTimer ? TEXT("GPU") : TEXT("CPU")); + ETimerNodeType Type = Timer.IsGpuTimer ? ETimerNodeType::GpuScope : ETimerNodeType::CpuScope; + FTimerNode* TimerPtr = new FTimerNode(Timer.Id, Name, Group, Type); + FTimerNodePtr TimerNodePtr = MakeShareable(TimerPtr); + TimerNodes.Add(TimerNodePtr); + //TimerNodesMap.Add(Name, TimerNodePtr); + TimerNodesIdMap.Add(Timer.Id, TimerNodePtr); + } + } + }); + } + + if (bListHasChanged) + { + UpdateTree(); + UpdateStats(StatsStartTime, StatsEndTime); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::UpdateStats(double StartTime, double EndTime) +{ + for (const FTimerNodePtr& TimerNodePtr : TimerNodes) + { + TimerNodePtr->ResetAggregatedStats(); + } + + if (Session.IsValid() && StartTime < EndTime && Trace::ReadTimingProfilerProvider(*Session.Get())) + { + TSharedPtr Wnd = FTimingProfilerManager::Get()->GetProfilerWindow(); + TSharedPtr TimingView = Wnd.IsValid() ? Wnd->TimingView : nullptr; + + auto ThreadFilter = [&TimingView](uint32 ThreadId) + { + return !TimingView.IsValid() || TimingView->IsCpuTrackVisible(ThreadId); + }; + + const bool bIsGpuTrackVisible = TimingView.IsValid() && TimingView->IsGpuTrackVisible(); + + TUniquePtr> AggregationResultTable; + { + Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); + const Trace::ITimingProfilerProvider& TimingProfilerProvider = *Trace::ReadTimingProfilerProvider(*Session.Get()); + AggregationResultTable.Reset(TimingProfilerProvider.CreateAggregation(StartTime, EndTime, ThreadFilter, bIsGpuTrackVisible)); + } + + TUniquePtr> AggregationResultTableReader(AggregationResultTable->CreateReader()); + while (AggregationResultTableReader->IsValid()) + { + const Trace::FTimingProfilerAggregatedStats* Row = AggregationResultTableReader->GetCurrentRow(); + FTimerNodePtr* TimerNodePtrPtr = TimerNodesIdMap.Find(Row->Timer->Id); + if (TimerNodePtrPtr != nullptr) + { + (*TimerNodePtrPtr)->SetAggregatedStats(*Row); + } + AggregationResultTableReader->NextRow(); + } + + StatsStartTime = StartTime; + StatsEndTime = EndTime; + + UpdateTree(); + TreeView->RebuildList(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersView::SelectTimerNode(uint64 Id) +{ + FTimerNodePtr* TimerNodePtrPtr = TimerNodesIdMap.Find(Id); + if (TimerNodePtrPtr != nullptr) + { + //UE_LOG(TimingProfiler, Log, TEXT("Select and RequestScrollIntoView %s"), *(*TimerNodePtrPtr)->GetName().ToString()); + TreeView->SetSelection(*TimerNodePtrPtr); + TreeView->RequestScrollIntoView(*TimerNodePtrPtr); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimersView.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimersView.h new file mode 100644 index 000000000000..83c875b42f18 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimersView.h @@ -0,0 +1,321 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/FilterCollection.h" +#include "Misc/TextFilter.h" +#include "SlateFwd.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/SWidget.h" +#include "Widgets/Views/SHeaderRow.h" +#include "Widgets/Views/STableRow.h" +#include "Widgets/Views/STableViewBase.h" +#include "Widgets/Views/STreeView.h" + +// Insights +#include "Insights/ViewModels/TimerGroupingAndSorting.h" +#include "Insights/ViewModels/TimerNode.h" +#include "Insights/ViewModels/TimersViewColumn.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FMenuBuilder; + +namespace Trace +{ + class IAnalysisSession; + struct FTimelineEvent; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** The filter collection - used for updating the list of timer nodes. */ +typedef TFilterCollection FTimerNodeFilterCollection; + +/** The text based filter - used for updating the list of timer nodes. */ +typedef TTextFilter FTimerNodeTextFilter; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * A custom widget used to display the list of timers. + */ +class STimersView : public SCompoundWidget +{ +public: + /** Default constructor. */ + STimersView(); + + /** Virtual destructor. */ + virtual ~STimersView(); + + SLATE_BEGIN_ARGS(STimersView){} + SLATE_END_ARGS() + + /** + * Construct this widget + * @param InArgs - The declaration data for this widget + */ + void Construct(const FArguments& InArgs); + + /** + * Rebuilds the tree (if necessary). + * @param bResync - If true, it forces a resync with list of timers from Analysis, even if the list did not changed since last sync. + */ + void RebuildTree(bool bResync = true); + void UpdateStats(double StartTime, double EndTime); + + void SelectTimerNode(uint64 Id); + + //const TSet& GetTimerNodes() const { return TimerNodes; } + //const TMap GetTimerNodesIdMap() const { return TimerNodesIdMap; } + const FTimerNodePtr* GetTimerNode(uint64 Id) const { return TimerNodesIdMap.Find(Id); } + +protected: + + void UpdateTree(); + + /** Called when the analysis session has changed. */ + void InsightsManager_OnSessionChanged(); + + /** + * Populates OutSearchStrings with the strings that should be used in searching. + * + * @param GroupOrStatNodePtr - the group and stat node to get a text description from. + * @param OutSearchStrings - an array of strings to use in searching. + * + */ + void HandleItemToStringArray(const FTimerNodePtr& GroupOrStatNodePtr, TArray& OutSearchStrings) const; + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Tree View - Context Menu + + TSharedPtr TreeView_GetMenuContent(); + void TreeView_BuildSortByMenu(FMenuBuilder& MenuBuilder); + void TreeView_BuildViewColumnMenu(FMenuBuilder& MenuBuilder); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Tree View - Columns' Header + + void InitializeAndShowHeaderColumns(); + void TreeViewHeaderRow_CreateColumnArgs(const int32 ColumnIndex); + void TreeViewHeaderRow_ShowColumn(const FName ColumnId); + TSharedRef TreeViewHeaderRow_GenerateColumnMenu(const FTimersViewColumn& Column); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Tree View - Misc + + void TreeView_Refresh(); + + /** + * Called by STreeView to retrieves the children for the specified parent item. + * @param InParent - The parent node to retrieve the children from. + * @param OutChildren - List of children for the parent node. + */ + void TreeView_OnGetChildren(FTimerNodePtr InParent, TArray& OutChildren); + + /** Called by STreeView when selection has changed. */ + void TreeView_OnSelectionChanged(FTimerNodePtr SelectedItem, ESelectInfo::Type SelectInfo); + + /** Called by STreeView when a tree item is double clicked. */ + void TreeView_OnMouseButtonDoubleClick(FTimerNodePtr TreeNode); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Tree View - Table Row + + /** Called by STreeView to generate a table row for the specified item. */ + TSharedRef TreeView_OnGenerateRow(FTimerNodePtr TreeNode, const TSharedRef& OwnerTable); + + bool TableRow_IsColumnVisible(const FName ColumnId) const; + void TableRow_SetHoveredTableCell(const FName ColumnId, const FTimerNodePtr TimerNodePtr); + EHorizontalAlignment TableRow_GetColumnOutlineHAlignment(const FName ColumnId) const; + + FText TableRow_GetHighlightText() const; + FName TableRow_GetHighlightedTimerName() const; + + bool TableRow_ShouldBeEnabled(const uint32 TimerId) const; + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Filtering + + /** Populates the group and stat tree with items based on the current data. */ + void ApplyFiltering(); + + TSharedRef GetToggleButtonForTimerType(const ETimerNodeType StatType); + void FilterByTimerType_OnCheckStateChanged(ECheckBoxState NewRadioState, const ETimerNodeType InStatType); + ECheckBoxState FilterByTimerType_IsChecked(const ETimerNodeType InStatType) const; + + bool SearchBox_IsEnabled() const; + void SearchBox_OnTextChanged(const FText& InFilterText); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // GroupBy + + void CreateGroups(); + void CreateGroupByOptionsSources(); + + void GroupBy_OnSelectionChanged(TSharedPtr NewGroupingMode, ESelectInfo::Type SelectInfo); + + TSharedRef GroupBy_OnGenerateWidget(TSharedPtr InGroupingMode) const; + + FText GroupBy_GetSelectedText() const; + + FText GroupBy_GetSelectedTooltipText() const; + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Sorting + + void SortTimers(); + + EColumnSortMode::Type GetSortModeForColumn(const FName ColumnId) const; + void SetSortModeForColumn(const FName& ColumnId, EColumnSortMode::Type SortMode); + void OnSortModeChanged(const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type SortMode); + + //void TreeView_BuildSortByMenu(FMenuBuilder& MenuBuilder); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Sorting actions + + // SortMode (HeaderMenu) + bool HeaderMenu_SortMode_IsChecked(const FName ColumnId, const EColumnSortMode::Type InSortMode); + bool HeaderMenu_SortMode_CanExecute(const FName ColumnId, const EColumnSortMode::Type InSortMode) const; + void HeaderMenu_SortMode_Execute(const FName ColumnId, const EColumnSortMode::Type InSortMode); + + // SortMode (ContextMenu) + bool ContextMenu_SortMode_IsChecked(const EColumnSortMode::Type InSortMode); + bool ContextMenu_SortMode_CanExecute(const EColumnSortMode::Type InSortMode) const; + void ContextMenu_SortMode_Execute(const EColumnSortMode::Type InSortMode); + + // SortByColumn (ContextMenu) + bool ContextMenu_SortByColumn_IsChecked(const FName ColumnId); + bool ContextMenu_SortByColumn_CanExecute(const FName ColumnId) const; + void ContextMenu_SortByColumn_Execute(const FName ColumnId); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Column visibility actions + + // HideColumn (HeaderMenu) + bool HeaderMenu_HideColumn_CanExecute(const FName ColumnId) const; + void HeaderMenu_HideColumn_Execute(const FName ColumnId); + + // ToggleColumn (ContextMenu) + bool ContextMenu_ToggleColumn_IsChecked(const FName ColumnId); + bool ContextMenu_ToggleColumn_CanExecute(const FName ColumnId) const; + void ContextMenu_ToggleColumn_Execute(const FName ColumnId); + + // ShowAllColumns (ContextMenu) + bool ContextMenu_ShowAllColumns_CanExecute() const; + void ContextMenu_ShowAllColumns_Execute(); + + // MinMaxMedColumns (ContextMenu) + bool ContextMenu_ShowMinMaxMedColumns_CanExecute() const; + void ContextMenu_ShowMinMaxMedColumns_Execute(); + + // ResetColumns (ContextMenu) + bool ContextMenu_ResetColumns_CanExecute() const; + void ContextMenu_ResetColumns_Execute(); + +protected: + /** A weak pointer to the profiler session used to populate this widget. */ + TSharedPtr/*Weak*/ Session; + + ////////////////////////////////////////////////// + // Tree View, Columns + + /** The tree widget which holds the list of groups and timers corresponding with each group. */ + TSharedPtr> TreeView; + + /** Column metadata used to initialize column arguments, stored as PropertyName -> FEventGraphColumn. */ + TMap TreeViewHeaderColumns; + + /** Column arguments used to initialize a new header column in the tree view, stored as column name to column arguments mapping. */ + TMap TreeViewHeaderColumnArgs; + + /** Holds the tree view header row widget which display all columns in the tree view. */ + TSharedPtr TreeViewHeaderRow; + + /** External scrollbar used to synchronize tree view position. */ + TSharedPtr ExternalScrollbar; + + ////////////////////////////////////////////////// + // Hovered Column, Hovered Timer Node + + /** Name of the column currently being hovered by the mouse. */ + FName HoveredColumnId; + + /** A shared pointer to the timer node currently being hovered by the mouse. */ + FTimerNodePtr HoveredTimerNodePtr; + + /** Name of the timer that should be drawn as highlighted. */ + FName HighlightedTimerName; + + ////////////////////////////////////////////////// + // Timer Nodes + + /** An array of group and timer nodes generated from the metadata. */ + TArray GroupNodes; + + /** A filtered array of group and timer nodes to be displayed in the tree widget. */ + TArray FilteredGroupNodes; + + /** All timer nodes. */ + TSet TimerNodes; + + /** All timer nodes, stored as TimerName -> FTimerNodePtr. */ + //TMap TimerNodesMap; + + /** All timer nodes, stored as TimerId -> FTimerNodePtr. */ + TMap TimerNodesIdMap; + + /** Currently expanded group nodes. */ + TSet ExpandedNodes; + + /** If true, the expanded nodes have been saved before applying a text filter. */ + bool bExpansionSaved; + + ////////////////////////////////////////////////// + // Search box and filters + + /** The search box widget used to filter items displayed in the stats and groups tree. */ + TSharedPtr SearchBox; + + /** The text based filter. */ + TSharedPtr TextFilter; + + /** The filter collection. */ + TSharedPtr Filters; + + /** Holds the visibility of each timer type. */ + bool bTimerNodeIsVisible[static_cast(ETimerNodeType::InvalidOrMax)]; + + ////////////////////////////////////////////////// + // Grouping + + TArray> GroupByOptionsSource; + + TSharedPtr>> GroupByComboBox; + + /** How we group the timers? */ + ETimerGroupingMode GroupingMode; + + ////////////////////////////////////////////////// + // Sorting + + /** How we sort the timers? */ + EColumnSortMode::Type ColumnSortMode; + + /** Name of the column currently being sorted, NAME_None if sorting is disabled. */ + FName ColumnBeingSorted; + + static const EColumnSortMode::Type DefaultColumnSortMode; + static const FName DefaultColumnBeingSorted; + + ////////////////////////////////////////////////// + + double StatsStartTime; + double StatsEndTime; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimersViewTooltip.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimersViewTooltip.cpp new file mode 100644 index 000000000000..d88414532b9f --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimersViewTooltip.cpp @@ -0,0 +1,558 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "STimersViewTooltip.h" + +#include "EditorStyleSet.h" +#include "SlateOptMacros.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SGridPanel.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/SToolTip.h" +#include "Widgets/Text/STextBlock.h" + +// Insights +#include "Insights/Common/TimeUtils.h" +#include "Insights/ViewModels/TimerNode.h" +#include "Insights/ViewModels/TimersViewColumn.h" +#include "Insights/ViewModels/TimerNodeHelper.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define LOCTEXT_NAMESPACE "STimersViewTooltip" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + +TSharedPtr STimersViewTooltip::GetColumnTooltip(const FTimersViewColumn& Column) +{ + TSharedPtr ColumnTooltip = + SNew(SToolTip) + [ + SNew(SVerticalBox) + + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(Column.TitleName) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + ] + + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(Column.Description) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + ] + ]; + + return ColumnTooltip; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedPtr STimersViewTooltip::GetTableCellTooltip(const TSharedPtr TimerNodePtr) +{ + //const FSlateFontInfo TitleFont = FCoreStyle::GetDefaultFontStyle("Bold", 10); + //const FSlateFontInfo DescriptionFont = FCoreStyle::GetDefaultFontStyle("Regular", 8); + //const FSlateFontInfo DescriptionFontB = FCoreStyle::GetDefaultFontStyle("Bold", 8); + + const FLinearColor DefaultColor(1.0f,1.0f,1.0f,1.0f); + const FLinearColor ThreadColor(5.0f, 0.0f, 0.0f, 1.0f); + const float Alpha = 0.0f;//TimerNodePtr->_FramePct * 0.01f; + const FLinearColor ColorAndOpacity = FMath::Lerp(DefaultColor, ThreadColor,Alpha); + + const Trace::FAggregatedTimingStats& Stats = TimerNodePtr->GetAggregatedStats(); + + const int32 NumDigits = 5; + + const FText TotalInclusiveTimeText = FText::FromString(TimeUtils::FormatTimeAuto(Stats.TotalInclusiveTime)); + const FText MinInclusiveTimeText = FText::FromString(TimeUtils::FormatTimeMs(Stats.MinInclusiveTime, NumDigits, true)); + const FText MaxInclusiveTimeText = FText::FromString(TimeUtils::FormatTimeMs(Stats.MaxInclusiveTime, NumDigits, true)); + const FText AvgInclusiveTimeText = FText::FromString(TimeUtils::FormatTimeMs(Stats.AverageInclusiveTime, NumDigits, true)); + const FText MedInclusiveTimeText = FText::FromString(TimeUtils::FormatTimeMs(Stats.MedianInclusiveTime, NumDigits, true)); + + const FText TotalExclusiveTimeText = FText::FromString(TimeUtils::FormatTimeAuto(Stats.TotalExclusiveTime)); + const FText MinExclusiveTimeText = FText::FromString(TimeUtils::FormatTimeMs(Stats.MinExclusiveTime, NumDigits, true)); + const FText MaxExclusiveTimeText = FText::FromString(TimeUtils::FormatTimeMs(Stats.MaxExclusiveTime, NumDigits, true)); + const FText AvgExclusiveTimeText = FText::FromString(TimeUtils::FormatTimeMs(Stats.AverageExclusiveTime, NumDigits, true)); + const FText MedExclusiveTimeText = FText::FromString(TimeUtils::FormatTimeMs(Stats.MedianExclusiveTime, NumDigits, true)); + + const FText InstanceCountText = FText::AsNumber(Stats.InstanceCount); + + //TSharedPtr HBoxCaption; + TSharedPtr GridPanel; + TSharedPtr HBox; + + TSharedPtr TableCellTooltip = + SNew(SToolTip) + [ + SAssignNew(HBox, SHorizontalBox) + + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SVerticalBox) + + //+SVerticalBox::Slot() + //.AutoHeight() + //.Padding(2.0f) + //[ + // SAssignNew(HBoxCaption, SHorizontalBox) + //] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SSeparator) + .Orientation(Orient_Horizontal) + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SGridPanel) + + // Id: [Id] + +SGridPanel::Slot(0, 0) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("TT_Id", "Id:")) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + ] + +SGridPanel::Slot(1, 0) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(FText::AsNumber(TimerNodePtr->GetId())) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + ] + + // Name: [Name] + +SGridPanel::Slot(0, 1) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("TT_Name", "Name:")) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + ] + +SGridPanel::Slot(1, 1) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(FText::FromName(TimerNodePtr->GetName())) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + ] + + //// Group: [MetaGroupName] + //+SGridPanel::Slot(0, 2) + //.Padding(2.0f) + //[ + // SNew(STextBlock) + // .Text(LOCTEXT("TT_Group", "Group:")) + // .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + //] + //+SGridPanel::Slot(1, 2) + //.Padding(2.0f) + //[ + // SNew(STextBlock) + // .Text(FText::FromName(TimerNodePtr->GetMetaGroupName())) + // .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + //] + + // Type: [Type] + + SGridPanel::Slot(0, 3) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("TT_Type", "Type:")) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + ] + + SGridPanel::Slot(1, 3) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(TimerNodeTypeHelper::ToName(TimerNodePtr->GetType())) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + ] + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SSeparator) + .Orientation(Orient_Horizontal) + ] + + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SGridPanel) + + + SGridPanel::Slot(0, 0) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("TT_NumInstances", "Num Instances:")) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + ] + + SGridPanel::Slot(1, 0) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(InstanceCountText) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + ] + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SSeparator) + .Orientation(Orient_Horizontal) + ] + + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SAssignNew(GridPanel, SGridPanel) + + + SGridPanel::Slot(1, 0) + .Padding(2.0f) + .HAlign(HAlign_Right) + [ + SNew(STextBlock) + .Text(LOCTEXT("TT_InclusiveTime", "Inclusive")) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + ] + + SGridPanel::Slot(2, 0) + .Padding(2.0f) + .HAlign(HAlign_Right) + [ + SNew(STextBlock) + .Text(LOCTEXT("TT_ExclusiveTime", "Exclusive")) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + ] + + // Stats are added here. + ] + + +SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SSeparator) + .Orientation(Orient_Horizontal) + ] + ] + ]; + + int32 Row = 1; + AddStatsRow(GridPanel, Row, LOCTEXT("TT_TotalTime", "Total Time:"), TotalInclusiveTimeText, TotalExclusiveTimeText); + AddStatsRow(GridPanel, Row, LOCTEXT("TT_MaxTime", "Max Time:"), MaxInclusiveTimeText, MaxExclusiveTimeText); + AddStatsRow(GridPanel, Row, LOCTEXT("TT_AverageTime", "Average Time:"), AvgInclusiveTimeText, AvgExclusiveTimeText); + AddStatsRow(GridPanel, Row, LOCTEXT("TT_MedianTime", "Median Time:"), MedInclusiveTimeText, MedExclusiveTimeText); + AddStatsRow(GridPanel, Row, LOCTEXT("TT_MinTime", "Min Time:"), MinInclusiveTimeText, MinExclusiveTimeText); + + /* + //TODO: We need Stats hierarchy (not the grouping hierarchy)! + const bool bHasParent = TimerNodePtr->GetStats()->GetParent().IsValid(); + const bool bHasChildren = TimerNodePtr->GetStats()->GetChildren().Num() > 0; + + if (bHasParent) + { + const FText ParentName = FText::FromName(TimerNodePtr->GetGroupPtr()->GetName()); + HBoxCaption->AddSlot() + .AutoWidth() + [ + SNew(STextBlock) + .Text(ParentName) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Caption")) + ]; + + HBoxCaption->AddSlot() + .AutoWidth() + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("BreadcrumbTrail.Delimiter")) + ]; + } + + const FText TimerName = FText::FromName(TimerNodePtr->GetName()); + HBoxCaption->AddSlot() + .AutoWidth() + [ + SNew(STextBlock) + .Text(TimerName) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.CaptionBold")) + ]; + + if (bHasChildren) + { + typedef TKeyValuePair FEventNameAndPct; + TArray MinimalChildren; + + const TArray& Children = TimerNodePtr->GetChildren(); + for(int32 ChildIndex = 0; ChildIndex < Children.Num(); ChildIndex++) + { + const FTimerNodePtr Child = Children[ChildIndex]; + float Percent = static_cast(Child->GetStats().TotalInclusiveTime / TimerNodePtr->GetStats().TotalInclusiveTime * 100.0); + MinimalChildren.Add(FEventNameAndPct(Child->GetStats().TotalInclusiveTime, Child->GetName())); + } + + struct FCompareByFloatDescending + { + FORCEINLINE bool operator()(const FEventNameAndPct& A, const FEventNameAndPct& B) const + { + return A.Key > B.Key; + } + }; + MinimalChildren.Sort(FCompareByFloatDescending()); + + FString ChildrenNames; + const int32 NumChildrenToDisplay = FMath::Min(MinimalChildren.Num(), 3); + for(int32 SortedChildIndex = 0; SortedChildIndex < NumChildrenToDisplay; SortedChildIndex++) + { + const FEventNameAndPct& MinimalChild = MinimalChildren[SortedChildIndex]; + ChildrenNames += FString::Printf(TEXT("%s (%.1f %%)"), *MinimalChild.Value.ToString(), MinimalChild.Key); + + const bool bAddDelimiter = SortedChildIndex < NumChildrenToDisplay - 1; + if (bAddDelimiter) + { + ChildrenNames += TEXT(", "); + } + } + + HBoxCaption->AddSlot() + .AutoWidth() + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("BreadcrumbTrail.Delimiter")) + ]; + + HBoxCaption->AddSlot() + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString(ChildrenNames)) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Caption")) + ]; + } + */ + + return TableCellTooltip; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef STimersViewTooltip::GetTooltip() +{ + if (Session.IsValid()) + { + const TSharedRef ToolTipGrid = SNew(SGridPanel); + int32 CurrentRowPos = 0; + + AddHeader(ToolTipGrid, CurrentRowPos); + AddDescription(ToolTipGrid, CurrentRowPos); + + AddNoDataInformation(ToolTipGrid, CurrentRowPos); + + return SNew(SToolTip) + [ + ToolTipGrid + ]; + } + else + { + return SNew(SToolTip) + .Text(LOCTEXT("NotImplemented", "Tooltip for multiple profiler instances has not been implemented yet")); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersViewTooltip::AddNoDataInformation(const TSharedRef& Grid, int32& RowPos) +{ + Grid->AddSlot(0, RowPos) + .Padding(2.0f) + .ColumnSpan(3) + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + .Text(LOCTEXT("NoDataAvailable", "N/A")) + ]; + RowPos++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersViewTooltip::AddHeader(const TSharedRef& Grid, int32& RowPos) +{ + const FString InstanceName = Session->GetName(); + + Grid->AddSlot(0, RowPos++) + .Padding(2.0f) + .ColumnSpan(3) + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + .Text(LOCTEXT("StatInstance", "Stat information for profiler instance")) + ]; + + Grid->AddSlot(0, RowPos++) + .Padding(2.0f) + .ColumnSpan(3) + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + .Text(FText::FromString(InstanceName)) + ]; + + AddSeparator(Grid, RowPos); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersViewTooltip::AddDescription(const TSharedRef& Grid, int32& RowPos) +{ + /* + const FProfilerStat& ProfilerStat = Session->GetMetaData()->GetTimerById(TimerId); + const ETimerNodeType SampleType = Session->GetMetaData()->GetSampleTypeForTimerId(TimerId); + const FSlateBrush* const StatIcon = STimersViewHelper::GetIconForStatType(SampleType); + + Grid->AddSlot(0, RowPos) + .Padding(2.0f) + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + .Text(LOCTEXT("GroupDesc","Group:")) + ]; + + Grid->AddSlot(1, RowPos) + .Padding(2.0f) + .ColumnSpan(2) + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + .Text(FText::FromName(ProfilerStat.OwningGroup().Name())) + ]; + RowPos++; + + Grid->AddSlot(0, RowPos) + .Padding(2.0f) + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + .Text(LOCTEXT("NameDesc","Name:")) + ]; + + Grid->AddSlot(1, RowPos) + .Padding(2.0f) + .ColumnSpan(2) + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + .Text(FText::FromName(ProfilerStat.Name())) + ]; + RowPos++; + + Grid->AddSlot(0, RowPos) + .Padding(2.0f) + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + .Text(LOCTEXT("TypeDesc","Type:")) + ]; + + Grid->AddSlot(1, RowPos) + .Padding(2.0f) + .ColumnSpan(2) + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SImage) + .Image(StatIcon) + ] + + +SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString(ETimerNodeType::ToDescription(SampleType))) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + ] + ]; + RowPos++; + + AddSeparator(Grid, RowPos); + */ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersViewTooltip::AddSeparator(const TSharedRef& Grid, int32& RowPos) +{ + Grid->AddSlot(0, RowPos++) + .Padding(2.0f) + .ColumnSpan(3) + [ + SNew(SSeparator) + .Orientation(Orient_Horizontal) + ]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimersViewTooltip::AddStatsRow(TSharedPtr Grid, int32& Row, const FText& Name, const FText& Value1, const FText& Value2) +{ + Grid->AddSlot(0, Row) + .Padding(2.0f) + [ + SNew(STextBlock) + .Text(Name) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.TooltipBold")) + ]; + + Grid->AddSlot(1, Row) + .Padding(2.0f) + .HAlign(HAlign_Right) + [ + SNew(STextBlock) + .Text(Value1) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + ]; + + Grid->AddSlot(2, Row) + .Padding(2.0f) + .HAlign(HAlign_Right) + [ + SNew(STextBlock) + .Text(Value2) + .TextStyle(FEditorStyle::Get(), TEXT("Profiler.Tooltip")) + ]; + + Row++; +} + +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimersViewTooltip.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimersViewTooltip.h new file mode 100644 index 000000000000..45e70a2e3a23 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimersViewTooltip.h @@ -0,0 +1,53 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "EditorStyleSet.h" +#include "SlateOptMacros.h" +#include "TraceServices/AnalysisService.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SGridPanel.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/SToolTip.h" +#include "Widgets/Text/STextBlock.h" + +// Insights +#include "Insights/InsightsManager.h" + +// Insights +class FTimerNode; +class FTimersViewColumn; + +#define LOCTEXT_NAMESPACE "STimersView" + +/** Timers View Tooltip */ +class STimersViewTooltip +{ + const uint32 TimerId; + TSharedPtr Session; + +public: + STimersViewTooltip(const uint32 InTimerId) + : TimerId(InTimerId) + { + Session = FInsightsManager::Get()->GetSession(); + } + + TSharedRef GetTooltip(); + +protected: + void AddNoDataInformation(const TSharedRef& Grid, int32& RowPos); + void AddHeader(const TSharedRef& Grid, int32& RowPos); + void AddDescription(const TSharedRef& Grid, int32& RowPos); + void AddSeparator(const TSharedRef& Grid, int32& RowPos); + +public: + static TSharedPtr GetColumnTooltip(const FTimersViewColumn& Column); + static TSharedPtr GetTableCellTooltip(const TSharedPtr TimerNodePtr); + +protected: + static void AddStatsRow(TSharedPtr Grid, int32& Row, const FText& Name, const FText& Value1, const FText& Value2); +}; + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingProfilerToolbar.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingProfilerToolbar.cpp new file mode 100644 index 000000000000..079a217ffdf3 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingProfilerToolbar.cpp @@ -0,0 +1,155 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "STimingProfilerToolbar.h" + +#include "EditorStyleSet.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/Docking/TabManager.h" +#include "Framework/MultiBox/MultiBoxDefs.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Widgets/Docking/SDockTab.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/SBoxPanel.h" + +// Insights +#include "Insights/InsightsCommands.h" +#include "Insights/InsightsManager.h" +#include "Insights/TimingProfilerCommands.h" +#include "Insights/TimingProfilerManager.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define LOCTEXT_NAMESPACE "STimingProfilerToolbar" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +STimingProfilerToolbar::STimingProfilerToolbar() +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +STimingProfilerToolbar::~STimingProfilerToolbar() +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingProfilerToolbar::Construct(const FArguments& InArgs) +{ + CreateCommands(); + + struct Local + { + static void FillToolbar1(FToolBarBuilder& ToolbarBuilder) + { + //ToolbarBuilder.BeginSection("Session"); + //{ + // ToolbarBuilder.AddToolBarButton(FInsightsCommands::Get().InsightsManager_Live); + // ToolbarBuilder.AddToolBarButton(FInsightsCommands::Get().InsightsManager_Load); + //} + //ToolbarBuilder.EndSection(); + ToolbarBuilder.BeginSection("View"); + { + ToolbarBuilder.AddToolBarButton(FTimingProfilerCommands::Get().ToggleFramesTrackVisibility); + ToolbarBuilder.AddToolBarButton(FTimingProfilerCommands::Get().ToggleGraphTrackVisibility); + ToolbarBuilder.AddToolBarButton(FTimingProfilerCommands::Get().ToggleTimingViewVisibility); + ToolbarBuilder.AddToolBarButton(FTimingProfilerCommands::Get().ToggleTimersViewVisibility); + ToolbarBuilder.AddToolBarButton(FTimingProfilerCommands::Get().ToggleStatsCountersViewVisibility); + ToolbarBuilder.AddToolBarButton(FTimingProfilerCommands::Get().ToggleLogViewVisibility); + } + ToolbarBuilder.EndSection(); + //ToolbarBuilder.BeginSection("Options"); + //{ + // ToolbarBuilder.AddToolBarButton(FInsightsCommands::Get().OpenSettings); + //} + //ToolbarBuilder.EndSection(); + } + + static void FillToolbar2(FToolBarBuilder& ToolbarBuilder) + { + ToolbarBuilder.BeginSection("Options"); + { + ToolbarBuilder.AddToolBarButton(FInsightsCommands::Get().ToggleDebugInfo); + } + ToolbarBuilder.EndSection(); + } + }; + + TSharedPtr CommandList = FInsightsManager::Get()->GetCommandList(); + + FToolBarBuilder ToolbarBuilder1(CommandList.ToSharedRef(), FMultiBoxCustomization::None); + Local::FillToolbar1(ToolbarBuilder1); + + FToolBarBuilder ToolbarBuilder2(CommandList.ToSharedRef(), FMultiBoxCustomization::None); + Local::FillToolbar2(ToolbarBuilder2); + + // Create the tool bar! + ChildSlot + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .FillWidth(1.0) + .Padding(0.0f) + [ + SNew(SBorder) + .Padding(0) + .BorderImage(FEditorStyle::GetBrush("NoBorder")) + .IsEnabled(FSlateApplication::Get().GetNormalExecutionAttribute()) + [ + ToolbarBuilder1.MakeWidget() + ] + ] + + +SHorizontalBox::Slot() + .HAlign(HAlign_Right) + .VAlign(VAlign_Center) + .FillWidth(1.0) + .Padding(0.0f) + [ + SNew(SBorder) + .Padding(0) + .BorderImage(FEditorStyle::GetBrush("NoBorder")) + .IsEnabled(FSlateApplication::Get().GetNormalExecutionAttribute()) + [ + ToolbarBuilder2.MakeWidget() + ] + ] + ]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingProfilerToolbar::ShowStats() +{ + // do nothing +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingProfilerToolbar::ShowMemory() +{ + // do nothing +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingProfilerToolbar::CreateCommands() +{ + //TSharedPtr ProfilerCommandList = FTimingProfilerManager::Get()->GetCommandList(); + //const FTimingProfilerCommands& Commands = FTimingProfilerCommands::Get(); + // + //// Stats command + //ProfilerCommandList->MapAction(Commands.StatsProfiler, + // FExecuteAction::CreateRaw(this, &STimingProfilerToolbar::ShowStats), + // FCanExecuteAction(), + // FIsActionChecked::CreateRaw(this, &STimingProfilerToolbar::IsShowingStats) + //); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingProfilerToolbar.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingProfilerToolbar.h new file mode 100644 index 000000000000..9a79171cbf4c --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingProfilerToolbar.h @@ -0,0 +1,55 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#pragma once + +#include "CoreMinimal.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" + +class SBorder; + +/** Ribbon based toolbar used as a main menu in the Profiler window. */ +class STimingProfilerToolbar : public SCompoundWidget +{ +public: + /** Default constructor. */ + STimingProfilerToolbar(); + + /** Virtual destructor. */ + virtual ~STimingProfilerToolbar(); + + SLATE_BEGIN_ARGS( STimingProfilerToolbar ) + {} + SLATE_END_ARGS() + + /** + * Construct this widget + * + * @param InArgs The declaration data for this widget + */ + void Construct( const FArguments& InArgs ); + + bool IsImplemented() const { return false; } + bool IsShowingStats() const { return true; } + bool IsShowingMemory() const { return false; } + +protected: + /** Called when the list of sessions has changed. @see FProfilerManager.FOnSessionsUpdated */ + void ProfilerManager_SessionsUpdated(); + + /** Create the UI commands for the toolbar */ + void CreateCommands(); + + /** Shows the stats profiler */ + void ShowStats(); + + /** Shows the memory profiler */ + void ShowMemory(); + + /** Shows the FPSChart view */ + void ShowFPSChart(); + +protected: + TSharedPtr Border; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingProfilerWindow.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingProfilerWindow.cpp new file mode 100644 index 000000000000..51e040b501b2 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingProfilerWindow.cpp @@ -0,0 +1,506 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "STimingProfilerWindow.h" + +#include "EditorStyleSet.h" +#include "Framework/Docking/WorkspaceItem.h" +#include "SlateOptMacros.h" +#include "Widgets/Docking/SDockTab.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Layout/SSpacer.h" +#include "Widgets/Notifications/SNotificationList.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Text/STextBlock.h" +//#include "WorkspaceMenuStructure.h" +//#include "WorkspaceMenuStructureModule.h" + +#if WITH_EDITOR + #include "EngineAnalytics.h" + #include "Runtime/Analytics/Analytics/Public/AnalyticsEventAttribute.h" + #include "Runtime/Analytics/Analytics/Public/Interfaces/IAnalyticsProvider.h" +#endif // WITH_EDITOR + +// Insights +#include "Insights/InsightsManager.h" +#include "Insights/InsightsStyle.h" +#include "Insights/TimingProfilerManager.h" +#include "Insights/Version.h" +#include "Insights/Widgets/SFrameTrack.h" +#include "Insights/Widgets/SGraphTrack.h" +#include "Insights/Widgets/SInsightsSettings.h" +#include "Insights/Widgets/SLogView.h" +#include "Insights/Widgets/SStatsView.h" +#include "Insights/Widgets/STimersView.h" +#include "Insights/Widgets/STimingProfilerToolbar.h" +#include "Insights/Widgets/STimingView.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define LOCTEXT_NAMESPACE "STimingProfilerWindow" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FName FTimingProfilerTabs::ToolbarID(TEXT("Toolbar")); +const FName FTimingProfilerTabs::FramesTrackID(TEXT("Frames")); +const FName FTimingProfilerTabs::GraphTrackID(TEXT("Graph")); +const FName FTimingProfilerTabs::TimingViewID(TEXT("TimingView")); +const FName FTimingProfilerTabs::TimersID(TEXT("Timers")); +const FName FTimingProfilerTabs::StatsCountersID(TEXT("StasCounters")); +const FName FTimingProfilerTabs::LogViewID(TEXT("LogView")); + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +STimingProfilerWindow::STimingProfilerWindow() + : DurationActive(0.0f) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +STimingProfilerWindow::~STimingProfilerWindow() +{ +#if WITH_EDITOR + if (DurationActive > 0.0f && FEngineAnalytics::IsAvailable()) + { + FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.Profiler"), FAnalyticsEventAttribute(TEXT("Duration"), DurationActive)); + } +#endif // WITH_EDITOR +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + +TSharedRef STimingProfilerWindow::SpawnTab_Toolbar(const FSpawnTabArgs& Args) +{ + return SNew(SDockTab) + .ShouldAutosize(true) + .TabRole(ETabRole::PanelTab) + //.IsEnabled(this, &STimingProfilerWindow::IsProfilerEnabled) + [ + SNew(STimingProfilerToolbar) + ]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef STimingProfilerWindow::SpawnTab_FramesTrack(const FSpawnTabArgs& Args) +{ + FTimingProfilerManager::Get()->SetFramesTrackVisible(true); + + return SNew(SDockTab) + .ShouldAutosize(false) + .TabRole(ETabRole::PanelTab) + //.IsEnabled(this, &STimingProfilerWindow::IsProfilerEnabled) + [ + SAssignNew(FrameTrack, SFrameTrack) + ]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef STimingProfilerWindow::SpawnTab_GraphTrack(const FSpawnTabArgs& Args) +{ + FTimingProfilerManager::Get()->SetGraphTrackVisible(true); + + return SNew(SDockTab) + .ShouldAutosize(false) + .TabRole(ETabRole::PanelTab) + //.IsEnabled(this, &STimingProfilerWindow::IsProfilerEnabled) + [ + SAssignNew(GraphTrack, SGraphTrack) + ]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef STimingProfilerWindow::SpawnTab_TimingView(const FSpawnTabArgs& Args) +{ + FTimingProfilerManager::Get()->SetTimingViewVisible(true); + + return SNew(SDockTab) + .ShouldAutosize(false) + .TabRole(ETabRole::PanelTab) + //.IsEnabled(this, &STimingProfilerWindow::IsProfilerEnabled) + [ + SAssignNew(TimingView, STimingView) + ]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef STimingProfilerWindow::SpawnTab_Timers(const FSpawnTabArgs& Args) +{ + FTimingProfilerManager::Get()->SetTimersViewVisible(true); + + return SNew(SDockTab) + .ShouldAutosize(false) + .TabRole(ETabRole::PanelTab) + //.IsEnabled(this, &STimingProfilerWindow::IsProfilerEnabled) + [ + SAssignNew(TimersView, STimersView) + ]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef STimingProfilerWindow::SpawnTab_StatsCounters(const FSpawnTabArgs& Args) +{ + FTimingProfilerManager::Get()->SetStatsCountersViewVisible(true); + + return SNew(SDockTab) + .ShouldAutosize(false) + .TabRole(ETabRole::PanelTab) + //.IsEnabled(this, &STimingProfilerWindow::IsProfilerEnabled) + [ + SAssignNew(StatsView, SStatsView) + ]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef STimingProfilerWindow::SpawnTab_LogView(const FSpawnTabArgs& Args) +{ + FTimingProfilerManager::Get()->SetLogViewVisible(true); + + return SNew(SDockTab) + .ShouldAutosize(false) + .TabRole(ETabRole::PanelTab) + //.IsEnabled(this, &STimingProfilerWindow::IsProfilerEnabled) + [ + SAssignNew(LogView, SLogView) + ]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingProfilerWindow::Construct(const FArguments& InArgs, const TSharedRef& ConstructUnderMajorTab, const TSharedPtr& ConstructUnderWindow) +{ + // Create & initialize tab manager. + TabManager = FGlobalTabmanager::Get()->NewTabManager(ConstructUnderMajorTab); + TSharedRef AppMenuGroup = TabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("TimingProfilerMenuGroupName", "Timing Insights")); + + TabManager->RegisterTabSpawner(FTimingProfilerTabs::ToolbarID, FOnSpawnTab::CreateRaw(this, &STimingProfilerWindow::SpawnTab_Toolbar)) + .SetDisplayName(LOCTEXT("DeviceToolbarTabTitle", "Toolbar")) + .SetIcon(FSlateIcon(FInsightsStyle::GetStyleSetName(), "Toolbar.Icon.Small")) + .SetGroup(AppMenuGroup); + + TabManager->RegisterTabSpawner(FTimingProfilerTabs::FramesTrackID, FOnSpawnTab::CreateRaw(this, &STimingProfilerWindow::SpawnTab_FramesTrack)) + .SetDisplayName(LOCTEXT("FramesTrackTabTitle", "Frames")) + .SetIcon(FSlateIcon(FInsightsStyle::GetStyleSetName(), "FramesTrack.Icon.Small")) + .SetGroup(AppMenuGroup); + + TabManager->RegisterTabSpawner(FTimingProfilerTabs::GraphTrackID, FOnSpawnTab::CreateRaw(this, &STimingProfilerWindow::SpawnTab_GraphTrack)) + .SetDisplayName(LOCTEXT("GraphTrackTabTitle", "Graph")) + .SetIcon(FSlateIcon(FInsightsStyle::GetStyleSetName(), "GraphTrack.Icon.Small")) + .SetGroup(AppMenuGroup); + + TabManager->RegisterTabSpawner(FTimingProfilerTabs::TimingViewID, FOnSpawnTab::CreateRaw(this, &STimingProfilerWindow::SpawnTab_TimingView)) + .SetDisplayName(LOCTEXT("TimingViewTabTitle", "Timing View")) + .SetIcon(FSlateIcon(FInsightsStyle::GetStyleSetName(), "TimingView.Icon.Small")) + .SetGroup(AppMenuGroup); + + TabManager->RegisterTabSpawner(FTimingProfilerTabs::TimersID, FOnSpawnTab::CreateRaw(this, &STimingProfilerWindow::SpawnTab_Timers)) + .SetDisplayName(LOCTEXT("TimersTabTitle", "Timers")) + .SetIcon(FSlateIcon(FInsightsStyle::GetStyleSetName(), "TimersView.Icon.Small")) + .SetGroup(AppMenuGroup); + + TabManager->RegisterTabSpawner(FTimingProfilerTabs::StatsCountersID, FOnSpawnTab::CreateRaw(this, &STimingProfilerWindow::SpawnTab_StatsCounters)) + .SetDisplayName(LOCTEXT("StatsCountersTabTitle", "Stats Counters")) + .SetIcon(FSlateIcon(FInsightsStyle::GetStyleSetName(), "StatsCountersView.Icon.Small")) + .SetGroup(AppMenuGroup); + + TabManager->RegisterTabSpawner(FTimingProfilerTabs::LogViewID, FOnSpawnTab::CreateRaw(this, &STimingProfilerWindow::SpawnTab_LogView)) + .SetDisplayName(LOCTEXT("LogViewTabTitle", "Log View")) + .SetIcon(FSlateIcon(FInsightsStyle::GetStyleSetName(), "LogView.Icon.Small")) + .SetGroup(AppMenuGroup); + + TSharedPtr TimingProfilerManager = FTimingProfilerManager::Get(); + ensure(TimingProfilerManager.IsValid()); + + // Create tab layout. + const TSharedRef Layout = FTabManager::NewLayout("InsightsTimingProfilerLayout_v1.0") + ->AddArea + ( + FTabManager::NewPrimaryArea() + ->SetOrientation(Orient_Vertical) + ->Split + ( + FTabManager::NewStack() + ->AddTab(FTimingProfilerTabs::ToolbarID, ETabState::OpenedTab) + ->SetHideTabWell(true) + ) + ->Split + ( + FTabManager::NewSplitter() + ->SetOrientation(Orient_Horizontal) + ->SetSizeCoefficient(1.0f) + ->Split + ( + FTabManager::NewSplitter() + ->SetOrientation(Orient_Vertical) + ->SetSizeCoefficient(0.65f) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient(0.1f) + ->SetHideTabWell(true) + ->AddTab(FTimingProfilerTabs::FramesTrackID, TimingProfilerManager->IsFramesTrackVisible() ? ETabState::OpenedTab : ETabState::ClosedTab) + ) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient(0.2f) + ->SetHideTabWell(true) + ->AddTab(FTimingProfilerTabs::GraphTrackID, TimingProfilerManager->IsGraphTrackVisible() ? ETabState::OpenedTab : ETabState::ClosedTab) + ) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient(0.5f) + ->SetHideTabWell(true) + ->AddTab(FTimingProfilerTabs::TimingViewID, TimingProfilerManager->IsTimingViewVisible() ? ETabState::OpenedTab : ETabState::ClosedTab) + ) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient(0.2f) + ->SetHideTabWell(true) + ->AddTab(FTimingProfilerTabs::LogViewID, TimingProfilerManager->IsLogViewVisible() ? ETabState::OpenedTab : ETabState::ClosedTab) + ) + ) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient(0.35f) + ->AddTab(FTimingProfilerTabs::TimersID, TimingProfilerManager->IsTimersViewVisible() ? ETabState::OpenedTab : ETabState::ClosedTab) + ->AddTab(FTimingProfilerTabs::StatsCountersID, TimingProfilerManager->IsStatsCountersViewVisible() ? ETabState::OpenedTab : ETabState::ClosedTab) + ->SetForegroundTab(FTimingProfilerTabs::TimersID) + ) + ) + ); + + // Create & initialize main menu. + FMenuBarBuilder MenuBarBuilder = FMenuBarBuilder(TSharedPtr()); + + MenuBarBuilder.AddPullDownMenu( + LOCTEXT("MenuLabel", "MENU"), + FText::GetEmpty(), + FNewMenuDelegate::CreateStatic(&STimingProfilerWindow::FillMenu, TabManager), + FName(TEXT("MENU")) + ); + + ChildSlot + [ + SNew(SOverlay) + + // Version + + SOverlay::Slot() + .HAlign(HAlign_Right) + .VAlign(VAlign_Top) + .Padding(0.0f, -16.0f, 0.0f, 0.0f) + [ + SNew(STextBlock) + .Clipping(EWidgetClipping::ClipToBoundsWithoutIntersecting) + .Text(LOCTEXT("UnrealInsightsVersion", UNREAL_INSIGHTS_VERSION_STRING_EX)) + .ColorAndOpacity(FLinearColor(0.15f, 0.15f, 0.15f, 1.0f)) + ] + + // Overlay slot for the main profiler window area + + SOverlay::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + [ + SNew(SVerticalBox) + + + SVerticalBox::Slot() + .AutoHeight() + [ + MenuBarBuilder.MakeWidget() + ] + + + SVerticalBox::Slot() + .FillHeight(1.0f) + [ + TabManager->RestoreFrom(Layout, ConstructUnderWindow).ToSharedRef() + ] + ] + + // Session hint overlay + + SOverlay::Slot() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(SBorder) + .Visibility(this, &STimingProfilerWindow::IsSessionOverlayVisible) + .BorderImage(FEditorStyle::GetBrush("NotificationList.ItemBackground")) + .Padding(8.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("SelectTraceOverlayText", "Please select a trace.")) + ] + ] + ]; + + // Tell tab-manager about the global menu bar. + TabManager->SetMenuMultiBox(MenuBarBuilder.GetMultiBox()); +} + +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingProfilerWindow::FillMenu(FMenuBuilder& MenuBuilder, const TSharedPtr TabManager) +{ + if (!TabManager.IsValid()) + { + return; + } + +#if !WITH_EDITOR + //TODO: FGlobalTabmanager::Get()->PopulateTabSpawnerMenu(MenuBuilder, WorkspaceMenu::GetMenuStructure().GetStructureRoot()); +#endif //!WITH_EDITOR + + TabManager->PopulateLocalTabSpawnerMenu(MenuBuilder); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingProfilerWindow::ShowTab(const FName& TabID) +{ + if (TabManager->HasTabSpawner(TabID)) + { + TabManager->InvokeTab(TabID); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingProfilerWindow::HideTab(const FName& TabID) +{ + TSharedPtr Tab = TabManager->FindExistingLiveTab(TabID); + if (Tab.IsValid()) + { + Tab->RequestCloseTab(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +EVisibility STimingProfilerWindow::IsSessionOverlayVisible() const +{ + if (FInsightsManager::Get()->GetSession().IsValid()) + { + return EVisibility::Hidden; + } + else + { + return EVisibility::Visible; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimingProfilerWindow::IsProfilerEnabled() const +{ + return FInsightsManager::Get()->GetSession().IsValid(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +EActiveTimerReturnType STimingProfilerWindow::UpdateActiveDuration(double InCurrentTime, float InDeltaTime) +{ + DurationActive += InDeltaTime; + + // The profiler window will explicitly unregister this active timer when the mouse leaves. + return EActiveTimerReturnType::Continue; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingProfilerWindow::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + SCompoundWidget::OnMouseEnter(MyGeometry, MouseEvent); + + if (!ActiveTimerHandle.IsValid()) + { + ActiveTimerHandle = RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &STimingProfilerWindow::UpdateActiveDuration)); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingProfilerWindow::OnMouseLeave(const FPointerEvent& MouseEvent) +{ + SCompoundWidget::OnMouseLeave(MouseEvent); + + auto PinnedActiveTimerHandle = ActiveTimerHandle.Pin(); + if (PinnedActiveTimerHandle.IsValid()) + { + UnRegisterActiveTimer(PinnedActiveTimerHandle.ToSharedRef()); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply STimingProfilerWindow::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + return FTimingProfilerManager::Get()->GetCommandList()->ProcessCommandBindings(InKeyEvent) ? FReply::Handled() : FReply::Unhandled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply STimingProfilerWindow::OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) +{ + TSharedPtr DragDropOp = DragDropEvent.GetOperationAs(); + if (DragDropOp.IsValid()) + { + if (DragDropOp->HasFiles()) + { + const TArray& Files = DragDropOp->GetFiles(); + if (Files.Num() == 1) + { + const FString DraggedFileExtension = FPaths::GetExtension(Files[0], true); + if (DraggedFileExtension == TEXT(".utrace")) + { + return FReply::Handled(); + } + } + } + } + + return SCompoundWidget::OnDragOver(MyGeometry,DragDropEvent); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply STimingProfilerWindow::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) +{ + TSharedPtr DragDropOp = DragDropEvent.GetOperationAs(); + if (DragDropOp.IsValid()) + { + if (DragDropOp->HasFiles()) + { + // For now, only allow a single file. + const TArray& Files = DragDropOp->GetFiles(); + if (Files.Num() == 1) + { + const FString DraggedFileExtension = FPaths::GetExtension(Files[0], true); + if (DraggedFileExtension == TEXT(".utrace")) + { + // Enqueue load operation. + FInsightsManager::Get()->LoadTraceFile(Files[0]); + return FReply::Handled(); + } + } + } + } + + return SCompoundWidget::OnDrop(MyGeometry,DragDropEvent); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingProfilerWindow.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingProfilerWindow.h new file mode 100644 index 000000000000..b7e30e0e4afc --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingProfilerWindow.h @@ -0,0 +1,175 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Docking/TabManager.h" +#include "Input/Reply.h" +#include "Layout/Visibility.h" +#include "Misc/Guid.h" +#include "SlateFwd.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/Docking/SDockTab.h" +#include "Widgets/SCompoundWidget.h" + +// Insights +#include "Insights/InsightsManager.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +class FActiveTimerHandle; +class SVerticalBox; +class SFrameTrack; +class SGraphTrack; +class SStatsView; +class STimersView; +class STimingView; +class SLogView; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct FTimingProfilerTabs +{ + // Tab identifiers + static const FName ToolbarID; + static const FName FramesTrackID; + static const FName GraphTrackID; + static const FName TimingViewID; + static const FName TimersID; + static const FName StatsCountersID; + static const FName LogViewID; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** Implements the timing profiler window. */ +class STimingProfilerWindow : public SCompoundWidget +{ +public: + /** Default constructor. */ + STimingProfilerWindow(); + + /** Virtual destructor. */ + virtual ~STimingProfilerWindow(); + + SLATE_BEGIN_ARGS(STimingProfilerWindow){} + SLATE_END_ARGS() + + /** Constructs this widget. */ + void Construct(const FArguments& InArgs, const TSharedRef& ConstructUnderMajorTab, const TSharedPtr& ConstructUnderWindow); + + void ShowTab(const FName& TabID); + void HideTab(const FName& TabID); + void ShowHideTab(const FName& TabID, bool bShow) { bShow ? ShowTab(TabID) : HideTab(TabID); } + + TSharedPtr GetTabManager() const { return TabManager; } + +private: + TSharedRef SpawnTab_Toolbar(const FSpawnTabArgs& Args); + TSharedRef SpawnTab_FramesTrack(const FSpawnTabArgs& Args); + TSharedRef SpawnTab_GraphTrack(const FSpawnTabArgs& Args); + TSharedRef SpawnTab_TimingView(const FSpawnTabArgs& Args); + TSharedRef SpawnTab_Timers(const FSpawnTabArgs& Args); + TSharedRef SpawnTab_StatsCounters(const FSpawnTabArgs& Args); + TSharedRef SpawnTab_LogView(const FSpawnTabArgs& Args); + + /** + * Fill the main menu with menu items. + * + * @param MenuBuilder The multi-box builder that should be filled with content for this pull-down menu. + * @param TabManager A Tab Manager from which to populate tab spawner menu items. + */ + static void FillMenu(FMenuBuilder& MenuBuilder, const TSharedPtr TabManager); + + /** Callback for determining the visibility of the 'Select a session' overlay. */ + EVisibility IsSessionOverlayVisible() const; + + /** Callback for getting the enabled state of the profiler window. */ + bool IsProfilerEnabled() const; + + /** Updates the amount of time the profiler has been active. */ + EActiveTimerReturnType UpdateActiveDuration(double InCurrentTime, float InDeltaTime); + + /** + * Ticks this widget. Override in derived classes, but always call the parent implementation. + * + * @param AllottedGeometry The space allotted for this widget + * @param InCurrentTime Current absolute real time + * @param InDeltaTime Real time passed since last tick + */ + //virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; + + /** + * The system will use this event to notify a widget that the cursor has entered it. This event is NOT bubbled. + * + * @param MyGeometry The Geometry of the widget receiving the event + * @param MouseEvent Information about the input event + */ + virtual void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + /** + * The system will use this event to notify a widget that the cursor has left it. This event is NOT bubbled. + * + * @param MouseEvent Information about the input event + */ + virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override; + + /** + * Called after a key is pressed when this widget has focus + * + * @param MyGeometry The Geometry of the widget receiving the event + * @param InKeyEvent Key event + * + * @return Returns whether the event was handled, along with other possible actions + */ + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; + + /** + * Called when the user is dropping something onto a widget; terminates drag and drop. + * + * @param MyGeometry The geometry of the widget receiving the event. + * @param DragDropEvent The drag and drop event. + * + * @return A reply that indicated whether this event was handled. + */ + virtual FReply OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override; + + /** + * Called during drag and drop when the the mouse is being dragged over a widget. + * + * @param MyGeometry The geometry of the widget receiving the event. + * @param DragDropEvent The drag and drop event. + * + * @return A reply that indicated whether this event was handled. + */ + virtual FReply OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override; + +public: + /** Widget for the frame track */ + TSharedPtr FrameTrack; + + /** Widget for the graph track */ + TSharedPtr GraphTrack; + + /** Widget for the timing track */ + TSharedPtr TimingView; + + /** Holds the Timers view widget. */ + TSharedPtr TimersView; + + /** Holds the Stats (Counters) view widget. */ + TSharedPtr StatsView; + + /** Widget for the log view */ + TSharedPtr LogView; + + /** The number of seconds the profiler has been active */ + float DurationActive; + +private: + /** Holds the tab manager that manages the front-end's tabs. */ + TSharedPtr TabManager; + + /** The handle to the active update duration tick */ + TWeakPtr ActiveTimerHandle; +}; diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingView.cpp b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingView.cpp new file mode 100644 index 000000000000..e7d291f6bac3 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingView.cpp @@ -0,0 +1,3806 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "STimingView.h" + +#include "Brushes/SlateBorderBrush.h" +#include "Brushes/SlateBoxBrush.h" +#include "Brushes/SlateColorBrush.h" +#include "Containers/ArrayBuilder.h" +#include "Containers/MapBuilder.h" +#include "EditorStyleSet.h" +#include "Fonts/FontMeasure.h" +#include "Fonts/SlateFontInfo.h" +#include "Framework/Application/MenuStack.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "HAL/PlatformTime.h" +#include "Layout/WidgetPath.h" +#include "Misc/Paths.h" +#include "Rendering/DrawElements.h" +#include "SlateOptMacros.h" +#include "Styling/CoreStyle.h" +#include "TraceServices/AnalysisService.h" +#include "TraceServices/SessionService.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SComboButton.h" +#include "Widgets/Layout/SScrollBar.h" +#include "Widgets/Layout/SSpacer.h" +#include "Widgets/SOverlay.h" +#include "Widgets/Text/STextBlock.h" + +// Insights +#include "Insights/Common/PaintUtils.h" +#include "Insights/Common/Stopwatch.h" +#include "Insights/Common/TimeUtils.h" +#include "Insights/InsightsManager.h" +#include "Insights/TimingProfilerCommon.h" +#include "Insights/TimingProfilerManager.h" +#include "Insights/ViewModels/BaseTimingTrack.h" +#include "Insights/ViewModels/TimingViewDrawHelper.h" +#include "Insights/Widgets/SStatsView.h" +#include "Insights/Widgets/STimersView.h" +#include "Insights/Widgets/STimingProfilerWindow.h" + +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define DEBUG_TIMING_TRACK 0 +#define LOCTEXT_NAMESPACE "STimingView" + +// start auto generated ids from a big number (MSB set to 1) to avoid collisions with ids for gpu/cpu tracks based on 32bit timeline index +uint64 FBaseTimingTrack::IdGenerator = (1ULL << 63); + +const TCHAR* GetName(ELoadTimeProfilerPackageEventType Type); +const TCHAR* GetName(ELoadTimeProfilerObjectEventType Type); + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +STimingView::STimingView() + : bAssetLoadingMode(false) + , TimeRulerTrack(FBaseTimingTrack::GenerateId()) + , MarkersTrack(FBaseTimingTrack::GenerateId()) + , GraphTrack(FBaseTimingTrack::GenerateId()) + , TooltipWidth(MinTooltipWidth) + , TooltipAlpha(0.0f) + , WhiteBrush(FCoreStyle::Get().GetBrush("WhiteBrush")) + , MainFont(FCoreStyle::GetDefaultFontStyle("Regular", 8)) +{ + Reset(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +STimingView::~STimingView() +{ + for (const auto& KV : CachedTimelines) + { + delete KV.Value; + } + CachedTimelines.Reset(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::Construct(const FArguments& InArgs) +{ + OnSelectionChanged = InArgs._OnSelectionChanged; + OnHoveredEventChanged = InArgs._OnHoveredEventChanged; + OnSelectedEventChanged = InArgs._OnSelectedEventChanged; + + ChildSlot + [ + SNew(SOverlay) + .Visibility(EVisibility::SelfHitTestInvisible) + + + SOverlay::Slot() + .VAlign(VAlign_Bottom) + .Padding(FMargin(0, 0, 12, 0)) + [ + SAssignNew(HorizontalScrollBar, SScrollBar) + .Orientation(Orient_Horizontal) + .AlwaysShowScrollbar(false) + .Visibility(EVisibility::Visible) + .Thickness(FVector2D(5.0f, 5.0f)) + //.RenderOpacity(0.75) + .OnUserScrolled(this, &STimingView::HorizontalScrollBar_OnUserScrolled) + ] + + + SOverlay::Slot() + .HAlign(HAlign_Right) + .Padding(FMargin(0, 22, 0, 12)) + [ + SAssignNew(VerticalScrollBar, SScrollBar) + .Orientation(Orient_Vertical) + .AlwaysShowScrollbar(false) + .Visibility(EVisibility::Visible) + .Thickness(FVector2D(5.0f, 5.0f)) + //.RenderOpacity(0.75) + .OnUserScrolled(this, &STimingView::VerticalScrollBar_OnUserScrolled) + ] + + + SOverlay::Slot() + .HAlign(HAlign_Left) + .VAlign(VAlign_Top) + .Padding(FMargin(0.0f, 0.0f, 0.0f, 0.0f)) + [ + // Tracks Filter + SNew(SComboButton) + .ComboButtonStyle(FEditorStyle::Get(), "GenericFilters.ComboButtonStyle") + .ForegroundColor(FLinearColor::White) + .ToolTipText(LOCTEXT("TracksFilterToolTip", "Filter timing tracks.")) + .OnGetMenuContent(this, &STimingView::MakeTracksFilterMenu) + .HasDownArrow(true) + .ContentPadding(FMargin(1.0f, 1.0f, 1.0f, 1.0f)) + .ButtonContent() + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .Padding(0.0f) + .AutoWidth() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "GenericFilters.TextStyle") + .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.9")) + .Text(FText::FromString(FString(TEXT("\xf0b0"))) /*fa-filter*/) + ] + + +SHorizontalBox::Slot() + .Padding(2.0f, 0.0f, 0.0f, 0.0f) + .AutoWidth() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "GenericFilters.TextStyle") + .Text(LOCTEXT("TracksFilter", "Tracks")) + ] + ] + ] + ]; + + UpdateHorizontalScrollBar(); + UpdateVerticalScrollBar(); + + BindCommands(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::Reset() +{ + bShowHideAllGpuTracks = true; + bShowHideAllCpuTracks = true; + bShowHideAllLoadingTracks = false; + bShowHideAllIoTracks = false; + + ////////////////////////////////////////////////// + + Viewport.Reset(); + + bIsViewportDirty = true; + bIsVerticalViewportDirty = true; + + ////////////////////////////////////////////////// + + for (const auto& KV : CachedTimelines) + { + delete KV.Value; + } + CachedTimelines.Reset(); + + //TODO: TopTracks.Reset(); + //TODO: ScrollableTracks.Reset(); + //TODO: BottomTracks.Reset(); + //TODO: ForegroundTracks.Reset(); + + ////////////////////////////////////////////////// + + TimingEventsTracks.Reset(); + + bAreTimingEventsTracksDirty = true; + + bUseDownSampling = true; + + ////////////////////////////////////////////////// + + GpuTrack = nullptr; + CpuTracks.Reset(); + + ThreadGroups.Reset(); + + ////////////////////////////////////////////////// + + LoadingMainThreadTrack = nullptr; + LoadingAsyncThreadTrack = nullptr; + + LoadingMainThreadId = 0; + LoadingAsyncThreadId = 0; + + LoadingGetEventNameFn = FLoadingTrackGetEventNameDelegate::CreateRaw(this, &STimingView::GetLoadTimeProfilerEventName); + + EventAggregationStr.Reset(); + ObjectTypeAggregationStr.Reset(); + + IoOverviewTrack = nullptr; + IoActivityTrack = nullptr; + + bForceIoEventsUpdate = true; + bMergeIoLanes = true; + AllIoEvents.Reset(); + //AllIoEvents.Reserve(1000000); + + ////////////////////////////////////////////////// + + TimeRulerTrack.Reset(); + MarkersTrack.Reset(); + + GraphTrack.Reset(); + GraphTrack.SetVisibilityFlag(false); + + ////////////////////////////////////////////////// + + MousePosition = FVector2D::ZeroVector; + + MousePositionOnButtonDown = FVector2D::ZeroVector; + ViewportStartTimeOnButtonDown = 0.0; + ViewportScrollPosYOnButtonDown = 0.0f; + + MousePositionOnButtonUp = FVector2D::ZeroVector; + + bIsLMB_Pressed = false; + bIsRMB_Pressed = false; + + bIsSpaceBarKeyPressed = false; + bIsDragging = false; + + bIsPanning = false; + PanningMode = EPanningMode::None; + + bIsSelecting = false; + + SelectionStartTime = 0.0; + SelectionEndTime = 0.0; + + HoveredTimingEvent.Reset(); + SelectedTimingEvent.Reset(); + + LastSelectionType = ESelectionType::None; + + TimeMarker = std::numeric_limits::infinity(); + + //ThisGeometry + + Layout.ForceNormalMode(); + + ////////////////////////////////////////////////// + + NumUpdatedEvents = 0; + TimelineCacheUpdateDurationHistory.Reset(); + TimelineCacheUpdateDurationHistory.AddValue(0); + TimeMarkerCacheUpdateDurationHistory.Reset(); + TimeMarkerCacheUpdateDurationHistory.AddValue(0); + DrawDurationHistory.Reset(); + OnPaintDurationHistory.Reset(); + LastOnPaintTime = FPlatformTime::Cycles64(); + + if (bAssetLoadingMode) + { + EnableAssetLoadingMode(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::EnableAssetLoadingMode() +{ + bAssetLoadingMode = true; + + bShowHideAllGpuTracks = false; + bShowHideAllCpuTracks = false; + bShowHideAllLoadingTracks = true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) +{ + ThisGeometry = AllottedGeometry; + + const float ViewWidth = AllottedGeometry.GetLocalSize().X; + const float ViewHeight = AllottedGeometry.GetLocalSize().Y; + if (Viewport.UpdateSize(ViewWidth, ViewHeight)) + { + bIsViewportDirty = true; + } + + ////////////////////////////////////////////////// + // Update Y postion of non-scrollable tracks (docked on top). + // Also compute total height (TopOffset) of top docked tracks. + + float TopOffset = 0.0f; + if (TimeRulerTrack.IsVisible()) + { + TimeRulerTrack.SetPosY(TopOffset); + TopOffset += TimeRulerTrack.GetHeight(); + } + if (MarkersTrack.IsVisible()) + { + MarkersTrack.SetPosY(TopOffset); + TopOffset += MarkersTrack.GetHeight(); + } + if (GraphTrack.IsVisible()) + { + GraphTrack.SetPosY(TopOffset); + GraphTrack.SetHeight(200.0f); + TopOffset += GraphTrack.GetHeight(); + } + Viewport.TopOffset = TopOffset; + + ////////////////////////////////////////////////// + + if (!bIsPanning) + { + ////////////////////////////////////////////////// + // Elastic snap to vertical scroll limits. + + const float MinY = 0.0f;// -0.5f * Viewport.Height; + const float MaxY = Viewport.ScrollHeight - Viewport.Height + TopOffset + 7.0f; + const float U = 0.5f; + + float ScrollPosY = Viewport.ScrollPosY; + if (ScrollPosY < MinY) + { + ScrollPosY = ScrollPosY * U + (1.0f - U) * MinY; + if (FMath::IsNearlyEqual(ScrollPosY, MinY, 0.5f)) + { + ScrollPosY = MinY; + } + } + else if (ScrollPosY > MaxY) + { + ScrollPosY = ScrollPosY * U + (1.0f - U) * MaxY; + if (FMath::IsNearlyEqual(ScrollPosY, MaxY, 0.5f)) + { + ScrollPosY = MaxY; + } + if (ScrollPosY < MinY) + { + ScrollPosY = MinY; + } + } + + if (Viewport.ScrollPosY != ScrollPosY) + { + Viewport.ScrollPosY = ScrollPosY; + UpdateVerticalScrollBar(); + bIsVerticalViewportDirty = true; + } + + ////////////////////////////////////////////////// + // Elastic snap to horizontal time limits. + + if (Viewport.EnforceHorizontalScrollLimits(0.5)) // 0.5 is the interpolation factor + { + UpdateHorizontalScrollBar(); + bIsViewportDirty = true; + } + } + + // We need to check if TimersView or StatsView needs to update their lists of timers / counters. + // But, ensure we do not check too often. + static uint64 NextTimestamp = 0; + uint64 Time = FPlatformTime::Cycles64(); + if (Time > NextTimestamp) + { + const uint64 WaitTime = static_cast(0.2 / FPlatformTime::GetSecondsPerCycle64()); // 200ms + NextTimestamp = Time + WaitTime; + + TSharedPtr Wnd = FTimingProfilerManager::Get()->GetProfilerWindow(); + if (Wnd) + { + if (Wnd->TimersView) + { + Wnd->TimersView->RebuildTree(false); + } + if (Wnd->StatsView) + { + Wnd->StatsView->RebuildTree(false); + } + } + } + + UpdateIo(); + + TSharedPtr Session = FInsightsManager::Get()->GetSession(); + if (Session) + { + Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); + + // Check if horizontal scroll area has changed. + double SessionTime = Session->GetDurationSeconds(); + if (SessionTime > Viewport.MaxValidTime && + SessionTime != DBL_MAX && + SessionTime != std::numeric_limits::infinity()) + { + //UE_LOG(TimingProfiler, Log, TEXT("Session Duration: %g"), DT); + Viewport.MaxValidTime = SessionTime; + UpdateHorizontalScrollBar(); + //bIsMaxValidTimeDirty = true; + + if (SessionTime >= Viewport.StartTime && SessionTime <= Viewport.EndTime) + { + bAreTimingEventsTracksDirty = true; + MarkersTrack.SetDirtyFlag(); + } + } + + bool bIsTimingEventsTrackDirty = false; + + if (Trace::ReadLoadTimeProfilerProvider(*Session.Get())) + { + const Trace::ILoadTimeProfilerProvider& LoadTimeProfilerProvider = *Trace::ReadLoadTimeProfilerProvider(*Session.Get()); + + LoadingMainThreadId = LoadTimeProfilerProvider.GetMainThreadId(); + LoadingAsyncThreadId = LoadTimeProfilerProvider.GetAsyncLoadingThreadId(); + + if (LoadingMainThreadTrack == nullptr) + { + uint64 TrackId = FBaseTimingTrack::GenerateId(); + LoadingMainThreadTrack = AddTimingEventsTrack(TrackId, ETimingEventsTrackType::Loading, TEXT("Loading - Main Thread"), nullptr, -3); + LoadingMainThreadTrack->SetVisibilityFlag(bShowHideAllLoadingTracks); + bIsTimingEventsTrackDirty = true; + } + + if (LoadingAsyncThreadTrack == nullptr) + { + uint64 TrackId = FBaseTimingTrack::GenerateId(); + LoadingAsyncThreadTrack = AddTimingEventsTrack(TrackId, ETimingEventsTrackType::Loading, TEXT("Loading - Async Thread"), nullptr, -2); + LoadingAsyncThreadTrack->SetVisibilityFlag(bShowHideAllLoadingTracks); + bIsTimingEventsTrackDirty = true; + } + } + + if (Trace::ReadFileActivityProvider(*Session.Get())) + { + if (IoOverviewTrack == nullptr) + { + // Note: The I/O timelines are just prototypes for now (will be removed once the functionality is moved in analyzer). + const uint64 TrackId = FBaseTimingTrack::GenerateId(); + IoOverviewTrack = AddTimingEventsTrack(TrackId, ETimingEventsTrackType::Io, TEXT("I/O Overview"), nullptr, -1); + IoOverviewTrack->SetVisibilityFlag(bShowHideAllIoTracks); + bIsTimingEventsTrackDirty = true; + } + } + + if (Trace::ReadTimingProfilerProvider(*Session.Get())) + { + const Trace::ITimingProfilerProvider& TimingProfilerProvider = *Trace::ReadTimingProfilerProvider(*Session.Get()); + + // Check if we have a GPU track. + uint32 GpuTimelineIndex; + if (TimingProfilerProvider.GetGpuTimelineIndex(GpuTimelineIndex)) + { + const uint64 TrackId = static_cast(GpuTimelineIndex); + if (!CachedTimelines.Contains(TrackId)) + { + GpuTrack = AddTimingEventsTrack(TrackId, ETimingEventsTrackType::Gpu, TEXT("GPU"), nullptr, 0); + GpuTrack->SetVisibilityFlag(bShowHideAllGpuTracks); + bIsTimingEventsTrackDirty = true; + } + } + + // Check available CPU tracks. + int32 Order = 1; + const Trace::IThreadProvider& ThreadProvider = Trace::ReadThreadProvider(*Session.Get()); + ThreadProvider.EnumerateThreads([this, &Order, &bIsTimingEventsTrackDirty, &TimingProfilerProvider](const Trace::FThreadInfo& ThreadInfo) + { + const TCHAR* GroupName = ThreadInfo.GroupName; + if (GroupName == nullptr) + { + GroupName = ThreadInfo.Name; + } + + bool bIsGroupVisible = bShowHideAllCpuTracks; + if (GroupName != nullptr) + { + if (!ThreadGroups.Contains(GroupName)) + { + ThreadGroups.Add(GroupName, { GroupName, bIsGroupVisible, 0 }); + } + else + { + bIsGroupVisible = ThreadGroups[GroupName].bIsVisible; + } + } + + uint32 CpuTimelineIndex; + if (TimingProfilerProvider.GetCpuThreadTimelineIndex(ThreadInfo.Id, CpuTimelineIndex)) + { + FTimingEventsTrack* Track = nullptr; + const uint64 TrackId = static_cast(CpuTimelineIndex); + + if (!CachedTimelines.Contains(TrackId)) + { + FString TrackName(ThreadInfo.Name && *ThreadInfo.Name ? ThreadInfo.Name : FString::Printf(TEXT("Thread %u"))); + + // Create new Timing Events track for the CPU thread. + Track = AddTimingEventsTrack(TrackId, ETimingEventsTrackType::Cpu, TrackName, GroupName, Order); + + Track->SetThreadId(ThreadInfo.Id); + CpuTracks.Add(ThreadInfo.Id, Track); + + FThreadGroup& ThreadGroup = ThreadGroups[GroupName]; + ThreadGroup.NumTimelines++; + + if (bAssetLoadingMode && (ThreadInfo.Id == LoadingMainThreadId || ThreadInfo.Id == LoadingAsyncThreadId)) + { + Track->SetVisibilityFlag(true); + ThreadGroup.bIsVisible = true; + } + else + { + Track->SetVisibilityFlag(bIsGroupVisible); + } + + bIsTimingEventsTrackDirty = true; + } + else + { + Track = CachedTimelines[TrackId]; + + if (Track->GetOrder() != Order) + { + Track->SetOrder(Order); + bIsTimingEventsTrackDirty = true; + } + } + + Order++; + } + }); + } + + if (Trace::ReadFileActivityProvider(*Session.Get())) + { + if (IoActivityTrack == nullptr) + { + // Note: The I/O timelines are just prototypes for now (will be removed once the functionality is moved in analyzer). + uint64 TrackId = FBaseTimingTrack::GenerateId(); + IoActivityTrack = AddTimingEventsTrack(TrackId, ETimingEventsTrackType::Io, TEXT("I/O Activity"), nullptr, 999999); + IoActivityTrack->SetVisibilityFlag(bShowHideAllIoTracks); + bIsTimingEventsTrackDirty = true; + } + } + + if (bIsTimingEventsTrackDirty) + { + // The list has changed. Sort the list again. + TimingEventsTracks.Sort([this](const FTimingEventsTrack& A, const FTimingEventsTrack& B) -> bool + { + return A.GetOrder() < B.GetOrder(); + }); + } + } + + // Compute total height of visible tracks. + float TotalScrollHeight = 0.0f; + for (FTimingEventsTrack* TrackPtr : TimingEventsTracks) + { + ensure(TrackPtr != nullptr); + FTimingEventsTrack& Track = *TrackPtr; + + if (Track.IsVisible()) + { + Track.SetPosY(TotalScrollHeight); + TotalScrollHeight += Track.GetHeight(); + } + + // Reset track's hovered/selected state. + Track.SetHoveredState(false); + Track.SetSelectedFlag(false); + } + TotalScrollHeight += 1.0f; // allow 1 pixel at the bottom (for last horizontal line) + + // Set hovered/selected state for actual hovered/selected tracks, if any. + if (HoveredTimingEvent.Track != nullptr) + { + const_cast(HoveredTimingEvent.Track)->SetHoveredState(true); + } + if (SelectedTimingEvent.Track != nullptr) + { + const_cast(SelectedTimingEvent.Track)->SetSelectedFlag(true); + } + + //TimeRulerTrack.Tick(InCurrentTime, InDeltaTime); + MarkersTrack.Tick(InCurrentTime, InDeltaTime); + + // Check if vertical scroll area has changed. + if (TotalScrollHeight != Viewport.ScrollHeight) + { + Viewport.ScrollHeight = TotalScrollHeight; + UpdateVerticalScrollBar(); + bIsVerticalViewportDirty = true; + } + + // Update layout change animation (i.e. compact mode <-> normal mode). + Layout.Update(); + + ////////////////////////////////////////////////// + // Update dirty tracks. + + if (bIsViewportDirty) + { + bIsViewportDirty = false; + + //TimeRuler.SetDirtyFlag(); + MarkersTrack.SetDirtyFlag(); + bAreTimingEventsTracksDirty = true; + } + + if (bIsVerticalViewportDirty) + { + bIsVerticalViewportDirty = false; + + bAreTimingEventsTracksDirty = true; + } + + //if (TimeRuler.IsVisible() && TimeRuler.IsDirty()) + //{ + // TimeRuler.ClearDirtyFlag(); + // + // TimeRuler.OnViewportChanged(Viewport); + //} + + if (MarkersTrack.IsVisible() && MarkersTrack.IsDirty()) + { + MarkersTrack.ClearDirtyFlag(); + + FStopwatch Stopwatch; + Stopwatch.Start(); + + MarkersTrack.Update(Viewport); + + Stopwatch.Stop(); + TimeMarkerCacheUpdateDurationHistory.AddValue(Stopwatch.AccumulatedTime); + } + + if (bAreTimingEventsTracksDirty) + { + bAreTimingEventsTracksDirty = false; + + FStopwatch Stopwatch; + Stopwatch.Start(); + + if (GraphTrack.IsVisible()) + { + GraphTrack.Update(Viewport); + } + + for (int32 TrackIndex = 0; TrackIndex < TimingEventsTracks.Num(); ++TrackIndex) + { + TimingEventsTracks[TrackIndex]->Update(Viewport); + } + + Stopwatch.Stop(); + TimelineCacheUpdateDurationHistory.AddValue(Stopwatch.AccumulatedTime); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +int32 STimingView::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const +{ + const bool bEnabled = ShouldBeEnabled(bParentEnabled); + const ESlateDrawEffect DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect; + FDrawContext DrawContext(AllottedGeometry, MyCullingRect, InWidgetStyle, DrawEffects, OutDrawElements, LayerId); + +#if 0 // Enabling this may further increase UI performance (TODO: profile if this is really needed again). + // Avoids multiple resizes of Slate's draw elements buffers. + OutDrawElements.GetRootDrawLayer().DrawElements.Reserve(50000); +#endif + + const TSharedRef FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService(); + + const float ViewWidth = AllottedGeometry.GetLocalSize().X; + const float ViewHeight = AllottedGeometry.GetLocalSize().Y; + +#if 0 // Enabling this may further increase UI performance (TODO: profile if this is really needed again). + // Warm up Slate vertex/index buffers to avoid initial freezes due to multiple resizes of those buffers. + static bool bWarmingUp = false; + if (!bWarmingUp) + { + bWarmingUp = true; + + FRandomStream RandomStream(0); + const int32 Count = 1'000'000; + for (int32 Index = 0; Index < Count; ++Index) + { + float X = ViewWidth * RandomStream.GetFraction(); + float Y = ViewHeight * RandomStream.GetFraction(); + FLinearColor Color(RandomStream.GetFraction(), RandomStream.GetFraction(), RandomStream.GetFraction(), 1.0f); + DrawContext.DrawBox(X, Y, 1.0f, 1.0f, WhiteBrush, Color); + } + LayerId++; + LayerId++; + } +#endif + + ////////////////////////////////////////////////// + + FStopwatch Stopwatch; + Stopwatch.Start(); + + FTimingViewDrawHelper Helper(DrawContext, Viewport, Layout); + + // Draw background. + Helper.DrawBackground(); + + // Draw scrollable tracks. + { + Helper.BeginTimelines(); + + for (int32 TrackIndex = 0; TrackIndex < TimingEventsTracks.Num(); ++TrackIndex) + { + FTimingEventsTrack& Track = *TimingEventsTracks[TrackIndex]; + if (Track.IsVisible()) + { + //TODO: Track.Draw(Helper); + if (Track.GetType() == ETimingEventsTrackType::Cpu) + { + DrawTimingProfilerTrack(Helper, Track); + } + else if (Track.GetType() == ETimingEventsTrackType::Gpu) + { + DrawTimingProfilerTrack(Helper, Track); + } + else if (Track.GetType() == ETimingEventsTrackType::Loading) + { + DrawLoadTimeProfilerTrack(Helper, Track); + } + else if (Track.GetType() == ETimingEventsTrackType::Io) + { + if (&Track == IoOverviewTrack) + { + DrawIoOverviewTrack(Helper, Track); + } + else if (&Track == IoActivityTrack) + { + DrawIoActivityTrack(Helper, Track); + } + } + } + } + + Helper.EndTimelines(); + } + + if (GraphTrack.IsVisible()) + { + GraphTrack.Draw(DrawContext, Viewport); + } + + if (!FTimingEvent::AreEquals(SelectedTimingEvent, HoveredTimingEvent)) + { + // Highlight the selected timing event (if any). + if (SelectedTimingEvent.IsValid()) + { + const float Y = Viewport.GetViewportY(SelectedTimingEvent.Track->GetPosY()) + Layout.GetLaneY(SelectedTimingEvent.Depth); + Helper.DrawTimingEventHighlight(SelectedTimingEvent.StartTime, SelectedTimingEvent.EndTime, Y, FTimingViewDrawHelper::EHighlightMode::Selected); + } + + // Highlight the hovered timing event (if any). + if (HoveredTimingEvent.IsValid()) + { + const float Y = Viewport.GetViewportY(HoveredTimingEvent.Track->GetPosY()) + Layout.GetLaneY(HoveredTimingEvent.Depth); + Helper.DrawTimingEventHighlight(HoveredTimingEvent.StartTime, HoveredTimingEvent.EndTime, Y, FTimingViewDrawHelper::EHighlightMode::Hovered); + } + } + else + { + // Highlight the selected and hovered timing event (if any). + if (SelectedTimingEvent.IsValid()) + { + const float Y = Viewport.GetViewportY(SelectedTimingEvent.Track->GetPosY()) + Layout.GetLaneY(SelectedTimingEvent.Depth); + Helper.DrawTimingEventHighlight(SelectedTimingEvent.StartTime, SelectedTimingEvent.EndTime, Y, FTimingViewDrawHelper::EHighlightMode::SelectedAndHovered); + } + } + + if (MarkersTrack.IsVisible()) + { + // Draw the time markers (vertical lines + category & message texts). + MarkersTrack.Draw(DrawContext, Viewport); + } + + ////////////////////////////////////////////////// + + // Draw the time ruler. + if (TimeRulerTrack.IsVisible()) + { + TimeRulerTrack.Draw(DrawContext, Viewport, MousePosition, bIsSelecting, SelectionStartTime, SelectionEndTime); + } + + // Fill background for the Tracks filter combobox. + DrawContext.DrawBox(0, 0, 66, 22, WhiteBrush, FLinearColor(0.05f, 0.05f, 0.05f, 1.0f)); + + ////////////////////////////////////////////////// + + // Draw the time range selection. + DrawTimeRangeSelection(DrawContext); + + ////////////////////////////////////////////////// + + // Draw the time marker (orange vertical line). + //DrawTimeMarker(OnPaintState); + float TimeMarkerX = Viewport.TimeToSlateUnitsRounded(TimeMarker); + if (TimeMarkerX >= 0.0f && TimeMarkerX < Viewport.Width) + { + DrawContext.DrawBox(TimeMarkerX, 0.0f, 1.0f, Viewport.Height, WhiteBrush, FLinearColor(0.85f, 0.5f, 0.03f, 0.5f)); + DrawContext.LayerId++; + } + + ////////////////////////////////////////////////// + + Stopwatch.Stop(); + DrawDurationHistory.AddValue(Stopwatch.AccumulatedTime); + + ////////////////////////////////////////////////// + + const bool bShouldDisplayDebugInfo = FInsightsManager::Get()->IsDebugInfoEnabled(); + if (bShouldDisplayDebugInfo) + { + const FSlateFontInfo& SummaryFont = MainFont; + + const float MaxFontCharHeight = FontMeasureService->Measure(TEXT("!"), SummaryFont).Y; + const float DbgDY = MaxFontCharHeight; + + const float DbgW = 320.0f; + const float DbgH = DbgDY * 9 + 3.0f; + const float DbgX = ViewWidth - DbgW - 20.0f; + float DbgY = Viewport.TopOffset + 10.0f; + + DrawContext.LayerId++; + + DrawContext.DrawBox(DbgX - 2.0f, DbgY - 2.0f, DbgW, DbgH, WhiteBrush, FLinearColor(1.0f, 1.0f, 1.0f, 0.9f)); + DrawContext.LayerId++; + + FLinearColor DbgTextColor(0.0f, 0.0f, 0.0f, 0.9f); + + ////////////////////////////////////////////////// + // Display the "Draw" performance info. + + // Time interval since last OnPaint call. + const uint64 CurrentTime = FPlatformTime::Cycles64(); + const uint64 OnPaintDuration = CurrentTime - LastOnPaintTime; + LastOnPaintTime = CurrentTime; + OnPaintDurationHistory.AddValue(OnPaintDuration); // saved for last 32 OnPaint calls + const uint64 AvgOnPaintDuration = OnPaintDurationHistory.ComputeAverage(); + const uint64 AvgOnPaintDurationMs = FStopwatch::Cycles64ToMilliseconds(AvgOnPaintDuration); + const double AvgOnPaintFps = AvgOnPaintDurationMs != 0 ? 1.0 / FStopwatch::Cycles64ToSeconds(AvgOnPaintDuration) : 0.0; + + const uint64 AvgDrawDurationMs = FStopwatch::Cycles64ToMilliseconds(DrawDurationHistory.ComputeAverage()); + + DrawContext.DrawText + ( + DbgX, DbgY, + FString::Printf(TEXT("D: %llu ms + %llu ms = %llu ms (%d fps)"), + AvgDrawDurationMs, // drawing time + AvgOnPaintDurationMs - AvgDrawDurationMs, // other overhead to OnPaint calls + AvgOnPaintDurationMs, // average time between two OnPaint calls + FMath::RoundToInt(AvgOnPaintFps)), // framerate of OnPaint calls + SummaryFont, DbgTextColor + ); + DbgY += DbgDY; + + ////////////////////////////////////////////////// + // Display the "timelines cache update" info. + + const uint64 AvgTimelineUpdateDurationMs = FStopwatch::Cycles64ToMilliseconds(TimelineCacheUpdateDurationHistory.ComputeAverage()); + const uint64 LastTimelineUpdateDurationMs = FStopwatch::Cycles64ToMilliseconds(TimelineCacheUpdateDurationHistory.GetValue(0)); + + DrawContext.DrawText + ( + DbgX, DbgY, + FString::Printf(TEXT("U1 avg: %llu ms, last: %llu ms"), + AvgTimelineUpdateDurationMs, // time to update the Timeline cache + LastTimelineUpdateDurationMs), // last time to update the Timeline cache + SummaryFont, DbgTextColor + ); + DbgY += DbgDY; + + ////////////////////////////////////////////////// + // Display the "time markers cache update" info. + + const uint64 AvgTimeMarkerUpdateDurationMs = FStopwatch::Cycles64ToMilliseconds(TimeMarkerCacheUpdateDurationHistory.ComputeAverage()); + const uint64 LastTimeMarkerUpdateDurationMs = FStopwatch::Cycles64ToMilliseconds(TimeMarkerCacheUpdateDurationHistory.GetValue(0)); + + DrawContext.DrawText + ( + DbgX, DbgY, + FString::Printf(TEXT("U2 avg: %llu ms, last: %llu ms"), + AvgTimeMarkerUpdateDurationMs, // time to update the TimeMarker cache + LastTimeMarkerUpdateDurationMs), // last time to update the Timeline cache + SummaryFont, DbgTextColor + ); + DbgY += DbgDY; + + ////////////////////////////////////////////////// + // Display timing events stats. + + DrawContext.DrawText + ( + DbgX, DbgY, + FString::Format(TEXT("{0} events : {1} ({2}) boxes, {3} borders, {4} texts"), + { + FText::AsNumber(Helper.GetNumEvents()).ToString(), + FText::AsNumber(Helper.GetNumDrawBoxes()).ToString(), + FText::AsPercent((double)Helper.GetNumDrawBoxes() / (Helper.GetNumDrawBoxes() + Helper.GetNumMergedBoxes())).ToString(), + FText::AsNumber(Helper.GetNumDrawBorders()).ToString(), + FText::AsNumber(Helper.GetNumDrawTexts()).ToString(), + //OutDrawElements.GetRootDrawLayer().GetElementCount(), + }), + SummaryFont, DbgTextColor + ); + DbgY += DbgDY; + + ////////////////////////////////////////////////// + // Display time markers stats. + + DrawContext.DrawText + ( + DbgX, DbgY, + FString::Format(TEXT("{0}{1} logs : {2} boxes, {3} texts"), + { + MarkersTrack.IsVisible() ? TEXT("") : TEXT("*"), + FText::AsNumber(MarkersTrack.GetNumLogMessages()).ToString(), + FText::AsNumber(MarkersTrack.GetNumBoxes()).ToString(), + FText::AsNumber(MarkersTrack.GetNumTexts()).ToString(), + }), + SummaryFont, DbgTextColor + ); + DbgY += DbgDY; + + ////////////////////////////////////////////////// + // Display Graph track stats. + + DrawContext.DrawText + ( + DbgX, DbgY, + FString::Format(TEXT("{0} events : {1} points, {2} lines, {3} boxes"), + { + FText::AsNumber(GraphTrack.GetNumAddedEvents()).ToString(), + FText::AsNumber(GraphTrack.GetNumDrawPoints()).ToString(), + FText::AsNumber(GraphTrack.GetNumDrawLines()).ToString(), + FText::AsNumber(GraphTrack.GetNumDrawBoxes()).ToString(), + }), + SummaryFont, DbgTextColor + ); + DbgY += DbgDY; + + ////////////////////////////////////////////////// + // Display viewport's horizontal info. + + DrawContext.DrawText + ( + DbgX, DbgY, + FString::Printf(TEXT("SX: %g, ST: %g, ET: %s"), Viewport.ScaleX, Viewport.StartTime, *TimeUtils::FormatTimeAuto(Viewport.MaxValidTime)), + SummaryFont, DbgTextColor + ); + DbgY += DbgDY; + + ////////////////////////////////////////////////// + // Display viewport's vertical info. + + DrawContext.DrawText + ( + DbgX, DbgY, + FString::Printf(TEXT("Y: %.2f, H: %g, VH: %g"), Viewport.ScrollPosY, Viewport.ScrollHeight, Viewport.Height), + SummaryFont, DbgTextColor + ); + DbgY += DbgDY; + + ////////////////////////////////////////////////// + // Display input related debug info. + + FString InputStr = FString::Printf(TEXT("(%.0f, %.0f)"), MousePosition.X, MousePosition.Y); + if (bIsSpaceBarKeyPressed) InputStr += " Space"; + if (bIsLMB_Pressed) InputStr += " LMB"; + if (bIsRMB_Pressed) InputStr += " RMB"; + if (bIsPanning) InputStr += " Panning"; + if (bIsSelecting) InputStr += " Selecting"; + if (bIsDragging) InputStr += " Dragging"; + DrawContext.DrawText(DbgX, DbgY, InputStr, SummaryFont, DbgTextColor); + DbgY += DbgDY; + } + + ////////////////////////////////////////////////// + + { + // Draw info about selected event (bottom-right corner). + if (SelectedTimingEvent.IsValid()) + { + if (SelectedTimingEvent.Track->GetType() == ETimingEventsTrackType::Cpu || + SelectedTimingEvent.Track->GetType() == ETimingEventsTrackType::Gpu) + { + const FTimerNodePtr TimerNodePtr = GetTimerNode(SelectedTimingEvent.TypeId); + + FString Str = FString::Printf(TEXT("%s (Incl.: %s, Excl.: %s)"), + TimerNodePtr ? *(TimerNodePtr->GetName().ToString()) : TEXT("N/A"), + *TimeUtils::FormatTimeAuto(SelectedTimingEvent.Duration()), + *TimeUtils::FormatTimeAuto(SelectedTimingEvent.ExclusiveTime)); + + const FVector2D Size = FontMeasureService->Measure(Str, MainFont); + const float X = Viewport.Width - Size.X - 23.0f; + const float Y = Viewport.Height - Size.Y - 18.0f; + + const FLinearColor BackgroundColor(0.05f, 0.05f, 0.05f, 1.0f); + const FLinearColor TextColor(0.7f, 0.7f, 0.7f, 1.0f); + + DrawContext.DrawBox(X - 8.0f, Y - 2.0f, Size.X + 16.0f, Size.Y + 4.0f, WhiteBrush, BackgroundColor); + DrawContext.LayerId++; + + DrawContext.DrawText(X, Y, Str, MainFont, TextColor); + DrawContext.LayerId++; + } + else if (SelectedTimingEvent.Track->GetType() == ETimingEventsTrackType::Loading) + { + //TODO: ... + } + } + + // Draw info about hovered event (like a tooltip at mouse position). + if (HoveredTimingEvent.IsValid() && !MousePosition.IsZero()) + { + if (HoveredTimingEvent.Track->GetType() == ETimingEventsTrackType::Cpu || + HoveredTimingEvent.Track->GetType() == ETimingEventsTrackType::Gpu) + { + const FTimerNodePtr TimerNodePtr = GetTimerNode(HoveredTimingEvent.TypeId); + FString Name = TimerNodePtr ? TimerNodePtr->GetName().ToString() : TEXT("N/A"); + + float W = FontMeasureService->Measure(Name, MainFont).X; + + if (W < MinTooltipWidth) + { + W = MinTooltipWidth; + } + if (TooltipWidth != W) + { + TooltipWidth = TooltipWidth * 0.75f + W * 0.25f; + } + + const float MaxX = FMath::Max(0.0f, Viewport.Width - TooltipWidth - 21.0f); + const float X = FMath::Clamp(MousePosition.X + 18.0f, 0.0f, MaxX); + + constexpr float LineH = 14.0f; + constexpr float H = 4 * LineH; + const float MaxY = FMath::Max(0.0f, Viewport.Height - H - 18.0f); + float Y = FMath::Clamp(MousePosition.Y + 18.0f, 0.0f, MaxY); + + const float Alpha = 1.0f - FMath::Abs(TooltipWidth - W) / W; + if (TooltipAlpha < Alpha) + { + TooltipAlpha = TooltipAlpha * 0.9f + Alpha * 0.1f; + } + else + { + TooltipAlpha = Alpha; + } + + const FLinearColor BackgroundColor(0.05f, 0.05f, 0.05f, TooltipAlpha); + const FLinearColor NameColor(0.9f, 0.9f, 0.5f, TooltipAlpha); + const FLinearColor TextColor(0.6f, 0.6f, 0.6f, TooltipAlpha); + const FLinearColor ValueColor(1.0f, 1.0f, 1.0f, TooltipAlpha); + + DrawContext.DrawBox(X - 6.0f, Y - 3.0f, TooltipWidth + 12.0f, H + 6.0f, WhiteBrush, BackgroundColor); + DrawContext.LayerId++; + + DrawContext.DrawText(X, Y, Name, MainFont, NameColor); + Y += LineH; + + const float ValueX = X + 58.0f; + + DrawContext.DrawText(X + 3.0f, Y, TEXT("Incl. Time:"), MainFont, TextColor); + FString InclStr = TimeUtils::FormatTimeAuto(HoveredTimingEvent.Duration()); + DrawContext.DrawText(ValueX, Y, InclStr, MainFont, ValueColor); + Y += LineH; + + DrawContext.DrawText(X, Y, TEXT("Excl. Time:"), MainFont, TextColor); + FString ExclStr = TimeUtils::FormatTimeAuto(HoveredTimingEvent.ExclusiveTime); + DrawContext.DrawText(ValueX, Y, ExclStr, MainFont, ValueColor); + Y += LineH; + + DrawContext.DrawText(X + 24.0f, Y, TEXT("Depth:"), MainFont, TextColor); + FString DepthStr = FString::Printf(TEXT("%d"), HoveredTimingEvent.Depth); + DrawContext.DrawText(ValueX, Y, DepthStr, MainFont, ValueColor); + Y += LineH; + + DrawContext.LayerId++; + } + else if (HoveredTimingEvent.Track->GetType() == ETimingEventsTrackType::Loading) + { + FString Name(LoadingGetEventNameFn.Execute(HoveredTimingEvent.Depth, HoveredTimingEvent.LoadingInfo)); + + const Trace::FPackageInfo* Package = HoveredTimingEvent.LoadingInfo.Package; + const Trace::FPackageExportInfo* Export = HoveredTimingEvent.LoadingInfo.Export; + + FString PackageName = Package ? Package->Name : TEXT("N/A"); + + constexpr float ValueOffsetX = 100.0f; + constexpr float MinValueTextWidth = 220.0f; + + float W = FontMeasureService->Measure(Name, MainFont).X; + + float PackageNameW = ValueOffsetX + FontMeasureService->Measure(PackageName, MainFont).X; + if (W < PackageNameW) + { + W = PackageNameW; + } + if (W < ValueOffsetX + MinValueTextWidth) + { + W = ValueOffsetX + MinValueTextWidth; + } + if (W < MinTooltipWidth) + { + W = MinTooltipWidth; + } + if (TooltipWidth != W) + { + TooltipWidth = TooltipWidth * 0.75f + W * 0.25f; + } + + const float MaxX = FMath::Max(0.0f, Viewport.Width - TooltipWidth - 21.0f); + const float X = FMath::Clamp(MousePosition.X + 18.0f, 0.0f, MaxX); + const float ValueX = X + ValueOffsetX; + + constexpr float LineH = 14.0f; + constexpr float H = 10 * LineH; + const float MaxY = FMath::Max(0.0f, Viewport.Height - H - 18.0f); + float Y = FMath::Clamp(MousePosition.Y + 18.0f, 0.0f, MaxY); + + const float Alpha = 1.0f - FMath::Abs(TooltipWidth - W) / W; + if (TooltipAlpha < Alpha) + { + TooltipAlpha = TooltipAlpha * 0.9f + Alpha * 0.1f; + } + else + { + TooltipAlpha = Alpha; + } + + const FLinearColor BackgroundColor(0.05f, 0.05f, 0.05f, TooltipAlpha); + const FLinearColor NameColor(0.9f, 0.9f, 0.5f, TooltipAlpha); + const FLinearColor TextColor(0.6f, 0.6f, 0.6f, TooltipAlpha); + const FLinearColor ValueColor(1.0f, 1.0f, 1.0f, TooltipAlpha); + + DrawContext.DrawBox(X - 6.0f, Y - 3.0f, TooltipWidth + 12.0f, H + 6.0f, WhiteBrush, BackgroundColor); + DrawContext.LayerId++; + + DrawContext.DrawText(X, Y, Name, MainFont, NameColor); + Y += LineH; + + DrawContext.DrawText(X, Y, TEXT("Duration:"), MainFont, TextColor); + FString InclStr = TimeUtils::FormatTimeAuto(HoveredTimingEvent.Duration()); + DrawContext.DrawText(ValueX, Y, InclStr, MainFont, ValueColor); + Y += LineH; + + DrawContext.DrawText(X, Y, TEXT("Depth:"), MainFont, TextColor); + FString DepthStr = FString::Printf(TEXT("%d"), HoveredTimingEvent.Depth); + DrawContext.DrawText(ValueX, Y, DepthStr, MainFont, ValueColor); + Y += LineH; + + DrawContext.DrawText(X, Y, TEXT("Package Type:"), MainFont, TextColor); + DrawContext.DrawText(ValueX, Y, GetName(HoveredTimingEvent.LoadingInfo.PackageEventType), MainFont, ValueColor); + Y += LineH; + + if (Package) + { + DrawContext.DrawText(X, Y, TEXT("Package Name:"), MainFont, TextColor); + DrawContext.DrawText(ValueX, Y, PackageName, MainFont, ValueColor); + Y += LineH; + + DrawContext.DrawText(X, Y, TEXT("Header Size:"), MainFont, TextColor); + FString HeaderSizeStr = FString::Printf(TEXT("%d bytes"), Package->Summary.TotalHeaderSize); + DrawContext.DrawText(ValueX, Y, HeaderSizeStr, MainFont, ValueColor); + Y += LineH; + + DrawContext.DrawText(X, Y, TEXT("Package Summary:"), MainFont, TextColor); + FString SummaryStr = FString::Printf(TEXT("%d names, %d imports, %d exports"), Package->Summary.NameCount, Package->Summary.ImportCount, Package->Summary.ExportCount); + DrawContext.DrawText(ValueX, Y, SummaryStr, MainFont, ValueColor); + Y += LineH; + } + + { + DrawContext.DrawText(X, Y, TEXT("Export Type:"), MainFont, TextColor); + FString ExportTypeStr = FString::Printf(TEXT("%s%s"), GetName(HoveredTimingEvent.LoadingInfo.ExportEventType), Export && Export->IsAsset ? TEXT(" [asset]") : TEXT("")); + DrawContext.DrawText(ValueX, Y, ExportTypeStr, MainFont, ValueColor); + Y += LineH; + } + + if (Export) + { + DrawContext.DrawText(X, Y, TEXT("Export Class:"), MainFont, TextColor); + FString ClassStr = FString::Printf(TEXT("%s"), Export->Class ? Export->Class->Name : TEXT("N/A")); + DrawContext.DrawText(ValueX, Y, ClassStr, MainFont, ValueColor); + Y += LineH; + + DrawContext.DrawText(X, Y, TEXT("Serial:"), MainFont, TextColor); + FString SerialStr = FString::Printf(TEXT("Offset: %llu, Size: %llu%s"), Export->SerialOffset, Export->SerialSize); + DrawContext.DrawText(ValueX, Y, SerialStr, MainFont, ValueColor); + Y += LineH; + } + + DrawContext.LayerId++; + } + } + else + { + TooltipWidth = MinTooltipWidth; + TooltipAlpha = 0.0f; + } + } + + if (bAssetLoadingMode) + { + const FLinearColor BackgroundColor(0.01f, 0.01f, 0.01f, 0.9f); + const FLinearColor TextColor(1.0f, 1.0f, 1.0f, 0.9f); + + constexpr float MarginX = 8.0f; + constexpr float MarginY = 8.0f; + + constexpr float BorderX = 4.0f; + constexpr float BorderY = 4.0f; + float Y = ViewHeight; + + if (ObjectTypeAggregationStr.Len() > 0) + { + FVector2D TextSize = FontMeasureService->Measure(ObjectTypeAggregationStr, MainFont); + const float X = ViewWidth - MarginX - TextSize.X - 2 * BorderX; + Y -= MarginY + TextSize.Y + 2 * BorderY; + DrawContext.DrawBox(X, Y, TextSize.X + 2 * BorderX, TextSize.Y + 2 * BorderY, WhiteBrush, BackgroundColor); + DrawContext.LayerId++; + DrawContext.DrawText(X + BorderX, Y + BorderY, ObjectTypeAggregationStr, MainFont, TextColor); + DrawContext.LayerId++; + } + + if (EventAggregationStr.Len() > 0) + { + FVector2D TextSize = FontMeasureService->Measure(EventAggregationStr, MainFont); + const float X = ViewWidth - MarginX - TextSize.X - 2 * BorderX; + Y -= MarginY + TextSize.Y + 2 * BorderY; + DrawContext.DrawBox(X, Y, TextSize.X + 2 * BorderX, TextSize.Y + 2 * BorderY, WhiteBrush, BackgroundColor); + DrawContext.LayerId++; + DrawContext.DrawText(X + BorderX, Y + BorderY, EventAggregationStr, MainFont, TextColor); + DrawContext.LayerId++; + } + } + + return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled && IsEnabled()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const TCHAR* GetName(ETimingEventsTrackType Type) +{ + switch (Type) + { + case ETimingEventsTrackType::Gpu: return TEXT("GPU"); + case ETimingEventsTrackType::Cpu: return TEXT("CPU"); + case ETimingEventsTrackType::Loading: return TEXT("Loading"); + case ETimingEventsTrackType::Io: return TEXT("I/O"); + default: return TEXT("unknown"); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FTimingEventsTrack* STimingView::AddTimingEventsTrack(uint64 TrackId, ETimingEventsTrackType TrackType, const FString& TrackName, const TCHAR* GroupName, int32 Order) +{ + FTimingEventsTrack* Track = new FTimingEventsTrack(TrackId, TrackType, TrackName, GroupName); + + Track->SetOrder(Order); + + UE_LOG(TimingProfiler, Log, TEXT("New Timing Events Track (%d) : %s (\"%s\")"), TimingEventsTracks.Num() + 1, GetName(TrackType), *TrackName); + + CachedTimelines.Add(TrackId, Track); + TimingEventsTracks.Add(Track); + + return Track; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimingView::IsGpuTrackVisible() const +{ + return GpuTrack != nullptr && GpuTrack->IsVisible(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimingView::IsCpuTrackVisible(uint32 ThreadId) const +{ + return CpuTracks.Contains(ThreadId) && CpuTracks[ThreadId]->IsVisible(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::DrawTimingProfilerTrack(FTimingViewDrawHelper& Helper, FTimingEventsTrack& Track) const +{ + if (Helper.BeginTimeline(Track)) + { + TSharedPtr Session = FInsightsManager::Get()->GetSession(); + if (Session.IsValid() && Trace::ReadTimingProfilerProvider(*Session.Get())) + { + Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); + + const Trace::ITimingProfilerProvider& TimingProfilerProvider = *Trace::ReadTimingProfilerProvider(*Session.Get()); + + TimingProfilerProvider.ReadTimers([this, &Helper, &Track, &TimingProfilerProvider](const Trace::FTimingProfilerTimer* Timers, uint64 TimersCount) + { + TimingProfilerProvider.ReadTimeline(Track.GetId(), [this, &Helper, &Track, Timers](const Trace::ITimingProfilerProvider::Timeline& Timeline) + { + if (bUseDownSampling) + { + const double SecondsPerPixel = 1.0 / Helper.GetViewport().ScaleX; + Timeline.EnumerateEventsDownSampled(Helper.GetViewport().StartTime, Helper.GetViewport().EndTime, SecondsPerPixel, [this, &Helper, Timers](double StartTime, double EndTime, uint32 Depth, const Trace::FTimingProfilerEvent& Event) + { + Helper.AddEvent(StartTime, EndTime, Depth, Timers[Event.TimerIndex].Name); + }); + } + else + { + Timeline.EnumerateEvents(Helper.GetViewport().StartTime, Helper.GetViewport().EndTime, [this, &Helper, Timers](double StartTime, double EndTime, uint32 Depth, const Trace::FTimingProfilerEvent& Event) + { + Helper.AddEvent(StartTime, EndTime, Depth, Timers[Event.TimerIndex].Name); + }); + } + }); + }); + + Helper.EndTimeline(Track); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const TCHAR* GetName(ELoadTimeProfilerPackageEventType Type) +{ + switch (Type) + { + case LoadTimeProfilerPackageEventType_CreateLinker: return TEXT("CreateLinker"); + case LoadTimeProfilerPackageEventType_FinishLinker: return TEXT("FinishLinker"); + case LoadTimeProfilerPackageEventType_StartImportPackages: return TEXT("StartImportPackages"); + case LoadTimeProfilerPackageEventType_SetupImports: return TEXT("SetupImports"); + case LoadTimeProfilerPackageEventType_SetupExports: return TEXT("SetupExports"); + case LoadTimeProfilerPackageEventType_ProcessImportsAndExports: return TEXT("ProcessImportsAndExports"); + case LoadTimeProfilerPackageEventType_ExportsDone: return TEXT("ExportsDone"); + case LoadTimeProfilerPackageEventType_PostLoadWait: return TEXT("PostLoadWait"); + case LoadTimeProfilerPackageEventType_StartPostLoad: return TEXT("StartPostLoad"); + case LoadTimeProfilerPackageEventType_Tick: return TEXT("Tick"); + case LoadTimeProfilerPackageEventType_Finish: return TEXT("Finish"); + case LoadTimeProfilerPackageEventType_DeferredPostLoad: return TEXT("DeferredPostLoad"); + case LoadTimeProfilerPackageEventType_None: return TEXT("None"); + default: return TEXT(""); + } +} + +const TCHAR* GetName(ELoadTimeProfilerObjectEventType Type) +{ + switch (Type) + { + case LoadTimeProfilerObjectEventType_Create: return TEXT("Create"); + case LoadTimeProfilerObjectEventType_Serialize: return TEXT("Serialize"); + case LoadTimeProfilerObjectEventType_PostLoad: return TEXT("PostLoad"); + case LoadTimeProfilerObjectEventType_None: return TEXT("None"); + default: return TEXT(""); + }; +} + +const TCHAR* STimingView::GetLoadTimeProfilerEventNameByPackageEventType(uint32 Depth, const Trace::FLoadTimeProfilerCpuEvent& Event) const +{ + return GetName(Event.PackageEventType); +} + +const TCHAR* STimingView::GetLoadTimeProfilerEventNameByExportEventType(uint32 Depth, const Trace::FLoadTimeProfilerCpuEvent& Event) const +{ + return GetName(Event.ExportEventType); +} + +const TCHAR* STimingView::GetLoadTimeProfilerEventNameByPackageName(uint32 Depth, const Trace::FLoadTimeProfilerCpuEvent& Event) const +{ + return Event.Package ? Event.Package->Name : TEXT(""); +} + +const TCHAR* STimingView::GetLoadTimeProfilerEventNameByExportClassName(uint32 Depth, const Trace::FLoadTimeProfilerCpuEvent& Event) const +{ + return Event.Export && Event.Export->Class ? Event.Export->Class->Name : TEXT(""); +} + +const TCHAR* STimingView::GetLoadTimeProfilerEventName(uint32 Depth, const Trace::FLoadTimeProfilerCpuEvent& Event) const +{ + if (Depth == 0) + { + if (Event.Package) + { + return Event.Package->Name; + } + } + + if (Event.Export && Event.Export->Class) + { + return Event.Export->Class->Name; + } + + return TEXT(""); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::DrawLoadTimeProfilerTrack(FTimingViewDrawHelper& Helper, FTimingEventsTrack& Track) const +{ + if (Helper.BeginTimeline(Track)) + { + TSharedPtr Session = FInsightsManager::Get()->GetSession(); + if (Session.IsValid() && Trace::ReadLoadTimeProfilerProvider(*Session.Get())) + { + Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); + + const Trace::ILoadTimeProfilerProvider& LoadTimeProfilerProvider = *Trace::ReadLoadTimeProfilerProvider(*Session.Get()); + + if (&Track == LoadingMainThreadTrack) + { + LoadTimeProfilerProvider.ReadMainThreadCpuTimeline([this, &Helper, &Track](const Trace::ILoadTimeProfilerProvider::CpuTimeline& Timeline) + { + if (bUseDownSampling) + { + const double SecondsPerPixel = 1.0 / Helper.GetViewport().ScaleX; + Timeline.EnumerateEventsDownSampled(Helper.GetViewport().StartTime, Helper.GetViewport().EndTime, SecondsPerPixel, [this, &Helper](double StartTime, double EndTime, uint32 Depth, const Trace::FLoadTimeProfilerCpuEvent& Event) + { + const TCHAR* Name = LoadingGetEventNameFn.Execute(Depth, Event); + Helper.AddEvent(StartTime, EndTime, Depth, Name); + }); + } + else + { + Timeline.EnumerateEvents(Helper.GetViewport().StartTime, Helper.GetViewport().EndTime, [this, &Helper](double StartTime, double EndTime, uint32 Depth, const Trace::FLoadTimeProfilerCpuEvent& Event) + { + const TCHAR* Name = LoadingGetEventNameFn.Execute(Depth, Event); + Helper.AddEvent(StartTime, EndTime, Depth, Name); + }); + } + }); + } + else if (&Track == LoadingAsyncThreadTrack) + { + LoadTimeProfilerProvider.ReadAsyncLoadingThreadCpuTimeline([this, &Helper, &Track](const Trace::ILoadTimeProfilerProvider::CpuTimeline& Timeline) + { + if (bUseDownSampling) + { + const double SecondsPerPixel = 1.0 / Helper.GetViewport().ScaleX; + Timeline.EnumerateEventsDownSampled(Helper.GetViewport().StartTime, Helper.GetViewport().EndTime, SecondsPerPixel, [this, &Helper](double StartTime, double EndTime, uint32 Depth, const Trace::FLoadTimeProfilerCpuEvent& Event) + { + const TCHAR* Name = LoadingGetEventNameFn.Execute(Depth, Event); + Helper.AddEvent(StartTime, EndTime, Depth, Name); + }); + } + else + { + Timeline.EnumerateEvents(Helper.GetViewport().StartTime, Helper.GetViewport().EndTime, [this, &Helper](double StartTime, double EndTime, uint32 Depth, const Trace::FLoadTimeProfilerCpuEvent& Event) + { + const TCHAR* Name = LoadingGetEventNameFn.Execute(Depth, Event); + Helper.AddEvent(StartTime, EndTime, Depth, Name); + }); + } + }); + } + } + + Helper.EndTimeline(Track); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// "I/O - File Activity" prototype +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// The I/O timelines are just prototypes for now. +// Below code will be removed once the functionality is moved in analyzer. + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const TCHAR* GetFileActivityTypeName(Trace::EFileActivityType Type) +{ + static_assert(Trace::FileActivityType_Open == 0, "Trace::EFileActivityType enum has changed!?"); + static_assert(Trace::FileActivityType_Close == 1, "Trace::EFileActivityType enum has changed!?"); + static_assert(Trace::FileActivityType_Read == 2, "Trace::EFileActivityType enum has changed!?"); + static_assert(Trace::FileActivityType_Write == 3, "Trace::EFileActivityType enum has changed!?"); + static_assert(Trace::FileActivityType_Count == 4, "Trace::EFileActivityType enum has changed!?"); + static const TCHAR* GFileActivityTypeNames[] = + { + TEXT("Open"), + TEXT("Close"), + TEXT("Read"), + TEXT("Write"), + TEXT("Idle"), // virtual events added for cases where Close event is more than 1s away from last Open/Read/Write event. + TEXT("NotClosed") // virtual events added when an Open activity never closes + }; + return GFileActivityTypeNames[Type]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +uint32 GetFileActivityTypeColor(Trace::EFileActivityType Type) +{ + static const uint32 GFileActivityTypeColors[] = + { + 0xFFCCAA33, // open + 0xFF33AACC, // close + 0xFF33AA33, // read + 0xFFDD33CC, // write + 0x55333333, // idle + 0x55553333, // close + }; + return GFileActivityTypeColors[Type]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::DrawIoOverviewTrack(FTimingViewDrawHelper& Helper, FTimingEventsTrack& Track) const +{ + if (Helper.BeginTimeline(Track)) + { + // Draw the IO Overiew track using cached events (as those are sorted by Start Time). + for (const FIoTimingEvent& Event : AllIoEvents) + { + const Trace::EFileActivityType ActivityType = static_cast(Event.Type & 0x0F); + + if (ActivityType >= Trace::FileActivityType_Count) + { + // Ignore "Idle" and "NotClosed" events. + continue; + } + if (Event.EndTime <= Helper.GetViewport().StartTime) + { + continue; + } + if (Event.StartTime >= Helper.GetViewport().EndTime) + { + break; + } + + uint32 Color = GetFileActivityTypeColor(ActivityType); + + const bool bHasFailed = ((Event.Type & 0xF0) != 0); + + if (bHasFailed) + { + FString Name = TEXT("Failed "); + Name += GetFileActivityTypeName(ActivityType); + Name += " ["; + Name += Event.Path; + Name += "]"; + Color = 0xFFAA0000; + Helper.AddEvent(Event.StartTime, Event.EndTime, 0, *Name, Color); + } + else if (ActivityType == Trace::FileActivityType_Open) + { + FString Name = GetFileActivityTypeName(ActivityType); + Name += " ["; + Name += Event.Path; + Name += "]"; + Helper.AddEvent(Event.StartTime, Event.EndTime, 0, *Name, Color); + } + else + { + Helper.AddEvent(Event.StartTime, Event.EndTime, 0, GetFileActivityTypeName(ActivityType), Color); + } + } + + Helper.EndTimeline(Track); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::UpdateIo() +{ + if (bForceIoEventsUpdate) + { + bForceIoEventsUpdate = false; + + AllIoEvents.Reset(); + + TSharedPtr Session = FInsightsManager::Get()->GetSession(); + if (Session.IsValid() && Trace::ReadFileActivityProvider(*Session.Get())) + { + Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); + + const Trace::IFileActivityProvider& FileActivityProvider = *Trace::ReadFileActivityProvider(*Session.Get()); + + // Enumerate all IO events and cache them. + FileActivityProvider.EnumerateFileActivity([this](const Trace::FFileInfo& FileInfo, const Trace::IFileActivityProvider::Timeline& Timeline) + { + Timeline.EnumerateEvents(-std::numeric_limits::infinity(), +std::numeric_limits::infinity(), + [this, &FileInfo, &Timeline](double EventStartTime, double EventEndTime, uint32 EventDepth, const Trace::FFileActivity& FileActivity) + { + if (bMergeIoLanes) + { + EventDepth = static_cast(FileInfo.Id); // used by MergeLanes algorithm + } + else + { + EventDepth = FileInfo.Id % 32; // simple layout + } + uint32 Type = ((uint32)FileActivity.ActivityType & 0x0F) | (FileActivity.Failed ? 0x80 : 0); + AllIoEvents.Add(FIoTimingEvent{ EventStartTime, EventEndTime, EventDepth, Type, FileInfo.Path }); + }); + + return true; + }); + } + + // Sort cached IO events by Start Time. + AllIoEvents.Sort([](const FIoTimingEvent& A, const FIoTimingEvent& B) { return A.StartTime < B.StartTime; }); + + if (bMergeIoLanes) + { + struct FIoLane + { + uint32 FileId; + const TCHAR* Path; + double LastEndTime; + double EndTime; + }; + TArray Lanes; + + TArray IoEventsToAdd; + + for (FIoTimingEvent& Event : AllIoEvents) + { + uint64 TimelineId = Event.Depth; + + int32 Depth = -1; + + bool bIsCloseEvent = false; + + const Trace::EFileActivityType ActivityType = static_cast(Event.Type & 0x0F); + + if (ActivityType == Trace::FileActivityType_Open) + { + // Find lane (avoiding overlaps with other opened files). + for (int32 LaneIndex = 0; LaneIndex < Lanes.Num(); ++LaneIndex) + { + FIoLane& Lane = Lanes[LaneIndex]; + if (Event.StartTime >= Lane.EndTime) + { + Depth = LaneIndex; + break; + } + } + + if (Depth < 0) + { + // Add new lane. + Depth = Lanes.AddDefaulted(); + } + + const bool bHasFailed = ((Event.Type & 0xF0) != 0); + + FIoLane& Lane = Lanes[Depth]; + Lane.FileId = TimelineId; + Lane.Path = Event.Path; + Lane.LastEndTime = Event.EndTime; + Lane.EndTime = bHasFailed ? Event.EndTime : std::numeric_limits::infinity(); + } + else if (ActivityType == Trace::FileActivityType_Close) + { + bIsCloseEvent = true; + + // Find lane with same id. + for (int32 LaneIndex = 0; LaneIndex < Lanes.Num(); ++LaneIndex) + { + FIoLane& Lane = Lanes[LaneIndex]; + if (Lane.FileId == TimelineId) + { + // Adds an Idle event, but only if has passed at least 1s since last open/read/write activity. + if (Event.StartTime - Lane.LastEndTime > 1.0) + { + IoEventsToAdd.Add(FIoTimingEvent{ Lane.LastEndTime, Event.StartTime, static_cast(LaneIndex), Trace::FileActivityType_Count, Event.Path }); + } + Lane.LastEndTime = Event.EndTime; + Lane.EndTime = Event.EndTime; + Depth = LaneIndex; + break; + } + } + ensure(Depth >= 0); + } + else + { + // All other events should be inside the virtual Open-Close event. + // Find lane with same id. + for (int32 LaneIndex = 0; LaneIndex < Lanes.Num(); ++LaneIndex) + { + FIoLane& Lane = Lanes[LaneIndex]; + if (Lane.FileId == TimelineId) + { + Lane.LastEndTime = Event.EndTime; + Depth = LaneIndex; + break; + } + } + ensure(Depth >= 0); + } + + if (Depth < 0) // just in case + { + // Add new lane. + Depth = Lanes.AddDefaulted(); + FIoLane& Lane = Lanes[Depth]; + Lane.FileId = TimelineId; + Lane.Path = Event.Path; + Lane.LastEndTime = Event.EndTime; + Lane.EndTime = bIsCloseEvent ? Event.EndTime : std::numeric_limits::infinity(); + } + + Event.Depth = Depth; + } + + for (int32 LaneIndex = 0; LaneIndex < Lanes.Num(); ++LaneIndex) + { + FIoLane& Lane = Lanes[LaneIndex]; + if (Lane.EndTime == std::numeric_limits::infinity()) + { + IoEventsToAdd.Add(FIoTimingEvent{ Lane.LastEndTime, Lane.EndTime, static_cast(LaneIndex), Trace::FileActivityType_Count + 1, Lane.Path }); + } + } + + for (const FIoTimingEvent& Event : IoEventsToAdd) + { + AllIoEvents.Add(Event); + } + + // Sort cached IO events one more time, also by Start Time. + AllIoEvents.Sort([](const FIoTimingEvent& A, const FIoTimingEvent& B) { return A.StartTime < B.StartTime; }); + + //// Sort cached IO events again, by Depth and then by Start Time. + //AllIoEvents.Sort([](const FIoTimingEvent& A, const FIoTimingEvent& B) + //{ + // return A.Depth == B.Depth ? A.StartTime < B.StartTime : A.Depth < B.Depth; + //}); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::DrawIoActivityTrack(FTimingViewDrawHelper& Helper, FTimingEventsTrack& Track) const +{ + if (Helper.BeginTimeline(Track)) + { + // Draw IO track using cached events. + for (const FIoTimingEvent& Event : AllIoEvents) + { + if (Event.EndTime <= Helper.GetViewport().StartTime) + { + continue; + } + if (Event.StartTime >= Helper.GetViewport().EndTime) + { + break; + } + + const Trace::EFileActivityType ActivityType = static_cast(Event.Type & 0x0F); + + uint32 Color = GetFileActivityTypeColor(ActivityType); + + if (ActivityType < Trace::FileActivityType_Count) + { + const bool bHasFailed = ((Event.Type & 0xF0) != 0); + + if (bHasFailed) + { + FString Name = TEXT("Failed "); + Name += GetFileActivityTypeName(ActivityType); + Name += " ["; + Name += Event.Path; + Name += "]"; + Color = 0xFFAA0000; + Helper.AddEvent(Event.StartTime, Event.EndTime, Event.Depth, *Name, Color); + } + else if (ActivityType == Trace::FileActivityType_Open) + { + FString Name = GetFileActivityTypeName(ActivityType); + Name += " ["; + Name += Event.Path; + Name += "]"; + Helper.AddEvent(Event.StartTime, Event.EndTime, Event.Depth, *Name, Color); + } + else + { + Helper.AddEvent(Event.StartTime, Event.EndTime, Event.Depth, GetFileActivityTypeName(ActivityType), Color); + } + } + else + { + FString Name = GetFileActivityTypeName(ActivityType); + Name += " ["; + Name += Event.Path; + Name += "]"; + Helper.AddEvent(Event.StartTime, Event.EndTime, Event.Depth, *Name, Color); + } + } + + Helper.EndTimeline(Track); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// end of "I/O - File Activity" prototype +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::DrawTimeRangeSelection(FDrawContext& DrawContext) const +{ + if (SelectionEndTime > SelectionStartTime) + { + float SelectionX1 = Viewport.TimeToSlateUnitsRounded(SelectionStartTime); + float SelectionX2 = Viewport.TimeToSlateUnitsRounded(SelectionEndTime); + + if (SelectionX1 <= Viewport.Width && + SelectionX2 >= 0) + { + float ClipLeft = 0.0f; + float ClipRight = 0.0f; + if (SelectionX1 < 0.0f) + { + ClipLeft = -SelectionX1; + SelectionX1 = 0.0f; + } + if (SelectionX2 > Viewport.Width) + { + ClipRight = SelectionX2 - Viewport.Width; + SelectionX2 = Viewport.Width; + } + + // Draw selection area. + DrawContext.DrawBox(SelectionX1, 0.0f, SelectionX2 - SelectionX1, Viewport.Height, WhiteBrush, FLinearColor(0.25f, 0.5f, 1.0f, 0.25f)); + DrawContext.LayerId++; + + FColor ArrowFillColor(32, 64, 128, 255); + FLinearColor ArrowColor(ArrowFillColor); + + if (SelectionX1 > 0.0f) + { + // Draw left side (vertical line). + DrawContext.DrawBox(SelectionX1 - 1.0f, 0.0f, 1.0f, Viewport.Height, WhiteBrush, ArrowColor); + } + + if (SelectionX2 < Viewport.Width) + { + // Draw right side (vertical line). + DrawContext.DrawBox(SelectionX2, 0.0f, 1.0f, Viewport.Height, WhiteBrush, ArrowColor); + } + + DrawContext.LayerId++; + + const float ArrowSize = 6.0f; + const float ArrowY = 6.0f; + + if (SelectionX2 - SelectionX1 > 2 * ArrowSize) + { + // Draw horizontal line. + float HorizLineX1 = SelectionX1; + if (ClipLeft == 0.0f) + { + HorizLineX1 += 1.0f; + } + float HorizLineX2 = SelectionX2; + if (ClipRight == 0.0f) + { + HorizLineX2 -= 1.0f; + } + DrawContext.DrawBox(HorizLineX1, ArrowY - 1.0f, HorizLineX2 - HorizLineX1, 3.0f, WhiteBrush, ArrowColor); + + if (ClipLeft < ArrowSize) + { + // Draw left arrow. + for (float AH = 0.0f; AH < ArrowSize; AH += 1.0f) + { + DrawContext.DrawBox(SelectionX1 - ClipLeft + AH, ArrowY - AH, 1.0f, 2.0f * AH + 1.0f, WhiteBrush, ArrowColor); + } + } + + if (ClipRight < ArrowSize) + { + // Draw right arrow. + for (float AH = 0.0f; AH < ArrowSize; AH += 1.0f) + { + DrawContext.DrawBox(SelectionX2 + ClipRight - AH - 1.0f, ArrowY - AH, 1.0f, 2.0f * AH + 1.0f, WhiteBrush, ArrowColor); + } + } + + DrawContext.LayerId++; + +#if 0 + //im: This should be a more efficeint way top draw the arrows, but it renders them with artifacts (missing vertical lines; shader bug?)! + + const FSlateBrush* MyBrush = WhiteBrush; + FSlateShaderResourceProxy* ResourceProxy = FSlateDataPayload::ResourceManager->GetShaderResource(*MyBrush); + FSlateResourceHandle ResourceHandle = FSlateApplication::Get().GetRenderer()->GetResourceHandle(*MyBrush); + + FVector2D AtlasOffset = ResourceProxy ? ResourceProxy->StartUV : FVector2D(0.0f, 0.0f); + FVector2D AtlasUVSize = ResourceProxy ? ResourceProxy->SizeUV : FVector2D(1.0f, 1.0f); + + const FVector2D Pos = AllottedGeometry.GetAbsolutePosition() + FVector2D(0.0f, 40.0f); + const float Scale = AllottedGeometry.Scale; + + FSlateRenderTransform RenderTransform; + + TArray Verts; + Verts.Reserve(6); + Verts.Add(FSlateVertex::Make(RenderTransform, Pos + FVector2D(0.5f + SelectionX1 + ArrowSize, 0.5f + ArrowY + ArrowSize) * Scale, AtlasOffset + FVector2D(0.0f, 1.0f) * AtlasUVSize, ArrowFillColor)); + Verts.Add(FSlateVertex::Make(RenderTransform, Pos + FVector2D(0.5f + SelectionX1, 0.5f + ArrowY ) * Scale, AtlasOffset + FVector2D(1.0f, 0.5f) * AtlasUVSize, ArrowFillColor)); + Verts.Add(FSlateVertex::Make(RenderTransform, Pos + FVector2D(0.5f + SelectionX1 + ArrowSize, 0.5f + ArrowY - ArrowSize) * Scale, AtlasOffset + FVector2D(0.0f, 0.0f) * AtlasUVSize, ArrowFillColor)); + Verts.Add(FSlateVertex::Make(RenderTransform, Pos + FVector2D(0.5f + SelectionX2 - ArrowSize, 0.5f + ArrowY - ArrowSize) * Scale, AtlasOffset + FVector2D(0.0f, 0.0f) * AtlasUVSize, ArrowFillColor)); + Verts.Add(FSlateVertex::Make(RenderTransform, Pos + FVector2D(0.5f + SelectionX2, 0.5f + ArrowY ) * Scale, AtlasOffset + FVector2D(1.0f, 0.5f) * AtlasUVSize, ArrowFillColor)); + Verts.Add(FSlateVertex::Make(RenderTransform, Pos + FVector2D(0.5f + SelectionX2 - ArrowSize, 0.5f + ArrowY + ArrowSize) * Scale, AtlasOffset + FVector2D(0.0f, 1.0f) * AtlasUVSize, ArrowFillColor)); + + TArray Indices; + Indices.Reserve(6); + if (ClipLeft < ArrowSize) + { + Indices.Add(0); + Indices.Add(1); + Indices.Add(2); + } + if (ClipRight < ArrowSize) + { + Indices.Add(3); + Indices.Add(4); + Indices.Add(5); + } + + FSlateDrawElement::MakeCustomVerts( + OutDrawElements, + LayerId, + ResourceHandle, + Verts, + Indices, + nullptr, + 0, + 0, + ESlateDrawEffect::PreMultipliedAlpha); + + DrawContext.LayerId++; +#endif + } + + ////////////////////////////////////////////////// + // Draw duration for selected time interval. + + double Duration = SelectionEndTime - SelectionStartTime; + FString Text = TimeUtils::FormatTimeAuto(Duration); + + const TSharedRef FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService(); + const float TextWidth = FontMeasureService->Measure(Text, MainFont).X; + + const float CenterX = (SelectionX1 + SelectionX2) / 2.0f; + + DrawContext.DrawBox(CenterX - TextWidth / 2 - 2.0, ArrowY - 6.0f, TextWidth + 4.0f, 13.0f, WhiteBrush, ArrowColor); + DrawContext.LayerId++; + + DrawContext.DrawText(CenterX - TextWidth / 2, ArrowY - 6.0f, Text, MainFont, FLinearColor::White); + DrawContext.LayerId++; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply STimingView::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + FReply Reply = FReply::Unhandled(); + MousePositionOnButtonDown = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()); + + bool bStartPanning = false; + + if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) + { + if (MarkersTrack.IsVisible() && MarkersTrack.IsHeaderHovered()) + { + MarkersTrack.ToggleCollapsed(); + } + + if (!bIsRMB_Pressed) + { + bIsLMB_Pressed = true; + + if (bIsSpaceBarKeyPressed || MousePositionOnButtonDown.Y > TimeRulerTrack.GetHeight()) + { + bStartPanning = true; + } + else + { + bIsSelecting = true; + bIsDragging = false; + + SelectionStartTime = Viewport.SlateUnitsToTime(MousePositionOnButtonDown.X); + SelectionEndTime = SelectionStartTime; + LastSelectionType = ESelectionType::None; + //TODO: SelectionChangingEvent.Broadcast(SelectionStartTime, SelectionEndTime); + EventAggregationStr.Reset(); + ObjectTypeAggregationStr.Reset(); + } + + // Capture mouse, so we can drag outside this widget. + Reply = FReply::Handled().CaptureMouse(SharedThis(this)); + } + } + else if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton) + { + if (!bIsLMB_Pressed) + { + bIsRMB_Pressed = true; + + bStartPanning = true; + + // Capture mouse, so we can drag outside this widget. + Reply = FReply::Handled().CaptureMouse(SharedThis(this)); + } + } + + if (bStartPanning) + { + bIsPanning = true; + bIsDragging = false; + + ViewportStartTimeOnButtonDown = Viewport.StartTime; + ViewportScrollPosYOnButtonDown = Viewport.ScrollPosY; + + if (MouseEvent.GetModifierKeys().IsControlDown()) + { + // Allow panning only horizontally. + PanningMode = EPanningMode::Horizontal; + } + else if (MouseEvent.GetModifierKeys().IsShiftDown()) + { + // Allow panning only vertically. + PanningMode = EPanningMode::Vertical; + } + else + { + // Allow panning both horizontally and vertically. + PanningMode = EPanningMode::HorizontalAndVertical; + } + } + + return Reply; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply STimingView::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + FReply Reply = FReply::Unhandled(); + MousePositionOnButtonUp = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()); + + const bool bIsValidForMouseClick = MousePositionOnButtonUp.Equals(MousePositionOnButtonDown, 2.0f); + + if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) + { + if (bIsLMB_Pressed) + { + if (bIsPanning) + { + PanningMode = EPanningMode::None; + + bIsPanning = false; + } + else if (bIsSelecting) + { + //TODO: SelectionChangedEvent.Broadcast(SelectionStartTime, SelectionEndTime); + UpdateAggregatedStats(); + + bIsSelecting = false; + } + + if (bIsValidForMouseClick) + { + // Select the hovered timing event (if any). + UpdateHoveredTimingEvent(MousePositionOnButtonUp.X, MousePositionOnButtonUp.Y); + SelectHoveredTimingEvent(); + + // When clicking on an empty space... + if (!SelectedTimingEvent.IsValid()) + { + // ...reset selection. + SelectionEndTime = SelectionStartTime = 0.0; + LastSelectionType = ESelectionType::None; + //TODO: SelectionChangedEvent.Broadcast(SelectionStartTime, SelectionEndTime); + EventAggregationStr.Reset(); + ObjectTypeAggregationStr.Reset(); + } + } + + bIsDragging = false; + + // Release mouse as we no longer drag. + Reply = FReply::Handled().ReleaseMouseCapture(); + + bIsLMB_Pressed = false; + } + } + else if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton) + { + if (bIsRMB_Pressed) + { + if (bIsPanning) + { + PanningMode = EPanningMode::None; + + bIsPanning = false; + } + + if (!bIsDragging && !bIsSpaceBarKeyPressed && bIsValidForMouseClick) + { + ShowContextMenu(MouseEvent.GetScreenSpacePosition(), MouseEvent); + Reply = FReply::Handled(); + } + + bIsDragging = false; + + // Release mouse as we no longer drag. + Reply = FReply::Handled().ReleaseMouseCapture(); + + bIsRMB_Pressed = false; + } + } + + return Reply; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply STimingView::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + FReply Reply = FReply::Unhandled(); + + if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) + { + if (MarkersTrack.IsVisible() && MarkersTrack.IsHovered()) + { + MarkersTrack.ToggleCollapsed(); + } + else if (HoveredTimingEvent.IsValid()) + { + double EndTime = Viewport.RestrictEndTime(HoveredTimingEvent.EndTime); + SelectTimeInterval(HoveredTimingEvent.StartTime, EndTime - HoveredTimingEvent.StartTime); + } + + Reply = FReply::Handled(); + } + + return Reply; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply STimingView::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + FReply Reply = FReply::Unhandled(); + + MousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()); + + if (bIsPanning) + { + if (HasMouseCapture() && !MouseEvent.GetCursorDelta().IsZero()) + { + bIsDragging = true; + + if ((int32)PanningMode & (int32)EPanningMode::Horizontal) + { + ScrollAtTime(ViewportStartTimeOnButtonDown + static_cast(MousePositionOnButtonDown.X - MousePosition.X) / Viewport.ScaleX); + } + + if ((int32)PanningMode & (int32)EPanningMode::Vertical) + { + Viewport.ScrollPosY = ViewportScrollPosYOnButtonDown + (MousePositionOnButtonDown.Y - MousePosition.Y); + UpdateVerticalScrollBar(); + bIsVerticalViewportDirty = true; + } + } + + Reply = FReply::Handled(); + } + else if (bIsSelecting) + { + if (HasMouseCapture() && !MouseEvent.GetCursorDelta().IsZero()) + { + bIsDragging = true; + + SelectionStartTime = Viewport.SlateUnitsToTime(MousePositionOnButtonDown.X); + SelectionEndTime = Viewport.SlateUnitsToTime(MousePosition.X); + if (SelectionStartTime > SelectionEndTime) + { + double Temp = SelectionStartTime; + SelectionStartTime = SelectionEndTime; + SelectionEndTime = Temp; + } + LastSelectionType = ESelectionType::TimeRange; + //TODO: SelectionChangingEvent.Broadcast(SelectionStartTime, SelectionEndTime); + EventAggregationStr.Reset(); + ObjectTypeAggregationStr.Reset(); + } + + Reply = FReply::Handled(); + } + else + { + if (MarkersTrack.IsVisible()) + { + MarkersTrack.UpdateHoveredState(MousePosition.X, MousePosition.Y, Viewport); + } + + UpdateHoveredTimingEvent(MousePosition.X, MousePosition.Y); + } + + return Reply; // SAssetViewItem::CreateToolTipWidget +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::OnMouseLeave(const FPointerEvent& MouseEvent) +{ + if (!HasMouseCapture()) + { + // No longer dragging (unless we have mouse capture). + bIsDragging = false; + bIsPanning = false; + bIsSelecting = false; + + bIsLMB_Pressed = false; + bIsRMB_Pressed = false; + + MousePosition = FVector2D::ZeroVector; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply STimingView::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + if (MouseEvent.GetModifierKeys().IsShiftDown()) + { + // Scroll vertically. + constexpr float ScrollSpeedY = 16.0f * 3; + Viewport.ScrollPosY -= ScrollSpeedY * MouseEvent.GetWheelDelta(); + UpdateVerticalScrollBar(); + bIsVerticalViewportDirty = true; + } + else if (MouseEvent.GetModifierKeys().IsControlDown()) + { + // Scroll horizontally. + const double ScrollSpeedX = Viewport.GetDurationForViewportDX(16.0 * 3); + ScrollAtTime(Viewport.StartTime - ScrollSpeedX * MouseEvent.GetWheelDelta()); + } + else + { + // Zoom in/out horizontally. + const double Delta = MouseEvent.GetWheelDelta(); + constexpr double ZoomStep = 0.25; // as percent + double ScaleX; + + if (Delta > 0) + { + ScaleX = Viewport.ScaleX * FMath::Pow(1.0 + ZoomStep, Delta); + } + else + { + ScaleX = Viewport.ScaleX * FMath::Pow(1.0 / (1.0 + ZoomStep), -Delta); + } + + //UE_LOG(TimingProfiler, Log, TEXT("%.2f, %.2f, %.2f"), Delta, Viewport.ScaleX, ScaleX); + MousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()); + + if (Viewport.ZoomWithFixedX(ScaleX, MousePosition.X)) + { + //Viewport.EnforceHorizontalScrollLimits(1.0); + UpdateHorizontalScrollBar(); + bIsViewportDirty = true; + } + } + + return FReply::Handled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) +{ + SCompoundWidget::OnDragEnter(MyGeometry, DragDropEvent); + + //TSharedPtr Operation = DragDropEvent.GetOperationAs(); + //if (Operation.IsValid()) + //{ + // Operation->ShowOK(); + //} +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::OnDragLeave(const FDragDropEvent& DragDropEvent) +{ + SCompoundWidget::OnDragLeave(DragDropEvent); + + //TSharedPtr Operation = DragDropEvent.GetOperationAs(); + //if (Operation.IsValid()) + //{ + // Operation->ShowError(); + //} +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply STimingView::OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) +{ + return SCompoundWidget::OnDragOver(MyGeometry,DragDropEvent); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply STimingView::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) +{ + //TSharedPtr Operation = DragDropEvent.GetOperationAs(); + //if (Operation.IsValid()) + //{ + // return FReply::Handled(); + //} + + return SCompoundWidget::OnDrop(MyGeometry,DragDropEvent); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FCursorReply STimingView::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const +{ + if (bIsPanning) + { + if (bIsDragging) + { + //return FCursorReply::Cursor(EMouseCursor::GrabHandClosed); + return FCursorReply::Cursor(EMouseCursor::GrabHand); + } + } + else if (bIsSelecting) + { + if (bIsDragging) + { + return FCursorReply::Cursor(EMouseCursor::ResizeLeftRight); + } + } + else if (bIsSpaceBarKeyPressed) + { + return FCursorReply::Cursor(EMouseCursor::GrabHand); + } + + return FCursorReply::Unhandled(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply STimingView::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + if (InKeyEvent.GetKey() == EKeys::B) + { + // Toggle Bookmarks. + if (MarkersTrack.IsVisible()) + { + if (!MarkersTrack.IsBookmarksTrack()) + { + SetDrawOnlyBookmarks(true); + } + else + { + SetTimeMarkersVisible(false); + } + } + else + { + SetTimeMarkersVisible(true); + SetDrawOnlyBookmarks(true); + } + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::M) + { + // Toggle Time Markers. + if (MarkersTrack.IsVisible()) + { + if (MarkersTrack.IsBookmarksTrack()) + { + SetDrawOnlyBookmarks(false); + } + else + { + SetTimeMarkersVisible(false); + } + } + else + { + SetTimeMarkersVisible(true); + SetDrawOnlyBookmarks(false); + } + + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::F) + { + FrameSelection(); + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::C) + { + Layout.bIsCompactMode = !Layout.bIsCompactMode; + bAreTimingEventsTracksDirty = true; + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::V) + { + ToggleAutoHideEmptyTracks(); + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::Equals || + InKeyEvent.GetKey() == EKeys::Add) + { + // Zoom In + double ScaleX = Viewport.ScaleX * 1.25; + if (Viewport.ZoomWithFixedX(ScaleX, Viewport.Width/2)) + { + //Viewport.EnforceHorizontalScrollLimits(1.0); + UpdateHorizontalScrollBar(); + bIsViewportDirty = true; + } + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::Hyphen || + InKeyEvent.GetKey() == EKeys::Subtract) + { + // Zoom Out + double ScaleX = Viewport.ScaleX / 1.25; + if (Viewport.ZoomWithFixedX(ScaleX, Viewport.Width / 2)) + { + //Viewport.EnforceHorizontalScrollLimits(1.0); + UpdateHorizontalScrollBar(); + bIsViewportDirty = true; + } + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::Left) + { + if (InKeyEvent.GetModifierKeys().IsControlDown()) + { + // Scroll Left + double DT = Viewport.EndTime - Viewport.StartTime; + //ScrollAtTime(Viewport.StartTime - DT * 0.05); + if (Viewport.ScrollAtTime(Viewport.StartTime - DT * 0.05)) + { + //Viewport.EnforceHorizontalScrollLimits(1.0); + UpdateHorizontalScrollBar(); + bIsViewportDirty = true; + } + } + else + { + SelectLeftTimingEvent(); + } + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::Right) + { + if (InKeyEvent.GetModifierKeys().IsControlDown()) + { + // Scroll Right + double DT = Viewport.EndTime - Viewport.StartTime; + //ScrollAtTime(Viewport.StartTime + DT * 0.05); + if (Viewport.ScrollAtTime(Viewport.StartTime + DT * 0.05)) + { + //Viewport.EnforceHorizontalScrollLimits(1.0); + UpdateHorizontalScrollBar(); + bIsViewportDirty = true; + } + } + else + { + SelectRightTimingEvent(); + } + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::Up) + { + if (InKeyEvent.GetModifierKeys().IsControlDown()) + { + // Scroll Up + Viewport.ScrollPosY -= 16.0 * 3; + //Viewport.EnforceVerticalScrollLimits(1.0); + UpdateVerticalScrollBar(); + bIsVerticalViewportDirty = true; + } + else + { + SelectUpTimingEvent(); + } + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::Down) + { + if (InKeyEvent.GetModifierKeys().IsControlDown()) + { + // Scroll Down + Viewport.ScrollPosY += 16.0 * 3; + //Viewport.EnforceVerticalScrollLimits(1.0); + UpdateVerticalScrollBar(); + bIsVerticalViewportDirty = true; + } + else + { + SelectDownTimingEvent(); + } + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::Enter) + { + if (SelectedTimingEvent.IsValid()) + { + const double Duration = Viewport.RestrictDuration(SelectedTimingEvent.StartTime, SelectedTimingEvent.EndTime); + SelectTimeInterval(SelectedTimingEvent.StartTime, Duration); + } + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::SpaceBar) + { + bIsSpaceBarKeyPressed = true; + FSlateApplication::Get().QueryCursor(); + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::D) // debug: toggles down-sampling on/off + { + bUseDownSampling = !bUseDownSampling; + bAreTimingEventsTracksDirty = true; + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::G) // debug: toggles Graph track on/off + { + GraphTrack.ToggleVisibility(); + bAreTimingEventsTracksDirty = true; + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::Y) // debug: toggles GPU track on/off + { + ShowHideAllGpuTracks_Execute(); + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::U) // debug: toggles CPU tracks on/off + { + ShowHideAllCpuTracks_Execute(); + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::L) // debug: toggles Loading tracks on/off + { + ShowHideAllLoadingTracks_Execute(); + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::I) // debug: toggles IO tracks on/off + { + ShowHideAllIoTracks_Execute(); + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::O) // debug: toggles IO merge lanes algorithm + { + bMergeIoLanes = !bMergeIoLanes; + bForceIoEventsUpdate = true; + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::One) + { + LoadingGetEventNameFn = FLoadingTrackGetEventNameDelegate::CreateRaw(this, &STimingView::GetLoadTimeProfilerEventNameByPackageEventType); + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::Two) + { + LoadingGetEventNameFn = FLoadingTrackGetEventNameDelegate::CreateRaw(this, &STimingView::GetLoadTimeProfilerEventNameByExportEventType); + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::Three) + { + LoadingGetEventNameFn = FLoadingTrackGetEventNameDelegate::CreateRaw(this, &STimingView::GetLoadTimeProfilerEventNameByPackageName); + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::Four) + { + LoadingGetEventNameFn = FLoadingTrackGetEventNameDelegate::CreateRaw(this, &STimingView::GetLoadTimeProfilerEventNameByExportClassName); + return FReply::Handled(); + } + else if (InKeyEvent.GetKey() == EKeys::Five) + { + LoadingGetEventNameFn = FLoadingTrackGetEventNameDelegate::CreateRaw(this, &STimingView::GetLoadTimeProfilerEventName); + return FReply::Handled(); + } + + return SCompoundWidget::OnKeyDown(MyGeometry, InKeyEvent); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +FReply STimingView::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + if (InKeyEvent.GetKey() == EKeys::SpaceBar) + { + bIsSpaceBarKeyPressed = false; + FSlateApplication::Get().QueryCursor(); + return FReply::Handled(); + } + + return SCompoundWidget::OnKeyUp(MyGeometry, InKeyEvent); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::ShowContextMenu(const FVector2D& ScreenSpacePosition, const FPointerEvent& MouseEvent) +{ + TSharedPtr ProfilerCommandList = FTimingProfilerManager::Get()->GetCommandList(); + const FTimingProfilerCommands& ProfilerCommands = FTimingProfilerManager::GetCommands(); + const FTimingProfilerActionManager& ProfilerActionManager = FTimingProfilerManager::GetActionManager(); + + const bool bShouldCloseWindowAfterMenuSelection = true; + FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, ProfilerCommandList); + + MenuBuilder.BeginSection(TEXT("Misc"), LOCTEXT("Miscellaneous", "Miscellaneous")); + { + //MenuBuilder.AddMenuEntry(FTimingProfilerManager::GetCommands().EventGraph_SelectAllFrames); + //MenuBuilder.AddMenuEntry(FTimingProfilerManager::GetCommands().ProfilerManager_ToggleLivePreview); + } + MenuBuilder.EndSection(); + + TSharedRef MenuWidget = MenuBuilder.MakeWidget(); + + FWidgetPath EventPath = MouseEvent.GetEventPath() != nullptr ? *MouseEvent.GetEventPath() : FWidgetPath(); + FSlateApplication::Get().PushMenu(SharedThis(this), EventPath, MenuWidget, ScreenSpacePosition, FPopupTransitionEffect::ContextMenu); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::BindCommands() +{ + //TSharedPtr CommandList = FTimingProfilerManager::Get()->GetCommandList(); + //const FTimingProfilerCommands& Commands = FTimingProfilerManager::GetCommands(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::HorizontalScrollBar_OnUserScrolled(float ScrollOffset) +{ + const double SX = 1.0 / (Viewport.MaxValidTime - Viewport.MinValidTime); + const float ThumbSizeFraction = FMath::Clamp((Viewport.EndTime - Viewport.StartTime) * SX, 0.0f, 1.0f); + const float OffsetFraction = FMath::Clamp(ScrollOffset, 0.0f, 1.0f - ThumbSizeFraction); + + const double Time = Viewport.MinValidTime + static_cast(OffsetFraction) * (Viewport.MaxValidTime - Viewport.MinValidTime); + if (Viewport.StartTime != Time) + { + Viewport.ScrollAtTime(Time); + bIsViewportDirty = true; + } + + HorizontalScrollBar->SetState(OffsetFraction, ThumbSizeFraction); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::UpdateHorizontalScrollBar() +{ + const double SX = 1.0 / (Viewport.MaxValidTime - Viewport.MinValidTime); + const float ThumbSizeFraction = FMath::Clamp((Viewport.EndTime - Viewport.StartTime) * SX, 0.0f, 1.0f); + const float ScrollOffset = static_cast((Viewport.StartTime - Viewport.MinValidTime) * SX); + const float OffsetFraction = FMath::Clamp(ScrollOffset, 0.0f, 1.0f - ThumbSizeFraction); + + HorizontalScrollBar->SetState(OffsetFraction, ThumbSizeFraction); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::VerticalScrollBar_OnUserScrolled(float ScrollOffset) +{ + const float SY = 1.0 / Viewport.ScrollHeight; + const float H = Viewport.Height - Viewport.TopOffset; + const float ThumbSizeFraction = FMath::Clamp(H * SY, 0.0f, 1.0f); + const float OffsetFraction = FMath::Clamp(ScrollOffset, 0.0f, 1.0f - ThumbSizeFraction); + + const float ScrollPosY = OffsetFraction * Viewport.ScrollHeight; + if (Viewport.ScrollPosY != ScrollPosY) + { + Viewport.ScrollPosY = ScrollPosY; + bIsVerticalViewportDirty = true; + } + + VerticalScrollBar->SetState(OffsetFraction, ThumbSizeFraction); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::UpdateVerticalScrollBar() +{ + const float SY = 1.0 / Viewport.ScrollHeight; + const float H = Viewport.Height - Viewport.TopOffset; + const float ThumbSizeFraction = FMath::Clamp(H * SY, 0.0f, 1.0f); + const float ScrollOffset = Viewport.ScrollPosY * SY; + float OffsetFraction = FMath::Clamp(ScrollOffset, 0.0f, 1.0f - ThumbSizeFraction); + + VerticalScrollBar->SetState(OffsetFraction, ThumbSizeFraction); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::ScrollAtTime(double StartTime) +{ + if (Viewport.ScrollAtTime(StartTime)) + { + UpdateHorizontalScrollBar(); + bIsViewportDirty = true; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::CenterOnTimeInterval(double IntervalStartTime, double IntervalDuration) +{ + if (Viewport.CenterOnTimeInterval(IntervalStartTime, IntervalDuration)) + { + Viewport.EnforceHorizontalScrollLimits(1.0); + + UpdateHorizontalScrollBar(); + bIsViewportDirty = true; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::BringIntoView(double StartTime, double EndTime) +{ + EndTime = Viewport.RestrictEndTime(EndTime); + + // Increase interval with 8% on each side. + const double DT = (Viewport.EndTime - Viewport.StartTime) * 0.08; + StartTime -= DT; + EndTime += DT; + + double NewStartTime = Viewport.StartTime; + + if (EndTime > Viewport.EndTime) + { + NewStartTime += EndTime - Viewport.EndTime; + } + + if (StartTime < NewStartTime) + { + NewStartTime = StartTime; + } + + ScrollAtTime(NewStartTime); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::SelectTimeInterval(double IntervalStartTime, double IntervalDuration) +{ + SelectionStartTime = IntervalStartTime; + SelectionEndTime = IntervalStartTime + IntervalDuration; + LastSelectionType = ESelectionType::TimeRange; + + //TODO: SelectionChangedEvent.Broadcast(SelectionStartTime, SelectionEndTime); + UpdateAggregatedStats(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::UpdateAggregatedStats() +{ + TSharedPtr Wnd = FTimingProfilerManager::Get()->GetProfilerWindow(); + if (Wnd) + { + if (Wnd->TimersView) + { + Wnd->TimersView->UpdateStats(SelectionStartTime, SelectionEndTime); + } + if (Wnd->StatsView) + { + Wnd->StatsView->UpdateStats(SelectionStartTime, SelectionEndTime); + } + } + + if (bAssetLoadingMode) + { + TSharedPtr Session = FInsightsManager::Get()->GetSession(); + if (Session.IsValid() && Trace::ReadLoadTimeProfilerProvider(*Session.Get())) + { + Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); + + const Trace::ILoadTimeProfilerProvider& LoadTimeProfilerProvider = *Trace::ReadLoadTimeProfilerProvider(*Session.Get()); + + Trace::ITable* EventAggregationTable = LoadTimeProfilerProvider.CreateEventAggregation(SelectionStartTime, SelectionEndTime); + Trace::ITable* ObjectTypeAggregationTable = LoadTimeProfilerProvider.CreateObjectTypeAggregation(SelectionStartTime, SelectionEndTime); + + auto TableToString = [](const TCHAR* TableName, Trace::ITable* Table) -> FString + { + FString Str; + + const uint32 TotalRowCount = static_cast(Table->GetRowCount()); + + Str.Append(TableName).Append(": ").Append(FText::AsNumber(TotalRowCount).ToString()).Append(" records, sorted by Total time"); + + constexpr uint32 MaxRowCount = 10; // only get first 10 records + const uint32 RowCount = FMath::Min(TotalRowCount, MaxRowCount); + + const Trace::ITableLayout& TableLayout = Table->GetLayout(); + const int32 ColumnCount = TableLayout.GetColumnCount(); + + auto Reader = Table->CreateReader(); + + ////////////////////////////////////////////////// + + struct FSortedIndexEntry + { + uint32 RowIndex; + double Value; + }; + + TArray SortedIndex; + SortedIndex.Reserve(TotalRowCount); + + constexpr uint32 TotalTimeColumnIndex = 2; + ensure(TableLayout.GetColumnType(TotalTimeColumnIndex) == Trace::ETableColumnType::TableColumnType_Double); + + for (uint32 RowIndex = 0; RowIndex < TotalRowCount; ++RowIndex) + { + Reader->SetRowIndex(RowIndex); + double Value = Reader->GetValueDouble(TotalTimeColumnIndex); + SortedIndex.Add({ RowIndex, Value }); + } + + SortedIndex.Sort([](const FSortedIndexEntry& A, const FSortedIndexEntry& B) { return A.Value > B.Value; }); + + ////////////////////////////////////////////////// + + for (uint32 Index = 0; Index < RowCount; ++Index) + { + uint32 RowIndex = SortedIndex[Index].RowIndex; + Reader->SetRowIndex(RowIndex); + + Str.Append("\n").Append(FText::AsNumber(Index + 1).ToString()).Append(". "); + + for (int32 ColumnIndex = 0; ColumnIndex < ColumnCount; ++ColumnIndex) + { + Str.Append(TableLayout.GetColumnName(ColumnIndex)).Append("="); + + Trace::ETableColumnType ColumnType = TableLayout.GetColumnType(ColumnIndex); + switch (ColumnType) + { + case Trace::ETableColumnType::TableColumnType_Bool: + Str.Append(Reader->GetValueBool(ColumnIndex) ? TEXT("True") : TEXT("False")).Append(", "); + break; + case Trace::ETableColumnType::TableColumnType_Int: + Str.Append(FText::AsNumber(Reader->GetValueInt(ColumnIndex)).ToString()).Append(", "); + break; + case Trace::ETableColumnType::TableColumnType_Float: + Str.Append(FText::AsNumber(Reader->GetValueFloat(ColumnIndex)).ToString()).Append(", "); + break; + case Trace::ETableColumnType::TableColumnType_Double: + Str.Append(FText::AsNumber(Reader->GetValueDouble(ColumnIndex)).ToString()).Append(", "); + break; + case Trace::ETableColumnType::TableColumnType_CString: + Str.Append(Reader->GetValueCString(ColumnIndex)).Append(", "); + break; + } + } + } + + if (RowCount != TotalRowCount) + { + Str.Append("\n[...]"); + } + + return Str; + }; + + EventAggregationStr = TableToString(TEXT("Event Aggregation"), EventAggregationTable); + ObjectTypeAggregationStr = TableToString(TEXT("Object Type Aggregation"), ObjectTypeAggregationTable); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::SetAndCenterOnTimeMarker(double Time) +{ + TimeMarker = Time; + + double MinT, MaxT; + Viewport.GetHorizontalScrollLimits(MinT, MaxT); + const double ViewportDuration = static_cast(Viewport.Width) / Viewport.ScaleX; + MinT += ViewportDuration / 2; + MaxT += ViewportDuration / 2; + Time = FMath::Clamp(Time, MinT, MaxT); + + Time = Viewport.AlignTimeToPixel(Time); + CenterOnTimeInterval(Time, 0.0); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::SelectToTimeMarker(double Time) +{ + if (TimeMarker < Time) + { + SelectTimeInterval(TimeMarker, Time - TimeMarker); + } + else + { + SelectTimeInterval(Time, TimeMarker - Time); + } + + TimeMarker = Time; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::SetTimeMarkersVisible(bool bIsMarkersTrackVisible) +{ + if (MarkersTrack.IsVisible() != bIsMarkersTrackVisible) + { + MarkersTrack.SetVisibilityFlag(bIsMarkersTrackVisible); + + if (MarkersTrack.IsVisible()) + { + if (Viewport.ScrollPosY != 0.0f) + { + Viewport.ScrollPosY += MarkersTrack.GetHeight(); + } + + MarkersTrack.SetDirtyFlag(); + } + else + { + Viewport.ScrollPosY -= MarkersTrack.GetHeight(); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::SetDrawOnlyBookmarks(bool bIsBookmarksTrack) +{ + if (MarkersTrack.IsBookmarksTrack() != bIsBookmarksTrack) + { + const float PrevHeight = MarkersTrack.GetHeight(); + MarkersTrack.SetBookmarksTrackFlag(bIsBookmarksTrack); + + if (MarkersTrack.IsVisible()) + { + if (Viewport.ScrollPosY != 0.0f) + { + Viewport.ScrollPosY += MarkersTrack.GetHeight() - PrevHeight; + } + + MarkersTrack.SetDirtyFlag(); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::UpdateHoveredTimingEvent(float MX, float MY) +{ + HoveredTimingEvent.Track = nullptr; + HoveredTimingEvent.TypeId = FTimerNode::InvalidId; + + if (MY >= Viewport.TopOffset && MY < Viewport.Height) + { + for (const auto& KV : CachedTimelines) + { + const FTimingEventsTrack& Track = *KV.Value; + if (Track.IsVisible()) + { + const float Y = Viewport.TopOffset + Track.GetPosY() - Viewport.ScrollPosY; + if (MY >= Y && MY < Y + Track.GetHeight()) + { + HoveredTimingEvent.Track = &Track; + break; + } + } + } + + if (HoveredTimingEvent.Track) + { + const float Y0 = Viewport.TopOffset + HoveredTimingEvent.Track->GetPosY() - Viewport.ScrollPosY + 1.0f + Layout.TimelineDY; + + // If mouse is not above first sub-track or below last sub-track... + if (MY >= Y0 && MY < Y0 + HoveredTimingEvent.Track->GetHeight() + Layout.TimelineDY) + { + int32 Depth = (MY - Y0) / (Layout.EventDY + Layout.EventH); + float EventMY = (MY - Y0) - Depth * (Layout.EventDY + Layout.EventH); + + const double StartTime = Viewport.SlateUnitsToTime(MX); + const double EndTime = StartTime + 2.0 / Viewport.ScaleX; // +2px + constexpr bool bStopAtFirstMatch = true; // get first one matching + constexpr bool bSearchForLargestEvent = false; + SearchTimingEvent(StartTime, EndTime, + [Depth](double, double, uint32 EventDepth, uint32) + { + return EventDepth == Depth; + }, + HoveredTimingEvent, bStopAtFirstMatch, bSearchForLargestEvent); + + //TODO: ComputeSingleTimingEventStats(HoveredTimingEvent) --> compute ExclusiveTime + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimingView::SearchTimingEvent(const double InStartTime, + const double InEndTime, + TFunctionRef InPredicate, + FTimingEvent& InOutTimingEvent, + bool bInStopAtFirstMatch, + bool bInSearchForLargestEvent) const +{ + struct FSearchTimingEventContext + { + const double StartTime; + const double EndTime; + TFunctionRef Predicate; + FTimingEvent& TimingEvent; + const bool bStopAtFirstMatch; + const bool bSearchForLargestEvent; + mutable bool bFound; + mutable bool bContinueSearching; + mutable double LargestDuration; + + FSearchTimingEventContext(const double InStartTime, const double InEndTime, TFunctionRef InPredicate, FTimingEvent& InOutTimingEvent, bool bInStopAtFirstMatch, bool bInSearchForLargestEvent) + : StartTime(InStartTime) + , EndTime(InEndTime) + , Predicate(InPredicate) + , TimingEvent(InOutTimingEvent) + , bStopAtFirstMatch(bInStopAtFirstMatch) + , bSearchForLargestEvent(bInSearchForLargestEvent) + , bFound(false) + , bContinueSearching(true) + , LargestDuration(-1.0) + { + } + + void CheckEvent(double EventStartTime, double EventEndTime, uint32 EventDepth, const Trace::FTimingProfilerEvent& Event) + { + if (bContinueSearching && Predicate(EventStartTime, EventEndTime, EventDepth, Event.TimerIndex)) + { + if (!bSearchForLargestEvent || EventEndTime - EventStartTime > LargestDuration) + { + LargestDuration = EventEndTime - EventStartTime; + + TimingEvent.TypeId = Event.TimerIndex; + TimingEvent.Depth = EventDepth; + TimingEvent.StartTime = EventStartTime; + TimingEvent.EndTime = EventEndTime; + + bFound = true; + bContinueSearching = !bStopAtFirstMatch || bSearchForLargestEvent; + } + } + } + + void CheckEvent(double EventStartTime, double EventEndTime, uint32 EventDepth, const Trace::FLoadTimeProfilerCpuEvent& Event) + { + if (bContinueSearching && Predicate(EventStartTime, EventEndTime, EventDepth, 0)) + { + if (!bSearchForLargestEvent || EventEndTime - EventStartTime > LargestDuration) + { + LargestDuration = EventEndTime - EventStartTime; + + TimingEvent.TypeId = 0; + TimingEvent.Depth = EventDepth; + TimingEvent.StartTime = EventStartTime; + TimingEvent.EndTime = EventEndTime; + + TimingEvent.LoadingInfo = Event; + + bFound = true; + bContinueSearching = !bStopAtFirstMatch || bSearchForLargestEvent; + } + } + } + }; + + FSearchTimingEventContext Ctx(InStartTime, InEndTime, InPredicate, InOutTimingEvent, bInStopAtFirstMatch, bInSearchForLargestEvent); + + TSharedPtr Session = FInsightsManager::Get()->GetSession(); + if (Session.IsValid()) + { + Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get()); + + const FTimingEventsTrack* Track = Ctx.TimingEvent.Track; + + if (Track->GetType() == ETimingEventsTrackType::Cpu || + Track->GetType() == ETimingEventsTrackType::Gpu) + { + if (Trace::ReadTimingProfilerProvider(*Session.Get())) + { + const Trace::ITimingProfilerProvider& TimingProfilerProvider = *Trace::ReadTimingProfilerProvider(*Session.Get()); + + TimingProfilerProvider.ReadTimeline(Track->GetId(), [&Ctx](const Trace::ITimingProfilerProvider::Timeline& Timeline) + { + Timeline.EnumerateEvents(Ctx.StartTime, Ctx.EndTime, [&Ctx](double EventStartTime, double EventEndTime, uint32 EventDepth, const Trace::FTimingProfilerEvent& Event) + { + Ctx.CheckEvent(EventStartTime, EventEndTime, EventDepth, Event); + }); + }); + + if (Ctx.bFound) + { + // Compute Exclusive Time. + TimingProfilerProvider.ReadTimeline(Track->GetId(), [&Ctx](const Trace::ITimingProfilerProvider::Timeline& Timeline) + { + struct FEnumerationState + { + double EventStartTime; + double EventEndTime; + uint64 EventDepth; + + uint64 CurrentDepth; + double LastTime; + double ExclusiveTime; + bool IsInEventScope; + }; + FEnumerationState State; + + State.EventStartTime = Ctx.TimingEvent.StartTime; + State.EventEndTime = Ctx.TimingEvent.EndTime; + State.EventDepth = Ctx.TimingEvent.Depth; + + State.CurrentDepth = 0; + State.LastTime = 0.0; + State.ExclusiveTime = 0.0; + State.IsInEventScope = false; + + Timeline.EnumerateEvents(Ctx.TimingEvent.StartTime, Ctx.TimingEvent.EndTime, [&State](bool IsEnter, double Time, const Trace::FTimingProfilerEvent& Event) + { + if (IsEnter) + { + if (State.IsInEventScope && State.CurrentDepth == State.EventDepth + 1) + { + State.ExclusiveTime += Time - State.LastTime; + } + if (State.CurrentDepth == State.EventDepth && Time == State.EventStartTime) + { + State.IsInEventScope = true; + } + ++State.CurrentDepth; + } + else + { + --State.CurrentDepth; + if (State.CurrentDepth == State.EventDepth && Time == State.EventEndTime) + { + State.IsInEventScope = false; + State.ExclusiveTime += Time - State.LastTime; + } + } + State.LastTime = Time; + }); + + Ctx.TimingEvent.ExclusiveTime = State.ExclusiveTime; + }); + } + } + } + else if (Track->GetType() == ETimingEventsTrackType::Loading) + { + if (Trace::ReadLoadTimeProfilerProvider(*Session.Get())) + { + const Trace::ILoadTimeProfilerProvider& LoadTimeProfilerProvider = *Trace::ReadLoadTimeProfilerProvider(*Session.Get()); + + if (Track == LoadingMainThreadTrack) + { + LoadTimeProfilerProvider.ReadMainThreadCpuTimeline([&Ctx](const Trace::ILoadTimeProfilerProvider::CpuTimeline& Timeline) + { + Timeline.EnumerateEvents(Ctx.StartTime, Ctx.EndTime, [&Ctx](double EventStartTime, double EventEndTime, uint32 EventDepth, const Trace::FLoadTimeProfilerCpuEvent& Event) + { + Ctx.CheckEvent(EventStartTime, EventEndTime, EventDepth, Event); + }); + }); + } + else if (Track == LoadingAsyncThreadTrack) + { + LoadTimeProfilerProvider.ReadAsyncLoadingThreadCpuTimeline([&Ctx](const Trace::ILoadTimeProfilerProvider::CpuTimeline& Timeline) + { + Timeline.EnumerateEvents(Ctx.StartTime, Ctx.EndTime, [&Ctx](double EventStartTime, double EventEndTime, uint32 EventDepth, const Trace::FLoadTimeProfilerCpuEvent& Event) + { + Ctx.CheckEvent(EventStartTime, EventEndTime, EventDepth, Event); + }); + }); + } + } + } + } + + return Ctx.bFound; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::OnSelectedTimingEventChanged() +{ + // Select the timer node coresponding to timing event type of selected timing event. + if (SelectedTimingEvent.IsValid() && + (SelectedTimingEvent.Track->GetType() == ETimingEventsTrackType::Cpu || + SelectedTimingEvent.Track->GetType() == ETimingEventsTrackType::Gpu)) + { + TSharedPtr Wnd = FTimingProfilerManager::Get()->GetProfilerWindow(); + if (Wnd) + { + if (Wnd->TimersView) + { + Wnd->TimersView->SelectTimerNode(SelectedTimingEvent.TypeId); + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::SelectHoveredTimingEvent() +{ + SelectedTimingEvent = HoveredTimingEvent; + if (SelectedTimingEvent.IsValid()) + { + LastSelectionType = ESelectionType::TimingEvent; + BringIntoView(SelectedTimingEvent.StartTime, SelectedTimingEvent.EndTime); + } + OnSelectedTimingEventChanged(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::SelectLeftTimingEvent() +{ + if (SelectedTimingEvent.IsValid()) + { + const uint32 Depth = SelectedTimingEvent.Depth; + const double StartTime = SelectedTimingEvent.StartTime; + const double EndTime = SelectedTimingEvent.EndTime; + const bool bStopAtFirstMatch = false; // get last one matching + const bool bSearchForLargestEvent = false; + if (SearchTimingEvent(0.0, StartTime, + [Depth, StartTime, EndTime](double EventStartTime, double EventEndTime, uint32 EventDepth, uint32 EventTypeId) + { + return EventDepth == Depth && + (EventStartTime < StartTime || EventEndTime < EndTime); + }, + SelectedTimingEvent, bStopAtFirstMatch, bSearchForLargestEvent)) + { + BringIntoView(SelectedTimingEvent.StartTime, SelectedTimingEvent.EndTime); + OnSelectedTimingEventChanged(); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::SelectRightTimingEvent() +{ + if (SelectedTimingEvent.IsValid()) + { + const uint32 Depth = SelectedTimingEvent.Depth; + const double StartTime = SelectedTimingEvent.StartTime; + const double EndTime = SelectedTimingEvent.EndTime; + const bool bStopAtFirstMatch = true; // get first one matching + const bool bSearchForLargestEvent = false; + if (SearchTimingEvent(EndTime, Viewport.MaxValidTime, + [Depth, StartTime, EndTime](double EventStartTime, double EventEndTime, uint32 EventDepth, uint32 EventTypeId) + { + return EventDepth == Depth && + (EventStartTime > StartTime || EventEndTime > EndTime); + }, + SelectedTimingEvent, bStopAtFirstMatch, bSearchForLargestEvent)) + { + BringIntoView(SelectedTimingEvent.StartTime, SelectedTimingEvent.EndTime); + OnSelectedTimingEventChanged(); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::SelectUpTimingEvent() +{ + if (SelectedTimingEvent.IsValid() && + SelectedTimingEvent.Depth > 0) + { + const uint32 Depth = SelectedTimingEvent.Depth - 1; + double StartTime = SelectedTimingEvent.StartTime; + double EndTime = SelectedTimingEvent.EndTime; + const bool bStopAtFirstMatch = true; // get first one matching + const bool bSearchForLargestEvent = false; + if (SearchTimingEvent(StartTime, EndTime, + [Depth, StartTime, EndTime](double EventStartTime, double EventEndTime, uint32 EventDepth, uint32 EventTypeId) + { + return EventDepth == Depth + && EventStartTime <= EndTime + && EventEndTime >= StartTime; + }, + SelectedTimingEvent, bStopAtFirstMatch, bSearchForLargestEvent)) + { + BringIntoView(SelectedTimingEvent.StartTime, SelectedTimingEvent.EndTime); + OnSelectedTimingEventChanged(); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::SelectDownTimingEvent() +{ + if (SelectedTimingEvent.IsValid()) + { + const uint32 Depth = SelectedTimingEvent.Depth + 1; + double StartTime = SelectedTimingEvent.StartTime; + double EndTime = SelectedTimingEvent.EndTime; + const bool bStopAtFirstMatch = false; // check all timing events + const bool bSearchForLargestEvent = true; // get largest timing event + if (SearchTimingEvent(StartTime, EndTime, + [Depth, StartTime, EndTime](double EventStartTime, double EventEndTime, uint32 EventDepth, uint32 EventTypeId) + { + return EventDepth == Depth + && EventStartTime <= EndTime + && EventEndTime >= StartTime; + }, + SelectedTimingEvent, bStopAtFirstMatch, bSearchForLargestEvent)) + { + BringIntoView(SelectedTimingEvent.StartTime, SelectedTimingEvent.EndTime); + OnSelectedTimingEventChanged(); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::FrameSelection() +{ + double StartTime, EndTime; + + ESelectionType Type = ESelectionType::None; + + if (LastSelectionType == ESelectionType::TimingEvent) + { + // Try framing the selected timing event. + if (SelectedTimingEvent.IsValid()) + { + Type = ESelectionType::TimingEvent; + } + + // Next time, try framing the selected time range. + LastSelectionType = ESelectionType::TimeRange; + } + else if (LastSelectionType == ESelectionType::TimeRange) + { + // Try framing the selected time range. + if (SelectionEndTime > SelectionStartTime) + { + Type = ESelectionType::TimeRange; + } + + // Next time, try framing the selected timing event. + LastSelectionType = ESelectionType::TimingEvent; + } + + // If no last selection or last selection is empty... + if (LastSelectionType == ESelectionType::None || Type == ESelectionType::None) + { + // First, try framing the selected timing event... + if (SelectedTimingEvent.IsValid()) + { + Type = ESelectionType::TimingEvent; + } + else // ...otherwise, try framing the selected time range + { + Type = ESelectionType::TimeRange; + } + } + + if (Type == ESelectionType::TimingEvent) + { + // Frame the selected event. + StartTime = SelectedTimingEvent.StartTime; + EndTime = Viewport.RestrictEndTime(SelectedTimingEvent.EndTime); + if (EndTime == StartTime) + { + EndTime += 1.0 / Viewport.ScaleX; // +1px + } + } + else + { + // Frame the selected time range. + StartTime = SelectionStartTime; + EndTime = Viewport.RestrictEndTime(SelectionEndTime); + } + + if (EndTime > StartTime) + { + const double Duration = EndTime - StartTime; + if (Viewport.ZoomOnTimeInterval(StartTime - Duration * 0.1, Duration * 1.2)) + { + UpdateHorizontalScrollBar(); + bIsViewportDirty = true; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const FTimerNodePtr STimingView::GetTimerNode(uint64 TypeId) const +{ + const FTimerNodePtr* TimerNodePtrPtr = nullptr; + TSharedPtr Wnd = FTimingProfilerManager::Get()->GetProfilerWindow(); + if (Wnd && Wnd->TimersView) + { + TimerNodePtrPtr = Wnd->TimersView->GetTimerNode(TypeId); + if (TimerNodePtrPtr == nullptr) + { + // List of timers in TimersView not up to date? + // Refresh and try again. + Wnd->TimersView->RebuildTree(false); + TimerNodePtrPtr = Wnd->TimersView->GetTimerNode(TypeId); + } + } + return TimerNodePtrPtr ? *TimerNodePtrPtr : nullptr; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +TSharedRef STimingView::MakeTracksFilterMenu() +{ + FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr); + + MenuBuilder.BeginSection("QuickFilter", LOCTEXT("TracksFilterHeading", "Quick Filter")); + { + MenuBuilder.AddMenuEntry( + LOCTEXT("ShowAllGpuTracks", "GPU Track (Y)"), + LOCTEXT("ShowAllGpuTracks_Tooltip", "Show/hide the GPU track"), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &STimingView::ShowHideAllGpuTracks_Execute), + FCanExecuteAction::CreateLambda([] { return true; }), + FIsActionChecked::CreateSP(this, &STimingView::ShowHideAllGpuTracks_IsChecked)), + NAME_None, + EUserInterfaceActionType::ToggleButton + ); + + MenuBuilder.AddMenuEntry( + LOCTEXT("ShowAllCpuTracks", "CPU Thread Tracks (U)"), + LOCTEXT("ShowAllCpuTracks_Tooltip", "Show/hide all CPU tracks (and all CPU thread groups)"), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &STimingView::ShowHideAllCpuTracks_Execute), + FCanExecuteAction::CreateLambda([] { return true; }), + FIsActionChecked::CreateSP(this, &STimingView::ShowHideAllCpuTracks_IsChecked)), + NAME_None, + EUserInterfaceActionType::ToggleButton + ); + + MenuBuilder.AddMenuEntry( + LOCTEXT("ShowAllLoadingTracks", "Asset Loading Tracks (L)"), + LOCTEXT("ShowAllLoadingTracks_Tooltip", "Show/hide the Asset Loading tracks"), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &STimingView::ShowHideAllLoadingTracks_Execute), + FCanExecuteAction::CreateLambda([] { return true; }), + FIsActionChecked::CreateSP(this, &STimingView::ShowHideAllLoadingTracks_IsChecked)), + NAME_None, + EUserInterfaceActionType::ToggleButton + ); + + MenuBuilder.AddMenuEntry( + LOCTEXT("ShowAllIoTracks", "I/O Tracks (I)"), + LOCTEXT("ShowAllIoTracks_Tooltip", "Show/hide the I/O (File Activity) tracks"), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &STimingView::ShowHideAllIoTracks_Execute), + FCanExecuteAction::CreateLambda([] { return true; }), + FIsActionChecked::CreateSP(this, &STimingView::ShowHideAllIoTracks_IsChecked)), + NAME_None, + EUserInterfaceActionType::ToggleButton + ); + + MenuBuilder.AddMenuEntry( + LOCTEXT("AutoHideEmptyTracks", "Auto Hide Empty Tracks (V)"), + LOCTEXT("AutoHideEmptyTracks_Tooltip", "Auto hide empty tracks (ones without timing events in current viewport)"), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &STimingView::ToggleAutoHideEmptyTracks), + FCanExecuteAction::CreateLambda([] { return true; }), + FIsActionChecked::CreateSP(this, &STimingView::IsAutoHideEmptyTracksEnabled)), + NAME_None, + EUserInterfaceActionType::ToggleButton + ); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("ThreadGroups", LOCTEXT("ThreadGroupsHeading", "CPU Thread Groups")); + CreateThreadGroupsMenu(MenuBuilder); + MenuBuilder.EndSection(); + + //MenuBuilder.BeginSection("Tracks", LOCTEXT("TracksHeading", "Tracks")); + //CreateTracksMenu(MenuBuilder); + //MenuBuilder.EndSection(); + + return MenuBuilder.MakeWidget(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::CreateThreadGroupsMenu(FMenuBuilder& MenuBuilder) +{ + for (const auto& KV : ThreadGroups) + { + const FThreadGroup& ThreadGroup = KV.Value; + if (ThreadGroup.NumTimelines > 0) + { + MenuBuilder.AddMenuEntry( + FText::FromString(ThreadGroup.Name), + TAttribute(), // no tooltip + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &STimingView::ToggleTrackVisibilityByGroup_Execute, ThreadGroup.Name), + FCanExecuteAction::CreateLambda([] { return true; }), + FIsActionChecked::CreateSP(this, &STimingView::ToggleTrackVisibilityByGroup_IsChecked, ThreadGroup.Name)), + NAME_None, + EUserInterfaceActionType::ToggleButton + ); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::CreateTracksMenu(FMenuBuilder& MenuBuilder) +{ + for (int32 TrackIndex = 0; TrackIndex < TimingEventsTracks.Num(); ++TrackIndex) + { + const FTimingEventsTrack& Track = *TimingEventsTracks[TrackIndex]; + if (Track.GetHeight() > 0.0f || Layout.TargetMinTimelineH > 0.0f) + { + MenuBuilder.AddMenuEntry( + FText::FromString(Track.GetName()), + TAttribute(), // no tooltip + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &STimingView::ToggleTrackVisibility_Execute, Track.GetId()), + FCanExecuteAction::CreateLambda([] { return true; }), + FIsActionChecked::CreateSP(this, &STimingView::ToggleTrackVisibility_IsChecked, Track.GetId())), + NAME_None, + EUserInterfaceActionType::ToggleButton + ); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimingView::ShowHideAllCpuTracks_IsChecked() const +{ + return bShowHideAllCpuTracks; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::ShowHideAllCpuTracks_Execute() +{ + bShowHideAllCpuTracks = !bShowHideAllCpuTracks; + + for (int32 TrackIndex = 0; TrackIndex < TimingEventsTracks.Num(); ++TrackIndex) + { + FTimingEventsTrack& Track = *TimingEventsTracks[TrackIndex]; + if (Track.GetType() == ETimingEventsTrackType::Cpu) + { + Track.SetVisibilityFlag(bShowHideAllCpuTracks); + } + } + + for (auto& KV: ThreadGroups) + { + KV.Value.bIsVisible = bShowHideAllCpuTracks; + } + + HoveredTimingEvent.Reset(); + SelectedTimingEvent.Reset(); + + bAreTimingEventsTracksDirty = true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimingView::ShowHideAllGpuTracks_IsChecked() const +{ + return bShowHideAllGpuTracks; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::ShowHideAllGpuTracks_Execute() +{ + bShowHideAllGpuTracks = !bShowHideAllGpuTracks; + + for (int32 TrackIndex = 0; TrackIndex < TimingEventsTracks.Num(); ++TrackIndex) + { + FTimingEventsTrack& Track = *TimingEventsTracks[TrackIndex]; + if (Track.GetType() == ETimingEventsTrackType::Gpu) + { + Track.SetVisibilityFlag(bShowHideAllGpuTracks); + } + } + + HoveredTimingEvent.Reset(); + SelectedTimingEvent.Reset(); + + bAreTimingEventsTracksDirty = true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimingView::ShowHideAllLoadingTracks_IsChecked() const +{ + return bShowHideAllLoadingTracks; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::ShowHideAllLoadingTracks_Execute() +{ + bShowHideAllLoadingTracks = !bShowHideAllLoadingTracks; + + for (int32 TrackIndex = 0; TrackIndex < TimingEventsTracks.Num(); ++TrackIndex) + { + FTimingEventsTrack& Track = *TimingEventsTracks[TrackIndex]; + if (Track.GetType() == ETimingEventsTrackType::Loading) + { + Track.SetVisibilityFlag(bShowHideAllLoadingTracks); + } + } + + bAreTimingEventsTracksDirty = true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimingView::ShowHideAllIoTracks_IsChecked() const +{ + return bShowHideAllIoTracks; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::ShowHideAllIoTracks_Execute() +{ + bShowHideAllIoTracks = !bShowHideAllIoTracks; + + for (int32 TrackIndex = 0; TrackIndex < TimingEventsTracks.Num(); ++TrackIndex) + { + FTimingEventsTrack& Track = *TimingEventsTracks[TrackIndex]; + if (Track.GetType() == ETimingEventsTrackType::Io) + { + Track.SetVisibilityFlag(bShowHideAllIoTracks); + } + } + + bAreTimingEventsTracksDirty = true; + + if (bShowHideAllIoTracks) + { + bForceIoEventsUpdate = true; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimingView::IsAutoHideEmptyTracksEnabled() const +{ + return (Layout.TargetMinTimelineH == 0.0f); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::ToggleAutoHideEmptyTracks() +{ + if (Layout.TargetMinTimelineH == 0.0f) + { + Layout.TargetMinTimelineH = RealMinTimelineH; + } + else + { + Layout.TargetMinTimelineH = 0.0f; + } + + Layout.MinTimelineH = Layout.TargetMinTimelineH; // no layout animation + + for (auto& CachedTimelineKV : CachedTimelines) + { + CachedTimelineKV.Value->SetHeight(0.0f); + } + + Viewport.ScrollPosY = 0.0f; + UpdateVerticalScrollBar(); + bIsVerticalViewportDirty = true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimingView::ToggleTrackVisibility_IsChecked(uint64 InTrackId) const +{ + if (CachedTimelines.Contains(InTrackId)) + { + const FTimingEventsTrack* Track = CachedTimelines[InTrackId]; + return Track->IsVisible(); + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::ToggleTrackVisibility_Execute(uint64 InTrackId) +{ + if (CachedTimelines.Contains(InTrackId)) + { + FTimingEventsTrack* Track = CachedTimelines[InTrackId]; + Track->ToggleVisibility(); + + HoveredTimingEvent.Reset(); + SelectedTimingEvent.Reset(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool STimingView::ToggleTrackVisibilityByGroup_IsChecked(const TCHAR* InGroupName) const +{ + if (ThreadGroups.Contains(InGroupName)) + { + const FThreadGroup& ThreadGroup = ThreadGroups[InGroupName]; + return ThreadGroup.bIsVisible; + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void STimingView::ToggleTrackVisibilityByGroup_Execute(const TCHAR* InGroupName) +{ + if (ThreadGroups.Contains(InGroupName)) + { + FThreadGroup& ThreadGroup = ThreadGroups[InGroupName]; + ThreadGroup.bIsVisible = !ThreadGroup.bIsVisible; + + for (auto& KV : CachedTimelines) + { + FTimingEventsTrack& Track = *KV.Value; + if (Track.GetType() == ETimingEventsTrackType::Cpu && + Track.GetGroupName() == InGroupName) + { + Track.SetVisibilityFlag(ThreadGroup.bIsVisible); + } + } + + HoveredTimingEvent.Reset(); + SelectedTimingEvent.Reset(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingView.h b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingView.h new file mode 100644 index 000000000000..bd56c62152d7 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Private/Insights/Widgets/STimingView.h @@ -0,0 +1,545 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Input/CursorReply.h" +#include "Input/Reply.h" +#include "Layout/Geometry.h" +#include "TraceServices/AnalysisService.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/SWidget.h" + +// Insights +#include "Insights/Common/FixedCircularBuffer.h" +#include "Insights/ViewModels/GraphTrack.h" +#include "Insights/ViewModels/MarkersTimingTrack.h" +#include "Insights/ViewModels/TimerNode.h" +#include "Insights/ViewModels/TimeRulerTrack.h" +#include "Insights/ViewModels/TimingEvent.h" +#include "Insights/ViewModels/TimingEventsTrack.h" +#include "Insights/ViewModels/TimingTrackViewport.h" +#include "Insights/ViewModels/TimingViewDrawHelper.h" + +class FTimingViewDrawHelper; +class SScrollBar; + +/** The delegate to be invoked when the selection have been changed. */ +DECLARE_DELEGATE_TwoParams(FSelectionChangedDelegate, double /*StartTime*/, double /*EndTime*/); + +/** The delegate to be invoked when the timing event being hovered by the mouse has changed. Returns id of timing event or -1 (if no event is hovered). */ +DECLARE_DELEGATE_RetVal(int32, FHoveredEventChangedDelegate); + +/** The delegate to be invoked when the selected timing event has changed. Returns id of timing event or -1 (if no event is hovered). */ +DECLARE_DELEGATE_RetVal(int32, FSelectedEventChangedDelegate); + +/** Defines FLoadingTrackGetEventNameDelegate delegate interface. Returns the name for a timing event in a Loading track. */ +DECLARE_DELEGATE_RetVal_TwoParams(const TCHAR*, FLoadingTrackGetEventNameDelegate, uint32 /*Depth*/, const Trace::FLoadTimeProfilerCpuEvent& /*Event*/); + +/** A custom widget used to display timing events. */ +class STimingView : public SCompoundWidget +{ +private: + static constexpr float MinTooltipWidth = 110.0f; + +public: + /** Default constructor. */ + STimingView(); + + /** Virtual destructor. */ + virtual ~STimingView(); + + SLATE_BEGIN_ARGS(STimingView) + : _OnSelectionChanged() + , _OnHoveredEventChanged() + , _OnSelectedEventChanged() + { + _Clipping = EWidgetClipping::ClipToBounds; + } + + SLATE_EVENT(FSelectionChangedDelegate, OnSelectionChanged) + SLATE_EVENT(FHoveredEventChangedDelegate, OnHoveredEventChanged) + SLATE_EVENT(FSelectedEventChangedDelegate, OnSelectedEventChanged) + SLATE_END_ARGS() + + /** + * Construct this widget + * + * @param InArgs The declaration data for this widget + */ + void Construct(const FArguments& InArgs); + + TSharedRef MakeTracksFilterMenu(); + void CreateThreadGroupsMenu(FMenuBuilder& MenuBuilder); + void CreateTracksMenu(FMenuBuilder& MenuBuilder); + + bool ShowHideAllCpuTracks_IsChecked() const; + void ShowHideAllCpuTracks_Execute(); + + bool ShowHideAllGpuTracks_IsChecked() const; + void ShowHideAllGpuTracks_Execute(); + + bool ShowHideAllLoadingTracks_IsChecked() const; + void ShowHideAllLoadingTracks_Execute(); + + bool ShowHideAllIoTracks_IsChecked() const; + void ShowHideAllIoTracks_Execute(); + + bool IsAutoHideEmptyTracksEnabled() const; + void ToggleAutoHideEmptyTracks(); + + bool ToggleTrackVisibility_IsChecked(uint64 InTrackId) const; + void ToggleTrackVisibility_Execute(uint64 InTrackId); + + bool ToggleTrackVisibilityByGroup_IsChecked(const TCHAR* InGroupName) const; + void ToggleTrackVisibilityByGroup_Execute(const TCHAR* InGroupName); + + void EnableAssetLoadingMode(); + + /** Resets internal widget's data to the default one. */ + void Reset(); + + /** + * Ticks this widget. Override in derived classes, but always call the parent implementation. + * + * @param AllottedGeometry The space allotted for this widget + * @param InCurrentTime Current absolute real time + * @param InDeltaTime Real time passed since last tick + */ + virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; + + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; + + /** + * The system calls this method to notify the widget that a mouse button was pressed within it. This event is bubbled. + * + * @param MyGeometry The Geometry of the widget receiving the event + * @param MouseEvent Information about the input event + * + * @return Whether the event was handled along with possible requests for the system to take action. + */ + virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + /** + * The system calls this method to notify the widget that a mouse button was release within it. This event is bubbled. + * + * @param MyGeometry The Geometry of the widget receiving the event + * @param MouseEvent Information about the input event + * + * @return Whether the event was handled along with possible requests for the system to take action. + */ + virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + /** + * Called when a mouse button is double clicked. Override this in derived classes. + * + * @param InMyGeometry Widget geometry + * @param InMouseEvent Mouse button event + * + * @return Returns whether the event was handled, along with other possible actions + */ + virtual FReply OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + /** + * The system calls this method to notify the widget that a mouse moved within it. This event is bubbled. + * + * @param MyGeometry The Geometry of the widget receiving the event + * @param MouseEvent Information about the input event + * + * @return Whether the event was handled along with possible requests for the system to take action. + */ + virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + /** + * The system will use this event to notify a widget that the cursor has entered it. This event is NOT bubbled. + * + * @param MyGeometry The Geometry of the widget receiving the event + * @param MouseEvent Information about the input event + */ + virtual void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + /** + * The system will use this event to notify a widget that the cursor has left it. This event is NOT bubbled. + * + * @param MouseEvent Information about the input event + */ + virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override; + + /** + * Called when the mouse wheel is spun. This event is bubbled. + * + * @param MouseEvent Mouse event + * + * @return Returns whether the event was handled, along with other possible actions + */ + virtual FReply OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + /** + * Called when the user is dropping something onto a widget; terminates drag and drop. + * + * @param MyGeometry The geometry of the widget receiving the event. + * @param DragDropEvent The drag and drop event. + * + * @return A reply that indicated whether this event was handled. + */ + virtual FReply OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override; + + /** + * Called during drag and drop when the drag enters a widget. + * + * Enter/Leave events in slate are meant as lightweight notifications. + * So we do not want to capture mouse or set focus in response to these. + * However, OnDragEnter must also support external APIs (e.g. OLE Drag/Drop) + * Those require that we let them know whether we can handle the content + * being dragged OnDragEnter. + * + * The concession is to return a can_handled/cannot_handle + * boolean rather than a full FReply. + * + * @param MyGeometry The geometry of the widget receiving the event. + * @param DragDropEvent The drag and drop event. + * + * @return A reply that indicated whether the contents of the DragDropEvent can potentially be processed by this widget. + */ + virtual void OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override; + + /** + * Called during drag and drop when the drag leaves a widget. + * + * @param DragDropEvent The drag and drop event. + */ + virtual void OnDragLeave(const FDragDropEvent& DragDropEvent) override; + + /** + * Called during drag and drop when the the mouse is being dragged over a widget. + * + * @param MyGeometry The geometry of the widget receiving the event. + * @param DragDropEvent The drag and drop event. + * + * @return A reply that indicated whether this event was handled. + */ + virtual FReply OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override; + + /** + * Called when the system wants to know which cursor to display for this Widget. This event is bubbled. + * + * @return The cursor requested (can be None.) + */ + virtual FCursorReply OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const override; + + virtual bool SupportsKeyboardFocus() const override { return true; } + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; + virtual FReply OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; + + //////////////////////////////////////////////////////////////////////////////////////////////////// + + const FTimingTrackViewport& GetViewport() { return Viewport; } + + bool IsTimeSelected(double Time) const { return Time >= SelectionStartTime && Time < SelectionEndTime; } + bool IsTimeSelectedInclusive(double Time) const { return Time >= SelectionStartTime && Time <= SelectionEndTime; } + + void ScrollAtTime(double StartTime); + void CenterOnTimeInterval(double IntervalStartTime, double IntervalDuration); + void BringIntoView(double StartTime, double EndTime); + void SelectTimeInterval(double IntervalStartTime, double IntervalDuration); + void SetAndCenterOnTimeMarker(double Time); + void SelectToTimeMarker(double Time); + + bool AreTimeMarkersVisible() { return MarkersTrack.IsVisible(); } + void SetTimeMarkersVisible(bool bOnOff); + bool IsDrawOnlyBookmarksEnabled() { return MarkersTrack.IsBookmarksTrack(); } + void SetDrawOnlyBookmarks(bool bOnOff); + + bool IsGpuTrackVisible() const; + bool IsCpuTrackVisible(uint32 ThreadId) const; + +protected: + virtual FVector2D ComputeDesiredSize(float) const override + { + return FVector2D(16.0f, 16.0f); + } + + FTimingEventsTrack* AddTimingEventsTrack(uint64 TrackId, ETimingEventsTrackType TrackType, const FString& TrackName, const TCHAR* GroupName, int32 Order); + + void DrawTimingProfilerTrack(FTimingViewDrawHelper& Helper, FTimingEventsTrack& Track) const; + + const TCHAR* GetLoadTimeProfilerEventNameByPackageEventType(uint32 Depth, const Trace::FLoadTimeProfilerCpuEvent& Event) const; + const TCHAR* GetLoadTimeProfilerEventNameByExportEventType(uint32 Depth, const Trace::FLoadTimeProfilerCpuEvent& Event) const; + const TCHAR* GetLoadTimeProfilerEventNameByPackageName(uint32 Depth, const Trace::FLoadTimeProfilerCpuEvent& Event) const; + const TCHAR* GetLoadTimeProfilerEventNameByExportClassName(uint32 Depth, const Trace::FLoadTimeProfilerCpuEvent& Event) const; + const TCHAR* GetLoadTimeProfilerEventName(uint32 Depth, const Trace::FLoadTimeProfilerCpuEvent& Event) const; + void DrawLoadTimeProfilerTrack(FTimingViewDrawHelper& Helper, FTimingEventsTrack& Track) const; + + void UpdateIo(); + void DrawIoOverviewTrack(FTimingViewDrawHelper& Helper, FTimingEventsTrack& Track) const; + void DrawIoActivityTrack(FTimingViewDrawHelper& Helper, FTimingEventsTrack& Track) const; + + void DrawTimeRangeSelection(FDrawContext& DH) const; + + void ShowContextMenu(const FVector2D& ScreenSpacePosition, const FPointerEvent& MouseEvent); + + /** Binds our UI commands to delegates. */ + void BindCommands(); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Called when the user scrolls the horizontal scrollbar. + * + * @param ScrollOffset Scroll offset as a fraction between 0 and 1. + */ + void HorizontalScrollBar_OnUserScrolled(float ScrollOffset); + void UpdateHorizontalScrollBar(); + + /** + * Called when the user scrolls the vertical scrollbar. + * + * @param ScrollOffset Scroll offset as a fraction between 0 and 1. + */ + void VerticalScrollBar_OnUserScrolled(float ScrollOffset); + void UpdateVerticalScrollBar(); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + + void UpdateAggregatedStats(); + + void UpdateHoveredTimingEvent(float MX, float MY); + + bool SearchTimingEvent(const double InStartTime, + const double InEndTime, + TFunctionRef InPredicate, + FTimingEvent& InOutTimingEvent, + bool bInStopAtFirstMatch, + bool bInSearchForLargestEvent) const; + + void OnSelectedTimingEventChanged(); + void SelectHoveredTimingEvent(); + void SelectLeftTimingEvent(); + void SelectRightTimingEvent(); + void SelectUpTimingEvent(); + void SelectDownTimingEvent(); + + void FrameSelection(); + + const FTimerNodePtr GetTimerNode(uint64 TypeId) const; + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // FrameSelectionChanged Event + +public: + /** + * The event to execute when the selected frames have been changed. + * + * @param FrameStartIndex - The index of the first frame selected. + * @param FrameEndIndex - The index of the last frame selected. + * + */ + DECLARE_EVENT_TwoParams(STimingView, FFrameSelectionChangedEvent, int32 /*FrameStartIndex*/, int32 /*FrameEndIndex*/); + FFrameSelectionChangedEvent& OnFrameSelectionChanged() + { + return FrameSelectionChangedEvent; + } + +protected: + /** The event to execute when the selected frames have been changed. */ + FFrameSelectionChangedEvent FrameSelectionChangedEvent; + + //////////////////////////////////////////////////////////////////////////////////////////////////// + +protected: + bool bShowHideAllGpuTracks; + bool bShowHideAllCpuTracks; + bool bShowHideAllLoadingTracks; + bool bShowHideAllIoTracks; + + //////////////////////////////////////////////////////////// + + /** The track's viewport. Encapsulates info about position and scale. */ + FTimingTrackViewport Viewport; + + bool bIsViewportDirty; + bool bIsVerticalViewportDirty; + + //////////////////////////////////////////////////////////// + + /** All created tracks. + * Maps track id to track pointer. + */ + TMap CachedTimelines; + + //TODO: TArray TopTracks; /**< tracks docked on top, in order to be displayed (top to bottom) */ + //TODO: TArray ScrollableTracks; /**< tracks in scrollable area, in order to be displayed (top to bottom) */ + //TODO: TArray BottomTracks; /**< tracks docked on bottom, in order to be displayed (top to bottom) */ + //TODO: TArray ForegorundTracks; /**< tracks to draw over top/scrollable/bottom tracks (can use entire area), in order to be displayed (back to front) */ + + //////////////////////////////////////////////////////////// + + TArray TimingEventsTracks; /**< all timing events tracks in order to be displayed */ + + bool bAreTimingEventsTracksDirty; + + bool bUseDownSampling; + + //////////////////////////////////////////////////////////// + // Cpu/Gpu + + FTimingEventsTrack* GpuTrack; + TMap CpuTracks; /**< maps thread id to track pointer */ + + struct FThreadGroup + { + const TCHAR* Name; /**< the thread group name; pointer to string owned by ThreadProvider */ + bool bIsVisible; + uint32 NumTimelines; + }; + + TMap ThreadGroups; /**< maps thread group name to thread group info */ + + //////////////////////////////////////////////////////////// + // Asset Loading + + FTimingEventsTrack* LoadingMainThreadTrack; + FTimingEventsTrack* LoadingAsyncThreadTrack; + + uint32 LoadingMainThreadId; + uint32 LoadingAsyncThreadId; + + FLoadingTrackGetEventNameDelegate LoadingGetEventNameFn; + + bool bAssetLoadingMode; + + FString EventAggregationStr; + FString ObjectTypeAggregationStr; + + //////////////////////////////////////////////////////////// + // File activity (I/O) + + FTimingEventsTrack* IoOverviewTrack; + FTimingEventsTrack* IoActivityTrack; + + bool bForceIoEventsUpdate; + + bool bMergeIoLanes; + + struct FIoTimingEvent + { + double StartTime; + double EndTime; + uint32 Depth; + uint32 Type; // Trace::EFileActivityType + Failed + const TCHAR* Path; + }; + + /** All IO events, cached. */ + TArray AllIoEvents; + + //////////////////////////////////////////////////////////// + + /** The time ruler track. */ + FTimeRulerTrack TimeRulerTrack; + + /** The time markers track. */ + FMarkersTimingTrack MarkersTrack; + + /** A graph track for frame times. */ + FFramesGraphTrack GraphTrack; + + //////////////////////////////////////////////////////////// + + /** Horizontal scroll bar, used for scrolling timing events' viewport. */ + TSharedPtr HorizontalScrollBar; + + /** Vertical scroll bar, used for scrolling timing events' viewport. */ + TSharedPtr VerticalScrollBar; + + //////////////////////////////////////////////////////////// + + /** The current mouse position. */ + FVector2D MousePosition; + + /** Mouse position during the call on mouse button down. */ + FVector2D MousePositionOnButtonDown; + double ViewportStartTimeOnButtonDown; + float ViewportScrollPosYOnButtonDown; + + /** Mouse position during the call on mouse button up. */ + FVector2D MousePositionOnButtonUp; + + bool bIsLMB_Pressed; + bool bIsRMB_Pressed; + + bool bIsSpaceBarKeyPressed; + bool bIsDragging; + + //////////////////////////////////////////////////////////// + // Panning + + /** + * True, if the user is currently interactively panning the view (horizontally and/or vertically), + * either by holding the right mouse button and dragging + * or by holding spacebar pressed and dragging with left mouse button. + */ + bool bIsPanning; + + /** How to pan. */ + enum class EPanningMode : uint8 + { + None = 0, + Horizontal = 0x01, + Vertical = 0x02, + HorizontalAndVertical = Horizontal | Vertical, + }; + EPanningMode PanningMode; + + //////////////////////////////////////////////////////////// + // Selection + + /** True, if the user is currently changing the selection (by holding the left mouse button and dragging). */ + bool bIsSelecting; + + double SelectionStartTime; + double SelectionEndTime; + + mutable float TooltipWidth; + mutable float TooltipAlpha; + + FTimingEvent HoveredTimingEvent; + FTimingEvent SelectedTimingEvent; + + enum class ESelectionType + { + None, + TimeRange, + TimingEvent + }; + ESelectionType LastSelectionType; + + double TimeMarker; + + //////////////////////////////////////////////////////////// + // Misc + + FGeometry ThisGeometry; + + const FSlateBrush* WhiteBrush; + const FSlateFontInfo MainFont; + + FTimingEventsTrackLayout Layout; + + // Debug stats + int32 NumUpdatedEvents; + TFixedCircularBuffer TimelineCacheUpdateDurationHistory; + TFixedCircularBuffer TimeMarkerCacheUpdateDurationHistory; + mutable TFixedCircularBuffer DrawDurationHistory; + mutable TFixedCircularBuffer OnPaintDurationHistory; + mutable uint64 LastOnPaintTime; + + //////////////////////////////////////////////////////////// + // Delegates + + FSelectionChangedDelegate OnSelectionChanged; + FHoveredEventChangedDelegate OnHoveredEventChanged; + FSelectedEventChangedDelegate OnSelectedEventChanged; +}; diff --git a/Engine/Source/Developer/TraceInsights/Public/Insights/IUnrealInsightsModule.h b/Engine/Source/Developer/TraceInsights/Public/Insights/IUnrealInsightsModule.h new file mode 100644 index 000000000000..f6d54d130b05 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Public/Insights/IUnrealInsightsModule.h @@ -0,0 +1,26 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "Framework/Docking/TabManager.h" + +/** Interface for an Unreal Insights module. */ +class IUnrealInsightsModule : public IModuleInterface +{ +public: + /** + * Called when application starts and a new layout is created. It allows the module to create its own areas. + * + * @param NewLayout The newly created layout. + */ + virtual void OnNewLayout(TSharedRef NewLayout) = 0; + + /** + * Called after application layout was restored. + * + * @param TabManager The tab manager used by Unreal Insights tools. + */ + virtual void OnLayoutRestored(TSharedPtr TabManager) = 0; +}; diff --git a/Engine/Source/Developer/TraceInsights/Public/Insights/TimingProfilerCommon.h b/Engine/Source/Developer/TraceInsights/Public/Insights/TimingProfilerCommon.h new file mode 100644 index 000000000000..386ef4203013 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Public/Insights/TimingProfilerCommon.h @@ -0,0 +1,32 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Logging/LogMacros.h" +#include "Stats/Stats.h" +#include "Stats/StatsMisc.h" + +DECLARE_LOG_CATEGORY_EXTERN(TimingProfiler, Log, All); + +#define DEBUG_TIMING_PROFILER_PERFORMANCE 0 + +#if DEBUG_TIMING_PROFILER_PERFORMANCE==1 +#define TP_SCOPE_LOG_TIME(arg0,arg1) SCOPE_LOG_TIME(arg0,arg1) +#else +#define TP_SCOPE_LOG_TIME(arg0,arg1) +#endif + +DECLARE_STATS_GROUP(TEXT("TimingProfiler"), STATGROUP_TimingProfiler, STATCAT_Advanced); + +/** Time spent on Frame Track drawing. */ +DECLARE_CYCLE_STAT_EXTERN(TEXT("FrameTrackOnPaint"), STAT_FT_OnPaint, STATGROUP_TimingProfiler, ); + +/** Time spent on Graph Track drawing. */ +DECLARE_CYCLE_STAT_EXTERN(TEXT("GraphTrackOnPaint"), STAT_GT_OnPaint, STATGROUP_TimingProfiler, ); + +/** Time spent on Timing Track drawing. */ +DECLARE_CYCLE_STAT_EXTERN(TEXT("TimingTrackOnPaint"), STAT_TT_OnPaint, STATGROUP_TimingProfiler, ); + +/** Time spent on ticking profiler manager. */ +DECLARE_CYCLE_STAT_EXTERN(TEXT("TimingProfilerTick"), STAT_TPM_Tick, STATGROUP_TimingProfiler, ); diff --git a/Engine/Source/Developer/TraceInsights/Public/Insights/Version.h b/Engine/Source/Developer/TraceInsights/Public/Insights/Version.h new file mode 100644 index 000000000000..6f12290fc9be --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/Public/Insights/Version.h @@ -0,0 +1,20 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +// See https://semver.org/ + +#define UNREAL_INSIGHTS_VERSION_MAJOR 0 +#define UNREAL_INSIGHTS_VERSION_MINOR 6 +#define UNREAL_INSIGHTS_VERSION_PATCH 0 + +#define UNREAL_INSIGHTS_VERSION_STRING "0.06" + +/** Extra identifier string (like: "alpha", "beta.1", "beta.2", "test", "featureX", etc.). */ +//#define UNREAL_INSIGHTS_VERSION_ID_STRING "alpha" + +#ifdef UNREAL_INSIGHTS_VERSION_ID_STRING +#define UNREAL_INSIGHTS_VERSION_STRING_EX "v" UNREAL_INSIGHTS_VERSION_STRING "-" UNREAL_INSIGHTS_VERSION_ID_STRING +#else +#define UNREAL_INSIGHTS_VERSION_STRING_EX "v" UNREAL_INSIGHTS_VERSION_STRING +#endif diff --git a/Engine/Source/Developer/TraceInsights/TraceInsights.Build.cs b/Engine/Source/Developer/TraceInsights/TraceInsights.Build.cs new file mode 100644 index 000000000000..0cd99ac66f90 --- /dev/null +++ b/Engine/Source/Developer/TraceInsights/TraceInsights.Build.cs @@ -0,0 +1,54 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class TraceInsights : ModuleRules +{ + public TraceInsights(ReadOnlyTargetRules Target) : base(Target) + { + PrivateIncludePaths.AddRange + ( + new string[] { + "Developer/TraceInsights/Private", + } + ); + + PublicDependencyModuleNames.AddRange + ( + new string[] { + "Core", + "ApplicationCore", + "InputCore", + "RHI", + "RenderCore", + "Slate", + "EditorStyle", + "TraceLog", + "TraceServices", + "DesktopPlatform", + } + ); + + if (Target.bBuildEditor) + { + PrivateDependencyModuleNames.AddRange( + new string[] { + "Engine", + } + ); + } + + PrivateDependencyModuleNames.AddRange( + new string[] { + "SlateCore", + } + ); + + PrivateIncludePathModuleNames.AddRange( + new string[] { + "Messaging", + "SessionServices", + } + ); + } +} diff --git a/Engine/Source/Developer/TraceServices/Private/AnalysisService.cpp b/Engine/Source/Developer/TraceServices/Private/AnalysisService.cpp new file mode 100644 index 000000000000..4669aacd9e0d --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/AnalysisService.cpp @@ -0,0 +1,187 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TraceServices/AnalysisService.h" +#include "AnalysisServicePrivate.h" +#include "Trace/Analyzer.h" +#include "Trace/Analysis.h" +#include "Trace/DataStream.h" +#include "HAL/PlatformFile.h" +#include "Analyzers/MiscTraceAnalysis.h" +#include "Analyzers/LogTraceAnalysis.h" +#include "Math/RandomStream.h" +#include "ModuleServicePrivate.h" +#include "Model/LogPrivate.h" +#include "Model/FramesPrivate.h" +#include "Model/BookmarksPrivate.h" +#include "Model/ThreadsPrivate.h" + +namespace Trace +{ + +void FAnalysisSessionLock::ReadAccessCheck() const +{ + checkf(IsReadOnly || FPlatformTLS::GetCurrentThreadId() == OwnerThread, TEXT("Trying to read from session while someone else is writing")); +} + +void FAnalysisSessionLock::WriteAccessCheck() const +{ + checkf(!IsReadOnly, TEXT("Trying to edit session while in read only mode")); + checkf(FPlatformTLS::GetCurrentThreadId() == OwnerThread, TEXT("Trying to edit session from thread without write access")); +} + +void FAnalysisSessionLock::BeginRead() +{ + CriticalSection.Lock(); + check(OwnerThread == 0); + OwnerThread = FPlatformTLS::GetCurrentThreadId(); + IsReadOnly = true; +} + +void FAnalysisSessionLock::EndRead() +{ + check(FPlatformTLS::GetCurrentThreadId() == OwnerThread); + OwnerThread = 0; + IsReadOnly = false; + CriticalSection.Unlock(); +} + +void FAnalysisSessionLock::BeginEdit() +{ + CriticalSection.Lock(); + check(OwnerThread == 0); + check(!IsReadOnly); + OwnerThread = FPlatformTLS::GetCurrentThreadId(); +} + +void FAnalysisSessionLock::EndEdit() +{ + check(OwnerThread == FPlatformTLS::GetCurrentThreadId()); + OwnerThread = 0; + CriticalSection.Unlock(); +} + +FAnalysisSession::FAnalysisSession(const TCHAR* SessionName) + : Name(SessionName) + , DurationSeconds(0.0) + , Allocator(32 << 20) + , StringStore(Allocator) +{ + +} + +FAnalysisSession::~FAnalysisSession() +{ + for (auto& KV : Providers) + { + IProvider* Provider = KV.Value; + delete Provider; + } +} + +void FAnalysisSession::AddProvider(const FName& InName, IProvider* Provider) +{ + Providers.Add(InName, Provider); +} + +const IProvider* FAnalysisSession::ReadProviderPrivate(const FName& InName) const +{ + IProvider* const* FindIt = Providers.Find(InName); + if (FindIt) + { + return *FindIt; + } + else + { + return nullptr; + } +} + +FAnalysisService::FAnalysisService(FModuleService& InModuleService) + : ModuleService(InModuleService) +{ + +} + +FAnalysisService::FAnalysisWorker::FAnalysisWorker(FAnalysisService& InOuter, TUniquePtr&& InDataStream, TSharedRef InAnalysisSession) + : Outer(InOuter) + , DataStream(MoveTemp(InDataStream)) + , AnalysisSession(InAnalysisSession) +{ + +} + +void FAnalysisService::AnalyzeInternal(TSharedRef AnalysisSession, Trace::IInDataStream* DataStream) +{ + OnAnalysisStarted().Broadcast(AnalysisSession); + + TArray Analyzers; + { + IAnalysisSession& Session = AnalysisSession.Get(); + Trace::FAnalysisSessionEditScope _(Session); + FBookmarkProvider* BookmarkProvider = new FBookmarkProvider(Session); + Session.AddProvider(FBookmarkProvider::ProviderName, BookmarkProvider); + FLogProvider* LogProvider = new FLogProvider(Session); + Session.AddProvider(FLogProvider::ProviderName, LogProvider); + FThreadProvider* ThreadProvider = new FThreadProvider(Session); + Session.AddProvider(FThreadProvider::ProviderName, ThreadProvider); + FFrameProvider* FrameProvider = new FFrameProvider(Session); + Session.AddProvider(FFrameProvider::ProviderName, FrameProvider); + Analyzers.Add(new FMiscTraceAnalyzer(Session, *ThreadProvider, *BookmarkProvider, *LogProvider, *FrameProvider)); + Analyzers.Add(new FLogTraceAnalyzer(Session, *LogProvider)); + ModuleService.OnAnalysisBegin(Session, Analyzers); + } + + FAnalysisContext Context; + for (Trace::IAnalyzer* Analyzer : Analyzers) + { + Context.AddAnalyzer(*Analyzer); + } + Trace::FAnalysisProcessor Processor = Context.Process(); + + Processor.Start(*DataStream); + + for (Trace::IAnalyzer* Analyzer : Analyzers) + { + delete Analyzer; + } + Analyzers.Empty(); + + delete DataStream; + + AnalysisSession->SetComplete(); + + OnAnalysisFinished().Broadcast(AnalysisSession); +} + +void FAnalysisService::FAnalysisWorker::DoWork() +{ + Outer.AnalyzeInternal(AnalysisSession.ToSharedRef(), DataStream.Release()); + AnalysisSession = nullptr; +} + +FAnalysisService::~FAnalysisService() +{ + for (TSharedPtr> Task : Tasks) + { + Task->EnsureCompletion(); + } +} + +TSharedPtr FAnalysisService::Analyze(const TCHAR* SessionName, TUniquePtr&& InDataStream) +{ + TSharedRef AnalysisSession = MakeShared(SessionName); + TUniquePtr DataStream = MoveTemp(InDataStream); + AnalyzeInternal(AnalysisSession, DataStream.Release()); + return AnalysisSession; +} + +TSharedPtr FAnalysisService::StartAnalysis(const TCHAR* SessionName, TUniquePtr&& DataStream) +{ + TSharedRef AnalysisSession = MakeShared(SessionName); + TSharedPtr> Task = MakeShared>(*this, MoveTemp(DataStream), AnalysisSession); + Tasks.Add(Task); + Task->StartBackgroundTask(); + return AnalysisSession; +} + +} diff --git a/Engine/Source/Developer/TraceServices/Private/AnalysisServicePrivate.h b/Engine/Source/Developer/TraceServices/Private/AnalysisServicePrivate.h new file mode 100644 index 000000000000..8c00c3bf9e29 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/AnalysisServicePrivate.h @@ -0,0 +1,110 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/AnalysisService.h" +#include "Async/AsyncWork.h" +#include "Containers/HashTable.h" +#include "Misc/ScopeLock.h" +#include "Common/StringStore.h" + +namespace Trace +{ + +class FModuleService; + +class FAnalysisSessionLock +{ +public: + void ReadAccessCheck() const; + void WriteAccessCheck() const; + + void BeginRead(); + void EndRead(); + + void BeginEdit(); + void EndEdit(); + +private: + FCriticalSection CriticalSection; + uint32 OwnerThread = 0; + bool IsReadOnly = false; +}; + +class FAnalysisSession + : public IAnalysisSession +{ +public: + FAnalysisSession(const TCHAR* SessionName); + virtual ~FAnalysisSession(); + + virtual const TCHAR* GetName() const { return *Name; } + virtual bool IsAnalysisComplete() const override { return IsComplete; } + virtual double GetDurationSeconds() const { Lock.ReadAccessCheck(); return DurationSeconds; } + virtual void UpdateDurationSeconds(double Duration) override { Lock.WriteAccessCheck(); DurationSeconds = FMath::Max(Duration, DurationSeconds); } + void SetComplete() { IsComplete = true; } + + virtual ILinearAllocator& GetLinearAllocator() override { return Allocator; } + virtual const TCHAR* StoreString(const TCHAR* String) override { return StringStore.Store(String); } + + virtual void BeginRead() const override { Lock.BeginRead(); } + virtual void EndRead() const override { Lock.EndRead(); } + + virtual void BeginEdit() override { Lock.BeginEdit(); } + virtual void EndEdit() override { Lock.EndEdit(); } + + virtual void ReadAccessCheck() const override { return Lock.ReadAccessCheck(); } + virtual void WriteAccessCheck() override { return Lock.WriteAccessCheck(); } + + virtual void AddProvider(const FName& Name, IProvider* Provider) override; + +private: + virtual const IProvider* ReadProviderPrivate(const FName& Name) const override; + + mutable FAnalysisSessionLock Lock; + + FString Name; + bool IsComplete = false; + double DurationSeconds = 0.0; + FSlabAllocator Allocator; + FStringStore StringStore; + TMap Providers; +}; + +class FAnalysisService + : public IAnalysisService +{ +public: + FAnalysisService(FModuleService& ModuleService); + virtual ~FAnalysisService(); + virtual TSharedPtr Analyze(const TCHAR* SessionName, TUniquePtr&& DataStream) override; + virtual TSharedPtr StartAnalysis(const TCHAR* SessionName, TUniquePtr&& DataStream) override; + virtual FAnalysisStartedEvent& OnAnalysisStarted() override { return AnalysisStartedEvent; } + virtual FAnalysisFinishedEvent& OnAnalysisFinished() override { return AnalysisFinishedEvent; } + +private: + class FAnalysisWorker : public FNonAbandonableTask + { + public: + FAnalysisWorker(FAnalysisService& Outer, TUniquePtr&& InDataStream, TSharedRef InAnalysisSession); + void DoWork(); + FORCEINLINE TStatId GetStatId() const + { + RETURN_QUICK_DECLARE_CYCLE_STAT(FAnalysisWorker, STATGROUP_ThreadPoolAsyncTasks); + } + + private: + FAnalysisService& Outer; + TUniquePtr DataStream; + TSharedPtr AnalysisSession; + }; + + void AnalyzeInternal(TSharedRef AnalysisSession, Trace::IInDataStream* DataStream); + + FModuleService& ModuleService; + FAnalysisStartedEvent AnalysisStartedEvent; + FAnalysisFinishedEvent AnalysisFinishedEvent; + TArray>> Tasks; +}; + +} diff --git a/Engine/Source/Developer/TraceServices/Private/Analyzers/CpuProfilerTraceAnalysis.cpp b/Engine/Source/Developer/TraceServices/Private/Analyzers/CpuProfilerTraceAnalysis.cpp new file mode 100644 index 000000000000..fcd3ba876e61 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Analyzers/CpuProfilerTraceAnalysis.cpp @@ -0,0 +1,123 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +#include "CpuProfilerTraceAnalysis.h" +#include "AnalysisServicePrivate.h" +#include "Common/Utils.h" +#include "TraceServices/Model/Threads.h" + +FCpuProfilerAnalyzer::FCpuProfilerAnalyzer(Trace::IAnalysisSession& InSession, Trace::FTimingProfilerProvider& InTimingProfilerProvider) + : Session(InSession) + , TimingProfilerProvider(InTimingProfilerProvider) +{ + +} + +void FCpuProfilerAnalyzer::OnAnalysisBegin(const FOnAnalysisContext& Context) +{ + auto& Builder = Context.InterfaceBuilder; + + Builder.RouteEvent(RouteId_EventSpec, "CpuProfiler", "EventSpec"); + Builder.RouteEvent(RouteId_EventBatch, "CpuProfiler", "EventBatch"); + Builder.RouteEvent(RouteId_EndCapture, "CpuProfiler", "EndCapture"); +} + +void FCpuProfilerAnalyzer::OnAnalysisEnd() +{ +} + +void FCpuProfilerAnalyzer::OnEvent(uint16 RouteId, const FOnEventContext& Context) +{ + Trace::FAnalysisSessionEditScope _(Session); + + const auto& EventData = Context.EventData; + switch (RouteId) + { + case RouteId_EventSpec: + { + uint16 Id = EventData.GetValue("Id").As(); + if (ScopeIdToEventIdMap.Contains(Id)) + { + TimingProfilerProvider.SetTimerName(ScopeIdToEventIdMap[Id], reinterpret_cast(EventData.GetAttachment())); + } + else + { + ScopeIdToEventIdMap.Add(Id, TimingProfilerProvider.AddCpuTimer(reinterpret_cast(EventData.GetAttachment()))); + } + break; + } + case RouteId_EventBatch: + case RouteId_EndCapture: + { + TotalEventSize += EventData.GetTotalSize(); + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + TSharedRef ThreadState = GetThreadState(ThreadId); + uint64 LastCycle = ThreadState->LastCycle; + uint64 BufferSize = EventData.GetAttachmentSize(); + const uint8* BufferPtr = EventData.GetAttachment(); + const uint8* BufferEnd = BufferPtr + BufferSize; + while (BufferPtr < BufferEnd) + { + uint64 DecodedCycle = FTraceAnalyzerUtils::Decode7bit(BufferPtr); + uint64 ActualCycle = (DecodedCycle >> 1) + LastCycle; + LastCycle = ActualCycle; + if (DecodedCycle & 1ull) + { + EventScopeState& ScopeState = ThreadState->ScopeStack.AddDefaulted_GetRef(); + ScopeState.StartCycle = ActualCycle; + uint16 SpecId = FTraceAnalyzerUtils::Decode7bit(BufferPtr); + uint32* FindIt = ScopeIdToEventIdMap.Find(SpecId); + if (!FindIt) + { + ScopeState.EventTypeId = ScopeIdToEventIdMap.Add(SpecId, TimingProfilerProvider.AddCpuTimer(TEXT(""))); + } + else + { + ScopeState.EventTypeId = *FindIt; + } + Trace::FTimingProfilerEvent Event; + Event.TimerIndex = ScopeState.EventTypeId; + ThreadState->Timeline->AppendBeginEvent(Context.SessionContext.TimestampFromCycle(ScopeState.StartCycle), Event); + ++TotalScopeCount; + } + else if (ThreadState->ScopeStack.Num()) + { + ThreadState->ScopeStack.Pop(); + ThreadState->Timeline->AppendEndEvent(Context.SessionContext.TimestampFromCycle(ActualCycle)); + } + } + check(BufferPtr == BufferEnd); + if (LastCycle) + { + double LastTimestamp = Context.SessionContext.TimestampFromCycle(LastCycle); + Session.UpdateDurationSeconds(LastTimestamp); + if (RouteId == RouteId_EndCapture) + { + + while (ThreadState->ScopeStack.Num()) + { + ThreadState->ScopeStack.Pop(); + ThreadState->Timeline->AppendEndEvent(LastTimestamp); + } + } + } + ThreadState->LastCycle = LastCycle; + BytesPerScope = double(TotalEventSize) / double(TotalScopeCount); + break; + } + } +} + +TSharedRef FCpuProfilerAnalyzer::GetThreadState(uint32 ThreadId) +{ + if (!ThreadStatesMap.Contains(ThreadId)) + { + TSharedRef ThreadState = MakeShared(); + ThreadState->Timeline = &TimingProfilerProvider.EditCpuThreadTimeline(ThreadId); + ThreadStatesMap.Add(ThreadId, ThreadState); + return ThreadState; + } + else + { + return ThreadStatesMap[ThreadId]; + } +} + diff --git a/Engine/Source/Developer/TraceServices/Private/Analyzers/CpuProfilerTraceAnalysis.h b/Engine/Source/Developer/TraceServices/Private/Analyzers/CpuProfilerTraceAnalysis.h new file mode 100644 index 000000000000..6e16f4733301 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Analyzers/CpuProfilerTraceAnalysis.h @@ -0,0 +1,54 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Trace/Trace.h" +#include "Trace/Analyzer.h" +#include "Containers/UnrealString.h" +#include "Model/TimingProfilerPrivate.h" + +namespace Trace +{ + class IAnalysisSession; +} + +class FCpuProfilerAnalyzer + : public Trace::IAnalyzer +{ +public: + FCpuProfilerAnalyzer(Trace::IAnalysisSession& Session, Trace::FTimingProfilerProvider& TimingProfilerProvider); + virtual void OnAnalysisBegin(const FOnAnalysisContext& Context) override; + virtual void OnEvent(uint16 RouteId, const FOnEventContext& Context) override; + virtual void OnAnalysisEnd() override; + +private: + struct EventScopeState + { + uint64 StartCycle; + uint32 EventTypeId; + }; + + struct FThreadState + { + TArray ScopeStack; + Trace::FTimingProfilerProvider::TimelineInternal* Timeline; + double LastCycle = 0.0; + }; + + TSharedRef GetThreadState(uint32 ThreadId); + + enum : uint16 + { + RouteId_EventSpec, + RouteId_EventBatch, + RouteId_EndCapture, + }; + + Trace::IAnalysisSession& Session; + Trace::FTimingProfilerProvider& TimingProfilerProvider; + TMap> ThreadStatesMap; + TMap ScopeIdToEventIdMap; + uint64 TotalEventSize = 0; + uint64 TotalScopeCount = 0; + double BytesPerScope = 0.0; +}; diff --git a/Engine/Source/Developer/TraceServices/Private/Analyzers/GpuProfilerTraceAnalysis.cpp b/Engine/Source/Developer/TraceServices/Private/Analyzers/GpuProfilerTraceAnalysis.cpp new file mode 100644 index 000000000000..45f54903315b --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Analyzers/GpuProfilerTraceAnalysis.cpp @@ -0,0 +1,89 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +#include "GpuProfilerTraceAnalysis.h" +#include "AnalysisServicePrivate.h" +#include "Common/Utils.h" + +FGpuProfilerAnalyzer::FGpuProfilerAnalyzer(Trace::FAnalysisSession& InSession, Trace::FTimingProfilerProvider& InTimingProfilerProvider) + : Session(InSession) + , TimingProfilerProvider(InTimingProfilerProvider) + , Timeline(TimingProfilerProvider.EditGpuTimeline()) + , Calibrated(false) +{ + +} + +void FGpuProfilerAnalyzer::OnAnalysisBegin(const FOnAnalysisContext& Context) +{ + auto& Builder = Context.InterfaceBuilder; + + Builder.RouteEvent(RouteId_EventSpec, "GpuProfiler", "EventSpec"); + Builder.RouteEvent(RouteId_Frame, "GpuProfiler", "Frame"); +} + +void FGpuProfilerAnalyzer::OnEvent(uint16 RouteId, const FOnEventContext& Context) +{ + Trace::FAnalysisSessionEditScope _(Session); + + const auto& EventData = Context.EventData; + + switch (RouteId) + { + case RouteId_EventSpec: + { + uint64 EventType = EventData.GetValue("EventType").As(); + FString EventName(reinterpret_cast(EventData.GetAttachment()), EventData.GetValue("NameLength").As()); + EventTypeMap.Add(EventType, TimingProfilerProvider.AddGpuTimer(*EventName)); + break; + } + case RouteId_Frame: + { + uint64 BufferSize = EventData.GetAttachmentSize(); + const uint8* BufferPtr = EventData.GetAttachment(); + const uint8* BufferEnd = BufferPtr + BufferSize; + + uint32 CurrentDepth = 0; + + uint64 LastTimestamp = EventData.GetValue("TimestampBase").As(); + double LastTime = 0.0; + while (BufferPtr < BufferEnd) + { + uint64 DecodedTimestamp = FTraceAnalyzerUtils::Decode7bit(BufferPtr); + uint64 ActualTimestamp = (DecodedTimestamp >> 1) + LastTimestamp; + LastTimestamp = ActualTimestamp; + LastTime = GpuTimestampToSessionTime(ActualTimestamp); + if (DecodedTimestamp & 1ull) + { + uint64 EventType = *reinterpret_cast(BufferPtr); + BufferPtr += sizeof(uint64); + check(EventTypeMap.Contains(EventType)); + Trace::FTimingProfilerEvent Event; + Event.TimerIndex = EventTypeMap[EventType]; + Timeline.AppendBeginEvent(LastTime, Event); + ++CurrentDepth; + } + else + { + check(CurrentDepth > 0); + --CurrentDepth; + Timeline.AppendEndEvent(LastTime); + } + } + Session.UpdateDurationSeconds(LastTime); + check(BufferPtr == BufferEnd); + check(CurrentDepth == 0); + break; + } + + } +} + +double FGpuProfilerAnalyzer::GpuTimestampToSessionTime(uint64 GpuMicroseconds) +{ + if (!Calibrated) + { + uint64 SessionTimeMicroseconds = uint64(Session.GetDurationSeconds() * 1000000.0); + GpuTimeOffset = GpuMicroseconds - SessionTimeMicroseconds; + Calibrated = true; + } + return (GpuMicroseconds - GpuTimeOffset) / 1000000.0; +} diff --git a/Engine/Source/Developer/TraceServices/Private/Analyzers/GpuProfilerTraceAnalysis.h b/Engine/Source/Developer/TraceServices/Private/Analyzers/GpuProfilerTraceAnalysis.h new file mode 100644 index 000000000000..05c16807df0b --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Analyzers/GpuProfilerTraceAnalysis.h @@ -0,0 +1,38 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Trace/Trace.h" +#include "Trace/Analyzer.h" +#include "Model/TimingProfilerPrivate.h" + +namespace Trace +{ + class FAnalysisSession; +} + +class FGpuProfilerAnalyzer + : public Trace::IAnalyzer +{ +public: + FGpuProfilerAnalyzer(Trace::FAnalysisSession& Session, Trace::FTimingProfilerProvider& TimingProfilerProvider); + virtual void OnAnalysisBegin(const FOnAnalysisContext& Context) override; + virtual void OnEvent(uint16 RouteId, const FOnEventContext& Context) override; + virtual void OnAnalysisEnd() override {}; + +private: + enum : uint16 + { + RouteId_EventSpec, + RouteId_Frame, + }; + + double GpuTimestampToSessionTime(uint64 GpuMicroseconds); + + Trace::FAnalysisSession& Session; + Trace::FTimingProfilerProvider& TimingProfilerProvider; + Trace::FTimingProfilerProvider::TimelineInternal& Timeline; + TMap EventTypeMap; + bool Calibrated; + uint64 GpuTimeOffset; +}; diff --git a/Engine/Source/Developer/TraceServices/Private/Analyzers/LoadTimeTraceAnalysis.cpp b/Engine/Source/Developer/TraceServices/Private/Analyzers/LoadTimeTraceAnalysis.cpp new file mode 100644 index 000000000000..43fa0a3d320e --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Analyzers/LoadTimeTraceAnalysis.cpp @@ -0,0 +1,457 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +#include "LoadTimeTraceAnalysis.h" + +#include "HAL/FileManager.h" +#include "Serialization/LoadTimeTrace.h" +#include "Trace/Trace.h" +#include "Model/LoadTimeProfilerPrivate.h" +#include "Analyzers/MiscTraceAnalysis.h" + +FAsyncLoadingTraceAnalyzer::FAsyncLoadingTraceAnalyzer(Trace::IAnalysisSession& InSession, Trace::FLoadTimeProfilerProvider& InLoadTimeProfilerProvider) + : Session(InSession) + , LoadTimeProfilerProvider(InLoadTimeProfilerProvider) +{ +} + +FAsyncLoadingTraceAnalyzer::~FAsyncLoadingTraceAnalyzer() +{ + +} + +TSharedRef FAsyncLoadingTraceAnalyzer::GetThreadState(uint32 ThreadId) +{ + if (!ThreadStatesMap.Contains(ThreadId)) + { + TSharedRef ThreadState = MakeShared(); + ThreadStatesMap.Add(ThreadId, ThreadState); + if (MainThreadId == -1) + { + MainThreadId = ThreadId; + LoadTimeProfilerProvider.SetMainThreadId(MainThreadId); + ThreadState->CpuTimeline = LoadTimeProfilerProvider.EditMainThreadCpuTimeline(); + } + else if (ThreadId == AsyncLoadingThreadId) + { + ThreadState->CpuTimeline = LoadTimeProfilerProvider.EditAsyncLoadingThreadCpuTimeline(); + } + return ThreadState; + } + else + { + return ThreadStatesMap[ThreadId]; + } +} + +const Trace::FClassInfo* FAsyncLoadingTraceAnalyzer::GetClassInfo(uint64 ClassPtr) const +{ + const Trace::FClassInfo* const* ClassInfo = ClassInfosMap.Find(ClassPtr); + if (ClassInfo) + { + return *ClassInfo; + } + else + { + return nullptr; + } +} + +void FAsyncLoadingTraceAnalyzer::OnAnalysisBegin(const FOnAnalysisContext& Context) +{ + auto& Builder = Context.InterfaceBuilder; + + Builder.RouteEvent(RouteId_StartAsyncLoading, "LoadTime", "StartAsyncLoading"); + Builder.RouteEvent(RouteId_SuspendAsyncLoading, "LoadTime", "SuspendAsyncLoading"); + Builder.RouteEvent(RouteId_ResumeAsyncLoading, "LoadTime", "ResumeAsyncLoading"); + Builder.RouteEvent(RouteId_NewLinker, "LoadTime", "NewLinker"); + Builder.RouteEvent(RouteId_DestroyLinker, "LoadTime", "DestroyLinker"); + Builder.RouteEvent(RouteId_PackageSummary, "LoadTime", "PackageSummary"); + Builder.RouteEvent(RouteId_BeginCreateExport, "LoadTime", "BeginCreateExport"); + Builder.RouteEvent(RouteId_EndCreateExport, "LoadTime", "EndCreateExport"); + Builder.RouteEvent(RouteId_BeginObjectScope, "LoadTime", "BeginObjectScope"); + Builder.RouteEvent(RouteId_EndObjectScope, "LoadTime", "EndObjectScope"); + Builder.RouteEvent(RouteId_NewAsyncPackage, "LoadTime", "NewAsyncPackage"); + Builder.RouteEvent(RouteId_DestroyAsyncPackage, "LoadTime", "DestroyAsyncPackage"); + Builder.RouteEvent(RouteId_BeginRequest, "LoadTime", "BeginRequest"); + Builder.RouteEvent(RouteId_EndRequest, "LoadTime", "EndRequest"); + Builder.RouteEvent(RouteId_BeginLoadMap, "LoadTime", "BeginLoadMap"); + Builder.RouteEvent(RouteId_EndLoadMap, "LoadTime", "EndLoadMap"); + Builder.RouteEvent(RouteId_NewStreamableHandle, "LoadTime", "NewStreamableHandle"); + Builder.RouteEvent(RouteId_DestroyStreamableHandle, "LoadTime", "DestroyStreamableHandle"); + Builder.RouteEvent(RouteId_BeginLoadStreamableHandle, "LoadTime", "BeginLoadStreamableHandle"); + Builder.RouteEvent(RouteId_EndLoadStreamableHandle, "LoadTime", "EndLoadStreamableHandle"); + Builder.RouteEvent(RouteId_BeginWaitForStreamableHandle, "LoadTime", "BeginWaitForStreamableHandle"); + Builder.RouteEvent(RouteId_EndWaitForStreamableHandle, "LoadTime", "EndWaitForStreamableHandle"); + Builder.RouteEvent(RouteId_StreamableHandleRequestAssociation, "LoadTime", "StreamableHandleRequestAssociation"); + Builder.RouteEvent(RouteId_AsyncPackageRequestAssociation, "LoadTime", "AsyncPackageRequestAssociation"); + Builder.RouteEvent(RouteId_AsyncPackageLinkerAssociation, "LoadTime", "AsyncPackageLinkerAssociation"); + Builder.RouteEvent(RouteId_LinkerArchiveAssociation, "LoadTime", "LinkerArchiveAssociation"); + Builder.RouteEvent(RouteId_BeginAsyncPackageScope, "LoadTime", "BeginAsyncPackageScope"); + Builder.RouteEvent(RouteId_EndAsyncPackageScope, "LoadTime", "EndAsyncPackageScope"); + Builder.RouteEvent(RouteId_BeginFlushAsyncLoading, "LoadTime", "BeginFlushAsyncLoading"); + Builder.RouteEvent(RouteId_EndFlushAsyncLoading, "LoadTime", "EndFlushAsyncLoading"); + Builder.RouteEvent(RouteId_ClassInfo, "LoadTime", "ClassInfo"); +} + +void FAsyncLoadingTraceAnalyzer::OnAnalysisEnd() +{ + +} + +void FAsyncLoadingTraceAnalyzer::OnEvent(uint16 RouteId, const FOnEventContext& Context) +{ + const auto& EventData = Context.EventData; + switch (RouteId) + { + case RouteId_StartAsyncLoading: + { + Trace::FAnalysisSessionEditScope _(Session); + AsyncLoadingThreadId = EventData.GetValue("ThreadId").As(); + LoadTimeProfilerProvider.SetAsyncLoadingThreadId(AsyncLoadingThreadId); + break; + } + case RouteId_NewLinker: + { + uint64 LinkerPtr = EventData.GetValue("Linker").As(); + //check(!ActiveLinkersMap.Contains(LinkerPtr)); + TSharedRef LinkerState = MakeShared(); + ActiveLinkersMap.Add(LinkerPtr, LinkerState); + break; + } + case RouteId_DestroyLinker: + { + uint64 Ptr = EventData.GetValue("Linker").As(); + //check(ActiveLinkersMap.Contains(Ptr)); + ActiveLinkersMap.Remove(Ptr); + break; + } + case RouteId_PackageSummary: + { + uint64 LinkerPtr = EventData.GetValue("Linker").As(); + TSharedRef* LinkerState = ActiveLinkersMap.Find(LinkerPtr); + if (LinkerState && (*LinkerState)->PackageInfo) + { + Trace::FAnalysisSessionEditScope _(Session); + Trace::FPackageSummaryInfo& Summary = (*LinkerState)->PackageInfo->Summary; + Summary.TotalHeaderSize = EventData.GetValue("TotalHeaderSize").As(); + Summary.NameCount = EventData.GetValue("NameCount").As(); + Summary.ImportCount = EventData.GetValue("ImportCount").As(); + Summary.ExportCount = EventData.GetValue("ExportCount").As(); + } + break; + } + case RouteId_BeginCreateExport: + { + Trace::FAnalysisSessionEditScope _(Session); + + Trace::FPackageExportInfo& Export = LoadTimeProfilerProvider.CreateExport(); + Export.SerialOffset = EventData.GetValue("SerialOffset").As(); + Export.SerialSize = EventData.GetValue("SerialSize").As(); + Export.IsAsset = EventData.GetValue("IsAsset").As(); + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + TSharedRef ThreadState = GetThreadState(ThreadId); + ThreadState->EnterExportScope(Context.SessionContext.TimestampFromCycle(EventData.GetValue("Cycle").As()), &Export, LoadTimeProfilerObjectEventType_Create); + uint64 LinkerPtr = EventData.GetValue("Linker").As(); + TSharedRef* LinkerState = ActiveLinkersMap.Find(LinkerPtr); + if (LinkerState && (*LinkerState)->PackageInfo) + { + (*LinkerState)->PackageInfo->Exports.Add(&Export); + } + break; + } + case RouteId_EndCreateExport: + { + Trace::FAnalysisSessionEditScope _(Session); + + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + TSharedRef ThreadState = GetThreadState(ThreadId); + uint64 ObjectPtr = EventData.GetValue("Object").As(); + Trace::FPackageExportInfo* Export = ThreadState->GetCurrentExportScope(); + if (Export) + { + ExportsMap.Add(ObjectPtr, Export); + const Trace::FClassInfo* ObjectClass = GetClassInfo(EventData.GetValue("Class").As()); + Export->Class = ObjectClass; + } + ThreadState->LeaveScope(Context.SessionContext.TimestampFromCycle(EventData.GetValue("Cycle").As())); + break; + } + case RouteId_BeginObjectScope: + { + uint64 ObjectPtr = EventData.GetValue("Object").As(); + Trace::FPackageExportInfo** Export = ExportsMap.Find(ObjectPtr); + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + TSharedRef ThreadState = GetThreadState(ThreadId); + ELoadTimeProfilerObjectEventType EventType = static_cast(EventData.GetValue("EventType").As()); + { + Trace::FAnalysisSessionEditScope _(Session); + ThreadState->EnterExportScope(Context.SessionContext.TimestampFromCycle(EventData.GetValue("Cycle").As()), Export ? *Export : nullptr, EventType); + } + break; + } + case RouteId_EndObjectScope: + { + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + TSharedRef ThreadState = GetThreadState(ThreadId); + { + Trace::FAnalysisSessionEditScope _(Session); + ThreadState->LeaveScope(Context.SessionContext.TimestampFromCycle(EventData.GetValue("Cycle").As())); + } + break; + } + case RouteId_BeginRequest: + { + uint64 RequestId = EventData.GetValue("RequestId").As(); + //check(!ActiveRequestsMap.Contains(RequestId)); + TSharedRef RequestState = MakeShared(); + RequestState->Id = RequestId; + RequestState->WallTimeStartCycle = EventData.GetValue("Cycle").As(); + RequestState->WallTimeEndCycle = 0; + Requests.Add(RequestState); + ActiveRequestsMap.Add(RequestId, RequestState); + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + TSharedRef ThreadState = GetThreadState(ThreadId); + if (ThreadState->ActiveLoadMap) + { + ThreadState->ActiveLoadMap->Requests.Add(RequestState); + } + + { + /*Trace::FAnalysisSessionEditScope _(Session.Get()); + RequestTimelineEventTypeMap.Add(RequestState->Id, TimelineProvider->AddEventType(*RequestState->PackageName, 0));*/ + } + + break; + } + case RouteId_EndRequest: + { + uint64 RequestId = EventData.GetValue("RequestId").As(); + TSharedRef* RequestState = ActiveRequestsMap.Find(RequestId); + if (RequestState) + { + (*RequestState)->WallTimeEndCycle = EventData.GetValue("Cycle").As(); + } + break; + } + case RouteId_BeginLoadMap: + { + TSharedRef LoadMapState = MakeShared(); + LoadMapState->Id = NextMapId++; + LoadMapState->WallTimeStartCycle = EventData.GetValue("Cycle").As(); + LoadMapState->WallTimeEndCycle = 0; + LoadMapState->Name = reinterpret_cast(EventData.GetAttachment()); + Maps.Add(LoadMapState); + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + TSharedRef ThreadState = GetThreadState(ThreadId); + ThreadState->ActiveLoadMap = LoadMapState; + break; + } + case RouteId_EndLoadMap: + { + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + TSharedRef ThreadState = GetThreadState(ThreadId); + if (ThreadState->ActiveLoadMap) + { + ThreadState->ActiveLoadMap->WallTimeEndCycle = EventData.GetValue("Cycle").As(); + ThreadState->ActiveLoadMap = nullptr; + } + break; + } + case RouteId_NewStreamableHandle: + { + uint64 Ptr = EventData.GetValue("StreamableHandle").As(); + //check(!ActiveStreamableHandlesMap.Contains(Ptr)); + TSharedRef StreamableHandleState = MakeShared(); + StreamableHandleState->Id = NextStreamableHandleId++; + StreamableHandleState->DebugName = FString((const char*)EventData.GetAttachment(), EventData.GetValue("DebugNameSize").As()); + StreamableHandleState->WallTimeStartCycle = 0; + StreamableHandleState->WallTimeEndCycle = 0; + StreamableHandles.Add(StreamableHandleState); + ActiveStreamableHandlesMap.Add(Ptr, StreamableHandleState); + + { + /*Trace::FAnalysisSessionEditScope _(Session.Get()); + StreamableHandlesTimelineEventTypeMap.Add(StreamableHandleState->Id, TimelineProvider->AddEventType(*StreamableHandleState->DebugName, 0));*/ + } + + break; + } + case RouteId_DestroyStreamableHandle: + { + uint64 Ptr = EventData.GetValue("StreamableHandle").As(); + ActiveStreamableHandlesMap.Remove(Ptr); + break; + } + case RouteId_BeginLoadStreamableHandle: + { + uint64 Ptr = EventData.GetValue("StreamableHandle").As(); + TSharedRef* StreamableHandleState = ActiveStreamableHandlesMap.Find(Ptr); + if (StreamableHandleState) + { + (*StreamableHandleState)->WallTimeStartCycle = EventData.GetValue("Cycle").As(); + } + break; + } + case RouteId_EndLoadStreamableHandle: + { + uint64 Ptr = EventData.GetValue("StreamableHandle").As(); + TSharedRef* StreamableHandleState = ActiveStreamableHandlesMap.Find(Ptr); + if (StreamableHandleState) + { + (*StreamableHandleState)->WallTimeEndCycle = EventData.GetValue("Cycle").As(); + } + break; + } + case RouteId_BeginWaitForStreamableHandle: + { + uint64 Ptr = EventData.GetValue("StreamableHandle").As(); + TSharedRef* StreamableHandleState = ActiveStreamableHandlesMap.Find(Ptr); + if (StreamableHandleState) + { + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + TSharedRef ThreadState = GetThreadState(ThreadId); + ThreadState->WaitForStreamableHandleHandle = *StreamableHandleState; + ThreadState->WaitForStreamableHandleStartCycle = EventData.GetValue("Cycle").As(); + } + /*uint64 EventTypeId = StreamableHandlesTimelineEventTypeMap[ThreadState->WaitForStreamableHandleHandle->Id]; + + { + Trace::FAnalysisSessionEditScope _(Session.Get()); + BlockingRequestsTimeline->AppendBeginEvent(Context.SessionContext.TimestampFromCycle(ThreadState->WaitForStreamableHandleStartCycle), EventTypeId); + }*/ + + break; + } + case RouteId_EndWaitForStreamableHandle: + { + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + TSharedRef ThreadState = GetThreadState(ThreadId); + ThreadState->WaitForStreamableHandleHandle = nullptr; + + /*{ + Trace::FAnalysisSessionEditScope _(Session.Get()); + BlockingRequestsTimeline->AppendEndEvent(Context.SessionContext.TimestampFromCycle(EventData.GetValue("Cycle").As())); + }*/ + + break; + } + case RouteId_NewAsyncPackage: + { + Trace::FAnalysisSessionEditScope _(Session); + + uint64 AsyncPackagePtr = EventData.GetValue("AsyncPackage").As(); + //check(!ActivePackagesMap.Contains(AsyncPackagePtr)); + TSharedRef AsyncPackageState = MakeShared(); + AsyncPackageState->PackageInfo = &LoadTimeProfilerProvider.CreatePackage(reinterpret_cast(EventData.GetAttachment())); + ActiveAsyncPackagesMap.Add(AsyncPackagePtr, AsyncPackageState); + break; + } + case RouteId_DestroyAsyncPackage: + { + uint64 AsyncPackagePtr = EventData.GetValue("AsyncPackage").As(); + //check(ActiveAsyncPackagesMap.Contains(AsyncPackagePtr)); + ActiveAsyncPackagesMap.Remove(AsyncPackagePtr); + break; + } + + case RouteId_StreamableHandleRequestAssociation: + { + uint64 StreamableHandlePtr = EventData.GetValue("StreamableHandle").As(); + TSharedRef* StreamableHandleState = ActiveStreamableHandlesMap.Find(StreamableHandlePtr); + uint64 RequestId = EventData.GetValue("RequestId").As(); + TSharedRef* RequestState = ActiveRequestsMap.Find(RequestId); + if (StreamableHandleState && RequestState) + { + (*StreamableHandleState)->Requests.Add(*RequestState); + } + break; + } + case RouteId_AsyncPackageRequestAssociation: + { + uint64 AsyncPackagePtr = EventData.GetValue("AsyncPackage").As(); + TSharedRef* AsyncPackageState = ActiveAsyncPackagesMap.Find(AsyncPackagePtr); + uint64 RequestId = EventData.GetValue("RequestId").As(); + TSharedRef* RequestState = ActiveRequestsMap.Find(RequestId); + if (AsyncPackageState && RequestState) + { + (*RequestState)->AsyncPackages.Add(*AsyncPackageState); + (*AsyncPackageState)->Requests.Add(*RequestState); + } + break; + } + case RouteId_AsyncPackageLinkerAssociation: + { + uint64 LinkerPtr = EventData.GetValue("Linker").As(); + TSharedRef* LinkerState = ActiveLinkersMap.Find(LinkerPtr); + uint64 AsyncPackagePtr = EventData.GetValue("AsyncPackage").As(); + TSharedRef* AsyncPackageState = ActiveAsyncPackagesMap.Find(AsyncPackagePtr); + if (LinkerState && AsyncPackageState) + { + (*AsyncPackageState)->LinkerState = *LinkerState; + (*LinkerState)->AsyncPackageState = *AsyncPackageState; + (*LinkerState)->PackageInfo = (*AsyncPackageState)->PackageInfo; + } + break; + } + case RouteId_LinkerArchiveAssociation: + { + uint64 ArchivePtr = EventData.GetValue("Archive").As(); + uint64 LinkerPtr = EventData.GetValue("Linker").As(); + break; + } + case RouteId_BeginAsyncPackageScope: + { + Trace::FAnalysisSessionEditScope _(Session); + + uint64 AsyncPackagePtr = EventData.GetValue("AsyncPackage").As(); + TSharedRef* AsyncPackageState = ActiveAsyncPackagesMap.Find(AsyncPackagePtr); + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + TSharedRef ThreadState = GetThreadState(ThreadId); + ELoadTimeProfilerPackageEventType EventType = static_cast(EventData.GetValue("EventType").As()); + ThreadState->EnterPackageScope(Context.SessionContext.TimestampFromCycle(EventData.GetValue("Cycle").As()), AsyncPackageState ? (*AsyncPackageState)->PackageInfo : nullptr, EventType); + break; + } + case RouteId_EndAsyncPackageScope: + { + Trace::FAnalysisSessionEditScope _(Session); + + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + TSharedRef ThreadState = GetThreadState(ThreadId); + uint64 Cycle = EventData.GetValue("Cycle").As(); + ThreadState->LeaveScope(Context.SessionContext.TimestampFromCycle(EventData.GetValue("Cycle").As())); + break; + } + case RouteId_BeginFlushAsyncLoading: + { + Trace::FAnalysisSessionEditScope _(Session); + + FlushAsyncLoadingRequestId = EventData.GetValue("RequestId").As(); + FlushAsyncLoadingStartCycle = EventData.GetValue("Cycle").As(); + if (FlushAsyncLoadingRequestId != INDEX_NONE) + { + //check(ActiveRequestsMap.Contains(FlushAsyncLoadingRequestId)); + /*uint64 EventTypeId = RequestTimelineEventTypeMap[FlushAsyncLoadingRequestId]; + BlockingRequestsTimeline->AppendBeginEvent(Context.SessionContext.TimestampFromCycle(FlushAsyncLoadingStartCycle), EventTypeId);*/ + } + else + { + //BlockingRequestsTimeline->AppendBeginEvent(Context.SessionContext.TimestampFromCycle(FlushAsyncLoadingStartCycle), FlushAsyncLoadingEventId); + } + break; + } + case RouteId_EndFlushAsyncLoading: + { + uint64 Cycle = EventData.GetValue("Cycle").As(); + //BlockingRequestsTimeline->AppendEndEvent(Context.SessionContext.TimestampFromCycle(Cycle)); + break; + } + case RouteId_ClassInfo: + { + Trace::FAnalysisSessionEditScope _(Session); + + uint64 ClassPtr = EventData.GetValue("Class").As(); + const Trace::FClassInfo& ClassInfo = LoadTimeProfilerProvider.AddClassInfo(reinterpret_cast(EventData.GetAttachment())); + ClassInfosMap.Add(ClassPtr, &ClassInfo); + break; + } + } +} diff --git a/Engine/Source/Developer/TraceServices/Private/Analyzers/LoadTimeTraceAnalysis.h b/Engine/Source/Developer/TraceServices/Private/Analyzers/LoadTimeTraceAnalysis.h new file mode 100644 index 000000000000..48b565fff054 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Analyzers/LoadTimeTraceAnalysis.h @@ -0,0 +1,224 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Templates/SharedPointer.h" +#include "Trace/Analyzer.h" +#include "AnalysisServicePrivate.h" +#include "Model/LoadTimeProfilerPrivate.h" + +namespace Trace +{ + struct FClassInfo; +} + +inline bool operator!=(const Trace::FLoadTimeProfilerCpuEvent& Lhs, const Trace::FLoadTimeProfilerCpuEvent& Rhs) +{ + return Lhs.Package != Rhs.Package || + Lhs.Export != Rhs.Export || + Lhs.PackageEventType != Rhs.PackageEventType || + Lhs.ExportEventType != Rhs.ExportEventType; +} + +class FAsyncLoadingTraceAnalyzer : public Trace::IAnalyzer +{ +public: + FAsyncLoadingTraceAnalyzer(Trace::IAnalysisSession& Session, Trace::FLoadTimeProfilerProvider& LoadTimeProfilerProvider); + virtual ~FAsyncLoadingTraceAnalyzer(); + + virtual void OnAnalysisBegin(const FOnAnalysisContext& Context) override; + virtual void OnEvent(uint16 RouteId, const FOnEventContext& Context) override; + virtual void OnAnalysisEnd() override; + +private: + struct FRequestState; + struct FAsyncPackageState; + + struct FLoadMapState + { + uint64 Id; + FString Name; + uint64 WallTimeStartCycle; + uint64 WallTimeEndCycle; + TArray> Requests; + }; + + struct FStreamableHandleState + { + uint64 Id; + FString DebugName; + uint64 WallTimeStartCycle; + uint64 WallTimeEndCycle; + TArray> Requests; + }; + + struct FRequestState + { + uint64 Id; + uint64 WallTimeStartCycle; + uint64 WallTimeEndCycle; + TArray> AsyncPackages; + }; + + struct FLinkerState + { + TSharedPtr AsyncPackageState; + Trace::FPackageInfo* PackageInfo = nullptr; + }; + + struct FAsyncPackageState + { + Trace::FPackageInfo* PackageInfo = nullptr; + TSharedPtr LinkerState; + TArray> Requests; + }; + + struct FScopeStackEntry + { + Trace::FLoadTimeProfilerCpuEvent Event; + bool EnteredEvent; + }; + + struct FThreadState + { + FScopeStackEntry CpuScopeStack[256]; + uint64 CpuScopeStackDepth = 0; + Trace::FLoadTimeProfilerCpuEvent CurrentEvent; + uint64 WaitForStreamableHandleStartCycle = 0; + TSharedPtr WaitForStreamableHandleHandle; + TSharedPtr ActiveLoadMap; + + TSharedPtr CpuTimeline; + + void EnterPackageScope(double Time, const Trace::FPackageInfo* PackageInfo, ELoadTimeProfilerPackageEventType EventType) + { + FScopeStackEntry& StackEntry = CpuScopeStack[CpuScopeStackDepth++]; + StackEntry.Event.Export = CurrentEvent.Export; + StackEntry.Event.ExportEventType = CurrentEvent.ExportEventType; + StackEntry.Event.Package = PackageInfo; + StackEntry.Event.PackageEventType = EventType; + if (PackageInfo && CurrentEvent != StackEntry.Event) + { + CurrentEvent = StackEntry.Event; + CpuTimeline->AppendBeginEvent(Time, StackEntry.Event); + StackEntry.EnteredEvent = true; + } + else + { + StackEntry.EnteredEvent = false; + } + } + + void EnterExportScope(double Time, const Trace::FPackageExportInfo* ExportInfo, ELoadTimeProfilerObjectEventType EventType) + { + FScopeStackEntry& StackEntry = CpuScopeStack[CpuScopeStackDepth++]; + StackEntry.Event.Export = ExportInfo; + StackEntry.Event.ExportEventType = EventType; + StackEntry.Event.Package = CurrentEvent.Package; + StackEntry.Event.PackageEventType = CurrentEvent.PackageEventType; + + if (ExportInfo && CurrentEvent != StackEntry.Event) + { + CurrentEvent = StackEntry.Event; + CpuTimeline->AppendBeginEvent(Time, StackEntry.Event); + StackEntry.EnteredEvent = true; + } + else + { + StackEntry.EnteredEvent = false; + } + } + + void LeaveScope(double Time) + { + FScopeStackEntry& StackEntry = CpuScopeStack[--CpuScopeStackDepth]; + if (StackEntry.EnteredEvent) + { + CpuTimeline->AppendEndEvent(Time); + } + if (CpuScopeStackDepth > 0) + { + CurrentEvent = CpuScopeStack[CpuScopeStackDepth - 1].Event; + } + else + { + CurrentEvent = Trace::FLoadTimeProfilerCpuEvent(); + } + } + + Trace::FPackageExportInfo* GetCurrentExportScope() + { + if (CpuScopeStackDepth > 0) + { + FScopeStackEntry& StackEntry = CpuScopeStack[CpuScopeStackDepth - 1]; + return const_cast(StackEntry.Event.Export); + } + else + { + return nullptr; + } + } + }; + + TSharedRef GetThreadState(uint32 ThreadId); + const Trace::FClassInfo* GetClassInfo(uint64 ClassPtr) const; + + enum : uint16 + { + RouteId_StartAsyncLoading, + RouteId_SuspendAsyncLoading, + RouteId_ResumeAsyncLoading, + RouteId_NewLinker, + RouteId_DestroyLinker, + RouteId_NewAsyncPackage, + RouteId_DestroyAsyncPackage, + RouteId_BeginRequest, + RouteId_EndRequest, + RouteId_BeginLoadMap, + RouteId_EndLoadMap, + RouteId_NewStreamableHandle, + RouteId_DestroyStreamableHandle, + RouteId_BeginLoadStreamableHandle, + RouteId_EndLoadStreamableHandle, + RouteId_BeginWaitForStreamableHandle, + RouteId_EndWaitForStreamableHandle, + RouteId_PackageSummary, + RouteId_StreamableHandleRequestAssociation, + RouteId_AsyncPackageRequestAssociation, + RouteId_AsyncPackageLinkerAssociation, + RouteId_LinkerArchiveAssociation, + RouteId_BeginAsyncPackageScope, + RouteId_EndAsyncPackageScope, + RouteId_BeginCreateExport, + RouteId_EndCreateExport, + RouteId_BeginObjectScope, + RouteId_EndObjectScope, + RouteId_BeginFlushAsyncLoading, + RouteId_EndFlushAsyncLoading, + RouteId_ClassInfo, + }; + + Trace::IAnalysisSession& Session; + Trace::FLoadTimeProfilerProvider& LoadTimeProfilerProvider; + //TSharedPtr> BlockingRequestsTimeline; + + TArray> Maps; + TArray> StreamableHandles; + TArray> Requests; + + TMap> ActiveLinkersMap; + TMap> ActiveAsyncPackagesMap; + TMap ExportsMap; + TMap> ActiveStreamableHandlesMap; + TMap> ActiveRequestsMap; + TMap> ThreadStatesMap; + TMap ClassInfosMap; + + uint64 FlushAsyncLoadingRequestId; + uint64 FlushAsyncLoadingStartCycle; + + uint64 NextMapId = 0; + uint64 NextStreamableHandleId = 0; + int64 MainThreadId = -1; + int64 AsyncLoadingThreadId = -1; +}; diff --git a/Engine/Source/Developer/TraceServices/Private/Analyzers/LogTraceAnalysis.cpp b/Engine/Source/Developer/TraceServices/Private/Analyzers/LogTraceAnalysis.cpp new file mode 100644 index 000000000000..4ad11ba07e37 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Analyzers/LogTraceAnalysis.cpp @@ -0,0 +1,60 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +#include "LogTraceAnalysis.h" +#include "AnalysisServicePrivate.h" +#include "Logging/LogTrace.h" +#include "Model/LogPrivate.h" + +FLogTraceAnalyzer::FLogTraceAnalyzer(Trace::IAnalysisSession& InSession, Trace::FLogProvider& InLogProvider) + : Session(InSession) + , LogProvider(InLogProvider) +{ + +} + +void FLogTraceAnalyzer::OnAnalysisBegin(const FOnAnalysisContext& Context) +{ + auto& Builder = Context.InterfaceBuilder; + + Builder.RouteEvent(RouteId_LogCategory, "Logging", "LogCategory"); + Builder.RouteEvent(RouteId_LogMessageSpec, "Logging", "LogMessageSpec"); + Builder.RouteEvent(RouteId_LogMessage, "Logging", "LogMessage"); +} + +void FLogTraceAnalyzer::OnEvent(uint16 RouteId, const FOnEventContext& Context) +{ + Trace::FAnalysisSessionEditScope _(Session); + + const auto& EventData = Context.EventData; + switch (RouteId) + { + case RouteId_LogCategory: + { + uint64 CategoryPointer = EventData.GetValue("CategoryPointer").As(); + Trace::FLogCategory& Category = LogProvider.GetCategory(CategoryPointer); + Category.Name = Session.StoreString(reinterpret_cast(EventData.GetAttachment())); + Category.DefaultVerbosity = static_cast(EventData.GetValue("DefaultVerbosity").As()); + break; + } + case RouteId_LogMessageSpec: + { + uint64 LogPoint = EventData.GetValue("LogPoint").As(); + Trace::FLogMessageSpec& Spec = LogProvider.GetMessageSpec(LogPoint); + uint64 CategoryPointer = EventData.GetValue("CategoryPointer").As(); + Trace::FLogCategory& Category = LogProvider.GetCategory(CategoryPointer); + Spec.Category = &Category; + Spec.Line = EventData.GetValue("Line").As(); + Spec.Verbosity = static_cast(EventData.GetValue("Verbosity").As()); + const ANSICHAR* File = reinterpret_cast(EventData.GetAttachment()); + Spec.File = Session.StoreString(ANSI_TO_TCHAR(File)); + Spec.FormatString = Session.StoreString(reinterpret_cast(EventData.GetAttachment() + strlen(File) + 1)); + break; + } + case RouteId_LogMessage: + { + uint64 LogPoint = EventData.GetValue("LogPoint").As(); + uint64 Cycle = EventData.GetValue("Cycle").As(); + LogProvider.AppendMessage(LogPoint, Context.SessionContext.TimestampFromCycle(Cycle), EventData.GetAttachment()); + break; + } + } +} diff --git a/Engine/Source/Developer/TraceServices/Private/Analyzers/LogTraceAnalysis.h b/Engine/Source/Developer/TraceServices/Private/Analyzers/LogTraceAnalysis.h new file mode 100644 index 000000000000..65f783d319a5 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Analyzers/LogTraceAnalysis.h @@ -0,0 +1,33 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Trace/Trace.h" +#include "Trace/Analyzer.h" + +namespace Trace +{ + class IAnalysisSession; + class FLogProvider; +} + +class FLogTraceAnalyzer + : public Trace::IAnalyzer +{ +public: + FLogTraceAnalyzer(Trace::IAnalysisSession& Session, Trace::FLogProvider& LogProvider); + virtual void OnAnalysisBegin(const FOnAnalysisContext& Context) override; + virtual void OnEvent(uint16 RouteId, const FOnEventContext& Context) override; + virtual void OnAnalysisEnd() override {}; + +private: + enum : uint16 + { + RouteId_LogCategory, + RouteId_LogMessageSpec, + RouteId_LogMessage, + }; + + Trace::IAnalysisSession& Session; + Trace::FLogProvider& LogProvider; +}; diff --git a/Engine/Source/Developer/TraceServices/Private/Analyzers/MiscTraceAnalysis.cpp b/Engine/Source/Developer/TraceServices/Private/Analyzers/MiscTraceAnalysis.cpp new file mode 100644 index 000000000000..d7bcc9474d10 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Analyzers/MiscTraceAnalysis.cpp @@ -0,0 +1,174 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +#include "MiscTraceAnalysis.h" +#include "TraceServices/Model/AnalysisSession.h" +#include "Model/LogPrivate.h" +#include "Model/ThreadsPrivate.h" +#include "Model/BookmarksPrivate.h" +#include "Model/FramesPrivate.h" +#include "Common/Utils.h" + +FMiscTraceAnalyzer::FMiscTraceAnalyzer(Trace::IAnalysisSession& InSession, + Trace::FThreadProvider& InThreadProvider, + Trace::FBookmarkProvider& InBookmarkProvider, + Trace::FLogProvider& InLogProvider, + Trace::FFrameProvider& InFrameProvider) + : Session(InSession) + , ThreadProvider(InThreadProvider) + , BookmarkProvider(InBookmarkProvider) + , LogProvider(InLogProvider) + , FrameProvider(InFrameProvider) +{ + Trace::FLogCategory& BookmarkLogCategory = LogProvider.GetCategory(Trace::FLogProvider::ReservedLogCategory_Bookmark); + BookmarkLogCategory.Name = TEXT("LogBookmark"); + BookmarkLogCategory.DefaultVerbosity = ELogVerbosity::All; +} + +void FMiscTraceAnalyzer::OnAnalysisBegin(const FOnAnalysisContext& Context) +{ + auto& Builder = Context.InterfaceBuilder; + + Builder.RouteEvent(RouteId_RegisterGameThread, "Misc", "RegisterGameThread"); + Builder.RouteEvent(RouteId_CreateThread, "Misc", "CreateThread"); + Builder.RouteEvent(RouteId_SetThreadGroup, "Misc", "SetThreadGroup"); + Builder.RouteEvent(RouteId_BeginThreadGroupScope, "Misc", "BeginThreadGroupScope"); + Builder.RouteEvent(RouteId_EndThreadGroupScope, "Misc", "EndThreadGroupScope"); + Builder.RouteEvent(RouteId_BookmarkSpec, "Misc", "BookmarkSpec"); + Builder.RouteEvent(RouteId_Bookmark, "Misc", "Bookmark"); + Builder.RouteEvent(RouteId_BeginFrame, "Misc", "BeginFrame"); + Builder.RouteEvent(RouteId_EndFrame, "Misc", "EndFrame"); + Builder.RouteEvent(RouteId_BeginGameFrame, "Misc", "BeginGameFrame"); + Builder.RouteEvent(RouteId_EndGameFrame, "Misc", "EndGameFrame"); + Builder.RouteEvent(RouteId_BeginRenderFrame, "Misc", "BeginRenderFrame"); + Builder.RouteEvent(RouteId_EndRenderFrame, "Misc", "EndRenderFrame"); +} + +void FMiscTraceAnalyzer::OnEvent(uint16 RouteId, const FOnEventContext& Context) +{ + Trace::FAnalysisSessionEditScope _(Session); + + const auto& EventData = Context.EventData; + switch (RouteId) + { + case RouteId_RegisterGameThread: + { + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + ThreadProvider.AddGameThread(ThreadId); + break; + } + case RouteId_CreateThread: + { + uint32 CreatedThreadId = EventData.GetValue("CreatedThreadId").As(); + EThreadPriority Priority = static_cast(EventData.GetValue("Priority").As()); + ThreadProvider.AddThread(CreatedThreadId, reinterpret_cast(EventData.GetAttachment()), Priority); + uint32 CurrentThreadId = EventData.GetValue("CurrentThreadId").As(); + FThreadState* ThreadState = GetThreadState(CurrentThreadId); + if (ThreadState->ThreadGroupStack.Num()) + { + ThreadProvider.SetThreadGroup(CreatedThreadId, ThreadState->ThreadGroupStack.Top()); + } + break; + } + case RouteId_SetThreadGroup: + { + const TCHAR* GroupName = Session.StoreString(ANSI_TO_TCHAR(reinterpret_cast(EventData.GetAttachment()))); + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + ThreadProvider.SetThreadGroup(ThreadId, GroupName); + break; + } + case RouteId_BeginThreadGroupScope: + { + const TCHAR* GroupName = Session.StoreString(ANSI_TO_TCHAR(reinterpret_cast(EventData.GetAttachment()))); + uint32 CurrentThreadId = EventData.GetValue("CurrentThreadId").As(); + FThreadState* ThreadState = GetThreadState(CurrentThreadId); + ThreadState->ThreadGroupStack.Push(GroupName); + break; + } + case RouteId_EndThreadGroupScope: + { + uint32 CurrentThreadId = EventData.GetValue("CurrentThreadId").As(); + FThreadState* ThreadState = GetThreadState(CurrentThreadId); + ThreadState->ThreadGroupStack.Pop(); + break; + } + case RouteId_BookmarkSpec: + { + uint64 BookmarkPoint = EventData.GetValue("BookmarkPoint").As(); + Trace::FBookmarkSpec& Spec = BookmarkProvider.GetSpec(BookmarkPoint); + Spec.Line = EventData.GetValue("Line").As(); + const ANSICHAR* File = reinterpret_cast(EventData.GetAttachment()); + Spec.File = Session.StoreString(ANSI_TO_TCHAR(File)); + Spec.FormatString = Session.StoreString(reinterpret_cast(EventData.GetAttachment() + strlen(File) + 1)); + + Trace::FLogMessageSpec& LogMessageSpec = LogProvider.GetMessageSpec(BookmarkPoint); + LogMessageSpec.Category = &LogProvider.GetCategory(Trace::FLogProvider::ReservedLogCategory_Bookmark); + LogMessageSpec.Line = Spec.Line; + LogMessageSpec.File = Spec.File; + LogMessageSpec.FormatString = Spec.FormatString; + LogMessageSpec.Verbosity = ELogVerbosity::Log; + break; + } + case RouteId_Bookmark: + { + uint64 BookmarkPoint = EventData.GetValue("BookmarkPoint").As(); + uint64 Cycle = EventData.GetValue("Cycle").As(); + double Timestamp = Context.SessionContext.TimestampFromCycle(Cycle); + BookmarkProvider.AppendBookmark(Timestamp, BookmarkPoint, EventData.GetAttachment()); + LogProvider.AppendMessage(BookmarkPoint, Timestamp, EventData.GetAttachment()); + break; + } + case RouteId_BeginFrame: + { + uint64 Cycle = EventData.GetValue("Cycle").As(); + uint8 FrameType = EventData.GetValue("FrameType").As(); + check(FrameType < TraceFrameType_Count); + FrameProvider.BeginFrame(ETraceFrameType(FrameType), Context.SessionContext.TimestampFromCycle(Cycle)); + break; + } + case RouteId_EndFrame: + { + uint64 Cycle = EventData.GetValue("Cycle").As(); + uint8 FrameType = EventData.GetValue("FrameType").As(); + check(FrameType < TraceFrameType_Count); + FrameProvider.EndFrame(ETraceFrameType(FrameType), Context.SessionContext.TimestampFromCycle(Cycle)); + break; + } + case RouteId_BeginGameFrame: + case RouteId_EndGameFrame: + case RouteId_BeginRenderFrame: + case RouteId_EndRenderFrame: + { + ETraceFrameType FrameType; + if (RouteId == RouteId_BeginGameFrame || RouteId == RouteId_EndGameFrame) + { + FrameType = TraceFrameType_Game; + } + else + { + FrameType = TraceFrameType_Rendering; + } + const uint8* BufferPtr = EventData.GetAttachment(); + uint64 CycleDiff = FTraceAnalyzerUtils::Decode7bit(BufferPtr); + uint64 Cycle = LastFrameCycle[FrameType] + CycleDiff; + LastFrameCycle[FrameType] = Cycle; + if (RouteId == RouteId_BeginGameFrame || RouteId == RouteId_BeginRenderFrame) + { + FrameProvider.BeginFrame(FrameType, Context.SessionContext.TimestampFromCycle(Cycle)); + } + else + { + FrameProvider.EndFrame(FrameType, Context.SessionContext.TimestampFromCycle(Cycle)); + } + break; + } + } +} + +FMiscTraceAnalyzer::FThreadState* FMiscTraceAnalyzer::GetThreadState(uint32 ThreadId) +{ + if (!ThreadStateMap.Contains(ThreadId)) + { + TSharedRef ThreadState = MakeShared(); + ThreadStateMap.Add(ThreadId, ThreadState); + } + return &ThreadStateMap[ThreadId].Get(); +} diff --git a/Engine/Source/Developer/TraceServices/Private/Analyzers/MiscTraceAnalysis.h b/Engine/Source/Developer/TraceServices/Private/Analyzers/MiscTraceAnalysis.h new file mode 100644 index 000000000000..86b733d1e260 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Analyzers/MiscTraceAnalysis.h @@ -0,0 +1,66 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Trace/Trace.h" +#include "Trace/Analyzer.h" +#include "Templates/SharedPointer.h" +#include "ProfilingDebugging/MiscTrace.h" +#include "Common/PagedArray.h" + +namespace Trace +{ + class IAnalysisSession; + class FThreadProvider; + class FBookmarkProvider; + class FLogProvider; + class FFrameProvider; +} + +class FMiscTraceAnalyzer + : public Trace::IAnalyzer +{ +public: + FMiscTraceAnalyzer(Trace::IAnalysisSession& Session, + Trace::FThreadProvider& ThreadProvider, + Trace::FBookmarkProvider& BookmarkProvider, + Trace::FLogProvider& LogProvider, + Trace::FFrameProvider& FrameProvider); + virtual void OnAnalysisBegin(const FOnAnalysisContext& Context) override; + virtual void OnEvent(uint16 RouteId, const FOnEventContext& Context) override; + virtual void OnAnalysisEnd() override {}; + +private: + enum : uint16 + { + RouteId_RegisterGameThread, + RouteId_CreateThread, + RouteId_SetThreadGroup, + RouteId_BeginThreadGroupScope, + RouteId_EndThreadGroupScope, + RouteId_BookmarkSpec, + RouteId_Bookmark, + RouteId_BeginFrame, + RouteId_EndFrame, + RouteId_BeginGameFrame, + RouteId_EndGameFrame, + RouteId_BeginRenderFrame, + RouteId_EndRenderFrame, + }; + + struct FThreadState + { + TArray ThreadGroupStack; + }; + + FThreadState* GetThreadState(uint32 ThreadId); + + Trace::IAnalysisSession& Session; + Trace::FThreadProvider& ThreadProvider; + Trace::FBookmarkProvider& BookmarkProvider; + Trace::FLogProvider& LogProvider; + Trace::FFrameProvider& FrameProvider; + TMap> ThreadStateMap; + uint64 LastFrameCycle[TraceFrameType_Count] = { 0, 0 }; +}; + diff --git a/Engine/Source/Developer/TraceServices/Private/Analyzers/PlatformFileTraceAnalysis.cpp b/Engine/Source/Developer/TraceServices/Private/Analyzers/PlatformFileTraceAnalysis.cpp new file mode 100644 index 000000000000..815960302715 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Analyzers/PlatformFileTraceAnalysis.cpp @@ -0,0 +1,152 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +#include "PlatformFileTraceAnalysis.h" +#include "AnalysisServicePrivate.h" +#include "Model/FileActivity.h" + +FPlatformFileTraceAnalyzer::FPlatformFileTraceAnalyzer(Trace::IAnalysisSession& InSession, Trace::FFileActivityProvider& InFileActivityProvider) + : Session(InSession) + , FileActivityProvider(InFileActivityProvider) +{ + +} + +void FPlatformFileTraceAnalyzer::OnAnalysisBegin(const FOnAnalysisContext& Context) +{ + auto& Builder = Context.InterfaceBuilder; + + Builder.RouteEvent(RouteId_BeginOpen, "PlatformFile", "BeginOpen"); + Builder.RouteEvent(RouteId_EndOpen, "PlatformFile", "EndOpen"); + Builder.RouteEvent(RouteId_BeginClose, "PlatformFile", "BeginClose"); + Builder.RouteEvent(RouteId_EndClose, "PlatformFile", "EndClose"); + Builder.RouteEvent(RouteId_BeginRead, "PlatformFile", "BeginRead"); + Builder.RouteEvent(RouteId_EndRead, "PlatformFile", "EndRead"); + Builder.RouteEvent(RouteId_BeginWrite, "PlatformFile", "BeginWrite"); + Builder.RouteEvent(RouteId_EndWrite, "PlatformFile", "EndWrite"); +} + +void FPlatformFileTraceAnalyzer::OnEvent(uint16 RouteId, const FOnEventContext& Context) +{ + Trace::FAnalysisSessionEditScope _(Session); + + const auto& EventData = Context.EventData; + switch (RouteId) + { + case RouteId_BeginOpen: + { + double Time = Context.SessionContext.TimestampFromCycle(EventData.GetValue("Cycle").As()); + uint64 TempHandle = EventData.GetValue("TempHandle").As(); + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + check(!PendingOpenMap.Contains(TempHandle)); + uint32 FileIndex = FileActivityProvider.GetFileIndex(reinterpret_cast(EventData.GetAttachment())); + FPendingActivity& Open = PendingOpenMap.Add(TempHandle); + Open.ActivityIndex = FileActivityProvider.BeginActivity(FileIndex, Trace::FileActivityType_Open, 0, 0, Time); + Open.FileIndex = FileIndex; + break; + } + case RouteId_EndOpen: + { + double Time = Context.SessionContext.TimestampFromCycle(EventData.GetValue("Cycle").As()); + uint64 TempHandle = EventData.GetValue("TempHandle").As(); + uint64 FileHandle = EventData.GetValue("FileHandle").As(); + check(PendingOpenMap.Contains(TempHandle)); + FPendingActivity& Open = PendingOpenMap[TempHandle]; + if (FileHandle == uint64(-1)) // TODO: Portable invalid file handle? + { + FileActivityProvider.EndActivity(Open.FileIndex, Open.ActivityIndex, Time, true); + } + else + { + check(!OpenFilesMap.Contains(FileHandle)); + OpenFilesMap.Add(FileHandle, Open.FileIndex); + FileActivityProvider.EndActivity(Open.FileIndex, Open.ActivityIndex, Time, false); + } + PendingOpenMap.Remove(TempHandle); + break; + } + case RouteId_BeginClose: + { + double Time = Context.SessionContext.TimestampFromCycle(EventData.GetValue("Cycle").As()); + uint64 TempHandle = EventData.GetValue("TempHandle").As(); + uint64 FileHandle = EventData.GetValue("FileHandle").As(); + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + check(OpenFilesMap.Contains(FileHandle)); + check(!PendingCloseMap.Contains(TempHandle)); + uint64 FileIndex = OpenFilesMap[FileHandle]; + OpenFilesMap.Remove(FileHandle); + FPendingActivity& Close = PendingCloseMap.Add(TempHandle); + Close.ActivityIndex = FileActivityProvider.BeginActivity(FileIndex, Trace::FileActivityType_Close, 0, 0, Time); + Close.FileIndex = FileIndex; + break; + } + case RouteId_EndClose: + { + double Time = Context.SessionContext.TimestampFromCycle(EventData.GetValue("Cycle").As()); + uint64 TempHandle = EventData.GetValue("TempHandle").As(); + check(PendingCloseMap.Contains(TempHandle)); + FPendingActivity& Close = PendingCloseMap[TempHandle]; + FileActivityProvider.EndActivity(Close.FileIndex, Close.ActivityIndex, Time, false); + PendingCloseMap.Remove(TempHandle); + break; + } + case RouteId_BeginRead: + { + double Time = Context.SessionContext.TimestampFromCycle(EventData.GetValue("Cycle").As()); + uint64 ReadHandle = EventData.GetValue("ReadHandle").As(); + uint64 FileHandle = EventData.GetValue("FileHandle").As(); + uint64 Offset = EventData.GetValue("Offset").As(); + uint64 Size = EventData.GetValue("Size").As(); + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + check(OpenFilesMap.Contains(FileHandle)); + uint32 FileIndex = OpenFilesMap[FileHandle]; + uint64 ReadIndex = FileActivityProvider.BeginActivity(FileIndex, Trace::FileActivityType_Read, Offset, Size, Time); + FPendingActivity& Read = ActiveReadsMap.Add(ReadHandle); + Read.FileIndex = FileIndex; + Read.ActivityIndex = ReadIndex; + break; + } + case RouteId_EndRead: + { + double Time = Context.SessionContext.TimestampFromCycle(EventData.GetValue("Cycle").As()); + uint64 ReadHandle = EventData.GetValue("ReadHandle").As(); + uint64 SizeRead = EventData.GetValue("SizeRead").As(); + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + check(ActiveReadsMap.Contains(ReadHandle)); + const FPendingActivity& Read = ActiveReadsMap[ReadHandle]; + FileActivityProvider.EndActivity(Read.FileIndex, Read.ActivityIndex, Time, false); + ActiveReadsMap.Remove(ReadHandle); + break; + } + case RouteId_BeginWrite: + { + double Time = Context.SessionContext.TimestampFromCycle(EventData.GetValue("Cycle").As()); + uint64 WriteHandle = EventData.GetValue("WriteHandle").As(); + uint64 FileHandle = EventData.GetValue("FileHandle").As(); + uint64 Offset = EventData.GetValue("Offset").As(); + uint64 Size = EventData.GetValue("Size").As(); + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + check(OpenFilesMap.Contains(FileHandle)); + uint32 FileIndex = OpenFilesMap[FileHandle]; + uint64 WriteIndex = FileActivityProvider.BeginActivity(FileIndex, Trace::FileActivityType_Write, Offset, Size, Time); + FPendingActivity& Write = ActiveWritesMap.Add(WriteHandle); + Write.FileIndex = FileIndex; + Write.ActivityIndex = WriteIndex; + break; + } + case RouteId_EndWrite: + { + double Time = Context.SessionContext.TimestampFromCycle(EventData.GetValue("Cycle").As()); + uint64 WriteHandle = EventData.GetValue("WriteHandle").As(); + uint64 SizeWritten = EventData.GetValue("SizeWritten").As(); + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + check(ActiveWritesMap.Contains(WriteHandle)); + const FPendingActivity& Write = ActiveWritesMap[WriteHandle]; + FileActivityProvider.EndActivity(Write.FileIndex, Write.ActivityIndex, Time, false); + ActiveWritesMap.Remove(WriteHandle); + break; + } + } +} + +void FPlatformFileTraceAnalyzer::OnAnalysisEnd() +{ +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Analyzers/PlatformFileTraceAnalysis.h b/Engine/Source/Developer/TraceServices/Private/Analyzers/PlatformFileTraceAnalysis.h new file mode 100644 index 000000000000..91e97d3dfb4a --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Analyzers/PlatformFileTraceAnalysis.h @@ -0,0 +1,53 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Trace/Trace.h" +#include "Trace/Analyzer.h" +#include "Containers/Map.h" +#include "TraceServices/Model/AnalysisSession.h" + +namespace Trace +{ + class FTimeline; + class FFileActivityProvider; +} + +class FPlatformFileTraceAnalyzer + : public Trace::IAnalyzer +{ +public: + FPlatformFileTraceAnalyzer(Trace::IAnalysisSession& Session, Trace::FFileActivityProvider& FileActivityProvider); + virtual void OnAnalysisBegin(const FOnAnalysisContext& Context) override; + virtual void OnEvent(uint16 RouteId, const FOnEventContext& Context) override; + virtual void OnAnalysisEnd() override; + +private: + enum : uint16 + { + RouteId_BeginOpen, + RouteId_EndOpen, + RouteId_BeginClose, + RouteId_EndClose, + RouteId_BeginRead, + RouteId_EndRead, + RouteId_BeginWrite, + RouteId_EndWrite, + + RouteId_Count + }; + + struct FPendingActivity + { + uint64 ActivityIndex; + uint32 FileIndex; + }; + + Trace::IAnalysisSession& Session; + Trace::FFileActivityProvider& FileActivityProvider; + TMap OpenFilesMap; + TMap PendingOpenMap; + TMap PendingCloseMap; + TMap ActiveReadsMap; + TMap ActiveWritesMap; +}; diff --git a/Engine/Source/Developer/TraceServices/Private/Analyzers/StatsTraceAnalysis.cpp b/Engine/Source/Developer/TraceServices/Private/Analyzers/StatsTraceAnalysis.cpp new file mode 100644 index 000000000000..fad59cc79fa7 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Analyzers/StatsTraceAnalysis.cpp @@ -0,0 +1,140 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +#include "StatsTraceAnalysis.h" +#include "AnalysisServicePrivate.h" +#include "Common/Utils.h" +#include "Model/Counters.h" + +FStatsAnalyzer::FStatsAnalyzer(Trace::FAnalysisSession& InSession, Trace::FCounterProvider& InCounterProvider) + : Session(InSession) + , CounterProvider(InCounterProvider) +{ + +} + +void FStatsAnalyzer::OnAnalysisBegin(const FOnAnalysisContext& Context) +{ + auto& Builder = Context.InterfaceBuilder; + + Builder.RouteEvent(RouteId_Spec, "Stats", "Spec"); + Builder.RouteEvent(RouteId_EventBatch, "Stats", "EventBatch"); +} + +void FStatsAnalyzer::OnAnalysisEnd() +{ +} + +void FStatsAnalyzer::OnEvent(uint16 RouteId, const FOnEventContext& Context) +{ + const auto& EventData = Context.EventData; + switch (RouteId) + { + case RouteId_Spec: + { + Trace::FAnalysisSessionEditScope _(Session); + uint32 StatId = EventData.GetValue("Id").As(); + check(!CountersMap.Contains(StatId)); + const ANSICHAR* Name = reinterpret_cast(EventData.GetAttachment()); + const TCHAR* Description = reinterpret_cast(EventData.GetAttachment() + strlen(Name) + 1); + Trace::ECounterDisplayHint DisplayHint = Trace::CounterDisplayHint_None; + if (EventData.GetValue("IsFloatingPoint").As()) + { + DisplayHint = Trace::CounterDisplayHint_FloatingPoint; + } + else if (EventData.GetValue("IsMemory").As()) + { + DisplayHint = Trace::CounterDisplayHint_Memory; + } + Trace::FCounterInternal* Counter = CounterProvider.CreateCounter(ANSI_TO_TCHAR(Name), Description, DisplayHint); + CountersMap.Add(StatId, Counter); + break; + } + case RouteId_EventBatch: + { + Trace::FAnalysisSessionEditScope _(Session); + uint32 ThreadId = EventData.GetValue("ThreadId").As(); + TSharedRef ThreadState = GetThreadState(ThreadId); + uint64 BufferSize = EventData.GetAttachmentSize(); + const uint8* BufferPtr = EventData.GetAttachment(); + const uint8* BufferEnd = BufferPtr + BufferSize; + while (BufferPtr < BufferEnd) + { + enum EOpType + { + Increment = 0, + Decrement = 1, + AddInteger = 2, + SetInteger = 3, + AddFloat = 4, + SetFloat = 5, + }; + + uint64 DecodedIdAndOp = FTraceAnalyzerUtils::Decode7bit(BufferPtr); + uint32 StatId = DecodedIdAndOp >> 3; + //check(CountersMap.Contains(StatId)); + Trace::FCounterInternal& Counter = *CountersMap[StatId]; + uint8 Op = DecodedIdAndOp & 0x7; + uint64 CycleDiff = FTraceAnalyzerUtils::Decode7bit(BufferPtr); + uint64 Cycle = ThreadState->LastCycle + CycleDiff; + double Time = Context.SessionContext.TimestampFromCycle(Cycle); + ThreadState->LastCycle = Cycle; + switch (Op) + { + case Increment: + { + CounterProvider.Add(Counter, Time, int64(1)); + break; + } + case Decrement: + { + CounterProvider.Add(Counter, Time, int64(-1)); + break; + } + case AddInteger: + { + int64 Amount = FTraceAnalyzerUtils::DecodeZigZag(BufferPtr); + CounterProvider.Add(Counter, Time, Amount); + break; + } + case SetInteger: + { + int64 Value = FTraceAnalyzerUtils::DecodeZigZag(BufferPtr); + CounterProvider.Set(Counter, Time, Value); + break; + } + case AddFloat: + { + double Amount; + memcpy(&Amount, BufferPtr, sizeof(double)); + BufferPtr += sizeof(double); + CounterProvider.Add(Counter, Time, Amount); + break; + } + case SetFloat: + { + double Value; + memcpy(&Value, BufferPtr, sizeof(double)); + BufferPtr += sizeof(double); + break; + } + } + } + check(BufferPtr == BufferEnd); + break; + } + } +} + +TSharedRef FStatsAnalyzer::GetThreadState(uint32 ThreadId) +{ + if (!ThreadStatesMap.Contains(ThreadId)) + { + TSharedRef ThreadState = MakeShared(); + ThreadState->LastCycle = 0; + ThreadStatesMap.Add(ThreadId, ThreadState); + return ThreadState; + } + else + { + return ThreadStatesMap[ThreadId]; + } +} diff --git a/Engine/Source/Developer/TraceServices/Private/Analyzers/StatsTraceAnalysis.h b/Engine/Source/Developer/TraceServices/Private/Analyzers/StatsTraceAnalysis.h new file mode 100644 index 000000000000..11d4dd08af43 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Analyzers/StatsTraceAnalysis.h @@ -0,0 +1,43 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Trace/Trace.h" +#include "Trace/Analyzer.h" +#include "Templates/SharedPointer.h" + +namespace Trace +{ + class FAnalysisSession; + class FCounterInternal; + class FCounterProvider; +} + +class FStatsAnalyzer + : public Trace::IAnalyzer +{ +public: + FStatsAnalyzer(Trace::FAnalysisSession& Session, Trace::FCounterProvider& CounterProvider); + virtual void OnAnalysisBegin(const FOnAnalysisContext& Context) override; + virtual void OnEvent(uint16 RouteId, const FOnEventContext& Context) override; + virtual void OnAnalysisEnd() override; + +private: + enum : uint16 + { + RouteId_Spec, + RouteId_EventBatch, + }; + + struct FThreadState + { + uint64 LastCycle = 0; + }; + + TSharedRef GetThreadState(uint32 ThreadId); + + Trace::FAnalysisSession& Session; + Trace::FCounterProvider& CounterProvider; + TMap CountersMap; + TMap> ThreadStatesMap; +}; diff --git a/Engine/Source/Developer/TraceServices/Private/Common/FormatArgs.cpp b/Engine/Source/Developer/TraceServices/Private/Common/FormatArgs.cpp new file mode 100644 index 000000000000..c3a96f8dab30 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Common/FormatArgs.cpp @@ -0,0 +1,347 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Common/FormatArgs.h" +#include "ProfilingDebugging/FormatArgsTrace.h" +#include "CoreMinimal.h" + +namespace Trace +{ + +const TCHAR* FFormatArgsHelper::ExtractNextFormatArg(const TCHAR* FormatString, FFormatArgSpec& Spec) +{ + Spec.PassthroughLength = 0; + Spec.AdditionalIntegerArgumentCount = 0; + Spec.Valid = false; + Spec.NothingPrinted = false; + + enum EState + { + None, + Flags, + Width, + PrecisionStart, + Precision, + Length, + Specifier + }; + EState CurrentState = None; + const TCHAR* Src = FormatString; + const TCHAR* FormatSpecifierStart = nullptr; + while (*Src != 0) + { + switch (CurrentState) + { + case None: + if (*Src == '%') + { + FormatSpecifierStart = Src; + CurrentState = Flags; + } + ++Src; + break; + case Flags: + if (*Src == '%') + { + ++Src; + CurrentState = None; + break; + } + if (*Src == '-' || + *Src == '+' || + *Src == ' ' || + *Src == '#' || + *Src == '0') + { + ++Src; + break; + } + CurrentState = Width; + break; + case Width: + if (*Src == '*') + { + ++Spec.AdditionalIntegerArgumentCount; + ++Src; + CurrentState = PrecisionStart; + break; + } + else if ('0' <= *Src && *Src <= '9') + { + ++Src; + break; + } + CurrentState = PrecisionStart; + break; + case PrecisionStart: + if (*Src == '.') + { + ++Src; + CurrentState = Precision; + break; + } + CurrentState = Length; + break; + case Precision: + if (*Src == '*') + { + ++Spec.AdditionalIntegerArgumentCount; + ++Src; + CurrentState = Length; + break; + } + else if ('0' <= *Src && *Src <= '9') + { + ++Src; + break; + } + CurrentState = Length; + break; + case Length: + if (*Src == 'h' || + *Src == 'l' || + *Src == 'j' || + *Src == 'z' || + *Src == 't' || + *Src == 'L') + { + ++Src; + break; + } + CurrentState = Specifier; + break; + case Specifier: + if (*Src == 'd' || + *Src == 'i' || + *Src == 'u' || + *Src == 'o' || + *Src == 'x' || + *Src == 'X' || + *Src == 'c' || + *Src == 'p') + { + Spec.ExpectedTypeCategory = FFormatArgsTrace::FormatArgTypeCode_CategoryInteger; + } + else if (*Src == 'f' || + *Src == 'F' || + *Src == 'e' || + *Src == 'E' || + *Src == 'g' || + *Src == 'G' || + *Src == 'a' || + *Src == 'A') + { + Spec.ExpectedTypeCategory = FFormatArgsTrace::FormatArgTypeCode_CategoryFloatingPoint; + } + else if (*Src == 's' || *Src == 'S') + { + Spec.ExpectedTypeCategory = FFormatArgsTrace::FormatArgTypeCode_CategoryString; + } + else if (*Src == 'n') + { + Spec.ExpectedTypeCategory = FFormatArgsTrace::FormatArgTypeCode_CategoryInteger; + Spec.NothingPrinted = true; + } + else + { + CurrentState = None; + break; + } + + ++Src; + uint64 FormatSpecifierLength = Src + 1 - FormatSpecifierStart; + check(FormatSpecifierLength < 255); + FCString::Strncpy(Spec.FormatString, FormatSpecifierStart, FormatSpecifierLength); + Spec.Valid = true; + Spec.PassthroughLength = FormatSpecifierStart - FormatString; + return Src; + } + } + Spec.PassthroughLength = Src - FormatString; + return Src; +} + +void FFormatArgsHelper::InitArgumentStream(FFormatArgsStreamContext& Context, const uint8* ArgumentsData) +{ + Context.ArgumentCount = *ArgumentsData++; + Context.DescriptorPtr = ArgumentsData; + Context.PayloadPtr = ArgumentsData + Context.ArgumentCount; + if (Context.ArgumentCount) + { + Context.ArgumentTypeCategory = *Context.DescriptorPtr & FFormatArgsTrace::FormatArgTypeCode_CategoryBitMask; + Context.ArgumentTypeSize = *Context.DescriptorPtr & FFormatArgsTrace::FormatArgTypeCode_SizeBitMask; + } + else + { + Context.ArgumentTypeCategory = 0; + Context.ArgumentTypeSize = 0; + } +} + +bool FFormatArgsHelper::AdvanceArgumentStream(FFormatArgsStreamContext& Context) +{ + if (Context.ArgumentTypeCategory == 0) + { + return false; + } + if (Context.ArgumentTypeCategory == FFormatArgsTrace::FormatArgTypeCode_CategoryString) + { + check(Context.ArgumentTypeSize == 2); + const TCHAR* StringPtr = reinterpret_cast(Context.PayloadPtr); + while (*StringPtr++); + Context.PayloadPtr = reinterpret_cast(StringPtr); + } + else + { + Context.PayloadPtr += Context.ArgumentTypeSize; + } + ++Context.DescriptorPtr; + Context.ArgumentTypeCategory = *Context.DescriptorPtr & FFormatArgsTrace::FormatArgTypeCode_CategoryBitMask; + Context.ArgumentTypeSize = *Context.DescriptorPtr & FFormatArgsTrace::FormatArgTypeCode_SizeBitMask; + --Context.ArgumentCount; + return true; +} + +uint64 FFormatArgsHelper::ExtractIntegerArgument(FFormatArgsStreamContext& ArgStream) +{ + uint64 Result = 0; + if (ArgStream.ArgumentTypeCategory == FFormatArgsTrace::FormatArgTypeCode_CategoryInteger) + { + memcpy(&Result, ArgStream.PayloadPtr, ArgStream.ArgumentTypeSize); + } + AdvanceArgumentStream(ArgStream); + return Result; +} + +double FFormatArgsHelper::ExtractFloatingPointArgument(FFormatArgsStreamContext& ArgStream) +{ + double Result = 0.0; + if (ArgStream.ArgumentTypeCategory == FFormatArgsTrace::FormatArgTypeCode_CategoryFloatingPoint) + { + if (ArgStream.ArgumentTypeSize == 4) + { + Result = *reinterpret_cast(ArgStream.PayloadPtr); + } + else + { + check(ArgStream.ArgumentTypeSize == 8) + Result = *reinterpret_cast(ArgStream.PayloadPtr); + } + } + AdvanceArgumentStream(ArgStream); + return Result; +} + +const TCHAR* FFormatArgsHelper::ExtractStringArgument(FFormatArgsStreamContext& ArgStream) +{ + static TCHAR Empty[] = TEXT(""); + const TCHAR* Result = Empty; + if (ArgStream.ArgumentTypeCategory == FFormatArgsTrace::FormatArgTypeCode_CategoryString) + { + check(ArgStream.ArgumentTypeSize == 2); + Result = reinterpret_cast(ArgStream.PayloadPtr); + } + AdvanceArgumentStream(ArgStream); + return Result; +} + +int32 FFormatArgsHelper::FormatArgument(TCHAR* Out, uint64 MaxOut, const FFormatArgSpec& ArgSpec, FFormatArgsStreamContext& ArgStream) +{ + check(ArgSpec.AdditionalIntegerArgumentCount <= 2); + switch (ArgSpec.ExpectedTypeCategory) + { + case FFormatArgsTrace::FormatArgTypeCode_CategoryInteger: + if (ArgSpec.NothingPrinted) + { + for (uint8 IntegerArgIndex = 0; IntegerArgIndex < ArgSpec.AdditionalIntegerArgumentCount + 1; ++IntegerArgIndex) + { + ExtractIntegerArgument(ArgStream); + } + return 0; + } + else + { + if (ArgSpec.AdditionalIntegerArgumentCount == 2) + { + return FCString::Snprintf(Out, MaxOut, ArgSpec.FormatString, ExtractIntegerArgument(ArgStream), ExtractIntegerArgument(ArgStream), ExtractIntegerArgument(ArgStream)); + } + else if (ArgSpec.AdditionalIntegerArgumentCount == 1) + { + return FCString::Snprintf(Out, MaxOut, ArgSpec.FormatString, ExtractIntegerArgument(ArgStream), ExtractIntegerArgument(ArgStream)); + } + else + { + return FCString::Snprintf(Out, MaxOut, ArgSpec.FormatString, ExtractIntegerArgument(ArgStream)); + } + } + break; + case FFormatArgsTrace::FormatArgTypeCode_CategoryFloatingPoint: + if (ArgSpec.AdditionalIntegerArgumentCount == 2) + { + return FCString::Snprintf(Out, MaxOut, ArgSpec.FormatString, ExtractIntegerArgument(ArgStream), ExtractIntegerArgument(ArgStream), ExtractFloatingPointArgument(ArgStream)); + } + else if (ArgSpec.AdditionalIntegerArgumentCount == 1) + { + return FCString::Snprintf(Out, MaxOut, ArgSpec.FormatString, ExtractIntegerArgument(ArgStream), ExtractFloatingPointArgument(ArgStream)); + } + else + { + return FCString::Snprintf(Out, MaxOut, ArgSpec.FormatString, ExtractFloatingPointArgument(ArgStream)); + } + break; + case FFormatArgsTrace::FormatArgTypeCode_CategoryString: + if (ArgSpec.AdditionalIntegerArgumentCount == 2) + { + return FCString::Snprintf(Out, MaxOut, ArgSpec.FormatString, ExtractIntegerArgument(ArgStream), ExtractIntegerArgument(ArgStream), ExtractStringArgument(ArgStream)); + } + else if (ArgSpec.AdditionalIntegerArgumentCount == 1) + { + return FCString::Snprintf(Out, MaxOut, ArgSpec.FormatString, ExtractIntegerArgument(ArgStream), ExtractStringArgument(ArgStream)); + } + else + { + return FCString::Snprintf(Out, MaxOut, ArgSpec.FormatString, ExtractStringArgument(ArgStream)); + } + break; + default: + check(false); + return 0; + } +} + +void FFormatArgsHelper::Format(TCHAR* Out, uint64 MaxOut, const TCHAR* FormatString, const uint8* FormatArgs) +{ + FFormatArgsStreamContext ArgumentStream; + InitArgumentStream(ArgumentStream, FormatArgs); + if (ArgumentStream.ArgumentCount == 0) + { + FCString::Strcpy(Out, MaxOut, FormatString); + return; + } + + const TCHAR* Src = FormatString; + TCHAR* Dst = Out; + TCHAR* DstEnd = Out + MaxOut; + while (*Src != 0 && Dst != DstEnd) + { + FFormatArgSpec Spec; + const TCHAR* NextSrc = ExtractNextFormatArg(Src, Spec); + uint64 PassthroughCopyLength = FMath::Min(Spec.PassthroughLength, DstEnd - Dst); + if (PassthroughCopyLength) + { + FCString::Strncpy(Dst, Src, PassthroughCopyLength + 1); + Dst += PassthroughCopyLength; + } + if (Spec.Valid) + { + int32 Length = FormatArgument(Dst, DstEnd - Dst, Spec, ArgumentStream); + if (Length < 0) + { + break; + } + Dst += Length; + } + Src = NextSrc; + } +} + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Common/FormatArgs.h b/Engine/Source/Developer/TraceServices/Private/Common/FormatArgs.h new file mode 100644 index 000000000000..2e3959402bdb --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Common/FormatArgs.h @@ -0,0 +1,43 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" + +namespace Trace +{ + +struct FFormatArgsHelper +{ + static void Format(TCHAR* Out, uint64 MaxOut, const TCHAR* FormatString, const uint8* FormatArgs); + +private: + struct FFormatArgSpec + { + uint64 PassthroughLength; + TCHAR FormatString[255]; + uint8 ExpectedTypeCategory; + uint8 AdditionalIntegerArgumentCount; + bool Valid; + bool NothingPrinted; + }; + + struct FFormatArgsStreamContext + { + uint8 ArgumentCount; + uint8 ArgumentTypeCategory; + uint8 ArgumentTypeSize; + const uint8* DescriptorPtr; + const uint8* PayloadPtr; + }; + + static const TCHAR* ExtractNextFormatArg(const TCHAR* FormatString, FFormatArgSpec& Spec); + static void InitArgumentStream(FFormatArgsStreamContext& Context, const uint8* ArgumentsData); + static bool AdvanceArgumentStream(FFormatArgsStreamContext& Context); + static uint64 ExtractIntegerArgument(FFormatArgsStreamContext& ArgStream); + static double ExtractFloatingPointArgument(FFormatArgsStreamContext& ArgStream); + static const TCHAR* ExtractStringArgument(FFormatArgsStreamContext& ArgStream); + static int32 FormatArgument(TCHAR* Out, uint64 MaxOut, const FFormatArgSpec& ArgSpec, FFormatArgsStreamContext& ArgStream); +}; + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Common/PagedArray.h b/Engine/Source/Developer/TraceServices/Private/Common/PagedArray.h new file mode 100644 index 000000000000..ef0d61b5330f --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Common/PagedArray.h @@ -0,0 +1,319 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/Containers/Allocators.h" +#include "Containers/Array.h" + +template +struct TPagedArrayPage +{ + ItemType* Items = nullptr; + uint64 Count = 0; +}; + +template +inline const ItemType* GetData(const TPagedArrayPage& Page) +{ + return Page.Items; +} + +template +inline SIZE_T GetNum(const TPagedArrayPage& Page) +{ + return Page.Count; +} + +template +inline const ItemType* GetFirstItem(const TPagedArrayPage& Page) +{ + return Page.Items; +} + +template +inline const ItemType* GetLastItem(const TPagedArrayPage& Page) +{ + if (Page.Count) + { + return Page.Items + Page.Count - 1; + } + else + { + return nullptr; + } +} + +template> +class TPagedArray +{ +public: + typedef InItemType ItemType; + typedef InPageType PageType; + + class FIterator + { + public: + uint64 GetCurrentPageIndex() + { + return CurrentPageIndex; + } + + const PageType* GetCurrentPage() + { + return Outer.FirstPage + CurrentPageIndex; + } + + const ItemType* GetCurrentItem() + { + return CurrentItem; + } + + const PageType* PrevPage() + { + if (CurrentPageIndex == 0) + { + return nullptr; + } + --CurrentPageIndex; + OnCurrentPageChanged(); + CurrentItem = CurrentPageLastItem; + return GetCurrentPage(); + } + + const PageType* NextPage() + { + if (CurrentPageIndex == Outer.PagesArray.Num() - 1) + { + return nullptr; + } + ++CurrentPageIndex; + OnCurrentPageChanged(); + CurrentItem = CurrentPageFirstItem; + return GetCurrentPage(); + } + + const ItemType* NextItem() + { + if (CurrentItem == CurrentPageLastItem) + { + if (!NextPage()) + { + return nullptr; + } + else + { + return CurrentItem; + } + } + ++CurrentItem; + return CurrentItem; + } + + const ItemType* PrevItem() + { + if (CurrentItem == CurrentPageFirstItem) + { + if (!PrevPage()) + { + return nullptr; + } + else + { + return CurrentItem; + } + } + --CurrentItem; + return CurrentItem; + } + + const ItemType* SetPosition(uint64 Index) + { + uint64 PageIndex = Index / Outer.PageSize; + uint64 ItemIndexInPage = Index % Outer.PageSize; + if (PageIndex != CurrentPageIndex) + { + check(PageIndex < Outer.PagesArray.Num()); + CurrentPageIndex = PageIndex; + OnCurrentPageChanged(); + } + PageType* CurrentPage = Outer.FirstPage + CurrentPageIndex; + check(ItemIndexInPage < CurrentPage->Count); + CurrentItem = CurrentPage->Items + ItemIndexInPage; + return CurrentItem; + } + + private: + friend class TPagedArray; + + FIterator(const TPagedArray& InOuter, uint64 InitialPageIndex, uint64 InitialItemIndex) + : Outer(InOuter) + { + if (Outer.PagesArray.Num()) + { + check(InitialPageIndex < Outer.PagesArray.Num()); + CurrentPageIndex = InitialPageIndex; + OnCurrentPageChanged(); + PageType* CurrentPage = Outer.FirstPage + CurrentPageIndex; + check(InitialItemIndex < CurrentPage->Count); + CurrentItem = CurrentPage->Items + InitialItemIndex; + } + } + + void OnCurrentPageChanged() + { + PageType* CurrentPage = Outer.FirstPage + CurrentPageIndex; + CurrentPageFirstItem = CurrentPage->Items; + if (CurrentPage->Items) + { + CurrentPageLastItem = CurrentPage->Items + CurrentPage->Count - 1; + } + else + { + CurrentPageLastItem = nullptr; + } + } + + const TPagedArray& Outer; + uint64 CurrentPageIndex = 0; + const ItemType* CurrentItem = nullptr; + const ItemType* CurrentPageFirstItem = nullptr; + const ItemType* CurrentPageLastItem = nullptr; + }; + + TPagedArray(Trace::ILinearAllocator& InAllocator, uint64 InPageSize) + : Allocator(InAllocator) + , PageSize(InPageSize) + { + + } + + ~TPagedArray() + { + for (PageType& Page : PagesArray) + { + ItemType* PageEnd = Page.Items + Page.Count; + for (ItemType* Item = Page.Items; Item != PageEnd; ++Item) + { + Item->~ItemType(); + } + } + } + + uint64 Num() const + { + return TotalItemCount; + } + + uint64 GetPageSize() const + { + return PageSize; + } + + uint64 NumPages() const + { + return PagesArray.Num(); + } + + ItemType& PushBack() + { + if (!LastPage || LastPage->Count == PageSize) + { + LastPage = &PagesArray.AddDefaulted_GetRef(); + FirstPage = PagesArray.GetData(); + LastPage->Items = reinterpret_cast(Allocator.Allocate(PageSize * sizeof(ItemType))); + } + ++TotalItemCount; + ItemType* ItemPtr = LastPage->Items + LastPage->Count; + new (ItemPtr) ItemType(); + ++LastPage->Count; + return *ItemPtr; + } + + ItemType& Insert(uint64 Index) + { + if (Index >= TotalItemCount) + { + return PushBack(); + } + PushBack(); + uint64 PageIndex = Index / PageSize; + uint64 PageItemIndex = Index % PageSize; + for (uint64 CurrentPageIndex = PagesArray.Num() - 1; CurrentPageIndex > PageIndex; --CurrentPageIndex) + { + PageType* CurrentPage = FirstPage + CurrentPageIndex; + memmove(CurrentPage->Items + 1, CurrentPage->Items, sizeof(ItemType) * (CurrentPage->Count - 1)); + PageType* PrevPage = CurrentPage - 1; + memcpy(CurrentPage->Items, PrevPage->Items + PrevPage->Count - 1, sizeof(ItemType)); + } + PageType* Page = FirstPage + PageIndex; + memmove(Page->Items + PageItemIndex + 1, Page->Items + PageItemIndex, sizeof(ItemType) * (Page->Count - PageItemIndex - 1)); + return Page->Items[PageItemIndex]; + } + + PageType* GetLastPage() + { + return LastPage; + } + + PageType* GetPage(uint64 PageIndex) + { + return FirstPage + PageIndex; + } + + PageType* GetItemPage(uint64 ItemIndex) + { + uint64 PageIndex = ItemIndex / PageSize; + return FirstPage + PageIndex; + } + + FIterator GetIteratorFromPage(uint64 PageIndex) const + { + return FIterator(*this, PageIndex, 0); + } + + FIterator GetIteratorFromItem(uint64 ItemIndex) const + { + uint64 PageIndex = ItemIndex / PageSize; + uint64 IndexInPage = ItemIndex % PageSize; + return FIterator(*this, PageIndex, IndexInPage); + } + + const PageType* GetPages() const + { + return FirstPage; + } + + ItemType& operator[](uint64 Index) + { + uint64 PageIndex = Index / PageSize; + uint64 IndexInPage = Index % PageSize; + PageType* Page = FirstPage + PageIndex; + ItemType* Item = Page->Items + IndexInPage; + return *Item; + } + + const ItemType& operator[](uint64 Index) const + { + return const_cast(*this)[Index]; + } + +private: + Trace::ILinearAllocator& Allocator; + TArray PagesArray; + PageType* FirstPage = nullptr; + PageType* LastPage = nullptr; + uint64 PageSize; + uint64 TotalItemCount = 0; +}; + +template +inline const PageType* GetData(const TPagedArray& PagedArray) +{ + return PagedArray.GetPages(); +} + +template +inline SIZE_T GetNum(const TPagedArray& PagedArray) +{ + return PagedArray.NumPages(); +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Common/SlabAllocator.h b/Engine/Source/Developer/TraceServices/Private/Common/SlabAllocator.h new file mode 100644 index 000000000000..f3bf1b56a6f8 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Common/SlabAllocator.h @@ -0,0 +1,49 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "HAL/UnrealMemory.h" +#include "Containers/Array.h" +#include "TraceServices/Containers/Allocators.h" + +class FSlabAllocator + : public Trace::ILinearAllocator +{ +public: + FSlabAllocator(uint64 InSlabSize) + : SlabSize(InSlabSize) + { + + } + + ~FSlabAllocator() + { + for (void* Slab : Slabs) + { + FMemory::Free(Slab); + } + } + + virtual void* Allocate(uint64 Size) override + { + uint64 AllocationSize = Size + (16 - 1) / 16; + if (!CurrentSlab || CurrentSlabAllocatedSize + AllocationSize > SlabSize) + { + TotalAllocatedSize += SlabSize; + void* Allocation = FMemory::Malloc(SlabSize, 16); + CurrentSlab = reinterpret_cast(Allocation); + CurrentSlabAllocatedSize = 0; + Slabs.Add(CurrentSlab); + } + void* Allocation = CurrentSlab + CurrentSlabAllocatedSize; + CurrentSlabAllocatedSize += AllocationSize; + return Allocation; + } + +private: + TArray Slabs; + uint8* CurrentSlab = nullptr; + const uint64 SlabSize; + uint64 CurrentSlabAllocatedSize = 0; + uint64 TotalAllocatedSize = 0; +}; diff --git a/Engine/Source/Developer/TraceServices/Private/Common/StringStore.cpp b/Engine/Source/Developer/TraceServices/Private/Common/StringStore.cpp new file mode 100644 index 000000000000..5fc16c0d6c68 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Common/StringStore.cpp @@ -0,0 +1,41 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Common/StringStore.h" + +namespace Trace +{ + +FStringStore::FStringStore(FSlabAllocator& InAllocator) + : Allocator(InAllocator) +{ + +} + +const TCHAR* FStringStore::Store(const TCHAR* String) +{ + uint32 Hash = GetTypeHash(String); + const TCHAR** AlreadyStored = StoredStrings.Find(Hash); + if (AlreadyStored && !FCString::Strcmp(String, *AlreadyStored)) + { + return *AlreadyStored; + } + + int32 StringLength = FCString::Strlen(String) + 1; + if (BufferLeft < StringLength) + { + BufferPtr = reinterpret_cast(Allocator.Allocate(BlockSize * sizeof(TCHAR))); + ++BlockCount; + BufferLeft = BlockSize; + } + const TCHAR* Stored = BufferPtr; + memcpy(BufferPtr, String, StringLength * sizeof(TCHAR)); + BufferLeft -= StringLength; + BufferPtr += StringLength; + if (!AlreadyStored) + { + StoredStrings.Add(Hash, Stored); + } + return Stored; +} + +} diff --git a/Engine/Source/Developer/TraceServices/Private/Common/StringStore.h b/Engine/Source/Developer/TraceServices/Private/Common/StringStore.h new file mode 100644 index 000000000000..b6d773e7fb3d --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Common/StringStore.h @@ -0,0 +1,32 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "Common/SlabAllocator.h" +#include "Common/PagedArray.h" +#include "Containers/Map.h" + +namespace Trace +{ + +class FStringStore +{ +public: + FStringStore(FSlabAllocator& Allocator); + const TCHAR* Store(const TCHAR* String); + +private: + enum + { + BlockSize = 4 << 20 + }; + FSlabAllocator& Allocator; + TMap StoredStrings; + TCHAR* BufferPtr = nullptr; + uint64 BufferLeft = 0; + uint64 BlockCount = 0; + +}; + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Common/TimelineStatistics.h b/Engine/Source/Developer/TraceServices/Private/Common/TimelineStatistics.h new file mode 100644 index 000000000000..907c3c893a45 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Common/TimelineStatistics.h @@ -0,0 +1,269 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +namespace Trace +{ + +class FTimelineStatistics +{ +public: + template + static void CreateAggregation(const TArray& Timelines, BucketMappingFunc BucketMapper, double IntervalStart, double IntervalEnd, TMap& Result) + { + TMap InternalResult; + // Compute instance count and total/min/max inclusive/exclusive times for each timer. + for (const TimelineType* Timeline : Timelines) + { + ProcessTimeline(Timeline, BucketMapper, UpdateTotalMinMaxTimerStats, IntervalStart, IntervalEnd, InternalResult); + } + // Now, as we know min/max inclusive/exclusive times for each timer, we can compute histogram and median values. + const bool ComputeMedian = true; + if (ComputeMedian) + { + // Update bucket size (DT) for computing histogram. + for (auto& KV : InternalResult) + { + PreComputeHistogram(KV.Value); + } + + // Compute histogram. + for (const TimelineType* Timeline : Timelines) + { + ProcessTimeline(Timeline, BucketMapper, UpdateHistogramForTimerStats, IntervalStart, IntervalEnd, InternalResult); + } + } + + // Compute average and median inclusive/exclusive times. + for (auto& KV : InternalResult) + { + PostProcessTimerStats(KV.Value, ComputeMedian); + Result.Add(KV.Key, KV.Value.Stats); + } + } + +private: + enum + { + HistogramLen = 100 + }; + + struct FInternalAggregationEntry + { + FInternalAggregationEntry() + { + FMemory::Memzero(InclHistogram, sizeof(int32) * HistogramLen); + InclDT = 1.0; + + FMemory::Memzero(ExclHistogram, sizeof(int32) * HistogramLen); + ExclDT = 1.0; + } + + // Histogram for computing median inclusive time. + int32 InclHistogram[HistogramLen]; + double InclDT; // bucket size + + // Histogram for computing median exclusive time. + int32 ExclHistogram[HistogramLen]; + double ExclDT; // bucket size + + FAggregatedTimingStats Stats; + }; + + template + static void ProcessTimeline(const TimelineType* Timeline, BucketMappingFunc BucketMapper, CallbackType Callback, double IntervalStart, double IntervalEnd, TMap& InternalResult) + { + struct FStackEntry + { + double StartTime; + double ExclusiveTime; + BucketKeyType BucketKey; + }; + + TArray Stack; + Stack.Reserve(1024); + double LastTime = 0.0; + Timeline->EnumerateEvents(IntervalStart, IntervalEnd, [BucketMapper, Callback, IntervalStart, IntervalEnd, &Stack, &LastTime, &InternalResult](bool IsEnter, double Time, const typename TimelineType::EventType& Event) + { + Time = FMath::Clamp(Time, IntervalStart, IntervalEnd); + BucketKeyType BucketKey = BucketMapper(Event); + if (Stack.Num()) + { + FStackEntry& StackEntry = Stack.Top(); + StackEntry.ExclusiveTime += Time - LastTime; + } + LastTime = Time; + if (IsEnter) + { + FStackEntry& StackEntry = Stack.AddDefaulted_GetRef(); + StackEntry.StartTime = Time; + StackEntry.ExclusiveTime = 0.0; + StackEntry.BucketKey = BucketKey; + if (!InternalResult.Contains(BucketKey)) + { + InternalResult.Add(BucketKey); + } + } + else + { + FStackEntry& StackEntry = Stack.Last(); + double EventInclusiveTime = Time - StackEntry.StartTime; + check(EventInclusiveTime >= 0.0); + double EventExclusiveTime = StackEntry.ExclusiveTime; + check(EventExclusiveTime >= 0.0 && EventExclusiveTime <= EventInclusiveTime); + Stack.Pop(false); + double EventNonRecursiveInclusiveTime = EventInclusiveTime; + for (const FStackEntry& AncestorStackEntry : Stack) + { + if (AncestorStackEntry.BucketKey == BucketKey) + { + EventNonRecursiveInclusiveTime = 0.0; + } + } + Callback(InternalResult[BucketKey], EventNonRecursiveInclusiveTime, EventExclusiveTime); + } + }); + } + + static void UpdateTotalMinMaxTimerStats(FInternalAggregationEntry& AggregationEntry, double InclTime, double ExclTime) + { + FAggregatedTimingStats& Stats = AggregationEntry.Stats; + Stats.TotalInclusiveTime += InclTime; + if (InclTime < Stats.MinInclusiveTime) + { + Stats.MinInclusiveTime = InclTime; + } + if (InclTime > Stats.MaxInclusiveTime) + { + Stats.MaxInclusiveTime = InclTime; + } + + Stats.TotalExclusiveTime += ExclTime; + if (ExclTime < Stats.MinExclusiveTime) + { + Stats.MinExclusiveTime = ExclTime; + } + if (ExclTime > Stats.MaxExclusiveTime) + { + Stats.MaxExclusiveTime = ExclTime; + } + + Stats.InstanceCount++; + } + + static void PreComputeHistogram(FInternalAggregationEntry& AggregationEntry) + { + const FAggregatedTimingStats& Stats = AggregationEntry.Stats; + + // Each bucket (Histogram[i]) will be centered on a value. + // I.e. First bucket (bucket 0) is centered on Min value: [Min-DT/2, Min+DT/2) + // and last bucket (bucket N-1) is centered on Max value: [Max-DT/2, Max+DT/2). + + constexpr double InvHistogramLen = 1.0 / static_cast(HistogramLen - 1); + + if (Stats.MaxInclusiveTime == Stats.MinInclusiveTime) + { + AggregationEntry.InclDT = 1.0; // single large bucket + } + else + { + AggregationEntry.InclDT = (Stats.MaxInclusiveTime - Stats.MinInclusiveTime) * InvHistogramLen; + } + + if (Stats.MaxExclusiveTime == Stats.MinExclusiveTime) + { + AggregationEntry.ExclDT = 1.0; // single large bucket + } + else + { + AggregationEntry.ExclDT = (Stats.MaxExclusiveTime - Stats.MinExclusiveTime) * InvHistogramLen; + } + } + + static void UpdateHistogramForTimerStats(FInternalAggregationEntry& AggregationEntry, double InclTime, double ExclTime) + { + const FAggregatedTimingStats& Stats = AggregationEntry.Stats; + + int32 InclIndex = static_cast((InclTime - Stats.MinInclusiveTime + AggregationEntry.InclDT / 2) / AggregationEntry.InclDT); + ensure(InclIndex >= 0); + if (InclIndex < 0) + { + InclIndex = 0; + } + ensure(InclIndex < HistogramLen); + if (InclIndex >= HistogramLen) + { + InclIndex = HistogramLen - 1; + } + AggregationEntry.InclHistogram[InclIndex]++; + + int32 ExclIndex = static_cast((ExclTime - Stats.MinExclusiveTime + AggregationEntry.ExclDT / 2) / AggregationEntry.ExclDT); + ensure(ExclIndex >= 0); + if (ExclIndex < 0) + { + ExclIndex = 0; + } + ensure(ExclIndex < HistogramLen); + if (ExclIndex >= HistogramLen) + { + ExclIndex = HistogramLen - 1; + } + AggregationEntry.ExclHistogram[ExclIndex]++; + } + + static void PostProcessTimerStats(FInternalAggregationEntry& AggregationEntry, bool ComputeMedian) + { + FAggregatedTimingStats& Stats = AggregationEntry.Stats; + + // Compute average inclusive/exclusive times. + ensure(Stats.InstanceCount > 0); + double InvCount = 1.0f / static_cast(Stats.InstanceCount); + Stats.AverageInclusiveTime = Stats.TotalInclusiveTime * InvCount; + Stats.AverageExclusiveTime = Stats.TotalExclusiveTime * InvCount; + + if (ComputeMedian) + { + const int32 HalfCount = Stats.InstanceCount / 2; + + // Compute median inclusive time. + int32 InclCount = 0; + for (int32 HistogramIndex = 0; HistogramIndex < HistogramLen; HistogramIndex++) + { + InclCount += AggregationEntry.InclHistogram[HistogramIndex]; + if (InclCount > HalfCount) + { + Stats.MedianInclusiveTime = Stats.MinInclusiveTime + HistogramIndex * AggregationEntry.InclDT; + if (HistogramIndex > 0 && + Stats.InstanceCount % 2 == 0 && + InclCount - AggregationEntry.InclHistogram[HistogramIndex] == HalfCount) + { + const double PrevMedian = Stats.MinInclusiveTime + (HistogramIndex - 1) * AggregationEntry.InclDT; + Stats.MedianInclusiveTime = (Stats.MedianInclusiveTime + PrevMedian) / 2; + } + break; + } + } + + // Compute median exclusive time. + int32 ExclCount = 0; + for (int32 HistogramIndex = 0; HistogramIndex < HistogramLen; HistogramIndex++) + { + ExclCount += AggregationEntry.InclHistogram[HistogramIndex]; + if (ExclCount > HalfCount) + { + Stats.MedianExclusiveTime = Stats.MinExclusiveTime + HistogramIndex * AggregationEntry.ExclDT; + if (HistogramIndex > 0 && + Stats.InstanceCount % 2 == 0 && + ExclCount - AggregationEntry.ExclHistogram[HistogramIndex] == HalfCount) + { + const double PrevMedian = Stats.MinExclusiveTime + (HistogramIndex - 1) * AggregationEntry.ExclDT; + Stats.MedianExclusiveTime = (Stats.MedianExclusiveTime + PrevMedian) / 2; + } + break; + } + } + } + } +}; + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Common/Utils.h b/Engine/Source/Developer/TraceServices/Private/Common/Utils.h new file mode 100644 index 000000000000..d01d1fc25ca1 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Common/Utils.h @@ -0,0 +1,29 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" + +struct FTraceAnalyzerUtils +{ + static uint64 Decode7bit(const uint8*& BufferPtr) + { + uint64 Value = 0; + uint64 ByteIndex = 0; + bool HasMoreBytes; + do + { + uint8 ByteValue = *BufferPtr++; + HasMoreBytes = ByteValue & 0x80; + Value |= uint64(ByteValue & 0x7f) << (ByteIndex * 7); + ++ByteIndex; + } while (HasMoreBytes); + return Value; + } + + static int64 DecodeZigZag(const uint8*& BufferPtr) + { + uint64 Z = Decode7bit(BufferPtr); + return (Z & 1) ? (Z >> 1) ^ -1 : (Z >> 1); + } +}; diff --git a/Engine/Source/Developer/TraceServices/Private/Model/Bookmarks.cpp b/Engine/Source/Developer/TraceServices/Private/Model/Bookmarks.cpp new file mode 100644 index 000000000000..3f2af0bf19a6 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/Bookmarks.cpp @@ -0,0 +1,86 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TraceServices/Model/Bookmarks.h" +#include "Model/BookmarksPrivate.h" +#include "AnalysisServicePrivate.h" +#include "Common/FormatArgs.h" + +namespace Trace +{ + +const FName FBookmarkProvider::ProviderName("BookmarkProvider"); + +FBookmarkProvider::FBookmarkProvider(IAnalysisSession& InSession) + : Session(InSession) +{ + +} + +FBookmarkSpec& FBookmarkProvider::GetSpec(uint64 BookmarkPoint) +{ + Session.WriteAccessCheck(); + if (SpecMap.Contains(BookmarkPoint)) + { + return *SpecMap[BookmarkPoint].Get(); + } + else + { + TSharedPtr Spec = MakeShared(); + SpecMap.Add(BookmarkPoint, Spec); + return *Spec.Get(); + } +} + +void FBookmarkProvider::AppendBookmark(double Time, uint64 BookmarkPoint, const uint8* FormatArgs) +{ + Session.WriteAccessCheck(); + FBookmarkSpec& Spec = GetSpec(BookmarkPoint); + TSharedRef Bookmark = MakeShared(); + Bookmark->Time = Time; + FFormatArgsHelper::Format(FormatBuffer, FormatBufferSize - 1, Spec.FormatString, FormatArgs); + Bookmark->Text = Session.StoreString(FormatBuffer); + Bookmarks.Add(Bookmark); + Session.UpdateDurationSeconds(Time); +} + +void FBookmarkProvider::EnumerateBookmarks(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const +{ + Session.ReadAccessCheck(); + if (IntervalStart > IntervalEnd) + { + return; + } + uint64 FirstBookmarkIndex = Algo::LowerBoundBy(Bookmarks, IntervalStart, [](const TSharedRef& B) + { + return B->Time; + }); + uint64 BookmarkCount = Bookmarks.Num(); + if (FirstBookmarkIndex >= BookmarkCount) + { + return; + } + uint64 LastBookmarkIndex = Algo::UpperBoundBy(Bookmarks, IntervalEnd, [](const TSharedRef& B) + { + return B->Time; + }); + if (LastBookmarkIndex == 0) + { + return; + } + --LastBookmarkIndex; + for (uint64 Index = FirstBookmarkIndex; Index <= LastBookmarkIndex; ++Index) + { + const FBookmarkInternal& InternalBookmark = Bookmarks[Index].Get(); + FBookmark Bookmark; + Bookmark.Time = InternalBookmark.Time; + Bookmark.Text = InternalBookmark.Text; + Callback(Bookmark); + } +} + +const IBookmarkProvider& ReadBookmarkProvider(const IAnalysisSession& Session) +{ + return *Session.ReadProvider(FBookmarkProvider::ProviderName); +} + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Model/BookmarksPrivate.h b/Engine/Source/Developer/TraceServices/Private/Model/BookmarksPrivate.h new file mode 100644 index 000000000000..ef176f5375c4 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/BookmarksPrivate.h @@ -0,0 +1,52 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/AnalysisService.h" +#include "Templates/SharedPointer.h" + +namespace Trace +{ + +class FAnalysisSessionLock; +class FStringStore; + +struct FBookmarkSpec +{ + const TCHAR* File; + const TCHAR* FormatString; + int32 Line; +}; + +struct FBookmarkInternal +{ + double Time; + const TCHAR* Text; +}; + +class FBookmarkProvider + : public IBookmarkProvider +{ +public: + static const FName ProviderName; + + FBookmarkProvider(IAnalysisSession& Session); + + FBookmarkSpec& GetSpec(uint64 BookmarkPoint); + virtual uint64 GetBookmarkCount() const override { return Bookmarks.Num(); } + void AppendBookmark(double Time, uint64 BookmarkPoint, const uint8* FormatArgs); + virtual void EnumerateBookmarks(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const override; + +private: + enum + { + FormatBufferSize = 65536 + }; + + IAnalysisSession& Session; + TMap> SpecMap; + TArray> Bookmarks; + TCHAR FormatBuffer[FormatBufferSize]; +}; + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Model/Counters.cpp b/Engine/Source/Developer/TraceServices/Private/Model/Counters.cpp new file mode 100644 index 000000000000..d08cb5f9eccd --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/Counters.cpp @@ -0,0 +1,188 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Model/Counters.h" +#include "AnalysisServicePrivate.h" + +namespace Trace +{ + +FCounterInternal::FCounterInternal(ILinearAllocator& Allocator, uint32 InId, const TCHAR* InName, const TCHAR* InDescription, ECounterDisplayHint InDisplayHint) + : Name(InName) + , Description(InDescription) + , Id(InId) + , DisplayHint(InDisplayHint) + , Timestamps(Allocator, 1024) + , Ops(Allocator, 1024) + , IntOpArguments(Allocator, 1024) + , DoubleOpArguments(Allocator, 1024) +{ + +} + +template +static void EnumerateCounterValuesInternal(const TPagedArray& Timestamps, const TPagedArray& Ops, const TPagedArray& OpArgs, double IntervalStart, double IntervalEnd, TFunctionRef Callback) +{ + CounterType Value = 0; + auto TimeIterator = Timestamps.GetIteratorFromItem(0); + auto OpIterator = Ops.GetIteratorFromItem(0); + auto OpArgIterator = OpArgs.GetIteratorFromItem(0); + const double* Time = TimeIterator.GetCurrentItem(); + const ECounterOpType* Op = OpIterator.GetCurrentItem(); + const CounterType* OpArg = OpArgIterator.GetCurrentItem(); + + double PrevTime = 0.0; + CounterType PrevValue = CounterType(); + bool IsFirstValue = true; + bool IsFirstIncludedValue = true; + while (Time) + { + switch (*Op) + { + case CounterOpType_Add: + Value += *OpArg; + break; + case CounterOpType_Set: + Value = *OpArg; + break; + } + + if (*Time >= IntervalStart) + { + if (IsFirstIncludedValue && !IsFirstValue) + { + Callback(PrevTime, PrevValue); + } + PrevTime = *Time; + PrevValue = EnumerationType(Value); + Callback(PrevTime, PrevValue); + IsFirstIncludedValue = false; + } + if (*Time > IntervalEnd) + { + break; + } + + IsFirstValue = false; + + Time = TimeIterator.NextItem(); + Op = OpIterator.NextItem(); + OpArg = OpArgIterator.NextItem(); + } +} + +void FCounterInternal::EnumerateValues(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const +{ + if (DisplayHint == CounterDisplayHint_FloatingPoint) + { + EnumerateCounterValuesInternal(Timestamps, Ops, DoubleOpArguments, IntervalStart, IntervalEnd, Callback); + } + else + { + EnumerateCounterValuesInternal(Timestamps, Ops, IntOpArguments, IntervalStart, IntervalEnd, Callback); + } +} + +void FCounterInternal::EnumerateFloatValues(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const +{ + if (DisplayHint == CounterDisplayHint_FloatingPoint) + { + EnumerateCounterValuesInternal(Timestamps, Ops, DoubleOpArguments, IntervalStart, IntervalEnd, Callback); + } + else + { + EnumerateCounterValuesInternal(Timestamps, Ops, IntOpArguments, IntervalStart, IntervalEnd, Callback); + } +} + +FCounterProvider::FCounterProvider(IAnalysisSession& InSession) + : Session(InSession) +{ + +} + +FCounterProvider::~FCounterProvider() +{ + for (FCounterInternal* Counter : Counters) + { + delete Counter; + } +} + +void FCounterProvider::EnumerateCounters(TFunctionRef Callback) const +{ + uint32 Id = 0; + for (FCounterInternal* Counter : Counters) + { + Callback(*Counter); + } +} + +FCounterInternal* FCounterProvider::CreateCounter(const TCHAR* Name, const TCHAR* Description, ECounterDisplayHint DisplayHint) +{ + FCounterInternal* Counter = new FCounterInternal(Session.GetLinearAllocator(), Counters.Num(), Name, Description, DisplayHint); + Counters.Add(Counter); + return Counter; +} + +template +static void InsertOp(FCounterInternal& Counter, double Time, ECounterOpType Type, T Arg) +{ + TPagedArray& Timestamps = Counter.EditTimestamps(); + uint64 InsertionIndex; + if (Timestamps.Num() == 0 || Timestamps[Timestamps.Num() - 1] <= Time) + { + InsertionIndex = Timestamps.Num(); + } + else + { + auto TimestampIterator = Timestamps.GetIteratorFromItem(Timestamps.Num() - 1); + auto CurrentPage = TimestampIterator.GetCurrentPage(); + while (CurrentPage && *GetFirstItem(*CurrentPage) > Time) + { + CurrentPage = TimestampIterator.PrevPage(); + } + if (!CurrentPage) + { + InsertionIndex = 0; + } + else + { + uint64 PageInsertionIndex = Algo::LowerBound(*CurrentPage, Time); + InsertionIndex = TimestampIterator.GetCurrentPageIndex() * Timestamps.GetPageSize() + PageInsertionIndex; + } + } + Timestamps.Insert(InsertionIndex) = Time; + Counter.EditOps().Insert(InsertionIndex) = Type; + if (Counter.GetDisplayHint() == CounterDisplayHint_FloatingPoint) + { + Counter.EditDoubleOpArguments().Insert(InsertionIndex) = double(Arg); + } + else + { + Counter.EditIntOpArguments().Insert(InsertionIndex) = int64(Arg); + } +} + +void FCounterProvider::Add(FCounterInternal& Counter, double Time, int64 Value) +{ + InsertOp(Counter, Time, CounterOpType_Add, Value); +} + +void FCounterProvider::Add(FCounterInternal& Counter, double Time, double Value) +{ + InsertOp(Counter, Time, CounterOpType_Add, Value); +} + +void FCounterProvider::Set(FCounterInternal& Counter, double Time, int64 Value) +{ + InsertOp(Counter, Time, CounterOpType_Set, Value); +} + +void FCounterProvider::Set(FCounterInternal& Counter, double Time, double Value) +{ + InsertOp(Counter, Time, CounterOpType_Set, Value); +} + + + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Model/Counters.h b/Engine/Source/Developer/TraceServices/Private/Model/Counters.h new file mode 100644 index 000000000000..0fdd320d4eb6 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/Counters.h @@ -0,0 +1,72 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/Model/AnalysisSession.h" +#include "Templates/SharedPointer.h" +#include "Common/PagedArray.h" +#include "TraceServices/Model/Stats.h" + +namespace Trace +{ + +class FAnalysisSessionLock; + +enum ECounterOpType : uint8 +{ + CounterOpType_Add, + CounterOpType_Set, +}; + +class FCounterInternal + : public ICounter +{ +public: + FCounterInternal(ILinearAllocator& Allocator, uint32 Id, const TCHAR* Name, const TCHAR* Description, ECounterDisplayHint DisplayHint); + virtual const TCHAR* GetName() const override { return *Name; } + virtual uint32 GetId() const override { return Id; } + virtual ECounterDisplayHint GetDisplayHint() const { return DisplayHint; } + virtual void EnumerateValues(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const override; + virtual void EnumerateFloatValues(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const override; + + const TPagedArray& ReadIntOpArguments() const { return IntOpArguments; } + const TPagedArray& ReadDoubleOpArguments() const { return DoubleOpArguments; } + TPagedArray& EditTimestamps() { return Timestamps; } + TPagedArray& EditOps() { return Ops; } + TPagedArray& EditIntOpArguments() { return IntOpArguments; } + TPagedArray& EditDoubleOpArguments() { return DoubleOpArguments; } + +private: + friend class FCountersProvider; + + FString Name; + FString Description; + uint32 Id; + ECounterDisplayHint DisplayHint; + TPagedArray Timestamps; + TPagedArray Ops; + TPagedArray IntOpArguments; + TPagedArray DoubleOpArguments; +}; + +class FCounterProvider + : public ICounterProvider +{ +public: + FCounterProvider(IAnalysisSession& Session); + virtual ~FCounterProvider(); + virtual uint64 GetCounterCount() const override { return Counters.Num(); } + virtual void EnumerateCounters(TFunctionRef Callback) const override; + + FCounterInternal* CreateCounter(const TCHAR* Name, const TCHAR* Description, ECounterDisplayHint DisplayHint); + void Add(FCounterInternal& Counter, double Time, int64 Value); + void Add(FCounterInternal& Counter, double Time, double Value); + void Set(FCounterInternal& Counter, double Time, int64 Value); + void Set(FCounterInternal& Counter, double Time, double Value); + +private: + IAnalysisSession& Session; + TArray Counters; +}; + +} diff --git a/Engine/Source/Developer/TraceServices/Private/Model/FileActivity.cpp b/Engine/Source/Developer/TraceServices/Private/Model/FileActivity.cpp new file mode 100644 index 000000000000..79a4e6288eab --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/FileActivity.cpp @@ -0,0 +1,63 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Model/FileActivity.h" +#include "Model/IntervalTimeline.h" +#include "AnalysisServicePrivate.h" + +namespace Trace +{ + +FFileActivityProvider::FFileActivityProvider(IAnalysisSession& InSession) + : Session(InSession) + , Files(InSession.GetLinearAllocator(), 1024) +{ +} + +void FFileActivityProvider::EnumerateFileActivity(TFunctionRef&)> Callback) const +{ + for (uint64 FileIndex = 0, FileCount = Files.Num(); FileIndex < FileCount; ++FileIndex) + { + const FFileInfoInternal& FileInfoInternal = Files[FileIndex]; + FFileInfo FileInfo; + FileInfo.Id = FileIndex; + FileInfo.Path = *FileInfoInternal.Path; + if (!Callback(FileInfo, *FileInfoInternal.ActivityTimeline)) + { + return; + } + } +} + +uint32 FFileActivityProvider::GetFileIndex(const TCHAR* Path) +{ + uint32 FileIndex = Files.Num(); + FFileInfoInternal& FileInfo = Files.PushBack(); + FileInfo.Path = Path; + FileInfo.ActivityTimeline = MakeShared(Session.GetLinearAllocator()); + return FileIndex; +} + +uint64 FFileActivityProvider::BeginActivity(uint32 FileIndex, EFileActivityType Type, uint64 Offset, uint64 Size, double Time) +{ + FFileInfoInternal& FileInfo = Files[FileIndex]; + FFileActivity FileActivity; + FileActivity.ActivityType = Type; + FileActivity.Offset = Offset; + FileActivity.Size = Size; + FileActivity.Failed = false; + return FileInfo.ActivityTimeline->AppendBeginEvent(Time, FileActivity); +} + +void FFileActivityProvider::EndActivity(uint32 FileIndex, uint64 ActivityIndex, double Time, bool Failed) +{ + FFileInfoInternal& FileInfo = Files[FileIndex]; + FFileActivity& Activity = FileInfo.ActivityTimeline->EndEvent(ActivityIndex, Time); + Activity.Failed = Failed; +} + +const TCHAR* FFileActivityProvider::GetFilePath(uint32 FileIndex) const +{ + return *Files[FileIndex].Path; +} + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Model/FileActivity.h b/Engine/Source/Developer/TraceServices/Private/Model/FileActivity.h new file mode 100644 index 000000000000..5d1925532783 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/FileActivity.h @@ -0,0 +1,48 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/Model/AnalysisSession.h" +#include "Common/PagedArray.h" +#include "Model/IntervalTimeline.h" +#include "TraceServices/Model/LoadTimeProfiler.h" +#include "Containers/UnrealString.h" +#include "Templates/SharedPointer.h" + +namespace Trace +{ + +class FAnalysisSessionLock; + +class FFileActivityProvider + : public IFileActivityProvider +{ +public: + struct FTimelineSettings + { + enum + { + EventsPerPage = 128 + }; + }; + typedef TIntervalTimeline TimelineInternal; + + FFileActivityProvider(IAnalysisSession& Session); + virtual void EnumerateFileActivity(TFunctionRef Callback) const override; + uint32 GetFileIndex(const TCHAR* Path); + uint64 BeginActivity(uint32 FileIndex, EFileActivityType Type, uint64 Offset, uint64 Size, double Time); + void EndActivity(uint32 FileIndex, uint64 ActivityIndex, double Time, bool Failed); + const TCHAR* GetFilePath(uint32 FileIndex) const; + +private: + struct FFileInfoInternal + { + FString Path; + TSharedPtr ActivityTimeline; + }; + + IAnalysisSession& Session; + TPagedArray Files; +}; + +} diff --git a/Engine/Source/Developer/TraceServices/Private/Model/Frames.cpp b/Engine/Source/Developer/TraceServices/Private/Model/Frames.cpp new file mode 100644 index 000000000000..a859fd380477 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/Frames.cpp @@ -0,0 +1,71 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TraceServices/Model/Frames.h" +#include "Model/FramesPrivate.h" +#include "AnalysisServicePrivate.h" +#include + +namespace Trace +{ + +const FName FFrameProvider::ProviderName("FrameProvider"); + +FFrameProvider::FFrameProvider(IAnalysisSession& InSession) + : Session(InSession) +{ + for (int32 FrameType = 0; FrameType < TraceFrameType_Count; ++FrameType) + { + Frames.Emplace(Session.GetLinearAllocator(), 65536); + } +} + +uint64 FFrameProvider::GetFrameCount(ETraceFrameType FrameType) const +{ + Session.ReadAccessCheck(); + return Frames[FrameType].Num(); +} + +void FFrameProvider::EnumerateFrames(ETraceFrameType FrameType, uint64 Start, uint64 End, TFunctionRef Callback) const +{ + Session.ReadAccessCheck(); + + End = FMath::Min(End, Frames[FrameType].Num()); + if (Start >= End) + { + return; + } + auto Iterator = Frames[FrameType].GetIteratorFromItem(Start); + const FFrame* Frame = Iterator.GetCurrentItem(); + for (uint64 Index = Start; Index < End; ++Index) + { + Callback(*Frame); + Frame = Iterator.NextItem(); + } +} + +void FFrameProvider::BeginFrame(ETraceFrameType FrameType, double Time) +{ + Session.WriteAccessCheck(); + + uint64 Index = Frames[FrameType].Num(); + FFrame& Frame = Frames[FrameType].PushBack(); + Frame.StartTime = Time; + Frame.EndTime = std::numeric_limits::infinity(); + Frame.Index = Index; + Session.UpdateDurationSeconds(Time); +} + +void FFrameProvider::EndFrame(ETraceFrameType FrameType, double Time) +{ + Session.WriteAccessCheck(); + FFrame& Frame = Frames[FrameType][Frames[FrameType].Num() - 1]; + Frame.EndTime = Time; + Session.UpdateDurationSeconds(Time); +} + +const IFrameProvider& ReadFrameProvider(const IAnalysisSession& Session) +{ + return *Session.ReadProvider(FFrameProvider::ProviderName); +} + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Model/FramesPrivate.h b/Engine/Source/Developer/TraceServices/Private/Model/FramesPrivate.h new file mode 100644 index 000000000000..4d60b004a187 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/FramesPrivate.h @@ -0,0 +1,30 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/AnalysisService.h" +#include "ProfilingDebugging/MiscTrace.h" +#include "Common/PagedArray.h" + +namespace Trace +{ + +class FFrameProvider + : public IFrameProvider +{ +public: + static const FName ProviderName; + + FFrameProvider(IAnalysisSession& Session); + + virtual uint64 GetFrameCount(ETraceFrameType FrameType) const override; + virtual void EnumerateFrames(ETraceFrameType FrameType, uint64 Start, uint64 End, TFunctionRef Callback) const override; + void BeginFrame(ETraceFrameType FrameType, double Time); + void EndFrame(ETraceFrameType FrameType, double Time); + +private: + IAnalysisSession& Session; + TArray> Frames; +}; + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Model/IntervalTimeline.h b/Engine/Source/Developer/TraceServices/Private/Model/IntervalTimeline.h new file mode 100644 index 000000000000..e2a4a56539b5 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/IntervalTimeline.h @@ -0,0 +1,136 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/Containers/Timelines.h" +#include "Common/PagedArray.h" +#include "TraceServices/Containers/Allocators.h" +#include + +namespace Trace +{ + +struct FIntervalTimelineDefaultSettings +{ + enum + { + EventsPerPage = 65536 + }; +}; + +template +class TIntervalTimeline + : public ITimeline +{ +public: + TIntervalTimeline(ILinearAllocator& Allocator) + : Events(Allocator, SettingsType::EventsPerPage) + { + } + + virtual ~TIntervalTimeline() = default; + virtual uint64 GetModCount() const override { return ModCount; } + virtual uint64 GetEventCount() const override { return Events.Num(); } + + virtual void EnumerateEventsDownSampled(double IntervalStart, double IntervalEnd, double Resolution, TFunctionRef Callback) const override { check(false); } + virtual void EnumerateEventsDownSampled(double IntervalStart, double IntervalEnd, double Resolution, TFunctionRef Callback) const override { check(false); } + virtual void EnumerateEvents(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const override { check(false); } + + virtual void EnumerateEvents(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const override + { + auto EventIterator = Events.GetIteratorFromPage(0); + const FEventInternal* Event = EventIterator.GetCurrentItem(); + while (Event) + { + const FEventPage* Page = EventIterator.GetCurrentPage(); + if (Page->EndTime < IntervalStart || IntervalEnd < Page->BeginTime) + { + if (!EventIterator.NextPage()) + { + return; + } + Event = EventIterator.GetCurrentItem(); + } + else + { + if (!(Event->EndTime < IntervalStart || IntervalEnd < Event->StartTime)) + { + Callback(Event->StartTime, Event->EndTime, 0, Event->Event); + } + Event = EventIterator.NextItem(); + } + } + } + + uint64 AppendBeginEvent(double StartTime, const EventType& Event) + { + //check(StartTime >= LastTime); + //LastTime = StartTime; + + uint64 Index = Events.Num(); + FEventInternal& EventInternal = Events.PushBack(); + EventInternal.StartTime = StartTime; + EventInternal.EndTime = std::numeric_limits::infinity(); + EventInternal.Event = Event; + + FEventPage* LastPage = Events.GetLastPage(); + if (LastPage != CurrentPage) + { + CurrentPage = LastPage; + LastPage->BeginTime = StartTime; + LastPage->EndTime = StartTime; + LastPage->BeginEventIndex = Index; + } + else + { + LastPage->BeginTime = FMath::Min(LastPage->BeginTime, StartTime); + LastPage->EndTime = FMath::Max(LastPage->EndTime, StartTime); + } + + LastPage->EndEventIndex = Index + 1; + + ++ModCount; + + return Index; + } + + EventType& EndEvent(uint64 EventIndex, double EndTime) + { + //check(EndTime >= LastTime); + //LastTime = EndTime; + FEventInternal& EventInternal = Events[EventIndex]; + check(EventIndex < Events.Num()); + EventInternal.EndTime = EndTime; + FEventPage* Page = Events.GetItemPage(EventIndex); + Page->EndTime = FMath::Max(Page->EndTime, EndTime); + + ++ModCount; + + return EventInternal.Event; + } + +private: + struct FEventInternal + { + double StartTime; + double EndTime; + EventType Event; + }; + + struct FEventPage + { + double BeginTime = 0.0; + double EndTime = 0.0; + uint64 BeginEventIndex = 0; + uint64 EndEventIndex = 0; + FEventInternal* Items = nullptr; + uint64 Count = 0; + }; + + TPagedArray Events; + FEventPage* CurrentPage = nullptr; + double LastTime = 0.0; + uint64 ModCount = 0; +}; + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Model/LoadTimeProfiler.cpp b/Engine/Source/Developer/TraceServices/Private/Model/LoadTimeProfiler.cpp new file mode 100644 index 000000000000..6715e41345cc --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/LoadTimeProfiler.cpp @@ -0,0 +1,175 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TraceServices/Model/LoadTimeProfiler.h" +#include "Model/LoadTimeProfilerPrivate.h" +#include "AnalysisServicePrivate.h" +#include "Common/TimelineStatistics.h" + +namespace Trace +{ + +FLoadTimeProfilerProvider::FLoadTimeProfilerProvider(IAnalysisSession& InSession) + : Session(InSession) + , ClassInfos(Session.GetLinearAllocator(), 4096) + , Packages(Session.GetLinearAllocator(), 4096) + , Exports(Session.GetLinearAllocator(), 4090) + , MainThreadCpuTimeline(MakeShared(Session.GetLinearAllocator())) + , AsyncLoadingThreadCpuTimeline(MakeShared(Session.GetLinearAllocator())) +{ + +} + +void FLoadTimeProfilerProvider::EnumeratePackages(TFunctionRef Callback) const +{ + Session.ReadAccessCheck(); + + auto Iterator = Packages.GetIteratorFromItem(0); + const FPackageInfo* Package = Iterator.GetCurrentItem(); + while (Package) + { + Callback(*Package); + Package = Iterator.NextItem(); + } +} + +void FLoadTimeProfilerProvider::ReadMainThreadCpuTimeline(TFunctionRef Callback) const +{ + Session.ReadAccessCheck(); + Callback(*MainThreadCpuTimeline); +} + +void FLoadTimeProfilerProvider::ReadAsyncLoadingThreadCpuTimeline(TFunctionRef Callback) const +{ + Session.ReadAccessCheck(); + Callback(*AsyncLoadingThreadCpuTimeline); +} + +ITable* FLoadTimeProfilerProvider::CreateEventAggregation(double IntervalStart, double IntervalEnd) const +{ + TArray Timelines; + Timelines.Add(&MainThreadCpuTimeline.Get()); + Timelines.Add(&AsyncLoadingThreadCpuTimeline.Get()); + + auto BucketMapper = [](const FLoadTimeProfilerCpuEvent& Event) + { + return Event.PackageEventType; + }; + TMap Aggregation; + FTimelineStatistics::CreateAggregation(Timelines, BucketMapper, IntervalStart, IntervalEnd, Aggregation); + TTable* Table = new TTable(); + for (const auto& KV : Aggregation) + { + FLoadTimeProfilerAggregatedStats& Row = Table->AddRow(); + switch (KV.Key) + { + case LoadTimeProfilerPackageEventType_CreateLinker: + Row.Name = TEXT("CreateLinker"); + break; + case LoadTimeProfilerPackageEventType_FinishLinker: + Row.Name = TEXT("FinishLinker"); + break; + case LoadTimeProfilerPackageEventType_StartImportPackages: + Row.Name = TEXT("StartImportPackages"); + break; + case LoadTimeProfilerPackageEventType_SetupImports: + Row.Name = TEXT("SetupImports"); + break; + case LoadTimeProfilerPackageEventType_SetupExports: + Row.Name = TEXT("SetupExports"); + break; + case LoadTimeProfilerPackageEventType_ProcessImportsAndExports: + Row.Name = TEXT("ProcessImportsAndExports"); + break; + case LoadTimeProfilerPackageEventType_ExportsDone: + Row.Name = TEXT("ExportsDone"); + break; + case LoadTimeProfilerPackageEventType_PostLoadWait: + Row.Name = TEXT("PostLoadWait"); + break; + case LoadTimeProfilerPackageEventType_StartPostLoad: + Row.Name = TEXT("StartPostLoad"); + break; + case LoadTimeProfilerPackageEventType_Tick: + Row.Name = TEXT("Tick"); + break; + case LoadTimeProfilerPackageEventType_DeferredPostLoad: + Row.Name = TEXT("DeferredPostLoad"); + break; + case LoadTimeProfilerPackageEventType_Finish: + Row.Name = TEXT("Finish"); + break; + } + const FAggregatedTimingStats& Stats = KV.Value; + Row.Count = Stats.InstanceCount; + Row.Total = Stats.TotalExclusiveTime; + Row.Min = Stats.MinExclusiveTime; + Row.Max = Stats.MaxExclusiveTime; + Row.Average = Stats.AverageExclusiveTime; + Row.Median = Stats.MedianExclusiveTime; + } + return Table; +} + +ITable* FLoadTimeProfilerProvider::CreateObjectTypeAggregation(double IntervalStart, double IntervalEnd) const +{ + TArray Timelines; + Timelines.Add(&MainThreadCpuTimeline.Get()); + Timelines.Add(&AsyncLoadingThreadCpuTimeline.Get()); + + auto BucketMapper = [](const FLoadTimeProfilerCpuEvent& Event) -> const FClassInfo* + { + return Event.Export ? Event.Export->Class : nullptr; + }; + TMap Aggregation; + FTimelineStatistics::CreateAggregation(Timelines, BucketMapper, IntervalStart, IntervalEnd, Aggregation); + TTable* Table = new TTable(); + for (const auto& KV : Aggregation) + { + const FClassInfo* ClassInfo = KV.Key; + if (ClassInfo) + { + FLoadTimeProfilerAggregatedStats& Row = Table->AddRow(); + const FAggregatedTimingStats& Stats = KV.Value; + Row.Name = ClassInfo->Name; + Row.Count = Stats.InstanceCount; + Row.Total = Stats.TotalExclusiveTime; + Row.Min = Stats.MinExclusiveTime; + Row.Max = Stats.MaxExclusiveTime; + Row.Average = Stats.AverageExclusiveTime; + Row.Median = Stats.MedianExclusiveTime; + } + } + return Table; +} + +const Trace::FClassInfo& FLoadTimeProfilerProvider::AddClassInfo(const TCHAR* ClassName) +{ + Session.WriteAccessCheck(); + + FClassInfo& ClassInfo = ClassInfos.PushBack(); + ClassInfo.Name = Session.StoreString(ClassName); + return ClassInfo; +} + +Trace::FPackageInfo& FLoadTimeProfilerProvider::CreatePackage(const TCHAR* PackageName) +{ + Session.WriteAccessCheck(); + + uint32 PackageId = Packages.Num(); + FPackageInfo& Package = Packages.PushBack(); + Package.Id = PackageId; + Package.Name = Session.StoreString(PackageName); + return Package; +} + +Trace::FPackageExportInfo& FLoadTimeProfilerProvider::CreateExport() +{ + Session.WriteAccessCheck(); + + uint32 ExportId = Exports.Num(); + FPackageExportInfo& Export = Exports.PushBack(); + Export.Id = ExportId; + return Export; +} + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Model/LoadTimeProfilerPrivate.h b/Engine/Source/Developer/TraceServices/Private/Model/LoadTimeProfilerPrivate.h new file mode 100644 index 000000000000..75fda335b8f1 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/LoadTimeProfilerPrivate.h @@ -0,0 +1,62 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/Model/LoadTimeProfiler.h" +#include "TraceServices/AnalysisService.h" +#include "Common/SlabAllocator.h" +#include "Common/PagedArray.h" +#include "Common/StringStore.h" +#include "Model/MonotonicTimeline.h" +#include "Model/Tables.h" + +namespace Trace +{ + +class FAnalysisSessionLock; + +class FLoadTimeProfilerProvider + : public ILoadTimeProfilerProvider +{ +public: + typedef TMonotonicTimeline CpuTimelineInternal; + + FLoadTimeProfilerProvider(IAnalysisSession& Session); + virtual uint64 GetPackageCount() const override { return Packages.Num(); } + virtual void EnumeratePackages(TFunctionRef Callback) const override; + virtual void ReadMainThreadCpuTimeline(TFunctionRef Callback) const override; + virtual void ReadAsyncLoadingThreadCpuTimeline(TFunctionRef Callback) const override; + virtual ITable* CreateEventAggregation(double IntervalStart, double IntervalEnd) const override; + virtual ITable* CreateObjectTypeAggregation(double IntervalStart, double IntervalEnd) const override; + const FClassInfo& AddClassInfo(const TCHAR* ClassName); + FPackageInfo& CreatePackage(const TCHAR* PackageName); + FPackageExportInfo& CreateExport(); + TSharedRef EditMainThreadCpuTimeline() { return MainThreadCpuTimeline; } + TSharedRef EditAsyncLoadingThreadCpuTimeline() { return AsyncLoadingThreadCpuTimeline; } + virtual uint32 GetMainThreadId() const override { return MainThreadId; } + void SetMainThreadId(uint32 ThreadId) { MainThreadId = ThreadId; } + virtual uint32 GetAsyncLoadingThreadId() const override { return AsyncLoadingThreadId; } + void SetAsyncLoadingThreadId(uint32 ThreadId) { AsyncLoadingThreadId = ThreadId; } + +private: + IAnalysisSession& Session; + TPagedArray ClassInfos; + TPagedArray Packages; + TPagedArray Exports; + TSharedRef MainThreadCpuTimeline; + TSharedRef AsyncLoadingThreadCpuTimeline; + uint32 MainThreadId = uint32(-1); + uint32 AsyncLoadingThreadId = uint32(-1); + + UE_TRACE_TABLE_LAYOUT_BEGIN(FAggregatedStatsTableLayout, FLoadTimeProfilerAggregatedStats) + UE_TRACE_TABLE_COLUMN(Name, TEXT("Name")) + UE_TRACE_TABLE_COLUMN(Count, TEXT("Count")) + UE_TRACE_TABLE_COLUMN(Total, TEXT("Total")) + UE_TRACE_TABLE_COLUMN(Min, TEXT("Min")) + UE_TRACE_TABLE_COLUMN(Max, TEXT("Max")) + UE_TRACE_TABLE_COLUMN(Average, TEXT("Avg")) + UE_TRACE_TABLE_COLUMN(Median, TEXT("Med")) + UE_TRACE_TABLE_LAYOUT_END() +}; + +} diff --git a/Engine/Source/Developer/TraceServices/Private/Model/Log.cpp b/Engine/Source/Developer/TraceServices/Private/Model/Log.cpp new file mode 100644 index 000000000000..3f7e4376e98e --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/Log.cpp @@ -0,0 +1,153 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TraceServices/Model/Log.h" +#include "Model/LogPrivate.h" +#include "AnalysisServicePrivate.h" +#include "Common/FormatArgs.h" + +namespace Trace +{ + +const FName FLogProvider::ProviderName("LogProvider"); + +FLogProvider::FLogProvider(IAnalysisSession& InSession) + : Session(InSession) + , Categories(InSession.GetLinearAllocator(), 128) + , MessageSpecs(InSession.GetLinearAllocator(), 1024) + , Messages(InSession.GetLinearAllocator(), 1024) + , MessagesTable(Messages) +{ + +} + +FLogCategory& FLogProvider::GetCategory(uint64 CategoryPointer) +{ + Session.WriteAccessCheck(); + if (CategoryMap.Contains(CategoryPointer)) + { + return *CategoryMap[CategoryPointer]; + } + else + { + FLogCategory& Category = Categories.PushBack(); + CategoryMap.Add(CategoryPointer, &Category); + return Category; + } +} + +FLogMessageSpec& FLogProvider::GetMessageSpec(uint64 LogPoint) +{ + Session.WriteAccessCheck(); + if (SpecMap.Contains(LogPoint)) + { + return *SpecMap[LogPoint]; + } + else + { + FLogMessageSpec& Spec = MessageSpecs.PushBack(); + SpecMap.Add(LogPoint, &Spec); + return Spec; + } +} + +void FLogProvider::AppendMessage(uint64 LogPoint, double Time, const uint8* FormatArgs) +{ + Session.WriteAccessCheck(); + check(SpecMap.Contains(LogPoint)); + FLogMessageInternal& InternalMessage = Messages.PushBack(); + InternalMessage.Time = Time; + InternalMessage.Spec = SpecMap[LogPoint]; + FFormatArgsHelper::Format(FormatBuffer, FormatBufferSize - 1, InternalMessage.Spec->FormatString, FormatArgs); + InternalMessage.Message = Session.StoreString(FormatBuffer); + Session.UpdateDurationSeconds(Time); +} + +uint64 FLogProvider::GetMessageCount() const +{ + Session.ReadAccessCheck(); + return Messages.Num(); +} + +bool FLogProvider::ReadMessage(uint64 Index, TFunctionRef Callback) const +{ + Session.ReadAccessCheck(); + if (Index >= Messages.Num()) + { + return false; + } + ConstructMessage(Index, Callback); + return true; +} + +void FLogProvider::EnumerateMessages(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const +{ + Session.ReadAccessCheck(); + if (IntervalStart > IntervalEnd) + { + return; + } + uint64 MessageCount = Messages.Num(); + for (uint64 Index = 0; Index < MessageCount; ++Index) + { + double Time = Messages[Index].Time; + if (IntervalStart <= Time && Time <= IntervalEnd) + { + ConstructMessage(Index, Callback); + } + } +} + +void FLogProvider::EnumerateMessagesByIndex(uint64 Start, uint64 End, TFunctionRef Callback) const +{ + Session.ReadAccessCheck(); + uint64 Count = Messages.Num(); + if (Start >= Count) + { + return; + } + if (End > Count) + { + End = Count; + } + if (Start >= End) + { + return; + } + for (uint64 Index = Start; Index < End; ++Index) + { + ConstructMessage(Index, Callback); + } +} + +void FLogProvider::ConstructMessage(uint64 Index, TFunctionRef Callback) const +{ + const FLogMessageInternal& InternalMessage = Messages[Index]; + FLogMessage Message; + Message.Index = Index; + Message.Time = InternalMessage.Time; + Message.Category = InternalMessage.Spec->Category; + Message.File = InternalMessage.Spec->File; + Message.Line = InternalMessage.Spec->Line; + Message.Verbosity = InternalMessage.Spec->Verbosity; + Message.Message = InternalMessage.Message; + Callback(Message); +} + +void FLogProvider::EnumerateCategories(TFunctionRef Callback) const +{ + Session.ReadAccessCheck(); + auto Iterator = Categories.GetIteratorFromItem(0); + const FLogCategory* Category = Iterator.GetCurrentItem(); + while (Category) + { + Callback(*Category); + Category = Iterator.NextItem(); + } +} + +const ILogProvider& ReadLogProvider(const IAnalysisSession& Session) +{ + return *Session.ReadProvider(FLogProvider::ProviderName); +} + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Model/LogPrivate.h b/Engine/Source/Developer/TraceServices/Private/Model/LogPrivate.h new file mode 100644 index 000000000000..855b7b64dfa5 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/LogPrivate.h @@ -0,0 +1,85 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/AnalysisService.h" +#include "Templates/SharedPointer.h" +#include "Common/PagedArray.h" +#include "Model/Tables.h" +#include "Misc/OutputDeviceHelper.h" + +namespace Trace +{ + +class FAnalysisSessionLock; +class FStringStore; + +struct FLogMessageSpec +{ + FLogCategory* Category = nullptr; + const TCHAR* File = nullptr; + const TCHAR* FormatString = nullptr; + int32 Line; + ELogVerbosity::Type Verbosity; +}; + +struct FLogMessageInternal +{ + FLogMessageSpec* Spec = nullptr; + double Time; + const TCHAR* Message = nullptr; +}; + +class FLogProvider : + public ILogProvider +{ +public: + enum + { + ReservedLogCategory_Bookmark = 0 + }; + + static const FName ProviderName; + + FLogProvider(IAnalysisSession& Session); + + FLogCategory& GetCategory(uint64 CategoryPointer); + FLogMessageSpec& GetMessageSpec(uint64 LogPoint); + void AppendMessage(uint64 LogPoint, double Time, const uint8* FormatArgs); + + virtual uint64 GetMessageCount() const override; + virtual bool ReadMessage(uint64 Index, TFunctionRef Callback) const override; + virtual void EnumerateMessages(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const override; + virtual void EnumerateMessagesByIndex(uint64 Start, uint64 End, TFunctionRef Callback) const override; + virtual uint64 GetCategoryCount() const override { return Categories.Num(); } + virtual void EnumerateCategories(TFunctionRef Callback) const override; + virtual const IUntypedTable& GetMessagesTable() const override { return MessagesTable; } + +private: + void ConstructMessage(uint64 Id, TFunctionRef Callback) const; + + UE_TRACE_TABLE_LAYOUT_BEGIN(FMessagesTableLayout, FLogMessageInternal) + UE_TRACE_TABLE_COLUMN(Time, TEXT("Time")) + UE_TRACE_TABLE_PROJECTED_COLUMN(TableColumnType_CString, TEXT("Verbosity"), [](const FLogMessageInternal& Message) { return FOutputDeviceHelper::VerbosityToString(Message.Spec->Verbosity); }) + UE_TRACE_TABLE_PROJECTED_COLUMN(TableColumnType_CString, TEXT("Category"), [](const FLogMessageInternal& Message) { return Message.Spec->Category->Name; }) + UE_TRACE_TABLE_PROJECTED_COLUMN(TableColumnType_CString, TEXT("File"), [](const FLogMessageInternal& Message) { return Message.Spec->File; }) + UE_TRACE_TABLE_PROJECTED_COLUMN(TableColumnType_Int, TEXT("Line"), [](const FLogMessageInternal& Message) { return Message.Spec->Line; }) + UE_TRACE_TABLE_COLUMN(Message, TEXT("Message")) + UE_TRACE_TABLE_LAYOUT_END() + + enum + { + FormatBufferSize = 65536 + }; + + IAnalysisSession& Session; + TMap CategoryMap; + TMap SpecMap; + TPagedArray Categories; + TPagedArray MessageSpecs; + TPagedArray Messages; + TCHAR FormatBuffer[FormatBufferSize]; + TTableView MessagesTable; +}; + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Model/MonotonicTimeline.h b/Engine/Source/Developer/TraceServices/Private/Model/MonotonicTimeline.h new file mode 100644 index 000000000000..3dbc1292b251 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/MonotonicTimeline.h @@ -0,0 +1,472 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/AnalysisService.h" +#include "Common/PagedArray.h" + +namespace Trace +{ + +struct FMonotonicTimelineDefaultSettings +{ + enum + { + MaxDepth = 1024, + ScopeEntriesPageSize = 65536, + EventsPageSize = 65536, + DetailLevelsCount = 6, + }; + + constexpr static double DetailLevelResolution(int32 Index) + { + const double DetailLevels[DetailLevelsCount] = { 0.0, 0.0001, 0.001, 0.008, 0.04, 0.2 }; + return DetailLevels[Index]; + } +}; + +template +class TMonotonicTimeline + : public ITimeline +{ +public: + using EventType = InEventType; + + TMonotonicTimeline(ILinearAllocator& InAllocator) + : Allocator(InAllocator) + { + + for (int32 DetailLevelIndex = 0; DetailLevelIndex < SettingsType::DetailLevelsCount; ++DetailLevelIndex) + { + double Resolution = SettingsType::DetailLevelResolution(DetailLevelIndex); + DetailLevels.Emplace(Allocator, Resolution); + } + } + + virtual ~TMonotonicTimeline() = default; + + void AppendBeginEvent(double StartTime, const EventType& Event) + { + int32 CurrentDepth = DetailLevels[0].InsertionState.CurrentDepth; + + AddScopeEntry(DetailLevels[0], StartTime, true); + AddEvent(DetailLevels[0], Event); + FDetailLevelDepthState& Lod0DepthState = DetailLevels[0].InsertionState.DepthStates[CurrentDepth]; + Lod0DepthState.EnterTime = StartTime; + Lod0DepthState.DominatingEvent = Event; + //Lod0DepthState.DebugDominatingEventType = Owner.EventTypes[TypeId]; + + for (int32 DetailLevelIndex = 1; DetailLevelIndex < SettingsType::DetailLevelsCount; ++DetailLevelIndex) + { + FDetailLevel& DetailLevel = DetailLevels[DetailLevelIndex]; + FDetailLevelDepthState& CurrentDepthState = DetailLevel.InsertionState.DepthStates[CurrentDepth]; + + if (CurrentDepthState.PendingScopeEnterIndex < 0 || StartTime >= CurrentDepthState.EnterTime + DetailLevel.Resolution) + { + for (int32 Depth = DetailLevel.InsertionState.PendingDepth; Depth >= CurrentDepth; --Depth) + { + FDetailLevelDepthState& DepthState = DetailLevel.InsertionState.DepthStates[Depth]; + check(DepthState.PendingScopeEnterIndex >= 0); + AddScopeEntry(DetailLevel, DepthState.ExitTime, false); + + DepthState.PendingScopeEnterIndex = -1; + DepthState.PendingEventIndex = -1; + } + DetailLevel.InsertionState.PendingDepth = CurrentDepth; + + uint64 EnterScopeIndex = DetailLevel.ScopeEntries.Num(); + uint64 EventIndex = DetailLevel.Events.Num(); + + AddScopeEntry(DetailLevel, StartTime, true); + AddEvent(DetailLevel, Event); + + CurrentDepthState.DominatingEventStartTime = StartTime; + CurrentDepthState.DominatingEventEndTime = StartTime; + CurrentDepthState.DominatingEventDuration = 0.0; + CurrentDepthState.PendingScopeEnterIndex = EnterScopeIndex; + CurrentDepthState.PendingEventIndex = EventIndex; + CurrentDepthState.EnterTime = StartTime; + CurrentDepthState.DominatingEvent = Event; + //CurrentDepthState.DebugDominatingEventType = Owner.EventTypes[TypeId]; + } + else if (CurrentDepth > DetailLevel.InsertionState.PendingDepth) + { + DetailLevel.InsertionState.PendingDepth = CurrentDepth; + } + SetEvent(DetailLevel, CurrentDepthState.PendingEventIndex, Event); + } + ++ModCount; + } + + void AppendEndEvent(double EndTime) + { + AddScopeEntry(DetailLevels[0], EndTime, false); + + int32 CurrentDepth = DetailLevels[0].InsertionState.CurrentDepth; + for (int32 DetailLevelIndex = 1; DetailLevelIndex < SettingsType::DetailLevelsCount; ++DetailLevelIndex) + { + FDetailLevel& DetailLevel = DetailLevels[DetailLevelIndex]; + + DetailLevel.InsertionState.DepthStates[CurrentDepth].ExitTime = EndTime; + + UpdateDominatingEvent(DetailLevel, CurrentDepth, EndTime); + } + ++ModCount; + } + + virtual uint64 GetModCount() const override + { + return ModCount; + } + + virtual uint64 GetEventCount() const override + { + return DetailLevels[0].Events.Num(); + } + + virtual void EnumerateEventsDownSampled(double IntervalStart, double IntervalEnd, double Resolution, TFunctionRef Callback) const override + { + int32 DetailLevelIndex = SettingsType::DetailLevelsCount - 1; + for (; DetailLevelIndex > 0; --DetailLevelIndex) + { + if (DetailLevels[DetailLevelIndex].Resolution <= Resolution) + { + break; + } + } + + const FDetailLevel& DetailLevel = DetailLevels[DetailLevelIndex]; + if (DetailLevel.ScopeEntries.Num() == 0) + { + return; + } + + uint64 FirstScopePageIndex = Algo::UpperBoundBy(DetailLevel.ScopeEntries, IntervalStart, [](const FEventScopeEntryPage& Page) + { + return Page.BeginTime; + }); + if (FirstScopePageIndex > 0) + { + --FirstScopePageIndex; + } + auto ScopeEntryIterator = DetailLevel.ScopeEntries.GetIteratorFromPage(FirstScopePageIndex); + const FEventScopeEntryPage* ScopePage = ScopeEntryIterator.GetCurrentPage(); + if (ScopePage->BeginTime > IntervalEnd) + { + return; + } + if (ScopePage->EndTime < IntervalStart) + { + return; + } + auto EventsIterator = DetailLevel.Events.GetIteratorFromItem(ScopePage->BeginEventIndex); + struct FEnumerationStackEntry + { + double StartTime; + EventType Event; + }; + FEnumerationStackEntry EventStack[SettingsType::MaxDepth]; + int32 CurrentStackDepth = ScopePage->InitialStackCount; + for (int32 InitialStackIndex = 0; InitialStackIndex < CurrentStackDepth; ++InitialStackIndex) + { + FEnumerationStackEntry& EnumerationStackEntry = EventStack[InitialStackIndex]; + const FEventStackEntry& EventStackEntry = ScopePage->InitialStack[InitialStackIndex]; + EnumerationStackEntry.StartTime = GetScopeEntryTime(DetailLevel, EventStackEntry.EnterScopeIndex); + EnumerationStackEntry.Event = GetEvent(DetailLevel, EventStackEntry.EventIndex); + } + + const FEventScopeEntry* ScopeEntry = ScopeEntryIterator.GetCurrentItem(); + const EventType* Event = EventsIterator.GetCurrentItem(); + while (ScopeEntry && FMath::Abs(ScopeEntry->Time) < IntervalStart) + { + if (ScopeEntry->Time < 0.0) + { + check(CurrentStackDepth < SettingsType::MaxDepth); + FEnumerationStackEntry& StackEntry = EventStack[CurrentStackDepth++]; + StackEntry.Event = *Event; + StackEntry.StartTime = -ScopeEntry->Time; + Event = EventsIterator.NextItem(); + } + else + { + check(CurrentStackDepth > 0); + --CurrentStackDepth; + } + ScopeEntry = ScopeEntryIterator.NextItem(); + } + if (CurrentStackDepth == 1 && EventStack[0].StartTime > IntervalEnd) + { + return; + } + for (int32 StackIndex = 0; StackIndex < CurrentStackDepth; ++StackIndex) + { + FEnumerationStackEntry& StackEntry = EventStack[StackIndex]; + Callback(true, StackEntry.StartTime, StackEntry.Event); + } + while (ScopeEntry && FMath::Abs(ScopeEntry->Time) <= IntervalEnd) + { + if (ScopeEntry->Time < 0.0) + { + check(CurrentStackDepth < SettingsType::MaxDepth); + FEnumerationStackEntry& StackEntry = EventStack[CurrentStackDepth++]; + StackEntry.Event = *Event; + Callback(true, -ScopeEntry->Time, StackEntry.Event); + Event = EventsIterator.NextItem(); + } + else + { + check(CurrentStackDepth > 0); + FEnumerationStackEntry& StackEntry = EventStack[--CurrentStackDepth]; + Callback(false, ScopeEntry->Time, StackEntry.Event); + } + ScopeEntry = ScopeEntryIterator.NextItem(); + } + uint32 ExitDepth = 0; + while (CurrentStackDepth > 0 && ScopeEntry) + { + if (ScopeEntry->Time < 0.0) + { + ++ExitDepth; + } + else + { + if (ExitDepth == 0) + { + FEnumerationStackEntry& StackEntry = EventStack[--CurrentStackDepth]; + Callback(false, ScopeEntry->Time, StackEntry.Event); + } + else + { + --ExitDepth; + } + } + ScopeEntry = ScopeEntryIterator.NextItem(); + } + while (CurrentStackDepth > 0) + { + FEnumerationStackEntry& StackEntry = EventStack[--CurrentStackDepth]; + Callback(false, DetailLevel.InsertionState.LastTime, StackEntry.Event); + } + } + + virtual void EnumerateEventsDownSampled(double IntervalStart, double IntervalEnd, double Resolution, TFunctionRef Callback) const override + { + struct FStackEntry + { + uint64 LocalEventIndex; + }; + FStackEntry EventStack[SettingsType::MaxDepth]; + uint32 CurrentDepth = 0; + + struct FOutputEvent + { + double StartTime; + double EndTime; + uint32 Depth; + EventType Event; + }; + TArray OutputEvents; + + EnumerateEventsDownSampled(IntervalStart, IntervalEnd, Resolution, [&EventStack, &OutputEvents, &CurrentDepth, Callback](bool IsEnter, double Time, const EventType& Event) + { + if (IsEnter) + { + FStackEntry& StackEntry = EventStack[CurrentDepth]; + StackEntry.LocalEventIndex = OutputEvents.Num(); + FOutputEvent& OutputEvent = OutputEvents.AddDefaulted_GetRef(); + OutputEvent.StartTime = Time; + OutputEvent.EndTime = Time; + OutputEvent.Depth = CurrentDepth; + OutputEvent.Event = Event; + ++CurrentDepth; + } + else + { + { + FStackEntry& StackEntry = EventStack[--CurrentDepth]; + FOutputEvent* OutputEvent = OutputEvents.GetData() + StackEntry.LocalEventIndex; + OutputEvent->EndTime = Time; + } + if (CurrentDepth == 0) + { + for (FOutputEvent& OutputEvent : OutputEvents) + { + Callback(OutputEvent.StartTime, OutputEvent.EndTime, OutputEvent.Depth, OutputEvent.Event); + } + OutputEvents.Empty(OutputEvents.Num()); + } + } + }); + } + + virtual void EnumerateEvents(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const override + { + EnumerateEventsDownSampled(IntervalStart, IntervalEnd, 0.0, Callback); + } + + virtual void EnumerateEvents(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const override + { + EnumerateEventsDownSampled(IntervalStart, IntervalEnd, 0.0, Callback); + } + +private: + struct FEventScopeEntry + { + double Time; + //uint32 DebugDepth; + }; + + struct FEventStackEntry + { + uint64 EnterScopeIndex; + uint64 EventIndex; + }; + + struct FEventScopeEntryPage + { + FEventScopeEntry* Items = nullptr; + uint64 Count = 0; + double BeginTime = 0.0; + double EndTime = 0.0; + uint64 BeginEventIndex = 0; + uint64 EndEventIndex = 0; + FEventStackEntry* InitialStack = nullptr; + uint16 InitialStackCount = 0; + }; + + struct FDetailLevelDepthState + { + int64 PendingScopeEnterIndex = -1; + int64 PendingEventIndex = -1; + + EventType DominatingEvent; + double DominatingEventStartTime = 0.0; + double DominatingEventEndTime = 0.0; + double DominatingEventDuration = 0.0; + + double EnterTime = 0.0; + double ExitTime = 0.0; + + //const FTimelineEventType* DebugDominatingEventType; + }; + + struct FDetailLevelInsertionState + { + double LastTime = -1.0; + uint16 CurrentDepth = 0; + int32 PendingDepth = -1; + FDetailLevelDepthState DepthStates[SettingsType::MaxDepth]; + FEventStackEntry EventStack[SettingsType::MaxDepth]; + FEventScopeEntryPage* CurrentScopeEntryPage = nullptr; + }; + + struct FDetailLevel + { + FDetailLevel(ILinearAllocator& Allocator, double InResolution) + : Resolution(InResolution) + , ScopeEntries(Allocator, SettingsType::ScopeEntriesPageSize) + , Events(Allocator, SettingsType::EventsPageSize) + { + + } + + double Resolution; + TPagedArray ScopeEntries; + TPagedArray Events; + + FDetailLevelInsertionState InsertionState; + }; + + void UpdateDominatingEvent(FDetailLevel& DetailLevel, int32 Depth, double CurrentTime) + { + FDetailLevelDepthState& Lod0DepthState = DetailLevels[0].InsertionState.DepthStates[Depth]; + double Lod0EventDuration = CurrentTime - Lod0DepthState.EnterTime; + FDetailLevelDepthState& CurrentDepthState = DetailLevel.InsertionState.DepthStates[Depth]; + if (Lod0EventDuration > CurrentDepthState.DominatingEventDuration) + { + check(CurrentDepthState.PendingScopeEnterIndex >= 0); + check(CurrentDepthState.PendingEventIndex >= 0); + + CurrentDepthState.DominatingEvent = Lod0DepthState.DominatingEvent; + CurrentDepthState.DominatingEventStartTime = Lod0DepthState.EnterTime; + CurrentDepthState.DominatingEventEndTime = CurrentTime; + CurrentDepthState.DominatingEventDuration = Lod0EventDuration; + + SetEvent(DetailLevel, CurrentDepthState.PendingEventIndex, CurrentDepthState.DominatingEvent); + + //CurrentDepthState.DebugDominatingEventType = Owner.EventTypes[CurrentDepthState.DominatingEventType]; + } + } + + void AddScopeEntry(FDetailLevel& DetailLevel, double Time, bool IsEnter) + { + check(Time >= DetailLevel.InsertionState.LastTime); + DetailLevel.InsertionState.LastTime = Time; + + uint64 EventIndex = DetailLevel.Events.Num(); + uint64 ScopeIndex = DetailLevel.ScopeEntries.Num(); + + FEventScopeEntry& ScopeEntry = DetailLevel.ScopeEntries.PushBack(); + ScopeEntry.Time = IsEnter ? -Time : Time; + FEventScopeEntryPage* LastPage = DetailLevel.ScopeEntries.GetLastPage(); + if (LastPage != DetailLevel.InsertionState.CurrentScopeEntryPage) + { + DetailLevel.InsertionState.CurrentScopeEntryPage = LastPage; + LastPage->BeginTime = Time; + LastPage->BeginEventIndex = DetailLevel.Events.Num(); + LastPage->EndEventIndex = LastPage->BeginEventIndex; + LastPage->InitialStackCount = DetailLevel.InsertionState.CurrentDepth; + if (LastPage->InitialStackCount) + { + LastPage->InitialStack = reinterpret_cast(Allocator.Allocate(LastPage->InitialStackCount * sizeof(FEventStackEntry))); + memcpy(LastPage->InitialStack, DetailLevel.InsertionState.EventStack, LastPage->InitialStackCount * sizeof(FEventStackEntry)); + } + } + LastPage->EndTime = Time; + + if (IsEnter) + { + FEventStackEntry& StackEntry = DetailLevel.InsertionState.EventStack[DetailLevel.InsertionState.CurrentDepth++]; + check(DetailLevel.InsertionState.CurrentDepth < SettingsType::MaxDepth); + StackEntry.EventIndex = EventIndex; + StackEntry.EnterScopeIndex = ScopeIndex; + } + else + { + check(DetailLevel.InsertionState.CurrentDepth > 0); + --DetailLevel.InsertionState.CurrentDepth; + } + + //ScopeEntry.DebugDepth = DetailLevel.InsertionState.CurrentDepth; + } + + void AddEvent(FDetailLevel& DetailLevel, const EventType& Event) + { + ++DetailLevel.InsertionState.CurrentScopeEntryPage->EndEventIndex; + DetailLevel.Events.PushBack() = Event; + + //Event.DebugType = Owner.EventTypes[TypeIndex]; + } + + double GetScopeEntryTime(const FDetailLevel& DetailLevel, uint64 Index) const + { + const FEventScopeEntry& ScopeEntry = DetailLevel.ScopeEntries[Index]; + return ScopeEntry.Time < 0 ? -ScopeEntry.Time : ScopeEntry.Time; + } + + void SetEvent(FDetailLevel& DetailLevel, uint64 Index, const EventType& Event) + { + DetailLevel.Events[Index] = Event; + } + + EventType GetEvent(const FDetailLevel& DetailLevel, uint64 Index) const + { + return DetailLevel.Events[Index]; + } + + ILinearAllocator& Allocator; + TArray DetailLevels; + uint64 ModCount = 0; +}; + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Model/Tables.h b/Engine/Source/Developer/TraceServices/Private/Model/Tables.h new file mode 100644 index 000000000000..fedb3e3e9915 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/Tables.h @@ -0,0 +1,552 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "TraceServices/AnalysisService.h" +#include "Templates/Function.h" +#include "Common/PagedArray.h" +#include "Common/SlabAllocator.h" + +#define UE_TRACE_TABLE_LAYOUT_BEGIN(Name, InRowType) \ + class Name \ + : public ::Trace::TTableLayoutBase \ + { \ + public: \ + typedef InRowType RowType; \ + virtual uint8 GetColumnCount() const override { return decltype(LastColumn)::Index; } \ + template \ + constexpr ::Trace::ETableColumnType GetColumnTypeInternal() const { return TableColumnType_Invalid; } \ + template \ + constexpr const TCHAR* GetColumnNameInternal() const { return nullptr; } \ + template \ + ::Trace::FColumnValueContainer GetColumnValueInternal(const RowType& Row) const { return 0; } \ + ::Trace::TColumnDeclaration<0, RowType + +#if PLATFORM_WINDOWS + +#define UE_TRACE_TABLE_COLUMN(Name, DisplayName) \ + > PREPROCESSOR_JOIN(__Column__, __LINE__); \ + template<> \ + constexpr ::Trace::ETableColumnType GetColumnTypeInternal() const { return ::Trace::GetColumnTypeFromNativeType(); } \ + template<> \ + constexpr const TCHAR* GetColumnNameInternal() const { return DisplayName; } \ + template<> \ + ::Trace::FColumnValueContainer GetColumnValueInternal(const RowType& Row) const { return Row.Name; } \ + ::Trace::TColumnDeclaration PREPROCESSOR_JOIN(__Column__, __LINE__); \ + TFunction::Type(const RowType&)> PREPROCESSOR_JOIN(__ColumnProjector__, __LINE__) = ProjectionFunc; \ + template<> \ + constexpr ::Trace::ETableColumnType GetColumnTypeInternal() const { return ::Trace::ColumnType; } \ + template<> \ + constexpr const TCHAR* GetColumnNameInternal() const { return DisplayName; } \ + template<> \ + ::Trace::FColumnValueContainer GetColumnValueInternal(const RowType& Row) const { return PREPROCESSOR_JOIN(__ColumnProjector__, __LINE__)(Row); } \ + ::Trace::TColumnDeclaration PREPROCESSOR_JOIN(__Column__, __LINE__); \ + ::Trace::TColumnDeclaration PREPROCESSOR_JOIN(__Column__, __LINE__); \ + ::Trace::TColumnDeclaration LastColumn;\ + }; + +namespace Trace +{ + +template +static constexpr ETableColumnType GetColumnTypeFromNativeType(); + +template<> +constexpr ETableColumnType GetColumnTypeFromNativeType() +{ + return TableColumnType_Bool; +} + +template<> +constexpr ETableColumnType GetColumnTypeFromNativeType() +{ + return TableColumnType_Int; +}; + +template<> +constexpr ETableColumnType GetColumnTypeFromNativeType() +{ + return TableColumnType_Int; +}; + +template<> +constexpr ETableColumnType GetColumnTypeFromNativeType() +{ + return TableColumnType_Int; +}; + +template<> +constexpr ETableColumnType GetColumnTypeFromNativeType() +{ + return TableColumnType_Int; +}; + +template<> +constexpr ETableColumnType GetColumnTypeFromNativeType() +{ + return TableColumnType_Int; +}; + +template<> +constexpr ETableColumnType GetColumnTypeFromNativeType() +{ + return TableColumnType_Int; +}; + +template<> +constexpr ETableColumnType GetColumnTypeFromNativeType() +{ + return TableColumnType_Int; +}; + +template<> +constexpr ETableColumnType GetColumnTypeFromNativeType() +{ + return TableColumnType_Int; +}; + +template<> +constexpr ETableColumnType GetColumnTypeFromNativeType() +{ + return TableColumnType_Float; +}; + +template<> +constexpr ETableColumnType GetColumnTypeFromNativeType() +{ + return TableColumnType_Double; +}; + +template<> +constexpr ETableColumnType GetColumnTypeFromNativeType() +{ + return TableColumnType_CString; +} + +template +struct FNativeTypeFromColumnType +{ + +}; + +template<> +struct FNativeTypeFromColumnType +{ + using Type = bool; +}; + +template<> +struct FNativeTypeFromColumnType +{ + using Type = int64; +}; + +template<> +struct FNativeTypeFromColumnType +{ + using Type = float; +}; + +template<> +struct FNativeTypeFromColumnType +{ + using Type = double; +}; + +template<> +struct FNativeTypeFromColumnType +{ + using Type = const TCHAR*; +}; + +template +MemberType GetMemberTypeHelper(MemberType ObjectType::*); + +struct FColumnValueContainer +{ + FColumnValueContainer(bool Value) + { + BoolValue = Value; + } + + FColumnValueContainer(int8 Value) + { + IntValue = Value; + } + + FColumnValueContainer(uint8 Value) + { + IntValue = Value; + } + + FColumnValueContainer(int32 Value) + { + IntValue = Value; + } + + FColumnValueContainer(uint32 Value) + { + IntValue = Value; + } + + FColumnValueContainer(int64 Value) + { + IntValue = Value; + } + + FColumnValueContainer(uint64 Value) + { + IntValue = int64(Value); + } + + FColumnValueContainer(float Value) + { + FloatValue = Value; + } + + FColumnValueContainer(double Value) + { + DoubleValue = Value; + } + + FColumnValueContainer(const TCHAR* Value) + { + CStringValue = Value; + } + + union + { + bool BoolValue; + int64 IntValue; + float FloatValue; + double DoubleValue; + const TCHAR* CStringValue; + }; +}; + +template +struct TColumnDeclaration +{ + enum + { + Index = IndexValue + }; + + static constexpr ETableColumnType GetType() { return ::Trace::TableColumnType_Invalid; } + static constexpr const TCHAR* GetName() { return nullptr; } + static const void* GetValue(const RowType& Row) { return nullptr; } +}; + +template +class TTableReader + : public ITableReader +{ +public: + TTableReader(const LayoutType& InLayout, const TPagedArray& InRows) + : Layout(InLayout) + , Iterator(InRows.GetIteratorFromItem(0)) + { + CurrentRow = Iterator.GetCurrentItem(); + } + + virtual bool IsValid() const override + { + return CurrentRow != nullptr; + } + + virtual void NextRow() override + { + CurrentRow = Iterator.NextItem(); + } + + virtual void SetRowIndex(uint64 RowIndex) override + { + CurrentRow = Iterator.SetPosition(RowIndex); + } + + virtual const typename LayoutType::RowType* GetCurrentRow() const override + { + return CurrentRow; + } + + virtual bool GetValueBool(uint8 ColumnIndex) const override + { + if (!CurrentRow) + { + return false; + } + ETableColumnType ColumnType = Layout.GetColumnTypeConstExpr(ColumnIndex); + switch (ColumnType) + { + case TableColumnType_Bool: + return Layout.GetColumnValue(*CurrentRow, ColumnIndex).BoolValue; + case TableColumnType_Int: + return static_cast(Layout.GetColumnValue(*CurrentRow, ColumnIndex).IntValue); + case TableColumnType_Float: + return static_cast(Layout.GetColumnValue(*CurrentRow, ColumnIndex).FloatValue); + case TableColumnType_Double: + return static_cast(Layout.GetColumnValue(*CurrentRow, ColumnIndex).DoubleValue); + } + return false; + } + + virtual int64 GetValueInt(uint8 ColumnIndex) const override + { + if (!CurrentRow) + { + return 0; + } + ETableColumnType ColumnType = Layout.GetColumnTypeConstExpr(ColumnIndex); + switch (ColumnType) + { + case TableColumnType_Bool: + return static_cast(Layout.GetColumnValue(*CurrentRow, ColumnIndex).BoolValue); + case TableColumnType_Int: + return Layout.GetColumnValue(*CurrentRow, ColumnIndex).IntValue; + case TableColumnType_Float: + return static_cast(Layout.GetColumnValue(*CurrentRow, ColumnIndex).FloatValue); + case TableColumnType_Double: + return static_cast(Layout.GetColumnValue(*CurrentRow, ColumnIndex).DoubleValue); + } + return 0; + } + + virtual float GetValueFloat(uint8 ColumnIndex) const override + { + if (!CurrentRow) + { + return 0.0; + } + ETableColumnType ColumnType = Layout.GetColumnTypeConstExpr(ColumnIndex); + switch (ColumnType) + { + case TableColumnType_Bool: + return static_cast(Layout.GetColumnValue(*CurrentRow, ColumnIndex).BoolValue); + case TableColumnType_Int: + return static_cast(Layout.GetColumnValue(*CurrentRow, ColumnIndex).IntValue); + case TableColumnType_Float: + return Layout.GetColumnValue(*CurrentRow, ColumnIndex).FloatValue; + case TableColumnType_Double: + return static_cast(Layout.GetColumnValue(*CurrentRow, ColumnIndex).DoubleValue); + } + return 0.0; + } + + virtual double GetValueDouble(uint8 ColumnIndex) const override + { + if (!CurrentRow) + { + return 0.0; + } + ETableColumnType ColumnType = Layout.GetColumnTypeConstExpr(ColumnIndex); + switch (ColumnType) + { + case TableColumnType_Bool: + return static_cast(Layout.GetColumnValue(*CurrentRow, ColumnIndex).BoolValue); + case TableColumnType_Int: + return static_cast(Layout.GetColumnValue(*CurrentRow, ColumnIndex).IntValue); + case TableColumnType_Float: + return static_cast(Layout.GetColumnValue(*CurrentRow, ColumnIndex).FloatValue); + case TableColumnType_Double: + return Layout.GetColumnValue(*CurrentRow, ColumnIndex).DoubleValue; + } + return 0.0; + } + + virtual const TCHAR* GetValueCString(uint8 ColumnIndex) const override + { + if (!CurrentRow) + { + return nullptr; + } + ETableColumnType ColumnType = Layout.GetColumnTypeConstExpr(ColumnIndex); + if (ColumnType == TableColumnType_CString) + { + return Layout.GetColumnValue(*CurrentRow, ColumnIndex).CStringValue; + } + return nullptr; + } + +private: + const LayoutType& Layout; + typename TPagedArray::FIterator Iterator; + const typename LayoutType::RowType* CurrentRow; +}; + +template +class TTableLayoutBase + : public ITableLayout +{ +public: + constexpr ETableColumnType GetColumnTypeConstExpr(uint8 ColumnIndex) const + { + const LayoutType* This = static_cast(this); + switch (ColumnIndex) + { + case 0: return This->template GetColumnTypeInternal<0>(); + case 1: return This->template GetColumnTypeInternal<1>(); + case 2: return This->template GetColumnTypeInternal<2>(); + case 3: return This->template GetColumnTypeInternal<3>(); + case 4: return This->template GetColumnTypeInternal<4>(); + case 5: return This->template GetColumnTypeInternal<5>(); + case 6: return This->template GetColumnTypeInternal<6>(); + case 7: return This->template GetColumnTypeInternal<7>(); + case 8: return This->template GetColumnTypeInternal<8>(); + case 9: return This->template GetColumnTypeInternal<9>(); + case 10: return This->template GetColumnTypeInternal<10>(); + case 11: return This->template GetColumnTypeInternal<11>(); + case 12: return This->template GetColumnTypeInternal<12>(); + case 13: return This->template GetColumnTypeInternal<13>(); + case 14: return This->template GetColumnTypeInternal<14>(); + case 15: return This->template GetColumnTypeInternal<15>(); + } + return ::Trace::TableColumnType_Invalid; + } + + virtual ETableColumnType GetColumnType(uint8 ColumnIndex) const override + { + return GetColumnTypeConstExpr(ColumnIndex); + } + + constexpr const TCHAR* GetColumnNameConstExpr(uint8 ColumnIndex) const + { + const LayoutType* This = static_cast(this); + switch (ColumnIndex) + { + case 0: return This->template GetColumnNameInternal<0>(); + case 1: return This->template GetColumnNameInternal<1>(); + case 2: return This->template GetColumnNameInternal<2>(); + case 3: return This->template GetColumnNameInternal<3>(); + case 4: return This->template GetColumnNameInternal<4>(); + case 5: return This->template GetColumnNameInternal<5>(); + case 6: return This->template GetColumnNameInternal<6>(); + case 7: return This->template GetColumnNameInternal<7>(); + case 8: return This->template GetColumnNameInternal<8>(); + case 9: return This->template GetColumnNameInternal<9>(); + case 10: return This->template GetColumnNameInternal<10>(); + case 11: return This->template GetColumnNameInternal<11>(); + case 12: return This->template GetColumnNameInternal<12>(); + case 13: return This->template GetColumnNameInternal<13>(); + case 14: return This->template GetColumnNameInternal<14>(); + case 15: return This->template GetColumnNameInternal<15>(); + } + return nullptr; + } + + virtual const TCHAR* GetColumnName(uint8 ColumnIndex) const override + { + return GetColumnNameConstExpr(ColumnIndex); + } + + FColumnValueContainer GetColumnValue(const RowType& Row, uint8 ColumnIndex) const + { + const LayoutType* This = static_cast(this); + switch (ColumnIndex) + { + case 0: return This->template GetColumnValueInternal<0>(Row); + case 1: return This->template GetColumnValueInternal<1>(Row); + case 2: return This->template GetColumnValueInternal<2>(Row); + case 3: return This->template GetColumnValueInternal<3>(Row); + case 4: return This->template GetColumnValueInternal<4>(Row); + case 5: return This->template GetColumnValueInternal<5>(Row); + case 6: return This->template GetColumnValueInternal<6>(Row); + case 7: return This->template GetColumnValueInternal<7>(Row); + case 8: return This->template GetColumnValueInternal<8>(Row); + case 9: return This->template GetColumnValueInternal<9>(Row); + case 10: return This->template GetColumnValueInternal<10>(Row); + case 11: return This->template GetColumnValueInternal<11>(Row); + case 12: return This->template GetColumnValueInternal<12>(Row); + case 13: return This->template GetColumnValueInternal<13>(Row); + case 14: return This->template GetColumnValueInternal<14>(Row); + case 15: return This->template GetColumnValueInternal<15>(Row); + } + return nullptr; + } +}; + +template +class TTableView + : public ITable +{ +public: + TTableView(const TPagedArray& InRows) + : Rows(InRows) + { + } + + virtual const ITableLayout& GetLayout() const override + { + return Layout; + } + + virtual uint64 GetRowCount() const override + { + return Rows.Num(); + } + + virtual ITableReader* CreateReader() const override + { + return new TTableReader(Layout, Rows); + } + +private: + LayoutType Layout; + const TPagedArray& Rows; +}; + +template +class TTable + : public ITable +{ +public: + TTable() + : Allocator(2 << 20) + , Rows(Allocator, 1024) + { + + } + + virtual const ITableLayout& GetLayout() const override + { + return Layout; + } + + virtual uint64 GetRowCount() const override + { + return Rows.Num(); + } + + virtual ITableReader* CreateReader() const override + { + return new TTableReader(Layout, Rows); + } + + typename LayoutType::RowType& AddRow() + { + return Rows.PushBack(); + } + +private: + LayoutType Layout; + FSlabAllocator Allocator; + TPagedArray Rows; +}; + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Model/Threads.cpp b/Engine/Source/Developer/TraceServices/Private/Model/Threads.cpp new file mode 100644 index 000000000000..a1187ed7bf45 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/Threads.cpp @@ -0,0 +1,205 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TraceServices/Model/Threads.h" +#include "Model/ThreadsPrivate.h" +#include "Misc/ScopeLock.h" +#include "AnalysisServicePrivate.h" +#include "Common/StringStore.h" + +namespace Trace +{ + +const FName FThreadProvider::ProviderName = "ThreadProvider"; + +FThreadProvider::FThreadProvider(IAnalysisSession& InSession) + : Session(InSession) +{ + +} + +FThreadProvider::~FThreadProvider() +{ + for (FThreadInfoInternal* Thread : SortedThreads) + { + delete Thread; + } +} + +void FThreadProvider::AddGameThread(uint32 Id) +{ + Session.WriteAccessCheck(); + + check(!ThreadMap.Contains(Id)); + FThreadInfoInternal* ThreadInfo = new FThreadInfoInternal(); + ThreadInfo->Id = Id; + ThreadInfo->PrioritySortOrder = GetPrioritySortOrder(TPri_Normal); + ThreadInfo->Name = Session.StoreString(*FName(NAME_GameThread).GetPlainNameString()); + ThreadInfo->FallbackSortOrder = SortedThreads.Num(); + ThreadInfo->IsGameThread = true; + SortedThreads.Add(ThreadInfo); + ThreadMap.Add(Id, ThreadInfo); + ++ModCount; +} + +void FThreadProvider::AddThread(uint32 Id, const TCHAR* Name, EThreadPriority Priority) +{ + Session.WriteAccessCheck(); + + FThreadInfoInternal* ThreadInfo; + if (!ThreadMap.Contains(Id)) + { + ThreadInfo = new FThreadInfoInternal(); + ThreadInfo->Id = Id; + ThreadInfo->FallbackSortOrder = SortedThreads.Num(); + SortedThreads.Add(ThreadInfo); + ThreadMap.Add(Id, ThreadInfo); + } + else + { + ThreadInfo = ThreadMap[Id]; + } + ThreadInfo->PrioritySortOrder = GetPrioritySortOrder(Priority); + ThreadInfo->Name = Session.StoreString(Name); + SortThreads(); + ++ModCount; +} + +void FThreadProvider::SetThreadPriority(uint32 Id, EThreadPriority Priority) +{ + Session.WriteAccessCheck(); + + check(ThreadMap.Contains(Id)); + FThreadInfoInternal* ThreadInfo = ThreadMap[Id]; + ThreadInfo->PrioritySortOrder = GetPrioritySortOrder(Priority); + SortThreads(); + ++ModCount; +} + +void FThreadProvider::SetThreadGroup(uint32 Id, const TCHAR* GroupName) +{ + Session.WriteAccessCheck(); + + check(ThreadMap.Contains(Id)); + FThreadInfoInternal* ThreadInfo = ThreadMap[Id]; + ThreadInfo->GroupName = GroupName; + ThreadInfo->GroupSortOrder = GetGroupSortOrder(GroupName); + SortThreads(); + ++ModCount; +} + +void FThreadProvider::EnumerateThreads(TFunctionRef Callback) const +{ + Session.ReadAccessCheck(); + + for (const FThreadInfoInternal* Thread : SortedThreads) + { + FThreadInfo ThreadInfo; + ThreadInfo.Id = Thread->Id; + ThreadInfo.Name = Thread->Name; + ThreadInfo.GroupName = Thread->GroupName; + Callback(ThreadInfo); + } +} + +void FThreadProvider::SortThreads() +{ + Algo::SortBy(SortedThreads, [](const FThreadInfoInternal* In) -> const FThreadInfoInternal& + { + return *In; + }); +} + +uint32 FThreadProvider::GetGroupSortOrder(const TCHAR* GroupName) +{ + if (!GroupName) + { + return uint32(-1); + } + else if (!FCString::Strcmp(GroupName, TEXT("Render"))) + { + return 0; + } + else if (!FCString::Strcmp(GroupName, TEXT("AsyncLoading"))) + { + return 1; + } + else if (!FCString::Strcmp(GroupName, TEXT("TaskGraphHigh"))) + { + return 2; + } + else if (!FCString::Strcmp(GroupName, TEXT("TaskGraphNormal"))) + { + return 3; + } + else if (!FCString::Strcmp(GroupName, TEXT("TaskGraphLow"))) + { + return 4; + } + else if (!FCString::Strcmp(GroupName, TEXT("LargeThreadPool"))) + { + return 5; + } + else if (!FCString::Strcmp(GroupName, TEXT("ThreadPool"))) + { + return 6; + } + else if (!FCString::Strcmp(GroupName, TEXT("BackgroundThreadPool"))) + { + return 7; + } + else if (!FCString::Strcmp(GroupName, TEXT("IOThreadPool"))) + { + return 8; + } + else + { + return GetTypeHash(GroupName); + } +} + +uint32 FThreadProvider::GetPrioritySortOrder(EThreadPriority ThreadPriority) +{ + switch (ThreadPriority) + { + case TPri_TimeCritical: + return 0; + case TPri_Highest: + return 1; + case TPri_AboveNormal: + return 2; + case TPri_Normal: + return 3; + case TPri_SlightlyBelowNormal: + return 4; + case TPri_BelowNormal: + return 5; + case TPri_Lowest: + return 6; + default: + return 7; + } +} + +bool FThreadProvider::FThreadInfoInternal::operator<(const FThreadInfoInternal& Other) const +{ + if (IsGameThread == Other.IsGameThread) + { + if (GroupSortOrder == Other.GroupSortOrder) + { + if (PrioritySortOrder == Other.PrioritySortOrder) + { + return FallbackSortOrder < Other.FallbackSortOrder; + } + return PrioritySortOrder < Other.PrioritySortOrder; + } + return GroupSortOrder < Other.GroupSortOrder; + } + return IsGameThread; +} + +const IThreadProvider& ReadThreadProvider(const IAnalysisSession& Session) +{ + return *Session.ReadProvider(FThreadProvider::ProviderName); +} + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Model/ThreadsPrivate.h b/Engine/Source/Developer/TraceServices/Private/Model/ThreadsPrivate.h new file mode 100644 index 000000000000..1c3671d2f7c1 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/ThreadsPrivate.h @@ -0,0 +1,51 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/Model/AnalysisSession.h" +#include "ProfilingDebugging/MiscTrace.h" +#include "HAL/PlatformAffinity.h" +#include "Containers/Map.h" + +namespace Trace +{ + +class FThreadProvider + : public IThreadProvider +{ +public: + static const FName ProviderName; + FThreadProvider(IAnalysisSession& Session); + ~FThreadProvider(); + void AddGameThread(uint32 Id); + void AddThread(uint32 Id, const TCHAR* Name, EThreadPriority Priority); + void SetThreadPriority(uint32 Id, EThreadPriority Priority); + void SetThreadGroup(uint32 Id, const TCHAR* GroupName); + virtual uint64 GetModCount() const override { return ModCount; } + virtual void EnumerateThreads(TFunctionRef Callback) const override; + +private: + struct FThreadInfoInternal + { + uint32 Id = 0; + const TCHAR* Name = nullptr; + uint32 GroupSortOrder = ~0u; + uint32 PrioritySortOrder = ~0u; + uint32 FallbackSortOrder = ~0u; + const TCHAR* GroupName = nullptr; + bool IsGameThread = false; + + bool operator<(const FThreadInfoInternal& Other) const; + }; + + void SortThreads(); + static uint32 GetPrioritySortOrder(EThreadPriority ThreadPriority); + static uint32 GetGroupSortOrder(const TCHAR* GroupName); + + IAnalysisSession& Session; + uint64 ModCount = 0; + TMap ThreadMap; + TArray SortedThreads; +}; + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/Model/TimingProfiler.cpp b/Engine/Source/Developer/TraceServices/Private/Model/TimingProfiler.cpp new file mode 100644 index 000000000000..64088a7cdd8a --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/TimingProfiler.cpp @@ -0,0 +1,179 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TraceServices/Model/TimingProfiler.h" +#include "Model/TimingProfilerPrivate.h" +#include "AnalysisServicePrivate.h" +#include "Common/StringStore.h" +#include "Common/TimelineStatistics.h" + +namespace Trace +{ + +FTimingProfilerProvider::FTimingProfilerProvider(IAnalysisSession& InSession) + : Session(InSession) +{ + Timelines.Add(MakeShared(Session.GetLinearAllocator())); +} + +FTimingProfilerProvider::~FTimingProfilerProvider() +{ +} + +uint32 FTimingProfilerProvider::AddCpuTimer(const TCHAR* Name) +{ + Session.WriteAccessCheck(); + + FTimingProfilerTimer& Timer = AddTimerInternal(Name, false); + return Timer.Id; +} + +void FTimingProfilerProvider::SetTimerName(uint32 TimerId, const TCHAR* Name) +{ + Session.WriteAccessCheck(); + + FTimingProfilerTimer& Timer = Timers[TimerId]; + Timer.Name = Session.StoreString(Name); + uint32 NameHash = 0; + for (const TCHAR* c = Name; *c; ++c) + { + NameHash = (NameHash + *c) * 0x2c2c57ed; + } + Timer.NameHash = NameHash; +} + +uint32 FTimingProfilerProvider::AddGpuTimer(const TCHAR* Name) +{ + Session.WriteAccessCheck(); + + FTimingProfilerTimer& Timer = AddTimerInternal(Name, true); + return Timer.Id; +} + +FTimingProfilerTimer& FTimingProfilerProvider::AddTimerInternal(const TCHAR* Name, bool IsGpuTimer) +{ + FTimingProfilerTimer& Timer = Timers.AddDefaulted_GetRef(); + Timer.Id = Timers.Num() - 1; + Timer.Name = Session.StoreString(Name); + uint32 NameHash = 0; + for (const TCHAR* c = Name; *c; ++c) + { + NameHash = (NameHash + *c) * 0x2c2c57ed; + } + Timer.NameHash = NameHash; + Timer.IsGpuTimer = IsGpuTimer; + return Timer; +} + +FTimingProfilerProvider::TimelineInternal& FTimingProfilerProvider::EditCpuThreadTimeline(uint32 ThreadId) +{ + Session.WriteAccessCheck(); + + if (!CpuThreadTimelineIndexMap.Contains(ThreadId)) + { + TSharedRef Timeline = MakeShared(Session.GetLinearAllocator()); + uint32 TimelineIndex = Timelines.Num(); + CpuThreadTimelineIndexMap.Add(ThreadId, TimelineIndex); + Timelines.Add(Timeline); + return Timeline.Get(); + } + else + { + uint32 TimelineIndex = CpuThreadTimelineIndexMap[ThreadId]; + return Timelines[TimelineIndex].Get(); + } +} + +FTimingProfilerProvider::TimelineInternal& FTimingProfilerProvider::EditGpuTimeline() +{ + Session.WriteAccessCheck(); + + return Timelines[GpuTimelineIndex].Get(); +} + +bool FTimingProfilerProvider::GetCpuThreadTimelineIndex(uint32 ThreadId, uint32& OutTimelineIndex) const +{ + Session.ReadAccessCheck(); + + if (CpuThreadTimelineIndexMap.Contains(ThreadId)) + { + OutTimelineIndex = CpuThreadTimelineIndexMap[ThreadId]; + return true; + } + return false; +} + +bool FTimingProfilerProvider::GetGpuTimelineIndex(uint32& OutTimelineIndex) const +{ + Session.ReadAccessCheck(); + + OutTimelineIndex = GpuTimelineIndex; + return true; +} + +bool FTimingProfilerProvider::ReadTimeline(uint32 Index, TFunctionRef Callback) const +{ + Session.ReadAccessCheck(); + + if (Index < uint32(Timelines.Num())) + { + Callback(*Timelines[Index]); + return true; + } + else + { + return false; + } +} + +void FTimingProfilerProvider::EnumerateTimelines(TFunctionRef Callback) const +{ + Session.ReadAccessCheck(); + + for (const auto& Timeline : Timelines) + { + Callback(*Timeline); + } +} + +void FTimingProfilerProvider::ReadTimers(TFunctionRef Callback) const +{ + Session.ReadAccessCheck(); + + Callback(Timers.GetData(), Timers.Num()); +} + +ITable* FTimingProfilerProvider::CreateAggregation(double IntervalStart, double IntervalEnd, TFunctionRef CpuThreadFilter, bool IncludeGpu) const +{ + Session.ReadAccessCheck(); + + TArray IncludedTimelines; + if (IncludeGpu) + { + IncludedTimelines.Add(&Timelines[GpuTimelineIndex].Get()); + } + for (const auto& KV : CpuThreadTimelineIndexMap) + { + if (CpuThreadFilter(KV.Key)) + { + IncludedTimelines.Add(&Timelines[KV.Value].Get()); + } + } + + auto BucketMappingFunc = [this](const TimelineInternal::EventType& Event) -> const FTimingProfilerTimer* + { + return &Timers[Event.TimerIndex]; + }; + + TMap Aggregation; + FTimelineStatistics::CreateAggregation(IncludedTimelines, BucketMappingFunc, IntervalStart, IntervalEnd, Aggregation); + TTable* Table = new TTable(); + for (const auto& KV : Aggregation) + { + FTimingProfilerAggregatedStats& Row = Table->AddRow(); + *static_cast(&Row) = KV.Value; + Row.Timer = KV.Key; + } + return Table; +} + +} diff --git a/Engine/Source/Developer/TraceServices/Private/Model/TimingProfilerPrivate.h b/Engine/Source/Developer/TraceServices/Private/Model/TimingProfilerPrivate.h new file mode 100644 index 000000000000..5744c79e0afd --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Model/TimingProfilerPrivate.h @@ -0,0 +1,62 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/Model/TimingProfiler.h" +#include "Common/SlabAllocator.h" +#include "Model/MonotonicTimeline.h" +#include "Model/Tables.h" + +namespace Trace +{ + +class FAnalysisSessionLock; +class FStringStore; + +class FTimingProfilerProvider + : public ITimingProfilerProvider +{ +public: + typedef TMonotonicTimeline TimelineInternal; + + FTimingProfilerProvider(IAnalysisSession& InSession); + virtual ~FTimingProfilerProvider(); + uint32 AddCpuTimer(const TCHAR* Name); + uint32 AddGpuTimer(const TCHAR* Name); + void SetTimerName(uint32 TimerId, const TCHAR* Name); + TimelineInternal& EditCpuThreadTimeline(uint32 ThreadId); + TimelineInternal& EditGpuTimeline(); + virtual bool GetCpuThreadTimelineIndex(uint32 ThreadId, uint32& OutTimelineIndex) const override; + virtual bool GetGpuTimelineIndex(uint32& OutTimelineIndex) const override; + virtual bool ReadTimeline(uint32 Index, TFunctionRef Callback) const override; + virtual uint64 GetTimelineCount() const override { return Timelines.Num(); } + virtual void EnumerateTimelines(TFunctionRef Callback) const override; + virtual void ReadTimers(TFunctionRef Callback) const override; + virtual ITable* CreateAggregation(double IntervalStart, double IntervalEnd, TFunctionRef CpuThreadFilter, bool IncludeGpu) const override; + +private: + FTimingProfilerTimer& AddTimerInternal(const TCHAR* Name, bool IsGpuEvent); + + IAnalysisSession& Session; + TArray Timers; + TArray> Timelines; + TMap CpuThreadTimelineIndexMap; + uint32 GpuTimelineIndex = 0; + + UE_TRACE_TABLE_LAYOUT_BEGIN(FAggregatedStatsTableLayout, FTimingProfilerAggregatedStats) + UE_TRACE_TABLE_PROJECTED_COLUMN(TableColumnType_CString, TEXT("Name"), [](const FTimingProfilerAggregatedStats& Row) { return Row.Timer->Name; }) + UE_TRACE_TABLE_COLUMN(InstanceCount, TEXT("Count")) + UE_TRACE_TABLE_COLUMN(TotalInclusiveTime, TEXT("Incl")) + UE_TRACE_TABLE_COLUMN(MinInclusiveTime, TEXT("I.Min")) + UE_TRACE_TABLE_COLUMN(MaxInclusiveTime, TEXT("I.Max")) + UE_TRACE_TABLE_COLUMN(AverageInclusiveTime, TEXT("I.Avg")) + UE_TRACE_TABLE_COLUMN(MedianInclusiveTime, TEXT("I.Med")) + UE_TRACE_TABLE_COLUMN(TotalExclusiveTime, TEXT("Excl")) + UE_TRACE_TABLE_COLUMN(MinExclusiveTime, TEXT("E.Min")) + UE_TRACE_TABLE_COLUMN(MaxExclusiveTime, TEXT("E.Max")) + UE_TRACE_TABLE_COLUMN(AverageExclusiveTime, TEXT("E.Avg")) + UE_TRACE_TABLE_COLUMN(MedianExclusiveTime, TEXT("E.Med")) + UE_TRACE_TABLE_LAYOUT_END() +}; + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Private/ModuleService.cpp b/Engine/Source/Developer/TraceServices/Private/ModuleService.cpp new file mode 100644 index 000000000000..6bd48b1af7df --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/ModuleService.cpp @@ -0,0 +1,96 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TraceServices/ModuleService.h" +#include "ModuleServicePrivate.h" +#include "UObject/NameTypes.h" +#include "Features/IModularFeatures.h" + +namespace Trace +{ + +const FName ModuleFeatureName("TraceModuleFeature"); + +FModuleService::FModuleService() +{ + +} + +void FModuleService::Initialize() +{ + if (bIsInitialized) + { + return; + } + TArray Modules = IModularFeatures::Get().GetModularFeatureImplementations(ModuleFeatureName); + for (IModule* Module : Modules) + { + FModuleInfo ModuleInfo; + Module->GetModuleInfo(ModuleInfo); + ModulesMap.Add(ModuleInfo.Name, Module); + } + + bIsInitialized = true; +} + +void FModuleService::GetAvailableModules(TArray& OutModules) +{ + FScopeLock Lock(&CriticalSection); + Initialize(); + OutModules.Empty(ModulesMap.Num()); + for (const auto& KV : ModulesMap) + { + IModule* Module = KV.Value; + FModuleInfo& ModuleInfo = OutModules.AddDefaulted_GetRef(); + Module->GetModuleInfo(ModuleInfo); + } +} + +void FModuleService::SetModuleEnabled(const FName& ModuleName, bool bEnabled) +{ + FScopeLock Lock(&CriticalSection); + Initialize(); + IModule** FindIt = ModulesMap.Find(ModuleName); + if (!FindIt) + { + return; + } + bool bWasEnabled = !!EnabledModules.Find(*FindIt); + if (bEnabled == bWasEnabled) + { + return; + } + if (bEnabled) + { + EnabledModules.Add(*FindIt); + } + else + { + EnabledModules.Remove(*FindIt); + } +} + +void FModuleService::OnAnalysisBegin(IAnalysisSession& Session, TArray& OutAnalyzers) +{ + FScopeLock Lock(&CriticalSection); + Initialize(); + for (const auto& KV : ModulesMap) + { + IModule* Module = KV.Value; + Module->OnAnalysisBegin(Session, EnabledModules.Contains(Module), OutAnalyzers); + } +} + +bool FModuleService::GetModuleLoggers(const FName& ModuleName, TArray& OutLoggers) +{ + FScopeLock Lock(&CriticalSection); + Initialize(); + IModule** FindIt = ModulesMap.Find(ModuleName); + if (!FindIt) + { + return false; + } + (*FindIt)->GetLoggers(OutLoggers); + return true; +} + +} diff --git a/Engine/Source/Developer/TraceServices/Private/ModuleServicePrivate.h b/Engine/Source/Developer/TraceServices/Private/ModuleServicePrivate.h new file mode 100644 index 000000000000..710b75f4e50f --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/ModuleServicePrivate.h @@ -0,0 +1,34 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/ModuleService.h" +#include "Containers/Map.h" +#include "Containers/Set.h" +#include "Misc/ScopeLock.h" + +namespace Trace +{ + +class IAnalysisSession; + +class FModuleService + : public IModuleService +{ +public: + FModuleService(); + virtual void GetAvailableModules(TArray& OutModules) override; + virtual void SetModuleEnabled(const FName& ModuleName, bool bEnabled) override; + void OnAnalysisBegin(IAnalysisSession& Session, TArray& OutAnalyzers); + bool GetModuleLoggers(const FName& ModuleName, TArray& OutLoggers); + +private: + void Initialize(); + + mutable FCriticalSection CriticalSection; + bool bIsInitialized = false; + TSet EnabledModules; + TMap ModulesMap; +}; + +} diff --git a/Engine/Source/Developer/TraceServices/Private/Modules/LoadTimeProfilerModule.cpp b/Engine/Source/Developer/TraceServices/Private/Modules/LoadTimeProfilerModule.cpp new file mode 100644 index 000000000000..0aab4bf2de97 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Modules/LoadTimeProfilerModule.cpp @@ -0,0 +1,49 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "LoadTimeProfilerModule.h" +#include "Analyzers/PlatformFileTraceAnalysis.h" +#include "Analyzers/LoadTimeTraceAnalysis.h" +#include "AnalysisServicePrivate.h" +#include "Model/FileActivity.h" + +namespace Trace +{ + +FName FLoadTimeProfilerModule::ModuleName("TraceModule_LoadTimeProfiler"); + +void FLoadTimeProfilerModule::GetModuleInfo(FModuleInfo& OutModuleInfo) +{ + OutModuleInfo.Name = ModuleName; + OutModuleInfo.DisplayName = TEXT("Asset Loading"); +} + +static const FName LoadTimeProfilerProviderName("LoadTimeProfiler"); +static const FName FileActivityProviderName("FileActivity"); + +void FLoadTimeProfilerModule::OnAnalysisBegin(IAnalysisSession& Session, bool bIsEnabled, TArray& OutAnalyzers) +{ + FLoadTimeProfilerProvider* LoadTimeProfilerProvider = new FLoadTimeProfilerProvider(Session); + Session.AddProvider(LoadTimeProfilerProviderName, LoadTimeProfilerProvider); + OutAnalyzers.Add(new FAsyncLoadingTraceAnalyzer(Session, *LoadTimeProfilerProvider)); + FFileActivityProvider* FileActivityProvider = new FFileActivityProvider(Session); + Session.AddProvider(FileActivityProviderName, FileActivityProvider); + OutAnalyzers.Add(new FPlatformFileTraceAnalyzer(Session, *FileActivityProvider)); +} + +void FLoadTimeProfilerModule::GetLoggers(TArray& OutLoggers) +{ + OutLoggers.Add(TEXT("LoadTime")); + OutLoggers.Add(TEXT("PlatformFile")); +} + +const ILoadTimeProfilerProvider* ReadLoadTimeProfilerProvider(const IAnalysisSession& Session) +{ + return Session.ReadProvider(LoadTimeProfilerProviderName); +} + +const IFileActivityProvider* ReadFileActivityProvider(const IAnalysisSession& Session) +{ + return Session.ReadProvider(FileActivityProviderName); +} + +} diff --git a/Engine/Source/Developer/TraceServices/Private/Modules/LoadTimeProfilerModule.h b/Engine/Source/Developer/TraceServices/Private/Modules/LoadTimeProfilerModule.h new file mode 100644 index 000000000000..15cae4fd0bac --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Modules/LoadTimeProfilerModule.h @@ -0,0 +1,22 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/ModuleService.h" + +namespace Trace +{ + +class FLoadTimeProfilerModule + : public IModule +{ +public: + virtual void GetModuleInfo(FModuleInfo& OutModuleInfo) override; + virtual void OnAnalysisBegin(IAnalysisSession& Session, bool bIsEnabled, TArray& OutAnalyzers) override; + virtual void GetLoggers(TArray& OutLoggers) override; + +private: + static FName ModuleName; +}; + +} diff --git a/Engine/Source/Developer/TraceServices/Private/Modules/StatsModule.cpp b/Engine/Source/Developer/TraceServices/Private/Modules/StatsModule.cpp new file mode 100644 index 000000000000..aeafdd939f97 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Modules/StatsModule.cpp @@ -0,0 +1,40 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "StatsModule.h" +#include "Analyzers/StatsTraceAnalysis.h" +#include "AnalysisServicePrivate.h" +#include "TraceServices/ModuleService.h" +#include "Model/Counters.h" + +namespace Trace +{ + +FName FStatsModule::ModuleName("TraceModule_Stats"); + +void FStatsModule::GetModuleInfo(FModuleInfo& OutModuleInfo) +{ + OutModuleInfo.Name = ModuleName; + OutModuleInfo.DisplayName = TEXT("Stats"); +} + +static const FName CounterProviderName("CounterProvider"); + +void FStatsModule::OnAnalysisBegin(IAnalysisSession& InSession, bool bIsEnabled, TArray& OutAnalyzers) +{ + FAnalysisSession& Session = static_cast(InSession); + FCounterProvider* CounterProvider = new FCounterProvider(InSession); + InSession.AddProvider(CounterProviderName, CounterProvider); + OutAnalyzers.Add(new FStatsAnalyzer(Session, *CounterProvider)); +} + +void FStatsModule::GetLoggers(TArray& OutLoggers) +{ + OutLoggers.Add(TEXT("Stats")); +} + +const ICounterProvider* ReadCounterProvider(const IAnalysisSession& Session) +{ + return Session.ReadProvider(CounterProviderName); +} + +} diff --git a/Engine/Source/Developer/TraceServices/Private/Modules/StatsModule.h b/Engine/Source/Developer/TraceServices/Private/Modules/StatsModule.h new file mode 100644 index 000000000000..54b5c109ca47 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Modules/StatsModule.h @@ -0,0 +1,23 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/ModuleService.h" +#include "UObject/NameTypes.h" + +namespace Trace +{ + +class FStatsModule + : public IModule +{ +public: + virtual void GetModuleInfo(FModuleInfo& OutModuleInfo) override; + virtual void OnAnalysisBegin(IAnalysisSession& Session, bool bIsEnabled, TArray& OutAnalyzers) override; + virtual void GetLoggers(TArray& OutLoggers) override; + +private: + static FName ModuleName; +}; + +} diff --git a/Engine/Source/Developer/TraceServices/Private/Modules/TimingProfilerModule.cpp b/Engine/Source/Developer/TraceServices/Private/Modules/TimingProfilerModule.cpp new file mode 100644 index 000000000000..b5e310e5b2dc --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Modules/TimingProfilerModule.cpp @@ -0,0 +1,43 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TimingProfilerModule.h" +#include "Analyzers/CpuProfilerTraceAnalysis.h" +#include "Analyzers/GpuProfilerTraceAnalysis.h" +#include "AnalysisServicePrivate.h" +#include "Model/TimingProfilerPrivate.h" + +namespace Trace +{ + +FName FTimingProfilerModule::ModuleName("TraceModule_TimingProfiler"); + +void FTimingProfilerModule::GetModuleInfo(FModuleInfo& OutModuleInfo) +{ + OutModuleInfo.Name = ModuleName; + OutModuleInfo.DisplayName = TEXT("Timing"); +} + +static const FName TimingProfilerProviderName("TimingProfilerProvider"); + +void FTimingProfilerModule::OnAnalysisBegin(IAnalysisSession& InSession, bool bIsEnabled, TArray& OutAnalyzers) +{ + FAnalysisSession& Session = static_cast(InSession); + + FTimingProfilerProvider* TimingProfilerProvider = new FTimingProfilerProvider(Session); + Session.AddProvider(TimingProfilerProviderName, TimingProfilerProvider); + OutAnalyzers.Add(new FCpuProfilerAnalyzer(Session, *TimingProfilerProvider)); + OutAnalyzers.Add(new FGpuProfilerAnalyzer(Session, *TimingProfilerProvider)); +} + +void FTimingProfilerModule::GetLoggers(TArray& OutLoggers) +{ + OutLoggers.Add(TEXT("CpuProfiler")); + OutLoggers.Add(TEXT("GpuProfiler")); +} + +const ITimingProfilerProvider* ReadTimingProfilerProvider(const IAnalysisSession& Session) +{ + return Session.ReadProvider(TimingProfilerProviderName); +} + +} diff --git a/Engine/Source/Developer/TraceServices/Private/Modules/TimingProfilerModule.h b/Engine/Source/Developer/TraceServices/Private/Modules/TimingProfilerModule.h new file mode 100644 index 000000000000..2181457fc6d7 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/Modules/TimingProfilerModule.h @@ -0,0 +1,23 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/ModuleService.h" +#include "UObject/NameTypes.h" + +namespace Trace +{ + +class FTimingProfilerModule + : public IModule +{ +public: + virtual void GetModuleInfo(FModuleInfo& OutModuleInfo) override; + virtual void OnAnalysisBegin(IAnalysisSession& Session, bool bIsEnabled, TArray& OutAnalyzers) override; + virtual void GetLoggers(TArray& OutLoggers) override; + +private: + static FName ModuleName; +}; + +} diff --git a/Engine/Source/Developer/TraceServices/Private/SessionService.cpp b/Engine/Source/Developer/TraceServices/Private/SessionService.cpp new file mode 100644 index 000000000000..8563d2e5999f --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/SessionService.cpp @@ -0,0 +1,256 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "TraceServices/SessionService.h" +#include "SessionServicePrivate.h" +#include "Trace/DataStream.h" +#include "Misc/Paths.h" +#include "Misc/OutputDeviceRedirector.h" +#include "SocketSubsystem.h" +#include "Trace/ControlClient.h" +#include "IPAddress.h" +#include "AddressInfoTypes.h" + +#if PLATFORM_WINDOWS + #include "Windows/AllowWindowsPlatformTypes.h" + #include "Windows/MinWindows.h" + #include "Windows/HideWindowsPlatformTypes.h" +#endif + +namespace Trace +{ + +FSessionService::FSessionService(FModuleService& InModuleService) + : ModuleService(InModuleService) +{ + LocalSessionDirectory = FPaths::ProjectSavedDir() / TEXT("TraceSessions"); + TraceStore = Store_Create(*LocalSessionDirectory); + TraceRecorder = Recorder_Create(TraceStore.ToSharedRef()); + + FTickerDelegate TickDelegate = FTickerDelegate::CreateRaw(this, &FSessionService::Tick); + TickHandle = FTicker::GetCoreTicker().AddTicker(TickDelegate, 0.5f); +} + +FSessionService::~FSessionService() +{ + if (TickHandle.IsValid()) + { + FTicker::GetCoreTicker().RemoveTicker(TickHandle); + TickHandle.Reset(); + } +} + +bool FSessionService::StartRecorderServer() +{ + bool bOk = TraceRecorder->StartRecording(); +#if PLATFORM_WINDOWS + // Create a named event that other processes can use detect a running + // recorder and connect to it automatically + if (bOk && RecorderEvent == nullptr) + { + RecorderEvent = ::CreateEvent(nullptr, true, false, TEXT("Local\\UnrealInsightsRecorder")); + } +#endif // PLATFORM_WINDOWS + return bOk; +} + +bool FSessionService::IsRecorderServerRunning() const +{ + return (TraceRecorder == nullptr) ? false : TraceRecorder->IsRunning(); +} + +void FSessionService::StopRecorderServer() +{ +#if PLATFORM_WINDOWS + if (RecorderEvent != nullptr) + { + ::CloseHandle(RecorderEvent); + RecorderEvent = nullptr; + } +#endif // PLATFORM_WINDOWS + TraceRecorder->StopRecording(); +} + +void FSessionService::GetAvailableSessions(TArray& OutSessions) const +{ + FScopeLock Lock(&SessionsCS); + OutSessions.Reserve(OutSessions.Num() + Sessions.Num()); + for (const auto& KV : Sessions) + { + OutSessions.Add(static_cast(KV.Key)); + } +} + +void FSessionService::GetLiveSessions(TArray& OutSessions) const +{ + FScopeLock Lock(&SessionsCS); + OutSessions.Reserve(OutSessions.Num() + Sessions.Num()); + for (const auto& KV : Sessions) + { + if (KV.Value.bIsLive) + { + OutSessions.Add(static_cast(KV.Key)); + } + } +} + +bool FSessionService::GetSessionInfo(FSessionHandle SessionHandle, FSessionInfo& OutSessionInfo) const +{ + FScopeLock Lock(&SessionsCS); + const FSessionInfoInternal* FindIt = Sessions.Find(SessionHandle); + if (!FindIt) + { + return false; + } + OutSessionInfo.Uri = FindIt->Uri; + OutSessionInfo.Name = FindIt->Name; + OutSessionInfo.bIsLive = FindIt->bIsLive; + return true; +} + +Trace::IInDataStream* FSessionService::OpenSessionStream(FSessionHandle SessionHandle) +{ + return TraceStore->OpenSessionStream(SessionHandle); +} + +Trace::IInDataStream* FSessionService::OpenSessionFromFile(const TCHAR* FilePath) +{ + return Trace::DataStream_ReadFile(FilePath); +} + +void FSessionService::SetModuleEnabled(FSessionHandle SessionHandle, const FName& ModuleName, bool bState) +{ + FScopeLock Lock(&SessionsCS); + FSessionInfoInternal* FindIt = Sessions.Find(SessionHandle); + if (!FindIt) + { + return; + } + if (bState) + { + TArray Loggers; + if (!ModuleService.GetModuleLoggers(ModuleName, Loggers)) + { + return; + } + TSet& EnabledLoggers = FindIt->EnabledModuleLoggersMap.FindOrAdd(ModuleName); + for (const TCHAR* Logger : Loggers) + { + EnabledLoggers.Add(Logger); + } + if (FindIt->RecorderSessionHandle) + { + for (const FString& Logger : EnabledLoggers) + { + TraceRecorder->ToggleEvent(FindIt->RecorderSessionHandle, *Logger, true); + } + } + } + else + { + TSet* EnabledLoggers = FindIt->EnabledModuleLoggersMap.Find(ModuleName); + if (EnabledLoggers) + { + if (FindIt->RecorderSessionHandle) + { + for (const FString& Logger : *EnabledLoggers) + { + TraceRecorder->ToggleEvent(FindIt->RecorderSessionHandle, *Logger, false); + } + } + FindIt->EnabledModuleLoggersMap.Remove(ModuleName); + } + } +} + +bool FSessionService::IsModuleEnabled(Trace::FSessionHandle SessionHandle, const FName& ModuleName) const +{ + FScopeLock Lock(&SessionsCS); + const FSessionInfoInternal* FindIt = Sessions.Find(SessionHandle); + if (!FindIt) + { + return false; + } + return FindIt->EnabledModuleLoggersMap.Contains(ModuleName); +} + +bool FSessionService::ConnectSession(const TCHAR* ControlClientAddress) +{ + ISocketSubsystem* Sockets = ISocketSubsystem::Get(); + if (!Sockets) + { + return false; + } + bool bCanBindAll = false; + TSharedPtr RecorderAddr = Sockets->GetLocalHostAddr(*GLog, bCanBindAll); + if (!RecorderAddr.IsValid()) + { + return false; + } + + uint16 Port = 1985; + FString AddressString = ControlClientAddress; + const int32 LastColonIndex = AddressString.Find(":", ESearchCase::IgnoreCase, ESearchDir::FromEnd); + if (INDEX_NONE != LastColonIndex) + { + FString PortString = AddressString.RightChop(LastColonIndex + 1); + Port = FCString::Atoi(*PortString); + AddressString = AddressString.Left(LastColonIndex); + } + + TSharedPtr ClientAddr = Sockets->GetAddressFromString(AddressString); + if (!ClientAddr.IsValid() || !ClientAddr->IsValid()) + { + FAddressInfoResult GAIRequest = Sockets->GetAddressInfo(*AddressString, nullptr, EAddressInfoFlags::Default, NAME_None); + if (GAIRequest.ReturnCode != SE_NO_ERROR || GAIRequest.Results.Num() == 0) + { + return false; + } + + ClientAddr = GAIRequest.Results[0].Address; + } + + ClientAddr->SetPort(Port); + FControlClient ControlClient; + if (!ControlClient.Connect(*ClientAddr)) + { + return false; + } + ControlClient.SendConnect(*RecorderAddr->ToString(false)); + ControlClient.Disconnect(); + return true; +} + +bool FSessionService::Tick(float DeltaTime) +{ + UpdateSessions(); + return true; +} + +void FSessionService::UpdateSessions() +{ + TArray StoreSessions; + TraceStore->GetAvailableSessions(StoreSessions); + TArray RecorderSessions; + TraceRecorder->GetActiveSessions(RecorderSessions); + + FScopeLock Lock(&SessionsCS); + for (const FStoreSessionInfo& StoreSession : StoreSessions) + { + FSessionInfoInternal& Session = Sessions.FindOrAdd(StoreSession.Handle); + Session.Uri = StoreSession.Uri; + Session.Name = StoreSession.Name; + Session.bIsLive = StoreSession.bIsLive; + Session.RecorderSessionHandle = 0; + } + + for (const FRecorderSessionInfo& RecorderSession : RecorderSessions) + { + FSessionInfoInternal* FindIt = Sessions.Find(RecorderSession.StoreSessionHandle); + if (FindIt) + { + FindIt->RecorderSessionHandle = RecorderSession.Handle; + } + } +} + +} diff --git a/Engine/Source/Developer/TraceServices/Private/SessionServicePrivate.h b/Engine/Source/Developer/TraceServices/Private/SessionServicePrivate.h new file mode 100644 index 000000000000..0e90fd8b5671 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/SessionServicePrivate.h @@ -0,0 +1,57 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/SessionService.h" +#include "Misc/ScopeLock.h" +#include "Containers/Ticker.h" +#include "Trace/Recorder.h" +#include "Trace/Store.h" +#include "ModuleServicePrivate.h" + +namespace Trace +{ + +class FSessionService + : public ISessionService +{ +public: + FSessionService(FModuleService& ModuleService); + virtual ~FSessionService(); + virtual bool StartRecorderServer() override; + virtual bool IsRecorderServerRunning() const override; + virtual void StopRecorderServer() override; + virtual const TCHAR* GetLocalSessionDirectory() const override { return *LocalSessionDirectory; } + virtual void GetAvailableSessions(TArray& OutSessions) const override; + virtual void GetLiveSessions(TArray& OutSessions) const override; + virtual bool GetSessionInfo(Trace::FSessionHandle SessionHandle, Trace::FSessionInfo& OutSessionInfo) const override; + virtual Trace::IInDataStream* OpenSessionStream(Trace::FSessionHandle SessionHandle) override; + virtual Trace::IInDataStream* OpenSessionFromFile(const TCHAR* FilePath) override; + virtual void SetModuleEnabled(Trace::FSessionHandle SessionHandle, const FName& ModuleName, bool bState) override; + virtual bool IsModuleEnabled(Trace::FSessionHandle SessionHandle, const FName& ModuleName) const override; + virtual bool ConnectSession(const TCHAR* ControlClientAddress) override; + +private: + bool Tick(float DeltaTime); + void UpdateSessions(); + + struct FSessionInfoInternal + { + FRecorderSessionHandle RecorderSessionHandle; + const TCHAR* Uri; + const TCHAR* Name; + TMap> EnabledModuleLoggersMap; + bool bIsLive; + }; + + FModuleService& ModuleService; + void* RecorderEvent = nullptr; + FString LocalSessionDirectory; + TSharedPtr TraceStore; + TSharedPtr TraceRecorder; + mutable FCriticalSection SessionsCS; + TMap Sessions; + FDelegateHandle TickHandle; +}; + +} diff --git a/Engine/Source/Developer/TraceServices/Private/TraceServicesModule.cpp b/Engine/Source/Developer/TraceServices/Private/TraceServicesModule.cpp new file mode 100644 index 000000000000..fd0421ae572c --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Private/TraceServicesModule.cpp @@ -0,0 +1,82 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Modules/ModuleManager.h" +#include "TraceServices/ITraceServicesModule.h" +#include "SessionServicePrivate.h" +#include "AnalysisServicePrivate.h" +#include "ModuleServicePrivate.h" +#include "Features/IModularFeatures.h" +#include "Modules/TimingProfilerModule.h" +#include "Modules/LoadTimeProfilerModule.h" +#include "Modules/StatsModule.h" +#include "Stats/StatsTrace.h" + +class FTraceServicesModule + : public ITraceServicesModule +{ +public: + virtual TSharedPtr GetSessionService() override; + virtual TSharedPtr GetAnalysisService() override; + virtual TSharedPtr GetModuleService() override; + + virtual void StartupModule() override; + virtual void ShutdownModule() override; + +private: + TSharedPtr SessionService; + TSharedPtr AnalysisService; + TSharedPtr ModuleService; + + Trace::FTimingProfilerModule TimingProfilerModule; + Trace::FLoadTimeProfilerModule LoadTimeProfilerModule; + Trace::FStatsModule StatsModule; +}; + +TSharedPtr FTraceServicesModule::GetSessionService() +{ + if (!SessionService.IsValid()) + { + GetModuleService(); + SessionService = MakeShared(*ModuleService.Get()); + } + return SessionService; +} + +TSharedPtr FTraceServicesModule::GetAnalysisService() +{ + if (!AnalysisService.IsValid()) + { + GetModuleService(); + AnalysisService = MakeShared(*ModuleService.Get()); + } + return AnalysisService; +} + +TSharedPtr FTraceServicesModule::GetModuleService() +{ + if (!ModuleService.IsValid()) + { + ModuleService = MakeShared(); + } + return ModuleService; +} + +void FTraceServicesModule::StartupModule() +{ + IModularFeatures::Get().RegisterModularFeature(Trace::ModuleFeatureName, &TimingProfilerModule); + IModularFeatures::Get().RegisterModularFeature(Trace::ModuleFeatureName, &LoadTimeProfilerModule); +#if EXPERIMENTAL_STATSTRACE_ENABLED + IModularFeatures::Get().RegisterModularFeature(Trace::ModuleFeatureName, &StatsModule); +#endif +} + +void FTraceServicesModule::ShutdownModule() +{ +#if EXPERIMENTAL_STATSTRACE_ENABLED + IModularFeatures::Get().UnregisterModularFeature(Trace::ModuleFeatureName, &StatsModule); +#endif + IModularFeatures::Get().UnregisterModularFeature(Trace::ModuleFeatureName, &LoadTimeProfilerModule); + IModularFeatures::Get().UnregisterModularFeature(Trace::ModuleFeatureName, &TimingProfilerModule); +} + +IMPLEMENT_MODULE(FTraceServicesModule, TraceServices) diff --git a/Engine/Source/Developer/TraceServices/Public/TraceServices/AnalysisService.h b/Engine/Source/Developer/TraceServices/Public/TraceServices/AnalysisService.h new file mode 100644 index 000000000000..fc1fe0b7d9c4 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Public/TraceServices/AnalysisService.h @@ -0,0 +1,35 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "Templates/SharedPointer.h" +#include "Templates/UniquePtr.h" +#include "Delegates/Delegate.h" +#include "Trace/DataStream.h" +#include "TraceServices/Model/AnalysisSession.h" +#include "TraceServices/Model/Log.h" +#include "TraceServices/Model/Bookmarks.h" +#include "TraceServices/Model/Frames.h" +#include "TraceServices/Model/Threads.h" +#include "TraceServices/Model/TimingProfiler.h" +#include "TraceServices/Model/LoadTimeProfiler.h" +#include "TraceServices/Model/Stats.h" + +namespace Trace +{ + +class IAnalysisService +{ +public: + virtual TSharedPtr Analyze(const TCHAR* SessionName, TUniquePtr&& DataStream) = 0; + virtual TSharedPtr StartAnalysis(const TCHAR* SessionName, TUniquePtr&& DataStream) = 0; + + DECLARE_EVENT_OneParam(IAnalysisService, FAnalysisStartedEvent, TSharedRef) + virtual FAnalysisStartedEvent& OnAnalysisStarted() = 0; + + DECLARE_EVENT_OneParam(IAnalysisService, FAnalysisFinishedEvent, TSharedRef) + virtual FAnalysisFinishedEvent& OnAnalysisFinished() = 0; +}; + +} diff --git a/Engine/Source/Developer/TraceServices/Public/TraceServices/Containers/Allocators.h b/Engine/Source/Developer/TraceServices/Public/TraceServices/Containers/Allocators.h new file mode 100644 index 000000000000..3f7460609c70 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Public/TraceServices/Containers/Allocators.h @@ -0,0 +1,15 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +namespace Trace +{ + +class ILinearAllocator +{ +public: + virtual ~ILinearAllocator() = default; + virtual void* Allocate(uint64 Size) = 0; +}; + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Public/TraceServices/Containers/Tables.h b/Engine/Source/Developer/TraceServices/Public/TraceServices/Containers/Tables.h new file mode 100644 index 000000000000..1bdea5bbc879 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Public/TraceServices/Containers/Tables.h @@ -0,0 +1,67 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +namespace Trace +{ + +enum ETableColumnType +{ + TableColumnType_Invalid, + TableColumnType_Bool, + TableColumnType_Int, + TableColumnType_Float, + TableColumnType_Double, + TableColumnType_CString, +}; + +class ITableLayout +{ +public: + virtual ~ITableLayout() = default; + virtual uint8 GetColumnCount() const = 0; + virtual const TCHAR* GetColumnName(uint8 ColumnIndex) const = 0; + virtual ETableColumnType GetColumnType(uint8 ColumnIndex) const = 0; +}; + +class IUntypedTableReader +{ +public: + virtual ~IUntypedTableReader() = default; + virtual bool IsValid() const = 0; + virtual void NextRow() = 0; + virtual void SetRowIndex(uint64 RowIndex) = 0; + virtual bool GetValueBool(uint8 ColumnIndex) const = 0; + virtual int64 GetValueInt(uint8 ColumnIndex) const = 0; + virtual float GetValueFloat(uint8 ColumnIndex) const = 0; + virtual double GetValueDouble(uint8 ColumnIndex) const = 0; + virtual const TCHAR* GetValueCString(uint8 ColumnIndex) const = 0; +}; + +template +class ITableReader + : public IUntypedTableReader +{ +public: + virtual const RowType* GetCurrentRow() const = 0; +}; + +class IUntypedTable +{ +public: + virtual ~IUntypedTable() = default; + virtual const ITableLayout& GetLayout() const = 0; + virtual uint64 GetRowCount() const = 0; + virtual IUntypedTableReader* CreateReader() const = 0; +}; + +template +class ITable + : public IUntypedTable +{ +public: + virtual ~ITable() = default; + virtual ITableReader* CreateReader() const = 0; +}; + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Public/TraceServices/Containers/Timelines.h b/Engine/Source/Developer/TraceServices/Public/TraceServices/Containers/Timelines.h new file mode 100644 index 000000000000..41e47af31967 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Public/TraceServices/Containers/Timelines.h @@ -0,0 +1,40 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Templates/Function.h" + +namespace Trace +{ + +template +class ITimeline +{ +public: + typedef InEventType EventType; + + virtual ~ITimeline() = default; + virtual uint64 GetModCount() const = 0; + virtual uint64 GetEventCount() const = 0; + virtual void EnumerateEventsDownSampled(double IntervalStart, double IntervalEnd, double Resolution, TFunctionRef Callback) const = 0; + virtual void EnumerateEventsDownSampled(double IntervalStart, double IntervalEnd, double Resolution, TFunctionRef Callback) const = 0; + virtual void EnumerateEvents(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const = 0; + virtual void EnumerateEvents(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const = 0; +}; + +struct FAggregatedTimingStats +{ + uint64 InstanceCount = 0; + double TotalInclusiveTime = 0.0; + double MinInclusiveTime = DBL_MAX; + double MaxInclusiveTime = -DBL_MAX; + double AverageInclusiveTime = 0.0; + double MedianInclusiveTime = 0.0; + double TotalExclusiveTime = 0.0; + double MinExclusiveTime = DBL_MAX; + double MaxExclusiveTime = -DBL_MAX; + double AverageExclusiveTime = 0.0; + double MedianExclusiveTime = 0.0; +}; + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Public/TraceServices/ITraceServicesModule.h b/Engine/Source/Developer/TraceServices/Public/TraceServices/ITraceServicesModule.h new file mode 100644 index 000000000000..bde09fa3a592 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Public/TraceServices/ITraceServicesModule.h @@ -0,0 +1,27 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Trace/Store.h" +#include "Modules/ModuleInterface.h" + +namespace Trace +{ + +class IAnalysisService; +class ISessionService; +class IModuleService; + +} + +class ITraceServicesModule + : public IModuleInterface +{ +public: + virtual TSharedPtr GetSessionService() = 0; + virtual TSharedPtr GetAnalysisService() = 0; + virtual TSharedPtr GetModuleService() = 0; + + virtual ~ITraceServicesModule() = default; +}; diff --git a/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/AnalysisSession.h b/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/AnalysisSession.h new file mode 100644 index 000000000000..385cabb1a3ef --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/AnalysisSession.h @@ -0,0 +1,85 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" + +class FName; + +namespace Trace +{ + +class ILinearAllocator; + +class IProvider +{ +public: + virtual ~IProvider() = default; +}; + +class IAnalysisSession +{ +public: + virtual ~IAnalysisSession() = default; + + virtual const TCHAR* GetName() const = 0; + virtual bool IsAnalysisComplete() const = 0; + virtual double GetDurationSeconds() const = 0; + virtual void UpdateDurationSeconds(double Duration) = 0; + + virtual void BeginRead() const = 0; + virtual void EndRead() const = 0; + virtual void ReadAccessCheck() const = 0; + + virtual void BeginEdit() = 0; + virtual void EndEdit() = 0; + virtual void WriteAccessCheck() = 0; + + virtual ILinearAllocator& GetLinearAllocator() = 0; + virtual const TCHAR* StoreString(const TCHAR* String) = 0; + + virtual void AddProvider(const FName& Name, IProvider* Provider) = 0; + template + const ProviderType* ReadProvider(const FName& Name) const + { + return static_cast(ReadProviderPrivate(Name)); + } + +private: + virtual const IProvider* ReadProviderPrivate(const FName& Name) const = 0; +}; + +struct FAnalysisSessionReadScope +{ + FAnalysisSessionReadScope(const IAnalysisSession& InAnalysisSession) + : AnalysisSession(InAnalysisSession) + { + AnalysisSession.BeginRead(); + } + + ~FAnalysisSessionReadScope() + { + AnalysisSession.EndRead(); + } + +private: + const IAnalysisSession& AnalysisSession; +}; + +struct FAnalysisSessionEditScope +{ + FAnalysisSessionEditScope(IAnalysisSession& InAnalysisSession) + : AnalysisSession(InAnalysisSession) + { + AnalysisSession.BeginEdit(); + } + + ~FAnalysisSessionEditScope() + { + AnalysisSession.EndEdit(); + } + + IAnalysisSession& AnalysisSession; +}; + +} diff --git a/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/Bookmarks.h b/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/Bookmarks.h new file mode 100644 index 000000000000..9197cc7f1c48 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/Bookmarks.h @@ -0,0 +1,28 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/Model/AnalysisSession.h" +#include "Templates/Function.h" + +namespace Trace +{ + +struct FBookmark +{ + double Time; + const TCHAR* Text; +}; + +class IBookmarkProvider + : public IProvider +{ +public: + virtual ~IBookmarkProvider() = default; + virtual uint64 GetBookmarkCount() const = 0; + virtual void EnumerateBookmarks(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const = 0; +}; + +TRACESERVICES_API const IBookmarkProvider& ReadBookmarkProvider(const IAnalysisSession& Session); + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/Frames.h b/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/Frames.h new file mode 100644 index 000000000000..40e2c0fed1b9 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/Frames.h @@ -0,0 +1,30 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/Model/AnalysisSession.h" +#include "ProfilingDebugging/MiscTrace.h" +#include "Templates/Function.h" + +namespace Trace +{ + +struct FFrame +{ + uint64 Index; + double StartTime; + double EndTime; +}; + +class IFrameProvider + : public IProvider +{ +public: + virtual ~IFrameProvider() = default; + virtual uint64 GetFrameCount(ETraceFrameType FrameType) const = 0; + virtual void EnumerateFrames(ETraceFrameType FrameType, uint64 Start, uint64 End, TFunctionRef Callback) const = 0; +}; + +TRACESERVICES_API const IFrameProvider& ReadFrameProvider(const IAnalysisSession& Session); + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/LoadTimeProfiler.h b/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/LoadTimeProfiler.h new file mode 100644 index 000000000000..33c0cb9da931 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/LoadTimeProfiler.h @@ -0,0 +1,117 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/Model/AnalysisSession.h" +#include "Serialization/LoadTimeTrace.h" +#include "TraceServices/Containers/Timelines.h" +#include "TraceServices/Containers/Tables.h" +#include "Containers/Array.h" + +namespace Trace +{ + +struct FFileInfo +{ + uint32 Id; + const TCHAR* Path; +}; + +enum EFileActivityType +{ + FileActivityType_Open, + FileActivityType_Close, + FileActivityType_Read, + FileActivityType_Write, + + FileActivityType_Count, +}; + +struct FFileActivity +{ + uint64 Offset; + uint64 Size; + EFileActivityType ActivityType; + bool Failed; +}; + +class IFileActivityProvider + : public IProvider +{ +public: + typedef ITimeline Timeline; + + virtual ~IFileActivityProvider() = default; + virtual void EnumerateFileActivity(TFunctionRef Callback) const = 0; +}; + +struct FPackageSummaryInfo +{ + uint32 TotalHeaderSize = 0; + uint32 NameCount = 0; + uint32 ImportCount = 0; + uint32 ExportCount = 0; +}; + +struct FClassInfo +{ + const TCHAR* Name; +}; + +struct FPackageExportInfo +{ + uint32 Id; + const FClassInfo* Class = nullptr; + uint64 SerialOffset = 0; + uint64 SerialSize = 0; + bool IsAsset = false; +}; + +struct FPackageInfo +{ + uint32 Id; + const TCHAR* Name = nullptr; + FPackageSummaryInfo Summary; + TArray Exports; +}; + +struct FLoadTimeProfilerCpuEvent +{ + const FPackageInfo* Package = nullptr; + const FPackageExportInfo* Export = nullptr; + ELoadTimeProfilerPackageEventType PackageEventType = LoadTimeProfilerPackageEventType_None; + ELoadTimeProfilerObjectEventType ExportEventType = LoadTimeProfilerObjectEventType_None; +}; + +struct FLoadTimeProfilerAggregatedStats +{ + const TCHAR* Name; + uint64 Count; + double Total; + double Min; + double Max; + double Average; + double Median; +}; + +class ILoadTimeProfilerProvider + : public IProvider +{ +public: + typedef ITimeline CpuTimeline; + + virtual ~ILoadTimeProfilerProvider() = default; + virtual uint64 GetPackageCount() const = 0; + virtual void EnumeratePackages(TFunctionRef Callback) const = 0; + virtual uint32 GetMainThreadId() const = 0; + virtual void ReadMainThreadCpuTimeline(TFunctionRef Callback) const = 0; + virtual uint32 GetAsyncLoadingThreadId() const = 0; + virtual void ReadAsyncLoadingThreadCpuTimeline(TFunctionRef Callback) const = 0; + virtual ITable* CreateEventAggregation(double IntervalStart, double IntervalEnd) const = 0; + virtual ITable* CreateObjectTypeAggregation(double IntervalStart, double IntervalEnd) const = 0; +}; + +TRACESERVICES_API const ILoadTimeProfilerProvider* ReadLoadTimeProfilerProvider(const IAnalysisSession& Session); +TRACESERVICES_API const IFileActivityProvider* ReadFileActivityProvider(const IAnalysisSession& Session); + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/Log.h b/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/Log.h new file mode 100644 index 000000000000..a9b5742c5a88 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/Log.h @@ -0,0 +1,46 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "TraceServices/Model/AnalysisSession.h" +#include "TraceServices/Containers/Tables.h" +#include "Templates/Function.h" + +namespace Trace +{ + +struct FLogCategory +{ + const TCHAR* Name = nullptr; + ELogVerbosity::Type DefaultVerbosity; +}; + +struct FLogMessage +{ + uint64 Index; + double Time; + const FLogCategory* Category = nullptr; + const TCHAR* File = nullptr; + const TCHAR* Message = nullptr; + int32 Line; + ELogVerbosity::Type Verbosity; +}; + +class ILogProvider + : public IProvider +{ +public: + virtual ~ILogProvider() = default; + virtual uint64 GetMessageCount() const = 0; + virtual void EnumerateMessages(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const = 0; + virtual void EnumerateMessagesByIndex(uint64 Start, uint64 End, TFunctionRef Callback) const = 0; + virtual bool ReadMessage(uint64 Index, TFunctionRef Callback) const = 0; + virtual uint64 GetCategoryCount() const = 0; + virtual void EnumerateCategories(TFunctionRef Callback) const = 0; + virtual const IUntypedTable& GetMessagesTable() const = 0; +}; + +TRACESERVICES_API const ILogProvider& ReadLogProvider(const IAnalysisSession& Session); + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/Stats.h b/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/Stats.h new file mode 100644 index 000000000000..f94bc4df7f23 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/Stats.h @@ -0,0 +1,41 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/Model/AnalysisSession.h" + +namespace Trace +{ + +enum ECounterDisplayHint +{ + CounterDisplayHint_None, + CounterDisplayHint_Memory, + CounterDisplayHint_FloatingPoint, +}; + +class ICounter +{ +public: + virtual ~ICounter() = default; + + virtual const TCHAR* GetName() const = 0; + virtual uint32 GetId() const = 0; + virtual ECounterDisplayHint GetDisplayHint() const = 0; + virtual void EnumerateValues(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const = 0; + virtual void EnumerateFloatValues(double IntervalStart, double IntervalEnd, TFunctionRef Callback) const = 0; +}; + +class ICounterProvider + : public IProvider +{ +public: + static const FName ProviderName; + virtual ~ICounterProvider() = default; + virtual uint64 GetCounterCount() const = 0; + virtual void EnumerateCounters(TFunctionRef Callback) const = 0; +}; + +TRACESERVICES_API const ICounterProvider* ReadCounterProvider(const IAnalysisSession& Session); + +} diff --git a/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/Threads.h b/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/Threads.h new file mode 100644 index 000000000000..fd427c40cefb --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/Threads.h @@ -0,0 +1,29 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/Model/AnalysisSession.h" +#include "Templates/Function.h" + +namespace Trace +{ + +struct FThreadInfo +{ + uint32 Id; + const TCHAR* Name; + const TCHAR* GroupName; +}; + +class IThreadProvider + : public IProvider +{ +public: + virtual ~IThreadProvider() = default; + virtual uint64 GetModCount() const = 0; + virtual void EnumerateThreads(TFunctionRef Callback) const = 0; +}; + +TRACESERVICES_API const IThreadProvider& ReadThreadProvider(const IAnalysisSession& Session); + +} \ No newline at end of file diff --git a/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/TimingProfiler.h b/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/TimingProfiler.h new file mode 100644 index 000000000000..93fdf9889e42 --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Public/TraceServices/Model/TimingProfiler.h @@ -0,0 +1,49 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TraceServices/Model/AnalysisSession.h" +#include "TraceServices/Containers/Timelines.h" +#include "TraceServices/Containers/Tables.h" + +namespace Trace +{ + +struct FTimingProfilerTimer +{ + const TCHAR* Name; + uint32 Id; + uint32 NameHash; + bool IsGpuTimer; +}; + +struct FTimingProfilerEvent +{ + uint32 TimerIndex; +}; + +struct FTimingProfilerAggregatedStats + : public FAggregatedTimingStats +{ + const FTimingProfilerTimer* Timer = nullptr; +}; + +class ITimingProfilerProvider + : public IProvider +{ +public: + typedef ITimeline Timeline; + + virtual ~ITimingProfilerProvider() = default; + virtual bool GetCpuThreadTimelineIndex(uint32 ThreadId, uint32& OutTimelineIndex) const = 0; + virtual bool GetGpuTimelineIndex(uint32& OutTimelineIndex) const = 0; + virtual bool ReadTimeline(uint32 Index, TFunctionRef Callback) const = 0; + virtual uint64 GetTimelineCount() const = 0; + virtual void EnumerateTimelines(TFunctionRef Callback) const = 0; + virtual void ReadTimers(TFunctionRef Callback) const = 0; + virtual ITable* CreateAggregation(double IntervalStart, double IntervalEnd, TFunctionRef CpuThreadFilter, bool IncludeGpu) const = 0; +}; + +TRACESERVICES_API const ITimingProfilerProvider* ReadTimingProfilerProvider(const IAnalysisSession& Session); + +} diff --git a/Engine/Source/Developer/TraceServices/Public/TraceServices/ModuleService.h b/Engine/Source/Developer/TraceServices/Public/TraceServices/ModuleService.h new file mode 100644 index 000000000000..d01965c16add --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Public/TraceServices/ModuleService.h @@ -0,0 +1,40 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Features/IModularFeature.h" + +class FName; + +namespace Trace +{ + +class IAnalyzer; +class IAnalysisSession; +extern const FName ModuleFeatureName; + +struct FModuleInfo +{ + FName Name; + const TCHAR* DisplayName = nullptr; +}; + +class IModule + : public IModularFeature +{ +public: + virtual void GetModuleInfo(FModuleInfo& OutModuleInfo) = 0; + virtual void OnAnalysisBegin(IAnalysisSession& Session, bool bIsEnabled, TArray& OutAnalyzers) = 0; + virtual void GetLoggers(TArray& OutLoggers) = 0; +}; + +class IModuleService +{ +public: + virtual ~IModuleService() = default; + virtual void GetAvailableModules(TArray& OutModules) = 0; + virtual void SetModuleEnabled(const FName& ModuleName, bool bEnabled) = 0; +}; + +} diff --git a/Engine/Source/Developer/TraceServices/Public/TraceServices/SessionService.h b/Engine/Source/Developer/TraceServices/Public/TraceServices/SessionService.h new file mode 100644 index 000000000000..f45dde4a514e --- /dev/null +++ b/Engine/Source/Developer/TraceServices/Public/TraceServices/SessionService.h @@ -0,0 +1,40 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Templates/SharedPointer.h" +#include "Trace/Store.h" +#include "Trace/DataStream.h" + +namespace Trace +{ + +typedef uint64 FSessionHandle; + +struct FSessionInfo +{ + const TCHAR* Uri; + const TCHAR* Name; + bool bIsLive; +}; + +class ISessionService +{ +public: + virtual ~ISessionService() = default; + virtual bool StartRecorderServer() = 0; + virtual bool IsRecorderServerRunning() const = 0; + virtual void StopRecorderServer() = 0; + virtual const TCHAR* GetLocalSessionDirectory() const = 0; + virtual void GetAvailableSessions(TArray& OutSessions) const = 0; + virtual void GetLiveSessions(TArray& OutSessions) const = 0; + virtual bool GetSessionInfo(FSessionHandle SessionHandle, FSessionInfo& OutSessionInfo) const = 0; + virtual Trace::IInDataStream* OpenSessionStream(FSessionHandle SessionHandle) = 0; + virtual Trace::IInDataStream* OpenSessionFromFile(const TCHAR* FilePath) = 0; + virtual void SetModuleEnabled(Trace::FSessionHandle SessionHandle, const FName& ModuleName, bool bState) = 0; + virtual bool IsModuleEnabled(Trace::FSessionHandle SessionHandle, const FName& ModuleName) const = 0; + virtual bool ConnectSession(const TCHAR* ControlClientAddress) = 0; +}; + +} diff --git a/Engine/Source/Developer/TraceServices/TraceServices.Build.cs b/Engine/Source/Developer/TraceServices/TraceServices.Build.cs new file mode 100644 index 000000000000..ad3b69b47ddf --- /dev/null +++ b/Engine/Source/Developer/TraceServices/TraceServices.Build.cs @@ -0,0 +1,18 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class TraceServices : ModuleRules +{ + public TraceServices(ReadOnlyTargetRules Target) : base(Target) + { + PublicDependencyModuleNames.AddRange( + new string[] { + "Core", + "CoreUObject", + "Sockets", + "TraceAnalysis", + }); + + } +} diff --git a/Engine/Source/Developer/Windows/LiveCoding/Private/External/LC_ClientCommandActions.cpp b/Engine/Source/Developer/Windows/LiveCoding/Private/External/LC_ClientCommandActions.cpp index 4090f7a1724f..f877d386b448 100644 --- a/Engine/Source/Developer/Windows/LiveCoding/Private/External/LC_ClientCommandActions.cpp +++ b/Engine/Source/Developer/Windows/LiveCoding/Private/External/LC_ClientCommandActions.cpp @@ -96,8 +96,7 @@ bool actions::CallHooks::Execute(const CommandType* command, const DuplexPipe* p // BEGIN EPIC MOD - Support for UE4 debug visualizers -struct FNameEntry; -extern FNameEntry*** GFNameTableForDebuggerVisualizers_MT; +extern uint8** GNameBlocksDebug; class FChunkedFixedUObjectArray; extern FChunkedFixedUObjectArray*& GObjectArrayForDebugVisualizers; @@ -112,12 +111,12 @@ bool actions::LoadPatch::Execute(const CommandType* command, const DuplexPipe* p // BEGIN EPIC MOD - Support for UE4 debug visualizers if (module != nullptr) { - typedef void InitNatvisHelpersFunc(FNameEntry*** NameTable, FChunkedFixedUObjectArray* ObjectArray); + typedef void InitNatvisHelpersFunc(uint8** NameTable, FChunkedFixedUObjectArray* ObjectArray); InitNatvisHelpersFunc* InitNatvisHelpers = (InitNatvisHelpersFunc*)(void*)GetProcAddress(module, "InitNatvisHelpers"); if (InitNatvisHelpers != nullptr) { - (*InitNatvisHelpers)(GFNameTableForDebuggerVisualizers_MT, GObjectArrayForDebugVisualizers); + (*InitNatvisHelpers)(GNameBlocksDebug, GObjectArrayForDebugVisualizers); } } // END EPIC MOD diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintNodeOptionalPinManager.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintNodeOptionalPinManager.cpp index c1373406e548..c02913cecee1 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintNodeOptionalPinManager.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintNodeOptionalPinManager.cpp @@ -115,7 +115,7 @@ void FAnimBlueprintNodeOptionalPinManager::PostRemovedOldPin(FOptionalPinFromPro if (UEdGraphPin* OldPin = OldPinMap.FindRef(OldPinName)) { // Convert DefaultValue/DefaultValueObject and push back into the struct - FBlueprintEditorUtils::PropertyValueFromString_Direct(Property, OldPin->GetDefaultAsString(), PropertyAddress); + FBlueprintEditorUtils::PropertyValueFromString_Direct(Property, OldPin->GetDefaultAsString(), PropertyAddress, OldPin->GetOwningNodeUnchecked()); } } } diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimGraphCommands.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimGraphCommands.cpp index 16fb80b9d051..4326930a950b 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimGraphCommands.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimGraphCommands.cpp @@ -7,6 +7,7 @@ void FAnimGraphCommands::RegisterCommands() { UI_COMMAND(TogglePoseWatch, "Toggle Pose Watch", "Toggle Pose Watching on this node", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND( ToggleHideUnrelatedNodes, "Hide Unrelated", "Toggles automatically hiding nodes which are unrelated to the selected nodes.", EUserInterfaceActionType::ToggleButton, FInputChord() ); } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_BlendListByEnum.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_BlendListByEnum.cpp index 8c0a71ce7ca4..08fb60e84c1a 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_BlendListByEnum.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_BlendListByEnum.cpp @@ -95,7 +95,7 @@ void UAnimGraphNode_BlendListByEnum::GetContextMenuActions(const FGraphNodeConte if (ExposedEnumIndex != INDEX_NONE) { // Offer to remove this specific pin - FUIAction Action = FUIAction( FExecuteAction::CreateUObject( this, &UAnimGraphNode_BlendListByEnum::RemovePinFromBlendList, const_cast(Context.Pin)) ); + FUIAction Action = FUIAction( FExecuteAction::CreateUObject( const_cast(this), &UAnimGraphNode_BlendListByEnum::RemovePinFromBlendList, const_cast(Context.Pin)) ); Context.MenuBuilder->AddMenuEntry( LOCTEXT("RemovePose", "Remove Pose"), FText::GetEmpty(), FSlateIcon(), Action ); } } @@ -117,14 +117,14 @@ void UAnimGraphNode_BlendListByEnum::GetContextMenuActions(const FGraphNodeConte bAddedHeader = true; Context.MenuBuilder->BeginSection("AnimGraphNodeAddElementPin", LOCTEXT("ExposeHeader", "Add pin for element")); { - FUIAction Action = FUIAction( FExecuteAction::CreateUObject( this, &UAnimGraphNode_BlendListByEnum::ExposeEnumElementAsPin, ElementName) ); + FUIAction Action = FUIAction( FExecuteAction::CreateUObject( const_cast(this), &UAnimGraphNode_BlendListByEnum::ExposeEnumElementAsPin, ElementName) ); Context.MenuBuilder->AddMenuEntry(PrettyElementName, PrettyElementName, FSlateIcon(), Action); } Context.MenuBuilder->EndSection(); } else { - FUIAction Action = FUIAction( FExecuteAction::CreateUObject( this, &UAnimGraphNode_BlendListByEnum::ExposeEnumElementAsPin, ElementName) ); + FUIAction Action = FUIAction( FExecuteAction::CreateUObject( const_cast(this), &UAnimGraphNode_BlendListByEnum::ExposeEnumElementAsPin, ElementName) ); Context.MenuBuilder->AddMenuEntry(PrettyElementName, PrettyElementName, FSlateIcon(), Action); } } diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_ModifyCurve.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_ModifyCurve.cpp index 9e41656cc176..eca1dc1a33dd 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_ModifyCurve.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_ModifyCurve.cpp @@ -48,7 +48,7 @@ TArray UAnimGraphNode_ModifyCurve::GetCurvesToAdd() const CurvesToAdd.RemoveSingleSwap(ExistingCurveName, false); } - CurvesToAdd.Sort(); + CurvesToAdd.Sort(FNameLexicalLess()); } return CurvesToAdd; @@ -59,7 +59,7 @@ void UAnimGraphNode_ModifyCurve::GetAddCurveMenuActions(FMenuBuilder& MenuBuilde TArray CurvesToAdd = GetCurvesToAdd(); for (FName CurveName : CurvesToAdd) { - FUIAction Action = FUIAction(FExecuteAction::CreateUObject(this, &UAnimGraphNode_ModifyCurve::AddCurvePin, CurveName)); + FUIAction Action = FUIAction(FExecuteAction::CreateUObject(const_cast(this), &UAnimGraphNode_ModifyCurve::AddCurvePin, CurveName)); MenuBuilder.AddMenuEntry(FText::FromName(CurveName), FText::GetEmpty(), FSlateIcon(), Action); } } @@ -68,7 +68,7 @@ void UAnimGraphNode_ModifyCurve::GetRemoveCurveMenuActions(FMenuBuilder& MenuBui { for (FName CurveName : Node.CurveNames) { - FUIAction Action = FUIAction(FExecuteAction::CreateUObject(this, &UAnimGraphNode_ModifyCurve::RemoveCurvePin, CurveName)); + FUIAction Action = FUIAction(FExecuteAction::CreateUObject(const_cast(this), &UAnimGraphNode_ModifyCurve::RemoveCurvePin, CurveName)); MenuBuilder.AddMenuEntry(FText::FromName(CurveName), FText::GetEmpty(), FSlateIcon(), Action); } } @@ -92,7 +92,7 @@ void UAnimGraphNode_ModifyCurve::GetContextMenuActions(const FGraphNodeContextMe if (PinPropertyName == GET_MEMBER_NAME_CHECKED(FAnimNode_ModifyCurve, CurveValues) && Context.Pin->Direction == EGPD_Input) { FString PinName = Context.Pin->PinFriendlyName.ToString(); - FUIAction Action = FUIAction( FExecuteAction::CreateUObject(this, &UAnimGraphNode_ModifyCurve::RemoveCurvePin, FName(*PinName)) ); + FUIAction Action = FUIAction( FExecuteAction::CreateUObject(const_cast(this), &UAnimGraphNode_ModifyCurve::RemoveCurvePin, FName(*PinName)) ); FText RemovePinLabelText = FText::Format(LOCTEXT("RemoveThisPin", "Remove This Curve Pin: {0}"), FText::FromString(PinName)); Context.MenuBuilder->AddMenuEntry(RemovePinLabelText, LOCTEXT("RemoveThisPinTooltip", "Remove this curve pin from this node"), FSlateIcon(), Action); } diff --git a/Engine/Source/Editor/AnimGraph/Private/K2Node_AnimGetter.cpp b/Engine/Source/Editor/AnimGraph/Private/K2Node_AnimGetter.cpp index cc88b341ae22..6c767aa6a72c 100644 --- a/Engine/Source/Editor/AnimGraph/Private/K2Node_AnimGetter.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/K2Node_AnimGetter.cpp @@ -135,7 +135,7 @@ void UK2Node_AnimGetter::GetMenuActions(FBlueprintActionDatabaseRegistrar& Actio Params.SourceNode = AssetNode; Params.CachedTitle = Title; - UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(UK2Node_AnimGetter::StaticClass(), /*AssetNode->GetGraph()*/nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateUObject(this, &UK2Node_AnimGetter::PostSpawnNodeSetup, Params)); + UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(UK2Node_AnimGetter::StaticClass(), /*AssetNode->GetGraph()*/nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateUObject(const_cast(this), &UK2Node_AnimGetter::PostSpawnNodeSetup, Params)); Spawner->DynamicUiSignatureGetter = UBlueprintNodeSpawner::FUiSpecOverrideDelegate::CreateStatic(UiSpecOverride, Title); ActionRegistrar.AddBlueprintAction(AnimBlueprint, Spawner); } @@ -161,7 +161,7 @@ void UK2Node_AnimGetter::GetMenuActions(FBlueprintActionDatabaseRegistrar& Actio Params.SourceStateNode = StateNode; Params.CachedTitle = Title; - UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(UK2Node_AnimGetter::StaticClass(), /*StateNode->GetGraph()*/nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateUObject(this, &UK2Node_AnimGetter::PostSpawnNodeSetup, Params)); + UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(UK2Node_AnimGetter::StaticClass(), /*StateNode->GetGraph()*/nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateUObject(const_cast(this), &UK2Node_AnimGetter::PostSpawnNodeSetup, Params)); Spawner->DynamicUiSignatureGetter = UBlueprintNodeSpawner::FUiSpecOverrideDelegate::CreateStatic(UiSpecOverride, Title); ActionRegistrar.AddBlueprintAction(AnimBlueprint, Spawner); } @@ -183,7 +183,7 @@ void UK2Node_AnimGetter::GetMenuActions(FBlueprintActionDatabaseRegistrar& Actio Params.SourceStateNode = TransitionNode; Params.CachedTitle = Title; - UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(UK2Node_AnimGetter::StaticClass(), /*TransitionNode->GetGraph()*/nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateUObject(this, &UK2Node_AnimGetter::PostSpawnNodeSetup, Params)); + UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(UK2Node_AnimGetter::StaticClass(), /*TransitionNode->GetGraph()*/nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateUObject(const_cast(this), &UK2Node_AnimGetter::PostSpawnNodeSetup, Params)); Spawner->DynamicUiSignatureGetter = UBlueprintNodeSpawner::FUiSpecOverrideDelegate::CreateStatic(UiSpecOverride, Title); ActionRegistrar.AddBlueprintAction(AnimBlueprint, Spawner); } @@ -197,7 +197,7 @@ void UK2Node_AnimGetter::GetMenuActions(FBlueprintActionDatabaseRegistrar& Actio Params.SourceNode = MachineNode; Params.CachedTitle = Title; - UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(UK2Node_AnimGetter::StaticClass(), /*MachineNode*/nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateUObject(this, &UK2Node_AnimGetter::PostSpawnNodeSetup, Params)); + UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(UK2Node_AnimGetter::StaticClass(), /*MachineNode*/nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateUObject(const_cast(this), &UK2Node_AnimGetter::PostSpawnNodeSetup, Params)); Spawner->DynamicUiSignatureGetter = UBlueprintNodeSpawner::FUiSpecOverrideDelegate::CreateStatic(UiSpecOverride, Title); ActionRegistrar.AddBlueprintAction(AnimBlueprint, Spawner); } @@ -209,7 +209,7 @@ void UK2Node_AnimGetter::GetMenuActions(FBlueprintActionDatabaseRegistrar& Actio FText Title = FText::Format(LOCTEXT("NodeTitleNoNode", "{0}"), Getter->GetDisplayNameText()); Params.CachedTitle = Title; - UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(UK2Node_AnimGetter::StaticClass(), nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateUObject(this, &UK2Node_AnimGetter::PostSpawnNodeSetup, Params)); + UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(UK2Node_AnimGetter::StaticClass(), nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateUObject(const_cast(this), &UK2Node_AnimGetter::PostSpawnNodeSetup, Params)); Spawner->DynamicUiSignatureGetter = UBlueprintNodeSpawner::FUiSpecOverrideDelegate::CreateStatic(UiSpecOverride, Title); ActionRegistrar.AddBlueprintAction(AnimBlueprint, Spawner); } diff --git a/Engine/Source/Editor/AnimGraph/Private/PoseDriverDetails.cpp b/Engine/Source/Editor/AnimGraph/Private/PoseDriverDetails.cpp index aac147c0a2cd..2b784cef8930 100644 --- a/Engine/Source/Editor/AnimGraph/Private/PoseDriverDetails.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/PoseDriverDetails.cpp @@ -360,6 +360,7 @@ TSharedRef< SWidget > SPDD_TargetRow::GenerateWidgetForColumn(const FName& Colum +SWidgetSwitcher::Slot() [ SNew(SVectorInputBox) + .AllowSpin(true) .X(this, &SPDD_TargetRow::GetTranslation, BoneIndex, EAxis::X) .OnXChanged(this, &SPDD_TargetRow::SetTranslation, BoneIndex, EAxis::X) .Y(this, &SPDD_TargetRow::GetTranslation, BoneIndex, EAxis::Y) @@ -1064,7 +1065,7 @@ void FPoseDriverDetails::UpdateDrivenNameOptions() { TArray NameArray; Mapping->FillNameArray(NameArray); - NameArray.Sort(); + NameArray.Sort(FNameLexicalLess()); for (FName CurveName : NameArray) { diff --git a/Engine/Source/Editor/AnimGraph/Public/AnimGraphCommands.h b/Engine/Source/Editor/AnimGraph/Public/AnimGraphCommands.h index 51a841e0389b..a1db5e9ee6f3 100644 --- a/Engine/Source/Editor/AnimGraph/Public/AnimGraphCommands.h +++ b/Engine/Source/Editor/AnimGraph/Public/AnimGraphCommands.h @@ -21,4 +21,7 @@ public: // Toggle pose watching for a given node TSharedPtr TogglePoseWatch; + + // Toggle hiding nodes which are not related to the selected nodes + TSharedPtr< FUICommandInfo > ToggleHideUnrelatedNodes; }; diff --git a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationBlueprintEditor.cpp b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationBlueprintEditor.cpp index 35024b0a2bee..be32b067ca32 100644 --- a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationBlueprintEditor.cpp +++ b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationBlueprintEditor.cpp @@ -68,6 +68,13 @@ #include "EditorFontGlyphs.h" #include "AnimationBlueprintInterfaceEditorMode.h" +// Hide related nodes feature +#include "Preferences/AnimationBlueprintEditorOptions.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "EdGraphNode_Comment.h" +#include "AnimStateNodeBase.h" +#include "AnimStateEntryNode.h" + #define LOCTEXT_NAMESPACE "AnimationBlueprintEditor" const FName AnimationBlueprintEditorAppName(TEXT("AnimationBlueprintEditorApp")); @@ -166,6 +173,8 @@ FAnimationBlueprintEditor::~FAnimationBlueprintEditor() FReimportManager::Instance()->OnPostReimport().RemoveAll(this); // NOTE: Any tabs that we still have hanging out when destroyed will be cleaned up by FBaseToolkit's destructor + + SaveEditorSettings(); } UAnimBlueprint* FAnimationBlueprintEditor::GetAnimBlueprint() const @@ -209,6 +218,8 @@ void FAnimationBlueprintEditor::InitAnimationBlueprintEditor(const EToolkitMode: Toolbar = MakeShareable(new FBlueprintEditorToolbar(SharedThis(this))); } + LoadEditorSettings(); + GetToolkitCommands()->Append(FPlayWorldCommands::GlobalPlayWorldActions.ToSharedRef()); FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked("Persona"); @@ -347,6 +358,39 @@ void FAnimationBlueprintEditor::ExtendToolbar() } )); } + + struct Local + { + static void FillToolbar(FToolBarBuilder& ToolbarBuilder, const TSharedRef< FUICommandList > ToolkitCommands, FAnimationBlueprintEditor* BlueprintEditor) + { + ToolbarBuilder.BeginSection("Graph"); + { + ToolbarBuilder.AddToolBarButton( + FAnimGraphCommands::Get().ToggleHideUnrelatedNodes, + NAME_None, + TAttribute(), + TAttribute(), + FSlateIcon(FEditorStyle::GetStyleSetName(), "GraphEditor.ToggleHideUnrelatedNodes") + ); + ToolbarBuilder.AddComboButton( + FUIAction(), + FOnGetContent::CreateSP(BlueprintEditor, &FBlueprintEditor::MakeHideUnrelatedNodesOptionsMenu), + LOCTEXT("HideUnrelatedNodesOptions", "Hide Unrelated Nodes Options"), + LOCTEXT("HideUnrelatedNodesOptionsMenu", "Hide Unrelated Nodes options menu"), + TAttribute(), + true + ); + } + ToolbarBuilder.EndSection(); + } + }; + + ToolbarExtender->AddToolBarExtension( + "Asset", + EExtensionHook::After, + GetToolkitCommands(), + FToolBarExtensionDelegate::CreateStatic( &Local::FillToolbar, GetToolkitCommands(), this ) + ); } UBlueprint* FAnimationBlueprintEditor::GetBlueprintObj() const @@ -386,6 +430,11 @@ void FAnimationBlueprintEditor::OnGraphEditorFocused(const TSharedRefOnPinDefaultValueChanged.Add(FOnPinDefaultValueChanged::FDelegate::CreateSP(this, &FAnimationBlueprintEditor::HandlePinDefaultValueChanged)); } + + if (bHideUnrelatedNodes && GetSelectedNodes().Num() <= 0) + { + ResetAllNodesUnrelatedStates(); + } } void FAnimationBlueprintEditor::OnGraphEditorBackgrounded(const TSharedRef& InGraphEditor) @@ -405,6 +454,12 @@ void FAnimationBlueprintEditor::CreateDefaultCommands() if (GetBlueprintObj()) { FBlueprintEditor::CreateDefaultCommands(); + + ToolkitCommands->MapAction( + FAnimGraphCommands::Get().ToggleHideUnrelatedNodes, + FExecuteAction::CreateSP(this, &FBlueprintEditor::ToggleHideUnrelatedNodes), + FCanExecuteAction(), + FIsActionChecked::CreateSP(this, &FBlueprintEditor::IsToggleHideUnrelatedNodesChecked)); } else { @@ -1392,6 +1447,11 @@ void FAnimationBlueprintEditor::HandleOpenNewAsset(UObject* InNewAsset) FAssetEditorManager::Get().OpenEditorForAsset(InNewAsset); } +void FAnimationBlueprintEditor::AddReferencedObjects( FReferenceCollector& Collector ) +{ + Collector.AddReferencedObject( EditorOptions ); +} + FAnimNode_Base* FAnimationBlueprintEditor::FindAnimNode(UAnimGraphNode_Base* AnimGraphNode) const { FAnimNode_Base* AnimNode = nullptr; @@ -1440,6 +1500,29 @@ void FAnimationBlueprintEditor::OnSelectedNodesChangedImpl(const TSet(*It); + UAnimStateNodeBase* AnimGraphNodeBase = Cast(*It); + UAnimStateEntryNode* AnimStateEntryNode = Cast(*It); + if (!SeqNode && !AnimGraphNodeBase && !AnimStateEntryNode) + { + bSelectRegularNode = true; + break; + } + } + + if (bHideUnrelatedNodes && !bLockNodeFadeState) + { + ResetAllNodesUnrelatedStates(); + + if ( bSelectRegularNode ) + { + HideUnrelatedNodes(); + } + } } void FAnimationBlueprintEditor::OnPostCompile() @@ -1667,5 +1750,24 @@ void FAnimationBlueprintEditor::HandleViewportCreated(const TSharedRef(); + + if (EditorOptions->bHideUnrelatedNodes) + { + ToggleHideUnrelatedNodes(); + } +} + +void FAnimationBlueprintEditor::SaveEditorSettings() +{ + if ( EditorOptions ) + { + EditorOptions->bHideUnrelatedNodes = bHideUnrelatedNodes; + EditorOptions->SaveConfig(); + } +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationBlueprintEditor.h b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationBlueprintEditor.h index 77422cb28cb0..f917a7e7c59c 100644 --- a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationBlueprintEditor.h +++ b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationBlueprintEditor.h @@ -11,6 +11,7 @@ #include "IAnimationBlueprintEditor.h" #include "Containers/ArrayView.h" +class UAnimationBlueprintEditorOptions; class IPersonaToolkit; class IPersonaViewport; class ISkeletonTree; @@ -124,6 +125,10 @@ public: /** Get the object to be displayed in the asset properties */ UObject* HandleGetObject(); + + //~ Begin FGCObject Interface + virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + //~ End FGCObject Interface /** Handle opening a new asset from the asset browser */ void HandleOpenNewAsset(UObject* InNewAsset); @@ -269,6 +274,16 @@ private: /** Handle the viewport being created */ void HandleViewportCreated(const TSharedRef& InPersonaViewport); + /** + * Load editor settings from disk (docking state, window pos/size, option state, etc). + */ + virtual void LoadEditorSettings(); + + /** + * Saves editor settings to disk (docking state, window pos/size, option state, etc). + */ + virtual void SaveEditorSettings(); + /** The extender to pass to the level editor to extend it's window menu */ TSharedPtr MenuExtender; @@ -292,4 +307,7 @@ private: /** The last pin type we added to a graph's inputs */ FEdGraphPinType LastGraphPinType; + + /** Configuration class used to store editor settings across sessions. */ + UAnimationBlueprintEditorOptions* EditorOptions; }; diff --git a/Engine/Source/Editor/BehaviorTreeEditor/Classes/BehaviorTreeGraphNode_Composite.h b/Engine/Source/Editor/BehaviorTreeEditor/Classes/BehaviorTreeGraphNode_Composite.h index 028a2af70316..39377e89c308 100644 --- a/Engine/Source/Editor/BehaviorTreeEditor/Classes/BehaviorTreeGraphNode_Composite.h +++ b/Engine/Source/Editor/BehaviorTreeEditor/Classes/BehaviorTreeGraphNode_Composite.h @@ -8,7 +8,7 @@ #include "BehaviorTreeGraphNode_Composite.generated.h" UCLASS() -class UBehaviorTreeGraphNode_Composite : public UBehaviorTreeGraphNode +class BEHAVIORTREEEDITOR_API UBehaviorTreeGraphNode_Composite : public UBehaviorTreeGraphNode { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeDebugger.cpp b/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeDebugger.cpp index 6bf7820304e5..35f9663d2ca3 100644 --- a/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeDebugger.cpp +++ b/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeDebugger.cpp @@ -21,6 +21,7 @@ #include "BehaviorTreeGraphNode_Service.h" #include "BehaviorTree/BehaviorTree.h" #include "BehaviorTreeDelegates.h" +#include "Framework/Application/SlateApplication.h" FBehaviorTreeDebugger::FBehaviorTreeDebugger() { @@ -1018,6 +1019,11 @@ void FBehaviorTreeDebugger::OnActiveNodeChanged(const TArray& ActivePath if (bShouldPause) { + if (EditorOwner.IsValid()) + { + EditorOwner.Pin()->FocusWindow(TreeAsset); + } + PausePlaySession(); } } @@ -1027,6 +1033,13 @@ void FBehaviorTreeDebugger::StopPlaySession() if (GUnrealEd->PlayWorld) { GEditor->RequestEndPlayMap(); + + // @TODO: we need a unified flow to leave debugging mode from the different debuggers to prevent strong coupling between modules. + // Each debugger (Blueprint & BehaviorTree for now) could then take the appropriate actions to resume the session. + if (FSlateApplication::Get().InKismetDebuggingMode()) + { + FSlateApplication::Get().LeaveDebuggingMode(); + } } } @@ -1060,6 +1073,13 @@ void FBehaviorTreeDebugger::ResumePlaySession() }); if(bResumed) { + // @TODO: we need a unified flow to leave debugging mode from the different debuggers to prevent strong coupling between modules. + // Each debugger (Blueprint & BehaviorTree for now) could then take the appropriate actions to resume the session. + if (FSlateApplication::Get().InKismetDebuggingMode()) + { + FSlateApplication::Get().LeaveDebuggingMode(); + } + GUnrealEd->PlaySessionResumed(); } } diff --git a/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeEditor.cpp b/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeEditor.cpp index 8fb474730213..47979bc3de4a 100644 --- a/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeEditor.cpp +++ b/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeEditor.cpp @@ -96,19 +96,24 @@ FBehaviorTreeEditor::~FBehaviorTreeEditor() Debugger.Reset(); } +void FBehaviorTreeEditor::RefreshBlackboardViewsAssociatedObject() +{ + if (BlackboardView.IsValid()) + { + BlackboardView->SetObject(GetBlackboardData()); + } + + if (BlackboardEditor.IsValid()) + { + BlackboardEditor->SetObject(GetBlackboardData()); + } +} + void FBehaviorTreeEditor::PostUndo(bool bSuccess) { if (bSuccess) { - if(BlackboardView.IsValid()) - { - BlackboardView->SetObject(GetBlackboardData()); - } - - if(BlackboardEditor.IsValid()) - { - BlackboardEditor->SetObject(GetBlackboardData()); - } + RefreshBlackboardViewsAssociatedObject(); } FAIGraphEditor::PostUndo(bSuccess); @@ -118,15 +123,7 @@ void FBehaviorTreeEditor::PostRedo(bool bSuccess) { if (bSuccess) { - if (BlackboardView.IsValid()) - { - BlackboardView->SetObject(GetBlackboardData()); - } - - if (BlackboardEditor.IsValid()) - { - BlackboardEditor->SetObject(GetBlackboardData()); - } + RefreshBlackboardViewsAssociatedObject(); } FAIGraphEditor::PostRedo(bSuccess); @@ -141,14 +138,7 @@ void FBehaviorTreeEditor::NotifyPostChange( const FPropertyChangedEvent& Propert BlackboardData = BehaviorTree->BlackboardAsset; } - if(BlackboardView.IsValid()) - { - BlackboardView->SetObject(GetBlackboardData()); - } - if(BlackboardEditor.IsValid()) - { - BlackboardEditor->SetObject(GetBlackboardData()); - } + RefreshBlackboardViewsAssociatedObject(); } } @@ -215,7 +205,8 @@ void FBehaviorTreeEditor::InitBehaviorTreeEditor( const EToolkitMode::Type Mode, ToolbarBuilder = MakeShareable(new FBehaviorTreeEditorToolbar(SharedThis(this))); } - // if we are already editing objects, dont try to recreate the editor from scratch + // if we are already editing objects, dont try to recreate the editor from scratch but update the list of objects in edition + // ex: BehaviorTree may want to reuse an editor already opened for its associated Blackboard asset. const TArray* EditedObjects = GetObjectsCurrentlyBeingEdited(); if(EditedObjects == nullptr || EditedObjects->Num() == 0) { @@ -265,6 +256,14 @@ void FBehaviorTreeEditor::InitBehaviorTreeEditor( const EToolkitMode::Type Mode, { check(Debugger.IsValid()); Debugger->Setup(BehaviorTree, SharedThis(this)); + + for (UObject* ObjectToEdit : ObjectsToEdit) + { + if (!EditedObjects->Contains(ObjectToEdit)) + { + AddEditingObject(ObjectToEdit); + } + } } if(BehaviorTreeToEdit != nullptr) @@ -316,6 +315,7 @@ void FBehaviorTreeEditor::RestoreBehaviorTree() if(bNewGraph) { MyGraph->UpdateAsset(UBehaviorTreeGraph::ClearDebuggerFlags | UBehaviorTreeGraph::KeepRebuildCounter); + RefreshBlackboardViewsAssociatedObject(); } else { @@ -384,14 +384,7 @@ float FBehaviorTreeEditor::HandleGetDebugTimeStamp(bool bUseCurrentState) const void FBehaviorTreeEditor::HandleDebuggedBlackboardChanged(UBlackboardData* InBlackboardData) { - if(BlackboardView.IsValid()) - { - BlackboardView->SetObject(InBlackboardData); - } - if(BlackboardEditor.IsValid()) - { - BlackboardEditor->SetObject(InBlackboardData); - } + RefreshBlackboardViewsAssociatedObject(); } bool FBehaviorTreeEditor::HandleGetDisplayCurrentState() const @@ -567,6 +560,12 @@ TSharedRef FBehaviorTreeEditor::CreateGraphEditorWidget(UEdGraph* FIsActionChecked(), FIsActionButtonVisible::CreateSP( this, &FBehaviorTreeEditor::CanToggleBreakpoint ) ); + + GraphEditorCommands->MapAction( + FGraphEditorCommands::Get().CreateComment, + FExecuteAction::CreateSP(this, &FBehaviorTreeEditor::OnCreateComment), + FCanExecuteAction::CreateSP(this, &FBehaviorTreeEditor::CanCreateComment) + ); } SGraphEditor::FGraphEditorEvents InEvents; @@ -1840,4 +1839,21 @@ bool FBehaviorTreeEditor::CanCreateNewBlackboard() const return !IsDebuggerReady(); } +bool FBehaviorTreeEditor::CanCreateComment() const +{ + return (SelectedNodesCount > 0); +} + +void FBehaviorTreeEditor::OnCreateComment() +{ + if (BehaviorTree && BehaviorTree->BTGraph) + { + TSharedPtr Action = BehaviorTree->BTGraph->GetSchema()->GetCreateCommentAction(); + if (Action.IsValid()) + { + Action->PerformAction(BehaviorTree->BTGraph, nullptr, FVector2D()); + } + } +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeEditor.h b/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeEditor.h index a69baa7f1b08..e33ae5a274fc 100644 --- a/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeEditor.h +++ b/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeEditor.h @@ -85,6 +85,8 @@ public: bool CanAddBreakpoint() const; void OnRemoveBreakpoint(); bool CanRemoveBreakpoint() const; + void OnCreateComment(); + bool CanCreateComment() const; void SearchTree(); bool CanSearchTree() const; @@ -281,6 +283,9 @@ private: /** Refresh the debugger's display */ void RefreshDebugger(); + /** Push new associated Blackboard data to Blackboard views */ + void RefreshBlackboardViewsAssociatedObject(); + TSharedPtr DocumentManager; TWeakPtr GraphEditorTabFactoryPtr; diff --git a/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeGraph.cpp b/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeGraph.cpp index 40dde5bd994e..53ff8a298798 100644 --- a/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeGraph.cpp +++ b/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeGraph.cpp @@ -24,6 +24,7 @@ #include "BehaviorTree/Tasks/BTTask_Wait.h" #include "BehaviorTreeGraphNode_SimpleParallel.h" #include "BehaviorTreeGraphNode_SubtreeTask.h" +#include "BehaviorTreeDecoratorGraphNode_Decorator.h" ////////////////////////////////////////////////////////////////////////// // BehaviorTreeGraph @@ -942,27 +943,27 @@ void UBehaviorTreeGraph::CreateBTFromGraph(UBehaviorTreeGraphNode* RootEdNode) RemoveOrphanedNodes(); } -void UBehaviorTreeGraph::CollectAllNodeInstances(TSet& NodeInstance) +void UBehaviorTreeGraph::CollectAllNodeInstances(TSet& NodeInstances) { - Super::CollectAllNodeInstances(NodeInstance); + Super::CollectAllNodeInstances(NodeInstances); - for (int32 Idx = 0; Idx < Nodes.Num(); Idx++) + for (UEdGraphNode* EdGraphNode : Nodes) { - UBehaviorTreeGraphNode* MyNode = Cast(Nodes[Idx]); - if (MyNode) + UBehaviorTreeGraphNode* BTGraphNode = Cast(EdGraphNode); + if (BTGraphNode != nullptr) { - for (int32 SubIdx = 0; SubIdx < MyNode->Decorators.Num(); SubIdx++) + for (UBehaviorTreeGraphNode* BTDecoratorGraphNode : BTGraphNode->Decorators) { - UBehaviorTreeGraphNode_CompositeDecorator* SubgraphNode = Cast(MyNode->Decorators[SubIdx]); - if (SubgraphNode) + UEdGraph* EdSubGraph = BTDecoratorGraphNode->GetBoundGraph(); + if (EdSubGraph != nullptr) { - TArray DecoratorInstances; - TArray DummyOps; - SubgraphNode->CollectDecoratorData(DecoratorInstances, DummyOps); - - for (int32 DecoratorIdx = 0; DecoratorIdx < DecoratorInstances.Num(); DecoratorIdx++) + for (UEdGraphNode* SubGraphNode : EdSubGraph->Nodes) { - NodeInstance.Add(DecoratorInstances[DecoratorIdx]); + const UBehaviorTreeDecoratorGraphNode_Decorator* DecoratorNode = Cast(SubGraphNode); + if (DecoratorNode && DecoratorNode->NodeInstance) + { + NodeInstances.Add(DecoratorNode->NodeInstance); + } } } } diff --git a/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeGraphNode.cpp b/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeGraphNode.cpp index 4f672bdcf9a3..6fa9e53bdf62 100644 --- a/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeGraphNode.cpp +++ b/Engine/Source/Editor/BehaviorTreeEditor/Private/BehaviorTreeGraphNode.cpp @@ -136,14 +136,27 @@ void UBehaviorTreeGraphNode::FindDiffs(UEdGraphNode* OtherNode, FDiffResults& Re FGraphDiffControl::FNodeMatch NodeMatch; NodeMatch.NewNode = RhsSubNode; + // Do two passes, exact and soft for (UEdGraphNode* LhsSubNode : LhsSubNodes) { - if (FGraphDiffControl::IsNodeMatch(LhsSubNode, RhsSubNode, &NodeMatches)) + if (FGraphDiffControl::IsNodeMatch(LhsSubNode, RhsSubNode, true, &NodeMatches)) { NodeMatch.OldNode = LhsSubNode; break; } } + + if (NodeMatch.NewNode == nullptr) + { + for (UEdGraphNode* LhsSubNode : LhsSubNodes) + { + if (FGraphDiffControl::IsNodeMatch(LhsSubNode, RhsSubNode, false, &NodeMatches)) + { + NodeMatch.OldNode = LhsSubNode; + break; + } + } + } // if we found a corresponding node in the lhs graph, track it (so we can prevent future matches with the same nodes) if (NodeMatch.IsValid()) @@ -168,18 +181,10 @@ void UBehaviorTreeGraphNode::FindDiffs(UEdGraphNode* OtherNode, FDiffResults& Re continue; } + // There can't be a matching node in RhsGraph because it would have been found above FGraphDiffControl::FNodeMatch NodeMatch; NodeMatch.NewNode = LhsSubNode; - for (UEdGraphNode* RhsSubNode : RhsSubNodes) - { - if (FGraphDiffControl::IsNodeMatch(LhsSubNode, RhsSubNode, &NodeMatches)) - { - NodeMatch.OldNode = RhsSubNode; - break; - } - } - NodeMatch.Diff(SubtractiveDiffContext, Results); } }; diff --git a/Engine/Source/Editor/BehaviorTreeEditor/Private/DetailCustomizations/BehaviorDecoratorDetails.cpp b/Engine/Source/Editor/BehaviorTreeEditor/Private/DetailCustomizations/BehaviorDecoratorDetails.cpp index 85b2172712d4..9132e8ec4e8a 100644 --- a/Engine/Source/Editor/BehaviorTreeEditor/Private/DetailCustomizations/BehaviorDecoratorDetails.cpp +++ b/Engine/Source/Editor/BehaviorTreeEditor/Private/DetailCustomizations/BehaviorDecoratorDetails.cpp @@ -139,7 +139,7 @@ TSharedRef FBehaviorDecoratorDetails::OnGetAbortModeContent() const for (int32 i = 0; i < ModeValues.Num(); i++) { - FUIAction ItemAction( FExecuteAction::CreateSP( this, &FBehaviorDecoratorDetails::OnAbortModeChange, ModeValues[i].Int ) ); + FUIAction ItemAction( FExecuteAction::CreateSP( const_cast(this), &FBehaviorDecoratorDetails::OnAbortModeChange, ModeValues[i].Int ) ); MenuBuilder.AddMenuEntry(FText::FromString( ModeValues[i].Str ), TAttribute(), FSlateIcon(), ItemAction); } diff --git a/Engine/Source/Editor/BehaviorTreeEditor/Private/DetailCustomizations/BlackboardDecoratorDetails.cpp b/Engine/Source/Editor/BehaviorTreeEditor/Private/DetailCustomizations/BlackboardDecoratorDetails.cpp index a7564b3cc28e..3fa850670365 100644 --- a/Engine/Source/Editor/BehaviorTreeEditor/Private/DetailCustomizations/BlackboardDecoratorDetails.cpp +++ b/Engine/Source/Editor/BehaviorTreeEditor/Private/DetailCustomizations/BlackboardDecoratorDetails.cpp @@ -189,7 +189,7 @@ TSharedRef FBlackboardDecoratorDetails::OnGetEnumValueContent() const for (int32 i = 0; i < EnumPropValues.Num(); i++) { - FUIAction ItemAction( FExecuteAction::CreateSP( this, &FBlackboardDecoratorDetails::OnEnumValueComboChange, i ) ); + FUIAction ItemAction( FExecuteAction::CreateSP( const_cast(this), &FBlackboardDecoratorDetails::OnEnumValueComboChange, i ) ); MenuBuilder.AddMenuEntry( FText::FromString( EnumPropValues[i] ), TAttribute(), FSlateIcon(), ItemAction); } diff --git a/Engine/Source/Editor/BehaviorTreeEditor/Private/DetailCustomizations/BlackboardSelectorDetails.cpp b/Engine/Source/Editor/BehaviorTreeEditor/Private/DetailCustomizations/BlackboardSelectorDetails.cpp index a3e71400cdd5..0d470c51051d 100644 --- a/Engine/Source/Editor/BehaviorTreeEditor/Private/DetailCustomizations/BlackboardSelectorDetails.cpp +++ b/Engine/Source/Editor/BehaviorTreeEditor/Private/DetailCustomizations/BlackboardSelectorDetails.cpp @@ -182,7 +182,7 @@ TSharedRef FBlackboardSelectorDetails::OnGetKeyContent() const for (int32 Idx = 0; Idx < KeyValues.Num(); Idx++) { - FUIAction ItemAction( FExecuteAction::CreateSP( this, &FBlackboardSelectorDetails::OnKeyComboChange, Idx) ); + FUIAction ItemAction( FExecuteAction::CreateSP( const_cast(this), &FBlackboardSelectorDetails::OnKeyComboChange, Idx) ); MenuBuilder.AddMenuEntry( FText::FromName( KeyValues[Idx] ), TAttribute(), FSlateIcon(), ItemAction); } diff --git a/Engine/Source/Editor/BehaviorTreeEditor/Private/SBehaviorTreeBlackboardEditor.cpp b/Engine/Source/Editor/BehaviorTreeEditor/Private/SBehaviorTreeBlackboardEditor.cpp index 3c9324ec5e9e..a60d93577065 100644 --- a/Engine/Source/Editor/BehaviorTreeEditor/Private/SBehaviorTreeBlackboardEditor.cpp +++ b/Engine/Source/Editor/BehaviorTreeEditor/Private/SBehaviorTreeBlackboardEditor.cpp @@ -207,7 +207,7 @@ TSharedRef SBehaviorTreeBlackboardEditor::HandleCreateNewEntryMenu() co Options.NameTypeToDisplay = EClassViewerNameTypeToDisplay::DisplayName; Options.ClassFilter = MakeShareable( new FBlackboardEntryClassFilter ); - FOnClassPicked OnPicked( FOnClassPicked::CreateRaw( this, &SBehaviorTreeBlackboardEditor::HandleKeyClassPicked ) ); + FOnClassPicked OnPicked( FOnClassPicked::CreateRaw( const_cast(this), &SBehaviorTreeBlackboardEditor::HandleKeyClassPicked ) ); // clear the search box, just in case there's something typed in there // We need to do that since key adding code takes advantage of selection mechanics diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/EdGraphSchema_K2.h b/Engine/Source/Editor/BlueprintGraph/Classes/EdGraphSchema_K2.h index 9aa23b0a2680..ec4f844f862b 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/EdGraphSchema_K2.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/EdGraphSchema_K2.h @@ -114,6 +114,9 @@ public: // [FunctionMetadata] Indicates that the function should be drawn with this title over the function name static const FName MD_DisplayName; + // [FunctionMetadata] Indicates the display name of the return value pin + static const FName MD_ReturnDisplayName; + // [FunctionMetadata] Indicates that a particular function parameter is for internal use only, which means it will be both hidden and not connectible. static const FName MD_InternalUseParam; @@ -548,6 +551,9 @@ public: /** Returns true if the pin has a value field that can be edited inline */ bool PinDefaultValueIsEditable(const UEdGraphPin& InGraphPin) const; + /** Returns true if the pin has a custom default string format and it is not safe to use ExportText */ + bool PinHasCustomDefaultFormat(const UEdGraphPin& InGraphPin) const; + struct FCreateSplitPinNodeParams { FCreateSplitPinNodeParams(const bool bInTransient) @@ -572,7 +578,7 @@ public: UK2Node* CreateSplitPinNode(UEdGraphPin* Pin, const FCreateSplitPinNodeParams& Params) const; /** Reads in a FString and gets the values of the pin defaults for that type. This can be passed to DefaultValueSimpleValidation to validate. OwningObject can be null */ - virtual void GetPinDefaultValuesFromString(const FEdGraphPinType& PinType, UObject* OwningObject, const FString& NewValue, FString& UseDefaultValue, UObject*& UseDefaultObject, FText& UseDefaultText) const; + virtual void GetPinDefaultValuesFromString(const FEdGraphPinType& PinType, UObject* OwningObject, const FString& NewValue, FString& UseDefaultValue, UObject*& UseDefaultObject, FText& UseDefaultText, bool bPreserveTextIdentity = true) const; /** Do validation, that doesn't require a knowledge about actual pin */ virtual bool DefaultValueSimpleValidation(const FEdGraphPinType& PinType, const FName PinName, const FString& NewDefaultValue, UObject* NewDefaultObject, const FText& InText, FString* OutMsg = nullptr) const; @@ -754,7 +760,7 @@ public: * @param Function The function to find a parent function for * @return The UFunction parentfunction, if any. */ - UFunction* GetCallableParentFunction(UFunction* Function) const; + static UFunction* GetCallableParentFunction(UFunction* Function); /** Whether or not the specified actor is a valid target for bound events and literal references (in the right level, not a builder brush, etc */ bool IsActorValidForLevelScriptRefs(const AActor* TestActor, const UBlueprint* Blueprint) const; diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/EdGraphSchema_K2_Actions.h b/Engine/Source/Editor/BlueprintGraph/Classes/EdGraphSchema_K2_Actions.h index f297d914f248..e6f7aaf2e216 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/EdGraphSchema_K2_Actions.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/EdGraphSchema_K2_Actions.h @@ -335,7 +335,7 @@ struct FEdGraphSchemaAction_K2AddCallOnActor : public FEdGraphSchemaAction_K2New /** Action to add a 'comment' node to the graph */ USTRUCT() -struct FEdGraphSchemaAction_K2AddComment : public FEdGraphSchemaAction +struct BLUEPRINTGRAPH_VTABLE FEdGraphSchemaAction_K2AddComment : public FEdGraphSchemaAction { GENERATED_USTRUCT_BODY() diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node.h index 56df5952847e..cb0e4121ded6 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node.h @@ -270,6 +270,9 @@ class UK2Node : public UEdGraphNode /** Expands a node while compiling, which may add additional nodes or delete this node */ BLUEPRINTGRAPH_API virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph); + /** Clears out any cached data that needs to be regenerated after a structural blueprint change */ + BLUEPRINTGRAPH_API virtual void ClearCachedBlueprintData(UBlueprint* Blueprint) {} + /** Performs a node-specific deprecation fixup, which may delete this node and replace it with another one */ BLUEPRINTGRAPH_API virtual void ConvertDeprecatedNode(UEdGraph* Graph, bool bOnlySafeChanges) {} @@ -455,7 +458,11 @@ protected: } } - void FixupPinDefaultValues(); + /** Handle backwards compatible fixes on load */ + BLUEPRINTGRAPH_API virtual void FixupPinDefaultValues(); + + /** Fixes up structure/soft object ref pins, on both save and load */ + BLUEPRINTGRAPH_API virtual void FixupPinStringDataReferences(FArchive* SavingArchive); private: diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_AddComponent.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_AddComponent.h index b8448335364e..d04c9087ac99 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_AddComponent.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_AddComponent.h @@ -43,6 +43,7 @@ class UK2Node_AddComponent : public UK2Node_CallFunction virtual FString GetDocumentationExcerptName() const override; virtual bool IsCompatibleWithGraph(UEdGraph const* Graph) const override; virtual void ReconstructNode() override; + virtual void FindDiffs(class UEdGraphNode* OtherNode, struct FDiffResults& Results) override; //~ End UEdGraphNode Interface //~ Begin K2Node Interface diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_BaseMCDelegate.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_BaseMCDelegate.h index 10f8ec1e7b4f..829c63d0bfdc 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_BaseMCDelegate.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_BaseMCDelegate.h @@ -33,6 +33,8 @@ public: virtual bool HasExternalDependencies(TArray* OptionalOutput) const override; virtual void GetNodeAttributes( TArray>& OutNodeAttributes ) const override; virtual void AutowireNewNode(UEdGraphPin* FromPin) override; + virtual bool HasDeprecatedReference() const override; + virtual FEdGraphNodeDeprecationResponse GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const override; // End of UK2Node interface // UEdGraphNode interface diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CallFunction.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CallFunction.h index fca35a6e718d..951553c6c4a3 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CallFunction.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CallFunction.h @@ -80,9 +80,8 @@ public: virtual FText GetTooltipText() const override; virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; virtual FString GetDescriptiveCompiledName() const override; - virtual bool IsDeprecated() const override; - virtual bool ShouldWarnOnDeprecation() const override; - virtual FString GetDeprecationMessage() const override; + virtual bool HasDeprecatedReference() const override; + virtual FEdGraphNodeDeprecationResponse GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const override; virtual void PostPlacedNewNode() override; virtual FString GetDocumentationLink() const override; virtual FString GetDocumentationExcerptName() const override; diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_ConstructObjectFromClass.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_ConstructObjectFromClass.h index df349943d31b..3aaf7daeebf7 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_ConstructObjectFromClass.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_ConstructObjectFromClass.h @@ -27,6 +27,7 @@ class BLUEPRINTGRAPH_API UK2Node_ConstructObjectFromClass : public UK2Node virtual void PinConnectionListChanged(UEdGraphPin* Pin); virtual void GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextOut) const override; virtual void PostPlacedNewNode() override; + virtual void AddSearchMetaDataInfo(TArray& OutTaggedMetaData) const override; //~ End UEdGraphNode Interface. //~ Begin UK2Node Interface diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CreateDelegate.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CreateDelegate.h index c60ddb2c9633..7fde59baece7 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CreateDelegate.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CreateDelegate.h @@ -17,7 +17,7 @@ class UK2Node_CreateDelegate : public UK2Node { GENERATED_UCLASS_BODY() - UPROPERTY() + UPROPERTY(meta = (BlueprintSearchable = "true")) FName SelectedFunctionName; UPROPERTY() diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CustomEvent.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CustomEvent.h index fa93686bca74..2e8448667719 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CustomEvent.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CustomEvent.h @@ -19,6 +19,14 @@ class UK2Node_CustomEvent : public UK2Node_Event { GENERATED_UCLASS_BODY() + /** Optional message to display when the event is deprecated */ + UPROPERTY() + FString DeprecationMessage; + + /** Specifies that usage of this event has been deprecated */ + UPROPERTY() + bool bIsDeprecated; + /** Specifies that the event can be triggered in Editor */ UPROPERTY() bool bCallInEditor; @@ -41,12 +49,15 @@ class UK2Node_CustomEvent : public UK2Node_Event virtual void AutowireNewNode(UEdGraphPin* FromPin) override; virtual void AddSearchMetaDataInfo(TArray& OutTaggedMetaData) const override; virtual FText GetKeywords() const override; + virtual bool HasDeprecatedReference() const override { return bIsDeprecated; } + virtual FEdGraphNodeDeprecationResponse GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const override; //~ End UEdGraphNode Interface //~ Begin UK2Node Interface - BLUEPRINTGRAPH_API virtual void ValidateNodeDuringCompilation(class FCompilerResultsLog& MessageLog) const override; + virtual void ValidateNodeDuringCompilation(class FCompilerResultsLog& MessageLog) const override; virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override; - BLUEPRINTGRAPH_API virtual bool NodeCausesStructuralBlueprintChange() const override { return true; } + virtual void FixupPinStringDataReferences(FArchive* SavingArchive) override; + virtual bool NodeCausesStructuralBlueprintChange() const override { return true; } //~ End UK2Node Interface //~ Begin UK2Node_EditablePinBase Interface diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_EditablePinBase.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_EditablePinBase.h index 2d7aacdbb8e9..724d576c4b65 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_EditablePinBase.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_EditablePinBase.h @@ -59,6 +59,12 @@ struct FKismetUserDeclaredFunctionMetadata UPROPERTY() FLinearColor InstanceTitleColor; + UPROPERTY() + FString DeprecationMessage; + + UPROPERTY() + bool bIsDeprecated; + UPROPERTY() bool bCallInEditor; @@ -69,6 +75,7 @@ struct FKismetUserDeclaredFunctionMetadata public: FKismetUserDeclaredFunctionMetadata() : InstanceTitleColor(FLinearColor::White) + , bIsDeprecated(false) , bCallInEditor(false) , HasLatentFunctions(INDEX_NONE) { @@ -76,7 +83,7 @@ public: }; UCLASS(abstract, MinimalAPI) -class UK2Node_EditablePinBase : public UK2Node +class BLUEPRINTGRAPH_VTABLE UK2Node_EditablePinBase : public UK2Node { GENERATED_UCLASS_BODY() @@ -145,7 +152,7 @@ class UK2Node_EditablePinBase : public UK2Node */ virtual UEdGraphPin* CreatePinFromUserDefinition(const TSharedPtr NewPinInfo) { return nullptr; } - // Modifies the default value of an existing pin on the node. + /** Modifies the default value of an existing pin on the node, this will update both the UserPinInfo and the linked editor pin */ BLUEPRINTGRAPH_API virtual bool ModifyUserDefinedPinDefaultValue(TSharedPtr PinInfo, const FString& NewDefaultValue); /** @@ -167,5 +174,9 @@ class UK2Node_EditablePinBase : public UK2Node * Should this node require 'const' for pass-by-reference parameters? */ virtual bool ShouldUseConstRefParams() const { return false; } + +private: + /** Internal function that just updates the UEdGraphPin, separate to avoid recursive update calls */ + bool UpdateEdGraphPinDefaultValue(TSharedPtr PinInfo, FString& NewDefaultValue); }; diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_EnumLiteral.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_EnumLiteral.h index 5781664770cc..13bd18d80f53 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_EnumLiteral.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_EnumLiteral.h @@ -30,6 +30,7 @@ class UK2Node_EnumLiteral : public UK2Node, public INodeDependingOnEnumInterface virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; virtual FSlateIcon GetIconAndTint(FLinearColor& OutColor) const override; virtual void ValidateNodeDuringCompilation(class FCompilerResultsLog& MessageLog) const override; + virtual void AddSearchMetaDataInfo(TArray& OutTaggedMetaData) const override; //~ End UEdGraphNode Interface //~ Begin UK2Node Interface diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_Event.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_Event.h index 956ec4dce81d..d3b04f478ce1 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_Event.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_Event.h @@ -18,7 +18,7 @@ class FNodeHandlingFunctor; class UEdGraph; UCLASS(MinimalAPI) -class UK2Node_Event : public UK2Node_EditablePinBase, public IK2Node_EventNodeInterface +class BLUEPRINTGRAPH_VTABLE UK2Node_Event : public UK2Node_EditablePinBase, public IK2Node_EventNodeInterface { GENERATED_UCLASS_BODY() BLUEPRINTGRAPH_API static const FName DelegateOutputName; @@ -66,11 +66,12 @@ class UK2Node_Event : public UK2Node_EditablePinBase, public IK2Node_EventNodeIn BLUEPRINTGRAPH_API virtual bool CanPasteHere(const UEdGraph* TargetGraph) const override; BLUEPRINTGRAPH_API virtual bool IsCompatibleWithGraph(const UEdGraph* TargetGraph) const override; BLUEPRINTGRAPH_API virtual FName GetCornerIcon() const override; - BLUEPRINTGRAPH_API virtual bool IsDeprecated() const override; - BLUEPRINTGRAPH_API virtual FString GetDeprecationMessage() const override; + BLUEPRINTGRAPH_API virtual bool HasDeprecatedReference() const override; + BLUEPRINTGRAPH_API virtual FEdGraphNodeDeprecationResponse GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const override; BLUEPRINTGRAPH_API virtual UObject* GetJumpTargetForDoubleClick() const override; BLUEPRINTGRAPH_API virtual FSlateIcon GetIconAndTint(FLinearColor& OutColor) const override; BLUEPRINTGRAPH_API virtual FString GetFindReferenceSearchString() const override; + BLUEPRINTGRAPH_API virtual void FindDiffs(UEdGraphNode* OtherNode, struct FDiffResults& Results) override; //~ End UEdGraphNode Interface //~ Begin UK2Node Interface diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionEntry.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionEntry.h index c2242051f4b9..16487dfb078b 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionEntry.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionEntry.h @@ -6,6 +6,7 @@ #include "UObject/ObjectMacros.h" #include "K2Node_FunctionTerminator.h" #include "Engine/Blueprint.h" +#include "UObject/StructOnScope.h" #include "K2Node_FunctionEntry.generated.h" class UEdGraph; @@ -34,15 +35,17 @@ class UK2Node_FunctionEntry : public UK2Node_FunctionTerminator //~ Begin UObject Interface virtual void Serialize(FArchive& Ar) override; virtual void PreSave(const class ITargetPlatform* TargetPlatform) override; + virtual void PostLoad() override; //~ End UObject Interface //~ Begin UEdGraphNode Interface virtual void AllocateDefaultPins() override; virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; virtual bool CanUserDeleteNode() const override { return false; } - virtual bool IsDeprecated() const override; - virtual FString GetDeprecationMessage() const override; + virtual bool HasDeprecatedReference() const override; + virtual FEdGraphNodeDeprecationResponse GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const override; virtual FText GetTooltipText() const override; + virtual void FindDiffs(UEdGraphNode* OtherNode, struct FDiffResults& Results) override; //~ End UEdGraphNode Interface //~ Begin UK2Node Interface @@ -51,6 +54,8 @@ class UK2Node_FunctionEntry : public UK2Node_FunctionTerminator virtual void GetRedirectPinNames(const UEdGraphPin& Pin, TArray& RedirectPinNames) const override; virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override; virtual void PostReconstructNode() override; + virtual void ClearCachedBlueprintData(UBlueprint* Blueprint) override; + virtual void FixupPinStringDataReferences(FArchive* SavingArchive) override; //~ End UK2Node Interface //~ Begin UK2Node_EditablePinBase Interface @@ -64,6 +69,15 @@ class UK2Node_FunctionEntry : public UK2Node_FunctionTerminator virtual UEdGraphPin* CreatePinFromUserDefinition(const TSharedPtr NewPinInfo) override; //~ End K2Node_FunctionTerminator Interface + /** Gets the UFunction and function variable cache structure that should be used for serialization fixups for local variables. If bForceRefresh is true it will always recreate the cache */ + BLUEPRINTGRAPH_API TSharedPtr GetFunctionVariableCache(bool bForceRefresh = false); + + /** Copies data from the local variable defaults into the variable cache */ + BLUEPRINTGRAPH_API bool RefreshFunctionVariableCache(); + + /** Handles updating loaded default values, by going default string into variable cache and back, if bForceRefresh it will happen even if the cache is already setup */ + BLUEPRINTGRAPH_API bool UpdateLoadedDefaultValues(bool bForceRefresh = false); + // Removes an output pin from the node BLUEPRINTGRAPH_API void RemoveOutputPin(UEdGraphPin* PinToRemove); @@ -96,8 +110,20 @@ class UK2Node_FunctionEntry : public UK2Node_FunctionTerminator } protected: + /** Copies data from any local variables matching properties in VariableStruct into the VariableStructData */ + BLUEPRINTGRAPH_API bool UpdateVariableStructFromDefaults(const UStruct* VariableStruct, uint8* VariableStructData); + + /** Copies data from VariableStruct into the local variables */ + BLUEPRINTGRAPH_API bool UpdateDefaultsFromVariableStruct(const UStruct* VariableStruct, uint8* VariableStructData); + /** Any extra flags that the function may need */ UPROPERTY() int32 ExtraFlags; + + /** Holds a an in-memory representation of the UFunction struct, used to fixup local and user variables */ + TSharedPtr FunctionVariableCache; + + /** True if we've updated the default values on this node at least once */ + bool bUpdatedDefaultValuesOnLoad; }; diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionTerminator.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionTerminator.h index 1646fe2d5a59..2cea56bd17ab 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionTerminator.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_FunctionTerminator.h @@ -15,9 +15,7 @@ class UK2Node_FunctionTerminator : public UK2Node_EditablePinBase { GENERATED_UCLASS_BODY() - /** - * Reference to the function signature. - */ + /** Reference to the function signature. This is only resolvable by default if this is an inherited function */ UPROPERTY() FMemberReference FunctionReference; @@ -42,7 +40,10 @@ class UK2Node_FunctionTerminator : public UK2Node_EditablePinBase //~ End UK2Node_EditablePinBase Interface /** Promotes the node from being a part of an interface override to a full function that allows for parameter and result pin additions */ - virtual void PromoteFromInterfaceOverride(bool bIsPrimaryTerminator = true); + BLUEPRINTGRAPH_API virtual void PromoteFromInterfaceOverride(bool bIsPrimaryTerminator = true); + + /** Returns the UFunction that this node actually represents, this will work for both inherited and newly created functions */ + BLUEPRINTGRAPH_API UFunction* FindSignatureFunction() const; private: /** (DEPRECATED) Function signature class. Replaced by the 'FunctionReference' property. */ diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_SpawnActor.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_SpawnActor.h index 61bbe07fa775..eadb295dd813 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_SpawnActor.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_SpawnActor.h @@ -24,8 +24,7 @@ class UK2Node_SpawnActor : public UK2Node virtual void PinDefaultValueChanged(UEdGraphPin* Pin) override; virtual FText GetTooltipText() const override; virtual bool IsDeprecated() const override; - virtual bool ShouldWarnOnDeprecation() const override; - virtual FString GetDeprecationMessage() const override; + virtual FEdGraphNodeDeprecationResponse GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const override; virtual FSlateIcon GetIconAndTint(FLinearColor& OutColor) const override; virtual bool IsCompatibleWithGraph(const UEdGraph* TargetGraph) const override; //~ End UEdGraphNode Interface. diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_Tunnel.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_Tunnel.h index 8328300fcfdb..1cb56af481fc 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_Tunnel.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_Tunnel.h @@ -54,6 +54,7 @@ class BLUEPRINTGRAPH_API UK2Node_Tunnel : public UK2Node_EditablePinBase virtual bool DrawNodeAsEntry() const override; virtual bool DrawNodeAsExit() const override; virtual bool NodeCausesStructuralBlueprintChange() const override { return true; } + virtual void ClearCachedBlueprintData(UBlueprint* Blueprint) override; //~ End UK2Node Interface //~ Begin UK2Node_EditablePinBase Interface. diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_Variable.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_Variable.h index cc7718649690..ab47e418b74a 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_Variable.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_Variable.h @@ -71,8 +71,8 @@ public: virtual void AutowireNewNode(UEdGraphPin* FromPin) override; virtual bool CanPasteHere(const UEdGraph* TargetGraph) const override; virtual void PostPasteNode() override; - virtual bool IsDeprecated() const; - virtual FString GetDeprecationMessage() const; + virtual bool HasDeprecatedReference() const override; + virtual FEdGraphNodeDeprecationResponse GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const override; virtual UObject* GetJumpTargetForDoubleClick() const override; virtual bool CanJumpToDefinition() const override; virtual void JumpToDefinition() const override; diff --git a/Engine/Source/Editor/BlueprintGraph/Private/BlueprintActionDatabase.cpp b/Engine/Source/Editor/BlueprintGraph/Private/BlueprintActionDatabase.cpp index a052a0429839..77e39e9694b8 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/BlueprintActionDatabase.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/BlueprintActionDatabase.cpp @@ -1103,6 +1103,11 @@ void FBlueprintActionDatabase::AddReferencedObjects(FReferenceCollector& Collect } } +FString FBlueprintActionDatabase::GetReferencerName() const +{ + return TEXT("FBlueprintActionDatabase"); +} + int32 GBlueprintDatabasePrimingMaxPerFrame = 16; static FAutoConsoleVariableRef CVarBlueprintDatabasePrimingMaxPerFrame( TEXT("bp.DatabasePrimingMaxPerFrame"), diff --git a/Engine/Source/Editor/BlueprintGraph/Private/BlueprintNodeTemplateCache.cpp b/Engine/Source/Editor/BlueprintGraph/Private/BlueprintNodeTemplateCache.cpp index 7c17dfa25487..8f0e4ad7524d 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/BlueprintNodeTemplateCache.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/BlueprintNodeTemplateCache.cpp @@ -477,6 +477,11 @@ void FBlueprintNodeTemplateCache::AddReferencedObjects(FReferenceCollector& Coll Collector.AddReferencedObjects(TemplateOuters); } +FString FBlueprintNodeTemplateCache::GetReferencerName() const +{ + return TEXT("FBlueprintNodeTemplateCache"); +} + //------------------------------------------------------------------------------ bool FBlueprintNodeTemplateCache::CacheBlueprintOuter(UBlueprint* Blueprint) { diff --git a/Engine/Source/Editor/BlueprintGraph/Private/EdGraphSchema_K2.cpp b/Engine/Source/Editor/BlueprintGraph/Private/EdGraphSchema_K2.cpp index cbced5dda84b..499db3231abd 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/EdGraphSchema_K2.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/EdGraphSchema_K2.cpp @@ -117,6 +117,7 @@ const FName FBlueprintMetadata::MD_DeprecatedFunction(TEXT("DeprecatedFunction") const FName FBlueprintMetadata::MD_DeprecationMessage(TEXT("DeprecationMessage")); const FName FBlueprintMetadata::MD_CompactNodeTitle(TEXT("CompactNodeTitle")); const FName FBlueprintMetadata::MD_DisplayName(TEXT("DisplayName")); +const FName FBlueprintMetadata::MD_ReturnDisplayName(TEXT("ReturnDisplayName")); const FName FBlueprintMetadata::MD_InternalUseParam(TEXT("InternalUseParam")); const FName FBlueprintMetadata::MD_PropertyGetFunction(TEXT("BlueprintGetter")); @@ -887,7 +888,7 @@ bool UEdGraphSchema_K2::CanFunctionBeUsedInGraph(const UClass* InClass, const UF return false; } -UFunction* UEdGraphSchema_K2::GetCallableParentFunction(UFunction* Function) const +UFunction* UEdGraphSchema_K2::GetCallableParentFunction(UFunction* Function) { if( Function && Cast(Function->GetOuter()) ) { @@ -1295,7 +1296,7 @@ bool UEdGraphSchema_K2::PinHasSplittableStructType(const UEdGraphPin* InGraphPin bCanSplit = UK2Node_MakeStruct::CanBeSplit(StructType); if (!bCanSplit) { - const FString& MetaData = StructType->GetMetaData(TEXT("HasNativeMake")); + const FString& MetaData = StructType->GetMetaData(FBlueprintMetadata::MD_NativeMakeFunction); UFunction* Function = FindObject(NULL, *MetaData, true); bCanSplit = (Function != NULL); } @@ -1305,7 +1306,7 @@ bool UEdGraphSchema_K2::PinHasSplittableStructType(const UEdGraphPin* InGraphPin bCanSplit = UK2Node_BreakStruct::CanBeSplit(StructType); if (!bCanSplit) { - const FString& MetaData = StructType->GetMetaData(TEXT("HasNativeBreak")); + const FString& MetaData = StructType->GetMetaData(FBlueprintMetadata::MD_NativeBreakFunction); UFunction* Function = FindObject(NULL, *MetaData, true); bCanSplit = (Function != NULL); } @@ -1346,6 +1347,20 @@ bool UEdGraphSchema_K2::PinDefaultValueIsEditable(const UEdGraphPin& InGraphPin) return true; } +bool UEdGraphSchema_K2::PinHasCustomDefaultFormat(const UEdGraphPin& InGraphPin) const +{ + if (InGraphPin.PinType.PinCategory == PC_Struct) + { + // Some struct types have custom formats for default value for historical reasons + UObject const& SubCategoryObject = *InGraphPin.PinType.PinSubCategoryObject; + return &SubCategoryObject == VectorStruct + || &SubCategoryObject == RotatorStruct + || &SubCategoryObject == TransformStruct + || &SubCategoryObject == LinearColorStruct; + } + return false; +} + void UEdGraphSchema_K2::SelectAllNodesInDirection(TEnumAsByte InDirection, UEdGraph* Graph, UEdGraphPin* InGraphPin) { /** Traverses the node graph out from the specified pin, logging each node that it visits along the way. */ @@ -2402,7 +2417,22 @@ private: // Exclude the bitmask subcategory string from integral types so that autocast will work. PinSubCategory.Reset(); } - return FString::Printf(TEXT("%s;%s;%s"), *PinType.PinCategory.ToString(), *PinSubCategory, Obj ? *Obj->GetPathName() : TEXT("")); + + FString TypeString = FString::Printf(TEXT("%s;%s;%s;%d"), *PinType.PinCategory.ToString(), *PinSubCategory, Obj ? *Obj->GetPathName() : TEXT(""), (int32)PinType.ContainerType); + + if (PinType.ContainerType == EPinContainerType::Map) + { + // Add value type to string + Obj = PinType.PinValueType.TerminalSubCategoryObject.Get(); + PinSubCategory = PinType.PinValueType.TerminalSubCategory.ToString(); + if (PinSubCategory.StartsWith(UEdGraphSchema_K2::PSC_Bitmask.ToString())) + { + PinSubCategory.Reset(); + } + return FString::Printf(TEXT("%s;%s;%s;%s"), *TypeString, *PinType.PinValueType.TerminalCategory.ToString(), *PinSubCategory, Obj ? *Obj->GetPathName() : TEXT("")); + } + + return TypeString; } static FString GenerateCastData(const FEdGraphPinType& InputPinType, const FEdGraphPinType& OutputPinType) @@ -2572,68 +2602,72 @@ bool UEdGraphSchema_K2::SearchForAutocastFunction(const UEdGraphPin* OutputPin, FunctionOwner = Function->GetOwnerClass(); return true; } - return false; + + // Skip the other special cases if container check fails, but allow checking the autocast map } - - // SPECIAL CASES, not supported by FAutocastFunctionMap - if ((OutputPin->PinType.PinCategory == PC_Interface) && (InputPin->PinType.PinCategory == PC_Object)) + else { - UClass const* InputClass = Cast(InputPin->PinType.PinSubCategoryObject.Get()); - - bool const bInputIsUObject = (InputClass && (InputClass == UObject::StaticClass())); - if (bInputIsUObject) - { - UFunction* Function = UKismetSystemLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetSystemLibrary, Conv_InterfaceToObject)); - TargetFunction = Function->GetFName(); - FunctionOwner = Function->GetOwnerClass(); - } - } - else if (OutputPin->PinType.PinCategory == PC_Object) - { - UClass const* OutputClass = Cast(OutputPin->PinType.PinSubCategoryObject.Get()); - if (InputPin->PinType.PinCategory == PC_Class) + // SPECIAL CASES, not supported by FAutocastFunctionMap. + if ((OutputPin->PinType.PinCategory == PC_Interface) && (InputPin->PinType.PinCategory == PC_Object)) { UClass const* InputClass = Cast(InputPin->PinType.PinSubCategoryObject.Get()); - if ((OutputClass != nullptr) && - (InputClass != nullptr) && - OutputClass->IsChildOf(InputClass)) + + bool const bInputIsUObject = (InputClass && (InputClass == UObject::StaticClass())); + if (bInputIsUObject) { - UFunction* Function = UGameplayStatics::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UGameplayStatics, GetObjectClass)); + UFunction* Function = UKismetSystemLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetSystemLibrary, Conv_InterfaceToObject)); TargetFunction = Function->GetFName(); FunctionOwner = Function->GetOwnerClass(); } } - else if (InputPin->PinType.PinCategory == PC_String) + else if (OutputPin->PinType.PinCategory == PC_Object) { - UFunction* Function = UKismetSystemLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetSystemLibrary, GetDisplayName)); - TargetFunction = Function->GetFName(); - FunctionOwner = Function->GetOwnerClass(); - } - } - else if (OutputPin->PinType.PinCategory == PC_Class) - { - if (InputPin->PinType.PinCategory == PC_String) - { - UFunction* Function = UKismetSystemLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetSystemLibrary, GetClassDisplayName)); - TargetFunction = Function->GetFName(); - FunctionOwner = Function->GetOwnerClass(); - } - } - else if (OutputPin->PinType.PinCategory == PC_Struct) - { - const UScriptStruct* OutputStructType = Cast(OutputPin->PinType.PinSubCategoryObject.Get()); - if (OutputStructType == TBaseStructure::Get()) - { - const UScriptStruct* InputStructType = Cast(InputPin->PinType.PinSubCategoryObject.Get()); - if ((InputPin->PinType.PinCategory == PC_Struct) && (InputStructType == TBaseStructure::Get())) + UClass const* OutputClass = Cast(OutputPin->PinType.PinSubCategoryObject.Get()); + if (InputPin->PinType.PinCategory == PC_Class) { - UFunction* Function = UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetMathLibrary, MakeTransform)); + UClass const* InputClass = Cast(InputPin->PinType.PinSubCategoryObject.Get()); + if ((OutputClass != nullptr) && + (InputClass != nullptr) && + OutputClass->IsChildOf(InputClass)) + { + UFunction* Function = UGameplayStatics::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UGameplayStatics, GetObjectClass)); + TargetFunction = Function->GetFName(); + FunctionOwner = Function->GetOwnerClass(); + } + } + else if (InputPin->PinType.PinCategory == PC_String) + { + UFunction* Function = UKismetSystemLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetSystemLibrary, GetDisplayName)); TargetFunction = Function->GetFName(); FunctionOwner = Function->GetOwnerClass(); } } + else if (OutputPin->PinType.PinCategory == PC_Class) + { + if (InputPin->PinType.PinCategory == PC_String) + { + UFunction* Function = UKismetSystemLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetSystemLibrary, GetClassDisplayName)); + TargetFunction = Function->GetFName(); + FunctionOwner = Function->GetOwnerClass(); + } + } + else if (OutputPin->PinType.PinCategory == PC_Struct) + { + const UScriptStruct* OutputStructType = Cast(OutputPin->PinType.PinSubCategoryObject.Get()); + if (OutputStructType == TBaseStructure::Get()) + { + const UScriptStruct* InputStructType = Cast(InputPin->PinType.PinSubCategoryObject.Get()); + if ((InputPin->PinType.PinCategory == PC_Struct) && (InputStructType == TBaseStructure::Get())) + { + UFunction* Function = UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetMathLibrary, MakeTransform)); + TargetFunction = Function->GetFName(); + FunctionOwner = Function->GetOwnerClass(); + } + } + } } + // Try looking for a marked up autocast if we've not found a built-in one that works if (TargetFunction == NAME_None) { const FAutocastFunctionMap& AutocastFunctionMap = FAutocastFunctionMap::Get(); @@ -4597,7 +4631,7 @@ void UEdGraphSchema_K2::HandleGraphBeingDeleted(UEdGraph& GraphBeingRemoved) con } } -void UEdGraphSchema_K2::GetPinDefaultValuesFromString(const FEdGraphPinType& PinType, UObject* OwningObject, const FString& NewDefaultValue, FString& UseDefaultValue, UObject*& UseDefaultObject, FText& UseDefaultText) const +void UEdGraphSchema_K2::GetPinDefaultValuesFromString(const FEdGraphPinType& PinType, UObject* OwningObject, const FString& NewDefaultValue, FString& UseDefaultValue, UObject*& UseDefaultObject, FText& UseDefaultText, bool bPreserveTextIdentity) const { if ((PinType.PinCategory == PC_Object) || (PinType.PinCategory == PC_Class) @@ -4629,16 +4663,20 @@ void UEdGraphSchema_K2::GetPinDefaultValuesFromString(const FEdGraphPinType& Pin } else if (PinType.PinCategory == PC_Text) { - FString PackageNamespace; -#if USE_STABLE_LOCALIZATION_KEYS - if (GIsEditor) + if (bPreserveTextIdentity) { - PackageNamespace = TextNamespaceUtil::EnsurePackageNamespace(OwningObject); + UseDefaultText = FTextStringHelper::CreateFromBuffer(*NewDefaultValue); } -#endif // USE_STABLE_LOCALIZATION_KEYS - if (!FTextStringHelper::ReadFromBuffer(*NewDefaultValue, UseDefaultText, nullptr, *PackageNamespace)) + else { - UseDefaultText = FText::FromString(NewDefaultValue); + FString PackageNamespace; +#if USE_STABLE_LOCALIZATION_KEYS + if (GIsEditor) + { + PackageNamespace = TextNamespaceUtil::EnsurePackageNamespace(OwningObject); + } +#endif // USE_STABLE_LOCALIZATION_KEYS + UseDefaultText = FTextStringHelper::CreateFromBuffer(*NewDefaultValue, nullptr, *PackageNamespace); } UseDefaultObject = nullptr; UseDefaultValue.Empty(); @@ -4671,7 +4709,7 @@ void UEdGraphSchema_K2::TrySetDefaultValue(UEdGraphPin& Pin, const FString& NewD UObject* UseDefaultObject = nullptr; FText UseDefaultText; - GetPinDefaultValuesFromString(Pin.PinType, Pin.GetOwningNodeUnchecked(), NewDefaultValue, UseDefaultValue, UseDefaultObject, UseDefaultText); + GetPinDefaultValuesFromString(Pin.PinType, Pin.GetOwningNodeUnchecked(), NewDefaultValue, UseDefaultValue, UseDefaultObject, UseDefaultText, /*bPreserveTextIdentity*/false); // Check the default value and make it an error if it's bogus if (IsPinDefaultValid(&Pin, UseDefaultValue, UseDefaultObject, UseDefaultText).IsEmpty()) @@ -5390,6 +5428,11 @@ void UEdGraphSchema_K2::GetGraphDisplayInformation(const UEdGraph& Graph, /*out* { DisplayInfo.Notes.Add(TEXT("const")); } + + if (Function->HasMetaData(FBlueprintMetadata::MD_DeprecatedFunction)) + { + DisplayInfo.Notes.Add(LOCTEXT("FunctionGraphDisplayInfo_Deprecated", "deprecated").ToString()); + } } // Mark transient graphs as obviously so @@ -5979,19 +6022,21 @@ struct FBackwardCompatibilityConversionHelper { if (ConversionParams.FuncScope) { - const UFunction* OldFunc = ConversionParams.FuncScope->FindFunctionByName(ConversionParams.OldFuncName); - check(OldFunc); const UFunction* NewFunc = ConversionParams.FuncScope->FindFunctionByName(ConversionParams.NewFuncName); - check(NewFunc); - - for (UK2Node_CallFunction* Node : Nodes) + if (ensureMsgf(NewFunc, TEXT("Can't find conversion function %s on %s!"), *ConversionParams.NewFuncName.ToString(), *ConversionParams.FuncScope->GetName())) { - if (OldFunc == Node->GetTargetFunction()) + for (UK2Node_CallFunction* Node : Nodes) { - UK2Node_CallFunction* NewNode = NewObject(Graph); - NewNode->SetFromFunction(NewFunc); - ConvertNode(Node, ConversionParams.BlueprintPinName, NewNode, - ConversionParams.ClassPinName, Schema, bOnlyWithDefaultBlueprint); + // Check to see if the class scope and name are the same, we can't depend on the UFunction still existing + UClass* MemberParent = Node->FunctionReference.GetMemberParentClass(Node->GetBlueprintClassFromNode()); + + if (MemberParent == ConversionParams.FuncScope && Node->FunctionReference.GetMemberName() == ConversionParams.OldFuncName) + { + UK2Node_CallFunction* NewNode = NewObject(Graph); + NewNode->SetFromFunction(NewFunc); + ConvertNode(Node, ConversionParams.BlueprintPinName, NewNode, + ConversionParams.ClassPinName, Schema, bOnlyWithDefaultBlueprint); + } } } } @@ -6495,7 +6540,12 @@ UEdGraph* UEdGraphSchema_K2::DuplicateGraph(UEdGraph* GraphToDuplicate) const if (EntryNode->FunctionReference.GetMemberName() == GraphToDuplicate->GetFName()) { EntryNode->Modify(); - EntryNode->FunctionReference.SetMemberName(NewGraph->GetFName()); + + // We're duplicating the graph, so fully reset the member reference (including the GUID!) + FMemberReference NewRef; + NewRef.SetMemberName(NewGraph->GetFName()); + EntryNode->FunctionReference = NewRef; + break; } } @@ -6814,7 +6864,7 @@ UK2Node* UEdGraphSchema_K2::CreateSplitPinNode(UEdGraphPin* Pin, const FCreateSp } else { - const FString& MetaData = StructType->GetMetaData(TEXT("HasNativeMake")); + const FString& MetaData = StructType->GetMetaData(FBlueprintMetadata::MD_NativeMakeFunction); const UFunction* Function = FindObject(nullptr, *MetaData, true); UK2Node_CallFunction* CallFunctionNode; @@ -6862,7 +6912,7 @@ UK2Node* UEdGraphSchema_K2::CreateSplitPinNode(UEdGraphPin* Pin, const FCreateSp } else { - const FString& MetaData = StructType->GetMetaData(TEXT("HasNativeBreak")); + const FString& MetaData = StructType->GetMetaData(FBlueprintMetadata::MD_NativeBreakFunction); const UFunction* Function = FindObject(nullptr, *MetaData, true); UK2Node_CallFunction* CallFunctionNode; diff --git a/Engine/Source/Editor/BlueprintGraph/Private/EdGraphSchema_K2_Actions.cpp b/Engine/Source/Editor/BlueprintGraph/Private/EdGraphSchema_K2_Actions.cpp index 3961e0365f23..e16aa58091a9 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/EdGraphSchema_K2_Actions.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/EdGraphSchema_K2_Actions.cpp @@ -157,8 +157,14 @@ FEdGraphSchemaActionDefiningObject FEdGraphSchemaAction_K2Graph::GetPersistentIt UObject* DefiningObject = GetSourceBlueprint(); if (UFunction* Func = GetFunction()) { - DefiningObject = Func->GetOwnerStruct(); + // Use the class where the function was initially introduced as the defining object + while (Func->GetSuperFunction()) + { + Func = Func->GetSuperFunction(); + } + DefiningObject = Func->GetOuterUClassUnchecked(); } + return FEdGraphSchemaActionDefiningObject(DefiningObject, (void*)GraphType); } diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node.cpp index 2fd0eec41580..b7e9cf889619 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node.cpp @@ -76,48 +76,26 @@ void UK2Node::Serialize(FArchive& Ar) Ar.UsingCustomVersion(FFrameworkObjectVersion::GUID); if (Ar.IsSaving()) - { - for (UEdGraphPin* Pin : Pins) + { + if (Ar.IsObjectReferenceCollector() || Ar.Tell() < 0) { - if (!Pin->bDefaultValueIsIgnored && !Pin->DefaultValue.IsEmpty() ) - { - // If looking for references during save, expand any default values on the pins - // This is only reliable when saving in the editor, the cook case is handled below - if (Ar.IsObjectReferenceCollector() && Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct && Pin->PinType.PinSubCategoryObject.IsValid()) - { - UScriptStruct* Struct = Cast(Pin->PinType.PinSubCategoryObject.Get()); - - if (Struct) - { - TSharedPtr StructData = MakeShareable(new FStructOnScope(Struct)); - - // Import the literal text to a dummy struct and then serialize that. Hard object references will not properly import, this is only useful for soft references! - FOutputDeviceNull NullOutput; - Struct->ImportText(*Pin->DefaultValue, StructData->GetStructMemory(), nullptr, PPF_SerializedAsImportText, &NullOutput, Pin->PinName.ToString()); - Struct->SerializeItem(Ar, StructData->GetStructMemory(), nullptr); - } - } - - if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_SoftObject || Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_SoftClass) - { - FSoftObjectPath TempRef(Pin->DefaultValue); - - // Serialize the asset reference, this will do the save fixup. It won't actually serialize the string if this is a real archive like linkersave - FSoftObjectPathSerializationScope DisableSerialize(NAME_None, NAME_None, ESoftObjectPathCollectType::AlwaysCollect, ESoftObjectPathSerializeType::SkipSerializeIfArchiveHasSize); - Ar << TempRef; - - Pin->DefaultValue = TempRef.ToString(); - } - } + // When this is a reference collector/modifier, serialize some pins as structs + FixupPinStringDataReferences(&Ar); } } Super::Serialize(Ar); - if (Ar.IsLoading()) + if (Ar.IsLoading() && ((Ar.GetPortFlags() & PPF_Duplicate) == 0)) { // Fix up pin default values, must be done before post load FixupPinDefaultValues(); + + if (GIsEditor) + { + // We need to serialize string data references on load in editor builds so the cooker knows about them + FixupPinStringDataReferences(nullptr); + } } } @@ -161,33 +139,95 @@ void UK2Node::FixupPinDefaultValues() } // Fix soft object ptr pins - if (GIsEditor || LinkerFrameworkVersion < FFrameworkObjectVersion::ChangeAssetPinsToString) + if (LinkerFrameworkVersion < FFrameworkObjectVersion::ChangeAssetPinsToString) { - FSoftObjectPathSerializationScope SetPackage(GetOutermost()->GetFName(), NAME_None, ESoftObjectPathCollectType::AlwaysCollect, ESoftObjectPathSerializeType::SkipSerializeIfArchiveHasSize); for (int32 i = 0; i < Pins.Num(); ++i) { UEdGraphPin* Pin = Pins[i]; if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_SoftObject || Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_SoftClass) { // Fix old assetptr pins - if (LinkerFrameworkVersion < FFrameworkObjectVersion::ChangeAssetPinsToString) + if (Pin->DefaultObject && Pin->DefaultValue.IsEmpty()) { - if (Pin->DefaultObject && Pin->DefaultValue.IsEmpty()) + Pin->DefaultValue = Pin->DefaultObject->GetPathName(); + Pin->DefaultObject = nullptr; + } + } + } + } +} + +void UK2Node::FixupPinStringDataReferences(FArchive* SavingArchive) +{ + FSoftObjectPathSerializationScope SetPackage(GetOutermost()->GetFName(), NAME_None, ESoftObjectPathCollectType::AlwaysCollect, ESoftObjectPathSerializeType::SkipSerializeIfArchiveHasSize); + + // This code expands some pin types into their real representation, optionally serializes them, and then writes them out as strings again + const UEdGraphSchema_K2* K2Schema = GetDefault(); + FLinkerLoad* LinkerLoad = GetLinker(); + + // Can't do any fixups without an archive of some sort + if (!SavingArchive && !LinkerLoad) + { + return; + } + + for (UEdGraphPin* Pin : Pins) + { + if (!Pin->bDefaultValueIsIgnored && !Pin->DefaultValue.IsEmpty()) + { + // We skip this for structs like FVector that have a custom format for default values + if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct && Pin->PinType.PinSubCategoryObject.IsValid() && !K2Schema->PinHasCustomDefaultFormat(*Pin)) + { + UScriptStruct* Struct = Cast(Pin->PinType.PinSubCategoryObject.Get()); + if (Struct) + { + // While loading, our user struct may not have been linked yet + if (Struct->HasAnyFlags(RF_NeedLoad) && LinkerLoad) { - Pin->DefaultValue = Pin->DefaultObject->GetPathName(); - Pin->DefaultObject = nullptr; + LinkerLoad->Preload(Struct); + } + + TSharedPtr StructData = MakeShareable(new FStructOnScope(Struct)); + StructData->SetPackage(GetOutermost()); + + // Import the literal text to a dummy struct and then serialize that. Hard object references will not properly import, this is only useful for soft references! + FOutputDeviceNull NullOutput; + Struct->ImportText(*Pin->DefaultValue, StructData->GetStructMemory(), this, PPF_SerializedAsImportText, &NullOutput, Pin->PinName.ToString()); + + if (SavingArchive) + { + // If we're saving, use the archive to do any replacements + Struct->SerializeItem(*SavingArchive, StructData->GetStructMemory(), nullptr); + } + + // Convert back to the default value string as we might have changed + FString NewValue; + Struct->ExportText(NewValue, StructData->GetStructMemory(), StructData->GetStructMemory(), this, PPF_SerializedAsImportText, nullptr); + + if (Pin->DefaultValue != NewValue) + { + Pin->DefaultValue = NewValue; } } + } - // In editor, fixup soft object ptrs on load on to handle redirects and finding refs for cooking - // We're not handling soft object ptrs inside FStructs because it's a rare edge case and would be a performance hit on load - if (GIsEditor && !Pin->DefaultValue.IsEmpty()) + if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_SoftObject || Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_SoftClass) + { + FSoftObjectPath TempRef(Pin->DefaultValue); + + if (SavingArchive) { - FSoftObjectPath TempRef(Pin->DefaultValue); - TempRef.PostLoadPath(GetLinker()); - TempRef.PreSavePath(); - Pin->DefaultValue = TempRef.ToString(); + // Serialize it directly, this won't do anything if it's a real archive like LinkerSave + *SavingArchive << TempRef; } + else + { + // Transform it as strings + TempRef.PostLoadPath(LinkerLoad); + TempRef.PreSavePath(); + } + + Pin->DefaultValue = TempRef.ToString(); } } } @@ -266,10 +306,26 @@ void UK2Node::AutowireNewNode(UEdGraphPin* FromPin) UEdGraphPin* Pin = Pins[i]; check(Pin); - // Never consider for auto-wiring a hidden pin being connected to a Wildcard. It is never what the user expects - if (Pin->bHidden && FromPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard) + if (Pin->bHidden) { - continue; + // Never consider for auto-wiring a hidden pin being connected to a Wildcard. It is never what the user expects + if (FromPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard) + { + continue; + } + + // Never connect wires to hidden WorldContextObject pins + if (UK2Node_CallFunction* CallFunctionNode = Cast(this)) + { + if (UFunction* NodeTargetFunction = CallFunctionNode->GetTargetFunction()) + { + const FString& WorldContextPinName = NodeTargetFunction->GetMetaData(FBlueprintMetadata::MD_WorldContext); + if (!WorldContextPinName.IsEmpty() && WorldContextPinName == Pin->PinName.ToString()) + { + continue; + } + } + } } ECanCreateConnectionResponse ConnectResponse = K2Schema->CanCreateConnection(FromPin, Pin).Response; diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ActorBoundEvent.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ActorBoundEvent.cpp index 05adc82f58d5..41904faa0bc9 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ActorBoundEvent.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ActorBoundEvent.cpp @@ -62,7 +62,6 @@ void UK2Node_ActorBoundEvent::ReconstructNode() // Found a remapped property, update the node TargetDelegateProp = NewProperty; DelegatePropertyName = NewProperty->GetFName(); - CachedNodeTitle.MarkDirty(); } } diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_AddComponent.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_AddComponent.cpp index e499b5fa31d2..345cfb79bfa7 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_AddComponent.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_AddComponent.cpp @@ -14,6 +14,7 @@ #include "UObject/ReleaseObjectVersion.h" #include "KismetCompilerMisc.h" #include "KismetCompiler.h" +#include "DiffResults.h" #include "UObject/BlueprintsObjectVersion.h" // for ComponentTemplateClassSupport #define LOCTEXT_NAMESPACE "K2Node_AddComponent" @@ -115,7 +116,7 @@ void UK2Node_AddComponent::AllocatePinsForExposedVariables() if ((ClassDefaultObject != nullptr) && K2Schema->PinDefaultValueIsEditable(*Pin)) { FString DefaultValueAsString; - const bool bDefaultValueSet = FBlueprintEditorUtils::PropertyValueToString(Property, reinterpret_cast(ClassDefaultObject), DefaultValueAsString); + const bool bDefaultValueSet = FBlueprintEditorUtils::PropertyValueToString(Property, reinterpret_cast(ClassDefaultObject), DefaultValueAsString, this); check(bDefaultValueSet); K2Schema->SetPinAutogeneratedDefaultValue(Pin, DefaultValueAsString); } @@ -407,6 +408,28 @@ void UK2Node_AddComponent::ReconstructNode() } } +void UK2Node_AddComponent::FindDiffs(class UEdGraphNode* OtherNode, struct FDiffResults& Results) +{ + Super::FindDiffs(OtherNode, Results); + + if (UK2Node_AddComponent* OtherGraphNode = Cast(OtherNode)) + { + UActorComponent* MyComponent = GetTemplateFromNode(); + UActorComponent* OtherComponent = OtherGraphNode->GetTemplateFromNode(); + if (MyComponent && OtherComponent) + { + FDiffSingleResult Diff; + Diff.Diff = EDiffType::NODE_PROPERTY; + Diff.Node1 = this; + Diff.Node2 = OtherNode; + Diff.ToolTip = LOCTEXT("DIF_ComponentTemplatePropertyToolTip", "A property of the component template has changed"); + Diff.DisplayColor = FLinearColor(0.25f, 0.71f, 0.85f); + + DiffProperties(MyComponent->GetClass(), OtherComponent->GetClass(), MyComponent, OtherComponent, Results, Diff); + } + } +} + void UK2Node_AddComponent::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_BaseAsyncTask.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_BaseAsyncTask.cpp index f87e3b20361e..1276434b6781 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_BaseAsyncTask.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_BaseAsyncTask.cpp @@ -73,10 +73,15 @@ void UK2Node_BaseAsyncTask::AllocateDefaultPins() bool bExposeProxy = false; bool bHideThen = false; + FText ExposeProxyDisplayName; for (const UStruct* TestStruct = ProxyClass; TestStruct; TestStruct = TestStruct->GetSuperStruct()) { bExposeProxy |= TestStruct->HasMetaData(TEXT("ExposedAsyncProxy")); bHideThen |= TestStruct->HasMetaData(TEXT("HideThen")); + if (ExposeProxyDisplayName.IsEmpty()) + { + ExposeProxyDisplayName = TestStruct->GetMetaDataText(TEXT("ExposedAsyncProxy")); + } } if (!bHideThen) @@ -86,7 +91,11 @@ void UK2Node_BaseAsyncTask::AllocateDefaultPins() if (bExposeProxy) { - CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Object, ProxyClass, FBaseAsyncTaskHelper::GetAsyncTaskProxyName()); + UEdGraphPin* ProxyPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Object, ProxyClass, FBaseAsyncTaskHelper::GetAsyncTaskProxyName()); + if (!ExposeProxyDisplayName.IsEmpty()) + { + ProxyPin->PinFriendlyName = ExposeProxyDisplayName; + } } UFunction* Function = GetFactoryFunction(); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_BreakStruct.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_BreakStruct.cpp index b902519d75b3..947828cb614c 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_BreakStruct.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_BreakStruct.cpp @@ -154,7 +154,7 @@ static bool CanCreatePinForProperty(const UProperty* Property) bool UK2Node_BreakStruct::CanBeBroken(const UScriptStruct* Struct, const bool bForInternalUse) { - if (Struct && !Struct->HasMetaData(TEXT("HasNativeBreak")) && UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Struct, bForInternalUse)) + if (Struct && !Struct->HasMetaData(FBlueprintMetadata::MD_NativeBreakFunction) && UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Struct, bForInternalUse)) { for (TFieldIterator It(Struct); It; ++It) { diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CallFunction.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CallFunction.cpp index c1917f2add6b..f7225062b89c 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CallFunction.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CallFunction.cpp @@ -437,30 +437,36 @@ UK2Node_CallFunction::UK2Node_CallFunction(const FObjectInitializer& ObjectIniti } -bool UK2Node_CallFunction::IsDeprecated() const +bool UK2Node_CallFunction::HasDeprecatedReference() const { UFunction* Function = GetTargetFunction(); return (Function && Function->HasMetaData(FBlueprintMetadata::MD_DeprecatedFunction)); } -bool UK2Node_CallFunction::ShouldWarnOnDeprecation() const +FEdGraphNodeDeprecationResponse UK2Node_CallFunction::GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const { - // TEMP: Do not warn in the case of SpawnActor, as we have a special upgrade path for those nodes - return (FunctionReference.GetMemberName() != FName(TEXT("BeginSpawningActorFromBlueprint"))); -} - -FString UK2Node_CallFunction::GetDeprecationMessage() const -{ - UFunction* Function = GetTargetFunction(); - if (Function && Function->HasMetaData(FBlueprintMetadata::MD_DeprecationMessage)) + FEdGraphNodeDeprecationResponse Response = Super::GetDeprecationResponse(DeprecationType); + if (DeprecationType == EEdGraphNodeDeprecationType::NodeHasDeprecatedReference) { - return FString::Printf(TEXT("%s %s"), *LOCTEXT("CallFunctionDeprecated_Warning", "@@ is deprecated;").ToString(), *Function->GetMetaData(FBlueprintMetadata::MD_DeprecationMessage)); + // TEMP: Do not warn in the case of SpawnActor, as we have a special upgrade path for those nodes + if (FunctionReference.GetMemberName() == FName(TEXT("BeginSpawningActorFromBlueprint"))) + { + Response.MessageType = EEdGraphNodeDeprecationMessageType::None; + } + else + { + UFunction* Function = GetTargetFunction(); + if (ensureMsgf(Function != nullptr, TEXT("This node should not be able to report having a deprecated reference if the target function cannot be resolved."))) + { + FString DetailedMessage = Function->GetMetaData(FBlueprintMetadata::MD_DeprecationMessage); + Response.MessageText = FBlueprintEditorUtils::GetDeprecatedMemberUsageNodeWarning(GetUserFacingFunctionName(Function), FText::FromString(DetailedMessage)); + } + } } - - return Super::GetDeprecationMessage(); + + return Response; } - FText UK2Node_CallFunction::GetFunctionContextString() const { FText ContextString; @@ -1037,6 +1043,10 @@ bool UK2Node_CallFunction::CreatePinsForFunctionCall(const UFunction* Function) { Pin->PinFriendlyName = FText::FromString(PinDisplayName); } + else if (Function->GetReturnProperty() == Param && Function->HasMetaData(FBlueprintMetadata::MD_ReturnDisplayName)) + { + Pin->PinFriendlyName = Function->GetMetaDataText(FBlueprintMetadata::MD_ReturnDisplayName); + } //Flag pin as read only for const reference property Pin->bDefaultValueIsIgnored = Param->HasAllPropertyFlags(CPF_ConstParm | CPF_ReferenceParm) && (!Function->HasMetaData(FBlueprintMetadata::MD_AutoCreateRefTerm) || Pin->PinType.IsContainer()); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CommutativeAssociativeBinaryOperator.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CommutativeAssociativeBinaryOperator.cpp index ece39674fbe3..1c494917898a 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CommutativeAssociativeBinaryOperator.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CommutativeAssociativeBinaryOperator.cpp @@ -238,7 +238,7 @@ void UK2Node_CommutativeAssociativeBinaryOperator::GetContextMenuActions(const F LOCTEXT("RemovePinTooltip", "Remove this input pin"), FSlateIcon(), FUIAction( - FExecuteAction::CreateUObject(this, &UK2Node_CommutativeAssociativeBinaryOperator::RemoveInputPin, const_cast(Context.Pin)) + FExecuteAction::CreateUObject(const_cast(this), &UK2Node_CommutativeAssociativeBinaryOperator::RemoveInputPin, const_cast(Context.Pin)) ) ); Context.MenuBuilder->EndSection(); @@ -252,7 +252,7 @@ void UK2Node_CommutativeAssociativeBinaryOperator::GetContextMenuActions(const F LOCTEXT("AddPinTooltip", "Add another input pin"), FSlateIcon(), FUIAction( - FExecuteAction::CreateUObject(this, &UK2Node_CommutativeAssociativeBinaryOperator::AddInputPin) + FExecuteAction::CreateUObject(const_cast(this), &UK2Node_CommutativeAssociativeBinaryOperator::AddInputPin) ) ); Context.MenuBuilder->EndSection(); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ComponentBoundEvent.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ComponentBoundEvent.cpp index 5c8f538f2888..23c0626042d0 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ComponentBoundEvent.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ComponentBoundEvent.cpp @@ -147,7 +147,6 @@ void UK2Node_ComponentBoundEvent::ReconstructNode() // Found a remapped property, update the node TargetDelegateProp = NewProperty; DelegatePropertyName = NewProperty->GetFName(); - CachedNodeTitle.MarkDirty(); } } diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ConstructObjectFromClass.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ConstructObjectFromClass.cpp index 521547d5904f..b45c7e14f31d 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ConstructObjectFromClass.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ConstructObjectFromClass.cpp @@ -7,6 +7,7 @@ #include "BlueprintNodeSpawner.h" #include "EditorCategoryUtils.h" #include "BlueprintActionDatabaseRegistrar.h" +#include "FindInBlueprintManager.h" struct FK2Node_ConstructObjectFromClassHelper { @@ -121,7 +122,7 @@ void UK2Node_ConstructObjectFromClass::CreatePinsForClass(UClass* InClass, TArra if (ClassDefaultObject && K2Schema->PinDefaultValueIsEditable(*Pin)) { FString DefaultValueAsString; - const bool bDefaultValueSet = FBlueprintEditorUtils::PropertyValueToString(Property, reinterpret_cast(ClassDefaultObject), DefaultValueAsString); + const bool bDefaultValueSet = FBlueprintEditorUtils::PropertyValueToString(Property, reinterpret_cast(ClassDefaultObject), DefaultValueAsString, this); check(bDefaultValueSet); K2Schema->SetPinAutogeneratedDefaultValue(Pin, DefaultValueAsString); } @@ -176,6 +177,12 @@ void UK2Node_ConstructObjectFromClass::PostPlacedNewNode() } } +void UK2Node_ConstructObjectFromClass::AddSearchMetaDataInfo(TArray& OutTaggedMetaData) const +{ + Super::AddSearchMetaDataInfo(OutTaggedMetaData); + OutTaggedMetaData.Add(FSearchTagDataPair(FFindInBlueprintSearchTags::FiB_NativeName, CachedNodeTitle.GetCachedText())); +} + bool UK2Node_ConstructObjectFromClass::IsSpawnVarPin(UEdGraphPin* Pin) const { return( Pin->PinName != UEdGraphSchema_K2::PN_Execute && diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CreateDelegate.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CreateDelegate.cpp index 701e27867a2a..700f6b5f3026 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CreateDelegate.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CreateDelegate.cpp @@ -158,7 +158,7 @@ bool UK2Node_CreateDelegate::IsValid(FString* OutMsg, bool bDontUseSkeletalClass { if (OutMsg) { - *OutMsg = NSLOCTEXT("K2Node", "Function_cannot_be_used_in_delegate", "The selected function/event is not bindable - is the function/event pure or latent?").ToString(); + *OutMsg = NSLOCTEXT("K2Node", "Function_cannot_be_used_in_delegate", "The selected function/event is not bindable - is the function/event deprecated, pure or latent?").ToString(); } return false; } diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CustomEvent.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CustomEvent.cpp index 3fd2f1e8111f..a6fa6da682c8 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CustomEvent.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CustomEvent.cpp @@ -107,6 +107,7 @@ UK2Node_CustomEvent::UK2Node_CustomEvent(const FObjectInitializer& ObjectInitial bOverrideFunction = false; bIsEditable = true; bCanRenameNode = true; + bIsDeprecated = false; bCallInEditor = false; } @@ -342,6 +343,31 @@ void UK2Node_CustomEvent::GetMenuActions(FBlueprintActionDatabaseRegistrar& Acti } } +void UK2Node_CustomEvent::FixupPinStringDataReferences(FArchive* SavingArchive) +{ + Super::FixupPinStringDataReferences(SavingArchive); + if (SavingArchive) + { + // If any of our pins got fixed up, we need to refresh our user pin default values + // For custom events, the Pin default values are authoritative + for (TSharedPtr PinInfo : UserDefinedPins) + { + if (UEdGraphPin* Pin = FindPin(PinInfo->PinName)) + { + if (Pin->Direction == PinInfo->DesiredPinDirection) + { + FString DefaultsString = Pin->GetDefaultAsString(); + + if (DefaultsString != PinInfo->PinDefaultValue) + { + ModifyUserDefinedPinDefaultValue(PinInfo, DefaultsString); + } + } + } + } + } +} + void UK2Node_CustomEvent::ReconstructNode() { CachedNodeTitle.MarkDirty(); @@ -518,4 +544,27 @@ FText UK2Node_CustomEvent::GetKeywords() const return FText::Format(LOCTEXT("CustomEventKeywords", "{ParentKeywords} Custom"), Args); } +FEdGraphNodeDeprecationResponse UK2Node_CustomEvent::GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const +{ + FEdGraphNodeDeprecationResponse Response = Super::GetDeprecationResponse(DeprecationType); + if (DeprecationType == EEdGraphNodeDeprecationType::NodeHasDeprecatedReference) + { + // Only warn on override usage. + if (IsOverride()) + { + FText EventName = FText::FromName(GetFunctionName()); + FText DetailedMessage = FText::FromString(DeprecationMessage); + Response.MessageText = FBlueprintEditorUtils::GetDeprecatedMemberUsageNodeWarning(EventName, DetailedMessage); + } + else + { + // Allow the source event to be marked as deprecated in the class that defines it without warning, but use a note to visually indicate that the definition itself has been deprecated. + Response.MessageType = EEdGraphNodeDeprecationMessageType::Note; + Response.MessageText = LOCTEXT("DeprecatedCustomEventMessage", "@@: This custom event has been marked as deprecated. It can be safely deleted if all references have been replaced or removed."); + } + } + + return Response; +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_DoOnceMultiInput.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_DoOnceMultiInput.cpp index 7861b1cbda23..044134bb9c4c 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_DoOnceMultiInput.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_DoOnceMultiInput.cpp @@ -286,7 +286,7 @@ void UK2Node_DoOnceMultiInput::GetContextMenuActions(const FGraphNodeContextMenu LOCTEXT("RemovePinTooltip", "Remove this input pin"), FSlateIcon(), FUIAction( - FExecuteAction::CreateUObject(this, &UK2Node_DoOnceMultiInput::RemoveInputPin, const_cast(Context.Pin)) + FExecuteAction::CreateUObject(const_cast(this), &UK2Node_DoOnceMultiInput::RemoveInputPin, const_cast(Context.Pin)) ) ); Context.MenuBuilder->EndSection(); @@ -300,7 +300,7 @@ void UK2Node_DoOnceMultiInput::GetContextMenuActions(const FGraphNodeContextMenu LOCTEXT("AddPinTooltip", "Add another input pin"), FSlateIcon(), FUIAction( - FExecuteAction::CreateUObject(this, &UK2Node_DoOnceMultiInput::AddInputPin) + FExecuteAction::CreateUObject(const_cast(this), &UK2Node_DoOnceMultiInput::AddInputPin) ) ); Context.MenuBuilder->EndSection(); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_DynamicCast.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_DynamicCast.cpp index 1b228d1090ba..a78e876721a9 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_DynamicCast.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_DynamicCast.cpp @@ -154,7 +154,7 @@ void UK2Node_DynamicCast::GetContextMenuActions(const FGraphNodeContextMenuBuild MenuEntryTooltip, FSlateIcon(), FUIAction( - FExecuteAction::CreateUObject(this, &UK2Node_DynamicCast::TogglePurity), + FExecuteAction::CreateUObject(const_cast(this), &UK2Node_DynamicCast::TogglePurity), FCanExecuteAction::CreateStatic(CanExecutePurityToggle, bCanTogglePurity), FIsActionChecked() ) diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EaseFunction.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EaseFunction.cpp index a5b4ad7e9167..56d26795975f 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EaseFunction.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EaseFunction.cpp @@ -548,7 +548,7 @@ void UK2Node_EaseFunction::GetContextMenuActions(const FGraphNodeContextMenuBuil LOCTEXT("AddPinTooltip", "Resets A, B and Results pins to its default wildcard state"), FSlateIcon(), FUIAction( - FExecuteAction::CreateUObject(this, &UK2Node_EaseFunction::ResetToWildcards) + FExecuteAction::CreateUObject(const_cast(this), &UK2Node_EaseFunction::ResetToWildcards) ) ); } diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EditablePinBase.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EditablePinBase.cpp index dff6c4eb1b63..a5b0938f15e5 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EditablePinBase.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EditablePinBase.cpp @@ -5,6 +5,7 @@ #include "UObject/FrameworkObjectVersion.h" #include "Misc/FeedbackContext.h" #include "EdGraphSchema_K2.h" +#include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetDebugUtilities.h" // Ensure that the UserDefinedPin's "desired direction" matches the direction of @@ -256,6 +257,23 @@ void UK2Node_EditablePinBase::Serialize(FArchive& Ar) } } + // We need to fixup text default values because many of them got saved with the wrong package namespace + /*if (!PinInfo->PinDefaultValue.IsEmpty() && PinInfo->PinType.PinCategory == UEdGraphSchema_K2::PC_Text) + { + FString UseDefaultValue; + UObject* UseDefaultObject = nullptr; + FText UseDefaultText; + + K2Schema->GetPinDefaultValuesFromString(PinInfo->PinType, this, PinInfo->PinDefaultValue, UseDefaultValue, UseDefaultObject, UseDefaultText,false); + + if (!UseDefaultText.IsEmpty()) + { + // This will have the namespace changed if needed + PinInfo->PinDefaultValue.Reset(); + FTextStringHelper::WriteToBuffer(PinInfo->PinDefaultValue, UseDefaultText); + } + }*/ + UserDefinedPins.Add(PinInfo); } } @@ -323,14 +341,25 @@ void UK2Node_EditablePinBase::PinDefaultValueChanged(UEdGraphPin* Pin) bool UK2Node_EditablePinBase::ModifyUserDefinedPinDefaultValue(TSharedPtr PinInfo, const FString& InDefaultValue) { - const UEdGraphSchema_K2* K2Schema = GetDefault(); FString NewDefaultValue = InDefaultValue; + if (!UpdateEdGraphPinDefaultValue(PinInfo, NewDefaultValue)) + { + return false; + } + + PinInfo->PinDefaultValue = NewDefaultValue; + return true; +} + +bool UK2Node_EditablePinBase::UpdateEdGraphPinDefaultValue(TSharedPtr PinInfo, FString& NewDefaultValue) +{ + const UEdGraphSchema_K2* K2Schema = GetDefault(); // Find and modify the current pin if (UEdGraphPin* OldPin = FindPin(PinInfo->PinName)) { FString SavedDefaultValue = OldPin->DefaultValue; - + K2Schema->SetPinAutogeneratedDefaultValue(OldPin, NewDefaultValue); // Validate the new default value @@ -345,7 +374,6 @@ bool UK2Node_EditablePinBase::ModifyUserDefinedPinDefaultValue(TSharedPtrPinDefaultValue = NewDefaultValue; return true; } diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EnumLiteral.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EnumLiteral.cpp index bdd329d1d111..cdd4fbc03bc9 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EnumLiteral.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_EnumLiteral.cpp @@ -12,6 +12,7 @@ #include "KismetCompilerMisc.h" #include "BlueprintFieldNodeSpawner.h" #include "EditorCategoryUtils.h" +#include "FindInBlueprintManager.h" #include "BlueprintActionDatabaseRegistrar.h" const FName UK2Node_EnumLiteral::GetEnumInputPinName() @@ -34,6 +35,34 @@ void UK2Node_EnumLiteral::ValidateNodeDuringCompilation(class FCompilerResultsLo } } +void UK2Node_EnumLiteral::AddSearchMetaDataInfo(TArray& OutTaggedMetaData) const +{ + Super::AddSearchMetaDataInfo(OutTaggedMetaData); + + const UEdGraphPin* Pin = FindPinChecked(GetEnumInputPinName()); + if (!Pin->DefaultValue.IsEmpty()) + { + OutTaggedMetaData.Add(FSearchTagDataPair(FFindInBlueprintSearchTags::FiB_NativeName, FText::FromString(Pin->DefaultValue))); + + const int32 ValueIndex = Enum ? Enum->GetIndexByName(*Enum->GenerateFullEnumName(*Pin->DefaultValue)) : INDEX_NONE; + if (ValueIndex != INDEX_NONE) + { + FText SearchName = FText::FormatOrdered(NSLOCTEXT("K2Node", "EnumLiteral_SearchName", "{0} - {1}"), GetTooltipText(), Enum->GetDisplayNameTextByIndex(ValueIndex)); + + // Find the Name, populated by Super::AddSearchMetaDataInfo + for (FSearchTagDataPair& SearchData : OutTaggedMetaData) + { + // Should always be the first item, but there is no guarantee + if (SearchData.Key.CompareTo(FFindInBlueprintSearchTags::FiB_Name) == 0) + { + SearchData.Value = SearchName; + break; + } + } + } + } +} + void UK2Node_EnumLiteral::AllocateDefaultPins() { const UEdGraphSchema_K2* Schema = GetDefault(); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_Event.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_Event.cpp index 7de3f908c064..70a486c33462 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_Event.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_Event.cpp @@ -16,6 +16,7 @@ #include "KismetCompilerMisc.h" #include "KismetCompiler.h" #include "EventEntryHandler.h" +#include "DiffResults.h" const FName UK2Node_Event::DelegateOutputName(TEXT("OutputDelegate")); @@ -778,7 +779,7 @@ FText UK2Node_Event::GetMenuCategory() const return FunctionCategory; } -bool UK2Node_Event::IsDeprecated() const +bool UK2Node_Event::HasDeprecatedReference() const { if (UFunction* Function = EventReference.ResolveMember(GetBlueprintClassFromNode())) { @@ -788,17 +789,31 @@ bool UK2Node_Event::IsDeprecated() const return false; } -FString UK2Node_Event::GetDeprecationMessage() const +FEdGraphNodeDeprecationResponse UK2Node_Event::GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const { - if (UFunction* Function = EventReference.ResolveMember(GetBlueprintClassFromNode())) + FEdGraphNodeDeprecationResponse Response = Super::GetDeprecationResponse(DeprecationType); + if (DeprecationType == EEdGraphNodeDeprecationType::NodeHasDeprecatedReference) { - if (Function->HasMetaData(FBlueprintMetadata::MD_DeprecationMessage)) + // Only warn on override usage. + if (bOverrideFunction) { - return FString::Printf(TEXT("%s %s"), *LOCTEXT("EventDeprecated_Warning", "@@ is deprecated;").ToString(), *Function->GetMetaData(FBlueprintMetadata::MD_DeprecationMessage)); + UFunction* Function = EventReference.ResolveMember(GetBlueprintClassFromNode()); + if (ensureMsgf(Function != nullptr, TEXT("This node should not be able to report having a deprecated reference if the event override cannot be resolved."))) + { + FText EventName = FText::FromName(GetFunctionName()); + FText DetailedMessage = FText::FromString(Function->GetMetaData(FBlueprintMetadata::MD_DeprecationMessage)); + Response.MessageText = FBlueprintEditorUtils::GetDeprecatedMemberUsageNodeWarning(EventName, DetailedMessage); + } + } + else + { + // Allow the source event to be marked as deprecated in the class that defines it without warning, but use a note to visually indicate that the definition itself has been deprecated. + Response.MessageType = EEdGraphNodeDeprecationMessageType::Note; + Response.MessageText = LOCTEXT("DeprecatedEventMessage", "@@: This event has been marked as deprecated. It can be safely deleted if all references have been replaced or removed."); } } - return Super::GetDeprecationMessage(); + return Response; } UObject* UK2Node_Event::GetJumpTargetForDoubleClick() const @@ -838,6 +853,27 @@ FString UK2Node_Event::GetFindReferenceSearchString() const return FunctionName; } +void UK2Node_Event::FindDiffs(UEdGraphNode* OtherNode, struct FDiffResults& Results) +{ + Super::FindDiffs(OtherNode, Results); + UK2Node_Event* OtherFunction = Cast(OtherNode); + + if (OtherFunction) + { + if (FunctionFlags != OtherFunction->FunctionFlags) + { + FDiffSingleResult Diff; + Diff.Diff = EDiffType::NODE_PROPERTY; + Diff.Node1 = this; + Diff.Node2 = OtherNode; + Diff.DisplayString = LOCTEXT("DIF_EventFlags", "Event flags have changed"); + Diff.DisplayColor = FLinearColor(0.25f, 0.71f, 0.85f); + + Results.Add(Diff); + } + } +} + bool UK2Node_Event::AreEventNodesIdentical(const UK2Node_Event* InNodeA, const UK2Node_Event* InNodeB) { return InNodeA->EventReference.GetMemberName() == InNodeB->EventReference.GetMemberName() diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FunctionEntry.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FunctionEntry.cpp index 6854302483dc..56d71a07f4f0 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FunctionEntry.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FunctionEntry.cpp @@ -24,6 +24,7 @@ #include "KismetCompilerMisc.h" #include "KismetCompiler.h" #include "Misc/OutputDeviceNull.h" +#include "DiffResults.h" #define LOCTEXT_NAMESPACE "K2Node_FunctionEntry" @@ -195,6 +196,7 @@ UK2Node_FunctionEntry::UK2Node_FunctionEntry(const FObjectInitializer& ObjectIni { // Enforce const-correctness by default bEnforceConstCorrectness = true; + bUpdatedDefaultValuesOnLoad = false; } void UK2Node_FunctionEntry::PreSave(const class ITargetPlatform* TargetPlatform) @@ -202,38 +204,23 @@ void UK2Node_FunctionEntry::PreSave(const class ITargetPlatform* TargetPlatform) Super::PreSave(TargetPlatform); const UBlueprint* Blueprint = HasValidBlueprint() ? GetBlueprint() : nullptr; - if (LocalVariables.Num() > 0 && Blueprint) + if (Blueprint && LocalVariables.Num() > 0) { - // This code is here as it's unsafe to call when GIsSavingPackage is true - UFunction* Function = FindField(Blueprint->SkeletonGeneratedClass, *GetOuter()->GetName()); + // Forcibly fixup defaults before we save + UpdateLoadedDefaultValues(true); + } +} - if (Function) - { - if (Function->GetStructureSize() > 0 || !ensure(Function->PropertyLink == nullptr)) - { - TSharedPtr LocalVarData = MakeShareable(new FStructOnScope(Function)); +void UK2Node_FunctionEntry::PostLoad() +{ + Super::PostLoad(); - for (TFieldIterator It(Function); It; ++It) - { - if (const UProperty* Property = *It) - { - const UStructProperty* PotentialUDSProperty = Cast(Property); - // UDS requires default data even when the LocalVariable value is empty + if (GIsEditor) + { + // In the editor, we need to handle processing function default values at load time so they get picked up properly by the cooker + // This normally won't do anything because it gets called during the duplicate save during BP compilation, but if compilation gets skipped we need to make sure they get updated - for (FBPVariableDescription& LocalVar : LocalVariables) - { - if (LocalVar.VarName == Property->GetFName() && !LocalVar.DefaultValue.IsEmpty()) - { - // Go to property and back, this handles redirector fixup and will sanitize the output - // The asset registry only knows about these references because when the node is expanded it turns into a hard reference - FBlueprintEditorUtils::PropertyValueFromString(Property, LocalVar.DefaultValue, LocalVarData->GetStructMemory()); - FBlueprintEditorUtils::PropertyValueToString(Property, LocalVarData->GetStructMemory(), LocalVar.DefaultValue); - } - } - } - } - } - } + UpdateLoadedDefaultValues(); } } @@ -244,38 +231,18 @@ void UK2Node_FunctionEntry::Serialize(FArchive& Ar) Ar.UsingCustomVersion(FBlueprintsObjectVersion::GUID); if (Ar.IsSaving()) - { - for (FBPVariableDescription& LocalVariable : LocalVariables) + { + if (Ar.IsObjectReferenceCollector() || Ar.Tell() < 0) { - if (!LocalVariable.DefaultValue.IsEmpty()) + // If this is explicitly a reference collector, or it's a save with no backing archive, then we want to use the function variable cache if it exists + // It's not safe to regenerate the cache at this point as we could be in GIsSaving + if (FunctionVariableCache.IsValid() && FunctionVariableCache->IsValid()) { - // If looking for references during save, expand any default values on the local variables - // This is only reliable when saving in the editor, the cook case is handled below - if (Ar.IsObjectReferenceCollector() && LocalVariable.VarType.PinCategory == UEdGraphSchema_K2::PC_Struct && LocalVariable.VarType.PinSubCategoryObject.IsValid()) - { - UScriptStruct* Struct = Cast(LocalVariable.VarType.PinSubCategoryObject.Get()); + UStruct* Struct = const_cast(FunctionVariableCache->GetStruct()); + Struct->SerializeBin(Ar, FunctionVariableCache->GetStructMemory()); - if (Struct) - { - TSharedPtr StructData = MakeShareable(new FStructOnScope(Struct)); - - // Import the literal text to a dummy struct and then serialize that. Hard object references will not properly import, this is only useful for soft references! - FOutputDeviceNull NullOutput; - Struct->ImportText(*LocalVariable.DefaultValue, StructData->GetStructMemory(), nullptr, PPF_SerializedAsImportText, &NullOutput, LocalVariable.VarName.ToString()); - Struct->SerializeItem(Ar, StructData->GetStructMemory(), nullptr); - } - } - - if (LocalVariable.VarType.PinCategory == UEdGraphSchema_K2::PC_SoftObject || LocalVariable.VarType.PinCategory == UEdGraphSchema_K2::PC_SoftClass) - { - FSoftObjectPath TempRef(LocalVariable.DefaultValue); - - // Serialize the asset reference, this will do the save fixup. It won't actually serialize the string if this is a real archive like linkersave - FSoftObjectPathSerializationScope DisableSerialize(NAME_None, NAME_None, ESoftObjectPathCollectType::AlwaysCollect, ESoftObjectPathSerializeType::SkipSerializeIfArchiveHasSize); - Ar << TempRef; - - LocalVariable.DefaultValue = TempRef.ToString(); - } + // Copy back into defaults as they may have changed + UpdateDefaultsFromVariableStruct(FunctionVariableCache->GetStruct(), FunctionVariableCache->GetStructMemory()); } } } @@ -338,24 +305,6 @@ void UK2Node_FunctionEntry::Serialize(FArchive& Ar) } } } - - // In editor, fixup soft object ptrs on load on to handle redirects and finding refs for cooking - // We're not handling soft object ptrs inside FStructs because it's a rare edge case and would be a performance hit on load - if (GIsEditor) - { - FSoftObjectPathSerializationScope SetPackage(GetOutermost()->GetFName(), NAME_None, ESoftObjectPathCollectType::AlwaysCollect, ESoftObjectPathSerializeType::SkipSerializeIfArchiveHasSize); - - for (FBPVariableDescription& LocalVariable : LocalVariables) - { - if (!LocalVariable.DefaultValue.IsEmpty() && (LocalVariable.VarType.PinCategory == UEdGraphSchema_K2::PC_SoftObject || LocalVariable.VarType.PinCategory == UEdGraphSchema_K2::PC_SoftClass)) - { - FSoftObjectPath TempRef(LocalVariable.DefaultValue); - TempRef.PostLoadPath(&Ar); - TempRef.PreSavePath(); - LocalVariable.DefaultValue = TempRef.ToString(); - } - } - } } } @@ -370,8 +319,12 @@ FText UK2Node_FunctionEntry::GetNodeTitle(ENodeTitleType::Type TitleType) const void UK2Node_FunctionEntry::AllocateDefaultPins() { + // Update our default values before copying them into pins + UpdateLoadedDefaultValues(); + CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then); + // Find any pins inherited from parent if (UFunction* Function = FunctionReference.ResolveMember(GetBlueprintClassFromNode())) { CreatePinsForFunctionEntryExit(Function, /*bIsFunctionEntry=*/ true); @@ -434,6 +387,157 @@ UEdGraphPin* UK2Node_FunctionEntry::CreatePinFromUserDefinition(const TSharedPtr return NewPin; } +TSharedPtr UK2Node_FunctionEntry::GetFunctionVariableCache(bool bForceRefresh) +{ + if (bForceRefresh && FunctionVariableCache.IsValid()) + { + // On force refresh, delete old one if it exists + FunctionVariableCache.Reset(); + } + + if (!FunctionVariableCache.IsValid() || !FunctionVariableCache->IsValid()) + { + if (UFunction* const Function = FindSignatureFunction()) + { + if (LocalVariables.Num() > 0) + { + FunctionVariableCache = MakeShared(Function); + FunctionVariableCache->SetPackage(GetOutermost()); + + RefreshFunctionVariableCache(); + } + } + } + + return FunctionVariableCache; +} + +bool UK2Node_FunctionEntry::RefreshFunctionVariableCache() +{ + GetFunctionVariableCache(false); + + if (FunctionVariableCache.IsValid()) + { + // Update the cache if it was created + return UpdateVariableStructFromDefaults(FunctionVariableCache->GetStruct(), FunctionVariableCache->GetStructMemory()); + } + return false; +} + +bool UK2Node_FunctionEntry::UpdateLoadedDefaultValues(bool bForceRefresh) +{ + // If we don't have a cache or it's force refresh, create one + if (!bUpdatedDefaultValuesOnLoad || bForceRefresh) + { + GetFunctionVariableCache(bForceRefresh); + + bUpdatedDefaultValuesOnLoad = true; + + if (FunctionVariableCache.IsValid()) + { + // Now copy back into the default value strings + return UpdateDefaultsFromVariableStruct(FunctionVariableCache->GetStruct(), FunctionVariableCache->GetStructMemory()); + } + else + { + // No variable cache created + return true; + } + } + + return false; +} + +void UK2Node_FunctionEntry::ClearCachedBlueprintData(UBlueprint* Blueprint) +{ + FunctionVariableCache.Reset(); +} + +bool UK2Node_FunctionEntry::UpdateVariableStructFromDefaults(const UStruct* VariableStruct, uint8* VariableStructData) +{ + const UEdGraphSchema_K2* K2Schema = GetDefault(); + + if (!VariableStruct || !VariableStructData) + { + return false; + } + + for (FBPVariableDescription& LocalVariable : LocalVariables) + { + if (!LocalVariable.DefaultValue.IsEmpty()) + { + UProperty* PinProperty = VariableStruct->FindPropertyByName(LocalVariable.VarName); + + if (PinProperty && (!PinProperty->HasAnyPropertyFlags(CPF_OutParm) || PinProperty->HasAnyPropertyFlags(CPF_ReferenceParm))) + { + FEdGraphPinType PinType; + K2Schema->ConvertPropertyToPinType(PinProperty, /*out*/ PinType); + + if (PinType != LocalVariable.VarType) + { + //UE_LOG(LogBlueprint, Log, TEXT("Pin type for local variable %s does not match type on struct %s during UpdateVariableStructFromDefaults, ignoring old default"), *LocalVariable.VarName.ToString(), *VariableStruct->GetName()); + } + else + { + FBlueprintEditorUtils::PropertyValueFromString(PinProperty, LocalVariable.DefaultValue, VariableStructData, this); + } + } + else + { + //UE_LOG(LogBlueprint, Log, TEXT("Could not find local variable property %s on struct %s during UpdateVariableStructFromDefaults"), *LocalVariable.VarName.ToString(), *VariableStruct->GetName()); + } + } + } + + return true; +} + +bool UK2Node_FunctionEntry::UpdateDefaultsFromVariableStruct(const UStruct* VariableStruct, uint8* VariableStructData) +{ + const UEdGraphSchema_K2* K2Schema = GetDefault(); + + if (!VariableStruct || !VariableStructData) + { + return false; + } + + for (FBPVariableDescription& LocalVariable : LocalVariables) + { + if (!LocalVariable.DefaultValue.IsEmpty()) + { + // We don't want to write out fields that were empty before, as they were guaranteed to not have actual real data + UProperty* PinProperty = VariableStruct->FindPropertyByName(LocalVariable.VarName); + + if (PinProperty && (!PinProperty->HasAnyPropertyFlags(CPF_OutParm) || PinProperty->HasAnyPropertyFlags(CPF_ReferenceParm))) + { + FEdGraphPinType PinType; + K2Schema->ConvertPropertyToPinType(PinProperty, /*out*/ PinType); + + if (PinType != LocalVariable.VarType) + { + //UE_LOG(LogBlueprint, Log, TEXT("Pin type for local variable %s does not match type on struct %s during UpdateDefaultsFromVariableStruct, ignoring old default"), *LocalVariable.VarName.ToString(), *VariableStruct->GetName()); + } + else + { + FString NewValue; + FBlueprintEditorUtils::PropertyValueToString(PinProperty, VariableStructData, NewValue, this); + if (NewValue != LocalVariable.DefaultValue) + { + LocalVariable.DefaultValue = NewValue; + } + } + } + else + { + //UE_LOG(LogBlueprint, Log, TEXT("Could not find local variable property %s on struct %s during UpdateDefaultsFromVariableStruct"), *LocalVariable.VarName.ToString(), *VariableStruct->GetName()); + } + } + } + + return true; +} + + FNodeHandlingFunctor* UK2Node_FunctionEntry::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const { return new FKCHandler_FunctionEntry(CompilerContext); @@ -459,38 +563,138 @@ void UK2Node_FunctionEntry::GetRedirectPinNames(const UEdGraphPin& Pin, TArray(GetBlueprintClassFromNode())) { return Function->HasMetaData(FBlueprintMetadata::MD_DeprecatedFunction); } - - return false; + else + { + return MetaData.bIsDeprecated; + } } -FString UK2Node_FunctionEntry::GetDeprecationMessage() const +FEdGraphNodeDeprecationResponse UK2Node_FunctionEntry::GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const { - if (UFunction* const Function = FunctionReference.ResolveMember(GetBlueprintClassFromNode())) + FEdGraphNodeDeprecationResponse Response = Super::GetDeprecationResponse(DeprecationType); + if (DeprecationType == EEdGraphNodeDeprecationType::NodeHasDeprecatedReference) { - if (Function->HasMetaData(FBlueprintMetadata::MD_DeprecationMessage)) + // Only warn on non-editable (i.e. override) usage. + if (!IsEditable()) { - return FString::Printf(TEXT("%s %s"), *LOCTEXT("FunctionDeprecated_Warning", "@@ is deprecated;").ToString(), *Function->GetMetaData(FBlueprintMetadata::MD_DeprecationMessage)); + UFunction* const Function = FunctionReference.ResolveMember(GetBlueprintClassFromNode()); + if (ensureMsgf(Function != nullptr, TEXT("This node should not be able to report having a deprecated reference if the override function cannot be resolved."))) + { + FText FunctionName = FText::FromName(FunctionReference.GetMemberName()); + FText DetailedMessage = FText::FromString(Function->GetMetaData(FBlueprintMetadata::MD_DeprecationMessage)); + Response.MessageText = FBlueprintEditorUtils::GetDeprecatedMemberUsageNodeWarning(FunctionName, DetailedMessage); + } + } + else + { + // Allow the function to be marked as deprecated in the class that defines it without warning, but use a note to visually indicate that the definition itself has been deprecated. + Response.MessageType = EEdGraphNodeDeprecationMessageType::Note; + Response.MessageText = LOCTEXT("DeprecatedFunctionMessage", "@@: This function has been marked as deprecated. It can be safely deleted if all references have been replaced or removed."); } } - return Super::GetDeprecationMessage(); + return Response; } FText UK2Node_FunctionEntry::GetTooltipText() const { - if (UFunction* const Function = FunctionReference.ResolveMember(GetBlueprintClassFromNode())) + if (UFunction* const Function = FindSignatureFunction()) { return FText::FromString(UK2Node_CallFunction::GetDefaultTooltipForFunction(Function)); } return Super::GetTooltipText(); } +void UK2Node_FunctionEntry::FindDiffs(UEdGraphNode* OtherNode, struct FDiffResults& Results) +{ + Super::FindDiffs(OtherNode, Results); + UK2Node_FunctionEntry* OtherFunction = Cast(OtherNode); + + if (OtherFunction) + { + if (ExtraFlags != OtherFunction->ExtraFlags) + { + FDiffSingleResult Diff; + Diff.Diff = EDiffType::NODE_PROPERTY; + Diff.Node1 = this; + Diff.Node2 = OtherNode; + Diff.DisplayString = LOCTEXT("DIF_FunctionFlags", "Function flags have changed"); + Diff.DisplayColor = FLinearColor(0.25f, 0.71f, 0.85f); + + Results.Add(Diff); + } + + if (!FKismetUserDeclaredFunctionMetadata::StaticStruct()->CompareScriptStruct(&MetaData, &OtherFunction->MetaData, 0)) + { + FDiffSingleResult Diff; + Diff.Diff = EDiffType::NODE_PROPERTY; + Diff.Node1 = this; + Diff.Node2 = OtherNode; + Diff.DisplayString = LOCTEXT("DIF_FunctionMetadata", "Function metadata has changed"); + Diff.DisplayColor = FLinearColor(0.25f, 0.71f, 0.85f); + + Results.Add(Diff); + } + + bool bLocalVarsDiffer = (LocalVariables.Num() != OtherFunction->LocalVariables.Num()); + + for (int32 i = 0; i < LocalVariables.Num() && !bLocalVarsDiffer; i++) + { + const FBPVariableDescription& ThisVar = LocalVariables[i]; + const FBPVariableDescription& OtherVar = OtherFunction->LocalVariables[i]; + + // Can't do a raw compare, for local variable defaults we need to compare the struct + if (ThisVar.VarName != OtherVar.VarName + || ThisVar.VarType != OtherVar.VarType + || ThisVar.FriendlyName != OtherVar.FriendlyName + || !ThisVar.Category.EqualTo(OtherVar.Category) + || ThisVar.PropertyFlags != OtherVar.PropertyFlags + || ThisVar.RepNotifyFunc != OtherVar.RepNotifyFunc + || ThisVar.ReplicationCondition != OtherVar.ReplicationCondition) + { + bLocalVarsDiffer = true; + } + } + + if (bLocalVarsDiffer) + { + FDiffSingleResult Diff; + Diff.Diff = EDiffType::NODE_PROPERTY; + Diff.Node1 = this; + Diff.Node2 = OtherNode; + Diff.DisplayString = LOCTEXT("DIF_FunctionLocalVariables", "Function local variables have changed in structure"); + Diff.DisplayColor = FLinearColor(0.25f, 0.71f, 0.85f); + + Results.Add(Diff); + } + else + { + TSharedPtr MyLocals = GetFunctionVariableCache(); + TSharedPtr OtherLocals = OtherFunction->GetFunctionVariableCache(); + + if (MyLocals.IsValid() && MyLocals->IsValid() && OtherLocals.IsValid() && OtherLocals->IsValid()) + { + // Check for local var diffs + FDiffSingleResult Diff; + Diff.Diff = EDiffType::NODE_PROPERTY; + Diff.Node1 = this; + Diff.Node2 = OtherNode; + Diff.ToolTip = LOCTEXT("DIF_FunctionLocalVariableDefaults", "Function local variable default values have changed"); + Diff.DisplayColor = FLinearColor(0.25f, 0.71f, 0.85f); + + DiffProperties(const_cast(MyLocals->GetStruct()), const_cast(OtherLocals->GetStruct()), MyLocals->GetStructMemory(), OtherLocals->GetStructMemory(), Results, Diff); + } + } + } +} + int32 UK2Node_FunctionEntry::GetFunctionFlags() const { int32 ReturnFlags = 0; @@ -631,6 +835,31 @@ void UK2Node_FunctionEntry::PostReconstructNode() Super::PostReconstructNode(); } +void UK2Node_FunctionEntry::FixupPinStringDataReferences(FArchive* SavingArchive) +{ + Super::FixupPinStringDataReferences(SavingArchive); + if (SavingArchive) + { + // If any of our pins got fixed up, we need to refresh our user pin default values + // For custom events, the Pin default values are authoritative + for (TSharedPtr PinInfo : UserDefinedPins) + { + if (UEdGraphPin* Pin = FindPin(PinInfo->PinName)) + { + if (Pin->Direction == PinInfo->DesiredPinDirection) + { + FString DefaultsString = Pin->GetDefaultAsString(); + + if (DefaultsString != PinInfo->PinDefaultValue) + { + ModifyUserDefinedPinDefaultValue(PinInfo, DefaultsString); + } + } + } + } + } +} + bool UK2Node_FunctionEntry::ModifyUserDefinedPinDefaultValue(TSharedPtr PinInfo, const FString& NewDefaultValue) { if (Super::ModifyUserDefinedPinDefaultValue(PinInfo, NewDefaultValue)) @@ -638,6 +867,8 @@ bool UK2Node_FunctionEntry::ModifyUserDefinedPinDefaultValue(TSharedPtr(); K2Schema->HandleParameterDefaultValueChanged(this); + RefreshFunctionVariableCache(); + return true; } return false; diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FunctionTerminator.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FunctionTerminator.cpp index 2f0cfec3c1e2..541546d8289c 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FunctionTerminator.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FunctionTerminator.cpp @@ -121,6 +121,20 @@ void UK2Node_FunctionTerminator::PromoteFromInterfaceOverride(bool bIsPrimaryTer Schema->ReconstructNode(*this, true); } +UFunction* UK2Node_FunctionTerminator::FindSignatureFunction() const +{ + UClass* FoundClass = GetBlueprintClassFromNode(); + UFunction* FoundFunction = FunctionReference.ResolveMember(FoundClass); + + if (!FoundFunction && FoundClass && GetOuter()) + { + // The resolve will fail if this is a locally-created function, so search using the event graph name + FoundFunction = FindField(FoundClass, *GetOuter()->GetName()); + } + + return FoundFunction; +} + void UK2Node_FunctionTerminator::ValidateNodeDuringCompilation(FCompilerResultsLog& MessageLog) const { Super::ValidateNodeDuringCompilation(MessageLog); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_GenericCreateObject.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_GenericCreateObject.cpp index 93684c2e6bca..8a00e4d7964b 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_GenericCreateObject.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_GenericCreateObject.cpp @@ -18,7 +18,8 @@ struct FK2Node_GenericCreateObject_Utils && (bAllowAbstract || !ObjectClass->HasAnyClassFlags(CLASS_Abstract)) && !ObjectClass->HasAnyClassFlags(CLASS_Deprecated | CLASS_NewerVersionExists); - if (bCanSpawnObject) + // UObject is a special case where if we are allowing abstract we are going to allow it through even though it doesn't have BlueprintType on it + if (bCanSpawnObject && (!bAllowAbstract || (*ObjectClass != UObject::StaticClass()))) { static const FName BlueprintTypeName(TEXT("BlueprintType")); static const FName NotBlueprintTypeName(TEXT("NotBlueprintType")); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_GetArrayItem.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_GetArrayItem.cpp index 95a7f9bc3dc6..41ebb1944df1 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_GetArrayItem.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_GetArrayItem.cpp @@ -194,7 +194,7 @@ void UK2Node_GetArrayItem::GetContextMenuActions(const FGraphNodeContextMenuBuil bReturnIsRef ? LOCTEXT("ChangeNodeToRef", "Change to return a copy") : LOCTEXT("ChangeNodeToVal", "Change to return a reference"), ToggleTooltip, FSlateIcon(), - FUIAction(FExecuteAction::CreateUObject(this, &UK2Node_GetArrayItem::ToggleReturnPin), + FUIAction(FExecuteAction::CreateUObject(const_cast(this), &UK2Node_GetArrayItem::ToggleReturnPin), FCanExecuteAction::CreateLambda([bCannotReturnRef]()->bool { return !bCannotReturnRef; })) ); Context.MenuBuilder->EndSection(); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_LoadAsset.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_LoadAsset.cpp index 3edabb1b0dd5..5ed4d2227e65 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_LoadAsset.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_LoadAsset.cpp @@ -202,7 +202,7 @@ void UK2Node_LoadAsset::ExpandNode(class FKismetCompilerContext& CompilerContext FText UK2Node_LoadAsset::GetTooltipText() const { - return FText(LOCTEXT("UK2Node_LoadAssetGetTooltipText", "Async Load Asset")); + return FText(LOCTEXT("UK2Node_LoadAssetGetTooltipText", "Asynchronously loads a Soft Object Reference and returns object of the correct type if the load succeeds")); } FText UK2Node_LoadAsset::GetNodeTitle(ENodeTitleType::Type TitleType) const @@ -289,7 +289,7 @@ FName UK2Node_LoadAssetClass::NativeFunctionName() const FText UK2Node_LoadAssetClass::GetTooltipText() const { - return FText(LOCTEXT("UK2Node_LoadAssetClassGetTooltipText", "Async Load Class Asset")); + return FText(LOCTEXT("UK2Node_LoadAssetClassGetTooltipText", "Asynchronously loads a Soft Class Reference and returns class of the correct type if the load succeeds")); } FText UK2Node_LoadAssetClass::GetNodeTitle(ENodeTitleType::Type TitleType) const diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MCDelegate.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MCDelegate.cpp index 3abdf5c33739..b72803367eb9 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MCDelegate.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MCDelegate.cpp @@ -17,6 +17,7 @@ #include "K2Node_CallDelegate.h" #include "K2Node_ClearDelegate.h" #include "K2Node_RemoveDelegate.h" +#include "Kismet2/BlueprintEditorUtils.h" #include "KismetCompiler.h" #include "DelegateNodeHandlers.h" @@ -269,6 +270,27 @@ void UK2Node_BaseMCDelegate::AutowireNewNode(UEdGraphPin* FromPin) } } +bool UK2Node_BaseMCDelegate::HasDeprecatedReference() const +{ + // Check if the referenced delegate is deprecated. + return DelegateReference.IsDeprecated(); +} + +FEdGraphNodeDeprecationResponse UK2Node_BaseMCDelegate::GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const +{ + FEdGraphNodeDeprecationResponse Response = Super::GetDeprecationResponse(DeprecationType); + if (DeprecationType == EEdGraphNodeDeprecationType::NodeHasDeprecatedReference) + { + if (UProperty* DelegateProperty = DelegateReference.ResolveMember(GetBlueprintClassFromNode())) + { + FString DetailedMessage = DelegateProperty->GetMetaData(FBlueprintMetadata::MD_DeprecationMessage); + Response.MessageText = FBlueprintEditorUtils::GetDeprecatedMemberUsageNodeWarning(GetPropertyDisplayName(), FText::FromString(DetailedMessage)); + } + } + + return Response; +} + /////// UK2Node_AddDelegate /////////// UK2Node_AddDelegate::UK2Node_AddDelegate(const FObjectInitializer& ObjectInitializer) diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeArray.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeArray.cpp index 67365db2c8ac..90762d0f3a78 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeArray.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeArray.cpp @@ -89,7 +89,7 @@ void UK2Node_MakeArray::GetContextMenuActions(const FGraphNodeContextMenuBuilder LOCTEXT("RemovePinTooltip", "Remove this array element pin"), FSlateIcon(), FUIAction( - FExecuteAction::CreateUObject(this, &UK2Node_MakeArray::RemoveInputPin, const_cast(Context.Pin)) + FExecuteAction::CreateUObject(const_cast(this), &UK2Node_MakeArray::RemoveInputPin, const_cast(Context.Pin)) ) ); } @@ -101,7 +101,7 @@ void UK2Node_MakeArray::GetContextMenuActions(const FGraphNodeContextMenuBuilder LOCTEXT("AddPinTooltip", "Add another array element pin"), FSlateIcon(), FUIAction( - FExecuteAction::CreateUObject(this, &UK2Node_MakeArray::InteractiveAddInputPin) + FExecuteAction::CreateUObject(const_cast(this), &UK2Node_MakeArray::InteractiveAddInputPin) ) ); } @@ -111,7 +111,7 @@ void UK2Node_MakeArray::GetContextMenuActions(const FGraphNodeContextMenuBuilder LOCTEXT("ResetToWildcardTooltip", "Reset the node to have wildcard input/outputs. Requires no pins are connected."), FSlateIcon(), FUIAction( - FExecuteAction::CreateUObject(this, &UK2Node_MakeArray::ClearPinTypeToWildcard), + FExecuteAction::CreateUObject(const_cast(this), &UK2Node_MakeArray::ClearPinTypeToWildcard), FCanExecuteAction::CreateUObject(this, &UK2Node_MakeArray::CanResetToWildcard) ) ); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeMap.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeMap.cpp index a5797a7affe3..35c0f1edaea4 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeMap.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeMap.cpp @@ -161,7 +161,7 @@ void UK2Node_MakeMap::GetContextMenuActions(const FGraphNodeContextMenuBuilder& LOCTEXT("RemovePinTooltip", "Remove this pin and its corresponding key/value pin"), FSlateIcon(), FUIAction( - FExecuteAction::CreateUObject(this, &UK2Node_MakeMap::RemoveInputPin, const_cast(Context.Pin)) + FExecuteAction::CreateUObject(const_cast(this), &UK2Node_MakeMap::RemoveInputPin, const_cast(Context.Pin)) ) ); } @@ -173,7 +173,7 @@ void UK2Node_MakeMap::GetContextMenuActions(const FGraphNodeContextMenuBuilder& LOCTEXT("AddPinTooltip", "Add another pair of key/value pins"), FSlateIcon(), FUIAction( - FExecuteAction::CreateUObject(this, &UK2Node_MakeMap::InteractiveAddInputPin) + FExecuteAction::CreateUObject(const_cast(this), &UK2Node_MakeMap::InteractiveAddInputPin) ) ); } @@ -183,7 +183,7 @@ void UK2Node_MakeMap::GetContextMenuActions(const FGraphNodeContextMenuBuilder& LOCTEXT("ResetToWildcardTooltip", "Reset the node to have wildcard input/outputs. Requires no pins are connected."), FSlateIcon(), FUIAction( - FExecuteAction::CreateUObject(this, &UK2Node_MakeMap::ClearPinTypeToWildcard), + FExecuteAction::CreateUObject(const_cast(this), &UK2Node_MakeMap::ClearPinTypeToWildcard), FCanExecuteAction::CreateUObject(this, &UK2Node_MakeMap::CanResetToWildcard) ) ); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeSet.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeSet.cpp index 772a5c3c5aa2..3dfcbce4251b 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeSet.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeSet.cpp @@ -89,7 +89,7 @@ void UK2Node_MakeSet::GetContextMenuActions(const FGraphNodeContextMenuBuilder& LOCTEXT("RemovePinTooltip", "Remove this set element pin"), FSlateIcon(), FUIAction( - FExecuteAction::CreateUObject(this, &UK2Node_MakeSet::RemoveInputPin, const_cast(Context.Pin)) + FExecuteAction::CreateUObject(const_cast(this), &UK2Node_MakeSet::RemoveInputPin, const_cast(Context.Pin)) ) ); } @@ -101,7 +101,7 @@ void UK2Node_MakeSet::GetContextMenuActions(const FGraphNodeContextMenuBuilder& LOCTEXT("AddPinTooltip", "Add another set element pin"), FSlateIcon(), FUIAction( - FExecuteAction::CreateUObject(this, &UK2Node_MakeSet::InteractiveAddInputPin) + FExecuteAction::CreateUObject(const_cast(this), &UK2Node_MakeSet::InteractiveAddInputPin) ) ); } @@ -111,7 +111,7 @@ void UK2Node_MakeSet::GetContextMenuActions(const FGraphNodeContextMenuBuilder& LOCTEXT("ResetToWildcardTooltip", "Reset the node to have wildcard input/outputs. Requires no pins are connected."), FSlateIcon(), FUIAction( - FExecuteAction::CreateUObject(this, &UK2Node_MakeSet::ClearPinTypeToWildcard), + FExecuteAction::CreateUObject(const_cast(this), &UK2Node_MakeSet::ClearPinTypeToWildcard), FCanExecuteAction::CreateUObject(this, &UK2Node_MakeSet::CanResetToWildcard) ) ); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeStruct.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeStruct.cpp index f8e0a2a1aa13..c7e71bc76750 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeStruct.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeStruct.cpp @@ -243,7 +243,7 @@ FLinearColor UK2Node_MakeStruct::GetNodeTitleColor() const bool UK2Node_MakeStruct::CanBeMade(const UScriptStruct* Struct, const bool bForInternalUse) { - return (Struct && !Struct->HasMetaData(TEXT("HasNativeMake")) && UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Struct, bForInternalUse)); + return (Struct && !Struct->HasMetaData(FBlueprintMetadata::MD_NativeMakeFunction) && UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Struct, bForInternalUse)); } bool UK2Node_MakeStruct::CanBeSplit(const UScriptStruct* Struct) diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeVariable.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeVariable.cpp index dc3e6cb0605a..357510ba60c2 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeVariable.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeVariable.cpp @@ -100,7 +100,7 @@ void UK2Node_MakeVariable::SetupVariable(const FBPVariableDescription& InVariabl // make input pins for every value in the default value, for map pins we'll make a pair of inputs: TSharedPtr StructData = MakeShareable(new FStructOnScope(Scope)); - FBlueprintEditorUtils::PropertyValueFromString(Property, VariableType.DefaultValue, StructData->GetStructMemory()); + FBlueprintEditorUtils::PropertyValueFromString(Property, VariableType.DefaultValue, StructData->GetStructMemory(), this); if(VariableType.VarType.IsArray()) { diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_SpawnActor.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_SpawnActor.cpp index 52a6ea12ca4f..038fece8d5f2 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_SpawnActor.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_SpawnActor.cpp @@ -494,14 +494,16 @@ bool UK2Node_SpawnActor::IsDeprecated() const return false; } -bool UK2Node_SpawnActor::ShouldWarnOnDeprecation() const +FEdGraphNodeDeprecationResponse UK2Node_SpawnActor::GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const { - return false; -} + FEdGraphNodeDeprecationResponse Response = Super::GetDeprecationResponse(DeprecationType); + if (DeprecationType == EEdGraphNodeDeprecationType::NodeTypeIsDeprecated) + { + Response.MessageType = EEdGraphNodeDeprecationMessageType::None; + Response.MessageText = LOCTEXT("SpawnActorNodeOnlyDefaultBlueprint_Deprecatio", "Spawn Actor @@ is DEPRECATED and should be replaced by SpawnActorFromClass"); + } -FString UK2Node_SpawnActor::GetDeprecationMessage() const -{ - return LOCTEXT("SpawnActorNodeOnlyDefaultBlueprint_Deprecatio", "Spawn Actor @@ is DEPRECATED and should be replaced by SpawnActorFromClass").ToString(); + return Response; } FSlateIcon UK2Node_SpawnActor::GetIconAndTint(FLinearColor& OutColor) const diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_Tunnel.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_Tunnel.cpp index fdcff74bb7a7..9857e7eef053 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_Tunnel.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_Tunnel.cpp @@ -177,6 +177,12 @@ bool UK2Node_Tunnel::CanCreateUserDefinedPin(const FEdGraphPinType& InPinType, E return bResult; } +void UK2Node_Tunnel::ClearCachedBlueprintData(UBlueprint* Blueprint) +{ + // Remove data marking graphs as latent, this will be re-cache'd as needed + MetaData.HasLatentFunctions = INDEX_NONE; +} + UEdGraphPin* UK2Node_Tunnel::CreatePinFromUserDefinition(const TSharedPtr NewPinInfo) { // Create the new pin diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_Variable.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_Variable.cpp index 32fbdda6107c..9a7ac187654b 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_Variable.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_Variable.cpp @@ -952,31 +952,40 @@ void UK2Node_Variable::PostPasteNode() } } -bool UK2Node_Variable::IsDeprecated() const +bool UK2Node_Variable::HasDeprecatedReference() const { - if (Super::IsDeprecated() || VariableReference.IsDeprecated()) + // Check if the referenced variable is deprecated. + if (VariableReference.IsDeprecated()) { return true; } + else if (UProperty* VariableProperty = VariableReference.ResolveMember(GetBlueprintClassFromNode())) + { + // Backcompat: Allow variables tagged only with 'DeprecationMessage' meta to be seen as deprecated if inherited from a native parent class. + const bool bHasDeprecationMessage = VariableProperty->HasMetaData(FBlueprintMetadata::MD_DeprecationMessage); + if (bHasDeprecationMessage && VariableProperty->GetOuter()->IsNative()) + { + return true; + } + } - UProperty* VariableProperty = VariableReference.ResolveMember(GetBlueprintClassFromNode()); - if (VariableProperty - && (VariableProperty->HasAllPropertyFlags(CPF_Deprecated) || VariableProperty->HasMetaData(FBlueprintMetadata::MD_DeprecationMessage))) - { - return true; - } return false; } -FString UK2Node_Variable::GetDeprecationMessage() const +FEdGraphNodeDeprecationResponse UK2Node_Variable::GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const { - UProperty* VariableProperty = VariableReference.ResolveMember(GetBlueprintClassFromNode()); - if (VariableProperty && VariableProperty->HasMetaData(FBlueprintMetadata::MD_DeprecationMessage)) + FEdGraphNodeDeprecationResponse Response = Super::GetDeprecationResponse(DeprecationType); + if (DeprecationType == EEdGraphNodeDeprecationType::NodeHasDeprecatedReference) { - return FString::Printf(TEXT("%s %s"), *LOCTEXT("PropertyDeprecated_Warning", "@@ is deprecated;").ToString(), *VariableProperty->GetMetaData(FBlueprintMetadata::MD_DeprecationMessage)); + if (UProperty* VariableProperty = VariableReference.ResolveMember(GetBlueprintClassFromNode())) + { + FText MemberName = FText::FromName(VariableReference.GetMemberName()); + FText DetailedMessage = FText::FromString(VariableProperty->GetMetaData(FBlueprintMetadata::MD_DeprecationMessage)); + Response.MessageText = FBlueprintEditorUtils::GetDeprecatedMemberUsageNodeWarning(MemberName, DetailedMessage); + } } - return Super::GetDeprecationMessage(); + return Response; } UObject* UK2Node_Variable::GetJumpTargetForDoubleClick() const diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_VariableGet.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_VariableGet.cpp index 4de6fb5af6a4..bd5cb95dd665 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_VariableGet.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_VariableGet.cpp @@ -361,7 +361,7 @@ void UK2Node_VariableGet::GetContextMenuActions(const FGraphNodeContextMenuBuild MenuEntryTooltip, FSlateIcon(), FUIAction( - FExecuteAction::CreateUObject(this, &UK2Node_VariableGet::TogglePurity), + FExecuteAction::CreateUObject(const_cast(this), &UK2Node_VariableGet::TogglePurity), FCanExecuteAction::CreateStatic(CanExecutePurityToggle, bCanTogglePurity && !Context.bIsDebugging), FIsActionChecked() ) diff --git a/Engine/Source/Editor/BlueprintGraph/Public/BlueprintActionDatabase.h b/Engine/Source/Editor/BlueprintGraph/Public/BlueprintActionDatabase.h index 10c18bcb3949..897e29641470 100644 --- a/Engine/Source/Editor/BlueprintGraph/Public/BlueprintActionDatabase.h +++ b/Engine/Source/Editor/BlueprintGraph/Public/BlueprintActionDatabase.h @@ -52,6 +52,7 @@ public: // FGCObject interface virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + virtual FString GetReferencerName() const override; // End FGCObject interface /** diff --git a/Engine/Source/Editor/BlueprintGraph/Public/BlueprintNodeTemplateCache.h b/Engine/Source/Editor/BlueprintGraph/Public/BlueprintNodeTemplateCache.h index 726857d319c5..35852a2e2b19 100644 --- a/Engine/Source/Editor/BlueprintGraph/Public/BlueprintNodeTemplateCache.h +++ b/Engine/Source/Editor/BlueprintGraph/Public/BlueprintNodeTemplateCache.h @@ -82,6 +82,7 @@ public: // FGCObject interface virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + virtual FString GetReferencerName() const override; // End FGCObject interface private: diff --git a/Engine/Source/Editor/Blutility/Classes/EditorUtilityBlueprint.h b/Engine/Source/Editor/Blutility/Classes/EditorUtilityBlueprint.h index 29bc5e5eff3b..1ea01e816478 100644 --- a/Engine/Source/Editor/Blutility/Classes/EditorUtilityBlueprint.h +++ b/Engine/Source/Editor/Blutility/Classes/EditorUtilityBlueprint.h @@ -12,7 +12,7 @@ #include "EditorUtilityBlueprint.generated.h" UCLASS() -class UEditorUtilityBlueprint : public UBlueprint +class BLUTILITY_API UEditorUtilityBlueprint : public UBlueprint { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Editor/Blutility/Classes/EditorUtilityWidgetBlueprint.h b/Engine/Source/Editor/Blutility/Classes/EditorUtilityWidgetBlueprint.h index ef4f82a134aa..1664d22c77ec 100644 --- a/Engine/Source/Editor/Blutility/Classes/EditorUtilityWidgetBlueprint.h +++ b/Engine/Source/Editor/Blutility/Classes/EditorUtilityWidgetBlueprint.h @@ -31,7 +31,7 @@ public: TSharedRef CreateUtilityWidget(); /** Recreate the tab's content on recompile */ - void RegenerateCreatedTab(UBlueprint* RecompiledBlueprint); + void RegenerateCreatedTab(); void UpdateRespawnListIfNeeded(TSharedRef TabBeingClosed); @@ -60,6 +60,7 @@ private: TWeakPtr CreatedTab; + UPROPERTY() UEditorUtilityWidget* CreatedUMGWidget; }; diff --git a/Engine/Source/Editor/Blutility/Private/AssetTypeActions_EditorUtilityWidgetBlueprint.cpp b/Engine/Source/Editor/Blutility/Private/AssetTypeActions_EditorUtilityWidgetBlueprint.cpp index cbdf3ffed657..c3b1fff6fd11 100644 --- a/Engine/Source/Editor/Blutility/Private/AssetTypeActions_EditorUtilityWidgetBlueprint.cpp +++ b/Engine/Source/Editor/Blutility/Private/AssetTypeActions_EditorUtilityWidgetBlueprint.cpp @@ -18,6 +18,7 @@ #include "Widgets/Docking/SDockTab.h" #include "Framework/Docking/TabManager.h" #include "IBlutilityModule.h" +#include "SBlueprintDiff.h" #define LOCTEXT_NAMESPACE "AssetTypeActions" @@ -88,6 +89,26 @@ uint32 FAssetTypeActions_EditorUtilityWidgetBlueprint::GetCategories() return BlutilityModule->GetAssetCategory(); } +void FAssetTypeActions_EditorUtilityWidgetBlueprint::PerformAssetDiff(UObject* Asset1, UObject* Asset2, const struct FRevisionInfo& OldRevision, const struct FRevisionInfo& NewRevision) const +{ + UBlueprint* OldBlueprint = CastChecked(Asset1); + UBlueprint* NewBlueprint = CastChecked(Asset2); + + // sometimes we're comparing different revisions of one single asset (other + // times we're comparing two completely separate assets altogether) + bool bIsSingleAsset = (NewBlueprint->GetName() == OldBlueprint->GetName()); + + FText WindowTitle = LOCTEXT("NamelessWidgetBlueprintDiff", "Editor Utility Widget Blueprint Diff"); + // if we're diffing one asset against itself + if (bIsSingleAsset) + { + // identify the assumed single asset in the window's title + WindowTitle = FText::Format(LOCTEXT("WidgetBlueprintDiff", "{0} - Editor Utility Widget Blueprint Diff"), FText::FromString(NewBlueprint->GetName())); + } + + SBlueprintDiff::CreateDiffWindow(WindowTitle, OldBlueprint, NewBlueprint, OldRevision, NewRevision); +} + void FAssetTypeActions_EditorUtilityWidgetBlueprint::ExecuteRun(FWeakBlueprintPointerArray InObjects) { for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ++ObjIt) @@ -109,7 +130,7 @@ void FAssetTypeActions_EditorUtilityWidgetBlueprint::ExecuteRun(FWeakBlueprintPo FText DisplayName = FText::FromString(Blueprint->GetName()); FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); TSharedPtr LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager(); - if (!LevelEditorTabManager->CanSpawnTab(RegistrationName)) + if (!LevelEditorTabManager->HasTabSpawner(RegistrationName)) { IBlutilityModule* BlutilityModule = FModuleManager::GetModulePtr("Blutility"); UEditorUtilityWidgetBlueprint* WidgetBlueprint = Cast(Blueprint); diff --git a/Engine/Source/Editor/Blutility/Private/AssetTypeActions_EditorUtilityWidgetBlueprint.h b/Engine/Source/Editor/Blutility/Private/AssetTypeActions_EditorUtilityWidgetBlueprint.h index df683057d084..394db085ddb2 100644 --- a/Engine/Source/Editor/Blutility/Private/AssetTypeActions_EditorUtilityWidgetBlueprint.h +++ b/Engine/Source/Editor/Blutility/Private/AssetTypeActions_EditorUtilityWidgetBlueprint.h @@ -20,6 +20,7 @@ public: virtual void GetActions(const TArray& InObjects, FMenuBuilder& MenuBuilder) override; virtual void OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor = TSharedPtr()) override; virtual uint32 GetCategories() override; + virtual void PerformAssetDiff(UObject* Asset1, UObject* Asset2, const struct FRevisionInfo& OldRevision, const struct FRevisionInfo& NewRevision) const override; virtual bool CanLocalize() const override { return false; } // End of IAssetTypeActions interface diff --git a/Engine/Source/Editor/Blutility/Private/BlutilityModule.cpp b/Engine/Source/Editor/Blutility/Private/BlutilityModule.cpp index b835f126be2e..f0e447326f3d 100644 --- a/Engine/Source/Editor/Blutility/Private/BlutilityModule.cpp +++ b/Engine/Source/Editor/Blutility/Private/BlutilityModule.cpp @@ -117,7 +117,7 @@ public: FName RegistrationName = FName(*(Blueprint->GetPathName() + LOCTEXT("ActiveTabSuffix", "_ActiveTab").ToString())); Blueprint->SetRegistrationName(RegistrationName); FText DisplayName = FText::FromString(Blueprint->GetName()); - if (LevelEditorTabManager && !LevelEditorTabManager->CanSpawnTab(RegistrationName)) + if (LevelEditorTabManager && !LevelEditorTabManager->HasTabSpawner(RegistrationName)) { LevelEditorTabManager->RegisterTabSpawner(RegistrationName, FOnSpawnTab::CreateUObject(Blueprint, &UEditorUtilityWidgetBlueprint::SpawnEditorUITab)) .SetDisplayName(DisplayName) diff --git a/Engine/Source/Editor/Blutility/Private/EditorUtilityLibrary.cpp b/Engine/Source/Editor/Blutility/Private/EditorUtilityLibrary.cpp index 87fbc2c1f3e8..96c15c22a60b 100644 --- a/Engine/Source/Editor/Blutility/Private/EditorUtilityLibrary.cpp +++ b/Engine/Source/Editor/Blutility/Private/EditorUtilityLibrary.cpp @@ -75,6 +75,15 @@ TArray UEditorUtilityLibrary::GetSelectedAssets() return Result; } +TArray UEditorUtilityLibrary::GetSelectedAssetData() +{ + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + TArray SelectedAssets; + ContentBrowserModule.Get().GetSelectedAssets(SelectedAssets); + + return SelectedAssets; +} + void UEditorUtilityLibrary::RenameAsset(UObject* Asset, const FString& NewName) { FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); diff --git a/Engine/Source/Editor/Blutility/Private/EditorUtilitySubsystem.cpp b/Engine/Source/Editor/Blutility/Private/EditorUtilitySubsystem.cpp index 6102215261a8..0a0c5246dd1a 100644 --- a/Engine/Source/Editor/Blutility/Private/EditorUtilitySubsystem.cpp +++ b/Engine/Source/Editor/Blutility/Private/EditorUtilitySubsystem.cpp @@ -4,6 +4,7 @@ #include "EditorUtilityCommon.h" #include "Interfaces/IMainFrameModule.h" #include "Engine/Blueprint.h" +#include "ScopedTransaction.h" UEditorUtilitySubsystem::UEditorUtilitySubsystem() : UEditorSubsystem() diff --git a/Engine/Source/Editor/Blutility/Private/EditorUtilityWidgetBlueprint.cpp b/Engine/Source/Editor/Blutility/Private/EditorUtilityWidgetBlueprint.cpp index 982714915899..b9c36abb6026 100644 --- a/Engine/Source/Editor/Blutility/Private/EditorUtilityWidgetBlueprint.cpp +++ b/Engine/Source/Editor/Blutility/Private/EditorUtilityWidgetBlueprint.cpp @@ -56,33 +56,36 @@ TSharedRef UEditorUtilityWidgetBlueprint::SpawnEditorUITab(const FSpaw SpawnedTab->SetOnTabClosed(SDockTab::FOnTabClosedCallback::CreateUObject(this, &UEditorUtilityWidgetBlueprint::UpdateRespawnListIfNeeded)); CreatedTab = SpawnedTab; - OnCompiled().AddUObject(this, &UEditorUtilityWidgetBlueprint::RegenerateCreatedTab); + GEditor->OnBlueprintReinstanced().AddUObject(this, &UEditorUtilityWidgetBlueprint::RegenerateCreatedTab); return SpawnedTab; } TSharedRef UEditorUtilityWidgetBlueprint::CreateUtilityWidget() { + TSharedRef TabWidget = SNullWidget::NullWidget; + UClass* BlueprintClass = GeneratedClass; TSubclassOf WidgetClass = BlueprintClass; UWorld* World = GEditor->GetEditorWorldContext().World(); - check(World); - CreatedUMGWidget = CreateWidget(World, WidgetClass); - TSharedRef TabWidget = SNullWidget::NullWidget; + if (!CreatedUMGWidget && World) + { + CreatedUMGWidget = CreateWidget(World, WidgetClass); + } + if (CreatedUMGWidget) { - TSharedRef CreatedSlateWidget = CreatedUMGWidget->TakeWidget(); TabWidget = SNew(SVerticalBox) + SVerticalBox::Slot() .HAlign(HAlign_Fill) [ - CreatedSlateWidget + CreatedUMGWidget->TakeWidget() ]; } return TabWidget; } -void UEditorUtilityWidgetBlueprint::RegenerateCreatedTab(UBlueprint* RecompiledBlueprint) +void UEditorUtilityWidgetBlueprint::RegenerateCreatedTab() { if (CreatedTab.IsValid()) { diff --git a/Engine/Source/Editor/Blutility/Public/EditorUtilityLibrary.h b/Engine/Source/Editor/Blutility/Public/EditorUtilityLibrary.h index aa84b1844cc9..537622583254 100644 --- a/Engine/Source/Editor/Blutility/Public/EditorUtilityLibrary.h +++ b/Engine/Source/Editor/Blutility/Public/EditorUtilityLibrary.h @@ -3,6 +3,7 @@ #pragma once #include "CoreMinimal.h" +#include "AssetData.h" #include "UObject/ObjectMacros.h" #include "UObject/Object.h" #include "UObject/ScriptMacros.h" @@ -29,6 +30,10 @@ public: UFUNCTION(BlueprintCallable, Category = "Development|Editor") static TArray GetSelectedAssets(); + // Gets the set of currently selected asset data + UFUNCTION(BlueprintCallable, Category = "Development|Editor") + static TArray GetSelectedAssetData(); + // Renames an asset (cannot move folders) UFUNCTION(BlueprintCallable, Category = "Development|Editor") static void RenameAsset(UObject* Asset, const FString& NewName); diff --git a/Engine/Source/Editor/Cascade/Private/Cascade.cpp b/Engine/Source/Editor/Cascade/Private/Cascade.cpp index f5da6ad58dd1..a902aacee6eb 100644 --- a/Engine/Source/Editor/Cascade/Private/Cascade.cpp +++ b/Engine/Source/Editor/Cascade/Private/Cascade.cpp @@ -72,6 +72,8 @@ static const FName Cascade_CurveEditorTab("Cascade_CurveEditor"); DEFINE_LOG_CATEGORY(LogCascade); +FRandomStream FCascade::RandomStream(FPlatformTime::Cycles()); + FCascade::FCascade() : ParticleSystem(nullptr) , ParticleSystemComponent(nullptr) @@ -1530,6 +1532,11 @@ void FCascade::AddReferencedObjects(FReferenceCollector& Collector) } } +FString FCascade::GetReferencerName() const +{ + return TEXT("FCascade"); +} + void FCascade::Tick(float DeltaTime) { // This is a bit of a hack. In order to not tick all open Cascade editors (which tick through engine tick) even when not visible, @@ -1668,14 +1675,6 @@ void FCascade::Tick(float DeltaTime) } } - if (CurveEditor.IsValid()) - { - if (CurveEditor->GetNeedsRedraw()) - { - CurveEditor->DrawViewport(); - } - } - if (bCurrentlySoloing != bIsSoloing) { bIsSoloing = bCurrentlySoloing; @@ -4756,7 +4755,7 @@ void FCascade::OnSetRandomSeed() ParticleSystem->PreEditChange(NULL); ParticleSystemComponent->PreEditChange(NULL); - int32 RandomSeed = FMath::RoundToInt(RAND_MAX * FMath::SRand()); + int32 RandomSeed = FMath::RoundToInt(RAND_MAX * RandomStream.FRand()); if (SelectedModule->SetRandomSeedEntry(0, RandomSeed) == false) { UE_LOG(LogCascade, Warning, TEXT("Failed to set random seed entry on module %s"), *(SelectedModule->GetClass()->GetName())); @@ -4898,7 +4897,7 @@ bool FCascade::ConvertModuleToSeeded(UParticleSystem* ParticleSystem, UParticleE if (RandSeedInfo != NULL) { RandSeedInfo->bResetSeedOnEmitterLooping = true; - RandSeedInfo->RandomSeeds.Add(FMath::TruncToInt(FMath::Rand() * UINT_MAX)); + RandSeedInfo->RandomSeeds.Add(FMath::TruncToInt(RandomStream.FRand() * UINT_MAX)); } } diff --git a/Engine/Source/Editor/Cascade/Private/Cascade.h b/Engine/Source/Editor/Cascade/Private/Cascade.h index 11acad96d6fd..14717aa91413 100644 --- a/Engine/Source/Editor/Cascade/Private/Cascade.h +++ b/Engine/Source/Editor/Cascade/Private/Cascade.h @@ -16,6 +16,7 @@ #include "ICascade.h" #include "IDistCurveEditor.h" #include "Particles/ParticleEmitter.h" +#include "Math/RandomStream.h" class FFXSystemInterface; class IDetailsView; @@ -197,6 +198,7 @@ public: /** FGCObject interface */ virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + virtual FString GetReferencerName() const override; /** FTickableEditorObject interface */ virtual void Tick(float DeltaTime) override; @@ -496,4 +498,7 @@ private: /** Maps particle emitters to rendered thumbnails for the emitter UI */ FParticleEmitterThumbnailMap EmitterToThumbnailMap; + + /** Random stream used to seed particle modules */ + static FRandomStream RandomStream; }; diff --git a/Engine/Source/Editor/Cascade/Private/SCascadeEmitterCanvas.cpp b/Engine/Source/Editor/Cascade/Private/SCascadeEmitterCanvas.cpp index 6449e66f9c24..431899a14942 100644 --- a/Engine/Source/Editor/Cascade/Private/SCascadeEmitterCanvas.cpp +++ b/Engine/Source/Editor/Cascade/Private/SCascadeEmitterCanvas.cpp @@ -77,6 +77,7 @@ void SCascadeEmitterCanvas::Construct(const FArguments& InArgs) void SCascadeEmitterCanvas::RefreshViewport() { Viewport->Invalidate(); + Viewport->InvalidateDisplay(); } bool SCascadeEmitterCanvas::IsVisible() const diff --git a/Engine/Source/Editor/ClassViewer/Private/ClassViewerFilter.cpp b/Engine/Source/Editor/ClassViewer/Private/ClassViewerFilter.cpp new file mode 100644 index 000000000000..b1610add649e --- /dev/null +++ b/Engine/Source/Editor/ClassViewer/Private/ClassViewerFilter.cpp @@ -0,0 +1,556 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "ClassViewerFilter.h" + +#include "UnloadedBlueprintData.h" +#include "Engine/Brush.h" +#include "Kismet2/KismetEditorUtilities.h" +#include "Misc/ConfigCacheIni.h" +#include "Misc/PackageName.h" +#include "Misc/Paths.h" +#include "Misc/TextFilterExpressionEvaluator.h" + +EFilterReturn::Type FClassViewerFilterFuncs::IfInChildOfClassesSet(TSet< const UClass* >& InSet, const UClass* InClass) +{ + check(InClass); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + if (InClass->IsChildOf(*CurClassIt)) + { + return EFilterReturn::Passed; + } + } + + return EFilterReturn::Failed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfInChildOfClassesSet(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) +{ + check(InClass.IsValid()); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + if (InClass->IsChildOf(*CurClassIt)) + { + return EFilterReturn::Passed; + } + } + + return EFilterReturn::Failed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAllInChildOfClassesSet(TSet< const UClass* >& InSet, const UClass* InClass) +{ + check(InClass); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + if (!InClass->IsChildOf(*CurClassIt)) + { + // Since it doesn't match one, it fails. + return EFilterReturn::Failed; + } + } + + // It matches all of them, so it passes. + return EFilterReturn::Passed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAllInChildOfClassesSet(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) +{ + check(InClass.IsValid()); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + if (!InClass->IsChildOf(*CurClassIt)) + { + // Since it doesn't match one, it fails. + return EFilterReturn::Failed; + } + } + + // It matches all of them, so it passes. + return EFilterReturn::Passed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ObjectsSetIsAClass(TSet< const UObject* >& InSet, const UClass* InClass) +{ + check(InClass); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + if (!(*CurClassIt)->IsA(InClass)) + { + // Since it doesn't match one, it fails. + return EFilterReturn::Failed; + } + } + + // It matches all of them, so it passes. + return EFilterReturn::Passed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ObjectsSetIsAClass(TSet< const UObject* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) +{ + check(InClass.IsValid()); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + if (!(*CurClassIt)->IsA(UBlueprintGeneratedClass::StaticClass())) + { + // Since it doesn't match one, it fails. + return EFilterReturn::Failed; + } + } + + // It matches all of them, so it passes. + return EFilterReturn::Passed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ClassesSetIsAClass(TSet< const UClass* >& InSet, const UClass* InClass) +{ + check(InClass); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + const UObject* Object = *CurClassIt; + if (!Object->IsA(InClass)) + { + // Since it doesn't match one, it fails. + return EFilterReturn::Failed; + } + } + + // It matches all of them, so it passes. + return EFilterReturn::Passed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ClassesSetIsAClass(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) +{ + check(InClass.IsValid()); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + const UObject* Object = *CurClassIt; + if (!Object->IsA(UBlueprintGeneratedClass::StaticClass())) + { + // Since it doesn't match one, it fails. + return EFilterReturn::Failed; + } + } + + // It matches all of them, so it passes. + return EFilterReturn::Passed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfMatches_ClassesSetIsAClass(TSet< const UClass* >& InSet, const UClass* InClass) +{ + check(InClass); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + const UObject* Object = *CurClassIt; + if (Object->IsA(InClass)) + { + return EFilterReturn::Passed; + } + } + + return EFilterReturn::Failed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfMatches_ClassesSetIsAClass(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) +{ + check(InClass.IsValid()); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + const UObject* Object = *CurClassIt; + if (Object->IsA(UBlueprintGeneratedClass::StaticClass())) + { + return EFilterReturn::Passed; + } + } + + return EFilterReturn::Failed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfInClassesSet(TSet< const UClass* >& InSet, const UClass* InClass) +{ + check(InClass); + + if (InSet.Num()) + { + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + if (InClass == *CurClassIt) + { + return EFilterReturn::Passed; + } + } + return EFilterReturn::Failed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfInClassesSet(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) +{ + check(InClass.IsValid()); + + if (InSet.Num()) + { + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + const TSharedPtr UnloadedBlueprintData = StaticCastSharedPtr(InClass); + if (*UnloadedBlueprintData->GetClassViewerNode().Pin()->GetClassName() == (*CurClassIt)->GetName()) + { + return EFilterReturn::Passed; + } + } + return EFilterReturn::Failed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +/** Checks if a particular class is a brush. +* @param InClass The Class to check. +* @return Returns true if the class is a brush. +*/ +static bool IsBrush(const UClass* InClass) +{ + return InClass->IsChildOf(ABrush::StaticClass()); +} + +static bool IsBrush(const TSharedRef& InBlueprintData) +{ + return InBlueprintData->IsChildOf(ABrush::StaticClass()); +} + +/** Checks if a particular class is placeable. +* @param InClass The Class to check. +* @return Returns true if the class is placeable. +*/ +static bool IsPlaceable(const UClass* InClass) +{ + return !InClass->HasAnyClassFlags(CLASS_Abstract | CLASS_NotPlaceable) + && InClass->IsChildOf(AActor::StaticClass()); +} + +static bool IsPlaceable(const TSharedRef& InBlueprintData) +{ + return !InBlueprintData->HasAnyClassFlags(CLASS_Abstract | CLASS_NotPlaceable) + && InBlueprintData->IsChildOf(AActor::StaticClass()); +} + +/** Util class to checks if a particular class can be made into a Blueprint, ignores deprecation + * + * @param InClass The class to verify can be made into a Blueprint + * @return true if the class can be made into a Blueprint + */ +static bool CanCreateBlueprintOfClass(UClass* InClass) +{ + // Temporarily remove the deprecated flag so we can check if it is valid for + bool bIsClassDeprecated = InClass->HasAnyClassFlags(CLASS_Deprecated); + InClass->ClassFlags &= ~CLASS_Deprecated; + + bool bCanCreateBlueprintOfClass = FKismetEditorUtilities::CanCreateBlueprintOfClass(InClass); + + // Reassign the deprecated flag if it was previously assigned + if (bIsClassDeprecated) + { + InClass->ClassFlags |= CLASS_Deprecated; + } + + return bCanCreateBlueprintOfClass; +} + +/** Checks if a node is a blueprint base or not. +* @param InNode The node to check if it is a blueprint base. +* @return true if the class is a blueprint base. +*/ +static bool CheckIfBlueprintBase(const TSharedRef& InBlueprintData) +{ + if (InBlueprintData->IsNormalBlueprintType()) + { + bool bAllowDerivedBlueprints = false; + GConfig->GetBool(TEXT("Kismet"), TEXT("AllowDerivedBlueprints"), /*out*/ bAllowDerivedBlueprints, GEngineIni); + + return bAllowDerivedBlueprints; + } + return false; +} + +/** Checks if the TestString passes the filter. +* @param InTestString The string to test against the filter. +* @param InTextFilter Compiled text filter to apply. +* +* @return true if it passes the filter. +*/ +static bool PassesTextFilter(const FString& InTestString, const TSharedRef& InTextFilter) +{ + class FClassFilterContext : public ITextFilterExpressionContext + { + public: + explicit FClassFilterContext(const FString& InStr) + : StrPtr(&InStr) + { + } + + virtual bool TestBasicStringExpression(const FTextFilterString& InValue, const ETextFilterTextComparisonMode InTextComparisonMode) const override + { + return TextFilterUtils::TestBasicStringExpression(*StrPtr, InValue, InTextComparisonMode); + } + + virtual bool TestComplexExpression(const FName& InKey, const FTextFilterString& InValue, const ETextFilterComparisonOperation InComparisonOperation, const ETextFilterTextComparisonMode InTextComparisonMode) const override + { + return false; + } + + private: + const FString* StrPtr; + }; + + return InTextFilter->TestTextFilter(FClassFilterContext(InTestString)); +} + +FClassViewerFilter::FClassViewerFilter(const FClassViewerInitializationOptions& InInitOptions) : + TextFilter(MakeShared(ETextFilterExpressionEvaluatorMode::BasicString)), + FilterFunctions(MakeShared()) +{ +} + +bool FClassViewerFilter::IsNodeAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef& InNode) +{ + if (InNode->Class.IsValid()) + { + return IsClassAllowed(InInitOptions, InNode->Class.Get(), FilterFunctions); + } + else if (InInitOptions.bShowUnloadedBlueprints && InNode->UnloadedBlueprintData.IsValid()) + { + return IsUnloadedClassAllowed(InInitOptions, InNode->UnloadedBlueprintData.ToSharedRef(), FilterFunctions); + } + + return false; +} + +bool FClassViewerFilter::IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef InFilterFuncs) +{ + if (InInitOptions.bIsActorsOnly && !InClass->IsChildOf(AActor::StaticClass())) + { + return false; + } + + const bool bPassesBlueprintBaseFilter = !InInitOptions.bIsBlueprintBaseOnly || CanCreateBlueprintOfClass(const_cast(InClass)); + const bool bPassesEditorClassFilter = !InInitOptions.bEditorClassesOnly || IsEditorOnlyObject(InClass); + + // Determine if we allow any developer folder classes, if so determine if this class is in one of the allowed developer folders. + static const FString DeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameDevelopersDir()); + static const FString UserDeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameUserDeveloperDir()); + FString GeneratedClassPathString = InClass->GetPathName(); + + bool bPassesDeveloperFilter = true; + EClassViewerDeveloperType AllowedDeveloperType = GetDefault()->DeveloperFolderType; + if (AllowedDeveloperType == EClassViewerDeveloperType::CVDT_None) + { + bPassesDeveloperFilter = !GeneratedClassPathString.StartsWith(DeveloperPathWithSlash); + } + else if (AllowedDeveloperType == EClassViewerDeveloperType::CVDT_CurrentUser) + { + if (GeneratedClassPathString.StartsWith(DeveloperPathWithSlash)) + { + bPassesDeveloperFilter = GeneratedClassPathString.StartsWith(UserDeveloperPathWithSlash); + } + } + + // The INI files declare classes and folders that are considered internal only. Does this class match any of those patterns? + // INI path: /Script/ClassViewer.ClassViewerProjectSettings + bool bPassesInternalFilter = true; + if (!GetDefault()->DisplayInternalClasses) + { + for (int i = 0; i < InternalPaths.Num(); ++i) + { + if (GeneratedClassPathString.StartsWith(InternalPaths[i].Path)) + { + bPassesInternalFilter = false; + break; + } + } + + if (bPassesInternalFilter) + { + for (int i = 0; i < InternalClasses.Num(); ++i) + { + if (InClass->IsChildOf(InternalClasses[i])) + { + bPassesInternalFilter = false; + break; + } + } + } + } + + bool bPassesPlaceableFilter = true; + if (InInitOptions.bIsPlaceableOnly) + { + bPassesPlaceableFilter = IsPlaceable(InClass) && + (InInitOptions.Mode == EClassViewerMode::ClassPicker || !IsBrush(InClass)); + } + + bool bPassesCustomFilter = true; + if (InInitOptions.ClassFilter.IsValid()) + { + bPassesCustomFilter = InInitOptions.ClassFilter->IsClassAllowed(InInitOptions, InClass, FilterFunctions); + } + + const bool bPassesTextFilter = PassesTextFilter(InClass->GetName(), TextFilter); + + bool bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter + && bPassesDeveloperFilter && bPassesInternalFilter && bPassesEditorClassFilter + && bPassesCustomFilter && bPassesTextFilter; + + return bPassesFilter; +} + +bool FClassViewerFilter::IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef InUnloadedClassData, TSharedRef InFilterFuncs) +{ + if (InInitOptions.bIsActorsOnly && !InUnloadedClassData->IsChildOf(AActor::StaticClass())) + { + return false; + } + + const bool bPassesBlueprintBaseFilter = !InInitOptions.bIsBlueprintBaseOnly || CheckIfBlueprintBase(InUnloadedClassData); + + // unloaded blueprints cannot be editor-only + const bool bPassesEditorClassFilter = !InInitOptions.bEditorClassesOnly; + + // Determine if we allow any developer folder classes, if so determine if this class is in one of the allowed developer folders. + static const FString DeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameDevelopersDir()); + static const FString UserDeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameUserDeveloperDir()); + FString GeneratedClassPathString = InUnloadedClassData->GetClassPath().ToString(); + + bool bPassesDeveloperFilter = true; + + EClassViewerDeveloperType AllowedDeveloperType = GetDefault()->DeveloperFolderType; + if (AllowedDeveloperType == EClassViewerDeveloperType::CVDT_None) + { + bPassesDeveloperFilter = !GeneratedClassPathString.StartsWith(DeveloperPathWithSlash); + } + else if (AllowedDeveloperType == EClassViewerDeveloperType::CVDT_CurrentUser) + { + if (GeneratedClassPathString.StartsWith(DeveloperPathWithSlash)) + { + bPassesDeveloperFilter = GeneratedClassPathString.StartsWith(UserDeveloperPathWithSlash); + } + } + + // The INI files declare classes and folders that are considered internal only. Does this class match any of those patterns? + // INI path: /Script/ClassViewer.ClassViewerProjectSettings + bool bPassesInternalFilter = true; + if (!GetDefault()->DisplayInternalClasses) + { + for (int i = 0; i < InternalPaths.Num(); ++i) + { + if (GeneratedClassPathString.StartsWith(InternalPaths[i].Path)) + { + bPassesInternalFilter = false; + break; + } + } + } + + bool bPassesPlaceableFilter = true; + if (InInitOptions.bIsPlaceableOnly) + { + bPassesPlaceableFilter = IsPlaceable(InUnloadedClassData) && + (InInitOptions.Mode == EClassViewerMode::ClassPicker || !IsBrush(InUnloadedClassData)); + } + + bool bPassesCustomFilter = true; + if (InInitOptions.ClassFilter.IsValid()) + { + bPassesCustomFilter = InInitOptions.ClassFilter->IsUnloadedClassAllowed(InInitOptions, InUnloadedClassData, FilterFunctions); + } + + const bool bPassesTextFilter = PassesTextFilter(*InUnloadedClassData->GetClassName().Get(), TextFilter); + + bool bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter + && bPassesDeveloperFilter && bPassesInternalFilter && bPassesEditorClassFilter + && bPassesCustomFilter && bPassesTextFilter; + + return bPassesFilter; +} diff --git a/Engine/Source/Editor/ClassViewer/Private/ClassViewerModule.cpp b/Engine/Source/Editor/ClassViewer/Private/ClassViewerModule.cpp index 9e72b0f5614c..75a866ea4b32 100644 --- a/Engine/Source/Editor/ClassViewer/Private/ClassViewerModule.cpp +++ b/Engine/Source/Editor/ClassViewer/Private/ClassViewerModule.cpp @@ -16,6 +16,7 @@ #include "Widgets/Docking/SDockTab.h" #include "ClassViewerProjectSettings.h" #include "ISettingsModule.h" +#include "ClassViewerFilter.h" #define LOCTEXT_NAMESPACE "ClassViewer" @@ -95,5 +96,14 @@ TSharedRef FClassViewerModule::CreateClassViewer(const FClassViewerInit .OnClassPickedDelegate(OnClassPickedDelegate); } +TSharedRef FClassViewerModule::CreateClassFilter(const FClassViewerInitializationOptions& InitOptions) +{ + return TSharedRef(new FClassViewerFilter(InitOptions)); +} + +TSharedRef FClassViewerModule::CreateFilterFuncs() +{ + return TSharedRef(new FClassViewerFilterFuncs()); +} #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/ClassViewer/Private/ClassViewerNode.cpp b/Engine/Source/Editor/ClassViewer/Private/ClassViewerNode.cpp index 0f5ba4fd9ef5..417406b8f700 100644 --- a/Engine/Source/Editor/ClassViewer/Private/ClassViewerNode.cpp +++ b/Engine/Source/Editor/ClassViewer/Private/ClassViewerNode.cpp @@ -8,12 +8,35 @@ #include "ClassViewerFilter.h" #include "PropertyHandle.h" +FClassViewerNode::FClassViewerNode(UClass* InClass) +{ + Class = InClass; + ClassName = MakeShareable(new FString(Class->GetName())); + ClassDisplayName = MakeShareable(new FString(Class->GetDisplayNameText().ToString())); + ClassPath = FName(*Class->GetPathName()); + + if (Class->GetSuperClass()) + { + ParentClassPath = FName(*Class->GetSuperClass()->GetPathName()); + } + + if (Class->ClassGeneratedBy && Class->ClassGeneratedBy->IsA(UBlueprint::StaticClass())) + { + Blueprint = Cast(Class->ClassGeneratedBy); + } + else + { + Blueprint = nullptr; + } + + bPassesFilter = false; +} + FClassViewerNode::FClassViewerNode(const FString& InClassName, const FString& InClassDisplayName) { ClassName = MakeShareable(new FString(InClassName)); ClassDisplayName = MakeShareable(new FString(InClassDisplayName)); bPassesFilter = false; - bIsBPNormalType = false; Class = nullptr; Blueprint = nullptr; @@ -34,7 +57,6 @@ FClassViewerNode::FClassViewerNode( const FClassViewerNode& InCopyObject) ParentClassPath = InCopyObject.ParentClassPath; ClassName = InCopyObject.ClassName; BlueprintAssetPath = InCopyObject.BlueprintAssetPath; - bIsBPNormalType = InCopyObject.bIsBPNormalType; // We do not want to copy the child list, do not add it. It should be the only item missing. } diff --git a/Engine/Source/Editor/ClassViewer/Private/ClassViewerNode.h b/Engine/Source/Editor/ClassViewer/Private/ClassViewerNode.h index 3e5749c2363b..9ced0c9311f9 100644 --- a/Engine/Source/Editor/ClassViewer/Private/ClassViewerNode.h +++ b/Engine/Source/Editor/ClassViewer/Private/ClassViewerNode.h @@ -13,6 +13,8 @@ class UBlueprint; class FClassViewerNode { public: + FClassViewerNode(UClass* Class); + /** * Creates a node for the widget's tree. * @@ -101,9 +103,6 @@ public: /** true if the class passed the filter. */ bool bPassesFilter; - /** true if the class is a "normal type", this is used to identify unloaded blueprints as blueprint bases. */ - bool bIsBPNormalType; - /** Pointer to the parent to this object. */ TWeakPtr< FClassViewerNode > ParentNode; diff --git a/Engine/Source/Editor/ClassViewer/Private/SClassViewer.cpp b/Engine/Source/Editor/ClassViewer/Private/SClassViewer.cpp index 0868709cafe0..8938a7fd471d 100644 --- a/Engine/Source/Editor/ClassViewer/Private/SClassViewer.cpp +++ b/Engine/Source/Editor/ClassViewer/Private/SClassViewer.cpp @@ -81,287 +81,6 @@ DEFINE_LOG_CATEGORY_STATIC(LogEditorClassViewer, Log, All); -////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////// - -EFilterReturn::Type FClassViewerFilterFuncs::IfInChildOfClassesSet(TSet< const UClass* >& InSet, const UClass* InClass) -{ - check(InClass); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - if(InClass->IsChildOf(*CurClassIt)) - { - return EFilterReturn::Passed; - } - } - - return EFilterReturn::Failed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfInChildOfClassesSet(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) -{ - check(InClass.IsValid()); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - if(InClass->IsChildOf(*CurClassIt)) - { - return EFilterReturn::Passed; - } - } - - return EFilterReturn::Failed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAllInChildOfClassesSet(TSet< const UClass* >& InSet, const UClass* InClass) -{ - check(InClass); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - if(!InClass->IsChildOf(*CurClassIt)) - { - // Since it doesn't match one, it fails. - return EFilterReturn::Failed; - } - } - - // It matches all of them, so it passes. - return EFilterReturn::Passed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAllInChildOfClassesSet(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) -{ - check(InClass.IsValid()); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - if(!InClass->IsChildOf(*CurClassIt)) - { - // Since it doesn't match one, it fails. - return EFilterReturn::Failed; - } - } - - // It matches all of them, so it passes. - return EFilterReturn::Passed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ObjectsSetIsAClass(TSet< const UObject* >& InSet, const UClass* InClass) -{ - check(InClass); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - if(!(*CurClassIt)->IsA(InClass)) - { - // Since it doesn't match one, it fails. - return EFilterReturn::Failed; - } - } - - // It matches all of them, so it passes. - return EFilterReturn::Passed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ObjectsSetIsAClass(TSet< const UObject* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) -{ - check(InClass.IsValid()); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - if(!(*CurClassIt)->IsA(UBlueprintGeneratedClass::StaticClass())) - { - // Since it doesn't match one, it fails. - return EFilterReturn::Failed; - } - } - - // It matches all of them, so it passes. - return EFilterReturn::Passed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ClassesSetIsAClass(TSet< const UClass* >& InSet, const UClass* InClass) -{ - check(InClass); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - const UObject* Object = *CurClassIt; - if(!Object->IsA(InClass)) - { - // Since it doesn't match one, it fails. - return EFilterReturn::Failed; - } - } - - // It matches all of them, so it passes. - return EFilterReturn::Passed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ClassesSetIsAClass(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) -{ - check(InClass.IsValid()); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - const UObject* Object = *CurClassIt; - if(!Object->IsA(UBlueprintGeneratedClass::StaticClass())) - { - // Since it doesn't match one, it fails. - return EFilterReturn::Failed; - } - } - - // It matches all of them, so it passes. - return EFilterReturn::Passed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfMatches_ClassesSetIsAClass(TSet< const UClass* >& InSet, const UClass* InClass) -{ - check(InClass); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - const UObject* Object = *CurClassIt; - if(Object->IsA(InClass)) - { - return EFilterReturn::Passed; - } - } - - return EFilterReturn::Failed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfMatches_ClassesSetIsAClass(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) -{ - check(InClass.IsValid()); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - const UObject* Object = *CurClassIt; - if(Object->IsA(UBlueprintGeneratedClass::StaticClass())) - { - return EFilterReturn::Passed; - } - } - - return EFilterReturn::Failed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfInClassesSet(TSet< const UClass* >& InSet, const UClass* InClass) -{ - check(InClass); - - if(InSet.Num()) - { - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - if(InClass == *CurClassIt) - { - return EFilterReturn::Passed; - } - } - return EFilterReturn::Failed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfInClassesSet(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) -{ - check(InClass.IsValid()); - - if(InSet.Num()) - { - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - const TSharedPtr UnloadedBlueprintData = StaticCastSharedPtr(InClass); - if(*UnloadedBlueprintData->GetClassViewerNode().Pin()->GetClassName() == (*CurClassIt)->GetName()) - { - return EFilterReturn::Passed; - } - } - return EFilterReturn::Failed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////// // A hack to make IMPLEMENT_COMPARE_CONSTREF with a templated type @@ -430,9 +149,9 @@ public: private: /** Recursive function to build a tree, will not filter. * @param InOutRootNode The node that this function will add the children of to the tree. - * @param PackageNameToAssetDataMap The asset registry map of blueprint package names to blueprint data + * @param InOutClassPathToNode Map that will be populated with the class path to the corresponding class node. */ - void AddChildren_NoFilter( TSharedPtr< FClassViewerNode >& InOutRootNode, const TMultiMap& BlueprintPackageToAssetDataMap ); + void AddChildren_NoFilter( TSharedPtr< FClassViewerNode >& InOutRootNode, TMap>& InOutClassPathToNode ); /** Called when hot reload has finished */ void OnHotReload( bool bWasTriggeredAutomatically ); @@ -500,7 +219,6 @@ namespace ClassViewer static bool bPopulateClassHierarchy; // Pre-declare these functions. - static bool CheckIfBlueprintBase( TSharedPtr< FClassViewerNode> InNode ); static UBlueprint* GetBlueprint( UClass* InClass ); static void UpdateClassInNode(FName InGeneratedClassPath, UClass* InNewClass, UBlueprint* InNewBluePrint ); @@ -526,24 +244,6 @@ namespace ClassViewer return bCanCreateBlueprintOfClass; } - /** Checks if a particular class is a brush. - * @param InClass The Class to check. - * @return Returns true if the class is a brush. - */ - static bool IsBrush(const UClass* InClass) - { - return InClass->IsChildOf( ABrush::StaticClass() ); - } - - /** Checks if a particular class is placeable. - * @param InClass The Class to check. - * @return Returns true if the class is placeable. - */ - static bool IsPlaceable(const UClass* InClass) - { - return !InClass->HasAnyClassFlags(CLASS_Abstract | CLASS_NotPlaceable) && InClass->IsChildOf(); - } - /** Checks if a particular class is abstract. * @param InClass The Class to check. * @return Returns true if the class is abstract. @@ -553,67 +253,6 @@ namespace ClassViewer return InClass->HasAnyClassFlags(CLASS_Abstract); } - /** Checks if the class is allowed under the init options of the class viewer currently building it's tree/list. - * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. - * @param InClass The class to test against. - */ - static bool IsClassAllowed( const FClassViewerInitializationOptions& InInitOptions, const TWeakObjectPtr InClass ) - { - if(InInitOptions.ClassFilter.IsValid()) - { - return InInitOptions.ClassFilter->IsClassAllowed(InInitOptions, InClass.Get(), MakeShared()); - } - - return true; - } - - /** Checks if the unloaded class is allowed under the init options of the class viewer currently building it's tree/list. - * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. - * @param InNode The node to pull the unloaded class from. - */ - bool IsClassAllowed_UnloadedBlueprint(const FClassViewerInitializationOptions& InInitOptions, TSharedPtr< FClassViewerNode > InNode ) - { - if(InInitOptions.ClassFilter.IsValid() && InNode->UnloadedBlueprintData.IsValid()) - { - return InInitOptions.ClassFilter->IsUnloadedClassAllowed(InInitOptions, InNode->UnloadedBlueprintData.ToSharedRef(), MakeShared()); - } - - return true; - } - - /** Checks if the TestString passes the filter. - * @param InTestString The string to test against the filter. - * @param InTextFilter Compiled text filter to apply. - * - * @return true if it passes the filter. - */ - static bool PassesFilter( const FString& InTestString, const FTextFilterExpressionEvaluator& InTextFilter ) - { - class FClassFilterContext : public ITextFilterExpressionContext - { - public: - explicit FClassFilterContext(const FString& InStr) - : StrPtr(&InStr) - { - } - - virtual bool TestBasicStringExpression(const FTextFilterString& InValue, const ETextFilterTextComparisonMode InTextComparisonMode) const override - { - return TextFilterUtils::TestBasicStringExpression(*StrPtr, InValue, InTextComparisonMode); - } - - virtual bool TestComplexExpression(const FName& InKey, const FTextFilterString& InValue, const ETextFilterComparisonOperation InComparisonOperation, const ETextFilterTextComparisonMode InTextComparisonMode) const override - { - return false; - } - - private: - const FString* StrPtr; - }; - - return InTextFilter.TestTextFilter(FClassFilterContext(InTestString)); - } - /** Will create the instance of FClassHierarchy and populate the class hierarchy tree. */ static void ConstructClassHierarchy() { @@ -660,319 +299,131 @@ namespace ClassViewer } /** Recursive function to build a tree, filtering out nodes based on the InitOptions and filter search terms. - * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * @param InOutRootNode The node that this function will add the children of to the tree. * @param InRootClassIndex The index of the root node. - * @param InTextFilter Compiled text filter to apply. - * @param bInOnlyActors Filter option to remove non-actor classes. - * @param bInOnlyPlaceables Filter option to remove non-placeable classes. - * @param bInOnlyBlueprintBases Filter option to remove non-blueprint base classes. - * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class filter options. - * @param InAllowedDeveloperType Filter option for dealing with developer folders. - * @param bInInternalClasses Filter option for showing internal classes. - * @param InternalClasses The classes that have been marked as Internal Only. - * @param InternalPaths The paths that have been marked Internal Only. + * @param InClassFilter The class filter to use to filter nodes. + * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * * @return Returns true if the child passed the filter. */ - static bool AddChildren_Tree(const FClassViewerInitializationOptions& InInitOptions, TSharedPtr< FClassViewerNode >& InOutRootNode, - const TSharedPtr< FClassViewerNode >& InOriginalRootNode, const FTextFilterExpressionEvaluator& InTextFilter, bool bInOnlyActors, - bool bInOnlyPlaceables, bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints, EClassViewerDeveloperType InAllowedDeveloperType, bool bInInternalClasses, - const TArray& InternalClasses, const TArray& InternalPaths) + static bool AddChildren_Tree(TSharedPtr< FClassViewerNode >& InOutRootNode, const TSharedPtr< FClassViewerNode >& InOriginalRootNode, + const TSharedPtr& InClassFilter, const FClassViewerInitializationOptions& InInitOptions) { - if (bInOnlyActors && *InOriginalRootNode->GetClassName().Get() != FString(TEXT("Actor"))) + InOutRootNode->bPassesFilter = InClassFilter->IsNodeAllowed(InInitOptions, InOutRootNode.ToSharedRef()); + if (!InOutRootNode->bPassesFilter) { - InOutRootNode->bPassesFilter = false; return false; } - bool bChildrenPassesFilter(false); - bool bReturnPassesFilter(false); - - bool bPassesBlueprintBaseFilter = !bInOnlyBlueprintBases || CheckIfBlueprintBase(InOriginalRootNode); - bool bIsUnloadedBlueprint = !InOriginalRootNode->Class.IsValid(); - bool bPassesPlaceableFilter = false; - - // Determine if we allow any developer folder classes, if so determine if this class is in one of the allowed developer folders. - bool bPassesDeveloperFilter = true; - static const FString DeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameDevelopersDir()); - static const FString UserDeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameUserDeveloperDir()); - FString GeneratedClassPathString = InOriginalRootNode->ClassPath.ToString(); - - if (InAllowedDeveloperType == EClassViewerDeveloperType::CVDT_None) - { - bPassesDeveloperFilter = !GeneratedClassPathString.StartsWith(DeveloperPathWithSlash); - } - else if (InAllowedDeveloperType == EClassViewerDeveloperType::CVDT_CurrentUser) - { - if (GeneratedClassPathString.StartsWith(DeveloperPathWithSlash)) - bPassesDeveloperFilter = GeneratedClassPathString.StartsWith(UserDeveloperPathWithSlash); - } - - // The INI files declare classes and folders that are considered internal only. Does this class match any of those patterns? - // INI path: /Script/ClassViewer.ClassViewerProjectSettings - bool bPassesInternalFilter = true; - if (!bInInternalClasses && InternalPaths.Num() > 0) - { - for (int i = 0; i < InternalPaths.Num(); i++) - { - if (GeneratedClassPathString.StartsWith(InternalPaths[i].Path)) - { - bPassesInternalFilter = false; - break; - } - } - } - if (!bInInternalClasses && InternalClasses.Num() > 0 && bPassesInternalFilter && InOriginalRootNode->Class.IsValid()) - { - for (int i = 0; i < InternalClasses.Num(); i++) - { - if (InOriginalRootNode->Class->IsChildOf(InternalClasses[i])) - { - bPassesInternalFilter = false; - break; - } - } - } - - // When in picker mode, brushes are valid "placeable" actors - if (bInOnlyPlaceables && InInitOptions.Mode == EClassViewerMode::ClassPicker && IsBrush(InOriginalRootNode->Class.Get()) && IsPlaceable(InOriginalRootNode->Class.Get())) - { - bPassesPlaceableFilter = true; - } - else - { - bPassesPlaceableFilter = !bInOnlyPlaceables || InOriginalRootNode->IsClassPlaceable(); - } - - // There are few options for filtering an unloaded blueprint, if it matches with this filter, it passes. - if(bIsUnloadedBlueprint) - { - if(bInShowUnloadedBlueprints) - { - bReturnPassesFilter = InOutRootNode->bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter && bPassesDeveloperFilter && bPassesInternalFilter && IsClassAllowed_UnloadedBlueprint(InInitOptions, InOriginalRootNode) && PassesFilter(*InOriginalRootNode->GetClassName().Get(), InTextFilter); - } - } - else - { - bReturnPassesFilter = InOutRootNode->bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter && bPassesDeveloperFilter && bPassesInternalFilter && IsClassAllowed(InInitOptions, InOriginalRootNode->Class) && PassesFilter(*InOriginalRootNode->GetClassName().Get(), InTextFilter); - } - + bool bReturnPassesFilter = false; TArray< TSharedPtr< FClassViewerNode > >& ChildList = InOriginalRootNode->GetChildrenList(); for(int32 ChildIdx = 0; ChildIdx < ChildList.Num(); ChildIdx++) { TSharedPtr< FClassViewerNode > NewNode = MakeShareable( new FClassViewerNode( *ChildList[ChildIdx].Get() ) ); - bReturnPassesFilter |= bChildrenPassesFilter = AddChildren_Tree(InInitOptions, NewNode, ChildList[ChildIdx], InTextFilter, false, /* bInOnlyActors - false so that anything below Actor is added */ - bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, InAllowedDeveloperType, bInInternalClasses, InternalClasses, InternalPaths); - if(bChildrenPassesFilter) + bool bChildrenPassesFilter = AddChildren_Tree(NewNode, ChildList[ChildIdx], InClassFilter, InInitOptions); + bReturnPassesFilter |= bChildrenPassesFilter; + + if (bChildrenPassesFilter) { InOutRootNode->AddChild(NewNode); } - } return bReturnPassesFilter; } /** Builds the class tree. - * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * @param InOutRootNode The node to root the tree to. - * @param InTextFilter Compiled text filter to apply. - * @param bInOnlyPlaceables Filter option to remove non-placeable classes. - * @param bInOnlyActors Filter option to root the tree in the "Actor" class. - * @param bInOnlyBlueprintBases Filter option to remove non-blueprint base classes. - * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class filter options. - * @param InAllowedDeveloperType Filter option for dealing with developer folders. - * @param bInInternalClasses Filter option for showing internal classes. - * @param InternalClasses The classes that have been marked as Internal Only. - * @param InternalPaths The paths that have been marked Internal Only. + * @param InClassFilter The class filter to use to filter nodes. + * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. + * * @return A fully built tree. */ - static void GetClassTree(const FClassViewerInitializationOptions& InInitOptions, TSharedPtr< FClassViewerNode >& InOutRootNode, - const FTextFilterExpressionEvaluator& InTextFilter, bool bInOnlyPlaceables, bool bInOnlyActors, bool bInOnlyBlueprintBases, - bool bInShowUnloadedBlueprints, EClassViewerDeveloperType InAllowedDeveloperType = EClassViewerDeveloperType::CVDT_All, bool bInInternalClasses = true, - const TArray& InternalClasses = TArray(), const TArray& InternalPaths = TArray()) + static void GetClassTree(TSharedPtr< FClassViewerNode >& InOutRootNode, const TSharedPtr& InClassFilter, + const FClassViewerInitializationOptions& InInitOptions) { - const TSharedPtr< FClassViewerNode > ObjectClassRoot = ClassHierarchy->GetObjectRootNode(); // Duplicate the node, it will have no children. InOutRootNode = MakeShareable(new FClassViewerNode(*ObjectClassRoot)); - if(bInOnlyActors) + if (InInitOptions.bIsActorsOnly) { - for(int32 ClassIdx = 0; ClassIdx < ObjectClassRoot->GetChildrenList().Num(); ClassIdx++) + for (int32 ClassIdx = 0; ClassIdx < ObjectClassRoot->GetChildrenList().Num(); ClassIdx++) { TSharedPtr ChildNode = MakeShareable(new FClassViewerNode(*ObjectClassRoot->GetChildrenList()[ClassIdx].Get())); - if (AddChildren_Tree(InInitOptions, ChildNode, ObjectClassRoot->GetChildrenList()[ClassIdx], InTextFilter, true, bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, InAllowedDeveloperType, bInInternalClasses, InternalClasses, InternalPaths)) + if (AddChildren_Tree(ChildNode, ObjectClassRoot->GetChildrenList()[ClassIdx], InClassFilter, InInitOptions)) + { InOutRootNode->AddChild(ChildNode); + } } } else { - AddChildren_Tree(InInitOptions, InOutRootNode, ObjectClassRoot, InTextFilter, false, bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, InAllowedDeveloperType, bInInternalClasses, InternalClasses, InternalPaths); + AddChildren_Tree(InOutRootNode, ObjectClassRoot, InClassFilter, InInitOptions); } } /** Recursive function to build the list, filtering out nodes based on the InitOptions and filter search terms. - * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * @param InOutRootNode The node that this function will add the children of to the tree. * @param InRootClassIndex The index of the root node. - * @param InTextFilter Compiled text filter to apply. - * @param bInOnlyActors Filter option to remove non-actor classes. - * @param bInOnlyPlaceables Filter option to remove non-placeable classes. - * @param bInOnlyBlueprintBases Filter option to remove non-blueprint base classes. - * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class filter options. - * @param InAllowedDeveloperType Filter option for dealing with developer folders. - * @param bInInternalClasses Filter option for showing internal classes. - * @param InternalClasses The classes that have been marked as Internal Only. - * @param InternalPaths The paths that have been marked Internal Only. + * @param InClassFilter The class filter to use to filter nodes. + * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * * @return Returns true if the child passed the filter. */ - static void AddChildren_List(const FClassViewerInitializationOptions& InInitOptions, TArray< TSharedPtr< FClassViewerNode > >& InOutNodeList, - const TSharedPtr< FClassViewerNode >& InOriginalRootNode, const FTextFilterExpressionEvaluator& InTextFilter, - bool bInOnlyActors, bool bInOnlyPlaceables, bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints, - EClassViewerDeveloperType InAllowedDeveloperType, bool bInInternalClasses, - const TArray& InternalClasses, const TArray& InternalPaths) + static void AddChildren_List(TArray< TSharedPtr< FClassViewerNode > >& InOutNodeList, const TSharedPtr< FClassViewerNode >& InOriginalRootNode, + const TSharedPtr< FClassViewerFilter >& InClassFilter, const FClassViewerInitializationOptions& InInitOptions) { - if (bInOnlyActors && *InOriginalRootNode->GetClassName().Get() != FString(TEXT("Actor"))) + if (InClassFilter->IsNodeAllowed(InInitOptions, InOriginalRootNode.ToSharedRef())) { - return; - } + TSharedPtr< FClassViewerNode > NewNode = MakeShareable(new FClassViewerNode(*InOriginalRootNode.Get())); + NewNode->bPassesFilter = true; + NewNode->PropertyHandle = InOriginalRootNode->PropertyHandle; - bool bPassesBlueprintBaseFilter = !bInOnlyBlueprintBases || CheckIfBlueprintBase(InOriginalRootNode); - bool bIsUnloadedBlueprint = !InOriginalRootNode->Class.IsValid(); - bool bPassesPlaceableFilter = false; - const bool bPassesEditorClassTest = !InInitOptions.bEditorClassesOnly || InOriginalRootNode->IsEditorOnlyClass(); - - // Determine if we allow any developer folder classes, if so determine if this class is in one of the allowed developer folders. - bool bPassesDeveloperFilter = true; - static const FString DeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameDevelopersDir()); - static const FString UserDeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameUserDeveloperDir()); - FString GeneratedClassPathString = InOriginalRootNode->ClassPath.ToString(); - if (InAllowedDeveloperType == EClassViewerDeveloperType::CVDT_None) - { - bPassesDeveloperFilter = GeneratedClassPathString.StartsWith(DeveloperPathWithSlash); - } - else if (InAllowedDeveloperType == EClassViewerDeveloperType::CVDT_CurrentUser) - { - if (GeneratedClassPathString.StartsWith(DeveloperPathWithSlash)) - bPassesDeveloperFilter = GeneratedClassPathString.StartsWith(UserDeveloperPathWithSlash); - } - - // The INI files declare classes and folders that are considered internal only. Does this class match any of those patterns? - // INI path: /Script/ClassViewer.ClassViewerProjectSettings - bool bPassesInternalFilter = true; - if (!bInInternalClasses && InternalPaths.Num() > 0) - { - for (int i = 0; i < InternalPaths.Num(); i++) - { - if (GeneratedClassPathString.StartsWith(InternalPaths[i].Path)) - { - bPassesInternalFilter = false; - break; - } - } - } - if (!bInInternalClasses && InternalClasses.Num() > 0 && bPassesInternalFilter) - { - for (int i = 0; i < InternalClasses.Num(); i++) - { - if (InOriginalRootNode->Class->IsChildOf(InternalClasses[i])) - { - bPassesInternalFilter = false; - break; - } - } - } - - // When in picker mode, brushes are valid "placeable" actors - if( bInOnlyPlaceables && InInitOptions.Mode == EClassViewerMode::ClassPicker && IsBrush(InOriginalRootNode->Class.Get()) && IsPlaceable(InOriginalRootNode->Class.Get())) - { - bPassesPlaceableFilter = true; - } - else - { - bPassesPlaceableFilter = !bInOnlyPlaceables || InOriginalRootNode->IsClassPlaceable(); - } - - TSharedPtr< FClassViewerNode > NewNode = MakeShareable( new FClassViewerNode( *InOriginalRootNode.Get() ) ); - - // There are few options for filtering an unloaded blueprint, if it matches with this filter, it passes. - if(bIsUnloadedBlueprint) - { - if(bInShowUnloadedBlueprints) - { - NewNode->bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter && bPassesDeveloperFilter && bPassesInternalFilter && bPassesEditorClassTest - && IsClassAllowed_UnloadedBlueprint(InInitOptions, InOriginalRootNode) - && PassesFilter(*InOriginalRootNode->GetClassName().Get(), InTextFilter); - } - } - else - { - NewNode->bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter && bPassesDeveloperFilter && bPassesInternalFilter && bPassesEditorClassTest - && IsClassAllowed(InInitOptions, InOriginalRootNode->Class) - && PassesFilter(*InOriginalRootNode->GetClassName().Get(), InTextFilter); - } - - if(NewNode->bPassesFilter) - { InOutNodeList.Add(NewNode); } - NewNode->PropertyHandle = InInitOptions.PropertyHandle; - - TArray< TSharedPtr< FClassViewerNode > >& ChildList = InOriginalRootNode->GetChildrenList(); - for(int32 ChildIdx = 0; ChildIdx < ChildList.Num(); ChildIdx++) + for(const TSharedPtr& ChildNode : InOriginalRootNode->GetChildrenList()) { - AddChildren_List(InInitOptions, InOutNodeList, ChildList[ChildIdx], InTextFilter, false, /* bInOnlyActors - false so that anything below Actor is added */ - bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, InAllowedDeveloperType, bInInternalClasses, InternalClasses, InternalPaths); + FClassViewerInitializationOptions TempOptions = InInitOptions; + + // set bOnlyActors to false so that anything below Actor is added + TempOptions.bIsActorsOnly = false; + AddChildren_List(InOutNodeList, ChildNode, InClassFilter, InInitOptions); } } /** Builds the class list. - * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * @param InOutNodeList The list to add all the nodes to. - * @param InTextFilter Compiled text filter to apply. - * @param bInOnlyPlaceables Filter option to remove non-placeable classes. - * @param bInOnlyActors Filter option to root the tree in the "Actor" class. - * @param bInOnlyBlueprintBases Filter option to remove non-blueprint base classes. - * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class filter options. - * @param InAllowedDeveloperType Filter option for dealing with developer folders. - * @param bInInternalClasses Filter option for showing internal classes. - * @param InternalClasses The classes that have been marked as Internal Only. - * @param InternalPaths The paths that have been marked Internal Only. + * @param InClassFilter The class filter to use to filter nodes. + * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * * @return A fully built list. */ - static void GetClassList(const FClassViewerInitializationOptions& InInitOptions, TArray< TSharedPtr< FClassViewerNode > >& InOutNodeList, - const FTextFilterExpressionEvaluator& InTextFilter, bool bInOnlyPlaceables, bool bInOnlyActors, bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints, - EClassViewerDeveloperType InAllowedDeveloperType = EClassViewerDeveloperType::CVDT_All, bool bInInternalClasses = true, - const TArray& InternalClasses = TArray(), const TArray& InternalPaths = TArray()) + static void GetClassList(TArray< TSharedPtr< FClassViewerNode > >& InOutNodeList, const TSharedPtr& InClassFilter, + const FClassViewerInitializationOptions& InInitOptions) { const TSharedPtr< FClassViewerNode > ObjectClassRoot = ClassHierarchy->GetObjectRootNode(); // If the option to see the object root class is set, add it to the list, proceed normally from there so the actor's only filter continues to work. - if(InInitOptions.bShowObjectRootClass) + if (InInitOptions.bShowObjectRootClass) { - TSharedPtr< FClassViewerNode > NewNode = MakeShareable( new FClassViewerNode( *ObjectClassRoot.Get() ) ); - NewNode->bPassesFilter = IsClassAllowed(InInitOptions, ObjectClassRoot->Class) && PassesFilter(*ObjectClassRoot->GetClassName().Get(), InTextFilter); - - if(NewNode->bPassesFilter) + if (InClassFilter->IsNodeAllowed(InInitOptions, ObjectClassRoot.ToSharedRef())) { + TSharedPtr< FClassViewerNode > NewNode = MakeShareable(new FClassViewerNode(*ObjectClassRoot.Get())); + NewNode->bPassesFilter = true; + NewNode->PropertyHandle = InInitOptions.PropertyHandle; + InOutNodeList.Add(NewNode); } - - NewNode->PropertyHandle = InInitOptions.PropertyHandle; } - TArray< TSharedPtr< FClassViewerNode > >& ChildList = ObjectClassRoot->GetChildrenList(); - for(int32 ObjectChildIndex = 0; ObjectChildIndex < ChildList.Num(); ObjectChildIndex++) + for(const TSharedPtr& ChildNode : ObjectClassRoot->GetChildrenList()) { - AddChildren_List(InInitOptions, InOutNodeList, ChildList[ObjectChildIndex], InTextFilter, bInOnlyActors, bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, InAllowedDeveloperType, bInInternalClasses, InternalClasses, InternalPaths); + AddChildren_List(InOutNodeList, ChildNode, InClassFilter, InInitOptions); } } @@ -1012,29 +463,6 @@ namespace ClassViewer } } - /** Checks if a node is a blueprint base or not. - * @param InNode The node to check if it is a blueprint base. - * - * @return true if the class is a blueprint. - */ - static bool CheckIfBlueprintBase( TSharedPtr< FClassViewerNode> InNode ) - { - // If there is no class, it may be an unloaded blueprint. - if(UClass* Class = InNode->Class.Get()) - { - return CanCreateBlueprintOfClass_IgnoreDeprecation(Class); - } - else if(InNode->bIsBPNormalType) - { - bool bAllowDerivedBlueprints = false; - GConfig->GetBool(TEXT("Kismet"), TEXT("AllowDerivedBlueprints"), /*out*/ bAllowDerivedBlueprints, GEngineIni); - - return bAllowDerivedBlueprints; - } - - return false; - } - /** * Creates a blueprint from a class. * @@ -1616,27 +1044,12 @@ FClassHierarchy::~FClassHierarchy() FModuleManager::Get().OnModulesChanged().RemoveAll(this); } -static TSharedPtr< FClassViewerNode > CreateNodeForClass(UClass* Class, const TMultiMap& BlueprintPackageToAssetDataMap) -{ - // Create the new node so it can be passed to AddChildren, fill it in with if it is placeable, abstract, and/or a brush. - TSharedPtr< FClassViewerNode > NewNode = MakeShareable(new FClassViewerNode(Class->GetName(), Class->GetDisplayNameText().ToString())); - NewNode->Blueprint = ClassViewer::Helpers::GetBlueprint(Class); - NewNode->Class = Class; - NewNode->ClassPath = FName(*Class->GetPathName()); - if (Class->GetSuperClass()) - { - NewNode->ParentClassPath = FName(*Class->GetSuperClass()->GetPathName()); - } - - return NewNode; -} - void FClassHierarchy::OnHotReload( bool bWasTriggeredAutomatically ) { ClassViewer::Helpers::RequestPopulateClassHierarchy(); } -void FClassHierarchy::AddChildren_NoFilter( TSharedPtr< FClassViewerNode >& InOutRootNode, const TMultiMap& BlueprintPackageToAssetDataMap ) +void FClassHierarchy::AddChildren_NoFilter( TSharedPtr< FClassViewerNode >& InOutRootNode, TMap>& InOutClassPathToNode ) { UClass* RootClass = UObject::StaticClass(); @@ -1674,13 +1087,15 @@ void FClassHierarchy::AddChildren_NoFilter( TSharedPtr< FClassViewerNode >& InOu TSharedPtr& ParentEntry = Nodes.FindOrAdd(CurrentClass->GetSuperClass()); if ( !ParentEntry.IsValid() ) { - ParentEntry = CreateNodeForClass(CurrentClass->GetSuperClass(), BlueprintPackageToAssetDataMap); + ParentEntry = MakeShareable(new FClassViewerNode(CurrentClass->GetSuperClass())); + InOutClassPathToNode.Add(ParentEntry->ClassPath, ParentEntry); } TSharedPtr& MyEntry = Nodes.FindOrAdd(CurrentClass); if ( !MyEntry.IsValid() ) { - MyEntry = CreateNodeForClass(CurrentClass, BlueprintPackageToAssetDataMap); + MyEntry = MakeShareable(new FClassViewerNode(CurrentClass)); + InOutClassPathToNode.Add(MyEntry->ClassPath, MyEntry); } if ( !Visited.Contains(CurrentClass) ) @@ -1929,12 +1344,13 @@ void FClassHierarchy::LoadUnloadedTagData(TSharedPtr& InOutCla InOutClassViewerNode->ParentClassPath = FName(*FPackageName::ExportTextPathToObjectPath(ParentClassPathString)); } - InOutClassViewerNode->bIsBPNormalType = InAssetData.GetTagValueRef(FBlueprintTags::BlueprintType) == TEXT("BPType_Normal"); - // It is an unloaded blueprint, so we need to create the structure that will hold the data. TSharedPtr UnloadedBlueprintData = MakeShareable( new FUnloadedBlueprintData(InOutClassViewerNode) ); InOutClassViewerNode->UnloadedBlueprintData = UnloadedBlueprintData; + const bool bNormalBlueprintType = InAssetData.GetTagValueRef(FBlueprintTags::BlueprintType) == TEXT("BPType_Normal"); + InOutClassViewerNode->UnloadedBlueprintData->SetNormalBlueprintType( bNormalBlueprintType ); + // Get the class flags. const uint32 ClassFlags = InAssetData.GetTagValueRef(FBlueprintTags::ClassFlags); InOutClassViewerNode->UnloadedBlueprintData->SetClassFlags(ClassFlags); @@ -1983,21 +1399,21 @@ void FClassHierarchy::PopulateClassHierarchy() Filter.bRecursiveClasses = true; AssetRegistryModule.Get().GetAssets(Filter, BlueprintList); - TMultiMap BlueprintPackageToAssetDataMap; + TMap> ClassPathToNode; for ( int32 AssetIdx = 0; AssetIdx < BlueprintList.Num(); ++AssetIdx ) { TSharedPtr< FClassViewerNode > NewNode; LoadUnloadedTagData(NewNode, BlueprintList[AssetIdx]); RootLevelClasses.Add(NewNode); + check(!NewNode->GetChildrenList().Num()); + ClassPathToNode.Add(NewNode->ClassPath, NewNode); + // Find the blueprint if it's loaded. FindClass(NewNode); - - - BlueprintPackageToAssetDataMap.Add(BlueprintList[AssetIdx].PackageName, BlueprintList[AssetIdx]); } - AddChildren_NoFilter(ObjectClassRoot, BlueprintPackageToAssetDataMap); + AddChildren_NoFilter(ObjectClassRoot, ClassPathToNode); RootLevelClasses.Add(ObjectClassRoot); @@ -2009,18 +1425,15 @@ void FClassHierarchy::PopulateClassHierarchy() // Resolve the parent's class name locally and use it to find the parent's class. FString ParentClassPath = RootLevelClasses[CurrentNodeIdx]->ParentClassPath.ToString(); const UClass* ParentClass = FindObject(nullptr, *ParentClassPath); + TSharedPtr< FClassViewerNode > ParentNode; - for (int32 SearchNodeIdx = 0; SearchNodeIdx < RootLevelClasses.Num(); ++SearchNodeIdx) + if (TSharedPtr< FClassViewerNode >* ParentNodePtr = ClassPathToNode.Find(RootLevelClasses[CurrentNodeIdx]->ParentClassPath)) { - TSharedPtr< FClassViewerNode > ParentNode = FindParent(RootLevelClasses[SearchNodeIdx], RootLevelClasses[CurrentNodeIdx]->ParentClassPath, ParentClass); - if(ParentNode.IsValid()) - { - // AddUniqueChild makes sure that when a node was generated one by EditorClassHierarchy and one from LoadUnloadedTagData - the proper one is selected - ParentNode->AddUniqueChild(RootLevelClasses[CurrentNodeIdx]); - RootLevelClasses.RemoveAtSwap(CurrentNodeIdx); - --CurrentNodeIdx; - break; - } + // AddUniqueChild makes sure that when a node was generated one by EditorClassHierarchy and one from LoadUnloadedTagData - the proper one is selected + ParentNode = *ParentNodePtr; + ParentNode->AddUniqueChild(RootLevelClasses[CurrentNodeIdx]); + RootLevelClasses.RemoveAtSwap(CurrentNodeIdx); + --CurrentNodeIdx; } } @@ -2039,8 +1452,6 @@ void SClassViewer::Construct(const FArguments& InArgs, const FClassViewerInitial bNeedsRefresh = true; NumClasses = 0; - bCanShowInternalClasses = true; - // Listen for when view settings are changed UClassViewerSettings::OnSettingChanged().AddSP(this, &SClassViewer::HandleSettingChanged); @@ -2048,22 +1459,15 @@ void SClassViewer::Construct(const FArguments& InArgs, const FClassViewerInitial OnClassPicked = InArgs._OnClassPickedDelegate; - TextFilterPtr = MakeShareable(new FTextFilterExpressionEvaluator(ETextFilterExpressionEvaluatorMode::BasicString)); - bSaveExpansionStates = true; bPendingSetExpansionStates = false; + ClassFilter = MakeShareable(new FClassViewerFilter(InitOptions)); + bEnableClassDynamicLoading = InInitOptions.bEnableClassDynamicLoading; EVisibility HeaderVisibility = (this->InitOptions.Mode == EClassViewerMode::ClassBrowsing)? EVisibility::Visible : EVisibility::Collapsed; - // Set these values to the user specified settings. - bIsActorsOnly = InitOptions.bIsActorsOnly | InitOptions.bIsPlaceableOnly; - bIsPlaceableOnly = InitOptions.bIsPlaceableOnly; - bIsBlueprintBaseOnly = InitOptions.bIsBlueprintBaseOnly; - bShowUnloadedBlueprints = InitOptions.bShowUnloadedBlueprints; - bool bHasTitle = InitOptions.ViewerTitleString.IsEmpty() == false; - // If set to default, decide what display mode to use. if( InitOptions.DisplayMode == EClassViewerDisplayMode::DefaultView ) { @@ -2167,6 +1571,8 @@ void SClassViewer::Construct(const FArguments& InArgs, const FClassViewerInitial ); TSharedRef > > ClassTreeView = ClassTree.ToSharedRef(); TSharedRef > > ClassListView = ClassList.ToSharedRef(); + + bool bHasTitle = InitOptions.ViewerTitleString.IsEmpty() == false; // Holds the bulk of the class viewer's sub-widgets, to be added to the widget after construction TSharedPtr< SWidget > ClassViewerContent; @@ -2616,7 +2022,6 @@ EClassViewerDeveloperType SClassViewer::GetCurrentDeveloperViewType() const { return EClassViewerDeveloperType::CVDT_All; } - return GetDefault()->DeveloperFolderType; } @@ -2631,7 +2036,6 @@ void SClassViewer::GetInternalOnlyClasses(TArray& Classes) { return; } - Classes = GetDefault()->InternalOnlyClasses; } @@ -2644,7 +2048,6 @@ void SClassViewer::GetInternalOnlyPaths(TArray& Paths) Paths = GetDefault()->InternalOnlyPaths; } - FText SClassViewer::GetClassCountText() const { @@ -2743,8 +2146,8 @@ void SClassViewer::FindInContentBrowser() void SClassViewer::OnFilterTextChanged( const FText& InFilterText ) { // Update the compiled filter and report any syntax error information back to the user - TextFilterPtr->SetFilterText(InFilterText); - SearchBox->SetError(TextFilterPtr->GetFilterErrorText()); + ClassFilter->TextFilter->SetFilterText(InFilterText); + SearchBox->SetError(ClassFilter->TextFilter->GetFilterErrorText()); // Repopulate the list to show only what has not been filtered out. Refresh(); @@ -2790,12 +2193,12 @@ bool SClassViewer::Menu_CanExecute() const void SClassViewer::MenuActorsOnly_Execute() { - bIsActorsOnly = !bIsActorsOnly; + InitOptions.bIsActorsOnly = !InitOptions.bIsActorsOnly; // "Placeable Only" cannot be true when "Actors Only" is false. - if(!bIsActorsOnly) + if (!InitOptions.bIsActorsOnly) { - bIsPlaceableOnly = false; + InitOptions.bIsPlaceableOnly = false; } Refresh(); @@ -2803,17 +2206,17 @@ void SClassViewer::MenuActorsOnly_Execute() bool SClassViewer::MenuActorsOnly_IsChecked() const { - return bIsActorsOnly; + return InitOptions.bIsActorsOnly; } void SClassViewer::MenuPlaceableOnly_Execute() { - bIsPlaceableOnly = !bIsPlaceableOnly; + InitOptions.bIsPlaceableOnly = !InitOptions.bIsPlaceableOnly; // "Actors Only" must be true when "Placeable Only" is true. - if(bIsPlaceableOnly) + if (InitOptions.bIsPlaceableOnly) { - bIsActorsOnly = true; + InitOptions.bIsActorsOnly = true; } Refresh(); @@ -2821,19 +2224,19 @@ void SClassViewer::MenuPlaceableOnly_Execute() bool SClassViewer::MenuPlaceableOnly_IsChecked() const { - return bIsPlaceableOnly; + return InitOptions.bIsPlaceableOnly; } void SClassViewer::MenuBlueprintBasesOnly_Execute() { - bIsBlueprintBaseOnly = !bIsBlueprintBaseOnly; + InitOptions.bIsBlueprintBaseOnly = !InitOptions.bIsBlueprintBaseOnly; Refresh(); } bool SClassViewer::MenuBlueprintBasesOnly_IsChecked() const { - return bIsBlueprintBaseOnly; + return InitOptions.bIsBlueprintBaseOnly; } TSharedRef SClassViewer::FillFilterEntries() @@ -2975,15 +2378,11 @@ void SClassViewer::Populate() // Empty the tree out so it can be redone. RootTreeItems.Empty(); - bool ShowingInternalClasses = IsShowingInternalClasses(); - TArray InternalClassNames; - TArray InternalClasses; - TArray InternalPaths; // If we aren't showing the internal classes, then we need to know what classes to consider Internal Only, so let's gather them up from the settings object. - if (!ShowingInternalClasses) + if (!IsShowingInternalClasses()) { - GetInternalOnlyPaths(InternalPaths); + GetInternalOnlyPaths(ClassFilter->InternalPaths); GetInternalOnlyClasses(InternalClassNames); // Take the package names for the internal only classes and convert them into their UClass @@ -2994,11 +2393,13 @@ void SClassViewer::Populate() if (ClassNode.IsValid()) { - InternalClasses.Add(ClassNode->Class.Get()); + ClassFilter->InternalClasses.Add(ClassNode->Class.Get()); } } } + + // Based on if the list or tree is visible we create what will be displayed differently. if(InitOptions.DisplayMode == EClassViewerDisplayMode::TreeView) { @@ -3006,11 +2407,10 @@ void SClassViewer::Populate() TSharedPtr RootNode; // Get the class tree, passing in certain filter options. - ClassViewer::Helpers::GetClassTree(InitOptions, RootNode, *TextFilterPtr, MenuPlaceableOnly_IsChecked(), MenuActorsOnly_IsChecked(), MenuBlueprintBasesOnly_IsChecked(), - bShowUnloadedBlueprints, GetCurrentDeveloperViewType(), ShowingInternalClasses, InternalClasses, InternalPaths); + ClassViewer::Helpers::GetClassTree(RootNode, ClassFilter, InitOptions); // Check if we will restore expansion states, we will not if there is filtering happening. - const bool bRestoreExpansionState = TextFilterPtr->GetFilterType() == ETextFilterExpressionType::Empty; + const bool bRestoreExpansionState = ClassFilter->TextFilter->GetFilterType() == ETextFilterExpressionType::Empty; if(InitOptions.bShowObjectRootClass) { @@ -3022,7 +2422,7 @@ void SClassViewer::Populate() } // Expand any items that pass the filter. - if(TextFilterPtr->GetFilterType() != ETextFilterExpressionType::Empty) + if(ClassFilter->TextFilter->GetFilterType() != ETextFilterExpressionType::Empty) { ExpandFilteredInNodes(RootNode); } @@ -3039,7 +2439,7 @@ void SClassViewer::Populate() } // Expand any items that pass the filter. - if(TextFilterPtr->GetFilterType() != ETextFilterExpressionType::Empty) + if(ClassFilter->TextFilter->GetFilterType() != ETextFilterExpressionType::Empty) { ExpandFilteredInNodes(RootNode->GetChildrenList()[ChildIndex]); } @@ -3064,8 +2464,7 @@ void SClassViewer::Populate() else { // Get the class list, passing in certain filter options. - ClassViewer::Helpers::GetClassList(InitOptions, RootTreeItems, *TextFilterPtr, MenuPlaceableOnly_IsChecked(), MenuActorsOnly_IsChecked(), MenuBlueprintBasesOnly_IsChecked(), - bShowUnloadedBlueprints, GetCurrentDeveloperViewType(), ShowingInternalClasses, InternalClasses, InternalPaths); + ClassViewer::Helpers::GetClassList(RootTreeItems, ClassFilter, InitOptions); // Sort the list alphabetically. struct FCompareFClassViewerNode @@ -3180,7 +2579,7 @@ void SClassViewer::Tick( const FGeometry& AllottedGeometry, const double InCurre bool SClassViewer::IsClassAllowed(const UClass* InClass) const { - return ClassViewer::Helpers::IsClassAllowed(InitOptions, InClass); + return ClassFilter->IsClassAllowed(InitOptions, InClass, ClassFilter->FilterFunctions); } void SClassViewer::HandleSettingChanged(FName PropertyName) @@ -3202,7 +2601,7 @@ void SClassViewer::ToggleShowInternalClasses() bool SClassViewer::IsToggleShowInternalClassesAllowed() const { - return bCanShowInternalClasses; + return InitOptions.bAllowViewOptions; } bool SClassViewer::IsShowingInternalClasses() const diff --git a/Engine/Source/Editor/ClassViewer/Private/SClassViewer.h b/Engine/Source/Editor/ClassViewer/Private/SClassViewer.h index 32c06619848e..48b8c9262872 100644 --- a/Engine/Source/Editor/ClassViewer/Private/SClassViewer.h +++ b/Engine/Source/Editor/ClassViewer/Private/SClassViewer.h @@ -21,8 +21,7 @@ class FMenuBuilder; class FTextFilterExpressionEvaluator; class UBlueprint; class SComboButton; -class FClassViewerNode; -class FTextFilterExpressionEvaluator; +class FClassViewerFilter; ////////////////////////////////////////////////////////////////////////// // SClassViewer @@ -226,12 +225,12 @@ private: /** Init options, cached */ FClassViewerInitializationOptions InitOptions; + /** Filter to use to determine what classes are valid. */ + TSharedPtr ClassFilter; + /** The items to be displayed in the tree. */ TArray< TSharedPtr< FClassViewerNode > > RootTreeItems; - /** Compiled filter search terms. */ - TSharedPtr TextFilterPtr; - /** Holds the Slate Tree widget which holds the classes for the Class Viewer. */ TSharedPtr >> ClassTree; @@ -241,18 +240,6 @@ private: /** The Class Search Box, used for filtering the classes visible. */ TSharedPtr SearchBox; - /** true to filter for Actors only. */ - bool bIsActorsOnly; - - /** true to filter for Placeable classes only. */ - bool bIsPlaceableOnly; - - /** true to filter for Blueprint Base classes only. */ - bool bIsBlueprintBaseOnly; - - /** true to filter for unloaded Blueprint classes. */ - bool bShowUnloadedBlueprints; - /** true to allow class dynamic loading. */ bool bEnableClassDynamicLoading; @@ -283,9 +270,6 @@ private: /** True if we need to set the tree expansion states according to our local copy next tick */ bool bPendingSetExpansionStates; - /** Indicates if the 'Show Internal Classes' option should be enabled or disabled */ - bool bCanShowInternalClasses; - /** The button that displays view options */ TSharedPtr ViewOptionsComboButton; diff --git a/Engine/Source/Editor/ClassViewer/Private/UnloadedBlueprintData.cpp b/Engine/Source/Editor/ClassViewer/Private/UnloadedBlueprintData.cpp index 99942f726052..21b10344f580 100644 --- a/Engine/Source/Editor/ClassViewer/Private/UnloadedBlueprintData.cpp +++ b/Engine/Source/Editor/ClassViewer/Private/UnloadedBlueprintData.cpp @@ -116,6 +116,25 @@ const UClass* FUnloadedBlueprintData::GetNativeParent() const return nullptr; } +TSharedPtr FUnloadedBlueprintData::GetClassName() const +{ + if (ClassViewerNode.IsValid()) + { + return ClassViewerNode.Pin()->GetClassName(); + } + + return TSharedPtr(); +} + +FName FUnloadedBlueprintData::GetClassPath() const +{ + if (ClassViewerNode.IsValid()) + { + return ClassViewerNode.Pin()->ClassPath; + } + + return FName(); +} const TWeakPtr& FUnloadedBlueprintData::GetClassViewerNode() const { diff --git a/Engine/Source/Editor/ClassViewer/Private/UnloadedBlueprintData.h b/Engine/Source/Editor/ClassViewer/Private/UnloadedBlueprintData.h index f103335bbd11..241bbde47efc 100644 --- a/Engine/Source/Editor/ClassViewer/Private/UnloadedBlueprintData.h +++ b/Engine/Source/Editor/ClassViewer/Private/UnloadedBlueprintData.h @@ -12,9 +12,9 @@ public: virtual ~FUnloadedBlueprintData() {} - virtual bool HasAnyClassFlags( uint32 InFlagsToCheck ) const override; + virtual bool HasAnyClassFlags(uint32 InFlagsToCheck) const override; - virtual bool HasAllClassFlags( uint32 InFlagsToCheck ) const override; + virtual bool HasAllClassFlags(uint32 InFlagsToCheck) const override; virtual void SetClassFlags(uint32 InFlags) override; @@ -24,6 +24,14 @@ public: virtual bool IsA(const UClass* InClass) const override; + virtual void SetNormalBlueprintType(bool bInNormalBPType) override { bNormalBlueprintType = bInNormalBPType; } + + virtual bool IsNormalBlueprintType() const override { return bNormalBlueprintType; } + + virtual TSharedPtr GetClassName() const override; + + virtual FName GetClassPath() const override; + virtual const UClass* GetClassWithin() const override; virtual const UClass* GetNativeParent() const override; @@ -38,6 +46,9 @@ private: /** Flags for the class. */ uint32 ClassFlags = CLASS_None; + /** Is this a normal blueprint type? */ + bool bNormalBlueprintType; + /** The implemented interfaces for this class. */ TArray ImplementedInterfaces; diff --git a/Engine/Source/Editor/ClassViewer/Public/ClassViewerFilter.h b/Engine/Source/Editor/ClassViewer/Public/ClassViewerFilter.h index 45dda6d94f3b..f890e1fde641 100644 --- a/Engine/Source/Editor/ClassViewer/Public/ClassViewerFilter.h +++ b/Engine/Source/Editor/ClassViewer/Public/ClassViewerFilter.h @@ -2,8 +2,12 @@ #pragma once #include "CoreMinimal.h" +#include "Engine/EngineTypes.h" +#include "Settings/ClassViewerSettings.h" +class FClassViewerNode; class FClassViewerInitializationOptions; +class FTextFilterExpressionEvaluator; /** Interface class for creating filters for the Class Viewer. */ class IClassViewerFilter @@ -30,6 +34,24 @@ public: virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const class IUnloadedBlueprintData > InUnloadedClassData, TSharedRef< class FClassViewerFilterFuncs > InFilterFuncs) = 0; }; +/** Filter class that performs many common checks. */ +class FClassViewerFilter : public IClassViewerFilter +{ +public: + FClassViewerFilter(const FClassViewerInitializationOptions& InInitOptions); + + virtual bool IsNodeAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef& Node); + + virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< class FClassViewerFilterFuncs > InFilterFuncs ) override; + virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const class IUnloadedBlueprintData > InUnloadedClassData, TSharedRef< class FClassViewerFilterFuncs > InFilterFuncs) override; + + TArray InternalClasses; + TArray InternalPaths; + + TSharedRef TextFilter; + TSharedRef FilterFunctions; +}; + namespace EFilterReturn { enum Type{ Failed = 0, Passed, NoItems }; @@ -164,6 +186,7 @@ public: class IUnloadedBlueprintData { public: + /** * Used to safely check whether the passed in flag is set. * @@ -227,4 +250,24 @@ public: * @return The child-most Native class in the hierarchy. */ virtual const UClass* GetNativeParent() const = 0; + + /** + * Set whether or not this blueprint is a normal blueprint. + */ + virtual void SetNormalBlueprintType(bool bInNormalBPType) = 0; + + /** + * Get whether or not this blueprint is a normal blueprint. + */ + virtual bool IsNormalBlueprintType() const = 0; + + /** + * Get the generated class name of this blueprint. + */ + virtual TSharedPtr GetClassName() const = 0; + + /** + * Get the class path of this blueprint. + */ + virtual FName GetClassPath() const = 0; }; diff --git a/Engine/Source/Editor/ClassViewer/Public/ClassViewerModule.h b/Engine/Source/Editor/ClassViewer/Public/ClassViewerModule.h index 0ee1d7c615f1..e80650a89f4d 100644 --- a/Engine/Source/Editor/ClassViewer/Public/ClassViewerModule.h +++ b/Engine/Source/Editor/ClassViewer/Public/ClassViewerModule.h @@ -7,6 +7,7 @@ class IClassViewerFilter; class IPropertyHandle; +class FClassViewerFilterFuncs; /** Delegate used with the Class Viewer in 'class picking' mode. You'll bind a delegate when the class viewer widget is created, which will be fired off when a class is selected in the list */ @@ -163,4 +164,10 @@ public: virtual TSharedRef CreateClassViewer(const FClassViewerInitializationOptions& InitOptions, const FOnClassPicked& OnClassPickedDelegate ); + /** + * Create a new class filter from the given initialization options. + */ + virtual TSharedRef CreateClassFilter(const FClassViewerInitializationOptions& InitOptions); + + virtual TSharedRef CreateFilterFuncs(); }; diff --git a/Engine/Source/Editor/ClothPainter/Private/ClothPaintingModule.cpp b/Engine/Source/Editor/ClothPainter/Private/ClothPaintingModule.cpp index 7d6229c6d79b..5a0c544e07dd 100644 --- a/Engine/Source/Editor/ClothPainter/Private/ClothPaintingModule.cpp +++ b/Engine/Source/Editor/ClothPainter/Private/ClothPaintingModule.cpp @@ -175,7 +175,7 @@ TSharedPtr FClothPaintingModule::GetActiveClothTab(TWeakPtrCanSpawnTab(FClothPaintTabSummoner::TabName)) + if(TabManager->HasTabSpawner(FClothPaintTabSummoner::TabName)) { TSharedPtr Tab = TabManager->FindExistingLiveTab(FTabId(FClothPaintTabSummoner::TabName)); diff --git a/Engine/Source/Editor/ComponentVisualizers/Private/SplineComponentVisualizer.cpp b/Engine/Source/Editor/ComponentVisualizers/Private/SplineComponentVisualizer.cpp index 650538b9e8f5..46825d91fd01 100644 --- a/Engine/Source/Editor/ComponentVisualizers/Private/SplineComponentVisualizer.cpp +++ b/Engine/Source/Editor/ComponentVisualizers/Private/SplineComponentVisualizer.cpp @@ -812,9 +812,10 @@ void FSplineComponentVisualizer::DuplicateKey() SelectedKeysSorted.Sort([](int32 A, int32 B) { return A > B; }); // Insert duplicates into the list, highest index first, so that the lower indices remain the same - FInterpCurveVector& SplinePosition = SplineComp->SplineCurves.Position; - FInterpCurveQuat& SplineRotation = SplineComp->SplineCurves.Rotation; - FInterpCurveVector& SplineScale = SplineComp->SplineCurves.Scale; + FInterpCurveVector& SplinePosition = SplineComp->GetSplinePointsPosition(); + FInterpCurveQuat& SplineRotation = SplineComp->GetSplinePointsRotation(); + FInterpCurveVector& SplineScale = SplineComp->GetSplinePointsScale(); + USplineMetadata* SplineMetadata = SplineComp->GetSplinePointsMetadata(); for (int32 SelectedKeyIndex : SelectedKeysSorted) { @@ -824,6 +825,11 @@ void FSplineComponentVisualizer::DuplicateKey() SplineRotation.Points.Insert(FInterpCurvePoint(SplineRotation.Points[SelectedKeyIndex]), SelectedKeyIndex); SplineScale.Points.Insert(FInterpCurvePoint(SplineScale.Points[SelectedKeyIndex]), SelectedKeyIndex); + if (SplineMetadata) + { + SplineMetadata->DuplicatePoint(SelectedKeyIndex); + } + // Adjust input keys of subsequent points for (int Index = SelectedKeyIndex + 1; Index < SplinePosition.Points.Num(); Index++) { @@ -891,7 +897,8 @@ void FSplineComponentVisualizer::OnAddKey() FInterpCurveVector& SplinePosition = SplineComp->GetSplinePointsPosition(); FInterpCurveQuat& SplineRotation = SplineComp->GetSplinePointsRotation(); FInterpCurveVector& SplineScale = SplineComp->GetSplinePointsScale(); - + USplineMetadata* SplineMetadata = SplineComp->GetSplinePointsMetadata(); + FInterpCurvePoint NewPoint( SelectedSegmentIndex, SplineComp->GetComponentTransform().InverseTransformPosition(SelectedSplinePosition), @@ -916,6 +923,10 @@ void FSplineComponentVisualizer::OnAddKey() SplinePosition.Points.Insert(NewPoint, SelectedSegmentIndex + 1); SplineRotation.Points.Insert(NewRotPoint, SelectedSegmentIndex + 1); SplineScale.Points.Insert(NewScalePoint, SelectedSegmentIndex + 1); + if (SplineMetadata) + { + SplineMetadata->InsertPoint(SelectedSegmentIndex, SelectedSegmentIndex + 1); + } // Adjust input keys of subsequent points for (int Index = SelectedSegmentIndex + 1; Index < SplinePosition.Points.Num(); Index++) @@ -964,12 +975,18 @@ void FSplineComponentVisualizer::OnDeleteKey() SelectedKeysSorted.Sort([](int32 A, int32 B) { return A > B; }); // Delete selected keys from list, highest index first - FInterpCurveVector& SplinePosition = SplineComp->SplineCurves.Position; - FInterpCurveQuat& SplineRotation = SplineComp->SplineCurves.Rotation; - FInterpCurveVector& SplineScale = SplineComp->SplineCurves.Scale; - + FInterpCurveVector& SplinePosition = SplineComp->GetSplinePointsPosition(); + FInterpCurveQuat& SplineRotation = SplineComp->GetSplinePointsRotation(); + FInterpCurveVector& SplineScale = SplineComp->GetSplinePointsScale(); + USplineMetadata* SplineMetadata = SplineComp->GetSplinePointsMetadata(); + for (int32 SelectedKeyIndex : SelectedKeysSorted) { + if (SplineMetadata) + { + SplineMetadata->RemovePoint(SelectedKeyIndex); + } + SplinePosition.Points.RemoveAt(SelectedKeyIndex); SplineRotation.Points.RemoveAt(SelectedKeyIndex); SplineScale.Points.RemoveAt(SelectedKeyIndex); diff --git a/Engine/Source/Editor/ContentBrowser/Private/AssetContextMenu.cpp b/Engine/Source/Editor/ContentBrowser/Private/AssetContextMenu.cpp index 4e4b49335495..d1c8712679bd 100644 --- a/Engine/Source/Editor/ContentBrowser/Private/AssetContextMenu.cpp +++ b/Engine/Source/Editor/ContentBrowser/Private/AssetContextMenu.cpp @@ -6,6 +6,8 @@ #include "Framework/Commands/UIAction.h" #include "Textures/SlateIcon.h" #include "Engine/Blueprint.h" +#include "Engine/UserDefinedStruct.h" +#include "Engine/UserDefinedEnum.h" #include "Misc/MessageDialog.h" #include "HAL/PlatformApplicationMisc.h" #include "HAL/FileManager.h" @@ -1408,7 +1410,7 @@ bool FAssetContextMenu::AddCollectionMenuOptions(FMenuBuilder& MenuBuilder) AvailableCollections.Sort([](const FCollectionNameType& One, const FCollectionNameType& Two) -> bool { - return One.Name < Two.Name; + return One.Name.LexicalLess(Two.Name); }); for (const FCollectionNameType& AvailableCollection : AvailableCollections) @@ -2156,6 +2158,22 @@ void FAssetContextMenu::ExecuteReload() continue; } + if (AssetData.AssetClass == UUserDefinedStruct::StaticClass()->GetFName()) + { + FNotificationInfo Notification(LOCTEXT("CannotReloadUserStruct", "User created structures cannot be safely reloaded.")); + Notification.ExpireDuration = 3.0f; + FSlateNotificationManager::Get().AddNotification(Notification); + continue; + } + + if (AssetData.AssetClass == UUserDefinedEnum::StaticClass()->GetFName()) + { + FNotificationInfo Notification(LOCTEXT("CannotReloadUserEnum", "User created enumerations cannot be safely reloaded.")); + Notification.ExpireDuration = 3.0f; + FSlateNotificationManager::Get().AddNotification(Notification); + continue; + } + PackagesToReload.AddUnique(AssetData.GetPackage()); } diff --git a/Engine/Source/Editor/ContentBrowser/Private/AssetViewWidgets.cpp b/Engine/Source/Editor/ContentBrowser/Private/AssetViewWidgets.cpp index 91dbf6dcab7a..8c9c08bafacb 100644 --- a/Engine/Source/Editor/ContentBrowser/Private/AssetViewWidgets.cpp +++ b/Engine/Source/Editor/ContentBrowser/Private/AssetViewWidgets.cpp @@ -46,7 +46,9 @@ FReply FAssetViewModeUtils::OnViewModeKeyDown( const TSet< TSharedPtr >& SelectedItems, const FKeyEvent& InKeyEvent ) { // All asset views use Ctrl-C to copy references to assets - if ( InKeyEvent.IsControlDown() && InKeyEvent.GetCharacter() == 'C' ) + if ( InKeyEvent.IsControlDown() && InKeyEvent.GetCharacter() == 'C' + && !InKeyEvent.IsShiftDown() && !InKeyEvent.IsAltDown() + ) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); diff --git a/Engine/Source/Editor/ContentBrowser/Private/CollectionContextMenu.cpp b/Engine/Source/Editor/ContentBrowser/Private/CollectionContextMenu.cpp index d734a4eaa0cc..ad2a3edd096d 100644 --- a/Engine/Source/Editor/ContentBrowser/Private/CollectionContextMenu.cpp +++ b/Engine/Source/Editor/ContentBrowser/Private/CollectionContextMenu.cpp @@ -255,7 +255,7 @@ void FCollectionContextMenu::MakeSaveDynamicCollectionSubMenu(FMenuBuilder& Menu AvailableCollections.Sort([](const FCollectionNameType& One, const FCollectionNameType& Two) -> bool { - return One.Name < Two.Name; + return One.Name.LexicalLess(Two.Name); }); if (AvailableCollections.Num() > 0) diff --git a/Engine/Source/Editor/ContentBrowser/Private/CollectionViewTypes.h b/Engine/Source/Editor/ContentBrowser/Private/CollectionViewTypes.h index 558a27d3a72c..84a438b9394e 100644 --- a/Engine/Source/Editor/ContentBrowser/Private/CollectionViewTypes.h +++ b/Engine/Source/Editor/ContentBrowser/Private/CollectionViewTypes.h @@ -37,7 +37,7 @@ struct FCollectionItem { FORCEINLINE bool operator()( const TSharedPtr& A, const TSharedPtr& B ) const { - return A->CollectionName < B->CollectionName; + return A->CollectionName.LexicalLess(B->CollectionName); } }; diff --git a/Engine/Source/Editor/ContentBrowser/Private/PathContextMenu.cpp b/Engine/Source/Editor/ContentBrowser/Private/PathContextMenu.cpp index d78db41d16a0..e7603ad64f78 100644 --- a/Engine/Source/Editor/ContentBrowser/Private/PathContextMenu.cpp +++ b/Engine/Source/Editor/ContentBrowser/Private/PathContextMenu.cpp @@ -107,7 +107,7 @@ TSharedRef FPathContextMenu::MakePathViewContextMenuExtender(const TA { if (MenuExtenderDelegates[i].IsBound()) { - Extenders.Add(MenuExtenderDelegates[i].Execute( SelectedPaths )); + Extenders.Add(MenuExtenderDelegates[i].Execute( InSelectedPaths )); } } TSharedPtr MenuExtender = FExtender::Combine(Extenders); diff --git a/Engine/Source/Editor/ContentBrowser/Private/SContentBrowser.cpp b/Engine/Source/Editor/ContentBrowser/Private/SContentBrowser.cpp index 7bc3257e9ea9..62ae02e08612 100644 --- a/Engine/Source/Editor/ContentBrowser/Private/SContentBrowser.cpp +++ b/Engine/Source/Editor/ContentBrowser/Private/SContentBrowser.cpp @@ -1798,7 +1798,7 @@ TSharedPtr SContentBrowser::OnGetCrumbDelimiterContent(const FString& C FText::FromName(ChildCollection.Name), FText::GetEmpty(), FSlateIcon(FEditorStyle::GetStyleSetName(), ECollectionShareType::GetIconStyleName(ChildCollection.Type)), - FUIAction(FExecuteAction::CreateSP(this, &SContentBrowser::OnPathMenuItemClicked, ChildCollectionCrumbData)) + FUIAction(FExecuteAction::CreateSP(const_cast(this), &SContentBrowser::OnPathMenuItemClicked, ChildCollectionCrumbData)) ); } @@ -1842,7 +1842,7 @@ TSharedPtr SContentBrowser::OnGetCrumbDelimiterContent(const FString& C FText::FromString(PathWithoutParent), FText::GetEmpty(), FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.BreadcrumbPathPickerFolder"), - FUIAction(FExecuteAction::CreateSP(this, &SContentBrowser::OnPathMenuItemClicked, SubPath)) + FUIAction(FExecuteAction::CreateSP(const_cast(this), &SContentBrowser::OnPathMenuItemClicked, SubPath)) ); } diff --git a/Engine/Source/Editor/ContentBrowser/Private/SFilterList.cpp b/Engine/Source/Editor/ContentBrowser/Private/SFilterList.cpp index f699d7316b6d..41fa2c8fddd5 100644 --- a/Engine/Source/Editor/ContentBrowser/Private/SFilterList.cpp +++ b/Engine/Source/Editor/ContentBrowser/Private/SFilterList.cpp @@ -978,7 +978,7 @@ void SFilterList::CreateFiltersMenuCategory(FMenuBuilder& MenuBuilder, const TAr FText::Format( LOCTEXT("FilterByTooltipPrefix", "Filter by {0}"), LabelText ), FSlateIcon(), FUIAction( - FExecuteAction::CreateSP( this, &SFilterList::FilterByTypeClicked, WeakTypeActions ), + FExecuteAction::CreateSP( const_cast(this), &SFilterList::FilterByTypeClicked, WeakTypeActions ), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SFilterList::IsAssetTypeActionsInUse, WeakTypeActions ) ), NAME_None, @@ -1002,7 +1002,7 @@ void SFilterList::CreateOtherFiltersMenuCategory(FMenuBuilder& MenuBuilder, TSha FrontendFilter->GetToolTipText(), FSlateIcon(FEditorStyle::GetStyleSetName(), FrontendFilter->GetIconName()), FUIAction( - FExecuteAction::CreateSP( this, &SFilterList::FrontendFilterClicked, FrontendFilter ), + FExecuteAction::CreateSP( const_cast(this), &SFilterList::FrontendFilterClicked, FrontendFilter ), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SFilterList::IsFrontendFilterInUse, FrontendFilter ) ), NAME_None, diff --git a/Engine/Source/Editor/CurveEditor/Private/CurveEditor.cpp b/Engine/Source/Editor/CurveEditor/Private/CurveEditor.cpp index 6e190e96efd7..a074bbadee1b 100644 --- a/Engine/Source/Editor/CurveEditor/Private/CurveEditor.cpp +++ b/Engine/Source/Editor/CurveEditor/Private/CurveEditor.cpp @@ -206,11 +206,6 @@ void FCurveEditor::RemoveTreeItem(FCurveEditorTreeItemID ItemID) ++ActiveCurvesSerialNumber; } -FSimpleMulticastDelegate& FCurveEditor::OnTreeChanged() -{ - return Tree.OnChanged(); -} - ECurveEditorTreeSelectionState FCurveEditor::GetTreeSelectionState(FCurveEditorTreeItemID InTreeItemID) const { return Tree.GetSelectionState(InTreeItemID); diff --git a/Engine/Source/Editor/CurveEditor/Private/Tree/CurveEditorTree.cpp b/Engine/Source/Editor/CurveEditor/Private/Tree/CurveEditorTree.cpp index 93c46f25cd7f..fd43f658362b 100644 --- a/Engine/Source/Editor/CurveEditor/Private/Tree/CurveEditorTree.cpp +++ b/Engine/Source/Editor/CurveEditor/Private/Tree/CurveEditorTree.cpp @@ -2,8 +2,11 @@ #include "Tree/CurveEditorTree.h" #include "Tree/ICurveEditorTreeItem.h" +#include "Tree/CurveEditorTreeFilter.h" #include "CurveEditor.h" +#include "Algo/AnyOf.h" + class FCurveModel; TArrayView FCurveEditorTreeItem::GetOrCreateCurves(FCurveEditor* CurveEditor) @@ -49,9 +52,40 @@ void FCurveEditorTreeItem::DestroyUnpinnedCurves(FCurveEditor* CurveEditor) } } +FScopedCurveEditorTreeUpdateGuard::FScopedCurveEditorTreeUpdateGuard(FCurveEditorTree* InTree) + : Tree(InTree) +{ + Tree->OnChanged()->UpdateGuardCounter += 1; +} + +FScopedCurveEditorTreeUpdateGuard::FScopedCurveEditorTreeUpdateGuard(FScopedCurveEditorTreeUpdateGuard&& RHS) + : Tree(RHS.Tree) +{ + Tree->OnChanged()->UpdateGuardCounter += 1; +} + +FScopedCurveEditorTreeUpdateGuard& FScopedCurveEditorTreeUpdateGuard::operator=(FScopedCurveEditorTreeUpdateGuard&& RHS) +{ + if (&RHS != this) + { + Tree = RHS.Tree; + Tree->OnChanged()->UpdateGuardCounter += 1; + } + return *this; +} + +FScopedCurveEditorTreeUpdateGuard::~FScopedCurveEditorTreeUpdateGuard() +{ + FCurveEditorOnChangedEvent* OnChangedEvent = Tree->OnChanged(); + if (--OnChangedEvent->UpdateGuardCounter == 0) + { + TGuardValue Guard(OnChangedEvent->bIsBroadcastInProgress, true); + OnChangedEvent->Delegate.Broadcast(); + } +} + FCurveEditorTree::FCurveEditorTree() { - bReentrantDelegate = false; NextTreeItemID.Value = 1; } @@ -75,10 +109,15 @@ const TArray& FCurveEditorTree::GetRootItems() const return RootItems.ChildIDs; } +const TMap& FCurveEditorTree::GetAllItems() const +{ + return Items; +} + FCurveEditorTreeItem* FCurveEditorTree::AddItem(FCurveEditorTreeItemID ParentID) { - checkf(!bReentrantDelegate, TEXT("Curve editor tree must not be manipulated in response to it changing")); - TGuardValue Guard(bReentrantDelegate, true); + checkf(!OnChangedEvent.IsBroadcastInProgress(), TEXT("Curve editor tree must not be manipulated in response to it changing")); + FScopedCurveEditorTreeUpdateGuard BroadcastChangeUpdate(this); FCurveEditorTreeItemID NewItemID = NextTreeItemID; @@ -92,14 +131,13 @@ FCurveEditorTreeItem* FCurveEditorTree::AddItem(FCurveEditorTreeItemID ParentID) ++NextTreeItemID.Value; - OnChangedEvent.Broadcast(); return &NewItem; } void FCurveEditorTree::RemoveItem(FCurveEditorTreeItemID ItemID, FCurveEditor* CurveEditor) { - checkf(!bReentrantDelegate, TEXT("Curve editor tree must not be manipulated in response to it changing")); - TGuardValue Guard(bReentrantDelegate, true); + checkf(!OnChangedEvent.IsBroadcastInProgress(), TEXT("Curve editor tree must not be manipulated in response to it changing")); + FScopedCurveEditorTreeUpdateGuard BroadcastChangeUpdate(this); FCurveEditorTreeItem* Item = Items.Find(ItemID); if (!Item) @@ -120,8 +158,6 @@ void FCurveEditorTree::RemoveItem(FCurveEditorTreeItemID ItemID, FCurveEditor* C Items.Remove(ItemID); Selection.Remove(ItemID); - - OnChangedEvent.Broadcast(); } void FCurveEditorTree::RemoveChildrenRecursive(TArray&& LocalChildren, FCurveEditor* CurveEditor) @@ -141,6 +177,108 @@ void FCurveEditorTree::RemoveChildrenRecursive(TArray&& } } + +bool FCurveEditorTree::FilterSpecificItems(TArrayView FilterPtrs, TArrayView ItemsToFilter, ECurveEditorTreeFilterState InheritedState) +{ + bool bAnyMatched = false; + + for (FCurveEditorTreeItemID ItemID : ItemsToFilter) + { + const FCurveEditorTreeItem& TreeItem = GetItem(ItemID); + + // Retrieve the existing filter state for this item. This may have been set by preceeding filters. + ECurveEditorTreeFilterState FilterState = InheritedState; + ECurveEditorTreeFilterState ChildInheritedState = InheritedState; + + // If this has already not been matched, run it through our filter + TSharedPtr TreeItemImpl = TreeItem.GetItem(); + if (TreeItemImpl) + { + const bool bMatchesFilter = Algo::AnyOf(FilterPtrs, [TreeItemImpl](const FCurveEditorTreeFilter* Filter){ return TreeItemImpl->PassesFilter(Filter); }); + if (bMatchesFilter) + { + bAnyMatched = true; + FilterState = ECurveEditorTreeFilterState::Match; + ChildInheritedState = ECurveEditorTreeFilterState::ImplicitChild; + } + } + + // Run the filter on all child nodes + const bool bMatchedChildren = FilterSpecificItems(FilterPtrs, TreeItem.GetChildren(), ChildInheritedState); + + // If we matched children we become an implicit parent if not already matched + if (bMatchedChildren && FilterState != ECurveEditorTreeFilterState::Match) + { + bAnyMatched = true; + FilterState = ECurveEditorTreeFilterState::ImplicitParent; + } + + if (FilterState != ECurveEditorTreeFilterState::NoMatch) + { + FilterStates.SetFilterState(ItemID, FilterState); + } + } + + return bAnyMatched; +} + +void FCurveEditorTree::RunFilters() +{ + checkf(!OnChangedEvent.IsBroadcastInProgress(), TEXT("Curve editor tree must not be manipulated in response to it changing")); + + FScopedCurveEditorTreeUpdateGuard BroadcastChangeUpdate(this); + + // Reset all the filter states back to the default + FilterStates.Reset(); + + if (Filters.Num()) + { + FilterStates.Activate(); + + TArray FilterPtrs; + + for (int32 Index = Filters.Num() - 1; Index >= 0; --Index) + { + TSharedPtr Filter = Filters[Index].Pin(); + if (!Filter) + { + // Remove invalid filters + Filters.RemoveAtSwap(Index, 1, false); + } + else + { + FilterPtrs.Add(Filter.Get()); + } + } + + FilterSpecificItems(FilterPtrs, RootItems.ChildIDs, ECurveEditorTreeFilterState::NoMatch); + } + else + { + FilterStates.Deactivate(); + } +} + +void FCurveEditorTree::AddFilter(TWeakPtr NewFilter) +{ + Filters.AddUnique(NewFilter); +} + +void FCurveEditorTree::RemoveFilter(TWeakPtr FilterToRemove) +{ + Filters.Remove(FilterToRemove); +} + +const FCurveEditorFilterStates& FCurveEditorTree::GetFilterStates() const +{ + return FilterStates; +} + +ECurveEditorTreeFilterState FCurveEditorTree::GetFilterState(FCurveEditorTreeItemID InTreeItemID) const +{ + return FilterStates.Get(InTreeItemID); +} + void FCurveEditorTree::SetDirectSelection(TArray&& TreeItems) { Selection.Reset(); @@ -176,4 +314,4 @@ ECurveEditorTreeSelectionState FCurveEditorTree::GetSelectionState(FCurveEditorT { const ECurveEditorTreeSelectionState* State = Selection.Find(InTreeItemID); return State ? *State : ECurveEditorTreeSelectionState::None; -} \ No newline at end of file +} diff --git a/Engine/Source/Editor/CurveEditor/Private/Tree/CurveEditorTreeFilter.cpp b/Engine/Source/Editor/CurveEditor/Private/Tree/CurveEditorTreeFilter.cpp new file mode 100644 index 000000000000..567e246914d0 --- /dev/null +++ b/Engine/Source/Editor/CurveEditor/Private/Tree/CurveEditorTreeFilter.cpp @@ -0,0 +1,24 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Tree/CurveEditorTreeFilter.h" +#include "Tree/ICurveEditorTreeItem.h" +#include "Tree/CurveEditorTree.h" + + + +ECurveEditorTreeFilterType FCurveEditorTreeFilter::RegisterFilterType() +{ + static ECurveEditorTreeFilterType NextFilterType = ECurveEditorTreeFilterType::CUSTOM_START; + ensureMsgf(NextFilterType != ECurveEditorTreeFilterType::First, TEXT("Maximum limit for registered curve tree filters (64) reached.")); + if (NextFilterType == ECurveEditorTreeFilterType::First) + { + return NextFilterType; + } + + ECurveEditorTreeFilterType ThisFilterType = NextFilterType; + + // When the custom view ID reaches 0x80000000 the left shift will result in well-defined unsigned integer wraparound, resulting in 0 (None) + NextFilterType = ECurveEditorTreeFilterType( ((__underlying_type(ECurveEditorTreeFilterType))NextFilterType) + 1 ); + + return NextFilterType; +} \ No newline at end of file diff --git a/Engine/Source/Editor/CurveEditor/Private/Tree/SCurveEditorTree.cpp b/Engine/Source/Editor/CurveEditor/Private/Tree/SCurveEditorTree.cpp index 3fa0dc700857..de627b994379 100644 --- a/Engine/Source/Editor/CurveEditor/Private/Tree/SCurveEditorTree.cpp +++ b/Engine/Source/Editor/CurveEditor/Private/Tree/SCurveEditorTree.cpp @@ -23,7 +23,8 @@ public: using MapKeyFuncsSparse = TDefaultMapHashableKeyFuncs; using SetKeyFuncs = DefaultKeyFuncs; - static void AddReferencedObjects(FReferenceCollector&, TArray&, TSet&) {} + template + static void AddReferencedObjects(FReferenceCollector&, TArray&, TSet&, TMap< const U*, FCurveEditorTreeItemID >&) {} static bool IsPtrValid(NullableType InPtr) { @@ -75,6 +76,16 @@ struct SCurveEditorTableRow : SMultiColumnTableRow WeakCurveEditor = InCurveEditor; SMultiColumnTableRow::Construct(InArgs, OwnerTableView); + + SetForegroundColor(MakeAttributeSP(this, &SCurveEditorTableRow::GetForegroundColorByFilterState)); + } + + FSlateColor GetForegroundColorByFilterState() const + { + TSharedPtr CurveEditor = WeakCurveEditor.Pin(); + + const bool bIsMatch = CurveEditor.IsValid() && ( CurveEditor->GetTree()->GetFilterState(TreeItemID) == ECurveEditorTreeFilterState::Match ); + return bIsMatch ? FSlateColor::UseForeground() : FSlateColor::UseSubduedForeground(); } virtual TSharedRef GenerateWidgetForColumn(const FName& InColumnName) override @@ -116,6 +127,7 @@ struct SCurveEditorTableRow : SMultiColumnTableRow void SCurveEditorTree::Construct(const FArguments& InArgs, TSharedPtr InCurveEditor) { + bFilterWasActive = false; HeaderRow = SNew(SHeaderRow) .Visibility(EVisibility::Collapsed) @@ -132,7 +144,7 @@ void SCurveEditorTree::Construct(const FArguments& InArgs, TSharedPtrGetRootTreeItems()) + .TreeItemsSource(&RootItems) .OnGetChildren(this, &SCurveEditorTree::GetTreeItemChildren) .OnGenerateRow(this, &SCurveEditorTree::GenerateRow) .OnSetExpansionRecursive(this, &SCurveEditorTree::SetItemExpansionRecursive) @@ -144,7 +156,63 @@ void SCurveEditorTree::Construct(const FArguments& InArgs, TSharedPtrOnTreeChanged().AddSP(TreeView.ToSharedRef(), &SCurveEditorTreeView::RequestTreeRefresh); + CurveEditor->GetTree()->Bind_OnChanged(FSimpleDelegate::CreateSP(this, &SCurveEditorTree::RefreshTree)); +} + +void SCurveEditorTree::RefreshTree() +{ + RootItems.Reset(); + + const FCurveEditorTree* CurveEditorTree = CurveEditor->GetTree(); + const FCurveEditorFilterStates& FilterStates = CurveEditorTree->GetFilterStates(); + + // When changing to/from a filtered state, we save and restore expansion states + if (FilterStates.IsActive() && !bFilterWasActive) + { + // Save expansion states + PreFilterExpandedItems.Reset(); + TreeView->GetExpandedItems(PreFilterExpandedItems); + } + else if (!FilterStates.IsActive() && bFilterWasActive) + { + // Restore expansion states + TreeView->ClearExpandedItems(); + for (FCurveEditorTreeItemID ExpandedItem : PreFilterExpandedItems) + { + TreeView->SetItemExpansion(ExpandedItem, true); + } + PreFilterExpandedItems.Reset(); + } + + // Repopulate root tree items based on filters + for (FCurveEditorTreeItemID RootItemID : CurveEditor->GetRootTreeItems()) + { + if (FilterStates.Get(RootItemID) != ECurveEditorTreeFilterState::NoMatch) + { + RootItems.Add(RootItemID); + } + } + + RootItems.Shrink(); + TreeView->RequestTreeRefresh(); + + if (FilterStates.IsActive()) + { + // If a filter is active, all matched items and their parents are expanded + TreeView->ClearExpandedItems(); + for (const TTuple& Pair : CurveEditorTree->GetAllItems()) + { + ECurveEditorTreeFilterState FilterState = FilterStates.Get(Pair.Key); + + // Expand any matched items or parents of matched items + if (FilterState == ECurveEditorTreeFilterState::Match || FilterState == ECurveEditorTreeFilterState::ImplicitParent) + { + TreeView->SetItemExpansion(Pair.Key, true); + } + } + } + + bFilterWasActive = FilterStates.IsActive(); } FReply SCurveEditorTree::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) @@ -165,8 +233,15 @@ TSharedRef SCurveEditorTree::GenerateRow(FCurveEditorTreeItemID ItemI void SCurveEditorTree::GetTreeItemChildren(FCurveEditorTreeItemID Parent, TArray& OutChildren) { - TArrayView Children = CurveEditor->GetTreeItem(Parent).GetChildren(); - OutChildren.Append(Children.GetData(), Children.Num()); + const FCurveEditorFilterStates& FilterStates = CurveEditor->GetTree()->GetFilterStates(); + + for (FCurveEditorTreeItemID ChildID : CurveEditor->GetTreeItem(Parent).GetChildren()) + { + if (FilterStates.Get(ChildID) != ECurveEditorTreeFilterState::NoMatch) + { + OutChildren.Add(ChildID); + } + } } void SCurveEditorTree::OnTreeSelectionChanged(FCurveEditorTreeItemID, ESelectInfo::Type) diff --git a/Engine/Source/Editor/CurveEditor/Private/Tree/SCurveEditorTreeTextFilter.cpp b/Engine/Source/Editor/CurveEditor/Private/Tree/SCurveEditorTreeTextFilter.cpp new file mode 100644 index 000000000000..f832d1caf32a --- /dev/null +++ b/Engine/Source/Editor/CurveEditor/Private/Tree/SCurveEditorTreeTextFilter.cpp @@ -0,0 +1,49 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Tree/SCurveEditorTreeTextFilter.h" +#include "Tree/CurveEditorTreeFilter.h" +#include "Tree/CurveEditorTree.h" +#include "CurveEditor.h" + +#include "Widgets/Input/SSearchBox.h" + + +void SCurveEditorTreeTextFilter::Construct(const FArguments& InArgs, TSharedPtr CurveEditor) +{ + WeakCurveEditor = CurveEditor; + + ChildSlot + [ + SNew(SSearchBox) + .HintText(NSLOCTEXT("CurveEditor", "TextFilterHint", "Filter")) + .OnTextChanged(this, &SCurveEditorTreeTextFilter::OnFilterTextChanged) + ]; +} + +void SCurveEditorTreeTextFilter::OnFilterTextChanged( const FText& FilterText ) +{ + TSharedPtr CurveEditor = WeakCurveEditor.Pin(); + if (CurveEditor) + { + if (!Filter) + { + Filter = MakeShared(); + } + + Filter->FilterTerms.Reset(); + + static const bool bCullEmpty = true; + FilterText.ToString().ParseIntoArray(Filter->FilterTerms, TEXT(" "), bCullEmpty); + + if (Filter->FilterTerms.Num() > 0) + { + CurveEditor->GetTree()->AddFilter(Filter); + } + else + { + CurveEditor->GetTree()->RemoveFilter(Filter); + } + + CurveEditor->GetTree()->RunFilters(); + } +} \ No newline at end of file diff --git a/Engine/Source/Editor/CurveEditor/Public/CurveEditor.h b/Engine/Source/Editor/CurveEditor/Public/CurveEditor.h index 92f8a028ecee..f088f6b7914d 100644 --- a/Engine/Source/Editor/CurveEditor/Public/CurveEditor.h +++ b/Engine/Source/Editor/CurveEditor/Public/CurveEditor.h @@ -293,11 +293,6 @@ public: */ void RemoveTreeItem(FCurveEditorTreeItemID ItemID); - /** - * Retrieve an event that is broadcast any time the tree changes - */ - FSimpleMulticastDelegate& OnTreeChanged(); - /** * Set the current tree selection */ @@ -313,6 +308,14 @@ public: */ const TMap& GetTreeSelection() const; + /** + * Access the curve editor tree. + */ + FCurveEditorTree* GetTree() + { + return &Tree; + } + /** * Retrieve a serial number that is incremented any time a curve is added or removed */ diff --git a/Engine/Source/Editor/CurveEditor/Public/Tree/CurveEditorTree.h b/Engine/Source/Editor/CurveEditor/Public/Tree/CurveEditorTree.h index c74de69d4a04..b5b4b54c5104 100644 --- a/Engine/Source/Editor/CurveEditor/Public/Tree/CurveEditorTree.h +++ b/Engine/Source/Editor/CurveEditor/Public/Tree/CurveEditorTree.h @@ -11,10 +11,78 @@ #include "CurveEditorTypes.h" struct ICurveEditorTreeItem; +struct FCurveEditorTreeFilter; class FCurveEditor; class FCurveEditorTree; +/** Enumeration specifying how a specific tree item has matched the current set of filters */ +enum class ECurveEditorTreeFilterState : uint8 +{ + /** The item did not match any filter, and neither did any of its parents or children */ + NoMatch, + + /** Neither this item nor any of its children match filters, but one of its parents did (ie it resides within a matched item) */ + ImplicitChild, + + /** Neither this item nor any of its parents match the filters, but one of its descendant children did (ie it is a parent of a matched item) */ + ImplicitParent, + + /** This item itself matched one or more of the filters */ + Match, +}; + +/** + * Scoped guard that will trigger the tree OnChanged event when all scoped guards have been exited + */ +struct CURVEEDITOR_API FScopedCurveEditorTreeUpdateGuard +{ + explicit FScopedCurveEditorTreeUpdateGuard(FCurveEditorTree* InTree); + ~FScopedCurveEditorTreeUpdateGuard(); + + FScopedCurveEditorTreeUpdateGuard(FScopedCurveEditorTreeUpdateGuard&& RHS); + FScopedCurveEditorTreeUpdateGuard& operator=(FScopedCurveEditorTreeUpdateGuard&& RHS); + +private: + FCurveEditorTree* Tree; +}; + +/** + * Struct that represents an event for when the tree has been changed. + * This type carefully only allows FScopedCurveEditorTreeUpdateGuard to broadcast the event, and makes special checks for re-entrancy + */ +struct FCurveEditorOnChangedEvent +{ + /** + * @return true if the event is currently being broadcast + */ + bool IsBroadcastInProgress() const + { + return bIsBroadcastInProgress; + } + + /** + * Bind a new handler to this OnChanged event + */ + FDelegateHandle Bind(FSimpleDelegate&& Handler) { return Delegate.Add(MoveTemp(Handler)); } + + /** + * Unbind a previously bound handler from this event + */ + void Unbind(FDelegateHandle Handle) { Delegate.Remove(Handle); } + +private: + + friend FScopedCurveEditorTreeUpdateGuard; + + /** Safety check to ensure that invocations of OnChangedEvent are never re-entrant */ + bool bIsBroadcastInProgress = false; + /** Counter that is incremented for each living instance of FCurveEditorOnChangedEvent */ + uint32 UpdateGuardCounter = 0; + /** The actual multi-cast delegate */ + FSimpleMulticastDelegate Delegate; +}; + /** * Container specifying a linear set of child identifiers and */ @@ -124,6 +192,76 @@ private: }; +/** + * Sparse map of filter states specifying items that have matched a filter + */ +struct FCurveEditorFilterStates +{ + /** + * Reset all the filter states currently being tracked (does not affect IsActive + */ + void Reset() + { + FilterStates.Reset(); + } + + /** + * Retrieve the filter state for a specific tree item ID + * @return The item's filter state, or ECurveEditorTreeFilterState::Match if filters are not currently active. + */ + ECurveEditorTreeFilterState Get(FCurveEditorTreeItemID ItemID) const + { + if (!bIsActive) + { + // If not active, everything is treated as having matched the (non-existent) filters + return ECurveEditorTreeFilterState::Match; + } + + const ECurveEditorTreeFilterState* State = FilterStates.Find(ItemID); + return State ? *State : ECurveEditorTreeFilterState::NoMatch; + } + + /** + * Assign a new filter state to an item + */ + void SetFilterState(FCurveEditorTreeItemID ItemID, ECurveEditorTreeFilterState NewState) + { + FilterStates.Add(ItemID, NewState); + } + + /** + * Check whether filters are active or not + */ + bool IsActive() const + { + return bIsActive; + } + + /** + * Activate the filters so that they begin to take effect + */ + void Activate() + { + bIsActive = true; + } + + /** + * Deactivate the filters so that they no longer take effect + */ + void Deactivate() + { + bIsActive = false; + } + +private: + + /** Whether filters should be active or not */ + bool bIsActive = false; + + /** Filter state map. Items with no implicit or explicit filter state are not present */ + TMap FilterStates; +}; + /** * Complete implementation of a curve editor tree. Only really defines the hierarchy and selection states for tree items. */ @@ -149,10 +287,15 @@ public: FCurveEditorTreeItem* FindItem(FCurveEditorTreeItemID ItemID); /** - * Retrieve this curve editor's root items + * Retrieve this curve editor's root items irrespective of filter state */ const TArray& GetRootItems() const; + /** + * Retrieve all the items stored in this tree irrespective of filter state + */ + const TMap& GetAllItems() const; + /** * Add a new empty item to the tree * @@ -169,12 +312,23 @@ public: void RemoveItem(FCurveEditorTreeItemID ItemID, FCurveEditor* CurveEditor); /** - * Access a delegate that is executed whenever this tree's hiararchy has changed in some way. + * Run all the filters on this tree, updating filter state for all tree items */ - FSimpleMulticastDelegate& OnChanged() - { - return OnChangedEvent; - } + void RunFilters(); + + /** + * Add a new filter to this tree. Does not run the filter (and thus update any tree views) until RunFilters is called. + * + * @param NewFilter The new filter to add to this tree + */ + void AddFilter(TWeakPtr NewFilter); + + /** + * Remove an existing filter from this tree. Does not re-run the filters (and thus update any tree views) until RunFilters is called. + * + * @param FilterToRemove The filter to remove from this tree + */ + void RemoveFilter(TWeakPtr FilterToRemove); /** * Inform this tree that the specified tree item IDs have been directly selected on the UI. @@ -192,26 +346,83 @@ public: */ ECurveEditorTreeSelectionState GetSelectionState(FCurveEditorTreeItemID InTreeItemID) const; + /** + * Access the filter state for this tree. Items that are neither implicitly or explicitly filtered-in are not present in the map. + */ + const FCurveEditorFilterStates& GetFilterStates() const; + + /** + * Check a specific tree item's filter state + */ + ECurveEditorTreeFilterState GetFilterState(FCurveEditorTreeItemID InTreeItemID) const; + + /** + * Retrieve this tree's on-changed event + */ + FCurveEditorOnChangedEvent* OnChanged() + { + return &OnChangedEvent; + } + + /** + * Retrieve a scoped guard that will broadcast the on changed handlers for this tree when it goes out of scope (along with all other scoped guards on the stack) + * Can be used to defer such broadcasts in situations where many changes are made to the tree at a time. + */ + FScopedCurveEditorTreeUpdateGuard ScopedUpdateGuard() + { + return FScopedCurveEditorTreeUpdateGuard(this); + } + + /** + * Add a handler for when this tree structure is changed in some way (items added/removed, tree filters changed etc) + */ + FDelegateHandle Bind_OnChanged(FSimpleDelegate&& Handler) + { + return OnChangedEvent.Bind(MoveTemp(Handler)); + } + + /** + * Remove a handler for when this tree structure is changed in some way (items added/removed, tree filters changed etc) + */ + void Unbind_OnChanged(FDelegateHandle Handle) + { + return OnChangedEvent.Unbind(Handle); + } + private: // Recursively removes children without removing them from the parent (assuming the parent is also being removed) void RemoveChildrenRecursive(TArray&& Children, FCurveEditor* CurveEditor); - /** Safety check to ensure that invocations of OnChangedEvent are never re-entrant */ - bool bReentrantDelegate; + /** + * Run the specified filters over the specified items and their recursive children, storing the results in this instance's FilterStates struct. + * + * @param FilterPtrs Array of non-null pointers to filters to use. Items are considered matched if they match any filter in this array. + * @param Items Array item IDs to filter + * @param InheritedState The filter state for each item to receive if it does not directly match a filter (either ECurveEditorTreeFilterState::NoMatch or ECurveEditorTreeFilterState::InheritedChild) + * @return Whether any of the items or any their recursive children matched any filter + */ + bool FilterSpecificItems(TArrayView FilterPtrs, TArrayView Items, ECurveEditorTreeFilterState InheritedState); - /** Incrememnting ID for the next tree item to be created */ + /** Incrementing ID for the next tree item to be created */ FCurveEditorTreeItemID NextTreeItemID; - FSimpleMulticastDelegate OnChangedEvent; + /** Container housing the machinery required for deferred broadcast of changes to the tree */ + FCurveEditorOnChangedEvent OnChangedEvent; /** Map of all tree items by their ID */ TMap Items; + /** Map of all tree items by their ID */ + TArray> Filters; + /** Hierarchical information for the tree */ FSortedCurveEditorTreeItems RootItems; TMap ChildItemIDs; /** Selection state map. Items with no implicit or explicit selection are not present */ TMap Selection; + + /** Filter state map. Items with no implicit or explicit filter state are not present */ + FCurveEditorFilterStates FilterStates; }; \ No newline at end of file diff --git a/Engine/Source/Editor/CurveEditor/Public/Tree/CurveEditorTreeFilter.h b/Engine/Source/Editor/CurveEditor/Public/Tree/CurveEditorTreeFilter.h new file mode 100644 index 000000000000..42faf88f336e --- /dev/null +++ b/Engine/Source/Editor/CurveEditor/Public/Tree/CurveEditorTreeFilter.h @@ -0,0 +1,87 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Templates/SharedPointer.h" +#include "Misc/EnumClassFlags.h" +#include "Containers/UnrealString.h" + +class FCurveEditorTree; +struct FCurveEditorTreeItem; +struct FCurveEditorTreeItemID; +struct FCurveEditorFilterStates; +template class TArrayView; +enum class ECurveEditorTreeFilterState : uint8; + +enum class ECurveEditorTreeFilterType : uint32 +{ + /** Filter is a FCurveEditorTreeTextFilter instance */ + Text, + + CUSTOM_START, + + + First = Text, +}; + + + +struct CURVEEDITOR_API FCurveEditorTreeFilter +{ + explicit FCurveEditorTreeFilter(ECurveEditorTreeFilterType InFilterType) + : FilterType(InFilterType) + {} + + virtual ~FCurveEditorTreeFilter() {} + + /** + * @return The type of this filter as registered by RegisterFilterType + */ + ECurveEditorTreeFilterType GetType() const + { + return FilterType; + } + +public: + + /** + * Register a new filter type that is passed to ICurveEditorTreeItem::Filter + * + * @param InTreeItem The tree item to test + * @return true if the item matches the filter, false otherwise + */ + static ECurveEditorTreeFilterType RegisterFilterType(); + +protected: + + /** The static type of this filter as retrieved by RegisterFilterType */ + ECurveEditorTreeFilterType FilterType; +}; + +/** + * Built-in text filter of type ECurveEditorTreeFilterType::Text. Filter terms are applied as a case-insensitive boolean OR substring match. + */ +struct FCurveEditorTreeTextFilter : FCurveEditorTreeFilter +{ + /** Array of case-insensitive terms to find within tree items. */ + TArray FilterTerms; + + FCurveEditorTreeTextFilter() + : FCurveEditorTreeFilter(ECurveEditorTreeFilterType::Text) + {} + + /** + * Check whether the supplied string matches any of the terms + */ + bool Match(const TCHAR* InString) const + { + for (const FString& Term : FilterTerms) + { + if (FCString::Stristr(InString, *Term) != nullptr) + { + return true; + } + } + return false; + } +}; \ No newline at end of file diff --git a/Engine/Source/Editor/CurveEditor/Public/Tree/CurveEditorTreeTraits.h b/Engine/Source/Editor/CurveEditor/Public/Tree/CurveEditorTreeTraits.h new file mode 100644 index 000000000000..71d52e6c36b1 --- /dev/null +++ b/Engine/Source/Editor/CurveEditor/Public/Tree/CurveEditorTreeTraits.h @@ -0,0 +1,60 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "CurveEditorTypes.h" + +template<> +struct TListTypeTraits +{ +public: + struct NullableType : FCurveEditorTreeItemID + { + NullableType(TYPE_OF_NULLPTR){} + NullableType(FCurveEditorTreeItemID Other) : FCurveEditorTreeItemID(Other) {} + }; + + using MapKeyFuncs = TDefaultMapHashableKeyFuncs, false>; + using MapKeyFuncsSparse = TDefaultMapHashableKeyFuncs; + using SetKeyFuncs = DefaultKeyFuncs; + + static void AddReferencedObjects(FReferenceCollector&, TArray&, TSet&) {} + + static bool IsPtrValid(NullableType InPtr) + { + return InPtr.IsValid(); + } + + static void ResetPtr(NullableType& InPtr) + { + InPtr = nullptr; + } + + static NullableType MakeNullPtr() + { + return nullptr; + } + + static FCurveEditorTreeItemID NullableItemTypeConvertToItemType(NullableType InPtr) + { + return InPtr; + } + + static FString DebugDump(FCurveEditorTreeItemID InPtr) + { + return FString::Printf(TEXT("%d"), InPtr.GetValue()); + } + + class SerializerType{}; +}; + +template <> +struct TIsValidListItem +{ + enum + { + Value = true + }; +}; + diff --git a/Engine/Source/Editor/CurveEditor/Public/Tree/ICurveEditorTreeItem.h b/Engine/Source/Editor/CurveEditor/Public/Tree/ICurveEditorTreeItem.h index d5e9349a762c..8b5b0db2c30e 100644 --- a/Engine/Source/Editor/CurveEditor/Public/Tree/ICurveEditorTreeItem.h +++ b/Engine/Source/Editor/CurveEditor/Public/Tree/ICurveEditorTreeItem.h @@ -6,27 +6,58 @@ #include "Containers/Array.h" #include "Templates/UniquePtr.h" +enum class ECurveEditorTreeFilterType : uint32; + struct FCurveEditorTreeItemID; +struct FCurveEditorTreeFilter; class FName; class SWidget; class FCurveModel; class FCurveEditor; +/** + * Optional implementation interface for any tree item to be shown on the curve editor tree. + */ struct CURVEEDITOR_API ICurveEditorTreeItem { virtual ~ICurveEditorTreeItem() {} + + /** + * Generate the widget content for the specified column name of the curve editor tree view + * + * @param InColumnName The name of the column to generate widget content for. See FColumnNames for valid names. + * @param InCurveEditor Weak pointer to the curve editor instance. Persistent TSharedPtrs should not be held to this pointer. + * @param InTreeItemID The ID of the tree item that this interface is assigned to + * @return (Optional) Widget content for this column, or nullptr if none is necessary for this column. + */ virtual TSharedPtr GenerateCurveEditorTreeWidget(const FName& InColumnName, TWeakPtr InCurveEditor, FCurveEditorTreeItemID InTreeItemID) = 0; + /** + * Populate the specified array with curve models that are represented by this tree item + * + * @param OutCurveModels Array of curve models to be populated with the curves of this tree item + */ virtual void CreateCurveModels(TArray>& OutCurveModels) = 0; + /** + * Check whether this tree item passes the specified filter. Filter's types can be checked using FCurveEditorTreeFilter::GetType() and static_casted for known filter types. + * + * @param InFilter The filter to test against. + * @return True if this item is deemed to have passed the filter, false otherwise + */ + virtual bool PassesFilter(const FCurveEditorTreeFilter* InFilter) const { return false; } + + /** Structure of pre-defined supported column names for the curve editor outliner */ struct FColumnNames { + /** Generic label content */ FName Label; + /** Right-aligned column for pin buttons */ FName PinHeader; CURVEEDITOR_API FColumnNames(); }; static const FColumnNames ColumnNames; -}; \ No newline at end of file +}; diff --git a/Engine/Source/Editor/CurveEditor/Public/Tree/SCurveEditorTree.h b/Engine/Source/Editor/CurveEditor/Public/Tree/SCurveEditorTree.h index c23df04fd66d..d77efb06d427 100644 --- a/Engine/Source/Editor/CurveEditor/Public/Tree/SCurveEditorTree.h +++ b/Engine/Source/Editor/CurveEditor/Public/Tree/SCurveEditorTree.h @@ -35,8 +35,17 @@ private: void SetItemExpansionRecursive(FCurveEditorTreeItemID Model, bool bInExpansionState); + void RefreshTree(); + private: + bool bFilterWasActive; + + TArray RootItems; + + /** Set of item IDs that were expanded before a filter was applied */ + TSet PreFilterExpandedItems; + TSharedPtr CurveEditor; TSharedPtr HeaderRow; diff --git a/Engine/Source/Editor/CurveEditor/Public/Tree/SCurveEditorTreeTextFilter.h b/Engine/Source/Editor/CurveEditor/Public/Tree/SCurveEditorTreeTextFilter.h new file mode 100644 index 000000000000..868ad9db2230 --- /dev/null +++ b/Engine/Source/Editor/CurveEditor/Public/Tree/SCurveEditorTreeTextFilter.h @@ -0,0 +1,26 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" + +class FCurveEditor; +struct FCurveEditorTreeTextFilter; + +class CURVEEDITOR_API SCurveEditorTreeTextFilter : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SCurveEditorTreeTextFilter){} + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs, TSharedPtr CurveEditor); + +private: + + void OnFilterTextChanged(const FText& FilterText); + + TSharedPtr Filter; + TWeakPtr WeakCurveEditor; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/CurveTableEditor/Private/CurveTableEditor.cpp b/Engine/Source/Editor/CurveTableEditor/Private/CurveTableEditor.cpp index ea3881fe604d..180541368ab3 100644 --- a/Engine/Source/Editor/CurveTableEditor/Private/CurveTableEditor.cpp +++ b/Engine/Source/Editor/CurveTableEditor/Private/CurveTableEditor.cpp @@ -274,11 +274,11 @@ TSharedRef FCurveTableEditor::SpawnTab_CurveTable( const FSpawnTabArgs TSharedRef HorizontalScrollBar = SNew(SScrollBar) .Orientation(Orient_Horizontal) - .Thickness(FVector2D(8.0f, 8.0f)); + .Thickness(FVector2D(12.0f, 12.0f)); TSharedRef VerticalScrollBar = SNew(SScrollBar) .Orientation(Orient_Vertical) - .Thickness(FVector2D(8.0f, 8.0f)); + .Thickness(FVector2D(12.0f, 12.0f)); TSharedRef RowNamesHeaderRow = SNew(SHeaderRow) .Visibility(this, &FCurveTableEditor::GetGridViewControlsVisibility); diff --git a/Engine/Source/Editor/DataTableEditor/DataTableEditor.Build.cs b/Engine/Source/Editor/DataTableEditor/DataTableEditor.Build.cs index efa66c48f35a..1214cf965950 100644 --- a/Engine/Source/Editor/DataTableEditor/DataTableEditor.Build.cs +++ b/Engine/Source/Editor/DataTableEditor/DataTableEditor.Build.cs @@ -12,6 +12,7 @@ public class DataTableEditor : ModuleRules new string[] { "Core", "CoreUObject", + "ApplicationCore", "Engine", "InputCore", "Slate", diff --git a/Engine/Source/Editor/DataTableEditor/Private/DataTableEditor.cpp b/Engine/Source/Editor/DataTableEditor/Private/DataTableEditor.cpp index 41b103d96185..972c3c5f77c4 100644 --- a/Engine/Source/Editor/DataTableEditor/Private/DataTableEditor.cpp +++ b/Engine/Source/Editor/DataTableEditor/Private/DataTableEditor.cpp @@ -7,11 +7,17 @@ #include "EditorStyleSet.h" #include "Fonts/FontMeasure.h" #include "Framework/Application/SlateApplication.h" +#include "Framework/Commands/GenericCommands.h" #include "Framework/Layout/Overscroll.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Framework/Notifications/NotificationManager.h" +#include "HAL/PlatformApplicationMisc.h" #include "IDocumentation.h" +#include "Misc/FeedbackContext.h" #include "Misc/FileHelper.h" #include "Modules/ModuleManager.h" #include "Policies/PrettyJsonPrintPolicy.h" +#include "ScopedTransaction.h" #include "SDataTableListViewRowName.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" @@ -25,11 +31,18 @@ #include "Widgets/Input/SButton.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SHyperlink.h" +#include "Widgets/Notifications/SNotificationList.h" #include "SourceCodeNavigation.h" +#include "PropertyEditorModule.h" +#include "UObject/StructOnScope.h" + + + #define LOCTEXT_NAMESPACE "DataTableEditor" const FName FDataTableEditor::DataTableTabId("DataTableEditor_DataTable"); +const FName FDataTableEditor::DataTableDetailsTabId("DataTableEditor_DataTableDetails"); const FName FDataTableEditor::RowEditorTabId("DataTableEditor_RowEditor"); const FName FDataTableEditor::RowNameColumnId("RowName"); @@ -78,6 +91,7 @@ void FDataTableEditor::RegisterTabSpawners(const TSharedRef& FAssetEditorToolkit::RegisterTabSpawners(InTabManager); CreateAndRegisterDataTableTab(InTabManager); + CreateAndRegisterDataTableDetailsTab(InTabManager); CreateAndRegisterRowEditorTab(InTabManager); } @@ -86,6 +100,7 @@ void FDataTableEditor::UnregisterTabSpawners(const TSharedRef FAssetEditorToolkit::UnregisterTabSpawners(InTabManager); InTabManager->UnregisterTabSpawner(DataTableTabId); + InTabManager->UnregisterTabSpawner(DataTableDetailsTabId); InTabManager->UnregisterTabSpawner(RowEditorTabId); DataTableTabWidget.Reset(); @@ -101,6 +116,17 @@ void FDataTableEditor::CreateAndRegisterDataTableTab(const TSharedRef& InTabManager) +{ + FPropertyEditorModule & EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor"); + FDetailsViewArgs DetailsViewArgs(/*bUpdateFromSelection=*/ false, /*bLockable=*/ false, /*bAllowSearch=*/ true, /*InNameAreaSettings=*/ FDetailsViewArgs::HideNameArea, /*bHideSelectionTip=*/ true); + PropertyView = EditModule.CreateDetailView(DetailsViewArgs); + + InTabManager->RegisterTabSpawner(DataTableDetailsTabId, FOnSpawnTab::CreateSP(this, &FDataTableEditor::SpawnTab_DataTableDetails)) + .SetDisplayName(LOCTEXT("DataTableDetailsTab", "Data Table Details")) + .SetGroup(WorkspaceMenuCategory.ToSharedRef()); +} + void FDataTableEditor::CreateAndRegisterRowEditorTab(const TSharedRef& InTabManager) { RowEditorTabWidget = CreateRowEditorBox(); @@ -202,7 +228,7 @@ void FDataTableEditor::HandlePostChange() void FDataTableEditor::InitDataTableEditor( const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UDataTable* Table ) { - TSharedRef StandaloneDefaultLayout = FTabManager::NewLayout( "Standalone_DataTableEditor_Layout_v2" ) + TSharedRef StandaloneDefaultLayout = FTabManager::NewLayout( "Standalone_DataTableEditor_Layout_v3" ) ->AddArea ( FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical) @@ -217,6 +243,8 @@ void FDataTableEditor::InitDataTableEditor( const EToolkitMode::Type Mode, const ( FTabManager::NewStack() ->AddTab(DataTableTabId, ETabState::OpenedTab) + ->AddTab(DataTableDetailsTabId, ETabState::OpenedTab) + ->SetForegroundTab(DataTableTabId) ) ->Split ( @@ -245,7 +273,10 @@ void FDataTableEditor::InitDataTableEditor( const EToolkitMode::Type Mode, const SpawnToolkitTab( DataTableTabId, TabInitializationPayload, EToolkitTabSpot::Details ); }*/ - // NOTE: Could fill in asset editor commands here! + // asset editor commands here + ToolkitCommands->MapAction(FGenericCommands::Get().Copy, FExecuteAction::CreateSP(this, &FDataTableEditor::CopySelectedRow)); + ToolkitCommands->MapAction(FGenericCommands::Get().Paste, FExecuteAction::CreateSP(this, &FDataTableEditor::PasteOnSelectedRow)); + ToolkitCommands->MapAction(FGenericCommands::Get().Duplicate, FExecuteAction::CreateSP(this, &FDataTableEditor::DuplicateSelectedRow)); } FName FDataTableEditor::GetToolkitFName() const @@ -458,6 +489,65 @@ void FDataTableEditor::OnRowSelectionChanged(FDataTableEditorRowListViewDataPtr } } +void FDataTableEditor::CopySelectedRow() +{ + UDataTable* TablePtr = Cast(GetEditingObject()); + uint8* RowPtr = TablePtr ? TablePtr->GetRowMap().FindRef(HighlightedRowName) : nullptr; + + if (!RowPtr || !TablePtr->RowStruct) + return; + + FString ClipboardValue; + TablePtr->RowStruct->ExportText(ClipboardValue, RowPtr, RowPtr, TablePtr, PPF_Copy, nullptr); + + FPlatformApplicationMisc::ClipboardCopy(*ClipboardValue); +} + +void FDataTableEditor::PasteOnSelectedRow() +{ + UDataTable* TablePtr = Cast(GetEditingObject()); + uint8* RowPtr = TablePtr ? TablePtr->GetRowMap().FindRef(HighlightedRowName) : nullptr; + + if (!RowPtr || !TablePtr->RowStruct) + return; + + const FScopedTransaction Transaction(LOCTEXT("PasteDataTableRow", "Paste Data Table Row")); + TablePtr->Modify(); + + FString ClipboardValue; + FPlatformApplicationMisc::ClipboardPaste(ClipboardValue); + + FDataTableEditorUtils::BroadcastPreChange(TablePtr, FDataTableEditorUtils::EDataTableChangeInfo::RowData); + + const TCHAR* Result = TablePtr->RowStruct->ImportText(*ClipboardValue, RowPtr, TablePtr, PPF_Copy, GWarn, GetPathNameSafe(TablePtr->RowStruct)); + + FDataTableEditorUtils::BroadcastPostChange(TablePtr, FDataTableEditorUtils::EDataTableChangeInfo::RowData); + + if (Result == nullptr) + { + FNotificationInfo Info(LOCTEXT("FailedPaste", "Failed to paste row")); + FSlateNotificationManager::Get().AddNotification(Info); + } +} + +void FDataTableEditor::DuplicateSelectedRow() +{ + UDataTable* TablePtr = Cast(GetEditingObject()); + FName NewName = HighlightedRowName; + + if (NewName == NAME_None || TablePtr == nullptr) + return; + + const TArray ExistingNames = TablePtr->GetRowNames(); + while (ExistingNames.Contains(NewName)) + { + NewName.SetNumber(NewName.GetNumber() + 1); + } + + FDataTableEditorUtils::DuplicateRow(TablePtr, HighlightedRowName, NewName); + FDataTableEditorUtils::SelectRow(TablePtr, NewName); +} + FText FDataTableEditor::GetFilterText() const { return ActiveFilterText; @@ -632,6 +722,11 @@ void FDataTableEditor::RefreshCachedDataTable(const FName InCachedSelection, con } UpdateVisibleRows(InCachedSelection, bUpdateEvenIfValid); + + if (PropertyView.IsValid()) + { + PropertyView->SetObject(const_cast(Table)); + } } void FDataTableEditor::UpdateVisibleRows(const FName InCachedSelection, const bool bUpdateEvenIfValid) @@ -711,11 +806,11 @@ TSharedRef FDataTableEditor::CreateContentBox() { TSharedRef HorizontalScrollBar = SNew(SScrollBar) .Orientation(Orient_Horizontal) - .Thickness(FVector2D(8.0f, 8.0f)); + .Thickness(FVector2D(12.0f, 12.0f)); TSharedRef VerticalScrollBar = SNew(SScrollBar) .Orientation(Orient_Vertical) - .Thickness(FVector2D(8.0f, 8.0f)); + .Thickness(FVector2D(12.0f, 12.0f)); TSharedRef RowNamesHeaderRow = SNew(SHeaderRow); RowNamesHeaderRow->AddColumn( @@ -876,6 +971,26 @@ TSharedRef FDataTableEditor::SpawnTab_DataTable( const FSpawnTabArgs& ]; } +TSharedRef FDataTableEditor::SpawnTab_DataTableDetails(const FSpawnTabArgs& Args) +{ + check(Args.GetTabId().TabType == DataTableDetailsTabId); + + PropertyView->SetObject(const_cast(GetDataTable())); + + return SNew(SDockTab) + .Icon(FEditorStyle::GetBrush("DataTableEditor.Tabs.Properties")) + .Label(LOCTEXT("DataTableDetails", "Data Table Details")) + .TabColorScale(GetTabColorScale()) + [ + SNew(SBorder) + .Padding(2) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + [ + PropertyView.ToSharedRef() + ] + ]; +} + void FDataTableEditor::SetHighlightedRow(FName Name) { if (Name == HighlightedRowName) diff --git a/Engine/Source/Editor/DataTableEditor/Private/DataTableEditor.h b/Engine/Source/Editor/DataTableEditor/Private/DataTableEditor.h index db100e041eac..fcdd4c3b1d68 100644 --- a/Engine/Source/Editor/DataTableEditor/Private/DataTableEditor.h +++ b/Engine/Source/Editor/DataTableEditor/Private/DataTableEditor.h @@ -106,6 +106,9 @@ protected: /** Spawns the tab with the data table inside */ TSharedRef SpawnTab_DataTable( const FSpawnTabArgs& Args ); + /** Spawns the tab with the data table inside */ + TSharedRef SpawnTab_DataTableDetails(const FSpawnTabArgs& Args); + /** Spawns the tab with the Row Editor inside */ TSharedRef SpawnTab_RowEditor(const FSpawnTabArgs& Args); @@ -134,9 +137,16 @@ protected: void OnRowSelectionChanged(FDataTableEditorRowListViewDataPtr InNewSelection, ESelectInfo::Type InSelectInfo); + void CopySelectedRow(); + void PasteOnSelectedRow(); + void DuplicateSelectedRow(); + /** Helper function for creating and registering the tab containing the data table data */ virtual void CreateAndRegisterDataTableTab(const TSharedRef& InTabManager); + /** Helper function for creating and registering the tab containing the data table details */ + virtual void CreateAndRegisterDataTableDetailsTab(const TSharedRef& InTabManager); + /** Helper function for creating and registering the tab containing the row editor */ virtual void CreateAndRegisterRowEditorTab(const TSharedRef& InTabManager); @@ -160,6 +170,9 @@ protected: /** UI for the "Data Table" tab */ TSharedPtr DataTableTabWidget; + /** Property viewing widget */ + TSharedPtr PropertyView; + /** UI for the "Row Editor" tab */ TSharedPtr RowEditorTabWidget; @@ -203,6 +216,9 @@ protected: /** The tab id for the data table tab */ static const FName DataTableTabId; + /** The tab id for the data table details tab */ + static const FName DataTableDetailsTabId; + /** The tab id for the row editor tab */ static const FName RowEditorTabId; diff --git a/Engine/Source/Editor/DataTableEditor/Private/SDataTableListViewRowName.cpp b/Engine/Source/Editor/DataTableEditor/Private/SDataTableListViewRowName.cpp index 13824c3b4e54..a042aba080fa 100644 --- a/Engine/Source/Editor/DataTableEditor/Private/SDataTableListViewRowName.cpp +++ b/Engine/Source/Editor/DataTableEditor/Private/SDataTableListViewRowName.cpp @@ -36,9 +36,11 @@ void SDataTableListViewRowName::Construct(const FArguments& InArgs, const TShare FReply SDataTableListViewRowName::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { - if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton && RowDataPtr.IsValid() && FEditorDelegates::OnOpenReferenceViewer.IsBound()) + if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton && RowDataPtr.IsValid() && FEditorDelegates::OnOpenReferenceViewer.IsBound() && DataTableEditor.IsValid()) { - TSharedRef MenuWidget = FDataTableRowUtils::MakeRowActionsMenu(FExecuteAction::CreateSP(this, &SDataTableListViewRowName::OnSearchForReferences)); + FDataTableEditorUtils::SelectRow(DataTableEditor.Pin()->GetDataTable(), RowDataPtr->RowId); + + TSharedRef MenuWidget = FDataTableRowUtils::MakeRowActionsMenu(DataTableEditor.Pin(), FExecuteAction::CreateSP(this, &SDataTableListViewRowName::OnSearchForReferences)); FWidgetPath WidgetPath = MouseEvent.GetEventPath() != nullptr ? *MouseEvent.GetEventPath() : FWidgetPath(); diff --git a/Engine/Source/Editor/DataTableEditor/Public/DataTableRowUtlis.cpp b/Engine/Source/Editor/DataTableEditor/Public/DataTableRowUtlis.cpp index cfcda5923ddb..3043e22cdb6c 100644 --- a/Engine/Source/Editor/DataTableEditor/Public/DataTableRowUtlis.cpp +++ b/Engine/Source/Editor/DataTableEditor/Public/DataTableRowUtlis.cpp @@ -6,17 +6,23 @@ #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Textures/SlateIcon.h" #include "Widgets/SWidget.h" +#include "Framework/Commands/GenericCommands.h" +#include "IDataTableEditor.h" #define LOCTEXT_NAMESPACE "FDataTableRowUtils" const FText FDataTableRowUtils::SearchForReferencesActionName = LOCTEXT("FDataTableRowUtils_SearchForReferences", "Find Row References"); const FText FDataTableRowUtils::SearchForReferencesActionTooltip = LOCTEXT("FDataTableRowUtils_SearchForReferencesTooltip", "Find assets that reference this Row"); -TSharedRef FDataTableRowUtils::MakeRowActionsMenu(FExecuteAction SearchForReferencesAction) +TSharedRef FDataTableRowUtils::MakeRowActionsMenu(TSharedPtr Editor, FExecuteAction SearchForReferencesAction) { if (SearchForReferencesAction.IsBound()) { - FMenuBuilder MenuBuilder(true, nullptr); + FMenuBuilder MenuBuilder(true, Editor->GetToolkitCommands()); + MenuBuilder.AddMenuEntry(FGenericCommands::Get().Copy); + MenuBuilder.AddMenuEntry(FGenericCommands::Get().Paste); + MenuBuilder.AddMenuEntry(FGenericCommands::Get().Duplicate); + MenuBuilder.AddMenuSeparator(); MenuBuilder.AddMenuEntry(SearchForReferencesActionName, SearchForReferencesActionTooltip, FSlateIcon(), FUIAction(SearchForReferencesAction)); return MenuBuilder.MakeWidget(); diff --git a/Engine/Source/Editor/DataTableEditor/Public/DataTableRowUtlis.h b/Engine/Source/Editor/DataTableEditor/Public/DataTableRowUtlis.h index f01ddfb78f3f..140e578d454d 100644 --- a/Engine/Source/Editor/DataTableEditor/Public/DataTableRowUtlis.h +++ b/Engine/Source/Editor/DataTableEditor/Public/DataTableRowUtlis.h @@ -12,7 +12,7 @@ class FDetailWidgetRow; class DATATABLEEDITOR_API FDataTableRowUtils { public: - static TSharedRef MakeRowActionsMenu(FExecuteAction SearchForReferencesAction); + static TSharedRef MakeRowActionsMenu(TSharedPtr Editor, FExecuteAction SearchForReferencesAction); static void AddSearchForReferencesContextMenu(FDetailWidgetRow& RowNameDetailWidget, FExecuteAction SearchForReferencesAction); private: diff --git a/Engine/Source/Editor/DetailCustomizations/Private/ActorDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/ActorDetails.cpp index 565cf8719f10..7d0ba35a4f1c 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/ActorDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/ActorDetails.cpp @@ -318,15 +318,10 @@ TSharedRef FActorDetails::OnGetConvertContent() return SNew(SBox) - .WidthOverride(280) + .WidthOverride(280) + .MaxDesiredHeight(500) [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - .AutoHeight() - .MaxHeight(500) - [ - ClassPicker - ] + ClassPicker ]; } diff --git a/Engine/Source/Editor/DetailCustomizations/Private/BodyInstanceCustomization.cpp b/Engine/Source/Editor/DetailCustomizations/Private/BodyInstanceCustomization.cpp index 079a9d8239f0..e359124ad73e 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/BodyInstanceCustomization.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/BodyInstanceCustomization.cpp @@ -824,6 +824,8 @@ void FBodyInstanceCustomization::OnCollisionProfileComboOpening() void FBodyInstanceCustomization::MarkAllBodiesDefaultCollision(bool bUseDefaultCollision) { + CollisionResponsesHandle->NotifyPreChange(); + if(PrimComponents.Num() && UseDefaultCollisionHandle.IsValid()) //If we have prim components we might be coming from bp editor which needs to propagate all instances { for(UPrimitiveComponent* PrimComp : PrimComponents) @@ -850,6 +852,8 @@ void FBodyInstanceCustomization::MarkAllBodiesDefaultCollision(bool bUseDefaultC } } } + + CollisionResponsesHandle->NotifyPostChange(); } void FBodyInstanceCustomization::OnCollisionProfileChanged( TSharedPtr NewSelection, ESelectInfo::Type SelectInfo, IDetailGroup* CollisionGroup ) diff --git a/Engine/Source/Editor/DetailCustomizations/Private/CameraDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/CameraDetails.cpp index b070476736e9..685bd6b30840 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/CameraDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/CameraDetails.cpp @@ -213,7 +213,7 @@ TSharedRef FCameraDetails::OnGetComboContent() const for (auto ItemIter = Items.CreateConstIterator(); ItemIter; ++ItemIter) { FText ItemText = *ItemIter; - FUIAction ItemAction( FExecuteAction::CreateSP( this, &FCameraDetails::CommitAspectRatioText, ItemText ) ); + FUIAction ItemAction( FExecuteAction::CreateSP( const_cast(this), &FCameraDetails::CommitAspectRatioText, ItemText ) ); MenuBuilder.AddMenuEntry(ItemText, TAttribute(), FSlateIcon(), ItemAction); } diff --git a/Engine/Source/Editor/DetailCustomizations/Private/ComponentMaterialCategory.cpp b/Engine/Source/Editor/DetailCustomizations/Private/ComponentMaterialCategory.cpp index d78bdbf2433a..6c3143840210 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/ComponentMaterialCategory.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/ComponentMaterialCategory.cpp @@ -15,7 +15,7 @@ #include "Editor.h" #include "DetailLayoutBuilder.h" #include "DetailCategoryBuilder.h" -#include "PropertyCustomizationHelpers.h" +#include "MaterialList.h" #include "IPropertyUtilities.h" #include "LandscapeProxy.h" @@ -116,7 +116,7 @@ public: /** * @return Whether or not the iterator is valid */ - operator bool() + explicit operator bool() const { return !bReachedEnd; } diff --git a/Engine/Source/Editor/DetailCustomizations/Private/ComponentTransformDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/ComponentTransformDetails.cpp index 1cbd52a8921e..5d56079103d5 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/ComponentTransformDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/ComponentTransformDetails.cpp @@ -285,15 +285,15 @@ FUIAction FComponentTransformDetails::CreateCopyAction( ETransformField::Type Tr return FUIAction ( - FExecuteAction::CreateSP(this, &FComponentTransformDetails::OnCopy, TransformField ), - FCanExecuteAction::CreateSP(this, &FComponentTransformDetails::OnCanCopy, TransformField ) + FExecuteAction::CreateSP(const_cast(this), &FComponentTransformDetails::OnCopy, TransformField ), + FCanExecuteAction::CreateSP(const_cast(this), &FComponentTransformDetails::OnCanCopy, TransformField ) ); } FUIAction FComponentTransformDetails::CreatePasteAction( ETransformField::Type TransformField ) const { return - FUIAction( FExecuteAction::CreateSP(this, &FComponentTransformDetails::OnPaste, TransformField ) ); + FUIAction( FExecuteAction::CreateSP(const_cast(this), &FComponentTransformDetails::OnPaste, TransformField ) ); } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION @@ -341,12 +341,18 @@ void FComponentTransformDetails::GenerateChildContent( IDetailChildrenBuilder& C .bColorAxisLabels( true ) .AllowResponsiveLayout( true ) .IsEnabled( this, &FComponentTransformDetails::GetIsEnabled ) + .OnXChanged( this, &FComponentTransformDetails::OnSetTransformAxis, ETextCommit::Default, ETransformField::Location, EAxisList::X, false ) + .OnYChanged( this, &FComponentTransformDetails::OnSetTransformAxis, ETextCommit::Default, ETransformField::Location, EAxisList::Y, false ) + .OnZChanged( this, &FComponentTransformDetails::OnSetTransformAxis, ETextCommit::Default, ETransformField::Location, EAxisList::Z, false ) .OnXCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Location, EAxisList::X, true ) .OnYCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Location, EAxisList::Y, true ) .OnZCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Location, EAxisList::Z, true ) .Font( FontInfo ) .TypeInterface( TypeInterface ) - .AllowSpin(false) + .AllowSpin( SelectedObjects.Num() == 1 ) + .SpinDelta( 1 ) + .OnBeginSliderMovement( this, &FComponentTransformDetails::OnBeginLocationSlider ) + .OnEndSliderMovement( this, &FComponentTransformDetails::OnEndLocationSlider ) ] +SHorizontalBox::Slot() .AutoWidth() @@ -409,7 +415,7 @@ void FComponentTransformDetails::GenerateChildContent( IDetailChildrenBuilder& C .AllowResponsiveLayout( true ) .bColorAxisLabels( true ) .IsEnabled( this, &FComponentTransformDetails::GetIsEnabled ) - .OnBeginSliderMovement( this, &FComponentTransformDetails::OnBeginRotatonSlider ) + .OnBeginSliderMovement( this, &FComponentTransformDetails::OnBeginRotationSlider ) .OnEndSliderMovement( this, &FComponentTransformDetails::OnEndRotationSlider ) .OnRollChanged( this, &FComponentTransformDetails::OnSetTransformAxis, ETextCommit::Default, ETransformField::Rotation, EAxisList::X, false ) .OnPitchChanged( this, &FComponentTransformDetails::OnSetTransformAxis, ETextCommit::Default, ETransformField::Rotation, EAxisList::Y, false ) @@ -417,7 +423,7 @@ void FComponentTransformDetails::GenerateChildContent( IDetailChildrenBuilder& C .OnRollCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Rotation, EAxisList::X, true ) .OnPitchCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Rotation, EAxisList::Y, true ) .OnYawCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Rotation, EAxisList::Z, true ) - .TypeInterface(TypeInterface) + .TypeInterface( TypeInterface ) .Font( FontInfo ) ] +SHorizontalBox::Slot() @@ -474,6 +480,9 @@ void FComponentTransformDetails::GenerateChildContent( IDetailChildrenBuilder& C .bColorAxisLabels( true ) .AllowResponsiveLayout( true ) .IsEnabled( this, &FComponentTransformDetails::GetIsEnabled ) + .OnXChanged( this, &FComponentTransformDetails::OnSetTransformAxis, ETextCommit::Default, ETransformField::Scale, EAxisList::X, false ) + .OnYChanged( this, &FComponentTransformDetails::OnSetTransformAxis, ETextCommit::Default, ETransformField::Scale, EAxisList::Y, false ) + .OnZChanged( this, &FComponentTransformDetails::OnSetTransformAxis, ETextCommit::Default, ETransformField::Scale, EAxisList::Z, false ) .OnXCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Scale, EAxisList::X, true ) .OnYCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Scale, EAxisList::Y, true ) .OnZCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Scale, EAxisList::Z, true ) @@ -481,7 +490,10 @@ void FComponentTransformDetails::GenerateChildContent( IDetailChildrenBuilder& C .ContextMenuExtenderY( this, &FComponentTransformDetails::ExtendYScaleContextMenu ) .ContextMenuExtenderZ( this, &FComponentTransformDetails::ExtendZScaleContextMenu ) .Font( FontInfo ) - .AllowSpin(false) + .AllowSpin( SelectedObjects.Num() == 1 ) + .SpinDelta( 0.0025f ) + .OnBeginSliderMovement( this, &FComponentTransformDetails::OnBeginScaleSlider ) + .OnEndSliderMovement( this, &FComponentTransformDetails::OnEndScaleSlider ) ] +SHorizontalBox::Slot() .AutoWidth() @@ -856,14 +868,14 @@ void FComponentTransformDetails::OnYScaleMirrored() { FSlateApplication::Get().ClearKeyboardFocus(EFocusCause::Mouse); FScopedTransaction Transaction(LOCTEXT("MirrorActorScaleY", "Mirror actor scale Y")); - OnSetTransform(ETransformField::Scale, EAxisList::X, FVector(1.0f), true, true); + OnSetTransform(ETransformField::Scale, EAxisList::Y, FVector(1.0f), true, true); } void FComponentTransformDetails::OnZScaleMirrored() { FSlateApplication::Get().ClearKeyboardFocus(EFocusCause::Mouse); FScopedTransaction Transaction(LOCTEXT("MirrorActorScaleZ", "Mirror actor scale Z")); - OnSetTransform(ETransformField::Scale, EAxisList::X, FVector(1.0f), true, true); + OnSetTransform(ETransformField::Scale, EAxisList::Z, FVector(1.0f), true, true); } void FComponentTransformDetails::CacheTransform() @@ -1060,7 +1072,7 @@ void FComponentTransformDetails::OnSetTransform(ETransformField::Type TransformF // Set the incoming value if (bMirror) { - NewComponentValue = -OldComponentValue; + NewComponentValue = GetAxisFilteredVector(Axis, -OldComponentValue, OldComponentValue); } else { @@ -1300,40 +1312,37 @@ void FComponentTransformDetails::OnSetTransformAxis(float NewValue, ETextCommit: OnSetTransform(TransformField, Axis, NewVector, false, bCommitted); } -void FComponentTransformDetails::OnBeginRotatonSlider() +void FComponentTransformDetails::BeginSliderTransaction(FText ActorTransaction, FText ComponentTransaction) const { - bEditingRotationInUI = true; - bool bBeganTransaction = false; - for( int32 ObjectIndex = 0; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex ) + for (TWeakObjectPtr ObjectPtr : SelectedObjects) { - TWeakObjectPtr ObjectPtr = SelectedObjects[ObjectIndex]; - if( ObjectPtr.IsValid() ) + if (ObjectPtr.IsValid()) { UObject* Object = ObjectPtr.Get(); - // Start a new transation when a rotator slider begins to change - // We'll end it when the slider is release + // Start a new transaction when a slider begins to change + // We'll end it when the slider is released // NOTE: One transaction per change, not per actor - if(!bBeganTransaction) + if (!bBeganTransaction) { - if(Object->IsA()) + if (Object->IsA()) { - GEditor->BeginTransaction( LOCTEXT( "OnSetRotation", "Set Rotation" ) ); + GEditor->BeginTransaction(ActorTransaction); } else { - GEditor->BeginTransaction( LOCTEXT( "OnSetRotation_ComponentDirect", "Modify Component(s)") ); + GEditor->BeginTransaction(ComponentTransaction); } bBeganTransaction = true; } - USceneComponent* SceneComponent = GetSceneComponentFromDetailsObject( Object ); - if( SceneComponent ) + USceneComponent* SceneComponent = GetSceneComponentFromDetailsObject(Object); + if (SceneComponent) { - FScopedSwitchWorldForObject WorldSwitcher( Object ); - + FScopedSwitchWorldForObject WorldSwitcher(Object); + if (SceneComponent->HasAnyFlags(RF_DefaultSubObject)) { // Default subobjects must be included in any undo/redo operations @@ -1342,18 +1351,41 @@ void FComponentTransformDetails::OnBeginRotatonSlider() // Call modify but not PreEdit, we don't do the proper "Edit" until it's committed SceneComponent->Modify(); + } + } + } + + // Just in case we couldn't start a new transaction for some reason + if (!bBeganTransaction) + { + GEditor->BeginTransaction(ActorTransaction); + } +} + +void FComponentTransformDetails::OnBeginRotationSlider() +{ + FText ActorTransaction = LOCTEXT("OnSetRotation", "Set Rotation"); + FText ComponentTransaction = LOCTEXT("OnSetRotation_ComponentDirect", "Modify Component(s)"); + BeginSliderTransaction(ActorTransaction, ComponentTransaction); + + bEditingRotationInUI = true; + + for (TWeakObjectPtr ObjectPtr : SelectedObjects) + { + if (ObjectPtr.IsValid()) + { + UObject* Object = ObjectPtr.Get(); + + USceneComponent* SceneComponent = GetSceneComponentFromDetailsObject(Object); + if (SceneComponent) + { + FScopedSwitchWorldForObject WorldSwitcher(Object); // Add/update cached rotation value prior to slider interaction ObjectToRelativeRotationMap.FindOrAdd(SceneComponent) = SceneComponent->RelativeRotation; } } } - - // Just in case we couldn't start a new transaction for some reason - if(!bBeganTransaction) - { - GEditor->BeginTransaction( LOCTEXT( "OnSetRotation", "Set Rotation" ) ); - } } void FComponentTransformDetails::OnEndRotationSlider(float NewValue) @@ -1363,4 +1395,28 @@ void FComponentTransformDetails::OnEndRotationSlider(float NewValue) GEditor->EndTransaction(); } +void FComponentTransformDetails::OnBeginLocationSlider() +{ + FText ActorTransaction = LOCTEXT("OnSetLocation", "Set Location"); + FText ComponentTransaction = LOCTEXT("OnSetLocation_ComponentDirect", "Modify Component Location"); + BeginSliderTransaction(ActorTransaction, ComponentTransaction); +} + +void FComponentTransformDetails::OnEndLocationSlider(float NewValue) +{ + GEditor->EndTransaction(); +} + +void FComponentTransformDetails::OnBeginScaleSlider() +{ + FText ActorTransaction = LOCTEXT("OnSetScale", "Set Scale"); + FText ComponentTransaction = LOCTEXT("OnSetScale_ComponentDirect", "Modify Component Scale"); + BeginSliderTransaction(ActorTransaction, ComponentTransaction); +} + +void FComponentTransformDetails::OnEndScaleSlider(float NewValue) +{ + GEditor->EndTransaction(); +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/DetailCustomizations/Private/ComponentTransformDetails.h b/Engine/Source/Editor/DetailCustomizations/Private/ComponentTransformDetails.h index 435a9fb5f7ac..c1973baac399 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/ComponentTransformDetails.h +++ b/Engine/Source/Editor/DetailCustomizations/Private/ComponentTransformDetails.h @@ -81,16 +81,32 @@ private: */ void OnSetTransformAxis(float NewValue, ETextCommit::Type CommitInfo, ETransformField::Type TransformField, EAxisList::Type Axis, bool bCommitted); - /** - * Called when the one of the axis sliders for object rotation begins to change for the first time - */ - void OnBeginRotatonSlider(); - /** - * Called when the one of the axis sliders for object rotation is released + /** + * Helper to begin a new transaction for a slider interaction. + * @param ActorTransaction The name to give the transaction when changing an actor transform. + * @param ComponentTransaction The name to give the transaction when directly editing a component transform. */ + void BeginSliderTransaction(FText ActorTransaction, FText ComponentTransaction) const; + + /** Called when the one of the axis sliders for object rotation begins to change for the first time */ + void OnBeginRotationSlider(); + + /** Called when the one of the axis sliders for object rotation is released */ void OnEndRotationSlider(float NewValue); + /** Called when one of the axis sliders for object location begins to change */ + void OnBeginLocationSlider(); + + /** Called when one of the axis sliders for object location is released */ + void OnEndLocationSlider(float NewValue); + + /** Called when one of the axis sliders for object scale begins to change */ + void OnBeginScaleSlider(); + + /** Called when one of the axis sliders for object scale is released */ + void OnEndScaleSlider(float NewValue); + /** @return Icon to use in the preserve scale ratio check box */ const FSlateBrush* GetPreserveScaleRatioImage() const; @@ -216,7 +232,7 @@ private: /** @return The visibility of the "Reset to Default" button for the scale component */ EVisibility GetScaleResetVisibility() const; - /** Cache a single unit to display all location comonents in */ + /** Cache a single unit to display all location components in */ void CacheCommonLocationUnits(); private: @@ -254,7 +270,7 @@ private: TOptional Y; TOptional Z; }; - + FSelectedActorInfo SelectedActorInfo; /** Copy of selected actor references in the details view */ TArray< TWeakObjectPtr > SelectedObjects; diff --git a/Engine/Source/Editor/DetailCustomizations/Private/CurveStructCustomization.cpp b/Engine/Source/Editor/DetailCustomizations/Private/CurveStructCustomization.cpp index de136bf9f320..07df9309d08b 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/CurveStructCustomization.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/CurveStructCustomization.cpp @@ -108,7 +108,7 @@ void FCurveStructCustomization::CustomizeHeader( TSharedRef InS } else { - CurveWidget->SetCurveOwner(this); + CurveWidget->SetCurveOwner(this, InStructPropertyHandle->IsEditable()); } } else @@ -276,7 +276,7 @@ void FCurveStructCustomization::OnExternalCurveChanged(TSharedRefSetCurveOwner(this); + CurveWidget->SetCurveOwner(this, CurvePropertyHandle->IsEditable()); } CurvePropertyHandle->NotifyPostChange(); diff --git a/Engine/Source/Editor/DetailCustomizations/Private/DeviceProfileDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/DeviceProfileDetails.cpp index 021ace76228d..18046fdb523f 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/DeviceProfileDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/DeviceProfileDetails.cpp @@ -747,7 +747,7 @@ void FDeviceProfileConsoleVariablesPropertyDetails::CreateRowWidgetForCVarProper SNew(SEditableTextBox) .Text(FText::FromString(CVarValueAsString)) .SelectAllTextWhenFocused(true) - .OnTextCommitted(this, &FDeviceProfileConsoleVariablesPropertyDetails::OnCVarValueCommited, InProperty) + .OnTextCommitted(const_cast(this), &FDeviceProfileConsoleVariablesPropertyDetails::OnCVarValueCommited, InProperty) ] + SHorizontalBox::Slot() .Padding(DeviceProfilePropertyConstants::PropertyPadding) @@ -755,7 +755,7 @@ void FDeviceProfileConsoleVariablesPropertyDetails::CreateRowWidgetForCVarProper [ SNew(SButton) .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") - .OnClicked(this, &FDeviceProfileConsoleVariablesPropertyDetails::OnRemoveCVarProperty, InProperty) + .OnClicked(const_cast(this), &FDeviceProfileConsoleVariablesPropertyDetails::OnRemoveCVarProperty, InProperty) .ContentPadding(4.0f) .ForegroundColor(FSlateColor::UseForeground()) .IsFocusable(false) diff --git a/Engine/Source/Editor/DetailCustomizations/Private/FbxImportUIDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/FbxImportUIDetails.cpp index fbd207403285..2c147a77e797 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/FbxImportUIDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/FbxImportUIDetails.cpp @@ -593,6 +593,18 @@ void FFbxImportUIDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder } } } + + TSharedPtr ReorderMaterialToFbxOrderProp = nullptr; + if (ImportUI->MeshTypeToImport == FBXIT_StaticMesh) + { + ReorderMaterialToFbxOrderProp = StaticMeshDataProp->GetChildHandle(GET_MEMBER_NAME_CHECKED(UFbxStaticMeshImportData, bReorderMaterialToFbxOrder)); + } + else if (ImportUI->MeshTypeToImport == FBXIT_SkeletalMesh) + { + ReorderMaterialToFbxOrderProp = SkeletalMeshDataProp->GetChildHandle(GET_MEMBER_NAME_CHECKED(UFbxSkeletalMeshImportData, bReorderMaterialToFbxOrder)); + } + //Add the advance reorder option at the end + MaterialCategory.AddProperty(ReorderMaterialToFbxOrderProp); } //Information category diff --git a/Engine/Source/Editor/DetailCustomizations/Private/MarginCustomization.cpp b/Engine/Source/Editor/DetailCustomizations/Private/MarginCustomization.cpp index a0ed8fac9677..088e3798603c 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/MarginCustomization.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/MarginCustomization.cpp @@ -23,6 +23,8 @@ void FMarginStructCustomization::CustomizeHeader( TSharedRefGetProperty()->GetMetaData( TEXT( "UVSpace" ) ) ); bIsMarginUsingUVSpace = UVSpaceString.Len() > 0 && UVSpaceString == TEXT( "true" ); + NumericInterface = MakeShareable(new TDefaultNumericTypeInterface); + uint32 NumChildren; StructPropertyHandle->GetNumChildren( NumChildren ); @@ -92,16 +94,17 @@ TSharedRef FMarginStructCustomization::MakeChildPropertyWidget( int32 P .Value( this, &FMarginStructCustomization::OnGetValue, PropertyIndex ) .Font( IDetailLayoutBuilder::GetDetailFont() ) .UndeterminedString( NSLOCTEXT( "PropertyEditor", "MultipleValues", "Multiple Values") ) - .OnValueCommitted( this, &FMarginStructCustomization::OnValueCommitted, PropertyIndex ) - .OnValueChanged( this, &FMarginStructCustomization::OnValueChanged, PropertyIndex ) - .OnBeginSliderMovement( this, &FMarginStructCustomization::OnBeginSliderMovement ) - .OnEndSliderMovement( this, &FMarginStructCustomization::OnEndSliderMovement ) + .OnValueCommitted( const_cast(this), &FMarginStructCustomization::OnValueCommitted, PropertyIndex ) + .OnValueChanged( const_cast(this), &FMarginStructCustomization::OnValueChanged, PropertyIndex ) + .OnBeginSliderMovement( const_cast(this), &FMarginStructCustomization::OnBeginSliderMovement ) + .OnEndSliderMovement( const_cast(this), &FMarginStructCustomization::OnEndSliderMovement ) .LabelVAlign( VAlign_Center ) .AllowSpin( bIsMarginUsingUVSpace ? true : false ) .MinValue( bIsMarginUsingUVSpace ? 0.0f : TNumericLimits::Lowest() ) .MaxValue( bIsMarginUsingUVSpace ? 1.0f : TNumericLimits::Max() ) .MinSliderValue( bIsMarginUsingUVSpace ? 0.0f : TNumericLimits::Lowest() ) .MaxSliderValue( bIsMarginUsingUVSpace ? 1.0f : TNumericLimits::Max() ) + .TypeInterface(NumericInterface) .Label() [ SNew( STextBlock ) @@ -141,10 +144,11 @@ void FMarginStructCustomization::OnMarginTextCommitted( const FText& InText, ETe LeftString.TrimStartAndEndInline(); - if( LeftString.IsNumeric() ) + float Value = 0.f; + TOptional NumericValue = NumericInterface->FromString(LeftString, Value); + if (NumericValue.IsSet()) { - float Value; - TTypeFromString::FromString( Value, *LeftString ); + Value = NumericValue.GetValue(); PropertyValues.Add( bIsMarginUsingUVSpace ? FMath::Clamp( Value, 0.0f, 1.0f ) : FMath::Max( Value, 0.0f ) ); } else diff --git a/Engine/Source/Editor/DetailCustomizations/Private/MarginCustomization.h b/Engine/Source/Editor/DetailCustomizations/Private/MarginCustomization.h index a60601f0e25b..f26422bb547d 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/MarginCustomization.h +++ b/Engine/Source/Editor/DetailCustomizations/Private/MarginCustomization.h @@ -4,6 +4,7 @@ #include "CoreMinimal.h" #include "Widgets/SWidget.h" +#include "Widgets/Input/NumericTypeInterface.h" #include "UnrealClient.h" #include "IPropertyTypeCustomization.h" #include "PropertyHandle.h" @@ -103,5 +104,8 @@ private: /** Margin text editable text box */ TSharedPtr MarginEditableTextBox; + + /** Used to evaluate margin strings as mathematical expressions **/ + TSharedPtr< INumericTypeInterface > NumericInterface; }; diff --git a/Engine/Source/Editor/DetailCustomizations/Private/MaterialProxySettingsCustomizations.cpp b/Engine/Source/Editor/DetailCustomizations/Private/MaterialProxySettingsCustomizations.cpp index 3019d75bece3..99d4c92d88a0 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/MaterialProxySettingsCustomizations.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/MaterialProxySettingsCustomizations.cpp @@ -138,16 +138,16 @@ void FMaterialProxySettingsCustomizations::AddTextureSizeClamping(TSharedPtrGetProperty()->SetMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); TextureSizeProperty->GetProperty()->SetMetaData(TEXT("UIMax"), *MaxTextureResolutionString); - PropertyX->GetProperty()->SetMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); - PropertyX->GetProperty()->SetMetaData(TEXT("UIMax"), *MaxTextureResolutionString); - PropertyY->GetProperty()->SetMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); - PropertyY->GetProperty()->SetMetaData(TEXT("UIMax"), *MaxTextureResolutionString); + PropertyX->SetInstanceMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); + PropertyX->SetInstanceMetaData(TEXT("UIMax"), *MaxTextureResolutionString); + PropertyY->SetInstanceMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); + PropertyY->SetInstanceMetaData(TEXT("UIMax"), *MaxTextureResolutionString); const FString MinTextureResolutionString("1"); - PropertyX->GetProperty()->SetMetaData(TEXT("ClampMin"), *MinTextureResolutionString); - PropertyX->GetProperty()->SetMetaData(TEXT("UIMin"), *MinTextureResolutionString); - PropertyY->GetProperty()->SetMetaData(TEXT("ClampMin"), *MinTextureResolutionString); - PropertyY->GetProperty()->SetMetaData(TEXT("UIMin"), *MinTextureResolutionString); + PropertyX->SetInstanceMetaData(TEXT("ClampMin"), *MinTextureResolutionString); + PropertyX->SetInstanceMetaData(TEXT("UIMin"), *MinTextureResolutionString); + PropertyY->SetInstanceMetaData(TEXT("ClampMin"), *MinTextureResolutionString); + PropertyY->SetInstanceMetaData(TEXT("UIMin"), *MinTextureResolutionString); } EVisibility FMaterialProxySettingsCustomizations::AreManualOverrideTextureSizesEnabled() const diff --git a/Engine/Source/Editor/DetailCustomizations/Private/MathStructProxyCustomizations.cpp b/Engine/Source/Editor/DetailCustomizations/Private/MathStructProxyCustomizations.cpp index a26c9df9df8c..d5f17941f994 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/MathStructProxyCustomizations.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/MathStructProxyCustomizations.cpp @@ -29,6 +29,7 @@ TSharedRef FMathStructProxyCustomization::MakeNumericProxyWidget(TShare return SNew( SNumericEntryBox ) + .IsEnabled( this, &FMathStructProxyCustomization::IsValueEnabled, WeakHandlePtr ) .Value( this, &FMathStructProxyCustomization::OnGetValue, WeakHandlePtr, ProxyValue ) .Font( IDetailLayoutBuilder::GetDetailFont() ) .UndeterminedString( NSLOCTEXT("PropertyEditor", "MultipleValues", "Multiple Values") ) diff --git a/Engine/Source/Editor/DetailCustomizations/Private/MoviePlayerSettingsDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/MoviePlayerSettingsDetails.cpp index 30f6bc4e84d5..33c8a700f754 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/MoviePlayerSettingsDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/MoviePlayerSettingsDetails.cpp @@ -16,6 +16,7 @@ #include "Widgets/Input/SFilePathPicker.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" +#include "HAL/FileManager.h" #define LOCTEXT_NAMESPACE "MoviePlayerSettingsDetails" @@ -77,6 +78,16 @@ void FMoviePlayerSettingsDetails::GenerateArrayElementWidget(TSharedRef Property ) { + // Ignore if value is unchanged. + FString OldValue; + if (Property->GetValueAsFormattedString(OldValue) == FPropertyAccess::Success) + { + if (PickedPath == OldValue) + { + return; + } + } + FEditorDirectories::Get().SetLastDirectory(ELastDirectory::GENERIC_OPEN, FPaths::GetPath(PickedPath)); // sanitize the location of the chosen movies to the content/movies directory @@ -102,35 +113,75 @@ void FMoviePlayerSettingsDetails::HandleFilePathPickerPathPicked( const FString& } else if (!PickedPath.IsEmpty()) { - // ask the user if they want to import this movie - FSuppressableWarningDialog::FSetupInfo Info( - LOCTEXT("ExternalMovieImportWarning", "This movie needs to be copied into your project, would you like to copy the file now?"), - LOCTEXT("ExternalMovieImportTitle", "Copy Movie"), - TEXT("ImportMovieIntoProject") ); - Info.ConfirmText = LOCTEXT("ExternalMovieImport_Confirm", "Copy"); - Info.CancelText = LOCTEXT("ExternalMovieImport_Cancel", "Don't Copy"); - - FSuppressableWarningDialog ImportWarningDialog( Info ); - - if(ImportWarningDialog.ShowModal() != FSuppressableWarningDialog::EResult::Cancel) + // If the path of PickedPath is empty then we are dealing with a movie already in the correct directory. + if (FPaths::GetPath(PickedPath).IsEmpty()) { - const FString FileName = FPaths::GetCleanFilename(PickedPath); - const FString DestPath = MoviesBaseDir / FileName; + FString FullPickedPath = FPaths::Combine(MoviesBaseDir, PickedPath); - FText FailReason; - - if (SourceControlHelpers::CopyFileUnderSourceControl(DestPath, PickedPath, LOCTEXT("MovieFileDescription", "movie"), FailReason)) + // Look for this movie. + TArray ExistingMovieFiles; + IFileManager::Get().FindFiles(ExistingMovieFiles, *MoviesBaseDir); + bool bHasValidMovie = ExistingMovieFiles.ContainsByPredicate( + [&PickedPath](const FString& ExistingMovie) { - // trim the path so we just have a partial path with no extension (the movie player expects this) - Property->SetValue(FPaths::GetBaseFilename(DestPath.RightChop(MoviesBaseDir.Len()))); + return ExistingMovie.Contains(PickedPath); + }); + + // Did we find a movie? + if (bHasValidMovie) + { + // Yes. + Property->SetValue(FPaths::GetBaseFilename(PickedPath)); } else { - FNotificationInfo FailureInfo(FailReason); - FailureInfo.ExpireDuration = 3.0f; - FSlateNotificationManager::Get().AddNotification(FailureInfo); + // Nope. Bring up a dialog box informing the user. + FSuppressableWarningDialog::FSetupInfo Info( + LOCTEXT("ExternalMovieImportNotExistWarning", "This movie does not exist."), + LOCTEXT("ExternalMovieImportNotExistTitle", "Does not exist"), + TEXT("ImportMovieIntoProjectNotExist")); + Info.ConfirmText = LOCTEXT("ExternalMovieImportNotExist_Confirm", "OK"); + Info.CancelText = LOCTEXT("ExternalMovieImportNotExist_Cancel", "Cancel"); + + FSuppressableWarningDialog ImportWarningDialog(Info); + if (ImportWarningDialog.ShowModal() == FSuppressableWarningDialog::EResult::Cancel) + { + Property->SetValue(FPaths::GetBaseFilename(PickedPath)); + } } - } + } + else + { + // ask the user if they want to import this movie + FSuppressableWarningDialog::FSetupInfo Info( + LOCTEXT("ExternalMovieImportWarning", "This movie needs to be copied into your project, would you like to copy the file now?"), + LOCTEXT("ExternalMovieImportTitle", "Copy Movie"), + TEXT("ImportMovieIntoProject")); + Info.ConfirmText = LOCTEXT("ExternalMovieImport_Confirm", "Copy"); + Info.CancelText = LOCTEXT("ExternalMovieImport_Cancel", "Don't Copy"); + + FSuppressableWarningDialog ImportWarningDialog(Info); + + if (ImportWarningDialog.ShowModal() != FSuppressableWarningDialog::EResult::Cancel) + { + const FString FileName = FPaths::GetCleanFilename(PickedPath); + const FString DestPath = MoviesBaseDir / FileName; + + FText FailReason; + + if (SourceControlHelpers::CopyFileUnderSourceControl(DestPath, PickedPath, LOCTEXT("MovieFileDescription", "movie"), FailReason)) + { + // trim the path so we just have a partial path with no extension (the movie player expects this) + Property->SetValue(FPaths::GetBaseFilename(DestPath.RightChop(MoviesBaseDir.Len()))); + } + else + { + FNotificationInfo FailureInfo(FailReason); + FailureInfo.ExpireDuration = 3.0f; + FSlateNotificationManager::Get().AddNotification(FailureInfo); + } + } + } } else { diff --git a/Engine/Source/Editor/DetailCustomizations/Private/NavAgentSelectorCustomization.cpp b/Engine/Source/Editor/DetailCustomizations/Private/NavAgentSelectorCustomization.cpp index 4c68af99fde0..d4f130698c5e 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/NavAgentSelectorCustomization.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/NavAgentSelectorCustomization.cpp @@ -39,7 +39,7 @@ void FNavAgentSelectorCustomization::CustomizeChildren(TSharedRefGetNumChildren(NumChildren); FString AgentPrefix("bSupportsAgent"); - const UNavigationSystemV1* NavSysCDO = (*GEngine->NavigationSystemClass != nullptr) + const UNavigationSystemV1* NavSysCDO = (*GEngine->NavigationSystemClass != nullptr && GEngine->NavigationSystemClass->IsChildOf(UNavigationSystemV1::StaticClass())) ? GetDefault(GEngine->NavigationSystemClass) : GetDefault(); @@ -85,7 +85,7 @@ void FNavAgentSelectorCustomization::CustomizeChildren(TSharedRefNavigationSystemClass != nullptr) + const UNavigationSystemV1* NavSysCDO = (*GEngine->NavigationSystemClass != nullptr && GEngine->NavigationSystemClass->IsChildOf(UNavigationSystemV1::StaticClass())) ? GetDefault(GEngine->NavigationSystemClass) : GetDefault(); diff --git a/Engine/Source/Editor/DetailCustomizations/Private/ObjectDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/ObjectDetails.cpp index 5297aa99ea45..3decdd61edbd 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/ObjectDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/ObjectDetails.cpp @@ -46,9 +46,9 @@ void FObjectDetails::AddExperimentalWarningCategory(IDetailLayoutBuilder& Detail const FText CategoryDisplayName = LOCTEXT("WarningCategoryDisplayName", "Warning"); FString ClassUsed = DetailBuilder.GetTopLevelProperty().ToString(); const FText WarningText = bBaseClassIsExperimental ? FText::Format( LOCTEXT("ExperimentalClassWarning", "Uses experimental class: {0}") , FText::FromString(ClassUsed) ) - : FText::Format( LOCTEXT("EarlyAccessClassWarning", "Uses early access class {0}"), FText::FromString(*ClassUsed) ); + : FText::Format( LOCTEXT("EarlyAccessClassWarning", "Uses beta class {0}"), FText::FromString(*ClassUsed) ); const FText SearchString = WarningText; - const FText Tooltip = bBaseClassIsExperimental ? LOCTEXT("ExperimentalClassTooltip", "Here be dragons! Uses one or more unsupported 'experimental' classes") : LOCTEXT("EarlyAccessClassTooltip", "Uses one or more 'early access' classes"); + const FText Tooltip = bBaseClassIsExperimental ? LOCTEXT("ExperimentalClassTooltip", "Here be dragons! Uses one or more unsupported 'experimental' classes") : LOCTEXT("EarlyAccessClassTooltip", "Uses one or more 'beta' classes"); const FString ExcerptName = bBaseClassIsExperimental ? TEXT("ObjectUsesExperimentalClass") : TEXT("ObjectUsesEarlyAccessClass"); const FSlateBrush* WarningIcon = FEditorStyle::GetBrush(bBaseClassIsExperimental ? "PropertyEditor.ExperimentalClass" : "PropertyEditor.EarlyAccessClass"); @@ -110,7 +110,11 @@ void FObjectDetails::AddCallInEditorMethods(IDetailLayoutBuilder& DetailBuilder) } } - CallInEditorFunctions.Add(*FunctionIter); + const FName FunctionName = TestFunction->GetFName(); + if (!CallInEditorFunctions.FindByPredicate([&FunctionName](const UFunction* Func) { return Func->GetFName() == FunctionName; })) + { + CallInEditorFunctions.Add(*FunctionIter); + } } } diff --git a/Engine/Source/Editor/DetailCustomizations/Private/RenderPassesCustomization.cpp b/Engine/Source/Editor/DetailCustomizations/Private/RenderPassesCustomization.cpp index d198678fd47b..df17bca968d4 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/RenderPassesCustomization.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/RenderPassesCustomization.cpp @@ -100,10 +100,15 @@ public: } else { - TSharedRef RemoveButton = PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([=]{ - Property->Value.RemoveAt(PropertyIndex); - Update(); - })); + TSharedRef RemoveButton = PropertyCustomizationHelpers::MakeRemoveButton( + FSimpleDelegate::CreateLambda([this, PropertyIndex] + { + if (PropertyIndex < Property->Value.Num()) + { + Property->Value.RemoveAt(PropertyIndex); + Update(); + } + })); EnabledPassesContainer->AddSlot() .AutoHeight() @@ -133,6 +138,8 @@ public: ComboEntries.Sort([](const TSharedPtr& A, const TSharedPtr& B){ return A->Text.CompareToCaseIgnored(B->Text) < 0; }); + + ComboBox->ClearSelection(); ComboBox->SetVisibility(ComboEntries.Num() == 0 ? EVisibility::Collapsed : EVisibility::Visible); } diff --git a/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.cpp index 46acc38abfb4..2449334ce505 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.cpp @@ -34,79 +34,105 @@ void FSkeletalControlNodeDetails::CustomizeDetails(class IDetailLayoutBuilder& D TSharedRef AvailablePins = DetailBuilder.GetProperty("ShowPinForProperties"); ArrayProperty = AvailablePins->AsArray(); + bool bShowAvailablePins = true; + const TArray< TWeakObjectPtr >& SelectedObjects = DetailBuilder.GetSelectedObjects(); - for (const TWeakObjectPtr& CurrentObject : SelectedObjects) + if (SelectedObjects.Num() == 1) { - if (Cast(CurrentObject.Get()) || Cast(CurrentObject.Get())) - { - if (HideUnconnectedPinsNode.IsValid()) - { - // Have more than one break struct node, don't cache so we don't - // create the hide unconnected pins UI - HideUnconnectedPinsNode = nullptr; - break; - } - HideUnconnectedPinsNode = Cast(CurrentObject.Get()); - } + UObject* CurObj = SelectedObjects[0].Get(); + HideUnconnectedPinsNode = ((CurObj && (CurObj->IsA() || CurObj->IsA())) ? static_cast(CurObj) : nullptr); } - - TSet UniqueCategoryNames; + else { - int32 NumElements = 0; + UStruct* CommonStruct = nullptr; + for (const TWeakObjectPtr& CurrentObject : SelectedObjects) { - uint32 UnNumElements = 0; - if (ArrayProperty.IsValid() && (FPropertyAccess::Success == ArrayProperty->GetNumElements(UnNumElements))) + UObject* CurObj = CurrentObject.Get(); + if (UK2Node_StructOperation* StructOpNode = Cast(CurObj)) { - NumElements = static_cast(UnNumElements); + if (CommonStruct && CommonStruct != StructOpNode->StructType) + { + bShowAvailablePins = false; + break; + } + CommonStruct = StructOpNode->StructType; } - } - for (int32 Index = 0; Index < NumElements; ++Index) - { - TSharedRef StructPropHandle = ArrayProperty->GetElement(Index); - TSharedPtr CategoryPropHandle = StructPropHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FOptionalPinFromProperty, CategoryName)); - check(CategoryPropHandle.IsValid()); - FName CategoryNameValue; - const FPropertyAccess::Result Result = CategoryPropHandle->GetValue(CategoryNameValue); - if (ensure(FPropertyAccess::Success == Result)) + else if (UK2Node_GetClassDefaults* CDONode = Cast(CurObj)) { - UniqueCategoryNames.Add(CategoryNameValue); + if (CommonStruct && CommonStruct != CDONode->GetInputClass()) + { + bShowAvailablePins = false; + break; + } + CommonStruct = CDONode->GetInputClass(); } } } - //@TODO: Shouldn't show this if the available pins array is empty! - const bool bGenerateHeader = true; - const bool bDisplayResetToDefault = false; - const bool bDisplayElementNum = false; - const bool bForAdvanced = false; - for (const FName& CategoryName : UniqueCategoryNames) + if (bShowAvailablePins) { - //@TODO: Pay attention to category filtering here + TSet UniqueCategoryNames; + { + int32 NumElements = 0; + { + uint32 UnNumElements = 0; + if (ArrayProperty.IsValid() && (FPropertyAccess::Success == ArrayProperty->GetNumElements(UnNumElements))) + { + NumElements = static_cast(UnNumElements); + } + } + for (int32 Index = 0; Index < NumElements; ++Index) + { + TSharedRef StructPropHandle = ArrayProperty->GetElement(Index); + TSharedPtr CategoryPropHandle = StructPropHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FOptionalPinFromProperty, CategoryName)); + check(CategoryPropHandle.IsValid()); + FName CategoryNameValue; + const FPropertyAccess::Result Result = CategoryPropHandle->GetValue(CategoryNameValue); + if (ensure(FPropertyAccess::Success == Result)) + { + UniqueCategoryNames.Add(CategoryNameValue); + } + } + } + + //@TODO: Shouldn't show this if the available pins array is empty! + const bool bGenerateHeader = true; + const bool bDisplayResetToDefault = false; + const bool bDisplayElementNum = false; + const bool bForAdvanced = false; + for (const FName& CategoryName : UniqueCategoryNames) + { + //@TODO: Pay attention to category filtering here - TSharedRef AvailablePinsBuilder = MakeShareable(new FDetailArrayBuilder(AvailablePins, bGenerateHeader, bDisplayResetToDefault, bDisplayElementNum)); - AvailablePinsBuilder->OnGenerateArrayElementWidget(FOnGenerateArrayElementWidget::CreateSP(this, &FSkeletalControlNodeDetails::OnGenerateElementForPropertyPin, CategoryName)); - AvailablePinsBuilder->SetDisplayName((CategoryName == NAME_None) ? LOCTEXT("DefaultCategory", "Default Category") : FText::FromName(CategoryName)); - DetailCategory->AddCustomBuilder(AvailablePinsBuilder, bForAdvanced); - } + TSharedRef AvailablePinsBuilder = MakeShareable(new FDetailArrayBuilder(AvailablePins, bGenerateHeader, bDisplayResetToDefault, bDisplayElementNum)); + AvailablePinsBuilder->OnGenerateArrayElementWidget(FOnGenerateArrayElementWidget::CreateSP(this, &FSkeletalControlNodeDetails::OnGenerateElementForPropertyPin, CategoryName)); + AvailablePinsBuilder->SetDisplayName((CategoryName == NAME_None) ? LOCTEXT("DefaultCategory", "Default Category") : FText::FromName(CategoryName)); + DetailCategory->AddCustomBuilder(AvailablePinsBuilder, bForAdvanced); + } - // Add the action buttons - if(HideUnconnectedPinsNode.IsValid()) - { - FDetailWidgetRow& GroupActionsRow = DetailCategory->AddCustomRow(LOCTEXT("GroupActionsSearchText", "Split Sort")) - .ValueContent() - .HAlign(HAlign_Left) - .MaxDesiredWidth(250.f) - [ - SNew(SButton) - .OnClicked(this, &FSkeletalControlNodeDetails::HideAllUnconnectedPins) - .ToolTipText(LOCTEXT("HideAllUnconnectedPinsTooltip", "All unconnected pins get hidden (removed from the graph node)")) + // Add the action buttons + if(HideUnconnectedPinsNode.IsValid()) + { + FDetailWidgetRow& GroupActionsRow = DetailCategory->AddCustomRow(LOCTEXT("GroupActionsSearchText", "Split Sort")) + .ValueContent() + .HAlign(HAlign_Left) + .MaxDesiredWidth(250.f) [ - SNew(STextBlock) - .Text(LOCTEXT("HideAllUnconnectedPins", "Hide Unconnected Pins")) - .Font(DetailBuilder.GetDetailFont()) - ] - ]; + SNew(SButton) + .OnClicked(this, &FSkeletalControlNodeDetails::HideAllUnconnectedPins) + .ToolTipText(LOCTEXT("HideAllUnconnectedPinsTooltip", "All unconnected pins get hidden (removed from the graph node)")) + [ + SNew(STextBlock) + .Text(LOCTEXT("HideAllUnconnectedPins", "Hide Unconnected Pins")) + .Font(DetailBuilder.GetDetailFont()) + ] + ]; + } + } + else + { + DetailBuilder.HideProperty(AvailablePins); } } diff --git a/Engine/Source/Editor/DetailCustomizations/Private/SkeletalMeshReductionSettingsDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/SkeletalMeshReductionSettingsDetails.cpp index 0e69c3bad7a3..0e468646a528 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/SkeletalMeshReductionSettingsDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/SkeletalMeshReductionSettingsDetails.cpp @@ -15,6 +15,8 @@ #include "Widgets/Text/STextBlock.h" #include "SkeletalRenderPublic.h" +#include "Rendering/SkeletalMeshModel.h" + #define LOCTEXT_NAMESPACE "SkeletalMeshReductionSettingsDetails" TSharedRef FSkeletalMeshReductionSettingsDetails::MakeInstance() @@ -54,6 +56,14 @@ void FSkeletalMeshReductionSettingsDetails::CustomizeChildren(TSharedRef Objects; + StructPropertyHandle->GetOuterObjects(Objects); + if (Objects.Num() > 0) + { + SkeletalMesh = Cast(Objects[0]); + } + ReductionMethodPropertyHandle = StructPropertyHandle->GetChildHandle(CustomizedProperties[0]); NumTrianglesPercentagePropertyHandle = StructPropertyHandle->GetChildHandle(CustomizedProperties[1]); MaxDeviationPercentagePropertyHandle = StructPropertyHandle->GetChildHandle(CustomizedProperties[2]); @@ -73,11 +83,13 @@ void FSkeletalMeshReductionSettingsDetails::CustomizeChildren(TSharedRef& BaseLODPropertyHandle) + auto BaseLODCustomization = [LODIndex, &StructBuilder, &SkeletalMesh](TSharedPtr& BaseLODPropertyHandle) { - // Only able to do this for LOD2 and above, so only show the property if this is the case - if (LODIndex > 1) + // Only able to do this for LOD1 and above, so only show the property if this is the case + if (LODIndex > 0) { + const FSkeletalMeshModel* ImportedModel = SkeletalMesh != nullptr ? SkeletalMesh->GetImportedModel() : nullptr; + bool AllowInline = ImportedModel != nullptr && ImportedModel->LODModels.IsValidIndex(LODIndex) && !ImportedModel->LODModels[LODIndex].RawSkeletalMeshBulkData.IsEmpty(); // Add and retrieve the default widgets IDetailPropertyRow& Row = StructBuilder.AddProperty(BaseLODPropertyHandle->AsShared()); @@ -99,7 +111,7 @@ void FSkeletalMeshReductionSettingsDetails::CustomizeChildren(TSharedRef) .Font(IDetailLayoutBuilder::GetDetailFont()) .MinValue(0) - .MaxValue(FMath::Max(LODIndex - 1, 0)) + .MaxValue(FMath::Max(LODIndex - (AllowInline ? 0 : 1), 0)) .Value_Lambda([BaseLODPropertyHandle]() -> int32 { int32 Value = INDEX_NONE; diff --git a/Engine/Source/Editor/DetailCustomizations/Private/SlateBrushCustomization.cpp b/Engine/Source/Editor/DetailCustomizations/Private/SlateBrushCustomization.cpp index 928491c88a10..a7316f541565 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/SlateBrushCustomization.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/SlateBrushCustomization.cpp @@ -981,6 +981,8 @@ class SSlateBrushStaticPreview : public SCompoundWidget { ResourceObjectProperty = InResourceObjectProperty; + UpdateBrush(); + ChildSlot [ SNew(SHorizontalBox) @@ -1014,19 +1016,7 @@ class SSlateBrushStaticPreview : public SCompoundWidget void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { - TArray RawData; - - if (ResourceObjectProperty.IsValid() && ResourceObjectProperty->GetProperty()) - { - ResourceObjectProperty->AccessRawData(RawData); - - // RawData will be empty when creating a new Data Table, an idiosyncrasy - // of the Data Table Editor... - if (RawData.Num() > 0) - { - TemporaryBrush = *static_cast(RawData[0]); - } - } + UpdateBrush(); } private: @@ -1061,6 +1051,22 @@ private: return TemporaryBrush.DrawAs == ESlateBrushDrawType::Image ? EVisibility::Visible : EVisibility::Collapsed; } + void UpdateBrush() + { + if (ResourceObjectProperty.IsValid() && ResourceObjectProperty->GetProperty()) + { + TArray RawData; + ResourceObjectProperty->AccessRawData(RawData); + + // RawData will be empty when creating a new Data Table, an idiosyncrasy + // of the Data Table Editor... + if (RawData.Num() > 0) + { + TemporaryBrush = *static_cast(RawData[0]); + } + } + } + private: /** @@ -1154,43 +1160,13 @@ class SBrushResourceObjectBox : public SCompoundWidget ] ] ]; + + UpdateResourceError(); } void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { - UObject* Resource = nullptr; - - if( ResourceObjectProperty->GetValue(Resource) == FPropertyAccess::Success && Resource && Resource->IsA() ) - { - UMaterialInterface* MaterialInterface = Cast( Resource ); - UMaterial* BaseMaterial = MaterialInterface->GetBaseMaterial(); - if( BaseMaterial && !BaseMaterial->IsUIMaterial() ) - { - ResourceError->SetVisibility( EVisibility::Visible ); - - // Special engine materials cannot change domain. This typically occurs when - // the user creates or assigns a material instance with no parent material. - // In this case, we warn the user rather than offer to change the domain. - if (BaseMaterial->bUsedAsSpecialEngineMaterial) - { - ChangeDomainLink->SetVisibility( EVisibility::Collapsed ); - IsEngineMaterialError->SetVisibility( EVisibility::Visible ); - } - else - { - ChangeDomainLink->SetVisibility( EVisibility::Visible ); - IsEngineMaterialError->SetVisibility( EVisibility::Collapsed ); - } - } - else - { - ResourceError->SetVisibility( EVisibility::Collapsed ); - } - } - else if( ResourceError->GetVisibility() != EVisibility::Collapsed ) - { - ResourceError->SetVisibility( EVisibility::Collapsed ); - } + UpdateResourceError(); } private: @@ -1258,6 +1234,43 @@ private: } } + void UpdateResourceError() + { + UObject* Resource = nullptr; + + if( ResourceObjectProperty->GetValue(Resource) == FPropertyAccess::Success && Resource && Resource->IsA() ) + { + UMaterialInterface* MaterialInterface = Cast( Resource ); + UMaterial* BaseMaterial = MaterialInterface->GetBaseMaterial(); + if( BaseMaterial && !BaseMaterial->IsUIMaterial() ) + { + ResourceError->SetVisibility( EVisibility::Visible ); + + // Special engine materials cannot change domain. This typically occurs when + // the user creates or assigns a material instance with no parent material. + // In this case, we warn the user rather than offer to change the domain. + if (BaseMaterial->bUsedAsSpecialEngineMaterial) + { + ChangeDomainLink->SetVisibility( EVisibility::Collapsed ); + IsEngineMaterialError->SetVisibility( EVisibility::Visible ); + } + else + { + ChangeDomainLink->SetVisibility( EVisibility::Visible ); + IsEngineMaterialError->SetVisibility( EVisibility::Collapsed ); + } + } + else + { + ResourceError->SetVisibility( EVisibility::Collapsed ); + } + } + else if( ResourceError->GetVisibility() != EVisibility::Collapsed ) + { + ResourceError->SetVisibility( EVisibility::Collapsed ); + } + } + private: TSharedPtr ResourceObjectProperty; TSharedPtr ImageSizeProperty; diff --git a/Engine/Source/Editor/DetailCustomizations/Private/SoftObjectPathCustomization.cpp b/Engine/Source/Editor/DetailCustomizations/Private/SoftObjectPathCustomization.cpp index f0b3589a4942..90620375d106 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/SoftObjectPathCustomization.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/SoftObjectPathCustomization.cpp @@ -93,6 +93,9 @@ void FSoftObjectPathCustomization::CustomizeHeader( TSharedRef .OnShouldFilterAsset( AssetFilter ) .AllowClear( bAllowClear ) ]; + + // This avoids making duplicate reset boxes + StructPropertyHandle->MarkResetToDefaultCustomized(); } void FSoftObjectPathCustomization::CustomizeChildren(TSharedRef InStructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) diff --git a/Engine/Source/Editor/DetailCustomizations/Private/SplineComponentDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/SplineComponentDetails.cpp index 99d5a54c5cc0..489a7c420726 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/SplineComponentDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/SplineComponentDetails.cpp @@ -1,6 +1,7 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "SplineComponentDetails.h" +#include "SplineMetadataDetailsFactory.h" #include "Misc/MessageDialog.h" #include "UObject/ObjectMacros.h" #include "UObject/Class.h" @@ -28,6 +29,12 @@ #define LOCTEXT_NAMESPACE "SplineComponentDetails" +USplineMetadataDetailsFactoryBase::USplineMetadataDetailsFactoryBase(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + class FSplinePointDetails : public IDetailCustomNodeBuilder, public TSharedFromThis { public: @@ -185,6 +192,7 @@ private: FSplineComponentVisualizer* SplineVisualizer; UProperty* SplineCurvesProperty; TArray> SplinePointTypes; + TSharedPtr SplineMetaDataDetails; }; FSplinePointDetails::FSplinePointDetails() @@ -272,6 +280,7 @@ void FSplinePointDetails::GenerateChildContent(IDetailChildrenBuilder& ChildrenB .Y(this, &FSplinePointDetails::GetPositionY) .Z(this, &FSplinePointDetails::GetPositionZ) .AllowResponsiveLayout(true) + .AllowSpin(false) .OnXCommitted(this, &FSplinePointDetails::OnSetPosition, 0) .OnYCommitted(this, &FSplinePointDetails::OnSetPosition, 1) .OnZCommitted(this, &FSplinePointDetails::OnSetPosition, 2) @@ -298,6 +307,7 @@ void FSplinePointDetails::GenerateChildContent(IDetailChildrenBuilder& ChildrenB .Y(this, &FSplinePointDetails::GetArriveTangentY) .Z(this, &FSplinePointDetails::GetArriveTangentZ) .AllowResponsiveLayout(true) + .AllowSpin(false) .OnXCommitted(this, &FSplinePointDetails::OnSetArriveTangent, 0) .OnYCommitted(this, &FSplinePointDetails::OnSetArriveTangent, 1) .OnZCommitted(this, &FSplinePointDetails::OnSetArriveTangent, 2) @@ -324,6 +334,7 @@ void FSplinePointDetails::GenerateChildContent(IDetailChildrenBuilder& ChildrenB .Y(this, &FSplinePointDetails::GetLeaveTangentY) .Z(this, &FSplinePointDetails::GetLeaveTangentZ) .AllowResponsiveLayout(true) + .AllowSpin(false) .OnXCommitted(this, &FSplinePointDetails::OnSetLeaveTangent, 0) .OnYCommitted(this, &FSplinePointDetails::OnSetLeaveTangent, 1) .OnZCommitted(this, &FSplinePointDetails::OnSetLeaveTangent, 2) @@ -350,6 +361,7 @@ void FSplinePointDetails::GenerateChildContent(IDetailChildrenBuilder& ChildrenB .Y(this, &FSplinePointDetails::GetRotationPitch) .Z(this, &FSplinePointDetails::GetRotationYaw) .AllowResponsiveLayout(true) + .AllowSpin(false) .OnXCommitted(this, &FSplinePointDetails::OnSetRotation, 0) .OnYCommitted(this, &FSplinePointDetails::OnSetRotation, 1) .OnZCommitted(this, &FSplinePointDetails::OnSetRotation, 2) @@ -375,6 +387,7 @@ void FSplinePointDetails::GenerateChildContent(IDetailChildrenBuilder& ChildrenB .X(this, &FSplinePointDetails::GetScaleX) .Y(this, &FSplinePointDetails::GetScaleY) .Z(this, &FSplinePointDetails::GetScaleZ) + .AllowSpin(false) .AllowResponsiveLayout(true) .OnXCommitted(this, &FSplinePointDetails::OnSetScale, 0) .OnYCommitted(this, &FSplinePointDetails::OnSetScale, 1) @@ -407,6 +420,32 @@ void FSplinePointDetails::GenerateChildContent(IDetailChildrenBuilder& ChildrenB .Text(this, &FSplinePointDetails::GetPointType) ] ]; + + TArray> SelectedObjects; + ChildrenBuilder.GetParentCategory().GetParentLayout().GetObjectsBeingCustomized(SelectedObjects); + if (SelectedObjects.Num() == 1) + { + if (USplineComponent* SelectedSplineComp = Cast(SelectedObjects[0])) + { + if (SelectedSplineComp->GetSplinePointsMetadata()) + { + for (TObjectIterator ClassIterator; ClassIterator; ++ClassIterator) + { + if (ClassIterator->IsChildOf(USplineMetadataDetailsFactoryBase::StaticClass()) && !ClassIterator->HasAnyClassFlags(CLASS_Abstract | CLASS_Deprecated | CLASS_NewerVersionExists)) + { + USplineMetadataDetailsFactoryBase* Factory = ClassIterator->GetDefaultObject(); + if (Factory->GetMetadataClass() == SelectedSplineComp->GetSplinePointsMetadata()->GetClass()) + { + SplineMetaDataDetails = Factory->Create(); + IDetailGroup& Group = ChildrenBuilder.AddGroup(SplineMetaDataDetails->GetName(), SplineMetaDataDetails->GetDisplayName()); + SplineMetaDataDetails->GenerateChildContent(Group); + break; + } + } + } + } + } + } } void FSplinePointDetails::Tick(float DeltaTime) @@ -418,7 +457,7 @@ void FSplinePointDetails::UpdateValues() { SplineComp = SplineVisualizer->GetEditedSplineComponent(); SelectedKeys = SplineVisualizer->GetSelectedKeys(); - + // Cache values to be shown by the details customization. // An unset optional value represents 'multiple values' (in the case where multiple points are selected). InputKey.Reset(); @@ -442,6 +481,11 @@ void FSplinePointDetails::UpdateValues() PointType.Add(ConvertInterpCurveModeToSplinePointType(SplineComp->GetSplinePointsPosition().Points[Index].InterpMode)); } } + + if (SplineMetaDataDetails) + { + SplineMetaDataDetails->Update(SplineComp, SelectedKeys); + } } FName FSplinePointDetails::GetName() const diff --git a/Engine/Source/Editor/DetailCustomizations/Public/SplineMetadataDetailsFactory.h b/Engine/Source/Editor/DetailCustomizations/Public/SplineMetadataDetailsFactory.h new file mode 100644 index 000000000000..0a96d3b79f92 --- /dev/null +++ b/Engine/Source/Editor/DetailCustomizations/Public/SplineMetadataDetailsFactory.h @@ -0,0 +1,30 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "SplineMetadataDetailsFactory.generated.h" + +class USplineComponent; +class IDetailGroup; + +class ISplineMetadataDetails +{ +public: + virtual FName GetName() const = 0; + virtual FText GetDisplayName() const = 0; + virtual void Update(USplineComponent* InSplineComponent, const TSet& InSelectedKeys) = 0; + virtual void GenerateChildContent(IDetailGroup& InGroup) = 0; +}; + +UCLASS(Abstract) +class DETAILCUSTOMIZATIONS_API USplineMetadataDetailsFactoryBase : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + virtual TSharedPtr Create() PURE_VIRTUAL(USplineMetadataDetailsFactoryBase::Create, return nullptr;); + virtual UClass* GetMetadataClass() const PURE_VIRTUAL(USplineMetadataDetailsFactoryBase::GetMetadataClass, return nullptr;); +}; + diff --git a/Engine/Source/Editor/DistCurveEditor/Private/CurveEditorViewportClient.cpp b/Engine/Source/Editor/DistCurveEditor/Private/CurveEditorViewportClient.cpp index 1fec6d02a2b9..222e35e3e1b7 100644 --- a/Engine/Source/Editor/DistCurveEditor/Private/CurveEditorViewportClient.cpp +++ b/Engine/Source/Editor/DistCurveEditor/Private/CurveEditorViewportClient.cpp @@ -49,7 +49,6 @@ FCurveEditorViewportClient::FCurveEditorViewportClient(TWeakPtrDraw(); - } - - if (ViewportClient.IsValid()) - { - ViewportClient->SetNeedsRedraw(false); - } -} - void SCurveEditorViewport::RefreshViewport() { Viewport->Invalidate(); + Viewport->InvalidateDisplay(); } void SCurveEditorViewport::SetVerticalScrollBarPosition(float Position) diff --git a/Engine/Source/Editor/DistCurveEditor/Private/SCurveEditorViewport.h b/Engine/Source/Editor/DistCurveEditor/Private/SCurveEditorViewport.h index fb73825da465..e5ec3c728bac 100644 --- a/Engine/Source/Editor/DistCurveEditor/Private/SCurveEditorViewport.h +++ b/Engine/Source/Editor/DistCurveEditor/Private/SCurveEditorViewport.h @@ -35,9 +35,6 @@ public: /** SCompoundWidget interface */ void Construct(const FArguments& InArgs); - /** Draws the viewport */ - void DrawViewport(); - /** Refreshes the viewport */ void RefreshViewport(); diff --git a/Engine/Source/Editor/DistCurveEditor/Private/SDistributionCurveEditor.cpp b/Engine/Source/Editor/DistCurveEditor/Private/SDistributionCurveEditor.cpp index dd751a7481c4..5e9204f82f65 100644 --- a/Engine/Source/Editor/DistCurveEditor/Private/SDistributionCurveEditor.cpp +++ b/Engine/Source/Editor/DistCurveEditor/Private/SDistributionCurveEditor.cpp @@ -63,21 +63,7 @@ void SDistributionCurveEditor::Construct(const FArguments& InArgs) void SDistributionCurveEditor::RefreshViewport() { Viewport->GetViewport()->Invalidate(); -} - -bool SDistributionCurveEditor::GetNeedsRedraw() -{ - if (Viewport->GetViewportClient().IsValid()) - { - return Viewport->GetViewportClient()->GetNeedsRedraw(); - } - - return false; -} - -void SDistributionCurveEditor::DrawViewport() -{ - Viewport->DrawViewport(); + Viewport->GetViewport()->InvalidateDisplay(); } void SDistributionCurveEditor::CurveChanged() diff --git a/Engine/Source/Editor/DistCurveEditor/Private/SDistributionCurveEditor.h b/Engine/Source/Editor/DistCurveEditor/Private/SDistributionCurveEditor.h index 1380312e6268..e253c35f47e6 100644 --- a/Engine/Source/Editor/DistCurveEditor/Private/SDistributionCurveEditor.h +++ b/Engine/Source/Editor/DistCurveEditor/Private/SDistributionCurveEditor.h @@ -61,8 +61,6 @@ public: void Construct(const FArguments& InArgs); /** IDistributionCurveEditor interface */ - virtual void DrawViewport() override; - virtual bool GetNeedsRedraw() override; virtual void RefreshViewport() override; virtual void CurveChanged() override; virtual void SetCurveVisible(const UObject* InCurve, bool bShow) override; diff --git a/Engine/Source/Editor/DistCurveEditor/Public/IDistCurveEditor.h b/Engine/Source/Editor/DistCurveEditor/Public/IDistCurveEditor.h index baf5b5ccb480..6e89d764ed05 100644 --- a/Engine/Source/Editor/DistCurveEditor/Public/IDistCurveEditor.h +++ b/Engine/Source/Editor/DistCurveEditor/Public/IDistCurveEditor.h @@ -38,13 +38,7 @@ public: class DISTCURVEEDITOR_API IDistributionCurveEditor : public SCompoundWidget { public: - /** Draws the viewport */ - virtual void DrawViewport() = 0; - - /** Returns whether draw is needed */ - virtual bool GetNeedsRedraw() = 0; - - /** Refreshes the viewport by invalidating */ + /** Refreshes the viewport */ virtual void RefreshViewport() = 0; /** Clears selected keys and updates the viewport */ diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/EditorInteractiveToolsFramework.Build.cs b/Engine/Source/Editor/EditorInteractiveToolsFramework/EditorInteractiveToolsFramework.Build.cs new file mode 100644 index 000000000000..735e90172283 --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/EditorInteractiveToolsFramework.Build.cs @@ -0,0 +1,62 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class EditorInteractiveToolsFramework : ModuleRules +{ + public EditorInteractiveToolsFramework(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + //"ContentBrowser" + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core" + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "InputCore", + "UnrealEd", + "ContentBrowser", + "LevelEditor", + "ApplicationCore", + "EditorStyle", + "InteractiveToolsFramework" + + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EdModeInteractiveToolsContext.cpp b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EdModeInteractiveToolsContext.cpp new file mode 100644 index 000000000000..0e2d7bfb646d --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EdModeInteractiveToolsContext.cpp @@ -0,0 +1,788 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "EdModeInteractiveToolsContext.h" +#include "Editor.h" +#include "EditorViewportClient.h" +#include "EditorModeManager.h" +#include "LevelEditorViewport.h" // for GCurrentLevelEditingViewportClient +#include "Engine/Selection.h" +#include "Misc/ITransaction.h" +#include "ScopedTransaction.h" +#include "Materials/Material.h" +#include "Engine/StaticMesh.h" +#include "Components/StaticMeshComponent.h" + +#include "EditorToolAssetAPI.h" +#include "EditorComponentSourceFactory.h" + +//#include "PhysicsEngine/BodySetup.h" +//#include "Interfaces/Interface_CollisionDataProvider.h" + +//#define ENABLE_DEBUG_PRINTING + + + +class FEdModeToolsContextQueriesImpl : public IToolsContextQueriesAPI +{ +public: + UEdModeInteractiveToolsContext* ToolsContext; + FEdMode* EditorMode; + + FViewCameraState CachedViewState; + + FEdModeToolsContextQueriesImpl(UEdModeInteractiveToolsContext* Context, FEdMode* EditorModeIn) + { + ToolsContext = Context; + EditorMode = EditorModeIn; + } + + void CacheCurrentViewState(FEditorViewportClient* ViewportClient) + { + FViewportCameraTransform ViewTransform = ViewportClient->GetViewTransform(); + CachedViewState.Position = ViewTransform.GetLocation(); + CachedViewState.Orientation = ViewTransform.GetRotation().Quaternion(); + CachedViewState.bIsOrthographic = ViewportClient->IsOrtho(); + CachedViewState.bIsVR = false; + } + + virtual void GetCurrentSelectionState(FToolBuilderState& StateOut) const override + { + StateOut.ToolManager = ToolsContext->ToolManager; + StateOut.GizmoManager = ToolsContext->GizmoManager; + StateOut.World = EditorMode->GetWorld(); + StateOut.SelectedActors = EditorMode->GetModeManager()->GetSelectedActors(); + StateOut.SelectedComponents = EditorMode->GetModeManager()->GetSelectedComponents(); + StateOut.SourceBuilder = ToolsContext->GetComponentSourceFactory(); + } + + + virtual void GetCurrentViewState(FViewCameraState& StateOut) const override + { + StateOut = CachedViewState; + } + + + virtual bool ExecuteSceneSnapQuery(const FSceneSnapQueryRequest& Request, TArray& Results) const override + { + if (Request.RequestType != ESceneSnapQueryType::Position) + { + return false; // not supported yet + } + + int FoundResultCount = 0; + + // + // Run a snap query by casting ray into the world. + // If a hit is found, we look up what triangle was hit, and then test its vertices and edges + // + + // cast ray into world + FVector RayStart = CachedViewState.Position; + FVector RayDirection = Request.Position - RayStart; RayDirection.Normalize(); + FVector RayEnd = RayStart + 9999999 * RayDirection; + FCollisionObjectQueryParams ObjectQueryParams(FCollisionObjectQueryParams::AllObjects); + FCollisionQueryParams QueryParams = FCollisionQueryParams::DefaultQueryParam; + QueryParams.bTraceComplex = true; + QueryParams.bReturnFaceIndex = true; + FHitResult HitResult; + bool bHitWorld = EditorMode->GetWorld()->LineTraceSingleByObjectType(HitResult, RayStart, RayEnd, ObjectQueryParams, QueryParams); + if (bHitWorld && HitResult.FaceIndex >= 0) + { + float VisualAngle = OpeningAngleDeg(Request.Position, HitResult.ImpactPoint, RayStart); + //UE_LOG(LogTemp, Warning, TEXT("[HIT] visualangle %f faceindex %d"), VisualAngle, HitResult.FaceIndex); + if (VisualAngle < Request.VisualAngleThresholdDegrees) + { + UPrimitiveComponent* Component = HitResult.Component.Get(); + if (Cast(Component) != nullptr) + { + // HitResult.FaceIndex is apparently an index into the TriMeshCollisionData, not sure how + // to directly access it. Calling GetPhysicsTriMeshData is expensive! + //UBodySetup* BodySetup = Cast(Component)->GetBodySetup(); + //UObject* CDPObj = BodySetup->GetOuter(); + //IInterface_CollisionDataProvider* CDP = Cast(CDPObj); + //FTriMeshCollisionData TriMesh; + //CDP->GetPhysicsTriMeshData(&TriMesh, true); + //FTriIndices Triangle = TriMesh.Indices[HitResult.FaceIndex]; + //FVector Positions[3] = { TriMesh.Vertices[Triangle.v0], TriMesh.Vertices[Triangle.v1], TriMesh.Vertices[Triangle.v2] }; + + // physics collision data is created from StaticMesh RenderData + // so use HitResult.FaceIndex to extract triangle from the LOD0 mesh + // (note: this may be incorrect if there are multiple sections...in that case I think we have to + // first find section whose accumulated index range would contain .FaceIndexX) + UStaticMesh* StaticMesh = Cast(Component)->GetStaticMesh(); + FStaticMeshLODResources& LOD = StaticMesh->RenderData->LODResources[0]; + FIndexArrayView Indices = LOD.IndexBuffer.GetArrayView(); + int32 TriIdx = 3 * HitResult.FaceIndex; + FVector Positions[3]; + Positions[0] = LOD.VertexBuffers.PositionVertexBuffer.VertexPosition(Indices[TriIdx]); + Positions[1] = LOD.VertexBuffers.PositionVertexBuffer.VertexPosition(Indices[TriIdx+1]); + Positions[2] = LOD.VertexBuffers.PositionVertexBuffer.VertexPosition(Indices[TriIdx+2]); + + // transform to world space + FTransform ComponentTransform = Component->GetComponentTransform(); + Positions[0] = ComponentTransform.TransformPosition(Positions[0]); + Positions[1] = ComponentTransform.TransformPosition(Positions[1]); + Positions[2] = ComponentTransform.TransformPosition(Positions[2]); + + FSceneSnapQueryResult SnapResult; + SnapResult.TriVertices[0] = Positions[0]; + SnapResult.TriVertices[1] = Positions[1]; + SnapResult.TriVertices[2] = Positions[2]; + + // try snapping to vertices + float SmallestAngle = Request.VisualAngleThresholdDegrees; + if ( (Request.TargetTypes & ESceneSnapQueryTargetType::MeshVertex) != ESceneSnapQueryTargetType::None) + { + for (int j = 0; j < 3; ++j) + { + VisualAngle = OpeningAngleDeg(Request.Position, Positions[j], RayStart); + if (VisualAngle < SmallestAngle) + { + SmallestAngle = VisualAngle; + SnapResult.Position = Positions[j]; + SnapResult.TargetType = ESceneSnapQueryTargetType::MeshVertex; + SnapResult.TriSnapIndex = j; + } + } + } + + // try snapping to nearest points on edges + if ( ((Request.TargetTypes & ESceneSnapQueryTargetType::MeshEdge) != ESceneSnapQueryTargetType::None) && + (SnapResult.TargetType != ESceneSnapQueryTargetType::MeshVertex) ) + { + for (int j = 0; j < 3; ++j) + { + FVector EdgeNearestPt = NearestSegmentPt(Positions[j], Positions[(j+1)%3], Request.Position); + VisualAngle = OpeningAngleDeg(Request.Position, EdgeNearestPt, RayStart); + if (VisualAngle < SmallestAngle ) + { + SmallestAngle = VisualAngle; + SnapResult.Position = EdgeNearestPt; + SnapResult.TargetType = ESceneSnapQueryTargetType::MeshEdge; + SnapResult.TriSnapIndex = j; + } + } + } + + // if we found a valid snap, return it + if (SmallestAngle < Request.VisualAngleThresholdDegrees) + { + SnapResult.TargetActor = HitResult.Actor.Get(); + SnapResult.TargetComponent = HitResult.Component.Get(); + Results.Add(SnapResult); + FoundResultCount++; + } + } + } + + } + + return (FoundResultCount > 0); + } + + + //@ todo this are mirrored from GeometryProcessing, which is still experimental...replace w/ direct calls once GP component is standardized + static float OpeningAngleDeg(FVector A, FVector B, const FVector& P) + { + A -= P; + A.Normalize(); + B -= P; + B.Normalize(); + float Dot = FMath::Clamp(FVector::DotProduct(A,B), -1.0f, 1.0f); + return acos(Dot) * (180.0f / 3.141592653589f); + } + static FVector NearestSegmentPt(FVector A, FVector B, const FVector& P) + { + FVector Direction = (B - A); + float Length = Direction.Size(); + Direction /= Length; + float t = FVector::DotProduct( (P - A), Direction); + if (t >= Length) + { + return B; + } + if (t <= 0) + { + return A; + } + return A + t * Direction; + } + + + + virtual UMaterialInterface* GetStandardMaterial(EStandardToolContextMaterials MaterialType) const + { + if (MaterialType == EStandardToolContextMaterials::VertexColorMaterial) + { + return ToolsContext->StandardVertexColorMaterial; + } + check(false); + return nullptr; + } + +}; + + + + +class FEdModeToolsContextTransactionImpl : public IToolsContextTransactionsAPI +{ +public: + UEdModeInteractiveToolsContext* ToolsContext; + FEdMode* EditorMode; + + FEdModeToolsContextTransactionImpl(UEdModeInteractiveToolsContext* Context, FEdMode* EditorModeIn) + { + ToolsContext = Context; + EditorMode = EditorModeIn; + } + + + virtual void PostMessage(const TCHAR* Message, EToolMessageLevel Level) override + { + UE_LOG(LogTemp, Warning, TEXT("[ToolsContext] %s"), Message); + } + + + virtual void PostInvalidation() override + { + ToolsContext->PostInvalidation(); + } + + virtual void BeginUndoTransaction(const FText& Description) override + { + GEditor->BeginTransaction(Description); + } + + virtual void EndUndoTransaction() override + { + GEditor->EndTransaction(); + } + + virtual void AppendChange(UObject* TargetObject, TUniquePtr Change, const FText& Description) override + { + FScopedTransaction Transaction(Description); + check(GUndo != nullptr); + GUndo->StoreUndo(TargetObject, MoveTemp(Change)); + // end transaction + } + + + virtual bool RequestSelectionChange(const FSelectedOjectsChangeList& SelectionChange) override + { + checkf(SelectionChange.Components.Num() == 0, TEXT("FEdModeToolsContextTransactionImpl::RequestSelectionChange - Component selection not supported yet")); + + if (SelectionChange.ModificationType == ESelectedObjectsModificationType::Clear) + { + GEditor->SelectNone(true, true, false); + return true; + } + + if (SelectionChange.ModificationType == ESelectedObjectsModificationType::Replace ) + { + GEditor->SelectNone(false, true, false); + } + + bool bAdd = (SelectionChange.ModificationType != ESelectedObjectsModificationType::Remove); + int NumActors = SelectionChange.Actors.Num(); + for (int k = 0; k < NumActors; ++k) + { + GEditor->SelectActor(SelectionChange.Actors[k], bAdd, false, true, false); + } + + GEditor->NoteSelectionChange(true); + return true; + } + +}; + + + + + +UEdModeInteractiveToolsContext::UEdModeInteractiveToolsContext() +{ + EditorMode = nullptr; + QueriesAPI = nullptr; + TransactionAPI = nullptr; + AssetAPI = nullptr; + SourceFactory = nullptr; +} + + + +void UEdModeInteractiveToolsContext::Initialize(IToolsContextQueriesAPI* QueriesAPIIn, IToolsContextTransactionsAPI* TransactionsAPIIn) +{ + UInteractiveToolsContext::Initialize(QueriesAPIIn, TransactionsAPIIn); + + BeginPIEDelegateHandle = FEditorDelegates::BeginPIE.AddLambda([this](bool bSimulating) + { + TerminateActiveToolsOnPIEStart(); + }); + PreSaveWorldDelegateHandle = FEditorDelegates::PreSaveWorld.AddLambda([this](uint32 SaveFlags, UWorld* World) + { + TerminateActiveToolsOnSaveWorld(); + }); + + bInvalidationPending = false; +} + +void UEdModeInteractiveToolsContext::Shutdown() +{ + FEditorDelegates::BeginPIE.Remove(BeginPIEDelegateHandle); + FEditorDelegates::PreSaveWorld.Remove(PreSaveWorldDelegateHandle); + + UInteractiveToolsContext::Shutdown(); +} + + + + +void UEdModeInteractiveToolsContext::InitializeContextFromEdMode(FEdMode* EditorModeIn) +{ + this->EditorMode = EditorModeIn; + + this->TransactionAPI = new FEdModeToolsContextTransactionImpl(this, EditorModeIn); + this->QueriesAPI = new FEdModeToolsContextQueriesImpl(this, EditorModeIn); + this->AssetAPI = new FEditorToolAssetAPI(); + this->SourceFactory = new FEditorComponentSourceFactory(); + + Initialize(QueriesAPI, TransactionAPI); + + // enable auto invalidation in Editor, because invalidating for all hover and capture events is unpleasant + this->InputRouter->bAutoInvalidateOnHover = true; + this->InputRouter->bAutoInvalidateOnCapture = true; + + + // set up standard materials + StandardVertexColorMaterial = LoadObject(nullptr, TEXT("/Game/Materials/VertexColor")); +} + + +void UEdModeInteractiveToolsContext::ShutdownContext() +{ + Shutdown(); + + if (QueriesAPI != nullptr) + { + delete QueriesAPI; + QueriesAPI = nullptr; + } + + if (TransactionAPI != nullptr) + { + delete TransactionAPI; + TransactionAPI = nullptr; + } + + if (AssetAPI != nullptr) + { + delete AssetAPI; + AssetAPI = nullptr; + } + + if (SourceFactory != nullptr) + { + delete SourceFactory; + SourceFactory = nullptr; + } + + this->EditorMode = nullptr; +} + + + + +void UEdModeInteractiveToolsContext::TerminateActiveToolsOnPIEStart() +{ + if (ToolManager->HasActiveTool(EToolSide::Left)) + { + EToolShutdownType ShutdownType = ToolManager->CanAcceptActiveTool(EToolSide::Left) ? + EToolShutdownType::Accept : EToolShutdownType::Cancel; + ToolManager->DeactivateTool(EToolSide::Left, ShutdownType); + } + if (ToolManager->HasActiveTool(EToolSide::Right)) + { + EToolShutdownType ShutdownType = ToolManager->CanAcceptActiveTool(EToolSide::Right) ? + EToolShutdownType::Accept : EToolShutdownType::Cancel; + ToolManager->DeactivateTool(EToolSide::Right, ShutdownType); + } +} +void UEdModeInteractiveToolsContext::TerminateActiveToolsOnSaveWorld() +{ + if (ToolManager->HasActiveTool(EToolSide::Left)) + { + EToolShutdownType ShutdownType = ToolManager->CanAcceptActiveTool(EToolSide::Left) ? + EToolShutdownType::Accept : EToolShutdownType::Cancel; + ToolManager->DeactivateTool(EToolSide::Left, ShutdownType); + } + if (ToolManager->HasActiveTool(EToolSide::Right)) + { + EToolShutdownType ShutdownType = ToolManager->CanAcceptActiveTool(EToolSide::Right) ? + EToolShutdownType::Accept : EToolShutdownType::Cancel; + ToolManager->DeactivateTool(EToolSide::Right, ShutdownType); + } +} + + + + + +void UEdModeInteractiveToolsContext::PostInvalidation() +{ + bInvalidationPending = true; +} + + +void UEdModeInteractiveToolsContext::Tick(FEditorViewportClient* ViewportClient, float DeltaTime) +{ + ToolManager->Tick(DeltaTime); + GizmoManager->Tick(DeltaTime); + + if (bInvalidationPending) + { + ViewportClient->Invalidate(); + bInvalidationPending = false; + } + + // save this view + // Check against GCurrentLevelEditingViewportClient is temporary and should be removed in future. + // Current issue is that this ::Tick() is called *per viewport*, so once for each view in a 4-up view. + if (ViewportClient == GCurrentLevelEditingViewportClient) + { + ((FEdModeToolsContextQueriesImpl*)this->QueriesAPI)->CacheCurrentViewState(ViewportClient); + } +} + + + +class TempRenderContext : public IToolsContextRenderAPI +{ +public: + FPrimitiveDrawInterface* PDI; + + virtual FPrimitiveDrawInterface* GetPrimitiveDrawInterface() override + { + return PDI; + } +}; + + +void UEdModeInteractiveToolsContext::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) +{ + TempRenderContext RenderContext; + RenderContext.PDI = PDI; + ToolManager->Render(&RenderContext); + GizmoManager->Render(&RenderContext); +} + + + + +bool UEdModeInteractiveToolsContext::InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) +{ +#ifdef ENABLE_DEBUG_PRINTING + if (Event == IE_Pressed) { UE_LOG(LogTemp, Warning, TEXT("PRESSED EVENT")); } + else if (Event == IE_Released) { UE_LOG(LogTemp, Warning, TEXT("RELEASED EVENT")); } + else if (Event == IE_Repeat) { UE_LOG(LogTemp, Warning, TEXT("REPEAT EVENT")); } + else if (Event == IE_Axis) { UE_LOG(LogTemp, Warning, TEXT("AXIS EVENT")); } + else if (Event == IE_DoubleClick) { UE_LOG(LogTemp, Warning, TEXT("DOUBLECLICK EVENT")); } +#endif + + bool bHandled = false; + + + // escape key cancels current tool + if (Key == EKeys::Escape && Event == IE_Released ) + { + if (ToolManager->HasAnyActiveTool()) + { + if (ToolManager->HasActiveTool(EToolSide::Mouse)) + { + ToolManager->DeactivateTool(EToolSide::Mouse, EToolShutdownType::Cancel); + } + return true; + } + } + + // enter key accepts current tool, or ends tool if it does not have accept state + if (Key == EKeys::Enter && Event == IE_Released && ToolManager->HasAnyActiveTool()) + { + if (ToolManager->HasActiveTool(EToolSide::Mouse)) + { + if (ToolManager->GetActiveTool(EToolSide::Mouse)->HasAccept()) + { + if (ToolManager->CanAcceptActiveTool(EToolSide::Mouse)) + { + ToolManager->DeactivateTool(EToolSide::Mouse, EToolShutdownType::Accept); + return true; + } + } + else + { + ToolManager->DeactivateTool(EToolSide::Mouse, EToolShutdownType::Completed); + return true; + } + } + } + + // if alt is down we do not process mouse event + if (ViewportClient->IsAltPressed()) + { + return false; + } + + if (Event == IE_Pressed || Event == IE_Released) + { + if (Key.IsMouseButton()) + { + bool bIsLeftMouse = (Key == EKeys::LeftMouseButton); + bool bIsMiddleMouse = (Key == EKeys::MiddleMouseButton); + bool bIsRightMouse = (Key == EKeys::RightMouseButton); + + if (bIsLeftMouse || bIsMiddleMouse || bIsRightMouse) + { + + // early-out here if we are going to do camera manipulation + if (ViewportClient->IsAltPressed()) + { + return bHandled; + } + + FInputDeviceState InputState = CurrentMouseState; + InputState.InputDevice = EInputDevices::Mouse; + InputState.SetModifierKeyStates( + ViewportClient->IsShiftPressed(), ViewportClient->IsAltPressed(), + ViewportClient->IsCtrlPressed(), ViewportClient->IsCmdPressed()); + + if (bIsLeftMouse) + { + InputState.Mouse.Left.SetStates( + (Event == IE_Pressed), (Event == IE_Pressed), (Event == IE_Released)); + CurrentMouseState.Mouse.Left.bDown = (Event == IE_Pressed); + } + else if (bIsMiddleMouse) + { + InputState.Mouse.Middle.SetStates( + (Event == IE_Pressed), (Event == IE_Pressed), (Event == IE_Released)); + CurrentMouseState.Mouse.Middle.bDown = (Event == IE_Pressed); + } + else + { + InputState.Mouse.Right.SetStates( + (Event == IE_Pressed), (Event == IE_Pressed), (Event == IE_Released)); + CurrentMouseState.Mouse.Right.bDown = (Event == IE_Pressed); + } + + InputRouter->PostInputEvent(InputState); + + if (InputRouter->HasActiveMouseCapture()) + { + // what is this about? MeshPaintMode has it... + ViewportClient->bLockFlightCamera = true; + bHandled = true; // indicate that we handled this event, + // which will disable camera movement/etc ? + } + else + { + //ViewportClient->bLockFlightCamera = false; + } + + } + } + else if (Key.IsGamepadKey()) + { + // not supported yet + } + else if (Key.IsTouch()) + { + // not supported yet + } + else if (Key.IsFloatAxis() || Key.IsVectorAxis()) + { + // not supported yet + } + else // is this definitely a keyboard key? + { + FInputDeviceState InputState; + InputState.InputDevice = EInputDevices::Keyboard; + InputState.SetModifierKeyStates( + ViewportClient->IsShiftPressed(), ViewportClient->IsAltPressed(), + ViewportClient->IsCtrlPressed(), ViewportClient->IsCmdPressed()); + InputState.Keyboard.ActiveKey.Button = Key; + bool bPressed = (Event == IE_Pressed); + InputState.Keyboard.ActiveKey.SetStates(bPressed, bPressed, !bPressed); + InputRouter->PostInputEvent(InputState); + } + + } + + return bHandled; +} + + + + +bool UEdModeInteractiveToolsContext::MouseEnter(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) +{ +#ifdef ENABLE_DEBUG_PRINTING + UE_LOG(LogTemp, Warning, TEXT("MOUSE ENTER")); +#endif + + CurrentMouseState.Mouse.Position2D = FVector2D(x, y); + CurrentMouseState.Mouse.WorldRay = GetRayFromMousePos(ViewportClient, Viewport, x, y); + + return false; +} + + +bool UEdModeInteractiveToolsContext::MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) +{ +#ifdef ENABLE_DEBUG_PRINTING + //UE_LOG(LogTemp, Warning, TEXT("MOUSE MOVE")); +#endif + + CurrentMouseState.Mouse.Position2D = FVector2D(x, y); + CurrentMouseState.Mouse.WorldRay = GetRayFromMousePos(ViewportClient, Viewport, x, y); + FInputDeviceState InputState = CurrentMouseState; + InputState.InputDevice = EInputDevices::Mouse; + + InputState.SetModifierKeyStates( + ViewportClient->IsShiftPressed(), ViewportClient->IsAltPressed(), + ViewportClient->IsCtrlPressed(), ViewportClient->IsCmdPressed()); + + if (InputRouter->HasActiveMouseCapture()) + { + // This state occurs if InputBehavior did not release capture on mouse release. + // UMultiClickSequenceInputBehavior does this, eg for multi-click draw-polygon sequences. + // It's not ideal though and maybe would be better done via multiple captures + hover...? + InputRouter->PostInputEvent(InputState); + } + else + { + InputRouter->PostHoverInputEvent(InputState); + } + + return false; +} + + +bool UEdModeInteractiveToolsContext::MouseLeave(FEditorViewportClient* ViewportClient, FViewport* Viewport) +{ +#ifdef ENABLE_DEBUG_PRINTING + UE_LOG(LogTemp, Warning, TEXT("MOUSE LEAVE")); +#endif + + return false; +} + + + +bool UEdModeInteractiveToolsContext::StartTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) +{ + return false; +} + +bool UEdModeInteractiveToolsContext::CapturedMouseMove(FEditorViewportClient* InViewportClient, FViewport* InViewport, int32 InMouseX, int32 InMouseY) +{ +#ifdef ENABLE_DEBUG_PRINTING + //UE_LOG(LogTemp, Warning, TEXT("CAPTURED MOUSE MOVE")); +#endif + + // if alt is down we will not allow client to see this event + if (InViewportClient->IsAltPressed()) + { + return false; + } + + FVector2D OldPosition = CurrentMouseState.Mouse.Position2D; + CurrentMouseState.Mouse.Position2D = FVector2D(InMouseX, InMouseY); + CurrentMouseState.Mouse.WorldRay = GetRayFromMousePos(InViewportClient, InViewport, InMouseX, InMouseY); + + if (InputRouter->HasActiveMouseCapture()) + { + FInputDeviceState InputState = CurrentMouseState; + InputState.InputDevice = EInputDevices::Mouse; + InputState.SetModifierKeyStates( + InViewportClient->IsShiftPressed(), InViewportClient->IsAltPressed(), + InViewportClient->IsCtrlPressed(), InViewportClient->IsCmdPressed()); + InputState.Mouse.Delta2D = CurrentMouseState.Mouse.Position2D - OldPosition; + InputRouter->PostInputEvent(InputState); + return true; + } + + return false; +} + + +bool UEdModeInteractiveToolsContext::EndTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) +{ +#ifdef ENABLE_DEBUG_PRINTING + UE_LOG(LogTemp, Warning, TEXT("END TRACKING")); +#endif + + return true; +} + + + + + + +FRay UEdModeInteractiveToolsContext::GetRayFromMousePos(FEditorViewportClient* ViewportClient, FViewport* Viewport, int MouseX, int MouseY) +{ + FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues( + ViewportClient->Viewport, + ViewportClient->GetScene(), + ViewportClient->EngineShowFlags) + .SetRealtimeUpdate(ViewportClient->IsRealtime())); + FSceneView* View = ViewportClient->CalcSceneView(&ViewFamily); + FViewportCursorLocation MouseViewportRay(View, (FEditorViewportClient*)Viewport->GetClient(), MouseX, MouseY); + + return FRay(MouseViewportRay.GetOrigin(), MouseViewportRay.GetDirection(), true); +} + + + + + + +bool UEdModeInteractiveToolsContext::CanStartTool(const FString& ToolTypeIdentifier) const +{ + return (ToolManager->HasActiveTool(EToolSide::Mouse) == false) && + (ToolManager->CanActivateTool(EToolSide::Mouse, ToolTypeIdentifier) == true); +} +bool UEdModeInteractiveToolsContext::ActiveToolHasAccept() const +{ + return ToolManager->HasActiveTool(EToolSide::Mouse) && + ToolManager->GetActiveTool(EToolSide::Mouse)->HasAccept(); +} +bool UEdModeInteractiveToolsContext::CanAcceptActiveTool() const +{ + return ToolManager->CanAcceptActiveTool(EToolSide::Mouse); +} +bool UEdModeInteractiveToolsContext::CanCancelActiveTool() const +{ + return ToolManager->CanCancelActiveTool(EToolSide::Mouse); +} + +bool UEdModeInteractiveToolsContext::CanCompleteActiveTool() const +{ + return ToolManager->HasActiveTool(EToolSide::Mouse) && CanCancelActiveTool() == false; +} +void UEdModeInteractiveToolsContext::StartTool(const FString& ToolTypeIdentifier) +{ + if (ToolManager->SelectActiveToolType(EToolSide::Mouse, ToolTypeIdentifier) == false) + { + UE_LOG(LogTemp, Warning, TEXT("ToolManager: Unknown Tool Type %s"), *ToolTypeIdentifier); + } + else + { + ToolManager->ActivateTool(EToolSide::Mouse); + } +} +void UEdModeInteractiveToolsContext::EndTool(EToolShutdownType ShutdownType) +{ + ToolManager->DeactivateTool(EToolSide::Mouse, ShutdownType); +} \ No newline at end of file diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorComponentSourceFactory.cpp b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorComponentSourceFactory.cpp new file mode 100644 index 000000000000..5799eb513e56 --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorComponentSourceFactory.cpp @@ -0,0 +1,108 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "EditorComponentSourceFactory.h" + +#include "Engine/StaticMesh.h" +#include "Components/ActorComponent.h" +#include "Components/StaticMeshComponent.h" +#include "PhysicsEngine/BodySetup.h" + + +TUniquePtr FEditorComponentSourceFactory::MakeMeshDescriptionSource(UActorComponent* Component) +{ + UStaticMeshComponent* StaticMeshComp = Cast(Component); + if (StaticMeshComp != nullptr) + { + auto NewSource = new FStaticMeshComponentMeshDescriptionSource(StaticMeshComp, 0); + return TUniquePtr(NewSource); + } + return nullptr; +} + + + + + +FStaticMeshComponentMeshDescriptionSource::FStaticMeshComponentMeshDescriptionSource( + UStaticMeshComponent* ComponentIn, int LODIndex) +{ + this->Component = ComponentIn; + this->LODIndex = LODIndex; +} + + +AActor* FStaticMeshComponentMeshDescriptionSource::GetOwnerActor() const +{ + return Component->GetOwner(); +} + +UActorComponent* FStaticMeshComponentMeshDescriptionSource::GetOwnerComponent() const +{ + return Component; +} + + +void FStaticMeshComponentMeshDescriptionSource::SetOwnerVisibility(bool bVisible) const +{ + Component->SetVisibility(bVisible); +} + +FMeshDescription* FStaticMeshComponentMeshDescriptionSource::GetMeshDescription() const +{ + return Component->GetStaticMesh()->GetMeshDescription(0); +} + +UMaterialInterface* FStaticMeshComponentMeshDescriptionSource::GetMaterial(int32 MaterialIndex) const +{ + return Component->GetMaterial(MaterialIndex); +} + +FTransform FStaticMeshComponentMeshDescriptionSource::GetWorldTransform() const +{ + //return Component->GetOwner()->GetActorTransform(); + return Component->GetComponentTransform(); +} + + + +bool FStaticMeshComponentMeshDescriptionSource::IsReadOnly() const +{ + return false; +} + + +void FStaticMeshComponentMeshDescriptionSource::CommitInPlaceModification(const TFunction& ModifyFunction) +{ + //bool bSaved = Component->Modify(); + //check(bSaved); + UStaticMesh* StaticMesh = Component->GetStaticMesh(); + + // make sure transactional flag is on + StaticMesh->SetFlags(RF_Transactional); + + bool bSavedToTransactionBuffer = StaticMesh->Modify(); + check(bSavedToTransactionBuffer); + FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(LODIndex); + + ModifyFunction(MeshDescription); + + StaticMesh->CommitMeshDescription(LODIndex); + StaticMesh->PostEditChange(); + + // this rebuilds physics, but it doesn't undo! + Component->RecreatePhysicsState(); + + //Component->PostEditChange(); +} + + +bool FStaticMeshComponentMeshDescriptionSource::HitTest(const FRay& WorldRay, FHitResult& OutHit) const +{ + FVector End = WorldRay.PointAt(HALF_WORLD_MAX); + if (Component->LineTraceComponent(OutHit, WorldRay.Origin, End, FCollisionQueryParams(SCENE_QUERY_STAT(HitTest), true))) + { + return true; + } + + return false; +} \ No newline at end of file diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorInteractiveToolsFrameworkModule.cpp b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorInteractiveToolsFrameworkModule.cpp new file mode 100644 index 000000000000..c2e4272e8335 --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorInteractiveToolsFrameworkModule.cpp @@ -0,0 +1,20 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "EditorInteractiveToolsFrameworkModule.h" + +#define LOCTEXT_NAMESPACE "FEditorInteractiveToolsFrameworkModule" + +void FEditorInteractiveToolsFrameworkModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FEditorInteractiveToolsFrameworkModule::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(FEditorInteractiveToolsFrameworkModule, EditorInteractiveToolsFramework) \ No newline at end of file diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorToolAssetAPI.cpp b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorToolAssetAPI.cpp new file mode 100644 index 000000000000..02e3e6e276ed --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorToolAssetAPI.cpp @@ -0,0 +1,93 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "EditorToolAssetAPI.h" +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" + + +#include "AssetRegistryModule.h" +#include "FileHelpers.h" +#include "PackageTools.h" +#include "ObjectTools.h" + +// for content-browser things +#include "ContentBrowserModule.h" +#include "IContentBrowserSingleton.h" + + +FString FEditorToolAssetAPI::GetActiveAssetFolderPath() +{ + check(false); // [RMS] this only works if we have an asset selected! + + IContentBrowserSingleton& ContentBrowser = FModuleManager::LoadModuleChecked("ContentBrowser").Get(); + + //ContentBrowser.CreatePathPicker() + + TArray SelectedAssets; + ContentBrowser.GetSelectedAssets(SelectedAssets); + return SelectedAssets[0].PackagePath.ToString(); +} + + + +FString FEditorToolAssetAPI::MakePackageName(const FString& AssetName, const FString& FolderPath) +{ + return FolderPath + TEXT("/") + AssetName; +} + + +FString FEditorToolAssetAPI::MakeUniqueAssetName(const FString& AssetName, const FString& FolderPath) +{ + FString NewPackageName = MakePackageName(AssetName, FolderPath); + if (FPackageName::DoesPackageExist(NewPackageName) == false) + { + return AssetName; + } + + int Counter = 1; + FString UniqueName = AssetName; + while ( FPackageName::DoesPackageExist(NewPackageName) ) + { + UniqueName = AssetName + FString::Printf(TEXT("_%d"), Counter++); + NewPackageName = MakePackageName(UniqueName, FolderPath); + } + return UniqueName; +} + + +UPackage* FEditorToolAssetAPI::CreateNewPackage(const FString& AssetName, const FString& FolderPath) +{ + FString NewPackageName = MakePackageName(AssetName, FolderPath); + NewPackageName = UPackageTools::SanitizePackageName(NewPackageName); + UPackage* AssetPackage = CreatePackage(nullptr, *NewPackageName); + return AssetPackage; +} + + + +void FEditorToolAssetAPI::InteractiveSaveGeneratedAsset(UObject* Asset, UPackage* AssetPackage) +{ + Asset->MarkPackageDirty(); + + FAssetRegistryModule::AssetCreated(Asset); + + TArray PackagesToSave; + PackagesToSave.Add(AssetPackage); + bool bCheckDirty = true; + bool bPromptToSave = true; + FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, bCheckDirty, bPromptToSave); +} + + +void FEditorToolAssetAPI::AutoSaveGeneratedAsset(UObject* Asset, UPackage* AssetPackage) +{ + Asset->MarkPackageDirty(); + + FAssetRegistryModule::AssetCreated(Asset); + + TArray PackagesToSave; + PackagesToSave.Add(AssetPackage); + bool bCheckDirty = true; + bool bPromptToSave = false; + FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, bCheckDirty, bPromptToSave); +} \ No newline at end of file diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/StandardToolModeCommands.cpp b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/StandardToolModeCommands.cpp new file mode 100644 index 000000000000..29a3978a0e4c --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/StandardToolModeCommands.cpp @@ -0,0 +1,38 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "StandardToolModeCommands.h" +#include "EditorStyleSet.h" + +#define LOCTEXT_NAMESPACE "StandardToolModeCommands" + + +FStandardToolModeCommands::FStandardToolModeCommands() : + TCommands( + "StandardToolCommands", // Context name for fast lookup + NSLOCTEXT("Contexts", "StandardToolCommands", "Standard Tool Commands"), // Localized context name for displaying + NAME_None, // Parent + FEditorStyle::GetStyleSetName() // Icon Style Set + ) +{} + + +void FStandardToolModeCommands::RegisterCommands() +{ + TSharedPtr IncreaseBrushSize; + UI_COMMAND(IncreaseBrushSize, "Increase Brush Size", "Increases the size of the brush", EUserInterfaceActionType::Button, FInputChord(EKeys::RightBracket)); + Commands.Add(EStandardToolModeCommands::IncreaseBrushSize, IncreaseBrushSize); + + TSharedPtr DecreaseBrushSize; + UI_COMMAND(DecreaseBrushSize, "Decrease Brush Size", "Decreases the size of the brush", EUserInterfaceActionType::Button, FInputChord(EKeys::LeftBracket)); + Commands.Add(EStandardToolModeCommands::DecreaseBrushSize, DecreaseBrushSize); +} + + +TSharedPtr FStandardToolModeCommands::FindStandardCommand(EStandardToolModeCommands Command) const +{ + const TSharedPtr* Found = Commands.Find(Command); + checkf(Found != nullptr, TEXT("FStandardToolModeCommands::FindStandardCommand: standard command %d was not found!"), Command); + return (Found != nullptr) ? *Found : nullptr; +} + + diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EdModeInteractiveToolsContext.h b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EdModeInteractiveToolsContext.h new file mode 100644 index 000000000000..82e97b36f626 --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EdModeInteractiveToolsContext.h @@ -0,0 +1,99 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "EdMode.h" +#include "InteractiveToolsContext.h" +#include "EdModeInteractiveToolsContext.generated.h" + +/** + * EdModeInteractiveToolsContext is an extension/adapter of an InteractiveToolsContext which + * allows it to be easily embedded inside an FEdMode. A set of functions are provided which can be + * called from the FEdMode functions of the same name. These will handle the data type + * conversions and forwarding calls necessary to operate the ToolsContext + */ +UCLASS(Transient) +class EDITORINTERACTIVETOOLSFRAMEWORK_API UEdModeInteractiveToolsContext : public UInteractiveToolsContext +{ + GENERATED_BODY() + +public: + UEdModeInteractiveToolsContext(); + + + virtual void InitializeContextFromEdMode(FEdMode* EditorMode); + virtual void ShutdownContext(); + + // default behavior is to accept active tool + virtual void TerminateActiveToolsOnPIEStart(); + + // default behavior is to accept active tool + virtual void TerminateActiveToolsOnSaveWorld(); + + IToolsContextQueriesAPI* GetQueriesAPI() const { return QueriesAPI; } + IToolsContextTransactionsAPI* GetTransactionAPI() const { return TransactionAPI; } + IToolsContextAssetAPI* GetAssetAPI() const { return AssetAPI; } + IComponentSourceFactory* GetComponentSourceFactory() const { return SourceFactory; } + + virtual void PostInvalidation(); + + + // call these from your FEdMode functions of the same name + + virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime); + virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI); + + virtual bool InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event); + + virtual bool MouseEnter(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y); + virtual bool MouseLeave(FEditorViewportClient* ViewportClient, FViewport* Viewport); + virtual bool MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y); + + virtual bool StartTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport); + virtual bool CapturedMouseMove(FEditorViewportClient* InViewportClient, FViewport* InViewport, int32 InMouseX, int32 InMouseY); + virtual bool EndTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport); + + + // + // Utility functions useful for hooking up to UICommand/etc + // + + virtual bool CanStartTool(const FString& ToolTypeIdentifier) const; + virtual bool ActiveToolHasAccept() const; + virtual bool CanAcceptActiveTool() const; + virtual bool CanCancelActiveTool() const; + virtual bool CanCompleteActiveTool() const; + virtual void StartTool(const FString& ToolTypeIdentifier); + virtual void EndTool(EToolShutdownType ShutdownType); + + +protected: + // we hide these + virtual void Initialize(IToolsContextQueriesAPI* QueriesAPI, IToolsContextTransactionsAPI* TransactionsAPI) override; + virtual void Shutdown() override; + + +public: + UPROPERTY() + UMaterialInterface* StandardVertexColorMaterial; + +protected: + FEdMode* EditorMode; + + FDelegateHandle BeginPIEDelegateHandle; + FDelegateHandle PreSaveWorldDelegateHandle; + + IToolsContextQueriesAPI* QueriesAPI; + IToolsContextTransactionsAPI* TransactionAPI; + IToolsContextAssetAPI* AssetAPI; + IComponentSourceFactory* SourceFactory; + + bool bInvalidationPending; + + /** Input event instance used to keep track of various button states, etc, that we cannot directly query on-demand */ + FInputDeviceState CurrentMouseState; + + static FRay GetRayFromMousePos(FEditorViewportClient* ViewportClient, FViewport* Viewport, int MouseX, int MouseY); + +}; diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorComponentSourceFactory.h b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorComponentSourceFactory.h new file mode 100644 index 000000000000..06951f9d2094 --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorComponentSourceFactory.h @@ -0,0 +1,54 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "ComponentSourceInterfaces.h" + + + + +/** + * Component Source Factory for use in the Editor (ie inside FEdMode, etc) + */ +class EDITORINTERACTIVETOOLSFRAMEWORK_API FEditorComponentSourceFactory : public IComponentSourceFactory +{ +public: + virtual ~FEditorComponentSourceFactory() {} + + virtual TUniquePtr MakeMeshDescriptionSource(UActorComponent* Component); +}; + + + + + +class UStaticMeshComponent; + +/** + * This MeshDescriptionSource provides a specific LOD from a StaticMeshComponent + */ +class EDITORINTERACTIVETOOLSFRAMEWORK_API FStaticMeshComponentMeshDescriptionSource : public IMeshDescriptionSource +{ +public: + UStaticMeshComponent* Component; + int LODIndex; + + FStaticMeshComponentMeshDescriptionSource( + UStaticMeshComponent* ComponentIn, + int LODIndex = 0); + virtual ~FStaticMeshComponentMeshDescriptionSource() {} + + virtual AActor* GetOwnerActor() const override; + virtual UActorComponent* GetOwnerComponent() const override; + virtual FMeshDescription* GetMeshDescription() const override; + virtual UMaterialInterface* GetMaterial(int32 MaterialIndex) const override; + virtual FTransform GetWorldTransform() const override; + virtual bool HitTest(const FRay& WorldRay, FHitResult& OutHit) const override; + + virtual bool IsReadOnly() const override; + + virtual void SetOwnerVisibility(bool bVisible) const override; + virtual void CommitInPlaceModification(const TFunction& ModifyFunction) override; +}; + diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorInteractiveToolsFrameworkModule.h b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorInteractiveToolsFrameworkModule.h new file mode 100644 index 000000000000..6f6428eea95e --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorInteractiveToolsFrameworkModule.h @@ -0,0 +1,15 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FEditorInteractiveToolsFrameworkModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorToolAssetAPI.h b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorToolAssetAPI.h new file mode 100644 index 000000000000..3dc61a51893b --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorToolAssetAPI.h @@ -0,0 +1,28 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "ToolContextInterfaces.h" + +/** + * Implementation of ToolsContext Asset management API that is suitable for use + * inside the Editor (eg inside an FEdMode) + */ +class EDITORINTERACTIVETOOLSFRAMEWORK_API FEditorToolAssetAPI : public IToolsContextAssetAPI +{ +public: + virtual FString GetActiveAssetFolderPath() override; + + virtual FString MakePackageName(const FString& AssetName, const FString& FolderPath) override; + + virtual FString MakeUniqueAssetName(const FString& AssetName, const FString& FolderPath) override; + + // FolderPath should neither start with, nor end with, path separators + // Generated path will be /Game/FolderPath/AssetName + virtual UPackage* CreateNewPackage(const FString& AssetName, const FString& FolderPath) override; + + virtual void InteractiveSaveGeneratedAsset(UObject* Asset, UPackage* AssetPackage) override; + + virtual void AutoSaveGeneratedAsset(UObject* Asset, UPackage* AssetPackage) override; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/InteractiveToolsCommands.h b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/InteractiveToolsCommands.h new file mode 100644 index 000000000000..5ca48d70770e --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/InteractiveToolsCommands.h @@ -0,0 +1,302 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Commands/Commands.h" +#include "Framework/Commands/UICommandList.h" +#include "InteractiveTool.h" +#include "StandardToolModeCommands.h" + + + +/** + * TInteractiveToolCommands is a base class that handles connecting up Tool Actions + * (ie the FInteractiveToolAction provided by a UInteractiveTool) to the UnrealEditor + * Command system, which allows for remappable hotkeys, etc + * + * Usage is as follows: + * - in your EdMode Module, create a subclass-instance of this, say FMyToolCommands and call FMyToolCommands::Register() in your ::StartupModule() function + * - subclass must implement ::GetToolDefaultObjectList(), here you just add GetMutableDefault to the input list for all your Tools + * - subclass must implement ::GetNamespaceString(), likely you can just return TEXT(LOCTEXT_NAMESPACE) + * - add a member TSharedPtr UICommandList; to your EdMode impl + * - when you start a new Tool, call FMyToolCommands::Get().BindCommandsForCurrentTool(UICommandList, NewTool) + * - when you end a Tool, call FMyToolCommands::Get().UnbindActiveCommands() + * - in your EdModeImpl::InputKey override, call UICommandList->ProcessCommandBindings() + */ +template +class TInteractiveToolCommands : public TCommands +{ + +public: + /** + * Initialize commands. Collects possible Actions from the available UInteractiveTools + * (provided by GetToolDefaultObjectList) and then registers a FUICommandInfo for each. + * This needs to be called in ::StartupModule for your EdMode Module + */ + virtual void RegisterCommands() override; + + /** + * Bind any of the registered UICommands to the given Tool, if they are compatible. + */ + virtual void BindCommandsForCurrentTool(TSharedPtr UICommandList, UInteractiveTool* Tool) const; + + /** + * Unbind all of the currently-registered commands + */ + virtual void UnbindActiveCommands(TSharedPtr UICommandList) const; + + + // + // Interface that subclasses need to implement + // + + + /** + * RegisterCommands() needs actual UInteractiveTool instances for all the Tools that want to + * provide Actions which will be connected to hotkeys. We can do this based on the CDO's for + * each tool, returned by GetMutableDefault(). The Tool CDOs are + * not owned by a ToolManager and we will only call .GetActionSet() on them. + */ + virtual void GetToolDefaultObjectList(TArray& ToolCDOs) = 0; + + /** + * The Namespace used to identify this Command set + */ + virtual const TCHAR * GetNamespaceString() = 0; + + +protected: + + /** Forwarding constructor */ + TInteractiveToolCommands(const FName InContextName, const FText& InContextDesc, const FName InContextParent, const FName InStyleSetName) + : TCommands(InContextName, InContextDesc, InContextParent, InStyleSetName) + { + } + + /** + * Query FStandardToolModeCommands to find an existing command/hotkey for this standard tool action + */ + virtual TSharedPtr FindStandardCommand(EStandardToolActions ToolActionID); + + /** + * List of pairs of known Tool Actions and their associated UICommands. + * This list is used at bind-time to connect up the already-registered UICommands to the active Tool + */ + struct FToolActionCommand + { + FInteractiveToolAction ToolAction; + TSharedPtr UICommand; + }; + TArray ActionCommands; + + + // + // Support for sharing common commands between Tools where the commands + // are not shared across Tool Modes (and hence not in FStandardToolModeCommands) + // + + /** List FToolActionCommands for standard Tool Actions */ + TMap SharedActionCommands; + + /** + * Find or Create a UICommand for a standard Tool Action, that will be shared across Tools + */ + bool FindOrCreateSharedCommand(EStandardToolActions ActionID, FToolActionCommand& FoundOut); + + /** + * Create a suitable FInteractiveToolAction for the standard Tool Action, ie with suitable + * command name and description strings and hotkeys. This info will be used to create the shared UICommand. + */ + virtual bool IntializeStandardToolAction(EStandardToolActions ActionID, FInteractiveToolAction& ToolActionOut); + + /** + * Utility function that registeres a Tool Aciton as a UICommand + */ + void RegisterUIToolCommand(const FInteractiveToolAction& ToolAction, TSharedPtr& UICommandInfo); +}; + + + + + + +template +void TInteractiveToolCommands::RegisterCommands() +{ + // make sure standard commands are registered + FStandardToolModeCommands::Register(); + + // get list of all tools used by command set + TArray AllToolsCDOs; + GetToolDefaultObjectList(AllToolsCDOs); + + // collect all the actions in these tools + TArray ToolActions; + for (UInteractiveTool* Tool : AllToolsCDOs) + { + Tool->GetActionSet()->CollectActions(ToolActions); + } + + // register all the commands in all the actions + int NumActions = ToolActions.Num(); + ActionCommands.SetNum(NumActions); + for (int k = 0; k < NumActions; ++k) + { + const FInteractiveToolAction& ToolAction = ToolActions[k]; + ActionCommands[k].ToolAction = ToolAction; + + bool bRegistered = false; + if ((int32)ToolAction.ActionID < (int32)EStandardToolActions::BaseClientDefinedActionID) + { + TSharedPtr FoundStandard = FindStandardCommand((EStandardToolActions)ToolAction.ActionID); + if (FoundStandard.IsValid()) + { + ActionCommands[k].UICommand = FoundStandard; + bRegistered = true; + } + else + { + FToolActionCommand StandardActionCommand; + bool bFoundRemap = FindOrCreateSharedCommand((EStandardToolActions)ToolAction.ActionID, StandardActionCommand); + if (bFoundRemap) + { + ActionCommands[k].UICommand = StandardActionCommand.UICommand; + bRegistered = true; + } + } + } + + if (bRegistered == false) + { + RegisterUIToolCommand(ActionCommands[k].ToolAction, ActionCommands[k].UICommand); + } + } +} + + +template +void TInteractiveToolCommands::BindCommandsForCurrentTool(TSharedPtr UICommandList, UInteractiveTool* Tool) const +{ + UClass* ToolClassType = Tool->GetClass(); + + int NumActionCommands = ActionCommands.Num(); + for (int32 k = 0; k < NumActionCommands; ++k) + { + const FToolActionCommand& ActionCommand = ActionCommands[k]; + if (ActionCommand.ToolAction.ClassType == ToolClassType) + { + UICommandList->MapAction( + ActionCommand.UICommand, + FExecuteAction::CreateUObject(Tool, &UInteractiveTool::ExecuteAction, ActionCommand.ToolAction.ActionID)); + //FCanExecuteAction::CreateRaw(this, &FEdModeFoliage::CurrentToolUsesBrush)); + } + } +} + + +template +void TInteractiveToolCommands::UnbindActiveCommands(TSharedPtr UICommandList) const +{ + // @todo would be more efficient if we kept track of which commands were mapped. + // However currently this function must be const because TCommands::Get() returns a const + for (const FToolActionCommand& ActionCommand : ActionCommands) + { + if (UICommandList->IsActionMapped(ActionCommand.UICommand)) + { + UICommandList->UnmapAction(ActionCommand.UICommand); + } + } +} + + + + + + +// +// Support for re-using known commands from FStandardToolModeCommands +// + +template +TSharedPtr TInteractiveToolCommands::FindStandardCommand(EStandardToolActions ToolActionID) +{ + const FStandardToolModeCommands& StdCommands = FStandardToolModeCommands::Get(); + switch (ToolActionID) + { + case EStandardToolActions::IncreaseBrushSize: + return StdCommands.FindStandardCommand(EStandardToolModeCommands::IncreaseBrushSize); + case EStandardToolActions::DecreaseBrushSize: + return StdCommands.FindStandardCommand(EStandardToolModeCommands::DecreaseBrushSize); + default: + return nullptr; + } +} + + + + + + +// +// Support for sharing UI commands between multiple Tools, where the +// shared command is not at the shared-between-modes level +// + +template +bool TInteractiveToolCommands::FindOrCreateSharedCommand(EStandardToolActions ActionID, FToolActionCommand& FoundOut) +{ + if (SharedActionCommands.Contains(ActionID)) + { + FoundOut = SharedActionCommands[ActionID]; + return true; + } + + FInteractiveToolAction NewAction; + bool bIsKnownAction = IntializeStandardToolAction(ActionID, NewAction); + + if (bIsKnownAction) + { + check(NewAction.ActionID == (int32)ActionID); + FToolActionCommand NewActionCommand; + NewActionCommand.ToolAction = NewAction; + RegisterUIToolCommand(NewActionCommand.ToolAction, NewActionCommand.UICommand); + FoundOut = SharedActionCommands.Add(ActionID, NewActionCommand); + return true; + } + return false; +} + + +template +bool TInteractiveToolCommands::IntializeStandardToolAction(EStandardToolActions ActionID, FInteractiveToolAction& ToolActionOut) +{ + // base class does not have any standard Tool actions. Subclasses can override + // this function, populating ToolActionOut and returning true, if the ActionID should be shared between multiple Tools + + return false; +} + + + + +template +void TInteractiveToolCommands::RegisterUIToolCommand(const FInteractiveToolAction& ToolAction, TSharedPtr& UICommandInfo) +{ + FString TooltipString = ToolAction.ActionName + FString("_ToolTip"); + FString DotString = FString(".") + ToolAction.ActionName; + + UI_COMMAND_Function(this, + UICommandInfo, + GetNamespaceString(), + *ToolAction.ActionName, + *TooltipString, + TCHAR_TO_ANSI(*DotString), + *ToolAction.ShortName, *ToolAction.Description, + EUserInterfaceActionType::Button, + FInputChord(ToolAction.DefaultModifiers, ToolAction.DefaultKey)); +} + + + + diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/StandardToolModeCommands.h b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/StandardToolModeCommands.h new file mode 100644 index 000000000000..d129f3a39bb0 --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/StandardToolModeCommands.h @@ -0,0 +1,54 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Commands/Commands.h" +#include "Framework/Commands/UICommandInfo.h" + + +/** + * Integer identifiers for "Standard" Tool commands, that many EditorModes / AssetEditors / etc may share. + * This allows a single hotkey binding to be used across many contexts. + */ +enum class EStandardToolModeCommands +{ + IncreaseBrushSize = 100, + DecreaseBrushSize = 101 +}; + + + +/** + * FStandardToolModeCommands provides standard commands for Tools. This allows + * for "sharing" common hotkey bindings between multiple EditorModes + * + * The set of standard commands is defined by EStandardToolModeCommands. + * You must call FStandardToolModeCommands::Register() on module startup to register these commands + * (note that they may already be registered by another module, but you should call this to be safe). + * + * Then, when you want to Bind a standard command in your FUICommandList, + * call FStandardToolModeCommands::Get().FindStandardCommand() to get the registered UICommandInfo. + * + */ +class EDITORINTERACTIVETOOLSFRAMEWORK_API FStandardToolModeCommands : public TCommands +{ +public: + FStandardToolModeCommands(); + + /** + * Registers the set of standard commands. Call on module startup. + */ + virtual void RegisterCommands() override; + + /** + * Look up the UICommandInfo for a standard command + */ + TSharedPtr FindStandardCommand(EStandardToolModeCommands Command) const; + +protected: + TMap> Commands; + +}; + + diff --git a/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp b/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp index c13d382639ff..79fa2a634516 100644 --- a/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp +++ b/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp @@ -179,6 +179,12 @@ void FSlateEditorStyle::FStyle::Initialize() SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate")); SetupGeneralStyles(); + SetupLevelGeneralStyles(); + SetupWorldBrowserStyles(); + SetupSequencerStyles(); + SetupViewportStyles(); + SetupNotificationBarStyles(); + SetupMenuBarStyles(); SetupGeneralIcons(); SetupWindowStyles(); SetupPropertyEditorStyles(); @@ -1378,64 +1384,7 @@ void FSlateEditorStyle::FStyle::SetupGeneralStyles() Set( "LayerBrowserButton.LabelFont", DEFAULT_FONT( "Regular", 8 ) ); } - // Levels General - { - Set( "Level.VisibleIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_visible_16px", Icon16x16 ) ); - Set( "Level.VisibleHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_visible_hi_16px", Icon16x16 ) ); - Set( "Level.NotVisibleIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_invisible_16px", Icon16x16 ) ); - Set( "Level.NotVisibleHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_invisible_hi_16px", Icon16x16 ) ); - Set( "Level.LightingScenarioIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_LightingScenario_16px", Icon16x16 ) ); - Set( "Level.LightingScenarioNotIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_LightingScenarioNot_16px", Icon16x16 ) ); - Set( "Level.LockedIcon16x", new IMAGE_BRUSH( "Icons/icon_locked_16px", Icon16x16 ) ); - Set( "Level.LockedHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_locked_highlight_16px", Icon16x16 ) ); - Set( "Level.UnlockedIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_unlocked_16px", Icon16x16 ) ); - Set( "Level.UnlockedHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_unlocked_hi_16px", Icon16x16 ) ); - Set( "Level.ReadOnlyLockedIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_LockedReadOnly_16px", Icon16x16 ) ); - Set( "Level.ReadOnlyLockedHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_LockedReadOnly_hi_16px", Icon16x16 ) ); - Set( "Level.SaveIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_Save_16px", Icon16x16 ) ); - Set( "Level.SaveHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_Save_hi_16px", Icon16x16 ) ); - Set( "Level.SaveModifiedIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_SaveModified_16px", Icon16x16 ) ); - Set( "Level.SaveModifiedHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_SaveModified_hi_16px", Icon16x16 ) ); - Set( "Level.SaveDisabledIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_SaveDisabled_16px", Icon16x16 ) ); - Set( "Level.SaveDisabledHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_SaveDisabled_hi_16px", Icon16x16 ) ); - Set( "Level.ScriptIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_Blueprint_16px", Icon16x16 ) ); - Set( "Level.ScriptHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_Blueprint_hi_16px", Icon16x16 ) ); - Set( "Level.EmptyIcon16x", new IMAGE_BRUSH( "Icons/Empty_16x", Icon16x16 ) ); - Set( "Level.ColorIcon40x", new IMAGE_BRUSH( "Icons/icon_levels_back_16px", Icon16x16 ) ); - } - - // World Browser - { - Set( "WorldBrowser.AddLayer", new IMAGE_BRUSH( "Icons/icon_levels_addlayer_16x", Icon16x16 ) ); - Set( "WorldBrowser.SimulationViewPositon", new IMAGE_BRUSH( "Icons/icon_levels_simulationviewpos_16x", Icon16x16 ) ); - Set( "WorldBrowser.MouseLocation", new IMAGE_BRUSH( "Icons/icon_levels_mouselocation_16x", Icon16x16 ) ); - Set( "WorldBrowser.MarqueeRectSize", new IMAGE_BRUSH( "Icons/icon_levels_marqueerectsize_16x", Icon16x16 ) ); - Set( "WorldBrowser.WorldSize", new IMAGE_BRUSH( "Icons/icon_levels_worldsize_16x", Icon16x16 ) ); - Set( "WorldBrowser.WorldOrigin", new IMAGE_BRUSH( "Icons/icon_levels_worldorigin_16x", Icon16x16 ) ); - Set( "WorldBrowser.DirectionXPositive", new IMAGE_BRUSH( "Icons/icon_PanRight", Icon16x16 ) ); - Set( "WorldBrowser.DirectionXNegative", new IMAGE_BRUSH( "Icons/icon_PanLeft", Icon16x16 ) ); - Set( "WorldBrowser.DirectionYPositive", new IMAGE_BRUSH( "Icons/icon_PanUp", Icon16x16 ) ); - Set( "WorldBrowser.DirectionYNegative", new IMAGE_BRUSH( "Icons/icon_PanDown", Icon16x16 ) ); - Set( "WorldBrowser.LevelStreamingAlwaysLoaded", new FSlateNoResource() ); - Set( "WorldBrowser.LevelStreamingBlueprint", new IMAGE_BRUSH( "Icons/icon_levels_blueprinttype_7x16", Icon7x16 ) ); - Set( "WorldBrowser.LevelsMenuBrush", new IMAGE_BRUSH( "Icons/icon_levels_levelsmenu_40x", Icon25x25 ) ); - Set( "WorldBrowser.HierarchyButtonBrush", new IMAGE_BRUSH( "Icons/icon_levels_hierarchybutton_16x", Icon16x16 ) ); - Set( "WorldBrowser.DetailsButtonBrush", new IMAGE_BRUSH( "Icons/icon_levels_detailsbutton_40x", Icon16x16 ) ); - Set( "WorldBrowser.CompositionButtonBrush", new IMAGE_BRUSH( "Icons/icon_levels_compositionbutton_16x", Icon16x16 ) ); - - Set( "WorldBrowser.FolderClosed", new IMAGE_BRUSH( "Icons/FolderClosed", Icon16x16 ) ); - Set( "WorldBrowser.FolderOpen", new IMAGE_BRUSH( "Icons/FolderOpen", Icon16x16 ) ); - Set( "WorldBrowser.NewFolderIcon", new IMAGE_BRUSH( "Icons/icon_AddFolder_16x", Icon16x16 ) ); - - Set( "WorldBrowser.StatusBarText", FTextBlockStyle(NormalText) - .SetFont( DEFAULT_FONT( "BoldCondensed", 12 ) ) - .SetColorAndOpacity( FLinearColor(0.9, 0.9f, 0.9f, 0.5f) ) - .SetShadowOffset( FVector2D::ZeroVector ) - ); - - Set( "WorldBrowser.LabelFont", DEFAULT_FONT( "Regular", 9 ) ); - Set( "WorldBrowser.LabelFontBold", DEFAULT_FONT( "Bold", 10 ) ); - } + // Scene Outliner { @@ -1779,294 +1728,6 @@ void FSlateEditorStyle::FStyle::SetupGeneralStyles() Set( "BuildAndSubmit.SmallFont", DEFAULT_FONT( "Regular", 7 ) ); } - // Sequencer - if (IncludeEditorSpecificStyles()) - { - Set( "Sequencer.IconKeyAuto", new IMAGE_BRUSH( "Sequencer/IconKeyAuto", Icon12x12 ) ); - Set( "Sequencer.IconKeyBreak", new IMAGE_BRUSH( "Sequencer/IconKeyBreak", Icon12x12 ) ); - Set( "Sequencer.IconKeyConstant", new IMAGE_BRUSH( "Sequencer/IconKeyConstant", Icon12x12 ) ); - Set( "Sequencer.IconKeyLinear", new IMAGE_BRUSH( "Sequencer/IconKeyLinear", Icon12x12 ) ); - Set( "Sequencer.IconKeyUser", new IMAGE_BRUSH( "Sequencer/IconKeyUser", Icon12x12 ) ); - - Set( "Sequencer.KeyCircle", new IMAGE_BRUSH( "Sequencer/KeyCircle", Icon12x12 ) ); - Set( "Sequencer.KeyDiamond", new IMAGE_BRUSH( "Sequencer/KeyDiamond", Icon12x12 ) ); - Set( "Sequencer.KeyDiamondBorder", new IMAGE_BRUSH( "Sequencer/KeyDiamondBorder", Icon12x12 ) ); - Set( "Sequencer.KeySquare", new IMAGE_BRUSH( "Sequencer/KeySquare", Icon12x12 ) ); - Set( "Sequencer.KeyTriangle", new IMAGE_BRUSH( "Sequencer/KeyTriangle", Icon12x12 ) ); - Set( "Sequencer.KeyLeft", new IMAGE_BRUSH( "Sequencer/KeyLeft", Icon12x12 ) ); - Set( "Sequencer.KeyRight", new IMAGE_BRUSH( "Sequencer/KeyRight", Icon12x12 ) ); - Set( "Sequencer.PartialKey", new IMAGE_BRUSH( "Sequencer/PartialKey", FVector2D(11.f, 11.f) ) ); - Set( "Sequencer.Star", new IMAGE_BRUSH( "Sequencer/Star", Icon12x12 ) ); - Set( "Sequencer.Empty", new IMAGE_BRUSH( "Sequencer/Empty", Icon12x12 ) ); - Set( "Sequencer.TangentHandle", new IMAGE_BRUSH( "Sequencer/TangentHandle", FVector2D(7, 7) ) ); - Set( "Sequencer.GenericDivider", new IMAGE_BRUSH( "Sequencer/GenericDivider", FVector2D(2.f, 2.f), FLinearColor::White, ESlateBrushTileType::Vertical ) ); - - Set("Sequencer.Timeline.ScrubHandleDown", new BOX_BRUSH("Sequencer/ScrubHandleDown", FMargin(6.f / 13.f, 5 / 12.f, 6 / 13.f, 8 / 12.f))); - Set("Sequencer.Timeline.ScrubHandleUp", new BOX_BRUSH("Sequencer/ScrubHandleUp", FMargin(6.f / 13.f, 8 / 12.f, 6 / 13.f, 5 / 12.f))); - Set( "Sequencer.Timeline.ScrubFill", new BOX_BRUSH( "Sequencer/ScrubFill", FMargin( 2.f/4.f, 0.f ) ) ); - Set( "Sequencer.Timeline.FrameBlockScrubHandleDown", new BOX_BRUSH( "Sequencer/ScrubHandleDown", FMargin( 6.f/13.f, 5/12.f, 6/13.f, 8/12.f ) ) ); - Set( "Sequencer.Timeline.FrameBlockScrubHandleUp", new BOX_BRUSH( "Sequencer/ScrubHandleUp", FMargin( 6.f/13.f, 8/12.f, 6/13.f, 5/12.f ) ) ); - Set( "Sequencer.Timeline.VanillaScrubHandleDown", new BOX_BRUSH( "Sequencer/ScrubHandleDown_Clamped", FMargin( 6.f/13.f, 3.f/12.f, 6.f/13.f, 7.f/12.f ) ) ); - Set( "Sequencer.Timeline.VanillaScrubHandleUp", new BOX_BRUSH( "Sequencer/ScrubHandleUp_Clamped", FMargin( 6.f/13.f, 8/12.f, 6/13.f, 5/12.f ) ) ); - Set( "Sequencer.Timeline.ScrubHandleWhole", new BOX_BRUSH( "Sequencer/ScrubHandleWhole", FMargin( 6.f/13.f, 10/24.f, 6/13.f, 10/24.f ) ) ); - Set( "Sequencer.Timeline.RangeHandleLeft", new BOX_BRUSH( "Sequencer/GenericGripLeft", FMargin(5.f/16.f) ) ); - Set( "Sequencer.Timeline.RangeHandleRight", new BOX_BRUSH( "Sequencer/GenericGripRight", FMargin(5.f/16.f) ) ); - Set( "Sequencer.Timeline.RangeHandle", new BOX_BRUSH( "Sequencer/GenericSectionBackground", FMargin(5.f/16.f) ) ); - Set( "Sequencer.Timeline.NotifyAlignmentMarker", new IMAGE_BRUSH( "Sequencer/NotifyAlignmentMarker", FVector2D(10,19) ) ); - Set( "Sequencer.Timeline.PlayRange_Top_L", new BOX_BRUSH( "Sequencer/PlayRange_Top_L", FMargin(1.f, 0.5f, 0.f, 0.5f) ) ); - Set( "Sequencer.Timeline.PlayRange_Top_R", new BOX_BRUSH( "Sequencer/PlayRange_Top_R", FMargin(0.f, 0.5f, 1.f, 0.5f) ) ); - Set( "Sequencer.Timeline.PlayRange_L", new BOX_BRUSH( "Sequencer/PlayRange_L", FMargin(1.f, 0.5f, 0.f, 0.5f) ) ); - Set( "Sequencer.Timeline.PlayRange_R", new BOX_BRUSH( "Sequencer/PlayRange_R", FMargin(0.f, 0.5f, 1.f, 0.5f) ) ); - Set( "Sequencer.Timeline.PlayRange_Bottom_L", new BOX_BRUSH( "Sequencer/PlayRange_Bottom_L", FMargin(1.f, 0.5f, 0.f, 0.5f) ) ); - Set( "Sequencer.Timeline.PlayRange_Bottom_R", new BOX_BRUSH( "Sequencer/PlayRange_Bottom_R", FMargin(0.f, 0.5f, 1.f, 0.5f) ) ); - - Set( "Sequencer.Timeline.SubSequenceRangeHashL", new BORDER_BRUSH( "Sequencer/SubSequenceRangeHashL", FMargin(1.f, 0.f, 0.f, 0.f) ) ); - Set( "Sequencer.Timeline.SubSequenceRangeHashR", new BORDER_BRUSH( "Sequencer/SubSequenceRangeHashR", FMargin(1.f, 0.f, 0.f, 0.f) ) ); - Set( "Sequencer.Timeline.EaseInOut", new IMAGE_BRUSH( "Sequencer/EaseInOut", FVector2D(128, 128) ) ); - Set( "Sequencer.InterpLine", new BOX_BRUSH( "Sequencer/InterpLine", FMargin(5.f/7.f, 0.f, 0.f, 0.f) ) ); - - Set( "Sequencer.Transport.JumpToPreviousKey", FButtonStyle() - .SetNormal ( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Previous_Frame_OFF", Icon24x24 ) ) - .SetPressed( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Previous_Frame", Icon24x24 ) ) - .SetHovered( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Previous_Frame_OFF", Icon24x24 ) )); - Set( "Sequencer.Transport.JumpToNextKey", FButtonStyle() - .SetNormal ( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Next_Frame_24x_OFF", Icon24x24 ) ) - .SetPressed( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Next_Frame_24x", Icon24x24 ) ) - .SetHovered( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Next_Frame_24x_OFF", Icon24x24 ) ) ); - Set( "Sequencer.Transport.SetPlayStart", FButtonStyle() - .SetNormal ( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Bracket_In_16x24_OFF", FVector2D(16,24) ) ) - .SetPressed( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Bracket_In_16x24", FVector2D(16,24) ) ) - .SetHovered( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Bracket_In_16x24_OFF", FVector2D(16,24) ) ) ); - Set( "Sequencer.Transport.SetPlayEnd", FButtonStyle() - .SetNormal ( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Bracket_Out_16x24_OFF", FVector2D(16,24) ) ) - .SetPressed( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Bracket_Out_16x24", FVector2D(16,24) ) ) - .SetHovered( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Bracket_Out_16x24_OFF", FVector2D(16,24) ) ) ); - - Set( "Sequencer.Transport.CloseButton", FButtonStyle() - .SetNormal ( IMAGE_BRUSH( "/Docking/CloseApp_Normal", Icon16x16 ) ) - .SetPressed( IMAGE_BRUSH( "/Docking/CloseApp_Pressed", Icon16x16 ) ) - .SetHovered( IMAGE_BRUSH( "/Docking/CloseApp_Hovered", Icon16x16 ))); - - Set( "Sequencer.NotificationImage_AddedPlayMovieSceneEvent", new IMAGE_BRUSH( "Old/Checkbox_checked", Icon16x16 ) ); - - Set( "Sequencer.Save", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Save_48x", Icon48x48) ); - Set( "Sequencer.Save.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Save_48x", Icon24x24) ); - Set( "Sequencer.SaveAsterisk", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_SaveAsterisk_48x", Icon48x48) ); - Set( "Sequencer.SaveAsterisk.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_SaveAsterisk_48x", Icon24x24) ); - Set( "Sequencer.SaveAs", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_SaveAs_48x", Icon48x48 ) ); - Set( "Sequencer.SaveAs.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_SaveAs_48x", Icon24x24 ) ); - Set( "Sequencer.DiscardChanges", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Revert_24x", Icon48x48 ) ); - Set( "Sequencer.DiscardChanges.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Revert_24x", Icon24x24 ) ); - Set( "Sequencer.RestoreAnimatedState", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_RestoreAnimatedState_24x", Icon48x48 ) ); - Set( "Sequencer.RestoreAnimatedState.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_RestoreAnimatedState_24x", Icon24x24 ) ); - Set( "Sequencer.GenericGripLeft", new BOX_BRUSH( "Sequencer/GenericGripLeft", FMargin(5.f/16.f) ) ); - Set( "Sequencer.GenericGripRight", new BOX_BRUSH( "Sequencer/GenericGripRight", FMargin(5.f/16.f) ) ); - Set( "Sequencer.SectionArea.Background", new FSlateColorBrush( FColor::White ) ); - - Set( "Sequencer.Section.Background", new BORDER_BRUSH( TEXT("Sequencer/SectionBackground"), FMargin(4.f/16.f) ) ); - Set( "Sequencer.Section.BackgroundTint", new BOX_BRUSH( TEXT("Sequencer/SectionBackgroundTint"), FMargin(4/16.f) ) ); - Set( "Sequencer.Section.SelectedSectionOverlay", new IMAGE_BRUSH( TEXT("Sequencer/SelectedSectionOverlay"), Icon16x16, FLinearColor::White, ESlateBrushTileType::Both ) ); - Set( "Sequencer.Section.SelectedTrackTint", new BOX_BRUSH( TEXT("Sequencer/SelectedTrackTint"), FMargin(0.f, 0.5f) ) ); - Set( "Sequencer.Section.SelectionBorder", new BORDER_BRUSH( TEXT("Sequencer/SectionHighlight"), FMargin(7.f/16.f) ) ); - Set( "Sequencer.Section.LockedBorder", new BORDER_BRUSH( TEXT("Sequencer/SectionLocked"), FMargin(7.f/16.f) ) ); - Set( "Sequencer.Section.SelectedSectionOverlay", new IMAGE_BRUSH( TEXT("Sequencer/SelectedSectionOverlay"), Icon16x16, FLinearColor::White, ESlateBrushTileType::Both ) ); - Set( "Sequencer.Section.FilmBorder", new IMAGE_BRUSH( TEXT("Sequencer/SectionFilmBorder"), FVector2D(10, 7), FLinearColor::White, ESlateBrushTileType::Horizontal ) ); - Set( "Sequencer.Section.GripLeft", new BOX_BRUSH( "Sequencer/SectionGripLeft", FMargin(5.f/16.f) ) ); - Set( "Sequencer.Section.GripRight", new BOX_BRUSH( "Sequencer/SectionGripRight", FMargin(5.f/16.f) ) ); - Set( "Sequencer.Section.EasingHandle", new IMAGE_BRUSH( "Sequencer/EasingHandle", FVector2D(10.f,10.f) ) ); - - Set( "Sequencer.Section.PreRoll", new BORDER_BRUSH( TEXT("Sequencer/PreRoll"), FMargin(0.f, .5f, 0.f, .5f) ) ); - - Set( "Sequencer.Section.PinCusion", new IMAGE_BRUSH( TEXT("Sequencer/PinCusion"), Icon16x16, FLinearColor::White, ESlateBrushTileType::Both ) ); - Set( "Sequencer.Section.OverlapBorder", new BORDER_BRUSH( TEXT("Sequencer/OverlapBorder"), FMargin(1.f/4.f, 0.f) ) ); - Set( "Sequencer.Section.StripeOverlay", new BOX_BRUSH( "Sequencer/SectionStripeOverlay", FMargin(0.f, .5f) ) ); - Set( "Sequencer.Section.BackgroundText", DEFAULT_FONT( "Bold", 24 ) ); - Set( "Sequencer.Section.EmptySpace", new BOX_BRUSH( TEXT("Sequencer/EmptySpace"), FMargin(0.f, 7.f/14.f) ) ); - - Set( "Sequencer.AnimationOutliner.ColorStrip", FButtonStyle() - .SetNormal(FSlateNoResource()) - .SetHovered(FSlateNoResource()) - .SetPressed(FSlateNoResource()) - .SetNormalPadding(FMargin(0,0,0,0)) - .SetPressedPadding(FMargin(0,0,0,0)) - ); - - Set( "Sequencer.AnimationOutliner.TopLevelBorder_Expanded", new BOX_BRUSH( "Sequencer/TopLevelNodeBorder_Expanded", FMargin(4.0f/16.0f) ) ); - Set( "Sequencer.AnimationOutliner.TopLevelBorder_Collapsed", new BOX_BRUSH( "Sequencer/TopLevelNodeBorder_Collapsed", FMargin(4.0f/16.0f) ) ); - Set( "Sequencer.AnimationOutliner.DefaultBorder", new FSlateColorBrush( FLinearColor::White ) ); - Set( "Sequencer.AnimationOutliner.TransparentBorder", new FSlateColorBrush( FLinearColor::Transparent ) ); - Set( "Sequencer.AnimationOutliner.BoldFont", DEFAULT_FONT( "Bold", 11 ) ); - Set( "Sequencer.AnimationOutliner.RegularFont", DEFAULT_FONT( "Regular", 9 ) ); - Set( "Sequencer.ShotFilter", new IMAGE_BRUSH( "Sequencer/FilteredArea", FVector2D(74,74), FLinearColor::White, ESlateBrushTileType::Both ) ); - Set( "Sequencer.KeyMark", new IMAGE_BRUSH("Sequencer/KeyMark", FVector2D(3,21), FLinearColor::White, ESlateBrushTileType::NoTile ) ); - Set( "Sequencer.SetAutoKey", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Auto_Key_24x", Icon48x48 ) ); - Set( "Sequencer.SetAutoKey.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Auto_Key_24x", Icon24x24 ) ); - Set( "Sequencer.SetAutoTrack", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Auto_Track_24x", Icon48x48 ) ); - Set( "Sequencer.SetAutoTrack.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Auto_Track_24x", Icon24x24 ) ); - Set( "Sequencer.SetAutoChangeAll", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Auto_Key_All_24x", Icon48x48 ) ); - Set( "Sequencer.SetAutoChangeAll.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Auto_Key_All_24x", Icon24x24 ) ); - Set( "Sequencer.SetAutoChangeNone", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Disable_Auto_Key_24x", Icon48x48)); - Set( "Sequencer.SetAutoChangeNone.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Disable_Auto_Key_24x", Icon24x24 ) ); - Set( "Sequencer.AllowAllEdits", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Allow_All_Edits_24x", Icon48x48 ) ); - Set( "Sequencer.AllowAllEdits.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Allow_All_Edits_24x", Icon24x24 ) ); - Set( "Sequencer.AllowSequencerEditsOnly", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Allow_Sequencer_Edits_Only_24x", Icon48x48 ) ); - Set( "Sequencer.AllowSequencerEditsOnly.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Allow_Sequencer_Edits_Only_24x", Icon24x24 ) ); - Set( "Sequencer.AllowLevelEditsOnly", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Allow_Level_Edits_Only_24x", Icon48x48 ) ); - Set( "Sequencer.AllowLevelEditsOnly.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Allow_Level_Edits_Only_24x", Icon24x24 ) ); - Set( "Sequencer.SetKeyAll", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Key_All_24x", Icon48x48 ) ); - Set( "Sequencer.SetKeyAll.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Key_All_24x", Icon24x24 ) ); - Set( "Sequencer.SetKeyGroup", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Key_Group_24x", Icon48x48)); - Set( "Sequencer.SetKeyGroup.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Key_Group_24x", Icon24x24)); - Set( "Sequencer.SetKeyChanged", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Key_Part_24x", Icon48x48 ) ); - Set( "Sequencer.SetKeyChanged.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Key_Part_24x", Icon24x24 ) ); - Set( "Sequencer.ToggleIsSnapEnabled", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Snap_24x", Icon48x48 ) ); - Set( "Sequencer.ToggleIsSnapEnabled.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Snap_24x", Icon24x24 ) ); - Set( "Sequencer.ToggleShowCurveEditor", new IMAGE_BRUSH("GenericCurveEditor/Icons/GenericCurveEditor_48x", Icon48x48) ); - Set( "Sequencer.ToggleShowCurveEditor.Small", new IMAGE_BRUSH("GenericCurveEditor/Icons/GenericCurveEditor_48x", Icon24x24) ); - Set( "Sequencer.ToggleAutoScroll", new IMAGE_BRUSH( "Icons/icon_Sequencer_ToggleAutoScroll_40x", Icon48x48 ) ); - Set( "Sequencer.ToggleAutoScroll.Small", new IMAGE_BRUSH( "Icons/icon_Sequencer_ToggleAutoScroll_16x", Icon16x16 ) ); - Set( "Sequencer.MoveTool.Small", new IMAGE_BRUSH( "Icons/SequencerIcons/icon_Sequencer_Move_24x", Icon16x16 ) ); - Set( "Sequencer.MarqueeTool.Small", new IMAGE_BRUSH( "Icons/SequencerIcons/icon_Sequencer_Marquee_24x", Icon16x16 ) ); - Set( "Sequencer.RenderMovie.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Create_Movie_24x", Icon24x24 ) ); - Set( "Sequencer.CreateCamera.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Create_Camera_24x", Icon24x24 ) ); - Set( "Sequencer.FindInContentBrowser.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Find_In_Content_Browser_24x", Icon24x24 ) ); - Set( "Sequencer.LockCamera", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Look_Thru_24x", Icon16x16 ) ); - Set( "Sequencer.UnlockCamera", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Look_Thru_24x", Icon16x16, FLinearColor(1.f, 1.f, 1.f, 0.5f) ) ); - Set( "Sequencer.Thumbnail.SectionHandle", new IMAGE_BRUSH( "Old/White", Icon16x16, FLinearColor::Black ) ); - Set( "Sequencer.TrackHoverHighlight_Top", new IMAGE_BRUSH( TEXT("Sequencer/TrackHoverHighlight_Top"), FVector2D(4, 4) ) ); - Set( "Sequencer.TrackHoverHighlight_Bottom", new IMAGE_BRUSH( TEXT("Sequencer/TrackHoverHighlight_Bottom"), FVector2D(4, 4) ) ); - Set( "Sequencer.SpawnableIconOverlay", new IMAGE_BRUSH( TEXT("Sequencer/SpawnableIconOverlay"), FVector2D(13, 13) ) ); - Set( "Sequencer.MultipleIconOverlay", new IMAGE_BRUSH(TEXT("Sequencer/MultipleIconOverlay"), FVector2D(13, 13) ) ); - Set( "Sequencer.LockSequence", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Locked_16x", Icon16x16) ); - Set( "Sequencer.UnlockSequence", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Unlocked_16x", Icon16x16) ); - - Set( "Sequencer.GeneralOptions", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_General_Options_24x", Icon48x48 ) ); - Set( "Sequencer.GeneralOptions.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_General_Options_24x", Icon24x24 ) ); - Set( "Sequencer.PlaybackOptions", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Playback_Options_24x", Icon48x48 ) ); - Set( "Sequencer.PlaybackOptions.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Playback_Options_24x", Icon24x24 ) ); - Set( "Sequencer.SelectEditOptions", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_SelectEdit_Options_24x", Icon48x48 ) ); - Set( "Sequencer.SelectEditOptions.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_SelectEdit_Options_24x", Icon24x24 ) ); - Set( "Sequencer.Time", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Time_24x", Icon48x48 ) ); - Set( "Sequencer.Time.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Time_24x", Icon24x24 ) ); - Set( "Sequencer.Value", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Value_24x", Icon48x48 ) ); - Set( "Sequencer.Value.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Value_24x", Icon24x24 ) ); - - Set( "Sequencer.OverlayPanel.Background", new BOX_BRUSH( "Sequencer/OverlayPanelBackground", FMargin(26.f/54.f) ) ); - - Set( "Sequencer.TrackArea.LaneColor", FLinearColor(0.3f, 0.3f, 0.3f, 0.3f) ); - - Set( "Sequencer.Tracks.Media", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Media_Track_16x", Icon16x16)); - Set( "Sequencer.Tracks.Audio", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Audio_Track_16x", Icon16x16)); - Set( "Sequencer.Tracks.Event", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Event_Track_16x", Icon16x16)); - Set( "Sequencer.Tracks.Fade", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Fade_Track_16x", Icon16x16)); - Set( "Sequencer.Tracks.CameraCut", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Camera_Cut_Track_16x", Icon16x16)); - Set( "Sequencer.Tracks.CinematicShot", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Shot_Track_16x", Icon16x16)); - Set( "Sequencer.Tracks.Slomo", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Play_Rate_Track_16x", Icon16x16)); - Set( "Sequencer.Tracks.Sub", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Sub_Track_16x", Icon16x16)); - Set( "Sequencer.Tracks.LevelVisibility", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Level_Visibility_Track_16x", Icon16x16)); - - Set( "Sequencer.CursorDecorator_MarqueeAdd", new IMAGE_BRUSH( "Sequencer/CursorDecorator_MarqueeAdd", Icon16x16)); - Set( "Sequencer.CursorDecorator_MarqueeSubtract", new IMAGE_BRUSH( "Sequencer/CursorDecorator_MarqueeSubtract", Icon16x16)); - - Set( "Sequencer.BreadcrumbText", FTextBlockStyle(NormalText) - .SetFont( DEFAULT_FONT( "Bold", 11 ) ) - .SetColorAndOpacity( FLinearColor( 1.0f, 1.0f, 1.0f ) ) - .SetHighlightColor( FLinearColor( 1.0f, 1.0f, 1.0f ) ) - .SetShadowOffset( FVector2D( 1,1 ) ) - .SetShadowColorAndOpacity( FLinearColor(0,0,0,0.9f) ) ); - Set( "Sequencer.BreadcrumbIcon", new IMAGE_BRUSH( "Common/SmallArrowRight", Icon10x10 ) ); - - const FButtonStyle DetailsKeyButton = FButtonStyle(NoBorder) - .SetNormal( IMAGE_BRUSH("Sequencer/AddKey_Details", FVector2D(11,11) ) ) - .SetHovered( IMAGE_BRUSH("Sequencer/AddKey_Details", FVector2D(11,11), SelectionColor ) ) - .SetPressed( IMAGE_BRUSH("Sequencer/AddKey_Details", FVector2D(11,11), SelectionColor_Pressed ) ) - .SetNormalPadding(FMargin(0, 1)) - .SetPressedPadding(FMargin(0, 2, 0, 0)); - Set( "Sequencer.AddKey.Details", DetailsKeyButton ); - - const FSplitterStyle OutlinerSplitterStyle = FSplitterStyle() - .SetHandleNormalBrush( FSlateNoResource() ) - .SetHandleHighlightBrush( FSlateNoResource() ); - Set( "Sequencer.AnimationOutliner.Splitter", OutlinerSplitterStyle ); - - Set( "Sequencer.HyperlinkSpinBox", FSpinBoxStyle(GetWidgetStyle("SpinBox")) - .SetTextPadding(FMargin(0)) - .SetBackgroundBrush(BORDER_BRUSH("Old/HyperlinkDotted", FMargin(0,0,0,3/16.0f), FSlateColor::UseSubduedForeground())) - .SetHoveredBackgroundBrush(FSlateNoResource()) - .SetInactiveFillBrush(FSlateNoResource()) - .SetActiveFillBrush(FSlateNoResource()) - .SetForegroundColor(FSlateColor::UseSubduedForeground()) - .SetArrowsImage(FSlateNoResource()) - ); - - Set("Sequencer.PlayTimeSpinBox", FSpinBoxStyle(GetWidgetStyle("SpinBox")) - .SetTextPadding(FMargin(0)) - .SetBackgroundBrush(FSlateNoResource()) - .SetHoveredBackgroundBrush(FSlateNoResource()) - .SetInactiveFillBrush(FSlateNoResource()) - .SetActiveFillBrush(FSlateNoResource()) - .SetForegroundColor(SelectionColor_Pressed) - .SetArrowsImage(FSlateNoResource()) - ); - - Set( "Sequencer.HyperlinkTextBox", FEditableTextBoxStyle() - .SetFont( DEFAULT_FONT( "Regular", 9 ) ) - .SetBackgroundImageNormal( FSlateNoResource() ) - .SetBackgroundImageHovered( FSlateNoResource() ) - .SetBackgroundImageFocused( FSlateNoResource() ) - .SetBackgroundImageReadOnly( FSlateNoResource() ) - .SetBackgroundColor( FLinearColor::Transparent ) - .SetForegroundColor( FSlateColor::UseSubduedForeground() ) - ); - Set( "Sequencer.FixedFont", DEFAULT_FONT( "Mono", 9 ) ); - - Set( "Sequencer.RecordSelectedActors", new IMAGE_BRUSH( "SequenceRecorder/icon_tab_SequenceRecorder_16x", Icon16x16 ) ); - - FComboButtonStyle SequencerSectionComboButton = FComboButtonStyle() - .SetButtonStyle( - FButtonStyle() - .SetNormal(FSlateNoResource()) - .SetHovered(FSlateNoResource()) - .SetPressed(FSlateNoResource()) - .SetNormalPadding(FMargin(0,0,0,0)) - .SetPressedPadding(FMargin(0,1,0,0)) - ) - .SetDownArrowImage(IMAGE_BRUSH("Common/ComboArrow", Icon8x8)); - Set( "Sequencer.SectionComboButton", SequencerSectionComboButton ); - - Set("Sequencer.CreateEventBinding", new IMAGE_BRUSH("Icons/icon_Blueprint_AddFunction_16px", Icon16x16)); - Set("Sequencer.CreateQuickBinding", new IMAGE_BRUSH("Icons/icon_Blueprint_Node_16x", Icon16x16)); - Set("Sequencer.ClearEventBinding", new IMAGE_BRUSH("Icons/Edit/icon_Edit_Delete_40x", Icon16x16)); - Set("Sequencer.MultipleEvents", new IMAGE_BRUSH("Sequencer/MultipleEvents", Icon16x16)); - Set("Sequencer.UnboundEvent", new IMAGE_BRUSH("Sequencer/UnboundEvent", Icon16x16)); - - // Sequencer Blending Iconography - Set( "EMovieSceneBlendType::Absolute", new IMAGE_BRUSH( "Sequencer/EMovieSceneBlendType_Absolute", FVector2D(32, 16) ) ); - Set( "EMovieSceneBlendType::Relative", new IMAGE_BRUSH( "Sequencer/EMovieSceneBlendType_Relative", FVector2D(32, 16) ) ); - Set( "EMovieSceneBlendType::Additive", new IMAGE_BRUSH( "Sequencer/EMovieSceneBlendType_Additive", FVector2D(32, 16) ) ); - } - - - // Sequence recorder standalone UI - if (IncludeEditorSpecificStyles()) - { - Set( "SequenceRecorder.TabIcon", new IMAGE_BRUSH( "SequenceRecorder/icon_tab_SequenceRecorder_16x", Icon16x16 ) ); - Set( "SequenceRecorder.Common.RecordAll.Small", new IMAGE_BRUSH( "SequenceRecorder/icon_RecordAll_40x", Icon20x20 ) ); - Set( "SequenceRecorder.Common.RecordAll", new IMAGE_BRUSH( "SequenceRecorder/icon_RecordAll_40x", Icon40x40 ) ); - Set( "SequenceRecorder.Common.StopAll.Small", new IMAGE_BRUSH( "SequenceRecorder/icon_StopAll_40x", Icon20x20 ) ); - Set( "SequenceRecorder.Common.StopAll", new IMAGE_BRUSH( "SequenceRecorder/icon_StopAll_40x", Icon40x40 ) ); - Set( "SequenceRecorder.Common.AddRecording.Small", new IMAGE_BRUSH( "SequenceRecorder/icon_AddRecording_40x", Icon20x20 ) ); - Set( "SequenceRecorder.Common.AddRecording", new IMAGE_BRUSH( "SequenceRecorder/icon_AddRecording_40x", Icon40x40 ) ); - Set( "SequenceRecorder.Common.AddCurrentPlayerRecording.Small", new IMAGE_BRUSH( "SequenceRecorder/icon_AddCurrentPlayerRecording_40x", Icon20x20 ) ); - Set( "SequenceRecorder.Common.AddCurrentPlayerRecording", new IMAGE_BRUSH( "SequenceRecorder/icon_AddCurrentPlayerRecording_40x", Icon40x40 ) ); - Set( "SequenceRecorder.Common.RemoveRecording.Small", new IMAGE_BRUSH( "SequenceRecorder/icon_RemoveRecording_40x", Icon20x20 ) ); - Set( "SequenceRecorder.Common.RemoveRecording", new IMAGE_BRUSH( "SequenceRecorder/icon_RemoveRecording_40x", Icon40x40 ) ); - Set( "SequenceRecorder.Common.RemoveAllRecordings.Small", new IMAGE_BRUSH( "SequenceRecorder/icon_RemoveRecording_40x", Icon20x20 ) ); - Set( "SequenceRecorder.Common.RemoveAllRecordings", new IMAGE_BRUSH( "SequenceRecorder/icon_RemoveRecording_40x", Icon40x40 ) ); - Set( "SequenceRecorder.Common.RecordingActive", new IMAGE_BRUSH( "Common/SmallCheckBox_Checked", Icon14x14 ) ); - Set( "SequenceRecorder.Common.RecordingInactive", new IMAGE_BRUSH( "Common/SmallCheckBox", Icon14x14 ) ); - } - // Foliage Edit Mode if (IncludeEditorSpecificStyles()) { @@ -2372,6 +2033,22 @@ void FSlateEditorStyle::FStyle::SetupGeneralStyles() /* ... and add new style */ Set( "ToolBar.CheckBox", ToolBarCheckBoxStyle ); + /* Read-only checkbox that appears next to a toolbar item */ + /* Set images for various SCheckBox states associated with read-only toolbar check box items... */ + const FCheckBoxStyle ToolBarCheckStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUncheckedHoveredImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUncheckedPressedImage(IMAGE_BRUSH("Common/SmallCheckBox_Hovered", Icon14x14)) + .SetCheckedImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetUndeterminedImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUndeterminedHoveredImage(FSlateNoResource()) + .SetUndeterminedPressedImage(FSlateNoResource()); + + /* ...and add the new style */ + Set("Toolbar.Check", ToolBarCheckStyle); + // This radio button is actually just a check box with different images /* Create style for "ToolBar.RadioButton" widget ... */ const FCheckBoxStyle ToolbarRadioButtonCheckBoxStyle = FCheckBoxStyle() @@ -2439,108 +2116,7 @@ void FSlateEditorStyle::FStyle::SetupGeneralStyles() ); } - // MenuBar - { - Set( "Menu.Background", new BOX_BRUSH( "Old/Menu_Background", FMargin(8.0f/64.0f) ) ); - Set( "Menu.Icon", new IMAGE_BRUSH( "Icons/icon_tab_toolbar_16px", Icon16x16 ) ); - Set( "Menu.Expand", new IMAGE_BRUSH( "Icons/toolbar_expand_16x", Icon16x16) ); - Set( "Menu.SubMenuIndicator", new IMAGE_BRUSH( "Common/SubmenuArrow", Icon8x8 ) ); - Set( "Menu.SToolBarComboButtonBlock.Padding", FMargin(4.0f)); - Set( "Menu.SToolBarButtonBlock.Padding", FMargin(4.0f)); - Set( "Menu.SToolBarCheckComboButtonBlock.Padding", FMargin(4.0f)); - Set( "Menu.SToolBarButtonBlock.CheckBox.Padding", FMargin(0.0f) ); - Set( "Menu.SToolBarComboButtonBlock.ComboButton.Color", DefaultForeground ); - - Set( "Menu.Block.IndentedPadding", FMargin( 18.0f, 2.0f, 4.0f, 4.0f ) ); - Set( "Menu.Block.Padding", FMargin( 2.0f, 2.0f, 4.0f, 4.0f ) ); - - Set( "Menu.Separator", new BOX_BRUSH( "Old/Button", 4.0f/32.0f ) ); - Set( "Menu.Separator.Padding", FMargin( 0.5f ) ); - - Set( "Menu.Label", FTextBlockStyle(NormalText) .SetFont( DEFAULT_FONT( "Regular", 9 ) ) ); - Set( "Menu.Label.Padding", FMargin(0.0f, 0.0f, 0.0f, 0.0f) ); - Set( "Menu.Label.ContentPadding", FMargin(10.0f, 2.0f) ); - Set( "Menu.EditableText", FEditableTextBoxStyle(NormalEditableTextBoxStyle) .SetFont( DEFAULT_FONT( "Regular", 9 ) ) ); - Set( "Menu.Keybinding", FTextBlockStyle(NormalText) .SetFont( DEFAULT_FONT( "Regular", 8 ) ) ); - - Set( "Menu.Heading", FTextBlockStyle(NormalText) - .SetFont( DEFAULT_FONT( "Regular", 8 ) ) - .SetColorAndOpacity( FLinearColor( 0.4f, 0.4, 0.4f, 1.0f ) ) ); - - /* Set images for various SCheckBox states associated with menu check box items... */ - const FCheckBoxStyle BasicMenuCheckBoxStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Common/SmallCheckBox", Icon14x14 ) ) - .SetUncheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheckBox_Hovered", Icon14x14 ) ) - .SetUncheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Hovered", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked", Icon14x14 ) ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked_Hovered", Icon14x14 ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked_Hovered", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ) - .SetUndeterminedImage( IMAGE_BRUSH( "Common/CheckBox_Undetermined", Icon14x14 ) ) - .SetUndeterminedHoveredImage( IMAGE_BRUSH( "Common/CheckBox_Undetermined_Hovered", Icon14x14 ) ) - .SetUndeterminedPressedImage( IMAGE_BRUSH( "Common/CheckBox_Undetermined_Hovered", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ); - - /* ...and add the new style */ - Set( "Menu.CheckBox", BasicMenuCheckBoxStyle ); - - /* Read-only checkbox that appears next to a menu item */ - /* Set images for various SCheckBox states associated with read-only menu check box items... */ - const FCheckBoxStyle BasicMenuCheckStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Icons/Empty_14x", Icon14x14 ) ) - .SetUncheckedHoveredImage( IMAGE_BRUSH( "Icons/Empty_14x", Icon14x14 ) ) - .SetUncheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Hovered", Icon14x14 ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ) - .SetUndeterminedImage( IMAGE_BRUSH( "Icons/Empty_14x", Icon14x14 ) ) - .SetUndeterminedHoveredImage( FSlateNoResource() ) - .SetUndeterminedPressedImage( FSlateNoResource() ); - - /* ...and add the new style */ - Set( "Menu.Check", BasicMenuCheckStyle ); - - /* This radio button is actually just a check box with different images */ - /* Set images for various Menu radio button (SCheckBox) states... */ - const FCheckBoxStyle BasicMenuRadioButtonStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16 ) ) - .SetUncheckedHoveredImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16 ) ) - .SetUncheckedPressedImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16 ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/RadioButton_Selected_16x", Icon16x16 ) ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/RadioButton_Selected_16x", Icon16x16, SelectionColor ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor_Pressed ) ) - .SetUndeterminedImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16 ) ) - .SetUndeterminedHoveredImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor ) ) - .SetUndeterminedPressedImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor_Pressed ) ); - - /* ...and set new style */ - Set( "Menu.RadioButton", BasicMenuRadioButtonStyle ); - - /* Create style for "Menu.ToggleButton" widget ... */ - const FCheckBoxStyle MenuToggleButtonCheckBoxStyle = FCheckBoxStyle() - .SetCheckBoxType( ESlateCheckBoxType::ToggleButton ) - .SetUncheckedImage( FSlateNoResource() ) - .SetUncheckedPressedImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ) - .SetUncheckedHoveredImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor ) ) - .SetCheckedImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ) - .SetCheckedHoveredImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ) - .SetCheckedPressedImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor ) ); - /* ... and add new style */ - Set( "Menu.ToggleButton", MenuToggleButtonCheckBoxStyle ); - - Set( "Menu.Button", FButtonStyle( NoBorder ) - .SetNormal ( FSlateNoResource() ) - .SetPressed( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ) - .SetHovered( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor ) ) - .SetNormalPadding( FMargin(0,1) ) - .SetPressedPadding( FMargin(0,2,0,0) ) - ); - - Set( "Menu.Button.Checked", new BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ); - Set( "Menu.Button.Checked_Hovered", new BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ); - Set( "Menu.Button.Checked_Pressed", new BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor ) ); - - /* The style of a menu bar button when it has a sub menu open */ - Set( "Menu.Button.SubMenuOpen", new BORDER_BRUSH( "Common/Selection", FMargin(4.f/16.f), FLinearColor(0.10f, 0.10f, 0.10f) ) ); - } + // ViewportLayoutToolbar { @@ -2568,239 +2144,7 @@ void FSlateEditorStyle::FStyle::SetupGeneralStyles() Set( "ViewportLayoutToolbar.SToolBarComboButtonBlock.ComboButton.Color", DefaultForeground ); } - // NotificationBar - { - Set( "NotificationBar.Background", new FSlateNoResource() ); - Set( "NotificationBar.Icon", new FSlateNoResource() ); - Set( "NotificationBar.Expand", new IMAGE_BRUSH( "Icons/toolbar_expand_16x", Icon16x16) ); - Set( "NotificationBar.SubMenuIndicator", new IMAGE_BRUSH( "Common/SubmenuArrow", Icon8x8 ) ); - - Set( "NotificationBar.Block.IndentedPadding", FMargin( 0 ) ); - Set( "NotificationBar.Block.Padding", FMargin( 0 ) ); - - Set( "NotificationBar.Separator", new BOX_BRUSH( "Old/Button", 4.0f/32.0f ) ); - Set( "NotificationBar.Separator.Padding", FMargin( 0.5f ) ); - - Set( "NotificationBar.Label", FTextBlockStyle(NormalText) .SetFont( DEFAULT_FONT( "Regular", 9 ) ) ); - Set( "NotificationBar.EditableText", FEditableTextBoxStyle(NormalEditableTextBoxStyle) .SetFont( DEFAULT_FONT( "Regular", 9 ) ) ); - Set( "NotificationBar.Keybinding", FTextBlockStyle(NormalText) .SetFont( DEFAULT_FONT( "Regular", 8 ) ) ); - - Set( "NotificationBar.Heading", FTextBlockStyle(NormalText) - .SetFont( DEFAULT_FONT( "Regular", 8 ) ) - .SetColorAndOpacity( FLinearColor( 0.4f, 0.4, 0.4f, 1.0f ) ) ); - - const FCheckBoxStyle NotificationBarCheckBoxCheckBoxStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Common/SmallCheckBox", Icon14x14 ) ) - .SetUncheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Hovered", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ) - .SetUncheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheckBox_Hovered", Icon14x14 ) ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked_Hovered", Icon14x14 ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked_Hovered", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked", Icon14x14 ) ) - .SetUndeterminedImage( IMAGE_BRUSH( "Common/CheckBox_Undetermined", Icon14x14 ) ) - .SetUndeterminedHoveredImage( IMAGE_BRUSH( "Common/CheckBox_Undetermined_Hovered", Icon14x14 ) ) - .SetUndeterminedPressedImage( IMAGE_BRUSH( "Common/CheckBox_Undetermined_Hovered", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ); - Set( "NotificationBar.CheckBox", NotificationBarCheckBoxCheckBoxStyle ); - - // Read-only checkbox that appears next to a menu item - const FCheckBoxStyle NotificationBarCheckCheckBoxStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Icons/Empty_14x", Icon14x14 ) ) - .SetUncheckedPressedImage( FSlateNoResource() ) - .SetUncheckedHoveredImage( FSlateNoResource() ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ) - .SetUndeterminedImage( IMAGE_BRUSH( "Icons/Empty_14x", Icon14x14 ) ) - .SetUndeterminedPressedImage( FSlateNoResource() ) - .SetUndeterminedHoveredImage( FSlateNoResource() ); - Set( "NotificationBar.Check", NotificationBarCheckCheckBoxStyle ); - - // This radio button is actually just a check box with different images - const FCheckBoxStyle NotificationBarRadioButtonCheckBoxStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16 ) ) - .SetUncheckedPressedImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor_Pressed ) ) - .SetUncheckedHoveredImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor ) ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/RadioButton_Selected_16x", Icon16x16, SelectionColor ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/RadioButton_Selected_16x", Icon16x16, SelectionColor_Pressed ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/RadioButton_Selected_16x", Icon16x16 ) ); - Set( "NotificationBar.RadioButton", NotificationBarRadioButtonCheckBoxStyle ); - - const FCheckBoxStyle NotificationBarToggleButtonCheckBoxStyle = FCheckBoxStyle() - .SetCheckBoxType( ESlateCheckBoxType::ToggleButton ) - .SetUncheckedImage( FSlateNoResource() ) - .SetUncheckedPressedImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ) - .SetUncheckedHoveredImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor ) ) - .SetCheckedHoveredImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ) - .SetCheckedPressedImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor ) ) - .SetCheckedImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ); - Set( "NotificationBar.ToggleButton", NotificationBarToggleButtonCheckBoxStyle ); - - Set( "NotificationBar.Button", FButtonStyle( NoBorder ) - .SetNormal ( FSlateNoResource() ) - .SetPressed( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ) - .SetHovered( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor ) ) - .SetNormalPadding( FMargin(0,1) ) - .SetPressedPadding( FMargin(0,2,0,0) ) - ); - - Set( "NotificationBar.Button.Checked", new BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ); - Set( "NotificationBar.Button.Checked_Hovered", new BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ); - Set( "NotificationBar.Button.Checked_Pressed", new BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor ) ); - - Set( "NotificationBar.SToolBarButtonBlock.CheckBox.Padding", FMargin(4.0f) ); - Set( "NotificationBar.SToolBarButtonBlock.Button.Padding", FMargin(0.0f) ); - Set( "NotificationBar.SToolBarComboButtonBlock.ComboButton.Color", DefaultForeground ); - } - - // Viewport ToolbarBar - { - Set( "ViewportMenu.Background", new BOX_BRUSH( "Old/Menu_Background", FMargin(8.0f/64.0f), FLinearColor::Transparent ) ); - Set( "ViewportMenu.Icon", new IMAGE_BRUSH( "Icons/icon_tab_toolbar_16px", Icon16x16 ) ); - Set( "ViewportMenu.Expand", new IMAGE_BRUSH( "Icons/toolbar_expand_16x", Icon8x8) ); - Set( "ViewportMenu.SubMenuIndicator", new IMAGE_BRUSH( "Common/SubmenuArrow", Icon8x8) ); - Set( "ViewportMenu.SToolBarComboButtonBlock.Padding", FMargin(0)); - Set( "ViewportMenu.SToolBarButtonBlock.Padding", FMargin(0)); - Set( "ViewportMenu.SToolBarButtonBlock.Button.Padding", FMargin(0)); - Set( "ViewportMenu.SToolBarCheckComboButtonBlock.Padding", FMargin(0)); - Set( "ViewportMenu.SToolBarButtonBlock.CheckBox.Padding", FMargin(4.0f) ); - Set( "ViewportMenu.SToolBarComboButtonBlock.ComboButton.Color", FLinearColor(0.f,0.f,0.f,0.75f) ); - - Set( "ViewportMenu.Separator", new BOX_BRUSH( "Old/Button", 8.0f/32.0f, FLinearColor::Transparent ) ); - Set( "ViewportMenu.Separator.Padding", FMargin( 100.0f ) ); - - Set( "ViewportMenu.Label", FTextBlockStyle(NormalText) - .SetFont(DEFAULT_FONT("Bold", 9)) - .SetColorAndOpacity(FLinearColor(0.0f, 0.0f, 0.0f, 1.0f))); - Set( "ViewportMenu.Label.Padding", FMargin(0.0f, 0.0f, 3.0f, 0.0f) ); - Set( "ViewportMenu.Label.ContentPadding", FMargin(5.0f, 2.0f) ); - Set( "ViewportMenu.EditableText", FEditableTextBoxStyle(NormalEditableTextBoxStyle) .SetFont( DEFAULT_FONT( "Regular", 9) ) ); - Set( "ViewportMenu.Keybinding", FTextBlockStyle(NormalText) .SetFont( DEFAULT_FONT( "Regular", 8) ) ); - - Set( "ViewportMenu.Block.IndentedPadding", FMargin( 0 ) ); - Set( "ViewportMenu.Block.Padding", FMargin( 0 ) ); - - Set( "ViewportMenu.Heading.Font", DEFAULT_FONT( "Regular", 8 ) ); - Set( "ViewportMenu.Heading.ColorAndOpacity", FLinearColor( 0.4f, 0.4, 0.4f, 1.0f ) ); - - const FCheckBoxStyle ViewportMenuCheckBoxCheckBoxStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Common/SmallCheckBox", Icon14x14 ) ) - .SetUncheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Hovered", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ) - .SetUncheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheckBox_Hovered", Icon14x14 ) ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked_Hovered", Icon14x14 ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked_Hovered", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked", Icon14x14 ) ); - Set( "ViewportMenu.CheckBox", ViewportMenuCheckBoxCheckBoxStyle ); - - // Read-only checkbox that appears next to a menu item - const FCheckBoxStyle ViewportMenuCheckCheckBoxStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Icons/Empty_14x", Icon14x14 ) ) - .SetUncheckedPressedImage( FSlateNoResource() ) - .SetUncheckedHoveredImage( FSlateNoResource() ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ); - Set( "ViewportMenu.Check", ViewportMenuCheckCheckBoxStyle ); - - const FString SmallRoundedButton (TEXT("Common/SmallRoundedButton")); - const FString SmallRoundedButtonStart (TEXT("Common/SmallRoundedButtonLeft")); - const FString SmallRoundedButtonMiddle (TEXT("Common/SmallRoundedButtonCentre")); - const FString SmallRoundedButtonEnd (TEXT("Common/SmallRoundedButtonRight")); - - const FLinearColor NormalColor(1,1,1,0.75f); - const FLinearColor PressedColor(1,1,1,1.f); - - const FCheckBoxStyle ViewportMenuRadioButtonCheckBoxStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Common/MenuItemRadioButton_Off", Icon14x14 ) ) - .SetUncheckedPressedImage( IMAGE_BRUSH( "Common/MenuItemRadioButton_Off", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ) - .SetUncheckedHoveredImage( IMAGE_BRUSH( "Common/MenuItemRadioButton_Off", Icon14x14 ) ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/MenuItemRadioButton_On", Icon14x14 ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/MenuItemRadioButton_On_Pressed", Icon14x14 ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/MenuItemRadioButton_On", Icon14x14 ) ); - Set( "ViewportMenu.RadioButton", ViewportMenuRadioButtonCheckBoxStyle ); - - /* Create style for "ViewportMenu.ToggleButton" ... */ - const FCheckBoxStyle ViewportMenuToggleButtonStyle = FCheckBoxStyle() - .SetCheckBoxType( ESlateCheckBoxType::ToggleButton ) - .SetUncheckedImage( BOX_BRUSH( *SmallRoundedButton, FMargin(7.f/16.f), NormalColor ) ) - .SetUncheckedPressedImage( BOX_BRUSH( *SmallRoundedButton, FMargin(7.f/16.f), PressedColor ) ) - .SetUncheckedHoveredImage( BOX_BRUSH( *SmallRoundedButton, FMargin(7.f/16.f), PressedColor ) ) - .SetCheckedHoveredImage( BOX_BRUSH( *SmallRoundedButton, FMargin(7.f/16.f), SelectionColor_Pressed ) ) - .SetCheckedPressedImage( BOX_BRUSH( *SmallRoundedButton, FMargin(7.f/16.f), SelectionColor_Pressed ) ) - .SetCheckedImage( BOX_BRUSH( *SmallRoundedButton, FMargin(7.f/16.f), SelectionColor_Pressed ) ); - /* ... and add new style */ - Set( "ViewportMenu.ToggleButton", ViewportMenuToggleButtonStyle ); - - /* Create style for "ViewportMenu.ToggleButton.Start" ... */ - const FCheckBoxStyle ViewportMenuToggleStartButtonStyle = FCheckBoxStyle() - .SetCheckBoxType( ESlateCheckBoxType::ToggleButton ) - .SetUncheckedImage( BOX_BRUSH( *SmallRoundedButtonStart, FMargin(7.f/16.f), NormalColor ) ) - .SetUncheckedPressedImage( BOX_BRUSH( *SmallRoundedButtonStart, FMargin(7.f/16.f), PressedColor ) ) - .SetUncheckedHoveredImage( BOX_BRUSH( *SmallRoundedButtonStart, FMargin(7.f/16.f), PressedColor ) ) - .SetCheckedHoveredImage( BOX_BRUSH( *SmallRoundedButtonStart, FMargin(7.f/16.f), SelectionColor_Pressed ) ) - .SetCheckedPressedImage( BOX_BRUSH( *SmallRoundedButtonStart, FMargin(7.f/16.f), SelectionColor_Pressed ) ) - .SetCheckedImage( BOX_BRUSH( *SmallRoundedButtonStart, FMargin(7.f/16.f), SelectionColor_Pressed ) ); - /* ... and add new style */ - Set( "ViewportMenu.ToggleButton.Start", ViewportMenuToggleStartButtonStyle ); - - /* Create style for "ViewportMenu.ToggleButton.Middle" ... */ - const FCheckBoxStyle ViewportMenuToggleMiddleButtonStyle = FCheckBoxStyle() - .SetCheckBoxType( ESlateCheckBoxType::ToggleButton ) - .SetUncheckedImage( BOX_BRUSH( *SmallRoundedButtonMiddle, FMargin(7.f/16.f), NormalColor ) ) - .SetUncheckedPressedImage( BOX_BRUSH( *SmallRoundedButtonMiddle, FMargin(7.f/16.f), PressedColor ) ) - .SetUncheckedHoveredImage( BOX_BRUSH( *SmallRoundedButtonMiddle, FMargin(7.f/16.f), PressedColor ) ) - .SetCheckedHoveredImage( BOX_BRUSH( *SmallRoundedButtonMiddle, FMargin(7.f/16.f), SelectionColor_Pressed ) ) - .SetCheckedPressedImage( BOX_BRUSH( *SmallRoundedButtonMiddle, FMargin(7.f/16.f), SelectionColor_Pressed ) ) - .SetCheckedImage( BOX_BRUSH( *SmallRoundedButtonMiddle, FMargin(7.f/16.f), SelectionColor_Pressed ) ); - /* ... and add new style */ - Set( "ViewportMenu.ToggleButton.Middle", ViewportMenuToggleMiddleButtonStyle ); - - /* Create style for "ViewportMenu.ToggleButton.End" ... */ - const FCheckBoxStyle ViewportMenuToggleEndButtonStyle = FCheckBoxStyle() - .SetCheckBoxType( ESlateCheckBoxType::ToggleButton ) - .SetUncheckedImage( BOX_BRUSH( *SmallRoundedButtonEnd, FMargin(7.f/16.f), NormalColor ) ) - .SetUncheckedPressedImage( BOX_BRUSH( *SmallRoundedButtonEnd, FMargin(7.f/16.f), PressedColor ) ) - .SetUncheckedHoveredImage( BOX_BRUSH( *SmallRoundedButtonEnd, FMargin(7.f/16.f), PressedColor ) ) - .SetCheckedHoveredImage( BOX_BRUSH( *SmallRoundedButtonEnd, FMargin(7.f/16.f), SelectionColor_Pressed ) ) - .SetCheckedPressedImage( BOX_BRUSH( *SmallRoundedButtonEnd, FMargin(7.f/16.f), SelectionColor_Pressed ) ) - .SetCheckedImage( BOX_BRUSH( *SmallRoundedButtonEnd, FMargin(7.f/16.f), SelectionColor_Pressed ) ); - /* ... and add new style */ - Set( "ViewportMenu.ToggleButton.End", ViewportMenuToggleEndButtonStyle ); - - const FMargin NormalPadding = FMargin(4.0f, 4.0f, 4.0f, 4.0f); - const FMargin PressedPadding = FMargin(4.0f, 4.0f, 4.0f, 4.0f); - - const FButtonStyle ViewportMenuButton = FButtonStyle(Button) - .SetNormal ( BOX_BRUSH( *SmallRoundedButton, 7.0f/16.0f, NormalColor)) - .SetPressed( BOX_BRUSH( *SmallRoundedButton, 7.0f/16.0f, PressedColor ) ) - .SetHovered(BOX_BRUSH(*SmallRoundedButton, 7.0f / 16.0f, PressedColor)) - .SetPressedPadding(PressedPadding) - .SetNormalPadding(NormalPadding); - - Set( "ViewportMenu.Button", ViewportMenuButton ); - - Set( "ViewportMenu.Button.Start", FButtonStyle(ViewportMenuButton) - .SetNormal ( BOX_BRUSH( *SmallRoundedButtonStart, 7.0f/16.0f, NormalColor)) - .SetPressed( BOX_BRUSH( *SmallRoundedButtonStart, 7.0f/16.0f, PressedColor ) ) - .SetHovered( BOX_BRUSH( *SmallRoundedButtonStart, 7.0f/16.0f, PressedColor ) ) - ); - - Set( "ViewportMenu.Button.Middle", FButtonStyle(ViewportMenuButton) - .SetNormal ( BOX_BRUSH( *SmallRoundedButtonMiddle, 7.0f/16.0f, NormalColor)) - .SetPressed( BOX_BRUSH( *SmallRoundedButtonMiddle, 7.0f/16.0f, PressedColor ) ) - .SetHovered( BOX_BRUSH( *SmallRoundedButtonMiddle, 7.0f/16.0f, PressedColor ) ) - ); - - Set( "ViewportMenu.Button.End", FButtonStyle(ViewportMenuButton) - .SetNormal ( BOX_BRUSH( *SmallRoundedButtonEnd, 7.0f/16.0f, NormalColor)) - .SetPressed( BOX_BRUSH( *SmallRoundedButtonEnd, 7.0f/16.0f, PressedColor ) ) - .SetHovered( BOX_BRUSH( *SmallRoundedButtonEnd, 7.0f/16.0f, PressedColor ) ) - ); - } - - // Viewport actor preview's pin/unpin buttons - { - Set( "ViewportActorPreview.Pinned", new IMAGE_BRUSH( "Common/PushPin_Down", Icon16x16 ) ); - Set( "ViewportActorPreview.Unpinned", new IMAGE_BRUSH( "Common/PushPin_Up", Icon16x16 ) ); - } + // Standard Dialog Settings { @@ -2901,6 +2245,731 @@ void FSlateEditorStyle::FStyle::SetupGeneralStyles() #endif // WITH_EDITOR || (IS_PROGRAM && WITH_UNREAL_DEVELOPER_TOOLS) } +void FSlateEditorStyle::FStyle::SetupLevelGeneralStyles() +{ +// Levels General + { + Set("Level.VisibleIcon16x", new IMAGE_BRUSH("Icons/icon_levels_visible_16px", Icon16x16)); + Set("Level.VisibleHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_levels_visible_hi_16px", Icon16x16)); + Set("Level.NotVisibleIcon16x", new IMAGE_BRUSH("Icons/icon_levels_invisible_16px", Icon16x16)); + Set("Level.NotVisibleHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_levels_invisible_hi_16px", Icon16x16)); + Set("Level.LightingScenarioIcon16x", new IMAGE_BRUSH("Icons/icon_levels_LightingScenario_16px", Icon16x16)); + Set("Level.LightingScenarioNotIcon16x", new IMAGE_BRUSH("Icons/icon_levels_LightingScenarioNot_16px", Icon16x16)); + Set("Level.LockedIcon16x", new IMAGE_BRUSH("Icons/icon_locked_16px", Icon16x16)); + Set("Level.LockedHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_locked_highlight_16px", Icon16x16)); + Set("Level.UnlockedIcon16x", new IMAGE_BRUSH("Icons/icon_levels_unlocked_16px", Icon16x16)); + Set("Level.UnlockedHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_levels_unlocked_hi_16px", Icon16x16)); + Set("Level.ReadOnlyLockedIcon16x", new IMAGE_BRUSH("Icons/icon_levels_LockedReadOnly_16px", Icon16x16)); + Set("Level.ReadOnlyLockedHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_levels_LockedReadOnly_hi_16px", Icon16x16)); + Set("Level.SaveIcon16x", new IMAGE_BRUSH("Icons/icon_levels_Save_16px", Icon16x16)); + Set("Level.SaveHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_levels_Save_hi_16px", Icon16x16)); + Set("Level.SaveModifiedIcon16x", new IMAGE_BRUSH("Icons/icon_levels_SaveModified_16px", Icon16x16)); + Set("Level.SaveModifiedHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_levels_SaveModified_hi_16px", Icon16x16)); + Set("Level.SaveDisabledIcon16x", new IMAGE_BRUSH("Icons/icon_levels_SaveDisabled_16px", Icon16x16)); + Set("Level.SaveDisabledHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_levels_SaveDisabled_hi_16px", Icon16x16)); + Set("Level.ScriptIcon16x", new IMAGE_BRUSH("Icons/icon_levels_Blueprint_16px", Icon16x16)); + Set("Level.ScriptHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_levels_Blueprint_hi_16px", Icon16x16)); + Set("Level.EmptyIcon16x", new IMAGE_BRUSH("Icons/Empty_16x", Icon16x16)); + Set("Level.ColorIcon40x", new IMAGE_BRUSH("Icons/icon_levels_back_16px", Icon16x16)); + } +} + +void FSlateEditorStyle::FStyle::SetupWorldBrowserStyles() +{ + + // World Browser + { + Set("WorldBrowser.AddLayer", new IMAGE_BRUSH("Icons/icon_levels_addlayer_16x", Icon16x16)); + Set("WorldBrowser.SimulationViewPositon", new IMAGE_BRUSH("Icons/icon_levels_simulationviewpos_16x", Icon16x16)); + Set("WorldBrowser.MouseLocation", new IMAGE_BRUSH("Icons/icon_levels_mouselocation_16x", Icon16x16)); + Set("WorldBrowser.MarqueeRectSize", new IMAGE_BRUSH("Icons/icon_levels_marqueerectsize_16x", Icon16x16)); + Set("WorldBrowser.WorldSize", new IMAGE_BRUSH("Icons/icon_levels_worldsize_16x", Icon16x16)); + Set("WorldBrowser.WorldOrigin", new IMAGE_BRUSH("Icons/icon_levels_worldorigin_16x", Icon16x16)); + Set("WorldBrowser.DirectionXPositive", new IMAGE_BRUSH("Icons/icon_PanRight", Icon16x16)); + Set("WorldBrowser.DirectionXNegative", new IMAGE_BRUSH("Icons/icon_PanLeft", Icon16x16)); + Set("WorldBrowser.DirectionYPositive", new IMAGE_BRUSH("Icons/icon_PanUp", Icon16x16)); + Set("WorldBrowser.DirectionYNegative", new IMAGE_BRUSH("Icons/icon_PanDown", Icon16x16)); + Set("WorldBrowser.LevelStreamingAlwaysLoaded", new FSlateNoResource()); + Set("WorldBrowser.LevelStreamingBlueprint", new IMAGE_BRUSH("Icons/icon_levels_blueprinttype_7x16", Icon7x16)); + Set("WorldBrowser.LevelsMenuBrush", new IMAGE_BRUSH("Icons/icon_levels_levelsmenu_40x", Icon25x25)); + Set("WorldBrowser.HierarchyButtonBrush", new IMAGE_BRUSH("Icons/icon_levels_hierarchybutton_16x", Icon16x16)); + Set("WorldBrowser.DetailsButtonBrush", new IMAGE_BRUSH("Icons/icon_levels_detailsbutton_40x", Icon16x16)); + Set("WorldBrowser.CompositionButtonBrush", new IMAGE_BRUSH("Icons/icon_levels_compositionbutton_16x", Icon16x16)); + + Set("WorldBrowser.FolderClosed", new IMAGE_BRUSH("Icons/FolderClosed", Icon16x16)); + Set("WorldBrowser.FolderOpen", new IMAGE_BRUSH("Icons/FolderOpen", Icon16x16)); + Set("WorldBrowser.NewFolderIcon", new IMAGE_BRUSH("Icons/icon_AddFolder_16x", Icon16x16)); + + Set("WorldBrowser.StatusBarText", FTextBlockStyle(NormalText) + .SetFont(DEFAULT_FONT("BoldCondensed", 12)) + .SetColorAndOpacity(FLinearColor(0.9, 0.9f, 0.9f, 0.5f)) + .SetShadowOffset(FVector2D::ZeroVector) + ); + + Set("WorldBrowser.LabelFont", DEFAULT_FONT("Regular", 9)); + Set("WorldBrowser.LabelFontBold", DEFAULT_FONT("Bold", 10)); + } +} + +void FSlateEditorStyle::FStyle::SetupSequencerStyles() +{ + // Sequencer + if (IncludeEditorSpecificStyles()) + { + Set("Sequencer.IconKeyAuto", new IMAGE_BRUSH("Sequencer/IconKeyAuto", Icon12x12)); + Set("Sequencer.IconKeyBreak", new IMAGE_BRUSH("Sequencer/IconKeyBreak", Icon12x12)); + Set("Sequencer.IconKeyConstant", new IMAGE_BRUSH("Sequencer/IconKeyConstant", Icon12x12)); + Set("Sequencer.IconKeyLinear", new IMAGE_BRUSH("Sequencer/IconKeyLinear", Icon12x12)); + Set("Sequencer.IconKeyUser", new IMAGE_BRUSH("Sequencer/IconKeyUser", Icon12x12)); + + Set("Sequencer.KeyCircle", new IMAGE_BRUSH("Sequencer/KeyCircle", Icon12x12)); + Set("Sequencer.KeyDiamond", new IMAGE_BRUSH("Sequencer/KeyDiamond", Icon12x12)); + Set("Sequencer.KeyDiamondBorder", new IMAGE_BRUSH("Sequencer/KeyDiamondBorder", Icon12x12)); + Set("Sequencer.KeySquare", new IMAGE_BRUSH("Sequencer/KeySquare", Icon12x12)); + Set("Sequencer.KeyTriangle", new IMAGE_BRUSH("Sequencer/KeyTriangle", Icon12x12)); + Set("Sequencer.KeyLeft", new IMAGE_BRUSH("Sequencer/KeyLeft", Icon12x12)); + Set("Sequencer.KeyRight", new IMAGE_BRUSH("Sequencer/KeyRight", Icon12x12)); + Set("Sequencer.PartialKey", new IMAGE_BRUSH("Sequencer/PartialKey", FVector2D(11.f, 11.f))); + Set("Sequencer.Star", new IMAGE_BRUSH("Sequencer/Star", Icon12x12)); + Set("Sequencer.Empty", new IMAGE_BRUSH("Sequencer/Empty", Icon12x12)); + Set("Sequencer.TangentHandle", new IMAGE_BRUSH("Sequencer/TangentHandle", FVector2D(7, 7))); + Set("Sequencer.GenericDivider", new IMAGE_BRUSH("Sequencer/GenericDivider", FVector2D(2.f, 2.f), FLinearColor::White, ESlateBrushTileType::Vertical)); + + Set("Sequencer.Timeline.ScrubHandleDown", new BOX_BRUSH("Sequencer/ScrubHandleDown", FMargin(6.f / 13.f, 5 / 12.f, 6 / 13.f, 8 / 12.f))); + Set("Sequencer.Timeline.ScrubHandleUp", new BOX_BRUSH("Sequencer/ScrubHandleUp", FMargin(6.f / 13.f, 8 / 12.f, 6 / 13.f, 5 / 12.f))); + Set("Sequencer.Timeline.ScrubFill", new BOX_BRUSH("Sequencer/ScrubFill", FMargin(2.f / 4.f, 0.f))); + Set("Sequencer.Timeline.FrameBlockScrubHandleDown", new BOX_BRUSH("Sequencer/ScrubHandleDown", FMargin(6.f / 13.f, 5 / 12.f, 6 / 13.f, 8 / 12.f))); + Set("Sequencer.Timeline.FrameBlockScrubHandleUp", new BOX_BRUSH("Sequencer/ScrubHandleUp", FMargin(6.f / 13.f, 8 / 12.f, 6 / 13.f, 5 / 12.f))); + Set("Sequencer.Timeline.VanillaScrubHandleDown", new BOX_BRUSH("Sequencer/ScrubHandleDown_Clamped", FMargin(6.f / 13.f, 3.f / 12.f, 6.f / 13.f, 7.f / 12.f))); + Set("Sequencer.Timeline.VanillaScrubHandleUp", new BOX_BRUSH("Sequencer/ScrubHandleUp_Clamped", FMargin(6.f / 13.f, 8 / 12.f, 6 / 13.f, 5 / 12.f))); + Set("Sequencer.Timeline.ScrubHandleWhole", new BOX_BRUSH("Sequencer/ScrubHandleWhole", FMargin(6.f / 13.f, 10 / 24.f, 6 / 13.f, 10 / 24.f))); + Set("Sequencer.Timeline.RangeHandleLeft", new BOX_BRUSH("Sequencer/GenericGripLeft", FMargin(5.f / 16.f))); + Set("Sequencer.Timeline.RangeHandleRight", new BOX_BRUSH("Sequencer/GenericGripRight", FMargin(5.f / 16.f))); + Set("Sequencer.Timeline.RangeHandle", new BOX_BRUSH("Sequencer/GenericSectionBackground", FMargin(5.f / 16.f))); + Set("Sequencer.Timeline.NotifyAlignmentMarker", new IMAGE_BRUSH("Sequencer/NotifyAlignmentMarker", FVector2D(10, 19))); + Set("Sequencer.Timeline.PlayRange_Top_L", new BOX_BRUSH("Sequencer/PlayRange_Top_L", FMargin(1.f, 0.5f, 0.f, 0.5f))); + Set("Sequencer.Timeline.PlayRange_Top_R", new BOX_BRUSH("Sequencer/PlayRange_Top_R", FMargin(0.f, 0.5f, 1.f, 0.5f))); + Set("Sequencer.Timeline.PlayRange_L", new BOX_BRUSH("Sequencer/PlayRange_L", FMargin(1.f, 0.5f, 0.f, 0.5f))); + Set("Sequencer.Timeline.PlayRange_R", new BOX_BRUSH("Sequencer/PlayRange_R", FMargin(0.f, 0.5f, 1.f, 0.5f))); + Set("Sequencer.Timeline.PlayRange_Bottom_L", new BOX_BRUSH("Sequencer/PlayRange_Bottom_L", FMargin(1.f, 0.5f, 0.f, 0.5f))); + Set("Sequencer.Timeline.PlayRange_Bottom_R", new BOX_BRUSH("Sequencer/PlayRange_Bottom_R", FMargin(0.f, 0.5f, 1.f, 0.5f))); + + Set("Sequencer.Timeline.SubSequenceRangeHashL", new BORDER_BRUSH("Sequencer/SubSequenceRangeHashL", FMargin(1.f, 0.f, 0.f, 0.f))); + Set("Sequencer.Timeline.SubSequenceRangeHashR", new BORDER_BRUSH("Sequencer/SubSequenceRangeHashR", FMargin(1.f, 0.f, 0.f, 0.f))); + Set("Sequencer.Timeline.EaseInOut", new IMAGE_BRUSH("Sequencer/EaseInOut", FVector2D(128, 128))); + Set("Sequencer.InterpLine", new BOX_BRUSH("Sequencer/InterpLine", FMargin(5.f / 7.f, 0.f, 0.f, 0.f))); + + Set("Sequencer.Transport.JumpToPreviousKey", FButtonStyle() + .SetNormal(IMAGE_BRUSH("/Sequencer/Transport_Bar/Previous_Frame_OFF", Icon24x24)) + .SetPressed(IMAGE_BRUSH("/Sequencer/Transport_Bar/Previous_Frame", Icon24x24)) + .SetHovered(IMAGE_BRUSH("/Sequencer/Transport_Bar/Previous_Frame_OFF", Icon24x24))); + Set("Sequencer.Transport.JumpToNextKey", FButtonStyle() + .SetNormal(IMAGE_BRUSH("/Sequencer/Transport_Bar/Next_Frame_24x_OFF", Icon24x24)) + .SetPressed(IMAGE_BRUSH("/Sequencer/Transport_Bar/Next_Frame_24x", Icon24x24)) + .SetHovered(IMAGE_BRUSH("/Sequencer/Transport_Bar/Next_Frame_24x_OFF", Icon24x24))); + Set("Sequencer.Transport.SetPlayStart", FButtonStyle() + .SetNormal(IMAGE_BRUSH("/Sequencer/Transport_Bar/Bracket_In_16x24_OFF", FVector2D(16, 24))) + .SetPressed(IMAGE_BRUSH("/Sequencer/Transport_Bar/Bracket_In_16x24", FVector2D(16, 24))) + .SetHovered(IMAGE_BRUSH("/Sequencer/Transport_Bar/Bracket_In_16x24_OFF", FVector2D(16, 24)))); + Set("Sequencer.Transport.SetPlayEnd", FButtonStyle() + .SetNormal(IMAGE_BRUSH("/Sequencer/Transport_Bar/Bracket_Out_16x24_OFF", FVector2D(16, 24))) + .SetPressed(IMAGE_BRUSH("/Sequencer/Transport_Bar/Bracket_Out_16x24", FVector2D(16, 24))) + .SetHovered(IMAGE_BRUSH("/Sequencer/Transport_Bar/Bracket_Out_16x24_OFF", FVector2D(16, 24)))); + + Set("Sequencer.Transport.CloseButton", FButtonStyle() + .SetNormal(IMAGE_BRUSH("/Docking/CloseApp_Normal", Icon16x16)) + .SetPressed(IMAGE_BRUSH("/Docking/CloseApp_Pressed", Icon16x16)) + .SetHovered(IMAGE_BRUSH("/Docking/CloseApp_Hovered", Icon16x16))); + + Set("Sequencer.NotificationImage_AddedPlayMovieSceneEvent", new IMAGE_BRUSH("Old/Checkbox_checked", Icon16x16)); + + Set("Sequencer.Save", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Save_48x", Icon48x48)); + Set("Sequencer.Save.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Save_48x", Icon24x24)); + Set("Sequencer.SaveAsterisk", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_SaveAsterisk_48x", Icon48x48)); + Set("Sequencer.SaveAsterisk.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_SaveAsterisk_48x", Icon24x24)); + Set("Sequencer.SaveAs", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_SaveAs_48x", Icon48x48 )); + Set("Sequencer.SaveAs.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_SaveAs_48x", Icon24x24 )); + Set("Sequencer.DiscardChanges", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Revert_24x", Icon48x48)); + Set("Sequencer.DiscardChanges.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Revert_24x", Icon24x24)); + Set("Sequencer.RestoreAnimatedState", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_RestoreAnimatedState_24x", Icon48x48)); + Set("Sequencer.RestoreAnimatedState.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_RestoreAnimatedState_24x", Icon24x24)); + Set("Sequencer.GenericGripLeft", new BOX_BRUSH("Sequencer/GenericGripLeft", FMargin(5.f / 16.f))); + Set("Sequencer.GenericGripRight", new BOX_BRUSH("Sequencer/GenericGripRight", FMargin(5.f / 16.f))); + Set("Sequencer.SectionArea.Background", new FSlateColorBrush(FColor::White)); + + Set("Sequencer.Section.Background", new BORDER_BRUSH(TEXT("Sequencer/SectionBackground"), FMargin(4.f / 16.f))); + Set("Sequencer.Section.BackgroundTint", new BOX_BRUSH(TEXT("Sequencer/SectionBackgroundTint"), FMargin(4 / 16.f))); + Set("Sequencer.Section.SelectedSectionOverlay", new IMAGE_BRUSH(TEXT("Sequencer/SelectedSectionOverlay"), Icon16x16, FLinearColor::White, ESlateBrushTileType::Both)); + Set("Sequencer.Section.SelectedTrackTint", new BOX_BRUSH(TEXT("Sequencer/SelectedTrackTint"), FMargin(0.f, 0.5f))); + Set("Sequencer.Section.SelectionBorder", new BORDER_BRUSH(TEXT("Sequencer/SectionHighlight"), FMargin(7.f / 16.f))); + Set("Sequencer.Section.LockedBorder", new BORDER_BRUSH(TEXT("Sequencer/SectionLocked"), FMargin(7.f / 16.f))); + Set("Sequencer.Section.SelectedSectionOverlay", new IMAGE_BRUSH(TEXT("Sequencer/SelectedSectionOverlay"), Icon16x16, FLinearColor::White, ESlateBrushTileType::Both)); + Set("Sequencer.Section.FilmBorder", new IMAGE_BRUSH(TEXT("Sequencer/SectionFilmBorder"), FVector2D(10, 7), FLinearColor::White, ESlateBrushTileType::Horizontal)); + Set("Sequencer.Section.GripLeft", new BOX_BRUSH("Sequencer/SectionGripLeft", FMargin(5.f / 16.f))); + Set("Sequencer.Section.GripRight", new BOX_BRUSH("Sequencer/SectionGripRight", FMargin(5.f / 16.f))); + Set("Sequencer.Section.EasingHandle", new IMAGE_BRUSH("Sequencer/EasingHandle", FVector2D(10.f, 10.f))); + + Set("Sequencer.Section.PreRoll", new BORDER_BRUSH(TEXT("Sequencer/PreRoll"), FMargin(0.f, .5f, 0.f, .5f))); + + Set("Sequencer.Section.PinCusion", new IMAGE_BRUSH(TEXT("Sequencer/PinCusion"), Icon16x16, FLinearColor::White, ESlateBrushTileType::Both)); + Set("Sequencer.Section.OverlapBorder", new BORDER_BRUSH(TEXT("Sequencer/OverlapBorder"), FMargin(1.f / 4.f, 0.f))); + Set("Sequencer.Section.StripeO.verlay", new BOX_BRUSH("Sequencer/SectionStripeOverlay", FMargin(0.f, .5f))); + Set("Sequencer.Section.BackgroundText", DEFAULT_FONT("Bold", 24)); + Set("Sequencer.Section.EmptySpace", new BOX_BRUSH(TEXT("Sequencer/EmptySpace"), FMargin(0.f, 7.f / 14.f))); + + Set("Sequencer.AnimationOutliner.ColorStrip", FButtonStyle() + .SetNormal(FSlateNoResource()) + .SetHovered(FSlateNoResource()) + .SetPressed(FSlateNoResource()) + .SetNormalPadding(FMargin(0, 0, 0, 0)) + .SetPressedPadding(FMargin(0, 0, 0, 0)) + ); + + Set("Sequencer.AnimationOutliner.TopLevelBorder_Expanded", new BOX_BRUSH("Sequencer/TopLevelNodeBorder_Expanded", FMargin(4.0f / 16.0f))); + Set("Sequencer.AnimationOutliner.TopLevelBorder_Collapsed", new BOX_BRUSH("Sequencer/TopLevelNodeBorder_Collapsed", FMargin(4.0f / 16.0f))); + Set("Sequencer.AnimationOutliner.DefaultBorder", new FSlateColorBrush(FLinearColor::White)); + Set("Sequencer.AnimationOutliner.TransparentBorder", new FSlateColorBrush(FLinearColor::Transparent)); + Set("Sequencer.AnimationOutliner.BoldFont", DEFAULT_FONT("Bold", 11)); + Set("Sequencer.AnimationOutliner.RegularFont", DEFAULT_FONT("Regular", 9)); + Set("Sequencer.ShotFilter", new IMAGE_BRUSH("Sequencer/FilteredArea", FVector2D(74, 74), FLinearColor::White, ESlateBrushTileType::Both)); + Set("Sequencer.KeyMark", new IMAGE_BRUSH("Sequencer/KeyMark", FVector2D(3, 21), FLinearColor::White, ESlateBrushTileType::NoTile)); + Set("Sequencer.SetAutoKey", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Auto_Key_24x", Icon48x48)); + Set("Sequencer.SetAutoKey.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Auto_Key_24x", Icon24x24)); + Set("Sequencer.SetAutoTrack", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Auto_Track_24x", Icon48x48)); + Set("Sequencer.SetAutoTrack.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Auto_Track_24x", Icon24x24)); + Set("Sequencer.SetAutoChangeAll", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Auto_Key_All_24x", Icon48x48)); + Set("Sequencer.SetAutoChangeAll.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Auto_Key_All_24x", Icon24x24)); + Set("Sequencer.SetAutoChangeNone", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Disable_Auto_Key_24x", Icon48x48)); + Set("Sequencer.SetAutoChangeNone.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Disable_Auto_Key_24x", Icon24x24)); + Set("Sequencer.AllowAllEdits", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Allow_All_Edits_24x", Icon48x48)); + Set("Sequencer.AllowAllEdits.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Allow_All_Edits_24x", Icon24x24)); + Set("Sequencer.AllowSequencerEditsOnly", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Allow_Sequencer_Edits_Only_24x", Icon48x48)); + Set("Sequencer.AllowSequencerEditsOnly.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Allow_Sequencer_Edits_Only_24x", Icon24x24)); + Set("Sequencer.AllowLevelEditsOnly", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Allow_Level_Edits_Only_24x", Icon48x48)); + Set("Sequencer.AllowLevelEditsOnly.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Allow_Level_Edits_Only_24x", Icon24x24)); + Set("Sequencer.SetKeyAll", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Key_All_24x", Icon48x48)); + Set("Sequencer.SetKeyAll.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Key_All_24x", Icon24x24)); + Set("Sequencer.SetKeyGroup", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Key_Group_24x", Icon48x48)); + Set("Sequencer.SetKeyGroup.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Key_Group_24x", Icon24x24)); + Set("Sequencer.SetKeyChanged", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Key_Part_24x", Icon48x48)); + Set("Sequencer.SetKeyChanged.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Key_Part_24x", Icon24x24)); + Set("Sequencer.ToggleIsSnapEnabled", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Snap_24x", Icon48x48)); + Set("Sequencer.ToggleIsSnapEnabled.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Snap_24x", Icon24x24)); + Set("Sequencer.ToggleShowCurveEditor", new IMAGE_BRUSH("Icons/SequencerIcons/icon_Sequencer_CurveEditor_24x", Icon48x48)); + Set("Sequencer.ToggleShowCurveEditor.Small", new IMAGE_BRUSH("Icons/SequencerIcons/icon_Sequencer_CurveEditor_24x", Icon24x24)); + Set("Sequencer.ToggleAutoScroll", new IMAGE_BRUSH("Icons/icon_Sequencer_ToggleAutoScroll_40x", Icon48x48)); + Set("Sequencer.ToggleAutoScroll.Small", new IMAGE_BRUSH("Icons/icon_Sequencer_ToggleAutoScroll_16x", Icon16x16)); + Set("Sequencer.MoveTool.Small", new IMAGE_BRUSH("Icons/SequencerIcons/icon_Sequencer_Move_24x", Icon16x16)); + Set("Sequencer.MarqueeTool.Small", new IMAGE_BRUSH("Icons/SequencerIcons/icon_Sequencer_Marquee_24x", Icon16x16)); + Set("Sequencer.RenderMovie.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Create_Movie_24x", Icon24x24)); + Set("Sequencer.CreateCamera.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Create_Camera_24x", Icon24x24)); + Set("Sequencer.FindInContentBrowser.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Find_In_Content_Browser_24x", Icon24x24)); + Set("Sequencer.LockCamera", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Look_Thru_24x", Icon16x16)); + Set("Sequencer.UnlockCamera", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Look_Thru_24x", Icon16x16, FLinearColor(1.f, 1.f, 1.f, 0.5f))); + Set("Sequencer.Thumbnail.SectionHandle", new IMAGE_BRUSH("Old/White", Icon16x16, FLinearColor::Black)); + Set("Sequencer.TrackHoverHighlight_Top", new IMAGE_BRUSH(TEXT("Sequencer/TrackHoverHighlight_Top"), FVector2D(4, 4))); + Set("Sequencer.TrackHoverHighlight_Bottom", new IMAGE_BRUSH(TEXT("Sequencer/TrackHoverHighlight_Bottom"), FVector2D(4, 4))); + Set("Sequencer.SpawnableIconOverlay", new IMAGE_BRUSH(TEXT("Sequencer/SpawnableIconOverlay"), FVector2D(13, 13))); + Set("Sequencer.MultipleIconOverlay", new IMAGE_BRUSH(TEXT("Sequencer/MultipleIconOverlay"), FVector2D(13, 13))); + Set("Sequencer.LockSequence", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Locked_16x", Icon16x16)); + Set("Sequencer.UnlockSequence", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Unlocked_16x", Icon16x16)); + + Set("Sequencer.GeneralOptions", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_General_Options_24x", Icon48x48)); + Set("Sequencer.GeneralOptions.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_General_Options_24x", Icon24x24)); + Set("Sequencer.PlaybackOptions", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Playback_Options_24x", Icon48x48)); + Set("Sequencer.PlaybackOptions.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Playback_Options_24x", Icon24x24)); + Set("Sequencer.SelectEditOptions", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_SelectEdit_Options_24x", Icon48x48)); + Set("Sequencer.SelectEditOptions.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_SelectEdit_Options_24x", Icon24x24)); + Set("Sequencer.Time", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Time_24x", Icon48x48)); + Set("Sequencer.Time.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Time_24x", Icon24x24)); + Set("Sequencer.Value", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Value_24x", Icon48x48)); + Set("Sequencer.Value.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Value_24x", Icon24x24)); + + Set("Sequencer.OverlayPanel.Background", new BOX_BRUSH("Sequencer/OverlayPanelBackground", FMargin(26.f / 54.f))); + + Set("Sequencer.TrackArea.LaneColor", FLinearColor(0.3f, 0.3f, 0.3f, 0.3f)); + + Set("Sequencer.Tracks.Media", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Media_Track_16x", Icon16x16)); + Set("Sequencer.Tracks.Audio", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Audio_Track_16x", Icon16x16)); + Set("Sequencer.Tracks.Event", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Event_Track_16x", Icon16x16)); + Set("Sequencer.Tracks.Fade", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Fade_Track_16x", Icon16x16)); + Set("Sequencer.Tracks.CameraCut", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Camera_Cut_Track_16x", Icon16x16)); + Set("Sequencer.Tracks.CinematicShot", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Shot_Track_16x", Icon16x16)); + Set("Sequencer.Tracks.Slomo", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Play_Rate_Track_16x", Icon16x16)); + Set("Sequencer.Tracks.Sub", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Sub_Track_16x", Icon16x16)); + Set("Sequencer.Tracks.LevelVisibility", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Level_Visibility_Track_16x", Icon16x16)); + + Set("Sequencer.CursorDecorator_MarqueeAdd", new IMAGE_BRUSH("Sequencer/CursorDecorator_MarqueeAdd", Icon16x16)); + Set("Sequencer.CursorDecorator_MarqueeSubtract", new IMAGE_BRUSH("Sequencer/CursorDecorator_MarqueeSubtract", Icon16x16)); + + Set("Sequencer.BreadcrumbText", FTextBlockStyle(NormalText) + .SetFont(DEFAULT_FONT("Bold", 11)) + .SetColorAndOpacity(FLinearColor(1.0f, 1.0f, 1.0f)) + .SetHighlightColor(FLinearColor(1.0f, 1.0f, 1.0f)) + .SetShadowOffset(FVector2D(1, 1)) + .SetShadowColorAndOpacity(FLinearColor(0, 0, 0, 0.9f))); + Set("Sequencer.BreadcrumbIcon", new IMAGE_BRUSH("Common/SmallArrowRight", Icon10x10)); + + const FButtonStyle NoBorder = FButtonStyle() + .SetNormal(FSlateNoResource()) + .SetHovered(FSlateNoResource()) + .SetPressed(FSlateNoResource()) + .SetNormalPadding(FMargin(0, 0, 0, 1)) + .SetPressedPadding(FMargin(0, 1, 0, 0)); + + const FButtonStyle DetailsKeyButton = FButtonStyle(NoBorder) + .SetNormal(IMAGE_BRUSH("Sequencer/AddKey_Details", FVector2D(11, 11))) + .SetHovered(IMAGE_BRUSH("Sequencer/AddKey_Details", FVector2D(11, 11), SelectionColor)) + .SetPressed(IMAGE_BRUSH("Sequencer/AddKey_Details", FVector2D(11, 11), SelectionColor_Pressed)) + .SetNormalPadding(FMargin(0, 1)) + .SetPressedPadding(FMargin(0, 2, 0, 0)); + Set("Sequencer.AddKey.Details", DetailsKeyButton); + + const FSplitterStyle OutlinerSplitterStyle = FSplitterStyle() + .SetHandleNormalBrush(FSlateNoResource()) + .SetHandleHighlightBrush(FSlateNoResource()); + Set("Sequencer.AnimationOutliner.Splitter", OutlinerSplitterStyle); + + Set("Sequencer.HyperlinkSpinBox", FSpinBoxStyle(GetWidgetStyle("SpinBox")) + .SetTextPadding(FMargin(0)) + .SetBackgroundBrush(BORDER_BRUSH("Old/HyperlinkDotted", FMargin(0, 0, 0, 3 / 16.0f), FSlateColor::UseSubduedForeground())) + .SetHoveredBackgroundBrush(FSlateNoResource()) + .SetInactiveFillBrush(FSlateNoResource()) + .SetActiveFillBrush(FSlateNoResource()) + .SetForegroundColor(FSlateColor::UseSubduedForeground()) + .SetArrowsImage(FSlateNoResource()) + ); + + Set("Sequencer.PlayTimeSpinBox", FSpinBoxStyle(GetWidgetStyle("SpinBox")) + .SetTextPadding(FMargin(0)) + .SetBackgroundBrush(FSlateNoResource()) + .SetHoveredBackgroundBrush(FSlateNoResource()) + .SetInactiveFillBrush(FSlateNoResource()) + .SetActiveFillBrush(FSlateNoResource()) + .SetForegroundColor(SelectionColor_Pressed) + .SetArrowsImage(FSlateNoResource()) + ); + + Set("Sequencer.HyperlinkTextBox", FEditableTextBoxStyle() + .SetFont(DEFAULT_FONT("Regular", 9)) + .SetBackgroundImageNormal(FSlateNoResource()) + .SetBackgroundImageHovered(FSlateNoResource()) + .SetBackgroundImageFocused(FSlateNoResource()) + .SetBackgroundImageReadOnly(FSlateNoResource()) + .SetBackgroundColor(FLinearColor::Transparent) + .SetForegroundColor(FSlateColor::UseSubduedForeground()) + ); + Set("Sequencer.FixedFont", DEFAULT_FONT("Mono", 9)); + + Set("Sequencer.RecordSelectedActors", new IMAGE_BRUSH("SequenceRecorder/icon_tab_SequenceRecorder_16x", Icon16x16)); + + FComboButtonStyle SequencerSectionComboButton = FComboButtonStyle() + .SetButtonStyle( + FButtonStyle() + .SetNormal(FSlateNoResource()) + .SetHovered(FSlateNoResource()) + .SetPressed(FSlateNoResource()) + .SetNormalPadding(FMargin(0, 0, 0, 0)) + .SetPressedPadding(FMargin(0, 1, 0, 0)) + ) + .SetDownArrowImage(IMAGE_BRUSH("Common/ComboArrow", Icon8x8)); + Set("Sequencer.SectionComboButton", SequencerSectionComboButton); + + Set("Sequencer.CreateEventBinding", new IMAGE_BRUSH("Icons/icon_Blueprint_AddFunction_16px", Icon16x16)); + Set("Sequencer.CreateQuickBinding", new IMAGE_BRUSH("Icons/icon_Blueprint_Node_16x", Icon16x16)); + Set("Sequencer.ClearEventBinding", new IMAGE_BRUSH("Icons/Edit/icon_Edit_Delete_40x", Icon16x16)); + Set("Sequencer.MultipleEvents", new IMAGE_BRUSH("Sequencer/MultipleEvents", Icon16x16)); + Set("Sequencer.UnboundEvent", new IMAGE_BRUSH("Sequencer/UnboundEvent", Icon16x16)); + + // Sequencer Blending Iconography + Set("EMovieSceneBlendType::Absolute", new IMAGE_BRUSH("Sequencer/EMovieSceneBlendType_Absolute", FVector2D(32, 16))); + Set("EMovieSceneBlendType::Relative", new IMAGE_BRUSH("Sequencer/EMovieSceneBlendType_Relative", FVector2D(32, 16))); + Set("EMovieSceneBlendType::Additive", new IMAGE_BRUSH("Sequencer/EMovieSceneBlendType_Additive", FVector2D(32, 16))); + } + + + // Sequence recorder standalone UI + if (IncludeEditorSpecificStyles()) + { + Set("SequenceRecorder.TabIcon", new IMAGE_BRUSH("SequenceRecorder/icon_tab_SequenceRecorder_16x", Icon16x16)); + Set("SequenceRecorder.Common.RecordAll.Small", new IMAGE_BRUSH("SequenceRecorder/icon_RecordAll_40x", Icon20x20)); + Set("SequenceRecorder.Common.RecordAll", new IMAGE_BRUSH("SequenceRecorder/icon_RecordAll_40x", Icon40x40)); + Set("SequenceRecorder.Common.StopAll.Small", new IMAGE_BRUSH("SequenceRecorder/icon_StopAll_40x", Icon20x20)); + Set("SequenceRecorder.Common.StopAll", new IMAGE_BRUSH("SequenceRecorder/icon_StopAll_40x", Icon40x40)); + Set("SequenceRecorder.Common.AddRecording.Small", new IMAGE_BRUSH("SequenceRecorder/icon_AddRecording_40x", Icon20x20)); + Set("SequenceRecorder.Common.AddRecording", new IMAGE_BRUSH("SequenceRecorder/icon_AddRecording_40x", Icon40x40)); + Set("SequenceRecorder.Common.AddCurrentPlayerRecording.Small", new IMAGE_BRUSH("SequenceRecorder/icon_AddCurrentPlayerRecording_40x", Icon20x20)); + Set("SequenceRecorder.Common.AddCurrentPlayerRecording", new IMAGE_BRUSH("SequenceRecorder/icon_AddCurrentPlayerRecording_40x", Icon40x40)); + Set("SequenceRecorder.Common.RemoveRecording.Small", new IMAGE_BRUSH("SequenceRecorder/icon_RemoveRecording_40x", Icon20x20)); + Set("SequenceRecorder.Common.RemoveRecording", new IMAGE_BRUSH("SequenceRecorder/icon_RemoveRecording_40x", Icon40x40)); + Set("SequenceRecorder.Common.RemoveAllRecordings.Small", new IMAGE_BRUSH("SequenceRecorder/icon_RemoveRecording_40x", Icon20x20)); + Set("SequenceRecorder.Common.RemoveAllRecordings", new IMAGE_BRUSH("SequenceRecorder/icon_RemoveRecording_40x", Icon40x40)); + Set("SequenceRecorder.Common.RecordingActive", new IMAGE_BRUSH("Common/SmallCheckBox_Checked", Icon14x14)); + Set("SequenceRecorder.Common.RecordingInactive", new IMAGE_BRUSH("Common/SmallCheckBox", Icon14x14)); + } + +} + +void FSlateEditorStyle::FStyle::SetupViewportStyles() +{ +// Viewport ToolbarBar + { + Set("ViewportMenu.Background", new BOX_BRUSH("Old/Menu_Background", FMargin(8.0f / 64.0f), FLinearColor::Transparent)); + Set("ViewportMenu.Icon", new IMAGE_BRUSH("Icons/icon_tab_toolbar_16px", Icon16x16)); + Set("ViewportMenu.Expand", new IMAGE_BRUSH("Icons/toolbar_expand_16x", Icon8x8)); + Set("ViewportMenu.SubMenuIndicator", new IMAGE_BRUSH("Common/SubmenuArrow", Icon8x8)); + Set("ViewportMenu.SToolBarComboButtonBlock.Padding", FMargin(0)); + Set("ViewportMenu.SToolBarButtonBlock.Padding", FMargin(0)); + Set("ViewportMenu.SToolBarButtonBlock.Button.Padding", FMargin(0)); + Set("ViewportMenu.SToolBarCheckComboButtonBlock.Padding", FMargin(0)); + Set("ViewportMenu.SToolBarButtonBlock.CheckBox.Padding", FMargin(4.0f)); + Set("ViewportMenu.SToolBarComboButtonBlock.ComboButton.Color", FLinearColor(0.f, 0.f, 0.f, 0.75f)); + + Set("ViewportMenu.Separator", new BOX_BRUSH("Old/Button", 8.0f / 32.0f, FLinearColor::Transparent)); + Set("ViewportMenu.Separator.Padding", FMargin(100.0f)); + + Set("ViewportMenu.Label", FTextBlockStyle(NormalText) + .SetFont(DEFAULT_FONT("Bold", 9)) + .SetColorAndOpacity(FLinearColor(0.0f, 0.0f, 0.0f, 1.0f))); + Set("ViewportMenu.Label.Padding", FMargin(0.0f, 0.0f, 3.0f, 0.0f)); + Set("ViewportMenu.Label.ContentPadding", FMargin(5.0f, 2.0f)); + Set("ViewportMenu.EditableText", FEditableTextBoxStyle(NormalEditableTextBoxStyle).SetFont(DEFAULT_FONT("Regular", 9))); + Set("ViewportMenu.Keybinding", FTextBlockStyle(NormalText).SetFont(DEFAULT_FONT("Regular", 8))); + + Set("ViewportMenu.Block.IndentedPadding", FMargin(0)); + Set("ViewportMenu.Block.Padding", FMargin(0)); + + Set("ViewportMenu.Heading.Font", DEFAULT_FONT("Regular", 8)); + Set("ViewportMenu.Heading.ColorAndOpacity", FLinearColor(0.4f, 0.4, 0.4f, 1.0f)); + + const FCheckBoxStyle ViewportMenuCheckBoxCheckBoxStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Common/SmallCheckBox", Icon14x14)) + .SetUncheckedPressedImage(IMAGE_BRUSH("Common/SmallCheckBox_Hovered", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))) + .SetUncheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheckBox_Hovered", Icon14x14)) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked_Hovered", Icon14x14)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked_Hovered", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))) + .SetCheckedImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked", Icon14x14)); + Set("ViewportMenu.CheckBox", ViewportMenuCheckBoxCheckBoxStyle); + + // Read-only checkbox that appears next to a menu item + const FCheckBoxStyle ViewportMenuCheckCheckBoxStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUncheckedPressedImage(FSlateNoResource()) + .SetUncheckedHoveredImage(FSlateNoResource()) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetCheckedImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)); + Set("ViewportMenu.Check", ViewportMenuCheckCheckBoxStyle); + + const FString SmallRoundedButton(TEXT("Common/SmallRoundedButton")); + const FString SmallRoundedButtonStart(TEXT("Common/SmallRoundedButtonLeft")); + const FString SmallRoundedButtonMiddle(TEXT("Common/SmallRoundedButtonCentre")); +const FString SmallRoundedButtonEnd(TEXT("Common/SmallRoundedButtonRight")); + +const FLinearColor NormalColor(1, 1, 1, 0.75f); +const FLinearColor PressedColor(1, 1, 1, 1.f); + +const FCheckBoxStyle ViewportMenuRadioButtonCheckBoxStyle = FCheckBoxStyle() +.SetUncheckedImage(IMAGE_BRUSH("Common/MenuItemRadioButton_Off", Icon14x14)) +.SetUncheckedPressedImage(IMAGE_BRUSH("Common/MenuItemRadioButton_Off", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))) +.SetUncheckedHoveredImage(IMAGE_BRUSH("Common/MenuItemRadioButton_Off", Icon14x14)) +.SetCheckedHoveredImage(IMAGE_BRUSH("Common/MenuItemRadioButton_On", Icon14x14)) +.SetCheckedPressedImage(IMAGE_BRUSH("Common/MenuItemRadioButton_On_Pressed", Icon14x14)) +.SetCheckedImage(IMAGE_BRUSH("Common/MenuItemRadioButton_On", Icon14x14)); +Set("ViewportMenu.RadioButton", ViewportMenuRadioButtonCheckBoxStyle); + +/* Create style for "ViewportMenu.ToggleButton" ... */ +const FCheckBoxStyle ViewportMenuToggleButtonStyle = FCheckBoxStyle() +.SetCheckBoxType(ESlateCheckBoxType::ToggleButton) +.SetUncheckedImage(BOX_BRUSH(*SmallRoundedButton, FMargin(7.f / 16.f), NormalColor)) +.SetUncheckedPressedImage(BOX_BRUSH(*SmallRoundedButton, FMargin(7.f / 16.f), PressedColor)) +.SetUncheckedHoveredImage(BOX_BRUSH(*SmallRoundedButton, FMargin(7.f / 16.f), PressedColor)) +.SetCheckedHoveredImage(BOX_BRUSH(*SmallRoundedButton, FMargin(7.f / 16.f), SelectionColor_Pressed)) +.SetCheckedPressedImage(BOX_BRUSH(*SmallRoundedButton, FMargin(7.f / 16.f), SelectionColor_Pressed)) +.SetCheckedImage(BOX_BRUSH(*SmallRoundedButton, FMargin(7.f / 16.f), SelectionColor_Pressed)); +/* ... and add new style */ +Set("ViewportMenu.ToggleButton", ViewportMenuToggleButtonStyle); + +/* Create style for "ViewportMenu.ToggleButton.Start" ... */ +const FCheckBoxStyle ViewportMenuToggleStartButtonStyle = FCheckBoxStyle() +.SetCheckBoxType(ESlateCheckBoxType::ToggleButton) +.SetUncheckedImage(BOX_BRUSH(*SmallRoundedButtonStart, FMargin(7.f / 16.f), NormalColor)) +.SetUncheckedPressedImage(BOX_BRUSH(*SmallRoundedButtonStart, FMargin(7.f / 16.f), PressedColor)) +.SetUncheckedHoveredImage(BOX_BRUSH(*SmallRoundedButtonStart, FMargin(7.f / 16.f), PressedColor)) +.SetCheckedHoveredImage(BOX_BRUSH(*SmallRoundedButtonStart, FMargin(7.f / 16.f), SelectionColor_Pressed)) +.SetCheckedPressedImage(BOX_BRUSH(*SmallRoundedButtonStart, FMargin(7.f / 16.f), SelectionColor_Pressed)) +.SetCheckedImage(BOX_BRUSH(*SmallRoundedButtonStart, FMargin(7.f / 16.f), SelectionColor_Pressed)); +/* ... and add new style */ +Set("ViewportMenu.ToggleButton.Start", ViewportMenuToggleStartButtonStyle); + +/* Create style for "ViewportMenu.ToggleButton.Middle" ... */ +const FCheckBoxStyle ViewportMenuToggleMiddleButtonStyle = FCheckBoxStyle() +.SetCheckBoxType(ESlateCheckBoxType::ToggleButton) +.SetUncheckedImage(BOX_BRUSH(*SmallRoundedButtonMiddle, FMargin(7.f / 16.f), NormalColor)) +.SetUncheckedPressedImage(BOX_BRUSH(*SmallRoundedButtonMiddle, FMargin(7.f / 16.f), PressedColor)) +.SetUncheckedHoveredImage(BOX_BRUSH(*SmallRoundedButtonMiddle, FMargin(7.f / 16.f), PressedColor)) +.SetCheckedHoveredImage(BOX_BRUSH(*SmallRoundedButtonMiddle, FMargin(7.f / 16.f), SelectionColor_Pressed)) +.SetCheckedPressedImage(BOX_BRUSH(*SmallRoundedButtonMiddle, FMargin(7.f / 16.f), SelectionColor_Pressed)) +.SetCheckedImage(BOX_BRUSH(*SmallRoundedButtonMiddle, FMargin(7.f / 16.f), SelectionColor_Pressed)); +/* ... and add new style */ +Set("ViewportMenu.ToggleButton.Middle", ViewportMenuToggleMiddleButtonStyle); + +/* Create style for "ViewportMenu.ToggleButton.End" ... */ +const FCheckBoxStyle ViewportMenuToggleEndButtonStyle = FCheckBoxStyle() +.SetCheckBoxType(ESlateCheckBoxType::ToggleButton) +.SetUncheckedImage(BOX_BRUSH(*SmallRoundedButtonEnd, FMargin(7.f / 16.f), NormalColor)) +.SetUncheckedPressedImage(BOX_BRUSH(*SmallRoundedButtonEnd, FMargin(7.f / 16.f), PressedColor)) +.SetUncheckedHoveredImage(BOX_BRUSH(*SmallRoundedButtonEnd, FMargin(7.f / 16.f), PressedColor)) +.SetCheckedHoveredImage(BOX_BRUSH(*SmallRoundedButtonEnd, FMargin(7.f / 16.f), SelectionColor_Pressed)) +.SetCheckedPressedImage(BOX_BRUSH(*SmallRoundedButtonEnd, FMargin(7.f / 16.f), SelectionColor_Pressed)) +.SetCheckedImage(BOX_BRUSH(*SmallRoundedButtonEnd, FMargin(7.f / 16.f), SelectionColor_Pressed)); +/* ... and add new style */ +Set("ViewportMenu.ToggleButton.End", ViewportMenuToggleEndButtonStyle); + +const FMargin NormalPadding = FMargin(4.0f, 4.0f, 4.0f, 4.0f); +const FMargin PressedPadding = FMargin(4.0f, 4.0f, 4.0f, 4.0f); + +const FButtonStyle ViewportMenuButton = FButtonStyle(Button) +.SetNormal(BOX_BRUSH(*SmallRoundedButton, 7.0f / 16.0f, NormalColor)) +.SetPressed(BOX_BRUSH(*SmallRoundedButton, 7.0f / 16.0f, PressedColor)) +.SetHovered(BOX_BRUSH(*SmallRoundedButton, 7.0f / 16.0f, PressedColor)) +.SetPressedPadding(PressedPadding) +.SetNormalPadding(NormalPadding); + +Set("ViewportMenu.Button", ViewportMenuButton); + +Set("ViewportMenu.Button.Start", FButtonStyle(ViewportMenuButton) + .SetNormal(BOX_BRUSH(*SmallRoundedButtonStart, 7.0f / 16.0f, NormalColor)) + .SetPressed(BOX_BRUSH(*SmallRoundedButtonStart, 7.0f / 16.0f, PressedColor)) + .SetHovered(BOX_BRUSH(*SmallRoundedButtonStart, 7.0f / 16.0f, PressedColor)) +); + +Set("ViewportMenu.Button.Middle", FButtonStyle(ViewportMenuButton) + .SetNormal(BOX_BRUSH(*SmallRoundedButtonMiddle, 7.0f / 16.0f, NormalColor)) + .SetPressed(BOX_BRUSH(*SmallRoundedButtonMiddle, 7.0f / 16.0f, PressedColor)) + .SetHovered(BOX_BRUSH(*SmallRoundedButtonMiddle, 7.0f / 16.0f, PressedColor)) +); + +Set("ViewportMenu.Button.End", FButtonStyle(ViewportMenuButton) + .SetNormal(BOX_BRUSH(*SmallRoundedButtonEnd, 7.0f / 16.0f, NormalColor)) + .SetPressed(BOX_BRUSH(*SmallRoundedButtonEnd, 7.0f / 16.0f, PressedColor)) + .SetHovered(BOX_BRUSH(*SmallRoundedButtonEnd, 7.0f / 16.0f, PressedColor)) +); + } + + // Viewport actor preview's pin/unpin buttons + { + Set("ViewportActorPreview.Pinned", new IMAGE_BRUSH("Common/PushPin_Down", Icon16x16)); + Set("ViewportActorPreview.Unpinned", new IMAGE_BRUSH("Common/PushPin_Up", Icon16x16)); + } +} + +void FSlateEditorStyle::FStyle::SetupNotificationBarStyles() +{ +// NotificationBar + { + Set("NotificationBar.Background", new FSlateNoResource()); + Set("NotificationBar.Icon", new FSlateNoResource()); + Set("NotificationBar.Expand", new IMAGE_BRUSH("Icons/toolbar_expand_16x", Icon16x16)); + Set("NotificationBar.SubMenuIndicator", new IMAGE_BRUSH("Common/SubmenuArrow", Icon8x8)); + + Set("NotificationBar.Block.IndentedPadding", FMargin(0)); + Set("NotificationBar.Block.Padding", FMargin(0)); + + Set("NotificationBar.Separator", new BOX_BRUSH("Old/Button", 4.0f / 32.0f)); + Set("NotificationBar.Separator.Padding", FMargin(0.5f)); + + Set("NotificationBar.Label", FTextBlockStyle(NormalText).SetFont(DEFAULT_FONT("Regular", 9))); + Set("NotificationBar.EditableText", FEditableTextBoxStyle(NormalEditableTextBoxStyle).SetFont(DEFAULT_FONT("Regular", 9))); + Set("NotificationBar.Keybinding", FTextBlockStyle(NormalText).SetFont(DEFAULT_FONT("Regular", 8))); + + Set("NotificationBar.Heading", FTextBlockStyle(NormalText) + .SetFont(DEFAULT_FONT("Regular", 8)) + .SetColorAndOpacity(FLinearColor(0.4f, 0.4, 0.4f, 1.0f))); + + const FCheckBoxStyle NotificationBarCheckBoxCheckBoxStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Common/SmallCheckBox", Icon14x14)) + .SetUncheckedPressedImage(IMAGE_BRUSH("Common/SmallCheckBox_Hovered", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))) + .SetUncheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheckBox_Hovered", Icon14x14)) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked_Hovered", Icon14x14)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked_Hovered", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))) + .SetCheckedImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked", Icon14x14)) + .SetUndeterminedImage(IMAGE_BRUSH("Common/CheckBox_Undetermined", Icon14x14)) + .SetUndeterminedHoveredImage(IMAGE_BRUSH("Common/CheckBox_Undetermined_Hovered", Icon14x14)) + .SetUndeterminedPressedImage(IMAGE_BRUSH("Common/CheckBox_Undetermined_Hovered", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))); + Set("NotificationBar.CheckBox", NotificationBarCheckBoxCheckBoxStyle); + + // Read-only checkbox that appears next to a menu item + const FCheckBoxStyle NotificationBarCheckCheckBoxStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUncheckedPressedImage(FSlateNoResource()) + .SetUncheckedHoveredImage(FSlateNoResource()) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetCheckedImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetUndeterminedImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUndeterminedPressedImage(FSlateNoResource()) + .SetUndeterminedHoveredImage(FSlateNoResource()); + Set("NotificationBar.Check", NotificationBarCheckCheckBoxStyle); + + // This radio button is actually just a check box with different images + const FCheckBoxStyle NotificationBarRadioButtonCheckBoxStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16)) + .SetUncheckedPressedImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor_Pressed)) + .SetUncheckedHoveredImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor)) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/RadioButton_Selected_16x", Icon16x16, SelectionColor)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/RadioButton_Selected_16x", Icon16x16, SelectionColor_Pressed)) + .SetCheckedImage(IMAGE_BRUSH("Common/RadioButton_Selected_16x", Icon16x16)); + Set("NotificationBar.RadioButton", NotificationBarRadioButtonCheckBoxStyle); + + const FCheckBoxStyle NotificationBarToggleButtonCheckBoxStyle = FCheckBoxStyle() + .SetCheckBoxType(ESlateCheckBoxType::ToggleButton) + .SetUncheckedImage(FSlateNoResource()) + .SetUncheckedPressedImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)) + .SetUncheckedHoveredImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor)) + .SetCheckedHoveredImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)) + .SetCheckedPressedImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor)) + .SetCheckedImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)); + Set("NotificationBar.ToggleButton", NotificationBarToggleButtonCheckBoxStyle); + + const FButtonStyle NoBorder = FButtonStyle() + .SetNormal(FSlateNoResource()) + .SetHovered(FSlateNoResource()) + .SetPressed(FSlateNoResource()) + .SetNormalPadding(FMargin(0, 0, 0, 1)) + .SetPressedPadding(FMargin(0, 1, 0, 0)); + + Set("NotificationBar.Button", FButtonStyle(NoBorder) + .SetNormal(FSlateNoResource()) + .SetPressed(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)) + .SetHovered(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor)) + .SetNormalPadding(FMargin(0, 1)) + .SetPressedPadding(FMargin(0, 2, 0, 0)) + ); + + Set("NotificationBar.Button.Checked", new BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)); + Set("NotificationBar.Button.Checked_Hovered", new BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)); + Set("NotificationBar.Button.Checked_Pressed", new BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor)); + + Set("NotificationBar.SToolBarButtonBlock.CheckBox.Padding", FMargin(4.0f)); + Set("NotificationBar.SToolBarButtonBlock.Button.Padding", FMargin(0.0f)); + Set("NotificationBar.SToolBarComboButtonBlock.ComboButton.Color", DefaultForeground); + } +} + +void FSlateEditorStyle::FStyle::SetupMenuBarStyles() +{ + // MenuBar + { + Set("Menu.Background", new BOX_BRUSH("Old/Menu_Background", FMargin(8.0f / 64.0f))); + Set("Menu.Icon", new IMAGE_BRUSH("Icons/icon_tab_toolbar_16px", Icon16x16)); + Set("Menu.Expand", new IMAGE_BRUSH("Icons/toolbar_expand_16x", Icon16x16)); + Set("Menu.SubMenuIndicator", new IMAGE_BRUSH("Common/SubmenuArrow", Icon8x8)); + Set("Menu.SToolBarComboButtonBlock.Padding", FMargin(4.0f)); + Set("Menu.SToolBarButtonBlock.Padding", FMargin(4.0f)); + Set("Menu.SToolBarCheckComboButtonBlock.Padding", FMargin(4.0f)); + Set("Menu.SToolBarButtonBlock.CheckBox.Padding", FMargin(0.0f)); + Set("Menu.SToolBarComboButtonBlock.ComboButton.Color", DefaultForeground); + + Set("Menu.Block.IndentedPadding", FMargin(18.0f, 2.0f, 4.0f, 4.0f)); + Set("Menu.Block.Padding", FMargin(2.0f, 2.0f, 4.0f, 4.0f)); + + Set("Menu.Separator", new BOX_BRUSH("Old/Button", 4.0f / 32.0f)); + Set("Menu.Separator.Padding", FMargin(0.5f)); + + Set("Menu.Label", FTextBlockStyle(NormalText).SetFont(DEFAULT_FONT("Regular", 9))); + Set("Menu.Label.Padding", FMargin(0.0f, 0.0f, 0.0f, 0.0f)); + Set("Menu.Label.ContentPadding", FMargin(10.0f, 2.0f)); + Set("Menu.EditableText", FEditableTextBoxStyle(NormalEditableTextBoxStyle).SetFont(DEFAULT_FONT("Regular", 9))); + Set("Menu.Keybinding", FTextBlockStyle(NormalText).SetFont(DEFAULT_FONT("Regular", 8))); + + Set("Menu.Heading", FTextBlockStyle(NormalText) + .SetFont(DEFAULT_FONT("Regular", 8)) + .SetColorAndOpacity(FLinearColor(0.4f, 0.4, 0.4f, 1.0f))); + + /* Set images for various SCheckBox states associated with menu check box items... */ + const FCheckBoxStyle BasicMenuCheckBoxStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Common/SmallCheckBox", Icon14x14)) + .SetUncheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheckBox_Hovered", Icon14x14)) + .SetUncheckedPressedImage(IMAGE_BRUSH("Common/SmallCheckBox_Hovered", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))) + .SetCheckedImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked", Icon14x14)) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked_Hovered", Icon14x14)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked_Hovered", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))) + .SetUndeterminedImage(IMAGE_BRUSH("Common/CheckBox_Undetermined", Icon14x14)) + .SetUndeterminedHoveredImage(IMAGE_BRUSH("Common/CheckBox_Undetermined_Hovered", Icon14x14)) + .SetUndeterminedPressedImage(IMAGE_BRUSH("Common/CheckBox_Undetermined_Hovered", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))); + + /* ...and add the new style */ + Set("Menu.CheckBox", BasicMenuCheckBoxStyle); + + /* Read-only checkbox that appears next to a menu item */ + /* Set images for various SCheckBox states associated with read-only menu check box items... */ + const FCheckBoxStyle BasicMenuCheckStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUncheckedHoveredImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUncheckedPressedImage(IMAGE_BRUSH("Common/SmallCheckBox_Hovered", Icon14x14)) + .SetCheckedImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetUndeterminedImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUndeterminedHoveredImage(FSlateNoResource()) + .SetUndeterminedPressedImage(FSlateNoResource()); + + /* ...and add the new style */ + Set("Menu.Check", BasicMenuCheckStyle); + + /* This radio button is actually just a check box with different images */ + /* Set images for various Menu radio button (SCheckBox) states... */ + const FCheckBoxStyle BasicMenuRadioButtonStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16)) + .SetUncheckedHoveredImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16)) + .SetUncheckedPressedImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16)) + .SetCheckedImage(IMAGE_BRUSH("Common/RadioButton_Selected_16x", Icon16x16)) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/RadioButton_Selected_16x", Icon16x16, SelectionColor)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor_Pressed)) + .SetUndeterminedImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16)) + .SetUndeterminedHoveredImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor)) + .SetUndeterminedPressedImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor_Pressed)); + + /* ...and set new style */ + Set("Menu.RadioButton", BasicMenuRadioButtonStyle); + + /* Create style for "Menu.ToggleButton" widget ... */ + const FCheckBoxStyle MenuToggleButtonCheckBoxStyle = FCheckBoxStyle() + .SetCheckBoxType(ESlateCheckBoxType::ToggleButton) + .SetUncheckedImage(FSlateNoResource()) + .SetUncheckedPressedImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)) + .SetUncheckedHoveredImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor)) + .SetCheckedImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)) + .SetCheckedHoveredImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)) + .SetCheckedPressedImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor)); + /* ... and add new style */ + Set("Menu.ToggleButton", MenuToggleButtonCheckBoxStyle); + + const FButtonStyle NoBorder = FButtonStyle() + .SetNormal(FSlateNoResource()) + .SetHovered(FSlateNoResource()) + .SetPressed(FSlateNoResource()) + .SetNormalPadding(FMargin(0, 0, 0, 1)) + .SetPressedPadding(FMargin(0, 1, 0, 0)); + + Set("Menu.Button", FButtonStyle(NoBorder) + .SetNormal(FSlateNoResource()) + .SetPressed(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)) + .SetHovered(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor)) + .SetNormalPadding(FMargin(0, 1)) + .SetPressedPadding(FMargin(0, 2, 0, 0)) + ); + + Set("Menu.Button.Checked", new BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)); + Set("Menu.Button.Checked_Hovered", new BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)); + Set("Menu.Button.Checked_Pressed", new BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor)); + + /* The style of a menu bar button when it has a sub menu open */ + Set("Menu.Button.SubMenuOpen", new BORDER_BRUSH("Common/Selection", FMargin(4.f / 16.f), FLinearColor(0.10f, 0.10f, 0.10f))); + } +} + void FSlateEditorStyle::FStyle::SetupGeneralIcons() { Set("Plus", new IMAGE_BRUSH("Icons/PlusSymbol_12x", Icon12x12)); @@ -3640,6 +3709,7 @@ void FSlateEditorStyle::FStyle::SetupPropertyEditorStyles() Set( "DetailsView.CategoryMiddle", new IMAGE_BRUSH( "PropertyView/DetailCategoryMiddle", FVector2D( 16, 16 ) ) ); Set( "DetailsView.CategoryMiddle_Hovered", new IMAGE_BRUSH( "PropertyView/DetailCategoryMiddle_Hovered", FVector2D( 16, 16 ) ) ); Set( "DetailsView.CategoryMiddle_Highlighted", new BOX_BRUSH( "Common/TextBox_Special_Active", FMargin(8.0f/32.0f) ) ); + Set( "DetailsView.CategoryMiddle_Active", new BOX_BRUSH( "Common/TextBox_Special_Active", FMargin(8.0f/32.0f), SelectionColor_Pressed ) ); Set( "DetailsView.PropertyIsFavorite", new IMAGE_BRUSH("PropertyView/Favorites_Enabled", Icon12x12)); Set( "DetailsView.PropertyIsNotFavorite", new IMAGE_BRUSH("PropertyView/Favorites_Disabled", Icon12x12)); @@ -4397,8 +4467,10 @@ void FSlateEditorStyle::FStyle::SetupGraphEditorStyles() Set( "GraphEditor.InterfaceFunction_16x", new IMAGE_BRUSH( "Icons/icon_Blueprint_Interfacefunction_16x", Icon16x16 ) ); Set( "GraphEditor.Macro_16x", new IMAGE_BRUSH( "Icons/icon_Blueprint_Macro_16x", Icon16x16 ) ); Set( "GraphEditor.Function_16x", new IMAGE_BRUSH( "Icons/icon_Blueprint_NewFunction_16x", Icon16x16 ) ); + Set( "GraphEditor.PureFunction_16x", new IMAGE_BRUSH( "Icons/icon_Blueprint_NewPureFunction_16x", Icon16x16 ) ); Set( "GraphEditor.PotentialOverrideFunction_16x", new IMAGE_BRUSH( "Icons/icon_Blueprint_OverrideableFunction_16x", Icon16x16 ) ); Set( "GraphEditor.OverrideFunction_16x", new IMAGE_BRUSH( "Icons/icon_Blueprint_OverrideFunction_16x", Icon16x16 ) ); + Set( "GraphEditor.OverridePureFunction_16x", new IMAGE_BRUSH( "Icons/icon_Blueprint_OverridePureFunction_16x", Icon16x16 ) ); Set( "GraphEditor.SubGraph_16x", new IMAGE_BRUSH( "Icons/icon_Blueprint_SubgraphComposite_16x", Icon16x16 ) ); Set( "GraphEditor.Animation_16x", new IMAGE_BRUSH( "Icons/icon_Blueprint_Anim_16x", Icon16x16 ) ); Set( "GraphEditor.Conduit_16x", new IMAGE_BRUSH( "Icons/icon_Blueprint_Conduit_16x", Icon16x16 ) ); @@ -4497,6 +4569,9 @@ void FSlateEditorStyle::FStyle::SetupGraphEditorStyles() Set( "GraphEditor.DistributeNodesHorizontally", new IMAGE_BRUSH( "Icons/GraphEditor/icon_DistributeNodesHorizontally_20px", Icon20x20 ) ); Set( "GraphEditor.DistributeNodesVertically", new IMAGE_BRUSH( "Icons/GraphEditor/icon_DistributeNodesVertically_20px", Icon20x20 ) ); + + Set( "GraphEditor.ToggleHideUnrelatedNodes", new IMAGE_BRUSH( "Icons/icon_HideUnrelatedNodes_40x", Icon40x40 ) ); + Set( "GraphEditor.ToggleHideUnrelatedNodes.Small", new IMAGE_BRUSH( "Icons/icon_HideUnrelatedNodes_40x", Icon20x20 ) ); // Graph editor widgets { @@ -6594,7 +6669,7 @@ void FSlateEditorStyle::FStyle::SetupLandscapeEditorStyle() Set("LandscapeEditor.ManageMode.Small", new IMAGE_BRUSH("Icons/icon_Landscape_Mode_Manage_20x", Icon20x20)); Set("LandscapeEditor.SculptMode.Small", new IMAGE_BRUSH("Icons/icon_Landscape_Mode_Sculpt_20x", Icon20x20)); Set("LandscapeEditor.PaintMode.Small", new IMAGE_BRUSH("Icons/icon_Landscape_Mode_Paint_20x", Icon20x20)); - + // Tools Set("LandscapeEditor.NewLandscape", new IMAGE_BRUSH("Icons/icon_Landscape_New_Landscape_40x", Icon40x40)); Set("LandscapeEditor.NewLandscape.Small", new IMAGE_BRUSH("Icons/icon_Landscape_New_Landscape_20x", Icon20x20)); @@ -6612,7 +6687,7 @@ void FSlateEditorStyle::FStyle::SetupLandscapeEditorStyle() Set("LandscapeEditor.NoiseTool", new IMAGE_BRUSH("Icons/icon_Landscape_Tool_Noise_40x", Icon40x40)); Set("LandscapeEditor.RetopologizeTool", new IMAGE_BRUSH("Icons/icon_Landscape_Tool_Retopologize_40x", Icon40x40)); Set("LandscapeEditor.VisibilityTool", new IMAGE_BRUSH("Icons/icon_Landscape_Tool_Visibility_40x", Icon40x40)); - Set("LandscapeEditor.BPCustomTool", new IMAGE_BRUSH("Icons/icon_Landscape_Tool_Visibility_40x", Icon40x40));// TODO: change icon + Set("LandscapeEditor.BlueprintBrushTool", new IMAGE_BRUSH("Icons/icon_Landscape_Tool_BlueprintBrush_40x", Icon40x40)); Set("LandscapeEditor.SculptTool.Small", new IMAGE_BRUSH("Icons/icon_Landscape_Tool_Sculpt_20x", Icon20x20)); Set("LandscapeEditor.EraseTool.Small", new IMAGE_BRUSH("Icons/icon_Landscape_Tool_Erase_20x", Icon20x20)); Set("LandscapeEditor.PaintTool.Small", new IMAGE_BRUSH("Icons/icon_Landscape_Tool_Paint_20x", Icon20x20)); @@ -6624,7 +6699,7 @@ void FSlateEditorStyle::FStyle::SetupLandscapeEditorStyle() Set("LandscapeEditor.NoiseTool.Small", new IMAGE_BRUSH("Icons/icon_Landscape_Tool_Noise_20x", Icon20x20)); Set("LandscapeEditor.RetopologizeTool.Small", new IMAGE_BRUSH("Icons/icon_Landscape_Tool_Retopologize_20x", Icon20x20)); Set("LandscapeEditor.VisibilityTool.Small", new IMAGE_BRUSH("Icons/icon_Landscape_Tool_Visibility_20x", Icon20x20)); - Set("LandscapeEditor.BPCustomTool.Small", new IMAGE_BRUSH("Icons/icon_Landscape_Tool_Visibility_20x", Icon20x20)); // TODO: change icon + Set("LandscapeEditor.BlueprintBrushTool.Small", new IMAGE_BRUSH("Icons/icon_Landscape_Tool_BlueprintBrush_20x", Icon20x20)); Set("LandscapeEditor.SelectComponentTool", new IMAGE_BRUSH("Icons/icon_Landscape_Tool_Selection_40x", Icon40x40)); Set("LandscapeEditor.AddComponentTool", new IMAGE_BRUSH("Icons/icon_Landscape_Tool_AddComponent_40x", Icon40x40)); @@ -6673,6 +6748,11 @@ void FSlateEditorStyle::FStyle::SetupLandscapeEditorStyle() Set("LandscapeEditor.Brushes.Alpha.UseBChannel", new IMAGE_BRUSH("Icons/icon_Landscape_Brush_Alpha_UseBChannel_20x", Icon20x20)); Set("LandscapeEditor.Brushes.Alpha.UseAChannel", new IMAGE_BRUSH("Icons/icon_Landscape_Brush_Alpha_UseAChannel_20x", Icon20x20)); + Set("LandscapeEditor.Brush.AffectsHeight.Enabled", new IMAGE_BRUSH("Icons/icon_Landscape_Affects_Height_Enabled_16x", Icon16x16)); + Set("LandscapeEditor.Brush.AffectsHeight.Disabled", new IMAGE_BRUSH("Icons/icon_Landscape_Affects_Height_Disabled_16x", Icon16x16)); + Set("LandscapeEditor.Brush.AffectsWeight.Enabled", new IMAGE_BRUSH("Icons/icon_Landscape_Affects_Weight_Enabled_16x", Icon16x16)); + Set("LandscapeEditor.Brush.AffectsWeight.Disabled", new IMAGE_BRUSH("Icons/icon_Landscape_Affects_Weight_Disabled_16x", Icon16x16)); + // Target List Set("LandscapeEditor.TargetList.RowBackground", new FSlateNoResource()); Set("LandscapeEditor.TargetList.RowBackgroundHovered", new BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, FLinearColor(1.0f, 1.0f, 1.0f, 0.1f))); @@ -7868,6 +7948,17 @@ void FSlateEditorStyle::FStyle::SetupUMGEditorStyles() .SetOddRowBackgroundBrush(BOX_BRUSH("PropertyView/DetailCategoryMiddle", FMargin(4 / 16.0f, 8.0f / 16.0f, 4 / 16.0f, 4 / 16.0f))) ); + // Style of the favorite toggle + const FCheckBoxStyle UMGEditorFavoriteToggleStyle = FCheckBoxStyle() + .SetCheckBoxType(ESlateCheckBoxType::CheckBox) + .SetUncheckedImage(IMAGE_BRUSH("Icons/EmptyStar_16x", Icon10x10, FLinearColor(0.8f, 0.8f, 0.8f, 1.f))) + .SetUncheckedHoveredImage(IMAGE_BRUSH("Icons/EmptyStar_16x", Icon10x10, FLinearColor(2.5f, 2.5f, 2.5f, 1.f))) + .SetUncheckedPressedImage(IMAGE_BRUSH("Icons/EmptyStar_16x", Icon10x10, FLinearColor(0.8f, 0.8f, 0.8f, 1.f))) + .SetCheckedImage(IMAGE_BRUSH("Icons/Star_16x", Icon10x10, FLinearColor(0.2f, 0.2f, 0.2f, 1.f))) + .SetCheckedHoveredImage(IMAGE_BRUSH("Icons/Star_16x", Icon10x10, FLinearColor(0.4f, 0.4f, 0.4f, 1.f))) + .SetCheckedPressedImage(IMAGE_BRUSH("Icons/Star_16x", Icon10x10, FLinearColor(0.2f, 0.2f, 0.2f, 1.f))); + Set("UMGEditor.Palette.FavoriteToggleStyle", UMGEditorFavoriteToggleStyle); + Set("HorizontalAlignment_Left", new IMAGE_BRUSH("Icons/UMG/Alignment/Horizontal_Left", Icon20x20)); Set("HorizontalAlignment_Center", new IMAGE_BRUSH("Icons/UMG/Alignment/Horizontal_Center", Icon20x20)); Set("HorizontalAlignment_Right", new IMAGE_BRUSH("Icons/UMG/Alignment/Horizontal_Right", Icon20x20)); diff --git a/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.h b/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.h index 80bec0136467..b1014cfe8f82 100644 --- a/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.h +++ b/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.h @@ -70,6 +70,12 @@ public: void Initialize(); void SetupGeneralStyles(); + void SetupLevelGeneralStyles(); + void SetupWorldBrowserStyles(); + void SetupSequencerStyles(); + void SetupViewportStyles(); + void SetupNotificationBarStyles(); + void SetupMenuBarStyles(); void SetupGeneralIcons(); void SetupWindowStyles(); void SetupProjectBadgeStyle(); diff --git a/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.cpp b/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.cpp index f606092f86b4..f6bbceb2e0df 100644 --- a/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.cpp +++ b/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.cpp @@ -157,7 +157,7 @@ public: return CurrentInfo; } - FORCEINLINE operator bool() const + FORCEINLINE explicit operator bool() const { return CurrentInfo != nullptr; } @@ -233,7 +233,6 @@ FEdModeFoliage::FEdModeFoliage() FName OpacityParamName("OpacityAmount"); BrushMID->GetScalarParameterValue(OpacityParamName, DefaultBrushOpacity); - FFoliageEditCommands::Register(); UICommandList = MakeShareable(new FUICommandList); BindCommands(); } @@ -244,12 +243,32 @@ void FEdModeFoliage::BindCommands() UICommandList->MapAction( Commands.IncreaseBrushSize, - FExecuteAction::CreateRaw(this, &FEdModeFoliage::AdjustBrushRadius, 50.f), + FExecuteAction::CreateRaw(this, &FEdModeFoliage::AdjustBrushRadius, 1.f), FCanExecuteAction::CreateRaw(this, &FEdModeFoliage::CurrentToolUsesBrush)); UICommandList->MapAction( Commands.DecreaseBrushSize, - FExecuteAction::CreateRaw(this, &FEdModeFoliage::AdjustBrushRadius, -50.f), + FExecuteAction::CreateRaw(this, &FEdModeFoliage::AdjustBrushRadius, -1.f), + FCanExecuteAction::CreateRaw(this, &FEdModeFoliage::CurrentToolUsesBrush)); + + UICommandList->MapAction( + Commands.IncreasePaintDensity, + FExecuteAction::CreateRaw(this, &FEdModeFoliage::AdjustPaintDensity, 1.f), + FCanExecuteAction::CreateRaw(this, &FEdModeFoliage::CurrentToolUsesBrush)); + + UICommandList->MapAction( + Commands.DecreasePaintDensity, + FExecuteAction::CreateRaw(this, &FEdModeFoliage::AdjustPaintDensity, -1.f), + FCanExecuteAction::CreateRaw(this, &FEdModeFoliage::CurrentToolUsesBrush)); + + UICommandList->MapAction( + Commands.IncreaseUnpaintDensity, + FExecuteAction::CreateRaw(this, &FEdModeFoliage::AdjustUnpaintDensity, 1.f), + FCanExecuteAction::CreateRaw(this, &FEdModeFoliage::CurrentToolUsesBrush)); + + UICommandList->MapAction( + Commands.DecreaseUnpaintDensity, + FExecuteAction::CreateRaw(this, &FEdModeFoliage::AdjustUnpaintDensity, -1.f), FCanExecuteAction::CreateRaw(this, &FEdModeFoliage::CurrentToolUsesBrush)); UICommandList->MapAction( @@ -1983,23 +2002,44 @@ void FEdModeFoliage::SelectInvalidInstances(const UFoliageType* Settings) } } -void FEdModeFoliage::AdjustBrushRadius(float Adjustment) +void FEdModeFoliage::AdjustBrushRadius(float Multiplier) { if (UISettings.IsInAnySingleInstantiationMode()) { return; } + const float PercentageChange = 0.05f; const float CurrentBrushRadius = UISettings.GetRadius(); - if (Adjustment > 0.f) + float NewValue = CurrentBrushRadius * (1 + PercentageChange * Multiplier); + UISettings.SetRadius(FMath::Clamp(NewValue, 0.1f, 8192.0f)); +} + +void FEdModeFoliage::AdjustPaintDensity(float Multiplier) +{ + if (UISettings.IsInAnySingleInstantiationMode()) { - UISettings.SetRadius(FMath::Min(CurrentBrushRadius + Adjustment, 8192.f)); + return; } - else if (Adjustment < 0.f) + + const float AdjustmentAmount = 0.02f; + const float CurrentDensity = UISettings.GetPaintDensity(); + + UISettings.SetPaintDensity(FMath::Clamp(CurrentDensity + AdjustmentAmount * Multiplier, 0.0f, 1.0f)); +} + +void FEdModeFoliage::AdjustUnpaintDensity(float Multiplier) +{ + if (UISettings.IsInAnySingleInstantiationMode()) { - UISettings.SetRadius(FMath::Max(CurrentBrushRadius + Adjustment, 0.f)); + return; } + + const float AdjustmentAmount = 0.02f; + const float CurrentDensity = UISettings.GetUnpaintDensity(); + + UISettings.SetUnpaintDensity(FMath::Clamp(CurrentDensity + AdjustmentAmount * Multiplier, 0.0f, 1.0f)); } void FEdModeFoliage::ReapplyInstancesForBrush(UWorld* InWorld, const UFoliageType* Settings, const FSphere& BrushSphere, float Pressure) diff --git a/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.h b/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.h index 6cd07215789c..aeb5d804df2b 100644 --- a/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.h +++ b/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.h @@ -462,8 +462,14 @@ public: /** Find and select instances that don't have valid base or 'off-ground' */ void SelectInvalidInstances(const UFoliageType* Settings); - /** Adjusts the radius of the foliage brush by the specified amount */ - void AdjustBrushRadius(float Adjustment); + /** Adjusts the radius of the foliage brush, using the given multiplier to adjust speed */ + void AdjustBrushRadius(float Multiplier); + + /** Adjusts the painting density of the foliage brush, using the given multiplier to adjust speed */ + void AdjustPaintDensity(float Multiplier); + + /** Adjusts the unpainting (erasing) density of the foliage brush, using the given multiplier to adjust speed */ + void AdjustUnpaintDensity(float Multiplier); /** Add desired instances. Uses foliage settings to determine location/scale/rotation and whether instances should be ignored */ static void AddInstances(UWorld* InWorld, const TArray& DesiredInstances, const FFoliagePaintingGeometryFilter& OverrideGeometryFilter, bool InRebuildFoliageTree = true); diff --git a/Engine/Source/Editor/FoliageEdit/Private/FoliageEditActions.cpp b/Engine/Source/Editor/FoliageEdit/Private/FoliageEditActions.cpp index 9f9370f1e790..7914e3e8ea17 100644 --- a/Engine/Source/Editor/FoliageEdit/Private/FoliageEditActions.cpp +++ b/Engine/Source/Editor/FoliageEdit/Private/FoliageEditActions.cpp @@ -8,8 +8,14 @@ void FFoliageEditCommands::RegisterCommands() { - UI_COMMAND(DecreaseBrushSize, "Decrease Brush Size", "Decreases the size of the foliage brush", EUserInterfaceActionType::Button, FInputChord(EKeys::LeftBracket)); - UI_COMMAND(IncreaseBrushSize, "Increase Brush Size", "Increases the size of the foliage brush", EUserInterfaceActionType::Button, FInputChord(EKeys::RightBracket)); + UI_COMMAND( DecreaseBrushSize, "Decrease Brush Size", "Decreases the size of the foliage brush", EUserInterfaceActionType::Button, FInputChord(EKeys::LeftBracket) ); + UI_COMMAND( IncreaseBrushSize, "Increase Brush Size", "Increases the size of the foliage brush", EUserInterfaceActionType::Button, FInputChord(EKeys::RightBracket) ); + + UI_COMMAND( DecreasePaintDensity, "Decrease Brush Density", "Decreases the density of the foliage brush", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control, EKeys::LeftBracket) ); + UI_COMMAND( IncreasePaintDensity, "Increase Brush Density", "Increases the density of the foliage brush", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control, EKeys::RightBracket) ); + + UI_COMMAND( DecreaseUnpaintDensity, "Decrease Erase Density", "Decreases the density of the foliage eraser", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control | EModifierKey::Shift, EKeys::LeftBracket)); + UI_COMMAND( IncreaseUnpaintDensity, "Increase Erase Density", "Increases the density of the foliage eraser", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control | EModifierKey::Shift, EKeys::RightBracket)); UI_COMMAND( SetPaint, "Paint", "Paint", EUserInterfaceActionType::ToggleButton, FInputChord() ); UI_COMMAND( SetReapplySettings, "Reapply", "Reapply settings to instances", EUserInterfaceActionType::ToggleButton, FInputChord() ); diff --git a/Engine/Source/Editor/FoliageEdit/Private/FoliageEditActions.h b/Engine/Source/Editor/FoliageEdit/Private/FoliageEditActions.h index 5e9f6db7414b..21cdeddaac37 100644 --- a/Engine/Source/Editor/FoliageEdit/Private/FoliageEditActions.h +++ b/Engine/Source/Editor/FoliageEdit/Private/FoliageEditActions.h @@ -31,6 +31,12 @@ public: TSharedPtr< FUICommandInfo > IncreaseBrushSize; TSharedPtr< FUICommandInfo > DecreaseBrushSize; + TSharedPtr< FUICommandInfo > IncreasePaintDensity; + TSharedPtr< FUICommandInfo > DecreasePaintDensity; + + TSharedPtr< FUICommandInfo > IncreaseUnpaintDensity; + TSharedPtr< FUICommandInfo > DecreaseUnpaintDensity; + /** Commands for the tools toolbar. */ TSharedPtr< FUICommandInfo > SetPaint; TSharedPtr< FUICommandInfo > SetReapplySettings; diff --git a/Engine/Source/Editor/FoliageEdit/Private/FoliageEditModule.cpp b/Engine/Source/Editor/FoliageEdit/Private/FoliageEditModule.cpp index b3a92a634f0a..8abff00f4a6a 100644 --- a/Engine/Source/Editor/FoliageEdit/Private/FoliageEditModule.cpp +++ b/Engine/Source/Editor/FoliageEdit/Private/FoliageEditModule.cpp @@ -19,6 +19,7 @@ const FName FoliageEditAppIdentifier = FName(TEXT("FoliageEdApp")); #include "FoliageType_Actor.h" #include "InstancedFoliageActor.h" #include "FoliageEdMode.h" +#include "FoliageEditActions.h" #include "PropertyEditorModule.h" #include "FoliageTypeDetails.h" #include "ProceduralFoliageComponent.h" @@ -52,6 +53,8 @@ public: true, 400 ); + FFoliageEditCommands::Register(); + // Register the details customizer FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); PropertyModule.RegisterCustomClassLayout("FoliageType", FOnGetDetailCustomizationInstance::CreateStatic(&FFoliageTypeDetails::MakeInstance)); @@ -76,7 +79,7 @@ public: SubscribeEvents(); #endif - + // Register thumbnail renderer UThumbnailManager::Get().RegisterCustomRenderer(UFoliageType_InstancedStaticMesh::StaticClass(), UFoliageType_ISMThumbnailRenderer::StaticClass()); UThumbnailManager::Get().RegisterCustomRenderer(UFoliageType_Actor::StaticClass(), UFoliageType_ActorThumbnailRenderer::StaticClass()); @@ -87,6 +90,8 @@ public: */ virtual void ShutdownModule() override { + FFoliageEditCommands::Unregister(); + FEditorModeRegistry::Get().UnregisterMode(FBuiltinEditorModes::EM_Foliage); if (!UObjectInitialized()) diff --git a/Engine/Source/Editor/GameProjectGeneration/Private/GameProjectUtils.cpp b/Engine/Source/Editor/GameProjectGeneration/Private/GameProjectUtils.cpp index e17252a4165e..27eefdfa2933 100644 --- a/Engine/Source/Editor/GameProjectGeneration/Private/GameProjectUtils.cpp +++ b/Engine/Source/Editor/GameProjectGeneration/Private/GameProjectUtils.cpp @@ -240,10 +240,10 @@ FString FNewClassInfo::GetClassPrefixCPP() const switch(ClassType) { case EClassType::UObject: - return BaseClass ? BaseClass->GetPrefixCPP() : TEXT(""); + return BaseClass ? BaseClass->GetPrefixCPP() : TEXT("U"); case EClassType::EmptyCpp: - return TEXT(""); + return TEXT("F"); case EClassType::SlateWidget: return TEXT("S"); @@ -824,46 +824,46 @@ void GameProjectUtils::CheckForOutOfDateGameProjectFile() if ( ProjectStatus.bRequiresUpdate ) { bRequiresUpdate = true; - } + } } // Get the current project descriptor - const FProjectDescriptor* Project = IProjectManager::Get().GetCurrentProject(); + const FProjectDescriptor* Project = IProjectManager::Get().GetCurrentProject(); // Check if there are any installed plugins that need to be added as a reference - TArray NewPluginReferences = Project->Plugins; - for(TSharedRef& Plugin: IPluginManager::Get().GetEnabledPlugins()) - { - if(Plugin->GetDescriptor().bInstalled && Project->FindPluginReferenceIndex(Plugin->GetName()) == INDEX_NONE) - { - FPluginReferenceDescriptor PluginReference(Plugin->GetName(), true); - NewPluginReferences.Add(PluginReference); + TArray NewPluginReferences = Project->Plugins; + for(TSharedRef& Plugin: IPluginManager::Get().GetEnabledPlugins()) + { + if(Plugin->GetDescriptor().bInstalled && Project->FindPluginReferenceIndex(Plugin->GetName()) == INDEX_NONE) + { + FPluginReferenceDescriptor PluginReference(Plugin->GetName(), true); + NewPluginReferences.Add(PluginReference); bRequiresUpdate = true; - } - } + } + } - // Check if there are any referenced plugins that do not have a matching supported plugins list - for(FPluginReferenceDescriptor& Reference: NewPluginReferences) + // Check if there are any referenced plugins that do not have a matching supported plugins list + for(FPluginReferenceDescriptor& Reference: NewPluginReferences) + { + if(Reference.bEnabled) + { + TSharedPtr Plugin = IPluginManager::Get().FindPlugin(Reference.Name); + if(Plugin.IsValid()) { - if(Reference.bEnabled) + const FPluginDescriptor& Descriptor = Plugin->GetDescriptor(); + if(Reference.MarketplaceURL != Descriptor.MarketplaceURL) { - TSharedPtr Plugin = IPluginManager::Get().FindPlugin(Reference.Name); - if(Plugin.IsValid()) - { - const FPluginDescriptor& Descriptor = Plugin->GetDescriptor(); - if(Reference.MarketplaceURL != Descriptor.MarketplaceURL) - { - Reference.MarketplaceURL = Descriptor.MarketplaceURL; + Reference.MarketplaceURL = Descriptor.MarketplaceURL; bRequiresUpdate = true; - } - if(Reference.SupportedTargetPlatforms != Descriptor.SupportedTargetPlatforms) - { - Reference.SupportedTargetPlatforms = Descriptor.SupportedTargetPlatforms; + } + if(Reference.SupportedTargetPlatforms != Descriptor.SupportedTargetPlatforms) + { + Reference.SupportedTargetPlatforms = Descriptor.SupportedTargetPlatforms; bRequiresUpdate = true; - } - } } } + } + } // If we have updates pending, show the prompt if (bRequiresUpdate) @@ -887,10 +887,10 @@ void GameProjectUtils::CheckForOutOfDateGameProjectFile() Info.ButtonDetails.Add(FNotificationButtonInfo(UpdateProjectCancelText, FText(), FSimpleDelegate::CreateStatic(&GameProjectUtils::OnUpdateProjectCancel))); if (UpdateGameProjectNotification.IsValid()) - { + { UpdateGameProjectNotification.Pin()->ExpireAndFadeout(); UpdateGameProjectNotification.Reset(); - } + } UpdateGameProjectNotification = FSlateNotificationManager::Get().AddNotification(Info); diff --git a/Engine/Source/Editor/GameProjectGeneration/Private/SGetSuggestedIDEWidget.cpp b/Engine/Source/Editor/GameProjectGeneration/Private/SGetSuggestedIDEWidget.cpp index 76f7481a0a12..bf01755993fe 100644 --- a/Engine/Source/Editor/GameProjectGeneration/Private/SGetSuggestedIDEWidget.cpp +++ b/Engine/Source/Editor/GameProjectGeneration/Private/SGetSuggestedIDEWidget.cpp @@ -33,14 +33,14 @@ TSharedRef SGetSuggestedIDEWidget::CreateGetSuggestedIDEWidget() const // If the installer for this platform's IDE can be downloaded and launched directly, show a button return SNew(SButton) .Text(FText::Format(LOCTEXT("IDEInstallButtonText", "Install {0}"), FSourceCodeNavigation::GetSuggestedSourceCodeIDE())) - .OnClicked(this, &SGetSuggestedIDEWidget::OnInstallIDEClicked); + .OnClicked(const_cast(this), &SGetSuggestedIDEWidget::OnInstallIDEClicked); } else { // If the user must open a web page, show a link return SNew(SHyperlink) .Text(FText::Format(LOCTEXT("IDEDownloadLinkText", "Download {0}"), FSourceCodeNavigation::GetSuggestedSourceCodeIDE())) - .OnNavigate(this, &SGetSuggestedIDEWidget::OnDownloadIDEClicked, FSourceCodeNavigation::GetSuggestedSourceCodeIDEDownloadURL()); + .OnNavigate(const_cast(this), &SGetSuggestedIDEWidget::OnDownloadIDEClicked, FSourceCodeNavigation::GetSuggestedSourceCodeIDEDownloadURL()); } } diff --git a/Engine/Source/Editor/GameProjectGeneration/Private/SProjectBrowser.cpp b/Engine/Source/Editor/GameProjectGeneration/Private/SProjectBrowser.cpp index d00d35254dcb..b7591017a59d 100644 --- a/Engine/Source/Editor/GameProjectGeneration/Private/SProjectBrowser.cpp +++ b/Engine/Source/Editor/GameProjectGeneration/Private/SProjectBrowser.cpp @@ -396,10 +396,10 @@ void SProjectBrowser::ConstructCategory( const TSharedRef& InCateg .SelectionMode(ESelectionMode::Single) .ClearSelectionOnClick(false) .AllowOverscroll(EAllowOverscroll::No) - .OnGenerateTile(this, &SProjectBrowser::MakeProjectViewWidget) + .OnGenerateTile(const_cast(this), &SProjectBrowser::MakeProjectViewWidget) .OnContextMenuOpening(this, &SProjectBrowser::OnGetContextMenuContent) - .OnMouseButtonDoubleClick(this, &SProjectBrowser::HandleProjectItemDoubleClick) - .OnSelectionChanged(this, &SProjectBrowser::HandleProjectViewSelectionChanged, Category->CategoryName) + .OnMouseButtonDoubleClick(const_cast(this), &SProjectBrowser::HandleProjectItemDoubleClick) + .OnSelectionChanged(const_cast(this), &SProjectBrowser::HandleProjectViewSelectionChanged, Category->CategoryName) .ItemHeight(ThumbnailSize + ThumbnailBorderPadding + 32) .ItemWidth(ThumbnailSize + ThumbnailBorderPadding) ]; @@ -872,10 +872,7 @@ FReply SProjectBrowser::FindProjects() TargetPlatforms.Add(PlatformInfo.PlatformInfoName); } } - TargetPlatforms.Sort([](const FName& One, const FName& Two) -> bool - { - return One < Two; - }); + TargetPlatforms.Sort(FNameLexicalLess()); const bool bIsNewProjectItem = false; TSharedRef NewProjectItem = MakeShareable( new FProjectItem(ProjectName, ProjectDescription, ProjectEngineIdentifier, bIsUpToDate, DynamicBrush, ProjectFilename, bIsNewProjectItem, TargetPlatforms, ProjectStatus.SupportsAllPlatforms() ) ); diff --git a/Engine/Source/Editor/GameplayTasksEditor/Private/K2Node_LatentGameplayTaskCall.cpp b/Engine/Source/Editor/GameplayTasksEditor/Private/K2Node_LatentGameplayTaskCall.cpp index c85336f9608f..ce3fe905068b 100644 --- a/Engine/Source/Editor/GameplayTasksEditor/Private/K2Node_LatentGameplayTaskCall.cpp +++ b/Engine/Source/Editor/GameplayTasksEditor/Private/K2Node_LatentGameplayTaskCall.cpp @@ -218,7 +218,7 @@ void UK2Node_LatentGameplayTaskCall::CreatePinsForClass(UClass* InClass) if (ClassDefaultObject && K2Schema->PinDefaultValueIsEditable(*Pin)) { FString DefaultValueAsString; - const bool bDefaultValueSet = FBlueprintEditorUtils::PropertyValueToString(Property, reinterpret_cast(ClassDefaultObject), DefaultValueAsString); + const bool bDefaultValueSet = FBlueprintEditorUtils::PropertyValueToString(Property, reinterpret_cast(ClassDefaultObject), DefaultValueAsString, this); check(bDefaultValueSet); K2Schema->SetPinAutogeneratedDefaultValue(Pin, DefaultValueAsString); } @@ -434,7 +434,7 @@ bool UK2Node_LatentGameplayTaskCall::ConnectSpawnProperties(UClass* ClassToSpawn // We don't want to generate an assignment node unless the default value // differs from the value in the CDO: FString DefaultValueAsString; - FBlueprintEditorUtils::PropertyValueToString(Property, (uint8*)ClassToSpawn->ClassDefaultObject, DefaultValueAsString); + FBlueprintEditorUtils::PropertyValueToString(Property, (uint8*)ClassToSpawn->ClassDefaultObject, DefaultValueAsString, this); if (DefaultValueAsString == SpawnVarPin->DefaultValue) { continue; diff --git a/Engine/Source/Editor/GeometryMode/Private/EditorGeometry.cpp b/Engine/Source/Editor/GeometryMode/Private/EditorGeometry.cpp index 327d01a5b917..01e7216a3f65 100644 --- a/Engine/Source/Editor/GeometryMode/Private/EditorGeometry.cpp +++ b/Engine/Source/Editor/GeometryMode/Private/EditorGeometry.cpp @@ -744,3 +744,8 @@ void FGeomObject::AddReferencedObjects( FReferenceCollector& Collector ) { Collector.AddReferencedObject( ActualBrush ); } + +FString FGeomObject::GetReferencerName() const +{ + return TEXT("FGeomObject"); +} diff --git a/Engine/Source/Editor/GeometryMode/Public/EditorGeometry.h b/Engine/Source/Editor/GeometryMode/Public/EditorGeometry.h index 530c7641723b..c0600d8e7359 100644 --- a/Engine/Source/Editor/GeometryMode/Public/EditorGeometry.h +++ b/Engine/Source/Editor/GeometryMode/Public/EditorGeometry.h @@ -304,6 +304,7 @@ public: void ForceLastSelectionIndex( int32 InLastSelectionIndex ) { LastSelectionIndex = InLastSelectionIndex; } virtual void AddReferencedObjects( FReferenceCollector& Collector ) override; + virtual FString GetReferencerName() const override; /** * Set the pivot position based on the 'highest' selected object (vertex/edge/poly) in the given selection array diff --git a/Engine/Source/Editor/GraphEditor/Private/BlueprintConnectionDrawingPolicy.cpp b/Engine/Source/Editor/GraphEditor/Private/BlueprintConnectionDrawingPolicy.cpp index 6090f326a36d..9f75cf8673eb 100644 --- a/Engine/Source/Editor/GraphEditor/Private/BlueprintConnectionDrawingPolicy.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/BlueprintConnectionDrawingPolicy.cpp @@ -563,6 +563,11 @@ void FKismetConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin } } + if ((OutputPin && OutputPin->GetOwningNode()->IsNodeUnrelated()) || (InputPin && InputPin->GetOwningNode()->IsNodeUnrelated())) + { + bWireIsOnDisabledNodeAndNotPassthru = true; + } + if (bWireIsOnDisabledNodeAndNotPassthru) { Params.WireColor *= 0.5f; @@ -574,6 +579,7 @@ void FKismetConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin { ApplyHoverDeemphasis(OutputPin, InputPin, /*inout*/ Params.WireThickness, /*inout*/ Params.WireColor); } + } void FKismetConnectionDrawingPolicy::SetIncompatiblePinDrawState(const TSharedPtr& StartPin, const TSet< TSharedRef >& VisiblePins) diff --git a/Engine/Source/Editor/GraphEditor/Private/GraphDiffControl.cpp b/Engine/Source/Editor/GraphEditor/Private/GraphDiffControl.cpp index 6184407233da..b993a1bee3a4 100644 --- a/Engine/Source/Editor/GraphEditor/Private/GraphDiffControl.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/GraphDiffControl.cpp @@ -4,6 +4,7 @@ #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphPin.h" #include "EdGraph/EdGraph.h" +#include "Kismet2/BlueprintEditorUtils.h" #define LOCTEXT_NAMESPACE "GraphDiffControl" @@ -59,19 +60,19 @@ static void DiffR_NodeRemoved( const FGraphDiffControl::FNodeDiffContext& DiffCo } /** Diff result when a node comment was changed */ -static void DiffR_NodeCommentChanged(const FGraphDiffControl::FNodeDiffContext& DiffContext, FDiffResults& Results, class UEdGraphNode* Node, class UEdGraphNode* Node2) +static void DiffR_NodeCommentChanged(const FGraphDiffControl::FNodeDiffContext& DiffContext, FDiffResults& Results, UEdGraphNode* NewNode, UEdGraphNode* OldNode) { FDiffSingleResult Diff; Diff.Diff = EDiffType::NODE_COMMENT; - Diff.Node1 = Node; - Diff.Node2 = Node2; + Diff.Node1 = OldNode; + Diff.Node2 = NewNode; // Only bother setting up the display data if we're storing the result if(Results.CanStoreResults()) { FFormatNamedArguments Args; Args.Add(TEXT("NodeType"), DiffContext.NodeTypeDisplayName); - Args.Add(TEXT("NodeTitle"), Node->GetNodeTitle(ENodeTitleType::ListView)); + Args.Add(TEXT("NodeTitle"), NewNode->GetNodeTitle(ENodeTitleType::ListView)); Diff.ToolTip = FText::Format(LOCTEXT("DIF_CommentModified", "Comment Modified {NodeType} '{NodeTitle}'"), Args); Diff.DisplayString = Diff.ToolTip; Diff.DisplayColor = FLinearColor(0.25f,0.4f,0.5f); @@ -81,19 +82,19 @@ static void DiffR_NodeCommentChanged(const FGraphDiffControl::FNodeDiffContext& } /** Diff result when a node was moved on the graph */ -static void DiffR_NodeMoved(const FGraphDiffControl::FNodeDiffContext& DiffContext, FDiffResults& Results, UEdGraphNode* Node, class UEdGraphNode* OtherNode) +static void DiffR_NodeMoved(const FGraphDiffControl::FNodeDiffContext& DiffContext, FDiffResults& Results, UEdGraphNode* NewNode, UEdGraphNode* OldNode) { FDiffSingleResult Diff; Diff.Diff = EDiffType::NODE_MOVED; - Diff.Node1 = Node; - Diff.Node2 = OtherNode; + Diff.Node1 = OldNode; + Diff.Node2 = NewNode; // Only bother setting up the display data if we're storing the result if(Results.CanStoreResults()) { FFormatNamedArguments Args; Args.Add(TEXT("NodeType"), DiffContext.NodeTypeDisplayName); - Args.Add(TEXT("NodeTitle"), Node->GetNodeTitle(ENodeTitleType::ListView)); + Args.Add(TEXT("NodeTitle"), NewNode->GetNodeTitle(ENodeTitleType::ListView)); Diff.ToolTip = FText::Format(LOCTEXT("DIF_MoveNode", "Moved {NodeType} '{NodeTitle}'"), Args); Diff.DisplayString = Diff.ToolTip; Diff.DisplayColor = FLinearColor(0.9f, 0.84f, 0.43f); @@ -103,10 +104,10 @@ static void DiffR_NodeMoved(const FGraphDiffControl::FNodeDiffContext& DiffConte } /** Diff result when a pin type was changed */ -static void DiffR_PinTypeChanged(FDiffResults& Results, UEdGraphPin* Pin2, UEdGraphPin* Pin1) +static void DiffR_PinTypeChanged(FDiffResults& Results, UEdGraphPin* NewPin, UEdGraphPin* OldPin) { - FEdGraphPinType Type1 = Pin1->PinType; - FEdGraphPinType Type2 = Pin2->PinType; + FEdGraphPinType Type1 = OldPin->PinType; + FEdGraphPinType Type2 = NewPin->PinType; FDiffSingleResult Diff; @@ -120,9 +121,9 @@ static void DiffR_PinTypeChanged(FDiffResults& Results, UEdGraphPin* Pin2, UEdGr // Only bother setting up the display data if we're storing the result if(Results.CanStoreResults()) { - Diff.ToolTip = FText::Format(LOCTEXT("DIF_PinCategoryToolTipFmt", "Pin '{0}' Category was '{1}', but is now '{2}"), FText::FromName(Pin2->PinName), FText::FromName(Pin1->PinType.PinCategory), FText::FromName(Pin2->PinType.PinCategory)); + Diff.ToolTip = FText::Format(LOCTEXT("DIF_PinCategoryToolTipFmt", "Pin '{0}' Category was '{1}', but is now '{2}"), FText::FromName(NewPin->PinName), FText::FromName(OldPin->PinType.PinCategory), FText::FromName(NewPin->PinType.PinCategory)); Diff.DisplayColor = FLinearColor(0.15f,0.53f,0.15f); - Diff.DisplayString = FText::Format(LOCTEXT("DIF_PinCategoryFmt", "Pin Category '{0}' ['{1}' -> '{2}']"), FText::FromName(Pin2->PinName), FText::FromName(Pin1->PinType.PinCategory), FText::FromName(Pin2->PinType.PinCategory)); + Diff.DisplayString = FText::Format(LOCTEXT("DIF_PinCategoryFmt", "Pin Category '{0}' ['{1}' -> '{2}']"), FText::FromName(NewPin->PinName), FText::FromName(OldPin->PinType.PinCategory), FText::FromName(NewPin->PinType.PinCategory)); } } else if(Type1.PinSubCategory != Type2.PinSubCategory) @@ -132,9 +133,9 @@ static void DiffR_PinTypeChanged(FDiffResults& Results, UEdGraphPin* Pin2, UEdGr // Only bother setting up the display data if we're storing the result if(Results.CanStoreResults()) { - Diff.ToolTip = FText::Format(LOCTEXT("DIF_PinSubCategoryToolTipFmt", "Pin '{0}' SubCategory was '{1}', but is now '{2}"), FText::FromName(Pin2->PinName), FText::FromName(Pin1->PinType.PinSubCategory), FText::FromName(Pin2->PinType.PinSubCategory)); + Diff.ToolTip = FText::Format(LOCTEXT("DIF_PinSubCategoryToolTipFmt", "Pin '{0}' SubCategory was '{1}', but is now '{2}"), FText::FromName(NewPin->PinName), FText::FromName(OldPin->PinType.PinSubCategory), FText::FromName(NewPin->PinType.PinSubCategory)); Diff.DisplayColor = FLinearColor(0.45f,0.53f,0.65f); - Diff.DisplayString = FText::Format(LOCTEXT("DIF_PinSubCategoryFmt", "Pin SubCategory '{0}' ['{1}' -> '{2}']"), FText::FromName(Pin2->PinName), FText::FromName(Pin1->PinType.PinSubCategory), FText::FromName(Pin2->PinType.PinSubCategory)); + Diff.DisplayString = FText::Format(LOCTEXT("DIF_PinSubCategoryFmt", "Pin SubCategory '{0}' ['{1}' -> '{2}']"), FText::FromName(NewPin->PinName), FText::FromName(OldPin->PinType.PinSubCategory), FText::FromName(NewPin->PinType.PinSubCategory)); } } else if(T1Obj != T2Obj && (T1Obj && T2Obj ) && @@ -148,9 +149,9 @@ static void DiffR_PinTypeChanged(FDiffResults& Results, UEdGraphPin* Pin2, UEdGr const FName Obj1 = T1Obj->GetFName(); const FName Obj2 = T2Obj->GetFName(); - Diff.ToolTip = FText::Format(LOCTEXT("DIF_PinSubCategorObjToolTipFmt", "Pin '{0}' was SubCategoryObject '{1}', but is now '{2}"), FText::FromName(Pin2->PinName), FText::FromName(Obj1), FText::FromName(Obj2)); + Diff.ToolTip = FText::Format(LOCTEXT("DIF_PinSubCategorObjToolTipFmt", "Pin '{0}' was SubCategoryObject '{1}', but is now '{2}"), FText::FromName(NewPin->PinName), FText::FromName(Obj1), FText::FromName(Obj2)); Diff.DisplayColor = FLinearColor(0.45f,0.13f,0.25f); - Diff.DisplayString = FText::Format(LOCTEXT("DIF_PinSubCategoryObjFmt", "Pin SubCategoryObject '{0}' ['{1}' -> '{2}']"), FText::FromName(Pin2->PinName), FText::FromName(Obj1), FText::FromName(Obj2)); + Diff.DisplayString = FText::Format(LOCTEXT("DIF_PinSubCategoryObjFmt", "Pin SubCategoryObject '{0}' ['{1}' -> '{2}']"), FText::FromName(NewPin->PinName), FText::FromName(Obj1), FText::FromName(Obj2)); } } else if(Type1.ContainerType != Type2.ContainerType) @@ -161,11 +162,11 @@ static void DiffR_PinTypeChanged(FDiffResults& Results, UEdGraphPin* Pin2, UEdGr // Only bother setting up the display data if we're storing the result if(Results.CanStoreResults()) { - FText IsArray1 = Pin1->PinType.IsArray() ? LOCTEXT("true", "true") : LOCTEXT("false", "false"); - FText IsArray2 = Pin2->PinType.IsArray() ? LOCTEXT("true", "true") : LOCTEXT("false", "false"); + FText IsArray1 = OldPin->PinType.IsArray() ? LOCTEXT("true", "true") : LOCTEXT("false", "false"); + FText IsArray2 = NewPin->PinType.IsArray() ? LOCTEXT("true", "true") : LOCTEXT("false", "false"); - Diff.ToolTip = FText::Format(LOCTEXT("DIF_PinIsArrayToolTipFmt", "PinType IsArray for '{0}' modified. Was '{1}', but is now '{2}"), FText::FromName(Pin2->PinName), IsArray1, IsArray2); - Diff.DisplayString = FText::Format(LOCTEXT("DIF_PinIsArrayFmt", "Pin IsArray '{0}' ['{1}' -> '{2}']"), FText::FromName(Pin2->PinName), IsArray1, IsArray2); + Diff.ToolTip = FText::Format(LOCTEXT("DIF_PinIsArrayToolTipFmt", "PinType IsArray for '{0}' modified. Was '{1}', but is now '{2}"), FText::FromName(NewPin->PinName), IsArray1, IsArray2); + Diff.DisplayString = FText::Format(LOCTEXT("DIF_PinIsArrayFmt", "Pin IsArray '{0}' ['{1}' -> '{2}']"), FText::FromName(NewPin->PinName), IsArray1, IsArray2); Diff.DisplayColor = FLinearColor(0.45f,0.33f,0.35f); } } @@ -176,42 +177,42 @@ static void DiffR_PinTypeChanged(FDiffResults& Results, UEdGraphPin* Pin2, UEdGr // Only bother setting up the display data if we're storing the result if(Results.CanStoreResults()) { - FText IsRef1 = Pin1->PinType.bIsReference ? LOCTEXT("true", "true") : LOCTEXT("false", "false"); - FText IsRef2 = Pin2->PinType.bIsReference ? LOCTEXT("true", "true") : LOCTEXT("false", "false"); + FText IsRef1 = OldPin->PinType.bIsReference ? LOCTEXT("true", "true") : LOCTEXT("false", "false"); + FText IsRef2 = NewPin->PinType.bIsReference ? LOCTEXT("true", "true") : LOCTEXT("false", "false"); - Diff.ToolTip = FText::Format(LOCTEXT("DIF_PinIsRefToolTipFmt", "PinType IsReference for '{0}' modified. Was '{1}', but is now '{2}"), FText::FromName(Pin2->PinName), IsRef1, IsRef2); + Diff.ToolTip = FText::Format(LOCTEXT("DIF_PinIsRefToolTipFmt", "PinType IsReference for '{0}' modified. Was '{1}', but is now '{2}"), FText::FromName(NewPin->PinName), IsRef1, IsRef2); Diff.DisplayColor = FLinearColor(0.25f,0.43f,0.35f); - Diff.DisplayString = FText::Format(LOCTEXT("DIF_PinIsRefFmt", "Pin IsReference '{0}' ['{1}' -> '{2}']"), FText::FromName(Pin2->PinName), IsRef1, IsRef2); + Diff.DisplayString = FText::Format(LOCTEXT("DIF_PinIsRefFmt", "Pin IsReference '{0}' ['{1}' -> '{2}']"), FText::FromName(NewPin->PinName), IsRef1, IsRef2); } } - Diff.Pin1 = Pin1; - Diff.Pin2 = Pin2; + Diff.Pin1 = OldPin; + Diff.Pin2 = NewPin; Results.Add(Diff); } /** Diff result when the # of links to a pin was changed */ -static void DiffR_PinLinkCountChanged(FDiffResults& Results, UEdGraphPin* Pin2, UEdGraphPin* Pin1) +static void DiffR_PinLinkCountChanged(FDiffResults& Results, UEdGraphPin* NewPin, UEdGraphPin* OldPin) { FDiffSingleResult Diff; - Diff.Diff = Pin2->LinkedTo.Num() > Pin1->LinkedTo.Num() ? EDiffType::PIN_LINKEDTO_NUM_INC : EDiffType::PIN_LINKEDTO_NUM_DEC; - Diff.Pin2 = Pin2; - Diff.Pin1 = Pin1; + Diff.Diff = NewPin->LinkedTo.Num() > OldPin->LinkedTo.Num() ? EDiffType::PIN_LINKEDTO_NUM_INC : EDiffType::PIN_LINKEDTO_NUM_DEC; + Diff.Pin2 = NewPin; + Diff.Pin1 = OldPin; // Only bother setting up the display data if we're storing the result if(Results.CanStoreResults()) { if(Diff.Diff == EDiffType::PIN_LINKEDTO_NUM_INC) { - Diff.ToolTip = FText::Format(LOCTEXT("DIF_PinLinkCountIncToolTipFmt", "Pin '{0}' has more links (was {1} now {2})"), FText::FromName(Pin1->PinName), FText::AsNumber(Pin1->LinkedTo.Num()), FText::AsNumber(Pin2->LinkedTo.Num())); + Diff.ToolTip = FText::Format(LOCTEXT("DIF_PinLinkCountIncToolTipFmt", "Pin '{0}' has more links (was {1} now {2})"), FText::FromName(OldPin->PinName), FText::AsNumber(OldPin->LinkedTo.Num()), FText::AsNumber(NewPin->LinkedTo.Num())); Diff.DisplayColor = FLinearColor(0.5f,0.3f,0.85f); - Diff.DisplayString = FText::Format(LOCTEXT("DIF_PinLinkCountIncFmt", "Added Link to '{0}'"), FText::FromName(Pin1->PinName)); + Diff.DisplayString = FText::Format(LOCTEXT("DIF_PinLinkCountIncFmt", "Added Link to '{0}'"), FText::FromName(OldPin->PinName)); } else { - Diff.ToolTip = FText::Format(LOCTEXT("DIF_PinLinkCountDecToolTipFmt", "Pin '{0}' has fewer links (was {1} now {2})"), FText::FromName(Pin1->PinName), FText::AsNumber(Pin1->LinkedTo.Num()), FText::AsNumber(Pin2->LinkedTo.Num())); + Diff.ToolTip = FText::Format(LOCTEXT("DIF_PinLinkCountDecToolTipFmt", "Pin '{0}' has fewer links (was {1} now {2})"), FText::FromName(OldPin->PinName), FText::AsNumber(OldPin->LinkedTo.Num()), FText::AsNumber(NewPin->LinkedTo.Num())); Diff.DisplayColor = FLinearColor(0.5f,0.3f,0.85f); - Diff.DisplayString = FText::Format(LOCTEXT("DIF_PinLinkCountDecFmt", "Removed Link to '{0}'"), FText::FromName(Pin1->PinName)); + Diff.DisplayString = FText::Format(LOCTEXT("DIF_PinLinkCountDecFmt", "Removed Link to '{0}'"), FText::FromName(OldPin->PinName)); } } @@ -219,23 +220,23 @@ static void DiffR_PinLinkCountChanged(FDiffResults& Results, UEdGraphPin* Pin2, } /** Diff result when a pin to relinked to a different node */ -static void DiffR_LinkedToNode(FDiffResults& Results, UEdGraphPin* Pin1, UEdGraphPin* Pin2, UEdGraphNode* Node1, UEdGraphNode* Node2) +static void DiffR_LinkedToNode(FDiffResults& Results, UEdGraphPin* OldPin, UEdGraphPin* NewPin, UEdGraphNode* OldNode, UEdGraphNode* NewNode) { FDiffSingleResult Diff; Diff.Diff = EDiffType::PIN_LINKEDTO_NODE; - Diff.Pin2 = Pin2; - Diff.Pin1 = Pin1; - Diff.Node1 = Node1; - Diff.Node2 = Node2; + Diff.Pin1 = OldPin; + Diff.Pin2 = NewPin; + Diff.Node1 = OldNode; + Diff.Node2 = NewNode; // Only bother setting up the display data if we're storing the result if(Results.CanStoreResults()) { - FText Node1Name = Node1->GetNodeTitle(ENodeTitleType::ListView); - FText Node2Name = Node2->GetNodeTitle(ENodeTitleType::ListView); + FText Node1Name = OldNode->GetNodeTitle(ENodeTitleType::ListView); + FText Node2Name = NewNode->GetNodeTitle(ENodeTitleType::ListView); FFormatNamedArguments Args; - Args.Add(TEXT("PinNameForNode1"), FText::FromName(Pin1->PinName)); + Args.Add(TEXT("PinNameForNode1"), FText::FromName(OldPin->PinName)); Args.Add(TEXT("NodeName1"), Node1Name); Args.Add(TEXT("NodeName2"), Node2Name); Diff.ToolTip = FText::Format(LOCTEXT("DIF_PinLinkMovedToolTip", "Pin '{PinNameForNode1}' was linked to Node '{NodeName1}', but is now linked to Node '{NodeName2}'"), Args); @@ -248,20 +249,20 @@ static void DiffR_LinkedToNode(FDiffResults& Results, UEdGraphPin* Pin1, UEdGrap } /** Diff result when a pin default value was changed, and is in use*/ -static void DiffR_PinDefaultValueChanged(FDiffResults& Results, UEdGraphPin* Pin2, UEdGraphPin* Pin1) +static void DiffR_PinDefaultValueChanged(FDiffResults& Results, UEdGraphPin* NewPin, UEdGraphPin* OldPin) { FDiffSingleResult Diff; Diff.Diff = EDiffType::PIN_DEFAULT_VALUE; - Diff.Pin1 = Pin1; - Diff.Pin2 = Pin2; + Diff.Pin1 = OldPin; + Diff.Pin2 = NewPin; // Only bother setting up the display data if we're storing the result if(Results.CanStoreResults()) { FFormatNamedArguments Args; - Args.Add(TEXT("PinNameForValue1"), FText::FromName(Pin2->PinName)); - Args.Add(TEXT("PinValue1"), FText::FromString(Pin1->GetDefaultAsString())); - Args.Add(TEXT("PinValue2"), FText::FromString(Pin2->GetDefaultAsString())); + Args.Add(TEXT("PinNameForValue1"), FText::FromName(NewPin->PinName)); + Args.Add(TEXT("PinValue1"), FText::FromString(OldPin->GetDefaultAsString())); + Args.Add(TEXT("PinValue2"), FText::FromString(NewPin->GetDefaultAsString())); Diff.ToolTip = FText::Format(LOCTEXT("DIF_PinDefaultValueToolTip", "Pin '{PinNameForValue1}' Default Value was '{PinValue1}', but is now '{PinValue2}"), Args); Diff.DisplayColor = FLinearColor(0.665f,0.13f,0.455f); Diff.DisplayString = FText::Format(LOCTEXT("DIF_PinDefaultValue", "Pin Default '{PinNameForValue1}' '{PinValue1}' -> '{PinValue2}']"), Args); @@ -270,24 +271,16 @@ static void DiffR_PinDefaultValueChanged(FDiffResults& Results, UEdGraphPin* Pin Results.Add(Diff); } -/** - * Diff result when pin count is not the same. - * - * @param Result Struct to receive result. - * @param Node Node 1 to which the pins relate. - * @param Node2 Node 2 to which the pins relate. - * @param Pins1 Array of relevant pins on Node 1. - * @param Pins2 Array of relevant pins on Node 2. - */ -static void DiffR_NodePinCount(FDiffResults& Results, UEdGraphNode* Node, UEdGraphNode* Node2, const TArray &Pins1, const TArray &Pins2) +/** Diff result when pin count is not the same */ +static void DiffR_NodePinCount(FDiffResults& Results, UEdGraphNode* NewNode, UEdGraphNode* OldNode, const TArray& NewPins, const TArray& OldPins) { - FText NodeName = Node->GetNodeTitle(ENodeTitleType::ListView); - int32 OriginalCount = Pins2.Num(); - int32 NewCount = Pins1.Num(); + FText NodeName = NewNode->GetNodeTitle(ENodeTitleType::ListView); + int32 OriginalCount = OldPins.Num(); + int32 NewCount = NewPins.Num(); FDiffSingleResult Diff; Diff.Diff = EDiffType::NODE_PIN_COUNT; - Diff.Node1 = Node; - Diff.Node2 = Node2; + Diff.Node1 = OldNode; + Diff.Node2 = NewNode; // Only bother setting up the display data if we're storing the result if(Results.CanStoreResults()) @@ -315,22 +308,23 @@ static void DiffR_NodePinCount(FDiffResults& Results, UEdGraphNode* Node, UEdGra FText ListOfPins; TArray< FText > RemovedPins; - for (UEdGraphPin* OldPin : Pins1) + TArray< FText > AddedPins; + + for (UEdGraphPin* SearchPin : OldPins) { - const UEdGraphPin* const* FoundPin = Pins2.FindByPredicate(FMatchName(OldPin->PinName)); + const UEdGraphPin* const* FoundPin = NewPins.FindByPredicate(FMatchName(SearchPin->PinName)); if (FoundPin == nullptr) { - RemovedPins.Add(OldPin->GetDisplayName()); + RemovedPins.Add(SearchPin->GetDisplayName()); } } - TArray< FText > AddedPins; - for (UEdGraphPin* OldPin : Pins2) + for (UEdGraphPin* SearchPin : NewPins) { - const UEdGraphPin* const* FoundPin = Pins1.FindByPredicate(FMatchName(OldPin->PinName)); + const UEdGraphPin* const* FoundPin = OldPins.FindByPredicate(FMatchName(SearchPin->PinName)); if (FoundPin == nullptr) { - AddedPins.Add(OldPin->GetDisplayName()); + AddedPins.Add(SearchPin->GetDisplayName()); } } @@ -397,7 +391,7 @@ static void BuildArrayOfRelevantPins(const TArray& InPins, TArray< for(int32 i = 0;ibHidden == false ) { @@ -424,10 +418,10 @@ static bool IsPinTypeDifferent(const FEdGraphPinType& T1, const FEdGraphPinType& return bIsDifferent; } -/** Find Pin in array that matches pin */ +/** Find linked pin in array that matches pin */ static UEdGraphPin* FindOtherLink(TArray& Links2, int32 OriginalIndex, UEdGraphPin* PinToFind) { - //sometimes the order of the pins is different between revisions, although the pins themselves are unchanged, so we have to look at all of them + // Sometimes the order of the pins is different between revisions, although the pins themselves are unchanged, so we have to look at all of them UEdGraphNode* Node1 = PinToFind->GetOwningNode(); for (UEdGraphPin* Other : Links2) { @@ -441,20 +435,20 @@ static UEdGraphPin* FindOtherLink(TArray& Links2, int32 OriginalIn } /** Determine if the LinkedTo pins are the same */ -static bool LinkedToDifferent(UEdGraphPin* OriginalPin1, UEdGraphPin* OriginalPin2, const TArray& Links1, TArray& Links2, FDiffResults& Results) +static bool LinkedToDifferent(UEdGraphPin* OldPin, UEdGraphPin* NewPin, const TArray& OldLinks, TArray& NewLinks, FDiffResults& Results) { - const int32 Size = Links1.Num(); + const int32 Size = OldLinks.Num(); bool bHasResult = false; for(int32 i = 0;iGetOwningNode(); - UEdGraphNode* Node2 = Pin2->GetOwningNode(); - if(!FGraphDiffControl::IsNodeMatch(Node1, Node2)) + UEdGraphNode* OldNode = OldLinkedPin->GetOwningNode(); + UEdGraphNode* NewNode = NewLinkedPin->GetOwningNode(); + if(!FGraphDiffControl::IsNodeMatch(OldNode, NewNode)) { - DiffR_LinkedToNode(Results, OriginalPin1, OriginalPin2, Node1, Node2); + DiffR_LinkedToNode(Results, OldPin, NewPin, OldNode, NewNode); KEEP_GOING_IF_RESULTS() } } @@ -464,39 +458,41 @@ static bool LinkedToDifferent(UEdGraphPin* OriginalPin1, UEdGraphPin* OriginalPi /** * Determine of two Arrays of Pins are different. * - * @param Pins1 First set of pins to compare. - * @param Pins2 Second set of pins to compare. + * @param OldPins First set of pins to compare. + * @param NewPins Second set of pins to compare. * @param Results Difference results. * * returns true if any pins are different and populates the Results array */ -static bool ArePinsDifferent(const TArray& Pins1, TArray& Pins2, FDiffResults& Results) +static bool ArePinsDifferent(const TArray& OldPins, TArray& NewPins, FDiffResults& Results) { - const int32 Size = Pins1.Num(); + const int32 Size = OldPins.Num(); bool bHasResult = false; for(int32 i = 0;iPinType, Pin2->PinType)) + const UEdGraphSchema* Schema = OldPin->GetSchema(); + + if(IsPinTypeDifferent(OldPin->PinType, NewPin->PinType)) { - DiffR_PinTypeChanged(Results, Pin2, Pin1); + DiffR_PinTypeChanged(Results, NewPin, OldPin); KEEP_GOING_IF_RESULTS() } - if(Pin1->LinkedTo.Num() != Pin2->LinkedTo.Num()) + if(OldPin->LinkedTo.Num() != NewPin->LinkedTo.Num()) { - DiffR_PinLinkCountChanged(Results, Pin2, Pin1); + DiffR_PinLinkCountChanged(Results, NewPin, OldPin); KEEP_GOING_IF_RESULTS() } - else if(LinkedToDifferent(Pin1, Pin2,Pin1->LinkedTo, Pin2->LinkedTo, Results)) + else if(LinkedToDifferent(OldPin, NewPin, OldPin->LinkedTo, NewPin->LinkedTo, Results)) { KEEP_GOING_IF_RESULTS() } - if(Pin2->LinkedTo.Num() == 0 && (Pin2->GetDefaultAsString() != Pin1->GetDefaultAsString())) + if(NewPin->LinkedTo.Num() == 0 && Schema && !Schema->DoesDefaultValueMatch(*OldPin, NewPin->GetDefaultAsString())) { - DiffR_PinDefaultValueChanged(Results, Pin2, Pin1); //note: some issues with how floating point is stored as string format(0.0 vs 0.00) can cause false diffs + DiffR_PinDefaultValueChanged(Results, NewPin, OldPin); //note: some issues with how floating point is stored as string format(0.0 vs 0.00) can cause false diffs KEEP_GOING_IF_RESULTS() } } @@ -509,10 +505,10 @@ static bool ArePinsDifferent(const TArray& Pins1, TArray* OptionalDiffsArray /* = NULL*/) const +bool FGraphDiffControl::FNodeMatch::Diff(const FNodeDiffContext& DiffContext, TArray* OptionalDiffsArray /* = nullptr*/) const { FDiffResults DiffsOut(OptionalDiffsArray); return Diff(DiffContext, DiffsOut); @@ -567,7 +563,7 @@ bool FGraphDiffControl::FNodeMatch::Diff(const FNodeDiffContext& DiffContext, FD } } else if(DiffContext.DiffFlags & EDiffFlags::NodeExistance) - // one of the nodes is NULL + // one of the nodes is nullptr { bIsDifferent = true; switch (DiffContext.DiffMode) @@ -592,20 +588,29 @@ bool FGraphDiffControl::FNodeMatch::Diff(const FNodeDiffContext& DiffContext, FD * FGraphDiffControl *******************************************************************************/ -FGraphDiffControl::FNodeMatch FGraphDiffControl::FindNodeMatch(class UEdGraph* Graph, class UEdGraphNode* Node, TArray const& PriorMatches) +FGraphDiffControl::FNodeMatch FGraphDiffControl::FindNodeMatch(UEdGraph* OldGraph, UEdGraphNode* NewNode, TArray const& PriorMatches) { FNodeMatch Match; - Match.NewNode = Node; + Match.NewNode = NewNode; - if (Graph) + if (OldGraph) { - // attempt to find a node matching 'Node' - for (UEdGraphNode* GraphNode : Graph->Nodes) + // Attempt to find a node matching 'NewNode', first try exact then try soft match + for (UEdGraphNode* GraphNode : OldGraph->Nodes) { - if (GraphNode && IsNodeMatch(Node, GraphNode, &PriorMatches)) + if (GraphNode && IsNodeMatch(NewNode, GraphNode, true, &PriorMatches)) { Match.OldNode = GraphNode; - break; + return Match; + } + } + + for (UEdGraphNode* GraphNode : OldGraph->Nodes) + { + if (GraphNode && IsNodeMatch(NewNode, GraphNode, false, &PriorMatches)) + { + Match.OldNode = GraphNode; + return Match; } } } @@ -613,7 +618,7 @@ FGraphDiffControl::FNodeMatch FGraphDiffControl::FindNodeMatch(class UEdGraph* G return Match; } -bool FGraphDiffControl::IsNodeMatch(class UEdGraphNode* Node1, class UEdGraphNode* Node2, TArray const* Exclusions) +bool FGraphDiffControl::IsNodeMatch(UEdGraphNode* Node1, UEdGraphNode* Node2, bool bExactOnly, TArray const* Exclusions) { if(Node2->GetClass() != Node1->GetClass()) { @@ -625,19 +630,14 @@ bool FGraphDiffControl::IsNodeMatch(class UEdGraphNode* Node1, class UEdGraphNod return true; } - // we could be diffing two completely separate assets, this makes sure both - // nodes historically belong to the same graph - bool bIsIntraAssetDiff = (Node1->GetGraph()->GraphGuid == Node2->GetGraph()->GraphGuid); - - // if both nodes are from the same graph - if (bIsIntraAssetDiff) + if (bExactOnly) { - return (Node1->GetFName() == Node2->GetFName()); + return false; } if (Exclusions) { - // have to see if this node has already been matched with another + // Have to see if this node has already been matched with another, if so don't allow a soft match for (const FGraphDiffControl::FNodeMatch& PriorMatch : *Exclusions) { if (!PriorMatch.IsValid()) @@ -656,8 +656,7 @@ bool FGraphDiffControl::IsNodeMatch(class UEdGraphNode* Node1, class UEdGraphNod } } - // the name hashes won't match for nodes from separate graph assets, so we - // need to look for some kind of semblance between the two... title? (what's displayed to the user) + // For a soft match use the node title, which includes the function name and target usually FText Title1 = Node1->GetNodeTitle(ENodeTitleType::FullTitle); FText Title2 = Node2->GetNodeTitle(ENodeTitleType::FullTitle); @@ -707,19 +706,40 @@ bool FGraphDiffControl::DiffGraphs(UEdGraph* const LhsGraph, UEdGraph* const Rhs continue; } - FGraphDiffControl::FNodeMatch NodeMatch = FGraphDiffControl::FindNodeMatch(RhsGraph, LhsNode, NodeMatches); + // There can't be a matching node in RhsGraph because it would have been found above + FGraphDiffControl::FNodeMatch NodeMatch; + NodeMatch.NewNode = LhsNode; + bFoundDifferences |= NodeMatch.Diff(SubtractiveDiffContext, &DiffsOut); } } // storing the graph name for all diff entries: - const FName GraphName = LhsGraph ? LhsGraph->GetFName() : RhsGraph->GetFName(); + const FString GraphPath = LhsGraph ? GetGraphPath(LhsGraph) : GetGraphPath(RhsGraph); for( FDiffSingleResult& Entry : DiffsOut ) { - Entry.OwningGraph = GraphName; + Entry.OwningObjectPath = GraphPath; } return bFoundDifferences; } +FString FGraphDiffControl::GetGraphPath(UEdGraph* Graph) +{ + FString GraphPath; + if (UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph)) + { + return Graph->GetPathName(Blueprint); + } + else if (UPackage* Package = Graph->GetOutermost()) + { + return Graph->GetPathName(Package); + } + else if (Graph) + { + return Graph->GetName(); + } + return FString(); +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/GraphEditor/Private/KismetNodes/SGraphNodeK2Base.cpp b/Engine/Source/Editor/GraphEditor/Private/KismetNodes/SGraphNodeK2Base.cpp index 771c530443a4..6ce3660ab71c 100644 --- a/Engine/Source/Editor/GraphEditor/Private/KismetNodes/SGraphNodeK2Base.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/KismetNodes/SGraphNodeK2Base.cpp @@ -108,10 +108,26 @@ void SGraphNodeK2Base::UpdateCompactNode() ] ]; + // Default to "pure" styling, where we can just center the pins vertically + // since don't need to worry about alignment with other nodes + float PinPaddingTop = 0.f; + EVerticalAlignment PinVerticalAlignment = VAlign_Center; + + // But if this is an impure node, we'll align the pins to the top, + // and add some padding so that the exec pins line up with the exec pins of other nodes + if (UK2Node* K2Node = Cast(GraphNode)) + { + if (!K2Node->IsNodePure()) + { + PinPaddingTop += 8.0f; + PinVerticalAlignment = VAlign_Top; + } + } + NodeOverlay->AddSlot() .HAlign(HAlign_Left) - .VAlign(VAlign_Center) - .Padding(0.f, 0.f, 55.f, 0.f) + .VAlign(PinVerticalAlignment) + .Padding(0.f, PinPaddingTop, 55.f, 0.f) [ // LEFT SAssignNew(LeftNodeBox, SVerticalBox) @@ -119,8 +135,8 @@ void SGraphNodeK2Base::UpdateCompactNode() NodeOverlay->AddSlot() .HAlign(HAlign_Right) - .VAlign(VAlign_Center) - .Padding(55.f, 0.f, 0.f, 0.f) + .VAlign(PinVerticalAlignment) + .Padding(55.f, PinPaddingTop, 0.f, 0.f) [ // RIGHT SAssignNew(RightNodeBox, SVerticalBox) @@ -136,10 +152,8 @@ void SGraphNodeK2Base::UpdateCompactNode() // |_______|______|_______| // this->ContentScale.Bind( this, &SGraphNode::GetContentScale ); - this->GetOrAddSlot( ENodeZone::Center ) - .HAlign(HAlign_Center) - .VAlign(VAlign_Center) - [ + + TSharedRef InnerVerticalBox = SNew(SVerticalBox) +SVerticalBox::Slot() [ @@ -160,13 +174,33 @@ void SGraphNodeK2Base::UpdateCompactNode() [ NodeOverlay ] - ] - +SVerticalBox::Slot() + ]; + + TSharedPtr EnabledStateWidget = GetEnabledStateWidget(); + if (EnabledStateWidget.IsValid()) + { + InnerVerticalBox->AddSlot() + .AutoHeight() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Top) + .Padding(FMargin(2, 0)) + [ + EnabledStateWidget.ToSharedRef() + ]; + } + + InnerVerticalBox->AddSlot() .AutoHeight() .Padding( FMargin(5.0f, 1.0f) ) [ ErrorReporting->AsWidget() - ] + ]; + + this->GetOrAddSlot( ENodeZone::Center ) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + InnerVerticalBox ]; CreatePinWidgets(); diff --git a/Engine/Source/Editor/GraphEditor/Private/KismetPins/SGraphPinKey.cpp b/Engine/Source/Editor/GraphEditor/Private/KismetPins/SGraphPinKey.cpp index d2cbffdaa550..8d24cca814ae 100644 --- a/Engine/Source/Editor/GraphEditor/Private/KismetPins/SGraphPinKey.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/KismetPins/SGraphPinKey.cpp @@ -7,25 +7,27 @@ void SGraphPinKey::Construct(const FArguments& InArgs, UEdGraphPin* InGraphPinObj) { - - - TArray KeyList; - EKeys::GetAllKeys(KeyList); - SelectedKey = FKey(*InGraphPinObj->GetDefaultAsString()); + InGraphPinObj->AutogeneratedDefaultValue = "None"; + if (SelectedKey.GetFName() == NAME_None) + { + InGraphPinObj->GetSchema()->ResetPinToAutogeneratedDefaultValue(InGraphPinObj, false); + SelectedKey = FKey(*InGraphPinObj->GetDefaultAsString()); + } + if (InGraphPinObj->Direction == EEdGraphPinDirection::EGPD_Input) { // None is a valid key if (SelectedKey.GetFName() == NAME_None) { SelectedKey = EKeys::Invalid; - InGraphPinObj->AutogeneratedDefaultValue = "None"; - InGraphPinObj->GetSchema()->ResetPinToAutogeneratedDefaultValue(InGraphPinObj, false); } else if (!SelectedKey.IsValid()) { // Ensure first valid key is always set by default + TArray KeyList; + EKeys::GetAllKeys(KeyList); SelectedKey = KeyList[0]; InGraphPinObj->GetSchema()->TrySetDefaultValue(*InGraphPinObj, SelectedKey.ToString()); } diff --git a/Engine/Source/Editor/GraphEditor/Private/MaterialGraphConnectionDrawingPolicy.cpp b/Engine/Source/Editor/GraphEditor/Private/MaterialGraphConnectionDrawingPolicy.cpp index 2fb78403b691..25c97586c498 100644 --- a/Engine/Source/Editor/GraphEditor/Private/MaterialGraphConnectionDrawingPolicy.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/MaterialGraphConnectionDrawingPolicy.cpp @@ -125,7 +125,7 @@ void FMaterialGraphConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* Ou Params.StartDirection = EGPD_Input; } } - else if (!OutputNode->IsNodeEnabled() || OutputNode->IsDisplayAsDisabledForced()) + else if (!OutputNode->IsNodeEnabled() || OutputNode->IsDisplayAsDisabledForced() || OutputNode->IsNodeUnrelated()) { Params.WireColor = MaterialGraphSchema->InactivePinColor; } @@ -145,7 +145,7 @@ void FMaterialGraphConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* Ou Params.EndDirection = EGPD_Output; } } - else if (!InputNode->IsNodeEnabled() || InputNode->IsDisplayAsDisabledForced()) + else if (!InputNode->IsNodeEnabled() || InputNode->IsDisplayAsDisabledForced() || InputNode->IsNodeUnrelated()) { Params.WireColor = MaterialGraphSchema->InactivePinColor; } diff --git a/Engine/Source/Editor/GraphEditor/Private/MaterialNodes/SGraphNodeMaterialBase.cpp b/Engine/Source/Editor/GraphEditor/Private/MaterialNodes/SGraphNodeMaterialBase.cpp index b46fcdff2288..044be9436061 100644 --- a/Engine/Source/Editor/GraphEditor/Private/MaterialNodes/SGraphNodeMaterialBase.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/MaterialNodes/SGraphNodeMaterialBase.cpp @@ -453,6 +453,7 @@ TSharedRef SGraphNodeMaterialBase::CreatePreviewWidget() TSharedPtr ViewportWidget = SNew( SViewport ) + .RenderDirectlyToWindow(true) .EnableGammaCorrection(false); PreviewViewport = MakeShareable(new FPreviewViewport(MaterialNode)); diff --git a/Engine/Source/Editor/GraphEditor/Private/SCommentBubble.cpp b/Engine/Source/Editor/GraphEditor/Private/SCommentBubble.cpp index 80f1959d616b..5b36d7d7bbc2 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SCommentBubble.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SCommentBubble.cpp @@ -387,7 +387,7 @@ FSlateColor SCommentBubble::GetBubbleColor() const { FLinearColor ReturnColor = ColorAndOpacity.Get().GetSpecifiedColor(); - if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced()) + if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || GraphNode->IsNodeUnrelated()) { ReturnColor.A *= 0.6f; } diff --git a/Engine/Source/Editor/GraphEditor/Private/SGraphActionMenu.cpp b/Engine/Source/Editor/GraphEditor/Private/SGraphActionMenu.cpp index 1fca25311b18..91400466936d 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SGraphActionMenu.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SGraphActionMenu.cpp @@ -1358,6 +1358,11 @@ void SGraphActionMenu::AddReferencedObjects( FReferenceCollector& Collector ) } } +FString SGraphActionMenu::GetReferencerName() const +{ + return TEXT("SGraphActionMenu"); +} + bool SGraphActionMenu::HandleSelection( TSharedPtr< FGraphActionNode > &InSelectedItem, ESelectInfo::Type InSelectionType ) { bool bResult = false; diff --git a/Engine/Source/Editor/GraphEditor/Private/SGraphEditorImpl.cpp b/Engine/Source/Editor/GraphEditor/Private/SGraphEditorImpl.cpp index 1b62532f75d8..98ab3c381907 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SGraphEditorImpl.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SGraphEditorImpl.cpp @@ -287,6 +287,7 @@ void SGraphEditorImpl::Construct( const FArguments& InArgs ) OnFocused = InArgs._GraphEvents.OnFocused; OnCreateActionMenu = InArgs._GraphEvents.OnCreateActionMenu; + OnCreateNodeOrPinMenu = InArgs._GraphEvents.OnCreateNodeOrPinMenu; struct Local { @@ -531,9 +532,16 @@ FActionMenuContent SGraphEditorImpl::GraphEd_OnGetContextMenuFor(const FGraphCon // Show the menu for the pin or node under the cursor const bool bShouldCloseAfterAction = true; FMenuBuilder MenuBuilder( bShouldCloseAfterAction, this->Commands, MenuExtender ); - Schema->GetContextMenuActions(EdGraphObj, SpawnInfo.GraphNode, SpawnInfo.GraphPin, &MenuBuilder, !IsEditable.Get()); - Result = FActionMenuContent(MenuBuilder.MakeWidget()); + if (OnCreateNodeOrPinMenu.IsBound()) + { + Result = OnCreateNodeOrPinMenu.Execute(EdGraphObj, SpawnInfo.GraphNode, SpawnInfo.GraphPin, &MenuBuilder, !IsEditable.Get()); + } + else + { + Schema->GetContextMenuActions(EdGraphObj, SpawnInfo.GraphNode, SpawnInfo.GraphPin, &MenuBuilder, !IsEditable.Get()); + Result = FActionMenuContent(MenuBuilder.MakeWidget()); + } } else if (IsEditable.Get()) { @@ -727,7 +735,7 @@ void SGraphEditorImpl::UnlockFromGraphEditor( TWeakPtr Other ) { check(Other.IsValid()); int idx = LockedGraphs.Find(Other); - if( ensureMsgf(idx != INDEX_NONE, TEXT("Attempted to unlock graphs that were not locked together: %s %s"), *GetReadableLocation(), *(Other.Pin()->GetReadableLocation()) ) ) + if (idx != INDEX_NONE) { LockedGraphs.RemoveAtSwap(idx); } diff --git a/Engine/Source/Editor/GraphEditor/Private/SGraphEditorImpl.h b/Engine/Source/Editor/GraphEditor/Private/SGraphEditorImpl.h index f7e532cf3577..944187abd5ce 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SGraphEditorImpl.h +++ b/Engine/Source/Editor/GraphEditor/Private/SGraphEditorImpl.h @@ -195,6 +195,7 @@ private: SGraphEditor::FOnFocused OnFocused; SGraphEditor::FOnCreateActionMenu OnCreateActionMenu; + SGraphEditor::FOnCreateNodeOrPinMenu OnCreateNodeOrPinMenu; TAttribute IsEditable; diff --git a/Engine/Source/Editor/GraphEditor/Private/SGraphNode.cpp b/Engine/Source/Editor/GraphEditor/Private/SGraphNode.cpp index e07ccf2cc08c..e62d43784820 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SGraphNode.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SGraphNode.cpp @@ -563,7 +563,7 @@ FSlateColor SGraphNode::GetNodeTitleColor() const { FLinearColor ReturnTitleColor = GraphNode->IsDeprecated() ? FLinearColor::Red : GetNodeObj()->GetNodeTitleColor(); - if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced()) + if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || GraphNode->IsNodeUnrelated()) { ReturnTitleColor *= FLinearColor(0.5f, 0.5f, 0.5f, 0.4f); } @@ -577,7 +577,7 @@ FSlateColor SGraphNode::GetNodeTitleColor() const FSlateColor SGraphNode::GetNodeBodyColor() const { FLinearColor ReturnBodyColor = GraphNode->GetNodeBodyTintColor(); - if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced()) + if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || GraphNode->IsNodeUnrelated()) { ReturnBodyColor *= FLinearColor(1.0f, 1.0f, 1.0f, 0.5f); } @@ -592,7 +592,7 @@ const FSlateBrush * SGraphNode::GetNodeBodyBrush() const FSlateColor SGraphNode::GetNodeTitleIconColor() const { FLinearColor ReturnIconColor = IconColor; - if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced()) + if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || GraphNode->IsNodeUnrelated()) { ReturnIconColor *= FLinearColor(1.0f, 1.0f, 1.0f, 0.3f); } @@ -602,7 +602,7 @@ FSlateColor SGraphNode::GetNodeTitleIconColor() const FLinearColor SGraphNode::GetNodeTitleTextColor() const { FLinearColor ReturnTextColor = FLinearColor::White; - if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced()) + if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || GraphNode->IsNodeUnrelated()) { ReturnTextColor *= FLinearColor(1.0f, 1.0f, 1.0f, 0.3f); } @@ -879,33 +879,16 @@ void SGraphNode::UpdateGraphNode() CreateNodeContentArea() ]; - if ((GraphNode->GetDesiredEnabledState() != ENodeEnabledState::Enabled) && !GraphNode->IsAutomaticallyPlacedGhostNode()) + TSharedPtr EnabledStateWidget = GetEnabledStateWidget(); + if (EnabledStateWidget.IsValid()) { - const bool bDevelopmentOnly = GraphNode->GetDesiredEnabledState() == ENodeEnabledState::DevelopmentOnly; - const FText StatusMessage = bDevelopmentOnly ? NSLOCTEXT("SGraphNode", "DevelopmentOnly", "Development Only") : NSLOCTEXT("SGraphNode", "DisabledNode", "Disabled"); - const FText StatusMessageTooltip = bDevelopmentOnly ? - NSLOCTEXT("SGraphNode", "DevelopmentOnlyTooltip", "This node will only be executed in the editor and in Development builds in a packaged game (it will be treated as disabled in Shipping or Test builds cooked from a commandlet)") : - NSLOCTEXT("SGraphNode", "DisabledNodeTooltip", "This node is currently disabled and will not be executed"); - InnerVerticalBox->AddSlot() .AutoHeight() .HAlign(HAlign_Fill) .VAlign(VAlign_Top) .Padding(FMargin(2, 0)) [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush(bDevelopmentOnly ? "Graph.Node.DevelopmentBanner" : "Graph.Node.DisabledBanner")) - .HAlign(HAlign_Fill) - .VAlign(VAlign_Fill) - [ - SNew(STextBlock) - .Text(StatusMessage) - .ToolTipText(StatusMessageTooltip) - .Justification(ETextJustify::Center) - .ColorAndOpacity(FLinearColor::White) - .ShadowOffset(FVector2D::UnitVector) - .Visibility(EVisibility::Visible) - ] + EnabledStateWidget.ToSharedRef() ]; } @@ -977,6 +960,34 @@ void SGraphNode::UpdateGraphNode() END_SLATE_FUNCTION_BUILD_OPTIMIZATION +TSharedPtr SGraphNode::GetEnabledStateWidget() +{ + if ((GraphNode->GetDesiredEnabledState() != ENodeEnabledState::Enabled) && !GraphNode->IsAutomaticallyPlacedGhostNode()) + { + const bool bDevelopmentOnly = GraphNode->GetDesiredEnabledState() == ENodeEnabledState::DevelopmentOnly; + const FText StatusMessage = bDevelopmentOnly ? NSLOCTEXT("SGraphNode", "DevelopmentOnly", "Development Only") : NSLOCTEXT("SGraphNode", "DisabledNode", "Disabled"); + const FText StatusMessageTooltip = bDevelopmentOnly ? + NSLOCTEXT("SGraphNode", "DevelopmentOnlyTooltip", "This node will only be executed in the editor and in Development builds in a packaged game (it will be treated as disabled in Shipping or Test builds cooked from a commandlet)") : + NSLOCTEXT("SGraphNode", "DisabledNodeTooltip", "This node is currently disabled and will not be executed"); + + return SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush(bDevelopmentOnly ? "Graph.Node.DevelopmentBanner" : "Graph.Node.DisabledBanner")) + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + [ + SNew(STextBlock) + .Text(StatusMessage) + .ToolTipText(StatusMessageTooltip) + .Justification(ETextJustify::Center) + .ColorAndOpacity(FLinearColor::White) + .ShadowOffset(FVector2D::UnitVector) + .Visibility(EVisibility::Visible) + ]; + } + + return TSharedPtr(); +} + TSharedRef SGraphNode::CreateNodeContentArea() { // NODE CONTENT AREA diff --git a/Engine/Source/Editor/GraphEditor/Private/SGraphNodeDocumentation.cpp b/Engine/Source/Editor/GraphEditor/Private/SGraphNodeDocumentation.cpp index c14fa376a1d5..77a34a07ec24 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SGraphNodeDocumentation.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SGraphNodeDocumentation.cpp @@ -215,8 +215,8 @@ TSharedPtr SGraphNodeDocumentation::CreateDocumentationPage() +SOverlay::Slot() [ SNew(SSimpleGradient) - .StartColor(GraphNodeDocumentationDefs::PageGradientStartColor) - .EndColor(GraphNodeDocumentationDefs::PageGradientEndColor) + .StartColor(this, &SGraphNodeDocumentation::GetPageGradientStartColor) + .EndColor(this, &SGraphNodeDocumentation::GetPageGradientEndColor) ] +SOverlay::Slot() [ @@ -355,6 +355,18 @@ void SGraphNodeDocumentation::Tick( const FGeometry& AllottedGeometry, const dou } } +FLinearColor SGraphNodeDocumentation::GetPageGradientStartColor() const +{ + FLinearColor Color = GraphNodeDocumentationDefs::PageGradientStartColor; + return Color; +} + +FLinearColor SGraphNodeDocumentation::GetPageGradientEndColor() const +{ + FLinearColor Color = GraphNodeDocumentationDefs::PageGradientEndColor; + return Color; +} + ///////////////////////////////////////////////////// #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/GraphEditor/Private/SGraphNodeKnot.cpp b/Engine/Source/Editor/GraphEditor/Private/SGraphNodeKnot.cpp index d63896caa173..bd44a5c6c37d 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SGraphNodeKnot.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SGraphNodeKnot.cpp @@ -464,23 +464,3 @@ void SGraphNodeKnot::OnCommentTextCommitted(const FText& NewComment, ETextCommit CommentBubble->SetCommentBubbleVisibility(/*bVisible =*/false); } } - -void SGraphNodeKnot::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) -{ - SGraphNode::OnMouseEnter(MyGeometry, MouseEvent); - if (!GraphNode->bCommentBubbleVisible && !GraphNode->NodeComment.IsEmpty()) - { - // Show the bubble widget while hovered - CommentBubble->SetCommentBubbleVisibility(/*bVisible =*/true); - } -} - -void SGraphNodeKnot::OnMouseLeave(const FPointerEvent& MouseEvent) -{ - SGraphNode::OnMouseLeave(MouseEvent); - if (!bAlwaysShowCommentBubble && !CommentBubble->TextBlockHasKeyboardFocus()) - { - // Hide the comment bubble if visibility hasn't changed; - CommentBubble->SetCommentBubbleVisibility(/*bVisible =*/false); - } -} diff --git a/Engine/Source/Editor/GraphEditor/Private/SGraphPanel.cpp b/Engine/Source/Editor/GraphEditor/Private/SGraphPanel.cpp index e91000742a63..36760d423cf8 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SGraphPanel.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SGraphPanel.cpp @@ -172,6 +172,9 @@ int32 SGraphPanel::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeo if (bNodeIsVisible) { const bool bSelected = SelectionToVisualize->Contains( StaticCastSharedRef(CurWidget.Widget)->GetObjectBeingDisplayed() ); + + UEdGraphNode* NodeObj = Cast(ChildNode->GetObjectBeingDisplayed()); + float Alpha = 1.0f; // Handle Node renaming once the node is visible if( bSelected && ChildNode->IsRenamePending() ) @@ -192,7 +195,9 @@ int32 SGraphPanel::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeo OutDrawElements, ShadowLayerId, CurWidget.Geometry.ToInflatedPaintGeometry(NodeShadowSize), - ShadowBrush + ShadowBrush, + ESlateDrawEffect::None, + FLinearColor(1.0f, 1.0f, 1.0f, Alpha) ); } @@ -216,8 +221,6 @@ int32 SGraphPanel::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeo int32 CurWidgetsMaxLayerId; { - UEdGraphNode* NodeObj = Cast(ChildNode->GetObjectBeingDisplayed()); - /** When diffing nodes, nodes that are different between revisions are opaque, nodes that have not changed are faded */ FGraphDiffControl::FNodeMatch NodeMatch = FGraphDiffControl::FindNodeMatch(GraphObjToDiff, NodeObj, NodeMatches); if (NodeMatch.IsValid()) @@ -229,7 +232,10 @@ int32 SGraphPanel::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeo /* When dragging off a pin, we want to duck the alpha of some nodes */ TSharedPtr< SGraphPin > OnlyStartPin = (1 == PreviewConnectorFromPins.Num()) ? PreviewConnectorFromPins[0].FindInGraphPanel(*this) : TSharedPtr< SGraphPin >(); const bool bNodeIsNotUsableInCurrentContext = Schema->FadeNodeWhenDraggingOffPin(NodeObj, OnlyStartPin.IsValid() ? OnlyStartPin.Get()->GetPinObj() : nullptr); - const FWidgetStyle& NodeStyleToUse = (bNodeIsDifferent && !bNodeIsNotUsableInCurrentContext)? InWidgetStyle : FadedStyle; + + const FWidgetStyle& NodeStyle = (bNodeIsDifferent && !bNodeIsNotUsableInCurrentContext)? InWidgetStyle : FadedStyle; + FWidgetStyle NodeStyleToUse = NodeStyle; + NodeStyleToUse.BlendColorAndOpacityTint(FLinearColor(1.0f, 1.0f, 1.0f, Alpha)); // Draw the node.O CurWidgetsMaxLayerId = CurWidget.Widget->Paint(NewArgs, CurWidget.Geometry, MyCullingRect, OutDrawElements, ChildLayerId, NodeStyleToUse, !DisplayAsReadOnly.Get() && ShouldBeEnabled( bParentEnabled ) ); @@ -261,7 +267,9 @@ int32 SGraphPanel::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeo OutDrawElements, CurWidgetsMaxLayerId, BouncedGeometry, - OverlayBrush + OverlayBrush, + ESlateDrawEffect::None, + FLinearColor(1.0f, 1.0f, 1.0f, Alpha) ); } @@ -1718,6 +1726,11 @@ void SGraphPanel::AddReferencedObjects(FReferenceCollector& Collector) Collector.AddReferencedObject( GraphObjToDiff ); } +FString SGraphPanel::GetReferencerName() const +{ + return TEXT("SGraphPanel"); +} + EActiveTimerReturnType SGraphPanel::InvalidatePerTick(double InCurrentTime, float InDeltaTime) { // Invalidate the layout so it will redraw. diff --git a/Engine/Source/Editor/GraphEditor/Private/SGraphPin.cpp b/Engine/Source/Editor/GraphEditor/Private/SGraphPin.cpp index 3c4489b01767..1fe2e4b1cbef 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SGraphPin.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SGraphPin.cpp @@ -978,7 +978,7 @@ FSlateColor SGraphPin::GetPinColor() const } if (const UEdGraphSchema* Schema = GraphPinObj->GetSchema()) { - if (!GetPinObj()->GetOwningNode()->IsNodeEnabled() || GetPinObj()->GetOwningNode()->IsDisplayAsDisabledForced() || !IsEditingEnabled()) + if (!GetPinObj()->GetOwningNode()->IsNodeEnabled() || GetPinObj()->GetOwningNode()->IsDisplayAsDisabledForced() || !IsEditingEnabled() || GetPinObj()->GetOwningNode()->IsNodeUnrelated()) { return Schema->GetPinTypeColor(GraphPinObj->PinType) * FLinearColor(1.0f, 1.0f, 1.0f, 0.5f); } @@ -1001,13 +1001,13 @@ FSlateColor SGraphPin::GetPinTextColor() const // If there is no schema there is no owning node (or basically this is a deleted node) if (UEdGraphNode* GraphNode = GraphPinObj->GetOwningNodeUnchecked()) { - const bool bDisabled = (!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || !IsEditingEnabled()); + const bool bDisabled = (!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || !IsEditingEnabled() || GraphNode->IsNodeUnrelated()); if (GraphPinObj->bOrphanedPin) { FLinearColor PinColor = FLinearColor::Red; if (bDisabled) { - PinColor.A = 0.5f; + PinColor.A = .25f; } return PinColor; } diff --git a/Engine/Source/Editor/GraphEditor/Public/GraphDiffControl.h b/Engine/Source/Editor/GraphEditor/Public/GraphDiffControl.h index 0c68f9610d2f..8efb14455bd9 100644 --- a/Engine/Source/Editor/GraphEditor/Public/GraphDiffControl.h +++ b/Engine/Source/Editor/GraphEditor/Public/GraphDiffControl.h @@ -4,6 +4,9 @@ #include "CoreMinimal.h" #include "DiffResults.h" +class UEdGraph; +class UEdGraphNode; + /** Used to find differences between revisions of a graph. */ class GRAPHEDITOR_API FGraphDiffControl { @@ -65,27 +68,27 @@ public: struct GRAPHEDITOR_API FNodeMatch { FNodeMatch() - : NewNode(NULL) - , OldNode(NULL) + : NewNode(nullptr) + , OldNode(nullptr) {} - class UEdGraphNode* NewNode; - class UEdGraphNode* OldNode; + UEdGraphNode* NewNode; + UEdGraphNode* OldNode; /** * Looks at the two node members and compares them to see if there are any - * differences. If one of the nodes is NULL, then this will return true with + * differences. If one of the nodes is nullptr, then this will return true with * a EDiffType::NODE_ADDED result. * * @param DiffsArrayOut If supplied, this will be filled out with all the differences that were found. * @return True if there were differences found, false if the two nodes are identical. */ - bool Diff(const FNodeDiffContext& DiffContext, TArray* DiffsResultsOut = NULL) const; + bool Diff(const FNodeDiffContext& DiffContext, TArray* DiffsResultsOut = nullptr) const; bool Diff(const FNodeDiffContext& DiffContext, FDiffResults& DiffsOut) const; /** * Checks to see if this is a valid match. - * @return False if NewNode or OldNode is NULL, true if both are valid. + * @return False if NewNode or OldNode is nullptr, true if both are valid. */ bool IsValid() const; }; @@ -96,15 +99,21 @@ public: * match, so providing a list of already matched nodes helps us narrow it down (and * prevents us from matching one node with multiple others). * - * @param Graph The graph you want to search. - * @param Node The node you want to match. + * @param OldGraph The graph you want to search. + * @param NewNode The new node you want to match. * @param PriorMatches Previous made matches to exclude from our search. - * @return A pair of nodes (including the supplied one) that best match each other (one may be NULL if no match was found). + * @return A pair of nodes (including the supplied one) that best match each other (one may be nullptr if no match was found). */ - static FNodeMatch FindNodeMatch(class UEdGraph* OldGraph, class UEdGraphNode* Node, TArray const& PriorMatches); + static FNodeMatch FindNodeMatch(UEdGraph* OldGraph, UEdGraphNode* NewNode, TArray const& PriorMatches); - /** Determine if the two Nodes are the same */ - static bool IsNodeMatch(class UEdGraphNode* Node1, class UEdGraphNode* Node2, TArray const* Exclusions = nullptr); + /** + * Returns true if the two Nodes are the same + * + * @param Node1 First node to check + * @param Node2 Second node + * @param Exclusions Previous made matches to exclude from our search. + */ + static bool IsNodeMatch(UEdGraphNode* Node1, UEdGraphNode* Node2, bool bExactOnly = false, TArray const* Exclusions = nullptr); /** * Looks for node differences between the two supplied graphs. Diffs will be @@ -115,5 +124,8 @@ public: * @param DiffsOut All the differences that were found between the two. * @return True if any differences were found, false if both graphs are identical. */ - static bool DiffGraphs(class UEdGraph* const OldGraph, class UEdGraph* const NewGraph, TArray& DiffsOut); + static bool DiffGraphs(UEdGraph* const OldGraph, UEdGraph* const NewGraph, TArray& DiffsOut); + + /** Computes an object path for a specific graph, relative to the blueprint/asset root */ + static FString GetGraphPath(UEdGraph* Graph); }; diff --git a/Engine/Source/Editor/GraphEditor/Public/SGraphActionMenu.h b/Engine/Source/Editor/GraphEditor/Public/SGraphActionMenu.h index 8cc91bed3364..0d94e1930777 100644 --- a/Engine/Source/Editor/GraphEditor/Public/SGraphActionMenu.h +++ b/Engine/Source/Editor/GraphEditor/Public/SGraphActionMenu.h @@ -147,6 +147,7 @@ public: // FGCObject override virtual void AddReferencedObjects( FReferenceCollector& Collector ) override; + virtual FString GetReferencerName() const override; /** * Refreshes the actions that this widget should display diff --git a/Engine/Source/Editor/GraphEditor/Public/SGraphNode.h b/Engine/Source/Editor/GraphEditor/Public/SGraphNode.h index 57893e28c7fe..71e430efccf5 100644 --- a/Engine/Source/Editor/GraphEditor/Public/SGraphNode.h +++ b/Engine/Source/Editor/GraphEditor/Public/SGraphNode.h @@ -356,6 +356,9 @@ protected: /** Returns TRUE if the input pin should be hidden from view */ bool ShouldPinBeHidden(const UEdGraphPin* InPin) const; + /** Returns the widget to use for the enabled state of the node */ + TSharedPtr GetEnabledStateWidget(); + protected: /** Input pin widgets on this node */ TArray< TSharedRef > InputPins; diff --git a/Engine/Source/Editor/GraphEditor/Public/SGraphNodeDocumentation.h b/Engine/Source/Editor/GraphEditor/Public/SGraphNodeDocumentation.h index 6c1037011e96..dbcd18bffb08 100644 --- a/Engine/Source/Editor/GraphEditor/Public/SGraphNodeDocumentation.h +++ b/Engine/Source/Editor/GraphEditor/Public/SGraphNodeDocumentation.h @@ -52,6 +52,10 @@ protected: float GetDocumentationWrapWidth() const; /** Returns the current child widgets visibility for hit testing */ EVisibility GetWidgetVisibility() const; + /** Color of the page gradient start */ + FLinearColor GetPageGradientStartColor() const; + /** Color of the page gradient end */ + FLinearColor GetPageGradientEndColor() const; private: diff --git a/Engine/Source/Editor/GraphEditor/Public/SGraphNodeKnot.h b/Engine/Source/Editor/GraphEditor/Public/SGraphNodeKnot.h index 104928a54032..940394794120 100644 --- a/Engine/Source/Editor/GraphEditor/Public/SGraphNodeKnot.h +++ b/Engine/Source/Editor/GraphEditor/Public/SGraphNodeKnot.h @@ -28,11 +28,6 @@ public: virtual void RequestRenameOnSpawn() override { } // End of SGraphNode interface - // SWidget interface - virtual void OnMouseEnter( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override; - virtual void OnMouseLeave( const FPointerEvent& MouseEvent ) override; - // End of SWidget interface - protected: /** Returns Offset to center comment on the node's only pin */ FVector2D GetCommentOffset() const; diff --git a/Engine/Source/Editor/GraphEditor/Public/SGraphPanel.h b/Engine/Source/Editor/GraphEditor/Public/SGraphPanel.h index a11d4617d90f..84575d785288 100644 --- a/Engine/Source/Editor/GraphEditor/Public/SGraphPanel.h +++ b/Engine/Source/Editor/GraphEditor/Public/SGraphPanel.h @@ -119,6 +119,7 @@ public: // FGCObject interface. virtual void AddReferencedObjects( FReferenceCollector& Collector ) override; + virtual FString GetReferencerName() const override; // End of FGCObject interface. void ArrangeChildrenForContextMenuSummon(const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren) const; diff --git a/Engine/Source/Editor/HierarchicalLODOutliner/Private/HLODOutliner.cpp b/Engine/Source/Editor/HierarchicalLODOutliner/Private/HLODOutliner.cpp index 109a22ee7511..a7cafdaff4ab 100644 --- a/Engine/Source/Editor/HierarchicalLODOutliner/Private/HLODOutliner.cpp +++ b/Engine/Source/Editor/HierarchicalLODOutliner/Private/HLODOutliner.cpp @@ -981,7 +981,7 @@ namespace HLODOutliner LOCTEXT("AutoLODTooltip", "Determine LOD level automatically"), FSlateIcon(), FUIAction( - FExecuteAction::CreateSP(this, &SHLODOutliner::SetForcedLODLevel, -1), + FExecuteAction::CreateSP(const_cast(this), &SHLODOutliner::SetForcedLODLevel, -1), FCanExecuteAction(), FGetActionCheckState::CreateLambda([this](){ return ForcedLODLevel == -1 ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })), NAME_None, @@ -999,7 +999,7 @@ namespace HLODOutliner FText::Format(LOCTEXT("LODLevelTooltipFormat", "Force LOD to level {0}"), FText::AsNumber(LODIndex)), FSlateIcon(), FUIAction( - FExecuteAction::CreateSP(this, &SHLODOutliner::SetForcedLODLevel, LODIndex), + FExecuteAction::CreateSP(const_cast(this), &SHLODOutliner::SetForcedLODLevel, LODIndex), FCanExecuteAction(), FGetActionCheckState::CreateLambda([this, LODIndex](){ return ForcedLODLevel == LODIndex ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })), NAME_None, diff --git a/Engine/Source/Editor/IntroTutorials/Private/IntroTutorials.cpp b/Engine/Source/Editor/IntroTutorials/Private/IntroTutorials.cpp index 7aa67e0df614..a25a6743042c 100644 --- a/Engine/Source/Editor/IntroTutorials/Private/IntroTutorials.cpp +++ b/Engine/Source/Editor/IntroTutorials/Private/IntroTutorials.cpp @@ -78,7 +78,7 @@ TSharedRef FIntroTutorials::AddSummonBlueprintTutorialsMenuExtender(c "HelpBrowse", EExtensionHook::After, CommandList, - FMenuExtensionDelegate::CreateRaw(this, &FIntroTutorials::AddSummonBlueprintTutorialsMenuExtension, PrimaryObject)); + FMenuExtensionDelegate::CreateRaw(const_cast(this), &FIntroTutorials::AddSummonBlueprintTutorialsMenuExtension, PrimaryObject)); return Extender; } diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintCompilationManager.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintCompilationManager.cpp index 16034fee762d..c12e3a9b12a9 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintCompilationManager.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintCompilationManager.cpp @@ -90,6 +90,7 @@ struct FBlueprintCompilationManagerImpl : public FGCObject // FGCObject: virtual void AddReferencedObjects(FReferenceCollector& Collector); + virtual FString GetReferencerName() const override; void RegisterCompilerExtension(TSubclassOf BlueprintType, UBlueprintCompilerExtension* Extension); @@ -174,6 +175,11 @@ void FBlueprintCompilationManagerImpl::AddReferencedObjects(FReferenceCollector& Collector.AddReferencedObjects(OldCDOs); } +FString FBlueprintCompilationManagerImpl::GetReferencerName() const +{ + return TEXT("FBlueprintCompilationManagerImpl"); +} + void FBlueprintCompilationManagerImpl::RegisterCompilerExtension(TSubclassOf BlueprintType, UBlueprintCompilerExtension* Extension) { CompilerExtensions.FindOrAdd(BlueprintType).Emplace(Extension); @@ -619,7 +625,7 @@ void FBlueprintCompilationManagerImpl::FlushCompilationQueueImpl(bool bSuppressB if (DepthA == DepthB) { - return A.GetFName() < B.GetFName(); + return A.GetFName().LexicalLess(B.GetFName()); } return DepthA < DepthB; }; @@ -1061,7 +1067,7 @@ void FBlueprintCompilationManagerImpl::FlushCompilationQueueImpl(bool bSuppressB } else { - // default value propagation occurrs below: + // default value propagation occurs below: if(BPGC) { if( BPGC->ClassDefaultObject && @@ -1168,7 +1174,7 @@ void FBlueprintCompilationManagerImpl::FlushCompilationQueueImpl(bool bSuppressB } } - // STAGE XV: CLEAR TEMPORARY FLAGS + // STAGE XVI: CLEAR TEMPORARY FLAGS for (FCompilerData& CompilerData : CurrentlyCompilingBPs) { UBlueprint* BP = CompilerData.BP; @@ -1708,7 +1714,7 @@ void FBlueprintCompilationManagerImpl::ReinstanceBatch(TArray& if (DepthA == DepthB && A && B) { - return A->GetFName() < B->GetFName(); + return A->GetFName().LexicalLess(B->GetFName()); } return DepthA < DepthB; } @@ -1953,8 +1959,7 @@ void FBlueprintCompilationManagerImpl::ReinstanceBatch(TArray& for(UObject* ArchetypeReferencer : ArchetypeReferencers) { - UPackage* NewPackage = ArchetypeReferencer->GetOutermost(); - FArchiveReplaceOrClearExternalReferences ReplaceInCDOAr(ArchetypeReferencer, OldArchetypeToNewArchetype, NewPackage); + FArchiveReplaceObjectRef ReplaceInCDOAr(ArchetypeReferencer, OldArchetypeToNewArchetype, false, false, false, false, true); } } @@ -2284,7 +2289,7 @@ UClass* FBlueprintCompilationManagerImpl::FastGenerateSkeletonClass(UBlueprint* UField** CurrentFieldStorageLocation = &Ret->Children; // Helper function for making UFunctions generated for 'event' nodes, e.g. custom event and timelines - const auto MakeEventFunction = [&CurrentFieldStorageLocation, MakeFunction, Schema]( FName InName, EFunctionFlags ExtraFnFlags, const TArray& InputPins, const TArray< TSharedPtr >& UserPins, UFunction* InSourceFN, bool bInCallInEditor ) + const auto MakeEventFunction = [&CurrentFieldStorageLocation, MakeFunction, Schema]( FName InName, EFunctionFlags ExtraFnFlags, const TArray& InputPins, const TArray< TSharedPtr >& UserPins, UFunction* InSourceFN, bool bInCallInEditor, bool bIsDeprecated, const FString& DeprecationMessage ) { UField** CurrentParamStorageLocation = nullptr; @@ -2304,6 +2309,15 @@ UClass* FBlueprintCompilationManagerImpl::FastGenerateSkeletonClass(UBlueprint* { FKismetCompilerContext::SetDefaultInputValueMetaData(NewFunction, UserPins); + if (bIsDeprecated) + { + NewFunction->SetMetaData(FBlueprintMetadata::MD_DeprecatedFunction, TEXT("true")); + if (!DeprecationMessage.IsEmpty()) + { + NewFunction->SetMetaData(FBlueprintMetadata::MD_DeprecationMessage, *DeprecationMessage); + } + } + if(bInCallInEditor) { NewFunction->SetMetaData(FBlueprintMetadata::MD_CallInEditor, TEXT( "true" )); @@ -2343,10 +2357,17 @@ UClass* FBlueprintCompilationManagerImpl::FastGenerateSkeletonClass(UBlueprint* Graph->GetNodesOfClass(EventNodes); for( UK2Node_Event* Event : EventNodes ) { + FString DeprecationMessage; + bool bIsDeprecated = false; bool bCallInEditor = false; if(UK2Node_CustomEvent* CustomEvent = Cast(Event)) { bCallInEditor = CustomEvent->bCallInEditor; + bIsDeprecated = CustomEvent->bIsDeprecated; + if (bIsDeprecated) + { + DeprecationMessage = CustomEvent->DeprecationMessage; + } } MakeEventFunction( CompilerContext.GetEventStubFunctionName(Event), @@ -2354,7 +2375,9 @@ UClass* FBlueprintCompilationManagerImpl::FastGenerateSkeletonClass(UBlueprint* Event->Pins, Event->UserDefinedPins, Event->FindEventSignatureFunction(), - bCallInEditor + bCallInEditor, + bIsDeprecated, + DeprecationMessage ); } } @@ -2371,11 +2394,11 @@ UClass* FBlueprintCompilationManagerImpl::FastGenerateSkeletonClass(UBlueprint* for (const FTTEventTrack& EventTrack : Timeline->EventTracks) { - MakeEventFunction(EventTrack.GetFunctionName(), EFunctionFlags::FUNC_None, TArray(), TArray< TSharedPtr >(), nullptr, false); + MakeEventFunction(EventTrack.GetFunctionName(), EFunctionFlags::FUNC_None, TArray(), TArray< TSharedPtr >(), nullptr, false, false, FString()); } - MakeEventFunction(Timeline->GetUpdateFunctionName(), EFunctionFlags::FUNC_None, TArray(), TArray< TSharedPtr >(), nullptr, false); - MakeEventFunction(Timeline->GetFinishedFunctionName(), EFunctionFlags::FUNC_None, TArray(), TArray< TSharedPtr >(), nullptr, false); + MakeEventFunction(Timeline->GetUpdateFunctionName(), EFunctionFlags::FUNC_None, TArray(), TArray< TSharedPtr >(), nullptr, false, false, FString()); + MakeEventFunction(Timeline->GetFinishedFunctionName(), EFunctionFlags::FUNC_None, TArray(), TArray< TSharedPtr >(), nullptr, false, false, FString()); } } diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.cpp index 0b8376ae67e3..eca06dc1c8f3 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.cpp @@ -107,9 +107,10 @@ void FBlueprintDetails::AddEventsCategory(IDetailLayoutBuilder& DetailBuilder, U UProperty* Property = *PropertyIt; FName PropertyName = ComponentProperty->GetFName(); - + static const FName HideInDetailPanelName("HideInDetailPanel"); // Check for multicast delegates that we can safely assign - if ( !Property->HasAnyPropertyFlags(CPF_Parm) && Property->HasAllPropertyFlags(CPF_BlueprintAssignable) ) + if ( !Property->HasAnyPropertyFlags(CPF_Parm) && Property->HasAllPropertyFlags(CPF_BlueprintAssignable) && + !Property->HasMetaData(HideInDetailPanelName) ) { FName EventName = Property->GetFName(); FText EventText = Property->GetDisplayNameText(); @@ -830,7 +831,6 @@ void FBlueprintVarActionDetails::CustomizeDetails( IDetailLayoutBuilder& DetailL UFunction* StructScope = Cast(VariableProperty->GetOuter()); check(StructScope); - TSharedPtr StructData = MakeShareable(new FStructOnScope((UFunction*)StructScope)); UEdGraph* Graph = FBlueprintEditorUtils::FindScopeGraph(GetBlueprintObj(), (UFunction*)StructScope); // Find the function entry nodes in the current graph @@ -843,18 +843,9 @@ void FBlueprintVarActionDetails::CustomizeDetails( IDetailLayoutBuilder& DetailL const UStructProperty* PotentialUDSProperty = Cast(VariableProperty); UK2Node_FunctionEntry* FuncEntry = EntryNodes[0]; - for (const FBPVariableDescription& LocalVar : FuncEntry->LocalVariables) - { - if(LocalVar.VarName == VariableProperty->GetFName()) //Property->GetFName()) - { - // Only set the default value if there is one - if(!LocalVar.DefaultValue.IsEmpty()) - { - FBlueprintEditorUtils::PropertyValueFromString(VariableProperty, LocalVar.DefaultValue, StructData->GetStructMemory()); - } - break; - } - } + + // This uses the cached struct data inside the node, which is updated on change + TSharedPtr StructData = FuncEntry->GetFunctionVariableCache(); if(BlueprintEditor.IsValid()) { @@ -981,6 +972,48 @@ void FBlueprintVarActionDetails::CustomizeDetails( IDetailLayoutBuilder& DetailL .ToolTip(MultilineTooltip) ]; + TSharedPtr DeprecatedTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VariableDeprecated_Tooltip", "Deprecate usage of this variable. Any nodes that reference it will produce a compiler warning indicating that it should be removed or replaced."), nullptr, DocLink, TEXT("Deprecated")); + + Category.AddCustomRow(LOCTEXT("VariableDeprecated", "Deprecated"), true) + .Visibility(TAttribute(this, &FBlueprintVarActionDetails::GetDeprecatedVisibility)) + .NameContent() + [ + SNew(STextBlock) + .ToolTip(DeprecatedTooltip) + .Text(LOCTEXT("VariableDeprecated", "Deprecated")) + .Font(DetailFontInfo) + ] + .ValueContent() + [ + SNew(SCheckBox) + .IsChecked(this, &FBlueprintVarActionDetails::OnGetDeprecatedCheckboxState) + .OnCheckStateChanged(this, &FBlueprintVarActionDetails::OnDeprecatedChanged) + .IsEnabled(IsVariableInBlueprint()) + .ToolTip(DeprecatedTooltip) + ]; + + TSharedPtr DeprecationMessageTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VariableDeprecationMessage_Tooltip", "Optional message to include with the deprecation compiler warning. For example: \'X is no longer being used. Please replace with Y.\'"), nullptr, DocLink, TEXT("DeprecationMessage")); + + Category.AddCustomRow(LOCTEXT("VariableDeprecationMessageLabel", "Deprecation Message"), true) + .Visibility(TAttribute(this, &FBlueprintVarActionDetails::GetDeprecatedVisibility)) + .IsEnabled(TAttribute(this, &FBlueprintVarActionDetails::IsVariableDeprecated)) + .NameContent() + [ + SNew(STextBlock) + .ToolTip(DeprecationMessageTooltip) + .Text(LOCTEXT("VariableDeprecationMessageLabel", "Deprecation Message")) + .Font(DetailFontInfo) + ] + .ValueContent() + [ + SNew(SEditableTextBox) + .Text(this, &FBlueprintVarActionDetails::GetDeprecationMessageText) + .OnTextCommitted(this, &FBlueprintVarActionDetails::OnDeprecationMessageTextCommitted, CachedVariableName) + .IsEnabled(IsVariableInBlueprint()) + .ToolTipText(this, &FBlueprintVarActionDetails::GetDeprecationMessageText) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ]; + TSharedPtr PropertyFlagsTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("DefinedPropertyFlags_Tooltip", "List of defined flags for this property"), NULL, DocLink, TEXT("PropertyFlags")); Category.AddCustomRow(LOCTEXT("DefinedPropertyFlags", "Defined Property Flags"), true) @@ -2472,17 +2505,32 @@ void FBlueprintVarActionDetails::OnAdvancedDisplayChanged(ECheckBoxState InNewSt EVisibility FBlueprintVarActionDetails::GetMultilineVisibility() const { - if (UProperty* VariableProperty = CachedVariableProperty.Get()) + UProperty* VariableProperty = nullptr; + if (UProperty* RawVariableProperty = CachedVariableProperty.Get()) { - if (IsABlueprintVariable(VariableProperty)) + if (IsABlueprintVariable(RawVariableProperty)) { - if (VariableProperty->IsA(UTextProperty::StaticClass()) || VariableProperty->IsA(UStrProperty::StaticClass())) + if (const UArrayProperty* ArrayProperty = Cast(RawVariableProperty)) { - return EVisibility::Visible; + VariableProperty = ArrayProperty->Inner; + } + else if (const USetProperty* SetProperty = Cast(RawVariableProperty)) + { + VariableProperty = SetProperty->ElementProp; + } + else if (const UMapProperty* MapProperty = Cast(RawVariableProperty)) + { + VariableProperty = MapProperty->ValueProp; + } + else + { + VariableProperty = RawVariableProperty; } } } - return EVisibility::Collapsed; + + const bool bCanBeMultiline = (VariableProperty != nullptr) && (VariableProperty->IsA(UTextProperty::StaticClass()) || VariableProperty->IsA(UStrProperty::StaticClass())); + return bCanBeMultiline ? EVisibility::Visible : EVisibility::Collapsed; } ECheckBoxState FBlueprintVarActionDetails::OnGetMultilineCheckboxState() const @@ -2505,6 +2553,60 @@ void FBlueprintVarActionDetails::OnMultilineChanged(ECheckBoxState InNewState) } } +EVisibility FBlueprintVarActionDetails::GetDeprecatedVisibility() const +{ + if (UProperty* VariableProperty = CachedVariableProperty.Get()) + { + if (IsABlueprintVariable(VariableProperty) && IsAUserVariable(VariableProperty)) + { + return EVisibility::Visible; + } + } + return EVisibility::Collapsed; +} + +ECheckBoxState FBlueprintVarActionDetails::OnGetDeprecatedCheckboxState() const +{ + return IsVariableDeprecated() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +void FBlueprintVarActionDetails::OnDeprecatedChanged(ECheckBoxState InNewState) +{ + UProperty* Property = CachedVariableProperty.Get(); + if (Property) + { + const bool bDeprecatedFlag = (InNewState == ECheckBoxState::Checked); + FBlueprintEditorUtils::SetVariableDeprecatedFlag(GetBlueprintObj(), Property->GetFName(), bDeprecatedFlag); + } +} + +FText FBlueprintVarActionDetails::GetDeprecationMessageText() const +{ + FName VarName = CachedVariableName; + if (VarName != NAME_None) + { + if (UBlueprint* OwnerBlueprint = GetPropertyOwnerBlueprint()) + { + FString Result; + FBlueprintEditorUtils::GetBlueprintVariableMetaData(GetPropertyOwnerBlueprint(), VarName, GetLocalVariableScope(CachedVariableProperty.Get()), FBlueprintMetadata::MD_DeprecationMessage, Result); + return FText::FromString(Result); + } + } + return FText(); +} + +void FBlueprintVarActionDetails::OnDeprecationMessageTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit, FName VarName) +{ + if (NewText.IsEmpty()) + { + FBlueprintEditorUtils::RemoveBlueprintVariableMetaData(GetBlueprintObj(), VarName, GetLocalVariableScope(CachedVariableProperty.Get()), FBlueprintMetadata::MD_DeprecationMessage); + } + else + { + FBlueprintEditorUtils::SetBlueprintVariableMetaData(GetBlueprintObj(), VarName, GetLocalVariableScope(CachedVariableProperty.Get()), FBlueprintMetadata::MD_DeprecationMessage, NewText.ToString()); + } +} + EVisibility FBlueprintVarActionDetails::IsTooltipEditVisible() const { UProperty* VariableProperty = CachedVariableProperty.Get(); @@ -2539,12 +2641,12 @@ void FBlueprintVarActionDetails::OnFinishedChangingProperties(const FPropertyCha if (InStructData.IsValid()) { - bDefaultValueSet = FBlueprintEditorUtils::PropertyValueToString(DirectProperty, InStructData->GetStructMemory(), DefaultValueString); + UK2Node_FunctionEntry* FuncEntry = Cast(InEntryNode.Get()); + + bDefaultValueSet = FBlueprintEditorUtils::PropertyValueToString(DirectProperty, InStructData->GetStructMemory(), DefaultValueString, FuncEntry); if(bDefaultValueSet) { - UK2Node_FunctionEntry* FuncEntry = Cast(InEntryNode.Get()); - // Search out the correct local variable in the Function Entry Node and set the default value for (FBPVariableDescription& LocalVar : FuncEntry->LocalVariables) { @@ -2555,6 +2657,7 @@ void FBlueprintVarActionDetails::OnFinishedChangingProperties(const FPropertyCha FuncEntry->Modify(); GetBlueprintObj()->Modify(); LocalVar.DefaultValue = DefaultValueString; + FuncEntry->RefreshFunctionVariableCache(); FBlueprintEditorUtils::MarkBlueprintAsModified(GetBlueprintObj()); break; } @@ -2577,6 +2680,13 @@ bool FBlueprintVarActionDetails::IsVariableInheritedByBlueprint() const return GetBlueprintObj()->SkeletonGeneratedClass->IsChildOf(PropertyOwnerClass); } +bool FBlueprintVarActionDetails::IsVariableDeprecated() const +{ + UProperty* Property = CachedVariableProperty.Get(); + + return Property && Property->HasAnyPropertyFlags(CPF_Deprecated); +} + static FDetailWidgetRow& AddRow( TArray >& OutChildRows ) { TSharedRef NewRow( new FDetailWidgetRow ); @@ -2973,7 +3083,7 @@ void FBlueprintGraphArgumentLayout::OnArgNameTextCommitted(const FText& NewText, { const FName OldName = ParamItemPtr.Pin()->PinName; const FString& NewName = NewText.ToString(); - if (OldName.ToString() != NewName) + if (!OldName.ToString().Equals(NewName)) { GraphActionDetailsPtr.Pin()->OnPinRenamed(TargetNode, OldName, NewName); } @@ -3006,8 +3116,10 @@ ECheckBoxState FBlueprintGraphArgumentLayout::IsRefChecked() const void FBlueprintGraphArgumentLayout::OnRefCheckStateChanged(ECheckBoxState InState) { + const FScopedTransaction Transaction(LOCTEXT("ChangeByRef", "Change Pass By Reference")); + FEdGraphPinType PinType = OnGetPinInfo(); - PinType.bIsReference = (InState == ECheckBoxState::Checked)? true : false; + PinType.bIsReference = (InState == ECheckBoxState::Checked) ? true : false; PinInfoChanged(PinType); } @@ -3038,6 +3150,7 @@ void FBlueprintGraphArgumentLayout::PinInfoChanged(const FEdGraphPinType& PinTyp }); if (UDPinPtr) { + Node->Modify(); (*UDPinPtr)->PinType = PinType; // Inputs flagged as pass-by-reference will also be flagged as 'const' here to conform to the expected native C++ @@ -3096,6 +3209,9 @@ void FBlueprintGraphActionDetails::CustomizeDetails( IDetailLayoutBuilder& Detai if (FunctionEntryNode && FunctionEntryNode->IsEditable()) { + const bool bIsCustomEvent = IsCustomEvent(); + const bool bIsFunctionGraph = FunctionEntryNode->IsA(); + IDetailCategoryBuilder& Category = DetailLayout.EditCategory("Graph", LOCTEXT("FunctionDetailsGraph", "Graph")); if (bHasAGraph) { @@ -3293,7 +3409,7 @@ void FBlueprintGraphActionDetails::CustomizeDetails( IDetailLayoutBuilder& Detai } } - if (IsCustomEvent()) + if (bIsCustomEvent) { /** A collection of static utility callbacks to provide the custom-event details ui with */ struct LocalCustomEventUtils @@ -3418,7 +3534,7 @@ void FBlueprintGraphActionDetails::CustomizeDetails( IDetailLayoutBuilder& Detai ] ]; } - const bool bShowCallInEditor = IsCustomEvent() || FBlueprintEditorUtils::IsEditorUtilityBlueprint( GetBlueprintObj() ) || (FunctionEntryNode && FunctionEntryNode->IsEditable()); + const bool bShowCallInEditor = bIsCustomEvent || FBlueprintEditorUtils::IsEditorUtilityBlueprint( GetBlueprintObj() ) || (FunctionEntryNode && FunctionEntryNode->IsEditable()); if( bShowCallInEditor ) { Category.AddCustomRow( LOCTEXT( "EditorCallable", "Call In Editor" ) ) @@ -3448,6 +3564,67 @@ void FBlueprintGraphActionDetails::CustomizeDetails( IDetailLayoutBuilder& Detai ]; } + const bool bShowDeprecated = bIsFunctionGraph || bIsCustomEvent; + if (bShowDeprecated) + { + FFormatNamedArguments DeprecationTooltipFormatArgs; + if (bIsFunctionGraph) + { + DeprecationTooltipFormatArgs.Add(TEXT("FunctionOrCustomEvent"), LOCTEXT("FunctionOrEvent_Function", "function")); + } + else + { + DeprecationTooltipFormatArgs.Add(TEXT("FunctionOrCustomEvent"), LOCTEXT("FunctionOrEvent_CustomEvent", "custom event")); + } + + bool bIsOverride = false; + FFunctionFromNodeHelper FunctionFromNode(FunctionEntryNode); + if (FunctionFromNode.Function) + { + bIsOverride = (UEdGraphSchema_K2::GetCallableParentFunction(FunctionFromNode.Function) != nullptr); + } + + const FText DeprecatedTooltipText = FText::Format(LOCTEXT("DeprecatedFunction_Tooltip", "Deprecate usage of this {FunctionOrCustomEvent}. Any nodes that reference it will produce a compiler warning indicating that it should be removed or replaced."), DeprecationTooltipFormatArgs); + + Category.AddCustomRow(LOCTEXT("DeprecatedFunction", "Deprecated"), true) + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("DeprecatedFunction", "Deprecated")) + .ToolTipText(DeprecatedTooltipText) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + [ + SNew(SCheckBox) + .IsChecked(this, &FBlueprintGraphActionDetails::OnGetDeprecatedCheckboxState) + .ToolTipText(DeprecatedTooltipText) + .OnCheckStateChanged(this, &FBlueprintGraphActionDetails::OnDeprecatedChanged) + .IsEnabled(!bIsOverride) + ]; + + const FText DeprecationMessageTooltipText = LOCTEXT("DeprecationMessage_Tooltip", "Optional message to include with the deprecation compiler warning. For example: \'X is no longer being used. Please replace with Y.\'"); + + Category.AddCustomRow(LOCTEXT("DeprecationMessage", "Deprecation Message"), true) + .IsEnabled(TAttribute(this, &FBlueprintGraphActionDetails::IsFunctionDeprecated)) + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("DeprecationMessage", "Deprecation Message")) + .ToolTipText(DeprecationMessageTooltipText) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + [ + SNew(SEditableTextBox) + .Text(this, &FBlueprintGraphActionDetails::GetDeprecationMessageText) + .OnTextCommitted(this, &FBlueprintGraphActionDetails::OnDeprecationMessageTextCommitted) + .IsEnabled(!bIsOverride) + .ToolTipText(this, &FBlueprintGraphActionDetails::GetDeprecationMessageText) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ]; + } + IDetailCategoryBuilder& InputsCategory = DetailLayout.EditCategory("Inputs", LOCTEXT("FunctionDetailsInputs", "Inputs")); TSharedRef InputArgumentGroup = @@ -3745,6 +3922,103 @@ void FBlueprintGraphActionDetails::OnEditorCallableEventModified( const ECheckBo } } +bool FBlueprintGraphActionDetails::IsFunctionDeprecated() const +{ + bool bIsDeprecated = false; + + UK2Node_EditablePinBase* Node = FunctionEntryNodePtr.Get(); + if (Node != nullptr) + { + UK2Node_CustomEvent* CustomEventNode = Cast(Node); + if (CustomEventNode != nullptr) + { + bIsDeprecated = CustomEventNode->bIsDeprecated; + } + else + { + UK2Node_FunctionEntry* FunctionEntryNode = Cast(Node); + if (FunctionEntryNode != nullptr) + { + bIsDeprecated = FunctionEntryNode->MetaData.bIsDeprecated; + } + } + } + + return bIsDeprecated; +} + +ECheckBoxState FBlueprintGraphActionDetails::OnGetDeprecatedCheckboxState() const +{ + return IsFunctionDeprecated() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +void FBlueprintGraphActionDetails::OnDeprecatedChanged(ECheckBoxState InNewState) +{ + const bool bIsDeprecated = (InNewState == ECheckBoxState::Checked); + + UK2Node_EditablePinBase* Node = FunctionEntryNodePtr.Get(); + if (Node != nullptr) + { + UK2Node_CustomEvent* CustomEventNode = Cast(Node); + if (CustomEventNode != nullptr) + { + CustomEventNode->bIsDeprecated = bIsDeprecated; + } + else + { + CastChecked(Node)->MetaData.bIsDeprecated = bIsDeprecated; + } + + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Node->GetBlueprint()); + } +} + +FText FBlueprintGraphActionDetails::GetDeprecationMessageText() const +{ + FText DeprecationMessage; + + UK2Node_EditablePinBase* Node = FunctionEntryNodePtr.Get(); + if (Node != nullptr) + { + UK2Node_CustomEvent* CustomEventNode = Cast(Node); + if (CustomEventNode != nullptr) + { + DeprecationMessage = FText::FromString(CustomEventNode->DeprecationMessage); + } + else + { + UK2Node_FunctionEntry* FunctionEntryNode = Cast(Node); + if (FunctionEntryNode != nullptr) + { + DeprecationMessage = FText::FromString(FunctionEntryNode->MetaData.DeprecationMessage); + } + } + } + + return DeprecationMessage; +} + +void FBlueprintGraphActionDetails::OnDeprecationMessageTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit) +{ + const FString DeprecationMessage = NewText.ToString(); + + UK2Node_EditablePinBase* Node = FunctionEntryNodePtr.Get(); + if (Node != nullptr) + { + UK2Node_CustomEvent* CustomEventNode = Cast(Node); + if (CustomEventNode != nullptr) + { + CustomEventNode->DeprecationMessage = DeprecationMessage; + } + else + { + CastChecked(Node)->MetaData.DeprecationMessage = DeprecationMessage; + } + + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Node->GetBlueprint()); + } +} + UMulticastDelegateProperty* FBlueprintDelegateActionDetails::GetDelegateProperty() const { if (MyBlueprint.IsValid()) @@ -3795,84 +4069,6 @@ UEdGraph* FBlueprintDelegateActionDetails::GetGraph() const return NULL; } -FText FBlueprintDelegateActionDetails::OnGetTooltipText() const -{ - if (UMulticastDelegateProperty* DelegateProperty = GetDelegateProperty()) - { - FString Result; - FBlueprintEditorUtils::GetBlueprintVariableMetaData(GetBlueprintObj(), DelegateProperty->GetFName(), NULL, TEXT("tooltip"), Result); - return FText::FromString(Result); - } - return FText(); -} - -void FBlueprintDelegateActionDetails::OnTooltipTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit) -{ - if (UMulticastDelegateProperty* DelegateProperty = GetDelegateProperty()) - { - FBlueprintEditorUtils::SetBlueprintVariableMetaData(GetBlueprintObj(), DelegateProperty->GetFName(), NULL, TEXT("tooltip"), NewText.ToString() ); - } -} - -FText FBlueprintDelegateActionDetails::OnGetCategoryText() const -{ - if (UMulticastDelegateProperty* DelegateProperty = GetDelegateProperty()) - { - FName DelegateName = DelegateProperty->GetFName(); - FText Category = FBlueprintEditorUtils::GetBlueprintVariableCategory(GetBlueprintObj(), DelegateName, NULL); - - // Older blueprints will have their name as the default category - if( Category.EqualTo(FText::FromString(GetBlueprintObj()->GetName())) || Category.EqualTo(UEdGraphSchema_K2::VR_DefaultCategory) ) - { - return LOCTEXT("DefaultCategory", "Default"); - } - else - { - return Category; - } - } - return FText(); -} - -void FBlueprintDelegateActionDetails::OnCategoryTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit) -{ - if (InTextCommit == ETextCommit::OnEnter || InTextCommit == ETextCommit::OnUserMovedFocus) - { - if (UMulticastDelegateProperty* DelegateProperty = GetDelegateProperty()) - { - // Remove excess whitespace and prevent categories with just spaces - FText CategoryName = FText::TrimPrecedingAndTrailing(NewText); - - FBlueprintEditorUtils::SetBlueprintVariableCategory(GetBlueprintObj(), DelegateProperty->GetFName(), NULL, CategoryName); - check(MyBlueprint.IsValid()); - FBlueprintVarActionDetails::PopulateCategories(MyBlueprint.Pin().Get(), CategorySource); - MyBlueprint.Pin()->ExpandCategory(CategoryName); - } - } -} - -TSharedRef< ITableRow > FBlueprintDelegateActionDetails::MakeCategoryViewWidget( TSharedPtr Item, const TSharedRef< STableViewBase >& OwnerTable ) -{ - return SNew(STableRow>, OwnerTable) - [ - SNew(STextBlock) .Text(*Item.Get()) - ]; -} - -void FBlueprintDelegateActionDetails::OnCategorySelectionChanged( TSharedPtr ProposedSelection, ESelectInfo::Type /*SelectInfo*/ ) -{ - UMulticastDelegateProperty* DelegateProperty = GetDelegateProperty(); - if (DelegateProperty && ProposedSelection.IsValid()) - { - FText NewCategory = *ProposedSelection.Get(); - - FBlueprintEditorUtils::SetBlueprintVariableCategory(GetBlueprintObj(), DelegateProperty->GetFName(), NULL, NewCategory); - CategoryListView.Pin()->ClearSelection(); - CategoryComboButton.Pin()->SetIsOpen(false); - MyBlueprint.Pin()->ExpandCategory(NewCategory); - } -} - void FBlueprintDelegateActionDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { DetailsLayoutPtr = &DetailLayout; @@ -3883,72 +4079,6 @@ void FBlueprintDelegateActionDetails::CustomizeDetails( IDetailLayoutBuilder& De const UEdGraphSchema_K2* Schema = GetDefault(); const FSlateFontInfo DetailFontInfo = IDetailLayoutBuilder::GetDetailFont(); - { - IDetailCategoryBuilder& Category = DetailLayout.EditCategory("Delegate", LOCTEXT("DelegateDetailsCategory", "Delegate")); - Category.AddCustomRow( LOCTEXT("VariableToolTipLabel", "Tooltip") ) - .NameContent() - [ - SNew(STextBlock) - .Text( LOCTEXT("VariableToolTipLabel", "Tooltip") ) - .Font( DetailFontInfo ) - ] - .ValueContent() - [ - SNew(SEditableTextBox) - .Text( this, &FBlueprintDelegateActionDetails::OnGetTooltipText ) - .OnTextCommitted( this, &FBlueprintDelegateActionDetails::OnTooltipTextCommitted) - .Font( DetailFontInfo ) - ]; - - FBlueprintVarActionDetails::PopulateCategories(MyBlueprint.Pin().Get(), CategorySource); - TSharedPtr NewComboButton; - TSharedPtr>> NewListView; - - Category.AddCustomRow( LOCTEXT("CategoryLabel", "Category") ) - .NameContent() - [ - SNew(STextBlock) - .Text( LOCTEXT("CategoryLabel", "Category") ) - .Font( DetailFontInfo ) - ] - .ValueContent() - [ - SAssignNew(NewComboButton, SComboButton) - .ContentPadding(FMargin(0,0,5,0)) - .IsEnabled(this, &FBlueprintDelegateActionDetails::IsBlueprintProperty) - .ButtonContent() - [ - SNew(SBorder) - .BorderImage( FEditorStyle::GetBrush("NoBorder") ) - .Padding(FMargin(0, 0, 5, 0)) - [ - SNew(SEditableTextBox) - .Text(this, &FBlueprintDelegateActionDetails::OnGetCategoryText) - .OnTextCommitted(this, &FBlueprintDelegateActionDetails::OnCategoryTextCommitted) - .SelectAllTextWhenFocused(true) - .RevertTextOnEscape(true) - .Font( DetailFontInfo ) - ] - ] - .MenuContent() - [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - .AutoHeight() - .MaxHeight(400.0f) - [ - SAssignNew(NewListView, SListView>) - .ListItemsSource(&CategorySource) - .OnGenerateRow(this, &FBlueprintDelegateActionDetails::MakeCategoryViewWidget) - .OnSelectionChanged(this, &FBlueprintDelegateActionDetails::OnCategorySelectionChanged) - ] - ] - ]; - - CategoryComboButton = NewComboButton; - CategoryListView = NewListView; - } - if (UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get()) { IDetailCategoryBuilder& InputsCategory = DetailLayout.EditCategory("DelegateInputs", LOCTEXT("DelegateDetailsInputs", "Inputs")); @@ -4786,8 +4916,6 @@ bool FBlueprintGraphActionDetails::IsConstFunctionVisible() const UK2Node_EditablePinBase * FunctionEntryNode = FunctionEntryNodePtr.Get(); if(FunctionEntryNode) { - UBlueprint* Blueprint = FunctionEntryNode->GetBlueprint(); - bSupportedType = FunctionEntryNode->IsA(); bIsEditable = FunctionEntryNode->IsEditable(); } @@ -5142,21 +5270,16 @@ TSharedRef FBlueprintInterfaceLayout::OnGetAddInterfaceMenuContent() TArray Blueprints; Blueprints.Add(Blueprint); TSharedRef ClassPicker = FBlueprintEditorUtils::ConstructBlueprintInterfaceClassPicker(Blueprints, FOnClassPicked::CreateSP(this, &FBlueprintInterfaceLayout::OnClassPicked)); - return - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + // Achieving fixed width by nesting items within a fixed width box. + return SNew(SBox) + .WidthOverride(350.0f) [ - // Achieving fixed width by nesting items within a fixed width box. - SNew(SBox) - .WidthOverride(350.0f) + SNew(SVerticalBox) + +SVerticalBox::Slot() + .MaxHeight(400.0f) + .AutoHeight() [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - .MaxHeight(400.0f) - .AutoHeight() - [ - ClassPicker - ] + ClassPicker ] ]; } @@ -5199,21 +5322,16 @@ TSharedRef FBlueprintGlobalOptionsDetails::GetParentClassMenuContent() Blueprints.Add(GetBlueprintObj()); TSharedRef ClassPicker = FBlueprintEditorUtils::ConstructBlueprintParentClassPicker(Blueprints, FOnClassPicked::CreateSP(this, &FBlueprintGlobalOptionsDetails::OnClassPicked)); - return - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + // Achieving fixed width by nesting items within a fixed width box. + return SNew(SBox) + .WidthOverride(350.0f) [ - // Achieving fixed width by nesting items within a fixed width box. - SNew(SBox) - .WidthOverride(350.0f) + SNew(SVerticalBox) + +SVerticalBox::Slot() + .MaxHeight(400.0f) + .AutoHeight() [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - .MaxHeight(400.0f) - .AutoHeight() - [ - ClassPicker - ] + ClassPicker ] ]; } @@ -5689,16 +5807,26 @@ void FBlueprintComponentDetails::OnVariableTextChanged(const FText& InNewText) bIsVariableNameInvalid = true; - USCS_Node* SCS_Node = CachedNodePtr->GetSCSNode(); - if(SCS_Node != NULL && !InNewText.IsEmpty() && !FComponentEditorUtils::IsValidVariableNameString(SCS_Node->ComponentTemplate, InNewText.ToString())) + const FString& NewTextStr = InNewText.ToString(); + + if (USCS_Node* SCS_Node = CachedNodePtr->GetSCSNode()) { - VariableNameEditableTextBox->SetError(LOCTEXT("ComponentVariableRenameFailed_NotValid", "This name is reserved for engine use.")); - return; + if (!NewTextStr.IsEmpty() && !FComponentEditorUtils::IsValidVariableNameString(SCS_Node->ComponentTemplate, NewTextStr)) + { + VariableNameEditableTextBox->SetError(LOCTEXT("ComponentVariableRenameFailed_NotValid", "This name is reserved for engine use.")); + return; + } + + if (!FComponentEditorUtils::IsComponentNameAvailable(NewTextStr, SCS_Node->ComponentTemplate->GetOuter(), SCS_Node->ComponentTemplate)) + { + VariableNameEditableTextBox->SetError(FText::Format(LOCTEXT("ComponentVariableRenameFailed_InUse", "{0} is in use by another variable or function!"), InNewText)); + return; + } } TSharedPtr VariableNameValidator = MakeShareable(new FKismetNameValidator(GetBlueprintObj(), CachedNodePtr->GetVariableName())); - EValidatorResult ValidatorResult = VariableNameValidator->IsValid(InNewText.ToString()); + EValidatorResult ValidatorResult = VariableNameValidator->IsValid(NewTextStr); if(ValidatorResult == EValidatorResult::AlreadyInUse) { VariableNameEditableTextBox->SetError(FText::Format(LOCTEXT("ComponentVariableRenameFailed_InUse", "{0} is in use by another variable or function!"), InNewText)); @@ -5759,8 +5887,8 @@ void FBlueprintComponentDetails::OnTooltipTextCommitted(const FText& NewText, ET bool FBlueprintComponentDetails::OnVariableCategoryChangeEnabled() const { check(CachedNodePtr.IsValid()); - - return !CachedNodePtr->CanRename(); + + return !CachedNodePtr->IsInherited(); } FText FBlueprintComponentDetails::OnGetVariableCategoryText() const @@ -5899,7 +6027,7 @@ void FBlueprintComponentDetails::OnBrowseSocket() if (ParentFNode.IsValid()) { - if (USceneComponent* ParentSceneComponent = Cast(ParentFNode->GetEditableComponentTemplate(Editor->GetBlueprint()))) + if (USceneComponent* ParentSceneComponent = Cast(ParentFNode->GetOrCreateEditableComponentTemplate(Editor->GetBlueprint()))) { if (ParentSceneComponent->HasAnySockets()) { diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.h b/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.h index 86bcdc3259a8..1be7ce51a1be 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.h +++ b/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.h @@ -224,6 +224,13 @@ private: ECheckBoxState OnGetMultilineCheckboxState() const; void OnMultilineChanged(ECheckBoxState InNewState); + EVisibility GetDeprecatedVisibility() const; + ECheckBoxState OnGetDeprecatedCheckboxState() const; + void OnDeprecatedChanged(ECheckBoxState InNewState); + + FText GetDeprecationMessageText() const; + void OnDeprecationMessageTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit, FName VarName); + /** Refresh the property flags list */ void RefreshPropertyFlags(); @@ -244,6 +251,9 @@ private: /** Returns TRUE if the Variable is inherited by the current Blueprint */ bool IsVariableInheritedByBlueprint() const; + + /** Returns TRUE if the Variable is marked as deprecated */ + bool IsVariableDeprecated() const; private: /** Pointer back to my parent tab */ TWeakPtr MyBlueprint; @@ -385,12 +395,6 @@ private: void SetEntryNode(); UMulticastDelegateProperty* GetDelegateProperty() const; - FText OnGetTooltipText() const; - void OnTooltipTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit); - FText OnGetCategoryText() const; - void OnCategoryTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit); - TSharedRef< ITableRow > MakeCategoryViewWidget( TSharedPtr Item, const TSharedRef< STableViewBase >& OwnerTable ); - void OnCategorySelectionChanged( TSharedPtr ProposedSelection, ESelectInfo::Type /*SelectInfo*/ ); void CollectAvailibleSignatures(); void OnFunctionSelected(TSharedPtr FunctionItemData, ESelectInfo::Type SelectInfo); @@ -398,14 +402,6 @@ private: EVisibility OnGetSectionTextVisibility(TWeakPtr RowWidget) const; private: - - /** A list of all category names to choose from */ - TArray> CategorySource; - - /** Widgets for the categories */ - TWeakPtr CategoryComboButton; - TWeakPtr>> CategoryListView; - TArray> FunctionsToCopySignatureFrom; TSharedPtr CopySignatureComboButton; }; @@ -609,6 +605,12 @@ private: /** Enables/Disables selected event as editor callable */ void OnEditorCallableEventModified( const ECheckBoxState NewCheckedState ) const; + bool IsFunctionDeprecated() const; + ECheckBoxState OnGetDeprecatedCheckboxState() const; + void OnDeprecatedChanged(ECheckBoxState InNewState); + + FText GetDeprecationMessageText() const; + void OnDeprecationMessageTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit); FReply OnAddNewOutputClicked(); diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp index a3b49761fac1..b48e8a23734c 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp @@ -154,6 +154,11 @@ #include "Widgets/Notifications/SNotificationList.h" #include "NativeCodeGenerationTool.h" +// Focusing related nodes feature +#include "Preferences/BlueprintEditorOptions.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Widgets/Input/SNumericEntryBox.h" + #define LOCTEXT_NAMESPACE "BlueprintEditor" ///////////////////////////////////////////////////// @@ -952,7 +957,7 @@ void FBlueprintEditor::OnSelectionUpdated(const TArrayGetEditableComponentTemplate(GetBlueprintObj()); + UActorComponent* EditableComponent = NodePtr->GetOrCreateEditableComponentTemplate(GetBlueprintObj()); if (EditableComponent) { InspectorTitle = FText::FromString(NodePtr->GetDisplayString()); @@ -1483,7 +1488,8 @@ void FBlueprintEditor::OnChangeBreadCrumbGraph(UEdGraph* InGraph) } FBlueprintEditor::FBlueprintEditor() - : bSaveIntermediateBuildProducts(false) + : EditorOptions(nullptr) + , bSaveIntermediateBuildProducts(false) , bPendingDeferredClose(false) , bRequestedSavingOpenDocumentState(false) , bBlueprintModifiedOnOpen (false) @@ -1491,6 +1497,9 @@ FBlueprintEditor::FBlueprintEditor() , bIsActionMenuContextSensitive(true) , CurrentUISelection(NAME_None) , bEditorMarkedAsClosed(false) + , bHideUnrelatedNodes(false) + , bLockNodeFadeState(false) + , bSelectRegularNode(false) , HasOpenActionMenu(nullptr) , InstructionsFadeCountdown(0.f) { @@ -1505,7 +1514,7 @@ FBlueprintEditor::FBlueprintEditor() AnalyticsStats.NodePasteCreateCount = 0; UEditorEngine* Editor = (UEditorEngine*)GEngine; - if (Editor != NULL) + if (Editor != nullptr) { Editor->RegisterForUndo(this); } @@ -1758,6 +1767,11 @@ void FBlueprintEditor::InitBlueprintEditor( // TRUE if a single Blueprint is being opened and is marked as newly created bool bNewlyCreated = InBlueprints.Num() == 1 && InBlueprints[0]->bIsNewlyCreated; + EditorOptions = nullptr; + + // Load editor settings from disk. + LoadEditorSettings(); + TArray< UObject* > Objects; for (UBlueprint* Blueprint : InBlueprints) { @@ -1785,6 +1799,42 @@ void FBlueprintEditor::InitBlueprintEditor( InitalizeExtenders(); + struct Local + { + static void FillToolbar(FToolBarBuilder& ToolbarBuilder, const TSharedRef< FUICommandList > ToolkitCommands, FBlueprintEditor* BlueprintEditor) + { + ToolbarBuilder.BeginSection("Graph"); + { + ToolbarBuilder.AddToolBarButton( + FBlueprintEditorCommands::Get().ToggleHideUnrelatedNodes, + NAME_None, + TAttribute(), + TAttribute(), + FSlateIcon(FEditorStyle::GetStyleSetName(), "GraphEditor.ToggleHideUnrelatedNodes") + ); + ToolbarBuilder.AddComboButton( + FUIAction(), + FOnGetContent::CreateSP(BlueprintEditor, &FBlueprintEditor::MakeHideUnrelatedNodesOptionsMenu), + LOCTEXT("HideUnrelatedNodesOptions", "Focus Related Nodes Options"), + LOCTEXT("HideUnrelatedNodesOptionsMenu", "Focus Related Nodes options menu"), + TAttribute(), + true + ); + } + ToolbarBuilder.EndSection(); + } + }; + + TSharedPtr ToolbarExtender = MakeShareable(new FExtender); + ToolbarExtender->AddToolBarExtension( + "Asset", + EExtensionHook::After, + GetToolkitCommands(), + FToolBarExtensionDelegate::CreateStatic( &Local::FillToolbar, GetToolkitCommands(), this ) + ); + + AddToolbarExtender(ToolbarExtender); + RegenerateMenusAndToolbars(); RegisterApplicationModes(InBlueprints, bShouldOpenInDefaultsMode, bNewlyCreated); @@ -2259,6 +2309,8 @@ FBlueprintEditor::~FBlueprintEditor() FEngineAnalytics::GetProvider().RecordEvent( FString( "Editor.Usage.BPDisallowedPinConnection" ), BPEditorPinConnectAttribs ); } } + + SaveEditorSettings(); } void FBlueprintEditor::FocusInspectorOnGraphSelection(const FGraphPanelSelectionSet& NewSelection, bool bForceRefresh) @@ -2667,6 +2719,13 @@ void FBlueprintEditor::CreateDefaultCommands() FGraphEditorCommands::Get().ClearAllQuickJumps, FExecuteAction::CreateSP(this, &FBlueprintEditor::ClearAllGraphEditorQuickJumps) ); + + ToolkitCommands->MapAction( + FBlueprintEditorCommands::Get().ToggleHideUnrelatedNodes, + FExecuteAction::CreateSP(this, &FBlueprintEditor::ToggleHideUnrelatedNodes), + FCanExecuteAction(), + FIsActionChecked::CreateSP(this, &FBlueprintEditor::IsToggleHideUnrelatedNodesChecked) + ); } void FBlueprintEditor::OpenNativeCodeGenerationTool() @@ -3043,6 +3102,11 @@ void FBlueprintEditor::OnGraphEditorFocused(const TSharedRef& InGr } } + if (bHideUnrelatedNodes && SelectedNodes.Num() <= 0) + { + ResetAllNodesUnrelatedStates(); + } + // If the bookmarks view is active, check whether or not we're restricting the view to the current graph. If we are, update the tree to reflect the focused graph context. if (BookmarksWidget.IsValid() && GetDefault()->bShowBookmarksForCurrentDocumentOnlyInTab) @@ -3169,6 +3233,28 @@ void FBlueprintEditor::OnSelectedNodesChangedImpl(const FGraphPanelSelectionSet& } Inspector->ShowDetailsForObjects(NewSelection.Array()); + + + bSelectRegularNode = false; + for (FGraphPanelSelectionSet::TConstIterator It(NewSelection); It; ++It) + { + UEdGraphNode_Comment* SeqNode = Cast(*It); + if (!SeqNode) + { + bSelectRegularNode = true; + break; + } + } + + if (bHideUnrelatedNodes && !bLockNodeFadeState) + { + ResetAllNodesUnrelatedStates(); + + if ( bSelectRegularNode ) + { + HideUnrelatedNodes(); + } + } } void FBlueprintEditor::OnBlueprintChangedImpl(UBlueprint* InBlueprint, bool bIsJustBeingCompiled ) @@ -3594,6 +3680,8 @@ void FBlueprintEditor::AddReferencedObjects( FReferenceCollector& Collector ) } } + Collector.AddReferencedObject(EditorOptions); + UserDefinedStructures.Remove(TWeakObjectPtr()); // Remove NULLs for (const TWeakObjectPtr& ObjectPtr : UserDefinedStructures) { @@ -3604,6 +3692,11 @@ void FBlueprintEditor::AddReferencedObjects( FReferenceCollector& Collector ) } } +FString FBlueprintEditor::GetReferencerName() const +{ + return TEXT("FBlueprintEditor"); +} + bool FBlueprintEditor::IsNodeTitleVisible(const UEdGraphNode* Node, bool bRequestRename) { TSharedPtr GraphEditor; @@ -6263,6 +6356,301 @@ ECheckBoxState FBlueprintEditor::CheckEnabledStateForSelectedNodes(ENodeEnabledS return Result; } +void FBlueprintEditor::UpdateNodesUnrelatedStatesAfterGraphChange() +{ + if (bHideUnrelatedNodes && !bLockNodeFadeState && bSelectRegularNode) + { + ResetAllNodesUnrelatedStates(); + + HideUnrelatedNodes(); + } +} + +void FBlueprintEditor::ResetAllNodesUnrelatedStates() +{ + TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); + + if (FocusedGraphEd.IsValid()) + { + FocusedGraphEd->ResetAllNodesUnrelatedStates(); + } +} + +void FBlueprintEditor::CollectExecDownstreamNodes(UEdGraphNode* CurrentNode, TArray& CollectedNodes) +{ + const UEdGraphSchema_K2* K2Schema = GetDefault(); + + TArray AllPins = CurrentNode->GetAllPins(); + + for (auto& Pin : AllPins) + { + if (Pin->Direction == EGPD_Output && Pin->PinType.PinCategory == K2Schema->PC_Exec) + { + for (auto& Link : Pin->LinkedTo) + { + UEdGraphNode* LinkedNode = Cast(Link->GetOwningNode()); + if (LinkedNode && !CollectedNodes.Contains(LinkedNode)) + { + CollectedNodes.Add(LinkedNode); + CollectExecDownstreamNodes( LinkedNode, CollectedNodes ); + } + } + } + } +} + +void FBlueprintEditor::CollectExecUpstreamNodes(UEdGraphNode* CurrentNode, TArray& CollectedNodes) +{ + const UEdGraphSchema_K2* K2Schema = GetDefault(); + + TArray AllPins = CurrentNode->GetAllPins(); + + for (auto& Pin : AllPins) + { + if (Pin->Direction == EGPD_Input && Pin->PinType.PinCategory == K2Schema->PC_Exec) + { + for (auto& Link : Pin->LinkedTo) + { + UEdGraphNode* LinkedNode = Cast(Link->GetOwningNode()); + if (LinkedNode && !CollectedNodes.Contains(LinkedNode)) + { + CollectedNodes.Add(LinkedNode); + CollectExecUpstreamNodes( LinkedNode, CollectedNodes ); + } + } + } + } +} + +void FBlueprintEditor::CollectPureDownstreamNodes(UEdGraphNode* CurrentNode, TArray& CollectedNodes) +{ + const UEdGraphSchema_K2* K2Schema = GetDefault(); + + TArray AllPins = CurrentNode->GetAllPins(); + + for (auto& Pin : AllPins) + { + if (Pin->Direction == EGPD_Output && Pin->PinType.PinCategory != K2Schema->PC_Exec) + { + for (auto& Link : Pin->LinkedTo) + { + UK2Node* LinkedNode = Cast(Link->GetOwningNode()); + if (LinkedNode && !CollectedNodes.Contains(LinkedNode)) + { + CollectedNodes.Add(LinkedNode); + if (LinkedNode->IsNodePure()) + { + CollectPureDownstreamNodes( LinkedNode, CollectedNodes ); + } + } + } + } + } +} + +void FBlueprintEditor::CollectPureUpstreamNodes(UEdGraphNode* CurrentNode, TArray& CollectedNodes) +{ + const UEdGraphSchema_K2* K2Schema = GetDefault(); + + TArray AllPins = CurrentNode->GetAllPins(); + + for (auto& Pin : AllPins) + { + if (Pin->Direction == EGPD_Input && Pin->PinType.PinCategory != K2Schema->PC_Exec) + { + for (auto& Link : Pin->LinkedTo) + { + UK2Node* LinkedNode = Cast(Link->GetOwningNode()); + if (LinkedNode && !CollectedNodes.Contains(LinkedNode)) + { + CollectedNodes.Add(LinkedNode); + if (LinkedNode->IsNodePure()) + { + CollectPureUpstreamNodes( LinkedNode, CollectedNodes ); + } + } + } + } + } +} + +void FBlueprintEditor::HideUnrelatedNodes() +{ + TArray NodesToShow; + + TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); + if (FocusedGraphEd.IsValid()) + { + FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); + + TArray ImpureNodes = SelectedNodes.Array().FilterByPredicate([](UObject* Node){ + UK2Node* K2Node = Cast(Node); + if (K2Node) + { + return !(K2Node->IsNodePure()); + } + return false; + }); + + TArray PureNodes = SelectedNodes.Array().FilterByPredicate([](UObject* Node){ + UK2Node* K2Node = Cast(Node); + if (K2Node) + { + return K2Node->IsNodePure(); + } + // Treat a node which can't cast to an UK2Node as a pure node (like a document node or a commment node) + // Make sure all selected nodes are handled + return true; + }); + + for (auto Node : ImpureNodes) + { + UEdGraphNode* SelectedNode = Cast(Node); + + if (SelectedNode) + { + NodesToShow.Add(SelectedNode); + CollectExecDownstreamNodes( SelectedNode, NodesToShow ); + CollectExecUpstreamNodes( SelectedNode, NodesToShow ); + CollectPureDownstreamNodes( SelectedNode, NodesToShow ); + CollectPureUpstreamNodes( SelectedNode, NodesToShow ); + } + } + + for (auto Node : PureNodes) + { + UEdGraphNode* SelectedNode = Cast(Node); + + if (SelectedNode) + { + NodesToShow.Add(SelectedNode); + CollectPureDownstreamNodes( SelectedNode, NodesToShow ); + CollectPureUpstreamNodes( SelectedNode, NodesToShow ); + } + } + + TArray AllNodes = FocusedGraphEd->GetCurrentGraph()->Nodes; + + TArray CommentNodes; + TArray RelatedNodes; + + for (auto& Node : AllNodes) + { + if (NodesToShow.Contains(Cast(Node))) + { + Node->SetNodeUnrelated(false); + RelatedNodes.Add(Node); + } + else + { + if (UEdGraphNode_Comment* CommentNode = Cast(Node)) + { + CommentNodes.Add(Node); + } + else + { + Node->SetNodeUnrelated(true); + } + } + } + + if (FocusedGraphEd.IsValid()) + { + FocusedGraphEd->FocusCommentNodes(CommentNodes, RelatedNodes); + } + } +} + +void FBlueprintEditor::ToggleHideUnrelatedNodes() +{ + bHideUnrelatedNodes = !bHideUnrelatedNodes; + + ResetAllNodesUnrelatedStates(); + + if (bHideUnrelatedNodes && bSelectRegularNode) + { + HideUnrelatedNodes(); + } + else + { + bLockNodeFadeState = false; + } +} + +bool FBlueprintEditor::IsToggleHideUnrelatedNodesChecked() const +{ + return bHideUnrelatedNodes == true; +} + +TSharedRef FBlueprintEditor::MakeHideUnrelatedNodesOptionsMenu() +{ + const bool bShouldCloseWindowAfterMenuSelection = true; + FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, GetToolkitCommands() ); + + TSharedRef OptionsHeading = SNew(SBox) + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(LOCTEXT("FocusRelatedOptions", "Focus Related Options")) + .TextStyle(FEditorStyle::Get(), "Menu.Heading") + ] + ]; + + TSharedRef LockNodeStateCheckBox = SNew(SBox) + [ + SNew(SCheckBox) + .IsChecked(bLockNodeFadeState ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + .OnCheckStateChanged(this, &FBlueprintEditor::OnLockNodeStateCheckStateChanged) + .Style(FEditorStyle::Get(), "Menu.CheckBox") + .ToolTipText(LOCTEXT("LockNodeStateCheckBoxToolTip", "Lock the current state of all nodes.")) + .Content() + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .Padding(2.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("LockNodeState", "Lock Node State")) + ] + ] + ]; + + MenuBuilder.AddWidget(OptionsHeading, FText::GetEmpty(), true); + + MenuBuilder.AddMenuEntry(FUIAction(), LockNodeStateCheckBox); + + return MenuBuilder.MakeWidget(); +} + +void FBlueprintEditor::LoadEditorSettings() +{ + EditorOptions = NewObject(); + + if (EditorOptions->bHideUnrelatedNodes) + { + ToggleHideUnrelatedNodes(); + } +} + +void FBlueprintEditor::SaveEditorSettings() +{ + if ( EditorOptions ) + { + EditorOptions->bHideUnrelatedNodes = bHideUnrelatedNodes; + EditorOptions->SaveConfig(); + } +} + +void FBlueprintEditor::OnLockNodeStateCheckStateChanged(ECheckBoxState NewCheckedState) +{ + bLockNodeFadeState = (NewCheckedState == ECheckBoxState::Checked) ? true : false; +} + void FBlueprintEditor::ToggleSaveIntermediateBuildProducts() { bSaveIntermediateBuildProducts = !bSaveIntermediateBuildProducts; @@ -8158,7 +8546,7 @@ bool FBlueprintEditor::IsFocusedGraphEditable() const void FBlueprintEditor::TryInvokingDetailsTab(bool bFlash) { - if ( TabManager->CanSpawnTab(FBlueprintEditorTabs::DetailsID) ) + if ( TabManager->HasTabSpawner(FBlueprintEditorTabs::DetailsID) ) { TSharedPtr BlueprintTab = FGlobalTabmanager::Get()->GetMajorTabForTabManager(TabManager.ToSharedRef()); diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintEditorCommands.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintEditorCommands.cpp index ca736aea7f0c..a51c557c2f9c 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintEditorCommands.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintEditorCommands.cpp @@ -74,6 +74,9 @@ void FBlueprintEditorCommands::RegisterCommands() // SCC commands UI_COMMAND( BeginBlueprintMerge, "Merge", "Shows the Blueprint merge panel and toolbar, allowing the user to resolve conflicted blueprints", EUserInterfaceActionType::Button, FInputChord() ); + + // Hide unrelated nodes + UI_COMMAND( ToggleHideUnrelatedNodes, "Hide Unrelated", "Toggles automatically hiding nodes which are unrelated to the selected nodes.", EUserInterfaceActionType::ToggleButton, FInputChord() ); } PRAGMA_ENABLE_OPTIMIZATION diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintEditorCommands.h b/Engine/Source/Editor/Kismet/Private/BlueprintEditorCommands.h index 631d9a215eb2..7b7b90130ed1 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintEditorCommands.h +++ b/Engine/Source/Editor/Kismet/Private/BlueprintEditorCommands.h @@ -75,6 +75,9 @@ public: // SSC commands TSharedPtr< FUICommandInfo > BeginBlueprintMerge; + + // Toggle focusing nodes which are related to the selected nodes + TSharedPtr< FUICommandInfo > ToggleHideUnrelatedNodes; }; ////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintEditorModule.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintEditorModule.cpp index 27adb503fd79..cfd0c6ed0650 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintEditorModule.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintEditorModule.cpp @@ -29,6 +29,7 @@ #include "BlueprintGraphPanelPinFactory.h" #include "WatchPointViewer.h" #include "KismetCompiler.h" +#include "KismetWidgets.h" #define LOCTEXT_NAMESPACE "BlueprintEditor" @@ -181,6 +182,8 @@ void FBlueprintEditorModule::StartupModule() auto& MenuExtenders = LevelEditorModule.GetAllLevelViewportContextMenuExtenders(); MenuExtenders.Add(LevelViewportContextMenuBlueprintExtender); LevelViewportContextMenuBlueprintExtenderDelegateHandle = MenuExtenders.Last().GetHandle(); + + FModuleManager::Get().LoadModuleChecked("KismetWidgets"); } FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked("MessageLog"); diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintEditorTabFactories.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintEditorTabFactories.cpp index ebbbb82403da..ce137a562397 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintEditorTabFactories.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintEditorTabFactories.cpp @@ -197,7 +197,7 @@ TSharedRef FDefaultsEditorSummoner::CreateOptionalDataOnlyMessage() con [ SNew(SHyperlink) .Style(FEditorStyle::Get(), "Common.GotoBlueprintHyperlink") - .OnNavigate(this, &FDefaultsEditorSummoner::OnChangeBlueprintToNotDataOnly) + .OnNavigate(const_cast(this), &FDefaultsEditorSummoner::OnChangeBlueprintToNotDataOnly) .Text(LOCTEXT("FullEditor", "Open Full Blueprint Editor")) .ToolTipText(LOCTEXT("FullEditorToolTip", "This opens the blueprint in the full editor.")) ] diff --git a/Engine/Source/Editor/Kismet/Private/DetailsDiff.cpp b/Engine/Source/Editor/Kismet/Private/DetailsDiff.cpp index bae1c4a35d4e..cc0f7d2d01d1 100644 --- a/Engine/Source/Editor/Kismet/Private/DetailsDiff.cpp +++ b/Engine/Source/Editor/Kismet/Private/DetailsDiff.cpp @@ -14,6 +14,7 @@ FDetailsDiff::FDetailsDiff(const UObject* InObject, FOnDisplayedPropertiesChange { FDetailsViewArgs DetailsViewArgs; DetailsViewArgs.bShowDifferingPropertiesOption = true; + DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea; FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor"); DetailsView = EditModule.CreateDetailView(DetailsViewArgs); @@ -62,7 +63,7 @@ TArray FDetailsDiff::GetDisplayedProperties() const return Ret; } -void FDetailsDiff::DiffAgainst(const FDetailsDiff& Newer, TArray< FSingleObjectDiffEntry > &OutDifferences) const +void FDetailsDiff::DiffAgainst(const FDetailsDiff& Newer, TArray< FSingleObjectDiffEntry > &OutDifferences, bool bSortByDisplayOrder) const { TSharedPtr< class IDetailsView > OldDetailsView = DetailsView; TSharedPtr< class IDetailsView > NewDetailsView = Newer.DetailsView; @@ -115,4 +116,101 @@ void FDetailsDiff::DiffAgainst(const FDetailsDiff& Newer, TArray< FSingleObjectD } } } + + if (bSortByDisplayOrder) + { + TArray AllDifferingProperties = OutDifferences; + OutDifferences.Reset(); + + // OrderedProperties will contain differences in the order they are displayed: + TArray< const FSingleObjectDiffEntry* > OrderedProperties; + + // create differing properties list based on what is displayed by the old properties.. + TArray SoftOldProperties = GetDisplayedProperties(); + TArray SoftNewProperties = Newer.GetDisplayedProperties(); + + const auto FindAndPushDiff = [&OrderedProperties, &AllDifferingProperties](const FPropertySoftPath& PropertyIdentifier) -> bool + { + bool bDiffers = false; + for (const auto& Difference : AllDifferingProperties) + { + if (Difference.Identifier == PropertyIdentifier) + { + bDiffers = true; + // if there are any nested differences associated with PropertyIdentifier, add those + // as well: + OrderedProperties.AddUnique(&Difference); + } + else if (Difference.Identifier.IsSubPropertyMatch(PropertyIdentifier)) + { + bDiffers = true; + OrderedProperties.AddUnique(&Difference); + } + } + return bDiffers; + }; + + // zip the two sets of properties, zip iterators are not trivial to write in c++, + // so this procedural stuff will have to do: + int IterOld = 0; + int IterNew = 0; + while (IterOld < SoftOldProperties.Num() || IterNew < SoftNewProperties.Num()) + { + const bool bOldIterValid = IterOld < SoftOldProperties.Num(); + const bool bNewIterValid = IterNew < SoftNewProperties.Num(); + + // We've reached the end of the new list, but still have properties in the old list. + // Continue over the old list to catch any remaining diffs. + if (bOldIterValid && !bNewIterValid) + { + FindAndPushDiff(SoftOldProperties[IterOld]); + ++IterOld; + } + // We've reached the end of the old list, but still have properties in the new list. + // Continue over the new list to catch any remaining diffs. + else if (!bOldIterValid && bNewIterValid) + { + FindAndPushDiff(SoftNewProperties[IterNew]); + ++IterNew; + } + else + { + // If both properties have the same path, check to ensure the property hasn't changed. + if (SoftOldProperties[IterOld] == SoftNewProperties[IterNew]) + { + FindAndPushDiff(SoftOldProperties[IterOld]); + ++IterNew; + ++IterOld; + } + else + { + // If the old property is different, add it to the list and increment the old iter. + // This indicates the property was removed. + if (FindAndPushDiff(SoftOldProperties[IterOld])) + { + ++IterOld; + } + // If the new property is different, add it to the list and increment the new iter. + // This indicates the property was added. + else if (FindAndPushDiff(SoftNewProperties[IterNew])) + { + ++IterNew; + } + // Neither property was different. + // This indicates the iterators were just out of step from a previous addition or removal. + else + { + ++IterOld; + ++IterNew; + } + } + } + } + + // Readd to OutDifferences + for (const FSingleObjectDiffEntry* Difference : OrderedProperties) + { + OutDifferences.Add(*Difference); + } + } } diff --git a/Engine/Source/Editor/Kismet/Private/DiffUtils.cpp b/Engine/Source/Editor/Kismet/Private/DiffUtils.cpp index 5c8b2c9147a6..74d216219091 100644 --- a/Engine/Source/Editor/Kismet/Private/DiffUtils.cpp +++ b/Engine/Source/Editor/Kismet/Private/DiffUtils.cpp @@ -2,6 +2,7 @@ #include "DiffUtils.h" #include "UObject/PropertyPortFlags.h" +#include "UObject/Package.h" #include "Widgets/Images/SImage.h" #include "EditorStyleSet.h" #include "ISourceControlProvider.h" @@ -11,6 +12,7 @@ #include "Engine/Blueprint.h" #include "IAssetTypeActions.h" #include "ObjectEditorUtils.h" +#include "Components/ActorComponent.h" namespace UE4DiffUtils_Private { @@ -32,25 +34,30 @@ namespace UE4DiffUtils_Private return nullptr; } - FPropertySoftPathSet GetPropertyNameSet(const UObject* ForObj) + FPropertySoftPathSet GetPropertyNameSet(const UStruct* ForStruct) { - return FPropertySoftPathSet(DiffUtils::GetVisiblePropertiesInOrderDeclared(ForObj)); + return FPropertySoftPathSet(DiffUtils::GetVisiblePropertiesInOrderDeclared(ForStruct)); } } FResolvedProperty FPropertySoftPath::Resolve(const UObject* Object) const +{ + return Resolve(Object->GetClass(), Object); +} + +FResolvedProperty FPropertySoftPath::Resolve(const UStruct* Struct, const void* StructData) const { // dig into the object, finding nested objects, etc: - const void* CurrentBlock = Object; - const UStruct* NextClass = Object->GetClass(); + const void* CurrentBlock = StructData; + const UStruct* NextClass = Struct; const void* NextBlock = CurrentBlock; const UProperty* Property = nullptr; - for( int32 i = 0; i < PropertyChain.Num(); ++i ) + for (int32 i = 0; i < PropertyChain.Num(); ++i) { CurrentBlock = NextBlock; - const UProperty* NextProperty = UE4DiffUtils_Private::Resolve(NextClass, PropertyChain[i]); - if( NextProperty ) + const UProperty* NextProperty = UE4DiffUtils_Private::Resolve(NextClass, PropertyChain[i].PropertyName); + if (NextProperty) { Property = NextProperty; if (const UObjectProperty* ObjectProperty = Cast(Property)) @@ -101,11 +108,11 @@ FPropertyPath FPropertySoftPath::ResolvePath(const UObject* Object) const } }; - auto TryReadIndex = [](const TArray& LocalPropertyChain, int32& OutIndex) -> int32 + auto TryReadIndex = [](const TArray& LocalPropertyChain, int32& OutIndex) -> int32 { if(OutIndex + 1 < LocalPropertyChain.Num()) { - FString AsString = LocalPropertyChain[OutIndex + 1].ToString(); + FString AsString = LocalPropertyChain[OutIndex + 1].DisplayString; if(AsString.IsNumeric()) { ++OutIndex; @@ -121,7 +128,7 @@ FPropertyPath FPropertySoftPath::ResolvePath(const UObject* Object) const FPropertyPath Ret; for( int32 I = 0; I < PropertyChain.Num(); ++I ) { - FName PropertyIdentifier = PropertyChain[I]; + FName PropertyIdentifier = PropertyChain[I].PropertyName; UProperty* ResolvedProperty = UE4DiffUtils_Private::Resolve(ContainerStruct, PropertyIdentifier); FPropertyInfo Info(ResolvedProperty, INDEX_NONE); @@ -186,7 +193,7 @@ FPropertyPath FPropertySoftPath::ResolvePath(const UObject* Object) const // we have an index, but are we looking into a key or value? Peek ahead to find out: if(ensure(I + 1 < PropertyChain.Num())) { - if(PropertyChain[I+1] == MapProperty->KeyProp->GetFName()) + if(PropertyChain[I+1].PropertyName == MapProperty->KeyProp->GetFName()) { ++I; @@ -195,7 +202,7 @@ FPropertyPath FPropertySoftPath::ResolvePath(const UObject* Object) const FPropertyInfo MakKeyInfo(MapProperty->KeyProp, RealIndex); Ret.AddProperty(MakKeyInfo); } - else if(ensure( PropertyChain[I+1] == MapProperty->ValueProp->GetFName() )) + else if(ensure( PropertyChain[I+1].PropertyName == MapProperty->ValueProp->GetFName() )) { ++I; @@ -245,9 +252,9 @@ FPropertyPath FPropertySoftPath::ResolvePath(const UObject* Object) const FString FPropertySoftPath::ToDisplayName() const { FString Ret; - for( FName Property : PropertyChain ) + for( FChainElement Element : PropertyChain ) { - FString PropertyAsString = Property.ToString(); + FString PropertyAsString = Element.DisplayString; if(Ret.IsEmpty()) { Ret.Append(PropertyAsString); @@ -278,23 +285,23 @@ const UObject* DiffUtils::GetCDO(const UBlueprint* ForBlueprint) return ForBlueprint->GeneratedClass->ClassDefaultObject; } -void DiffUtils::CompareUnrelatedObjects(const UObject* A, const UObject* B, TArray& OutDifferingProperties) +void DiffUtils::CompareUnrelatedStructs(const UStruct* StructA, const void* A, const UStruct* StructB, const void* B, TArray& OutDifferingProperties) { - FPropertySoftPathSet PropertiesInA = UE4DiffUtils_Private::GetPropertyNameSet(A); - FPropertySoftPathSet PropertiesInB = UE4DiffUtils_Private::GetPropertyNameSet(B); + FPropertySoftPathSet PropertiesInA = UE4DiffUtils_Private::GetPropertyNameSet(StructA); + FPropertySoftPathSet PropertiesInB = UE4DiffUtils_Private::GetPropertyNameSet(StructB); // any properties in A that aren't in B are differing: auto AddedToA = PropertiesInA.Difference(PropertiesInB).Array(); - for( const auto& Entry : AddedToA ) + for (const auto& Entry : AddedToA) { - OutDifferingProperties.Push(FSingleObjectDiffEntry( Entry, EPropertyDiffType::PropertyAddedToA )); + OutDifferingProperties.Push(FSingleObjectDiffEntry(Entry, EPropertyDiffType::PropertyAddedToA)); } // and the converse: auto AddedToB = PropertiesInB.Difference(PropertiesInA).Array(); for (const auto& Entry : AddedToB) { - OutDifferingProperties.Push(FSingleObjectDiffEntry( Entry, EPropertyDiffType::PropertyAddedToB )); + OutDifferingProperties.Push(FSingleObjectDiffEntry(Entry, EPropertyDiffType::PropertyAddedToB)); } // for properties in common, dig out the uproperties and determine if they're identical: @@ -303,8 +310,8 @@ void DiffUtils::CompareUnrelatedObjects(const UObject* A, const UObject* B, TArr FPropertySoftPathSet Common = PropertiesInA.Intersect(PropertiesInB); for (const auto& PropertyName : Common) { - FResolvedProperty AProp = PropertyName.Resolve(A); - FResolvedProperty BProp = PropertyName.Resolve(B); + FResolvedProperty AProp = PropertyName.Resolve(StructA, A); + FResolvedProperty BProp = PropertyName.Resolve(StructB, B); check(AProp != FResolvedProperty() && BProp != FResolvedProperty()); TArray DifferingSubProperties; @@ -319,6 +326,14 @@ void DiffUtils::CompareUnrelatedObjects(const UObject* A, const UObject* B, TArr } } +void DiffUtils::CompareUnrelatedObjects(const UObject* A, const UObject* B, TArray& OutDifferingProperties) +{ + if (A && B) + { + return CompareUnrelatedStructs(A->GetClass(), A, B->GetClass(), B, OutDifferingProperties); + } +} + void DiffUtils::CompareUnrelatedSCS(const UBlueprint* Old, const TArray< FSCSResolvedIdentifier >& OldHierarchy, const UBlueprint* New, const TArray< FSCSResolvedIdentifier >& NewHierarchy, FSCSDiffRoot& OutDifferingEntries ) { const auto FindEntry = [](TArray< FSCSResolvedIdentifier > const& InArray, const FSCSIdentifier* Value) -> const FSCSResolvedIdentifier* @@ -569,21 +584,30 @@ static void IdenticalHelper(const UProperty* AProperty, const UProperty* BProper return; } - // dig into the objects if they are in the same package as our initial object: const UObjectProperty* BPropAsObject = CastChecked(BProperty); const UObject* A = *((const UObject* const*)AValue); const UObject* B = *((const UObject* const*)BValue); + // dig into the objects if they are in the same package as our initial object: if(BPropAsObject->HasAnyPropertyFlags(CPF_InstancedReference) && APropAsObject->HasAnyPropertyFlags(CPF_InstancedReference) && A && B && A->GetClass() == B->GetClass()) { - // dive into the object to find actual differences: const UClass* AClass = A->GetClass(); // BClass and AClass are identical! - for (TFieldIterator PropertyIt(AClass); PropertyIt; ++PropertyIt) + // We only want to recurse if this is EditInlineNew and not a component + // Other instanced refs are likely to form a type-specific web so recursion doesn't make sense and won't be displayed properly in the details pane + if (AClass->HasAnyClassFlags(CLASS_EditInlineNew) && !AClass->IsChildOf(UActorComponent::StaticClass())) { - const UProperty* ClassProp = *PropertyIt; - IdenticalHelper(ClassProp, ClassProp, ClassProp->ContainerPtrToValuePtr(A, 0), ClassProp->ContainerPtrToValuePtr(B, 0), FPropertySoftPath(RootPath, ClassProp), DifferingSubProperties); + for (TFieldIterator PropertyIt(AClass); PropertyIt; ++PropertyIt) + { + const UProperty* ClassProp = *PropertyIt; + IdenticalHelper(ClassProp, ClassProp, ClassProp->ContainerPtrToValuePtr(A, 0), ClassProp->ContainerPtrToValuePtr(B, 0), FPropertySoftPath(RootPath, ClassProp), DifferingSubProperties); + } + } + else if (A->GetFName() != B->GetFName()) + { + // If the names don't match, report that as a difference as the object was likely changed + DifferingSubProperties.Push(RootPath); } } else @@ -626,34 +650,22 @@ bool DiffUtils::Identical(const FResolvedProperty& AProp, const FResolvedPropert return DifferingProperties.Num() == 0; } -TArray DiffUtils::GetVisiblePropertiesInOrderDeclared(const UObject* ForObj, const TArray& Scope /*= TArray()*/) +TArray DiffUtils::GetVisiblePropertiesInOrderDeclared(const UStruct* ForStruct, const FPropertySoftPath& Scope /*= TArray()*/) { TArray Ret; - if (ForObj) + if (ForStruct) { - const UClass* Class = ForObj->GetClass(); - TSet HiddenCategories = FEditorCategoryUtils::GetHiddenCategories(Class); - for (TFieldIterator PropertyIt(Class); PropertyIt; ++PropertyIt) + TSet HiddenCategories = FEditorCategoryUtils::GetHiddenCategories(ForStruct); + for (TFieldIterator PropertyIt(ForStruct); PropertyIt; ++PropertyIt) { FName CategoryName = FObjectEditorUtils::GetCategoryFName(*PropertyIt); if (!HiddenCategories.Contains(CategoryName.ToString())) { if (PropertyIt->PropertyFlags&CPF_Edit) { - TArray NewPath(Scope); - NewPath.Push(PropertyIt->GetFName()); - if (const UObjectProperty* ObjectProperty = Cast(*PropertyIt)) - { - const UObject* const* BaseObject = reinterpret_cast( ObjectProperty->ContainerPtrToValuePtr(ForObj) ); - if (BaseObject && *BaseObject) - { - Ret.Append( GetVisiblePropertiesInOrderDeclared(*BaseObject, NewPath) ); - } - } - else - { - Ret.Push(NewPath); - } + // We don't need to recurse into objects/structs as those will be picked up in the Identical check later + FPropertySoftPath NewPath(Scope, *PropertyIt); + Ret.Push(NewPath); } } } @@ -701,103 +713,53 @@ TSharedPtr FBlueprintDifferenceTreeEntry::NoDiffe ) ); } -TSharedPtr FBlueprintDifferenceTreeEntry::AnimBlueprintEntry() +TSharedPtr FBlueprintDifferenceTreeEntry::UnknownDifferencesEntry() { - // For now, a widget and a short message explaining that differences in the AnimGraph are - // not detected by the diff tool: + // Warn about there being unknown differences const auto GenerateWidget = []() -> TSharedRef { return SNew(STextBlock) .ColorAndOpacity(FLinearColor(.7f, .7f, .7f)) .TextStyle(FEditorStyle::Get(), TEXT("BlueprintDif.ItalicText")) - .Text(NSLOCTEXT("FBlueprintDifferenceTreeEntry", "AnimBlueprintsNotSupported", "Warning: Detecting differences in Animation Blueprint specific data is not yet supported...")); - }; - - TArray< TSharedPtr > Children; - Children.Emplace( TSharedPtr(new FBlueprintDifferenceTreeEntry( - FOnDiffEntryFocused() - , FGenerateDiffEntryWidget::CreateStatic(GenerateWidget) - , TArray< TSharedPtr >() - )) ); - - const auto CreateAnimGraphRootEntry = []() -> TSharedRef - { - return SNew(STextBlock) - .ToolTipText(NSLOCTEXT("FBlueprintDifferenceTreeEntry", "AnimGraphTooltip", "Detecting differences in Animation Blueprint specific data is not yet supported")) - .ColorAndOpacity(DiffViewUtils::LookupColor(true)) - .Text(NSLOCTEXT("FBlueprintDifferenceTreeEntry", "AnimGraphLabel", "Animation Blueprint")); + .Text(NSLOCTEXT("FBlueprintDifferenceTreeEntry", "BlueprintTypeNotSupported", "Warning: Detecting differences in this Blueprint type specific data is not yet supported...")); }; return TSharedPtr(new FBlueprintDifferenceTreeEntry( - FOnDiffEntryFocused() - , FGenerateDiffEntryWidget::CreateStatic(CreateAnimGraphRootEntry) - , Children - )); -} - -TSharedPtr FBlueprintDifferenceTreeEntry::WidgetBlueprintEntry() -{ - // For now, a widget and a short message explaining that differences in the WidgetTree are - // not detected by the diff tool: - const auto GenerateWidget = []() -> TSharedRef - { - return SNew(STextBlock) - .ColorAndOpacity(FLinearColor(.7f, .7f, .7f)) - .TextStyle(FEditorStyle::Get(), TEXT("BlueprintDif.ItalicText")) - .Text(NSLOCTEXT("FBlueprintDifferenceTreeEntry", "WidgetTreeNotSupported", "Warning: Detecting differences in Widget Blueprint specific data is not yet supported...")); - }; - - TArray< TSharedPtr > Children; - Children.Emplace(TSharedPtr(new FBlueprintDifferenceTreeEntry( FOnDiffEntryFocused() , FGenerateDiffEntryWidget::CreateStatic(GenerateWidget) , TArray< TSharedPtr >() - ))); - - const auto CreateWidgetTreeRootEntry = []() -> TSharedRef - { - return SNew(STextBlock) - .ToolTipText(NSLOCTEXT("FBlueprintDifferenceTreeEntry", "WidgetTreeTooltip", "Detecting differences in Widget Blueprint specific data is not yet supported")) - .ColorAndOpacity(DiffViewUtils::LookupColor(true)) - .Text(NSLOCTEXT("FBlueprintDifferenceTreeEntry", "WidgetTreeLabel", "Widget Blueprint")); - }; - - return TSharedPtr(new FBlueprintDifferenceTreeEntry( - FOnDiffEntryFocused() - , FGenerateDiffEntryWidget::CreateStatic(CreateWidgetTreeRootEntry) - , Children - )); + )); } -TSharedPtr FBlueprintDifferenceTreeEntry::CreateDefaultsCategoryEntry(FOnDiffEntryFocused FocusCallback, const TArray< TSharedPtr >& Children, bool bHasDifferences) +TSharedPtr FBlueprintDifferenceTreeEntry::CreateCategoryEntry(const FText& LabelText, const FText& ToolTipText, FOnDiffEntryFocused FocusCallback, const TArray< TSharedPtr >& Children, bool bHasDifferences) { - const auto CreateDefaultsRootEntry = [](FLinearColor Color) -> TSharedRef + const auto CreateDefaultsRootEntry = [](FText LabelText, FText ToolTipText, FLinearColor Color) -> TSharedRef { return SNew(STextBlock) - .ToolTipText(NSLOCTEXT("FBlueprintDifferenceTreeEntry", "DefaultsTooltip", "The list of changes made in the Defaults panel")) + .ToolTipText(ToolTipText) .ColorAndOpacity(Color) - .Text(NSLOCTEXT("FBlueprintDifferenceTreeEntry", "DefaultsLabel", "Defaults")); + .Text(LabelText); }; return TSharedPtr(new FBlueprintDifferenceTreeEntry( FocusCallback - , FGenerateDiffEntryWidget::CreateStatic(CreateDefaultsRootEntry, DiffViewUtils::LookupColor(bHasDifferences) ) + , FGenerateDiffEntryWidget::CreateStatic(CreateDefaultsRootEntry, LabelText, ToolTipText, DiffViewUtils::LookupColor(bHasDifferences)) , Children )); } -TSharedPtr FBlueprintDifferenceTreeEntry::CreateDefaultsCategoryEntryForMerge(FOnDiffEntryFocused FocusCallback, const TArray< TSharedPtr >& Children, bool bHasRemoteDifferences, bool bHasLocalDifferences, bool bHasConflicts) +TSharedPtr FBlueprintDifferenceTreeEntry::CreateCategoryEntryForMerge(const FText& LabelText, const FText& ToolTipText, FOnDiffEntryFocused FocusCallback, const TArray< TSharedPtr >& Children, bool bHasRemoteDifferences, bool bHasLocalDifferences, bool bHasConflicts) { - const auto CreateDefaultsRootEntry = [](bool bInHasRemoteDifferences, bool bInHasLocalDifferences, bool bInHasConflicts) -> TSharedRef + const auto CreateDefaultsRootEntry = [](FText LabelText, FText ToolTipText, bool bInHasRemoteDifferences, bool bInHasLocalDifferences, bool bInHasConflicts) -> TSharedRef { const FLinearColor BaseColor = DiffViewUtils::LookupColor(bInHasRemoteDifferences || bInHasLocalDifferences, bInHasConflicts); return SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SNew(STextBlock) - .ToolTipText(NSLOCTEXT("FBlueprintDifferenceTreeEntry", "DefaultsTooltip", "The list of changes made in the Defaults panel")) + .ToolTipText(ToolTipText) .ColorAndOpacity(BaseColor) - .Text(NSLOCTEXT("FBlueprintDifferenceTreeEntry", "DefaultsLabel", "Defaults")) + .Text(LabelText) ] + DiffViewUtils::Box(true, DiffViewUtils::LookupColor(bInHasRemoteDifferences, bInHasConflicts)) + DiffViewUtils::Box(true, BaseColor) @@ -806,49 +768,7 @@ TSharedPtr FBlueprintDifferenceTreeEntry::CreateD return TSharedPtr(new FBlueprintDifferenceTreeEntry( FocusCallback - , FGenerateDiffEntryWidget::CreateStatic(CreateDefaultsRootEntry, bHasRemoteDifferences, bHasLocalDifferences, bHasConflicts) - , Children - )); -} - -TSharedPtr FBlueprintDifferenceTreeEntry::CreateComponentsCategoryEntry(FOnDiffEntryFocused FocusCallback, const TArray< TSharedPtr >& Children, bool bHasDifferences) -{ - const auto CreateComponentsRootEntry = [](FLinearColor Color) -> TSharedRef - { - return SNew(STextBlock) - .ToolTipText(NSLOCTEXT("FBlueprintDifferenceTreeEntry", "SCSTooltip", "The list of changes made in the Components panel")) - .ColorAndOpacity(Color) - .Text(NSLOCTEXT("FBlueprintDifferenceTreeEntry", "SCSLabel", "Components")); - }; - - return TSharedPtr(new FBlueprintDifferenceTreeEntry( - FocusCallback - , FGenerateDiffEntryWidget::CreateStatic(CreateComponentsRootEntry, DiffViewUtils::LookupColor(bHasDifferences)) - , Children - )); -} - -TSharedPtr FBlueprintDifferenceTreeEntry::CreateComponentsCategoryEntryForMerge(FOnDiffEntryFocused FocusCallback, const TArray< TSharedPtr >& Children, bool bHasRemoteDifferences, bool bHasLocalDifferences, bool bHasConflicts) -{ - const auto CreateComponentsRootEntry = [](bool bInHasRemoteDifferences, bool bInHasLocalDifferences, bool bInHasConflicts) -> TSharedRef - { - const FLinearColor BaseColor = DiffViewUtils::LookupColor(bInHasRemoteDifferences || bInHasLocalDifferences, bInHasConflicts); - return SNew(SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(STextBlock) - .ToolTipText(NSLOCTEXT("FBlueprintDifferenceTreeEntry", "SCSTooltip", "The list of changes made in the Components panel")) - .ColorAndOpacity(BaseColor) - .Text(NSLOCTEXT("FBlueprintDifferenceTreeEntry", "SCSLabel", "Components")) - ] - + DiffViewUtils::Box(true, DiffViewUtils::LookupColor(bInHasRemoteDifferences, bInHasConflicts)) - + DiffViewUtils::Box(true, BaseColor) - + DiffViewUtils::Box(true, DiffViewUtils::LookupColor(bInHasLocalDifferences, bInHasConflicts)); - }; - - return TSharedPtr(new FBlueprintDifferenceTreeEntry( - FocusCallback - , FGenerateDiffEntryWidget::CreateStatic(CreateComponentsRootEntry, bHasRemoteDifferences, bHasLocalDifferences, bHasConflicts) + , FGenerateDiffEntryWidget::CreateStatic(CreateDefaultsRootEntry, LabelText, ToolTipText, bHasRemoteDifferences, bHasLocalDifferences, bHasConflicts) , Children )); } @@ -863,7 +783,7 @@ TSharedRef< STreeView > > DiffTreeVi ]; }; - const auto ChildrenAccessor = [](TSharedPtr InTreeItem, TArray< TSharedPtr< FBlueprintDifferenceTreeEntry > >& OutChildren, TArray< TSharedPtr >* MasterList) + const auto ChildrenAccessor = [](TSharedPtr InTreeItem, TArray< TSharedPtr< FBlueprintDifferenceTreeEntry > >& OutChildren) { OutChildren = InTreeItem->Children; }; @@ -878,7 +798,7 @@ TSharedRef< STreeView > > DiffTreeVi return SNew(STreeView< TSharedPtr< FBlueprintDifferenceTreeEntry > >) .OnGenerateRow(STreeView< TSharedPtr< FBlueprintDifferenceTreeEntry > >::FOnGenerateRow::CreateStatic(RowGenerator)) - .OnGetChildren(STreeView< TSharedPtr< FBlueprintDifferenceTreeEntry > >::FOnGetChildren::CreateStatic(ChildrenAccessor, DifferencesList)) + .OnGetChildren(STreeView< TSharedPtr< FBlueprintDifferenceTreeEntry > >::FOnGetChildren::CreateStatic(ChildrenAccessor)) .OnSelectionChanged(STreeView< TSharedPtr< FBlueprintDifferenceTreeEntry > >::FOnSelectionChanged::CreateStatic(Selector)) .TreeItemsSource(DifferencesList); } @@ -1056,19 +976,37 @@ FText DiffViewUtils::GetPanelLabel(const UBlueprint* Blueprint, const FRevisionI , FText::FromString(Revision.Date.ToString(TEXT("%m/%d/%Y")))); } - return FText::Format( NSLOCTEXT("DiffViewUtils", "RevisionLabel", "{0}\n{1}\n{2}") - , Label - , FText::FromString( Blueprint->GetName() ) - , RevisionData ); + if (Label.IsEmpty()) + { + return FText::Format(NSLOCTEXT("DiffViewUtils", "RevisionLabelTwoLines", "{0}\n{1}") + , FText::FromString(Blueprint->GetName()) + , RevisionData); + } + else + { + return FText::Format(NSLOCTEXT("DiffViewUtils", "RevisionLabel", "{0}\n{1}\n{2}") + , Label + , FText::FromString(Blueprint->GetName()) + , RevisionData); + } } else { if( Blueprint ) { - return FText::Format( NSLOCTEXT("DiffViewUtils", "RevisionLabel", "{0}\n{1}\n{2}") - , Label - , FText::FromString( Blueprint->GetName() ) - , NSLOCTEXT("DiffViewUtils", "LocalRevisionLabel", "Local Revision" )); + if (Label.IsEmpty()) + { + return FText::Format(NSLOCTEXT("DiffViewUtils", "RevisionLabelTwoLines", "{0}\n{1}") + , FText::FromString(Blueprint->GetName()) + , NSLOCTEXT("DiffViewUtils", "LocalRevisionLabel", "Local Revision")); + } + else + { + return FText::Format(NSLOCTEXT("DiffViewUtils", "RevisionLabel", "{0}\n{1}\n{2}") + , Label + , FText::FromString(Blueprint->GetName()) + , NSLOCTEXT("DiffViewUtils", "LocalRevisionLabel", "Local Revision")); + } } return NSLOCTEXT("DiffViewUtils", "NoBlueprint", "None" ); diff --git a/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp b/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp index 8794b64c7e39..6a4b18ef5210 100644 --- a/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp +++ b/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp @@ -2588,7 +2588,7 @@ void FFindInBlueprintSearchManager::EnableGlobalFindResults(bool bEnable) for (int32 TabIdx = 0; TabIdx < ARRAY_COUNT(GlobalFindResultsTabIDs); TabIdx++) { const FName TabID = GlobalFindResultsTabIDs[TabIdx]; - if (!GlobalTabManager->CanSpawnTab(TabID)) + if (!GlobalTabManager->HasTabSpawner(TabID)) { const FText DisplayName = FText::Format(LOCTEXT("GlobalFindResultsDisplayName", "Find in Blueprints {0}"), FText::AsNumber(TabIdx + 1)); @@ -2623,7 +2623,7 @@ void FFindInBlueprintSearchManager::EnableGlobalFindResults(bool bEnable) for (int32 TabIdx = 0; TabIdx < ARRAY_COUNT(GlobalFindResultsTabIDs); TabIdx++) { const FName TabID = GlobalFindResultsTabIDs[TabIdx]; - if (GlobalTabManager->CanSpawnTab(TabID)) + if (GlobalTabManager->HasTabSpawner(TabID)) { GlobalTabManager->UnregisterNomadTabSpawner(TabID); } @@ -2644,7 +2644,7 @@ void FFindInBlueprintSearchManager::CloseOrphanedGlobalFindResultsTabs(TSharedPt for (int32 TabIdx = 0; TabIdx < ARRAY_COUNT(GlobalFindResultsTabIDs); TabIdx++) { const FName TabID = GlobalFindResultsTabIDs[TabIdx]; - if (!FGlobalTabmanager::Get()->CanSpawnTab(TabID)) + if (!FGlobalTabmanager::Get()->HasTabSpawner(TabID)) { TSharedPtr OrphanedTab = TabManager->FindExistingLiveTab(FTabId(TabID)); if (OrphanedTab.IsValid()) diff --git a/Engine/Source/Editor/Kismet/Private/FindInBlueprints.cpp b/Engine/Source/Editor/Kismet/Private/FindInBlueprints.cpp index 06e10cfd7cd2..bae3079d182d 100644 --- a/Engine/Source/Editor/Kismet/Private/FindInBlueprints.cpp +++ b/Engine/Source/Editor/Kismet/Private/FindInBlueprints.cpp @@ -418,7 +418,7 @@ FReply FFindInBlueprintsProperty::OnClick() UBlueprint* Blueprint = GetParentBlueprint(); if (Blueprint) { - TSharedPtr BlueprintEditor = FKismetEditorUtilities::GetIBlueprintEditorForObject(Blueprint, false); + TSharedPtr BlueprintEditor = FKismetEditorUtilities::GetIBlueprintEditorForObject(Blueprint, true); if (BlueprintEditor.IsValid()) { diff --git a/Engine/Source/Editor/Kismet/Private/ImaginaryBlueprintData.cpp b/Engine/Source/Editor/Kismet/Private/ImaginaryBlueprintData.cpp index 11a4f4803739..edb8b716af8c 100644 --- a/Engine/Source/Editor/Kismet/Private/ImaginaryBlueprintData.cpp +++ b/Engine/Source/Editor/Kismet/Private/ImaginaryBlueprintData.cpp @@ -35,7 +35,10 @@ FText FSearchableValueInfo::GetDisplayText(const TMap& InLookupTab AsyncTask(ENamedThreads::GameThread, [TableId, &Promise]() { FName ResolvedTableId = TableId; - IStringTableEngineBridge::FullyLoadStringTableAsset(ResolvedTableId); // Trigger the asset load + if (IStringTableEngineBridge::CanFindOrLoadStringTableAsset()) + { + IStringTableEngineBridge::FullyLoadStringTableAsset(ResolvedTableId); // Trigger the asset load + } Promise.SetValue(true); // Signal completion }); diff --git a/Engine/Source/Editor/Kismet/Private/SBlueprintDiff.cpp b/Engine/Source/Editor/Kismet/Private/SBlueprintDiff.cpp index cb8bf5eb3d54..b0e86eba13c8 100644 --- a/Engine/Source/Editor/Kismet/Private/SBlueprintDiff.cpp +++ b/Engine/Source/Editor/Kismet/Private/SBlueprintDiff.cpp @@ -8,16 +8,15 @@ #include "Widgets/Layout/SSpacer.h" #include "Framework/MultiBox/MultiBoxDefs.h" #include "Framework/MultiBox/MultiBoxBuilder.h" -#include "Framework/Docking/TabManager.h" #include "EditorStyleSet.h" #include "Animation/AnimBlueprint.h" #include "K2Node_MathExpression.h" #include "Kismet2/BlueprintEditorUtils.h" +#include "Kismet2/KismetEditorUtilities.h" #include "BlueprintEditorModes.h" #include "DetailsDiff.h" #include "EdGraphUtilities.h" #include "GraphDiffControl.h" -#include "Widgets/Docking/SDockTab.h" #include "SMyBlueprint.h" #include "SCSDiff.h" #include "WorkflowOrientedApp/SModeWidget.h" @@ -33,356 +32,21 @@ class IDiffControl { public: virtual ~IDiffControl() {} + + /** Adds widgets to the tree of differences to show */ + virtual void GenerateTreeEntries(TArray< TSharedPtr >& OutTreeEntries, TArray< TSharedPtr >& OutRealDifferences) = 0; }; FText RightRevision = LOCTEXT("OlderRevisionIdentifier", "Right Revision"); typedef TMap< FName, const UProperty* > FNamePropertyMap; -const FName DiffMyBluerpintTabId = FName(TEXT("DiffMyBluerpintTab")); -const FName DiffGraphTabId = FName(TEXT("DiffGraphTab")); - -DECLARE_DELEGATE(FOnSCSDiffControlChanged); - -// Each difference in the tree will either be a tree node that is added in one Blueprint -// or a tree node and an FName of a property that has been added or edited in one Blueprint -class FSCSDiffControl : public TSharedFromThis< FSCSDiffControl > - , public IDiffControl -{ -public: - FSCSDiffControl( - const UBlueprint* InOldBlueprint - , const UBlueprint* InNewBlueprint - , TArray< TSharedPtr >& OutTreeEntries - , TArray< TSharedPtr >& OutRealDifferences - , FOnSCSDiffControlChanged SelectionCallback - ); - - - TSharedRef OldTreeWidget() { return OldSCS.TreeWidget(); } - TSharedRef NewTreeWidget() { return NewSCS.TreeWidget(); } - - virtual ~FSCSDiffControl() {} -private: - FSCSDiffRoot DifferingProperties; - - FSCSDiff OldSCS; - FSCSDiff NewSCS; -}; - -FSCSDiffControl::FSCSDiffControl( - const UBlueprint* InOldBlueprint - , const UBlueprint* InNewBlueprint - , TArray< TSharedPtr >& OutTreeEntries - , TArray< TSharedPtr >& OutRealDifferences - , FOnSCSDiffControlChanged SelectionCallback - ) - : DifferingProperties() - , OldSCS( InOldBlueprint ) - , NewSCS( InNewBlueprint ) -{ - TArray< FSCSResolvedIdentifier > OldHierarchy = OldSCS.GetDisplayedHierarchy(); - TArray< FSCSResolvedIdentifier > NewHierarchy = NewSCS.GetDisplayedHierarchy(); - DiffUtils::CompareUnrelatedSCS(InOldBlueprint, OldHierarchy, InNewBlueprint, NewHierarchy, DifferingProperties); - - const auto FocusSCSDifferenceEntry = [](FSCSDiffEntry Entry, FOnSCSDiffControlChanged InSelectionCallback, FSCSDiffControl* Owner) - { - InSelectionCallback.ExecuteIfBound(); - if (Entry.TreeIdentifier.Name != NAME_None) - { - Owner->OldSCS.HighlightProperty(Entry.TreeIdentifier.Name, FPropertyPath()); - Owner->NewSCS.HighlightProperty(Entry.TreeIdentifier.Name, FPropertyPath()); - } - }; - - const auto CreateSCSDifferenceWidget = [](FSCSDiffEntry Entry, FText ObjectName) -> TSharedRef - { - return SNew(STextBlock) - .Text(DiffViewUtils::SCSDiffMessage(Entry,ObjectName)) - .ColorAndOpacity(DiffViewUtils::Differs()); - }; - - TArray< TSharedPtr > Children; - for( auto Difference : DifferingProperties.Entries ) - { - auto Entry = TSharedPtr( - new FBlueprintDifferenceTreeEntry( - FOnDiffEntryFocused::CreateStatic(FocusSCSDifferenceEntry, Difference, SelectionCallback, this) - , FGenerateDiffEntryWidget::CreateStatic(CreateSCSDifferenceWidget, Difference, RightRevision) - , TArray< TSharedPtr >() - ) - ); - Children.Push(Entry); - OutRealDifferences.Push(Entry); - } - - const bool bHasDiffferences = Children.Num() != 0; - if (!bHasDiffferences) - { - // make one child informing the user that there are no differences: - Children.Push(FBlueprintDifferenceTreeEntry::NoDifferencesEntry()); - } - - const auto ForwardSelection = [](FOnSCSDiffControlChanged InSelectionCallback) - { - // This allows the owning control to focus the correct tab (or do whatever else it likes): - InSelectionCallback.ExecuteIfBound(); - }; - - OutTreeEntries.Push(FBlueprintDifferenceTreeEntry::CreateComponentsCategoryEntry( - FOnDiffEntryFocused::CreateStatic(ForwardSelection, SelectionCallback), - Children, - bHasDiffferences - )); -} - -DECLARE_DELEGATE(FOnCDODiffControlChanged); - -class FCDODiffControl : public TSharedFromThis - , public IDiffControl -{ -public: - FCDODiffControl( const UObject* InOldCDO - , const UObject* InNewCDO - , TArray< TSharedPtr >& OutTreeEntries - , TArray< TSharedPtr >& OutRealDifferences - , FOnCDODiffControlChanged SelectionCallback ); - - TSharedRef OldDetailsWidget() { return OldDetails.DetailsWidget(); } - TSharedRef NewDetailsWidget() { return NewDetails.DetailsWidget(); } - - virtual ~FCDODiffControl() {} - -private: - void HighlightDifference(const FPropertySoftPath& PropertyName); - void UpdateDisplayedDifferences(TArray< FSingleObjectDiffEntry > const& InDifferences, TArray< TSharedPtr >& OutTreeEntries, FOnCDODiffControlChanged SelectionCallback); - - FDetailsDiff OldDetails; - FDetailsDiff NewDetails; - - int CurrentDifference; -}; - -FCDODiffControl::FCDODiffControl( - const UObject* InOldCDO - , const UObject* InNewCDO - , TArray< TSharedPtr >& OutTreeEntries - , TArray< TSharedPtr >& OutRealDifferences - , FOnCDODiffControlChanged SelectionCallback ) - : OldDetails(InOldCDO, FDetailsDiff::FOnDisplayedPropertiesChanged() ) - , NewDetails(InNewCDO, FDetailsDiff::FOnDisplayedPropertiesChanged() ) -{ - TArray< FSingleObjectDiffEntry > DifferingProperties; - OldDetails.DiffAgainst(NewDetails, DifferingProperties); - - // OrderedProperties will contain differences in the order they are displayed: - TArray< const FSingleObjectDiffEntry* > OrderedProperties; - - // create differing properties list based on what is displayed by the old properties.. - TArray OldProperties = OldDetails.GetDisplayedProperties(); - TArray NewProperties = NewDetails.GetDisplayedProperties(); - - const auto FindAndPushDiff = [&OrderedProperties, &DifferingProperties](const FPropertySoftPath& PropertyIdentifier) -> bool - { - bool bDiffers = false; - for (const auto& Difference : DifferingProperties) - { - if (Difference.Identifier == PropertyIdentifier) - { - bDiffers = true; - // if there are any nested differences associated with PropertyIdentifier, add those - // as well: - OrderedProperties.AddUnique(&Difference); - } - else if (Difference.Identifier.IsSubPropertyMatch(PropertyIdentifier)) - { - bDiffers = true; - OrderedProperties.AddUnique(&Difference); - } - } - return bDiffers; - }; - - // zip the two sets of properties, zip iterators are not trivial to write in c++, - // so this procedural stuff will have to do: - int IterOld = 0; - int IterNew = 0; - while (IterOld < OldProperties.Num() || IterNew < NewProperties.Num()) - { - const bool bOldIterValid = IterOld < OldProperties.Num(); - const bool bNewIterValid = IterNew < NewProperties.Num(); - - // We've reached the end of the new list, but still have properties in the old list. - // Continue over the old list to catch any remaining diffs. - if (bOldIterValid && !bNewIterValid) - { - FindAndPushDiff(OldProperties[IterOld]); - ++IterOld; - } - // We've reached the end of the old list, but still have properties in the new list. - // Continue over the new list to catch any remaining diffs. - else if (!bOldIterValid && bNewIterValid) - { - FindAndPushDiff(NewProperties[IterNew]); - ++IterNew; - } - else - { - // If both properties have the same path, check to ensure the property hasn't changed. - if (OldProperties[IterOld] == NewProperties[IterNew]) - { - FindAndPushDiff(OldProperties[IterOld]); - ++IterNew; - ++IterOld; - } - else - { - // If the old property is different, add it to the list and increment the old iter. - // This indicates the property was removed. - if (FindAndPushDiff(OldProperties[IterOld])) - { - ++IterOld; - } - // If the new property is different, add it to the list and increment the new iter. - // This indicates the property was added. - else if (FindAndPushDiff(NewProperties[IterNew])) - { - ++IterNew; - } - // Neither property was different. - // This indicates the iterators were just out of step from a previous addition or removal. - else - { - ++IterOld; - ++IterNew; - } - } - } - } - - const auto CreateCDODifferenceWidget = [](FSingleObjectDiffEntry DiffEntry, FText ObjectName) -> TSharedRef - { - return SNew(STextBlock) - .Text(DiffViewUtils::PropertyDiffMessage(DiffEntry, ObjectName)) - .ColorAndOpacity(DiffViewUtils::Differs()); - }; - - const auto FocusDetailsDifferenceEntry = [](FPropertySoftPath Identifier, FCDODiffControl* Control, FOnCDODiffControlChanged InSelectionCallback) - { - // This allows the owning control to focus the correct tab (or do whatever else it likes): - InSelectionCallback.ExecuteIfBound(); - Control->HighlightDifference(Identifier); - }; - - TArray< TSharedPtr > Children; - - for (auto Difference : OrderedProperties) - { - auto Entry = TSharedPtr( - new FBlueprintDifferenceTreeEntry( - FOnDiffEntryFocused::CreateStatic(FocusDetailsDifferenceEntry, Difference->Identifier, this, SelectionCallback) - , FGenerateDiffEntryWidget::CreateStatic(CreateCDODifferenceWidget, *Difference, RightRevision) - , TArray< TSharedPtr >() - ) - ); - Children.Push(Entry); - OutRealDifferences.Push(Entry); - } - - const bool bHasDiffferences = Children.Num() != 0; - FLinearColor Color = bHasDiffferences ? DiffViewUtils::Differs() : DiffViewUtils::Identical(); - if (!bHasDiffferences) - { - // make one child informing the user that there are no differences: - Children.Push(FBlueprintDifferenceTreeEntry::NoDifferencesEntry() ); - } - - const auto ForwardSelection = [](FOnCDODiffControlChanged InSelectionCallback) - { - // This allows the owning control to focus the correct tab (or do whatever else it likes): - InSelectionCallback.ExecuteIfBound(); - }; - - OutTreeEntries.Push(FBlueprintDifferenceTreeEntry::CreateDefaultsCategoryEntry( - FOnDiffEntryFocused::CreateStatic(ForwardSelection, SelectionCallback) - , Children - , bHasDiffferences - )); -} - -void FCDODiffControl::HighlightDifference(const FPropertySoftPath& PropertyName) -{ - OldDetails.HighlightProperty(PropertyName); - NewDetails.HighlightProperty(PropertyName); -} - -/*List item that entry for a graph*/ -struct KISMET_API FListItemGraphToDiff : public TSharedFromThis -{ - FListItemGraphToDiff(class SBlueprintDiff* Diff, class UEdGraph* GraphOld, class UEdGraph* GraphNew, const FRevisionInfo& RevisionOld, const FRevisionInfo& RevisionNew); - virtual ~FListItemGraphToDiff(); - - /*Generate Widget for list item*/ - TSharedRef GenerateWidget(); - - /*Get tooltip for list item */ - FText GetToolTip(); - - /*Get old(left) graph*/ - UEdGraph* GetGraphOld()const{ return GraphOld; } - - /*Get new(right) graph*/ - UEdGraph* GetGraphNew()const{ return GraphNew; } - - /** Source for list view */ - TArray> DiffListSource; -private: - /*Diff widget*/ - class SBlueprintDiff* Diff; - - /*The old graph(left)*/ - class UEdGraph* GraphOld; - - /*The new graph(right)*/ - class UEdGraph* GraphNew; - - /*Description of Old and new graph*/ - FRevisionInfo RevisionOld, RevisionNew; - - ////////////////////////////////////////////////////////////////////////// - // Diff list - ////////////////////////////////////////////////////////////////////////// - - typedef TSharedPtr FSharedDiffOnGraph; - typedef SListView SListViewType; - -public: - - /** Called when the Newer Graph is modified*/ - void OnGraphChanged(const FEdGraphEditAction& Action); - - /** Generate list of differences*/ - TSharedRef GenerateDiffListWidget(); - - /** Build up the Diff Source Array*/ - void BuildDiffSourceArray(); - - /** Called when user clicks on a new graph list item */ - void OnSelectionChanged(FSharedDiffOnGraph Item, ESelectInfo::Type SelectionType); - -private: - /** Get Index of the current diff that is selected */ - int32 GetCurrentDiffIndex() const; - - /* Called when a new row is being generated */ - TSharedRef OnGenerateRow(FSharedDiffOnGraph ParamItem, const TSharedRef& OwnerTable); - - /** ListView of differences */ - TSharedPtr DiffList; - - /** Handle to the registered OnGraphChanged delegate. */ - FDelegateHandle OnGraphChangedDelegateHandle; -}; +const FName BlueprintTypeMode = FName(TEXT("BlueprintTypeMode")); +const FName MyBlueprintMode = FName(TEXT("MyBlueprintMode")); +const FName DefaultsMode = FName(TEXT("DefaultsMode")); +const FName ClassSettingsMode = FName(TEXT("ClassSettingsMode")); +const FName ComponentsMode = FName(TEXT("ComponentsMode")); +const FName GraphMode = FName(TEXT("GraphMode")); TSharedRef FDiffResultItem::GenerateWidget() const { @@ -400,21 +64,625 @@ TSharedRef FDiffResultItem::GenerateWidget() const .Text(Text); } -FListItemGraphToDiff::FListItemGraphToDiff( class SBlueprintDiff* InDiff, class UEdGraph* InGraphOld, class UEdGraph* InGraphNew, const FRevisionInfo& InRevisionOld, const FRevisionInfo& InRevisionNew ) - : Diff(InDiff), GraphOld(InGraphOld), GraphNew(InGraphNew), RevisionOld(InRevisionOld), RevisionNew(InRevisionNew) +static TSharedRef GenerateObjectDiffWidget(FSingleObjectDiffEntry DiffEntry, FText ObjectName) +{ + return SNew(STextBlock) + .Text(DiffViewUtils::PropertyDiffMessage(DiffEntry, ObjectName)) + .ToolTipText(DiffViewUtils::PropertyDiffMessage(DiffEntry, ObjectName)) + .ColorAndOpacity(DiffViewUtils::Differs()); +} + +static TSharedRef GenerateSimpleDiffWidget(FText DiffText) +{ + return SNew(STextBlock) + .Text(DiffText) + .ToolTipText(DiffText) + .ColorAndOpacity(DiffViewUtils::Differs()); +}; + +/** Shows all differences for the blueprint structure itself that aren't picked up elsewhere */ +class FMyBlueprintDiffControl : public TSharedFromThis, public IDiffControl +{ +public: + FMyBlueprintDiffControl(const UBlueprint* InOldBlueprint, const UBlueprint* InNewBlueprint, FOnDiffEntryFocused InSelectionCallback) + : SelectionCallback(MoveTemp(InSelectionCallback)), OldBlueprint(InOldBlueprint), NewBlueprint(InNewBlueprint) + { + } + + virtual void GenerateTreeEntries(TArray< TSharedPtr >& OutTreeEntries, TArray< TSharedPtr >& OutRealDifferences) override + { + TArray< TSharedPtr > Children; + + if (OldBlueprint && NewBlueprint) + { + for (TFieldIterator PropertyIt(OldBlueprint->SkeletonGeneratedClass); PropertyIt; ++PropertyIt) + { + UProperty* OldProperty = *PropertyIt; + UProperty* NewProperty = NewBlueprint->SkeletonGeneratedClass->FindPropertyByName(OldProperty->GetFName()); + + FText PropertyText = FText::FromString(OldProperty->GetAuthoredName()); + + if (NewProperty) + { + const int32 OldVarIndex = FBlueprintEditorUtils::FindNewVariableIndex(OldBlueprint, OldProperty->GetFName()); + const int32 NewVarIndex = FBlueprintEditorUtils::FindNewVariableIndex(NewBlueprint, OldProperty->GetFName()); + + if (OldVarIndex != INDEX_NONE && NewVarIndex != INDEX_NONE) + { + TArray DifferingProperties; + DiffUtils::CompareUnrelatedStructs(FBPVariableDescription::StaticStruct(), &OldBlueprint->NewVariables[OldVarIndex], FBPVariableDescription::StaticStruct(), &NewBlueprint->NewVariables[NewVarIndex], DifferingProperties); + for (const FSingleObjectDiffEntry& Difference : DifferingProperties) + { + TSharedPtr Entry = MakeShared( + SelectionCallback, + FGenerateDiffEntryWidget::CreateStatic(&GenerateObjectDiffWidget, Difference, PropertyText)); + Children.Push(Entry); + OutRealDifferences.Push(Entry); + } + } + } + else + { + FText DiffText = FText::Format(LOCTEXT("VariableRemoved", "Removed Variable {0}"), PropertyText); + + TSharedPtr Entry = MakeShared( + SelectionCallback, + FGenerateDiffEntryWidget::CreateStatic(&GenerateSimpleDiffWidget, DiffText)); + + Children.Push(Entry); + OutRealDifferences.Push(Entry); + } + } + + for (TFieldIterator PropertyIt(NewBlueprint->SkeletonGeneratedClass); PropertyIt; ++PropertyIt) + { + UProperty* NewProperty = *PropertyIt; + UProperty* OldProperty = OldBlueprint->SkeletonGeneratedClass->FindPropertyByName(NewProperty->GetFName()); + + if (!OldProperty) + { + FText DiffText = FText::Format(LOCTEXT("VariableAdded", "Added Variable {0}"), FText::FromString(NewProperty->GetAuthoredName())); + + TSharedPtr Entry = MakeShared( + SelectionCallback, + FGenerateDiffEntryWidget::CreateStatic(&GenerateSimpleDiffWidget, DiffText)); + + Children.Push(Entry); + OutRealDifferences.Push(Entry); + } + } + } + const bool bHasDifferences = Children.Num() != 0; + if (!bHasDifferences) + { + // make one child informing the user that there are no differences: + Children.Push(FBlueprintDifferenceTreeEntry::NoDifferencesEntry()); + } + + OutTreeEntries.Push(FBlueprintDifferenceTreeEntry::CreateCategoryEntry( + NSLOCTEXT("FBlueprintDifferenceTreeEntry", "MyBlueprintLabel", "My Blueprint"), + NSLOCTEXT("FBlueprintDifferenceTreeEntry", "MyBlueprintTooltip", "The list of changes made to blueprint structure in the My Blueprint panel"), + SelectionCallback, + Children, + bHasDifferences + )); + } + +private: + FOnDiffEntryFocused SelectionCallback; + const UBlueprint* OldBlueprint; + const UBlueprint* NewBlueprint; +}; + +/** + * Each difference in the tree will either be a tree node that is added in one Blueprint + * or a tree node and an FName of a property that has been added or edited in one Blueprint + */ +class FSCSDiffControl : public TSharedFromThis, public IDiffControl +{ +public: + FSCSDiffControl(const UBlueprint* InOldBlueprint, const UBlueprint* InNewBlueprint, FOnDiffEntryFocused InSelectionCallback) + : SelectionCallback(InSelectionCallback) + , OldSCS(InOldBlueprint) + , NewSCS(InNewBlueprint) + { + } + + virtual void GenerateTreeEntries(TArray< TSharedPtr >& OutTreeEntries, TArray< TSharedPtr >& OutRealDifferences) override + { + TArray< FSCSResolvedIdentifier > OldHierarchy = OldSCS.GetDisplayedHierarchy(); + TArray< FSCSResolvedIdentifier > NewHierarchy = NewSCS.GetDisplayedHierarchy(); + DiffUtils::CompareUnrelatedSCS(OldSCS.GetBlueprint(), OldHierarchy, NewSCS.GetBlueprint(), NewHierarchy, DifferingProperties); + + const auto FocusSCSDifferenceEntry = [](FSCSDiffEntry Entry, FOnDiffEntryFocused InSelectionCallback, FSCSDiffControl* Owner) + { + InSelectionCallback.ExecuteIfBound(); + if (Entry.TreeIdentifier.Name != NAME_None) + { + Owner->OldSCS.HighlightProperty(Entry.TreeIdentifier.Name, FPropertyPath()); + Owner->NewSCS.HighlightProperty(Entry.TreeIdentifier.Name, FPropertyPath()); + } + }; + + const auto CreateSCSDifferenceWidget = [](FSCSDiffEntry Entry, FText ObjectName) -> TSharedRef + { + return SNew(STextBlock) + .Text(DiffViewUtils::SCSDiffMessage(Entry, ObjectName)) + .ColorAndOpacity(DiffViewUtils::Differs()); + }; + + TArray< TSharedPtr > Children; + for (const FSCSDiffEntry& Difference : DifferingProperties.Entries) + { + TSharedPtr Entry = MakeShared( + FOnDiffEntryFocused::CreateStatic(FocusSCSDifferenceEntry, Difference, SelectionCallback, this), + FGenerateDiffEntryWidget::CreateStatic(CreateSCSDifferenceWidget, Difference, RightRevision)); + Children.Push(Entry); + OutRealDifferences.Push(Entry); + } + + const bool bHasDifferences = Children.Num() != 0; + if (!bHasDifferences) + { + // make one child informing the user that there are no differences: + Children.Push(FBlueprintDifferenceTreeEntry::NoDifferencesEntry()); + } + + OutTreeEntries.Push(FBlueprintDifferenceTreeEntry::CreateCategoryEntry( + NSLOCTEXT("FBlueprintDifferenceTreeEntry", "SCSLabel", "Components"), + NSLOCTEXT("FBlueprintDifferenceTreeEntry", "SCSTooltip", "The list of changes made in the Components panel"), + SelectionCallback, + Children, + bHasDifferences + )); + } + + TSharedRef OldTreeWidget() { return OldSCS.TreeWidget(); } + TSharedRef NewTreeWidget() { return NewSCS.TreeWidget(); } + +private: + FOnDiffEntryFocused SelectionCallback; + FSCSDiffRoot DifferingProperties; + + FSCSDiff OldSCS; + FSCSDiff NewSCS; +}; + +/** Generic wrapper around a details view, this does not actually fill out OutTreeEntries */ +class FDetailsDiffControl : public TSharedFromThis, public IDiffControl +{ +public: + FDetailsDiffControl(const UObject* InOldObject, const UObject* InNewObject, FOnDiffEntryFocused InSelectionCallback) + : SelectionCallback(InSelectionCallback) + , OldDetails(InOldObject, FDetailsDiff::FOnDisplayedPropertiesChanged()) + , NewDetails(InNewObject, FDetailsDiff::FOnDisplayedPropertiesChanged()) + { + OldDetails.DiffAgainst(NewDetails, DifferingProperties, true); + } + + virtual void GenerateTreeEntries(TArray< TSharedPtr >& OutTreeEntries, TArray< TSharedPtr >& OutRealDifferences) override + { + for (const FSingleObjectDiffEntry& Difference : DifferingProperties) + { + TSharedPtr Entry = MakeShared( + FOnDiffEntryFocused::CreateSP(AsShared(), &FDetailsDiffControl::OnSelectDiffEntry, Difference.Identifier), + FGenerateDiffEntryWidget::CreateStatic(&GenerateObjectDiffWidget, Difference, RightRevision)); + Children.Push(Entry); + OutRealDifferences.Push(Entry); + } + } + + TSharedRef OldDetailsWidget() { return OldDetails.DetailsWidget(); } + TSharedRef NewDetailsWidget() { return NewDetails.DetailsWidget(); } + +protected: + virtual void OnSelectDiffEntry(FPropertySoftPath PropertyName) + { + SelectionCallback.ExecuteIfBound(); + OldDetails.HighlightProperty(PropertyName); + NewDetails.HighlightProperty(PropertyName); + } + + FOnDiffEntryFocused SelectionCallback; + FDetailsDiff OldDetails; + FDetailsDiff NewDetails; + + TArray DifferingProperties; + TArray< TSharedPtr > Children; +}; + +/** Override for CDO special case */ +class FCDODiffControl : public FDetailsDiffControl +{ +public: + FCDODiffControl(const UObject* InOldObject, const UObject* InNewObject, FOnDiffEntryFocused InSelectionCallback) + : FDetailsDiffControl(InOldObject, InNewObject, InSelectionCallback) + { + } + + virtual void GenerateTreeEntries(TArray< TSharedPtr >& OutTreeEntries, TArray< TSharedPtr >& OutRealDifferences) override + { + FDetailsDiffControl::GenerateTreeEntries(OutTreeEntries, OutRealDifferences); + + const bool bHasDifferences = Children.Num() != 0; + if (!bHasDifferences) + { + // make one child informing the user that there are no differences: + Children.Push(FBlueprintDifferenceTreeEntry::NoDifferencesEntry()); + } + + OutTreeEntries.Push(FBlueprintDifferenceTreeEntry::CreateCategoryEntry( + NSLOCTEXT("FBlueprintDifferenceTreeEntry", "DefaultsLabel", "Defaults"), + NSLOCTEXT("FBlueprintDifferenceTreeEntry", "DefaultsTooltip", "The list of changes made in the Defaults panel"), + SelectionCallback, + Children, + bHasDifferences + )); + } +}; + +/** Override for class class settings */ +class FClassSettingsDiffControl : public FDetailsDiffControl +{ +public: + FClassSettingsDiffControl(const UObject* InOldObject, const UObject* InNewObject, FOnDiffEntryFocused InSelectionCallback) + : FDetailsDiffControl(InOldObject, InNewObject, InSelectionCallback) + { + } + + virtual void GenerateTreeEntries(TArray< TSharedPtr >& OutTreeEntries, TArray< TSharedPtr >& OutRealDifferences) override + { + FDetailsDiffControl::GenerateTreeEntries(OutTreeEntries, OutRealDifferences); + + // Check for parent class and interfaces here + const UBlueprint* OldBlueprint = Cast(OldDetails.GetDisplayedObject()); + const UBlueprint* NewBlueprint = Cast(NewDetails.GetDisplayedObject()); + + if (OldBlueprint && NewBlueprint) + { + if (OldBlueprint->ParentClass != NewBlueprint->ParentClass) + { + FText DiffText = FText::Format(LOCTEXT("ParentChanged", "Parent Class changed from {0} to {1}"), FText::FromString(OldBlueprint->ParentClass->GetName()), FText::FromString(NewBlueprint->ParentClass->GetName())); + + TSharedPtr Entry = MakeShared( + SelectionCallback, + FGenerateDiffEntryWidget::CreateStatic(&GenerateSimpleDiffWidget, DiffText)); + + Children.Push(Entry); + OutRealDifferences.Push(Entry); + } + + FString OldInterfaces, NewInterfaces; + for (const FBPInterfaceDescription& Desc : OldBlueprint->ImplementedInterfaces) + { + if (!OldInterfaces.IsEmpty()) + { + OldInterfaces += TEXT(", "); + } + OldInterfaces += GetNameSafe(*Desc.Interface); + } + + for (const FBPInterfaceDescription& Desc : NewBlueprint->ImplementedInterfaces) + { + if (!NewInterfaces.IsEmpty()) + { + NewInterfaces += TEXT(", "); + } + NewInterfaces += GetNameSafe(*Desc.Interface); + } + + if (OldInterfaces != NewInterfaces) + { + FText DiffText = FText::Format(LOCTEXT("InterfacesChanged", "Interfaces changed from '{0}' to '{1}'"), FText::FromString(OldInterfaces), FText::FromString(NewInterfaces)); + + TSharedPtr Entry = MakeShared( + SelectionCallback, + FGenerateDiffEntryWidget::CreateStatic(&GenerateSimpleDiffWidget, DiffText)); + + Children.Push(Entry); + OutRealDifferences.Push(Entry); + } + + if (OldBlueprint->SupportsNativization() != NewBlueprint->SupportsNativization()) + { + FText DiffText = FText::Format(LOCTEXT("NativizationChanged", "Nativization changed from {0} to {1}"), FText::AsNumber(OldBlueprint->SupportsNativization()), FText::AsNumber(NewBlueprint->SupportsNativization())); + + TSharedPtr Entry = MakeShared( + SelectionCallback, + FGenerateDiffEntryWidget::CreateStatic(&GenerateSimpleDiffWidget, DiffText)); + + Children.Push(Entry); + OutRealDifferences.Push(Entry); + } + } + + const bool bHasDifferences = Children.Num() != 0; + if (!bHasDifferences) + { + // make one child informing the user that there are no differences: + Children.Push(FBlueprintDifferenceTreeEntry::NoDifferencesEntry()); + } + + OutTreeEntries.Push(FBlueprintDifferenceTreeEntry::CreateCategoryEntry( + NSLOCTEXT("FBlueprintDifferenceTreeEntry", "SettingsLabel", "Class Settings"), + NSLOCTEXT("FBlueprintDifferenceTreeEntry", "SettingsTooltip", "The list of changes made in the Class Settings panel"), + SelectionCallback, + Children, + bHasDifferences + )); + } +}; + +/** Diff control to handle finding type-specific differences */ +struct FBlueprintTypeDiffControl : public TSharedFromThis, public IDiffControl +{ + struct FSubObjectDiff + { + FDiffSingleResult SourceResult; + FDetailsDiff OldDetails; + FDetailsDiff NewDetails; + TArray> Diffs; + + FSubObjectDiff(const FDiffSingleResult& InSourceResult, const UObject* OldObject, const UObject* NewObject) + : SourceResult(InSourceResult) + , OldDetails(OldObject, FDetailsDiff::FOnDisplayedPropertiesChanged()) + , NewDetails(NewObject, FDetailsDiff::FOnDisplayedPropertiesChanged()) + {} + }; + + FBlueprintTypeDiffControl(const UBlueprint* InBlueprintOld, const UBlueprint* InBlueprintNew, FOnDiffEntryFocused InSelectionCallback) + : BlueprintOld(InBlueprintOld), BlueprintNew(InBlueprintNew), SelectionCallback(InSelectionCallback), bDiffSucceeded(false) + { + check(InBlueprintNew && InBlueprintOld); + } + + /** Generate difference tree widgets */ + virtual void GenerateTreeEntries(TArray< TSharedPtr >& OutTreeEntries, TArray< TSharedPtr >& OutRealDifferences) override; + + /** The old blueprint (left) */ + const UBlueprint* BlueprintOld; + + /** The new blueprint(right) */ + const UBlueprint* BlueprintNew; + + /** Boxes that will display the details diffs */ + TSharedPtr OldDetailsBox; + TSharedPtr NewDetailsBox; + +private: + /** Generate Widget for top category */ + TSharedRef GenerateCategoryWidget(bool bHasRealDiffs); + + /** Build up the Diff Source Array*/ + void BuildDiffSourceArray(); + + /** Handle selecting a diff */ + void OnSelectSubobjectDiff(FPropertySoftPath Identifier, TSharedPtr SubObjectDiff); + + /** List of objects with differences */ + TArray> SubObjectDiffs; + + /** Source for list view */ + TArray> DiffListSource; + + /** Selection callback */ + FOnDiffEntryFocused SelectionCallback; + + /** Did diff generation succeed? */ + bool bDiffSucceeded; +}; + +TSharedRef FBlueprintTypeDiffControl::GenerateCategoryWidget(bool bHasRealDiffs) +{ + FLinearColor Color = FLinearColor::White; + + if (bHasRealDiffs) + { + Color = DiffViewUtils::Differs(); + } + + FText Label = BlueprintNew->GetClass()->GetDisplayNameText(); + + return SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(STextBlock) + .ColorAndOpacity(Color) + .Text(Label) + ]; +} + +void FBlueprintTypeDiffControl::OnSelectSubobjectDiff(FPropertySoftPath Identifier, TSharedPtr SubObjectDiff) +{ + // This allows the owning control to focus the correct tab (or do whatever else it likes): + SelectionCallback.ExecuteIfBound(); + + if (SubObjectDiff.IsValid()) + { + SubObjectDiff->OldDetails.HighlightProperty(Identifier); + SubObjectDiff->NewDetails.HighlightProperty(Identifier); + + OldDetailsBox->SetContent(SubObjectDiff->OldDetails.DetailsWidget()); + NewDetailsBox->SetContent(SubObjectDiff->NewDetails.DetailsWidget()); + } +} + +void FBlueprintTypeDiffControl::BuildDiffSourceArray() +{ + TArray BlueprintDiffResults; + FDiffResults BlueprintDiffs(&BlueprintDiffResults); + if (BlueprintNew->FindDiffs(BlueprintOld, BlueprintDiffs)) + { + bDiffSucceeded = true; + + // Add manual diffs + for (const FDiffSingleResult& CurrentDiff : BlueprintDiffResults) + { + if (CurrentDiff.Diff == EDiffType::OBJECT_REQUEST_DIFF) + { + // Turn into a subobject diff + + // Invert order, we want old then new + TSharedPtr SubObjectDiff = MakeShared(CurrentDiff, CurrentDiff.Object2, CurrentDiff.Object1); + + TArray DifferingProperties; + SubObjectDiff->OldDetails.DiffAgainst(SubObjectDiff->NewDetails, DifferingProperties, true); + + if (DifferingProperties.Num() > 0) + { + // Actual differences, so add to tree + SubObjectDiffs.Add(SubObjectDiff); + + for (const FSingleObjectDiffEntry& Difference : DifferingProperties) + { + TSharedPtr Entry = MakeShared( + FOnDiffEntryFocused::CreateSP(AsShared(), &FBlueprintTypeDiffControl::OnSelectSubobjectDiff, Difference.Identifier, SubObjectDiff), + FGenerateDiffEntryWidget::CreateStatic(&GenerateObjectDiffWidget, Difference, RightRevision)); + SubObjectDiff->Diffs.Push(Entry); + } + } + } + else + { + DiffListSource.Add(MakeShared(CurrentDiff)); + } + } + + struct SortDiff + { + bool operator () (const TSharedPtr& A, const TSharedPtr& B) const + { + return A->Result.Diff < B->Result.Diff; + } + }; + + Sort(DiffListSource.GetData(), DiffListSource.Num(), SortDiff()); + } +} + +void FBlueprintTypeDiffControl::GenerateTreeEntries(TArray< TSharedPtr >& OutTreeEntries, TArray< TSharedPtr >& OutRealDifferences) +{ + BuildDiffSourceArray(); + + TArray< TSharedPtr > Children; + + bool bHasRealChange = false; + + // First add manual diffs in main category + for (const TSharedPtr& Difference : DiffListSource) + { + TSharedPtr ChildEntry = MakeShared( + SelectionCallback, + FGenerateDiffEntryWidget::CreateSP(Difference.ToSharedRef(), &FDiffResultItem::GenerateWidget)); + Children.Push(ChildEntry); + OutRealDifferences.Push(ChildEntry); + + if (Difference->Result.IsRealDifference()) + { + bHasRealChange = true; + } + } + + if (Children.Num() == 0) + { + // Make one child informing the user that there are no differences, or that it is unknown + if (bDiffSucceeded) + { + Children.Push(FBlueprintDifferenceTreeEntry::NoDifferencesEntry()); + } + else + { + Children.Push(FBlueprintDifferenceTreeEntry::UnknownDifferencesEntry()); + } + } + + TSharedPtr CategoryEntry = MakeShared( + SelectionCallback, + FGenerateDiffEntryWidget::CreateSP(AsShared(), &FBlueprintTypeDiffControl::GenerateCategoryWidget, bHasRealChange), + Children); + OutTreeEntries.Push(CategoryEntry); + + // Now add subobject diffs, one category per object + for (const TSharedPtr SubObjectDiff : SubObjectDiffs) + { + Children.Reset(); + + Children.Append(SubObjectDiff->Diffs); + OutRealDifferences.Append(SubObjectDiff->Diffs); + + TSharedPtr SubObjectEntry = FBlueprintDifferenceTreeEntry::CreateCategoryEntry( + SubObjectDiff->SourceResult.DisplayString, + SubObjectDiff->SourceResult.ToolTip, + FOnDiffEntryFocused::CreateSP(AsShared(), &FBlueprintTypeDiffControl::OnSelectSubobjectDiff, FPropertySoftPath(), SubObjectDiff), + Children, + true); + + OutTreeEntries.Push(SubObjectEntry); + } +} + +/** Category list item for a graph*/ +struct FGraphToDiff : public TSharedFromThis, IDiffControl +{ + FGraphToDiff(SBlueprintDiff* DiffWidget, UEdGraph* GraphOld, UEdGraph* GraphNew, const FRevisionInfo& RevisionOld, const FRevisionInfo& RevisionNew); + virtual ~FGraphToDiff(); + + /** Add widgets to the differences tree */ + virtual void GenerateTreeEntries(TArray< TSharedPtr >& OutTreeEntries, TArray< TSharedPtr >& OutRealDifferences) override; + + /** Get old(left) graph*/ + UEdGraph* GetGraphOld() const { return GraphOld; } + + /** Get new(right) graph*/ + UEdGraph* GetGraphNew() const { return GraphNew; } + + /** Source for list view */ + TArray> DiffListSource; + +private: + /** Get tooltip for category */ + FText GetToolTip(); + + /** Generate Widget for category list */ + TSharedRef GenerateCategoryWidget(); + + /** Called when the Newer Graph is modified*/ + void OnGraphChanged(const FEdGraphEditAction& Action); + + /** Build up the Diff Source Array*/ + void BuildDiffSourceArray(); + + /** Diff widget */ + class SBlueprintDiff* DiffWidget; + + /** The old graph(left)*/ + UEdGraph* GraphOld; + + /** The new graph(right)*/ + UEdGraph* GraphNew; + + /** Description of Old and new graph*/ + FRevisionInfo RevisionOld, RevisionNew; + + /** Handle to the registered OnGraphChanged delegate. */ + FDelegateHandle OnGraphChangedDelegateHandle; +}; + +FGraphToDiff::FGraphToDiff(SBlueprintDiff* InDiffWidget, UEdGraph* InGraphOld, UEdGraph* InGraphNew, const FRevisionInfo& InRevisionOld, const FRevisionInfo& InRevisionNew) + : DiffWidget(InDiffWidget), GraphOld(InGraphOld), GraphNew(InGraphNew), RevisionOld(InRevisionOld), RevisionNew(InRevisionNew) { check(InGraphOld || InGraphNew); //one of them needs to exist //need to know when it is modified if(InGraphNew) { - OnGraphChangedDelegateHandle = InGraphNew->AddOnGraphChangedHandler( FOnGraphChanged::FDelegate::CreateRaw(this, &FListItemGraphToDiff::OnGraphChanged)); + OnGraphChangedDelegateHandle = InGraphNew->AddOnGraphChangedHandler( FOnGraphChanged::FDelegate::CreateRaw(this, &FGraphToDiff::OnGraphChanged)); } BuildDiffSourceArray(); } -FListItemGraphToDiff::~FListItemGraphToDiff() +FGraphToDiff::~FGraphToDiff() { if(GraphNew) { @@ -422,7 +690,7 @@ FListItemGraphToDiff::~FListItemGraphToDiff() } } -TSharedRef FListItemGraphToDiff::GenerateWidget() +TSharedRef FGraphToDiff::GenerateCategoryWidget() { const UEdGraph* Graph = GraphOld ? GraphOld : GraphNew; check(Graph); @@ -455,81 +723,17 @@ TSharedRef FListItemGraphToDiff::GenerateWidget() SNew(STextBlock) .ColorAndOpacity(Color) .Text(GraphName) + .ToolTipText(GetToolTip()) ] + DiffViewUtils::Box( GraphOld != nullptr, Color ) + DiffViewUtils::Box( GraphNew != nullptr, Color ); } -TSharedRef FListItemGraphToDiff::GenerateDiffListWidget() +FText FGraphToDiff::GetToolTip() { - if(DiffListSource.Num() > 0) + if (GraphOld && GraphNew) { - TSharedPtr DiffListRef; - TSharedRef Result = SNew(SHorizontalBox) - +SHorizontalBox::Slot() - .FillWidth(1.f) - .MaxWidth(350.f) - [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - .Padding(0.f) - .AutoHeight() - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("PropertyWindow.CategoryBackground")) - .Padding(FMargin(2.0f)) - .ForegroundColor(FEditorStyle::GetColor("PropertyWindow.CategoryForeground")) - .ToolTipText(LOCTEXT("BlueprintDifDifferencesToolTip", "List of differences found between revisions, click to select")) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Text(LOCTEXT("RevisionDifferences", "Revision Differences")) - ] - ] - +SVerticalBox::Slot() - .Padding(1.f) - .FillHeight(1.f) - [ - SAssignNew(DiffList, SListViewType) - .ItemHeight(24) - .ListItemsSource( &DiffListSource ) - .OnGenerateRow( this, &FListItemGraphToDiff::OnGenerateRow ) - .SelectionMode( ESelectionMode::Single ) - .OnSelectionChanged(this, &FListItemGraphToDiff::OnSelectionChanged) - ] - ]; - return Result; - } - else - { - return SNew(SBorder).Visibility(EVisibility::Hidden); - } - - -} - -TSharedRef FListItemGraphToDiff::OnGenerateRow( FSharedDiffOnGraph ParamItem, const TSharedRef& OwnerTable ) -{ - return SNew( STableRow< FSharedDiffOnGraph >, OwnerTable ) - .Content() - [ - ParamItem->GenerateWidget() - ]; -} - -void FListItemGraphToDiff::OnSelectionChanged( FSharedDiffOnGraph Item, ESelectInfo::Type SelectionType ) -{ - if(Item.IsValid()) - { - Diff->OnDiffListSelectionChanged(Item); - } -} - -FText FListItemGraphToDiff::GetToolTip() -{ - if ( GraphOld && GraphNew ) - { - if ( DiffListSource.Num() > 0 ) + if (DiffListSource.Num() > 0) { return LOCTEXT("ContainsDifferences", "Revisions are different"); } @@ -540,34 +744,59 @@ FText FListItemGraphToDiff::GetToolTip() } else { - auto GoodGraph = GraphOld ? GraphOld : GraphNew; + UEdGraph* GoodGraph = GraphOld ? GraphOld : GraphNew; check(GoodGraph); const FRevisionInfo& Revision = GraphNew ? RevisionOld : RevisionNew; FText RevisionText = LOCTEXT("CurrentRevision", "Current Revision"); - if ( !Revision.Revision.IsEmpty() ) + if (!Revision.Revision.IsEmpty()) { - RevisionText = FText::Format( LOCTEXT("Revision Number", "Revision {0}") , FText::FromString( Revision.Revision ) ); + RevisionText = FText::Format(LOCTEXT("Revision Number", "Revision {0}"), FText::FromString(Revision.Revision)); } - return FText::Format( LOCTEXT("MissingGraph", "Graph '{0}' missing from {1}"), FText::FromString( GoodGraph->GetName() ), RevisionText ); + return FText::Format(LOCTEXT("MissingGraph", "Graph '{0}' missing from {1}"), FText::FromString(GoodGraph->GetName()), RevisionText); } } -void FListItemGraphToDiff::BuildDiffSourceArray() +void FGraphToDiff::GenerateTreeEntries(TArray< TSharedPtr >& OutTreeEntries, TArray< TSharedPtr >& OutRealDifferences) +{ + TArray< TSharedPtr > Children; + for (const TSharedPtr& Difference : DiffListSource) + { + TSharedPtr ChildEntry = MakeShared( + FOnDiffEntryFocused::CreateRaw(DiffWidget, &SBlueprintDiff::OnDiffListSelectionChanged, Difference), + FGenerateDiffEntryWidget::CreateSP(Difference.ToSharedRef(), &FDiffResultItem::GenerateWidget)); + Children.Push(ChildEntry); + OutRealDifferences.Push(ChildEntry); + } + + if (Children.Num() == 0) + { + // make one child informing the user that there are no differences: + Children.Push(FBlueprintDifferenceTreeEntry::NoDifferencesEntry()); + } + + TSharedPtr Entry = MakeShared( + FOnDiffEntryFocused::CreateRaw(DiffWidget, &SBlueprintDiff::OnGraphSelectionChanged, TSharedPtr(AsShared()), ESelectInfo::Direct), + FGenerateDiffEntryWidget::CreateSP(AsShared(), &FGraphToDiff::GenerateCategoryWidget), + Children); + OutTreeEntries.Push(Entry); +} + +void FGraphToDiff::BuildDiffSourceArray() { TArray FoundDiffs; FGraphDiffControl::DiffGraphs(GraphOld, GraphNew, FoundDiffs); DiffListSource.Empty(); - for (auto DiffIt(FoundDiffs.CreateIterator()); DiffIt; ++DiffIt) + for (const FDiffSingleResult& Diff : FoundDiffs) { - DiffListSource.Add(FSharedDiffOnGraph(new FDiffResultItem(*DiffIt))); + DiffListSource.Add(MakeShared(Diff)); } struct SortDiff { - bool operator () (const FSharedDiffOnGraph& A, const FSharedDiffOnGraph& B) const + bool operator () (const TSharedPtr& A, const TSharedPtr& B) const { return A->Result.Diff < B->Result.Diff; } @@ -576,35 +805,15 @@ void FListItemGraphToDiff::BuildDiffSourceArray() Sort(DiffListSource.GetData(), DiffListSource.Num(), SortDiff()); } -int32 FListItemGraphToDiff::GetCurrentDiffIndex() const +void FGraphToDiff::OnGraphChanged( const FEdGraphEditAction& Action ) { - if ( DiffList.IsValid() ) - { - auto Selected = DiffList->GetSelectedItems(); - if(Selected.Num() == 1) - { - int32 Index = 0; - for(auto It(DiffListSource.CreateConstIterator());It;++It,Index++) - { - if(*It == Selected[0]) - { - return Index; - } - } - } - } - return -1; -} - -void FListItemGraphToDiff::OnGraphChanged( const FEdGraphEditAction& Action ) -{ - Diff->OnGraphChanged(this); + DiffWidget->OnGraphChanged(this); } FDiffPanel::FDiffPanel() { - Blueprint = NULL; - LastFocusedPin = NULL; + Blueprint = nullptr; + LastFocusedPin = nullptr; } void FDiffPanel::InitializeDiffPanel() @@ -618,15 +827,15 @@ void FDiffPanel::InitializeDiffPanel() MyBlueprint->SetInspector(DetailsView); } -int32 GetCurrentIndex( SListView< TSharedPtr< struct FDiffSingleResult> > const& ListView, const TArray< TSharedPtr< FDiffSingleResult > >& ListViewSource ) +static int32 GetCurrentIndex( SListView< TSharedPtr< FDiffSingleResult> > const& ListView, const TArray< TSharedPtr< FDiffSingleResult > >& ListViewSource ) { - const auto& Selected = ListView.GetSelectedItems(); + const TArray< TSharedPtr >& Selected = ListView.GetSelectedItems(); if (Selected.Num() == 1) { int32 Index = 0; - for (auto It(ListViewSource.CreateConstIterator()); It; ++It, Index++) + for (const TSharedPtr& Diff : ListViewSource) { - if (*It == Selected[0]) + if (Diff == Selected[0]) { return Index; } @@ -657,13 +866,13 @@ void DiffWidgetUtils::SelectPrevRow(SListView< TSharedPtr< FDiffSingleResult> >& ListView.SetSelection(ListViewSource[CurrentIndex - 1]); } -bool DiffWidgetUtils::HasNextDifference(SListView< TSharedPtr< struct FDiffSingleResult> >& ListView, const TArray< TSharedPtr< struct FDiffSingleResult > >& ListViewSource) +bool DiffWidgetUtils::HasNextDifference(SListView< TSharedPtr< FDiffSingleResult> >& ListView, const TArray< TSharedPtr< FDiffSingleResult > >& ListViewSource) { int32 CurrentIndex = GetCurrentIndex(ListView, ListViewSource); return ListViewSource.IsValidIndex(CurrentIndex+1); } -bool DiffWidgetUtils::HasPrevDifference(SListView< TSharedPtr< struct FDiffSingleResult> >& ListView, const TArray< TSharedPtr< struct FDiffSingleResult > >& ListViewSource) +bool DiffWidgetUtils::HasPrevDifference(SListView< TSharedPtr< FDiffSingleResult> >& ListView, const TArray< TSharedPtr< FDiffSingleResult > >& ListViewSource) { int32 CurrentIndex = GetCurrentIndex(ListView, ListViewSource); return ListViewSource.IsValidIndex(CurrentIndex - 1); @@ -672,26 +881,23 @@ bool DiffWidgetUtils::HasPrevDifference(SListView< TSharedPtr< struct FDiffSingl BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SBlueprintDiff::Construct( const FArguments& InArgs) { - const TSharedRef MajorTab = SNew(SDockTab) - .TabRole(ETabRole::MajorTab); - TabManager = FGlobalTabmanager::Get()->NewTabManager(MajorTab); - - TabManager->RegisterTabSpawner(DiffGraphTabId, - FOnSpawnTab::CreateRaw(this, &SBlueprintDiff::CreateGraphDiffViews )) - .SetDisplayName(NSLOCTEXT("SBlueprintDiff", "GraphsTabTitle", "Graphs")) - .SetTooltipText(NSLOCTEXT("SBlueprintDiff", "GraphsTooltipText", "Differences in the various graphs present in the blueprint")); - - TabManager->RegisterTabSpawner(DiffMyBluerpintTabId, - FOnSpawnTab::CreateRaw(this, &SBlueprintDiff::CreateMyBlueprintsViews)) - .SetDisplayName(NSLOCTEXT("SBlueprintDiff", "MyBlueprintTabTitle", "My Blueprint")) - .SetTooltipText(NSLOCTEXT("SBlueprintDiff", "MyBlueprintTooltipText", "Differences in the 'My Blueprints' attributes of the blueprint")); - check(InArgs._BlueprintOld && InArgs._BlueprintNew); PanelOld.Blueprint = InArgs._BlueprintOld; PanelNew.Blueprint = InArgs._BlueprintNew; PanelOld.RevisionInfo = InArgs._OldRevision; PanelNew.RevisionInfo = InArgs._NewRevision; + // Create a skeleton if we don't have one, this is true for revision history diffs + if (!PanelOld.Blueprint->SkeletonGeneratedClass) + { + FKismetEditorUtilities::GenerateBlueprintSkeleton(const_cast(PanelOld.Blueprint)); + } + + if (!PanelNew.Blueprint->SkeletonGeneratedClass) + { + FKismetEditorUtilities::GenerateBlueprintSkeleton(const_cast(PanelNew.Blueprint)); + } + // sometimes we want to clearly identify the assets being diffed (when it's // not the same asset in each panel) PanelOld.bShowAssetName = InArgs._ShowAssetNames; @@ -736,26 +942,47 @@ void SBlueprintDiff::Construct( const FArguments& InArgs) , TAttribute(this, &SBlueprintDiff::GetLockViewImage) ); - GraphPanel = GenerateGraphPanel(); + DifferencesTreeView = DiffTreeView::CreateTreeView(&MasterDifferencesList); GenerateDifferencesList(); const auto TextBlock = [](FText Text) -> TSharedRef { - return SNew(STextBlock) + return SNew(SBox) + .Padding(FMargin(4.0f,10.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) .Visibility(EVisibility::HitTestInvisible) - .TextStyle(FEditorStyle::Get(), "GraphPreview.CornerText") - .Text(Text); + .TextStyle(FEditorStyle::Get(), "DetailsView.CategoryTextStyle") + .Text(Text) + ]; }; - TSharedRef Overlay = SNew(SHorizontalBox) - + SHorizontalBox::Slot() + TSharedRef Overlay = + SNew(SSplitter) + .Visibility(EVisibility::HitTestInvisible) + + SSplitter::Slot() + .Value(.2f) [ - TextBlock(DiffViewUtils::GetPanelLabel(PanelOld.Blueprint, PanelOld.RevisionInfo, FText())) + SNew(SBox) ] - + SHorizontalBox::Slot() + + SSplitter::Slot() + .Value(.8f) [ - TextBlock(DiffViewUtils::GetPanelLabel(PanelNew.Blueprint, PanelNew.RevisionInfo, FText())) + SNew(SSplitter) + .PhysicalSplitterHandleSize(10.0f) + + SSplitter::Slot() + .Value(.5f) + [ + TextBlock(DiffViewUtils::GetPanelLabel(PanelOld.Blueprint, PanelOld.RevisionInfo, FText())) + ] + + SSplitter::Slot() + .Value(.5f) + [ + TextBlock(DiffViewUtils::GetPanelLabel(PanelNew.Blueprint, PanelNew.RevisionInfo, FText())) + ] ]; this->ChildSlot @@ -763,55 +990,54 @@ void SBlueprintDiff::Construct( const FArguments& InArgs) SNew(SBorder) .BorderImage(FEditorStyle::GetBrush( "Docking.Tab", ".ContentAreaBrush" )) [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - .AutoHeight() - .Padding(0.0f, 2.0f, 0.0f, 2.0f) + SNew(SOverlay) + + SOverlay::Slot() [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(4.f) - .AutoWidth() + SNew(SVerticalBox) + +SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 2.0f, 0.0f, 2.0f) [ - ToolbarBuilder.MakeWidget() - ] - + SHorizontalBox::Slot() - [ - SNew(SSpacer) - ] - ] - + SVerticalBox::Slot() - [ - SNew(SSplitter) - + SSplitter::Slot() - .Value(.2f) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(4.f) + .AutoWidth() [ - DifferencesTreeView.ToSharedRef() + ToolbarBuilder.MakeWidget() + ] + + SHorizontalBox::Slot() + [ + SNew(SSpacer) ] ] - + SSplitter::Slot() - .Value(.8f) + + SVerticalBox::Slot() [ - SNew(SOverlay) - + SOverlay::Slot() + SNew(SSplitter) + + SSplitter::Slot() + .Value(.2f) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + [ + DifferencesTreeView.ToSharedRef() + ] + ] + + SSplitter::Slot() + .Value(.8f) [ SAssignNew(ModeContents, SBox) ] - + SOverlay::Slot() - .VAlign(VAlign_Bottom) - [ - Overlay - ] ] ] + + SOverlay::Slot() + .VAlign(VAlign_Top) + [ + Overlay + ] ] - ]; - SetCurrentMode( FBlueprintEditorApplicationModes::StandardBlueprintEditorMode ); + SetCurrentMode(MyBlueprintMode); // Bind to blueprint changed events as they may be real in memory blueprints that will be modified const_cast(PanelNew.Blueprint)->OnChanged().AddSP(this, &SBlueprintDiff::OnBlueprintChanged); @@ -846,22 +1072,12 @@ void SBlueprintDiff::OnCloseAssetEditor(UObject* Asset, EAssetEditorCloseReason } } -TSharedRef SBlueprintDiff::OnGenerateRow( FGraphToDiff ParamItem, const TSharedRef& OwnerTable ) +void SBlueprintDiff::CreateGraphEntry( UEdGraph* GraphOld, UEdGraph* GraphNew ) { - return SNew( STableRow< FGraphToDiff >, OwnerTable ) - .ToolTipText(ParamItem->GetToolTip()) - .Content() - [ - ParamItem->GenerateWidget() - ]; + Graphs.Add(MakeShared(this, GraphOld, GraphNew, PanelOld.RevisionInfo, PanelNew.RevisionInfo)); } -void SBlueprintDiff::CreateGraphEntry( class UEdGraph* GraphOld, class UEdGraph* GraphNew ) -{ - Graphs.Add(FGraphToDiff(new FListItemGraphToDiff(this, GraphOld, GraphNew, PanelOld.RevisionInfo, PanelNew.RevisionInfo))); -} - -void SBlueprintDiff::OnSelectionChanged( FGraphToDiff Item, ESelectInfo::Type SelectionType) +void SBlueprintDiff::OnGraphSelectionChanged(TSharedPtr Item, ESelectInfo::Type SelectionType) { if(!Item.IsValid()) { @@ -872,7 +1088,7 @@ void SBlueprintDiff::OnSelectionChanged( FGraphToDiff Item, ESelectInfo::Type Se } -void SBlueprintDiff::OnGraphChanged(FListItemGraphToDiff* Diff) +void SBlueprintDiff::OnGraphChanged(FGraphToDiff* Diff) { if(PanelNew.GraphEditor.IsValid() && PanelNew.GraphEditor.Pin()->GetCurrentGraph() == Diff->GetGraphNew()) { @@ -882,13 +1098,11 @@ void SBlueprintDiff::OnGraphChanged(FListItemGraphToDiff* Diff) void SBlueprintDiff::OnBlueprintChanged(UBlueprint* InBlueprint) { - if (InBlueprint == PanelOld.Blueprint && PanelOld.GraphEditor.IsValid()) + if (InBlueprint == PanelOld.Blueprint || InBlueprint == PanelNew.Blueprint) { - PanelOld.GraphEditor.Pin()->NotifyGraphChanged(); - } - if (InBlueprint == PanelNew.Blueprint && PanelNew.GraphEditor.IsValid()) - { - PanelNew.GraphEditor.Pin()->NotifyGraphChanged(); + // After a BP has changed significantly, we need to regenerate the UI and set back to initial UI to avoid crashes + GenerateDifferencesList(); + SetCurrentMode(MyBlueprintMode); } } @@ -904,7 +1118,7 @@ TSharedRef SBlueprintDiff::DefaultEmptyPanel() ]; } -TSharedPtr SBlueprintDiff::CreateDiffWindow(FText WindowTitle, UBlueprint* OldBlueprint, UBlueprint* NewBlueprint, const struct FRevisionInfo& OldRevision, const struct FRevisionInfo& NewRevision) +TSharedPtr SBlueprintDiff::CreateDiffWindow(FText WindowTitle, UBlueprint* OldBlueprint, UBlueprint* NewBlueprint, const FRevisionInfo& OldRevision, const FRevisionInfo& NewRevision) { // sometimes we're comparing different revisions of one single asset (other // times we're comparing two completely separate assets altogether) @@ -956,52 +1170,12 @@ bool SBlueprintDiff::HasPrevDiff() const return DiffTreeView::HasPrevDifference(DifferencesTreeView.ToSharedRef(), RealDifferences); } -TSharedRef SBlueprintDiff::CreateGraphDiffViews(const FSpawnTabArgs& Args) +FGraphToDiff* SBlueprintDiff::FindGraphToDiffEntry(const FString& GraphPath) { - return SNew(SDockTab) - [ - SNew(SSplitter) - + SSplitter::Slot() - [ - SAssignNew(PanelOld.GraphEditorBorder, SBox) - .VAlign(VAlign_Fill) - [ - DefaultEmptyPanel() - ] - ] - + SSplitter::Slot() - [ - SAssignNew(PanelNew.GraphEditorBorder, SBox) - .VAlign(VAlign_Fill) - [ - DefaultEmptyPanel() - ] - ] - ]; -} - -TSharedRef SBlueprintDiff::CreateMyBlueprintsViews(const FSpawnTabArgs& Args) -{ - return SNew(SDockTab) - [ - SNew(SSplitter) - + SSplitter::Slot() - [ - PanelOld.GenerateMyBlueprintPanel() - ] - + SSplitter::Slot() - [ - PanelNew.GenerateMyBlueprintPanel() - ] - ]; -} - -FListItemGraphToDiff* SBlueprintDiff::FindGraphToDiffEntry(FName ByName) -{ - for( const auto& Graph : Graphs ) + for(const TSharedPtr& Graph : Graphs) { - FName GraphName = Graph->GetGraphOld() ? Graph->GetGraphOld()->GetFName() : Graph->GetGraphNew()->GetFName(); - if (GraphName == ByName ) + FString SearchGraphPath = Graph->GetGraphOld() ? FGraphDiffControl::GetGraphPath(Graph->GetGraphOld()) : FGraphDiffControl::GetGraphPath(Graph->GetGraphNew()); + if (SearchGraphPath.Equals(GraphPath, ESearchCase::CaseSensitive)) { return Graph.Get(); } @@ -1009,19 +1183,12 @@ FListItemGraphToDiff* SBlueprintDiff::FindGraphToDiffEntry(FName ByName) return nullptr; } -void SBlueprintDiff::FocusOnGraphRevisions( FListItemGraphToDiff* Diff ) +void SBlueprintDiff::FocusOnGraphRevisions( FGraphToDiff* Diff ) { UEdGraph* Graph = Diff->GetGraphOld() ? Diff->GetGraphOld() : Diff->GetGraphNew(); - FString GraphPath; - if (UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph)) - { - GraphPath = Graph->GetPathName(Blueprint); - } - else - { - GraphPath = Graph->GetName(); - } + FString GraphPath = FGraphDiffControl::GetGraphPath(Graph); + HandleGraphChanged(GraphPath); ResetGraphEditors(); @@ -1029,13 +1196,13 @@ void SBlueprintDiff::FocusOnGraphRevisions( FListItemGraphToDiff* Diff ) void SBlueprintDiff::OnDiffListSelectionChanged(TSharedPtr TheDiff ) { - check( TheDiff->Result.OwningGraph != FName() ); - FocusOnGraphRevisions( FindGraphToDiffEntry( TheDiff->Result.OwningGraph ) ); + check( !TheDiff->Result.OwningObjectPath.IsEmpty() ); + FocusOnGraphRevisions( FindGraphToDiffEntry( TheDiff->Result.OwningObjectPath) ); FDiffSingleResult Result = TheDiff->Result; const auto SafeClearSelection = []( TWeakPtr GraphEditor ) { - auto GraphEditorPtr = GraphEditor.Pin(); + TSharedPtr GraphEditorPtr = GraphEditor.Pin(); if( GraphEditorPtr.IsValid()) { GraphEditorPtr->ClearSelectionSet(); @@ -1102,7 +1269,7 @@ void FDiffPanel::GeneratePanel(UEdGraph* Graph, UEdGraph* GraphToDiff ) { LastFocusedPin->bIsDiffing = false; } - LastFocusedPin = NULL; + LastFocusedPin = nullptr; TSharedPtr Widget = SNew(SBorder) .HAlign(HAlign_Center) @@ -1119,12 +1286,20 @@ void FDiffPanel::GeneratePanel(UEdGraph* Graph, UEdGraph* GraphToDiff ) { Container->ShowDetailsForObjects(SelectionSet.Array()); }; + + const auto ContextMenuHandler = [](UEdGraph* CurrentGraph, const UEdGraphNode* InGraphNode, const UEdGraphPin* InGraphPin, FMenuBuilder* MenuBuilder, bool bIsDebugging) + { + MenuBuilder->AddMenuEntry(FGenericCommands::Get().Copy); + return FActionMenuContent(MenuBuilder->MakeWidget()); + }; + InEvents.OnSelectionChanged = SGraphEditor::FOnSelectionChanged::CreateStatic(SelectionChangedHandler, DetailsView); + InEvents.OnCreateNodeOrPinMenu = SGraphEditor::FOnCreateNodeOrPinMenu::CreateStatic(ContextMenuHandler); } if ( !GraphEditorCommands.IsValid() ) { - GraphEditorCommands = TSharedPtr( new FUICommandList() ); + GraphEditorCommands = MakeShared(); GraphEditorCommands->MapAction( FGenericCommands::Get().Copy, FExecuteAction::CreateRaw( this, &FDiffPanel::CopySelectedNodes ), @@ -1135,7 +1310,7 @@ void FDiffPanel::GeneratePanel(UEdGraph* Graph, UEdGraph* GraphToDiff ) MyBlueprint->SetFocusedGraph(Graph); MyBlueprint->Refresh(); - auto Editor = SNew(SGraphEditor) + TSharedRef Editor = SNew(SGraphEditor) .AdditionalCommands(GraphEditorCommands) .GraphToEdit(Graph) .GraphToDiff(GraphToDiff) @@ -1146,10 +1321,10 @@ void FDiffPanel::GeneratePanel(UEdGraph* Graph, UEdGraph* GraphToDiff ) Widget = Editor; } - GraphEditorBorder->SetContent(Widget.ToSharedRef()); + GraphEditorBox->SetContent(Widget.ToSharedRef()); } -TSharedRef FDiffPanel::GenerateMyBlueprintPanel() +TSharedRef FDiffPanel::GenerateMyBlueprintWidget() { return SAssignNew(MyBlueprint, SMyBlueprint, TWeakPtr(), Blueprint); } @@ -1182,7 +1357,7 @@ bool FDiffPanel::CanCopyNodes() const for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { UEdGraphNode* Node = Cast(*SelectedIter); - if ((Node != NULL) && Node->CanDuplicateNode()) + if ((Node != nullptr) && Node->CanDuplicateNode()) { return true; } @@ -1208,7 +1383,7 @@ void FDiffPanel::FocusDiff(UEdGraphNode& Node) { LastFocusedPin->bIsDiffing = false; } - LastFocusedPin = NULL; + LastFocusedPin = nullptr; if (GraphEditor.IsValid()) { @@ -1218,12 +1393,12 @@ void FDiffPanel::FocusDiff(UEdGraphNode& Node) FDiffPanel& SBlueprintDiff::GetDiffPanelForNode(UEdGraphNode& Node) { - auto OldGraphEditorPtr = PanelOld.GraphEditor.Pin(); + TSharedPtr OldGraphEditorPtr = PanelOld.GraphEditor.Pin(); if (OldGraphEditorPtr.IsValid() && Node.GetGraph() == OldGraphEditorPtr->GetCurrentGraph()) { return PanelOld; } - auto NewGraphEditorPtr = PanelNew.GraphEditor.Pin(); + TSharedPtr NewGraphEditorPtr = PanelNew.GraphEditor.Pin(); if (NewGraphEditorPtr.IsValid() && Node.GetGraph() == NewGraphEditorPtr->GetCurrentGraph()) { return PanelNew; @@ -1235,7 +1410,7 @@ FDiffPanel& SBlueprintDiff::GetDiffPanelForNode(UEdGraphNode& Node) void SBlueprintDiff::HandleGraphChanged( const FString& GraphPath ) { - SetCurrentMode(FBlueprintEditorApplicationModes::StandardBlueprintEditorMode); + SetCurrentMode(GraphMode); TArray GraphsOld, GraphsNew; PanelOld.Blueprint->GetAllGraphs(GraphsOld); @@ -1244,7 +1419,7 @@ void SBlueprintDiff::HandleGraphChanged( const FString& GraphPath ) UEdGraph* GraphOld = nullptr; for (UEdGraph* OldGraph : GraphsOld) { - if (GraphPath.Equals(OldGraph->GetPathName(PanelOld.Blueprint))) + if (GraphPath.Equals(FGraphDiffControl::GetGraphPath(OldGraph))) { GraphOld = OldGraph; break; @@ -1254,7 +1429,7 @@ void SBlueprintDiff::HandleGraphChanged( const FString& GraphPath ) UEdGraph* GraphNew = nullptr; for (UEdGraph* NewGraph : GraphsNew) { - if (GraphPath.Equals(NewGraph->GetPathName(PanelNew.Blueprint))) + if (GraphPath.Equals(FGraphDiffControl::GetGraphPath(NewGraph))) { GraphNew = NewGraph; break; @@ -1268,24 +1443,43 @@ void SBlueprintDiff::HandleGraphChanged( const FString& GraphPath ) void SBlueprintDiff::GenerateDifferencesList() { MasterDifferencesList.Empty(); + RealDifferences.Empty(); Graphs.Empty(); + ModePanels.Empty(); + + // SMyBlueprint needs to be created *before* the KismetInspector or the diffs are generated, because the KismetInspector's customizations + // need a reference to the SMyBlueprint widget that is controlling them... + const auto CreateInspector = [](TSharedPtr InMyBlueprint) { + return SNew(SKismetInspector) + .HideNameArea(true) + .ViewIdentifier(FName("BlueprintInspector")) + .MyBlueprintWidget(InMyBlueprint) + .IsPropertyEditingEnabledDelegate(FIsPropertyEditingEnabled::CreateStatic([] { return false; })); + }; + + PanelOld.GenerateMyBlueprintWidget(); + PanelOld.DetailsView = CreateInspector(PanelOld.MyBlueprint); + PanelOld.MyBlueprint->SetInspector(PanelOld.DetailsView); + PanelNew.GenerateMyBlueprintWidget(); + PanelNew.DetailsView = CreateInspector(PanelNew.MyBlueprint); + PanelNew.MyBlueprint->SetInspector(PanelNew.DetailsView); TArray GraphsOld, GraphsNew; PanelOld.Blueprint->GetAllGraphs(GraphsOld); PanelNew.Blueprint->GetAllGraphs(GraphsNew); //Add Graphs that exist in both blueprints, or in blueprint 1 only - for (auto It(GraphsOld.CreateConstIterator()); It; It++) + for (UEdGraph* GraphOld : GraphsOld) { - UEdGraph* GraphOld = *It; - UEdGraph* GraphNew = NULL; - for (auto It2(GraphsNew.CreateIterator()); It2; It2++) + UEdGraph* GraphNew = nullptr; + for (UEdGraph*& TestGraph : GraphsNew) { - UEdGraph* TestGraph = *It2; if (TestGraph && GraphOld->GetName() == TestGraph->GetName()) { GraphNew = TestGraph; - *It2 = NULL; + + // Null reference inside array + TestGraph = nullptr; break; } } @@ -1297,138 +1491,181 @@ void SBlueprintDiff::GenerateDifferencesList() } //Add graphs that only exist in 2nd(new) blueprint - for (auto It2(GraphsNew.CreateIterator()); It2; It2++) + for (UEdGraph* GraphNew : GraphsNew) { - UEdGraph* GraphNew = *It2; - if (GraphNew != NULL && IsGraphDiffNeeded(GraphNew)) + if (GraphNew != nullptr && IsGraphDiffNeeded(GraphNew)) { - CreateGraphEntry(NULL, GraphNew); + CreateGraphEntry(nullptr, GraphNew); } } - bool bHasComponents = true; - if (const UAnimBlueprint* AnimBP = Cast(PanelOld.Blueprint)) + bool bHasComponents = false; + UClass* BlueprintClass = PanelOld.Blueprint->GeneratedClass; + if (BlueprintClass->IsChildOf()) { - MasterDifferencesList.Push(FBlueprintDifferenceTreeEntry::AnimBlueprintEntry()); - bHasComponents = false; - } - else if (const UWidgetBlueprint* WidgetBP = Cast(PanelOld.Blueprint)) - { - MasterDifferencesList.Push(FBlueprintDifferenceTreeEntry::WidgetBlueprintEntry()); - bHasComponents = false; + bHasComponents = true; } - // Unfortunately we can't perform the diff until the UI is generated, the primary reason for this is that - // details customizations determine what is actually editable: - DefaultsPanel = GenerateDefaultsPanel(); + // If this isn't a normal blueprint type, add the type panel + if (PanelOld.Blueprint->GetClass() != UBlueprint::StaticClass()) + { + ModePanels.Add(BlueprintTypeMode, GenerateBlueprintTypePanel()); + } + + // Now that we have done the diffs, create the panel widgets + ModePanels.Add(MyBlueprintMode, GenerateMyBlueprintPanel()); + ModePanels.Add(GraphMode, GenerateGraphPanel()); + ModePanels.Add(DefaultsMode, GenerateDefaultsPanel()); + ModePanels.Add(ClassSettingsMode, GenerateClassSettingsPanel()); if (bHasComponents) { - ComponentsPanel = GenerateComponentsPanel(); + ModePanels.Add(ComponentsMode, GenerateComponentsPanel()); } - for (const auto& Graph : Graphs) + for (const TSharedPtr& Graph : Graphs) { - TArray< TSharedPtr > Children; - for (const auto& Difference : Graph->DiffListSource) - { - auto ChildEntry = TSharedPtr( - new FBlueprintDifferenceTreeEntry( - FOnDiffEntryFocused::CreateRaw(this, &SBlueprintDiff::OnDiffListSelectionChanged, Difference) - , FGenerateDiffEntryWidget::CreateSP(Difference.ToSharedRef(), &FDiffResultItem::GenerateWidget) - , TArray< TSharedPtr >() - ) - ); - Children.Push(ChildEntry); - RealDifferences.Push(ChildEntry); - (void)ChildEntry; - } - - if (Children.Num() == 0) - { - // make one child informing the user that there are no differences: - Children.Push(FBlueprintDifferenceTreeEntry::NoDifferencesEntry()); - } - - auto Entry = TSharedPtr( - new FBlueprintDifferenceTreeEntry( - FOnDiffEntryFocused::CreateRaw(this, &SBlueprintDiff::OnSelectionChanged, Graph, ESelectInfo::Direct) - , FGenerateDiffEntryWidget::CreateSP(Graph.ToSharedRef(), &FListItemGraphToDiff::GenerateWidget) - , Children) - ); - MasterDifferencesList.Push(Entry); + Graph->GenerateTreeEntries(MasterDifferencesList, RealDifferences); } - DifferencesTreeView = DiffTreeView::CreateTreeView(&MasterDifferencesList); + DifferencesTreeView->RebuildList(); +} + +SBlueprintDiff::FDiffControl SBlueprintDiff::GenerateBlueprintTypePanel() +{ + TSharedPtr NewDiffControl = MakeShared(PanelOld.Blueprint, PanelNew.Blueprint, FOnDiffEntryFocused::CreateRaw(this, &SBlueprintDiff::SetCurrentMode, BlueprintTypeMode)); + NewDiffControl->GenerateTreeEntries(MasterDifferencesList, RealDifferences); + + SBlueprintDiff::FDiffControl Ret; + //Splitter for left and right blueprint. Current convention is for the local (probably newer?) blueprint to be on the right: + Ret.DiffControl = NewDiffControl; + Ret.Widget = SNew(SSplitter) + .PhysicalSplitterHandleSize(10.0f) + + SSplitter::Slot() + .Value(0.5f) + [ + SAssignNew(NewDiffControl->OldDetailsBox, SBox) + .VAlign(VAlign_Fill) + [ + DefaultEmptyPanel() + ] + ] + + SSplitter::Slot() + .Value(0.5f) + [ + SAssignNew(NewDiffControl->NewDetailsBox, SBox) + .VAlign(VAlign_Fill) + [ + DefaultEmptyPanel() + ] + ]; + + return Ret; +} + +SBlueprintDiff::FDiffControl SBlueprintDiff::GenerateMyBlueprintPanel() +{ + TSharedPtr NewDiffControl = MakeShared(PanelOld.Blueprint, PanelNew.Blueprint, FOnDiffEntryFocused::CreateRaw(this, &SBlueprintDiff::SetCurrentMode, MyBlueprintMode)); + NewDiffControl->GenerateTreeEntries(MasterDifferencesList, RealDifferences); + + SBlueprintDiff::FDiffControl Ret; + + Ret.DiffControl = NewDiffControl; + Ret.Widget = SNew(SVerticalBox) + + SVerticalBox::Slot() + .FillHeight(1.f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.f) + [ + //diff window + SNew(SSplitter) + .Orientation(Orient_Vertical) + +SSplitter::Slot() + .Value(.8f) + [ + SNew(SSplitter) + .PhysicalSplitterHandleSize(10.0f) + + SSplitter::Slot() + [ + PanelOld.MyBlueprint.ToSharedRef() + ] + + SSplitter::Slot() + [ + PanelNew.MyBlueprint.ToSharedRef() + ] + ] + + SSplitter::Slot() + .Value(.2f) + [ + SNew(SSplitter) + .PhysicalSplitterHandleSize(10.0f) + +SSplitter::Slot() + [ + PanelOld.DetailsView.ToSharedRef() + ] + + SSplitter::Slot() + [ + PanelNew.DetailsView.ToSharedRef() + ] + ] + ] + ]; + + return Ret; } SBlueprintDiff::FDiffControl SBlueprintDiff::GenerateGraphPanel() { - const TSharedRef DefaultLayout = FTabManager::NewLayout("BlueprintDiff_Layout_v1") - ->AddArea - ( - FTabManager::NewPrimaryArea() - ->Split - ( - FTabManager::NewStack() - ->AddTab(DiffMyBluerpintTabId, ETabState::OpenedTab) - ->AddTab(DiffGraphTabId, ETabState::OpenedTab) - ) - ); - - // SMyBlueprint needs to be created *before* the KismetInspector, because the KismetInspector's customizations - // need a reference to the SMyBlueprint widget that is controlling them... - TSharedRef TabControl = TabManager->RestoreFrom(DefaultLayout, TSharedPtr()).ToSharedRef(); - - const auto CreateInspector = [](TSharedPtr InMyBlueprint) { - return SNew( SKismetInspector) - .HideNameArea(true) - .ViewIdentifier(FName("BlueprintInspector")) - .MyBlueprintWidget(InMyBlueprint) - .IsPropertyEditingEnabledDelegate(FIsPropertyEditingEnabled::CreateStatic([] { return false; })); - }; - SBlueprintDiff::FDiffControl Ret; - PanelOld.DetailsView = CreateInspector(PanelOld.MyBlueprint); - PanelOld.MyBlueprint->SetInspector(PanelOld.DetailsView); - PanelNew.DetailsView = CreateInspector(PanelNew.MyBlueprint); - PanelNew.MyBlueprint->SetInspector(PanelNew.DetailsView); - - Ret.Widget = SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) - .Content() + Ret.Widget = SNew(SVerticalBox) + + SVerticalBox::Slot() + .FillHeight(1.f) [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .FillHeight(1.f) + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.f) [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.f) + //diff window + SNew(SSplitter) + .Orientation(Orient_Vertical) + +SSplitter::Slot() + .Value(.8f) [ - //diff window SNew(SSplitter) - .Orientation(Orient_Vertical) - +SSplitter::Slot() - .Value(.8f) + .PhysicalSplitterHandleSize(10.0f) + + SSplitter::Slot() [ - // graph and my blueprint views: - TabControl + SAssignNew(PanelOld.GraphEditorBox, SBox) + .VAlign(VAlign_Fill) + [ + DefaultEmptyPanel() + ] ] + SSplitter::Slot() - .Value(.2f) [ - SNew( SSplitter ) - +SSplitter::Slot() + SAssignNew(PanelNew.GraphEditorBox, SBox) + .VAlign(VAlign_Fill) [ - PanelOld.DetailsView.ToSharedRef() - ] - + SSplitter::Slot() - [ - PanelNew.DetailsView.ToSharedRef() + DefaultEmptyPanel() ] ] ] + + SSplitter::Slot() + .Value(.2f) + [ + SNew(SSplitter) + .PhysicalSplitterHandleSize(10.0f) + +SSplitter::Slot() + [ + PanelOld.DetailsView.ToSharedRef() + ] + + SSplitter::Slot() + [ + PanelNew.DetailsView.ToSharedRef() + ] + ] ] ]; @@ -1440,29 +1677,45 @@ SBlueprintDiff::FDiffControl SBlueprintDiff::GenerateDefaultsPanel() const UObject* A = DiffUtils::GetCDO(PanelOld.Blueprint); const UObject* B = DiffUtils::GetCDO(PanelNew.Blueprint); - auto NewDiffControl = TSharedPtr(new FCDODiffControl(A, B, MasterDifferencesList, RealDifferences, FOnCDODiffControlChanged::CreateRaw(this, &SBlueprintDiff::SetCurrentMode, FBlueprintEditorApplicationModes::BlueprintDefaultsMode))); + TSharedPtr NewDiffControl = MakeShared(A, B, FOnDiffEntryFocused::CreateRaw(this, &SBlueprintDiff::SetCurrentMode, DefaultsMode)); + NewDiffControl->GenerateTreeEntries(MasterDifferencesList, RealDifferences); SBlueprintDiff::FDiffControl Ret; - //Splitter for left and right blueprint. Current convention is for the local (probably newer?) blueprint to be on the right: Ret.DiffControl = NewDiffControl; Ret.Widget = SNew(SSplitter) + .PhysicalSplitterHandleSize(10.0f) + SSplitter::Slot() .Value(0.5f) [ - SNew(SBorder) - .VAlign(VAlign_Fill) - [ - NewDiffControl->OldDetailsWidget() - ] + NewDiffControl->OldDetailsWidget() ] - + SSplitter::Slot() + + SSplitter::Slot() .Value(0.5f) [ - SNew(SBorder) - .VAlign(VAlign_Fill) - [ - NewDiffControl->NewDetailsWidget() - ] + NewDiffControl->NewDetailsWidget() + ]; + + return Ret; +} + +SBlueprintDiff::FDiffControl SBlueprintDiff::GenerateClassSettingsPanel() +{ + TSharedPtr NewDiffControl = MakeShared(PanelOld.Blueprint, PanelNew.Blueprint, FOnDiffEntryFocused::CreateRaw(this, &SBlueprintDiff::SetCurrentMode, ClassSettingsMode)); + NewDiffControl->GenerateTreeEntries(MasterDifferencesList, RealDifferences); + + SBlueprintDiff::FDiffControl Ret; + Ret.DiffControl = NewDiffControl; + Ret.Widget = SNew(SSplitter) + .PhysicalSplitterHandleSize(10.0f) + + SSplitter::Slot() + .Value(0.5f) + [ + NewDiffControl->OldDetailsWidget() + ] + + SSplitter::Slot() + .Value(0.5f) + [ + NewDiffControl->NewDetailsWidget() ]; return Ret; @@ -1470,29 +1723,22 @@ SBlueprintDiff::FDiffControl SBlueprintDiff::GenerateDefaultsPanel() SBlueprintDiff::FDiffControl SBlueprintDiff::GenerateComponentsPanel() { - SBlueprintDiff::FDiffControl Ret; + TSharedPtr NewDiffControl = MakeShared(PanelOld.Blueprint, PanelNew.Blueprint, FOnDiffEntryFocused::CreateRaw(this, &SBlueprintDiff::SetCurrentMode, ComponentsMode)); + NewDiffControl->GenerateTreeEntries(MasterDifferencesList, RealDifferences); - //Splitter for left and right blueprint. Current convention is for the local (probably newer?) blueprint to be on the right: - auto NewDiffControl = TSharedPtr(new FSCSDiffControl(PanelOld.Blueprint, PanelNew.Blueprint, MasterDifferencesList, RealDifferences, FOnSCSDiffControlChanged::CreateRaw(this, &SBlueprintDiff::SetCurrentMode, FBlueprintEditorApplicationModes::BlueprintComponentsMode) ) ); + SBlueprintDiff::FDiffControl Ret; Ret.DiffControl = NewDiffControl; Ret.Widget = SNew(SSplitter) + .PhysicalSplitterHandleSize(10.0f) + SSplitter::Slot() .Value(0.5f) [ - SNew(SBorder) - .VAlign(VAlign_Fill) - [ - NewDiffControl->OldTreeWidget() - ] + NewDiffControl->OldTreeWidget() ] + SSplitter::Slot() .Value(0.5f) [ - SNew(SBorder) - .VAlign(VAlign_Fill) - [ - NewDiffControl->NewTreeWidget() - ] + NewDiffControl->NewTreeWidget() ]; return Ret; @@ -1507,21 +1753,15 @@ void SBlueprintDiff::SetCurrentMode(FName NewMode) CurrentMode = NewMode; - DiffControl = TSharedPtr< IDiffControl >(); - if( NewMode == FBlueprintEditorApplicationModes::StandardBlueprintEditorMode) + FDiffControl* FoundControl = ModePanels.Find(NewMode); + + if (FoundControl) { - DiffControl = GraphPanel.DiffControl; - ModeContents->SetContent( GraphPanel.Widget.ToSharedRef() ); - } - else if( NewMode == FBlueprintEditorApplicationModes::BlueprintDefaultsMode ) - { - DiffControl = DefaultsPanel.DiffControl; - ModeContents->SetContent( DefaultsPanel.Widget.ToSharedRef() ); - } - else if (NewMode == FBlueprintEditorApplicationModes::BlueprintComponentsMode) - { - DiffControl = ComponentsPanel.DiffControl; - ModeContents->SetContent( ComponentsPanel.Widget.ToSharedRef() ); + // Reset inspector view + PanelOld.DetailsView->ShowDetailsForObjects(TArray()); + PanelNew.DetailsView->ShowDetailsForObjects(TArray()); + + ModeContents->SetContent(FoundControl->Widget.ToSharedRef()); } else { @@ -1529,7 +1769,7 @@ void SBlueprintDiff::SetCurrentMode(FName NewMode) } } -bool SBlueprintDiff::IsGraphDiffNeeded(class UEdGraph* InGraph) const +bool SBlueprintDiff::IsGraphDiffNeeded(UEdGraph* InGraph) const { // Do not worry about graphs that are contained in MathExpression nodes, they are recreated each compile return !InGraph->GetOuter()->IsA(); diff --git a/Engine/Source/Editor/Kismet/Private/SBlueprintEditorToolbar.cpp b/Engine/Source/Editor/Kismet/Private/SBlueprintEditorToolbar.cpp index f4c7ca00669c..306edf1d82fb 100644 --- a/Engine/Source/Editor/Kismet/Private/SBlueprintEditorToolbar.cpp +++ b/Engine/Source/Editor/Kismet/Private/SBlueprintEditorToolbar.cpp @@ -88,7 +88,7 @@ void FKismet2Menu::FillFileMenuBlueprintSection( FMenuBuilder& MenuBuilder, FBlu LOCTEXT("DeveloperMenu", "Developer"), LOCTEXT("DeveloperMenu_ToolTip", "Open the developer menu"), FNewMenuDelegate::CreateStatic( &FKismet2Menu::FillDeveloperMenu ), - true); + false); } MenuBuilder.EndSection(); } @@ -243,7 +243,7 @@ static void OnDiffRevisionPicked(FRevisionInfo const& RevisionInfo, TWeakObjectP if (Revision->Get(PreviousTempPkgName)) { // Try and load that package - UPackage* PreviousTempPkg = LoadPackage(NULL, *PreviousTempPkgName, LOAD_DisableCompileOnLoad); + UPackage* PreviousTempPkg = LoadPackage(NULL, *PreviousTempPkgName, LOAD_ForDiff|LOAD_DisableCompileOnLoad); if (PreviousTempPkg != NULL) { diff --git a/Engine/Source/Editor/Kismet/Private/SBlueprintPalette.cpp b/Engine/Source/Editor/Kismet/Private/SBlueprintPalette.cpp index cf91c18cef30..96fca67d9739 100644 --- a/Engine/Source/Editor/Kismet/Private/SBlueprintPalette.cpp +++ b/Engine/Source/Editor/Kismet/Private/SBlueprintPalette.cpp @@ -53,6 +53,7 @@ #include "BlueprintEditorSettings.h" #include "Widgets/Text/SInlineEditableTextBlock.h" #include "SPinTypeSelector.h" +#include "GraphEditorSettings.h" #define LOCTEXT_NAMESPACE "BlueprintPalette" @@ -250,25 +251,29 @@ static void GetSubGraphIcon(FEdGraphSchemaAction_K2Graph const* const ActionIn, break; case EEdGraphSchemaAction_K2Graph::Function: { - if ( ActionIn->EdGraph == nullptr ) + if (ActionIn->EdGraph == nullptr) { IconOut = FEditorStyle::GetBrush(TEXT("GraphEditor.PotentialOverrideFunction_16x")); ToolTipOut = LOCTEXT("PotentialOverride_Tooltip", "Potential Override"); } else { - if ( ActionIn->EdGraph->IsA(UAnimationGraph::StaticClass()) ) + if (ActionIn->EdGraph->IsA(UAnimationGraph::StaticClass())) { IconOut = FEditorStyle::GetBrush(TEXT("GraphEditor.Animation_16x")); } else if (UFunction* OverrideFunc = FindField(BlueprintIn->ParentClass, ActionIn->FuncName)) { - IconOut = FEditorStyle::GetBrush(TEXT("GraphEditor.OverrideFunction_16x")); + const bool bIsPureFunction = OverrideFunc && OverrideFunc->HasAnyFunctionFlags(FUNC_BlueprintPure); + IconOut = FEditorStyle::GetBrush(bIsPureFunction ? TEXT("GraphEditor.OverridePureFunction_16x") : TEXT("GraphEditor.OverrideFunction_16x")); ToolTipOut = LOCTEXT("Override_Tooltip", "Override"); } else { - IconOut = FEditorStyle::GetBrush(TEXT("GraphEditor.Function_16x")); + UFunction* Function = FindField(BlueprintIn->SkeletonGeneratedClass, ActionIn->FuncName); + const bool bIsPureFunction = Function && Function->HasAnyFunctionFlags(FUNC_BlueprintPure); + + IconOut = FEditorStyle::GetBrush(bIsPureFunction ? TEXT("GraphEditor.PureFunction_16x") : TEXT("GraphEditor.Function_16x")); if (ActionIn->EdGraph->IsA(UAnimationGraph::StaticClass())) { ToolTipOut = LOCTEXT("AnimationGraph_Tooltip", "Animation Graph"); @@ -703,18 +708,18 @@ public: .TargetPinType(this, &SPinTypeSelectorHelper::OnGetVarType) .OnPinTypeChanged(this, &SPinTypeSelectorHelper::OnVarTypeChanged) .TypeTreeFilter(ETypeTreeFilter::None) - .bCompactSelector(true) + .SelectorType(BlueprintEditorPtr.IsValid() ? SPinTypeSelector::ESelectorType::Compact : SPinTypeSelector::ESelectorType::None) ]; } private: FEdGraphPinType OnGetVarType() const { - if (VariableProperty) + if (UProperty* VarProp = VariableProperty.Get()) { const UEdGraphSchema_K2* K2Schema = GetDefault(); FEdGraphPinType Type; - K2Schema->ConvertPropertyToPinType(VariableProperty, Type); + K2Schema->ConvertPropertyToPinType(VarProp, Type); return Type; } return FEdGraphPinType(); @@ -724,16 +729,16 @@ private: { if (FBlueprintEditorUtils::IsPinTypeValid(InNewPinType)) { - if (VariableProperty) + if (UProperty* VarProp = VariableProperty.Get()) { - FName VarName = VariableProperty->GetFName(); + FName VarName = VarProp->GetFName(); if (VarName != NAME_None) { // Set the MyBP tab's last pin type used as this, for adding lots of variables of the same type BlueprintEditorPtr.Pin()->GetMyBlueprintWidget()->GetLastPinTypeUsed() = InNewPinType; - if (UFunction* LocalVariableScope = Cast(VariableProperty->GetOuter())) + if (UFunction* LocalVariableScope = Cast(VarProp->GetOuter())) { FBlueprintEditorUtils::ChangeLocalVariableType(BlueprintObj, LocalVariableScope, VarName, InNewPinType); } @@ -757,7 +762,7 @@ private: TWeakPtr BlueprintEditorPtr; /** Variable Property to change the type of */ - UProperty* VariableProperty; + TWeakObjectPtr VariableProperty; }; /******************************************************************************* @@ -934,18 +939,7 @@ private: } else { - TSharedPtr VarAction = StaticCastSharedPtr(ActionPtr.Pin()); - - FString Result; - FBlueprintEditorUtils::GetBlueprintVariableMetaData(BlueprintObj, VarAction->GetVariableName(), nullptr, TEXT("tooltip"), Result); - if ( !Result.IsEmpty() ) - { - ToolTipText = LOCTEXT("VariablePrivacy_is_public_Tooltip", "Variable is public and is editable on each instance of this Blueprint."); - } - else - { - ToolTipText = LOCTEXT("VariablePrivacy_is_public_no_tooltip_Tooltip", "Variable is public but MISSING TOOLTIP."); - } + ToolTipText = LOCTEXT("VariablePrivacy_is_public_Tooltip", "Variable is public and is editable on each instance of this Blueprint."); } return ToolTipText; } diff --git a/Engine/Source/Editor/Kismet/Private/SBlueprintSubPalette.cpp b/Engine/Source/Editor/Kismet/Private/SBlueprintSubPalette.cpp index 88b130fe8f2a..49844724aeac 100644 --- a/Engine/Source/Editor/Kismet/Private/SBlueprintSubPalette.cpp +++ b/Engine/Source/Editor/Kismet/Private/SBlueprintSubPalette.cpp @@ -376,7 +376,7 @@ void SBlueprintSubPalette::BindCommands(TSharedPtr CommandListIn CommandListIn->MapAction( PaletteCommands.RefreshPalette, - FExecuteAction::CreateSP(this, &SBlueprintSubPalette::RefreshActionsList, /*bPreserveExpansion =*/true) + FExecuteAction::CreateSP(const_cast(this), &SBlueprintSubPalette::RefreshActionsList, /*bPreserveExpansion =*/true) ); } diff --git a/Engine/Source/Editor/Kismet/Private/SCSDiff.cpp b/Engine/Source/Editor/Kismet/Private/SCSDiff.cpp index 7203c5f9289c..8c8c489b9eca 100644 --- a/Engine/Source/Editor/Kismet/Private/SCSDiff.cpp +++ b/Engine/Source/Editor/Kismet/Private/SCSDiff.cpp @@ -18,6 +18,9 @@ FSCSDiff::FSCSDiff(const UBlueprint* InBlueprint) return; } + // Need to pull off const because the ICH access functions theoretically could modify it + Blueprint = const_cast(InBlueprint); + Inspector = SNew(SKismetInspector) .HideNameArea(true) .ViewIdentifier(FName("BlueprintInspector")) @@ -55,16 +58,16 @@ TSharedRef< SWidget > FSCSDiff::TreeWidget() return ContainerWidget.ToSharedRef(); } -void GetDisplayedHierarchyRecursive(TArray< int32 >& TreeAddress, const FSCSEditorTreeNode& Node, TArray< FSCSResolvedIdentifier >& OutResult) +void GetDisplayedHierarchyRecursive(UBlueprint* Blueprint, TArray< int32 >& TreeAddress, const FSCSEditorTreeNode& Node, TArray< FSCSResolvedIdentifier >& OutResult) { FSCSIdentifier Identifier = { Node.GetVariableName(), TreeAddress }; - FSCSResolvedIdentifier ResolvedIdentifier = { Identifier, Node.GetComponentTemplate() }; + FSCSResolvedIdentifier ResolvedIdentifier = { Identifier, Node.GetOrCreateEditableComponentTemplate(Blueprint) }; OutResult.Push(ResolvedIdentifier); const auto& Children = Node.GetChildren(); for (int32 Iter = 0; Iter != Children.Num(); ++Iter) { TreeAddress.Push(Iter); - GetDisplayedHierarchyRecursive(TreeAddress, *Children[Iter], OutResult); + GetDisplayedHierarchyRecursive(Blueprint, TreeAddress, *Children[Iter], OutResult); TreeAddress.Pop(); } } @@ -73,14 +76,14 @@ TArray< FSCSResolvedIdentifier > FSCSDiff::GetDisplayedHierarchy() const { TArray< FSCSResolvedIdentifier > Ret; - if( SCSEditor.IsValid() ) + if( SCSEditor.IsValid() && SCSEditor->GetActorNode().IsValid()) { - const TArray& RootNodes = SCSEditor->GetRootComponentNodes(); + const TArray& RootNodes = SCSEditor->GetActorNode()->GetComponentNodes(); for (int32 Iter = 0; Iter != RootNodes.Num(); ++Iter) { TArray< int32 > TreeAddress; TreeAddress.Push(Iter); - GetDisplayedHierarchyRecursive(TreeAddress, *RootNodes[Iter], Ret); + GetDisplayedHierarchyRecursive(Blueprint, TreeAddress, *RootNodes[Iter], Ret); } } @@ -98,7 +101,7 @@ void FSCSDiff::OnSCSEditorUpdateSelectionFromNodes(const TArrayCanEditDefaults()) { InspectorTitle = FText::FromString(NodePtr->GetDisplayString()); - InspectorObjects.Add(NodePtr->GetComponentTemplate()); + InspectorObjects.Add(NodePtr->GetOrCreateEditableComponentTemplate(Blueprint)); } } diff --git a/Engine/Source/Editor/Kismet/Private/SCSEditorViewportClient.cpp b/Engine/Source/Editor/Kismet/Private/SCSEditorViewportClient.cpp index 307970cfe171..5dd7eca9663a 100644 --- a/Engine/Source/Editor/Kismet/Private/SCSEditorViewportClient.cpp +++ b/Engine/Source/Editor/Kismet/Private/SCSEditorViewportClient.cpp @@ -447,7 +447,7 @@ bool FSCSEditorViewportClient::InputWidgetDelta( FViewport* InViewport, EAxisLis } USceneComponent* SceneComp = Cast(SelectedNodePtr->FindComponentInstanceInActor(PreviewActor)); - USceneComponent* SelectedTemplate = Cast(SelectedNodePtr->GetEditableComponentTemplate(BlueprintEditor->GetBlueprintObj())); + USceneComponent* SelectedTemplate = Cast(SelectedNodePtr->GetOrCreateEditableComponentTemplate(BlueprintEditor->GetBlueprintObj())); if(SceneComp != NULL && SelectedTemplate != NULL) { // Cache the current default values for propagation @@ -588,31 +588,34 @@ FWidget::EWidgetMode FSCSEditorViewportClient::GetWidgetMode() const if ( BluePrintEditor.IsValid() ) { TArray SelectedNodes = BluePrintEditor->GetSelectedSCSEditorTreeNodes(); - const TArray& RootNodes = BluePrintEditor->GetSCSEditor()->GetRootComponentNodes(); + if (BluePrintEditor->GetSCSEditor()->GetActorNode().IsValid()) + { + const TArray& RootNodes = BluePrintEditor->GetSCSEditor()->GetActorNode()->GetComponentNodes(); - if (GUnrealEd->ComponentVisManager.IsActive() && - GUnrealEd->ComponentVisManager.IsVisualizingArchetype()) - { - // Component visualizer is active and editing the archetype - ReturnWidgetMode = WidgetMode; - } - else - { - // if the selected nodes array is empty, or only contains entries from the - // root nodes array, or isn't visible in the preview actor, then don't display a transform widget - for (int32 CurrentNodeIndex = 0; CurrentNodeIndex < SelectedNodes.Num(); CurrentNodeIndex++) + if (GUnrealEd->ComponentVisManager.IsActive() && + GUnrealEd->ComponentVisManager.IsVisualizingArchetype()) { - FSCSEditorTreeNodePtrType CurrentNodePtr = SelectedNodes[CurrentNodeIndex]; - if ((CurrentNodePtr.IsValid() && - ((!RootNodes.Contains(CurrentNodePtr) && !CurrentNodePtr->IsRootComponent()) || - (Cast(CurrentNodePtr->GetComponentTemplate()) && // show widget if we are editing individual instances even if it is the root component - CastChecked(CurrentNodePtr->FindComponentInstanceInActor(GetPreviewActor()))->SelectedInstances.Contains(true))) && - CurrentNodePtr->CanEditDefaults() && - CurrentNodePtr->FindComponentInstanceInActor(PreviewActor))) + // Component visualizer is active and editing the archetype + ReturnWidgetMode = WidgetMode; + } + else + { + // if the selected nodes array is empty, or only contains entries from the + // root nodes array, or isn't visible in the preview actor, then don't display a transform widget + for (int32 CurrentNodeIndex = 0; CurrentNodeIndex < SelectedNodes.Num(); CurrentNodeIndex++) { - // a non-NULL, non-root item is selected, draw the widget - ReturnWidgetMode = WidgetMode; - break; + FSCSEditorTreeNodePtrType CurrentNodePtr = SelectedNodes[CurrentNodeIndex]; + if ((CurrentNodePtr.IsValid() && + ((!RootNodes.Contains(CurrentNodePtr) && !CurrentNodePtr->IsRootComponent()) || + (Cast(CurrentNodePtr->GetComponentTemplate()) && // show widget if we are editing individual instances even if it is the root component + CastChecked(CurrentNodePtr->FindComponentInstanceInActor(GetPreviewActor()))->SelectedInstances.Contains(true))) && + CurrentNodePtr->CanEditDefaults() && + CurrentNodePtr->FindComponentInstanceInActor(PreviewActor))) + { + // a non-NULL, non-root item is selected, draw the widget + ReturnWidgetMode = WidgetMode; + break; + } } } } @@ -927,7 +930,7 @@ void FSCSEditorViewportClient::BeginTransaction(const FText& Description) } // Modify template, any instances will be reconstructed as part of PostUndo: - UActorComponent* ComponentTemplate = Node->GetEditableComponentTemplate(PreviewBlueprint); + UActorComponent* ComponentTemplate = Node->GetOrCreateEditableComponentTemplate(PreviewBlueprint); if (ComponentTemplate != nullptr) { ComponentTemplate->SetFlags(RF_Transactional); diff --git a/Engine/Source/Editor/Kismet/Private/SMyBlueprint.cpp b/Engine/Source/Editor/Kismet/Private/SMyBlueprint.cpp index c98c749653ad..369ca20c3d7b 100644 --- a/Engine/Source/Editor/Kismet/Private/SMyBlueprint.cpp +++ b/Engine/Source/Editor/Kismet/Private/SMyBlueprint.cpp @@ -841,6 +841,12 @@ TSharedRef SMyBlueprint::OnGetFunctionListMenu() void SMyBlueprint::BuildOverridableFunctionsMenu(FMenuBuilder& MenuBuilder) { + // Sort by function name so that it's easier for users to find the function they're looking for: + OverridableFunctionActions.Sort([](const TSharedPtr &LHS, const TSharedPtr &RHS) + { + return LHS->GetMenuDescription().CompareToCaseIgnored(RHS->GetMenuDescription()) < 0; + }); + MenuBuilder.BeginSection("OverrideFunction", LOCTEXT("OverrideFunction", "Override Function")); { for ( auto& OverrideAction : OverridableFunctionActions ) @@ -1154,6 +1160,12 @@ void SMyBlueprint::CollectAllActions(FGraphActionListBuilderBase& OutAllActions) } } + // Default, so place in 'non' category + if (FunctionCategory.EqualTo(FText::FromString(BlueprintObj->GetName())) || FunctionCategory.EqualTo(UEdGraphSchema_K2::VR_DefaultCategory)) + { + FunctionCategory = FText::GetEmpty(); + } + //@TODO: Should be a bit more generic (or the AnimGraph shouldn't be stored as a FunctionGraph...) const bool bIsConstructionScript = Graph->GetFName() == UEdGraphSchema_K2::FN_UserConstructionScript; @@ -2803,33 +2815,37 @@ void SMyBlueprint::ExpandCategory(const FText& CategoryName) GraphActionMenu->ExpandCategory(CategoryName); } -bool SMyBlueprint::MoveCategoryBeforeCategory( const FText& InCategoryToMove, const FText& InTargetCategory ) +bool SMyBlueprint::MoveCategoryBeforeCategory(const FText& InCategoryToMove, const FText& InTargetCategory) { bool bResult = false; - UBlueprint* BlueprintObj = BlueprintEditorPtr.Pin()->GetBlueprintObj(); FString CategoryToMoveString = InCategoryToMove.ToString(); FString TargetCategoryString = InTargetCategory.ToString(); - if( BlueprintObj ) + if (UBlueprint* BlueprintObj = BlueprintEditorPtr.Pin()->GetBlueprintObj()) { + FScopedTransaction Transaction(LOCTEXT("ReorderCategories", "Reorder Categories")); + BlueprintObj->Modify(); + // Find root categories - int32 RootCategoryDelim = CategoryToMoveString.Find( TEXT( "|" ), ESearchCase::CaseSensitive ); - FName CategoryToMove = RootCategoryDelim == INDEX_NONE ? *CategoryToMoveString : *CategoryToMoveString.Left( RootCategoryDelim ); - RootCategoryDelim = TargetCategoryString.Find( TEXT( "|" ), ESearchCase::CaseSensitive ); - FName TargetCategory = RootCategoryDelim == INDEX_NONE ? *TargetCategoryString : *TargetCategoryString.Left( RootCategoryDelim ); + int32 RootCategoryDelim = CategoryToMoveString.Find(TEXT("|"), ESearchCase::CaseSensitive); + FName CategoryToMove = RootCategoryDelim == INDEX_NONE ? *CategoryToMoveString : *CategoryToMoveString.Left(RootCategoryDelim); + RootCategoryDelim = TargetCategoryString.Find(TEXT("|"), ESearchCase::CaseSensitive); + FName TargetCategory = RootCategoryDelim == INDEX_NONE ? *TargetCategoryString : *TargetCategoryString.Left(RootCategoryDelim); TArray& CategorySort = BlueprintObj->CategorySorting; - const int32 RemovalIndex = CategorySort.Find( CategoryToMove ); + // Remove existing sort index - if( RemovalIndex != INDEX_NONE ) + const int32 RemovalIndex = CategorySort.Find(CategoryToMove); + if (RemovalIndex != INDEX_NONE) { - CategorySort.RemoveAt( RemovalIndex ); + CategorySort.RemoveAt(RemovalIndex); } + // Update the Category sort order and refresh ( if the target category has an entry ) - const int32 InsertIndex = CategorySort.Find( TargetCategory ); - if( InsertIndex != INDEX_NONE ) + const int32 InsertIndex = CategorySort.Find(TargetCategory); + if (InsertIndex != INDEX_NONE) { - CategorySort.Insert( CategoryToMove, InsertIndex ); + CategorySort.Insert(CategoryToMove, InsertIndex); Refresh(); bResult = true; } diff --git a/Engine/Source/Editor/Kismet/Private/SSCSEditor.cpp b/Engine/Source/Editor/Kismet/Private/SSCSEditor.cpp index a9f9d620bf9a..5d034dcddfca 100644 --- a/Engine/Source/Editor/Kismet/Private/SSCSEditor.cpp +++ b/Engine/Source/Editor/Kismet/Private/SSCSEditor.cpp @@ -212,7 +212,7 @@ FReply SSCSEditor::TryHandleAssetDragDropOperation(const FDragDropEvent& DragDro bMarkBlueprintAsModified = true; } } - else if ((PotentialActorClass != nullptr) && !PotentialActorClass->HasAnyClassFlags(CLASS_Deprecated | CLASS_Abstract | CLASS_NewerVersionExists)) + else if ((PotentialActorClass != nullptr) && !PotentialActorClass->HasAnyClassFlags(CLASS_Deprecated | CLASS_Abstract | CLASS_NewerVersionExists | CLASS_NotPlaceable)) { AddNewComponent(UChildActorComponent::StaticClass(), PotentialActorClass, true, bSetFocusToNewItem ); bMarkBlueprintAsModified = true; @@ -433,7 +433,7 @@ class USCS_Node* FSCSEditorTreeNode::GetSCSNode() const return nullptr; } -UActorComponent* FSCSEditorTreeNode::GetEditableComponentTemplate(UBlueprint* ActualEditedBlueprint) +UActorComponent* FSCSEditorTreeNode::GetOrCreateEditableComponentTemplate(UBlueprint* ActualEditedBlueprint) const { return nullptr; } @@ -529,7 +529,7 @@ void FSCSEditorTreeNode::UpdateCachedFilterState(bool bMatchesFilter, bool bUpda void FSCSEditorTreeNode::RefreshCachedChildFilterState(bool bUpdateParent) { - const bool bCointainedMatch = !IsFlaggedForFiltration(); + const bool bContainedMatch = !IsFlaggedForFiltration(); FilterFlags &= ~EFilteredState::ChildMatches; for (FSCSEditorTreeNodePtrType Child : Children) @@ -540,9 +540,9 @@ void FSCSEditorTreeNode::RefreshCachedChildFilterState(bool bUpdateParent) break; } } - const bool bCointainsMatch = !IsFlaggedForFiltration(); + const bool bContainsMatch = !IsFlaggedForFiltration(); - const bool bStateChange = bCointainedMatch != bCointainsMatch; + const bool bStateChange = bContainedMatch != bContainsMatch; if (bUpdateParent && bStateChange) { ApplyFilteredStateToParent(); @@ -789,7 +789,7 @@ FSCSEditorTreeNodePtrType FSCSEditorTreeNode::FactoryNodeFromComponent(UActorCom } else { - return MakeShareable(new FSCSEditorTreeNodeInstancedInheritedComponent(Owner, InComponent->GetFName())); + return MakeShareable(new FSCSEditorTreeNodeInstancedInheritedComponent(Owner, InComponent)); } } @@ -866,7 +866,7 @@ FSCSEditorTreeNodePtrType FSCSEditorTreeNode::FindChild(const FName& InVariableO for(int32 ChildIndex = 0; ChildIndex < Children.Num() && !Result.IsValid(); ++ChildIndex) { FName ItemName = Children[ChildIndex]->GetVariableName(); - if(ItemName == NAME_None) + if(ItemName == NAME_None && Children[ChildIndex]->GetNodeType() == ComponentNode) { UActorComponent* ComponentTemplateOrInstance = Children[ChildIndex]->GetComponentTemplate(); check(ComponentTemplateOrInstance != nullptr); @@ -989,22 +989,13 @@ FString FSCSEditorTreeNodeComponentBase::GetDisplayString() const ////////////////////////////////////////////////////////////////////////// // FSCSEditorTreeNodeInstancedInheritedComponent -FSCSEditorTreeNodeInstancedInheritedComponent::FSCSEditorTreeNodeInstancedInheritedComponent(AActor* Owner, FName InComponentName) +FSCSEditorTreeNodeInstancedInheritedComponent::FSCSEditorTreeNodeInstancedInheritedComponent(AActor* Owner, UActorComponent* ComponentInstance) { - InstancedComponentName = InComponentName; - check(InstancedComponentName != NAME_None); // ...otherwise IsRootActor() can return a false positive. + check(ComponentInstance != nullptr); InstancedComponentOwnerPtr = Owner; - SetComponentTemplate(nullptr); - for (UActorComponent* ComponentInstance : Owner->GetComponents()) - { - if (ComponentInstance && ComponentInstance->GetFName() == InstancedComponentName) - { SetComponentTemplate(ComponentInstance); - break; - } - } } bool FSCSEditorTreeNodeInstancedInheritedComponent::IsNative() const @@ -1061,7 +1052,7 @@ FText FSCSEditorTreeNodeInstancedInheritedComponent::GetDisplayName() const return FText::GetEmpty(); } -UActorComponent* FSCSEditorTreeNodeInstancedInheritedComponent::GetEditableComponentTemplate(UBlueprint* ActualEditedBlueprint) +UActorComponent* FSCSEditorTreeNodeInstancedInheritedComponent::GetOrCreateEditableComponentTemplate(UBlueprint* ActualEditedBlueprint) const { if (CanEditDefaults()) { @@ -1120,7 +1111,7 @@ FText FSCSEditorTreeNodeInstanceAddedComponent::GetDisplayName() const return FText::FromName(InstancedComponentName); } -UActorComponent* FSCSEditorTreeNodeInstanceAddedComponent::GetEditableComponentTemplate(UBlueprint* ActualEditedBlueprint) +UActorComponent* FSCSEditorTreeNodeInstanceAddedComponent::GetOrCreateEditableComponentTemplate(UBlueprint* ActualEditedBlueprint) const { return GetComponentTemplate(); } @@ -1149,7 +1140,7 @@ void FSCSEditorTreeNodeInstanceAddedComponent::OnCompleteRename(const FText& InN } ERenameFlags RenameFlags = REN_DontCreateRedirectors; - + // name collision could occur due to e.g. our archetype being updated and causing a conflict with our ComponentInstance: FString NewNameAsString = InNewName.ToString(); if(StaticFindObject(UObject::StaticClass(), ComponentInstance->GetOuter(), *NewNameAsString) == nullptr) @@ -1277,7 +1268,7 @@ class USCS_Node* FSCSEditorTreeNodeComponent::GetSCSNode() const return SCSNodePtr.Get(); } -UActorComponent* FSCSEditorTreeNodeComponent::GetEditableComponentTemplate(UBlueprint* ActualEditedBlueprint) +UActorComponent* FSCSEditorTreeNodeComponent::GetOrCreateEditableComponentTemplate(UBlueprint* ActualEditedBlueprint) const { if (CanEditDefaults()) { @@ -1328,15 +1319,9 @@ UActorComponent* FSCSEditorTreeNode::FindComponentInstanceInActor(const AActor* } else if (ComponentTemplate != NULL) { - // Look for a native component instance with a name that matches the template name - for (UActorComponent* Component : InActor->GetComponents()) - { - if (Component && Component->GetFName() == ComponentTemplate->GetFName()) - { - ComponentInstance = Component; - break; - } - } + TInlineComponentArray Components; + InActor->GetComponents(Components); + ComponentInstance = FComponentEditorUtils::FindMatchingComponent(ComponentTemplate, Components); } } @@ -1395,6 +1380,84 @@ UActorComponent* FSCSEditorTreeNodeComponent::INTERNAL_GetOverridenComponentTemp ////////////////////////////////////////////////////////////////////////// // FSCSEditorTreeNodeRootActor +FSCSEditorTreeNodePtrType FSCSEditorTreeNodeRootActor::GetSceneRootNode() const +{ + return SceneRootNodePtr; +} + +void FSCSEditorTreeNodeRootActor::SetSceneRootNode(FSCSEditorTreeNodePtrType NewSceneRootNode) +{ + if (SceneRootNodePtr.IsValid()) + { + ComponentNodes.Remove(SceneRootNodePtr); + } + + SceneRootNodePtr = NewSceneRootNode; + + if (!ComponentNodes.Contains(SceneRootNodePtr)) + { + ComponentNodes.Add(SceneRootNodePtr); + } +} + +const TArray& FSCSEditorTreeNodeRootActor::GetComponentNodes() const +{ + return ComponentNodes; +} + +void FSCSEditorTreeNodeRootActor::AddChild(FSCSEditorTreeNodePtrType InChildNodePtr) +{ + if (InChildNodePtr->GetNodeType() == FSCSEditorTreeNode::ComponentNode) + { + ComponentNodes.Add(InChildNodePtr); + USceneComponent* SceneComponent = Cast(InChildNodePtr->GetComponentTemplate()); + if (!SceneRootNodePtr.IsValid() && SceneComponent != nullptr) + { + SetSceneRootNode(InChildNodePtr); + } + + // Make sure separators are shown + if (SceneComponent != nullptr && !SceneComponentSeparatorNodePtr.IsValid()) + { + SceneComponentSeparatorNodePtr = MakeShareable(new FSCSEditorTreeNodeSeparator()); + Super::AddChild(SceneComponentSeparatorNodePtr); + } + else if (SceneComponent == nullptr && !NonSceneComponentSeparatorNodePtr.IsValid()) + { + NonSceneComponentSeparatorNodePtr = MakeShareable(new FSCSEditorTreeNodeSeparator()); + Super::AddChild(NonSceneComponentSeparatorNodePtr); + } + } + + Super::AddChild(InChildNodePtr); +} + +void FSCSEditorTreeNodeRootActor::RemoveChild(FSCSEditorTreeNodePtrType InChildNodePtr) +{ + Super::RemoveChild(InChildNodePtr); + + int32 indexOfFirstSceneComponent = ComponentNodes.IndexOfByPredicate([](const FSCSEditorTreeNodePtrType& NodePtr) + { + return NodePtr->GetNodeType() == FSCSEditorTreeNode::ComponentNode && Cast(NodePtr->GetComponentTemplate()); + }); + + if (indexOfFirstSceneComponent == -1 && SceneComponentSeparatorNodePtr.IsValid()) + { + Super::RemoveChild(SceneComponentSeparatorNodePtr); + SceneComponentSeparatorNodePtr = nullptr; + } + + int32 indexOffFirstNonSceneComponent = ComponentNodes.IndexOfByPredicate([](const FSCSEditorTreeNodePtrType& NodePtr) + { + return NodePtr->GetNodeType() == FSCSEditorTreeNode::ComponentNode && !Cast(NodePtr->GetComponentTemplate()); + }); + + if (indexOffFirstNonSceneComponent == -1 && NonSceneComponentSeparatorNodePtr.IsValid()) + { + Super::RemoveChild(NonSceneComponentSeparatorNodePtr); + NonSceneComponentSeparatorNodePtr = nullptr; + } +} FName FSCSEditorTreeNodeRootActor::GetNodeID() const { @@ -2088,7 +2151,7 @@ void SSCS_RowWidget::HandleOnDragEnter( const FDragDropEvent& DragDropEvent ) if (Message.IsEmpty()) { - FSCSEditorTreeNodePtrType SceneRootNodePtr = SCSEditor.Pin()->SceneRootNodePtr; + FSCSEditorTreeNodePtrType SceneRootNodePtr = SCSEditor.Pin()->GetSceneRootNode(); check(SceneRootNodePtr.IsValid()); FSCSEditorTreeNodePtrType NodePtr = GetNode(); @@ -2690,11 +2753,12 @@ void SSCS_RowWidget::OnDetachFromDropAction(const TArraySceneRootNodePtr.IsValid()); - SCSEditorPtr->SceneRootNodePtr->AddChild(DroppedNodePtr); + FSCSEditorTreeNodePtrType SceneRootNodePtr = SCSEditorPtr->GetSceneRootNode(); + check(SceneRootNodePtr.IsValid()); + SceneRootNodePtr->AddChild(DroppedNodePtr); // Attempt to locate a matching instance of the scene root component template in the Actor context that's being edited - USceneComponent* InstancedSceneRootComponent = Cast(SCSEditorPtr->SceneRootNodePtr->FindComponentInstanceInActor(PreviewActor)); + USceneComponent* InstancedSceneRootComponent = Cast(SceneRootNodePtr->FindComponentInstanceInActor(PreviewActor)); if(SceneComponentTemplate && InstancedSceneRootComponent && InstancedSceneRootComponent->IsRegistered()) { // If we find a match, calculate its new position relative to the scene root component instance in the preview scene @@ -2751,8 +2815,9 @@ void SSCS_RowWidget::OnDetachFromDropAction(const TArrayRemoveChild(DroppedNodePtr); // Attach the dropped node to the current scene root node - check(SCSEditorPtr->SceneRootNodePtr.IsValid()); - SCSEditorPtr->SceneRootNodePtr->AddChild(DroppedNodePtr); + FSCSEditorTreeNodePtrType SceneRootNodePtr = SCSEditorPtr->GetSceneRootNode(); + check(SceneRootNodePtr.IsValid()); + SceneRootNodePtr->AddChild(DroppedNodePtr); } } @@ -2765,7 +2830,7 @@ void SSCS_RowWidget::OnMakeNewRootDropAction(FSCSEditorTreeNodePtrType DroppedNo check(SCSEditorPtr.IsValid()); // Get the current scene root node - FSCSEditorTreeNodePtrType& SceneRootNodePtr = SCSEditorPtr->SceneRootNodePtr; + FSCSEditorTreeNodePtrType SceneRootNodePtr = SCSEditorPtr->GetSceneRootNode(); FSCSEditorTreeNodePtrType NodePtr = GetNode(); @@ -2876,6 +2941,7 @@ void SSCS_RowWidget::OnMakeNewRootDropAction(FSCSEditorTreeNodePtrType DroppedNo // Set node we are dropping as new root SceneRootNodePtr = DroppedNodePtr; + SCSEditorPtr->SetSceneRootNode(SceneRootNodePtr); // Add dropped node to the SCS context Blueprint->SimpleConstructionScript->AddNode(SceneRootNodePtr->GetSCSNode()); @@ -2910,6 +2976,7 @@ void SSCS_RowWidget::OnMakeNewRootDropAction(FSCSEditorTreeNodePtrType DroppedNo // Set node we are dropping as new root SceneRootNodePtr = DroppedNodePtr; + SCSEditorPtr->SetSceneRootNode(SceneRootNodePtr); // Remove or re-parent the old root if (OldSceneRootNodePtr.IsValid()) @@ -3020,7 +3087,7 @@ FString SSCS_RowWidget::GetDocumentationLink() const check(SCSEditor.IsValid()); FSCSEditorTreeNodePtrType NodePtr = GetNode(); - if ((NodePtr == SCSEditor.Pin()->SceneRootNodePtr) || NodePtr->IsInherited()) + if ((NodePtr == SCSEditor.Pin()->GetSceneRootNode()) || NodePtr->IsInherited()) { return TEXT("Shared/Editors/BlueprintEditor/ComponentsMode"); } @@ -3033,7 +3100,7 @@ FString SSCS_RowWidget::GetDocumentationExcerptName() const check(SCSEditor.IsValid()); FSCSEditorTreeNodePtrType NodePtr = GetNode(); - if (NodePtr == SCSEditor.Pin()->SceneRootNodePtr) + if (NodePtr == SCSEditor.Pin()->GetSceneRootNode()) { return TEXT("RootComponent"); } @@ -3071,8 +3138,15 @@ bool SSCS_RowWidget::OnNameTextVerifyChanged(const FText& InNewText, FText& OutE FSCSEditorTreeNodePtrType NodePtr = GetNode(); UBlueprint* Blueprint = GetBlueprint(); - if (!InNewText.IsEmpty()) + const FString& NewTextStr = InNewText.ToString(); + + if (!NewTextStr.IsEmpty()) { + if (NodePtr->GetVariableName().ToString() == NewTextStr) + { + return true; + } + const UActorComponent* ComponentInstance = NodePtr->GetComponentTemplate(); if (ensure(ComponentInstance)) { @@ -3082,20 +3156,20 @@ bool SSCS_RowWidget::OnNameTextVerifyChanged(const FText& InNewText, FText& OutE ExistingNameSearchScope = Cast(Blueprint->GeneratedClass->GetDefaultObject()); } - if (!FComponentEditorUtils::IsValidVariableNameString(ComponentInstance, InNewText.ToString())) + if (!FComponentEditorUtils::IsValidVariableNameString(ComponentInstance, NewTextStr)) { OutErrorMessage = LOCTEXT("RenameFailed_EngineReservedName", "This name is reserved for engine use."); return false; } - else if (InNewText.ToString().Len() > NAME_SIZE) + else if (NewTextStr.Len() > NAME_SIZE) { FFormatNamedArguments Arguments; Arguments.Add(TEXT("CharCount"), NAME_SIZE); OutErrorMessage = FText::Format(LOCTEXT("ComponentRenameFailed_TooLong", "Component name must be less than {CharCount} characters long."), Arguments); return false; } - else if (!FComponentEditorUtils::IsComponentNameAvailable(InNewText.ToString(), ExistingNameSearchScope, ComponentInstance) - || !FComponentEditorUtils::IsComponentNameAvailable(InNewText.ToString(), ComponentInstance->GetOuter(), ComponentInstance )) + else if (!FComponentEditorUtils::IsComponentNameAvailable(NewTextStr, ExistingNameSearchScope, ComponentInstance) + || !FComponentEditorUtils::IsComponentNameAvailable(NewTextStr, ComponentInstance->GetOuter(), ComponentInstance )) { OutErrorMessage = LOCTEXT("RenameFailed_ExistingName", "Another component already has the same name."); return false; @@ -3118,7 +3192,7 @@ bool SSCS_RowWidget::OnNameTextVerifyChanged(const FText& InNewText, FText& OutE NameValidator = MakeShareable(new FStringSetNameValidator(NodePtr->GetComponentTemplate()->GetName())); } - EValidatorResult ValidatorResult = NameValidator->IsValid(InNewText.ToString()); + EValidatorResult ValidatorResult = NameValidator->IsValid(NewTextStr); if (ValidatorResult == EValidatorResult::AlreadyInUse) { OutErrorMessage = FText::Format(LOCTEXT("RenameFailed_InUse", "{0} is in use by another variable or function!"), InNewText); @@ -3369,9 +3443,10 @@ FText SSCS_RowWidget_ActorRoot::GetActorMobilityText() const { USceneComponent* RootComponent = DefaultActor->GetRootComponent(); - if ((RootComponent == nullptr) && (SCSEditorPtr->SceneRootNodePtr.IsValid())) + FSCSEditorTreeNodePtrType SceneRootNodePtr = SCSEditorPtr->GetSceneRootNode(); + if ((RootComponent == nullptr) && SceneRootNodePtr.IsValid()) { - RootComponent = Cast(SCSEditorPtr->SceneRootNodePtr->GetComponentTemplate()); + RootComponent = Cast(SceneRootNodePtr->GetComponentTemplate()); } if (RootComponent != nullptr) @@ -3428,7 +3503,6 @@ void SSCSEditor::Construct( const FArguments& InArgs ) OnItemDoubleClicked = InArgs._OnItemDoubleClicked; OnHighlightPropertyInDetailsView = InArgs._OnHighlightPropertyInDetailsView; bUpdatingSelection = false; - bHasAddedSceneAndBehaviorComponentSeparator = false; bAllowTreeUpdates = true; bIsDiffing = InArgs._IsDiffing; @@ -3474,7 +3548,7 @@ void SSCSEditor::Construct( const FArguments& InArgs ) SCSTreeWidget = SNew(SSCSTreeType) .ToolTipText(LOCTEXT("DropAssetToAddComponent", "Drop asset here to add a component.")) .SCSEditor(this) - .TreeItemsSource(&FilteredRootNodes) + .TreeItemsSource(&RootNodes) .SelectionMode(ESelectionMode::Multi) .OnGenerateRow(this, &SSCSEditor::MakeTableRowWidget) .OnGetChildren(this, &SSCSEditor::OnGetChildrenForTree) @@ -3753,7 +3827,10 @@ void SSCSEditor::OnLevelComponentRequestRename(const UActorComponent* InComponen void SSCSEditor::OnObjectsReplaced(const TMap& OldToNewInstanceMap) { - ReplaceComponentReferencesInTree(GetRootComponentNodes(), OldToNewInstanceMap); + if (GetActorNode().IsValid()) + { + ReplaceComponentReferencesInTree(GetActorNode()->GetComponentNodes(), OldToNewInstanceMap); + } } void SSCSEditor::ReplaceComponentReferencesInTree(const TArray& Nodes, const TMap& OldToNewInstanceMap) @@ -4124,7 +4201,7 @@ void SSCSEditor::OnDuplicateComponent() { // If we're duplicating the root then we're already a child of it so need to reparent, but we do need to reset the scale // otherwise we'll end up with the square of the root's scale instead of being the same size. - if (OriginalNodePtr == SceneRootNodePtr) + if (OriginalNodePtr == GetSceneRootNode()) { NewSceneComponent->RelativeScale3D = FVector(1.f); } @@ -4145,7 +4222,7 @@ void SSCSEditor::OnDuplicateComponent() } // Locate the duplicate node (as a child of the current scene root node), and switch it to be a child of the original node's parent - FSCSEditorTreeNodePtrType NewChildNodePtr = SceneRootNodePtr->FindChild(NewSceneComponent, true); + FSCSEditorTreeNodePtrType NewChildNodePtr = GetSceneRootNode()->FindChild(NewSceneComponent, true); if (NewChildNodePtr.IsValid()) { // Note: This method will handle removal from the scene root node as well @@ -4458,7 +4535,7 @@ void SSCSEditor::UpdateTree(bool bRegenerateTreeNodes) { // Obtain the set of expandable tree nodes that are currently collapsed TSet CollapsedTreeNodes; - GetCollapsedNodes(SceneRootNodePtr, CollapsedTreeNodes); + GetCollapsedNodes(GetSceneRootNode(), CollapsedTreeNodes); // Obtain the list of selected items TArray SelectedTreeNodes = SCSTreeWidget->GetSelectedItems(); @@ -4469,17 +4546,11 @@ void SSCSEditor::UpdateTree(bool bRegenerateTreeNodes) SCSTreeWidget->ClearSelection(); } RootNodes.Empty(); - RootComponentNodes.Empty(); - - bHasAddedSceneAndBehaviorComponentSeparator = false; - - // Reset the scene root node - SceneRootNodePtr.Reset(); TSharedPtr ActorTreeNode = MakeShareable(new FSCSEditorTreeNodeRootActor(GetActorContext(),EditorMode == EComponentEditorMode::ActorInstance)); - + RefreshFilteredState(ActorTreeNode, false); + SCSTreeWidget->SetItemExpansion(ActorTreeNode, true); RootNodes.Add(ActorTreeNode); - RootNodes.Add(MakeShareable(new FSCSEditorTreeNodeSeparator())); // Build the tree data source according to what mode we're in if (EditorMode == EComponentEditorMode::BlueprintSCS) @@ -4511,26 +4582,13 @@ void SSCSEditor::UpdateTree(bool bRegenerateTreeNodes) if(RootComponent != nullptr) { Components.Remove(RootComponent); - AddTreeNodeFromComponent(RootComponent); + AddTreeNodeFromComponent(RootComponent, FindOrCreateParentForExistingComponent(RootComponent, GetActorNode())); } for (UActorComponent* Component : Components) { - if (USceneComponent* SceneComp = Cast(Component)) - { // Add the rest of the native base class SceneComponent hierarchy - AddTreeNodeFromComponent(SceneComp); - } - else - { - // Add native ActorComponent nodes that aren't SceneComponents - if (!bHasAddedSceneAndBehaviorComponentSeparator) - { - bHasAddedSceneAndBehaviorComponentSeparator = true; - RootNodes.Add(MakeShareable(new FSCSEditorTreeNodeSeparator())); - } - AddRootComponentTreeNode(Component); - } + AddTreeNodeFromComponent(Component, FindOrCreateParentForExistingComponent(Component, GetActorNode())); } } @@ -4560,7 +4618,7 @@ void SSCSEditor::UpdateTree(bool bRegenerateTreeNodes) } else { - NewNodePtr = AddTreeNode(SCS_Node, SceneRootNodePtr, StackIndex > 0); + NewNodePtr = AddTreeNode(SCS_Node, ActorTreeNode, StackIndex > 0); } // Only necessary to do the following for inherited nodes (StackIndex > 0). @@ -4568,12 +4626,12 @@ void SSCSEditor::UpdateTree(bool bRegenerateTreeNodes) { // This call creates ICH override templates for the current Blueprint. Without this, the parent node // search above can fail when attempting to match an inherited node in the tree via component template. - NewNodePtr->GetEditableComponentTemplate(ParentBPStack[0]); + NewNodePtr->GetOrCreateEditableComponentTemplate(ParentBPStack[0]); for (FSCSEditorTreeNodePtrType ChildNodePtr : NewNodePtr->GetChildren()) { if (ensure(ChildNodePtr.IsValid())) { - ChildNodePtr->GetEditableComponentTemplate(ParentBPStack[0]); + ChildNodePtr->GetOrCreateEditableComponentTemplate(ParentBPStack[0]); } } } @@ -4591,15 +4649,7 @@ void SSCSEditor::UpdateTree(bool bRegenerateTreeNodes) { if(Component->CreationMethod == EComponentCreationMethod::UserConstructionScript) { - USceneComponent* SceneComponent = Cast(Component); - if(SceneComponent != nullptr) - { - AddTreeNodeFromComponent(SceneComponent); - } - else - { - AddRootComponentTreeNode(Component); - } + AddTreeNodeFromComponent(Component, FindOrCreateParentForExistingComponent(Component, GetActorNode())); } } } @@ -4612,6 +4662,47 @@ void SSCSEditor::UpdateTree(bool bRegenerateTreeNodes) // Get the full set of instanced components TSet ComponentsToAdd(ActorInstance->GetComponents()); + const bool bHideConstructionScriptComponentsInDetailsView = GetDefault()->bHideConstructionScriptComponentsInDetailsView; + auto ShouldAddInstancedActorComponent = [bHideConstructionScriptComponentsInDetailsView](UActorComponent* ActorComp, USceneComponent* ParentSceneComp) + { + // Exclude nested DSOs attached to BP-constructed instances, which are not mutable. + return (ActorComp != nullptr + && (!ActorComp->IsVisualizationComponent()) + && (ActorComp->CreationMethod != EComponentCreationMethod::UserConstructionScript || !bHideConstructionScriptComponentsInDetailsView) + && (ParentSceneComp == nullptr || !ParentSceneComp->IsCreatedByConstructionScript() || !ActorComp->HasAnyFlags(RF_DefaultSubObject))) + && (ActorComp->CreationMethod != EComponentCreationMethod::Native || FComponentEditorUtils::CanEditNativeComponent(ActorComp)); + }; + + for (auto It(ComponentsToAdd.CreateIterator()); It; ++It) + { + UActorComponent* ActorComp = *It; + USceneComponent* SceneComp = Cast(ActorComp); + USceneComponent* ParentSceneComp = SceneComp != nullptr ? SceneComp->GetAttachParent() : nullptr; + if (!ShouldAddInstancedActorComponent(ActorComp, ParentSceneComp)) + { + It.RemoveCurrent(); + } + } + + TFunction AddInstancedTreeNodesRecursive = [&](USceneComponent* Component, FSCSEditorTreeNodePtrType TreeNode) + { + if (Component != nullptr) + { + TArray Components = Component->GetAttachChildren(); + for (USceneComponent* ChildComponent : Components) + { + if (ComponentsToAdd.Contains(ChildComponent) + && ChildComponent->GetOwner() == Component->GetOwner()) + { + ComponentsToAdd.Remove(ChildComponent); + + FSCSEditorTreeNodePtrType NewParentNode = AddTreeNodeFromComponent(ChildComponent, TreeNode); + AddInstancedTreeNodesRecursive(ChildComponent, NewParentNode); + } + } + } + }; + // Add the root component first (it may not be the first one) USceneComponent* RootComponent = ActorInstance->GetRootComponent(); if(RootComponent != nullptr) @@ -4620,8 +4711,8 @@ void SSCSEditor::UpdateTree(bool bRegenerateTreeNodes) // Recursively add any instanced children that are already attached through the root, and keep track of added // instances. This will be a faster path than the loop below, because we create new parent tree nodes as we go. - FSCSEditorTreeNodePtrType NewParentNode = AddTreeNodeFromComponent(RootComponent); - AddInstancedTreeNodesRecursive(RootComponent, NewParentNode, ComponentsToAdd); + FSCSEditorTreeNodePtrType NewParentNode = AddTreeNodeFromComponent(RootComponent, FindOrCreateParentForExistingComponent(RootComponent, GetActorNode())); + AddInstancedTreeNodesRecursive(RootComponent, NewParentNode); } // Sort components by type (always put scene components first in the tree) @@ -4634,24 +4725,7 @@ void SSCSEditor::UpdateTree(bool bRegenerateTreeNodes) // unattached scene components followed by any instanced non-scene components owned by the Actor instance. for (UActorComponent* ActorComp : ComponentsToAdd) { - USceneComponent* SceneComp = Cast(ActorComp); - USceneComponent* ParentSceneComp = SceneComp != nullptr ? SceneComp->GetAttachParent() : nullptr; - if (ShouldAddInstancedActorComponent(ActorComp, ParentSceneComp)) - { - if (SceneComp != nullptr) - { - AddTreeNodeFromComponent(SceneComp); - } - else - { - if (!bHasAddedSceneAndBehaviorComponentSeparator) - { - bHasAddedSceneAndBehaviorComponentSeparator = true; - RootNodes.Add(MakeShareable(new FSCSEditorTreeNode(FSCSEditorTreeNode::SeparatorNode))); - } - AddRootComponentTreeNode(ActorComp); - } - } + AddTreeNodeFromComponent(ActorComp, FindOrCreateParentForExistingComponent(ActorComp, GetActorNode())); } } } @@ -4706,44 +4780,12 @@ void SSCSEditor::UpdateTree(bool bRegenerateTreeNodes) SCSTreeWidget->RequestScrollIntoView(NodeToRenamePtr); } } - - RebuildFilteredRootList(); } // refresh widget SCSTreeWidget->RequestTreeRefresh(); } -void SSCSEditor::AddInstancedTreeNodesRecursive(USceneComponent* Component, FSCSEditorTreeNodePtrType TreeNode, TSet& ComponentsToAdd) -{ - if (Component != nullptr) - { - TArray Components = Component->GetAttachChildren(); - for (USceneComponent* ChildComponent : Components) - { - if (ComponentsToAdd.Contains(ChildComponent) - && ShouldAddInstancedActorComponent(ChildComponent, Component) - && ChildComponent->GetOwner() == Component->GetOwner()) - { - ComponentsToAdd.Remove(ChildComponent); - - FSCSEditorTreeNodePtrType NewParentNode = AddTreeNodeFromComponent(ChildComponent, TreeNode); - AddInstancedTreeNodesRecursive(ChildComponent, NewParentNode, ComponentsToAdd); - } - } - } -} - -bool SSCSEditor::ShouldAddInstancedActorComponent(UActorComponent* ActorComp, USceneComponent* ParentSceneComp) const -{ - // Exclude nested DSOs attached to BP-constructed instances, which are not mutable. - return (ActorComp != nullptr - && (!ActorComp->IsVisualizationComponent()) - && (ActorComp->CreationMethod != EComponentCreationMethod::UserConstructionScript || !GetDefault()->bHideConstructionScriptComponentsInDetailsView) - && (ParentSceneComp == nullptr || !ParentSceneComp->IsCreatedByConstructionScript() || !ActorComp->HasAnyFlags(RF_DefaultSubObject))) - && (ActorComp->CreationMethod != EComponentCreationMethod::Native || FComponentEditorUtils::CanEditNativeComponent(ActorComp) ); -} - void SSCSEditor::DumpTree() { /* Example: @@ -4912,29 +4954,30 @@ const TArray& SSCSEditor::GetRootNodes() const return RootNodes; } -TSharedPtr SSCSEditor::AddRootComponentTreeNode(UActorComponent* ActorComp) +FSCSEditorActorNodePtrType SSCSEditor::GetActorNode() const { - TSharedPtr NewTreeNode; - if (RootTreeNode.IsValid()) + if (RootNodes.Num() > 0) { - NewTreeNode = RootTreeNode->AddChildFromComponent(ActorComp); - RefreshFilteredState(NewTreeNode, /*bRecursive =*/false); + return StaticCastSharedPtr(RootNodes[0]); } - else - { - NewTreeNode = FSCSEditorTreeNode::FactoryNodeFromComponent(ActorComp); - RootNodes.Add(NewTreeNode); - bool bIsFilteredOut = RefreshFilteredState(NewTreeNode, /*bRecursive =*/false); - if (!bIsFilteredOut) + return FSCSEditorActorNodePtrType(); +} + +FSCSEditorTreeNodePtrType SSCSEditor::GetSceneRootNode() const + { + FSCSEditorActorNodePtrType ActorNode = GetActorNode(); + if (ActorNode.IsValid()) { - FilteredRootNodes.Add(NewTreeNode); + return ActorNode->GetSceneRootNode(); } + + return FSCSEditorTreeNodePtrType(); } - RootComponentNodes.Add(NewTreeNode); - - return NewTreeNode; +void SSCSEditor::SetSceneRootNode(FSCSEditorTreeNodePtrType NewSceneRootNode) +{ + GetActorNode()->SetSceneRootNode(NewSceneRootNode); } class FComponentClassParentFilter : public IClassViewerFilter @@ -5035,36 +5078,6 @@ UClass* SSCSEditor::CreateNewBPComponent(TSubclassOf ComponentC return NewClass; } -void SSCSEditor::RebuildFilteredRootList() -{ - FilteredRootNodes.Empty(RootNodes.Num()); - - FSCSEditorTreeNodePtrType PendingSeparator; - for (const FSCSEditorTreeNodePtrType& Node : RootNodes) - { - switch (Node->GetNodeType()) - { - case FSCSEditorTreeNode::ENodeType::ComponentNode: - if (Node->IsFlaggedForFiltration()) - { - break; - } - case FSCSEditorTreeNode::ENodeType::RootActorNode: - if (PendingSeparator.IsValid()) - { - FilteredRootNodes.Add(PendingSeparator); - PendingSeparator.Reset(); - } - FilteredRootNodes.Add(Node); - break; - - case FSCSEditorTreeNode::ENodeType::SeparatorNode: - PendingSeparator = Node; - break; - } - } -} - void SSCSEditor::ClearSelection() { if ( bUpdatingSelection == false ) @@ -5205,7 +5218,9 @@ UActorComponent* SSCSEditor::AddNewComponent( UClass* NewComponentClass, UObject if (ComponentTemplate) { // Create a duplicate of the provided template - NewComponent = AddNewNodeForInstancedComponent(MoveTemp(AddTransaction), FComponentEditorUtils::DuplicateComponent(ComponentTemplate), nullptr, bSetFocusToNewItem); + UActorComponent* NewInstanceComponent = FComponentEditorUtils::DuplicateComponent(ComponentTemplate); + FSCSEditorTreeNodePtrType ParentNodePtr = FindParentForNewComponent(NewInstanceComponent); + NewComponent = AddNewNodeForInstancedComponent(MoveTemp(AddTransaction), NewInstanceComponent, ParentNodePtr, nullptr, bSetFocusToNewItem); } else if (AActor* ActorInstance = GetActorContext()) { @@ -5229,16 +5244,24 @@ UActorComponent* SSCSEditor::AddNewComponent( UClass* NewComponentClass, UObject // Construct the new component and attach as needed UActorComponent* NewInstanceComponent = NewObject(ActorInstance, NewComponentClass, NewComponentName, RF_Transactional); + FSCSEditorTreeNodePtrType ParentNodePtr = FindParentForNewComponent(NewInstanceComponent); + + // Do Scene Attachment if this new Comnponent is a USceneComponent if (USceneComponent* NewSceneComponent = Cast(NewInstanceComponent)) { - USceneComponent* RootComponent = ActorInstance->GetRootComponent(); - if (RootComponent) + if(ParentNodePtr->GetNodeType() == FSCSEditorTreeNode::RootActorNode) { - NewSceneComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + ActorInstance->SetRootComponent(NewSceneComponent); } else { - ActorInstance->SetRootComponent(NewSceneComponent); + USceneComponent* AttachTo = Cast(ParentNodePtr->GetComponentTemplate()); + if (AttachTo == nullptr) + { + AttachTo = ActorInstance->GetRootComponent(); + } + check(AttachTo != nullptr); + NewSceneComponent->AttachToComponent(AttachTo, FAttachmentTransformRules::KeepRelativeTransform); } } @@ -5267,13 +5290,125 @@ UActorComponent* SSCSEditor::AddNewComponent( UClass* NewComponentClass, UObject // Rerun construction scripts ActorInstance->RerunConstructionScripts(); - NewComponent = AddNewNodeForInstancedComponent(MoveTemp(AddTransaction), NewInstanceComponent, Asset, bSetFocusToNewItem); + NewComponent = AddNewNodeForInstancedComponent(MoveTemp(AddTransaction), NewInstanceComponent, ParentNodePtr, Asset, bSetFocusToNewItem); } } return NewComponent; } +FSCSEditorTreeNodePtrType SSCSEditor::FindOrCreateParentForExistingComponent(UActorComponent* InActorComponent, FSCSEditorActorNodePtrType ActorRootNode) +{ + check(InActorComponent != nullptr); + + USceneComponent* SceneComponent = Cast(InActorComponent); + if (SceneComponent == nullptr) + { + check(ActorRootNode.IsValid()); + check(ActorRootNode->GetNodeType() == FSCSEditorTreeNode::RootActorNode); + return ActorRootNode; + } + + FSCSEditorTreeNodePtrType ParentNodePtr; + if (SceneComponent->GetAttachParent() != nullptr + && (EditorMode != EComponentEditorMode::ActorInstance || SceneComponent->GetAttachParent()->GetOwner() == GetActorContext())) + { + // Attempt to find the parent node in the current tree + ParentNodePtr = FindTreeNode(SceneComponent->GetAttachParent()); + if (!ParentNodePtr.IsValid()) +{ + // If the actual attach parent wasn't found, attempt to find its archetype. + // This handles the BP editor case where we might add UCS component nodes taken + // from the preview actor instance, which are not themselves template objects. + ParentNodePtr = FindTreeNode(Cast(SceneComponent->GetAttachParent()->GetArchetype())); + if (!ParentNodePtr.IsValid()) + { + // Recursively add the parent node to the tree if it does not exist yet + ParentNodePtr = AddTreeNodeFromComponent(SceneComponent->GetAttachParent(), FindOrCreateParentForExistingComponent(SceneComponent->GetAttachParent(), ActorRootNode)); + } + } + } + + if (!ParentNodePtr.IsValid()) + { + ParentNodePtr = ActorRootNode->GetSceneRootNode(); + } + + // Actor doesn't have a root component yet + if (!ParentNodePtr.IsValid()) + { + ParentNodePtr = ActorRootNode; + } + + return ParentNodePtr; +} + +FSCSEditorTreeNodePtrType SSCSEditor::FindParentForNewComponent(UActorComponent* NewComponent) const + { + // Find Parent to attach to (depending on the new Node type). + FSCSEditorTreeNodePtrType TargetParentNode; + TArray SelectedTreeNodes; + if (SCSTreeWidget.IsValid() && SCSTreeWidget->GetSelectedItems(SelectedTreeNodes)) + { + TargetParentNode = SelectedTreeNodes[0]; + } + + if (USceneComponent* NewSceneComponent = Cast(NewComponent)) + { + if (TargetParentNode.IsValid()) + { + if (TargetParentNode->GetNodeType() == FSCSEditorTreeNode::RootActorNode) + { + FSCSEditorActorNodePtrType TargetActorNode = StaticCastSharedPtr(TargetParentNode); + if (TargetActorNode.IsValid()) + { + TargetParentNode = TargetActorNode->GetSceneRootNode(); + USceneComponent* CastTargetToSceneComponent = Cast(TargetParentNode->GetComponentTemplate()); + if (CastTargetToSceneComponent == nullptr || !NewSceneComponent->CanAttachAsChild(CastTargetToSceneComponent, NAME_None)) + { + TargetParentNode = GetSceneRootNode(); // Default to SceneRoot + } + } + } + else if(TargetParentNode->GetNodeType() == FSCSEditorTreeNode::ComponentNode) + { + USceneComponent* CastTargetToSceneComponent = Cast(TargetParentNode->GetComponentTemplate()); + if (CastTargetToSceneComponent == nullptr || !NewSceneComponent->CanAttachAsChild(CastTargetToSceneComponent, NAME_None)) + { + TargetParentNode = GetSceneRootNode(); // Default to SceneRoot + } + } + } + else + { + TargetParentNode = GetSceneRootNode(); + } + } + else + { + if (TargetParentNode.IsValid()) + { + while (TargetParentNode->GetNodeType() == FSCSEditorTreeNode::ComponentNode) + { + TargetParentNode = TargetParentNode->GetParent(); + } + } + else + { + TargetParentNode = GetActorNode(); + } + + check(TargetParentNode->GetNodeType() == FSCSEditorTreeNode::RootActorNode); + } + + return TargetParentNode; + } + +FSCSEditorTreeNodePtrType SSCSEditor::FindParentForNewNode(USCS_Node* NewNode) const + { + return FindParentForNewComponent(NewNode->ComponentTemplate); +} + UActorComponent* SSCSEditor::AddNewNode(TUniquePtr InOngoingCreateTransaction, USCS_Node* NewNode, UObject* Asset, bool bMarkBlueprintModified, bool bSetFocusToNewItem) { check(NewNode != nullptr); @@ -5284,35 +5419,13 @@ UActorComponent* SSCSEditor::AddNewNode(TUniquePtr InOngoing } FSCSEditorTreeNodePtrType NewNodePtr; - + FSCSEditorTreeNodePtrType ParentNodePtr = FindParentForNewNode(NewNode); + UBlueprint* Blueprint = GetBlueprint(); check(Blueprint != nullptr && Blueprint->SimpleConstructionScript != nullptr); - bool AttachToSceneRootNode = true; - if (USceneComponent* NewSceneComponent = Cast(NewNode->ComponentTemplate)) - { - // get currently selected component - TArray SelectedTreeNodes; - if (SCSTreeWidget.IsValid() && SCSTreeWidget->GetSelectedItems(SelectedTreeNodes) > 0) - { - FSCSEditorTreeNodePtrType FirstTreeNode = SelectedTreeNodes[0]; - if (FirstTreeNode.IsValid() && FirstTreeNode->GetComponentTemplate()) - { - USceneComponent* CastFirstTreeNode = Cast(FirstTreeNode->GetComponentTemplate()); - if (CastFirstTreeNode && NewSceneComponent->CanAttachAsChild(CastFirstTreeNode, NAME_None)) - { - NewNodePtr = AddTreeNode(NewNode, FirstTreeNode, false); - AttachToSceneRootNode = false; - } - } - } - } - - if (AttachToSceneRootNode) - { // Add the new node to the editor tree - NewNodePtr = AddTreeNode(NewNode, SceneRootNodePtr, false); - } + NewNodePtr = AddTreeNode(NewNode, ParentNodePtr, /*bIsInheritedSCS=*/ false); // Potentially adjust variable names for any child blueprints const FName VariableName = NewNode->GetVariableName(); @@ -5341,37 +5454,14 @@ UActorComponent* SSCSEditor::AddNewNode(TUniquePtr InOngoing return NewNode->ComponentTemplate; } -UActorComponent* SSCSEditor::AddNewNodeForInstancedComponent(TUniquePtr InOngoingCreateTransaction, UActorComponent* NewInstanceComponent, UObject* Asset, bool bSetFocusToNewItem) +UActorComponent* SSCSEditor::AddNewNodeForInstancedComponent(TUniquePtr InOngoingCreateTransaction, UActorComponent* NewInstanceComponent, FSCSEditorTreeNodePtrType InParentNodePtr, UObject* Asset, bool bSetFocusToNewItem) { check(NewInstanceComponent != nullptr); FSCSEditorTreeNodePtrType NewNodePtr; // Add the new node to the editor tree - USceneComponent* NewSceneComponent = Cast(NewInstanceComponent); - if(NewSceneComponent != nullptr) - { - NewNodePtr = AddTreeNodeFromComponent(NewSceneComponent); - - // Remove the old scene root node if it's set to the default one - //if(SceneRootNodePtr.IsValid() && SceneRootNodePtr->IsDefaultSceneRoot()) - //{ - // RemoveComponentNode(SceneRootNodePtr); - // RootNodes.Remove( SceneRootNodePtr ); - // SceneRootNodePtr.Reset(); - //} - } - else - { - // Make sure we've added the separator between scene and behavior components - if (!bHasAddedSceneAndBehaviorComponentSeparator) - { - bHasAddedSceneAndBehaviorComponentSeparator = true; - RootNodes.Add(MakeShareable(new FSCSEditorTreeNode(FSCSEditorTreeNode::SeparatorNode))); - } - - NewNodePtr = AddRootComponentTreeNode(NewInstanceComponent); - } + NewNodePtr = AddTreeNodeFromComponent(NewInstanceComponent, InParentNodePtr); if(bSetFocusToNewItem) { @@ -5522,6 +5612,7 @@ bool SSCSEditor::CanPasteNodes() const return false; } + FSCSEditorTreeNodePtrType SceneRootNodePtr = GetSceneRootNode(); return SceneRootNodePtr.IsValid() && FComponentEditorUtils::CanPasteComponents(Cast(SceneRootNodePtr->GetComponentTemplate()), SceneRootNodePtr->IsDefaultSceneRoot(), true); } @@ -5882,124 +5973,73 @@ FSCSEditorTreeNodePtrType SSCSEditor::AddTreeNode(USCS_Node* InSCSNode, FSCSEdit { FSCSEditorTreeNodePtrType NewNodePtr; - check(InSCSNode != NULL); + check(InSCSNode != nullptr && InParentNodePtr.IsValid()); // During diffs, ComponentTemplates can easily be null, so prevent these checks. if (!bIsDiffing && InSCSNode->ComponentTemplate) { checkf(InSCSNode->ParentComponentOrVariableName == NAME_None - || (!InSCSNode->bIsParentComponentNative && InParentNodePtr->GetSCSNode() != NULL && InParentNodePtr->GetSCSNode()->GetVariableName() == InSCSNode->ParentComponentOrVariableName) - || (InSCSNode->bIsParentComponentNative && InParentNodePtr->GetComponentTemplate() != NULL && InParentNodePtr->GetComponentTemplate()->GetFName() == InSCSNode->ParentComponentOrVariableName), + || (!InSCSNode->bIsParentComponentNative && InParentNodePtr->GetSCSNode() != nullptr && InParentNodePtr->GetSCSNode()->GetVariableName() == InSCSNode->ParentComponentOrVariableName) + || (InSCSNode->bIsParentComponentNative && InParentNodePtr->GetComponentTemplate() != nullptr && InParentNodePtr->GetComponentTemplate()->GetFName() == InSCSNode->ParentComponentOrVariableName), TEXT("Failed to add SCS node %s to tree:\n- bIsParentComponentNative=%d\n- Stored ParentComponentOrVariableName=%s\n- Actual ParentComponentOrVariableName=%s"), *InSCSNode->GetVariableName().ToString(), !!InSCSNode->bIsParentComponentNative, *InSCSNode->ParentComponentOrVariableName.ToString(), !InSCSNode->bIsParentComponentNative - ? (InParentNodePtr->GetSCSNode() != NULL ? *InParentNodePtr->GetSCSNode()->GetVariableName().ToString() : TEXT("NULL")) - : (InParentNodePtr->GetComponentTemplate() != NULL ? *InParentNodePtr->GetComponentTemplate()->GetFName().ToString() : TEXT("NULL"))); + ? (InParentNodePtr->GetSCSNode() != nullptr ? *InParentNodePtr->GetSCSNode()->GetVariableName().ToString() : TEXT("NULL")) + : (InParentNodePtr->GetComponentTemplate() != nullptr ? *InParentNodePtr->GetComponentTemplate()->GetFName().ToString() : TEXT("NULL"))); } // Determine whether or not the given node is inherited from a parent Blueprint USimpleConstructionScript* NodeSCS = InSCSNode->GetSCS(); - if(InSCSNode->ComponentTemplate && InSCSNode->ComponentTemplate->IsA(USceneComponent::StaticClass())) - { - FSCSEditorTreeNodePtrType ParentPtr = InParentNodePtr.IsValid() ? InParentNodePtr : SceneRootNodePtr; - if(ParentPtr.IsValid()) - { // do this first, because we need a FSCSEditorTreeNodePtrType for the new node - NewNodePtr = ParentPtr->AddChild(InSCSNode, bIsInheritedSCS); + NewNodePtr = InParentNodePtr->AddChild(InSCSNode, bIsInheritedSCS); RefreshFilteredState(NewNodePtr, /*bRecursive =*/false); - bool bParentIsEditorOnly = ParentPtr->GetComponentTemplate()->IsEditorOnly(); + + if( InSCSNode->ComponentTemplate && + InSCSNode->ComponentTemplate->IsA(USceneComponent::StaticClass()) && + InParentNodePtr->GetNodeType() == FSCSEditorTreeNode::ComponentNode) + { + bool bParentIsEditorOnly = InParentNodePtr->GetComponentTemplate()->IsEditorOnly(); // if you can't nest this new node under the proposed parent (then swap the two) - if (bParentIsEditorOnly && !InSCSNode->ComponentTemplate->IsEditorOnly() && ParentPtr->CanReparent()) + if (bParentIsEditorOnly && !InSCSNode->ComponentTemplate->IsEditorOnly() && InParentNodePtr->CanReparent()) { - FSCSEditorTreeNodePtrType OldParentPtr = ParentPtr; - ParentPtr = OldParentPtr->GetParent(); + FSCSEditorTreeNodePtrType OldParentPtr = InParentNodePtr; + InParentNodePtr = OldParentPtr->GetParent(); OldParentPtr->RemoveChild(NewNodePtr); NodeSCS->RemoveNode(OldParentPtr->GetSCSNode()); // if the grandparent node is invalid (assuming this means that the parent node was the scene-root) - if (!ParentPtr.IsValid()) + if (!InParentNodePtr.IsValid()) { - check(OldParentPtr == SceneRootNodePtr); - SceneRootNodePtr = NewNodePtr; - NodeSCS->AddNode(SceneRootNodePtr->GetSCSNode()); + check(OldParentPtr == GetSceneRootNode()); + SetSceneRootNode(NewNodePtr); + NodeSCS->AddNode(NewNodePtr->GetSCSNode()); } else { - ParentPtr->AddChild(NewNodePtr); + InParentNodePtr->AddChild(NewNodePtr); } // move the proposed parent in as a child to the new node NewNodePtr->AddChild(OldParentPtr); } // if bParentIsEditorOnly... - - // Expand parent nodes by default - SCSTreeWidget->SetItemExpansion(ParentPtr, true); } - //else, if !SceneRootNodePtr.IsValid(), make it the scene root node if it has not been set yet else { - // Create a new root node - if (RootTreeNode.IsValid()) - { - NewNodePtr = RootTreeNode->AddChild(InSCSNode, bIsInheritedSCS); - } - else - { - NewNodePtr = MakeShareable(new FSCSEditorTreeNodeComponent(InSCSNode, bIsInheritedSCS)); - RootNodes.Add(NewNodePtr); - - bool bIsFilteredOut = RefreshFilteredState(NewNodePtr, /*bRecursive =*/false); - if (!bIsFilteredOut) - { - FilteredRootNodes.Add(NewNodePtr); - } - } - - NodeSCS->AddNode(InSCSNode); - - // Add it to the root set - RootComponentNodes.Insert(NewNodePtr, 0); - - // Make it the scene root node - SceneRootNodePtr = NewNodePtr; - - // Expand the scene root node by default - SCSTreeWidget->SetItemExpansion(SceneRootNodePtr, true); - } - } - else - { - // If the given SCS node does not contain a scene component template, we create a new root node - if (RootTreeNode.IsValid()) - { - NewNodePtr = RootTreeNode->AddChild(InSCSNode, bIsInheritedSCS); - } - else - { - NewNodePtr = MakeShareable(new FSCSEditorTreeNodeComponent(InSCSNode, bIsInheritedSCS)); - RootNodes.Add(NewNodePtr); - - bool bIsFilteredOut = RefreshFilteredState(NewNodePtr, /*bRecursive =*/false); - if (!bIsFilteredOut) - { - FilteredRootNodes.Add(NewNodePtr); - } - } - - RootComponentNodes.Add(NewNodePtr); - // If the SCS root node array does not already contain the given node, this will add it (this should only occur after node creation) - if(NodeSCS != NULL) - { + if(NodeSCS != nullptr) + { NodeSCS->AddNode(InSCSNode); } } + // Expand parent nodes by default + SCSTreeWidget->SetItemExpansion(InParentNodePtr, true); + // Recursively add the given SCS node's child nodes for (USCS_Node* ChildNode : InSCSNode->GetChildNodes()) { @@ -6009,68 +6049,20 @@ FSCSEditorTreeNodePtrType SSCSEditor::AddTreeNode(USCS_Node* InSCSNode, FSCSEdit return NewNodePtr; } -FSCSEditorTreeNodePtrType SSCSEditor::AddTreeNodeFromComponent(USceneComponent* InSceneComponent, FSCSEditorTreeNodePtrType InParentTreeNode) +FSCSEditorTreeNodePtrType SSCSEditor::AddTreeNodeFromComponent(UActorComponent* InActorComponent, FSCSEditorTreeNodePtrType InParentTreeNode) { - FSCSEditorTreeNodePtrType NewNodePtr; + check(InActorComponent != NULL); + ensure(!InActorComponent->IsPendingKill()); - check(InSceneComponent != NULL); - ensure(!InSceneComponent->IsPendingKill()); - - // If the given component has a parent, and if we're not in "instance" mode OR the owner of the parent matches the Actor instance we're editing - if(InSceneComponent->GetAttachParent() != NULL - && (EditorMode != EComponentEditorMode::ActorInstance || InSceneComponent->GetAttachParent()->GetOwner() == GetActorContext())) - { - // Attempt to find the parent node in the current tree - FSCSEditorTreeNodePtrType ParentNodePtr; - if (InParentTreeNode.IsValid()) - { - ParentNodePtr = InParentTreeNode; - } - else - { - ParentNodePtr = FindTreeNode(InSceneComponent->GetAttachParent()); - if(!ParentNodePtr.IsValid()) + FSCSEditorTreeNodePtrType NewNodePtr = InParentTreeNode->FindChild(InActorComponent); + if (!NewNodePtr.IsValid()) { - // If the actual attach parent wasn't found, attempt to find its archetype. - // This handles the BP editor case where we might add UCS component nodes taken - // from the preview actor instance, which are not themselves template objects. - ParentNodePtr = FindTreeNode(Cast(InSceneComponent->GetAttachParent()->GetArchetype())); - if(!ParentNodePtr.IsValid()) - { - // Recursively add the parent node to the tree if it does not exist yet - ParentNodePtr = AddTreeNodeFromComponent(InSceneComponent->GetAttachParent()); + NewNodePtr = FSCSEditorTreeNode::FactoryNodeFromComponent(InActorComponent); + InParentTreeNode->AddChild(NewNodePtr); + RefreshFilteredState(NewNodePtr, false); } - } - } - // Add a new tree node for the given scene component - check(ParentNodePtr.IsValid()); - NewNodePtr = ParentNodePtr->AddChildFromComponent(InSceneComponent); - RefreshFilteredState(NewNodePtr, /*bRecursive =*/false); - - // Expand parent nodes by default - SCSTreeWidget->SetItemExpansion(ParentNodePtr, true); - } - else - { - // Make it the scene root node if it has not been set yet - if(!SceneRootNodePtr.IsValid()) - { - // Create a new root node - NewNodePtr = AddRootComponentTreeNode(InSceneComponent); - - // Make it the scene root node - SceneRootNodePtr = NewNodePtr; - - // Expand the scene root node by default - SCSTreeWidget->SetItemExpansion(SceneRootNodePtr, true); - } - else if (SceneRootNodePtr->GetComponentTemplate() != InSceneComponent) - { - NewNodePtr = SceneRootNodePtr->AddChildFromComponent(InSceneComponent); - RefreshFilteredState(NewNodePtr, /*bRecursive =*/false); - } - } + SCSTreeWidget->SetItemExpansion(NewNodePtr, true); return NewNodePtr; } @@ -6083,7 +6075,7 @@ FSCSEditorTreeNodePtrType SSCSEditor::FindTreeNode(const USCS_Node* InSCSNode, F // Start at the scene root node if none was given if(!InStartNodePtr.IsValid()) { - InStartNodePtr = SceneRootNodePtr; + InStartNodePtr = GetSceneRootNode(); } if(InStartNodePtr.IsValid()) @@ -6119,17 +6111,17 @@ FSCSEditorTreeNodePtrType SSCSEditor::FindTreeNode(const UActorComponent* InComp // Start at the scene root node if none was given if(!InStartNodePtr.IsValid()) { - InStartNodePtr = SceneRootNodePtr; + InStartNodePtr = GetActorNode(); } if(InStartNodePtr.IsValid()) { // Check to see if the given component template matches the given tree node // - // For certain node types, GetEditableComponentTemplate() will handle retrieving + // For certain node types, GetOrCreateEditableComponentTemplate() will handle retrieving // the "OverridenComponentTemplate" which may be what we're looking for in some // cases; if not, then we fall back to just checking GetComponentTemplate() - if (InStartNodePtr->GetEditableComponentTemplate(GetBlueprint()) == InComponent) + if (InStartNodePtr->GetOrCreateEditableComponentTemplate(GetBlueprint()) == InComponent) { NodePtr = InStartNodePtr; } @@ -6160,10 +6152,10 @@ FSCSEditorTreeNodePtrType SSCSEditor::FindTreeNode(const FName& InVariableOrInst FSCSEditorTreeNodePtrType NodePtr; if(InVariableOrInstanceName != NAME_None) { - // Start at the scene root node if none was given + // Start at the root node if none was given if(!InStartNodePtr.IsValid()) { - InStartNodePtr = SceneRootNodePtr; + InStartNodePtr = GetActorNode(); } if(InStartNodePtr.IsValid()) @@ -6316,7 +6308,7 @@ FText SSCSEditor::OnGetApplyChangesToBlueprintTooltip() const AActor* BlueprintCDO = Actor->GetClass()->GetDefaultObject(); if(BlueprintCDO != NULL) { - const EditorUtilities::ECopyOptions::Type CopyOptions = (EditorUtilities::ECopyOptions::Type)(EditorUtilities::ECopyOptions::PreviewOnly|EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties); + const EditorUtilities::ECopyOptions::Type CopyOptions = (EditorUtilities::ECopyOptions::Type)(EditorUtilities::ECopyOptions::PreviewOnly|EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties|EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties); NumChangedProperties += EditorUtilities::CopyActorProperties(Actor, BlueprintCDO, CopyOptions); } NumChangedProperties += Actor->GetInstanceComponents().Num(); @@ -6495,7 +6487,7 @@ void SSCSEditor::OnApplyChangesToBlueprint() const AActor* BlueprintCDO = Actor->GetClass()->GetDefaultObject(); if (BlueprintCDO != NULL) { - const EditorUtilities::ECopyOptions::Type CopyOptions = (EditorUtilities::ECopyOptions::Type)(EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances); + const EditorUtilities::ECopyOptions::Type CopyOptions = (EditorUtilities::ECopyOptions::Type)(EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances | EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties); NumChangedProperties = EditorUtilities::CopyActorProperties(Actor, BlueprintCDO, CopyOptions); if (Actor->GetInstanceComponents().Num() > 0) { @@ -6628,11 +6620,6 @@ FReply SSCSEditor::OnPromoteToBlueprintClicked() return FReply::Handled(); } -const TArray& SSCSEditor::GetRootComponentNodes() -{ - return RootComponentNodes; -} - /** Returns the Actor context for which we are viewing/editing the SCS. Can return null. Should not be cached as it may change from frame to frame. */ AActor* SSCSEditor::GetActorContext() const { @@ -6689,25 +6676,21 @@ void SSCSEditor::OnFilterTextChanged(const FText& InFilterText) bool bRootItemFilteredBackIn = false; // iterate backwards so we select from the top down - for (int32 ComponentIndex = RootComponentNodes.Num() - 1; ComponentIndex >= 0; --ComponentIndex) + for (int32 ComponentIndex = RootNodes.Num() - 1; ComponentIndex >= 0; --ComponentIndex) { - FSCSEditorTreeNodePtrType Component = RootComponentNodes[ComponentIndex]; + FSCSEditorTreeNodePtrType Node = RootNodes[ComponentIndex]; - const bool bWasFilteredOut = Component->IsFlaggedForFiltration(); - bool bFilteredOut = RefreshFilteredState(Component, /*bRecursive =*/true); + const bool bWasFilteredOut = Node->IsFlaggedForFiltration(); + bool bFilteredOut = RefreshFilteredState(Node, true); if (!bFilteredOut) { if (!bIsFilterBlank) { - NewSelection = OnFilterTextChanged_Inner::ExpandToFilteredChildren(this, Component); + NewSelection = OnFilterTextChanged_Inner::ExpandToFilteredChildren(this, Node); } bRootItemFilteredBackIn |= bWasFilteredOut; } - else - { - FilteredRootNodes.Remove(Component); - } } if (NewSelection.IsValid() && !SCSTreeWidget->IsItemSelected(NewSelection)) @@ -6715,10 +6698,6 @@ void SSCSEditor::OnFilterTextChanged(const FText& InFilterText) SelectNode(NewSelection, /*IsCntrlDown =*/false); } - if (bRootItemFilteredBackIn) - { - RebuildFilteredRootList(); - } UpdateTree(/*bRegenerateTreeNodes =*/false); } diff --git a/Engine/Source/Editor/Kismet/Private/STimelineEditor.cpp b/Engine/Source/Editor/Kismet/Private/STimelineEditor.cpp index 4dc674915e9e..9e54e0eeef72 100644 --- a/Engine/Source/Editor/Kismet/Private/STimelineEditor.cpp +++ b/Engine/Source/Editor/Kismet/Private/STimelineEditor.cpp @@ -1657,7 +1657,7 @@ TSharedPtr< SWidget > STimelineEditor::MakeContextMenu() const { TSharedRef SizeSlider = SNew(SSlider) .Value(this, &STimelineEditor::GetSizeScaleValue) - .OnValueChanged(this, &STimelineEditor::SetSizeScaleValue); + .OnValueChanged(const_cast(this), &STimelineEditor::SetSizeScaleValue); MenuBuilder.AddWidget(SizeSlider, LOCTEXT("TimelineEditorVerticalSize", "Height")); } diff --git a/Engine/Source/Editor/Kismet/Private/UserDefinedStructureEditor.cpp b/Engine/Source/Editor/Kismet/Private/UserDefinedStructureEditor.cpp index 59831d00f7b7..231a4482d1d2 100644 --- a/Engine/Source/Editor/Kismet/Private/UserDefinedStructureEditor.cpp +++ b/Engine/Source/Editor/Kismet/Private/UserDefinedStructureEditor.cpp @@ -216,7 +216,7 @@ void FDefaultValueDetails::OnFinishedChangingProperties(const FPropertyChangedEv { if (StructData.IsValid() && StructData->IsValid()) { - bDefaultValueSet = FBlueprintEditorUtils::PropertyValueToString(DirectProperty, StructData->GetStructMemory(), DefaultValueString); + bDefaultValueSet = FBlueprintEditorUtils::PropertyValueToString(DirectProperty, StructData->GetStructMemory(), DefaultValueString, OwnerStruct); } } @@ -588,7 +588,7 @@ public: auto StructureDetailsSP = StructureDetails.Pin(); if(StructureDetailsSP.IsValid()) { - return FText::FromString(FStructureEditorUtils::GetVariableDisplayName(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid)); + return FText::FromString(FStructureEditorUtils::GetVariableFriendlyName(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid)); } return FText::GetEmpty(); } @@ -686,7 +686,7 @@ public: { if (const FStructVariableDescription* FieldDesc = StructureDetailsSP->FindStructureFieldByGuid(FieldGuid)) { - return !FieldDesc->bDontEditoOnInstance ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + return !FieldDesc->bDontEditOnInstance ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } } return ECheckBoxState::Undetermined; @@ -701,6 +701,28 @@ public: } } + ECheckBoxState OnGetSaveGameState() const + { + auto StructureDetailsSP = StructureDetails.Pin(); + if (StructureDetailsSP.IsValid()) + { + if (const FStructVariableDescription* FieldDesc = StructureDetailsSP->FindStructureFieldByGuid(FieldGuid)) + { + return FieldDesc->bEnableSaveGame ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + } + } + return ECheckBoxState::Undetermined; + } + + void OnSaveGameCommitted(ECheckBoxState InNewState) + { + auto StructureDetailsSP = StructureDetails.Pin(); + if (StructureDetailsSP.IsValid() && (ECheckBoxState::Undetermined != InNewState)) + { + FStructureEditorUtils::ChangeSaveGameEnabled(StructureDetailsSP->GetUserDefinedStruct(), FieldGuid, InNewState == ECheckBoxState::Checked); + } + } + // Multi-line text EVisibility IsMultiLineTextOptionVisible() const { @@ -971,6 +993,21 @@ public: .IsChecked(this, &FUserDefinedStructureFieldLayout::OnGetEditableOnBPInstanceState) ]; + ChildrenBuilder.AddCustomRow(LOCTEXT("SaveGame", "Save Game")) + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("SaveGameText", "Save Game")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + [ + SNew(SCheckBox) + .ToolTipText(LOCTEXT("SaveGame_Tooltip", "Should variable be serialized for saved games")) + .OnCheckStateChanged(this, &FUserDefinedStructureFieldLayout::OnSaveGameCommitted) + .IsChecked(this, &FUserDefinedStructureFieldLayout::OnGetSaveGameState) + ]; + ChildrenBuilder.AddCustomRow(LOCTEXT("MultiLineText", "Multi-line Text")) .NameContent() [ diff --git a/Engine/Source/Editor/Kismet/Private/WorkflowOrientedApp/WorkflowTabManager.cpp b/Engine/Source/Editor/Kismet/Private/WorkflowOrientedApp/WorkflowTabManager.cpp index 57dea2f8a1cd..ee7aef569234 100644 --- a/Engine/Source/Editor/Kismet/Private/WorkflowOrientedApp/WorkflowTabManager.cpp +++ b/Engine/Source/Editor/Kismet/Private/WorkflowOrientedApp/WorkflowTabManager.cpp @@ -199,7 +199,7 @@ TSharedRef FTabInfo::CreateHistoryMenu(bool bInBackHistory) const { MenuBuilder.AddMenuEntry(History[HistoryIdx]->GetHistoryTitle().Get(), FText(), FSlateIcon(), FUIAction( - FExecuteAction::CreateRaw(this, &FTabInfo::GoToHistoryIndex, HistoryIdx) + FExecuteAction::CreateRaw(const_cast(this), &FTabInfo::GoToHistoryIndex, HistoryIdx) ), NAME_None, EUserInterfaceActionType::Button); } @@ -216,7 +216,7 @@ TSharedRef FTabInfo::CreateHistoryMenu(bool bInBackHistory) const { MenuBuilder.AddMenuEntry(History[HistoryIdx]->GetHistoryTitle().Get(), FText(), FSlateIcon(), FUIAction( - FExecuteAction::CreateRaw(this, &FTabInfo::GoToHistoryIndex, HistoryIdx) + FExecuteAction::CreateRaw(const_cast(this), &FTabInfo::GoToHistoryIndex, HistoryIdx) ), NAME_None, EUserInterfaceActionType::Button); } diff --git a/Engine/Source/Editor/Kismet/Public/BlueprintEditor.h b/Engine/Source/Editor/Kismet/Public/BlueprintEditor.h index 1f85c14c0a07..0ce460f2637b 100644 --- a/Engine/Source/Editor/Kismet/Public/BlueprintEditor.h +++ b/Engine/Source/Editor/Kismet/Public/BlueprintEditor.h @@ -43,6 +43,7 @@ class UEdGraph; class UEdGraphNode; class UUserDefinedEnum; class UUserDefinedStruct; +class UBlueprintEditorOptions; struct Rect; /* Enums to use when grouping the blueprint members in the list panel. The order here will determine the order in the list */ @@ -204,6 +205,7 @@ public: //~ Begin FGCObject Interface virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + virtual FString GetReferencerName() const override; //~ End FGCObject Interface //~ Begin IBlueprintEditor Interface @@ -1082,6 +1084,19 @@ private: /** Returns the appropriate check box state representing whether or not the selected nodes are enabled */ ECheckBoxState GetEnabledCheckBoxStateForSelectedNodes(); + /** Configuration class used to store editor settings across sessions. */ + UBlueprintEditorOptions* EditorOptions; + + /** + * Load editor settings from disk (docking state, window pos/size, option state, etc). + */ + virtual void LoadEditorSettings(); + + /** + * Saves editor settings to disk (docking state, window pos/size, option state, etc). + */ + virtual void SaveEditorSettings(); + /** Attempt to match the given enabled state for currently-selected nodes */ ECheckBoxState CheckEnabledStateForSelectedNodes(ENodeEnabledState CheckState); @@ -1090,6 +1105,9 @@ private: public://@TODO TSharedPtr DocumentManager; + + /** Update all nodes' unrelated states when the graph has changed */ + void UpdateNodesUnrelatedStatesAfterGraphChange(); protected: @@ -1191,6 +1209,35 @@ protected: /** The preview actor representing the current preview */ mutable TWeakObjectPtr PreviewActorPtr; + /** If true, fade out nodes which are unrelated to the selected nodes automatically. */ + bool bHideUnrelatedNodes; + + /** Lock the current fade state of each node */ + bool bLockNodeFadeState; + + /** If a regular node (not a comment node) has been selected */ + bool bSelectRegularNode; + + /** Focus nodes which are related to the selected nodes */ + void ResetAllNodesUnrelatedStates(); + void CollectExecUpstreamNodes(UEdGraphNode* CurrentNode, TArray& CollectedNodes); + void CollectExecDownstreamNodes(UEdGraphNode* CurrentNode, TArray& CollectedNodes); + void CollectPureDownstreamNodes(UEdGraphNode* CurrentNode, TArray& CollectedNodes); + void CollectPureUpstreamNodes(UEdGraphNode* CurrentNode, TArray& CollectedNodes); + void HideUnrelatedNodes(); + +public: + /** Make nodes which are unrelated to the selected nodes fade out */ + void ToggleHideUnrelatedNodes(); + bool IsToggleHideUnrelatedNodesChecked() const; + + /** Make a drop down menu to control the opacity of unrelated nodes */ + TSharedRef MakeHideUnrelatedNodesOptionsMenu(); + TOptional HandleUnrelatedNodesOpacityBoxValue() const; + void HandleUnrelatedNodesOpacityBoxChanged(float NewOpacity); + void OnLockNodeStateCheckStateChanged(ECheckBoxState NewCheckedState); + + public: //@TODO: To be moved/merged TSharedPtr OpenDocument(const UObject* DocumentID, FDocumentTracker::EOpenDocumentCause Cause); diff --git a/Engine/Source/Editor/Kismet/Public/DetailsDiff.h b/Engine/Source/Editor/Kismet/Public/DetailsDiff.h index 59e722b7d2c6..99c8cfb68e18 100644 --- a/Engine/Source/Editor/Kismet/Public/DetailsDiff.h +++ b/Engine/Source/Editor/Kismet/Public/DetailsDiff.h @@ -8,6 +8,7 @@ class IDetailsView; +/** Struct to handle showing details for an object and provide an interface for listing all differences */ class KISMET_API FDetailsDiff { public: @@ -16,11 +17,20 @@ public: FDetailsDiff( const UObject* InObject, FOnDisplayedPropertiesChanged InOnDisplayedPropertiesChanged ); ~FDetailsDiff(); + /** Attempt to highlight the property with the given path, may not always succeed */ void HighlightProperty( const FPropertySoftPath& PropertyName ); + + /** Returns actual widget that is used to display details */ TSharedRef< SWidget > DetailsWidget(); + + /** Returns object being displayed */ + const UObject* GetDisplayedObject() const { return DisplayedObject; } + + /** Returns a list of all properties that would be diffed */ TArray GetDisplayedProperties() const; - void DiffAgainst(const FDetailsDiff& Newer, TArray< FSingleObjectDiffEntry > &OutDifferences) const; + /** Perform a diff against another view, ordering either by display order or by remove/add/change */ + void DiffAgainst(const FDetailsDiff& Newer, TArray& OutDifferences, bool bSortByDisplayOrder = false) const; private: void HandlePropertiesChanged(); diff --git a/Engine/Source/Editor/Kismet/Public/DiffUtils.h b/Engine/Source/Editor/Kismet/Public/DiffUtils.h index ef66a5255227..2b4400edff49 100644 --- a/Engine/Source/Editor/Kismet/Public/DiffUtils.h +++ b/Engine/Source/Editor/Kismet/Public/DiffUtils.h @@ -47,22 +47,25 @@ struct FPropertySoftPath } FPropertySoftPath(TArray InPropertyChain) - : PropertyChain(InPropertyChain) { + for (FName PropertyName : InPropertyChain) + { + PropertyChain.Push(FChainElement(PropertyName)); + } } FPropertySoftPath( FPropertyPath InPropertyPath ) { for( int32 i = 0, end = InPropertyPath.GetNumProperties(); i != end; ++i ) { - PropertyChain.Push( InPropertyPath.GetPropertyInfo(i).Property->GetFName() ); + PropertyChain.Push(FChainElement(InPropertyPath.GetPropertyInfo(i).Property.Get())); } } FPropertySoftPath(const FPropertySoftPath& SubPropertyPath, const UProperty* LeafProperty) : PropertyChain(SubPropertyPath.PropertyChain) { - PropertyChain.Push(LeafProperty->GetFName()); + PropertyChain.Push(FChainElement(LeafProperty)); } FPropertySoftPath(const FPropertySoftPath& SubPropertyPath, int32 ContainerIndex) @@ -71,9 +74,10 @@ struct FPropertySoftPath PropertyChain.Push(FName(*FString::FromInt(ContainerIndex))); } - FResolvedProperty Resolve(const UObject* Object) const; - FPropertyPath ResolvePath(const UObject* Object) const; - FString ToDisplayName() const; + KISMET_API FResolvedProperty Resolve(const UObject* Object) const; + KISMET_API FResolvedProperty Resolve(const UStruct* Struct, const void* StructData) const; + KISMET_API FPropertyPath ResolvePath(const UObject* Object) const; + KISMET_API FString ToDisplayName() const; inline bool IsSubPropertyMatch(const FPropertySoftPath& PotentialBasePropertyPath) const { @@ -103,8 +107,46 @@ struct FPropertySoftPath return !(*this == RHS); } private: + struct FChainElement + { + // FName of the property + FName PropertyName; + + // Display string of the property + FString DisplayString; + + FChainElement(FName InPropertyName, const FString& InDisplayString = FString()) + : PropertyName(InPropertyName) + , DisplayString(InDisplayString) + { + if (DisplayString.IsEmpty()) + { + DisplayString = PropertyName.ToString(); + } + } + + FChainElement(const UProperty* Property) + { + if (Property) + { + PropertyName = Property->GetFName(); + DisplayString = Property->GetAuthoredName(); + } + } + + inline bool operator==(FChainElement const& RHS) const + { + return PropertyName == RHS.PropertyName; + } + + inline bool operator!=(FChainElement const& RHS) const + { + return !(*this == RHS); + } + }; + friend uint32 GetTypeHash( FPropertySoftPath const& Path ); - TArray PropertyChain; + TArray PropertyChain; }; struct FSCSIdentifier @@ -132,9 +174,9 @@ FORCEINLINE bool operator!=(const FSCSIdentifier& A, const FSCSIdentifier& B) FORCEINLINE uint32 GetTypeHash( FPropertySoftPath const& Path ) { uint32 Ret = 0; - for( const auto& ProperytName : Path.PropertyChain ) + for( const FPropertySoftPath::FChainElement& PropertyElement : Path.PropertyChain ) { - Ret = Ret ^ GetTypeHash(ProperytName); + Ret = Ret ^ GetTypeHash(PropertyElement.PropertyName); } return Ret; } @@ -208,10 +250,11 @@ struct FSCSDiffRoot namespace DiffUtils { KISMET_API const UObject* GetCDO(const UBlueprint* ForBlueprint); + KISMET_API void CompareUnrelatedStructs(const UStruct* StructA, const void* A, const UStruct* StructB, const void* B, TArray& OutDifferingProperties); KISMET_API void CompareUnrelatedObjects(const UObject* A, const UObject* B, TArray& OutDifferingProperties); KISMET_API void CompareUnrelatedSCS(const UBlueprint* Old, const TArray< FSCSResolvedIdentifier >& OldHierarchy, const UBlueprint* New, const TArray< FSCSResolvedIdentifier >& NewHierarchy, FSCSDiffRoot& OutDifferingEntries ); KISMET_API bool Identical(const FResolvedProperty& AProp, const FResolvedProperty& BProp, const FPropertySoftPath& RootPath, TArray& DifferingProperties); - TArray GetVisiblePropertiesInOrderDeclared(const UObject* ForObj, const TArray& Scope = TArray()); + KISMET_API TArray GetVisiblePropertiesInOrderDeclared(const UStruct* ForStruct, const FPropertySoftPath& Scope = FPropertySoftPath()); KISMET_API TArray ResolveAll(const UObject* Object, const TArray& InSoftProperties); KISMET_API TArray ResolveAll(const UObject* Object, const TArray& InDifferences); @@ -223,7 +266,7 @@ DECLARE_DELEGATE_RetVal(TSharedRef, FGenerateDiffEntryWidget); class FBlueprintDifferenceTreeEntry { public: - FBlueprintDifferenceTreeEntry( FOnDiffEntryFocused InOnFocus, FGenerateDiffEntryWidget InGenerateWidget, TArray< TSharedPtr > InChildren ) + FBlueprintDifferenceTreeEntry(FOnDiffEntryFocused InOnFocus, FGenerateDiffEntryWidget InGenerateWidget, TArray< TSharedPtr > InChildren = TArray< TSharedPtr >()) : OnFocus(InOnFocus) , GenerateWidget(InGenerateWidget) , Children(InChildren) @@ -231,15 +274,17 @@ public: check( InGenerateWidget.IsBound() ); } - /** The FBlueprintDifferenceTreeEntry used to display a message to the user explaining that there are no differences: */ + /** Displays message to user saying there are no differences */ KISMET_API static TSharedPtr NoDifferencesEntry(); - KISMET_API static TSharedPtr AnimBlueprintEntry(); - KISMET_API static TSharedPtr WidgetBlueprintEntry(); - /** The FBlueprintDifferenceTreeEntry used to label the defaults category: */ - KISMET_API static TSharedPtr CreateDefaultsCategoryEntry(FOnDiffEntryFocused FocusCallback, const TArray< TSharedPtr >& Children, bool bHasDifferences ); - KISMET_API static TSharedPtr CreateDefaultsCategoryEntryForMerge(FOnDiffEntryFocused FocusCallback, const TArray< TSharedPtr >& Children, bool bHasRemoteDifferences, bool bHasLocalDifferences, bool bHasConflicts ); - KISMET_API static TSharedPtr CreateComponentsCategoryEntry(FOnDiffEntryFocused FocusCallback, const TArray< TSharedPtr >& Children, bool bHasDifferences); - KISMET_API static TSharedPtr CreateComponentsCategoryEntryForMerge(FOnDiffEntryFocused FocusCallback, const TArray< TSharedPtr >& Children, bool bHasRemoteDifferences, bool bHasLocalDifferences, bool bHasConflicts); + + /** Displays message to user warning that there may be undetected differences */ + KISMET_API static TSharedPtr UnknownDifferencesEntry(); + + /** Create category message for the diff UI */ + KISMET_API static TSharedPtr CreateCategoryEntry(const FText& LabelText, const FText& ToolTipText, FOnDiffEntryFocused FocusCallback, const TArray< TSharedPtr >& Children, bool bHasDifferences); + + /** Create category message for the merge UI */ + KISMET_API static TSharedPtr CreateCategoryEntryForMerge(const FText& LabelText, const FText& ToolTipText, FOnDiffEntryFocused FocusCallback, const TArray< TSharedPtr >& Children, bool bHasRemoteDifferences, bool bHasLocalDifferences, bool bHasConflicts); FOnDiffEntryFocused OnFocus; FGenerateDiffEntryWidget GenerateWidget; diff --git a/Engine/Source/Editor/Kismet/Public/SBlueprintDiff.h b/Engine/Source/Editor/Kismet/Public/SBlueprintDiff.h index f23edefaf609..bca95d9cee28 100644 --- a/Engine/Source/Editor/Kismet/Public/SBlueprintDiff.h +++ b/Engine/Source/Editor/Kismet/Public/SBlueprintDiff.h @@ -11,7 +11,7 @@ #include "Widgets/Views/SListView.h" #include "GraphEditor.h" #include "DiffUtils.h" -#include "Editor/GraphEditor/Public/DiffResults.h" +#include "DiffResults.h" #include "SKismetInspector.h" #include "Developer/AssetTools/Public/IAssetTypeActions.h" @@ -20,24 +20,9 @@ class FTabManager; class IDiffControl; class SMyBlueprint; class UEdGraph; -struct FListItemGraphToDiff; +struct FGraphToDiff; enum class EAssetEditorCloseReason : uint8; -struct FMatchFName -{ - FMatchFName(FName InName) - : Name(InName) - { - } - - bool operator() (const UObject* Object) - { - return Object->GetFName() == Name; - } - - FName const Name; -}; - /** Individual Diff item shown in the list of diffs */ struct FDiffResultItem : public TSharedFromThis { @@ -58,7 +43,7 @@ namespace DiffWidgetUtils KISMET_API bool HasPrevDifference(SListView< TSharedPtr< struct FDiffSingleResult> >& ListView, const TArray< TSharedPtr< struct FDiffSingleResult > >& ListViewSource); } -/*panel used to display the blueprint*/ +/** Panel used to display the blueprint */ struct KISMET_API FDiffPanel { FDiffPanel(); @@ -66,50 +51,50 @@ struct KISMET_API FDiffPanel /** Initializes the panel, can be moved into constructor if diff and merge clients are made more uniform: */ void InitializeDiffPanel(); - /* Generate this panel based on the specified graph */ + /** Generate this panel based on the specified graph */ void GeneratePanel(UEdGraph* Graph, UEdGraph* GraphToDiff); - /* Generate the 'MyBlueprint' widget, which is private to this module */ - TSharedRef GenerateMyBlueprintPanel(); + /** Generate the 'MyBlueprint' widget, which is private to this module */ + TSharedRef GenerateMyBlueprintWidget(); - /* Called when user hits keyboard shortcut to copy nodes*/ + /** Called when user hits keyboard shortcut to copy nodes */ void CopySelectedNodes(); - /*Gets whatever nodes are selected in the Graph Editor*/ + /** Gets whatever nodes are selected in the Graph Editor */ FGraphPanelSelectionSet GetSelectedNodes() const; - /*Can user copy any of the selected nodes?*/ + /** Can user copy any of the selected nodes? */ bool CanCopyNodes() const; - /*Functions used to focus/find a particular change in a diff result*/ + /** Functions used to focus/find a particular change in a diff result */ void FocusDiff(UEdGraphPin& Pin); void FocusDiff(UEdGraphNode& Node); - /*The blueprint that owns the graph we are showing*/ - const class UBlueprint* Blueprint; + /** The blueprint that owns the graph we are showing */ + const UBlueprint* Blueprint; - /*The border around the graph editor, used to change the content when new graphs are set */ - TSharedPtr GraphEditorBorder; + /** The box around the graph editor, used to change the content when new graphs are set */ + TSharedPtr GraphEditorBox; - /* The border around the my blueprint panel, used to regenerate the panel when the new graphs are set */ + /** The actual my blueprint panel, used to regenerate the panel when the new graphs are set */ TSharedPtr MyBlueprint; - /*The box around the the details view associated with the graph editor */ + /** The details view associated with the graph editor */ TSharedPtr DetailsView; - /*The graph editor which does the work of displaying the graph*/ + /** The graph editor which does the work of displaying the graph */ TWeakPtr GraphEditor; - /*Revision information for this blueprint */ + /** Revision information for this blueprint */ FRevisionInfo RevisionInfo; - /*A name identifying which asset this panel is displaying */ + /** True if we should show a name identifying which asset this panel is displaying */ bool bShowAssetName; - /*The panel stores the last pin that was focused on by the user, so that it can clear the visual style when selection changes*/ + /** The panel stores the last pin that was focused on by the user, so that it can clear the visual style when selection changes */ UEdGraphPin* LastFocusedPin; private: - /*Command list for this diff panel*/ + /** Command list for this diff panel */ TSharedPtr GraphEditorCommands; }; @@ -132,11 +117,17 @@ public: virtual ~SBlueprintDiff(); /** Called when a new Graph is clicked on by user */ - void OnGraphChanged(struct FListItemGraphToDiff* Diff); + void OnGraphChanged(FGraphToDiff* Diff); /** Called when blueprint is modified */ void OnBlueprintChanged(UBlueprint* InBlueprint); + /** Called when user clicks on a new graph list item */ + void OnGraphSelectionChanged(TSharedPtr Item, ESelectInfo::Type SelectionType); + + /** Called when user clicks on an entry in the listview of differences */ + void OnDiffListSelectionChanged(TSharedPtr TheDiff); + /** Helper function for generating an empty widget */ static TSharedRef DefaultEmptyPanel(); @@ -154,52 +145,36 @@ protected: bool HasNextDiff() const; bool HasPrevDiff() const; - /** Spawns the tabs that contain the Graph views and blueprints views respectively: */ - TSharedRef CreateGraphDiffViews( const FSpawnTabArgs& Args ); - TSharedRef CreateMyBlueprintsViews( const FSpawnTabArgs& Args ); - - typedef TSharedPtr FGraphToDiff; - typedef SListView SListViewType; - - /** Find the FListItemGraphToDiff that displays the graph with GraphName: */ - FListItemGraphToDiff* FindGraphToDiffEntry(FName ByName); + /** Find the FGraphToDiff that displays the graph with GraphPath relative path */ + FGraphToDiff* FindGraphToDiffEntry(const FString& GraphPath); /** Bring these revisions of graph into focus on main display*/ - void FocusOnGraphRevisions( struct FListItemGraphToDiff* Diff); + void FocusOnGraphRevisions(FGraphToDiff* Diff); - /*Create a list item entry graph that exists in at least one of the blueprints */ + /** Create a list item entry graph that exists in at least one of the blueprints */ void CreateGraphEntry(class UEdGraph* GraphOld, class UEdGraph* GraphNew); - - /* Called when a new row is being generated */ - TSharedRef OnGenerateRow(FGraphToDiff ParamItem, const TSharedRef& OwnerTable ); - - /*Called when user clicks on a new graph list item */ - void OnSelectionChanged(FGraphToDiff Item, ESelectInfo::Type SelectionType); - - /** Called when user clicks on an entry in the listview of differences */ - void OnDiffListSelectionChanged(TSharedPtr TheDiff ); /** Disable the focus on a particular pin */ void DisablePinDiffFocus(); - /*User toggles the option to lock the views between the two blueprints */ + /** User toggles the option to lock the views between the two blueprints */ void OnToggleLockView(); - /*Reset the graph editor, called when user switches graphs to display*/ + /** Reset the graph editor, called when user switches graphs to display*/ void ResetGraphEditors(); - /*Get the image to show for the toggle lock option*/ + /** Get the image to show for the toggle lock option*/ FSlateIcon GetLockViewImage() const; - /* This buffer stores the currently displayed results */ - TArray< FGraphToDiff> Graphs; + /** List of graphs to diff, are added to panel last */ + TArray> Graphs; /** Get Graph editor associated with this Graph */ FDiffPanel& GetDiffPanelForNode(UEdGraphNode& Node); /** Event handler that updates the graph view when user selects a new graph */ void HandleGraphChanged( const FString& GraphPath ); - + /** Function used to generate the list of differences and the widgets needed to calculate that list */ void GenerateDifferencesList(); @@ -213,7 +188,7 @@ protected: { FDiffControl() : Widget() - , DiffControl(NULL) + , DiffControl(nullptr) { } @@ -221,8 +196,11 @@ protected: TSharedPtr< class IDiffControl > DiffControl; }; + FDiffControl GenerateBlueprintTypePanel(); + FDiffControl GenerateMyBlueprintPanel(); FDiffControl GenerateGraphPanel(); FDiffControl GenerateDefaultsPanel(); + FDiffControl GenerateClassSettingsPanel(); FDiffControl GenerateComponentsPanel(); /** Accessor and event handler for toggling between diff view modes (defaults, components, graph view, interface, macro): */ @@ -242,9 +220,6 @@ protected: friend struct FListItemGraphToDiff; - /** Helper class for highlighting diffs in different types of controls (graph view, details view, etc) */ - TSharedPtr< class IDiffControl > DiffControl; - /** We can't use the global tab manager because we need to instance the diff control, so we have our own tab manager: */ TSharedPtr TabManager; @@ -257,10 +232,8 @@ protected: /** Tree view that displays the differences, cached for the buttons that iterate the differences: */ TSharedPtr< STreeView< TSharedPtr< FBlueprintDifferenceTreeEntry > > > DifferencesTreeView; - /** Stored references to widgets used to display various parts of a blueprint: */ - FDiffControl GraphPanel; - FDiffControl DefaultsPanel; - FDiffControl ComponentsPanel; + /** Stored references to widgets used to display various parts of a blueprint, from the mode name */ + TMap ModePanels; /** A pointer to the window holding this */ TWeakPtr WeakParentWindow; diff --git a/Engine/Source/Editor/Kismet/Public/SCSDiff.h b/Engine/Source/Editor/Kismet/Public/SCSDiff.h index 3c84dd471e80..6f17184b079b 100644 --- a/Engine/Source/Editor/Kismet/Public/SCSDiff.h +++ b/Engine/Source/Editor/Kismet/Public/SCSDiff.h @@ -9,6 +9,7 @@ class FSCSEditorTreeNode; class SKismetInspector; class SSCSEditor; +/** Struct to support diffing the component tree for a blueprint */ class KISMET_API FSCSDiff { public: @@ -19,6 +20,8 @@ public: TArray< FSCSResolvedIdentifier > GetDisplayedHierarchy() const; + const UBlueprint* GetBlueprint() const { return Blueprint; } + protected: void OnSCSEditorUpdateSelectionFromNodes(const TArray< TSharedPtr >& SelectedNodes); void OnSCSEditorHighlightPropertyInDetailsView(const class FPropertyPath& InPropertyPath); @@ -27,4 +30,7 @@ private: TSharedPtr< class SWidget > ContainerWidget; TSharedPtr< class SSCSEditor > SCSEditor; TSharedPtr< class SKismetInspector > Inspector; + + /** Blueprint we are inspecting */ + UBlueprint* Blueprint; }; diff --git a/Engine/Source/Editor/Kismet/Public/SSCSEditor.h b/Engine/Source/Editor/Kismet/Public/SSCSEditor.h index d693f9ed0677..087becf035fb 100644 --- a/Engine/Source/Editor/Kismet/Public/SSCSEditor.h +++ b/Engine/Source/Editor/Kismet/Public/SSCSEditor.h @@ -31,7 +31,8 @@ class UPrimitiveComponent; struct EventData; // SCS tree node pointer type -typedef TSharedPtr FSCSEditorTreeNodePtrType; +using FSCSEditorTreeNodePtrType = TSharedPtr; +using FSCSEditorActorNodePtrType = TSharedPtr; /** * FSCSEditorTreeNode @@ -82,7 +83,7 @@ public: * * @return The component template that can be editable for actual class. */ - virtual UActorComponent* GetEditableComponentTemplate(UBlueprint* ActualEditedBlueprint); + virtual UActorComponent* GetOrCreateEditableComponentTemplate(UBlueprint* ActualEditedBlueprint) const; /** * Finds the component instance represented by this node contained within a given Actor instance. * @@ -108,6 +109,7 @@ public: * have been reinstanced following construction script execution). * * @note Deliberately non-virtual, for performance reasons. + * @warning This will not return the right component for components overridden by the inherited component handler, you need to call GetOrCreateEditableComponentTemplate instead * @return The component template or instance represented by this node, if it's a component node. */ UActorComponent* GetComponentTemplate(bool bEvenIfPendingKill = false) const; @@ -137,7 +139,7 @@ public: * * @param InChildNodePtr The node to add as a child node. */ - void AddChild(FSCSEditorTreeNodePtrType InChildNodePtr); + virtual void AddChild(FSCSEditorTreeNodePtrType InChildNodePtr); /** * Adds a child node for the given SCS node. @@ -191,7 +193,7 @@ public: * * @param InChildNodePtr The child node to remove. */ - void RemoveChild(FSCSEditorTreeNodePtrType InChildNodePtr); + virtual void RemoveChild(FSCSEditorTreeNodePtrType InChildNodePtr); bool IsSceneComponent() const { @@ -293,7 +295,7 @@ public: void SetRenameRequestedDelegate(FOnRenameRequested InRenameRequested) { RenameRequestedDelegate = InRenameRequested; } /** Query that determines if this item should be filtered out or not */ - bool IsFlaggedForFiltration() const + virtual bool IsFlaggedForFiltration() const { return ensureMsgf(FilterFlags != EFilteredState::Unknown, TEXT("Querying a bad filtration state.")) ? (FilterFlags & EFilteredState::FilteredInMask) == 0 : false; @@ -370,7 +372,7 @@ public: * * @param InComponentTemplate The component template represented by this object. */ - FSCSEditorTreeNodeInstancedInheritedComponent(AActor* Owner, FName InComponentName); + FSCSEditorTreeNodeInstancedInheritedComponent(AActor* Owner, UActorComponent* InComponentTemplate); // FSCSEditorTreeNode public interface virtual bool IsNative() const override; @@ -384,11 +386,10 @@ public: //virtual FName GetVariableName() const override; //virtual FString GetDisplayString() const override; virtual FText GetDisplayName() const override; - virtual UActorComponent* GetEditableComponentTemplate(UBlueprint* ActualEditedBlueprint) override; + virtual UActorComponent* GetOrCreateEditableComponentTemplate(UBlueprint* ActualEditedBlueprint) const override; // End of FSCSEditorTreeNode public interface private: - FName InstancedComponentName; TWeakObjectPtr InstancedComponentOwnerPtr; }; @@ -416,7 +417,7 @@ public: virtual FName GetVariableName() const override { return NAME_None; } virtual FString GetDisplayString() const override; virtual FText GetDisplayName() const override; - virtual UActorComponent* GetEditableComponentTemplate(UBlueprint* ActualEditedBlueprint) override; + virtual UActorComponent* GetOrCreateEditableComponentTemplate(UBlueprint* ActualEditedBlueprint) const override; virtual void OnCompleteRename(const FText& InNewName) override; // End of FSCSEditorTreeNode public interface @@ -464,7 +465,7 @@ public: //virtual FString GetDisplayString() const override; virtual FText GetDisplayName() const override; virtual class USCS_Node* GetSCSNode() const override; - virtual UActorComponent* GetEditableComponentTemplate(UBlueprint* ActualEditedBlueprint) override; + virtual UActorComponent* GetOrCreateEditableComponentTemplate(UBlueprint* ActualEditedBlueprint) const override; virtual void OnCompleteRename(const FText& InNewName) override; // End of FSCSEditorTreeNode public interface @@ -484,7 +485,6 @@ private: TWeakObjectPtr SCSNodePtr; }; - class KISMET_API FSCSEditorTreeNodeRootActor : public FSCSEditorTreeNode { public: @@ -495,15 +495,32 @@ public: { } + FSCSEditorTreeNodePtrType GetSceneRootNode() const; + void SetSceneRootNode(FSCSEditorTreeNodePtrType NewSceneRootNode); + + /** Returns the set of root nodes */ + const TArray& GetComponentNodes() const; + // FSCSEditorTreeNode public interface virtual FName GetNodeID() const override; virtual bool CanRename() const override { return bAllowRename; } virtual void OnCompleteRename(const FText& InNewName) override; + virtual void AddChild(FSCSEditorTreeNodePtrType InChildNodePtr) override; + virtual void RemoveChild(FSCSEditorTreeNodePtrType InChildNodePtr) override; // End of FSCSEditorTreeNode public interface +protected: + using Super = FSCSEditorTreeNode; private: + AActor* Actor; bool bAllowRename; + + FSCSEditorTreeNodePtrType SceneRootNodePtr; + /** Root set of components (contains the root scene component and any non-scene component nodes) */ + TArray ComponentNodes; + FSCSEditorTreeNodePtrType SceneComponentSeparatorNodePtr; + FSCSEditorTreeNodePtrType NonSceneComponentSeparatorNodePtr; }; class KISMET_API FSCSEditorTreeNodeSeparator : public FSCSEditorTreeNode @@ -513,6 +530,8 @@ public: : FSCSEditorTreeNode(FSCSEditorTreeNode::SeparatorNode) { } + + virtual bool IsFlaggedForFiltration() const override { return false; } }; ////////////////////////////////////////////////////////////////////////// @@ -803,6 +822,14 @@ public: /** Returns true if editing is allowed */ bool IsEditingAllowed() const; + /** gets the actor root */ + FSCSEditorActorNodePtrType GetActorNode() const; + + /** get the root scene node */ + FSCSEditorTreeNodePtrType GetSceneRootNode() const; + + void SetSceneRootNode(FSCSEditorTreeNodePtrType NewSceneRootNode); + /** Adds a component to the SCS Table @param NewComponentClass (In) The class to add @param Asset (In) Optional asset to assign to the component @@ -822,10 +849,11 @@ public: /** Adds a new component instance node to the component Table @param OngoingCreateTransaction (In) The transaction containing the creation of the node. The transaction will remain ongoing until the node gets its initial name from user. @param NewInstanceComponent (In) The component being added to the actor instance + @param InParentNodePtr (In) The node this component will be added to @param Asset (In) Optional asset to assign to the component @param bSetFocusToNewItem (In) Select the new item and activate the inline rename widget (default is true) @return The reference of the newly created ActorComponent */ - UActorComponent* AddNewNodeForInstancedComponent(TUniquePtr OngoingCreateTransaction, UActorComponent* NewInstanceComponent, UObject* Asset, bool bSetFocusToNewItem = true); + UActorComponent* AddNewNodeForInstancedComponent(TUniquePtr OngoingCreateTransaction, UActorComponent* NewInstanceComponent, FSCSEditorTreeNodePtrType InParentNodePtr, UObject* Asset, bool bSetFocusToNewItem = true); /** Returns true if the specified component is currently selected */ bool IsComponentSelected(const UPrimitiveComponent* PrimComponent) const; @@ -961,9 +989,6 @@ public: /** Provides access to the Blueprint context that's being edited */ class UBlueprint* GetBlueprint() const; - /** Returns the set of root nodes */ - const TArray& GetRootComponentNodes(); - /** @return The current editor mode (editing live actors or editing blueprints) */ EComponentEditorMode::Type GetEditorMode() const { return EditorMode; } @@ -980,6 +1005,10 @@ public: void OnPostTick(float); protected: + FSCSEditorTreeNodePtrType FindOrCreateParentForExistingComponent(UActorComponent* InActorComponent, FSCSEditorActorNodePtrType ActorRootNode); + FSCSEditorTreeNodePtrType FindParentForNewComponent(UActorComponent* NewComponent) const; + FSCSEditorTreeNodePtrType FindParentForNewNode(USCS_Node* NewNode) const; + FString GetSelectedClassText() const; /** Add a component from the selection in the combo box */ @@ -1040,15 +1069,9 @@ protected: /** Helper method to add a tree node for the given SCS node */ FSCSEditorTreeNodePtrType AddTreeNode(USCS_Node* InSCSNode, FSCSEditorTreeNodePtrType InParentNodePtr, const bool bIsInheritedSCS); - /** Helper method to add a tree node for the given scene component */ - FSCSEditorTreeNodePtrType AddTreeNodeFromComponent(USceneComponent* InSceneComponent, FSCSEditorTreeNodePtrType InParentTreeNode = FSCSEditorTreeNodePtrType()); + /** Helper method to add a tree node for the given actor component */ + FSCSEditorTreeNodePtrType AddTreeNodeFromComponent(UActorComponent* InSceneComponent, FSCSEditorTreeNodePtrType InParentTreeNode = FSCSEditorTreeNodePtrType()); - /** Helper method to recursively add tree nodes for an already-attached instanced scene component hierarchy */ - void AddInstancedTreeNodesRecursive(USceneComponent* Component, FSCSEditorTreeNodePtrType TreeNode, TSet& ComponentsToAdd); - - /** Helper method to determine whether or not an instanced actor component should be added to the tree */ - bool ShouldAddInstancedActorComponent(UActorComponent* ActorComp, USceneComponent* ParentSceneComp = nullptr) const; - /** Helper method to recursively find a tree node for the given SCS node starting at the given tree node */ FSCSEditorTreeNodePtrType FindTreeNode(const USCS_Node* InSCSNode, FSCSEditorTreeNodePtrType InStartNodePtr = FSCSEditorTreeNodePtrType()) const; @@ -1097,9 +1120,6 @@ protected: /** gets a root nodes of the tree */ const TArray& GetRootNodes() const; - /** Adds a root component tree node */ - TSharedPtr AddRootComponentTreeNode(UActorComponent* ActorComp); - /** * Creates a new C++ component from the specified class type * The user will be prompted to pick a new subclass name and code will be recompiled @@ -1126,19 +1146,10 @@ protected: */ bool RefreshFilteredState(FSCSEditorTreeNodePtrType TreeNode, bool bRecursive); - /** - * Iterates the RootNodes list, and uses the cached filtered state to - * determine what items should be listed in the tree view. - */ - void RebuildFilteredRootList(); - public: /** Tree widget */ TSharedPtr SCSTreeWidget; - /** The node that represents the root component in the scene hierarchy */ - FSCSEditorTreeNodePtrType SceneRootNodePtr; - /** Command list for handling actions in the SSCSEditor */ TSharedPtr< FUICommandList > CommandList; @@ -1178,15 +1189,6 @@ private: /** Root set of tree */ TArray RootNodes; - /** Root set of components (contains the root scene component and any non-scene component nodes) */ - TArray RootComponentNodes; - - /** The list of nodes used for the UI (a filtered version of RootNodes) */ - TArray FilteredRootNodes; - - /* Root Tree Node (for scene components) */ - TSharedPtr RootTreeNode; - /* Root Tree Node*/ TSharedPtr ActorMenuExtender; @@ -1196,9 +1198,6 @@ private: /** Gate to prevent changing the selection while selection change is being broadcast. */ bool bUpdatingSelection; - /** true if we've added the separator between the scene and behavior components to the root nodes */ - bool bHasAddedSceneAndBehaviorComponentSeparator; - /** Controls whether or not to allow calls to UpdateTree() */ bool bAllowTreeUpdates; diff --git a/Engine/Source/Editor/KismetCompiler/Private/EdGraphCompiler.cpp b/Engine/Source/Editor/KismetCompiler/Private/EdGraphCompiler.cpp index 8459b9081c0c..9a1c84e30b50 100644 --- a/Engine/Source/Editor/KismetCompiler/Private/EdGraphCompiler.cpp +++ b/Engine/Source/Editor/KismetCompiler/Private/EdGraphCompiler.cpp @@ -65,9 +65,24 @@ void FGraphCompilerContext::ValidatePin(const UEdGraphPin* Pin) const /** Validates that the node is schema compatible */ void FGraphCompilerContext::ValidateNode(const UEdGraphNode* Node) const { - if (Node->IsDeprecated() && Node->ShouldWarnOnDeprecation()) + const bool bIsNodeDeprecated = Node->IsDeprecated(); + if (bIsNodeDeprecated || Node->HasDeprecatedReference()) { - MessageLog.Warning(*Node->GetDeprecationMessage(), Node); + EEdGraphNodeDeprecationType ResponseType = bIsNodeDeprecated ? EEdGraphNodeDeprecationType::NodeTypeIsDeprecated : EEdGraphNodeDeprecationType::NodeHasDeprecatedReference; + FEdGraphNodeDeprecationResponse Response = Node->GetDeprecationResponse(ResponseType); + if (Response.MessageType != EEdGraphNodeDeprecationMessageType::None && !Response.MessageText.IsEmpty()) + { + switch (Response.MessageType) + { + case EEdGraphNodeDeprecationMessageType::Note: + MessageLog.Note(*Response.MessageText.ToString(), Node); + break; + + case EEdGraphNodeDeprecationMessageType::Warning: + MessageLog.Warning(*Response.MessageText.ToString(), Node); + break; + } + } } for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex) diff --git a/Engine/Source/Editor/KismetCompiler/Private/KismetCompiler.cpp b/Engine/Source/Editor/KismetCompiler/Private/KismetCompiler.cpp index 5410a75b5d7c..ea7e2f0e9151 100644 --- a/Engine/Source/Editor/KismetCompiler/Private/KismetCompiler.cpp +++ b/Engine/Source/Editor/KismetCompiler/Private/KismetCompiler.cpp @@ -1701,6 +1701,16 @@ void FKismetCompilerContext::PrecompileFunction(FKismetFunctionContext& Context, Context.Function->SetMetaData(FBlueprintMetadata::MD_CallInEditor, TEXT( "true" )); } + // Set appropriate metadata if the function is deprecated + if (FunctionMetaData.bIsDeprecated) + { + Context.Function->SetMetaData(FBlueprintMetadata::MD_DeprecatedFunction, TEXT("true")); + if (!FunctionMetaData.DeprecationMessage.IsEmpty()) + { + Context.Function->SetMetaData(FBlueprintMetadata::MD_DeprecationMessage, *FunctionMetaData.DeprecationMessage); + } + } + // Set the required function flags if (Context.CanBeCalledByKismet()) { @@ -2233,10 +2243,22 @@ void FKismetCompilerContext::SetCalculatedMetaDataAndFlags(UFunction* Function, { Function->SetMetaData(FBlueprintMetadata::MD_Tooltip, *EntryNode->MetaData.ToolTip.ToString()); } + if (EntryNode->MetaData.bCallInEditor) { - Function->SetMetaData(FBlueprintMetadata::MD_CallInEditor, TEXT( "true" )); + Function->SetMetaData(FBlueprintMetadata::MD_CallInEditor, TEXT("true")); } + + if (EntryNode->MetaData.bIsDeprecated) + { + Function->SetMetaData(FBlueprintMetadata::MD_DeprecatedFunction, TEXT("true")); + + if (!EntryNode->MetaData.DeprecationMessage.IsEmpty()) + { + Function->SetMetaData(FBlueprintMetadata::MD_DeprecationMessage, *(EntryNode->MetaData.DeprecationMessage)); + } + } + if (UEdGraphPin* WorldContextPin = EntryNode->GetAutoWorldContextPin()) { Function->SetMetaData(FBlueprintMetadata::MD_WorldContext, *WorldContextPin->PinName.ToString()); @@ -2629,11 +2651,6 @@ void FKismetCompilerContext::ExpandTimelineNodes(UEdGraph* SourceGraph) // Set the timeline template as wired/not wired for component pruning later const bool bWiredIn = bPlayPinConnected || bPlayFromStartPinConnected || bStopPinConnected || bReversePinConnected || bReverseFromEndPinConnected || bSetNewTimePinConnected; - const bool bWiredOut = bUpdatePinConnected || bFinishedPinConnected; - const bool bPlayWired = Timeline->bAutoPlay; - const bool bReferenced = TimelinePlayNodes.Find(TimelineNode->TimelineName) != INDEX_NONE; - - Timeline->bValidatedAsWired = bWiredIn || bReferenced || (bPlayWired && bWiredOut); // Only create nodes for play/stop if they are actually connected - otherwise we get a 'unused node being pruned' warning if (bWiredIn) @@ -2869,6 +2886,9 @@ void FKismetCompilerContext::CreateFunctionStubForEvent(UK2Node_Event* SrcEventN StubContext.MarkAsNetFunction(SrcCustomEventNode->GetNetFlags()); // Synchronize the entry node call in editor value with the entry point EntryNode->MetaData.bCallInEditor = SrcCustomEventNode->bCallInEditor; + // Synchronize the node deprecation state with the entry point + EntryNode->MetaData.bIsDeprecated = SrcCustomEventNode->bIsDeprecated; + EntryNode->MetaData.DeprecationMessage = SrcCustomEventNode->DeprecationMessage; } EntryNode->AllocateDefaultPins(); @@ -3128,9 +3148,11 @@ void FKismetCompilerContext::VerifyValidOverrideEvent(const UEdGraph* Graph) (EventNode->EventReference.GetMemberParentClass(EventNode->GetBlueprintClassFromNode()) == FuncClass) && (EventNode->EventReference.GetMemberName() == FuncName)) { - if (EventNode->IsDeprecated()) + if (EventNode->HasDeprecatedReference()) { - MessageLog.Warning(*EventNode->GetDeprecationMessage(), EventNode); + // The event cannot be placed because it has been deprecated. However, we already emit a + // warning in FGraphCompilerContext::ValidateNode(), so there's no need to repeat it here. + continue; } else if(!Function->HasAllFunctionFlags(FUNC_Const)) // ...allow legacy event nodes that override methods declared as 'const' to pass. { diff --git a/Engine/Source/Editor/KismetCompiler/Private/KismetCompilerMisc.cpp b/Engine/Source/Editor/KismetCompiler/Private/KismetCompilerMisc.cpp index d8027bdc4f21..48b4ddecace8 100644 --- a/Engine/Source/Editor/KismetCompiler/Private/KismetCompilerMisc.cpp +++ b/Engine/Source/Editor/KismetCompiler/Private/KismetCompilerMisc.cpp @@ -1162,7 +1162,7 @@ UProperty* FKismetCompilerUtilities::CreatePropertyOnScope(UStruct* Scope, const NewProperty = CreatePrimitiveProperty(PropertyScope, ValidatedPropertyName, Type.PinCategory, Type.PinSubCategory, Type.PinSubCategoryObject.Get(), SelfClass, Type.bIsWeakPointer, Schema, MessageLog); } - if (NewContainerProperty && NewProperty && NewProperty->HasAnyPropertyFlags(CPF_ContainsInstancedReference)) + if (NewContainerProperty && NewProperty && NewProperty->HasAnyPropertyFlags(CPF_ContainsInstancedReference | CPF_InstancedReference)) { NewContainerProperty->SetPropertyFlags(CPF_ContainsInstancedReference); } @@ -1196,9 +1196,9 @@ UProperty* FKismetCompilerUtilities::CreatePropertyOnScope(UStruct* Scope, const } else { - if (NewMapProperty->ValueProp && NewMapProperty->ValueProp->HasAnyPropertyFlags(CPF_ContainsInstancedReference)) + if (NewMapProperty->ValueProp && NewMapProperty->ValueProp->HasAnyPropertyFlags(CPF_ContainsInstancedReference | CPF_InstancedReference)) { - NewMapProperty->ValueProp->SetPropertyFlags(CPF_ContainsInstancedReference); + NewContainerProperty->SetPropertyFlags(CPF_ContainsInstancedReference); } NewProperty = NewMapProperty; diff --git a/Engine/Source/Editor/KismetCompiler/Private/UserDefinedStructureCompilerUtils.cpp b/Engine/Source/Editor/KismetCompiler/Private/UserDefinedStructureCompilerUtils.cpp index c36e725e3336..3bba42b3c7ed 100644 --- a/Engine/Source/Editor/KismetCompiler/Private/UserDefinedStructureCompilerUtils.cpp +++ b/Engine/Source/Editor/KismetCompiler/Private/UserDefinedStructureCompilerUtils.cpp @@ -253,10 +253,14 @@ struct FUserDefinedStructureCompilerInner } VarProperty->SetPropertyFlags(CPF_Edit | CPF_BlueprintVisible); - if (VarDesc.bDontEditoOnInstance) + if (VarDesc.bDontEditOnInstance) { VarProperty->SetPropertyFlags(CPF_DisableEditOnInstance); } + if (VarDesc.bEnableSaveGame) + { + VarProperty->SetPropertyFlags(CPF_SaveGame); + } if (VarDesc.bEnableMultiLineText) { VarProperty->SetMetaData("MultiLine", TEXT("true")); @@ -286,9 +290,9 @@ struct FUserDefinedStructureCompilerInner { const UClass* ClassObject = Cast(VarType.PinSubCategoryObject.Get()); - if (ClassObject && ClassObject->IsChildOf(AActor::StaticClass())) + if (ClassObject && ClassObject->IsChildOf(AActor::StaticClass()) && (VarType.PinCategory == UEdGraphSchema_K2::PC_Object || VarType.PinCategory == UEdGraphSchema_K2::PC_Interface)) { - // prevent Actor variables from having default values (because Blueprint templates are library elements that can + // prevent hard reference Actor variables from having default values (because Blueprint templates are library elements that can // bridge multiple levels and different levels might not have the actor that the default is referencing). VarProperty->PropertyFlags |= CPF_DisableEditOnTemplate; } diff --git a/Engine/Source/Editor/KismetWidgets/Private/SPinTypeSelector.cpp b/Engine/Source/Editor/KismetWidgets/Private/SPinTypeSelector.cpp index 28c2f767c341..aa5832e5f016 100644 --- a/Engine/Source/Editor/KismetWidgets/Private/SPinTypeSelector.cpp +++ b/Engine/Source/Editor/KismetWidgets/Private/SPinTypeSelector.cpp @@ -260,21 +260,21 @@ void SPinTypeSelector::Construct(const FArguments& InArgs, FGetPinTypeTree GetPi TreeViewHeight = InArgs._TreeViewHeight; TargetPinType = InArgs._TargetPinType; - bIsCompactSelector = InArgs._bCompactSelector; + SelectorType = InArgs._SelectorType; bIsRightMousePressed = false; // Depending on if this is a compact selector or not, we generate a different compound widget TSharedPtr Widget; - if (InArgs._bCompactSelector) + if (SelectorType == ESelectorType::Compact) { // Only have a combo button with an icon Widget = SAssignNew( TypeComboButton, SComboButton ) .OnGetMenuContent(this, &SPinTypeSelector::GetMenuContent, false) .ContentPadding(0) .ToolTipText(this, &SPinTypeSelector::GetToolTipForComboBoxType) - .HasDownArrow(!InArgs._bCompactSelector) + .HasDownArrow(false) .ButtonStyle(FEditorStyle::Get(), "BlueprintEditor.CompactPinTypeSelector") .ButtonContent() [ @@ -287,7 +287,18 @@ void SPinTypeSelector::Construct(const FArguments& InArgs, FGetPinTypeTree GetPi .ColorAndOpacity(this, &SPinTypeSelector::GetTypeIconColor) ]; } - else + else if (SelectorType == ESelectorType::None) + { + Widget = SNew( + SDoubleImage, + TAttribute(this, &SPinTypeSelector::GetSecondaryTypeIconImage), + TAttribute(this, &SPinTypeSelector::GetSecondaryTypeIconColor) + ) + .ToolTipText(this, &SPinTypeSelector::GetToolTipForComboBoxType) + .Image(this, &SPinTypeSelector::GetTypeIconImage) + .ColorAndOpacity(this, &SPinTypeSelector::GetTypeIconColor); + } + else if (SelectorType == ESelectorType::Full) { // Traditional Pin Type Selector with a combo button, the icon, the current type name, and a toggle button for being an array TSharedPtr ContainerControl = SNew(SComboButton) @@ -433,7 +444,6 @@ void SPinTypeSelector::Construct(const FArguments& InArgs, FGetPinTypeTree GetPi .ColorAndOpacity( this, &SPinTypeSelector::GetSecondaryTypeIconColor ) ] +SHorizontalBox::Slot() - .AutoWidth() .VAlign(VAlign_Center) .HAlign(HAlign_Left) [ @@ -531,6 +541,8 @@ void SPinTypeSelector::OnArrayStateToggled() void SPinTypeSelector::OnContainerTypeSelectionChanged( EPinContainerType PinContainerType) { + const FScopedTransaction Transaction(LOCTEXT("ChangeParam", "Change Parameter Type")); + FEdGraphPinType NewTargetPinType = TargetPinType.Get(); NewTargetPinType.ContainerType = PinContainerType; @@ -1062,21 +1074,21 @@ FText SPinTypeSelector::GetToolTipForComboBoxType() const FText EditText; if(IsEnabled()) { - if (bIsCompactSelector) + if (SelectorType == ESelectorType::Compact) { - EditText = LOCTEXT("CompactPinTypeSelector", "Left click to select the variable's pin type. Right click to toggle the type as an array."); + EditText = LOCTEXT("CompactPinTypeSelector", "Left click to select the variable's pin type. Right click to toggle the type as an array.\n"); } - else + else if (SelectorType == ESelectorType::Full) { - EditText = LOCTEXT("PinTypeSelector", "Select the variable's pin type."); + EditText = LOCTEXT("PinTypeSelector", "Select the variable's pin type.\n"); } } else { - EditText = LOCTEXT("PinTypeSelector_Disabled", "Cannot edit variable type when they are inherited from parent."); + EditText = LOCTEXT("PinTypeSelector_Disabled", "Cannot edit variable type when they are inherited from parent.\n"); } - return FText::Format(LOCTEXT("PrimaryTypeTwoLines", "{0}\nCurrent Type: {1}"), EditText, GetTypeDescription()); + return FText::Format(LOCTEXT("PrimaryTypeTwoLines", "{0}Current Type: {1}"), EditText, GetTypeDescription()); } FText SPinTypeSelector::GetToolTipForComboBoxSecondaryType() const @@ -1147,7 +1159,7 @@ FText SPinTypeSelector::GetToolTipForContainerWidget() const FReply SPinTypeSelector::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { - if(bIsCompactSelector && MouseEvent.GetEffectingButton() == EKeys::RightMouseButton) + if (SelectorType == ESelectorType::Compact && MouseEvent.GetEffectingButton() == EKeys::RightMouseButton) { bIsRightMousePressed = true; return FReply::Handled(); @@ -1158,7 +1170,7 @@ FReply SPinTypeSelector::OnMouseButtonDown( const FGeometry& MyGeometry, const F FReply SPinTypeSelector::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { - if(bIsCompactSelector && MouseEvent.GetEffectingButton() == EKeys::RightMouseButton) + if (SelectorType == ESelectorType::Compact && MouseEvent.GetEffectingButton() == EKeys::RightMouseButton) { if (bIsRightMousePressed) { diff --git a/Engine/Source/Editor/KismetWidgets/Public/SPinTypeSelector.h b/Engine/Source/Editor/KismetWidgets/Public/SPinTypeSelector.h index c59b753e92e9..e5e196939cb2 100644 --- a/Engine/Source/Editor/KismetWidgets/Public/SPinTypeSelector.h +++ b/Engine/Source/Editor/KismetWidgets/Public/SPinTypeSelector.h @@ -46,6 +46,14 @@ public: static TSharedRef ConstructPinTypeImage(TAttribute PrimaryIcon, TAttribute PrimaryColor, TAttribute SecondaryIcon, TAttribute SecondaryColor ); static TSharedRef ConstructPinTypeImage(UEdGraphPin* Pin); + /** Which type of selector should be used: compact or full mode, or not a selector at all, but just the type image. */ + enum class ESelectorType : uint8 + { + None, + Compact, + Full + }; + SLATE_BEGIN_ARGS( SPinTypeSelector ) : _TargetPinType() , _Schema(nullptr) @@ -54,7 +62,7 @@ public: , _TreeViewWidth(300.f) , _TreeViewHeight(400.f) , _Font( FEditorStyle::GetFontStyle( TEXT("NormalFont") ) ) - , _bCompactSelector( false ) + , _SelectorType( ESelectorType::Full ) {} SLATE_ATTRIBUTE( FEdGraphPinType, TargetPinType ) SLATE_ARGUMENT( const UEdGraphSchema_K2*, Schema ) @@ -65,7 +73,7 @@ public: SLATE_EVENT( FOnPinTypeChanged, OnPinTypePreChanged ) SLATE_EVENT( FOnPinTypeChanged, OnPinTypeChanged ) SLATE_ATTRIBUTE( FSlateFontInfo, Font ) - SLATE_ARGUMENT( bool, bCompactSelector ) + SLATE_ARGUMENT( ESelectorType, SelectorType ) SLATE_END_ARGS() public: void Construct(const FArguments& InArgs, FGetPinTypeTree GetPinTypeTreeFunc); @@ -125,8 +133,8 @@ protected: /** TRUE when the right mouse button is pressed, keeps from handling a right click that does not begin in the widget */ bool bIsRightMousePressed; - /** TRUE if displaying a compact selector widget, some functionality is enabled in different ways if this is TRUE */ - bool bIsCompactSelector; + /** Whether the selector is using the compact or full mode, or not a selector at all, but just the type image.*/ + ESelectorType SelectorType; /** Holds a cache of the allowed Object Reference types for the last sub-menu opened. */ TArray AllowedObjectReferenceTypes; diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdMode.cpp b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdMode.cpp index 8ed88768ee44..2a4b36fac893 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdMode.cpp +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdMode.cpp @@ -99,7 +99,7 @@ IMPLEMENT_HIT_PROXY(HNewLandscapeGrabHandleProxy, HHitProxy) ENGINE_API extern bool GDisableAutomaticTextureMaterialUpdateDependencies; -void ALandscape::SplitHeightmap(ULandscapeComponent* Comp, ALandscapeProxy* TargetProxy, FMaterialUpdateContext* InOutUpdateContext, TArray* InOutRecreateRenderStateContext, bool InReregisterComponent) +void ALandscape::SplitHeightmap(ULandscapeComponent* Comp, ALandscapeProxy* TargetProxy,FMaterialUpdateContext* InOutUpdateContext, TArray* InOutRecreateRenderStateContext, bool InReregisterComponent) { ULandscapeInfo* Info = Comp->GetLandscapeInfo(); @@ -340,7 +340,7 @@ FEdModeLandscape::FEdModeLandscape() InitializeTool_Splines(); InitializeTool_Ramp(); InitializeTool_Mirror(); - InitializeTool_BPCustom(); + InitializeTool_BlueprintBrush(); // Initialize brushes InitializeBrushes(); @@ -436,7 +436,7 @@ void FEdModeLandscape::UpdateToolModes() if (CanHaveLandscapeLayersContent()) { - ToolMode_Sculpt->ValidTools.Add(TEXT("BPCustom")); + ToolMode_Sculpt->ValidTools.Add(TEXT("BlueprintBrush")); } ToolMode_Sculpt->ValidTools.Add(TEXT("Mask")); @@ -452,7 +452,7 @@ void FEdModeLandscape::UpdateToolModes() if (CanHaveLandscapeLayersContent()) { - ToolMode_Paint->ValidTools.Add(TEXT("BPCustom")); + ToolMode_Paint->ValidTools.Add(TEXT("BlueprintBrush")); } // Since available tools might have changed try and reset the current tool @@ -569,6 +569,7 @@ void FEdModeLandscape::Enter() // For now depends on the SpawnActor() above in order to get the current editor world as edmodes don't get told UpdateLandscapeList(); UpdateTargetList(); + UpdateBrushList(); OnWorldChangeDelegateHandle = FEditorSupportDelegates::WorldChange.AddRaw(this, &FEdModeLandscape::HandleLevelsChanged, true); OnLevelsChangedDelegateHandle = GetWorld()->OnLevelsChanged().AddRaw(this, &FEdModeLandscape::HandleLevelsChanged, true); @@ -579,15 +580,17 @@ void FEdModeLandscape::Enter() ALandscapeProxy* LandscapeProxy = CurrentToolTarget.LandscapeInfo->GetLandscapeProxy(); LandscapeProxy->OnMaterialChangedDelegate().AddRaw(this, &FEdModeLandscape::OnLandscapeMaterialChangedDelegate); - ALandscape* Landscape = GetLandscape(); - - if(Landscape && Landscape->HasLayersContent()) + if (ALandscape* Landscape = GetLandscape()) { - if (Landscape->GetLandscapeSplinesReservedLayer()) + Landscape->OnBPCustomBrushChangedDelegate().AddLambda([this]() { this->RefreshDetailPanel(); }); + if (Landscape->HasLayersContent()) { - Landscape->UpdateLandscapeSplines(); + if (Landscape->GetLandscapeSplinesReservedLayer()) + { + Landscape->UpdateLandscapeSplines(); + } + Landscape->RequestLayersContentUpdateForceAll(); } - Landscape->RequestLayersContentUpdateForceAll(); } } @@ -765,6 +768,10 @@ void FEdModeLandscape::Exit() { ALandscapeProxy* LandscapeProxy = CurrentToolTarget.LandscapeInfo->GetLandscapeProxy(); LandscapeProxy->OnMaterialChangedDelegate().RemoveAll(this); + if (ALandscape* Landscape = GetLandscape()) + { + Landscape->OnBPCustomBrushChangedDelegate().RemoveAll(this); + } } // Restore real-time viewport state if we changed it @@ -2377,6 +2384,11 @@ void FEdModeLandscape::SetCurrentBrush(int32 BrushIndex) } } +const TArray& FEdModeLandscape::GetBrushList() const +{ + return BrushList; +} + const TArray>& FEdModeLandscape::GetTargetList() const { return LandscapeTargetList; @@ -2516,6 +2528,10 @@ void FEdModeLandscape::SetTargetLandscape(const TWeakObjectPtr& { ALandscapeProxy* LandscapeProxy = CurrentToolTarget.LandscapeInfo->GetLandscapeProxy(); LandscapeProxy->OnMaterialChangedDelegate().RemoveAll(this); + if (ALandscape* Landscape = GetLandscape()) + { + Landscape->OnBPCustomBrushChangedDelegate().RemoveAll(this); + } } SetLandscapeInfo(InLandscapeInfo.Get()); @@ -2532,6 +2548,10 @@ void FEdModeLandscape::SetTargetLandscape(const TWeakObjectPtr& { ALandscapeProxy* LandscapeProxy = CurrentToolTarget.LandscapeInfo->GetLandscapeProxy(); LandscapeProxy->OnMaterialChangedDelegate().AddRaw(this, &FEdModeLandscape::OnLandscapeMaterialChangedDelegate); + if (ALandscape* Landscape = GetLandscape()) + { + Landscape->OnBPCustomBrushChangedDelegate().AddLambda([this]() { this->RefreshDetailPanel(); }); + } } UpdateTargetList(); @@ -2553,6 +2573,12 @@ bool FEdModeLandscape::CanEditCurrentTarget(FText* Reason) const if (GetLandscape() == nullptr) { ALandscapeProxy* Proxy = CurrentToolTarget.LandscapeInfo->GetLandscapeProxy(); + if (!Proxy) + { + LocalReason = NSLOCTEXT("UnrealEd", "LandscapeNotFound", "No Landscape found."); + return false; + } + if (Proxy->HasLayersContent()) { LocalReason = NSLOCTEXT("UnrealEd", "LandscapeActorNotLoaded", "Landscape actor is not loaded. It is needed to do layer editing."); @@ -2700,7 +2726,7 @@ void FEdModeLandscape::UpdateTargetLayerDisplayOrder(ELandscapeLayerDisplayMode SavedTargetNameList.Add(LandscapeTargetList[i]->LayerName); } - SavedTargetNameList.Sort(); + SavedTargetNameList.Sort(FNameLexicalLess()); // Then insert the non layer target that shouldn't be sorted for (int32 i = 0; i < GetTargetLayerStartingIndex(); ++i) @@ -2973,6 +2999,7 @@ void FEdModeLandscape::HandleLevelsChanged(bool ShouldExitMode) UpdateLandscapeList(); UpdateTargetList(); UpdateShownLayerList(); + UpdateBrushList(); // if the Landscape is deleted then close the landscape editor if (ShouldExitMode && bHadLandscape && CurrentToolTarget.LandscapeInfo == nullptr) @@ -3479,7 +3506,7 @@ void FEdModeLandscape::ForceRealTimeViewports(const bool bEnable, const bool bSt void FEdModeLandscape::ReimportData(const FLandscapeTargetListInfo& TargetInfo) { - const FString& SourceFilePath = TargetInfo.ReimportFilePath(); + const FString& SourceFilePath = TargetInfo.GetReimportFilePath(); if (SourceFilePath.Len()) { FScopedSetLandscapeEditingLayer Scope(GetLandscape(), GetCurrentLayerGuid(), [&] { RequestLayersContentUpdateForceAll(); }); @@ -4100,7 +4127,6 @@ ALandscape* FEdModeLandscape::ChangeComponentSetting(int32 NumComponentsX, int32 NewLandscape->PositiveZBoundsExtension = OldLandscape->PositiveZBoundsExtension; NewLandscape->DefaultPhysMaterial = OldLandscape->DefaultPhysMaterial; NewLandscape->StreamingDistanceMultiplier = OldLandscape->StreamingDistanceMultiplier; - NewLandscape->LandscapeHoleMaterial = OldLandscape->LandscapeHoleMaterial; NewLandscape->StaticLightingResolution = OldLandscape->StaticLightingResolution; NewLandscape->bCastStaticShadow = OldLandscape->bCastStaticShadow; NewLandscape->bCastShadowAsTwoSided = OldLandscape->bCastShadowAsTwoSided; @@ -4452,7 +4478,7 @@ void FEdModeLandscape::RequestLayersContentUpdateForceAll(ELandscapeLayerUpdateM } } -void FEdModeLandscape::AddBrushToCurrentLayer(int32 InTargetType, ALandscapeBlueprintCustomBrush* InBrush) +void FEdModeLandscape::AddBrushToCurrentLayer(ALandscapeBlueprintCustomBrush* InBrush) { ALandscape* Landscape = GetLandscape(); if (Landscape == nullptr) @@ -4460,10 +4486,11 @@ void FEdModeLandscape::AddBrushToCurrentLayer(int32 InTargetType, ALandscapeBlue return; } - Landscape->AddBrushToLayer(GetCurrentLayerIndex(), InTargetType, InBrush); + Landscape->AddBrushToLayer(GetCurrentLayerIndex(), InBrush); + RefreshDetailPanel(); } -void FEdModeLandscape::RemoveBrushFromCurrentLayer(int32 InTargetType, ALandscapeBlueprintCustomBrush* InBrush) +void FEdModeLandscape::RemoveBrushFromCurrentLayer(ALandscapeBlueprintCustomBrush* InBrush) { ALandscape* Landscape = GetLandscape(); @@ -4472,65 +4499,26 @@ void FEdModeLandscape::RemoveBrushFromCurrentLayer(int32 InTargetType, ALandscap return; } - Landscape->RemoveBrushFromLayer(GetCurrentLayerIndex(), InTargetType, InBrush); + Landscape->RemoveBrushFromLayer(GetCurrentLayerIndex(), InBrush); + RefreshDetailPanel(); } -bool FEdModeLandscape::AreAllBrushesCommitedToCurrentLayer(int32 InTargetType) +ALandscapeBlueprintCustomBrush* FEdModeLandscape::GetBrushForCurrentLayer(int8 InBrushIndex) const { - ALandscape* Landscape = GetLandscape(); - - if (Landscape == nullptr) + if (ALandscape* Landscape = GetLandscape()) { - return false; + return Landscape->GetBrushForLayer(GetCurrentLayerIndex(), InBrushIndex); } - - return Landscape->AreAllBrushesCommitedToLayer(GetCurrentLayerIndex(), InTargetType); + return nullptr; } -void FEdModeLandscape::SetBrushesCommitStateForCurrentLayer(int32 InTargetType, bool InCommited) +TArray FEdModeLandscape::GetBrushesForCurrentLayer() { - ALandscape* Landscape = GetLandscape(); - - if (Landscape == nullptr) - { - return; - } - - Landscape->SetBrushesCommitStateForLayer(GetCurrentLayerIndex(), InTargetType, InCommited); - - GEngine->BroadcastLevelActorListChanged(); -} - -TArray& FEdModeLandscape::GetBrushesOrderForCurrentLayer(int32 InTargetType) const -{ - ALandscape* Landscape = GetLandscape(); - check(Landscape); - return Landscape->GetBrushesOrderForLayer(GetCurrentLayerIndex(), InTargetType); -} - -ALandscapeBlueprintCustomBrush* FEdModeLandscape::GetBrushForCurrentLayer(int32 InTargetType, int8 InBrushIndex) const -{ - ALandscape* Landscape = GetLandscape(); - - if (Landscape == nullptr) - { - return nullptr; - } - - return Landscape->GetBrushForLayer(GetCurrentLayerIndex(), InTargetType, InBrushIndex); -} - -TArray FEdModeLandscape::GetBrushesForCurrentLayer(int32 InTargetType) -{ - ALandscape* Landscape = GetLandscape(); TArray Brushes; - - if (Landscape == nullptr) + if (ALandscape* Landscape = GetLandscape()) { - return Brushes; + Brushes = Landscape->GetBrushesForLayer(GetCurrentLayerIndex()); } - - Brushes = Landscape->GetBrushesForLayer(GetCurrentLayerIndex(), InTargetType); return Brushes; } @@ -4637,7 +4625,7 @@ bool FEdModeLandscape::CanEditLayer(FText* Reason /*=nullptr*/, FLandscapeLayer* } } - if (CurrentToolTarget.TargetType == ELandscapeToolTargetType::Weightmap && CurrentToolTarget.LayerInfo == NULL && CurrentTool->GetToolName() != FName("BPCustom")) + if (CurrentToolTarget.TargetType == ELandscapeToolTargetType::Weightmap && CurrentToolTarget.LayerInfo == NULL && CurrentTool->GetToolName() != FName("BlueprintBrush")) { if (Reason) { @@ -4723,6 +4711,16 @@ bool FEdModeLandscape::NeedToFillEmptyMaterialLayers() const return bCanFill; } +void FEdModeLandscape::UpdateBrushList() +{ + BrushList.Empty(); + for (TObjectIterator BrushIt(RF_Transient|RF_ClassDefaultObject|RF_ArchetypeObject, true, EInternalObjectFlags::PendingKill); BrushIt; ++BrushIt) + { + BrushList.Add(*BrushIt); + } +} + + void FEdModeLandscape::OnLevelActorAdded(AActor* InActor) { if (ALandscape* Landscape = Cast (InActor)) @@ -4734,7 +4732,8 @@ void FEdModeLandscape::OnLevelActorAdded(AActor* InActor) if (Brush != nullptr && Brush->GetTypedOuter() != GetTransientPackage()) { - AddBrushToCurrentLayer(CurrentToolTarget.TargetType, Brush); + BrushList.Add(Brush); + AddBrushToCurrentLayer(Brush); RefreshDetailPanel(); } } @@ -4750,7 +4749,8 @@ void FEdModeLandscape::OnLevelActorRemoved(AActor* InActor) if (Brush != nullptr && Brush->GetTypedOuter() != GetTransientPackage()) { - RemoveBrushFromCurrentLayer(CurrentToolTarget.TargetType, Brush); + BrushList.Remove(Brush); + RemoveBrushFromCurrentLayer(Brush); RefreshDetailPanel(); } } diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdMode.h b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdMode.h index 4d50c29f4c9e..bd37562c868a 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdMode.h +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdMode.h @@ -152,7 +152,7 @@ struct FLandscapeTargetListInfo FName GetLayerName() const; - FString& ReimportFilePath() const + FString GetReimportFilePath() const { if (TargetType == ELandscapeToolTargetType::Weightmap) { @@ -162,7 +162,39 @@ struct FLandscapeTargetListInfo } else //if (TargetType == ELandscapeToolTargetType::Heightmap) { - return LandscapeInfo->GetLandscapeProxy()->ReimportHeightmapFilePath; + if (LandscapeInfo.IsValid()) + { + ALandscapeProxy* LandscapeProxy = LandscapeInfo->GetLandscapeProxy(); + + if (LandscapeProxy) + { + return LandscapeProxy->ReimportHeightmapFilePath; + } + } + + return FString(TEXT("")); + } + } + + void SetReimportFilePath(const FString& InNewPath) + { + if (TargetType == ELandscapeToolTargetType::Weightmap) + { + FLandscapeEditorLayerSettings* EditorLayerSettings = GetEditorLayerSettings(); + check(EditorLayerSettings); + EditorLayerSettings->ReimportLayerFilePath = InNewPath; + } + else //if (TargetType == ELandscapeToolTargetType::Heightmap) + { + if (LandscapeInfo.IsValid()) + { + ALandscapeProxy* LandscapeProxy = LandscapeInfo->GetLandscapeProxy(); + + if (LandscapeProxy) + { + LandscapeProxy->ReimportHeightmapFilePath = InNewPath; + } + } } } }; @@ -323,7 +355,7 @@ public: void InitializeTool_Splines(); void InitializeTool_Ramp(); void InitializeTool_Mirror(); - void InitializeTool_BPCustom(); + void InitializeTool_BlueprintBrush(); void UpdateToolModes(); /** Destructor */ @@ -476,6 +508,9 @@ public: void SetCurrentBrush(FName BrushName); void SetCurrentBrush(int32 BrushIndex); + void UpdateBrushList(); + const TArray& GetBrushList() const; + const TArray>& GetTargetList() const; const TArray* GetTargetDisplayOrderList() const; const TArray& GetTargetShownList() const; @@ -527,13 +562,10 @@ public: void AutoUpdateDirtyLandscapeSplines(); bool CanEditLayer(FText* Reason = nullptr, FLandscapeLayer* InLayer = nullptr); - void AddBrushToCurrentLayer(int32 InTargetType, class ALandscapeBlueprintCustomBrush* InBrush); - void RemoveBrushFromCurrentLayer(int32 InTargetType, class ALandscapeBlueprintCustomBrush* InBrush); - bool AreAllBrushesCommitedToCurrentLayer(int32 InTargetType); - void SetBrushesCommitStateForCurrentLayer(int32 InTargetType, bool InCommited); - TArray& GetBrushesOrderForCurrentLayer(int32 InTargetType) const; - class ALandscapeBlueprintCustomBrush* GetBrushForCurrentLayer(int32 InTargetType, int8 BrushIndex) const; - TArray GetBrushesForCurrentLayer(int32 InTargetType); + void AddBrushToCurrentLayer(class ALandscapeBlueprintCustomBrush* InBrush); + void RemoveBrushFromCurrentLayer(class ALandscapeBlueprintCustomBrush* InBrush); + class ALandscapeBlueprintCustomBrush* GetBrushForCurrentLayer(int8 BrushIndex) const; + TArray GetBrushesForCurrentLayer(); bool NeedToFillEmptyMaterialLayers() const; void RequestLayersContentUpdate(ELandscapeLayerUpdateMode InUpdateMode); @@ -585,6 +617,7 @@ public: private: TArray> LandscapeTargetList; TArray LandscapeList; + TArray BrushList; TArray ShownTargetLayerList; /** Represent the index offset of the target layer in LandscapeTargetList */ diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeBPCustomTools.cpp b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeBPCustomTools.cpp index 204aff20d1f9..04cfee6c5c71 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeBPCustomTools.cpp +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeBPCustomTools.cpp @@ -27,13 +27,13 @@ #define LOCTEXT_NAMESPACE "Landscape" template -class FLandscapeToolBPCustom : public FLandscapeTool +class FLandscapeToolBlueprintBrush : public FLandscapeTool { protected: FEdModeLandscape* EdMode; public: - FLandscapeToolBPCustom(FEdModeLandscape* InEdMode) + FLandscapeToolBlueprintBrush(FEdModeLandscape* InEdMode) : EdMode(InEdMode) { } @@ -46,7 +46,7 @@ public: { } - virtual const TCHAR* GetToolName() override { return TEXT("BPCustom"); } + virtual const TCHAR* GetToolName() override { return TEXT("BlueprintBrush"); } virtual FText GetDisplayName() override { return FText(); }; virtual void SetEditRenderType() override { GLandscapeEditRenderMode = ELandscapeEditRenderMode::None | (GLandscapeEditRenderMode & ELandscapeEditRenderMode::BitMaskForMask); } @@ -63,6 +63,7 @@ public: virtual void ExitTool() override { + GEditor->SelectNone(true, true); } virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) @@ -96,6 +97,7 @@ public: SpawnInfo.bNoFail = true; SpawnInfo.OverrideLevel = Info->LandscapeActor.Get()->GetTypedOuter(); // always spawn in the same level as the one containing the ALandscape + FScopedTransaction Transaction(LOCTEXT("LandscapeEdModeBPCustomToolSpawn", "Create landscape brush")); ALandscapeBlueprintCustomBrush* Brush = ViewportClient->GetWorld()->SpawnActor(EdMode->UISettings->BlueprintCustomBrush, SpawnLocation, FRotator(0.0f), SpawnInfo); EdMode->UISettings->BlueprintCustomBrush = nullptr; @@ -193,15 +195,15 @@ void FEdModeLandscape::CenterMirrorTool() // // Toolset initialization // -void FEdModeLandscape::InitializeTool_BPCustom() +void FEdModeLandscape::InitializeTool_BlueprintBrush() { - auto Sculpt_Tool_BPCustom = MakeUnique>(this); - Sculpt_Tool_BPCustom->ValidBrushes.Add("BrushSet_Dummy"); - LandscapeTools.Add(MoveTemp(Sculpt_Tool_BPCustom)); + auto Sculpt_Tool_BlueprintBrush = MakeUnique>(this); + Sculpt_Tool_BlueprintBrush->ValidBrushes.Add("BrushSet_Dummy"); + LandscapeTools.Add(MoveTemp(Sculpt_Tool_BlueprintBrush)); - auto Paint_Tool_BPCustom = MakeUnique>(this); - Paint_Tool_BPCustom->ValidBrushes.Add("BrushSet_Dummy"); - LandscapeTools.Add(MoveTemp(Paint_Tool_BPCustom)); + auto Paint_Tool_BlueprintBrush = MakeUnique>(this); + Paint_Tool_BlueprintBrush->ValidBrushes.Add("BrushSet_Dummy"); + LandscapeTools.Add(MoveTemp(Paint_Tool_BlueprintBrush)); } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeComponentTools.cpp b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeComponentTools.cpp index 09893563cd0b..1fca772e428d 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeComponentTools.cpp +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeComponentTools.cpp @@ -313,11 +313,8 @@ public: virtual bool BeginTool(FEditorViewportClient* ViewportClient, const FLandscapeToolTarget& InTarget, const FVector& InHitLocation) override { ALandscapeProxy* Proxy = InTarget.LandscapeInfo->GetLandscapeProxy(); - UMaterialInterface* HoleMaterial = Proxy->GetLandscapeHoleMaterial(); - if (!HoleMaterial) - { - HoleMaterial = Proxy->GetLandscapeMaterial(); - } + UMaterialInterface* HoleMaterial = Proxy->GetLandscapeMaterial(); + if (!HoleMaterial->GetMaterial()->HasAnyExpressionsInMaterialAndFunctionsOfType()) { FMessageDialog::Open(EAppMsgType::Ok, @@ -546,7 +543,7 @@ public: { ALandscape::SplitHeightmap(HeightmapUpdateComponentPair.Key, HeightmapUpdateComponentPair.Value ? LandscapeProxy : nullptr); } - + // Delete if it is no referenced textures... for (UTexture2D* Texture : OldHeightmapTextures) { @@ -974,8 +971,10 @@ public: virtual void EnterTool() override { FLandscapeToolBase::EnterTool(); - ULandscapeInfo* LandscapeInfo = EdMode->CurrentToolTarget.LandscapeInfo.Get(); - LandscapeInfo->UpdateAllAddCollisions(); // Todo - as this is only used by this tool, move it into this tool? + if(ULandscapeInfo* LandscapeInfo = EdMode->CurrentToolTarget.LandscapeInfo.Get()) + { + LandscapeInfo->UpdateAllAddCollisions(); // Todo - as this is only used by this tool, move it into this tool? + } } virtual void ExitTool() override @@ -1893,23 +1892,26 @@ public: virtual void EnterTool() override { - const int32 ComponentSizeQuads = EdMode->CurrentToolTarget.LandscapeInfo->ComponentSizeQuads; - int32 MinX, MinY, MaxX, MaxY; - if (EdMode->CurrentToolTarget.LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + if (ULandscapeInfo* LandscapeInfo = EdMode->CurrentToolTarget.LandscapeInfo.Get()) { - EdMode->UISettings->ResizeLandscape_Original_ComponentCount.X = (MaxX - MinX) / ComponentSizeQuads; - EdMode->UISettings->ResizeLandscape_Original_ComponentCount.Y = (MaxY - MinY) / ComponentSizeQuads; - EdMode->UISettings->ResizeLandscape_ComponentCount = EdMode->UISettings->ResizeLandscape_Original_ComponentCount; + const int32 ComponentSizeQuads = LandscapeInfo->ComponentSizeQuads; + int32 MinX, MinY, MaxX, MaxY; + if (EdMode->CurrentToolTarget.LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + { + EdMode->UISettings->ResizeLandscape_Original_ComponentCount.X = (MaxX - MinX) / ComponentSizeQuads; + EdMode->UISettings->ResizeLandscape_Original_ComponentCount.Y = (MaxY - MinY) / ComponentSizeQuads; + EdMode->UISettings->ResizeLandscape_ComponentCount = EdMode->UISettings->ResizeLandscape_Original_ComponentCount; + } + else + { + EdMode->UISettings->ResizeLandscape_Original_ComponentCount = FIntPoint::ZeroValue; + EdMode->UISettings->ResizeLandscape_ComponentCount = FIntPoint::ZeroValue; + } + EdMode->UISettings->ResizeLandscape_Original_QuadsPerSection = EdMode->CurrentToolTarget.LandscapeInfo->SubsectionSizeQuads; + EdMode->UISettings->ResizeLandscape_Original_SectionsPerComponent = EdMode->CurrentToolTarget.LandscapeInfo->ComponentNumSubsections; + EdMode->UISettings->ResizeLandscape_QuadsPerSection = EdMode->UISettings->ResizeLandscape_Original_QuadsPerSection; + EdMode->UISettings->ResizeLandscape_SectionsPerComponent = EdMode->UISettings->ResizeLandscape_Original_SectionsPerComponent; } - else - { - EdMode->UISettings->ResizeLandscape_Original_ComponentCount = FIntPoint::ZeroValue; - EdMode->UISettings->ResizeLandscape_ComponentCount = FIntPoint::ZeroValue; - } - EdMode->UISettings->ResizeLandscape_Original_QuadsPerSection = EdMode->CurrentToolTarget.LandscapeInfo->SubsectionSizeQuads; - EdMode->UISettings->ResizeLandscape_Original_SectionsPerComponent = EdMode->CurrentToolTarget.LandscapeInfo->ComponentNumSubsections; - EdMode->UISettings->ResizeLandscape_QuadsPerSection = EdMode->UISettings->ResizeLandscape_Original_QuadsPerSection; - EdMode->UISettings->ResizeLandscape_SectionsPerComponent = EdMode->UISettings->ResizeLandscape_Original_SectionsPerComponent; } virtual void ExitTool() override diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModePaintTools.cpp b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModePaintTools.cpp index 6e79ed2efae8..19646f2375a1 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModePaintTools.cpp +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModePaintTools.cpp @@ -604,6 +604,180 @@ public: virtual FText GetDisplayName() override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Erase", "Erase"); }; }; +template +class FLandscapeLayerDataCache +{ +public: + FLandscapeLayerDataCache(const FLandscapeToolTarget& InTarget, typename ToolTarget::CacheClass& Cache) + : LandscapeInfo(nullptr) + , Landscape(nullptr) + , EditingLayerIndex(MAX_uint8) + , bIsInitialized(false) + , bCombinedLayerOperation(false) + , bVisibilityChanged(false) + , bTargetIsHeightmap(InTarget.TargetType == ELandscapeToolTargetType::Heightmap) + , CacheUpToEditingLayer(Cache) + , CacheBottomLayers(InTarget) + { + } + + void Initialize(ULandscapeInfo* InLandscapeInfo, bool InCombinedLayerOperation) + { + if (!bIsInitialized) + { + LandscapeInfo = InLandscapeInfo; + Landscape = LandscapeInfo ? LandscapeInfo->LandscapeActor.Get() : nullptr; + bCombinedLayerOperation = Landscape && Landscape->HasLayersContent() && InCombinedLayerOperation && bTargetIsHeightmap; + if (bCombinedLayerOperation) + { + EditingLayerGuid = Landscape->GetEditingLayer(); + for (int i = 0; i < Landscape->GetLayerCount(); ++i) + { + FLandscapeLayer* CurrentLayer = Landscape->GetLayer(i); + BackupLayerVisibility.Add(CurrentLayer->bVisible); + if (CurrentLayer->Guid == EditingLayerGuid) + { + EditingLayerIndex = i; + } + } + check(EditingLayerIndex < Landscape->GetLayerCount()); + } + bIsInitialized = true; + } + } + + void Read(int32 X1, int32 Y1, int32 X2, int32 Y2, TArray& Data) + { + check(bIsInitialized); + if (bCombinedLayerOperation) + { + TArray NewLayerVisibility; + for (int i = 0; i < Landscape->GetLayerCount(); ++i) + { + FLandscapeLayer* CurrentLayer = Landscape->GetLayer(i); + NewLayerVisibility.Add((i > EditingLayerIndex) ? false : CurrentLayer->bVisible); + } + + FIntRect Bounds; + const float BoundStep = LandscapeInfo->ComponentSizeQuads / 4; + Bounds.Min.X = (FMath::FloorToInt((float)X1 / BoundStep) - 1) * BoundStep; + Bounds.Min.Y = (FMath::FloorToInt((float)Y1 / BoundStep) - 1) * BoundStep; + Bounds.Max.X = (FMath::CeilToInt((float)X2 / BoundStep) + 1) * BoundStep; + Bounds.Max.Y = (FMath::CeilToInt((float)Y2 / BoundStep) + 1) * BoundStep; + + FScopedSetLandscapeEditingLayer Scope(Landscape, FGuid()); + CacheUpToEditingLayer.GetDataAndCache(X1, Y1, X2, Y2, Data, [&]() -> FIntRect + { + TSet AffectedComponents; + LandscapeInfo->GetComponentsInRegion(X1, Y1, X2, Y2, AffectedComponents); + SynchronousUpdateComponentVisibilityForHeight(AffectedComponents, NewLayerVisibility); + return Bounds; + }); + + CacheBottomLayers.GetDataAndCache(X1, Y1, X2, Y2, BottomLayersData, [&]() -> FIntRect + { + NewLayerVisibility[EditingLayerIndex] = false; + TSet AffectedComponents; + LandscapeInfo->GetComponentsInRegion(X1, Y1, X2, Y2, AffectedComponents); + SynchronousUpdateComponentVisibilityForHeight(AffectedComponents, NewLayerVisibility); + return Bounds; + }); + } + else + { + CacheUpToEditingLayer.CacheData(X1, Y1, X2, Y2); + CacheUpToEditingLayer.GetCachedData(X1, Y1, X2, Y2, Data); + } + } + + void Write(int32 X1, int32 Y1, int32 X2, int32 Y2, TArray& Data, ELandscapeLayerPaintingRestriction PaintingRestriction) + { + check(bIsInitialized); + if (bCombinedLayerOperation) + { + const float Alpha = Landscape->GetLayerAlpha(EditingLayerIndex, bTargetIsHeightmap); + const float InverseAlpha = (Alpha != 0.f) ? 1.f / Alpha : 1.f; + TArray DataContribution; + DataContribution.Empty(Data.Num()); + DataContribution.AddUninitialized(Data.Num()); + check(Data.Num() == BottomLayersData.Num()); + for (int i = 0; i < Data.Num(); ++i) + { + float Contribution = (LandscapeDataAccess::GetLocalHeight(Data[i]) - LandscapeDataAccess::GetLocalHeight(BottomLayersData[i])) * InverseAlpha; + DataContribution[i] = LandscapeDataAccess::GetTexHeight(Contribution); + } + check(EditingLayerGuid == Landscape->GetEditingLayer()); + + // Restore layers visibility + SetLayersVisibility(BackupLayerVisibility); + // Only store data in cache + CacheUpToEditingLayer.SetCachedData(X1, Y1, X2, Y2, Data, PaintingRestriction, false); + // Effectively write the contribution + CacheUpToEditingLayer.DataAccess.SetData(X1, Y1, X2, Y2, DataContribution.GetData(), PaintingRestriction); + CacheUpToEditingLayer.DataAccess.Flush(); + if (bVisibilityChanged) + { + TSet AffectedComponents; + LandscapeInfo->GetComponentsInRegion(X1, Y1, X2, Y2, AffectedComponents); + SynchronousUpdateHeightmapForComponents(AffectedComponents); + bVisibilityChanged = false; + } + } + else + { + CacheUpToEditingLayer.SetCachedData(X1, Y1, X2, Y2, Data, PaintingRestriction); + CacheUpToEditingLayer.Flush(); + } + } + +private: + + void SynchronousUpdateHeightmapForComponents(const TSet& InComponents) + { + for (ULandscapeComponent* Component : InComponents) + { + Component->RequestHeightmapUpdate(); + } + Landscape->ForceUpdateLayersContent(); + }; + + void SetLayersVisibility(const TArray& InLayerVisibility) + { + check(InLayerVisibility.Num() == Landscape->GetLayerCount()); + for (int i = 0; i < InLayerVisibility.Num(); ++i) + { + if (FLandscapeLayer* Layer = Landscape->GetLayer(i)) + { + if (Layer->bVisible != InLayerVisibility[i]) + { + Layer->bVisible = InLayerVisibility[i]; + bVisibilityChanged = true; + } + } + } + }; + + void SynchronousUpdateComponentVisibilityForHeight(const TSet& InComponents, const TArray& InLayerVisibility) + { + SetLayersVisibility(InLayerVisibility); + SynchronousUpdateHeightmapForComponents(InComponents); + }; + + ULandscapeInfo* LandscapeInfo; + ALandscape* Landscape; + FGuid EditingLayerGuid; + uint8 EditingLayerIndex; + TArray BackupLayerVisibility; + TArray BottomLayersData; + bool bIsInitialized; + bool bCombinedLayerOperation; + bool bVisibilityChanged; + bool bTargetIsHeightmap; + + typename ToolTarget::CacheClass& CacheUpToEditingLayer; + typename ToolTarget::CacheClass CacheBottomLayers; +}; + // // FLandscapeToolSmooth // @@ -611,9 +785,13 @@ public: template class FLandscapeToolStrokeSmooth : public FLandscapeToolStrokePaintBase { + bool bTargetIsHeightmap; + FLandscapeLayerDataCache LayerDataCache; public: FLandscapeToolStrokeSmooth(FEdModeLandscape* InEdMode, FEditorViewportClient* InViewportClient, const FLandscapeToolTarget& InTarget) : FLandscapeToolStrokePaintBase(InEdMode, InViewportClient, InTarget) + , bTargetIsHeightmap(InTarget.TargetType == ELandscapeToolTargetType::Heightmap) + , LayerDataCache(InTarget, this->Cache) { } @@ -621,6 +799,9 @@ public: { if (!this->LandscapeInfo) return; + ALandscape* Landscape = this->LandscapeInfo->LandscapeActor.Get(); + const bool bCombinedLayerOperation = bTargetIsHeightmap && UISettings->bCombinedLayersOperation && Landscape && Landscape->HasLayersContent(); + // Get list of verts to update FLandscapeBrushData BrushInfo = Brush->ApplyBrush(InteractorPositions); if (!BrushInfo) @@ -644,11 +825,9 @@ public: Y2 += 1; } - this->Cache.CacheData(X1, Y1, X2, Y2); - TArray Data; - this->Cache.GetCachedData(X1, Y1, X2, Y2, Data); - + LayerDataCache.Initialize(LandscapeInfo, bCombinedLayerOperation); + LayerDataCache.Read(X1, Y1, X2, Y2, Data); const float ToolStrength = FMath::Clamp(UISettings->ToolStrength * Pressure, 0.0f, 1.0f); // Apply the brush @@ -681,8 +860,8 @@ public: const int32 SampleX1 = X - XRadius; checkSlow(SampleX1 >= BrushInfo.GetBounds().Min.X); const int32 SampleY1 = Y - YRadius; checkSlow(SampleY1 >= BrushInfo.GetBounds().Min.Y); - const int32 SampleX2 = X + XRadius; checkSlow(SampleX2 < BrushInfo.GetBounds().Max.X); - const int32 SampleY2 = Y + YRadius; checkSlow(SampleY2 < BrushInfo.GetBounds().Max.Y); + const int32 SampleX2 = X + XRadius; checkSlow(SampleX2 < BrushInfo.GetBounds().Max.X); + const int32 SampleY2 = Y + YRadius; checkSlow(SampleY2 < BrushInfo.GetBounds().Max.Y); for (int32 SampleY = SampleY1; SampleY <= SampleY2; SampleY++) { const float* SampleBrushScanline = BrushInfo.GetDataPtr(FIntPoint(0, SampleY)); @@ -694,9 +873,9 @@ public: // constrain sample to within the brush, symmetrically to prevent flattening bug const float SampleBrushValue = FMath::Min( - FMath::Min(SampleBrushScanline [SampleX], SampleBrushScanline [X + (X - SampleX)]), + FMath::Min(SampleBrushScanline[SampleX], SampleBrushScanline[X + (X - SampleX)]), FMath::Min(SampleBrushScanline2[SampleX], SampleBrushScanline2[X + (X - SampleX)]) - ); + ); if (SampleBrushValue > 0.0f) { FilterValue += SampleDataScanline[SampleX]; @@ -713,8 +892,7 @@ public: } } - this->Cache.SetCachedData(X1, Y1, X2, Y2, Data, UISettings->PaintingRestriction); - this->Cache.Flush(); + LayerDataCache.Write(X1, Y1, X2, Y2, Data, UISettings->PaintingRestriction); } }; @@ -744,12 +922,14 @@ class FLandscapeToolStrokeFlatten : public FLandscapeToolStrokePaintBase LayerDataCache; public: FLandscapeToolStrokeFlatten(FEdModeLandscape* InEdMode, FEditorViewportClient* InViewportClient, const FLandscapeToolTarget& InTarget) : FLandscapeToolStrokePaintBase(InEdMode, InViewportClient, InTarget) , bInitializedFlattenValue(false) , bTargetIsHeightmap(InTarget.TargetType == ELandscapeToolTargetType::Heightmap) + , LayerDataCache(InTarget, this->Cache) { if (InEdMode->UISettings->bUseFlattenTarget && bTargetIsHeightmap) { @@ -763,10 +943,9 @@ public: void Apply(FEditorViewportClient* ViewportClient, FLandscapeBrush* Brush, const ULandscapeEditorObject* UISettings, const TArray& InteractorPositions) { if (!this->LandscapeInfo) return; - bool bLayerSystemFlattenHeightMode = this->LandscapeInfo->CanHaveLayersContent() && bTargetIsHeightmap; + ALandscape* Landscape = this->LandscapeInfo->LandscapeActor.Get(); - if (!Landscape && bLayerSystemFlattenHeightMode) - return; + const bool bCombinedLayerOperation = bTargetIsHeightmap && UISettings->bCombinedLayersOperation && Landscape && Landscape->HasLayersContent(); // Can't use slope if use Flatten Target because no normal is provided bool bUseSlopeFlatten = UISettings->bUseSlopeFlatten && !UISettings->bUseFlattenTarget; @@ -784,7 +963,7 @@ public: float V10 = 0.f; float V01 = 0.f; float V11 = 0.f; - if (bLayerSystemFlattenHeightMode) + if (bCombinedLayerOperation) { // Can't rely on cache in this mode FScopedSetLandscapeEditingLayer Scope(Landscape, FGuid()); @@ -808,7 +987,7 @@ public: if (bUseSlopeFlatten && bTargetIsHeightmap) { - if (bLayerSystemFlattenHeightMode) + if (bCombinedLayerOperation) { // Can't rely on cache in this mode FVector Vert00 = FVector(0.0f, 0.0f, V00); @@ -854,72 +1033,8 @@ public: } TArray Data; - TArray BottomLayersData; - FGuid EditingLayer; - uint8 EditingLayerIndex = MAX_uint8; - TSet AffectedComponents; - TArray BackupLayerVisibility; - auto& LayerSystemDataAccess = this->Cache.DataAccess; - - auto SynchronousUpdateHeightmapForComponents = [&](const TSet& InComponents) - { - for (ULandscapeComponent* Component : InComponents) - { - Component->RequestHeightmapUpdate(); - } - Landscape->ForceUpdateLayersContent(); - }; - - auto SetLayersVisibility = [&](const TArray& InLayerVisibility) - { - check(InLayerVisibility.Num() == Landscape->GetLayerCount()); - for (int i = 0; i < InLayerVisibility.Num(); ++i) - { - if (FLandscapeLayer* Layer = Landscape->GetLayer(i)) - { - Layer->bVisible = InLayerVisibility[i]; - } - } - }; - - auto SynchronousUpdateComponentVisibilityForHeight = [&](const TSet& InComponents, const TArray& InLayerVisibility) - { - SetLayersVisibility(InLayerVisibility); - SynchronousUpdateHeightmapForComponents(InComponents); - }; - - if (bLayerSystemFlattenHeightMode) - { - EditingLayer = Landscape->GetEditingLayer(); - this->LandscapeInfo->GetComponentsInRegion(X1, Y1, X2, Y2, AffectedComponents); - TArray NewLayerVisibility; - for (int i = 0; i < Landscape->GetLayerCount(); ++i) - { - FLandscapeLayer* CurrentLayer = Landscape->GetLayer(i); - BackupLayerVisibility.Add(CurrentLayer->bVisible); - NewLayerVisibility.Add((i > EditingLayerIndex) ? false : CurrentLayer->bVisible); - if (CurrentLayer->Guid == EditingLayer) - { - EditingLayerIndex = i; - } - } - check(EditingLayerIndex < Landscape->GetLayerCount()); - - FScopedSetLandscapeEditingLayer Scope(Landscape, FGuid()); - SynchronousUpdateComponentVisibilityForHeight(AffectedComponents, NewLayerVisibility); - Data.AddZeroed((X2 - X1 + 1) * (Y2 - Y1 + 1)); - LayerSystemDataAccess.GetDataFast(X1, Y1, X2, Y2, Data.GetData()); - - BottomLayersData.AddZeroed((X2 - X1 + 1) * (Y2 - Y1 + 1)); - NewLayerVisibility[EditingLayerIndex] = false; - SynchronousUpdateComponentVisibilityForHeight(AffectedComponents, NewLayerVisibility); - LayerSystemDataAccess.GetDataFast(X1, Y1, X2, Y2, BottomLayersData.GetData()); - } - else - { - this->Cache.CacheData(X1, Y1, X2, Y2); - this->Cache.GetCachedData(X1, Y1, X2, Y2, Data); - } + LayerDataCache.Initialize(LandscapeInfo, bCombinedLayerOperation); + LayerDataCache.Read(X1, Y1, X2, Y2, Data); // Apply the brush for (int32 Y = BrushInfo.GetBounds().Min.Y; Y < BrushInfo.GetBounds().Max.Y; Y++) @@ -1056,27 +1171,7 @@ public: } } - if (bLayerSystemFlattenHeightMode) - { - const float Alpha = Landscape->GetLayerAlpha(EditingLayerIndex, bTargetIsHeightmap); - const float InverseAlpha = (Alpha != 0.f) ? 1.f / Alpha : 1.f; - for (int i = 0; i < Data.Num(); ++i) - { - float Diff = (LandscapeDataAccess::GetLocalHeight(Data[i]) - LandscapeDataAccess::GetLocalHeight(BottomLayersData[i])) * InverseAlpha; - Data[i] = LandscapeDataAccess::GetTexHeight(Diff); - } - check(EditingLayer == Landscape->GetEditingLayer()); - - SetLayersVisibility(BackupLayerVisibility); - LayerSystemDataAccess.SetData(X1, Y1, X2, Y2, Data.GetData(), UISettings->PaintingRestriction); - LayerSystemDataAccess.Flush(); - SynchronousUpdateHeightmapForComponents(AffectedComponents); - } - else - { - this->Cache.SetCachedData(X1, Y1, X2, Y2, Data, UISettings->PaintingRestriction); - this->Cache.Flush(); - } + LayerDataCache.Write(X1, Y1, X2, Y2, Data, UISettings->PaintingRestriction); } }; @@ -1172,6 +1267,10 @@ public: virtual void EnterTool() override { FLandscapeToolPaintBase>::EnterTool(); + if (!this->EdMode->CurrentToolTarget.LandscapeInfo.Get()) + { + return; + } ALandscapeProxy* LandscapeProxy = this->EdMode->CurrentToolTarget.LandscapeInfo->GetLandscapeProxy(); MeshComponent = NewObject(LandscapeProxy, NAME_None, RF_Transient); @@ -1193,8 +1292,11 @@ public: { FLandscapeToolPaintBase>::ExitTool(); - MeshComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - MeshComponent->DestroyComponent(); + if (MeshComponent) + { + MeshComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + MeshComponent->DestroyComponent(); + } } }; diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeTools.h b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeTools.h index e37e32bb347f..1957c21533c3 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeTools.h +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeTools.h @@ -292,7 +292,7 @@ public: } // X2/Y2 Coordinates are "inclusive" max values - void CacheData(int32 X1, int32 Y1, int32 X2, int32 Y2) + void CacheData(int32 X1, int32 Y1, int32 X2, int32 Y2, bool bCacheOriginalData = true) { if (!Valid) { @@ -320,8 +320,10 @@ public: DataAccess.GetDataFast(CachedX1, CachedY1, CachedX2, CachedY2, CachedData); } - OriginalData = CachedData; - + if (bCacheOriginalData) + { + OriginalData = CachedData; + } Valid = true; } else @@ -344,7 +346,10 @@ public: DataAccess.GetDataFast(X1, CachedY1, CachedX1 - 1, CachedY2, CachedData); } - CacheOriginalData(X1, CachedY1, CachedX1 - 1, CachedY2); + if (bCacheOriginalData) + { + CacheOriginalData(X1, CachedY1, CachedX1 - 1, CachedY2); + } CachedX1 = X1; } @@ -364,7 +369,10 @@ public: { DataAccess.GetDataFast(CachedX2 + 1, CachedY1, X2, CachedY2, CachedData); } - CacheOriginalData(CachedX2 + 1, CachedY1, X2, CachedY2); + if (bCacheOriginalData) + { + CacheOriginalData(CachedX2 + 1, CachedY1, X2, CachedY2); + } CachedX2 = X2; } @@ -384,7 +392,10 @@ public: { DataAccess.GetDataFast(CachedX1, Y1, CachedX2, CachedY1 - 1, CachedData); } - CacheOriginalData(CachedX1, Y1, CachedX2, CachedY1 - 1); + if (bCacheOriginalData) + { + CacheOriginalData(CachedX1, Y1, CachedX2, CachedY1 - 1); + } CachedY1 = Y1; } @@ -404,8 +415,10 @@ public: { DataAccess.GetDataFast(CachedX1, CachedY2 + 1, CachedX2, Y2, CachedData); } - - CacheOriginalData(CachedX1, CachedY2 + 1, CachedX2, Y2); + if (bCacheOriginalData) + { + CacheOriginalData(CachedX1, CachedY2 + 1, CachedX2, Y2); + } CachedY2 = Y2; } } @@ -485,6 +498,25 @@ public: return Value == 0; } + bool HasCachedData(int32 X1, int32 Y1, int32 X2, int32 Y2) const + { + return (Valid && X1 >= CachedX1 && Y1 >= CachedY1 && X2 <= CachedX2 && Y2 <= CachedY2); + } + + template + bool GetDataAndCache(int32 X1, int32 Y1, int32 X2, int32 Y2, TArray& OutData, TGetCacheRegionFunction GetCacheRegion) + { + if (!HasCachedData(X1, Y1, X2, Y2)) + { + FIntRect Bounds = GetCacheRegion(); + check((Bounds.Min.X <= X1) && (Bounds.Min.Y <= Y1) && (Bounds.Max.X >= X2) && (Bounds.Max.Y >= Y2)); + const bool bCacheOriginalData = false; + CacheData(Bounds.Min.X, Bounds.Min.Y, Bounds.Max.X, Bounds.Max.Y, bCacheOriginalData); + } + ensure(HasCachedData(X1, Y1, X2, Y2)); + return GetCachedData(X1, Y1, X2, Y2, OutData); + } + // X2/Y2 Coordinates are "inclusive" max values bool GetCachedData(int32 X1, int32 Y1, int32 X2, int32 Y2, TArray& OutData) { @@ -521,7 +553,7 @@ public: } // X2/Y2 Coordinates are "inclusive" max values - void SetCachedData(int32 X1, int32 Y1, int32 X2, int32 Y2, TArray& Data, ELandscapeLayerPaintingRestriction PaintingRestriction = ELandscapeLayerPaintingRestriction::None) + void SetCachedData(int32 X1, int32 Y1, int32 X2, int32 Y2, TArray& Data, ELandscapeLayerPaintingRestriction PaintingRestriction = ELandscapeLayerPaintingRestriction::None, bool bUpdateData = true) { checkSlow(Data.Num() == (1 + Y2 - Y1) * (1 + X2 - X1)); @@ -534,8 +566,11 @@ public: } } - // Update real data - DataAccess.SetData(X1, Y1, X2, Y2, Data.GetData(), PaintingRestriction); + if (bUpdateData) + { + // Update real data + DataAccess.SetData(X1, Y1, X2, Y2, Data.GetData(), PaintingRestriction); + } } // Get the original data before we made any changes with the SetCachedData interface. diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorCommands.cpp b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorCommands.cpp index 88f2d2431355..e577170f6a16 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorCommands.cpp +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorCommands.cpp @@ -56,8 +56,8 @@ void FLandscapeEditorCommands::RegisterCommands() UI_COMMAND(VisibilityTool, "Tool - Visibility", "", EUserInterfaceActionType::RadioButton, FInputChord()); NameToCommandMap.Add("Tool_Visibility", VisibilityTool); - UI_COMMAND(BPCustomTool, "Tool - BP Custom", "", EUserInterfaceActionType::RadioButton, FInputChord()); - NameToCommandMap.Add("Tool_BPCustom", BPCustomTool); + UI_COMMAND(BlueprintBrushTool, "Tool - Blueprint Brush", "", EUserInterfaceActionType::RadioButton, FInputChord()); + NameToCommandMap.Add("Tool_BlueprintBrush", BlueprintBrushTool); UI_COMMAND(SelectComponentTool, "Tool - Component Selection", "", EUserInterfaceActionType::RadioButton, FInputChord()); NameToCommandMap.Add("Tool_Select", SelectComponentTool); diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorCommands.h b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorCommands.h index 28c92e2f6ad5..584520866b51 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorCommands.h +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorCommands.h @@ -44,7 +44,7 @@ public: TSharedPtr NoiseTool; TSharedPtr RetopologizeTool; TSharedPtr VisibilityTool; - TSharedPtr BPCustomTool; + TSharedPtr BlueprintBrushTool; TSharedPtr SelectComponentTool; TSharedPtr AddComponentTool; diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_Layers.cpp b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_Layers.cpp index dda43fc7f117..4baed5941e90 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_Layers.cpp +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_Layers.cpp @@ -127,19 +127,7 @@ void FLandscapeEditorCustomNodeBuilder_Layers::SetOnRebuildChildren(FSimpleDeleg void FLandscapeEditorCustomNodeBuilder_Layers::GenerateHeaderRowContent(FDetailWidgetRow& NodeRow) { - FEdModeLandscape* LandscapeEdMode = GetEditorMode(); - if (LandscapeEdMode == NULL) - { - return; - } - - NodeRow.NameWidget - [ - SNew(STextBlock) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Text(FText::FromString(TEXT("Layers"))) - ]; } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION @@ -359,6 +347,26 @@ void FLandscapeEditorCustomNodeBuilder_Layers::SetLayerName(const FText& InText, } } + +void FLandscapeEditorCustomNodeBuilder_Layers::FillAddBrushMenu(FMenuBuilder& MenuBuilder, TArray Brushes) +{ + for (ALandscapeBlueprintCustomBrush* Brush : Brushes) + { + if (Brush->GetOwningLandscape() == nullptr) + { + TSharedRef SharedThis = AsShared(); + FUIAction AddAction = FUIAction(FExecuteAction::CreateLambda([SharedThis, Brush]() { SharedThis->AddBrushToCurrentLayer(Brush); })); + MenuBuilder.AddMenuEntry(FText::FromString(Brush->GetActorLabel()), FText(), FSlateIcon(), AddAction); + } + } +} + +void FLandscapeEditorCustomNodeBuilder_Layers::AddBrushToCurrentLayer(ALandscapeBlueprintCustomBrush* Brush) +{ + const FScopedTransaction Transaction(LOCTEXT("LandscapeBrushAddToCurrentLayerTransaction", "Add brush to current layer")); + GetEditorMode()->AddBrushToCurrentLayer(Brush); +} + TSharedPtr FLandscapeEditorCustomNodeBuilder_Layers::OnLayerContextMenuOpening(int32 InLayerIndex) { FEdModeLandscape* LandscapeEdMode = GetEditorMode(); @@ -439,9 +447,25 @@ TSharedPtr FLandscapeEditorCustomNodeBuilder_Layers::OnLayerContextMenu } MenuBuilder.EndSection(); + const TArray& Brushes = LandscapeEdMode->GetBrushList(); + if (Brushes.ContainsByPredicate([](ALandscapeBlueprintCustomBrush* Brush) { return Brush->GetOwningLandscape() == nullptr; })) + { + MenuBuilder.BeginSection("LandscapeEditorBrushActions", LOCTEXT("LandscapeEditorBrushActions.Heading", "Brushes")); + { + MenuBuilder.AddSubMenu( + LOCTEXT("LandscaeEditorBrushAddSubMenu", "Add"), + LOCTEXT("LandscaeEditorBrushAddSubMenuToolTip", "Add brush to current layer"), + FNewMenuDelegate::CreateSP(this, &FLandscapeEditorCustomNodeBuilder_Layers::FillAddBrushMenu, Brushes), + false, + FSlateIcon() + ); + } + MenuBuilder.EndSection(); + } + return MenuBuilder.MakeWidget(); } - return NULL; + return nullptr; } void FLandscapeEditorCustomNodeBuilder_Layers::ForceUpdateSplines() @@ -717,7 +741,6 @@ FReply FLandscapeEditorCustomNodeBuilder_Layers::HandleAcceptDrop(FDragDropEvent { LandscapeEdMode->SetCurrentLayer(DestinationLayerIndex); LandscapeEdMode->RefreshDetailPanel(); - LandscapeEdMode->RequestLayersContentUpdateForceAll(); return FReply::Handled(); } } diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_Layers.h b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_Layers.h index cdfeb5db3c49..9e57e39ecaa2 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_Layers.h +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_Layers.h @@ -23,6 +23,7 @@ class FDetailWidgetRow; class IDetailChildrenBuilder; class IDetailLayoutBuilder; class SDragAndDropVerticalBox; +class ALandscapeBlueprintCustomBrush; /** * Slate widgets customizer for the layers list in the Landscape Editor @@ -97,6 +98,9 @@ protected: FReply OnToggleLock(int32 InLayerIndex); const FSlateBrush* GetLockBrushForLayer(int32 InLayerIndex) const; + void FillAddBrushMenu(FMenuBuilder& MenuBuilder, TArray Brushes); + void AddBrushToCurrentLayer(ALandscapeBlueprintCustomBrush* Brush); + private: /** Widgets for displaying and editing the layer name */ diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_LayersBrushStack.cpp b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_LayersBrushStack.cpp index 70f295e0b811..a7f21e163d0e 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_LayersBrushStack.cpp +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_LayersBrushStack.cpp @@ -45,10 +45,7 @@ void FLandscapeEditorDetailCustomization_LayersBrushStack::CustomizeDetails(IDet { const FName CurrentToolName = LandscapeEdMode->CurrentTool->GetToolName(); - if (LandscapeEdMode->CurrentToolMode->SupportedTargetTypes != 0 && CurrentToolName == TEXT("BPCustom")) - { - LayerCategory.AddCustomBuilder(MakeShareable(new FLandscapeEditorCustomNodeBuilder_LayersBrushStack(DetailBuilder.GetThumbnailPool().ToSharedRef()))); - } + LayerCategory.AddCustomBuilder(MakeShareable(new FLandscapeEditorCustomNodeBuilder_LayersBrushStack(DetailBuilder.GetThumbnailPool().ToSharedRef()))); } } END_SLATE_FUNCTION_BUILD_OPTIMIZATION @@ -76,26 +73,12 @@ void FLandscapeEditorCustomNodeBuilder_LayersBrushStack::SetOnRebuildChildren(FS void FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GenerateHeaderRowContent(FDetailWidgetRow& NodeRow) { - FEdModeLandscape* LandscapeEdMode = GetEditorMode(); - - if (LandscapeEdMode == NULL) - { - return; - } - - NodeRow.NameWidget - [ - SNew(STextBlock) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Text(FText::FromString(TEXT("Stack"))) - ]; } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder) { - FEdModeLandscape* LandscapeEdMode = GetEditorMode(); - if (LandscapeEdMode != NULL) + if (FEdModeLandscape* LandscapeEdMode = GetEditorMode()) { TSharedPtr BrushesList = SNew(SDragAndDropVerticalBox) .OnCanAcceptDrop(this, &FLandscapeEditorCustomNodeBuilder_LayersBrushStack::HandleCanAcceptDrop) @@ -109,7 +92,6 @@ void FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GenerateChildContent(ID .Visibility(EVisibility::Visible) [ SNew(SVerticalBox) - + SVerticalBox::Slot() .AutoHeight() .VAlign(VAlign_Center) @@ -117,34 +99,14 @@ void FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GenerateChildContent(ID [ BrushesList.ToSharedRef() ] - - + SVerticalBox::Slot() - .AutoHeight() - .VAlign(VAlign_Center) - .Padding(0, 2) - [ - SNew(SHorizontalBox) - - +SHorizontalBox::Slot() - .HAlign(HAlign_Right) - //.Padding(4, 0) - [ - SNew(SButton) - .Text(this, &FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GetCommitBrushesButtonText) - .OnClicked(this, &FLandscapeEditorCustomNodeBuilder_LayersBrushStack::ToggleCommitBrushes) - .IsEnabled(this, &FLandscapeEditorCustomNodeBuilder_LayersBrushStack::IsCommitBrushesButtonEnabled) - ] - ] ]; - if (LandscapeEdMode->CurrentToolMode != nullptr) + if (LandscapeEdMode->CurrentToolMode) { - const TArray& BrushOrderStack = LandscapeEdMode->GetBrushesOrderForCurrentLayer(LandscapeEdMode->CurrentToolTarget.TargetType); - - for (int32 i = 0; i < BrushOrderStack.Num(); ++i) + int32 BrushCount = LandscapeEdMode->GetBrushesForCurrentLayer().Num(); + for (int32 i = 0; i < BrushCount; ++i) { TSharedPtr GeneratedRowWidget = GenerateRow(i); - if (GeneratedRowWidget.IsValid()) { BrushesList->AddSlot() @@ -166,12 +128,29 @@ TSharedPtr FLandscapeEditorCustomNodeBuilder_LayersBrushStack::Generate TSharedPtr RowWidget = SNew(SLandscapeEditorSelectableBorder) .Padding(0) .VAlign(VAlign_Center) + .OnContextMenuOpening(this, &FLandscapeEditorCustomNodeBuilder_LayersBrushStack::OnBrushContextMenuOpening, InBrushIndex) .OnSelected(this, &FLandscapeEditorCustomNodeBuilder_LayersBrushStack::OnBrushSelectionChanged, InBrushIndex) .IsSelected(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FLandscapeEditorCustomNodeBuilder_LayersBrushStack::IsBrushSelected, InBrushIndex))) [ SNew(SHorizontalBox) - + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ContentPadding(0) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .OnClicked(this, &FLandscapeEditorCustomNodeBuilder_LayersBrushStack::OnToggleVisibility, InBrushIndex) + .ToolTipText(LOCTEXT("LandscapeBrushVisibility", "Toggle Brush Visibility")) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .Content() + [ + SNew(SImage) + .Image(this, &FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GetVisibilityBrush, InBrushIndex) + ] + ] + +SHorizontalBox::Slot() .VAlign(VAlign_Center) .Padding(4, 0) [ @@ -184,44 +163,167 @@ TSharedPtr FLandscapeEditorCustomNodeBuilder_LayersBrushStack::Generate SNew(STextBlock) .ColorAndOpacity(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GetBrushTextColor, InBrushIndex))) .Text(this, &FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GetBrushText, InBrushIndex) + .IsEnabled(true) ] ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0, 0, 4, 0) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ContentPadding(0) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .OnClicked(this, &FLandscapeEditorCustomNodeBuilder_LayersBrushStack::OnToggleAffectsHeightmap, InBrushIndex) + .ToolTipText(LOCTEXT("LandscapeBrushAffectsHeightmap", "Toggle Affects Heightmap")) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .Content() + [ + SNew(SImage) + .Image(this, &FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GetAffectsHeightmapBrush, InBrushIndex) + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ContentPadding(0) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .OnClicked(this, &FLandscapeEditorCustomNodeBuilder_LayersBrushStack::OnToggleAffectsWeightmap, InBrushIndex) + .ToolTipText(LOCTEXT("LandscapeBrushAffectsWeightmap", "Toggle Affects Weightmap")) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .Content() + [ + SNew(SImage) + .Image(this, &FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GetAffectsWeightmapBrush, InBrushIndex) + ] + ] ]; return RowWidget; } END_SLATE_FUNCTION_BUILD_OPTIMIZATION +void FLandscapeEditorCustomNodeBuilder_LayersBrushStack::RemoveBrush(ALandscapeBlueprintCustomBrush* Brush) +{ + const FScopedTransaction Transaction(LOCTEXT("LandscapeBrushRemoveTransaction", "Remove Brush")); + GetEditorMode()->RemoveBrushFromCurrentLayer(Brush); +} + +TSharedPtr FLandscapeEditorCustomNodeBuilder_LayersBrushStack::OnBrushContextMenuOpening(int32 InBrushIndex) +{ + if (ALandscapeBlueprintCustomBrush* CurrentBrush = GetBrush(InBrushIndex)) + { + FMenuBuilder MenuBuilder(true, NULL); + MenuBuilder.BeginSection("LandscapeEditorBrushActions", LOCTEXT("LandscapeEditorBrushActions.Heading", "Brushes")); + { + TSharedRef SharedThis = AsShared(); + + // Remove Brush + FUIAction RemoveAction = FUIAction(FExecuteAction::CreateLambda([SharedThis, CurrentBrush] { SharedThis->RemoveBrush(CurrentBrush); })); + MenuBuilder.AddMenuEntry(LOCTEXT("LandscapeBrushRemove", "Remove"), LOCTEXT("LandscapeBrushRemoveToolTip", "Remove brush from current layer"), FSlateIcon(), RemoveAction); + + + } + MenuBuilder.EndSection(); + return MenuBuilder.MakeWidget(); + } + + return nullptr; +} + +FReply FLandscapeEditorCustomNodeBuilder_LayersBrushStack::OnToggleAffectsHeightmap(int32 InBrushIndex) +{ + if (ALandscapeBlueprintCustomBrush* Brush = GetBrush(InBrushIndex)) + { + const FScopedTransaction Transaction(LOCTEXT("Landscape_Brush_AffectsHeightmap", "Set Brush Affects Heightmap")); + bool bAffectsHeightmap = Brush->IsAffectingHeightmap(); + Brush->SetAffectsHeightmap(!bAffectsHeightmap); + } + return FReply::Handled(); +} + +FReply FLandscapeEditorCustomNodeBuilder_LayersBrushStack::OnToggleAffectsWeightmap(int32 InBrushIndex) +{ + if (ALandscapeBlueprintCustomBrush* Brush = GetBrush(InBrushIndex)) + { + const FScopedTransaction Transaction(LOCTEXT("Landscape_Brush_AffectsWeightmap", "Set Brush Affects Weightmap")); + bool bAffectsWeightmap = Brush->IsAffectingWeightmap(); + Brush->SetAffectsWeightmap(!bAffectsWeightmap); + } + return FReply::Handled(); +} + +FReply FLandscapeEditorCustomNodeBuilder_LayersBrushStack::OnToggleVisibility(int32 InBrushIndex) +{ + if (ALandscapeBlueprintCustomBrush* Brush = GetBrush(InBrushIndex)) + { + const FScopedTransaction Transaction(LOCTEXT("Landscape_Brush_SetVisibility", "Set Brush Visibility")); + bool bVisible = Brush->IsVisible(); + Brush->SetIsVisible(!bVisible); + } + return FReply::Handled(); +} + +const FSlateBrush* FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GetAffectsWeightmapBrush(int32 InBrushIndex) const +{ + ALandscapeBlueprintCustomBrush* Brush = GetBrush(InBrushIndex); + return Brush && Brush->IsAffectingWeightmap() ? FEditorStyle::GetBrush("LandscapeEditor.Brush.AffectsWeight.Enabled") : FEditorStyle::GetBrush("LandscapeEditor.Brush.AffectsWeight.Disabled"); +} + +const FSlateBrush* FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GetAffectsHeightmapBrush(int32 InBrushIndex) const +{ + ALandscapeBlueprintCustomBrush* Brush = GetBrush(InBrushIndex); + return Brush && Brush->IsAffectingHeightmap() ? FEditorStyle::GetBrush("LandscapeEditor.Brush.AffectsHeight.Enabled") : FEditorStyle::GetBrush("LandscapeEditor.Brush.AffectsHeight.Disabled"); +} + +const FSlateBrush* FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GetVisibilityBrush(int32 InBrushIndex) const +{ + ALandscapeBlueprintCustomBrush* Brush = GetBrush(InBrushIndex); + bool bIsVisible = Brush && Brush->IsVisible(); + return bIsVisible ? FEditorStyle::GetBrush("Level.VisibleIcon16x") : FEditorStyle::GetBrush("Level.NotVisibleIcon16x"); +} + +bool FLandscapeEditorCustomNodeBuilder_LayersBrushStack::IsBrushEnabled(int32 InBrushIndex) const +{ + ALandscapeBlueprintCustomBrush* Brush = GetBrush(InBrushIndex); + const FEdModeLandscape* LandscapeEdMode = GetEditorMode(); + return Brush && ((Brush->IsAffectingHeightmap() && LandscapeEdMode->CurrentToolTarget.TargetType == ELandscapeToolTargetType::Type::Heightmap) || (Brush->IsAffectingWeightmap() && LandscapeEdMode->CurrentToolTarget.TargetType == ELandscapeToolTargetType::Type::Weightmap)); +} + bool FLandscapeEditorCustomNodeBuilder_LayersBrushStack::IsBrushSelected(int32 InBrushIndex) const { ALandscapeBlueprintCustomBrush* Brush = GetBrush(InBrushIndex); - - return Brush != nullptr ? Brush->IsSelected() : false; + return Brush && Brush->IsSelected(); } void FLandscapeEditorCustomNodeBuilder_LayersBrushStack::OnBrushSelectionChanged(int32 InBrushIndex) { FEdModeLandscape* LandscapeEdMode = GetEditorMode(); - - if (LandscapeEdMode != nullptr && LandscapeEdMode->AreAllBrushesCommitedToCurrentLayer(LandscapeEdMode->CurrentToolTarget.TargetType)) + if (!LandscapeEdMode || !LandscapeEdMode->CurrentToolMode) { return; } - ALandscapeBlueprintCustomBrush* Brush = GetBrush(InBrushIndex); - - if (Brush != nullptr && !Brush->IsCommited()) + const FName CurrentToolName = LandscapeEdMode->CurrentTool->GetToolName(); + if (CurrentToolName == TEXT("BlueprintBrush")) { - GEditor->SelectNone(true, true); - GEditor->SelectActor(Brush, true, true); + ALandscapeBlueprintCustomBrush* Brush = GetBrush(InBrushIndex); + if (Brush != nullptr && !Brush->IsCommited()) + { + FScopedTransaction Transaction(LOCTEXT("LandscapeBrushSelect", "Brush selection")); + GEditor->SelectNone(true, true); + GEditor->SelectActor(Brush, true, true); + } } } FText FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GetBrushText(int32 InBrushIndex) const { - ALandscapeBlueprintCustomBrush* Brush = GetBrush(InBrushIndex); - - if (Brush != nullptr) + if (ALandscapeBlueprintCustomBrush* Brush = GetBrush(InBrushIndex)) { return FText::FromString(Brush->GetActorLabel()); } @@ -231,9 +333,7 @@ FText FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GetBrushText(int32 InB FSlateColor FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GetBrushTextColor(int32 InBrushIndex) const { - ALandscapeBlueprintCustomBrush* Brush = GetBrush(InBrushIndex); - - if (Brush != nullptr) + if (ALandscapeBlueprintCustomBrush* Brush = GetBrush(InBrushIndex)) { return Brush->IsCommited() ? FSlateColor::UseSubduedForeground() : FSlateColor::UseForeground(); } @@ -243,85 +343,32 @@ FSlateColor FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GetBrushTextColo ALandscapeBlueprintCustomBrush* FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GetBrush(int32 InBrushIndex) const { - FEdModeLandscape* LandscapeEdMode = GetEditorMode(); - - if (LandscapeEdMode != nullptr) + if (FEdModeLandscape* LandscapeEdMode = GetEditorMode()) { - return LandscapeEdMode->GetBrushForCurrentLayer(LandscapeEdMode->CurrentToolTarget.TargetType, InBrushIndex); + return LandscapeEdMode->GetBrushForCurrentLayer(InBrushIndex); } return nullptr; } -FReply FLandscapeEditorCustomNodeBuilder_LayersBrushStack::ToggleCommitBrushes() -{ - FEdModeLandscape* LandscapeEdMode = GetEditorMode(); - - if (LandscapeEdMode != nullptr) - { - bool CommitBrushes = !LandscapeEdMode->AreAllBrushesCommitedToCurrentLayer(LandscapeEdMode->CurrentToolTarget.TargetType); - - if (CommitBrushes) - { - TArray BrushStack = LandscapeEdMode->GetBrushesForCurrentLayer(LandscapeEdMode->CurrentToolTarget.TargetType); - - for (ALandscapeBlueprintCustomBrush* Brush : BrushStack) - { - GEditor->SelectActor(Brush, false, true); - } - } - - LandscapeEdMode->SetBrushesCommitStateForCurrentLayer(LandscapeEdMode->CurrentToolTarget.TargetType, CommitBrushes); - } - - return FReply::Handled(); -} - -bool FLandscapeEditorCustomNodeBuilder_LayersBrushStack::IsCommitBrushesButtonEnabled() const -{ - FEdModeLandscape* LandscapeEdMode = GetEditorMode(); - - if (LandscapeEdMode != nullptr) - { - TArray BrushStack = LandscapeEdMode->GetBrushesForCurrentLayer(LandscapeEdMode->CurrentToolTarget.TargetType); - - return BrushStack.Num() > 0; - } - - return false; -} - -FText FLandscapeEditorCustomNodeBuilder_LayersBrushStack::GetCommitBrushesButtonText() const -{ - FEdModeLandscape* LandscapeEdMode = GetEditorMode(); - - if (LandscapeEdMode != nullptr) - { - return LandscapeEdMode->AreAllBrushesCommitedToCurrentLayer(LandscapeEdMode->CurrentToolTarget.TargetType) ? LOCTEXT("UnCommitBrushesText", "Uncommit") : LOCTEXT("CommitBrushesText", "Commit"); - } - - return FText::FromName(NAME_None); -} - FReply FLandscapeEditorCustomNodeBuilder_LayersBrushStack::HandleDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, int32 SlotIndex, SVerticalBox::FSlot* Slot) { - FEdModeLandscape* LandscapeEdMode = GetEditorMode(); - - if (LandscapeEdMode != nullptr) + if (FEdModeLandscape* LandscapeEdMode = GetEditorMode()) { - const TArray& BrushOrderStack = LandscapeEdMode->GetBrushesOrderForCurrentLayer(LandscapeEdMode->CurrentToolTarget.TargetType); - - if (BrushOrderStack.IsValidIndex(SlotIndex)) + FLandscapeLayer* Layer = LandscapeEdMode->GetCurrentLayer(); + if (Layer && !Layer->bLocked) { - TSharedPtr Row = GenerateRow(SlotIndex); - - if (Row.IsValid()) + const TArray& BrushStack = LandscapeEdMode->GetBrushesForCurrentLayer(); + if (BrushStack.IsValidIndex(SlotIndex)) { - return FReply::Handled().BeginDragDrop(FLandscapeListElementDragDropOp::New(SlotIndex, Slot, Row)); + TSharedPtr Row = GenerateRow(SlotIndex); + if (Row.IsValid()) + { + return FReply::Handled().BeginDragDrop(FLandscapeListElementDragDropOp::New(SlotIndex, Slot, Row)); + } } } } - return FReply::Unhandled(); } @@ -344,28 +391,16 @@ FReply FLandscapeEditorCustomNodeBuilder_LayersBrushStack::HandleAcceptDrop(FDra if (DragDropOperation.IsValid()) { FEdModeLandscape* LandscapeEdMode = GetEditorMode(); - - if (LandscapeEdMode != nullptr) + ALandscape* Landscape = LandscapeEdMode ? LandscapeEdMode->GetLandscape() : nullptr; + if (Landscape) { - TArray& BrushOrderStack = LandscapeEdMode->GetBrushesOrderForCurrentLayer(LandscapeEdMode->CurrentToolTarget.TargetType); - - if (BrushOrderStack.IsValidIndex(DragDropOperation->SlotIndexBeingDragged) && BrushOrderStack.IsValidIndex(SlotIndex)) + int32 StartingLayerIndex = DragDropOperation->SlotIndexBeingDragged; + int32 DestinationLayerIndex = SlotIndex; + const FScopedTransaction Transaction(LOCTEXT("Landscape_LayerBrushes_Reorder", "Reorder Layer Brush")); + if (Landscape->ReorderLayerBrush(LandscapeEdMode->GetCurrentLayerIndex(), StartingLayerIndex, DestinationLayerIndex)) { - int32 StartingLayerIndex = DragDropOperation->SlotIndexBeingDragged; - int32 DestinationLayerIndex = SlotIndex; - - if (StartingLayerIndex != INDEX_NONE && DestinationLayerIndex != INDEX_NONE) - { - int8 MovingBrushIndex = BrushOrderStack[StartingLayerIndex]; - - BrushOrderStack.RemoveAt(StartingLayerIndex); - BrushOrderStack.Insert(MovingBrushIndex, DestinationLayerIndex); - - LandscapeEdMode->RefreshDetailPanel(); - LandscapeEdMode->RequestLayersContentUpdateForceAll(); - - return FReply::Handled(); - } + LandscapeEdMode->RefreshDetailPanel(); + return FReply::Handled(); } } } diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_LayersBrushStack.h b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_LayersBrushStack.h index 448a668a8def..c78f9f41e9b0 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_LayersBrushStack.h +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_LayersBrushStack.h @@ -56,18 +56,25 @@ protected: static class FEdModeLandscape* GetEditorMode(); - TSharedPtr GenerateRow(int32 InLayerIndex); + TSharedPtr GenerateRow(int32 InBrushIndex); + TSharedPtr OnBrushContextMenuOpening(int32 InBrushIndex); + void RemoveBrush(ALandscapeBlueprintCustomBrush* Brush); + + FReply OnToggleVisibility(int32 InBrushIndex); + FReply OnToggleAffectsHeightmap(int32 InBrushIndex); + FReply OnToggleAffectsWeightmap(int32 InBrushIndex); + + const FSlateBrush* GetAffectsHeightmapBrush(int32 InBrushIndex) const; + const FSlateBrush* GetAffectsWeightmapBrush(int32 InBrushIndex) const; + const FSlateBrush* GetVisibilityBrush(int32 InBrushIndex) const; bool IsBrushSelected(int32 InBrushIndex) const; + bool IsBrushEnabled(int32 InBrushIndex) const; void OnBrushSelectionChanged(int32 InBrushIndex); FText GetBrushText(int32 InBrushIndex) const; FSlateColor GetBrushTextColor(int32 InBrushIndex) const; ALandscapeBlueprintCustomBrush* GetBrush(int32 InBrushIndex) const; - FReply ToggleCommitBrushes(); - bool IsCommitBrushesButtonEnabled() const; - FText GetCommitBrushesButtonText() const; - // Drag/Drop handling FReply HandleDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, int32 SlotIndex, SVerticalBox::FSlot* Slot); TOptional HandleCanAcceptDrop(const FDragDropEvent& DragDropEvent, SDragAndDropVerticalBox::EItemDropZone DropZone, SVerticalBox::FSlot* Slot); diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_NewLandscape.cpp b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_NewLandscape.cpp index 0e65cbbc3ab8..d4615a0fac39 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_NewLandscape.cpp +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_NewLandscape.cpp @@ -226,6 +226,10 @@ void FLandscapeEditorDetailCustomization_NewLandscape::CustomizeDetails(IDetailL .OnXCommitted_Static(&SetPropertyValue, PropertyHandle_Location_X) .OnYCommitted_Static(&SetPropertyValue, PropertyHandle_Location_Y) .OnZCommitted_Static(&SetPropertyValue, PropertyHandle_Location_Z) + .OnXChanged_Lambda([=](float NewValue) { ensure(PropertyHandle_Location_X->SetValue(NewValue, EPropertyValueSetFlags::InteractiveChange) == FPropertyAccess::Success); }) + .OnYChanged_Lambda([=](float NewValue) { ensure(PropertyHandle_Location_Y->SetValue(NewValue, EPropertyValueSetFlags::InteractiveChange) == FPropertyAccess::Success); }) + .OnZChanged_Lambda([=](float NewValue) { ensure(PropertyHandle_Location_Z->SetValue(NewValue, EPropertyValueSetFlags::InteractiveChange) == FPropertyAccess::Success); }) + .AllowSpin(true) ]; TSharedRef PropertyHandle_Rotation = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULandscapeEditorObject, NewLandscape_Rotation)); @@ -251,6 +255,7 @@ void FLandscapeEditorDetailCustomization_NewLandscape::CustomizeDetails(IDetailL .Yaw_Static(&GetOptionalPropertyValue, PropertyHandle_Rotation_Yaw) .OnYawCommitted_Static(&SetPropertyValue, PropertyHandle_Rotation_Yaw) // not allowed to roll or pitch landscape .OnYawChanged_Lambda([=](float NewValue){ ensure(PropertyHandle_Rotation_Yaw->SetValue(NewValue, EPropertyValueSetFlags::InteractiveChange) == FPropertyAccess::Success); }) + .AllowSpin(true) ]; TSharedRef PropertyHandle_Scale = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULandscapeEditorObject, NewLandscape_Scale)); @@ -276,6 +281,10 @@ void FLandscapeEditorDetailCustomization_NewLandscape::CustomizeDetails(IDetailL .OnXCommitted_Static(&SetScale, PropertyHandle_Scale_X) .OnYCommitted_Static(&SetScale, PropertyHandle_Scale_Y) .OnZCommitted_Static(&SetScale, PropertyHandle_Scale_Z) + .OnXChanged_Lambda([=](float NewValue) { ensure(PropertyHandle_Scale_X->SetValue(NewValue, EPropertyValueSetFlags::InteractiveChange) == FPropertyAccess::Success); }) + .OnYChanged_Lambda([=](float NewValue) { ensure(PropertyHandle_Scale_Y->SetValue(NewValue, EPropertyValueSetFlags::InteractiveChange) == FPropertyAccess::Success); }) + .OnZChanged_Lambda([=](float NewValue) { ensure(PropertyHandle_Scale_Z->SetValue(NewValue, EPropertyValueSetFlags::InteractiveChange) == FPropertyAccess::Success); }) + .AllowSpin(true) ]; TSharedRef PropertyHandle_QuadsPerSection = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULandscapeEditorObject, NewLandscape_QuadsPerSection)); diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_TargetLayers.cpp b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_TargetLayers.cpp index e0bda2ba6932..6dfe6682d218 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_TargetLayers.cpp +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_TargetLayers.cpp @@ -98,7 +98,7 @@ bool FLandscapeEditorDetailCustomization_TargetLayers::ShouldShowTargetLayers() //// Visible if there are possible choices //if (bSupportsWeightmap || bSupportsHeightmap || bSupportsVisibility) - if (LandscapeEdMode->CurrentToolMode->SupportedTargetTypes != 0 && CurrentToolName != TEXT("BPCustom")) + if (LandscapeEdMode->CurrentToolMode->SupportedTargetTypes != 0 && CurrentToolName != TEXT("BlueprintBrush")) { return true; } @@ -115,7 +115,7 @@ bool FLandscapeEditorDetailCustomization_TargetLayers::ShouldShowPaintingRestric { const FName CurrentToolName = LandscapeEdMode->CurrentTool->GetToolName(); - if ((LandscapeEdMode->CurrentToolTarget.TargetType == ELandscapeToolTargetType::Weightmap && CurrentToolName != TEXT("BPCustom")) + if ((LandscapeEdMode->CurrentToolTarget.TargetType == ELandscapeToolTargetType::Weightmap && CurrentToolName != TEXT("BlueprintBrush")) || LandscapeEdMode->CurrentToolTarget.TargetType == ELandscapeToolTargetType::Visibility) { return true; @@ -138,12 +138,9 @@ bool FLandscapeEditorDetailCustomization_TargetLayers::ShouldShowVisibilityTip() if (LandscapeEdMode->CurrentToolTarget.TargetType == ELandscapeToolTargetType::Visibility) { ALandscapeProxy* Proxy = LandscapeEdMode->CurrentToolTarget.LandscapeInfo->GetLandscapeProxy(); - UMaterialInterface* HoleMaterial = Proxy->GetLandscapeHoleMaterial(); - if (!HoleMaterial) - { - HoleMaterial = Proxy->GetLandscapeMaterial(); - } - if (!HoleMaterial->GetMaterial()->HasAnyExpressionsInMaterialAndFunctionsOfType()) + UMaterialInterface* HoleMaterial = Proxy->GetLandscapeMaterial(); + + if (HoleMaterial && !HoleMaterial->GetMaterial()->HasAnyExpressionsInMaterialAndFunctionsOfType()) { return true; } @@ -916,7 +913,7 @@ TSharedPtr FLandscapeEditorCustomNodeBuilder_TargetLayers::OnTargetLaye MenuBuilder.AddMenuEntry(LOCTEXT("LayerContextMenu.Import", "Import from file"), FText(), FSlateIcon(), ImportAction); // Reimport - const FString& ReimportPath = Target->ReimportFilePath(); + const FString& ReimportPath = Target->GetReimportFilePath(); if (!ReimportPath.IsEmpty()) { @@ -1014,7 +1011,7 @@ void FLandscapeEditorCustomNodeBuilder_TargetLayers::OnExportLayer(const TShared LandscapeInfo->ExportLayer(LayerInfoObj, SaveFilename); } - Target->ReimportFilePath() = SaveFilename; + Target->SetReimportFilePath(SaveFilename); } } } @@ -1069,7 +1066,7 @@ void FLandscapeEditorCustomNodeBuilder_TargetLayers::OnImportLayer(const TShared // Actually do the Import LandscapeEdMode->ImportData(*Target, OpenFilename); - Target->ReimportFilePath() = OpenFilename; + Target->SetReimportFilePath(OpenFilename); } } } diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetails.cpp b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetails.cpp index 62ceba13f706..b73986825ab2 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetails.cpp +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetails.cpp @@ -173,7 +173,7 @@ FText FLandscapeEditorDetails::GetLocalizedName(FString Name) if (LandscapeEdMode->CanHaveLandscapeLayersContent()) { - LOCTEXT("ToolSet_BPCustom", "Blueprint Custom"); + LOCTEXT("ToolSet_BlueprintBrush", "Blueprint Brushes"); } LOCTEXT("ToolSet_Select", "Selection"); @@ -336,7 +336,7 @@ TSharedRef FLandscapeEditorDetails::GetToolSelector() if (LandscapeEdMode->CanHaveLandscapeLayersContent()) { - MenuBuilder.AddToolButton(NameToCommandMap.FindChecked("Tool_BPCustom"), NAME_None, LOCTEXT("Tool.SculptBPCustom", "Blueprint Custom"), LOCTEXT("Tool.SculptBPCustom.Tooltip", "Custom sculpting tools created using Blueprint.")); + MenuBuilder.AddToolButton(NameToCommandMap.FindChecked("Tool_BlueprintBrush"), NAME_None, LOCTEXT("Tool.SculptBlueprintBrush", "Blueprint Brushes"), LOCTEXT("Tool.SculptBlueprintBrush.Tooltip", "Custom sculpting tools created using Blueprint.")); } MenuBuilder.EndSection(); @@ -358,7 +358,7 @@ TSharedRef FLandscapeEditorDetails::GetToolSelector() if (LandscapeEdMode->CanHaveLandscapeLayersContent()) { - MenuBuilder.AddToolButton(NameToCommandMap.FindChecked("Tool_BPCustom"), NAME_None, LOCTEXT("Tool.PaintBPCustom", "Blueprint Custom"), LOCTEXT("Tool.PaintBPCustom.Tooltip", "Custom painting tools created using Blueprint.")); + MenuBuilder.AddToolButton(NameToCommandMap.FindChecked("Tool_BlueprintBrush"), NAME_None, LOCTEXT("Tool.PaintBlueprintBrush", "Blueprint Brushes"), LOCTEXT("Tool.PaintBlueprintBrush.Tooltip", "Custom painting tools created using Blueprint.")); } MenuBuilder.EndSection(); diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorObject.cpp b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorObject.cpp index b20cbc13f96f..341405f9a4ce 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorObject.cpp +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorObject.cpp @@ -21,6 +21,7 @@ ULandscapeEditorObject::ULandscapeEditorObject(const FObjectInitializer& ObjectI , bUseWeightTargetValue(false) , WeightTargetValue(1.0f) , MaximumValueRadius(10000.0f) + , bCombinedLayersOperation(true) , FlattenMode(ELandscapeToolFlattenMode::Both) , bUseSlopeFlatten(false) diff --git a/Engine/Source/Editor/LandscapeEditor/Private/SLandscapeEditor.cpp b/Engine/Source/Editor/LandscapeEditor/Private/SLandscapeEditor.cpp index 75aef72575f8..a163cbcdd31a 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/SLandscapeEditor.cpp +++ b/Engine/Source/Editor/LandscapeEditor/Private/SLandscapeEditor.cpp @@ -104,7 +104,7 @@ void FLandscapeToolKit::Init(const TSharedPtr& InitToolkitHost) MAP_TOOL("Noise"); MAP_TOOL("Retopologize"); MAP_TOOL("Visibility"); - MAP_TOOL("BPCustom"); + MAP_TOOL("BlueprintBrush"); MAP_TOOL("Select"); MAP_TOOL("AddComponent"); @@ -525,11 +525,18 @@ bool SLandscapeEditor::GetIsPropertyVisible(const FPropertyAndParent& PropertyAn return false; } } - if (Property.HasMetaData("ShowForBPCustomTool")) + if (Property.HasMetaData("ShowForBlueprintBrushTool")) { const FName CurrentToolName = LandscapeEdMode->CurrentTool->GetToolName(); - if (CurrentToolName != TEXT("BPCustom")) + if (CurrentToolName != TEXT("BlueprintBrush")) + { + return false; + } + } + if (Property.HasMetaData("ShowForLandscapeLayerSystem")) + { + if (!LandscapeEdMode->HasLandscapeLayersContent()) { return false; } diff --git a/Engine/Source/Editor/LandscapeEditor/Public/LandscapeEditorObject.h b/Engine/Source/Editor/LandscapeEditor/Public/LandscapeEditorObject.h index 5eba3abb8e47..7ee55e4326da 100644 --- a/Engine/Source/Editor/LandscapeEditor/Public/LandscapeEditorObject.h +++ b/Engine/Source/Editor/LandscapeEditor/Public/LandscapeEditorObject.h @@ -266,6 +266,9 @@ class ULandscapeEditorObject : public UObject UPROPERTY(NonTransactional) float MaximumValueRadius; + UPROPERTY(Category="Tool Settings", EditAnywhere, NonTransactional, meta=(ShowForTools="Flatten,Smooth", ShowForTargetTypes="Heightmap", ShowForLandscapeLayerSystem)) + bool bCombinedLayersOperation; + // Flatten Tool: // Whether to flatten by lowering, raising, both or terracing @@ -448,9 +451,9 @@ class ULandscapeEditorObject : public UObject UPROPERTY(Category="Tool Settings", EditAnywhere, NonTransactional, meta=(DisplayName="Smoothing Width", ShowForTools="Mirror", ClampMin="0", UIMin="0", UIMax="20")) int32 MirrorSmoothingWidth; - // BP Custom Tool + // Blueprint Brush Tool - UPROPERTY(Category = "Tool Settings", EditAnywhere, Transient, meta = (DisplayName = "Blueprint Brush", ShowForTools = "BPCustom")) + UPROPERTY(Category = "Tool Settings", EditAnywhere, Transient, meta = (DisplayName = "Blueprint Brush", ShowForTools = "BlueprintBrush")) TSubclassOf BlueprintCustomBrush; // Resize Landscape Tool diff --git a/Engine/Source/Editor/LevelEditor/Private/DlgDeltaTransform.cpp b/Engine/Source/Editor/LevelEditor/Private/DlgDeltaTransform.cpp index fc9f6689268c..ab1f64265575 100644 --- a/Engine/Source/Editor/LevelEditor/Private/DlgDeltaTransform.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/DlgDeltaTransform.cpp @@ -72,6 +72,7 @@ public: .Z( this, &SDlgDeltaTransform::GetDeltaZ ) .bColorAxisLabels( true ) .AllowResponsiveLayout( true ) + .AllowSpin( false ) .OnXCommitted( this, &SDlgDeltaTransform::OnSetDelta, 0 ) .OnYCommitted( this, &SDlgDeltaTransform::OnSetDelta, 1 ) .OnZCommitted( this, &SDlgDeltaTransform::OnSetDelta, 2 ) diff --git a/Engine/Source/Editor/LevelEditor/Private/LevelEditorGenericDetails.cpp b/Engine/Source/Editor/LevelEditor/Private/LevelEditorGenericDetails.cpp index 0b31a06d5cd0..f029d95fc3ea 100644 --- a/Engine/Source/Editor/LevelEditor/Private/LevelEditorGenericDetails.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/LevelEditorGenericDetails.cpp @@ -22,8 +22,7 @@ #include "SurfaceIterators.h" #include "ScopedTransaction.h" #include "FunctionalTest.h" - -#include "PropertyCustomizationHelpers.h" +#include "MaterialList.h" #define LOCTEXT_NAMESPACE "FLevelEditorGenericDetails" diff --git a/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.cpp b/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.cpp index 7350be6ec70c..be530ea25e82 100644 --- a/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.cpp @@ -2206,11 +2206,13 @@ TSharedRef< SWidget > FLevelEditorToolBar::GenerateOpenBlueprintMenuContent( TSh } }; + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); + TSharedPtr Extender = FExtender::Combine(LevelEditorModule.GetAllLevelEditorToolbarBlueprintsMenuExtenders()); const bool bShouldCloseWindowAfterMenuSelection = true; - FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, InCommandList ); + FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, InCommandList, Extender); - MenuBuilder.BeginSection(NAME_None, LOCTEXT("BlueprintClass", "Blueprint Class")); + MenuBuilder.BeginSection("BlueprintClass", LOCTEXT("BlueprintClass", "Blueprint Class")); { // Create a blank BP MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().CreateBlankBlueprintClass); @@ -2230,7 +2232,7 @@ TSharedRef< SWidget > FLevelEditorToolBar::GenerateOpenBlueprintMenuContent( TSh } MenuBuilder.EndSection(); - MenuBuilder.BeginSection(NAME_None, LOCTEXT("LevelScriptBlueprints", "Level Blueprints")); + MenuBuilder.BeginSection("LevelScriptBlueprints", LOCTEXT("LevelScriptBlueprints", "Level Blueprints")); { MenuBuilder.AddMenuEntry( FLevelEditorCommands::Get().OpenLevelBlueprint ); @@ -2247,7 +2249,7 @@ TSharedRef< SWidget > FLevelEditorToolBar::GenerateOpenBlueprintMenuContent( TSh } MenuBuilder.EndSection(); - MenuBuilder.BeginSection(NAME_None, LOCTEXT("ProjectSettingsClasses", "Project Settings")); + MenuBuilder.BeginSection("ProjectSettingsClasses", LOCTEXT("ProjectSettingsClasses", "Project Settings")); { // If source control is enabled, queue up a query to the status of the config file so it is (hopefully) ready before we get to the sub-menu if(ISourceControlModule::Get().IsEnabled()) @@ -2261,7 +2263,7 @@ TSharedRef< SWidget > FLevelEditorToolBar::GenerateOpenBlueprintMenuContent( TSh } MenuBuilder.EndSection(); - MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldSettingsClasses", "World Override")); + MenuBuilder.BeginSection("WorldSettingsClasses", LOCTEXT("WorldSettingsClasses", "World Override")); { LevelEditorActionHelpers::CreateGameModeSubMenu(MenuBuilder, InCommandList, InLevelEditor, false); } diff --git a/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.cpp b/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.cpp index 46e227ceac0c..48451478a8d7 100644 --- a/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.cpp @@ -632,6 +632,10 @@ TSharedRef SLevelEditor::SpawnLevelEditorTab( const FSpawnTabArgs& Arg ]; } + else if (TabIdentifier == FEditorModeTools::EditorModeToolbarTabName) + { + return GLevelEditorModeTools().MakeModeToolbarTab(); + } else if( TabIdentifier == TEXT("LevelEditorSelectionDetails") || TabIdentifier == TEXT("LevelEditorSelectionDetails2") || TabIdentifier == TEXT("LevelEditorSelectionDetails3") || TabIdentifier == TEXT("LevelEditorSelectionDetails4") ) { TSharedRef DetailsPanel = SummonDetailsPanel( TabIdentifier ); @@ -671,6 +675,7 @@ TSharedRef SLevelEditor::SpawnLevelEditorTab( const FSpawnTabArgs& Arg else if( TabIdentifier == TEXT("LevelEditorSceneOutliner") ) { SceneOutliner::FInitializationOptions InitOptions; + InitOptions.bShowTransient = true; InitOptions.Mode = ESceneOutlinerMode::ActorBrowsing; { TWeakPtr WeakLevelEditor = SharedThis(this); @@ -826,6 +831,11 @@ TSharedRef SLevelEditor::SpawnLevelEditorTab( const FSpawnTabArgs& Arg return SNew(SDockTab); } +bool SLevelEditor::CanSpawnEditorModeToolbarTab(const FSpawnTabArgs& Args) const +{ + return GLevelEditorModeTools().ShouldShowModeToolbar(); +} + TSharedRef SLevelEditor::InvokeTab( FName TabID ) { TSharedPtr LevelEditorTabManager = GetTabManager(); @@ -1034,7 +1044,7 @@ TSharedRef SLevelEditor::RestoreContentArea( const TSharedRef { const FSlateIcon ToolbarIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Toolbar"); - LevelEditorTabManager->RegisterTabSpawner( "LevelEditorToolBar", FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorToolBar"), FString()) ) + LevelEditorTabManager->RegisterTabSpawner( "LevelEditorToolBar", FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorToolBar"), FString())) .SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorToolBar", "Toolbar")) .SetTooltipText(NSLOCTEXT("LevelEditorTabs", "LevelEditorToolBarTooltipText", "Open the Toolbar tab, which provides access to the most common / important actions.")) .SetGroup( MenuStructure.GetLevelEditorCategory() ) @@ -1070,12 +1080,22 @@ TSharedRef SLevelEditor::RestoreContentArea( const TSharedRef .SetIcon( DetailsIcon ); } - const FSlateIcon ToolsIcon( FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Modes" ); - LevelEditorTabManager->RegisterTabSpawner( "LevelEditorToolBox", FOnSpawnTab::CreateSP( this, &SLevelEditor::SpawnLevelEditorTab, FName( "LevelEditorToolBox" ), FString() ) ) - .SetDisplayName( NSLOCTEXT( "LevelEditorTabs", "LevelEditorToolBox", "Modes" ) ) - .SetTooltipText( NSLOCTEXT( "LevelEditorTabs", "LevelEditorToolBoxTooltipText", "Open the Modes tab, which specifies all the available editing modes." ) ) - .SetGroup( MenuStructure.GetLevelEditorCategory() ) - .SetIcon( ToolsIcon ); + { + + const FSlateIcon ToolsIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Modes"); + LevelEditorTabManager->RegisterTabSpawner("LevelEditorToolBox", FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorToolBox"), FString())) + .SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorToolBox", "Modes Panel")) + .SetTooltipText(NSLOCTEXT("LevelEditorTabs", "LevelEditorToolBoxTooltipText", "Open the Modes tab, which specifies all the available editing modes.")) + .SetGroup(MenuStructure.GetLevelEditorModesCategory()) + .SetIcon(ToolsIcon); + + FCanSpawnTab CanSpawnTabDelegate = FCanSpawnTab::CreateSP(this, &SLevelEditor::CanSpawnEditorModeToolbarTab); + LevelEditorTabManager->RegisterTabSpawner(FEditorModeTools::EditorModeToolbarTabName, FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FEditorModeTools::EditorModeToolbarTabName, FString()), CanSpawnTabDelegate) + .SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorModesTab", "Active Mode Tools")) + .SetTooltipText(NSLOCTEXT("LevelEditorTabs", "LevelEditorModesTabTooltipText", "Open the modes toolbar which has the active mode's tools")) + .SetGroup(MenuStructure.GetLevelEditorModesCategory()) + .SetIcon(ToolsIcon); + } { const FSlateIcon OutlinerIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Outliner"); @@ -1203,6 +1223,12 @@ TSharedRef SLevelEditor::RestoreContentArea( const TSharedRef ->AddTab("LevelEditorToolBar", ETabState::OpenedTab) ) ->Split + ( + FTabManager::NewStack() + ->SetHideTabWell(true) + ->AddTab(FEditorModeTools::EditorModeToolbarTabName, ETabState::ClosedTab) + ) + ->Split ( FTabManager::NewStack() ->SetHideTabWell(true) @@ -1244,6 +1270,10 @@ TSharedRef SLevelEditor::RestoreContentArea( const TSharedRef )); FLayoutExtender LayoutExtender; + + // Make a layout extension for the mode toolbar. This avoids bumping level editor layout version to add a mandatory tab + LayoutExtender.ExtendLayout(FName("LevelEditorToolBar"), ELayoutExtensionPosition::Below, FTabManager::FTab(FEditorModeTools::EditorModeToolbarTabName, ETabState::ClosedTab)); + LevelEditorModule.OnRegisterLayoutExtensions().Broadcast(LayoutExtender); Layout->ProcessExtensions(LayoutExtender); diff --git a/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.h b/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.h index 2eb78385606b..40e5743f7fad 100644 --- a/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.h +++ b/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.h @@ -138,6 +138,8 @@ public: private: TSharedRef SpawnLevelEditorTab(const FSpawnTabArgs& Args, FName TabIdentifier, FString InitializationPayload); + bool CanSpawnEditorModeToolbarTab(const FSpawnTabArgs& Args) const; + //TSharedRef SpawnLevelEditorModeTab(const FSpawnTabArgs& Args, FEdMode* EditorMode); TSharedRef SummonDetailsPanel( FName Identifier ); diff --git a/Engine/Source/Editor/LevelEditor/Private/SLevelViewport.cpp b/Engine/Source/Editor/LevelEditor/Private/SLevelViewport.cpp index ffe0a97bd9e3..6c9cee938d88 100644 --- a/Engine/Source/Editor/LevelEditor/Private/SLevelViewport.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/SLevelViewport.cpp @@ -295,12 +295,12 @@ void SLevelViewport::ConstructViewportOverlayContent() .AutoHeight() .Padding(2.0f, 1.0f, 2.0f, 1.0f) [ - SNew(SHorizontalBox) + SNew(SVerticalBox) .Visibility(this, &SLevelViewport::GetSelectedActorsCurrentLevelTextVisibility) // Current level label - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 1.0f, 2.0f, 1.0f) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(6.0f, 1.0f, 2.0f, 1.0f) [ SNew(STextBlock) .Text(this, &SLevelViewport::GetSelectedActorsCurrentLevelText, true) @@ -308,9 +308,9 @@ void SLevelViewport::ConstructViewportOverlayContent() .ShadowOffset(FVector2D(1, 1)) ] // Current level - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(4.0f, 1.0f, 2.0f, 1.0f) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(6.0f, 1.0f, 2.0f, 1.0f) [ SNew(STextBlock) .Text(this, &SLevelViewport::GetSelectedActorsCurrentLevelText, false) @@ -333,7 +333,7 @@ void SLevelViewport::ConstructViewportOverlayContent() .VAlign(VAlign_Center) .ButtonStyle(FEditorStyle::Get(), "EditorViewportToolBar.MenuButton") .OnClicked(this, &SLevelViewport::OnMenuClicked) - .Visibility(this, &SLevelViewport::GetCurrentLevelTextVisibility) + .Visibility(this, &SLevelViewport::GetCurrentLevelButtonVisibility) [ SNew(SHorizontalBox) .Visibility(this, &SLevelViewport::GetCurrentLevelTextVisibility) @@ -3568,7 +3568,20 @@ EVisibility SLevelViewport::GetCurrentLevelTextVisibility() const { ContentVisibility = EVisibility::SelfHitTestInvisible; } - return (&GetLevelViewportClient() == GCurrentLevelEditingViewportClient) && !IsPlayInEditorViewportActive() ? ContentVisibility : EVisibility::Collapsed; + return (&GetLevelViewportClient() == GCurrentLevelEditingViewportClient) + && !IsPlayInEditorViewportActive() + && GetWorld() && GetWorld()->GetCurrentLevel()->OwningWorld->GetLevels().Num() > 1 + ? ContentVisibility : EVisibility::Collapsed; +} + +EVisibility SLevelViewport::GetCurrentLevelButtonVisibility() const +{ + EVisibility TextVisibility = GetCurrentLevelTextVisibility(); + if (TextVisibility == EVisibility::SelfHitTestInvisible) + { + TextVisibility = EVisibility::Visible; + } + return TextVisibility; } EVisibility SLevelViewport::GetSelectedActorsCurrentLevelTextVisibility() const @@ -3578,7 +3591,11 @@ EVisibility SLevelViewport::GetSelectedActorsCurrentLevelTextVisibility() const { ContentVisibility = EVisibility::SelfHitTestInvisible; } - return (&GetLevelViewportClient() == GCurrentLevelEditingViewportClient) && (GEditor->GetSelectedActorCount() > 0) && !IsPlayInEditorViewportActive() ? ContentVisibility : EVisibility::Collapsed; + return (&GetLevelViewportClient() == GCurrentLevelEditingViewportClient) + && (GEditor->GetSelectedActorCount() > 0) + && !IsPlayInEditorViewportActive() + && GetWorld() && GetWorld()->GetCurrentLevel()->OwningWorld->GetLevels().Num() > 1 + ? ContentVisibility : EVisibility::Collapsed; } FText SLevelViewport::GetSelectedActorsCurrentLevelText(bool bDrawOnlyLabel) const diff --git a/Engine/Source/Editor/LevelEditor/Private/SLevelViewportToolBar.cpp b/Engine/Source/Editor/LevelEditor/Private/SLevelViewportToolBar.cpp index f558e0cfadb4..e83e4eb105dd 100644 --- a/Engine/Source/Editor/LevelEditor/Private/SLevelViewportToolBar.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/SLevelViewportToolBar.cpp @@ -628,7 +628,7 @@ TSharedRef SLevelViewportToolBar::GenerateDevicePreviewMenu() const // Default menu - clear all settings { - FUIAction Action( FExecuteAction::CreateSP( this, &SLevelViewportToolBar::SetLevelProfile, FString( "Default" ) ), + FUIAction Action( FExecuteAction::CreateSP( const_cast(this), &SLevelViewportToolBar::SetLevelProfile, FString( "Default" ) ), FCanExecuteAction(), FIsActionChecked::CreateSP( ViewportRef, &SLevelViewport::IsDeviceProfileStringSet, FString( "Default" ) ) ); DeviceMenuBuilder.AddMenuEntry( LOCTEXT("DevicePreviewMenuClear", "Off"), FText::GetEmpty(), FSlateIcon(), Action, NAME_None, EUserInterfaceActionType::Button ); @@ -651,7 +651,7 @@ TSharedRef SLevelViewportToolBar::GenerateDevicePreviewMenu() const { const FName PlatformIcon = UIManager->GetDeviceIconName( CurItem ); - FUIAction Action( FExecuteAction::CreateSP( this, &SLevelViewportToolBar::SetLevelProfile, CurItem ), + FUIAction Action( FExecuteAction::CreateSP( const_cast(this), &SLevelViewportToolBar::SetLevelProfile, CurItem ), FCanExecuteAction(), FIsActionChecked::CreateSP( ViewportRef, &SLevelViewport::IsDeviceProfileStringSet, CurItem ) ); DeviceMenuBuilder.AddMenuEntry( FText::FromString( CurItem ), FText(), FSlateIcon(FEditorStyle::GetStyleSetName(), PlatformIcon), Action, NAME_None, EUserInterfaceActionType::Button ); @@ -675,7 +675,7 @@ TSharedRef SLevelViewportToolBar::GenerateDevicePreviewMenu() const DeviceMenuBuilder.AddSubMenu( FText::FromString( PlatformNameStr ), FText::GetEmpty(), - FNewMenuDelegate::CreateRaw( this, &SLevelViewportToolBar::MakeDevicePreviewSubMenu, DeviceProfiles ), + FNewMenuDelegate::CreateRaw( const_cast(this), &SLevelViewportToolBar::MakeDevicePreviewSubMenu, DeviceProfiles ), false, FSlateIcon(FEditorStyle::GetStyleSetName(), PlatformIcon) ); @@ -1062,7 +1062,7 @@ TSharedRef SLevelViewportToolBar::GenerateFOVMenu() const .MinValue(FOVMin) .MaxValue(FOVMax) .Value( this, &SLevelViewportToolBar::OnGetFOVValue ) - .OnValueChanged( this, &SLevelViewportToolBar::OnFOVValueChanged ) + .OnValueChanged( const_cast(this), &SLevelViewportToolBar::OnFOVValueChanged ) ] ]; } @@ -1114,7 +1114,7 @@ TSharedRef SLevelViewportToolBar::GenerateScreenPercentageMenu() const .MinValue(PreviewScreenPercentageMin) .MaxValue(PreviewScreenPercentageMax) .Value(this, &SLevelViewportToolBar::OnGetScreenPercentageValue) - .OnValueChanged(this, &SLevelViewportToolBar::OnScreenPercentageValueChanged) + .OnValueChanged(const_cast(this), &SLevelViewportToolBar::OnScreenPercentageValueChanged) ] ]; } @@ -1152,7 +1152,7 @@ TSharedRef SLevelViewportToolBar::GenerateFarViewPlaneMenu() const .MaxValue(100000.0f) .Font(FEditorStyle::GetFontStyle(TEXT("MenuItem.Font"))) .Value(this, &SLevelViewportToolBar::OnGetFarViewPlaneValue) - .OnValueChanged(this, &SLevelViewportToolBar::OnFarViewPlaneValueChanged) + .OnValueChanged(const_cast(this), &SLevelViewportToolBar::OnFarViewPlaneValueChanged) ] ]; } @@ -1218,7 +1218,7 @@ static TMap> GroupFoliageByOuter(const TArray SSurfaceProperties::GetLightmapResolutionValue() const { float LightMapScale = 0.0f; diff --git a/Engine/Source/Editor/LevelEditor/Private/SSurfaceProperties.h b/Engine/Source/Editor/LevelEditor/Private/SSurfaceProperties.h index 77f66862a974..b997fc64ce47 100644 --- a/Engine/Source/Editor/LevelEditor/Private/SSurfaceProperties.h +++ b/Engine/Source/Editor/LevelEditor/Private/SSurfaceProperties.h @@ -54,6 +54,7 @@ private: // FGCObject Interface virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + virtual FString GetReferencerName() const override; /** * Creates Pan texture controls diff --git a/Engine/Source/Editor/LevelEditor/Public/LevelEditor.h b/Engine/Source/Editor/LevelEditor/Public/LevelEditor.h index ff3d5b48eeb6..9693d03d9d8c 100644 --- a/Engine/Source/Editor/LevelEditor/Public/LevelEditor.h +++ b/Engine/Source/Editor/LevelEditor/Public/LevelEditor.h @@ -231,6 +231,7 @@ public: virtual TArray& GetAllLevelEditorToolbarSourceControlMenuExtenders() { return LevelEditorToolbarSourceControlMenuExtenders; } virtual TArray& GetAllLevelEditorToolbarCreateMenuExtenders() { return LevelEditorToolbarCreateMenuExtenders; } virtual TArray& GetAllLevelEditorToolbarPlayMenuExtenders() { return LevelEditorToolbarPlayMenuExtenders; } + virtual TArray>& GetAllLevelEditorToolbarBlueprintsMenuExtenders() { return LevelEditorToolbarBlueprintsMenuExtenders; } virtual TArray>& GetAllLevelEditorToolbarCinematicsMenuExtenders() {return LevelEditorToolbarCinematicsMenuExtenders;} virtual TArray& GetAllLevelEditorLevelMenuExtenders() { return LevelEditorLevelMenuExtenders; } @@ -384,6 +385,7 @@ private: TArray LevelEditorToolbarCreateMenuExtenders; TArray LevelEditorToolbarPlayMenuExtenders; TArray> LevelEditorToolbarCinematicsMenuExtenders; + TArray> LevelEditorToolbarBlueprintsMenuExtenders; TArray LevelEditorLevelMenuExtenders; /* Pointer to the current level Editor instance */ diff --git a/Engine/Source/Editor/LevelEditor/Public/SLevelViewport.h b/Engine/Source/Editor/LevelEditor/Public/SLevelViewport.h index 1d269fd2d96c..deb9bd6c555a 100644 --- a/Engine/Source/Editor/LevelEditor/Public/SLevelViewport.h +++ b/Engine/Source/Editor/LevelEditor/Public/SLevelViewport.h @@ -295,6 +295,9 @@ public: /** @return The visibility of the current level text display */ virtual EVisibility GetCurrentLevelTextVisibility() const; + /** @return The visibility of the current level button display */ + virtual EVisibility GetCurrentLevelButtonVisibility() const; + /** @return The visibility of the current level text display */ virtual EVisibility GetSelectedActorsCurrentLevelTextVisibility() const; diff --git a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditingLibrary.cpp b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditingLibrary.cpp index a215f0170140..7fd5ac35df28 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditingLibrary.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditingLibrary.cpp @@ -26,6 +26,7 @@ #include "EditorSupportDelegates.h" #include "Misc/RuntimeErrors.h" #include "SceneTypes.h" +#include "AssetRegistryModule.h" DEFINE_LOG_CATEGORY_STATIC(LogMaterialEditingLibrary, Warning, All); @@ -563,6 +564,16 @@ bool UMaterialEditingLibrary::SetMaterialUsage(UMaterial* Material, EMaterialUsa return bResult; } +bool UMaterialEditingLibrary::HasMaterialUsage(UMaterial* Material, EMaterialUsage Usage) +{ + bool bResult = false; + if (Material) + { + bResult = Material->GetUsageByFlag(Usage); + } + return bResult; +} + bool UMaterialEditingLibrary::ConnectMaterialProperty(UMaterialExpression* FromExpression, FString FromOutputName, EMaterialProperty Property) { bool bResult = false; @@ -648,6 +659,47 @@ void UMaterialEditingLibrary::LayoutMaterialExpressions(UMaterial* Material) MaterialEditingLibraryImpl::LayoutMaterialExpressions( Material ); } +float UMaterialEditingLibrary::GetMaterialDefaultScalarParameterValue(UMaterial* Material, FName ParameterName) +{ + float Result = 0.f; + if (Material) + { + Material->GetScalarParameterDefaultValue(ParameterName, Result); + } + return Result; +} + +UTexture* UMaterialEditingLibrary::GetMaterialDefaultTextureParameterValue(UMaterial* Material, FName ParameterName) +{ + UTexture* Result = nullptr; + if (Material) + { + Material->GetTextureParameterDefaultValue(ParameterName, Result); + } + return Result; +} + +FLinearColor UMaterialEditingLibrary::GetMaterialDefaultVectorParameterValue(UMaterial* Material, FName ParameterName) +{ + FLinearColor Result = FLinearColor::Black; + if (Material) + { + Material->GetVectorParameterDefaultValue(ParameterName, Result); + } + return Result; +} + +bool UMaterialEditingLibrary::GetMaterialDefaultStaticSwitchParameterValue(UMaterial* Material, FName ParameterName) +{ + bool bResult = false; + if (Material) + { + FGuid OutGuid; + Material->GetStaticSwitchParameterDefaultValue(ParameterName, bResult, OutGuid); + } + return bResult; +} + ////////////////////////////////////////////////////////////////////////// int32 UMaterialEditingLibrary::GetNumMaterialExpressionsInFunction(const UMaterialFunction* MaterialFunction) @@ -888,6 +940,17 @@ bool UMaterialEditingLibrary::SetMaterialInstanceVectorParameterValue(UMaterialI } +bool UMaterialEditingLibrary::GetMaterialInstanceStaticSwitchParameterValue(UMaterialInstanceConstant* Instance, FName ParameterName) +{ + bool bResult = false; + if (Instance) + { + FGuid OutGuid; + Instance->GetStaticSwitchParameterValue(ParameterName, bResult, OutGuid); + } + return bResult; +} + void UMaterialEditingLibrary::UpdateMaterialInstance(UMaterialInstanceConstant* Instance) { if (Instance) @@ -903,4 +966,26 @@ void UMaterialEditingLibrary::UpdateMaterialInstance(UMaterialInstanceConstant* FEditorDelegates::RefreshEditor.Broadcast(); FEditorSupportDelegates::RedrawAllViewports.Broadcast(); } -} \ No newline at end of file +} + +void UMaterialEditingLibrary::GetChildInstances(UMaterialInterface* Parent, TArray< TSoftObjectPtr >& ChildInstances) +{ + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + FAssetData ParentData = FAssetData(Parent); + TArray AssetList; + TMultiMap TagsAndValues; + FString ParentNameString = ParentData.GetExportTextName(); + TagsAndValues.Add(GET_MEMBER_NAME_CHECKED(UMaterialInstance, Parent), ParentNameString); + AssetRegistryModule.Get().GetAssetsByTagValues(TagsAndValues, AssetList); + + for (const FAssetData& MatInstRef : AssetList) + { + if (UMaterialInstance* MaterialInstance = Cast(MatInstRef.GetAsset())) + { + if (MaterialInstance->Parent == Parent) + { + ChildInstances.Add(MaterialInstance); + } + } + } +} diff --git a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.cpp b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.cpp index e24b2e6218da..7544f82cc32c 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.cpp @@ -11,6 +11,8 @@ #include "Widgets/Layout/SSeparator.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Input/SButton.h" +#include "Widgets/Input/SNumericEntryBox.h" +#include "Widgets/Input/SCheckBox.h" #include "EditorStyleSet.h" #include "EdGraph/EdGraph.h" #include "MaterialGraph/MaterialGraph.h" @@ -121,6 +123,8 @@ #include "Materials/MaterialExpressionMaterialLayerOutput.h" #include "MaterialStats.h" +#include "Materials/MaterialExpression.h" + #include "SMaterialParametersOverviewWidget.h" #include "IPropertyRowGenerator.h" @@ -724,23 +728,27 @@ void FMaterialEditor::InitMaterialEditor( const EToolkitMode::Type Mode, const T FMaterialEditor::FMaterialEditor() : bMaterialDirty(false) , bStatsFromPreviewMaterial(false) - , Material(NULL) - , OriginalMaterial(NULL) - , ExpressionPreviewMaterial(NULL) - , EmptyMaterial(NULL) - , PreviewExpression(NULL) - , MaterialFunction(NULL) - , OriginalMaterialObject(NULL) - , EditorOptions(NULL) - , ScopedTransaction(NULL) + , Material(nullptr) + , OriginalMaterial(nullptr) + , ExpressionPreviewMaterial(nullptr) + , EmptyMaterial(nullptr) + , PreviewExpression(nullptr) + , MaterialFunction(nullptr) + , OriginalMaterialObject(nullptr) + , EditorOptions(nullptr) + , ScopedTransaction(nullptr) , bAlwaysRefreshAllPreviews(false) , bHideUnusedConnectors(false) , bLivePreview(true) , bIsRealtime(false) , bShowBuiltinStats(false) + , bHideUnrelatedNodes(false) + , bLockNodeFadeState(false) + , bFocusWholeChain(false) + , bSelectRegularNode(false) , MenuExtensibilityManager(new FExtensibilityManager) , ToolBarExtensibilityManager(new FExtensibilityManager) - , MaterialEditorInstance(NULL) + , MaterialEditorInstance(nullptr) { } @@ -1127,6 +1135,21 @@ void FMaterialEditor::FillToolbar(FToolBarBuilder& ToolbarBuilder) ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ToggleLivePreview); ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ToggleRealtimeExpressions); ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().AlwaysRefreshAllPreviews); + ToolbarBuilder.AddToolBarButton( + FMaterialEditorCommands::Get().ToggleHideUnrelatedNodes, + NAME_None, + TAttribute(), + TAttribute(), + FSlateIcon(FEditorStyle::GetStyleSetName(), "GraphEditor.ToggleHideUnrelatedNodes") + ); + ToolbarBuilder.AddComboButton( + FUIAction(), + FOnGetContent::CreateSP(this, &FMaterialEditor::MakeHideUnrelatedNodesOptionsMenu), + LOCTEXT("HideUnrelatedNodesOptions", "Hide Unrelated Nodes Options"), + LOCTEXT("HideUnrelatedNodesOptionsMenu", "Hide Unrelated Nodes options menu"), + TAttribute(), + true + ); } ToolbarBuilder.EndSection(); @@ -1537,6 +1560,11 @@ void FMaterialEditor::LoadEditorSettings() PreviewViewport->OnToggleRealtime(); } } + + if (EditorOptions->bHideUnrelatedNodes) + { + ToggleHideUnrelatedNodes(); + } // Primitive type int32 PrimType; @@ -1558,7 +1586,8 @@ void FMaterialEditor::SaveEditorSettings() EditorOptions->bHideUnusedConnectors = !IsOnShowConnectorsChecked(); EditorOptions->bAlwaysRefreshAllPreviews = IsOnAlwaysRefreshAllPreviews(); EditorOptions->bRealtimeExpressionViewport = IsToggleRealTimeExpressionsChecked(); - EditorOptions->bLivePreviewUpdate = IsToggleLivePreviewChecked(); + EditorOptions->bLivePreviewUpdate = IsToggleLivePreviewChecked(); + EditorOptions->bHideUnrelatedNodes = bHideUnrelatedNodes; EditorOptions->SaveConfig(); } @@ -2286,6 +2315,12 @@ void FMaterialEditor::BindCommands() FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleLivePreviewChecked)); + ToolkitCommands->MapAction( + Commands.ToggleHideUnrelatedNodes, + FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleHideUnrelatedNodes), + FCanExecuteAction(), + FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleHideUnrelatedNodesChecked)); + ToolkitCommands->MapAction( Commands.ToggleRealtimeExpressions, FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleRealTimeExpressions), @@ -2475,6 +2510,203 @@ bool FMaterialEditor::IsOnAlwaysRefreshAllPreviews() const return bAlwaysRefreshAllPreviews == true; } +void FMaterialEditor::ToggleHideUnrelatedNodes() +{ + bHideUnrelatedNodes = !bHideUnrelatedNodes; + + GraphEditor->ResetAllNodesUnrelatedStates(); + + if (bHideUnrelatedNodes && bSelectRegularNode) + { + HideUnrelatedNodes(); + } + else + { + bLockNodeFadeState = false; + bFocusWholeChain = false; + } +} + +bool FMaterialEditor::IsToggleHideUnrelatedNodesChecked() const +{ + return bHideUnrelatedNodes == true; +} + +void FMaterialEditor::CollectDownstreamNodes(UMaterialGraphNode* CurrentNode, TArray& CollectedNodes) +{ + TArray OutputPins; + CurrentNode->GetOutputPins(OutputPins); + + for (auto& OutputPin : OutputPins) + { + for (auto& Link : OutputPin->LinkedTo) + { + UMaterialGraphNode* LinkedNode = Cast(Link->GetOwningNode()); + if (LinkedNode && !CollectedNodes.Contains(LinkedNode)) + { + CollectedNodes.Add(LinkedNode); + CollectDownstreamNodes( LinkedNode, CollectedNodes ); + + if (bFocusWholeChain) + { + CollectUpstreamNodes( LinkedNode, CollectedNodes ); + } + } + } + } +} + +void FMaterialEditor::CollectUpstreamNodes(UMaterialGraphNode* CurrentNode, TArray& CollectedNodes) +{ + TArray InputPins; + CurrentNode->GetInputPins(InputPins); + + for (auto& InputPin : InputPins) + { + for (auto& Link : InputPin->LinkedTo) + { + UMaterialGraphNode* LinkedNode = Cast(Link->GetOwningNode()); + if (LinkedNode && !CollectedNodes.Contains(LinkedNode)) + { + CollectedNodes.Add(LinkedNode); + CollectUpstreamNodes( LinkedNode, CollectedNodes ); + } + } + } +} + +void FMaterialEditor::HideUnrelatedNodes() +{ + TArray NodesToShow; + + const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); + + for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) + { + UMaterialGraphNode* SelectedNode = Cast(*NodeIt); + + if (SelectedNode) + { + NodesToShow.Add(SelectedNode); + CollectDownstreamNodes( SelectedNode, NodesToShow ); + CollectUpstreamNodes( SelectedNode, NodesToShow ); + } + } + + TArray AllNodes = GraphEditor->GetCurrentGraph()->Nodes; + + TArray CommentNodes; + TArray RelatedNodes; + + for (auto& Node : AllNodes) + { + // Always draw the root graph node which can't cast to UMaterialGraphNode + if (UMaterialGraphNode* GraphNode = Cast(Node)) + { + if (NodesToShow.Contains(GraphNode)) + { + Node->SetNodeUnrelated(false); + RelatedNodes.Add(Node); + } + else + { + Node->SetNodeUnrelated(true); + } + } + else if (UMaterialGraphNode_Comment* CommentNode = Cast(Node)) + { + CommentNodes.Add(Node); + } + } + + GraphEditor->FocusCommentNodes(CommentNodes, RelatedNodes); +} + +TSharedRef FMaterialEditor::MakeHideUnrelatedNodesOptionsMenu() +{ + const bool bShouldCloseWindowAfterMenuSelection = true; + FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, GetToolkitCommands() ); + + TSharedRef OptionsHeading = SNew(SBox) + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(LOCTEXT("HideUnrelatedOptions", "Hide Unrelated Options")) + .TextStyle(FEditorStyle::Get(), "Menu.Heading") + ] + ]; + + TSharedRef LockNodeStateCheckBox = SNew(SBox) + [ + SNew(SCheckBox) + .IsChecked(bLockNodeFadeState ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + .OnCheckStateChanged(this, &FMaterialEditor::OnLockNodeStateCheckStateChanged) + .Style(FEditorStyle::Get(), "Menu.CheckBox") + .ToolTipText(LOCTEXT("LockNodeStateCheckBoxToolTip", "Lock the current state of all nodes.")) + .Content() + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .Padding(2.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("LockNodeState", "Lock Node State")) + ] + ] + ]; + + TSharedRef FocusWholeChainCheckBox = SNew(SBox) + [ + SNew(SCheckBox) + .IsChecked(bFocusWholeChain ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + .OnCheckStateChanged(this, &FMaterialEditor::OnFocusWholeChainCheckStateChanged) + .Style(FEditorStyle::Get(), "Menu.CheckBox") + .ToolTipText(LOCTEXT("FocusWholeChainCheckBoxToolTip", "Focus all nodes in the chain.")) + .Content() + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .Padding(2.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("FocusWholeChain", "Focus Whole Chain")) + ] + ] + ]; + + MenuBuilder.AddWidget(OptionsHeading, FText::GetEmpty(), true); + + MenuBuilder.AddMenuEntry(FUIAction(), LockNodeStateCheckBox); + + MenuBuilder.AddMenuEntry(FUIAction(), FocusWholeChainCheckBox); + + return MenuBuilder.MakeWidget(); +} + +void FMaterialEditor::OnLockNodeStateCheckStateChanged(ECheckBoxState NewCheckedState) +{ + bLockNodeFadeState = (NewCheckedState == ECheckBoxState::Checked) ? true : false; +} + +void FMaterialEditor::OnFocusWholeChainCheckStateChanged(ECheckBoxState NewCheckedState) +{ + bFocusWholeChain = (NewCheckedState == ECheckBoxState::Checked) ? true : false; + + if (bHideUnrelatedNodes && !bLockNodeFadeState && bSelectRegularNode) + { + GraphEditor->ResetAllNodesUnrelatedStates(); + + HideUnrelatedNodes(); + } +} + + void FMaterialEditor::OnUseCurrentTexture() { // Set the currently selected texture in the generic browser @@ -3993,6 +4225,13 @@ void FMaterialEditor::UpdateMaterialAfterGraphChange() RegenerateCodeView(); RefreshExpressionPreviews(); SetMaterialDirty(); + + if (bHideUnrelatedNodes && !bLockNodeFadeState && bSelectRegularNode) + { + GraphEditor->ResetAllNodesUnrelatedStates(); + + HideUnrelatedNodes(); + } } int32 FMaterialEditor::GetNumberOfSelectedNodes() const @@ -4000,7 +4239,7 @@ int32 FMaterialEditor::GetNumberOfSelectedNodes() const return GraphEditor->GetSelectedNodes().Num(); } -FMaterialRenderProxy* FMaterialEditor::GetExpressionPreview(UMaterialExpression* InExpression) +FMatExpressionPreview* FMaterialEditor::GetExpressionPreview(UMaterialExpression* InExpression) { bool bNewlyCreated; return GetExpressionPreview(InExpression, bNewlyCreated); @@ -4737,6 +4976,7 @@ void FMaterialEditor::OnSelectedNodesChanged(const TSet& NewSele EditObject = MaterialFunction; } + bSelectRegularNode = false; if( NewSelection.Num() == 0 ) { SelectedObjects.Add(EditObject); @@ -4747,6 +4987,7 @@ void FMaterialEditor::OnSelectedNodesChanged(const TSet& NewSele { if (UMaterialGraphNode* GraphNode = Cast(*SetIt)) { + bSelectRegularNode = true; SelectedObjects.Add(GraphNode->MaterialExpression); } else if (UMaterialGraphNode_Comment* CommentNode = Cast(*SetIt)) @@ -4762,6 +5003,16 @@ void FMaterialEditor::OnSelectedNodesChanged(const TSet& NewSele GetDetailView()->SetObjects( SelectedObjects, true ); FocusDetailsPanel(); + + if (bHideUnrelatedNodes && !bLockNodeFadeState) + { + GraphEditor->ResetAllNodesUnrelatedStates(); + + if (bSelectRegularNode) + { + HideUnrelatedNodes(); + } + } } void FMaterialEditor::OnNodeDoubleClicked(class UEdGraphNode* Node) diff --git a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.h b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.h index acde63533b51..7ad06192c70e 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.h +++ b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.h @@ -34,6 +34,7 @@ class UFactory; class UMaterialEditorOptions; class UMaterialExpressionComment; class UMaterialInstance; +class UMaterialGraphNode; struct FGraphAppearanceInfo; /** @@ -44,6 +45,7 @@ class FMatExpressionPreview : public FMaterial, public FMaterialRenderProxy public: FMatExpressionPreview() : FMaterial() + , UnrelatedNodesOpacity(1.0f) { // Register this FMaterial derivative with AddEditorLoadedMaterialResource since it does not have a corresponding UMaterialInterface FMaterial::AddEditorLoadedMaterialResource(this); @@ -52,6 +54,7 @@ public: FMatExpressionPreview(UMaterialExpression* InExpression) : FMaterial() + , UnrelatedNodesOpacity(1.0f) , Expression(InExpression) { FMaterial::AddEditorLoadedMaterialResource(this); @@ -150,7 +153,7 @@ public: virtual bool IsSpecialEngineMaterial() const override { return false; } virtual bool IsWireframe() const override { return false; } virtual bool IsMasked() const override { return false; } - virtual enum EBlendMode GetBlendMode() const override { return BLEND_Opaque; } + virtual enum EBlendMode GetBlendMode() const override { return BLEND_Translucent; } virtual FMaterialShadingModelField GetShadingModels() const override { return MSM_Unlit; } virtual float GetOpacityMaskClipValue() const override { return 0.5f; } virtual bool GetCastDynamicShadowAsMasked() const override { return false; } @@ -172,6 +175,8 @@ public: return Ar << V.Expression; } + float UnrelatedNodesOpacity; + private: TWeakObjectPtr Expression; TArray ReferencedTextures; @@ -363,7 +368,7 @@ public: virtual bool CanPasteNodes() const override; virtual void PasteNodesHere(const FVector2D& Location) override; virtual int32 GetNumberOfSelectedNodes() const override; - virtual FMaterialRenderProxy* GetExpressionPreview(UMaterialExpression* InExpression) override; + virtual FMatExpressionPreview* GetExpressionPreview(UMaterialExpression* InExpression) override; virtual void DeleteNodes(const TArray& NodesToDelete) override; @@ -585,6 +590,20 @@ private: bool IsOnAlwaysRefreshAllPreviews() const; /** Command for the stats button */ + /** Make nodes which are unrelated to the selected nodes fade out */ + void ToggleHideUnrelatedNodes(); + bool IsToggleHideUnrelatedNodesChecked() const; + void CollectDownstreamNodes(UMaterialGraphNode* CurrentNode, TArray& CollectedNodes); + void CollectUpstreamNodes(UMaterialGraphNode* CurrentNode, TArray& CollectedNodes); + void HideUnrelatedNodes(); + + /** Make a drop down menu to control the opacity of unrelated nodes */ + TSharedRef MakeHideUnrelatedNodesOptionsMenu(); + TOptional HandleUnrelatedNodesOpacityBoxValue() const; + void HandleUnrelatedNodesOpacityBoxChanged(float NewOpacity); + void OnLockNodeStateCheckStateChanged(ECheckBoxState NewCheckedState); + void OnFocusWholeChainCheckStateChanged(ECheckBoxState NewCheckedState); + void ToggleReleaseStats(); bool IsToggleReleaseStatsChecked() const; void ToggleBuiltinStats(); @@ -787,6 +806,18 @@ private: /** If true, show stats for an empty material. Helps artists to judge the cost of their changes to the graph. */ bool bShowBuiltinStats; + /** If true, fade out nodes which are unrelated to the selected nodes automatically. */ + bool bHideUnrelatedNodes; + + /** Lock the current fade state of each node */ + bool bLockNodeFadeState; + + /** Focus all nodes in the same output chain */ + bool bFocusWholeChain; + + /** If a regular node (not a comment node, not the output node) has been selected */ + bool bSelectRegularNode; + /** Command list for this editor */ TSharedPtr GraphEditorCommands; diff --git a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditorActions.cpp b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditorActions.cpp index e39192fbd7cb..89c56933f375 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditorActions.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditorActions.cpp @@ -30,7 +30,8 @@ void FMaterialEditorCommands::RegisterCommands() UI_COMMAND( ToggleRealtimeExpressions, "Live Nodes", "Toggles real time update of the graph canvas.", EUserInterfaceActionType::ToggleButton, FInputChord() ); UI_COMMAND( AlwaysRefreshAllPreviews, "Live Update", "All nodes are previewed live.", EUserInterfaceActionType::ToggleButton, FInputChord() ); UI_COMMAND( ToggleMaterialStats, "Stats", "Toggles displaying of the material's stats.", EUserInterfaceActionType::ToggleButton, FInputChord()); - UI_COMMAND(TogglePlatformStats, "Platform Stats", "Toggles the window that shows material stats and compilation errors for multiple platforms.", EUserInterfaceActionType::ToggleButton, FInputChord() ); + UI_COMMAND( TogglePlatformStats, "Platform Stats", "Toggles the window that shows material stats and compilation errors for multiple platforms.", EUserInterfaceActionType::ToggleButton, FInputChord() ); + UI_COMMAND( ToggleHideUnrelatedNodes, "Hide Unrelated", "Toggles hiding nodes which are unrelated to the selected nodes automatically.", EUserInterfaceActionType::ToggleButton, FInputChord() ); UI_COMMAND( NewComment, "New Comment", "Creates a new comment node.", EUserInterfaceActionType::Button, FInputChord() ); UI_COMMAND( MatertialPasteHere, "Paste Here", "Pastes copied items at this location.", EUserInterfaceActionType::Button, FInputChord() ); UI_COMMAND( UseCurrentTexture, "Use Current Texture", "Uses the current texture selected in the content browser.", EUserInterfaceActionType::Button, FInputChord() ); diff --git a/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorStatsWidget.cpp b/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorStatsWidget.cpp index 9d633e3f7503..7639eb906745 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorStatsWidget.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorStatsWidget.cpp @@ -570,11 +570,11 @@ void SMaterialEditorStatsWidget::Construct(const FArguments& InArgs) const auto VerticalScrollbar = SNew(SScrollBar) .Orientation(Orient_Vertical) - .Thickness(FVector2D(7.0f, 7.0f)); + .Thickness(FVector2D(11.0f, 11.0f)); const auto HorizontalScrollbar = SNew(SScrollBar) .Orientation(Orient_Horizontal) - .Thickness(FVector2D(7.0f, 7.0f)); + .Thickness(FVector2D(11.0f, 11.0f)); this->ChildSlot [ diff --git a/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorViewport.cpp b/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorViewport.cpp index 152f6eb2ea4b..3cf8a115be1b 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorViewport.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorViewport.cpp @@ -29,6 +29,7 @@ #include "AdvancedPreviewScene.h" #include "AssetViewerSettings.h" #include "Engine/PostProcessVolume.h" +#include "Widgets/Input/SCheckBox.h" #define LOCTEXT_NAMESPACE "MaterialEditor" @@ -890,7 +891,7 @@ void SMaterialEditorUIPreviewZoomer::SetPreviewMaterial(UMaterialInterface* InPr void SMaterialEditorUIPreviewViewport::Construct( const FArguments& InArgs, UMaterialInterface* PreviewMaterial ) { - + bRealtime = false; ChildSlot [ SNew( SVerticalBox ) @@ -905,6 +906,21 @@ void SMaterialEditorUIPreviewViewport::Construct( const FArguments& InArgs, UMat SNew( SHorizontalBox ) + SHorizontalBox::Slot() .VAlign(VAlign_Center) + .Padding( 9.f ) + .AutoWidth() + [ + SNew( SCheckBox ) + .CheckBoxContentUsesAutoWidth( true ) + .OnCheckStateChanged(this, &SMaterialEditorUIPreviewViewport::OnRealtimeChanged) + .IsChecked(this, &SMaterialEditorUIPreviewViewport::IsRealtimeChecked) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("Realtime", "Realtime")) + ] + ] + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) .Padding( 3.f ) .AutoWidth() [ @@ -1005,4 +1021,32 @@ void SMaterialEditorUIPreviewViewport::OnPreviewYCommitted( int32 NewValue, ETex OnPreviewYChanged( NewValue ); } +void SMaterialEditorUIPreviewViewport::OnRealtimeChanged(ECheckBoxState State) +{ + if (State == ECheckBoxState::Checked) + { + bRealtime = true; + ActiveTimerHandle = RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SMaterialEditorUIPreviewViewport::EnsureTick)); + } + else + { + bRealtime = false; + if (ActiveTimerHandle.IsValid()) + { + UnRegisterActiveTimer(ActiveTimerHandle.Pin().ToSharedRef()); + } + } +} + +ECheckBoxState SMaterialEditorUIPreviewViewport::IsRealtimeChecked() const +{ + return bRealtime ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +EActiveTimerReturnType SMaterialEditorUIPreviewViewport::EnsureTick(double InCurrentTime, float InDeltaTime) +{ + // Keep the timer going if we're realtime + return bRealtime ? EActiveTimerReturnType::Continue : EActiveTimerReturnType::Stop; +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorViewport.h b/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorViewport.h index e0f3ab94b1ab..c9aa1e9f32ff 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorViewport.h +++ b/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorViewport.h @@ -148,7 +148,13 @@ private: void OnPreviewYCommitted( int32 NewValue, ETextCommit::Type ); TOptional OnGetPreviewXValue() const { return PreviewSize.X; } TOptional OnGetPreviewYValue() const { return PreviewSize.Y; } + void OnRealtimeChanged(ECheckBoxState State); + ECheckBoxState IsRealtimeChecked() const; + EActiveTimerReturnType EnsureTick(double InCurrentTime, float InDeltaTime); + private: FIntPoint PreviewSize; TSharedPtr PreviewZoomer; + bool bRealtime; + TWeakPtr ActiveTimerHandle; }; diff --git a/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.cpp b/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.cpp index 4cb2a2b0d223..1af08c2b2678 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.cpp @@ -376,6 +376,7 @@ void SMaterialParametersOverviewTree::Construct(const FArguments& InArgs) .OnGenerateRow(this, &SMaterialParametersOverviewTree::OnGenerateRowMaterialLayersFunctionsTreeView) .OnGetChildren(this, &SMaterialParametersOverviewTree::OnGetChildrenMaterialLayersFunctionsTreeView) .OnExpansionChanged(this, &SMaterialParametersOverviewTree::OnExpansionChanged) + .ExternalScrollbar(InArgs._InScrollbar) ); } @@ -621,7 +622,6 @@ void SMaterialParametersOverviewPanel::Refresh() [ SNew(SVerticalBox) +SVerticalBox::Slot() - .AutoHeight() [ SNew(SBorder) .BorderImage(this, &SMaterialParametersOverviewPanel::GetBackgroundImage) @@ -646,22 +646,36 @@ void SMaterialParametersOverviewPanel::Refresh() ] + SVerticalBox::Slot() .Padding(FMargin(3.0f, 2.0f, 3.0f, 3.0f)) - .AutoHeight() [ SNew(SBorder) .BorderImage(FEditorStyle::GetBrush("DetailsView.CategoryTop")) [ - SNew(SWidgetSwitcher) - .WidgetIndex(this, &SMaterialParametersOverviewPanel::GetPanelIndex) - +SWidgetSwitcher::Slot() + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Fill) [ - SNew(STextBlock) - .Text(LOCTEXT("AddParams", "Add parameters to see them here.")) + SNew(SWidgetSwitcher) + .WidgetIndex(this, &SMaterialParametersOverviewPanel::GetPanelIndex) + +SWidgetSwitcher::Slot() + [ + SNew(STextBlock) + .Text(LOCTEXT("AddParams", "Add parameters to see them here.")) + ] + +SWidgetSwitcher::Slot() + [ + NestedTree.ToSharedRef() + ] ] - +SWidgetSwitcher::Slot() + + SHorizontalBox::Slot() + .HAlign(HAlign_Right) + .AutoWidth() [ - NestedTree.ToSharedRef() - ] + SNew(SBox) + .WidthOverride(16.0f) + [ + ExternalScrollbar.ToSharedRef() + ] + ] ] ] ] @@ -713,13 +727,15 @@ void SMaterialParametersOverviewPanel::Refresh() void SMaterialParametersOverviewPanel::Construct(const FArguments& InArgs) { + ExternalScrollbar = SNew(SScrollBar); + NestedTree = SNew(SMaterialParametersOverviewTree) .InMaterialEditorInstance(InArgs._InMaterialEditorInstance) - .InOwner(SharedThis(this)); + .InOwner(SharedThis(this)) + .InScrollbar(ExternalScrollbar); MaterialEditorInstance = InArgs._InMaterialEditorInstance; FEditorSupportDelegates::UpdateUI.AddSP(this, &SMaterialParametersOverviewPanel::Refresh); - } void SMaterialParametersOverviewPanel::UpdateEditorInstance(UMaterialEditorPreviewParameters* InMaterialEditorInstance) diff --git a/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.h b/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.h index 7e61665119e7..7e8f6a9ea350 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.h +++ b/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.h @@ -88,6 +88,8 @@ private: /** The tree contained in this item */ TSharedPtr NestedTree; + + TSharedPtr ExternalScrollbar; }; // ********* SMaterialParametersOverviewTree ******* @@ -100,10 +102,12 @@ public: SLATE_BEGIN_ARGS(SMaterialParametersOverviewTree) : _InMaterialEditorInstance(nullptr) , _InOwner(nullptr) + , _InScrollbar() {} SLATE_ARGUMENT(UMaterialEditorPreviewParameters*, InMaterialEditorInstance) SLATE_ARGUMENT(TSharedPtr, InOwner) + SLATE_ARGUMENT(TSharedPtr, InScrollbar) SLATE_END_ARGS() /** Constructs this widget with InArgs */ diff --git a/Engine/Source/Editor/MaterialEditor/Public/MaterialEditingLibrary.h b/Engine/Source/Editor/MaterialEditor/Public/MaterialEditingLibrary.h index 3efee2147286..99fef11dac17 100644 --- a/Engine/Source/Editor/MaterialEditor/Public/MaterialEditingLibrary.h +++ b/Engine/Source/Editor/MaterialEditor/Public/MaterialEditingLibrary.h @@ -9,7 +9,8 @@ #include "MaterialEditingLibrary.generated.h" class UMaterialFunction; -class MaterialInstance; +class UMaterialInstance; + /** Blueprint library for creating/editing Materials */ UCLASS() @@ -78,6 +79,14 @@ public: UFUNCTION(BlueprintCallable, Category = "MaterialEditing") static bool SetMaterialUsage(UMaterial* Material, EMaterialUsage Usage, bool& bNeedsRecompile); + /** + * Check if a particular usage is enabled for the supplied material (e.g. SkeletalMesh, ParticleSprite etc) + * @param Material Material to check usage for + * @param Usage Usage type to check for this material + */ + UFUNCTION(BlueprintPure, Category = "MaterialEditing") + static bool HasMaterialUsage(UMaterial* Material, EMaterialUsage Usage); + /** * Connect a material expression output to one of the material property inputs (e.g. diffuse color, opacity etc) * @param FromExpression Expression to make connection from @@ -109,6 +118,23 @@ public: UFUNCTION(BlueprintCallable, Category = "MaterialEditing") static void LayoutMaterialExpressions(UMaterial* Material); + /** Get the default scalar (float) parameter value from a Material */ + UFUNCTION(BlueprintPure, Category = "MaterialEditing") + static float GetMaterialDefaultScalarParameterValue(UMaterial* Material, FName ParameterName); + + + /** Get the default texture parameter value from a Material */ + UFUNCTION(BlueprintPure, Category = "MaterialEditing") + static UTexture* GetMaterialDefaultTextureParameterValue(UMaterial* Material, FName ParameterName); + + /** Get the default vector parameter value from a Material */ + UFUNCTION(BlueprintPure, Category = "MaterialEditing") + static FLinearColor GetMaterialDefaultVectorParameterValue(UMaterial* Material, FName ParameterName); + + /** Get the default static switch parameter value from a Material */ + UFUNCTION(BlueprintPure, Category = "MaterialEditing") + static bool GetMaterialDefaultStaticSwitchParameterValue(UMaterial* Material, FName ParameterName); + //////// MATERIAL FUNCTION EDITING /** Returns number of material expressions in the supplied material */ @@ -183,8 +209,15 @@ public: UFUNCTION(BlueprintCallable, Category = "MaterialEditing") static bool SetMaterialInstanceVectorParameterValue(UMaterialInstanceConstant* Instance, FName ParameterName, FLinearColor Value); + /** Get the current static switch parameter value from a Material Instance */ + UFUNCTION(BlueprintPure, Category = "MaterialEditing") + static bool GetMaterialInstanceStaticSwitchParameterValue(UMaterialInstanceConstant* Instance, FName ParameterName); /** Called after making modifications to a Material Instance to recompile shaders etc. */ UFUNCTION(BlueprintCallable, Category = "MaterialEditing") static void UpdateMaterialInstance(UMaterialInstanceConstant* Instance); + + /** Gets all direct child mat instances */ + UFUNCTION(BlueprintCallable, Category = "MaterialEditing") + static void GetChildInstances(UMaterialInterface* Parent, TArray>& ChildInstances); }; \ No newline at end of file diff --git a/Engine/Source/Editor/MaterialEditor/Public/MaterialEditorActions.h b/Engine/Source/Editor/MaterialEditor/Public/MaterialEditorActions.h index cfc7f6c361be..477a46881584 100644 --- a/Engine/Source/Editor/MaterialEditor/Public/MaterialEditorActions.h +++ b/Engine/Source/Editor/MaterialEditor/Public/MaterialEditorActions.h @@ -86,6 +86,9 @@ public: /** Toggles live updating of the preview material. */ TSharedPtr< FUICommandInfo > ToggleLivePreview; + + /** Fade nodes which are not connected to the selected nodes. */ + TSharedPtr< FUICommandInfo > ToggleHideUnrelatedNodes; /** Toggles real time expression nodes */ TSharedPtr< FUICommandInfo > ToggleRealtimeExpressions; diff --git a/Engine/Source/Editor/Matinee/Private/Matinee.cpp b/Engine/Source/Editor/Matinee/Private/Matinee.cpp index 618424f3d553..1643ee68e53b 100644 --- a/Engine/Source/Editor/Matinee/Private/Matinee.cpp +++ b/Engine/Source/Editor/Matinee/Private/Matinee.cpp @@ -438,7 +438,6 @@ void FMatinee::BuildCurveEditor() CurveEd->SetEndMarker(true, IData->InterpLength); CurveEd->SetPositionMarker(true, 0.f, PosMarkerColor); CurveEd->SetRegionMarker(true, IData->EdSectionStart, IData->EdSectionEnd, RegionFillColor); - CurveEd->DrawViewport(); }; /** Should NOT open an InterpEd unless InInterp has a valid MatineeData attached! */ @@ -2639,7 +2638,6 @@ void FMatinee::NotifyPreChange( UProperty* PropertyAboutToChange ) void FMatinee::NotifyPostChange( const FPropertyChangedEvent& PropertyChangedEvent, UProperty* PropertyThatChanged ) { CurveEd->CurveChanged(); - CurveEd->DrawViewport(); // Dirty the track window viewports InvalidateTrackWindowViewports(); diff --git a/Engine/Source/Editor/Matinee/Private/MatineeTrackHelpers.cpp b/Engine/Source/Editor/Matinee/Private/MatineeTrackHelpers.cpp index c88079470f10..1c488e211a96 100644 --- a/Engine/Source/Editor/Matinee/Private/MatineeTrackHelpers.cpp +++ b/Engine/Source/Editor/Matinee/Private/MatineeTrackHelpers.cpp @@ -185,7 +185,7 @@ bool UMatineeTrackAnimControlHelper::PreCreateTrack( UInterpGroup* Group, const SNew(STextComboPopup) .Label(NSLOCTEXT("Matinee.Popups", "ChooseAnimSlot", "Choose Anim Slot...")) .TextOptions(SlotStrings) - .OnTextChosen_UObject(this, &UMatineeTrackAnimControlHelper::OnCreateTrackTextEntry, NewWindow, (FString *)&Result) + .OnTextChosen_UObject(const_cast(this), &UMatineeTrackAnimControlHelper::OnCreateTrackTextEntry, NewWindow, (FString *)&Result) ; NewWindow->SetContent(TextEntryPopup); @@ -282,7 +282,7 @@ bool UMatineeTrackAnimControlHelper::PreCreateKeyframe( UInterpTrack *Track, flo if ( Parent.IsValid() ) { FAssetPickerConfig AssetPickerConfig; - AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateUObject( this, &UMatineeTrackAnimControlHelper::OnAddKeyTextEntry, Mode->InterpEd, Track ); + AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateUObject( const_cast(this), &UMatineeTrackAnimControlHelper::OnAddKeyTextEntry, Mode->InterpEd, Track ); AssetPickerConfig.bAllowNullSelection = false; AssetPickerConfig.InitialAssetViewType = EAssetViewType::List; @@ -377,7 +377,7 @@ bool UMatineeTrackDirectorHelper::PreCreateKeyframe( UInterpTrack *Track, float SNew(STextComboPopup) .Label(NSLOCTEXT("Matinee.Popups", "NewCut", "Cut to Group...")) .TextOptions(GroupNames) - .OnTextChosen_UObject(this, &UMatineeTrackDirectorHelper::OnAddKeyTextEntry, Mode->InterpEd, Track); + .OnTextChosen_UObject(const_cast(this), &UMatineeTrackDirectorHelper::OnAddKeyTextEntry, Mode->InterpEd, Track); TSharedPtr< SWindow > Parent = FSlateApplication::Get().GetActiveTopLevelWindow(); if ( Parent.IsValid() ) @@ -435,7 +435,7 @@ bool UMatineeTrackEventHelper::PreCreateKeyframe( UInterpTrack *Track, float Key SNew(STextEntryPopup) .Label(NSLOCTEXT("Matinee.Popups", "NewEventName", "New Event Name")) .DefaultText(FText::FromString(TEXT("Event"))) - .OnTextCommitted_UObject(this, &UMatineeTrackEventHelper::OnAddKeyTextEntry, (IMatineeBase*)Mode->InterpEd, Track) + .OnTextCommitted_UObject(const_cast(this), &UMatineeTrackEventHelper::OnAddKeyTextEntry, (IMatineeBase*)Mode->InterpEd, Track) .SelectAllTextWhenFocused(true) .ClearKeyboardFocusOnCommit(false) .MaxWidth(1024.0f) @@ -564,7 +564,7 @@ bool UMatineeTrackFloatPropHelper::PreCreateTrack( UInterpGroup* Group, const UI SNew(STextComboPopup) .Label(NSLOCTEXT("Matinee.Popups", "PropertyName", "Property Name")) .TextOptions(PropStrings) - .OnTextChosen_UObject(this, &UMatineeTrackFloatPropHelper::OnCreateTrackTextEntry, NewWindow, (FString *)&Result) + .OnTextChosen_UObject(const_cast(this), &UMatineeTrackFloatPropHelper::OnCreateTrackTextEntry, NewWindow, (FString *)&Result) ; NewWindow->SetContent(TextEntryPopup); @@ -702,7 +702,7 @@ bool UMatineeTrackBoolPropHelper::PreCreateTrack( UInterpGroup* Group, const UIn SNew(STextComboPopup) .Label(NSLOCTEXT("Matinee.Popups", "PropertyName", "Property Name")) .TextOptions(PropStrings) - .OnTextChosen_UObject(this, &UMatineeTrackBoolPropHelper::OnCreateTrackTextEntry, TWeakPtr(NewWindow), (FString *)&Result); + .OnTextChosen_UObject(const_cast(this), &UMatineeTrackBoolPropHelper::OnCreateTrackTextEntry, TWeakPtr(NewWindow), (FString *)&Result); NewWindow->SetContent(TextEntryPopup); GEditor->EditorAddModalWindow(NewWindow); @@ -818,7 +818,7 @@ bool UMatineeTrackToggleHelper::PreCreateKeyframe( UInterpTrack *Track, float Ke SNew(STextComboPopup) .Label(NSLOCTEXT("Matinee.Popups", "ToggleAction", "Toggle Action")) .TextOptions(PropStrings) - .OnTextChosen_UObject(this, &UMatineeTrackToggleHelper::OnAddKeyTextEntry, InterpEd, Track) + .OnTextChosen_UObject(const_cast(this), &UMatineeTrackToggleHelper::OnAddKeyTextEntry, InterpEd, Track) ; TSharedPtr< SWindow > Parent = FSlateApplication::Get().GetActiveTopLevelWindow(); @@ -905,7 +905,7 @@ bool UMatineeTrackVectorPropHelper::ChooseProperty(TArray &PropNames) con SNew(STextComboPopup) .Label(NSLOCTEXT("Matinee.Popups", "PropertyName", "Property Name")) .TextOptions(PropStrings) - .OnTextChosen_UObject(this, &UMatineeTrackVectorPropHelper::OnCreateTrackTextEntry, TWeakPtr(NewWindow), (FString *)&Result) + .OnTextChosen_UObject(const_cast(this), &UMatineeTrackVectorPropHelper::OnCreateTrackTextEntry, TWeakPtr(NewWindow), (FString *)&Result) ; NewWindow->SetContent(TextEntryPopup); @@ -1170,7 +1170,7 @@ bool UMatineeTrackVisibilityHelper::PreCreateKeyframe( UInterpTrack *Track, floa SNew(STextComboPopup) .Label(NSLOCTEXT("Matinee.Popups", "VisibilityAction", "Visibility Action")) .TextOptions(PropStrings) - .OnTextChosen_UObject(this, &UMatineeTrackVisibilityHelper::OnAddKeyTextEntry, Mode->InterpEd, Track) + .OnTextChosen_UObject(const_cast(this), &UMatineeTrackVisibilityHelper::OnAddKeyTextEntry, Mode->InterpEd, Track) ; TSharedPtr< SWindow > Parent = FSlateApplication::Get().GetActiveTopLevelWindow(); diff --git a/Engine/Source/Editor/Matinee/Public/MatineeGroupData.h b/Engine/Source/Editor/Matinee/Public/MatineeGroupData.h index fa665ec6939c..24cd13b6c7a7 100644 --- a/Engine/Source/Editor/Matinee/Public/MatineeGroupData.h +++ b/Engine/Source/Editor/Matinee/Public/MatineeGroupData.h @@ -176,7 +176,7 @@ public: /** * @return true if the iterator has not reached the end; false, otherwise. */ - FORCEINLINE operator bool() + FORCEINLINE explicit operator bool() const { return IsCurrentGroupValid(); } diff --git a/Engine/Source/Editor/Matinee/Public/MatineeTrackData.h b/Engine/Source/Editor/Matinee/Public/MatineeTrackData.h index 293e3d7757e7..bf10c1f6fc45 100644 --- a/Engine/Source/Editor/Matinee/Public/MatineeTrackData.h +++ b/Engine/Source/Editor/Matinee/Public/MatineeTrackData.h @@ -148,7 +148,7 @@ public: /** * @return true if the iterator has not reached the end. */ - FORCEINLINE operator bool() + FORCEINLINE explicit operator bool() const { // The iterator must be pointing to a valid track, otherwise the iterator is considered invalid. return GroupIt && IsCurrentTrackValid(); diff --git a/Engine/Source/Editor/MeshPaint/Private/IMeshPainter.cpp b/Engine/Source/Editor/MeshPaint/Private/IMeshPainter.cpp index 32f6e06a0173..eb09bbf0ec6f 100644 --- a/Engine/Source/Editor/MeshPaint/Private/IMeshPainter.cpp +++ b/Engine/Source/Editor/MeshPaint/Private/IMeshPainter.cpp @@ -17,18 +17,26 @@ #include "VREditorInteractor.h" #include "EditorWorldExtension.h" -#include "Framework/Commands/UICommandList.h" #include "Components/MeshComponent.h" #include "Components/PrimitiveComponent.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/Commands/UICommandList.h" -IMeshPainter::IMeshPainter() - : CurrentViewportInteractor(nullptr), bArePainting(false), TimeSinceStartedPainting(0.0f), Time(0.0f), WidgetLineThickness(1.0f), VertexPointColor(FLinearColor::White), HoverVertexPointColor(0.3f, 1.0f, 0.3f), PaintTransaction(nullptr) +IMeshPainter::IMeshPainter() : + CurrentViewportInteractor(nullptr), + bArePainting(false), + TimeSinceStartedPainting(0.0f), + Time(0.0f), + WidgetLineThickness(1.0f), + VertexPointColor(FLinearColor::White), + HoverVertexPointColor(0.3f, 1.0f, 0.3f), + PaintTransaction(nullptr) { - FMeshPainterCommands::Register(); } IMeshPainter::~IMeshPainter() { + UICommandList.Reset(); } void IMeshPainter::RenderInteractors(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI, bool bRenderVertices, ESceneDepthPriorityGroup DepthGroup/* = SDPG_World*/) @@ -66,24 +74,53 @@ void IMeshPainter::Tick(FEditorViewportClient* ViewportClient, float DeltaTime) Time += DeltaTime; } +void IMeshPainter::ChangeBrushRadius(float Multiplier) +{ + const float ChangePercentage = 0.05f; + const float OldValue = GetBrushSettings()->GetBrushRadius(); + + float NewValue = OldValue * (1 + ChangePercentage * Multiplier); + GetBrushSettings()->SetBrushRadius(NewValue); +} + +void IMeshPainter::ChangeBrushStrength(float Multiplier) +{ + const float ChangeAmount = 0.02f; + const float OldValue = GetBrushSettings()->GetBrushStrength(); + + float NewValue = OldValue + ChangeAmount * Multiplier; + GetBrushSettings()->SetBrushStrength(NewValue); +} + +void IMeshPainter::ChangeBrushFalloff(float Multiplier) +{ + const float ChangeAmount = 0.02f; + const float OldValue = GetBrushSettings()->GetBrushFalloff(); + + float NewValue = OldValue + ChangeAmount * Multiplier; + GetBrushSettings()->SetBrushFalloff(NewValue); +} + void IMeshPainter::RegisterCommands(TSharedRef CommandList) { const FMeshPainterCommands& Commands = FMeshPainterCommands::Get(); - // Lambda used to alter the paint brush size - auto BrushLambda = [this](float Multiplier) - { - const float BrushChangeValue = 0.05f; - float BrushRadius = GetBrushSettings()->GetBrushRadius(); - BrushRadius *= (1.f + (BrushChangeValue * Multiplier)); - GetBrushSettings()->SetBrushRadius(BrushRadius); - }; - CommandList->MapAction(Commands.IncreaseBrushSize, FExecuteAction::CreateLambda(BrushLambda, 1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled); - CommandList->MapAction(Commands.DecreaseBrushSize, FExecuteAction::CreateLambda(BrushLambda, -1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled); + CommandList->MapAction(Commands.IncreaseBrushRadius, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushRadius, 1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled); + CommandList->MapAction(Commands.DecreaseBrushRadius, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushRadius, -1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled); + + CommandList->MapAction(Commands.IncreaseBrushStrength, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushStrength, 1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled); + CommandList->MapAction(Commands.DecreaseBrushStrength, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushStrength, -1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled); + + CommandList->MapAction(Commands.IncreaseBrushFalloff, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushFalloff, 1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled); + CommandList->MapAction(Commands.DecreaseBrushFalloff, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushFalloff, -1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled); + + UICommandList = CommandList; } void IMeshPainter::UnregisterCommands(TSharedRef CommandList) { + UICommandList.Reset(); + const FMeshPainterCommands& Commands = FMeshPainterCommands::Get(); for (const TSharedPtr Action : Commands.Commands) { @@ -142,26 +179,11 @@ bool IMeshPainter::InputKey(FEditorViewportClient* InViewportClient, FViewport* { bool bHandled = false; - // Lambda used to alter the paint brush size - auto BrushLambda = [this](float Multiplier) + if (UICommandList.IsValid()) { - const float BrushChangeValue = 0.05f; - float BrushRadius = GetBrushSettings()->GetBrushRadius(); - BrushRadius *= (1.f + (BrushChangeValue * Multiplier)); - GetBrushSettings()->SetBrushRadius(BrushRadius); - }; - - if (InKey == EKeys::LeftBracket) - { - BrushLambda(-1.0f); - bHandled = true; + bHandled = UICommandList->ProcessCommandBindings(InKey, FSlateApplication::Get().GetModifierKeys(), InEvent == IE_Repeat); } - else if (InKey == EKeys::RightBracket) - { - BrushLambda(1.0f); - bHandled = true; - } - + return bHandled; } diff --git a/Engine/Source/Editor/MeshPaint/Private/MeshPaintModule.cpp b/Engine/Source/Editor/MeshPaint/Private/MeshPaintModule.cpp index ecdb89894ba0..31c2ba8dc884 100644 --- a/Engine/Source/Editor/MeshPaint/Private/MeshPaintModule.cpp +++ b/Engine/Source/Editor/MeshPaint/Private/MeshPaintModule.cpp @@ -6,6 +6,7 @@ #include "EditorStyleSet.h" #include "EditorModeRegistry.h" #include "EditorModes.h" +#include "MeshPainterCommands.h" #include "MeshPaintAdapterFactory.h" #include "MeshPaintStaticMeshAdapter.h" #include "MeshPaintSplineMeshAdapter.h" @@ -38,6 +39,8 @@ public: */ virtual void StartupModule() override { + FMeshPainterCommands::Register(); + RegisterGeometryAdapterFactory(MakeShareable(new FMeshPaintGeometryAdapterForSplineMeshesFactory)); RegisterGeometryAdapterFactory(MakeShareable(new FMeshPaintGeometryAdapterForStaticMeshesFactory)); RegisterGeometryAdapterFactory(MakeShareable(new FMeshPaintGeometryAdapterForSkeletalMeshesFactory)); @@ -60,6 +63,8 @@ public: virtual void ShutdownModule() override { + FMeshPainterCommands::Unregister(); + FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); PropertyModule.UnregisterCustomClassLayout("VertexColorImportOptions"); diff --git a/Engine/Source/Editor/MeshPaint/Private/MeshPaintSettings.cpp b/Engine/Source/Editor/MeshPaint/Private/MeshPaintSettings.cpp index e859aa90b415..04de96d20c1b 100644 --- a/Engine/Source/Editor/MeshPaint/Private/MeshPaintSettings.cpp +++ b/Engine/Source/Editor/MeshPaint/Private/MeshPaintSettings.cpp @@ -16,7 +16,13 @@ UPaintBrushSettings::UPaintBrushSettings(const FObjectInitializer& ObjectInitial BrushRadiusMin = 0.01f, BrushRadiusMax = 250000.0f; GConfig->GetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushRadius"), BrushRadius, GEditorPerProjectIni); - BrushRadius = (float)FMath::Clamp(BrushRadius, BrushRadiusMin, BrushRadiusMax); + BrushRadius = FMath::Clamp(BrushRadius, BrushRadiusMin, BrushRadiusMax); + + GConfig->GetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushStrength"), BrushStrength, GEditorPerProjectIni); + BrushStrength = FMath::Clamp(BrushRadius, 0.f, 1.f); + + GConfig->GetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushFalloff"), BrushFalloffAmount, GEditorPerProjectIni); + BrushFalloffAmount = FMath::Clamp(BrushFalloffAmount, 0.f, 1.f); } UPaintBrushSettings::~UPaintBrushSettings() @@ -29,10 +35,32 @@ void UPaintBrushSettings::SetBrushRadius(float InRadius) GConfig->SetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushRadius"), BrushRadius, GEditorPerProjectIni); } +void UPaintBrushSettings::SetBrushStrength(float InStrength) +{ + BrushStrength = FMath::Clamp(InStrength, 0.f, 1.f); + GConfig->SetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushStrength"), BrushStrength, GEditorPerProjectIni); +} + +void UPaintBrushSettings::SetBrushFalloff(float InFalloff) +{ + BrushFalloffAmount = FMath::Clamp(InFalloff, 0.f, 1.f); + GConfig->SetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushFalloff"), BrushFalloffAmount, GEditorPerProjectIni); +} + void UPaintBrushSettings::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UPaintBrushSettings, BrushRadius) && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) { GConfig->SetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushRadius"), BrushRadius, GEditorPerProjectIni); } + + if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UPaintBrushSettings, BrushStrength) && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) + { + GConfig->SetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushStrength"), BrushStrength, GEditorPerProjectIni); + } + + if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UPaintBrushSettings, BrushFalloffAmount) && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) + { + GConfig->SetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushFalloff"), BrushFalloffAmount, GEditorPerProjectIni); + } } \ No newline at end of file diff --git a/Engine/Source/Editor/MeshPaint/Private/MeshPainterCommands.cpp b/Engine/Source/Editor/MeshPaint/Private/MeshPainterCommands.cpp index ccb63a443c91..c2c5d7e990ae 100644 --- a/Engine/Source/Editor/MeshPaint/Private/MeshPainterCommands.cpp +++ b/Engine/Source/Editor/MeshPaint/Private/MeshPainterCommands.cpp @@ -4,13 +4,31 @@ #define LOCTEXT_NAMESPACE "MeshPainterCommands" +FMeshPainterCommands::FMeshPainterCommands() + : TCommands( + "MeshPainter", + NSLOCTEXT("Contexts", "MeshPainter", "Mesh Painter"), + NAME_None, + FEditorStyle::GetStyleSetName()) +{ +} + void FMeshPainterCommands::RegisterCommands() { - UI_COMMAND(IncreaseBrushSize, "IncreaseBrush", "Increases brush size", EUserInterfaceActionType::Button, FInputChord(EKeys::RightBracket, EModifierKey::Control)); - Commands.Add(IncreaseBrushSize); + UI_COMMAND(IncreaseBrushRadius, "Increase Brush Radius", "Press this key to increase brush radius by a percentage of its current size.", EUserInterfaceActionType::Button, FInputChord(EKeys::RightBracket)); + Commands.Add(IncreaseBrushRadius); + UI_COMMAND(DecreaseBrushRadius, "Decrease Brush Size", "Press this key to decrease brush radius by a percentage of its current size.", EUserInterfaceActionType::Button, FInputChord(EKeys::LeftBracket)); + Commands.Add(DecreaseBrushRadius); - UI_COMMAND(DecreaseBrushSize, "DecreaseBrush", "Decreases brush size", EUserInterfaceActionType::Button, FInputChord(EKeys::LeftBracket, EModifierKey::Control)); - Commands.Add(DecreaseBrushSize); + UI_COMMAND(IncreaseBrushStrength, "Increase Brush Strength", "Press this key to increase brush strength by a fixed increment.", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control, EKeys::RightBracket)); + Commands.Add(IncreaseBrushStrength); + UI_COMMAND(DecreaseBrushStrength, "Decrease Brush Strength", "Press this key to decrease brush strength by a fixed increment.", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control, EKeys::LeftBracket)); + Commands.Add(DecreaseBrushStrength); + + UI_COMMAND(IncreaseBrushFalloff, "Increase Brush Falloff", "Press this key to increase brush falloff by a fixed increment.", EUserInterfaceActionType::Button, FInputChord(FInputChord(EModifierKey::Control | EModifierKey::Shift, EKeys::RightBracket))); + Commands.Add(IncreaseBrushFalloff); + UI_COMMAND(DecreaseBrushFalloff, "Decrease Brush Falloff", "Press this key to decrease brush falloff by a fixed increment.", EUserInterfaceActionType::Button, FInputChord(FInputChord(EModifierKey::Control | EModifierKey::Shift, EKeys::LeftBracket))); + Commands.Add(DecreaseBrushFalloff); } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/MeshPaint/Public/IMeshPainter.h b/Engine/Source/Editor/MeshPaint/Public/IMeshPainter.h index 1cb2cb1f1ff7..5a28ba387ede 100644 --- a/Engine/Source/Editor/MeshPaint/Public/IMeshPainter.h +++ b/Engine/Source/Editor/MeshPaint/Public/IMeshPainter.h @@ -112,6 +112,9 @@ protected: FLinearColor WidgetLineColor; FLinearColor VertexPointColor; FLinearColor HoverVertexPointColor; + + TSharedPtr UICommandList; + protected: /** Begins and keeps track of an Editor transaction with the given Description */ void BeginTransaction(const FText Description); @@ -119,4 +122,15 @@ protected: void EndTransaction(); /** Painting transaction instance which is currently active */ FScopedTransaction* PaintTransaction; + +private: + + /** Change the brush radius by a percentage, multiplied by the given value. */ + void ChangeBrushRadius(float Multiplier); + + /** Change the brush radius by one increment, multiplied by the given value */ + void ChangeBrushStrength(float Multiplier); + + /** Change the brush radius by one increment, multiplied by the given value */ + void ChangeBrushFalloff(float Multiplier); }; \ No newline at end of file diff --git a/Engine/Source/Editor/MeshPaint/Public/MeshPaintSettings.h b/Engine/Source/Editor/MeshPaint/Public/MeshPaintSettings.h index c8df2f631b6d..3b831010fccc 100644 --- a/Engine/Source/Editor/MeshPaint/Public/MeshPaintSettings.h +++ b/Engine/Source/Editor/MeshPaint/Public/MeshPaintSettings.h @@ -41,6 +41,13 @@ public: float GetBrushRadius() const { return BrushRadius; } void SetBrushRadius(float InRadius); + + float GetBrushStrength() const { return BrushStrength; } + void SetBrushStrength(float InStrength); + + float GetBrushFalloff() const { return BrushFalloffAmount; } + void SetBrushFalloff(float InFalloff); + protected: /** Radius of the Brush used for Painting */ UPROPERTY(EditAnywhere, Category = Brush, meta = (DisplayName = "Radius", UIMin = "0.01", UIMax = "2048.0", ClampMin = "0.01", ClampMax = "250000.0")) diff --git a/Engine/Source/Editor/MeshPaint/Public/MeshPainterCommands.h b/Engine/Source/Editor/MeshPaint/Public/MeshPainterCommands.h index abf749095f9c..8e73df8b3be2 100644 --- a/Engine/Source/Editor/MeshPaint/Public/MeshPainterCommands.h +++ b/Engine/Source/Editor/MeshPaint/Public/MeshPainterCommands.h @@ -9,9 +9,8 @@ /** Base set of mesh painter commands */ class MESHPAINT_API FMeshPainterCommands : public TCommands { - public: - FMeshPainterCommands() : TCommands("MeshPainter", NSLOCTEXT("Contexts", "MeshPainter", "Mesh Painter"), NAME_None, FEditorStyle::GetStyleSetName()) {} + FMeshPainterCommands(); /** * Initialize commands @@ -19,7 +18,14 @@ public: virtual void RegisterCommands() override; public: - TSharedPtr IncreaseBrushSize; - TSharedPtr DecreaseBrushSize; + TSharedPtr IncreaseBrushRadius; + TSharedPtr DecreaseBrushRadius; + + TSharedPtr IncreaseBrushStrength; + TSharedPtr DecreaseBrushStrength; + + TSharedPtr IncreaseBrushFalloff; + TSharedPtr DecreaseBrushFalloff; + TArray> Commands; }; \ No newline at end of file diff --git a/Engine/Source/Editor/MeshPaintMode/Private/PaintModeSettings.h b/Engine/Source/Editor/MeshPaintMode/Private/PaintModeSettings.h index dbc0d2643899..156f859980e5 100644 --- a/Engine/Source/Editor/MeshPaintMode/Private/PaintModeSettings.h +++ b/Engine/Source/Editor/MeshPaintMode/Private/PaintModeSettings.h @@ -167,20 +167,20 @@ enum class EPaintMode : uint8 }; /** Paint mode settings class derives from base mesh painting settings */ -UCLASS() +UCLASS(Config=EditorPerProjectUserSettings) class UPaintModeSettings : public UMeshPaintSettings { GENERATED_UCLASS_BODY() public: static UPaintModeSettings* Get(); - + UPROPERTY() EPaintMode PaintMode; - UPROPERTY(EditAnywhere, Category = VertexPainting, meta=(ShowOnlyInnerProperties)) + UPROPERTY(EditAnywhere, Config, Category = VertexPainting, meta=(ShowOnlyInnerProperties)) FVertexPaintSettings VertexPaintSettings; - UPROPERTY(EditAnywhere, Category = TexturePainting, meta=(ShowOnlyInnerProperties)) + UPROPERTY(EditAnywhere, Config, Category = TexturePainting, meta=(ShowOnlyInnerProperties)) FTexturePaintSettings TexturePaintSettings; }; diff --git a/Engine/Source/Editor/MeshPaintMode/Private/SPaintModeWidget.cpp b/Engine/Source/Editor/MeshPaintMode/Private/SPaintModeWidget.cpp index 1cd6b74d3aff..70686790e5a9 100644 --- a/Engine/Source/Editor/MeshPaintMode/Private/SPaintModeWidget.cpp +++ b/Engine/Source/Editor/MeshPaintMode/Private/SPaintModeWidget.cpp @@ -104,7 +104,7 @@ void SPaintModeWidget::CreateDetailsView() /*bAllowSearch=*/ false, FDetailsViewArgs::HideNameArea, /*bHideSelectionTip=*/ true, - /*InNotifyHook=*/ nullptr, + /*InNotifyHook=*/ this, /*InSearchInitialKeyFocus=*/ false, /*InViewIdentifier=*/ NAME_None); DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Automatic; @@ -245,4 +245,15 @@ EVisibility SPaintModeWidget::IsTexturePaintModeVisible() const return (MeshPaintSettings->PaintMode == EPaintMode::Textures) ? EVisibility::Visible : EVisibility::Collapsed; } +void SPaintModeWidget::NotifyPostChange(const FPropertyChangedEvent& PropertyChangedEvent, UProperty* PropertyThatChanged) +{ + if (PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) + { + for (UObject* Settings : SettingsObjects) + { + Settings->SaveConfig(); + } + } +} + #undef LOCTEXT_NAMESPACE // "PaintModePainter" diff --git a/Engine/Source/Editor/MeshPaintMode/Private/SPaintModeWidget.h b/Engine/Source/Editor/MeshPaintMode/Private/SPaintModeWidget.h index 1b8672cf1984..4a4955b181a5 100644 --- a/Engine/Source/Editor/MeshPaintMode/Private/SPaintModeWidget.h +++ b/Engine/Source/Editor/MeshPaintMode/Private/SPaintModeWidget.h @@ -4,13 +4,14 @@ #include "Widgets/SCompoundWidget.h" #include "Widgets/DeclarativeSyntaxSupport.h" +#include "Misc/NotifyHook.h" class FPaintModePainter; class UPaintModeSettings; class IDetailsView; /** Widget representing the state / functionality and settings for PaintModePainter*/ -class SPaintModeWidget : public SCompoundWidget +class SPaintModeWidget : public SCompoundWidget, public FNotifyHook { public: SLATE_BEGIN_ARGS(SPaintModeWidget) {} @@ -44,4 +45,7 @@ protected: FPaintModePainter* MeshPainter; /** Paint settings instance */ UPaintModeSettings* PaintModeSettings; + +private: + virtual void NotifyPostChange(const FPropertyChangedEvent& PropertyChangedEvent, UProperty* PropertyThatChanged) override; }; diff --git a/Engine/Source/Editor/MovieSceneTools/Private/K2Node_GetSequenceBinding.cpp b/Engine/Source/Editor/MovieSceneTools/Private/K2Node_GetSequenceBinding.cpp index 87014d87c059..2fc1683fb2d7 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/K2Node_GetSequenceBinding.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/K2Node_GetSequenceBinding.cpp @@ -260,7 +260,7 @@ void UK2Node_GetSequenceBinding::GetContextMenuActions(const FGraphNodeContextMe AllowedClasses, PropertyCustomizationHelpers::GetNewAssetFactoriesForClasses(AllowedClasses), FOnShouldFilterAsset(), - FOnAssetSelected::CreateUObject(this, &UK2Node_GetSequenceBinding::SetSequence), + FOnAssetSelected::CreateUObject(const_cast(this), &UK2Node_GetSequenceBinding::SetSequence), FSimpleDelegate()); SubMenuBuilder.AddWidget(MenuContent, FText::GetEmpty(), false); diff --git a/Engine/Source/Editor/MovieSceneTools/Private/K2Node_GetSequenceBindings.cpp b/Engine/Source/Editor/MovieSceneTools/Private/K2Node_GetSequenceBindings.cpp index 64e48d35676a..86616770497e 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/K2Node_GetSequenceBindings.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/K2Node_GetSequenceBindings.cpp @@ -176,7 +176,7 @@ void UDEPRECATED_K2Node_GetSequenceBindings::GetContextMenuActions(const FGraphN AllowedClasses, PropertyCustomizationHelpers::GetNewAssetFactoriesForClasses(AllowedClasses), FOnShouldFilterAsset(), - FOnAssetSelected::CreateUObject(this, &UDEPRECATED_K2Node_GetSequenceBindings::SetSequence), + FOnAssetSelected::CreateUObject(const_cast(this), &UDEPRECATED_K2Node_GetSequenceBindings::SetSequence), FSimpleDelegate()); SubMenuBuilder.AddWidget(MenuContent, FText::GetEmpty(), false); @@ -193,7 +193,7 @@ void UDEPRECATED_K2Node_GetSequenceBindings::GetContextMenuActions(const FGraphN LOCTEXT("Refresh_ToolTip", "Refresh this node's bindings"), FSlateIcon(), FUIAction( - FExecuteAction::CreateUObject(this, &UDEPRECATED_K2Node_GetSequenceBindings::ReconstructNode) + FExecuteAction::CreateUObject(const_cast(this), &UDEPRECATED_K2Node_GetSequenceBindings::ReconstructNode) ) ); } diff --git a/Engine/Source/Editor/MovieSceneTools/Private/MovieSceneEventCustomization.cpp b/Engine/Source/Editor/MovieSceneTools/Private/MovieSceneEventCustomization.cpp index dad0e59c0af9..3daebbf4bf94 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/MovieSceneEventCustomization.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/MovieSceneEventCustomization.cpp @@ -350,7 +350,7 @@ void FMovieSceneEventCustomization::PopulateQuickBindSubMenu(FMenuBuilder& MenuB } } - Algo::SortBy(Functions, &UFunction::GetFName); + Algo::SortBy(Functions, &UFunction::GetFName, FNameLexicalLess()); for (UFunction* Function : Functions) { diff --git a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/MaterialParameterCollectionTrackEditor.cpp b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/MaterialParameterCollectionTrackEditor.cpp index 739ca3e7fcc4..990c1a96149c 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/MaterialParameterCollectionTrackEditor.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/MaterialParameterCollectionTrackEditor.cpp @@ -203,7 +203,7 @@ TSharedRef FMaterialParameterCollectionTrackEditor::OnGetAddParameterMe MenuBuilder.BeginSection(NAME_None, LOCTEXT("ScalarParametersHeading", "Scalar")); { TArray ScalarParameters = MPCTrack->MPC->ScalarParameters; - Algo::SortBy(ScalarParameters, &FCollectionParameterBase::ParameterName); + Algo::SortBy(ScalarParameters, &FCollectionParameterBase::ParameterName, FNameLexicalLess()); for (const FCollectionScalarParameter& Scalar : ScalarParameters) { @@ -220,7 +220,7 @@ TSharedRef FMaterialParameterCollectionTrackEditor::OnGetAddParameterMe MenuBuilder.BeginSection(NAME_None, LOCTEXT("VectorParametersHeading", "Vector")); { TArray VectorParameters = MPCTrack->MPC->VectorParameters; - Algo::SortBy(VectorParameters, &FCollectionParameterBase::ParameterName); + Algo::SortBy(VectorParameters, &FCollectionParameterBase::ParameterName, FNameLexicalLess()); for (const FCollectionVectorParameter& Vector : VectorParameters) { diff --git a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/MaterialTrackEditor.cpp b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/MaterialTrackEditor.cpp index 8f7405e27ccb..1db4004e1183 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/MaterialTrackEditor.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/MaterialTrackEditor.cpp @@ -53,7 +53,7 @@ struct FParameterNameAndAction bool operator<(FParameterNameAndAction const& Other) const { - return ParameterName < Other.ParameterName; + return ParameterName.LexicalLess(Other.ParameterName); } }; diff --git a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/ParticleParameterTrackEditor.h b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/ParticleParameterTrackEditor.h index 5570d23a3127..be7a8dbe528e 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/ParticleParameterTrackEditor.h +++ b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/ParticleParameterTrackEditor.h @@ -64,7 +64,7 @@ private: bool operator<( FParameterNameAndAction const& Other ) const { - return ParameterName < Other.ParameterName; + return ParameterName.LexicalLess(Other.ParameterName); } }; diff --git a/Engine/Source/Editor/MovieSceneTools/Public/KeyframeTrackEditor.h b/Engine/Source/Editor/MovieSceneTools/Public/KeyframeTrackEditor.h index 1c2ba293c74b..7f638ee0008a 100644 --- a/Engine/Source/Editor/MovieSceneTools/Public/KeyframeTrackEditor.h +++ b/Engine/Source/Editor/MovieSceneTools/Public/KeyframeTrackEditor.h @@ -377,6 +377,13 @@ protected: return KeyPropertyResult; } + FORCEINLINE FKeyPropertyResult AddKeysToObjects( + std::initializer_list ObjectsToKey, FFrameNumber KeyTime, FGeneratedTrackKeys& GeneratedKeys, + ESequencerKeyMode KeyMode, TSubclassOf TrackClass, FName PropertyName, + TFunction OnInitializeNewTrack) + { + return AddKeysToObjects(MakeArrayView(ObjectsToKey), KeyTime, GeneratedKeys, KeyMode, TrackClass, PropertyName, MoveTemp(OnInitializeNewTrack)); + } private: diff --git a/Engine/Source/Editor/Persona/Private/AnimNotifyDetails.cpp b/Engine/Source/Editor/Persona/Private/AnimNotifyDetails.cpp index 4a10771c5e93..e53372e54b71 100644 --- a/Engine/Source/Editor/Persona/Private/AnimNotifyDetails.cpp +++ b/Engine/Source/Editor/Persona/Private/AnimNotifyDetails.cpp @@ -330,6 +330,7 @@ bool FAnimNotifyDetails::CustomizeProperty(IDetailCategoryBuilder& CategoryBuild { FString ClassName = Notify->GetClass()->GetName(); FString PropertyName = Property->GetProperty()->GetName(); + bool bIsBoneName = Property->GetBoolMetaData(TEXT("AnimNotifyBoneName")); if(ClassName.Find(TEXT("AnimNotify_PlayParticleEffect")) != INDEX_NONE && PropertyName == TEXT("SocketName")) { @@ -359,6 +360,11 @@ bool FAnimNotifyDetails::CustomizeProperty(IDetailCategoryBuilder& CategoryBuild return true; } } + else if (bIsBoneName) + { + AddBoneNameProperty(CategoryBuilder, Notify, Property); + return true; + } } return false; } diff --git a/Engine/Source/Editor/Persona/Private/PersonaEditorModeManager.cpp b/Engine/Source/Editor/Persona/Private/PersonaEditorModeManager.cpp index 27ef44809a60..8350add6e6cc 100644 --- a/Engine/Source/Editor/Persona/Private/PersonaEditorModeManager.cpp +++ b/Engine/Source/Editor/Persona/Private/PersonaEditorModeManager.cpp @@ -6,9 +6,9 @@ bool FPersonaEditorModeManager::GetCameraTarget(FSphere& OutTarget) const { // Note: assumes all of our modes are IPersonaEditMode! - for(int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex) + for(int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex) { - TSharedPtr EditMode = StaticCastSharedPtr(Modes[ModeIndex]); + TSharedPtr EditMode = StaticCastSharedPtr(ActiveModes[ModeIndex]); FSphere Target; if (EditMode->GetCameraTarget(Target)) @@ -24,9 +24,9 @@ bool FPersonaEditorModeManager::GetCameraTarget(FSphere& OutTarget) const void FPersonaEditorModeManager::GetOnScreenDebugInfo(TArray& OutDebugText) const { // Note: assumes all of our modes are IPersonaEditMode! - for (int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex) + for (int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex) { - TSharedPtr EditMode = StaticCastSharedPtr(Modes[ModeIndex]); + TSharedPtr EditMode = StaticCastSharedPtr(ActiveModes[ModeIndex]); EditMode->GetOnScreenDebugInfo(OutDebugText); } } diff --git a/Engine/Source/Editor/Persona/Private/PersonaMeshDetails.cpp b/Engine/Source/Editor/Persona/Private/PersonaMeshDetails.cpp index e29618382b6f..e3ca8f061985 100644 --- a/Engine/Source/Editor/Persona/Private/PersonaMeshDetails.cpp +++ b/Engine/Source/Editor/Persona/Private/PersonaMeshDetails.cpp @@ -32,6 +32,7 @@ #include "EditorDirectories.h" #include "UnrealEdGlobals.h" #include "IDetailsView.h" +#include "MaterialList.h" #include "PropertyCustomizationHelpers.h" #include "DesktopPlatformModule.h" #include "Interfaces/IMainFrameModule.h" @@ -2084,11 +2085,47 @@ FReply FPersonaMeshDetails::OnReimportLodClicked(EReimportButtonType InReimportT } FString SourceFilenameBackup(""); + + //If we alter the reduction setting and the user cancel the import we must set them back + bool bRestoreReductionOnfail = false; + FSkeletalMeshOptimizationSettings ReductionSettingsBackup; + FSkeletalMeshLODInfo* LODInfo = SkelMesh->GetLODInfo(InLODIndex); if(InReimportType == EReimportButtonType::ReimportWithNewFile) { // Back up current source filename and empty it so the importer asks for a new one. - SourceFilenameBackup = SkelMesh->GetLODInfo(InLODIndex)->SourceImportFilename; - SkelMesh->GetLODInfo(InLODIndex)->SourceImportFilename.Empty(); + SourceFilenameBackup = LODInfo->SourceImportFilename; + LODInfo->SourceImportFilename.Empty(); + + //Avoid changing the settings if the skeletal mesh is using a LODSettings asset valid for this LOD + bool bUseLODSettingAsset = SkelMesh->LODSettings != nullptr && SkelMesh->LODSettings->GetNumberOfSettings() > InLODIndex; + //Make the reduction settings change according to the context + if (!bUseLODSettingAsset && SkelMesh->IsReductionActive(InLODIndex) && LODInfo->bHasBeenSimplified && SkelMesh->GetImportedModel()->LODModels[InLODIndex].RawSkeletalMeshBulkData.IsEmpty()) + { + FSkeletalMeshOptimizationSettings& ReductionSettings = LODInfo->ReductionSettings; + //Backup the reduction settings + ReductionSettingsBackup = ReductionSettings; + //In case we have a vert/tri percent we just put the percent to 100% and avoid reduction + //If we have a maximum criterion we change the BaseLOD to reduce the imported fbx instead of other LOD + switch (ReductionSettings.TerminationCriterion) + { + case SkeletalMeshTerminationCriterion::SMTC_NumOfTriangles: + ReductionSettings.NumOfTrianglesPercentage = 1.0f; + break; + case SkeletalMeshTerminationCriterion::SMTC_NumOfVerts: + ReductionSettings.NumOfVertPercentage = 1.0f; + break; + case SkeletalMeshTerminationCriterion::SMTC_TriangleOrVert: + ReductionSettings.NumOfTrianglesPercentage = 1.0f; + ReductionSettings.NumOfVertPercentage = 1.0f; + break; + case SkeletalMeshTerminationCriterion::SMTC_AbsNumOfTriangles: + case SkeletalMeshTerminationCriterion::SMTC_AbsNumOfVerts: + case SkeletalMeshTerminationCriterion::SMTC_AbsTriangleOrVert: + ReductionSettings.BaseLOD = InLODIndex; + break; + } + bRestoreReductionOnfail = true; + } } bool bImportSucceeded = FbxMeshUtils::ImportMeshLODDialog(SkelMesh, InLODIndex); @@ -2096,7 +2133,16 @@ FReply FPersonaMeshDetails::OnReimportLodClicked(EReimportButtonType InReimportT if(InReimportType == EReimportButtonType::ReimportWithNewFile && !bImportSucceeded) { // Copy old source file back, as this one failed - SkelMesh->GetLODInfo(InLODIndex)->SourceImportFilename = SourceFilenameBackup; + LODInfo->SourceImportFilename = SourceFilenameBackup; + if (bRestoreReductionOnfail) + { + LODInfo->ReductionSettings = ReductionSettingsBackup; + } + } + else if(InReimportType == EReimportButtonType::ReimportWithNewFile) + { + //Refresh the layout so the BaseLOD min max get recompute + MeshDetailLayout->ForceRefreshDetails(); } return FReply::Handled(); diff --git a/Engine/Source/Editor/Persona/Private/PersonaModule.cpp b/Engine/Source/Editor/Persona/Private/PersonaModule.cpp index 8075919375ae..263ca414cd00 100644 --- a/Engine/Source/Editor/Persona/Private/PersonaModule.cpp +++ b/Engine/Source/Editor/Persona/Private/PersonaModule.cpp @@ -1014,7 +1014,7 @@ TSharedRef< SWidget > FPersonaModule::GenerateCreateAssetMenu(TWeakPtrGetSkeleton()); } - AnimationEditorUtils::FillCreateAssetMenu(MenuBuilder, Objects, FAnimAssetCreated::CreateRaw(this, &FPersonaModule::HandleAssetCreated), false); + AnimationEditorUtils::FillCreateAssetMenu(MenuBuilder, Objects, FAnimAssetCreated::CreateRaw(const_cast(this), &FPersonaModule::HandleAssetCreated), false); return MenuBuilder.MakeWidget(); } @@ -1040,7 +1040,7 @@ void FPersonaModule::FillCreateAnimationMenu(FMenuBuilder& MenuBuilder, TWeakPtr LOCTEXT("CreateAnimation_RefPose_Tooltip", "Create Animation from reference pose."), FSlateIcon(), FUIAction( - FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset, Objects, FString("_Sequence"), FAnimAssetCreated::CreateRaw(this, &FPersonaModule::CreateAnimation, EPoseSourceOption::ReferencePose, InWeakPersonaToolkit), false), + FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset, Objects, FString("_Sequence"), FAnimAssetCreated::CreateRaw(const_cast(this), &FPersonaModule::CreateAnimation, EPoseSourceOption::ReferencePose, InWeakPersonaToolkit), false), FCanExecuteAction() ) ); @@ -1050,7 +1050,7 @@ void FPersonaModule::FillCreateAnimationMenu(FMenuBuilder& MenuBuilder, TWeakPtr LOCTEXT("CreateAnimation_CurrentPose_Tooltip", "Create Animation from current pose."), FSlateIcon(), FUIAction( - FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset, Objects, FString("_Sequence"), FAnimAssetCreated::CreateRaw(this, &FPersonaModule::CreateAnimation, EPoseSourceOption::CurrentPose, InWeakPersonaToolkit), false), + FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset, Objects, FString("_Sequence"), FAnimAssetCreated::CreateRaw(const_cast(this), &FPersonaModule::CreateAnimation, EPoseSourceOption::CurrentPose, InWeakPersonaToolkit), false), FCanExecuteAction() ) ); @@ -1091,7 +1091,7 @@ void FPersonaModule::FillCreateAnimationFromCurrentAnimationMenu(FMenuBuilder& M LOCTEXT("CreateAnimation_CurrentAnimation_AnimData_Tooltip", "Create Animation from Animation Source Data."), FSlateIcon(), FUIAction( - FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset, Objects, FString("_Sequence"), FAnimAssetCreated::CreateRaw(this, &FPersonaModule::CreateAnimation, EPoseSourceOption::CurrentAnimation_AnimData, InWeakPersonaToolkit), false) + FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset, Objects, FString("_Sequence"), FAnimAssetCreated::CreateRaw(const_cast(this), &FPersonaModule::CreateAnimation, EPoseSourceOption::CurrentAnimation_AnimData, InWeakPersonaToolkit), false) ) ); @@ -1100,7 +1100,7 @@ void FPersonaModule::FillCreateAnimationFromCurrentAnimationMenu(FMenuBuilder& M LOCTEXT("CreateAnimation_CurrentAnimation_PreviewMesh_Tooltip", "Create Animation by playing on the Current Preview Mesh, including Retargeting, Post Process Graph, or anything you see on the preview mesh."), FSlateIcon(), FUIAction( - FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset, Objects, FString("_Sequence"), FAnimAssetCreated::CreateRaw(this, &FPersonaModule::CreateAnimation, EPoseSourceOption::CurrentAnimation_PreviewMesh, InWeakPersonaToolkit), false) + FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset, Objects, FString("_Sequence"), FAnimAssetCreated::CreateRaw(const_cast(this), &FPersonaModule::CreateAnimation, EPoseSourceOption::CurrentAnimation_PreviewMesh, InWeakPersonaToolkit), false) ) ); } @@ -1129,7 +1129,7 @@ void FPersonaModule::FillCreatePoseAssetMenu(FMenuBuilder& MenuBuilder, TWeakPtr LOCTEXT("CreatePoseAsset_CurrentPose_Tooltip", "Create PoseAsset from current pose."), FSlateIcon(), FUIAction( - FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset, Objects, FString("_PoseAsset"), FAnimAssetCreated::CreateRaw(this, &FPersonaModule::CreatePoseAsset, EPoseSourceOption::CurrentPose, InWeakPersonaToolkit), false), + FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset, Objects, FString("_PoseAsset"), FAnimAssetCreated::CreateRaw(const_cast(this), &FPersonaModule::CreatePoseAsset, EPoseSourceOption::CurrentPose, InWeakPersonaToolkit), false), FCanExecuteAction() ) ); @@ -1139,7 +1139,7 @@ void FPersonaModule::FillCreatePoseAssetMenu(FMenuBuilder& MenuBuilder, TWeakPtr LOCTEXT("CreatePoseAsset_CurrentAnimation_Tooltip", "Create Animation from current animation."), FSlateIcon(), FUIAction( - FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset, Objects, FString("_PoseAsset"), FAnimAssetCreated::CreateRaw(this, &FPersonaModule::CreatePoseAsset, EPoseSourceOption::CurrentAnimation_AnimData, InWeakPersonaToolkit), false), + FExecuteAction::CreateStatic(&AnimationEditorUtils::ExecuteNewAnimAsset, Objects, FString("_PoseAsset"), FAnimAssetCreated::CreateRaw(const_cast(this), &FPersonaModule::CreatePoseAsset, EPoseSourceOption::CurrentAnimation_AnimData, InWeakPersonaToolkit), false), FCanExecuteAction() ) ); @@ -1174,7 +1174,7 @@ void FPersonaModule::FillInsertPoseMenu(FMenuBuilder& MenuBuilder, TWeakPtr(this), &FPersonaModule::InsertCurrentPoseToAsset, InWeakPersonaToolkit); /** The default view mode should be a list view */ AssetPickerConfig.InitialAssetViewType = EAssetViewType::List; diff --git a/Engine/Source/Editor/Persona/Private/SAnimCurvePanel.cpp b/Engine/Source/Editor/Persona/Private/SAnimCurvePanel.cpp index 4aa884491f97..fccde3b0fbd7 100644 --- a/Engine/Source/Editor/Persona/Private/SAnimCurvePanel.cpp +++ b/Engine/Source/Editor/Persona/Private/SAnimCurvePanel.cpp @@ -855,7 +855,7 @@ void SAnimCurvePanel::UpdatePanel() MetadataNameMap->GetName(A.Name.UID, AName); MetadataNameMap->GetName(B.Name.UID, BName); - return AName < BName; + return AName.LexicalLess(BName); }); // Store expanded state before clearing the tracks @@ -1282,14 +1282,14 @@ TSharedRef SAnimCurvePanel::CreateCurveContextMenu(FAnimCurveBaseInterf TypeToggleToolTip = LOCTEXT("TypeToggleToVariableToolTip", "Turns this curve into a variable curve."); } - NewAction.ExecuteAction.BindSP(this, &SAnimCurvePanel::ToggleCurveTypeMenuCallback, Curve); + NewAction.ExecuteAction.BindSP(const_cast(this), &SAnimCurvePanel::ToggleCurveTypeMenuCallback, Curve); MenuBuilder.AddMenuEntry( TypeToggleLabel, TypeToggleToolTip, FSlateIcon(), NewAction); - NewAction.ExecuteAction.BindSP(this, &SAnimCurvePanel::DeleteTrack, Curve->CurveUID); + NewAction.ExecuteAction.BindSP(const_cast(this), &SAnimCurvePanel::DeleteTrack, Curve->CurveUID); MenuBuilder.AddMenuEntry( LOCTEXT("RemoveTrack", "Remove Track"), LOCTEXT("RemoveTrackTooltip", "Remove this track"), diff --git a/Engine/Source/Editor/Persona/Private/SAnimTrackCurvePanel.cpp b/Engine/Source/Editor/Persona/Private/SAnimTrackCurvePanel.cpp index 4b981dd80d7e..59b1d3cc89e6 100644 --- a/Engine/Source/Editor/Persona/Private/SAnimTrackCurvePanel.cpp +++ b/Engine/Source/Editor/Persona/Private/SAnimTrackCurvePanel.cpp @@ -644,7 +644,7 @@ void SAnimTrackCurvePanel::UpdatePanel() MetadataNameMap->GetName(A.Name.UID, AName); MetadataNameMap->GetName(B.Name.UID, BName); - return AName < BName; + return AName.LexicalLess(BName); }); // see if we need to clear or not @@ -894,7 +894,7 @@ TSharedRef SAnimTrackCurvePanel::CreateCurveContextMenu(USkeleton::Anim MenuBuilder.AddWidget( SNew(SCheckBox) .IsChecked(this, &SAnimTrackCurvePanel::GetCurveFlagAsCheckboxState, CurveUid, AACF_Disabled) - .OnCheckStateChanged(this, &SAnimTrackCurvePanel::SetCurveFlagFromCheckboxState, CurveUid, AACF_Disabled) + .OnCheckStateChanged(const_cast(this), &SAnimTrackCurvePanel::SetCurveFlagFromCheckboxState, CurveUid, AACF_Disabled) .ToolTipText(LOCTEXT("DisableCurveTooltip", "Disable Track")) [ SNew(STextBlock) @@ -909,7 +909,7 @@ TSharedRef SAnimTrackCurvePanel::CreateCurveContextMenu(USkeleton::Anim { FUIAction NewAction; - NewAction.ExecuteAction.BindSP(this, &SAnimTrackCurvePanel::DeleteTrack, CurveUid); + NewAction.ExecuteAction.BindSP(const_cast(this), &SAnimTrackCurvePanel::DeleteTrack, CurveUid); MenuBuilder.AddMenuEntry( LOCTEXT("RemoveTrack", "Remove Track"), LOCTEXT("RemoveTrackTooltip", "Remove this track"), diff --git a/Engine/Source/Editor/Persona/Private/SAnimViewportToolBar.cpp b/Engine/Source/Editor/Persona/Private/SAnimViewportToolBar.cpp index 972c0c3dec63..76ea9662c430 100644 --- a/Engine/Source/Editor/Persona/Private/SAnimViewportToolBar.cpp +++ b/Engine/Source/Editor/Persona/Private/SAnimViewportToolBar.cpp @@ -359,7 +359,7 @@ TSharedRef SAnimViewportToolBar::MakeFloorOffsetWidget() const .MinSliderValue(-100.0f) .MaxSliderValue(100.0f) .Value(this, &SAnimViewportToolBar::OnGetFloorOffset) - .OnValueChanged(this, &SAnimViewportToolBar::OnFloorOffsetChanged) + .OnValueChanged(const_cast(this), &SAnimViewportToolBar::OnFloorOffsetChanged) .ToolTipText(LOCTEXT("FloorOffsetToolTip", "Height offset for the floor mesh (stored per-mesh)")) ] ]; @@ -386,8 +386,8 @@ TSharedRef SAnimViewportToolBar::MakeFOVWidget() const .MinSliderValue(FOVMin) .MaxSliderValue(FOVMax) .Value(this, &SAnimViewportToolBar::OnGetFOVValue) - .OnValueChanged(this, &SAnimViewportToolBar::OnFOVValueChanged) - .OnValueCommitted(this, &SAnimViewportToolBar::OnFOVValueCommitted) + .OnValueChanged(const_cast(this), &SAnimViewportToolBar::OnFOVValueChanged) + .OnValueCommitted(const_cast(this), &SAnimViewportToolBar::OnFOVValueCommitted) ] ]; } @@ -690,7 +690,7 @@ TSharedRef SAnimViewportToolBar::GenerateCharacterMenu() const InMenuBuilder.AddSubMenu( LOCTEXT("CharacterMenu_ClothingSubMenu", "Clothing"), LOCTEXT("CharacterMenu_ClothingSubMenuToolTip", "Options relating to clothing"), - FNewMenuDelegate::CreateRaw(this, &SAnimViewportToolBar::FillCharacterClothingMenu)); + FNewMenuDelegate::CreateRaw(const_cast(this), &SAnimViewportToolBar::FillCharacterClothingMenu)); } #endif // #if WITH_APEX_CLOTHING } @@ -711,7 +711,7 @@ TSharedRef SAnimViewportToolBar::GenerateCharacterMenu() const InMenuBuilder.AddSubMenu( LOCTEXT("CharacterMenu_AdvancedSubMenu", "Advanced"), LOCTEXT("CharacterMenu_AdvancedSubMenuToolTip", "Advanced options"), - FNewMenuDelegate::CreateRaw(this, &SAnimViewportToolBar::FillCharacterAdvancedMenu)); + FNewMenuDelegate::CreateRaw(const_cast(this), &SAnimViewportToolBar::FillCharacterAdvancedMenu)); InMenuBuilder.EndSection(); } diff --git a/Engine/Source/Editor/Persona/Private/SAnimationSequenceBrowser.cpp b/Engine/Source/Editor/Persona/Private/SAnimationSequenceBrowser.cpp index 1a5bb1762f85..6380459ddc20 100644 --- a/Engine/Source/Editor/Persona/Private/SAnimationSequenceBrowser.cpp +++ b/Engine/Source/Editor/Persona/Private/SAnimationSequenceBrowser.cpp @@ -811,7 +811,7 @@ TSharedRef SAnimationSequenceBrowser::CreateHistoryMenu(bool bInBackHis MenuBuilder.AddMenuEntry(DisplayName, Tooltip, FSlateIcon(), FUIAction( - FExecuteAction::CreateRaw(this, &SAnimationSequenceBrowser::GoToHistoryIndex, HistoryIdx) + FExecuteAction::CreateRaw(const_cast(this), &SAnimationSequenceBrowser::GoToHistoryIndex, HistoryIdx) ), NAME_None, EUserInterfaceActionType::Button); } @@ -833,7 +833,7 @@ TSharedRef SAnimationSequenceBrowser::CreateHistoryMenu(bool bInBackHis MenuBuilder.AddMenuEntry(DisplayName, Tooltip, FSlateIcon(), FUIAction( - FExecuteAction::CreateRaw(this, &SAnimationSequenceBrowser::GoToHistoryIndex, HistoryIdx) + FExecuteAction::CreateRaw(const_cast(this), &SAnimationSequenceBrowser::GoToHistoryIndex, HistoryIdx) ), NAME_None, EUserInterfaceActionType::Button); } diff --git a/Engine/Source/Editor/Persona/Private/SMorphTargetViewer.cpp b/Engine/Source/Editor/Persona/Private/SMorphTargetViewer.cpp index 4150c5638544..bdf6c36bc7c1 100644 --- a/Engine/Source/Editor/Persona/Private/SMorphTargetViewer.cpp +++ b/Engine/Source/Editor/Persona/Private/SMorphTargetViewer.cpp @@ -431,7 +431,7 @@ TSharedPtr SMorphTargetViewer::OnGetContextMenuContent() const FUIAction Action; { - Action.ExecuteAction = FExecuteAction::CreateSP(this, &SMorphTargetViewer::OnDeleteMorphTargets); + Action.ExecuteAction = FExecuteAction::CreateSP(const_cast(this), &SMorphTargetViewer::OnDeleteMorphTargets); Action.CanExecuteAction = FCanExecuteAction::CreateSP(this, &SMorphTargetViewer::CanPerformDelete); const FText Label = LOCTEXT("DeleteMorphTargetButtonLabel", "Delete"); const FText ToolTipText = LOCTEXT("DeleteMorphTargetButtonTooltip", "Deletes the selected morph targets."); @@ -439,7 +439,7 @@ TSharedPtr SMorphTargetViewer::OnGetContextMenuContent() const } { - Action.ExecuteAction = FExecuteAction::CreateSP(this, &SMorphTargetViewer::OnCopyMorphTargetNames); + Action.ExecuteAction = FExecuteAction::CreateSP(const_cast(this), &SMorphTargetViewer::OnCopyMorphTargetNames); Action.CanExecuteAction = nullptr; const FText Label = LOCTEXT("CopyMorphTargetNamesButtonLabel", "Copy Names"); const FText ToolTipText = LOCTEXT("CopyMorphTargetNamesButtonTooltip", "Copy the names of selected morph targets to clipboard"); diff --git a/Engine/Source/Editor/Persona/Private/SPoseEditor.cpp b/Engine/Source/Editor/Persona/Private/SPoseEditor.cpp index b37afc8c146a..f7fb2ca09d87 100644 --- a/Engine/Source/Editor/Persona/Private/SPoseEditor.cpp +++ b/Engine/Source/Editor/Persona/Private/SPoseEditor.cpp @@ -640,7 +640,7 @@ TSharedPtr SPoseViewer::OnGetContextMenuContentForCurveList() const MenuBuilder.BeginSection("CurveAction", LOCTEXT("CurveActions", "Selected Item Actions")); { - FUIAction Action = FUIAction(FExecuteAction::CreateSP(this, &SPoseViewer::OnDeleteCurves), + FUIAction Action = FUIAction(FExecuteAction::CreateSP(const_cast(this), &SPoseViewer::OnDeleteCurves), FCanExecuteAction::CreateSP(this, &SPoseViewer::IsCurveSelected)); const FText MenuLabel = LOCTEXT("DeleteCurveButtonLabel", "Delete"); const FText MenuToolTip = LOCTEXT("DeleteCurveButtonTooltip", "Deletes the selected animation curve."); diff --git a/Engine/Source/Editor/Persona/Private/SRetargetSourceWindow.cpp b/Engine/Source/Editor/Persona/Private/SRetargetSourceWindow.cpp index 29cb6626eb0c..593f70ddae5b 100644 --- a/Engine/Source/Editor/Persona/Private/SRetargetSourceWindow.cpp +++ b/Engine/Source/Editor/Persona/Private/SRetargetSourceWindow.cpp @@ -337,7 +337,7 @@ TSharedPtr SRetargetSourceWindow::OnGetContextMenuContent() const MenuBuilder.BeginSection("RetargetSourceAction", LOCTEXT( "New", "New" ) ); { - FUIAction Action = FUIAction( FExecuteAction::CreateSP( this, &SRetargetSourceWindow::OnAddRetargetSource ) ); + FUIAction Action = FUIAction( FExecuteAction::CreateSP( const_cast(this), &SRetargetSourceWindow::OnAddRetargetSource ) ); const FText Label = LOCTEXT("AddRetargetSourceActionLabel", "Add..."); const FText ToolTipText = LOCTEXT("AddRetargetSourceActionTooltip", "Add new retarget source."); MenuBuilder.AddMenuEntry( Label, ToolTipText, FSlateIcon(), Action); @@ -346,21 +346,21 @@ TSharedPtr SRetargetSourceWindow::OnGetContextMenuContent() const MenuBuilder.BeginSection("RetargetSourceAction", LOCTEXT( "Selected", "Selected Item Actions" ) ); { - FUIAction Action = FUIAction( FExecuteAction::CreateSP( this, &SRetargetSourceWindow::OnRenameRetargetSource ), + FUIAction Action = FUIAction( FExecuteAction::CreateSP( const_cast(this), &SRetargetSourceWindow::OnRenameRetargetSource ), FCanExecuteAction::CreateSP( this, &SRetargetSourceWindow::CanPerformRename ) ); const FText Label = LOCTEXT("RenameRetargetSourceActionLabel", "Rename"); const FText ToolTipText = LOCTEXT("RenameRetargetSourceActionTooltip", "Rename the selected retarget source."); MenuBuilder.AddMenuEntry( Label, ToolTipText, FSlateIcon(), Action); } { - FUIAction Action = FUIAction( FExecuteAction::CreateSP( this, &SRetargetSourceWindow::OnDeleteRetargetSource ), + FUIAction Action = FUIAction( FExecuteAction::CreateSP( const_cast(this), &SRetargetSourceWindow::OnDeleteRetargetSource ), FCanExecuteAction::CreateSP( this, &SRetargetSourceWindow::CanPerformDelete ) ); const FText Label = LOCTEXT("DeleteRetargetSourceActionLabel", "Delete"); const FText ToolTipText = LOCTEXT("DeleteRetargetSourceActionTooltip", "Deletes the selected retarget sources."); MenuBuilder.AddMenuEntry( Label, ToolTipText, FSlateIcon(), Action); } { - FUIAction Action = FUIAction( FExecuteAction::CreateSP( this, &SRetargetSourceWindow::OnRefreshRetargetSource, false ), + FUIAction Action = FUIAction( FExecuteAction::CreateSP( const_cast(this), &SRetargetSourceWindow::OnRefreshRetargetSource, false ), FCanExecuteAction::CreateSP( this, &SRetargetSourceWindow::CanPerformRefresh ) ); const FText Label = LOCTEXT("RefreshRetargetSourceActionLabel", "Update"); const FText ToolTipText = LOCTEXT("RefreshRetargetSourceActionTooltip", "Updates the selected retarget sources from source mesh."); diff --git a/Engine/Source/Editor/Persona/Private/SSkeletonAnimNotifies.cpp b/Engine/Source/Editor/Persona/Private/SSkeletonAnimNotifies.cpp index b66aa46a9f2b..a2cbabada481 100644 --- a/Engine/Source/Editor/Persona/Private/SSkeletonAnimNotifies.cpp +++ b/Engine/Source/Editor/Persona/Private/SSkeletonAnimNotifies.cpp @@ -166,7 +166,7 @@ TSharedPtr SSkeletonAnimNotifies::OnGetContextMenuContent() const { MenuBuilder.BeginSection("AnimNotifyAction", LOCTEXT("SelectedSyncMarkerActions", "Selected Sync Marker Actions")); { - FUIAction Action = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonAnimNotifies::OnDeleteSyncMarker)); + FUIAction Action = FUIAction(FExecuteAction::CreateSP(const_cast(this), &SSkeletonAnimNotifies::OnDeleteSyncMarker)); const FText Label = LOCTEXT("DeleteSyncMarkerButtonLabel", "Delete"); const FText ToolTipText = LOCTEXT("DeleteSyncMarkerButtonTooltip", "Deletes the sync marker from the suggestions"); MenuBuilder.AddMenuEntry(Label, ToolTipText, FSlateIcon(), Action); @@ -177,7 +177,7 @@ TSharedPtr SSkeletonAnimNotifies::OnGetContextMenuContent() const { MenuBuilder.BeginSection("AnimNotifyAction", LOCTEXT("AnimNotifyActions", "Notifies")); { - FUIAction Action = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonAnimNotifies::OnAddAnimNotify)); + FUIAction Action = FUIAction(FExecuteAction::CreateSP(const_cast(this), &SSkeletonAnimNotifies::OnAddAnimNotify)); const FText Label = LOCTEXT("NewAnimNotifyButtonLabel", "New..."); const FText ToolTipText = LOCTEXT("NewAnimNotifyButtonTooltip", "Creates a new anim notify."); MenuBuilder.AddMenuEntry(Label, ToolTipText, FSlateIcon(), Action); @@ -187,7 +187,7 @@ TSharedPtr SSkeletonAnimNotifies::OnGetContextMenuContent() const MenuBuilder.BeginSection("AnimNotifyAction", LOCTEXT("SelectedAnimNotifyActions", "Selected Notify Actions")); { { - FUIAction Action = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonAnimNotifies::OnRenameAnimNotify), + FUIAction Action = FUIAction(FExecuteAction::CreateSP(const_cast(this), &SSkeletonAnimNotifies::OnRenameAnimNotify), FCanExecuteAction::CreateSP(this, &SSkeletonAnimNotifies::CanPerformRename)); const FText Label = LOCTEXT("RenameAnimNotifyButtonLabel", "Rename"); const FText ToolTipText = LOCTEXT("RenameAnimNotifyButtonTooltip", "Renames the selected anim notifies."); @@ -195,7 +195,7 @@ TSharedPtr SSkeletonAnimNotifies::OnGetContextMenuContent() const } { - FUIAction Action = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonAnimNotifies::OnDeleteAnimNotify), + FUIAction Action = FUIAction(FExecuteAction::CreateSP(const_cast(this), &SSkeletonAnimNotifies::OnDeleteAnimNotify), FCanExecuteAction::CreateSP(this, &SSkeletonAnimNotifies::CanPerformDelete)); const FText Label = LOCTEXT("DeleteAnimNotifyButtonLabel", "Delete"); const FText ToolTipText = LOCTEXT("DeleteAnimNotifyButtonTooltip", "Deletes the selected anim notifies."); diff --git a/Engine/Source/Editor/Persona/Private/SSkeletonSlotNames.cpp b/Engine/Source/Editor/Persona/Private/SSkeletonSlotNames.cpp index 3ee809422426..2ca31d2b7e32 100644 --- a/Engine/Source/Editor/Persona/Private/SSkeletonSlotNames.cpp +++ b/Engine/Source/Editor/Persona/Private/SSkeletonSlotNames.cpp @@ -258,8 +258,8 @@ TSharedPtr SSkeletonSlotNames::OnGetContextMenuContent() const { FDisplayedSlotNameInfo* SlotInfo = SelectedItemPtr.Get(); - FUIAction Action = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonSlotNames::OnDeleteSlotGroup, SlotInfo->Name)); - Action.CanExecuteAction.BindSP(this, &SSkeletonSlotNames::CanDeleteSlotGroup, SlotInfo->Name); + FUIAction Action = FUIAction(FExecuteAction::CreateSP(const_cast(this), &SSkeletonSlotNames::OnDeleteSlotGroup, SlotInfo->Name)); + Action.CanExecuteAction.BindSP(const_cast(this), &SSkeletonSlotNames::CanDeleteSlotGroup, SlotInfo->Name); const FText Label = LOCTEXT("AnimSlotManagerContextMenuDeleteSlotGroupLabel", "Delete Slot Group"); const FText ToolTipText = LOCTEXT("AnimSlotManagerContextMenuDeleteSlotGroupTooltip", "Delete this slot group."); MenuBuilder.AddMenuEntry(Label, ToolTipText, FSlateIcon(), Action); @@ -276,13 +276,13 @@ TSharedPtr SSkeletonSlotNames::OnGetContextMenuContent() const MenuBuilder.AddSubMenu( FText::Format(LOCTEXT("ContextMenuSetSlotGroupLabel", "Set Slot {0} Group to"), FText::FromName(SelectedItems[0].Get()->Name)), FText::Format(LOCTEXT("ContextMenuSetSlotGroupToolTip", "Set Slot {0} Group"), FText::FromName(SelectedItems[0].Get()->Name)), - FNewMenuDelegate::CreateRaw(this, &SSkeletonSlotNames::FillSetSlotGroupSubMenu)); + FNewMenuDelegate::CreateRaw(const_cast(this), &SSkeletonSlotNames::FillSetSlotGroupSubMenu)); } // Rename Slot { FDisplayedSlotNameInfo* SlotInfo = SelectedItemPtr.Get(); - FUIAction Action = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonSlotNames::OnRenameSlot, SlotInfo->Name)); + FUIAction Action = FUIAction(FExecuteAction::CreateSP(const_cast(this), &SSkeletonSlotNames::OnRenameSlot, SlotInfo->Name)); const FText Label = LOCTEXT("AnimSlotManagerContextMenuRenameSlotLabel", "Rename Slot"); const FText ToolTipText = LOCTEXT("AnimSlotManagerContextMenuRenameSlotTooltip", "Rename this slot"); MenuBuilder.AddMenuEntry(Label, ToolTipText, FSlateIcon(), Action); @@ -291,7 +291,7 @@ TSharedPtr SSkeletonSlotNames::OnGetContextMenuContent() const { FDisplayedSlotNameInfo* SlotInfo = SelectedItemPtr.Get(); - FUIAction Action = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonSlotNames::OnDeleteSlot, SlotInfo->Name)); + FUIAction Action = FUIAction(FExecuteAction::CreateSP(const_cast(this), &SSkeletonSlotNames::OnDeleteSlot, SlotInfo->Name)); const FText Label = LOCTEXT("AnimSlotManagerContextMenuDeleteSlotLabel", "Delete Slot"); const FText ToolTipText = LOCTEXT("AnimSlotManagerContextMenuDeleteSlotTooltip", "Delete this slot."); MenuBuilder.AddMenuEntry(Label, ToolTipText, FSlateIcon(), Action); @@ -302,14 +302,14 @@ TSharedPtr SSkeletonSlotNames::OnGetContextMenuContent() const MenuBuilder.BeginSection("SlotManagerGeneralActions", LOCTEXT("SlotManagerGeneralActions", "Slot Manager Actions")); // Add Slot { - FUIAction Action = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonSlotNames::OnAddSlot)); + FUIAction Action = FUIAction(FExecuteAction::CreateSP(const_cast(this), &SSkeletonSlotNames::OnAddSlot)); const FText Label = LOCTEXT("AnimSlotManagerContextMenuAddSlotLabel", "Add Slot"); const FText ToolTipText = LOCTEXT("AnimSlotManagerContextMenuAddSlotTooltip", "Adds a new Slot"); MenuBuilder.AddMenuEntry(Label, ToolTipText, FSlateIcon(), Action); } // Add Group { - FUIAction Action = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonSlotNames::OnAddGroup)); + FUIAction Action = FUIAction(FExecuteAction::CreateSP(const_cast(this), &SSkeletonSlotNames::OnAddGroup)); const FText Label = LOCTEXT("AnimSlotManagerContextMenuAddGroupLabel", "Add Group"); const FText ToolTipText = LOCTEXT("AnimSlotManagerContextMenuAddGroupTooltip", "Adds a new Group"); MenuBuilder.AddMenuEntry(Label, ToolTipText, FSlateIcon(), Action); diff --git a/Engine/Source/Editor/Persona/Private/SSlotNameReferenceWindow.cpp b/Engine/Source/Editor/Persona/Private/SSlotNameReferenceWindow.cpp index 711c967807fa..4efc401e298a 100644 --- a/Engine/Source/Editor/Persona/Private/SSlotNameReferenceWindow.cpp +++ b/Engine/Source/Editor/Persona/Private/SSlotNameReferenceWindow.cpp @@ -222,7 +222,7 @@ void SSlotNameReferenceWindow::UpdateInfo(FReferenceWindowInfo& UpdatedInfo) // Sort for nicer display UpdatedInfo.ReferencingMontages->Sort([](const FAssetData& A, const FAssetData& B) { - return A.AssetName < B.AssetName; + return A.AssetName.LexicalLess(B.AssetName); }); ReferencingMontages.Empty(UpdatedInfo.ReferencingMontages->Num()); diff --git a/Engine/Source/Editor/Persona/Private/TabSpawners.cpp b/Engine/Source/Editor/Persona/Private/TabSpawners.cpp index 157f44ccf6a9..0573f1070e09 100644 --- a/Engine/Source/Editor/Persona/Private/TabSpawners.cpp +++ b/Engine/Source/Editor/Persona/Private/TabSpawners.cpp @@ -473,7 +473,7 @@ TSharedRef FAnimBlueprintPreviewEditorSummoner::CreateTabBody(const FWo SNew(SCheckBox) .Style(FEditorStyle::Get(), "RadioButton") .IsChecked(this, &FAnimBlueprintPreviewEditorSummoner::IsChecked, EAnimBlueprintEditorMode::PreviewMode) - .OnCheckStateChanged(this, &FAnimBlueprintPreviewEditorSummoner::OnCheckedChanged, EAnimBlueprintEditorMode::PreviewMode) + .OnCheckStateChanged(const_cast(this), &FAnimBlueprintPreviewEditorSummoner::OnCheckedChanged, EAnimBlueprintEditorMode::PreviewMode) .ToolTip(IDocumentation::Get()->CreateToolTip( LOCTEXT("AnimBlueprintPropertyEditorPreviewMode", "Switch to editing the preview instance properties"), NULL, TEXT("Shared/Editors/Persona"), @@ -495,7 +495,7 @@ TSharedRef FAnimBlueprintPreviewEditorSummoner::CreateTabBody(const FWo SNew(SCheckBox) .Style(FEditorStyle::Get(), "RadioButton") .IsChecked(this, &FAnimBlueprintPreviewEditorSummoner::IsChecked, EAnimBlueprintEditorMode::DefaultsMode) - .OnCheckStateChanged(this, &FAnimBlueprintPreviewEditorSummoner::OnCheckedChanged, EAnimBlueprintEditorMode::DefaultsMode) + .OnCheckStateChanged(const_cast(this), &FAnimBlueprintPreviewEditorSummoner::OnCheckedChanged, EAnimBlueprintEditorMode::DefaultsMode) .ToolTip(IDocumentation::Get()->CreateToolTip( LOCTEXT("AnimBlueprintPropertyEditorDefaultMode", "Switch to editing the class defaults"), NULL, TEXT("Shared/Editors/Persona"), @@ -630,8 +630,8 @@ TSharedRef FAdvancedPreviewSceneTabSummoner::CreateTabBody(const FWorkf TArray DetailsCustomizations; TArray PropertyTypeCustomizations; - DetailsCustomizations.Add({ UPersonaPreviewSceneDescription::StaticClass(), FOnGetDetailCustomizationInstance::CreateSP(this, &FAdvancedPreviewSceneTabSummoner::CustomizePreviewSceneDescription) }); - PropertyTypeCustomizations.Add({ FPreviewMeshCollectionEntry::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateSP(this, &FAdvancedPreviewSceneTabSummoner::CustomizePreviewMeshCollectionEntry) }); + DetailsCustomizations.Add({ UPersonaPreviewSceneDescription::StaticClass(), FOnGetDetailCustomizationInstance::CreateSP(const_cast(this), &FAdvancedPreviewSceneTabSummoner::CustomizePreviewSceneDescription) }); + PropertyTypeCustomizations.Add({ FPreviewMeshCollectionEntry::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateSP(const_cast(this), &FAdvancedPreviewSceneTabSummoner::CustomizePreviewMeshCollectionEntry) }); FAdvancedPreviewSceneModule& AdvancedPreviewSceneModule = FModuleManager::LoadModuleChecked("AdvancedPreviewScene"); return AdvancedPreviewSceneModule.CreateAdvancedPreviewSceneSettingsWidget(PreviewSceneRef, PreviewSceneRef->GetPreviewSceneDescription(), DetailsCustomizations, PropertyTypeCustomizations); diff --git a/Engine/Source/Editor/PinnedCommandList/Private/SPinnedCommandList.cpp b/Engine/Source/Editor/PinnedCommandList/Private/SPinnedCommandList.cpp index 382c1f23539d..2e41f3c49146 100644 --- a/Engine/Source/Editor/PinnedCommandList/Private/SPinnedCommandList.cpp +++ b/Engine/Source/Editor/PinnedCommandList/Private/SPinnedCommandList.cpp @@ -722,7 +722,7 @@ void SPinnedCommandList::SortCommands() } // fallback to lexical sort - return CommandIdentifier0 < CommandIdentifier1; + return CommandIdentifier0.LexicalLess(CommandIdentifier1); }); RefreshCommandWidgets(); diff --git a/Engine/Source/Editor/PinnedCommandList/Private/UICommandList_Pinnable.cpp b/Engine/Source/Editor/PinnedCommandList/Private/UICommandList_Pinnable.cpp index 645bec441f10..9fe6e8380ffc 100644 --- a/Engine/Source/Editor/PinnedCommandList/Private/UICommandList_Pinnable.cpp +++ b/Engine/Source/Editor/PinnedCommandList/Private/UICommandList_Pinnable.cpp @@ -26,7 +26,7 @@ void FUICommandList_Pinnable::MapAction( const TSharedPtr< const FUICommandInfo FUICommandList::MapAction(InUICommandInfo, InUIAction); // Map the index and group - int32 ActionIndex = InUICommandInfo->GetBindingContext().GetComparisonIndex() + CurrentActionIndex; + int32 ActionIndex = InUICommandInfo->GetBindingContext().GetComparisonIndex().ToUnstableInt() + CurrentActionIndex; CurrentActionIndex++; CommandIndexMap.Add(InUICommandInfo->GetCommandName(), ActionIndex); diff --git a/Engine/Source/Editor/PlacementMode/Private/SPlacementModeTools.cpp b/Engine/Source/Editor/PlacementMode/Private/SPlacementModeTools.cpp index b3cacfdf8e5e..65625990f260 100644 --- a/Engine/Source/Editor/PlacementMode/Private/SPlacementModeTools.cpp +++ b/Engine/Source/Editor/PlacementMode/Private/SPlacementModeTools.cpp @@ -321,7 +321,7 @@ void SPlacementModeTools::Construct( const FArguments& InArgs ) } TSharedRef ScrollBar = SNew(SScrollBar) - .Thickness(FVector2D(5, 5)); + .Thickness(FVector2D(9.0f, 9.0f)); ChildSlot [ diff --git a/Engine/Source/Editor/PropertyEditor/Private/DetailPropertyRow.cpp b/Engine/Source/Editor/PropertyEditor/Private/DetailPropertyRow.cpp index 90e123676691..157dd1c64cfb 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/DetailPropertyRow.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/DetailPropertyRow.cpp @@ -13,6 +13,7 @@ #include "ObjectPropertyNode.h" #include "DetailWidgetRow.h" #include "CategoryPropertyNode.h" +#include "Widgets/Layout/SSpacer.h" const float FDetailWidgetRow::DefaultValueMinWidth = 125.0f; const float FDetailWidgetRow::DefaultValueMaxWidth = 125.0f; @@ -261,6 +262,11 @@ void FDetailPropertyRow::OnGenerateChildren( FDetailNodeList& OutChildren ) const bool bIgnoreAdvancedDropdown = true; MyCategory->GetGeneratedChildren(OutChildren, bIgnoreVisibility, bIgnoreAdvancedDropdown); } + else + { + // Fall back to the default if we can't find the category implementation + GenerateChildrenForPropertyNode(PropertyNode, OutChildren); + } } else if (PropertyNode->AsCategoryNode() || PropertyNode->GetProperty() || ExternalObjectLayout.IsValid()) { @@ -690,15 +696,30 @@ void FDetailPropertyRow::MakeValueWidget( FDetailWidgetRow& Row, const TSharedPt .IsEnabled( IsEnabledAttrib ); TSharedPtr ResetButton = nullptr; - if (!PropertyHandle->HasMetaData(TEXT("NoResetToDefault")) && !PropertyHandle->IsResetToDefaultCustomized()) + TSharedPtr ResetWidget = nullptr; + if (!PropertyHandle->HasMetaData(TEXT("NoResetToDefault"))) { - SAssignNew(ResetButton, SResetToDefaultPropertyEditor, PropertyEditor->GetPropertyHandle()) - .IsEnabled(IsEnabledAttrib) - .CustomResetToDefault(CustomResetToDefault); + if (PropertyHandle->IsResetToDefaultCustomized()) + { + // FIXME: Workaround for JIRA UE-73210. + // We had an oscillating SPropertyValueWidget width while dragging a UMG widget in the designer. + // The way drag&drop is implemented (SDesignerView::ProcessDropAndAddWidget), a new UCanvasPanelSlot gets + // recreated every frame, so the details panel gets refreshed every frame. Since new property rows are created + // before old ones are destroyed in the details panel, the HasCustomResetToDefault flag on the property node + // toggles from frame to frame, so we alternate between having a ResetToDefaultPropertyEditor and not having one. + // By having a spacer fill the blank, the property row layout doesn't change while dragging, but we still see + // a flashing yellow reset arrow (when visible). + const FSlateBrush* DiffersFromDefaultBrush = FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault"); + ResetWidget = SNew(SSpacer).Size(DiffersFromDefaultBrush != nullptr ? DiffersFromDefaultBrush->ImageSize : FVector2D(8.0f, 8.0f)); + } + else + { + SAssignNew(ResetButton, SResetToDefaultPropertyEditor, PropertyEditor->GetPropertyHandle()) + .IsEnabled(IsEnabledAttrib) + .CustomResetToDefault(CustomResetToDefault); + ResetWidget = ResetButton; + } }; - - - TSharedRef ResetWidget = ResetButton.IsValid() ? ResetButton.ToSharedRef() : SNullWidget::NullWidget; TSharedPtr PropertyValue; @@ -718,7 +739,7 @@ void FDetailPropertyRow::MakeValueWidget( FDetailWidgetRow& Row, const TSharedPt [ SAssignNew( PropertyValue, SPropertyValueWidget, PropertyEditor, GetPropertyUtilities() ) .ShowPropertyButtons( false ) // We handle this ourselves - .OptionalResetWidget(ResetWidget) + .OptionalResetWidget(ResetButton.IsValid() ? ResetButton.ToSharedRef() : SNullWidget::NullWidget) ]; MinWidth = PropertyValue->GetMinDesiredWidth(); MaxWidth = PropertyValue->GetMaxDesiredWidth(); @@ -757,7 +778,7 @@ void FDetailPropertyRow::MakeValueWidget( FDetailWidgetRow& Row, const TSharedPt } if ((!PropertyValue.IsValid() || (PropertyValue.IsValid() && !PropertyValue->CreatedResetButton())) - && ResetButton.IsValid()) + && ResetWidget.IsValid()) { ValueWidget->AddSlot() .Padding(4.0f, 0.0f) @@ -765,7 +786,7 @@ void FDetailPropertyRow::MakeValueWidget( FDetailWidgetRow& Row, const TSharedPt .VAlign(VAlign_Center) .HAlign(HAlign_Left) [ - ResetWidget + ResetWidget.ToSharedRef() ]; } } diff --git a/Engine/Source/Editor/PropertyEditor/Private/EditConditionContext.cpp b/Engine/Source/Editor/PropertyEditor/Private/EditConditionContext.cpp new file mode 100644 index 000000000000..b241813e1c8c --- /dev/null +++ b/Engine/Source/Editor/PropertyEditor/Private/EditConditionContext.cpp @@ -0,0 +1,245 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "EditConditionContext.h" +#include "EditConditionParser.h" + +#include "PropertyNode.h" +#include "PropertyEditorHelpers.h" + +DEFINE_LOG_CATEGORY_STATIC(LogEditCondition, Log, All); + +FEditConditionContext::FEditConditionContext(FPropertyNode& InPropertyNode) +{ + PropertyNode = InPropertyNode.AsShared(); + + FComplexPropertyNode* ComplexParentNode = FindComplexParent(); + check(ComplexParentNode); + + const UProperty* Property = InPropertyNode.GetProperty(); + check(Property); +} + +FComplexPropertyNode* FEditConditionContext::FindComplexParent() const +{ + if (!PropertyNode.IsValid()) + { + return nullptr; + } + + TSharedPtr PinnedNode = PropertyNode.Pin(); + + FPropertyNode* SearchStart = PinnedNode.Get(); + if (PropertyEditorHelpers::IsStaticArray(*SearchStart)) + { + //in the case of conditional static arrays, we have to go up one more level to get the proper parent struct. + SearchStart = SearchStart->GetParentNode(); + } + + return SearchStart->FindComplexParent(); +} + +const UBoolProperty* FEditConditionContext::GetSingleBoolProperty(const TSharedPtr& Expression) const +{ + if (!PropertyNode.IsValid()) + { + return nullptr; + } + + const UProperty* Property = PropertyNode.Pin()->GetProperty(); + + const UBoolProperty* BoolProperty = nullptr; + for (const FCompiledToken& Token : Expression->Tokens) + { + if (const EditConditionParserTokens::FPropertyToken* PropertyToken = Token.Node.Cast()) + { + if (BoolProperty != nullptr) + { + // second property token + return nullptr; + } + + const UProperty* Field = FindField(Property->GetOwnerStruct(), *PropertyToken->PropertyName); + BoolProperty = Cast(Field); + + // not a bool + if (BoolProperty == nullptr) + { + return nullptr; + } + } + } + + return BoolProperty; +} + +template +T* FindTypedField(const TWeakPtr& PropertyNode, const FString& PropertyName) +{ + if (PropertyNode.IsValid()) + { + TSharedPtr PinnedNode = PropertyNode.Pin(); + const UProperty* Property = PinnedNode->GetProperty(); + + UProperty* Field = FindField(Property->GetOwnerStruct(), *PropertyName); + if (Field == nullptr) + { + UE_LOG(LogEditCondition, Error, TEXT("EditCondition parsing failed: Field name %s was not found in struct %s."), *PropertyName, *Property->GetOwnerStruct()->GetName()); + return nullptr; + } + + return Cast(Field); + } + + return nullptr; +} + +TOptional FEditConditionContext::GetBoolValue(const FString& PropertyName) const +{ + const UBoolProperty* BoolProperty = FindTypedField(PropertyNode, PropertyName); + if (BoolProperty == nullptr) + { + return TOptional(); + } + + TSharedPtr PinnedNode = PropertyNode.Pin(); + + TOptional Result; + + FComplexPropertyNode* ComplexParentNode = FindComplexParent(); + for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) + { + uint8* BasePtr = ComplexParentNode->GetMemoryOfInstance(Index); + uint8* ValuePtr = BoolProperty->ContainerPtrToValuePtr(BasePtr); + + bool bValue = BoolProperty->GetPropertyValue(ValuePtr); + if (!Result.IsSet()) + { + Result = bValue; + } + else if (Result.GetValue() != bValue) + { + // all values aren't the same... + return TOptional(); + } + } + + return Result; +} + +TOptional FEditConditionContext::GetNumericValue(const FString& PropertyName) const +{ + const UNumericProperty* NumericProperty = FindTypedField(PropertyNode, PropertyName); + if (NumericProperty == nullptr) + { + return TOptional(); + } + + TSharedPtr PinnedNode = PropertyNode.Pin(); + + TOptional Result; + + FComplexPropertyNode* ComplexParentNode = FindComplexParent(); + for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) + { + uint8* BasePtr = ComplexParentNode->GetMemoryOfInstance(Index); + uint8* ValuePtr = NumericProperty->ContainerPtrToValuePtr(BasePtr); + + double Value = 0; + + if (NumericProperty->IsInteger()) + { + Value = (double) NumericProperty->GetSignedIntPropertyValue(ValuePtr); + } + else if (NumericProperty->IsFloatingPoint()) + { + Value = NumericProperty->GetFloatingPointPropertyValue(ValuePtr); + } + + if (!Result.IsSet()) + { + Result = Value; + } + else if (!FMath::IsNearlyEqual(Result.GetValue(), Value)) + { + // all values aren't the same... + return TOptional(); + } + } + + return Result; +} + +TOptional FEditConditionContext::GetEnumValue(const FString& PropertyName) const +{ + const UProperty* Property = FindTypedField(PropertyNode, PropertyName); + if (Property == nullptr) + { + return TOptional(); + } + + const UEnum* EnumType = nullptr; + const UNumericProperty* NumericProperty = nullptr; + if (const UEnumProperty* EnumProperty = Cast(Property)) + { + NumericProperty = EnumProperty->GetUnderlyingProperty(); + EnumType = EnumProperty->GetEnum(); + } + else if (const UByteProperty* ByteProperty = Cast(Property)) + { + NumericProperty = ByteProperty; + EnumType = ByteProperty->GetIntPropertyEnum(); + } + else + { + return TOptional(); + } + + TSharedPtr PinnedNode = PropertyNode.Pin(); + + TOptional Result; + + FComplexPropertyNode* ComplexParentNode = FindComplexParent(); + for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) + { + uint8* BasePtr = ComplexParentNode->GetMemoryOfInstance(Index); + uint8* ValuePtr = Property->ContainerPtrToValuePtr(BasePtr); + + int64 Value = NumericProperty->GetSignedIntPropertyValue(ValuePtr); + if (!Result.IsSet()) + { + Result = Value; + } + else if (Result.GetValue() != Value) + { + // all values aren't the same... + return TOptional(); + } + } + + if (Result.IsSet()) + { + return EnumType->GetNameStringByValue(Result.GetValue()); + } + + return TOptional(); +} + +TOptional FEditConditionContext::GetTypeName(const FString& PropertyName) const +{ + const UProperty* Property = FindTypedField(PropertyNode, PropertyName); + if (Property == nullptr) + { + return TOptional(); + } + + if (const UEnumProperty* EnumProperty = Cast(Property)) + { + return EnumProperty->GetEnum()->GetName(); + } + else if (const UByteProperty* ByteProperty = Cast(Property)) + { + return ByteProperty->GetIntPropertyEnum()->GetName(); + } + + return Property->GetCPPType(); +} \ No newline at end of file diff --git a/Engine/Source/Editor/PropertyEditor/Private/EditConditionContext.h b/Engine/Source/Editor/PropertyEditor/Private/EditConditionContext.h new file mode 100644 index 000000000000..10f95aeaaf92 --- /dev/null +++ b/Engine/Source/Editor/PropertyEditor/Private/EditConditionContext.h @@ -0,0 +1,42 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +class IEditConditionContext +{ +public: + virtual TOptional GetBoolValue(const FString& PropertyName) const = 0; + virtual TOptional GetNumericValue(const FString& PropertyName) const = 0; + virtual TOptional GetEnumValue(const FString& PropertyName) const = 0; + virtual TOptional GetTypeName(const FString& PropertyName) const = 0; +}; + +class UProperty; +class FPropertyNode; +class FComplexPropertyNode; +class FEditConditionExpression; + +class FEditConditionContext : public IEditConditionContext +{ +public: + FEditConditionContext(FPropertyNode& InPropertyNode); + virtual ~FEditConditionContext() {} + + virtual TOptional GetBoolValue(const FString& PropertyName) const override; + virtual TOptional GetNumericValue(const FString& PropertyName) const override; + virtual TOptional GetEnumValue(const FString& PropertyName) const override; + virtual TOptional GetTypeName(const FString& PropertyName) const override; + + /** + * Fetch the single boolean property referenced. + * Returns nullptr if more than one property is referenced. + */ + const UBoolProperty* GetSingleBoolProperty(const TSharedPtr& Expression) const; + +private: + TWeakPtr PropertyNode; + + FComplexPropertyNode* FindComplexParent() const; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/PropertyEditor/Private/EditConditionParser.cpp b/Engine/Source/Editor/PropertyEditor/Private/EditConditionParser.cpp new file mode 100644 index 000000000000..320ba695584b --- /dev/null +++ b/Engine/Source/Editor/PropertyEditor/Private/EditConditionParser.cpp @@ -0,0 +1,675 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "EditConditionParser.h" +#include "EditConditionContext.h" + +#include "Math/BasicMathExpressionEvaluator.h" +#include "Misc/ExpressionParser.h" + +#define LOCTEXT_NAMESPACE "EditConditionParser" + +namespace EditConditionParserTokens +{ + const TCHAR* const FEqual::Moniker = TEXT("=="); + const TCHAR* const FNotEqual::Moniker = TEXT("!="); + const TCHAR* const FGreater::Moniker = TEXT(">"); + const TCHAR* const FGreaterEqual::Moniker = TEXT(">="); + const TCHAR* const FLess::Moniker = TEXT("<"); + const TCHAR* const FLessEqual::Moniker = TEXT("<="); + const TCHAR* const FNot::Moniker = TEXT("!"); + const TCHAR* const FAnd::Moniker = TEXT("&&"); + const TCHAR* const FOr::Moniker = TEXT("||"); + const TCHAR* const FAdd::Moniker = TEXT("+"); + const TCHAR* const FSubtract::Moniker = TEXT("-"); + const TCHAR* const FMultiply::Moniker = TEXT("*"); + const TCHAR* const FDivide::Moniker = TEXT("/"); +} + +static const TCHAR PropertyBreakingChars[] = { '|', '=', '&', '>', '<', '!', '+', '-', '*', '/', ' ', '\t' }; + +static TOptional ConsumeBool(FExpressionTokenConsumer& Consumer) +{ + int TrueChars = 0; + int FalseChars = 0; + TOptional StringToken = Consumer.GetStream().ParseToken([&TrueChars, &FalseChars](TCHAR InC) + { + if (TEXT("true")[TrueChars] == InC) + { + ++TrueChars; + if (TrueChars == 4) + { + return EParseState::StopAfter; + } + else + { + return EParseState::Continue; + } + } + else if (TEXT("false")[FalseChars] == InC) + { + ++FalseChars; + if (FalseChars == 5) + { + return EParseState::StopAfter; + } + else + { + return EParseState::Continue; + } + } + return EParseState::Cancel; + }); + + if (StringToken.IsSet()) + { + bool bIsTrue = TrueChars == 4; + bool bIsFalse = FalseChars == 5; + ensure(bIsTrue || bIsFalse); + + Consumer.Add(StringToken.GetValue(), bIsTrue); + } + + return TOptional(); +} + +static TOptional ConsumePropertyName(FExpressionTokenConsumer& Consumer) +{ + FString PropertyName; + bool bShouldBeEnum = false; + + TOptional StringToken = Consumer.GetStream().ParseToken([&PropertyName, &bShouldBeEnum](TCHAR InC) + { + for (const TCHAR BreakingChar : PropertyBreakingChars) + { + if (InC == BreakingChar) + { + return EParseState::StopBefore; + } + } + + if (InC == ':') + { + bShouldBeEnum = true; + } + + PropertyName.AppendChar(InC); + + return EParseState::Continue; + }); + + if (StringToken.IsSet()) + { + if (bShouldBeEnum) + { + int32 DoubleColonIndex = PropertyName.Find("::"); + if (DoubleColonIndex == INDEX_NONE) + { + return FExpressionError(FText::Format(LOCTEXT("PropertyContainsSingleColon", "EditCondition contains single colon in property name \"{0}\", expected double colons."), FText::FromString(PropertyName))); + } + + if (DoubleColonIndex == 0) + { + return FExpressionError(FText::Format(LOCTEXT("PropertyDoubleColonAtStart", "EditCondition contained double colon at start of property name \"{0}\", expected enum type."), FText::FromString(PropertyName))); + } + + FString EnumType = PropertyName.Left(DoubleColonIndex); + FString EnumValue = PropertyName.RightChop(DoubleColonIndex + 2); + + if (EnumValue.Len() == 0) + { + return FExpressionError(FText::Format(LOCTEXT("PropertyDoubleColonAtEnd", "EditCondition contained double colon at end of property name \"{0}\", expected enum value."), FText::FromString(PropertyName))); + } + + Consumer.Add(StringToken.GetValue(), EditConditionParserTokens::FEnumToken(MoveTemp(EnumType), MoveTemp(EnumValue))); + } + else + { + Consumer.Add(StringToken.GetValue(), EditConditionParserTokens::FPropertyToken(MoveTemp(PropertyName))); + } + } + + return TOptional(); +} + +template +TOptional GetValueInternal(const IEditConditionContext& Context, const FString& PropertyName) +{ + return TOptional(); +} + +template<> +TOptional GetValueInternal(const IEditConditionContext& Context, const FString& PropertyName) +{ + return Context.GetBoolValue(PropertyName); +} + +template<> +TOptional GetValueInternal(const IEditConditionContext& Context, const FString& PropertyName) +{ + return Context.GetNumericValue(PropertyName); +} + +template +struct TOperand +{ + TOperand(T InValue) : Value(InValue), Property(nullptr), Context(nullptr) {} + TOperand(const EditConditionParserTokens::FPropertyToken& InProperty, const IEditConditionContext& InContext) : + Property(&InProperty), Context(&InContext) {} + + bool IsProperty() const { return Property != nullptr; } + TOptional GetValue() const + { + if (IsProperty()) + { + return GetValueInternal(*Context, Property->PropertyName); + } + + return TOptional(Value); + } + + const FString& GetName() const { check(IsProperty()); return Property->PropertyName; } + +private: + T Value; + const EditConditionParserTokens::FPropertyToken* Property; + const IEditConditionContext* Context; +}; + +static FExpressionResult ApplyNot(TOperand A) +{ + TOptional Value = A.GetValue(); + if (Value.IsSet()) + { + return MakeValue(!Value.GetValue()); + } + return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(A.GetName()))); +} + +template +FExpressionResult ApplyBinary(TOperand A, TOperand B, Function Apply) +{ + TOptional ValueA = A.GetValue(); + if (!ValueA.IsSet()) + { + return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(A.GetName()))); + } + + TOptional ValueB = B.GetValue(); + if (!ValueB.IsSet()) + { + return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(B.GetName()))); + } + + return MakeValue(Apply(ValueA.GetValue(), ValueB.GetValue())); +} + +static FExpressionResult ApplyPropertiesEqual(const EditConditionParserTokens::FPropertyToken& A, const EditConditionParserTokens::FPropertyToken& B, const IEditConditionContext& Context, bool bNegate) +{ + TOptional TypeNameA = Context.GetTypeName(A.PropertyName); + TOptional TypeNameB = Context.GetTypeName(B.PropertyName); + if (!TypeNameA.IsSet()) + { + return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(A.PropertyName))); + } + + if (!TypeNameB.IsSet()) + { + return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(B.PropertyName))); + } + + if (TypeNameA.GetValue() != TypeNameB.GetValue()) + { + return MakeError(FText::Format(LOCTEXT("OperandTypeMismatch", "EditCondition attempted to compare operands of different types: \"{0}\" and \"{1}\"."), FText::FromString(A.PropertyName), FText::FromString(B.PropertyName))); + } + + TOptional bEqual; + + TOptional BoolA = Context.GetBoolValue(A.PropertyName); + TOptional BoolB = Context.GetBoolValue(B.PropertyName); + if (BoolA.IsSet() && BoolB.IsSet()) + { + bEqual = BoolA.GetValue() == BoolB.GetValue(); + } + + TOptional DoubleA = Context.GetNumericValue(A.PropertyName); + TOptional DoubleB = Context.GetNumericValue(B.PropertyName); + if (DoubleA.IsSet() && DoubleB.IsSet()) + { + bEqual = DoubleA.GetValue() == DoubleB.GetValue(); + } + + TOptional EnumA = Context.GetEnumValue(A.PropertyName); + TOptional EnumB = Context.GetEnumValue(B.PropertyName); + if (EnumA.IsSet() && EnumB.IsSet()) + { + bEqual = EnumA.GetValue() == EnumB.GetValue(); + } + + if (bEqual.IsSet()) + { + return MakeValue(bNegate ? !bEqual.GetValue() : bEqual.GetValue()); + } + + return MakeError(FText::Format(LOCTEXT("OperandTypeMismatch", "EditCondition attempted to compare operands of different types: \"{0}\" and \"{1}\"."), FText::FromString(A.PropertyName), FText::FromString(B.PropertyName))); +} + +static void CreateBooleanOperators(TOperatorJumpTable& OperatorJumpTable) +{ + using namespace EditConditionParserTokens; + + OperatorJumpTable.MapPreUnary([](bool A) { return !A; }); + OperatorJumpTable.MapPreUnary([](const FPropertyToken& A, const IEditConditionContext* Context) + { + return ApplyNot(TOperand(A, *Context)); + }); + + // AND + { + auto ApplyAnd = [](bool First, bool Second) { return First && Second; }; + + OperatorJumpTable.MapBinary(ApplyAnd); + OperatorJumpTable.MapBinary([ApplyAnd](const FPropertyToken& A, bool B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyAnd); + }); + OperatorJumpTable.MapBinary([ApplyAnd](bool A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyAnd); + }); + OperatorJumpTable.MapBinary([ApplyAnd](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyAnd); + }); + } + + // OR + { + auto ApplyOr = [](bool First, bool Second) { return First || Second; }; + + OperatorJumpTable.MapBinary(ApplyOr); + OperatorJumpTable.MapBinary([ApplyOr](const FPropertyToken& A, bool B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyOr); + }); + OperatorJumpTable.MapBinary([ApplyOr](bool A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyOr); + }); + OperatorJumpTable.MapBinary([ApplyOr](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyOr); + }); + } + + // EQUALS + { + auto ApplyEqual = [](bool First, bool Second) { return First == Second; }; + + OperatorJumpTable.MapBinary(ApplyEqual); + OperatorJumpTable.MapBinary([ApplyEqual](const FPropertyToken& A, bool B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyEqual); + }); + OperatorJumpTable.MapBinary([ApplyEqual](bool A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyEqual); + }); + } + + // NOT-EQUALS + { + auto ApplyNotEqual = [](bool First, bool Second) { return First != Second; }; + + OperatorJumpTable.MapBinary(ApplyNotEqual); + OperatorJumpTable.MapBinary([ApplyNotEqual](const FPropertyToken& A, bool B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyNotEqual); + }); + OperatorJumpTable.MapBinary([ApplyNotEqual](bool A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyNotEqual); + }); + } +} + +template +void CreateNumberOperators(TOperatorJumpTable& OperatorJumpTable) +{ + using namespace EditConditionParserTokens; + + // EQUAL + { + auto ApplyEqual = [](T First, T Second) { return First == Second; }; + + OperatorJumpTable.MapBinary(ApplyEqual); + OperatorJumpTable.MapBinary([ApplyEqual](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyEqual); + }); + OperatorJumpTable.MapBinary([ApplyEqual](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyEqual); + }); + } + + // NOT-EQUAL + { + auto ApplyNotEqual = [](T First, T Second) { return First != Second; }; + + OperatorJumpTable.MapBinary(ApplyNotEqual); + OperatorJumpTable.MapBinary([ApplyNotEqual](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyNotEqual); + }); + OperatorJumpTable.MapBinary([ApplyNotEqual](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyNotEqual); + }); + } + + // GREATER + { + auto ApplyGreater = [](T First, T Second) { return First > Second; }; + + OperatorJumpTable.MapBinary(ApplyGreater); + OperatorJumpTable.MapBinary([ApplyGreater](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyGreater); + }); + OperatorJumpTable.MapBinary([ApplyGreater](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyGreater); + }); + OperatorJumpTable.MapBinary([ApplyGreater](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyGreater); + }); + } + + // GREATER-EQUAL + { + auto ApplyGreaterEqual = [](T First, T Second) { return First >= Second; }; + + OperatorJumpTable.MapBinary(ApplyGreaterEqual); + OperatorJumpTable.MapBinary([ApplyGreaterEqual](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyGreaterEqual); + }); + OperatorJumpTable.MapBinary([ApplyGreaterEqual](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyGreaterEqual); + }); + OperatorJumpTable.MapBinary([ApplyGreaterEqual](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyGreaterEqual); + }); + } + + // LESS + { + auto ApplyLess = [](T First, T Second) { return First < Second; }; + + OperatorJumpTable.MapBinary(ApplyLess); + OperatorJumpTable.MapBinary([ApplyLess](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyLess); + }); + OperatorJumpTable.MapBinary([ApplyLess](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyLess); + }); + OperatorJumpTable.MapBinary([ApplyLess](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyLess); + }); + } + + // LESS-EQUAL + { + auto ApplyLessEqual = [](T First, T Second) { return First <= Second; }; + + OperatorJumpTable.MapBinary(ApplyLessEqual); + OperatorJumpTable.MapBinary([ApplyLessEqual](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyLessEqual); + }); + OperatorJumpTable.MapBinary([ApplyLessEqual](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyLessEqual); + }); + OperatorJumpTable.MapBinary([ApplyLessEqual](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyLessEqual); + }); + } + + // ADD + { + auto ApplyAdd = [](T First, T Second) { return First + Second; }; + + OperatorJumpTable.MapBinary(ApplyAdd); + OperatorJumpTable.MapBinary([ApplyAdd](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyAdd); + }); + OperatorJumpTable.MapBinary([ApplyAdd](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyAdd); + }); + OperatorJumpTable.MapBinary([ApplyAdd](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyAdd); + }); + } + + // SUBTRACT + { + auto ApplySubtract = [](T First, T Second) { return First - Second; }; + + OperatorJumpTable.MapBinary(ApplySubtract); + OperatorJumpTable.MapBinary([ApplySubtract](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplySubtract); + }); + OperatorJumpTable.MapBinary([ApplySubtract](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplySubtract); + }); + OperatorJumpTable.MapBinary([ApplySubtract](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplySubtract); + }); + } + + // MULTIPLY + { + auto ApplyMultiply = [](T First, T Second) { return First * Second; }; + + OperatorJumpTable.MapBinary(ApplyMultiply); + OperatorJumpTable.MapBinary([ApplyMultiply](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyMultiply); + }); + OperatorJumpTable.MapBinary([ApplyMultiply](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyMultiply); + }); + OperatorJumpTable.MapBinary([ApplyMultiply](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyMultiply); + }); + } + + // DIVIDE + { + auto ApplyDivide = [](T First, T Second) { return First / Second; }; + + OperatorJumpTable.MapBinary(ApplyDivide); + OperatorJumpTable.MapBinary([ApplyDivide](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyDivide); + }); + OperatorJumpTable.MapBinary([ApplyDivide](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyDivide); + }); + OperatorJumpTable.MapBinary([ApplyDivide](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyDivide); + }); + } +} + +static FExpressionResult EnumPropertyEquals(const EditConditionParserTokens::FEnumToken& Enum, const EditConditionParserTokens::FPropertyToken& Property, const IEditConditionContext& Context, bool bNegate) +{ + TOptional TypeName = Context.GetTypeName(Property.PropertyName); + + if (TypeName.GetValue() != Enum.Type) + { + return MakeError(FText::Format(LOCTEXT("OperandTypeMismatch", "EditCondition attempted to compare operands of different types: \"{0}\" and \"{1}\"."), FText::FromString(Property.PropertyName), FText::FromString(Enum.Type + TEXT("::") + Enum.Value))); + } + + TOptional ValueProp = Context.GetEnumValue(Property.PropertyName); + + if (!ValueProp.IsSet()) + { + return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(Property.PropertyName))); + } + + bool bEqual = ValueProp.GetValue() == Enum.Value; + return MakeValue(bNegate ? !bEqual : bEqual); +} + +static void CreateEnumOperators(TOperatorJumpTable& OperatorJumpTable) +{ + using namespace EditConditionParserTokens; + + // EQUALS + { + OperatorJumpTable.MapBinary([](const FEnumToken& A, const FEnumToken& B, const IEditConditionContext* Context) + { + return A.Type == B.Type && A.Value == B.Value; + }); + OperatorJumpTable.MapBinary([](const FPropertyToken& A, const FEnumToken& B, const IEditConditionContext* Context) + { + return EnumPropertyEquals(B, A, *Context, false); + }); + OperatorJumpTable.MapBinary([](const FEnumToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return EnumPropertyEquals(A, B, *Context, false); + }); + } + + // NOT-EQUALS + { + OperatorJumpTable.MapBinary([](const FEnumToken& A, const FEnumToken& B, const IEditConditionContext* Context) + { + return A.Type != B.Type || A.Value != B.Value; + }); + OperatorJumpTable.MapBinary([](const FPropertyToken& A, const FEnumToken& B, const IEditConditionContext* Context) -> FExpressionResult + { + return EnumPropertyEquals(B, A, *Context, true); + }); + OperatorJumpTable.MapBinary([](const FEnumToken& A, const FPropertyToken& B, const IEditConditionContext* Context) -> FExpressionResult + { + return EnumPropertyEquals(A, B, *Context, true); + }); + } +} + +FEditConditionParser::FEditConditionParser() +{ + using namespace EditConditionParserTokens; + + TokenDefinitions.IgnoreWhitespace(); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeNumber); + TokenDefinitions.DefineToken(&ConsumeBool); + TokenDefinitions.DefineToken(&ConsumePropertyName); + + ExpressionGrammar.DefineBinaryOperator(4); + ExpressionGrammar.DefineBinaryOperator(4); + ExpressionGrammar.DefineBinaryOperator(3); + ExpressionGrammar.DefineBinaryOperator(3); + ExpressionGrammar.DefineBinaryOperator(3); + ExpressionGrammar.DefineBinaryOperator(3); + ExpressionGrammar.DefineBinaryOperator(3); + ExpressionGrammar.DefineBinaryOperator(3); + ExpressionGrammar.DefineBinaryOperator(2); + ExpressionGrammar.DefineBinaryOperator(2); + ExpressionGrammar.DefineBinaryOperator(1); + ExpressionGrammar.DefineBinaryOperator(1); + ExpressionGrammar.DefinePreUnaryOperator(); + + OperatorJumpTable.MapBinary([](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) -> FExpressionResult + { + return ApplyPropertiesEqual(A, B, *Context, false); + }); + + OperatorJumpTable.MapBinary([](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) -> FExpressionResult + { + return ApplyPropertiesEqual(A, B, *Context, true); + }); + + CreateBooleanOperators(OperatorJumpTable); + CreateNumberOperators(OperatorJumpTable); + CreateEnumOperators(OperatorJumpTable); +} + +TOptional FEditConditionParser::Evaluate(const FEditConditionExpression& Expression, const IEditConditionContext& Context) const +{ + using namespace EditConditionParserTokens; + + FExpressionResult Result = ExpressionParser::Evaluate(Expression.Tokens, OperatorJumpTable, &Context); + if (Result.IsValid()) + { + const bool* BoolResult = Result.GetValue().Cast(); + if (BoolResult != nullptr) + { + return *BoolResult; + } + + const FPropertyToken* PropertyResult = Result.GetValue().Cast(); + if (PropertyResult != nullptr) + { + TOptional PropertyValue = Context.GetBoolValue(PropertyResult->PropertyName); + if (PropertyValue.IsSet()) + { + return PropertyValue.GetValue(); + } + } + } + + return TOptional(); +} + +TSharedPtr FEditConditionParser::Parse(const FString& ExpressionString) const +{ + using namespace ExpressionParser; + + LexResultType LexResult = ExpressionParser::Lex(*ExpressionString, TokenDefinitions); + if (LexResult.IsValid()) + { + CompileResultType CompileResult = ExpressionParser::Compile(LexResult.StealValue(), ExpressionGrammar); + if (CompileResult.IsValid()) + { + return TSharedPtr(new FEditConditionExpression(CompileResult.StealValue())); + } + } + + return TSharedPtr(); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Source/Editor/PropertyEditor/Private/EditConditionParser.h b/Engine/Source/Editor/PropertyEditor/Private/EditConditionParser.h new file mode 100644 index 000000000000..6e8003bb76b1 --- /dev/null +++ b/Engine/Source/Editor/PropertyEditor/Private/EditConditionParser.h @@ -0,0 +1,109 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "Misc/ExpressionParserTypes.h" +#include "UObject/WeakObjectPtr.h" +#include "UObject/WeakObjectPtrTemplates.h" + +namespace EditConditionParserTokens +{ + struct FPropertyToken + { + FPropertyToken(FString&& InProperty) : + PropertyName(InProperty) {} + + FPropertyToken(const FPropertyToken& Other) : + PropertyName(Other.PropertyName) {} + + FPropertyToken& operator=(const FPropertyToken& Other) + { + PropertyName = Other.PropertyName; + return *this; + } + + FString PropertyName; + }; + + struct FEnumToken + { + FEnumToken(FString&& InType, FString&& InValue) : + Type(InType), Value(InValue) {} + + FEnumToken(const FEnumToken& Other) : + Type(Other.Type), Value(Other.Value) {} + + FEnumToken& operator=(const FEnumToken& Other) + { + Type = Other.Type; + Value = Other.Value; + return *this; + } + + FString Type; + FString Value; + }; +} + +#define DEFINE_EDIT_CONDITION_NODE(TYPE, ...) \ + namespace EditConditionParserTokens { struct TYPE { static const TCHAR* const Moniker; }; } \ + DEFINE_EXPRESSION_NODE_TYPE(EditConditionParserTokens::TYPE, __VA_ARGS__) + + DEFINE_EDIT_CONDITION_NODE(FEqual, 0x3AF0EE1B, 0xC3F847C7, 0xA2B95102, 0x4EFC202D) + DEFINE_EDIT_CONDITION_NODE(FNotEqual, 0x5CDF3FA4, 0x35614D88, 0x8F94017C, 0xF9229625) + DEFINE_EDIT_CONDITION_NODE(FLessEqual, 0x0D097F12, 0x2268447B, 0xA9C6769E, 0x5FE086EA) + DEFINE_EDIT_CONDITION_NODE(FLess, 0xD3B1D8E9, 0x552249C7, 0x92646CF8, 0x6D065F45) + DEFINE_EDIT_CONDITION_NODE(FGreaterEqual, 0x5BC680B9, 0x148448FB, 0xA37A0EBB, 0x34C4BCA8) + DEFINE_EDIT_CONDITION_NODE(FGreater, 0xBD99934C, 0x5E7F4BE9, 0x99A1BD6E, 0x582F8FC9) + DEFINE_EDIT_CONDITION_NODE(FNot, 0xE0FB4BB7, 0x66A646C2, 0xB2360362, 0x0E1FAE33) + DEFINE_EDIT_CONDITION_NODE(FAnd, 0x71F839BC, 0xD75F6B47, 0x9C68017B, 0x33FAB080) + DEFINE_EDIT_CONDITION_NODE(FOr, 0x9C317A4C, 0xCE50304D, 0x8BF6D471, 0x9D791746) + DEFINE_EDIT_CONDITION_NODE(FAdd, 0xF4086317, 0x80428041, 0x903149EB, 0xC5A60ACF) + DEFINE_EDIT_CONDITION_NODE(FSubtract, 0xE80D53E9, 0x32F1E145, 0x8535B767, 0x0634DE9E) + DEFINE_EDIT_CONDITION_NODE(FMultiply, 0x0D4D37DB, 0x53C7EA41, 0x8297C200, 0xF5B28D27) + DEFINE_EDIT_CONDITION_NODE(FDivide, 0x48009789, 0x2EE84F40, 0x83467A99, 0x92617168) + DEFINE_EDIT_CONDITION_NODE(FSubExpressionStart, 0x78CE8411, 0x34076241, 0xA2BD0548, 0x54458A3A) + DEFINE_EDIT_CONDITION_NODE(FSubExpressionEnd, 0x5CB25466, 0x780CAE4D, 0x8A88E000, 0xD3695885) + + DEFINE_EXPRESSION_NODE_TYPE(bool, 0xCACBC715, 0x505A6B4A, 0x8808809F, 0x897AA5F6) + DEFINE_EXPRESSION_NODE_TYPE(EditConditionParserTokens::FPropertyToken, 0x9A3FAF6F, 0xB2E45E4D, 0xA80A70C6, 0x47A89BD7) + DEFINE_EXPRESSION_NODE_TYPE(EditConditionParserTokens::FEnumToken, 0xC9A35C24, 0x21FC904B, 0x9F1B2B6A, 0xDF6F4BC4) + +#undef DEFINE_EDIT_CONDITION_NODE + +class IEditConditionContext; + +class FEditConditionExpression +{ +public: + FEditConditionExpression(TArray&& InTokens) : + Tokens(MoveTemp(InTokens)) + {} + + TArray Tokens; +}; + +class FEditConditionParser +{ +public: + FEditConditionParser(); + + FEditConditionParser(const FEditConditionParser&) = delete; + FEditConditionParser(FEditConditionParser&&) = default; + FEditConditionParser& operator=(const FEditConditionParser&) = delete; + FEditConditionParser& operator=(FEditConditionParser&&) = default; + + TSharedPtr Parse(const FString& ExpressionString) const; + + /** + * Evaluate the given expression within the given context. + * @returns The result of the evaluated expression if valid, invalid TOptional if the evaluation failed or produced a non-bool result. + */ + TOptional Evaluate(const FEditConditionExpression& Expression, const IEditConditionContext& Context) const; + +private: + FTokenDefinitions TokenDefinitions; + FExpressionGrammar ExpressionGrammar; + TOperatorJumpTable OperatorJumpTable; +}; diff --git a/Engine/Source/Editor/PropertyEditor/Private/EditConditionParserTests.cpp b/Engine/Source/Editor/PropertyEditor/Private/EditConditionParserTests.cpp new file mode 100644 index 000000000000..8ae3d1f56019 --- /dev/null +++ b/Engine/Source/Editor/PropertyEditor/Private/EditConditionParserTests.cpp @@ -0,0 +1,407 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "EditConditionParserTests.h" +#include "EditConditionParser.h" +#include "EditConditionContext.h" +#include "ObjectPropertyNode.h" +#include "Misc/AutomationTest.h" + +UEditConditionTestObject::UEditConditionTestObject(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +#if WITH_DEV_AUTOMATION_TESTS +struct FTestEditConditionContext : IEditConditionContext +{ + TMap BoolValues; + TMap DoubleValues; + TMap EnumValues; + FString EnumTypeName; + + FTestEditConditionContext(){} + virtual ~FTestEditConditionContext() {} + + virtual TOptional GetBoolValue(const FString& PropertyName) const override + { + TOptional Result; + if (const bool* Value = BoolValues.Find(PropertyName)) + { + Result = *Value; + } + return Result; + } + + virtual TOptional GetNumericValue(const FString& PropertyName) const override + { + TOptional Result; + if (const double* Value = DoubleValues.Find(PropertyName)) + { + Result = *Value; + } + return Result; + } + + virtual TOptional GetEnumValue(const FString& PropertyName) const override + { + TOptional Result; + if (const FString* Value = EnumValues.Find(PropertyName)) + { + Result = *Value; + } + return Result; + } + + virtual TOptional GetTypeName(const FString& PropertyName) const override + { + TOptional Result; + + if (BoolValues.Find(PropertyName) != nullptr) + { + Result = TEXT("bool"); + } + else if (DoubleValues.Find(PropertyName) != nullptr) + { + Result = TEXT("double"); + } + else if (EnumValues.Find(PropertyName) != nullptr) + { + Result = EnumTypeName; + } + + return Result; + } + + void SetupBool(const FString& PropertyName, bool Value) + { + BoolValues.Add(PropertyName, Value); + } + + void SetupDouble(const FString& PropertyName, double Value) + { + DoubleValues.Add(PropertyName, Value); + } + + void SetupEnum(const FString& PropertyName, const FString& Value) + { + EnumValues.Add(PropertyName, Value); + } + + void SetupEnumType(const FString& EnumType) + { + EnumTypeName = EnumType; + } +}; + +static bool CanParse(const FEditConditionParser& Parser, const FString& Expression, int32 ExpectedTokens, int32 ExpectedProperties) +{ + TSharedPtr Parsed = Parser.Parse(Expression); + + if (!Parsed.IsValid()) + { + ensureMsgf(false, TEXT("Failed to parse expression: %s"), *Expression); + return false; + } + + int PropertyCount = 0; + + for (const auto& Token : Parsed->Tokens) + { + const EditConditionParserTokens::FPropertyToken* PropertyToken = Token.Node.Cast(); + if (PropertyToken != nullptr) + { + ++PropertyCount; + } + } + + return Parsed->Tokens.Num() == ExpectedTokens && + PropertyCount == ExpectedProperties; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_Parse, "EditConditionParser.Parse", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) +bool FEditConditionParser_Parse::RunTest(const FString& Parameters) +{ + FEditConditionParser Parser; + bool bResult = true; + + bResult &= CanParse(Parser, TEXT("BoolProperty"), 1, 1); + bResult &= CanParse(Parser, TEXT("!BoolProperty"), 2, 1); + bResult &= CanParse(Parser, TEXT("BoolProperty == true"), 3, 1); + bResult &= CanParse(Parser, TEXT("BoolProperty == false"), 3, 1); + bResult &= CanParse(Parser, TEXT("IntProperty == 0"), 3, 1); + bResult &= CanParse(Parser, TEXT("IntProperty != 0"), 3, 1); + bResult &= CanParse(Parser, TEXT("IntProperty > 0"), 3, 1); + bResult &= CanParse(Parser, TEXT("IntProperty < 0"), 3, 1); + bResult &= CanParse(Parser, TEXT("IntProperty <= 0"), 3, 1); + bResult &= CanParse(Parser, TEXT("IntProperty >= 0"), 3, 1); + bResult &= CanParse(Parser, TEXT("Foo > Bar"), 3, 2); + bResult &= CanParse(Parser, TEXT("Foo && Bar"), 3, 2); + bResult &= CanParse(Parser, TEXT("Foo || Bar"), 3, 2); + bResult &= CanParse(Parser, TEXT("Foo == Bar + 5"), 5, 2); + bResult &= CanParse(Parser, TEXT("Foo == Bar - 5"), 5, 2); + bResult &= CanParse(Parser, TEXT("Foo == Bar * 5"), 5, 2); + bResult &= CanParse(Parser, TEXT("Foo == Bar / 5"), 5, 2); + bResult &= CanParse(Parser, TEXT("Enum == EType::Value"), 3, 1); + bResult &= CanParse(Parser, TEXT("Enum != EType::Value"), 3, 1); + bResult &= CanParse(Parser, TEXT("Enum != EType::Value && BoolProperty"), 5, 2); + bResult &= CanParse(Parser, TEXT("Enum == EType::Value || BoolProperty == false"), 7, 2); + bResult &= CanParse(Parser, TEXT("Enum != EType::Value || BoolProperty == bFoo"), 7, 3); + bResult &= CanParse(Parser, TEXT("Enum == EType::Value && Foo != 5"), 7, 2); + bResult &= CanParse(Parser, TEXT("Enum != EType::Value && Foo == Bar"), 7, 3); + + return bResult; +} + +static bool CanEvaluate(const FEditConditionParser& Parser, const IEditConditionContext& Context, const FString& Expression, bool Expected) +{ + TSharedPtr Parsed = Parser.Parse(Expression); + if (!Parsed.IsValid()) + { + ensureMsgf(false, TEXT("Failed to parse expression: %s"), *Expression); + return false; + } + + TOptional Result = Parser.Evaluate(*Parsed.Get(), Context); + if (!Result.IsSet()) + { + ensureMsgf(false, TEXT("Expression failed to evaluate: %s"), *Expression); + return false; + } + + if (Result.GetValue() != Expected) + { + ensureMsgf(false, TEXT("Expression evaluated to unexpected value."), *Expression); + return false; + } + + return true; +} + +static bool RunBoolTests(const IEditConditionContext& Context) +{ + FEditConditionParser Parser; + bool bResult = true; + + bResult &= CanEvaluate(Parser, Context, TEXT("true"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("false"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("!true"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("!false"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("!BoolProperty"), false); + + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty == true"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty == false"), false); + + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty == BoolProperty"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty != BoolProperty"), false); + + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty != true"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty != false"), true); + + bResult &= CanEvaluate(Parser, Context, TEXT("true && true"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("true && false"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("false && true"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("false && false"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("true && true && true"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("true && true && false"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty && BoolProperty"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty && false"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("false && BoolProperty"), false); + + bResult &= CanEvaluate(Parser, Context, TEXT("true || true"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("true || false"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("false || true"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("false || false"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("true || true || true"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("true || true || false"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty || BoolProperty"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty || false"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("false || BoolProperty"), true); + + return bResult; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_EvaluateBool, "EditConditionParser.EvaluateBool", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) +bool FEditConditionParser_EvaluateBool::RunTest(const FString& Parameters) +{ + FTestEditConditionContext TestContext; + TestContext.SetupBool(TEXT("BoolProperty"), true); + + return RunBoolTests(TestContext); +} + +static bool RunNumericTests(const IEditConditionContext& Context) +{ + FEditConditionParser Parser; + bool bResult = true; + + bResult &= CanEvaluate(Parser, Context, TEXT("5 == 5"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("5.0 == 5.0"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 5.0"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 5"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == DoubleProperty"), true); + + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty != 5.0"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty != 6.0"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty != 6"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty != DoubleProperty"), false); + + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty > 4.5"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty > 5"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty > 6"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty > DoubleProperty"), false); + + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty < 4.5"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty < 5"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty < 6"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty < DoubleProperty"), false); + + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty >= 4.5"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty >= 5"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty >= 6"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty >= DoubleProperty"), true); + + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty <= 4.5"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty <= 5"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty <= 6"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty <= DoubleProperty"), true); + + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 2 + 3"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 6 - 1"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 2.5 * 2"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 10 / 2"), true); + + return bResult; +} + + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_EvaluateDouble, "EditConditionParser.EvaluateDouble", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) +bool FEditConditionParser_EvaluateDouble::RunTest(const FString& Parameters) +{ + FTestEditConditionContext TestContext; + TestContext.SetupDouble(TEXT("DoubleProperty"), 5.0); + + return RunNumericTests(TestContext); +} + +static bool RunEnumTests(const IEditConditionContext& Context, const FString& EnumName, const FString& PropertyName) +{ + FEditConditionParser Parser; + bool bResult = true; + + bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::First == ") + EnumName + TEXT("::First"), true); + bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::First == ") + EnumName + TEXT("::Second"), false); + + bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::First != ") + EnumName + TEXT("::First"), false); + bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::First != ") + EnumName + TEXT("::Second"), true); + + bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" == ") + PropertyName, true); + bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" != ") + PropertyName, false); + + bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" == ") + EnumName + TEXT("::First"), true); + bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::First == ") + PropertyName, true); + bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" == ") + EnumName + TEXT("::Second"), false); + + bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" != ") + EnumName + TEXT("::Second"), true); + bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" != ") + EnumName + TEXT("::First"), false); + bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::Second != ") + PropertyName, true); + + return bResult; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_EvaluateEnum, "EditConditionParser.EvaluateEnum", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) +bool FEditConditionParser_EvaluateEnum::RunTest(const FString& Parameters) +{ + FTestEditConditionContext TestContext; + + const FString EnumType = TEXT("EditConditionTestEnum"); + TestContext.SetupEnumType(EnumType); + + const FString PropertyName = TEXT("EnumProperty"); + TestContext.SetupEnum(PropertyName, TEXT("First")); + + return RunEnumTests(TestContext, EnumType, PropertyName); +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_EvaluateUObject, "EditConditionParser.EvaluateUObject", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) +bool FEditConditionParser_EvaluateUObject::RunTest(const FString& Parameters) +{ + UEditConditionTestObject* TestObject = NewObject(); + TestObject->AddToRoot(); + + TSharedPtr ObjectNode(new FObjectPropertyNode); + ObjectNode->AddObject(TestObject); + + FPropertyNodeInitParams InitParams; + ObjectNode->InitNode(InitParams); + + TOptional bResult; + bool bAllResults = true; + + // enum comparison + { + static const FName EnumPropertyName = TEXT("EnumProperty"); + TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(EnumPropertyName, true); + FEditConditionContext Context(*PropertyNode.Get()); + + TestObject->EnumProperty = EditConditionTestEnum::First; + TestObject->ByteEnumProperty = EditConditionByteEnum::First; + + static const FString EnumType = TEXT("EditConditionTestEnum"); + bAllResults &= RunEnumTests(Context, EnumType, EnumPropertyName.ToString()); + + static const FString ByteEnumType = TEXT("EditConditionByteEnum"); + static const FString ByteEnumPropertyName = TEXT("ByteEnumProperty"); + bAllResults &= RunEnumTests(Context, ByteEnumType, ByteEnumPropertyName); + } + + // bool comparison + { + static const FName BoolPropertyName(TEXT("BoolProperty")); + TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(BoolPropertyName, true); + FEditConditionContext Context(*PropertyNode.Get()); + + TestObject->BoolProperty = true; + + bAllResults &= RunBoolTests(Context); + } + + // double comparison + { + static const FName DoublePropertyName(TEXT("DoubleProperty")); + TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(DoublePropertyName, true); + FEditConditionContext Context(*PropertyNode.Get()); + + TestObject->DoubleProperty = 5.0; + + bAllResults &= RunNumericTests(Context); + } + + // integer comparison + { + static const FName IntegerPropertyName(TEXT("IntegerProperty")); + TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(IntegerPropertyName, true); + FEditConditionContext Context(*PropertyNode.Get()); + + TestObject->IntegerProperty = 5; + + bAllResults &= RunNumericTests(Context); + } + + { + static const FName DoublePropertyName(TEXT("DoubleProperty")); + TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(DoublePropertyName, true); + FEditConditionContext Context(*PropertyNode.Get()); + + TestEqual(TEXT("Boolean Type Name"), Context.GetTypeName(TEXT("BoolProperty")).GetValue(), TEXT("bool")); + TestEqual(TEXT("Enum Type Name"), Context.GetTypeName(TEXT("EnumProperty")).GetValue(), TEXT("EditConditionTestEnum")); + TestEqual(TEXT("Byte Enum Type Name"), Context.GetTypeName(TEXT("ByteEnumProperty")).GetValue(), TEXT("EditConditionByteEnum")); + TestEqual(TEXT("Double Type Name"), Context.GetTypeName(TEXT("DoubleProperty")).GetValue(), TEXT("double")); + } + + TestObject->RemoveFromRoot(); + + return bAllResults; +} + +#endif // WITH_DEV_AUTOMATION_TESTS \ No newline at end of file diff --git a/Engine/Source/Editor/PropertyEditor/Private/EditConditionParserTests.h b/Engine/Source/Editor/PropertyEditor/Private/EditConditionParserTests.h new file mode 100644 index 000000000000..a40321af564a --- /dev/null +++ b/Engine/Source/Editor/PropertyEditor/Private/EditConditionParserTests.h @@ -0,0 +1,41 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "UObject/Object.h" +#include "EditConditionParserTests.generated.h" + +UENUM() +enum class EditConditionTestEnum +{ + First = 15, + Second = 31 +}; + +UENUM() +enum EditConditionByteEnum +{ + First = 15, + Second = 31 +}; + +UCLASS(transient) +class UEditConditionTestObject : public UObject +{ + GENERATED_UCLASS_BODY() + + UPROPERTY(EditAnywhere, Category=Test) + bool BoolProperty; + + UPROPERTY(EditAnywhere, Category=Test) + EditConditionTestEnum EnumProperty; + + UPROPERTY(EditAnywhere, Category=Test) + TEnumAsByte ByteEnumProperty; + + UPROPERTY(EditAnywhere, Category=Test) + double DoubleProperty; + + UPROPERTY(EditAnywhere, Category=Test) + int32 IntegerProperty; +}; diff --git a/Engine/Source/Editor/PropertyEditor/Private/IDetailsViewPrivate.h b/Engine/Source/Editor/PropertyEditor/Private/IDetailsViewPrivate.h index 682c2b632b95..33f052c95e01 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/IDetailsViewPrivate.h +++ b/Engine/Source/Editor/PropertyEditor/Private/IDetailsViewPrivate.h @@ -8,6 +8,7 @@ #include "PropertyNode.h" #include "IDetailsView.h" +class FEditConditionParser; class FNotifyHook; class IDetailPropertyExtensionHandler; class IDetailRootObjectCustomization; @@ -79,8 +80,6 @@ public: */ virtual bool IsPropertyReadOnly( const struct FPropertyAndParent& PropertyAndParent ) const = 0; - virtual TSharedPtr GetExtensionHandler() = 0; - /** * @return The thumbnail pool that should be used for thumbnails being rendered in this view */ @@ -130,4 +129,6 @@ public: * Restores the expansion state of property nodes for the selected object set */ virtual void RestoreExpandedItems(TSharedRef StartNode) = 0; + + virtual TSharedPtr GetEditConditionParser() const = 0; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/MaterialList.cpp b/Engine/Source/Editor/PropertyEditor/Private/MaterialList.cpp new file mode 100644 index 000000000000..b076faf6ae21 --- /dev/null +++ b/Engine/Source/Editor/PropertyEditor/Private/MaterialList.cpp @@ -0,0 +1,640 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MaterialList.h" +#include "DetailLayoutBuilder.h" +#include "Editor.h" +#include "IDetailChildrenBuilder.h" +#include "PropertyCustomizationHelpers.h" +#include "PropertyHandle.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Widgets/Input/SComboButton.h" +#include "Widgets/Input/SHyperlink.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Text/STextBlock.h" + +#define LOCTEXT_NAMESPACE "PropertyCustomizationHelpers" + +/** + * Builds up a list of unique materials while creating some information about the materials + */ +class FMaterialListBuilder : public IMaterialListBuilder +{ + friend class FMaterialList; +public: + + /** + * Adds a new material to the list + * + * @param SlotIndex The slot (usually mesh element index) where the material is located on the component + * @param Material The material being used + * @param bCanBeReplced Whether or not the material can be replaced by a user + */ + virtual void AddMaterial( uint32 SlotIndex, UMaterialInterface* Material, bool bCanBeReplaced ) override + { + int32 NumMaterials = MaterialSlots.Num(); + + FMaterialListItem MaterialItem( Material, SlotIndex, bCanBeReplaced ); + if( !UniqueMaterials.Contains( MaterialItem ) ) + { + MaterialSlots.Add( MaterialItem ); + UniqueMaterials.Add( MaterialItem ); + } + + // Did we actually add material? If we did then we need to increment the number of materials in the element + if( MaterialSlots.Num() > NumMaterials ) + { + // Resize the array to support the slot if needed + if( !MaterialCount.IsValidIndex(SlotIndex) ) + { + int32 NumToAdd = (SlotIndex - MaterialCount.Num()) + 1; + if( NumToAdd > 0 ) + { + MaterialCount.AddZeroed( NumToAdd ); + } + } + + ++MaterialCount[SlotIndex]; + } + } + + /** Empties the list */ + void Empty() + { + UniqueMaterials.Empty(); + MaterialSlots.Reset(); + MaterialCount.Reset(); + } + + /** Sorts the list by slot index */ + void Sort() + { + struct FSortByIndex + { + bool operator()( const FMaterialListItem& A, const FMaterialListItem& B ) const + { + return A.SlotIndex < B.SlotIndex; + } + }; + + MaterialSlots.Sort( FSortByIndex() ); + } + + /** @return The number of materials in the list */ + uint32 GetNumMaterials() const { return MaterialSlots.Num(); } + + /** @return The number of materials in the list at a given slot */ + uint32 GetNumMaterialsInSlot( uint32 Index ) const { return MaterialCount[Index]; } +private: + /** All unique materials */ + TSet UniqueMaterials; + /** All material items in the list */ + TArray MaterialSlots; + /** Material counts for each slot. The slot is the index and the value at that index is the count */ + TArray MaterialCount; +}; + +/** + * A view of a single item in an FMaterialList + */ +class FMaterialItemView : public TSharedFromThis +{ +public: + /** + * Creates a new instance of this class + * + * @param Material The material to view + * @param InOnMaterialChanged Delegate for when the material changes + */ + static TSharedRef Create( + const FMaterialListItem& Material, + FOnMaterialChanged InOnMaterialChanged, + FOnGenerateWidgetsForMaterial InOnGenerateNameWidgetsForMaterial, + FOnGenerateWidgetsForMaterial InOnGenerateWidgetsForMaterial, + FOnResetMaterialToDefaultClicked InOnResetToDefaultClicked, + int32 InMultipleMaterialCount, + bool bShowUsedTextures, + bool bDisplayCompactSize) + { + return MakeShareable( new FMaterialItemView( Material, InOnMaterialChanged, InOnGenerateNameWidgetsForMaterial, InOnGenerateWidgetsForMaterial, InOnResetToDefaultClicked, InMultipleMaterialCount, bShowUsedTextures, bDisplayCompactSize) ); + } + + TSharedRef CreateNameContent() + { + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("ElementIndex"), MaterialItem.SlotIndex); + + return + SNew(SVerticalBox) + +SVerticalBox::Slot() + .VAlign(VAlign_Center) + [ + SNew( STextBlock ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + .Text( FText::Format(LOCTEXT("ElementIndex", "Element {ElementIndex}"), Arguments ) ) + ] + +SVerticalBox::Slot() + .Padding(0.0f,4.0f) + .AutoHeight() + [ + OnGenerateCustomNameWidgets.IsBound() ? OnGenerateCustomNameWidgets.Execute( MaterialItem.Material.Get(), MaterialItem.SlotIndex ) : StaticCastSharedRef( SNullWidget::NullWidget ) + ]; + } + + TSharedRef CreateValueContent( const TSharedPtr& ThumbnailPool ) + { + FIntPoint ThumbnailSize(64, 64); + + FResetToDefaultOverride ResetToDefaultOverride = FResetToDefaultOverride::Create( + FIsResetToDefaultVisible::CreateSP(this, &FMaterialItemView::GetReplaceVisibility), + FResetToDefaultHandler::CreateSP(this, &FMaterialItemView::OnResetToBaseClicked) + ); + + return + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + .AutoHeight() + .Padding( 0.0f ) + .VAlign(VAlign_Center) + .HAlign(HAlign_Fill) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew( SObjectPropertyEntryBox ) + .ObjectPath(this, &FMaterialItemView::OnGetObjectPath) + .AllowedClass(UMaterialInterface::StaticClass()) + .OnObjectChanged(this, &FMaterialItemView::OnSetObject) + .ThumbnailPool(ThumbnailPool) + .DisplayCompactSize(bDisplayCompactSize) + .CustomResetToDefault(ResetToDefaultOverride) + .CustomContentSlot() + [ + SNew( SBox ) + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .Padding(0.0f, 0.0f, 3.0f, 0.0f) + .AutoWidth() + [ + // Add a menu for displaying all textures + SNew( SComboButton ) + .OnGetMenuContent( this, &FMaterialItemView::OnGetTexturesMenuForMaterial ) + .VAlign(VAlign_Center) + .ContentPadding(2) + .IsEnabled( this, &FMaterialItemView::IsTexturesMenuEnabled ) + .Visibility( bShowUsedTextures ? EVisibility::Visible : EVisibility::Hidden ) + .ButtonContent() + [ + SNew( STextBlock ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + .ToolTipText( LOCTEXT("ViewTexturesToolTip", "View the textures used by this material" ) ) + .Text( LOCTEXT("ViewTextures","Textures") ) + ] + ] + +SHorizontalBox::Slot() + .Padding(3.0f, 0.0f) + .FillWidth(1.0f) + [ + OnGenerateCustomMaterialWidgets.IsBound() && bDisplayCompactSize ? OnGenerateCustomMaterialWidgets.Execute(MaterialItem.Material.Get(), MaterialItem.SlotIndex) : StaticCastSharedRef(SNullWidget::NullWidget) + ] + ] + ] + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + .Padding(2) + .VAlign( VAlign_Center ) + [ + OnGenerateCustomMaterialWidgets.IsBound() && !bDisplayCompactSize ? OnGenerateCustomMaterialWidgets.Execute( MaterialItem.Material.Get(), MaterialItem.SlotIndex ) : StaticCastSharedRef( SNullWidget::NullWidget ) + ] + ]; + } + +private: + + FMaterialItemView( const FMaterialListItem& InMaterial, + FOnMaterialChanged& InOnMaterialChanged, + FOnGenerateWidgetsForMaterial& InOnGenerateNameWidgets, + FOnGenerateWidgetsForMaterial& InOnGenerateMaterialWidgets, + FOnResetMaterialToDefaultClicked& InOnResetToDefaultClicked, + int32 InMultipleMaterialCount, + bool bInShowUsedTextures, + bool bInDisplayCompactSize) + + : MaterialItem( InMaterial ) + , OnMaterialChanged( InOnMaterialChanged ) + , OnGenerateCustomNameWidgets( InOnGenerateNameWidgets ) + , OnGenerateCustomMaterialWidgets( InOnGenerateMaterialWidgets ) + , OnResetToDefaultClicked( InOnResetToDefaultClicked ) + , MultipleMaterialCount( InMultipleMaterialCount ) + , bShowUsedTextures( bInShowUsedTextures ) + , bDisplayCompactSize(bInDisplayCompactSize) + { + + } + + void ReplaceMaterial( UMaterialInterface* NewMaterial, bool bReplaceAll = false ) + { + UMaterialInterface* PrevMaterial = NULL; + if( MaterialItem.Material.IsValid() ) + { + PrevMaterial = MaterialItem.Material.Get(); + } + + if( NewMaterial != PrevMaterial ) + { + // Replace the material + OnMaterialChanged.ExecuteIfBound( NewMaterial, PrevMaterial, MaterialItem.SlotIndex, bReplaceAll ); + } + } + + void OnSetObject( const FAssetData& AssetData ) + { + const bool bReplaceAll = false; + + UMaterialInterface* NewMaterial = Cast(AssetData.GetAsset()); + ReplaceMaterial( NewMaterial, bReplaceAll ); + } + + FString OnGetObjectPath() const + { + return MaterialItem.Material->GetPathName(); + } + + /** + * @return Whether or not the textures menu is enabled + */ + bool IsTexturesMenuEnabled() const + { + return MaterialItem.Material.Get() != NULL; + } + + TSharedRef OnGetTexturesMenuForMaterial() + { + FMenuBuilder MenuBuilder( true, NULL ); + + if( MaterialItem.Material.IsValid() ) + { + UMaterialInterface* Material = MaterialItem.Material.Get(); + + TArray< UTexture* > Textures; + Material->GetUsedTextures(Textures, EMaterialQualityLevel::Num, false, ERHIFeatureLevel::Num, true); + + // Add a menu item for each texture. Clicking on the texture will display it in the content browser + // UObject for delegate compatibility + for( UObject* Texture : Textures ) + { + FUIAction Action( FExecuteAction::CreateSP( this, &FMaterialItemView::GoToAssetInContentBrowser, MakeWeakObjectPtr(Texture) ) ); + + MenuBuilder.AddMenuEntry( FText::FromString( Texture->GetName() ), LOCTEXT( "BrowseTexture_ToolTip", "Find this texture in the content browser" ), FSlateIcon(), Action ); + } + } + + return MenuBuilder.MakeWidget(); + } + + /** + * Finds the asset in the content browser + */ + void GoToAssetInContentBrowser( TWeakObjectPtr Object ) + { + if( Object.IsValid() ) + { + TArray< UObject* > Objects; + Objects.Add( Object.Get() ); + GEditor->SyncBrowserToObjects( Objects ); + } + } + + /** + * Called to get the visibility of the replace button + */ + bool GetReplaceVisibility(TSharedPtr PropertyHandle) const + { + // Only show the replace button if the current material can be replaced + if (OnMaterialChanged.IsBound() && MaterialItem.bCanBeReplaced) + { + return true; + } + + return false; + } + + /** + * Called when reset to base is clicked + */ + void OnResetToBaseClicked(TSharedPtr PropertyHandle) + { + // Only allow reset to base if the current material can be replaced + if( MaterialItem.Material.IsValid() && MaterialItem.bCanBeReplaced ) + { + bool bReplaceAll = false; + ReplaceMaterial( NULL, bReplaceAll ); + OnResetToDefaultClicked.ExecuteIfBound( MaterialItem.Material.Get(), MaterialItem.SlotIndex ); + } + } + +private: + FMaterialListItem MaterialItem; + FOnMaterialChanged OnMaterialChanged; + FOnGenerateWidgetsForMaterial OnGenerateCustomNameWidgets; + FOnGenerateWidgetsForMaterial OnGenerateCustomMaterialWidgets; + FOnResetMaterialToDefaultClicked OnResetToDefaultClicked; + int32 MultipleMaterialCount; + bool bShowUsedTextures; + bool bDisplayCompactSize; +}; + + +FMaterialList::FMaterialList(IDetailLayoutBuilder& InDetailLayoutBuilder, FMaterialListDelegates& InMaterialListDelegates, bool bInAllowCollapse, bool bInShowUsedTextures, bool bInDisplayCompactSize) + : MaterialListDelegates( InMaterialListDelegates ) + , DetailLayoutBuilder( InDetailLayoutBuilder ) + , MaterialListBuilder( new FMaterialListBuilder ) + , bAllowCollpase(bInAllowCollapse) + , bShowUsedTextures(bInShowUsedTextures) + , bDisplayCompactSize(bInDisplayCompactSize) +{ +} + +void FMaterialList::OnDisplayMaterialsForElement( int32 SlotIndex ) +{ + // We now want to display all the materials in the element + ExpandedSlots.Add( SlotIndex ); + + MaterialListBuilder->Empty(); + MaterialListDelegates.OnGetMaterials.ExecuteIfBound( *MaterialListBuilder ); + + OnRebuildChildren.ExecuteIfBound(); +} + +void FMaterialList::OnHideMaterialsForElement( int32 SlotIndex ) +{ + // No longer want to expand the element + ExpandedSlots.Remove( SlotIndex ); + + // regenerate the materials + MaterialListBuilder->Empty(); + MaterialListDelegates.OnGetMaterials.ExecuteIfBound( *MaterialListBuilder ); + + OnRebuildChildren.ExecuteIfBound(); +} + + +void FMaterialList::Tick( float DeltaTime ) +{ + // Check each material to see if its still valid. This allows the material list to stay up to date when materials are changed out from under us + if( MaterialListDelegates.OnGetMaterials.IsBound() ) + { + // Whether or not to refresh the material list + bool bRefreshMaterialList = false; + + // Get the current list of materials from the user + MaterialListBuilder->Empty(); + MaterialListDelegates.OnGetMaterials.ExecuteIfBound( *MaterialListBuilder ); + + if( MaterialListBuilder->GetNumMaterials() != DisplayedMaterials.Num() ) + { + // The array sizes differ so we need to refresh the list + bRefreshMaterialList = true; + } + else + { + // Compare the new list against the currently displayed list + for( int32 MaterialIndex = 0; MaterialIndex < MaterialListBuilder->MaterialSlots.Num(); ++MaterialIndex ) + { + const FMaterialListItem& Item = MaterialListBuilder->MaterialSlots[MaterialIndex]; + + // The displayed materials is out of date if there isn't a 1:1 mapping between the material sets + if( !DisplayedMaterials.IsValidIndex( MaterialIndex ) || DisplayedMaterials[ MaterialIndex ] != Item ) + { + bRefreshMaterialList = true; + break; + } + } + } + + if (!bRefreshMaterialList && MaterialListDelegates.OnMaterialListDirty.IsBound()) + { + bRefreshMaterialList = MaterialListDelegates.OnMaterialListDirty.Execute(); + } + + if( bRefreshMaterialList ) + { + OnRebuildChildren.ExecuteIfBound(); + } + } +} + +void FMaterialList::GenerateHeaderRowContent( FDetailWidgetRow& NodeRow ) +{ + NodeRow.CopyAction(FUIAction(FExecuteAction::CreateSP(this, &FMaterialList::OnCopyMaterialList), FCanExecuteAction::CreateSP(this, &FMaterialList::OnCanCopyMaterialList))); + NodeRow.PasteAction(FUIAction(FExecuteAction::CreateSP(this, &FMaterialList::OnPasteMaterialList))); + + if (bAllowCollpase) + { + NodeRow.NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("MaterialHeaderTitle", "Materials")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ]; + } +} + +void FMaterialList::GenerateChildContent( IDetailChildrenBuilder& ChildrenBuilder ) +{ + ViewedMaterials.Empty(); + DisplayedMaterials.Empty(); + if( MaterialListBuilder->GetNumMaterials() > 0 ) + { + DisplayedMaterials = MaterialListBuilder->MaterialSlots; + + MaterialListBuilder->Sort(); + TArray& MaterialSlots = MaterialListBuilder->MaterialSlots; + + int32 CurrentSlot = INDEX_NONE; + bool bDisplayAllMaterialsInSlot = true; + for( auto It = MaterialSlots.CreateConstIterator(); It; ++It ) + { + const FMaterialListItem& Material = *It; + + if( CurrentSlot != Material.SlotIndex ) + { + // We've encountered a new slot. Make a widget to display that + CurrentSlot = Material.SlotIndex; + + uint32 NumMaterials = MaterialListBuilder->GetNumMaterialsInSlot(CurrentSlot); + + // If an element is expanded we want to display all its materials + bool bWantToDisplayAllMaterials = NumMaterials > 1 && ExpandedSlots.Contains(CurrentSlot); + + // If we are currently displaying an expanded set of materials for an element add a link to collapse all of them + if( bWantToDisplayAllMaterials ) + { + FDetailWidgetRow& ChildRow = ChildrenBuilder.AddCustomRow( LOCTEXT( "HideAllMaterialSearchString", "Hide All Materials") ); + + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("ElementSlot"), CurrentSlot); + ChildRow + .ValueContent() + .MaxDesiredWidth(0.0f)// No Max Width + [ + SNew( SBox ) + .HAlign( HAlign_Center ) + [ + SNew( SHyperlink ) + .TextStyle( FEditorStyle::Get(), "MaterialList.HyperlinkStyle" ) + .Text( FText::Format(LOCTEXT("HideAllMaterialLinkText", "Hide All Materials on Element {ElementSlot}"), Arguments ) ) + .OnNavigate( this, &FMaterialList::OnHideMaterialsForElement, CurrentSlot ) + ] + ]; + } + + if( NumMaterials > 1 && !bWantToDisplayAllMaterials ) + { + // The current slot has multiple elements to view + bDisplayAllMaterialsInSlot = false; + + FDetailWidgetRow& ChildRow = ChildrenBuilder.AddCustomRow( FText::GetEmpty() ); + + AddMaterialItem( ChildRow, CurrentSlot, FMaterialListItem( NULL, CurrentSlot, true ), !bDisplayAllMaterialsInSlot ); + } + else + { + bDisplayAllMaterialsInSlot = true; + } + + } + + // Display each thumbnail element unless we shouldn't display multiple materials for one slot + if( bDisplayAllMaterialsInSlot ) + { + FDetailWidgetRow& ChildRow = ChildrenBuilder.AddCustomRow( Material.Material.IsValid()? FText::FromString(Material.Material->GetName()) : FText::GetEmpty() ); + + AddMaterialItem( ChildRow, CurrentSlot, Material, !bDisplayAllMaterialsInSlot ); + } + } + } + else + { + FDetailWidgetRow& ChildRow = ChildrenBuilder.AddCustomRow( LOCTEXT("NoMaterials", "No Materials") ); + + ChildRow + [ + SNew( SBox ) + .HAlign( HAlign_Center ) + [ + SNew( STextBlock ) + .Text( LOCTEXT("NoMaterials", "No Materials") ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + ] + ]; + } +} + +bool FMaterialList::OnCanCopyMaterialList() const +{ + if (MaterialListDelegates.OnCanCopyMaterialList.IsBound()) + { + return MaterialListDelegates.OnCanCopyMaterialList.Execute(); + } + + return false; +} + +void FMaterialList::OnCopyMaterialList() +{ + if (MaterialListDelegates.OnCopyMaterialList.IsBound()) + { + MaterialListDelegates.OnCopyMaterialList.Execute(); + } +} + +void FMaterialList::OnPasteMaterialList() +{ + if (MaterialListDelegates.OnPasteMaterialList.IsBound()) + { + MaterialListDelegates.OnPasteMaterialList.Execute(); + } +} + +bool FMaterialList::OnCanCopyMaterialItem(int32 CurrentSlot) const +{ + if (MaterialListDelegates.OnCanCopyMaterialItem.IsBound()) + { + return MaterialListDelegates.OnCanCopyMaterialItem.Execute(CurrentSlot); + } + + return false; +} + +void FMaterialList::OnCopyMaterialItem(int32 CurrentSlot) +{ + if (MaterialListDelegates.OnCopyMaterialItem.IsBound()) + { + MaterialListDelegates.OnCopyMaterialItem.Execute(CurrentSlot); + } +} + +void FMaterialList::OnPasteMaterialItem(int32 CurrentSlot) +{ + if (MaterialListDelegates.OnPasteMaterialItem.IsBound()) + { + MaterialListDelegates.OnPasteMaterialItem.Execute(CurrentSlot); + } +} + +void FMaterialList::AddMaterialItem( FDetailWidgetRow& Row, int32 CurrentSlot, const FMaterialListItem& Item, bool bDisplayLink ) +{ + uint32 NumMaterials = MaterialListBuilder->GetNumMaterialsInSlot(CurrentSlot); + + TSharedRef NewView = FMaterialItemView::Create( Item, MaterialListDelegates.OnMaterialChanged, MaterialListDelegates.OnGenerateCustomNameWidgets, MaterialListDelegates.OnGenerateCustomMaterialWidgets, MaterialListDelegates.OnResetMaterialToDefaultClicked, NumMaterials, bShowUsedTextures, bDisplayCompactSize); + + TSharedPtr RightSideContent; + if( bDisplayLink ) + { + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("NumMaterials"), NumMaterials); + + RightSideContent = + SNew( SBox ) + .HAlign(HAlign_Left) + .VAlign(VAlign_Top) + [ + SNew( SHyperlink ) + .TextStyle( FEditorStyle::Get(), "MaterialList.HyperlinkStyle" ) + .Text( FText::Format(LOCTEXT("DisplayAllMaterialLinkText", "Display {NumMaterials} materials"), Arguments) ) + .ToolTipText( LOCTEXT("DisplayAllMaterialLink_ToolTip","Display all materials. Drag and drop a material here to replace all materials.") ) + .OnNavigate( this, &FMaterialList::OnDisplayMaterialsForElement, CurrentSlot ) + ]; + } + else + { + RightSideContent = NewView->CreateValueContent( DetailLayoutBuilder.GetThumbnailPool() ); + ViewedMaterials.Add( NewView ); + } + + Row.CopyAction(FUIAction(FExecuteAction::CreateSP(this, &FMaterialList::OnCopyMaterialItem, Item.SlotIndex), FCanExecuteAction::CreateSP(this, &FMaterialList::OnCanCopyMaterialItem, Item.SlotIndex))); + Row.PasteAction(FUIAction(FExecuteAction::CreateSP(this, &FMaterialList::OnPasteMaterialItem, Item.SlotIndex))); + + Row.NameContent() + [ + NewView->CreateNameContent() + ] + .ValueContent() + .MinDesiredWidth(250.f) + .MaxDesiredWidth(0.0f) // no maximum + [ + RightSideContent.ToSharedRef() + ]; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Source/Editor/PropertyEditor/Private/ObjectPropertyNode.cpp b/Engine/Source/Editor/PropertyEditor/Private/ObjectPropertyNode.cpp index 4ea14da7e65a..bb82d09c1371 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/ObjectPropertyNode.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/ObjectPropertyNode.cpp @@ -136,6 +136,13 @@ bool FObjectPropertyNode::GetReadAddressUncached(FPropertyNode& InNode, return false; } + UStruct* OwnerStruct = InItemProperty->GetOwnerStruct(); + if (!OwnerStruct || OwnerStruct->Children == nullptr) + { + // Verify that the property is not part of an invalid trash class + return false; + } + // Requesting a single selection? if( InRequiresSingleSelection && GetNumObjects() > 1) { diff --git a/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyEditor/PropertyEditor.cpp b/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyEditor/PropertyEditor.cpp index 6b3d92816276..a0d526059728 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyEditor/PropertyEditor.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyEditor/PropertyEditor.cpp @@ -16,6 +16,9 @@ #include "Kismet2/KismetEditorUtilities.h" #include "IConfigEditorModule.h" #include "PropertyNode.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "EditConditionParser.h" +#include "EditConditionContext.h" #define LOCTEXT_NAMESPACE "PropertyEditor" @@ -27,11 +30,9 @@ TSharedRef< FPropertyEditor > FPropertyEditor::Create( const TSharedRef< class F } FPropertyEditor::FPropertyEditor( const TSharedRef& InPropertyNode, const TSharedRef& InPropertyUtilities ) - : PropertyEditConditions() - , PropertyHandle( NULL ) + : PropertyHandle( NULL ) , PropertyNode( InPropertyNode ) , PropertyUtilities( InPropertyUtilities ) - , EditConditionProperty( NULL ) { // FPropertyEditor isn't built to handle CategoryNodes check( InPropertyNode->AsCategoryNode() == NULL ); @@ -40,14 +41,19 @@ FPropertyEditor::FPropertyEditor( const TSharedRef& InPropertyNod if( Property ) { - //see if the property supports some kind of edit condition and this isn't the "parent" property of a static array - const bool bStaticArray = Property->ArrayDim > 1 && InPropertyNode->GetArrayIndex() == INDEX_NONE; + static const FName EditConditionName = TEXT("EditCondition"); - if ( Property->HasMetaData( TEXT( "EditCondition" ) ) && !bStaticArray ) + //see if the property supports some kind of edit condition and this isn't the "parent" property of a static array + if (Property->HasMetaData(EditConditionName) && !PropertyEditorHelpers::IsStaticArray(PropertyNode.Get())) { - if ( !GetEditConditionPropertyAddress( /*OUT*/EditConditionProperty, *InPropertyNode, PropertyEditConditions ) ) + TSharedPtr Parser = PropertyUtilities->GetEditConditionParser(); + if (Parser.IsValid()) { - EditConditionProperty = NULL; + EditConditionExpression = Parser->Parse(Property->GetMetaData(EditConditionName)); + if (EditConditionExpression.IsValid()) + { + EditConditionContext = MakeShareable(new FEditConditionContext(PropertyNode.Get())); + } } } } @@ -195,6 +201,12 @@ void FPropertyEditor::AddItem() PropertyUtilities->EnqueueDeferredAction( FSimpleDelegate::CreateSP( this, &FPropertyEditor::OnAddItem ) ); } +void FPropertyEditor::AddGivenItem(const FString& InGivenItem) +{ + // This action must be deferred until next tick so that we avoid accessing invalid data before we have a chance to tick + PropertyUtilities->EnqueueDeferredAction(FSimpleDelegate::CreateSP(this, &FPropertyEditor::OnAddGivenItem, InGivenItem)); +} + void FPropertyEditor::OnAddItem() { // Check to make sure that the property is a valid container @@ -227,6 +239,30 @@ void FPropertyEditor::OnAddItem() } } +void FPropertyEditor::OnAddGivenItem(const FString InGivenItem) +{ + OnAddItem(); + + // Check to make sure that the property is a valid container + TSharedPtr ArrayHandle = PropertyHandle->AsArray(); + + check(ArrayHandle.IsValid()); + + TSharedPtr ElementHandle; + + if (ArrayHandle.IsValid()) + { + uint32 Last; + ArrayHandle->GetNumElements(Last); + ElementHandle = ArrayHandle->GetElement(Last - 1); + } + + if (ElementHandle.IsValid()) + { + ElementHandle->SetValueFromFormattedString(InGivenItem); + } +} + void FPropertyEditor::ClearItem() { OnClearItem(); @@ -244,12 +280,19 @@ void FPropertyEditor::MakeNewBlueprint() UClassProperty* ClassProp = Cast(NodeProperty); UClass* Class = (ClassProp ? ClassProp->MetaClass : FEditorClassUtils::GetClassFromString(NodeProperty->GetMetaData("MetaClass"))); + UClass* RequiredInterface = FEditorClassUtils::GetClassFromString(NodeProperty->GetMetaData("MustImplement")); + if (Class) { UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprintFromClass(LOCTEXT("CreateNewBlueprint", "Create New Blueprint"), Class, FString::Printf(TEXT("New%s"),*Class->GetName())); if(Blueprint != NULL && Blueprint->GeneratedClass) { + if (RequiredInterface != nullptr && FKismetEditorUtilities::CanBlueprintImplementInterface(Blueprint, RequiredInterface)) + { + FBlueprintEditorUtils::ImplementNewInterface(Blueprint, RequiredInterface->GetFName()); + } + PropertyHandle->SetValueFromFormattedString(Blueprint->GeneratedClass->GetPathName()); FAssetEditorManager::Get().OpenEditorForAsset(Blueprint); @@ -402,7 +445,7 @@ bool FPropertyEditor::IsEditConst() const return PropertyNode->IsEditConst(); } -void FPropertyEditor::SetEditConditionState( bool bShouldEnable ) +void FPropertyEditor::ToggleEditConditionState() { const FScopedTransaction Transaction(FText::Format(LOCTEXT("SetEditConditionState", "Set {0} edit condition state "), PropertyNode->GetDisplayName())); @@ -413,17 +456,25 @@ void FPropertyEditor::SetEditConditionState( bool bShouldEnable ) check(ParentNode != nullptr); PropertyNode->NotifyPreChange( PropertyNode->GetProperty(), PropertyUtilities->GetNotifyHook() ); - for ( int32 ValueIdx = 0; ValueIdx < PropertyEditConditions.Num(); ValueIdx++ ) + + const UBoolProperty* EditConditionProperty = EditConditionContext->GetSingleBoolProperty(EditConditionExpression); + check(EditConditionProperty != nullptr); + + FComplexPropertyNode* ComplexParentNode = ParentNode->FindComplexParent(); + for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) { + uint8* BaseAddress = ComplexParentNode->GetMemoryOfInstance(Index); + check(BaseAddress != nullptr); + // Get the address corresponding to the base of this property (i.e. if a struct property, set BaseOffset to the address of value for the whole struct) - uint8* BaseOffset = ParentNode->GetValueAddress(PropertyEditConditions[ValueIdx].BaseAddress); + uint8* BaseOffset = ParentNode->GetValueAddress(BaseAddress); check(BaseOffset != NULL); uint8* ValueAddr = EditConditionProperty->ContainerPtrToValuePtr(BaseOffset); - const bool OldValue = EditConditionProperty->GetPropertyValue( ValueAddr ); - const bool NewValue = XOR(bShouldEnable, PropertyEditConditions[ValueIdx].bNegateValue); - EditConditionProperty->SetPropertyValue( ValueAddr, NewValue ); + const bool OldValue = EditConditionProperty->GetPropertyValue(ValueAddr); + const bool NewValue = !OldValue; + EditConditionProperty->SetPropertyValue(ValueAddr, NewValue); if (ObjectNode != nullptr) { @@ -505,8 +556,7 @@ void FPropertyEditor::OnGetActorFiltersForSceneOutliner( TSharedPtrIsPropertyEditingEnabled() ) && - (EditConditionProperty == NULL || IsEditConditionMet( EditConditionProperty, PropertyEditConditions )); + return ( PropertyUtilities->IsPropertyEditingEnabled() ) && (!HasEditCondition() || IsEditConditionMet()); } void FPropertyEditor::ForceRefresh() @@ -521,17 +571,48 @@ void FPropertyEditor::RequestRefresh() bool FPropertyEditor::HasEditCondition() const { - return EditConditionProperty != NULL; + return EditConditionExpression.IsValid(); } bool FPropertyEditor::IsEditConditionMet() const { - return IsEditConditionMet( EditConditionProperty, PropertyEditConditions ); + if (HasEditCondition()) + { + TSharedPtr EditConditionParser = PropertyUtilities->GetEditConditionParser(); + if (EditConditionParser.IsValid()) + { + TOptional Result = EditConditionParser->Evaluate(*EditConditionExpression.Get(), *EditConditionContext.Get()); + if (Result.IsSet()) + { + return Result.GetValue(); + } + } + } + + return true; } bool FPropertyEditor::SupportsEditConditionToggle() const { - return SupportsEditConditionToggle( PropertyNode->GetProperty() ); + UProperty* Property = PropertyNode->GetProperty(); + + static const FName Name_HideEditConditionToggle("HideEditConditionToggle"); + if (!Property->HasMetaData(Name_HideEditConditionToggle) && EditConditionExpression.IsValid()) + { + const UBoolProperty* ConditionalProperty = EditConditionContext->GetSingleBoolProperty(EditConditionExpression); + if (ConditionalProperty != nullptr) + { + // If it's editable, then only put an inline toggle box if the metadata specifies it + static const FName Name_InlineEditConditionToggle("InlineEditConditionToggle"); + if (ConditionalProperty->HasAllPropertyFlags(CPF_Edit) && + ConditionalProperty->HasMetaData(Name_InlineEditConditionToggle)) + { + return true; + } + } + } + + return false; } void FPropertyEditor::AddPropertyEditorChild( const TSharedRef& Child ) @@ -564,145 +645,6 @@ TSharedRef< IPropertyHandle > FPropertyEditor::GetPropertyHandle() const return PropertyHandle.ToSharedRef(); } -bool FPropertyEditor::IsEditConditionMet( UBoolProperty* ConditionProperty, const TArray& ConditionValues ) const -{ - check(ConditionProperty); - - bool bResult = false; - - FPropertyNode* ParentNode = PropertyNode->GetParentNode(); - if (ParentNode) - { - bool bAllConditionsMet = true; - for (int32 ValueIdx = 0; bAllConditionsMet && ValueIdx < ConditionValues.Num(); ValueIdx++) - { - if (!ConditionValues[ValueIdx].Object.IsValid()) - { - bAllConditionsMet = false; - break; - } - - uint8* BaseOffset = ParentNode->GetValueAddress(ConditionValues[ValueIdx].BaseAddress); - if (!BaseOffset) - { - bAllConditionsMet = false; - break; - } - - uint8* ValueAddr = EditConditionProperty->ContainerPtrToValuePtr(BaseOffset); - - if (ConditionValues[ValueIdx].bNegateValue) - { - bAllConditionsMet = !ConditionProperty->GetPropertyValue(ValueAddr); - } - else - { - bAllConditionsMet = ConditionProperty->GetPropertyValue(ValueAddr); - } - } - - bResult = bAllConditionsMet; - } - - return bResult; -} - -bool FPropertyEditor::GetEditConditionPropertyAddress( UBoolProperty*& ConditionProperty, FPropertyNode& InPropertyNode, TArray& ConditionPropertyAddresses ) -{ - bool bResult = false; - bool bNegate = false; - UBoolProperty* EditConditionProperty = PropertyCustomizationHelpers::GetEditConditionProperty(InPropertyNode.GetProperty(), bNegate); - if ( EditConditionProperty != NULL ) - { - FPropertyNode* ParentNode = InPropertyNode.GetParentNode(); - check(ParentNode); - - UProperty* Property = InPropertyNode.GetProperty(); - if (Property) - { - bool bStaticArray = (Property->ArrayDim > 1) && (InPropertyNode.GetArrayIndex() != INDEX_NONE); - if (bStaticArray) - { - //in the case of conditional static arrays, we have to go up one more level to get the proper parent struct. - ParentNode = ParentNode->GetParentNode(); - check(ParentNode); - } - } - - auto ComplexParentNode = ParentNode->FindComplexParent(); - if (ComplexParentNode) - { - for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) - { - uint8* BaseAddress = ComplexParentNode->GetMemoryOfInstance(Index); - if (BaseAddress) - { - // Get the address corresponding to the base of this property (i.e. if a struct property, set BaseOffset to the address of value for the whole struct) - uint8* BaseOffset = ParentNode->GetValueAddress(BaseAddress); - check(BaseOffset != NULL); - - FPropertyConditionInfo NewCondition; - // now calculate the address of the property value being used as the condition and add it to the array. - NewCondition.Object = ComplexParentNode->AsStructureNode() ? TWeakObjectPtr(ComplexParentNode->GetBaseStructure()) : ComplexParentNode->GetInstanceAsUObject(Index); - NewCondition.BaseAddress = BaseAddress; - NewCondition.bNegateValue = bNegate; - ConditionPropertyAddresses.Add(NewCondition); - bResult = true; - } - } - } - } - - if ( bResult ) - { - // set the output variable - ConditionProperty = EditConditionProperty; - } - - return bResult; -} - -bool FPropertyEditor::SupportsEditConditionToggle( UProperty* InProperty ) -{ - bool bShowEditConditionToggle = false; - - static const FName Name_HideEditConditionToggle("HideEditConditionToggle"); - if (!InProperty->HasMetaData(Name_HideEditConditionToggle)) - { - bool bNegateValue = false; - UBoolProperty* ConditionalProperty = PropertyCustomizationHelpers::GetEditConditionProperty( InProperty, bNegateValue ); - if( ConditionalProperty != NULL ) - { - if (!ConditionalProperty->HasAllPropertyFlags(CPF_Edit)) - { - // Warn if the editcondition property is not marked as editable; this will break when the component is added to a Blueprint. - UObject* PropertyScope = InProperty->GetOwnerClass(); - UObject* ConditionalPropertyScope = ConditionalProperty->GetOwnerClass(); - if (!PropertyScope) - { - PropertyScope = InProperty->GetOwnerStruct(); - ConditionalPropertyScope = ConditionalProperty->GetOwnerStruct(); - } - const FString PropertyScopeName = PropertyScope ? PropertyScope->GetName() : FString(); - const FString ConditionalPropertyScopeName = ConditionalPropertyScope ? ConditionalPropertyScope->GetName() : FString(); - - bShowEditConditionToggle = true; - } - else - { - // If it's editable, then only put an inline toggle box if the metadata specifies it - static const FName Name_InlineEditConditionToggle("InlineEditConditionToggle"); - if (ConditionalProperty->HasMetaData(Name_InlineEditConditionToggle)) - { - bShowEditConditionToggle = true; - } - } - } - } - - return bShowEditConditionToggle; -} - void FPropertyEditor::SyncToObjectsInNode( const TWeakPtr< FPropertyNode >& WeakPropertyNode ) { #if WITH_EDITOR diff --git a/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyEditor/PropertyEditor.h b/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyEditor/PropertyEditor.h index 2856df2d4e31..78370334543d 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyEditor/PropertyEditor.h +++ b/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyEditor/PropertyEditor.h @@ -6,6 +6,9 @@ #include "PropertyHandle.h" #include "Editor/SceneOutliner/Public/SceneOutlinerFwd.h" +class FEditConditionExpression; +class FEditConditionContext; + class FPropertyEditor : public TSharedFromThis< FPropertyEditor > { public: @@ -38,6 +41,7 @@ public: void RequestRefresh(); bool SupportsEditConditionToggle() const; + void ToggleEditConditionState(); /** @return Whether the property is editconst */ bool IsEditConst() const; @@ -45,9 +49,6 @@ public: /** @return Whether the property has a condition which must be met before allowing editing of it's value */ bool HasEditCondition() const; - /** Sets the state of the property's edit condition to the specified value */ - void SetEditConditionState( bool bShouldEnable ); - /** @return Whether the condition has been met to allow editing of this property's value */ bool IsEditConditionMet() const; @@ -72,6 +73,7 @@ public: void UseSelected(); void AddItem(); + void AddGivenItem(const FString& InGivenItem); void ClearItem(); void InsertItem(); void DeleteItem(); @@ -96,19 +98,9 @@ public: private: FPropertyEditor( const TSharedRef< class FPropertyNode >& InPropertyNode, const TSharedRef& InPropertyUtilities ); - /** Stores information about property condition metadata. */ - struct FPropertyConditionInfo - { - public: - TWeakObjectPtr Object; - - uint8* BaseAddress; - /** Whether the condition should be negated. */ - bool bNegateValue; - }; - void OnUseSelected(); void OnAddItem(); + void OnAddGivenItem(const FString InGivenItem); void OnClearItem(); void OnInsertItem(); void OnDeleteItem(); @@ -116,29 +108,8 @@ private: void OnBrowseTo(); void OnEmptyArray(); - /** - * Returns true if the value of the conditional property matches the value required. Indicates whether editing or otherwise interacting with this item's - * associated property should be allowed. - */ - bool IsEditConditionMet( UBoolProperty* ConditionProperty, const TArray& ConditionValues ) const; - - /** - * Finds the property being used to determine whether this item's associated property should be editable/expandable. - * If the property is successfully found, ConditionPropertyAddresses will be filled with the addresses of the conditional properties' values. - * - * @param ConditionProperty receives the value of the property being used to control this item's ability to be edited. - * @param ConditionPropertyAddresses receives the addresses of the actual property values for the conditional property - * - * @return true if both the conditional property and property value addresses were successfully determined. - */ - static bool GetEditConditionPropertyAddress( UBoolProperty*& ConditionProperty, FPropertyNode& InPropertyNode, TArray& ConditionPropertyAddresses ); - - static bool SupportsEditConditionToggle( UProperty* InProperty ); - private: - TArray< FPropertyConditionInfo > PropertyEditConditions; - TArray< TSharedRef< FPropertyEditor > > ChildPropertyEditors; /** Property handle for actually reading/writing the value of a property */ @@ -150,6 +121,7 @@ private: /** The property view where this widget resides */ TSharedRef< class IPropertyUtilities > PropertyUtilities; - /** Edit condition property used to determine if this property editor can modify its property */ - class UBoolProperty* EditConditionProperty; + /** Edit condition expression used to determine if this property editor can modify its property */ + TSharedPtr EditConditionExpression; + TSharedPtr EditConditionContext; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyTable/PropertyTable.cpp b/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyTable/PropertyTable.cpp index 558275d8ee78..d2f1c6e6ab03 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyTable/PropertyTable.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyTable/PropertyTable.cpp @@ -3,7 +3,7 @@ #include "Presentation/PropertyTable/PropertyTable.h" #include "Misc/FeedbackContext.h" #include "Editor/EditorPerProjectUserSettings.h" - +#include "Editor.h" #include "Presentation/PropertyTable/PropertyTableColumn.h" #include "Presentation/PropertyTable/PropertyTableRow.h" @@ -11,6 +11,7 @@ #include "Presentation/PropertyTable/PropertyTableObjectNameColumn.h" #include "Presentation/PropertyTable/PropertyTablePropertyNameColumn.h" +#include "EditConditionParser.h" #define LOCTEXT_NAMESPACE "PropertyTable" @@ -32,9 +33,18 @@ FPropertyTable::FPropertyTable() , AllowUserToChangeRoot( true ) , bRefreshRequested( false ) , Orientation( EPropertyTableOrientation::AlignPropertiesInColumns ) + , EditConditionParser( new FEditConditionParser ) { } +FPropertyTable::~FPropertyTable() +{ + if (GEditor && ObjectsReplacedHandle.IsValid()) + { + GEditor->OnObjectsReplaced().Remove(ObjectsReplacedHandle); + } +} + void FPropertyTable::Tick() { // Execute any deferred actions @@ -103,6 +113,11 @@ TSharedPtr FPropertyTable::GetThumbnailPool() const return NULL; } +TSharedPtr FPropertyTable::GetEditConditionParser() const +{ + return EditConditionParser; +} + bool FPropertyTable::GetIsUserAllowedToChangeRoot() { return AllowUserToChangeRoot; @@ -214,6 +229,22 @@ void FPropertyTable::RemoveRow( const TSharedRef< class IPropertyTableRow >& Row } } +void FPropertyTable::ResetTable() +{ + Rows.Reset(); + Columns.Reset(); + SourceObjectPropertyNodes.Reset(); + SelectedColumns.Reset(); + SelectedRows.Reset(); + SelectedCells.Reset(); + StartingCellSelectionRange = nullptr; + EndingCellSelectionRange = nullptr; + CurrentRow = nullptr; + CurrentCell = nullptr; + CurrentColumn = nullptr; + LastClickedCell = nullptr; +} + void FPropertyTable::PurgeInvalidObjectNodes() { TArray< TSharedRef< FObjectPropertyNode > > ValidNodes; @@ -221,7 +252,7 @@ void FPropertyTable::PurgeInvalidObjectNodes() { const TWeakObjectPtr< UObject > Object = NodeIter.Key(); - if ( !Object.IsValid() ) + if ( Object.IsValid() ) { ValidNodes.Add( NodeIter.Value() ); } @@ -264,7 +295,7 @@ TArray< FPropertyInfo > FPropertyTable::GetPossibleExtensionsForPath( const TSha { const FPropertyInfo& Info = *ExtensionIter; - if ( Info.ArrayIndex == INDEX_NONE && + if ( Info.ArrayIndex == INDEX_NONE && Info.Property.IsValid() && ( Info.Property->IsA( UStructProperty::StaticClass() ) || Info.Property->IsA( UArrayProperty::StaticClass() ) ) ) { @@ -993,19 +1024,56 @@ void FPropertyTable::SetObjects( const TArray< TWeakObjectPtr< UObject > >& Obje UpdateColumns(); UpdateRows(); + + // Bind to object delegates, we can't do this at construction because the shared pointer isn't set up yet + if (GEditor && !ObjectsReplacedHandle.IsValid()) + { + ObjectsReplacedHandle = GEditor->OnObjectsReplaced().AddSP(this, &FPropertyTable::OnObjectsReplaced); + } } void FPropertyTable::SetObjects( const TArray< UObject* >& Objects ) { - SourceObjects.Empty(); + TArray> WeakObjects; - for( auto ObjectIter = Objects.CreateConstIterator(); ObjectIter; ++ObjectIter ) + for(UObject* Object : Objects) { - SourceObjects.Add( *ObjectIter ); + WeakObjects.Add(Object); } - UpdateColumns(); - UpdateRows(); + SetObjects(WeakObjects); +} + +void FPropertyTable::OnObjectsReplaced(const TMap& ReplacementMap) +{ + bool bChangedAny = false; + + // Fix up any objects in our source list that were replaced, then refresh UI + for (int32 i = 0; i < SourceObjects.Num(); i++) + { + UObject* SourceObject = SourceObjects[i].Get(true); + UObject* ReplacedObject = ReplacementMap.FindRef(SourceObject); + + if (ReplacedObject && ReplacedObject != SourceObject) + { + SourceObjectPropertyNodes.Remove(SourceObject); + SourceObjects[i] = ReplacedObject; + + bChangedAny = true; + } + } + + if (bChangedAny) + { + // The update functions do not correctly handle partial reconstruction, so delete everything and reconstruct + ResetTable(); + + UpdateRows(); + UpdateColumns(); + + // This reset our selection so broadcast it + SelectionChanged.Broadcast(); + } } void FPropertyTable::SetShowRowHeader( const bool InShowRowHeader ) diff --git a/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyTable/PropertyTable.h b/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyTable/PropertyTable.h index 2e235f9d7b71..922e4f721ca3 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyTable/PropertyTable.h +++ b/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyTable/PropertyTable.h @@ -15,6 +15,7 @@ class FPropertyTable : public TSharedFromThis< FPropertyTable >, public IPropert public: FPropertyTable(); + virtual ~FPropertyTable(); virtual void Tick() override; @@ -28,6 +29,7 @@ public: virtual void EnqueueDeferredAction( FSimpleDelegate DeferredAction ) override; virtual TSharedPtr GetThumbnailPool() const override; virtual void NotifyFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent) override {} + virtual TSharedPtr GetEditConditionParser() const override; virtual bool GetIsUserAllowedToChangeRoot() override; virtual void SetIsUserAllowedToChangeRoot( bool InAllowUserToChangeRoot ) override; @@ -167,6 +169,9 @@ private: void PurgeInvalidObjectNodes(); + void ResetTable(); + + void OnObjectsReplaced(const TMap& ReplacementMap); private: @@ -218,5 +223,9 @@ private: /** The Orientation of this table, I.e. do we swap columns and rows */ EPropertyTableOrientation::Type Orientation; + + FDelegateHandle ObjectsReplacedHandle; + + TSharedRef EditConditionParser; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/PropertyCustomizationHelpers.cpp b/Engine/Source/Editor/PropertyEditor/Private/PropertyCustomizationHelpers.cpp index 7c07d1e9f886..0d5eca08a316 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/PropertyCustomizationHelpers.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/PropertyCustomizationHelpers.cpp @@ -687,624 +687,6 @@ bool SProperty::IsValidProperty() const return PropertyHandle.IsValid() && PropertyHandle->IsValidHandle(); } -/** - * Builds up a list of unique materials while creating some information about the materials - */ -class FMaterialListBuilder : public IMaterialListBuilder -{ - friend class FMaterialList; -public: - - /** - * Adds a new material to the list - * - * @param SlotIndex The slot (usually mesh element index) where the material is located on the component - * @param Material The material being used - * @param bCanBeReplced Whether or not the material can be replaced by a user - */ - virtual void AddMaterial( uint32 SlotIndex, UMaterialInterface* Material, bool bCanBeReplaced ) override - { - int32 NumMaterials = MaterialSlots.Num(); - - FMaterialListItem MaterialItem( Material, SlotIndex, bCanBeReplaced ); - if( !UniqueMaterials.Contains( MaterialItem ) ) - { - MaterialSlots.Add( MaterialItem ); - UniqueMaterials.Add( MaterialItem ); - } - - // Did we actually add material? If we did then we need to increment the number of materials in the element - if( MaterialSlots.Num() > NumMaterials ) - { - // Resize the array to support the slot if needed - if( !MaterialCount.IsValidIndex(SlotIndex) ) - { - int32 NumToAdd = (SlotIndex - MaterialCount.Num()) + 1; - if( NumToAdd > 0 ) - { - MaterialCount.AddZeroed( NumToAdd ); - } - } - - ++MaterialCount[SlotIndex]; - } - } - - /** Empties the list */ - void Empty() - { - UniqueMaterials.Empty(); - MaterialSlots.Reset(); - MaterialCount.Reset(); - } - - /** Sorts the list by slot index */ - void Sort() - { - struct FSortByIndex - { - bool operator()( const FMaterialListItem& A, const FMaterialListItem& B ) const - { - return A.SlotIndex < B.SlotIndex; - } - }; - - MaterialSlots.Sort( FSortByIndex() ); - } - - /** @return The number of materials in the list */ - uint32 GetNumMaterials() const { return MaterialSlots.Num(); } - - /** @return The number of materials in the list at a given slot */ - uint32 GetNumMaterialsInSlot( uint32 Index ) const { return MaterialCount[Index]; } -private: - /** All unique materials */ - TSet UniqueMaterials; - /** All material items in the list */ - TArray MaterialSlots; - /** Material counts for each slot. The slot is the index and the value at that index is the count */ - TArray MaterialCount; -}; - -/** - * A view of a single item in an FMaterialList - */ -class FMaterialItemView : public TSharedFromThis -{ -public: - /** - * Creates a new instance of this class - * - * @param Material The material to view - * @param InOnMaterialChanged Delegate for when the material changes - */ - static TSharedRef Create( - const FMaterialListItem& Material, - FOnMaterialChanged InOnMaterialChanged, - FOnGenerateWidgetsForMaterial InOnGenerateNameWidgetsForMaterial, - FOnGenerateWidgetsForMaterial InOnGenerateWidgetsForMaterial, - FOnResetMaterialToDefaultClicked InOnResetToDefaultClicked, - int32 InMultipleMaterialCount, - bool bShowUsedTextures, - bool bDisplayCompactSize) - { - return MakeShareable( new FMaterialItemView( Material, InOnMaterialChanged, InOnGenerateNameWidgetsForMaterial, InOnGenerateWidgetsForMaterial, InOnResetToDefaultClicked, InMultipleMaterialCount, bShowUsedTextures, bDisplayCompactSize) ); - } - - TSharedRef CreateNameContent() - { - FFormatNamedArguments Arguments; - Arguments.Add(TEXT("ElementIndex"), MaterialItem.SlotIndex); - - return - SNew(SVerticalBox) - +SVerticalBox::Slot() - .VAlign(VAlign_Center) - [ - SNew( STextBlock ) - .Font( IDetailLayoutBuilder::GetDetailFont() ) - .Text( FText::Format(LOCTEXT("ElementIndex", "Element {ElementIndex}"), Arguments ) ) - ] - +SVerticalBox::Slot() - .Padding(0.0f,4.0f) - .AutoHeight() - [ - OnGenerateCustomNameWidgets.IsBound() ? OnGenerateCustomNameWidgets.Execute( MaterialItem.Material.Get(), MaterialItem.SlotIndex ) : StaticCastSharedRef( SNullWidget::NullWidget ) - ]; - } - - TSharedRef CreateValueContent( const TSharedPtr& ThumbnailPool ) - { - FIntPoint ThumbnailSize(64, 64); - - FResetToDefaultOverride ResetToDefaultOverride = FResetToDefaultOverride::Create( - FIsResetToDefaultVisible::CreateSP(this, &FMaterialItemView::GetReplaceVisibility), - FResetToDefaultHandler::CreateSP(this, &FMaterialItemView::OnResetToBaseClicked) - ); - - return - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - .AutoHeight() - .Padding( 0.0f ) - .VAlign(VAlign_Center) - .HAlign(HAlign_Fill) - [ - SNew(SHorizontalBox) - +SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew( SObjectPropertyEntryBox ) - .ObjectPath(MaterialItem.Material->GetPathName()) - .AllowedClass(UMaterialInterface::StaticClass()) - .OnObjectChanged(this, &FMaterialItemView::OnSetObject) - .ThumbnailPool(ThumbnailPool) - .DisplayCompactSize(bDisplayCompactSize) - .CustomResetToDefault(ResetToDefaultOverride) - .CustomContentSlot() - [ - SNew( SBox ) - .HAlign(HAlign_Left) - .VAlign(VAlign_Center) - [ - SNew(SHorizontalBox) - +SHorizontalBox::Slot() - .VAlign(VAlign_Center) - .Padding(0.0f, 0.0f, 3.0f, 0.0f) - .AutoWidth() - [ - // Add a menu for displaying all textures - SNew( SComboButton ) - .OnGetMenuContent( this, &FMaterialItemView::OnGetTexturesMenuForMaterial ) - .VAlign(VAlign_Center) - .ContentPadding(2) - .IsEnabled( this, &FMaterialItemView::IsTexturesMenuEnabled ) - .Visibility( bShowUsedTextures ? EVisibility::Visible : EVisibility::Hidden ) - .ButtonContent() - [ - SNew( STextBlock ) - .Font( IDetailLayoutBuilder::GetDetailFont() ) - .ToolTipText( LOCTEXT("ViewTexturesToolTip", "View the textures used by this material" ) ) - .Text( LOCTEXT("ViewTextures","Textures") ) - ] - ] - +SHorizontalBox::Slot() - .Padding(3.0f, 0.0f) - .FillWidth(1.0f) - [ - OnGenerateCustomMaterialWidgets.IsBound() && bDisplayCompactSize ? OnGenerateCustomMaterialWidgets.Execute(MaterialItem.Material.Get(), MaterialItem.SlotIndex) : StaticCastSharedRef(SNullWidget::NullWidget) - ] - ] - ] - ] - ] - +SVerticalBox::Slot() - .AutoHeight() - .Padding(2) - .VAlign( VAlign_Center ) - [ - OnGenerateCustomMaterialWidgets.IsBound() && !bDisplayCompactSize ? OnGenerateCustomMaterialWidgets.Execute( MaterialItem.Material.Get(), MaterialItem.SlotIndex ) : StaticCastSharedRef( SNullWidget::NullWidget ) - ] - ]; - } - -private: - - FMaterialItemView( const FMaterialListItem& InMaterial, - FOnMaterialChanged& InOnMaterialChanged, - FOnGenerateWidgetsForMaterial& InOnGenerateNameWidgets, - FOnGenerateWidgetsForMaterial& InOnGenerateMaterialWidgets, - FOnResetMaterialToDefaultClicked& InOnResetToDefaultClicked, - int32 InMultipleMaterialCount, - bool bInShowUsedTextures, - bool bInDisplayCompactSize) - - : MaterialItem( InMaterial ) - , OnMaterialChanged( InOnMaterialChanged ) - , OnGenerateCustomNameWidgets( InOnGenerateNameWidgets ) - , OnGenerateCustomMaterialWidgets( InOnGenerateMaterialWidgets ) - , OnResetToDefaultClicked( InOnResetToDefaultClicked ) - , MultipleMaterialCount( InMultipleMaterialCount ) - , bShowUsedTextures( bInShowUsedTextures ) - , bDisplayCompactSize(bInDisplayCompactSize) - { - - } - - void ReplaceMaterial( UMaterialInterface* NewMaterial, bool bReplaceAll = false ) - { - UMaterialInterface* PrevMaterial = NULL; - if( MaterialItem.Material.IsValid() ) - { - PrevMaterial = MaterialItem.Material.Get(); - } - - if( NewMaterial != PrevMaterial ) - { - // Replace the material - OnMaterialChanged.ExecuteIfBound( NewMaterial, PrevMaterial, MaterialItem.SlotIndex, bReplaceAll ); - } - } - - void OnSetObject( const FAssetData& AssetData ) - { - const bool bReplaceAll = false; - - UMaterialInterface* NewMaterial = Cast(AssetData.GetAsset()); - ReplaceMaterial( NewMaterial, bReplaceAll ); - } - - /** - * @return Whether or not the textures menu is enabled - */ - bool IsTexturesMenuEnabled() const - { - return MaterialItem.Material.Get() != NULL; - } - - TSharedRef OnGetTexturesMenuForMaterial() - { - FMenuBuilder MenuBuilder( true, NULL ); - - if( MaterialItem.Material.IsValid() ) - { - UMaterialInterface* Material = MaterialItem.Material.Get(); - - TArray< UTexture* > Textures; - Material->GetUsedTextures(Textures, EMaterialQualityLevel::Num, false, ERHIFeatureLevel::Num, true); - - // Add a menu item for each texture. Clicking on the texture will display it in the content browser - // UObject for delegate compatibility - for( UObject* Texture : Textures ) - { - FUIAction Action( FExecuteAction::CreateSP( this, &FMaterialItemView::GoToAssetInContentBrowser, MakeWeakObjectPtr(Texture) ) ); - - MenuBuilder.AddMenuEntry( FText::FromString( Texture->GetName() ), LOCTEXT( "BrowseTexture_ToolTip", "Find this texture in the content browser" ), FSlateIcon(), Action ); - } - } - - return MenuBuilder.MakeWidget(); - } - - /** - * Finds the asset in the content browser - */ - void GoToAssetInContentBrowser( TWeakObjectPtr Object ) - { - if( Object.IsValid() ) - { - TArray< UObject* > Objects; - Objects.Add( Object.Get() ); - GEditor->SyncBrowserToObjects( Objects ); - } - } - - /** - * Called to get the visibility of the replace button - */ - bool GetReplaceVisibility(TSharedPtr PropertyHandle) const - { - // Only show the replace button if the current material can be replaced - if (OnMaterialChanged.IsBound() && MaterialItem.bCanBeReplaced) - { - return true; - } - - return false; - } - - /** - * Called when reset to base is clicked - */ - void OnResetToBaseClicked(TSharedPtr PropertyHandle) - { - // Only allow reset to base if the current material can be replaced - if( MaterialItem.Material.IsValid() && MaterialItem.bCanBeReplaced ) - { - bool bReplaceAll = false; - ReplaceMaterial( NULL, bReplaceAll ); - OnResetToDefaultClicked.ExecuteIfBound( MaterialItem.Material.Get(), MaterialItem.SlotIndex ); - } - } - -private: - FMaterialListItem MaterialItem; - FOnMaterialChanged OnMaterialChanged; - FOnGenerateWidgetsForMaterial OnGenerateCustomNameWidgets; - FOnGenerateWidgetsForMaterial OnGenerateCustomMaterialWidgets; - FOnResetMaterialToDefaultClicked OnResetToDefaultClicked; - int32 MultipleMaterialCount; - bool bShowUsedTextures; - bool bDisplayCompactSize; -}; - - -FMaterialList::FMaterialList(IDetailLayoutBuilder& InDetailLayoutBuilder, FMaterialListDelegates& InMaterialListDelegates, bool bInAllowCollapse, bool bInShowUsedTextures, bool bInDisplayCompactSize) - : MaterialListDelegates( InMaterialListDelegates ) - , DetailLayoutBuilder( InDetailLayoutBuilder ) - , MaterialListBuilder( new FMaterialListBuilder ) - , bAllowCollpase(bInAllowCollapse) - , bShowUsedTextures(bInShowUsedTextures) - , bDisplayCompactSize(bInDisplayCompactSize) -{ -} - -void FMaterialList::OnDisplayMaterialsForElement( int32 SlotIndex ) -{ - // We now want to display all the materials in the element - ExpandedSlots.Add( SlotIndex ); - - MaterialListBuilder->Empty(); - MaterialListDelegates.OnGetMaterials.ExecuteIfBound( *MaterialListBuilder ); - - OnRebuildChildren.ExecuteIfBound(); -} - -void FMaterialList::OnHideMaterialsForElement( int32 SlotIndex ) -{ - // No longer want to expand the element - ExpandedSlots.Remove( SlotIndex ); - - // regenerate the materials - MaterialListBuilder->Empty(); - MaterialListDelegates.OnGetMaterials.ExecuteIfBound( *MaterialListBuilder ); - - OnRebuildChildren.ExecuteIfBound(); -} - - -void FMaterialList::Tick( float DeltaTime ) -{ - // Check each material to see if its still valid. This allows the material list to stay up to date when materials are changed out from under us - if( MaterialListDelegates.OnGetMaterials.IsBound() ) - { - // Whether or not to refresh the material list - bool bRefrestMaterialList = false; - - // Get the current list of materials from the user - MaterialListBuilder->Empty(); - MaterialListDelegates.OnGetMaterials.ExecuteIfBound( *MaterialListBuilder ); - - if( MaterialListBuilder->GetNumMaterials() != DisplayedMaterials.Num() ) - { - // The array sizes differ so we need to refresh the list - bRefrestMaterialList = true; - } - else - { - // Compare the new list against the currently displayed list - for( int32 MaterialIndex = 0; MaterialIndex < MaterialListBuilder->MaterialSlots.Num(); ++MaterialIndex ) - { - const FMaterialListItem& Item = MaterialListBuilder->MaterialSlots[MaterialIndex]; - - // The displayed materials is out of date if there isn't a 1:1 mapping between the material sets - if( !DisplayedMaterials.IsValidIndex( MaterialIndex ) || DisplayedMaterials[ MaterialIndex ] != Item ) - { - bRefrestMaterialList = true; - break; - } - } - } - - if (!bRefrestMaterialList && MaterialListDelegates.OnMaterialListDirty.IsBound()) - { - bRefrestMaterialList = MaterialListDelegates.OnMaterialListDirty.Execute(); - } - - if( bRefrestMaterialList ) - { - OnRebuildChildren.ExecuteIfBound(); - } - } -} - -void FMaterialList::GenerateHeaderRowContent( FDetailWidgetRow& NodeRow ) -{ - NodeRow.CopyAction(FUIAction(FExecuteAction::CreateSP(this, &FMaterialList::OnCopyMaterialList), FCanExecuteAction::CreateSP(this, &FMaterialList::OnCanCopyMaterialList))); - NodeRow.PasteAction(FUIAction(FExecuteAction::CreateSP(this, &FMaterialList::OnPasteMaterialList))); - - if (bAllowCollpase) - { - NodeRow.NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("MaterialHeaderTitle", "Materials")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ]; - } -} - -void FMaterialList::GenerateChildContent( IDetailChildrenBuilder& ChildrenBuilder ) -{ - ViewedMaterials.Empty(); - DisplayedMaterials.Empty(); - if( MaterialListBuilder->GetNumMaterials() > 0 ) - { - DisplayedMaterials = MaterialListBuilder->MaterialSlots; - - MaterialListBuilder->Sort(); - TArray& MaterialSlots = MaterialListBuilder->MaterialSlots; - - int32 CurrentSlot = INDEX_NONE; - bool bDisplayAllMaterialsInSlot = true; - for( auto It = MaterialSlots.CreateConstIterator(); It; ++It ) - { - const FMaterialListItem& Material = *It; - - if( CurrentSlot != Material.SlotIndex ) - { - // We've encountered a new slot. Make a widget to display that - CurrentSlot = Material.SlotIndex; - - uint32 NumMaterials = MaterialListBuilder->GetNumMaterialsInSlot(CurrentSlot); - - // If an element is expanded we want to display all its materials - bool bWantToDisplayAllMaterials = NumMaterials > 1 && ExpandedSlots.Contains(CurrentSlot); - - // If we are currently displaying an expanded set of materials for an element add a link to collapse all of them - if( bWantToDisplayAllMaterials ) - { - FDetailWidgetRow& ChildRow = ChildrenBuilder.AddCustomRow( LOCTEXT( "HideAllMaterialSearchString", "Hide All Materials") ); - - FFormatNamedArguments Arguments; - Arguments.Add(TEXT("ElementSlot"), CurrentSlot); - ChildRow - .ValueContent() - .MaxDesiredWidth(0.0f)// No Max Width - [ - SNew( SBox ) - .HAlign( HAlign_Center ) - [ - SNew( SHyperlink ) - .TextStyle( FEditorStyle::Get(), "MaterialList.HyperlinkStyle" ) - .Text( FText::Format(LOCTEXT("HideAllMaterialLinkText", "Hide All Materials on Element {ElementSlot}"), Arguments ) ) - .OnNavigate( this, &FMaterialList::OnHideMaterialsForElement, CurrentSlot ) - ] - ]; - } - - if( NumMaterials > 1 && !bWantToDisplayAllMaterials ) - { - // The current slot has multiple elements to view - bDisplayAllMaterialsInSlot = false; - - FDetailWidgetRow& ChildRow = ChildrenBuilder.AddCustomRow( FText::GetEmpty() ); - - AddMaterialItem( ChildRow, CurrentSlot, FMaterialListItem( NULL, CurrentSlot, true ), !bDisplayAllMaterialsInSlot ); - } - else - { - bDisplayAllMaterialsInSlot = true; - } - - } - - // Display each thumbnail element unless we shouldn't display multiple materials for one slot - if( bDisplayAllMaterialsInSlot ) - { - FDetailWidgetRow& ChildRow = ChildrenBuilder.AddCustomRow( Material.Material.IsValid()? FText::FromString(Material.Material->GetName()) : FText::GetEmpty() ); - - AddMaterialItem( ChildRow, CurrentSlot, Material, !bDisplayAllMaterialsInSlot ); - } - } - } - else - { - FDetailWidgetRow& ChildRow = ChildrenBuilder.AddCustomRow( LOCTEXT("NoMaterials", "No Materials") ); - - ChildRow - [ - SNew( SBox ) - .HAlign( HAlign_Center ) - [ - SNew( STextBlock ) - .Text( LOCTEXT("NoMaterials", "No Materials") ) - .Font( IDetailLayoutBuilder::GetDetailFont() ) - ] - ]; - } -} - -bool FMaterialList::OnCanCopyMaterialList() const -{ - if (MaterialListDelegates.OnCanCopyMaterialList.IsBound()) - { - return MaterialListDelegates.OnCanCopyMaterialList.Execute(); - } - - return false; -} - -void FMaterialList::OnCopyMaterialList() -{ - if (MaterialListDelegates.OnCopyMaterialList.IsBound()) - { - MaterialListDelegates.OnCopyMaterialList.Execute(); - } -} - -void FMaterialList::OnPasteMaterialList() -{ - if (MaterialListDelegates.OnPasteMaterialList.IsBound()) - { - MaterialListDelegates.OnPasteMaterialList.Execute(); - } -} - -bool FMaterialList::OnCanCopyMaterialItem(int32 CurrentSlot) const -{ - if (MaterialListDelegates.OnCanCopyMaterialItem.IsBound()) - { - return MaterialListDelegates.OnCanCopyMaterialItem.Execute(CurrentSlot); - } - - return false; -} - -void FMaterialList::OnCopyMaterialItem(int32 CurrentSlot) -{ - if (MaterialListDelegates.OnCopyMaterialItem.IsBound()) - { - MaterialListDelegates.OnCopyMaterialItem.Execute(CurrentSlot); - } -} - -void FMaterialList::OnPasteMaterialItem(int32 CurrentSlot) -{ - if (MaterialListDelegates.OnPasteMaterialItem.IsBound()) - { - MaterialListDelegates.OnPasteMaterialItem.Execute(CurrentSlot); - } -} - -void FMaterialList::AddMaterialItem( FDetailWidgetRow& Row, int32 CurrentSlot, const FMaterialListItem& Item, bool bDisplayLink ) -{ - uint32 NumMaterials = MaterialListBuilder->GetNumMaterialsInSlot(CurrentSlot); - - TSharedRef NewView = FMaterialItemView::Create( Item, MaterialListDelegates.OnMaterialChanged, MaterialListDelegates.OnGenerateCustomNameWidgets, MaterialListDelegates.OnGenerateCustomMaterialWidgets, MaterialListDelegates.OnResetMaterialToDefaultClicked, NumMaterials, bShowUsedTextures, bDisplayCompactSize); - - TSharedPtr RightSideContent; - if( bDisplayLink ) - { - FFormatNamedArguments Arguments; - Arguments.Add(TEXT("NumMaterials"), NumMaterials); - - RightSideContent = - SNew( SBox ) - .HAlign(HAlign_Left) - .VAlign(VAlign_Top) - [ - SNew( SHyperlink ) - .TextStyle( FEditorStyle::Get(), "MaterialList.HyperlinkStyle" ) - .Text( FText::Format(LOCTEXT("DisplayAllMaterialLinkText", "Display {NumMaterials} materials"), Arguments) ) - .ToolTipText( LOCTEXT("DisplayAllMaterialLink_ToolTip","Display all materials. Drag and drop a material here to replace all materials.") ) - .OnNavigate( this, &FMaterialList::OnDisplayMaterialsForElement, CurrentSlot ) - ]; - } - else - { - RightSideContent = NewView->CreateValueContent( DetailLayoutBuilder.GetThumbnailPool() ); - ViewedMaterials.Add( NewView ); - } - - Row.CopyAction(FUIAction(FExecuteAction::CreateSP(this, &FMaterialList::OnCopyMaterialItem, Item.SlotIndex), FCanExecuteAction::CreateSP(this, &FMaterialList::OnCanCopyMaterialItem, Item.SlotIndex))); - Row.PasteAction(FUIAction(FExecuteAction::CreateSP(this, &FMaterialList::OnPasteMaterialItem, Item.SlotIndex))); - - Row.NameContent() - [ - NewView->CreateNameContent() - ] - .ValueContent() - .MinDesiredWidth(250.f) - .MaxDesiredWidth(0.0f) // no maximum - [ - RightSideContent.ToSharedRef() - ]; -} - TSharedRef PropertyCustomizationHelpers::MakePropertyComboBox(const TSharedPtr& InPropertyHandle, FOnGetPropertyComboBoxStrings OnGetStrings, FOnGetPropertyComboBoxValue OnGetValue, FOnPropertyComboBoxValueSelected OnValueSelected) { FSlateFontInfo FontStyle = FEditorStyle::GetFontStyle(PropertyEditorConstants::PropertyFontStyle); diff --git a/Engine/Source/Editor/PropertyEditor/Private/PropertyEditorHelpers.cpp b/Engine/Source/Editor/PropertyEditor/Private/PropertyEditorHelpers.cpp index aa1a770d4968..b7830c2c8790 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/PropertyEditorHelpers.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/PropertyEditorHelpers.cpp @@ -362,7 +362,7 @@ void SEditConditionWidget::OnEditConditionCheckChanged( ECheckBoxState CheckStat if( PropertyEditor.IsValid() && PropertyEditor->HasEditCondition() && PropertyEditor->SupportsEditConditionToggle() ) { - PropertyEditor->SetEditConditionState( CheckState == ECheckBoxState::Checked ); + PropertyEditor->ToggleEditConditionState(); } else { @@ -621,13 +621,13 @@ namespace PropertyEditorHelpers return (NodeProperty->IsA() || NodeProperty->IsA()) && (!bUsingAssetPicker || !SPropertyEditorAsset::Supports(NodeProperty)); } - static bool IsSoftObjectPath( const UProperty* Property ) + bool IsSoftObjectPath( const UProperty* Property ) { const UStructProperty* StructProp = Cast( Property ); return StructProp && StructProp->Struct == TBaseStructure::Get(); } - static bool IsSoftClassPath( const UProperty* Property ) + bool IsSoftClassPath( const UProperty* Property ) { const UStructProperty* StructProp = Cast(Property); return StructProp && StructProp->Struct == TBaseStructure::Get(); @@ -733,16 +733,29 @@ namespace PropertyEditorHelpers // Handle a class property. UClassProperty* ClassProp = Cast(NodeProperty); - if( ClassProp || IsSoftClassPath(NodeProperty)) + USoftClassProperty* SoftClassProp = Cast(NodeProperty); + if( ClassProp || SoftClassProp || IsSoftClassPath(NodeProperty)) { OutRequiredButtons.Add( EPropertyButton::Use ); OutRequiredButtons.Add( EPropertyButton::Browse ); - UClass* Class = (ClassProp ? ClassProp->MetaClass : FEditorClassUtils::GetClassFromString(NodeProperty->GetMetaData("MetaClass"))); + UClass* Class = nullptr; + if (ClassProp) + { + Class = ClassProp->MetaClass; + } + else if (SoftClassProp) + { + Class = SoftClassProp->MetaClass; + } + else + { + Class = NodeProperty->GetOwnerProperty()->GetClassMetaData(TEXT("MetaClass")); + } if (Class && FKismetEditorUtilities::CanCreateBlueprintOfClass(Class) && !NodeProperty->HasMetaData("DisallowCreateNew")) { - OutRequiredButtons.Add( EPropertyButton::NewBlueprint ); + OutRequiredButtons.Add(EPropertyButton::NewBlueprint); } if( !(NodeProperty->PropertyFlags & CPF_NoClear) ) @@ -750,18 +763,7 @@ namespace PropertyEditorHelpers OutRequiredButtons.Add( EPropertyButton::Clear ); } } - else if (NodeProperty->IsA() ) - { - OutRequiredButtons.Add( EPropertyButton::Use ); - - OutRequiredButtons.Add( EPropertyButton::Browse ); - - if( !(NodeProperty->PropertyFlags & CPF_NoClear) ) - { - OutRequiredButtons.Add( EPropertyButton::Clear ); - } - } - + if( OuterArrayProp ) { if( PropertyNode->HasNodeFlags(EPropertyNodeFlags::SingleSelectOnly) && !(OuterArrayProp->PropertyFlags & CPF_EditFixedSize) ) @@ -795,6 +797,11 @@ namespace PropertyEditorHelpers PropertyEditorHelpers::MakeRequiredPropertyButtons( PropertyEditor, OutButtons, ButtonsToIgnore, bUsingAssetPicker ); } + static bool IsPropertyButtonEnabled(TWeakPtr PropertyNode) + { + return PropertyNode.IsValid() ? !PropertyNode.Pin()->IsEditConst() : false; + } + TSharedRef MakePropertyReorderHandle(const TSharedRef& PropertyNode, TSharedPtr InParentRow) { TSharedRef Handle = SNew(SArrayRowHandle) @@ -893,12 +900,6 @@ namespace PropertyEditorHelpers return SelectionPathName; } - - static bool IsPropertyButtonEnabled( TWeakPtr PropertyNode ) - { - return PropertyNode.IsValid() ? !PropertyNode.Pin()->IsEditConst() : false; - } - /** * A helper method that checks to see if the editor's current selection is * compatible with the specified property. diff --git a/Engine/Source/Editor/PropertyEditor/Private/PropertyEditorHelpers.h b/Engine/Source/Editor/PropertyEditor/Private/PropertyEditorHelpers.h index 4562b34fbb96..04874345c259 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/PropertyEditorHelpers.h +++ b/Engine/Source/Editor/PropertyEditor/Private/PropertyEditorHelpers.h @@ -116,8 +116,6 @@ private: namespace PropertyEditorHelpers { - - static bool IsPropertyButtonEnabled(TWeakPtr PropertyNode); /** * Returns whether or not a property is a built in struct property like a vector or color * @@ -148,6 +146,7 @@ namespace PropertyEditorHelpers * @param InPropertyNode The property node containing the property to check */ bool IsStaticArray( const FPropertyNode& InPropertyNode ); + /** * Returns whether or not a property a static array * @@ -155,6 +154,16 @@ namespace PropertyEditorHelpers */ bool IsDynamicArray( const FPropertyNode& InPropertyNode ); + /** + * Returns true if this is an FSoftObjectPath and should be treated like a TSoftObjectPtr + */ + bool IsSoftObjectPath(const UProperty* Property); + + /** + * Returns true if this is an FSoftClassPath and should be treated like a TSoftClassPtr + */ + bool IsSoftClassPath(const UProperty* Property); + /** * Gets the array parent of a property if it is in a dynamic or static array */ diff --git a/Engine/Source/Editor/PropertyEditor/Private/PropertyEditorToolkit.cpp b/Engine/Source/Editor/PropertyEditor/Private/PropertyEditorToolkit.cpp index 145369629d46..d3d04f63ad1b 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/PropertyEditorToolkit.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/PropertyEditorToolkit.cpp @@ -461,6 +461,13 @@ void FPropertyEditorToolkit::GridSelectionChanged() { TArray< TWeakObjectPtr< UObject > > SelectedObjects; PropertyTable->GetSelectedTableObjects( SelectedObjects ); + + if (SelectedObjects.Num() == 0) + { + // If none are selected, show all of them to match the initial open behavior + SelectedObjects = PropertyTable->GetSelectedObjects(); + } + PropertyTree->SetObjectArray( SelectedObjects ); const TSet< TSharedRef< IPropertyTableRow > > SelectedRows = PropertyTable->GetSelectedRows(); diff --git a/Engine/Source/Editor/PropertyEditor/Private/PropertyHandleImpl.cpp b/Engine/Source/Editor/PropertyEditor/Private/PropertyHandleImpl.cpp index 39ad77394c3d..c68043b264cf 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/PropertyHandleImpl.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/PropertyHandleImpl.cpp @@ -24,6 +24,7 @@ #include "UObject/EnumProperty.h" #include "IDetailPropertyRow.h" #include "ObjectEditorUtils.h" +#include "PropertyEditorHelpers.h" #define LOCTEXT_NAMESPACE "PropertyHandleImplementation" @@ -3617,7 +3618,8 @@ bool FPropertyHandleObject::Supports( TSharedRef PropertyNode ) return false; } - return Property->IsA( UObjectPropertyBase::StaticClass() ) || Property->IsA(UInterfaceProperty::StaticClass()); + return Property->IsA(UObjectPropertyBase::StaticClass()) || Property->IsA(UInterfaceProperty::StaticClass()) + || PropertyEditorHelpers::IsSoftClassPath(Property) || PropertyEditorHelpers::IsSoftObjectPath(Property); } FPropertyAccess::Result FPropertyHandleObject::GetValue( UObject*& OutValue ) const @@ -3644,6 +3646,14 @@ FPropertyAccess::Result FPropertyHandleObject::GetValue( const UObject*& OutValu const FScriptInterface& ScriptInterface = InterfaceProp->GetPropertyValue(PropValue); OutValue = ScriptInterface.GetObject(); } + else + { + // This is a struct path, get the path string and search for the object + FString ObjectPathString; + Res = Implementation->GetValueAsString(ObjectPathString); + FSoftObjectPath ObjectPath(ObjectPathString); + OutValue = ObjectPath.ResolveObject(); + } } return Res; @@ -3738,6 +3748,15 @@ FPropertyAccess::Result FPropertyHandleObject::SetValueFromFormattedString(const { InterfaceThatMustBeImplemented = InterfaceProperty->InterfaceClass; } + else if (PropertyEditorHelpers::IsSoftClassPath(NodeProperty)) + { + RequiredClass = NodeProperty->GetOwnerProperty()->GetClassMetaData(TEXT("MetaClass")); + InterfaceThatMustBeImplemented = NodeProperty->GetOwnerProperty()->GetClassMetaData(TEXT("MustImplement")); + } + else if (PropertyEditorHelpers::IsSoftObjectPath(NodeProperty)) + { + // No metaclass, allowedclasses is the only filter + } else { return FPropertyAccess::Fail; @@ -3862,13 +3881,27 @@ FPropertyAccess::Result FPropertyHandleObject::SetObjectValueFromSelection() UInterfaceProperty* IntProp = Cast(NodeProperty); UClassProperty* ClassProp = Cast(NodeProperty); USoftClassProperty* SoftClassProperty = Cast(NodeProperty); - UClass* const InterfaceThatMustBeImplemented = ObjProp ? ObjProp->GetOwnerProperty()->GetClassMetaData(TEXT("MustImplement")) : nullptr; + UClass* const InterfaceThatMustBeImplemented = NodeProperty ? NodeProperty->GetOwnerProperty()->GetClassMetaData(TEXT("MustImplement")) : nullptr; - if (ClassProp || SoftClassProperty) + if (ClassProp || SoftClassProperty || PropertyEditorHelpers::IsSoftClassPath(NodeProperty)) { FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast(); - const UClass* const SelectedClass = GEditor->GetFirstSelectedClass(ClassProp ? ClassProp->MetaClass : SoftClassProperty->MetaClass); + UClass* RequiredClass = nullptr; + if (ClassProp) + { + RequiredClass = ClassProp->MetaClass; + } + else if (SoftClassProperty) + { + RequiredClass = SoftClassProperty->MetaClass; + } + else + { + RequiredClass = NodeProperty->GetOwnerProperty()->GetClassMetaData(TEXT("MetaClass")); + } + + const UClass* const SelectedClass = GEditor->GetFirstSelectedClass(RequiredClass); if (SelectedClass) { if (!InterfaceThatMustBeImplemented || SelectedClass->ImplementsInterface(InterfaceThatMustBeImplemented)) diff --git a/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.cpp b/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.cpp index 7f5a0fc3ba60..ab110c30d449 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.cpp @@ -418,6 +418,13 @@ EPropertyDataValidationResult FPropertyNode::EnsureDataIsValid() if (Property.IsValid()) { UProperty* MyProperty = Property.Get(); + UStruct* OwnerStruct = MyProperty->GetOwnerStruct(); + + if (!OwnerStruct || OwnerStruct->Children == nullptr) + { + //verify that the property is not part of an invalid trash class, treat it as an invalid object if it is which will cause a refresh + return EPropertyDataValidationResult::ObjectInvalid; + } //verify that the number of container children is correct UArrayProperty* ArrayProperty = Cast(MyProperty); @@ -725,6 +732,19 @@ TSharedPtr FPropertyNode::FindChildPropertyNode( const FName InPr return nullptr; } +/** + * Returns whether this window's property is read only or has the CPF_EditConst flag. + */ +bool FPropertyNode::IsPropertyConst() const +{ + bool bIsPropertyConst = (HasNodeFlags(EPropertyNodeFlags::IsReadOnly) != 0); + if (!bIsPropertyConst && Property != nullptr) + { + bIsPropertyConst = (Property->PropertyFlags & CPF_EditConst) ? true : false; + } + + return bIsPropertyConst; +} /** @return whether this window's property is constant (can't be edited by the user) */ bool FPropertyNode::IsEditConst() const @@ -734,23 +754,19 @@ bool FPropertyNode::IsEditConst() const // Ask the objects whether this property can be changed const FObjectPropertyNode* ObjectPropertyNode = FindObjectItemParent(); - bIsEditConst = (HasNodeFlags(EPropertyNodeFlags::IsReadOnly) != 0); + bIsEditConst = IsPropertyConst(); if(!bIsEditConst && Property != nullptr && ObjectPropertyNode) { - bIsEditConst = (Property->PropertyFlags & CPF_EditConst) ? true : false; - if(!bIsEditConst) + // travel up the chain to see if this property's owner struct is editconst - if it is, so is this property + FPropertyNode* NextParent = ParentNode; + while(NextParent != nullptr && Cast(NextParent->GetProperty()) != NULL) { - // travel up the chain to see if this property's owner struct is editconst - if it is, so is this property - FPropertyNode* NextParent = ParentNode; - while(NextParent != nullptr && Cast(NextParent->GetProperty()) != NULL) + if(NextParent->IsEditConst()) { - if(NextParent->IsEditConst()) - { - bIsEditConst = true; - break; - } - NextParent = NextParent->ParentNode; + bIsEditConst = true; + break; } + NextParent = NextParent->ParentNode; } if(!bIsEditConst) diff --git a/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.h b/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.h index 16cbef3eeedb..84215b07593c 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.h +++ b/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.h @@ -401,6 +401,11 @@ public: */ bool GetChildNode(const int32 ChildArrayIndex, TSharedPtr& OutChildNode) const; + /** + * Returns whether this window's property is read only or has the CPF_EditConst flag. + */ + bool IsPropertyConst() const; + /** @return whether this window's property is constant (can't be edited by the user) */ bool IsEditConst() const; diff --git a/Engine/Source/Editor/PropertyEditor/Private/PropertyRowGenerator.cpp b/Engine/Source/Editor/PropertyEditor/Private/PropertyRowGenerator.cpp index 1aef926e0731..ea0039ef1b54 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/PropertyRowGenerator.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/PropertyRowGenerator.cpp @@ -12,13 +12,16 @@ #include "DetailLayoutHelpers.h" #include "PropertyHandleImpl.h" #include "IPropertyGenerationUtilities.h" +#include "EditConditionParser.h" class FPropertyRowGeneratorUtilities : public IPropertyUtilities { public: FPropertyRowGeneratorUtilities(FPropertyRowGenerator& InGenerator) : Generator(&InGenerator) - {} + , EditConditionParser(new FEditConditionParser) + { + } void ResetGenerator() { @@ -87,8 +90,15 @@ public: { return Generator != nullptr && Generator->HasClassDefaultObject(); } + + virtual TSharedPtr GetEditConditionParser() const override + { + return EditConditionParser; + } + private: FPropertyRowGenerator* Generator; + TSharedPtr EditConditionParser; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/SDetailSingleItemRow.cpp b/Engine/Source/Editor/PropertyEditor/Private/SDetailSingleItemRow.cpp index 3bc6e0b39fdb..e9570ab32dbc 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/SDetailSingleItemRow.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/SDetailSingleItemRow.cpp @@ -221,6 +221,12 @@ FReply SDetailSingleItemRow::OnArrayDrop(const FDragDropEvent& DragDropEvent) return FReply::Handled(); } +FReply SDetailSingleItemRow::OnArrayHeaderDrop(const FDragDropEvent& DragDropEvent) +{ + OnArrayDragLeave(DragDropEvent); + return FReply::Handled(); +} + TSharedPtr SDetailSingleItemRow::GetCopyPastePropertyNode() const { TSharedPtr PropertyNode = Customization->GetPropertyNode(); @@ -361,7 +367,15 @@ void SDetailSingleItemRow::Construct( const FArguments& InArgs, FDetailLayoutCus ArrayDropDelegate = FOnTableRowDrop::CreateSP(this, &SDetailSingleItemRow::OnArrayDrop); SwappablePropertyNode = PropertyNode; } - + else if (PropertyNode.IsValid() + && Cast(PropertyNode->GetProperty()) != nullptr // Is an array + && Cast(Cast(PropertyNode->GetProperty())->Inner) != nullptr) // Is an object array + { + ArrayDragDelegate = FOnTableRowDragEnter::CreateSP(this, &SDetailSingleItemRow::OnArrayDragEnter); + ArrayDragLeaveDelegate = FOnTableRowDragLeave::CreateSP(this, &SDetailSingleItemRow::OnArrayDragLeave); + ArrayDropDelegate = FOnTableRowDrop::CreateSP(this, &SDetailSingleItemRow::OnArrayHeaderDrop); + } + InternalLeftColumnRowBox->AddSlot() .Padding(0.0f, 0.0f) .HAlign(HAlign_Left) @@ -687,6 +701,10 @@ const FSlateBrush* SDetailSingleItemRow::GetBorderImage() const { return FEditorStyle::GetBrush("DetailsView.CategoryMiddle_Highlighted"); } + else if (bIsDragDropObject) + { + return FEditorStyle::GetBrush("DetailsView.CategoryMiddle_Active"); + } else if (IsHovered() && !bIsHoveredDragTarget) { return FEditorStyle::GetBrush("DetailsView.CategoryMiddle_Hovered"); @@ -788,6 +806,11 @@ bool SDetailSingleItemRow::IsHighlighted() const return OwnerTreeNode.Pin()->IsHighlighted(); } +void SDetailSingleItemRow::SetIsDragDrop(bool bInIsDragDrop) +{ + bIsDragDropObject = bInIsDragDrop; +} + void SArrayRowHandle::Construct(const FArguments& InArgs) { ParentRow = InArgs._ParentRow; @@ -802,12 +825,10 @@ FReply SArrayRowHandle::OnDragDetected(const FGeometry& MyGeometry, const FPoint { if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) { - { - TSharedPtr DragDropOp = CreateDragDropOperation(ParentRow.Pin()); - if (DragDropOp.IsValid()) - { - return FReply::Handled().BeginDragDrop(DragDropOp.ToSharedRef()); - } + TSharedPtr DragDropOp = CreateDragDropOperation(ParentRow.Pin()); + if (DragDropOp.IsValid()) + { + return FReply::Handled().BeginDragDrop(DragDropOp.ToSharedRef()); } } @@ -821,3 +842,46 @@ TSharedPtr SArrayRowHandle::CreateDragDropOperation(TShared return Operation; } + +FArrayRowDragDropOp::FArrayRowDragDropOp(TSharedPtr InRow) +{ + Row = InRow; + + TSharedPtr RowPtr = nullptr; + if (Row.IsValid()) + { + RowPtr = Row.Pin(); + // mark row as being used for drag and drop + RowPtr->SetIsDragDrop(true); + } + + DecoratorWidget = SNew(SBorder) + .Padding(8.f) + .BorderImage(FEditorStyle::GetBrush("Graph.ConnectorFeedback.Border")) + .Content() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(NSLOCTEXT("ArrayDragDrop", "PlaceRowHere", "Place Row Here")) + ] + ]; + + Construct(); +} + +void FArrayRowDragDropOp::OnDrop(bool bDropWasHandled, const FPointerEvent& MouseEvent) +{ + FDecoratedDragDropOp::OnDrop(bDropWasHandled, MouseEvent); + + TSharedPtr RowPtr = nullptr; + if (Row.IsValid()) + { + RowPtr = Row.Pin(); + // reset value + RowPtr->SetIsDragDrop(false); + } +} \ No newline at end of file diff --git a/Engine/Source/Editor/PropertyEditor/Private/SDetailSingleItemRow.h b/Engine/Source/Editor/PropertyEditor/Private/SDetailSingleItemRow.h index 83e0e1a2a852..469e184a2297 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/SDetailSingleItemRow.h +++ b/Engine/Source/Editor/PropertyEditor/Private/SDetailSingleItemRow.h @@ -80,6 +80,8 @@ public: */ void Construct( const FArguments& InArgs, FDetailLayoutCustomization* InCustomization, bool bHasMultipleColumns, TSharedRef InOwnerTreeNode, const TSharedRef& InOwnerTableView ); + void SetIsDragDrop(bool bInIsDragDrop); + protected: virtual bool OnContextMenuOpening( FMenuBuilder& MenuBuilder ) override; private: @@ -100,6 +102,7 @@ private: void OnArrayDragEnter(const FDragDropEvent& DragDropEvent); void OnArrayDragLeave(const FDragDropEvent& DragDropEvent); FReply OnArrayDrop(const FDragDropEvent& DragDropEvent); + FReply OnArrayHeaderDrop(const FDragDropEvent& DragDropEvent); TSharedPtr GetCopyPastePropertyNode() const; private: @@ -109,6 +112,7 @@ private: FDetailColumnSizeData ColumnSizeData; bool bAllowFavoriteSystem; bool bIsHoveredDragTarget; + bool bIsDragDropObject; TSharedPtr SwappablePropertyNode; }; @@ -117,28 +121,12 @@ class FArrayRowDragDropOp : public FDecoratedDragDropOp public: DRAG_DROP_OPERATOR_TYPE(FArrayRowDragDropOp, FDecoratedDragDropOp) - FArrayRowDragDropOp(TSharedPtr InRow) - { - Row = InRow; - DecoratorWidget = SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Graph.ConnectorFeedback.Border")) - .Content() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(NSLOCTEXT("ArrayDragDrop", "PlaceRowHere", "Place Row Here")) - ] - ]; - - Construct(); - }; + FArrayRowDragDropOp(TSharedPtr InRow); TSharedPtr DecoratorWidget; + virtual void OnDrop(bool bDropWasHandled, const FPointerEvent& MouseEvent) override; + virtual TSharedPtr GetDefaultDecorator() const override { return DecoratorWidget; diff --git a/Engine/Source/Editor/PropertyEditor/Private/SDetailsViewBase.cpp b/Engine/Source/Editor/PropertyEditor/Private/SDetailsViewBase.cpp index 544726c64b82..707c4c877894 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/SDetailsViewBase.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/SDetailsViewBase.cpp @@ -23,6 +23,7 @@ #include "Widgets/Input/SSearchBox.h" #include "Classes/EditorStyleSettings.h" #include "DetailLayoutHelpers.h" +#include "EditConditionParser.h" SDetailsViewBase::~SDetailsViewBase() { @@ -145,7 +146,12 @@ TArray< FPropertyPath > SDetailsViewBase::GetPropertiesInOrderDisplayed() const // @return populates OutNodes with the leaf node corresponding to property as the first entry in the list (e.g. [leaf, parent, grandparent]): static void FindTreeNodeFromPropertyRecursive( const TArray< TSharedRef >& Nodes, const FPropertyPath& Property, TArray< TSharedPtr< FDetailTreeNode > >& OutNodes ) { - for (auto& TreeNode : Nodes) + if (Property == FPropertyPath()) + { + return; + } + + for (const TSharedRef& TreeNode : Nodes) { if (TreeNode->IsLeaf()) { @@ -171,7 +177,7 @@ static void FindTreeNodeFromPropertyRecursive( const TArray< TSharedRef PrevHighlightedNodePtr = CurrentlyHighlightedNode.Pin(); if (PrevHighlightedNodePtr.IsValid()) { PrevHighlightedNodePtr->SetIsHighlighted(false); @@ -477,6 +483,16 @@ TSharedPtr SDetailsViewBase::GetThumbnailPool() const return ThumbnailPool; } +TSharedPtr SDetailsViewBase::GetEditConditionParser() const +{ + if (!EditConditionParser.IsValid()) + { + EditConditionParser = MakeShareable(new FEditConditionParser); + } + + return EditConditionParser; +} + void SDetailsViewBase::NotifyFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent) { OnFinishedChangingPropertiesDelegate.Broadcast(PropertyChangedEvent); @@ -1030,7 +1046,7 @@ void SDetailsViewBase::UpdateFilteredDetails() FDetailNodeList InitialRootNodeList; - NumVisbleTopLevelObjectNodes = 0; + NumVisibleTopLevelObjectNodes = 0; FRootPropertyNodeList& RootPropertyNodes = GetRootNodes(); if( GetDefault()->bShowAllAdvancedDetails ) @@ -1065,7 +1081,7 @@ void SDetailsViewBase::UpdateFilteredDetails() if (LayoutRoots.Num() > 0) { // A top level object nodes has a non-filtered away root so add one to the total number we have - ++NumVisbleTopLevelObjectNodes; + ++NumVisibleTopLevelObjectNodes; InitialRootNodeList.Append(LayoutRoots); } diff --git a/Engine/Source/Editor/PropertyEditor/Private/SDetailsViewBase.h b/Engine/Source/Editor/PropertyEditor/Private/SDetailsViewBase.h index 0416baf0af92..77e9d016fc5a 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/SDetailsViewBase.h +++ b/Engine/Source/Editor/PropertyEditor/Private/SDetailsViewBase.h @@ -86,7 +86,7 @@ public: , bIsLocked(false) , bHasOpenColorPicker(false) , bDisableCustomDetailLayouts( false ) - , NumVisbleTopLevelObjectNodes(0) + , NumVisibleTopLevelObjectNodes(0) { } @@ -107,10 +107,9 @@ public: virtual int32 GetNumVisibleTopLevelObjects() const override { - return NumVisbleTopLevelObjectNodes; + return NumVisibleTopLevelObjectNodes; } - /** @return The identifier for this details view, or NAME_None is this view is anonymous */ virtual FName GetIdentifier() const override { @@ -162,6 +161,7 @@ public: virtual void NotifyFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent) override; void RefreshTree() override; TSharedPtr GetThumbnailPool() const override; + TSharedPtr GetEditConditionParser() const override; TSharedPtr GetPropertyUtilities() override; void CreateColorPickerWindow(const TSharedRef< class FPropertyEditor >& PropertyEditor, bool bUseAlpha) override; virtual void UpdateSinglePropertyMap(TSharedPtr InRootPropertyNode, FDetailLayoutData& LayoutData, bool bIsExternal) override; @@ -422,7 +422,7 @@ protected: /** True if we want to skip generation of custom layouts for displayed object */ bool bDisableCustomDetailLayouts; - int32 NumVisbleTopLevelObjectNodes; + int32 NumVisibleTopLevelObjectNodes; /** Delegate for overriding the show modified filter */ FSimpleDelegate CustomFilterDelegate; @@ -431,4 +431,6 @@ protected: bool bCustomFilterActive; FText CustomFilterLabel; + + mutable TSharedPtr EditConditionParser; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/SPropertyTreeViewImpl.cpp b/Engine/Source/Editor/PropertyEditor/Private/SPropertyTreeViewImpl.cpp index 29626f50e3a4..169f6dfffa0b 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/SPropertyTreeViewImpl.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/SPropertyTreeViewImpl.cpp @@ -11,6 +11,7 @@ #include "Presentation/PropertyEditor/PropertyEditor.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" +#include "EditConditionParser.h" #include "CategoryPropertyNode.h" #include "UserInterface/PropertyTree/PropertyTreeConstants.h" @@ -31,6 +32,7 @@ public: FPropertyUtilitiesTreeView( SPropertyTreeViewImpl& InView ) : View( InView ) { + EditConditionParser = MakeShareable(new FEditConditionParser()); } virtual class FNotifyHook* GetNotifyHook() const override @@ -95,8 +97,14 @@ public: { return false; } -private: + virtual TSharedPtr GetEditConditionParser() const + { + return EditConditionParser; + } + +private: + TSharedPtr EditConditionParser; SPropertyTreeViewImpl& View; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/SSingleProperty.cpp b/Engine/Source/Editor/PropertyEditor/Private/SSingleProperty.cpp index 94591a555e02..89418b1dde2b 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/SSingleProperty.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/SSingleProperty.cpp @@ -90,6 +90,11 @@ public: return false; } + virtual TSharedPtr GetEditConditionParser() const override + { + return nullptr; + } + private: TWeakPtr< SSingleProperty > View; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/StructurePropertyNode.h b/Engine/Source/Editor/PropertyEditor/Private/StructurePropertyNode.h index 913555143685..a69e1fec12d1 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/StructurePropertyNode.h +++ b/Engine/Source/Editor/PropertyEditor/Private/StructurePropertyNode.h @@ -56,6 +56,13 @@ public: return false; } + UStruct* OwnerStruct = InItemProperty->GetOwnerStruct(); + if (!OwnerStruct || OwnerStruct->Children == nullptr) + { + // Verify that the property is not part of an invalid trash class + return false; + } + uint8* ReadAddress = StructData->GetStructMemory(); check(ReadAddress); OutAddresses.Add(NULL, InPropertyNode.GetValueBaseAddress(ReadAddress), true); diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyDetails/PropertyDetailsUtilities.cpp b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyDetails/PropertyDetailsUtilities.cpp index 468e1de143b1..440b78a434d4 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyDetails/PropertyDetailsUtilities.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyDetails/PropertyDetailsUtilities.cpp @@ -73,4 +73,9 @@ const TArray>& FPropertyDetailsUtilities::GetSelectedObj bool FPropertyDetailsUtilities::HasClassDefaultObject() const { return DetailsView.HasClassDefaultObject(); -} \ No newline at end of file +} + +TSharedPtr FPropertyDetailsUtilities::GetEditConditionParser() const +{ + return DetailsView.GetEditConditionParser(); +} diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyDetails/PropertyDetailsUtilities.h b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyDetails/PropertyDetailsUtilities.h index 686765f9817a..6cdd4f0db3fc 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyDetails/PropertyDetailsUtilities.h +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyDetails/PropertyDetailsUtilities.h @@ -26,6 +26,8 @@ public: virtual bool DontUpdateValueWhileEditing() const override; const TArray>& GetSelectedObjects() const override; virtual bool HasClassDefaultObject() const override; + virtual TSharedPtr GetEditConditionParser() const override; + private: IDetailsViewPrivate& DetailsView; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorArray.h b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorArray.h index a578a1e6f375..6af487f52c68 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorArray.h +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorArray.h @@ -11,6 +11,8 @@ #include "PropertyEditorHelpers.h" #include "UserInterface/PropertyEditor/PropertyEditorConstants.h" #include "Widgets/Text/STextBlock.h" +#include "DragAndDrop/AssetDragDropOp.h" +#include "SDropTarget.h" #define LOCTEXT_NAMESPACE "PropertyEditor" @@ -43,9 +45,17 @@ public: ChildSlot .Padding( 0.0f, 0.0f, 2.0f, 0.0f ) [ - SNew( STextBlock ) - .Text( TextAttr ) - .Font( InArgs._Font ) + SNew(SDropTarget) + .OnDrop(this, &SPropertyEditorArray::OnDragDropTarget) + .OnAllowDrop(this, &SPropertyEditorArray::WillAddValidElements) + .OnIsRecognized(this, &SPropertyEditorArray::IsValidAssetDropOp) + .Content() + [ + SNew(STextBlock) + .Margin(FMargin(4.0f, 2.0f)) + .Text(TextAttr) + .Font(InArgs._Font) + ] ]; SetEnabled( TAttribute( this, &SPropertyEditorArray::CanEdit ) ); @@ -82,6 +92,85 @@ private: { return PropertyEditor.IsValid() ? !PropertyEditor->IsEditConst() : true; } + + FReply OnDragDropTarget(TSharedPtr InOperation) + { + UObjectProperty* ObjectProperty = nullptr; + if (const UArrayProperty* ArrayProperty = Cast(PropertyEditor->GetProperty())) + { + ObjectProperty = Cast(ArrayProperty->Inner); + } + + // Only try to add entries if we are dropping on an asset array + if (ObjectProperty && InOperation->IsOfType()) + { + TSharedPtr DragDropOp = StaticCastSharedPtr(InOperation); + if (DragDropOp.IsValid()) + { + for (FAssetData AssetData : DragDropOp->GetAssets()) + { + // if the type matches + if (ObjectProperty->PropertyClass == AssetData.GetClass()) + { + PropertyEditor->AddGivenItem(AssetData.ObjectPath.ToString()); + } + } + // Let this bubble up to the rest of the row + return FReply::Unhandled(); + + } + } + return FReply::Unhandled(); + } + + bool IsValidAssetDropOp(TSharedPtr InOperation) + { + UObjectProperty* ObjectProperty = nullptr; + if (const UArrayProperty* ArrayProperty = Cast(PropertyEditor->GetProperty())) + { + ObjectProperty = Cast(ArrayProperty->Inner); + } + + // Only try to add entries if we are dropping on an asset array + if (ObjectProperty && InOperation->IsOfType()) + { + return true; + } + return false; + } + + bool WillAddValidElements(TSharedPtr InOperation) + { + UObjectProperty* ObjectProperty = nullptr; + + if (const UArrayProperty* ArrayProperty = Cast(PropertyEditor->GetProperty())) + { + ObjectProperty = Cast(ArrayProperty->Inner); + } + + // Only try to add entries if we are dropping on an asset array + if (ObjectProperty && InOperation->IsOfType()) + { + bool bHasOnlyValidElements = true; + TSharedPtr DragDropOp = StaticCastSharedPtr(InOperation); + if (DragDropOp.IsValid()) + { + for (FAssetData AssetData : DragDropOp->GetAssets()) + { + // if the type matches + if (ObjectProperty->PropertyClass != AssetData.GetClass()) + { + bHasOnlyValidElements = false; + break; + } + } + } + return bHasOnlyValidElements; + } + + return false; + } + private: TSharedPtr PropertyEditor; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorAsset.cpp b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorAsset.cpp index df06aa0f8a48..2e80168a3cf0 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorAsset.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorAsset.cpp @@ -680,7 +680,33 @@ void SPropertyEditorAsset::OnMenuOpenChanged(bool bOpen) bool SPropertyEditorAsset::IsFilteredActor( const AActor* const Actor ) const { - return Actor->IsA( ObjectClass ) && !Actor->IsChildActor(); + bool IsAllowed = Actor->IsA(ObjectClass) && !Actor->IsChildActor(); + + if (IsAllowed) + { + bool ClassFilterAllowed = false; + for (const UClass* Class : AllowedClassFilters) + { + if (Actor->GetClass()->IsChildOf(Class)) + { + ClassFilterAllowed = true; + break; + } + } + + for (const UClass* Class : DisallowedClassFilters) + { + if (Actor->GetClass()->IsChildOf(Class)) + { + ClassFilterAllowed = false; + break; + } + } + + IsAllowed = ClassFilterAllowed; + } + + return IsAllowed; } void SPropertyEditorAsset::CloseComboButton() diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorClass.cpp b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorClass.cpp index 17d17d637695..3af35fdf87c8 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorClass.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorClass.cpp @@ -11,7 +11,6 @@ #include "ClassViewerModule.h" #include "ClassViewerFilter.h" - #define LOCTEXT_NAMESPACE "PropertyEditor" class FPropertyEditorClassFilter : public IClassViewerFilter @@ -26,7 +25,7 @@ public: /** Whether or not abstract classes are allowed. */ bool bAllowAbstract; - bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs ) override + virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs ) override { bool bMatchesFlags = !InClass->HasAnyClassFlags(CLASS_Hidden|CLASS_HideDropDown|CLASS_Deprecated) && (bAllowAbstract || !InClass->HasAnyClassFlags(CLASS_Abstract)); @@ -84,6 +83,8 @@ bool SPropertyEditorClass::Supports(const TSharedRef< class FPropertyEditor >& I void SPropertyEditorClass::Construct(const FArguments& InArgs, const TSharedPtr< class FPropertyEditor >& InPropertyEditor) { PropertyEditor = InPropertyEditor; + + CreateClassFilter(); if (PropertyEditor.IsValid()) { @@ -129,8 +130,6 @@ void SPropertyEditorClass::Construct(const FArguments& InArgs, const TSharedPtr< SelectedClass = InArgs._SelectedClass; OnSetClass = InArgs._OnSetClass; - - } SAssignNew(ComboButton, SComboButton) @@ -199,28 +198,36 @@ FText SPropertyEditorClass::GetDisplayValueAsString() const } -TSharedRef SPropertyEditorClass::GenerateClassPicker() +void SPropertyEditorClass::CreateClassFilter() { - FClassViewerInitializationOptions Options; - Options.bShowUnloadedBlueprints = true; - Options.bShowNoneOption = bAllowNone; + ClassViewerOptions.bShowBackgroundBorder = false; + ClassViewerOptions.bShowUnloadedBlueprints = true; + ClassViewerOptions.bShowNoneOption = bAllowNone; - if(PropertyEditor.IsValid()) + if (PropertyEditor.IsValid()) { - Options.PropertyHandle = PropertyEditor->GetPropertyHandle(); + ClassViewerOptions.PropertyHandle = PropertyEditor->GetPropertyHandle(); } - TSharedPtr ClassFilter = MakeShareable(new FPropertyEditorClassFilter); - Options.ClassFilter = ClassFilter; - ClassFilter->ClassPropertyMetaClass = MetaClass; - ClassFilter->InterfaceThatMustBeImplemented = RequiredInterface; - ClassFilter->bAllowAbstract = bAllowAbstract; - Options.bIsBlueprintBaseOnly = bIsBlueprintBaseOnly; - Options.bIsPlaceableOnly = bAllowOnlyPlaceable; - Options.NameTypeToDisplay = (bShowDisplayNames ? EClassViewerNameTypeToDisplay::DisplayName : EClassViewerNameTypeToDisplay::ClassName); - Options.DisplayMode = bShowTree ? EClassViewerDisplayMode::TreeView : EClassViewerDisplayMode::ListView; - Options.bAllowViewOptions = bShowViewOptions; + ClassViewerOptions.bIsBlueprintBaseOnly = bIsBlueprintBaseOnly; + ClassViewerOptions.bIsPlaceableOnly = bAllowOnlyPlaceable; + ClassViewerOptions.NameTypeToDisplay = (bShowDisplayNames ? EClassViewerNameTypeToDisplay::DisplayName : EClassViewerNameTypeToDisplay::ClassName); + ClassViewerOptions.DisplayMode = bShowTree ? EClassViewerDisplayMode::TreeView : EClassViewerDisplayMode::ListView; + ClassViewerOptions.bAllowViewOptions = bShowViewOptions; + TSharedPtr PropEdClassFilter = MakeShareable(new FPropertyEditorClassFilter); + ClassViewerOptions.ClassFilter = PropEdClassFilter; + + PropEdClassFilter->ClassPropertyMetaClass = MetaClass; + PropEdClassFilter->InterfaceThatMustBeImplemented = RequiredInterface; + PropEdClassFilter->bAllowAbstract = bAllowAbstract; + + ClassFilter = FModuleManager::LoadModuleChecked("ClassViewer").CreateClassFilter(ClassViewerOptions); + ClassFilterFuncs = FModuleManager::LoadModuleChecked("ClassViewer").CreateFilterFuncs(); +} + +TSharedRef SPropertyEditorClass::GenerateClassPicker() +{ FOnClassPicked OnPicked(FOnClassPicked::CreateRaw(this, &SPropertyEditorClass::OnClassPicked)); return SNew(SBox) @@ -231,7 +238,7 @@ TSharedRef SPropertyEditorClass::GenerateClassPicker() .AutoHeight() .MaxHeight(500) [ - FModuleManager::LoadModuleChecked("ClassViewer").CreateClassViewer(Options, OnPicked) + FModuleManager::LoadModuleChecked("ClassViewer").CreateClassViewer(ClassViewerOptions, OnPicked) ] ]; } @@ -272,51 +279,60 @@ void SPropertyEditorClass::SendToObjects(const FString& NewValue) } } +static UObject* LoadDragDropObject(TSharedPtr UnloadedClassOp) +{ + FString AssetPath; + + // Find the class/blueprint path + if (UnloadedClassOp->HasAssets()) + { + AssetPath = UnloadedClassOp->GetAssets()[0].ObjectPath.ToString(); + } + else if (UnloadedClassOp->HasAssetPaths()) + { + AssetPath = UnloadedClassOp->GetAssetPaths()[0]; + } + + // Check to see if the asset can be found, otherwise load it. + UObject* Object = FindObject(nullptr, *AssetPath); + if (Object == nullptr) + { + // Load the package. + GWarn->BeginSlowTask(LOCTEXT("OnDrop_LoadPackage", "Fully Loading Package For Drop"), true, false); + + Object = LoadObject(nullptr, *AssetPath); + + GWarn->EndSlowTask(); + } + + return Object; +} + void SPropertyEditorClass::OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) { TSharedPtr UnloadedClassOp = DragDropEvent.GetOperationAs(); if (UnloadedClassOp.IsValid()) { - bool bAllAssetWereLoaded = true; + UObject* Object = LoadDragDropObject(UnloadedClassOp); - FString AssetPath; - FString PathName; - - // Find the class/blueprint path - if (UnloadedClassOp->HasAssets()) - { - AssetPath = UnloadedClassOp->GetAssets()[0].ObjectPath.ToString(); - } - else if (UnloadedClassOp->HasAssetPaths()) - { - AssetPath = UnloadedClassOp->GetAssetPaths()[0]; - } - - // Check to see if the asset can be found, otherwise load it. - UObject* Object = FindObject(nullptr, *AssetPath); - if (Object == nullptr) - { - // Load the package. - GWarn->BeginSlowTask(LOCTEXT("OnDrop_LoadPackage", "Fully Loading Package For Drop"), true, false); - - Object = LoadObject(nullptr, *AssetPath); - - GWarn->EndSlowTask(); - } + bool bOK = false; if (UClass* Class = Cast(Object)) { - // This was pointing to a class directly - UnloadedClassOp->SetToolTip(FText::GetEmpty(), FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK"))); + bOK = ClassFilter->IsClassAllowed(ClassViewerOptions, Class, ClassFilterFuncs.ToSharedRef()); } else if (UBlueprint* Blueprint = Cast(Object)) { if (Blueprint->GeneratedClass) { - // This was pointing to a blueprint, get generated class - UnloadedClassOp->SetToolTip(FText::GetEmpty(), FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK"))); + bOK = ClassFilter->IsClassAllowed(ClassViewerOptions, Blueprint->GeneratedClass, ClassFilterFuncs.ToSharedRef()); } } + + if (bOK) + { + UnloadedClassOp->SetToolTip(FText::GetEmpty(), FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK"))); + } else { UnloadedClassOp->SetToolTip(FText::GetEmpty(), FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error"))); @@ -350,43 +366,25 @@ FReply SPropertyEditorClass::OnDrop(const FGeometry& MyGeometry, const FDragDrop TSharedPtr UnloadedClassOp = DragDropEvent.GetOperationAs(); if (UnloadedClassOp.IsValid()) { - bool bAllAssetWereLoaded = true; - - FString AssetPath; - - // Find the class/blueprint path - if (UnloadedClassOp->HasAssets()) - { - AssetPath = UnloadedClassOp->GetAssets()[0].ObjectPath.ToString(); - } - else if (UnloadedClassOp->HasAssetPaths()) - { - AssetPath = UnloadedClassOp->GetAssetPaths()[0]; - } - - // Check to see if the asset can be found, otherwise load it. - UObject* Object = FindObject(nullptr, *AssetPath); - if(Object == nullptr) - { - // Load the package. - GWarn->BeginSlowTask(LOCTEXT("OnDrop_LoadPackage", "Fully Loading Package For Drop"), true, false); - - Object = LoadObject(nullptr, *AssetPath); - - GWarn->EndSlowTask(); - } + UObject* Object = LoadDragDropObject(UnloadedClassOp); if (UClass* Class = Cast(Object)) { - // This was pointing to a class directly - SendToObjects(Class->GetPathName()); + if (ClassFilter->IsClassAllowed(ClassViewerOptions, Class, ClassFilterFuncs.ToSharedRef())) + { + // This was pointing to a class directly + SendToObjects(Class->GetPathName()); + } } else if (UBlueprint* Blueprint = Cast(Object)) { if (Blueprint->GeneratedClass) { - // This was pointing to a blueprint, get generated class - SendToObjects(Blueprint->GeneratedClass->GetPathName()); + if (ClassFilter->IsClassAllowed(ClassViewerOptions, Blueprint->GeneratedClass, ClassFilterFuncs.ToSharedRef())) + { + // This was pointing to a blueprint, get generated class + SendToObjects(Blueprint->GeneratedClass->GetPathName()); + } } } diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorClass.h b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorClass.h index 5ce4a17885ea..f4624717500f 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorClass.h +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorClass.h @@ -14,6 +14,10 @@ #include "Presentation/PropertyEditor/PropertyEditor.h" #include "UserInterface/PropertyEditor/PropertyEditorConstants.h" #include "PropertyCustomizationHelpers.h" +#include "ClassViewerModule.h" + +class IClassViewerFilter; +class FClassViewerFilterFuncs; /** * A widget used to edit Class properties (UClass type properties). @@ -69,6 +73,8 @@ private: virtual void OnDragLeave(const FDragDropEvent& DragDropEvent) override; virtual FReply OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override; + TSharedRef ConstructClassViewer(); + /** * Generates a class picker with a filter to show only classes allowed to be selected. * @@ -95,6 +101,15 @@ private: /** Used when the property deals with Classes and will display a Class Picker. */ TSharedPtr ComboButton; + /** Class filter that the class viewer is using. */ + TSharedPtr ClassFilter; + + /** Filter functions for class viewer. */ + TSharedPtr ClassFilterFuncs; + + /** Options used for creating the class viewer. */ + FClassViewerInitializationOptions ClassViewerOptions; + /** The meta class that the selected class must be a child-of */ const UClass* MetaClass; /** An interface that the selected class must implement */ @@ -118,4 +133,6 @@ private: TAttribute SelectedClass; /** Delegate used to set the currently selected class (required if PropertyEditor == null) */ FOnSetClass OnSetClass; + + void CreateClassFilter(); }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorEditInline.cpp b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorEditInline.cpp index 4cf1b6a51911..5ff07902798d 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorEditInline.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorEditInline.cpp @@ -149,7 +149,7 @@ bool SPropertyEditorEditInline::Supports( const FPropertyNode* InTreeNode, int32 return InTreeNode && InTreeNode->HasNodeFlags(EPropertyNodeFlags::EditInlineNew) && InTreeNode->FindObjectItemParent() - && !InTreeNode->IsEditConst(); + && !InTreeNode->IsPropertyConst(); } bool SPropertyEditorEditInline::Supports( const TSharedRef< class FPropertyEditor >& InPropertyEditor ) @@ -167,6 +167,7 @@ bool SPropertyEditorEditInline::IsClassAllowed( UClass* CheckClass, bool bAllowA TSharedRef SPropertyEditorEditInline::GenerateClassPicker() { FClassViewerInitializationOptions Options; + Options.bShowBackgroundBorder = false; Options.bShowUnloadedBlueprints = true; Options.NameTypeToDisplay = EClassViewerNameTypeToDisplay::DisplayName; diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorTableRow.cpp b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorTableRow.cpp index b7db89a7e017..989defcc2d1a 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorTableRow.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorTableRow.cpp @@ -194,7 +194,7 @@ const FSlateBrush* SPropertyEditorTableRow::OnGetFavoriteImage() const void SPropertyEditorTableRow::OnEditConditionCheckChanged( ECheckBoxState CheckState ) { - PropertyEditor->SetEditConditionState( CheckState == ECheckBoxState::Checked ); + PropertyEditor->ToggleEditConditionState(); } ECheckBoxState SPropertyEditorTableRow::OnGetEditConditionCheckState() const diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyMenuActorPicker.cpp b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyMenuActorPicker.cpp index c5de2e5515e5..4e7187a33131 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyMenuActorPicker.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyMenuActorPicker.cpp @@ -95,11 +95,7 @@ void SPropertyMenuActorPicker::Construct( const FArguments& InArgs ) .WidthOverride(PropertyEditorAssetConstants::SceneOutlinerWindowSize.X) .HeightOverride(PropertyEditorAssetConstants::SceneOutlinerWindowSize.Y) [ - SNew( SBorder ) - .BorderImage( FEditorStyle::GetBrush("Menu.Background") ) - [ - SceneOutlinerModule.CreateSceneOutliner(InitOptions, FOnActorPicked::CreateSP(this, &SPropertyMenuActorPicker::OnActorSelected)) - ] + SceneOutlinerModule.CreateSceneOutliner(InitOptions, FOnActorPicked::CreateSP(this, &SPropertyMenuActorPicker::OnActorSelected)) ]; MenuBuilder.AddWidget(MenuContent.ToSharedRef(), FText::GetEmpty(), true); diff --git a/Engine/Source/Editor/PropertyEditor/Public/IDetailsView.h b/Engine/Source/Editor/PropertyEditor/Public/IDetailsView.h index 00ed0b47849d..ccb64a2465a0 100644 --- a/Engine/Source/Editor/PropertyEditor/Public/IDetailsView.h +++ b/Engine/Source/Editor/PropertyEditor/Public/IDetailsView.h @@ -131,6 +131,7 @@ public: , bForceHiddenPropertyVisibility(false) , bShowKeyablePropertiesOption(true) , bShowAnimatedPropertiesOption(true) + , bShowCustomFilterOption(false) , ColumnWidth(.65f) { } @@ -266,6 +267,8 @@ public: virtual void SetExtensionHandler(TSharedPtr InExtensionandler) = 0; + virtual TSharedPtr GetExtensionHandler() = 0; + /** * @return true if property editing is enabled (based on the FIsPropertyEditingEnabled delegate) */ diff --git a/Engine/Source/Editor/PropertyEditor/Public/IPropertyUtilities.h b/Engine/Source/Editor/PropertyEditor/Public/IPropertyUtilities.h index 4da259dca59d..2593c6108a71 100644 --- a/Engine/Source/Editor/PropertyEditor/Public/IPropertyUtilities.h +++ b/Engine/Source/Editor/PropertyEditor/Public/IPropertyUtilities.h @@ -5,6 +5,7 @@ #include "AssetThumbnail.h" struct FPropertyChangedEvent; +class FEditConditionParser; /** * Settings for property editor widgets that call up to the base container for the widgets @@ -28,4 +29,5 @@ public: virtual const TArray>& GetSelectedObjects() const = 0; /** If a customization standalone widget is used, the value should be update only once, when its window is closed */ virtual bool DontUpdateValueWhileEditing() const = 0; + virtual TSharedPtr GetEditConditionParser() const = 0; }; diff --git a/Engine/Source/Editor/PropertyEditor/Public/MaterialList.h b/Engine/Source/Editor/PropertyEditor/Public/MaterialList.h new file mode 100644 index 000000000000..cc2547563836 --- /dev/null +++ b/Engine/Source/Editor/PropertyEditor/Public/MaterialList.h @@ -0,0 +1,219 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "IDetailCustomNodeBuilder.h" +#include "DetailWidgetRow.h" +#include "Materials/MaterialInterface.h" + +class FMaterialItemView; +class FMaterialListBuilder; +class IDetailChildrenBuilder; +class IDetailLayoutBuilder; +class IMaterialListBuilder; + +/** + * Delegate called when we need to get new materials for the list + */ +DECLARE_DELEGATE_OneParam(FOnGetMaterials, IMaterialListBuilder&); + +/** + * Delegate called when a user changes the material + */ +DECLARE_DELEGATE_FourParams( FOnMaterialChanged, UMaterialInterface*, UMaterialInterface*, int32, bool ); + +DECLARE_DELEGATE_RetVal_TwoParams( TSharedRef, FOnGenerateWidgetsForMaterial, UMaterialInterface*, int32 ); + +DECLARE_DELEGATE_TwoParams( FOnResetMaterialToDefaultClicked, UMaterialInterface*, int32 ); + +DECLARE_DELEGATE_RetVal(bool, FOnMaterialListDirty); + +DECLARE_DELEGATE_RetVal(bool, FOnCanCopyMaterialList); +DECLARE_DELEGATE(FOnCopyMaterialList); +DECLARE_DELEGATE(FOnPasteMaterialList); + +DECLARE_DELEGATE_RetVal_OneParam(bool, FOnCanCopyMaterialItem, int32); +DECLARE_DELEGATE_OneParam(FOnCopyMaterialItem, int32); +DECLARE_DELEGATE_OneParam(FOnPasteMaterialItem, int32); + +struct FMaterialListDelegates +{ + FMaterialListDelegates() + : OnGetMaterials() + , OnMaterialChanged() + , OnGenerateCustomNameWidgets() + , OnGenerateCustomMaterialWidgets() + , OnResetMaterialToDefaultClicked() + {} + + /** Delegate called to populate the list with materials */ + FOnGetMaterials OnGetMaterials; + /** Delegate called when a user changes the material */ + FOnMaterialChanged OnMaterialChanged; + /** Delegate called to generate custom widgets under the name of in the left column of a details panel*/ + FOnGenerateWidgetsForMaterial OnGenerateCustomNameWidgets; + /** Delegate called to generate custom widgets under each material */ + FOnGenerateWidgetsForMaterial OnGenerateCustomMaterialWidgets; + /** Delegate called when a material list item should be reset to default */ + FOnResetMaterialToDefaultClicked OnResetMaterialToDefaultClicked; + /** Delegate called when we tick the material list to know if the list is dirty*/ + FOnMaterialListDirty OnMaterialListDirty; + + /** Delegate called Copying a material list */ + FOnCopyMaterialList OnCopyMaterialList; + /** Delegate called to know if we can copy a material list */ + FOnCanCopyMaterialList OnCanCopyMaterialList; + /** Delegate called Pasting a material list */ + FOnPasteMaterialList OnPasteMaterialList; + + /** Delegate called Copying a material item */ + FOnCopyMaterialItem OnCopyMaterialItem; + /** Delegate called to know if we can copy a material item */ + FOnCanCopyMaterialItem OnCanCopyMaterialItem; + /** Delegate called Pasting a material item */ + FOnPasteMaterialItem OnPasteMaterialItem; +}; + +/** + * Builds up a list of unique materials while creating some information about the materials + */ +class IMaterialListBuilder +{ +public: + + /** Virtual destructor. */ + virtual ~IMaterialListBuilder(){}; + + /** + * Adds a new material to the list + * + * @param SlotIndex The slot (usually mesh element index) where the material is located on the component. + * @param Material The material being used. + * @param bCanBeReplced Whether or not the material can be replaced by a user. + */ + virtual void AddMaterial( uint32 SlotIndex, UMaterialInterface* Material, bool bCanBeReplaced ) = 0; +}; + + +/** + * A Material item in a material list slot + */ +struct FMaterialListItem +{ + /** Material being used */ + TWeakObjectPtr Material; + + /** Slot on a component where this material is at (mesh element) */ + int32 SlotIndex; + + /** Whether or not this material can be replaced by a new material */ + bool bCanBeReplaced; + + FMaterialListItem( UMaterialInterface* InMaterial = NULL, uint32 InSlotIndex = 0, bool bInCanBeReplaced = false ) + : Material( InMaterial ) + , SlotIndex( InSlotIndex ) + , bCanBeReplaced( bInCanBeReplaced ) + {} + + friend uint32 GetTypeHash( const FMaterialListItem& InItem ) + { + return GetTypeHash( InItem.Material ) + InItem.SlotIndex ; + } + + bool operator==( const FMaterialListItem& Other ) const + { + return Material == Other.Material && SlotIndex == Other.SlotIndex ; + } + + bool operator!=( const FMaterialListItem& Other ) const + { + return !(*this == Other); + } +}; + + +class FMaterialList + : public IDetailCustomNodeBuilder + , public TSharedFromThis +{ +public: + PROPERTYEDITOR_API FMaterialList( IDetailLayoutBuilder& InDetailLayoutBuilder, FMaterialListDelegates& MaterialListDelegates, bool bInAllowCollapse = false, bool bInShowUsedTextures = true, bool bInDisplayCompactSize = false); + + /** + * @return true if materials are being displayed. + */ + bool IsDisplayingMaterials() const { return true; } + +private: + + /** + * Called when a user expands all materials in a slot. + * + * @param SlotIndex The index of the slot being expanded. + */ + void OnDisplayMaterialsForElement( int32 SlotIndex ); + + /** + * Called when a user hides all materials in a slot. + * + * @param SlotIndex The index of the slot being hidden. + */ + void OnHideMaterialsForElement( int32 SlotIndex ); + + /** IDetailCustomNodeBuilder interface */ + virtual void SetOnRebuildChildren( FSimpleDelegate InOnRebuildChildren ) override { OnRebuildChildren = InOnRebuildChildren; } + virtual bool RequiresTick() const override { return true; } + virtual void Tick( float DeltaTime ) override; + virtual void GenerateHeaderRowContent( FDetailWidgetRow& NodeRow ) override; + virtual void GenerateChildContent( IDetailChildrenBuilder& ChildrenBuilder ) override; + virtual FName GetName() const override { return NAME_None; } + virtual bool InitiallyCollapsed() const override { return bAllowCollpase; } + + /** + * Adds a new material item to the list + * + * @param Row The row to add the item to + * @param CurrentSlot The slot id of the material + * @param Item The material item to add + * @param bDisplayLink If a link to the material should be displayed instead of the actual item (for multiple materials) + */ + void AddMaterialItem(FDetailWidgetRow& Row, int32 CurrentSlot, const FMaterialListItem& Item, bool bDisplayLink); + +private: + bool OnCanCopyMaterialList() const; + void OnCopyMaterialList(); + void OnPasteMaterialList(); + + bool OnCanCopyMaterialItem(int32 CurrentSlot) const; + void OnCopyMaterialItem(int32 CurrentSlot); + void OnPasteMaterialItem(int32 CurrentSlot); + + /** Delegates for the material list */ + FMaterialListDelegates MaterialListDelegates; + + /** Called to rebuild the children of the detail tree */ + FSimpleDelegate OnRebuildChildren; + + /** Parent detail layout this list is in */ + IDetailLayoutBuilder& DetailLayoutBuilder; + + /** Set of all unique displayed materials */ + TArray< FMaterialListItem > DisplayedMaterials; + + /** Set of all materials currently in view (may be less than DisplayedMaterials) */ + TArray< TSharedRef > ViewedMaterials; + + /** Set of all expanded slots */ + TSet ExpandedSlots; + + /** Material list builder used to generate materials */ + TSharedRef MaterialListBuilder; + + /** Allow Collapse of material header row. Right now if you allow collapse, it will initially collapse. */ + bool bAllowCollpase; + /** Whether or not to use the used textures menu for each material entry */ + bool bShowUsedTextures; + /** Whether or not to display a compact form of material entry*/ + bool bDisplayCompactSize; +}; + diff --git a/Engine/Source/Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h b/Engine/Source/Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h index ed403bf6d78c..3f4953f2453c 100644 --- a/Engine/Source/Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h +++ b/Engine/Source/Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h @@ -20,16 +20,12 @@ #include "SceneDepthPickerMode.h" #include "IDetailPropertyRow.h" - class AActor; struct FAssetData; class FAssetThumbnailPool; -class FMaterialItemView; -class FMaterialListBuilder; class FPropertyEditor; class IDetailChildrenBuilder; class IDetailLayoutBuilder; -class IMaterialListBuilder; class SPropertyEditorAsset; class SPropertyEditorClass; class UFactory; @@ -40,7 +36,6 @@ namespace SceneOutliner struct FOutlinerFilters; } - DECLARE_DELEGATE_OneParam(FOnAssetSelected, const FAssetData& /*AssetData*/); DECLARE_DELEGATE_RetVal_OneParam(bool, FOnShouldSetAsset, const FAssetData& /*AssetData*/); DECLARE_DELEGATE_RetVal_OneParam(bool, FOnShouldFilterAsset, const FAssetData& /*AssetData*/); @@ -232,7 +227,7 @@ private: * Represents a widget that can display a UProperty * With the ability to customize the look of the property */ -class SProperty +class PROPERTYEDITOR_VTABLE SProperty : public SCompoundWidget { public: @@ -418,214 +413,6 @@ private: bool bDisplayElementNum; }; - -/** - * Delegate called when we need to get new materials for the list - */ -DECLARE_DELEGATE_OneParam(FOnGetMaterials, IMaterialListBuilder&); - -/** - * Delegate called when a user changes the material - */ -DECLARE_DELEGATE_FourParams( FOnMaterialChanged, UMaterialInterface*, UMaterialInterface*, int32, bool ); - -DECLARE_DELEGATE_RetVal_TwoParams( TSharedRef, FOnGenerateWidgetsForMaterial, UMaterialInterface*, int32 ); - -DECLARE_DELEGATE_TwoParams( FOnResetMaterialToDefaultClicked, UMaterialInterface*, int32 ); - -DECLARE_DELEGATE_RetVal(bool, FOnMaterialListDirty); - -DECLARE_DELEGATE_RetVal(bool, FOnCanCopyMaterialList); -DECLARE_DELEGATE(FOnCopyMaterialList); -DECLARE_DELEGATE(FOnPasteMaterialList); - -DECLARE_DELEGATE_RetVal_OneParam(bool, FOnCanCopyMaterialItem, int32); -DECLARE_DELEGATE_OneParam(FOnCopyMaterialItem, int32); -DECLARE_DELEGATE_OneParam(FOnPasteMaterialItem, int32); - -struct FMaterialListDelegates -{ - FMaterialListDelegates() - : OnGetMaterials() - , OnMaterialChanged() - , OnGenerateCustomNameWidgets() - , OnGenerateCustomMaterialWidgets() - , OnResetMaterialToDefaultClicked() - {} - - /** Delegate called to populate the list with materials */ - FOnGetMaterials OnGetMaterials; - /** Delegate called when a user changes the material */ - FOnMaterialChanged OnMaterialChanged; - /** Delegate called to generate custom widgets under the name of in the left column of a details panel*/ - FOnGenerateWidgetsForMaterial OnGenerateCustomNameWidgets; - /** Delegate called to generate custom widgets under each material */ - FOnGenerateWidgetsForMaterial OnGenerateCustomMaterialWidgets; - /** Delegate called when a material list item should be reset to default */ - FOnResetMaterialToDefaultClicked OnResetMaterialToDefaultClicked; - /** Delegate called when we tick the material list to know if the list is dirty*/ - FOnMaterialListDirty OnMaterialListDirty; - - /** Delegate called Copying a material list */ - FOnCopyMaterialList OnCopyMaterialList; - /** Delegate called to know if we can copy a material list */ - FOnCanCopyMaterialList OnCanCopyMaterialList; - /** Delegate called Pasting a material list */ - FOnPasteMaterialList OnPasteMaterialList; - - /** Delegate called Copying a material item */ - FOnCopyMaterialItem OnCopyMaterialItem; - /** Delegate called to know if we can copy a material item */ - FOnCanCopyMaterialItem OnCanCopyMaterialItem; - /** Delegate called Pasting a material item */ - FOnPasteMaterialItem OnPasteMaterialItem; -}; - - -/** - * Builds up a list of unique materials while creating some information about the materials - */ -class IMaterialListBuilder -{ -public: - - /** Virtual destructor. */ - virtual ~IMaterialListBuilder(){}; - - /** - * Adds a new material to the list - * - * @param SlotIndex The slot (usually mesh element index) where the material is located on the component. - * @param Material The material being used. - * @param bCanBeReplced Whether or not the material can be replaced by a user. - */ - virtual void AddMaterial( uint32 SlotIndex, UMaterialInterface* Material, bool bCanBeReplaced ) = 0; -}; - - -/** - * A Material item in a material list slot - */ -struct FMaterialListItem -{ - /** Material being used */ - TWeakObjectPtr Material; - - /** Slot on a component where this material is at (mesh element) */ - int32 SlotIndex; - - /** Whether or not this material can be replaced by a new material */ - bool bCanBeReplaced; - - FMaterialListItem( UMaterialInterface* InMaterial = NULL, uint32 InSlotIndex = 0, bool bInCanBeReplaced = false ) - : Material( InMaterial ) - , SlotIndex( InSlotIndex ) - , bCanBeReplaced( bInCanBeReplaced ) - {} - - friend uint32 GetTypeHash( const FMaterialListItem& InItem ) - { - return GetTypeHash( InItem.Material ) + InItem.SlotIndex ; - } - - bool operator==( const FMaterialListItem& Other ) const - { - return Material == Other.Material && SlotIndex == Other.SlotIndex ; - } - - bool operator!=( const FMaterialListItem& Other ) const - { - return !(*this == Other); - } -}; - - -class FMaterialList - : public IDetailCustomNodeBuilder - , public TSharedFromThis -{ -public: - PROPERTYEDITOR_API FMaterialList( IDetailLayoutBuilder& InDetailLayoutBuilder, FMaterialListDelegates& MaterialListDelegates, bool bInAllowCollapse = false, bool bInShowUsedTextures = true, bool bInDisplayCompactSize = false); - - /** - * @return true if materials are being displayed. - */ - bool IsDisplayingMaterials() const { return true; } - -private: - - /** - * Called when a user expands all materials in a slot. - * - * @param SlotIndex The index of the slot being expanded. - */ - void OnDisplayMaterialsForElement( int32 SlotIndex ); - - /** - * Called when a user hides all materials in a slot. - * - * @param SlotIndex The index of the slot being hidden. - */ - void OnHideMaterialsForElement( int32 SlotIndex ); - - /** IDetailCustomNodeBuilder interface */ - virtual void SetOnRebuildChildren( FSimpleDelegate InOnRebuildChildren ) override { OnRebuildChildren = InOnRebuildChildren; } - virtual bool RequiresTick() const override { return true; } - virtual void Tick( float DeltaTime ) override; - virtual void GenerateHeaderRowContent( FDetailWidgetRow& NodeRow ) override; - virtual void GenerateChildContent( IDetailChildrenBuilder& ChildrenBuilder ) override; - virtual FName GetName() const override { return NAME_None; } - virtual bool InitiallyCollapsed() const override { return bAllowCollpase; } - - /** - * Adds a new material item to the list - * - * @param Row The row to add the item to - * @param CurrentSlot The slot id of the material - * @param Item The material item to add - * @param bDisplayLink If a link to the material should be displayed instead of the actual item (for multiple materials) - */ - void AddMaterialItem(FDetailWidgetRow& Row, int32 CurrentSlot, const FMaterialListItem& Item, bool bDisplayLink); - -private: - bool OnCanCopyMaterialList() const; - void OnCopyMaterialList(); - void OnPasteMaterialList(); - - bool OnCanCopyMaterialItem(int32 CurrentSlot) const; - void OnCopyMaterialItem(int32 CurrentSlot); - void OnPasteMaterialItem(int32 CurrentSlot); - - /** Delegates for the material list */ - FMaterialListDelegates MaterialListDelegates; - - /** Called to rebuild the children of the detail tree */ - FSimpleDelegate OnRebuildChildren; - - /** Parent detail layout this list is in */ - IDetailLayoutBuilder& DetailLayoutBuilder; - - /** Set of all unique displayed materials */ - TArray< FMaterialListItem > DisplayedMaterials; - - /** Set of all materials currently in view (may be less than DisplayedMaterials) */ - TArray< TSharedRef > ViewedMaterials; - - /** Set of all expanded slots */ - TSet ExpandedSlots; - - /** Material list builder used to generate materials */ - TSharedRef MaterialListBuilder; - - /** Allow Collapse of material header row. Right now if you allow collapse, it will initially collapse. */ - bool bAllowCollpase; - /** Whether or not to use the used textures menu for each material entry */ - bool bShowUsedTextures; - /** Whether or not to display a compact form of material entry*/ - bool bDisplayCompactSize; -}; - - /** * Helper class to create a material slot name widget for material lists */ diff --git a/Engine/Source/Editor/SceneOutliner/Private/SOutlinerTreeView.cpp b/Engine/Source/Editor/SceneOutliner/Private/SOutlinerTreeView.cpp index 0604d7340a48..f47696bdb1bb 100644 --- a/Engine/Source/Editor/SceneOutliner/Private/SOutlinerTreeView.cpp +++ b/Engine/Source/Editor/SceneOutliner/Private/SOutlinerTreeView.cpp @@ -33,7 +33,7 @@ namespace SceneOutliner else if (Operation->IsOfType()) { auto* DecoratedOp = static_cast(Operation); - DecoratedOp->SetToolTip(ValidationInfo.ValidationText, Icon); + DecoratedOp->SetToolTip(LOCTEXT("SceneOutlinerInvalidDragDropOp", "Invalid drag and drop operation, Drag into the Viewport."), Icon); } } } @@ -134,14 +134,9 @@ namespace SceneOutliner FFolderDropTarget DropTarget(NAME_None); auto Reply = HandleDrop(SceneOutlinerWeak, DragDropEvent, DropTarget, ValidationInfo); - - if (Reply.IsEventHandled()) - { - UpdateOperationDecorator(DragDropEvent, ValidationInfo); - return FReply::Handled(); - } - - return FReply::Unhandled(); + UpdateOperationDecorator(DragDropEvent, ValidationInfo); + + return Reply; } void SOutlinerTreeView::OnDragLeave(const FDragDropEvent& DragDropEvent) diff --git a/Engine/Source/Editor/SceneOutliner/Private/SSceneOutliner.cpp b/Engine/Source/Editor/SceneOutliner/Private/SSceneOutliner.cpp index 4eb7f5d7f38c..54721d60985a 100644 --- a/Engine/Source/Editor/SceneOutliner/Private/SSceneOutliner.cpp +++ b/Engine/Source/Editor/SceneOutliner/Private/SSceneOutliner.cpp @@ -118,7 +118,7 @@ namespace SceneOutliner TSharedPtr< FOutlinerFilter > CreateHideTemporaryActorsFilter() { return MakeShareable( new FOutlinerPredicateFilter( FActorFilterPredicate::CreateStatic( []( const AActor* InActor ){ - return (InActor->GetWorld() && InActor->GetWorld()->WorldType != EWorldType::PIE) || GEditor->ObjectsThatExistInEditorWorld.Get(InActor); + return ((InActor->GetWorld() && InActor->GetWorld()->WorldType != EWorldType::PIE) || GEditor->ObjectsThatExistInEditorWorld.Get(InActor)) && !InActor->HasAnyFlags(EObjectFlags::RF_Transient); } ), EDefaultFilterBehaviour::Pass ) ); } @@ -1163,8 +1163,7 @@ namespace SceneOutliner } // If we are allowing intermediate sorts and met the conditions, or this is the final sort after all ops are complete - if ((bMadeAnySignificantChanges && !SharedData->bRepresentingPlayWorld && !bDisableIntermediateSorting) || - bFinalSort) + if ((bMadeAnySignificantChanges && !bDisableIntermediateSorting) || bFinalSort) { RequestSort(); } @@ -1670,7 +1669,7 @@ namespace SceneOutliner void SSceneOutliner::FillFoldersSubMenu(FMenuBuilder& MenuBuilder) const { MenuBuilder.AddMenuEntry(LOCTEXT( "CreateNew", "Create New Folder" ), LOCTEXT( "CreateNew_ToolTip", "Move to a new folder" ), - FSlateIcon(FEditorStyle::GetStyleSetName(), "SceneOutliner.NewFolderIcon"), FExecuteAction::CreateSP(this, &SSceneOutliner::CreateFolder)); + FSlateIcon(FEditorStyle::GetStyleSetName(), "SceneOutliner.NewFolderIcon"), FExecuteAction::CreateSP(const_cast(this), &SSceneOutliner::CreateFolder)); AddMoveToFolderOutliner(MenuBuilder); } @@ -1796,7 +1795,7 @@ namespace SceneOutliner [ SNew(SSceneOutliner, MiniSceneOutlinerInitOptions) .IsEnabled(FSlateApplication::Get().GetNormalExecutionAttribute()) - .OnItemPickedDelegate(FOnSceneOutlinerItemPicked::CreateSP(this, &SSceneOutliner::MoveSelectionTo)) + .OnItemPickedDelegate(FOnSceneOutlinerItemPicked::CreateSP(const_cast(this), &SSceneOutliner::MoveSelectionTo)) ]; MenuBuilder.BeginSection(FName(), LOCTEXT("ExistingFolders", "Existing:")); @@ -1810,12 +1809,12 @@ namespace SceneOutliner LOCTEXT( "AddChildrenToSelection", "Immediate Children" ), LOCTEXT( "AddChildrenToSelection_ToolTip", "Select all immediate actor children of the selected folders" ), FSlateIcon(), - FExecuteAction::CreateSP(this, &SSceneOutliner::SelectFoldersDescendants, /*bSelectImmediateChildrenOnly=*/ true)); + FExecuteAction::CreateSP(const_cast(this), &SSceneOutliner::SelectFoldersDescendants, /*bSelectImmediateChildrenOnly=*/ true)); MenuBuilder.AddMenuEntry( LOCTEXT( "AddDescendantsToSelection", "All Descendants" ), LOCTEXT( "AddDescendantsToSelection_ToolTip", "Select all actor descendants of the selected folders" ), FSlateIcon(), - FExecuteAction::CreateSP(this, &SSceneOutliner::SelectFoldersDescendants, /*bSelectImmediateChildrenOnly=*/ false)); + FExecuteAction::CreateSP(const_cast(this), &SSceneOutliner::SelectFoldersDescendants, /*bSelectImmediateChildrenOnly=*/ false)); } void SSceneOutliner::SelectFoldersDescendants(bool bSelectImmediateChildrenOnly) @@ -2238,7 +2237,7 @@ namespace SceneOutliner CacheFoldersEdit = InFolders; // Sort folder names so parents appear before children - CacheFoldersEdit.Sort(); + CacheFoldersEdit.Sort(FNameLexicalLess()); // Cache existing children for (FName Folder : CacheFoldersEdit) @@ -2426,7 +2425,7 @@ namespace SceneOutliner // Sort in descending order so children will be deleted before parents CacheFoldersDelete.Sort([](const FFolderTreeItem& FolderA, const FFolderTreeItem& FolderB) { - return (FolderA.Path > FolderB.Path); + return FolderB.Path.LexicalLess(FolderA.Path); }); for (FFolderTreeItem* Folder : CacheFoldersDelete) diff --git a/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerDisplayNode.cpp b/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerDisplayNode.cpp index 4129e9dec3b4..794d97942efe 100644 --- a/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerDisplayNode.cpp +++ b/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerDisplayNode.cpp @@ -26,6 +26,7 @@ #include "CommonMovieSceneTools.h" #include "Framework/Commands/GenericCommands.h" #include "Tree/SCurveEditorTreePin.h" +#include "Tree/CurveEditorTreeFilter.h" #include "ScopedTransaction.h" #include "SequencerKeyTimeCache.h" #include "SequencerNodeSortingMethods.h" @@ -1054,5 +1055,14 @@ void FSequencerDisplayNode::CreateCurveModels(TArray>& O { } +bool FSequencerDisplayNode::PassesFilter(const FCurveEditorTreeFilter* InFilter) const +{ + if (InFilter->GetType() == ECurveEditorTreeFilterType::Text) + { + const FCurveEditorTreeTextFilter* Filter = static_cast(InFilter); + return Filter->Match(*GetDisplayName().ToString()); + } + return false; +} #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerDisplayNode.h b/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerDisplayNode.h index 70164dbc4614..9ca88c4aa6c2 100644 --- a/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerDisplayNode.h +++ b/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerDisplayNode.h @@ -276,6 +276,7 @@ public: // ICurveEditorTreeItem interface virtual TSharedPtr GenerateCurveEditorTreeWidget(const FName& InColumnName, TWeakPtr InCurveEditor, FCurveEditorTreeItemID InTreeItemID) override; virtual void CreateCurveModels(TArray>& OutCurveModels) override; + virtual bool PassesFilter(const FCurveEditorTreeFilter* InFilter) const override; /** * Get the display node that is ultimately responsible for constructing a section area widget for this node. diff --git a/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerTrackNode.cpp b/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerTrackNode.cpp index af112e592eae..1f34660e45fa 100644 --- a/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerTrackNode.cpp +++ b/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerTrackNode.cpp @@ -236,6 +236,8 @@ void FSequencerTrackNode::UpdateSections() { Sections.RemoveAt(Sections.Num()-NumToRemove, NumToRemove, true); } + + RemoveStaleChildren(); } void FSequencerTrackNode::ClearChildren() @@ -249,6 +251,33 @@ void FSequencerTrackNode::ClearChildren() } } +void FSequencerTrackNode::RemoveStaleChildren() +{ + // Gather stale nodes into a separate array + TArray> StaleNodes; + + TArray> NodesToCheck = ChildNodes; + for (int32 Index = 0; Index < NodesToCheck.Num(); ++Index) + { + TSharedRef Child = NodesToCheck[Index]; + if (Child->TreeSerialNumber != TreeSerialNumber) + { + // This node is stale - remove it + StaleNodes.Add(Child); + } + else + { + // This node is still relevant, but its children may not be - recurse into those + NodesToCheck.Append(Child->GetChildNodes()); + } + } + + for (TSharedRef StaleNode : StaleNodes) + { + StaleNode->SetParent(nullptr); + } +} + FSequencerTrackNode::ESubTrackMode FSequencerTrackNode::GetSubTrackMode() const { return SubTrackMode; diff --git a/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerTrackNode.h b/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerTrackNode.h index c6086e3604a0..598b45db1966 100644 --- a/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerTrackNode.h +++ b/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerTrackNode.h @@ -153,6 +153,8 @@ private: void ClearChildren(); + void RemoveStaleChildren(); + private: /** The track editor for the track associated with this node. */ diff --git a/Engine/Source/Editor/Sequencer/Private/ISequencerSection.cpp b/Engine/Source/Editor/Sequencer/Private/ISequencerSection.cpp index d0b53889dcf3..625367c9e390 100644 --- a/Engine/Source/Editor/Sequencer/Private/ISequencerSection.cpp +++ b/Engine/Source/Editor/Sequencer/Private/ISequencerSection.cpp @@ -111,7 +111,7 @@ void ISequencerSection::GenerateSectionLayout( ISectionLayoutBuilder& LayoutBuil Pair.Value.Channels.Sort([](const FChannelData& A, const FChannelData& B){ if (A.MetaData.SortOrder == B.MetaData.SortOrder) { - return A.MetaData.Name < B.MetaData.Name; + return A.MetaData.Name.LexicalLess(B.MetaData.Name); } return A.MetaData.SortOrder < B.MetaData.SortOrder; }); diff --git a/Engine/Source/Editor/Sequencer/Private/SSequencer.cpp b/Engine/Source/Editor/Sequencer/Private/SSequencer.cpp index eccb0640fac7..287bd0759204 100644 --- a/Engine/Source/Editor/Sequencer/Private/SSequencer.cpp +++ b/Engine/Source/Editor/Sequencer/Private/SSequencer.cpp @@ -71,6 +71,7 @@ #include "Camera/CameraActor.h" #include "SCurveEditorPanel.h" #include "Tree/SCurveEditorTree.h" +#include "Tree/SCurveEditorTreeTextFilter.h" #include "SCurveKeyDetailPanel.h" #include "MovieSceneTimeHelpers.h" #include "FrameNumberNumericInterface.h" @@ -381,7 +382,7 @@ void SSequencer::Construct(const FArguments& InArgs, TSharedRef InSe } TSharedRef ScrollBar = SNew(SScrollBar) - .Thickness(FVector2D(5.0f, 5.0f)); + .Thickness(FVector2D(9.0f, 9.0f)); SAssignNew(TrackOutliner, SSequencerTrackOutliner); SAssignNew(TrackArea, SSequencerTrackArea, TimeSliderControllerRef, InSequencer); SAssignNew(TreeView, SSequencerTreeView, InSequencer->GetNodeTree(), TrackArea.ToSharedRef()) @@ -410,7 +411,18 @@ void SSequencer::Construct(const FArguments& InArgs, TSharedRef InSe .DisabledTimeSnapTooltip(LOCTEXT("CurveEditorTimeSnapDisabledTooltip", "Time Snapping is currently driven by Sequencer.")) .TreeContent() [ - SNew(SCurveEditorTree, InSequencer->GetCurveEditor()) + SNew(SVerticalBox) + + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SCurveEditorTreeTextFilter, InSequencer->GetCurveEditor()) + ] + + + SVerticalBox::Slot() + [ + SNew(SCurveEditorTree, InSequencer->GetCurveEditor()) + ] ]; // Register an instanced custom property type layout to handle converting FFrameNumber from Tick Resolution to Display Rate. diff --git a/Engine/Source/Editor/Sequencer/Private/Sequencer.cpp b/Engine/Source/Editor/Sequencer/Private/Sequencer.cpp index 64a36789f6ff..a42be539b402 100644 --- a/Engine/Source/Editor/Sequencer/Private/Sequencer.cpp +++ b/Engine/Source/Editor/Sequencer/Private/Sequencer.cpp @@ -2917,7 +2917,7 @@ void FSequencer::PossessPIEViewports(UObject* CameraObject, UObject* UnlockIfCam { if (PC->PlayerCameraManager) { - PC->PlayerCameraManager->bGameCameraCutThisFrame = true; + PC->PlayerCameraManager->SetGameCameraCutThisFrame(); } if (CameraComponent) @@ -2960,7 +2960,7 @@ void FSequencer::PossessPIEViewports(UObject* CameraObject, UObject* UnlockIfCam if (PC->PlayerCameraManager) { PC->PlayerCameraManager->bClientSimulatingViewTarget = (CameraActor != nullptr); - PC->PlayerCameraManager->bGameCameraCutThisFrame = true; + PC->PlayerCameraManager->SetGameCameraCutThisFrame(); } } @@ -3115,6 +3115,10 @@ void FSequencer::AddReferencedObjects( FReferenceCollector& Collector ) FMovieSceneRootEvaluationTemplateInstance::StaticStruct()->SerializeBin(Collector.GetVerySlowReferenceCollectorArchive(), &RootTemplateInstance); } +FString FSequencer::GetReferencerName() const +{ + return TEXT("FSequencer"); +} void FSequencer::ResetPerMovieSceneData() { diff --git a/Engine/Source/Editor/Sequencer/Private/Sequencer.h b/Engine/Source/Editor/Sequencer/Private/Sequencer.h index 3ad7db79b2a2..f6f1a9388ed6 100644 --- a/Engine/Source/Editor/Sequencer/Private/Sequencer.h +++ b/Engine/Source/Editor/Sequencer/Private/Sequencer.h @@ -629,8 +629,8 @@ public: public: //~ FGCObject Interface - virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + virtual FString GetReferencerName() const override; public: diff --git a/Engine/Source/Editor/Sequencer/Private/SequencerNodeTree.cpp b/Engine/Source/Editor/Sequencer/Private/SequencerNodeTree.cpp index 4f9d1f64dbff..dfb1abc5226b 100644 --- a/Engine/Source/Editor/Sequencer/Private/SequencerNodeTree.cpp +++ b/Engine/Source/Editor/Sequencer/Private/SequencerNodeTree.cpp @@ -703,6 +703,10 @@ TArray< TSharedRef > FSequencerNodeTree::GetAllNodes() co void FSequencerNodeTree::UpdateCurveEditorTree() { FCurveEditor* CurveEditor = Sequencer.GetCurveEditor().Get(); + + // Guard against multiple broadcasts here and defer them until the end of this function + FScopedCurveEditorTreeUpdateGuard ScopedUpdateGuard = CurveEditor->GetTree()->ScopedUpdateGuard(); + auto Traverse_AddToCurveEditor = [this, CurveEditor](FSequencerDisplayNode& InNode) { if (InNode.GetType() == ESequencerNode::Track) diff --git a/Engine/Source/Editor/Sequencer/Public/ISequencerSection.h b/Engine/Source/Editor/Sequencer/Public/ISequencerSection.h index 3e18c50cefc9..3f366db2366a 100644 --- a/Engine/Source/Editor/Sequencer/Public/ISequencerSection.h +++ b/Engine/Source/Editor/Sequencer/Public/ISequencerSection.h @@ -43,7 +43,7 @@ namespace SequencerSectionConstants /** * Interface that should be implemented for the UI portion of a section */ -class ISequencerSection +class SEQUENCER_VTABLE ISequencerSection { public: virtual ~ISequencerSection(){} @@ -187,7 +187,7 @@ public: virtual void SlipSection(FFrameNumber SlipTime) {} }; -class FSequencerSection : public ISequencerSection +class SEQUENCER_VTABLE FSequencerSection : public ISequencerSection { public: FSequencerSection(UMovieSceneSection& InSection) @@ -205,4 +205,4 @@ public: protected: TWeakObjectPtr WeakSection; -}; \ No newline at end of file +}; diff --git a/Engine/Source/Editor/SkeletonEditor/Private/EditableSkeleton.cpp b/Engine/Source/Editor/SkeletonEditor/Private/EditableSkeleton.cpp index 1be71a5cfafe..440dafa52ebf 100644 --- a/Engine/Source/Editor/SkeletonEditor/Private/EditableSkeleton.cpp +++ b/Engine/Source/Editor/SkeletonEditor/Private/EditableSkeleton.cpp @@ -298,8 +298,8 @@ void FEditableSkeleton::RenameSmartname(const FName InContainerName, SmartName:: { SequencesToRecompress.Add(Seq); - Seq->CompressedCurveByteStream.Empty(); - Seq->CurveCompressionCodec = nullptr; + Seq->CompressedData.CompressedCurveByteStream.Empty(); + Seq->CompressedData.CurveCompressionCodec = nullptr; } } } @@ -425,9 +425,9 @@ void FEditableSkeleton::RemoveSmartnamesAndFixupAnimations(const FName& InContai { if (A.AssetClass == B.AssetClass) { - return A.AssetName < B.AssetName; + return A.AssetName.LexicalLess(B.AssetName); } - return A.AssetClass < B.AssetClass; + return A.AssetClass.LexicalLess(B.AssetClass); }); for (FAssetData& Data : AnimationAssets) diff --git a/Engine/Source/Editor/SkeletonEditor/Public/BoneProxy.h b/Engine/Source/Editor/SkeletonEditor/Public/BoneProxy.h index 4be6ab07a1f6..26135a380ab0 100644 --- a/Engine/Source/Editor/SkeletonEditor/Public/BoneProxy.h +++ b/Engine/Source/Editor/SkeletonEditor/Public/BoneProxy.h @@ -24,6 +24,7 @@ public: /** UObject interface */ virtual void PreEditChange(FEditPropertyChain& PropertyAboutToChange) override; virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + virtual bool IsDestructionThreadSafe() const override { return false; } /** FTickableEditorObject interface */ virtual void Tick(float DeltaTime) override; diff --git a/Engine/Source/Editor/SkeletonEditor/Public/ISkeletonTreeBuilder.h b/Engine/Source/Editor/SkeletonEditor/Public/ISkeletonTreeBuilder.h index a22a955ae9de..0a23da5d5192 100644 --- a/Engine/Source/Editor/SkeletonEditor/Public/ISkeletonTreeBuilder.h +++ b/Engine/Source/Editor/SkeletonEditor/Public/ISkeletonTreeBuilder.h @@ -27,6 +27,18 @@ struct SKELETONEDITOR_API FSkeletonTreeBuilderOutput */ void Add(const TSharedPtr& InItem, const FName& InParentName, TArrayView InParentTypes, bool bAddToHead = false); + /** + * Add an item to the output + * @param InItem The item to add + * @param InParentName The name of the item's parent + * @param InParentTypes The types of items to search. If this is empty all items will be searched. + * @param bAddToHead Whether to add the item to the start or end of the parent's children array + */ + FORCEINLINE void Add(const TSharedPtr& InItem, const FName& InParentName, std::initializer_list InParentTypes, bool bAddToHead = false) + { + Add(InItem, InParentName, MakeArrayView(InParentTypes), bAddToHead); + } + /** * Add an item to the output * @param InItem The item to add @@ -44,6 +56,17 @@ struct SKELETONEDITOR_API FSkeletonTreeBuilderOutput */ TSharedPtr Find(const FName& InName, TArrayView InTypes) const; + /** + * Find the item with the specified name + * @param InName The item's name + * @param InTypes The types of items to search. If this is empty all items will be searched. + * @return the item found, or an invalid ptr if it was not found. + */ + FORCEINLINE TSharedPtr Find(const FName& InName, std::initializer_list InTypes) const + { + return Find(InName, MakeArrayView(InTypes)); + } + /** * Find the item with the specified name * @param InName The item's name diff --git a/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlHistory.cpp b/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlHistory.cpp index 4b0bbd2f024d..fe705315b752 100644 --- a/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlHistory.cpp +++ b/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlHistory.cpp @@ -209,7 +209,7 @@ static UObject* GetAssetRevisionObject(TSharedPtr HistoryTreeI if (FileRevision.IsValid() && FileRevision->Get(TempPackageName)) // grab the path to a temporary package (where the revision item will be stored) { // try and load the temporary package - AssetPackage = LoadPackage(NULL, *TempPackageName, LOAD_DisableCompileOnLoad); + AssetPackage = LoadPackage(NULL, *TempPackageName, LOAD_ForDiff|LOAD_DisableCompileOnLoad); } } // if FileSourceControlState.IsValid() } @@ -655,6 +655,7 @@ public: void Construct( const FArguments& InArgs ) { AddHistoryInfo(InArgs._SourceControlStates.Get()); + ParentWindow = InArgs._ParentWindow.Get(); TSharedRef HeaderRow = SNew(SHeaderRow); @@ -718,10 +719,25 @@ public: { MainHistoryListView->SetItemExpansion(HistoryCollection[i],true); } + + ParentWindow.Pin()->SetWidgetToFocusOnActivate(MainHistoryListView); } private: + /** Used to intercept Escape key press, and interpret it as a close event */ + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override + { + // Pressing escape returns as if the user closed the window + if (InKeyEvent.GetKey() == EKeys::Escape) + { + ParentWindow.Pin()->RequestDestroyWindow(); + return FReply::Handled(); + } + + return FReply::Unhandled(); + } + /** * Constructs the "Additional Info" panel that displays specific revision info */ @@ -1426,6 +1442,9 @@ private: /** The last selected revision item; Displayed in the "additional information" subpanel */ TWeakPtr LastSelectedRevisionItem; + + /** Pointer to the parent window */ + TWeakPtr ParentWindow; }; void FSourceControlWindows::DisplayRevisionHistory( const TArray& InPackageNames ) diff --git a/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlSubmit.cpp b/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlSubmit.cpp index 7d6f0f56c43e..1be3a625058f 100644 --- a/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlSubmit.cpp +++ b/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlSubmit.cpp @@ -69,6 +69,12 @@ TSharedRef SSourceControlSubmitListRow::GenerateWidgetForColumn(const F return SNullWidget::NullWidget; } +FText SSourceControlSubmitWidget::SavedChangeListDescription; + +SSourceControlSubmitWidget::~SSourceControlSubmitWidget() +{ + SavedChangeListDescription = ChangeListDescriptionTextCtrl->GetText(); +} void SSourceControlSubmitWidget::Construct(const FArguments& InArgs) { @@ -133,6 +139,7 @@ void SSourceControlSubmitWidget::Construct(const FArguments& InArgs) [ SAssignNew( ChangeListDescriptionTextCtrl, SMultiLineEditableTextBox ) .SelectAllTextWhenFocused( true ) + .Text(SavedChangeListDescription) .AutoWrapText( true ) ] ] @@ -384,6 +391,9 @@ void SSourceControlSubmitWidget::OnToggleSelectedCheckBox(ECheckBoxState InNewSt void SSourceControlSubmitWidget::FillChangeListDescription(FChangeListDescription& OutDesc) { OutDesc.Description = ChangeListDescriptionTextCtrl->GetText(); + + ChangeListDescriptionTextCtrl->SetText(FText()); // Don't save description on Submit + OutDesc.FilesForAdd.Empty(); OutDesc.FilesForSubmit.Empty(); diff --git a/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlSubmit.h b/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlSubmit.h index f7426984f2fa..6106b4392d23 100644 --- a/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlSubmit.h +++ b/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlSubmit.h @@ -99,10 +99,7 @@ public: SLATE_END_ARGS() - /** Constructor */ - SSourceControlSubmitWidget() - { - } + ~SSourceControlSubmitWidget(); /** Constructs the widget */ void Construct(const FArguments& InArgs); @@ -216,6 +213,9 @@ private: /** Currently selected sorting mode */ EColumnSortMode::Type SortMode; + + /** Submit Description saved when the widget is destroyed if canceled */ + static FText SavedChangeListDescription; }; class SSourceControlSubmitListRow : public SMultiColumnTableRow> diff --git a/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.cpp b/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.cpp index 019c3f0ee67f..010a63d4612e 100644 --- a/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.cpp +++ b/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.cpp @@ -2140,6 +2140,19 @@ void FStaticMeshEditor::NotifyPostChange( const FPropertyChangedEvent& PropertyC { RefreshTool(); } + else if (PropertyChangedEvent.GetPropertyName() == TEXT("CollisionResponses")) + { + for (FObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) + { + UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); + if (StaticMeshComponent->GetStaticMesh() == StaticMesh) + { + StaticMeshComponent->UpdateCollisionFromStaticMesh(); + StaticMeshComponent->MarkRenderTransformDirty(); + } + } + } + } } @@ -2232,8 +2245,10 @@ TStatId FStaticMeshEditor::GetStatId() const bool FStaticMeshEditor::CanRemoveUVChannel() { - // Can remove UV channel if there's one that is currently being selected and displayed - return Viewport->GetViewportClient().IsDrawUVOverlayChecked(); + // Can remove UV channel if there's one that is currently being selected and displayed, + // and the current LOD has more than one UV channel + return Viewport->GetViewportClient().IsDrawUVOverlayChecked() && + StaticMesh->GetNumUVChannels(GetCurrentLODIndex()) > 1; } void FStaticMeshEditor::RemoveCurrentUVChannel() diff --git a/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditorTools.cpp b/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditorTools.cpp index a01a610d1302..d050c2cc5976 100644 --- a/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditorTools.cpp +++ b/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditorTools.cpp @@ -23,6 +23,7 @@ #include "StaticMeshResources.h" #include "StaticMeshEditor.h" #include "PropertyCustomizationHelpers.h" +#include "MaterialList.h" #include "PhysicsEngine/BodySetup.h" #include "FbxMeshUtils.h" #include "Widgets/Input/SVectorInputBox.h" @@ -627,6 +628,7 @@ void FMeshBuildSettingsLayout::GenerateChildContent( IDetailChildrenBuilder& Chi .Z(this, &FMeshBuildSettingsLayout::GetBuildScaleZ) .bColorAxisLabels(false) .AllowResponsiveLayout(true) + .AllowSpin(false) .OnXCommitted(this, &FMeshBuildSettingsLayout::OnBuildScaleXChanged) .OnYCommitted(this, &FMeshBuildSettingsLayout::OnBuildScaleYChanged) .OnZCommitted(this, &FMeshBuildSettingsLayout::OnBuildScaleZChanged) @@ -3576,8 +3578,8 @@ TSharedRef FLevelOfDetailSettingsLayout::GetLODScreenSizeWidget(FName P .MaxValue(WORLD_MAX) .SliderExponent(2.0f) .Value(this, &FLevelOfDetailSettingsLayout::GetLODScreenSize, PlatformGroupName, LODIndex) - .OnValueChanged(this, &FLevelOfDetailSettingsLayout::OnLODScreenSizeChanged, PlatformGroupName, LODIndex) - .OnValueCommitted(this, &FLevelOfDetailSettingsLayout::OnLODScreenSizeCommitted, PlatformGroupName, LODIndex) + .OnValueChanged(const_cast(this), &FLevelOfDetailSettingsLayout::OnLODScreenSizeChanged, PlatformGroupName, LODIndex) + .OnValueCommitted(const_cast(this), &FLevelOfDetailSettingsLayout::OnLODScreenSizeCommitted, PlatformGroupName, LODIndex) .IsEnabled(this, &FLevelOfDetailSettingsLayout::CanChangeLODScreenSize); } @@ -3585,7 +3587,7 @@ TArray FLevelOfDetailSettingsLayout::GetLODScreenSizePlatformOverrideName { TArray KeyArray; LODScreenSizes[LODIndex].PerPlatform.GenerateKeyArray(KeyArray); - KeyArray.Sort(); + KeyArray.Sort(FNameLexicalLess()); return KeyArray; } @@ -4014,8 +4016,8 @@ TSharedRef FLevelOfDetailSettingsLayout::GetMinLODWidget(FName Platform return SNew(SSpinBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .Value(this, &FLevelOfDetailSettingsLayout::GetMinLOD, PlatformGroupName) - .OnValueChanged(this, &FLevelOfDetailSettingsLayout::OnMinLODChanged, PlatformGroupName) - .OnValueCommitted(this, &FLevelOfDetailSettingsLayout::OnMinLODCommitted, PlatformGroupName) + .OnValueChanged(const_cast(this), &FLevelOfDetailSettingsLayout::OnMinLODChanged, PlatformGroupName) + .OnValueCommitted(const_cast(this), &FLevelOfDetailSettingsLayout::OnMinLODCommitted, PlatformGroupName) .MinValue(0) .MaxValue(MAX_STATIC_MESH_LODS) .ToolTipText(this, &FLevelOfDetailSettingsLayout::GetMinLODTooltip) @@ -4058,7 +4060,7 @@ TArray FLevelOfDetailSettingsLayout::GetMinLODPlatformOverrideNames() con check(StaticMesh); TArray KeyArray; StaticMesh->MinLOD.PerPlatform.GenerateKeyArray(KeyArray); - KeyArray.Sort(); + KeyArray.Sort(FNameLexicalLess()); return KeyArray; } @@ -4124,8 +4126,8 @@ TSharedRef FLevelOfDetailSettingsLayout::GetNumStreamedLODsWidget(FName return SNew(SSpinBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .Value(this, &FLevelOfDetailSettingsLayout::GetNumStreamedLODs, PlatformGroupName) - .OnValueChanged(this, &FLevelOfDetailSettingsLayout::OnNumStreamedLODsChanged, PlatformGroupName) - .OnValueCommitted(this, &FLevelOfDetailSettingsLayout::OnNumStreamedLODsCommitted, PlatformGroupName) + .OnValueChanged(const_cast(this), &FLevelOfDetailSettingsLayout::OnNumStreamedLODsChanged, PlatformGroupName) + .OnValueCommitted(const_cast(this), &FLevelOfDetailSettingsLayout::OnNumStreamedLODsCommitted, PlatformGroupName) .MinValue(-1) .MaxValue(MAX_STATIC_MESH_LODS) .ToolTipText(this, &FLevelOfDetailSettingsLayout::GetNumStreamedLODsTooltip) @@ -4168,7 +4170,7 @@ TArray FLevelOfDetailSettingsLayout::GetNumStreamedLODsPlatformOverrideNa check(StaticMesh); TArray KeyArray; StaticMesh->NumStreamedLODs.PerPlatform.GenerateKeyArray(KeyArray); - KeyArray.Sort(); + KeyArray.Sort(FNameLexicalLess()); return KeyArray; } diff --git a/Engine/Source/Editor/StatsViewer/Private/SStatsViewer.cpp b/Engine/Source/Editor/StatsViewer/Private/SStatsViewer.cpp index 00377e77fe93..dfeae5df502d 100644 --- a/Engine/Source/Editor/StatsViewer/Private/SStatsViewer.cpp +++ b/Engine/Source/Editor/StatsViewer/Private/SStatsViewer.cpp @@ -615,7 +615,7 @@ TSharedRef SStatsViewer::OnGetDisplayMenuContent() const StatsPage->GetToolTip(), FSlateIcon(), FUIAction( - FExecuteAction::CreateSP( this, &SStatsViewer::SetDisplayedStats, StatsPage ), + FExecuteAction::CreateSP( const_cast(this), &SStatsViewer::SetDisplayedStats, StatsPage ), FCanExecuteAction(), FIsActionChecked::CreateSP( this, &SStatsViewer::AreStatsDisplayed, StatsPage ) ), @@ -640,7 +640,7 @@ TSharedRef SStatsViewer::OnGetObjectSetMenuContent() const FText::FromString( CurrentStats->GetObjectSetToolTip( ObjectSetIndex ) ), FSlateIcon(), FUIAction( - FExecuteAction::CreateSP( this, &SStatsViewer::SetObjectSet, ObjectSetIndex ), + FExecuteAction::CreateSP( const_cast(this), &SStatsViewer::SetObjectSet, ObjectSetIndex ), FCanExecuteAction(), FIsActionChecked::CreateSP( this, &SStatsViewer::IsObjectSetSelected, ObjectSetIndex ) ), @@ -686,7 +686,7 @@ TSharedRef SStatsViewer::OnGetFilterMenuContent() const FText::Format( LOCTEXT( "FilterMenuEntry_Tooltip", "Search statistics by {FilterName}.\n{FilterDesc}" ), Arguments ), FSlateIcon(), FUIAction( - FExecuteAction::CreateSP( this, &SStatsViewer::SetSearchFilter, ColumnIndex ), + FExecuteAction::CreateSP( const_cast(this), &SStatsViewer::SetSearchFilter, ColumnIndex ), FCanExecuteAction(), FIsActionChecked::CreateSP( this, &SStatsViewer::IsSearchFilterSelected, ColumnIndex ) ), diff --git a/Engine/Source/Editor/StatsViewer/Private/StatsPages/CookerStatsPage.cpp b/Engine/Source/Editor/StatsViewer/Private/StatsPages/CookerStatsPage.cpp index 07b465edaf93..d062439c495f 100644 --- a/Engine/Source/Editor/StatsViewer/Private/StatsPages/CookerStatsPage.cpp +++ b/Engine/Source/Editor/StatsViewer/Private/StatsPages/CookerStatsPage.cpp @@ -180,7 +180,7 @@ TSharedRef FCookerStatsPage::HandleFilterComboButtonGetMenuContent( ) c FText::FromString(PlatformName), FSlateIcon(), FUIAction( - FExecuteAction::CreateRaw(&CookerStatsPage, &FCookerStatsPage::HandleFilterMenuEntryExecute, PlatformName), + FExecuteAction::CreateRaw(const_cast(&CookerStatsPage), &FCookerStatsPage::HandleFilterMenuEntryExecute, PlatformName), FCanExecuteAction(), FIsActionChecked::CreateRaw(&CookerStatsPage, &FCookerStatsPage::HandleFilterMenuEntryIsChecked, PlatformName) ), diff --git a/Engine/Source/Editor/SwarmInterface/Private/SwarmInterfaceLocal.cpp b/Engine/Source/Editor/SwarmInterface/Private/SwarmInterfaceLocal.cpp index ab5513e11454..a51f22df72f2 100644 --- a/Engine/Source/Editor/SwarmInterface/Private/SwarmInterfaceLocal.cpp +++ b/Engine/Source/Editor/SwarmInterface/Private/SwarmInterfaceLocal.cpp @@ -138,10 +138,11 @@ namespace SwarmInterfaceLocalImpl if (SocketSubsystem) { // create socket - FSocket* Socket = SocketSubsystem->CreateSocket(NAME_DGram, TEXT("TestSocket"), true); + TSharedRef BindAddr = FIPv4Endpoint::Any.ToInternetAddr(); + FSocket* Socket = SocketSubsystem->CreateSocket(NAME_DGram, TEXT("TestSocket"), BindAddr->GetProtocolType()); if (Socket) { - if (Socket->Bind(*FIPv4Endpoint::Any.ToInternetAddr())) + if (Socket->Bind(*BindAddr)) { if (Socket->SetBroadcast(true) && Socket->SetMulticastLoopback(true)) { diff --git a/Engine/Source/Editor/UATHelper/UATHelperModule.cpp b/Engine/Source/Editor/UATHelper/UATHelperModule.cpp index 19d4d9afcefb..138e0e4851b4 100644 --- a/Engine/Source/Editor/UATHelper/UATHelperModule.cpp +++ b/Engine/Source/Editor/UATHelper/UATHelperModule.cpp @@ -449,6 +449,10 @@ public: ); TSharedPtr NotificationItem = FSlateNotificationManager::Get().AddNotification(Info); + if(NotificationItemPtr.IsValid()) + { + NotificationItemPtr.Pin().Get()->Fadeout(); + } if (!NotificationItem.IsValid()) { @@ -461,7 +465,7 @@ public: NotificationItem->SetCompletionState(SNotificationItem::CS_Pending); // launch the packager - TWeakPtr NotificationItemPtr(NotificationItem); + NotificationItemPtr = NotificationItem; EventData Data; Data.StartTime = FPlatformTime::Seconds(); @@ -647,6 +651,10 @@ public: } } + +private: + TWeakPtr NotificationItemPtr; + }; IMPLEMENT_MODULE(FUATHelperModule, UATHelper) diff --git a/Engine/Source/Editor/UMGEditor/Classes/WidgetPaletteFavorites.cpp b/Engine/Source/Editor/UMGEditor/Classes/WidgetPaletteFavorites.cpp new file mode 100644 index 000000000000..59f75bb8cd69 --- /dev/null +++ b/Engine/Source/Editor/UMGEditor/Classes/WidgetPaletteFavorites.cpp @@ -0,0 +1,27 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "WidgetPaletteFavorites.h" +#include "UObject/UnrealType.h" + +UWidgetPaletteFavorites::UWidgetPaletteFavorites(FObjectInitializer const& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void UWidgetPaletteFavorites::Add(const FString& InWidgetTemplateName) +{ + Favorites.AddUnique(InWidgetTemplateName); + + SaveConfig(); + + OnFavoritesUpdated.Broadcast(); +} + +void UWidgetPaletteFavorites::Remove(const FString& InWidgetTemplateName) +{ + Favorites.Remove(InWidgetTemplateName); + + SaveConfig(); + + OnFavoritesUpdated.Broadcast(); +} \ No newline at end of file diff --git a/Engine/Source/Editor/UMGEditor/Classes/WidgetPaletteFavorites.h b/Engine/Source/Editor/UMGEditor/Classes/WidgetPaletteFavorites.h new file mode 100644 index 000000000000..45fa0e3b3008 --- /dev/null +++ b/Engine/Source/Editor/UMGEditor/Classes/WidgetPaletteFavorites.h @@ -0,0 +1,29 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Object.h" +#include "WidgetPaletteFavorites.generated.h" + +UCLASS(config = EditorPerProjectUserSettings) +class UMGEDITOR_API UWidgetPaletteFavorites : public UObject +{ + GENERATED_UCLASS_BODY() +public: + + void Add(const FString& InWidgetTemplateName); + void Remove(const FString& InWidgetTemplateName); + + TArray GetFavorites() const { return Favorites; } + + DECLARE_MULTICAST_DELEGATE(FOnFavoritesUpdated) + + /** Fires after the list of favorites is updated */ + FOnFavoritesUpdated OnFavoritesUpdated; + +private: + UPROPERTY(config) + TArray Favorites; +}; diff --git a/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.cpp b/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.cpp index c6fa475d4dc8..37acbdf570b4 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.cpp @@ -25,6 +25,8 @@ #include "Components/PanelSlot.h" #include "Details/SPropertyBinding.h" #include "Widgets/Layout/SWidgetSwitcher.h" +#include "IDetailsView.h" +#include "IDetailPropertyExtensionHandler.h" #define LOCTEXT_NAMESPACE "UMG" @@ -283,6 +285,11 @@ void FBlueprintWidgetCustomization::CustomizeDetails( IDetailLayoutBuilder& Deta static const FName LayoutCategoryKey(TEXT("Layout")); static const FName LocalizationCategoryKey(TEXT("Localization")); + static const FName AccessibleBehaviorKey(TEXT("AccessibleBehavior")); + static const FName AccessibleTextKey(TEXT("AccessibleText")); + static const FName AccessibleSummaryBehaviorKey(TEXT("AccessibleSummaryBehavior")); + static const FName AccessibleSummaryTextKey(TEXT("AccessibleSummaryText")); + DetailLayout.EditCategory(LocalizationCategoryKey, FText::GetEmpty(), ECategoryPriority::Uncommon); TArray< TWeakObjectPtr > OutObjects; @@ -307,6 +314,9 @@ void FBlueprintWidgetCustomization::CustomizeDetails( IDetailLayoutBuilder& Deta } } + CustomizeAccessibilityProperty(DetailLayout, AccessibleBehaviorKey, AccessibleTextKey); + CustomizeAccessibilityProperty(DetailLayout, AccessibleSummaryBehaviorKey, AccessibleSummaryTextKey); + PerformBindingCustomization(DetailLayout); } @@ -342,4 +352,66 @@ void FBlueprintWidgetCustomization::PerformBindingCustomization(IDetailLayoutBui } } +void FBlueprintWidgetCustomization::CustomizeAccessibilityProperty(IDetailLayoutBuilder& DetailLayout, const FName& BehaviorPropertyName, const FName& TextPropertyName) +{ + static const FName AccessibilityCategoryKey(TEXT("Accessibility")); + + // Treat the *Behavior property as the "base" property for the row, and then add the *Text properties to the end of it. + IDetailCategoryBuilder& AccessibilityCategory = DetailLayout.EditCategory(AccessibilityCategoryKey); + TSharedRef AccessibleBehaviorPropertyHandle = DetailLayout.GetProperty(BehaviorPropertyName); + IDetailPropertyRow& AccessibilityRow = AccessibilityCategory.AddProperty(AccessibleBehaviorPropertyHandle); + + TSharedRef AccessibleTextPropertyHandle = DetailLayout.GetProperty(TextPropertyName); + // Make sure the old *Text properties are hidden so we don't get duplicate widgets + DetailLayout.HideProperty(AccessibleTextPropertyHandle); + // The *Text properties are put into an HBox which will consist of the text box and the delegate's combo box. + // The combo box may not always be valid for the current selection, as its creation is determined by the ExtensionHandler. + TSharedRef CustomTextLayout = SNew(SHorizontalBox) + .Visibility(TAttribute::Create([AccessibleBehaviorPropertyHandle]() -> EVisibility + { + // Toggle the visibility of the *Text properties on only if the *Behavior property is set to Custom + uint8 Behavior = 0; + AccessibleBehaviorPropertyHandle->GetValue(Behavior); + return (ESlateAccessibleBehavior)Behavior == ESlateAccessibleBehavior::Custom ? EVisibility::Visible : EVisibility::Hidden; + })) + + SHorizontalBox::Slot() + .Padding(FMargin(4.0f, 0.0f)) + [ + AccessibleTextPropertyHandle->CreatePropertyValueWidget() + ]; + + TSharedRef ExtensionWidget = const_cast(DetailLayout.GetDetailsView())->GetExtensionHandler()->GenerateExtensionWidget(UWidget::StaticClass(), AccessibleTextPropertyHandle); + if (ExtensionWidget != SNullWidget::NullWidget) + { + CustomTextLayout->AddSlot() + .AutoWidth() + [ + ExtensionWidget + ]; + } + + TSharedPtr AccessibleBehaviorNameWidget, AccessibleBehaviorValueWidget; + AccessibilityRow.GetDefaultWidgets(AccessibleBehaviorNameWidget, AccessibleBehaviorValueWidget); + + AccessibilityRow.CustomWidget() + .NameContent() + [ + AccessibleBehaviorNameWidget.ToSharedRef() + ] + .ValueContent() + .HAlign(HAlign_Fill) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + AccessibleBehaviorValueWidget.ToSharedRef() + ] + + SHorizontalBox::Slot() + [ + CustomTextLayout + ] + ]; +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.h b/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.h index efd28ee65739..1e1268115f2d 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.h +++ b/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.h @@ -43,6 +43,9 @@ private: FReply HandleAddOrViewEventForVariable(const FName EventName, FName PropertyName, TWeakObjectPtr PropertyClass); int32 HandleAddOrViewIndexForButton(const FName EventName, FName PropertyName) const; + + /** Handle specific customizations for ESlateAccessibleBehavior properties */ + void CustomizeAccessibilityProperty(IDetailLayoutBuilder& DetailLayout, const FName& BehaviorPropertyName, const FName& TextPropertyName); private: TWeakPtr Editor; diff --git a/Engine/Source/Editor/UMGEditor/Private/Designer/DesignTimeUtils.cpp b/Engine/Source/Editor/UMGEditor/Private/Designer/DesignTimeUtils.cpp index b3ad2f995641..35de48948499 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Designer/DesignTimeUtils.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Designer/DesignTimeUtils.cpp @@ -20,7 +20,7 @@ bool FDesignTimeUtils::GetArrangedWidget(TSharedRef Widget, FArrangedWi FWidgetPath WidgetPath; if ( FSlateApplication::Get().GeneratePathToWidgetUnchecked(Widget, WidgetPath) ) { - ArrangedWidget = WidgetPath.FindArrangedWidget(Widget).Get(FArrangedWidget::NullWidget); + ArrangedWidget = WidgetPath.FindArrangedWidget(Widget).Get(FArrangedWidget::GetNullWidget()); return true; } @@ -40,7 +40,7 @@ bool FDesignTimeUtils::GetArrangedWidgetRelativeToWindow(TSharedRef Wid FWidgetPath WidgetPath; if ( FSlateApplication::Get().GeneratePathToWidgetUnchecked(Widget, WidgetPath) ) { - ArrangedWidget = WidgetPath.FindArrangedWidget(Widget).Get(FArrangedWidget::NullWidget); + ArrangedWidget = WidgetPath.FindArrangedWidget(Widget).Get(FArrangedWidget::GetNullWidget()); ArrangedWidget.Geometry.AppendTransform(FSlateLayoutTransform(Inverse(CurrentWindowRef->GetPositionInScreen()))); //ArrangedWidget.Geometry.AppendTransform(Inverse(CurrentWindowRef->GetLocalToScreenTransform())); return true; diff --git a/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.cpp b/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.cpp index ee918a34a585..868b578870f9 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.cpp @@ -70,6 +70,7 @@ #include "DeviceProfiles/DeviceProfile.h" #include "DeviceProfiles/DeviceProfileManager.h" #include "Engine/DPICustomScalingRule.h" +#include "UMGEditorModule.h" #define LOCTEXT_NAMESPACE "UMG" @@ -215,6 +216,7 @@ public: /** The original parent of the widget. */ FWidgetReference ParentWidget; + /** The offset of the original click location, as a percentage of the widget's size. */ FVector2D DraggedOffset; }; @@ -241,7 +243,7 @@ TSharedRef FSelectedWidgetDragDropOp::New(TSharedPtr< Operation->bShowingMessage = false; Operation->Designer = InDesigner; - for (const auto& InDraggedWidget : InWidgets) + for (const FDraggingWidgetReference& InDraggedWidget : InWidgets) { FItem DraggedWidget; DraggedWidget.bStayingInParent = false; @@ -355,6 +357,15 @@ void SDesignerView::Construct(const FArguments& InArgs, TSharedPtr("UMGEditor"); + + TSharedPtrDesignerExtensibilityManager = UMGEditorInterface.GetDesignerExtensibilityManager(); + for (const auto& Extension : DesignerExtensibilityManager->GetExternalDesignerExtensions()) + { + Register(Extension); + } + GEditor->OnBlueprintReinstanced().AddRaw(this, &SDesignerView::OnPreviewNeedsRecreation); BindCommands(); @@ -431,6 +442,11 @@ void SDesignerView::Construct(const FArguments& InArgs, TSharedPtrSetContent(SNullWidget::NullWidget); + PreviewSizeConstraint->SetContent(SNullWidget::NullWidget); } SDesignerView::FWidgetHitResult::FWidgetHitResult() @@ -2288,20 +2304,11 @@ void SDesignerView::UpdatePreviewWidget(bool bForceUpdate) if ( PreviewWidget ) { TSharedRef NewPreviewSlateWidget = PreviewWidget->TakeWidget(); - NewPreviewSlateWidget->SlatePrepass(); + NewPreviewSlateWidget->SlatePrepass(PreviewSizeConstraint->GetCachedGeometry().Scale); PreviewSlateWidget = NewPreviewSlateWidget; - // The constraint box for the widget size needs to inside the DPI scaler in order to make sure it too - // is sized accurately for the size screen it's on. - TSharedRef NewPreviewSizeConstraintBox = SNew(SBox) - .WidthOverride(this, &SDesignerView::GetPreviewSizeWidth) - .HeightOverride(this, &SDesignerView::GetPreviewSizeHeight) - [ - NewPreviewSlateWidget - ]; - - PreviewSurface->SetContent(NewPreviewSizeConstraintBox); + PreviewSizeConstraint->SetContent(NewPreviewSlateWidget); // Notify all selected widgets that they are selected, because there are new preview objects // state may have been lost so this will recreate it if the widget does something special when @@ -2518,7 +2525,7 @@ FReply SDesignerView::OnDragDetected(const FGeometry& MyGeometry, const FPointer // Clear any pending selected widgets, the user has already decided what widget they want. PendingSelectedWidget = FWidgetReference(); - for (const auto& SelectedWidget : SelectedWidgets) + for (const FWidgetReference& SelectedWidget : SelectedWidgets) { // Determine The offset to keep the widget from the mouse while dragging FArrangedWidget ArrangedWidget(SNullWidget::NullWidget, FGeometry()); @@ -2527,14 +2534,13 @@ FReply SDesignerView::OnDragDetected(const FGeometry& MyGeometry, const FPointer FDragWidget DraggingWidget; DraggingWidget.Widget = SelectedWidget; - DraggingWidget.DraggedOffset = SelectedWidgetContextMenuLocation; - + DraggingWidget.DraggedOffset = SelectedWidgetContextMenuLocation / ArrangedWidget.Geometry.GetLocalSize(); DraggingWidgetCandidates.Add(DraggingWidget); } TArray DraggingWidgets; - for (const auto& Candidate : DraggingWidgetCandidates) + for (const FDragWidget& Candidate : DraggingWidgetCandidates) { // check the parent chain of each dragged widget and ignore those that are children of other dragged widgets bool bIsChild = false; @@ -2965,13 +2971,11 @@ void SDesignerView::ProcessDropAndAddWidget(const FGeometry& MyGeometry, const F } else { - Slot = ParentWidget->AddChild(Widget); + Slot = ParentWidget->InsertChildAt(ParentWidget->GetChildIndex(Widget), Widget); } if (Slot != nullptr) { - FVector2D NewPosition = LocalPosition - DraggedWidget.DraggedOffset; - FWidgetBlueprintEditorUtils::ImportPropertiesFromText(Slot, DraggedWidget.ExportedSlotProperties); bool HasChangedLayout = false; @@ -2986,6 +2990,12 @@ void SDesignerView::ProcessDropAndAddWidget(const FGeometry& MyGeometry, const F { CanvasSlot->SaveBaseLayout(); + FArrangedWidget ArrangedWidget(SNullWidget::NullWidget, FGeometry()); + FDesignTimeUtils::GetArrangedWidget(Widget->GetCachedWidget().ToSharedRef(), ArrangedWidget); + + FVector2D Offset = DraggedWidget.DraggedOffset * ArrangedWidget.Geometry.GetLocalSize(); + FVector2D NewPosition = LocalPosition - Offset; + // Perform grid snapping on X and Y if we need to. if (bGridSnapX) { diff --git a/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.h b/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.h index b969e421b8ce..795c32a11a62 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.h +++ b/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.h @@ -318,6 +318,7 @@ private: TSharedPtr PreviewHitTestRoot; TSharedPtr PreviewAreaConstraint; TSharedPtr PreviewSurface; + TSharedPtr PreviewSizeConstraint; TSharedPtr DesignerControls; TSharedPtr DesignerWidgetCanvas; diff --git a/Engine/Source/Editor/UMGEditor/Private/Designer/STransformHandle.cpp b/Engine/Source/Editor/UMGEditor/Private/Designer/STransformHandle.cpp index 200434f27696..baacf29b233b 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Designer/STransformHandle.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Designer/STransformHandle.cpp @@ -128,8 +128,8 @@ FReply STransformHandle::OnMouseMove(const FGeometry& MyGeometry, const FPointer UWidget* Preview = SelectedWidget.GetPreview(); { - FVector2D Delta = MouseEvent.GetScreenSpacePosition() - MouseDownPosition; - FVector2D TranslateAmount = Delta * ( 1.0f / Designer->GetPreviewScale() ); + const FVector2D Delta = MouseEvent.GetScreenSpacePosition() - MouseDownPosition; + const FVector2D TranslateAmount = Delta * (1.0f / (Designer->GetPreviewScale() * MyGeometry.Scale)); Resize(Cast(Preview->Slot), DragDirection, TranslateAmount); Resize(Cast(Template->Slot), DragDirection, TranslateAmount); diff --git a/Engine/Source/Editor/UMGEditor/Private/Details/SWidgetDetailsView.cpp b/Engine/Source/Editor/UMGEditor/Private/Details/SWidgetDetailsView.cpp index 41cc0e2ecfc6..17bc2b9681f9 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Details/SWidgetDetailsView.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Details/SWidgetDetailsView.cpp @@ -549,7 +549,11 @@ void SWidgetDetailsView::NotifyPostChange(const FPropertyChangedEvent& PropertyC // Any time we migrate a property value we need to mark the blueprint as structurally modified so users don't need // to recompile it manually before they see it play in game using the latest version. FBlueprintEditorUtils::MarkBlueprintAsModified(BlueprintEditor.Pin()->GetBlueprintObj()); - ClearFocusIfOwned(); + + if (PropertyChangedEvent.ChangeType != EPropertyChangeType::ValueSet) + { + ClearFocusIfOwned(); + } } // If the property that changed is marked as "DesignerRebuild" we invalidate diff --git a/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyView.cpp b/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyView.cpp index 03ca05df1c71..2e3856cb454a 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyView.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyView.cpp @@ -32,7 +32,7 @@ void SHierarchyView::Construct(const FArguments& InArgs, TSharedPtrOnObjectsReplaced().AddRaw(this, &SHierarchyView::OnObjectsReplaced); // Create the filter for searching in the tree - SearchBoxWidgetFilter = MakeShareable(new WidgetTextFilter(WidgetTextFilter::FItemToStringArray::CreateSP(this, &SHierarchyView::TransformWidgetToString))); + SearchBoxWidgetFilter = MakeShareable(new WidgetTextFilter(WidgetTextFilter::FItemToStringArray::CreateSP(this, &SHierarchyView::GetWidgetFilterStrings))); UWidgetBlueprint* Blueprint = GetBlueprint(); Blueprint->OnChanged().AddRaw(this, &SHierarchyView::OnBlueprintChanged); @@ -105,11 +105,6 @@ void SHierarchyView::Tick( const FGeometry& AllottedGeometry, const double InCur { if ( bRebuildTreeRequested || bRefreshRequested ) { - if (!bExpandAllNodes) - { - FindExpandedItemNames(); - } - if ( bRebuildTreeRequested ) { RebuildTreeView(); @@ -117,15 +112,12 @@ void SHierarchyView::Tick( const FGeometry& AllottedGeometry, const double InCur RefreshTree(); - RestoreExpandedItems(); + UpdateItemsExpansionFromModel(); OnEditorSelectionChanged(); bRefreshRequested = false; bRebuildTreeRequested = false; - bExpandAllNodes = false; - - ExpandedItemNames.Empty(); } } @@ -176,16 +168,27 @@ bool SHierarchyView::CanRename() const return SelectedItems.Num() == 1 && SelectedItems[0]->CanRename(); } -void SHierarchyView::TransformWidgetToString(TSharedPtr Item, OUT TArray< FString >& Array) +void SHierarchyView::GetWidgetFilterStrings(TSharedPtr Item, TArray& OutStrings) { - Array.Add( Item->GetText().ToString() ); + Item->GetFilterStrings(OutStrings); } void SHierarchyView::OnSearchChanged(const FText& InFilterText) { bRefreshRequested = true; - bExpandAllNodes = InFilterText.IsEmpty(); - FilterHandler->SetIsEnabled(!InFilterText.IsEmpty()); + const bool bFilteringEnabled = !InFilterText.IsEmpty(); + if (bFilteringEnabled != FilterHandler->GetIsEnabled()) + { + FilterHandler->SetIsEnabled(bFilteringEnabled); + if (bFilteringEnabled) + { + SaveItemsExpansion(); + } + else + { + RestoreItemsExpansion(); + } + } SearchBoxWidgetFilter->SetRawFilterText(InFilterText); SearchBoxPtr->SetError(SearchBoxWidgetFilter->GetFilterErrorText()); } @@ -346,17 +349,23 @@ void SHierarchyView::OnObjectsReplaced(const TMap& Replaceme } } -void SHierarchyView::RestoreExpandedItems() +void SHierarchyView::UpdateItemsExpansionFromModel() { - EExpandBehavior ExpandBehavior = bExpandAllNodes ? EExpandBehavior::AlwaysExpand : EExpandBehavior::RestoreFromPrevious; - - for ( TSharedPtr& Model : RootWidgets ) + for (TSharedPtr& Model : RootWidgets) { - RecursiveExpand(Model, ExpandBehavior); + RecursiveExpand(Model, EExpandBehavior::FromModel); } } -void SHierarchyView::FindExpandedItemNames() +void SHierarchyView::RestoreItemsExpansion() +{ + for (TSharedPtr& Model : RootWidgets) + { + RecursiveExpand(Model, EExpandBehavior::RestoreFromPrevious); + } +} + +void SHierarchyView::SaveItemsExpansion() { ExpandedItemNames.Empty(); @@ -394,11 +403,17 @@ void SHierarchyView::RecursiveExpand(TSharedPtr& Model, EExpand break; case EExpandBehavior::AlwaysExpand: - default: { bShouldExpandItem = true; } break; + + case EExpandBehavior::FromModel: + default: + { + bShouldExpandItem = Model->IsExpanded(); + } + break; } WidgetTreeView->SetItemExpansion(Model, bShouldExpandItem); @@ -410,11 +425,6 @@ void SHierarchyView::RecursiveExpand(TSharedPtr& Model, EExpand { RecursiveExpand(ChildModel, ExpandBehavior); } - - if (Model->IsExpanded()) - { - WidgetTreeView->SetItemExpansion(Model, true); - } } void SHierarchyView::RestoreSelectedItems() diff --git a/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyView.h b/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyView.h index 5f8cc1c0abc6..cca99227a3fd 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyView.h +++ b/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyView.h @@ -88,24 +88,31 @@ private: /** Gets the search text currently being used to filter the list, also used to highlight text */ FText GetSearchText() const; - /** Transforms the widget into a searchable string */ - void TransformWidgetToString(TSharedPtr Widget, OUT TArray< FString >& Array); + /** Gets an array of strings used for filtering/searching the specified widget. */ + void GetWidgetFilterStrings(TSharedPtr Widget, TArray& OutStrings); /** Called when a Blueprint is recompiled and live objects are swapped out for replacements */ void OnObjectsReplaced(const TMap& ReplacementMap); - /** Restores the state of expanded items based on the saved expanded item state, then clears the expanded state cache. */ - void RestoreExpandedItems(); + /** Sets the expansion state of hierarchy view items based on their model. */ + void UpdateItemsExpansionFromModel(); + + /** Stores the names of all currently expanded nodes in the hierarchy view. */ + void SaveItemsExpansion(); + + /** Sets the expansion state of hierarchy view items based on the state saved by SaveItemsExpansion. */ + void RestoreItemsExpansion(); enum class EExpandBehavior : uint8 { NeverExpand, AlwaysExpand, RestoreFromPrevious, + FromModel }; /** Recursively expands the models based on the expansion set. */ - void RecursiveExpand(TSharedPtr& Model, EExpandBehavior ExpandBehavior = EExpandBehavior::AlwaysExpand); + void RecursiveExpand(TSharedPtr& Model, EExpandBehavior ExpandBehavior); /** */ void RestoreSelectedItems(); @@ -116,9 +123,6 @@ private: /** Handler for recursively expanding/collapsing items */ void SetItemExpansionRecursive(TSharedPtr Model, bool bInExpansionState); - /** Find and store the names of all currently expanded nodes in the hierarchy view. Should only be called when rebuilding the tree */ - void FindExpandedItemNames(); - private: /** Cached pointer to the blueprint editor that owns this tree. */ @@ -159,7 +163,4 @@ private: /** Flag to ignore selections while the hierarchy view is updating the selection. */ bool bIsUpdatingSelection; - - /** Should all nodes in the tree be expanded? */ - bool bExpandAllNodes; }; diff --git a/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyViewItem.cpp b/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyViewItem.cpp index 5dceecdb08a3..b8d199cd1161 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyViewItem.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyViewItem.cpp @@ -1093,6 +1093,18 @@ FText FHierarchyWidget::GetLabelToolTipText() const return FText::GetEmpty(); } +void FHierarchyWidget::GetFilterStrings(TArray& OutStrings) const +{ + FHierarchyModel::GetFilterStrings(OutStrings); + + UWidget* WidgetTemplate = Item.GetTemplate(); + if (WidgetTemplate && !WidgetTemplate->IsGeneratedName()) + { + OutStrings.Add(WidgetTemplate->GetClass()->GetName()); + OutStrings.Add(WidgetTemplate->GetClass()->GetDisplayNameText().ToString()); + } +} + const FSlateBrush* FHierarchyWidget::GetImage() const { if (Item.GetTemplate()) diff --git a/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyViewItem.h b/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyViewItem.h index 751e35ea84d3..f7e35fc47ac2 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyViewItem.h +++ b/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyViewItem.h @@ -32,6 +32,9 @@ public: /** @return The tooltip for the tree item label */ virtual FText GetLabelToolTipText() const { return FText::GetEmpty(); } + + /** @param OutStrings - Returns an array of strings used for filtering/searching this item. */ + virtual void GetFilterStrings(TArray& OutStrings) const { OutStrings.Add(GetText().ToString()); } virtual const FSlateBrush* GetImage() const = 0; @@ -196,6 +199,8 @@ public: virtual FText GetText() const override; virtual FText GetImageToolTipText() const override; virtual FText GetLabelToolTipText() const override; + + virtual void GetFilterStrings(TArray& OutStrings) const override; virtual const FSlateBrush* GetImage() const override; diff --git a/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_CreateWidget.cpp b/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_CreateWidget.cpp index 20dbc1d1c7de..76b307d6a0f0 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_CreateWidget.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_CreateWidget.cpp @@ -110,6 +110,9 @@ void UK2Node_CreateWidget::ExpandNode(class FKismetCompilerContext& CompilerCont CallCreateNode->FunctionReference.SetExternalMember(Create_FunctionName, UWidgetBlueprintLibrary::StaticClass()); CallCreateNode->AllocateDefaultPins(); + // store off the class to spawn before we mutate pin connections: + UClass* ClassToSpawn = GetClassToSpawn(); + UEdGraphPin* CallCreateExec = CallCreateNode->GetExecPin(); UEdGraphPin* CallCreateWorldContextPin = CallCreateNode->FindPinChecked(WorldContextObject_ParamName); UEdGraphPin* CallCreateWidgetTypePin = CallCreateNode->FindPinChecked(WidgetType_ParamName); @@ -147,7 +150,7 @@ void UK2Node_CreateWidget::ExpandNode(class FKismetCompilerContext& CompilerCont // create 'set var' nodes // Get 'result' pin from 'begin spawn', this is the actual actor we want to set properties on - UEdGraphPin* LastThen = FKismetCompilerUtilities::GenerateAssignmentNodes(CompilerContext, SourceGraph, CallCreateNode, CreateWidgetNode, CallCreateResult, GetClassToSpawn()); + UEdGraphPin* LastThen = FKismetCompilerUtilities::GenerateAssignmentNodes(CompilerContext, SourceGraph, CallCreateNode, CreateWidgetNode, CallCreateResult, ClassToSpawn); // Move 'then' connection from create widget node to the last 'then' CompilerContext.MovePinLinksToIntermediate(*SpawnNodeThen, *LastThen); diff --git a/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_PlayAnimation.cpp b/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_PlayAnimation.cpp new file mode 100644 index 000000000000..90463ff774fa --- /dev/null +++ b/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_PlayAnimation.cpp @@ -0,0 +1,24 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "K2Node_PlayAnimation.h" +#include "Animation/WidgetAnimationPlayCallbackProxy.h" + +#define LOCTEXT_NAMESPACE "UMG" + +UK2Node_PlayAnimation::UK2Node_PlayAnimation(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + ProxyFactoryFunctionName = GET_FUNCTION_NAME_CHECKED(UWidgetAnimationPlayCallbackProxy, CreatePlayAnimationProxyObject); + ProxyFactoryClass = UWidgetAnimationPlayCallbackProxy::StaticClass(); + ProxyClass = UWidgetAnimationPlayCallbackProxy::StaticClass(); +} + +UK2Node_PlayAnimationTimeRange::UK2Node_PlayAnimationTimeRange(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + ProxyFactoryFunctionName = GET_FUNCTION_NAME_CHECKED(UWidgetAnimationPlayCallbackProxy, CreatePlayAnimationTimeRangeProxyObject); + ProxyFactoryClass = UWidgetAnimationPlayCallbackProxy::StaticClass(); + ProxyClass = UWidgetAnimationPlayCallbackProxy::StaticClass(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_PlayAnimation.h b/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_PlayAnimation.h new file mode 100644 index 000000000000..00a5f6238a94 --- /dev/null +++ b/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_PlayAnimation.h @@ -0,0 +1,18 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "K2Node_BaseAsyncTask.h" +#include "K2Node_PlayAnimation.generated.h" + +UCLASS() +class UMGEDITOR_API UK2Node_PlayAnimation : public UK2Node_BaseAsyncTask +{ + GENERATED_UCLASS_BODY() +}; + +UCLASS() +class UMGEDITOR_API UK2Node_PlayAnimationTimeRange : public UK2Node_BaseAsyncTask +{ + GENERATED_UCLASS_BODY() +}; diff --git a/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteView.cpp b/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteView.cpp index 1e5be29c7bc3..3e4f15698ba6 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteView.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteView.cpp @@ -1,6 +1,7 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "Palette/SPaletteView.h" +#include "Palette/SPaletteViewModel.h" #include "Misc/ConfigCacheIni.h" #include "UObject/UObjectHash.h" #include "UObject/UObjectIterator.h" @@ -24,6 +25,7 @@ #include "AssetRegistryModule.h" #include "Widgets/Input/SSearchBox.h" +#include "Widgets/Input/SCheckBox.h" #include "Settings/ContentBrowserSettings.h" #include "WidgetBlueprintEditorUtils.h" @@ -34,39 +36,66 @@ #define LOCTEXT_NAMESPACE "UMG" -class SPaletteViewItem : public SCompoundWidget +FText SPaletteViewItem::GetFavoriteToggleToolTipText() const { -public: - - SLATE_BEGIN_ARGS(SPaletteViewItem) {} - - /** The current text to highlight */ - SLATE_ATTRIBUTE(FText, HighlightText) - - SLATE_END_ARGS() - - /** - * Constructs this widget - * - * @param InArgs Declaration from which to construct the widget - */ - void Construct(const FArguments& InArgs, TSharedPtr InTemplate) + if (GetFavoritedState() == ECheckBoxState::Checked) { - Template = InTemplate; + return LOCTEXT("Unfavorite", "Click to remove this widget from your favorites."); + } + return LOCTEXT("Favorite", "Click to add this widget to your favorites."); +} - ChildSlot +ECheckBoxState SPaletteViewItem::GetFavoritedState() const +{ + if (WidgetViewModel->IsFavorite()) + { + return ECheckBoxState::Checked; + } + else + { + return ECheckBoxState::Unchecked; + } +} + +void SPaletteViewItem::OnFavoriteToggled(ECheckBoxState InNewState) +{ + if (InNewState == ECheckBoxState::Checked) + { + //Add to favorites + WidgetViewModel->AddToFavorites(); + } + else + { + //Remove from favorites + WidgetViewModel->RemoveFromFavorites(); + } +} + +void SPaletteViewItem::Construct(const FArguments& InArgs, TSharedPtr InWidgetViewModel) +{ + WidgetViewModel = InWidgetViewModel; + + ChildSlot [ SNew(SHorizontalBox) - .Visibility(EVisibility::Visible) - .ToolTip(Template->GetToolTip()) - + .ToolTip(WidgetViewModel->Template->GetToolTip()) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SCheckBox) + .ToolTipText(this, &SPaletteViewItem::GetFavoriteToggleToolTipText) + .IsChecked(this, &SPaletteViewItem::GetFavoritedState) + .OnCheckStateChanged(this, &SPaletteViewItem::OnFavoriteToggled) + .Style(FEditorStyle::Get(), "UMGEditor.Palette.FavoriteToggleStyle") + ] + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SImage) .ColorAndOpacity(FLinearColor(1, 1, 1, 0.5)) - .Image(Template->GetIcon()) + .Image(WidgetViewModel->Template->GetIcon()) ] + SHorizontalBox::Slot() @@ -75,135 +104,34 @@ public: .VAlign(VAlign_Center) [ SNew(STextBlock) - .Text(Template->Name) + .Text(InWidgetViewModel->GetName()) .HighlightText(InArgs._HighlightText) ] ]; - } +} - virtual FReply OnMouseButtonDoubleClick( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent ) override - { - return Template->OnDoubleClicked(); - } - -private: - TSharedPtr Template; -}; - -class FWidgetTemplateViewModel : public FWidgetViewModel +FReply SPaletteViewItem::OnMouseButtonDoubleClick(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent) { -public: - virtual ~FWidgetTemplateViewModel() - { - } - - virtual FText GetName() const override - { - return Template->Name; - } - - virtual bool IsTemplate() const override - { - return true; - } - - virtual FString GetFilterString() const override - { - return Template->Name.ToString(); - } - - virtual TSharedRef BuildRow(const TSharedRef& OwnerTable) override - { - return SNew(STableRow< TSharedPtr >, OwnerTable) - .Padding(2.0f) - .Style(FEditorStyle::Get(), "UMGEditor.PaletteItem") - .OnDragDetected(this, &FWidgetTemplateViewModel::OnDraggingWidgetTemplateItem) - [ - SNew(SPaletteViewItem, Template) - .HighlightText(OwnerView, &SPaletteView::GetSearchText) - ]; - } - - FReply OnDraggingWidgetTemplateItem(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) - { - return FReply::Handled().BeginDragDrop(FWidgetTemplateDragDropOp::New(Template)); - } - - SPaletteView* OwnerView; - TSharedPtr Template; -}; - -class FWidgetHeaderViewModel : public FWidgetViewModel -{ -public: - virtual ~FWidgetHeaderViewModel() - { - } - - virtual FText GetName() const override - { - return GroupName; - } - - virtual bool IsTemplate() const override - { - return false; - } - - virtual FString GetFilterString() const override - { - // Headers should never be included in filtering to avoid showing a header with all of - // it's widgets filtered out, so return an empty filter string. - return TEXT(""); - } - - virtual TSharedRef BuildRow(const TSharedRef& OwnerTable) override - { - return SNew(STableRow< TSharedPtr >, OwnerTable) - .Style( FEditorStyle::Get(), "UMGEditor.PaletteHeader" ) - .Padding(2.0f) - .ShowSelection(false) - [ - SNew(STextBlock) - .Text(GroupName) - .Font(FEditorStyle::GetFontStyle("DetailsView.CategoryFontStyle")) - .ShadowOffset(FVector2D(1.0f, 1.0f)) - ]; - } - - virtual void GetChildren(TArray< TSharedPtr >& OutChildren) override - { - for ( TSharedPtr& Child : Children ) - { - OutChildren.Add(Child); - } - } - - FText GroupName; - TArray< TSharedPtr > Children; + return WidgetViewModel->Template->OnDoubleClicked(); }; void SPaletteView::Construct(const FArguments& InArgs, TSharedPtr InBlueprintEditor) { - // Register for events that can trigger a palette rebuild - GEditor->OnBlueprintReinstanced().AddRaw(this, &SPaletteView::OnBlueprintReinstanced); - FEditorDelegates::OnAssetsDeleted.AddSP(this, &SPaletteView::HandleOnAssetsDeleted); - IHotReloadModule::Get().OnHotReload().AddSP(this, &SPaletteView::HandleOnHotReload); - - // register for any objects replaced - GEditor->OnObjectsReplaced().AddRaw(this, &SPaletteView::OnObjectsReplaced); - BlueprintEditor = InBlueprintEditor; - UWidgetBlueprint* WBP = InBlueprintEditor->GetWidgetBlueprintObj(); - bAllowEditorWidget = WBP ? WBP->AllowEditorWidget() : false; + UBlueprint* BP = InBlueprintEditor->GetBlueprintObj(); + PaletteViewModel = InBlueprintEditor->GetPaletteViewModel(); + + // Register to the update of the viewmodel. + PaletteViewModel->OnUpdating.AddRaw(this, &SPaletteView::OnViewModelUpdating); + PaletteViewModel->OnUpdated.AddRaw(this, &SPaletteView::OnViewModelUpdated); WidgetFilter = MakeShareable(new WidgetViewModelTextFilter( - WidgetViewModelTextFilter::FItemToStringArray::CreateSP(this, &SPaletteView::TransformWidgetViewModelToString))); + WidgetViewModelTextFilter::FItemToStringArray::CreateSP(this, &SPaletteView::GetWidgetFilterStrings))); FilterHandler = MakeShareable(new PaletteFilterHandler()); FilterHandler->SetFilter(WidgetFilter.Get()); - FilterHandler->SetRootItems(&WidgetViewModels, &TreeWidgetViewModels); + FilterHandler->SetRootItems(&(PaletteViewModel->GetWidgetViewModels()), &TreeWidgetViewModels); FilterHandler->SetGetChildrenDelegate(PaletteFilterHandler::FOnGetChildren::CreateRaw(this, &SPaletteView::OnGetChildren)); SAssignNew(WidgetTemplatesView, STreeView< TSharedPtr >) @@ -242,14 +170,16 @@ void SPaletteView::Construct(const FArguments& InArgs, TSharedPtrUpdate(); LoadItemExpansion(); - - bRebuildRequested = false; } SPaletteView::~SPaletteView() { + // Unregister to the update of the viewmodel. + PaletteViewModel->OnUpdating.RemoveAll(this); + PaletteViewModel->OnUpdated.RemoveAll(this); + // If the filter is enabled, disable it before saving the expanded items since // filtering expands all items by default. if (FilterHandler->GetIsEnabled()) @@ -258,12 +188,6 @@ SPaletteView::~SPaletteView() FilterHandler->RefreshAndFilterTree(); } - GEditor->OnBlueprintReinstanced().RemoveAll(this); - FEditorDelegates::OnAssetsDeleted.RemoveAll(this); - IHotReloadModule::Get().OnHotReload().RemoveAll(this); - GEditor->OnObjectsReplaced().RemoveAll( this ); - - SaveItemExpansion(); } @@ -273,12 +197,7 @@ void SPaletteView::OnSearchChanged(const FText& InFilterText) FilterHandler->SetIsEnabled(!InFilterText.IsEmpty()); WidgetFilter->SetRawFilterText(InFilterText); SearchBoxPtr->SetError(WidgetFilter->GetFilterErrorText()); - SearchText = InFilterText; -} - -FText SPaletteView::GetSearchText() const -{ - return SearchText; + PaletteViewModel->SetSearchText(InFilterText); } void SPaletteView::WidgetPalette_OnSelectionChanged(TSharedPtr SelectedItem, ESelectInfo::Type SelectInfo) @@ -339,7 +258,7 @@ TSharedPtr SPaletteView::GetSelectedTemplateWidget() const void SPaletteView::LoadItemExpansion() { // Restore the expansion state of the widget groups. - for ( TSharedPtr& ViewModel : WidgetViewModels ) + for ( TSharedPtr& ViewModel : PaletteViewModel->GetWidgetViewModels()) { bool IsExpanded; if ( GConfig->GetBool(TEXT("WidgetTemplatesExpanded"), *ViewModel->GetName().ToString(), IsExpanded, GEditorPerProjectIni) && IsExpanded ) @@ -352,285 +271,13 @@ void SPaletteView::LoadItemExpansion() void SPaletteView::SaveItemExpansion() { // Restore the expansion state of the widget groups. - for ( TSharedPtr& ViewModel : WidgetViewModels ) + for ( TSharedPtr& ViewModel : PaletteViewModel->GetWidgetViewModels() ) { const bool IsExpanded = WidgetTemplatesView->IsItemExpanded(ViewModel); GConfig->SetBool(TEXT("WidgetTemplatesExpanded"), *ViewModel->GetName().ToString(), IsExpanded, GEditorPerProjectIni); } } -UWidgetBlueprint* SPaletteView::GetBlueprint() const -{ - if ( BlueprintEditor.IsValid() ) - { - UBlueprint* BP = BlueprintEditor.Pin()->GetBlueprintObj(); - return Cast(BP); - } - - return NULL; -} - -void SPaletteView::BuildWidgetList() -{ - // Clear the current list of view models and categories - WidgetViewModels.Reset(); - WidgetTemplateCategories.Reset(); - - // Generate a list of templates - BuildClassWidgetList(); - BuildSpecialWidgetList(); - - // For each entry in the category create a view model for the widget template - for ( auto& Entry : WidgetTemplateCategories ) - { - TSharedPtr Header = MakeShareable(new FWidgetHeaderViewModel()); - Header->GroupName = FText::FromString(Entry.Key); - - for ( auto& Template : Entry.Value ) - { - TSharedPtr TemplateViewModel = MakeShareable(new FWidgetTemplateViewModel()); - TemplateViewModel->Template = Template; - TemplateViewModel->OwnerView = this; - Header->Children.Add(TemplateViewModel); - } - - Header->Children.Sort([] (TSharedPtr L, TSharedPtr R) { return R->GetName().CompareTo(L->GetName()) > 0; }); - - WidgetViewModels.Add(Header); - } - - // Sort the view models by name - WidgetViewModels.Sort([] (TSharedPtr L, TSharedPtr R) { return R->GetName().CompareTo(L->GetName()) > 0; }); - - // Take the Advanced Section, and put it at the end. - TSharedPtr* advancedSectionPtr = WidgetViewModels.FindByPredicate([](TSharedPtr widget) {return widget->GetName().CompareTo(LOCTEXT("Advanced", "Advanced")) == 0; }); - if (advancedSectionPtr) - { - TSharedPtr advancedSection = *advancedSectionPtr; - WidgetViewModels.Remove(advancedSection); - WidgetViewModels.Push(advancedSection); - } -} - -void SPaletteView::BuildClassWidgetList() -{ - static const FName DevelopmentStatusKey(TEXT("DevelopmentStatus")); - - TMap> LoadedWidgetBlueprintClassesByName; - - auto ActiveWidgetBlueprintClass = GetBlueprint()->GeneratedClass; - FName ActiveWidgetBlueprintClassName = ActiveWidgetBlueprintClass->GetFName(); - - TArray WidgetClassesToHide = GetDefault()->WidgetClassesToHide; - - // Locate all UWidget classes from code and loaded widget BPs - for (TObjectIterator ClassIt; ClassIt; ++ClassIt) - { - UClass* WidgetClass = *ClassIt; - - if (!FWidgetBlueprintEditorUtils::IsUsableWidgetClass(WidgetClass)) - { - continue; - } - - // Initialize AssetData for checking PackagePath - FAssetData WidgetAssetData = FAssetData(WidgetClass); - - // Excludes engine content if user sets it to false - if (!GetDefault()->GetDisplayEngineFolder() || !GetDefault()->bShowWidgetsFromEngineContent) - { - if (WidgetAssetData.PackagePath.ToString().Find(TEXT("/Engine")) == 0) - { - continue; - } - } - - // Excludes developer content if user sets it to false - if (!GetDefault()->GetDisplayDevelopersFolder() || !GetDefault()->bShowWidgetsFromDeveloperContent) - { - if (WidgetAssetData.PackagePath.ToString().Find(TEXT("/Game/Developers")) == 0) - { - continue; - } - } - - // Excludes this widget if it is on the hide list - bool bIsOnList = false; - for (FSoftClassPath Widget : WidgetClassesToHide) - { - if (WidgetAssetData.ObjectPath.ToString().Find(Widget.ToString()) == 0) - { - bIsOnList = true; - break; - } - } - if (bIsOnList) - { - continue; - } - - const bool bIsSameClass = WidgetClass->GetFName() == ActiveWidgetBlueprintClassName; - - // Check that the asset that generated this class is valid (necessary b/c of a larger issue wherein force delete does not wipe the generated class object) - if ( bIsSameClass ) - { - continue; - } - - if (!bAllowEditorWidget) - { - if (WidgetClass->GetOutermost()->IsEditorOnly()) - { - continue; - } - } - - if (WidgetClass->IsChildOf(UUserWidget::StaticClass())) - { - if ( WidgetClass->ClassGeneratedBy ) - { - // Track the widget blueprint classes that are already loaded - LoadedWidgetBlueprintClassesByName.Add(WidgetClass->ClassGeneratedBy->GetFName()) = WidgetClass; - } - } - else - { - TSharedPtr Template = MakeShareable(new FWidgetTemplateClass(WidgetClass)); - - AddWidgetTemplate(Template); - } - - //TODO UMG does not prevent deep nested circular references - } - - // Locate all widget BP assets (include unloaded) - const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); - TArray AllBPsAssetData; - AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetFName(), AllBPsAssetData, true); - - for (FAssetData& BPAssetData : AllBPsAssetData) - { - // Blueprints get the class type actions for their parent native class - this avoids us having to load the blueprint - UClass* ParentClass = nullptr; - FString ParentClassName; - if (!BPAssetData.GetTagValue(FBlueprintTags::NativeParentClassPath, ParentClassName)) - { - BPAssetData.GetTagValue(FBlueprintTags::ParentClassPath, ParentClassName); - } - if (!ParentClassName.IsEmpty()) - { - UObject* Outer = nullptr; - ResolveName(Outer, ParentClassName, false, false); - ParentClass = FindObject(ANY_PACKAGE, *ParentClassName); - // UUserWidgets have their own loading section, and we don't want to process any blueprints that don't have UWidget parents - if (!ParentClass->IsChildOf(UWidget::StaticClass()) || ParentClass->IsChildOf(UUserWidget::StaticClass())) - { - continue; - } - } - - if (!FilterAssetData(BPAssetData)) - { - // If this object isn't currently loaded, add it to the palette view - if (BPAssetData.ToSoftObjectPath().ResolveObject() == nullptr) - { - auto Template = MakeShareable(new FWidgetTemplateClass(BPAssetData, nullptr)); - AddWidgetTemplate(Template); - } - } - } - - TArray AllWidgetBPsAssetData; - AssetRegistryModule.Get().GetAssetsByClass(UWidgetBlueprint::StaticClass()->GetFName(), AllWidgetBPsAssetData, true); - - FName ActiveWidgetBlueprintName = ActiveWidgetBlueprintClass->ClassGeneratedBy->GetFName(); - for (FAssetData& WidgetBPAssetData : AllWidgetBPsAssetData) - { - // Excludes the blueprint you're currently in - if (WidgetBPAssetData.AssetName == ActiveWidgetBlueprintName) - { - continue; - } - - if (!FilterAssetData(WidgetBPAssetData)) - { - // Excludes this widget if it is on the hide list - bool bIsOnList = false; - for (FSoftClassPath Widget : WidgetClassesToHide) - { - if (Widget.ToString().Find(WidgetBPAssetData.ObjectPath.ToString()) == 0) - { - bIsOnList = true; - break; - } - } - if (bIsOnList) - { - continue; - } - - // If the blueprint generated class was found earlier, pass it to the template - TSubclassOf WidgetBPClass = nullptr; - auto LoadedWidgetBPClass = LoadedWidgetBlueprintClassesByName.Find(WidgetBPAssetData.AssetName); - if (LoadedWidgetBPClass) - { - WidgetBPClass = *LoadedWidgetBPClass; - } - - auto Template = MakeShareable(new FWidgetTemplateBlueprintClass(WidgetBPAssetData, WidgetBPClass)); - - AddWidgetTemplate(Template); - } - } -} - -bool SPaletteView::FilterAssetData(FAssetData &InAssetData) -{ - // Excludes engine content if user sets it to false - if (!GetDefault()->GetDisplayEngineFolder() || !GetDefault()->bShowWidgetsFromEngineContent) - { - if (InAssetData.PackagePath.ToString().Find(TEXT("/Engine")) == 0) - { - return true; - } - } - - // Excludes developer content if user sets it to false - if (!GetDefault()->GetDisplayDevelopersFolder() || !GetDefault()->bShowWidgetsFromDeveloperContent) - { - if (InAssetData.PackagePath.ToString().Find(TEXT("/Game/Developers")) == 0) - { - return true; - } - } - return false; -} - -void SPaletteView::BuildSpecialWidgetList() -{ - //AddWidgetTemplate(MakeShareable(new FWidgetTemplateButton())); - //AddWidgetTemplate(MakeShareable(new FWidgetTemplateCheckBox())); - - //TODO UMG Make this pluggable. -} - -void SPaletteView::AddWidgetTemplate(TSharedPtr Template) -{ - FString Category = Template->GetCategory().ToString(); - - // Hide user specific categories - TArray CategoriesToHide = GetDefault()->CategoriesToHide; - for (FString CategoryName : CategoriesToHide) - { - if (Category == CategoryName) - { - return; - } - } - WidgetTemplateArray& Group = WidgetTemplateCategories.FindOrAdd(Category); - Group.Add(Template); -} - void SPaletteView::OnGetChildren(TSharedPtr Item, TArray< TSharedPtr >& Children) { return Item->GetChildren(Children); @@ -641,31 +288,32 @@ TSharedRef SPaletteView::OnGenerateWidgetTemplateItem(TSharedPtrBuildRow(OwnerTable); } -void SPaletteView::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) +void SPaletteView::OnViewModelUpdating() { - if ( bRebuildRequested ) + // Save the old expanded items temporarily + WidgetTemplatesView->GetExpandedItems(ExpandedItems); +} + +void SPaletteView::OnViewModelUpdated() +{ + bRefreshRequested = true; + + // Restore the expansion state + for (TSharedPtr& ExpandedItem : ExpandedItems) { - bRebuildRequested = false; - - // Save the old expanded items temporarily - TSet> ExpandedItems; - WidgetTemplatesView->GetExpandedItems(ExpandedItems); - - BuildWidgetList(); - - // Restore the expansion state - for ( TSharedPtr& ExpandedItem : ExpandedItems ) + for (TSharedPtr& ViewModel : PaletteViewModel->GetWidgetViewModels()) { - for ( TSharedPtr& ViewModel : WidgetViewModels ) + if (ViewModel->GetName().EqualTo(ExpandedItem->GetName()) || ViewModel->ShouldForceExpansion()) { - if ( ViewModel->GetName().EqualTo(ExpandedItem->GetName()) ) - { - WidgetTemplatesView->SetItemExpansion(ViewModel, true); - } + WidgetTemplatesView->SetItemExpansion(ViewModel, true); } } } + ExpandedItems.Reset(); +} +void SPaletteView::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) +{ if (bRefreshRequested) { bRefreshRequested = false; @@ -673,40 +321,9 @@ void SPaletteView::Tick( const FGeometry& AllottedGeometry, const double InCurre } } -void SPaletteView::TransformWidgetViewModelToString(TSharedPtr WidgetViewModel, OUT TArray< FString >& Array) +void SPaletteView::GetWidgetFilterStrings(TSharedPtr WidgetViewModel, TArray& OutStrings) { - Array.Add(WidgetViewModel->GetFilterString()); -} - -void SPaletteView::OnObjectsReplaced(const TMap& ReplacementMap) -{ - //bRefreshRequested = true; - //bRebuildRequested = true; -} - -void SPaletteView::OnBlueprintReinstanced() -{ - bRebuildRequested = true; - bRefreshRequested = true; -} - -void SPaletteView::HandleOnHotReload(bool bWasTriggeredAutomatically) -{ - bRebuildRequested = true; - bRefreshRequested = true; -} - -void SPaletteView::HandleOnAssetsDeleted(const TArray& DeletedAssetClasses) -{ - for (auto DeletedAssetClass : DeletedAssetClasses) - { - if (DeletedAssetClass->IsChildOf(UWidgetBlueprint::StaticClass())) - { - bRebuildRequested = true; - bRefreshRequested = true; - } - } - + WidgetViewModel->GetFilterStrings(OutStrings); } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteView.h b/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteView.h index 1aa3e955b9a3..5f759606a6e6 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteView.h +++ b/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteView.h @@ -5,8 +5,6 @@ #include "CoreMinimal.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SCompoundWidget.h" -#include "Widgets/Views/STableViewBase.h" -#include "Widgets/Views/STableRow.h" #include "WidgetBlueprintEditor.h" #include "Misc/TextFilter.h" #include "Widgets/Views/STreeView.h" @@ -14,28 +12,61 @@ class FWidgetTemplate; class UWidgetBlueprint; +class SPaletteView; +class FPaletteViewModel; +class FWidgetViewModel; +class FWidgetTemplateViewModel; -/** View model for the items in the widget template list */ -class FWidgetViewModel : public TSharedFromThis +/** Widget used to show a single row of the Palette and Palette favorite panel. */ +class SPaletteViewItem : public SCompoundWidget { public: - virtual ~FWidgetViewModel() { } - virtual FText GetName() const = 0; + SLATE_BEGIN_ARGS(SPaletteViewItem) {} - virtual bool IsTemplate() const = 0; + /** The current text to highlight */ + SLATE_ATTRIBUTE(FText, HighlightText) - /** Get the string which should be used for filtering the item. */ - virtual FString GetFilterString() const = 0; + SLATE_END_ARGS() - virtual TSharedRef BuildRow(const TSharedRef& OwnerTable) = 0; + /** + * Constructs this widget + * + * @param InArgs Declaration from which to construct the widget + */ + void Construct(const FArguments& InArgs, TSharedPtr InWidgetViewModel); - virtual void GetChildren(TArray< TSharedPtr >& OutChildren) - { - } +private: + /** + * Retrieves tooltip that describes the current favorited state + * + * @return Text describing what this toggle will do when you click on it. + */ + FText GetFavoriteToggleToolTipText() const; + + /** + * Checks on the associated action's favorite state, and returns a + * corresponding checkbox state to match. + * + * @return ECheckBoxState::Checked if the associated action is already favorited, ECheckBoxState::Unchecked if not. + */ + ECheckBoxState GetFavoritedState() const; + + /** + * Triggers when the user clicks this toggle, adds or removes the associated + * action to the user's favorites. + * + * @param InNewState The new state that the user set the checkbox to. + */ + void OnFavoriteToggled(ECheckBoxState InNewState); + + virtual FReply OnMouseButtonDoubleClick(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent) override; + +private: + TSharedPtr WidgetViewModel; }; -/** */ +/** */ class SPaletteView : public SCompoundWidget { public: @@ -60,12 +91,6 @@ public: TSharedPtr GetSelectedTemplateWidget() const; private: - UWidgetBlueprint* GetBlueprint() const; - - void BuildWidgetList(); - void BuildClassWidgetList(); - bool FilterAssetData(FAssetData &BPAssetData); - void BuildSpecialWidgetList(); void OnGetChildren(TSharedPtr Item, TArray< TSharedPtr >& Children); TSharedRef OnGenerateWidgetTemplateItem(TSharedPtr Item, const TSharedRef& OwnerTable); @@ -73,41 +98,27 @@ private: /** Called when the filter text is changed. */ void OnSearchChanged(const FText& InFilterText); + void OnViewModelUpdating(); + void OnViewModelUpdated(); + private: + /** Load the expansion state for the TreeView */ void LoadItemExpansion(); + + /** Save the expansion state for the TreeView */ void SaveItemExpansion(); - /** Called when a Blueprint is recompiled and live objects are swapped out for replacements */ - void OnObjectsReplaced(const TMap& ReplacementMap); - - void AddWidgetTemplate(TSharedPtr Template); - - /** Transforms the widget view model into a searchable string. */ - void TransformWidgetViewModelToString(TSharedPtr WidgetViewModel, OUT TArray< FString >& Array); - - /** Requests a rebuild of the widget list if a widget blueprint was compiled */ - void OnBlueprintReinstanced(); - - /** Requests a rebuild of the widget list */ - void HandleOnHotReload(bool bWasTriggeredAutomatically); - - /** Requests a rebuild of the widget list if a widget blueprint was deleted */ - void HandleOnAssetsDeleted(const TArray& DeletedAssetClasses); + /** Gets an array of strings used for filtering/searching the specified widget. */ + void GetWidgetFilterStrings(TSharedPtr WidgetViewModel, TArray& OutStrings); TWeakPtr BlueprintEditor; + TSharedPtr PaletteViewModel; /** Handles filtering the palette based on an IFilter. */ typedef TreeFilterHandler> PaletteFilterHandler; TSharedPtr FilterHandler; - typedef TArray< TSharedPtr > WidgetTemplateArray; - TMap< FString, WidgetTemplateArray > WidgetTemplateCategories; - - typedef TArray< TSharedPtr > ViewModelsArray; - - /** The source root view models for the tree. */ - ViewModelsArray WidgetViewModels; - + typedef TArray> ViewModelsArray; /** The root view models which are actually displayed by the TreeView which will be managed by the TreeFilterHandler. */ ViewModelsArray TreeWidgetViewModels; @@ -119,11 +130,11 @@ private: /** The filter instance which is used by the TreeFilterHandler to filter the TreeView. */ TSharedPtr WidgetFilter; - bool bRefreshRequested; - FText SearchText; + /** Expended Items in the Tree view */ + TSet> ExpandedItems; - /** Controls rebuilding the list of spawnable widgets */ - bool bRebuildRequested; + /** Set to true to force a refresh of the treeview */ + bool bRefreshRequested; /** Are blutility widget supported. */ bool bAllowEditorWidget; diff --git a/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteViewModel.cpp b/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteViewModel.cpp new file mode 100644 index 000000000000..ed7dfe85c84f --- /dev/null +++ b/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteViewModel.cpp @@ -0,0 +1,489 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Palette/SPaletteViewModel.h" +#include "Palette/SPaletteView.h" +#include "Widgets/Views/STableViewBase.h" +#include "Widgets/Views/STableRow.h" +#include "WidgetBlueprint.h" +#include "Editor.h" + +#if WITH_EDITOR + #include "EditorStyleSet.h" +#endif // WITH_EDITOR + +#include "DragDrop/WidgetTemplateDragDropOp.h" + +#include "Templates/WidgetTemplateClass.h" +#include "Templates/WidgetTemplateBlueprintClass.h" + +#include "Developer/HotReload/Public/IHotReload.h" + +#include "AssetRegistryModule.h" +#include "WidgetBlueprintEditorUtils.h" + +#include "Settings/ContentBrowserSettings.h" +#include "Settings/WidgetDesignerSettings.h" +#include "UMGEditorProjectSettings.h" +#include "WidgetPaletteFavorites.h" + +#define LOCTEXT_NAMESPACE "UMG" + +FWidgetTemplateViewModel::FWidgetTemplateViewModel() + : PaletteViewModel(nullptr), + bIsFavorite(false) +{ +} + +FText FWidgetTemplateViewModel::GetName() const +{ + return Template->Name; +} + +bool FWidgetTemplateViewModel::IsTemplate() const +{ + return true; +} + +void FWidgetTemplateViewModel::GetFilterStrings(TArray& OutStrings) const +{ + Template->GetFilterStrings(OutStrings); +} + +TSharedRef FWidgetTemplateViewModel::BuildRow(const TSharedRef& OwnerTable) +{ + return SNew(STableRow>, OwnerTable) + .Padding(2.0f) + .Style(FEditorStyle::Get(), "UMGEditor.PaletteItem") + .OnDragDetected(this, &FWidgetTemplateViewModel::OnDraggingWidgetTemplateItem) + [ + SNew(SPaletteViewItem, SharedThis(this)) + .HighlightText(PaletteViewModel, &FPaletteViewModel::GetSearchText) + ]; +} + +FReply FWidgetTemplateViewModel::OnDraggingWidgetTemplateItem(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + return FReply::Handled().BeginDragDrop(FWidgetTemplateDragDropOp::New(Template)); +} + +void FWidgetTemplateViewModel::AddToFavorites() +{ + bIsFavorite = true; + PaletteViewModel->AddToFavorites(this); +} + +void FWidgetTemplateViewModel::RemoveFromFavorites() +{ + bIsFavorite = false; + PaletteViewModel->RemoveFromFavorites(this); +} + +TSharedRef FWidgetHeaderViewModel::BuildRow(const TSharedRef& OwnerTable) +{ + return SNew(STableRow>, OwnerTable) + .Style(FEditorStyle::Get(), "UMGEditor.PaletteHeader") + .Padding(2.0f) + .ShowSelection(false) + [ + SNew(STextBlock) + .Text(GroupName) + .Font(FEditorStyle::GetFontStyle("DetailsView.CategoryFontStyle")) + .ShadowOffset(FVector2D(1.0f, 1.0f)) + ]; +} + +void FWidgetHeaderViewModel::GetChildren(TArray< TSharedPtr >& OutChildren) +{ + for (TSharedPtr& Child : Children) + { + OutChildren.Add(Child); + } +} + +FPaletteViewModel::FPaletteViewModel(TSharedPtr InBlueprintEditor) + : bRebuildRequested(true) +{ + BlueprintEditor = InBlueprintEditor; + + FavoriteHeader = MakeShareable(new FWidgetHeaderViewModel()); + FavoriteHeader->GroupName = LOCTEXT("Favorites", "Favorites"); +} + +void FPaletteViewModel::RegisterToEvents() +{ + // Register for events that can trigger a palette rebuild + GEditor->OnBlueprintReinstanced().AddRaw(this, &FPaletteViewModel::OnBlueprintReinstanced); + FEditorDelegates::OnAssetsDeleted.AddSP(this, &FPaletteViewModel::HandleOnAssetsDeleted); + IHotReloadModule::Get().OnHotReload().AddSP(this, &FPaletteViewModel::HandleOnHotReload); + + // register for any objects replaced + GEditor->OnObjectsReplaced().AddRaw(this, &FPaletteViewModel::OnObjectsReplaced); + + // Register for favorite list update to handle the case where a favorite is added in another window of the UMG Designer + UWidgetPaletteFavorites* Favorites = GetDefault()->Favorites; + Favorites->OnFavoritesUpdated.AddSP(this, &FPaletteViewModel::OnFavoritesUpdated); +} + +FPaletteViewModel::~FPaletteViewModel() +{ + GEditor->OnBlueprintReinstanced().RemoveAll(this); + FEditorDelegates::OnAssetsDeleted.RemoveAll(this); + IHotReloadModule::Get().OnHotReload().RemoveAll(this); + GEditor->OnObjectsReplaced().RemoveAll(this); + + UWidgetPaletteFavorites* Favorites = GetDefault()->Favorites; + Favorites->OnFavoritesUpdated.RemoveAll(this); +} + +void FPaletteViewModel::AddToFavorites(const FWidgetTemplateViewModel* WidgetTemplateViewModel) +{ + UWidgetPaletteFavorites* Favorites = GetDefault()->Favorites; + Favorites->Add(WidgetTemplateViewModel->GetName().ToString()); +} + +void FPaletteViewModel::RemoveFromFavorites(const FWidgetTemplateViewModel* WidgetTemplateViewModel) +{ + UWidgetPaletteFavorites* Favorites = GetDefault()->Favorites; + Favorites->Remove(WidgetTemplateViewModel->GetName().ToString()); +} + +void FPaletteViewModel::Update() +{ + if (bRebuildRequested) + { + OnUpdating.Broadcast(); + BuildWidgetList(); + bRebuildRequested = false; + OnUpdated.Broadcast(); + } +} + + +UWidgetBlueprint* FPaletteViewModel::GetBlueprint() const +{ + if (BlueprintEditor.IsValid()) + { + UBlueprint* BP = BlueprintEditor.Pin()->GetBlueprintObj(); + return Cast(BP); + } + + return NULL; +} + +void FPaletteViewModel::BuildWidgetList() +{ + // Clear the current list of view models and categories + WidgetViewModels.Reset(); + WidgetTemplateCategories.Reset(); + + // Generate a list of templates + BuildClassWidgetList(); + + // Clear the Favorite section + bool bHasFavorites = FavoriteHeader->Children.Num() != 0; + FavoriteHeader->Children.Reset(); + + // Copy of the list of favorites to be able to do some cleanup in the real list + UWidgetPaletteFavorites* FavoritesPalette = GetDefault()->Favorites; + TArray FavoritesList = FavoritesPalette->GetFavorites(); + + // For each entry in the category create a view model for the widget template + for ( auto& Entry : WidgetTemplateCategories ) + { + TSharedPtr Header = MakeShareable(new FWidgetHeaderViewModel()); + Header->GroupName = FText::FromString(Entry.Key); + + for ( auto& Template : Entry.Value ) + { + TSharedPtr TemplateViewModel = MakeShareable(new FWidgetTemplateViewModel()); + TemplateViewModel->Template = Template; + TemplateViewModel->PaletteViewModel = this; + Header->Children.Add(TemplateViewModel); + + // If it's a favorite, we also add it to the Favorite section + int32 index = FavoritesList.Find(Template->Name.ToString()); + if (index != INDEX_NONE) + { + TemplateViewModel->SetFavorite(); + + // We have to create a second copy of the ViewModel for the treeview has it doesn't support to have the same element twice. + TSharedPtr FavoriteTemplateViewModel = MakeShareable(new FWidgetTemplateViewModel()); + FavoriteTemplateViewModel->Template = Template; + FavoriteTemplateViewModel->PaletteViewModel = this; + FavoriteTemplateViewModel->SetFavorite(); + + FavoriteHeader->Children.Add(FavoriteTemplateViewModel); + + // Remove the favorite from the temporary list + FavoritesList.RemoveAt(index); + } + + } + + Header->Children.Sort([] (TSharedPtr L, TSharedPtr R) { return R->GetName().CompareTo(L->GetName()) > 0; }); + + WidgetViewModels.Add(Header); + } + + // Remove all Favorites that may be left in the list.Typically happening when the list of favorite contains widget that were deleted since the last opening. + for (const FString& favoriteName : FavoritesList) + { + FavoritesPalette->Remove(favoriteName); + } + + // Sort the view models by name + WidgetViewModels.Sort([] (TSharedPtr L, TSharedPtr R) { return R->GetName().CompareTo(L->GetName()) > 0; }); + + // Add the Favorite section at the top + if (FavoriteHeader->Children.Num() != 0) + { + // We force expansion of the favorite header when we add favorites for the first time. + FavoriteHeader->SetForceExpansion(!bHasFavorites); + FavoriteHeader->Children.Sort([](TSharedPtr L, TSharedPtr R) { return R->GetName().CompareTo(L->GetName()) > 0; }); + WidgetViewModels.Insert(FavoriteHeader, 0); + } + + // Take the Advanced Section, and put it at the end. + TSharedPtr* advancedSectionPtr = WidgetViewModels.FindByPredicate([](TSharedPtr widget) {return widget->GetName().CompareTo(LOCTEXT("Advanced", "Advanced")) == 0; }); + if (advancedSectionPtr) + { + TSharedPtr advancedSection = *advancedSectionPtr; + WidgetViewModels.Remove(advancedSection); + WidgetViewModels.Push(advancedSection); + } +} + +void FPaletteViewModel::BuildClassWidgetList() +{ + static const FName DevelopmentStatusKey(TEXT("DevelopmentStatus")); + + TMap> LoadedWidgetBlueprintClassesByName; + + auto ActiveWidgetBlueprintClass = GetBlueprint()->GeneratedClass; + FName ActiveWidgetBlueprintClassName = ActiveWidgetBlueprintClass->GetFName(); + + TArray WidgetClassesToHide = GetDefault()->WidgetClassesToHide; + + // Locate all UWidget classes from code and loaded widget BPs + for (TObjectIterator ClassIt; ClassIt; ++ClassIt) + { + UClass* WidgetClass = *ClassIt; + + if (!FWidgetBlueprintEditorUtils::IsUsableWidgetClass(WidgetClass)) + { + continue; + } + + // Initialize AssetData for checking PackagePath + FAssetData WidgetAssetData = FAssetData(WidgetClass); + + // Excludes engine content if user sets it to false + if (!GetDefault()->GetDisplayEngineFolder() || !GetDefault()->bShowWidgetsFromEngineContent) + { + if (WidgetAssetData.PackagePath.ToString().Find(TEXT("/Engine")) == 0) + { + continue; + } + } + + // Excludes developer content if user sets it to false + if (!GetDefault()->GetDisplayDevelopersFolder() || !GetDefault()->bShowWidgetsFromDeveloperContent) + { + if (WidgetAssetData.PackagePath.ToString().Find(TEXT("/Game/Developers")) == 0) + { + continue; + } + } + + // Excludes this widget if it is on the hide list + bool bIsOnList = false; + for (FSoftClassPath Widget : WidgetClassesToHide) + { + if (WidgetAssetData.ObjectPath.ToString().Find(Widget.ToString()) == 0) + { + bIsOnList = true; + break; + } + } + if (bIsOnList) + { + continue; + } + + const bool bIsSameClass = WidgetClass->GetFName() == ActiveWidgetBlueprintClassName; + + // Check that the asset that generated this class is valid (necessary b/c of a larger issue wherein force delete does not wipe the generated class object) + if ( bIsSameClass ) + { + continue; + } + + if (WidgetClass->IsChildOf(UUserWidget::StaticClass())) + { + if ( WidgetClass->ClassGeneratedBy ) + { + // Track the widget blueprint classes that are already loaded + LoadedWidgetBlueprintClassesByName.Add(WidgetClass->ClassGeneratedBy->GetFName()) = WidgetClass; + } + } + else + { + TSharedPtr Template = MakeShareable(new FWidgetTemplateClass(WidgetClass)); + + AddWidgetTemplate(Template); + } + + //TODO UMG does not prevent deep nested circular references + } + + // Locate all widget BP assets (include unloaded) + const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + TArray AllBPsAssetData; + AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetFName(), AllBPsAssetData, true); + + for (FAssetData& BPAssetData : AllBPsAssetData) + { + // Blueprints get the class type actions for their parent native class - this avoids us having to load the blueprint + UClass* ParentClass = nullptr; + FString ParentClassName; + if (!BPAssetData.GetTagValue(FBlueprintTags::NativeParentClassPath, ParentClassName)) + { + BPAssetData.GetTagValue(FBlueprintTags::ParentClassPath, ParentClassName); + } + if (!ParentClassName.IsEmpty()) + { + UObject* Outer = nullptr; + ResolveName(Outer, ParentClassName, false, false); + ParentClass = FindObject(ANY_PACKAGE, *ParentClassName); + // UUserWidgets have their own loading section, and we don't want to process any blueprints that don't have UWidget parents + if (!ParentClass->IsChildOf(UWidget::StaticClass()) || ParentClass->IsChildOf(UUserWidget::StaticClass())) + { + continue; + } + } + + if (!FilterAssetData(BPAssetData)) + { + // If this object isn't currently loaded, add it to the palette view + if (BPAssetData.ToSoftObjectPath().ResolveObject() == nullptr) + { + auto Template = MakeShareable(new FWidgetTemplateClass(BPAssetData, nullptr)); + AddWidgetTemplate(Template); + } + } + } + + TArray AllWidgetBPsAssetData; + AssetRegistryModule.Get().GetAssetsByClass(UWidgetBlueprint::StaticClass()->GetFName(), AllWidgetBPsAssetData, true); + + FName ActiveWidgetBlueprintName = ActiveWidgetBlueprintClass->ClassGeneratedBy->GetFName(); + for (FAssetData& WidgetBPAssetData : AllWidgetBPsAssetData) + { + // Excludes the blueprint you're currently in + if (WidgetBPAssetData.AssetName == ActiveWidgetBlueprintName) + { + continue; + } + + if (!FilterAssetData(WidgetBPAssetData)) + { + // Excludes this widget if it is on the hide list + bool bIsOnList = false; + for (FSoftClassPath Widget : WidgetClassesToHide) + { + if (Widget.ToString().Find(WidgetBPAssetData.ObjectPath.ToString()) == 0) + { + bIsOnList = true; + break; + } + } + if (bIsOnList) + { + continue; + } + + // If the blueprint generated class was found earlier, pass it to the template + TSubclassOf WidgetBPClass = nullptr; + auto LoadedWidgetBPClass = LoadedWidgetBlueprintClassesByName.Find(WidgetBPAssetData.AssetName); + if (LoadedWidgetBPClass) + { + WidgetBPClass = *LoadedWidgetBPClass; + } + + auto Template = MakeShareable(new FWidgetTemplateBlueprintClass(WidgetBPAssetData, WidgetBPClass)); + + AddWidgetTemplate(Template); + } + } +} + +bool FPaletteViewModel::FilterAssetData(FAssetData &InAssetData) +{ + // Excludes engine content if user sets it to false + if (!GetDefault()->GetDisplayEngineFolder() || !GetDefault()->bShowWidgetsFromEngineContent) + { + if (InAssetData.PackagePath.ToString().Find(TEXT("/Engine")) == 0) + { + return true; + } + } + + // Excludes developer content if user sets it to false + if (!GetDefault()->GetDisplayDevelopersFolder() || !GetDefault()->bShowWidgetsFromDeveloperContent) + { + if (InAssetData.PackagePath.ToString().Find(TEXT("/Game/Developers")) == 0) + { + return true; + } + } + return false; +} + +void FPaletteViewModel::AddWidgetTemplate(TSharedPtr Template) +{ + FString Category = Template->GetCategory().ToString(); + + // Hide user specific categories + TArray CategoriesToHide = GetDefault()->CategoriesToHide; + for (FString CategoryName : CategoriesToHide) + { + if (Category == CategoryName) + { + return; + } + } + WidgetTemplateArray& Group = WidgetTemplateCategories.FindOrAdd(Category); + Group.Add(Template); +} + +void FPaletteViewModel::OnObjectsReplaced(const TMap& ReplacementMap) +{ +} + +void FPaletteViewModel::OnBlueprintReinstanced() +{ + bRebuildRequested = true; +} + +void FPaletteViewModel::OnFavoritesUpdated() +{ + bRebuildRequested = true; +} + +void FPaletteViewModel::HandleOnHotReload(bool bWasTriggeredAutomatically) +{ + bRebuildRequested = true; +} + +void FPaletteViewModel::HandleOnAssetsDeleted(const TArray& DeletedAssetClasses) +{ + for (auto DeletedAssetClass : DeletedAssetClasses) + { + if (DeletedAssetClass->IsChildOf(UWidgetBlueprint::StaticClass())) + { + bRebuildRequested = true; + } + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteViewModel.h b/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteViewModel.h new file mode 100644 index 000000000000..0b72dd36368e --- /dev/null +++ b/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteViewModel.h @@ -0,0 +1,200 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "WidgetBlueprintEditor.h" +#include "AssetData.h" + +#include "Widgets/Views/STableViewBase.h" +#include "Widgets/Views/STableRow.h" + +class FWidgetTemplate; +class FWidgetBlueprintEditor; +class UWidgetBlueprint; +class SPaletteView; + +/** View model for the items in the widget template list */ +class FWidgetViewModel : public TSharedFromThis +{ +public: + virtual ~FWidgetViewModel() { } + + virtual FText GetName() const = 0; + + virtual bool IsTemplate() const = 0; + + /** @param OutStrings - Returns an array of strings used for filtering/searching this item. */ + virtual void GetFilterStrings(TArray& OutStrings) const = 0; + + virtual TSharedRef BuildRow(const TSharedRef& OwnerTable) = 0; + + virtual void GetChildren(TArray< TSharedPtr >& OutChildren) + { + } + + /** Return true if the widget is a favorite */ + virtual bool IsFavorite() const { return false; } + + /** Set the favorite flag */ + virtual void SetFavorite() + { + } + + virtual bool ShouldForceExpansion() const { return false; } +}; + +class FWidgetTemplateViewModel : public FWidgetViewModel +{ +public: + FWidgetTemplateViewModel(); + + virtual FText GetName() const override; + + virtual bool IsTemplate() const override; + + virtual void GetFilterStrings(TArray& OutStrings) const override; + + virtual TSharedRef BuildRow(const TSharedRef& OwnerTable) override; + + FReply OnDraggingWidgetTemplateItem(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent); + + /** Add the widget template to the list of favorites */ + void AddToFavorites(); + + /** Remove the widget template from the list of favorites */ + void RemoveFromFavorites(); + + /** Return true if the widget is a favorite */ + virtual bool IsFavorite() const override { return bIsFavorite; } + + /** Set the favorite flag */ + virtual void SetFavorite() override { bIsFavorite = true; } + + TSharedPtr Template; + FPaletteViewModel* PaletteViewModel; +private: + /** True is the widget is a favorite. It's keep as a state to prevent a search in the favorite list. */ + bool bIsFavorite; +}; + +class FWidgetHeaderViewModel : public FWidgetViewModel +{ +public: + virtual ~FWidgetHeaderViewModel() + { + } + + virtual FText GetName() const override + { + return GroupName; + } + + virtual bool IsTemplate() const override + { + return false; + } + + virtual void GetFilterStrings(TArray& OutStrings) const override + { + // Headers should never be included in filtering to avoid showing a header with all of + // it's widgets filtered out, so return an empty filter string. + } + + virtual TSharedRef BuildRow(const TSharedRef& OwnerTable) override; + + virtual void GetChildren(TArray< TSharedPtr >& OutChildren) override; + + virtual bool ShouldForceExpansion() const { return bForceExpansion; } + + void SetForceExpansion(bool bInForceExpansion) { bForceExpansion = bInForceExpansion; } + + FText GroupName; + TArray< TSharedPtr > Children; + +private: + + bool bForceExpansion = false; +}; + +class FPaletteViewModel: public TSharedFromThis +{ +public: + + DECLARE_MULTICAST_DELEGATE(FOnUpdating) + DECLARE_MULTICAST_DELEGATE(FOnUpdated) + +public: + FPaletteViewModel(TSharedPtr InBlueprintEditor); + ~FPaletteViewModel(); + + /** Register the View Model to events that should trigger a update of the Palette*/ + void RegisterToEvents(); + + /** Update the view model if needed and returns true if it did. */ + void Update(); + + /** Returns true if the view model needs to be updated */ + bool NeedUpdate() const { return bRebuildRequested; } + + /** Add the widget template to the list of favorites */ + void AddToFavorites(const FWidgetTemplateViewModel* WidgetTemplateViewModel); + + /** Remove the widget template to the list of favorites */ + void RemoveFromFavorites(const FWidgetTemplateViewModel* WidgetTemplateViewModel); + + typedef TArray< TSharedPtr > ViewModelsArray; + ViewModelsArray& GetWidgetViewModels() { return WidgetViewModels; } + + void SetSearchText(const FText& inSearchText) { SearchText = inSearchText; } + FText GetSearchText() const { return SearchText; } + + /** Fires before the view model is updated */ + FOnUpdating OnUpdating; + + /** Fires after the view model is updated */ + FOnUpdated OnUpdated; + +private: + FPaletteViewModel() {}; + + UWidgetBlueprint* GetBlueprint() const; + + void BuildWidgetList(); + void BuildClassWidgetList(); + + static bool FilterAssetData(FAssetData &BPAssetData); + + void AddWidgetTemplate(TSharedPtr Template); + + /** Called when a Blueprint is recompiled and live objects are swapped out for replacements */ + void OnObjectsReplaced(const TMap& ReplacementMap); + + /** Requests a rebuild of the widget list if a widget blueprint was compiled */ + void OnBlueprintReinstanced(); + + /** Called when the favorite list is changed */ + void OnFavoritesUpdated(); + + /** Requests a rebuild of the widget list */ + void HandleOnHotReload(bool bWasTriggeredAutomatically); + + /** Requests a rebuild of the widget list if a widget blueprint was deleted */ + void HandleOnAssetsDeleted(const TArray& DeletedAssetClasses); + + TWeakPtr BlueprintEditor; + + typedef TArray> WidgetTemplateArray; + TMap WidgetTemplateCategories; + + /** The source root view models for the tree. */ + ViewModelsArray WidgetViewModels; + + /** Controls rebuilding the list of spawnable widgets */ + bool bRebuildRequested; + + FText SearchText; + + TSharedPtr FavoriteHeader; +}; + diff --git a/Engine/Source/Editor/UMGEditor/Private/Settings/WidgetDesignerSettings.cpp b/Engine/Source/Editor/UMGEditor/Private/Settings/WidgetDesignerSettings.cpp index ee13f0404786..b0a2cfe461d6 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Settings/WidgetDesignerSettings.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Settings/WidgetDesignerSettings.cpp @@ -4,6 +4,9 @@ UWidgetDesignerSettings::UWidgetDesignerSettings() { + + Favorites = CreateDefaultSubobject(TEXT("WidgetPaletteFavorites")); + CategoryName = TEXT("ContentEditors"); GridSnapEnabled = true; diff --git a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateBlueprintClass.cpp b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateBlueprintClass.cpp index fade14e18478..b1a417055a0f 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateBlueprintClass.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateBlueprintClass.cpp @@ -87,11 +87,6 @@ FReply FWidgetTemplateBlueprintClass::OnDoubleClicked() return FReply::Handled(); } -FAssetData FWidgetTemplateBlueprintClass::GetWidgetAssetData() -{ - return WidgetAssetData; -} - bool FWidgetTemplateBlueprintClass::Supports(UClass* InClass) { return InClass != nullptr && InClass->IsChildOf(UWidgetBlueprint::StaticClass()); diff --git a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateBlueprintClass.h b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateBlueprintClass.h index 3a65eb744d83..c33fe714d5f3 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateBlueprintClass.h +++ b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateBlueprintClass.h @@ -44,9 +44,6 @@ public: /** Opens the widget blueprint for edit */ virtual FReply OnDoubleClicked() override; - /** Gets the asset data for this widget blueprint */ - FAssetData GetWidgetAssetData(); - /** Returns true if the supplied class is supported by this template */ static bool Supports(UClass* InClass); diff --git a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateClass.cpp b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateClass.cpp index e5f330933bc6..e123b2cb4af4 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateClass.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateClass.cpp @@ -113,6 +113,19 @@ TSharedRef FWidgetTemplateClass::GetToolTip() const } } +void FWidgetTemplateClass::GetFilterStrings(TArray& OutStrings) const +{ + FWidgetTemplate::GetFilterStrings(OutStrings); + if (WidgetClass.IsValid()) + { + OutStrings.Add(WidgetClass->GetName()); + } + if (WidgetAssetData.IsValid()) + { + OutStrings.Add(WidgetAssetData.AssetName.ToString()); + } +} + void FWidgetTemplateClass::OnObjectsReplaced(const TMap& ReplacementMap) { UObject* const* NewObject = ReplacementMap.Find(WidgetClass.Get()); diff --git a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateClass.h b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateClass.h index 5d1d6f7182e5..3235cf0188e3 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateClass.h +++ b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateClass.h @@ -37,9 +37,15 @@ public: /** Gets the tooltip widget for this palette item. */ virtual TSharedRef GetToolTip() const override; - /** Gets the WidgetClass */ + /** @param OutStrings - Returns an array of strings used for filtering/searching this widget template. */ + virtual void GetFilterStrings(TArray& OutStrings) const override; + + /** Gets the WidgetClass which might be null. */ TWeakObjectPtr GetWidgetClass() const { return WidgetClass; } + /** Returns the asset data for this widget which might be invalid. */ + FAssetData GetWidgetAssetData() { return WidgetAssetData; } + protected: /** Creates a widget template class without any class reference */ FWidgetTemplateClass(); diff --git a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateImageClass.cpp b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateImageClass.cpp index c3e1bc8ed0a9..8ccc96038f14 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateImageClass.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateImageClass.cpp @@ -6,8 +6,7 @@ #include "Engine/Texture.h" FWidgetTemplateImageClass::FWidgetTemplateImageClass(const FAssetData& InAssetData) - : FWidgetTemplateClass(UImage::StaticClass()) - , WidgetAssetData(InAssetData) + : FWidgetTemplateClass(InAssetData, UImage::StaticClass()) { } @@ -30,11 +29,6 @@ UWidget* FWidgetTemplateImageClass::Create(UWidgetTree* WidgetTree) return Widget; } -FAssetData FWidgetTemplateImageClass::GetWidgetAssetData() -{ - return WidgetAssetData; -} - bool FWidgetTemplateImageClass::Supports(UClass* InClass) { static const UClass* SlateTextureAtlasInterface = FindObject(ANY_PACKAGE, TEXT("SlateTextureAtlasInterface")); diff --git a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateImageClass.h b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateImageClass.h index 0db0bffe76ee..b0c0c08d2425 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateImageClass.h +++ b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateImageClass.h @@ -27,12 +27,6 @@ public: /** Creates an instance of the widget for the tree */ virtual UWidget* Create(UWidgetTree* WidgetTree) override; - /** Returns the asset data for this widget */ - FAssetData GetWidgetAssetData(); - /** Returns true if the supplied class is supported by this template */ static bool Supports(UClass* InClass); - -private: - FAssetData WidgetAssetData; }; diff --git a/Engine/Source/Editor/UMGEditor/Private/UMGEditorModule.cpp b/Engine/Source/Editor/UMGEditor/Private/UMGEditorModule.cpp index 5d6f5a3a8a6c..783e545524df 100644 --- a/Engine/Source/Editor/UMGEditor/Private/UMGEditorModule.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/UMGEditorModule.cpp @@ -61,6 +61,7 @@ public: MenuExtensibilityManager = MakeShareable(new FExtensibilityManager()); ToolBarExtensibilityManager = MakeShareable(new FExtensibilityManager()); + DesignerExtensibilityManager = MakeShareable(new FDesignerExtensibilityManager()); // Register widget blueprint compiler we do this no matter what. IKismetCompilerInterface& KismetCompilerModule = FModuleManager::LoadModuleChecked("KismetCompiler"); @@ -134,6 +135,7 @@ public: /** Gets the extensibility managers for outside entities to extend gui page editor's menus and toolbars */ virtual TSharedPtr GetMenuExtensibilityManager() override { return MenuExtensibilityManager; } virtual TSharedPtr GetToolBarExtensibilityManager() override { return ToolBarExtensibilityManager; } + virtual TSharedPtr GetDesignerExtensibilityManager() override { return DesignerExtensibilityManager; } /** Register settings objects. */ void RegisterSettings() @@ -186,6 +188,7 @@ private: private: TSharedPtr MenuExtensibilityManager; TSharedPtr ToolBarExtensibilityManager; + TSharedPtr DesignerExtensibilityManager; FDelegateHandle SequenceEditorHandle; FDelegateHandle MarginTrackEditorCreateTrackEditorHandle; diff --git a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprint.cpp b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprint.cpp index 1c62186800e0..fb7c2c9af534 100644 --- a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprint.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprint.cpp @@ -25,6 +25,7 @@ #if WITH_EDITOR #include "Interfaces/ITargetPlatform.h" #include "Modules/ModuleManager.h" +#include "DiffResults.h" #endif #include "Kismet2/BlueprintEditorUtils.h" #include "K2Node_CallFunction.h" @@ -598,6 +599,139 @@ void UWidgetBlueprint::NotifyGraphRenamed(class UEdGraph* Graph, FName OldName, }); } +bool UWidgetBlueprint::FindDiffs(const UBlueprint* OtherBlueprint, FDiffResults& Results) const +{ + const UWidgetBlueprint* OtherWidgetBP = Cast(OtherBlueprint); + if (!OtherWidgetBP) + { + return false; + } + + // Look for all widget instances in both, add shared ones to ObjectsToDiff and add notes for add/remove + TMap WidgetMap; + TMap OtherWidgetMap; + + WidgetTree->ForEachWidget([&](UWidget* Widget) + { + FString WidgetPath = Widget->GetPathName(this); + WidgetMap.Add(WidgetPath, Widget); + }); + + OtherWidgetBP->WidgetTree->ForEachWidget([&](UWidget* Widget) + { + FString WidgetPath = Widget->GetPathName(OtherWidgetBP); + OtherWidgetMap.Add(WidgetPath, Widget); + }); + + for (TPair Pair : WidgetMap) + { + UWidget** FoundOtherWidget = OtherWidgetMap.Find(Pair.Key); + UWidget* Widget = Pair.Value; + + if (FoundOtherWidget) + { + if (Results.CanStoreResults()) + { + // Add to general object diff map + FDiffSingleResult Diff; + Diff.Diff = EDiffType::OBJECT_REQUEST_DIFF; + Diff.Object1 = Widget; + Diff.Object2 = *FoundOtherWidget; + Diff.OwningObjectPath = Pair.Key; + FFormatNamedArguments Args; + Args.Add(TEXT("WidgetTitle"), Widget->GetLabelTextWithMetadata()); + Args.Add(TEXT("WidgetPath"), FText::FromString(Pair.Key)); + Diff.ToolTip = FText::Format(LOCTEXT("DIF_RequestWidgetTooltip", "Widget {WidgetTitle}\nPath: {WidgetPath}"), Args); + Diff.DisplayString = FText::Format(LOCTEXT("DIF_RequestWidgetLabel", "Widget {WidgetTitle}"), Args); + Diff.DisplayColor = FLinearColor(1.f, 0.4f, 0.4f); + + Results.Add(Diff); + + UPanelSlot* Slot = Widget->Slot; + UPanelSlot* OtherSlot = (*FoundOtherWidget)->Slot; + + if (Slot && OtherSlot) + { + FDiffSingleResult SlotDiff; + SlotDiff.Diff = EDiffType::OBJECT_REQUEST_DIFF; + SlotDiff.Object1 = Slot; + SlotDiff.Object2 = OtherSlot; + SlotDiff.OwningObjectPath = Pair.Key; + FFormatNamedArguments SlotArgs; + SlotArgs.Add(TEXT("WidgetTitle"), Widget->GetLabelTextWithMetadata()); + SlotArgs.Add(TEXT("WidgetPath"), FText::FromString(Pair.Key)); + SlotDiff.ToolTip = FText::Format(LOCTEXT("DIF_RequestSlotTooltip", "Slot for {WidgetTitle}\nPath: {WidgetPath}"), SlotArgs); + SlotDiff.DisplayString = FText::Format(LOCTEXT("DIF_RequestSlotLabel", "Slot for {WidgetTitle}"), SlotArgs); + SlotDiff.DisplayColor = FLinearColor(1.f, 0.4f, 0.4f); + + Results.Add(SlotDiff); + } + } + } + else + { + // This is newly added + FDiffSingleResult Diff; + Diff.Diff = EDiffType::OBJECT_ADDED; + Diff.Object1 = Widget; + Diff.OwningObjectPath = Pair.Key; + + if (Results.CanStoreResults()) + { + FFormatNamedArguments Args; + Args.Add(TEXT("WidgetTitle"), Widget->GetLabelTextWithMetadata()); + Args.Add(TEXT("WidgetPath"), FText::FromString(Pair.Key)); + Diff.ToolTip = FText::Format(LOCTEXT("DIF_AddedWidgetTooltip", "Added Widget {WidgetTitle}\nPath: {WidgetPath}"), Args); + Diff.DisplayString = FText::Format(LOCTEXT("DIF_AddedWidgetLabel", "Added Widget {WidgetTitle}"), Args); + Diff.DisplayColor = FLinearColor(1.f, 0.4f, 0.4f); + } + + Results.Add(Diff); + } + } + + for (TPair Pair : OtherWidgetMap) + { + UWidget** FoundMyWidget = WidgetMap.Find(Pair.Key); + UWidget* OtherWidget = Pair.Value; + + if (!FoundMyWidget) + { + // This is newly added + FDiffSingleResult Diff; + Diff.Diff = EDiffType::OBJECT_REMOVED; + Diff.Object1 = OtherWidget; + Diff.OwningObjectPath = Pair.Key; + + if (Results.CanStoreResults()) + { + FFormatNamedArguments Args; + Args.Add(TEXT("WidgetTitle"), OtherWidget->GetLabelTextWithMetadata()); + Args.Add(TEXT("WidgetPath"), FText::FromString(Pair.Key)); + Diff.ToolTip = FText::Format(LOCTEXT("DIF_RemovedWidgetTooltip", "Removed Widget {WidgetTitle}\nPath:{WidgetPath}"), Args); + Diff.DisplayString = FText::Format(LOCTEXT("DIF_RemovedWidgetLabel", "Removed Widget {WidgetTitle}"), Args); + Diff.DisplayColor = FLinearColor(1.f, 0.4f, 0.4f); + } + + Results.Add(Diff); + } + } + + // Add info warning + if (Results.CanStoreResults()) + { + FDiffSingleResult Diff; + Diff.Diff = EDiffType::INFO_MESSAGE; + Diff.DisplayColor = FLinearColor(.7f, .7f, .7f); + Diff.ToolTip = LOCTEXT("DIF_WidgetWarningMessage", "Warning: This may be missing changes to Animations and Bindings"); + Diff.DisplayString = Diff.ToolTip; + + Results.Add(Diff); + } + + return true; +} + #endif void UWidgetBlueprint::Serialize(FArchive& Ar) diff --git a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintCompiler.cpp b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintCompiler.cpp index 869424e755d9..1e3c7f6dc10f 100644 --- a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintCompiler.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintCompiler.cpp @@ -278,18 +278,41 @@ void FWidgetBlueprintCompilerContext::CreateClassVariablesFromBlueprint() Super::CreateClassVariablesFromBlueprint(); UWidgetBlueprint* WidgetBP = WidgetBlueprint(); + if (WidgetBP == nullptr) + { + return; + } + UClass* ParentClass = WidgetBP->ParentClass; ValidateWidgetNames(); - // Build the set of variables based on the variable widgets in the widget tree. - TArray Widgets = WidgetBP->GetAllSourceWidgets(); + // Build the set of variables based on the variable widgets in the first Widget Tree we find: + // in the current blueprint, the parent blueprint, and so on, until we find one. + TArray Widgets; + UWidgetBlueprint* WidgetBPToScan = WidgetBP; + bool bSkipVariableCreation = false; + while (WidgetBPToScan != nullptr) + { + Widgets = WidgetBPToScan->GetAllSourceWidgets(); + if (Widgets.Num() != 0) + { + // We found widgets. + break; + } + // We don't want to create variables for widgets that are in a parent blueprint. They will be created at the Parent compilation. + // But we want them to be added to the Member variable map for validation of the BindWidget property + bSkipVariableCreation = true; + + // Get the parent WidgetBlueprint + WidgetBPToScan = WidgetBPToScan->ParentClass && WidgetBPToScan->ParentClass->ClassGeneratedBy ? Cast(WidgetBPToScan->ParentClass->ClassGeneratedBy):nullptr; + } // Sort the widgets alphabetically - Widgets.Sort( []( const UWidget& Lhs, const UWidget& Rhs ) { return Rhs.GetFName() < Lhs.GetFName(); } ); + Widgets.Sort( []( const UWidget& Lhs, const UWidget& Rhs ) { return Rhs.GetFName().LexicalLess(Lhs.GetFName()); } ); // Add widget variables - for ( UWidget* Widget : Widgets ) + for ( UWidget* Widget : Widgets ) { bool bIsVariable = Widget->bIsVariable; @@ -311,6 +334,7 @@ void FWidgetBlueprintCompilerContext::CreateClassVariablesFromBlueprint() WidgetClass = BPWidgetClass->GetAuthoritativeClass(); } + // Look in the Parent class properties to find a property with the BindWidget meta tag of the same name and Type. UObjectPropertyBase* ExistingProperty = Cast(ParentClass->FindPropertyByName(Widget->GetFName())); if (ExistingProperty && FWidgetBlueprintEditorUtils::IsBindWidgetProperty(ExistingProperty) && @@ -326,6 +350,12 @@ void FWidgetBlueprintCompilerContext::CreateClassVariablesFromBlueprint() continue; } + // We skip variable creation if the Widget Tree was in the Parent Blueprint. + if (bSkipVariableCreation) + { + continue; + } + FEdGraphPinType WidgetPinType(UEdGraphSchema_K2::PC_Object, NAME_None, WidgetClass, EPinContainerType::None, false, FEdGraphTerminalType()); // Always name the variable according to the underlying FName of the widget object diff --git a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditor.cpp b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditor.cpp index 20af96ce6af3..e278007abe4d 100644 --- a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditor.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditor.cpp @@ -50,6 +50,8 @@ #include "UMGEditorActions.h" #include "GameProjectGenerationModule.h" +#include "SPaletteViewModel.h" + #define LOCTEXT_NAMESPACE "UMG" FWidgetBlueprintEditor::FWidgetBlueprintEditor() @@ -102,6 +104,10 @@ void FWidgetBlueprintEditor::InitWidgetBlueprintEditor(const EToolkitMode::Type bRespectLocks = GetDefault()->bRespectLocks; TSharedPtr ThisPtr(SharedThis(this)); + + PaletteViewModel = MakeShareable(new FPaletteViewModel(ThisPtr)); + PaletteViewModel->RegisterToEvents(); + WidgetToolbar = MakeShareable(new FWidgetBlueprintEditorToolbar(ThisPtr)); BindToolkitCommands(); @@ -552,6 +558,12 @@ void FWidgetBlueprintEditor::Tick(float DeltaTime) bPreviewInvalidated = false; RefreshPreview(); } + + // Updat the palette view model. + if (PaletteViewModel->NeedUpdate()) + { + PaletteViewModel->Update(); + } } static bool MigratePropertyValue(UObject* SourceObject, UObject* DestinationObject, FEditPropertyChain::TDoubleLinkedListNode* PropertyChainNode, UProperty* MemberProperty, bool bIsModify) @@ -960,17 +972,15 @@ void FWidgetBlueprintEditor::UpdatePreview(UBlueprint* InBlueprint, bool bInForc UWidgetTree* LatestWidgetTree = PreviewBlueprint->WidgetTree; - // HACK NickD: Doing this to match the hack in UUserWidget::Initialize(), to permit some semblance of widgettree - // inheritance. This will correctly show the parent widget tree provided your class does not specify a root. - UWidgetBlueprintGeneratedClass* SuperBGClass = Cast(PreviewBlueprint->GeneratedClass->GetSuperClass()); - if ( SuperBGClass ) + // If there is no RootWidget, we look for a WidgetTree in the parents classes until we find one. + if (LatestWidgetTree->RootWidget == nullptr) { - UWidgetBlueprint* SuperWidgetBlueprint = Cast(SuperBGClass->ClassGeneratedBy); - if ( SuperWidgetBlueprint && (LatestWidgetTree->RootWidget == nullptr) ) + UWidgetBlueprintGeneratedClass* BGClass = PreviewUserWidget->GetWidgetTreeOwningClass(); + if (BGClass) { - LatestWidgetTree = SuperWidgetBlueprint->WidgetTree; + LatestWidgetTree = BGClass->WidgetTree; } - } + } // Update the widget tree directly to match the blueprint tree. That way the preview can update // without needing to do a full recompile. diff --git a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.cpp b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.cpp index 11db790f1938..080eaba3d429 100644 --- a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.cpp @@ -783,11 +783,12 @@ void FWidgetBlueprintEditorUtils::ReplaceWidgetWithSelectedTemplate(TSharedRefReplaceChild(ThisWidget, NewReplacementWidget); FString ReplaceName = ThisWidget->GetName(); + bool bIsGeneratedName = ThisWidget->IsGeneratedName(); // Rename the removed widget to the transient package so that it doesn't conflict with future widgets sharing the same name. ThisWidget->Rename(nullptr, nullptr); // Rename the new Widget to maintain the current name if it's not a generic name - if (!IsGenericName(ReplaceName, ThisWidget->GetClass())) + if (!bIsGeneratedName) { ReplaceName = FindNextValidName(BP->WidgetTree, ReplaceName); NewReplacementWidget->Rename(*ReplaceName, BP->WidgetTree); @@ -948,11 +949,12 @@ void FWidgetBlueprintEditorUtils::ReplaceWidgets(TSharedRefGetName(); + bool bIsGeneratedName = Item.GetTemplate()->IsGeneratedName(); // Rename the removed widget to the transient package so that it doesn't conflict with future widgets sharing the same name. Item.GetTemplate()->Rename(nullptr, nullptr); // Rename the new Widget to maintain the current name if it's not a generic name - if (!IsGenericName(ReplaceName, Item.GetTemplate()->GetClass())) + if (!bIsGeneratedName) { ReplaceName = FindNextValidName(BP->WidgetTree, ReplaceName); NewReplacementWidget->Rename(*ReplaceName, BP->WidgetTree); @@ -1367,6 +1369,17 @@ void FWidgetBlueprintEditorUtils::ImportWidgetsFromText(UWidgetBlueprint* BP, co Widget->SetFlags(RF_Transactional); + // We don't export parent slot pointers, so each panel will need to point it's children back to itself + UPanelWidget* PanelWidget = Cast(Widget); + if (PanelWidget) + { + TArray PanelSlots = PanelWidget->GetSlots(); + for (int32 i = 0; i < PanelWidget->GetChildrenCount(); i++) + { + PanelWidget->GetChildAt(i)->Slot = PanelSlots[i]; + } + } + // If there is an existing widget with the same name, rename the newly placed widget. FString WidgetOldName = Widget->GetName(); FString NewName = FindNextValidName(BP->WidgetTree, WidgetOldName); @@ -1549,10 +1562,4 @@ FString FWidgetBlueprintEditorUtils::FindNextValidName(UWidgetTree* WidgetTree, return Name; } -bool FWidgetBlueprintEditorUtils::IsGenericName(const FString& Name, const UClass* WidgetClass) -{ - FString NewName = RemoveSuffixFromName(Name); - return (NewName == WidgetClass->GetName()); -} - #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.h b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.h index dbe23abd6437..30f5ddd448b8 100644 --- a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.h +++ b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.h @@ -88,6 +88,4 @@ private: static void ReplaceWidgets(TSharedRef BlueprintEditor, UWidgetBlueprint* BP, TSet Widgets, UClass* WidgetClass); static FString FindNextValidName(UWidgetTree* WidgetTree, const FString& Name); - - static bool IsGenericName(const FString& Name, const UClass* WidgetClass); }; diff --git a/Engine/Source/Editor/UMGEditor/Public/DesignerExtension.h b/Engine/Source/Editor/UMGEditor/Public/DesignerExtension.h index 5fefe58978c1..6ce221a43e95 100644 --- a/Engine/Source/Editor/UMGEditor/Public/DesignerExtension.h +++ b/Engine/Source/Editor/UMGEditor/Public/DesignerExtension.h @@ -109,20 +109,23 @@ public: /** Initializes the designer extension, this is called the first time a designer extension is registered */ virtual void Initialize(IUMGDesigner* InDesigner, UWidgetBlueprint* InBlueprint); + /** Returns true if the designer extension can extend the current selection. */ virtual bool CanExtendSelection(const TArray< FWidgetReference >& Selection) const { return false; } - /** Called every time the selection in the designer changes. */ + /** Called every time a element the designer can extend is selected. */ virtual void ExtendSelection(const TArray< FWidgetReference >& Selection, TArray< TSharedRef >& SurfaceElements) { } + /** Called each frames to tick the extension. */ virtual void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { } + /** Called to paint the extension. */ virtual void Paint(const TSet< FWidgetReference >& Selection, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId) const { } @@ -130,6 +133,11 @@ public: /** Gets the ID identifying this extension. */ FName GetExtensionId() const; + bool operator==(const FDesignerExtension& Other) const + { + return ExtensionId.IsEqual(Other.GetExtensionId()); + } + protected: void BeginTransaction(const FText& SessionName); diff --git a/Engine/Source/Editor/UMGEditor/Public/IHasDesignerExtensibility.h b/Engine/Source/Editor/UMGEditor/Public/IHasDesignerExtensibility.h new file mode 100644 index 000000000000..384a8c5393f8 --- /dev/null +++ b/Engine/Source/Editor/UMGEditor/Public/IHasDesignerExtensibility.h @@ -0,0 +1,39 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DesignerExtension.h" + +/** + * Designer Extensibility Manager keep a series of Designer Extensions. See FDesignerExtension class for more information. + */ +class UMGEDITOR_API FDesignerExtensibilityManager +{ +public: + void AddDesignerExtension(const TSharedRef& Extension) + { + ExternalExtensions.AddUnique(Extension); + } + + void RemoveDesignerExtension(const TSharedRef& Extension) + { + ExternalExtensions.Remove(Extension); + } + + const TArray>& GetExternalDesignerExtensions() const + { + return ExternalExtensions; + } + +private: + TArray> ExternalExtensions; +}; + +/** Indicates that a class has a designer that is extensible */ +class IHasDesignerExtensibility +{ +public: + virtual TSharedPtr GetDesignerExtensibilityManager() = 0; +}; + diff --git a/Engine/Source/Editor/UMGEditor/Public/Settings/WidgetDesignerSettings.h b/Engine/Source/Editor/UMGEditor/Public/Settings/WidgetDesignerSettings.h index 41f8865c86ad..41142cd95980 100644 --- a/Engine/Source/Editor/UMGEditor/Public/Settings/WidgetDesignerSettings.h +++ b/Engine/Source/Editor/UMGEditor/Public/Settings/WidgetDesignerSettings.h @@ -6,6 +6,7 @@ #include "UObject/ObjectMacros.h" #include "UObject/Object.h" #include "Engine/DeveloperSettings.h" +#include "WidgetPaletteFavorites.h" #include "WidgetDesignerSettings.generated.h" /** @@ -61,4 +62,11 @@ public: */ UPROPERTY(EditAnywhere, config, Category = Interaction) bool bRespectLocks; + + /** + * List of Favorites widgets used to populate the Favorites Palette + */ + UPROPERTY() + UWidgetPaletteFavorites* Favorites; + }; diff --git a/Engine/Source/Editor/UMGEditor/Public/UMGEditorModule.h b/Engine/Source/Editor/UMGEditor/Public/UMGEditorModule.h index d1abfc52aa4d..555e15cb999d 100644 --- a/Engine/Source/Editor/UMGEditor/Public/UMGEditorModule.h +++ b/Engine/Source/Editor/UMGEditor/Public/UMGEditorModule.h @@ -5,6 +5,7 @@ #include "CoreMinimal.h" #include "Modules/ModuleInterface.h" #include "Toolkits/AssetEditorToolkit.h" +#include "IHasDesignerExtensibility.h" extern const FName UMGEditorAppIdentifier; @@ -12,7 +13,7 @@ class FUMGEditor; class FWidgetBlueprintCompiler; /** The public interface of the UMG editor module. */ -class IUMGEditorModule : public IModuleInterface, public IHasMenuExtensibility, public IHasToolBarExtensibility +class IUMGEditorModule : public IModuleInterface, public IHasMenuExtensibility, public IHasToolBarExtensibility, public IHasDesignerExtensibility { public: virtual FWidgetBlueprintCompiler* GetRegisteredCompiler() = 0; diff --git a/Engine/Source/Editor/UMGEditor/Public/WidgetBlueprint.h b/Engine/Source/Editor/UMGEditor/Public/WidgetBlueprint.h index 5eefef31e67d..00aa1e84789b 100644 --- a/Engine/Source/Editor/UMGEditor/Public/WidgetBlueprint.h +++ b/Engine/Source/Editor/UMGEditor/Public/WidgetBlueprint.h @@ -275,6 +275,7 @@ public: #if WITH_EDITOR virtual void GetAssetRegistryTags(TArray& OutTags) const override; virtual void NotifyGraphRenamed(class UEdGraph* Graph, FName OldName, FName NewName) override; + virtual bool FindDiffs(const UBlueprint* OtherBlueprint, FDiffResults& Results) const override; #endif virtual void Serialize(FArchive& Ar) override; diff --git a/Engine/Source/Editor/UMGEditor/Public/WidgetBlueprintEditor.h b/Engine/Source/Editor/UMGEditor/Public/WidgetBlueprintEditor.h index 46362c5ea643..db2572869c7c 100644 --- a/Engine/Source/Editor/UMGEditor/Public/WidgetBlueprintEditor.h +++ b/Engine/Source/Editor/UMGEditor/Public/WidgetBlueprintEditor.h @@ -22,6 +22,7 @@ class STextBlock; class UPanelSlot; class UWidgetAnimation; class UWidgetBlueprint; +class FPaletteViewModel; struct FNamedSlotSelection { @@ -172,6 +173,8 @@ public: bool GetIsRespectingLocks() const; void SetIsRespectingLocks(bool Value); + TSharedPtr GetPaletteViewModel() { return PaletteViewModel; }; + public: /** Fires whenever a new widget is being hovered over */ FOnHoveredWidgetSet OnHoveredWidgetSet; @@ -361,4 +364,7 @@ private: /** When true the animation data in the generated class should be replaced with the current animation data. */ bool bRefreshGeneratedClassAnimations; + + /** ViewModel used by the Palette and Palette Favorite Views */ + TSharedPtr PaletteViewModel; }; diff --git a/Engine/Source/Editor/UMGEditor/Public/WidgetTemplate.h b/Engine/Source/Editor/UMGEditor/Public/WidgetTemplate.h index f96ca2d314c2..15975acc28df 100644 --- a/Engine/Source/Editor/UMGEditor/Public/WidgetTemplate.h +++ b/Engine/Source/Editor/UMGEditor/Public/WidgetTemplate.h @@ -26,16 +26,19 @@ public: /** Constructs the widget template. */ virtual UWidget* Create(class UWidgetTree* Tree) = 0; - /** Gets the icon to display in the template palate for this template. */ + /** Gets the icon to display for this widget template. */ virtual const FSlateBrush* GetIcon() const { static FSlateNoResource NullBrush; return &NullBrush; } - /** Gets tooltip widget for this palette item. */ + /** Gets tooltip widget for this widget template. */ virtual TSharedRef GetToolTip() const = 0; + /** @param OutStrings - Returns an array of strings used for filtering/searching this widget template. */ + virtual void GetFilterStrings(TArray& OutStrings) const { OutStrings.Add(Name.ToString()); } + /** The the action to perform when the template item is double clicked */ virtual FReply OnDoubleClicked() { return FReply::Unhandled(); } diff --git a/Engine/Source/Editor/UndoHistory/Private/Widgets/SUndoHistory.cpp b/Engine/Source/Editor/UndoHistory/Private/Widgets/SUndoHistory.cpp index 06e2111c380a..a973f26df44f 100644 --- a/Engine/Source/Editor/UndoHistory/Private/Widgets/SUndoHistory.cpp +++ b/Engine/Source/Editor/UndoHistory/Private/Widgets/SUndoHistory.cpp @@ -459,7 +459,7 @@ TSharedRef SUndoHistory::GetViewButtonContent() const LOCTEXT("ToggleShowTransactionDetailsToolTip", "When enabled, display additional information about transactions."), FSlateIcon(), FUIAction( - FExecuteAction::CreateSP(this, &SUndoHistory::ToggleShowTransactionDetails), + FExecuteAction::CreateSP(const_cast(this), &SUndoHistory::ToggleShowTransactionDetails), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SUndoHistory::IsShowingTransactionDetails) ), diff --git a/Engine/Source/Editor/UnrealEd/Classes/Animation/DebugSkelMeshComponent.h b/Engine/Source/Editor/UnrealEd/Classes/Animation/DebugSkelMeshComponent.h index 95e3dc5c5a25..42e24e2c9fb9 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Animation/DebugSkelMeshComponent.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Animation/DebugSkelMeshComponent.h @@ -510,7 +510,13 @@ public: */ virtual FTransform GetDrawTransform(int32 BoneIndex) const { - return GetComponentSpaceTransforms()[BoneIndex]; + const TArray& SpaceTransforms = GetComponentSpaceTransforms(); + if (SpaceTransforms.IsValidIndex(BoneIndex)) + { + return SpaceTransforms[BoneIndex]; + } + + return FTransform::Identity; } }; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Commandlets/DDCCleanupCommandlet.h b/Engine/Source/Editor/UnrealEd/Classes/Commandlets/DDCCleanupCommandlet.h new file mode 100644 index 000000000000..f7b8b4ef3c8f --- /dev/null +++ b/Engine/Source/Editor/UnrealEd/Classes/Commandlets/DDCCleanupCommandlet.h @@ -0,0 +1,19 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Commandlets/Commandlet.h" +#include "DDCCleanupCommandlet.generated.h" + +UCLASS() +class UDDCCleanupCommandlet : public UCommandlet +{ + GENERATED_BODY() + //~ Begin UCommandlet Interface + virtual int32 Main(const FString& Params) override; + //~ End UCommandlet Interface +}; + + diff --git a/Engine/Source/Editor/UnrealEd/Classes/CookOnTheSide/CookOnTheFlyServer.h b/Engine/Source/Editor/UnrealEd/Classes/CookOnTheSide/CookOnTheFlyServer.h index cb91e06004a3..d9f796421d85 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/CookOnTheSide/CookOnTheFlyServer.h +++ b/Engine/Source/Editor/UnrealEd/Classes/CookOnTheSide/CookOnTheFlyServer.h @@ -281,6 +281,11 @@ public: */ virtual bool Exec(class UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) override; + /** + * UObject interface + */ + virtual bool IsDestructionThreadSafe() const override { return false; } + /** * Dumps cooking stats to the log * run from the exec command "Cook stats" @@ -679,6 +684,13 @@ private: */ void InitializeTargetPlatforms(); + /** + * Some content plugins does not support all target platforms. + * Build up a map of unsupported packages per platform that can be checked before saving. + */ + void DiscoverPlatformSpecificNeverCookPackages( + const TArray& TargetPlatformNames, const TArray& TargetPlatformStrings); + /** * Clean up the sandbox */ diff --git a/Engine/Source/Editor/UnrealEd/Classes/Editor/EditorEngine.h b/Engine/Source/Editor/UnrealEd/Classes/Editor/EditorEngine.h index 1a8dcaab69c4..70a46d58f7c6 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Editor/EditorEngine.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Editor/EditorEngine.h @@ -1112,6 +1112,13 @@ public: */ void MoveViewportCamerasToComponent(USceneComponent* Component, bool bActiveViewportOnly); + /** + * Moves all viewport cameras to focus on the provided bounding box. + * @param BoundingBox Target box + * @param bActiveViewportOnly If true, move/reorient only the active viewport. + */ + void MoveViewportCamerasToBox(const FBox& BoundingBox, bool bActiveViewportOnly) const; + /** * Snaps an actor in a direction. Optionally will align with the trace normal. * @param InActor Actor to move to the floor. @@ -3030,13 +3037,6 @@ private: /** Gets the init values for worlds opened via Map_Load in the editor */ UWorld::InitializationValues GetEditorWorldInitializationValues() const; - /** - * Moves all viewport cameras to focus on the provided bounding box. - * @param BoundingBox Target box - * @param bActiveViewportOnly If true, move/reorient only the active viewport. - */ - void MoveViewportCamerasToBox(const FBox& BoundingBox, bool bActiveViewportOnly) const; - public: // Launcher Worker TSharedPtr LauncherWorker; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Editor/PropertyEditorTestObject.h b/Engine/Source/Editor/UnrealEd/Classes/Editor/PropertyEditorTestObject.h index 46c0b43668a6..c9f9d3560802 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Editor/PropertyEditorTestObject.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Editor/PropertyEditorTestObject.h @@ -19,7 +19,7 @@ class UStaticMeshComponent; class UTexture; UENUM() -enum PropertEditorTestEnum +enum PropertyEditorTestEnum { /** This comment should appear above enum 1 */ PropertyEditorTest_Enum1 UMETA(Hidden), @@ -124,13 +124,11 @@ class UPropertyEditorTestObject : public UObject { GENERATED_UCLASS_BODY() - - UPROPERTY(EditAnywhere, Category = BasicProperties) int8 Int8Property; UPROPERTY(EditAnywhere, Category = BasicProperties) - int16 Int16roperty; + int16 Int16Property; UPROPERTY(EditAnywhere, Category = BasicProperties) int32 Int32Property; @@ -196,7 +194,10 @@ class UPropertyEditorTestObject : public UObject FColor ColorProperty; UPROPERTY(EditAnywhere, Category=BasicProperties) - TEnumAsByte EnumProperty; + TEnumAsByte EnumByteProperty; + + UPROPERTY(EditAnywhere, Category=BasicProperties) + EditColor EnumProperty; UPROPERTY(EditAnywhere, Category = BasicProperties) FMatrix MatrixProperty; @@ -252,7 +253,7 @@ class UPropertyEditorTestObject : public UObject TArray ColorPropertyArray; UPROPERTY(EditAnywhere, Category=ArraysOfProperties) - TArray > EnumPropertyArray; + TArray > EnumPropertyArray; UPROPERTY(EditAnywhere, Category=ArraysOfProperties) TArray StructPropertyArray; @@ -266,7 +267,7 @@ class UPropertyEditorTestObject : public UObject UPROPERTY(EditAnywhere, Category=ArraysOfProperties) int32 StaticArrayOfIntsWithEnumLabels[ArrayIndex_MAX]; - /** This is a float property tooltip that is overridden */ + // This is a float property tooltip that is overridden UPROPERTY(EditAnywhere, Category=AdvancedProperties, meta=(ClampMin = "0.0", ClampMax = "100.0", UIMin = "0.0", UIMax = "50.0", ToolTip = "This is a custom tooltip that should be shown")) float FloatPropertyWithClampedRange; @@ -385,7 +386,7 @@ class UPropertyEditorTestObject : public UObject TMap ObjectToColorMap; UPROPERTY(EditAnywhere, Category=TMapTests) - TMap > IntToEnumMap; + TMap > IntToEnumMap; UPROPERTY(EditAnywhere, Category=TMapTests) TMap NameToNameMap; @@ -419,4 +420,67 @@ class UPropertyEditorTestObject : public UObject // filter for AllowedClasses correctly. UPROPERTY(EditAnywhere, Category=ObjectPropertyAllowedClasses, meta=(AllowedClasses="Texture,BlendableInterface")) UObject* TextureOrBlendableInterface; + + UPROPERTY(EditAnywhere, Category = "Subcategory") + bool bSubcategory; + + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Subcategory") + bool bSubcategoryAdvanced; + + UPROPERTY(EditAnywhere, Category = "Subcategory|Foo") + bool bSubcategoryFooSimple; + + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Subcategory|Foo") + bool bSubcategoryFooAdvanced; + + UPROPERTY(EditAnywhere, Category = "Subcategory|Bar") + bool bSubcategoryBarSimple; + + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Subcategory|Bar") + bool bSubcategoryBarAdvanced; + + UPROPERTY(EditAnywhere, Category = "Subcategory") + bool bSubcategoryLast; + + UPROPERTY(EditAnywhere, Category = EditCondition) + bool bEnablesNext; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "bEnablesNext == true")) + bool bEnabledByPrevious; + + UPROPERTY(EditAnywhere, Category = EditCondition) + EditColor EnumEditCondition; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "EnumEditCondition == EditColor::Blue")) + bool bEnabledWhenBlue; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "EnumEditCondition == EditColor::Pink")) + bool bEnabledWhenPink; + + UPROPERTY(EditAnywhere, Category = EditCondition) + TEnumAsByte EnumAsByteEditCondition; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "EnumAsByteEditCondition == PropertyEditorTestEnum::PropertyEditorTest_Enum2")) + bool bEnabledWhenEnumIs2; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "EnumAsByteEditCondition == PropertyEditorTestEnum::PropertyEditorTest_Enum4")) + bool bEnabledWhenEnumIs4; + + UPROPERTY(EditAnywhere, Category = EditCondition) + int32 IntegerEditCondition; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "IntegerEditCondition >= 5")) + bool bEnabledWhenIntGreaterOrEqual5; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "IntegerEditCondition <= 10")) + bool bEnabledWhenIntLessOrEqual10; + + UPROPERTY(EditAnywhere, Category = EditCondition) + float FloatEditCondition; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "FloatEditCondition > 5")) + bool bEnabledWhenFloatGreaterThan5; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "FloatEditCondition < 10")) + bool bEnabledWhenFloatLessThan10; }; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/CSVImportFactory.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/CSVImportFactory.h index 1eb3f4f9174f..76097f58cb61 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Factories/CSVImportFactory.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/CSVImportFactory.h @@ -17,7 +17,7 @@ DECLARE_LOG_CATEGORY_EXTERN(LogCSVImportFactory, Log, All); /** Enum to indicate what to import CSV as */ -UENUM() +UENUM(BlueprintType) enum class ECSVImportType : uint8 { /** Import as UDataTable */ @@ -32,20 +32,20 @@ enum class ECSVImportType : uint8 ECSV_CurveLinearColor, }; -USTRUCT() +USTRUCT(BlueprintType) struct FCSVImportSettings { GENERATED_BODY() FCSVImportSettings(); - UPROPERTY() + UPROPERTY(BlueprintReadWrite, Category="Misc") UScriptStruct* ImportRowStruct; - UPROPERTY() + UPROPERTY(BlueprintReadWrite, Category="Misc") ECSVImportType ImportType; - UPROPERTY() + UPROPERTY(BlueprintReadWrite, Category="Misc") TEnumAsByte ImportCurveInterpMode; }; @@ -79,7 +79,12 @@ private: /* Reimport object from the given path*/ EReimportResult::Type Reimport(UObject* Obj, const FString& Path); - UPROPERTY() +public: + UPROPERTY(BlueprintReadWrite, Category="Automation") FCSVImportSettings AutomatedImportSettings; + + /** Temporary data table to use to display import options */ + UPROPERTY() + UDataTable* TempImportDataTable; }; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxAnimSequenceImportData.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxAnimSequenceImportData.h index da21ce196775..114bdfd9ef82 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxAnimSequenceImportData.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxAnimSequenceImportData.h @@ -39,7 +39,7 @@ class UNREALED_API UFbxAnimSequenceImportData : public UFbxAssetImportData GENERATED_UCLASS_BODY() /** If checked, meshes nested in bone hierarchies will be imported instead of being converted to bones. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = ImportSettings, meta = (ImportType = "Animation")) + UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = ImportSettings, meta = (ImportType = "Animation")) bool bImportMeshesInBoneHierarchy; /** Which animation range to import. The one defined at Exported, at Animated time or define a range manually */ @@ -55,7 +55,7 @@ class UNREALED_API UFbxAnimSequenceImportData : public UFbxAssetImportData int32 EndFrame_DEPRECATED; /** Frame range used when Set Range is used in Animation Length */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = ImportSettings, meta=(UIMin=0, ClampMin=0)) + UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = ImportSettings, meta=(UIMin=0, ClampMin=0)) FInt32Interval FrameImportRange; /** Enable this option to use default sample rate for the imported animation at 30 frames per second */ @@ -73,9 +73,12 @@ class UNREALED_API UFbxAnimSequenceImportData : public UFbxAssetImportData /** Import if custom attribute as a curve within the animation */ UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = ImportSettings) bool bImportCustomAttribute; + + /** If true, all previous custom attribute curves will be deleted when doing a re-import. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = ImportSettings) + bool bDeleteExistingCustomAttributeCurves; /** Import bone transform tracks. If false, this will discard any bone transform tracks. (useful for curves only animations)*/ - UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = ImportSettings) bool bImportBoneTracks; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxMeshImportData.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxMeshImportData.h index 7810e64689df..212fa12eb59f 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxMeshImportData.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxMeshImportData.h @@ -82,6 +82,10 @@ class UFbxMeshImportData : public UFbxAssetImportData UPROPERTY(EditAnywhere, BlueprintReadWrite, config, AdvancedDisplay, Category = Mesh, meta=(ImportType="Mesh|GeoOnly")) TEnumAsByte NormalGenerationMethod; + /* If checked, The material list will be reorder to the same order has the FBX file. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, config, AdvancedDisplay, Category = Material, meta = (OBJRestrict = "true")) + bool bReorderMaterialToFbxOrder; + bool CanEditChange( const UProperty* InProperty ) const override; ////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxSceneImportOptionsSkeletalMesh.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxSceneImportOptionsSkeletalMesh.h index 9af226fd72a1..812abc54b120 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxSceneImportOptionsSkeletalMesh.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxSceneImportOptionsSkeletalMesh.h @@ -81,6 +81,10 @@ class UFbxSceneImportOptionsSkeletalMesh : public UObject UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = Animation) bool bImportCustomAttribute; + /** If true, all previous custom attribute curves will be deleted when doing a re-import. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = Animation) + bool bDeleteExistingCustomAttributeCurves; + /** Type of asset to import from the FBX file */ UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = Animation) bool bPreserveLocalTransform; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxSkeletalMeshImportData.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxSkeletalMeshImportData.h index f37483226437..2ba71b834c18 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxSkeletalMeshImportData.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxSkeletalMeshImportData.h @@ -46,7 +46,7 @@ public: FColor VertexOverrideColor; /** Enable this option to update Skeleton (of the mesh)'s reference pose. Mesh's reference pose is always updated. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category=Mesh, meta=(ImportType="SkeletalMesh|RigOnly", ToolTip="If enabled, update the Skeleton (of the mesh being imported)'s reference pose.")) + UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category=Mesh, meta=(ImportType="SkeletalMesh|RigOnly", ToolTip="If enabled, update the Skeleton (of the mesh being imported)'s reference pose.")) uint32 bUpdateSkeletonReferencePose:1; /** Enable this option to use frame 0 as reference pose */ diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxStaticMeshImportData.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxStaticMeshImportData.h index e663765a2387..86b0b01bc25b 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxStaticMeshImportData.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxStaticMeshImportData.h @@ -31,14 +31,14 @@ class UFbxStaticMeshImportData : public UFbxMeshImportData FColor VertexOverrideColor; /** Disabling this option will keep degenerate triangles found. In general you should leave this option on. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, Category = Mesh, meta = (ImportType = "StaticMesh", ReimportRestrict = "true")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, config, Category = Mesh, meta = (ImportType = "StaticMesh", ReimportRestrict = "true")) uint32 bRemoveDegenerates:1; /** Required for PNT tessellation but can be slow. Recommend disabling for larger meshes. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, Category = Mesh, meta = (ImportType = "StaticMesh", ReimportRestrict = "true")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, config, Category = Mesh, meta = (ImportType = "StaticMesh", ReimportRestrict = "true")) uint32 bBuildAdjacencyBuffer:1; - UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, Category = Mesh, meta = (ImportType = "StaticMesh", ReimportRestrict = "true")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, config, Category = Mesh, meta = (ImportType = "StaticMesh", ReimportRestrict = "true")) uint32 bBuildReversedIndexBuffer:1; UPROPERTY(EditAnywhere, BlueprintReadWrite, config, AdvancedDisplay, Category= Mesh, meta=(ImportType="StaticMesh", ReimportRestrict = "true")) diff --git a/Engine/Source/Editor/UnrealEd/Classes/Preferences/AnimationBlueprintEditorOptions.h b/Engine/Source/Editor/UnrealEd/Classes/Preferences/AnimationBlueprintEditorOptions.h new file mode 100644 index 000000000000..88a2da95c6cd --- /dev/null +++ b/Engine/Source/Editor/UnrealEd/Classes/Preferences/AnimationBlueprintEditorOptions.h @@ -0,0 +1,24 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +/** + * A configuration class used by the UAnimationBlueprint Editor to save editor + * settings across sessions. + */ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Object.h" +#include "AnimationBlueprintEditorOptions.generated.h" + +UCLASS(hidecategories=Object, config=EditorPerProjectUserSettings) +class UNREALED_API UAnimationBlueprintEditorOptions : public UObject +{ + GENERATED_UCLASS_BODY() + + /** If true, fade nodes which are not connected to the selected nodes */ + UPROPERTY(EditAnywhere, config, Category=Options) + uint32 bHideUnrelatedNodes:1; + +}; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Preferences/BlueprintEditorOptions.h b/Engine/Source/Editor/UnrealEd/Classes/Preferences/BlueprintEditorOptions.h new file mode 100644 index 000000000000..4419615df363 --- /dev/null +++ b/Engine/Source/Editor/UnrealEd/Classes/Preferences/BlueprintEditorOptions.h @@ -0,0 +1,24 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +/** + * A configuration class used by the UBlueprint Editor to save editor + * settings across sessions. + */ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Object.h" +#include "BlueprintEditorOptions.generated.h" + +UCLASS(hidecategories=Object, config=EditorPerProjectUserSettings) +class UNREALED_API UBlueprintEditorOptions : public UObject +{ + GENERATED_UCLASS_BODY() + + /** If true, fade nodes which are not connected to the selected nodes */ + UPROPERTY(EditAnywhere, config, Category=Options) + uint32 bHideUnrelatedNodes:1; + +}; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Preferences/MaterialEditorOptions.h b/Engine/Source/Editor/UnrealEd/Classes/Preferences/MaterialEditorOptions.h index a2b07c3c4809..314dc8353130 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Preferences/MaterialEditorOptions.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Preferences/MaterialEditorOptions.h @@ -37,6 +37,10 @@ class UNREALED_API UMaterialEditorOptions : public UObject /** If true, always refresh the material preview. */ UPROPERTY(EditAnywhere, config, Category = Options) uint32 bLivePreviewUpdate : 1; + + /** If true, fade nodes which are not connected to the selected nodes */ + UPROPERTY(EditAnywhere, config, Category=Options) + uint32 bHideUnrelatedNodes:1; /** If true, always refresh all expression previews. */ UPROPERTY(EditAnywhere, config, Category=Options) diff --git a/Engine/Source/Editor/UnrealEd/Classes/Settings/LevelEditorMiscSettings.h b/Engine/Source/Editor/UnrealEd/Classes/Settings/LevelEditorMiscSettings.h index 59f5651430e3..7188b84edbc7 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Settings/LevelEditorMiscSettings.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Settings/LevelEditorMiscSettings.h @@ -79,6 +79,9 @@ public: UPROPERTY(EditAnywhere, config, Category = Levels, Meta = (ClampMin = "0.0", ClampMax = "100.0")) float PercentageThresholdForPrompt; + UPROPERTY(EditAnywhere, config, Category = Levels) + FVector MinimumBoundsForCheckingSize; + public: /** The save directory for newly created screenshots */ diff --git a/Engine/Source/Editor/UnrealEd/Classes/ThumbnailRendering/BlueprintThumbnailRenderer.h b/Engine/Source/Editor/UnrealEd/Classes/ThumbnailRendering/BlueprintThumbnailRenderer.h index e9f6716806f4..fb556bbb914f 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/ThumbnailRendering/BlueprintThumbnailRenderer.h +++ b/Engine/Source/Editor/UnrealEd/Classes/ThumbnailRendering/BlueprintThumbnailRenderer.h @@ -17,7 +17,7 @@ class FCanvas; class FRenderTarget; UCLASS(config=Editor,MinimalAPI) -class UBlueprintThumbnailRenderer : public UDefaultSizedThumbnailRenderer +class UNREALED_VTABLE UBlueprintThumbnailRenderer : public UDefaultSizedThumbnailRenderer { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Editor/UnrealEd/Classes/UserDefinedStructure/UserDefinedStructEditorData.h b/Engine/Source/Editor/UnrealEd/Classes/UserDefinedStructure/UserDefinedStructEditorData.h index 8486356f25c9..ed8da4f29d94 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/UserDefinedStructure/UserDefinedStructEditorData.h +++ b/Engine/Source/Editor/UnrealEd/Classes/UserDefinedStructure/UserDefinedStructEditorData.h @@ -64,7 +64,10 @@ struct FStructVariableDescription uint8 bInvalidMember:1; UPROPERTY() - uint8 bDontEditoOnInstance:1; + uint8 bDontEditOnInstance:1; + + UPROPERTY() + uint8 bEnableSaveGame : 1; UPROPERTY() uint8 bEnableMultiLineText:1; @@ -92,7 +95,8 @@ struct FStructVariableDescription , bIsSet_DEPRECATED(false) , bIsMap_DEPRECATED(false) , bInvalidMember(false) - , bDontEditoOnInstance(false) + , bDontEditOnInstance(false) + , bEnableSaveGame(false) , bEnableMultiLineText(false) , bEnable3dWidget(false) { } diff --git a/Engine/Source/Editor/UnrealEd/Private/AssetSelection.cpp b/Engine/Source/Editor/UnrealEd/Private/AssetSelection.cpp index bb80f0b0e444..ab8cbbefd668 100644 --- a/Engine/Source/Editor/UnrealEd/Private/AssetSelection.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/AssetSelection.cpp @@ -45,6 +45,7 @@ #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "Settings/LevelEditorMiscSettings.h" +#include "Engine/LevelStreaming.h" #include "Engine/LevelBounds.h" #include "SourceControlHelpers.h" #include "Dialogs/Dialogs.h" @@ -448,9 +449,31 @@ namespace ActorPlacementUtils } } } + if (InLevel && InLevel->OwningWorld) + { + int32 LevelCount = InLevel->OwningWorld->GetStreamingLevels().Num(); + int32 NumLockedLevels = 0; + // Check for streaming level count b/c we know there is > 1 streaming level + for (ULevelStreaming* StreamingLevel : InLevel->OwningWorld->GetStreamingLevels()) + { + StreamingLevel->bLocked ? NumLockedLevels++ : 0; + } + // If there is only one unlocked level, a) ours is the unlocked level b/c of the previous IsLevelLocked test and b) we shouldn't try to check for level bounds on the next test + if (LevelCount - NumLockedLevels == 1) + { + return true; + } + } if (InLevel && GetDefault()->bPromptWhenAddingToLevelOutsideBounds) { FBox CurrentLevelBounds = ALevelBounds::CalculateLevelBounds(InLevel); + FVector BoundsExtent = CurrentLevelBounds.GetExtent(); + if (BoundsExtent.X < GetDefault()->MinimumBoundsForCheckingSize.X + && BoundsExtent.Y < GetDefault()->MinimumBoundsForCheckingSize.Y + && BoundsExtent.Z < GetDefault()->MinimumBoundsForCheckingSize.Z) + { + return true; + } FVector ExpandedScale = FVector(1.0f + (GetDefault()->PercentageThresholdForPrompt / 100.0f)); FTransform ExpandedScaleTransform = FTransform::Identity; ExpandedScaleTransform.SetScale3D(ExpandedScale); diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegUtilCommandlet.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegUtilCommandlet.cpp index 87db130eaa68..1948b5fdf2c2 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegUtilCommandlet.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegUtilCommandlet.cpp @@ -146,7 +146,7 @@ struct FSortableDependencySort if ((AClass != BClass) && bIsAClassGrouped && bIsBClassGrouped) { - return AClass > BClass; + return BClass.LexicalLess(AClass); } if (A.DepSet != B.DepSet) @@ -430,10 +430,10 @@ int32 UAssetRegUtilCommandlet::Main(const FString& CmdLineParams) AssetRegistry->SearchAllAssets(true); FString ReorderFile; - if (FParse::Value(*CmdLineParams, TEXT("ReorderFile="), ReorderFile)) + if (FParse::Value(*CmdLineParams, TEXT("ReorderFile="), ReorderFile, false)) { FString ReorderOutput; - if (!FParse::Value(*CmdLineParams, TEXT("ReorderOutput="), ReorderOutput)) + if (!FParse::Value(*CmdLineParams, TEXT("ReorderOutput="), ReorderOutput, false)) { //if nothing specified, base it on the input name ReorderOutput = FPaths::SetExtension(FPaths::SetExtension(ReorderFile, TEXT("")) + TEXT("Reordered"), FPaths::GetExtension(ReorderFile)); diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegistryGenerator.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegistryGenerator.cpp index b60e7207562f..9f3d84f2299f 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegistryGenerator.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegistryGenerator.cpp @@ -1283,7 +1283,7 @@ bool FAssetRegistryGenerator::GenerateAssetChunkInformationCSV(const FString& Ou // Sort list so it's consistent over time AssetDataList.Sort([](const FAssetData& A, const FAssetData& B) { - return A.ObjectPath < B.ObjectPath; + return A.ObjectPath.LexicalLess(B.ObjectPath); }); // Create file for all chunks diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/CookCommandlet.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/CookCommandlet.cpp index a82efb4799f6..7d1f25a97be2 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Commandlets/CookCommandlet.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/CookCommandlet.cpp @@ -597,6 +597,8 @@ int32 UCookCommandlet::Main(const FString& CmdLineParams) bool UCookCommandlet::CookByTheBook( const TArray& Platforms, TArray& FilesInPath ) { + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("CookByTheBook")); + COOK_STAT(FScopedDurationTimer CookByTheBookTimer(DetailedCookStats::CookByTheBookTimeSec)); UCookOnTheFlyServer *CookOnTheFlyServer = NewObject(); @@ -1034,7 +1036,10 @@ bool UCookCommandlet::CookByTheBook( const TArray& Platforms, } } while (bTestCook); - VerifyEDLCookInfo(); + if (!bIterativeCooking) + { + VerifyEDLCookInfo(); + } return true; } diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/DDCCleanupCommandlet.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/DDCCleanupCommandlet.cpp new file mode 100644 index 000000000000..b75adb877042 --- /dev/null +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/DDCCleanupCommandlet.cpp @@ -0,0 +1,21 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Commandlets/DDCCleanupCommandlet.h" +#include "DDCCleanup.h" +#include "HAL/PlatformProcess.h" + +int32 UDDCCleanupCommandlet::Main(const FString& Params) +{ + if (FDDCCleanup::Get()) + { + FDDCCleanup::GetNoInit()->WaitBetweenDeletes(false); + do + { + // Cleanup works from its own thread so just wait until it's done and flush logs once in a while + FPlatformProcess::SleepNoStats(1.0f); + GLog->Flush(); + } while (FDDCCleanup::GetNoInit() && !FDDCCleanup::GetNoInit()->IsFinished()); + } + + return 0; +} diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/DerivedDataCacheCommandlet.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/DerivedDataCacheCommandlet.cpp index 73b832ac5f00..fb67123638e4 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Commandlets/DerivedDataCacheCommandlet.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/DerivedDataCacheCommandlet.cpp @@ -244,12 +244,12 @@ int32 UDerivedDataCacheCommandlet::Main( const FString& Params ) // Keep track of which packages have already been processed along with the map. { const double FindProcessedPackagesStartTime = FPlatformTime::Seconds(); - TArray ObjectsInOuter; - GetObjectsWithOuter(NULL, ObjectsInOuter, false); - for (int32 Index = 0; Index < ObjectsInOuter.Num(); Index++) + TArray AllPackages; + GetObjectsOfClass(UPackage::StaticClass(), AllPackages); + for (int32 Index = 0; Index < AllPackages.Num(); Index++) { - UPackage* Pkg = Cast(ObjectsInOuter[Index]); - if (!Pkg) + UPackage* Pkg = Cast(AllPackages[Index]); + if (!Pkg || Pkg->GetOuter()) { continue; } diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/GatherTextCommandletBase.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/GatherTextCommandletBase.cpp index 18a59ad6c643..4b185be80e5f 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Commandlets/GatherTextCommandletBase.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/GatherTextCommandletBase.cpp @@ -38,7 +38,7 @@ void UGatherTextCommandletBase::Initialize( const TSharedRef< FLocTextHelper >& { SplitPlatforms.Add(*SplitPlatformName, FString::Printf(TEXT("/%s/"), *SplitPlatformName)); } - SplitPlatforms.KeySort(TLess()); + SplitPlatforms.KeySort(FNameLexicalLess()); } } diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/GatherTextFromAssetsCommandlet.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/GatherTextFromAssetsCommandlet.cpp index 4eab8dba63f0..87a5093f7247 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Commandlets/GatherTextFromAssetsCommandlet.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/GatherTextFromAssetsCommandlet.cpp @@ -854,7 +854,7 @@ int32 UGatherTextFromAssetsCommandlet::Main(const FString& Params) } check(PackagesPendingGather.Num() == 0); - PackagesWithStaleGatherCache.Sort(); + PackagesWithStaleGatherCache.Sort(FNameLexicalLess()); if (bReportStaleGatherCache) { diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/GenerateDistillFileSetsCommandlet.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/GenerateDistillFileSetsCommandlet.cpp index 0e188581e6a8..6f6f1f09b8cb 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Commandlets/GenerateDistillFileSetsCommandlet.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/GenerateDistillFileSetsCommandlet.cpp @@ -261,11 +261,11 @@ int32 UGenerateDistillFileSetsCommandlet::Main( const FString& InParams ) AllPackageNames.Add(Package->GetName()); UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT( "Finding content referenced by %s..." ), *MapPackage ); - TArray ObjectsInOuter; - GetObjectsWithOuter(NULL, ObjectsInOuter, false); - for (int32 Index = 0; Index < ObjectsInOuter.Num(); Index++) + TArray AllPackages; + GetObjectsOfClass(UPackage::StaticClass(), AllPackages); + for (int32 Index = 0; Index < AllPackages.Num(); Index++) { - FString OtherName = ObjectsInOuter[Index]->GetOutermost()->GetName(); + FString OtherName = AllPackages[Index]->GetOutermost()->GetName(); if (!AllPackageNames.Contains(OtherName)) { AllPackageNames.Add(OtherName); diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/PackageUtilities.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/PackageUtilities.cpp index 4edf723eeea3..7e5cfa772a92 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Commandlets/PackageUtilities.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/PackageUtilities.cpp @@ -976,8 +976,8 @@ void FPkgInfoReporter_Log::GeneratePackageReport( FLinkerLoad* InLinker /*=nullp Out.Logf(ELogVerbosity::Display, TEXT("========")); for( int32 i = 0; i < Linker->NameMap.Num(); ++i ) { - FName& name = Linker->NameMap[ i ]; - Out.Logf(ELogVerbosity::Display, TEXT("\t%d: Name '%s' Comparison Index %d Display Index %d [Internal: %s, %d]"), i, *name.ToString(), name.GetComparisonIndex(), name.GetDisplayIndex(), *name.GetPlainNameString(), name.GetNumber() ); + FName name = FName::CreateFromDisplayId(Linker->NameMap[ i ], 0); + Out.Logf(ELogVerbosity::Display, TEXT("\t%d: Name '%s' Comparison Index %d Display Index %d [Internal: %s, %d]"), i, *name.ToString(), name.GetComparisonIndex().ToUnstableInt(), name.GetDisplayIndex().ToUnstableInt(), *name.GetPlainNameString(), name.GetNumber() ); } } @@ -1473,7 +1473,13 @@ int32 UPkgInfoCommandlet::Main( const FString& Params ) FPkgInfoReporter* Reporter = new FPkgInfoReporter_Log(InfoFlags, bHideOffsets); TArray FilesInPath; - if( Switches.Contains(TEXT("AllPackages")) ) + + FString PathWithPackages; + if (FParse::Value(*Params, TEXT("AllPackagesIn="), PathWithPackages)) + { + FPackageName::FindPackagesInDirectory(FilesInPath, *PathWithPackages); + } + else if( Switches.Contains(TEXT("AllPackages")) ) { FEditorFileUtils::FindAllPackageFiles(FilesInPath); } @@ -1553,6 +1559,7 @@ int32 UPkgInfoCommandlet::Main( const FString& Params ) if (!bDumpProperties) { TGuardValue GuardAllowUnversionedContentInEditor(GAllowUnversionedContentInEditor, true); + TGuardValue GuardAllowCookedContentInEditor(GAllowCookedDataInEditorBuilds, 1); TRefCountPtr LoadContext(FUObjectThreadContext::Get().GetSerializeContext()); BeginLoad(LoadContext); Linker = CreateLinkerForFilename(LoadContext, Filename); @@ -1836,8 +1843,10 @@ struct CompressAnimationsFunctor NumTotalSize += ResourceSize; + FUECompressedAnimData& CompressedData = AnimSeq->CompressedData.CompressedDataStructure; + // Looking for PerTrackCompression using 96bit translation compression. - if( AnimSeq->KeyEncodingFormat == AKF_PerTrackCompression && AnimSeq->CompressedByteStream.Num() > 0 ) + if( CompressedData.KeyEncodingFormat == AKF_PerTrackCompression && CompressedData.CompressedByteStream.Num() > 0 ) { bool bCandidate = false; @@ -1850,12 +1859,12 @@ struct CompressAnimationsFunctor // Translation { // Use the CompressedTrackOffsets stream to find the data addresses - const int32* RESTRICT TrackDataForTransKey = AnimSeq->CompressedTrackOffsets.GetData() + (TrackIndex * 2); + const int32* RESTRICT TrackDataForTransKey = CompressedData.CompressedTrackOffsets.GetData() + (TrackIndex * 2); const int32 TransKeysOffset = TrackDataForTransKey[0]; if( TransKeysOffset != INDEX_NONE ) { - const uint8* RESTRICT TrackData = AnimSeq->CompressedByteStream.GetData() + TransKeysOffset + 4; - const int32 Header = *((int32*)(AnimSeq->CompressedByteStream.GetData() + TransKeysOffset)); + const uint8* RESTRICT TrackData = CompressedData.CompressedByteStream.GetData() + TransKeysOffset + 4; + const int32 Header = *((int32*)(CompressedData.CompressedByteStream.GetData() + TransKeysOffset)); int32 KeyFormat; int32 NumKeys; @@ -1955,12 +1964,12 @@ struct CompressAnimationsFunctor // Rotation { // Use the CompressedTrackOffsets stream to find the data addresses - const int32* RESTRICT TrackDataForRotKey = AnimSeq->CompressedTrackOffsets.GetData() + (TrackIndex * 2); + const int32* RESTRICT TrackDataForRotKey = CompressedData.CompressedTrackOffsets.GetData() + (TrackIndex * 2); const int32 RotKeysOffset = TrackDataForRotKey[1]; if( RotKeysOffset != INDEX_NONE ) { - const uint8* RESTRICT TrackData = AnimSeq->CompressedByteStream.GetData() + RotKeysOffset + 4; - const int32 Header = *((int32*)(AnimSeq->CompressedByteStream.GetData() + RotKeysOffset)); + const uint8* RESTRICT TrackData = CompressedData.CompressedByteStream.GetData() + RotKeysOffset + 4; + const int32 Header = *((int32*)(CompressedData.CompressedByteStream.GetData() + RotKeysOffset)); int32 KeyFormat; int32 NumKeys; @@ -2016,11 +2025,11 @@ struct CompressAnimationsFunctor // Scale { // Use the CompressedTrackOffsets stream to find the data addresses - const int32 ScaleKeysOffset = AnimSeq->CompressedScaleOffsets.GetOffsetData(TrackIndex, 0); + const int32 ScaleKeysOffset = CompressedData.CompressedScaleOffsets.GetOffsetData(TrackIndex, 0); if( ScaleKeysOffset != INDEX_NONE ) { - const uint8* RESTRICT TrackData = AnimSeq->CompressedByteStream.GetData() + ScaleKeysOffset + 4; - const int32 Header = *((int32*)(AnimSeq->CompressedByteStream.GetData() + ScaleKeysOffset)); + const uint8* RESTRICT TrackData = CompressedData.CompressedByteStream.GetData() + ScaleKeysOffset + 4; + const int32 Header = *((int32*)(CompressedData.CompressedByteStream.GetData() + ScaleKeysOffset)); int32 KeyFormat; int32 NumKeys; diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/TextAssetCommandlet.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/TextAssetCommandlet.cpp index 493a9938312d..4a922680dc3b 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Commandlets/TextAssetCommandlet.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/TextAssetCommandlet.cpp @@ -20,6 +20,7 @@ #include "Serialization/Formatters/JsonArchiveOutputFormatter.h" #include "Serialization/MemoryWriter.h" #include "Serialization/ArchiveUObjectFromStructuredArchive.h" +#include "ProfilingDebugging/CpuProfilerTrace.h" DEFINE_LOG_CATEGORY(LogTextAsset); @@ -51,12 +52,48 @@ void FindMismatchedSerializers() int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) { + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("UTextAssetCommandlet::Main")); + TArray Blacklist; + static const FString BackupExtension = TEXT("textassetbackup"); + static const FString BackupRoundtripExtension = TEXT("textassetbackup_roundtrip"); + static const FString BackupExtension_WithDot = TEXT(".") + BackupExtension; + static const FString BackupRoundtripExtension_WithDot = TEXT(".") + BackupRoundtripExtension; + + // Repair any damage caused by a failed run of this commandlet + TArray IntermediateFilenames; + struct FVisitor : public IPlatformFile::FDirectoryVisitor + { + virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override + { + FString Extension = FPaths::GetExtension(FilenameOrDirectory); + if (!bIsDirectory && (Extension == BackupExtension)) + { + UE_LOG(LogTextAsset, Display, TEXT("Cleaning up old intermediate file %s"), FilenameOrDirectory); + + FString BinaryFilename = FPaths::GetPath(FilenameOrDirectory) / FPaths::GetBaseFilename(FilenameOrDirectory); + FString TextFilename = FPaths::ChangeExtension(BinaryFilename, FPackageName::GetTextAssetPackageExtension()); + FString RoundtripBackup = BinaryFilename + BackupRoundtripExtension_WithDot; + + IFileManager::Get().Delete(*BinaryFilename); + IFileManager::Get().Delete(*TextFilename); + IFileManager::Get().Delete(*RoundtripBackup); + + IFileManager::Get().Move(*BinaryFilename, FilenameOrDirectory); + } + return true; + } + } RepairVisitor; + + IFileManager::Get().IterateDirectoryRecursively(*FPaths::ProjectContentDir(), RepairVisitor); + IFileManager::Get().IterateDirectoryRecursively(*FPaths::EngineContentDir(), RepairVisitor); + FString ModeString = TEXT("ResaveText"); FString IterationsString = TEXT("1"); - FString FilenameFilterString, OutputPathString; + FString FilenameFilterString, OutputPathString, CSVFilename; + FParse::Value(*CmdLineParams, TEXT("csv="), CSVFilename); FParse::Value(*CmdLineParams, TEXT("mode="), ModeString); FParse::Value(*CmdLineParams, TEXT("filter="), FilenameFilterString); FParse::Value(*CmdLineParams, TEXT("outputpath="), OutputPathString); @@ -67,8 +104,9 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) ResaveText, ResaveBinary, RoundTrip, + LoadBinary, LoadText, - FindMismatchedSerializers + FindMismatchedSerializers, }; TMap Modes; @@ -76,6 +114,7 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) Modes.Add(TEXT("ResaveBinary"), EMode::ResaveBinary); Modes.Add(TEXT("RoundTrip"), EMode::RoundTrip); Modes.Add(TEXT("LoadText"), EMode::LoadText); + Modes.Add(TEXT("LoadBinary"), EMode::LoadBinary); Modes.Add(TEXT("FindMismatchedSerializers"), EMode::FindMismatchedSerializers); check(Modes.Contains(ModeString)); @@ -124,6 +163,13 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) //IFileManager::Get().FindFilesRecursive(InputAssetFilenames, *BasePath, *(Wildcard + FPackageName::GetTextMapPackageExtension()), true, false, false); break; } + + case EMode::LoadBinary: + { + IFileManager::Get().FindFilesRecursive(InputAssetFilenames, *ProjectContentDir, *(Wildcard + FPackageName::GetAssetPackageExtension()), true, false, true); + //IFileManager::Get().FindFilesRecursive(InputAssetFilenames, *BasePath, *(Wildcard + FPackageName::GetTextMapPackageExtension()), true, false, false); + break; + } } TArray> FilesToProcess; @@ -173,6 +219,7 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) } case EMode::LoadText: + case EMode::LoadBinary: { break; } @@ -184,23 +231,6 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) } } - TArray IntermediateFilenames; - struct FVisitor : public IPlatformFile::FDirectoryVisitor - { - virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override - { - if (!bIsDirectory && FString(FilenameOrDirectory).Contains(TEXT(".txtassettemp"))) - { - UE_LOG(LogTextAsset, Display, TEXT("Cleaning up old intermediate file %s"), FilenameOrDirectory); - IFileManager::Get().Delete(FilenameOrDirectory); - } - return true; - } - } Visitor; - - IFileManager::Get().IterateDirectoryRecursively(*FPaths::ProjectContentDir(), Visitor); - IFileManager::Get().IterateDirectoryRecursively(*FPaths::EngineContentDir(), Visitor); - const FString FailedDiffsPath = FPaths::ProjectSavedDir() / TEXT("FailedDiffs"); static const bool bKeepFailedDiffs = FParse::Param(FCommandLine::Get(), TEXT("keepfaileddiffs")); if (bKeepFailedDiffs) @@ -211,6 +241,17 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) float TotalPackageLoadTime = 0.0; float TotalPackageSaveTime = 0.0; + FArchive* CSVWriter = nullptr; + if (CSVFilename.Len() > 0) + { + CSVWriter = IFileManager::Get().CreateFileWriter(*CSVFilename); + if (CSVWriter != nullptr) + { + FString CSVLine = FString::Printf(TEXT("Total Time,Num Files,AvgFileTime,MinFileTime,MaxFileTime,TotalLoadTime\n")); + CSVWriter->Serialize(TCHAR_TO_ANSI(*CSVLine), CSVLine.Len()); + } + } + for (int32 Iteration = 0; Iteration < NumSaveIterations; ++Iteration) { if (NumSaveIterations > 1) @@ -227,6 +268,7 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) FString MinTimePackage; float IterationPackageLoadTime = 0.0; float IterationPackageSaveTime = 0.0; + double ThisPackageLoadTime = 0.0; TArray PhaseSuccess; TArray> PhaseFails; @@ -238,6 +280,8 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) FString SourceLongPackageName = FPackageName::FilenameToLongPackageName(SourceFilename); FString DestinationFilename = FileToProcess.Get<1>(); + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*SourceFilename); + IntermediateFilenames.Empty(); double StartTime = FPlatformTime::Seconds(); @@ -251,7 +295,7 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) IFileManager::Get().Delete(*WorkingFilenames[1], false, false, true); - FString SourceBackupFilename = SourceFilename + TEXT(".bak"); + FString SourceBackupFilename = SourceFilename + BackupExtension_WithDot; if (IFileManager::Get().FileExists(*SourceBackupFilename)) { IFileManager::Get().Delete(*SourceFilename, false, false, true); @@ -266,7 +310,7 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) CollectGarbage(RF_NoFlags, true); // Make a copy of the resaved source package which we can use as the base revision for each test - FString BaseBinaryPackageBackup = SourceFilename + TEXT(".bak2"); + FString BaseBinaryPackageBackup = SourceFilename + BackupRoundtripExtension_WithDot; IFileManager::Get().Copy(*BaseBinaryPackageBackup, *SourceFilename, true); FSHAHash SourceHash; @@ -310,12 +354,25 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) case 1: // text only { Bucket = 1; + + if (i > 0) + { + IFileManager::Get().Delete(*WorkingFilenames[0]); + } + break; } case 2: // alternate { Bucket = i % 2; + + if ((i > 0) && Bucket == 0) + { + // We're doing alternating text/binary saves, so we need to delete the text version as we have no way of forcing the load to choose between text and binary + IFileManager::Get().Delete(*WorkingFilenames[0]); + } + break; } @@ -326,12 +383,6 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) } }; - if (Phase == 2 && Bucket == 1) - { - // We're doing alternating text/binary saves, so we need to delete the text version as we have no way of forcing the load to choose between text and binary - IFileManager::Get().Delete(*WorkingFilenames[1]); - } - UPackage* Package = LoadPackage(nullptr, *SourceLongPackageName, LOAD_None); SavePackageHelper(Package, *WorkingFilenames[Bucket], RF_Standalone, GWarn, nullptr, SAVE_KeepGUID); ResetLoaders(Package); @@ -368,7 +419,8 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) bPhasesMatched[Phase] = bPhasesMatched[Phase] && Hash == Refs[0]; } - UE_LOG(LogTextAsset, Display, TEXT("\tPass %i [%s] %s"), Pass++, *Hash.ToString(), bPhasesMatched[Phase] ? TEXT("OK") : TEXT("FAILED")); + UE_LOG(LogTextAsset, Display, TEXT("\tPass %i [%s] %s"), Pass, *Hash.ToString(), bPhasesMatched[Phase] ? TEXT("OK") : TEXT("FAILED")); + Pass++; } if (!bPhasesMatched[Phase]) @@ -454,35 +506,40 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) IterationPackageLoadTime += Timer; TotalPackageLoadTime += Timer; + bool bSaveSuccessful = false; + if (Package) { { SCOPE_SECONDS_COUNTER(Timer); IFileManager::Get().Delete(*DestinationFilename, false, true, true); - SavePackageHelper(Package, *DestinationFilename, RF_Standalone, GWarn, nullptr, SAVE_KeepGUID); + bSaveSuccessful = SavePackageHelper(Package, *DestinationFilename, RF_Standalone, GWarn, nullptr, SAVE_KeepGUID); } TotalPackageSaveTime += Timer; IterationPackageSaveTime += Timer; } - if (bVerifyJson) + if (bSaveSuccessful) { - FArchive* File = IFileManager::Get().CreateFileReader(*DestinationFilename); - TSharedPtr< FJsonObject > RootObject; - TSharedRef< TJsonReader > Reader = TJsonReaderFactory::Create(File); - ensure(FJsonSerializer::Deserialize(Reader, RootObject)); - delete File; - } + if (bVerifyJson) + { + FArchive* File = IFileManager::Get().CreateFileReader(*DestinationFilename); + TSharedPtr< FJsonObject > RootObject; + TSharedRef< TJsonReader > Reader = TJsonReaderFactory::Create(File); + ensure(FJsonSerializer::Deserialize(Reader, RootObject)); + delete File; + } - if (OutputPathString.Len() > 0) - { - FString CopyFilename = DestinationFilename; - FPaths::MakePathRelativeTo(CopyFilename, *FPaths::RootDir()); - CopyFilename = OutputPathString / CopyFilename; - CopyFilename.RemoveFromEnd(TEXT(".tmp")); - IFileManager::Get().MakeDirectory(*FPaths::GetPath(CopyFilename)); - IFileManager::Get().Move(*CopyFilename, *DestinationFilename); + if (OutputPathString.Len() > 0) + { + FString CopyFilename = DestinationFilename; + FPaths::MakePathRelativeTo(CopyFilename, *FPaths::RootDir()); + CopyFilename = OutputPathString / CopyFilename; + CopyFilename.RemoveFromEnd(TEXT(".tmp")); + IFileManager::Get().MakeDirectory(*FPaths::GetPath(CopyFilename)); + IFileManager::Get().Move(*CopyFilename, *DestinationFilename); + } } break; @@ -491,17 +548,37 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) case EMode::LoadText: { UPackage* Package = nullptr; + UE_LOG(LogTextAsset, Display, TEXT("Loading Text Asset '%s'"), *SourceFilename); CollectGarbage(RF_NoFlags, true); - double Timer = 0.0; + ThisPackageLoadTime = 0.0; { - SCOPE_SECONDS_COUNTER(Timer); - UE_LOG(LogTextAsset, Display, TEXT("Loading Text Asset '%s'"), *SourceFilename); + SCOPE_SECONDS_COUNTER(ThisPackageLoadTime); Package = LoadPackage(nullptr, *SourceFilename, 0); } CollectGarbage(RF_NoFlags, true); - IterationPackageLoadTime += Timer; - TotalPackageLoadTime += Timer; + IterationPackageLoadTime += ThisPackageLoadTime; + TotalPackageLoadTime += ThisPackageLoadTime; + + Package = nullptr; + + break; + } + + case EMode::LoadBinary: + { + UPackage* Package = nullptr; + UE_LOG(LogTextAsset, Display, TEXT("Loading Binary Asset '%s'"), *SourceFilename); + CollectGarbage(RF_NoFlags, true); + + ThisPackageLoadTime = 0.0; + { + SCOPE_SECONDS_COUNTER(ThisPackageLoadTime); + Package = LoadPackage(nullptr, *SourceFilename, 0); + } + CollectGarbage(RF_NoFlags, true); + IterationPackageLoadTime += ThisPackageLoadTime; + TotalPackageLoadTime += ThisPackageLoadTime; Package = nullptr; @@ -512,16 +589,33 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) double EndTime = FPlatformTime::Seconds(); double Time = EndTime - StartTime; - if (Time > MaxTime) + if (Mode == EMode::LoadBinary || Mode == EMode::LoadText) { - MaxTime = Time; - MaxTimePackage = SourceFilename; - } + if (ThisPackageLoadTime > MaxTime) + { + MaxTime = ThisPackageLoadTime; + MaxTimePackage = SourceFilename; + } - if (Time < MinTime) + if (ThisPackageLoadTime < MinTime) + { + MinTime = ThisPackageLoadTime; + MinTimePackage = SourceFilename; + } + } + else { - MinTime = Time; - MinTimePackage = SourceFilename; + if (Time > MaxTime) + { + MaxTime = Time; + MaxTimePackage = SourceFilename; + } + + if (Time < MinTime) + { + MinTime = Time; + MinTimePackage = SourceFilename; + } } TotalTime += Time; @@ -553,13 +647,35 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) } } + double AvgFileTime, MinFileTime, MaxFileTime; + + if (Mode == EMode::LoadBinary || Mode == EMode::LoadText) + { + AvgFileTime = IterationPackageLoadTime; + } + else + { + AvgFileTime = TotalTime; + } + + AvgFileTime /= (double)NumFiles; + MinFileTime = MinTime; + MaxFileTime = MaxTime; + UE_LOG(LogTextAsset, Display, TEXT("\tTotal Time:\t%.2fs"), TotalTime); - UE_LOG(LogTextAsset, Display, TEXT("\tAvg File Time: \t%.2fms"), (TotalTime * 1000.0) / (double)NumFiles); - UE_LOG(LogTextAsset, Display, TEXT("\tMin File Time: \t%.2fms (%s)"), MinTime * 1000.0, *MinTimePackage); - UE_LOG(LogTextAsset, Display, TEXT("\tMax File Time: \t%.2fms (%s)"), MaxTime * 1000.0, *MaxTimePackage); + UE_LOG(LogTextAsset, Display, TEXT("\tTotal Files:\t%i"), NumFiles); + UE_LOG(LogTextAsset, Display, TEXT("\tAvg File Time: \t%.2fms"), AvgFileTime * 1000.0); + UE_LOG(LogTextAsset, Display, TEXT("\tMin File Time: \t%.2fms (%s)"), MinFileTime * 1000.0, *MinTimePackage); + UE_LOG(LogTextAsset, Display, TEXT("\tMax File Time: \t%.2fms (%s)"), MaxFileTime * 1000.0, *MaxTimePackage); UE_LOG(LogTextAsset, Display, TEXT("\tTotal Package Load Time: \t%.2fs"), IterationPackageLoadTime); - if (Mode != EMode::LoadText) + if (CSVWriter != nullptr) + { + FString CSVLine = FString::Printf(TEXT("%f,%i,%f,%f,%f,%f\n"), TotalTime, NumFiles, AvgFileTime, MinFileTime, MaxFileTime, IterationPackageLoadTime); + CSVWriter->Serialize(TCHAR_TO_ANSI(*CSVLine), CSVLine.Len()); + } + + if (Mode != EMode::LoadText && Mode != EMode::ResaveText) { UE_LOG(LogTextAsset, Display, TEXT("\tTotal Package Save Time: \t%.2fs"), IterationPackageSaveTime); } @@ -572,6 +688,11 @@ int32 UTextAssetCommandlet::Main(const FString& CmdLineParams) UE_LOG(LogTextAsset, Display, TEXT("\tTotal Files Processed: \t%i"), FilesToProcess.Num()); UE_LOG(LogTextAsset, Display, TEXT("\tAvg Iteration Package Load Time: \t%.2fs"), TotalPackageLoadTime / (float)NumSaveIterations); + if (CSVWriter != nullptr) + { + delete CSVWriter; + } + if (Mode != EMode::LoadText) { UE_LOG(LogTextAsset, Display, TEXT("\tAvg Iteration Save Time: \t%.2fs"), TotalPackageSaveTime / (float)NumSaveIterations); diff --git a/Engine/Source/Editor/UnrealEd/Private/CookOnTheFlyServer.cpp b/Engine/Source/Editor/UnrealEd/Private/CookOnTheFlyServer.cpp index cdf07b743d85..0ebb34030577 100644 --- a/Engine/Source/Editor/UnrealEd/Private/CookOnTheFlyServer.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/CookOnTheFlyServer.cpp @@ -237,7 +237,9 @@ public: void Start() { if (StartTime) + { return; + } StartTime = FPlatformTime::Cycles64(); } @@ -245,7 +247,9 @@ public: void Stop() { if (!StartTime) + { return; + } HierarchyTimerInfo->Length += FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - StartTime); ++HierarchyTimerInfo->HitCount; @@ -307,18 +311,11 @@ void ClearHierarchyTimers() #define CREATE_TIMER(name, incrementScope) FScopeTimer ScopeTimer##name(__COUNTER__, #name, incrementScope); -#define SCOPE_TIMER(name) CREATE_TIMER(name, true); ScopeTimer##name.Start(); +#define SCOPE_TIMER(name) TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT(#name)); CREATE_TIMER(name, true); ScopeTimer##name.Start(); -#define ACCUMULATE_TIMER(name) CREATE_TIMER(name, false); -#define ACCUMULATE_TIMER_START(name) ScopeTimer##name.Start(); -#define ACCUMULATE_TIMER_STOP(name) ScopeTimer##name.Stop(); #else #define SCOPE_TIMER(name) -#define ACCUMULATE_TIMER(name) -#define ACCUMULATE_TIMER_START(name) -#define ACCUMULATE_TIMER_STOP(name) - void OutputHierarchyTimers() {} void ClearHierarchyTimers() {} #endif @@ -969,12 +966,15 @@ struct CookRequestQueue FScopeLock ScopeLock(&SynchronizationObject); const TArray* Platforms = PlatformList.Find(Filename); if (Platforms == NULL) + { return false; - + } for (const FName& PlatformName : PlatformNames) { if (!Platforms->Contains(PlatformName)) + { return false; + } } return true; } @@ -984,7 +984,9 @@ struct CookRequestQueue FScopeLock ScopeLock(&SynchronizationObject); const TArray* Platforms = PlatformList.Find(Filename); if (Platforms == NULL) + { return false; + } return true; } @@ -1227,7 +1229,7 @@ struct FPackageTracker : public FUObjectArray::FUObjectCreateListener, public FU auto Package = const_cast(static_cast(Object)); if (Package->GetOuter() == nullptr) - { + { LoadedPackages.Add(Package); NewPackages.Add(Package); @@ -1239,7 +1241,7 @@ struct FPackageTracker : public FUObjectArray::FUObjectCreateListener, public FU virtual void NotifyUObjectDeleted(const class UObjectBase *Object, int32 Index) override { if (Object->GetClass() == UPackage::StaticClass()) - { + { auto Package = const_cast(static_cast(Object)); LoadedPackages.Remove(Package); @@ -1249,6 +1251,12 @@ struct FPackageTracker : public FUObjectArray::FUObjectCreateListener, public FU } } + virtual void OnUObjectArrayShutdown() override + { + GUObjectArray.RemoveUObjectDeleteListener(this); + GUObjectArray.RemoveUObjectCreateListener(this); + } + // This is the set of packages which have already had PostLoadFixup called TSet PostLoadFixupPackages; @@ -1272,6 +1280,7 @@ struct FPackageTracker : public FUObjectArray::FUObjectCreateListener, public FU FThreadSafeSet NeverCookPackageList; FThreadSafeSet UncookedEditorOnlyPackages; // set of packages that have been rejected due to being referenced by editor-only properties + TMap> PlatformSpecificNeverCookPackages; // Currently targeted platforms TArray AllTargetPlatformNames; @@ -1343,10 +1352,14 @@ void FPackageTracker::FilterLoadedPackage(UPackage* Package) const FName StandardPackageFName = PackageNameCache->GetCachedStandardPackageFileFName(Package); if (StandardPackageFName == NAME_None) + { return; // if we have name none that means we are in core packages or something... + } if (CookedPackages.Exists(StandardPackageFName, AllTargetPlatformNames)) + { return; + } PackagesPendingSave.Add(Package); } @@ -1354,7 +1367,9 @@ void FPackageTracker::FilterLoadedPackage(UPackage* Package) void FPackageTracker::UpdatePackagesPendingSave() { if (bPackagesPendingSaveDirty == false) + { return; + } PackagesPendingSave.Empty(PackagesPendingSave.Num()); @@ -1449,6 +1464,8 @@ UCookOnTheFlyServer::~UCookOnTheFlyServer() // this tick only happens in the editor cook commandlet directly calls tick on the side void UCookOnTheFlyServer::Tick(float DeltaTime) { + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("UCookOnTheFlyServer::Tick")); + check(IsCookingInEditor()); if (IsCookByTheBookMode() && !IsCookByTheBookRunning() && !GIsSlowTask) @@ -2054,17 +2071,17 @@ UCookOnTheFlyServer::FReentryData& UCookOnTheFlyServer::GetReentryData(const UPa { FReentryData& CurrentReentryData = PackageReentryData.FindOrAdd(Package->GetFName()); - if ( (CurrentReentryData.bIsValid == false) && (Package->IsFullyLoaded() == true)) + if ((CurrentReentryData.bIsValid == false) && (Package->IsFullyLoaded() == true)) { CurrentReentryData.bIsValid = true; CurrentReentryData.FileName = Package->GetFName(); - GetObjectsWithOuter(Package, CurrentReentryData.CachedObjectsInOuter); -} + GetObjectsWithOuter(Package, CurrentReentryData.CachedObjectsInOuter); + } return CurrentReentryData; } -uint32 UCookOnTheFlyServer::TickCookOnTheSide( const float TimeSlice, uint32 &CookedPackageCount, ECookTickFlags TickFlags ) +uint32 UCookOnTheFlyServer::TickCookOnTheSide(const float TimeSlice, uint32 &CookedPackageCount, ECookTickFlags TickFlags) { if (IsCookByTheBookMode() && CookByTheBookOptions->bFullLoadAndSave) { @@ -2091,41 +2108,41 @@ uint32 UCookOnTheFlyServer::TickCookOnTheSide( const float TimeSlice, uint32 &Co // This is all the target platforms which we needed to process requests for this iteration // we use this in the unsolicited packages processing below TArray AllTargetPlatformNames; - + while (!GIsRequestingExit || CurrentCookMode == ECookMode::CookByTheBook) { if (HasCookRequests()) { const float CurrentProgressDisplayTime = FPlatformTime::Seconds(); - if ( LastCookedPackagesCount != PackageTracker->CookedPackages.Num() - || LastCookRequestsCount != PackageTracker->CookRequests.Num() - || (CurrentProgressDisplayTime - LastProgressDisplayTime) > GCookProgressRepeatTime) + if (LastCookedPackagesCount != PackageTracker->CookedPackages.Num() + || LastCookRequestsCount != PackageTracker->CookRequests.Num() + || (CurrentProgressDisplayTime - LastProgressDisplayTime) > GCookProgressRepeatTime) { - UE_CLOG(!(TickFlags & ECookTickFlags::HideProgressDisplay) && (GCookProgressDisplay & (int32)ECookProgressDisplayMode::RemainingPackages), - LogCook, - Display, - TEXT("Cooked packages %d Packages Remain %d Total %d"), - PackageTracker->CookedPackages.Num(), - PackageTracker->CookRequests.Num(), + UE_CLOG(!(TickFlags & ECookTickFlags::HideProgressDisplay) && (GCookProgressDisplay & (int32)ECookProgressDisplayMode::RemainingPackages), + LogCook, + Display, + TEXT("Cooked packages %d Packages Remain %d Total %d"), + PackageTracker->CookedPackages.Num(), + PackageTracker->CookRequests.Num(), PackageTracker->CookedPackages.Num() + PackageTracker->CookRequests.Num()); LastCookedPackagesCount = PackageTracker->CookedPackages.Num(); - LastCookRequestsCount = PackageTracker->CookRequests.Num(); + LastCookRequestsCount = PackageTracker->CookRequests.Num(); LastProgressDisplayTime = CurrentProgressDisplayTime; } } // if we just cooked a map then don't process anything the rest of this tick - if ( Result & COSR_RequiresGC ) + if (Result & COSR_RequiresGC) { break; } - if ( IsCookByTheBookMode() ) + if (IsCookByTheBookMode()) { - check( CookByTheBookOptions ); - if ( CookByTheBookOptions->bCancel ) + check(CookByTheBookOptions); + if (CookByTheBookOptions->bCancel) { CancelCookByTheBook(); } @@ -2163,40 +2180,32 @@ uint32 UCookOnTheFlyServer::TickCookOnTheSide( const float TimeSlice, uint32 &Co // prevent autosave from happening until we are finished cooking // causes really bad hitches - if ( GUnrealEd ) + if (GUnrealEd) { const static float SecondsWarningTillAutosave = 10.0f; GUnrealEd->GetPackageAutoSaver().ForceMinimumTimeTillAutoSave(SecondsWarningTillAutosave); } - if (PackageTracker->CookedPackages.Exists(ToBuild)) - { -#if DEBUG_COOKONTHEFLY - UE_LOG(LogCook, Display, TEXT("Package for platform already cooked %s, discarding request"), *ToBuild.GetFilename().ToString()); -#endif - continue; - } - #if DEBUG_COOKONTHEFLY UE_LOG(LogCook, Display, TEXT("Processing package %s"), *ToBuild.GetFilename().ToString()); #endif SCOPE_TIMER(TickCookOnTheSide); - check( ToBuild.IsValid() ); + check(ToBuild.IsValid()); const TArray& TargetPlatformNames = ToBuild.GetPlatformNames(); #if OUTPUT_TIMING //FScopeTimer PackageManualTimer( ToBuild.GetFilename().ToString(), false ); #endif - for ( const FName& PlatformName : TargetPlatformNames ) + for (const FName& PlatformName : TargetPlatformNames) { AllTargetPlatformNames.AddUnique(PlatformName); } - for ( const FName& PlatformName : AllTargetPlatformNames ) + for (const FName& PlatformName : AllTargetPlatformNames) { - if ( ToBuild.HasPlatform(PlatformName) == false ) + if (ToBuild.HasPlatform(PlatformName) == false) { ToBuild.AddPlatform(PlatformName); } @@ -2205,14 +2214,14 @@ uint32 UCookOnTheFlyServer::TickCookOnTheSide( const float TimeSlice, uint32 &Co const FString BuildFilename = ToBuild.GetFilename().ToString(); bool bShouldCook = true; - - if( CookByTheBookOptions && CookByTheBookOptions->bErrorOnEngineContentUse ) + + if (CookByTheBookOptions && CookByTheBookOptions->bErrorOnEngineContentUse) { check(IsCookingDLC()); FString DLCPath = FPaths::Combine(*GetBaseDirectoryForDLC(), TEXT("Content")); - if ( ToBuild.GetFilename().ToString().StartsWith(DLCPath) == false ) // if we don't start with the dlc path then we shouldn't be cooking this data + if (ToBuild.GetFilename().ToString().StartsWith(DLCPath) == false) // if we don't start with the dlc path then we shouldn't be cooking this data { - UE_LOG(LogCook, Error, TEXT("Engine or Game content %s is being referenced by DLC!"), *ToBuild.GetFilename().ToString() ); + UE_LOG(LogCook, Error, TEXT("Engine or Game content %s is being referenced by DLC!"), *ToBuild.GetFilename().ToString()); bShouldCook = false; } } @@ -2228,11 +2237,11 @@ uint32 UCookOnTheFlyServer::TickCookOnTheSide( const float TimeSlice, uint32 &Co UPackage* PackageForCooking = nullptr; - if ( bShouldCook ) // if we should cook the package then cook it otherwise add it to the list of already cooked packages below + if (bShouldCook) // if we should cook the package then cook it otherwise add it to the list of already cooked packages below { UPackage* Package = LoadPackageForCooking(BuildFilename); - if ( Package ) + if (Package) { FString Name = Package->GetPathName(); FString PackageFilename(PackageNameCache->GetCachedStandardPackageFilename(Package)); @@ -2257,7 +2266,7 @@ uint32 UCookOnTheFlyServer::TickCookOnTheSide( const float TimeSlice, uint32 &Co } } - + if (PackageForCooking == nullptr) { // if we are iterative cooking the package might already be cooked @@ -2278,7 +2287,7 @@ uint32 UCookOnTheFlyServer::TickCookOnTheSide( const float TimeSlice, uint32 &Co // if we find the file this means it was cooked on a previous cook, however source package can't be found now. // this could be because the source package was deleted or renamed, and we are using iterative cooking // perhaps in this case we should delete it? - UE_LOG(LogCook, Warning, TEXT("Found cooked file which shouldn't exist as it failed loading %s"), *SandboxFilename); + UE_LOG(LogCook, Warning, TEXT("Found cooked file which shouldn't exist as it failed loading %s"), *SandboxFilename); IFileManager::Get().Delete(*SandboxFilename); } } @@ -2291,9 +2300,9 @@ uint32 UCookOnTheFlyServer::TickCookOnTheSide( const float TimeSlice, uint32 &Co ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef(); TArray TargetPlatforms; - for ( const FName& TargetPlatformName : AllTargetPlatformNames ) + for (const FName& TargetPlatformName : AllTargetPlatformNames) { - TargetPlatforms.Add( TPM.FindTargetPlatform( TargetPlatformName.ToString() ) ); + TargetPlatforms.Add(TPM.FindTargetPlatform(TargetPlatformName.ToString())); } @@ -2304,10 +2313,10 @@ uint32 UCookOnTheFlyServer::TickCookOnTheSide( const float TimeSlice, uint32 &Co { SCOPE_TIMER(CallBeginCacheForCookedPlatformData); // cache the resources for this package for each platform - + bIsAllDataCached &= BeginPackageCacheForCookedPlatformData(PackageForCooking, TargetPlatforms, Timer); - if( bIsAllDataCached ) + if (bIsAllDataCached) { bIsAllDataCached &= FinishPackageCacheForCookedPlatformData(PackageForCooking, TargetPlatforms, Timer); } @@ -2317,7 +2326,7 @@ uint32 UCookOnTheFlyServer::TickCookOnTheSide( const float TimeSlice, uint32 &Co bool ShouldTickPrecache = true; // if we are ready to save then don't waste time precaching other stuff - if ( bIsAllDataCached == true ) + if (bIsAllDataCached == true) { ShouldTickPrecache = false; } @@ -2329,14 +2338,14 @@ uint32 UCookOnTheFlyServer::TickCookOnTheSide( const float TimeSlice, uint32 &Co else { // if we are doing no shader compilation right now try and precache something so that we load up the cpu - if ( GShaderCompilingManager->GetNumRemainingJobs() == 0 ) + if (GShaderCompilingManager->GetNumRemainingJobs() == 0) { ShouldTickPrecache = true; } } // cook on the fly mode we don't want to precache here because save package is going to stall on this package, we don't want to flood the system with precache requests before we stall - if (IsCookOnTheFlyMode()) + if (IsCookOnTheFlyMode()) { ShouldTickPrecache = false; } @@ -2353,7 +2362,7 @@ uint32 UCookOnTheFlyServer::TickCookOnTheSide( const float TimeSlice, uint32 &Co } ProcessUnsolicitedPackages(); - + // in cook by the book bail out early because shaders and compiled for the primary package we are trying to save. // note in this case we also put the package at the end of the queue that queue might be reordered if we do partial gc if ((bIsAllDataCached == false) && IsCookByTheBookMode() && !IsRealtimeMode()) @@ -2376,18 +2385,18 @@ uint32 UCookOnTheFlyServer::TickCookOnTheSide( const float TimeSlice, uint32 &Co SaveCookedPackages(PackageForCooking, AllTargetPlatformNames, TargetPlatforms, Timer, /* out */ CookedPackageCount, /* out */ Result); - if ( Timer.IsTimeUp() ) + if (Timer.IsTimeUp()) { break; } } - - if ( IsCookOnTheFlyMode() && (IsCookingInEditor() == false) ) + + if (IsCookOnTheFlyMode() && (IsCookingInEditor() == false)) { static int32 TickCounter = 0; ++TickCounter; - if ( TickCounter > 50 ) + if (TickCounter > 50) { // dump stats every 50 ticks or so DumpStats(); @@ -2426,10 +2435,14 @@ bool UCookOnTheFlyServer::BeginPackageCacheForCookedPlatformData(UPackage* Packa FReentryData& CurrentReentryData = GetReentryData(Package); if (CurrentReentryData.bIsValid == false) + { return true; + } if (CurrentReentryData.bBeginCacheFinished) + { return true; + } for (; CurrentReentryData.BeginCacheCount < CurrentReentryData.CachedObjectsInOuter.Num(); ++CurrentReentryData.BeginCacheCount) { @@ -2492,10 +2505,14 @@ bool UCookOnTheFlyServer::FinishPackageCacheForCookedPlatformData(UPackage* Pack FReentryData& CurrentReentryData = GetReentryData(Package); if (CurrentReentryData.bIsValid == false) + { return true; + } if (CurrentReentryData.bFinishedCacheFinished) + { return true; + } for (UObject* Obj : CurrentReentryData.CachedObjectsInOuter) { @@ -2619,8 +2636,9 @@ UPackage* UCookOnTheFlyServer::LoadPackageForCooking(const FString& BuildFilenam void UCookOnTheFlyServer::ProcessUnsolicitedPackages() { if (IsCookByTheBookMode() && CookByTheBookOptions->bDisableUnsolicitedPackages) + { return; - + } // Ensure sublevels are loaded by iterating all recently loaded packages and invoking // PostLoadPackageFixup @@ -2630,10 +2648,10 @@ void UCookOnTheFlyServer::ProcessUnsolicitedPackages() TArray NewPackages = PackageTracker->GetNewPackages(); for (UPackage* Package : NewPackages) - { + { PostLoadPackageFixup(Package); - } } + } } void UCookOnTheFlyServer::SaveCookedPackages( @@ -2665,8 +2683,8 @@ void UCookOnTheFlyServer::SaveCookedPackages( if (PackageToSave) { - if (PackagesToSave.Num()) - { + if (PackagesToSave.Num()) + { UPackage* First = PackagesToSave[0]; PackagesToSave.Add(First); PackagesToSave[0] = PackageToSave; @@ -3039,48 +3057,47 @@ void UCookOnTheFlyServer::PostLoadPackageFixup(UPackage* Package) // Perform special processing for UWorld - UWorld* World = UWorld::FindWorldInPackage(Package); - check(World); + UWorld* World = UWorld::FindWorldInPackage(Package); + check(World); - World->PersistentLevel->HandleLegacyMapBuildData(); + World->PersistentLevel->HandleLegacyMapBuildData(); if (IsCookByTheBookMode() == false) - { + { return; } - GIsCookerLoadingPackage = true; - if (World->GetStreamingLevels().Num()) - { - TSet NeverCookPackageNames; + GIsCookerLoadingPackage = true; + if (World->GetStreamingLevels().Num()) + { + TSet NeverCookPackageNames; PackageTracker->NeverCookPackageList.GetValues(NeverCookPackageNames); UE_LOG(LogCook, Display, TEXT("Loading secondary levels for package '%s'"), *World->GetName()); - World->LoadSecondaryLevels(true, &NeverCookPackageNames); - } - GIsCookerLoadingPackage = false; + World->LoadSecondaryLevels(true, &NeverCookPackageNames); + } + GIsCookerLoadingPackage = false; - TArray NewPackagesToCook; + TArray NewPackagesToCook; - // Collect world composition tile packages to cook - if (World->WorldComposition) - { - World->WorldComposition->CollectTilesToCook(NewPackagesToCook); - } + // Collect world composition tile packages to cook + if (World->WorldComposition) + { + World->WorldComposition->CollectTilesToCook(NewPackagesToCook); + } - for (const FString& PackageName : NewPackagesToCook) - { + for (const FString& PackageName : NewPackagesToCook) + { FName StandardPackageFName = PackageNameCache->GetCachedStandardPackageFileFName(FName(*PackageName)); - if (StandardPackageFName != NAME_None) - { - RequestPackage(StandardPackageFName, false); - } - } + if (StandardPackageFName != NAME_None) + { + RequestPackage(StandardPackageFName, false); + } + } } - void UCookOnTheFlyServer::TickPrecacheObjectsForPlatforms(const float TimeSlice, const TArray& TargetPlatforms) { @@ -3115,7 +3132,9 @@ void UCookOnTheFlyServer::TickPrecacheObjectsForPlatforms(const float TimeSlice, ++LastUpdateTick; if (Timer.IsTimeUp()) + { return; + } bool AllMaterialsCompiled = true; // queue up some shaders for compilation @@ -3144,7 +3163,9 @@ void UCookOnTheFlyServer::TickPrecacheObjectsForPlatforms(const float TimeSlice, } if (Timer.IsTimeUp()) + { return; + } if (GShaderCompilingManager->GetNumRemainingJobs() > MaxPrecacheShaderJobs) { @@ -3177,7 +3198,9 @@ void UCookOnTheFlyServer::TickPrecacheObjectsForPlatforms(const float TimeSlice, Texture->BeginCacheForCookedPlatformData(TargetPlatform); } if (Timer.IsTimeUp()) + { return; + } } } @@ -3238,12 +3261,12 @@ TArray UCookOnTheFlyServer::GetUnsolicitedPackages(const TArrayGetCachedStandardPackageFileFName(Package); - if (StandardPackageFName == NAME_None) + if (StandardPackageFName == NAME_None) continue; // if we have name none that means we are in core packages or something... if (PackageTracker->CookedPackages.Exists(StandardPackageFName, TargetPlatformNames)) - continue; - + continue; + PackagesToSave.Add(Package); UE_LOG(LogCook, Verbose, TEXT("Found unsolicited package to cook '%s'"), *Package->GetName()); @@ -3553,14 +3576,14 @@ void UCookOnTheFlyServer::MarkGCPackagesToKeepForCooker() // then sort by the number of dependencies which are referenced by the package // we want to process the packages with the highest dependencies so that they can // be evicted from memory and are likely to be able to be released on next GC pass - PackageTracker->CookRequests.Sort([&PackageDependenciesCount,&LoadedPackages](const FName& A, const FName& B) - { - int32 ADependencies = PackageDependenciesCount.FindChecked(A); - int32 BDependencies = PackageDependenciesCount.FindChecked(B); - bool ALoaded = LoadedPackages.Contains(A); - bool BLoaded = LoadedPackages.Contains(B); - return (ALoaded == BLoaded) ? (ADependencies > BDependencies) : ALoaded > BLoaded; - } + PackageTracker->CookRequests.Sort([&PackageDependenciesCount, &LoadedPackages](const FName& A, const FName& B) + { + int32 ADependencies = PackageDependenciesCount.FindChecked(A); + int32 BDependencies = PackageDependenciesCount.FindChecked(B); + bool ALoaded = LoadedPackages.Contains(A); + bool BLoaded = LoadedPackages.Contains(B); + return (ALoaded == BLoaded) ? (ADependencies > BDependencies) : ALoaded > BLoaded; + } ); } @@ -3569,7 +3592,6 @@ void UCookOnTheFlyServer::BeginDestroy() EndNetworkFileServer(); Super::BeginDestroy(); - } void UCookOnTheFlyServer::TickRecompileShaderRequests() @@ -3584,7 +3606,6 @@ void UCookOnTheFlyServer::TickRecompileShaderRequests() { HandleNetworkFileServerRecompileShaders(Request->RecompileData); - // all done! other thread can unblock now Request->bComplete = true; } @@ -3610,8 +3631,10 @@ bool UCookOnTheFlyServer::ShouldConsiderCompressedPackageFileLengthRequirements( bool UCookOnTheFlyServer::MakePackageFullyLoaded(UPackage* Package) const { - if ( Package->IsFullyLoaded() ) + if (Package->IsFullyLoaded()) + { return true; + } bool bPackageFullyLoaded = false; GIsCookerLoadingPackage = true; @@ -3917,14 +3940,24 @@ void UCookOnTheFlyServer::SaveCookedPackage(UPackage* Package, uint32 SaveFlags, Result = ESavePackageResult::ContainsEditorOnlyData; bCookPackage = false; } - // Check whether or not game-specific behaviour should prevent this package from being cooked for the target platform - if (UAssetManager::IsValid() && !UAssetManager::Get().ShouldCookForPlatform(Package, Target)) + else if (UAssetManager::IsValid() && !UAssetManager::Get().ShouldCookForPlatform(Package, Target)) { Result = ESavePackageResult::ContainsEditorOnlyData; bCookPackage = false; UE_LOG(LogCook, Display, TEXT("Excluding %s -> %s"), *Package->GetName(), *PlatFilename); } + // check if this package is unsupported for the target platform (typically plugin content) + else + { + TSet* NeverCookPackages = PackageTracker->PlatformSpecificNeverCookPackages.Find(FName(*Target->PlatformName())); + if (NeverCookPackages && NeverCookPackages->Find(FName(*PackagePathName))) + { + Result = ESavePackageResult::ContainsEditorOnlyData; + bCookPackage = false; + UE_LOG(LogCook, Display, TEXT("Excluding %s -> %s"), *Package->GetName(), *PlatFilename); + } + } if (bCookPackage == true) { @@ -4049,6 +4082,8 @@ void UCookOnTheFlyServer::SaveCookedPackage(UPackage* Package, uint32 SaveFlags, void UCookOnTheFlyServer::Initialize( ECookMode::Type DesiredCookMode, ECookInitializationFlags InCookFlags, const FString &InOutputDirectoryOverride ) { + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("UCookOnTheFlyServer::Initialize")); + OutputDirectoryOverride = InOutputDirectoryOverride; CurrentCookMode = DesiredCookMode; CookFlags = InCookFlags; @@ -4906,7 +4941,7 @@ bool UCookOnTheFlyServer::IniSettingsOutOfDate(const ITargetPlatform* TargetPlat bool UCookOnTheFlyServer::SaveCurrentIniSettings(const ITargetPlatform* TargetPlatform) const { - auto S = FScopeAssign(IniSettingRecurse, true); + FScopeAssign S = FScopeAssign(IniSettingRecurse, true); TMap AdditionalIniSettings; GetAdditionalCurrentIniVersionStrings(TargetPlatform, AdditionalIniSettings); @@ -5039,6 +5074,8 @@ FName UCookOnTheFlyServer::ConvertCookedPathToUncookedPath( void UCookOnTheFlyServer::GetAllCookedFiles(TMap& UncookedPathToCookedPath, const FString& SandboxRootDir) { + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("UCookOnTheFlyServer::GetAllCookedFiles")); + TArray CookedFiles; { IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); @@ -5066,6 +5103,8 @@ void UCookOnTheFlyServer::GetAllCookedFiles(TMap& UncookedPathToCo void UCookOnTheFlyServer::PopulateCookedPackagesFromDisk(const TArray& Platforms) { + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("UCookOnTheFlyServer::PopulateCookedPackagesFromDisk")); + // See what files are out of date in the sandbox folder for (int32 Index = 0; Index < Platforms.Num(); Index++) { @@ -5328,6 +5367,8 @@ const FString ExtractPackageNameFromObjectPath( const FString ObjectPath ) void UCookOnTheFlyServer::CleanSandbox(const bool bIterative) { + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("UCookOnTheFlyServer::CleanSandbox")); + const TArray& Platforms = GetCookingTargetPlatforms(); // before we can delete any cooked files we need to make sure that we have finished writing them @@ -5401,6 +5442,8 @@ void UCookOnTheFlyServer::CleanSandbox(const bool bIterative) void UCookOnTheFlyServer::GenerateAssetRegistry() { + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("UCookOnTheFlyServer::GenerateAssetRegistry")); + // Cache asset registry for later FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); AssetRegistry = &AssetRegistryModule.Get(); @@ -5538,6 +5581,8 @@ void UCookOnTheFlyServer::AddFileToCook( TArray& InOutFilesToCook, const void UCookOnTheFlyServer::CollectFilesToCook(TArray& FilesInPath, const TArray& CookMaps, const TArray& InCookDirectories, const TArray &IniMapSections, ECookByTheBookOptions FilesToCookFlags) { + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("UCookOnTheFlyServer::GenerateAssetRegistry")); + #if OUTPUT_TIMING SCOPE_TIMER(CollectFilesToCook); #endif @@ -6489,7 +6534,9 @@ void UCookOnTheFlyServer::BuildMapDependencyGraph(const FName& PlatformName) FName Name = FName(*FPackageName::FilenameToLongPackageName(CookedPackage.ToString())); if (!ContainsMap(Name)) + { continue; + } TSet DependentPackages; TSet Roots; @@ -6593,7 +6640,6 @@ void UCookOnTheFlyServer::ClearPlatformCookedData(const FName& PlatformName) void UCookOnTheFlyServer::ClearCachedCookedPlatformDataForPlatform( const FName& PlatformName ) { - ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef(); ITargetPlatform* TargetPlatform = TPM.FindTargetPlatform(PlatformName.ToString()); if ( TargetPlatform ) @@ -6616,7 +6662,6 @@ void UCookOnTheFlyServer::OnTargetPlatformChangedSupportedFormats(const ITargetP void UCookOnTheFlyServer::CreateSandboxFile() { - // initialize the sandbox file after determining if we are cooking dlc // Local sandbox file wrapper. This will be used to handle path conversions, // but will not be used to actually write/read files so we can safely @@ -6672,6 +6717,62 @@ void UCookOnTheFlyServer::InitializeTargetPlatforms() } } +void UCookOnTheFlyServer::DiscoverPlatformSpecificNeverCookPackages( + const TArray& TargetPlatformNames, const TArray& TargetPlatformStrings) +{ + TArray PluginUnsupportedTargetPlatforms; + TArray PluginAssets; + FARFilter PluginARFilter; + FString PluginPackagePath; + + TArray> AllContentPlugins = IPluginManager::Get().GetEnabledPluginsWithContent(); + for (TSharedRef Plugin : AllContentPlugins) + { + const FPluginDescriptor& Descriptor = Plugin->GetDescriptor(); + + // we are only interested in plugins that does not support all platforms + if (Descriptor.SupportedTargetPlatforms.Num() == 0) + { + continue; + } + + // find any unsupported target platforms for this plugin + PluginUnsupportedTargetPlatforms.Reset(); + for (int32 I = 0, Count = TargetPlatformNames.Num(); I < Count; ++I) + { + if (!Descriptor.SupportedTargetPlatforms.Contains(TargetPlatformStrings[I])) + { + PluginUnsupportedTargetPlatforms.Add(TargetPlatformNames[I]); + } + } + + // if there are unsupported target platforms, + // then add all packages for this plugin for these platforms to the PlatformSpecificNeverCookPackages map + if (PluginUnsupportedTargetPlatforms.Num() > 0) + { + PluginPackagePath.Reset(127); + PluginPackagePath.AppendChar(TEXT('/')); + PluginPackagePath.Append(Plugin->GetName()); + + PluginARFilter.bRecursivePaths = true; + PluginARFilter.bIncludeOnlyOnDiskAssets = true; + PluginARFilter.PackagePaths.Reset(1); + PluginARFilter.PackagePaths.Emplace(*PluginPackagePath); + + PluginAssets.Reset(); + AssetRegistry->GetAssets(PluginARFilter, PluginAssets); + + for (FName& PlatformName : PluginUnsupportedTargetPlatforms) + { + TSet& NeverCookPackages = PackageTracker->PlatformSpecificNeverCookPackages.FindOrAdd(PlatformName); + for (const FAssetData& Asset : PluginAssets) + { + NeverCookPackages.Add(Asset.PackageName); + } + } + } + } +} void UCookOnTheFlyServer::TermSandbox() { @@ -6822,12 +6923,20 @@ void UCookOnTheFlyServer::StartCookByTheBook( const FCookByTheBookStartupOptions } - - CookByTheBookOptions->TargetPlatformNames.Empty(); - for (const ITargetPlatform* Platform : TargetPlatforms) + // build persistent list of all target platform names in CookByTheBookOptions->TargetPlatformNames, + // also use temp list of platform strings to discover PlatformSpecificNeverCookPackages { - FName PlatformName = FName(*Platform->PlatformName()); - CookByTheBookOptions->TargetPlatformNames.Add(PlatformName); // build list of all target platform names + TArray TargetPlatformStrings; + TargetPlatformStrings.Reserve(TargetPlatforms.Num()); + + CookByTheBookOptions->TargetPlatformNames.Empty(TargetPlatforms.Num()); + for (const ITargetPlatform* Platform : TargetPlatforms) + { + FString& PlatformString = TargetPlatformStrings.Emplace_GetRef(Platform->PlatformName()); + CookByTheBookOptions->TargetPlatformNames.Emplace(*PlatformString); + } + + DiscoverPlatformSpecificNeverCookPackages(CookByTheBookOptions->TargetPlatformNames, TargetPlatformStrings); } const TArray& TargetPlatformNames = CookByTheBookOptions->TargetPlatformNames; @@ -7003,8 +7112,10 @@ void UCookOnTheFlyServer::StartCookByTheBook( const FCookByTheBookStartupOptions // add all the files for the requested platform to the cook list for ( const FName& FileFName : FilesInPath ) { - if ( FileFName == NAME_None ) + if (FileFName == NAME_None) + { continue; + } const FName PackageFileFName = PackageNameCache->GetCachedStandardPackageFileFName(FileFName); @@ -7045,9 +7156,7 @@ void UCookOnTheFlyServer::StartCookByTheBook( const FCookByTheBookStartupOptions { PackageTracker->CookRequests.EnqueueUnique( FFilePlatformRequest( PackageFilename, PlatformArray) ); } - } - } } @@ -7231,7 +7340,9 @@ void UCookOnTheFlyServer::HandleNetworkFileServerFileRequest(const FString& File PackageTracker->CookRequests.EnqueueUnique(FileRequest, true); if (PackageTracker->CookRequestEvent) + { PackageTracker->CookRequestEvent->Trigger(); + } #if PROFILE_NETWORK bool bFoundNetworkEventWait = true; @@ -7306,7 +7417,6 @@ void UCookOnTheFlyServer::HandleNetworkGetPrecookedList(const FString& PlatformN } } - void UCookOnTheFlyServer::HandleNetworkFileServerRecompileShaders(const FShaderRecompileData& RecompileData) { // shouldn't receive network requests unless we are in cook on the fly mode diff --git a/Engine/Source/Editor/UnrealEd/Private/DataTableEditorUtils.cpp b/Engine/Source/Editor/UnrealEd/Private/DataTableEditorUtils.cpp index 87cab42a3cf8..4f4238a43858 100644 --- a/Engine/Source/Editor/UnrealEd/Private/DataTableEditorUtils.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/DataTableEditorUtils.cpp @@ -10,11 +10,436 @@ #include "Engine/UserDefinedStruct.h" #include "ScopedTransaction.h" #include "K2Node_GetDataTableRow.h" +#include "Input/Reply.h" +#include "Widgets/Input/SComboButton.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Views/SListView.h" +#include "Widgets/Input/SComboBox.h" +#include "AssetRegistryModule.h" #define LOCTEXT_NAMESPACE "DataTableEditorUtils" +/** Combobox that allows selecting a struct row for a data table. Based off of SSearchableComboBox */ +class SDataTableStructComboBox : public SComboButton +{ +public: + /** Type of list used for showing menu options. */ + typedef SListView< TSharedPtr > SComboListType; + /** Delegate type used to generate widgets that represent Options */ + typedef typename TSlateDelegates< TSharedPtr >::FOnGenerateWidget FOnGenerateWidget; + typedef typename TSlateDelegates< TSharedPtr >::FOnSelectionChanged FOnSelectionChanged; + DECLARE_DELEGATE_OneParam(FOnFillComboBoxStrings, TArray>&); + + SLATE_BEGIN_ARGS(SDataTableStructComboBox) + : _Content() + , _ComboBoxStyle(&FCoreStyle::Get().GetWidgetStyle< FComboBoxStyle >("ComboBox")) + , _ButtonStyle(nullptr) + , _ItemStyle(&FCoreStyle::Get().GetWidgetStyle< FTableRowStyle >("TableView.Row")) + , _ContentPadding(FMargin(4.0, 2.0)) + , _ForegroundColor(FCoreStyle::Get().GetSlateColor("InvertedForeground")) + , _OnStructSelected() + , _InitiallySelectedItem(nullptr) + , _Method() + , _MaxListHeight(450.0f) + , _HasDownArrow(true) + {} + + /** Slot for this button's content (optional) */ + SLATE_DEFAULT_SLOT(FArguments, Content) + + SLATE_STYLE_ARGUMENT(FComboBoxStyle, ComboBoxStyle) + + /** The visual style of the button part of the combo box (overrides ComboBoxStyle) */ + SLATE_STYLE_ARGUMENT(FButtonStyle, ButtonStyle) + + SLATE_STYLE_ARGUMENT(FTableRowStyle, ItemStyle) + + SLATE_ATTRIBUTE(FMargin, ContentPadding) + SLATE_ATTRIBUTE(FSlateColor, ForegroundColor) + + SLATE_EVENT(FDataTableEditorUtils::FOnDataTableStructSelected, OnStructSelected) + + /** The custom scrollbar to use in the ListView */ + SLATE_ARGUMENT(TSharedPtr, CustomScrollbar) + + /** The option that should be selected when the combo box is first created */ + SLATE_ARGUMENT(TSharedPtr, InitiallySelectedItem) + + SLATE_ARGUMENT(TOptional, Method) + + /** The max height of the combo box menu */ + SLATE_ARGUMENT(float, MaxListHeight) + + /** + * When false, the down arrow is not generated and it is up to the API consumer + * to make their own visual hint that this is a drop down. + */ + SLATE_ARGUMENT(bool, HasDownArrow) + + SLATE_END_ARGS() + + /** + * Construct the widget from a declaration + * + * @param InArgs Declaration from which to construct the combo box + */ + void Construct(const FArguments& InArgs); + + void ClearSelection(); + + void SetSelectedItem(TSharedPtr InSelectedItem); + + /** @return the item currently selected by the combo box. */ + TSharedPtr GetSelectedItem(); + + /** + * Requests a list refresh after updating options + * Call SetSelectedItem to update the selected item if required + * @see SetSelectedItem + */ + void RefreshOptions(); + + /** Returns the asset data for a specific string, or null if not found */ + const FAssetData* FindAssetDataForString(TSharedPtr StringOption) const; + + /** Returns struct from AssetData, possibly loading it */ + UScriptStruct* GetOrLoadStruct(const FAssetData* AssetData); + +private: + + /** Generate a row for the InItem in the combo box's list (passed in as OwnerTable). Do this by calling the user-specified OnGenerateWidget */ + TSharedRef GenerateMenuItemRow(TSharedPtr InItem, const TSharedRef& OwnerTable); + + /** Called if the menu is closed */ + void OnMenuOpenChanged(bool bOpen); + + /** Invoked when the selection in the list changes */ + void OnSelectionChanged_Internal(TSharedPtr ProposedSelection, ESelectInfo::Type SelectInfo); + + /** Invoked when the search text changes */ + void OnSearchTextChanged(const FText& ChangedText); + + /** Text to display inside box */ + FText GetSelectedText() const; + + /** Show tooltip text for a specific option */ + FText GetTooltipText(TSharedPtr StringOption); + + /** Handle clicking on the content menu */ + virtual FReply OnButtonClicked() override; + + /** The item style to use. */ + const FTableRowStyle* ItemStyle; + +private: + /** Delegate that is invoked when the selected item in the combo box changes */ + FDataTableEditorUtils::FOnDataTableStructSelected OnStructSelected; + + /** The item currently selected in the combo box */ + TSharedPtr SelectedItem; + /** The search field used for the combox box's contents */ + TSharedPtr< SEditableTextBox > SearchField; + /** The ListView that we pop up; visualized the available options. */ + TSharedPtr< SComboListType > ComboListView; + /** The Scrollbar used in the ListView. */ + TSharedPtr< SScrollBar > CustomScrollbar; + + /** List of names to show in combo box, there is a 1:1 mapping to PossibleStructs */ + TArray< TSharedPtr > CurrentOptions; + /** List of AssetData representing rows */ + TArray PossibleStructs; +}; + +void SDataTableStructComboBox::Construct(const FArguments& InArgs) +{ + check(InArgs._ComboBoxStyle); + + ItemStyle = InArgs._ItemStyle; + + // Work out which values we should use based on whether we were given an override, or should use the style's version + const FComboButtonStyle& OurComboButtonStyle = InArgs._ComboBoxStyle->ComboButtonStyle; + const FButtonStyle* const OurButtonStyle = InArgs._ButtonStyle ? InArgs._ButtonStyle : &OurComboButtonStyle.ButtonStyle; + + this->OnStructSelected = InArgs._OnStructSelected; + + CustomScrollbar = InArgs._CustomScrollbar; + + TSharedRef ComboBoxMenuContent = + SNew(SBox) + .MaxDesiredHeight(InArgs._MaxListHeight) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SAssignNew(this->SearchField, SEditableTextBox) + .HintText(LOCTEXT("Search", "Search")) + .OnTextChanged(this, &SDataTableStructComboBox::OnSearchTextChanged) + ] + + + SVerticalBox::Slot() + [ + SAssignNew(this->ComboListView, SComboListType) + .ListItemsSource(&CurrentOptions) + .OnGenerateRow(this, &SDataTableStructComboBox::GenerateMenuItemRow) + .OnSelectionChanged(this, &SDataTableStructComboBox::OnSelectionChanged_Internal) + .SelectionMode(ESelectionMode::Single) + .ExternalScrollbar(InArgs._CustomScrollbar) + ] + ]; + + // Set up content + TSharedPtr ButtonContent = InArgs._Content.Widget; + if (InArgs._Content.Widget == SNullWidget::NullWidget) + { + SAssignNew(ButtonContent, STextBlock) + .Text(this, &SDataTableStructComboBox::GetSelectedText); + } + + SComboButton::Construct(SComboButton::FArguments() + .ComboButtonStyle(&OurComboButtonStyle) + .ButtonStyle(OurButtonStyle) + .Method(InArgs._Method) + .ButtonContent() + [ + ButtonContent.ToSharedRef() + ] + .MenuContent() + [ + ComboBoxMenuContent + ] + .HasDownArrow(InArgs._HasDownArrow) + .ContentPadding(InArgs._ContentPadding) + .ForegroundColor(InArgs._ForegroundColor) + .OnMenuOpenChanged(this, &SDataTableStructComboBox::OnMenuOpenChanged) + .IsFocusable(true) + ); + + // Better to select search field so you can type right away + SetMenuContentWidgetToFocus(SearchField); + + // Refresh options now + RefreshOptions(); + + // Need to establish the selected item at point of construction so its available for querying + // NB: If you need a selection to fire use SetItemSelection rather than setting an IntiallySelectedItem + SelectedItem = InArgs._InitiallySelectedItem; + if (TListTypeTraits>::IsPtrValid(SelectedItem)) + { + ComboListView->Private_SetItemSelection(SelectedItem, true); + } +} + +void SDataTableStructComboBox::ClearSelection() +{ + ComboListView->ClearSelection(); +} + +void SDataTableStructComboBox::SetSelectedItem(TSharedPtr InSelectedItem) +{ + if (TListTypeTraits>::IsPtrValid(InSelectedItem)) + { + ComboListView->SetSelection(InSelectedItem); + } + else + { + ComboListView->ClearSelection(); + } +} + +TSharedPtr SDataTableStructComboBox::GetSelectedItem() +{ + return SelectedItem; +} + +FText SDataTableStructComboBox::GetSelectedText() const +{ + if (SelectedItem.IsValid()) + { + return FText::FromString(*SelectedItem); + } + + return FText::GetEmpty(); +} + +FText SDataTableStructComboBox::GetTooltipText(TSharedPtr StringOption) +{ + const FAssetData* FoundAsset = FindAssetDataForString(StringOption); + + if (FoundAsset) + { + return FText::FromString(FoundAsset->PackageName.ToString()); + } + return FText::GetEmpty(); +} + +void SDataTableStructComboBox::RefreshOptions() +{ + if (PossibleStructs.Num() == 0) + { + FDataTableEditorUtils::GetPossibleStructAssetData(PossibleStructs); + + CurrentOptions.Reset(); + for (const FAssetData& FoundStruct : PossibleStructs) + { + CurrentOptions.Add(MakeShareable(new FString(FoundStruct.AssetName.ToString()))); + } + } + + if (!ComboListView->IsPendingRefresh()) + { + ComboListView->RequestListRefresh(); + } +} + +const FAssetData* SDataTableStructComboBox::FindAssetDataForString(TSharedPtr StringOption) const +{ + check(CurrentOptions.Num() == PossibleStructs.Num()); + for (int32 i = 0; i < CurrentOptions.Num(); i++) + { + if (StringOption == CurrentOptions[i]) + { + return &PossibleStructs[i]; + } + } + return nullptr; +} + +UScriptStruct* SDataTableStructComboBox::GetOrLoadStruct(const FAssetData* AssetData) +{ + if (!AssetData) + { + return nullptr; + } + + return Cast(AssetData->GetAsset()); +} + +TSharedRef SDataTableStructComboBox::GenerateMenuItemRow(TSharedPtr InItem, const TSharedRef& OwnerTable) +{ + FString SearchToken = SearchField->GetText().ToString().ToLower(); + EVisibility WidgetVisibility = EVisibility::Visible; + if (!SearchToken.IsEmpty()) + { + if (InItem->ToLower().Find(SearchToken) < 0) + { + WidgetVisibility = EVisibility::Collapsed; + } + } + + TAttribute OnGetToolTip = TAttribute::Create(TAttribute::FGetter::CreateSP(this, &SDataTableStructComboBox::GetTooltipText, InItem)); + + return SNew(SComboRow>, OwnerTable) + .Style(ItemStyle) + .Visibility(WidgetVisibility) + [ + SNew(STextBlock) + .Text(FText::FromString(*InItem)) + .ToolTipText(OnGetToolTip) + ]; +} + +void SDataTableStructComboBox::OnMenuOpenChanged(bool bOpen) +{ + if (bOpen == false) + { + if (TListTypeTraits>::IsPtrValid(SelectedItem)) + { + // Ensure the ListView selection is set back to the last committed selection + ComboListView->SetSelection(SelectedItem, ESelectInfo::OnNavigation); + ComboListView->RequestScrollIntoView(SelectedItem, 0); + } + + // Set focus back to ComboBox for users focusing the ListView that just closed + FSlateApplication::Get().ForEachUser([&](FSlateUser* User) { + if (FSlateApplication::Get().HasUserFocusedDescendants(AsShared(), User->GetUserIndex())) + { + FSlateApplication::Get().SetUserFocus(User->GetUserIndex(), AsShared(), EFocusCause::SetDirectly); + } + }); + } +} + +void SDataTableStructComboBox::OnSelectionChanged_Internal(TSharedPtr ProposedSelection, ESelectInfo::Type SelectInfo) +{ + // Ensure that the proposed selection is different + if (SelectInfo != ESelectInfo::OnNavigation) + { + // Ensure that the proposed selection is different from selected + if (ProposedSelection != SelectedItem) + { + SelectedItem = ProposedSelection; + + UScriptStruct* SelectedStruct = GetOrLoadStruct(FindAssetDataForString(SelectedItem)); + + OnStructSelected.ExecuteIfBound(SelectedStruct); + + } + // close combo even if user reselected item + this->SetIsOpen(false); + } +} + +void SDataTableStructComboBox::OnSearchTextChanged(const FText& ChangedText) +{ + FString SearchToken = ChangedText.ToString().ToLower(); + for (int32 i = 0; i < CurrentOptions.Num(); i++) + { + TSharedPtr Row = ComboListView->WidgetFromItem(CurrentOptions[i]); + if (Row) + { + if (SearchToken.IsEmpty()) + { + Row->AsWidget()->SetVisibility(EVisibility::Visible); + } + else if (CurrentOptions[i]->ToLower().Find(SearchToken) >= 0) + { + Row->AsWidget()->SetVisibility(EVisibility::Visible); + } + else + { + Row->AsWidget()->SetVisibility(EVisibility::Collapsed); + } + } + } + + ComboListView->RequestListRefresh(); + + SelectedItem = TSharedPtr< FString >(); +} + +FReply SDataTableStructComboBox::OnButtonClicked() +{ + // if user clicked to close the combo menu + if (this->IsOpen()) + { + // Re-select first selected item, just in case it was selected by navigation previously + TArray> SelectedItems = ComboListView->GetSelectedItems(); + if (SelectedItems.Num() > 0) + { + OnSelectionChanged_Internal(SelectedItems[0], ESelectInfo::Direct); + } + } + else + { + SearchField->SetText(FText::GetEmpty()); + RefreshOptions(); + } + + return SComboButton::OnButtonClicked(); +} + const FString FDataTableEditorUtils::VariableTypesTooltipDocLink = TEXT("Shared/Editor/Blueprint/VariableTypes"); +TSharedRef FDataTableEditorUtils::MakeRowStructureComboBox(FOnDataTableStructSelected OnSelected) +{ + TSharedRef ComboBox = SNew(SDataTableStructComboBox) + .OnStructSelected(OnSelected) + .ContentPadding(3); + + return ComboBox; +} + FDataTableEditorUtils::FDataTableEditorManager& FDataTableEditorUtils::FDataTableEditorManager::Get() { static TSharedRef< FDataTableEditorManager > EditorManager(new FDataTableEditorManager()); @@ -68,6 +493,31 @@ uint8* FDataTableEditorUtils::AddRow(UDataTable* DataTable, FName RowName) return RowData; } +uint8* FDataTableEditorUtils::DuplicateRow(UDataTable* DataTable, FName SourceRowName, FName RowName) +{ + if (!DataTable || (SourceRowName == NAME_None) || !DataTable->RowMap.Contains(SourceRowName) || DataTable->RowMap.Contains(RowName) || !DataTable->RowStruct) + { + return NULL; + } + + const FScopedTransaction Transaction(LOCTEXT("DuplicateDataTableRow", "Duplicate Data Table Row")); + + BroadcastPreChange(DataTable, EDataTableChangeInfo::RowList); + DataTable->Modify(); + + // Allocate data to store information, using UScriptStruct to know its size + uint8* OldRowData = *DataTable->RowMap.Find(SourceRowName); + uint8* NewRowData = (uint8*)FMemory::Malloc(DataTable->RowStruct->GetStructureSize()); + + DataTable->RowStruct->InitializeStruct(NewRowData); + DataTable->RowStruct->CopyScriptStruct(NewRowData, OldRowData); + + // Add to row map + DataTable->RowMap.Add(RowName, NewRowData); + BroadcastPostChange(DataTable, EDataTableChangeInfo::RowList); + return NewRowData; +} + bool FDataTableEditorUtils::RenameRow(UDataTable* DataTable, FName OldName, FName NewName) { bool bResult = false; @@ -167,7 +617,7 @@ bool FDataTableEditorUtils::MoveRow(UDataTable* DataTable, FName RowName, ERowMo return true; } -bool FDataTableEditorUtils::SelectRow(UDataTable* DataTable, FName RowName) +bool FDataTableEditorUtils::SelectRow(const UDataTable* DataTable, FName RowName) { for (auto Listener : FDataTableEditorManager::Get().GetListeners()) { @@ -268,7 +718,7 @@ void FDataTableEditorUtils::CacheDataTableForEditing(const UDataTable* DataTable for (int32 Index = 0; Index < StructProps.Num(); ++Index) { const UProperty* Prop = StructProps[Index]; - const FText PropertyDisplayName = FText::FromString(DataTableUtils::GetPropertyDisplayName(Prop, FName::NameToDisplayString(Prop->GetName(), Prop->IsA()))); + const FText PropertyDisplayName = DataTableUtils::GetPropertyDisplayName(Prop, FName::NameToDisplayString(Prop->GetName(), Prop->IsA())); FDataTableEditorColumnHeaderDataPtr CachedColumnData; @@ -360,6 +810,37 @@ TArray FDataTableEditorUtils::GetPossibleStructs() return RowStructs; } +void FDataTableEditorUtils::GetPossibleStructAssetData(TArray& StructAssets) +{ + StructAssets.Reset(); + + // Make combo of table rowstruct options + for (TObjectIterator It; It; ++It) + { + UScriptStruct* Struct = *It; + if (IsValidTableStruct(Struct)) + { + StructAssets.Add(FAssetData(Struct)); + } + } + + // Now get unloaded ones + const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + TArray AssetData; + AssetRegistryModule.Get().GetAssetsByClass(UUserDefinedStruct::StaticClass()->GetFName(), AssetData); + + for (int32 AssetIndex = 0; AssetIndex < AssetData.Num(); ++AssetIndex) + { + const FAssetData& Asset = AssetData[AssetIndex]; + if (Asset.IsValid() && !Asset.IsAssetLoaded()) + { + StructAssets.Add(Asset); + } + } + + StructAssets.Sort([](const FAssetData& A, const FAssetData& B) { return A.AssetName.LexicalLess(B.AssetName); }); +} + bool FDataTableEditorUtils::IsValidTableStruct(UScriptStruct* Struct) { UScriptStruct* TableRowStruct = FindObjectChecked(ANY_PACKAGE, TEXT("TableRowBase")); diff --git a/Engine/Source/Editor/UnrealEd/Private/Dialogs/CustomDialog.cpp b/Engine/Source/Editor/UnrealEd/Private/Dialogs/CustomDialog.cpp new file mode 100644 index 000000000000..a45ad6d3da92 --- /dev/null +++ b/Engine/Source/Editor/UnrealEd/Private/Dialogs/CustomDialog.cpp @@ -0,0 +1,162 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Dialogs/CustomDialog.h" + +#include "HAL/PlatformApplicationMisc.h" + +#include "EditorStyleSet.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/Docking/TabManager.h" +#include "Logging/LogMacros.h" +#include "Styling/SlateBrush.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Layout/SSpacer.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Layout/SScrollBox.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Widgets/SBoxPanel.h" + +DEFINE_LOG_CATEGORY_STATIC(LogCustomDialog, Log, All); + +void SCustomDialog::Construct(const FArguments& InArgs) +{ + UE_LOG(LogCustomDialog, Log, TEXT("Dialog displayed:"), *InArgs._Title.ToString()); + + check(InArgs._Buttons.Num() > 0); + + TSharedPtr ContentBox; + TSharedPtr ButtonBox; + + SWindow::Construct( SWindow::FArguments() + .Title(InArgs._Title) + .SizingRule(ESizingRule::Autosized) + .SupportsMaximize(false) + .SupportsMinimize(false) + [ + SNew(SBorder) + .Padding(4.f) + .BorderImage(FEditorStyle::GetBrush( "ToolPanel.GroupBorder" )) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .FillHeight(1.0f) + [ + SAssignNew(ContentBox, SHorizontalBox) + ] + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .AutoHeight() + [ + SAssignNew(ButtonBox, SHorizontalBox) + ] + ] + ] ); + + if (InArgs._IconBrush.IsValid()) + { + const FSlateBrush* ImageBrush = FEditorStyle::GetBrush(InArgs._IconBrush); + if (ImageBrush != nullptr) + { + ContentBox->AddSlot() + .AutoWidth() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(SImage) + .Image(ImageBrush) + ]; + } + } + + if (InArgs._UseScrollBox) + { + ContentBox->AddSlot() + [ + SNew(SBox) + .MaxDesiredHeight(InArgs._ScrollBoxMaxHeight) + [ + SNew(SScrollBox) + +SScrollBox::Slot() + [ + InArgs._DialogContent.ToSharedRef() + ] + ] + ]; + } + else + { + ContentBox->AddSlot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + InArgs._DialogContent.ToSharedRef() + ]; + } + + ButtonBox->AddSlot() + .AutoWidth() + [ + SNew(SSpacer) + .Size(FVector2D(20.0f, 1.0f)) + ]; + + TSharedPtr ButtonPanel; + + ButtonBox->AddSlot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Right) + [ + SAssignNew(ButtonPanel, SUniformGridPanel) + .SlotPadding(FEditorStyle::GetMargin("StandardDialog.SlotPadding")) + .MinDesiredSlotWidth(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotWidth")) + .MinDesiredSlotHeight(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotHeight")) + ]; + + for (int i = 0; i < InArgs._Buttons.Num(); ++i) + { + const FButton& Button = InArgs._Buttons[i]; + + ButtonPanel->AddSlot(ButtonPanel->GetChildren()->Num(), 0) + [ + SNew(SButton) + .OnClicked(FOnClicked::CreateSP(this, &SCustomDialog::OnButtonClicked, Button.OnClicked, i)) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text(Button.ButtonText) + ] + ] + ]; + } +} + +int SCustomDialog::ShowModal() +{ + FSlateApplication::Get().AddModalWindow(StaticCastSharedRef(this->AsShared()), FGlobalTabmanager::Get()->GetRootWindow()); + + return LastPressedButton; +} + +void SCustomDialog::Show() +{ + FSlateApplication::Get().AddWindow(StaticCastSharedRef(this->AsShared()), true); +} + +/** Handle the button being clicked */ +FReply SCustomDialog::OnButtonClicked(FSimpleDelegate OnClicked, int ButtonIndex) +{ + LastPressedButton = ButtonIndex; + + FSlateApplication::Get().RequestDestroyWindow(StaticCastSharedRef(this->AsShared())); + + OnClicked.ExecuteIfBound(); + return FReply::Handled(); +} diff --git a/Engine/Source/Editor/UnrealEd/Private/EdGraphUtilities.cpp b/Engine/Source/Editor/UnrealEd/Private/EdGraphUtilities.cpp index 65e5885efb83..b529d1ce5abc 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EdGraphUtilities.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EdGraphUtilities.cpp @@ -15,6 +15,68 @@ #include "Widgets/Notifications/SNotificationList.h" #include "K2Node_Composite.h" +///////////////////////////////////////////////////// +// Local namespace + +namespace +{ + // Reconcile other pin links: + // - Links between nodes within the copied set are fine + // - Links to nodes that were not copied need to be fixed up if the copy-paste was in the same graph or broken completely + void PostProcessPastedNodePinLinks(TSet& InNodes) + { + for (TSet::TIterator It(InNodes); It; ++It) + { + UEdGraphNode* Node = *It; + UEdGraph* CurrentGraph = Node->GetGraph(); + + for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex) + { + UEdGraphPin* ThisPin = Node->Pins[PinIndex]; + + // Ensure on any NULL entry, as it means there was a problem importing the pin from text, and we should be alerted to that. + if (ensure(ThisPin)) + { + for (int32 LinkIndex = 0; LinkIndex < ThisPin->LinkedTo.Num(); ) + { + UEdGraphPin* OtherPin = ThisPin->LinkedTo[LinkIndex]; + + if (OtherPin == nullptr) + { + // Totally bogus link + ThisPin->LinkedTo.RemoveAtSwap(LinkIndex); + } + else if (!InNodes.Contains(OtherPin->GetOwningNode())) + { + // It's a link across the selection set, so it should be broken + OtherPin->LinkedTo.RemoveSwap(ThisPin); + ThisPin->LinkedTo.RemoveAtSwap(LinkIndex); + } + else if (!OtherPin->LinkedTo.Contains(ThisPin)) + { + // The link needs to be reciprocal + check(OtherPin->GetOwningNode()->GetGraph() == CurrentGraph); + OtherPin->LinkedTo.Add(ThisPin); + ++LinkIndex; + } + else + { + // Everything seems fine but sanity check the graph + check(OtherPin->GetOwningNode()->GetGraph() == CurrentGraph); + ++LinkIndex; + } + } + } + else + { + // Remove NULL entries; these will be replaced with a default value when the node is reconstructed below. + Node->Pins.RemoveAt(PinIndex--); + } + } + } + } +} + ///////////////////////////////////////////////////// // FGraphObjectTextFactory @@ -26,7 +88,7 @@ public: TSet SubstituteNodes; const UEdGraph* DestinationGraph; TSet ExtraNamesInUse; - TArray NodesToDestroy; + TSet NodesToDestroy; public: FGraphObjectTextFactory(const UEdGraph* InDestinationGraph) : FCustomizableTextObjectFactory(GWarn) @@ -92,8 +154,25 @@ protected: { if (SubstituteNodes.Num() > 0) { - // Display a notification to inform the user that the variable type was invalid (likely due to corruption), it should no longer appear in the list. - FNotificationInfo Info(NSLOCTEXT("EdGraphUtilities", "SubstituteNodesWarning", "Conflicting nodes substituted during paste!")); + FText NotificationText; + if (SubstituteNodes.Contains(nullptr)) + { + if (SubstituteNodes.Num() > 1) + { + NotificationText = NSLOCTEXT("EdGraphUtilities", "SubstituteAndSkippedNodesWarning", "One or more copied nodes were substituted and/or could not be pasted into this graph!"); + } + else + { + NotificationText = NSLOCTEXT("EdGraphUtilities", "SkippedNodesWarning", "One or more copied nodes could not be pasted into this graph!"); + } + } + else + { + NotificationText = NSLOCTEXT("EdGraphUtilities", "SubstituteNodesWarning", "One or more copied nodes were substituted during paste!"); + } + + // Display a notification to inform the user that one or more nodes were substituted rather than pasted into the new graph. + FNotificationInfo Info(NotificationText); Info.ExpireDuration = 3.0f; Info.bUseLargeFont = false; Info.Image = FCoreStyle::Get().GetBrush(TEXT("MessageLog.Warning")); @@ -104,6 +183,9 @@ protected: } } + // Fix up pin cross-links, etc. before removing incompatible nodes below. Otherwise, BreakAllNodeLinks() will complain about non-reciprocating pin links. + PostProcessPastedNodePinLinks(NodesToDestroy); + for (UEdGraphNode* Node : NodesToDestroy) { // Move the old node into the transient package so that it is GC'd @@ -121,62 +203,11 @@ TArray< TSharedPtr > FEdGraphUtilities::VisualNodeFactor TArray< TSharedPtr > FEdGraphUtilities::VisualPinFactories; TArray< TSharedPtr > FEdGraphUtilities::VisualPinConnectionFactories; -// Reconcile other pin links: -// - Links between nodes within the copied set are fine -// - Links to nodes that were not copied need to be fixed up if the copy-paste was in the same graph or broken completely // Call PostPasteNode on each node void FEdGraphUtilities::PostProcessPastedNodes(TSet& SpawnedNodes) { // Run thru and fix up the node's pin links; they may point to invalid pins if the paste was to another graph - for (TSet::TIterator It(SpawnedNodes); It; ++It) - { - UEdGraphNode* Node = *It; - UEdGraph* CurrentGraph = Node->GetGraph(); - - for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex) - { - UEdGraphPin* ThisPin = Node->Pins[PinIndex]; - - // Ensure on any NULL entry, as it means there was a problem importing the pin from text, and we should be alerted to that. - if (ensure(ThisPin)) - { - for (int32 LinkIndex = 0; LinkIndex < ThisPin->LinkedTo.Num(); ) - { - UEdGraphPin* OtherPin = ThisPin->LinkedTo[LinkIndex]; - - if (OtherPin == nullptr) - { - // Totally bogus link - ThisPin->LinkedTo.RemoveAtSwap(LinkIndex); - } - else if (!SpawnedNodes.Contains(OtherPin->GetOwningNode())) - { - // It's a link across the selection set, so it should be broken - OtherPin->LinkedTo.RemoveSwap(ThisPin); - ThisPin->LinkedTo.RemoveAtSwap(LinkIndex); - } - else if (!OtherPin->LinkedTo.Contains(ThisPin)) - { - // The link needs to be reciprocal - check(OtherPin->GetOwningNode()->GetGraph() == CurrentGraph); - OtherPin->LinkedTo.Add(ThisPin); - ++LinkIndex; - } - else - { - // Everything seems fine but sanity check the graph - check(OtherPin->GetOwningNode()->GetGraph() == CurrentGraph); - ++LinkIndex; - } - } - } - else - { - // Remove NULL entries; these will be replaced with a default value when the node is reconstructed below. - Node->Pins.RemoveAt(PinIndex--); - } - } - } + PostProcessPastedNodePinLinks(SpawnedNodes); // Give every node a chance to deep copy associated resources, etc... for (TSet::TIterator It(SpawnedNodes); It; ++It) @@ -381,6 +412,9 @@ void FEdGraphUtilities::ImportNodesFromText(UEdGraph* DestinationGraph, const FS // Fix up pin cross-links, etc... FEdGraphUtilities::PostProcessPastedNodes(Factory.SpawnedNodes); + // If a pin link wasn't resolved by now it was connected to something outside the clipboard and should be cleared: + UEdGraphPin::ResolveAllPinReferences(); + ImportedNodeSet.Append(Factory.SpawnedNodes); } diff --git a/Engine/Source/Editor/UnrealEd/Private/Editor.cpp b/Engine/Source/Editor/UnrealEd/Private/Editor.cpp index 48138bfd1c83..c2a779adc1bf 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Editor.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Editor.cpp @@ -1256,7 +1256,8 @@ namespace EditorUtilities if( !bIsTransient && !bIsIdentical && !bIsComponentContainer && !bIsComponentProp && !bIsBlueprintReadonly) { - const bool bIsSafeToCopy = !( Options.Flags & ECopyOptions::OnlyCopyEditOrInterpProperties ) || ( Property->HasAnyPropertyFlags( CPF_Edit | CPF_Interp ) ); + const bool bIsSafeToCopy = (!( Options.Flags & ECopyOptions::OnlyCopyEditOrInterpProperties ) || ( Property->HasAnyPropertyFlags( CPF_Edit | CPF_Interp ) )) + && (!( Options.Flags & ECopyOptions::SkipInstanceOnlyProperties) || ( !Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate) ) ); if( bIsSafeToCopy ) { if (!Options.CanCopyProperty(*Property, *SourceActor)) @@ -1381,7 +1382,8 @@ namespace EditorUtilities if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) && ( !bIsTransform || SourceComponent != SourceActor->GetRootComponent() || ( !SourceActor->HasAnyFlags( RF_ClassDefaultObject | RF_ArchetypeObject ) && !TargetActor->HasAnyFlags( RF_ClassDefaultObject | RF_ArchetypeObject ) ) ) ) { - const bool bIsSafeToCopy = !( Options.Flags & ECopyOptions::OnlyCopyEditOrInterpProperties ) || ( Property->HasAnyPropertyFlags( CPF_Edit | CPF_Interp ) ); + const bool bIsSafeToCopy = (!(Options.Flags & ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) + && (!(Options.Flags & ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); if( bIsSafeToCopy ) { if (!Options.CanCopyProperty(*Property, *SourceActor)) diff --git a/Engine/Source/Editor/UnrealEd/Private/Editor/ActorPositioning.cpp b/Engine/Source/Editor/UnrealEd/Private/Editor/ActorPositioning.cpp index 88b3c1ee8cf0..a21fe2e6d510 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Editor/ActorPositioning.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Editor/ActorPositioning.cpp @@ -17,6 +17,7 @@ #include "SnappingUtils.h" #include "LandscapeHeightfieldCollisionComponent.h" #include "LandscapeComponent.h" +#include "Editor/EditorPerProjectUserSettings.h" FActorPositionTraceResult FActorPositioning::TraceWorldForPositionWithDefault(const FViewportCursorLocation& Cursor, const FSceneView& View, const TArray* IgnoreActors) { @@ -86,16 +87,18 @@ bool IsHitIgnored(const FHitResult& InHit, const FSceneView& InSceneView) // Only use this component if it is visible in the specified scene views bool bIsRenderedOnScreen = false; + bool bIgnoreTranslucentPrimitive = false; { if (PrimitiveComponent && PrimitiveComponent->SceneProxy) { const FPrimitiveViewRelevance ViewRelevance = PrimitiveComponent->SceneProxy->GetViewRelevance(&InSceneView); // BSP is a bit special in that its bDrawRelevance is false even when drawn as wireframe because InSceneView.Family->EngineShowFlags.BSPTriangles is off bIsRenderedOnScreen = ViewRelevance.bDrawRelevance || (PrimitiveComponent->IsA(UModelComponent::StaticClass()) && InSceneView.Family->EngineShowFlags.BSP); + bIgnoreTranslucentPrimitive = ViewRelevance.HasTranslucency() && !GetDefault()->bAllowSelectTranslucent; } } - return !bIsRenderedOnScreen; + return !bIsRenderedOnScreen || bIgnoreTranslucentPrimitive; } FActorPositionTraceResult FActorPositioning::TraceWorldForPosition(const UWorld& InWorld, const FSceneView& InSceneView, const FVector& RayStart, const FVector& RayEnd, const TArray* IgnoreActors) diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorActor.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorActor.cpp index 256c47160e9d..a8451ddb867c 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorActor.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorActor.cpp @@ -64,6 +64,7 @@ #include "IAssetTools.h" #include "AssetToolsModule.h" #include "AssetSelection.h" +#include "Framework/Application/SlateApplication.h" #define LOCTEXT_NAMESPACE "UnrealEd.EditorActor" @@ -702,6 +703,8 @@ bool UUnrealEdEngine::edactDeleteSelected( UWorld* InWorld, bool bVerifyDeletion const double StartSeconds = FPlatformTime::Seconds(); + FSlateApplication::Get().CancelDragDrop(); + if (GetSelectedComponentCount() > 0) { TArray SelectedEditableComponents; diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorCsg.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorCsg.cpp index 30d7db6aba8d..a75c36adfc0f 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorCsg.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorCsg.cpp @@ -98,7 +98,7 @@ public: * * @return true if iterator points to a suitable actor, false if it has reached the end */ - operator bool() + explicit operator bool() const { return !ReachedEnd; } diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp index 7c719b6a9bcd..3d3470197503 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp @@ -10,7 +10,6 @@ #include "Misc/App.h" #include "Modules/ModuleManager.h" #include "UObject/MetaData.h" -#include "Serialization/ArchiveTraceRoute.h" #include "UObject/ConstructorHelpers.h" #include "Application/ThrottleManager.h" #include "Framework/Application/SlateApplication.h" @@ -87,6 +86,7 @@ #include "Net/NetworkProfiler.h" #include "Interfaces/IPluginManager.h" #include "UObject/PackageReload.h" +#include "UObject/ReferenceChainSearch.h" #include "HAL/PlatformApplicationMisc.h" #include "IMediaModule.h" @@ -6920,14 +6920,11 @@ void UEditorEngine::VerifyLoadMapWorldCleanup() if (!ValidWorld) { - // Print some debug information... - UE_LOG(LogLoad, Log, TEXT("%s not cleaned up by garbage collection! "), *World->GetFullName()); - StaticExec(World, *FString::Printf(TEXT("OBJ REFS CLASS=WORLD NAME=%s"), *World->GetPathName())); - TMap Route = FArchiveTraceRoute::FindShortestRootPath( World, true, GARBAGE_COLLECTION_KEEPFLAGS ); - FString ErrorString = FArchiveTraceRoute::PrintRootPath( Route, World ); - UE_LOG(LogLoad, Log, TEXT("%s"),*ErrorString); - // before asserting. - UE_LOG(LogLoad, Fatal, TEXT("%s not cleaned up by garbage collection!") LINE_TERMINATOR TEXT("%s") , *World->GetFullName(), *ErrorString ); + UE_LOG(LogLoad, Error, TEXT("Previously active world %s not cleaned up by garbage collection!"), *World->GetPathName()); + UE_LOG(LogLoad, Error, TEXT("Once a world has become active, it cannot be reused and must be destroyed and reloaded. World referenced by:")); + + FReferenceChainSearch RefChainSearch(World, EReferenceChainSearchMode::Shortest | EReferenceChainSearchMode::PrintResults); + UE_LOG(LogLoad, Fatal, TEXT("Previously active world %s not cleaned up by garbage collection! Referenced by:") LINE_TERMINATOR TEXT("%s"), *World->GetPathName(), *RefChainSearch.GetRootPath()); } } } diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorLevelUtils.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorLevelUtils.cpp index b53f688da0f0..1a2b0636cdc8 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorLevelUtils.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorLevelUtils.cpp @@ -10,7 +10,6 @@ EditorLevelUtils.cpp: Editor-specific level management routines #include "UObject/GarbageCollection.h" #include "UObject/Class.h" #include "UObject/Package.h" -#include "Serialization/ArchiveTraceRoute.h" #include "Engine/EngineTypes.h" #include "GameFramework/Actor.h" #include "Engine/World.h" @@ -23,6 +22,7 @@ EditorLevelUtils.cpp: Editor-specific level management routines #include "EngineGlobals.h" #include "UObject/UObjectHash.h" #include "UObject/UObjectIterator.h" +#include "UObject/ReferenceChainSearch.h" #include "GameFramework/WorldSettings.h" #include "Engine/LevelStreaming.h" #include "Engine/Selection.h" @@ -713,10 +713,8 @@ bool UEditorLevelUtils::RemoveLevelFromWorld(ULevel* InLevel) UWorld* TheWorld = UWorld::FindWorldInPackage(LevelPackage->GetOutermost()); if (TheWorld != nullptr) { - StaticExec(NULL, *FString::Printf(TEXT("OBJ REFS CLASS=%s NAME=%s shortest"), *TheWorld->GetClass()->GetName(), *TheWorld->GetPathName())); - TMap Route = FArchiveTraceRoute::FindShortestRootPath(TheWorld, true, GARBAGE_COLLECTION_KEEPFLAGS); - FString ErrorString = FArchiveTraceRoute::PrintRootPath(Route, TheWorld); - UE_LOG(LogStreaming, Fatal, TEXT("%s didn't get garbage collected!") LINE_TERMINATOR TEXT("%s"), *TheWorld->GetFullName(), *ErrorString); + FReferenceChainSearch RefChainSearch(TheWorld, EReferenceChainSearchMode::Shortest | EReferenceChainSearchMode::PrintResults); + UE_LOG(LogStreaming, Fatal, TEXT("Removed world %s not cleaned up by garbage collection! Referenced by:") LINE_TERMINATOR TEXT("%s"), *TheWorld->GetPathName(), *RefChainSearch.GetRootPath()); } } } diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorModeManager.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorModeManager.cpp index c55cbd1f666c..b65d35bf35b7 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorModeManager.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorModeManager.cpp @@ -14,7 +14,6 @@ #include "EditorSupportDelegates.h" #include "EdMode.h" #include "Toolkits/IToolkitHost.h" - #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "Engine/LevelStreaming.h" @@ -23,8 +22,12 @@ #include "Editor/EditorEngine.h" #include "UnrealEdGlobals.h" #include "Editor/UnrealEdEngine.h" - #include "Bookmarks/IBookmarkTypeTools.h" +#include "Widgets/Docking/SDockTab.h" +#include "EditorStyleSet.h" +#include "Framework/Commands/UICommandList.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Toolkits/BaseToolkit.h" /*------------------------------------------------------------------------------ FEditorModeTools. @@ -32,6 +35,8 @@ The master class that handles tracking of the current mode. ------------------------------------------------------------------------------*/ +const FName FEditorModeTools::EditorModeToolbarTabName = TEXT("EditorModeToolbar"); + FEditorModeTools::FEditorModeTools() : PivotShown(false) , Snapping(false) @@ -297,21 +302,80 @@ void FEditorModeTools::ActivateDefaultMode() void FEditorModeTools::DeactivateModeAtIndex(int32 InIndex) { - check( InIndex >= 0 && InIndex < Modes.Num() ); + check( InIndex >= 0 && InIndex < ActiveModes.Num() ); - auto& Mode = Modes[InIndex]; + auto& Mode = ActiveModes[InIndex]; Mode->Exit(); + + // Remove the toolbar widget + ActiveToolBarRows.RemoveAll( + [&Mode](FEdModeToolbarRow& Row) + { + return Row.Mode == Mode; + } + ); + + RebuildModeToolBar(); + RecycledModes.Add( Mode->GetID(), Mode ); - Modes.RemoveAt( InIndex ); + ActiveModes.RemoveAt( InIndex ); +} + +void FEditorModeTools::RebuildModeToolBar() +{ + // If the tab or box is not valid the toolbar has not been opened or has been closed by the user + TSharedPtr ModeToolbarBoxPinned = ModeToolbarBox.Pin(); + if (ModeToolbarTab.IsValid() && ModeToolbarBoxPinned.IsValid()) + { + ModeToolbarBoxPinned->ClearChildren(); + + if(ActiveToolBarRows.Num()) + { + for(int32 RowIdx = 0; RowIdx < ActiveToolBarRows.Num(); ++RowIdx) + { + const FEdModeToolbarRow& Row = ActiveToolBarRows[RowIdx]; + if (ensure(Row.ToolbarWidget.IsValid())) + { + ModeToolbarBoxPinned->AddSlot() + .HAlign(HAlign_Left) + .AutoHeight() + .Padding(0.0f, RowIdx > 0 ? 5.0f : 0.0f, 0.0f, 0.0f) + [ + Row.ToolbarWidget.ToSharedRef() + ]; + } + } + } + else + { + ModeToolbarTab.Pin()->RequestCloseTab(); + } + } +} + + +void FEditorModeTools::SpawnOrUpdateModeToolbar() +{ + if(ShouldShowModeToolbar()) + { + if (ModeToolbarTab.IsValid()) + { + RebuildModeToolBar(); + } + else if (ToolkitHost.IsValid()) + { + ToolkitHost.Pin()->GetTabManager()->InvokeTab(EditorModeToolbarTabName); + } + } } void FEditorModeTools::DeactivateMode( FEditorModeID InID ) { // Find the mode from the ID and exit it. - for( int32 Index = Modes.Num() - 1; Index >= 0; --Index ) + for( int32 Index = ActiveModes.Num() - 1; Index >= 0; --Index ) { - auto& Mode = Modes[Index]; + auto& Mode = ActiveModes[Index]; if( Mode->GetID() == InID ) { DeactivateModeAtIndex(Index); @@ -319,7 +383,7 @@ void FEditorModeTools::DeactivateMode( FEditorModeID InID ) } } - if( Modes.Num() == 0 ) + if( ActiveModes.Num() == 0 ) { // Ensure the default mode is active if there are no active modes. ActivateDefaultMode(); @@ -328,7 +392,7 @@ void FEditorModeTools::DeactivateMode( FEditorModeID InID ) void FEditorModeTools::DeactivateAllModes() { - for( int32 Index = Modes.Num() - 1; Index >= 0; --Index ) + for( int32 Index = ActiveModes.Num() - 1; Index >= 0; --Index ) { DeactivateModeAtIndex(Index); } @@ -337,9 +401,9 @@ void FEditorModeTools::DeactivateAllModes() void FEditorModeTools::DestroyMode( FEditorModeID InID ) { // Find the mode from the ID and exit it. - for( int32 Index = Modes.Num() - 1; Index >= 0; --Index ) + for( int32 Index = ActiveModes.Num() - 1; Index >= 0; --Index ) { - auto& Mode = Modes[Index]; + auto& Mode = ActiveModes[Index]; if ( Mode->GetID() == InID ) { // Deactivate and destroy @@ -351,6 +415,32 @@ void FEditorModeTools::DestroyMode( FEditorModeID InID ) RecycledModes.Remove(InID); } +TSharedRef FEditorModeTools::MakeModeToolbarTab() +{ + TSharedRef ToolbarTabRef = + SNew(SDockTab) + .Label(NSLOCTEXT("EditorModes", "EditorModesToolbarTitle", "Mode Toolbar")) + .ShouldAutosize(true) + .Icon(FEditorStyle::GetBrush("ToolBar.Icon")) + [ + SAssignNew(ModeToolbarBox, SVerticalBox) + + ]; + + ModeToolbarTab = ToolbarTabRef; + + // Rebuild the toolbar with existing mode tools that may be active + RebuildModeToolBar(); + + return ToolbarTabRef; + +} + +bool FEditorModeTools::ShouldShowModeToolbar() +{ + return ActiveToolBarRows.Num() > 0; +} + void FEditorModeTools::ActivateMode(FEditorModeID InID, bool bToggle) { static bool bReentrant = false; @@ -407,20 +497,39 @@ void FEditorModeTools::ActivateMode(FEditorModeID InID, bool bToggle) } // Remove anything that isn't compatible with this mode - for (int32 ModeIndex = Modes.Num() - 1; ModeIndex >= 0; --ModeIndex) + for (int32 ModeIndex = ActiveModes.Num() - 1; ModeIndex >= 0; --ModeIndex) { - const bool bModesAreCompatible = Mode->IsCompatibleWith(Modes[ModeIndex]->GetID()) || Modes[ModeIndex]->IsCompatibleWith(Mode->GetID()); + const bool bModesAreCompatible = Mode->IsCompatibleWith(ActiveModes[ModeIndex]->GetID()) || ActiveModes[ModeIndex]->IsCompatibleWith(Mode->GetID()); if (!bModesAreCompatible) { DeactivateModeAtIndex(ModeIndex); } } - Modes.Add(Mode); + ActiveModes.Add(Mode); // Enter the new mode Mode->Enter(); + // Ask the mode to build the toolbar. + TSharedPtr CommandList; + const TSharedPtr Toolkit = Mode->GetToolkit(); + if (Toolkit.IsValid()) + { + CommandList = Toolkit->GetToolkitCommands(); + } + + FToolBarBuilder ModeToolbarBuilder(CommandList, FMultiBoxCustomization(Mode->GetModeInfo().ToolbarCustomizationName), TSharedPtr(), Orient_Horizontal, false); + Mode->BuildModeToolbar(ModeToolbarBuilder); + + if (ModeToolbarBuilder.GetMultiBox()->GetBlocks().Num()) + { + TSharedRef ToolbarWidget = ModeToolbarBuilder.MakeWidget(); + ActiveToolBarRows.Emplace(Mode, ToolbarWidget); + + SpawnOrUpdateModeToolbar(); + } + // Update the editor UI FEditorSupportDelegates::UpdateUI.Broadcast(); } @@ -447,7 +556,7 @@ bool FEditorModeTools::EnsureNotInMode(FEditorModeID ModeID, const FText& ErrorM FEdMode* FEditorModeTools::FindMode( FEditorModeID InID ) { - for( auto& Mode : Modes ) + for( auto& Mode : ActiveModes ) { if( Mode->GetID() == InID ) { @@ -474,7 +583,7 @@ FMatrix FEditorModeTools::GetCustomDrawingCoordinateSystem() // If it doesn't want to, create it by looking at the currently selected actors list. bool CustomCoordinateSystemProvided = false; - for (const auto& Mode : Modes) + for (const auto& Mode : ActiveModes) { if (Mode->GetCustomDrawingCoordinateSystem(Matrix, nullptr)) { @@ -520,11 +629,11 @@ FMatrix FEditorModeTools::GetCustomInputCoordinateSystem() EAxisList::Type FEditorModeTools::GetWidgetAxisToDraw( FWidget::EWidgetMode InWidgetMode ) const { EAxisList::Type OutAxis = EAxisList::All; - for( int Index = Modes.Num() - 1; Index >= 0 ; Index-- ) + for( int Index = ActiveModes.Num() - 1; Index >= 0 ; Index-- ) { - if ( Modes[Index]->ShouldDrawWidget() ) + if ( ActiveModes[Index]->ShouldDrawWidget() ) { - OutAxis = Modes[Index]->GetWidgetAxisToDraw( InWidgetMode ); + OutAxis = ActiveModes[Index]->GetWidgetAxisToDraw( InWidgetMode ); break; } } @@ -540,9 +649,9 @@ bool FEditorModeTools::StartTracking(FEditorViewportClient* InViewportClient, FV CachedLocation = PivotLocation; // Cache the pivot location - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bTransactionHandled |= Mode->StartTracking(InViewportClient, InViewport); } @@ -555,9 +664,9 @@ bool FEditorModeTools::EndTracking(FEditorViewportClient* InViewportClient, FVie bIsTracking = false; bool bTransactionHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bTransactionHandled |= Mode->EndTracking(InViewportClient, InViewportClient->Viewport); } @@ -569,7 +678,7 @@ bool FEditorModeTools::EndTracking(FEditorViewportClient* InViewportClient, FVie bool FEditorModeTools::AllowsViewportDragTool() const { bool bCanUseDragTool = false; - for (const TSharedPtr& Mode : Modes) + for (const TSharedPtr& Mode : ActiveModes) { bCanUseDragTool |= Mode->AllowsViewportDragTool(); } @@ -579,9 +688,9 @@ bool FEditorModeTools::AllowsViewportDragTool() const /** Notifies all active modes that a map change has occured */ void FEditorModeTools::MapChangeNotify() { - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; Mode->MapChangeNotify(); } } @@ -590,9 +699,9 @@ void FEditorModeTools::MapChangeNotify() /** Notifies all active modes to empty their selections */ void FEditorModeTools::SelectNone() { - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; Mode->SelectNone(); } } @@ -601,9 +710,9 @@ void FEditorModeTools::SelectNone() bool FEditorModeTools::BoxSelect( FBox& InBox, bool InSelect ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->BoxSelect( InBox, InSelect ); } return bHandled; @@ -613,9 +722,9 @@ bool FEditorModeTools::BoxSelect( FBox& InBox, bool InSelect ) bool FEditorModeTools::FrustumSelect( const FConvexVolume& InFrustum, FEditorViewportClient* InViewportClient, bool InSelect ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->FrustumSelect( InFrustum, InViewportClient, InSelect ); } return bHandled; @@ -626,7 +735,7 @@ bool FEditorModeTools::FrustumSelect( const FConvexVolume& InFrustum, FEditorVie bool FEditorModeTools::UsesTransformWidget() const { bool bUsesTransformWidget = false; - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { bUsesTransformWidget |= Mode->UsesTransformWidget(); } @@ -638,7 +747,7 @@ bool FEditorModeTools::UsesTransformWidget() const bool FEditorModeTools::UsesTransformWidget( FWidget::EWidgetMode CheckMode ) const { bool bUsesTransformWidget = false; - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { bUsesTransformWidget |= Mode->UsesTransformWidget(CheckMode); } @@ -649,9 +758,9 @@ bool FEditorModeTools::UsesTransformWidget( FWidget::EWidgetMode CheckMode ) con /** Sets the current widget axis */ void FEditorModeTools::SetCurrentWidgetAxis( EAxisList::Type NewAxis ) { - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; Mode->SetCurrentWidgetAxis( NewAxis ); } } @@ -660,9 +769,9 @@ void FEditorModeTools::SetCurrentWidgetAxis( EAxisList::Type NewAxis ) bool FEditorModeTools::HandleClick(FEditorViewportClient* InViewportClient, HHitProxy *HitProxy, const FViewportClick& Click ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->HandleClick(InViewportClient, HitProxy, Click); } @@ -673,12 +782,12 @@ bool FEditorModeTools::HandleClick(FEditorViewportClient* InViewportClient, HHi bool FEditorModeTools::ShouldDrawBrushWireframe( AActor* InActor ) const { bool bShouldDraw = false; - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { bShouldDraw |= Mode->ShouldDrawBrushWireframe( InActor ); } - if( Modes.Num() == 0 ) + if( ActiveModes.Num() == 0 ) { // We can get into a state where there are no active modes at editor startup if the builder brush is created before the default mode is activated. // Ensure we can see the builder brush when no modes are active. @@ -698,23 +807,23 @@ bool FEditorModeTools::ShouldDrawBrushVertices() const void FEditorModeTools::Tick( FEditorViewportClient* ViewportClient, float DeltaTime ) { // Remove anything pending destruction - for( int32 Index = Modes.Num() - 1; Index >= 0; --Index) + for( int32 Index = ActiveModes.Num() - 1; Index >= 0; --Index) { - if (Modes[Index]->IsPendingDeletion()) + if (ActiveModes[Index]->IsPendingDeletion()) { DeactivateModeAtIndex(Index); } } - if (Modes.Num() == 0) + if (ActiveModes.Num() == 0) { // Ensure the default mode is active if there are no active modes. ActivateDefaultMode(); } - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; Mode->Tick( ViewportClient, DeltaTime ); } } @@ -723,9 +832,9 @@ void FEditorModeTools::Tick( FEditorViewportClient* ViewportClient, float DeltaT bool FEditorModeTools::InputDelta( FEditorViewportClient* InViewportClient,FViewport* InViewport,FVector& InDrag,FRotator& InRot,FVector& InScale ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->InputDelta( InViewportClient, InViewport, InDrag, InRot, InScale ); } return bHandled; @@ -735,9 +844,9 @@ bool FEditorModeTools::InputDelta( FEditorViewportClient* InViewportClient,FView bool FEditorModeTools::CapturedMouseMove( FEditorViewportClient* InViewportClient, FViewport* InViewport, int32 InMouseX, int32 InMouseY ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->CapturedMouseMove( InViewportClient, InViewport, InMouseX, InMouseY ); } return bHandled; @@ -747,9 +856,9 @@ bool FEditorModeTools::CapturedMouseMove( FEditorViewportClient* InViewportClien bool FEditorModeTools::ProcessCapturedMouseMoves( FEditorViewportClient* InViewportClient, FViewport* InViewport, const TArrayView& CapturedMouseMoves ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->ProcessCapturedMouseMoves( InViewportClient, InViewport, CapturedMouseMoves ); } return bHandled; @@ -759,9 +868,9 @@ bool FEditorModeTools::ProcessCapturedMouseMoves( FEditorViewportClient* InViewp bool FEditorModeTools::InputKey(FEditorViewportClient* InViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->InputKey( InViewportClient, Viewport, Key, Event ); } return bHandled; @@ -771,9 +880,9 @@ bool FEditorModeTools::InputKey(FEditorViewportClient* InViewportClient, FViewpo bool FEditorModeTools::InputAxis(FEditorViewportClient* InViewportClient, FViewport* Viewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->InputAxis( InViewportClient, Viewport, ControllerId, Key, Delta, DeltaTime ); } return bHandled; @@ -782,9 +891,9 @@ bool FEditorModeTools::InputAxis(FEditorViewportClient* InViewportClient, FViewp bool FEditorModeTools::GetPivotForOrbit( FVector& Pivot ) const { // Just return the first pivot point specified by a mode - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; if ( Mode->GetPivotForOrbit( Pivot ) ) { return true; @@ -796,9 +905,9 @@ bool FEditorModeTools::GetPivotForOrbit( FVector& Pivot ) const bool FEditorModeTools::MouseEnter( FEditorViewportClient* InViewportClient, FViewport* Viewport, int32 X, int32 Y ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->MouseEnter( InViewportClient, Viewport, X, Y ); } return bHandled; @@ -807,9 +916,9 @@ bool FEditorModeTools::MouseEnter( FEditorViewportClient* InViewportClient, FVie bool FEditorModeTools::MouseLeave( FEditorViewportClient* InViewportClient, FViewport* Viewport ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->MouseLeave( InViewportClient, Viewport ); } return bHandled; @@ -819,9 +928,9 @@ bool FEditorModeTools::MouseLeave( FEditorViewportClient* InViewportClient, FVie bool FEditorModeTools::MouseMove( FEditorViewportClient* InViewportClient, FViewport* Viewport, int32 X, int32 Y ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->MouseMove( InViewportClient, Viewport, X, Y ); } return bHandled; @@ -830,9 +939,9 @@ bool FEditorModeTools::MouseMove( FEditorViewportClient* InViewportClient, FView bool FEditorModeTools::ReceivedFocus( FEditorViewportClient* InViewportClient, FViewport* Viewport ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->ReceivedFocus( InViewportClient, Viewport ); } return bHandled; @@ -841,9 +950,9 @@ bool FEditorModeTools::ReceivedFocus( FEditorViewportClient* InViewportClient, F bool FEditorModeTools::LostFocus( FEditorViewportClient* InViewportClient, FViewport* Viewport ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->LostFocus( InViewportClient, Viewport ); } return bHandled; @@ -852,7 +961,7 @@ bool FEditorModeTools::LostFocus( FEditorViewportClient* InViewportClient, FView /** Draws all active mode components */ void FEditorModeTools::DrawActiveModes( const FSceneView* InView, FPrimitiveDrawInterface* PDI ) { - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { Mode->Draw( InView, PDI ); } @@ -861,7 +970,7 @@ void FEditorModeTools::DrawActiveModes( const FSceneView* InView, FPrimitiveDraw /** Renders all active modes */ void FEditorModeTools::Render( const FSceneView* InView, FViewport* Viewport, FPrimitiveDrawInterface* PDI ) { - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { Mode->Render( InView, Viewport, PDI ); } @@ -870,7 +979,7 @@ void FEditorModeTools::Render( const FSceneView* InView, FViewport* Viewport, FP /** Draws the HUD for all active modes */ void FEditorModeTools::DrawHUD( FEditorViewportClient* InViewportClient,FViewport* Viewport, const FSceneView* View, FCanvas* Canvas ) { - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { Mode->DrawHUD( InViewportClient, Viewport, View, Canvas ); } @@ -881,9 +990,9 @@ void FEditorModeTools::PostUndo(bool bSuccess) { if (bSuccess) { - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; Mode->PostUndo(); } } @@ -897,7 +1006,7 @@ void FEditorModeTools::PostRedo(bool bSuccess) bool FEditorModeTools::AllowWidgetMove() const { bool bAllow = false; - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { bAllow |= Mode->AllowWidgetMove(); } @@ -907,7 +1016,7 @@ bool FEditorModeTools::AllowWidgetMove() const bool FEditorModeTools::DisallowMouseDeltaTracking() const { bool bDisallow = false; - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { bDisallow |= Mode->DisallowMouseDeltaTracking(); } @@ -917,7 +1026,7 @@ bool FEditorModeTools::DisallowMouseDeltaTracking() const bool FEditorModeTools::GetCursor(EMouseCursor::Type& OutCursor) const { bool bHandled = false; - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { bHandled |= Mode->GetCursor(OutCursor); } @@ -927,7 +1036,7 @@ bool FEditorModeTools::GetCursor(EMouseCursor::Type& OutCursor) const bool FEditorModeTools::GetOverrideCursorVisibility(bool& bWantsOverride, bool& bHardwareCursorVisible, bool bSoftwareCursorVisible) const { bool bHandled = false; - for (const auto& Mode : Modes) + for (const auto& Mode : ActiveModes) { bHandled |= Mode->GetOverrideCursorVisibility(bWantsOverride, bHardwareCursorVisible, bSoftwareCursorVisible); } @@ -937,7 +1046,7 @@ bool FEditorModeTools::GetOverrideCursorVisibility(bool& bWantsOverride, bool& b bool FEditorModeTools::PreConvertMouseMovement(FEditorViewportClient* InViewportClient) { bool bHandled = false; - for (const auto& Mode : Modes) + for (const auto& Mode : ActiveModes) { bHandled |= Mode->PreConvertMouseMovement(InViewportClient); } @@ -947,7 +1056,7 @@ bool FEditorModeTools::PreConvertMouseMovement(FEditorViewportClient* InViewport bool FEditorModeTools::PostConvertMouseMovement(FEditorViewportClient* InViewportClient) { bool bHandled = false; - for (const auto& Mode : Modes) + for (const auto& Mode : ActiveModes) { bHandled |= Mode->PostConvertMouseMovement(InViewportClient); } @@ -1009,11 +1118,11 @@ void FEditorModeTools::LoadWidgetSettings(void) FVector FEditorModeTools::GetWidgetLocation() const { - for (int Index = Modes.Num() - 1; Index >= 0 ; Index--) + for (int Index = ActiveModes.Num() - 1; Index >= 0 ; Index--) { - if ( Modes[Index]->UsesTransformWidget() ) + if ( ActiveModes[Index]->UsesTransformWidget() ) { - return Modes[Index]->GetWidgetLocation(); + return ActiveModes[Index]->GetWidgetLocation(); } } @@ -1126,15 +1235,15 @@ void FEditorModeTools::ClearAllBookmarks( FEditorViewportClient* InViewportClien void FEditorModeTools::AddReferencedObjects( FReferenceCollector& Collector ) { - for( int32 x = 0 ; x < Modes.Num() ; ++x ) + for( int32 x = 0 ; x < ActiveModes.Num() ; ++x ) { - Modes[x]->AddReferencedObjects( Collector ); + ActiveModes[x]->AddReferencedObjects( Collector ); } } FEdMode* FEditorModeTools::GetActiveMode( FEditorModeID InID ) { - for( auto& Mode : Modes ) + for( auto& Mode : ActiveModes ) { if( Mode->GetID() == InID ) { @@ -1146,7 +1255,7 @@ FEdMode* FEditorModeTools::GetActiveMode( FEditorModeID InID ) const FEdMode* FEditorModeTools::GetActiveMode( FEditorModeID InID ) const { - for (const auto& Mode : Modes) + for (const auto& Mode : ActiveModes) { if (Mode->GetID() == InID) { @@ -1191,14 +1300,14 @@ void FEditorModeTools::GetActiveModes( TArray& OutActiveModes ) { OutActiveModes.Empty(); // Copy into an array. Do not let users modify the active list directly. - for( auto& Mode : Modes) + for( auto& Mode : ActiveModes) { OutActiveModes.Add(Mode.Get()); } } bool FEditorModeTools::CanCycleWidgetMode() const { - for (auto& Mode : Modes) + for (auto& Mode : ActiveModes) { if (Mode->CanCycleWidgetMode()) { diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorModeRegistry.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorModeRegistry.cpp index d34fdfc8026c..65b9f2002607 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorModeRegistry.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorModeRegistry.cpp @@ -32,6 +32,7 @@ FEditorModeInfo::FEditorModeInfo( int32 InPriorityOrder ) : ID(InID) + , ToolbarCustomizationName(*(InID.ToString()+TEXT("Toolbar"))) , Name(InName) , IconBrush(InIconBrush) , bVisible(InIsVisible) diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorSelectUtils.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorSelectUtils.cpp index 5bc69af4bc98..635f9a8abdc0 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorSelectUtils.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorSelectUtils.cpp @@ -561,6 +561,15 @@ void UUnrealEdEngine::SelectActor(AActor* Actor, bool bInSelected, bool bNotify, // Select the actor and update its internals. if( !bSelectionHandled ) { + if (bInSelected) + { + // If trying to select an Actor spawned by a ChildActorComponent, instead iterate up the hierarchy until we find a valid actor to select + while (Actor->IsChildActor()) + { + Actor = Actor->GetParentComponent()->GetOwner(); + } + } + if (UActorGroupingUtils::IsGroupingActive()) { // if this actor is a group, do a group select/deselect diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorServer.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorServer.cpp index 56f4836ea867..a3143f336f2e 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorServer.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorServer.cpp @@ -23,9 +23,9 @@ #include "UObject/UnrealType.h" #include "UObject/UObjectAnnotation.h" #include "Serialization/ArchiveCountMem.h" -#include "Serialization/ArchiveTraceRoute.h" #include "Misc/PackageName.h" #include "UObject/PackageFileSummary.h" +#include "UObject/ReferenceChainSearch.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SWindow.h" #include "Framework/Application/SlateApplication.h" @@ -1963,17 +1963,12 @@ void UEditorEngine::CheckForWorldGCLeaks( UWorld* NewWorld, UPackage* WorldPacka const bool bIsPersistantWorldType = (RemainingWorld->WorldType == EWorldType::Inactive) || (RemainingWorld->WorldType == EWorldType::EditorPreview) || (RemainingWorld->WorldType == EWorldType::GamePreview); if(!bIsNewWorld && !bIsPersistantWorldType && !WorldHasValidContext(RemainingWorld)) { - StaticExec(RemainingWorld, *FString::Printf(TEXT("OBJ REFS CLASS=WORLD NAME=%s"), *RemainingWorld->GetPathName())); - - TMap Route = FArchiveTraceRoute::FindShortestRootPath(RemainingWorld, true, GARBAGE_COLLECTION_KEEPFLAGS); - FString ErrorString = FArchiveTraceRoute::PrintRootPath(Route, RemainingWorld); - - UE_LOG(LogEditorServer, Fatal, TEXT("%s still around while trying to load new map") LINE_TERMINATOR TEXT("%s"), *RemainingWorld->GetPathName(), *ErrorString); + FReferenceChainSearch RefChainSearch(RemainingWorld, EReferenceChainSearchMode::Shortest | EReferenceChainSearchMode::PrintResults); + UE_LOG(LogEditorServer, Fatal, TEXT("Old world %s not cleaned up by garbage collection while loading new map! Referenced by:") LINE_TERMINATOR TEXT("%s"), *RemainingWorld->GetPathName(), *RefChainSearch.GetRootPath()); } } - - if(WorldPackage != NULL) + if(WorldPackage != nullptr) { UPackage* NewWorldPackage = NewWorld ? NewWorld->GetOutermost() : nullptr; for(TObjectIterator It; It; ++It) @@ -1982,12 +1977,8 @@ void UEditorEngine::CheckForWorldGCLeaks( UWorld* NewWorld, UPackage* WorldPacka const bool bIsNewWorldPackage = (NewWorldPackage && RemainingPackage == NewWorldPackage); if(!bIsNewWorldPackage && RemainingPackage == WorldPackage) { - StaticExec(NULL, *FString::Printf(TEXT("OBJ REFS CLASS=PACKAGE NAME=%s"), *RemainingPackage->GetPathName())); - - TMap Route = FArchiveTraceRoute::FindShortestRootPath(RemainingPackage, true, GARBAGE_COLLECTION_KEEPFLAGS); - FString ErrorString = FArchiveTraceRoute::PrintRootPath(Route, RemainingPackage); - - UE_LOG(LogEditorServer, Fatal, TEXT("%s still around while trying to load new map") LINE_TERMINATOR TEXT("%s"), *RemainingPackage->GetPathName(), *ErrorString); + FReferenceChainSearch RefChainSearch(RemainingPackage, EReferenceChainSearchMode::Shortest | EReferenceChainSearchMode::PrintResults); + UE_LOG(LogEditorServer, Fatal, TEXT("Old level package %s not cleaned up by garbage collection while loading new map! Referenced by:") LINE_TERMINATOR TEXT("%s"), *RemainingPackage->GetPathName(), *RefChainSearch.GetRootPath()); } } } @@ -2562,7 +2553,7 @@ bool UEditorEngine::Map_Load(const TCHAR* Str, FOutputDevice& Ar) } } - if (WorldPackage == NULL) + if (WorldPackage == nullptr) { FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "MapPackageLoadFailed", "Failed to open map file. This is most likely because the map was saved with a newer version of the engine.")); GIsEditorLoadingPackage = false; @@ -2572,17 +2563,12 @@ bool UEditorEngine::Map_Load(const TCHAR* Str, FOutputDevice& Ar) // Reset the opened package to nothing. UserOpenedFile = FString(); - UWorld* World = UWorld::FindWorldInPackage(WorldPackage); - if (World == NULL) + if (World == nullptr) { - StaticExec(NULL, *FString::Printf(TEXT("OBJ REFS CLASS=PACKAGE NAME=%s"), *WorldPackage->GetPathName())); - - TMap Route = FArchiveTraceRoute::FindShortestRootPath( WorldPackage, true, GARBAGE_COLLECTION_KEEPFLAGS ); - FString ErrorString = FArchiveTraceRoute::PrintRootPath( Route, WorldPackage ); - - UE_LOG(LogEditorServer, Fatal,TEXT("Failed to find the world in %s.") LINE_TERMINATOR TEXT("%s"),*WorldPackage->GetPathName(),*TempFname,*ErrorString); + FReferenceChainSearch RefChainSearch(WorldPackage, EReferenceChainSearchMode::Shortest | EReferenceChainSearchMode::PrintResults); + UE_LOG(LogEditorServer, Fatal, TEXT("Failed to find the world in already loaded world package %s! Referenced by:") LINE_TERMINATOR TEXT("%s"), *WorldPackage->GetPathName(), *RefChainSearch.GetRootPath()); } Context.SetCurrentWorld(World); GWorld = World; @@ -4763,6 +4749,46 @@ void UEditorEngine::MoveViewportCamerasToComponent(USceneComponent* Component, b } } +void UEditorEngine::MoveViewportCamerasToBox(const FBox& BoundingBox, bool bActiveViewportOnly) const +{ + // Make sure we had at least one non-null actor in the array passed in. + if (BoundingBox.GetSize() != FVector::ZeroVector || BoundingBox.GetCenter() != FVector::ZeroVector) + { + if (bActiveViewportOnly) + { + if (GCurrentLevelEditingViewportClient) + { + GCurrentLevelEditingViewportClient->FocusViewportOnBox(BoundingBox); + + // Update Linked Orthographic viewports. + if (GCurrentLevelEditingViewportClient->IsOrtho() && GetDefault()->bUseLinkedOrthographicViewports) + { + // Search through all viewports + for (FLevelEditorViewportClient* LinkedViewportClient : GetLevelViewportClients()) + { + // Only update other orthographic viewports + if (LinkedViewportClient && LinkedViewportClient != GCurrentLevelEditingViewportClient && LinkedViewportClient->IsOrtho()) + { + LinkedViewportClient->FocusViewportOnBox(BoundingBox); + } + } + } + } + + } + else + { + // Update all viewports. + for (FLevelEditorViewportClient* LinkedViewportClient : GetLevelViewportClients()) + { + //Dont move camera attach to an actor + if (!LinkedViewportClient->IsAnyActorLocked()) + LinkedViewportClient->FocusViewportOnBox(BoundingBox); + } + } + } +} + /** * Snaps an actor in a direction. Optionally will align with the trace normal. * @param InActor Actor to move to the floor. @@ -6852,43 +6878,3 @@ void UEditorEngine::AutoMergeStaticMeshes() } #endif // #if TODO_STATICMESH } - -void UEditorEngine::MoveViewportCamerasToBox(const FBox& BoundingBox, bool bActiveViewportOnly) const -{ - // Make sure we had at least one non-null actor in the array passed in. - if (BoundingBox.GetSize() != FVector::ZeroVector || BoundingBox.GetCenter() != FVector::ZeroVector) - { - if (bActiveViewportOnly) - { - if (GCurrentLevelEditingViewportClient) - { - GCurrentLevelEditingViewportClient->FocusViewportOnBox(BoundingBox); - - // Update Linked Orthographic viewports. - if (GCurrentLevelEditingViewportClient->IsOrtho() && GetDefault()->bUseLinkedOrthographicViewports) - { - // Search through all viewports - for(FLevelEditorViewportClient* LinkedViewportClient : GetLevelViewportClients()) - { - // Only update other orthographic viewports - if (LinkedViewportClient && LinkedViewportClient != GCurrentLevelEditingViewportClient && LinkedViewportClient->IsOrtho()) - { - LinkedViewportClient->FocusViewportOnBox(BoundingBox); - } - } - } - } - - } - else - { - // Update all viewports. - for(FLevelEditorViewportClient* LinkedViewportClient : GetLevelViewportClients()) - { - //Dont move camera attach to an actor - if (!LinkedViewportClient->IsAnyActorLocked()) - LinkedViewportClient->FocusViewportOnBox(BoundingBox); - } - } - } -} diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorTransaction.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorTransaction.cpp index 5531d7c23642..1ce0eeccc537 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorTransaction.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorTransaction.cpp @@ -233,8 +233,24 @@ void FTransaction::FObjectRecord::Load(FTransaction* Owner) if (CustomChange.IsValid()) { - TUniquePtr InvertedChange = CustomChange->Execute( Object.Get() ); - CustomChange = MoveTemp( InvertedChange ); + if (CustomChange->GetChangeType() == FChange::EChangeStyle::InPlaceSwap) + { + TUniquePtr InvertedChange = CustomChange->Execute(Object.Get()); + ensure(InvertedChange->GetChangeType() == FChange::EChangeStyle::InPlaceSwap); + CustomChange = MoveTemp(InvertedChange); + } + else + { + bool bIsRedo = (Owner->Inc == 1); + if (bIsRedo) + { + CustomChange->Apply(Object.Get()); + } + else + { + CustomChange->Revert(Object.Get()); + } + } } else { @@ -457,8 +473,8 @@ void FTransaction::FObjectRecord::Diff( const FTransaction* Owner, const FSerial // Compare the data before the property block to see if something else in the object has changed if (!OutDeltaChange.bHasNonPropertyChanges) { - const int32 OldHeaderSize = StartOfOldPropertyBlock; - const int32 CurrentHeaderSize = StartOfNewPropertyBlock; + const int32 OldHeaderSize = FMath::Min(StartOfOldPropertyBlock, OldSerializedObject.Data.Num()); + const int32 CurrentHeaderSize = FMath::Min(StartOfNewPropertyBlock, NewSerializedObject.Data.Num()); bool bIsHeaderIdentical = OldHeaderSize == CurrentHeaderSize; if (bIsHeaderIdentical && CurrentHeaderSize > 0) @@ -475,8 +491,8 @@ void FTransaction::FObjectRecord::Diff( const FTransaction* Owner, const FSerial // Compare the data after the property block to see if something else in the object has changed if (!OutDeltaChange.bHasNonPropertyChanges) { - const int32 OldFooterSize = OldSerializedObject.Data.Num() - EndOfOldPropertyBlock; - const int32 CurrentFooterSize = NewSerializedObject.Data.Num() - EndOfNewPropertyBlock; + const int32 OldFooterSize = OldSerializedObject.Data.Num() - FMath::Max(EndOfOldPropertyBlock, 0); + const int32 CurrentFooterSize = NewSerializedObject.Data.Num() - FMath::Max(EndOfNewPropertyBlock, 0); bool bIsFooterIdentical = OldFooterSize == CurrentFooterSize; if (bIsFooterIdentical && CurrentFooterSize > 0) diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorViewportClient.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorViewportClient.cpp index 73d3890de892..7bbec07c7c9d 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorViewportClient.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorViewportClient.cpp @@ -2298,6 +2298,11 @@ void FEditorViewportClient::InputAxisForOrbit(FViewport* InViewport, const FVect SetViewLocation(RotatedViewLocation); FEditorViewportStats::Using(IsPerspective() ? FEditorViewportStats::CAT_PERSPECTIVE_MOUSE_ORBIT_ROTATION : FEditorViewportStats::CAT_ORTHOGRAPHIC_MOUSE_ORBIT_ROTATION); + + if (IsPerspective()) + { + PerspectiveCameraMoved(); + } } else { @@ -3373,6 +3378,11 @@ void FEditorViewportClient::AddReferencedObjects( FReferenceCollector& Collector } } +FString FEditorViewportClient::GetReferencerName() const +{ + return TEXT("FEditorViewportClient"); +} + void FEditorViewportClient::ProcessClick(class FSceneView& View, class HHitProxy* HitProxy, FKey Key, EInputEvent Event, uint32 HitX, uint32 HitY) { const FViewportClick Click(&View, this, Key, Event, HitX, HitY); diff --git a/Engine/Source/Editor/UnrealEd/Private/Factories/CSVImportFactory.cpp b/Engine/Source/Editor/UnrealEd/Private/Factories/CSVImportFactory.cpp index ada7c9602c04..741d02b3518a 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Factories/CSVImportFactory.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Factories/CSVImportFactory.cpp @@ -111,22 +111,30 @@ UObject* UCSVImportFactory::FactoryCreateText(UClass* InClass, UObject* InParent // Save off information if so bool bHaveInfo = false; - UScriptStruct* ImportRowStruct = nullptr; ERichCurveInterpMode ImportCurveInterpMode = RCIM_Linear; ECSVImportType ImportType = ECSVImportType::ECSV_DataTable; + UClass* DataTableClass = UDataTable::StaticClass(); + + // Clear our temp table + TempImportDataTable = nullptr; if (IsAutomatedImport()) { - ImportRowStruct = AutomatedImportSettings.ImportRowStruct; ImportCurveInterpMode = AutomatedImportSettings.ImportCurveInterpMode; ImportType = AutomatedImportSettings.ImportType; + TempImportDataTable = NewObject(this, UDataTable::StaticClass(), InName, Flags); + TempImportDataTable->RowStruct = AutomatedImportSettings.ImportRowStruct; + // For automated import to work a row struct must be specified for a datatable type or a curve type must be specified - bHaveInfo = ImportRowStruct != nullptr || ImportType != ECSVImportType::ECSV_DataTable; + bHaveInfo = TempImportDataTable->RowStruct != nullptr || ImportType != ECSVImportType::ECSV_DataTable; } else if (ExistingTable != nullptr) { - ImportRowStruct = ExistingTable->RowStruct; + ImportType = ECSVImportType::ECSV_DataTable; + TempImportDataTable = NewObject(this, ExistingTable->GetClass(), InName, Flags); + TempImportDataTable->CopyImportOptions(ExistingTable); + bHaveInfo = true; } else if (ExistingCurveTable != nullptr) @@ -142,6 +150,12 @@ UObject* UCSVImportFactory::FactoryCreateText(UClass* InClass, UObject* InParent bool bDoImport = true; + if (!TempImportDataTable) + { + // Create an empty one + TempImportDataTable = NewObject(this, UDataTable::StaticClass(), InName, Flags); + } + // If we do not have the info we need, pop up window to ask for things if (!bHaveInfo && !IsAutomatedImport()) { @@ -171,19 +185,20 @@ UObject* UCSVImportFactory::FactoryCreateText(UClass* InClass, UObject* InParent SAssignNew(ImportOptionsWindow, SCSVImportOptions) .WidgetWindow(Window) .FullPath(FText::FromString(ParentFullPath)) + .TempImportDataTable(TempImportDataTable) ); FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); ImportType = ImportOptionsWindow->GetSelectedImportType(); - ImportRowStruct = ImportOptionsWindow->GetSelectedRowStruct(); + TempImportDataTable->RowStruct = ImportOptionsWindow->GetSelectedRowStruct(); ImportCurveInterpMode = ImportOptionsWindow->GetSelectedCurveIterpMode(); bDoImport = ImportOptionsWindow->ShouldImport(); bOutOperationCanceled = !bDoImport; } else if (!bHaveInfo && IsAutomatedImport()) { - if (ImportType == ECSVImportType::ECSV_DataTable && !ImportRowStruct) + if (ImportType == ECSVImportType::ECSV_DataTable && !TempImportDataTable->RowStruct) { UE_LOG(LogCSVImportFactory, Error, TEXT("A Data table row type must be specified in the import settings json file for automated import")); } @@ -207,10 +222,7 @@ UObject* UCSVImportFactory::FactoryCreateText(UClass* InClass, UObject* InParent if (ImportType == ECSVImportType::ECSV_DataTable) { - UClass* DataTableClass = UDataTable::StaticClass(); - // If there is an existing table, need to call this to free data memory before recreating object - bool bStripFromClientBuilds = false; UDataTable::FOnDataTableChanged OldOnDataTableChanged; if (ExistingTable != nullptr) { @@ -218,14 +230,14 @@ UObject* UCSVImportFactory::FactoryCreateText(UClass* InClass, UObject* InParent ExistingTable->OnDataTableChanged().Clear(); DataTableClass = ExistingTable->GetClass(); ExistingTable->EmptyTable(); - bStripFromClientBuilds = ExistingTable->bStripFromClientBuilds; } // Create/reset table UDataTable* NewTable = NewObject(InParent, DataTableClass, InName, Flags); - NewTable->RowStruct = ImportRowStruct; + + NewTable->CopyImportOptions(TempImportDataTable); NewTable->AssetImportData->Update(CurrentFilename); - NewTable->bStripFromClientBuilds = bStripFromClientBuilds; + // Go ahead and create table from string Problems = DoImportDataTable(NewTable, String); diff --git a/Engine/Source/Editor/UnrealEd/Private/Factories/DataTableFactory.cpp b/Engine/Source/Editor/UnrealEd/Private/Factories/DataTableFactory.cpp index faefd57a955b..3fa4cdefac99 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Factories/DataTableFactory.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Factories/DataTableFactory.cpp @@ -28,26 +28,19 @@ bool UDataTableFactory::ConfigureProperties() class FDataTableFactoryUI : public TSharedFromThis < FDataTableFactoryUI > { TSharedPtr PickerWindow; - TSharedPtr> RowStructCombo; + TSharedPtr RowStructCombo; TSharedPtr OkButton; UScriptStruct* ResultStruct; public: FDataTableFactoryUI() : ResultStruct(nullptr) {} - TSharedRef MakeRowStructItemWidget(class UScriptStruct* InStruct) const + void OnStructSelected(UScriptStruct* NewStruct) { - return SNew(STextBlock).Text(InStruct ? InStruct->GetDisplayNameText() : FText::GetEmpty()); - } - - FText GetSelectedRowOptionText() const - { - UScriptStruct* RowStruct = RowStructCombo.IsValid() ? RowStructCombo->GetSelectedItem() : nullptr; - return RowStruct ? RowStruct->GetDisplayNameText() : FText::GetEmpty(); + ResultStruct = NewStruct; } FReply OnCreate() { - ResultStruct = RowStructCombo.IsValid() ? RowStructCombo->GetSelectedItem() : nullptr; if (PickerWindow.IsValid()) { PickerWindow->RequestDestroyWindow(); @@ -67,21 +60,14 @@ bool UDataTableFactory::ConfigureProperties() bool IsAnyRowSelected() const { - return RowStructCombo.IsValid() && RowStructCombo->GetSelectedItem(); + return ResultStruct != nullptr; } UScriptStruct* OpenStructSelector() { ResultStruct = nullptr; - auto RowStructs = FDataTableEditorUtils::GetPossibleStructs(); - RowStructCombo = SNew(SComboBox) - .OptionsSource(&RowStructs) - .OnGenerateWidget(this, &FDataTableFactoryUI::MakeRowStructItemWidget) - [ - SNew(STextBlock) - .Text(this, &FDataTableFactoryUI::GetSelectedRowOptionText) - ]; + RowStructCombo = FDataTableEditorUtils::MakeRowStructureComboBox(FDataTableEditorUtils::FOnDataTableStructSelected::CreateSP(this, &FDataTableFactoryUI::OnStructSelected)); PickerWindow = SNew(SWindow) .Title(LOCTEXT("DataTableFactoryOptions", "Pick Row Structure")) diff --git a/Engine/Source/Editor/UnrealEd/Private/Factories/EditorFactories.cpp b/Engine/Source/Editor/UnrealEd/Private/Factories/EditorFactories.cpp index f9eaf04b6a03..467fc86059bf 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Factories/EditorFactories.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Factories/EditorFactories.cpp @@ -5648,12 +5648,13 @@ EReimportResult::Type UReimportFbxStaticMeshFactory::Reimport( UObject* Obj ) { if ((*UserData)[Idx] != nullptr) { - bool bAddDupToRoot = !((*UserData)[Idx]->IsRooted()); + UAssetUserData* DupObject = (UAssetUserData*)StaticDuplicateObject((*UserData)[Idx], GetTransientPackage()); + bool bAddDupToRoot = !(DupObject->IsRooted()); if (bAddDupToRoot) { - (*UserData)[Idx]->AddToRoot(); + DupObject->AddToRoot(); } - UserDataCopy.Add((UAssetUserData*)StaticDuplicateObject((*UserData)[Idx], GetTransientPackage()), bAddDupToRoot); + UserDataCopy.Add(DupObject, bAddDupToRoot); } } } diff --git a/Engine/Source/Editor/UnrealEd/Private/Factories/SkeletalMeshImport.cpp b/Engine/Source/Editor/UnrealEd/Private/Factories/SkeletalMeshImport.cpp index 4ad6159d5ed4..512a642e3329 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Factories/SkeletalMeshImport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Factories/SkeletalMeshImport.cpp @@ -30,6 +30,7 @@ #include "Factories/FbxSkeletalMeshImportData.h" #include "IMeshReductionManagerModule.h" #include "Rendering/SkeletalMeshModel.h" +#include "Engine/AssetUserData.h" DEFINE_LOG_CATEGORY_STATIC(LogSkeletalMeshImport, Log, All); @@ -388,165 +389,184 @@ bool SkeletalMeshIsUsingMaterialSlotNameWorkflow(UAssetImportData* AssetImportDa ExistingSkelMeshData* SaveExistingSkelMeshData(USkeletalMesh* ExistingSkelMesh, bool bSaveMaterials, int32 ReimportLODIndex) { - struct ExistingSkelMeshData* ExistingMeshDataPtr = NULL; - if(ExistingSkelMesh) + ExistingSkelMeshData* ExistingMeshDataPtr = nullptr; + + if (!ExistingSkelMesh) { - bool ReimportSpecificLOD = (ReimportLODIndex > 0) && ExistingSkelMesh->GetLODNum() > ReimportLODIndex; + return nullptr; + } + bool ReimportSpecificLOD = (ReimportLODIndex > 0) && ExistingSkelMesh->GetLODNum() > ReimportLODIndex; - ExistingMeshDataPtr = new ExistingSkelMeshData(); + ExistingMeshDataPtr = new ExistingSkelMeshData(); - ExistingMeshDataPtr->UseMaterialNameSlotWorkflow = SkeletalMeshIsUsingMaterialSlotNameWorkflow(ExistingSkelMesh->AssetImportData); - ExistingMeshDataPtr->MinLOD = ExistingSkelMesh->MinLod; - ExistingMeshDataPtr->DisableBelowMinLodStripping = ExistingSkelMesh->DisableBelowMinLodStripping; + ExistingMeshDataPtr->UseMaterialNameSlotWorkflow = SkeletalMeshIsUsingMaterialSlotNameWorkflow(ExistingSkelMesh->AssetImportData); + ExistingMeshDataPtr->MinLOD = ExistingSkelMesh->MinLod; + ExistingMeshDataPtr->DisableBelowMinLodStripping = ExistingSkelMesh->DisableBelowMinLodStripping; - FSkeletalMeshModel* ImportedResource = ExistingSkelMesh->GetImportedModel(); + FSkeletalMeshModel* ImportedResource = ExistingSkelMesh->GetImportedModel(); - //Add the existing Material slot name data - for (int32 MaterialIndex = 0; MaterialIndex < ExistingSkelMesh->Materials.Num(); ++MaterialIndex) + //Add the existing Material slot name data + for (int32 MaterialIndex = 0; MaterialIndex < ExistingSkelMesh->Materials.Num(); ++MaterialIndex) + { + ExistingMeshDataPtr->ExistingImportMaterialOriginalNameData.Add(ExistingSkelMesh->Materials[MaterialIndex].ImportedMaterialSlotName); + } + + for (int32 LodIndex = 0; LodIndex < ImportedResource->LODModels.Num(); ++LodIndex) + { + ExistingMeshDataPtr->ExistingImportMeshLodSectionMaterialData.AddZeroed(); + for (int32 SectionIndex = 0; SectionIndex < ImportedResource->LODModels[LodIndex].Sections.Num(); ++SectionIndex) { - ExistingMeshDataPtr->ExistingImportMaterialOriginalNameData.Add(ExistingSkelMesh->Materials[MaterialIndex].ImportedMaterialSlotName); - } - - for (int32 LodIndex = 0; LodIndex < ImportedResource->LODModels.Num(); ++LodIndex) - { - ExistingMeshDataPtr->ExistingImportMeshLodSectionMaterialData.AddZeroed(); - for (int32 SectionIndex = 0; SectionIndex < ImportedResource->LODModels[LodIndex].Sections.Num(); ++SectionIndex) + int32 SectionMaterialIndex = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].MaterialIndex; + bool SectionCastShadow = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].bCastShadow; + bool SectionRecomputeTangents = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].bRecomputeTangent; + int32 GenerateUpTo = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].GenerateUpToLodIndex; + bool bDisabled = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].bDisabled; + if (ExistingMeshDataPtr->ExistingImportMaterialOriginalNameData.IsValidIndex(SectionMaterialIndex)) { - int32 SectionMaterialIndex = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].MaterialIndex; - bool SectionCastShadow = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].bCastShadow; - bool SectionRecomputeTangents = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].bRecomputeTangent; - int32 GenerateUpTo = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].GenerateUpToLodIndex; - bool bDisabled = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].bDisabled; - if (ExistingMeshDataPtr->ExistingImportMaterialOriginalNameData.IsValidIndex(SectionMaterialIndex)) - { - ExistingMeshDataPtr->ExistingImportMeshLodSectionMaterialData[LodIndex].Add(ExistingMeshLodSectionData(ExistingMeshDataPtr->ExistingImportMaterialOriginalNameData[SectionMaterialIndex], SectionCastShadow, SectionRecomputeTangents, GenerateUpTo, bDisabled)); - } - } - } - - ExistingMeshDataPtr->ExistingSockets = ExistingSkelMesh->GetMeshOnlySocketList(); - ExistingMeshDataPtr->bSaveRestoreMaterials = bSaveMaterials; - if (ExistingMeshDataPtr->bSaveRestoreMaterials) - { - ExistingMeshDataPtr->ExistingMaterials = ExistingSkelMesh->Materials; - } - ExistingMeshDataPtr->ExistingRetargetBasePose = ExistingSkelMesh->RetargetBasePose; - - if( ImportedResource->LODModels.Num() > 0 && - ExistingSkelMesh->GetLODNum() == ImportedResource->LODModels.Num() ) - { - int32 OffsetReductionLODIndex = 0; - FSkeletalMeshLODInfo* LODInfo = ExistingSkelMesh->GetLODInfo( ReimportLODIndex < 0 ? 0 : ReimportLODIndex); - ExistingMeshDataPtr->bIsReimportLODReduced = (LODInfo && LODInfo->bHasBeenSimplified); - if (ExistingMeshDataPtr->bIsReimportLODReduced) - { - //Save the imported LOD reduction settings - ExistingMeshDataPtr->ExistingReimportLODReductionSettings = LODInfo->ReductionSettings; - } - - // Remove the zero'th LOD (ie: the LOD being reimported). - if (!ReimportSpecificLOD) - { - ImportedResource->LODModels.RemoveAt(0); - ExistingSkelMesh->RemoveLODInfo(0); - OffsetReductionLODIndex = 1; - } - - // Copy off the remaining LODs. - for ( int32 LODModelIndex = 0 ; LODModelIndex < ImportedResource->LODModels.Num() ; ++LODModelIndex ) - { - FSkeletalMeshLODModel& LODModel = ImportedResource->LODModels[LODModelIndex]; - LODModel.RawPointIndices.Lock( LOCK_READ_ONLY ); - LODModel.LegacyRawPointIndices.Lock( LOCK_READ_ONLY ); - LODModel.RawSkeletalMeshBulkData.GetBulkData().Lock( LOCK_READ_ONLY ); - int32 ReductionLODIndex = LODModelIndex + OffsetReductionLODIndex; - if (ImportedResource->OriginalReductionSourceMeshData.IsValidIndex(ReductionLODIndex) && !ImportedResource->OriginalReductionSourceMeshData[ReductionLODIndex]->IsEmpty()) - { - FSkeletalMeshLODModel BaseLODModel; - TMap> BaseLODMorphTargetData; - ImportedResource->OriginalReductionSourceMeshData[ReductionLODIndex]->LoadReductionData(BaseLODModel, BaseLODMorphTargetData); - FReductionBaseSkeletalMeshBulkData* ReductionLODData = new FReductionBaseSkeletalMeshBulkData(); - ReductionLODData->SaveReductionData(BaseLODModel, BaseLODMorphTargetData); - //Add necessary empty slot - while (ExistingMeshDataPtr->ExistingOriginalReductionSourceMeshData.Num() < LODModelIndex) - { - FReductionBaseSkeletalMeshBulkData* EmptyReductionLODData = new FReductionBaseSkeletalMeshBulkData(); - ExistingMeshDataPtr->ExistingOriginalReductionSourceMeshData.Add(EmptyReductionLODData); - } - ExistingMeshDataPtr->ExistingOriginalReductionSourceMeshData.Add(ReductionLODData); - } - } - ExistingMeshDataPtr->ExistingLODModels = ImportedResource->LODModels; - for (int32 LODModelIndex = 0; LODModelIndex < ImportedResource->LODModels.Num(); ++LODModelIndex) - { - FSkeletalMeshLODModel& LODModel = ImportedResource->LODModels[LODModelIndex]; - LODModel.RawPointIndices.Unlock(); - LODModel.LegacyRawPointIndices.Unlock(); - LODModel.RawSkeletalMeshBulkData.GetBulkData().Unlock(); - } - - ExistingMeshDataPtr->ExistingLODInfo = ExistingSkelMesh->GetLODInfoArray(); - ExistingMeshDataPtr->ExistingRefSkeleton = ExistingSkelMesh->RefSkeleton; - - } - - // First asset should be the one that the skeletal mesh should point too - ExistingMeshDataPtr->ExistingPhysicsAssets.Empty(); - ExistingMeshDataPtr->ExistingPhysicsAssets.Add( ExistingSkelMesh->PhysicsAsset ); - for (TObjectIterator It; It; ++It) - { - UPhysicsAsset* PhysicsAsset = *It; - if ( PhysicsAsset->PreviewSkeletalMesh == ExistingSkelMesh && ExistingSkelMesh->PhysicsAsset != PhysicsAsset ) - { - ExistingMeshDataPtr->ExistingPhysicsAssets.Add( PhysicsAsset ); - } - } - - ExistingMeshDataPtr->ExistingShadowPhysicsAsset = ExistingSkelMesh->ShadowPhysicsAsset; - - ExistingMeshDataPtr->ExistingSkeleton = ExistingSkelMesh->Skeleton; - // since copying back original skeleton, this shoudl be safe to do - ExistingMeshDataPtr->ExistingPostProcessAnimBlueprint = ExistingSkelMesh->PostProcessAnimBlueprint; - - ExistingMeshDataPtr->ExistingLODSettings = ExistingSkelMesh->LODSettings; - - ExistingSkelMesh->ExportMirrorTable(ExistingMeshDataPtr->ExistingMirrorTable); - - ExistingMeshDataPtr->ExistingMorphTargets.Empty(ExistingSkelMesh->MorphTargets.Num()); - ExistingMeshDataPtr->ExistingMorphTargets.Append(ExistingSkelMesh->MorphTargets); - - ExistingMeshDataPtr->bExistingUseFullPrecisionUVs = ExistingSkelMesh->bUseFullPrecisionUVs; - ExistingMeshDataPtr->bExistingUseHighPrecisionTangentBasis = ExistingSkelMesh->bUseHighPrecisionTangentBasis; - - ExistingMeshDataPtr->ExistingAssetImportData = ExistingSkelMesh->AssetImportData; - ExistingMeshDataPtr->ExistingThumbnailInfo = ExistingSkelMesh->ThumbnailInfo; - - ExistingMeshDataPtr->ExistingClothingAssets = ExistingSkelMesh->MeshClothingAssets; - - ExistingMeshDataPtr->ExistingSamplingInfo = ExistingSkelMesh->GetSamplingInfo(); - - //Add the last fbx import data - UFbxSkeletalMeshImportData* ImportData = Cast(ExistingSkelMesh->AssetImportData); - if (ImportData && ExistingMeshDataPtr->UseMaterialNameSlotWorkflow) - { - for (int32 ImportMaterialOriginalNameDataIndex = 0; ImportMaterialOriginalNameDataIndex < ImportData->ImportMaterialOriginalNameData.Num(); ++ImportMaterialOriginalNameDataIndex) - { - FName MaterialName = ImportData->ImportMaterialOriginalNameData[ImportMaterialOriginalNameDataIndex]; - ExistingMeshDataPtr->LastImportMaterialOriginalNameData.Add(MaterialName); - } - for (int32 LodIndex = 0; LodIndex < ImportData->ImportMeshLodData.Num(); ++LodIndex) - { - ExistingMeshDataPtr->LastImportMeshLodSectionMaterialData.AddZeroed(); - const FImportMeshLodSectionsData &ImportMeshLodSectionsData = ImportData->ImportMeshLodData[LodIndex]; - for (int32 SectionIndex = 0; SectionIndex < ImportMeshLodSectionsData.SectionOriginalMaterialName.Num(); ++SectionIndex) - { - FName MaterialName = ImportMeshLodSectionsData.SectionOriginalMaterialName[SectionIndex]; - ExistingMeshDataPtr->LastImportMeshLodSectionMaterialData[LodIndex].Add(MaterialName); - } + ExistingMeshDataPtr->ExistingImportMeshLodSectionMaterialData[LodIndex].Add(ExistingMeshLodSectionData(ExistingMeshDataPtr->ExistingImportMaterialOriginalNameData[SectionMaterialIndex], SectionCastShadow, SectionRecomputeTangents, GenerateUpTo, bDisabled)); } } } + ExistingMeshDataPtr->ExistingSockets = ExistingSkelMesh->GetMeshOnlySocketList(); + ExistingMeshDataPtr->bSaveRestoreMaterials = bSaveMaterials; + if (ExistingMeshDataPtr->bSaveRestoreMaterials) + { + ExistingMeshDataPtr->ExistingMaterials = ExistingSkelMesh->Materials; + } + ExistingMeshDataPtr->ExistingRetargetBasePose = ExistingSkelMesh->RetargetBasePose; + + if( ImportedResource->LODModels.Num() > 0 && + ExistingSkelMesh->GetLODNum() == ImportedResource->LODModels.Num() ) + { + int32 OffsetReductionLODIndex = 0; + FSkeletalMeshLODInfo* LODInfo = ExistingSkelMesh->GetLODInfo( ReimportLODIndex < 0 ? 0 : ReimportLODIndex); + ExistingMeshDataPtr->bIsReimportLODReduced = (LODInfo && LODInfo->bHasBeenSimplified); + if (ExistingMeshDataPtr->bIsReimportLODReduced) + { + //Save the imported LOD reduction settings + ExistingMeshDataPtr->ExistingReimportLODReductionSettings = LODInfo->ReductionSettings; + } + + // Remove the zero'th LOD (ie: the LOD being reimported). + if (!ReimportSpecificLOD) + { + ImportedResource->LODModels.RemoveAt(0); + ExistingSkelMesh->RemoveLODInfo(0); + OffsetReductionLODIndex = 1; + } + + // Copy off the remaining LODs. + for ( int32 LODModelIndex = 0 ; LODModelIndex < ImportedResource->LODModels.Num() ; ++LODModelIndex ) + { + FSkeletalMeshLODModel& LODModel = ImportedResource->LODModels[LODModelIndex]; + LODModel.RawPointIndices.Lock( LOCK_READ_ONLY ); + LODModel.LegacyRawPointIndices.Lock( LOCK_READ_ONLY ); + LODModel.RawSkeletalMeshBulkData.GetBulkData().Lock( LOCK_READ_ONLY ); + int32 ReductionLODIndex = LODModelIndex + OffsetReductionLODIndex; + if (ImportedResource->OriginalReductionSourceMeshData.IsValidIndex(ReductionLODIndex) && !ImportedResource->OriginalReductionSourceMeshData[ReductionLODIndex]->IsEmpty()) + { + FSkeletalMeshLODModel BaseLODModel; + TMap> BaseLODMorphTargetData; + ImportedResource->OriginalReductionSourceMeshData[ReductionLODIndex]->LoadReductionData(BaseLODModel, BaseLODMorphTargetData); + FReductionBaseSkeletalMeshBulkData* ReductionLODData = new FReductionBaseSkeletalMeshBulkData(); + ReductionLODData->SaveReductionData(BaseLODModel, BaseLODMorphTargetData); + //Add necessary empty slot + while (ExistingMeshDataPtr->ExistingOriginalReductionSourceMeshData.Num() < LODModelIndex) + { + FReductionBaseSkeletalMeshBulkData* EmptyReductionLODData = new FReductionBaseSkeletalMeshBulkData(); + ExistingMeshDataPtr->ExistingOriginalReductionSourceMeshData.Add(EmptyReductionLODData); + } + ExistingMeshDataPtr->ExistingOriginalReductionSourceMeshData.Add(ReductionLODData); + } + } + ExistingMeshDataPtr->ExistingLODModels = ImportedResource->LODModels; + for (int32 LODModelIndex = 0; LODModelIndex < ImportedResource->LODModels.Num(); ++LODModelIndex) + { + FSkeletalMeshLODModel& LODModel = ImportedResource->LODModels[LODModelIndex]; + LODModel.RawPointIndices.Unlock(); + LODModel.LegacyRawPointIndices.Unlock(); + LODModel.RawSkeletalMeshBulkData.GetBulkData().Unlock(); + } + + ExistingMeshDataPtr->ExistingLODInfo = ExistingSkelMesh->GetLODInfoArray(); + ExistingMeshDataPtr->ExistingRefSkeleton = ExistingSkelMesh->RefSkeleton; + + } + + // First asset should be the one that the skeletal mesh should point too + ExistingMeshDataPtr->ExistingPhysicsAssets.Empty(); + ExistingMeshDataPtr->ExistingPhysicsAssets.Add( ExistingSkelMesh->PhysicsAsset ); + for (TObjectIterator It; It; ++It) + { + UPhysicsAsset* PhysicsAsset = *It; + if ( PhysicsAsset->PreviewSkeletalMesh == ExistingSkelMesh && ExistingSkelMesh->PhysicsAsset != PhysicsAsset ) + { + ExistingMeshDataPtr->ExistingPhysicsAssets.Add( PhysicsAsset ); + } + } + + ExistingMeshDataPtr->ExistingShadowPhysicsAsset = ExistingSkelMesh->ShadowPhysicsAsset; + + ExistingMeshDataPtr->ExistingSkeleton = ExistingSkelMesh->Skeleton; + // since copying back original skeleton, this shoudl be safe to do + ExistingMeshDataPtr->ExistingPostProcessAnimBlueprint = ExistingSkelMesh->PostProcessAnimBlueprint; + + ExistingMeshDataPtr->ExistingLODSettings = ExistingSkelMesh->LODSettings; + + ExistingSkelMesh->ExportMirrorTable(ExistingMeshDataPtr->ExistingMirrorTable); + + ExistingMeshDataPtr->ExistingMorphTargets.Empty(ExistingSkelMesh->MorphTargets.Num()); + ExistingMeshDataPtr->ExistingMorphTargets.Append(ExistingSkelMesh->MorphTargets); + + ExistingMeshDataPtr->bExistingUseFullPrecisionUVs = ExistingSkelMesh->bUseFullPrecisionUVs; + ExistingMeshDataPtr->bExistingUseHighPrecisionTangentBasis = ExistingSkelMesh->bUseHighPrecisionTangentBasis; + + ExistingMeshDataPtr->ExistingAssetImportData = ExistingSkelMesh->AssetImportData; + ExistingMeshDataPtr->ExistingThumbnailInfo = ExistingSkelMesh->ThumbnailInfo; + + ExistingMeshDataPtr->ExistingClothingAssets = ExistingSkelMesh->MeshClothingAssets; + + ExistingMeshDataPtr->ExistingSamplingInfo = ExistingSkelMesh->GetSamplingInfo(); + + //Add the last fbx import data + UFbxSkeletalMeshImportData* ImportData = Cast(ExistingSkelMesh->AssetImportData); + if (ImportData && ExistingMeshDataPtr->UseMaterialNameSlotWorkflow) + { + for (int32 ImportMaterialOriginalNameDataIndex = 0; ImportMaterialOriginalNameDataIndex < ImportData->ImportMaterialOriginalNameData.Num(); ++ImportMaterialOriginalNameDataIndex) + { + FName MaterialName = ImportData->ImportMaterialOriginalNameData[ImportMaterialOriginalNameDataIndex]; + ExistingMeshDataPtr->LastImportMaterialOriginalNameData.Add(MaterialName); + } + for (int32 LodIndex = 0; LodIndex < ImportData->ImportMeshLodData.Num(); ++LodIndex) + { + ExistingMeshDataPtr->LastImportMeshLodSectionMaterialData.AddZeroed(); + const FImportMeshLodSectionsData &ImportMeshLodSectionsData = ImportData->ImportMeshLodData[LodIndex]; + for (int32 SectionIndex = 0; SectionIndex < ImportMeshLodSectionsData.SectionOriginalMaterialName.Num(); ++SectionIndex) + { + FName MaterialName = ImportMeshLodSectionsData.SectionOriginalMaterialName[SectionIndex]; + ExistingMeshDataPtr->LastImportMeshLodSectionMaterialData[LodIndex].Add(MaterialName); + } + } + } + //Store the user asset data + const TArray* UserData = ExistingSkelMesh->GetAssetUserDataArray(); + if (UserData) + { + for (int32 Idx = 0; Idx < UserData->Num(); Idx++) + { + if ((*UserData)[Idx] != nullptr) + { + UAssetUserData* DupObject = (UAssetUserData*)StaticDuplicateObject((*UserData)[Idx], GetTransientPackage()); + bool bAddDupToRoot = !(DupObject->IsRooted()); + if (bAddDupToRoot) + { + DupObject->AddToRoot(); + } + ExistingMeshDataPtr->ExistingAssetUserData.Add(DupObject, bAddDupToRoot); + } + } + } return ExistingMeshDataPtr; } @@ -1155,5 +1175,18 @@ void RestoreExistingSkelMeshData(ExistingSkelMeshData* MeshData, USkeletalMesh* SkeletalMeshImportedModel->OriginalReductionSourceMeshData[ReimportLODIndex]->EmptyBulkData(); } } + + // Copy user data to newly created mesh + for (auto Kvp : MeshData->ExistingAssetUserData) + { + UAssetUserData* UserDataObject = Kvp.Key; + if (Kvp.Value) + { + //if the duplicated temporary UObject was add to root, we must remove it from the root + UserDataObject->RemoveFromRoot(); + } + UserDataObject->Rename(nullptr, SkeletalMesh, REN_DontCreateRedirectors | REN_DoNotDirty); + SkeletalMesh->AddAssetUserData(UserDataObject); + } } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxAnimationExport.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxAnimationExport.cpp index 21fe03fec62f..e0c7bc66830b 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxAnimationExport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxAnimationExport.cpp @@ -45,19 +45,26 @@ void FFbxExporter::ExportAnimSequenceToFbx(const UAnimSequence* AnimSeq, } const float FrameRate = FMath::TruncToFloat(((AnimSeq->GetRawNumberOfFrames() - 1) / AnimSeq->SequenceLength) + 0.5f); + //Configure the scene time line + { + FbxGlobalSettings& SceneGlobalSettings = Scene->GetGlobalSettings(); + double CurrentSceneFrameRate = FbxTime::GetFrameRate(SceneGlobalSettings.GetTimeMode()); + if (!bSceneGlobalTimeLineSet || FrameRate > CurrentSceneFrameRate) + { + FbxTime::EMode ComputeTimeMode = FbxTime::ConvertFrameRateToTimeMode(FrameRate); + FbxTime::SetGlobalTimeMode(ComputeTimeMode, ComputeTimeMode == FbxTime::eCustom ? FrameRate : 0.0); + SceneGlobalSettings.SetTimeMode(ComputeTimeMode); + if (ComputeTimeMode == FbxTime::eCustom) + { + SceneGlobalSettings.SetCustomFrameRate(FrameRate); + } + bSceneGlobalTimeLineSet = true; + } + } // set time correctly FbxTime ExportedStartTime, ExportedStopTime; - if ( FMath::IsNearlyEqual(FrameRate, DEFAULT_SAMPLERATE, 1.f) ) - { - ExportedStartTime.SetGlobalTimeMode(FbxTime::eFrames30); - ExportedStopTime.SetGlobalTimeMode(FbxTime::eFrames30); - } - else - { - ExportedStartTime.SetGlobalTimeMode(FbxTime::eCustom, FrameRate); - ExportedStopTime.SetGlobalTimeMode(FbxTime::eCustom, FrameRate); - } + ExportedStartTime.SetSecondDouble(0.f); ExportedStopTime.SetSecondDouble(AnimSeq->SequenceLength); @@ -130,7 +137,7 @@ void FFbxExporter::ExportAnimSequenceToFbx(const UAnimSequence* AnimSeq, ExportTimeIncrement.SetSecondDouble( TimePerKey ); int32 BoneTreeIndex = Skeleton->GetSkeletonBoneIndexFromMeshBoneIndex(SkelMesh, BoneIndex); - int32 BoneTrackIndex = Skeleton->GetAnimationTrackIndex(BoneTreeIndex, AnimSeq, true); + int32 BoneTrackIndex = Skeleton->GetRawAnimationTrackIndex(BoneTreeIndex, AnimSeq); if(BoneTrackIndex == INDEX_NONE) { // If this sequence does not have a track for the current bone, then skip it diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxFactory.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxFactory.cpp index 21e72412b212..eaed305424f7 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxFactory.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxFactory.cpp @@ -1059,6 +1059,9 @@ void UFbxImportUI::ResetToDefault() StaticMeshImportData->ReloadConfig(); SkeletalMeshImportData->ReloadConfig(); TextureImportData->ReloadConfig(); + + //Make sure the UI do not display the Base Material + TextureImportData->bUseBaseMaterial = false; } namespace ImportCompareHelper diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainExport.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainExport.cpp index b0eab66ed212..4864fc6bb010 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainExport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainExport.cpp @@ -255,6 +255,9 @@ void FFbxExporter::CreateDocument() // Maya use cm by default Scene->GetGlobalSettings().SetSystemUnit(FbxSystemUnit::cm); //FbxScene->GetGlobalSettings().SetOriginalSystemUnit( KFbxSystemUnit::m ); + + bSceneGlobalTimeLineSet = false; + Scene->GetGlobalSettings().SetTimeMode(FbxTime::eDefaultMode); // setup anim stack AnimStack = FbxAnimStack::Create(Scene, "Unreal Take"); @@ -381,6 +384,7 @@ void FFbxExporter::CloseDocument() Scene->Destroy(); Scene = NULL; } + bSceneGlobalTimeLineSet = false; } void FFbxExporter::CreateAnimatableUserProperty(FbxNode* Node, float Value, const char* Name, const char* Label) diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainImport.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainImport.cpp index 255cd925aa50..dc5769b7473c 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainImport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainImport.cpp @@ -400,6 +400,7 @@ void ApplyImportUIToImportOptions(UFbxImportUI* ImportUI, FBXImportOptions& InOu InOutImportOptions.bConvertSceneUnit = StaticMeshData->bConvertSceneUnit; InOutImportOptions.VertexColorImportOption = StaticMeshData->VertexColorImportOption; InOutImportOptions.VertexOverrideColor = StaticMeshData->VertexOverrideColor; + InOutImportOptions.bReorderMaterialToFbxOrder = StaticMeshData->bReorderMaterialToFbxOrder; } else if ( ImportUI->MeshTypeToImport == FBXIT_SkeletalMesh ) { @@ -419,6 +420,7 @@ void ApplyImportUIToImportOptions(UFbxImportUI* ImportUI, FBXImportOptions& InOu InOutImportOptions.bConvertSceneUnit = SkeletalMeshData->bConvertSceneUnit; InOutImportOptions.VertexColorImportOption = SkeletalMeshData->VertexColorImportOption; InOutImportOptions.VertexOverrideColor = SkeletalMeshData->VertexOverrideColor; + InOutImportOptions.bReorderMaterialToFbxOrder = SkeletalMeshData->bReorderMaterialToFbxOrder; if(ImportUI->bImportAnimations) { @@ -477,6 +479,7 @@ void ApplyImportUIToImportOptions(UFbxImportUI* ImportUI, FBXImportOptions& InOu InOutImportOptions.bRemoveRedundantKeys = ImportUI->AnimSequenceImportData->bRemoveRedundantKeys; InOutImportOptions.bDoNotImportCurveWithZero = ImportUI->AnimSequenceImportData->bDoNotImportCurveWithZero; InOutImportOptions.bImportCustomAttribute = ImportUI->AnimSequenceImportData->bImportCustomAttribute; + InOutImportOptions.bDeleteExistingCustomAttributeCurves = ImportUI->AnimSequenceImportData->bDeleteExistingCustomAttributeCurves; InOutImportOptions.bImportBoneTracks = ImportUI->AnimSequenceImportData->bImportBoneTracks; InOutImportOptions.bSetMaterialDriveParameterOnCustomAttribute = ImportUI->AnimSequenceImportData->bSetMaterialDriveParameterOnCustomAttribute; InOutImportOptions.MaterialCurveSuffixes = ImportUI->AnimSequenceImportData->MaterialCurveSuffixes; @@ -681,7 +684,7 @@ bool FFbxImporter::GetSceneInfo(FString Filename, FbxSceneInfo& SceneInfo, bool } GWarn->UpdateProgress( 90, 100 ); case IMPORTED: - + case FIXEDANDCONVERTED: default: break; } @@ -1423,6 +1426,7 @@ bool FFbxImporter::ImportFromFile(const FString& Filename, const FString& Type, * @EventParam ImportUniformScale float Returns the uniform scale apply on the import data * @EventParam MaterialBasePath string Returns the path pointing on the base material use to import material instance * @EventParam MaterialSearchLocation string Returns the scope of the search for existing materials (if material not found it can create one depending on bImportMaterials value) + * @EventParam ReorderMaterialToFbxOrder boolean returns weather the importer should reorder the materials in the same order has the fbx file * @EventParam AutoGenerateCollision boolean Returns whether the importer should create collision primitive * @EventParam CombineToSingle boolean Returns whether the importer should combine all mesh part together or import many meshes * @EventParam BakePivotInVertex boolean Returns whether the importer should bake the fbx mesh pivot into the vertex position @@ -1475,11 +1479,11 @@ bool FFbxImporter::ImportFromFile(const FString& Filename, const FString& Type, if( FEngineAnalytics::IsAvailable() ) { const static UEnum* FBXImportTypeEnum = StaticEnum(); - const static UEnum* FBXAnimationLengthImportTypeEnum = FindObject(ANY_PACKAGE, TEXT("EFBXAnimationLengthImportType")); - const static UEnum* MaterialSearchLocationEnum = FindObject(ANY_PACKAGE, TEXT("EMaterialSearchLocation")); - const static UEnum* FBXNormalGenerationMethodEnum = FindObject(ANY_PACKAGE, TEXT("EFBXNormalGenerationMethod")); - const static UEnum* FBXNormalImportMethodEnum = FindObject(ANY_PACKAGE, TEXT("EFBXNormalImportMethod")); - const static UEnum* VertexColorImportOptionEnum = FindObject(ANY_PACKAGE, TEXT("EVertexColorImportOption")); + const static UEnum* FBXAnimationLengthImportTypeEnum = StaticEnum(); + const static UEnum* MaterialSearchLocationEnum = StaticEnum(); + const static UEnum* FBXNormalGenerationMethodEnum = StaticEnum(); + const static UEnum* FBXNormalImportMethodEnum = StaticEnum(); + const static UEnum* VertexColorImportOptionEnum = StaticEnum(); TArray Attribs; @@ -1509,6 +1513,7 @@ bool FFbxImporter::ImportFromFile(const FString& Filename, const FString& Type, Attribs.Add(FAnalyticsEventAttribute(TEXT("GenOpt ImportUniformScale"), ImportOptions->ImportUniformScale)); Attribs.Add(FAnalyticsEventAttribute(TEXT("GenOpt MaterialBasePath"), ImportOptions->MaterialBasePath)); Attribs.Add(FAnalyticsEventAttribute(TEXT("GenOpt MaterialSearchLocation"), MaterialSearchLocationEnum->GetNameStringByValue((uint64)(ImportOptions->MaterialSearchLocation)))); + Attribs.Add(FAnalyticsEventAttribute(TEXT("GenOpt ReorderMaterialToFbxOrder"), ImportOptions->bReorderMaterialToFbxOrder)); //We cant capture a this member, so just assign the pointer here FBXImportOptions* CaptureImportOptions = ImportOptions; @@ -1563,6 +1568,7 @@ bool FFbxImporter::ImportFromFile(const FString& Filename, const FString& Type, Attribs.Add(FAnalyticsEventAttribute(TEXT("AnimOpt DoNotImportCurveWithZero"), CaptureImportOptions->bDoNotImportCurveWithZero)); Attribs.Add(FAnalyticsEventAttribute(TEXT("AnimOpt ImportBoneTracks"), CaptureImportOptions->bImportBoneTracks)); Attribs.Add(FAnalyticsEventAttribute(TEXT("AnimOpt ImportCustomAttribute"), CaptureImportOptions->bImportCustomAttribute)); + Attribs.Add(FAnalyticsEventAttribute(TEXT("AnimOpt DeleteExistingCustomAttributeCurves"), CaptureImportOptions->bDeleteExistingCustomAttributeCurves)); Attribs.Add(FAnalyticsEventAttribute(TEXT("AnimOpt PreserveLocalTransform"), CaptureImportOptions->bPreserveLocalTransform)); Attribs.Add(FAnalyticsEventAttribute(TEXT("AnimOpt RemoveRedundantKeys"), CaptureImportOptions->bRemoveRedundantKeys)); Attribs.Add(FAnalyticsEventAttribute(TEXT("AnimOpt Resample"), CaptureImportOptions->bResample)); @@ -1603,8 +1609,10 @@ bool FFbxImporter::ImportFromFile(const FString& Filename, const FString& Type, ConvertLodPrefixToLodGroup(); MeshNamesCache.Empty(); + CurPhase = FIXEDANDCONVERTED; + break; } - + case FIXEDANDCONVERTED: default: break; } @@ -2336,59 +2344,7 @@ FbxNode* FFbxImporter::GetRootSkeleton(FbxNode* Link) return RootBone; } - -void FFbxImporter::DumpFBXNode(FbxNode* Node) -{ - FbxMesh* Mesh = Node->GetMesh(); - const FString NodeName(Node->GetName()); - - if(Mesh) - { - UE_LOG(LogFbx, Log, TEXT("=================================================")); - UE_LOG(LogFbx, Log, TEXT("Dumping Node START [%s] "), *NodeName); - int DeformerCount = Mesh->GetDeformerCount(); - UE_LOG(LogFbx, Log,TEXT("\tTotal Deformer Count %d."), *NodeName, DeformerCount); - for(int i=0; iGetDeformer(i); - const FString DeformerName(Deformer->GetName()); - const FString DeformerTypeName(Deformer->GetTypeName()); - UE_LOG(LogFbx, Log,TEXT("\t\t[Node %d] %s (Type %s)."), i+1, *DeformerName, *DeformerTypeName); - UE_LOG(LogFbx, Log,TEXT("=================================================")); - } - - FbxNodeAttribute* NodeAttribute = Node->GetNodeAttribute(); - if(NodeAttribute) - { - FString NodeAttributeName(NodeAttribute->GetName()); - FbxNodeAttribute::EType Type = NodeAttribute->GetAttributeType(); - UE_LOG(LogFbx, Log,TEXT("\tAttribute (%s) Type (%d)."), *NodeAttributeName, (int32)Type); - - for (int i=0; iGetNodeCount(); ++i) - { - FbxNode * Child = NodeAttribute->GetNode(i); - - if (Child) - { - const FString ChildName(Child->GetName()); - const FString ChildTypeName(Child->GetTypeName()); - UE_LOG(LogFbx, Log,TEXT("\t\t[Node Attribute Child %d] %s (Type %s)."), i+1, *ChildName, *ChildTypeName); - } - } - - } - - UE_LOG(LogFbx, Log,TEXT("Dumping Node END [%s]"), *NodeName); - } - - for(int ChildIdx=0; ChildIdx < Node->GetChildCount(); ChildIdx++) - { - FbxNode* ChildNode = Node->GetChild(ChildIdx); - DumpFBXNode(ChildNode); - } - -} - + void FFbxImporter::ApplyTransformSettingsToFbxNode(FbxNode* Node, UFbxAssetImportData* AssetData) { check(Node); @@ -2489,8 +2445,6 @@ void FFbxImporter::RecursiveFindFbxSkelMesh(FbxNode* Node, TArray< TArrayGetMesh() && Node->GetMesh()->GetDeformerCount(FbxDeformer::eSkin) > 0 ) { SkelMeshNode = Node; diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMeshImportData.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMeshImportData.cpp index 787c3ec87a3f..8589b0cf7743 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMeshImportData.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMeshImportData.cpp @@ -9,6 +9,7 @@ UFbxMeshImportData::UFbxMeshImportData(const FObjectInitializer& ObjectInitializ NormalImportMethod = FBXNIM_ComputeNormals; NormalGenerationMethod = EFBXNormalGenerationMethod::MikkTSpace; bBakePivotInVertex = false; + bReorderMaterialToFbxOrder = true; } bool UFbxMeshImportData::CanEditChange(const UProperty* InProperty) const diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSceneImportOptionsSkeletalMesh.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSceneImportOptionsSkeletalMesh.cpp index 63cee238a4df..ade9647bf588 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSceneImportOptionsSkeletalMesh.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSceneImportOptionsSkeletalMesh.cpp @@ -23,6 +23,7 @@ UFbxSceneImportOptionsSkeletalMesh::UFbxSceneImportOptionsSkeletalMesh(const FOb , bUseDefaultSampleRate(false) , CustomSampleRate(0) , bImportCustomAttribute(true) + , bDeleteExistingCustomAttributeCurves(false) , bPreserveLocalTransform(false) , bDeleteExistingMorphTargetCurves(false) { @@ -53,6 +54,7 @@ void UFbxSceneImportOptionsSkeletalMesh::FillSkeletalMeshInmportData(UFbxSkeleta AnimSequenceImportData->AnimationLength = AnimationLength; AnimSequenceImportData->bDeleteExistingMorphTargetCurves = bDeleteExistingMorphTargetCurves; AnimSequenceImportData->bImportCustomAttribute = bImportCustomAttribute; + AnimSequenceImportData->bDeleteExistingCustomAttributeCurves = bDeleteExistingCustomAttributeCurves; AnimSequenceImportData->bPreserveLocalTransform = bPreserveLocalTransform; AnimSequenceImportData->bUseDefaultSampleRate = bUseDefaultSampleRate; AnimSequenceImportData->CustomSampleRate = CustomSampleRate; diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSkeletalMeshImport.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSkeletalMeshImport.cpp index 36ea94b2c834..664c6200e210 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSkeletalMeshImport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSkeletalMeshImport.cpp @@ -1222,6 +1222,41 @@ bool UnFbx::FFbxImporter::ImportBone(TArray& NodeArray, FSkeletalMeshI BoneName = UTF8_TO_TCHAR( MakeName( LinkName ) ); Bone.Name = BoneName; + //Check for nan and for zero scale + if (AllowImportBoneErrorAndWarning) + { + bool bFoundNan = false; + bool bFoundZeroScale = false; + for (int32 i = 0; i < 4; ++i) + { + if (i < 3) + { + if (FMath::IsNaN(LocalLinkT[i]) || FMath::IsNaN(LocalLinkS[i])) + { + bFoundNan = true; + } + if (FMath::IsNearlyZero(LocalLinkS[i])) + { + bFoundZeroScale = true; + } + } + if (FMath::IsNaN(LocalLinkQ[i])) + { + bFoundNan = true; + } + } + + if (bFoundNan) + { + AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("BoneTransformNAN", "Found NAN value in bone transform. Bone name: [{0}]"), FText::FromString(BoneName))), FFbxErrors::SkeletalMesh_InvalidBindPose); + } + + if (bFoundZeroScale) + { + AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("BoneTransformZeroScale", "Found zero scale value in bone transform. Bone name: [{0}]"), FText::FromString(BoneName))), FFbxErrors::SkeletalMesh_InvalidBindPose); + } + } + SkeletalMeshImportData::FJointPos& JointMatrix = Bone.BonePos; FbxSkeleton* Skeleton = Link->GetSkeleton(); if (Skeleton) @@ -1324,7 +1359,6 @@ bool UnFbx::FFbxImporter::FillSkeletalMeshImportData(TArray& NodeArray { if (!(bIsReimport && ImportOptions->bImportAsSkeletalGeometry)) //Do not import bone if we import only the geometry and we are reimporting { - AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FbxSkeletaLMeshimport_MultipleRootFound", "Multiple roots found")), FFbxErrors::SkeletalMesh_MultipleRoots); return false; } } @@ -3140,7 +3174,11 @@ bool UnFbx::FFbxImporter::FillSkelMeshImporterFromFbx( FSkeletalMeshImportData& int32 LayerSmoothingCount = Mesh->GetLayerCount(FbxLayerElement::eSmoothing); for(int32 i = 0; i < LayerSmoothingCount; i++) { - GeometryConverter->ComputePolygonSmoothingFromEdgeSmoothing (Mesh, i); + FbxLayerElementSmoothing const* SmoothingInfo = Mesh->GetLayer(i)->GetSmoothing(); + if (SmoothingInfo && SmoothingInfo->GetMappingMode() != FbxLayerElement::eByPolygon) + { + GeometryConverter->ComputePolygonSmoothingFromEdgeSmoothing(Mesh, i); + } } // @@ -3238,6 +3276,12 @@ bool UnFbx::FFbxImporter::FillSkelMeshImporterFromFbx( FSkeletalMeshImportData& AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("FbxSkeletaLMeshimport_ConvertSmoothingGroupFailed", "Unable to fully convert the smoothing groups for mesh '{0}'"), FText::FromString(Mesh->GetName()))), FFbxErrors::Generic_Mesh_ConvertSmoothingGroupFailed); bSmoothingAvailable = false; } + else + { + //After using the geometry converter we always have to get the Layer and the smoothing info + BaseLayer = Mesh->GetLayer(0); + SmoothingInfo = BaseLayer->GetSmoothing(); + } } if( SmoothingInfo->GetMappingMode() == FbxLayerElement::eByPolygon ) @@ -4358,6 +4402,9 @@ void UnFbx::FFbxImporter::ImportMorphTargetsInternal( TArray& SkelMesh GWarn->BeginSlowTask( NSLOCTEXT("FbxImporter", "BeginGeneratingMorphModelsTask", "Generating Morph Models"), true); + //Use this TMap to store the name of the blendshape map by the fbx uniqueID. This allow to assign unique name to each blend shape. + TMap UniqueIDToName; + // For each morph in FBX geometries, we create one morph target for the Unreal skeletal mesh for (int32 NodeIndex = 0; NodeIndex < SkelMeshNodeArray.Num(); NodeIndex++) { @@ -4417,6 +4464,36 @@ void UnFbx::FFbxImporter::ImportMorphTargetsInternal( TArray& SkelMesh } } + uint64 UniqueID = Shape->GetUniqueID(); + if (UniqueIDToName.Contains(UniqueID)) + { + ShapeName = UniqueIDToName.FindChecked(UniqueID); + } + else + { + //In case the shape do not have a unique name + //make it unique + int32 NameIndex = 0; + FString CurrentShapeName = ShapeName; + bool bNameNotUnique = ShapeNameToShapeArray.Contains(CurrentShapeName); + while(bNameNotUnique) + { + //Append ShapeX to the shape name (same has Maya) + if (NameIndex == 0) + { + CurrentShapeName = ShapeName + TEXT("Shape"); + NameIndex++; + } + else + { + CurrentShapeName = ShapeName + TEXT("Shape") + FString::FromInt(NameIndex++); + } + bNameNotUnique = ShapeNameToShapeArray.Contains(CurrentShapeName); + } + ShapeName = CurrentShapeName; + UniqueIDToName.Add(UniqueID, ShapeName); + } + TArray & ShapeArray = ShapeNameToShapeArray.FindOrAdd(ShapeName); if (ShapeArray.Num() == 0) { diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxStaticMeshImport.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxStaticMeshImport.cpp index f7e72f3c40cc..56e08e1f4ff8 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxStaticMeshImport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxStaticMeshImport.cpp @@ -34,6 +34,7 @@ #include "PhysicsEngine/BodySetup.h" #include "MeshDescription.h" #include "MeshAttributes.h" +#include "MeshDescriptionOperations.h" #include "IMeshBuilderModule.h" #include "Settings/EditorExperimentalSettings.h" @@ -68,6 +69,14 @@ static FbxString GetNodeNameWithoutNamespace( FbxNode* Node ) } } +void CreateTokenizedErrorForDegeneratedPart(UnFbx::FFbxImporter* FbxImporter, const FString MeshName, const FString NodeName) +{ + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("MeshName"), FText::FromString(MeshName)); + Arguments.Add(TEXT("PartName"), FText::FromString(NodeName)); + FText ErrorMsg = LOCTEXT("MeshHasNoRenderableTriangles", "Mesh name: [{MeshName}] part name: [{PartName}] could not be created because all of its polygons are degenerate."); + FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(ErrorMsg, Arguments)), FFbxErrors::StaticMesh_AllTrianglesDegenerate); +} //------------------------------------------------------------------------- // @@ -270,7 +279,7 @@ bool UnFbx::FFbxImporter::BuildStaticMeshFromGeometry(FbxNode* Node, UStaticMesh { check(StaticMesh->SourceModels.IsValidIndex(LODIndex)); FbxMesh* Mesh = Node->GetMesh(); - FStaticMeshSourceModel& SrcModel = StaticMesh->SourceModels[LODIndex]; + FString FbxNodeName = UTF8_TO_TCHAR(Node->GetName()); FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(LODIndex); //The mesh description should have been created before calling BuildStaticMeshFromGeometry @@ -390,7 +399,7 @@ bool UnFbx::FFbxImporter::BuildStaticMeshFromGeometry(FbxNode* Node, UStaticMesh if (!Mesh->IsTriangleMesh()) { if(!GIsAutomationTesting) - UE_LOG(LogFbx, Display, TEXT("Triangulating static mesh %s"), UTF8_TO_TCHAR(Node->GetName())); + UE_LOG(LogFbx, Display, TEXT("Triangulating static mesh %s"), *FbxNodeName); const bool bReplace = true; FbxNodeAttribute* ConvertedNode = GeometryConverter->Triangulate(Mesh, bReplace); @@ -907,10 +916,7 @@ bool UnFbx::FFbxImporter::BuildStaticMeshFromGeometry(FbxNode* Node, UStaticMesh if (!bHasNonDegeneratePolygons) { - FFormatNamedArguments Arguments; - Arguments.Add( TEXT("MeshName"), FText::FromString(StaticMesh->GetName())); - FText ErrorMsg = LOCTEXT("MeshHasNoRenderableTriangles", "{MeshName} could not be created because all of its polygons are degenerate."); - AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(ErrorMsg, Arguments)), FFbxErrors::StaticMesh_AllTrianglesDegenerate); + CreateTokenizedErrorForDegeneratedPart(this, StaticMesh->GetName(), FbxNodeName); } bool bIsValidMesh = bHasNonDegeneratePolygons; @@ -1530,7 +1536,9 @@ UStaticMesh* UnFbx::FFbxImporter::ImportStaticMeshAsSingle(UObject* InParent, TA StaticMesh->LightMapResolution = 64; StaticMesh->LightMapCoordinateIndex = 1; + float SqrBoundingBoxThreshold = THRESH_POINTS_ARE_NEAR * THRESH_POINTS_ARE_NEAR; + bool bAllDegenerated = true; TArray MeshMaterials; for (MeshIndex = 0; MeshIndex < MeshNodeArray.Num(); MeshIndex++ ) { @@ -1538,14 +1546,33 @@ UStaticMesh* UnFbx::FFbxImporter::ImportStaticMeshAsSingle(UObject* InParent, TA if (Node->GetMesh()) { - if (!BuildStaticMeshFromGeometry(Node, StaticMesh, MeshMaterials, LODIndex, - VertexColorImportOption, ExistingVertexColorData, ImportOptions->VertexOverrideColor)) + Node->GetMesh()->ComputeBBox(); + FbxDouble3 BoxExtend; + BoxExtend[0] = Node->GetMesh()->BBoxMax.Get()[0] - Node->GetMesh()->BBoxMin.Get()[0]; + BoxExtend[1] = Node->GetMesh()->BBoxMax.Get()[1] - Node->GetMesh()->BBoxMin.Get()[1]; + BoxExtend[2] = Node->GetMesh()->BBoxMax.Get()[2] - Node->GetMesh()->BBoxMin.Get()[2]; + double SqrSize = (BoxExtend[0] * BoxExtend[0]) + (BoxExtend[1] * BoxExtend[1]) + (BoxExtend[2] * BoxExtend[2]); + //If the bounding box of the mesh part is smaller then the position threshold, the part is consider degenerated and will be skip. + if (SqrSize > SqrBoundingBoxThreshold) { - bBuildStatus = false; - break; + bAllDegenerated = false; + if (!BuildStaticMeshFromGeometry(Node, StaticMesh, MeshMaterials, LODIndex, + VertexColorImportOption, ExistingVertexColorData, ImportOptions->VertexOverrideColor)) + { + bBuildStatus = false; + break; + } + } + else + { + CreateTokenizedErrorForDegeneratedPart(this, StaticMesh->GetName(), UTF8_TO_TCHAR(Node->GetName())); } } } + if (bAllDegenerated) + { + bBuildStatus = false; + } if (bBuildStatus) { @@ -1754,7 +1781,7 @@ UStaticMesh* UnFbx::FFbxImporter::ImportStaticMeshAsSingle(UObject* InParent, TA return StaticMesh; } -void ReorderMaterialAfterImport(UStaticMesh* StaticMesh, TArray& MeshNodeArray) +void ReorderMaterialAfterImport(UStaticMesh* StaticMesh, TArray& MeshNodeArray, bool bAllowFbxReorder) { if (StaticMesh == nullptr) { @@ -1843,70 +1870,73 @@ void ReorderMaterialAfterImport(UStaticMesh* StaticMesh, TArray& MeshN } //Reorder the StaticMaterials array to reflect the order in the fbx file - //So we make sure the order reflect the material ID in the DCCs - FMeshSectionInfoMap OldSectionInfoMap = StaticMesh->SectionInfoMap; - TArray FbxRemapMaterials; - TArray NewStaticMaterials; - for (int32 FbxMaterialIndex = 0; FbxMaterialIndex < MeshMaterials.Num(); ++FbxMaterialIndex) + if (bAllowFbxReorder) { - const FString &FbxMaterial = MeshMaterials[FbxMaterialIndex]; - int32 FoundMaterialIndex = INDEX_NONE; + //So we make sure the order reflect the material ID in the DCCs + FMeshSectionInfoMap OldSectionInfoMap = StaticMesh->SectionInfoMap; + TArray FbxRemapMaterials; + TArray NewStaticMaterials; + for (int32 FbxMaterialIndex = 0; FbxMaterialIndex < MeshMaterials.Num(); ++FbxMaterialIndex) + { + const FString &FbxMaterial = MeshMaterials[FbxMaterialIndex]; + int32 FoundMaterialIndex = INDEX_NONE; + for (int32 BuildMaterialIndex = 0; BuildMaterialIndex < StaticMesh->StaticMaterials.Num(); ++BuildMaterialIndex) + { + FStaticMaterial &BuildMaterial = StaticMesh->StaticMaterials[BuildMaterialIndex]; + if (FbxMaterial.Compare(BuildMaterial.ImportedMaterialSlotName.ToString()) == 0) + { + FoundMaterialIndex = BuildMaterialIndex; + break; + } + } + + if (FoundMaterialIndex != INDEX_NONE) + { + FbxRemapMaterials.Add(FoundMaterialIndex); + NewStaticMaterials.Add(StaticMesh->StaticMaterials[FoundMaterialIndex]); + } + } + //Add the materials not used by the LOD 0 at the end of the array. The order here is irrelevant since it can be used by many LOD other then LOD 0 and in different order for (int32 BuildMaterialIndex = 0; BuildMaterialIndex < StaticMesh->StaticMaterials.Num(); ++BuildMaterialIndex) { - FStaticMaterial &BuildMaterial = StaticMesh->StaticMaterials[BuildMaterialIndex]; - if (FbxMaterial.Compare(BuildMaterial.ImportedMaterialSlotName.ToString()) == 0) + const FStaticMaterial &StaticMaterial = StaticMesh->StaticMaterials[BuildMaterialIndex]; + bool bFoundMaterial = false; + for (const FStaticMaterial &BuildMaterial : NewStaticMaterials) { - FoundMaterialIndex = BuildMaterialIndex; - break; + if (StaticMaterial == BuildMaterial) + { + bFoundMaterial = true; + break; + } + } + if (!bFoundMaterial) + { + FbxRemapMaterials.Add(BuildMaterialIndex); + NewStaticMaterials.Add(StaticMaterial); } } - if (FoundMaterialIndex != INDEX_NONE) - { - FbxRemapMaterials.Add(FoundMaterialIndex); - NewStaticMaterials.Add(StaticMesh->StaticMaterials[FoundMaterialIndex]); - } - } - //Add the materials not used by the LOD 0 at the end of the array. The order here is irrelevant since it can be used by many LOD other then LOD 0 and in different order - for (int32 BuildMaterialIndex = 0; BuildMaterialIndex < StaticMesh->StaticMaterials.Num(); ++BuildMaterialIndex) - { - const FStaticMaterial &StaticMaterial = StaticMesh->StaticMaterials[BuildMaterialIndex]; - bool bFoundMaterial = false; + StaticMesh->StaticMaterials.Empty(); for (const FStaticMaterial &BuildMaterial : NewStaticMaterials) { - if (StaticMaterial == BuildMaterial) - { - bFoundMaterial = true; - break; - } + StaticMesh->StaticMaterials.Add(BuildMaterial); } - if (!bFoundMaterial) - { - FbxRemapMaterials.Add(BuildMaterialIndex); - NewStaticMaterials.Add(StaticMaterial); - } - } - StaticMesh->StaticMaterials.Empty(); - for (const FStaticMaterial &BuildMaterial : NewStaticMaterials) - { - StaticMesh->StaticMaterials.Add(BuildMaterial); - } - - //Remap the material instance of the staticmaterial array and remap the material index of all sections - for (int32 LODResoureceIndex = 0; LODResoureceIndex < StaticMesh->RenderData->LODResources.Num(); ++LODResoureceIndex) - { - FStaticMeshLODResources& LOD = StaticMesh->RenderData->LODResources[LODResoureceIndex]; - int32 NumSections = LOD.Sections.Num(); - for (int32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) + //Remap the material instance of the staticmaterial array and remap the material index of all sections + for (int32 LODResoureceIndex = 0; LODResoureceIndex < StaticMesh->RenderData->LODResources.Num(); ++LODResoureceIndex) { - FMeshSectionInfo Info = OldSectionInfoMap.Get(LODResoureceIndex, SectionIndex); - int32 RemapIndex = FbxRemapMaterials.Find(Info.MaterialIndex); - if (StaticMesh->StaticMaterials.IsValidIndex(RemapIndex)) + FStaticMeshLODResources& LOD = StaticMesh->RenderData->LODResources[LODResoureceIndex]; + int32 NumSections = LOD.Sections.Num(); + for (int32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) { - Info.MaterialIndex = RemapIndex; - StaticMesh->SectionInfoMap.Set(LODResoureceIndex, SectionIndex, Info); - StaticMesh->OriginalSectionInfoMap.Set(LODResoureceIndex, SectionIndex, Info); + FMeshSectionInfo Info = OldSectionInfoMap.Get(LODResoureceIndex, SectionIndex); + int32 RemapIndex = FbxRemapMaterials.Find(Info.MaterialIndex); + if (StaticMesh->StaticMaterials.IsValidIndex(RemapIndex)) + { + Info.MaterialIndex = RemapIndex; + StaticMesh->SectionInfoMap.Set(LODResoureceIndex, SectionIndex, Info); + StaticMesh->OriginalSectionInfoMap.Set(LODResoureceIndex, SectionIndex, Info); + } } } } @@ -2063,7 +2093,7 @@ void UnFbx::FFbxImporter::PostImportStaticMesh(UStaticMesh* StaticMesh, TArrayStaticMaterials.Num() > 1) { - ReorderMaterialAfterImport(StaticMesh, MeshNodeArray); + ReorderMaterialAfterImport(StaticMesh, MeshNodeArray, ImportOptions->bReorderMaterialToFbxOrder); } } @@ -2201,7 +2231,7 @@ void UnFbx::FFbxImporter::ImportStaticMeshLocalSockets(UStaticMesh* StaticMesh, check(Socket); Socket->SocketName = SocketNode.SocketName; - StaticMesh->Sockets.Add(Socket); + StaticMesh->AddSocket(Socket); } if (Socket) @@ -2267,7 +2297,7 @@ void UnFbx::FFbxImporter::ImportStaticMeshGlobalSockets( UStaticMesh* StaticMesh check(Socket); Socket->SocketName = SocketNode.SocketName; - StaticMesh->Sockets.Add(Socket); + StaticMesh->AddSocket(Socket); //Remove the axis conversion for the socket since its attach to a mesh containing this conversion. const FbxAMatrix& SocketMatrix = Scene->GetAnimationEvaluator()->GetNodeGlobalTransform(SocketNode.Node) * FFbxDataConverter::GetAxisConversionMatrixInv(); FTransform SocketTransform; diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/ReimportFbxSceneFactory.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/ReimportFbxSceneFactory.cpp index 2e692e754a75..7bb122e3a515 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/ReimportFbxSceneFactory.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/ReimportFbxSceneFactory.cpp @@ -1226,6 +1226,25 @@ EReimportResult::Type UReimportFbxSceneFactory::ReimportSkeletalMesh(void* VoidF return EReimportResult::Failed; } + const TArray* UserData = Mesh->GetAssetUserDataArray(); + TMap UserDataCopy; + if (UserData) + { + for (int32 Idx = 0; Idx < UserData->Num(); Idx++) + { + if ((*UserData)[Idx] != nullptr) + { + UAssetUserData* DupObject = (UAssetUserData*)StaticDuplicateObject((*UserData)[Idx], GetTransientPackage()); + bool bAddDupToRoot = !(DupObject->IsRooted()); + if (bAddDupToRoot) + { + DupObject->AddToRoot(); + } + UserDataCopy.Add(DupObject, bAddDupToRoot); + } + } + } + ApplyMeshInfoFbxOptions(MeshInfo); //TODO support bBakePivotInVertex bool Old_bBakePivotInVertex = GlobalImportSettings->bBakePivotInVertex; @@ -1241,6 +1260,19 @@ EReimportResult::Type UReimportFbxSceneFactory::ReimportSkeletalMesh(void* VoidF { Mesh->AssetImportData->Update(FbxImportFileName); + // Copy user data to newly created mesh + for (auto Kvp : UserDataCopy) + { + UAssetUserData* UserDataObject = Kvp.Key; + if (Kvp.Value) + { + //if the duplicated temporary UObject was add to root, we must remove it from the root + UserDataObject->RemoveFromRoot(); + } + UserDataObject->Rename(nullptr, Mesh, REN_DontCreateRedirectors | REN_DoNotDirty); + Mesh->AddAssetUserData(UserDataObject); + } + // Try to find the outer package so we can dirty it up if (Mesh->GetOuter()) { @@ -1432,14 +1464,20 @@ EReimportResult::Type UReimportFbxSceneFactory::ReimportStaticMesh(void* VoidFbx FbxImporter->ApplyTransformSettingsToFbxNode(FbxImporter->Scene->GetRootNode(), StaticMeshImportData); const TArray* UserData = Mesh->GetAssetUserDataArray(); - TArray UserDataCopy; + TMap UserDataCopy; if (UserData) { for (int32 Idx = 0; Idx < UserData->Num(); Idx++) { if ((*UserData)[Idx] != nullptr) { - UserDataCopy.Add((UAssetUserData*)StaticDuplicateObject((*UserData)[Idx], GetTransientPackage())); + UAssetUserData* DupObject = (UAssetUserData*)StaticDuplicateObject((*UserData)[Idx], GetTransientPackage()); + bool bAddDupToRoot = !(DupObject->IsRooted()); + if (bAddDupToRoot) + { + DupObject->AddToRoot(); + } + UserDataCopy.Add(DupObject, bAddDupToRoot); } } } @@ -1466,10 +1504,16 @@ EReimportResult::Type UReimportFbxSceneFactory::ReimportStaticMesh(void* VoidFbx Mesh->AssetImportData = StaticMeshImportData; // Copy user data to newly created mesh - for (int32 Idx = 0; Idx < UserDataCopy.Num(); Idx++) + for (auto Kvp : UserDataCopy) { - UserDataCopy[Idx]->Rename(nullptr, Mesh, REN_DontCreateRedirectors | REN_DoNotDirty); - Mesh->AddAssetUserData(UserDataCopy[Idx]); + UAssetUserData* UserDataObject = Kvp.Key; + if (Kvp.Value) + { + //if the duplicated temporary UObject was add to root, we must remove it from the root + UserDataObject->RemoveFromRoot(); + } + UserDataObject->Rename(nullptr, Mesh, REN_DontCreateRedirectors | REN_DoNotDirty); + Mesh->AddAssetUserData(UserDataObject); } if (NavCollision) diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/SFbxSceneOptionWindow.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/SFbxSceneOptionWindow.cpp index d85275cd55ae..8c062c514e8c 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/SFbxSceneOptionWindow.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/SFbxSceneOptionWindow.cpp @@ -1505,6 +1505,7 @@ void SFbxSceneOptionWindow::CopySkeletalMeshOptionsToFbxOptions(UnFbx::FBXImport ImportSettings->AnimationLengthImportType = SkeletalMeshOptions->AnimationLength; ImportSettings->bDeleteExistingMorphTargetCurves = SkeletalMeshOptions->bDeleteExistingMorphTargetCurves; ImportSettings->bImportCustomAttribute = SkeletalMeshOptions->bImportCustomAttribute; + ImportSettings->bDeleteExistingCustomAttributeCurves = SkeletalMeshOptions->bDeleteExistingCustomAttributeCurves; ImportSettings->bPreserveLocalTransform = SkeletalMeshOptions->bPreserveLocalTransform; ImportSettings->bResample = !SkeletalMeshOptions->bUseDefaultSampleRate; ImportSettings->ResampleRate = SkeletalMeshOptions->CustomSampleRate; @@ -1528,6 +1529,7 @@ void SFbxSceneOptionWindow::CopyFbxOptionsToSkeletalMeshOptions(UnFbx::FBXImport SkeletalMeshOptions->AnimationLength = ImportSettings->AnimationLengthImportType; SkeletalMeshOptions->bDeleteExistingMorphTargetCurves = ImportSettings->bDeleteExistingMorphTargetCurves; SkeletalMeshOptions->bImportCustomAttribute = ImportSettings->bImportCustomAttribute; + SkeletalMeshOptions->bDeleteExistingCustomAttributeCurves = ImportSettings->bDeleteExistingCustomAttributeCurves; SkeletalMeshOptions->bPreserveLocalTransform = ImportSettings->bPreserveLocalTransform; SkeletalMeshOptions->bUseDefaultSampleRate = !ImportSettings->bResample; SkeletalMeshOptions->CustomSampleRate = ImportSettings->ResampleRate; diff --git a/Engine/Source/Editor/UnrealEd/Private/FbxExporter.h b/Engine/Source/Editor/UnrealEd/Private/FbxExporter.h index 4c84977abde9..7863c22f2571 100644 --- a/Engine/Source/Editor/UnrealEd/Private/FbxExporter.h +++ b/Engine/Source/Editor/UnrealEd/Private/FbxExporter.h @@ -483,6 +483,8 @@ private: public: /** Returns currently active FBX export options. Automation or UI dialog based options. */ UFbxExportOption* GetExportOptions(); + + bool bSceneGlobalTimeLineSet = false; }; diff --git a/Engine/Source/Editor/UnrealEd/Private/FbxMeshUtils.cpp b/Engine/Source/Editor/UnrealEd/Private/FbxMeshUtils.cpp index bb16f401bc86..a16527cee97c 100644 --- a/Engine/Source/Editor/UnrealEd/Private/FbxMeshUtils.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/FbxMeshUtils.cpp @@ -255,6 +255,23 @@ namespace FbxMeshUtils // Check the file extension for FBX. Anything that isn't .FBX is rejected const FString FileExtension = FPaths::GetExtension(Filename); const bool bIsFBX = FCString::Stricmp(*FileExtension, TEXT("FBX")) == 0; + bool bSceneIsCleanUp = false; + TArray< TArray* > MeshArray; + auto CleanUpScene = [&bSceneIsCleanUp, &MeshArray, &FFbxImporter]() + { + if (bSceneIsCleanUp) + { + return; + } + bSceneIsCleanUp = true; + // Cleanup + for (int32 i = 0; i < MeshArray.Num(); i++) + { + delete MeshArray[i]; + } + FFbxImporter->ReleaseScene(); + FFbxImporter = nullptr; + }; //Skip none fbx file if (!bIsFBX) @@ -298,6 +315,26 @@ namespace FbxMeshUtils ClothingAsset->UnbindFromSkeletalMesh(SelectedSkelMesh, LODLevel); } + //Lambda to call to re-apply the clothing + auto ReapplyClothing = [&SelectedSkelMesh, &ClothingAssetsInUse, &ClothingAssetSectionIndices, &ClothingAssetInternalLodIndices, &ImportedResource, &LODLevel]() + { + // Re-apply our clothing assets + int32 NumClothingAssetsToApply = ClothingAssetsInUse.Num(); + if (ImportedResource && ImportedResource->LODModels.IsValidIndex(LODLevel)) + { + FSkeletalMeshLODModel& LodModel = ImportedResource->LODModels[LODLevel]; + for (int32 AssetIndex = 0; AssetIndex < NumClothingAssetsToApply; ++AssetIndex) + { + // Only if the same section exists + if (LodModel.Sections.IsValidIndex(ClothingAssetSectionIndices[AssetIndex])) + { + UClothingAssetBase* AssetToApply = ClothingAssetsInUse[AssetIndex]; + AssetToApply->BindToSkeletalMesh(SelectedSkelMesh, LODLevel, ClothingAssetSectionIndices[AssetIndex], ClothingAssetInternalLodIndices[AssetIndex]); + } + } + } + }; + // don't import material and animation UnFbx::FBXImportOptions* ImportOptions = FFbxImporter->GetImportOptions(); @@ -346,6 +383,7 @@ namespace FbxMeshUtils if ( !FFbxImporter->ImportFromFile( *Filename, FPaths::GetExtension( Filename ), true ) ) { + ReapplyClothing(); // Log the error message and fail the import. FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FBXImport_ParseFailed", "FBX file parsing failed.")), FFbxErrors::Generic_FBXFileParseFailed); } @@ -353,7 +391,6 @@ namespace FbxMeshUtils { bool bUseLODs = true; int32 MaxLODLevel = 0; - TArray< TArray* > MeshArray; TArray LODStrings; TArray* MeshObject = NULL;; @@ -363,8 +400,9 @@ namespace FbxMeshUtils // Nothing found, error out if (MeshArray.Num() == 0) { + ReapplyClothing(); FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FBXImport_NoMesh", "No meshes were found in file.")), FFbxErrors::Generic_MeshNotFound); - FFbxImporter->ReleaseScene(); + CleanUpScene(); return false; } @@ -403,6 +441,7 @@ namespace FbxMeshUtils int32 SelectedLOD = LODLevel; if (SelectedLOD > SelectedSkelMesh->GetLODNum()) { + ReapplyClothing(); // Make sure they don't manage to select a bad LOD index FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("FBXImport_InvalidLODIdx", "Invalid mesh LOD index {0}, no prior LOD index exists"), FText::AsNumber(SelectedLOD))), FFbxErrors::Generic_Mesh_LOD_InvalidIndex); } @@ -458,7 +497,7 @@ namespace FbxMeshUtils } ExistingSkelMeshData* SkelMeshDataPtr = nullptr; - if (SelectedSkelMesh->GetLODNum() > LODLevel) + if (SelectedSkelMesh->GetLODNum() > SelectedLOD) { SelectedSkelMesh->PreEditChange(NULL); SkelMeshDataPtr = SaveExistingSkelMeshData(SelectedSkelMesh, true, SelectedLOD); @@ -476,61 +515,52 @@ namespace FbxMeshUtils ImportSkeletalMeshArgs.Name = NAME_None; ImportSkeletalMeshArgs.Flags = RF_Transient; ImportSkeletalMeshArgs.TemplateImportData = TempAssetImportData; - ImportSkeletalMeshArgs.LodIndex = LODLevel; + ImportSkeletalMeshArgs.LodIndex = SelectedLOD; ImportSkeletalMeshArgs.OrderedMaterialNames = OrderedMaterialNames.Num() > 0 ? &OrderedMaterialNames : nullptr; ImportSkeletalMeshArgs.ImportMaterialOriginalNameData = &ImportMaterialOriginalNameData; ImportSkeletalMeshArgs.ImportMeshSectionsData = &ImportMeshLodData[0]; ImportSkeletalMeshArgs.OutData = &OutData; TempSkelMesh = (USkeletalMesh*)FFbxImporter->ImportSkeletalMesh( ImportSkeletalMeshArgs ); - - // Add imported mesh to existing model - bool bMeshImportSuccess = false; - if( TempSkelMesh ) + // Add the new imported LOD to the existing model (check skeleton compatibility) + if( TempSkelMesh && FFbxImporter->ImportSkeletalMeshLOD(TempSkelMesh, SelectedSkelMesh, SelectedLOD, true, nullptr, TempAssetImportData)) { - bMeshImportSuccess = FFbxImporter->ImportSkeletalMeshLOD(TempSkelMesh, SelectedSkelMesh, SelectedLOD, true, nullptr, TempAssetImportData); - + TComponentReregisterContext ReregisterContext; + SelectedSkelMesh->ReleaseResources(); + SelectedSkelMesh->ReleaseResourcesFence.Wait(); + //Update the import data for this lod - UnFbx::FFbxImporter::UpdateSkeletalMeshImportData(SelectedSkelMesh, nullptr, LODLevel, &ImportMaterialOriginalNameData, &ImportMeshLodData); + UnFbx::FFbxImporter::UpdateSkeletalMeshImportData(SelectedSkelMesh, nullptr, SelectedLOD, &ImportMaterialOriginalNameData, &ImportMeshLodData); if (SkelMeshDataPtr != nullptr) { RestoreExistingSkelMeshData(SkelMeshDataPtr, SelectedSkelMesh, SelectedLOD, false, ImportOptions->bImportAsSkeletalSkinning); } - SelectedSkelMesh->PostEditChange(); - // Mark package containing skeletal mesh as dirty. - SelectedSkelMesh->MarkPackageDirty(); - // Now iterate over all skeletal mesh components re-initialising them. - for (TObjectIterator It; It; ++It) + if (ImportOptions->bImportMorph) { - USkeletalMeshComponent* SkelComp = *It; - if (SkelComp->SkeletalMesh == SelectedSkelMesh) - { - FComponentReregisterContext ReregisterContext(SkelComp); - } + FFbxImporter->ImportFbxMorphTarget(SkelMeshNodeArray, SelectedSkelMesh, SelectedSkelMesh->GetOutermost(), SelectedLOD, OutData); } - } - if(ImportOptions->bImportMorph) - { - FFbxImporter->ImportFbxMorphTarget(SkelMeshNodeArray, SelectedSkelMesh, SelectedSkelMesh->GetOutermost(), SelectedLOD, OutData); - //If we have import some morph target we have to rebuild the render resources since morph target are now using GPU - if (SelectedSkelMesh->MorphTargets.Num() > 0) - { - SelectedSkelMesh->ReleaseResources(); - //Rebuild the resources with a post edit change since we have added some morph targets - SelectedSkelMesh->PostEditChange(); - } - } - - if (bMeshImportSuccess) - { bSuccess = true; // Set LOD source filename SelectedSkelMesh->GetLODInfo(SelectedLOD)->SourceImportFilename = UAssetImportData::SanitizeImportFilename(Filename, nullptr); SelectedSkelMesh->GetLODInfo(SelectedLOD)->bImportWithBaseMesh = false; + ReapplyClothing(); + + //Must be the last step because it cleanup the fbx importer to import the alternate skinning FBX + if (bMustReimportAlternateSkinWeightProfile) + { + //We cannot use anymore the FFbxImporter after the cleanup + CleanUpScene(); + FLODUtilities::ReimportAlternateSkinWeight(SelectedSkelMesh, SelectedLOD, false); + } + + SelectedSkelMesh->PostEditChange(); + SelectedSkelMesh->InitResources(); + SelectedSkelMesh->MarkPackageDirty(); + // Notification of success FNotificationInfo NotificationInfo(FText::GetEmpty()); NotificationInfo.Text = FText::Format(NSLOCTEXT("UnrealEd", "LODImportSuccessful", "Mesh for LOD {0} imported successfully!"), FText::AsNumber(SelectedLOD)); @@ -539,6 +569,7 @@ namespace FbxMeshUtils } else { + ReapplyClothing(); // Notification of failure FNotificationInfo NotificationInfo(FText::GetEmpty()); NotificationInfo.Text = FText::Format(NSLOCTEXT("UnrealEd", "LODImportFail", "Failed to import mesh for LOD {0}!"), FText::AsNumber(SelectedLOD)); @@ -546,36 +577,8 @@ namespace FbxMeshUtils FSlateNotificationManager::Get().AddNotification(NotificationInfo); } } - - // Cleanup - for (int32 i=0; iReleaseScene(); - - // Re-apply our clothing assets - int32 NumClothingAssetsToApply = ClothingAssetsInUse.Num(); - if(ImportedResource && ImportedResource->LODModels.IsValidIndex(LODLevel)) - { - FSkeletalMeshLODModel& LodModel = ImportedResource->LODModels[LODLevel]; - for(int32 AssetIndex = 0; AssetIndex < NumClothingAssetsToApply; ++AssetIndex) - { - // Only if the same section exists - if(LodModel.Sections.IsValidIndex(ClothingAssetSectionIndices[AssetIndex])) - { - UClothingAssetBase* AssetToApply = ClothingAssetsInUse[AssetIndex]; - AssetToApply->BindToSkeletalMesh(SelectedSkelMesh, LODLevel, ClothingAssetSectionIndices[AssetIndex], ClothingAssetInternalLodIndices[AssetIndex]); - } - } - } - - if (bMustReimportAlternateSkinWeightProfile) - { - FLODUtilities::ReimportAlternateSkinWeight(SelectedSkelMesh, LODLevel, true); - } - + CleanUpScene(); return bSuccess; } diff --git a/Engine/Source/Editor/UnrealEd/Private/GraphEditor.cpp b/Engine/Source/Editor/UnrealEd/Private/GraphEditor.cpp index b98749e155b5..fab4c67ef483 100644 --- a/Engine/Source/Editor/UnrealEd/Private/GraphEditor.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/GraphEditor.cpp @@ -117,3 +117,41 @@ void SGraphEditor::NotifyPostPropertyChange( const FPropertyChangedEvent& Proper } } +void SGraphEditor::ResetAllNodesUnrelatedStates() +{ + TArray AllNodes = GetCurrentGraph()->Nodes; + + for (auto& GraphNode : AllNodes) + { + if (GraphNode->IsNodeUnrelated()) + { + GraphNode->SetNodeUnrelated(false); + } + } +} + +void SGraphEditor::FocusCommentNodes(TArray &CommentNodes, TArray &RelatedNodes) +{ + for (auto& CommentNode : CommentNodes) + { + CommentNode->SetNodeUnrelated(true); + + const FSlateRect CommentRect( + CommentNode->NodePosX, + CommentNode->NodePosY, + CommentNode->NodePosX + CommentNode->NodeWidth, + CommentNode->NodePosY + CommentNode->NodeHeight + ); + + for (auto& RelatedNode : RelatedNodes) + { + const FVector2D NodePos(RelatedNode->NodePosX, RelatedNode->NodePosY); + + if (CommentRect.ContainsPoint(NodePos)) + { + CommentNode->SetNodeUnrelated(false); + break; + } + } + } +} diff --git a/Engine/Source/Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp b/Engine/Source/Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp index ca15bdf19357..42b6d0f169c0 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp @@ -2125,15 +2125,17 @@ void FBlueprintEditorUtils::MarkBlueprintAsModified(UBlueprint* Blueprint, FProp Blueprint->bCachedDependenciesUpToDate = false; if (Blueprint->Status != BS_BeingCreated) { + // This clears any cached data, which includes the macro tunnel node data TArray AllGraphs; Blueprint->GetAllGraphs(AllGraphs); - for (int32 i = 0; i < AllGraphs.Num(); i++) + for (UEdGraph* GraphToClear : AllGraphs) { - UK2Node_EditablePinBase* EntryNode = GetEntryNode(AllGraphs[i]); - if(UK2Node_Tunnel* TunnelNode = ExactCast(EntryNode)) + for (UEdGraphNode* Node : GraphToClear->Nodes) { - // Remove data marking graphs as latent, this will be re-cache'd as needed - TunnelNode->MetaData.HasLatentFunctions = INDEX_NONE; + if (UK2Node* BPNode = Cast(Node)) + { + BPNode->ClearCachedBlueprintData(Blueprint); + } } } @@ -2154,6 +2156,13 @@ void FBlueprintEditorUtils::MarkBlueprintAsModified(UBlueprint* Blueprint, FProp // Clear out the cache as the user may have added or removed a latent action to a macro graph FBlueprintEditorUtils::ClearMacroCosmeticInfoCache(Blueprint); } + + IAssetEditorInstance* AssetEditor = FAssetEditorManager::Get().FindEditorForAsset(Blueprint, false); + if (AssetEditor) + { + FBlueprintEditor* BlueprintEditor = static_cast(AssetEditor); + BlueprintEditor->UpdateNodesUnrelatedStatesAfterGraphChange(); + } } bool FBlueprintEditorUtils::ShouldRegenerateBlueprint(UBlueprint* Blueprint) @@ -3640,6 +3649,25 @@ void FBlueprintEditorUtils::SetVariableAdvancedDisplayFlag(UBlueprint* InBluepri FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(InBlueprint); } +void FBlueprintEditorUtils::SetVariableDeprecatedFlag(UBlueprint* InBlueprint, const FName& InVarName, const bool bInIsDeprecated) +{ + const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(InBlueprint, InVarName); + + if (VarIndex != INDEX_NONE) + { + if (bInIsDeprecated) + { + InBlueprint->NewVariables[VarIndex].PropertyFlags |= CPF_Deprecated; + } + else + { + InBlueprint->NewVariables[VarIndex].PropertyFlags &= ~CPF_Deprecated; + } + } + + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(InBlueprint); +} + struct FMetaDataDependencyHelper { static void OnChange(UBlueprint* Blueprint, FName MetaDataKey) @@ -3934,7 +3962,8 @@ void FBlueprintEditorUtils::SetBlueprintFunctionOrMacroCategory(UEdGraph* Graph, UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(Graph); if (FKismetUserDeclaredFunctionMetadata* MetaData = FBlueprintEditorUtils::GetGraphFunctionMetaData(Graph)) { - if(!MetaData->Category.EqualTo(InCategoryName)) + const FText& NewCategory = InCategoryName.IsEmpty() ? UEdGraphSchema_K2::VR_DefaultCategory : InCategoryName; + if (!MetaData->Category.EqualTo(NewCategory)) { FScopedTransaction Transaction(LOCTEXT("SetBlueprintFunctionOrMacroCategory", "Set Category")); @@ -3950,7 +3979,6 @@ void FBlueprintEditorUtils::SetBlueprintFunctionOrMacroCategory(UEdGraph* Graph, } } - const FText& NewCategory = InCategoryName.IsEmpty() ? UEdGraphSchema_K2::VR_DefaultCategory : InCategoryName; MetaData->Category = NewCategory; if (Function) @@ -4065,9 +4093,16 @@ bool FBlueprintEditorUtils::MoveGraphBeforeOtherGraph(UEdGraph* Graph, int32 New } } - if (bModified && !bDontRecompile) + if (bModified) { - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); + if (!bDontRecompile) + { + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); + } + else + { + FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); + } } return bModified; @@ -7781,6 +7816,7 @@ TSharedRef FBlueprintEditorUtils::ConstructBlueprintParentClassPicker( // Fill in options FClassViewerInitializationOptions Options; Options.Mode = EClassViewerMode::ClassPicker; + Options.bShowBackgroundBorder = false; TSharedPtr Filter = MakeShareable(new FBlueprintReparentFilter); Options.ClassFilter = Filter; @@ -7941,6 +7977,7 @@ TSharedRef FBlueprintEditorUtils::ConstructBlueprintInterfaceClassPicke // Fill in options FClassViewerInitializationOptions Options; Options.Mode = EClassViewerMode::ClassPicker; + Options.bShowBackgroundBorder = false; TSharedPtr Filter = MakeShareable(new FBlueprintInterfaceFilter); Options.ClassFilter = Filter; @@ -8233,12 +8270,12 @@ bool FBlueprintEditorUtils::IsObjectADebugCandidate( AActor* InActorObject, UBlu return bPassesFlags && bCanDebugThisObject; } -bool FBlueprintEditorUtils::PropertyValueFromString(const UProperty* Property, const FString& StrValue, uint8* Container) +bool FBlueprintEditorUtils::PropertyValueFromString(const UProperty* Property, const FString& StrValue, uint8* Container, UObject* OwningObject) { - return PropertyValueFromString_Direct(Property, StrValue, Property->ContainerPtrToValuePtr(Container)); + return PropertyValueFromString_Direct(Property, StrValue, Property->ContainerPtrToValuePtr(Container), OwningObject); } -bool FBlueprintEditorUtils::PropertyValueFromString_Direct(const UProperty* Property, const FString& StrValue, uint8* DirectValue) +bool FBlueprintEditorUtils::PropertyValueFromString_Direct(const UProperty* Property, const FString& StrValue, uint8* DirectValue, UObject* OwningObject) { bool bParseSucceeded = true; if (!Property->IsA(UStructProperty::StaticClass())) @@ -8304,7 +8341,7 @@ bool FBlueprintEditorUtils::PropertyValueFromString_Direct(const UProperty* Prop else if (Property->IsA(UTextProperty::StaticClass())) { FStringOutputDevice ImportError; - const TCHAR* EndOfParsedBuff = Property->ImportText(*StrValue, DirectValue, PPF_SerializedAsImportText, nullptr, &ImportError); + const TCHAR* EndOfParsedBuff = Property->ImportText(*StrValue, DirectValue, PPF_SerializedAsImportText, OwningObject, &ImportError); bParseSucceeded = EndOfParsedBuff && ImportError.IsEmpty(); } else @@ -8315,7 +8352,7 @@ bool FBlueprintEditorUtils::PropertyValueFromString_Direct(const UProperty* Prop : *StrValue; FStringOutputDevice ImportError; - const TCHAR* EndOfParsedBuff = Property->ImportText(*StrValue, DirectValue, PPF_SerializedAsImportText, nullptr, &ImportError); + const TCHAR* EndOfParsedBuff = Property->ImportText(*StrValue, DirectValue, PPF_SerializedAsImportText, OwningObject, &ImportError); bParseSucceeded = EndOfParsedBuff && ImportError.IsEmpty(); } } @@ -8362,7 +8399,7 @@ bool FBlueprintEditorUtils::PropertyValueFromString_Direct(const UProperty* Prop ensure(1 == StructProperty->ArrayDim); FStringOutputDevice ImportError; - const TCHAR* EndOfParsedBuff = StructProperty->ImportText(StrValue.IsEmpty() ? TEXT("()") : *StrValue, DirectValue, PPF_SerializedAsImportText, nullptr, &ImportError); + const TCHAR* EndOfParsedBuff = StructProperty->ImportText(StrValue.IsEmpty() ? TEXT("()") : *StrValue, DirectValue, PPF_SerializedAsImportText, OwningObject, &ImportError); bParseSucceeded &= EndOfParsedBuff && ImportError.IsEmpty(); } } @@ -8370,12 +8407,12 @@ bool FBlueprintEditorUtils::PropertyValueFromString_Direct(const UProperty* Prop return bParseSucceeded; } -bool FBlueprintEditorUtils::PropertyValueToString(const UProperty* Property, const uint8* Container, FString& OutForm) +bool FBlueprintEditorUtils::PropertyValueToString(const UProperty* Property, const uint8* Container, FString& OutForm, UObject* OwningObject) { - return PropertyValueToString_Direct(Property, Property->ContainerPtrToValuePtr(Container), OutForm); + return PropertyValueToString_Direct(Property, Property->ContainerPtrToValuePtr(Container), OutForm, OwningObject); } -bool FBlueprintEditorUtils::PropertyValueToString_Direct(const UProperty* Property, const uint8* DirectValue, FString& OutForm) +bool FBlueprintEditorUtils::PropertyValueToString_Direct(const UProperty* Property, const uint8* DirectValue, FString& OutForm, UObject* OwningObject) { check(Property && DirectValue); OutForm.Reset(); @@ -8419,7 +8456,7 @@ bool FBlueprintEditorUtils::PropertyValueToString_Direct(const UProperty* Proper if (OutForm.IsEmpty()) { const uint8* DefaultValue = DirectValue; - bSucceeded = Property->ExportText_Direct(OutForm, DirectValue, DefaultValue, nullptr, PPF_SerializedAsImportText); + bSucceeded = Property->ExportText_Direct(OutForm, DirectValue, DefaultValue, OwningObject, PPF_SerializedAsImportText); } return bSucceeded; } @@ -9121,6 +9158,17 @@ FString FBlueprintEditorUtils::GetClassNameWithoutSuffix(const UClass* Class) } } +FText FBlueprintEditorUtils::GetDeprecatedMemberUsageNodeWarning(const FText& MemberName, const FText& DetailedMessage) +{ + static FText UnknownName = LOCTEXT("UnknownDeprecatedMemberName", "[unknown]"); + static FText DefaultMessage = LOCTEXT("DefaultDeprecatedMemberUsageDetails", "Please replace or remove it."); + + FFormatNamedArguments Args; + Args.Add("MemberName", ensure(!MemberName.IsEmpty()) ? MemberName : UnknownName); + Args.Add("DetailedMessage", DetailedMessage.IsEmpty() ? DefaultMessage : DetailedMessage); + return FText::Format(LOCTEXT("DeprecatedMemberUsageNodeWarning", "@@: Usage of '{MemberName}' has been deprecated. {DetailedMessage}"), Args); +} + UK2Node_FunctionResult* FBlueprintEditorUtils::FindOrCreateFunctionResultNode(UK2Node_EditablePinBase* InFunctionEntryNode) { UK2Node_FunctionResult* FunctionResult = nullptr; diff --git a/Engine/Source/Editor/UnrealEd/Private/Kismet2/ComponentEditorUtils.cpp b/Engine/Source/Editor/UnrealEd/Private/Kismet2/ComponentEditorUtils.cpp index 12795d9fa0c7..ed2d191c7cc6 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Kismet2/ComponentEditorUtils.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Kismet2/ComponentEditorUtils.cpp @@ -977,6 +977,84 @@ void FComponentEditorUtils::FillComponentContextMenuOptions(FMenuBuilder& MenuBu } } +UActorComponent* FComponentEditorUtils::FindMatchingComponent(UActorComponent* ComponentInstance, const TInlineComponentArray& ComponentList) +{ + if (ComponentInstance == nullptr) + { + return nullptr; + } + + TInlineComponentArray FoundComponents; + UActorComponent* LastFoundComponent = nullptr; + for (UActorComponent* Component : ComponentList) + { + // Early out on pointer match + if (ComponentInstance == Component) + { + return Component; + } + + if (ComponentInstance->GetFName() == Component->GetFName()) + { + FoundComponents.Add(Component); + LastFoundComponent = Component; + } + } + + // No match or 1 match avoid sorting + if (FoundComponents.Num() <= 1) + { + return LastFoundComponent; + } + + if (USceneComponent* CurrentSceneComponent = Cast(ComponentInstance)) + { + // Sort by matching hierarchy + FoundComponents.Sort([&](const UActorComponent& ComponentA, const UActorComponent& ComponentB) + { + const USceneComponent* SceneComponentA = Cast(&ComponentA); + const USceneComponent* SceneComponentB = Cast(&ComponentB); + if (SceneComponentB == nullptr) + { + return true; + } + else if (SceneComponentA == nullptr) + { + return false; + } + + const USceneComponent* AttachParentA = SceneComponentA->GetAttachParent(); + const USceneComponent* AttachParentB = SceneComponentB->GetAttachParent(); + const USceneComponent* CurrentParent = CurrentSceneComponent->GetAttachParent(); + // No parents... + if (CurrentParent == nullptr) + { + return AttachParentA == nullptr; + } + + bool MatchA = AttachParentA != nullptr && AttachParentA->GetFName() == CurrentParent->GetFName(); + bool MatchB = AttachParentB != nullptr && AttachParentB->GetFName() == CurrentParent->GetFName(); + while (MatchA && MatchB) + { + AttachParentA = AttachParentA->GetAttachParent(); + AttachParentB = AttachParentB->GetAttachParent(); + CurrentParent = CurrentParent->GetAttachParent(); + if (CurrentParent == nullptr) + { + return AttachParentA == nullptr; + } + + MatchA = AttachParentA != nullptr && AttachParentA->GetFName() == CurrentParent->GetFName(); + MatchB = AttachParentB != nullptr && AttachParentB->GetFName() == CurrentParent->GetFName(); + } + + return MatchA; + }); + } + + return FoundComponents[0]; +} + void FComponentEditorUtils::OnGoToComponentAssetInBrowser(UObject* Asset) { TArray Objects; diff --git a/Engine/Source/Editor/UnrealEd/Private/Kismet2/Kismet2.cpp b/Engine/Source/Editor/UnrealEd/Private/Kismet2/Kismet2.cpp index 9dc75a691939..b3eab51ab44f 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Kismet2/Kismet2.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Kismet2/Kismet2.cpp @@ -80,6 +80,7 @@ #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "Engine/InheritableComponentHandler.h" +#include "Classes/EditorStyleSettings.h" DECLARE_CYCLE_STAT(TEXT("Compile Blueprint"), EKismetCompilerStats_CompileBlueprint, STATGROUP_KismetCompiler); DECLARE_CYCLE_STAT(TEXT("Broadcast Precompile"), EKismetCompilerStats_BroadcastPrecompile, STATGROUP_KismetCompiler); @@ -654,6 +655,14 @@ UK2Node_Event* FKismetEditorUtilities::AddDefaultEventNode(UBlueprint* InBluepri NewEventNode = NewObject(InGraph); NewEventNode->EventReference = EventReference; + // Snap the new position to the grid + const UEditorStyleSettings* StyleSettings = GetDefault(); + if (StyleSettings) + { + const uint32 GridSnapSize = StyleSettings->GridSnapSize; + InOutNodePosY = GridSnapSize * FMath::RoundFromZero(InOutNodePosY / (float)GridSnapSize); + } + // add update event graph NewEventNode->bOverrideFunction=true; NewEventNode->CreateNewGuid(); @@ -1642,7 +1651,7 @@ UBlueprint* FKismetEditorUtilities::CreateBlueprintFromActor(const FName Bluepri if (NewBlueprint->GeneratedClass != nullptr) { AActor* CDO = CastChecked(NewBlueprint->GeneratedClass->GetDefaultObject()); - const auto CopyOptions = (EditorUtilities::ECopyOptions::Type)(EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances); + const EditorUtilities::ECopyOptions::Type CopyOptions = (EditorUtilities::ECopyOptions::Type)(EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances | EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties); EditorUtilities::CopyActorProperties(Actor, CDO, CopyOptions); if (USceneComponent* DstSceneRoot = CDO->GetRootComponent()) diff --git a/Engine/Source/Editor/UnrealEd/Private/Kismet2/Kismet2NameValidators.cpp b/Engine/Source/Editor/UnrealEd/Private/Kismet2/Kismet2NameValidators.cpp index 7eb79195c89c..7774ca7ca0b7 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Kismet2/Kismet2NameValidators.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Kismet2/Kismet2NameValidators.cpp @@ -103,6 +103,7 @@ FKismetNameValidator::FKismetNameValidator(const class UBlueprint* Blueprint, FN ExistingName = InExistingName; BlueprintObject = Blueprint; FBlueprintEditorUtils::GetClassVariableList(BlueprintObject, Names, true); + FBlueprintEditorUtils::GetFunctionNameList(BlueprintObject, Names); FBlueprintEditorUtils::GetAllGraphNames(BlueprintObject, Names); FBlueprintEditorUtils::GetSCSVariableNameList(Blueprint, Names); FBlueprintEditorUtils::GetImplementingBlueprintsFunctionNameList(Blueprint, Names); diff --git a/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetDebugUtilities.cpp b/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetDebugUtilities.cpp index e01110642084..8a1dd8513de4 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetDebugUtilities.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetDebugUtilities.cpp @@ -913,16 +913,14 @@ void FKismetDebugUtilities::GetValidBreakpointLocations(const UK2Node_MacroInsta if (!bIsMacroPure && MacroEntryNode) { // Get the execute pin outputs on the entry node - for (auto PinIt = MacroEntryNode->Pins.CreateConstIterator(); PinIt; ++PinIt) + for (const UEdGraphPin* ExecPin : MacroEntryNode->Pins) { - const UEdGraphPin* ExecPin = *PinIt; if (ExecPin && ExecPin->Direction == EGPD_Output && ExecPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) { // For each pin linked to each execute pin, collect the node that owns it - for (auto LinkedToPinIt = ExecPin->LinkedTo.CreateConstIterator(); LinkedToPinIt; ++LinkedToPinIt) + for (const UEdGraphPin* LinkedToPin : ExecPin->LinkedTo) { - const UEdGraphPin* LinkedToPin = *LinkedToPinIt; check(LinkedToPin); const UEdGraphNode* LinkedToNode = LinkedToPin->GetOwningNode(); @@ -1185,6 +1183,13 @@ FKismetDebugUtilities::EWatchTextResult FKismetDebugUtilities::FindDebuggingData { if (OutParmRec->Property == Property) { + // try to use the output pin we're linked to + // otherwise the output param won't show any data since the return node hasn't executed when we stop here + if (WatchPin->Direction == EEdGraphPinDirection::EGPD_Input && WatchPin->LinkedTo.Num() == 1) + { + return FindDebuggingData(Blueprint, ActiveObject, WatchPin->LinkedTo[0], OutProperty, OutData, OutDelta, OutParent, SeenObjects); + } + PropertyBase = OutParmRec->PropAddr; break; } @@ -1208,7 +1213,7 @@ FKismetDebugUtilities::EWatchTextResult FKismetDebugUtilities::FindDebuggingData else if (AActor* Actor = Cast(ActiveObject)) { // Try and locate the propertybase in the actor components - for (auto ComponentIter : Actor->GetComponents()) + for (UActorComponent* ComponentIter : Actor->GetComponents()) { if (ComponentIter->GetClass()->IsChildOf(PropertyClass)) { diff --git a/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetReinstanceUtilities.cpp b/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetReinstanceUtilities.cpp index 86a1a170649a..b8c98d357052 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetReinstanceUtilities.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetReinstanceUtilities.cpp @@ -31,6 +31,7 @@ #include "BlueprintEditor.h" #include "Engine/Selection.h" #include "BlueprintEditorSettings.h" +#include "Algo/RemoveIf.h" extern COREUOBJECT_API bool GBlueprintUseCompilationManager; @@ -88,6 +89,24 @@ struct FReplaceReferenceHelper BP_SCOPED_COMPILER_EVENT_STAT(EKismetReinstancerStats_FindReferencers); Targets = FArchiveHasReferences::GetAllReferencers(SourceObjects, ObjectsThatShouldUseOldStuff); + + // Make sure we don't update properties in old objects, as they + // may take ownership of objects referenced in new objects (e.g. + // delete components owned by new actors) + int32 RangeEnd = Algo::RemoveIf(Targets, [&ObjectsToReplace](UObject* Obj) + { + for (UObject* ObjectToReplace : ObjectsToReplace) + { + if (ObjectToReplace == Obj || Obj->IsIn(ObjectToReplace)) + { + return true; + } + } + + return false; + }); + + Targets.RemoveAtSwap(RangeEnd, Targets.Num() - RangeEnd, false); } { @@ -95,51 +114,45 @@ struct FReplaceReferenceHelper for (UObject* Obj : Targets) { - // Make sure we don't update properties in old objects, as they - // may take ownership of objects referenced in new objects (e.g. - // delete components owned by new actors) - if (!ObjectsToReplace.Contains(Obj)) + // The class for finding and replacing weak references. + // We can't relay on "standard" weak references replacement as + // it depends on FSoftObjectPath::ResolveObject, which + // tries to find the object with the stored path. It is + // impossible, cause above we deleted old actors (after + // spawning new ones), so during objects traverse we have to + // find FSoftObjectPath with the raw given path taken + // before deletion of old actors and fix them. + class ReferenceReplace : public FArchiveReplaceObjectRef < UObject > { - // The class for finding and replacing weak references. - // We can't relay on "standard" weak references replacement as - // it depends on FSoftObjectPath::ResolveObject, which - // tries to find the object with the stored path. It is - // impossible, cause above we deleted old actors (after - // spawning new ones), so during objects traverse we have to - // find FSoftObjectPath with the raw given path taken - // before deletion of old actors and fix them. - class ReferenceReplace : public FArchiveReplaceObjectRef < UObject > + public: + ReferenceReplace(UObject* InSearchObject, const TMap& InReplacementMap, const TMap& InWeakReferencesMap) + : FArchiveReplaceObjectRef(InSearchObject, InReplacementMap, false, false, false, true), WeakReferencesMap(InWeakReferencesMap) { - public: - ReferenceReplace(UObject* InSearchObject, const TMap& InReplacementMap, const TMap& InWeakReferencesMap) - : FArchiveReplaceObjectRef(InSearchObject, InReplacementMap, false, false, false, true), WeakReferencesMap(InWeakReferencesMap) + SerializeSearchObject(); + } + + FArchive& operator<<(FSoftObjectPath& Ref) override + { + const UObject*const* PtrToObjPtr = WeakReferencesMap.Find(Ref); + + if (PtrToObjPtr != nullptr) { - SerializeSearchObject(); + Ref = *PtrToObjPtr; } - FArchive& operator<<(FSoftObjectPath& Ref) override - { - const UObject*const* PtrToObjPtr = WeakReferencesMap.Find(Ref); + return *this; + } - if (PtrToObjPtr != nullptr) - { - Ref = *PtrToObjPtr; - } + FArchive& operator<<(FSoftObjectPtr& Ref) override + { + return operator<<(Ref.GetUniqueID()); + } - return *this; - } + private: + const TMap& WeakReferencesMap; + }; - FArchive& operator<<(FSoftObjectPtr& Ref) override - { - return operator<<(Ref.GetUniqueID()); - } - - private: - const TMap& WeakReferencesMap; - }; - - ReferenceReplace ReplaceAr(Obj, OldToNewInstanceMap, ReinstancedObjectsWeakReferenceMap); - } + ReferenceReplace ReplaceAr(Obj, OldToNewInstanceMap, ReinstancedObjectsWeakReferenceMap); } } } @@ -1691,9 +1704,7 @@ static void ReplaceObjectHelper(UObject*& OldObject, UClass* OldClass, UObject*& } } - OldObject->RemoveFromRoot(); - OldObject->MarkPendingKill(); - + // NOTE: Don't mark the old object as pending kill yet! Otherwise, any weak references to this object will be invalidated too soon and won't get replaced. OldToNewInstanceMap.Add(OldObject, NewUObject); if (bIsComponent) @@ -1850,7 +1861,7 @@ static void ReplaceActorHelper(UObject* OldObject, UClass* OldClass, UObject*& N GEditor->Layers->DisassociateActorFromLayers(OldActor); } - World->EditorDestroyActor(OldActor, /*bShouldModifyLevel =*/true); + // NOTE: Don't destroy the Actor yet! Otherwise, any weak references to this Actor will be invalidated too soon and won't get replaced. OldToNewInstanceMap.Add(OldActor, NewActor); } @@ -1905,18 +1916,6 @@ void FBlueprintCompileReinstancer::ReplaceInstancesOfClass_Inner(TMapOnObjectsReplaced().AddRaw(&ObjectRemappingHelper, &FObjectRemappingHelper::OnObjectsReplaced); } - auto UpdateObjectBeingDebugged = [](UObject* InOldObject, UObject* InNewObject) - { - if (UBlueprint* OldObjBlueprint = Cast(InOldObject->GetClass()->ClassGeneratedBy)) - { - const UObject* DebugObj = OldObjBlueprint->GetObjectBeingDebugged(EGetObjectOrWorldBeingDebuggedFlags::IgnorePendingKill); - if (DebugObj == InOldObject) - { - OldObjBlueprint->SetObjectBeingDebugged(InNewObject); - } - } - }; - { TArray ObjectsToReplace; @@ -1967,7 +1966,7 @@ void FBlueprintCompileReinstancer::ReplaceInstancesOfClass_Inner(TMapRemoveFromRoot(); + checkf(!Obj->IsPendingKill(), TEXT("'%s' should not have been marked as PendingKill prior to this point."), *Obj->GetPathName()); + + AActor* ObjAsActor = Cast(Obj); + if (ObjAsActor && ObjAsActor->GetLevel()) + { + // This will take care of marking the object as PendingKill. + UWorld* World = ObjAsActor->GetWorld(); + check(World != nullptr); + World->EditorDestroyActor(ObjAsActor, /*bShouldModifyLevel =*/true); + } + else + { + Obj->MarkPendingKill(); + } } diff --git a/Engine/Source/Editor/UnrealEd/Private/Kismet2/StructureEditorUtils.cpp b/Engine/Source/Editor/UnrealEd/Private/Kismet2/StructureEditorUtils.cpp index a252a2890184..b8ff27a16427 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Kismet2/StructureEditorUtils.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Kismet2/StructureEditorUtils.cpp @@ -305,15 +305,13 @@ bool FStructureEditorUtils::AddVariable(UUserDefinedStruct* Struct, const FEdGra FString DisplayName; const FName VarName = FMemberVariableNameHelper::Generate(Struct, FString(), Guid, &DisplayName); check(NULL == GetVarDesc(Struct).FindByPredicate(FStructureEditorUtils::FFindByNameHelper(VarName))); - check(IsUniqueVariableDisplayName(Struct, DisplayName)); + check(IsUniqueVariableFriendlyName(Struct, DisplayName)); FStructVariableDescription NewVar; NewVar.VarName = VarName; NewVar.FriendlyName = DisplayName; NewVar.SetPinType(VarType); NewVar.VarGuid = Guid; - NewVar.bDontEditoOnInstance = false; - NewVar.bInvalidMember = false; GetVarDesc(Struct).Add(NewVar); OnStructureChanged(Struct, EStructureEditorChangeInfo::AddedVariable); @@ -355,7 +353,7 @@ bool FStructureEditorUtils::RenameVariable(UUserDefinedStruct* Struct, FGuid Var FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); if (VarDesc && !NewDisplayNameStr.IsEmpty() - && IsUniqueVariableDisplayName(Struct, NewDisplayNameStr)) + && IsUniqueVariableFriendlyName(Struct, NewDisplayNameStr)) { const FScopedTransaction Transaction(LOCTEXT("RenameVariable", "Rename Variable")); ModifyStructData(Struct); @@ -449,7 +447,7 @@ bool FStructureEditorUtils::ChangeVariableDefaultValue(UUserDefinedStruct* Struc const UProperty* Property = FindField(Struct, VarDesc->VarName); FStructOnScope StructDefaultMem(Struct); bAdvancedValidation = StructDefaultMem.IsValid() && Property && - FBlueprintEditorUtils::PropertyValueFromString(Property, NewDefaultValue, StructDefaultMem.GetStructMemory()); + FBlueprintEditorUtils::PropertyValueFromString(Property, NewDefaultValue, StructDefaultMem.GetStructMemory(), Struct); } if (bAdvancedValidation) @@ -468,7 +466,7 @@ bool FStructureEditorUtils::ChangeVariableDefaultValue(UUserDefinedStruct* Struc return false; } -bool FStructureEditorUtils::IsUniqueVariableDisplayName(const UUserDefinedStruct* Struct, const FString& DisplayName) +bool FStructureEditorUtils::IsUniqueVariableFriendlyName(const UUserDefinedStruct* Struct, const FString& DisplayName) { if(Struct) { @@ -484,13 +482,19 @@ bool FStructureEditorUtils::IsUniqueVariableDisplayName(const UUserDefinedStruct return false; } -FString FStructureEditorUtils::GetVariableDisplayName(const UUserDefinedStruct* Struct, FGuid VarGuid) +FString FStructureEditorUtils::GetVariableFriendlyName(const UUserDefinedStruct* Struct, FGuid VarGuid) { const FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); return VarDesc ? VarDesc->FriendlyName : FString(); } -UProperty* FStructureEditorUtils::GetPropertyByDisplayName(const UUserDefinedStruct* Struct, FString DisplayName) +FString FStructureEditorUtils::GetVariableFriendlyNameForProperty(const UUserDefinedStruct* Struct, const UProperty* Property) +{ + const FStructVariableDescription* VarDesc = Struct && Property ? GetVarDesc(Struct).FindByPredicate(FFindByNameHelper(Property->GetFName())) : nullptr; + return VarDesc ? VarDesc->FriendlyName : FString(); +} + +UProperty* FStructureEditorUtils::GetPropertyByFriendlyName(const UUserDefinedStruct* Struct, FString DisplayName) { if (Struct) { @@ -697,13 +701,29 @@ bool FStructureEditorUtils::ChangeEditableOnBPInstance(UUserDefinedStruct* Struc { const UEdGraphSchema_K2* K2Schema = GetDefault(); FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); - const bool bNewDontEditoOnInstance = !bInIsEditable; - if (VarDesc && (bNewDontEditoOnInstance != VarDesc->bDontEditoOnInstance)) + const bool bNewDontEditOnInstance = !bInIsEditable; + if (VarDesc && (bNewDontEditOnInstance != VarDesc->bDontEditOnInstance)) { const FScopedTransaction Transaction(LOCTEXT("ChangeVariableOnBPInstance", "Change variable editable on BP instance")); ModifyStructData(Struct); - VarDesc->bDontEditoOnInstance = bNewDontEditoOnInstance; + VarDesc->bDontEditOnInstance = bNewDontEditOnInstance; + OnStructureChanged(Struct); + return true; + } + return false; +} + +bool FStructureEditorUtils::ChangeSaveGameEnabled(UUserDefinedStruct* Struct, FGuid VarGuid, bool bInSaveGame) +{ + const UEdGraphSchema_K2* K2Schema = GetDefault(); + FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); + if (VarDesc && (bInSaveGame != VarDesc->bEnableSaveGame)) + { + const FScopedTransaction Transaction(LOCTEXT("ChangeSaveGameOnVariable", "Change variable SaveGame flag")); + ModifyStructData(Struct); + + VarDesc->bEnableSaveGame = bInSaveGame; OnStructureChanged(Struct); return true; } diff --git a/Engine/Source/Editor/UnrealEd/Private/LevelEditorViewport.cpp b/Engine/Source/Editor/UnrealEd/Private/LevelEditorViewport.cpp index 8fcd3c8a1e8c..f1428ccabcbb 100644 --- a/Engine/Source/Editor/UnrealEd/Private/LevelEditorViewport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/LevelEditorViewport.cpp @@ -2308,6 +2308,17 @@ void TrimLineToFrustum(const FConvexVolume& Frustum, FVector& Start, FVector& En } } +static void GetAttachedActorsRecursive(const AActor* InActor, TArray& OutActors) +{ + TArray AttachedActors; + InActor->GetAttachedActors(AttachedActors); + for (AActor* AttachedActor : AttachedActors) + { + GetAttachedActorsRecursive(AttachedActor, OutActors); + } + OutActors.Append(AttachedActors); +}; + void FLevelEditorViewportClient::ProjectActorsIntoWorld(const TArray& Actors, FViewport* InViewport, const FVector& Drag, const FRotator& Rot) { // Compile an array of selected actors @@ -2363,6 +2374,10 @@ void FLevelEditorViewportClient::ProjectActorsIntoWorld(const TArray& A if (bIsOnScreen) { + TArray IgnoreActors; + IgnoreActors.Append(Actors); // Add the whole list of actors so you can't hit the moving set with the ray + GetAttachedActorsRecursive(Actor, IgnoreActors); + // Determine how we're going to attempt to project the object onto the world if (CurrentAxis == EAxisList::XY || CurrentAxis == EAxisList::XZ || CurrentAxis == EAxisList::YZ) { @@ -2381,11 +2396,11 @@ void FLevelEditorViewportClient::ProjectActorsIntoWorld(const TArray& A TrimLineToFrustum(Frustum, RayStart, RayEnd); - TraceResult = FActorPositioning::TraceWorldForPosition(*GetWorld(), *SceneView, RayStart, RayEnd, &Actors); + TraceResult = FActorPositioning::TraceWorldForPosition(*GetWorld(), *SceneView, RayStart, RayEnd, &IgnoreActors); } else { - TraceResult = FActorPositioning::TraceWorldForPosition(Cursor, *SceneView, &Actors); + TraceResult = FActorPositioning::TraceWorldForPosition(Cursor, *SceneView, &IgnoreActors); } } @@ -3345,8 +3360,26 @@ void FLevelEditorViewportClient::MoveLockedActorToCamera() } } - ActiveActorLock->SetActorLocation(GCurrentLevelEditingViewportClient->GetViewLocation(), false); - ActiveActorLock->SetActorRotation(GCurrentLevelEditingViewportClient->GetViewRotation()); + // Need to disable orbit camera before setting actor position so that the viewport camera location is converted back + GCurrentLevelEditingViewportClient->ToggleOrbitCamera(false); + + // If we're locked to a camera then we're reflecting the camera view and not the actor position. We need to reflect that delta when we reposition the piloted actor + if (bUseControllingActorViewInfo) + { + const USceneComponent* ViewComponent = Cast(FindViewComponentForActor(ActiveActorLock)); + if (ViewComponent != nullptr) + { + const FTransform RelativeTransform = ViewComponent->GetComponentTransform().Inverse(); + const FTransform DesiredTransform = FTransform(GCurrentLevelEditingViewportClient->GetViewRotation(), GCurrentLevelEditingViewportClient->GetViewLocation()); + + ActiveActorLock->SetActorTransform(ActiveActorLock->GetActorTransform() * RelativeTransform * DesiredTransform); + } + } + else + { + ActiveActorLock->SetActorLocation(GCurrentLevelEditingViewportClient->GetViewLocation(), false); + ActiveActorLock->SetActorRotation(GCurrentLevelEditingViewportClient->GetViewRotation()); + } } if (ABrush* Brush = Cast(ActiveActorLock)) diff --git a/Engine/Source/Editor/UnrealEd/Private/LevelViewportClickHandlers.cpp b/Engine/Source/Editor/UnrealEd/Private/LevelViewportClickHandlers.cpp index 0688746d702b..f3cc0a284b0b 100644 --- a/Engine/Source/Editor/UnrealEd/Private/LevelViewportClickHandlers.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/LevelViewportClickHandlers.cpp @@ -140,7 +140,7 @@ namespace ClickHandlers GEditor->GetSelectedActors()->Modify(); - if( bAllowSelectionChange ) + if( bAllowSelectionChange && GEditor->CanSelectActor(Actor, true, true) ) { // If the actor the user clicked on was already selected, then we won't bother clearing the selection if( !Actor->IsSelected() ) @@ -173,7 +173,7 @@ namespace ClickHandlers GEditor->GetSelectedActors()->Modify(); - if( bAllowSelectionChange ) + if( bAllowSelectionChange && GEditor->CanSelectActor(Actor, true, true) ) { // Clear the selection GEditor->SelectNone( false, true ); @@ -229,7 +229,7 @@ namespace ClickHandlers } else if ( Actor ) { - if( bAllowSelectionChange ) + if( bAllowSelectionChange && GEditor->CanSelectActor(Actor, true, true, true) ) { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "ClickingOnActors", "Clicking on Actors") ); GEditor->GetSelectedActors()->Modify(); diff --git a/Engine/Source/Editor/UnrealEd/Private/MaterialGraphNode.cpp b/Engine/Source/Editor/UnrealEd/Private/MaterialGraphNode.cpp index 0f266873f9c7..5e70fdf1252c 100644 --- a/Engine/Source/Editor/UnrealEd/Private/MaterialGraphNode.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/MaterialGraphNode.cpp @@ -553,7 +553,7 @@ void UMaterialGraphNode::CreateInputPins() { // Makes sure pin has a name for lookup purposes but user will never see it NewPin->PinName = CreateUniquePinName(TEXT("Input")); - NewPin->PinFriendlyName = FText::FromString(TEXT(" ")); + NewPin->PinFriendlyName = FText::GetEmpty(); } } } @@ -607,7 +607,7 @@ void UMaterialGraphNode::CreateOutputPins() { // Makes sure pin has a name for lookup purposes but user will never see it NewPin->PinName = CreateUniquePinName(TEXT("Output")); - NewPin->PinFriendlyName = FText::FromString(TEXT(" ")); + NewPin->PinFriendlyName = FText::GetEmpty(); } } } diff --git a/Engine/Source/Editor/UnrealEd/Private/ObjectTools.cpp b/Engine/Source/Editor/UnrealEd/Private/ObjectTools.cpp index 78220e9eb359..e7980db49e63 100644 --- a/Engine/Source/Editor/UnrealEd/Private/ObjectTools.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/ObjectTools.cpp @@ -2019,10 +2019,37 @@ namespace ObjectTools bool ContainsWorldInUse(const TArray< UObject* >& ObjectsToDelete) { - TArray ActiveWorlds; + TArray WorldsToDelete; + + for (const UObject* ObjectToDelete : ObjectsToDelete) + { + if (const UWorld* World = Cast(ObjectToDelete)) + { + WorldsToDelete.AddUnique(World); + } + } + + if (WorldsToDelete.Num() == 0) + { + return false; + } + + auto GetCombinedWorldNames = [](const TArray& Worlds) -> FString + { + return FString::JoinBy(Worlds, TEXT(", "), + [](const UWorld* World) -> FString + { + return World->GetPathName(); + }); + }; + + UE_LOG(LogObjectTools, Log, TEXT("Deleting %d worlds: %s"), WorldsToDelete.Num(), *GetCombinedWorldNames(WorldsToDelete)); + + TArray ActiveWorlds; + for (const FWorldContext& WorldContext : GEditor->GetWorldContexts()) { - if (UWorld* World = WorldContext.World()) + if (const UWorld* World = WorldContext.World()) { ActiveWorlds.AddUnique(World); @@ -2030,20 +2057,22 @@ namespace ObjectTools { if (StreamingLevel && StreamingLevel->GetLoadedLevel() && StreamingLevel->GetLoadedLevel()->GetOuter()) { - ActiveWorlds.AddUnique(StreamingLevel->GetLoadedLevel()->GetOuter()); + if (const UWorld* StreamingWorld = Cast(StreamingLevel->GetLoadedLevel()->GetOuter())) + { + ActiveWorlds.AddUnique(StreamingWorld); + } } } } } - for (const UObject* ObjectToDelete : ObjectsToDelete) + UE_LOG(LogObjectTools, Log, TEXT("Currently %d active worlds: %s"), ActiveWorlds.Num(), *GetCombinedWorldNames(ActiveWorlds)); + + for (const UWorld* World : WorldsToDelete) { - if (const UWorld* World = Cast(ObjectToDelete)) + if (ActiveWorlds.Contains(World)) { - if (ActiveWorlds.Contains(World)) - { - return true; - } + return true; } } @@ -2341,11 +2370,25 @@ namespace ObjectTools return 0; } - // Close all editors to avoid changing references to temporary objects used by the editor - if ( !FAssetEditorManager::Get().CloseAllAssetEditors() ) + // Attempt to close all editors referencing this asset. + bool bClosedAllEditors = true; + + for (UObject* ObjectToDelete : InObjectsToDelete) + { + const TArray ObjectEditors = FAssetEditorManager::Get().FindEditorsForAsset(ObjectToDelete); + for (IAssetEditorInstance* ObjectEditorInstance : ObjectEditors) + { + if (!ObjectEditorInstance->CloseWindow()) + { + bClosedAllEditors = false; + } + } + } + + // Failed to close at least one editor. It is possible that this editor has in-memory object references + // which are not prepared to be changed dynamically so it is not safe to continue + if (!bClosedAllEditors) { - // Failed to close at least one editor. It is possible that this editor has in-memory object references - // which are not prepared to be changed dynamically so it is not safe to continue return 0; } diff --git a/Engine/Source/Editor/UnrealEd/Private/PlayLevel.cpp b/Engine/Source/Editor/UnrealEd/Private/PlayLevel.cpp index 4f1400f0ce24..233e6c41721f 100644 --- a/Engine/Source/Editor/UnrealEd/Private/PlayLevel.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/PlayLevel.cpp @@ -16,7 +16,7 @@ #include "UObject/Package.h" #include "UObject/LazyObjectPtr.h" #include "UObject/SoftObjectPtr.h" -#include "Serialization/ArchiveTraceRoute.h" +#include "UObject/ReferenceChainSearch.h" #include "Misc/PackageName.h" #include "InputCoreTypes.h" #include "Layout/Margin.h" @@ -91,6 +91,8 @@ #include "Engine/LocalPlayer.h" #include "Slate/SGameLayerManager.h" #include "HAL/PlatformApplicationMisc.h" +#include "Widgets/Input/SHyperlink.h" +#include "Dialogs/CustomDialog.h" #include "IHeadMountedDisplay.h" #include "IXRTrackingSystem.h" @@ -104,6 +106,8 @@ #include "EditorModeRegistry.h" #include "PhysicsManipulationMode.h" #include "CookerSettings.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/SBoxPanel.h" DEFINE_LOG_CATEGORY_STATIC(LogPlayLevel, Log, All); @@ -486,11 +490,10 @@ void UEditorEngine::EndPlayMap() UE_LOG(LogPlayLevel, Error, TEXT("No PIE world was found when attempting to gather references after GC.")); } - TMap Route = FArchiveTraceRoute::FindShortestRootPath( Object, true, GARBAGE_COLLECTION_KEEPFLAGS ); - FString ErrorString = FArchiveTraceRoute::PrintRootPath( Route, Object ); + FReferenceChainSearch RefChainSearch(Object, EReferenceChainSearchMode::Shortest); FFormatNamedArguments Arguments; - Arguments.Add(TEXT("Path"), FText::FromString(ErrorString)); + Arguments.Add(TEXT("Path"), FText::FromString(RefChainSearch.GetRootPath())); // We cannot safely recover from this. FMessageLog(NAME_CategoryPIE).CriticalError() @@ -1744,12 +1747,43 @@ struct FInternalPlayLevelUtils { static int32 ResolveDirtyBlueprints(const bool bPromptForCompile, TArray& ErroredBlueprints, const bool bForceLevelScriptRecompile = true) { + struct FLocal + { + static void OnMessageLogLinkActivated(const class TSharedRef& Token) + { + if (Token->GetType() == EMessageToken::Object) + { + const TSharedRef UObjectToken = StaticCastSharedRef(Token); + if (UObjectToken->GetObject().IsValid()) + { + FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(UObjectToken->GetObject().Get()); + } + } + } + + static void AddCompileErrorToLog(UBlueprint* ErroredBlueprint, FMessageLog& BlueprintLog) + { + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("Name"), FText::FromString(ErroredBlueprint->GetName())); + + TSharedRef Message = FTokenizedMessage::Create(EMessageSeverity::Warning); + Message->AddToken(FTextToken::Create(LOCTEXT("BlueprintCompileFailed", "Blueprint failed to compile: "))); + Message->AddToken(FUObjectToken::Create(ErroredBlueprint, FText::FromString(ErroredBlueprint->GetName())) + ->OnMessageTokenActivated(FOnMessageTokenActivated::CreateStatic(&FLocal::OnMessageLogLinkActivated)) + ); + + BlueprintLog.AddMessage(Message); + } + }; + const bool bAutoCompile = !bPromptForCompile; FString PromptDirtyList; TArray InNeedOfRecompile; ErroredBlueprints.Empty(); + FMessageLog BlueprintLog("BlueprintLog"); + double BPRegenStartTime = FPlatformTime::Seconds(); for (TObjectIterator BlueprintIt; BlueprintIt; ++BlueprintIt) { @@ -1778,6 +1812,7 @@ struct FInternalPlayLevelUtils else if (BS_Error == Blueprint->Status && Blueprint->bDisplayCompilePIEWarning) { ErroredBlueprints.Add(Blueprint); + FLocal::AddCompileErrorToLog(Blueprint, BlueprintLog); } } @@ -1793,7 +1828,6 @@ struct FInternalPlayLevelUtils } int32 RecompiledCount = 0; - FMessageLog BlueprintLog("BlueprintLog"); if (bRunCompilation && (InNeedOfRecompile.Num() > 0)) { const FText LogPageLabel = (bAutoCompile) ? LOCTEXT("BlueprintAutoCompilationPageLabel", "Pre-Play auto-recompile") : @@ -1850,11 +1884,7 @@ struct FInternalPlayLevelUtils if (bHadError && ErroredBlueprints.Find(CompiledBlueprint) == INDEX_NONE) { ErroredBlueprints.Add(CompiledBlueprint); - - FFormatNamedArguments Arguments; - Arguments.Add(TEXT("Name"), FText::FromString(CompiledBlueprint->GetName())); - - BlueprintLog.Info(FText::Format(LOCTEXT("BlueprintCompileFailed", "Blueprint {Name} failed to compile"), Arguments)); + FLocal::AddCompileErrorToLog(CompiledBlueprint, BlueprintLog); } ++RecompiledCount; @@ -2244,6 +2274,62 @@ bool UEditorEngine::SpawnPlayFromHereStart( UWorld* World, AActor*& PlayerStart, return true; } +static bool ShowBlueprintErrorDialog( TArray ErroredBlueprints ) +{ + struct Local + { + static void OnHyperlinkClicked( TWeakObjectPtr InBlueprint ) + { + if (UBlueprint* BlueprintToEdit = InBlueprint.Get()) + { + // Open the blueprint + GEditor->EditObject( BlueprintToEdit ); + } + } + }; + + TSharedRef DialogContents = SNew( SVerticalBox ) + + SVerticalBox::Slot() + [ + SNew( STextBlock ) + .Text( NSLOCTEXT( "PlayInEditor", "PrePIE_BlueprintErrors", "One or more blueprints has an unresolved compiler error, are you sure you want to Play in Editor?" ) ) + ] + + SVerticalBox::Slot() + [ + SNew( STextBlock ) + ]; + + for (UBlueprint* Blueprint : ErroredBlueprints) + { + TWeakObjectPtr BlueprintPtr = Blueprint; + + DialogContents->AddSlot() + .AutoHeight() + .HAlign(HAlign_Left) + [ + SNew(SHyperlink) + .Style(FEditorStyle::Get(), "Common.GotoBlueprintHyperlink") + .OnNavigate_Static(&Local::OnHyperlinkClicked, BlueprintPtr) + .Text(FText::FromString(Blueprint->GetName())) + .ToolTipText(NSLOCTEXT("SourceHyperlink", "EditBlueprint_ToolTip", "Click to edit the blueprint")) + ]; + } + + FText DialogTitle = NSLOCTEXT("PlayInEditor", "PrePIE_BlueprintErrorsTitle", "Blueprint Compilation Errors"); + + FText OKText = NSLOCTEXT("PlayInEditor", "PrePIE_OkText", "Play in Editor"); + FText CancelText = NSLOCTEXT("Dialogs", "EAppReturnTypeCancel", "Cancel"); + + TSharedRef CustomDialog = SNew(SCustomDialog) + .Title(DialogTitle) + .IconBrush("NotificationList.DefaultMessage") + .DialogContent(DialogContents) + .Buttons( { SCustomDialog::FButton(OKText), SCustomDialog::FButton(CancelText) } ); + + int ButtonPressed = CustomDialog->ShowModal(); + return ButtonPressed == 0; +} + void UEditorEngine::PlayInEditor( UWorld* InWorld, bool bInSimulateInEditor, FPlayInEditorOverrides Overrides ) { // Broadcast PreBeginPIE before checks that might block PIE below (BeginPIE is broadcast below after the checks) @@ -2331,19 +2417,13 @@ void UEditorEngine::PlayInEditor( UWorld* InWorld, bool bInSimulateInEditor, FPl if (ErroredBlueprints.Num() && !GIsDemoMode) { - FString ErroredBlueprintList; - for (UBlueprint* Blueprint : ErroredBlueprints) - { - ErroredBlueprintList += FString::Printf(TEXT("\n %s"), *Blueprint->GetName()); - } - - FFormatNamedArguments Args; - Args.Add(TEXT("ErrorBlueprints"), FText::FromString(ErroredBlueprintList)); - // There was at least one blueprint with an error, make sure the user is OK with that. - const bool bContinuePIE = EAppReturnType::Yes == FMessageDialog::Open( EAppMsgType::YesNo, FText::Format( NSLOCTEXT("PlayInEditor", "PrePIE_BlueprintErrors", "One or more blueprints has an unresolved compiler error, are you sure you want to Play in Editor?{ErrorBlueprints}"), Args ) ); + bool bContinuePIE = ShowBlueprintErrorDialog( ErroredBlueprints ); + if ( !bContinuePIE ) { + FMessageLog("BlueprintLog").Open(EMessageSeverity::Warning); + FEditorDelegates::EndPIE.Broadcast(bInSimulateInEditor); FNavigationSystem::OnPIEEnd(*InWorld); return; @@ -3283,8 +3363,12 @@ UGameInstance* UEditorEngine::CreatePIEGameInstance(int32 InPIEInstance, bool bI if (index <= 0) { - LevelEditorPlaySettings->NewWindowPosition.X = FPlatformMath::RoundToInt(PIEWindowPos.X); - LevelEditorPlaySettings->NewWindowPosition.Y = FPlatformMath::RoundToInt(PIEWindowPos.Y); + // only override the window position if the window isn't being centered + if (!LevelEditorPlaySettings->CenterNewWindow) + { + LevelEditorPlaySettings->NewWindowPosition.X = FPlatformMath::RoundToInt(PIEWindowPos.X); + LevelEditorPlaySettings->NewWindowPosition.Y = FPlatformMath::RoundToInt(PIEWindowPos.Y); + } } else { @@ -3336,9 +3420,6 @@ UGameInstance* UEditorEngine::CreatePIEGameInstance(int32 InPIEInstance, bool bI // Mark the viewport as PIE viewport ViewportClient->Viewport->SetPlayInEditorViewport( ViewportClient->bIsPlayInEditorViewport ); - // Ensure the window has a valid size before calling BeginPlay - PieWindow->ReshapeWindow(PieWindow->GetPositionInScreen(), FVector2D(NewWindowWidth, NewWindowHeight)); - // Change the system resolution to match our window, to make sure game and slate window are kept syncronised FSystemResolution::RequestResolutionChange(NewWindowWidth, NewWindowHeight, EWindowMode::Windowed); diff --git a/Engine/Source/Editor/UnrealEd/Private/PreferenceStubs.cpp b/Engine/Source/Editor/UnrealEd/Private/PreferenceStubs.cpp index 4e4ad8caaf60..3eb516050ed1 100644 --- a/Engine/Source/Editor/UnrealEd/Private/PreferenceStubs.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/PreferenceStubs.cpp @@ -5,7 +5,9 @@ #include "Preferences/CascadeOptions.h" #include "Preferences/CurveEdOptions.h" #include "Preferences/MaterialEditorOptions.h" +#include "Preferences/BlueprintEditorOptions.h" #include "Preferences/PersonaOptions.h" +#include "Preferences/AnimationBlueprintEditorOptions.h" #include "Preferences/PhysicsAssetEditorOptions.h" #include "Preferences/MaterialStatsOptions.h" @@ -70,6 +72,15 @@ UMaterialStatsOptions::UMaterialStatsOptions(const FObjectInitializer& ObjectIni bMaterialQualityUsed[EMaterialQualityLevel::High] = 1; } +UBlueprintEditorOptions::UBlueprintEditorOptions(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UAnimationBlueprintEditorOptions::UAnimationBlueprintEditorOptions(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} UCurveEdOptions::UCurveEdOptions(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) diff --git a/Engine/Source/Editor/UnrealEd/Private/SCSVImportOptions.cpp b/Engine/Source/Editor/UnrealEd/Private/SCSVImportOptions.cpp index 3b7b2c0fd763..6755c366ee58 100644 --- a/Engine/Source/Editor/UnrealEd/Private/SCSVImportOptions.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/SCSVImportOptions.cpp @@ -5,16 +5,21 @@ #include "UObject/UObjectIterator.h" #include "UObject/Package.h" #include "Widgets/Input/SButton.h" +#include "Widgets/Layout/SExpandableArea.h" #include "EditorStyleSet.h" #include "Engine/UserDefinedStruct.h" - - +#include "Engine/DataTable.h" +#include "Modules/ModuleManager.h" +#include "PropertyEditorModule.h" +#include "ObjectEditorUtils.h" +#include "DataTableEditorUtils.h" #define LOCTEXT_NAMESPACE "CSVImportFactory" void SCSVImportOptions::Construct(const FArguments& InArgs) { WidgetWindow = InArgs._WidgetWindow; + TempImportDataTable = InArgs._TempImportDataTable; // Make array of enum pointers TSharedPtr DataTableTypePtr = MakeShareable(new ECSVImportType(ECSVImportType::ECSV_DataTable)); @@ -23,34 +28,35 @@ void SCSVImportOptions::Construct(const FArguments& InArgs) ImportTypes.Add(MakeShareable(new ECSVImportType(ECSVImportType::ECSV_CurveFloat))); ImportTypes.Add(MakeShareable(new ECSVImportType(ECSVImportType::ECSV_CurveVector))); - // Find table row struct info - UScriptStruct* TableRowStruct = FindObjectChecked(ANY_PACKAGE, TEXT("TableRowBase")); - if (TableRowStruct != nullptr) + // Create properties view + FPropertyEditorModule & EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor"); + FDetailsViewArgs DetailsViewArgs(/*bUpdateFromSelection=*/ false, /*bLockable=*/ false, /*bAllowSearch=*/ false, /*InNameAreaSettings=*/ FDetailsViewArgs::HideNameArea, /*bHideSelectionTip=*/ true); + PropertyView = EditModule.CreateDetailView(DetailsViewArgs); + + PropertyView->SetIsPropertyVisibleDelegate(FIsPropertyVisible::CreateLambda([](const FPropertyAndParent& InPropertyAndParent) { - // Make combo of table rowstruct options - for (TObjectIterator It; It; ++It) + static FName ImportOptions = FName(TEXT("ImportOptions")); + + // Only show import options + FName CategoryName = FObjectEditorUtils::GetCategoryFName(&InPropertyAndParent.Property); + + if (CategoryName == ImportOptions) { - UScriptStruct* Struct = *It; - // If a child of the table row struct base, but not itself - const bool bBasedOnTableRowBase = Struct->IsChildOf(TableRowStruct) && (Struct != TableRowStruct); - const bool bUDStruct = Struct->IsA(); - const bool bValidStruct = (Struct->GetOutermost() != GetTransientPackage()); - if ((bBasedOnTableRowBase || bUDStruct) && bValidStruct) - { - RowStructs.Add(Struct); - } + return true; } - // Alphabetically sort the row structs by name - RowStructs.Sort([](const UScriptStruct& ElementA, const UScriptStruct& ElementB) { return (ElementA.GetName() < ElementB.GetName()); } ); - } + return false; + })); + + RowStructCombo = FDataTableEditorUtils::MakeRowStructureComboBox(FDataTableEditorUtils::FOnDataTableStructSelected::CreateSP(this, &SCSVImportOptions::OnStructSelected)); + RowStructCombo->SetVisibility(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &SCSVImportOptions::GetTableRowOptionVis))); // Create widget this->ChildSlot [ SNew(SBorder) - . BorderImage(FEditorStyle::GetBrush(TEXT("Menu.Background"))) - . Padding(10) + .BorderImage(FEditorStyle::GetBrush(TEXT("Menu.Background"))) + .Padding(10) [ SNew(SVerticalBox) +SVerticalBox::Slot() @@ -95,6 +101,7 @@ void SCSVImportOptions::Construct(const FArguments& InArgs) SAssignNew(ImportTypeCombo, SComboBox< TSharedPtr >) .OptionsSource( &ImportTypes ) .OnGenerateWidget( this, &SCSVImportOptions::MakeImportTypeItemWidget ) + .OnSelectionChanged( this, &SCSVImportOptions::OnImportTypeSelected) [ SNew(STextBlock) .Text(this, &SCSVImportOptions::GetSelectedItemText) @@ -103,6 +110,7 @@ void SCSVImportOptions::Construct(const FArguments& InArgs) // Data row struct +SVerticalBox::Slot() .AutoHeight() + .Padding(2) [ SNew(STextBlock) .Text( LOCTEXT("ChooseRowType", "Choose DataTable Row Type:") ) @@ -111,18 +119,12 @@ void SCSVImportOptions::Construct(const FArguments& InArgs) +SVerticalBox::Slot() .AutoHeight() [ - SAssignNew(RowStructCombo, SComboBox) - .OptionsSource( &RowStructs ) - .OnGenerateWidget( this, &SCSVImportOptions::MakeRowStructItemWidget ) - .Visibility( this, &SCSVImportOptions::GetTableRowOptionVis ) - [ - SNew(STextBlock) - .Text(this, &SCSVImportOptions::GetSelectedRowOptionText) - ] + RowStructCombo.ToSharedRef() ] // Curve interpolation +SVerticalBox::Slot() .AutoHeight() + .Padding(2) [ SNew(STextBlock) .Text( LOCTEXT("ChooseCurveType", "Choose Curve Interpolation Type:") ) @@ -140,6 +142,18 @@ void SCSVImportOptions::Construct(const FArguments& InArgs) .Text(this, &SCSVImportOptions::GetSelectedCurveTypeText) ] ] + // Details panel + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2) + [ + SNew(SBox) + .WidthOverride(400) + .Visibility(this, &SCSVImportOptions::GetDetailsPanelVis) + [ + PropertyView.ToSharedRef() + ] + ] // Ok/Cancel +SVerticalBox::Slot() .AutoHeight() @@ -147,6 +161,7 @@ void SCSVImportOptions::Construct(const FArguments& InArgs) SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() + .Padding(2) [ SNew(SButton) .Text(LOCTEXT("OK", "OK")) @@ -155,6 +170,7 @@ void SCSVImportOptions::Construct(const FArguments& InArgs) ] +SHorizontalBox::Slot() .AutoWidth() + .Padding(2) [ SNew(SButton) .Text(LOCTEXT("Cancel", "Cancel")) @@ -166,8 +182,9 @@ void SCSVImportOptions::Construct(const FArguments& InArgs) // set-up selection ImportTypeCombo->SetSelectedItem(DataTableTypePtr); + PropertyView->SetObject(TempImportDataTable.Get()); - // Populate the valid interploation modes + // Populate the valid interpolation modes { CurveInterpModes.Add( MakeShareable( new ERichCurveInterpMode(ERichCurveInterpMode::RCIM_Constant) ) ); CurveInterpModes.Add( MakeShareable( new ERichCurveInterpMode(ERichCurveInterpMode::RCIM_Linear) ) ); @@ -213,6 +230,11 @@ EVisibility SCSVImportOptions::GetCurveTypeVis() const return (ImportTypeCombo.IsValid() && *ImportTypeCombo->GetSelectedItem() == ECSVImportType::ECSV_CurveTable) ? EVisibility::Visible : EVisibility::Collapsed; } +EVisibility SCSVImportOptions::GetDetailsPanelVis() const +{ + return (ImportTypeCombo.IsValid() && *ImportTypeCombo->GetSelectedItem() == ECSVImportType::ECSV_DataTable) ? EVisibility::Visible : EVisibility::Hidden; +} + FString SCSVImportOptions::GetImportTypeText(TSharedPtr Type) const { FString EnumString; @@ -242,12 +264,21 @@ TSharedRef SCSVImportOptions::MakeImportTypeItemWidget(TSharedPtr SCSVImportOptions::MakeRowStructItemWidget(UScriptStruct* Struct) +void SCSVImportOptions::OnImportTypeSelected(TSharedPtr Selection, ESelectInfo::Type SelectionType) { - check(Struct != nullptr); - return SNew(STextBlock) - .Text(FText::FromString(Struct->GetName())); + if (Selection.IsValid() && *Selection == ECSVImportType::ECSV_DataTable) + { + PropertyView->SetObject(TempImportDataTable.Get()); + } + else + { + PropertyView->SetObject(nullptr); + } +} + +void SCSVImportOptions::OnStructSelected(UScriptStruct* NewStruct) +{ + SelectedStruct = NewStruct; } FString SCSVImportOptions::GetCurveTypeText(CurveInterpModePtr InterpMode) const @@ -281,7 +312,6 @@ TSharedRef SCSVImportOptions::MakeCurveTypeWidget(CurveInterpModePtr In /** Called when 'OK' button is pressed */ FReply SCSVImportOptions::OnImport() { - SelectedStruct = RowStructCombo->GetSelectedItem(); SelectedImportType = *ImportTypeCombo->GetSelectedItem(); if (CurveInterpCombo->GetSelectedItem().IsValid()) { @@ -302,7 +332,7 @@ bool SCSVImportOptions::CanImport() const switch (ImportType) { case ECSVImportType::ECSV_DataTable: - return RowStructCombo->GetSelectedItem() != nullptr; + return SelectedStruct != nullptr; break; case ECSVImportType::ECSV_CurveTable: return CurveInterpCombo->GetSelectedItem().IsValid(); @@ -336,14 +366,6 @@ FText SCSVImportOptions::GetSelectedItemText() const : FText::GetEmpty(); } -FText SCSVImportOptions::GetSelectedRowOptionText() const -{ - UScriptStruct* SelectedScript = RowStructCombo->GetSelectedItem(); - return (SelectedScript) - ? FText::FromString(SelectedScript->GetName()) - : FText::GetEmpty(); -} - FText SCSVImportOptions::GetSelectedCurveTypeText() const { CurveInterpModePtr CurveModePtr = CurveInterpCombo->GetSelectedItem(); diff --git a/Engine/Source/Editor/UnrealEd/Private/SCommonEditorViewportToolbarBase.cpp b/Engine/Source/Editor/UnrealEd/Private/SCommonEditorViewportToolbarBase.cpp index 3c953551bbc1..28c35bb85303 100644 --- a/Engine/Source/Editor/UnrealEd/Private/SCommonEditorViewportToolbarBase.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/SCommonEditorViewportToolbarBase.cpp @@ -356,7 +356,7 @@ TSharedRef SCommonEditorViewportToolbarBase::GenerateScreenPercentageMe .MinValue(PreviewScreenPercentageMin) .MaxValue(PreviewScreenPercentageMax) .Value(this, &SCommonEditorViewportToolbarBase::OnGetScreenPercentageValue) - .OnValueChanged(this, &SCommonEditorViewportToolbarBase::OnScreenPercentageValueChanged) + .OnValueChanged(const_cast(this), &SCommonEditorViewportToolbarBase::OnScreenPercentageValueChanged) ] ]; } @@ -395,7 +395,7 @@ TSharedRef SCommonEditorViewportToolbarBase::GenerateFarViewPlaneMenu() .MaxValue(100000.0f) .Font(FEditorStyle::GetFontStyle(TEXT("MenuItem.Font"))) .Value(this, &SCommonEditorViewportToolbarBase::OnGetFarViewPlaneValue) - .OnValueChanged(this, &SCommonEditorViewportToolbarBase::OnFarViewPlaneValueChanged) + .OnValueChanged(const_cast(this), &SCommonEditorViewportToolbarBase::OnFarViewPlaneValueChanged) ] ]; } @@ -429,7 +429,7 @@ TSharedPtr SCommonEditorViewportToolbarBase::GetViewMenuExtender() co TEXT("ViewMode"), EExtensionHook::After, GetInfoProvider().GetViewportWidget()->GetCommandList(), - FMenuExtensionDelegate::CreateSP(this, &SCommonEditorViewportToolbarBase::CreateViewMenuExtensions)); + FMenuExtensionDelegate::CreateSP(const_cast(this), &SCommonEditorViewportToolbarBase::CreateViewMenuExtensions)); return GetCombinedExtenderList(ViewModeExtender); } diff --git a/Engine/Source/Editor/UnrealEd/Private/SCurveEditor.cpp b/Engine/Source/Editor/UnrealEd/Private/SCurveEditor.cpp index b5e60be0b143..352b232af2c8 100644 --- a/Engine/Source/Editor/UnrealEd/Private/SCurveEditor.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/SCurveEditor.cpp @@ -611,7 +611,7 @@ TSharedRef SCurveEditor::CreateCurveSelectionWidget() const [ SNew(SCheckBox) .IsChecked(this, &SCurveEditor::IsCurveVisible, CurveViewModel) - .OnCheckStateChanged(this, &SCurveEditor::OnCurveIsVisibleChanged, CurveViewModel) + .OnCheckStateChanged(const_cast(this), &SCurveEditor::OnCurveIsVisibleChanged, CurveViewModel) .ToolTipText(this, &SCurveEditor::GetIsCurveVisibleToolTip, CurveViewModel) .CheckedImage(FEditorStyle::GetBrush("CurveEd.Visible")) .CheckedHoveredImage(FEditorStyle::GetBrush("CurveEd.VisibleHighlight")) @@ -628,7 +628,7 @@ TSharedRef SCurveEditor::CreateCurveSelectionWidget() const [ SNew(SCheckBox) .IsChecked(this, &SCurveEditor::IsCurveLocked, CurveViewModel) - .OnCheckStateChanged(this, &SCurveEditor::OnCurveIsLockedChanged, CurveViewModel) + .OnCheckStateChanged(const_cast(this), &SCurveEditor::OnCurveIsLockedChanged, CurveViewModel) .ToolTipText(this, &SCurveEditor::GetIsCurveLockedToolTip, CurveViewModel) .CheckedImage(FEditorStyle::GetBrush("CurveEd.Locked")) .CheckedHoveredImage(FEditorStyle::GetBrush("CurveEd.LockedHighlight")) diff --git a/Engine/Source/Editor/UnrealEd/Private/SEditorViewport.cpp b/Engine/Source/Editor/UnrealEd/Private/SEditorViewport.cpp index f849b57515b1..e48d0b0afa09 100644 --- a/Engine/Source/Editor/UnrealEd/Private/SEditorViewport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/SEditorViewport.cpp @@ -571,7 +571,7 @@ TSharedRef SEditorViewport::BuildFixedEV100Menu() const .MinValue(EV100Min) .MaxValue(EV100Max) .Value( this, &SEditorViewport::OnGetFixedEV100Value ) - .OnValueChanged( this, &SEditorViewport::OnFixedEV100ValueChanged ) + .OnValueChanged( const_cast(this), &SEditorViewport::OnFixedEV100ValueChanged ) .ToolTipText(LOCTEXT( "EV100ToolTip", "Sets the exposure value of the camera using the specified EV100. Exposure = 1 / (1.2 * 2^EV100)")) .IsEnabled( this, &SEditorViewport::IsFixedEV100Enabled ) ] diff --git a/Engine/Source/Editor/UnrealEd/Private/SEditorViewportToolBarButton.cpp b/Engine/Source/Editor/UnrealEd/Private/SEditorViewportToolBarButton.cpp index 95a02c60dfd4..a233660ac86b 100644 --- a/Engine/Source/Editor/UnrealEd/Private/SEditorViewportToolBarButton.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/SEditorViewportToolBarButton.cpp @@ -18,7 +18,7 @@ void SEditorViewportToolBarButton::Construct( const FArguments& Declaration ) bool bContentOverride = ContentSlotWidget != SNullWidget::NullWidget; - EUserInterfaceActionType::Type ButtonType = Declaration._ButtonType; + EUserInterfaceActionType ButtonType = Declaration._ButtonType; // The style of the image to show in the button const FName ImageStyleName = Declaration._Image.Get(); diff --git a/Engine/Source/Editor/UnrealEd/Private/SSocketManager.cpp b/Engine/Source/Editor/UnrealEd/Private/SSocketManager.cpp index 677d02b27e25..d35c46030fea 100644 --- a/Engine/Source/Editor/UnrealEd/Private/SSocketManager.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/SSocketManager.cpp @@ -405,7 +405,7 @@ void SSocketManager::CreateSocket() NewSocket->OnPropertyChanged().AddSP( this, &SSocketManager::OnSocketPropertyChanged ); CurrentStaticMesh->PreEditChange(NULL); - CurrentStaticMesh->Sockets.Add(NewSocket); + CurrentStaticMesh->AddSocket(NewSocket); CurrentStaticMesh->PostEditChange(); CurrentStaticMesh->MarkPackageDirty(); @@ -437,7 +437,7 @@ void SSocketManager::DuplicateSelectedSocket() // Add the new socket to the static mesh CurrentStaticMesh->PreEditChange(NULL); - CurrentStaticMesh->Sockets.Add(NewSocket); + CurrentStaticMesh->AddSocket(NewSocket); CurrentStaticMesh->PostEditChange(); CurrentStaticMesh->MarkPackageDirty(); diff --git a/Engine/Source/Editor/UnrealEd/Private/Settings/LevelEditorPlaySettingsCustomization.h b/Engine/Source/Editor/UnrealEd/Private/Settings/LevelEditorPlaySettingsCustomization.h index 1706b0671320..fbab30cc1bf2 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Settings/LevelEditorPlaySettingsCustomization.h +++ b/Engine/Source/Editor/UnrealEd/Private/Settings/LevelEditorPlaySettingsCustomization.h @@ -187,9 +187,9 @@ public: [ SNew(STextBlock) .Font(LayoutBuilder->GetDetailFont()) - .Text(LOCTEXT("CommonResolutionsButtonText", "Common Window Sizes")) + .Text(LOCTEXT("CommonResolutionsButtonText", "Common Resolutions")) ] - .ContentPadding(FMargin(6.0f, 2.0f)) + .ContentPadding(FMargin(6,2)) .MenuContent() [ MakeCommonResolutionsMenu() @@ -197,12 +197,13 @@ public: .ToolTipText(LOCTEXT("CommonResolutionsButtonTooltip", "Pick from a list of common screen resolutions")) ] + SHorizontalBox::Slot() + .Padding(0,0,6,0) .AutoWidth() .VAlign(VAlign_Center) [ SNew(SButton) .OnClicked(this, &SScreenResolutionCustomization::HandleSwapAspectRatioClicked) - .ContentPadding(FMargin(3.0f, 0.0f, 3.0f, 1.0f)) + .ContentPadding(FMargin(3,0,3,1)) .Content() [ SNew(SImage) @@ -221,7 +222,7 @@ public: + SVerticalBox::Slot() .AutoHeight() [ - WindowWidthProperty->CreatePropertyNameWidget(LOCTEXT("WindowWidthLabel", "Window Width")) + WindowWidthProperty->CreatePropertyNameWidget(LOCTEXT("ViewportWidthLabel", "Viewport Width")) ] + SVerticalBox::Slot() [ @@ -229,13 +230,13 @@ public: ] ] + SHorizontalBox::Slot() - .Padding(8.0f, 0.0f, 0.0f, 0.0f) + .Padding(8,0,0,0) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ - WindowHeightProperty->CreatePropertyNameWidget(LOCTEXT("WindowHeightLabel", "Window Height")) + WindowHeightProperty->CreatePropertyNameWidget(LOCTEXT("ViewportHeightLabel", "Viewport Height")) ] + SVerticalBox::Slot() .AutoHeight() @@ -526,7 +527,7 @@ public: } IDetailCategoryBuilder& GameViewportSettings = LayoutBuilder.EditCategory("GameViewportSettings"); { - // new window size + // new window resolution TSharedRef WindowHeightHandle = LayoutBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULevelEditorPlaySettings, NewWindowHeight)); TSharedRef WindowWidthHandle = LayoutBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULevelEditorPlaySettings, NewWindowWidth)); TSharedRef WindowPositionHandle = LayoutBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULevelEditorPlaySettings, NewWindowPosition)); @@ -539,12 +540,12 @@ public: CenterNewWindowHandle->MarkHiddenByCustomization(); EmulatedDeviceHandle->MarkHiddenByCustomization(); - GameViewportSettings.AddCustomRow(LOCTEXT("NewWindowSizeRow", "New Window Size"), false) + GameViewportSettings.AddCustomRow(LOCTEXT("NewViewportResolutionRow", "New Viewport Resolution"), false) .NameContent() [ SNew(STextBlock) .Font(LayoutBuilder.GetDetailFont()) - .Text(LOCTEXT("NewWindowSizeName", "New Window Size")) + .Text(LOCTEXT("NewViewportResolutionName", "New Viewport Resolution")) .ToolTipText(LOCTEXT("NewWindowSizeTooltip", "Sets the width and height of floating PIE windows (in pixels)")) ] .ValueContent() @@ -553,7 +554,7 @@ public: SNew(SScreenResolutionCustomization, &LayoutBuilder, WindowHeightHandle, WindowWidthHandle) ]; - GameViewportSettings.AddCustomRow(LOCTEXT("NewWindowPositionRow", "New Window Position"), false) + GameViewportSettings.AddCustomRow(LOCTEXT("NewWindowPositionRow", "New Window Position"), false) .NameContent() [ SNew(STextBlock) @@ -567,19 +568,19 @@ public: SNew(SScreenPositionCustomization, &LayoutBuilder, WindowPositionHandle, CenterNewWindowHandle) ]; - GameViewportSettings.AddCustomRow(LOCTEXT("SafeZonePreviewName", "Safe Zone Preview"), false) - .NameContent() - [ - SNew(STextBlock) - .Font(LayoutBuilder.GetDetailFont()) - .Text(LOCTEXT("SafeZonePreviewName", "Safe Zone Preview")) - ] - .ValueContent() - [ - SNew(STextBlock) - .Font(LayoutBuilder.GetDetailFont()) - .Text(this, &FLevelEditorPlaySettingsCustomization::GetPreviewText) - ]; + GameViewportSettings.AddCustomRow(LOCTEXT("SafeZonePreviewName", "Safe Zone Preview"), false) + .NameContent() + [ + SNew(STextBlock) + .Font(LayoutBuilder.GetDetailFont()) + .Text(LOCTEXT("SafeZonePreviewName", "Safe Zone Preview")) + ] + .ValueContent() + [ + SNew(STextBlock) + .Font(LayoutBuilder.GetDetailFont()) + .Text(this, &FLevelEditorPlaySettingsCustomization::GetPreviewText) + ]; } // play in new window settings @@ -699,10 +700,10 @@ public: .DisplayName(LOCTEXT("AdditionalLaunchOptionsLabel", "Command Line Arguments")) .Visibility(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FLevelEditorPlaySettingsCustomization::HandleCmdLineVisibility))); - NetworkCategory.AddCustomRow(LOCTEXT("PlayInNetworkWindowDetails", "Multiplayer Window Size"), false) + NetworkCategory.AddCustomRow(LOCTEXT("PlayInNetworkViewportSize", "Multiplayer Viewport Size"), false) .NameContent() [ - WindowHeightHandle->CreatePropertyNameWidget(LOCTEXT("ClientWindowSizeName", "Multiplayer Window Size (in pixels)"), LOCTEXT("ClientWindowSizeTooltip", "Width and Height to use when spawning additional windows.")) + WindowHeightHandle->CreatePropertyNameWidget(LOCTEXT("ClientViewportSizeName", "Multiplayer Viewport Size (in pixels)"), LOCTEXT("ClientWindowSizeTooltip", "Width and Height to use when spawning additional windows.")) ] .ValueContent() .MaxDesiredWidth(MaxPropertyWidth) diff --git a/Engine/Source/Editor/UnrealEd/Private/Settings/SettingsClasses.cpp b/Engine/Source/Editor/UnrealEd/Private/Settings/SettingsClasses.cpp index 41c9f8ee0f79..e71ceebebd9f 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Settings/SettingsClasses.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Settings/SettingsClasses.cpp @@ -348,6 +348,7 @@ ULevelEditorMiscSettings::ULevelEditorMiscSettings( const FObjectInitializer& Ob bPromptWhenAddingToLevelBeforeCheckout = true; bPromptWhenAddingToLevelOutsideBounds = true; PercentageThresholdForPrompt = 20.0f; + MinimumBoundsForCheckingSize = FVector(500.0f, 500.0f, 50.0f); bCreateNewAudioDeviceForPlayInEditor = true; } diff --git a/Engine/Source/Editor/UnrealEd/Private/SkeletalMeshEdit.cpp b/Engine/Source/Editor/UnrealEd/Private/SkeletalMeshEdit.cpp index 85d171b2b5d9..b7a7af449e15 100644 --- a/Engine/Source/Editor/UnrealEd/Private/SkeletalMeshEdit.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/SkeletalMeshEdit.cpp @@ -1317,22 +1317,26 @@ bool UnFbx::FFbxImporter::ImportAnimation(USkeleton* Skeleton, UAnimSequence * D USkeleton* MySkeleton = DestSeq->GetSkeleton(); check(MySkeleton); - if (ImportOptions->bDeleteExistingMorphTargetCurves) + if (ImportOptions->bDeleteExistingMorphTargetCurves || ImportOptions->bDeleteExistingCustomAttributeCurves) { for (int32 CurveIdx=0; CurveIdxRawCurveData.FloatCurves.Num(); ++CurveIdx) { auto& Curve = DestSeq->RawCurveData.FloatCurves[CurveIdx]; const FCurveMetaData* MetaData = MySkeleton->GetCurveMetaData(Curve.Name); - if (MetaData && MetaData->Type.bMorphtarget) + if (MetaData) { - DestSeq->RawCurveData.FloatCurves.RemoveAt(CurveIdx, 1, false); - --CurveIdx; + bool bDeleteCurve = MetaData->Type.bMorphtarget ? ImportOptions->bDeleteExistingMorphTargetCurves : ImportOptions->bDeleteExistingCustomAttributeCurves; + if (bDeleteCurve) + { + DestSeq->RawCurveData.FloatCurves.RemoveAt(CurveIdx, 1, false); + --CurveIdx; + } } } - DestSeq->RawCurveData.FloatCurves.Shrink(); } + // Store float curve tracks which use to exist on the animation TArray ExistingCurveNames; for (int32 CurveIdx = 0; CurveIdx < DestSeq->RawCurveData.FloatCurves.Num(); ++CurveIdx) diff --git a/Engine/Source/Editor/UnrealEd/Private/StaticMeshEdit.cpp b/Engine/Source/Editor/UnrealEd/Private/StaticMeshEdit.cpp index 26b38f8a1e0c..4ea9ecc49a47 100644 --- a/Engine/Source/Editor/UnrealEd/Private/StaticMeshEdit.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/StaticMeshEdit.cpp @@ -1514,8 +1514,9 @@ void RestoreExistingMeshData(ExistingStaticMeshData* ExistingMeshDataPtr, UStati continue; } + //When re-importing the asset, do not touch the LOD that was imported from file, the material array is keep intact so the section should still be valid. - bool NoRemapForThisLOD = LodLevel == INDEX_NONE && i != 0 && !IsReductionActive(NewMesh->SourceModels[i].ReductionSettings); + bool NoRemapForThisLOD = LodLevel == INDEX_NONE && i != 0 && !NewMesh->SourceModels[i].bImportWithBaseMesh && !IsReductionActive(NewMesh->SourceModels[i].ReductionSettings); FStaticMeshLODResources& LOD = NewMesh->RenderData->LODResources[i]; @@ -1609,7 +1610,7 @@ void RestoreExistingMeshData(ExistingStaticMeshData* ExistingMeshDataPtr, UStati UStaticMeshSocket* Socket = NewMesh->FindSocket(ExistingSocket->SocketName); if (!Socket && !ExistingSocket->bSocketCreatedAtImport) { - NewMesh->Sockets.Add(ExistingSocket); + NewMesh->AddSocket(ExistingSocket); } } diff --git a/Engine/Source/Editor/UnrealEd/Private/Toolkits/AssetEditorManager.cpp b/Engine/Source/Editor/UnrealEd/Private/Toolkits/AssetEditorManager.cpp index 007c21923c21..fd2d9f6dd60e 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Toolkits/AssetEditorManager.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Toolkits/AssetEditorManager.cpp @@ -137,6 +137,10 @@ void FAssetEditorManager::AddReferencedObjects( FReferenceCollector& Collector ) } } +FString FAssetEditorManager::GetReferencerName() const +{ + return TEXT("FAssetEditorManager"); +} IAssetEditorInstance* FAssetEditorManager::FindEditorForAsset(UObject* Asset, bool bFocusIfOpen) { diff --git a/Engine/Source/Editor/UnrealEd/Private/Toolkits/AssetEditorToolkit.cpp b/Engine/Source/Editor/UnrealEd/Private/Toolkits/AssetEditorToolkit.cpp index 21996f8f011c..d907e3330ee8 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Toolkits/AssetEditorToolkit.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Toolkits/AssetEditorToolkit.cpp @@ -1125,6 +1125,10 @@ void FAssetEditorToolkit::FGCEditingObjects::AddReferencedObjects(FReferenceColl OwnerToolkit.EditingObjects.RemoveAllSwap([](UObject* Obj) { return Obj == nullptr; } ); } +FString FAssetEditorToolkit::FGCEditingObjects::GetReferencerName() const +{ + return TEXT("FAssetEditorToolkit::FGCEditorObjects"); +} TSharedPtr FExtensibilityManager::GetAllExtenders() { diff --git a/Engine/Source/Editor/UnrealEd/Private/Toolkits/SimpleAssetEditor.cpp b/Engine/Source/Editor/UnrealEd/Private/Toolkits/SimpleAssetEditor.cpp index cd4aa9eae765..e14d5ef5a3de 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Toolkits/SimpleAssetEditor.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Toolkits/SimpleAssetEditor.cpp @@ -38,6 +38,7 @@ const FName FSimpleAssetEditor::SimpleEditorAppIdentifier( TEXT( "GenericEditorA FSimpleAssetEditor::~FSimpleAssetEditor() { GEditor->GetEditorSubsystem()->OnAssetPostImport.RemoveAll(this); + GEditor->OnObjectsReplaced().RemoveAll(this); DetailsView.Reset(); PropertiesTab.Reset(); @@ -51,7 +52,8 @@ void FSimpleAssetEditor::InitEditor( const EToolkitMode::Type Mode, const TShare const bool bIsLockable = false; EditingObjects = ObjectsToEdit; - GEditor->GetEditorSubsystem()->OnAssetPostImport.AddRaw(this, &FSimpleAssetEditor::HandleAssetPostImport); + GEditor->GetEditorSubsystem()->OnAssetPostImport.AddSP(this, &FSimpleAssetEditor::HandleAssetPostImport); + GEditor->OnObjectsReplaced().AddSP(this, &FSimpleAssetEditor::OnObjectsReplaced); FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked( "PropertyEditor" ); const FDetailsViewArgs DetailsViewArgs( bIsUpdatable, bIsLockable, true, FDetailsViewArgs::ObjectsUseNameArea, false ); @@ -251,6 +253,29 @@ void FSimpleAssetEditor::HandleAssetPostImport(UFactory* InFactory, UObject* InO } } +void FSimpleAssetEditor::OnObjectsReplaced(const TMap& ReplacementMap) +{ + bool bChangedAny = false; + + // Refresh our details view if one of the objects replaced was in the map. This gets called before the reinstance GC fixup, so we might as well fixup EditingObjects now too + for (int32 i = 0; i < EditingObjects.Num(); i++) + { + UObject* SourceObject = EditingObjects[i]; + UObject* ReplacedObject = ReplacementMap.FindRef(SourceObject); + + if (ReplacedObject && ReplacedObject != SourceObject) + { + EditingObjects[i] = ReplacedObject; + bChangedAny = true; + } + } + + if (bChangedAny) + { + DetailsView->SetObjects(EditingObjects); + } +} + FString FSimpleAssetEditor::GetWorldCentricTabPrefix() const { return LOCTEXT("WorldCentricTabPrefix", "Generic Asset ").ToString(); diff --git a/Engine/Source/Editor/UnrealEd/Private/UnrealEdGlobals.cpp b/Engine/Source/Editor/UnrealEd/Private/UnrealEdGlobals.cpp index 232ed7c8bc4b..6c4cd7f9571b 100644 --- a/Engine/Source/Editor/UnrealEd/Private/UnrealEdGlobals.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/UnrealEdGlobals.cpp @@ -171,6 +171,8 @@ int32 EditorInit( IEngineLoop& EngineLoop ) void EditorExit() { + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("EditorExit")); + GLevelEditorModeTools().SetDefaultMode(FBuiltinEditorModes::EM_Default); GLevelEditorModeTools().DeactivateAllModes(); // this also activates the default mode diff --git a/Engine/Source/Editor/UnrealEd/Private/UnrealEdPrivatePCH.h b/Engine/Source/Editor/UnrealEd/Private/UnrealEdPrivatePCH.h index 3b4bac788a01..4a184d76666b 100644 --- a/Engine/Source/Editor/UnrealEd/Private/UnrealEdPrivatePCH.h +++ b/Engine/Source/Editor/UnrealEd/Private/UnrealEdPrivatePCH.h @@ -392,7 +392,6 @@ #include "Framework/Commands/InputBindingManager.h" #include "Framework/Commands/Commands.h" #include "Framework/Text/TextLayout.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/TextRunRenderer.h" #include "Framework/Text/TextLineHighlight.h" #include "Framework/Text/IRun.h" diff --git a/Engine/Source/Editor/UnrealEd/Private/UserDefinedStructEditorData.cpp b/Engine/Source/Editor/UnrealEd/Private/UserDefinedStructEditorData.cpp index 7635b5038787..6aa252e7aeb2 100644 --- a/Engine/Source/Editor/UnrealEd/Private/UserDefinedStructEditorData.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/UserDefinedStructEditorData.cpp @@ -206,7 +206,7 @@ void UUserDefinedStructEditorData::ReinitializeDefaultInstance(FString* OutLog) FStructVariableDescription* VarDesc = VariablesDescriptions.FindByPredicate(FStructureEditorUtils::FFindByGuidHelper(VarGuid)); if (VarDesc && !VarDesc->CurrentDefaultValue.IsEmpty()) { - if (!FBlueprintEditorUtils::PropertyValueFromString(Property, VarDesc->CurrentDefaultValue, StructData)) + if (!FBlueprintEditorUtils::PropertyValueFromString(Property, VarDesc->CurrentDefaultValue, StructData, ScriptStruct)) { const FString Message = FString::Printf(TEXT("Cannot parse value. Property: %s String: \"%s\" ") , *Property->GetDisplayNameText().ToString() diff --git a/Engine/Source/Editor/UnrealEd/Private/VertexSnapping.cpp b/Engine/Source/Editor/UnrealEd/Private/VertexSnapping.cpp index fb2551fba132..e06a661bea1d 100644 --- a/Engine/Source/Editor/UnrealEd/Private/VertexSnapping.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/VertexSnapping.cpp @@ -41,7 +41,7 @@ public: } /** @return True if there are more vertices on the component */ - operator bool() + explicit operator bool() const { return HasMoreVertices(); } @@ -49,18 +49,18 @@ public: /** * @return The position in world space of the current vertex */ - virtual FVector Position() = 0; + virtual FVector Position() const = 0; /** * @return The position in world space of the current vertex normal */ - virtual FVector Normal() = 0; + virtual FVector Normal() const = 0; protected: /** * @return True if there are more vertices on the component */ - virtual bool HasMoreVertices() = 0; + virtual bool HasMoreVertices() const = 0; /** * Advances to the next vertex @@ -85,12 +85,12 @@ public: } /** FVertexIterator interface */ - virtual FVector Position() override + virtual FVector Position() const override { return StaticMeshComponent->GetComponentTransform().TransformPosition( PositionBuffer.VertexPosition( CurrentVertexIndex ) ); } - virtual FVector Normal() override + virtual FVector Normal() const override { return ComponentToWorldIT.TransformVector( VertexBuffer.VertexTangentZ( CurrentVertexIndex ) ); } @@ -101,7 +101,7 @@ protected: ++CurrentVertexIndex; } - virtual bool HasMoreVertices() override + virtual bool HasMoreVertices() const override { return CurrentVertexIndex < PositionBuffer.GetNumVertices(); } @@ -141,13 +141,13 @@ public: } /** FVertexIterator interface */ - virtual FVector Position() override + virtual FVector Position() const override { return BrushComponent->GetComponentTransform().TransformPosition( Vertices[CurrentVertexIndex] ); } /** FVertexIterator interface */ - virtual FVector Normal() override + virtual FVector Normal() const override { return FVector::ZeroVector; } @@ -158,7 +158,7 @@ protected: ++CurrentVertexIndex; } - virtual bool HasMoreVertices() override + virtual bool HasMoreVertices() const override { return Vertices.IsValidIndex( CurrentVertexIndex ); } @@ -188,13 +188,13 @@ public: } /** FVertexIterator interface */ - virtual FVector Position() override + virtual FVector Position() const override { const FVector VertPos = LODData.StaticVertexBuffers.PositionVertexBuffer.VertexPosition(VertexIndex); return SkinnedMeshComponent->GetComponentTransform().TransformPosition(VertPos); } - virtual FVector Normal() override + virtual FVector Normal() const override { FPackedNormal TangentZ = LODData.StaticVertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(VertexIndex); FVector VertNormal = TangentZ.ToFVector(); @@ -207,7 +207,7 @@ protected: VertexIndex++; } - virtual bool HasMoreVertices() override + virtual bool HasMoreVertices() const override { return VertexIndex < LODData.StaticVertexBuffers.PositionVertexBuffer.GetNumVertices(); } diff --git a/Engine/Source/Editor/UnrealEd/Public/Commandlets/AssetRegistryGenerator.h b/Engine/Source/Editor/UnrealEd/Public/Commandlets/AssetRegistryGenerator.h index 85d17599aa92..11c5f727250b 100644 --- a/Engine/Source/Editor/UnrealEd/Public/Commandlets/AssetRegistryGenerator.h +++ b/Engine/Source/Editor/UnrealEd/Public/Commandlets/AssetRegistryGenerator.h @@ -367,4 +367,4 @@ private: /** Initialize ChunkIdPakchunkIndexMapping and PakchunkIndexChunkIdMapping. */ void InitializeChunkIdPakchunkIndexMapping(); -}; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/UnrealEd/Public/Commandlets/EditorCommandlets.h b/Engine/Source/Editor/UnrealEd/Public/Commandlets/EditorCommandlets.h index 7b6621d59fbd..9f76c7ac6d42 100644 --- a/Engine/Source/Editor/UnrealEd/Public/Commandlets/EditorCommandlets.h +++ b/Engine/Source/Editor/UnrealEd/Public/Commandlets/EditorCommandlets.h @@ -263,7 +263,7 @@ struct FNativePropertyData } /** bool operator */ - inline operator bool() const + inline explicit operator bool() const { return PropertyData.Num() || PropertyText.Num(); } diff --git a/Engine/Source/Editor/UnrealEd/Public/DataTableEditorUtils.h b/Engine/Source/Editor/UnrealEd/Public/DataTableEditorUtils.h index b5309e286f03..0d4fabd9bd24 100644 --- a/Engine/Source/Editor/UnrealEd/Public/DataTableEditorUtils.h +++ b/Engine/Source/Editor/UnrealEd/Public/DataTableEditorUtils.h @@ -4,6 +4,8 @@ #include "CoreMinimal.h" #include "Engine/DataTable.h" #include "Kismet2/ListenerManager.h" +#include "Widgets/SWidget.h" +#include "AssetData.h" struct FDataTableEditorColumnHeaderData { @@ -71,9 +73,10 @@ struct UNREALED_API FDataTableEditorUtils static bool RemoveRow(UDataTable* DataTable, FName Name); static uint8* AddRow(UDataTable* DataTable, FName RowName); + static uint8* DuplicateRow(UDataTable* DataTable, FName SourceRowName, FName RowName); static bool RenameRow(UDataTable* DataTable, FName OldName, FName NewName); static bool MoveRow(UDataTable* DataTable, FName RowName, ERowMoveDirection Direction, int32 NumRowsToMoveBy = 1); - static bool SelectRow(UDataTable* DataTable, FName RowName); + static bool SelectRow(const UDataTable* DataTable, FName RowName); static bool DiffersFromDefault(UDataTable* DataTable, FName RowName); static bool ResetToDefault(UDataTable* DataTable, FName RowName); @@ -82,7 +85,12 @@ struct UNREALED_API FDataTableEditorUtils static void CacheDataTableForEditing(const UDataTable* DataTable, TArray& OutAvailableColumns, TArray& OutAvailableRows); + /** Returns all script structs that can be used as a data table row. This only includes loaded ones */ static TArray GetPossibleStructs(); + + /** Fills in an array with all possible DataTable structs, unloaded and loaded */ + static void GetPossibleStructAssetData(TArray& StructAssets); + /** Utility function which verifies that the specified struct type is viable for data tables */ static bool IsValidTableStruct(UScriptStruct* Struct); @@ -94,4 +102,10 @@ struct UNREALED_API FDataTableEditorUtils /** Link to variable type doc */ static const FString VariableTypesTooltipDocLink; + + /** Delegate called when a data table struct is selected */ + DECLARE_DELEGATE_OneParam(FOnDataTableStructSelected, UScriptStruct*); + + /** Creates a combo box that allows selecting from the list of possible row structures */ + static TSharedRef MakeRowStructureComboBox(FOnDataTableStructSelected OnSelected); }; diff --git a/Engine/Source/Editor/UnrealEd/Public/Dialogs/CustomDialog.h b/Engine/Source/Editor/UnrealEd/Public/Dialogs/CustomDialog.h new file mode 100644 index 000000000000..04cd6deefa23 --- /dev/null +++ b/Engine/Source/Editor/UnrealEd/Public/Dialogs/CustomDialog.h @@ -0,0 +1,77 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Widgets/SWindow.h" + +/** This is a custom dialog class, which allows any Slate widget to be used as the contents, + * with any number of buttons that have any text. + * It also supports adding a custom icon to the dialog. + * Usage: + * TSharedRef HelloWorldDialog = SNew(SCustomDialog) + .Title(FText("Hello, World!")) + .DialogContent( SNew(SImage).Image(FName("Hello")) ) + .Buttons( { SCustomDialog::FButton(LOCTEXT("OK", "OK"), []() { printf("OK pressed!" ); } ), + SCustomDialog::FButton(LOCTEXT("Cancel", "Cancel") } ); + + // returns 0 when OK is pressed, 1 when Cancel is pressed + int ButtonPressed = CustomDialog->ShowModal(); + */ +class SCustomDialog : public SWindow +{ +public: + struct FButton + { + FButton(const FText& InButtonText, const FSimpleDelegate& InOnClicked = FSimpleDelegate()) + : ButtonText(InButtonText), + OnClicked(InOnClicked) + { + } + + FText ButtonText; + FSimpleDelegate OnClicked; + }; + + SLATE_BEGIN_ARGS(SCustomDialog) + : _UseScrollBox(true) + , _ScrollBoxMaxHeight(300) + { + } + /** Title to display for the dialog. */ + SLATE_ARGUMENT(FText, Title) + + /** Optional icon to display in the dialog. (default: none) */ + SLATE_ARGUMENT(FName, IconBrush) + + /** Should this dialog use a scroll box for over-sized content? (default: true) */ + SLATE_ARGUMENT(bool, UseScrollBox) + + /** Max height for the scroll box (default: 300) */ + SLATE_ARGUMENT(int32, ScrollBoxMaxHeight) + + /** The buttons that this dialog should have. One or more buttons must be added.*/ + SLATE_ARGUMENT(TArray, Buttons) + + /** Content for the dialog */ + SLATE_ARGUMENT(TSharedPtr, DialogContent) + + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs); + + /** Show the dialog. + * This method will return immediately. + */ + void Show(); + + /** Show a modal dialog. Will block until an input is received. + * Returns the index of the button that was pressed. + */ + int ShowModal(); + +private: + FReply OnButtonClicked(FSimpleDelegate OnClicked, int ButtonIndex); + + /** The index of the button that was pressed last. */ + int LastPressedButton = -1; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/UnrealEd/Public/EdMode.h b/Engine/Source/Editor/UnrealEd/Public/EdMode.h index 80f053b20592..03cc7eb6569f 100644 --- a/Engine/Source/Editor/UnrealEd/Public/EdMode.h +++ b/Engine/Source/Editor/UnrealEd/Public/EdMode.h @@ -278,12 +278,22 @@ public: /** True if this mode uses a toolkit mode (eventually they all should) */ virtual bool UsesToolkits() const; + /** Gets the toolkit created by this mode */ + TSharedPtr GetToolkit() { return Toolkit; } + /** Returns the world this toolkit is editing */ UWorld* GetWorld() const; /** Returns the owning mode manager for this mode */ class FEditorModeTools* GetModeManager() const; + /** + * Called when the editor mode should rebuild its toolbar + * + * @param ToolbarBuilder The builder which should be used to add toolbar widgets + */ + virtual void BuildModeToolbar(class FToolBarBuilder& ToolbarBuilder) {} + // Property Widgets /** diff --git a/Engine/Source/Editor/UnrealEd/Public/Editor.h b/Engine/Source/Editor/UnrealEd/Public/Editor.h index cad2cc5fb997..f1ac2bb38e4f 100644 --- a/Engine/Source/Editor/UnrealEd/Public/Editor.h +++ b/Engine/Source/Editor/UnrealEd/Public/Editor.h @@ -653,6 +653,9 @@ namespace EditorUtilities /** Filters out Blueprint Read-only properties */ FilterBlueprintReadOnly = 1 << 5, + + /** Filters out properties that are marked instance only. */ + SkipInstanceOnlyProperties = 1 << 6, }; } diff --git a/Engine/Source/Editor/UnrealEd/Public/EditorModeManager.h b/Engine/Source/Editor/UnrealEd/Public/EditorModeManager.h index b83d2772564b..7d8893b00a07 100644 --- a/Engine/Source/Editor/UnrealEd/Public/EditorModeManager.h +++ b/Engine/Source/Editor/UnrealEd/Public/EditorModeManager.h @@ -81,11 +81,24 @@ public: */ void DestroyMode(FEditorModeID InID); + /** + * Creates the mode toolbar tab if needed + */ + TSharedRef MakeModeToolbarTab(); + + /** + * Whether or not the mode toolbar should be shown. If any active modes generated a toolbar this method will return true + */ + bool ShouldShowModeToolbar(); + protected: /** Deactivates the editor mode at the specified index */ void DeactivateModeAtIndex( int32 InIndex ); +private: + void RebuildModeToolBar(); + void SpawnOrUpdateModeToolbar(); public: /** @@ -420,6 +433,9 @@ public: /** Is the viewport UI hidden? */ bool IsViewportUIHidden() const { return bHideViewportUI; } + /** The toolbar tab name that should be used as the tab identifier */ + static const FName EditorModeToolbarTabName; + bool PivotShown; bool Snapping; bool SnappedActor; @@ -482,7 +498,7 @@ protected: TArray DefaultModeIDs; /** A list of active editor modes. */ - TArray< TSharedPtr > Modes; + TArray< TSharedPtr > ActiveModes; /** The host of the toolkits created by these modes */ TWeakPtr ToolkitHost; @@ -505,6 +521,18 @@ protected: /** if true the current selection has a scene component */ bool bSelectionHasSceneComponent; private: + struct FEdModeToolbarRow + { + FEdModeToolbarRow(TSharedPtr& InMode, TSharedRef& InToolbarWidget) + : Mode(InMode) + , ToolbarWidget(InToolbarWidget) + {} + TSharedPtr Mode; + TSharedPtr ToolbarWidget; + }; + + /** All toolbar rows generated by active modes. There will be one row per active mode that generates a toolbar */ + TArray ActiveToolBarRows; /** The coordinate system the widget is operating within. */ ECoordSystem CoordSystem; @@ -515,6 +543,12 @@ private: /** Multicast delegate that is broadcast when a widget mode is changed */ FWidgetModeChangedEvent WidgetModeChangedEvent; + /** The dock tab for any modes that generate a toolbar */ + TWeakPtr ModeToolbarTab; + + /** The actual toolbar rows will be placed in this vertical box */ + TWeakPtr ModeToolbarBox; + /** Flag set between calls to StartTracking() and EndTracking() */ bool bIsTracking; }; diff --git a/Engine/Source/Editor/UnrealEd/Public/EditorModeRegistry.h b/Engine/Source/Editor/UnrealEd/Public/EditorModeRegistry.h index 0a2e9ad3c6e0..c9db8e67f345 100644 --- a/Engine/Source/Editor/UnrealEd/Public/EditorModeRegistry.h +++ b/Engine/Source/Editor/UnrealEd/Public/EditorModeRegistry.h @@ -52,6 +52,9 @@ struct FEditorModeInfo /** The mode ID */ FEditorModeID ID; + /** Name of the toolbar this mode uses and can be used by external systems to customize that mode toolbar */ + FName ToolbarCustomizationName; + /** Name for the editor to display */ FText Name; diff --git a/Engine/Source/Editor/UnrealEd/Public/EditorReimportHandler.h b/Engine/Source/Editor/UnrealEd/Public/EditorReimportHandler.h index 24ac90c38277..f7f37611e8f1 100644 --- a/Engine/Source/Editor/UnrealEd/Public/EditorReimportHandler.h +++ b/Engine/Source/Editor/UnrealEd/Public/EditorReimportHandler.h @@ -10,7 +10,7 @@ class FReimportHandler; /** Reimport manager for package resources with associated source files on disk. */ -class FReimportManager : FGCObject +class UNREALED_VTABLE FReimportManager : FGCObject { public: /** @@ -166,7 +166,7 @@ namespace EReimportResult /** * Reimport handler for package resources with associated source files on disk. */ -class FReimportHandler +class UNREALED_VTABLE FReimportHandler { public: /** Constructor. Add self to manager */ diff --git a/Engine/Source/Editor/UnrealEd/Public/EditorViewportClient.h b/Engine/Source/Editor/UnrealEd/Public/EditorViewportClient.h index cb234b983451..22bcda539dd5 100644 --- a/Engine/Source/Editor/UnrealEd/Public/EditorViewportClient.h +++ b/Engine/Source/Editor/UnrealEd/Public/EditorViewportClient.h @@ -495,6 +495,7 @@ public: /** FGCObject interface */ virtual void AddReferencedObjects( FReferenceCollector& Collector ) override; + virtual FString GetReferencerName() const override; /** * Called when the user clicks in the viewport diff --git a/Engine/Source/Editor/UnrealEd/Public/FbxImporter.h b/Engine/Source/Editor/UnrealEd/Public/FbxImporter.h index 3e2ba54af616..1131b66280e6 100644 --- a/Engine/Source/Editor/UnrealEd/Public/FbxImporter.h +++ b/Engine/Source/Editor/UnrealEd/Public/FbxImporter.h @@ -166,6 +166,8 @@ struct FBXImportOptions FString BaseEmmisiveTextureName; FString BaseSpecularTextureName; EMaterialSearchLocation MaterialSearchLocation; + //If true the materials will be reorder to follow the fbx order + bool bReorderMaterialToFbxOrder; // Skeletal Mesh options bool bImportMorph; bool bImportAnimations; @@ -188,6 +190,7 @@ struct FBXImportOptions bool bPreserveLocalTransform; bool bDeleteExistingMorphTargetCurves; bool bImportCustomAttribute; + bool bDeleteExistingCustomAttributeCurves; bool bImportBoneTracks; bool bSetMaterialDriveParameterOnCustomAttribute; bool bRemoveRedundantKeys; @@ -1054,10 +1057,6 @@ public: template static void ShowFbxMaterialConflictWindow(const TArray& InSourceMaterials, const TArray& InResultMaterials, TArray& RemapMaterials, TArray& FuzzyRemapMaterials, EFBXReimportDialogReturnOption& OutReturnOption, bool bIsPreviewConflict = false); - - /** helper function **/ - UNREALED_API static void DumpFBXNode(FbxNode* Node); - /** * Apply asset import settings for transform to an FBX node * @@ -1248,7 +1247,8 @@ protected: { NOTSTARTED, FILEOPENED, - IMPORTED + IMPORTED, + FIXEDANDCONVERTED, }; static TSharedPtr StaticInstance; diff --git a/Engine/Source/Editor/UnrealEd/Public/GraphEditor.h b/Engine/Source/Editor/UnrealEd/Public/GraphEditor.h index a890bbacc739..61398f0d4149 100644 --- a/Engine/Source/Editor/UnrealEd/Public/GraphEditor.h +++ b/Engine/Source/Editor/UnrealEd/Public/GraphEditor.h @@ -86,6 +86,8 @@ public: DECLARE_DELEGATE_RetVal_FiveParams( FActionMenuContent, FOnCreateActionMenu, UEdGraph*, const FVector2D&, const TArray&, bool, FActionMenuClosed ); + DECLARE_DELEGATE_RetVal_FiveParams( FActionMenuContent, FOnCreateNodeOrPinMenu, UEdGraph*, const UEdGraphNode*, const UEdGraphPin*, FMenuBuilder*, bool); + DECLARE_DELEGATE_RetVal_TwoParams( FReply, FOnSpawnNodeByShortcut, FInputChord, const FVector2D& ); DECLARE_DELEGATE( FOnNodeSpawnedByKeymap ); @@ -109,8 +111,10 @@ public: FOnNodeVerifyTextCommit OnVerifyTextCommit; /** Called when text is committed on the graph */ FOnNodeTextCommitted OnTextCommitted; - /** Called to create context menu */ + /** Called to create context menu for right clicking in empty area */ FOnCreateActionMenu OnCreateActionMenu; + /** Called to create context menu for right clicking a node or pin, same parameters as GetContextMenuActions on schema */ + FOnCreateNodeOrPinMenu OnCreateNodeOrPinMenu; /** Called to spawn a node in the graph using a shortcut */ FOnSpawnNodeByShortcut OnSpawnNodeByShortcut; /** Called when a keymap spawns a node */ @@ -457,6 +461,11 @@ public: Implementation->SetNodeFactory(NewNodeFactory); } } + + /** Common methods for MaterialEditor and BlueprintEditor's focusing related nodes feature */ + UNREALED_API void ResetAllNodesUnrelatedStates(); + + UNREALED_API void FocusCommentNodes(TArray &CommentNodes, TArray &RelatedNodes); virtual void OnAlignTop() { diff --git a/Engine/Source/Editor/UnrealEd/Public/Kismet2/BlueprintEditorUtils.h b/Engine/Source/Editor/UnrealEd/Public/Kismet2/BlueprintEditorUtils.h index 40fe1684a615..e45b5f955e7b 100644 --- a/Engine/Source/Editor/UnrealEd/Public/Kismet2/BlueprintEditorUtils.h +++ b/Engine/Source/Editor/UnrealEd/Public/Kismet2/BlueprintEditorUtils.h @@ -1019,6 +1019,14 @@ public: */ static void SetVariableAdvancedDisplayFlag(UBlueprint* InBlueprint, const FName& InVarName, const bool bInIsAdvancedDisplay); + /** + * Sets the Deprecated flag on the variable with the specified name + * + * @param InVarName Name of the var to set the flag on + * @param bInIsDeprecated The new value to set the bitflag to + */ + static void SetVariableDeprecatedFlag(UBlueprint* InBlueprint, const FName& InVarName, const bool bInIsDeprecated); + /** Sets a metadata key/value on the specified variable * * @param Blueprint The Blueprint to find the variable in @@ -1166,16 +1174,16 @@ public: static bool IsVariableUsed(const UBlueprint* Blueprint, const FName& Name, UEdGraph* LocalGraphScope = nullptr); /** Copies the value from the passed in string into a property. ContainerMem points to the Struct or Class containing Property */ - static bool PropertyValueFromString(const UProperty* Property, const FString& StrValue, uint8* Container); + static bool PropertyValueFromString(const UProperty* Property, const FString& StrValue, uint8* Container, UObject* OwningObject = nullptr); /** Copies the value from the passed in string into a property. DirectValue is the raw memory address of the property value */ - static bool PropertyValueFromString_Direct(const UProperty* Property, const FString& StrValue, uint8* DirectValue); + static bool PropertyValueFromString_Direct(const UProperty* Property, const FString& StrValue, uint8* DirectValue, UObject* OwningObject = nullptr); /** Copies the value from a property into the string OutForm. ContainerMem points to the Struct or Class containing Property */ - static bool PropertyValueToString(const UProperty* Property, const uint8* Container, FString& OutForm); + static bool PropertyValueToString(const UProperty* Property, const uint8* Container, FString& OutForm, UObject* OwningObject = nullptr); /** Copies the value from a property into the string OutForm. DirectValue is the raw memory address of the property value */ - static bool PropertyValueToString_Direct(const UProperty* Property, const uint8* DirectValue, FString& OutForm); + static bool PropertyValueToString_Direct(const UProperty* Property, const uint8* DirectValue, FString& OutForm, UObject* OwningObject = nullptr); /** Call PostEditChange() on all Actors based on the given Blueprint */ static void PostEditChangeBlueprintActors(UBlueprint* Blueprint, bool bComponentEditChange = false); @@ -1618,6 +1626,14 @@ public: */ static FString GetClassNameWithoutSuffix(const UClass* Class); + /** + * Returns a formatted warning message regarding usage of a deprecated variable or function member with the given name. + * + * @param MemberName (Required) User-facing name of the deprecated variable or function. + * @param DetailedMessage (Optional) Instructional text or other details from the owner. If empty, a default message will be used. + */ + static FText GetDeprecatedMemberUsageNodeWarning(const FText& MemberName, const FText& DetailedMessage); + /** * Remove overridden component templates from instance component handlers when a parent class disables editable when inherited boolean. */ diff --git a/Engine/Source/Editor/UnrealEd/Public/Kismet2/ComponentEditorUtils.h b/Engine/Source/Editor/UnrealEd/Public/Kismet2/ComponentEditorUtils.h index 63834fd4e25e..44941144dfc4 100644 --- a/Engine/Source/Editor/UnrealEd/Public/Kismet2/ComponentEditorUtils.h +++ b/Engine/Source/Editor/UnrealEd/Public/Kismet2/ComponentEditorUtils.h @@ -244,6 +244,14 @@ public: */ static void FillComponentContextMenuOptions(FMenuBuilder& MenuBuilder, const TArray& SelectedComponents); + /** + * Tries to find a match for ComponentInstance in the ComponentList. First by name and then if multiple Components have a matching name try to match the SceneComponent hierarchy to find the best match. + * @param ComponentInstance Component we are trying to match in the ComponentList + * @param ComponentList List containing possible matches + * @return Valid Component pointer if match was found. nullptr otherwise. + */ + static UActorComponent* FindMatchingComponent(UActorComponent* ComponentInstance, const TInlineComponentArray& ComponentList); + private: static USceneComponent* FindClosestParentInList(UActorComponent* ChildComponent, const TArray& ComponentList); diff --git a/Engine/Source/Editor/UnrealEd/Public/Kismet2/StructureEditorUtils.h b/Engine/Source/Editor/UnrealEd/Public/Kismet2/StructureEditorUtils.h index eaaae94ad884..ef5016fb6a4b 100644 --- a/Engine/Source/Editor/UnrealEd/Public/Kismet2/StructureEditorUtils.h +++ b/Engine/Source/Editor/UnrealEd/Public/Kismet2/StructureEditorUtils.h @@ -87,11 +87,13 @@ public: static bool ChangeVariableDefaultValue(UUserDefinedStruct* Struct, FGuid VarGuid, const FString& NewDefaultValue); - static bool IsUniqueVariableDisplayName(const UUserDefinedStruct* Struct, const FString& DisplayName); + static bool IsUniqueVariableFriendlyName(const UUserDefinedStruct* Struct, const FString& DisplayName); - static FString GetVariableDisplayName(const UUserDefinedStruct* Struct, FGuid VarGuid); + static FString GetVariableFriendlyName(const UUserDefinedStruct* Struct, FGuid VarGuid); - static UProperty* GetPropertyByDisplayName(const UUserDefinedStruct* Struct, FString DisplayName); + static FString GetVariableFriendlyNameForProperty(const UUserDefinedStruct* Struct, const UProperty* Property); + + static UProperty* GetPropertyByFriendlyName(const UUserDefinedStruct* Struct, FString DisplayName); static FString GetVariableTooltip(const UUserDefinedStruct* Struct, FGuid VarGuid); @@ -99,6 +101,8 @@ public: static bool ChangeEditableOnBPInstance(UUserDefinedStruct* Struct, FGuid VarGuid, bool bInIsEditable); + static bool ChangeSaveGameEnabled(UUserDefinedStruct* Struct, FGuid VarGuid, bool bInSaveGame); + enum EMoveDirection { MD_Up, diff --git a/Engine/Source/Editor/UnrealEd/Public/SCSVImportOptions.h b/Engine/Source/Editor/UnrealEd/Public/SCSVImportOptions.h index 446aefd43588..c838a476bc45 100644 --- a/Engine/Source/Editor/UnrealEd/Public/SCSVImportOptions.h +++ b/Engine/Source/Editor/UnrealEd/Public/SCSVImportOptions.h @@ -25,16 +25,19 @@ public: SLATE_BEGIN_ARGS(SCSVImportOptions) : _WidgetWindow() , _FullPath() + , _TempImportDataTable() {} SLATE_ARGUMENT(TSharedPtr, WidgetWindow) SLATE_ARGUMENT(FText, FullPath) + SLATE_ARGUMENT(UDataTable*, TempImportDataTable) SLATE_END_ARGS() SCSVImportOptions() : bImport(false) , SelectedImportType(ECSVImportType::ECSV_DataTable) , SelectedStruct(nullptr) + , TempImportDataTable(nullptr) {} void Construct(const FArguments& InArgs); @@ -54,16 +57,22 @@ public: /** Whether to show table row options */ EVisibility GetTableRowOptionVis() const; - /** Whether to show table row options */ + /** Whether to show curve type options */ EVisibility GetCurveTypeVis() const; + /** Whether to show details panel */ + EVisibility GetDetailsPanelVis() const; + FString GetImportTypeText(TSharedPtr Type) const; /** Called to create a widget for each struct */ TSharedRef MakeImportTypeItemWidget(TSharedPtr Type); - /** Called to create a widget for each struct */ - TSharedRef MakeRowStructItemWidget(UScriptStruct* Struct); + /** Called when import type changes */ + void OnImportTypeSelected(TSharedPtr Selection, ESelectInfo::Type SelectionType); + + /** Called when datatable row is selected */ + void OnStructSelected(UScriptStruct* NewStruct); FString GetCurveTypeText(CurveInterpModePtr InterpMode) const; @@ -81,8 +90,6 @@ public: FText GetSelectedItemText() const; - FText GetSelectedRowOptionText() const; - FText GetSelectedCurveTypeText() const; private: @@ -106,18 +113,21 @@ private: // Row type - /** Array of row struct options */ - TArray< UScriptStruct* > RowStructs; - /** The row struct combo box */ - TSharedPtr< SComboBox > RowStructCombo; + TSharedPtr< SWidget > RowStructCombo; /** The selected row struct */ UScriptStruct* SelectedStruct; + /** Temp DataTable to hold import options */ + TWeakObjectPtr< UDataTable > TempImportDataTable; + /** The curve interpolation combo box */ TSharedPtr< SComboBox > CurveInterpCombo; + /** A property view to edit advanced options */ + TSharedPtr< class IDetailsView > PropertyView; + /** All available curve interpolation modes */ TArray< CurveInterpModePtr > CurveInterpModes; diff --git a/Engine/Source/Editor/UnrealEd/Public/SColorGradientEditor.h b/Engine/Source/Editor/UnrealEd/Public/SColorGradientEditor.h index 40a1f5184701..c47b03ebc236 100644 --- a/Engine/Source/Editor/UnrealEd/Public/SColorGradientEditor.h +++ b/Engine/Source/Editor/UnrealEd/Public/SColorGradientEditor.h @@ -17,7 +17,7 @@ class FCurveOwnerInterface; class FPaintArgs; class FSlateWindowElementList; -struct FGradientStopMark +struct UNREALED_API FGradientStopMark { public: diff --git a/Engine/Source/Editor/UnrealEd/Public/SCurveEditor.h b/Engine/Source/Editor/UnrealEd/Public/SCurveEditor.h index 9e5d9c2590f3..73fe09826346 100644 --- a/Engine/Source/Editor/UnrealEd/Public/SCurveEditor.h +++ b/Engine/Source/Editor/UnrealEd/Public/SCurveEditor.h @@ -128,7 +128,7 @@ DECLARE_DELEGATE_TwoParams( FOnSetInputViewRange, float, float ) DECLARE_DELEGATE_TwoParams( FOnSetOutputViewRange, float, float ) DECLARE_DELEGATE_OneParam( FOnSetAreCurvesVisible, bool ) -class SCurveEditor : +class UNREALED_VTABLE SCurveEditor : public SCompoundWidget, public FGCObject, public FEditorUndoClient diff --git a/Engine/Source/Editor/UnrealEd/Public/SEditorViewportToolBarButton.h b/Engine/Source/Editor/UnrealEd/Public/SEditorViewportToolBarButton.h index d8e76e782a42..b06e18443eee 100644 --- a/Engine/Source/Editor/UnrealEd/Public/SEditorViewportToolBarButton.h +++ b/Engine/Source/Editor/UnrealEd/Public/SEditorViewportToolBarButton.h @@ -27,7 +27,7 @@ public: /** Called when the button is clicked */ SLATE_EVENT( FOnClicked, OnClicked ) /** The button type to use */ - SLATE_ARGUMENT( EUserInterfaceActionType::Type, ButtonType ) + SLATE_ARGUMENT( EUserInterfaceActionType, ButtonType ) /** Checked state of the button */ SLATE_ATTRIBUTE( bool, IsChecked ) /** Style name of an image to use. Simple two state images are supported. An image can be different depending on checked/unchecked state */ diff --git a/Engine/Source/Editor/UnrealEd/Public/SScalabilitySettings.h b/Engine/Source/Editor/UnrealEd/Public/SScalabilitySettings.h index c472a381d925..8e59251a680e 100644 --- a/Engine/Source/Editor/UnrealEd/Public/SScalabilitySettings.h +++ b/Engine/Source/Editor/UnrealEd/Public/SScalabilitySettings.h @@ -15,7 +15,7 @@ enum class ECheckBoxState : uint8; * Scalability settings configuration widget **/ -class SScalabilitySettings : public SCompoundWidget +class UNREALED_VTABLE SScalabilitySettings : public SCompoundWidget { public: diff --git a/Engine/Source/Editor/UnrealEd/Public/SkelImport.h b/Engine/Source/Editor/UnrealEd/Public/SkelImport.h index 884584aa8396..c1e499848f03 100644 --- a/Engine/Source/Editor/UnrealEd/Public/SkelImport.h +++ b/Engine/Source/Editor/UnrealEd/Public/SkelImport.h @@ -83,6 +83,8 @@ struct ExistingSkelMeshData FSkeletalMeshSamplingInfo ExistingSamplingInfo; FPerPlatformInt MinLOD; FPerPlatformBool DisableBelowMinLodStripping; + + TMap ExistingAssetUserData; }; /** diff --git a/Engine/Source/Editor/UnrealEd/Public/SourceCodeNavigation.h b/Engine/Source/Editor/UnrealEd/Public/SourceCodeNavigation.h index 11e0e4e1f097..52ac37aa5847 100644 --- a/Engine/Source/Editor/UnrealEd/Public/SourceCodeNavigation.h +++ b/Engine/Source/Editor/UnrealEd/Public/SourceCodeNavigation.h @@ -360,5 +360,5 @@ private: static FSourceFileDatabase Instance; /** Cached result of check for compiler availability. Speeds up performance greatly since BlueprintEditor is checking this on draw. */ - static bool bCachedIsCompilerAvailable; + static bool UNREALED_API bCachedIsCompilerAvailable; }; diff --git a/Engine/Source/Editor/UnrealEd/Public/Toolkits/AssetEditorManager.h b/Engine/Source/Editor/UnrealEd/Public/Toolkits/AssetEditorManager.h index 1de9ff9500fe..adf6949b451d 100644 --- a/Engine/Source/Editor/UnrealEd/Public/Toolkits/AssetEditorManager.h +++ b/Engine/Source/Editor/UnrealEd/Public/Toolkits/AssetEditorManager.h @@ -117,6 +117,7 @@ public: // FGCObject interface virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + virtual FString GetReferencerName() const override; /** Close all open asset editors */ bool CloseAllAssetEditors(); diff --git a/Engine/Source/Editor/UnrealEd/Public/Toolkits/AssetEditorToolkit.h b/Engine/Source/Editor/UnrealEd/Public/Toolkits/AssetEditorToolkit.h index 5ed6aaa31b8c..342b43546700 100644 --- a/Engine/Source/Editor/UnrealEd/Public/Toolkits/AssetEditorToolkit.h +++ b/Engine/Source/Editor/UnrealEd/Public/Toolkits/AssetEditorToolkit.h @@ -339,6 +339,7 @@ private: /** FGCObject interface */ virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + virtual FString GetReferencerName() const override; private: FAssetEditorToolkit& OwnerToolkit; diff --git a/Engine/Source/Editor/UnrealEd/Public/Toolkits/SimpleAssetEditor.h b/Engine/Source/Editor/UnrealEd/Public/Toolkits/SimpleAssetEditor.h index 2a423ba3ebe6..84a00ebf6b11 100644 --- a/Engine/Source/Editor/UnrealEd/Public/Toolkits/SimpleAssetEditor.h +++ b/Engine/Source/Editor/UnrealEd/Public/Toolkits/SimpleAssetEditor.h @@ -52,6 +52,9 @@ private: /** Handles when an asset is imported */ void HandleAssetPostImport(class UFactory* InFactory, UObject* InObject); + /** Called when objects need to be swapped out for new versions, like after a blueprint recompile. */ + void OnObjectsReplaced(const TMap& ReplacementMap); + /** Dockable tab for properties */ TSharedPtr< SDockableTab > PropertiesTab; diff --git a/Engine/Source/Editor/UnrealEd/Public/UnrealEdSharedPCH.h b/Engine/Source/Editor/UnrealEd/Public/UnrealEdSharedPCH.h index e5e5b4210933..94617653cc7b 100644 --- a/Engine/Source/Editor/UnrealEd/Public/UnrealEdSharedPCH.h +++ b/Engine/Source/Editor/UnrealEd/Public/UnrealEdSharedPCH.h @@ -382,7 +382,6 @@ #include "Framework/Commands/UICommandList.h" #include "Framework/MultiBox/MultiBoxExtender.h" #include "Framework/Text/TextLayout.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/TextRunRenderer.h" #include "Framework/Text/TextLineHighlight.h" #include "Framework/Text/IRun.h" diff --git a/Engine/Source/Editor/UnrealEd/UnrealEd.Build.cs b/Engine/Source/Editor/UnrealEd/UnrealEd.Build.cs index 2ea0df8388a7..565c833ea70a 100644 --- a/Engine/Source/Editor/UnrealEd/UnrealEd.Build.cs +++ b/Engine/Source/Editor/UnrealEd/UnrealEd.Build.cs @@ -167,7 +167,8 @@ public class UnrealEd : ModuleRules "PIEPreviewDeviceProfileSelector", "PakFileUtilities", "TimeManagement", - } + "DerivedDataCache", + } ); DynamicallyLoadedModuleNames.AddRange( diff --git a/Engine/Source/Editor/WorkspaceMenuStructure/Private/WorkspaceMenuStructureModule.cpp b/Engine/Source/Editor/WorkspaceMenuStructure/Private/WorkspaceMenuStructureModule.cpp index 7b2b97005d8b..9c3a6b33d779 100644 --- a/Engine/Source/Editor/WorkspaceMenuStructure/Private/WorkspaceMenuStructureModule.cpp +++ b/Engine/Source/Editor/WorkspaceMenuStructure/Private/WorkspaceMenuStructureModule.cpp @@ -76,7 +76,7 @@ public: LevelEditorCategory->ClearItems(); LevelEditorViewportsCategory = LevelEditorCategory->AddGroup(LOCTEXT( "WorkspaceMenu_LevelEditorViewportCategory", "Viewports" ), LOCTEXT( "WorkspaceMenu_LevelEditorViewportCategoryTooltip", "Open a Viewport tab." ), FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports"), true); LevelEditorDetailsCategory = LevelEditorCategory->AddGroup(LOCTEXT("WorkspaceMenu_LevelEditorDetailCategory", "Details" ), LOCTEXT("WorkspaceMenu_LevelEditorDetailCategoryTooltip", "Open a Details tab." ), FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details"), true ); - LevelEditorModesCategory = LevelEditorCategory->AddGroup(LOCTEXT("WorkspaceMenu_LevelEditorToolsCategory", "Tools" ), FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.EditorModes"), true ); + LevelEditorModesCategory = LevelEditorCategory->AddGroup(LOCTEXT("WorkspaceMenu_LevelEditorToolsCategory", "Editor Modes" ), FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.EditorModes"), true ); } void ResetToolsCategory() diff --git a/Engine/Source/Editor/WorldBrowser/Private/LevelCollectionModel.cpp b/Engine/Source/Editor/WorldBrowser/Private/LevelCollectionModel.cpp index 06e5a1c8b96a..ab60238cef40 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/LevelCollectionModel.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/LevelCollectionModel.cpp @@ -794,7 +794,7 @@ void FLevelCollectionModel::CustomizeFileMainMenu(FMenuBuilder& InMenuBuilder) c InMenuBuilder.AddSubMenu( LOCTEXT("SourceControl", "Source Control"), LOCTEXT("SourceControl_ToolTip", "Source Control Options"), - FNewMenuDelegate::CreateSP(this, &FLevelCollectionModel::FillSourceControlSubMenu)); + FNewMenuDelegate::CreateSP(const_cast(this), &FLevelCollectionModel::FillSourceControlSubMenu)); if (AreAnyLevelsSelected()) { @@ -1258,7 +1258,7 @@ void FLevelCollectionModel::SCCDiffAgainstDepot(const FLevelModelList& InList, U { // Try and load that package FText NotMapReason; - UPackage* OldPackage = LoadPackage(NULL, *TempFileName, LOAD_DisableCompileOnLoad); + UPackage* OldPackage = LoadPackage(NULL, *TempFileName, LOAD_ForDiff|LOAD_DisableCompileOnLoad); if(OldPackage != NULL && InEditor->PackageIsAMapFile(*TempFileName, NotMapReason)) { /* Set the revision information*/ diff --git a/Engine/Source/Editor/WorldBrowser/Private/LevelModel.cpp b/Engine/Source/Editor/WorldBrowser/Private/LevelModel.cpp index af8f91859680..0e322c0caf80 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/LevelModel.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/LevelModel.cpp @@ -660,7 +660,7 @@ void FLevelModel::DeselectAllActors() AActor* CurActor = (*It); if (CurActor) { - SelectedActors->Deselect(CurActor); + GEditor->SelectActor(CurActor, false, false); } } } diff --git a/Engine/Source/Editor/WorldBrowser/Private/SWorldHierarchyImpl.cpp b/Engine/Source/Editor/WorldBrowser/Private/SWorldHierarchyImpl.cpp index 60ca584b332a..5ae22d0e068a 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/SWorldHierarchyImpl.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/SWorldHierarchyImpl.cpp @@ -498,7 +498,7 @@ TSharedPtr SWorldHierarchyImpl::ConstructLevelContextMenu() const MenuBuilder.AddSubMenu( LOCTEXT("MoveSelectionTo", "Move To"), LOCTEXT("MoveSelectionTo_Tooltip", "Move selection to another folder"), - FNewMenuDelegate::CreateSP(this, &SWorldHierarchyImpl::FillFoldersSubmenu) + FNewMenuDelegate::CreateSP(const_cast(this), &SWorldHierarchyImpl::FillFoldersSubmenu) ); } @@ -507,7 +507,7 @@ TSharedPtr SWorldHierarchyImpl::ConstructLevelContextMenu() const MenuBuilder.AddSubMenu( LOCTEXT("SelectSubmenu", "Select"), LOCTEXT("SelectSubmenu_Tooltip", "Select child items of the current selection"), - FNewMenuDelegate::CreateSP(this, &SWorldHierarchyImpl::FillSelectionSubmenu) + FNewMenuDelegate::CreateSP(const_cast(this), &SWorldHierarchyImpl::FillSelectionSubmenu) ); } } diff --git a/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCollectionModel.cpp b/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCollectionModel.cpp index 04fe8e835d8e..4fd2e00c7860 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCollectionModel.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCollectionModel.cpp @@ -288,13 +288,13 @@ void FStreamingLevelCollectionModel::BuildHierarchyMenu(FMenuBuilder& InMenuBuil InMenuBuilder.AddSubMenu( LOCTEXT("VisibilityHeader", "Visibility"), LOCTEXT("VisibilitySubMenu_ToolTip", "Selected Level(s) visibility commands"), - FNewMenuDelegate::CreateSP(this, &FStreamingLevelCollectionModel::FillVisibilitySubMenu ) ); + FNewMenuDelegate::CreateSP(const_cast(this), &FStreamingLevelCollectionModel::FillVisibilitySubMenu ) ); // Lock commands InMenuBuilder.AddSubMenu( LOCTEXT("LockHeader", "Lock"), LOCTEXT("LockSubMenu_ToolTip", "Selected Level(s) lock commands"), - FNewMenuDelegate::CreateSP(this, &FStreamingLevelCollectionModel::FillLockSubMenu ) ); + FNewMenuDelegate::CreateSP(const_cast(this), &FStreamingLevelCollectionModel::FillLockSubMenu ) ); // Level streaming specific commands if (AreAnyLevelsSelected() && !(IsOneLevelSelected() && GetSelectedLevels()[0]->IsPersistent())) @@ -304,7 +304,7 @@ void FStreamingLevelCollectionModel::BuildHierarchyMenu(FMenuBuilder& InMenuBuil InMenuBuilder.AddSubMenu( LOCTEXT("LevelsChangeStreamingMethod", "Change Streaming Method"), LOCTEXT("LevelsChangeStreamingMethod_Tooltip", "Changes the streaming method for the selected levels"), - FNewMenuDelegate::CreateRaw(this, &FStreamingLevelCollectionModel::FillSetStreamingMethodSubMenu )); + FNewMenuDelegate::CreateRaw(const_cast(this), &FStreamingLevelCollectionModel::FillSetStreamingMethodSubMenu )); } if (IsOneLevelSelected() && !GetSelectedLevels()[0]->IsPersistent()) @@ -312,7 +312,7 @@ void FStreamingLevelCollectionModel::BuildHierarchyMenu(FMenuBuilder& InMenuBuil InMenuBuilder.AddSubMenu( LOCTEXT("LevelsChangeLightingScenario", "Lighting Scenario"), LOCTEXT("LevelsChangeLightingScenario_Tooltip", "Changes Lighting Scenario Status for the selected level"), - FNewMenuDelegate::CreateRaw(this, &FStreamingLevelCollectionModel::FillChangeLightingScenarioSubMenu )); + FNewMenuDelegate::CreateRaw(const_cast(this), &FStreamingLevelCollectionModel::FillChangeLightingScenarioSubMenu )); } InMenuBuilder.AddMenuEntry(Commands.World_FindInContentBrowser); @@ -375,7 +375,7 @@ void FStreamingLevelCollectionModel::CustomizeFileMainMenu(FMenuBuilder& InMenuB InMenuBuilder.AddSubMenu( LOCTEXT("LevelsStreamingMethod", "Default Streaming Method"), LOCTEXT("LevelsStreamingMethod_Tooltip", "Changes the default streaming method for a new levels"), - FNewMenuDelegate::CreateRaw(this, &FStreamingLevelCollectionModel::FillDefaultStreamingMethodSubMenu ) ); + FNewMenuDelegate::CreateRaw(const_cast(this), &FStreamingLevelCollectionModel::FillDefaultStreamingMethodSubMenu ) ); InMenuBuilder.AddMenuEntry( Commands.World_CreateNewLevel ); InMenuBuilder.AddMenuEntry( Commands.World_AddExistingLevel ); @@ -421,7 +421,7 @@ void FStreamingLevelCollectionModel::CreateNewLevel_Executed() IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked(TEXT("MainFrame")); if (NewLevelDialogModule.CreateAndShowNewLevelDialog(MainFrameModule.GetParentWindow(), TemplateMapPackageName)) { - UPackage* TemplatePackage = LoadPackage(nullptr, *TemplateMapPackageName, LOAD_None); + UPackage* TemplatePackage = TemplateMapPackageName.Len() ? LoadPackage(nullptr, *TemplateMapPackageName, LOAD_None) : nullptr; UWorld* TemplateWorld = TemplatePackage ? UWorld::FindWorldInPackage(TemplatePackage) : nullptr; // Create the new level @@ -496,9 +496,10 @@ void FStreamingLevelCollectionModel::FixupInvalidReference_Executed() void FStreamingLevelCollectionModel::RemoveInvalidSelectedLevels_Executed() { - for (TSharedPtr LevelModel : InvalidSelectedLevels) + // needs to be an index-based iterator b/c we are removing elements based on it + for (int32 LevelIdx = InvalidSelectedLevels.Num() - 1; LevelIdx >= 0; LevelIdx--) { - TSharedPtr TargetModel = StaticCastSharedPtr(LevelModel); + TSharedPtr TargetModel = StaticCastSharedPtr(InvalidSelectedLevels[LevelIdx]); ULevelStreaming* LevelStreaming = TargetModel->GetLevelStreaming().Get(); if (LevelStreaming) diff --git a/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCustomization.cpp b/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCustomization.cpp index 2dc43d78159d..0e402511798a 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCustomization.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCustomization.cpp @@ -67,6 +67,7 @@ void FStreamingLevelCustomization::CustomizeDetails(IDetailLayoutBuilder& Detail .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) .bColorAxisLabels(true) .AllowResponsiveLayout(true) + .AllowSpin(false) .X(this, &FStreamingLevelCustomization::OnGetLevelPosition, 0) .Y(this, &FStreamingLevelCustomization::OnGetLevelPosition, 1) .Z(this, &FStreamingLevelCustomization::OnGetLevelPosition, 2) @@ -281,14 +282,14 @@ FUIAction FStreamingLevelCustomization::CreateCopyAction(ELevelTransformField::T return FUIAction ( - FExecuteAction::CreateSP(this, &FStreamingLevelCustomization::OnCopy, TransformField) + FExecuteAction::CreateSP(const_cast(this), &FStreamingLevelCustomization::OnCopy, TransformField) ); } FUIAction FStreamingLevelCustomization::CreatePasteAction(ELevelTransformField::Type TransformField) const { return - FUIAction(FExecuteAction::CreateSP(this, &FStreamingLevelCustomization::OnPaste, TransformField)); + FUIAction(FExecuteAction::CreateSP(const_cast(this), &FStreamingLevelCustomization::OnPaste, TransformField)); } void FStreamingLevelCustomization::OnSetLevelPosition( float NewValue, ETextCommit::Type CommitInfo, int32 Axis ) diff --git a/Engine/Source/Editor/WorldBrowser/Private/Tiles/STiledLandscapeImportDlg.cpp b/Engine/Source/Editor/WorldBrowser/Private/Tiles/STiledLandscapeImportDlg.cpp index 98e1c3981d2e..bc4dc12684b3 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/Tiles/STiledLandscapeImportDlg.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/Tiles/STiledLandscapeImportDlg.cpp @@ -177,6 +177,7 @@ void STiledLandcapeImportDlg::Construct(const FArguments& InArgs, TSharedPtr 0) { // Tile information(num, resolution) @@ -716,15 +724,15 @@ FText STiledLandcapeImportDlg::GetImportSummaryText() const float WidthX = 0.00001f*ImportSettings.Scale3D.X*WidthInTilesX*ImportSettings.SizeX; float WidthY = 0.00001f*ImportSettings.Scale3D.Y*WidthInTilesY*ImportSettings.SizeX; const FString LandscapeSummary = FString::Printf(TEXT("%.3fx%.3f"), WidthX, WidthY); - - StatusMessage = FText::Format( + + StatusMessageBuilder.AppendLine(FText::Format( LOCTEXT("TiledLandscapeImport_SummaryText", "{0} tiles, {1}km landscape"), FText::FromString(TilesSummary), FText::FromString(LandscapeSummary) - ); + )); } - return StatusMessage; + return StatusMessageBuilder.ToText(); } FText STiledLandcapeImportDlg::GetWeightmapCountText(TSharedPtr InLayerData) const diff --git a/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileCollectionModel.cpp b/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileCollectionModel.cpp index 2f279a7b391c..517b0bfb786f 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileCollectionModel.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileCollectionModel.cpp @@ -344,13 +344,13 @@ void FWorldTileCollectionModel::BuildWorldCompositionMenu(FMenuBuilder& InMenuBu InMenuBuilder.AddSubMenu( LOCTEXT("VisibilityHeader", "Visibility"), LOCTEXT("VisibilitySubMenu_ToolTip", "Selected Level(s) visibility commands"), - FNewMenuDelegate::CreateSP(this, &FWorldTileCollectionModel::FillVisibilitySubMenu ) ); + FNewMenuDelegate::CreateSP(const_cast(this), &FWorldTileCollectionModel::FillVisibilitySubMenu ) ); // Lock commands InMenuBuilder.AddSubMenu( LOCTEXT("LockHeader", "Lock"), LOCTEXT("LockSubMenu_ToolTip", "Selected Level(s) lock commands"), - FNewMenuDelegate::CreateSP(this, &FWorldTileCollectionModel::FillLockSubMenu ) ); + FNewMenuDelegate::CreateSP(const_cast(this), &FWorldTileCollectionModel::FillLockSubMenu ) ); InMenuBuilder.AddMenuEntry(Commands.World_FindInContentBrowser); } @@ -364,7 +364,7 @@ void FWorldTileCollectionModel::BuildWorldCompositionMenu(FMenuBuilder& InMenuBu InMenuBuilder.AddSubMenu( LOCTEXT("Layer_Assign", "Assign to Layer"), FText::GetEmpty(), - FNewMenuDelegate::CreateSP(this, &FWorldTileCollectionModel::FillLayersSubMenu)); + FNewMenuDelegate::CreateSP(const_cast(this), &FWorldTileCollectionModel::FillLayersSubMenu)); } InMenuBuilder.EndSection(); } @@ -410,7 +410,7 @@ void FWorldTileCollectionModel::BuildWorldCompositionMenu(FMenuBuilder& InMenuBu InMenuBuilder.AddSubMenu( LOCTEXT("AddLandscapeLevel", "Add Adjacent Landscape Level"), FText::GetEmpty(), - FNewMenuDelegate::CreateSP(this, &FWorldTileCollectionModel::FillAdjacentLandscapeSubMenu)); + FNewMenuDelegate::CreateSP(const_cast(this), &FWorldTileCollectionModel::FillAdjacentLandscapeSubMenu)); } // Tiled landscape @@ -419,7 +419,7 @@ void FWorldTileCollectionModel::BuildWorldCompositionMenu(FMenuBuilder& InMenuBu InMenuBuilder.AddSubMenu( LOCTEXT("ReimportTiledLandscape", "Reimport Tiled Landscape"), FText::GetEmpty(), - FNewMenuDelegate::CreateSP(this, &FWorldTileCollectionModel::FillReimportTiledLandscapeSubMenu)); + FNewMenuDelegate::CreateSP(const_cast(this), &FWorldTileCollectionModel::FillReimportTiledLandscapeSubMenu)); } InMenuBuilder.EndSection(); @@ -454,13 +454,13 @@ void FWorldTileCollectionModel::BuildHierarchyMenu(FMenuBuilder& InMenuBuilder) InMenuBuilder.AddSubMenu( LOCTEXT("VisibilityHeader", "Visibility"), LOCTEXT("VisibilitySubMenu_ToolTip", "Selected Level(s) visibility commands"), - FNewMenuDelegate::CreateSP(this, &FWorldTileCollectionModel::FillVisibilitySubMenu ) ); + FNewMenuDelegate::CreateSP(const_cast(this), &FWorldTileCollectionModel::FillVisibilitySubMenu ) ); // Lock commands InMenuBuilder.AddSubMenu( LOCTEXT("LockHeader", "Lock"), LOCTEXT("LockSubMenu_ToolTip", "Selected Level(s) lock commands"), - FNewMenuDelegate::CreateSP(this, &FWorldTileCollectionModel::FillLockSubMenu ) ); + FNewMenuDelegate::CreateSP(const_cast(this), &FWorldTileCollectionModel::FillLockSubMenu ) ); InMenuBuilder.AddMenuEntry(Commands.World_FindInContentBrowser); } @@ -474,7 +474,7 @@ void FWorldTileCollectionModel::BuildHierarchyMenu(FMenuBuilder& InMenuBuilder) InMenuBuilder.AddSubMenu( LOCTEXT("WorldLayers", "Assign to Layer"), FText::GetEmpty(), - FNewMenuDelegate::CreateSP(this, &FWorldTileCollectionModel::FillLayersSubMenu) + FNewMenuDelegate::CreateSP(const_cast(this), &FWorldTileCollectionModel::FillLayersSubMenu) ); } InMenuBuilder.EndSection(); @@ -524,7 +524,7 @@ void FWorldTileCollectionModel::FillLayersSubMenu(FMenuBuilder& InMenuBuilder) c FText::FromString((*It).Name), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateSP( - this, &FWorldTileCollectionModel::AssignSelectedLevelsToLayer_Executed, (*It) + const_cast(this), &FWorldTileCollectionModel::AssignSelectedLevelsToLayer_Executed, (*It) ) ) ); @@ -550,7 +550,7 @@ void FWorldTileCollectionModel::FillReimportTiledLandscapeSubMenu(FMenuBuilder& LOCTEXT("Menu_HeightmapTitle", "Heightmap"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateSP( - this, &FWorldTileCollectionModel::ReimportTiledLandscape_Executed, HeightmapLayerName + const_cast(this), &FWorldTileCollectionModel::ReimportTiledLandscape_Executed, HeightmapLayerName ) ) ); @@ -570,7 +570,7 @@ void FWorldTileCollectionModel::FillWeightmapsSubMenu(FMenuBuilder& InMenuBuilde LOCTEXT("Menu_AllWeightmapsTitle", "All Weightmaps"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateSP( - this, &FWorldTileCollectionModel::ReimportTiledLandscape_Executed, FName(NAME_None) + const_cast(this), &FWorldTileCollectionModel::ReimportTiledLandscape_Executed, FName(NAME_None) ) ) ); @@ -596,7 +596,7 @@ void FWorldTileCollectionModel::FillWeightmapsSubMenu(FMenuBuilder& InMenuBuilde FText::FromName(LayerName), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateSP( - this, &FWorldTileCollectionModel::ReimportTiledLandscape_Executed, LayerName + const_cast(this), &FWorldTileCollectionModel::ReimportTiledLandscape_Executed, LayerName ) ) ); diff --git a/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileModel.cpp b/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileModel.cpp index 9038edf785c1..e27ee46b8db2 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileModel.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileModel.cpp @@ -16,6 +16,8 @@ #include "Engine/WorldComposition.h" #include "GameFramework/WorldSettings.h" #include "LandscapeInfo.h" +#include "LandscapeEditorModule.h" +#include "LandscapeFileFormatInterface.h" #include "LandscapeStreamingProxy.h" #include "Landscape.h" @@ -335,15 +337,22 @@ bool FWorldTileModel::IsTiledLandscapeBased() const if (IsLandscapeBased() && !GetLandscape()->ReimportHeightmapFilePath.IsEmpty()) { // Check if single landscape actor resolution matches heightmap file size - IFileManager& FileManager = IFileManager::Get(); - const int64 ImportFileSize = FileManager.FileSize(*GetLandscape()->ReimportHeightmapFilePath); - - FIntRect ComponentsRect = GetLandscape()->GetBoundingRect(); - int64 LandscapeSamples = (int64)(ComponentsRect.Width()+1)*(ComponentsRect.Height()+1); - // Height samples are 2 bytes wide - if (LandscapeSamples*2 == ImportFileSize) + ILandscapeEditorModule& LandscapeEditorModule = FModuleManager::GetModuleChecked("LandscapeEditor"); + const FString TargetExtension = FPaths::GetExtension(GetLandscape()->ReimportHeightmapFilePath, true); + const ILandscapeHeightmapFileFormat* HeightmapFormat = LandscapeEditorModule.GetHeightmapFormatByExtension(*TargetExtension); + + FLandscapeHeightmapInfo HeightmapInfo = HeightmapFormat->Validate(*GetLandscape()->ReimportHeightmapFilePath); + if (HeightmapInfo.ResultCode != ELandscapeImportResult::Error) { - return true; + FIntRect ComponentsRect = GetLandscape()->GetBoundingRect(); + + for (FLandscapeFileResolution& PossibleResolution: HeightmapInfo.PossibleResolutions) + { + if ((PossibleResolution.Width == (ComponentsRect.Width() + 1)) && (PossibleResolution.Height == (ComponentsRect.Height() + 1))) + { + return true; + } + } } } diff --git a/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileModel.h b/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileModel.h index ce48d2e33aaf..2e5624e7e0e0 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileModel.h +++ b/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileModel.h @@ -32,7 +32,7 @@ public: FORCEINLINE bool operator()(const TSharedPtr& A, const TSharedPtr& B) const { - return A->GetLongPackageName() < B->GetLongPackageName(); + return A->GetLongPackageName().LexicalLess(B->GetLongPackageName()); } }; diff --git a/Engine/Source/Editor/WorldBrowser/Private/WorldBrowserModule.cpp b/Engine/Source/Editor/WorldBrowser/Private/WorldBrowserModule.cpp index bc47a910c242..6f6339e80c62 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/WorldBrowserModule.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/WorldBrowserModule.cpp @@ -44,25 +44,22 @@ void FWorldBrowserModule::BuildLevelMenu(FMenuBuilder& MenuBuilder) FLevelModelList ModelList = Model->GetFilteredLevels(); for (TSharedPtr LevelModel : ModelList) { - if (LevelModel->GetLevelObject()) - { - FUIAction Action(FExecuteAction::CreateRaw(this, &FWorldBrowserModule::SetCurrentSublevel, LevelModel->GetLevelObject()), - FCanExecuteAction(), - FIsActionChecked::CreateRaw(this, &FWorldBrowserModule::IsCurrentSublevel, LevelModel->GetLevelObject())); - MenuBuilder.AddMenuEntry(FText::FromString(LevelModel->GetDisplayName()), FText::GetEmpty(), FSlateIcon(), Action, NAME_None, EUserInterfaceActionType::Button); - } + FUIAction Action(FExecuteAction::CreateRaw(this, &FWorldBrowserModule::SetCurrentSublevel, LevelModel), + FCanExecuteAction(), + FIsActionChecked::CreateRaw(this, &FWorldBrowserModule::IsCurrentSublevel, LevelModel)); + MenuBuilder.AddMenuEntry(FText::FromString(LevelModel->GetDisplayName()), FText::GetEmpty(), FSlateIcon(), Action, NAME_None, EUserInterfaceActionType::Button); } } } -bool FWorldBrowserModule::IsCurrentSublevel(ULevel* InLevel) +bool FWorldBrowserModule::IsCurrentSublevel(TSharedPtr InLevelModel) { - return InLevel ? InLevel->IsCurrentLevel() : false; + return InLevelModel->IsCurrent(); } -void FWorldBrowserModule::SetCurrentSublevel(ULevel* InLevel) +void FWorldBrowserModule::SetCurrentSublevel(TSharedPtr InLevelModel) { - EditorLevelUtils::MakeLevelCurrent(InLevel); + InLevelModel->MakeLevelCurrent(); } void FWorldBrowserModule::StartupModule() diff --git a/Engine/Source/Editor/WorldBrowser/Private/WorldTreeItemTypes.cpp b/Engine/Source/Editor/WorldBrowser/Private/WorldTreeItemTypes.cpp index 6bd9ee857c35..7a8118a6b2e3 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/WorldTreeItemTypes.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/WorldTreeItemTypes.cpp @@ -484,7 +484,7 @@ namespace WorldHierarchy const FSlateIcon NewFolderIcon(FEditorStyle::GetStyleSetName(), "WorldBrowser.NewFolderIcon"); FName RootPath = LevelModel.Pin()->GetFolderPath(); - auto NewFolderAction = FExecuteAction::CreateSP(&Hierarchy, &SWorldHierarchyImpl::CreateFolder, LevelModel.Pin(), RootPath); + auto NewFolderAction = FExecuteAction::CreateSP(const_cast(&Hierarchy), &SWorldHierarchyImpl::CreateFolder, LevelModel.Pin(), RootPath); MenuBuilder.AddMenuEntry(LOCTEXT("CreateFolder", "Create Folder"), FText(), NewFolderIcon, FUIAction(NewFolderAction)); } @@ -912,9 +912,9 @@ namespace WorldHierarchy TArray Folders; Folders.Add(AsShared()); - auto NewFolderAction = FExecuteAction::CreateSP(&Hierarchy, &SWorldHierarchyImpl::CreateFolder, RootLevel, Path); - auto RenameFolderAction = FExecuteAction::CreateSP(&Hierarchy, &SWorldHierarchyImpl::InitiateRename, AsShared()); - auto DeleteFolderAction = FExecuteAction::CreateSP(&Hierarchy, &SWorldHierarchyImpl::DeleteFolders, Folders, /*bTransactional*/ true); + auto NewFolderAction = FExecuteAction::CreateSP(const_cast(&Hierarchy), &SWorldHierarchyImpl::CreateFolder, RootLevel, Path); + auto RenameFolderAction = FExecuteAction::CreateSP(const_cast(&Hierarchy), &SWorldHierarchyImpl::InitiateRename, AsShared()); + auto DeleteFolderAction = FExecuteAction::CreateSP(const_cast(&Hierarchy), &SWorldHierarchyImpl::DeleteFolders, Folders, /*bTransactional*/ true); MenuBuilder.AddMenuEntry(LOCTEXT("CreateSubFolder", "Create Subfolder"), FText(), NewFolderIcon, FUIAction(NewFolderAction)); MenuBuilder.AddMenuEntry(LOCTEXT("RenameFolder", "Rename"), FText(), FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.Rename"), FUIAction(RenameFolderAction)); diff --git a/Engine/Source/Editor/WorldBrowser/Public/WorldBrowserModule.h b/Engine/Source/Editor/WorldBrowser/Public/WorldBrowserModule.h index aecbb9ca0961..9750d30ddd2d 100644 --- a/Engine/Source/Editor/WorldBrowser/Public/WorldBrowserModule.h +++ b/Engine/Source/Editor/WorldBrowser/Public/WorldBrowserModule.h @@ -63,8 +63,8 @@ private: /** Fill out the level menu with entries for level operations */ void BuildLevelMenu(FMenuBuilder& MenuBuilder); - bool IsCurrentSublevel(ULevel* InLevel); - void SetCurrentSublevel(ULevel* InLevel); + bool IsCurrentSublevel(TSharedPtr InLevelModel); + void SetCurrentSublevel(TSharedPtr InLevelModel); private: TWeakPtr WorldModel; diff --git a/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Gauntlet.TestExecutor.cs b/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Gauntlet.TestExecutor.cs index 82806d206fe0..11d26dbd94d8 100644 --- a/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Gauntlet.TestExecutor.cs +++ b/Engine/Source/Programs/AutomationTool/Gauntlet/Framework/Gauntlet.TestExecutor.cs @@ -657,19 +657,24 @@ namespace Gauntlet TestResult NodeResult = TestInfo.TestNode.GetTestResult(); TestInfo.FinalResult = (TestInfo.FinalResult != TestResult.Invalid) ? TestInfo.FinalResult : NodeResult; + bool bCanFinalizeTest = true; if (TestInfo.FinalResult == TestResult.WantRetry) { Log.Info("{0} requested retry. Cleaning up old test and relaunching", TestInfo); DateTime OriginalStartTime = TestInfo.PostStartTime; - TestInfo.TestNode.RestartTest(); - - // Mark us as still running - TestInfo.CancellationReason = ""; - TestInfo.FinalResult = TestResult.Invalid; + bool bIsRestarted = TestInfo.TestNode.RestartTest(); + if (bIsRestarted) + { + // Mark us as still running + TestInfo.CancellationReason = ""; + TestInfo.FinalResult = TestResult.Invalid; + bCanFinalizeTest = false; + } } - else + + if (bCanFinalizeTest) { Log.Info("{0} result={1}", TestInfo, TestInfo.FinalResult); @@ -677,16 +682,16 @@ namespace Gauntlet { ReportTestSummary(TestInfo); } - } - // now cleanup - try - { - TestInfo.TestNode.CleanupTest(); - } - catch (System.Exception ex) - { - Log.Error("Test {0} threw an exception while cleaning up. Ex: {1}", TestInfo.TestNode.Name, ex.Message); + // now cleanup + try + { + TestInfo.TestNode.CleanupTest(); + } + catch (System.Exception ex) + { + Log.Error("Test {0} threw an exception while cleaning up. Ex: {1}", TestInfo.TestNode.Name, ex.Message); + } } Log.Info("****************************************************************"); diff --git a/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Base/Gauntlet.UnrealTestNode.cs b/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Base/Gauntlet.UnrealTestNode.cs index 516ee79b5d38..228a37ea3eb0 100644 --- a/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Base/Gauntlet.UnrealTestNode.cs +++ b/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Base/Gauntlet.UnrealTestNode.cs @@ -540,7 +540,13 @@ namespace Gauntlet { TestInstance = UnrealApp.RestartSession(); - return TestInstance != null; + bool bWasRestarted = (TestInstance != null); + if (bWasRestarted) + { + MarkTestStarted(); + } + + return bWasRestarted; } /// diff --git a/Engine/Source/Programs/AutomationTool/Scripts/CopyBuildToStagingDirectory.Automation.cs b/Engine/Source/Programs/AutomationTool/Scripts/CopyBuildToStagingDirectory.Automation.cs index dbc8efc199fb..c93ec3d05bc1 100644 --- a/Engine/Source/Programs/AutomationTool/Scripts/CopyBuildToStagingDirectory.Automation.cs +++ b/Engine/Source/Programs/AutomationTool/Scripts/CopyBuildToStagingDirectory.Automation.cs @@ -2047,13 +2047,17 @@ public partial class Project : CommandUtils string ChunkInstallBasePath = CombinePaths(Params.ChunkInstallDirectory, SC.FinalCookPlatform); string RawDataPath = CombinePaths(ChunkInstallBasePath, VersionString, PakName); string RawDataPakPath = CombinePaths(RawDataPath, PakName + "-" + SC.FinalCookPlatform + PostFix + ".pak"); + bool bPakFilesAreSigned = InternalUtils.SafeFileExists(Path.ChangeExtension(OutputLocation.FullName, ".sig")); + //copy the pak chunk to the raw data folder - if (InternalUtils.SafeFileExists(RawDataPakPath, true)) - { - InternalUtils.SafeDeleteFile(RawDataPakPath, true); - } + InternalUtils.SafeDeleteFile(RawDataPakPath, true); + InternalUtils.SafeDeleteFile(Path.ChangeExtension(RawDataPakPath, ".sig"), true); InternalUtils.SafeCreateDirectory(RawDataPath, true); InternalUtils.SafeCopyFile(OutputLocation.FullName, RawDataPakPath); + if (bPakFilesAreSigned) + { + InternalUtils.SafeCopyFile(Path.ChangeExtension(OutputLocation.FullName, ".sig"), Path.ChangeExtension(RawDataPakPath, ".sig"), true); + } InternalUtils.SafeDeleteFile(OutputLocation.FullName, true); if (Params.IsGeneratingPatch) @@ -2066,6 +2070,10 @@ public partial class Project : CommandUtils // for distribution. string SourceRawDataPakPath = CombinePaths(RawDataPath, PakName + "-" + SC.FinalCookPlatform + ".pak"); InternalUtils.SafeCopyFile(PatchSourceContentPath, SourceRawDataPakPath); + if (bPakFilesAreSigned) + { + InternalUtils.SafeCopyFile(PatchSourceContentPath, Path.ChangeExtension(SourceRawDataPakPath, ".sig"), true); + } } string BuildRoot = MakePathSafeToUseWithCommandLine(RawDataPath); diff --git a/Engine/Source/Programs/CrashReportClient/Private/CrashReportClientApp.cpp b/Engine/Source/Programs/CrashReportClient/Private/CrashReportClientApp.cpp index 7a14f58f8928..b1af00f4a8c7 100644 --- a/Engine/Source/Programs/CrashReportClient/Private/CrashReportClientApp.cpp +++ b/Engine/Source/Programs/CrashReportClient/Private/CrashReportClientApp.cpp @@ -54,6 +54,8 @@ static FString GameNameFromCmd; /** GUID of the crash passed via the command line. */ static FString CrashGUIDFromCmd; +/** If we are implicitly sending its assumed we are also unattended for now */ +static bool bImplicitSendFromCmd = false; /** If we want to enable analytics */ static bool AnalyticsEnabledFromCmd = true; @@ -110,6 +112,11 @@ void ParseCommandLine(const TCHAR* CommandLine) { CrashGUIDFromCmd = Params.FindRef(TEXT("CrashGUID")); } + + if (Switches.Contains(TEXT("ImplicitSend"))) + { + bImplicitSendFromCmd = true; + } if (Switches.Contains(TEXT("NoAnalytics"))) { @@ -300,6 +307,23 @@ bool RunWithUI(FPlatformErrorReport ErrorReport) } #endif // !CRASH_REPORT_UNATTENDED_ONLY +// When we want to implicitly send and use unattended we still want to show a message box of a crash if possible +class FMessageBoxThread : public FRunnable +{ + virtual uint32 Run() override + { + // We will not have any GUI for the crash reporter if we are sending implicitly, so pop a message box up at least + if (FApp::CanEverRender()) + { + FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, + *NSLOCTEXT("MessageDialog", "ReportCrash_Body", "The application has crashed and will now close. We apologize for the inconvenience.").ToString(), + *NSLOCTEXT("MessageDialog", "ReportCrash_Title", "Application Crash Detected").ToString()); + } + + return 0; + } +}; + void RunUnattended(FPlatformErrorReport ErrorReport) { // Set up the main ticker @@ -309,11 +333,24 @@ void RunUnattended(FPlatformErrorReport ErrorReport) FCrashReportCoreUnattended CrashReportClient(ErrorReport); ErrorReport.SetUserComment(NSLOCTEXT("CrashReportClient", "UnattendedMode", "Sent in the unattended mode")); + FMessageBoxThread MessageBox; + FRunnableThread* MessageBoxThread = nullptr; + + if (bImplicitSendFromCmd) + { + MessageBoxThread = FRunnableThread::Create(&MessageBox, TEXT("CrashReporter_MessageBox")); + } + // loop until the app is ready to quit while (!GIsRequestingExit) { MainLoop.Tick(); } + + if (bImplicitSendFromCmd && MessageBoxThread) + { + MessageBoxThread->WaitForCompletion(); + } } diff --git a/Engine/Source/Programs/TestPAL/Private/Main.cpp b/Engine/Source/Programs/TestPAL/Private/Main.cpp index 1a6b66cf5df1..33c0b5b23f8e 100644 --- a/Engine/Source/Programs/TestPAL/Private/Main.cpp +++ b/Engine/Source/Programs/TestPAL/Private/Main.cpp @@ -3,6 +3,7 @@ #include "CoreMinimal.h" #include "TestPALLog.h" #include "Parent.h" +#include "Misc/Guid.h" #include "Stats/StatsMisc.h" #include "HAL/RunnableThread.h" #include "HAL/PlatformApplicationMisc.h" @@ -34,6 +35,8 @@ IMPLEMENT_APPLICATION(TestPAL, "TestPAL"); #define ARG_THREAD_PRIO_TEST "threadpriotest" #define ARG_INLINE_CALLSTACK_TEST "inline" #define ARG_STRINGS_ALLOCATION_TEST "stringsallocation" +#define ARG_CREATEGUID_TEST "createguid" +#define ARG_THREADSTACK_TEST "threadstack" namespace TestPAL { @@ -500,12 +503,31 @@ int32 DynamicLibraryTest(const TCHAR* CommandLine) if (PLATFORM_LINUX) { - RootSteamPath = FPaths::EngineDir() / FString(TEXT("Binaries/ThirdParty/Steamworks/Steamv139/x86_64-unknown-linux-gnu/")); + RootSteamPath = FPaths::EngineDir() / FString(TEXT("Binaries/ThirdParty/Steamworks/*")); + + IFileManager& PlatformFileManager = IFileManager::Get(); + TArray FoundDirectories; + PlatformFileManager.FindFiles(FoundDirectories, *RootSteamPath, false, true); + + // Just use the first directory we find, this test does not have to be very sophisticated. + if (FoundDirectories.Num() > 0) + { + // This only gives us directories, so remove the wildcard from our initial search + RootSteamPath.RemoveFromEnd(TEXT("*")); + // And append the directory name we found. + RootSteamPath += FoundDirectories[0]; + } + else + { + UE_LOG(LogTestPAL, Fatal, TEXT("Could not find any steam versions.")); + } + + RootSteamPath += TEXT("/x86_64-unknown-linux-gnu/"); LibraryName = TEXT("libsteam_api.so"); } else { - UE_LOG(LogTestPAL, Fatal, TEXT("This test is not implemented for this platform.")) + UE_LOG(LogTestPAL, Fatal, TEXT("This test is not implemented for this platform.")); } FPlatformProcess::PushDllDirectory(*RootSteamPath); @@ -520,7 +542,7 @@ int32 DynamicLibraryTest(const TCHAR* CommandLine) if (SteamDLLHandle == nullptr) { - UE_LOG(LogTestPAL, Fatal, TEXT("Could not load Steam library!")) + UE_LOG(LogTestPAL, Fatal, TEXT("Could not load Steam library!")); } } @@ -680,6 +702,135 @@ int32 StringsAllocationTest(const TCHAR* CommandLine) return 0; } +/** + * Thread runnable + * bUseGenericMisc Whether to use FGenericPlatformMisc CreateGuid + */ +template +struct FCreateGuidThread : public FRunnable +{ + /** Number of CreateGuid calls to make. */ + int32 NumCalls; + + FCreateGuidThread(int32 InNumCalls) + : FRunnable() + , NumCalls(InNumCalls) + { + } + + virtual uint32 Run() + { + FGuid Result; + + for (int32 IdxRun = 0; IdxRun < NumCalls; ++IdxRun) + { + if (bUseGenericMisc) + { + FGenericPlatformMisc::CreateGuid(Result); + } + else + { + FPlatformMisc::CreateGuid(Result); + } + } + + return 0; + } +}; + + +/** + * CreateGuid test + */ +int32 CreateGuidTest(const TCHAR* CommandLine) +{ + FPlatformMisc::SetCrashHandler(NULL); + FPlatformMisc::SetGracefulTerminationHandler(); + + GEngineLoop.PreInit(CommandLine); + UE_LOG(LogTestPAL, Display, TEXT("Running CreateGuid test.")); + + TArray RunnableArray; + TArray ThreadArray; + + UE_LOG(LogTestPAL, Display, TEXT("Accepted options:")); + UE_LOG(LogTestPAL, Display, TEXT(" -numthreads=N")); + UE_LOG(LogTestPAL, Display, TEXT(" -numcalls=N (how many times each thread will call CreateGuid)")); + UE_LOG(LogTestPAL, Display, TEXT(" -norandomguids (disable SYS_getrandom syscall)")); + + int32 NumTestThreads = 4, NumCalls = 1000000; + if (FParse::Value(CommandLine, TEXT("numthreads="), NumTestThreads)) + { + NumTestThreads = FMath::Max(1, NumTestThreads); + } + if (FParse::Value(CommandLine, TEXT("numcalls="), NumCalls)) + { + NumCalls = FMath::Max(1, NumCalls); + } + + // start all threads + for (int Idx = 0; Idx < 2; ++Idx ) + { + bool bUseGenericMisc = (Idx == 0); + double WallTimeDuration = FPlatformTime::Seconds(); + + for (int IdxThread = 0; IdxThread < NumTestThreads; ++IdxThread) + { + RunnableArray.Add(bUseGenericMisc ? + static_cast(new FCreateGuidThread(NumCalls)) : + static_cast(new FCreateGuidThread(NumCalls)) ); + + ThreadArray.Add( FRunnableThread::Create(RunnableArray[IdxThread], + *FString::Printf(TEXT("GuidTest%d"), IdxThread)) ); + } + + GLog->FlushThreadedLogs(); + GLog->Flush(); + + // join all threads + for (int IdxThread = 0; IdxThread < NumTestThreads; ++IdxThread) + { + ThreadArray[IdxThread]->WaitForCompletion(); + + delete ThreadArray[IdxThread]; + ThreadArray[IdxThread] = nullptr; + + delete RunnableArray[IdxThread]; + RunnableArray[IdxThread] = nullptr; + } + + WallTimeDuration = FPlatformTime::Seconds() - WallTimeDuration; + + UE_LOG(LogTestPAL, Display, TEXT("--- Results for %s ---"), + bUseGenericMisc ? TEXT("FGenericPlatformMisc::CreateGuid") : TEXT("FPlatformMisc::CreateGuid")); + UE_LOG(LogTestPAL, Display, TEXT("Total wall time: %f seconds, Threads: %d, Calls: %d"), + WallTimeDuration, NumTestThreads, NumCalls); + + ThreadArray.Empty(); + RunnableArray.Empty(); + } + + // Spew out a small sample of GUIDs to visually check we haven't horribly broken something + UE_LOG(LogTestPAL, Display, TEXT("--- CreateGuid Samples ---")); + + for (int Idx = 0; Idx < 20; ++Idx) + { + FGuid GuidPlatform; + FGuid GuidGeneric; + + FPlatformMisc::CreateGuid(GuidPlatform); + FGenericPlatformMisc::CreateGuid(GuidGeneric); + + UE_LOG(LogTestPAL, Display, TEXT("%3d GuidGeneric:%s GuidPlatform:%s"), Idx, + *GuidGeneric.ToString(EGuidFormats::DigitsWithHyphensInBraces), + *GuidPlatform.ToString(EGuidFormats::DigitsWithHyphensInBraces)); + } + + // GMalloc = OldGMalloc; + FEngineLoop::AppExit(); + return 0; +} + /** An ugly way to pass a parameters to FRunnable; shouldn't matter for this test code. */ int32 GMallocTestNumRuns = 500; @@ -1337,6 +1488,86 @@ int32 InlineCallstacksTest(const TCHAR* CommandLine) return 0; } +struct FThreadStackTest : public FRunnable +{ + FThreadStackTest(uint64 InThreadId = ~0) : + ThreadId(InThreadId) + { + } + + uint64 ThreadId; + + virtual uint32 Run() + { + // If we dont have a valid ThreadId lets use the threads id as default + if (ThreadId == ~0) + { + ThreadId = FPlatformTLS::GetCurrentThreadId(); + } + + // Hopefully this wont be to much extra stack space for a testing thread + const SIZE_T StackTraceSize = 65536; + ANSICHAR StackTrace[StackTraceSize] = {0}; + FPlatformStackWalk::ThreadStackWalkAndDump(StackTrace, StackTraceSize, 0, ThreadId); + + UE_LOG(LogTestPAL, Warning, TEXT("***** ThreadStackWalkAndDump for ThreadId(%lu) ******\n%s"), ThreadId, ANSI_TO_TCHAR(StackTrace)); + + const SIZE_T BackTraceSize = 100; + uint64 BackTrace[BackTraceSize]; + int32 BackTraceCount = FPlatformStackWalk::CaptureThreadStackBackTrace(ThreadId, BackTrace, BackTraceSize); + + UE_LOG(LogTestPAL, Warning, TEXT("***** CaptureThreadStackBackTrace for ThreadId(%lu) ******"), ThreadId); + for (int i = 0; i < BackTraceCount; i++) + { + UE_LOG(LogTestPAL, Warning, TEXT("0x%llx"), BackTrace[i]); + } + UE_LOG(LogTestPAL, Warning, TEXT("\n\n")); + + UE_LOG(LogTestPAL, Warning, TEXT("***** ProgramCounterToHumanReadableString for BackTrace for ThreadId(%lu) ******"), ThreadId); + for (int i = 0; i < BackTraceCount; i++) + { + ANSICHAR TempString[1024] = {0}; + FPlatformStackWalk::ProgramCounterToHumanReadableString(i, BackTrace[i], TempString, ARRAY_COUNT(TempString)); + + UE_LOG(LogTestPAL, Warning, TEXT("%s"), ANSI_TO_TCHAR(TempString)); + } + + return 0; + } +}; + + +int32 ThreadTraceTest(const TCHAR* CommandLine) +{ + FPlatformMisc::SetCrashHandler(NULL); + FPlatformMisc::SetGracefulTerminationHandler(); + + GEngineLoop.PreInit(CommandLine); + + // Dump the callstack/backtrace of the thread that is running + { + FThreadStackTest* ThreadStackTest = new FThreadStackTest; + FRunnableThread* Runnable = FRunnableThread::Create(ThreadStackTest, ANSI_TO_TCHAR("ThreadStackTest"), 0); + + Runnable->WaitForCompletion(); + delete ThreadStackTest; + } + + // Dump the GGameThreadId callstack/backtrace from the thread + { + FThreadStackTest* ThreadStackTest = new FThreadStackTest(GGameThreadId); + FRunnableThread* Runnable = FRunnableThread::Create(ThreadStackTest, ANSI_TO_TCHAR("ThreadStackTest"), 0); + + Runnable->WaitForCompletion(); + delete ThreadStackTest; + } + + FEngineLoop::AppPreExit(); + FEngineLoop::AppExit(); + + return 0; +} + /** * Selects and runs one of test cases. * @@ -1418,6 +1649,14 @@ int32 MultiplexedMain(int32 ArgC, char* ArgV[]) { return StringsAllocationTest(*TestPAL::CommandLine); } + else if (!FCStringAnsi::Strcmp(ArgV[IdxArg], ARG_CREATEGUID_TEST)) + { + return CreateGuidTest(*TestPAL::CommandLine); + } + else if (!FCStringAnsi::Strcmp(ArgV[IdxArg], ARG_THREADSTACK_TEST)) + { + return ThreadTraceTest(*TestPAL::CommandLine); + } } FPlatformMisc::SetCrashHandler(NULL); @@ -1443,6 +1682,9 @@ int32 MultiplexedMain(int32 ArgC, char* ArgV[]) UE_LOG(LogTestPAL, Warning, TEXT(" %s: test by replaying a saved malloc history saved by -mallocsavereplay. Possible options: -replayfile=File, -stopafter=N (operation), -suppresserrors"), UTF8_TO_TCHAR(ARG_MALLOC_REPLAY)); UE_LOG(LogTestPAL, Warning, TEXT(" %s: test thread priorities."), UTF8_TO_TCHAR(ARG_THREAD_PRIO_TEST)); UE_LOG(LogTestPAL, Warning, TEXT(" %s: test inline callstacks through ensures and a final crash."), UTF8_TO_TCHAR(ARG_INLINE_CALLSTACK_TEST)); + UE_LOG(LogTestPAL, Warning, TEXT(" %s: test string allocations."), UTF8_TO_TCHAR(ARG_STRINGS_ALLOCATION_TEST)); + UE_LOG(LogTestPAL, Warning, TEXT(" %s: test CreateGuid."), UTF8_TO_TCHAR(ARG_CREATEGUID_TEST)); + UE_LOG(LogTestPAL, Warning, TEXT(" %s: test ThreadWalkStackAndDump and CaptureThreadBackTrace."), UTF8_TO_TCHAR(ARG_THREADSTACK_TEST)); UE_LOG(LogTestPAL, Warning, TEXT("")); UE_LOG(LogTestPAL, Warning, TEXT("Pass one of those to run an appropriate test.")); diff --git a/Engine/Source/Programs/UnrealBuildTool/Configuration/ModuleRules.cs b/Engine/Source/Programs/UnrealBuildTool/Configuration/ModuleRules.cs index 9a745c78a512..d8245ab3d56f 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Configuration/ModuleRules.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Configuration/ModuleRules.cs @@ -562,6 +562,11 @@ namespace UnrealBuildTool /// public bool bAddDefaultIncludePaths = true; + /// + /// Whether to ignore dangling (i.e. unresolved external) symbols in modules + /// + public bool bIgnoreUnresolvedSymbols = false; + /// /// Whether this module should be precompiled. Defaults to the bPrecompile flag from the target. Clear this flag to prevent a module being precompiled. /// diff --git a/Engine/Source/Programs/UnrealBuildTool/Configuration/TargetRules.cs b/Engine/Source/Programs/UnrealBuildTool/Configuration/TargetRules.cs index 1918124a96a6..94128b1de1e3 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Configuration/TargetRules.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Configuration/TargetRules.cs @@ -524,9 +524,15 @@ namespace UnrealBuildTool [RequiresUniqueBuildEnvironment] public bool bIncludePluginsForTargetPlatforms = false; - /// - /// Whether to include PerfCounters support. - /// + /// + /// Whether to allow accessibility code in both Slate and the OS layer. + /// + [RequiresUniqueBuildEnvironment] + public bool bCompileWithAccessibilitySupport = true; + + /// + /// Whether to include PerfCounters support. + /// [RequiresUniqueBuildEnvironment] [ConfigFile(ConfigHierarchyType.Engine, "/Script/BuildSettings.BuildSettings", "bWithPerfCounters")] public bool bWithPerfCounters = false; @@ -992,10 +998,11 @@ namespace UnrealBuildTool public bool bParseTimingInfoForTracing = false; /// - /// Whether to hide symbols by default on POSIX platforms + /// Whether to expose all symbols as public by default on POSIX platforms /// - [CommandLine("-HideSymbolsByDefault")] - public bool bHideSymbolsByDefault; + [CommandLine("-PublicSymbolsByDefault")] + [XmlConfigFile(Category = "BuildConfiguration")] + public bool bPublicSymbolsByDefault = false; /// /// Allows overriding the toolchain to be created for this target. This must match the name of a class declared in the UnrealBuildTool assembly. @@ -1868,7 +1875,12 @@ namespace UnrealBuildTool get { return Inner.bIncludePluginsForTargetPlatforms; } } - public bool bWithPerfCounters + public bool bCompileWithAccessibilitySupport + { + get { return Inner.bCompileWithAccessibilitySupport; } + } + + public bool bWithPerfCounters { get { return Inner.bWithPerfCounters; } } @@ -2214,9 +2226,9 @@ namespace UnrealBuildTool get { return Inner.bParseTimingInfoForTracing; } } - public bool bHideSymbolsByDefault + public bool bPublicSymbolsByDefault { - get { return Inner.bHideSymbolsByDefault; } + get { return Inner.bPublicSymbolsByDefault; } } public string ToolChainName diff --git a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildModule.cs b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildModule.cs index 3ed6533376b3..054f63b57e49 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildModule.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildModule.cs @@ -66,6 +66,11 @@ namespace UnrealBuildTool /// protected readonly string ModuleApiDefine; + /// + /// The name of the _VTABLE define for this module + /// + protected readonly string ModuleVTableDefine; + /// /// Set of all the public definitions /// @@ -165,6 +170,7 @@ namespace UnrealBuildTool this.Rules = Rules; ModuleApiDefine = Name.ToUpperInvariant() + "_API"; + ModuleVTableDefine = Name.ToUpperInvariant() + "_VTABLE"; PublicDefinitions = HashSetFromOptionalEnumerableStringParameter(Rules.PublicDefinitions); PublicIncludePaths = CreateDirectoryHashSet(Rules.PublicIncludePaths); @@ -493,23 +499,28 @@ namespace UnrealBuildTool { if (Rules.Target.bShouldCompileAsDLL && (Rules.Target.bHasExports || Rules.ModuleSymbolVisibility == ModuleRules.SymbolVisibility.VisibileForDll)) { + Definitions.Add(ModuleVTableDefine + "=DLLEXPORT_VTABLE"); Definitions.Add(ModuleApiDefine + "=DLLEXPORT"); } else { + Definitions.Add(ModuleVTableDefine + "="); Definitions.Add(ModuleApiDefine + "="); } } else if(Binary == null || SourceBinary != Binary) { + Definitions.Add(ModuleVTableDefine + "=DLLIMPORT_VTABLE"); Definitions.Add(ModuleApiDefine + "=DLLIMPORT"); } else if(!Binary.bAllowExports) { + Definitions.Add(ModuleVTableDefine + "="); Definitions.Add(ModuleApiDefine + "="); } else { + Definitions.Add(ModuleVTableDefine + "=DLLEXPORT_VTABLE"); Definitions.Add(ModuleApiDefine + "=DLLEXPORT"); } } @@ -737,6 +748,9 @@ namespace UnrealBuildTool // Add all the additional properties LinkEnvironment.AdditionalProperties.AddRange(Rules.AdditionalPropertiesForReceipt.Inner); + + // this is a link-time property that needs to be accumulated (if any modules contributing to this module is ignoring, all are ignoring) + LinkEnvironment.bIgnoreUnresolvedSymbols |= Rules.bIgnoreUnresolvedSymbols; } /// diff --git a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildModuleCPP.cs b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildModuleCPP.cs index b5620799ae85..e1698ac45995 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildModuleCPP.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildModuleCPP.cs @@ -385,6 +385,7 @@ namespace UnrealBuildTool { // Remove the module _API definition for cases where there are circular dependencies between the shared PCH module and modules using it Writer.WriteLine("#undef {0}", ModuleApiDefine); + Writer.WriteLine("#undef {0}", ModuleVTableDefine); // Games may choose to use shared PCHs from the engine, so allow them to change the value of these macros if(!Rules.bTreatAsEngineModule) diff --git a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildTarget.cs b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildTarget.cs index 66a890340787..7739467611f7 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildTarget.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildTarget.cs @@ -3200,7 +3200,7 @@ namespace UnrealBuildTool GlobalCompileEnvironment.bPrintTimingInfo = Rules.bPrintToolChainTimingInfo; GlobalCompileEnvironment.bUseRTTI = Rules.bForceEnableRTTI; GlobalCompileEnvironment.bUseInlining = Rules.bUseInlining; - GlobalCompileEnvironment.bHideSymbolsByDefault = Rules.bHideSymbolsByDefault; + GlobalCompileEnvironment.bHideSymbolsByDefault = !Rules.bPublicSymbolsByDefault; GlobalCompileEnvironment.CppStandard = Rules.CppStandard; GlobalCompileEnvironment.AdditionalArguments = Rules.AdditionalCompilerArguments; @@ -3358,7 +3358,16 @@ namespace UnrealBuildTool GlobalCompileEnvironment.Definitions.Add("WITH_PLUGIN_SUPPORT=0"); } - if (Rules.bWithPerfCounters) + if (Rules.bCompileWithAccessibilitySupport && !Rules.bIsBuildingConsoleApplication) + { + GlobalCompileEnvironment.Definitions.Add("WITH_ACCESSIBILITY=1"); + } + else + { + GlobalCompileEnvironment.Definitions.Add("WITH_ACCESSIBILITY=0"); + } + + if (Rules.bWithPerfCounters) { GlobalCompileEnvironment.Definitions.Add("WITH_PERFCOUNTERS=1"); } diff --git a/Engine/Source/Programs/UnrealBuildTool/Modes/BuildMode.cs b/Engine/Source/Programs/UnrealBuildTool/Modes/BuildMode.cs index fe9b43dd6e46..e74829e3cc14 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Modes/BuildMode.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Modes/BuildMode.cs @@ -539,8 +539,12 @@ namespace UnrealBuildTool HotReloadState HotReloadState = null; if(HotReloadMode == HotReloadMode.Disabled) { - // Delete the previous state file - HotReload.DeleteTemporaryFiles(HotReloadStateFile); + // Make sure we're not doing a partial build from the editor (eg. compiling a new plugin) + if(TargetDescriptor.ForeignPlugin == null && TargetDescriptor.SingleFileToCompile == null) + { + // Delete the previous state file + HotReload.DeleteTemporaryFiles(HotReloadStateFile); + } } else { diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/LinuxToolChain.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/LinuxToolChain.cs index 280195ef21e6..65f876201454 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/LinuxToolChain.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/LinuxToolChain.cs @@ -577,7 +577,6 @@ namespace UnrealBuildTool if (CompileEnvironment.bHideSymbolsByDefault) { Result += " -fvisibility=hidden"; - Result += " -fvisibility-inlines-hidden"; } if (String.IsNullOrEmpty(ClangPath)) @@ -751,6 +750,11 @@ namespace UnrealBuildTool // glibc/ld.so limit (DTV_SURPLUS) for number of dlopen()'ed DSOs with static TLS (see e.g. https://www.cygwin.com/ml/libc-help/2013-11/msg00033.html) Result += " -ftls-model=local-dynamic"; } + else + { + Result += " -ffunction-sections"; + Result += " -fdata-sections"; + } if (CompileEnvironment.bEnableExceptions) { @@ -922,8 +926,6 @@ namespace UnrealBuildTool Result += " -Wl,-rpath-link=${ORIGIN}"; Result += " -Wl,-rpath=${ORIGIN}/../../../Engine/Binaries/Linux"; Result += " -Wl,-rpath=${ORIGIN}/.."; // for modules that are in sub-folders of the main Engine/Binary/Linux folder - // FIXME: really ugly temp solution. Modules need to be able to specify this - Result += " -Wl,-rpath=${ORIGIN}/../../../Engine/Binaries/ThirdParty/Steamworks/Steamv139/x86_64-unknown-linux-gnu"; if (LinkEnvironment.Architecture.StartsWith("x86_64")) { Result += " -Wl,-rpath=${ORIGIN}/../../../Engine/Binaries/ThirdParty/Qualcomm/Linux"; @@ -950,9 +952,14 @@ namespace UnrealBuildTool // This apparently can help LLDB speed up symbol lookups Result += " -Wl,--build-id"; - if (bSuppressPIE && !LinkEnvironment.bIsBuildingDLL) + if (!LinkEnvironment.bIsBuildingDLL) { - Result += " -Wl,-nopie"; + Result += " -Wl,--gc-sections"; + + if (bSuppressPIE) + { + Result += " -Wl,-nopie"; + } } // Profile Guided Optimization (PGO) and Link Time Optimization (LTO) @@ -1001,10 +1008,10 @@ namespace UnrealBuildTool // Linking with the toolchain on linux appears to not search usr/ if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Linux) { - Result += String.Format(" -B{0}/usr/lib/", SysRootPath); - Result += String.Format(" -B{0}/usr/lib64/", SysRootPath); - Result += String.Format(" -L{0}/usr/lib/", SysRootPath); - Result += String.Format(" -L{0}/usr/lib64/", SysRootPath); + Result += String.Format(" -B\"{0}/usr/lib/\"", SysRootPath); + Result += String.Format(" -B\"{0}/usr/lib64/\"", SysRootPath); + Result += String.Format(" -L\"{0}/usr/lib/\"", SysRootPath); + Result += String.Format(" -L\"{0}/usr/lib64/\"", SysRootPath); } } @@ -1718,6 +1725,14 @@ namespace UnrealBuildTool LinkCommandString += " -Wl,--start-group"; LinkCommandString += ExternalLibraries; + + // make unresolved symbols an error, unless a) building a cross-referenced DSO b) we opted out + if ((!LinkEnvironment.bIsBuildingDLL || !LinkEnvironment.bIsCrossReferenced) && !LinkEnvironment.bIgnoreUnresolvedSymbols) + { + // This will make the linker report undefined symbols the current module, but ignore in the dependent DSOs. + // It is tempting, but may not be possible to change that report-all - due to circular dependencies between our libs. + LinkCommandString += " -Wl,--unresolved-symbols=ignore-in-shared-libs"; + } LinkCommandString += " -Wl,--end-group"; LinkCommandString += " -lrt"; // needed for clock_gettime() @@ -1732,6 +1747,7 @@ namespace UnrealBuildTool LinkCommandString += " " + "ThirdParty/Linux/LibCxx/lib/Linux/" + LinkEnvironment.Architecture + "/libc++abi.a"; LinkCommandString += " -lm"; LinkCommandString += " -lc"; + LinkCommandString += " -lpthread"; // pthread_mutex_trylock is missing from libc stubs LinkCommandString += " -lgcc_s"; LinkCommandString += " -lgcc"; } diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/UEBuildLinux.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/UEBuildLinux.cs index 4971948cd4c0..fe8f280eedfa 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/UEBuildLinux.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/UEBuildLinux.cs @@ -297,7 +297,7 @@ namespace UnrealBuildTool // [bschaefer] 2018-08-24: disabling XGE due to a bug where XGE seems to be lower casing folders names that are headers ie. misc/Header.h vs Misc/Header.h // [bschaefer] 2018-10-04: enabling XGE as an update in xgConsole seems to have fixed it for me // [bschaefer] 2018-12-17: disable XGE again, as the same issue before seems to still be happening but intermittently - return false; //BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Win64; + return false; // BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Win64; } public override bool CanUseParallelExecutor() @@ -518,12 +518,6 @@ namespace UnrealBuildTool ); } - // for now only hide by default monolithic builds. - if (Target.LinkType == TargetLinkType.Monolithic) - { - CompileEnvironment.bHideSymbolsByDefault = true; - } - // link with Linux libraries. LinkEnvironment.AdditionalLibraries.Add("pthread"); diff --git a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Project.cs b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Project.cs index d12efa8877b9..0d1e966f9e3c 100644 --- a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Project.cs +++ b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Project.cs @@ -306,7 +306,8 @@ namespace UnrealBuildTool // Ignore any API macros being import/export; we'll assume they're valid across the whole project if(Def.EndsWith("_API", StringComparison.Ordinal)) { - CurDef = Def + "="; + CurDef = Def + "="; + CurDef = Def + "_TYPE="; Value = ""; } diff --git a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioCode/VSCodeProjectFileGenerator.cs b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioCode/VSCodeProjectFileGenerator.cs index 83baf185ac30..720b3ea6ea88 100644 --- a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioCode/VSCodeProjectFileGenerator.cs +++ b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioCode/VSCodeProjectFileGenerator.cs @@ -1248,20 +1248,26 @@ namespace UnrealBuildTool { WorkspaceFile.BeginArray("folders"); { - if (bForeignProject) + // Add the directory in which which the code-workspace file exists. + // This is also known as ${workspaceRoot} + WorkspaceFile.BeginObject(); + { + string ProjectName = bForeignProject ? GameProjectName : "UE4"; + WorkspaceFile.AddField("name", ProjectName); + WorkspaceFile.AddField("path", "."); + } + WorkspaceFile.EndObject(); + + // If this project is outside the engine folder, add the root engine directory + if (bIncludeEngineSource && bForeignProject) { WorkspaceFile.BeginObject(); { - WorkspaceFile.AddField("path", "."); + WorkspaceFile.AddField("name", "UE4"); + WorkspaceFile.AddField("path", MakeUnquotedPathString(UnrealBuildTool.RootDirectory, EPathType.Absolute)); } WorkspaceFile.EndObject(); } - - WorkspaceFile.BeginObject(); - { - WorkspaceFile.AddField("path", MakeUnquotedPathString(UnrealBuildTool.RootDirectory, EPathType.Absolute)); - } - WorkspaceFile.EndObject(); } WorkspaceFile.EndArray(); } @@ -1271,6 +1277,26 @@ namespace UnrealBuildTool WorkspaceFile.AddField("typescript.tsc.autoDetect", "off"); } WorkspaceFile.EndObject(); + + WorkspaceFile.BeginObject("extensions"); + { + // extensions is a set of recommended extensions that a user should install. + // Adding this section aids discovery of extensions which are helpful to have installed for Unreal development. + WorkspaceFile.BeginArray("recommendations"); + { + WorkspaceFile.AddUnnamedField("ms-vscode.cpptools"); + WorkspaceFile.AddUnnamedField("ms-vscode.csharp"); + + // If the platform we run the generator on uses mono, there are additional debugging extensions to add. + if (Utils.IsRunningOnMono) + { + WorkspaceFile.AddUnnamedField("vadimcn.vscode-lldb"); + WorkspaceFile.AddUnnamedField("ms-vscode.mono-debug"); + } + } + WorkspaceFile.EndArray(); + } + WorkspaceFile.EndObject(); WorkspaceFile.EndRootObject(); diff --git a/Engine/Source/Programs/UnrealBuildTool/System/CppCompileEnvironment.cs b/Engine/Source/Programs/UnrealBuildTool/System/CppCompileEnvironment.cs index c3610784e40f..35fa5ddafcd9 100644 --- a/Engine/Source/Programs/UnrealBuildTool/System/CppCompileEnvironment.cs +++ b/Engine/Source/Programs/UnrealBuildTool/System/CppCompileEnvironment.cs @@ -342,7 +342,7 @@ namespace UnrealBuildTool /// /// Whether to hide symbols by default /// - public bool bHideSymbolsByDefault; + public bool bHideSymbolsByDefault = true; /// /// Which C++ standard to support. May not be compatible with all platforms. diff --git a/Engine/Source/Programs/UnrealBuildTool/System/LinkEnvironment.cs b/Engine/Source/Programs/UnrealBuildTool/System/LinkEnvironment.cs index 1865b60c60ab..302f72e5ca07 100644 --- a/Engine/Source/Programs/UnrealBuildTool/System/LinkEnvironment.cs +++ b/Engine/Source/Programs/UnrealBuildTool/System/LinkEnvironment.cs @@ -244,6 +244,11 @@ namespace UnrealBuildTool /// public bool bUseFastPDBLinking; + /// + /// Whether to ignore dangling (i.e. unresolved external) symbols in modules + /// + public bool bIgnoreUnresolvedSymbols; + /// /// Whether to log detailed timing information /// @@ -347,6 +352,7 @@ namespace UnrealBuildTool bAllowASLR = Other.bAllowASLR; bUsePDBFiles = Other.bUsePDBFiles; bUseFastPDBLinking = Other.bUseFastPDBLinking; + bIgnoreUnresolvedSymbols = Other.bIgnoreUnresolvedSymbols; bPrintTimingInfo = Other.bPrintTimingInfo; BundleVersion = Other.BundleVersion; InstallName = Other.InstallName; diff --git a/Engine/Source/Programs/UnrealBuildTool/ToolChain/RemoteMac.cs b/Engine/Source/Programs/UnrealBuildTool/ToolChain/RemoteMac.cs index 906ea1ef2f8e..01732f4e950c 100644 --- a/Engine/Source/Programs/UnrealBuildTool/ToolChain/RemoteMac.cs +++ b/Engine/Source/Programs/UnrealBuildTool/ToolChain/RemoteMac.cs @@ -911,9 +911,12 @@ namespace UnrealBuildTool } Execute("/", String.Format("rm -rf {0}/Intermediate/IOS/*.plist", GetRemotePath(UnrealBuildTool.EngineDirectory))); - Execute("/", String.Format("rm -rf {0}/Intermediate/IOS/*.plist", GetRemotePath(ProjectFile.Directory))); Execute("/", String.Format("rm -rf {0}/Intermediate/TVOS/*.plist", GetRemotePath(UnrealBuildTool.EngineDirectory))); - Execute("/", String.Format("rm -rf {0}/Intermediate/TVOS/*.plist", GetRemotePath(ProjectFile.Directory))); + if (ProjectFile != null) + { + Execute("/", String.Format("rm -rf {0}/Intermediate/IOS/*.plist", GetRemotePath(ProjectFile.Directory))); + Execute("/", String.Format("rm -rf {0}/Intermediate/TVOS/*.plist", GetRemotePath(ProjectFile.Directory))); + } // Fixup permissions on any shell scripts Execute(RemoteBaseDir, String.Format("chmod +x {0}/Build/BatchFiles/Mac/*.sh", EscapeShellArgument(GetRemotePath(UnrealBuildTool.EngineDirectory)))); diff --git a/Engine/Source/Programs/UnrealHeaderTool/Private/BaseParser.cpp b/Engine/Source/Programs/UnrealHeaderTool/Private/BaseParser.cpp index b8cae3718caa..539bdaee1851 100644 --- a/Engine/Source/Programs/UnrealHeaderTool/Private/BaseParser.cpp +++ b/Engine/Source/Programs/UnrealHeaderTool/Private/BaseParser.cpp @@ -1036,7 +1036,7 @@ bool FBaseParser::ReadOptionalCommaSeparatedListInParens(TArray& Items, void FBaseParser::ParseNameWithPotentialAPIMacroPrefix(FString& DeclaredName, FString& RequiredAPIMacroIfPresent, const TCHAR* FailureMessage) { - // Expecting Name | (MODULE_API Name) + // Expecting Name | (MODULE_API Name) | (MODULE_VTABLE Name) FToken NameToken; // Read an identifier @@ -1058,6 +1058,19 @@ void FBaseParser::ParseNameWithPotentialAPIMacroPrefix(FString& DeclaredName, FS } DeclaredName = NameToken.Identifier; } + else if (NameTokenStr.EndsWith(TEXT("_VTABLE"), ESearchCase::CaseSensitive)) + { + // Read the real name + if (!GetIdentifier(NameToken)) + { + FError::Throwf(TEXT("Missing %s name"), FailureMessage); + } + + DeclaredName = NameToken.Identifier; + + // Not required for things such a Minimal. Only used for Linux/Mac which just exposes the vtable for link-age + RequiredAPIMacroIfPresent.Empty(); + } else { DeclaredName = NameTokenStr; diff --git a/Engine/Source/Programs/UnrealHeaderTool/Private/CodeGenerator.cpp b/Engine/Source/Programs/UnrealHeaderTool/Private/CodeGenerator.cpp index 6a6b3f2430a6..00b3cb6d0f34 100644 --- a/Engine/Source/Programs/UnrealHeaderTool/Private/CodeGenerator.cpp +++ b/Engine/Source/Programs/UnrealHeaderTool/Private/CodeGenerator.cpp @@ -515,7 +515,7 @@ static FString OutputMetaDataCodeForObject(FOutputDevice& OutDeclaration, FOutpu // We sort the metadata here so that we can get consistent output across multiple runs // even when metadata is added in a different order - Algo::SortBy(KVPs, &KVPType::Key); + Algo::SortBy(KVPs, &KVPType::Key, FNameLexicalLess()); for (const KVPType& KVP : KVPs) { @@ -2397,7 +2397,7 @@ void FNativeClassHeaderGenerator::ExportNatives(FOutputDevice& Out, FClass* Clas } } - Algo::SortBy(NamedFunctionsToExport, [](const TTuple& Pair){ return Pair.Get<0>()->GetFName(); }); + Algo::SortBy(NamedFunctionsToExport, [](const TTuple& Pair){ return Pair.Get<0>()->GetFName(); }, FNameLexicalLess()); if (NamedFunctionsToExport.Num() > 0) { @@ -6085,11 +6085,6 @@ UClass* ProcessParsedClass(bool bClassIsAnInterface, TArray& De { FError::Throwf(TEXT("No prefix or invalid identifier for base class %s.\nClass names must match Unreal prefix specifications (e.g., \"UObject\" or \"AActor\")"), *BaseClassName); } - - if (DependentOn.ContainsByPredicate([&](const FHeaderProvider& Dependency){ FString DependencyStr = Dependency.GetId(); return !DependencyStr.Contains(TEXT(".generated.h")) && FPaths::GetBaseFilename(DependencyStr) == ClassNameStripped; })) - { - FError::Throwf(TEXT("Class '%s' contains a dependency (#include or base class) to itself"), *ClassName); - } } //UE_LOG(LogCompile, Log, TEXT("Class: %s extends %s"),*ClassName,*BaseClassName); diff --git a/Engine/Source/Programs/UnrealHeaderTool/Private/HeaderParser.cpp b/Engine/Source/Programs/UnrealHeaderTool/Private/HeaderParser.cpp index 83a03f2a18a5..9bb294dcff12 100644 --- a/Engine/Source/Programs/UnrealHeaderTool/Private/HeaderParser.cpp +++ b/Engine/Source/Programs/UnrealHeaderTool/Private/HeaderParser.cpp @@ -3825,6 +3825,11 @@ void FHeaderParser::GetVarType( { VarProperty = FPropertyBase(CPT_Int64); } + else if ( VarType.Matches(TEXT("uint64")) && IsBitfieldProperty() ) + { + // 64-bit bitfield (bool) type, treat it like 8 bit type + VarProperty = FPropertyBase(CPT_Bool8); + } else if ( VarType.Matches(TEXT("uint32")) && IsBitfieldProperty() ) { // 32-bit bitfield (bool) type, treat it like 8 bit type @@ -5058,6 +5063,11 @@ bool FHeaderParser::CompileDeclaration(FClasses& AllClasses, TArrayGetName(); + FError::Throwf(TEXT("Could not find the associated 'U%s' class while parsing 'I%s' - it could be missing or malformed"), *CurrentClassName, *CurrentClassName); + } ClassData->GeneratedBodyMacroAccessSpecifier = CurrentAccessSpecifier; ClassData->SetInterfaceGeneratedBodyLine(InputLine); diff --git a/Engine/Source/Programs/UnrealHeaderTool/Private/Scope.h b/Engine/Source/Programs/UnrealHeaderTool/Private/Scope.h index 8e5f76ba0cd6..6fc49fd24274 100644 --- a/Engine/Source/Programs/UnrealHeaderTool/Private/Scope.h +++ b/Engine/Source/Programs/UnrealHeaderTool/Private/Scope.h @@ -263,6 +263,8 @@ private: friend struct FStructScopeArchiveProxy; }; +using FScopeSet = TSet, TInlineSetAllocator<1024>>; + /** * Represents a scope associated with source file. */ @@ -300,18 +302,15 @@ public: * * @param Out (Output parameter) Array to append scopes. */ - void AppendIncludedFileScopes(TArray& Out) + void AppendIncludedFileScopes(FScopeSet& Out) { - if (!Out.Contains(this)) - { - Out.Add(this); - } + bool bAlreadyAdded = false; + Out.Add(this, &bAlreadyAdded); - for (FFileScope* IncludedScope : IncludedScopes) + if (!bAlreadyAdded) { - if (!Out.Contains(IncludedScope)) + for (FFileScope* IncludedScope : IncludedScopes) { - Out.Add(IncludedScope); IncludedScope->AppendIncludedFileScopes(Out); } } @@ -381,7 +380,7 @@ public: typedef typename TConditionalType::TConstIterator, TMap::TIterator>::Type MapIteratorType; typedef typename TConditionalType::Type ScopeType; typedef typename TConditionalType::Type FileScopeType; - typedef typename TConditionalType::TConstIterator, typename TArray::TIterator>::Type ScopeArrayIteratorType; + typedef typename TConditionalType::Type ScopeArrayIteratorType; // Constructor. TDeepScopeTypeIterator(ScopeType* Scope) @@ -418,7 +417,7 @@ public: */ bool MoveNext() { - if (!ScopeIterator.IsValid() && !MoveToNextScope()) + if (!ScopeIterator.IsSet() && !MoveToNextScope()) { return false; } @@ -431,7 +430,7 @@ public: { do { - ScopeIterator = nullptr; + ScopeIterator.Reset(); if (!MoveToNextScope()) { return false; @@ -461,9 +460,9 @@ private: */ bool MoveToNextScope() { - if (!ScopesIterator.IsValid()) + if (!ScopesIterator.IsSet()) { - ScopesIterator = MakeShareable(new ScopeArrayIteratorType(ScopesToTraverse)); + ScopesIterator.Emplace(ScopesToTraverse); } else { @@ -475,17 +474,17 @@ private: return false; } - ScopeIterator = MakeShareable(new FScope::TScopeTypeIterator(ScopesIterator->operator*())); + ScopeIterator.Emplace(ScopesIterator->operator*()); return true; } - // List of scopes to traverse. - TArray ScopesToTraverse; - // Current scope iterator. - TSharedPtr > ScopeIterator; + TOptional > ScopeIterator; // Scopes list iterator. - TSharedPtr ScopesIterator; + TOptional ScopesIterator; + + // List of scopes to traverse. + FScopeSet ScopesToTraverse; }; diff --git a/Engine/Source/Programs/UnrealInsights/Private/IOS/IOSUnrealInsightsMain.cpp b/Engine/Source/Programs/UnrealInsights/Private/IOS/IOSUnrealInsightsMain.cpp new file mode 100644 index 000000000000..c879552188f2 --- /dev/null +++ b/Engine/Source/Programs/UnrealInsights/Private/IOS/IOSUnrealInsightsMain.cpp @@ -0,0 +1,124 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "UnrealInsightsMain.h" +#include "IOS/IOSAppDelegate.h" +#include "IOS/IOSCommandLineHelper.h" +#include "IOS/SlateOpenGLESView.h" +#include "Widgets/Testing/STestSuite.h" + +#import + + +#define IOS_MAX_PATH 1024 +#define CMD_LINE_MAX 16384 + + +FString GSavedCommandLine; + + +void FAppEntry::Suspend() +{ +} + + +void FAppEntry::Resume() +{ +} + + +void FAppEntry::SuspendTick() +{ +} + + +bool FAppEntry::IsStartupMoviePlaying() +{ + return false; +} + + +void FAppEntry::PreInit(IOSAppDelegate* AppDelegate, UIApplication* Application) +{ + // make a controller object + AppDelegate.SlateController = [[SlateOpenGLESViewController alloc] init]; + + // property owns it now + [AppDelegate.SlateController release]; + + // point to the GL view we want to use + AppDelegate.RootView = [AppDelegate.SlateController view]; + + if (AppDelegate.OSVersion >= 6.0f) + { + // this probably works back to OS4, but would need testing + [AppDelegate.Window setRootViewController:AppDelegate.SlateController]; + } + else + { + [AppDelegate.Window addSubview:AppDelegate.RootView]; + } +} + + +void FAppEntry::PlatformInit() +{ +} + + +void FAppEntry::Init() +{ + // Start up the main loop. + GEngineLoop.PreInit(FCommandLine::Get()); + + // Move it to this thread. + SlateOpenGLESView* View = (SlateOpenGLESView*)[IOSAppDelegate GetDelegate].RootView; + [EAGLContext setCurrentContext:View.Context]; + + // Crank up a normal Slate application using the platform's standalone renderer. + FSlateApplication::InitializeAsStandaloneApplication(GetStandardStandaloneRenderer()); + + // Bring up the UI. + { + //TODO: RestoreUI(); + } + +#if WITH_SHARED_POINTER_TESTS + SharedPointerTesting::TestSharedPointer(); + SharedPointerTesting::TestSharedPointer(); +#endif + + // Loop while the server does the rest. + double LastTime = FPlatformTime::Seconds(); +} + + +void FAppEntry::Tick() +{ + FSlateApplication::Get().PumpMessages(); + FSlateApplication::Get().Tick(); + + // Sleep + FPlatformProcess::Sleep(0); +} + + +void FAppEntry::Shutdown() +{ + FSlateApplication::Shutdown(); +} + + +int main(int argc, char *argv[]) +{ + for (int32 Option = 1; Option < argc; Option++) + { + GSavedCommandLine += TEXT(" "); + GSavedCommandLine += ANSI_TO_TCHAR(argv[Option]); + } + + FIOSCommandLineHelper::InitCommandArgs(FString()); + + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([IOSAppDelegate class])); + } +} diff --git a/Engine/Source/Programs/UnrealInsights/Private/Linux/LinuxUnrealInsightsMain.cpp b/Engine/Source/Programs/UnrealInsights/Private/Linux/LinuxUnrealInsightsMain.cpp new file mode 100644 index 000000000000..2a90068a1f23 --- /dev/null +++ b/Engine/Source/Programs/UnrealInsights/Private/Linux/LinuxUnrealInsightsMain.cpp @@ -0,0 +1,9 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "UnrealInsightsMain.h" +#include "UnixCommonStartup.h" + +int main(int argc, char *argv[]) +{ + return CommonUnixMain(argc, argv, &UnrealInsightsMain); +} diff --git a/Engine/Source/Programs/UnrealInsights/Private/Mac/MacUnrealInsightsMain.cpp b/Engine/Source/Programs/UnrealInsights/Private/Mac/MacUnrealInsightsMain.cpp new file mode 100644 index 000000000000..8d226d800992 --- /dev/null +++ b/Engine/Source/Programs/UnrealInsights/Private/Mac/MacUnrealInsightsMain.cpp @@ -0,0 +1,117 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "UnrealInsightsMain.h" +#include "HAL/ExceptionHandling.h" +#include "LaunchEngineLoop.h" +#include "Mac/CocoaThread.h" + + +static FString GSavedCommandLine; + + +@interface UE4AppDelegate : NSObject +{ +} + +@end + + +@implementation UE4AppDelegate + +//handler for the quit apple event used by the Dock menu +- (void)handleQuitEvent:(NSAppleEventDescriptor*)Event withReplyEvent:(NSAppleEventDescriptor*)ReplyEvent +{ + [self requestQuit:self]; +} + +- (IBAction)requestQuit:(id)Sender +{ + GIsRequestingExit = true; +} + +- (void) runGameThread:(id)Arg +{ + FPlatformMisc::SetGracefulTerminationHandler(); + FPlatformMisc::SetCrashHandler(nullptr); + +#if !UE_BUILD_SHIPPING + if (FParse::Param(*GSavedCommandLine,TEXT("crashreports"))) + { + GAlwaysReportCrash = true; + } +#endif + +#if UE_BUILD_DEBUG + if (!GAlwaysReportCrash) +#else + if (FPlatformMisc::IsDebuggerPresent() && !GAlwaysReportCrash) +#endif + { + UnrealInsightsMain(*GSavedCommandLine); + } + else + { + GIsGuarded = 1; + UnrealInsightsMain(*GSavedCommandLine); + GIsGuarded = 0; + } + + FEngineLoop::AppExit(); + + [NSApp terminate: self]; +} + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)Sender; +{ + if(!GIsRequestingExit || ([NSThread gameThread] && [NSThread gameThread] != [NSThread mainThread])) + { + [self requestQuit:self]; + return NSTerminateLater; + } + else + { + return NSTerminateNow; + } +} + +- (void)applicationDidFinishLaunching:(NSNotification *)Notification +{ + //install the custom quit event handler + NSAppleEventManager* appleEventManager = [NSAppleEventManager sharedAppleEventManager]; + [appleEventManager setEventHandler:self andSelector:@selector(handleQuitEvent:withReplyEvent:) forEventClass:kCoreEventClass andEventID:kAEQuitApplication]; + + RunGameThread(self, @selector(runGameThread:)); +} + +@end + + +int main(int argc, char *argv[]) +{ + for (int32 Option = 1; Option < argc; Option++) + { + GSavedCommandLine += TEXT(" "); + FString Argument(ANSI_TO_TCHAR(argv[Option])); + if (Argument.Contains(TEXT(" "))) + { + if (Argument.Contains(TEXT("="))) + { + FString ArgName; + FString ArgValue; + Argument.Split( TEXT("="), &ArgName, &ArgValue ); + Argument = FString::Printf( TEXT("%s=\"%s\""), *ArgName, *ArgValue ); + } + else + { + Argument = FString::Printf(TEXT("\"%s\""), *Argument); + } + } + GSavedCommandLine += Argument; + } + + SCOPED_AUTORELEASE_POOL; + [NSApplication sharedApplication]; + [NSApp setDelegate:[UE4AppDelegate new]]; + [NSApp run]; + return 0; +} diff --git a/Engine/Source/Programs/UnrealInsights/Private/UnrealInsightsMain.cpp b/Engine/Source/Programs/UnrealInsights/Private/UnrealInsightsMain.cpp new file mode 100644 index 000000000000..bc76e23efdbb --- /dev/null +++ b/Engine/Source/Programs/UnrealInsights/Private/UnrealInsightsMain.cpp @@ -0,0 +1,46 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "UnrealInsightsMain.h" + +#include "RequiredProgramMainCPPInclude.h" +#include "UserInterfaceCommand.h" + + +IMPLEMENT_APPLICATION(UnrealInsights, "UnrealInsights"); + + +/** + * Platform agnostic implementation of the main entry point. + */ +int32 UnrealInsightsMain(const TCHAR* CommandLine) +{ + // Override the stack size for the thread pool. + FQueuedThreadPool::OverrideStackSize = 256 * 1024; + + //im: ??? + FCommandLine::Set(CommandLine); + + // Initialize core. + GEngineLoop.PreInit(CommandLine); + + // Make sure all UObject classes are registered and default properties have been initialized. + //im: ??? ProcessNewlyLoadedUObjects(); + + // Tell the module manager it may now process newly-loaded UObjects when new C++ modules are loaded. + FModuleManager::Get().StartProcessingNewlyLoadedObjects(); + + FUserInterfaceCommand::Run(); + + // Shut down. + //im: ??? FCoreDelegates::OnExit.Broadcast(); + FModuleManager::Get().UnloadModulesAtShutdown(); + FEngineLoop::AppPreExit(); //im: ??? + +#if STATS + FThreadStats::StopThread(); +#endif + + FTaskGraphInterface::Shutdown(); //im: ??? + + return 0; +} diff --git a/Engine/Source/Programs/UnrealInsights/Private/UnrealInsightsMain.h b/Engine/Source/Programs/UnrealInsights/Private/UnrealInsightsMain.h new file mode 100644 index 000000000000..433e8239fa68 --- /dev/null +++ b/Engine/Source/Programs/UnrealInsights/Private/UnrealInsightsMain.h @@ -0,0 +1,13 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +/** + * The application's main function. + * + * @param CommandLine The application command line. + * @return Application's exit value. + */ +int32 UnrealInsightsMain(const TCHAR* CommandLine); diff --git a/Engine/Source/Programs/UnrealInsights/Private/UserInterfaceCommand.cpp b/Engine/Source/Programs/UnrealInsights/Private/UserInterfaceCommand.cpp new file mode 100644 index 000000000000..8138562cb214 --- /dev/null +++ b/Engine/Source/Programs/UnrealInsights/Private/UserInterfaceCommand.cpp @@ -0,0 +1,165 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "UserInterfaceCommand.h" + +#include "Async/TaskGraphInterfaces.h" +#include "Containers/Ticker.h" +#include "EditorStyleSet.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/Docking/LayoutService.h" +#include "Framework/Docking/TabManager.h" +#include "HAL/PlatformApplicationMisc.h" +#include "HAL/PlatformProcess.h" +#include "Interfaces/IPluginManager.h" +#include "ISlateReflectorModule.h" +#include "ISourceCodeAccessModule.h" +#include "Misc/CommandLine.h" +#include "Misc/ConfigCacheIni.h" +#include "Modules/ModuleManager.h" +#include "StandaloneRenderer.h" +#include "Widgets/Docking/SDockTab.h" + +// Insights +#include "Insights/IUnrealInsightsModule.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define IDEAL_FRAMERATE 60 + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace UserInterfaceCommand +{ + TSharedPtr ApplicationLayout; + TSharedRef DeveloperTools = FWorkspaceItem::NewGroup(NSLOCTEXT("UnrealInsights", "DeveloperToolsMenu", "Developer Tools")); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FUserInterfaceCommand::Run() +{ + FString UnrealInsightsLayoutIni = FPaths::GetPath(GEngineIni) + "/UnrealInsightsLayout.ini"; + + FCoreStyle::ResetToDefault(); + + // Load required modules. + FModuleManager::Get().LoadModuleChecked("EditorStyle"); + FModuleManager::Get().LoadModuleChecked("TraceInsights"); + + // Load plug-ins. + // @todo: allow for better plug-in support in standalone Slate applications + IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreDefault); + + // Load optional modules. + FModuleManager::Get().LoadModule("SettingsEditor"); + + InitializeSlateApplication(UnrealInsightsLayoutIni); + + // Initialize source code access. + // Load the source code access module. + ISourceCodeAccessModule& SourceCodeAccessModule = FModuleManager::LoadModuleChecked(FName("SourceCodeAccess")); + + // Manually load in the source code access plugins, as standalone programs don't currently support plugins. +#if PLATFORM_MAC + IModuleInterface& XCodeSourceCodeAccessModule = FModuleManager::LoadModuleChecked(FName("XCodeSourceCodeAccess")); + SourceCodeAccessModule.SetAccessor(FName("XCodeSourceCodeAccess")); +#elif PLATFORM_WINDOWS + //im:TODO: IModuleInterface& VisualStudioSourceCodeAccessModule = FModuleManager::LoadModuleChecked(FName("VisualStudioSourceCodeAccess")); + //im:TODO: SourceCodeAccessModule.SetAccessor(FName("VisualStudioSourceCodeAccess")); +#endif + +#if WITH_SHARED_POINTER_TESTS + SharedPointerTesting::TestSharedPointer(); + SharedPointerTesting::TestSharedPointer(); +#endif + + // Enter main loop. + double DeltaTime = 0.0; + double LastTime = FPlatformTime::Seconds(); + const float IdealFrameTime = 1.0f / IDEAL_FRAMERATE; + + while (!GIsRequestingExit) + { + // Save the state of the tabs here rather than after close of application (the tabs are undesirably saved out with ClosedTab state on application close). + //UserInterfaceCommand::UserConfiguredNewLayout = FGlobalTabmanager::Get()->PersistLayout(); + + FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); + + FSlateApplication::Get().PumpMessages(); + FSlateApplication::Get().Tick(); + FTicker::GetCoreTicker().Tick(DeltaTime); + + // Throttle frame rate. + FPlatformProcess::Sleep(FMath::Max(0.0f, IdealFrameTime - (FPlatformTime::Seconds() - LastTime))); + + double CurrentTime = FPlatformTime::Seconds(); + DeltaTime = CurrentTime - LastTime; + LastTime = CurrentTime; + + FStats::AdvanceFrame(false); + + GLog->FlushThreadedLogs(); //im: ??? + } + + //im: ??? FCoreDelegates::OnExit.Broadcast(); + + ShutdownSlateApplication(UnrealInsightsLayoutIni); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FUserInterfaceCommand::InitializeSlateApplication(const FString& LayoutIni) +{ + // Crank up a normal Slate application using the platform's standalone renderer. + FSlateApplication::InitializeAsStandaloneApplication(GetStandardStandaloneRenderer()); + + // Set the application name. + FGlobalTabmanager::Get()->SetApplicationTitle(NSLOCTEXT("UnrealInsights", "AppTitle", "Unreal Insights")); + + // Load widget reflector. + const bool bAllowDebugTools = FParse::Param(FCommandLine::Get(), TEXT("DebugTools")); + if (bAllowDebugTools) + { + FModuleManager::LoadModuleChecked("SlateReflector").RegisterTabSpawner(UserInterfaceCommand::DeveloperTools); + } + + TSharedRef NewLayout = FTabManager::NewLayout("UnrealInsightsLayout_v1.0"); + + // Allow TraceInsights module to update the layout. + IUnrealInsightsModule& TraceInsightsModule = FModuleManager::LoadModuleChecked("TraceInsights"); + TraceInsightsModule.OnNewLayout(NewLayout); + + // Create area and tab for Slate's WidgetReflector. + const float DPIScaleFactor = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(10.0f, 10.0f); + NewLayout->AddArea + ( + FTabManager::NewArea(600.0f * DPIScaleFactor, 600.0f * DPIScaleFactor) + ->SetWindow(FVector2D(10.0f * DPIScaleFactor, 10.0f * DPIScaleFactor), false) + ->Split + ( + FTabManager::NewStack()->AddTab("WidgetReflector", bAllowDebugTools ? ETabState::OpenedTab : ETabState::ClosedTab) + ) + ); + + // Restore application layout. + UserInterfaceCommand::ApplicationLayout = FLayoutSaveRestore::LoadFromConfig(LayoutIni, NewLayout); + FGlobalTabmanager::Get()->RestoreFrom(UserInterfaceCommand::ApplicationLayout.ToSharedRef(), TSharedPtr()); + + TraceInsightsModule.OnLayoutRestored(FGlobalTabmanager::Get()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void FUserInterfaceCommand::ShutdownSlateApplication(const FString& LayoutIni) +{ + check(UserInterfaceCommand::ApplicationLayout.IsValid()); + + // Save application layout. + FLayoutSaveRestore::SaveToConfig(LayoutIni, UserInterfaceCommand::ApplicationLayout.ToSharedRef()); + GConfig->Flush(false, LayoutIni); + + // Shut down application. + FSlateApplication::Shutdown(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Programs/UnrealInsights/Private/UserInterfaceCommand.h b/Engine/Source/Programs/UnrealInsights/Private/UserInterfaceCommand.h new file mode 100644 index 000000000000..de7b2aa48523 --- /dev/null +++ b/Engine/Source/Programs/UnrealInsights/Private/UserInterfaceCommand.h @@ -0,0 +1,37 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +class SDockTab; +class FSpawnTabArgs; + +class FUserInterfaceCommand +{ +public: + + /** Executes the command. */ + static void Run(); + +protected: + + /** + * Initializes the Slate application. + * + * @param LayoutInit The path to the layout configuration file. + */ + static void InitializeSlateApplication(const FString& LayoutIni); + + /** + * Shuts down the Slate application. + * + * @param LayoutInit The path to the layout configuration file. + */ + static void ShutdownSlateApplication(const FString& LayoutIni); + +private: + + /** Callback for spawning tabs. */ + TSharedRef HandleTabManagerSpawnTab(const FSpawnTabArgs& Args, FName TabIdentifier) const; +}; diff --git a/Engine/Source/Programs/UnrealInsights/Private/Windows/WindowsUnrealInsightsMain.cpp b/Engine/Source/Programs/UnrealInsights/Private/Windows/WindowsUnrealInsightsMain.cpp new file mode 100644 index 000000000000..38b543375004 --- /dev/null +++ b/Engine/Source/Programs/UnrealInsights/Private/Windows/WindowsUnrealInsightsMain.cpp @@ -0,0 +1,66 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "UnrealInsightsMain.h" +#include "HAL/ExceptionHandling.h" +#include "Windows/WindowsHWrapper.h" +#include "LaunchEngineLoop.h" +#include "Misc/CommandLine.h" +#include "Misc/OutputDeviceError.h" + +/** + * The main application entry point for Windows platforms. + * + * @param hInInstance Handle to the current instance of the application. + * @param hPrevInstance Handle to the previous instance of the application (always NULL). + * @param lpCmdLine Command line for the application. + * @param nShowCmd Specifies how the window is to be shown. + * @return Application's exit value. + */ +int32 WINAPI WinMain(HINSTANCE hInInstance, HINSTANCE hPrevInstance, char* lpCmdLine, int32 nShowCmd) +{ + hInstance = hInInstance; + + const TCHAR* CmdLine = ::GetCommandLineW(); + CmdLine = FCommandLine::RemoveExeName(CmdLine); + +#if !UE_BUILD_SHIPPING + if (FParse::Param(CmdLine, TEXT("crashreports"))) + { + GAlwaysReportCrash = true; + } +#endif + + int32 ErrorLevel = 0; + +#if UE_BUILD_DEBUG + if (!GAlwaysReportCrash) +#else + if (FPlatformMisc::IsDebuggerPresent() && !GAlwaysReportCrash) +#endif + { + ErrorLevel = UnrealInsightsMain(CmdLine); + } + else + { +#if !PLATFORM_SEH_EXCEPTIONS_DISABLED + __try +#endif + { + GIsGuarded = 1; + ErrorLevel = UnrealInsightsMain(CmdLine); + GIsGuarded = 0; + } +#if !PLATFORM_SEH_EXCEPTIONS_DISABLED + __except (ReportCrash(GetExceptionInformation())) + { + ErrorLevel = 1; + GError->HandleError(); + FPlatformMisc::RequestExit(true); + } +#endif + } + + FEngineLoop::AppExit(); + + return ErrorLevel; +} diff --git a/Engine/Source/Programs/UnrealInsights/UnrealInsights.Build.cs b/Engine/Source/Programs/UnrealInsights/UnrealInsights.Build.cs new file mode 100644 index 000000000000..0e0c545b9c7a --- /dev/null +++ b/Engine/Source/Programs/UnrealInsights/UnrealInsights.Build.cs @@ -0,0 +1,68 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class UnrealInsights : ModuleRules +{ + public UnrealInsights(ReadOnlyTargetRules Target) : base(Target) + { + PublicIncludePaths.Add("Runtime/Launch/Public"); + PrivateIncludePaths.Add("Runtime/Launch/Private"); // For LaunchEngineLoop.cpp include + + PrivateDependencyModuleNames.AddRange( + new string[] { + "AppFramework", + //"AutomationController", + "Core", + "ApplicationCore", + "CoreUObject", + "Projects", + "Slate", + "SlateCore", + "SourceCodeAccess", + "StandaloneRenderer", + "TraceInsights", + "TraceServices" + } + ); + + PrivateIncludePathModuleNames.AddRange( + new string[] { + "SlateReflector" + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] { + "SlateReflector" + } + ); + + if (Target.Platform == UnrealTargetPlatform.Mac) + { + PrivateDependencyModuleNames.Add("XCodeSourceCodeAccess"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "CEF3"); + } + else if (Target.Platform == UnrealTargetPlatform.Win64) + { + PrivateDependencyModuleNames.Add("VisualStudioSourceCodeAccess"); + } + else if (Target.Platform == UnrealTargetPlatform.IOS || Target.Platform == UnrealTargetPlatform.TVOS) + { + PrivateDependencyModuleNames.AddRange( + new string [] { + "NetworkFile", + "StreamingFile" + } + ); + } + else if (Target.Platform == UnrealTargetPlatform.Linux) + { + PrivateDependencyModuleNames.AddRange( + new string[] { + "UnixCommonStartup" + } + ); + } + } +} diff --git a/Engine/Source/Programs/UnrealInsights/UnrealInsights.Target.cs b/Engine/Source/Programs/UnrealInsights/UnrealInsights.Target.cs new file mode 100644 index 000000000000..9d9c7e6e7659 --- /dev/null +++ b/Engine/Source/Programs/UnrealInsights/UnrealInsights.Target.cs @@ -0,0 +1,29 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; + +[SupportedPlatforms("Win64")] +public class UnrealInsightsTarget : TargetRules +{ + public UnrealInsightsTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Program; + LinkType = TargetLinkType.Modular; // TargetLinkType.Monolithic; + + LaunchModuleName = "UnrealInsights"; + ExtraModuleNames.Add("EditorStyle"); + + bBuildDeveloperTools = false; + bCompileAgainstEngine = false; + bCompileAgainstCoreUObject = true; + bForceBuildTargetPlatforms = true; + bCompileWithStatsWithoutEngine = true; + bCompileWithPluginSupport = true; + + // For UI functionality + bBuildDeveloperTools = true; + + bHasExports = false; + } +} diff --git a/Engine/Source/Runtime/AIModule/Classes/AISubsystem.h b/Engine/Source/Runtime/AIModule/Classes/AISubsystem.h index 7e566a8f1a7e..09ff9aaa4680 100644 --- a/Engine/Source/Runtime/AIModule/Classes/AISubsystem.h +++ b/Engine/Source/Runtime/AIModule/Classes/AISubsystem.h @@ -30,7 +30,7 @@ public: virtual void Tick(float DeltaTime) override {} virtual ETickableTickType GetTickableTickType() const override; virtual TStatId GetStatId() const override; - // FTickableGameObject begin + // FTickableGameObject end UWorld* GetWorldFast() const { return AISystem ? AISystem->GetOuterWorld() : GetOuter()->GetWorld(); } }; diff --git a/Engine/Source/Runtime/AIModule/Classes/AITypes.h b/Engine/Source/Runtime/AIModule/Classes/AITypes.h index 761520239d84..241a9aa72145 100644 --- a/Engine/Source/Runtime/AIModule/Classes/AITypes.h +++ b/Engine/Source/Runtime/AIModule/Classes/AITypes.h @@ -57,8 +57,7 @@ namespace EAIOptionFlag enum Type { Default, - Enable UMETA(DisplayName = "Yes"), // UHT was complaining when tried to use True as value instead of Enable - + Enable UMETA(DisplayName = "Yes"), Disable UMETA(DisplayName = "No"), MAX UMETA(Hidden) diff --git a/Engine/Source/Runtime/AIModule/Classes/EnvironmentQuery/EnvQueryManager.h b/Engine/Source/Runtime/AIModule/Classes/EnvironmentQuery/EnvQueryManager.h index bc1bb7a1e159..f5b54b915487 100644 --- a/Engine/Source/Runtime/AIModule/Classes/EnvironmentQuery/EnvQueryManager.h +++ b/Engine/Source/Runtime/AIModule/Classes/EnvironmentQuery/EnvQueryManager.h @@ -160,8 +160,11 @@ class AIMODULE_API UEnvQueryManager : public UAISubsystem, public FSelfRegisteri // makes sure we don't have any UEnvQueryManager instances serialized in. // Any loaded instance will get marked as PendingKill virtual void PostLoad() override; - virtual void PostInitProperties() override; + virtual bool IsDestructionThreadSafe() const + { + return false; + } // FTickableGameObject begin virtual void Tick(float DeltaTime) override; diff --git a/Engine/Source/Runtime/AIModule/Classes/EnvironmentQuery/EnvQueryTypes.h b/Engine/Source/Runtime/AIModule/Classes/EnvironmentQuery/EnvQueryTypes.h index ad2314848b2a..8e5cd558e197 100644 --- a/Engine/Source/Runtime/AIModule/Classes/EnvironmentQuery/EnvQueryTypes.h +++ b/Engine/Source/Runtime/AIModule/Classes/EnvironmentQuery/EnvQueryTypes.h @@ -426,7 +426,8 @@ struct AIMODULE_API FEnvOverlapData OverlapChannel(ECC_WorldStatic), OverlapShape(EEnvOverlapShape::Box), bOnlyBlockingHits(true), - bOverlapComplex(false) + bOverlapComplex(false), + bSkipOverlapQuerier(false) { } @@ -461,6 +462,10 @@ struct AIMODULE_API FEnvOverlapData /** if set, overlap will run on complex collisions */ UPROPERTY(EditDefaultsOnly, Category = Overlap, AdvancedDisplay) uint32 bOverlapComplex : 1; + + /** if set, overlap will skip querier context hits */ + UPROPERTY(EditDefaultsOnly, Category = Overlap, AdvancedDisplay) + uint32 bSkipOverlapQuerier : 1; }; ////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Runtime/AIModule/Classes/EnvironmentQuery/Tests/EnvQueryTest_Overlap.h b/Engine/Source/Runtime/AIModule/Classes/EnvironmentQuery/Tests/EnvQueryTest_Overlap.h index 28822b170ce1..ad9748c6757d 100644 --- a/Engine/Source/Runtime/AIModule/Classes/EnvironmentQuery/Tests/EnvQueryTest_Overlap.h +++ b/Engine/Source/Runtime/AIModule/Classes/EnvironmentQuery/Tests/EnvQueryTest_Overlap.h @@ -28,8 +28,12 @@ class UEnvQueryTest_Overlap : public UEnvQueryTest protected: - DECLARE_DELEGATE_RetVal_SixParams(bool, FRunOverlapSignature, const FVector&, const FCollisionShape&, AActor*, UWorld*, enum ECollisionChannel, const FCollisionQueryParams&); + bool RunOverlap(const FVector& ItemPos, const FCollisionShape& CollisionShape, const TArray& IgnoredActors, UWorld* World, enum ECollisionChannel Channel, const FCollisionQueryParams& Params) const; + bool RunOverlapBlocking(const FVector& ItemPos, const FCollisionShape& CollisionShape, const TArray& IgnoredActors, UWorld* World, enum ECollisionChannel Channel, const FCollisionQueryParams& Params) const; + UE_DEPRECATED(4.23, "This method is no longer called by RunTest and has been replaced by an overload using a list of actors to ignore. It now calls that overload but you should use the new overload instead.") bool RunOverlap(const FVector& ItemPos, const FCollisionShape& CollisionShape, AActor* ItemActor, UWorld* World, enum ECollisionChannel Channel, const FCollisionQueryParams& Params); + + UE_DEPRECATED(4.23, "This method is no longer called by RunTest and has been replaced by an overload using a list of actors to ignore. It now calls that overload but you should use the new overload instead.") bool RunOverlapBlocking(const FVector& ItemPos, const FCollisionShape& CollisionShape, AActor* ItemActor, UWorld* World, enum ECollisionChannel Channel, const FCollisionQueryParams& Params); }; diff --git a/Engine/Source/Runtime/AIModule/Classes/GenericTeamAgentInterface.h b/Engine/Source/Runtime/AIModule/Classes/GenericTeamAgentInterface.h index c680e11c3d27..a141bfaa615c 100644 --- a/Engine/Source/Runtime/AIModule/Classes/GenericTeamAgentInterface.h +++ b/Engine/Source/Runtime/AIModule/Classes/GenericTeamAgentInterface.h @@ -8,7 +8,7 @@ #include "GameFramework/Actor.h" #include "GenericTeamAgentInterface.generated.h" -UENUM() +UENUM(BlueprintType) namespace ETeamAttitude { enum Type @@ -49,18 +49,19 @@ public: static ETeamAttitude::Type GetAttitude(const AActor* A, const AActor* B); static ETeamAttitude::Type GetAttitude(FGenericTeamId TeamA, FGenericTeamId TeamB) { - return AttitudeSolverImpl ? (*AttitudeSolverImpl)(TeamA, TeamB) : ETeamAttitude::Neutral; + return AttitudeSolverImpl ? (AttitudeSolverImpl)(TeamA, TeamB) : ETeamAttitude::Neutral; } - typedef ETeamAttitude::Type FAttitudeSolverFunction(FGenericTeamId, FGenericTeamId); + typedef TFunction FAttitudeSolverFunction; - static void SetAttitudeSolver(FAttitudeSolverFunction* Solver); + static void SetAttitudeSolver(const FAttitudeSolverFunction& Solver); + static void ResetAttitudeSolver(); protected: // the default implementation makes all teams hostile // @note that for consistency IGenericTeamAgentInterface should be using the same function // (by default it does) - static FAttitudeSolverFunction* AttitudeSolverImpl; + static FAttitudeSolverFunction AttitudeSolverImpl; public: static const FGenericTeamId NoTeam; diff --git a/Engine/Source/Runtime/AIModule/Classes/Perception/AIPerceptionComponent.h b/Engine/Source/Runtime/AIModule/Classes/Perception/AIPerceptionComponent.h index 3f5ba61b56f0..f7633a022450 100644 --- a/Engine/Source/Runtime/AIModule/Classes/Perception/AIPerceptionComponent.h +++ b/Engine/Source/Runtime/AIModule/Classes/Perception/AIPerceptionComponent.h @@ -91,6 +91,7 @@ struct AIMODULE_API FActorPerceptionInfo return false; } + /** Indicates currently live (visible) stimulus from any sense */ bool HasAnyCurrentStimulus() const { for (const FAIStimulus& Stimulus : LastSensedStimuli) @@ -105,27 +106,47 @@ struct AIMODULE_API FActorPerceptionInfo return false; } - /** @note will return FAISystem::InvalidLocation if given sense has never registered related Target actor */ + /** Retrieves location of the last sensed stimuli for a given sense + * @param Sense The AISenseID of the sense + * + * @return Location of the last sensed stimuli or FAISystem::InvalidLocation if given sense has never registered related Target actor or if last stimuli has expired. + */ FORCEINLINE FVector GetStimulusLocation(FAISenseID Sense) const { - return LastSensedStimuli.IsValidIndex(Sense) && LastSensedStimuli[Sense].GetAge() < FAIStimulus::NeverHappenedAge ? LastSensedStimuli[Sense].StimulusLocation : FAISystem::InvalidLocation; + return LastSensedStimuli.IsValidIndex(Sense) && (LastSensedStimuli[Sense].IsValid() && (LastSensedStimuli[Sense].IsExpired() == false)) ? LastSensedStimuli[Sense].StimulusLocation : FAISystem::InvalidLocation; } + /** Retrieves receiver location of the last sense stimuli for a given sense + * @param Sense The AISenseID of the sense + * + * @return Location of the receiver for the last sensed stimuli or FAISystem::InvalidLocation if given sense has never registered related Target actor or last stimuli has expired. + */ FORCEINLINE FVector GetReceiverLocation(FAISenseID Sense) const { - return LastSensedStimuli.IsValidIndex(Sense) && LastSensedStimuli[Sense].GetAge() < FAIStimulus::NeverHappenedAge ? LastSensedStimuli[Sense].ReceiverLocation : FAISystem::InvalidLocation; + return LastSensedStimuli.IsValidIndex(Sense) && (LastSensedStimuli[Sense].IsValid() && (LastSensedStimuli[Sense].IsExpired() == false)) ? LastSensedStimuli[Sense].ReceiverLocation : FAISystem::InvalidLocation; } + UE_DEPRECATED(4.23, "This method is identical to IsSenseActive and will be removed in future versions. Please use IsSenseActive to check for a currently active stimuli or HasKnownStimulusOfSense for an active or remembered stimuli.") FORCEINLINE bool IsSenseRegistered(FAISenseID Sense) const { - return LastSensedStimuli.IsValidIndex(Sense) && LastSensedStimuli[Sense].WasSuccessfullySensed() && (LastSensedStimuli[Sense].GetAge() < FAIStimulus::NeverHappenedAge); + return LastSensedStimuli.IsValidIndex(Sense) && LastSensedStimuli[Sense].IsActive(); } + /** Indicates a currently active or "remembered" stimuli for a given sense + * @param Sense The AISenseID of the sense + * + * @return True if a target has been registered (even if not currently sensed) for the given sense and the stimuli is not expired. + */ FORCEINLINE bool HasKnownStimulusOfSense(FAISenseID Sense) const { - return LastSensedStimuli.IsValidIndex(Sense) && (LastSensedStimuli[Sense].GetAge() < FAIStimulus::NeverHappenedAge); + return LastSensedStimuli.IsValidIndex(Sense) && (LastSensedStimuli[Sense].IsValid() && (LastSensedStimuli[Sense].IsExpired() == false)); } + /** Indicates a currently active stimuli for a given sense + * @param Sense The AISenseID of the sense + * + * @return True if a target is still sensed for the given sense and the stimuli is not expired. + */ FORCEINLINE bool IsSenseActive(FAISenseID Sense) const { return LastSensedStimuli.IsValidIndex(Sense) && LastSensedStimuli[Sense].IsActive(); diff --git a/Engine/Source/Runtime/AIModule/Classes/Perception/AIPerceptionSystem.h b/Engine/Source/Runtime/AIModule/Classes/Perception/AIPerceptionSystem.h index e34a07667941..f84443e3adf8 100644 --- a/Engine/Source/Runtime/AIModule/Classes/Perception/AIPerceptionSystem.h +++ b/Engine/Source/Runtime/AIModule/Classes/Perception/AIPerceptionSystem.h @@ -39,6 +39,12 @@ public: virtual TStatId GetStatId() const override; // FTickableGameObject end + // UObject begin + virtual bool IsDestructionThreadSafe() const + { + return false; + } + // UObject end protected: AIPerception::FListenerMap ListenerContainer; diff --git a/Engine/Source/Runtime/AIModule/Classes/Perception/AIPerceptionTypes.h b/Engine/Source/Runtime/AIModule/Classes/Perception/AIPerceptionTypes.h index fb5977bb09f4..65c4895a1644 100644 --- a/Engine/Source/Runtime/AIModule/Classes/Perception/AIPerceptionTypes.h +++ b/Engine/Source/Runtime/AIModule/Classes/Perception/AIPerceptionTypes.h @@ -192,9 +192,9 @@ public: FORCEINLINE bool IsExpired() const { return bExpired; } FORCEINLINE void MarkNoLongerSensed() { bSuccessfullySensed = false; } FORCEINLINE void MarkExpired() { bExpired = true; MarkNoLongerSensed(); } - FORCEINLINE bool IsActive() const { return WasSuccessfullySensed() == true && GetAge() < NeverHappenedAge; } + FORCEINLINE bool IsActive() const { return WasSuccessfullySensed() == true && IsValid(); } FORCEINLINE bool WantsToNotifyOnlyOnPerceptionChange() const { return bWantsToNotifyOnlyOnValueChange; } - FORCEINLINE bool IsValid() const { return Type != FAISenseID::InvalidID(); } + FORCEINLINE bool IsValid() const { return Type != FAISenseID::InvalidID() && GetAge() < NeverHappenedAge; } #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) FString GetDebugDescription() const; diff --git a/Engine/Source/Runtime/AIModule/Classes/Perception/AISense.h b/Engine/Source/Runtime/AIModule/Classes/Perception/AISense.h index 05f1a6fd0d66..0027e0f6267c 100644 --- a/Engine/Source/Runtime/AIModule/Classes/Perception/AISense.h +++ b/Engine/Source/Runtime/AIModule/Classes/Perception/AISense.h @@ -23,8 +23,9 @@ class AIMODULE_API UAISense : public UObject static const float SuspendNextUpdate; protected: - /** age past which stimulus of this sense are "forgotten"*/ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AI Perception", config) + UE_DEPRECATED(4.23, "This property will be removed in future versions. Use AISenseConfig::MaxAge instead.") + /** age past which stimulus of this sense are "forgotten". (DEPRECATED: This property will be removed in future versions. Use AISenseConfig::MaxAge instead.)*/ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI Perception", config) float DefaultExpirationAge; UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "AI Perception", config) @@ -115,7 +116,13 @@ public: FORCEINLINE void OnListenerUpdate(const FPerceptionListener& NewListener) { OnListenerUpdateDelegate.ExecuteIfBound(NewListener); } FORCEINLINE void OnListenerRemoved(const FPerceptionListener& NewListener) { OnListenerRemovedDelegate.ExecuteIfBound(NewListener); } - FORCEINLINE float GetDefaultExpirationAge() const { return DefaultExpirationAge; } + UE_DEPRECATED(4.23, "This method will be removed in future versions. Perception relies on AISenseConfig::MaxAge so the value returned is no longer used by the perception system.") + FORCEINLINE float GetDefaultExpirationAge() const + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + return DefaultExpirationAge; + PRAGMA_ENABLE_DEPRECATION_WARNINGS + } bool WantsNewPawnNotification() const { return bWantsNewPawnNotification; } bool ShouldAutoRegisterAllPawnsAsSources() const { return bAutoRegisterAllPawnsAsSources; } diff --git a/Engine/Source/Runtime/AIModule/Classes/Perception/AISenseConfig_Sight.h b/Engine/Source/Runtime/AIModule/Classes/Perception/AISenseConfig_Sight.h index 9c48de33f442..417b32d9570f 100644 --- a/Engine/Source/Runtime/AIModule/Classes/Perception/AISenseConfig_Sight.h +++ b/Engine/Source/Runtime/AIModule/Classes/Perception/AISenseConfig_Sight.h @@ -41,11 +41,15 @@ public: FAISenseAffiliationFilter DetectionByAffiliation; /** If not an InvalidRange (which is the default), we will always be able to see the target that has already been seen if they are within this range of their last seen location. */ - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config, meta = (UIMin = 0.0, ClampMin = 0.0)) + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config) float AutoSuccessRangeFromLastSeenLocation; virtual TSubclassOf GetSenseImplementation() const override; +#if WITH_EDITOR + virtual void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; +#endif // WITH_EDITOR + #if WITH_GAMEPLAY_DEBUGGER virtual void DescribeSelfToGameplayDebugger(const UAIPerceptionComponent* PerceptionComponent, FGameplayDebuggerCategory* DebuggerCategory) const; #endif // WITH_GAMEPLAY_DEBUGGER diff --git a/Engine/Source/Runtime/AIModule/Classes/Perception/AISense_Sight.h b/Engine/Source/Runtime/AIModule/Classes/Perception/AISense_Sight.h index 3d0147901538..669b77f953a7 100644 --- a/Engine/Source/Runtime/AIModule/Classes/Perception/AISense_Sight.h +++ b/Engine/Source/Runtime/AIModule/Classes/Perception/AISense_Sight.h @@ -179,6 +179,7 @@ protected: void OnListenerRemovedImpl(const FPerceptionListener& UpdatedListener); void GenerateQueriesForListener(const FPerceptionListener& Listener, const FDigestedSightProperties& PropertyDigest); + void GenerateQueriesForListener(const FPerceptionListener& Listener, const FDigestedSightProperties& PropertyDigest, TFunctionRef OnAddedFunc); enum FQueriesOperationPostProcess { @@ -186,10 +187,13 @@ protected: Sort }; void RemoveAllQueriesByListener(const FPerceptionListener& Listener, FQueriesOperationPostProcess PostProcess); + void RemoveAllQueriesByListener(const FPerceptionListener& Listener, FQueriesOperationPostProcess PostProcess, TFunctionRef OnRemoveFunc); void RemoveAllQueriesToTarget(const FAISightTarget::FTargetId& TargetId, FQueriesOperationPostProcess PostProcess); + void RemoveAllQueriesToTarget(const FAISightTarget::FTargetId& TargetId, FQueriesOperationPostProcess PostProcess, TFunctionRef OnRemoveFunc); /** returns information whether new LoS queries have been added */ bool RegisterTarget(AActor& TargetActor, FQueriesOperationPostProcess PostProcess); + bool RegisterTarget(AActor& TargetActor, FQueriesOperationPostProcess PostProcess, TFunctionRef OnAddedFunc); FORCEINLINE void SortQueries() { SightQueryQueue.Sort(FAISightQuery::FSortPredicate()); } diff --git a/Engine/Source/Runtime/AIModule/Classes/Tasks/AITask_MoveTo.h b/Engine/Source/Runtime/AIModule/Classes/Tasks/AITask_MoveTo.h index 07678fc2a9e7..ef0a53acd995 100644 --- a/Engine/Source/Runtime/AIModule/Classes/Tasks/AITask_MoveTo.h +++ b/Engine/Source/Runtime/AIModule/Classes/Tasks/AITask_MoveTo.h @@ -30,10 +30,10 @@ public: EPathFollowingResult::Type GetMoveResult() const { return MoveResult; } bool WasMoveSuccessful() const { return MoveResult == EPathFollowingResult::Success; } - UFUNCTION(BlueprintCallable, Category = "AI|Tasks", meta = (AdvancedDisplay = "AcceptanceRadius,StopOnOverlap,AcceptPartialPath,bUsePathfinding,bUseContinuosGoalTracking", DefaultToSelf = "Controller", BlueprintInternalUseOnly = "TRUE", DisplayName = "Move To Location or Actor")) + UFUNCTION(BlueprintCallable, Category = "AI|Tasks", meta = (AdvancedDisplay = "AcceptanceRadius,StopOnOverlap,AcceptPartialPath,bUsePathfinding,bUseContinuosGoalTracking,ProjectGoalOnNavigation", DefaultToSelf = "Controller", BlueprintInternalUseOnly = "TRUE", DisplayName = "Move To Location or Actor")) static UAITask_MoveTo* AIMoveTo(AAIController* Controller, FVector GoalLocation, AActor* GoalActor = nullptr, float AcceptanceRadius = -1.f, EAIOptionFlag::Type StopOnOverlap = EAIOptionFlag::Default, EAIOptionFlag::Type AcceptPartialPath = EAIOptionFlag::Default, - bool bUsePathfinding = true, bool bLockAILogic = true, bool bUseContinuosGoalTracking = false); + bool bUsePathfinding = true, bool bLockAILogic = true, bool bUseContinuosGoalTracking = false, EAIOptionFlag::Type ProjectGoalOnNavigation = EAIOptionFlag::Default); UE_DEPRECATED(4.12, "This function is now depreacted, please use version with FAIMoveRequest parameter") void SetUp(AAIController* Controller, FVector GoalLocation, AActor* GoalActor = nullptr, float AcceptanceRadius = -1.f, bool bUsePathfinding = true, EAIOptionFlag::Type StopOnOverlap = EAIOptionFlag::Default, EAIOptionFlag::Type AcceptPartialPath = EAIOptionFlag::Default); diff --git a/Engine/Source/Runtime/AIModule/Private/AIController.cpp b/Engine/Source/Runtime/AIModule/Private/AIController.cpp index 9d445a242120..e95d2e9b83ec 100644 --- a/Engine/Source/Runtime/AIModule/Private/AIController.cpp +++ b/Engine/Source/Runtime/AIModule/Private/AIController.cpp @@ -633,7 +633,15 @@ FPathFollowingRequestResult AAIController::MoveTo(const FAIMoveRequest& MoveRequ if (NavSys && !NavSys->ProjectPointToNavigation(MoveRequest.GetGoalLocation(), ProjectedLocation, INVALID_NAVEXTENT, &AgentProps)) { - UE_VLOG_LOCATION(this, LogAINavigation, Error, MoveRequest.GetGoalLocation(), 30.f, FColor::Red, TEXT("AAIController::MoveTo failed to project destination location to navmesh")); + if (MoveRequest.IsUsingPathfinding()) + { + UE_VLOG_LOCATION(this, LogAINavigation, Error, MoveRequest.GetGoalLocation(), 30.f, FColor::Red, TEXT("AAIController::MoveTo failed to project destination location to navmesh")); + } + else + { + UE_VLOG_LOCATION(this, LogAINavigation, Error, MoveRequest.GetGoalLocation(), 30.f, FColor::Red, TEXT("AAIController::MoveTo failed to project destination location to navmesh, path finding is disabled perhaps disable goal projection ?")); + } + bCanRequestMove = false; } diff --git a/Engine/Source/Runtime/AIModule/Private/AIInterfaces.cpp b/Engine/Source/Runtime/AIModule/Private/AIInterfaces.cpp index bbf9b4f8500f..3929a1455e61 100644 --- a/Engine/Source/Runtime/AIModule/Private/AIInterfaces.cpp +++ b/Engine/Source/Runtime/AIModule/Private/AIInterfaces.cpp @@ -33,7 +33,7 @@ namespace } } -FGenericTeamId::FAttitudeSolverFunction* FGenericTeamId::AttitudeSolverImpl = &DefaultTeamAttitudeSolver; +FGenericTeamId::FAttitudeSolverFunction FGenericTeamId::AttitudeSolverImpl = &DefaultTeamAttitudeSolver; ETeamAttitude::Type FGenericTeamId::GetAttitude(const AActor* A, const AActor* B) { @@ -42,11 +42,16 @@ ETeamAttitude::Type FGenericTeamId::GetAttitude(const AActor* A, const AActor* B return TeamAgentA == NULL || B == NULL ? ETeamAttitude::Neutral : TeamAgentA->GetTeamAttitudeTowards(*B); } -void FGenericTeamId::SetAttitudeSolver(FGenericTeamId::FAttitudeSolverFunction* Solver) +void FGenericTeamId::SetAttitudeSolver(const FGenericTeamId::FAttitudeSolverFunction& Solver) { AttitudeSolverImpl = Solver; } +void FGenericTeamId::ResetAttitudeSolver() +{ + AttitudeSolverImpl = &DefaultTeamAttitudeSolver; +} + //----------------------------------------------------------------------// // UGenericTeamAgentInterface //----------------------------------------------------------------------// diff --git a/Engine/Source/Runtime/AIModule/Private/BehaviorTree/BehaviorTreeComponent.cpp b/Engine/Source/Runtime/AIModule/Private/BehaviorTree/BehaviorTreeComponent.cpp index d8b3454084a1..998105f4ed23 100644 --- a/Engine/Source/Runtime/AIModule/Private/BehaviorTree/BehaviorTreeComponent.cpp +++ b/Engine/Source/Runtime/AIModule/Private/BehaviorTree/BehaviorTreeComponent.cpp @@ -239,7 +239,7 @@ void UBehaviorTreeComponent::StopTree(EBTStopMode::Type StopMode) return; } - FScopedBehaviorTreeLock(*this, FScopedBehaviorTreeLock::LockReentry); + FScopedBehaviorTreeLock ScopedLock(*this, FScopedBehaviorTreeLock::LockReentry); if (!bRequestedStop) { bRequestedStop = true; @@ -1211,7 +1211,7 @@ void UBehaviorTreeComponent::TickComponent(float DeltaTime, enum ELevelTick Tick } { - FScopedBehaviorTreeLock(*this, FScopedBehaviorTreeLock::LockTick); + FScopedBehaviorTreeLock ScopedLock(*this, FScopedBehaviorTreeLock::LockTick); // tick active parallel tasks (in execution order, before task) for (int32 InstanceIndex = 0; InstanceIndex < InstanceStack.Num(); InstanceIndex++) @@ -1784,7 +1784,7 @@ void UBehaviorTreeComponent::RegisterMessageObserver(const UBTTaskNode* TaskNode NodeIdx.InstanceIndex = InstanceStack.Num() - 1; TaskMessageObservers.Add(NodeIdx, - FAIMessageObserver::Create(this, MessageType, FOnAIMessage::CreateUObject(TaskNode, &UBTTaskNode::ReceivedMessage)) + FAIMessageObserver::Create(this, MessageType, FOnAIMessage::CreateUObject(const_cast(TaskNode), &UBTTaskNode::ReceivedMessage)) ); UE_VLOG(GetOwner(), LogBehaviorTree, Log, TEXT("Message[%s] observer added for %s"), @@ -1801,7 +1801,7 @@ void UBehaviorTreeComponent::RegisterMessageObserver(const UBTTaskNode* TaskNode NodeIdx.InstanceIndex = InstanceStack.Num() - 1; TaskMessageObservers.Add(NodeIdx, - FAIMessageObserver::Create(this, MessageType, RequestID, FOnAIMessage::CreateUObject(TaskNode, &UBTTaskNode::ReceivedMessage)) + FAIMessageObserver::Create(this, MessageType, RequestID, FOnAIMessage::CreateUObject(const_cast(TaskNode), &UBTTaskNode::ReceivedMessage)) ); UE_VLOG(GetOwner(), LogBehaviorTree, Log, TEXT("Message[%s:%d] observer added for %s"), diff --git a/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/EnvQueryInstance.cpp b/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/EnvQueryInstance.cpp index d1fc5ab2363b..cee915d24a78 100644 --- a/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/EnvQueryInstance.cpp +++ b/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/EnvQueryInstance.cpp @@ -403,7 +403,7 @@ void FEnvQueryInstance::ExecuteOneStep(float TimeLimit) // item generator uses this flag to alter the scoring behavior bPassOnSingleResult = (bDoingLastTest && Mode == EEnvQueryRunMode::SingleResult && TestObject->CanRunAsFinalCondition()); - if (bPassOnSingleResult) + if (bPassOnSingleResult && (CurrentTestStartingItem == 0)) { // Since we know we're the last test that is a final condition, if we were scoring previously we should sort the tests now before we test them bool bSortTests = false; diff --git a/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/EnvQueryTest.cpp b/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/EnvQueryTest.cpp index 2402c68948db..5732a8097afe 100644 --- a/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/EnvQueryTest.cpp +++ b/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/EnvQueryTest.cpp @@ -250,8 +250,11 @@ FText UEnvQueryTest::DescribeFloatTestParams() const } else { - FText ScoreSignDesc = (ScoringFactor.DefaultValue > 0) ? LOCTEXT("Greater", "greater") : LOCTEXT("Lesser", "lesser"); - FText ScoreValueDesc = FText::AsNumber(FMath::Abs(ScoringFactor.DefaultValue), &NumberFormattingOptions); + const bool bScalesUp = (ScoringEquation == EEnvTestScoreEquation::InverseLinear) + ? (ScoringFactor.DefaultValue < 0) + : (ScoringFactor.DefaultValue > 0); + const FText ScoreSignDesc = bScalesUp ? LOCTEXT("Greater", "greater") : LOCTEXT("Lesser", "lesser"); + const FText ScoreValueDesc = FText::AsNumber(FMath::Abs(ScoringFactor.DefaultValue), &NumberFormattingOptions); ScoreDesc = FText::Format(FText::FromString("{0} {1} [x{2}]"), LOCTEXT("ScorePrefer", "prefer"), ScoreSignDesc, ScoreValueDesc); } diff --git a/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/Tests/EnvQueryTest_Overlap.cpp b/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/Tests/EnvQueryTest_Overlap.cpp index d3f7f95c3205..f532d3526c5a 100644 --- a/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/Tests/EnvQueryTest_Overlap.cpp +++ b/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/Tests/EnvQueryTest_Overlap.cpp @@ -6,6 +6,7 @@ #include "WorldCollision.h" #include "Engine/World.h" #include "EnvironmentQuery/Items/EnvQueryItemType_VectorBase.h" +#include "EnvironmentQuery/Contexts/EnvQueryContext_Querier.h" #define LOCTEXT_NAMESPACE "EnvQueryGenerator" @@ -47,23 +48,25 @@ void UEnvQueryTest_Overlap::RunTest(FEnvQueryInstance& QueryInstance) const return; } - FRunOverlapSignature OverlapFunc; - if (OverlapData.bOnlyBlockingHits) + typedef bool(UEnvQueryTest_Overlap::*FRunOverlapSignature)(const FVector&, const FCollisionShape&, const TArray&, UWorld*, enum ECollisionChannel, const FCollisionQueryParams&) const; + FRunOverlapSignature OverlapFunc = (OverlapData.bOnlyBlockingHits ? (FRunOverlapSignature)&UEnvQueryTest_Overlap::RunOverlapBlocking : (FRunOverlapSignature)&UEnvQueryTest_Overlap::RunOverlap); + + TArray IgnoredActors; + if (OverlapData.bSkipOverlapQuerier) { - OverlapFunc.BindUObject(this, &UEnvQueryTest_Overlap::RunOverlapBlocking); - } - else - { - OverlapFunc.BindUObject(this, &UEnvQueryTest_Overlap::RunOverlap); + QueryInstance.PrepareContext(UEnvQueryContext_Querier::StaticClass(), IgnoredActors); } for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It) { const FVector ItemLocation = GetItemLocation(QueryInstance, It.GetIndex()); AActor* ItemActor = GetItemActor(QueryInstance, It.GetIndex()); - - const bool bHit = OverlapFunc.Execute(ItemLocation + OverlapData.ShapeOffset, CollisionShape, ItemActor, QueryInstance.World, OverlapCollisionChannel, OverlapParams); + IgnoredActors.Push(ItemActor); + + const bool bHit = (this->*OverlapFunc)(ItemLocation + OverlapData.ShapeOffset, CollisionShape, IgnoredActors, QueryInstance.World, OverlapCollisionChannel, OverlapParams); It.SetScore(TestPurpose, FilterType, bHit, bWantsHit); + + IgnoredActors.Pop(/*bAllowShrinking=*/false); } } @@ -109,18 +112,32 @@ FText UEnvQueryTest_Overlap::GetDescriptionDetails() const } bool UEnvQueryTest_Overlap::RunOverlap(const FVector& ItemPos, const FCollisionShape& CollisionShape, AActor* ItemActor, UWorld* World, enum ECollisionChannel Channel, const FCollisionQueryParams& Params) +{ + TArray Actors; + Actors.Add(ItemActor); + return const_cast(this)->RunOverlap(ItemPos, CollisionShape, Actors, World, Channel, Params); +} + +bool UEnvQueryTest_Overlap::RunOverlapBlocking(const FVector& ItemPos, const FCollisionShape& CollisionShape, AActor* ItemActor, UWorld* World, enum ECollisionChannel Channel, const FCollisionQueryParams& Params) +{ + TArray Actors; + Actors.Add(ItemActor); + return const_cast(this)->RunOverlapBlocking(ItemPos, CollisionShape, Actors, World, Channel, Params); +} + +bool UEnvQueryTest_Overlap::RunOverlap(const FVector& ItemPos, const FCollisionShape& CollisionShape, const TArray& IgnoredActors, UWorld* World, enum ECollisionChannel Channel, const FCollisionQueryParams& Params) const { FCollisionQueryParams OverlapParams(Params); - OverlapParams.AddIgnoredActor(ItemActor); + OverlapParams.AddIgnoredActors(IgnoredActors); const bool bHit = World->OverlapAnyTestByChannel(ItemPos, FQuat::Identity, Channel, CollisionShape, OverlapParams); return bHit; } -bool UEnvQueryTest_Overlap::RunOverlapBlocking(const FVector& ItemPos, const FCollisionShape& CollisionShape, AActor* ItemActor, UWorld* World, enum ECollisionChannel Channel, const FCollisionQueryParams& Params) +bool UEnvQueryTest_Overlap::RunOverlapBlocking(const FVector& ItemPos, const FCollisionShape& CollisionShape, const TArray& IgnoredActors, UWorld* World, enum ECollisionChannel Channel, const FCollisionQueryParams& Params) const { FCollisionQueryParams OverlapParams(Params); - OverlapParams.AddIgnoredActor(ItemActor); + OverlapParams.AddIgnoredActors(IgnoredActors); const bool bHit = World->OverlapBlockingTestByChannel(ItemPos, FQuat::Identity, Channel, CollisionShape, OverlapParams); return bHit; diff --git a/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/Tests/EnvQueryTest_Trace.cpp b/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/Tests/EnvQueryTest_Trace.cpp index 48f563050db2..7eb6e0c4c6a5 100644 --- a/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/Tests/EnvQueryTest_Trace.cpp +++ b/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/Tests/EnvQueryTest_Trace.cpp @@ -53,19 +53,19 @@ void UEnvQueryTest_Trace::RunTest(FEnvQueryInstance& QueryInstance) const switch (TraceData.TraceShape) { case EEnvTraceShape::Line: - TraceFunc.BindUObject(this, bTraceToItem ? &UEnvQueryTest_Trace::RunLineTraceTo : &UEnvQueryTest_Trace::RunLineTraceFrom); + TraceFunc.BindUObject(const_cast(this), bTraceToItem ? &UEnvQueryTest_Trace::RunLineTraceTo : &UEnvQueryTest_Trace::RunLineTraceFrom); break; case EEnvTraceShape::Box: - TraceFunc.BindUObject(this, bTraceToItem ? &UEnvQueryTest_Trace::RunBoxTraceTo : &UEnvQueryTest_Trace::RunBoxTraceFrom); + TraceFunc.BindUObject(const_cast(this), bTraceToItem ? &UEnvQueryTest_Trace::RunBoxTraceTo : &UEnvQueryTest_Trace::RunBoxTraceFrom); break; case EEnvTraceShape::Sphere: - TraceFunc.BindUObject(this, bTraceToItem ? &UEnvQueryTest_Trace::RunSphereTraceTo : &UEnvQueryTest_Trace::RunSphereTraceFrom); + TraceFunc.BindUObject(const_cast(this), bTraceToItem ? &UEnvQueryTest_Trace::RunSphereTraceTo : &UEnvQueryTest_Trace::RunSphereTraceFrom); break; case EEnvTraceShape::Capsule: - TraceFunc.BindUObject(this, bTraceToItem ? &UEnvQueryTest_Trace::RunCapsuleTraceTo : &UEnvQueryTest_Trace::RunCapsuleTraceFrom); + TraceFunc.BindUObject(const_cast(this), bTraceToItem ? &UEnvQueryTest_Trace::RunCapsuleTraceTo : &UEnvQueryTest_Trace::RunCapsuleTraceFrom); break; default: diff --git a/Engine/Source/Runtime/AIModule/Private/Navigation/NavLinkProxy.cpp b/Engine/Source/Runtime/AIModule/Private/Navigation/NavLinkProxy.cpp index 53845cbed776..9d522f8a3f18 100644 --- a/Engine/Source/Runtime/AIModule/Private/Navigation/NavLinkProxy.cpp +++ b/Engine/Source/Runtime/AIModule/Private/Navigation/NavLinkProxy.cpp @@ -155,11 +155,6 @@ void ANavLinkProxy::PostLoad() { Link.InitializeAreaClass(); } - - if (SmartLinkComp) - { - SmartLinkComp->SetNavigationRelevancy(bSmartLinkIsRelevant); - } } #if ENABLE_VISUAL_LOG diff --git a/Engine/Source/Runtime/AIModule/Private/Perception/AIPerceptionComponent.cpp b/Engine/Source/Runtime/AIModule/Private/Perception/AIPerceptionComponent.cpp index 3dd808719abc..0efe7579bc83 100644 --- a/Engine/Source/Runtime/AIModule/Private/Perception/AIPerceptionComponent.cpp +++ b/Engine/Source/Runtime/AIModule/Private/Perception/AIPerceptionComponent.cpp @@ -295,7 +295,7 @@ void UAIPerceptionComponent::GetHostileActors(TArray& OutActors) const if (bDeadDataFound) { FSimpleDelegateGraphTask::CreateAndDispatchWhenReady( - FSimpleDelegateGraphTask::FDelegate::CreateUObject(this, &UAIPerceptionComponent::RemoveDeadData), + FSimpleDelegateGraphTask::FDelegate::CreateUObject(const_cast(this), &UAIPerceptionComponent::RemoveDeadData), GET_STATID(STAT_FSimpleDelegateGraphTask_RequestingRemovalOfDeadPerceptionData), NULL, ENamedThreads::GameThread); } } @@ -334,7 +334,7 @@ const FActorPerceptionInfo* UAIPerceptionComponent::GetFreshestTrace(const FAISe if (bDeadDataFound) { FSimpleDelegateGraphTask::CreateAndDispatchWhenReady( - FSimpleDelegateGraphTask::FDelegate::CreateUObject(this, &UAIPerceptionComponent::RemoveDeadData), + FSimpleDelegateGraphTask::FDelegate::CreateUObject(const_cast(this), &UAIPerceptionComponent::RemoveDeadData), GET_STATID(STAT_FSimpleDelegateGraphTask_RequestingRemovalOfDeadPerceptionData), NULL, ENamedThreads::GameThread); } @@ -651,7 +651,7 @@ void UAIPerceptionComponent::GetCurrentlyPerceivedActors(TSubclassOf S OutActors.Reserve(PerceptualData.Num()); for (FActorPerceptionContainer::TConstIterator DataIt = GetPerceptualDataConstIterator(); DataIt; ++DataIt) { - const bool bCurrentlyPerceived = (SenseToUse == nullptr) ? DataIt->Value.HasAnyCurrentStimulus() : DataIt->Value.IsSenseRegistered(SenseID); + const bool bCurrentlyPerceived = (SenseToUse == nullptr) ? DataIt->Value.HasAnyCurrentStimulus() : DataIt->Value.IsSenseActive(SenseID); if (bCurrentlyPerceived) { if (DataIt->Value.Target.IsValid()) diff --git a/Engine/Source/Runtime/AIModule/Private/Perception/AIPerceptionTypes.cpp b/Engine/Source/Runtime/AIModule/Private/Perception/AIPerceptionTypes.cpp index 64831aed9178..59911658fa73 100644 --- a/Engine/Source/Runtime/AIModule/Private/Perception/AIPerceptionTypes.cpp +++ b/Engine/Source/Runtime/AIModule/Private/Perception/AIPerceptionTypes.cpp @@ -18,7 +18,9 @@ FPerceptionListenerCounter FAIGenericID::Counter = F const float FAIStimulus::NeverHappenedAge = FLT_MAX; FAIStimulus::FAIStimulus(const UAISense& Sense, float StimulusStrength, const FVector& InStimulusLocation, const FVector& InReceiverLocation, FResult Result, FName InStimulusTag) - : Age(0.f), Strength(Result == SensingSucceeded ? StimulusStrength : -1.f) + : Age(0.f) + , ExpirationAge(FAIStimulus::NeverHappenedAge) + , Strength(Result == SensingSucceeded ? StimulusStrength : -1.f) , StimulusLocation(InStimulusLocation) , ReceiverLocation(InReceiverLocation) , Tag(InStimulusTag) @@ -27,7 +29,6 @@ FAIStimulus::FAIStimulus(const UAISense& Sense, float StimulusStrength, const FV , bExpired(false) { Type = Sense.GetSenseID(); - ExpirationAge = Sense.GetDefaultExpirationAge(); } #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) diff --git a/Engine/Source/Runtime/AIModule/Private/Perception/AISense.cpp b/Engine/Source/Runtime/AIModule/Private/Perception/AISense.cpp index e4b7b55a0307..8382b13e8312 100644 --- a/Engine/Source/Runtime/AIModule/Private/Perception/AISense.cpp +++ b/Engine/Source/Runtime/AIModule/Private/Perception/AISense.cpp @@ -27,7 +27,9 @@ UAISense::UAISense(const FObjectInitializer& ObjectInitializer) , TimeUntilNextUpdate(SuspendNextUpdate) , SenseID(FAISenseID::InvalidID()) { + PRAGMA_DISABLE_DEPRECATION_WARNINGS DefaultExpirationAge = FAIStimulus::NeverHappenedAge; + PRAGMA_ENABLE_DEPRECATION_WARNINGS bNeedsForgettingNotification = false; @@ -173,7 +175,28 @@ UAISenseConfig_Sight::UAISenseConfig_Sight(const FObjectInitializer& ObjectIniti TSubclassOf UAISenseConfig_Sight::GetSenseImplementation() const { return *Implementation; +} + +#if WITH_EDITOR +void UAISenseConfig_Sight::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) +{ + static const FName NAME_AutoSuccessRangeFromLastSeenLocation = GET_MEMBER_NAME_CHECKED(UAISenseConfig_Sight, AutoSuccessRangeFromLastSeenLocation); + + Super::PostEditChangeProperty(PropertyChangedEvent); + + if (PropertyChangedEvent.Property) + { + const FName PropName = PropertyChangedEvent.Property->GetFName(); + if (PropName == NAME_AutoSuccessRangeFromLastSeenLocation) + { + if (AutoSuccessRangeFromLastSeenLocation < 0) + { + AutoSuccessRangeFromLastSeenLocation = FAISystem::InvalidRange; + } + } + } } +#endif // WITH_EDITOR #if WITH_GAMEPLAY_DEBUGGER void UAISenseConfig_Sight::DescribeSelfToGameplayDebugger(const UAIPerceptionComponent* PerceptionComponent, FGameplayDebuggerCategory* DebuggerCategory) const diff --git a/Engine/Source/Runtime/AIModule/Private/Perception/AISense_Sight.cpp b/Engine/Source/Runtime/AIModule/Private/Perception/AISense_Sight.cpp index e5220acc1928..04190b89db81 100644 --- a/Engine/Source/Runtime/AIModule/Private/Perception/AISense_Sight.cpp +++ b/Engine/Source/Runtime/AIModule/Private/Perception/AISense_Sight.cpp @@ -425,6 +425,11 @@ void UAISense_Sight::CleanseInvalidSources() } bool UAISense_Sight::RegisterTarget(AActor& TargetActor, FQueriesOperationPostProcess PostProcess) +{ + return RegisterTarget(TargetActor, PostProcess, [](FAISightQuery& Query) {}); +} + +bool UAISense_Sight::RegisterTarget(AActor& TargetActor, FQueriesOperationPostProcess PostProcess, TFunctionRef OnAddedFunc) { SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_RegisterTarget); @@ -465,10 +470,12 @@ bool UAISense_Sight::RegisterTarget(AActor& TargetActor, FQueriesOperationPostPr if (FAISenseAffiliationFilter::ShouldSenseTeam(ListenersTeamAgent, TargetActor, PropDigest.AffiliationFlags)) { // create a sight query - FAISightQuery SightQuery(ItListener->Key, SightTarget->TargetId); - SightQuery.Importance = CalcQueryImportance(ItListener->Value, TargetLocation, PropDigest.SightRadiusSq); - - SightQueryQueue.Add(SightQuery); + FAISightQuery& AddedQuery = SightQueryQueue.AddDefaulted_GetRef(); + AddedQuery.ObserverId = ItListener->Key; + AddedQuery.TargetId = SightTarget->TargetId; + AddedQuery.Importance = CalcQueryImportance(ItListener->Value, TargetLocation, PropDigest.SightRadiusSq); + + OnAddedFunc(AddedQuery); bNewQueriesAdded = true; } } @@ -497,6 +504,11 @@ void UAISense_Sight::OnNewListenerImpl(const FPerceptionListener& NewListener) } void UAISense_Sight::GenerateQueriesForListener(const FPerceptionListener& Listener, const FDigestedSightProperties& PropertyDigest) +{ + GenerateQueriesForListener(Listener, PropertyDigest, [](FAISightQuery& Query) {}); +} + +void UAISense_Sight::GenerateQueriesForListener(const FPerceptionListener& Listener, const FDigestedSightProperties& PropertyDigest, TFunctionRef OnAddedFunc) { bool bNewQueriesAdded = false; const IGenericTeamAgentInterface* ListenersTeamAgent = Listener.GetTeamAgent(); @@ -514,10 +526,12 @@ void UAISense_Sight::GenerateQueriesForListener(const FPerceptionListener& Liste if (FAISenseAffiliationFilter::ShouldSenseTeam(ListenersTeamAgent, *TargetActor, PropertyDigest.AffiliationFlags)) { // create a sight query - FAISightQuery SightQuery(Listener.GetListenerID(), ItTarget->Key); - SightQuery.Importance = CalcQueryImportance(Listener, ItTarget->Value.GetLocationSimple(), PropertyDigest.SightRadiusSq); + FAISightQuery& AddedQuery = SightQueryQueue.AddDefaulted_GetRef(); + AddedQuery.ObserverId = Listener.GetListenerID(); + AddedQuery.TargetId = ItTarget->Key; + AddedQuery.Importance = CalcQueryImportance(Listener, ItTarget->Value.GetLocationSimple(), PropertyDigest.SightRadiusSq); - SightQueryQueue.Add(SightQuery); + OnAddedFunc(AddedQuery); bNewQueriesAdded = true; } } @@ -538,18 +552,31 @@ void UAISense_Sight::OnListenerUpdateImpl(const FPerceptionListener& UpdatedList // 1. remove all queries by this listener // 2. proceed as if it was a new listener - // remove all queries - RemoveAllQueriesByListener(UpdatedListener, DontSort); - // see if this listener is a Target as well const FAISightTarget::FTargetId AsTargetId = UpdatedListener.GetBodyActorUniqueID(); FAISightTarget* AsTarget = ObservedTargets.Find(AsTargetId); if (AsTarget != NULL) { - RemoveAllQueriesToTarget(AsTargetId, DontSort); if (AsTarget->Target.IsValid()) { - RegisterTarget(*(AsTarget->Target.Get()), DontSort); + // if still a valid target then backup list of observers for which the listener was visible to restore in the newly created queries + TSet LastVisibleObservers; + RemoveAllQueriesToTarget(AsTargetId, DontSort, [&LastVisibleObservers](const FAISightQuery& Query) + { + if (Query.bLastResult) + { + LastVisibleObservers.Add(Query.ObserverId); + } + }); + + RegisterTarget(*(AsTarget->Target.Get()), DontSort, [&LastVisibleObservers](FAISightQuery& Query) + { + Query.bLastResult = LastVisibleObservers.Contains(Query.ObserverId); + }); + } + else + { + RemoveAllQueriesToTarget(AsTargetId, DontSort); } } @@ -557,15 +584,31 @@ void UAISense_Sight::OnListenerUpdateImpl(const FPerceptionListener& UpdatedList if (UpdatedListener.HasSense(GetSenseID())) { + // if still a valid sense then backup list of targets that were visible by the listener to restore in the newly created queries + TSet LastVisibleTargets; + RemoveAllQueriesByListener(UpdatedListener, DontSort, [&LastVisibleTargets](const FAISightQuery& Query) + { + if (Query.bLastResult) + { + LastVisibleTargets.Add(Query.TargetId); + } + }); + const UAISenseConfig_Sight* SenseConfig = Cast(UpdatedListener.Listener->GetSenseConfig(GetSenseID())); check(SenseConfig); FDigestedSightProperties& PropertiesDigest = DigestedProperties.FindOrAdd(ListenerID); PropertiesDigest = FDigestedSightProperties(*SenseConfig); - GenerateQueriesForListener(UpdatedListener, PropertiesDigest); + GenerateQueriesForListener(UpdatedListener, PropertiesDigest, [&LastVisibleTargets](FAISightQuery& Query) + { + Query.bLastResult = LastVisibleTargets.Contains(Query.TargetId); + }); } else { + // remove all queries + RemoveAllQueriesByListener(UpdatedListener, DontSort); + DigestedProperties.Remove(ListenerID); } } @@ -582,6 +625,11 @@ void UAISense_Sight::OnListenerRemovedImpl(const FPerceptionListener& UpdatedLis } void UAISense_Sight::RemoveAllQueriesByListener(const FPerceptionListener& Listener, FQueriesOperationPostProcess PostProcess) +{ + RemoveAllQueriesByListener(Listener, PostProcess, [](const FAISightQuery& Query) {}); +} + +void UAISense_Sight::RemoveAllQueriesByListener(const FPerceptionListener& Listener, FQueriesOperationPostProcess PostProcess, TFunctionRef OnRemoveFunc) { SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_RemoveByListener); @@ -599,6 +647,7 @@ void UAISense_Sight::RemoveAllQueriesByListener(const FPerceptionListener& Liste if (SightQuery.ObserverId == ListenerId) { + OnRemoveFunc(SightQuery); SightQueryQueue.RemoveAt(QueryIndex, 1, /*bAllowShrinking=*/false); bQueriesRemoved = true; } @@ -611,6 +660,11 @@ void UAISense_Sight::RemoveAllQueriesByListener(const FPerceptionListener& Liste } void UAISense_Sight::RemoveAllQueriesToTarget(const FAISightTarget::FTargetId& TargetId, FQueriesOperationPostProcess PostProcess) +{ + RemoveAllQueriesToTarget(TargetId, PostProcess, [](const FAISightQuery& Query) {}); +} + +void UAISense_Sight::RemoveAllQueriesToTarget(const FAISightTarget::FTargetId& TargetId, FQueriesOperationPostProcess PostProcess, TFunctionRef OnRemoveFunc) { SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_RemoveToTarget); @@ -627,6 +681,7 @@ void UAISense_Sight::RemoveAllQueriesToTarget(const FAISightTarget::FTargetId& T if (SightQuery.TargetId == TargetId) { + OnRemoveFunc(SightQuery); SightQueryQueue.RemoveAt(QueryIndex, 1, /*bAllowShrinking=*/false); bQueriesRemoved = true; } diff --git a/Engine/Source/Runtime/AIModule/Private/Tasks/AITask_MoveTo.cpp b/Engine/Source/Runtime/AIModule/Private/Tasks/AITask_MoveTo.cpp index 4274e564598e..77254ed4676f 100644 --- a/Engine/Source/Runtime/AIModule/Private/Tasks/AITask_MoveTo.cpp +++ b/Engine/Source/Runtime/AIModule/Private/Tasks/AITask_MoveTo.cpp @@ -29,7 +29,7 @@ UAITask_MoveTo::UAITask_MoveTo(const FObjectInitializer& ObjectInitializer) UAITask_MoveTo* UAITask_MoveTo::AIMoveTo(AAIController* Controller, FVector InGoalLocation, AActor* InGoalActor, float AcceptanceRadius, EAIOptionFlag::Type StopOnOverlap, EAIOptionFlag::Type AcceptPartialPath, - bool bUsePathfinding, bool bLockAILogic, bool bUseContinuosGoalTracking) + bool bUsePathfinding, bool bLockAILogic, bool bUseContinuosGoalTracking, EAIOptionFlag::Type ProjectGoalOnNavigation) { UAITask_MoveTo* MyTask = Controller ? UAITask::NewAITask(*Controller, EAITaskPriority::High) : nullptr; if (MyTask) @@ -48,6 +48,7 @@ UAITask_MoveTo* UAITask_MoveTo::AIMoveTo(AAIController* Controller, FVector InGo MoveReq.SetReachTestIncludesAgentRadius(FAISystem::PickAIOption(StopOnOverlap, MoveReq.IsReachTestIncludingAgentRadius())); MoveReq.SetAllowPartialPath(FAISystem::PickAIOption(AcceptPartialPath, MoveReq.IsUsingPartialPaths())); MoveReq.SetUsePathfinding(bUsePathfinding); + MoveReq.SetProjectGoalLocation(FAISystem::PickAIOption(ProjectGoalOnNavigation, MoveReq.IsProjectingGoal())); if (Controller) { MoveReq.SetNavigationFilter(Controller->GetDefaultNavigationFilterClass()); diff --git a/Engine/Source/Runtime/AppFramework/Private/Widgets/Colors/SColorPicker.cpp b/Engine/Source/Runtime/AppFramework/Private/Widgets/Colors/SColorPicker.cpp index 12f7d9687394..a002ecc0b5ee 100644 --- a/Engine/Source/Runtime/AppFramework/Private/Widgets/Colors/SColorPicker.cpp +++ b/Engine/Source/Runtime/AppFramework/Private/Widgets/Colors/SColorPicker.cpp @@ -892,9 +892,9 @@ TSharedRef SColorPicker::MakeColorSlider( EColorPickerChannels Channel .SliderBarColor(FLinearColor::Transparent) .Style(&FCoreStyle::Get().GetWidgetStyle("ColorPicker.Slider")) .Value(this, &SColorPicker::HandleColorSpinBoxValue, Channel) - .OnMouseCaptureBegin(this, &SColorPicker::HandleInteractiveChangeBegin) - .OnMouseCaptureEnd(this, &SColorPicker::HandleInteractiveChangeEnd) - .OnValueChanged(this, &SColorPicker::HandleColorSpinBoxValueChanged, Channel) + .OnMouseCaptureBegin(const_cast(this), &SColorPicker::HandleInteractiveChangeBegin) + .OnMouseCaptureEnd(const_cast(this), &SColorPicker::HandleInteractiveChangeEnd) + .OnValueChanged(const_cast(this), &SColorPicker::HandleColorSpinBoxValueChanged, Channel) ]; } @@ -1019,9 +1019,9 @@ TSharedRef SColorPicker::MakeColorSpinBox( EColorPickerChannels Channel .Delta(Channel == EColorPickerChannels::Hue ? 1.0f : 0.001f) .Font(SmallLayoutFont) .Value(this, &SColorPicker::HandleColorSpinBoxValue, Channel) - .OnBeginSliderMovement(this, &SColorPicker::HandleInteractiveChangeBegin) - .OnEndSliderMovement(this, &SColorPicker::HandleInteractiveChangeEnd) - .OnValueChanged(this, &SColorPicker::HandleColorSpinBoxValueChanged, Channel) + .OnBeginSliderMovement(const_cast(this), &SColorPicker::HandleInteractiveChangeBegin) + .OnEndSliderMovement(const_cast(this), &SColorPicker::HandleInteractiveChangeEnd) + .OnValueChanged(const_cast(this), &SColorPicker::HandleColorSpinBoxValueChanged, Channel) ] + SVerticalBox::Slot() @@ -1065,7 +1065,7 @@ TSharedRef SColorPicker::MakeColorPreviewBox() const .IgnoreAlpha(true) .ToolTipText(LOCTEXT("OldColorToolTip", "Old color without alpha (drag to theme bar to save)")) .Color(OldColor) - .OnMouseButtonDown(this, &SColorPicker::HandleOldColorBlockMouseButtonDown, false) + .OnMouseButtonDown(const_cast(this), &SColorPicker::HandleOldColorBlockMouseButtonDown, false) .UseSRGB(SharedThis(this), &SColorPicker::HandleColorPickerUseSRGB) .Cursor(EMouseCursor::GrabHand) ] @@ -1079,7 +1079,7 @@ TSharedRef SColorPicker::MakeColorPreviewBox() const .ToolTipText(LOCTEXT("OldColorAlphaToolTip", "Old color with alpha (drag to theme bar to save)")) .Color(OldColor) .Visibility(SharedThis(this), &SColorPicker::HandleAlphaColorBlockVisibility) - .OnMouseButtonDown(this, &SColorPicker::HandleOldColorBlockMouseButtonDown, true) + .OnMouseButtonDown(const_cast(this), &SColorPicker::HandleOldColorBlockMouseButtonDown, true) .UseSRGB(SharedThis(this), &SColorPicker::HandleColorPickerUseSRGB) .Cursor(EMouseCursor::GrabHand) ] @@ -1097,7 +1097,7 @@ TSharedRef SColorPicker::MakeColorPreviewBox() const .IgnoreAlpha(true) .ToolTipText(LOCTEXT("NewColorToolTip", "New color without alpha (drag to theme bar to save)")) .Color(this, &SColorPicker::GetCurrentColor) - .OnMouseButtonDown(this, &SColorPicker::HandleNewColorBlockMouseButtonDown, false) + .OnMouseButtonDown(const_cast(this), &SColorPicker::HandleNewColorBlockMouseButtonDown, false) .UseSRGB(SharedThis(this), &SColorPicker::HandleColorPickerUseSRGB) .Cursor(EMouseCursor::GrabHand) ] @@ -1111,7 +1111,7 @@ TSharedRef SColorPicker::MakeColorPreviewBox() const .ToolTipText(LOCTEXT("NewColorAlphaToolTip", "New color with alpha (drag to theme bar to save)")) .Color(this, &SColorPicker::GetCurrentColor) .Visibility(SharedThis(this), &SColorPicker::HandleAlphaColorBlockVisibility) - .OnMouseButtonDown(this, &SColorPicker::HandleNewColorBlockMouseButtonDown, true) + .OnMouseButtonDown(const_cast(this), &SColorPicker::HandleNewColorBlockMouseButtonDown, true) .UseSRGB(SharedThis(this), &SColorPicker::HandleColorPickerUseSRGB) .Cursor(EMouseCursor::GrabHand) ] diff --git a/Engine/Source/Runtime/AppFramework/Private/Widgets/Testing/STestSuite.cpp b/Engine/Source/Runtime/AppFramework/Private/Widgets/Testing/STestSuite.cpp index 0c53a1f67f03..664792fd0d94 100644 --- a/Engine/Source/Runtime/AppFramework/Private/Widgets/Testing/STestSuite.cpp +++ b/Engine/Source/Runtime/AppFramework/Private/Widgets/Testing/STestSuite.cpp @@ -42,7 +42,6 @@ #include "Framework/Commands/InputChord.h" #include "Framework/Commands/Commands.h" #include "Framework/Commands/UICommandList.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/IRun.h" #include "Framework/Text/TextLayout.h" #include "Framework/Text/ISlateRun.h" @@ -2275,7 +2274,7 @@ public: FSlateWidgetRun::FWidgetRunInfo OnCreateWidgetDecoratorWidget( const FTextRunInfo& RunInfo, const ISlateStyle* Style ) const { - TSharedRef< SWidget > Widget = SNew( SButton ) .OnClicked( this, &SRichTextTest::OnWidgetDecoratorClicked ) + TSharedRef< SWidget > Widget = SNew( SButton ) .OnClicked( const_cast(this), &SRichTextTest::OnWidgetDecoratorClicked ) .ToolTip( SNew( SToolTip ) .BorderImage( FTestStyle::Get().GetBrush( "RichText.Tagline.Background" ) ) diff --git a/Engine/Source/Runtime/Apple/MetalRHI/Private/MetalCommandQueue.cpp b/Engine/Source/Runtime/Apple/MetalRHI/Private/MetalCommandQueue.cpp index 5ce486367a33..17d0c2904339 100644 --- a/Engine/Source/Runtime/Apple/MetalRHI/Private/MetalCommandQueue.cpp +++ b/Engine/Source/Runtime/Apple/MetalRHI/Private/MetalCommandQueue.cpp @@ -253,7 +253,7 @@ FMetalCommandQueue::FMetalCommandQueue(mtlpp::Device InDevice, uint32 const MaxN if (!GIsEditor) #endif { - if (!FParse::Param(FCommandLine::Get(),TEXT("nometalfence"))) + if (FParse::Param(FCommandLine::Get(),TEXT("metalfence"))) { Features |= EMetalFeaturesFences; } diff --git a/Engine/Source/Runtime/ApplicationCore/ApplicationCore.Build.cs b/Engine/Source/Runtime/ApplicationCore/ApplicationCore.Build.cs index 1ba470856f45..f65f44482c1a 100644 --- a/Engine/Source/Runtime/ApplicationCore/ApplicationCore.Build.cs +++ b/Engine/Source/Runtime/ApplicationCore/ApplicationCore.Build.cs @@ -32,7 +32,11 @@ public class ApplicationCore : ModuleRules AddEngineThirdPartyPrivateStaticDependencies(Target, "XInput" ); - } + if (Target.bCompileWithAccessibilitySupport && !Target.bIsBuildingConsoleApplication) + { + PublicAdditionalLibraries.Add("uiautomationcore.lib"); + } + } else if (Target.Platform == UnrealTargetPlatform.Mac) { AddEngineThirdPartyPrivateStaticDependencies(Target, diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Android/AndroidInputInterface.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Android/AndroidInputInterface.cpp index 6385f784ddbf..5e287efc547e 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/Android/AndroidInputInterface.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/Android/AndroidInputInterface.cpp @@ -105,6 +105,9 @@ FAndroidInputInterface::FAndroidInputInterface(const TSharedRef< FGenericApplica InitialButtonRepeatDelay = 0.2f; ButtonRepeatDelay = 0.1f; + GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("InitialButtonRepeatDelay"), InitialButtonRepeatDelay, GInputIni); + GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("ButtonRepeatDelay"), ButtonRepeatDelay, GInputIni); + VibeIsOn = false; for (int32 DeviceIndex = 0; DeviceIndex < MAX_NUM_CONTROLLERS; DeviceIndex++) diff --git a/Engine/Source/Runtime/ApplicationCore/Private/GenericPlatform/GenericAccessibleInterfaces.cpp b/Engine/Source/Runtime/ApplicationCore/Private/GenericPlatform/GenericAccessibleInterfaces.cpp new file mode 100644 index 000000000000..54de27f06c69 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Private/GenericPlatform/GenericAccessibleInterfaces.cpp @@ -0,0 +1,5 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "GenericPlatform/GenericAccessibleInterfaces.h" + +DEFINE_LOG_CATEGORY(LogAccessibility); diff --git a/Engine/Source/Runtime/ApplicationCore/Private/IOS/Accessibility/IOSAccessibilityCache.cpp b/Engine/Source/Runtime/ApplicationCore/Private/IOS/Accessibility/IOSAccessibilityCache.cpp new file mode 100644 index 000000000000..897acdeaf369 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Private/IOS/Accessibility/IOSAccessibilityCache.cpp @@ -0,0 +1,139 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_ACCESSIBILITY + +#include "IOS/Accessibility/IOSAccessibilityCache.h" +#include "IOS/Accessibility/IOSAccessibilityElement.h" +#include "IOS/IOSApplication.h" +#include "IOS/IOSAppDelegate.h" +#include "IOS/IOSAsyncTask.h" +#include "IOS/IOSView.h" + +@implementation FIOSAccessibilityCache + +- (id)init +{ + Cache = [[NSMutableDictionary alloc] init]; + return self; +} + +-(void)dealloc +{ + [Cache release]; + [super dealloc]; +} + +-(FIOSAccessibilityContainer*)GetAccessibilityElement:(AccessibleWidgetId)Id +{ + if (Id == IAccessibleWidget::InvalidAccessibleWidgetId) + { + return nil; + } + + FIOSAccessibilityContainer* ExistingElement = [Cache objectForKey:[NSNumber numberWithInt:Id]]; + if (ExistingElement == nil) + { + AccessibleWidgetId ParentId = IAccessibleWidget::InvalidAccessibleWidgetId; + // All IAccessibleWidget functions must be run on Game Thread + [IOSAppDelegate WaitAndRunOnGameThread : [Id, &ParentId]() + { + TSharedPtr Widget = [IOSAppDelegate GetDelegate].IOSApplication->GetAccessibleMessageHandler()->GetAccessibleWidgetFromId(Id); + if (Widget.IsValid()) + { + TSharedPtr ParentWidget = Widget->GetParent(); + if (ParentWidget.IsValid()) + { + ParentId = ParentWidget->GetId(); + } + } + }]; + + ExistingElement = [[[FIOSAccessibilityContainer alloc] initWithId:Id AndParentId:ParentId] autorelease]; + [Cache setObject:ExistingElement forKey:[NSNumber numberWithInt:Id]]; + } + return ExistingElement; +} + +-(bool)AccessibilityElementExists:(AccessibleWidgetId)Id +{ + return [Cache objectForKey:[NSNumber numberWithInt:Id]] != nil; +} + +-(void)RemoveAccessibilityElement:(AccessibleWidgetId)Id +{ + [Cache removeObjectForKey:[NSNumber numberWithInt:Id]]; +} + +-(void)Clear +{ + [Cache removeAllObjects]; +} + ++(id)AccessibilityElementCache +{ + static FIOSAccessibilityCache* Cache = nil; + if (Cache == nil) + { + Cache = [[self alloc] init]; + } + return Cache; +} + +-(void)UpdateAllCachedProperties +{ + TArray Ids; + for (NSString* Key in Cache) + { + Ids.Add(Key.intValue); + } + + if (Ids.Num() > 0) + { + // All IAccessibleWidget functions must be run on Game Thread + FFunctionGraphTask::CreateAndDispatchWhenReady([Ids]() + { + for (const AccessibleWidgetId Id : Ids) + { + TSharedPtr Widget = [IOSAppDelegate GetDelegate].IOSApplication->GetAccessibleMessageHandler()->GetAccessibleWidgetFromId(Id); + if (Widget.IsValid()) + { + // Children + TArray ChildIds; + for (int32 i = 0; i < Widget->GetNumberOfChildren(); ++i) + { + TSharedPtr Child = Widget->GetChildAt(i); + if (Child.IsValid()) + { + ChildIds.Add(Child->GetId()); + } + } + + // Bounding rect + FBox2D Bounds = Widget->GetBounds(); + const float Scale = [IOSAppDelegate GetDelegate].IOSView.contentScaleFactor; + Bounds.Min /= Scale; + Bounds.Max /= Scale; + + // Visibility + const bool bIsEnabled = Widget->IsEnabled(); + const bool bIsVisible = !Widget->IsHidden(); + + // All UIKit functions must be run on Main Thread + dispatch_async(dispatch_get_main_queue(), ^ + { + FIOSAccessibilityContainer* Element = [[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:Id]; + Element.ChildIds = ChildIds; + Element.Bounds = Bounds; + + [[Element GetLeaf] SetAccessibilityTrait:UIAccessibilityTraitNotEnabled Set:!bIsEnabled]; + Element.bIsVisible = bIsVisible; + }); + } + } + }, TStatId(), NULL, ENamedThreads::GameThread); + } +} + +@end + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/IOS/Accessibility/IOSAccessibilityElement.cpp b/Engine/Source/Runtime/ApplicationCore/Private/IOS/Accessibility/IOSAccessibilityElement.cpp new file mode 100644 index 000000000000..e9328a403896 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Private/IOS/Accessibility/IOSAccessibilityElement.cpp @@ -0,0 +1,333 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_ACCESSIBILITY + +#include "IOS/Accessibility/IOSAccessibilityElement.h" + +#include "Async/TaskGraphInterfaces.h" +#include "GenericPlatform/GenericAccessibleInterfaces.h" +#include "IOS/Accessibility/IOSAccessibilityCache.h" +#include "IOS/IOSApplication.h" +#include "IOS/IOSAppDelegate.h" +#include "IOS/IOSView.h" + +@implementation FIOSAccessibilityContainer + +@synthesize Id; +@synthesize ChildIds; +@synthesize Bounds; +@synthesize bIsVisible; + +-(id)initWithId:(AccessibleWidgetId)InId AndParentId:(AccessibleWidgetId)InParentId +{ + id UIParent = nil; + if (InParentId != IAccessibleWidget::InvalidAccessibleWidgetId) + { + UIParent = [[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:InParentId]; + } + else + { + UIParent = [IOSAppDelegate GetDelegate].IOSView; + } + + if (self = [super initWithAccessibilityContainer:UIParent]) + { + self.Id = InId; + self.bIsVisible = true; + Leaf = [[FIOSAccessibilityLeaf alloc] initWithParent:self]; + } + + return self; +} + +-(void)dealloc +{ + [Leaf release]; + [super dealloc]; +} + +-(void)SetParent:(AccessibleWidgetId)InParentId +{ + if (InParentId != IAccessibleWidget::InvalidAccessibleWidgetId) + { + self.accessibilityContainer = [[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:InParentId]; + } + else + { + self.accessibilityContainer = [IOSAppDelegate GetDelegate].IOSView; + } +} + +-(FIOSAccessibilityLeaf*)GetLeaf +{ + return Leaf; +} + +-(BOOL)isAccessibilityElement +{ + // Containers are never accessible + return NO; +} + +-(CGRect)accessibilityFrame +{ + // This function will be called less than the function to cache the bounds, so make + // the IOS rect here. If we refactor the code to not using a polling-based cache, + // it may make more sense to change the Bounds property itself to a CGRect. + return CGRectMake(Bounds.Min.X, Bounds.Min.Y, Bounds.Max.X - Bounds.Min.X, Bounds.Max.Y - Bounds.Min.Y); +} + +-(NSInteger)accessibilityElementCount +{ + // The extra +1 is from the Leaf element + return self.ChildIds.Num() + 1; +} + +-(id)accessibilityElementAtIndex:(NSInteger)index +{ + if (index == self.ChildIds.Num()) + { + return Leaf; + } + else + { + return [[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:self.ChildIds[index]]; + } +} + +-(NSInteger)indexOfAccessibilityElement:(id)element +{ + if ([element isKindOfClass : [FIOSAccessibilityLeaf class]]) + { + // If it's a Leaf, it is the last child of the parent + return [[element accessibilityContainer] accessibilityElementCount] - 1; + } + + const AccessibleWidgetId OtherId = ((FIOSAccessibilityContainer*)element).Id; + for (int32 i = 0; i < self.ChildIds.Num(); ++i) + { + if (self.Id == OtherId) + { + return i; + } + } + + return NSNotFound; +} + +-(id)accessibilityHitTest:(CGPoint)point +{ + const AccessibleWidgetId TempId = self.Id; + AccessibleWidgetId FoundId = IAccessibleWidget::InvalidAccessibleWidgetId; + + const float Scale = [IOSAppDelegate GetDelegate].IOSView.contentScaleFactor; + const int32 X = point.x * Scale; + const int32 Y = point.y * Scale; + // Update the labels while we're on the game thread, since IOS is going to request them immediately. + FString TempLabel, TempHint, TempValue; + + [IOSAppDelegate WaitAndRunOnGameThread:[TempId, X, Y, &FoundId, &TempLabel, &TempHint, &TempValue]() + { + const TSharedPtr Widget = [IOSAppDelegate GetDelegate].IOSApplication->GetAccessibleMessageHandler()->GetAccessibleWidgetFromId(TempId); + if (Widget.IsValid()) + { + const TSharedPtr HitWidget = Widget->GetTopLevelWindow()->AsWindow()->GetChildAtPosition(X, Y); + if (HitWidget.IsValid()) + { + FoundId = HitWidget->GetId(); + TempLabel = HitWidget->GetWidgetName(); + TempHint = HitWidget->GetHelpText(); + if (HitWidget->AsProperty()) + { + TempValue = HitWidget->AsProperty()->GetValue(); + } + } + } + }]; + + if (FoundId != IAccessibleWidget::InvalidAccessibleWidgetId) + { + FIOSAccessibilityLeaf* FoundLeaf = [[[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:FoundId] GetLeaf]; + if ([FoundLeaf ShouldCacheStrings]) + { + FoundLeaf.Label = MoveTemp(TempLabel); + FoundLeaf.Hint = MoveTemp(TempHint); + FoundLeaf.Value = MoveTemp(TempValue); + FoundLeaf.LastCachedStringTime = FPlatformTime::Seconds(); + } + return FoundLeaf; + } + else + { + return Leaf; + } +} + +@end + +@implementation FIOSAccessibilityLeaf + +@synthesize Label; +@synthesize Hint; +@synthesize Value; +@synthesize Traits; +@synthesize LastCachedStringTime; + +-(id)initWithParent:(FIOSAccessibilityContainer*)Parent +{ + if (self = [super initWithAccessibilityContainer : Parent]) + { + const AccessibleWidgetId ParentId = Parent.Id; + // All IAccessibleWidget functions must be run on Game Thread + FFunctionGraphTask::CreateAndDispatchWhenReady([ParentId]() + { + TSharedPtr Widget = [IOSAppDelegate GetDelegate].IOSApplication->GetAccessibleMessageHandler()->GetAccessibleWidgetFromId(ParentId); + if (Widget.IsValid()) + { + // Most accessibility traits cannot be changed after setting, so initialize them here + UIAccessibilityTraits InitialTraits = UIAccessibilityTraitNone; + if (Widget->AsProperty() && !FMath::IsNearlyZero(Widget->AsProperty()->GetStepSize())) + { + InitialTraits |= UIAccessibilityTraitAdjustable; + } + if (Widget->AsActivatable()) + { + InitialTraits |= UIAccessibilityTraitButton; + } + if (Widget->GetWidgetType() == EAccessibleWidgetType::Image) + { + InitialTraits |= UIAccessibilityTraitImage; + } + if (Widget->GetWidgetType() == EAccessibleWidgetType::Hyperlink) + { + InitialTraits |= UIAccessibilityTraitLink; + } + if (!Widget->IsEnabled()) + { + InitialTraits |= UIAccessibilityTraitNotEnabled; + } + + FString InitialLabel = Widget->GetWidgetName(); + FString InitialHint = Widget->GetHelpText(); + FString InitialValue; + if (Widget->AsProperty()) + { + InitialValue = Widget->AsProperty()->GetValue(); + } + + // All UIKit functions must be run on Main Thread + dispatch_async(dispatch_get_main_queue(), ^ + { + FIOSAccessibilityLeaf* Self = [[[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:ParentId] GetLeaf]; + Self.Traits = InitialTraits; + Self.Label = InitialLabel; + Self.Hint = InitialHint; + Self.Value = InitialValue; + Self.LastCachedStringTime = FPlatformTime::Seconds(); + }); + } + }, TStatId(), NULL, ENamedThreads::GameThread); + } + return self; +} + +-(BOOL)isAccessibilityElement +{ + return YES; +} + +-(CGRect)accessibilityFrame +{ + return [self.accessibilityContainer accessibilityFrame]; +} + +-(bool)ShouldCacheStrings +{ + return FPlatformTime::Seconds() - LastCachedStringTime > 1.0; +} + +-(NSString*)accessibilityLabel +{ + if (!self.Label.IsEmpty()) + { + return [NSString stringWithFString:self.Label]; + } + return nil; +} + +-(NSString*)accessibilityHint +{ + if (!self.Hint.IsEmpty()) + { + return [NSString stringWithFString:self.Hint]; + } + return nil; +} + +-(NSString*)accessibilityValue +{ + if (!self.Value.IsEmpty()) + { + return [NSString stringWithFString:self.Value]; + } + return nil; +} + +-(void)SetAccessibilityTrait:(UIAccessibilityTraits)Trait Set:(bool)IsEnabled +{ + if (IsEnabled) + { + self.Traits |= Trait; + } + else + { + self.Traits &= ~Trait; + } +} + +-(UIAccessibilityTraits)accessibilityTraits +{ + return self.Traits; +} + +-(void)accessibilityIncrement +{ + const AccessibleWidgetId TempId = ((FIOSAccessibilityContainer*)self.accessibilityContainer).Id; + // All IAccessibleWidget functions must be run on Game Thread + FFunctionGraphTask::CreateAndDispatchWhenReady([TempId]() + { + TSharedPtr Widget = [IOSAppDelegate GetDelegate].IOSApplication->GetAccessibleMessageHandler()->GetAccessibleWidgetFromId(TempId); + if (Widget.IsValid()) + { + IAccessibleProperty* Property = Widget->AsProperty(); + if (Property && !FMath::IsNearlyZero(Property->GetStepSize())) + { + const float CurrentValue = FCString::Atof(*Property->GetValue()); + Property->SetValue(FString::SanitizeFloat(CurrentValue + Property->GetStepSize())); + } + } + }, TStatId(), NULL, ENamedThreads::GameThread); +} + +-(void)accessibilityDecrement +{ + const AccessibleWidgetId TempId = ((FIOSAccessibilityContainer*)self.accessibilityContainer).Id; + // All IAccessibleWidget functions must be run on Game Thread + FFunctionGraphTask::CreateAndDispatchWhenReady([TempId]() + { + TSharedPtr Widget = [IOSAppDelegate GetDelegate].IOSApplication->GetAccessibleMessageHandler()->GetAccessibleWidgetFromId(TempId); + if (Widget.IsValid()) + { + IAccessibleProperty* Property = Widget->AsProperty(); + if (Property && !FMath::IsNearlyZero(Property->GetStepSize())) + { + const float CurrentValue = FCString::Atof(*Property->GetValue()); + Property->SetValue(FString::SanitizeFloat(CurrentValue - Property->GetStepSize())); + } + } + }, TStatId(), NULL, ENamedThreads::GameThread); +} + +@end + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSAppDelegate.cpp b/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSAppDelegate.cpp index 3a2ce6a24c8f..f83c61026c49 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSAppDelegate.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSAppDelegate.cpp @@ -6,6 +6,7 @@ #include "Modules/Boilerplate/ModuleBoilerplate.h" #include "Misc/CallbackDevice.h" #include "IOS/IOSView.h" +#include "IOS/IOSWindow.h" #include "Async/TaskGraphInterfaces.h" #include "GenericPlatform/GenericPlatformChunkInstall.h" #include "IOS/IOSPlatformMisc.h" @@ -15,6 +16,7 @@ #include "Misc/OutputDeviceError.h" #include "Misc/CommandLine.h" #include "IOS/IOSPlatformFramePacer.h" +#include "IOS/IOSApplication.h" #include "IOS/IOSAsyncTask.h" #include "Misc/ConfigCacheIni.h" #include "IOS/IOSPlatformCrashContext.h" @@ -34,6 +36,10 @@ #include #include "HAL/IConsoleManager.h" +#if WITH_ACCESSIBILITY +#include "IOS/Accessibility/IOSAccessibilityCache.h" +#endif + // this is the size of the game thread stack, it must be a multiple of 4k #if (UE_BUILD_SHIPPING || UE_BUILD_TEST) #define GAME_THREAD_STACK_SIZE 2 * 1024 * 1024 @@ -152,7 +158,9 @@ bool FIOSCoreDelegates::PassesPushNotificationFilters(NSDictionary* Payload) @synthesize timer; @synthesize IdleTimerEnableTimer; @synthesize IdleTimerEnablePeriod; - +#if WITH_ACCESSIBILITY +@synthesize AccessibilityCacheTimer; +#endif @synthesize savedOpenUrlParameters; @synthesize BackgroundSessionEventCompleteDelegate; @@ -213,6 +221,13 @@ static IOSAppDelegate* CachedDelegate = nil; [IOSView release]; [SlateController release]; [timer release]; +#if WITH_ACCESSIBILITY + if (AccessibilityCacheTimer != nil) + { + [AccessibilityCacheTimer invalidate]; + [AccessibilityCacheTimer release]; + } +#endif [super dealloc]; } @@ -267,6 +282,16 @@ static IOSAppDelegate* CachedDelegate = nil; FEmbeddedDelegates::GetEmbeddedToNativeParamsDelegateForSubsystem(TEXT("native")).Broadcast(Helper); #endif +#if WITH_ACCESSIBILITY + // Initialize accessibility code if VoiceOver is enabled. This must happen after Slate has been initialized. + dispatch_async(dispatch_get_main_queue(), ^{ + if (UIAccessibilityIsVoiceOverRunning()) + { + [[IOSAppDelegate GetDelegate] OnVoiceOverStatusChanged]; + } + }); +#endif + while( !GIsRequestingExit ) { if (self.bIsSuspended) @@ -754,7 +779,12 @@ static IOSAppDelegate* CachedDelegate = nil; -(bool)HasRecordPermission { +#if PLATFORM_TVOS + // TVOS does not have sound recording capabilities. + return false; +#else return [[AVAudioSession sharedInstance] recordPermission] == AVAudioSessionRecordPermissionGranted; +#endif } -(void)EnableHighQualityVoiceChat:(bool)bEnable @@ -1237,10 +1267,55 @@ static FAutoConsoleVariableRef CVarGEnableThermalsReport( #endif [self InitializeAudioSession]; - + +#if WITH_ACCESSIBILITY + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(OnVoiceOverStatusChanged) name:UIAccessibilityVoiceOverStatusDidChangeNotification object:nil]; +#endif + return YES; } +#if WITH_ACCESSIBILITY +-(void)OnVoiceOverStatusChanged +{ + if (UIAccessibilityIsVoiceOverRunning()) + { + // This must happen asynchronously because when the app activates from a suspended state, + // the IOS notification will emit before the game thread wakes up. This does mean that the + // accessibility element tree will probably not be 100% completed when the application + // opens for the first time. If this is a problem we can add separate branches for startup + // vs waking up. + FFunctionGraphTask::CreateAndDispatchWhenReady([]() + { + FIOSApplication* Application = [IOSAppDelegate GetDelegate].IOSApplication; + Application->GetAccessibleMessageHandler()->SetActive(true); + AccessibleWidgetId WindowId = Application->GetAccessibleMessageHandler()->GetAccessibleWindowId(Application->FindWindowByAppDelegateView()); + dispatch_async(dispatch_get_main_queue(), ^{ + IOSAppDelegate* Delegate = [IOSAppDelegate GetDelegate]; + [Delegate.IOSView SetAccessibilityWindow:WindowId]; + if (Delegate.AccessibilityCacheTimer == nil) + { + // Start caching accessibility data so that it can be returned instantly to IOS. If not cached, the data takes too long + // to retrieve due to cross-thread waiting and IOS will timeout. + Delegate.AccessibilityCacheTimer = [NSTimer scheduledTimerWithTimeInterval:0.1f target:[FIOSAccessibilityCache AccessibilityElementCache] selector:@selector(UpdateAllCachedProperties) userInfo:nil repeats:YES]; + } + }); + }, TStatId(), NULL, ENamedThreads::GameThread); + } + else + { + [AccessibilityCacheTimer invalidate]; + AccessibilityCacheTimer = nil; + [[IOSAppDelegate GetDelegate].IOSView SetAccessibilityWindow : IAccessibleWidget::InvalidAccessibleWidgetId]; + [[FIOSAccessibilityCache AccessibilityElementCache] Clear]; + FFunctionGraphTask::CreateAndDispatchWhenReady([]() + { + [IOSAppDelegate GetDelegate].IOSApplication->GetAccessibleMessageHandler()->SetActive(false); + }, TStatId(), NULL, ENamedThreads::GameThread); + } +} +#endif + - (void) StartGameThread { // create a new thread (the pointer will be retained forever) @@ -1257,6 +1332,23 @@ static FAutoConsoleVariableRef CVarGEnableThermalsReport( } } ++(bool)WaitAndRunOnGameThread:(TUniqueFunction)Function +{ + FGraphEventRef Task = FFunctionGraphTask::CreateAndDispatchWhenReady(MoveTemp(Function), TStatId(), NULL, ENamedThreads::GameThread); + + const double MaxThreadWaitTime = 2.0; + const double StartTime = FPlatformTime::Seconds(); + while ((FPlatformTime::Seconds() - StartTime) < MaxThreadWaitTime) + { + FPlatformProcess::Sleep(0.05f); + if (Task->IsComplete()) + { + return true; + } + } + return false; +} + #if !PLATFORM_TVOS extern EDeviceScreenOrientation ConvertFromUIInterfaceOrientation(UIInterfaceOrientation Orientation); #endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSApplication.cpp b/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSApplication.cpp index 6542c1704b05..92ec371c7376 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSApplication.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSApplication.cpp @@ -13,6 +13,10 @@ #include "HAL/IConsoleManager.h" #include "IOS/IOSAsyncTask.h" #include "Stats/Stats.h" +#if WITH_ACCESSIBILITY +#include "IOS/Accessibility/IOSAccessibilityCache.h" +#include "IOS/Accessibility/IOSAccessibilityElement.h" +#endif FIOSApplication* FIOSApplication::CreateIOSApplication() { @@ -49,6 +53,15 @@ void FIOSApplication::SetMessageHandler( const TSharedRef< FGenericApplicationMe } } +#if WITH_ACCESSIBILITY +void FIOSApplication::SetAccessibleMessageHandler(const TSharedRef& InAccessibleMessageHandler) +{ + GenericApplication::SetAccessibleMessageHandler(InAccessibleMessageHandler); + InAccessibleMessageHandler->SetAccessibleEventDelegate(FGenericAccessibleMessageHandler::FAccessibleEvent::CreateRaw(this, &FIOSApplication::OnAccessibleEventRaised)); + InAccessibleMessageHandler->SetActive(UIAccessibilityIsVoiceOverRunning()); +} +#endif + void FIOSApplication::AddExternalInputDevice(TSharedPtr InputDevice) { if (InputDevice.IsValid()) @@ -224,3 +237,53 @@ bool FIOSApplication::IsGamepadAttached() const return false; } + +TSharedRef FIOSApplication::FindWindowByAppDelegateView() +{ + for (const TSharedRef& Window : Windows) + { + if ([IOSAppDelegate GetDelegate].Window == static_cast(Window->GetOSWindowHandle())) + { + return Window; + } + } + check(false); + return Windows[0]; +} + +#if WITH_ACCESSIBILITY +void FIOSApplication::OnAccessibleEventRaised(TSharedRef Widget, EAccessibleEvent Event, FVariant OldValue, FVariant NewValue) +{ + // This should only be triggered by the accessible message handler which initiates from the Slate thread. + check(IsInGameThread()); + + const AccessibleWidgetId Id = Widget->GetId(); + switch (Event) + { + case EAccessibleEvent::BeforeRemoveFromParent: + dispatch_async(dispatch_get_main_queue(), ^{ + [[[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:Id] SetParent:IAccessibleWidget::InvalidAccessibleWidgetId]; + }); + break; + case EAccessibleEvent::AfterAddToParent: + { + AccessibleWidgetId ParentId = IAccessibleWidget::InvalidAccessibleWidgetId; + TSharedPtr ParentWidget = Widget->GetParent(); + if (ParentWidget.IsValid()) + { + ParentId = ParentWidget->GetId(); + } + dispatch_async(dispatch_get_main_queue(), ^{ + [[[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:Id] SetParent:ParentId]; + }); + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); + break; + } + case EAccessibleEvent::WidgetRemoved: + dispatch_async(dispatch_get_main_queue(), ^{ + [[FIOSAccessibilityCache AccessibilityElementCache] RemoveAccessibilityElement:Id]; + }); + break; + } +} +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSView.cpp b/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSView.cpp index 4fd285b7442e..7bce2f2d3e5b 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSView.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSView.cpp @@ -3,6 +3,7 @@ #include "IOS/IOSView.h" #include "IOS/IOSAppDelegate.h" #include "IOS/IOSApplication.h" +#include "IOSWindow.h" #include "Misc/ConfigCacheIni.h" #include "HAL/IConsoleManager.h" #include "Misc/CommandLine.h" @@ -16,6 +17,11 @@ #include "IOS/IOSCommandLineHelper.h" +#if WITH_ACCESSIBILITY +#include "IOS/Accessibility/IOSAccessibilityCache.h" +#include "IOS/Accessibility/IOSAccessibilityElement.h" +#endif + #if HAS_METAL id GMetalDevice = nil; #endif @@ -286,6 +292,9 @@ id GMetalDevice = nil; -(void)dealloc { [markedTextStyle release]; +#if WITH_ACCESSIBILITY + [_accessibilityElements release]; +#endif [super dealloc]; } @@ -538,6 +547,37 @@ id GMetalDevice = nil; SwapCount++; } +#if WITH_ACCESSIBILITY + +-(void)SetAccessibilityWindow:(AccessibleWidgetId)WindowId +{ + if (_accessibilityElements == nil) + { + _accessibilityElements = [[NSMutableArray alloc] init]; + } + else + { + [_accessibilityElements removeAllObjects]; + } + + if (WindowId != IAccessibleWidget::InvalidAccessibleWidgetId) + { + [_accessibilityElements addObject : [[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:WindowId]]; + } +} + +-(NSArray*)accessibilityElements +{ + return _accessibilityElements; +} + +-(BOOL)isAccessibilityElement +{ + return NO; +} + +#endif + /** * Returns the unique ID for the given touch */ diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxApplication.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxApplication.cpp index 0aac82af560b..2af6c3d0b03f 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxApplication.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxApplication.cpp @@ -1021,6 +1021,31 @@ void FLinuxApplication::ProcessDeferredMessage( SDL_Event Event ) } } +void FLinuxApplication::CheckIfApplicatioNeedsDeactivation() +{ + // If FocusOutDeactivationTime is set we have had a focus out event and are waiting to see if we need to Deactivate the Application + if (!FMath::IsNearlyZero(FocusOutDeactivationTime)) + { + // We still havent hit our timeout limit, keep waiting + if (FocusOutDeactivationTime > FPlatformTime::Seconds()) + { + return; + } + // If we don't use bIsDragWindowButtonPressed the draged window will be destroyed because we + // deactivate the whole appliacton. TODO Is that a bug? Do we have to do something? + else if (!CurrentFocusWindow.IsValid() && !bIsDragWindowButtonPressed) + { + DeactivateApplication(); + + FocusOutDeactivationTime = 0.0; + } + else + { + FocusOutDeactivationTime = 0.0; + } + } +} + FVector2D FLinuxApplication::GetTouchEventLocation(SDL_HWindow NativeWindow, SDL_Event TouchEvent) { checkf(TouchEvent.type == SDL_FINGERDOWN || TouchEvent.type == SDL_FINGERUP || TouchEvent.type == SDL_FINGERMOTION, TEXT("Wrong touch event.")); diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformApplicationMisc.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformApplicationMisc.cpp index f0f9935ab467..647fe6527f12 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformApplicationMisc.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformApplicationMisc.cpp @@ -415,6 +415,7 @@ void FLinuxPlatformApplicationMisc::PumpMessages( bool bFromMainLoop ) LinuxApplication->AddPendingEvent( event ); } + LinuxApplication->CheckIfApplicatioNeedsDeactivation(); LinuxApplication->ClearWindowPropertiesAfterEventLoop(); } else diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformSplash.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformSplash.cpp index 695959efb0d9..bcb518778bfb 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformSplash.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformSplash.cpp @@ -23,15 +23,17 @@ #include "Modules/ModuleManager.h" #include "HAL/PlatformApplicationMisc.h" +#include "SDL.h" + #if WITH_EDITOR -#include "SDL.h" #include "IImageWrapper.h" #include "IImageWrapperModule.h" #include "ft2build.h" #include FT_FREETYPE_H +#endif // WITH_EDITOR /** * Splash screen functions and static globals @@ -57,24 +59,29 @@ public: private: static SDL_Surface* LoadImage(const FString &InImagePath); + void Redraw(); + +#if WITH_EDITOR void DrawCharacter(int32 penx, int32 peny, FT_GlyphSlot Glyph, int32 CurTypeIndex, float Red, float Green, float Blue); void RenderStrings(); bool OpenFonts(); - void Redraw(); FT_Library FontLibrary = nullptr; FT_Face FontSmall = nullptr; FT_Face FontNormal = nullptr; FT_Face FontLarge = nullptr; - SDL_Surface *SplashSurface = nullptr; - SDL_Window *SplashWindow = nullptr; SDL_Renderer *SplashRenderer = nullptr; SDL_Texture *SplashTexture = nullptr; FText SplashText[ SplashTextType::NumTextTypes ]; Rect SplashTextRects[ SplashTextType::NumTextTypes ]; unsigned char *ScratchSpace = nullptr; +#endif // WITH_EDITOR + + SDL_Surface *SplashSurface = nullptr; + SDL_Window *SplashWindow = nullptr; + bool bNeedsRedraw = false; bool bStringsChanged = false; }; @@ -82,17 +89,14 @@ private: static FLinuxSplashState *GSplashState = nullptr; +//--------------------------------------------------------- FLinuxSplashState::~FLinuxSplashState() { +#if WITH_EDITOR // Just in case SDL's renderer steps on GL state... SDL_Window *CurrentWindow = SDL_GL_GetCurrentWindow(); SDL_GLContext CurrentContext = SDL_GL_GetCurrentContext(); - if (SplashSurface) - { - SDL_FreeSurface(SplashSurface); - } - if (SplashTexture) { SDL_DestroyTexture(SplashTexture); @@ -102,12 +106,19 @@ FLinuxSplashState::~FLinuxSplashState() { SDL_DestroyRenderer(SplashRenderer); } +#endif // WITH_EDITOR + + if (SplashSurface) + { + SDL_FreeSurface(SplashSurface); + } if (SplashWindow) { SDL_DestroyWindow(SplashWindow); } +#if WITH_EDITOR if (ScratchSpace) { FMemory::Free(ScratchSpace); @@ -137,10 +148,13 @@ FLinuxSplashState::~FLinuxSplashState() { SDL_GL_MakeCurrent(CurrentWindow, CurrentContext); } +#endif // WITH_EDITOR // do not deinit SDL here } +#if WITH_EDITOR + //--------------------------------------------------------- bool FLinuxSplashState::OpenFonts() { @@ -152,7 +166,7 @@ bool FLinuxSplashState::OpenFonts() // small font face FString FontPath = FPaths::ConvertRelativePathToFull(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Light.ttf")); - + if (FT_New_Face(FontLibrary, TCHAR_TO_UTF8(*FontPath), 0, &FontSmall)) { UE_LOG(LogHAL, Error, TEXT("*** Unable to open small font face for splash screen.")); @@ -164,7 +178,7 @@ bool FLinuxSplashState::OpenFonts() // normal font face FontPath = FPaths::ConvertRelativePathToFull(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Regular.ttf")); - + if (FT_New_Face(FontLibrary, TCHAR_TO_UTF8(*FontPath), 0, &FontNormal)) { UE_LOG(LogHAL, Error, TEXT("*** Unable to open normal font face for splash screen.")); @@ -173,13 +187,13 @@ bool FLinuxSplashState::OpenFonts() { FT_Set_Pixel_Sizes(FontNormal, 0, 12); } - + // large font face FontPath = FPaths::ConvertRelativePathToFull(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf")); - + if (FT_New_Face(FontLibrary, TCHAR_TO_UTF8(*FontPath), 0, &FontLarge)) { - UE_LOG(LogHAL, Error, TEXT("*** Unable to open large font face for splash screen.")); + UE_LOG(LogHAL, Error, TEXT("*** Unable to open large font face for splash screen.")); } else { @@ -217,12 +231,12 @@ void FLinuxSplashState::DrawCharacter(int32 PenX, int32 PenY, FT_GlyphSlot Glyph // find pixel position in splash image const int PosX = PenX + GlyphX + (Glyph->metrics.horiBearingX >> 6); const int PosY = PenY + GlyphY - (Glyph->metrics.horiBearingY >> 6); - + // make sure pixel is in drawing rectangle if (PosX < MinX || PosX >= MaxX || PosY < MinY || PosY >= MaxY) continue; - // get index of pixel in glyph bitmap + // get index of pixel in glyph bitmap const int32 SourceIndex = (GlyphY * GlyphPitch) + GlyphX; int32 DestIndex = (PosY * SplashWidth + PosX) * SplashBPP; @@ -258,20 +272,27 @@ void FLinuxSplashState::RenderStrings() const int SplashWidth = SplashSurface->w; const int SplashHeight = SplashSurface->h; const int SplashBPP = SplashSurface->format->BytesPerPixel; + unsigned char *SplashPixels = reinterpret_cast(SplashSurface->pixels); // clear the rendering scratch pad. - FMemory::Memcpy(ScratchSpace, SplashSurface->pixels, SplashWidth*SplashHeight*SplashBPP); + for (int y = 0; y < SplashHeight; ++y) + { + unsigned char *Src = SplashPixels + y * SplashSurface->pitch; + unsigned char *Dst = ScratchSpace + y * SplashWidth * SplashBPP; + + FMemory::Memcpy(Dst, Src, SplashWidth * SplashBPP); + } // draw each type of string for (int CurTypeIndex=0; CurTypeIndexglyph->format != FT_GLYPH_FORMAT_BITMAP) { FT_Render_Glyph(Font->glyph, FT_RENDER_MODE_NORMAL); @@ -339,22 +359,22 @@ void FLinuxSplashState::RenderStrings() { FT_Get_Kerning(Font, GlyphIndex, LastGlyph, FT_KERNING_DEFAULT, &Kerning); } - - PenX -= (Font->glyph->metrics.horiAdvance - Kerning.x) >> 6; + + PenX -= (Font->glyph->metrics.horiAdvance - Kerning.x) >> 6; } else { if (LastGlyph != 0) { FT_Get_Kerning(Font, LastGlyph, GlyphIndex, FT_KERNING_DEFAULT, &Kerning); - } + } } LastGlyph = GlyphIndex; // draw character DrawCharacter(PenX, PenY, Font->glyph, CurTypeIndex, Red, Green, Blue); - + if (!bRightJustify) { PenX += (Font->glyph->metrics.horiAdvance - Kerning.x) >> 6; @@ -367,6 +387,9 @@ void FLinuxSplashState::RenderStrings() } } +#endif // WITH_EDITOR + + /** * @brief Helper function to load an image in any format. * @@ -376,6 +399,7 @@ void FLinuxSplashState::RenderStrings() */ SDL_Surface* FLinuxSplashState::LoadImage(const FString &ImagePath) { +#if WITH_EDITOR TArray RawFileData; // Load the image buffer first (unless it's BMP) @@ -402,23 +426,13 @@ SDL_Surface* FLinuxSplashState::LoadImage(const FString &ImagePath) } } } +#endif // WITH_EDITOR // If for some reason the image cannot be loaded, use the default BMP function return SDL_LoadBMP(TCHAR_TO_UTF8(*ImagePath)); } -// This class helps cleans up SDL_Surfaces when they go out of scope. -class SdlSurfacePtr -{ -public: - SdlSurfacePtr(SDL_Surface *InSurface) : Surface(InSurface) {} - ~SdlSurfacePtr() { if (Surface) { SDL_FreeSurface(Surface); } } -private: - SDL_Surface *Surface; -}; - - /** Helper function to init resources used by the splash window */ bool FLinuxSplashState::InitSplashResources(const FText &AppName, const FString &SplashPath, const FString &IconPath) { @@ -448,16 +462,11 @@ bool FLinuxSplashState::InitSplashResources(const FText &AppName, const FString return -1; } - SDL_Surface *SplashIconImage = LoadImage(IconPath); - SdlSurfacePtr SplashIconPtr(SplashIconImage); - if (SplashIconImage == nullptr) - { - UE_LOG(LogHAL, Warning, TEXT("FLinuxSplashState::InitSplashResources() : Splash icon could not be created! SDL_Error: %s"), UTF8_TO_TCHAR(SDL_GetError())); - } - +#if WITH_EDITOR // Just in case SDL's renderer steps on GL state... SDL_Window *CurrentWindow = SDL_GL_GetCurrentWindow(); SDL_GLContext CurrentContext = SDL_GL_GetCurrentContext(); +#endif // on modern X11, your windows might turn gray if they don't pump the event queue fast enough. // But this is because they opt-in to an optional window manager protocol by default; legacy @@ -482,17 +491,31 @@ bool FLinuxSplashState::InitSplashResources(const FText &AppName, const FString return false; } - if (SplashIconImage != nullptr) + if (!IconPath.IsEmpty()) { - SDL_SetWindowIcon(SplashWindow, SplashIconImage); // SDL_SetWindowIcon() makes a copy of this. + SDL_Surface *SplashIconImage = LoadImage(IconPath); + + if (SplashIconImage == nullptr) + { + UE_LOG(LogHAL, Warning, TEXT("FLinuxSplashState::InitSplashResources() : Splash icon could not be created! SDL_Error: %s"), UTF8_TO_TCHAR(SDL_GetError())); + } + else + { + SDL_SetWindowIcon(SplashWindow, SplashIconImage); // SDL_SetWindowIcon() makes a copy of this. + SDL_FreeSurface(SplashIconImage); + } } +#if WITH_EDITOR SplashRenderer = SDL_CreateRenderer(SplashWindow, -1, 0); +#endif // it's safe to set the hint back once the Renderer is created (since it might recreate the window to get a GL or whatever visual). SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_PING, OriginalHint ? OriginalHint : "1"); SDL_free(OriginalHint); +#if WITH_EDITOR + if (SplashRenderer == nullptr) { UE_LOG(LogHAL, Error, TEXT("FLinuxSplashState::InitSplashResources() : Splash screen renderer could not be created! SDL_Error: %s"), UTF8_TO_TCHAR(SDL_GetError())); @@ -554,9 +577,27 @@ bool FLinuxSplashState::InitSplashResources(const FText &AppName, const FString SDL_GL_MakeCurrent(CurrentWindow, CurrentContext); } +#else + + SDL_Surface *WindowSurface = SDL_GetWindowSurface( SplashWindow ); + if (WindowSurface == nullptr) + { + UE_LOG(LogHAL, Error, TEXT("FLinuxSplashState::InitSplashResources() : Splash window surface could not be created! SDL_Error: %s"), UTF8_TO_TCHAR(SDL_GetError())); + return false; + } + + SDL_BlitSurface(SplashSurface, NULL, WindowSurface, NULL); + SDL_FreeSurface(WindowSurface); + + SDL_ShowWindow(SplashWindow); + Pump(); + +#endif + return true; } + /** * Sets the text displayed on the splash screen (for startup/loading progress) * @@ -574,8 +615,11 @@ void FLinuxSplashState::SetSplashText( const SplashTextType::Type InType, const #endif } + +//--------------------------------------------------------- void FLinuxSplashState::Redraw() { +#if WITH_EDITOR if (bNeedsRedraw || bStringsChanged) { // Just in case SDL's renderer steps on GL state... @@ -592,8 +636,16 @@ void FLinuxSplashState::Redraw() SDL_GL_MakeCurrent(CurrentWindow, CurrentContext); } } +#else + if (bNeedsRedraw) + { + SDL_UpdateWindowSurface(SplashWindow); + bNeedsRedraw = false; + } +#endif } + void FLinuxSplashState::Pump() { if (SplashWindow) @@ -606,10 +658,10 @@ void FLinuxSplashState::Pump() bNeedsRedraw = true; } } + Redraw(); } } -#endif //WITH_EDITOR /** @@ -618,9 +670,8 @@ void FLinuxSplashState::Pump() */ void FLinuxPlatformSplash::Show( ) { -#if WITH_EDITOR // need to do a splash screen? - if(GSplashState || FParse::Param(FCommandLine::Get(),TEXT("NOSPLASH")) == true) + if(GSplashState || !FApp::CanEverRender() || FParse::Param(FCommandLine::Get(), TEXT("NOSPLASH")) == true) { return; } @@ -672,7 +723,7 @@ void FLinuxPlatformSplash::Show( ) if (!bIconFound) { UE_LOG(LogHAL, Warning, TEXT("Game icon not found.")); - return; // early out + IconPath.Reset(); } } @@ -686,7 +737,7 @@ void FLinuxPlatformSplash::Show( ) // In the editor, we'll display loading info FText AppName; - if( GIsEditor ) + if ( GIsEditor ) { // Set initial startup progress info { @@ -725,7 +776,6 @@ void FLinuxPlatformSplash::Show( ) delete GSplashState; GSplashState = nullptr; } -#endif //WITH_EDITOR } @@ -734,10 +784,8 @@ void FLinuxPlatformSplash::Show( ) */ void FLinuxPlatformSplash::Hide() { -#if WITH_EDITOR delete GSplashState; GSplashState = nullptr; -#endif } @@ -749,7 +797,6 @@ void FLinuxPlatformSplash::Hide() */ void FLinuxPlatformSplash::SetSplashText( const SplashTextType::Type InType, const TCHAR* InText ) { -#if WITH_EDITOR if (GSplashState) { // We only want to bother drawing startup progress in the editor, since this information is @@ -760,6 +807,4 @@ void FLinuxPlatformSplash::SetSplashText( const SplashTextType::Type InType, con } GSplashState->Pump(); } - -#endif //WITH_EDITOR } diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxWindow.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxWindow.cpp index 38b99382149e..777676fdbcf9 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxWindow.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxWindow.cpp @@ -529,6 +529,11 @@ void FLinuxWindow::ReshapeWindow( int32 NewX, int32 NewY, int32 NewWidth, int32 } } + // If we have set our self to 0,0 Width/Height it will not be allowed we will still show the window + // this is a work around to at least reduce the visibile impact of a window that is lingering + NewWidth = FMath::Max(NewWidth, 1); + NewHeight = FMath::Max(NewHeight, 1); + // X11 will take until the next frame to send a SizeChanged event. This means the X11 window // will most likely have resized already by the time we render but the slate renderer will // not have been updated leading to an incorrect frame. @@ -580,7 +585,8 @@ void FLinuxWindow::ReshapeWindow( int32 NewX, int32 NewY, int32 NewWidth, int32 VirtualWidth = NewWidth; VirtualHeight = NewHeight; - if ( LinuxWindow ) + // Avoid broadcasting we have set a zero size as it will attempt to resize the backbuffer which on some RHI is invalid per the spec (ie. Vulkan) + if (LinuxWindow ) { OwningApplication->GetMessageHandler()->OnSizeChanged( LinuxWindow.ToSharedRef(), @@ -718,7 +724,7 @@ bool FLinuxWindow::IsMaximized() const /** @return true if the native window is minimized, false otherwise */ bool FLinuxWindow::IsMinimized() const { - return SDL_GetWindowFlags(HWnd) & SDL_WINDOW_MINIMIZED; + return SDL_GetWindowFlags(HWnd) & SDL_WINDOW_MINIMIZED || SDL_GetWindowFlags(HWnd) & SDL_WINDOW_HIDDEN; } /** @return true if the native window is visible, false otherwise */ diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Lumin/LuminGamepadInterface.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Lumin/LuminGamepadInterface.cpp index a19eac288e49..93ec0eeccf26 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/Lumin/LuminGamepadInterface.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/Lumin/LuminGamepadInterface.cpp @@ -3,6 +3,7 @@ #include "LuminGamepadInterface.h" #include "Android/AndroidApplication.h" #include "GenericPlatform/GenericApplication.h" +#include "Misc/ConfigCacheIni.h" #include "HAL/PlatformTime.h" #include #include @@ -76,6 +77,9 @@ FLuminGamepadInterface::FLuminGamepadInterface(const TSharedRef< FGenericApplica InitialButtonRepeatDelay = 0.2f; ButtonRepeatDelay = 0.1f; + GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("InitialButtonRepeatDelay"), InitialButtonRepeatDelay, GInputIni); + GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("ButtonRepeatDelay"), ButtonRepeatDelay, GInputIni); + // In the engine, all controllers map to xbox controllers for consistency X360ToXboxControllerMapping[0] = 0; // A X360ToXboxControllerMapping[1] = 1; // B diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Mac/HIDInputInterface.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Mac/HIDInputInterface.cpp index 7dbe0be41c06..7bab19199005 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/Mac/HIDInputInterface.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/Mac/HIDInputInterface.cpp @@ -3,6 +3,7 @@ #include "HIDInputInterface.h" #include "HAL/PlatformTime.h" #include "Misc/CallbackDevice.h" +#include "Misc/ConfigCacheIni.h" #include "Templates/SharedPointer.h" static int32 GetDevicePropertyAsInt32(IOHIDDeviceRef DeviceRef, CFStringRef Property) @@ -37,6 +38,9 @@ HIDInputInterface::HIDInputInterface(const TSharedRefGetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("InitialButtonRepeatDelay"), InitialButtonRepeatDelay, GInputIni); + GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("ButtonRepeatDelay"), ButtonRepeatDelay, GInputIni); + Buttons[0] = FGamepadKeyNames::FaceButtonBottom; Buttons[1] = FGamepadKeyNames::FaceButtonRight; Buttons[2] = FGamepadKeyNames::FaceButtonLeft; diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIABaseProvider.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIABaseProvider.cpp new file mode 100644 index 000000000000..d13a767335fb --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIABaseProvider.cpp @@ -0,0 +1,55 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_ACCESSIBILITY + +#include "Windows/Accessibility/WindowsUIABaseProvider.h" + +#include "Misc/AssertionMacros.h" +#include "Windows/Accessibility/WindowsUIAManager.h" +#include "GenericPlatform/GenericAccessibleInterfaces.h" + +FWindowsUIABaseProvider::FWindowsUIABaseProvider(FWindowsUIAManager& InManager, TSharedRef InWidget) + : UIAManager(&InManager) + , Widget(InWidget) + , RefCount(1) +{ + // Register with the UIA Manager in order to receive OnUIAManagerDestroyed signal. + UIAManager->ProviderList.Add(this); +} + +FWindowsUIABaseProvider::~FWindowsUIABaseProvider() +{ + if (UIAManager) + { + UIAManager->ProviderList.Remove(this); + } +} + +void FWindowsUIABaseProvider::OnUIAManagerDestroyed() +{ + UIAManager = nullptr; +} + +uint32 FWindowsUIABaseProvider::IncrementRef() +{ + // todo: check if this needs to be threadsafe using InterlockedIncrement + return ++RefCount; +} + +uint32 FWindowsUIABaseProvider::DecrementRef() +{ + ensure(RefCount > 0); + if (--RefCount == 0) + { + delete this; + return 0; + } + return RefCount; +} + +bool FWindowsUIABaseProvider::IsValid() const +{ + return UIAManager != nullptr && Widget->IsValid(); +} + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAControlProvider.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAControlProvider.cpp new file mode 100644 index 000000000000..d2353dba787c --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAControlProvider.cpp @@ -0,0 +1,700 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_ACCESSIBILITY + +#include "Windows/Accessibility/WindowsUIAControlProvider.h" +#include "Windows/Accessibility/WindowsUIAWidgetProvider.h" +#include "Windows/Accessibility/WindowsUIAPropertyGetters.h" +#include "Windows/Accessibility/WindowsUIAManager.h" +#include "GenericPlatform/GenericAccessibleInterfaces.h" + +// FWindowsUIATextRangeProvider + +FWindowsUIATextRangeProvider::FWindowsUIATextRangeProvider(FWindowsUIAManager& InManager, TSharedRef InWidget, FTextRange InRange) + : FWindowsUIABaseProvider(InManager, InWidget) + , TextRange(InRange) +{ +} + +FWindowsUIATextRangeProvider::~FWindowsUIATextRangeProvider() +{ +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::QueryInterface(REFIID riid, void** ppInterface) +{ + *ppInterface = nullptr; + + if (riid == __uuidof(IUnknown)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(ITextRangeProvider)) + { + *ppInterface = static_cast(this); + } + + if (*ppInterface) + { + AddRef(); + return S_OK; + } + else + { + return E_NOINTERFACE; + } +} + +FString FWindowsUIATextRangeProvider::TextFromTextRange() +{ + return TextFromTextRange(Widget->AsText()->GetText(), TextRange); +} + +FString FWindowsUIATextRangeProvider::TextFromTextRange(const FString& InString, const FTextRange& InRange) +{ + return InString.Mid(InRange.BeginIndex, InRange.EndIndex); +} + +ULONG STDCALL FWindowsUIATextRangeProvider::AddRef() +{ + return FWindowsUIABaseProvider::IncrementRef(); +} + +ULONG STDCALL FWindowsUIATextRangeProvider::Release() +{ + return FWindowsUIABaseProvider::DecrementRef(); +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::Clone(ITextRangeProvider** pRetVal) +{ + if (IsValid()) + { + *pRetVal = new FWindowsUIATextRangeProvider(*UIAManager, Widget, TextRange); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::Compare(ITextRangeProvider* range, BOOL* pRetVal) +{ + // The documentation states that different endpoints that produce the same text are not equal, + // but doesn't say anything about the same endpoints that come from different control providers. + // Perhaps we can assume that comparing text ranges from different Widgets is not valid. + *pRetVal = TextRange == static_cast(range)->TextRange; + return S_OK; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider* targetRange, TextPatternRangeEndpoint targetEndpoint, int* pRetVal) +{ + FWindowsUIATextRangeProvider* CastedRange = static_cast(targetRange); + int32 ThisEndpointIndex = (endpoint == TextPatternRangeEndpoint_Start) ? TextRange.BeginIndex : TextRange.EndIndex; + int32 OtherEndpointIndex = (targetEndpoint == TextPatternRangeEndpoint_Start) ? CastedRange->TextRange.BeginIndex : CastedRange->TextRange.EndIndex; + *pRetVal = ThisEndpointIndex - OtherEndpointIndex; + return S_OK; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::ExpandToEnclosingUnit(TextUnit unit) +{ + if (IsValid()) + { + switch (unit) + { + case TextUnit_Character: + TextRange.EndIndex = FMath::Min(TextRange.BeginIndex + 1, Widget->AsText()->GetText().Len()); + break; + case TextUnit_Format: + return E_NOTIMPL; + case TextUnit_Word: + return E_NOTIMPL; + case TextUnit_Line: + return E_NOTIMPL; + case TextUnit_Paragraph: + return E_NOTIMPL; + case TextUnit_Page: + return E_NOTIMPL; + case TextUnit_Document: + TextRange = FTextRange(0, Widget->AsText()->GetText().Len()); + break; + } + + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::FindAttribute(TEXTATTRIBUTEID attributeId, VARIANT val, BOOL backward, ITextRangeProvider** pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::FindText(BSTR text, BOOL backward, BOOL ignoreCase, ITextRangeProvider** pRetVal) +{ + if (IsValid()) + { + FString TextToSearch(text); + int32 FoundIndex = TextFromTextRange().Find(TextToSearch, ignoreCase ? ESearchCase::IgnoreCase : ESearchCase::CaseSensitive, backward ? ESearchDir::FromEnd : ESearchDir::FromStart); + if (FoundIndex == INDEX_NONE) + { + *pRetVal = nullptr; + } + else + { + const int32 StartIndex = TextRange.BeginIndex + FoundIndex; + *pRetVal = new FWindowsUIATextRangeProvider(*UIAManager, Widget, FTextRange(StartIndex, StartIndex + TextToSearch.Len())); + } + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::GetAttributeValue(TEXTATTRIBUTEID attributeId, VARIANT* pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::GetBoundingRectangles(SAFEARRAY** pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::GetEnclosingElement(IRawElementProviderSimple** pRetVal) +{ + if (IsValid()) + { + *pRetVal = static_cast(&UIAManager->GetWidgetProvider(Widget)); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::GetText(int maxLength, BSTR* pRetVal) +{ + if (IsValid()) + { + *pRetVal = SysAllocString(*TextFromTextRange().Left(maxLength)); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::Move(TextUnit unit, int count, int* pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count, int* pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider* targetRange, TextPatternRangeEndpoint targetEndpoint) +{ + FWindowsUIATextRangeProvider* CastedRange = static_cast(targetRange); + int32 NewIndex = (targetEndpoint == TextPatternRangeEndpoint_Start) ? CastedRange->TextRange.BeginIndex : CastedRange->TextRange.EndIndex; + if (endpoint == TextPatternRangeEndpoint_Start) + { + TextRange.BeginIndex = NewIndex; + if (TextRange.BeginIndex > TextRange.EndIndex) + { + TextRange.EndIndex = TextRange.BeginIndex; + } + } + else + { + TextRange.EndIndex = NewIndex; + if (TextRange.BeginIndex > TextRange.EndIndex) + { + TextRange.BeginIndex = TextRange.EndIndex; + } + } + return S_OK; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::Select() +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::AddToSelection() +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::RemoveFromSelection() +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::ScrollIntoView(BOOL alignToTop) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::GetChildren(SAFEARRAY** pRetVal) +{ + *pRetVal = nullptr; + return S_OK; +} + +// ~ + +// FWindowsUIAControlProvider + +FWindowsUIAControlProvider::FWindowsUIAControlProvider(FWindowsUIAManager& InManager, TSharedRef InWidget) + : FWindowsUIABaseProvider(InManager, InWidget) +{ +} + +FWindowsUIAControlProvider::~FWindowsUIAControlProvider() +{ +} + +HRESULT STDCALL FWindowsUIAControlProvider::QueryInterface(REFIID riid, void** ppInterface) +{ + *ppInterface = nullptr; + + if (riid == __uuidof(IInvokeProvider)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(IRangeValueProvider)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(ITextProvider)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(IToggleProvider)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(IValueProvider)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(IWindowProvider)) + { + *ppInterface = static_cast(this); + } + + if (*ppInterface) + { + AddRef(); + return S_OK; + } + else + { + return E_NOINTERFACE; + } +} + +ULONG STDCALL FWindowsUIAControlProvider::AddRef() +{ + return FWindowsUIABaseProvider::IncrementRef(); +} + +ULONG STDCALL FWindowsUIAControlProvider::Release() +{ + return FWindowsUIABaseProvider::DecrementRef(); +} + +HRESULT STDCALL FWindowsUIAControlProvider::Invoke() +{ + if (IsValid()) + { + Widget->AsActivatable()->Activate(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::SetValue(double val) +{ + if (IsValid()) + { + Widget->AsProperty()->SetValue(FString::SanitizeFloat(val)); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_Value(double* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_RangeValueValuePropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_IsReadOnly(BOOL* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_ValueIsReadOnlyPropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_Maximum(double* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_RangeValueMaximumPropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_Minimum(double* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_RangeValueMinimumPropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_LargeChange(double* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_RangeValueLargeChangePropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_SmallChange(double* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_RangeValueSmallChangePropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_DocumentRange(ITextRangeProvider** pRetVal) +{ + if (IsValid()) + { + *pRetVal = static_cast(new FWindowsUIATextRangeProvider(*UIAManager, Widget, FTextRange(0, Widget->AsText()->GetText().Len()))); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_SupportedTextSelection(SupportedTextSelection* pRetVal) +{ + // todo: implement selection + *pRetVal = SupportedTextSelection_None; + return S_OK; +} + +HRESULT STDCALL FWindowsUIAControlProvider::GetSelection(SAFEARRAY** pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIAControlProvider::GetVisibleRanges(SAFEARRAY** pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIAControlProvider::RangeFromChild(IRawElementProviderSimple* childElement, ITextRangeProvider** pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIAControlProvider::RangeFromPoint(UiaPoint point, ITextRangeProvider** pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_ToggleState(ToggleState* pRetVal) +{ + //ECheckBoxState CheckState = Widget->AsActivatable()->GetCheckedState(); + //switch (CheckState) + //{ + //case ECheckBoxState::Checked: + // *pRetVal = ToggleState_On; + // break; + //case ECheckBoxState::Unchecked: + // *pRetVal = ToggleState_Off; + // break; + //case ECheckBoxState::Undetermined: + // *pRetVal = ToggleState_Indeterminate; + // break; + //} + if (IsValid()) + { + *pRetVal = static_cast(WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_ToggleToggleStatePropertyId).GetValue()); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::Toggle() +{ + if (IsValid()) + { + Widget->AsActivatable()->Activate(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_CanMove(BOOL *pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_TransformCanMovePropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_CanResize(BOOL *pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_TransformCanResizePropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_CanRotate(BOOL *pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_TransformCanRotatePropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::Move(double x, double y) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIAControlProvider::Resize(double width, double height) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIAControlProvider::Rotate(double degrees) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIAControlProvider::SetValue(LPCWSTR val) +{ + if (IsValid()) + { + Widget->AsProperty()->SetValue(FString(val)); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_Value(BSTR* pRetVal) +{ + if (IsValid()) + { + *pRetVal = SysAllocString(*WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_ValueValuePropertyId).GetValue()); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +//HRESULT STDCALL FWindowsUIAControlProvider::get_IsReadOnly(BOOL* pRetVal) +//{ +// *pRetVal = Widget->AsProperty()->IsReadOnly(); +// return S_OK; +//} + +HRESULT STDCALL FWindowsUIAControlProvider::Close() +{ + if (IsValid()) + { + Widget->AsWindow()->Close(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_CanMaximize(BOOL* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_WindowCanMaximizePropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_CanMinimize(BOOL* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_WindowCanMinimizePropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_IsModal(BOOL* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_WindowIsModalPropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_IsTopmost(BOOL* pRetVal) +{ + // todo: not 100% sure what this is looking for. top window in hierarchy of child windows? on top of all other windows in Windows OS? + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_WindowIsTopmostPropertyId).GetValue(); + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_WindowInteractionState(WindowInteractionState* pRetVal) +{ + if (IsValid()) + { + // todo: do we have a way to identify if the app is processing data vs idling? + *pRetVal = static_cast(WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_WindowWindowInteractionStatePropertyId).GetValue()); + } + else + { + *pRetVal = WindowInteractionState_Closing; + } + return S_OK; +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_WindowVisualState(WindowVisualState* pRetVal) +{ + if (IsValid()) + { + *pRetVal = static_cast(WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_WindowWindowVisualStatePropertyId).GetValue()); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::SetVisualState(WindowVisualState state) +{ + if (IsValid()) + { + switch (state) + { + case WindowVisualState_Normal: + Widget->AsWindow()->SetDisplayState(IAccessibleWindow::EWindowDisplayState::Normal); + break; + case WindowVisualState_Minimized: + Widget->AsWindow()->SetDisplayState(IAccessibleWindow::EWindowDisplayState::Minimize); + break; + case WindowVisualState_Maximized: + Widget->AsWindow()->SetDisplayState(IAccessibleWindow::EWindowDisplayState::Maximize); + break; + } + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::WaitForInputIdle(int milliseconds, BOOL* pRetVal) +{ + return E_NOTIMPL; +} + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAManager.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAManager.cpp new file mode 100644 index 000000000000..9574d151a98a --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAManager.cpp @@ -0,0 +1,208 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_ACCESSIBILITY + +#include "Windows/Accessibility/WindowsUIAManager.h" + +#include "Windows/AllowWindowsPlatformTypes.h" +#include +#include "Windows/HideWindowsPlatformTypes.h" +#include + +#include "Misc/Variant.h" +#include "Windows/Accessibility/WindowsUIAControlProvider.h" +#include "Windows/Accessibility/WindowsUIAWidgetProvider.h" +#include "Windows/Accessibility/WindowsUIAPropertyGetters.h" +#include "Windows/WindowsApplication.h" +#include "Windows/WindowsWindow.h" + +TMap FWindowsUIAManager::WidgetTypeToWindowsTypeMap; +TMap FWindowsUIAManager::WidgetTypeToTextMap; + +#define LOCTEXT_NAMESPACE "SlateAccessibility" + +/** + * Helper function to raise a UIA property changed event. + */ +void EmitPropertyChangedEvent(FWindowsUIAWidgetProvider* Provider, PROPERTYID Property, const FVariant& OldValue, const FVariant& NewValue) +{ + UE_LOG(LogAccessibility, VeryVerbose, TEXT("UIA Property Changed: %i"), Property); + UiaRaiseAutomationPropertyChangedEvent(static_cast(Provider), Property, + WindowsUIAPropertyGetters::FVariantToWindowsVariant(OldValue), + WindowsUIAPropertyGetters::FVariantToWindowsVariant(NewValue)); +} + +FWindowsUIAManager::FWindowsUIAManager(const FWindowsApplication& InApplication) + : WindowsApplication(InApplication) +{ + OnAccessibleMessageHandlerChanged(); + + if (WidgetTypeToWindowsTypeMap.Num() == 0) + { + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::Button, UIA_ButtonControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::CheckBox, UIA_CheckBoxControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::ComboBox, UIA_ComboBoxControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::Hyperlink, UIA_HyperlinkControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::Image, UIA_ImageControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::Layout, UIA_PaneControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::ScrollBar, UIA_ScrollBarControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::Slider, UIA_SliderControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::Text, UIA_TextControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::TextEdit, UIA_EditControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::Window, UIA_WindowControlTypeId); + + WidgetTypeToTextMap.Add(EAccessibleWidgetType::Button, LOCTEXT("ControlTypeButton", "button")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::CheckBox, LOCTEXT("ControlTypeCheckBox", "check box")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::ComboBox, LOCTEXT("ControlTypeComboBox", "combobox")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::Hyperlink, LOCTEXT("ControlTypeHyperlink", "hyperlink")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::Image, LOCTEXT("ControlTypeImage", "image")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::Layout, LOCTEXT("ControlTypeLayout", "pane")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::ScrollBar, LOCTEXT("ControlTypeScrollBar", "scroll bar")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::Slider, LOCTEXT("ControlTypeSlider", "slider")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::Text, LOCTEXT("ControlTypeText", "text")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::TextEdit, LOCTEXT("ControlTypeTextEdit", "edit")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::Window, LOCTEXT("ControlTypeWindow", "window")); + } +} + +void FWindowsUIAManager::OnAccessibleMessageHandlerChanged() +{ + WindowsApplication.GetAccessibleMessageHandler()->SetAccessibleEventDelegate(FGenericAccessibleMessageHandler::FAccessibleEvent::CreateRaw(this, &FWindowsUIAManager::OnEventRaised)); +} + +FWindowsUIAManager::~FWindowsUIAManager() +{ + WindowsApplication.GetAccessibleMessageHandler()->SetAccessibleEventDelegate(FGenericAccessibleMessageHandler::FAccessibleEvent()); + for (FWindowsUIABaseProvider* Provider : ProviderList) + { + // The UIA Manager may be deleted before the Providers are, and external applications may attempt to call functions on those Providers. + Provider->OnUIAManagerDestroyed(); + } +} + +FWindowsUIAWidgetProvider& FWindowsUIAManager::GetWidgetProvider(TSharedRef InWidget) +{ + FWindowsUIAWidgetProvider** CachedProvider = CachedWidgetProviders.Find(InWidget); + if (CachedProvider) + { + (*CachedProvider)->AddRef(); + return **CachedProvider; + } + else if (InWidget->AsWindow()) + { + return *CachedWidgetProviders.Add(InWidget, new FWindowsUIAWindowProvider(*this, InWidget)); + } + else + { + return *CachedWidgetProviders.Add(InWidget, new FWindowsUIAWidgetProvider(*this, InWidget)); + } +} + +FWindowsUIAWindowProvider& FWindowsUIAManager::GetWindowProvider(TSharedRef InWindow) +{ + if (CachedWidgetProviders.Num() == 0) + { + // Enable application accessibility if this is the first Provider. The first Get*Provider() request + // MUST go through this function since IAccessibleWidgets will not exist until SetActive is called. + WindowsApplication.GetAccessibleMessageHandler()->SetActive(true); + } + + return static_cast(GetWidgetProvider(WindowsApplication.GetAccessibleMessageHandler()->GetAccessibleWindow(InWindow).ToSharedRef())); +} + +void FWindowsUIAManager::OnWidgetProviderRemoved(TSharedRef InWidget) +{ + CachedWidgetProviders.Remove(InWidget); + + if (CachedWidgetProviders.Num() == 0) + { + // If the last widget Provider is being removed, we can disable application accessibility. + // Technically an external application could still be running listening for mouse/keyboard events, + // but in practice I don't think its realistic to do this while having no references to any Provider. + // + // Note that there could still be control Providers with valid references. In practice I don't think + // this is possible, but if we run into problems then we can simply call AddRef and Release on the + // underlying widget Provider whenever a control Provider gets added/removed. + WindowsApplication.GetAccessibleMessageHandler()->SetActive(false); + } +} + +void FWindowsUIAManager::OnEventRaised(TSharedRef Widget, EAccessibleEvent Event, FVariant OldValue, FVariant NewValue) +{ + if (UiaClientsAreListening()) + { + FScopedWidgetProvider ScopedProvider(GetWidgetProvider(Widget)); + + switch (Event) + { + case EAccessibleEvent::FocusChange: + { + // On focus change, emit a generic FocusChanged event as well as a per-Provider PropertyChanged event + // todo: handle difference between any focus vs keyboard focus + if (Widget->HasFocus()) + { + UiaRaiseAutomationEvent(&ScopedProvider.Provider, UIA_AutomationFocusChangedEventId); + } + EmitPropertyChangedEvent(&ScopedProvider.Provider, UIA_HasKeyboardFocusPropertyId, OldValue, NewValue); + break; + } + case EAccessibleEvent::Activate: + if (ScopedProvider.Provider.SupportsInterface(UIA_TogglePatternId)) + { + EmitPropertyChangedEvent(&ScopedProvider.Provider, UIA_ToggleToggleStatePropertyId, OldValue, NewValue); + + } + else if (ScopedProvider.Provider.SupportsInterface(UIA_InvokePatternId)) + { + UiaRaiseAutomationEvent(&ScopedProvider.Provider, UIA_Invoke_InvokedEventId); + } + break; + case EAccessibleEvent::Notification: + { + typedef HRESULT(WINAPI* UiaRaiseNotificationEventFunc)(IRawElementProviderSimple*, NotificationKind, NotificationProcessing, BSTR, BSTR); + HMODULE Handle = GetModuleHandle(TEXT("Uiautomationcore.dll")); + if (Handle) + { + // Cast to intermediary void* to avoid compiler warning 4191, since GetProcAddress doesn't know function arguments + UiaRaiseNotificationEventFunc NotificationFunc = (UiaRaiseNotificationEventFunc)(void*)GetProcAddress(Handle, "UiaRaiseNotificationEvent"); + if (NotificationFunc) + { + NotificationFunc(&ScopedProvider.Provider, NotificationKind_ActionCompleted, NotificationProcessing_All, SysAllocString(*NewValue.GetValue()), SysAllocString(TEXT(""))); + } + } + break; + } + case EAccessibleEvent::BeforeRemoveFromParent: + { + // ChildRemoved events require an ID passed along with them to identify which child (which no longer exists) was removed + int id[2] = { UiaAppendRuntimeId, Widget->GetId() }; + FScopedWidgetProvider ParentProvider(GetWidgetProvider(Widget->GetParent().ToSharedRef())); + UiaRaiseStructureChangedEvent(&ParentProvider.Provider, StructureChangeType_ChildRemoved, id, ARRAYSIZE(id)); + break; + } + case EAccessibleEvent::AfterAddToParent: + { + FScopedWidgetProvider ParentProvider(GetWidgetProvider(Widget->GetParent().ToSharedRef())); + UiaRaiseStructureChangedEvent(&ParentProvider.Provider, StructureChangeType_ChildAdded, nullptr, 0); + break; + } + case EAccessibleEvent::WidgetRemoved: + typedef HRESULT(WINAPI* UiaDisconnectProviderFunc)(IRawElementProviderSimple*); + HMODULE Handle = GetModuleHandle(TEXT("Uiautomationcore.dll")); + if (Handle) + { + // Cast to intermediary void* to avoid compiler warning 4191, since GetProcAddress doesn't know function arguments + UiaDisconnectProviderFunc DisconnectFunc = (UiaDisconnectProviderFunc)(void*)GetProcAddress(Handle, "UiaDisconnectProvider"); + if (DisconnectFunc) + { + DisconnectFunc(&ScopedProvider.Provider); + } + } + break; + } + } +} + +#undef LOCTEXT_NAMESPACE + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAPropertyGetters.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAPropertyGetters.cpp new file mode 100644 index 000000000000..8f9c0f8e92a6 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAPropertyGetters.cpp @@ -0,0 +1,106 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_ACCESSIBILITY + +#include "Windows/Accessibility/WindowsUIAPropertyGetters.h" + +#include "GenericPlatform/GenericAccessibleInterfaces.h" +#include "Misc/Variant.h" + +namespace WindowsUIAPropertyGetters +{ + +VARIANT GetPropertyValueWindows(TSharedRef AccessibleWidget, PROPERTYID WindowsPropertyId) +{ + return FVariantToWindowsVariant(GetPropertyValue(AccessibleWidget, WindowsPropertyId)); +} + +FVariant GetPropertyValue(TSharedRef AccessibleWidget, PROPERTYID WindowsPropertyId) +{ + switch (WindowsPropertyId) + { + case UIA_ValueIsReadOnlyPropertyId: + case UIA_RangeValueIsReadOnlyPropertyId: + return AccessibleWidget->AsProperty()->IsReadOnly(); + case UIA_RangeValueLargeChangePropertyId: + case UIA_RangeValueSmallChangePropertyId: + return static_cast(AccessibleWidget->AsProperty()->GetStepSize()); + case UIA_RangeValueMaximumPropertyId: + return static_cast(AccessibleWidget->AsProperty()->GetMaximum()); + case UIA_RangeValueMinimumPropertyId: + return static_cast(AccessibleWidget->AsProperty()->GetMinimum()); + case UIA_RangeValueValuePropertyId: + return static_cast(FCString::Atof(*AccessibleWidget->AsProperty()->GetValue())); + case UIA_ToggleToggleStatePropertyId: + return static_cast(AccessibleWidget->AsActivatable()->GetCheckedState() ? ToggleState_On : ToggleState_Off); + case UIA_TransformCanMovePropertyId: + return false; + case UIA_TransformCanResizePropertyId: + return false; + case UIA_TransformCanRotatePropertyId: + return false; + case UIA_ValueValuePropertyId: + return AccessibleWidget->AsProperty()->GetValue(); + case UIA_WindowCanMaximizePropertyId: + return AccessibleWidget->AsWindow()->SupportsDisplayState(IAccessibleWindow::EWindowDisplayState::Maximize); + case UIA_WindowCanMinimizePropertyId: + return AccessibleWidget->AsWindow()->SupportsDisplayState(IAccessibleWindow::EWindowDisplayState::Minimize); + case UIA_WindowIsModalPropertyId: + return AccessibleWidget->AsWindow()->IsModal(); + case UIA_WindowIsTopmostPropertyId: + return false; + case UIA_WindowWindowInteractionStatePropertyId: + return static_cast(WindowInteractionState_Running); + case UIA_WindowWindowVisualStatePropertyId: + switch (AccessibleWidget->AsWindow()->GetDisplayState()) + { + case IAccessibleWindow::EWindowDisplayState::Normal: + return static_cast(WindowVisualState_Normal); + case IAccessibleWindow::EWindowDisplayState::Minimize: + return static_cast(WindowVisualState_Minimized); + case IAccessibleWindow::EWindowDisplayState::Maximize: + return static_cast(WindowVisualState_Maximized); + } + } + + return FVariant(); +} + +VARIANT FVariantToWindowsVariant(const FVariant& Value) +{ + VARIANT OutValue; + switch (Value.GetType()) + { + case EVariantTypes::Bool: + OutValue.vt = VT_BOOL; + OutValue.boolVal = Value.GetValue() ? VARIANT_TRUE : VARIANT_FALSE; + break; + case EVariantTypes::Double: + OutValue.vt = VT_R8; + OutValue.dblVal = Value.GetValue(); + break; + case EVariantTypes::Float: + OutValue.vt = VT_R4; + OutValue.fltVal = Value.GetValue(); + break; + case EVariantTypes::Int32: + OutValue.vt = VT_I4; + // todo: verify correct behavior on different operating systems + OutValue.intVal = Value.GetValue(); + break; + case EVariantTypes::String: + OutValue.vt = VT_BSTR; + OutValue.bstrVal = SysAllocString(*Value.GetValue()); + break; + case EVariantTypes::Empty: + default: + OutValue.vt = VT_EMPTY; + break; + } + + return OutValue; +} + +} + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAWidgetProvider.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAWidgetProvider.cpp new file mode 100644 index 000000000000..33cd18f4c1e0 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAWidgetProvider.cpp @@ -0,0 +1,666 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_ACCESSIBILITY + +#include "Windows/Accessibility/WindowsUIAWidgetProvider.h" + +#include "GenericPlatform/GenericAccessibleInterfaces.h" +#include "Windows/Accessibility/WindowsUIAControlProvider.h" +#include "Windows/Accessibility/WindowsUIAManager.h" +#include "Windows/Accessibility/WindowsUIAPropertyGetters.h" +#include "Windows/WindowsWindow.h" + +DECLARE_CYCLE_STAT(TEXT("Windows Accessibility: Navigate"), STAT_AccessibilityWindowsNavigate, STATGROUP_Accessibility); +DECLARE_CYCLE_STAT(TEXT("Windows Accessibility: GetProperty"), STAT_AccessibilityWindowsGetProperty, STATGROUP_Accessibility); + +#define LOCTEXT_NAMESPACE "SlateAccessibility" + +/** Convert our accessible widget type to a Windows control ID */ +ULONG WidgetTypeToControlType(TSharedRef Widget) +{ + ULONG* Type = FWindowsUIAManager::WidgetTypeToWindowsTypeMap.Find(Widget->GetWidgetType()); + if (Type) + { + return *Type; + } + else + { + return UIA_CustomControlTypeId; + } +} + +/** + * Convert our accessible widget type to a human-readable localized string + * See https://docs.microsoft.com/en-us/windows/desktop/winauto/uiauto-automation-element-propids for rules. + */ +FString WidgetTypeToLocalizedString(TSharedRef Widget) +{ + FText* Text = FWindowsUIAManager::WidgetTypeToTextMap.Find(Widget->GetWidgetType()); + if (Text) + { + return Text->ToString(); + } + else + { + static FText CustomControlTypeName = LOCTEXT("ControlTypeCustom", "custom"); + return CustomControlTypeName.ToString(); + } +} + +// FWindowsUIAWidgetProvider methods + +FWindowsUIAWidgetProvider::FWindowsUIAWidgetProvider(FWindowsUIAManager& InManager, TSharedRef InWidget) + : FWindowsUIABaseProvider(InManager, InWidget) +{ + //UpdateCachedProperties(); +} + +FWindowsUIAWidgetProvider::~FWindowsUIAWidgetProvider() +{ + if (UIAManager) + { + UIAManager->OnWidgetProviderRemoved(Widget); + } +} + +//void FWindowsUIAWidgetProvider::UpdateCachedProperty(PROPERTYID PropertyId) +//{ +// FVariant& CachedValue = CachedPropertyValues.FindOrAdd(PropertyId); +// FVariant CurrentValue = WindowsUIAPropertyGetters::GetPropertyValue(Widget, PropertyId); +// if (CachedValue.IsEmpty()) +// { +// CachedValue = CurrentValue; +// } +// else if (CachedValue != CurrentValue) +// { +// UE_LOG(LogAccessibility, VeryVerbose, TEXT("UIA Property Changed: %i"), PropertyId); +// UiaRaiseAutomationPropertyChangedEvent( +// static_cast(this), PropertyId, +// WindowsUIAPropertyGetters::FVariantToWindowsVariant(CachedValue), +// WindowsUIAPropertyGetters::FVariantToWindowsVariant(CurrentValue)); +// +// CachedValue = CurrentValue; +// } +//} +// +//void FWindowsUIAWidgetProvider::UpdateCachedProperties() +//{ +// if (IsValid()) +// { +// if (SupportsInterface(UIA_RangeValuePatternId)) +// { +// UpdateCachedProperty(UIA_RangeValueIsReadOnlyPropertyId); +// UpdateCachedProperty(UIA_RangeValueValuePropertyId); +// UpdateCachedProperty(UIA_RangeValueIsReadOnlyPropertyId); +// UpdateCachedProperty(UIA_RangeValueMinimumPropertyId); +// UpdateCachedProperty(UIA_RangeValueMaximumPropertyId); +// UpdateCachedProperty(UIA_RangeValueLargeChangePropertyId); +// UpdateCachedProperty(UIA_RangeValueSmallChangePropertyId); +// } +// +// if (SupportsInterface(UIA_TogglePatternId)) +// { +// UpdateCachedProperty(UIA_ToggleToggleStatePropertyId); +// } +// +// if (SupportsInterface(UIA_ValuePatternId)) +// { +// UpdateCachedProperty(UIA_ValueIsReadOnlyPropertyId); +// UpdateCachedProperty(UIA_ValueValuePropertyId); +// } +// +// if (SupportsInterface(UIA_WindowPatternId)) +// { +// UpdateCachedProperty(UIA_WindowCanMaximizePropertyId); +// UpdateCachedProperty(UIA_WindowCanMinimizePropertyId); +// UpdateCachedProperty(UIA_WindowIsModalPropertyId); +// UpdateCachedProperty(UIA_WindowIsTopmostPropertyId); +// UpdateCachedProperty(UIA_WindowWindowInteractionStatePropertyId); +// UpdateCachedProperty(UIA_WindowWindowVisualStatePropertyId); +// UpdateCachedProperty(UIA_TransformCanMovePropertyId); +// UpdateCachedProperty(UIA_TransformCanRotatePropertyId); +// UpdateCachedProperty(UIA_TransformCanResizePropertyId); +// } +// } +//} + +HRESULT STDCALL FWindowsUIAWidgetProvider::QueryInterface(REFIID riid, void** ppInterface) +{ + if (riid == __uuidof(IUnknown)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(IRawElementProviderSimple)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(IRawElementProviderFragment)) + { + *ppInterface = static_cast(this); + } + else + { + *ppInterface = nullptr; + } + + if (*ppInterface) + { + // QueryInterface is the one exception where we need to call AddRef without going through GetWidgetProvider(). + AddRef(); + return S_OK; + } + else + { + return E_NOINTERFACE; + } +} + +ULONG STDCALL FWindowsUIAWidgetProvider::AddRef() +{ + return FWindowsUIABaseProvider::IncrementRef(); +} + +ULONG STDCALL FWindowsUIAWidgetProvider::Release() +{ + return FWindowsUIABaseProvider::DecrementRef(); +} + +bool FWindowsUIAWidgetProvider::SupportsInterface(PATTERNID PatternId) const +{ + switch (PatternId) + { + case UIA_InvokePatternId: + { + IAccessibleActivatable* Activatable = Widget->AsActivatable(); + // Toggle and Invoke are mutually exclusive + return Activatable && !Activatable->IsCheckable(); + } + case UIA_RangeValuePatternId: + { + IAccessibleProperty* Property = Widget->AsProperty(); + // Value and RangeValue are mutually exclusive + return Property && Property->GetStepSize() > 0.0f; + } + case UIA_TextPatternId: + { + return Widget->AsText() != nullptr; + } + case UIA_TogglePatternId: + { + IAccessibleActivatable* Activatable = Widget->AsActivatable(); + return Activatable && Activatable->IsCheckable(); + } + case UIA_ValuePatternId: + { + IAccessibleProperty* Property = Widget->AsProperty(); + return Property && FMath::IsNearlyZero(Property->GetStepSize()); + } + } + return false; +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::get_ProviderOptions(ProviderOptions* pRetVal) +{ + // ServerSideProvider means that we are creating the definition of the accessible widgets for Clients (eg screenreaders) to consume. + // UseComThreading is necessary to ensure that COM messages are properly routed to the main thread. + *pRetVal = static_cast(ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading); + return S_OK; +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::GetPatternProvider(PATTERNID patternId, IUnknown** pRetVal) +{ + if (IsValid()) + { + if (SupportsInterface(patternId)) + { + // FWindowsUIAControlProvider implements all possible control providers that we support. + FWindowsUIAControlProvider* ControlProvider = new FWindowsUIAControlProvider(*UIAManager, Widget); + switch (patternId) + { + case UIA_InvokePatternId: + *pRetVal = static_cast(ControlProvider); + break; + case UIA_RangeValuePatternId: + *pRetVal = static_cast(ControlProvider); + break; + case UIA_TextPatternId: + *pRetVal = static_cast(ControlProvider); + break; + case UIA_TogglePatternId: + *pRetVal = static_cast(ControlProvider); + break; + case UIA_ValuePatternId: + *pRetVal = static_cast(ControlProvider); + break; + default: + UE_LOG(LogAccessibility, Error, TEXT("FWindowsUIAWidgetProvider::SupportsInterface() returned true, but was unhandled in GetPatternProvider(). PatternId = %i"), patternId); + *pRetVal = nullptr; + ControlProvider->Release(); + break; + } + } + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::GetPropertyValue(PROPERTYID propertyId, VARIANT* pRetVal) +{ + SCOPE_CYCLE_COUNTER(STAT_AccessibilityWindowsGetProperty); + + if (!IsValid()) + { + return UIA_E_ELEMENTNOTAVAILABLE; + } + + bool bValid = true; + + // https://docs.microsoft.com/en-us/windows/desktop/winauto/uiauto-automation-element-propids + // potential other properties: + // - UIA_CenterPointPropertyId + // - UIA_ControllerForPropertyId (eg. to make clicking on a label also click a checkbox) + // - UIA_DescribedByPropertyId (same as LabeledBy? not sure) + // - UIA_IsDialogPropertyId (doesn't seem to exist in our version of UIA) + // - UIA_ItemStatusPropertyId (eg. busy, loading) + // - UIA_LabeledByPropertyId (eg. to describe a checkbox from a separate label) + // - UIA_NativeWindowHandlePropertyId (unclear if this is only for windows or all widgets) + // - UIA_PositionInSetPropertyId (position in list of items, could be useful) + // - UIA_SizeOfSetPropertyId (number of items in list, could be useful) + switch (propertyId) + { + //case UIA_AcceleratorKeyPropertyId: + // pRetVal->vt = VT_BSTR; + // // todo: hotkey, used with IInvokeProvider + // pRetVal->bstrVal = SysAllocString(L""); + // break; + //case UIA_AccessKeyPropertyId: + // pRetVal->vt = VT_BSTR; + // // todo: activates menu, like F in &File + // pRetVal->bstrVal = SysAllocString(L""); + // break; + //case UIA_AutomationIdPropertyId: + // pRetVal->vt = VT_BSTR; + // // todo: an identifiable name for the widget for automation testing, used like App.Find("MainMenuBar") + // pRetVal->bstrVal = SysAllocString(L""); + // break; + case UIA_BoundingRectanglePropertyId: + pRetVal->vt = VT_R8 | VT_ARRAY; + pRetVal->parray = SafeArrayCreateVector(VT_R8, 0, 4); + if (pRetVal->parray) + { + FBox2D Box = Widget->GetBounds(); + LONG i = 0; + bValid &= (SafeArrayPutElement(pRetVal->parray, &i, &Box.Min.X) != S_OK); + i = 1; + bValid &= (SafeArrayPutElement(pRetVal->parray, &i, &Box.Max.X) != S_OK); + i = 2; + bValid &= (SafeArrayPutElement(pRetVal->parray, &i, &Box.Min.Y) != S_OK); + i = 3; + bValid &= (SafeArrayPutElement(pRetVal->parray, &i, &Box.Max.Y) != S_OK); + } + else + { + bValid = false; + } + break; + case UIA_ClassNamePropertyId: + pRetVal->vt = VT_BSTR; + pRetVal->bstrVal = SysAllocString(*Widget->GetClassName()); + break; + case UIA_ControlTypePropertyId: + pRetVal->vt = VT_I4; + pRetVal->lVal = WidgetTypeToControlType(Widget); + break; + //case UIA_CulturePropertyId: + // pRetVal->vt = VT_I4; + // // todo: https://docs.microsoft.com/en-us/windows/desktop/Intl/language-identifier-constants-and-strings + // pRetVal->lVal = 0; + // break; + case UIA_FrameworkIdPropertyId: + pRetVal->vt = VT_BSTR; + // todo: figure out what goes here + pRetVal->bstrVal = SysAllocString(*LOCTEXT("Slate", "Slate").ToString()); + break; + case UIA_HasKeyboardFocusPropertyId: + pRetVal->vt = VT_BOOL; + pRetVal->boolVal = Widget->HasFocus() ? VARIANT_TRUE : VARIANT_FALSE; + break; + case UIA_HelpTextPropertyId: + pRetVal->vt = VT_BSTR; + pRetVal->bstrVal = SysAllocString(*Widget->GetHelpText()); + break; + case UIA_IsContentElementPropertyId: + pRetVal->vt = VT_BOOL; + // todo: https://docs.microsoft.com/en-us/windows/desktop/winauto/uiauto-treeoverview + pRetVal->boolVal = VARIANT_TRUE; + break; + case UIA_IsControlElementPropertyId: + pRetVal->vt = VT_BOOL; + // todo: https://docs.microsoft.com/en-us/windows/desktop/winauto/uiauto-treeoverview + pRetVal->boolVal = VARIANT_TRUE; + break; + case UIA_IsEnabledPropertyId: + pRetVal->vt = VT_BOOL; + pRetVal->boolVal = Widget->IsEnabled() ? VARIANT_TRUE : VARIANT_FALSE; + break; + case UIA_IsKeyboardFocusablePropertyId: + pRetVal->vt = VT_BOOL; + pRetVal->boolVal = Widget->SupportsFocus() ? VARIANT_TRUE : VARIANT_FALSE; + break; + case UIA_IsOffscreenPropertyId: + pRetVal->vt = VT_BOOL; + pRetVal->boolVal = Widget->IsHidden() ? VARIANT_TRUE : VARIANT_FALSE; + break; + case UIA_IsPasswordPropertyId: + if (Widget->AsProperty()) + { + pRetVal->vt = VT_BOOL; + pRetVal->boolVal = Widget->AsProperty()->IsPassword() ? VARIANT_TRUE : VARIANT_FALSE; + } + break; +//#if WINVER >= 0x0603 // Windows 8.1 +// case UIA_IsPeripheralPropertyId: +// pRetVal->vt = VT_BOOL; +// // todo: see https://docs.microsoft.com/en-us/windows/desktop/winauto/uiauto-automation-element-propids for list of control types +// pRetVal->boolVal = VARIANT_FALSE; +// break; +//#endif +// case UIA_ItemTypePropertyId: +// pRetVal->vt = VT_BSTR; +// // todo: friendly name of what's in a listview +// pRetVal->bstrVal = SysAllocString(L""); +// break; +//#if WINVER >= 0x0602 // Windows 8 +// case UIA_LiveSettingPropertyId: +// pRetVal->vt = VT_I4; +// // todo: "politeness" setting +// pRetVal->lVal = 0; +// break; +//#endif + case UIA_LocalizedControlTypePropertyId: + pRetVal->vt = VT_BSTR; + pRetVal->bstrVal = SysAllocString(*WidgetTypeToLocalizedString(Widget)); + break; + case UIA_NamePropertyId: + pRetVal->vt = VT_BSTR; + // todo: slate widgets don't have names, screen reader may read this as accessible text + pRetVal->bstrVal = SysAllocString(*Widget->GetWidgetName()); + break; + //case UIA_OrientationPropertyId: + // pRetVal->vt = VT_I4; + // // todo: sliders, scroll bars, layouts + // pRetVal->lVal = OrientationType_None; + // break; + case UIA_ProcessIdPropertyId: + pRetVal->vt = VT_I4; + pRetVal->lVal = ::GetCurrentProcessId(); + break; + default: + pRetVal->vt = VT_EMPTY; + break; + } + + if (!bValid) + { + pRetVal->vt = VT_EMPTY; + return E_FAIL; + } + + return S_OK; +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::get_HostRawElementProvider(IRawElementProviderSimple** pRetVal) +{ + // Only return host provider for native windows + *pRetVal = nullptr; + return S_OK; +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::Navigate(NavigateDirection direction, IRawElementProviderFragment** pRetVal) +{ + SCOPE_CYCLE_COUNTER(STAT_AccessibilityWindowsNavigate); + + if (!IsValid()) + { + return UIA_E_ELEMENTNOTAVAILABLE; + } + + TSharedPtr Relative = nullptr; + switch (direction) + { + case NavigateDirection_Parent: + Relative = Widget->GetParent(); + break; + case NavigateDirection_NextSibling: + Relative = Widget->GetNextSibling(); + break; + case NavigateDirection_PreviousSibling: + Relative = Widget->GetPreviousSibling(); + break; + case NavigateDirection_FirstChild: + if (Widget->GetNumberOfChildren() > 0) + { + Relative = Widget->GetChildAt(0); + } + break; + case NavigateDirection_LastChild: + { + const int32 NumChildren = Widget->GetNumberOfChildren(); + if (NumChildren > 0) + { + Relative = Widget->GetChildAt(NumChildren - 1); + } + break; + } + } + + if (Relative.IsValid()) + { + *pRetVal = static_cast(&UIAManager->GetWidgetProvider(Relative.ToSharedRef())); + } + else + { + *pRetVal = nullptr; + } + return S_OK; +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::GetRuntimeId(SAFEARRAY** pRetVal) +{ + if (IsValid()) + { + int rtId[] = { UiaAppendRuntimeId, Widget->GetId() }; + *pRetVal = SafeArrayCreateVector(VT_I4, 0, 2); + if (*pRetVal) + { + for (LONG i = 0; i < 2; ++i) + { + if (SafeArrayPutElement(*pRetVal, &i, &rtId[i]) != S_OK) + { + return E_FAIL; + } + } + }; + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::get_BoundingRectangle(UiaRect* pRetVal) +{ + if (IsValid()) + { + FBox2D Box = Widget->GetBounds(); + pRetVal->left = Box.Min.X; + pRetVal->top = Box.Min.Y; + pRetVal->width = Box.Max.X - Box.Min.X; + pRetVal->height = Box.Max.Y - Box.Min.Y; + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} +HRESULT STDCALL FWindowsUIAWidgetProvider::GetEmbeddedFragmentRoots(SAFEARRAY** pRetVal) +{ + // This would technically only be valid in our case for a window within a window + *pRetVal = nullptr; + return S_OK; +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::SetFocus() +{ + if (IsValid()) + { + if (Widget->SupportsFocus()) + { + Widget->SetFocus(); + return S_OK; + } + else + { + return UIA_E_NOTSUPPORTED; + } + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::get_FragmentRoot(IRawElementProviderFragmentRoot** pRetVal) +{ + if (IsValid()) + { + TSharedPtr Window = Widget->GetTopLevelWindow(); + if (Window.IsValid()) + { + *pRetVal = static_cast(&static_cast(UIAManager->GetWidgetProvider(Window.ToSharedRef()))); + return S_OK; + } + } + + return UIA_E_ELEMENTNOTAVAILABLE; +} + +// ~ + +// FWindowsUIAWindowProvider methods + +FWindowsUIAWindowProvider::FWindowsUIAWindowProvider(FWindowsUIAManager& InManager, TSharedRef InWidget) + : FWindowsUIAWidgetProvider(InManager, InWidget) +{ + ensure(InWidget->AsWindow() != nullptr); +} + +FWindowsUIAWindowProvider::~FWindowsUIAWindowProvider() +{ +} + +HRESULT STDCALL FWindowsUIAWindowProvider::QueryInterface(REFIID riid, void** ppInterface) +{ + if (riid == __uuidof(IRawElementProviderFragmentRoot)) + { + *ppInterface = static_cast(this); + AddRef(); + return S_OK; + } + else + { + return FWindowsUIAWidgetProvider::QueryInterface(riid, ppInterface); + } +} + +ULONG STDCALL FWindowsUIAWindowProvider::AddRef() +{ + return FWindowsUIABaseProvider::IncrementRef(); +} + +ULONG STDCALL FWindowsUIAWindowProvider::Release() +{ + return FWindowsUIABaseProvider::DecrementRef(); +} + +HRESULT STDCALL FWindowsUIAWindowProvider::get_HostRawElementProvider(IRawElementProviderSimple** pRetVal) +{ + if (Widget->IsValid()) + { + TSharedPtr NativeWindow = Widget->AsWindow()->GetNativeWindow(); + if (NativeWindow.IsValid()) + { + HWND Hwnd = static_cast(NativeWindow->GetOSWindowHandle()); + if (Hwnd != nullptr) + { + return UiaHostProviderFromHwnd(Hwnd, pRetVal); + } + } + return UIA_E_INVALIDOPERATION; + } + + return UIA_E_ELEMENTNOTAVAILABLE; +} + +HRESULT STDCALL FWindowsUIAWindowProvider::GetPatternProvider(PATTERNID patternId, IUnknown** pRetVal) +{ + if (IsValid()) + { + switch (patternId) + { + case UIA_WindowPatternId: + *pRetVal = static_cast(new FWindowsUIAControlProvider(*UIAManager, Widget)); + return S_OK; + default: + return FWindowsUIAWidgetProvider::GetPatternProvider(patternId, pRetVal); + } + } + + return UIA_E_ELEMENTNOTAVAILABLE; +} + +HRESULT STDCALL FWindowsUIAWindowProvider::ElementProviderFromPoint(double x, double y, IRawElementProviderFragment** pRetVal) +{ + if (IsValid()) + { + TSharedPtr Child = Widget->AsWindow()->GetChildAtPosition(x, y); + if (Child.IsValid()) + { + *pRetVal = static_cast(&UIAManager->GetWidgetProvider(Child.ToSharedRef())); + } + else + { + *pRetVal = nullptr; + } + return S_OK; + } + + return UIA_E_ELEMENTNOTAVAILABLE; +} + +HRESULT STDCALL FWindowsUIAWindowProvider::GetFocus(IRawElementProviderFragment** pRetVal) +{ + *pRetVal = nullptr; + if (IsValid()) + { + TSharedPtr Focus = Widget->AsWindow()->GetFocusedWidget(); + if (Focus.IsValid()) + { + *pRetVal = static_cast(&UIAManager->GetWidgetProvider(Focus.ToSharedRef())); + } + return S_OK; + } + + return UIA_E_ELEMENTNOTAVAILABLE; +} + +// ~ + +#undef LOCTEXT_NAMESPACE + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Windows/WindowsApplication.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Windows/WindowsApplication.cpp index 39ab24b56273..beccf9d7de09 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/Windows/WindowsApplication.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/Windows/WindowsApplication.cpp @@ -24,6 +24,12 @@ #include "Developer/SourceCodeAccess/Public/ISourceCodeAccessModule.h" #endif +#if WITH_ACCESSIBILITY +#include "Windows/Accessibility/WindowsUIAManager.h" +#include "Windows/Accessibility/WindowsUIAWidgetProvider.h" +#include +#endif + // Allow Windows Platform types in the entire file. #include "Windows/AllowWindowsPlatformTypes.h" THIRD_PARTY_INCLUDES_START @@ -92,6 +98,9 @@ FWindowsApplication::FWindowsApplication( const HINSTANCE HInstance, const HICON bAllowedToDeferMessageProcessing, TEXT( "Whether windows message processing is deferred until tick or if they are processed immediately" ) ) , bInModalSizeLoop( false ) +#if WITH_ACCESSIBILITY + , UIAManager(new FWindowsUIAManager(*this)) +#endif { FMemory::Memzero(ModifierKeyState, EModifierKey::Count); @@ -313,6 +322,14 @@ void FWindowsApplication::SetMessageHandler( const TSharedRef< FGenericApplicati } +#if WITH_ACCESSIBILITY +void FWindowsApplication::SetAccessibleMessageHandler(const TSharedRef& InAccessibleMessageHandler) +{ + GenericApplication::SetAccessibleMessageHandler(InAccessibleMessageHandler); + UIAManager->OnAccessibleMessageHandlerChanged(); +} +#endif + bool FWindowsApplication::IsGamepadAttached() const { if (bForceNoGamepads) @@ -1354,6 +1371,13 @@ int32 FWindowsApplication::ProcessMessage( HWND hwnd, uint32 msg, WPARAM wParam, case WM_DESTROY: { Windows.Remove( CurrentNativeEventWindow ); +#if WITH_ACCESSIBILITY + // Tell UIA that the window no longer exists so that it can release some resources + if (GetAccessibleMessageHandler()->ApplicationIsAccessible()) + { + UiaReturnRawElementProvider(hwnd, 0, 0, nullptr); + } +#endif return 0; } break; @@ -1517,6 +1541,19 @@ int32 FWindowsApplication::ProcessMessage( HWND hwnd, uint32 msg, WPARAM wParam, } break; +#if WITH_ACCESSIBILITY + case WM_GETOBJECT: + { + if (GetAccessibleMessageHandler()->ApplicationIsAccessible()) + { + FScopedWidgetProvider Provider(UIAManager->GetWindowProvider(CurrentNativeEventWindow)); + LRESULT Result = UiaReturnRawElementProvider(hwnd, wParam, lParam, &Provider.Provider); + return Result; + } + break; + } +#endif + default: if (bMessageExternallyHandled) { diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Windows/XInputInterface.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Windows/XInputInterface.cpp index dd585893cd8b..ace03336ccf8 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/Windows/XInputInterface.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/Windows/XInputInterface.cpp @@ -5,6 +5,7 @@ #include "Misc/CoreDelegates.h" #include "Windows/WindowsApplication.h" #include "GenericPlatform/GenericApplication.h" +#include "Misc/ConfigCacheIni.h" #pragma pack (push,8) #include "Windows/AllowWindowsPlatformTypes.h" @@ -35,6 +36,9 @@ XInputInterface::XInputInterface( const TSharedRef< FGenericApplicationMessageHa InitialButtonRepeatDelay = 0.2f; ButtonRepeatDelay = 0.1f; + GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("InitialButtonRepeatDelay"), InitialButtonRepeatDelay, GInputIni); + GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("ButtonRepeatDelay"), ButtonRepeatDelay, GInputIni); + // In the engine, all controllers map to xbox controllers for consistency X360ToXboxControllerMapping[0] = 0; // A X360ToXboxControllerMapping[1] = 1; // B diff --git a/Engine/Source/Runtime/ApplicationCore/Public/GenericPlatform/GenericAccessibleInterfaces.h b/Engine/Source/Runtime/ApplicationCore/Public/GenericPlatform/GenericAccessibleInterfaces.h new file mode 100644 index 000000000000..3adc4142dedc --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Public/GenericPlatform/GenericAccessibleInterfaces.h @@ -0,0 +1,535 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +/** Whether a widget should be included in accessibility, and if so, how its text should be retrieved. */ +enum class EAccessibleBehavior : uint8 +{ + /** Not accessible. */ + NotAccessible, + /** Accessible, for the implementing library to decide what it means. Given all data about a particular widget, it should try to choose the most-relevant text automatically. */ + Auto, + /** Accessible, and traverse all child widgets and concat their summary text together. */ + Summary, + /** Accessible, and retrieve manually-assigned text from a TAttribute. */ + Custom, + /** Accessible, and use the tooltip's accessible text. */ + ToolTip +}; + +DECLARE_LOG_CATEGORY_EXTERN(LogAccessibility, Log, All); + +#if WITH_ACCESSIBILITY + +#include "Misc/Variant.h" +#include "Stats/Stats.h" + +class FGenericWindow; +class IAccessibleWidget; + +DECLARE_STATS_GROUP(TEXT("Accessibility"), STATGROUP_Accessibility, STATCAT_Advanced); + +/** What kind of widget to tell the operating system this is. This may be translated to a different type depending on the platform. */ +enum class EAccessibleWidgetType : uint8 +{ + Unknown, + Button, + CheckBox, + ComboBox, + Hyperlink, + Image, + Layout, + ScrollBar, + Slider, + Text, + TextEdit, + Window +}; + +/** Events that can be raised from accessible widgets to report back to the platform */ +enum class EAccessibleEvent : uint8 +{ + /** A widget has become focused or unfocused. */ + FocusChange, + /** A widget has been clicked, checked, or otherwise activated */ + Activate, + /** Notify the user that something has happened. The user is not guaranteed to get this message. */ + Notification, + /** Right before a widget is removed from its parent widget. */ + BeforeRemoveFromParent, + /** Right after a widget is added to its parent widget. */ + AfterAddToParent, + /** The widget was removed from the UI tree or deleted. */ + WidgetRemoved +}; + +/** + * An accessible window corresponds to a native OS window. Fake windows that are embedded + * within other widgets that simply look and feel like windows are not IAccessibleWindows. + */ +class IAccessibleWindow +{ +public: + /** The different states a window can be in to describe its screen anchors */ + enum class EWindowDisplayState + { + Normal, + Minimize, + Maximize + }; + + /** + * Retrieve the native OS window backing this accessible window. This can be safely + * casted if you know what OS you're in (ie FWindowsWindows on Windows platform). + * + * @return The native window causing this accessible window to exist + */ + virtual TSharedPtr GetNativeWindow() const = 0; + + /** + * Finds the deepest accessible widget in the hierarchy at the specified coordinates. The window + * may return a pointer to itself in the case where there are no accessible children at the position. + * This could return nullptr in the case where the coordinates are outside the window bounds. + * + * @param X The X coordinate in absolute screen space + * @param Y The Y coordinate in absolute screen space + * @return The deepest widget in the UI heirarchy at X,Y + */ + virtual TSharedPtr GetChildAtPosition(int32 X, int32 Y) = 0; + /** + * Retrieves the currently-focused widget, if it is accessible. + * + * @return The widget that has focus, or nullptr if the focused widget is not accessible. + */ + virtual TSharedPtr GetFocusedWidget() const = 0; + /** + * Request that the window closes itself. This may not happen immediately. + */ + virtual void Close() = 0; + + /** + * Check if the window can be minimized or maximized. + * + * @param State Whether to check for minimize or maximize. + * @return True if the display state can be switched to, otherwise false. + */ + virtual bool SupportsDisplayState(EWindowDisplayState State) const = 0; + /** + * Gets the current state minimize/maximize state of the window. + * + * @return The display state corresponding to how the window is displayed. + */ + virtual EWindowDisplayState GetDisplayState() const = 0; + /** + * Sets a window to be minimized, maximized, or restored to normal. + * + * @param State What to change the window's display to. + */ + virtual void SetDisplayState(EWindowDisplayState State) = 0; + /** + * Whether or not the window is modal. + * + * @return true if the window is modal, otherwise false. + */ + virtual bool IsModal() const = 0; +}; + +/** + * A widget that can be triggered to fire an event, such as buttons or checkboxes. + */ +class IAccessibleActivatable +{ +public: + /** Trigger the widget */ + virtual void Activate() = 0; + /** + * Check whether this widget can be toggled between various check states + * + * @return true if this widget supports being in different states + */ + virtual bool IsCheckable() const { return false; } + /** + * If IsCheckable() is true, this gets the current state that the widget is in. + * //todo: return ECheckState + * + * @return true if the current state is considered "on" or "checked" + */ + virtual bool GetCheckedState() const { return false; } +}; + +/** + * An accessible widget that stores an arbitrary value of any type capable of being serialized into a string. + * Optional overrides add support for slider-like functionality. + */ +class IAccessibleProperty +{ +public: + /** + * Whether the widget is in read-only mode, which could be different than IsEnabled(). + * + * @return true if the widget is in read-only mode. + */ + virtual bool IsReadOnly() const { return true; } + /** + * Check if this text is storing password data, indicating that it may need special handling to presenting itself to the user. + * + * @return true if the text is storing a password or otherwise senstive data that should be hidden. + */ + virtual bool IsPassword() const { return false; } + /** + * How much the value should increase/decrease when the user attempts to modify the value using UI controls. + * Note: This should always return a positive value. The caller is responsible for negating it when attempting to decrease. + * + * @return A number suggesting how much to modify GetValue() by when the user wants to increase/decrease the value. + */ + virtual float GetStepSize() const { return 0.0f; } + /** + * The maximum allowed value for this property. This should only be used if GetStepSize is not 0. + * + * @return The maximum value that this property can be assigned when using step sizes. + */ + virtual float GetMaximum() const { return 0.0f; } + /** + * The minimum allowed value for this property. This should only be used if GetStepSize is not 0. + * + * @return The minimum value that this property can be assigned when using step sizes. + */ + virtual float GetMinimum() const { return 0.0f; } + /** + * The current value stored by the widget. Even if the underlying value is not a String, it should be serialized to one + * in order to match the return type. + * + * @return A string representing the serialized value stored in the widget. + */ + virtual FString GetValue() const = 0; + /* + * Set the value stored by the widget. While this function accepts a String, there is no way to know + * what the underlying data is stored as. The platform layer must retain some additional information + * about what kind of widget this is, and ensure it's being called with valid arguments. + * + * @param Value The new value to assign to the widget, which may need to be converted before assigning to a variable. + */ + virtual void SetValue(const FString& Value) {} +}; + +/** + * A widget that contains text, with the potential ability to select sections, read specific words/paragraphs, etc. + * Note: This class is currently incomplete. + */ +class IAccessibleText +{ +public: + /** + * Get the full text contained in this widget, even if some if it is clipped. + * + * @return All the text held by this widget. + */ + virtual const FString& GetText() const = 0; +}; + +typedef int32 AccessibleWidgetId; + +/** + * Provides the core set of accessible data that is necessary in order for widget traversal and TTS to be implemented. + * In order to support functionality beyond this, subclasses must implement the other accessible interfaces and + * then override the As*() functions. + */ +class IAccessibleWidget : public TSharedFromThis +{ +public: + IAccessibleWidget() {} + virtual ~IAccessibleWidget() {} + + static const AccessibleWidgetId InvalidAccessibleWidgetId = -1; + + /** + * Get an application-unique identifier for this widget. If the widget is destroyed, + * a different widget is allowed to re-use that ID. + * + * @return A unique ID that specifically refers to this widget. + */ + virtual AccessibleWidgetId GetId() const = 0; + /** + * Whether or not the underlying widget backing this interface still exists + * + * @return true if functions can be called on this interface and should return valid results + */ + virtual bool IsValid() const = 0; + + /** + * Returns the window at the top of this widget's hierarchy. This function may return itself for accessible windows, + * and could return nullptr in cases where the widget is not currently part of a hierarchy. + * + * @return The root window in this widget's widget tree, or nullptr if there is no window. + */ + virtual TSharedPtr GetTopLevelWindow() const = 0; + /** + * Retrieving the bounding rect in absolute coordinates for this widget. On some platforms this may be used for hit testing. + * + * @return The bounds of the widget. + */ + virtual FBox2D GetBounds() const = 0; + /** + * Get the accessible parent of this widget. This may be nullptr if this widget is a window, or if the widget is + * currently disconnected from the UI tree. + * + * @return The accessible parent widget of this widget. + */ + virtual TSharedPtr GetParent() = 0; + /** + * Retrieves the widget after this one in the parent's list of children. This should return nullptr for the last widget. + * + * @return The next widget on the same level of the UI hierarchy. + */ + virtual TSharedPtr GetNextSibling() = 0; + /** + * Retrieves the widget before this one in the parent's list of children. This should return nullptr for the first widget. + * + * @return The previous widget on the same level of the UI hierarchy. + */ + virtual TSharedPtr GetPreviousSibling() = 0; + /** + * Retrieves the accessible child widget at a certain index. This should return nullptr if Index < 0 or Index >= GetNumberOfChildren(). + * + * @param The index of the child widget to get + * @return The accessible child widget at the specified index. + */ + virtual TSharedPtr GetChildAt(int32 Index) = 0; + /** + * How many accessible children this widget has. + * + * @return The number of accessible children that exist for this widget. + */ + virtual int32 GetNumberOfChildren() = 0; + /** + * What type of accessible widget the underlying widget should be treated as. A widget may be capable of presenting itself + * as multiple different types of widgets, but only one can be reported back to the platform. + * + * @return Which type of widget the platform layer should treat this as. + */ + virtual EAccessibleWidgetType GetWidgetType() const = 0; + + /** + * The name of the underlying class that this accessible widget represents. + * + * @return The class name of the underlying widget. + */ + virtual FString GetClassName() const = 0; + /** + * The name of the widget to report to the platform layer. For screen readers, this is often the text that will be spoken. + * + * @return Ideally, a human-readable name that represents what the widget does. + */ + virtual FString GetWidgetName() const = 0; + /** + * Additional information a user may need in order to effectively interact or use the widget, such as a tooltip. + * + * @return A more-detailed description of what the widget is or how its used. + */ + virtual FString GetHelpText() const = 0; + + /** + * Whether the widget is enabled and can be interacted with. + * + * @return true if the widget is enabled. + */ + virtual bool IsEnabled() const = 0; + /** + * Whether the widget is being rendered on screen or not. + * + * @return true if the widget is hidden off screen, collapsed, or something similar. + */ + virtual bool IsHidden() const = 0; + /** + * Whether the widget supports keyboard focus or not. + * + * @return true if the widget can receive keyboard focus. + */ + virtual bool SupportsFocus() const = 0; + /** + * Whether the widget has keyboard focus or not. + * + * @return true if the widget currently has keyboard focus. + */ + virtual bool HasFocus() const = 0; + /** Assign keyboard focus to this widget, if it supports it. If not, focus should not be affected. */ + virtual void SetFocus() = 0; + + /** + * Attempt to cast this to an IAccessibleWindow + * + * @return 'this' as an IAccessibleWindow if possible, otherwise nullptr + */ + virtual IAccessibleWindow* AsWindow() { return nullptr; } + /** + * Attempt to cast this to an IAccessibleActivatable + * + * @return 'this' as an IAccessibleActivatable if possible, otherwise nullptr + */ + virtual IAccessibleActivatable* AsActivatable() { return nullptr; } + /** + * Attempt to cast this to an IAccessibleProperty + * + * @return 'this' as an IAccessibleProperty if possible, otherwise nullptr + */ + virtual IAccessibleProperty* AsProperty() { return nullptr; } + /** + * Attempt to cast this to an IAccessibleText + * + * @return 'this' as an IAccessibleText if possible, otherwise nullptr + */ + virtual IAccessibleText* AsText() { return nullptr; } +}; + +/** + * Platform and application-agnostic messaging system for accessible events. The message handler + * lives in GenericApplication and any subclass that wishes to support accessibility should subclass + * this and use GenericAppliation::SetAccessibleMessageHandler to enable functionality. + * + * GetAccessibleWindow() is tne entry point to all accessible widgets. Once the window is retrieved, it + * can be queried for children in various ways. RaiseEvent() allows messages to bubble back up to the + * native OS through anything bound to the AccessibleEventDelegate. + * + * Callers can use ApplicationIsAccessible() to see if accessibility is supported or not. Alternatively, + * calling GetAccessibleWindow and seeing if the result is valid should provide the same information. + */ +class FGenericAccessibleMessageHandler +{ +public: + /** A widget raised an event to pass to the native OS implementation. */ + DECLARE_DELEGATE_FourParams( + FAccessibleEvent, + /** The accessible widget that generated the event */ + TSharedRef, + /** The type of event generated */ + EAccessibleEvent, + /** If this was a property changed event, the 'before' value */ + FVariant, + /** If this was a property changed event, the 'after' value. This may also be set for other events such as Notification. */ + FVariant); + + FGenericAccessibleMessageHandler() : bIsActive(false) {} + + virtual ~FGenericAccessibleMessageHandler() + { + if (AccessibleEventDelegate.IsBound()) + { + AccessibleEventDelegate.Unbind(); + } + } + + /** + * Subclasses should return true to indicate that they support accessibility. + * + * @return true if the application intends to return valid accessible widgets when queried. + */ + virtual bool ApplicationIsAccessible() const { return false; } + + /** + * Checks if accessibility is enabled in the application. Usually this happens when screen-reading software is turned on. + * Note: On some platforms, there is no way to deactivate this after enabling it. + * + * @return The last value SetActive() was called with. + */ + bool IsActive() const { return bIsActive; } + + /** + * Notify the application to start or stop processing accessible messages from the platform layer. + * + * @param bActive Whether to enable to disable the message handler. + */ + void SetActive(bool bActive) + { + if (bActive != bIsActive) + { + bIsActive = bActive; + + if (bIsActive) + { + UE_LOG(LogAccessibility, Log, TEXT("Enabling Accessibility")); + OnActivate(); + } + else + { + OnDeactivate(); + UE_LOG(LogAccessibility, Log, TEXT("Accessibility Disabled")); + } + } + } + + /** + * Creates or retrieves an accessible object for a native OS window. + * todo: Behavior for non-native windows (virtual or others) is currently undefined. + * + * @param InWindow The native window to find the accessible window for + * @return The accessible object corresponding to the supplied native window + */ + virtual TSharedPtr GetAccessibleWindow(const TSharedRef& InWindow) const { return nullptr; } + + /** + * Creates or retrieves the identifier for an accessible object for a native OS window. + * todo: Behavior for non-native windows (virtual or others) is currently undefined. + * + * @param InWindow The native window to find the accessible window for + * @return The identifier for the accessible window created + */ + virtual AccessibleWidgetId GetAccessibleWindowId(const TSharedRef& InWindow) const { return IAccessibleWidget::InvalidAccessibleWidgetId; } + + /** + * Retrieves an accessible widget that matches the given identifier. + * + * @param Id The identifier for the widget to get. + * @return The widget that matches this identifier, or nullptr if the widget does not exist. + */ + virtual TSharedPtr GetAccessibleWidgetFromId(AccessibleWidgetId Id) const { return nullptr; } + + /** + * Push an event from an accessible widget back to the platform layer. + * + * @param Widget The widget raising the event + * @param Event The type of event being raised + * @param OldValue If this is a property changed event, the old value of the property + * @param NewValue If this is a property changed event, the current value of the property. This may also be used by non-property events that require data. + */ + void RaiseEvent(TSharedRef Widget, EAccessibleEvent Event, FVariant OldValue, FVariant NewValue) + { + AccessibleEventDelegate.ExecuteIfBound(Widget, Event, OldValue, NewValue); + } + /** + * Push an event from an accessible widget back to the platform layer. + * This is a convenience function if OldValue and NewValue are irrelevant for this event type. + * + * @param Widget The widget raising the event + * @param Event The type of event being raised + */ + void RaiseEvent(TSharedRef Widget, EAccessibleEvent Event) + { + RaiseEvent(Widget, Event, FVariant(), FVariant()); + } + + /** + * Assign a function to be called whenever an accessible event is raised. + * + * @param Delegate The delegate to execute when an event is raised. + */ + void SetAccessibleEventDelegate(const FAccessibleEvent& Delegate) + { + AccessibleEventDelegate = Delegate; + } + +protected: + /** Triggered when bIsActive changes from false to true. */ + virtual void OnActivate() {} + /** Triggered when bIsActive changes from true to false. */ + virtual void OnDeactivate() {} + +private: + /** Whether or not accessibility is currently enabled in the application */ + bool bIsActive; + /** Delegate for the platform layer to listen to widget events */ + FAccessibleEvent AccessibleEventDelegate; +}; + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Public/GenericPlatform/GenericApplication.h b/Engine/Source/Runtime/ApplicationCore/Public/GenericPlatform/GenericApplication.h index 783ed3c7e915..0f9de9bb8244 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/GenericPlatform/GenericApplication.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/GenericPlatform/GenericApplication.h @@ -9,6 +9,9 @@ #include "Math/Vector4.h" #include "Templates/SharedPointer.h" #include "Delegates/Delegate.h" +#if WITH_ACCESSIBILITY +#include "GenericPlatform/GenericAccessibleInterfaces.h" +#endif #include "GenericPlatform/GenericApplicationMessageHandler.h" #include "GenericPlatform/GenericWindowDefinition.h" #include "GenericPlatform/GenericWindow.h" @@ -428,6 +431,9 @@ public: GenericApplication( const TSharedPtr< ICursor >& InCursor ) : Cursor( InCursor ) , MessageHandler( MakeShareable( new FGenericApplicationMessageHandler() ) ) +#if WITH_ACCESSIBILITY + , AccessibleMessageHandler(MakeShareable(new FGenericAccessibleMessageHandler())) +#endif { } @@ -438,6 +444,11 @@ public: TSharedRef< FGenericApplicationMessageHandler > GetMessageHandler() { return MessageHandler; } +#if WITH_ACCESSIBILITY + virtual void SetAccessibleMessageHandler(const TSharedRef& InAccessibleMessageHandler) { AccessibleMessageHandler = InAccessibleMessageHandler; } + TSharedRef GetAccessibleMessageHandler() const { return AccessibleMessageHandler; } +#endif + virtual void PollGameDeviceState( const float TimeDelta ) { } virtual void PumpMessages( const float TimeDelta ) { } @@ -543,6 +554,10 @@ public: protected: TSharedRef< class FGenericApplicationMessageHandler > MessageHandler; + +#if WITH_ACCESSIBILITY + TSharedRef AccessibleMessageHandler; +#endif /** Trigger the OnDisplayMetricsChanged event with the argument 'InMetrics' */ void BroadcastDisplayMetricsChanged( const FDisplayMetrics& InMetrics ){ OnDisplayMetricsChangedEvent.Broadcast( InMetrics ); } diff --git a/Engine/Source/Runtime/ApplicationCore/Public/IOS/Accessibility/IOSAccessibilityCache.h b/Engine/Source/Runtime/ApplicationCore/Public/IOS/Accessibility/IOSAccessibilityCache.h new file mode 100644 index 000000000000..9b0a95369526 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Public/IOS/Accessibility/IOSAccessibilityCache.h @@ -0,0 +1,44 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "GenericPlatform/GenericAccessibleInterfaces.h" + +@class FIOSAccessibilityContainer; + +/** + * This class is a singleton and should be accessed through [FIOSAccessibilityCache AccessibilityElementCache]. + * Stores a list of accessible containers that map to AccessibleWidgetIds for lookup. + * The cache is also responsible for polling attributes from the underlying + * IAccessibleWidgets that are too expensive to be done when requested by IOS due to + * needing to be accessed from a different thread. + * + * Leaf elements can be accessed by getting their container from the cache and calling + * [container GetLeaf] on it. + */ +@interface FIOSAccessibilityCache : NSObject +{ +@private + /** AccessibleWidgetId(String)->FIOSAccessibilityContainer map for all created containers. */ + NSMutableDictionary* Cache; +} + +/** Retrieve a cached container, or create one if it doesn't exist yet. */ +-(FIOSAccessibilityContainer*)GetAccessibilityElement:(AccessibleWidgetId)Id; +/** Returns true if the Cache contains the Id. Does not create one if it doesn't exist. */ +-(bool)AccessibilityElementExists:(AccessibleWidgetId)Id; +/** Removes an entry from the Cache. */ +-(void)RemoveAccessibilityElement:(AccessibleWidgetId)Id; +/** Completely empties the cache. */ +-(void)Clear; +/** Loop over all cached elements and update any properties necessary on the Game thread. */ +-(void)UpdateAllCachedProperties; + +/** Singleton accessor */ ++(id)AccessibilityElementCache; + +@end + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Public/IOS/Accessibility/IOSAccessibilityElement.h b/Engine/Source/Runtime/ApplicationCore/Public/IOS/Accessibility/IOSAccessibilityElement.h new file mode 100644 index 000000000000..622fce33deff --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Public/IOS/Accessibility/IOSAccessibilityElement.h @@ -0,0 +1,78 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "CoreMinimal.h" +#include "GenericPlatform/GenericAccessibleInterfaces.h" +#import + +@class FIOSAccessibilityLeaf; + +/** + * UIAccessibilityElements cannot be both accessible and have children. While + * the same class can be used in both cases, the value they return for + * isAccessibilityElement determines what type of widget they are. If false, + * the widget is a container and only functions regarding children will be called. + * If true, only functions regarding the value of the wigdet will be called. + * + * Because of this, all IAccessibleWidgets have both a corresponding container + * and leaf. The leaf is always reported as the last child of the container. This + * is our workaround for a widget being accessible and having children at the same time. + */ +@interface FIOSAccessibilityContainer : UIAccessibilityElement +{ +@private + /** A matching Leaf element that shares the same AccessibleWidgetId at this container. */ + FIOSAccessibilityLeaf* Leaf; +} + +/** This must be used instead of initWithAccessibilityContainer in order to work properly. */ +-(id)initWithId:(AccessibleWidgetId)InId AndParentId:(AccessibleWidgetId)InParentId; +/** Updates the accessibilityContainer property from UIAccessibilityElement. */ +-(void)SetParent:(AccessibleWidgetId)InParentId; +/** Get the accessible version of this widget. */ +-(FIOSAccessibilityLeaf*)GetLeaf; + +/** The identifier used to access this widget through the accessible API. */ +@property (nonatomic) AccessibleWidgetId Id; +/** A list of identifiers for all current children of this container. */ +@property (nonatomic) TArray ChildIds; +/** The bounding rect of the container. */ +@property (nonatomic) FBox2D Bounds; +/** Whether or not the widget is currently visible. */ +@property (nonatomic) bool bIsVisible; + +@end + +/** + * The accessible version of a widget for a given AccessibleWidgetId. A Leaf is + * guaranteed to have an FIOSAccessibilityContainer as its container, and can be + * accessed with [self accessibilityContainer] (in order to get things like bounds). + */ +@interface FIOSAccessibilityLeaf : UIAccessibilityElement +{ +} + +/** This must be used instead of initWithAccessibilityContainer in order to work properly. */ +-(id)initWithParent:(FIOSAccessibilityContainer*)Parent; +/** Check if LastCachedStringTime was updated recently. */ +-(bool)ShouldCacheStrings; +/** Toggle an individual trait on or off */ +-(void)SetAccessibilityTrait:(UIAccessibilityTraits)Trait Set:(bool)IsEnabled; + +/** A cached version of the name of the widget. */ +@property (nonatomic) FString Label; +/** A cached version of the help text of the widget. */ +@property (nonatomic) FString Hint; +/** A cached version of the value of property widgets. */ +@property (nonatomic) FString Value; +/** Bitflag of traits that describe the widget. Most are set once on initialization. */ +@property (nonatomic) UIAccessibilityTraits Traits; +/** Timestamp for when Label, Hint, and Value were last cached. */ +@property (nonatomic) double LastCachedStringTime; + +@end + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSAppDelegate.h b/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSAppDelegate.h index c00b9fb1e04b..259e1ad772b8 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSAppDelegate.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSAppDelegate.h @@ -133,6 +133,13 @@ APPLICATIONCORE_API /** The time delay (in seconds) between idle timer enable requests and actually enabling the idle timer */ @property (readonly) float IdleTimerEnablePeriod; +#if WITH_ACCESSIBILITY +/** Timer used for updating cached data from game thread for all accessible widgets. */ +@property (nonatomic, retain) NSTimer* AccessibilityCacheTimer; +/** Callback for IOS notification of VoiceOver being enabled or disabled. */ +-(void)OnVoiceOverStatusChanged; +#endif + // parameters passed from openURL @property (nonatomic, retain) NSMutableArray* savedOpenUrlParameters; @@ -170,6 +177,8 @@ APPLICATIONCORE_API -(bool)IsIdleTimerEnabled; -(void)EnableIdleTimer:(bool)bEnable; -(void)StartGameThread; +/** Uses the TaskGraph to execute a function on the game thread, and then blocks until the function is executed. */ ++(bool)WaitAndRunOnGameThread:(TUniqueFunction)Function; -(void)NoUrlCommandLine; -(int)GetAudioVolume; diff --git a/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSApplication.h b/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSApplication.h index f9c34289b862..cc5d2a71f226 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSApplication.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSApplication.h @@ -18,6 +18,9 @@ public: virtual ~FIOSApplication() {} void SetMessageHandler( const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler ); +#if WITH_ACCESSIBILITY + virtual void SetAccessibleMessageHandler(const TSharedRef& InAccessibleMessageHandler) override; +#endif virtual void PollGameDeviceState( const float TimeDelta ) override; @@ -35,13 +38,17 @@ public: virtual bool IsGamepadAttached() const override; + TSharedRef FindWindowByAppDelegateView(); + protected: virtual void InitializeWindow( const TSharedRef< FGenericWindow >& Window, const TSharedRef< FGenericWindowDefinition >& InDefinition, const TSharedPtr< FGenericWindow >& InParent, const bool bShowImmediately ) override; private: FIOSApplication(); - +#if WITH_ACCESSIBILITY + void OnAccessibleEventRaised(TSharedRef Widget, EAccessibleEvent Event, FVariant OldValue, FVariant NewValue); +#endif private: diff --git a/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSView.h b/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSView.h index 3e37ea1c4fde..396f640d67f0 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSView.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSView.h @@ -14,6 +14,10 @@ #import #endif +#if WITH_ACCESSIBILITY +#include "GenericPlatform/GenericAccessibleInterfaces.h" +#endif + struct FKeyboardConfig { UIKeyboardType KeyboardType; @@ -82,9 +86,17 @@ APPLICATIONCORE_API volatile int32 KeyboardShowCount; - +#if WITH_ACCESSIBILITY +@private + // Single-element array used for the accessibilityElements property. It holds the IAccessibleWindow for the app. + NSMutableArray* _accessibilityElements; +#endif } +#if WITH_ACCESSIBILITY +/** Repopulate _accessibilityElements when the accessible window's ID has changed. */ +-(void)SetAccessibilityWindow:(AccessibleWidgetId)WindowId; +#endif //// SHARED FUNCTIONALITY @property (nonatomic) GLuint SwapCount; diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Linux/LinuxApplication.h b/Engine/Source/Runtime/ApplicationCore/Public/Linux/LinuxApplication.h index 02e57d974fe8..90457232aa4b 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/Linux/LinuxApplication.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/Linux/LinuxApplication.h @@ -90,6 +90,8 @@ public: void RemoveNotificationWindow(SDL_HWindow HWnd); + void CheckIfApplicatioNeedsDeactivation(); + EWindowZone::Type WindowHitTest( const TSharedPtr< FLinuxWindow > &window, int x, int y ); TSharedPtr< FLinuxWindow > FindWindowBySDLWindow( SDL_Window *win ); diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Mac/CocoaMenu.h b/Engine/Source/Runtime/ApplicationCore/Public/Mac/CocoaMenu.h index 3c911b146ef4..21c73dd6570f 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/Mac/CocoaMenu.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/Mac/CocoaMenu.h @@ -3,7 +3,7 @@ #include "CoreMinimal.h" -@interface FCocoaMenu : NSMenu +OBJC_EXPORT @interface FCocoaMenu : NSMenu { @private bool bHighlightingKeyEquivalent; diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Mac/CocoaTextView.h b/Engine/Source/Runtime/ApplicationCore/Public/Mac/CocoaTextView.h index ceaa76d390cc..dc02508b9d84 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/Mac/CocoaTextView.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/Mac/CocoaTextView.h @@ -6,7 +6,7 @@ #ifdef __OBJC__ -@interface FCocoaTextView : NSView +OBJC_EXPORT @interface FCocoaTextView : NSView { TSharedPtr IMMContext; NSRange markedRange; diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacApplication.h b/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacApplication.h index 7473e8be522d..f95332316d13 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacApplication.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacApplication.h @@ -395,4 +395,4 @@ private: friend class FMacWindow; }; -extern FMacApplication* MacApplication; +APPLICATIONCORE_API extern FMacApplication* MacApplication; diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacPlatformFramePacer.h b/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacPlatformFramePacer.h index 48dc1c9e0a0b..58594b00da2e 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacPlatformFramePacer.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacPlatformFramePacer.h @@ -16,7 +16,7 @@ typedef void (^FMacFramePacerHandler)(uint32 CGDirectDisplayID, double OutputSec /** * Mac implementation of FGenericPlatformRHIFramePacer **/ -struct FMacPlatformRHIFramePacer : public FGenericPlatformRHIFramePacer +struct APPLICATIONCORE_API FMacPlatformRHIFramePacer : public FGenericPlatformRHIFramePacer { // FGenericPlatformRHIFramePacer interface static bool IsEnabled(); diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacPlatformSurvey.h b/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacPlatformSurvey.h index 03c7292a1c95..ba389048d508 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacPlatformSurvey.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacPlatformSurvey.h @@ -16,7 +16,7 @@ class FString; struct FMacPlatformSurvey : public FGenericPlatformSurvey { /** Start, or check on, the hardware survey */ - static bool GetSurveyResults( FHardwareSurveyResults& OutResults, bool bWait = false ); + APPLICATIONCORE_API static bool GetSurveyResults( FHardwareSurveyResults& OutResults, bool bWait = false ); private: /** diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Unix/UnixPlatformSurvey.h b/Engine/Source/Runtime/ApplicationCore/Public/Unix/UnixPlatformSurvey.h index 3d710eb9335a..cfd9859d54c9 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/Unix/UnixPlatformSurvey.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/Unix/UnixPlatformSurvey.h @@ -10,12 +10,12 @@ #include "GenericPlatform/GenericPlatformSurvey.h" /** -* Android implementation of FGenericPlatformSurvey +* Unix implementation of FGenericPlatformSurvey **/ struct FUnixPlatformSurvey : public FGenericPlatformSurvey { /** Start, or check on, the hardware survey */ - static bool GetSurveyResults(FHardwareSurveyResults& OutResults, bool bWait = false); + APPLICATIONCORE_API static bool GetSurveyResults(FHardwareSurveyResults& OutResults, bool bWait = false); private: diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIABaseProvider.h b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIABaseProvider.h new file mode 100644 index 000000000000..2cd136d83edd --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIABaseProvider.h @@ -0,0 +1,55 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "Windows/AllowWindowsPlatformTypes.h" +#include +#include "Windows/HideWindowsPlatformTypes.h" +#include + +#include "Templates/SharedPointer.h" + +class FWindowsUIAManager; +class IAccessibleWidget; + +/** + * Base class for all Windows UIA Providers to inherit from. Provides implementation of IUnknown's + * reference counting functions as well as integration with the UIA Manager class. + */ +class FWindowsUIABaseProvider +{ +public: + /** Notify the Provider that the backing application no longer exists. */ + void OnUIAManagerDestroyed(); + +protected: + FWindowsUIABaseProvider(FWindowsUIAManager& InManager, TSharedRef InWidget); + virtual ~FWindowsUIABaseProvider(); + + /** + * Check whether this Provider can have operations called on it. This might return false if the + * application is being destroyed, or if the underlying accessible widget is no longer valid. + */ + bool IsValid() const; + + /** Add one to RefCount, and return the new RefCount */ + uint32 IncrementRef(); + /** Subtract one from RefCount, and return the new RefCount. If RefCount is 0, deletes the Provider and returns 0. */ + uint32 DecrementRef(); + + /** A pointer to the UIA Manager which is guaranteed to be valid so long as the application is still running. */ + FWindowsUIAManager* UIAManager; + /** + * All Providers must have a valid accessible widget, even if the accessible widget itself does not + * have any valid data backing it. In this case, the accessible widget will return some default values. + */ + TSharedRef Widget; + +private: + /** Counter for the number of strong references to this object. Many of these will be from external applications. */ + uint32 RefCount; +}; + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAControlProvider.h b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAControlProvider.h new file mode 100644 index 000000000000..c559573cd4e5 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAControlProvider.h @@ -0,0 +1,152 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "Windows/Accessibility/WindowsUIABaseProvider.h" +#include "Templates/SharedPointer.h" + +class FWindowsUIAControlProvider; +class IAccessibleWidget; + +/** + * A TextRange Provider represents a start and end position within an ITextProvider. This can reference + * a word, a paragraph, a selection, etc. Multiple text ranges can exist for a single piece of text. + */ +class FWindowsUIATextRangeProvider + : public FWindowsUIABaseProvider + , public ITextRangeProvider +{ +public: + FWindowsUIATextRangeProvider(FWindowsUIAManager& InManager, TSharedRef InWidget, FTextRange InRange); + + // IUnknown + HRESULT STDCALL QueryInterface(REFIID riid, void** ppInterface) override; + ULONG STDCALL AddRef() override; + ULONG STDCALL Release() override; + // ~ + + // ITextRangeProvider + virtual HRESULT STDCALL Clone(ITextRangeProvider** pRetVal) override; + virtual HRESULT STDCALL Compare(ITextRangeProvider* range, BOOL* pRetVal) override; + virtual HRESULT STDCALL CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider* targetRange, TextPatternRangeEndpoint targetEndpoint, int* pRetVal) override; + virtual HRESULT STDCALL ExpandToEnclosingUnit(TextUnit unit) override; + virtual HRESULT STDCALL FindAttribute(TEXTATTRIBUTEID attributeId, VARIANT val, BOOL backward, ITextRangeProvider** pRetVal) override; + virtual HRESULT STDCALL FindText(BSTR text, BOOL backward, BOOL ignoreCase, ITextRangeProvider** pRetVal) override; + virtual HRESULT STDCALL GetAttributeValue(TEXTATTRIBUTEID attributeId, VARIANT* pRetVal) override; + virtual HRESULT STDCALL GetBoundingRectangles(SAFEARRAY** pRetVal) override; + virtual HRESULT STDCALL GetEnclosingElement(IRawElementProviderSimple** pRetVal) override; + virtual HRESULT STDCALL GetText(int maxLength, BSTR* pRetVal) override; + virtual HRESULT STDCALL Move(TextUnit unit, int count, int* pRetVal) override; + virtual HRESULT STDCALL MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count, int* pRetVal) override; + virtual HRESULT STDCALL MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider* targetRange, TextPatternRangeEndpoint targetEndpoint) override; + virtual HRESULT STDCALL Select() override; + virtual HRESULT STDCALL AddToSelection() override; + virtual HRESULT STDCALL RemoveFromSelection() override; + virtual HRESULT STDCALL ScrollIntoView(BOOL alignToTop) override; + virtual HRESULT STDCALL GetChildren(SAFEARRAY** pRetVal) override; + // ~ + +protected: + /** + * Gets the substring that this text range Provider represents. + * + * @return A substring of the underlying widget's text from TextRange's begin/end indices. + */ + FString TextFromTextRange(); + static FString TextFromTextRange(const FString& InString, const FTextRange& InRange); + + /** The range that this Provider represents in the underlying text. */ + FTextRange TextRange; + +private: + virtual ~FWindowsUIATextRangeProvider(); +}; + +/** + * The control Provider handles all control pattern-related functionality for the widget Provider. Control Providers should only + * be generated through widget Providers in their GetPatternProvider() function. In doing this, a control Pattern is only guaranteed + * to support functions that match the pattern Provider that it was generated for. This means that if GetPatternProvider() is called + * for UIA_ValuePatternId, the control Provider is only guaranteed to work with functions that implement IValueProvider. It is up + * to the caller to ensure that they are making valid function calls. + */ +class FWindowsUIAControlProvider + : public FWindowsUIABaseProvider + , public IInvokeProvider + , public IRangeValueProvider + , public ITextProvider + , public IToggleProvider + , public ITransformProvider + , public IValueProvider + , public IWindowProvider +{ +public: + FWindowsUIAControlProvider(FWindowsUIAManager& Manager, TSharedRef InWidget); + + // IUnknown + HRESULT STDCALL QueryInterface(REFIID riid, void** ppInterface) override; + ULONG STDCALL AddRef() override; + ULONG STDCALL Release() override; + // ~ + + // IInvokeProvider + virtual HRESULT STDCALL Invoke() override; + // ~ + + // IRangeValueProvider + virtual HRESULT STDCALL SetValue(double val) override; + virtual HRESULT STDCALL get_Value(double* pRetVal) override; + virtual HRESULT STDCALL get_IsReadOnly(BOOL* pRetVal) override; + virtual HRESULT STDCALL get_Maximum(double* pRetVal) override; + virtual HRESULT STDCALL get_Minimum(double* pRetVal) override; + virtual HRESULT STDCALL get_LargeChange(double* pRetVal) override; + virtual HRESULT STDCALL get_SmallChange(double* pRetVal) override; + // ~ + + // ITextProvider + virtual HRESULT STDCALL get_DocumentRange(ITextRangeProvider** pRetVal) override; + virtual HRESULT STDCALL get_SupportedTextSelection(SupportedTextSelection* pRetVal) override; + virtual HRESULT STDCALL GetSelection(SAFEARRAY** pRetVal) override; + virtual HRESULT STDCALL GetVisibleRanges(SAFEARRAY** pRetVal) override; + virtual HRESULT STDCALL RangeFromChild(IRawElementProviderSimple* childElement, ITextRangeProvider** pRetVal) override; + virtual HRESULT STDCALL RangeFromPoint(UiaPoint point, ITextRangeProvider** pRetVal) override; + // ~ + + // IToggleState + virtual HRESULT STDCALL get_ToggleState(ToggleState* pRetVal) override; + virtual HRESULT STDCALL Toggle() override; + // ~ + + // ITransformProvider + virtual HRESULT STDCALL get_CanMove(BOOL *pRetVal) override; + virtual HRESULT STDCALL get_CanResize(BOOL *pRetVal) override; + virtual HRESULT STDCALL get_CanRotate(BOOL *pRetVal) override; + virtual HRESULT STDCALL Move(double x, double y) override; + virtual HRESULT STDCALL Resize(double width, double height) override; + virtual HRESULT STDCALL Rotate(double degrees) override; + // ~ + + // IValueProvider + virtual HRESULT STDCALL SetValue(LPCWSTR val) override; + virtual HRESULT STDCALL get_Value(BSTR* pRetVal) override; + //virtual HRESULT STDCALL get_IsReadOnly(BOOL* pRetVal) override; // Duplicate of IRangeValueProvider + // ~ + + // IWindowProvider + virtual HRESULT STDCALL Close() override; + virtual HRESULT STDCALL get_CanMaximize(BOOL* pRetVal) override; + virtual HRESULT STDCALL get_CanMinimize(BOOL* pRetVal) override; + virtual HRESULT STDCALL get_IsModal(BOOL* pRetVal) override; + virtual HRESULT STDCALL get_IsTopmost(BOOL* pRetVal) override; + virtual HRESULT STDCALL get_WindowInteractionState(WindowInteractionState* pRetVal) override; + virtual HRESULT STDCALL get_WindowVisualState(WindowVisualState* pRetVal) override; + virtual HRESULT STDCALL SetVisualState(WindowVisualState state) override; + virtual HRESULT STDCALL WaitForInputIdle(int milliseconds, BOOL* pRetVal) override; + // ~ + +private: + virtual ~FWindowsUIAControlProvider(); +}; + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAManager.h b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAManager.h new file mode 100644 index 000000000000..c05aebb63c00 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAManager.h @@ -0,0 +1,89 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "Templates/SharedPointer.h" +#include "GenericPlatform/GenericAccessibleInterfaces.h" + +class FWindowsApplication; +class FWindowsWindow; +class FWindowsUIAControlProvider; +class FWindowsUIAWidgetProvider; +class FWindowsUIAWindowProvider; +class IAccessibleWidget; +class FVariant; + +/** + * Manager for Windows implementation of UE4's accessibility API, utilizing Microsoft's UI Automation API. It provides a + * central location for message passing to/from the WindowsApplication, AccessibleMessageHandler, and Windows UIA Providers. + * + * This class only accepts and returns references, so callers should ensure their objects are valid before using it. + */ +class FWindowsUIAManager +{ + /** Allow FWindowsUIABaseProvider to register and unregister itself from ProviderList automatically */ + friend class FWindowsUIABaseProvider; +public: + FWindowsUIAManager(const FWindowsApplication& InApplication); + ~FWindowsUIAManager(); + + /** + * Create a Windows UIA IRawElementProviderSimple from a given accessible widget. Providers are stored in a local cache, + * and this function will allocate a new Provider if one does not already exist for the given widget. Using this function + * will always increase the RefCount of the Provider by one. + * + * If InWidget->IsWindow() returns true, a FWindowsUIAWindowProvider will be created instead of a FWindowsUIAWidgetProvider. + * + * @param InWidget A non-null reference to an accessible widget + * @return A reference to the cached Provider + */ + FWindowsUIAWidgetProvider& GetWidgetProvider(TSharedRef InWidget); + + /** + * Create a Windows UIA IRawElementProviderSimple from a given native window handle. Providers are stored in a local cache, + * and this function will allocate a new Provider if one does not already exist for the given widget. Using this function + * will always increase the RefCount of the Provider by one. + * + * If the Provider cache is empty upon calling this function, it will activate accessibility in the entire application. + * + * @param InWindow A non-null reference to a native window + * @return A reference to the cached Provider + */ + FWindowsUIAWindowProvider& GetWindowProvider(TSharedRef InWindow); + + /** + * Notify the Manager that the RefCount for a Provider has reached 0, which will cause it to be removed from the cache. + * + * If the Provider cache is empty after removing the Provider, accessibility will be disabled through the entire application. + * + * @param InWidget The accessible widget backing the Provider that was removed + */ + void OnWidgetProviderRemoved(TSharedRef InWidget); + + /** + * Notify the Manager that the accessible message handler for the application has changed, in order to relink to the accessible event delegate. + */ + void OnAccessibleMessageHandlerChanged(); + + + static TMap WidgetTypeToWindowsTypeMap; + static TMap WidgetTypeToTextMap; + +private: + /** Callback function for processing events raised from the AccessibleMessageHandler */ + void OnEventRaised(TSharedRef Widget, EAccessibleEvent Event, FVariant OldValue, FVariant NewValue); + + /** Cache of all Providers with a RefCount of at least 1 that map to an accessible widget. */ + TMap, FWindowsUIAWidgetProvider*> CachedWidgetProviders; + /** + * A set of all Providers with a RefCount of at least 1. This also includes non-widget Providers such as text ranges. + * When the application is closed, this is used to notify Providers held by external applications that they are invalid. + */ + TSet ProviderList; + /** A reference back to the owning application that can be used to access the AccessibleMessageHandler */ + const FWindowsApplication& WindowsApplication; +}; + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAPropertyGetters.h b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAPropertyGetters.h new file mode 100644 index 000000000000..f26a33e3f545 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAPropertyGetters.h @@ -0,0 +1,29 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "Windows/AllowWindowsPlatformTypes.h" +#include +#include "Windows/HideWindowsPlatformTypes.h" +#include + +#include "Templates/SharedPointer.h" + +class FVariant; +class IAccessibleWidget; + +namespace WindowsUIAPropertyGetters +{ + VARIANT GetPropertyValueWindows(TSharedRef AccessibleWidget, PROPERTYID WindowsPropertyId); + FVariant GetPropertyValue(TSharedRef AccessibleWidget, PROPERTYID WindowsPropertyId); + + /** + * Convert an FVariant to a Windows VARIANT. + * Only necessary conversions are implemented. + */ + VARIANT FVariantToWindowsVariant(const FVariant& Value); +} + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAWidgetProvider.h b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAWidgetProvider.h new file mode 100644 index 000000000000..500588a808fc --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAWidgetProvider.h @@ -0,0 +1,119 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "Windows/Accessibility/WindowsUIABaseProvider.h" +#include "Misc/Variant.h" + +class FWindowsUIAManager; +class IAccessibleWidget; + +/** + * Windows UIA Provider implementation for all non-Window widgets. + * + * WidgetProvider does not inherit from IRawElementProviderFragmentRoot and thus cannot be queried + * by external applications for ElementProviderFromPoint(). This should work fine but there could + * be unforeseen consquences for cases like modal popup dialogs which are not necessarily windows. + */ +class FWindowsUIAWidgetProvider + : public FWindowsUIABaseProvider + , public IRawElementProviderSimple + , public IRawElementProviderFragment +{ + friend class FWindowsUIAManager; +public: + // IUnknown + virtual HRESULT STDCALL QueryInterface(REFIID riid, void** ppInterface) override; + virtual ULONG STDCALL AddRef() override; + virtual ULONG STDCALL Release() override; + // ~ + + // IRawElementProviderSimple + virtual HRESULT STDCALL get_ProviderOptions(ProviderOptions* pRetVal) override; + virtual HRESULT STDCALL GetPatternProvider(PATTERNID patternId, IUnknown** pRetVal) override; + virtual HRESULT STDCALL GetPropertyValue(PROPERTYID propertyId, VARIANT* pRetVal) override; + virtual HRESULT STDCALL get_HostRawElementProvider(IRawElementProviderSimple** pRetVal) override; + // ~ + + // IRawElementProviderFragment + virtual HRESULT STDCALL Navigate(NavigateDirection direction, IRawElementProviderFragment** pRetVal) override; + virtual HRESULT STDCALL GetRuntimeId(SAFEARRAY** pRetVal) override; + virtual HRESULT STDCALL get_BoundingRectangle(UiaRect* pRetVal) override; + virtual HRESULT STDCALL GetEmbeddedFragmentRoots(SAFEARRAY** pRetVal) override; + virtual HRESULT STDCALL SetFocus() override; + virtual HRESULT STDCALL get_FragmentRoot(IRawElementProviderFragmentRoot** pRetVal) override; + // ~ + + /** + * Check if this Provider implements a specific control pattern. A FWindowsUIAControlProvider can + * then be created of the pattern is supported. Since this is a helper function, the caller should + * separately ensure IsValid() before doing anything with the result. + * + * @param PatternId The control pattern to check for + * @return true if a control Provider can be created for this Provider for this control pattern + */ + bool SupportsInterface(PATTERNID PatternId) const; + +protected: + FWindowsUIAWidgetProvider(FWindowsUIAManager& InManager, TSharedRef InWidget); + virtual ~FWindowsUIAWidgetProvider(); + +// void UpdateCachedProperties(); +//private: +// void UpdateCachedProperty(PROPERTYID PropertyId); +// TMap CachedPropertyValues; +}; + +/** + * Windows UIA Provider implementation for all Window widgets. Widget->AsWindow() must return a valid pointer. + */ +class FWindowsUIAWindowProvider + : public FWindowsUIAWidgetProvider + , public IRawElementProviderFragmentRoot +{ + friend class FWindowsUIAManager; +public: + // IUnknown + virtual HRESULT STDCALL QueryInterface(REFIID riid, void** ppInterface) override; + virtual ULONG STDCALL AddRef() override; + virtual ULONG STDCALL Release() override; + // ~ + + // IRawElementProviderSimple + virtual HRESULT STDCALL get_HostRawElementProvider(IRawElementProviderSimple** pRetVal) override; + virtual HRESULT STDCALL GetPatternProvider(PATTERNID patternId, IUnknown** pRetVal) override; + // ~ + + // IRawElementProviderFragmentRoot + virtual HRESULT STDCALL ElementProviderFromPoint(double x, double y, IRawElementProviderFragment** pRetVal) override; + virtual HRESULT STDCALL GetFocus(IRawElementProviderFragment** pRetVal) override; + // ~ + +protected: + /** Note: InWidget must also be an IAccessibleWindow */ + FWindowsUIAWindowProvider(FWindowsUIAManager& InManager, TSharedRef InWidget); + virtual ~FWindowsUIAWindowProvider(); +}; + +/** + * Helper class which can be used in conjunction with FWindowsUIAManager::GetWidgetProvider, which will + * automatically calls Release on the Provider when the variable goes out of scope. Note that GetWidgetProvider + * already increments the ref count when its called, so the scoped provider does not have to also increase it. + */ +class FScopedWidgetProvider +{ +public: + FScopedWidgetProvider(FWindowsUIAWidgetProvider& InProvider) + : Provider(InProvider) + { + } + ~FScopedWidgetProvider() + { + Provider.Release(); + } + FWindowsUIAWidgetProvider& Provider; +}; + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Windows/WindowsApplication.h b/Engine/Source/Runtime/ApplicationCore/Public/Windows/WindowsApplication.h index 67a1d1b41cc6..e8ddc3996cb0 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/Windows/WindowsApplication.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/Windows/WindowsApplication.h @@ -342,6 +342,9 @@ public: // GenericApplication overrides virtual void SetMessageHandler( const TSharedRef< class FGenericApplicationMessageHandler >& InMessageHandler ) override; +#if WITH_ACCESSIBILITY + virtual void SetAccessibleMessageHandler(const TSharedRef& InAccessibleMessageHandler) override; +#endif virtual void PollGameDeviceState( const float TimeDelta ) override; virtual void PumpMessages( const float TimeDelta ) override; virtual void ProcessDeferredEvents( const float TimeDelta ) override; @@ -507,6 +510,11 @@ private: TSharedPtr TaskbarList; +#if WITH_ACCESSIBILITY + /** Handler for WM_GetObject messages that come in */ + TUniquePtr UIAManager; +#endif + // Accessibility shortcut keys STICKYKEYS StartupStickyKeys; TOGGLEKEYS StartupToggleKeys; diff --git a/Engine/Source/Runtime/AssetRegistry/Private/AssetDataGatherer.cpp b/Engine/Source/Runtime/AssetRegistry/Private/AssetDataGatherer.cpp index 517a3cf4688e..09fbef0b0926 100644 --- a/Engine/Source/Runtime/AssetRegistry/Private/AssetDataGatherer.cpp +++ b/Engine/Source/Runtime/AssetRegistry/Private/AssetDataGatherer.cpp @@ -2,9 +2,10 @@ #include "AssetDataGatherer.h" #include "HAL/PlatformProcess.h" +#include "HAL/RunnableThread.h" +#include "Misc/AsciiSet.h" #include "Misc/CommandLine.h" #include "Misc/Paths.h" -#include "HAL/RunnableThread.h" #include "Misc/ScopeLock.h" #include "AssetRegistryPrivate.h" #include "NameTableArchive.h" @@ -36,27 +37,13 @@ namespace } }; - bool IsValidPackageFileToRead(const FString& Filename) + static constexpr FAsciiSet InvalidLongPackageCharacters(INVALID_LONGPACKAGE_CHARACTERS); + + static bool ConvertToValidLongPackageName(const FString& Filename, /* out */ FString& LongPackageName) { - FString LongPackageName; - if (FPackageName::TryConvertFilenameToLongPackageName(Filename, LongPackageName)) - { - // Make sure the path does not contain invalid characters. These packages will not be successfully loaded or read later. - for (const TCHAR PackageChar : LongPackageName) - { - for (const TCHAR InvalidChar : INVALID_LONGPACKAGE_CHARACTERS) - { - if (PackageChar == InvalidChar) - { - return false; - } - } - } - - return true; - } - - return false; + // Make sure the path does not contain invalid characters. These packages will not be successfully loaded or read later. + return FPackageName::TryConvertFilenameToLongPackageName(Filename, LongPackageName) && + FAsciiSet::HasNone(*LongPackageName, InvalidLongPackageCharacters); } } @@ -137,11 +124,19 @@ uint32 FAssetDataDiscovery::Run() { FScopeLock CritSectionLock(&WorkerThreadCriticalSection); - // Place all the discovered files into the files to search list - DiscoveredPaths.Append(MoveTemp(LocalDiscoveredPathsArray)); + // Work around for TArray::Append(const TArray&) not growing expontentially. + // This causes O(N^2) allocations when continuously appending small arrays, + // at least on platforms with allocators that use QuantizeSize(). + const auto ReserveAndAppend = [](auto& To, auto&& From) + { + To.Reserve(FMath::RoundUpToPowerOfTwo(To.Num() + From.Num())); + To.Append(MoveTemp(From)); + }; - PriorityDiscoveredFiles.Append(MoveTemp(LocalPriorityFilesToSearch)); - NonPriorityDiscoveredFiles.Append(MoveTemp(LocalNonPriorityFilesToSearch)); + // Place all the discovered files into the files to search list + ReserveAndAppend(DiscoveredPaths, MoveTemp(LocalDiscoveredPathsArray)); + ReserveAndAppend(PriorityDiscoveredFiles, MoveTemp(LocalPriorityFilesToSearch)); + ReserveAndAppend(NonPriorityDiscoveredFiles, MoveTemp(LocalNonPriorityFilesToSearch)); } } @@ -178,10 +173,9 @@ uint32 FAssetDataDiscovery::Run() } else if (FPackageName::IsPackageFilename(PackageFilenameStr)) { - if (IsValidPackageFileToRead(PackageFilenameStr)) + FString LongPackageNameStr; + if (ConvertToValidLongPackageName(PackageFilenameStr, LongPackageNameStr)) { - const FString LongPackageNameStr = FPackageName::FilenameToLongPackageName(PackageFilenameStr); - if (IsPriorityFile(PackageFilenameStr)) { LocalPriorityFilesToSearch.Add(FDiscoveredPackageFile(PackageFilenameStr, InPackageStatData.ModificationTime)); @@ -791,9 +785,12 @@ void FAssetDataGatherer::AddPathToSearch(const FString& Path) void FAssetDataGatherer::AddFilesToSearch(const TArray& Files) { TArray FilesToAdd; + FilesToAdd.Reserve(Files.Num()); + FString LongPackageName; for (const FString& Filename : Files) { - if ( IsValidPackageFileToRead(Filename) ) + LongPackageName.Reset(); + if (ConvertToValidLongPackageName(Filename, LongPackageName)) { // Add the path to this asset into the list of discovered paths FilesToAdd.Add(Filename); diff --git a/Engine/Source/Runtime/AssetRegistry/Private/AssetRegistryState.cpp b/Engine/Source/Runtime/AssetRegistry/Private/AssetRegistryState.cpp index fc33f0f185da..a4478c11dd24 100644 --- a/Engine/Source/Runtime/AssetRegistry/Private/AssetRegistryState.cpp +++ b/Engine/Source/Runtime/AssetRegistry/Private/AssetRegistryState.cpp @@ -1698,7 +1698,7 @@ void FAssetRegistryState::Dump(const TArray& Arguments, TArray TArray Keys; AssetMap.GenerateKeyArray(Keys); - Keys.Sort(); + Keys.Sort(FNameLexicalLess()); TArray Items; Items.Reserve(1024); @@ -1775,7 +1775,7 @@ void FAssetRegistryState::Dump(const TArray& Arguments, TArray TArray Keys; CachedPackageData.GenerateKeyArray(Keys); - Keys.Sort(); + Keys.Sort(FNameLexicalLess()); for (const FName& Key : Keys) { diff --git a/Engine/Source/Runtime/AssetRegistry/Private/NameTableArchive.cpp b/Engine/Source/Runtime/AssetRegistry/Private/NameTableArchive.cpp index 2ed29ee2a4fe..ab4eb540d776 100644 --- a/Engine/Source/Runtime/AssetRegistry/Private/NameTableArchive.cpp +++ b/Engine/Source/Runtime/AssetRegistry/Private/NameTableArchive.cpp @@ -4,10 +4,17 @@ #include "HAL/FileManager.h" #include "AssetRegistryPrivate.h" +class FNameTableErrorArchive : public FArchive +{ +public: + FNameTableErrorArchive() + { + SetError(); + } +}; +static FNameTableErrorArchive GNameTableErrorArchive; + FNameTableArchiveReader::FNameTableArchiveReader(int32 SerializationVersion, const FString& Filename) - : FArchive() - , ProxyAr(nullptr) - , FileAr(nullptr) { this->SetIsLoading(true); @@ -36,6 +43,7 @@ FNameTableArchiveReader::FNameTableArchiveReader(int32 SerializationVersion, con } // If we got here it failed to load properly + ProxyAr = &GNameTableErrorArchive; SetError(); } @@ -48,17 +56,15 @@ FNameTableArchiveReader::FNameTableArchiveReader(FArchive& WrappedArchive) if (!SerializeNameMap()) { + ProxyAr = &GNameTableErrorArchive; SetError(); } } FNameTableArchiveReader::~FNameTableArchiveReader() { - if (FileAr) - { - delete FileAr; - FileAr = nullptr; - } + delete FileAr; + FileAr = nullptr; } bool FNameTableArchiveReader::SerializeNameMap() @@ -66,7 +72,7 @@ bool FNameTableArchiveReader::SerializeNameMap() int64 NameOffset = 0; *this << NameOffset; - if (IsError() || NameOffset > TotalSize()) + if (NameOffset > TotalSize()) { // The file was corrupted. Return false to fail to load the cache an thus regenerate it. return false; @@ -75,16 +81,12 @@ bool FNameTableArchiveReader::SerializeNameMap() if( NameOffset > 0 ) { int64 OriginalOffset = Tell(); - Seek( NameOffset ); + ProxyAr->Seek( NameOffset ); int32 NameCount = 0; *this << NameCount; - - if (IsError()) - { - return false; - } - + + NameMap.Reserve(NameCount); for ( int32 NameMapIdx = 0; NameMapIdx < NameCount; ++NameMapIdx ) { // Read the name entry from the file. @@ -96,10 +98,10 @@ bool FNameTableArchiveReader::SerializeNameMap() return false; } - NameMap.Add(FName(NameEntry)); + NameMap.Add(FName(NameEntry).GetDisplayIndex()); } - Seek( OriginalOffset ); + ProxyAr->Seek(OriginalOffset); } return true; @@ -107,20 +109,18 @@ bool FNameTableArchiveReader::SerializeNameMap() void FNameTableArchiveReader::Serialize(void* V, int64 Length) { - if (ProxyAr && !IsError()) - { - ProxyAr->Serialize(V, Length); + ProxyAr->Serialize(V, Length); - if (ProxyAr->IsError()) - { - SetError(); - } + if (ProxyAr->IsError()) + { + ProxyAr = &GNameTableErrorArchive; + SetError(); } } bool FNameTableArchiveReader::Precache(int64 PrecacheOffset, int64 PrecacheSize) { - if (ProxyAr && !IsError()) + if (!IsError()) { return ProxyAr->Precache(PrecacheOffset, PrecacheSize); } @@ -130,7 +130,7 @@ bool FNameTableArchiveReader::Precache(int64 PrecacheOffset, int64 PrecacheSize) void FNameTableArchiveReader::Seek(int64 InPos) { - if (ProxyAr && !IsError()) + if (!IsError()) { ProxyAr->Seek(InPos); } @@ -138,74 +138,52 @@ void FNameTableArchiveReader::Seek(int64 InPos) int64 FNameTableArchiveReader::Tell() { - if (ProxyAr) - { - return ProxyAr->Tell(); - } - - return 0; + return ProxyAr->Tell(); } int64 FNameTableArchiveReader::TotalSize() { - if (ProxyAr) - { - return ProxyAr->TotalSize(); - } - - return 0; + return ProxyAr->TotalSize(); } const FCustomVersionContainer& FNameTableArchiveReader::GetCustomVersions() const { - if (ProxyAr) - { - return ProxyAr->GetCustomVersions(); - } - return FArchive::GetCustomVersions(); + return ProxyAr->GetCustomVersions(); } void FNameTableArchiveReader::SetCustomVersions(const FCustomVersionContainer& NewVersions) { - if (ProxyAr) - { - ProxyAr->SetCustomVersions(NewVersions); - } + ProxyAr->SetCustomVersions(NewVersions); } void FNameTableArchiveReader::ResetCustomVersions() { - if (ProxyAr) - { - ProxyAr->ResetCustomVersions(); - } + ProxyAr->ResetCustomVersions(); } -FArchive& FNameTableArchiveReader::operator<<( FName& Name ) +FArchive& FNameTableArchiveReader::operator<<(FName& OutName) { int32 NameIndex; FArchive& Ar = *this; Ar << NameIndex; - if( !NameMap.IsValidIndex(NameIndex) ) + if (NameMap.IsValidIndex(NameIndex)) { - UE_LOG(LogAssetRegistry, Error, TEXT("Bad name index reading cache %i/%i"), NameIndex, NameMap.Num() ); - SetError(); - } + FNameEntryId MappedName = NameMap[NameIndex]; - const FName& MappedName = NameMap.IsValidIndex(NameIndex) ? NameMap[NameIndex] : NAME_None; - if (MappedName.IsNone()) - { - int32 TempNumber; - Ar << TempNumber; - Name = NAME_None; + int32 Number; + Ar << Number; + + OutName = FName::CreateFromDisplayId(MappedName, MappedName ? Number : 0); } else { - int32 Number; - Ar << Number; - // simply create the name from the NameMap's name and the serialized instance number - Name = FName(MappedName, Number); + UE_LOG(LogAssetRegistry, Error, TEXT("Bad name index reading cache %i/%i"), NameIndex, NameMap.Num()); + + ProxyAr = &GNameTableErrorArchive; + SetError(); + + OutName = FName(); } return *this; @@ -290,7 +268,7 @@ void FNameTableArchiveWriter::SerializeNameMap() for (auto& Pair : NameMap) { check(NameMapIdx == Pair.Value); - Pair.Key.GetDisplayNameEntry()->Write(*this); + FName::GetEntry(Pair.Key)->Write(*this); NameMapIdx++; } } @@ -375,15 +353,12 @@ void FNameTableArchiveWriter::ResetCustomVersions() FArchive& FNameTableArchiveWriter::operator<<( FName& Name ) { - int32* NameIndexPtr = NameMap.Find(Name); + int32* NameIndexPtr = NameMap.Find(Name.GetDisplayIndex()); int32 NameIndex = NameIndexPtr ? *NameIndexPtr : INDEX_NONE; if ( NameIndex == INDEX_NONE ) { - // We need to store the FName without the number, as the number is stored separately and we don't - // want duplicate entries in the name table just because of the number - const FName NameNoNumber(Name, 0); NameIndex = NameMap.Num(); - NameMap.Add(NameNoNumber, NameIndex); + NameMap.Add(Name.GetDisplayIndex(), NameIndex); } FArchive& Ar = *this; diff --git a/Engine/Source/Runtime/AssetRegistry/Private/NameTableArchive.h b/Engine/Source/Runtime/AssetRegistry/Private/NameTableArchive.h index 5a3de0e8d40e..e0e1065e90f1 100644 --- a/Engine/Source/Runtime/AssetRegistry/Private/NameTableArchive.h +++ b/Engine/Source/Runtime/AssetRegistry/Private/NameTableArchive.h @@ -51,7 +51,7 @@ private: FArchive* ProxyAr; FArchive* FileAr; - TArray NameMap; + TArray NameMap; }; class FNameTableArchiveWriter : public FArchive @@ -84,6 +84,6 @@ private: FArchive* FileAr; FString FinalFilename; FString TempFilename; - TMap> NameMap; + TMap NameMap; int64 NameOffsetLoc; }; diff --git a/Engine/Source/Runtime/AssetRegistry/Private/PackageReader.cpp b/Engine/Source/Runtime/AssetRegistry/Private/PackageReader.cpp index 680b269e98d1..014505e8e833 100644 --- a/Engine/Source/Runtime/AssetRegistry/Private/PackageReader.cpp +++ b/Engine/Source/Runtime/AssetRegistry/Private/PackageReader.cpp @@ -463,7 +463,7 @@ FArchive& FPackageReader::operator<<( FName& Name ) { check(Loader); - NAME_INDEX NameIndex; + int32 NameIndex; FArchive& Ar = *this; Ar << NameIndex; diff --git a/Engine/Source/Runtime/AssetRegistry/Public/AssetData.h b/Engine/Source/Runtime/AssetRegistry/Public/AssetData.h index 6b2b58e36f0f..a425bcc046b0 100644 --- a/Engine/Source/Runtime/AssetRegistry/Public/AssetData.h +++ b/Engine/Source/Runtime/AssetRegistry/Public/AssetData.h @@ -153,12 +153,12 @@ public: bool operator>(const FAssetData& Other) const { - return ObjectPath > Other.ObjectPath; + return Other.ObjectPath.LexicalLess(ObjectPath); } bool operator<(const FAssetData& Other) const { - return ObjectPath < Other.ObjectPath; + return ObjectPath.LexicalLess(Other.ObjectPath); } /** Checks to see if this AssetData refers to an asset or is NULL */ diff --git a/Engine/Source/Runtime/AssetRegistry/Public/AssetDataTagMap.h b/Engine/Source/Runtime/AssetRegistry/Public/AssetDataTagMap.h index 75347e44f538..78ae56455f0e 100644 --- a/Engine/Source/Runtime/AssetRegistry/Public/AssetDataTagMap.h +++ b/Engine/Source/Runtime/AssetRegistry/Public/AssetDataTagMap.h @@ -12,7 +12,7 @@ #if !USE_COMPACT_ASSET_REGISTRY /** Type of tag map */ -typedef TSortedMap FAssetDataTagMap; +typedef TSortedMap FAssetDataTagMap; #else @@ -113,18 +113,18 @@ private: FString ToString() const { FString Result; - if (Class != 0) + if (Class) { FName(Class, Class, 0).AppendString(Result); Result += TCHAR('\''); } FName(Package, Package, 0).AppendString(Result); - if (Object != 0) + if (Object) { Result += TCHAR('.'); FName(Object, Object, 0).AppendString(Result); } - if (Class != 0) + if (Class) { Result += TCHAR('\''); } @@ -153,14 +153,13 @@ private: FStorageID Result; Result.Fields.IsFName = true; FName ValueName(*Value); - if (ValueName.GetNumber() || ValueName.GetComparisonIndex() != ValueName.GetDisplayIndex() || ValueName.GetComparisonIndex() > FStorageID::MaxNoNumberFNameIndex) + if (ValueName.GetNumber() || ValueName.GetComparisonIndex() != ValueName.GetDisplayIndex() || ValueName.GetComparisonIndex().ToUnstableInt() > FStorageID::MaxNoNumberFNameIndex) { Result.Fields.Index = FNames.Add(ValueName); } else { - check(ValueName.GetComparisonIndex() >= 0); - Result.Fields.Index = ValueName.GetComparisonIndex(); + Result.Fields.Index = ValueName.GetComparisonIndex().ToUnstableInt(); Result.Fields.NoNumbers = true; } // there are cases where the results do not match on case @@ -297,7 +296,8 @@ public: { return FNames.IsAllocated(Id.Fields.Index); } - return FName(Id.Fields.Index, Id.Fields.Index, 0).IsValid(); + FNameEntryId EntryId = FNameEntryId::FromUnstableInt(Id.Fields.Index); + return FName(EntryId, EntryId, 0).IsValid(); } if (Id.Fields.IsFNameExportText) { @@ -332,7 +332,9 @@ public: { return TEXT("False"); } - return FName(Id.Fields.Index, Id.Fields.Index, 0).ToString(); + + FNameEntryId EntryId = FNameEntryId::FromUnstableInt(Id.Fields.Index); + return FName(EntryId, EntryId, 0).ToString(); } if (Id.Fields.IsFNameExportText) { @@ -400,7 +402,7 @@ inline FAssetDataTagMapValueStorage::FStorageID::operator FString() const return FAssetDataTagMapValueStorage::Get().IdToString(*this); } -typedef TSortedMap FAssetDataTagMapBase; +typedef TSortedMap FAssetDataTagMapBase; /** Wrapper of the underlying map that handles making sure that when the map dies, the underlying storage for the strings is freed. */ class FAssetDataTagMap : public FAssetDataTagMapBase @@ -474,7 +476,7 @@ public: /** This is serialization compatible with the non-compact version...so we just load the non-compact version and iterate it to compress the data structure. */ friend FArchive& operator<<(FArchive& Ar, FAssetDataTagMap& This) { - TSortedMap EmulatedContainer; + TSortedMap EmulatedContainer; if (Ar.IsLoading()) { Ar << EmulatedContainer; diff --git a/Engine/Source/Runtime/AudioMixer/Private/AudioMixerDevice.cpp b/Engine/Source/Runtime/AudioMixer/Private/AudioMixerDevice.cpp index aef61aea6338..3b7524745ce4 100644 --- a/Engine/Source/Runtime/AudioMixer/Private/AudioMixerDevice.cpp +++ b/Engine/Source/Runtime/AudioMixer/Private/AudioMixerDevice.cpp @@ -354,17 +354,6 @@ namespace Audio AudioMixerPlatform->ResumePlaybackOnNewDevice(); } -#if 0 // Disable touching the listener transforms - ListenerTransforms.Reset(); - for (FListener& Listener : Listeners) - { - ListenerTransforms.Add(Listener.Transform); - } - - // Update listener transforms, some effects use the listener transform data - SourceManager.SetListenerTransforms(ListenerTransforms); -#endif - // Loop through any envelope-following submixes and perform any broadcasting of envelope data if needed TArray SubmixEnvelopeData; for (USoundSubmix* SoundSubmix : EnvelopeFollowingSubmixes) @@ -1000,32 +989,42 @@ namespace Audio if (!IsMasterSubmixType(InSoundSubmix)) { + FMixerSubmixPtr MixerSubmix; + // If the sound submix wasn't already registered get it into the system. if (!Submixes.Contains(InSoundSubmix)) { - FMixerSubmixPtr MixerSubmix = FMixerSubmixPtr(new FMixerSubmix(this)); + MixerSubmix = FMixerSubmixPtr(new FMixerSubmix(this)); Submixes.Add(InSoundSubmix, MixerSubmix); + } + else + { + FMixerSubmixPtr* ExistingMixerSubmix = Submixes.Find(InSoundSubmix); + check(ExistingMixerSubmix); + MixerSubmix = *ExistingMixerSubmix; + } - if (bInit) + check(MixerSubmix.IsValid()); + + if (bInit) + { + // Setup the parent-child relationship + FMixerSubmixWeakPtr ParentSubmixInstance; + if (InSoundSubmix->ParentSubmix) { - // Setup the parent-child relationship - FMixerSubmixWeakPtr ParentSubmixInstance; - if (InSoundSubmix->ParentSubmix) - { - ParentSubmixInstance = GetSubmixInstance(InSoundSubmix->ParentSubmix); - } - else - { - ParentSubmixInstance = GetMasterSubmix(); - } + ParentSubmixInstance = GetSubmixInstance(InSoundSubmix->ParentSubmix); + } + else + { + ParentSubmixInstance = GetMasterSubmix(); + } - FMixerSubmixPtr ParentSubmixInstancePtr = ParentSubmixInstance.Pin(); - if (ParentSubmixInstancePtr.IsValid()) - { - ParentSubmixInstancePtr->AddChildSubmix(MixerSubmix); - MixerSubmix->SetParentSubmix(ParentSubmixInstance); - MixerSubmix->Init(InSoundSubmix); - } + FMixerSubmixPtr ParentSubmixInstancePtr = ParentSubmixInstance.Pin(); + if (ParentSubmixInstancePtr.IsValid()) + { + ParentSubmixInstancePtr->AddChildSubmix(MixerSubmix); + MixerSubmix->SetParentSubmix(ParentSubmixInstance); + MixerSubmix->Init(InSoundSubmix); } } } diff --git a/Engine/Source/Runtime/AudioMixer/Private/DSP/Noise.cpp b/Engine/Source/Runtime/AudioMixer/Private/DSP/Noise.cpp index cf51f2767262..009bddb54def 100644 --- a/Engine/Source/Runtime/AudioMixer/Private/DSP/Noise.cpp +++ b/Engine/Source/Runtime/AudioMixer/Private/DSP/Noise.cpp @@ -5,21 +5,21 @@ namespace Audio { FWhiteNoise::FWhiteNoise(const float InScale, const float InAdd) - : Scale(InScale) - , Add(InAdd) { - + SetScaleAdd(InScale, InAdd); } void FWhiteNoise::SetScaleAdd(const float InScale, const float InAdd) { Scale = InScale; Add = InAdd; + + RandomStream.Initialize(HashCombine(GetTypeHash(Scale), GetTypeHash(Add))); } float FWhiteNoise::Generate() { - return Add + Scale * FMath::SRand() * 2 - 1.0f; + return Add + Scale * RandomStream.FRand() * 2 - 1.0f; } FPinkNoise::FPinkNoise(const float InScale, const float InAdd) diff --git a/Engine/Source/Runtime/AudioMixer/Public/DSP/Noise.h b/Engine/Source/Runtime/AudioMixer/Public/DSP/Noise.h index 1a9f8783ec08..4ede3a2a95f3 100644 --- a/Engine/Source/Runtime/AudioMixer/Public/DSP/Noise.h +++ b/Engine/Source/Runtime/AudioMixer/Public/DSP/Noise.h @@ -3,6 +3,7 @@ #pragma once #include "CoreMinimal.h" +#include "Math/RandomStream.h" namespace Audio { @@ -24,6 +25,8 @@ namespace Audio private: float Scale; float Add; + + FRandomStream RandomStream; }; /** diff --git a/Engine/Source/Runtime/AugmentedReality/Public/ARBlueprintProxy.h b/Engine/Source/Runtime/AugmentedReality/Public/ARBlueprintProxy.h index 33ce3688787a..bc1d35b62f87 100644 --- a/Engine/Source/Runtime/AugmentedReality/Public/ARBlueprintProxy.h +++ b/Engine/Source/Runtime/AugmentedReality/Public/ARBlueprintProxy.h @@ -27,7 +27,7 @@ public: virtual bool IsTickable() const override { return !HasAnyFlags(RF_ClassDefaultObject) && bShouldTick; } virtual TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(UARBaseAsyncTaskBlueprintProxy, STATGROUP_Tickables); } //~ End FTickableObject Interface - + virtual void ReportSuccess() { check(0); } virtual void ReportFailure() { check(0); } diff --git a/Engine/Source/Runtime/Core/Core.Build.cs b/Engine/Source/Runtime/Core/Core.Build.cs index 78ed4991c76e..403409ea5de5 100644 --- a/Engine/Source/Runtime/Core/Core.Build.cs +++ b/Engine/Source/Runtime/Core/Core.Build.cs @@ -14,6 +14,8 @@ public class Core : ModuleRules PrivateDependencyModuleNames.Add("BuildSettings"); + PublicDependencyModuleNames.Add("TraceLog"); + PrivateIncludePaths.AddRange( new string[] { "Developer/DerivedDataCache/Public", @@ -197,7 +199,7 @@ public class Core : ModuleRules bool bWithMallocStomp = false; if (Target.Configuration != UnrealTargetConfiguration.Shipping) { - if (Target.Platform == UnrealTargetPlatform.Mac || Target.Platform == UnrealTargetPlatform.Mac || Target.Platform == UnrealTargetPlatform.Mac) + if (Target.Platform == UnrealTargetPlatform.Mac || Target.Platform == UnrealTargetPlatform.Linux || Target.Platform == UnrealTargetPlatform.Win64) // Target.Platform == UnrealTargetPlatform.Win32: // 32-bit windows can technically be supported, but will likely run out of virtual memory space quickly // Target.Platform == UnrealTargetPlatform.XboxOne: // XboxOne could be supported, as it's similar enough to Win64 { diff --git a/Engine/Source/Runtime/Core/Private/Async/TaskGraph.cpp b/Engine/Source/Runtime/Core/Private/Async/TaskGraph.cpp index 1a9535402675..aed181791c2e 100644 --- a/Engine/Source/Runtime/Core/Private/Async/TaskGraph.cpp +++ b/Engine/Source/Runtime/Core/Private/Async/TaskGraph.cpp @@ -28,6 +28,7 @@ #include "HAL/LowLevelMemTracker.h" #include "HAL/ThreadHeartBeat.h" #include "ProfilingDebugging/ExternalProfiler.h" +#include "ProfilingDebugging/MiscTrace.h" DEFINE_LOG_CATEGORY_STATIC(LogTaskGraph, Log, All); @@ -1210,12 +1211,14 @@ public: for (int32 ThreadIndex = LastExternalThread + 1; ThreadIndex < NumThreads; ThreadIndex++) { FString Name; + const ANSICHAR* GroupName = "TaskGraphNormal"; int32 Priority = ThreadIndexToPriorityIndex(ThreadIndex); EThreadPriority ThreadPri; uint64 Affinity = FPlatformAffinity::GetTaskGraphThreadMask(); if (Priority == 1) { Name = FString::Printf(TEXT("TaskGraphThreadHP %d"), ThreadIndex - (LastExternalThread + 1)); + GroupName = "TaskGraphHigh"; ThreadPri = TPri_SlightlyBelowNormal; // we want even hi priority tasks below the normal threads // If the platform defines FPlatformAffinity::GetTaskGraphHighPriorityTaskMask then use it @@ -1227,6 +1230,7 @@ public: else if (Priority == 2) { Name = FString::Printf(TEXT("TaskGraphThreadBP %d"), ThreadIndex - (LastExternalThread + 1)); + GroupName = "TaskGraphLow"; ThreadPri = TPri_Lowest; // If the platform defines FPlatformAffinity::GetTaskGraphBackgroundTaskMask then use it if ( FPlatformAffinity::GetTaskGraphBackgroundTaskMask() != 0xFFFFFFFFFFFFFFFF ) @@ -1248,6 +1252,10 @@ public: #endif WorkerThreads[ThreadIndex].RunnableThread = FRunnableThread::Create(&Thread(ThreadIndex), *Name, StackSize, ThreadPri, Affinity); // these are below normal threads so that they sleep when the named threads are active WorkerThreads[ThreadIndex].bAttached = true; + if (WorkerThreads[ThreadIndex].RunnableThread) + { + TRACE_SET_THREAD_GROUP(WorkerThreads[ThreadIndex].RunnableThread->GetThreadID(), GroupName); + } } } diff --git a/Engine/Source/Runtime/Core/Private/Containers/ContainersTest.cpp b/Engine/Source/Runtime/Core/Private/Containers/ContainersTest.cpp index 558f0132f114..33d27a3f87aa 100644 --- a/Engine/Source/Runtime/Core/Private/Containers/ContainersTest.cpp +++ b/Engine/Source/Runtime/Core/Private/Containers/ContainersTest.cpp @@ -503,13 +503,13 @@ bool FContainersFullTest::RunTest(const FString& Parameters) RunContainerTests>, FString>(); RunContainerTests, int32>(); - RunContainerTests, FName>(); + RunContainerTests, FName>(); RunContainerTests, FString>(); RunContainerTests>, FString>(); // Verify use of FName index sorter with SortedMap - TSortedMap NameMap; + TSortedMap NameMap; NameMap.Add(NAME_NameProperty); NameMap.Add(NAME_FloatProperty); NameMap.Add(NAME_None); @@ -555,11 +555,11 @@ bool FContainerPerformanceTest::RunTest(const FString& Parameters) RunPerformanceTest, int32>(TEXT("TSortedMap int32"), 1000, 1000000); RunPerformanceTest, int32>(TEXT("TSortedMap int32"), 10000, 1000000); - RunPerformanceTest, FName>(TEXT("TSortedMap FName"), 1, 1000000); - RunPerformanceTest, FName>(TEXT("TSortedMap FName"), 10, 1000000); - RunPerformanceTest, FName>(TEXT("TSortedMap FName"), 100, 1000000); - RunPerformanceTest, FName>(TEXT("TSortedMap FName"), 1000, 1000000); - RunPerformanceTest, FName>(TEXT("TSortedMap FName"), 10000, 1000000); + RunPerformanceTest, FName>(TEXT("TSortedMap FName"), 1, 1000000); + RunPerformanceTest, FName>(TEXT("TSortedMap FName"), 10, 1000000); + RunPerformanceTest, FName>(TEXT("TSortedMap FName"), 100, 1000000); + RunPerformanceTest, FName>(TEXT("TSortedMap FName"), 1000, 1000000); + RunPerformanceTest, FName>(TEXT("TSortedMap FName"), 10000, 1000000); RunPerformanceTest, FString>(TEXT("TSortedMap FString"), 1, 1000000); RunPerformanceTest, FString>(TEXT("TSortedMap FString"), 10, 1000000); diff --git a/Engine/Source/Runtime/Core/Private/Containers/String.cpp b/Engine/Source/Runtime/Core/Private/Containers/String.cpp index 14ff45af6a4e..fb3b63b37172 100644 --- a/Engine/Source/Runtime/Core/Private/Containers/String.cpp +++ b/Engine/Source/Runtime/Core/Private/Containers/String.cpp @@ -658,39 +658,29 @@ void FString::SerializeAsANSICharArray( FArchive& Ar, int32 MinCharacters ) cons } } -void FString::AppendInt( int32 InNum ) +void FString::AppendInt( int32 Num ) { - int64 Num = InNum; // This avoids having to deal with negating -MAX_int32-1 - const TCHAR* NumberChar[11] = { TEXT("0"), TEXT("1"), TEXT("2"), TEXT("3"), TEXT("4"), TEXT("5"), TEXT("6"), TEXT("7"), TEXT("8"), TEXT("9"), TEXT("-") }; - bool bIsNumberNegative = false; + const TCHAR* DigitToChar = TEXT("9876543210123456789"); + constexpr int32 ZeroDigitIndex = 9; + bool bIsNumberNegative = Num < 0; const int32 TempBufferSize = 16; // 16 is big enough TCHAR TempNum[TempBufferSize]; int32 TempAt = TempBufferSize; // fill the temp string from the top down. - // Correctly handle negative numbers and convert to positive integer. - if( Num < 0 ) - { - bIsNumberNegative = true; - Num = -Num; - } - - TempNum[--TempAt] = 0; // NULL terminator - - // Convert to string assuming base ten and a positive integer. + // Convert to string assuming base ten. do { - TempNum[--TempAt] = *NumberChar[Num % 10]; + TempNum[--TempAt] = DigitToChar[ZeroDigitIndex + (Num % 10)]; Num /= 10; } while( Num ); - // Append sign as we're going to reverse string afterwards. if( bIsNumberNegative ) { - TempNum[--TempAt] = *NumberChar[10]; + TempNum[--TempAt] = TEXT('-'); } const TCHAR* CharPtr = TempNum + TempAt; - const int32 NumChars = TempBufferSize - TempAt - 1; + const int32 NumChars = TempBufferSize - TempAt; Append(CharPtr, NumChars); } @@ -1587,4 +1577,35 @@ FString SlugStringForValidName(const FString& DisplayString, const TCHAR* Replac } return GeneratedName; -} \ No newline at end of file +} + +void FTextRange::CalculateLineRangesFromString(const FString& Input, TArray& LineRanges) +{ + int32 LineBeginIndex = 0; + + // Loop through splitting at new-lines + const TCHAR* const InputStart = *Input; + for (const TCHAR* CurrentChar = InputStart; CurrentChar && *CurrentChar; ++CurrentChar) + { + // Handle a chain of \r\n slightly differently to stop the FChar::IsLinebreak adding two separate new-lines + const bool bIsWindowsNewLine = (*CurrentChar == '\r' && *(CurrentChar + 1) == '\n'); + if (bIsWindowsNewLine || FChar::IsLinebreak(*CurrentChar)) + { + const int32 LineEndIndex = (CurrentChar - InputStart); + check(LineEndIndex >= LineBeginIndex); + LineRanges.Emplace(FTextRange(LineBeginIndex, LineEndIndex)); + + if (bIsWindowsNewLine) + { + ++CurrentChar; // skip the \n of the \r\n chain + } + LineBeginIndex = (CurrentChar - InputStart) + 1; // The next line begins after the end of the current line + } + } + + // Process any remaining string after the last new-line + if (LineBeginIndex <= Input.Len()) + { + LineRanges.Emplace(FTextRange(LineBeginIndex, Input.Len())); + } +} diff --git a/Engine/Source/Runtime/Core/Private/Containers/StringTest.cpp b/Engine/Source/Runtime/Core/Private/Containers/StringTest.cpp index 5a72f469e350..42027178a776 100644 --- a/Engine/Source/Runtime/Core/Private/Containers/StringTest.cpp +++ b/Engine/Source/Runtime/Core/Private/Containers/StringTest.cpp @@ -46,4 +46,44 @@ bool FStringSanitizeFloatTest::RunTest(const FString& Parameters) return true; } +IMPLEMENT_SIMPLE_AUTOMATION_TEST(FStringAppendIntTest, "System.Core.String.AppendInt", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::SmokeFilter) +bool FStringAppendIntTest::RunTest(const FString& Parameters) +{ + auto DoTest = [this](const TCHAR* Call, const FString& Result, const TCHAR* InExpected) + { + if (!Result.Equals(InExpected, ESearchCase::CaseSensitive)) + { + AddError(FString::Printf(TEXT("'%s' failure: result '%s' (expected '%s')"), Call, *Result, InExpected)); + } + }; + + { + FString Zero; + Zero.AppendInt(0); + DoTest(TEXT("AppendInt(0)"), Zero, TEXT("0")); + } + + { + FString IntMin; + IntMin.AppendInt(MIN_int32); + DoTest(TEXT("AppendInt(MIN_int32)"), IntMin, TEXT("-2147483648")); + } + + { + FString IntMin; + IntMin.AppendInt(MAX_int32); + DoTest(TEXT("AppendInt(MAX_int32)"), IntMin, TEXT("2147483647")); + } + + { + FString AppendMultipleInts; + AppendMultipleInts.AppendInt(1); + AppendMultipleInts.AppendInt(-2); + AppendMultipleInts.AppendInt(3); + DoTest(TEXT("AppendInt(1);AppendInt(-2);AppendInt(3)"), AppendMultipleInts, TEXT("1-23")); + } + + return true; +} + #endif // WITH_DEV_AUTOMATION_TESTS diff --git a/Engine/Source/Runtime/Core/Private/GenericPlatform/GenericPlatformOutputDevices.cpp b/Engine/Source/Runtime/Core/Private/GenericPlatform/GenericPlatformOutputDevices.cpp index 0c856c98e622..d8ff65079c61 100644 --- a/Engine/Source/Runtime/Core/Private/GenericPlatform/GenericPlatformOutputDevices.cpp +++ b/Engine/Source/Runtime/Core/Private/GenericPlatform/GenericPlatformOutputDevices.cpp @@ -16,7 +16,7 @@ #include "Misc/OutputDeviceConsole.h" #include "Templates/UniquePtr.h" -TCHAR FGenericPlatformOutputDevices::CachedAbsoluteFilename[1024] = { 0 }; +TCHAR FGenericPlatformOutputDevices::CachedAbsoluteFilename[FGenericPlatformOutputDevices::AbsoluteFileNameMaxLength] = { 0 }; void FGenericPlatformOutputDevices::SetupOutputDevices() { @@ -59,9 +59,10 @@ FString FGenericPlatformOutputDevices::GetAbsoluteLogFilename() { FCString::Strcpy(CachedAbsoluteFilename, ARRAY_COUNT(CachedAbsoluteFilename), *FPaths::ProjectLogDir()); FString LogFilename; - if (!FParse::Value(FCommandLine::Get(), TEXT("LOG="), LogFilename)) + const bool bShouldStopOnSeparator = false; + if (!FParse::Value(FCommandLine::Get(), TEXT("LOG="), LogFilename, bShouldStopOnSeparator)) { - if (FParse::Value(FCommandLine::Get(), TEXT("ABSLOG="), LogFilename)) + if (FParse::Value(FCommandLine::Get(), TEXT("ABSLOG="), LogFilename, bShouldStopOnSeparator)) { CachedAbsoluteFilename[0] = 0; } diff --git a/Engine/Source/Runtime/Core/Private/GenericPlatform/GenericWidePlatformString.cpp b/Engine/Source/Runtime/Core/Private/GenericPlatform/GenericWidePlatformString.cpp index 1e3570688779..ca55820bd69b 100644 --- a/Engine/Source/Runtime/Core/Private/GenericPlatform/GenericWidePlatformString.cpp +++ b/Engine/Source/Runtime/Core/Private/GenericPlatform/GenericWidePlatformString.cpp @@ -235,11 +235,20 @@ void RunGetVarArgsTests() TestGetVarArgs(OutputString, TEXT("Test D|%p|"), 0x12345); check(FString(OutputString) == FString(TEXT("Test D|0x12345|"))); - TestGetVarArgs(OutputString, TEXT("Test E|%lld|"), 12345678912345); + TestGetVarArgs(OutputString, TEXT("Test E|%" INT64_FMT "|"), int64(12345678912345LL)); check(FString(OutputString) == FString(TEXT("Test E|12345678912345|"))); TestGetVarArgs(OutputString, TEXT("Test F|%f|%e|%g|"), 123.456, 123.456, 123.456); check(FString(OutputString) == FString(TEXT("Test F|123.456000|1.234560e+02|123.456|"))); + + TestGetVarArgs(OutputString, TEXT("Test G|%" UPTRINT_X_FMT"|"), UPTRINT(49374)); + check(FString(OutputString) == FString(TEXT("Test G|C0DE|"))); + + TestGetVarArgs(OutputString, TEXT("Test H|%" UPTRINT_x_FMT "|"), UPTRINT(49374)); + check(FString(OutputString) == FString(TEXT("Test H|c0de|"))); + + TestGetVarArgs(OutputString, TEXT("Test I|%" UINT64_FMT "|"), MAX_uint64); + check(FString(OutputString) == FString(TEXT("Test I|18446744073709551615|"))); } #endif @@ -329,6 +338,11 @@ namespace WIDECHAR* Ptr; WIDECHAR* EndMinusOne; // when null, this means the iterator has already moved past the writable area of the buffer }; + + static inline bool CharIsIntegerFormatSpecifier(TCHAR Char) + { + return (Char == 'i') | (Char == 'd') | (Char == 'u') | (Char == 'X') | (Char == 'x'); + } } int32 FGenericWidePlatformString::GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, const WIDECHAR*& Fmt, va_list ArgPtr ) @@ -457,7 +471,7 @@ int32 FGenericWidePlatformString::GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, c Src++; int Val = va_arg(ArgPtr, int); ANSICHAR AnsiNum[64]; - ANSICHAR FmtBuf[30] = {0}; + ANSICHAR FmtBuf[30]; // limit width to the buffer size FieldLen = FMath::Min(static_cast(sizeof(AnsiNum)) - 1, FieldLen); @@ -506,7 +520,7 @@ int32 FGenericWidePlatformString::GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, c size_t Val = va_arg(ArgPtr, size_t); ANSICHAR AnsiNum[64]; - ANSICHAR FmtBuf[30] = {0}; + ANSICHAR FmtBuf[30]; // limit width to the buffer size FieldLen = FMath::Min(static_cast(sizeof(AnsiNum)) - 1, FieldLen); @@ -578,11 +592,11 @@ int32 FGenericWidePlatformString::GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, c break; } - // treat %ld as %d. Also shorts for %h will be promoted to ints - if ((Src[0] == 'l' && Src[1] == 'd') || Src[0] == 'h') + // treat %ld as %d. Also shorts for %h will be promoted to ints. This path also handles %li, %lu, %lx and %lX. + if ((Src[0] == 'l' && CharIsIntegerFormatSpecifier(Src[1])) || Src[0] == 'h') { Src+=2; - int Val = va_arg(ArgPtr, int); + long int Val = va_arg(ArgPtr, long int); ANSICHAR AnsiNum[30]; ANSICHAR FmtBuf[30]; @@ -608,7 +622,7 @@ int32 FGenericWidePlatformString::GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, c { Src += 2; double Val = va_arg(ArgPtr, double); - ANSICHAR AnsiNum[30]; + ANSICHAR AnsiNum[340]; // make sure buffer is large enough to handle the longest double values, such as the largest number which is 316 characters including decimal point and 6 decimals ANSICHAR FmtBuf[30]; // Yes, this is lame. @@ -629,62 +643,65 @@ int32 FGenericWidePlatformString::GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, c break; } + // At this point we expect RemainingSize >= 3, but we know it's at least 2. + if (RemainingSize < 3) + { + printf("Unknown percent [%lc%lc] in FGenericWidePlatformString::GetVarArgs() [%s]\n.", Src[0], Src[1], TCHAR_TO_ANSI(Fmt)); + Src++; // skip it, I guess. + break; + } + if (Src[0] == 'l') { - if (Src[1] != 'l' && Src[1] != 'u' && Src[1] != 'x') + if (Src[1] != 'l' || !CharIsIntegerFormatSpecifier(Src[2])) { - printf("Unknown percent [%lc%lc] in FGenericWidePlatformString::GetVarArgs() [%s]\n.", Src[0], Src[1], TCHAR_TO_ANSI(Fmt)); - Src++; // skip it, I guess. - break; - } - else if (Src[1] == 'l' && RemainingSize == 2) - { - printf("Unknown percent [%lc%lc] in FGenericWidePlatformString::GetVarArgs() [%s]\n.", Src[0], Src[1], TCHAR_TO_ANSI(Fmt)); + printf("Unknown percent [%lc%lc%lc] in FGenericWidePlatformString::GetVarArgs() [%s]\n.", Src[0], Src[1], Src[2], TCHAR_TO_ANSI(Fmt)); Src++; // skip it, I guess. break; } } else if (Src[0] == 'I') { - if (RemainingSize == 2) - { - printf("Unknown percent [%lc%lc] in FGenericWidePlatformString::GetVarArgs() [%s]\n.", Src[0], Src[1], TCHAR_TO_ANSI(Fmt)); - Src++; // skip it, I guess. - break; - } - else if (RemainingSize == 3) + if (RemainingSize < 4) { printf("Unknown percent [%lc%lc%lc] in FGenericWidePlatformString::GetVarArgs() [%s]\n.", Src[0], Src[1], Src[2], TCHAR_TO_ANSI(Fmt)); Src++; // skip it, I guess. break; } - else if (Src[1] != '6' || Src[2] != '4') + else if (Src[1] != '6' || Src[2] != '4' || !CharIsIntegerFormatSpecifier(Src[3])) { - printf("Unknown percent [%lc%lc%lc] in FGenericWidePlatformString::GetVarArgs() [%s]\n.", Src[0], Src[1], Src[2], TCHAR_TO_ANSI(Fmt)); + printf("Unknown percent [%lc%lc%lc%lc] in FGenericWidePlatformString::GetVarArgs() [%s]\n.", Src[0], Src[1], Src[2], Src[3], TCHAR_TO_ANSI(Fmt)); Src++; // skip it, I guess. break; } } - - // Yes, this is lame. - int CpyIdx = 0; - unsigned long long Val = va_arg(ArgPtr, unsigned long long); - ANSICHAR AnsiNum[60]; - ANSICHAR FmtBuf[30]; - if (Src[0] == 'l' && Src[1] == 'l') - { - Src += 3; - } - else if (Src[0] == 'l') - { - Src += 2; - } else { + printf("Unknown percent [%lc%lc%lc] in FGenericWidePlatformString::GetVarArgs() [%s]\n.", Src[0], Src[1], Src[2], TCHAR_TO_ANSI(Fmt)); + Src++; // skip it, I guess. + break; + } + + // Yes, this is lame. + unsigned long long Val = va_arg(ArgPtr, unsigned long long); + ANSICHAR AnsiNum[60]; + ANSICHAR FmtBuf[30]; + + int CpyIdx; + if (Src[0] == 'I') + { + // Convert I64 syntax to ll Src += 4; - strcpy(FmtBuf, "%L"); - Percent += 4; - CpyIdx = 2; + Percent += 3; + CpyIdx = 3; + FmtBuf[0] = '%'; + FmtBuf[1] = 'l'; + FmtBuf[2] = 'l'; + } + else + { + Src += 3; + CpyIdx = 0; } while (Percent < Src && CpyIdx < ARRAY_COUNT(FmtBuf)) @@ -709,8 +726,7 @@ int32 FGenericWidePlatformString::GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, c { Src++; double Val = va_arg(ArgPtr, double); - // doubles in the form of 1e+9999 can get quite large, make sure we have enough room for them - ANSICHAR AnsiNum[48]; + ANSICHAR AnsiNum[340]; // make sure buffer is large enough to handle the longest double values, such as the largest number which is 316 characters including decimal point and 6 decimals ANSICHAR FmtBuf[30]; // Yes, this is lame. diff --git a/Engine/Source/Runtime/Core/Private/HAL/Allocators/AnsiAllocator.h b/Engine/Source/Runtime/Core/Private/HAL/Allocators/AnsiAllocator.h index 531d1aede9c2..32f06061ee94 100644 --- a/Engine/Source/Runtime/Core/Private/HAL/Allocators/AnsiAllocator.h +++ b/Engine/Source/Runtime/Core/Private/HAL/Allocators/AnsiAllocator.h @@ -14,6 +14,7 @@ class CORE_API FAnsiAllocator { public: + using SizeType = int32; enum { NeedsElementType = false }; enum { RequireRangeCheck = true }; @@ -61,7 +62,7 @@ public: { return Data; } - void ResizeAllocation(int32 PreviousNumElements, int32 NumElements, SIZE_T NumBytesPerElement) + void ResizeAllocation(SizeType PreviousNumElements, SizeType NumElements, SIZE_T NumBytesPerElement) { // Avoid calling FMemory::Realloc( nullptr, 0 ) as ANSI C mandates returning a valid pointer which is not what we want. if (NumElements) @@ -76,25 +77,25 @@ public: Data = nullptr; } } - int32 CalculateSlackReserve(int32 NumElements, int32 NumBytesPerElement) const + SizeType CalculateSlackReserve(SizeType NumElements, SIZE_T NumBytesPerElement) const { return DefaultCalculateSlackReserve(NumElements, NumBytesPerElement, false); } - int32 CalculateSlackShrink(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const + SizeType CalculateSlackShrink(SizeType NumElements, SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { return DefaultCalculateSlackShrink(NumElements, NumAllocatedElements, NumBytesPerElement, false); } - int32 CalculateSlackGrow(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const + SizeType CalculateSlackGrow(SizeType NumElements, SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { return DefaultCalculateSlackGrow(NumElements, NumAllocatedElements, NumBytesPerElement, false); } - SIZE_T GetAllocatedSize(int32 NumAllocatedElements, SIZE_T NumBytesPerElement) const + SIZE_T GetAllocatedSize(SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { return NumAllocatedElements * NumBytesPerElement; } - bool HasAllocation() + bool HasAllocation() const { return !!Data; } diff --git a/Engine/Source/Runtime/Core/Private/HAL/ConsoleManager.cpp b/Engine/Source/Runtime/Core/Private/HAL/ConsoleManager.cpp index 8c37e04aad3e..f588e01ff181 100644 --- a/Engine/Source/Runtime/Core/Private/HAL/ConsoleManager.cpp +++ b/Engine/Source/Runtime/Core/Private/HAL/ConsoleManager.cpp @@ -303,7 +303,7 @@ public: { } - // interface IConsoleVariable ----------------------------------- + // interface IConsoleVariable ----------------------------------- virtual void Release() { @@ -321,7 +321,9 @@ public: virtual int32 GetInt() const; virtual float GetFloat() const; virtual FString GetString() const; - virtual bool IsVariableInt() const { return false; } + virtual bool IsVariableInt() const override { return false; } + virtual bool IsVariableFloat() const override { return false; } + virtual bool IsVariableString() const override { return false; } virtual class TConsoleVariableData* AsVariableInt() { return 0; } virtual class TConsoleVariableData* AsVariableFloat() { return 0; } virtual class TConsoleVariableData* AsVariableString() { return 0; } @@ -385,9 +387,13 @@ template<> FString FConsoleVariable::GetString() const { return FString::Printf(TEXT("%g"), Value()); } +template<> bool FConsoleVariable::IsVariableFloat() const +{ + return true; +} template<> TConsoleVariableData* FConsoleVariable::AsVariableFloat() { - return &Data; + return &Data; } // specialization for FString @@ -412,7 +418,10 @@ template<> FString FConsoleVariable::GetString() const { return Value(); } - +template<> bool FConsoleVariable::IsVariableString() const +{ + return true; +} template<> TConsoleVariableData* FConsoleVariable::AsVariableString() { return &Data; @@ -456,6 +465,9 @@ public: { return TTypeToString::ToString(MainValue); } + virtual bool IsVariableInt() const override { return false; } + virtual bool IsVariableFloat() const override { return false; } + virtual bool IsVariableString() const override { return false; } private: // ---------------------------------------------------- @@ -490,6 +502,18 @@ FString FConsoleVariableRef::GetString() const // otherwise we get 2.1f would become "2.100000" return FString::SanitizeFloat(RefValue); } +template<> bool FConsoleVariableRef::IsVariableInt() const +{ + return true; +} +template<> bool FConsoleVariableRef::IsVariableInt() const +{ + return true; +} +template<> bool FConsoleVariableRef::IsVariableFloat() const +{ + return true; +} // string version @@ -533,6 +557,10 @@ public: { return MainValue; } + virtual bool IsVariableString() const override + { + return true; + } private: // ---------------------------------------------------- @@ -1412,6 +1440,33 @@ IConsoleObject* FConsoleManager::AddConsoleObject(const TCHAR* Name, IConsoleObj ConsoleObjects.Add(Name, Var); return Var; } +#if WITH_HOT_RELOAD + else if (GIsHotReload) + { + // Variable is being replaced due to a hot reload - copy state across to new variable, but only if the type hasn't changed + { + if (ExistingVar->IsVariableFloat()) + { + Var->Set(ExistingVar->GetFloat()); + } + } + { + if (ExistingVar->IsVariableInt()) + { + Var->Set(ExistingVar->GetInt()); + } + } + { + if (ExistingVar->IsVariableString()) + { + Var->Set(*ExistingVar->GetString()); + } + } + ExistingVar->Release(); + ConsoleObjects.Add(Name, Var); + return Var; + } +#endif else { // Copy data over from the new variable, diff --git a/Engine/Source/Runtime/Core/Private/HAL/LowLevelMemTracker.cpp b/Engine/Source/Runtime/Core/Private/HAL/LowLevelMemTracker.cpp index 47ff6ae47f10..60ff918f041c 100644 --- a/Engine/Source/Runtime/Core/Private/HAL/LowLevelMemTracker.cpp +++ b/Engine/Source/Runtime/Core/Private/HAL/LowLevelMemTracker.cpp @@ -443,7 +443,7 @@ static int64 FNameToTag(FName Name) } // get the bits out of the FName we need - int64 NameIndex = Name.GetComparisonIndex(); + int64 NameIndex = Name.GetComparisonIndex().ToUnstableInt(); int64 NameNumber = Name.GetNumber(); int64 tag = (NameNumber << 32) | NameIndex; LLMCheckf(tag > LLM_TAG_COUNT, TEXT("Passed with a name index [%d - %s] that was less than MemTracker_MaxUserAllocation"), NameIndex, *Name.ToString()); @@ -455,7 +455,7 @@ static int64 FNameToTag(FName Name) static FName TagToFName(int64 Tag) { // pull the bits back out of the tag - int32 NameIndex = (int32)(Tag & 0xFFFFFFFF); + FNameEntryId NameIndex = FNameEntryId::FromUnstableInt((int32)(Tag & 0xFFFFFFFF)); int32 NameNumber = (int32)(Tag >> 32); return FName(NameIndex, NameIndex, NameNumber); } @@ -520,9 +520,7 @@ FLowLevelMemTracker::FLowLevelMemTracker() FLowLevelMemTracker::~FLowLevelMemTracker() { - // Ensure that we skip any further tracking since it will fail after this destructor - bIsDisabled = true; - + bIsDisabled = true; // tracking must stop at this point or it will crash while tracking its own destruction for (int32 TrackerIndex = 0; TrackerIndex < (int32)ELLMTracker::Max; ++TrackerIndex) { Trackers[TrackerIndex]->~FLLMTracker(); diff --git a/Engine/Source/Runtime/Core/Private/HAL/MallocVerify.h b/Engine/Source/Runtime/Core/Private/HAL/MallocVerify.h index 2970daa1821d..2bfccc7ee94e 100644 --- a/Engine/Source/Runtime/Core/Private/HAL/MallocVerify.h +++ b/Engine/Source/Runtime/Core/Private/HAL/MallocVerify.h @@ -117,9 +117,9 @@ public: { return UsedMalloc->QuantizeSize(Count, Alignment); } - virtual void Trim() override + virtual void Trim(bool bTrimThreadCaches) override { - return UsedMalloc->Trim(); + return UsedMalloc->Trim(bTrimThreadCaches); } virtual void SetupTLSCachesOnCurrentThread() override { diff --git a/Engine/Source/Runtime/Core/Private/HAL/ThreadingBase.cpp b/Engine/Source/Runtime/Core/Private/HAL/ThreadingBase.cpp index c3250a430ec5..30f9bde4b1b1 100644 --- a/Engine/Source/Runtime/Core/Private/HAL/ThreadingBase.cpp +++ b/Engine/Source/Runtime/Core/Private/HAL/ThreadingBase.cpp @@ -8,6 +8,7 @@ #include "Misc/EventPool.h" #include "Templates/Atomic.h" #include "HAL/PlatformStackWalk.h" +#include "ProfilingDebugging/MiscTrace.h" DEFINE_STAT( STAT_EventWaitWithId ); DEFINE_STAT( STAT_EventTriggerWithId ); @@ -410,6 +411,10 @@ FRunnableThread* FRunnableThread::Create( } } + if (NewThread) + { + TRACE_CREATE_THREAD(NewThread->GetThreadID(), *NewThread->GetThreadName(), InThreadPri); + } #if STATS if( NewThread ) { diff --git a/Engine/Source/Runtime/Core/Private/Internationalization/FastDecimalFormat.cpp b/Engine/Source/Runtime/Core/Private/Internationalization/FastDecimalFormat.cpp index f4c2af653741..98c71499b901 100644 --- a/Engine/Source/Runtime/Core/Private/Internationalization/FastDecimalFormat.cpp +++ b/Engine/Source/Runtime/Core/Private/Internationalization/FastDecimalFormat.cpp @@ -279,7 +279,7 @@ void FractionalToString_SplitAndRoundNumber(const bool bIsNegative, const double // Multiply the value to round by 10^DecimalPlacesToRoundTo - this will allow us to perform rounding calculations // that correctly trim any remaining fractional parts that are outside of our rounding range double& ValueToRound = ((bIsRoundingEntireNumber) ? IntegralPart : FractionalPart); - ValueToRound *= Pow10Table[DecimalPlacesToRoundTo]; + ValueToRound = FMath::TruncateToHalfIfClose(ValueToRound * Pow10Table[DecimalPlacesToRoundTo]); // The rounding modes here mimic those of ICU. See http://userguide.icu-project.org/formatparse/numbers/rounding-modes switch (InRoundingMode) diff --git a/Engine/Source/Runtime/Core/Private/Internationalization/StringTableCore.cpp b/Engine/Source/Runtime/Core/Private/Internationalization/StringTableCore.cpp index 2e9fd1c1f578..4f845002ac5c 100644 --- a/Engine/Source/Runtime/Core/Private/Internationalization/StringTableCore.cpp +++ b/Engine/Source/Runtime/Core/Private/Internationalization/StringTableCore.cpp @@ -577,7 +577,10 @@ void FStringTableRedirects::RedirectTableId(FName& InOutTableId) } // Process the asset redirect (only works if the asset is loaded) - IStringTableEngineBridge::RedirectStringTableAsset(InOutTableId); + if (IStringTableEngineBridge::CanFindOrLoadStringTableAsset()) + { + IStringTableEngineBridge::RedirectStringTableAsset(InOutTableId); + } } void FStringTableRedirects::RedirectKey(const FName InTableId, FString& InOutKey) diff --git a/Engine/Source/Runtime/Core/Private/Internationalization/Text.cpp b/Engine/Source/Runtime/Core/Private/Internationalization/Text.cpp index 59019f7aa4ed..9982609d591c 100644 --- a/Engine/Source/Runtime/Core/Private/Internationalization/Text.cpp +++ b/Engine/Source/Runtime/Core/Private/Internationalization/Text.cpp @@ -245,10 +245,10 @@ FText::FText( TSharedRef InTextData ) } FText::FText( FString&& InSourceString ) - : TextData(new TGeneratedTextData(FString(InSourceString))) + : TextData(new TGeneratedTextData(CopyTemp(InSourceString))) // Copy the source string as the live display string , Flags(0) { - TextData->SetTextHistory(FTextHistory_Base(MoveTemp(InSourceString))); + TextData->SetTextHistory(FTextHistory_Base(MoveTemp(InSourceString))); // Move the source string as the historic source string } FText::FText( FName InTableId, FString InKey, const EStringTableLoadingPolicy InLoadingPolicy ) @@ -321,119 +321,91 @@ FText FText::ToUpper() const FText FText::TrimPreceding( const FText& InText ) { - FString TrimmedString = InText.ToString(); - { - int32 StartPos = 0; - while ( StartPos < TrimmedString.Len() ) - { - if( !FText::IsWhitespace( TrimmedString[StartPos] ) ) - { - break; - } + const FString& CurrentString = InText.ToString(); - ++StartPos; + int32 StartPos = 0; + while (StartPos < CurrentString.Len()) + { + if (!FText::IsWhitespace(CurrentString[StartPos])) + { + break; } - TrimmedString = TrimmedString.Right( TrimmedString.Len() - StartPos ); + ++StartPos; } - FText NewText = FText( MoveTemp( TrimmedString ) ); - - if (!GIsEditor) + if (StartPos == 0) { - if( (NewText.Flags & ETextFlag::CultureInvariant) != 0 ) - { - NewText.Flags |= ETextFlag::Transient; - } - else - { - NewText.Flags |= ETextFlag::CultureInvariant; - } + // Nothing to trim! + return InText; } - return NewText; + // Trim the string, preserving culture invariance if set + FString TrimmedString = CurrentString.Right(CurrentString.Len() - StartPos); + return InText.IsCultureInvariant() ? FText::AsCultureInvariant(MoveTemp(TrimmedString)) : FText::FromString(MoveTemp(TrimmedString)); } FText FText::TrimTrailing( const FText& InText ) { - FString TrimmedString = InText.ToString(); - { - int32 EndPos = TrimmedString.Len() - 1; - while( EndPos >= 0 ) - { - if( !FText::IsWhitespace( TrimmedString[EndPos] ) ) - { - break; - } + const FString& CurrentString = InText.ToString(); - EndPos--; + int32 EndPos = CurrentString.Len() - 1; + while (EndPos >= 0) + { + if (!FText::IsWhitespace(CurrentString[EndPos])) + { + break; } - TrimmedString = TrimmedString.Left( EndPos + 1 ); + --EndPos; } - FText NewText = FText( MoveTemp ( TrimmedString ) ); - - if (!GIsEditor) + if (EndPos == CurrentString.Len() - 1) { - if( (NewText.Flags & ETextFlag::CultureInvariant) != 0 ) - { - NewText.Flags |= ETextFlag::Transient; - } - else - { - NewText.Flags |= ETextFlag::CultureInvariant; - } + // Nothing to trim! + return InText; } - return NewText; + // Trim the string, preserving culture invariance if set + FString TrimmedString = CurrentString.Left(EndPos + 1); + return InText.IsCultureInvariant() ? FText::AsCultureInvariant(MoveTemp(TrimmedString)) : FText::FromString(MoveTemp(TrimmedString)); } FText FText::TrimPrecedingAndTrailing( const FText& InText ) { - FString TrimmedString = InText.ToString(); + const FString& CurrentString = InText.ToString(); + + int32 StartPos = 0; + while (StartPos < CurrentString.Len()) { - int32 StartPos = 0; - while ( StartPos < TrimmedString.Len() ) + if (!FText::IsWhitespace(CurrentString[StartPos])) { - if( !FText::IsWhitespace( TrimmedString[StartPos] ) ) - { - break; - } - - ++StartPos; + break; } - int32 EndPos = TrimmedString.Len(); - while( EndPos > StartPos ) - { - if( !FText::IsWhitespace( TrimmedString[EndPos - 1] ) ) - { - break; - } - - --EndPos; - } - - const int32 Len = EndPos - StartPos; - TrimmedString = TrimmedString.Mid( StartPos, Len ); + ++StartPos; } - FText NewText = FText( MoveTemp( TrimmedString ) ); - - if (!GIsEditor) + int32 EndPos = CurrentString.Len() - 1; + while (EndPos > StartPos) { - if( (NewText.Flags & ETextFlag::CultureInvariant) != 0 ) + if (!FText::IsWhitespace(CurrentString[EndPos])) { - NewText.Flags |= ETextFlag::Transient; - } - else - { - NewText.Flags |= ETextFlag::CultureInvariant; + break; } + + --EndPos; } - return NewText; + if (StartPos == 0 && EndPos == CurrentString.Len() - 1) + { + // Nothing to trim! + return InText; + } + + // Trim the string, preserving culture invariance if set + FString TrimmedString = CurrentString.Mid(StartPos, EndPos - StartPos + 1); + return InText.IsCultureInvariant() ? FText::AsCultureInvariant(MoveTemp(TrimmedString)) : FText::FromString(MoveTemp(TrimmedString)); } void FText::GetFormatPatternParameters(const FTextFormat& Fmt, TArray& ParameterNames) @@ -824,6 +796,8 @@ void FText::SerializeText(FStructuredArchive::FSlot Slot, FText& Value) FArchive& UnderlyingArchive = Slot.GetUnderlyingArchive(); FStructuredArchive::FRecord Record = Slot.EnterRecord(); + UnderlyingArchive.UsingCustomVersion(FEditorObjectVersion::GUID); + //When duplicating, the CDO is used as the template, then values for the instance are assigned. //If we don't duplicate the string, the CDO and the instance are both pointing at the same thing. //This would result in all subsequently duplicated objects stamping over formerly duplicated ones. @@ -900,12 +874,20 @@ void FText::SerializeText(FStructuredArchive::FSlot Slot, FText& Value) if (UnderlyingArchive.IsSaving()) { // Skip the history for empty texts - bSerializeHistory = !Value.IsEmpty(); + bSerializeHistory = !Value.IsEmpty() && !Value.IsCultureInvariant(); if (!bSerializeHistory) { int8 HistoryType = (int8)ETextHistoryType::None; Record << NAMED_FIELD(HistoryType); + + bool bHasCultureInvariantString = !Value.IsEmpty() && Value.IsCultureInvariant(); + Record << NAMED_FIELD(bHasCultureInvariantString); + if (bHasCultureInvariantString) + { + FString CultureInvariantString = Value.GetSourceString(); + Record << NAMED_FIELD(CultureInvariantString); + } } } else if (UnderlyingArchive.IsLoading()) @@ -986,6 +968,18 @@ void FText::SerializeText(FStructuredArchive::FSlot Slot, FText& Value) { bSerializeHistory = false; Value.TextData = FText::GetEmpty().TextData; + + if (UnderlyingArchive.CustomVer(FEditorObjectVersion::GUID) >= FEditorObjectVersion::CultureInvariantTextSerializationKeyStability) + { + bool bHasCultureInvariantString = false; + Record << NAMED_FIELD(bHasCultureInvariantString); + if (bHasCultureInvariantString) + { + FString CultureInvariantString; + Record << NAMED_FIELD(CultureInvariantString); + Value.TextData = FText(MoveTemp(CultureInvariantString)).TextData; + } + } } } } @@ -1038,7 +1032,7 @@ FText FText::FromName( const FName& Val) FText FText::FromString( const FString& String ) { - FText NewText = String.IsEmpty() ? FText::GetEmpty() : FText( FString(String) ); + FText NewText = String.IsEmpty() ? FText::GetEmpty() : FText(CopyTemp(String)); if (!GIsEditor) { @@ -1051,7 +1045,7 @@ FText FText::FromString( const FString& String ) FText FText::FromString( FString&& String ) { - FText NewText = String.IsEmpty() ? FText::GetEmpty() : FText( MoveTemp(String) ); + FText NewText = String.IsEmpty() ? FText::GetEmpty() : FText(MoveTemp(String)); if (!GIsEditor) { @@ -1064,7 +1058,7 @@ FText FText::FromString( FString&& String ) FText FText::AsCultureInvariant( const FString& String ) { - FText NewText = String.IsEmpty() ? FText::GetEmpty() : FText( FString(String) ); + FText NewText = String.IsEmpty() ? FText::GetEmpty() : FText(CopyTemp(String)); NewText.Flags |= ETextFlag::CultureInvariant; return NewText; @@ -1072,7 +1066,7 @@ FText FText::AsCultureInvariant( const FString& String ) FText FText::AsCultureInvariant( FString&& String ) { - FText NewText = String.IsEmpty() ? FText() : FText( MoveTemp(String) ); + FText NewText = String.IsEmpty() ? FText::GetEmpty() : FText(MoveTemp(String)); NewText.Flags |= ETextFlag::CultureInvariant; return NewText; @@ -1080,7 +1074,7 @@ FText FText::AsCultureInvariant( FString&& String ) FText FText::AsCultureInvariant( FText Text ) { - FText NewText = FText( MoveTemp(Text) ); + FText NewText = FText(MoveTemp(Text)); NewText.Flags |= ETextFlag::CultureInvariant; return NewText; diff --git a/Engine/Source/Runtime/Core/Private/Internationalization/TextData.h b/Engine/Source/Runtime/Core/Private/Internationalization/TextData.h index e55ae6e06b24..f122a0b3b77b 100644 --- a/Engine/Source/Runtime/Core/Private/Internationalization/TextData.h +++ b/Engine/Source/Runtime/Core/Private/Internationalization/TextData.h @@ -158,7 +158,7 @@ public: if (!this->LocalizedString.IsValid()) { // We copy (rather than move) DisplayString here, as other threads may currently be accessing it - this->LocalizedString = MakeShareable(new FString(DisplayString)); + this->LocalizedString = MakeShared(DisplayString); } } } diff --git a/Engine/Source/Runtime/Core/Private/Internationalization/TextHistory.cpp b/Engine/Source/Runtime/Core/Private/Internationalization/TextHistory.cpp index b366abc102d2..ccfc2045a583 100644 --- a/Engine/Source/Runtime/Core/Private/Internationalization/TextHistory.cpp +++ b/Engine/Source/Runtime/Core/Private/Internationalization/TextHistory.cpp @@ -836,34 +836,36 @@ void FTextHistory_Base::SerializeForDisplayString(FStructuredArchive::FRecord Re TextNamespaceUtil::StripPackageNamespaceInline(Namespace); } else -#if USE_STABLE_LOCALIZATION_KEYS - // Make sure the package namespace for this text property is up-to-date - if (GIsEditor && !BaseArchive.HasAnyPortFlags(PPF_DuplicateVerbatim | PPF_DuplicateForPIE)) { - const FString PackageNamespace = TextNamespaceUtil::GetPackageNamespace(BaseArchive); - if (!PackageNamespace.IsEmpty()) +#if USE_STABLE_LOCALIZATION_KEYS + // Make sure the package namespace for this text property is up-to-date + if (GIsEditor && !BaseArchive.HasAnyPortFlags(PPF_DuplicateVerbatim | PPF_DuplicateForPIE)) { - const FString FullNamespace = TextNamespaceUtil::BuildFullNamespace(Namespace, PackageNamespace); - if (!Namespace.Equals(FullNamespace, ESearchCase::CaseSensitive)) + const FString PackageNamespace = TextNamespaceUtil::GetPackageNamespace(BaseArchive); + if (!PackageNamespace.IsEmpty()) { - // We may assign a new key when saving if we don't have the correct package namespace in order to avoid identity conflicts when instancing (which duplicates without any special flags) - // This can happen if an asset was duplicated (and keeps the same keys) but later both assets are instanced into the same world (causing them to both take the worlds package id, and conflict with each other) - Namespace = FullNamespace; - Key = FGuid::NewGuid().ToString(); + const FString FullNamespace = TextNamespaceUtil::BuildFullNamespace(Namespace, PackageNamespace); + if (!Namespace.Equals(FullNamespace, ESearchCase::CaseSensitive)) + { + // We may assign a new key when saving if we don't have the correct package namespace in order to avoid identity conflicts when instancing (which duplicates without any special flags) + // This can happen if an asset was duplicated (and keeps the same keys) but later both assets are instanced into the same world (causing them to both take the worlds package id, and conflict with each other) + Namespace = FullNamespace; + Key = FGuid::NewGuid().ToString(); + } } } - } #endif // USE_STABLE_LOCALIZATION_KEYS - // If this has no key, give it a GUID for a key - if (!bFoundNamespaceAndKey && GIsEditor && (BaseArchive.IsPersistent() && !BaseArchive.HasAnyPortFlags(PPF_Duplicate))) - { - Key = FGuid::NewGuid().ToString(); - if (!FTextLocalizationManager::Get().AddDisplayString(InOutDisplayString.ToSharedRef(), Namespace, Key)) + // If this has no key, give it a GUID for a key + if (!bFoundNamespaceAndKey && GIsEditor && (BaseArchive.IsPersistent() && !BaseArchive.HasAnyPortFlags(PPF_Duplicate))) { - // Could not add display string, reset namespace and key. - Namespace.Empty(); - Key.Empty(); + Key = FGuid::NewGuid().ToString(); + if (!FTextLocalizationManager::Get().AddDisplayString(InOutDisplayString.ToSharedRef(), Namespace, Key)) + { + // Could not add display string, reset namespace and key. + Namespace.Empty(); + Key.Empty(); + } } } @@ -2577,7 +2579,7 @@ void FTextHistory_StringTableEntry::FStringTableReferenceData::Initialize(uint16 LoadingPhase = EStringTableLoadingPhase::Loaded; ResolveStringTableEntry(); } - else if (InLoadingPolicy == EStringTableLoadingPolicy::FindOrFullyLoad && IsInGameThread()) + else if (InLoadingPolicy == EStringTableLoadingPolicy::FindOrFullyLoad && IStringTableEngineBridge::CanFindOrLoadStringTableAsset()) { // Forced synchronous load LoadingPhase = EStringTableLoadingPhase::Loaded; @@ -2667,7 +2669,7 @@ FStringTableEntryConstPtr FTextHistory_StringTableEntry::FStringTableReferenceDa void FTextHistory_StringTableEntry::FStringTableReferenceData::ConditionalBeginAssetLoad() { - if (!IsInGameThread()) + if (!IStringTableEngineBridge::CanFindOrLoadStringTableAsset()) { return; } diff --git a/Engine/Source/Runtime/Core/Private/Logging/LogCategory.cpp b/Engine/Source/Runtime/Core/Private/Logging/LogCategory.cpp index 57a81d22d4dc..9bd1af031b8f 100644 --- a/Engine/Source/Runtime/Core/Private/Logging/LogCategory.cpp +++ b/Engine/Source/Runtime/Core/Private/Logging/LogCategory.cpp @@ -10,6 +10,7 @@ FLogCategoryBase::FLogCategoryBase(const TCHAR *CategoryName, ELogVerbosity::Typ , CompileTimeVerbosity(InCompileTimeVerbosity) , CategoryFName(CategoryName) { + TRACE_LOG_CATEGORY(this, CategoryName, InDefaultVerbosity); ResetFromDefault(); if (CompileTimeVerbosity > ELogVerbosity::NoLogging) { diff --git a/Engine/Source/Runtime/Core/Private/Logging/LogTrace.cpp b/Engine/Source/Runtime/Core/Private/Logging/LogTrace.cpp new file mode 100644 index 000000000000..a39fe151c7d3 --- /dev/null +++ b/Engine/Source/Runtime/Core/Private/Logging/LogTrace.cpp @@ -0,0 +1,63 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +#include "Logging/LogTrace.h" + +#if LOGTRACE_ENABLED + +#include "Trace/Trace.h" +#include "Templates/Function.h" +#include "HAL/PlatformTime.h" +#include "HAL/PlatformTLS.h" + +UE_TRACE_EVENT_BEGIN(Logging, LogCategory, Always|Important) + UE_TRACE_EVENT_FIELD(const void*, CategoryPointer) + UE_TRACE_EVENT_FIELD(uint8, DefaultVerbosity) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(Logging, LogMessageSpec, Always|Important) + UE_TRACE_EVENT_FIELD(const void*, LogPoint) + UE_TRACE_EVENT_FIELD(const void*, CategoryPointer) + UE_TRACE_EVENT_FIELD(int32, Line) + UE_TRACE_EVENT_FIELD(uint8, Verbosity) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(Logging, LogMessage, Always) + UE_TRACE_EVENT_FIELD(const void*, LogPoint) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +void FLogTrace::OutputLogCategory(const FLogCategoryBase* Category, const TCHAR* Name, ELogVerbosity::Type DefaultVerbosity) +{ + uint16 NameSize = (FCString::Strlen(Name) + 1) * sizeof(TCHAR); + UE_TRACE_LOG(Logging, LogCategory, NameSize) + << LogCategory.CategoryPointer(Category) + << LogCategory.DefaultVerbosity(DefaultVerbosity) + << LogCategory.Attachment(Name, NameSize); +} + +void FLogTrace::OutputLogMessageSpec(const void* LogPoint, const FLogCategoryBase* Category, ELogVerbosity::Type Verbosity, const ANSICHAR* File, int32 Line, const TCHAR* Format) +{ + uint16 FileNameSize = strlen(File) + 1; + uint16 FormatStringSize = (FCString::Strlen(Format) + 1) * sizeof(TCHAR); + auto StringCopyFunc = [FileNameSize, FormatStringSize, File, Format](uint8* Out) { + memcpy(Out, File, FileNameSize); + memcpy(Out + FileNameSize, Format, FormatStringSize); + }; + UE_TRACE_LOG(Logging, LogMessageSpec, FileNameSize + FormatStringSize) + << LogMessageSpec.LogPoint(LogPoint) + << LogMessageSpec.CategoryPointer(Category) + << LogMessageSpec.Line(Line) + << LogMessageSpec.Verbosity(Verbosity) + << LogMessageSpec.Attachment(StringCopyFunc); +} + +void FLogTrace::OutputLogMessageInternal(const void* LogPoint, uint16 EncodedFormatArgsSize, uint8* EncodedFormatArgs) +{ + UE_TRACE_LOG(Logging, LogMessage, EncodedFormatArgsSize) + << LogMessage.LogPoint(LogPoint) + << LogMessage.Cycle(FPlatformTime::Cycles64()) + << LogMessage.ThreadId(FPlatformTLS::GetCurrentThreadId()) + << LogMessage.Attachment(EncodedFormatArgs, EncodedFormatArgsSize); +} + +#endif diff --git a/Engine/Source/Runtime/Core/Private/Mac/MacPlatformMisc.cpp b/Engine/Source/Runtime/Core/Private/Mac/MacPlatformMisc.cpp index df83e2a93fe3..bb13171e620e 100644 --- a/Engine/Source/Runtime/Core/Private/Mac/MacPlatformMisc.cpp +++ b/Engine/Source/Runtime/Core/Private/Mac/MacPlatformMisc.cpp @@ -81,7 +81,7 @@ static TAutoConsoleVariable CVarMacPlatformDumpAllThreadsOnHang( FMacApplicationInfo - class to contain all state for crash reporting that is unsafe to acquire in a signal. ------------------------------------------------------------------------------*/ -FMacMallocCrashHandler* GCrashMalloc = nullptr; + CORE_API FMacMallocCrashHandler* GCrashMalloc = nullptr; /** * Information that cannot be obtained during a signal-handler is initialised here. @@ -1882,6 +1882,12 @@ void FMacCrashContext::GenerateCrashInfoAndLaunchReporter() const // Prevent CrashReportClient from spawning another CrashReportClient. bool bCanRunCrashReportClient = FCString::Stristr( *(GMacAppInfo.ExecutableName), TEXT( "CrashReportClient" ) ) == nullptr; + bool bImplicitSend = false; + if (GConfig) + { + GConfig->GetBool(TEXT("CrashReportClient"), TEXT("bImplicitSend"), bImplicitSend, GEngineIni); + } + bool bSendUnattendedBugReports = true; if (GConfig) { @@ -1932,7 +1938,11 @@ void FMacCrashContext::GenerateCrashInfoAndLaunchReporter() const if(ForkPID == 0) { // Child - if(GMacAppInfo.bIsUnattended) + if (bImplicitSend) + { + execl(GMacAppInfo.CrashReportClient, "CrashReportClient", CrashInfoFolder, "-Unattended", "-ImplicitSend", NULL); + } + else if(GMacAppInfo.bIsUnattended) { execl(GMacAppInfo.CrashReportClient, "CrashReportClient", CrashInfoFolder, "-Unattended", NULL); } @@ -1949,6 +1959,7 @@ void FMacCrashContext::GenerateCrashInfoAndLaunchReporter() const } } } + // We no longer wait here because on return the OS will scribble & crash again due to the behaviour of the XPC function // macOS uses internally to launch/wait on the CrashReportClient. It is simpler, easier & safer to just die here like a good little Mac.app. } diff --git a/Engine/Source/Runtime/Core/Private/Math/UnrealMath.cpp b/Engine/Source/Runtime/Core/Private/Math/UnrealMath.cpp index ce9958c8f6e2..77fdff4e042c 100644 --- a/Engine/Source/Runtime/Core/Private/Math/UnrealMath.cpp +++ b/Engine/Source/Runtime/Core/Private/Math/UnrealMath.cpp @@ -2786,42 +2786,37 @@ void FVector::GenerateClusterCenters(TArray& Clusters, const TArray(FloorToFloat(((bIsNegative) ? -F : F))) % 2 == 0; @@ -2839,7 +2834,7 @@ float FMath::RoundHalfToEven(float F) double FMath::RoundHalfToEven(double F) { - F = MathRoundingUtil::TruncateToHalfIfClose(F); + F = FMath::TruncateToHalfIfClose(F); const bool bIsNegative = F < 0.0; const bool bValueIsEven = static_cast(FMath::FloorToDouble(((bIsNegative) ? -F : F))) % 2 == 0; @@ -2857,26 +2852,62 @@ double FMath::RoundHalfToEven(double F) float FMath::RoundHalfFromZero(float F) { - F = MathRoundingUtil::TruncateToHalfIfClose(F); - return (F < 0.0f) ? CeilToFloat(F - 0.5f) : FloorToFloat(F + 0.5f); + float ValueToFudgeIntegralPart = 0.0f; + float ValueToFudgeFractionalPart = FMath::Modf(F, &ValueToFudgeIntegralPart); + + if (F < 0.0f) + { + return ValueToFudgeFractionalPart > -0.5f ? ValueToFudgeIntegralPart : ValueToFudgeIntegralPart - 1.0f; + } + else + { + return ValueToFudgeFractionalPart < 0.5f ? ValueToFudgeIntegralPart : ValueToFudgeIntegralPart + 1.0f; + } } double FMath::RoundHalfFromZero(double F) { - F = MathRoundingUtil::TruncateToHalfIfClose(F); - return (F < 0.0) ? CeilToDouble(F - 0.5) : FloorToDouble(F + 0.5); + double ValueToFudgeIntegralPart = 0.0; + double ValueToFudgeFractionalPart = FMath::Modf(F, &ValueToFudgeIntegralPart); + + if (F < 0.0) + { + return ValueToFudgeFractionalPart > -0.5 ? ValueToFudgeIntegralPart : ValueToFudgeIntegralPart - 1.0; + } + else + { + return ValueToFudgeFractionalPart < 0.5 ? ValueToFudgeIntegralPart : ValueToFudgeIntegralPart + 1.0f; + } } float FMath::RoundHalfToZero(float F) { - F = MathRoundingUtil::TruncateToHalfIfClose(F); - return (F < 0.0f) ? FloorToFloat(F + 0.5f) : CeilToFloat(F - 0.5f); + float ValueToFudgeIntegralPart = 0.0f; + float ValueToFudgeFractionalPart = FMath::Modf(F, &ValueToFudgeIntegralPart); + + if (F < 0.0f) + { + return ValueToFudgeFractionalPart < -0.5f ? ValueToFudgeIntegralPart - 1.0f : ValueToFudgeIntegralPart; + } + else + { + return ValueToFudgeFractionalPart > 0.5f ? ValueToFudgeIntegralPart + 1.0f : ValueToFudgeIntegralPart; + } } double FMath::RoundHalfToZero(double F) { - F = MathRoundingUtil::TruncateToHalfIfClose(F); - return (F < 0.0) ? FloorToDouble(F + 0.5) : CeilToDouble(F - 0.5); + double ValueToFudgeIntegralPart = 0.0; + double ValueToFudgeFractionalPart = FMath::Modf(F, &ValueToFudgeIntegralPart); + + if (F < 0.0) + { + return ValueToFudgeFractionalPart < -0.5 ? ValueToFudgeIntegralPart - 1.0 : ValueToFudgeIntegralPart; + } + else + { + return ValueToFudgeFractionalPart > 0.5 ? ValueToFudgeIntegralPart + 1.0 : ValueToFudgeIntegralPart; + } } FString FMath::FormatIntToHumanReadable(int32 Val) diff --git a/Engine/Source/Runtime/Core/Private/Misc/AssertionMacros.cpp b/Engine/Source/Runtime/Core/Private/Misc/AssertionMacros.cpp index 35fac927f399..73ec8ee81b41 100644 --- a/Engine/Source/Runtime/Core/Private/Misc/AssertionMacros.cpp +++ b/Engine/Source/Runtime/Core/Private/Misc/AssertionMacros.cpp @@ -80,6 +80,27 @@ void PrintScriptCallstack() } } +static void AssertFailedImplV(const ANSICHAR* Expr, const ANSICHAR* File, int32 Line, const TCHAR* Format, va_list Args) +{ + if (GIsCriticalError) + { + return; + } + + // This is not perfect because another thread might crash and be handled before this assert + // but this static varible will report the crash as an assert. Given complexity of a thread + // aware solution, this should be good enough. If crash reports are obviously wrong we can + // look into fixing this. + bHasAsserted = true; + + TCHAR DescriptionString[4096]; + FCString::GetVarArgs(DescriptionString, ARRAY_COUNT(DescriptionString), Format, Args); + + TCHAR ErrorString[MAX_SPRINTF]; + FCString::Sprintf(ErrorString, TEXT("%s"), ANSI_TO_TCHAR(Expr)); + GError->Logf(TEXT("Assertion failed: %s") FILE_LINE_DESC TEXT("\n%s\n"), ErrorString, ANSI_TO_TCHAR(File), Line, DescriptionString); +} + /** * Prints error to the debug output, * prompts for the remote debugging if there is not debugger, breaks into the debugger @@ -418,7 +439,7 @@ void FORCENOINLINE FDebug::CheckVerifyFailedImpl( if (!FPlatformMisc::IsDebuggerPresent()) { FPlatformMisc::PromptForRemoteDebugging(false); - FDebug::AssertFailed(Expr, File, Line, Format, Args); + AssertFailedImplV(Expr, File, Line, Format, Args); } va_end(Args); } @@ -427,23 +448,10 @@ void FORCENOINLINE FDebug::CheckVerifyFailedImpl( void VARARGS FDebug::AssertFailed(const ANSICHAR* Expr, const ANSICHAR* File, int32 Line, const TCHAR* Format/* = TEXT("")*/, ...) { - if (GIsCriticalError) - { - return; - } - - // This is not perfect because another thread might crash and be handled before this assert - // but this static varible will report the crash as an assert. Given complexity of a thread - // aware solution, this should be good enough. If crash reports are obviously wrong we can - // look into fixing this. - bHasAsserted = true; - - TCHAR DescriptionString[4096]; - GET_VARARGS(DescriptionString, ARRAY_COUNT(DescriptionString), ARRAY_COUNT(DescriptionString) - 1, Format, Format); - - TCHAR ErrorString[MAX_SPRINTF]; - FCString::Sprintf(ErrorString, TEXT("%s"), ANSI_TO_TCHAR(Expr)); - GError->Logf(TEXT("Assertion failed: %s") FILE_LINE_DESC TEXT("\n%s\n"), ErrorString, ANSI_TO_TCHAR(File), Line, DescriptionString); + va_list Args; + va_start(Args, Format); + AssertFailedImplV(Expr, File, Line, Format, Args); + va_end(Args); } #if DO_CHECK || DO_GUARD_SLOW @@ -472,7 +480,12 @@ FORCENOINLINE void VARARGS LowLevelFatalErrorHandler(const ANSICHAR* File, int32 StaticFailDebug(TEXT("LowLevelFatalError"), File, Line, DescriptionString, false, NumStackFramesToIgnore); } -FORCENOINLINE void FDebug::DumpStackTraceToLog() +void FDebug::DumpStackTraceToLog() +{ + DumpStackTraceToLog(TEXT("=== FDebug::DumpStackTrace(): ===")); +} + +FORCENOINLINE void FDebug::DumpStackTraceToLog(const TCHAR* Heading) { #if !NO_LOGGING // Walk the stack and dump it to the allocated memory. @@ -492,7 +505,7 @@ FORCENOINLINE void FDebug::DumpStackTraceToLog() // Dump the error and flush the log. // ELogVerbosity::Error to make sure it gets printed in log for conveniency. - FDebug::LogFormattedMessageWithCallstack(LogOutputDevice.GetCategoryName(), __FILE__, __LINE__, TEXT("=== FDebug::DumpStackTrace(): ==="), ANSI_TO_TCHAR(StackTrace), ELogVerbosity::Error); + FDebug::LogFormattedMessageWithCallstack(LogOutputDevice.GetCategoryName(), __FILE__, __LINE__, Heading, ANSI_TO_TCHAR(StackTrace), ELogVerbosity::Error); GLog->Flush(); FMemory::SystemFree(StackTrace); #endif diff --git a/Engine/Source/Runtime/Core/Private/Misc/AutomationTest.cpp b/Engine/Source/Runtime/Core/Private/Misc/AutomationTest.cpp index 94c037e141d4..37e951aeea0b 100644 --- a/Engine/Source/Runtime/Core/Private/Misc/AutomationTest.cpp +++ b/Engine/Source/Runtime/Core/Private/Misc/AutomationTest.cpp @@ -12,7 +12,7 @@ #include "Modules/ModuleManager.h" #include "Misc/OutputDeviceRedirector.h" #include "Internationalization/Regex.h" - +#include DEFINE_LOG_CATEGORY_STATIC(LogAutomationTest, Warning, All); @@ -1066,6 +1066,15 @@ void FAutomationTestBase::TestEqual(const TCHAR* What, const int32 Actual, const } } + +void FAutomationTestBase::TestEqual(const TCHAR* What, const int64 Actual, const int64 Expected) +{ + if (Actual != Expected) + { + AddError(FString::Printf(TEXT("Expected '%s' to be %" PRId64 ", but it was %" PRId64 "."), What, Expected, Actual), 1); + } +} + void FAutomationTestBase::TestEqual(const TCHAR* What, const float Actual, const float Expected, float Tolerance) { if (!FMath::IsNearlyEqual(Actual, Expected, Tolerance)) @@ -1074,6 +1083,14 @@ void FAutomationTestBase::TestEqual(const TCHAR* What, const float Actual, const } } +void FAutomationTestBase::TestEqual(const TCHAR* What, const double Actual, const double Expected, double Tolerance) +{ + if (!FMath::IsNearlyEqual(Actual, Expected, Tolerance)) + { + AddError(FString::Printf(TEXT("Expected '%s' to be %f, but it was %f within tolerance %f."), What, Expected, Actual, Tolerance), 1); + } +} + void FAutomationTestBase::TestEqual(const TCHAR* What, const FVector Actual, const FVector Expected, float Tolerance) { if (!Expected.Equals(Actual, Tolerance)) @@ -1144,4 +1161,4 @@ bool FAutomationTestBase::IsExpectedError(const FString& Error) } return false; -} \ No newline at end of file +} diff --git a/Engine/Source/Runtime/Core/Private/Misc/CStringTest.cpp b/Engine/Source/Runtime/Core/Private/Misc/CStringTest.cpp new file mode 100644 index 000000000000..fef4802f3175 --- /dev/null +++ b/Engine/Source/Runtime/Core/Private/Misc/CStringTest.cpp @@ -0,0 +1,96 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "CoreTypes.h" +#include "Misc/AutomationTest.h" +#include "Misc/AssertionMacros.h" +#include "Misc/CString.h" + +#if WITH_DEV_AUTOMATION_TESTS + +// This class is a workaround for clang compilers causing error "'va_start' used in function with fixed args" when using it in a lambda in RunTest() +class FCStringGetVarArgsTestBase : public FAutomationTestBase +{ +public: + FCStringGetVarArgsTestBase(const FString& InName, const bool bInComplexTask) + : FAutomationTestBase(InName, bInComplexTask) + { + } + +protected: + void DoTest(const TCHAR* ExpectedOutput, const TCHAR* Format, ...) + { + constexpr SIZE_T OutputBufferCharacterCount = 512; + TCHAR OutputBuffer[OutputBufferCharacterCount]; + va_list ArgPtr; + va_start(ArgPtr, Format); + const int32 Result = FCString::GetVarArgs(OutputBuffer, OutputBufferCharacterCount, Format, ArgPtr); + va_end(ArgPtr); + + if (Result < 0) + { + this->AddError(FString::Printf(TEXT("'%s' could not be parsed."), Format)); + return; + } + + if (FCString::Strcmp(OutputBuffer, ExpectedOutput) != 0) + { + this->AddError(FString::Printf(TEXT("'%s' resulted in '%s', expected '%s'."), Format, OutputBuffer, ExpectedOutput)); + return; + } + } +}; + +IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(FCStringGetVarArgsTest, FCStringGetVarArgsTestBase, "System.Core.Misc.CString.GetVarArgs", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::EngineFilter) +bool FCStringGetVarArgsTest::RunTest(const FString& Parameters) +{ +#if PLATFORM_64BITS + DoTest(TEXT("SIZE_T_FMT |18446744073709551615|"), TEXT("SIZE_T_FMT |%" SIZE_T_FMT "|"), SIZE_T(MAX_uint64)); + DoTest(TEXT("SIZE_T_x_FMT |ffffffffffffffff|"), TEXT("SIZE_T_x_FMT |%" SIZE_T_x_FMT "|"), UPTRINT(MAX_uint64)); + DoTest(TEXT("SIZE_T_X_FMT |FFFFFFFFFFFFFFFF|"), TEXT("SIZE_T_X_FMT |%" SIZE_T_X_FMT "|"), UPTRINT(MAX_uint64)); + + DoTest(TEXT("SSIZE_T_FMT |-9223372036854775808|"), TEXT("SSIZE_T_FMT |%" SSIZE_T_FMT "|"), SSIZE_T(MIN_int64)); + DoTest(TEXT("SSIZE_T_x_FMT |ffffffffffffffff|"), TEXT("SSIZE_T_x_FMT |%" SSIZE_T_x_FMT "|"), SSIZE_T(-1)); + DoTest(TEXT("SSIZE_T_X_FMT |FFFFFFFFFFFFFFFF|"), TEXT("SSIZE_T_X_FMT |%" SSIZE_T_X_FMT "|"), SSIZE_T(-1)); + + DoTest(TEXT("PTRINT_FMT |-9223372036854775808|"), TEXT("PTRINT_FMT |%" PTRINT_FMT "|"), PTRINT(MIN_int64)); + DoTest(TEXT("PTRINT_x_FMT |ffffffffffffffff|"), TEXT("PTRINT_x_FMT |%" PTRINT_x_FMT "|"), PTRINT(-1)); + DoTest(TEXT("PTRINT_X_FMT |FFFFFFFFFFFFFFFF|"), TEXT("PTRINT_X_FMT |%" PTRINT_X_FMT "|"), PTRINT(-1)); + + DoTest(TEXT("UPTRINT_FMT |18446744073709551615|"), TEXT("UPTRINT_FMT |%" UPTRINT_FMT "|"), UPTRINT(MAX_uint64)); + DoTest(TEXT("UPTRINT_x_FMT |ffffffffffffffff|"), TEXT("UPTRINT_x_FMT |%" UPTRINT_x_FMT "|"), UPTRINT(MAX_uint64)); + DoTest(TEXT("UPTRINT_X_FMT |FFFFFFFFFFFFFFFF|"), TEXT("UPTRINT_X_FMT |%" UPTRINT_X_FMT "|"), UPTRINT(MAX_uint64)); +#else + DoTest(TEXT("SIZE_T_FMT |4294967295|"), TEXT("SIZE_T_FMT |%" SIZE_T_FMT "|"), SIZE_T(MAX_uint32)); + DoTest(TEXT("SIZE_T_x_FMT |ffffffff|"), TEXT("SIZE_T_x_FMT |%" SIZE_T_x_FMT "|"), UPTRINT(MAX_uint32)); + DoTest(TEXT("SIZE_T_X_FMT |FFFFFFFF|"), TEXT("SIZE_T_X_FMT |%" SIZE_T_X_FMT "|"), UPTRINT(MAX_uint32)); + + DoTest(TEXT("SSIZE_T_FMT |-2147483648|"), TEXT("SSIZE_T_FMT |%" SSIZE_T_FMT "|"), SSIZE_T(MIN_int32)); + DoTest(TEXT("SSIZE_T_x_FMT |ffffffff|"), TEXT("SSIZE_T_x_FMT |%" SSIZE_T_x_FMT "|"), SSIZE_T(-1)); + DoTest(TEXT("SSIZE_T_X_FMT |FFFFFFFF|"), TEXT("SSIZE_T_X_FMT |%" SSIZE_T_X_FMT "|"), SSIZE_T(-1)); + + DoTest(TEXT("PTRINT_FMT |-2147483648|"), TEXT("PTRINT_FMT |%" PTRINT_FMT "|"), PTRINT(MIN_int32)); + DoTest(TEXT("PTRINT_x_FMT |ffffffff|"), TEXT("PTRINT_x_FMT |%" PTRINT_x_FMT "|"), PTRINT(-1)); + DoTest(TEXT("PTRINT_X_FMT |FFFFFFFF|"), TEXT("PTRINT_X_FMT |%" PTRINT_X_FMT "|"), PTRINT(-1)); + + DoTest(TEXT("UPTRINT_FMT |4294967295|"), TEXT("UPTRINT_FMT |%" UPTRINT_FMT "|"), UPTRINT(MAX_uint32)); + DoTest(TEXT("UPTRINT_x_FMT |ffffffff|"), TEXT("UPTRINT_x_FMT |%" UPTRINT_x_FMT "|"), UPTRINT(MAX_uint32)); + DoTest(TEXT("UPTRINT_X_FMT |FFFFFFFF|"), TEXT("UPTRINT_X_FMT |%" UPTRINT_X_FMT "|"), UPTRINT(MAX_uint32)); +#endif + + DoTest(TEXT("INT64_FMT |-9223372036854775808|"), TEXT("INT64_FMT |%" INT64_FMT "|"), MIN_int64); + DoTest(TEXT("INT64_x_FMT |ffffffffffffffff|"), TEXT("INT64_x_FMT |%" INT64_x_FMT "|"), int64(-1)); + DoTest(TEXT("INT64_X_FMT |FFFFFFFFFFFFFFFF|"), TEXT("INT64_X_FMT |%" INT64_X_FMT "|"), int64(-1)); + + DoTest(TEXT("UINT64_FMT |18446744073709551615|"), TEXT("UINT64_FMT |%" UINT64_FMT "|"), MAX_uint64); + DoTest(TEXT("UINT64_x_FMT |ffffffffffffffff|"), TEXT("UINT64_x_FMT |%" UINT64_x_FMT "|"), MAX_uint64); + DoTest(TEXT("UINT64_X_FMT |FFFFFFFFFFFFFFFF|"), TEXT("UINT64_X_FMT |%" UINT64_X_FMT "|"), MAX_uint64); + + DoTest(TEXT("|LEFT | RIGHT| 33.33|66.67 |"), TEXT("|%-20s|%20s|%10.2f|%-10.2f|"), TEXT("LEFT"), TEXT("RIGHT"), 33.333333, 66.666666); + + DoTest(TEXT("Percents|%%%3|"), TEXT("Percents|%%%%%%%d|"), 3); + + DoTest(TEXT("Integer arguments|12345|54321|123ABC|f|99|"), TEXT("Integer arguments|%d|%i|%X|%x|%u|"), 12345, 54321, 0x123AbC, 15, 99); + return true; +} + +#endif // WITH_DEV_AUTOMATION_TESTS diff --git a/Engine/Source/Runtime/Core/Private/Misc/CoreGlobals.cpp b/Engine/Source/Runtime/Core/Private/Misc/CoreGlobals.cpp index d370ab0567ca..c57f4968f2d6 100644 --- a/Engine/Source/Runtime/Core/Private/Misc/CoreGlobals.cpp +++ b/Engine/Source/Runtime/Core/Private/Misc/CoreGlobals.cpp @@ -181,7 +181,7 @@ bool GExitPurge = false; FChunkedFixedUObjectArray* GCoreObjectArrayForDebugVisualizers = nullptr; #if PLATFORM_UNIX -FNameEntry*** CORE_API GFNameTableForDebuggerVisualizers_MT = FName::GetNameTableForDebuggerVisualizers_MT(); +uint8** CORE_API GNameBlocksDebug = FNameDebugVisualizer::GetBlocks(); FChunkedFixedUObjectArray*& CORE_API GObjectArrayForDebugVisualizers = GCoreObjectArrayForDebugVisualizers; #endif @@ -362,6 +362,7 @@ FScopedBootTiming::~FScopedBootTiming() } void BootTimingPoint(const ANSICHAR *Message) { + TRACE_BOOKMARK(TEXT("%s"), *FString(Message)); } void DumpBootTiming() { @@ -398,6 +399,8 @@ void DumpBootTiming() static void BootTimingPoint(const TCHAR *Message, const TCHAR *Prefix = nullptr, int32 Depth = 0, double TookTime = 0.0) { + TRACE_BOOKMARK(TEXT("%s"), Message); + static double LastTime = 0.0; static TArray MessageStack; static FString LastGapMessage; diff --git a/Engine/Source/Runtime/Core/Private/Misc/Fnv.cpp b/Engine/Source/Runtime/Core/Private/Misc/Fnv.cpp index 86c9b34ec52e..ba7eb601a42e 100644 --- a/Engine/Source/Runtime/Core/Private/Misc/Fnv.cpp +++ b/Engine/Source/Runtime/Core/Private/Misc/Fnv.cpp @@ -22,7 +22,7 @@ uint32 FFnv::MemFnv32( const void* InData, int32 Length, uint32 InOffset ) return Fnv; } -// generate a 32-bit hash from data in memory of length bytes +// generate a 64-bit hash from data in memory of length bytes uint64 FFnv::MemFnv64( const void* InData, int32 Length, uint64 InOffset ) { // constants from above reference diff --git a/Engine/Source/Runtime/Core/Private/Misc/SecureHash.cpp b/Engine/Source/Runtime/Core/Private/Misc/SecureHash.cpp index e067f45b0530..8a6ea68ba10a 100644 --- a/Engine/Source/Runtime/Core/Private/Misc/SecureHash.cpp +++ b/Engine/Source/Runtime/Core/Private/Misc/SecureHash.cpp @@ -605,11 +605,6 @@ FArchive& operator<<( FArchive& Ar, FSHAHash& G ) return Ar; } -uint32 GetTypeHash(FSHAHash const& InKey) -{ - return FCrc::MemCrc32(InKey.Hash, sizeof(InKey.Hash)); -} - FString LexToString(const FSHAHash& InHash) { return InHash.ToString(); @@ -832,12 +827,12 @@ void FSHA1::HMACBuffer(const void* Key, uint32 KeySize, const void* Data, uint64 * Shared hashes.sha reading code (each platform gets a buffer to the data, * then passes it to this function for processing) */ -void FSHA1::InitializeFileHashesFromBuffer(uint8* Buffer, int32 BufferSize, bool bDuplicateKeyMemory) +void FSHA1::InitializeFileHashesFromBuffer(uint8* Buffer, uint64 BufferSize, bool bDuplicateKeyMemory) { // the start of the file is full file hashes bool bIsDoingFullFileHashes = true; // if it exists, parse it - int32 Offset = 0; + uint64 Offset = 0; while (Offset < BufferSize) { // format is null terminated string followed by hash diff --git a/Engine/Source/Runtime/Core/Private/Modules/ModuleManager.cpp b/Engine/Source/Runtime/Core/Private/Modules/ModuleManager.cpp index 1329e3d8fe02..4d85b045bacc 100644 --- a/Engine/Source/Runtime/Core/Private/Modules/ModuleManager.cpp +++ b/Engine/Source/Runtime/Core/Private/Modules/ModuleManager.cpp @@ -681,6 +681,8 @@ void FModuleManager::UnloadModulesAtShutdown() { ensure(IsInGameThread()); + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("UnloadModulesAtShutdown")); + struct FModulePair { FName ModuleName; diff --git a/Engine/Source/Runtime/Core/Private/ProfilingDebugging/CpuProfilerTrace.cpp b/Engine/Source/Runtime/Core/Private/ProfilingDebugging/CpuProfilerTrace.cpp new file mode 100644 index 000000000000..c37c25b265b0 --- /dev/null +++ b/Engine/Source/Runtime/Core/Private/ProfilingDebugging/CpuProfilerTrace.cpp @@ -0,0 +1,157 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +#include "ProfilingDebugging/CpuProfilerTrace.h" +#include "Trace/Trace.h" +#include "HAL/PlatformTime.h" +#include "HAL/PlatformTLS.h" +#include "ProfilingDebugging/MiscTrace.h" + +#if CPUPROFILERTRACE_ENABLED + +UE_TRACE_EVENT_BEGIN(CpuProfiler, EventSpec, Always) + UE_TRACE_EVENT_FIELD(uint16, Id) + UE_TRACE_EVENT_FIELD(uint16, Group) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(CpuProfiler, EventBatch) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(CpuProfiler, EndCapture, Always) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +struct FCpuProfilerTraceInternal +{ +public: + enum + { + MaxBufferSize = 255, + MaxEncodedEventSize = 12, // 9 + 3 + FullBufferThreshold = MaxBufferSize - MaxEncodedEventSize, + }; + + struct FThreadState + { + uint64 LastCycle = 0; + uint32 ThreadId; + uint16 Depth = 0; + uint16 BufferSize = 0; + bool bEnabled = false; + uint8 Buffer[MaxBufferSize]; + }; + + FORCENOINLINE static FThreadState* InitThreadState(); + FORCENOINLINE static void FlushThreadBuffer(FThreadState* ThreadState); + FORCENOINLINE static void EndCapture(FThreadState* ThreadState); + + static thread_local FThreadState* ThreadLocalThreadState; +}; + +thread_local FCpuProfilerTraceInternal::FThreadState* FCpuProfilerTraceInternal::ThreadLocalThreadState = nullptr; + +FCpuProfilerTraceInternal::FThreadState* FCpuProfilerTraceInternal::InitThreadState() +{ + ThreadLocalThreadState = new FThreadState(); + ThreadLocalThreadState->ThreadId = FPlatformTLS::GetCurrentThreadId(); + return ThreadLocalThreadState; +} + +void FCpuProfilerTraceInternal::FlushThreadBuffer(FThreadState* ThreadState) +{ + UE_TRACE_LOG(CpuProfiler, EventBatch, ThreadState->BufferSize) + << EventBatch.ThreadId(ThreadState->ThreadId) + << EventBatch.Attachment(ThreadState->Buffer, ThreadState->BufferSize); + ThreadState->BufferSize = 0; +} + +void FCpuProfilerTraceInternal::EndCapture(FThreadState* ThreadState) +{ + UE_TRACE_LOG(CpuProfiler, EndCapture, ThreadState->BufferSize) + << EndCapture.ThreadId(ThreadState->ThreadId) + << EndCapture.Attachment(ThreadState->Buffer, ThreadState->BufferSize); + ThreadState->BufferSize = 0; +} + +void FCpuProfilerTrace::OutputBeginEvent(uint16 SpecId) +{ + FCpuProfilerTraceInternal::FThreadState* ThreadState = FCpuProfilerTraceInternal::ThreadLocalThreadState; + if (!ThreadState) + { + ThreadState = FCpuProfilerTraceInternal::InitThreadState(); + } + ++ThreadState->Depth; + bool bEventEnabled = UE_TRACE_EVENT_IS_ENABLED(CpuProfiler, EventBatch); + if ((!bEventEnabled) & (bEventEnabled == ThreadState->bEnabled)) + { + return; + } + ThreadState->bEnabled = bEventEnabled; + if (!bEventEnabled) + { + FCpuProfilerTraceInternal::EndCapture(ThreadState); + return; + } + if (ThreadState->BufferSize >= FCpuProfilerTraceInternal::FullBufferThreshold) + { + FCpuProfilerTraceInternal::FlushThreadBuffer(ThreadState); + } + uint64 Cycle = FPlatformTime::Cycles64(); + uint64 CycleDiff = Cycle - ThreadState->LastCycle; + ThreadState->LastCycle = Cycle; + uint8* BufferPtr = ThreadState->Buffer + ThreadState->BufferSize; + FTraceUtils::Encode7bit((CycleDiff << 1) | 1ull, BufferPtr); + FTraceUtils::Encode7bit(SpecId, BufferPtr); + ThreadState->BufferSize = BufferPtr - ThreadState->Buffer; +} + +void FCpuProfilerTrace::OutputEndEvent() +{ + FCpuProfilerTraceInternal::FThreadState* ThreadState = FCpuProfilerTraceInternal::ThreadLocalThreadState; + --ThreadState->Depth; + bool bEventEnabled = UE_TRACE_EVENT_IS_ENABLED(CpuProfiler, EventBatch); + if ((!bEventEnabled) & (bEventEnabled == ThreadState->bEnabled)) + { + return; + } + ThreadState->bEnabled = bEventEnabled; + if (!bEventEnabled) + { + FCpuProfilerTraceInternal::EndCapture(ThreadState); + return; + } + if ((ThreadState->Depth == 0) | (ThreadState->BufferSize >= FCpuProfilerTraceInternal::FullBufferThreshold)) + { + FCpuProfilerTraceInternal::FlushThreadBuffer(ThreadState); + } + uint64 Cycle = FPlatformTime::Cycles64(); + uint64 CycleDiff = Cycle - ThreadState->LastCycle; + ThreadState->LastCycle = Cycle; + uint8* BufferPtr = ThreadState->Buffer + ThreadState->BufferSize; + FTraceUtils::Encode7bit(CycleDiff << 1, BufferPtr); + ThreadState->BufferSize = BufferPtr - ThreadState->Buffer; +} + +uint16 FCpuProfilerTrace::OutputEventType(const TCHAR* Name, ECpuProfilerGroup Group) +{ + static TAtomic NextSpecId(1); + uint32 WideId = NextSpecId++; + check(WideId <= 0xFFFF); + uint16 Id = uint16(WideId); + uint16 NameSize = (FCString::Strlen(Name) + 1) * sizeof(TCHAR); + UE_TRACE_LOG(CpuProfiler, EventSpec, NameSize) + << EventSpec.Id(Id) + << EventSpec.Group(uint16(Group)) + << EventSpec.Attachment(Name, NameSize); + return Id; +} + +void FCpuProfilerTrace::Init(bool bStartEnabled) +{ + if (bStartEnabled) + { + UE_TRACE_EVENT_IS_ENABLED(CpuProfiler, EventBatch); + Trace::ToggleEvent(TEXT("CpuProfiler.EventBatch"), true); + } +} + +#endif \ No newline at end of file diff --git a/Engine/Source/Runtime/Core/Private/ProfilingDebugging/CsvProfiler.cpp b/Engine/Source/Runtime/Core/Private/ProfilingDebugging/CsvProfiler.cpp index 7101d6806e58..81e271832c75 100644 --- a/Engine/Source/Runtime/Core/Private/ProfilingDebugging/CsvProfiler.cpp +++ b/Engine/Source/Runtime/Core/Private/ProfilingDebugging/CsvProfiler.cpp @@ -923,7 +923,7 @@ public: if (bInIsFName) { check((InStatIDRaw & FNameOrIndexMask) == InStatIDRaw); - const FNameEntry* NameEntry = FName::GetEntry(UniqueID.Fields.FNameOrIndex); + const FNameEntry* NameEntry = FName::GetEntry(FNameEntryId::FromUnstableInt(UniqueID.Fields.FNameOrIndex)); NameStr = NameEntry->GetPlainNameString(); } else @@ -1628,7 +1628,7 @@ public: private: static CSV_PROFILER_INLINE uint64 GetStatID(const char* StatName) { return uint64(StatName); } - static CSV_PROFILER_INLINE uint64 GetStatID(const FName& StatId) { return uint64(StatId.GetComparisonIndex()); } + static CSV_PROFILER_INLINE uint64 GetStatID(const FName& StatId) { return StatId.GetComparisonIndex().ToUnstableInt(); } static inline FString GenerateThreadName(uint32 ThreadId) { diff --git a/Engine/Source/Runtime/Core/Private/ProfilingDebugging/LoadTimeTracker.cpp b/Engine/Source/Runtime/Core/Private/ProfilingDebugging/LoadTimeTracker.cpp index 15fc01a453d6..ae69addb0bfc 100644 --- a/Engine/Source/Runtime/Core/Private/ProfilingDebugging/LoadTimeTracker.cpp +++ b/Engine/Source/Runtime/Core/Private/ProfilingDebugging/LoadTimeTracker.cpp @@ -301,4 +301,4 @@ static FAutoConsoleCommand AccumulatorTimerStopCmd( TEXT("LoadTimes.StopAccumulating"), TEXT("Stops capturing fine-grained accumulated load time data and dump the results"), FConsoleCommandDelegate::CreateStatic(&FLoadTimeTracker::StopAccumulatedLoadTimesStatic) - ); \ No newline at end of file + ); diff --git a/Engine/Source/Runtime/Core/Private/ProfilingDebugging/MiscTrace.cpp b/Engine/Source/Runtime/Core/Private/ProfilingDebugging/MiscTrace.cpp new file mode 100644 index 000000000000..d0c8abbd223d --- /dev/null +++ b/Engine/Source/Runtime/Core/Private/ProfilingDebugging/MiscTrace.cpp @@ -0,0 +1,166 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +#include "ProfilingDebugging/MiscTrace.h" + +#if MISCTRACE_ENABLED + +#include "Trace/Trace.h" +#include "Misc/CString.h" +#include "HAL/PlatformTLS.h" +#include "HAL/PlatformTime.h" + +UE_TRACE_EVENT_BEGIN(Misc, RegisterGameThread, Always) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(Misc, CreateThread, Always) + UE_TRACE_EVENT_FIELD(uint32, CurrentThreadId) + UE_TRACE_EVENT_FIELD(uint32, CreatedThreadId) + UE_TRACE_EVENT_FIELD(uint32, Priority) + UE_TRACE_EVENT_FIELD(uint16, NameSize) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(Misc, SetThreadGroup, Always) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(Misc, BeginThreadGroupScope, Always) + UE_TRACE_EVENT_FIELD(uint32, CurrentThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(Misc, EndThreadGroupScope, Always) + UE_TRACE_EVENT_FIELD(uint32, CurrentThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(Misc, BookmarkSpec, Always) + UE_TRACE_EVENT_FIELD(const void*, BookmarkPoint) + UE_TRACE_EVENT_FIELD(int32, Line) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(Misc, Bookmark, Always) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(const void*, BookmarkPoint) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(Misc, BeginGameFrame, Always) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(Misc, EndGameFrame, Always) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(Misc, BeginRenderFrame, Always) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(Misc, EndRenderFrame, Always) +UE_TRACE_EVENT_END() + + +struct FMiscTraceInternal +{ + static uint64 LastFrameCycle[TraceFrameType_Count]; +}; + +uint64 FMiscTraceInternal::LastFrameCycle[TraceFrameType_Count] = { 0, 0 }; + +void FMiscTrace::OutputRegisterGameThread(uint32 Id) +{ + UE_TRACE_LOG(Misc, RegisterGameThread) + << RegisterGameThread.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +void FMiscTrace::OutputCreateThread(uint32 Id, const TCHAR* Name, uint32 Priority) +{ + uint16 NameSize = (FCString::Strlen(Name) + 1) * sizeof(TCHAR); + UE_TRACE_LOG(Misc, CreateThread, NameSize) + << CreateThread.CurrentThreadId(FPlatformTLS::GetCurrentThreadId()) + << CreateThread.CreatedThreadId(Id) + << CreateThread.Priority(Priority) + << CreateThread.Attachment(Name, NameSize); +} + +void FMiscTrace::OutputSetThreadGroup(uint32 Id, const ANSICHAR* GroupName) +{ + uint16 NameSize = strlen(GroupName) + 1; + UE_TRACE_LOG(Misc, SetThreadGroup, NameSize) + << SetThreadGroup.ThreadId(Id) + << SetThreadGroup.Attachment(GroupName, NameSize); +} + +void FMiscTrace::OutputBeginThreadGroupScope(const ANSICHAR* GroupName) +{ + uint16 NameSize = strlen(GroupName) + 1; + UE_TRACE_LOG(Misc, BeginThreadGroupScope, NameSize) + << BeginThreadGroupScope.CurrentThreadId(FPlatformTLS::GetCurrentThreadId()) + << BeginThreadGroupScope.Attachment(GroupName, NameSize); +} + +void FMiscTrace::OutputEndThreadGroupScope() +{ + UE_TRACE_LOG(Misc, EndThreadGroupScope) + << EndThreadGroupScope.CurrentThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +void FMiscTrace::OutputBookmarkSpec(const void* BookmarkPoint, const ANSICHAR* File, int32 Line, const TCHAR* Format) +{ + uint16 FileNameSize = strlen(File) + 1; + uint16 FormatStringSize = (FCString::Strlen(Format) + 1) * sizeof(TCHAR); + auto StringCopyFunc = [FileNameSize, FormatStringSize, File, Format](uint8* Out) { + memcpy(Out, File, FileNameSize); + memcpy(Out + FileNameSize, Format, FormatStringSize); + }; + UE_TRACE_LOG(Misc, BookmarkSpec, FileNameSize + FormatStringSize) + << BookmarkSpec.BookmarkPoint(BookmarkPoint) + << BookmarkSpec.Line(Line) + << BookmarkSpec.Attachment(StringCopyFunc); +} + +void FMiscTrace::OutputBookmarkInternal(const void* BookmarkPoint, uint16 EncodedFormatArgsSize, uint8* EncodedFormatArgs) +{ + UE_TRACE_LOG(Misc, Bookmark, EncodedFormatArgsSize) + << Bookmark.Cycle(FPlatformTime::Cycles64()) + << Bookmark.BookmarkPoint(BookmarkPoint) + << Bookmark.Attachment(EncodedFormatArgs, EncodedFormatArgsSize); +} + +void FMiscTrace::OutputBeginFrame(ETraceFrameType FrameType) +{ + uint64 Cycle = FPlatformTime::Cycles64(); + uint64 CycleDiff = Cycle - FMiscTraceInternal::LastFrameCycle[FrameType]; + FMiscTraceInternal::LastFrameCycle[FrameType] = Cycle; + uint8 Buffer[9]; + uint8* BufferPtr = Buffer; + FTraceUtils::Encode7bit(CycleDiff, BufferPtr); + uint16 BufferSize = BufferPtr - Buffer; + if (FrameType == TraceFrameType_Game) + { + UE_TRACE_LOG(Misc, BeginGameFrame, BufferSize) + << BeginGameFrame.Attachment(&Buffer, BufferSize); + } + else if (FrameType == TraceFrameType_Rendering) + { + UE_TRACE_LOG(Misc, BeginRenderFrame, BufferSize) + << BeginRenderFrame.Attachment(&Buffer, BufferSize); + } +} + +void FMiscTrace::OutputEndFrame(ETraceFrameType FrameType) +{ + uint64 Cycle = FPlatformTime::Cycles64(); + uint64 CycleDiff = Cycle - FMiscTraceInternal::LastFrameCycle[FrameType]; + FMiscTraceInternal::LastFrameCycle[FrameType] = Cycle; + uint8 Buffer[9]; + uint8* BufferPtr = Buffer; + FTraceUtils::Encode7bit(CycleDiff, BufferPtr); + uint16 BufferSize = BufferPtr - Buffer; + if (FrameType == TraceFrameType_Game) + { + UE_TRACE_LOG(Misc, EndGameFrame, BufferSize) + << EndGameFrame.Attachment(&Buffer, BufferSize); + } + else if (FrameType == TraceFrameType_Rendering) + { + UE_TRACE_LOG(Misc, EndRenderFrame, BufferSize) + << EndRenderFrame.Attachment(&Buffer, BufferSize); + } +} + +#endif diff --git a/Engine/Source/Runtime/Core/Private/ProfilingDebugging/PlatformFileTrace.cpp b/Engine/Source/Runtime/Core/Private/ProfilingDebugging/PlatformFileTrace.cpp new file mode 100644 index 000000000000..bcda56ff603c --- /dev/null +++ b/Engine/Source/Runtime/Core/Private/ProfilingDebugging/PlatformFileTrace.cpp @@ -0,0 +1,140 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +#include "ProfilingDebugging/PlatformFileTrace.h" + +#if PLATFORMFILETRACE_ENABLED + +#include "Misc/CString.h" +#include "HAL/PlatformTime.h" +#include "HAL/PlatformTLS.h" + +UE_TRACE_EVENT_BEGIN(PlatformFile, BeginOpen, Always) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint64, TempHandle) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(PlatformFile, EndOpen, Always) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint64, TempHandle) + UE_TRACE_EVENT_FIELD(uint64, FileHandle) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(PlatformFile, BeginClose, Always) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint64, TempHandle) + UE_TRACE_EVENT_FIELD(uint64, FileHandle) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(PlatformFile, EndClose, Always) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint64, TempHandle) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(PlatformFile, BeginRead, Always) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint64, ReadHandle) + UE_TRACE_EVENT_FIELD(uint64, FileHandle) + UE_TRACE_EVENT_FIELD(uint64, Offset) + UE_TRACE_EVENT_FIELD(uint64, Size) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(PlatformFile, EndRead, Always) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint64, ReadHandle) + UE_TRACE_EVENT_FIELD(uint64, SizeRead) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(PlatformFile, BeginWrite, Always) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint64, WriteHandle) + UE_TRACE_EVENT_FIELD(uint64, FileHandle) + UE_TRACE_EVENT_FIELD(uint64, Offset) + UE_TRACE_EVENT_FIELD(uint64, Size) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(PlatformFile, EndWrite, Always) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint64, WriteHandle) + UE_TRACE_EVENT_FIELD(uint64, SizeWritten) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +void FPlatformFileTrace::BeginOpen(uint64 TempHandle, const TCHAR* Path) +{ + uint16 PathSize = (FCString::Strlen(Path) + 1) * sizeof(TCHAR); + UE_TRACE_LOG(PlatformFile, BeginOpen, PathSize) + << BeginOpen.Cycle(FPlatformTime::Cycles64()) + << BeginOpen.TempHandle(TempHandle) + << BeginOpen.Attachment(Path, PathSize) + << BeginOpen.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +void FPlatformFileTrace::EndOpen(uint64 TempHandle, uint64 FileHandle) +{ + UE_TRACE_LOG(PlatformFile, EndOpen) + << EndOpen.Cycle(FPlatformTime::Cycles64()) + << EndOpen.TempHandle(TempHandle) + << EndOpen.FileHandle(FileHandle); +} + +void FPlatformFileTrace::BeginClose(uint64 TempHandle, uint64 FileHandle) +{ + UE_TRACE_LOG(PlatformFile, BeginClose) + << BeginClose.Cycle(FPlatformTime::Cycles64()) + << BeginClose.TempHandle(TempHandle) + << BeginClose.FileHandle(FileHandle) + << BeginClose.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +void FPlatformFileTrace::EndClose(uint64 TempHandle) +{ + UE_TRACE_LOG(PlatformFile, EndClose) + << EndClose.Cycle(FPlatformTime::Cycles64()) + << EndClose.TempHandle(TempHandle); +} + +void FPlatformFileTrace::BeginRead(uint64 ReadHandle, uint64 FileHandle, uint64 Offset, uint64 Size) +{ + UE_TRACE_LOG(PlatformFile, BeginRead) + << BeginRead.Cycle(FPlatformTime::Cycles64()) + << BeginRead.ReadHandle(ReadHandle) + << BeginRead.FileHandle(FileHandle) + << BeginRead.Offset(Offset) + << BeginRead.Size(Size) + << BeginRead.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +void FPlatformFileTrace::EndRead(uint64 ReadHandle, uint64 SizeRead) +{ + UE_TRACE_LOG(PlatformFile, EndRead) + << EndRead.Cycle(FPlatformTime::Cycles64()) + << EndRead.ReadHandle(ReadHandle) + << EndRead.SizeRead(SizeRead) + << EndRead.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +void FPlatformFileTrace::BeginWrite(uint64 WriteHandle, uint64 FileHandle, uint64 Offset, uint64 Size) +{ + UE_TRACE_LOG(PlatformFile, BeginWrite) + << BeginWrite.Cycle(FPlatformTime::Cycles64()) + << BeginWrite.WriteHandle(WriteHandle) + << BeginWrite.FileHandle(FileHandle) + << BeginWrite.Offset(Offset) + << BeginWrite.Size(Size) + << BeginWrite.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +void FPlatformFileTrace::EndWrite(uint64 WriteHandle, uint64 SizeWritten) +{ + UE_TRACE_LOG(PlatformFile, EndWrite) + << EndWrite.Cycle(FPlatformTime::Cycles64()) + << EndWrite.WriteHandle(WriteHandle) + << EndWrite.SizeWritten(SizeWritten) + << EndWrite.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +#endif \ No newline at end of file diff --git a/Engine/Source/Runtime/Core/Private/Serialization/Archive.cpp b/Engine/Source/Runtime/Core/Private/Serialization/Archive.cpp index 8ee3ae1fe3b0..b3bf43c4ac92 100644 --- a/Engine/Source/Runtime/Core/Private/Serialization/Archive.cpp +++ b/Engine/Source/Runtime/Core/Private/Serialization/Archive.cpp @@ -120,7 +120,6 @@ FArchive::~FArchive() } -PRAGMA_DISABLE_DEPRECATION_WARNINGS void FArchive::Reset() { #if DEVIRTUALIZE_FLinkerLoad_Serialize @@ -226,7 +225,6 @@ void FArchive::CopyTrivialFArchiveStatusMembers(const FArchive& ArchiveToCopy) SetBaseLocalizationNamespace(ArchiveToCopy.GetBaseLocalizationNamespace()); #endif // USE_STABLE_LOCALIZATION_KEYS } -PRAGMA_ENABLE_DEPRECATION_WARNINGS /** * Returns the name of the Archive. Useful for getting the name of the package a struct or object @@ -605,13 +603,6 @@ public: }; #endif // WITH_MULTI_THREADED_COMPRESSION - - -void FArchive::SerializeCompressed( void* V, int64 Length, ECompressionFlags Flags, bool bTreatBufferAsFileReader, bool bUsePlatformBitWindow ) -{ - SerializeCompressed(V, Length, FCompression::GetCompressionFormatFromDeprecatedFlags(Flags), ECompressionFlags(Flags & COMPRESS_OptionsFlagsMask), bTreatBufferAsFileReader); -} - void FArchive::SerializeCompressed(void* V, int64 Length, FName CompressionFormat, ECompressionFlags Flags, bool bTreatBufferAsFileReader) { if( IsLoading() ) @@ -1099,7 +1090,6 @@ void FArchive::LogfImpl(const TCHAR* Fmt, ...) FMemory::SystemFree( Buffer ); } -PRAGMA_DISABLE_DEPRECATION_WARNINGS void FArchive::SetUE4Ver(int32 InVer) { ArUE4Ver = InVer; @@ -1159,4 +1149,3 @@ void FArchive::SetIsPersistent(bool bInIsPersistent) { ArIsPersistent = bInIsPersistent; } -PRAGMA_ENABLE_DEPRECATION_WARNINGS diff --git a/Engine/Source/Runtime/Core/Private/Serialization/ArchiveFromStructuredArchive.cpp b/Engine/Source/Runtime/Core/Private/Serialization/ArchiveFromStructuredArchive.cpp index af7a9bc56314..0cafdb6f0118 100644 --- a/Engine/Source/Runtime/Core/Private/Serialization/ArchiveFromStructuredArchive.cpp +++ b/Engine/Source/Runtime/Core/Private/Serialization/ArchiveFromStructuredArchive.cpp @@ -3,6 +3,8 @@ #include "Serialization/ArchiveFromStructuredArchive.h" #include "Internationalization/Text.h" +#if WITH_TEXT_ARCHIVE_SUPPORT + FArchiveFromStructuredArchive::FArchiveFromStructuredArchive(FStructuredArchive::FSlot Slot) : FArchiveProxy(Slot.GetUnderlyingArchive()) , bPendingSerialize(true) @@ -289,4 +291,6 @@ void FArchiveFromStructuredArchive::OpenArchive() RootSlot.EnterStream(); } } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/Engine/Source/Runtime/Core/Private/Serialization/CustomVersion.cpp b/Engine/Source/Runtime/Core/Private/Serialization/CustomVersion.cpp index 085e5b00d999..7ced028cc5ef 100644 --- a/Engine/Source/Runtime/Core/Private/Serialization/CustomVersion.cpp +++ b/Engine/Source/Runtime/Core/Private/Serialization/CustomVersion.cpp @@ -6,6 +6,7 @@ #include "Serialization/CustomVersion.h" #include "Serialization/StructuredArchiveFromArchive.h" +#include "Algo/Sort.h" namespace { @@ -84,6 +85,11 @@ void FCustomVersionContainer::Empty() Versions.Empty(); } +void FCustomVersionContainer::SortByKey() +{ + Algo::SortBy(Versions, &FCustomVersion::Key); +} + FString FCustomVersionContainer::ToString(const FString& Indent) const { FString VersionsAsString; diff --git a/Engine/Source/Runtime/Core/Private/Stats/Stats2.cpp b/Engine/Source/Runtime/Core/Private/Stats/Stats2.cpp index f6416d557582..c185058640fe 100644 --- a/Engine/Source/Runtime/Core/Private/Stats/Stats2.cpp +++ b/Engine/Source/Runtime/Core/Private/Stats/Stats2.cpp @@ -602,6 +602,21 @@ public: { Found->AlwaysEnabledNamesInThisGroup.Add( Stat, Result ); } + +#if CPUPROFILERTRACE_ENABLED + if (bCycleStat) + { + Result->TraceCpuProfilerSpecId = FCpuProfilerTrace::OutputEventType(*StatDescription, CpuProfilerGroup_Stats); + } +#endif +#if STATSTRACE_ENABLED + if (!bCycleStat && (InStatType == EStatDataType::ST_int64 || InStatType == EStatDataType::ST_double)) + { + ANSICHAR NameBuffer[1024]; + StatShortName.GetPlainANSIString(NameBuffer); + FStatsTrace::DeclareStat(Stat, NameBuffer, *StatDescription, InStatType == EStatDataType::ST_double, MemoryRegion != FPlatformMemory::MCR_Invalid, bShouldClearEveryFrame); + } +#endif return TStatId(Result); } diff --git a/Engine/Source/Runtime/Core/Private/Stats/StatsCommand.cpp b/Engine/Source/Runtime/Core/Private/Stats/StatsCommand.cpp index ddf28ba2e60a..57188295e944 100644 --- a/Engine/Source/Runtime/Core/Private/Stats/StatsCommand.cpp +++ b/Engine/Source/Runtime/Core/Private/Stats/StatsCommand.cpp @@ -1406,7 +1406,7 @@ struct FHUDGroupManager AggregatedHierarchyHistory.CullByDepth( Params.MaxHierarchyDepth.Get() ); // Make sure the game thread is first. - AggregatedHierarchyHistory.Children.KeySort( TLess() ); + AggregatedHierarchyHistory.Children.KeySort(FNameLexicalLess()); FComplexStatUtils::DiviveStatArray( AggregatedFlatHistory, NumFrames, EComplexStatField::IncSum, EComplexStatField::IncAve ); FComplexStatUtils::DiviveStatArray( AggregatedFlatHistory, NumFrames, EComplexStatField::ExcSum, EComplexStatField::ExcAve ); diff --git a/Engine/Source/Runtime/Core/Private/Stats/StatsData.cpp b/Engine/Source/Runtime/Core/Private/Stats/StatsData.cpp index 927bfadbe8cc..8322eaa2ff07 100644 --- a/Engine/Source/Runtime/Core/Private/Stats/StatsData.cpp +++ b/Engine/Source/Runtime/Core/Private/Stats/StatsData.cpp @@ -1686,27 +1686,27 @@ void FStatsThreadState::Condense(int64 TargetFrame, TArray& OutSta void FStatsThreadState::FindOrAddMetaData(FStatMessage const& Item) { - const FName LongName = Item.NameAndInfo.GetRawName(); const FName ShortName = Item.NameAndInfo.GetShortName(); + const FName GroupName = Item.NameAndInfo.GetGroupName(); + + // Whether to add to the threads group. + const bool bIsThread = FStatConstants::NAME_ThreadGroup == GroupName; + if (bIsThread) + { + // The description of a thread group contains the thread id + const FString Desc = Item.NameAndInfo.GetDescription(); + Threads.Add(FStatsUtils::ParseThreadID(Desc), ShortName); + } FStatMessage* Result = ShortNameToLongName.Find(ShortName); if (!Result) { + const FName LongName = Item.NameAndInfo.GetRawName(); + check(ShortName != LongName); FStatMessage AsSet(Item); AsSet.Clear(); - const FName GroupName = Item.NameAndInfo.GetGroupName(); - - // Whether to add to the threads group. - const bool bIsThread = FStatConstants::NAME_ThreadGroup == GroupName; - if( bIsThread ) - { - // The description of a thread group contains the thread id - const FString Desc = Item.NameAndInfo.GetDescription(); - Threads.Add( FStatsUtils::ParseThreadID( Desc ), ShortName ); - } - ShortNameToLongName.Add(ShortName, AsSet); // we want this to be a clear, but it should be a SetLongName AsSet.NameAndInfo.SetField(EStatOperation::Set); check(Item.NameAndInfo.GetField()); diff --git a/Engine/Source/Runtime/Core/Private/Stats/StatsFile.cpp b/Engine/Source/Runtime/Core/Private/Stats/StatsFile.cpp index d3a09d77ccf5..e92005616494 100644 --- a/Engine/Source/Runtime/Core/Private/Stats/StatsFile.cpp +++ b/Engine/Source/Runtime/Core/Private/Stats/StatsFile.cpp @@ -330,15 +330,15 @@ void IStatsWriteFile::Finalize() } // Create a copy of names. - TSet FNamesToSent = FNamesSent; + TSet FNamesToSent = FNamesSent; FNamesSent.Empty( FNamesSent.Num() ); // Serialize FNames. Header.FNameTableOffset = Ar.Tell(); Header.NumFNames = FNamesToSent.Num(); - for (const int32 It : FNamesToSent) + for (FNameEntryId Id : FNamesToSent) { - WriteFName( Ar, FStatNameAndInfo( FName( It, It, 0 ), false ) ); + WriteFName( Ar, FStatNameAndInfo( FName( Id, Id, 0 ), false ) ); } // Serialize metadata messages. @@ -346,22 +346,6 @@ void IStatsWriteFile::Finalize() Header.NumMetadataMessages = Stats.ShortNameToLongName.Num(); WriteMetadata( Ar ); - // Verify data. - TSet BMinA = FNamesSent.Difference( FNamesToSent ); - struct FLocal - { - static TArray GetFNameArray( const TSet& NameIndices ) - { - TArray Result; - for (const int32 NameIndex : NameIndices) - { - new(Result)FName( NameIndex, NameIndex, 0 ); - } - return Result; - } - }; - TArray BMinANames = FLocal::GetFNameArray( BMinA ); - // Seek to the position just after a magic value of the file and write out proper header. Ar.Seek( sizeof( uint32 ) ); Ar << Header; diff --git a/Engine/Source/Runtime/Core/Private/Stats/StatsTrace.cpp b/Engine/Source/Runtime/Core/Private/Stats/StatsTrace.cpp new file mode 100644 index 000000000000..3e357cb95820 --- /dev/null +++ b/Engine/Source/Runtime/Core/Private/Stats/StatsTrace.cpp @@ -0,0 +1,177 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +#include "Stats/StatsTrace.h" +#include "ProfilingDebugging/MiscTrace.h" +#include "Templates/Atomic.h" +#include "Misc/CString.h" +#include "HAL/PlatformTime.h" +#include "HAL/PlatformTLS.h" +#include "UObject/NameTypes.h" + +#if STATSTRACE_ENABLED + +UE_TRACE_EVENT_BEGIN(Stats, Spec, Always) + UE_TRACE_EVENT_FIELD(uint32, Id) + UE_TRACE_EVENT_FIELD(bool, IsFloatingPoint) + UE_TRACE_EVENT_FIELD(bool, IsMemory) + UE_TRACE_EVENT_FIELD(bool, ShouldClearEveryFrame) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(Stats, EventBatch, Always) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +struct FStatsTraceInternal +{ +public: + enum + { + MaxBufferSize = 512, + MaxEncodedEventSize = 27, // 9 + 9 + 9 + FullBufferThreshold = MaxBufferSize - MaxEncodedEventSize, + }; + + enum EOpType + { + Increment = 0, + Decrement = 1, + AddInteger = 2, + SetInteger = 3, + AddFloat = 4, + SetFloat = 5, + }; + + struct FThreadState + { + uint64 LastCycle; + uint32 ThreadId; + uint32 BufferSize; + uint8 Buffer[MaxBufferSize]; + }; + + static FThreadState* GetThreadState() { return ThreadLocalThreadState; } + FORCENOINLINE static FThreadState* InitThreadState(); + FORCENOINLINE static void FlushThreadBuffer(FThreadState* ThreadState); + static void BeginEncodeOp(const FName& Stat, EOpType Op, FStatsTraceInternal::FThreadState*& OutThreadState, uint8*& OutBufferPtr); + static void EndEncodeOp(FStatsTraceInternal::FThreadState* ThreadState, uint8* BufferPtr); + +private: + static thread_local FThreadState* ThreadLocalThreadState; +}; + +thread_local FStatsTraceInternal::FThreadState* FStatsTraceInternal::ThreadLocalThreadState = nullptr; + +FStatsTraceInternal::FThreadState* FStatsTraceInternal::InitThreadState() +{ + ThreadLocalThreadState = new FThreadState(); + ThreadLocalThreadState->BufferSize = 0; + ThreadLocalThreadState->ThreadId = FPlatformTLS::GetCurrentThreadId(); + ThreadLocalThreadState->LastCycle = 0; + return ThreadLocalThreadState; +} + +void FStatsTraceInternal::FlushThreadBuffer(FThreadState* ThreadState) +{ + UE_TRACE_LOG(Stats, EventBatch, ThreadState->BufferSize) + << EventBatch.ThreadId(ThreadState->ThreadId) + << EventBatch.Attachment(ThreadState->Buffer, ThreadState->BufferSize); + ThreadState->BufferSize = 0; +} + +void FStatsTraceInternal::BeginEncodeOp(const FName& Stat, EOpType Op, FThreadState*& OutThreadState, uint8*& OutBufferPtr) +{ + uint64 Cycle = FPlatformTime::Cycles64(); + OutThreadState = GetThreadState(); + if (!OutThreadState) + { + OutThreadState = InitThreadState(); + } + uint64 CycleDiff = Cycle - OutThreadState->LastCycle; + OutThreadState->LastCycle = Cycle; + if (OutThreadState->BufferSize >= FullBufferThreshold) + { + FStatsTraceInternal::FlushThreadBuffer(OutThreadState); + } + OutBufferPtr = OutThreadState->Buffer + OutThreadState->BufferSize; + FTraceUtils::Encode7bit((uint64(Stat.GetComparisonIndex().ToUnstableInt()) << 3) | uint64(Op), OutBufferPtr); + FTraceUtils::Encode7bit(CycleDiff, OutBufferPtr); +} + +void FStatsTraceInternal::EndEncodeOp(FThreadState* ThreadState, uint8* BufferPtr) +{ + ThreadState->BufferSize = BufferPtr - ThreadState->Buffer; +} + +void FStatsTrace::DeclareStat(const FName& Stat, const ANSICHAR* Name, const TCHAR* Description, bool IsFloatingPoint, bool IsMemory, bool ShouldClearEveryFrame) +{ + uint16 NameSize = (strlen(Name) + 1) * sizeof(ANSICHAR); + uint16 DescriptionSize = (FCString::Strlen(Description) + 1) * sizeof(TCHAR); + auto Attachment = [Name, NameSize, Description, DescriptionSize](uint8* Buffer) + { + memcpy(Buffer, Name, NameSize); + memcpy(Buffer + NameSize, Description, DescriptionSize); + }; + UE_TRACE_LOG(Stats, Spec, NameSize + DescriptionSize) + << Spec.Id(Stat.GetComparisonIndex().ToUnstableInt()) + << Spec.IsFloatingPoint(IsFloatingPoint) + << Spec.IsMemory(IsMemory) + << Spec.ShouldClearEveryFrame(ShouldClearEveryFrame) + << Spec.Attachment(Attachment); +} + +void FStatsTrace::Increment(const FName& Stat) +{ + FStatsTraceInternal::FThreadState* ThreadState; + uint8* BufferPtr; + FStatsTraceInternal::BeginEncodeOp(Stat, FStatsTraceInternal::Increment, ThreadState, BufferPtr); + ThreadState->BufferSize = BufferPtr - ThreadState->Buffer; + FStatsTraceInternal::EndEncodeOp(ThreadState, BufferPtr); +} + +void FStatsTrace::Decrement(const FName& Stat) +{ + FStatsTraceInternal::FThreadState* ThreadState; + uint8* BufferPtr; + FStatsTraceInternal::BeginEncodeOp(Stat, FStatsTraceInternal::Decrement, ThreadState, BufferPtr); + ThreadState->BufferSize = BufferPtr - ThreadState->Buffer; + FStatsTraceInternal::EndEncodeOp(ThreadState, BufferPtr); +} + +void FStatsTrace::Add(const FName& Stat, int64 Amount) +{ + FStatsTraceInternal::FThreadState* ThreadState; + uint8* BufferPtr; + FStatsTraceInternal::BeginEncodeOp(Stat, FStatsTraceInternal::AddInteger, ThreadState, BufferPtr); + FTraceUtils::EncodeZigZag(Amount, BufferPtr); + FStatsTraceInternal::EndEncodeOp(ThreadState, BufferPtr); +} + +void FStatsTrace::Add(const FName& Stat, double Amount) +{ + FStatsTraceInternal::FThreadState* ThreadState; + uint8* BufferPtr; + FStatsTraceInternal::BeginEncodeOp(Stat, FStatsTraceInternal::AddFloat, ThreadState, BufferPtr); + memcpy(BufferPtr, &Amount, sizeof(double)); + BufferPtr += sizeof(double); + FStatsTraceInternal::EndEncodeOp(ThreadState, BufferPtr); +} + +void FStatsTrace::Set(const FName& Stat, int64 Value) +{ + FStatsTraceInternal::FThreadState* ThreadState; + uint8* BufferPtr; + FStatsTraceInternal::BeginEncodeOp(Stat, FStatsTraceInternal::SetInteger, ThreadState, BufferPtr); + FTraceUtils::EncodeZigZag(Value, BufferPtr); + FStatsTraceInternal::EndEncodeOp(ThreadState, BufferPtr); +} + +void FStatsTrace::Set(const FName& Stat, double Value) +{ + FStatsTraceInternal::FThreadState* ThreadState; + uint8* BufferPtr; + FStatsTraceInternal::BeginEncodeOp(Stat, FStatsTraceInternal::SetFloat, ThreadState, BufferPtr); + memcpy(BufferPtr, &Value, sizeof(double)); + BufferPtr += sizeof(double); + FStatsTraceInternal::EndEncodeOp(ThreadState, BufferPtr); +} + +#endif \ No newline at end of file diff --git a/Engine/Source/Runtime/Core/Private/Tests/HAL/PlatformAtomicsTest.cpp b/Engine/Source/Runtime/Core/Private/Tests/HAL/PlatformAtomicsTest.cpp new file mode 100644 index 000000000000..fd8b47876dce --- /dev/null +++ b/Engine/Source/Runtime/Core/Private/Tests/HAL/PlatformAtomicsTest.cpp @@ -0,0 +1,510 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "CoreTypes.h" +#include "Misc/AssertionMacros.h" +#include "Misc/AutomationTest.h" + +#if WITH_DEV_AUTOMATION_TESTS + +#if PLATFORM_WINDOWS +#include "Windows/AllowWindowsPlatformAtomics.h" +#include "Windows/HideWindowsPlatformAtomics.h" +#endif +#include "HAL/PlatformAtomics.h" + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(FPlatformAtomicsTest, "System.Core.HAL.PlatformAtomics", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::SmokeFilter) + +template +static bool TestInterlocked(volatile T* Dest, T ExpectedReturnValue, T ExpectedFinalValue, TFunctionRef InterlockedFunc, FAutomationTestBase& Test, const TCHAR* FunctionName, const TCHAR* TypeName) +{ + const T ReturnValue = InterlockedFunc(Dest); + if (ReturnValue != ExpectedReturnValue) + { + check(false); + Test.AddError(FString::Printf(TEXT("FPlatformAtomics::Interlocked%s on %s failed"), FunctionName, TypeName)); + return false; + } + + if (FPlatformAtomics::AtomicRead(Dest) != ExpectedFinalValue) + { + check(false); + Test.AddError(FString::Printf(TEXT("FPlatformAtomics::Interlocked%s on %s failed"), FunctionName, TypeName)); + return false; + } + + return true; +} + +static bool TestInterlockedAnd(FAutomationTestBase& Test) +{ + bool bSuccess = true; + + constexpr TCHAR FunctionName[] = TEXT("And"); + { + // Test and with value where some bits are set and some aren't set in the current value. + volatile int8 Value0 = 0x30; + bSuccess = TestInterlocked(&Value0, 0x30, 0x20, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedAnd(Dest, 0x66); }, Test, FunctionName, TEXT("int8")) && bSuccess; + + // Test and with all bits set. + volatile int8 Value1 = 0x30; + bSuccess = TestInterlocked(&Value1, 0x30, 0x30, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedAnd(Dest, -1); }, Test, FunctionName, TEXT("int8")) && bSuccess; + + // Test and with zero. + volatile int8 Value2 = 0x30; + bSuccess = TestInterlocked(&Value2, 0x30, 0, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedAnd(Dest, 0); }, Test, FunctionName, TEXT("int8")) && bSuccess; + } + + { + // Test and with value where some bits are set and some aren't set in the current value. + volatile int16 Value0 = 0x3030; + bSuccess = TestInterlocked(&Value0, 0x3030, 0x2020, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedAnd(Dest, 0x6666); }, Test, FunctionName, TEXT("int16")) && bSuccess; + + // Test and with all bits set. + volatile int16 Value1 = 0x3030; + bSuccess = TestInterlocked(&Value1, 0x3030, 0x3030, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedAnd(Dest, -1); }, Test, FunctionName, TEXT("int16")) && bSuccess; + + // Test and with zero. + volatile int16 Value2 = 0x3030; + bSuccess = TestInterlocked(&Value2, 0x3030, 0, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedAnd(Dest, 0); }, Test, FunctionName, TEXT("int16")) && bSuccess; + } + + { + // Test and with value where some bits are set and some aren't set in the current value. + volatile int32 Value0 = 0x30303030; + bSuccess = TestInterlocked(&Value0, 0x30303030, 0x20202020, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedAnd(Dest, 0x66666666); }, Test, FunctionName, TEXT("int32")) && bSuccess; + + // Test and with all bits set. + volatile int32 Value1 = 0x30303030; + bSuccess = TestInterlocked(&Value1, 0x30303030, 0x30303030, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedAnd(Dest, -1); }, Test, FunctionName, TEXT("int32")) && bSuccess; + + // Test and with zero. + volatile int32 Value2 = 0x30303030; + bSuccess = TestInterlocked(&Value2, 0x30303030, 0, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedAnd(Dest, 0); }, Test, FunctionName, TEXT("int32")) && bSuccess; + } + + { + // Test and with value where some bits are set and some aren't set in the current value. + alignas(8) volatile int64 Value0 = 0x3030303030303030LL; + bSuccess = TestInterlocked(&Value0, 0x3030303030303030LL, 0x2020202020202020LL, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedAnd(Dest, 0x6666666666666666); }, Test, FunctionName, TEXT("int64")) && bSuccess; + + // Test and with all bits set. + alignas(8) volatile int64 Value1 = 0x3030303030303030LL; + bSuccess = TestInterlocked(&Value1, 0x3030303030303030LL, 0x3030303030303030LL, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedAnd(Dest, -1); }, Test, FunctionName, TEXT("int64")) && bSuccess; + + // Test and with zero. + alignas(8) volatile int64 Value2 = 0x3030303030303030LL; + bSuccess = TestInterlocked(&Value2, 0x3030303030303030LL, 0, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedAnd(Dest, 0); }, Test, FunctionName, TEXT("int64")) && bSuccess; + } + + return bSuccess; +} + +static bool TestInterlockedOr(FAutomationTestBase& Test) +{ + bool bSuccess = true; + + constexpr TCHAR FunctionName[] = TEXT("Or"); + { + // Test or with value where some bits are set and some aren't set in the current value. + volatile int8 Value0 = 0x30; + bSuccess = TestInterlocked(&Value0, 0x30, 0x76, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedOr(Dest, 0x66); }, Test, FunctionName, TEXT("int8")) && bSuccess; + + // Test or with all bits set. + volatile int8 Value1 = 0x30; + bSuccess = TestInterlocked(&Value1, 0x30, -1, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedOr(Dest, -1); }, Test, FunctionName, TEXT("int8")) && bSuccess; + + // Test or with zero. + volatile int8 Value2 = 0x30; + bSuccess = TestInterlocked(&Value2, 0x30, 0x30, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedOr(Dest, 0); }, Test, FunctionName, TEXT("int8")) && bSuccess; + } + + { + // Test or with value where some bits are set and some aren't set in the current value. + volatile int16 Value0 = 0x3030; + bSuccess = TestInterlocked(&Value0, 0x3030, 0x7676, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedOr(Dest, 0x6666); }, Test, FunctionName, TEXT("int16")) && bSuccess; + + // Test or with all bits set. + volatile int16 Value1 = 0x3030; + bSuccess = TestInterlocked(&Value1, 0x3030, -1, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedOr(Dest, -1); }, Test, FunctionName, TEXT("int16")) && bSuccess; + + // Test or with zero. + volatile int16 Value2 = 0x3030; + bSuccess = TestInterlocked(&Value2, 0x3030, 0x3030, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedOr(Dest, 0); }, Test, FunctionName, TEXT("int16")) && bSuccess; + } + + { + // Test or with value where some bits are set and some aren't set in the current value. + volatile int32 Value0 = 0x30303030; + bSuccess = TestInterlocked(&Value0, 0x30303030, 0x76767676, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedOr(Dest, 0x66666666); }, Test, FunctionName, TEXT("int32")) && bSuccess; + + // Test or with all bits set. + volatile int32 Value1 = 0x30303030; + bSuccess = TestInterlocked(&Value1, 0x30303030, -1, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedOr(Dest, -1); }, Test, FunctionName, TEXT("int32")) && bSuccess; + + // Test or with zero. + volatile int32 Value2 = 0x30303030; + bSuccess = TestInterlocked(&Value2, 0x30303030, 0x30303030, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedOr(Dest, 0); }, Test, FunctionName, TEXT("int32")) && bSuccess; + } + + { + // Test or with value where some bits are set and some aren't set in the current value. + alignas(8) volatile int64 Value0 = 0x3030303030303030LL; + bSuccess = TestInterlocked(&Value0, 0x3030303030303030LL, 0x7676767676767676LL, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedOr(Dest, 0x6666666666666666LL); }, Test, FunctionName, TEXT("int64")) && bSuccess; + + // Test or with all bits set. + alignas(8) volatile int64 Value1 = 0x3030303030303030LL; + bSuccess = TestInterlocked(&Value1, 0x3030303030303030LL, -1LL, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedOr(Dest, -1); }, Test, FunctionName, TEXT("int64")) && bSuccess; + + // Test or with zero. + alignas(8) volatile int64 Value2 = 0x3030303030303030LL; + bSuccess = TestInterlocked(&Value2, 0x3030303030303030LL, 0x3030303030303030LL, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedOr(Dest, 0); }, Test, FunctionName, TEXT("int64")) && bSuccess; + } + + return bSuccess; +} + +static bool TestInterlockedXor(FAutomationTestBase& Test) +{ + bool bSuccess = true; + + constexpr TCHAR FunctionName[] = TEXT("Xor"); + { + // Test xor with value where some bits are set and some aren't set in the current value. + volatile int8 Value0 = 0x30; + bSuccess = TestInterlocked(&Value0, 0x30, 0x56, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedXor(Dest, 0x66); }, Test, FunctionName, TEXT("int8")) && bSuccess; + + // Test xor with all bits set. + volatile int8 Value1 = 0x30; + bSuccess = TestInterlocked(&Value1, 0x30, int8(~0x30), [](volatile int8* Dest) { return FPlatformAtomics::InterlockedXor(Dest, -1); }, Test, FunctionName, TEXT("int8")) && bSuccess; + + // Test xor with zero. + volatile int8 Value2 = 0x30; + bSuccess = TestInterlocked(&Value2, 0x30, 0x30, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedXor(Dest, 0); }, Test, FunctionName, TEXT("int8")) && bSuccess; + } + + { + // Test xor with value where some bits are set and some aren't set in the current value. + volatile int16 Value0 = 0x3030; + bSuccess = TestInterlocked(&Value0, 0x3030, 0x5656, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedXor(Dest, 0x6666); }, Test, FunctionName, TEXT("int16")) && bSuccess; + + // Test xor with all bits set. + volatile int16 Value1 = 0x3030; + bSuccess = TestInterlocked(&Value1, 0x3030, int16(~0x3030), [](volatile int16* Dest) { return FPlatformAtomics::InterlockedXor(Dest, -1); }, Test, FunctionName, TEXT("int16")) && bSuccess; + + // Test xor with zero. + volatile int16 Value2 = 0x3030; + bSuccess = TestInterlocked(&Value2, 0x3030, 0x3030, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedXor(Dest, 0); }, Test, FunctionName, TEXT("int16")) && bSuccess; + } + + { + // Test xor with value where some bits are set and some aren't set in the current value. + volatile int32 Value0 = 0x30303030; + bSuccess = TestInterlocked(&Value0, 0x30303030, 0x56565656, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedXor(Dest, 0x66666666); }, Test, FunctionName, TEXT("int32")) && bSuccess; + + // Test xor with all bits set. + volatile int32 Value1 = 0x30303030; + bSuccess = TestInterlocked(&Value1, 0x30303030, ~0x30303030, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedXor(Dest, -1); }, Test, FunctionName, TEXT("int32")) && bSuccess; + + // Test xor with zero. + volatile int32 Value2 = 0x30303030; + bSuccess = TestInterlocked(&Value2, 0x30303030, 0x30303030, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedXor(Dest, 0); }, Test, FunctionName, TEXT("int32")) && bSuccess; + } + + { + // Test xor with value where some bits are set and some aren't set in the current value. + alignas(8) volatile int64 Value0 = 0x3030303030303030LL; + bSuccess = TestInterlocked(&Value0, 0x3030303030303030LL, 0x5656565656565656LL, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedXor(Dest, 0x6666666666666666LL); }, Test, FunctionName, TEXT("int64")) && bSuccess; + + // Test xor with all bits set. + alignas(8) volatile int64 Value1 = 0x3030303030303030LL; + bSuccess = TestInterlocked(&Value1, 0x3030303030303030LL, ~0x3030303030303030LL, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedXor(Dest, -1); }, Test, FunctionName, TEXT("int64")) && bSuccess; + + // Test xor with zero. + alignas(8) volatile int64 Value2 = 0x3030303030303030LL; + bSuccess = TestInterlocked(&Value2, 0x3030303030303030LL, 0x3030303030303030LL, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedXor(Dest, 0); }, Test, FunctionName, TEXT("int64")) && bSuccess; + } + + return bSuccess; +} + +static bool TestInterlockedAdd(FAutomationTestBase& Test) +{ + bool bSuccess = true; + + constexpr TCHAR FunctionName[] = TEXT("Add"); + { + // Test adding positive value + volatile int8 Value0 = 0x0F; + bSuccess = TestInterlocked(&Value0, 0x0F, 0x11, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedAdd(Dest, +0x02); }, Test, FunctionName, TEXT("int8")) && bSuccess; + + // Test adding negative value + volatile int8 Value1 = 0x11; + bSuccess = TestInterlocked(&Value1, 0x11, 0x0F, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedAdd(Dest, -0x02); }, Test, FunctionName, TEXT("int8")) && bSuccess; + + // Test value overflow + volatile int8 Value2 = MAX_int8 - 1; + bSuccess = TestInterlocked(&Value2, MAX_int8 - 1, MIN_int8 + 2, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedAdd(Dest, +4); }, Test, FunctionName, TEXT("int8")) && bSuccess; + + // Test value underflow + volatile int8 Value3 = MIN_int8 + 2; + bSuccess = TestInterlocked(&Value3, MIN_int8 + 2, MAX_int8 - 1, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedAdd(Dest, -4); }, Test, FunctionName, TEXT("int8")) && bSuccess; + } + + { + // Test adding positive value + volatile int16 Value0 = 0x0F00; + bSuccess = TestInterlocked(&Value0, 0x0F00, 0x1001, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedAdd(Dest, 0x0101); }, Test, FunctionName, TEXT("int16")) && bSuccess; + + // Test adding negative value + volatile int16 Value1 = 0x1001; + bSuccess = TestInterlocked(&Value1, 0x1001, 0x0F00, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedAdd(Dest, -0x0101); }, Test, FunctionName, TEXT("int16")) && bSuccess; + + // Test value overflow + volatile int16 Value2 = MAX_int16 - 1; + bSuccess = TestInterlocked(&Value2, MAX_int16 - 1, MIN_int16 + 2, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedAdd(Dest, +4); }, Test, FunctionName, TEXT("int16")) && bSuccess; + + // Test value underflow + volatile int16 Value3 = MIN_int16 + 2; + bSuccess = TestInterlocked(&Value3, MIN_int16 + 2, MAX_int16 - 1, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedAdd(Dest, -4); }, Test, FunctionName, TEXT("int16")) && bSuccess; + } + + { + // Test adding positive value + volatile int32 Value0 = 0x0F000000; + bSuccess = TestInterlocked(&Value0, 0x0F000000, 0x10010101, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedAdd(Dest, 0x01010101); }, Test, FunctionName, TEXT("int32")) && bSuccess; + + // Test adding negative value + volatile int32 Value1 = 0x10010101; + bSuccess = TestInterlocked(&Value1, 0x10010101, 0x0F000000, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedAdd(Dest, -0x01010101); }, Test, FunctionName, TEXT("int32")) && bSuccess; + + // Test value overflow + volatile int32 Value2 = MAX_int32 - 1; + bSuccess = TestInterlocked(&Value2, MAX_int32 - 1, MIN_int32 + 2, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedAdd(Dest, +4); }, Test, FunctionName, TEXT("int32")) && bSuccess; + + // Test value underflow + volatile int32 Value3 = MIN_int32 + 2; + bSuccess = TestInterlocked(&Value3, MIN_int32 + 2, MAX_int32 - 1, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedAdd(Dest, -4); }, Test, FunctionName, TEXT("int32")) && bSuccess; + } + + { + // Test adding positive value + alignas(8) volatile int64 Value0 = 0x0F00000000000000LL; + bSuccess = TestInterlocked(&Value0, 0x0F00000000000000LL, 0x1001010101010101LL, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedAdd(Dest, 0x0101010101010101LL); }, Test, FunctionName, TEXT("int64")) && bSuccess; + + // Test adding negative value + alignas(8) volatile int64 Value1 = 0x1001010101010101LL; + bSuccess = TestInterlocked(&Value1, 0x1001010101010101LL, 0x0F00000000000000LL, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedAdd(Dest, -0x0101010101010101LL); }, Test, FunctionName, TEXT("int64")) && bSuccess; + + // Test value overflow + alignas(8) volatile int64 Value2 = MAX_int64 - 1LL; + bSuccess = TestInterlocked(&Value2, MAX_int64 - 1, MIN_int64 + 2, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedAdd(Dest, +4); }, Test, FunctionName, TEXT("int64")) && bSuccess; + + // Test value underflow + alignas(8) volatile int64 Value3 = MIN_int64 + 2LL; + bSuccess = TestInterlocked(&Value3, MIN_int64 + 2, MAX_int64 - 1, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedAdd(Dest, -4); }, Test, FunctionName, TEXT("int64")) && bSuccess; + } + + return bSuccess; +} + +static bool TestInterlockedIncrement(FAutomationTestBase& Test) +{ + bool bSuccess = true; + + constexpr TCHAR FunctionName[] = TEXT("Increment"); + { + volatile int8 Value0 = 0x0F; + bSuccess = TestInterlocked(&Value0, 0x10, 0x10, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedIncrement(Dest); }, Test, FunctionName, TEXT("int8")) && bSuccess; + + // Test value overflow + volatile int8 Value1 = MAX_int8; + bSuccess = TestInterlocked(&Value1, MIN_int8, MIN_int8, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedIncrement(Dest); }, Test, FunctionName, TEXT("int8")) && bSuccess; + } + + { + volatile int16 Value0 = 0x0F0F; + bSuccess = TestInterlocked(&Value0, 0x0F10, 0x0F10, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedIncrement(Dest); }, Test, FunctionName, TEXT("int16")) && bSuccess; + + // Test value overflow + volatile int16 Value1 = MAX_int16; + bSuccess = TestInterlocked(&Value1, MIN_int16, MIN_int16, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedIncrement(Dest); }, Test, FunctionName, TEXT("int16")) && bSuccess; + } + + { + volatile int32 Value0 = 0x0F00000F; + bSuccess = TestInterlocked(&Value0, 0x0F000010, 0x0F000010, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedIncrement(Dest); }, Test, FunctionName, TEXT("int32")) && bSuccess; + + // Test value overflow + volatile int32 Value1 = MAX_int32; + bSuccess = TestInterlocked(&Value1, MIN_int32, MIN_int32, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedIncrement(Dest); }, Test, FunctionName, TEXT("int32")) && bSuccess; + } + + { + alignas(8) volatile int64 Value0 = 0x0F0000000000000FLL; + bSuccess = TestInterlocked(&Value0, 0x0F00000000000010LL, 0x0F00000000000010LL, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedIncrement(Dest); }, Test, FunctionName, TEXT("int64")) && bSuccess; + + // Test value overflow + alignas(8) volatile int64 Value1 = MAX_int64; + bSuccess = TestInterlocked(&Value1, MIN_int64, MIN_int64, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedIncrement(Dest); }, Test, FunctionName, TEXT("int64")) && bSuccess; + } + + return bSuccess; +} + +static bool TestInterlockedDecrement(FAutomationTestBase& Test) +{ + bool bSuccess = true; + + constexpr TCHAR FunctionName[] = TEXT("Decrement"); + { + volatile int8 Value0 = 0x10; + bSuccess = TestInterlocked(&Value0, 0x0F, 0x0F, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedDecrement(Dest); }, Test, FunctionName, TEXT("int8")) && bSuccess; + + // Test value underflow + volatile int8 Value1 = MIN_int8; + bSuccess = TestInterlocked(&Value1, MAX_int8, MAX_int8, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedDecrement(Dest); }, Test, FunctionName, TEXT("int8")) && bSuccess; + } + + { + volatile int16 Value0 = 0x0F10; + bSuccess = TestInterlocked(&Value0, 0x0F0F, 0x0F0F, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedDecrement(Dest); }, Test, FunctionName, TEXT("int16")) && bSuccess; + + // Test value underflow + volatile int16 Value1 = MIN_int16; + bSuccess = TestInterlocked(&Value1, MAX_int16, MAX_int16, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedDecrement(Dest); }, Test, FunctionName, TEXT("int16")) && bSuccess; + } + + { + volatile int32 Value0 = 0x0F000010; + bSuccess = TestInterlocked(&Value0, 0x0F00000F, 0x0F00000F, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedDecrement(Dest); }, Test, FunctionName, TEXT("int32")) && bSuccess; + + // Test value underflow + volatile int32 Value1 = MIN_int32; + bSuccess = TestInterlocked(&Value1, MAX_int32, MAX_int32, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedDecrement(Dest); }, Test, FunctionName, TEXT("int32")) && bSuccess; + } + + { + alignas(8) volatile int64 Value0 = 0x0F00000000000010LL; + bSuccess = TestInterlocked(&Value0, 0x0F0000000000000FLL, 0x0F0000000000000FLL, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedDecrement(Dest); }, Test, FunctionName, TEXT("int64")) && bSuccess; + + // Test value underflow + alignas(8) volatile int64 Value1 = MIN_int64; + bSuccess = TestInterlocked(&Value1, MAX_int64, MAX_int64, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedDecrement(Dest); }, Test, FunctionName, TEXT("int64")) && bSuccess; + } + + return bSuccess; +} + +static bool TestInterlockedExchange(FAutomationTestBase& Test) +{ + bool bSuccess = true; + + constexpr TCHAR FunctionName[] = TEXT("Exchange"); + { + volatile int8 Value = 0x10; + bSuccess = TestInterlocked(&Value, 0x10, 0x01, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedExchange(Dest, 0x01); }, Test, FunctionName, TEXT("int8")) && bSuccess; + } + + { + volatile int16 Value = 0x1000; + bSuccess = TestInterlocked(&Value, 0x1000, 0x0001, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedExchange(Dest, 0x0001); }, Test, FunctionName, TEXT("int16")) && bSuccess; + } + + { + volatile int32 Value = 0x10000000; + bSuccess = TestInterlocked(&Value, 0x10000000, 0x00000101, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedExchange(Dest, 0x00000101); }, Test, FunctionName, TEXT("int32")) && bSuccess; + } + + { + alignas(8) volatile int64 Value = 0x1000000000000000LL; + bSuccess = TestInterlocked(&Value, 0x1000000000000000LL, 0x0000000001010101LL, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedExchange(Dest, 0x0000000001010101LL); }, Test, FunctionName, TEXT("int64")) && bSuccess; + } + + return bSuccess; +} + +static bool TestInterlockedExchangePtr(FAutomationTestBase& Test) +{ + void* volatile Value = &Test; + const void* ReturnValue = FPlatformAtomics::InterlockedExchangePtr(&Value, nullptr); + if (ReturnValue != &Test) + { + Test.AddError(TEXT("FPlatformAtomics::InterlockedExchangePtr failed")); + return false; + } + + if (Value != nullptr) + { + Test.AddError(TEXT("FPlatformAtomics::InterlockedExchangePtr failed")); + return false; + } + + return true; +} + +static bool TestInterlockedCompareExchange(FAutomationTestBase& Test) +{ + bool bSuccess = true; + + constexpr TCHAR FunctionName[] = TEXT("CompareExchange"); + { + volatile int8 Value = 0x10; + + // Test value isn't changed when comparand differs + bSuccess = TestInterlocked(&Value, 0x10, 0x10, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedCompareExchange(Dest, 0x10, 0x01); }, Test, FunctionName, TEXT("int8")) && bSuccess; + + // Test value is changed when comparand matches + bSuccess = TestInterlocked(&Value, 0x10, 0x01, [](volatile int8* Dest) { return FPlatformAtomics::InterlockedCompareExchange(Dest, 0x01, 0x10); }, Test, FunctionName, TEXT("int8")) && bSuccess; + } + + { + volatile int16 Value = 0x1000; + + // Test value isn't changed when comparand differs + bSuccess = TestInterlocked(&Value, 0x1000, 0x1000, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedCompareExchange(Dest, 0x1000, 0x0001); }, Test, FunctionName, TEXT("int16")) && bSuccess; + + // Test value is changed when comparand matches + bSuccess = TestInterlocked(&Value, 0x1000, 0x0001, [](volatile int16* Dest) { return FPlatformAtomics::InterlockedCompareExchange(Dest, 0x0001, 0x1000); }, Test, FunctionName, TEXT("int16")) && bSuccess; + } + + { + volatile int32 Value = 0x10000000; + + // Test value isn't changed when comparand differs + bSuccess = TestInterlocked(&Value, 0x10000000, 0x10000000, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedCompareExchange(Dest, 0x10000000, 0x00000101); }, Test, FunctionName, TEXT("int32")) && bSuccess; + + // Test value is changed when comparand matches + bSuccess = TestInterlocked(&Value, 0x10000000, 0x00000101, [](volatile int32* Dest) { return FPlatformAtomics::InterlockedCompareExchange(Dest, 0x00000101, 0x10000000); }, Test, FunctionName, TEXT("int32")) && bSuccess; + } + + { + alignas(8) volatile int64 Value = 0x1000000000000000LL; + + // Test value isn't changed when comparand differs + bSuccess = TestInterlocked(&Value, 0x1000000000000000LL, 0x1000000000000000LL, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedCompareExchange(Dest, 0x1000000000000000LL, 0x0000000001010101LL); }, Test, FunctionName, TEXT("int64")) && bSuccess; + + // Test value is changed when comparand matches + bSuccess = TestInterlocked(&Value, 0x1000000000000000LL, 0x0000000001010101LL, [](volatile int64* Dest) { return FPlatformAtomics::InterlockedCompareExchange(Dest, 0x0000000001010101LL, 0x1000000000000000LL); }, Test, FunctionName, TEXT("int64")) && bSuccess; + } + + return bSuccess; +} + +bool FPlatformAtomicsTest::RunTest(const FString& Parameters) +{ + bool bSuccess = true; + + bSuccess = TestInterlockedAnd(*this) && bSuccess; + bSuccess = TestInterlockedOr(*this) && bSuccess; + bSuccess = TestInterlockedXor(*this) && bSuccess; + bSuccess = TestInterlockedAdd(*this) && bSuccess; + bSuccess = TestInterlockedIncrement(*this) && bSuccess; + bSuccess = TestInterlockedDecrement(*this) && bSuccess; + bSuccess = TestInterlockedExchange(*this) && bSuccess; + bSuccess = TestInterlockedExchangePtr(*this) && bSuccess; + bSuccess = TestInterlockedCompareExchange(*this) && bSuccess; + + return bSuccess; +} + +#endif //WITH_DEV_AUTOMATION_TESTS diff --git a/Engine/Source/Runtime/Core/Private/Tests/Math/UnrealMathTest.cpp b/Engine/Source/Runtime/Core/Private/Tests/Math/UnrealMathTest.cpp index 242ba285f749..db0e4998e748 100644 --- a/Engine/Source/Runtime/Core/Private/Tests/Math/UnrealMathTest.cpp +++ b/Engine/Source/Runtime/Core/Private/Tests/Math/UnrealMathTest.cpp @@ -1071,6 +1071,18 @@ bool FVectorRegisterAbstractionTest::RunTest(const FString& Parameters) V3 = VectorMod(V0, V1); LogTest( TEXT("VectorMod negative"), TestVectorsEqual(V2, V3)); + // VectorSign + V0 = MakeVectorRegister(2.0f, -2.0f, 0.0f, -3.0f); + V2 = MakeVectorRegister(1.0f, -1.0f, 1.0f, -1.0f); + V3 = VectorSign(V0); + LogTest(TEXT("VectorSign"), TestVectorsEqual(V2, V3)); + + // VectorStep + V0 = MakeVectorRegister(2.0f, -2.0f, 0.0f, -3.0f); + V2 = MakeVectorRegister(1.0f, 0.0f, 1.0f, 0.0f); + V3 = VectorStep(V0); + LogTest(TEXT("VectorStep"), TestVectorsEqual(V2, V3)); + FMatrix M0, M1, M2, M3; FVector Eye, LookAt, Up; // Create Look at Matrix @@ -1600,4 +1612,152 @@ bool FInterpolationFunctionTests::RunTest(const FString&) return true; } +IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMathRoundHalfToZeroTests, "System.Core.Math.Round HalfToZero", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::SmokeFilter) +bool FMathRoundHalfToZeroTests::RunTest(const FString& Parameters) +{ + TestEqual(TEXT("RoundHalfToZero32-Zero"), FMath::RoundHalfToZero(0.0f), 0.0f); + TestEqual(TEXT("RoundHalfToZero32-One"), FMath::RoundHalfToZero(1.0f), 1.0f); + TestEqual(TEXT("RoundHalfToZero32-LessHalf"), FMath::RoundHalfToZero(1.4f), 1.0f); + TestEqual(TEXT("RoundHalfToZero32-NegGreaterHalf"), FMath::RoundHalfToZero(-1.4f), -1.0f); + TestEqual(TEXT("RoundHalfToZero32-LessNearHalf"), FMath::RoundHalfToZero(1.4999999f), 1.0f); + TestEqual(TEXT("RoundHalfToZero32-NegGreaterNearHalf"), FMath::RoundHalfToZero(-1.4999999f), -1.0f); + TestEqual(TEXT("RoundHalfToZero32-Half"), FMath::RoundHalfToZero(1.5f), 1.0f); + TestEqual(TEXT("RoundHalfToZero32-NegHalf"), FMath::RoundHalfToZero(-1.5f), -1.0f); + TestEqual(TEXT("RoundHalfToZero32-GreaterNearHalf"), FMath::RoundHalfToZero(1.5000001f), 2.0f); + TestEqual(TEXT("RoundHalfToZero32-NegLesserNearHalf"), FMath::RoundHalfToZero(-1.5000001f), -2.0f); + TestEqual(TEXT("RoundHalfToZero32-GreaterThanHalf"), FMath::RoundHalfToZero(1.6f), 2.0f); + TestEqual(TEXT("RoundHalfToZero32-NegLesserThanHalf"), FMath::RoundHalfToZero(-1.6f), -2.0f); + + TestEqual(TEXT("RoundHalfToZero32-TwoToOneBitPrecision"), FMath::RoundHalfToZero(4194303.25f), 4194303.0f); + TestEqual(TEXT("RoundHalfToZero32-TwoToOneBitPrecision"), FMath::RoundHalfToZero(4194303.5f), 4194303.0f); + TestEqual(TEXT("RoundHalfToZero32-TwoToOneBitPrecision"), FMath::RoundHalfToZero(4194303.75f), 4194304.0f); + TestEqual(TEXT("RoundHalfToZero32-TwoToOneBitPrecision"), FMath::RoundHalfToZero(4194304.0f), 4194304.0f); + TestEqual(TEXT("RoundHalfToZero32-TwoToOneBitPrecision"), FMath::RoundHalfToZero(4194304.5f), 4194304.0f); + TestEqual(TEXT("RoundHalfToZero32-NegTwoToOneBitPrecision"), FMath::RoundHalfToZero(-4194303.25f), -4194303.0f); + TestEqual(TEXT("RoundHalfToZero32-NegTwoToOneBitPrecision"), FMath::RoundHalfToZero(-4194303.5f), -4194303.0f); + TestEqual(TEXT("RoundHalfToZero32-NegTwoToOneBitPrecision"), FMath::RoundHalfToZero(-4194303.75f), -4194304.0f); + TestEqual(TEXT("RoundHalfToZero32-NegTwoToOneBitPrecision"), FMath::RoundHalfToZero(-4194304.0f), -4194304.0f); + TestEqual(TEXT("RoundHalfToZero32-NegTwoToOneBitPrecision"), FMath::RoundHalfToZero(-4194304.5f), -4194304.0f); + + TestEqual(TEXT("RoundHalfToZero32-OneToZeroBitPrecision"), FMath::RoundHalfToZero(8388607.0f), 8388607.0f); + TestEqual(TEXT("RoundHalfToZero32-OneToZeroBitPrecision"), FMath::RoundHalfToZero(8388607.5f), 8388607.0f); + TestEqual(TEXT("RoundHalfToZero32-OneToZeroBitPrecision"), FMath::RoundHalfToZero(8388608.0f), 8388608.0f); + TestEqual(TEXT("RoundHalfToZero32-NegOneToZeroBitPrecision"), FMath::RoundHalfToZero(-8388607.0f), -8388607.0f); + TestEqual(TEXT("RoundHalfToZero32-NegOneToZeroBitPrecision"), FMath::RoundHalfToZero(-8388607.5f), -8388607.0f); + TestEqual(TEXT("RoundHalfToZero32-NegOneToZeroBitPrecision"), FMath::RoundHalfToZero(-8388608.0f), -8388608.0f); + + TestEqual(TEXT("RoundHalfToZero32-ZeroBitPrecision"), FMath::RoundHalfToZero(16777215.0f), 16777215.0f); + TestEqual(TEXT("RoundHalfToZero32-NegZeroBitPrecision"), FMath::RoundHalfToZero(-16777215.0f), -16777215.0f); + + TestEqual(TEXT("RoundHalfToZero64-Zero"), FMath::RoundHalfToZero(0.0), 0.0); + TestEqual(TEXT("RoundHalfToZero64-One"), FMath::RoundHalfToZero(1.0), 1.0); + TestEqual(TEXT("RoundHalfToZero64-LessHalf"), FMath::RoundHalfToZero(1.4), 1.0); + TestEqual(TEXT("RoundHalfToZero64-NegGreaterHalf"), FMath::RoundHalfToZero(-1.4), -1.0); + TestEqual(TEXT("RoundHalfToZero64-LessNearHalf"), FMath::RoundHalfToZero(1.4999999999999997), 1.0); + TestEqual(TEXT("RoundHalfToZero64-NegGreaterNearHalf"), FMath::RoundHalfToZero(-1.4999999999999997), -1.0); + TestEqual(TEXT("RoundHalfToZero64-Half"), FMath::RoundHalfToZero(1.5), 1.0); + TestEqual(TEXT("RoundHalfToZero64-NegHalf"), FMath::RoundHalfToZero(-1.5), -1.0); + TestEqual(TEXT("RoundHalfToZero64-GreaterNearHalf"), FMath::RoundHalfToZero(1.5000000000000002), 2.0); + TestEqual(TEXT("RoundHalfToZero64-NegLesserNearHalf"), FMath::RoundHalfToZero(-1.5000000000000002), -2.0); + TestEqual(TEXT("RoundHalfToZero64-GreaterThanHalf"), FMath::RoundHalfToZero(1.6), 2.0); + TestEqual(TEXT("RoundHalfToZero64-NegLesserThanHalf"), FMath::RoundHalfToZero(-1.6), -2.0); + + TestEqual(TEXT("RoundHalfToZero64-TwoToOneBitPrecision"), FMath::RoundHalfToZero(2251799813685247.25), 2251799813685247.0); + TestEqual(TEXT("RoundHalfToZero64-TwoToOneBitPrecision"), FMath::RoundHalfToZero(2251799813685247.5), 2251799813685247.0); + TestEqual(TEXT("RoundHalfToZero64-TwoToOneBitPrecision"), FMath::RoundHalfToZero(2251799813685247.75), 2251799813685248.0); + TestEqual(TEXT("RoundHalfToZero64-TwoToOneBitPrecision"), FMath::RoundHalfToZero(2251799813685248.0), 2251799813685248.0); + TestEqual(TEXT("RoundHalfToZero64-TwoToOneBitPrecision"), FMath::RoundHalfToZero(2251799813685248.5), 2251799813685248.0); + TestEqual(TEXT("RoundHalfToZero64-NegTwoToOneBitPrecision"), FMath::RoundHalfToZero(-2251799813685247.25), -2251799813685247.0); + TestEqual(TEXT("RoundHalfToZero64-NegTwoToOneBitPrecision"), FMath::RoundHalfToZero(-2251799813685247.5), -2251799813685247.0); + TestEqual(TEXT("RoundHalfToZero64-NegTwoToOneBitPrecision"), FMath::RoundHalfToZero(-2251799813685247.75), -2251799813685248.0); + TestEqual(TEXT("RoundHalfToZero64-NegTwoToOneBitPrecision"), FMath::RoundHalfToZero(-2251799813685248.0), -2251799813685248.0); + TestEqual(TEXT("RoundHalfToZero64-NegTwoToOneBitPrecision"), FMath::RoundHalfToZero(-2251799813685248.5), -2251799813685248.0); + + TestEqual(TEXT("RoundHalfToZero64-OneToZeroBitPrecision"), FMath::RoundHalfToZero(4503599627370495.0), 4503599627370495.0); + TestEqual(TEXT("RoundHalfToZero64-OneToZeroBitPrecision"), FMath::RoundHalfToZero(4503599627370495.5), 4503599627370495.0); + TestEqual(TEXT("RoundHalfToZero64-OneToZeroBitPrecision"), FMath::RoundHalfToZero(4503599627370496.0), 4503599627370496.0); + TestEqual(TEXT("RoundHalfToZero64-NegOneToZeroBitPrecision"), FMath::RoundHalfToZero(-4503599627370495.0), -4503599627370495.0); + TestEqual(TEXT("RoundHalfToZero64-NegOneToZeroBitPrecision"), FMath::RoundHalfToZero(-4503599627370495.5), -4503599627370495.0); + TestEqual(TEXT("RoundHalfToZero64-NegOneToZeroBitPrecision"), FMath::RoundHalfToZero(-4503599627370496.0), -4503599627370496.0); + + TestEqual(TEXT("RoundHalfToZero64-ZeroBitPrecision"), FMath::RoundHalfToZero(9007199254740991.0), 9007199254740991.0); + TestEqual(TEXT("RoundHalfToZero64-NegZeroBitPrecision"), FMath::RoundHalfToZero(-9007199254740991.0), -9007199254740991.0); + + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMathRoundHalfFromZeroTests, "System.Core.Math.Round HalfFromZero", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::SmokeFilter) +bool FMathRoundHalfFromZeroTests::RunTest(const FString& Parameters) +{ + TestEqual(TEXT("RoundHalfFromZero32-Zero"), FMath::RoundHalfFromZero(0.0f), 0.0f); + TestEqual(TEXT("RoundHalfFromZero32-One"), FMath::RoundHalfFromZero(1.0f), 1.0f); + TestEqual(TEXT("RoundHalfFromZero32-LessHalf"), FMath::RoundHalfFromZero(1.4f), 1.0f); + TestEqual(TEXT("RoundHalfFromZero32-NegGreaterHalf"), FMath::RoundHalfFromZero(-1.4f), -1.0f); + TestEqual(TEXT("RoundHalfFromZero32-LessNearHalf"), FMath::RoundHalfFromZero(1.4999999f), 1.0f); + TestEqual(TEXT("RoundHalfFromZero32-NegGreaterNearHalf"), FMath::RoundHalfFromZero(-1.4999999f), -1.0f); + TestEqual(TEXT("RoundHalfFromZero32-Half"), FMath::RoundHalfFromZero(1.5f), 2.0f); + TestEqual(TEXT("RoundHalfFromZero32-NegHalf"), FMath::RoundHalfFromZero(-1.5f), -2.0f); + TestEqual(TEXT("RoundHalfFromZero32-LessGreaterNearHalf"), FMath::RoundHalfFromZero(1.5000001f), 2.0f); + TestEqual(TEXT("RoundHalfFromZero32-NegLesserNearHalf"), FMath::RoundHalfFromZero(-1.5000001f), -2.0f); + TestEqual(TEXT("RoundHalfFromZero32-GreaterThanHalf"), FMath::RoundHalfFromZero(1.6f), 2.0f); + TestEqual(TEXT("RoundHalfFromZero32-NegLesserThanHalf"), FMath::RoundHalfFromZero(-1.6f), -2.0f); + + TestEqual(TEXT("RoundHalfFromZero32-TwoToOneBitPrecision"), FMath::RoundHalfFromZero(4194303.25f), 4194303.0f); + TestEqual(TEXT("RoundHalfFromZero32-TwoToOneBitPrecision"), FMath::RoundHalfFromZero(4194303.5f), 4194304.0f); + TestEqual(TEXT("RoundHalfFromZero32-TwoToOneBitPrecision"), FMath::RoundHalfFromZero(4194303.75f), 4194304.0f); + TestEqual(TEXT("RoundHalfFromZero32-TwoToOneBitPrecision"), FMath::RoundHalfFromZero(4194304.0f), 4194304.0f); + TestEqual(TEXT("RoundHalfFromZero32-TwoToOneBitPrecision"), FMath::RoundHalfFromZero(4194304.5f), 4194305.0f); + TestEqual(TEXT("RoundHalfFromZero32-NegTwoToOneBitPrecision"), FMath::RoundHalfFromZero(-4194303.25f), -4194303.0f); + TestEqual(TEXT("RoundHalfFromZero32-NegTwoToOneBitPrecision"), FMath::RoundHalfFromZero(-4194303.5f), -4194304.0f); + TestEqual(TEXT("RoundHalfFromZero32-NegTwoToOneBitPrecision"), FMath::RoundHalfFromZero(-4194303.75f), -4194304.0f); + TestEqual(TEXT("RoundHalfFromZero32-NegTwoToOneBitPrecision"), FMath::RoundHalfFromZero(-4194304.0f), -4194304.0f); + TestEqual(TEXT("RoundHalfFromZero32-NegTwoToOneBitPrecision"), FMath::RoundHalfFromZero(-4194304.5f), -4194305.0f); + + TestEqual(TEXT("RoundHalfFromZero32-OneToZeroBitPrecision"), FMath::RoundHalfFromZero(8388607.0f), 8388607.0f); + TestEqual(TEXT("RoundHalfFromZero32-OneToZeroBitPrecision"), FMath::RoundHalfFromZero(8388607.5f), 8388608.0f); + TestEqual(TEXT("RoundHalfFromZero32-OneToZeroBitPrecision"), FMath::RoundHalfFromZero(8388608.0f), 8388608.0f); + TestEqual(TEXT("RoundHalfFromZero32-NegOneToZeroBitPrecision"), FMath::RoundHalfFromZero(-8388607.0f), -8388607.0f); + TestEqual(TEXT("RoundHalfFromZero32-NegOneToZeroBitPrecision"), FMath::RoundHalfFromZero(-8388607.5f), -8388608.0f); + TestEqual(TEXT("RoundHalfFromZero32-NegOneToZeroBitPrecision"), FMath::RoundHalfFromZero(-8388608.0f), -8388608.0f); + + TestEqual(TEXT("RoundHalfFromZero32-ZeroBitPrecision"), FMath::RoundHalfToZero(16777215.0f), 16777215.0f); + TestEqual(TEXT("RoundHalfFromZero32-NegZeroBitPrecision"), FMath::RoundHalfToZero(-16777215.0f), -16777215.0f); + + TestEqual(TEXT("RoundHalfFromZero64-Zero"), FMath::RoundHalfFromZero(0.0), 0.0); + TestEqual(TEXT("RoundHalfFromZero64-One"), FMath::RoundHalfFromZero(1.0), 1.0); + TestEqual(TEXT("RoundHalfFromZero64-LessHalf"), FMath::RoundHalfFromZero(1.4), 1.0); + TestEqual(TEXT("RoundHalfFromZero64-NegGreaterHalf"), FMath::RoundHalfFromZero(-1.4), -1.0); + TestEqual(TEXT("RoundHalfFromZero64-LessNearHalf"), FMath::RoundHalfFromZero(1.4999999999999997), 1.0); + TestEqual(TEXT("RoundHalfFromZero64-NegGreaterNearHalf"), FMath::RoundHalfFromZero(-1.4999999999999997), -1.0); + TestEqual(TEXT("RoundHalfFromZero64-Half"), FMath::RoundHalfFromZero(1.5), 2.0); + TestEqual(TEXT("RoundHalfFromZero64-NegHalf"), FMath::RoundHalfFromZero(-1.5), -2.0); + TestEqual(TEXT("RoundHalfFromZero64-LessGreaterNearHalf"), FMath::RoundHalfFromZero(1.5000000000000002), 2.0); + TestEqual(TEXT("RoundHalfFromZero64-NegLesserNearHalf"), FMath::RoundHalfFromZero(-1.5000000000000002), -2.0); + TestEqual(TEXT("RoundHalfFromZero64-GreaterThanHalf"), FMath::RoundHalfFromZero(1.6), 2.0); + TestEqual(TEXT("RoundHalfFromZero64-NegLesserThanHalf"), FMath::RoundHalfFromZero(-1.6), -2.0); + + TestEqual(TEXT("RoundHalfFromZero64-TwoToOneBitPrecision"), FMath::RoundHalfFromZero(2251799813685247.25), 2251799813685247.0); + TestEqual(TEXT("RoundHalfFromZero64-TwoToOneBitPrecision"), FMath::RoundHalfFromZero(2251799813685247.5), 2251799813685248.0); + TestEqual(TEXT("RoundHalfFromZero64-TwoToOneBitPrecision"), FMath::RoundHalfFromZero(2251799813685247.75), 2251799813685248.0); + TestEqual(TEXT("RoundHalfFromZero64-TwoToOneBitPrecision"), FMath::RoundHalfFromZero(2251799813685248.0), 2251799813685248.0); + TestEqual(TEXT("RoundHalfFromZero64-TwoToOneBitPrecision"), FMath::RoundHalfFromZero(2251799813685248.5), 2251799813685249.0); + TestEqual(TEXT("RoundHalfFromZero64-NegTwoToOneBitPrecision"), FMath::RoundHalfFromZero(-2251799813685247.25), -2251799813685247.0); + TestEqual(TEXT("RoundHalfFromZero64-NegTwoToOneBitPrecision"), FMath::RoundHalfFromZero(-2251799813685247.5), -2251799813685248.0); + TestEqual(TEXT("RoundHalfFromZero64-NegTwoToOneBitPrecision"), FMath::RoundHalfFromZero(-2251799813685247.75), -2251799813685248.0); + TestEqual(TEXT("RoundHalfFromZero64-NegTwoToOneBitPrecision"), FMath::RoundHalfFromZero(-2251799813685248.0), -2251799813685248.0); + TestEqual(TEXT("RoundHalfFromZero64-NegTwoToOneBitPrecision"), FMath::RoundHalfFromZero(-2251799813685248.5), -2251799813685249.0); + + TestEqual(TEXT("RoundHalfFromZero64-OneToZeroBitPrecision"), FMath::RoundHalfFromZero(4503599627370495.0), 4503599627370495.0); + TestEqual(TEXT("RoundHalfFromZero64-OneToZeroBitPrecision"), FMath::RoundHalfFromZero(4503599627370495.5), 4503599627370496.0); + TestEqual(TEXT("RoundHalfFromZero64-OneToZeroBitPrecision"), FMath::RoundHalfFromZero(4503599627370496.0), 4503599627370496.0); + TestEqual(TEXT("RoundHalfFromZero64-NegOneToZeroBitPrecision"), FMath::RoundHalfFromZero(-4503599627370495.0), -4503599627370495.0); + TestEqual(TEXT("RoundHalfFromZero64-NegOneToZeroBitPrecision"), FMath::RoundHalfFromZero(-4503599627370495.5), -4503599627370496.0); + TestEqual(TEXT("RoundHalfFromZero64-NegOneToZeroBitPrecision"), FMath::RoundHalfFromZero(-4503599627370496.0), -4503599627370496.0); + + TestEqual(TEXT("RoundHalfFromZero64-ZeroBitPrecision"), FMath::RoundHalfToZero(9007199254740991.0), 9007199254740991.0); + TestEqual(TEXT("RoundHalfFromZero64-NegZeroBitPrecision"), FMath::RoundHalfToZero(-9007199254740991.0), -9007199254740991.0); + + return true; +} + #endif //WITH_DEV_AUTOMATION_TESTS diff --git a/Engine/Source/Runtime/Core/Private/Tests/Misc/AsciiSetTest.cpp b/Engine/Source/Runtime/Core/Private/Tests/Misc/AsciiSetTest.cpp new file mode 100644 index 000000000000..23777a8733a2 --- /dev/null +++ b/Engine/Source/Runtime/Core/Private/Tests/Misc/AsciiSetTest.cpp @@ -0,0 +1,62 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_DEV_AUTOMATION_TESTS + +#include "Misc/AsciiSet.h" +#include "Misc/AutomationTest.h" + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(TAsciiSetTest, "System.Core.Misc.AsciiSet", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::SmokeFilter) + +bool TAsciiSetTest::RunTest(const FString& Parameters) +{ + constexpr FAsciiSet Whitespaces(" \v\f\t\r\n"); + TestTrue(TEXT("Contains"), Whitespaces.Contains(' ')); + TestTrue(TEXT("Contains"), Whitespaces.Contains('\n')); + TestFalse(TEXT("Contains"), Whitespaces.Contains('a')); + + constexpr FAsciiSet NonWhitespaces = ~Whitespaces; + uint32 WhitespaceNum = 0; + for (uint8 Char = 0; Char < 128; ++Char) + { + WhitespaceNum += !!Whitespaces.Test(Char); + TestEqual(TEXT("Inverse"), !!Whitespaces.Test(Char), !NonWhitespaces.Test(Char)); + } + TestEqual(TEXT("Num"), WhitespaceNum, 6); + + TestEqual(TEXT("Skip"), FAsciiSet::Skip(TEXT(" \t\tHello world!"), Whitespaces), TEXT("Hello world!")); + TestEqual(TEXT("Skip"), FAsciiSet::Skip(TEXT("Hello world!"), Whitespaces), TEXT("Hello world!")); + TestEqual(TEXT("AdvanceToFirst"), *FAsciiSet::FindFirstOrEnd("NonWhitespace\t \nNonWhitespace", Whitespaces), '\t'); + TestEqual(TEXT("AdvanceToLast"), *FAsciiSet::FindLastOrEnd("NonWhitespace\t \nNonWhitespace", Whitespaces), '\n'); + TestEqual(TEXT("AdvanceToLast"), *FAsciiSet::FindLastOrEnd("NonWhitespace\t NonWhitespace\n", Whitespaces), '\n'); + TestEqual(TEXT("AdvanceToFirst"), *FAsciiSet::FindFirstOrEnd("NonWhitespaceNonWhitespace", Whitespaces), '\0'); + TestEqual(TEXT("AdvanceToLast"), *FAsciiSet::FindLastOrEnd("NonWhitespaceNonWhitespace", Whitespaces), '\0'); + + constexpr FAsciiSet XmlEscapeChars("&<>\"'"); + TestTrue(TEXT("None"), FAsciiSet::HasNone("No escape chars", XmlEscapeChars)); + TestFalse(TEXT("Any"), FAsciiSet::HasAny("No escape chars", XmlEscapeChars)); + TestFalse(TEXT("Only"), FAsciiSet::HasOnly("No escape chars", XmlEscapeChars)); + + TestTrue(TEXT("None"), FAsciiSet::HasNone("", XmlEscapeChars)); + TestFalse(TEXT("Any"), FAsciiSet::HasAny("", XmlEscapeChars)); + TestTrue(TEXT("Only"), FAsciiSet::HasOnly("", XmlEscapeChars)); + + TestFalse(TEXT("None"), FAsciiSet::HasNone("&<>\"'", XmlEscapeChars)); + TestTrue(TEXT("Any"), FAsciiSet::HasAny("&<>\"'", XmlEscapeChars)); + TestTrue(TEXT("Only"), FAsciiSet::HasOnly("&<>\"'", XmlEscapeChars)); + + TestFalse(TEXT("None"), FAsciiSet::HasNone("&<>\"' and more", XmlEscapeChars)); + TestTrue(TEXT("Any"), FAsciiSet::HasAny("&<>\"' and more", XmlEscapeChars)); + TestFalse(TEXT("Only"), FAsciiSet::HasOnly("&<>\"' and more", XmlEscapeChars)); + + constexpr FAsciiSet Abc("abc"); + constexpr FAsciiSet Abcd = Abc + 'd'; + TestTrue(TEXT("Add"), Abcd.Contains('a')); + TestTrue(TEXT("Add"), Abcd.Contains('b')); + TestTrue(TEXT("Add"), Abcd.Contains('c')); + TestTrue(TEXT("Add"), Abcd.Contains('d')); + TestFalse(TEXT("Add"), Abcd.Contains('e')); + + return true; +} + +#endif //WITH_DEV_AUTOMATION_TESTS \ No newline at end of file diff --git a/Engine/Source/Runtime/Core/Private/Tests/Misc/AtomicTest.cpp b/Engine/Source/Runtime/Core/Private/Tests/Misc/AtomicTest.cpp index b6194d136fef..daaf3b8576eb 100644 --- a/Engine/Source/Runtime/Core/Private/Tests/Misc/AtomicTest.cpp +++ b/Engine/Source/Runtime/Core/Private/Tests/Misc/AtomicTest.cpp @@ -13,7 +13,7 @@ namespace { // Returns an element type filled with a repeated byte template - FORCEINLINE typename TEnableIf::Type GetByteFilledElement(uint8 Byte) + FORCEINLINE constexpr typename TEnableIf::Type GetByteFilledElement(uint8 Byte) { TUnsignedIntType_T Result = 0; @@ -27,7 +27,7 @@ namespace } template - FORCEINLINE typename TEnableIf::Type GetByteFilledElement(uint8 Byte) + FORCEINLINE constexpr typename TEnableIf::Type GetByteFilledElement(uint8 Byte) { return (const ElementType&)Byte; } @@ -165,12 +165,181 @@ namespace NativeVal = Data.GetNative()--; AtomicVal = Data.GetAtomic()--; check(NativeVal == AtomicVal); + NativeVal = Data.GetNative(); + AtomicVal = Data.GetAtomic(); + check(NativeVal == AtomicVal); Data.Check(); NativeVal = Data.GetNative()++; AtomicVal = Data.GetAtomic()++; check(NativeVal == AtomicVal); + NativeVal = Data.GetNative(); + AtomicVal = Data.GetAtomic(); + check(NativeVal == AtomicVal); Data.Check(); + + NativeVal = Data.GetNative()--; + AtomicVal = Data.GetAtomic().DecrementExchange(); + check(NativeVal == AtomicVal); + NativeVal = Data.GetNative(); + AtomicVal = Data.GetAtomic(); + check(NativeVal == AtomicVal); + Data.Check(); + + NativeVal = Data.GetNative()++; + AtomicVal = Data.GetAtomic().IncrementExchange(); + check(NativeVal == AtomicVal); + NativeVal = Data.GetNative(); + AtomicVal = Data.GetAtomic(); + check(NativeVal == AtomicVal); + Data.Check(); + + NativeVal = Data.GetNative(); + AtomicVal = Data.GetAtomic().AddExchange(47); + check(NativeVal == AtomicVal); + NativeVal = Data.GetNative() += 47; + AtomicVal = Data.GetAtomic(); + check(NativeVal == AtomicVal); + Data.Check(); + + NativeVal = Data.GetNative(); + AtomicVal = Data.GetAtomic().AddExchange(-11); + check(NativeVal == AtomicVal); + NativeVal = Data.GetNative() += -11; + AtomicVal = Data.GetAtomic(); + check(NativeVal == AtomicVal); + Data.Check(); + + NativeVal = Data.GetNative(); + AtomicVal = Data.GetAtomic().SubExchange(2); + check(NativeVal == AtomicVal); + NativeVal = Data.GetNative() -= 2; + AtomicVal = Data.GetAtomic(); + check(NativeVal == AtomicVal); + Data.Check(); + + NativeVal = Data.GetNative(); + AtomicVal = Data.GetAtomic().SubExchange(-9); + check(NativeVal == AtomicVal); + NativeVal = Data.GetNative() -= -9; + AtomicVal = Data.GetAtomic(); + check(NativeVal == AtomicVal); + Data.Check(); + } + + template + void RunBitwiseOperationsAtomicTests() + { + ElementType NativeVal; + ElementType AtomicVal; + + // And + { + constexpr ElementType Init = GetByteFilledElement(0x30); + constexpr ElementType AndValues[] = {GetByteFilledElement(0x66), GetByteFilledElement(0xFF), ElementType(0), }; + constexpr ElementType ExpectedValues[] = {GetByteFilledElement(0x20), Init, ElementType(0), }; + + // operator &= + for (SIZE_T TestIt = 0; TestIt < ARRAY_COUNT(AndValues); ++TestIt) + { + TAtomicTestWrapper Data(Init); + + AtomicVal = Data.GetAtomic() &= AndValues[TestIt]; + NativeVal = Data.GetNative() &= AndValues[TestIt]; + check(NativeVal == AtomicVal); + + check(AtomicVal == ExpectedValues[TestIt]); + Data.Check(); + } + + // AndExchange + for (SIZE_T TestIt = 0; TestIt < ARRAY_COUNT(AndValues); ++TestIt) + { + TAtomicTestWrapper Data(Init); + + AtomicVal = Data.GetAtomic().AndExchange(AndValues[TestIt]); + NativeVal = Data.GetNative(); + check(NativeVal == AtomicVal); + NativeVal = Data.GetNative() &= AndValues[TestIt]; + AtomicVal = Data.GetAtomic().Load(); + check(NativeVal == AtomicVal); + + check(AtomicVal == ExpectedValues[TestIt]); + Data.Check(); + } + } + + // Or + { + constexpr ElementType Init = GetByteFilledElement(0x30); + constexpr ElementType OrValues[] = {GetByteFilledElement(0x66), GetByteFilledElement(0xFF), ElementType(0), }; + constexpr ElementType ExpectedValues[] = {GetByteFilledElement(0x76), GetByteFilledElement(0xFF), Init, }; + + // operator |= + for (SIZE_T TestIt = 0; TestIt < ARRAY_COUNT(OrValues); ++TestIt) + { + TAtomicTestWrapper Data(Init); + + AtomicVal = Data.GetAtomic() |= OrValues[TestIt]; + NativeVal = Data.GetNative() |= OrValues[TestIt]; + check(NativeVal == AtomicVal); + + check(AtomicVal == ExpectedValues[TestIt]); + Data.Check(); + } + + // OrExchange + for (SIZE_T TestIt = 0; TestIt < ARRAY_COUNT(OrValues); ++TestIt) + { + TAtomicTestWrapper Data(Init); + + AtomicVal = Data.GetAtomic().OrExchange(OrValues[TestIt]); + NativeVal = Data.GetNative(); + check(NativeVal == AtomicVal); + NativeVal = Data.GetNative() |= OrValues[TestIt]; + AtomicVal = Data.GetAtomic().Load(); + check(NativeVal == AtomicVal); + + check(AtomicVal == ExpectedValues[TestIt]); + Data.Check(); + } + } + + // Xor + { + constexpr ElementType Init = GetByteFilledElement(0x30); + constexpr ElementType XorValues[] = {GetByteFilledElement(0x66), GetByteFilledElement(0xFF), ElementType(0), }; + constexpr ElementType ExpectedValues[] = {GetByteFilledElement(0x56), GetByteFilledElement(~0x30), Init, }; + + // operator ^= + for (SIZE_T TestIt = 0; TestIt < ARRAY_COUNT(XorValues); ++TestIt) + { + TAtomicTestWrapper Data(Init); + + AtomicVal = Data.GetAtomic() ^= XorValues[TestIt]; + NativeVal = Data.GetNative() ^= XorValues[TestIt]; + check(NativeVal == AtomicVal); + + check(AtomicVal == ExpectedValues[TestIt]); + Data.Check(); + } + + // XorExchange + for (SIZE_T TestIt = 0; TestIt < ARRAY_COUNT(XorValues); ++TestIt) + { + TAtomicTestWrapper Data(Init); + + AtomicVal = Data.GetAtomic().XorExchange(XorValues[TestIt]); + NativeVal = Data.GetNative(); + check(NativeVal == AtomicVal); + NativeVal = Data.GetNative() ^= XorValues[TestIt]; + AtomicVal = Data.GetAtomic().Load(); + check(NativeVal == AtomicVal); + + check(AtomicVal == ExpectedValues[TestIt]); + Data.Check(); + } + } } template @@ -179,6 +348,8 @@ namespace RunBasicAtomicTests(); RunArithmeticAtomicTests(50); + + RunBitwiseOperationsAtomicTests(); } template diff --git a/Engine/Source/Runtime/Core/Private/Tests/Misc/CharTest.cpp b/Engine/Source/Runtime/Core/Private/Tests/Misc/CharTest.cpp index ae40fa8e3419..d4f0cfe32196 100644 --- a/Engine/Source/Runtime/Core/Private/Tests/Misc/CharTest.cpp +++ b/Engine/Source/Runtime/Core/Private/Tests/Misc/CharTest.cpp @@ -32,10 +32,16 @@ void RunCharTests(FAutomationTestBase& Test, uint32 MaxChar) bool TCharTest::RunTest(const FString& Parameters) { - TestTrue(TEXT("C locale not used"), strcmp("C", setlocale(LC_CTYPE, nullptr)) == 0); - - RunCharTests(*this, 128); - RunCharTests(*this, 65536); + const char* CurrentLocale = setlocale(LC_CTYPE, nullptr); + if (strcmp("C", CurrentLocale)) + { + AddError(FString::Printf(TEXT("Locale is \"%s\" but should be \"C\". Did something call setlocale()?"), CurrentLocale)); + } + else + { + RunCharTests(*this, 128); + RunCharTests(*this, 65536); + } return true; } diff --git a/Engine/Source/Runtime/Core/Private/UObject/UnrealNames.cpp b/Engine/Source/Runtime/Core/Private/UObject/UnrealNames.cpp index 20137bac1fab..98a9f0475dde 100644 --- a/Engine/Source/Runtime/Core/Private/UObject/UnrealNames.cpp +++ b/Engine/Source/Runtime/Core/Private/UObject/UnrealNames.cpp @@ -15,7 +15,7 @@ #include "Misc/ByteSwap.h" #include "UObject/ObjectVersion.h" #include "HAL/ThreadSafeCounter.h" -#include "Misc/ScopeLock.h" +#include "Misc/ScopeRWLock.h" #include "Containers/Set.h" #include "Internationalization/Text.h" #include "Internationalization/Internationalization.h" @@ -23,13 +23,931 @@ #include "HAL/IConsoleManager.h" #include "HAL/LowLevelMemTracker.h" #include "Serialization/ArchiveFromStructuredArchive.h" - +#include "Hash/CityHash.h" DEFINE_LOG_CATEGORY_STATIC(LogUnrealNames, Log, All); +const TCHAR* LexToString(EName Ename) +{ + switch (Ename) + { +#define REGISTER_NAME(num,namestr) case num: return TEXT(#namestr); +#include "UObject/UnrealNames.inl" +#undef REGISTER_NAME + default: + return TEXT("*INVALID*"); + } +} + +int32 FNameEntry::GetDataOffset() +{ + return STRUCT_OFFSET(FNameEntry, AnsiName); +} + /*----------------------------------------------------------------------------- FName helpers. -----------------------------------------------------------------------------*/ + +static bool operator==(FNameEntryHeader A, FNameEntryHeader B) +{ + static_assert(sizeof(FNameEntryHeader) == 2, ""); + return (uint16&)A == (uint16&)B; +} + +template +ToCharType* ConvertInPlace(FromCharType* Str, uint32 Len) +{ + static_assert(TIsSame::Value, "Unsupported conversion"); + return Str; +} + +template<> +WIDECHAR* ConvertInPlace(ANSICHAR* Str, uint32 Len) +{ + for (uint32 Index = Len; Index--; ) + { + reinterpret_cast(Str)[Index] = Str[Index]; + } + + return reinterpret_cast(Str); +} + +template<> +ANSICHAR* ConvertInPlace(WIDECHAR* Str, uint32 Len) +{ + for (uint32 Index = 0; Index < Len; ++Index) + { + reinterpret_cast(Str)[Index] = Str[Index]; + } + + return reinterpret_cast(Str); +} + +union FNameBuffer +{ + ANSICHAR AnsiName[NAME_SIZE]; + WIDECHAR WideName[NAME_SIZE]; +}; + +struct FNameStringView +{ + FNameStringView() : Data(nullptr), Len(0), bIsWide(false) {} + FNameStringView(const ANSICHAR* Str, uint32 Len_) : Ansi(Str), Len(Len_), bIsWide(false) {} + FNameStringView(const WIDECHAR* Str, uint32 Len_) : Wide(Str), Len(Len_), bIsWide(true) {} + + union + { + const void* Data; + const ANSICHAR* Ansi; + const WIDECHAR* Wide; + }; + + uint32 Len; + bool bIsWide; + + bool IsAnsi() const { return !bIsWide; } + + int32 BytesWithTerminator() const + { + return (Len + 1) * (bIsWide ? sizeof(WIDECHAR) : sizeof(ANSICHAR)); + } + + int32 BytesWithoutTerminator() const + { + return Len * (bIsWide ? sizeof(WIDECHAR) : sizeof(ANSICHAR)); + } +}; + +template +FORCEINLINE bool EqualsSameDimensions(FNameStringView A, FNameStringView B) +{ + checkSlow(A.Len == B.Len && A.IsAnsi() == B.IsAnsi()); + + int32 Len = A.Len; + + if (Sensitivity == ENameCase::CaseSensitive) + { + return B.IsAnsi() ? !FPlatformString::Strncmp(A.Ansi, B.Ansi, Len) : !FPlatformString::Strncmp(A.Wide, B.Wide, Len); + } + else + { + return B.IsAnsi() ? !FPlatformString::Strnicmp(A.Ansi, B.Ansi, Len) : !FPlatformString::Strnicmp(A.Wide, B.Wide, Len); + } + +} + +template +FORCEINLINE bool Equals(FNameStringView A, FNameStringView B) +{ + return (A.Len == B.Len & A.IsAnsi() == B.IsAnsi()) && EqualsSameDimensions(A, B); +} + +// Minimize stack lifetime of large decode buffers +#ifdef WITH_CUSTOM_NAME_ENCODING +#define OUTLINE_DECODE_BUFFER FORCENOINLINE +#else +#define OUTLINE_DECODE_BUFFER +#endif + +template +OUTLINE_DECODE_BUFFER bool EqualsSameDimensions(const FNameEntry& Entry, FNameStringView Name) +{ + FNameBuffer DecodeBuffer; + return EqualsSameDimensions(Entry.MakeView(DecodeBuffer), Name); +} + +/** Remember to update natvis if you change these */ +enum { FNameMaxBlockBits = 13 }; // Limit block array a bit, still allowing 8k * block size = 1GB - 2G of FName entry data +enum { FNameBlockOffsetBits = 16 }; +enum { FNameMaxBlocks = 1 << FNameMaxBlockBits }; +enum { FNameBlockOffsets = 1 << FNameBlockOffsetBits }; + +/** An unpacked FNameEntryId */ +struct FNameEntryHandle +{ + uint32 Block = 0; + uint32 Offset = 0; + + FNameEntryHandle(uint32 InBlock, uint32 InOffset) + : Block(InBlock) + , Offset(InOffset) + {} + + FNameEntryHandle(FNameEntryId Id) + : Block(Id.ToUnstableInt() >> FNameBlockOffsetBits) + , Offset(Id.ToUnstableInt() & (FNameBlockOffsets - 1)) + {} + + operator FNameEntryId() const + { + return FNameEntryId::FromUnstableInt(Block << FNameBlockOffsetBits | Offset); + } + + explicit operator bool() const { return Block | Offset; } +}; + +static uint32 GetTypeHash(FNameEntryHandle Handle) +{ + return (Handle.Block << (32 - FNameMaxBlockBits)) + Handle.Block // Let block index impact most hash bits + + (Handle.Offset << FNameBlockOffsetBits) + Handle.Offset // Let offset impact most hash bits + + (Handle.Offset >> 4); // Reduce impact of non-uniformly distributed entry name lengths +} + +uint32 GetTypeHash(FNameEntryId Id) +{ + return GetTypeHash(FNameEntryHandle(Id)); +} + +FArchive& operator<<(FArchive& Ar, FNameEntryId& Id) +{ + if (Ar.IsLoading()) + { + uint32 UnstableInt = 0; + Ar << UnstableInt; + Id = FNameEntryId::FromUnstableInt(UnstableInt); + } + else + { + uint32 UnstableInt = Id.ToUnstableInt(); + Ar << UnstableInt; + } + + return Ar; +} + +FNameEntryId FNameEntryId::FromUnstableInt(uint32 Value) +{ + FNameEntryId Id; + Id.Value = Value; + return Id; +} + +struct FNameSlot +{ + // Use the remaining few bits to store a hash that can determine inequality + // during probing without touching entry data + static constexpr uint32 EntryIdBits = FNameMaxBlockBits + FNameBlockOffsetBits; + static constexpr uint32 EntryIdMask = (1 << EntryIdBits) - 1; + static constexpr uint32 ProbeHashShift = EntryIdBits; + static constexpr uint32 ProbeHashMask = ~EntryIdMask; + + FNameSlot() {} + FNameSlot(FNameEntryId Value, uint32 ProbeHash) + : IdAndHash(Value.ToUnstableInt() | ProbeHash) + { + check(!(Value.ToUnstableInt() & ProbeHashMask) && !(ProbeHash & EntryIdMask) && Used()); + } + + FNameEntryId GetId() const { return FNameEntryId::FromUnstableInt(IdAndHash & EntryIdMask); } + uint32 GetProbeHash() const { return IdAndHash & ProbeHashMask; } + + bool operator==(FNameSlot Rhs) const { return IdAndHash == Rhs.IdAndHash; } + + bool Used() const { return !!IdAndHash; } +private: + uint32 IdAndHash = 0; +}; + +/** + * Thread-safe paged FNameEntry allocator + */ +class FNameEntryAllocator +{ +public: + enum { Stride = alignof(FNameEntry) }; + enum { BlockSizeBytes = Stride * FNameBlockOffsets }; + + /** Initializes all member variables. */ + FNameEntryAllocator() + { + Blocks[0] = (uint8*)FMemory::Malloc(BlockSizeBytes); + } + + /** + * Allocates the requested amount of bytes and returns an id that can be used to access them + * + * @param Size Size in bytes to allocate, + * @return Allocation of passed in size cast to a FNameEntry pointer. + */ + FNameEntryHandle Allocate(uint32 Bytes) + { + Bytes = Align(Bytes, alignof(FNameEntry)); + check(Bytes <= BlockSizeBytes); + + FRWScopeLock _(Lock, FRWScopeLockType::SLT_Write); + + // Allocate a new pool if current one is exhausted. We don't worry about a little bit + // of waste at the end given the relative size of pool to average and max allocation. + if (BlockSizeBytes - CurrentByteCursor < Bytes) + { + AllocateNewBlock(); + } + + // Use current cursor position for this allocation and increment cursor for next allocation + uint32 ByteOffset = CurrentByteCursor; + CurrentByteCursor += Bytes; + + check(ByteOffset % Stride == 0 && ByteOffset / Stride < FNameBlockOffsets); + + return FNameEntryHandle(CurrentBlock, ByteOffset / Stride); + } + + FNameEntryHandle Create(FNameStringView Name, FNameEntryId ComparisonId, FNameEntryHeader Header) + { + FNameEntryHandle Handle = Allocate(FNameEntry::GetDataOffset() + Name.BytesWithoutTerminator()); + FNameEntry& Entry = Resolve(Handle); + +#if WITH_CASE_PRESERVING_NAME + Entry.ComparisonId = ComparisonId ? ComparisonId : FNameEntryId(Handle); +#endif + + Entry.Header = Header; + + if (Name.bIsWide) + { + Entry.StoreName(Name.Wide, Name.Len); + } + else + { + Entry.StoreName(Name.Ansi, Name.Len); + } + + return Handle; + } + + FNameEntry& Resolve(FNameEntryHandle Handle) const + { + // Lock not needed + return *reinterpret_cast(Blocks[Handle.Block] + Stride * Handle.Offset); + } + + /** Returns the number of blocks that have been allocated so far for names. */ + uint32 NumBlocks() const + { + return CurrentBlock + 1; + } + + uint8** GetBlocksForDebugVisualizer() { return Blocks; } + + void DebugDump(TArray& Out) const + { + FRWScopeLock _(Lock, FRWScopeLockType::SLT_ReadOnly); + + for (uint32 BlockIdx = 0; BlockIdx < CurrentBlock; ++BlockIdx) + { + DebugDumpBlock(Blocks[BlockIdx], BlockSizeBytes, Out); + } + + DebugDumpBlock(Blocks[CurrentBlock], CurrentByteCursor, Out); + } + +private: + static void DebugDumpBlock(const uint8* It, uint32 BlockSize, TArray& Out) + { + const uint8* End = It + BlockSize - FNameEntry::GetDataOffset(); + while (It < End) + { + const FNameEntry* Entry = (const FNameEntry*)It; + if (uint32 Len = Entry->Header.Len) + { + Out.Add(Entry); + It += FNameEntry::GetSize(Len, !Entry->IsWide()); + } + else // Null-terminator entry found + { + break; + } + } + } + + /** Allocates a new pool. */ + void AllocateNewBlock() + { + // Null-terminate final entry to allow DebugDump() entry iteration + if (CurrentByteCursor + FNameEntry::GetDataOffset() <= BlockSizeBytes) + { + FNameEntry* Terminator = (FNameEntry*)(Blocks[CurrentBlock] + CurrentByteCursor); + Terminator->Header.Len = 0; + } + + ++CurrentBlock; + CurrentByteCursor = 0; + + check(CurrentBlock < FNameMaxBlocks); + check(Blocks[CurrentBlock] == nullptr); + + Blocks[CurrentBlock] = (uint8*)FMemory::Malloc(BlockSizeBytes); + } + + mutable FRWLock Lock; + uint32 CurrentBlock = 0; + uint32 CurrentByteCursor = 0; + uint8* Blocks[FNameMaxBlocks] = {}; +}; + +// Increasing shards reduces contention but uses more memory and adds cache pressure. +// Reducing contention matters when multiple threads create FNames in parallel. +// Contention exists in some tool scenarios, for instance between main thread +// and asset data gatherer thread during editor startup. +#if WITH_CASE_PRESERVING_NAME +enum { FNamePoolShardBits = 10 }; +#else +enum { FNamePoolShardBits = 4 }; +#endif + +enum { FNamePoolShards = 1 << FNamePoolShardBits }; +enum { FNamePoolInitialSlotBits = 8 }; +enum { FNamePoolInitialSlotsPerShard = 1 << FNamePoolInitialSlotBits }; + +/** Hashes name into 64 bits that determines shard and slot index. + * + * Small parts of the hash is also stored in unused bits of the slot and entry. + * The former optimizes linear probing by accessing less entry data. + * The latter optimizes linear probing by avoiding copying and deobfuscating entry data. + * + * The slot index could be stored in the slot, at least in non shipping / test configs. + * This costs memory by doubling slot size but would essentially never touch entry data + * nor copy and deobfuscate a name needlessy. It also allows growing the hash table + * without rehashing the strings, since the unmasked slot index would be known. + */ +struct FNameHash +{ + uint32 ShardIndex; + uint32 UnmaskedSlotIndex; // Determines at what slot index to start probing + uint32 SlotProbeHash; // Helps cull equality checks (decode + strnicmp) when probing slots + FNameEntryHeader EntryProbeHeader; // Helps cull equality checks when probing inspects entries + + + template + FNameHash(const CharType* Str, int32 Len) + { + uint64 Hash = CityHash64(reinterpret_cast(Str), Len * sizeof(CharType)); + uint32 Hi = static_cast(Hash >> 32); + uint32 Lo = static_cast(Hash); + + // "None" has FNameEntryId with a value of zero + // Always set a bit in SlotProbeHash for "None" to distinguish unused slot values from None + // @see FNameSlot::Used() + uint32 IsNoneBit = IsAnsiNone(Str, Len) << FNameSlot::ProbeHashShift; + + static constexpr uint32 ShardMask = FNamePoolShards - 1; + static_assert((ShardMask & FNameSlot::ProbeHashMask) == 0, "Masks overlap"); + + ShardIndex = Hi & ShardMask; + UnmaskedSlotIndex = Lo; + SlotProbeHash = (Hi & FNameSlot::ProbeHashMask) | IsNoneBit; + EntryProbeHeader.Len = Len; + EntryProbeHeader.bIsWide = sizeof(CharType) == sizeof(WIDECHAR); + + // When we always use lowercase hashing, we can store parts of the hash in the entry + // to avoid copying and decoding entries needlessly. WITH_CUSTOM_NAME_ENCODING + // that makes this important is normally on when WITH_CASE_PRESERVING_NAME is off. +#if !WITH_CASE_PRESERVING_NAME + static constexpr uint32 EntryProbeMask = (1u << FNameEntryHeader::ProbeHashBits) - 1; + EntryProbeHeader.LowercaseProbeHash = static_cast((Hi >> FNamePoolShardBits) & EntryProbeMask); +#endif + } + + uint32 GetProbeStart(uint32 SlotMask) const + { + return UnmaskedSlotIndex & SlotMask; + } + + static uint32 GetProbeStart(uint32 UnmaskedSlotIndex, uint32 SlotMask) + { + return UnmaskedSlotIndex & SlotMask; + } + + static uint32 IsAnsiNone(const WIDECHAR* Str, int32 Len) + { + return 0; + } + + static uint32 IsAnsiNone(const ANSICHAR* Str, int32 Len) + { + if (Len != 4) + { + return 0; + } + +#if PLATFORM_LITTLE_ENDIAN + static constexpr uint32 NoneAsInt = 0x454e4f4e; +#else + static constexpr uint32 NoneAsInt = 0x4e4f4e45; +#endif + static constexpr uint32 ToUpperMask = 0xdfdfdfdf; + + uint32 FourChars = FPlatformMemory::ReadUnaligned(Str); + return (FourChars & ToUpperMask) == NoneAsInt; + } +}; + + +template +FORCENOINLINE FNameHash HashLowerCase(const CharType* Str, uint32 Len) +{ + CharType LowerStr[NAME_SIZE]; + for (uint32 I = 0; I < Len; ++I) + { + LowerStr[I] = TChar::ToLower(Str[I]); + } + return FNameHash(LowerStr, Len); +} + +template +FNameHash HashName(FNameStringView Name); + +template<> +FNameHash HashName(FNameStringView Name) +{ + return Name.IsAnsi() ? HashLowerCase(Name.Ansi, Name.Len) : HashLowerCase(Name.Wide, Name.Len); +} +template<> +FNameHash HashName(FNameStringView Name) +{ + return Name.IsAnsi() ? FNameHash(Name.Ansi, Name.Len) : FNameHash(Name.Wide, Name.Len); +} + +template +struct FNameValue +{ + FNameValue(FNameStringView InName) + : Name(InName) + , Hash(HashName(InName)) + {} + + FNameStringView Name; + FNameHash Hash; + FNameEntryId ComparisonId; +}; + +using FNameComparisonValue = FNameValue; +#if WITH_CASE_PRESERVING_NAME +using FNameDisplayValue = FNameValue; +#endif + + +class alignas(PLATFORM_CACHE_LINE_SIZE) FNamePoolShardBase : FNoncopyable +{ +public: + void Initialize(FNameEntryAllocator& InEntries) + { + Entries = &InEntries; + + Slots = (FNameSlot*)FMemory::Malloc(FNamePoolInitialSlotsPerShard * sizeof(FNameSlot), alignof(FNameSlot)); + memset(Slots, 0, FNamePoolInitialSlotsPerShard * sizeof(FNameSlot)); + CapacityMask = FNamePoolInitialSlotsPerShard - 1; + } + + uint32 Capacity() const { return CapacityMask + 1; } + +protected: + enum { LoadFactorQuotient = 9, LoadFactorDivisor = 10 }; // I.e. realloc slots when 90% full + + mutable FRWLock Lock; + uint32 UsedSlots = 0; + uint32 CapacityMask = 0; + FNameSlot* Slots = nullptr; + FNameEntryAllocator* Entries = nullptr; + + + template + FORCEINLINE static bool EntryEqualsValue(const FNameEntry& Entry, const FNameValue& Value) + { + return Entry.Header == Value.Hash.EntryProbeHeader && EqualsSameDimensions(Entry, Value.Name); + } +}; + +template +class FNamePoolShard : public FNamePoolShardBase +{ +public: + FNameEntryId Find(const FNameValue& Value) const + { + FRWScopeLock _(Lock, FRWScopeLockType::SLT_ReadOnly); + + return Probe(Value).GetId(); + } + + FNameEntryId Insert(const FNameValue& Value, bool& bCreatedNewEntry) + { + FRWScopeLock _(Lock, FRWScopeLockType::SLT_Write); + + FNameSlot& Slot = Probe(Value); + + if (Slot.Used()) + { + return Slot.GetId(); + } + + FNameEntryId NewEntryId = Entries->Create(Value.Name, Value.ComparisonId, Value.Hash.EntryProbeHeader); + + ClaimSlot(Slot, FNameSlot(NewEntryId, Value.Hash.SlotProbeHash)); + + bCreatedNewEntry = true; + + return NewEntryId; + } + + void InsertExistingEntry(FNameHash Hash, FNameEntryId ExistingId) + { + FNameSlot NewLookup(ExistingId, Hash.SlotProbeHash); + + FRWScopeLock _(Lock, FRWScopeLockType::SLT_Write); + + FNameSlot& Slot = Probe(Hash.UnmaskedSlotIndex, [=](FNameSlot Old) { return Old == NewLookup; }); + if (!Slot.Used()) + { + ClaimSlot(Slot, NewLookup); + } + } + +private: + void ClaimSlot(FNameSlot& UnusedSlot, FNameSlot NewValue) + { + UnusedSlot = NewValue; + + ++UsedSlots; + if (UsedSlots * LoadFactorDivisor >= LoadFactorQuotient * Capacity()) + { + Grow(); + } + } + + void Grow() + { + FNameSlot* const OldSlots = Slots; + const uint32 OldUsedSlots = UsedSlots; + const uint32 OldCapacity = Capacity(); + const uint32 NewCapacity = OldCapacity * 2; + + Slots = (FNameSlot*)FMemory::Malloc(NewCapacity * sizeof(FNameSlot), alignof(FNameSlot)); + memset(Slots, 0, NewCapacity * sizeof(FNameSlot)); + UsedSlots = 0; + CapacityMask = NewCapacity - 1; + + + for (uint32 OldIdx = 0; OldIdx < OldCapacity; ++OldIdx) + { + const FNameSlot& OldSlot = OldSlots[OldIdx]; + if (OldSlot.Used()) + { + FNameHash Hash = Rehash(OldSlot.GetId()); + FNameSlot& NewSlot = Probe(Hash.UnmaskedSlotIndex, [](FNameSlot Slot) { return false; }); + NewSlot = OldSlot; + ++UsedSlots; + } + } + + check(OldUsedSlots == UsedSlots); + + FMemory::Free(OldSlots); + } + + /** Find slot containing value or the first free slot that should be used to store it */ + FNameSlot& Probe(const FNameValue& Value) const + { + return Probe(Value.Hash.UnmaskedSlotIndex, + [&](FNameSlot Slot) { return Slot.GetProbeHash() == Value.Hash.SlotProbeHash && + EntryEqualsValue(Entries->Resolve(Slot.GetId()), Value); }); + } + + /** Find slot that fulfills predicate or the first free slot */ + template + FNameSlot& Probe(uint32 UnmaskedSlotIndex, PredicateFn Predicate) const + { + const uint32 Mask = CapacityMask; + for (uint32 I = FNameHash::GetProbeStart(UnmaskedSlotIndex, Mask); true; I = (I + 1) & Mask) + { + FNameSlot& Slot = Slots[I]; + if (!Slot.Used() || Predicate(Slot)) + { + return Slot; + } + } + } + + OUTLINE_DECODE_BUFFER FNameHash Rehash(FNameEntryId EntryId) + { + const FNameEntry& Entry = Entries->Resolve(EntryId); + FNameBuffer DecodeBuffer; + return HashName(Entry.MakeView(DecodeBuffer)); + } +}; + + +class FNamePool +{ +public: + FNamePool(); + + FNameEntryId Store(FNameStringView View); + FNameEntryId Find(FNameStringView View) const; + FNameEntryId Find(EName Ename) const; + const EName* FindEName(FNameEntryId Id) const; + + /** @pre !!Handle */ + FNameEntry& Resolve(FNameEntryHandle Handle) const { return Entries.Resolve(Handle); } + + bool IsValid(FNameEntryHandle Handle) const; + + /// Stats and debug related functions /// + + uint32 NumAnsiEntries() const { return AnsiCount; } + uint32 NumWideEntries() const { return WideCount; } + uint32 NumBlocks() const { return Entries.NumBlocks(); } + uint32 NumSlots() const; + void LogStats(FOutputDevice& Ar) const; + uint8** GetBlocksForDebugVisualizer() { return Entries.GetBlocksForDebugVisualizer(); } + TArray DebugDump() const; + +private: + enum { MaxENames = 512 }; + + FNameEntryAllocator Entries; + TAtomic AnsiCount; + TAtomic WideCount; + +#if WITH_CASE_PRESERVING_NAME + FNamePoolShard DisplayShards[FNamePoolShards]; +#endif + FNamePoolShard ComparisonShards[FNamePoolShards]; + + // Put constant lookup on separate cache line to avoid it being constantly invalidated by insertion + alignas(PLATFORM_CACHE_LINE_SIZE) FNameEntryId ENameToEntry[NAME_MaxHardcodedNameIndex] = {}; + uint32 LargestEnameUnstableId; + TMap> EntryToEName; +}; + +FNamePool::FNamePool() + : AnsiCount(0) + , WideCount(0) +{ + for (FNamePoolShardBase& Shard : ComparisonShards) + { + Shard.Initialize(Entries); + } + +#if WITH_CASE_PRESERVING_NAME + for (FNamePoolShardBase& Shard : DisplayShards) + { + Shard.Initialize(Entries); + } +#endif + + // Register all hardcoded names +#define REGISTER_NAME(num, name) ENameToEntry[num] = Store(FNameStringView(#name, FCStringAnsi::Strlen(#name))); +#include "UObject/UnrealNames.inl" +#undef REGISTER_NAME + + // Make reverse mapping + LargestEnameUnstableId = 0; + for (uint32 ENameIndex = 0; ENameIndex < NAME_MaxHardcodedNameIndex; ++ENameIndex) + { + if (ENameIndex == NAME_None || ENameToEntry[ENameIndex]) + { + EntryToEName.Add(ENameToEntry[ENameIndex], (EName)ENameIndex); + LargestEnameUnstableId = FMath::Max(LargestEnameUnstableId, ENameToEntry[ENameIndex].ToUnstableInt()); + } + } + + // Verify all ENames are unique + if (NumAnsiEntries() != EntryToEName.Num()) + { + // we can't print out here because there may be no log yet if this happens before main starts + if (FPlatformMisc::IsDebuggerPresent()) + { + UE_DEBUG_BREAK(); + } + else + { + FPlatformMisc::PromptForRemoteDebugging(false); + FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "DuplicatedHardcodedName", "Duplicate hardcoded name")); + FPlatformMisc::RequestExit(false); + } + } +} + +static bool IsPureAnsi(const WIDECHAR* Str, const int32 Len) +{ + // Consider SSE version if this function takes significant amount of time + uint32 Result = 0; + for (int32 I = 0; I < Len; ++I) + { + Result |= TChar::ToUnsigned(Str[I]); + } + return !(Result & 0xffffff80u); +} + +FNameEntryId FNamePool::Find(EName Ename) const +{ + check(Ename < NAME_MaxHardcodedNameIndex); + return ENameToEntry[Ename]; +} + +FNameEntryId FNamePool::Find(FNameStringView Name) const +{ +#if WITH_CASE_PRESERVING_NAME + FNameDisplayValue DisplayValue(Name); + if (FNameEntryId Existing = DisplayShards[DisplayValue.Hash.ShardIndex].Find(DisplayValue)) + { + return Existing; + } +#endif + + FNameComparisonValue ComparisonValue(Name); + return ComparisonShards[ComparisonValue.Hash.ShardIndex].Find(ComparisonValue); +} + +FNameEntryId FNamePool::Store(FNameStringView Name) +{ +#if WITH_CASE_PRESERVING_NAME + FNameDisplayValue DisplayValue(Name); + FNamePoolShard& DisplayShard = DisplayShards[DisplayValue.Hash.ShardIndex]; + if (FNameEntryId Existing = DisplayShard.Find(DisplayValue)) + { + return Existing; + } +#endif + + TAtomic& EntryCount = Name.IsAnsi() ? AnsiCount : WideCount; + bool bAdded = false; + + // Insert comparison name first since display value must contain comparison name + FNameComparisonValue ComparisonValue(Name); + FNameEntryId ComparisonId = ComparisonShards[ComparisonValue.Hash.ShardIndex].Insert(ComparisonValue, bAdded); + EntryCount += bAdded; + +#if WITH_CASE_PRESERVING_NAME + // Check if ComparisonId can be used as DisplayId + if (bAdded || EqualsSameDimensions(Resolve(ComparisonId), Name)) + { + DisplayShard.InsertExistingEntry(DisplayValue.Hash, ComparisonId); + return ComparisonId; + } + else + { + bAdded = false; + DisplayValue.ComparisonId = ComparisonId; + FNameEntryId DisplayId = DisplayShard.Insert(DisplayValue, bAdded); + EntryCount += bAdded; + + return DisplayId; + } +#else + return ComparisonId; +#endif +} + +uint32 FNamePool::NumSlots() const +{ + uint32 SlotCapacity = 0; +#if WITH_CASE_PRESERVING_NAME + for (const FNamePoolShardBase& Shard : DisplayShards) + { + SlotCapacity += Shard.Capacity(); + } +#endif + for (const FNamePoolShardBase& Shard : ComparisonShards) + { + SlotCapacity += Shard.Capacity(); + } + + return SlotCapacity; +} + +void FNamePool::LogStats(FOutputDevice& Ar) const +{ + Ar.Logf(TEXT("%i FNames using in %ikB + %ikB"), WideCount + AnsiCount, sizeof(FNamePool), Entries.NumBlocks() * FNameEntryAllocator::BlockSizeBytes / 1024); +} + +TArray FNamePool::DebugDump() const +{ + TArray Out; + Out.Reserve(WideCount + AnsiCount); + Entries.DebugDump(Out); + return Out; +} + +bool FNamePool::IsValid(FNameEntryHandle Handle) const +{ + return Handle.Block < Entries.NumBlocks(); +} + +const EName* FNamePool::FindEName(FNameEntryId Id) const +{ + return Id.ToUnstableInt() > LargestEnameUnstableId ? nullptr : EntryToEName.Find(Id); +} + +static bool bNamePoolInitialized; +alignas(FNamePool) static uint8 NamePoolData[sizeof(FNamePool)]; + +// Only call this once per public FName function called +// +// Not using magic statics to run as little code as possible +static FNamePool& GetNamePool() +{ + if (bNamePoolInitialized) + { + return *(FNamePool*)NamePoolData; + } + + FNamePool* Singleton = new (NamePoolData) FNamePool; + bNamePoolInitialized = true; + return *Singleton; +} + +// Only call from functions guaranteed to run after FName lazy initialization +static FNamePool& GetNamePoolPostInit() +{ + checkSlow(bNamePoolInitialized); + return (FNamePool&)NamePoolData; +} + +bool operator==(FNameEntryId Id, EName Ename) +{ + return Id == GetNamePoolPostInit().Find(Ename); +} + +static int32 CompareDifferentIdsAlphabetically(FNameEntryId AId, FNameEntryId BId) +{ + checkSlow(AId != BId); + + FNamePool& Pool = GetNamePool(); + FNameBuffer ABuffer, BBuffer; + FNameStringView AView = Pool.Resolve(AId).MakeView(ABuffer); + FNameStringView BView = Pool.Resolve(BId).MakeView(BBuffer); + + // If only one view is wide, convert the ansi view to wide as well + if (AView.bIsWide != BView.bIsWide) + { + FNameStringView& AnsiView = AView.bIsWide ? BView : AView; + FNameBuffer& AnsiBuffer = AView.bIsWide ? BBuffer : ABuffer; + +#ifndef WITH_CUSTOM_NAME_ENCODING + FPlatformMemory::Memcpy(AnsiBuffer.AnsiName, AnsiView.Ansi, AnsiView.Len * sizeof(ANSICHAR)); + AnsiView.Ansi = AnsiBuffer.AnsiName; +#endif + + ConvertInPlace(AnsiBuffer.AnsiName, AnsiView.Len); + AnsiView.bIsWide = true; + } + + int32 MinLen = FMath::Min(AView.Len, BView.Len); + if (int32 StrDiff = AView.bIsWide ? FCStringWide::Strnicmp(AView.Wide, BView.Wide, MinLen) : + FCStringAnsi::Strnicmp(AView.Ansi, BView.Ansi, MinLen)) + { + return StrDiff; + } + + return AView.Len - BView.Len; +} + +int32 FNameEntryId::CompareLexical(FNameEntryId Rhs) const +{ + return Value != Rhs.Value && CompareDifferentIdsAlphabetically(*this, Rhs); +} + #if !UE_BUILD_SHIPPING && !UE_BUILD_TEST void CallNameCreationHook(); #else @@ -38,11 +956,7 @@ DEFINE_LOG_CATEGORY_STATIC(LogUnrealNames, Log, All); } #endif -// Get the size of a FNameEntry without the union buffer included -static const int32 NameEntryWithoutUnionSize = sizeof(FNameEntry) - (NAME_SIZE * sizeof(TCHAR)); - -template -FNameEntry* AllocateNameEntry( const TCharType* Name, NAME_INDEX Index); +static FNameEntryId DebugCastNameEntryId(int32 Id) { return (FNameEntryId&)(Id); } /** * Helper function that can be used inside the debuggers watch window. E.g. "DebugFName(Class->Name.Index)". @@ -50,10 +964,10 @@ FNameEntry* AllocateNameEntry( const TCharType* Name, NAME_INDEX Index); * @param Index Name index to look up string for * @return Associated name */ -const TCHAR* DebugFName(int32 Index) +const TCHAR* DebugFName(FNameEntryId Index) { // Hardcoded static array. This function is only used inside the debugger so it should be fine to return it. - static TCHAR TempName[256]; + static TCHAR TempName[NAME_SIZE]; FCString::Strcpy(TempName, *FName::SafeString(Index)); return TempName; } @@ -68,8 +982,8 @@ const TCHAR* DebugFName(int32 Index) const TCHAR* DebugFName(int32 Index, int32 Number) { // Hardcoded static array. This function is only used inside the debugger so it should be fine to return it. - static TCHAR TempName[256]; - FCString::Strcpy(TempName, *FName::SafeString(Index, Number)); + static TCHAR TempName[NAME_SIZE]; + FCString::Strcpy(TempName, *FName::SafeString(DebugCastNameEntryId(Index), Number)); return TempName; } @@ -82,7 +996,7 @@ const TCHAR* DebugFName(int32 Index, int32 Number) const TCHAR* DebugFName(FName& Name) { // Hardcoded static array. This function is only used inside the debugger so it should be fine to return it. - static TCHAR TempName[256]; + static TCHAR TempName[NAME_SIZE]; FCString::Strcpy(TempName, *FName::SafeString(Name.GetDisplayIndex(), Name.GetNumber())); return TempName; } @@ -99,162 +1013,167 @@ static uint16 GetRawNonCasePreservingHash(const TCharType* Source) return FCrc::Strihash_DEPRECATED(Source) & 0xFFFF; } -/*---------------------------------------------------------------------------- - FNameBuffer. -----------------------------------------------------------------------------*/ - -union FNameBuffer -{ - ANSICHAR AnsiName[NAME_SIZE]; - WIDECHAR WideName[NAME_SIZE]; -}; - /*----------------------------------------------------------------------------- FNameEntry -----------------------------------------------------------------------------*/ -/** - * @return FString of name portion minus number. - */ +void FNameEntry::StoreName(const ANSICHAR* InName, uint32 Len) +{ + FPlatformMemory::Memcpy(AnsiName, InName, sizeof(ANSICHAR) * Len); + Encode(AnsiName, Len); +} + +void FNameEntry::StoreName(const WIDECHAR* InName, uint32 Len) +{ + FPlatformMemory::Memcpy(WideName, InName, sizeof(WIDECHAR) * Len); + Encode(WideName, Len); +} + +void FNameEntry::CopyUnterminatedName(ANSICHAR* Out) const +{ + FPlatformMemory::Memcpy(Out, AnsiName, sizeof(ANSICHAR) * Header.Len); + Decode(Out, Header.Len); +} + +void FNameEntry::CopyUnterminatedName(WIDECHAR* Out) const +{ + FPlatformMemory::Memcpy(Out, WideName, sizeof(WIDECHAR) * Header.Len); + Decode(Out, Header.Len); +} + +FORCEINLINE const WIDECHAR* FNameEntry::GetUnterminatedName(WIDECHAR(&OptionalDecodeBuffer)[NAME_SIZE]) const +{ +#ifdef WITH_CUSTOM_NAME_ENCODING + CopyUnterminatedName(OptionalDecodeBuffer); + return OptionalDecodeBuffer; +#else + return WideName; +#endif +} + +FORCEINLINE ANSICHAR const* FNameEntry::GetUnterminatedName(ANSICHAR(&OptionalDecodeBuffer)[NAME_SIZE]) const +{ +#ifdef WITH_CUSTOM_NAME_ENCODING + CopyUnterminatedName(OptionalDecodeBuffer); + return OptionalDecodeBuffer; +#else + return AnsiName; +#endif +} + +FORCEINLINE FNameStringView FNameEntry::MakeView(FNameBuffer& OptionalDecodeBuffer) const +{ + return IsWide() ? FNameStringView(GetUnterminatedName(OptionalDecodeBuffer.WideName), GetNameLength()) + : FNameStringView(GetUnterminatedName(OptionalDecodeBuffer.AnsiName), GetNameLength()); +} + +void FNameEntry::GetUnterminatedName(TCHAR* OutName, uint32 OutLen) const +{ + check(static_cast(OutLen) >= GetNameLength()); + CopyAndConvertUnterminatedName(OutName); +} + +void FNameEntry::GetName(TCHAR(&OutName)[NAME_SIZE]) const +{ + CopyAndConvertUnterminatedName(OutName); + OutName[GetNameLength()] = '\0'; +} + +void FNameEntry::CopyAndConvertUnterminatedName(TCHAR* OutName) const +{ + if (sizeof(TCHAR) < sizeof(WIDECHAR) && IsWide()) // Normally compiled out + { + FNameBuffer Temp; + CopyUnterminatedName(Temp.WideName); + ConvertInPlace(Temp.WideName, Header.Len); + FPlatformMemory::Memcpy(OutName, Temp.AnsiName, Header.Len * sizeof(TCHAR)); + } + else if (IsWide()) + { + CopyUnterminatedName((WIDECHAR*)OutName); + ConvertInPlace((WIDECHAR*)OutName, Header.Len); + } + else + { + CopyUnterminatedName((ANSICHAR*)OutName); + ConvertInPlace((ANSICHAR*)OutName, Header.Len); + } +} + +void FNameEntry::GetAnsiName(ANSICHAR(&Out)[NAME_SIZE]) const +{ + check(!IsWide()); + CopyUnterminatedName(Out); + Out[Header.Len] = '\0'; +} + +void FNameEntry::GetWideName(WIDECHAR(&Out)[NAME_SIZE]) const +{ + check(IsWide()); + CopyUnterminatedName(Out); + Out[Header.Len] = '\0'; +} + +/** @return null-terminated string */ +static const TCHAR* EntryToCString(const FNameEntry& Entry, FNameBuffer& Temp) +{ + if (Entry.IsWide()) + { + Entry.GetWideName(Temp.WideName); + return ConvertInPlace(Temp.WideName, Entry.GetNameLength() + 1); + } + else + { + Entry.GetAnsiName(Temp.AnsiName); + return ConvertInPlace(Temp.AnsiName, Entry.GetNameLength() + 1); + } +} + FString FNameEntry::GetPlainNameString() const { - FNameBuffer TempBuffer; - if( IsWide() ) - { - return FString(GetWideNamePtr(TempBuffer.WideName)); - } - else - { - return FString(GetAnsiNamePtr(TempBuffer.AnsiName)); - } + FNameBuffer Temp; + return FString(EntryToCString(*this, Temp)); } -/** - * Appends this name entry to the passed in string. - * - * @param String String to append this name to - */ -void FNameEntry::AppendNameToString( FString& String ) const +void FNameEntry::AppendNameToString(FString& Out) const { - FNameBuffer TempBuffer; - if( IsWide() ) - { - String += GetWideNamePtr(TempBuffer.WideName); - } - else - { - String += GetAnsiNamePtr(TempBuffer.AnsiName); - } + FNameBuffer Temp; + Out.Append(EntryToCString(*this, Temp), Header.Len); } -void FNameEntry::AppendNameToPathString(FString& String) const +void FNameEntry::AppendNameToPathString(FString& Out) const { - FNameBuffer TempBuffer; - if (IsWide()) - { - String /= GetWideNamePtr(TempBuffer.WideName); - } - else - { - String /= GetAnsiNamePtr(TempBuffer.AnsiName); - } + FNameBuffer Temp; + Out.PathAppend(EntryToCString(*this, Temp), Header.Len); } -/** - * @return length of name - */ -int32 FNameEntry::GetNameLength() const +int32 FNameEntry::GetSize(const TCHAR* Name) { - FNameBuffer TempBuffer; - if( IsWide() ) - { - return FCStringWide::Strlen( GetWideNamePtr(TempBuffer.WideName) ); - } - else - { - return FCStringAnsi::Strlen( GetAnsiNamePtr(TempBuffer.AnsiName) ); - } + return FNameEntry::GetSize(FCString::Strlen(Name), FCString::IsPureAnsi(Name)); } -/** - * Compares name using the compare method provided. - * - * @param InName Name to compare to - * @return true if equal, false otherwise - */ -bool FNameEntry::IsEqual( const ANSICHAR* InName, const ENameCase CompareMethod ) const +int32 FNameEntry::GetSize(int32 Length, bool bIsPureAnsi) { - if( IsWide() ) - { - // Matching wide-ness means strings are not equal. - return false; - } - else - { - ANSICHAR TempBuffer[NAME_SIZE]; - const ANSICHAR* CompareName = GetAnsiNamePtr(TempBuffer); - return ( (CompareMethod == ENameCase::CaseSensitive) ? FCStringAnsi::Strcmp( CompareName, InName ) : FCStringAnsi::Stricmp( CompareName, InName ) ) == 0; - } + int32 Bytes = GetDataOffset() + (Length + 1) * (bIsPureAnsi ? sizeof(ANSICHAR) : sizeof(TCHAR)); + return Align(Bytes, alignof(FNameEntry)); } -/** - * Compares name using the compare method provided. - * - * @param InName Name to compare to - * @return true if equal, false otherwise - */ -bool FNameEntry::IsEqual( const WIDECHAR* InName, const ENameCase CompareMethod ) const +int32 FNameEntry::GetSizeInBytes() const { - if( !IsWide() ) - { - // Matching wide-ness means strings are not equal. - return false; - } - else - { - WIDECHAR TempBuffer[NAME_SIZE]; - const WIDECHAR* CompareName = GetWideNamePtr(TempBuffer); - return ( (CompareMethod == ENameCase::CaseSensitive) ? FCStringWide::Strcmp( CompareName, InName ) : FCStringWide::Stricmp( CompareName, InName ) ) == 0; - } -} - -/** - * Returns the size in bytes for FNameEntry structure. This is != sizeof(FNameEntry) as we only allocated as needed. - * - * @param Name Name to determine size for - * @return required size of FNameEntry structure to hold this string (might be wide or ansi) - */ -int32 FNameEntry::GetSize( const TCHAR* Name ) -{ - return FNameEntry::GetSize( FCString::Strlen( Name ), FCString::IsPureAnsi( Name ) ); -} - -/** - * Returns the size in bytes for FNameEntry structure. This is != sizeof(FNameEntry) as we only allocated as needed. - * - * @param Length Length of name - * @param bIsPureAnsi Whether name is pure ANSI or not - * @return required size of FNameEntry structure to hold this string (might be wide or ansi) - */ -int32 FNameEntry::GetSize( int32 Length, bool bIsPureAnsi ) -{ - // Add size required for string to the base size used by the FNameEntry - int32 Size = NameEntryWithoutUnionSize + (Length + 1) * (bIsPureAnsi ? sizeof(ANSICHAR) : sizeof(TCHAR)); - return Size; + return GetSize(GetNameLength(), !IsWide()); } FNameEntrySerialized::FNameEntrySerialized(const FNameEntry& NameEntry) { - if (NameEntry.IsWide()) + bIsWide = NameEntry.IsWide(); + if (bIsWide) { - PreSetIsWideForSerialization(true); NameEntry.GetWideName(WideName); NonCasePreservingHash = GetRawNonCasePreservingHash(WideName); CasePreservingHash = GetRawCasePreservingHash(WideName); } else { - PreSetIsWideForSerialization(false); NameEntry.GetAnsiName(AnsiName); NonCasePreservingHash = GetRawNonCasePreservingHash(AnsiName); CasePreservingHash = GetRawCasePreservingHash(AnsiName); @@ -266,7 +1185,7 @@ FNameEntrySerialized::FNameEntrySerialized(const FNameEntry& NameEntry) */ FString FNameEntrySerialized::GetPlainNameString() const { - if( IsWide() ) + if (bIsWide) { return FString(WideName); } @@ -280,37 +1199,40 @@ FString FNameEntrySerialized::GetPlainNameString() const FName statics. -----------------------------------------------------------------------------*/ -TNameEntryArray& FName::GetNames() +int32 FName::GetNameEntryMemorySize() { - // NOTE: The reason we initialize to NULL here is due to an issue with static initialization of variables with - // constructors/destructors across DLL boundaries, where a function called from a statically constructed object - // calls a function in another module (such as this function) that creates a static variable. A crash can occur - // because the static initialization of this DLL has not yet happened, and the CRT's list of static destructors - // cannot be written to because it has not yet been initialized fully. (@todo UE4 DLL) - static TNameEntryArray* Names = NULL; - if( Names == NULL ) - { - check(IsInGameThread()); - Names = new TNameEntryArray(); - } - return *Names; + return GetNamePool().NumBlocks() * FNameEntryAllocator::BlockSizeBytes; } -FNameEntry*** FName::GetNameTableForDebuggerVisualizers_MT() +int32 FName::GetNameTableMemorySize() { - return GetNames().GetRootBlockForDebuggerVisualizers(); + return GetNameEntryMemorySize() + sizeof(FNamePool) + GetNamePool().NumSlots() * sizeof(FNameSlot); } - -FCriticalSection* FName::GetCriticalSection() +int32 FName::GetNumAnsiNames() { - static FCriticalSection* CriticalSection = NULL; - if( CriticalSection == NULL ) - { - check(IsInGameThread()); - CriticalSection = new FCriticalSection(); - } - return CriticalSection; + return GetNamePool().NumAnsiEntries(); +} + +int32 FName::GetNumWideNames() +{ + return GetNamePool().NumWideEntries(); +} + +TArray FName::DebugDump() +{ + return GetNamePool().DebugDump(); +} + +FNameEntry const* FName::GetEntry(EName Ename) +{ + FNamePool& Pool = GetNamePool(); + return &Pool.Resolve(Pool.Find(Ename)); +} + +FNameEntry const* FName::GetEntry(FNameEntryId Id) +{ + return &GetNamePool().Resolve(Id); } FString FName::NameToDisplayString( const FString& InDisplayName, const bool bIsBool ) @@ -442,512 +1364,399 @@ FString FName::NameToDisplayString( const FString& InDisplayName, const bool bIs return OutDisplayName; } +const EName* FName::ToEName() const +{ + return GetNamePoolPostInit().FindEName(ComparisonIndex); +} -// Static variables. -TAtomic FName::NameHashHead[FNameDefs::NameHashBucketCount]; -TAtomic FName::NameHashTail[FNameDefs::NameHashBucketCount]; -int32 FName::NameEntryMemorySize; -int32 FName::NumAnsiNames; -int32 FName::NumWideNames; - +bool FName::IsWithinBounds(FNameEntryId Id) +{ + return GetNamePoolPostInit().IsValid(Id); +} /*----------------------------------------------------------------------------- FName implementation. -----------------------------------------------------------------------------*/ -/** - * Create an FName. If FindType is FNAME_Find, and the string part of the name - * doesn't already exist, then the name will be NAME_None - * - * @param Name Value for the string portion of the name - * @param FindType Action to take (see EFindName) - */ -FName::FName(const WIDECHAR* Name, EFindName FindType) +template +static bool NumberEqualsString(int32 Number, const CharType* Str) { - if (Name) - { - Init(Name, NAME_NO_NUMBER_INTERNAL, FindType); - } - else - { - *this = FName(NAME_None); - } + CharType* End = nullptr; + return TCString::Strtoi64(Str, &End, 10) == Number && End && *End == '\0'; } +template +static bool StringAndNumberEqualsString(const CharType1* Name, uint32 NameLen, int32 InternalNumber, const CharType2* Str) +{ + if (FPlatformString::Strnicmp(Name, Str, NameLen)) + { + return false; + } + + if (InternalNumber == NAME_NO_NUMBER_INTERNAL) + { + return Str[NameLen] == '\0'; + } + + uint32 Number = NAME_INTERNAL_TO_EXTERNAL(InternalNumber); + return Str[NameLen] == '_' && NumberEqualsString(Number, Str + NameLen + 1); +} + +struct FAnsiStringView +{ + using CharType = ANSICHAR; + + const ANSICHAR* Str; + int32 Len; +}; + +struct FWideStringViewWithWidth +{ + using CharType = WIDECHAR; + + const WIDECHAR* Str; + int32 Len; + bool bIsWide; +}; + +static FAnsiStringView MakeUnconvertedView(const ANSICHAR* Str, int32 Len) +{ + return { Str, Len }; +} + +static FAnsiStringView MakeUnconvertedView(const ANSICHAR* Str) +{ + return { Str, Str ? FCStringAnsi::Strlen(Str) : 0 }; +} + +static bool IsWide(const WIDECHAR* Str, const int32 Len) +{ + uint32 UserCharBits = 0; + for (int32 I = 0; I < Len; ++I) + { + UserCharBits |= TChar::ToUnsigned(Str[I]); + } + return UserCharBits & 0xffffff80u; +} + +static int32 GetLengthAndWidth(const WIDECHAR* Str, bool& bOutIsWide) +{ + uint32 UserCharBits = 0; + const WIDECHAR* It = Str; + while (*It) + { + UserCharBits |= TChar::ToUnsigned(*It); + ++It; + } + + bOutIsWide = UserCharBits & 0xffffff80u; + + return It - Str; +} + +static FWideStringViewWithWidth MakeUnconvertedView(const WIDECHAR* Str, int32 Len) +{ + return { Str, Len, IsWide(Str, Len) }; +} + +static FWideStringViewWithWidth MakeUnconvertedView(const WIDECHAR* Str) +{ + FWideStringViewWithWidth View; + View.Str = Str; + View.Len = GetLengthAndWidth(Str, View.bIsWide); + return View; +} + +/** Templated implementations of non-templated member functions, helps keep header clean */ +struct FNameHelper +{ + template + static FName MakeDetectNumber(ViewType View, EFindName FindType) + { + if (View.Len == 0) + { + return FName(); + } + + using CharType = typename ViewType::CharType; + const CharType* Name = View.Str; + const int32 Len = View.Len; + + int32 Digits = 0; + for (const CharType* It = Name + Len - 1; It >= Name && *It >= '0' && *It <= '9'; --It) + { + ++Digits; + } + + const CharType* FirstDigit = Name + Len - Digits; + if (Digits && Digits < Len && *(FirstDigit - 1) == '_') + { + // check for the case where there are multiple digits after the _ and the first one + // is a 0 ("Rocket_04"). Can't split this case. (So, we check if the first char + // is not 0 or the length of the number is 1 (since ROcket_0 is valid) + if (Digits == 1 || *FirstDigit != '0') + { + // Attempt to convert what's following it to a number + // This relies on Name being null-terminated + uint64 Number = TCString::Atoi64(Name + Len - Digits); + if (Number < MAX_uint32) + { + View.Len -= 1 + Digits; + return MakeWithNumber(View, FindType, static_cast(NAME_EXTERNAL_TO_INTERNAL(Number))); + } + } + } + + return MakeWithNumber(View, FindType, NAME_NO_NUMBER_INTERNAL); + } + + static FName MakeWithNumber(FAnsiStringView View, EFindName FindType, int32 InternalNumber) + { + // Ignore the supplied number if the name string is empty + // to keep the semantics of the old FName implementation + if (View.Len == 0) + { + return FName(); + } + + return Make(FNameStringView(View.Str, View.Len), FindType, InternalNumber); + } + + static FName MakeWithNumber(const FWideStringViewWithWidth View, EFindName FindType, int32 InternalNumber) + { + // Ignore the supplied number if the name string is empty + // to keep the semantics of the old FName implementation + if (View.Len == 0) + { + return FName(); + } + + // Convert to narrow if possible + if (!View.bIsWide) + { + // Consider _mm_packus_epi16 or similar if this proves too slow + ANSICHAR AnsiName[NAME_SIZE]; + for (int32 I = 0; I < View.Len; ++I) + { + AnsiName[I] = View.Str[I]; + } + return Make(FNameStringView(AnsiName, View.Len), FindType, InternalNumber); + } + else + { + return Make(FNameStringView(View.Str, View.Len), FindType, InternalNumber); + } + } + + static FName Make(FNameStringView View, EFindName FindType, int32 InternalNumber) + { + FNamePool& Pool = GetNamePool(); + + FNameEntryId DisplayId, ComparisonId; + if (FindType == FNAME_Add) + { + DisplayId = Pool.Store(View); +#if WITH_CASE_PRESERVING_NAME + ComparisonId = Pool.Resolve(DisplayId).ComparisonId; +#else + ComparisonId = DisplayId; +#endif + } + else if (FindType == FNAME_Find) + { + DisplayId = Pool.Find(View); +#if WITH_CASE_PRESERVING_NAME + ComparisonId = DisplayId ? Pool.Resolve(DisplayId).ComparisonId : DisplayId; +#else + ComparisonId = DisplayId; +#endif + } + else + { + check(FindType == FNAME_Replace_Not_Safe_For_Threading); + + DisplayId = Pool.Store(View); +#if WITH_CASE_PRESERVING_NAME + ComparisonId = Pool.Resolve(DisplayId).ComparisonId; +#else + ComparisonId = DisplayId; +#endif + ReplaceName(Pool.Resolve(ComparisonId), View); + } + + return FName(ComparisonId, DisplayId, InternalNumber); + } + + static FName MakeFromLoaded(const FNameEntrySerialized& LoadedEntry) + { + FNameStringView View = LoadedEntry.bIsWide + ? FNameStringView(LoadedEntry.WideName, FCStringWide::Strlen(LoadedEntry.WideName)) + : FNameStringView(LoadedEntry.AnsiName, FCStringAnsi::Strlen(LoadedEntry.AnsiName)); + + return Make(View, FNAME_Add, NAME_NO_NUMBER_INTERNAL); + + } + + template + static bool EqualsString(FName Name, const CharType* Str) + { + // Make NAME_None == TEXT("") or nullptr consistent with NAME_None == FName(TEXT("")) or FName(nullptr) + if (Str == nullptr || Str[0] == '\0') + { + return Name == NAME_None; + } + + const FNameEntry& Entry = *Name.GetComparisonNameEntry(); + + uint32 NameLen = Entry.Header.Len; + FNameBuffer Temp; + return Entry.IsWide() + ? StringAndNumberEqualsString(Entry.GetUnterminatedName(Temp.WideName), NameLen, Name.GetNumber(), Str) + : StringAndNumberEqualsString(Entry.GetUnterminatedName(Temp.AnsiName), NameLen, Name.GetNumber(), Str); + } + + static void ReplaceName(FNameEntry& Existing, FNameStringView Updated) + { + check(Existing.Header.bIsWide == Updated.bIsWide); + check(Existing.Header.Len == Updated.Len); + + if (Updated.bIsWide) + { + Existing.StoreName(Updated.Wide, Updated.Len); + } + else + { + Existing.StoreName(Updated.Ansi, Updated.Len); + } + } +}; + + +#if WITH_CASE_PRESERVING_NAME +FNameEntryId FName::GetComparisonIdFromDisplayId(FNameEntryId DisplayId) +{ + return GetEntry(DisplayId)->ComparisonId; +} +#endif + +FName::FName(const WIDECHAR* Name, EFindName FindType) + : FName(FNameHelper::MakeDetectNumber(MakeUnconvertedView(Name), FindType)) +{} + FName::FName(const ANSICHAR* Name, EFindName FindType) -{ - if (Name) - { - Init(Name, NAME_NO_NUMBER_INTERNAL, FindType); - } - else - { - *this = FName(NAME_None); - } -} + : FName(FNameHelper::MakeDetectNumber(MakeUnconvertedView(Name), FindType)) +{} -/** - * Create an FName. If FindType is FNAME_Find, and the string part of the name - * doesn't already exist, then the name will be NAME_None - * - * @param Name Value for the string portion of the name - * @param Number Value for the number portion of the name - * @param FindType Action to take (see EFindName) - */ -FName::FName( const TCHAR* Name, int32 InNumber, EFindName FindType, const bool bSplitName ) -{ - Init(Name, InNumber, FindType, bSplitName); -} +FName::FName(int32 Len, const WIDECHAR* Name, EFindName FindType) + : FName(FNameHelper::MakeDetectNumber(MakeUnconvertedView(Name, Len), FindType)) +{} + +FName::FName(int32 Len, const ANSICHAR* Name, EFindName FindType) + : FName(FNameHelper::MakeDetectNumber(MakeUnconvertedView(Name, Len), FindType)) +{} + +FName::FName(const WIDECHAR* Name, int32 InNumber, EFindName FindType) + : FName(FNameHelper::MakeWithNumber(MakeUnconvertedView(Name), FindType, InNumber)) +{} + +FName::FName(const ANSICHAR* Name, int32 InNumber, EFindName FindType) + : FName(FNameHelper::MakeWithNumber(MakeUnconvertedView(Name), FindType, InNumber)) +{} + +FName::FName(int32 Len, const WIDECHAR* Name, int32 InNumber, EFindName FindType) + : FName(InNumber != NAME_NO_NUMBER_INTERNAL ? FNameHelper::MakeWithNumber(MakeUnconvertedView(Name, Len), FindType, InNumber) + : FNameHelper::MakeDetectNumber(MakeUnconvertedView(Name, Len), FindType)) +{} + +FName::FName(int32 Len, const ANSICHAR* Name, int32 InNumber, EFindName FindType) + : FName(InNumber != NAME_NO_NUMBER_INTERNAL ? FNameHelper::MakeWithNumber(MakeUnconvertedView(Name, Len), FindType, InNumber) + : FNameHelper::MakeDetectNumber(MakeUnconvertedView(Name, Len), FindType)) +{} + +FName::FName(const TCHAR* Name, int32 InNumber, EFindName FindType, bool bSplitName) + : FName(InNumber == NAME_NO_NUMBER_INTERNAL && bSplitName + ? FNameHelper::MakeDetectNumber(MakeUnconvertedView(Name), FindType) + : FNameHelper::MakeWithNumber(MakeUnconvertedView(Name), FindType, InNumber)) +{} FName::FName(const FNameEntrySerialized& LoadedEntry) + : FName(FNameHelper::MakeFromLoaded(LoadedEntry)) +{} + +FName::FName(EName Ename, int32 InNumber) + : ComparisonIndex(GetNamePool().Find(Ename)) +#if WITH_CASE_PRESERVING_NAME + , DisplayIndex(ComparisonIndex) +#endif + , Number(InNumber) { - if (LoadedEntry.bWereHashesLoaded) - { - // Since the name table can change sizes we need to mask the raw hash to the current size so we don't access out of bounds - const uint16 NonCasePreservingHash = LoadedEntry.NonCasePreservingHash & (FNameDefs::NameHashBucketCount - 1); - const uint16 CasePreservingHash = LoadedEntry.CasePreservingHash & (FNameDefs::NameHashBucketCount - 1); - if (LoadedEntry.IsWide()) - { - Init(LoadedEntry.GetWideName(), NAME_NO_NUMBER_INTERNAL, FNAME_Add, NonCasePreservingHash, CasePreservingHash); - } - else - { - Init(LoadedEntry.GetAnsiName(), NAME_NO_NUMBER_INTERNAL, FNAME_Add, NonCasePreservingHash, CasePreservingHash); - } - } - else - { - if (LoadedEntry.IsWide()) - { - Init(LoadedEntry.GetWideName(), NAME_NO_NUMBER_INTERNAL, FNAME_Add, false); - } - else - { - Init(LoadedEntry.GetAnsiName(), NAME_NO_NUMBER_INTERNAL, FNAME_Add, false); - } - } + check(Ename < NAME_MaxHardcodedNameIndex); } -FName::FName( EName HardcodedIndex, const TCHAR* Name ) +FName::FName(EName Ename) + : FName(Ename, NAME_NO_NUMBER_INTERNAL) +{} + +bool FName::operator==(const ANSICHAR* Str) const { - check(HardcodedIndex >= 0); - Init(Name, NAME_NO_NUMBER_INTERNAL, FNAME_Add, false, HardcodedIndex); + return FNameHelper::EqualsString(*this, Str); +} + +bool FName::operator==(const WIDECHAR* Str) const +{ + return FNameHelper::EqualsString(*this, Str); } -/** - * Compares name to passed in one. Sort is alphabetical ascending. - * - * @param Other Name to compare this against - * @return < 0 is this < Other, 0 if this == Other, > 0 if this > Other - */ int32 FName::Compare( const FName& Other ) const { // Names match, check whether numbers match. - if( GetComparisonIndexFast() == Other.GetComparisonIndexFast() ) + if (ComparisonIndex == Other.ComparisonIndex) { return GetNumber() - Other.GetNumber(); } + // Names don't match. This means we don't even need to check numbers. - else - { - TNameEntryArray& Names = GetNames(); - const FNameEntry* const ThisEntry = GetComparisonNameEntry(); - const FNameEntry* const OtherEntry = Other.GetComparisonNameEntry(); - - FNameBuffer TempBuffer1; - FNameBuffer TempBuffer2; - - // If one or both entries return an invalid name entry, the comparison fails - fallback to comparing the index - if (ThisEntry == nullptr || OtherEntry == nullptr) - { - return GetComparisonIndexFast() - Other.GetComparisonIndexFast(); - } - // Ansi/Wide mismatch, convert to wide - else if( ThisEntry->IsWide() != OtherEntry->IsWide() ) - { - return FCStringWide::Stricmp( - ThisEntry->IsWide() ? ThisEntry->GetWideNamePtr(TempBuffer1.WideName) : StringCast(ThisEntry->GetAnsiNamePtr(TempBuffer1.AnsiName)).Get(), - OtherEntry->IsWide() ? OtherEntry->GetWideNamePtr(TempBuffer2.WideName) : StringCast(OtherEntry->GetAnsiNamePtr(TempBuffer2.AnsiName)).Get() ); - } - // Both are wide. - else if( ThisEntry->IsWide() ) - { - return FCStringWide::Stricmp( ThisEntry->GetWideNamePtr(TempBuffer1.WideName), OtherEntry->GetWideNamePtr(TempBuffer2.WideName) ); - } - // Both are ansi. - else - { - return FCStringAnsi::Stricmp( ThisEntry->GetAnsiNamePtr(TempBuffer1.AnsiName), OtherEntry->GetAnsiNamePtr(TempBuffer2.AnsiName) ); - } - } + return CompareDifferentIdsAlphabetically(ComparisonIndex, Other.ComparisonIndex); } -template -uint16 FName::GetCasePreservingHash(const TCharType* Source) +uint32 FName::GetPlainNameString(TCHAR(&OutName)[NAME_SIZE]) const { - return GetRawCasePreservingHash(Source) & (FNameDefs::NameHashBucketCount - 1); + const FNameEntry& Entry = *GetDisplayNameEntry(); + Entry.GetName(OutName); + return Entry.GetNameLength(); } -template -uint16 FName::GetNonCasePreservingHash(const TCharType* Source) +FString FName::GetPlainNameString() const { - return GetRawNonCasePreservingHash(Source) & (FNameDefs::NameHashBucketCount - 1); + return GetDisplayNameEntry()->GetPlainNameString(); } -void FName::Init(const WIDECHAR* InName, int32 InNumber, EFindName FindType, bool bSplitName, int32 HardcodeIndex) +void FName::GetPlainANSIString(ANSICHAR(&AnsiName)[NAME_SIZE]) const { - LLM_SCOPE(ELLMTag::FName); - - const bool bIsPureAnsi = TCString::IsPureAnsi(InName); - // Switch to ANSI if possible to save memory - if (bIsPureAnsi) - { - InitInternal_HashSplit(StringCast(InName).Get(), InNumber, FindType, bSplitName, HardcodeIndex); - } - else - { - InitInternal_HashSplit(InName, InNumber, FindType, bSplitName, HardcodeIndex); - } + GetDisplayNameEntry()->GetAnsiName(AnsiName); } -void FName::Init(const WIDECHAR* InName, int32 InNumber, EFindName FindType, const uint16 NonCasePreservingHash, const uint16 CasePreservingHash) +void FName::GetPlainWIDEString(WIDECHAR(&WideName)[NAME_SIZE]) const { - // Since this comes from the FLinkerLoad we know that it is not pure ansi - InitInternal(InName, InNumber, FindType, -1, NonCasePreservingHash, CasePreservingHash); -} - -void FName::Init(const ANSICHAR* InName, int32 InNumber, EFindName FindType, bool bSplitName, int32 HardcodeIndex) -{ - InitInternal_HashSplit(InName, InNumber, FindType, bSplitName, HardcodeIndex); -} - -void FName::Init(const ANSICHAR* InName, int32 InNumber, EFindName FindType, const uint16 NonCasePreservingHash, const uint16 CasePreservingHash) -{ - InitInternal(InName, InNumber, FindType, -1, NonCasePreservingHash, CasePreservingHash); -} - -template -void FName::InitInternal_HashSplit(const TCharType* InName, int32 InNumber, const EFindName FindType, bool bSplitName, const int32 HardcodeIndex) -{ - TCharType TempBuffer[NAME_SIZE]; - int32 TempNumber; - // if we were passed in a number, we can't split again, other wise, a_1_2_3_4 would change every time - // it was loaded in - if (InNumber == NAME_NO_NUMBER_INTERNAL && bSplitName == true ) - { - if (SplitNameWithCheckImpl(InName, TempBuffer, ARRAY_COUNT(TempBuffer), TempNumber)) - { - InName = TempBuffer; - InNumber = NAME_EXTERNAL_TO_INTERNAL(TempNumber); - } - } - // Hash value of string after splitting - const uint16 NonCasePreservingHash = GetNonCasePreservingHash(InName); -#if WITH_CASE_PRESERVING_NAME - const uint16 CasePreservingHash = GetCasePreservingHash(InName); -#else - const uint16 CasePreservingHash = 0; -#endif - InitInternal(InName, InNumber, FindType, HardcodeIndex, NonCasePreservingHash, CasePreservingHash); -} - -template -void FName::InitInternal(const TCharType* InName, int32 InNumber, const EFindName FindType, const int32 HardcodeIndex, const uint16 NonCasePreservingHash, const uint16 CasePreservingHash) -{ - check(TCString::Strlen(InName)<=NAME_SIZE); - - // initialize the name subsystem if necessary - if (!GetIsInitialized()) - { - StaticInit(); - } - - check(InName); - - // If empty or invalid name was specified, return NAME_None. - if( !InName[0] ) - { - check(HardcodeIndex < 1); // if this is hardcoded, it better be zero - ComparisonIndex = NAME_None; -#if WITH_CASE_PRESERVING_NAME - DisplayIndex = NAME_None; -#endif - Number = NAME_NO_NUMBER_INTERNAL; - return; - } - - - //!!!! Caution, since these are set by static initializers from multiple threads, we must use local variables for this stuff until just before we return. - - bool bWasFoundOrAdded = true; - int32 OutComparisonIndex = HardcodeIndex; - int32 OutDisplayIndex = HardcodeIndex; - - const bool bIsPureAnsi = TCString::IsPureAnsi(InName); - if(bIsPureAnsi) - { - bWasFoundOrAdded = InitInternal_FindOrAdd(StringCast(InName).Get(), FindType, HardcodeIndex, NonCasePreservingHash, CasePreservingHash, OutComparisonIndex, OutDisplayIndex); - } - else - { - bWasFoundOrAdded = InitInternal_FindOrAdd(StringCast(InName).Get(), FindType, HardcodeIndex, NonCasePreservingHash, CasePreservingHash, OutComparisonIndex, OutDisplayIndex); - } - - if(bWasFoundOrAdded) - { - ComparisonIndex = OutComparisonIndex; -#if WITH_CASE_PRESERVING_NAME - DisplayIndex = OutDisplayIndex; -#endif - Number = InNumber; - } - else - { - ComparisonIndex = NAME_None; -#if WITH_CASE_PRESERVING_NAME - DisplayIndex = NAME_None; -#endif - Number = NAME_NO_NUMBER_INTERNAL; - } -} - -template void IncrementNameCount(); -template <> void IncrementNameCount() -{ - FName::NumAnsiNames++; -} -template <> void IncrementNameCount() -{ - FName::NumWideNames++; -} - -template -struct FNameInitHelper -{ -}; - -template <> -struct FNameInitHelper -{ - static const bool IsAnsi = true; - - static const ANSICHAR* GetNameString(const FNameEntry* const NameEntry, ANSICHAR(&OptionalTempBuffer)[NAME_SIZE]) - { - return NameEntry->GetAnsiNamePtr(OptionalTempBuffer); - } - - static void SetNameString(FNameEntry* DestNameEntry, const ANSICHAR* SrcName, const int32 NameLength) - { - DestNameEntry->SetAnsiName(SrcName, NameLength); - } - - static int32 GetIndexShiftValue() - { - return 0; - } - - static int32 GetSize(int32 Length) - { - // Add size required for string to the base size used by the FNameEntry - int32 Size = NameEntryWithoutUnionSize + (Length + 1) * sizeof(ANSICHAR); - return Size; - } -}; - -template <> -struct FNameInitHelper -{ - static const bool IsAnsi = false; - - static const WIDECHAR* GetNameString(const FNameEntry* const NameEntry, WIDECHAR(&OptionalTempBuffer)[NAME_SIZE]) - { - return NameEntry->GetWideNamePtr(OptionalTempBuffer); - } - - static void SetNameString(FNameEntry* DestNameEntry, const WIDECHAR* SrcName, const int32 NameLength) - { - DestNameEntry->SetWideName(SrcName, NameLength); - } - - static int32 GetIndexShiftValue() - { - return 1; - } - - static int32 GetSize(int32 Length) - { - // Add size required for string to the base size used by the FNameEntry - int32 Size = NameEntryWithoutUnionSize + (Length + 1) * sizeof(TCHAR); - return Size; - } -}; - -template -bool FName::InitInternal_FindOrAdd(const TCharType* InName, const EFindName FindType, const int32 HardcodeIndex, const uint16 NonCasePreservingHash, const uint16 CasePreservingHash, int32& OutComparisonIndex, int32& OutDisplayIndex) -{ - const bool bWasFoundOrAdded = InitInternal_FindOrAddNameEntry(InName, FindType, ENameCase::IgnoreCase, NonCasePreservingHash, OutComparisonIndex); - -#if WITH_CASE_PRESERVING_NAME - if(bWasFoundOrAdded && HardcodeIndex < 0) - { - TNameEntryArray& Names = GetNames(); - const FNameEntry* const NameEntry = Names[OutComparisonIndex]; - - // If the string we got back doesn't match the case of the string we provided, also add a case variant version for display purposes - TCharType TempBuffer[NAME_SIZE]; - if(TCString::Strcmp(InName, FNameInitHelper::GetNameString(NameEntry, TempBuffer)) != 0) - { - if(!InitInternal_FindOrAddNameEntry(InName, FindType, ENameCase::CaseSensitive, CasePreservingHash, OutDisplayIndex)) - { - // We don't consider failing to find/add the case variant a full failure - OutDisplayIndex = OutComparisonIndex; - } - } - else - { - OutDisplayIndex = OutComparisonIndex; - } - } - else -#endif - { - OutDisplayIndex = OutComparisonIndex; - } - - return bWasFoundOrAdded; -} - -template -bool FName::InitInternal_FindOrAddNameEntry(const TCharType* InName, const EFindName FindType, const ENameCase ComparisonMode, const uint16 iHash, int32& OutIndex) -{ - CallNameCreationHook(); - if (OutIndex < 0) - { - // Try to find the name in the hash. - FNameEntry* Hash = NameHashHead[iHash].Load(EMemoryOrder::Relaxed); - while (Hash) - { - FNameEntry* NextHash = Hash->HashNext.Load(EMemoryOrder::Relaxed); - - FPlatformMisc::Prefetch( NextHash ); - - // Compare the passed in string - if( Hash->IsEqual( InName, ComparisonMode ) ) - { - // Found it in the hash. - OutIndex = Hash->GetIndex(); - - // Check to see if the caller wants to replace the contents of the - // FName with the specified value. This is useful for compiling - // script classes where the file name is lower case but the class - // was intended to be uppercase. - if (FindType == FNAME_Replace_Not_Safe_For_Threading) - { - check(IsInGameThread()); - - // This *must* be true, or we'll overwrite memory when the - // copy happens if it is longer - int32 NewLength = TCString::Strlen(InName); - check(NewLength == Hash->GetNameLength()); - - FNameInitHelper::SetNameString(Hash, InName, NewLength); - } - check(OutIndex >= 0); - return true; - } - - Hash = NextHash; - } - - // Didn't find name. - if( FindType==FNAME_Find ) - { - // Not found. - return false; - } - } - // acquire the lock - FScopeLock ScopeLock(GetCriticalSection()); - FNameEntry* OldHashHead = NameHashHead[iHash].Load(EMemoryOrder::Relaxed); - if (OutIndex < 0) - { - // Try to find the name in the hash. AGAIN...we might have been adding from a different thread and we just missed it - for (FNameEntry* Hash = OldHashHead; Hash; Hash = Hash->HashNext.Load(EMemoryOrder::Relaxed)) - { - // Compare the passed in string - if( Hash->IsEqual( InName, ComparisonMode ) ) - { - // Found it in the hash. - OutIndex = Hash->GetIndex(); - check(FindType == FNAME_Add); // if this was a replace, well it isn't safe for threading. Find should have already been handled - return true; - } - } - } - FNameEntry* OldHashTail = NameHashTail[iHash].Load(EMemoryOrder::Relaxed); - TNameEntryArray& Names = GetNames(); - if (OutIndex < 0) - { - OutIndex = Names.AddZeroed(1); - } - else - { - check(OutIndex < Names.Num()); - } - FNameEntry* NewEntry = AllocateNameEntry(InName, OutIndex); - if (FPlatformAtomics::InterlockedCompareExchangePointer((void**)&Names[OutIndex], NewEntry, nullptr) != nullptr) // we use an atomic operation to check for unexpected concurrency, verify alignment, etc - { - UE_LOG(LogUnrealNames, Fatal, TEXT("Hardcoded name '%s' at index %i was duplicated (or unexpected concurrency). Existing entry is '%s'."), *NewEntry->GetPlainNameString(), NewEntry->GetIndex(), *Names[OutIndex]->GetPlainNameString() ); - } - if (!OldHashHead) - { - checkSlow(!OldHashTail); - - // atomically assign the new head as other threads may be reading it - if (!NameHashHead[iHash].CompareExchange(OldHashHead, NewEntry)) // we use an atomic operation to check for unexpected concurrency, verify alignment, etc - { - check(0); // someone changed this while we were changing it - } - } - else - { - checkSlow(OldHashTail); - - // atomically update the linked list as other threads may be reading it - FNameEntry* Expected = nullptr; - if (!OldHashTail->HashNext.CompareExchange(Expected, NewEntry)) // we use an atomic operation to check for unexpected concurrency, verify alignment, etc - { - check(0); // someone changed this while we were changing it - } - } - NameHashTail[iHash].Store(NewEntry, EMemoryOrder::Relaxed); // We can non-atomically assign the tail since it's only ever read while locked - check(OutIndex >= 0); - return true; + GetDisplayNameEntry()->GetWideName(WideName); } const FNameEntry* FName::GetComparisonNameEntry() const { - TNameEntryArray& Names = GetNames(); - const NAME_INDEX Index = GetComparisonIndex(); - return Names[Index]; + return &GetNamePool().Resolve(GetComparisonIndex()); } const FNameEntry* FName::GetDisplayNameEntry() const { - TNameEntryArray& Names = GetNames(); - const NAME_INDEX Index = GetDisplayIndex(); - return Names[Index]; + return &GetNamePool().Resolve(GetDisplayIndex()); } FString FName::ToString() const { if (GetNumber() == NAME_NO_NUMBER_INTERNAL) { - if (const FNameEntry* const DisplayEntry = GetDisplayNameEntry()) - { - // Avoids some extra allocations in non-number case - return DisplayEntry->GetPlainNameString(); - } + // Avoids some extra allocations in non-number case + return GetDisplayNameEntry()->GetPlainNameString(); } FString Out; @@ -960,11 +1769,7 @@ void FName::ToString(FString& Out) const // A version of ToString that saves at least one string copy const FNameEntry* const NameEntry = GetDisplayNameEntry(); - if (NameEntry == nullptr) - { - Out = TEXT("*INVALID*"); - } - else if (GetNumber() == NAME_NO_NUMBER_INTERNAL) + if (GetNumber() == NAME_NO_NUMBER_INTERNAL) { Out.Empty(NameEntry->GetNameLength()); NameEntry->AppendNameToString(Out); @@ -979,183 +1784,69 @@ void FName::ToString(FString& Out) const } } +uint32 FName::GetStringLength() const +{ + const FNameEntry& Entry = *GetDisplayNameEntry(); + uint32 NameLen = Entry.GetNameLength(); + + if (GetNumber() == NAME_NO_NUMBER_INTERNAL) + { + return NameLen; + } + else + { + TCHAR NumberSuffixStr[16]; + int32 SuffixLen = FCString::Sprintf(NumberSuffixStr, TEXT("_%d"), NAME_INTERNAL_TO_EXTERNAL(GetNumber())); + check(SuffixLen > 0); + + return NameLen + SuffixLen; + } +} + +uint32 FName::ToString(TCHAR* Out, uint32 OutSize) const +{ + const FNameEntry& Entry = *GetDisplayNameEntry(); + uint32 NameLen = Entry.GetNameLength(); + Entry.GetUnterminatedName(Out, OutSize); + + if (GetNumber() == NAME_NO_NUMBER_INTERNAL) + { + Out[NameLen] = '\0'; + return NameLen; + } + else + { + TCHAR NumberSuffixStr[16]; + int32 SuffixLen = FCString::Sprintf(NumberSuffixStr, TEXT("_%d"), NAME_INTERNAL_TO_EXTERNAL(GetNumber())); + uint32 TotalLen = NameLen + SuffixLen; + check(SuffixLen > 0 && OutSize > TotalLen); + + FPlatformMemory::Memcpy(Out + NameLen, NumberSuffixStr, SuffixLen * sizeof(TCHAR)); + Out[TotalLen] = '\0'; + return TotalLen; + } +} + void FName::AppendString(FString& Out) const { const FNameEntry* const NameEntry = GetDisplayNameEntry(); - if (NameEntry == nullptr) + NameEntry->AppendNameToString( Out ); + if (GetNumber() != NAME_NO_NUMBER_INTERNAL) { - Out += TEXT("*INVALID*"); - } - else - { - NameEntry->AppendNameToString( Out ); - if (GetNumber() != NAME_NO_NUMBER_INTERNAL) - { - Out += TEXT('_'); - Out.AppendInt(NAME_INTERNAL_TO_EXTERNAL(GetNumber())); - } + Out += TEXT('_'); + Out.AppendInt(NAME_INTERNAL_TO_EXTERNAL(GetNumber())); } } - -/*----------------------------------------------------------------------------- - FName subsystem. ------------------------------------------------------------------------------*/ - -void FName::StaticInit() +void FName::DisplayHash(FOutputDevice& Ar) { - check(IsInGameThread()); - /** Global instance used to initialize the GCRCTable. It used to be initialized in appInit() */ - //@todo: Massive workaround for static init order without needing to use a function call for every use of GCRCTable - // This ASSUMES that fname::StaticINit is going to be called BEFORE ANY use of GCRCTable - FCrc::Init(); - - check(GetIsInitialized() == false); - check((FNameDefs::NameHashBucketCount&(FNameDefs::NameHashBucketCount-1)) == 0); - GetIsInitialized() = true; - - { - FScopeLock ScopeLock(GetCriticalSection()); - - TNameEntryArray& Names = GetNames(); - Names.AddZeroed(NAME_MaxHardcodedNameIndex + 1); - } - - { - // Register all hardcoded names. - #define REGISTER_NAME(num,namestr) FName Temp_##namestr(EName(num), TEXT(#namestr)); - #include "UObject/UnrealNames.inl" - #undef REGISTER_NAME - } - -#if DO_CHECK - // Verify no duplicate names. - for (int32 HashIndex = 0; HashIndex < FNameDefs::NameHashBucketCount; HashIndex++) - { - FNameEntry* Hash = NameHashHead[HashIndex].Load(EMemoryOrder::Relaxed); - while (Hash) - { - FNameEntry* NextHash = Hash->HashNext.Load(EMemoryOrder::Relaxed); - for (FNameEntry* Other = NextHash; Other; Other = Other->HashNext.Load(EMemoryOrder::Relaxed)) - { - if (FCString::Stricmp(*Hash->GetPlainNameString(), *Other->GetPlainNameString()) == 0) - { - // we can't print out here because there may be no log yet if this happens before main starts - if (FPlatformMisc::IsDebuggerPresent()) - { - UE_DEBUG_BREAK(); - } - else - { - FPlatformMisc::PromptForRemoteDebugging(false); - FMessageDialog::Open(EAppMsgType::Ok, FText::Format( NSLOCTEXT("UnrealEd", "DuplicatedHardcodedName", "Duplicate hardcoded name: {0}"), FText::FromString( Hash->GetPlainNameString() ) ) ); - FPlatformMisc::RequestExit(false); - } - } - } - - Hash = NextHash; - } - } - // check that the MAX_NETWORKED_HARDCODED_NAME define is correctly set - if (GetMaxNames() <= MAX_NETWORKED_HARDCODED_NAME) - { - if (FPlatformMisc::IsDebuggerPresent()) - { - UE_DEBUG_BREAK(); - } - else - { - FPlatformMisc::PromptForRemoteDebugging(false); - // can't use normal check()/UE_LOG(LogUnrealNames, Fatal,) here - FMessageDialog::Open( EAppMsgType::Ok, FText::Format( NSLOCTEXT("UnrealEd", "MAX_NETWORKED_HARDCODED_NAME Incorrect", "MAX_NETWORKED_HARDCODED_NAME is incorrectly set! (Currently {0}, must be no greater than {1}"), FText::AsNumber( MAX_NETWORKED_HARDCODED_NAME ), FText::AsNumber( GetMaxNames() - 1 ) ) ); - FPlatformMisc::RequestExit(false); - } - } -#endif + GetNamePool().LogStats(Ar); } -bool& FName::GetIsInitialized() +FString FName::SafeString(FNameEntryId InDisplayIndex, int32 InstanceNumber) { - static bool bIsInitialized = false; - return bIsInitialized; -} - -void FName::DisplayHash( FOutputDevice& Ar ) -{ - int32 UsedBins=0, NameCount=0, MemUsed = 0; - for( int32 i=0; iHashNext.Load(EMemoryOrder::Relaxed)) - { - NameCount++; - // Count how much memory this entry is using - MemUsed += FNameEntry::GetSize( Hash->GetNameLength(), Hash->IsWide() ); - } - } - } - Ar.Logf( TEXT("Hash: %i names, %i/%i hash bins, Mem in bytes %i"), NameCount, UsedBins, FNameDefs::NameHashBucketCount, MemUsed); -} - -bool FName::SplitNameWithCheck(const WIDECHAR* OldName, WIDECHAR* NewName, int32 NewNameLen, int32& NewNumber) -{ - return SplitNameWithCheckImpl(OldName, NewName, NewNameLen, NewNumber); -} - -template -bool FName::SplitNameWithCheckImpl(const TCharType* OldName, TCharType* NewName, int32 NewNameLen, int32& NewNumber) -{ - bool bSucceeded = false; - const int32 OldNameLength = TCString::Strlen(OldName); - - if(OldNameLength > 0) - { - // get string length - const TCharType* LastChar = OldName + (OldNameLength - 1); - - // if the last char is a number, then we will try to split - const TCharType* Ch = LastChar; - if (*Ch >= '0' && *Ch <= '9') - { - // go backwards, looking an underscore or the start of the string - // (we don't look at first char because '_9' won't split well) - while (*Ch >= '0' && *Ch <= '9' && Ch > OldName) - { - Ch--; - } - - // if the first non-number was an underscore (as opposed to a letter), - // we can split - if (*Ch == '_') - { - // check for the case where there are multiple digits after the _ and the first one - // is a 0 ("Rocket_04"). Can't split this case. (So, we check if the first char - // is not 0 or the length of the number is 1 (since ROcket_0 is valid) - if (Ch[1] != '0' || LastChar - Ch == 1) - { - // attempt to convert what's following it to a number - uint64 TempConvert = TCString::Atoi64(Ch + 1); - if (TempConvert <= MAX_int32) - { - NewNumber = (int32)TempConvert; - // copy the name portion into the buffer - TCString::Strncpy(NewName, OldName, FMath::Min(Ch - OldName + 1, NewNameLen)); - - // mark successful - bSucceeded = true; - } - } - } - } - } - - // return success code - return bSucceeded; + return FName(InDisplayIndex, InDisplayIndex, InstanceNumber).ToString(); } bool FName::IsValidXName(const FString& InName, const FString& InInvalidChars, FText* OutReason, const FText* InErrorCtx) @@ -1194,32 +1885,194 @@ bool FName::IsValidXName(const FString& InName, const FString& InInvalidChars, F void FName::AutoTest() { +#if DO_CHECK + check(FNameHash::IsAnsiNone("None", 4) == 1); + check(FNameHash::IsAnsiNone("none", 4) == 1); + check(FNameHash::IsAnsiNone("NONE", 4) == 1); + check(FNameHash::IsAnsiNone("nOnE", 4) == 1); + check(FNameHash::IsAnsiNone("None", 5) == 0); + check(FNameHash::IsAnsiNone(TEXT("None"), 4) == 0); + check(FNameHash::IsAnsiNone("nono", 4) == 0); + check(FNameHash::IsAnsiNone("enon", 4) == 0); + const FName AutoTest_1("AutoTest_1"); const FName autoTest_1("autoTest_1"); const FName autoTeSt_1("autoTeSt_1"); - const FName AutoTest1Find("autoTEST_1", EFindName::FNAME_Find); const FName AutoTest_2(TEXT("AutoTest_2")); const FName AutoTestB_2(TEXT("AutoTestB_2")); - const FName NullName(static_cast(nullptr)); check(AutoTest_1 != AutoTest_2); check(AutoTest_1 == autoTest_1); check(AutoTest_1 == autoTeSt_1); + + TCHAR Buffer[FName::StringBufferSize]; + #if WITH_CASE_PRESERVING_NAME check(!FCString::Strcmp(*AutoTest_1.ToString(), TEXT("AutoTest_1"))); check(!FCString::Strcmp(*autoTest_1.ToString(), TEXT("autoTest_1"))); check(!FCString::Strcmp(*autoTeSt_1.ToString(), TEXT("autoTeSt_1"))); check(!FCString::Strcmp(*AutoTestB_2.ToString(), TEXT("AutoTestB_2"))); + + check(FName("ABC").ToString(Buffer) == 3 && !FCString::Strcmp(Buffer, TEXT("ABC"))); + check(FName("abc").ToString(Buffer) == 3 && !FCString::Strcmp(Buffer, TEXT("abc"))); + check(FName(TEXT("abc")).ToString(Buffer) == 3 && !FCString::Strcmp(Buffer, TEXT("abc"))); + check(FName("ABC_0").ToString(Buffer) == 5 && !FCString::Strcmp(Buffer, TEXT("ABC_0"))); + check(FName("ABC_10").ToString(Buffer) == 6 && !FCString::Strcmp(Buffer, TEXT("ABC_10"))); #endif + check(autoTest_1.GetComparisonIndex() == AutoTest_2.GetComparisonIndex()); check(autoTest_1.GetPlainNameString() == AutoTest_1.GetPlainNameString()); check(autoTest_1.GetPlainNameString() == AutoTest_2.GetPlainNameString()); check(*AutoTestB_2.GetPlainNameString() != *AutoTest_2.GetPlainNameString()); check(AutoTestB_2.GetNumber() == AutoTest_2.GetNumber()); check(autoTest_1.GetNumber() != AutoTest_2.GetNumber()); + + check(FCStringAnsi::Strlen("None") == FName().GetStringLength()); + check(FCStringAnsi::Strlen("ABC") == FName("ABC").GetStringLength()); + check(FCStringAnsi::Strlen("ABC_0") == FName("ABC_0").GetStringLength()); + check(FCStringAnsi::Strlen("ABC_9") == FName("ABC_9").GetStringLength()); + check(FCStringAnsi::Strlen("ABC_10") == FName("ABC_10").GetStringLength()); + check(FCStringAnsi::Strlen("ABC_2000000000") == FName("ABC_2000000000").GetStringLength()); + + const FName NullName(static_cast(nullptr)); check(NullName.IsNone()); + check(NullName == FName(NAME_None)); + check(NullName == FName()); + check(NullName == FName("")); + check(NullName == FName(TEXT(""))); + check(NullName == FName("None")); + check(NullName == FName(TEXT("None"))); + check(FName().ToEName()); + check(*FName().ToEName() == NAME_None); + check(NullName.GetComparisonIndex().ToUnstableInt() == 0); + + const FName Cylinder(NAME_Cylinder); + check(Cylinder == FName("Cylinder")); + check(Cylinder.ToEName()); + check(*Cylinder.ToEName() == NAME_Cylinder); + check(Cylinder.GetPlainNameString() == TEXT("Cylinder")); + + check(FName("") == FName(0, "Unused")); + check(FName("Used") == FName(4, "UsedUnused")); + check(FName("Used") == FName(4, "Used")); + + // Test wide strings + FString Wide("Wide "); + Wide[4] = 60000; + FName WideName(*Wide); + check(WideName.GetPlainNameString() == Wide); + check(FName(*Wide).GetPlainNameString() == Wide); + check(FName(*Wide).ToString(Buffer) == 5 && !FCString::Strcmp(Buffer, *Wide)); + check(Wide.Len() == WideName.GetStringLength()); + FString WideLong = FString::ChrN(1000, 60000); + check(FName(*WideLong).GetPlainNameString() == WideLong); + + + // Check that FNAME_Find doesn't add entries + static bool Once = true; + if (Once) + { + check(FName("UniqueUnicorn!!", FNAME_Find) == FName()); + + // Check that FNAME_Find can find entries + const FName UniqueName("UniqueUnicorn!!", FNAME_Add); + check(FName("UniqueUnicorn!!", FNAME_Find) == UniqueName); + check(FName(TEXT("UniqueUnicorn!!"), FNAME_Find) == UniqueName); + check(FName("UNIQUEUNICORN!!", FNAME_Find) == UniqueName); + check(FName(TEXT("UNIQUEUNICORN!!"), FNAME_Find) == UniqueName); + check(FName("uniqueunicorn!!", FNAME_Find) == UniqueName); + + // Check FNAME_Replace_Not_Safe_For_Threading updates casing + check(0 != UniqueName.GetPlainNameString().Compare("UNIQUEunicorn!!", ESearchCase::CaseSensitive)); + const FName UniqueNameReplaced("UNIQUEunicorn!!", FNAME_Replace_Not_Safe_For_Threading); + check(0 == UniqueName.GetPlainNameString().Compare("UNIQUEunicorn!!", ESearchCase::CaseSensitive)); + check(UniqueNameReplaced == UniqueName); + + // Check FNAME_Replace_Not_Safe_For_Threading works with wide string + check(0 != UniqueName.GetPlainNameString().Compare("uniqueunicorn!!", ESearchCase::CaseSensitive)); + const FName UpdatedCasing(TEXT("uniqueunicorn!!"), FNAME_Replace_Not_Safe_For_Threading); + check(0 == UniqueName.GetPlainNameString().Compare("uniqueunicorn!!", ESearchCase::CaseSensitive)); + + // Check FNAME_Replace_Not_Safe_For_Threading adds entries that do not exist + const FName AddedByReplace("WasAdded!!", FNAME_Replace_Not_Safe_For_Threading); + check(FName("WasAdded!!", FNAME_Find) == AddedByReplace); + + Once = false; + } + + check(NumberEqualsString(0, "0")); + check(NumberEqualsString(11, "11")); + check(NumberEqualsString(2147483647, "2147483647")); + + check(!NumberEqualsString(0, "1")); + check(!NumberEqualsString(1, "0")); + check(!NumberEqualsString(11, "12")); + check(!NumberEqualsString(12, "11")); + check(!NumberEqualsString(2147483647, "2147483646")); + check(!NumberEqualsString(2147483646, "2147483647")); + + check(StringAndNumberEqualsString("abc", 3, NAME_EXTERNAL_TO_INTERNAL(10), "abc_10")); + check(!StringAndNumberEqualsString("aba", 3, NAME_EXTERNAL_TO_INTERNAL(10), "abc_10")); + check(!StringAndNumberEqualsString("abc", 2, NAME_EXTERNAL_TO_INTERNAL(10), "abc_10")); + check(!StringAndNumberEqualsString("abc", 2, NAME_EXTERNAL_TO_INTERNAL(11), "abc_10")); + check(!StringAndNumberEqualsString("abc", 3, NAME_EXTERNAL_TO_INTERNAL(10), "aba_10")); + check(!StringAndNumberEqualsString("abc", 3, NAME_EXTERNAL_TO_INTERNAL(10), "abc_11")); + check(!StringAndNumberEqualsString("abc", 3, NAME_EXTERNAL_TO_INTERNAL(10), "abc_100")); + + check(StringAndNumberEqualsString("abc", 3, NAME_EXTERNAL_TO_INTERNAL(0), "abc_0")); + check(!StringAndNumberEqualsString("abc", 3, NAME_EXTERNAL_TO_INTERNAL(0), "abc_1")); + + check(StringAndNumberEqualsString("abc", 3, NAME_NO_NUMBER_INTERNAL, "abc")); + check(!StringAndNumberEqualsString("abc", 2, NAME_NO_NUMBER_INTERNAL, "abc")); + check(!StringAndNumberEqualsString("abc", 3, NAME_NO_NUMBER_INTERNAL, "abcd")); + check(!StringAndNumberEqualsString("abc", 3, NAME_NO_NUMBER_INTERNAL, "abc_0")); + check(!StringAndNumberEqualsString("abc", 3, NAME_NO_NUMBER_INTERNAL, "abc_")); + + TArray Names; + Names.Add("FooB"); + Names.Add("FooABCD"); + Names.Add("FooABC"); + Names.Add("FooAB"); + Names.Add("FooA"); + Names.Add("FooC"); + const WIDECHAR FooWide[] = {'F', 'o', 'o', (WIDECHAR)2000, '\0'}; + Names.Add(FooWide); + Algo::Sort(Names, FNameLexicalLess()); + + check(Names[0] == "FooA"); + check(Names[1] == "FooAB"); + check(Names[2] == "FooABC"); + check(Names[3] == "FooABCD"); + check(Names[4] == "FooB"); + check(Names[5] == "FooC"); + check(Names[6] == FooWide); + +#if 0 + // Check hash table growth still yields the same unique FName ids + static int32 OverflowAtLeastTwiceCount = 4 * FNamePoolInitialSlotsPerShard * FNamePoolShards; + TArray Ids; + for (int I = 0; I < OverflowAtLeastTwiceCount; ++I) + { + FNameEntryId Id = FName(*FString::Printf(TEXT("%d"), I)).GetComparisonIndex(); + Ids.Add(Id); + } + + for (int I = 0; I < OverflowAtLeastTwiceCount; ++I) + { + FNameEntryId Id = FName(*FString::Printf(TEXT("%d"), I)).GetComparisonIndex(); + FNameEntryId OldId = Ids[I]; + + while (Id != OldId) + { + Id = FName(*FString::Printf(TEXT("%d"), I)).GetComparisonIndex(); + } + check(Id == OldId); + } +#endif +#endif // DO_CHECK } + /*----------------------------------------------------------------------------- FNameEntry implementation. -----------------------------------------------------------------------------*/ @@ -1278,7 +2131,7 @@ FArchive& operator<<(FArchive& Ar, FNameEntrySerialized& E) } // mark the name will be wide - E.PreSetIsWideForSerialization(true); + E.bIsWide = true; // get the pointer to the wide array WIDECHAR* WideName = const_cast(E.GetWideName()); @@ -1303,28 +2156,21 @@ FArchive& operator<<(FArchive& Ar, FNameEntrySerialized& E) } // mark the name will be ansi - E.PreSetIsWideForSerialization(false); + E.bIsWide = false; // ansi strings can go right into the AnsiBuffer ANSICHAR* AnsiName = const_cast(E.GetAnsiName()); Ar.Serialize(AnsiName, StringLen); } - if (Ar.UE4Ver() >= VER_UE4_NAME_HASHES_SERIALIZED) - { - // Read the save time calculated hashes to save load time perf - Ar << E.NonCasePreservingHash; - Ar << E.CasePreservingHash; - E.bWereHashesLoaded = true; - if (Ar.UE4Ver() < VER_UE4_FIX_WIDE_STRING_CRC && E.IsWide()) - { - // Regenerate hashes for packages saved before a fix to how hashes are generated - E.NonCasePreservingHash = GetRawNonCasePreservingHash(E.GetWideName()); - } - } + uint16 DummyHashes[2]; + uint32 SkipPastHashBytes = (Ar.UE4Ver() >= VER_UE4_NAME_HASHES_SERIALIZED) * sizeof(DummyHashes); + Ar.Serialize(&DummyHashes, SkipPastHashBytes); } else { + // These hashes are no longer used. They're only kept to maintain serialization format. + // Please remove them if you ever change serialization format. FString Str = E.GetPlainNameString(); Ar << Str; Ar << E.NonCasePreservingHash; @@ -1344,13 +2190,11 @@ void operator<<(FStructuredArchive::FSlot Slot, FNameEntrySerialized& E) if (Slot.GetUnderlyingArchive().IsLoading()) { // mark the name will be wide - E.PreSetIsWideForSerialization(true); + E.bIsWide = true; // get the pointer to the wide array WIDECHAR* WideName = const_cast(E.GetWideName()); FCString::Strcpy(WideName, 1024, *Str); - - E.bWereHashesLoaded = false; } } else @@ -1360,110 +2204,6 @@ void operator<<(FStructuredArchive::FSlot Slot, FNameEntrySerialized& E) } } -/** - * Pooled allocator for FNameEntry structures. Doesn't have to worry about freeing memory as those - * never go away. It simply uses 64K chunks and allocates new ones as space runs out. This reduces - * allocation overhead significantly (only minor waste on 64k boundaries) and also greatly helps - * with fragmentation as 50-100k allocations turn into tens of allocations. - */ -class FNameEntryPoolAllocator -{ -public: - /** Initializes all member variables. */ - FNameEntryPoolAllocator() - { - TotalAllocatedPages = 0; - CurrentPoolStart = NULL; - CurrentPoolEnd = NULL; - check(ThreadGuard.GetValue() == 0); - } - - /** - * Allocates the requested amount of bytes and casts them to a FNameEntry pointer. - * - * @param Size Size in bytes to allocate - * @return Allocation of passed in size cast to a FNameEntry pointer. - */ - FNameEntry* Allocate( int32 Size ) - { - check(ThreadGuard.Increment() == 1); - // Some platforms need all of the name entries to be aligned to 4 bytes, so by - // aligning the size here the next allocation will be aligned to 4 - Size = Align( Size, alignof(FNameEntry) ); - - // Allocate a new pool if current one is exhausted. We don't worry about a little bit - // of waste at the end given the relative size of pool to average and max allocation. - if( CurrentPoolEnd - CurrentPoolStart < Size ) - { - AllocateNewPool(); - } - check( CurrentPoolEnd - CurrentPoolStart >= Size ); - // Return current pool start as allocation and increment by size. - FNameEntry* NameEntry = (FNameEntry*) CurrentPoolStart; - CurrentPoolStart += Size; - check(ThreadGuard.Decrement() == 0); - return NameEntry; - } - - /** - * Returns the amount of memory to allocate for each page pool. - * - * @return Page pool size. - */ - FORCEINLINE int32 PoolSize() - { - // Allocate in 64k chunks as it's ideal for page size. - return 256 * 1024; - } - - /** - * Returns the number of pages that have been allocated so far for names. - * - * @return The number of pages allocated. - */ - FORCEINLINE int32 PageCount() - { - return TotalAllocatedPages; - } - -private: - /** Allocates a new pool. */ - void AllocateNewPool() - { - TotalAllocatedPages++; - CurrentPoolStart = (uint8*) FMemory::Malloc(PoolSize()); - CurrentPoolEnd = CurrentPoolStart + PoolSize(); - } - - /** Beginning of pool. Allocated by AllocateNewPool, incremented by Allocate. */ - uint8* CurrentPoolStart; - /** End of current pool. Set by AllocateNewPool and checked by Allocate. */ - uint8* CurrentPoolEnd; - /** Total number of pages that have been allocated. */ - int32 TotalAllocatedPages; - /** Threadsafe counter to test for unwanted concurrency */ - FThreadSafeCounter ThreadGuard; -}; - -/** Global allocator for name entries. */ -FNameEntryPoolAllocator GNameEntryPoolAllocator; - -template -FNameEntry* AllocateNameEntry(const TCharType* Name, NAME_INDEX Index) -{ - LLM_SCOPE(ELLMTag::FName); - - const SIZE_T NameLen = TCString::Strlen(Name); - int32 NameEntrySize = FNameInitHelper::GetSize( NameLen ); - FNameEntry* NameEntry = GNameEntryPoolAllocator.Allocate( NameEntrySize ); - FName::NameEntryMemorySize += NameEntrySize; - FPlatformAtomics::InterlockedExchange(&NameEntry->Index, (Index << NAME_INDEX_SHIFT) | (FNameInitHelper::GetIndexShiftValue())); - NameEntry->HashNext.Store(nullptr, EMemoryOrder::Relaxed); - FNameInitHelper::SetNameString(NameEntry, Name, NameLen); - IncrementNameCount(); - return NameEntry; -} - #if !UE_BUILD_SHIPPING && !UE_BUILD_TEST @@ -1582,3 +2322,11 @@ void CallNameCreationHook() #endif +uint8** FNameDebugVisualizer::GetBlocks() +{ + static_assert(EntryStride == FNameEntryAllocator::Stride, "Natvis constants out of sync with actual constants"); + static_assert(BlockBits == FNameMaxBlockBits, "Natvis constants out of sync with actual constants"); + static_assert(OffsetBits == FNameBlockOffsetBits, "Natvis constants out of sync with actual constants"); + + return ((FNamePool*)(NamePoolData))->GetBlocksForDebugVisualizer(); +} diff --git a/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformCrashContext.cpp b/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformCrashContext.cpp index e5f64ffb1cd0..57b5b37b8716 100644 --- a/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformCrashContext.cpp +++ b/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformCrashContext.cpp @@ -24,6 +24,7 @@ #include "Misc/App.h" #include "Misc/EngineVersion.h" #include "HAL/PlatformMallocCrash.h" +#include "Unix/UnixPlatformRealTimeSignals.h" #include "Unix/UnixPlatformRunnableThread.h" #include "HAL/ExceptionHandling.h" #include "Stats/Stats.h" @@ -421,6 +422,12 @@ void FUnixCrashContext::GenerateCrashInfoAndLaunchReporter(bool bReportingNonCra } #endif + bool bImplicitSend = false; + if (GConfig) + { + GConfig->GetBool(TEXT("CrashReportClient"), TEXT("bImplicitSend"), bImplicitSend, GEngineIni); + } + // By default we wont upload unless the *.ini has set this to true bool bSendUnattendedBugReports = false; if (GConfig) @@ -571,8 +578,11 @@ void FUnixCrashContext::GenerateCrashInfoAndLaunchReporter(bool bReportingNonCra { CrashReportClientArguments += TEXT(" -NoAnalytics "); } - - if (bUnattended) + else if (bImplicitSend) + { + CrashReportClientArguments += TEXT(" -Unattended -ImplicitSend "); + } + else if (bUnattended) { CrashReportClientArguments += TEXT(" -Unattended "); } @@ -737,6 +747,27 @@ void PlatformCrashHandler(int32 Signal, siginfo_t* Info, void* Context) } } +void ThreadStackWalker(int32 Signal, siginfo_t* Info, void* Context) +{ + ThreadStackUserData* ThreadStackData = static_cast(Info->si_value.sival_ptr); + + if (ThreadStackData) + { + if (ThreadStackData->bCaptureCallStack) + { + // One for the pthread frame and one for siqueue + int32 IgnoreCount = 2; + FPlatformStackWalk::StackWalkAndDump(ThreadStackData->CallStack, ThreadStackData->CallStackSize, IgnoreCount); + } + else + { + ThreadStackData->BackTraceCount = FPlatformStackWalk::CaptureStackBackTrace(ThreadStackData->BackTrace, ThreadStackData->CallStackSize); + } + + ThreadStackData->bDone = true; + } +} + void FUnixPlatformMisc::SetGracefulTerminationHandler() { struct sigaction Action; @@ -827,6 +858,13 @@ void FUnixPlatformMisc::SetCrashHandler(void (* CrashHandler)(const FGenericCras } } + struct sigaction ActionForThread; + FMemory::Memzero(ActionForThread); + sigfillset(&ActionForThread.sa_mask); + ActionForThread.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK; + ActionForThread.sa_sigaction = ThreadStackWalker; + sigaction(THREAD_CALLSTACK_GENERATOR, &ActionForThread, nullptr); + checkf(IsInGameThread(), TEXT("Crash handler for the game thread should be set from the game thread only.")); FRunnableThreadUnix::SetupSignalHandlerStack(FRunnableThreadUnix::MainThreadSignalHandlerStack, sizeof(FRunnableThreadUnix::MainThreadSignalHandlerStack), nullptr); diff --git a/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformMisc.cpp b/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformMisc.cpp index e1ee204f5cac..4bb2aad00e6b 100644 --- a/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformMisc.cpp +++ b/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformMisc.cpp @@ -46,6 +46,8 @@ extern bool GInitializedSDL; +static int SysGetRandomSupported = -1; + namespace PlatformMiscLimits { enum @@ -229,6 +231,12 @@ void FUnixPlatformMisc::PlatformInit() // print output immediately setvbuf(stdout, NULL, _IONBF, 0); } + + if (FParse::Param(FCommandLine::Get(), TEXT("norandomguids"))) + { + // If "-norandomguids" specified, don't use SYS_getrandom syscall + SysGetRandomSupported = 0; + } } extern void CORE_API UnixPlatformStackWalk_UnloadPreloadedModuleSymbol(); @@ -900,6 +908,85 @@ bool FUnixPlatformMisc::IsRunningOnBattery() return bIsOnBattery; } +#if PLATFORM_UNIX && defined(_GNU_SOURCE) + +#include + +// http://man7.org/linux/man-pages/man2/getrandom.2.html +// getrandom() was introduced in version 3.17 of the Linux kernel +// and glibc version 2.25. + +// Check known platforms if SYS_getrandom isn't defined +#if !defined(SYS_getrandom) + #if PLATFORM_CPU_X86_FAMILY && PLATFORM_64BITS + #define SYS_getrandom 318 + #elif PLATFORM_CPU_X86_FAMILY && !PLATFORM_64BITS + #define SYS_getrandom 355 + #elif PLATFORM_CPU_ARM_FAMILY && PLATFORM_64BITS + #define SYS_getrandom 278 + #elif PLATFORM_CPU_ARM_FAMILY && !PLATFORM_64BITS + #define SYS_getrandom 384 + #endif +#endif // !defined(SYS_getrandom) + +#endif // PLATFORM_UNIX && _GNU_SOURCE + +namespace +{ +#if defined(SYS_getrandom) + +#if !defined(GRND_NONBLOCK) + #define GRND_NONBLOCK 0x0001 +#endif + + int SysGetRandom(void *buf, size_t buflen) + { + if (SysGetRandomSupported < 0) + { + int Ret = syscall(SYS_getrandom, buf, buflen, GRND_NONBLOCK); + + // If -1 is returned with ENOSYS, kernel doesn't support getrandom + SysGetRandomSupported = ((Ret == -1) && (errno == ENOSYS)) ? 0 : 1; + } + + return SysGetRandomSupported ? + syscall(SYS_getrandom, buf, buflen, GRND_NONBLOCK) : -1; + } + +#else + + int SysGetRandom(void *buf, size_t buflen) + { + return -1; + } + +#endif // !SYS_getrandom +} + +// If we fail to create a Guid with urandom fallback to the generic platform. +// This maybe need to be tweaked for Servers and hard fail here +void FUnixPlatformMisc::CreateGuid(FGuid& Result) +{ + int BytesRead = SysGetRandom(&Result, sizeof(Result)); + + if (BytesRead == sizeof(Result)) + { + // https://tools.ietf.org/html/rfc4122#section-4.4 + // https://en.wikipedia.org/wiki/Universally_unique_identifier + // + // The 4 bits of digit M indicate the UUID version, and the 1–3 + // most significant bits of digit N indicate the UUID variant. + // xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx + Result[1] = (Result[1] & 0xffff0fff) | 0x00004000; // version 4 + Result[2] = (Result[2] & 0x3fffffff) | 0x80000000; // variant 1 + } + else + { + // Fall back to generic CreateGuid + FGenericPlatformMisc::CreateGuid(Result); + } +} + #if STATS || ENABLE_STATNAMEDEVENTS void FUnixPlatformMisc::BeginNamedEventFrame() { diff --git a/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformStackWalk.cpp b/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformStackWalk.cpp index 026da8b4981a..86dcab8a9876 100644 --- a/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformStackWalk.cpp +++ b/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformStackWalk.cpp @@ -11,11 +11,20 @@ #include "Misc/ScopeLock.h" #include "Misc/CommandLine.h" #include "Unix/UnixPlatformCrashContext.h" +#include "Unix/UnixPlatformRealTimeSignals.h" #include "HAL/ExceptionHandling.h" #include "HAL/PlatformProcess.h" #include "HAL/PlatformTime.h" #include +#include + +#include "HAL/IConsoleManager.h" + +static TAutoConsoleVariable CVarUnixPlatformThreadCallStackMaxWait( + TEXT("UnixPlatformThreadStackWalk.MaxWait"), + 60.0f, + TEXT("The number of seconds allowed to spin before killing the process, with the assumption the signal handler has hung.")); // Init'ed in UnixPlatformMemory. Once this is tested more we can remove this fallback flag extern bool CORE_API GFullCrashCallstack; @@ -494,74 +503,89 @@ bool FUnixPlatformStackWalk::ProgramCounterToHumanReadableString( int32 CurrentC // Get filename, source file and line number FUnixCrashContext* UnixContext = static_cast< FUnixCrashContext* >( Context ); - if (UnixContext) + + // for ensure, use the fast path - do not even attempt to get detailed info as it will result in long hitch + bool bAddDetailedInfo = UnixContext ? UnixContext->GetType() != ECrashContextType::Ensure : false; + + // Program counters in the backtrace point to the location from where the execution will be resumed (in all frames except the one where we crashed), + // which results in callstack pointing to the next lines in code. In order to determine the source line where the actual call happened, we need to go + // back to the line that had the "call" instruction. Since x86(-64) instructions vary in length, we cannot do it reliably without disassembling, + // just go back one byte - even if it's not the actual address of the call site. + int OffsetToCallsite = CurrentCallDepth > 0 ? 1 : 0; + + FProgramCounterSymbolInfo TempSymbolInfo; + + // We can print detail info out during ensures, the only reason not to is if fail to populate the symbol info all the way + bAddDetailedInfo = PopulateProgramCounterSymbolInfoFromSymbolFile(ProgramCounter - OffsetToCallsite, TempSymbolInfo); + + if (bAddDetailedInfo) { - // for ensure, use the fast path - do not even attempt to get detailed info as it will result in long hitch - bool bAddDetailedInfo = UnixContext->GetType() != ECrashContextType::Ensure; + // append Module!FunctionName() [Source.cpp:X] to HumanReadableString + FCStringAnsi::Strncat(HumanReadableString, TempSymbolInfo.ModuleName, HumanReadableStringSize); + FCStringAnsi::Strncat(HumanReadableString, "!", HumanReadableStringSize); + FCStringAnsi::Strncat(HumanReadableString, TempSymbolInfo.FunctionName, HumanReadableStringSize); + FCStringAnsi::Sprintf(TempArray, " [%s:%d]", TempSymbolInfo.Filename, TempSymbolInfo.LineNumber); + FCStringAnsi::Strncat(HumanReadableString, TempArray, HumanReadableStringSize); - // Program counters in the backtrace point to the location from where the execution will be resumed (in all frames except the one where we crashed), - // which results in callstack pointing to the next lines in code. In order to determine the source line where the actual call happened, we need to go - // back to the line that had the "call" instruction. Since x86(-64) instructions vary in length, we cannot do it reliably without disassembling, - // just go back one byte - even if it's not the actual address of the call site. - int OffsetToCallsite = CurrentCallDepth > 0 ? 1 : 0; - - FProgramCounterSymbolInfo TempSymbolInfo; - - // We can print detail info out during ensures, the only reason not to is if fail to populate the symbol info all the way - bAddDetailedInfo = PopulateProgramCounterSymbolInfoFromSymbolFile(ProgramCounter - OffsetToCallsite, TempSymbolInfo); - - if (bAddDetailedInfo) + if (UnixContext) { - // append Module!FunctionName() [Source.cpp:X] to HumanReadableString - FCStringAnsi::Strncat(HumanReadableString, TempSymbolInfo.ModuleName, HumanReadableStringSize); - FCStringAnsi::Strncat(HumanReadableString, "!", HumanReadableStringSize); - FCStringAnsi::Strncat(HumanReadableString, TempSymbolInfo.FunctionName, HumanReadableStringSize); - FCStringAnsi::Sprintf(TempArray, " [%s:%d]", TempSymbolInfo.Filename, TempSymbolInfo.LineNumber); - FCStringAnsi::Strncat(HumanReadableString, TempArray, HumanReadableStringSize); - // append Module!FunctioName [Source.cpp:X] to MinidumpCallstackInfo FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, TempSymbolInfo.ModuleName, ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, "!", ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, TempSymbolInfo.FunctionName, ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, TempArray, ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); } - else + } + else + { + const char* ModuleName = nullptr; + const char* FunctionName = nullptr; + + // We have failed to fully populate the SymbolInfo, but we could still have basic information. Lets try to print as much info as possible + if (TempSymbolInfo.ModuleName[0] != '\0') { - const char* ModuleName = nullptr; - const char* FunctionName = nullptr; + ModuleName = TempSymbolInfo.ModuleName; + } - // We have failed to fully populate the SymbolInfo, but we could still have basic information. Lets try to print as much info as possible - if (TempSymbolInfo.ModuleName[0] != '\0') - { - ModuleName = TempSymbolInfo.ModuleName; - } + if (TempSymbolInfo.FunctionName[0] != '\0') + { + FunctionName = TempSymbolInfo.FunctionName; + } - if (TempSymbolInfo.FunctionName[0] != '\0') - { - FunctionName = TempSymbolInfo.FunctionName; - } - - FCStringAnsi::Strncat(HumanReadableString, ModuleName != nullptr ? ModuleName : "", HumanReadableStringSize); - FCStringAnsi::Strncat(HumanReadableString, "!", HumanReadableStringSize); - FCStringAnsi::Strncat(HumanReadableString, FunctionName != nullptr ? FunctionName : "UnknownFunction", HumanReadableStringSize); - FCStringAnsi::Strncat(HumanReadableString, FunctionName && TempSymbolInfo.SymbolDisplacement ? "(+": "(", HumanReadableStringSize); + FCStringAnsi::Strncat(HumanReadableString, ModuleName != nullptr ? ModuleName : "", HumanReadableStringSize); + FCStringAnsi::Strncat(HumanReadableString, "!", HumanReadableStringSize); + FCStringAnsi::Strncat(HumanReadableString, FunctionName != nullptr ? FunctionName : "UnknownFunction", HumanReadableStringSize); + FCStringAnsi::Strncat(HumanReadableString, FunctionName && TempSymbolInfo.SymbolDisplacement ? "(+": "(", HumanReadableStringSize); + if (UnixContext) + { FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, ModuleName != nullptr ? ModuleName : "Unknown", ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, "!", ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, FunctionName != nullptr ? FunctionName : "UnknownFunction", ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, FunctionName && TempSymbolInfo.SymbolDisplacement ? "(+": "(", ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); - - if (TempSymbolInfo.SymbolDisplacement > 0x0) - { - FCStringAnsi::Sprintf(TempArray, "%p", TempSymbolInfo.SymbolDisplacement); - FCStringAnsi::Strncat(HumanReadableString, TempArray, HumanReadableStringSize); - FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, TempArray, ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); - } - - FCStringAnsi::Strncat(HumanReadableString, ")", HumanReadableStringSize); - FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, ")", ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); } + if (TempSymbolInfo.SymbolDisplacement > 0x0) + { + FCStringAnsi::Sprintf(TempArray, "%p", TempSymbolInfo.SymbolDisplacement); + FCStringAnsi::Strncat(HumanReadableString, TempArray, HumanReadableStringSize); + + if (UnixContext) + { + FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, TempArray, ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); + } + } + + FCStringAnsi::Strncat(HumanReadableString, ")", HumanReadableStringSize); + + if (UnixContext) + { + FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, ")", ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); + } + } + + if (UnixContext) + { FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, "\r\n", ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); // this one always uses Windows line terminators } } @@ -674,19 +698,72 @@ uint32 FUnixPlatformStackWalk::CaptureStackBackTrace( uint64* BackTrace, uint32 return (uint32)Size; } -void FUnixPlatformStackWalk::ThreadStackWalkAndDump(ANSICHAR* HumanReadableString, SIZE_T HumanReadableStringSize, int32 IgnoreCount, uint32 ThreadId) +namespace { - // This is intentional on servers. Right now we cannot symbolicate the other thread, so we crash it instead, which also helps in identifying lock ups - if (UE_SERVER) + void WaitForSignalHandlerToFinishOrCrash(ThreadStackUserData& ThreadStack) { - if (kill(ThreadId, SIGQUIT) == 0) + float EndWaitTimestamp = FPlatformTime::Seconds() + CVarUnixPlatformThreadCallStackMaxWait.AsVariable()->GetFloat(); + float CurrentTimestamp = FPlatformTime::Seconds(); + + while (!ThreadStack.bDone) { - // do not exit, crash is imminent anyway (signals are delivered asynchronously) - for(;;) + if (CurrentTimestamp > EndWaitTimestamp) { + // We have waited for as long as we should for the signal handler to finish. Assume it has hang and we need to kill our selfs + *(int*)0x10 = 0x0; } + + CurrentTimestamp = FPlatformTime::Seconds(); } } + + void GatherCallstackFromThread(ThreadStackUserData& ThreadStack, uint64 ThreadId) + { + sigval UserData; + UserData.sival_ptr = &ThreadStack; + + siginfo_t info; + memset (&info, 0, sizeof (siginfo_t)); + info.si_signo = THREAD_CALLSTACK_GENERATOR; + info.si_code = SI_QUEUE; + info.si_pid = syscall(SYS_getpid); + info.si_uid = syscall(SYS_getuid); + info.si_value = UserData; + + // Avoid using sigqueue here as if the ThreadId is already blocked and in a signal handler + // sigqueue will try a different thread signal handler and report the wrong callstack + if (syscall(SYS_rt_tgsigqueueinfo, info.si_pid, ThreadId, THREAD_CALLSTACK_GENERATOR, &info) == 0) + { + WaitForSignalHandlerToFinishOrCrash(ThreadStack); + } + } +} + +void FUnixPlatformStackWalk::ThreadStackWalkAndDump(ANSICHAR* HumanReadableString, SIZE_T HumanReadableStringSize, int32 IgnoreCount, uint32 ThreadId) +{ + ThreadStackUserData ThreadCallStack; + ThreadCallStack.bCaptureCallStack = true; + ThreadCallStack.CallStackSize = HumanReadableStringSize; + ThreadCallStack.CallStack = HumanReadableString; + ThreadCallStack.BackTraceCount = 0; + ThreadCallStack.bDone = false; + + GatherCallstackFromThread(ThreadCallStack, ThreadId); +} + +uint32 FUnixPlatformStackWalk::CaptureThreadStackBackTrace(uint64 ThreadId, uint64* BackTrace, uint32 MaxDepth) +{ + ThreadStackUserData ThreadBackTrace; + ThreadBackTrace.bCaptureCallStack = false; + ThreadBackTrace.CallStackSize = MaxDepth; + ThreadBackTrace.BackTrace = BackTrace; + ThreadBackTrace.BackTraceCount = 0; + ThreadBackTrace.bDone = false; + + GatherCallstackFromThread(ThreadBackTrace, ThreadId); + + // The signal handler will set this value, we just have to make sure we wait for the signal handler we raised to finish + return ThreadBackTrace.BackTraceCount; } namespace diff --git a/Engine/Source/Runtime/Core/Private/Windows/WindowsAsyncIO.h b/Engine/Source/Runtime/Core/Private/Windows/WindowsAsyncIO.h index 2418ed00fa4f..743687ba1f3a 100644 --- a/Engine/Source/Runtime/Core/Private/Windows/WindowsAsyncIO.h +++ b/Engine/Source/Runtime/Core/Private/Windows/WindowsAsyncIO.h @@ -2,6 +2,8 @@ #pragma once +#include "ProfilingDebugging/PlatformFileTrace.h" + class FWindowsReadRequest; class FWindowsAsyncReadFileHandle; @@ -125,6 +127,7 @@ public: OverlappedIO.OffsetHigh = LI.HighPart; } OverlappedIO.hEvent = GetIOPooledEvent(); + TRACE_PLATFORMFILE_BEGIN_READ(&OverlappedIO, FileHandle, AlignedOffset, AlignedBytesToRead); if (!ReadFile(FileHandle, TempMemory ? TempMemory : Memory, AlignedBytesToRead, (LPDWORD)&NumRead, &OverlappedIO)) { uint32 ErrorCode = GetLastError(); @@ -162,11 +165,16 @@ public: if (GTriggerFailedWindowsRead || !GetOverlappedResult(FileHandle, &OverlappedIO, (LPDWORD)&BytesRead, TRUE)) { + TRACE_PLATFORMFILE_END_READ(&OverlappedIO, 0); GTriggerFailedWindowsRead = false; uint32 ErrorCode = GetLastError(); FailedMessage = FString::Printf(TEXT("FWindowsReadRequest GetOverlappedResult Code = %x Offset = %lld Size = %lld FileSize = %lld File = %s"), ErrorCode, AlignedOffset, AlignedBytesToRead, FileSize, GetFileNameForErrorMessagesAndPanicRetry()); bFailed = true; } + else + { + TRACE_PLATFORMFILE_END_READ(&OverlappedIO, BytesRead); + } if (!bFailed && int64(BytesRead) < BytesToRead + (Offset - AlignedOffset)) { uint32 ErrorCode = GetLastError(); @@ -383,7 +391,9 @@ public: FScopeLock Lock(&LiveRequestsCritical); check(!LiveRequests.Num()); // must delete all requests before you delete the handle #endif + TRACE_PLATFORMFILE_BEGIN_CLOSE(FileHandle); CloseHandle(FileHandle); + TRACE_PLATFORMFILE_END_CLOSE(); } void RemoveRequest(FWindowsReadRequest* Req) { diff --git a/Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformCrashContext.cpp b/Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformCrashContext.cpp index 1d3a1b70cbea..2799da75302f 100644 --- a/Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformCrashContext.cpp +++ b/Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformCrashContext.cpp @@ -314,6 +314,12 @@ int32 ReportCrashUsingCrashReportClient(FWindowsPlatformCrashContext& InContext, // Suppress the user input dialog if we're running in unattended mode bool bNoDialog = FApp::IsUnattended() || ReportUI == EErrorReportUI::ReportInUnattendedMode || IsRunningDedicatedServer(); + bool bImplicitSend = false; + if (GConfig) + { + GConfig->GetBool(TEXT("CrashReportClient"), TEXT("bImplicitSend"), bImplicitSend, GEngineIni); + } + bool bSendUnattendedBugReports = true; if (GConfig) { @@ -444,7 +450,11 @@ int32 ReportCrashUsingCrashReportClient(FWindowsPlatformCrashContext& InContext, // Pass nullrhi to CRC when the engine is in this mode to stop the CRC attempting to initialize RHI when the capability isn't available bool bNullRHI = !FApp::CanEverRender(); - if (bNoDialog || bNullRHI) + if (bImplicitSend) + { + CrashReportClientArguments += TEXT(" -Unattended -ImplicitSend"); + } + else if (bNoDialog || bNullRHI) { CrashReportClientArguments += TEXT(" -Unattended"); } diff --git a/Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformFile.cpp b/Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformFile.cpp index 524c6078d282..11252e5afcb2 100644 --- a/Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformFile.cpp +++ b/Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformFile.cpp @@ -19,6 +19,7 @@ #include "Async/AsyncWork.h" #include "Misc/ScopeLock.h" #include "HAL/IConsoleManager.h" +#include "ProfilingDebugging/PlatformFileTrace.h" #include "Windows/AllowWindowsPlatformTypes.h" @@ -178,7 +179,9 @@ protected: if (Handle != nullptr) { // Close the file handle + TRACE_PLATFORMFILE_BEGIN_CLOSE(Handle); CloseHandle(Handle); + TRACE_PLATFORMFILE_END_CLOSE(); Handle = nullptr; } return true; @@ -225,11 +228,13 @@ protected: uint32 NumRead = 0; if (GetOverlappedResult(Handle, &OverlappedIO, (::DWORD*)&NumRead, true) != false) { + TRACE_PLATFORMFILE_END_READ(&OverlappedIO, NumRead); UpdateFileOffsetAfterRead(NumRead); return true; } else if (GetLastError() == ERROR_HANDLE_EOF) { + TRACE_PLATFORMFILE_END_READ(&OverlappedIO, 0); bIsAtEOF = true; return true; } @@ -244,11 +249,13 @@ protected: CurrentAsyncReadBuffer = BufferToReadInto; uint32 NumRead = 0; // Now kick off an async read + TRACE_PLATFORMFILE_BEGIN_READ(&OverlappedIO, Handle, OverlappedFilePos, BufferSize); if (!ReadFile(Handle, Buffers[BufferToReadInto], BufferSize, (::DWORD*)&NumRead, &OverlappedIO)) { uint32 ErrorCode = GetLastError(); if (ErrorCode != ERROR_IO_PENDING) { + TRACE_PLATFORMFILE_END_READ(&OverlappedIO, 0); bIsAtEOF = true; bHasReadOutstanding = false; } @@ -256,6 +263,7 @@ protected: else { // Read completed immediately + TRACE_PLATFORMFILE_END_READ(&OverlappedIO, NumRead); UpdateFileOffsetAfterRead(NumRead); } } @@ -506,7 +514,9 @@ public: } virtual ~FFileHandleWindows() { + TRACE_PLATFORMFILE_BEGIN_CLOSE(FileHandle); CloseHandle(FileHandle); + TRACE_PLATFORMFILE_END_CLOSE(); FileHandle = NULL; } virtual int64 Tell(void) override @@ -541,12 +551,14 @@ public: check(IsValid()); uint32 NumRead = 0; // Now kick off an async read + TRACE_PLATFORMFILE_BEGIN_READ(&OverlappedIO, FileHandle, FilePos, BytesToRead); if (!ReadFile(FileHandle, Destination, BytesToRead, (::DWORD*)&NumRead, &OverlappedIO)) { uint32 ErrorCode = GetLastError(); if (ErrorCode != ERROR_IO_PENDING) { // Read failed + TRACE_PLATFORMFILE_END_READ(&OverlappedIO, 0); return false; } // Wait for the read to complete @@ -554,9 +566,11 @@ public: if (!GetOverlappedResult(FileHandle, &OverlappedIO, (::DWORD*)&NumRead, true)) { // Read failed + TRACE_PLATFORMFILE_END_READ(&OverlappedIO, 0); return false; } } + TRACE_PLATFORMFILE_END_READ(&OverlappedIO, NumRead); // Update where we are in the file FilePos += NumRead; UpdateOverlappedPos(); @@ -567,12 +581,14 @@ public: check(IsValid()); uint32 NumWritten = 0; // Now kick off an async write + TRACE_PLATFORMFILE_BEGIN_WRITE(this, FileHandle, FilePos, BytesToWrite); if (!WriteFile(FileHandle, Source, BytesToWrite, (::DWORD*)&NumWritten, &OverlappedIO)) { uint32 ErrorCode = GetLastError(); if (ErrorCode != ERROR_IO_PENDING) { // Write failed + TRACE_PLATFORMFILE_END_WRITE(this, 0); return false; } // Wait for the write to complete @@ -580,9 +596,11 @@ public: if (!GetOverlappedResult(FileHandle, &OverlappedIO, (::DWORD*)&NumWritten, true)) { // Write failed + TRACE_PLATFORMFILE_END_WRITE(this, 0); return false; } } + TRACE_PLATFORMFILE_END_WRITE(this, NumWritten); // Update where we are in the file FilePos += NumWritten; UpdateOverlappedPos(); @@ -657,7 +675,9 @@ public: { check(!NumOutstandingRegions); // can't delete the file before you delete all outstanding regions CloseHandle(MappingHandle); + TRACE_PLATFORMFILE_BEGIN_CLOSE(Handle); CloseHandle(Handle); + TRACE_PLATFORMFILE_END_CLOSE(); } virtual IMappedFileRegion* MapRegion(int64 Offset = 0, int64 BytesToMap = MAX_int64, bool bPreloadHint = false) override { @@ -788,7 +808,9 @@ public: virtual void SetTimeStamp(const TCHAR* Filename, FDateTime DateTime) override { + TRACE_PLATFORMFILE_BEGIN_OPEN(Filename); HANDLE Handle = CreateFileW(*NormalizeFilename(Filename), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr); + TRACE_PLATFORMFILE_END_OPEN(Handle); if (Handle != INVALID_HANDLE_VALUE) { const FILETIME ModificationFileTime = UEDateTimeToWindowsFileTime(DateTime); @@ -796,7 +818,9 @@ public: { UE_LOG(LogTemp, Warning, TEXT("SetTimeStamp: Failed to SetFileTime on %s"), Filename); } + TRACE_PLATFORMFILE_BEGIN_CLOSE(Handle); CloseHandle(Handle); + TRACE_PLATFORMFILE_END_CLOSE(); } else { @@ -817,7 +841,9 @@ public: virtual FString GetFilenameOnDisk(const TCHAR* Filename) override { + TRACE_PLATFORMFILE_BEGIN_OPEN(Filename); HANDLE hFile = CreateFile(Filename, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL); + TRACE_PLATFORMFILE_END_OPEN(hFile); if(hFile == INVALID_HANDLE_VALUE) { return NormalizeFilename(Filename); @@ -842,8 +868,9 @@ public: break; } } - + TRACE_PLATFORMFILE_BEGIN_CLOSE(hFile); CloseHandle(hFile); + TRACE_PLATFORMFILE_END_CLOSE(); NormalizedFileName.RemoveFromStart(TEXT("\\\\?\\"), ESearchCase::CaseSensitive); NormalizedFileName.ReplaceInline(TEXT("\\"), TEXT("/")); @@ -862,7 +889,9 @@ public: FString NormalizedFilename = NormalizeFilename(Filename); + TRACE_PLATFORMFILE_BEGIN_OPEN(Filename); HANDLE Handle = CreateFileW(*NormalizedFilename, Access, WinFlags, NULL, Create, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); + TRACE_PLATFORMFILE_END_OPEN(Handle); // we can't really fail here because this is intended to be an async open return new FWindowsAsyncReadFileHandle(Handle, *NormalizedFilename); @@ -877,13 +906,17 @@ public: #define USE_OVERLAPPED_IO (!IS_PROGRAM && !WITH_EDITOR) // Use straightforward synchronous I/O in cooker/editor #if USE_OVERLAPPED_IO + TRACE_PLATFORMFILE_BEGIN_OPEN(Filename); HANDLE Handle = CreateFileW(*NormalizeFilename(Filename), Access, WinFlags, NULL, Create, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); + TRACE_PLATFORMFILE_END_OPEN(Handle); if (Handle != INVALID_HANDLE_VALUE) { return new FAsyncBufferedFileReaderWindows(Handle); } #else + TRACE_PLATFORMFILE_BEGIN_OPEN(Filename); HANDLE Handle = CreateFileW(*NormalizeFilename(Filename), Access, WinFlags, NULL, Create, FILE_ATTRIBUTE_NORMAL, NULL); + TRACE_PLATFORMFILE_END_OPEN(Handle); if (Handle != INVALID_HANDLE_VALUE) { return new FFileHandleWindows(Handle); @@ -897,7 +930,9 @@ public: uint32 Access = GENERIC_READ; uint32 WinFlags = FILE_SHARE_READ | (bAllowWrite ? FILE_SHARE_WRITE : 0); uint32 Create = OPEN_EXISTING; + TRACE_PLATFORMFILE_BEGIN_OPEN(Filename); HANDLE Handle = CreateFileW(*NormalizeFilename(Filename), Access, WinFlags, NULL, Create, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); + TRACE_PLATFORMFILE_END_OPEN(Handle); if (Handle != INVALID_HANDLE_VALUE) { return new FFileHandleWindows(Handle); @@ -910,7 +945,9 @@ public: uint32 Access = GENERIC_WRITE | (bAllowRead ? GENERIC_READ : 0); uint32 WinFlags = bAllowRead ? FILE_SHARE_READ : 0; uint32 Create = bAppend ? OPEN_ALWAYS : CREATE_ALWAYS; + TRACE_PLATFORMFILE_BEGIN_OPEN(Filename); HANDLE Handle = CreateFileW(*NormalizeFilename(Filename), Access, WinFlags, NULL, Create, FILE_ATTRIBUTE_NORMAL, NULL); + TRACE_PLATFORMFILE_END_OPEN(Handle); if(Handle != INVALID_HANDLE_VALUE) { FFileHandleWindows *PlatformFileHandle = new FFileHandleWindows(Handle); @@ -934,7 +971,9 @@ public: uint32 Access = GENERIC_READ; uint32 WinFlags = FILE_SHARE_READ; uint32 Create = OPEN_EXISTING; + TRACE_PLATFORMFILE_BEGIN_OPEN(Filename); HANDLE Handle = CreateFileW(*NormalizeFilename(Filename), Access, WinFlags, NULL, Create, FILE_ATTRIBUTE_NORMAL, NULL); + TRACE_PLATFORMFILE_END_OPEN(Handle); if (Handle == INVALID_HANDLE_VALUE) { return nullptr; @@ -942,7 +981,9 @@ public: HANDLE MappingHandle = CreateFileMapping(Handle, NULL, PAGE_READONLY, 0, 0, NULL); if (MappingHandle == INVALID_HANDLE_VALUE) { + TRACE_PLATFORMFILE_BEGIN_CLOSE(Handle); CloseHandle(Handle); + TRACE_PLATFORMFILE_END_CLOSE(); return nullptr; } return new FMappedFileHandleWindows(Handle, MappingHandle, Size, Filename); diff --git a/Engine/Source/Runtime/Core/Private/Windows/WindowsRunnableThread.h b/Engine/Source/Runtime/Core/Private/Windows/WindowsRunnableThread.h index 789a070f1a0e..b4de1603c28f 100644 --- a/Engine/Source/Runtime/Core/Private/Windows/WindowsRunnableThread.h +++ b/Engine/Source/Runtime/Core/Private/Windows/WindowsRunnableThread.h @@ -48,7 +48,6 @@ class FRunnableThreadWin uint32 dwFlags; // Reserved for future use, must be zero. }; - Sleep(10); THREADNAME_INFO ThreadNameInfo; ThreadNameInfo.dwType = 0x1000; ThreadNameInfo.szName = ThreadName; diff --git a/Engine/Source/Runtime/Core/Public/Algo/IntroSort.h b/Engine/Source/Runtime/Core/Public/Algo/IntroSort.h index 04f2de3e4a47..237453b37c75 100644 --- a/Engine/Source/Runtime/Core/Public/Algo/IntroSort.h +++ b/Engine/Source/Runtime/Core/Public/Algo/IntroSort.h @@ -60,7 +60,7 @@ namespace AlgoImpl T *Max, *Item; for( Max=Current.Min, Item=Current.Min+1; Item<=Current.Max; Item++ ) { - if( Predicate( Invoke( Projection, *Max ), Invoke( Projection, *Item ) ) ) + if( Invoke( Predicate, Invoke( Projection, *Max ), Invoke( Projection, *Item ) ) ) { Max = Item; } @@ -78,8 +78,8 @@ namespace AlgoImpl Inner.Max = Current.Max+1; for( ; ; ) { - while( ++Inner.Min<=Current.Max && !Predicate( Invoke( Projection, *Current.Min ), Invoke( Projection, *Inner.Min ) ) ); - while( --Inner.Max> Current.Min && !Predicate( Invoke( Projection, *Inner.Max ), Invoke( Projection, *Current.Min ) ) ); + while( ++Inner.Min<=Current.Max && !Invoke( Predicate, Invoke( Projection, *Current.Min ), Invoke( Projection, *Inner.Min ) ) ); + while( --Inner.Max> Current.Min && !Invoke( Predicate, Invoke( Projection, *Inner.Max ), Invoke( Projection, *Current.Min ) ) ); if( Inner.Min>Inner.Max ) { break; diff --git a/Engine/Source/Runtime/Core/Public/Algo/Reverse.h b/Engine/Source/Runtime/Core/Public/Algo/Reverse.h index 7e28733eaf1a..cee35b4ee035 100644 --- a/Engine/Source/Runtime/Core/Public/Algo/Reverse.h +++ b/Engine/Source/Runtime/Core/Public/Algo/Reverse.h @@ -3,6 +3,7 @@ #pragma once #include "CoreTypes.h" +#include "Templates/UnrealTemplate.h" // for Swap namespace AlgoImpl { diff --git a/Engine/Source/Runtime/Core/Public/Algo/Rotate.h b/Engine/Source/Runtime/Core/Public/Algo/Rotate.h new file mode 100644 index 000000000000..26574982e6cf --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/Algo/Rotate.h @@ -0,0 +1,63 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Templates/UnrealTemplate.h" // For GetData, GetNum, Swap + + +namespace AlgoImpl +{ + template + int32 RotateInternal(T* First, int32 Num, int32 Count) + { + if (Count == 0) + { + return Num; + } + + if (Count >= Num) + { + return 0; + } + + T* Iter = First; + T* Mid = First + Count; + T* End = First + Num; + + T* OldMid = Mid; + for (;;) + { + Swap(*Iter++, *Mid++); + if (Mid == End) + { + if (Iter == OldMid) + { + return Num - Count; + } + + Mid = OldMid; + } + else if (Iter == OldMid) + { + OldMid = Mid; + } + } + } +} + +namespace Algo +{ + /** + * Rotates a given amount of elements from the front of the range to the end of the range. + * + * @param Range The range to rotate. + * @param Num The number of elements to rotate from the front of the range. + * + * @return The new index of the element that was previously at the start of the range. + */ + template + FORCEINLINE int32 Rotate(RangeType& Range, int32 Count) + { + AlgoImpl::RotateInternal(GetData(Range), GetNum(Range), Count); + } +} diff --git a/Engine/Source/Runtime/Core/Public/Algo/StableSort.h b/Engine/Source/Runtime/Core/Public/Algo/StableSort.h new file mode 100644 index 000000000000..4c50a96c7a46 --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/Algo/StableSort.h @@ -0,0 +1,166 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Algo/BinarySearch.h" +#include "Algo/Rotate.h" +#include "Templates/IdentityFunctor.h" +#include "Templates/Invoke.h" +#include "Templates/Less.h" +#include "Templates/UnrealTemplate.h" // For GetData, GetNum, Swap + + +namespace AlgoImpl +{ + template + void Merge(T* First, int32 Mid, int32 Num, ProjectionType Projection, PredicateType Predicate) + { + int32 AStart = 0; + int32 BStart = Mid; + + while (AStart < BStart && BStart < Num) + { + int32 NewAOffset = AlgoImpl::UpperBoundInternal(First + AStart, BStart - AStart, Invoke(Projection, First[BStart]), Projection, Predicate); + AStart += NewAOffset; + + if (AStart >= BStart) + { + return; + } + + int32 NewBOffset = AlgoImpl::LowerBoundInternal(First + BStart, Num - BStart, Invoke(Projection, First[AStart]), Projection, Predicate); + AlgoImpl::RotateInternal(First + AStart, NewBOffset + BStart - AStart, BStart - AStart); + BStart += NewBOffset; + AStart += NewBOffset + 1; + } + } + + constexpr int32 MinMergeSubgroupSize = 2; + + /** + * Sort elements using user defined projection and predicate classes. The sort is stable, meaning that the ordering of equal items is preserved. + * This is the internal sorting function used by the Algo::Sort overloads. + * + * @param First Pointer to the first element to sort. + * @param Num The number of items to sort. + * @param Projection A projection to apply to each element to get the value to sort by. + * @param Predicate A predicate class which compares two projected elements and returns whether one occurs before the other. + */ + template + void StableSortInternal(T* First, int32 Num, ProjectionType Projection, PredicateType Predicate) + { + int32 SubgroupStart = 0; + + if (MinMergeSubgroupSize > 1) + { + if (MinMergeSubgroupSize > 2) + { + // First pass with simple bubble-sort. + do + { + int32 GroupEnd = SubgroupStart + MinMergeSubgroupSize; + if (Num < GroupEnd) + { + GroupEnd = Num; + } + do + { + for (int32 It = SubgroupStart; It < GroupEnd - 1; ++It) + { + if (Invoke(Predicate, Invoke(Projection, First[It + 1]), Invoke(Projection, First[It]))) + { + Swap(First[It], First[It + 1]); + } + } + GroupEnd--; + } + while (GroupEnd - SubgroupStart > 1); + + SubgroupStart += MinMergeSubgroupSize; + } + while (SubgroupStart < Num); + } + else + { + for (int32 Subgroup = 0; Subgroup < Num; Subgroup += 2) + { + if (Subgroup + 1 < Num && Invoke(Predicate, Invoke(Projection, First[Subgroup + 1]), Invoke(Projection, First[Subgroup]))) + { + Swap(First[Subgroup], First[Subgroup + 1]); + } + } + } + } + + int32 SubgroupSize = MinMergeSubgroupSize; + while (SubgroupSize < Num) + { + SubgroupStart = 0; + do + { + int32 MergeNum = SubgroupSize << 1; + if (Num - SubgroupStart < MergeNum) + { + MergeNum = Num - SubgroupStart; + } + + Merge(First + SubgroupStart, SubgroupSize, MergeNum, Projection, Predicate); + SubgroupStart += SubgroupSize << 1; + } + while (SubgroupStart < Num); + + SubgroupSize <<= 1; + } + } +} + +namespace Algo +{ + /** + * Sort a range of elements using its operator<. The sort is stable. + * + * @param Range The range to sort. + */ + template + FORCEINLINE void StableSort(RangeType& Range) + { + AlgoImpl::StableSortInternal(GetData(Range), GetNum(Range), FIdentityFunctor(), TLess<>()); + } + + /** + * Sort a range of elements using a user-defined predicate class. The sort is stable. + * + * @param Range The range to sort. + * @param Predicate A binary predicate object used to specify if one element should precede another. + */ + template + FORCEINLINE void StableSort(RangeType& Range, PredicateType Pred) + { + AlgoImpl::StableSortInternal(GetData(Range), GetNum(Range), FIdentityFunctor(), MoveTemp(Pred)); + } + + /** + * Sort a range of elements by a projection using the projection's operator<. The sort is stable. + * + * @param Range The range to sort. + * @param Proj The projection to sort by when applied to the element. + */ + template + FORCEINLINE void StableSortBy(RangeType& Range, ProjectionType Proj) + { + AlgoImpl::StableSortInternal(GetData(Range), GetNum(Range), MoveTemp(Proj), TLess<>()); + } + + /** + * Sort a range of elements by a projection using a user-defined predicate class. The sort is stable. + * + * @param Range The range to sort. + * @param Proj The projection to sort by when applied to the element. + * @param Predicate A binary predicate object, applied to the projection, used to specify if one element should precede another. + */ + template + FORCEINLINE void StableSortBy(RangeType& Range, ProjectionType Proj, PredicateType Pred) + { + AlgoImpl::StableSortInternal(GetData(Range), GetNum(Range), MoveTemp(Proj), MoveTemp(Pred)); + } +} diff --git a/Engine/Source/Runtime/Core/Public/Android/AndroidPlatform.h b/Engine/Source/Runtime/Core/Public/Android/AndroidPlatform.h index 8ab48e8e1af1..65c22a473390 100644 --- a/Engine/Source/Runtime/Core/Public/Android/AndroidPlatform.h +++ b/Engine/Source/Runtime/Core/Public/Android/AndroidPlatform.h @@ -126,6 +126,12 @@ typedef FAndroidTypes FPlatformTypes; #define ABSTRACT abstract +// DLL export and import for types, only supported on clang +#if (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 8)) +#define DLLEXPORT_VTABLE __attribute__ ((__type_visibility__("default"))) +#define DLLIMPORT_VTABLE __attribute__ ((__type_visibility__("default"))) +#endif + // DLL export and import definitions #define DLLEXPORT __attribute__((visibility("default"))) #define DLLIMPORT __attribute__((visibility("default"))) diff --git a/Engine/Source/Runtime/Core/Public/Android/AndroidPlatformString.h b/Engine/Source/Runtime/Core/Public/Android/AndroidPlatformString.h index ba4d644d70e3..736ba5fe9fea 100644 --- a/Engine/Source/Runtime/Core/Public/Android/AndroidPlatformString.h +++ b/Engine/Source/Runtime/Core/Public/Android/AndroidPlatformString.h @@ -582,3 +582,60 @@ struct FAndroidPlatformString : public FGenericPlatformString typedef FAndroidPlatformString FPlatformString; +// Format specifiers to be able to print values of these types correctly, for example when using UE_LOG. +// SIZE_T format specifier +#if PLATFORM_64BITS +#define SIZE_T_FMT "llu" +// SIZE_T format specifier for lowercase hexadecimal output +#define SIZE_T_x_FMT "llx" +// SIZE_T format specifier for uppercase hexadecimal output +#define SIZE_T_X_FMT "llX" + +// SSIZE_T format specifier +#define SSIZE_T_FMT "lld" +// SSIZE_T format specifier for lowercase hexadecimal output +#define SSIZE_T_x_FMT "llx" +// SSIZE_T format specifier for uppercase hexadecimal output +#define SSIZE_T_X_FMT "llX" +#else +#define SIZE_T_FMT "u" +// SIZE_T format specifier for lowercase hexadecimal output +#define SIZE_T_x_FMT "x" +// SIZE_T format specifier for uppercase hexadecimal output +#define SIZE_T_X_FMT "X" + +// SSIZE_T format specifier +#define SSIZE_T_FMT "d" +// SSIZE_T format specifier for lowercase hexadecimal output +#define SSIZE_T_x_FMT "x" +// SSIZE_T format specifier for uppercase hexadecimal output +#define SSIZE_T_X_FMT "X" +#endif + +// PTRINT format specifier for decimal output +#define PTRINT_FMT SSIZE_T_FMT +// PTRINT format specifier for lowercase hexadecimal output +#define PTRINT_x_FMT SSIZE_T_x_FMT +// PTRINT format specifier for uppercase hexadecimal output +#define PTRINT_X_FMT SSIZE_T_X_FMT + +// UPTRINT format specifier for decimal output +#define UPTRINT_FMT SIZE_T_FMT +// UPTRINT format specifier for lowercase hexadecimal output +#define UPTRINT_x_FMT SIZE_T_x_FMT +// UPTRINT format specifier for uppercase hexadecimal output +#define UPTRINT_X_FMT SIZE_T_X_FMT + +// int64 format specifier for decimal output +#define INT64_FMT "lld" +// int64 format specifier for lowercase hexadecimal output +#define INT64_x_FMT "llx" +// int64 format specifier for uppercase hexadecimal output +#define INT64_X_FMT "llX" + +// uint64 format specifier for decimal output +#define UINT64_FMT "llu" +// uint64 format specifier for lowercase hexadecimal output +#define UINT64_x_FMT "llx" +// uint64 format specifier for uppercase hexadecimal output +#define UINT64_X_FMT "llX" diff --git a/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformCompilerPreSetup.h b/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformCompilerPreSetup.h index da0227d9a813..7af4b37f98b7 100644 --- a/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformCompilerPreSetup.h +++ b/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformCompilerPreSetup.h @@ -79,3 +79,6 @@ #define PRAGMA_DISABLE_OPTIMIZATION_ACTUAL _Pragma("clang optimize off") #define PRAGMA_ENABLE_OPTIMIZATION_ACTUAL _Pragma("clang optimize on") #endif + +#define PRAGMA_DEFAULT_VISIBILITY_START _Pragma("GCC visibility push(default)") +#define PRAGMA_DEFAULT_VISIBILITY_END _Pragma("GCC visibility pop") diff --git a/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformMemory.h b/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformMemory.h index bce0a25246dc..74a67634306f 100644 --- a/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformMemory.h +++ b/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformMemory.h @@ -17,7 +17,7 @@ NS_ASSUME_NONNULL_BEGIN * This ensures that memory allocated by custom Objective-C types can be tracked by UE4's tools and * that we benefit from the memory allocator's efficiencies. */ -@interface FApplePlatformObject : NSObject +OBJC_EXPORT @interface FApplePlatformObject : NSObject { @private OSQueueHead* AllocatorPtr; diff --git a/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformString.h b/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformString.h index 06021aa211a3..f86c89f6cd7c 100644 --- a/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformString.h +++ b/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformString.h @@ -100,3 +100,46 @@ struct FApplePlatformString }; typedef FApplePlatformString FPlatformString; + +// Format specifiers to be able to print values of these types correctly, for example when using UE_LOG. +// SIZE_T format specifier +#define SIZE_T_FMT "zu" +// SIZE_T format specifier for lowercase hexadecimal output +#define SIZE_T_x_FMT "zx" +// SIZE_T format specifier for uppercase hexadecimal output +#define SIZE_T_X_FMT "zX" + +// SSIZE_T format specifier +#define SSIZE_T_FMT "lld" +// SSIZE_T format specifier for lowercase hexadecimal output +#define SSIZE_T_x_FMT "llx" +// SSIZE_T format specifier for uppercase hexadecimal output +#define SSIZE_T_X_FMT "llX" + +// PTRINT format specifier for decimal output +#define PTRINT_FMT SSIZE_T_FMT +// PTRINT format specifier for lowercase hexadecimal output +#define PTRINT_x_FMT SSIZE_T_x_FMT +// PTRINT format specifier for uppercase hexadecimal output +#define PTRINT_X_FMT SSIZE_T_X_FMT + +// UPTRINT format specifier for decimal output +#define UPTRINT_FMT "llu" +// UPTRINT format specifier for lowercase hexadecimal output +#define UPTRINT_x_FMT "llx" +// UPTRINT format specifier for uppercase hexadecimal output +#define UPTRINT_X_FMT "llX" + +// int64 format specifier for decimal output +#define INT64_FMT SSIZE_T_FMT +// int64 format specifier for lowercase hexadecimal output +#define INT64_x_FMT SSIZE_T_x_FMT +// int64 format specifier for uppercase hexadecimal output +#define INT64_X_FMT SSIZE_T_X_FMT + +// uint64 format specifier for decimal output +#define UINT64_FMT "llu" +// uint64 format specifier for lowercase hexadecimal output +#define UINT64_x_FMT "llx" +// uint64 format specifier for uppercase hexadecimal output +#define UINT64_X_FMT "llX" diff --git a/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformSymbolication.h b/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformSymbolication.h index 7a8a55d13301..5a7258a1963a 100644 --- a/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformSymbolication.h +++ b/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformSymbolication.h @@ -25,10 +25,10 @@ struct FApplePlatformSymbolCache */ struct FApplePlatformSymbolDatabase { - FApplePlatformSymbolDatabase(); - FApplePlatformSymbolDatabase(FApplePlatformSymbolDatabase const& Other); - ~FApplePlatformSymbolDatabase(); - FApplePlatformSymbolDatabase& operator=(FApplePlatformSymbolDatabase const& Other); + CORE_API FApplePlatformSymbolDatabase(); + CORE_API FApplePlatformSymbolDatabase(FApplePlatformSymbolDatabase const& Other); + CORE_API ~FApplePlatformSymbolDatabase(); + CORE_API FApplePlatformSymbolDatabase& operator=(FApplePlatformSymbolDatabase const& Other); TSharedPtr GenericDB; FApplePlatformSymbolCache AppleDB; diff --git a/Engine/Source/Runtime/Core/Public/Clang/ClangPlatformAtomics.h b/Engine/Source/Runtime/Core/Public/Clang/ClangPlatformAtomics.h index f1861ac8aab3..cdaf68ec5d9e 100644 --- a/Engine/Source/Runtime/Core/Public/Clang/ClangPlatformAtomics.h +++ b/Engine/Source/Runtime/Core/Public/Clang/ClangPlatformAtomics.h @@ -94,7 +94,7 @@ struct CORE_API FClangPlatformAtomics : public FGenericPlatformAtomics return __sync_lock_test_and_set(Value, Exchange); } - static FORCEINLINE void* InterlockedExchangePtr(void** Dest, void* Exchange) + static FORCEINLINE void* InterlockedExchangePtr(void*volatile* Dest, void* Exchange) { return __sync_lock_test_and_set(Dest, Exchange); } @@ -119,6 +119,66 @@ struct CORE_API FClangPlatformAtomics : public FGenericPlatformAtomics return __sync_val_compare_and_swap(Dest, Comperand, Exchange); } + static FORCEINLINE int8 InterlockedAnd(volatile int8* Value, const int8 AndValue) + { + return __sync_fetch_and_and(Value, AndValue); + } + + static FORCEINLINE int16 InterlockedAnd(volatile int16* Value, const int16 AndValue) + { + return __sync_fetch_and_and(Value, AndValue); + } + + static FORCEINLINE int32 InterlockedAnd(volatile int32* Value, const int32 AndValue) + { + return __sync_fetch_and_and(Value, AndValue); + } + + static FORCEINLINE int64 InterlockedAnd(volatile int64* Value, const int64 AndValue) + { + return __sync_fetch_and_and(Value, AndValue); + } + + static FORCEINLINE int8 InterlockedOr(volatile int8* Value, const int8 OrValue) + { + return __sync_fetch_and_or(Value, OrValue); + } + + static FORCEINLINE int16 InterlockedOr(volatile int16* Value, const int16 OrValue) + { + return __sync_fetch_and_or(Value, OrValue); + } + + static FORCEINLINE int32 InterlockedOr(volatile int32* Value, const int32 OrValue) + { + return __sync_fetch_and_or(Value, OrValue); + } + + static FORCEINLINE int64 InterlockedOr(volatile int64* Value, const int64 OrValue) + { + return __sync_fetch_and_or(Value, OrValue); + } + + static FORCEINLINE int8 InterlockedXor(volatile int8* Value, const int8 XorValue) + { + return __sync_fetch_and_xor(Value, XorValue); + } + + static FORCEINLINE int16 InterlockedXor(volatile int16* Value, const int16 XorValue) + { + return __sync_fetch_and_xor(Value, XorValue); + } + + static FORCEINLINE int32 InterlockedXor(volatile int32* Value, const int32 XorValue) + { + return __sync_fetch_and_xor(Value, XorValue); + } + + static FORCEINLINE int64 InterlockedXor(volatile int64* Value, const int64 XorValue) + { + return __sync_fetch_and_xor(Value, XorValue); + } + static FORCEINLINE int8 AtomicRead(volatile const int8* Src) { int8 Result; @@ -221,7 +281,7 @@ struct CORE_API FClangPlatformAtomics : public FGenericPlatformAtomics return InterlockedCompareExchange((volatile int64*)Src, 0, 0); } - static FORCEINLINE void* InterlockedCompareExchangePointer(void** Dest, void* Exchange, void* Comperand) + static FORCEINLINE void* InterlockedCompareExchangePointer(void*volatile* Dest, void* Exchange, void* Comperand) { return __sync_val_compare_and_swap(Dest, Comperand, Exchange); } diff --git a/Engine/Source/Runtime/Core/Public/Concepts/EqualityComparable.h b/Engine/Source/Runtime/Core/Public/Concepts/EqualityComparable.h new file mode 100644 index 000000000000..ddc085d985bd --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/Concepts/EqualityComparable.h @@ -0,0 +1,26 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +/** + * Describes a type comparable with another type. + */ +struct CEqualityComparableWith { + template + auto Requires(bool& Result, const T& A, const U& B) -> decltype( + Result = A == B, + Result = B == A, + Result = A != B, + Result = B != A + ); +}; + +/** + * Describes a type comparable with itself. + */ +struct CEqualityComparable { + template + auto Requires() -> decltype( + Refines() + ); +}; diff --git a/Engine/Source/Runtime/Core/Public/Concepts/GetTypeHashable.h b/Engine/Source/Runtime/Core/Public/Concepts/GetTypeHashable.h new file mode 100644 index 000000000000..1bfd65576864 --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/Concepts/GetTypeHashable.h @@ -0,0 +1,16 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "Templates/TypeHash.h" + +/** + * Describes a type with a GetTypeHash overload. + */ +struct CGetTypeHashable { + template + auto Requires(uint32& Result, const T& Val) -> decltype( + Result = GetTypeHash(Val) + ); +}; diff --git a/Engine/Source/Runtime/Core/Public/Concepts/Insertable.h b/Engine/Source/Runtime/Core/Public/Concepts/Insertable.h new file mode 100644 index 000000000000..bb3e19658459 --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/Concepts/Insertable.h @@ -0,0 +1,14 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +/** + * Describes an insertion operation for a destination type where an instance of another type can be inserted via operator<<. + */ +template +struct CInsertable { + template + auto Requires(DestType Dest, T& Val) -> decltype( + Dest << Val + ); +}; diff --git a/Engine/Source/Runtime/Core/Public/Containers/Array.h b/Engine/Source/Runtime/Core/Public/Containers/Array.h index a517c15b0202..93d0d1a0ea17 100644 --- a/Engine/Source/Runtime/Core/Public/Containers/Array.h +++ b/Engine/Source/Runtime/Core/Public/Containers/Array.h @@ -6,6 +6,7 @@ #include "Misc/AssertionMacros.h" #include "HAL/UnrealMemory.h" #include "Templates/AreTypesEqual.h" +#include "Templates/IsSigned.h" #include "Templates/UnrealTypeTraits.h" #include "Templates/UnrealTemplate.h" #include "Containers/ContainerAllocationPolicies.h" @@ -30,16 +31,16 @@ /** * Generic iterator which can operate on types that expose the following: * - A type called ElementType representing the contained type. - * - A method IndexType Num() const that returns the number of items in the container. - * - A method bool IsValidIndex(IndexType index) which returns whether a given index is valid in the container. - * - A method T& operator\[\](IndexType index) which returns a reference to a contained object by index. - * - A method void RemoveAt(IndexType index) which removes the element at index + * - A method SizeType Num() const that returns the number of items in the container. + * - A method bool IsValidIndex(SizeType index) which returns whether a given index is valid in the container. + * - A method T& operator\[\](SizeType index) which returns a reference to a contained object by index. + * - A method void RemoveAt(SizeType index) which removes the element at index */ -template< typename ContainerType, typename ElementType, typename IndexType> +template< typename ContainerType, typename ElementType, typename SizeType> class TIndexedContainerIterator { public: - TIndexedContainerIterator(ContainerType& InContainer, IndexType StartIndex = 0) + TIndexedContainerIterator(ContainerType& InContainer, SizeType StartIndex = 0) : Container(InContainer) , Index (StartIndex) { @@ -72,24 +73,24 @@ public: } /** iterator arithmetic support */ - TIndexedContainerIterator& operator+=(int32 Offset) + TIndexedContainerIterator& operator+=(SizeType Offset) { Index += Offset; return *this; } - TIndexedContainerIterator operator+(int32 Offset) const + TIndexedContainerIterator operator+(SizeType Offset) const { TIndexedContainerIterator Tmp(*this); return Tmp += Offset; } - TIndexedContainerIterator& operator-=(int32 Offset) + TIndexedContainerIterator& operator-=(SizeType Offset) { return *this += -Offset; } - TIndexedContainerIterator operator-(int32 Offset) const + TIndexedContainerIterator operator-(SizeType Offset) const { TIndexedContainerIterator Tmp(*this); return Tmp -= Offset; @@ -112,7 +113,7 @@ public: } /** Returns an index to the current element. */ - IndexType GetIndex() const + SizeType GetIndex() const { return Index; } @@ -142,13 +143,13 @@ public: private: ContainerType& Container; - IndexType Index; + SizeType Index; }; /** operator + */ -template -FORCEINLINE TIndexedContainerIterator operator+(int32 Offset, TIndexedContainerIterator RHS) +template +FORCEINLINE TIndexedContainerIterator operator+(SizeType Offset, TIndexedContainerIterator RHS) { return RHS + Offset; } @@ -159,7 +160,7 @@ FORCEINLINE TIndexedContainerIterator ope * Pointer-like iterator type for ranged-for loops which checks that the * container hasn't been resized during iteration. */ - template + template struct TCheckedPointerIterator { // This iterator type only supports the minimal functionality needed to support @@ -167,7 +168,7 @@ FORCEINLINE TIndexedContainerIterator ope // // We do add an operator-- to help FString implementation - explicit TCheckedPointerIterator(const int32& InNum, ElementType* InPtr) + explicit TCheckedPointerIterator(const SizeType& InNum, ElementType* InPtr) : Ptr (InPtr) , CurrentNum(InNum) , InitialNum(InNum) @@ -192,9 +193,9 @@ FORCEINLINE TIndexedContainerIterator ope } private: - ElementType* Ptr; - const int32& CurrentNum; - int32 InitialNum; + ElementType* Ptr; + const SizeType& CurrentNum; + SizeType InitialNum; FORCEINLINE friend bool operator!=(const TCheckedPointerIterator& Lhs, const TCheckedPointerIterator& Rhs) { @@ -280,10 +281,12 @@ class TArray friend class TArray; public: - + typedef typename InAllocator::SizeType SizeType; typedef InElementType ElementType; typedef InAllocator Allocator; + static_assert(TIsSigned::Value, "TArray only supports signed index types"); + /** * Constructor, initializes element number counters. */ @@ -299,7 +302,7 @@ public: * @param Count The number of elements to copy from Ptr. * @see Append */ - FORCEINLINE TArray(const ElementType* Ptr, int32 Count) + FORCEINLINE TArray(const ElementType* Ptr, SizeType Count) { check(Ptr != nullptr || Count == 0); @@ -314,7 +317,7 @@ public: // This is not strictly legal, as std::initializer_list's iterators are not guaranteed to be pointers, but // this appears to be the case on all of our implementations. Also, if it's not true on a new implementation, // it will fail to compile rather than behave badly. - CopyToEmpty(InitList.begin(), (int32)InitList.size(), 0, 0); + CopyToEmpty(InitList.begin(), (SizeType)InitList.size(), 0, 0); } /** @@ -345,7 +348,7 @@ public: * @param ExtraSlack Tells how much extra memory should be preallocated * at the end of the array in the number of elements. */ - FORCEINLINE TArray(const TArray& Other, int32 ExtraSlack) + FORCEINLINE TArray(const TArray& Other, SizeType ExtraSlack) { CopyToEmpty(Other.GetData(), Other.Num(), 0, ExtraSlack); } @@ -362,7 +365,7 @@ public: // This is not strictly legal, as std::initializer_list's iterators are not guaranteed to be pointers, but // this appears to be the case on all of our implementations. Also, if it's not true on a new implementation, // it will fail to compile rather than behave badly. - CopyToEmpty(InitList.begin(), (int32)InitList.size(), ArrayMax, 0); + CopyToEmpty(InitList.begin(), (SizeType)InitList.size(), ArrayMax, 0); return *this; } @@ -409,7 +412,7 @@ private: * @param FromArray Array to move from. */ template - static FORCEINLINE typename TEnableIf::Value>::Type MoveOrCopy(ToArrayType& ToArray, FromArrayType& FromArray, int32 PrevMax) + static FORCEINLINE typename TEnableIf::Value>::Type MoveOrCopy(ToArrayType& ToArray, FromArrayType& FromArray, SizeType PrevMax) { ToArray.AllocatorInstance.MoveToEmpty(FromArray.AllocatorInstance); @@ -430,7 +433,7 @@ private: * at the end of the array in the number of elements. */ template - static FORCEINLINE typename TEnableIf::Value>::Type MoveOrCopy(ToArrayType& ToArray, FromArrayType& FromArray, int32 PrevMax) + static FORCEINLINE typename TEnableIf::Value>::Type MoveOrCopy(ToArrayType& ToArray, FromArrayType& FromArray, SizeType PrevMax) { ToArray.CopyToEmpty(FromArray.GetData(), FromArray.Num(), PrevMax, 0); } @@ -446,7 +449,7 @@ private: * at the end of the array in the number of elements. */ template - static FORCEINLINE typename TEnableIf::Value>::Type MoveOrCopyWithSlack(ToArrayType& ToArray, FromArrayType& FromArray, int32 PrevMax, int32 ExtraSlack) + static FORCEINLINE typename TEnableIf::Value>::Type MoveOrCopyWithSlack(ToArrayType& ToArray, FromArrayType& FromArray, SizeType PrevMax, SizeType ExtraSlack) { MoveOrCopy(ToArray, FromArray, PrevMax); @@ -464,7 +467,7 @@ private: * at the end of the array in the number of elements. */ template - static FORCEINLINE typename TEnableIf::Value>::Type MoveOrCopyWithSlack(ToArrayType& ToArray, FromArrayType& FromArray, int32 PrevMax, int32 ExtraSlack) + static FORCEINLINE typename TEnableIf::Value>::Type MoveOrCopyWithSlack(ToArrayType& ToArray, FromArrayType& FromArray, SizeType PrevMax, SizeType ExtraSlack) { ToArray.CopyToEmpty(FromArray.GetData(), FromArray.Num(), PrevMax, ExtraSlack); } @@ -499,7 +502,7 @@ public: * at the end of the array in the number of elements. */ template - TArray(TArray&& Other, int32 ExtraSlack) + TArray(TArray&& Other, SizeType ExtraSlack) { // We don't implement move semantics for general OtherAllocators, as there's no way // to tell if they're compatible with the current one. Probably going to be a pretty @@ -572,7 +575,7 @@ public: * * @returns Number of bytes allocated by this container. */ - FORCEINLINE uint32 GetAllocatedSize(void) const + FORCEINLINE SIZE_T GetAllocatedSize(void) const { return AllocatorInstance.GetAllocatedSize(ArrayMax, sizeof(ElementType)); } @@ -582,7 +585,7 @@ public: * * @see Num, Shrink */ - FORCEINLINE int32 GetSlack() const + FORCEINLINE SizeType GetSlack() const { return ArrayMax - ArrayNum; } @@ -601,7 +604,7 @@ public: * * @param Index Index to check. */ - FORCEINLINE void RangeCheck(int32 Index) const + FORCEINLINE void RangeCheck(SizeType Index) const { CheckInvariants(); @@ -618,7 +621,7 @@ public: * @param Index Index to test. * @returns True if index is valid. False otherwise. */ - FORCEINLINE bool IsValidIndex(int32 Index) const + FORCEINLINE bool IsValidIndex(SizeType Index) const { return Index >= 0 && Index < ArrayNum; } @@ -629,7 +632,7 @@ public: * @returns Number of elements in array. * @see GetSlack */ - FORCEINLINE int32 Num() const + FORCEINLINE SizeType Num() const { return ArrayNum; } @@ -640,7 +643,7 @@ public: * @returns Maximum number of elements in array. * @see GetSlack */ - FORCEINLINE int32 Max() const + FORCEINLINE SizeType Max() const { return ArrayMax; } @@ -650,7 +653,7 @@ public: * * @returns Reference to indexed element. */ - FORCEINLINE ElementType& operator[](int32 Index) + FORCEINLINE ElementType& operator[](SizeType Index) { RangeCheck(Index); return GetData()[Index]; @@ -663,7 +666,7 @@ public: * * @returns Reference to indexed element. */ - FORCEINLINE const ElementType& operator[](int32 Index) const + FORCEINLINE const ElementType& operator[](SizeType Index) const { RangeCheck(Index); return GetData()[Index]; @@ -736,7 +739,7 @@ public: * @param IndexFromTheEnd (Optional) Index from the end of array (default = 0). * @returns Reference to n-th last element from the array. */ - FORCEINLINE ElementType& Last(int32 IndexFromTheEnd = 0) + FORCEINLINE ElementType& Last(SizeType IndexFromTheEnd = 0) { RangeCheck(ArrayNum - IndexFromTheEnd - 1); return GetData()[ArrayNum - IndexFromTheEnd - 1]; @@ -750,7 +753,7 @@ public: * @param IndexFromTheEnd (Optional) Index from the end of array (default = 0). * @returns Reference to n-th last element from the array. */ - FORCEINLINE const ElementType& Last(int32 IndexFromTheEnd = 0) const + FORCEINLINE const ElementType& Last(SizeType IndexFromTheEnd = 0) const { RangeCheck(ArrayNum - IndexFromTheEnd - 1); return GetData()[ArrayNum - IndexFromTheEnd - 1]; @@ -778,7 +781,7 @@ public: * @returns True if found. False otherwise. * @see FindLast, FindLastByPredicate */ - FORCEINLINE bool Find(const ElementType& Item, int32& Index) const + FORCEINLINE bool Find(const ElementType& Item, SizeType& Index) const { Index = this->Find(Item); return Index != INDEX_NONE; @@ -791,14 +794,14 @@ public: * @returns Index of the found element. INDEX_NONE otherwise. * @see FindLast, FindLastByPredicate */ - int32 Find(const ElementType& Item) const + SizeType Find(const ElementType& Item) const { const ElementType* RESTRICT Start = GetData(); for (const ElementType* RESTRICT Data = Start, *RESTRICT DataEnd = Data + ArrayNum; Data != DataEnd; ++Data) { if (*Data == Item) { - return static_cast(Data - Start); + return static_cast(Data - Start); } } return INDEX_NONE; @@ -812,7 +815,7 @@ public: * @returns True if found. False otherwise. * @see Find, FindLastByPredicate */ - FORCEINLINE bool FindLast(const ElementType& Item, int32& Index) const + FORCEINLINE bool FindLast(const ElementType& Item, SizeType& Index) const { Index = this->FindLast(Item); return Index != INDEX_NONE; @@ -824,14 +827,14 @@ public: * @param Item Item to look for. * @returns Index of the found element. INDEX_NONE otherwise. */ - int32 FindLast(const ElementType& Item) const + SizeType FindLast(const ElementType& Item) const { for (const ElementType* RESTRICT Start = GetData(), *RESTRICT Data = Start + ArrayNum; Data != Start; ) { --Data; if (*Data == Item) { - return static_cast(Data - Start); + return static_cast(Data - Start); } } return INDEX_NONE; @@ -845,7 +848,7 @@ public: * @returns Index of the found element. INDEX_NONE otherwise. */ template - int32 FindLastByPredicate(Predicate Pred, int32 Count) const + SizeType FindLastByPredicate(Predicate Pred, SizeType Count) const { check(Count >= 0 && Count <= this->Num()); for (const ElementType* RESTRICT Start = GetData(), *RESTRICT Data = Start + Count; Data != Start; ) @@ -853,7 +856,7 @@ public: --Data; if (Pred(*Data)) { - return static_cast(Data - Start); + return static_cast(Data - Start); } } return INDEX_NONE; @@ -866,7 +869,7 @@ public: * @returns Index of the found element. INDEX_NONE otherwise. */ template - FORCEINLINE int32 FindLastByPredicate(Predicate Pred) const + FORCEINLINE SizeType FindLastByPredicate(Predicate Pred) const { return FindLastByPredicate(Pred, ArrayNum); } @@ -879,14 +882,14 @@ public: * @returns Index to the first matching element, or INDEX_NONE if none is found. */ template - int32 IndexOfByKey(const KeyType& Key) const + SizeType IndexOfByKey(const KeyType& Key) const { const ElementType* RESTRICT Start = GetData(); for (const ElementType* RESTRICT Data = Start, *RESTRICT DataEnd = Start + ArrayNum; Data != DataEnd; ++Data) { if (*Data == Key) { - return static_cast(Data - Start); + return static_cast(Data - Start); } } return INDEX_NONE; @@ -899,14 +902,14 @@ public: * @returns Index to the first matching element, or INDEX_NONE if none is found. */ template - int32 IndexOfByPredicate(Predicate Pred) const + SizeType IndexOfByPredicate(Predicate Pred) const { const ElementType* RESTRICT Start = GetData(); for (const ElementType* RESTRICT Data = Start, *RESTRICT DataEnd = Start + ArrayNum; Data != DataEnd; ++Data) { if (Pred(*Data)) { - return static_cast(Data - Start); + return static_cast(Data - Start); } } return INDEX_NONE; @@ -1043,7 +1046,7 @@ public: */ bool operator==(const TArray& OtherArray) const { - int32 Count = Num(); + SizeType Count = Num(); return Count == OtherArray.Num() && CompareItems(GetData(), OtherArray.GetData(), Count); } @@ -1071,8 +1074,8 @@ public: A.CountBytes(Ar); // For net archives, limit serialization to 16MB, to protect against excessive allocation - const int32 MaxNetArraySerialize = (16 * 1024 * 1024) / sizeof(ElementType); - int32 SerializeNum = (Ar.IsLoading() ? 0 : A.ArrayNum); + constexpr SizeType MaxNetArraySerialize = (16 * 1024 * 1024) / sizeof(ElementType); + SizeType SerializeNum = Ar.IsLoading() ? 0 : A.ArrayNum; Ar << SerializeNum; @@ -1097,7 +1100,7 @@ public: // Required for resetting ArrayNum A.Empty(SerializeNum); - for (int32 i=0; i= 0); - const int32 OldNum = ArrayNum; + const SizeType OldNum = ArrayNum; if ((ArrayNum += Count) > ArrayMax) { ResizeGrow(OldNum); @@ -1218,6 +1221,26 @@ public: return OldNum; } +private: + template + void InsertUninitializedImpl(SizeType Index, OtherSizeType Count) + { + CheckInvariants(); + checkSlow((Count >= 0) & (Index >= 0) & (Index <= ArrayNum)); + + SizeType NewNum = Count; + checkf((OtherSizeType)NewNum == Count, TEXT("Invalid number of elements to add to this array type: %llu"), (unsigned long long)NewNum); + + const SizeType OldNum = ArrayNum; + if ((ArrayNum += Count) > ArrayMax) + { + ResizeGrow(OldNum); + } + ElementType* Data = GetData() + Index; + RelocateConstructItems(Data + Count, Data, OldNum - Index); + } + +public: /** * Inserts a given number of uninitialized elements into the array at given * location. @@ -1230,18 +1253,9 @@ public: * @param Count Number of elements to add. * @see Insert, InsertZeroed, InsertDefaulted */ - void InsertUninitialized(int32 Index, int32 Count = 1) + FORCEINLINE void InsertUninitialized(SizeType Index, SizeType Count = 1) { - CheckInvariants(); - checkSlow((Count >= 0) & (Index >= 0) & (Index <= ArrayNum)); - - const int32 OldNum = ArrayNum; - if ((ArrayNum += Count) > ArrayMax) - { - ResizeGrow(OldNum); - } - ElementType* Data = GetData() + Index; - RelocateConstructItems(Data + Count, Data, OldNum - Index); + InsertUninitializedImpl(Index, Count); } /** @@ -1256,9 +1270,9 @@ public: * @param Count Number of elements to add. * @see Insert, InsertUninitialized, InsertDefaulted */ - void InsertZeroed(int32 Index, int32 Count = 1) + void InsertZeroed(SizeType Index, SizeType Count = 1) { - InsertUninitialized(Index, Count); + InsertUninitializedImpl(Index, Count); FMemory::Memzero(GetData() + Index, Count * sizeof(ElementType)); } @@ -1273,9 +1287,9 @@ public: * @return A reference to the newly-inserted element. * @see Insert_GetRef, InsertDefaulted_GetRef */ - ElementType& InsertZeroed_GetRef(int32 Index) + ElementType& InsertZeroed_GetRef(SizeType Index) { - InsertUninitialized(Index, 1); + InsertUninitializedImpl(Index, 1); ElementType* Ptr = GetData() + Index; FMemory::Memzero(Ptr, sizeof(ElementType)); return *Ptr; @@ -1289,9 +1303,9 @@ public: * @param Count Number of elements to add. * @see Insert, InsertUninitialized, InsertZeroed */ - void InsertDefaulted(int32 Index, int32 Count = 1) + void InsertDefaulted(SizeType Index, SizeType Count = 1) { - InsertUninitialized(Index, Count); + InsertUninitializedImpl(Index, Count); DefaultConstructItems(GetData() + Index, Count); } @@ -1303,9 +1317,9 @@ public: * @return A reference to the newly-inserted element. * @see Insert_GetRef, InsertZeroed_GetRef */ - ElementType& InsertDefaulted_GetRef(int32 Index) + ElementType& InsertDefaulted_GetRef(SizeType Index) { - InsertUninitialized(Index, 1); + InsertUninitializedImpl(Index, 1); ElementType* Ptr = GetData() + Index; DefaultConstructItems(Ptr, 1); return *Ptr; @@ -1318,11 +1332,11 @@ public: * @param InIndex Tells where to insert the new elements. * @returns Location at which the item was inserted. */ - int32 Insert(std::initializer_list InitList, const int32 InIndex) + SizeType Insert(std::initializer_list InitList, const SizeType InIndex) { - int32 NumNewElements = (int32)InitList.size(); + SizeType NumNewElements = (SizeType)InitList.size(); - InsertUninitialized(InIndex, NumNewElements); + InsertUninitializedImpl(InIndex, NumNewElements); ConstructItems(GetData() + InIndex, InitList.begin(), NumNewElements); return InIndex; @@ -1336,13 +1350,13 @@ public: * @returns Location at which the item was inserted. */ template - int32 Insert(const TArray& Items, const int32 InIndex) + SizeType Insert(const TArray& Items, const SizeType InIndex) { check((const void*)this != (const void*)&Items); - int32 NumNewElements = Items.Num(); + auto NumNewElements = Items.Num(); - InsertUninitialized(InIndex, NumNewElements); + InsertUninitializedImpl(InIndex, NumNewElements); ConstructItems(GetData() + InIndex, Items.GetData(), NumNewElements); return InIndex; @@ -1356,13 +1370,13 @@ public: * @returns Location at which the item was inserted. */ template - int32 Insert(TArray&& Items, const int32 InIndex) + SizeType Insert(TArray&& Items, const SizeType InIndex) { check((const void*)this != (const void*)&Items); - int32 NumNewElements = Items.Num(); + auto NumNewElements = Items.Num(); - InsertUninitialized(InIndex, NumNewElements); + InsertUninitializedImpl(InIndex, NumNewElements); RelocateConstructItems(GetData() + InIndex, Items.GetData(), NumNewElements); Items.ArrayNum = 0; @@ -1378,11 +1392,11 @@ public: * @return The index of the first element inserted. * @see Add, Remove */ - int32 Insert(const ElementType* Ptr, int32 Count, int32 Index) + SizeType Insert(const ElementType* Ptr, SizeType Count, SizeType Index) { check(Ptr != nullptr); - InsertUninitialized(Index, Count); + InsertUninitializedImpl(Index, Count); ConstructItems(GetData() + Index, Ptr, Count); return Index; @@ -1410,13 +1424,13 @@ public: * @returns Location at which the insert was done. * @see Add, Remove */ - int32 Insert(ElementType&& Item, int32 Index) + SizeType Insert(ElementType&& Item, SizeType Index) { CheckAddress(&Item); // construct a copy in place at Index (this new operator will insert at // Index, then construct that memory with Item) - InsertUninitialized(Index, 1); + InsertUninitializedImpl(Index, 1); new(GetData() + Index) ElementType(MoveTempIfPossible(Item)); return Index; } @@ -1429,13 +1443,13 @@ public: * @returns Location at which the insert was done. * @see Add, Remove */ - int32 Insert(const ElementType& Item, int32 Index) + SizeType Insert(const ElementType& Item, SizeType Index) { CheckAddress(&Item); // construct a copy in place at Index (this new operator will insert at // Index, then construct that memory with Item) - InsertUninitialized(Index, 1); + InsertUninitializedImpl(Index, 1); new(GetData() + Index) ElementType(Item); return Index; } @@ -1449,13 +1463,13 @@ public: * @return A reference to the newly-inserted element. * @see Add, Remove */ - ElementType& Insert_GetRef(ElementType&& Item, int32 Index) + ElementType& Insert_GetRef(ElementType&& Item, SizeType Index) { CheckAddress(&Item); // construct a copy in place at Index (this new operator will insert at // Index, then construct that memory with Item) - InsertUninitialized(Index, 1); + InsertUninitializedImpl(Index, 1); ElementType* Ptr = GetData() + Index; new(Ptr) ElementType(MoveTempIfPossible(Item)); return *Ptr; @@ -1469,20 +1483,20 @@ public: * @return A reference to the newly-inserted element. * @see Add, Remove */ - ElementType& Insert_GetRef(const ElementType& Item, int32 Index) + ElementType& Insert_GetRef(const ElementType& Item, SizeType Index) { CheckAddress(&Item); // construct a copy in place at Index (this new operator will insert at // Index, then construct that memory with Item) - InsertUninitialized(Index, 1); + InsertUninitializedImpl(Index, 1); ElementType* Ptr = GetData() + Index; new(Ptr) ElementType(Item); return *Ptr; } private: - void RemoveAtImpl(int32 Index, int32 Count, bool bAllowShrinking) + void RemoveAtImpl(SizeType Index, SizeType Count, bool bAllowShrinking) { if (Count) { @@ -1492,7 +1506,7 @@ private: DestructItems(GetData() + Index, Count); // Skip memmove in the common case that there is nothing to move. - int32 NumToMove = ArrayNum - Index - Count; + SizeType NumToMove = ArrayNum - Index - Count; if (NumToMove) { FMemory::Memmove @@ -1520,7 +1534,7 @@ public: * @param Count (Optional) Number of elements to remove. Default is 1. * @param bAllowShrinking (Optional) Tells if this call can shrink array if suitable after remove. Default is true. */ - FORCEINLINE void RemoveAt(int32 Index) + FORCEINLINE void RemoveAt(SizeType Index) { RemoveAtImpl(Index, 1, true); } @@ -1534,14 +1548,14 @@ public: * @param bAllowShrinking (Optional) Tells if this call can shrink array if suitable after remove. Default is true. */ template - FORCEINLINE void RemoveAt(int32 Index, CountType Count, bool bAllowShrinking = true) + FORCEINLINE void RemoveAt(SizeType Index, CountType Count, bool bAllowShrinking = true) { static_assert(!TAreTypesEqual::Value, "TArray::RemoveAt: unexpected bool passed as the Count argument"); RemoveAtImpl(Index, Count, bAllowShrinking); } private: - void RemoveAtSwapImpl(int32 Index, int32 Count = 1, bool bAllowShrinking = true) + void RemoveAtSwapImpl(SizeType Index, SizeType Count = 1, bool bAllowShrinking = true) { if (Count) { @@ -1551,9 +1565,9 @@ private: DestructItems(GetData() + Index, Count); // Replace the elements in the hole created by the removal with elements from the end of the array, so the range of indices used by the array is contiguous. - const int32 NumElementsInHole = Count; - const int32 NumElementsAfterHole = ArrayNum - (Index + Count); - const int32 NumElementsToMoveIntoHole = FPlatformMath::Min(NumElementsInHole, NumElementsAfterHole); + const SizeType NumElementsInHole = Count; + const SizeType NumElementsAfterHole = ArrayNum - (Index + Count); + const SizeType NumElementsToMoveIntoHole = FPlatformMath::Min(NumElementsInHole, NumElementsAfterHole); if (NumElementsToMoveIntoHole) { FMemory::Memcpy( @@ -1584,7 +1598,7 @@ public: * @param bAllowShrinking (Optional) Tells if this call can shrink array if * suitable after remove. Default is true. */ - FORCEINLINE void RemoveAtSwap(int32 Index) + FORCEINLINE void RemoveAtSwap(SizeType Index) { RemoveAtSwapImpl(Index, 1, true); } @@ -1602,7 +1616,7 @@ public: * suitable after remove. Default is true. */ template - FORCEINLINE void RemoveAtSwap(int32 Index, CountType Count, bool bAllowShrinking = true) + FORCEINLINE void RemoveAtSwap(SizeType Index, CountType Count, bool bAllowShrinking = true) { static_assert(!TAreTypesEqual::Value, "TArray::RemoveAtSwap: unexpected bool passed as the Count argument"); RemoveAtSwapImpl(Index, Count, bAllowShrinking); @@ -1614,7 +1628,7 @@ public: * * @param NewSize The expected usage size after calling this function. */ - void Reset(int32 NewSize = 0) + void Reset(SizeType NewSize = 0) { // If we have space to hold the excepted size, then don't reallocate if (NewSize <= ArrayMax) @@ -1633,7 +1647,7 @@ public: * * @param Slack (Optional) The expected usage size after empty operation. Default is 0. */ - void Empty(int32 Slack = 0) + void Empty(SizeType Slack = 0) { DestructItems(GetData(), ArrayNum); @@ -1652,12 +1666,12 @@ public: * @param NewNum New size of the array. * @param bAllowShrinking Tell if this function can shrink the memory in-use if suitable. */ - void SetNum(int32 NewNum, bool bAllowShrinking = true) + void SetNum(SizeType NewNum, bool bAllowShrinking = true) { if (NewNum > Num()) { - const int32 Diff = NewNum - ArrayNum; - const int32 Index = AddUninitialized(Diff); + const SizeType Diff = NewNum - ArrayNum; + const SizeType Index = AddUninitialized(Diff); DefaultConstructItems((uint8*)AllocatorInstance.GetAllocation() + Index * sizeof(ElementType), Diff); } else if (NewNum < Num()) @@ -1671,7 +1685,7 @@ public: * * @param NewNum New size of the array. */ - void SetNumZeroed(int32 NewNum, bool bAllowShrinking = true) + void SetNumZeroed(SizeType NewNum, bool bAllowShrinking = true) { if (NewNum > Num()) { @@ -1688,7 +1702,7 @@ public: * * @param NewNum New size of the array. */ - void SetNumUninitialized(int32 NewNum, bool bAllowShrinking = true) + void SetNumUninitialized(SizeType NewNum, bool bAllowShrinking = true) { if (NewNum > Num()) { @@ -1704,7 +1718,7 @@ public: * Does nothing except setting the new number of elements in the array. Does not destruct items, does not de-allocate memory. * @param NewNum New number of elements in the array, must be <= the current number of elements in the array. */ - void SetNumUnsafeInternal(int32 NewNum) + void SetNumUnsafeInternal(SizeType NewNum) { checkSlow(NewNum <= Num() && NewNum >= 0); ArrayNum = NewNum; @@ -1723,7 +1737,7 @@ public: { check((void*)this != (void*)&Source); - int32 SourceCount = Source.Num(); + SizeType SourceCount = Source.Num(); // Do nothing if the source is empty. if (!SourceCount) @@ -1749,7 +1763,7 @@ public: { check((void*)this != (void*)&Source); - int32 SourceCount = Source.Num(); + SizeType SourceCount = Source.Num(); // Do nothing if the source is empty. if (!SourceCount) @@ -1772,11 +1786,11 @@ public: * @param Count The number of elements to insert from Ptr. * @see Add, Insert */ - void Append(const ElementType* Ptr, int32 Count) + void Append(const ElementType* Ptr, SizeType Count) { check(Ptr != nullptr || Count == 0); - int32 Pos = AddUninitialized(Count); + SizeType Pos = AddUninitialized(Count); ConstructItems(GetData() + Pos, Ptr, Count); } @@ -1788,9 +1802,9 @@ public: */ FORCEINLINE void Append(std::initializer_list InitList) { - int32 Count = (int32)InitList.size(); + SizeType Count = (SizeType)InitList.size(); - int32 Pos = AddUninitialized(Count); + SizeType Pos = AddUninitialized(Count); ConstructItems(GetData() + Pos, InitList.begin(), Count); } @@ -1838,9 +1852,9 @@ public: * @return Index to the new item */ template - FORCEINLINE int32 Emplace(ArgsType&&... Args) + FORCEINLINE SizeType Emplace(ArgsType&&... Args) { - const int32 Index = AddUninitialized(1); + const SizeType Index = AddUninitialized(1); new(GetData() + Index) ElementType(Forward(Args)...); return Index; } @@ -1854,7 +1868,7 @@ public: template FORCEINLINE ElementType& Emplace_GetRef(ArgsType&&... Args) { - const int32 Index = AddUninitialized(1); + const SizeType Index = AddUninitialized(1); ElementType* Ptr = GetData() + Index; new(Ptr) ElementType(Forward(Args)...); return *Ptr; @@ -1867,9 +1881,9 @@ public: * @param Args The arguments to forward to the constructor of the new item. */ template - FORCEINLINE void EmplaceAt(int32 Index, ArgsType&&... Args) + FORCEINLINE void EmplaceAt(SizeType Index, ArgsType&&... Args) { - InsertUninitialized(Index, 1); + InsertUninitializedImpl(Index, 1); new(GetData() + Index) ElementType(Forward(Args)...); } @@ -1881,9 +1895,9 @@ public: * @return A reference to the newly-inserted element. */ template - FORCEINLINE ElementType& EmplaceAt_GetRef(int32 Index, ArgsType&&... Args) + FORCEINLINE ElementType& EmplaceAt_GetRef(SizeType Index, ArgsType&&... Args) { - InsertUninitialized(Index, 1); + InsertUninitializedImpl(Index, 1); ElementType* Ptr = GetData() + Index; new(Ptr) ElementType(Forward(Args)...); return *Ptr; @@ -1898,7 +1912,7 @@ public: * @return Index to the new item * @see AddDefaulted, AddUnique, AddZeroed, Append, Insert */ - FORCEINLINE int32 Add(ElementType&& Item) + FORCEINLINE SizeType Add(ElementType&& Item) { CheckAddress(&Item); return Emplace(MoveTempIfPossible(Item)); @@ -1911,7 +1925,7 @@ public: * @return Index to the new item * @see AddDefaulted, AddUnique, AddZeroed, Append, Insert */ - FORCEINLINE int32 Add(const ElementType& Item) + FORCEINLINE SizeType Add(const ElementType& Item) { CheckAddress(&Item); return Emplace(Item); @@ -1957,9 +1971,9 @@ public: * @return Index to the first of the new items. * @see Add, AddDefaulted, AddUnique, Append, Insert */ - int32 AddZeroed(int32 Count = 1) + SizeType AddZeroed(SizeType Count = 1) { - const int32 Index = AddUninitialized(Count); + const SizeType Index = AddUninitialized(Count); FMemory::Memzero((uint8*)AllocatorInstance.GetAllocation() + Index*sizeof(ElementType), Count*sizeof(ElementType)); return Index; } @@ -1977,7 +1991,7 @@ public: */ ElementType& AddZeroed_GetRef() { - const int32 Index = AddUninitialized(1); + const SizeType Index = AddUninitialized(1); ElementType* Ptr = GetData() + Index; FMemory::Memzero(Ptr, sizeof(ElementType)); return *Ptr; @@ -1991,9 +2005,9 @@ public: * @return Index to the first of the new items. * @see Add, AddZeroed, AddUnique, Append, Insert */ - int32 AddDefaulted(int32 Count = 1) + SizeType AddDefaulted(SizeType Count = 1) { - const int32 Index = AddUninitialized(Count); + const SizeType Index = AddUninitialized(Count); DefaultConstructItems((uint8*)AllocatorInstance.GetAllocation() + Index * sizeof(ElementType), Count); return Index; } @@ -2007,7 +2021,7 @@ public: */ ElementType& AddDefaulted_GetRef() { - const int32 Index = AddUninitialized(1); + const SizeType Index = AddUninitialized(1); ElementType* Ptr = GetData() + Index; DefaultConstructItems(Ptr, 1); return *Ptr; @@ -2022,9 +2036,9 @@ private: * @returns Index of the element in the array. */ template - int32 AddUniqueImpl(ArgsType&& Args) + SizeType AddUniqueImpl(ArgsType&& Args) { - int32 Index; + SizeType Index; if (Find(Args, Index)) { return Index; @@ -2044,7 +2058,7 @@ public: * @returns Index of the element in the array. * @see Add, AddDefaulted, AddZeroed, Append, Insert */ - FORCEINLINE int32 AddUnique(ElementType&& Item) { return AddUniqueImpl(MoveTempIfPossible(Item)); } + FORCEINLINE SizeType AddUnique(ElementType&& Item) { return AddUniqueImpl(MoveTempIfPossible(Item)); } /** * Adds unique element to array if it doesn't exist. @@ -2053,7 +2067,7 @@ public: * @returns Index of the element in the array. * @see Add, AddDefaulted, AddZeroed, Append, Insert */ - FORCEINLINE int32 AddUnique(const ElementType& Item) { return AddUniqueImpl(Item); } + FORCEINLINE SizeType AddUnique(const ElementType& Item) { return AddUniqueImpl(Item); } /** * Reserves memory such that the array can contain at least Number elements. @@ -2061,7 +2075,7 @@ public: * @param Number The number of elements that the array should be able to contain after allocation. * @see Shrink */ - FORCEINLINE void Reserve(int32 Number) + FORCEINLINE void Reserve(SizeType Number) { checkSlow(Number >= 0); if (Number > ArrayMax) @@ -2076,10 +2090,10 @@ public: * @param Element The element to fill array with. * @param Number The number of elements that the array should be able to contain after allocation. */ - void Init(const ElementType& Element, int32 Number) + void Init(const ElementType& Element, SizeType Number) { Empty(Number); - for (int32 Index = 0; Index < Number; ++Index) + for (SizeType Index = 0; Index < Number; ++Index) { new(*this) ElementType(Element); } @@ -2093,9 +2107,9 @@ public: * @returns The number of items removed. For RemoveSingleItem, this is always either 0 or 1. * @see Add, Insert, Remove, RemoveAll, RemoveAllSwap */ - int32 RemoveSingle(const ElementType& Item) + SizeType RemoveSingle(const ElementType& Item) { - int32 Index = Find(Item); + SizeType Index = Find(Item); if (Index == INDEX_NONE) { return 0; @@ -2105,7 +2119,7 @@ public: // Destruct items that match the specified Item. DestructItems(RemovePtr, 1); - const int32 NextIndex = Index + 1; + const SizeType NextIndex = Index + 1; RelocateConstructItems(RemovePtr, RemovePtr + 1, ArrayNum - (Index + 1)); // Update the array count @@ -2123,7 +2137,7 @@ public: * @returns Number of removed elements. * @see Add, Insert, RemoveAll, RemoveAllSwap, RemoveSingle, RemoveSwap */ - int32 Remove(const ElementType& Item) + SizeType Remove(const ElementType& Item) { CheckAddress(&Item); @@ -2140,25 +2154,25 @@ public: * @see Add, Insert, RemoveAllSwap, RemoveSingle, RemoveSwap */ template - int32 RemoveAll(const PREDICATE_CLASS& Predicate) + SizeType RemoveAll(const PREDICATE_CLASS& Predicate) { - const int32 OriginalNum = ArrayNum; + const SizeType OriginalNum = ArrayNum; if (!OriginalNum) { return 0; // nothing to do, loop assumes one item so need to deal with this edge case here } - int32 WriteIndex = 0; - int32 ReadIndex = 0; + SizeType WriteIndex = 0; + SizeType ReadIndex = 0; bool NotMatch = !Predicate(GetData()[ReadIndex]); // use a ! to guarantee it can't be anything other than zero or one do { - int32 RunStartIndex = ReadIndex++; + SizeType RunStartIndex = ReadIndex++; while (ReadIndex < OriginalNum && NotMatch == !Predicate(GetData()[ReadIndex])) { ReadIndex++; } - int32 RunLength = ReadIndex - RunStartIndex; + SizeType RunLength = ReadIndex - RunStartIndex; checkSlow(RunLength > 0); if (NotMatch) { @@ -2190,7 +2204,7 @@ public: template void RemoveAllSwap(const PREDICATE_CLASS& Predicate, bool bAllowShrinking = true) { - for (int32 ItemIndex = 0; ItemIndex < Num();) + for (SizeType ItemIndex = 0; ItemIndex < Num();) { if (Predicate((*this)[ItemIndex])) { @@ -2212,9 +2226,9 @@ public: * @returns The number of items removed. For RemoveSingleItem, this is always either 0 or 1. * @see Add, Insert, Remove, RemoveAll, RemoveAllSwap, RemoveSwap */ - int32 RemoveSingleSwap(const ElementType& Item, bool bAllowShrinking = true) + SizeType RemoveSingleSwap(const ElementType& Item, bool bAllowShrinking = true) { - int32 Index = Find(Item); + SizeType Index = Find(Item); if (Index == INDEX_NONE) { return 0; @@ -2236,12 +2250,12 @@ public: * @returns Number of elements removed. * @see Add, Insert, Remove, RemoveAll, RemoveAllSwap */ - int32 RemoveSwap(const ElementType& Item) + SizeType RemoveSwap(const ElementType& Item) { CheckAddress(&Item); - const int32 OriginalNum = ArrayNum; - for (int32 Index = 0; Index < ArrayNum; Index++) + const SizeType OriginalNum = ArrayNum; + for (SizeType Index = 0; Index < ArrayNum; Index++) { if ((*this)[Index] == Item) { @@ -2257,7 +2271,7 @@ public: * @param FirstIndexToSwap Position of the first element to swap. * @param SecondIndexToSwap Position of the second element to swap. */ - FORCEINLINE void SwapMemory(int32 FirstIndexToSwap, int32 SecondIndexToSwap) + FORCEINLINE void SwapMemory(SizeType FirstIndexToSwap, SizeType SecondIndexToSwap) { FMemory::Memswap( (uint8*)AllocatorInstance.GetAllocation() + (sizeof(ElementType)*FirstIndexToSwap), @@ -2274,7 +2288,7 @@ public: * @param FirstIndexToSwap Position of the first element to swap. * @param SecondIndexToSwap Position of the second element to swap. */ - FORCEINLINE void Swap(int32 FirstIndexToSwap, int32 SecondIndexToSwap) + FORCEINLINE void Swap(SizeType FirstIndexToSwap, SizeType SecondIndexToSwap) { check((FirstIndexToSwap >= 0) && (SecondIndexToSwap >= 0)); check((ArrayNum > FirstIndexToSwap) && (ArrayNum > SecondIndexToSwap)); @@ -2299,10 +2313,10 @@ public: * @returns True if element was found. False otherwise. */ template - bool FindItemByClass(SearchType **Item = nullptr, int32 *ItemIndex = nullptr, int32 StartIndex = 0) const + bool FindItemByClass(SearchType **Item = nullptr, SizeType *ItemIndex = nullptr, SizeType StartIndex = 0) const { UClass* SearchClass = SearchType::StaticClass(); - for (int32 Idx = StartIndex; Idx < ArrayNum; Idx++) + for (SizeType Idx = StartIndex; Idx < ArrayNum; Idx++) { if ((*this)[Idx] != nullptr && (*this)[Idx]->IsA(SearchClass)) { @@ -2321,8 +2335,8 @@ public: } // Iterators - typedef TIndexedContainerIterator< TArray, ElementType, int32> TIterator; - typedef TIndexedContainerIterator TConstIterator; + typedef TIndexedContainerIterator< TArray, ElementType, SizeType> TIterator; + typedef TIndexedContainerIterator TConstIterator; /** * Creates an iterator for the contents of this array @@ -2345,8 +2359,8 @@ public: } #if TARRAY_RANGED_FOR_CHECKS - typedef TCheckedPointerIterator< ElementType> RangedForIteratorType; - typedef TCheckedPointerIterator RangedForConstIteratorType; + typedef TCheckedPointerIterator< ElementType, SizeType> RangedForIteratorType; + typedef TCheckedPointerIterator RangedForConstIteratorType; #else typedef ElementType* RangedForIteratorType; typedef const ElementType* RangedForConstIteratorType; @@ -2440,7 +2454,7 @@ private: * @param Index Position to get. * @returns Reference to the element at given position. */ - FORCENOINLINE const ElementType& DebugGet(int32 Index) const + FORCENOINLINE const ElementType& DebugGet(SizeType Index) const { return GetData()[Index]; } @@ -2448,14 +2462,14 @@ private: private: - FORCENOINLINE void ResizeGrow(int32 OldNum) + FORCENOINLINE void ResizeGrow(SizeType OldNum) { ArrayMax = AllocatorInstance.CalculateSlackGrow(ArrayNum, ArrayMax, sizeof(ElementType)); AllocatorInstance.ResizeAllocation(OldNum, ArrayMax, sizeof(ElementType)); } FORCENOINLINE void ResizeShrink() { - const int32 NewArrayMax = AllocatorInstance.CalculateSlackShrink(ArrayNum, ArrayMax, sizeof(ElementType)); + const SizeType NewArrayMax = AllocatorInstance.CalculateSlackShrink(ArrayNum, ArrayMax, sizeof(ElementType)); if (NewArrayMax != ArrayMax) { ArrayMax = NewArrayMax; @@ -2463,7 +2477,7 @@ private: AllocatorInstance.ResizeAllocation(ArrayNum, ArrayMax, sizeof(ElementType)); } } - FORCENOINLINE void ResizeTo(int32 NewMax) + FORCENOINLINE void ResizeTo(SizeType NewMax) { if (NewMax) { @@ -2475,7 +2489,7 @@ private: AllocatorInstance.ResizeAllocation(ArrayNum, ArrayMax, sizeof(ElementType)); } } - FORCENOINLINE void ResizeForCopy(int32 NewMax, int32 PrevMax) + FORCENOINLINE void ResizeForCopy(SizeType NewMax, SizeType PrevMax) { if (NewMax) { @@ -2499,14 +2513,17 @@ private: * the end of the buffer. Counted in elements. Zero by * default. */ - template - void CopyToEmpty(const OtherElementType* OtherData, int32 OtherNum, int32 PrevMax, int32 ExtraSlack) + template + void CopyToEmpty(const OtherElementType* OtherData, OtherSizeType OtherNum, SizeType PrevMax, SizeType ExtraSlack) { + SizeType NewNum = OtherNum; + checkf((OtherSizeType)NewNum == OtherNum, TEXT("Invalid number of elements to add to this array type: %llu"), (unsigned long long)NewNum); + checkSlow(ExtraSlack >= 0); - ArrayNum = OtherNum; + ArrayNum = NewNum; if (OtherNum || ExtraSlack || PrevMax) { - ResizeForCopy(OtherNum + ExtraSlack, PrevMax); + ResizeForCopy(NewNum + ExtraSlack, PrevMax); ConstructItems(GetData(), OtherData, OtherNum); } else @@ -2524,8 +2541,8 @@ protected: >::Result ElementAllocatorType; ElementAllocatorType AllocatorInstance; - int32 ArrayNum; - int32 ArrayMax; + SizeType ArrayNum; + SizeType ArrayMax; /** * Implicit heaps @@ -2574,12 +2591,12 @@ public: * The auto-dereferencing behavior does not occur with smart pointers. */ template - int32 HeapPush(ElementType&& InItem, const PREDICATE_CLASS& Predicate) + SizeType HeapPush(ElementType&& InItem, const PREDICATE_CLASS& Predicate) { // Add at the end, then sift up Add(MoveTempIfPossible(InItem)); TDereferenceWrapper PredicateWrapper(Predicate); - int32 Result = AlgoImpl::HeapSiftUp(GetData(), 0, Num() - 1, FIdentityFunctor(), PredicateWrapper); + SizeType Result = AlgoImpl::HeapSiftUp(GetData(), 0, Num() - 1, FIdentityFunctor(), PredicateWrapper); return Result; } @@ -2596,12 +2613,12 @@ public: * The auto-dereferencing behavior does not occur with smart pointers. */ template - int32 HeapPush(const ElementType& InItem, const PREDICATE_CLASS& Predicate) + SizeType HeapPush(const ElementType& InItem, const PREDICATE_CLASS& Predicate) { // Add at the end, then sift up Add(InItem); TDereferenceWrapper PredicateWrapper(Predicate); - int32 Result = AlgoImpl::HeapSiftUp(GetData(), 0, Num() - 1, FIdentityFunctor(), PredicateWrapper); + SizeType Result = AlgoImpl::HeapSiftUp(GetData(), 0, Num() - 1, FIdentityFunctor(), PredicateWrapper); return Result; } @@ -2617,7 +2634,7 @@ public: * Therefore, your array will be heapified by the values being pointed to, rather than the pointers' values. * The auto-dereferencing behavior does not occur with smart pointers. */ - int32 HeapPush(ElementType&& InItem) + SizeType HeapPush(ElementType&& InItem) { return HeapPush(MoveTempIfPossible(InItem), TLess()); } @@ -2633,7 +2650,7 @@ public: * Therefore, your array will be heapified by the values being pointed to, rather than the pointers' values. * The auto-dereferencing behavior does not occur with smart pointers. */ - int32 HeapPush(const ElementType& InItem) + SizeType HeapPush(const ElementType& InItem) { return HeapPush(InItem, TLess()); } @@ -2753,7 +2770,7 @@ public: * The auto-dereferencing behavior does not occur with smart pointers. */ template - void HeapRemoveAt(int32 Index, const PREDICATE_CLASS& Predicate, bool bAllowShrinking = true) + void HeapRemoveAt(SizeType Index, const PREDICATE_CLASS& Predicate, bool bAllowShrinking = true) { RemoveAtSwap(Index, 1, bAllowShrinking); @@ -2773,7 +2790,7 @@ public: * Therefore, your array will be heapified by the values being pointed to, rather than the pointers' values. * The auto-dereferencing behavior does not occur with smart pointers. */ - void HeapRemoveAt(int32 Index, bool bAllowShrinking = true) + void HeapRemoveAt(SizeType Index, bool bAllowShrinking = true) { HeapRemoveAt(Index, TLess< ElementType >(), bAllowShrinking); } @@ -2846,13 +2863,12 @@ template struct TIsTArray void* operator new( size_t Size, TArray& Array ) { check(Size == sizeof(T)); - const int32 Index = Array.AddUninitialized(1); + const auto Index = Array.AddUninitialized(1); return &Array[Index]; } -template void* operator new( size_t Size, TArray& Array, int32 Index ) +template void* operator new( size_t Size, TArray& Array, typename TArray::SizeType Index ) { check(Size == sizeof(T)); - Array.InsertUninitialized(Index,1); + Array.InsertUninitialized(Index); return &Array[Index]; } - diff --git a/Engine/Source/Runtime/Core/Public/Containers/ArrayView.h b/Engine/Source/Runtime/Core/Public/Containers/ArrayView.h index b793a3f28154..b29f9915fab0 100644 --- a/Engine/Source/Runtime/Core/Public/Containers/ArrayView.h +++ b/Engine/Source/Runtime/Core/Public/Containers/ArrayView.h @@ -3,6 +3,7 @@ #pragma once #include "CoreTypes.h" +#include "Templates/IsInitializerList.h" #include "Templates/PointerIsConvertibleFromTo.h" #include "Misc/AssertionMacros.h" #include "Templates/UnrealTypeTraits.h" @@ -137,9 +138,9 @@ public: * * @param List The initializer list to view. */ - template ::Value>::Type> - FORCEINLINE TArrayView(std::initializer_list List) + template , TIsCompatibleElementType>::Value>::Type> + FORCEINLINE TArrayView(InitializerListType& List) : DataPtr(&*List.begin()) , ArrayNum(List.size()) { @@ -596,10 +597,13 @@ TArrayView MakeArrayView(const TArray return TArrayView(Other); } -template -TArrayView MakeArrayView(std::initializer_list List) +template +typename TEnableIf< + TIsInitializerList::Value, + TArrayView +>::Type MakeArrayView(InitializerListType& List) { - return TArrayView(List); + return TArrayView(List); } template diff --git a/Engine/Source/Runtime/Core/Public/Containers/ContainerAllocationPolicies.h b/Engine/Source/Runtime/Core/Public/Containers/ContainerAllocationPolicies.h index 56501cd24148..5cb1192bc7ff 100644 --- a/Engine/Source/Runtime/Core/Public/Containers/ContainerAllocationPolicies.h +++ b/Engine/Source/Runtime/Core/Public/Containers/ContainerAllocationPolicies.h @@ -12,6 +12,9 @@ class FDefaultBitArrayAllocator; +template class TSizedDefaultAllocator; +using FDefaultAllocator = TSizedDefaultAllocator<32>; + /** branchless pointer selection * return A ? A : B; **/ @@ -24,13 +27,14 @@ ReferencedType* IfAThenAElseB(ReferencedType* A,ReferencedType* B); template ReferencedType* IfPThenAElseB(PredicateType Predicate,ReferencedType* A,ReferencedType* B); -FORCEINLINE int32 DefaultCalculateSlackShrink(int32 NumElements, int32 NumAllocatedElements, SIZE_T BytesPerElement, bool bAllowQuantize, uint32 Alignment = DEFAULT_ALIGNMENT) +template +FORCEINLINE SizeType DefaultCalculateSlackShrink(SizeType NumElements, SizeType NumAllocatedElements, SIZE_T BytesPerElement, bool bAllowQuantize, uint32 Alignment = DEFAULT_ALIGNMENT) { - int32 Retval; + SizeType Retval; checkSlow(NumElements < NumAllocatedElements); // If the container has too much slack, shrink it to exactly fit the number of elements. - const uint32 CurrentSlackElements = NumAllocatedElements - NumElements; + const SizeType CurrentSlackElements = NumAllocatedElements - NumElements; const SIZE_T CurrentSlackBytes = (NumAllocatedElements - NumElements)*BytesPerElement; const bool bTooManySlackBytes = CurrentSlackBytes >= 16384; const bool bTooManySlackElements = 3 * NumElements < 2 * NumAllocatedElements; @@ -53,7 +57,8 @@ FORCEINLINE int32 DefaultCalculateSlackShrink(int32 NumElements, int32 NumAlloca return Retval; } -FORCEINLINE int32 DefaultCalculateSlackGrow(int32 NumElements, int32 NumAllocatedElements, SIZE_T BytesPerElement, bool bAllowQuantize, uint32 Alignment = DEFAULT_ALIGNMENT) +template +FORCEINLINE SizeType DefaultCalculateSlackGrow(SizeType NumElements, SizeType NumAllocatedElements, SIZE_T BytesPerElement, bool bAllowQuantize, uint32 Alignment = DEFAULT_ALIGNMENT) { #if !defined(AGGRESSIVE_MEMORY_SAVING) #error "AGGRESSIVE_MEMORY_SAVING must be defined" @@ -66,7 +71,7 @@ FORCEINLINE int32 DefaultCalculateSlackGrow(int32 NumElements, int32 NumAllocate const SIZE_T ConstantGrow = 16; #endif - int32 Retval; + SizeType Retval; checkSlow(NumElements > NumAllocatedElements && NumElements > 0); SIZE_T Grow = FirstGrow; // this is the amount for the first alloc @@ -86,15 +91,16 @@ FORCEINLINE int32 DefaultCalculateSlackGrow(int32 NumElements, int32 NumAllocate // NumElements and MaxElements are stored in 32 bit signed integers so we must be careful not to overflow here. if (NumElements > Retval) { - Retval = MAX_int32; + Retval = TNumericLimits::Max(); } return Retval; } -FORCEINLINE int32 DefaultCalculateSlackReserve(int32 NumElements, SIZE_T BytesPerElement, bool bAllowQuantize, uint32 Alignment = DEFAULT_ALIGNMENT) +template +FORCEINLINE SizeType DefaultCalculateSlackReserve(SizeType NumElements, SIZE_T BytesPerElement, bool bAllowQuantize, uint32 Alignment = DEFAULT_ALIGNMENT) { - int32 Retval = NumElements; + SizeType Retval = NumElements; checkSlow(NumElements > 0); if (bAllowQuantize) { @@ -102,7 +108,7 @@ FORCEINLINE int32 DefaultCalculateSlackReserve(int32 NumElements, SIZE_T BytesPe // NumElements and MaxElements are stored in 32 bit signed integers so we must be careful not to overflow here. if (NumElements > Retval) { - Retval = MAX_int32; + Retval = TNumericLimits::Max(); } } @@ -130,6 +136,8 @@ struct TAllocatorTraits : TAllocatorTraitsBase class FContainerAllocatorInterface { public: + /** The integral type to be used for element counts and indices used by the allocator and container - must be signed */ + using SizeType = int32; /** Determines whether the user of the allocator may use the ForAnyElementType inner class. */ enum { NeedsElementType = true }; @@ -159,8 +167,8 @@ public: * @param NumBytesPerElement - The number of bytes/element. */ void ResizeAllocation( - int32 PreviousNumElements, - int32 NumElements, + SizeType PreviousNumElements, + SizeType NumElements, SIZE_T NumBytesPerElement ); @@ -170,37 +178,45 @@ public: * @param CurrentNumSlackElements - The current number of elements allocated. * @param NumBytesPerElement - The number of bytes/element. */ - int32 CalculateSlack( - int32 NumElements, - int32 CurrentNumSlackElements, + SizeType CalculateSlackReserve( + SizeType NumElements, + SizeType CurrentNumSlackElements, SIZE_T NumBytesPerElement ) const; /** - * Calculates the amount of slack to allocate for an array that has just shrunk to a given number of elements. - * @param NumElements - The number of elements to allocate space for. - * @param CurrentNumSlackElements - The current number of elements allocated. - * @param NumBytesPerElement - The number of bytes/element. - */ - int32 CalculateSlackShrink( - int32 NumElements, - int32 CurrentNumSlackElements, + * Calculates the amount of slack to allocate for an array that has just shrunk to a given number of elements. + * @param NumElements - The number of elements to allocate space for. + * @param CurrentNumSlackElements - The current number of elements allocated. + * @param NumBytesPerElement - The number of bytes/element. + */ + SizeType CalculateSlackShrink( + SizeType NumElements, + SizeType CurrentNumSlackElements, SIZE_T NumBytesPerElement ) const; /** - * Calculates the amount of slack to allocate for an array that has just grown to a given number of elements. - * @param NumElements - The number of elements to allocate space for. - * @param CurrentNumSlackElements - The current number of elements allocated. - * @param NumBytesPerElement - The number of bytes/element. - */ - int32 CalculateSlackGrow( - int32 NumElements, - int32 CurrentNumSlackElements, + * Calculates the amount of slack to allocate for an array that has just grown to a given number of elements. + * @param NumElements - The number of elements to allocate space for. + * @param CurrentNumSlackElements - The current number of elements allocated. + * @param NumBytesPerElement - The number of bytes/element. + */ + SizeType CalculateSlackGrow( + SizeType NumElements, + SizeType CurrentNumSlackElements, SIZE_T NumBytesPerElement ) const; - SIZE_T GetAllocatedSize(int32 NumAllocatedElements, SIZE_T NumBytesPerElement) const; + /** + * Returns the size of any requested heap allocation currently owned by the allocator. + * @param NumAllocatedElements - The number of elements allocated by the container. + * @param NumBytesPerElement - The number of bytes/element. + */ + SIZE_T GetAllocatedSize(SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const; + + /** Returns true if the allocator has made any heap allocations */ + bool HasAllocation() const; }; /** @@ -215,6 +231,7 @@ template class TAlignedHeapAllocator { public: + using SizeType = int32; enum { NeedsElementType = false }; enum { RequireRangeCheck = true }; @@ -261,8 +278,8 @@ public: return Data; } void ResizeAllocation( - int32 PreviousNumElements, - int32 NumElements, + SizeType PreviousNumElements, + SizeType NumElements, SIZE_T NumBytesPerElement ) { @@ -273,25 +290,25 @@ public: Data = (FScriptContainerElement*)FMemory::Realloc( Data, NumElements*NumBytesPerElement, Alignment ); } } - FORCEINLINE int32 CalculateSlackReserve(int32 NumElements, int32 NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackReserve(SizeType NumElements, SIZE_T NumBytesPerElement) const { return DefaultCalculateSlackReserve(NumElements, NumBytesPerElement, true, Alignment); } - FORCEINLINE int32 CalculateSlackShrink(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackShrink(SizeType NumElements, SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { return DefaultCalculateSlackShrink(NumElements, NumAllocatedElements, NumBytesPerElement, true, Alignment); } - FORCEINLINE int32 CalculateSlackGrow(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackGrow(SizeType NumElements, SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { return DefaultCalculateSlackGrow(NumElements, NumAllocatedElements, NumBytesPerElement, true, Alignment); } - SIZE_T GetAllocatedSize(int32 NumAllocatedElements, SIZE_T NumBytesPerElement) const + SIZE_T GetAllocatedSize(SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { return NumAllocatedElements * NumBytesPerElement; } - bool HasAllocation() + bool HasAllocation() const { return !!Data; } @@ -327,15 +344,28 @@ struct TAllocatorTraits> : TAllocatorTraitsBase enum { IsZeroConstruct = true }; }; +template +struct TBitsToSizeType +{ + static_assert(IndexSize, "Unsupported allocator index size."); +}; + +template <> struct TBitsToSizeType<8> { using Type = int8; }; +template <> struct TBitsToSizeType<16> { using Type = int16; }; +template <> struct TBitsToSizeType<32> { using Type = int32; }; +template <> struct TBitsToSizeType<64> { using Type = int64; }; + /** The indirect allocation policy always allocates the elements indirectly. */ -class CORE_API FHeapAllocator +template +class TSizedHeapAllocator { public: + using SizeType = typename TBitsToSizeType::Type; enum { NeedsElementType = false }; enum { RequireRangeCheck = true }; - class CORE_API ForAnyElementType + class ForAnyElementType { public: /** Default constructor. */ @@ -375,7 +405,7 @@ public: { return Data; } - FORCEINLINE void ResizeAllocation(int32 PreviousNumElements, int32 NumElements, SIZE_T NumBytesPerElement) + FORCEINLINE void ResizeAllocation(SizeType PreviousNumElements, SizeType NumElements, SIZE_T NumBytesPerElement) { // Avoid calling FMemory::Realloc( nullptr, 0 ) as ANSI C mandates returning a valid pointer which is not what we want. if (Data || NumElements) @@ -384,25 +414,25 @@ public: Data = (FScriptContainerElement*)FMemory::Realloc( Data, NumElements*NumBytesPerElement ); } } - FORCEINLINE int32 CalculateSlackReserve(int32 NumElements, int32 NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackReserve(SizeType NumElements, SIZE_T NumBytesPerElement) const { return DefaultCalculateSlackReserve(NumElements, NumBytesPerElement, true); } - FORCEINLINE int32 CalculateSlackShrink(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackShrink(SizeType NumElements, SizeType NumAllocatedElements, SizeType NumBytesPerElement) const { return DefaultCalculateSlackShrink(NumElements, NumAllocatedElements, NumBytesPerElement, true); } - FORCEINLINE int32 CalculateSlackGrow(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackGrow(SizeType NumElements, SizeType NumAllocatedElements, SizeType NumBytesPerElement) const { return DefaultCalculateSlackGrow(NumElements, NumAllocatedElements, NumBytesPerElement, true); } - SIZE_T GetAllocatedSize(int32 NumAllocatedElements, SIZE_T NumBytesPerElement) const + SIZE_T GetAllocatedSize(SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { return NumAllocatedElements * NumBytesPerElement; } - bool HasAllocation() + bool HasAllocation() const { return !!Data; } @@ -431,14 +461,14 @@ public: }; }; -template <> -struct TAllocatorTraits : TAllocatorTraitsBase +template +struct TAllocatorTraits> : TAllocatorTraitsBase> { enum { SupportsMove = true }; enum { IsZeroConstruct = true }; }; -class FDefaultAllocator; +using FHeapAllocator = TSizedHeapAllocator<32>; /** * The inline allocation policy allocates up to a specified number of elements in the same allocation as the container. @@ -449,6 +479,7 @@ template (SecondaryData.GetAllocation(),GetInlineElements()); } - void ResizeAllocation(int32 PreviousNumElements,int32 NumElements,SIZE_T NumBytesPerElement) + void ResizeAllocation(SizeType PreviousNumElements, SizeType NumElements,SIZE_T NumBytesPerElement) { // Check if the new allocation will fit in the inline data area. if(NumElements <= NumInlineElements) @@ -521,21 +552,21 @@ public: } } - FORCEINLINE int32 CalculateSlackReserve(int32 NumElements, SIZE_T NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackReserve(SizeType NumElements, SIZE_T NumBytesPerElement) const { // If the elements use less space than the inline allocation, only use the inline allocation as slack. return NumElements <= NumInlineElements ? NumInlineElements : SecondaryData.CalculateSlackReserve(NumElements, NumBytesPerElement); } - FORCEINLINE int32 CalculateSlackShrink(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackShrink(SizeType NumElements, SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { // If the elements use less space than the inline allocation, only use the inline allocation as slack. return NumElements <= NumInlineElements ? NumInlineElements : SecondaryData.CalculateSlackShrink(NumElements, NumAllocatedElements, NumBytesPerElement); } - FORCEINLINE int32 CalculateSlackGrow(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackGrow(SizeType NumElements, SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { // If the elements use less space than the inline allocation, only use the inline allocation as slack. return NumElements <= NumInlineElements ? @@ -543,7 +574,7 @@ public: SecondaryData.CalculateSlackGrow(NumElements, NumAllocatedElements, NumBytesPerElement); } - SIZE_T GetAllocatedSize(int32 NumAllocatedElements, SIZE_T NumBytesPerElement) const + SIZE_T GetAllocatedSize(SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { if (NumAllocatedElements > NumInlineElements) { @@ -552,7 +583,7 @@ public: return 0; } - bool HasAllocation() + bool HasAllocation() const { return SecondaryData.HasAllocation(); } @@ -592,6 +623,7 @@ template class TNonRelocatableInlineAllocator { public: + using SizeType = int32; enum { NeedsElementType = true }; enum { RequireRangeCheck = true }; @@ -639,7 +671,7 @@ public: return Data; } - void ResizeAllocation(int32 PreviousNumElements,int32 NumElements,SIZE_T NumBytesPerElement) + void ResizeAllocation(SizeType PreviousNumElements, SizeType NumElements,SIZE_T NumBytesPerElement) { // Check if the new allocation will fit in the inline data area. if(NumElements <= NumInlineElements) @@ -670,25 +702,25 @@ public: } } - FORCEINLINE int32 CalculateSlackReserve(int32 NumElements, SIZE_T NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackReserve(SizeType NumElements, SIZE_T NumBytesPerElement) const { // If the elements use less space than the inline allocation, only use the inline allocation as slack. return (NumElements <= NumInlineElements) ? NumInlineElements : DefaultCalculateSlackReserve(NumElements, NumBytesPerElement, true); } - FORCEINLINE int32 CalculateSlackShrink(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackShrink(SizeType NumElements, SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { // If the elements use less space than the inline allocation, only use the inline allocation as slack. return (NumElements <= NumInlineElements) ? NumInlineElements : DefaultCalculateSlackShrink(NumElements, NumAllocatedElements, NumBytesPerElement, true); } - FORCEINLINE int32 CalculateSlackGrow(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackGrow(SizeType NumElements, SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { // If the elements use less space than the inline allocation, only use the inline allocation as slack. return (NumElements <= NumInlineElements) ? NumInlineElements : DefaultCalculateSlackGrow(NumElements, NumAllocatedElements, NumBytesPerElement, true); } - SIZE_T GetAllocatedSize(int32 NumAllocatedElements, SIZE_T NumBytesPerElement) const + SIZE_T GetAllocatedSize(SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { return HasAllocation()? (NumAllocatedElements * NumBytesPerElement) : 0; } @@ -732,6 +764,7 @@ template class TFixedAllocator { public: + using SizeType = int32; enum { NeedsElementType = true }; enum { RequireRangeCheck = true }; @@ -765,37 +798,37 @@ public: return GetInlineElements(); } - void ResizeAllocation(int32 PreviousNumElements,int32 NumElements,SIZE_T NumBytesPerElement) + void ResizeAllocation(SizeType PreviousNumElements, SizeType NumElements,SIZE_T NumBytesPerElement) { // Ensure the requested allocation will fit in the inline data area. checkSlow(NumElements <= NumInlineElements); } - FORCEINLINE int32 CalculateSlackReserve(int32 NumElements, SIZE_T NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackReserve(SizeType NumElements, SIZE_T NumBytesPerElement) const { // Ensure the requested allocation will fit in the inline data area. checkSlow(NumElements <= NumInlineElements); return NumInlineElements; } - FORCEINLINE int32 CalculateSlackShrink(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackShrink(SizeType NumElements, SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { // Ensure the requested allocation will fit in the inline data area. checkSlow(NumAllocatedElements <= NumInlineElements); return NumInlineElements; } - FORCEINLINE int32 CalculateSlackGrow(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackGrow(SizeType NumElements, SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { // Ensure the requested allocation will fit in the inline data area. checkSlow(NumElements <= NumInlineElements); return NumInlineElements; } - SIZE_T GetAllocatedSize(int32 NumAllocatedElements, SIZE_T NumBytesPerElement) const + SIZE_T GetAllocatedSize(SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { return 0; } - bool HasAllocation() + bool HasAllocation() const { return false; } @@ -832,9 +865,6 @@ struct TAllocatorTraits> : TAllocatorTraitsBa // Sparse array allocation definitions // -class FDefaultAllocator; -class FDefaultBitArrayAllocator; - /** Encapsulates the allocators used by a sparse array in a single type. */ template class TSparseArrayAllocator @@ -913,8 +943,6 @@ public: typedef InHashAllocator HashAllocator; }; -class FDefaultAllocator; - /** An inline set allocator that allows sizing of the inline allocations for a set number of elements. */ template< uint32 NumInlineElements, @@ -998,11 +1026,14 @@ public: * 'forward' these TAllocatorTraits specializations below. */ -class FDefaultAllocator : public FHeapAllocator { public: typedef FHeapAllocator Typedef; }; +template class TSizedDefaultAllocator : public TSizedHeapAllocator { public: typedef TSizedHeapAllocator Typedef; }; + class FDefaultSetAllocator : public TSetAllocator<> { public: typedef TSetAllocator<> Typedef; }; class FDefaultBitArrayAllocator : public TInlineAllocator<4> { public: typedef TInlineAllocator<4> Typedef; }; class FDefaultSparseArrayAllocator : public TSparseArrayAllocator<> { public: typedef TSparseArrayAllocator<> Typedef; }; +template struct TAllocatorTraits> : TAllocatorTraits::Typedef> {}; + template <> struct TAllocatorTraits : TAllocatorTraits {}; template <> struct TAllocatorTraits : TAllocatorTraits {}; template <> struct TAllocatorTraits : TAllocatorTraits {}; diff --git a/Engine/Source/Runtime/Core/Public/Containers/ContainersFwd.h b/Engine/Source/Runtime/Core/Public/Containers/ContainersFwd.h index 687a938d284f..145c8a556cab 100644 --- a/Engine/Source/Runtime/Core/Public/Containers/ContainersFwd.h +++ b/Engine/Source/Runtime/Core/Public/Containers/ContainersFwd.h @@ -3,12 +3,15 @@ #pragma once /// @cond DOXYGEN_WARNINGS -class FDefaultAllocator; +template class TSizedDefaultAllocator; +using FDefaultAllocator = TSizedDefaultAllocator<32>; +using FDefaultAllocator64 = TSizedDefaultAllocator<64>; class FDefaultSetAllocator; class FString; template class TArray; +template using TArray64 = TArray; template class TTransArray; template struct TDefaultMapHashableKeyFuncs; template > class TMap; diff --git a/Engine/Source/Runtime/Core/Public/Containers/Map.h b/Engine/Source/Runtime/Core/Public/Containers/Map.h index 37545d79ee3f..663953026b5d 100644 --- a/Engine/Source/Runtime/Core/Public/Containers/Map.h +++ b/Engine/Source/Runtime/Core/Public/Containers/Map.h @@ -3,17 +3,19 @@ #pragma once #include "CoreTypes.h" + +#include "Algo/Reverse.h" +#include "Concepts/GetTypeHashable.h" +#include "Containers/Set.h" +#include "Containers/UnrealString.h" #include "Misc/AssertionMacros.h" -#include "Templates/UnrealTypeTraits.h" -#include "Templates/UnrealTemplate.h" -#include "Templates/Sorting.h" #include "Misc/StructBuilder.h" #include "Templates/Function.h" -#include "Containers/Set.h" -#include "Algo/Reverse.h" +#include "Templates/Models.h" +#include "Templates/Sorting.h" #include "Templates/Tuple.h" -#include "Templates/HasGetTypeHash.h" -#include "Containers/UnrealString.h" +#include "Templates/UnrealTemplate.h" +#include "Templates/UnrealTypeTraits.h" #define ExchangeB(A,B) {bool T=A; A=B; B=T;} @@ -94,7 +96,7 @@ struct TDefaultMapKeyFuncs : BaseKeyFuncs,KeyType,bInAl template struct TDefaultMapHashableKeyFuncs : TDefaultMapKeyFuncs { - static_assert(THasGetTypeHash::Value, "TMap must have a hashable KeyType unless a custom key func is provided."); + static_assert(TModels::Value, "TMap must have a hashable KeyType unless a custom key func is provided."); }; /** diff --git a/Engine/Source/Runtime/Core/Public/Containers/Set.h b/Engine/Source/Runtime/Core/Public/Containers/Set.h index 81a2d1fa4d9e..3c150cd17240 100644 --- a/Engine/Source/Runtime/Core/Public/Containers/Set.h +++ b/Engine/Source/Runtime/Core/Public/Containers/Set.h @@ -164,13 +164,10 @@ public: /** Initialization constructor. */ template ::Type>::Value>::Type> explicit FORCEINLINE TSetElement(InitType&& InValue) : Value(Forward(InValue)) {} - /** Copy/move constructors */ - FORCEINLINE TSetElement(const TSetElement& Rhs) : Value( Rhs.Value ), HashNextId( Rhs.HashNextId ), HashIndex(Rhs.HashIndex) {} - FORCEINLINE TSetElement( TSetElement&& Rhs) : Value(MoveTempIfPossible(Rhs.Value)), HashNextId(MoveTemp(Rhs.HashNextId)), HashIndex(Rhs.HashIndex) {} - - /** Copy/move assignment */ - FORCEINLINE TSetElement& operator=(const TSetElement& Rhs) { Value = Rhs.Value ; HashNextId = Rhs.HashNextId ; HashIndex = Rhs.HashIndex; return *this; } - FORCEINLINE TSetElement& operator=( TSetElement&& Rhs) { Value = MoveTempIfPossible(Rhs.Value); HashNextId = MoveTemp(Rhs.HashNextId); HashIndex = Rhs.HashIndex; return *this; } + TSetElement(TSetElement&&) = default; + TSetElement(const TSetElement&) = default; + TSetElement& operator=(TSetElement&&) = default; + TSetElement& operator=(const TSetElement&) = default; /** Serializer. */ FORCEINLINE friend FArchive& operator<<(FArchive& Ar,TSetElement& Element) diff --git a/Engine/Source/Runtime/Core/Public/Containers/UnrealString.h b/Engine/Source/Runtime/Core/Public/Containers/UnrealString.h index 7db68f9731dc..31316ab1d3ff 100644 --- a/Engine/Source/Runtime/Core/Public/Containers/UnrealString.h +++ b/Engine/Source/Runtime/Core/Public/Containers/UnrealString.h @@ -22,6 +22,7 @@ #include "Templates/IsValidVariadicFunctionArg.h" #include "Templates/AndOrNot.h" #include "Templates/IsArrayOrRefOfType.h" +#include "Templates/TypeHash.h" struct FStringFormatArg; template class TMap; @@ -1167,7 +1168,8 @@ public: FORCEINLINE FString Mid( int32 Start, int32 Count=MAX_int32 ) const { check(Count >= 0); - uint32 End = Start+Count; + uint32 End = Count; + End += Start; Start = FMath::Clamp( (uint32)Start, (uint32)0, (uint32)Len() ); End = FMath::Clamp( (uint32)End, (uint32)Start, (uint32)Len() ); return FString( End-Start, **this + Start ); @@ -2378,4 +2380,65 @@ CORE_API int32 FindMatchingClosingParenthesis(const FString& TargetString, const */ CORE_API FString SlugStringForValidName(const FString& DisplayString, const TCHAR* ReplaceWith = TEXT("")); +struct CORE_API FTextRange +{ + FTextRange() + : BeginIndex(INDEX_NONE) + , EndIndex(INDEX_NONE) + { + + } + + FTextRange(int32 InBeginIndex, int32 InEndIndex) + : BeginIndex(InBeginIndex) + , EndIndex(InEndIndex) + { + + } + + FORCEINLINE bool operator==(const FTextRange& Other) const + { + return BeginIndex == Other.BeginIndex + && EndIndex == Other.EndIndex; + } + + FORCEINLINE bool operator!=(const FTextRange& Other) const + { + return !(*this == Other); + } + + friend inline uint32 GetTypeHash(const FTextRange& Key) + { + uint32 KeyHash = 0; + KeyHash = HashCombine(KeyHash, GetTypeHash(Key.BeginIndex)); + KeyHash = HashCombine(KeyHash, GetTypeHash(Key.EndIndex)); + return KeyHash; + } + + int32 Len() const { return EndIndex - BeginIndex; } + bool IsEmpty() const { return (EndIndex - BeginIndex) <= 0; } + void Offset(int32 Amount) { BeginIndex += Amount; BeginIndex = FMath::Max(0, BeginIndex); EndIndex += Amount; EndIndex = FMath::Max(0, EndIndex); } + bool Contains(int32 Index) const { return Index >= BeginIndex && Index < EndIndex; } + bool InclusiveContains(int32 Index) const { return Index >= BeginIndex && Index <= EndIndex; } + + FTextRange Intersect(const FTextRange& Other) const + { + FTextRange Intersected(FMath::Max(BeginIndex, Other.BeginIndex), FMath::Min(EndIndex, Other.EndIndex)); + if (Intersected.EndIndex <= Intersected.BeginIndex) + { + return FTextRange(0, 0); + } + + return Intersected; + } + + /** + * Produce an array of line ranges from the given text, breaking at any new-line characters + */ + static void CalculateLineRangesFromString(const FString& Input, TArray& LineRanges); + + int32 BeginIndex; + int32 EndIndex; +}; + #include "Misc/StringFormatArg.h" diff --git a/Engine/Source/Runtime/Core/Public/CoreGlobals.h b/Engine/Source/Runtime/Core/Public/CoreGlobals.h index 4720c209ecf4..afa64cf423b9 100644 --- a/Engine/Source/Runtime/Core/Public/CoreGlobals.h +++ b/Engine/Source/Runtime/Core/Public/CoreGlobals.h @@ -7,6 +7,7 @@ #include "Logging/LogMacros.h" #include "HAL/PlatformTLS.h" #include "Templates/Atomic.h" +#include "ProfilingDebugging/CpuProfilerTrace.h" class Error; class FConfigCacheIni; @@ -70,7 +71,7 @@ struct CORE_API FScopedBootTiming }; -#define SCOPED_BOOT_TIMING(x) FScopedBootTiming ANONYMOUS_VARIABLE(BootTiming_)(x); +#define SCOPED_BOOT_TIMING(x) TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT(x)); FScopedBootTiming ANONYMOUS_VARIABLE(BootTiming_)(x); #define GLog GetGlobalLogSingleton() extern CORE_API FConfigCacheIni* GConfig; diff --git a/Engine/Source/Runtime/Core/Public/Delegates/DelegateSignatureImpl.inl b/Engine/Source/Runtime/Core/Public/Delegates/DelegateSignatureImpl.inl index 1afd5cbd2bdd..78560b8c49f5 100644 --- a/Engine/Source/Runtime/Core/Public/Delegates/DelegateSignatureImpl.inl +++ b/Engine/Source/Runtime/Core/Public/Delegates/DelegateSignatureImpl.inl @@ -8,14 +8,15 @@ #pragma once #include "CoreTypes.h" #include "Misc/AssertionMacros.h" -#include "Templates/TypeWrapper.h" -#include "Templates/RemoveReference.h" -#include "Templates/UnrealTemplate.h" #include "Misc/Crc.h" #include "UObject/NameTypes.h" -#include "UObject/WeakObjectPtrTemplates.h" -#include "Templates/SharedPointer.h" #include "UObject/ScriptDelegates.h" +#include "UObject/WeakObjectPtrTemplates.h" +#include "Templates/IsConst.h" +#include "Templates/RemoveReference.h" +#include "Templates/SharedPointer.h" +#include "Templates/TypeWrapper.h" +#include "Templates/UnrealTemplate.h" class FDelegateBase; class FDelegateHandle; @@ -158,6 +159,8 @@ public: inline static TBaseDelegate CreateRaw(UserClass* InUserObject, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) FUNCTION_CHECK_RETURN_END { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + TBaseDelegate Result; TBaseRawMethodDelegateInstance::Create(Result, InUserObject, InFunc, Vars...); return Result; @@ -183,6 +186,8 @@ public: inline static TBaseDelegate CreateSP(const TSharedRef& InUserObjectRef, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) FUNCTION_CHECK_RETURN_END { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + TBaseDelegate Result; TBaseSPMethodDelegateInstance::Create(Result, InUserObjectRef, InFunc, Vars...); return Result; @@ -208,6 +213,8 @@ public: inline static TBaseDelegate CreateSP(UserClass* InUserObject, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) FUNCTION_CHECK_RETURN_END { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + return CreateSP(StaticCastSharedRef(InUserObject->AsShared()), InFunc, Vars...); } template @@ -229,6 +236,8 @@ public: inline static TBaseDelegate CreateThreadSafeSP(const TSharedRef& InUserObjectRef, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) FUNCTION_CHECK_RETURN_END { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + TBaseDelegate Result; TBaseSPMethodDelegateInstance::Create(Result, InUserObjectRef, InFunc, Vars...); return Result; @@ -254,6 +263,8 @@ public: inline static TBaseDelegate CreateThreadSafeSP(UserClass* InUserObject, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) FUNCTION_CHECK_RETURN_END { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + return CreateThreadSafeSP(StaticCastSharedRef(InUserObject->AsShared()), InFunc, Vars...); } template @@ -291,6 +302,8 @@ public: inline static TBaseDelegate CreateUObject(UserClass* InUserObject, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) FUNCTION_CHECK_RETURN_END { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + TBaseDelegate Result; TBaseUObjectMethodDelegateInstance::Create(Result, InUserObject, InFunc, Vars...); return Result; @@ -439,6 +452,8 @@ public: template inline void BindRaw(UserClass* InUserObject, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + *this = CreateRaw(InUserObject, InFunc, Vars...); } template @@ -453,6 +468,8 @@ public: template inline void BindSP(const TSharedRef& InUserObjectRef, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + *this = CreateSP(InUserObjectRef, InFunc, Vars...); } template @@ -470,6 +487,8 @@ public: template inline void BindSP(UserClass* InUserObject, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + *this = CreateSP(InUserObject, InFunc, Vars...); } template @@ -487,6 +506,8 @@ public: template inline void BindThreadSafeSP(const TSharedRef& InUserObjectRef, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + *this = CreateThreadSafeSP(InUserObjectRef, InFunc, Vars...); } template @@ -504,6 +525,8 @@ public: template inline void BindThreadSafeSP(UserClass* InUserObject, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + *this = CreateThreadSafeSP(InUserObject, InFunc, Vars...); } template @@ -533,6 +556,8 @@ public: template inline void BindUObject(UserClass* InUserObject, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + *this = CreateUObject(InUserObject, InFunc, Vars...); } template @@ -764,6 +789,8 @@ public: template inline FDelegateHandle AddRaw(UserClass* InUserObject, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + return Add(FDelegate::CreateRaw(InUserObject, InFunc, Vars...)); } template @@ -783,6 +810,8 @@ public: template inline FDelegateHandle AddSP(const TSharedRef& InUserObjectRef, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + return Add(FDelegate::CreateSP(InUserObjectRef, InFunc, Vars...)); } template @@ -802,6 +831,8 @@ public: template inline FDelegateHandle AddSP(UserClass* InUserObject, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + return Add(FDelegate::CreateSP(InUserObject, InFunc, Vars...)); } template @@ -819,6 +850,8 @@ public: template inline FDelegateHandle AddThreadSafeSP(const TSharedRef& InUserObjectRef, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + return Add(FDelegate::CreateThreadSafeSP(InUserObjectRef, InFunc, Vars...)); } template @@ -838,6 +871,8 @@ public: template inline FDelegateHandle AddThreadSafeSP(UserClass* InUserObject, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + return Add(FDelegate::CreateThreadSafeSP(InUserObject, InFunc, Vars...)); } template @@ -871,6 +906,8 @@ public: template inline FDelegateHandle AddUObject(UserClass* InUserObject, typename TMemFunPtrType::Type InFunc, VarTypes... Vars) { + UE_STATIC_DEPRECATE(4.23, TIsConst::Value, "Binding a delegate with a const object pointer and non-const function is deprecated."); + return Add(FDelegate::CreateUObject(InUserObject, InFunc, Vars...)); } template @@ -887,13 +924,16 @@ public: * Note that the order of the delegate instances may not be preserved! * * @param Handle The handle of the delegate instance to remove. + * @return true if the delegate was successfully removed. */ - void Remove( FDelegateHandle Handle ) + bool Remove( FDelegateHandle Handle ) { + bool bResult = false; if (Handle.IsValid()) { - RemoveDelegateInstance(Handle); + bResult = RemoveDelegateInstance(Handle); } + return bResult; } protected: @@ -992,12 +1032,10 @@ protected: /** * Removes a function from this multi-cast delegate's invocation list (performance is O(N)). * - * The function is not actually removed, but deleted and marked as removed. - * It will be removed next time the invocation list is compacted within Broadcast(). - * * @param Handle The handle of the delegate instance to remove. + * @return true if the delegate was successfully removed. */ - void RemoveDelegateInstance( FDelegateHandle Handle ) + bool RemoveDelegateInstance( FDelegateHandle Handle ) { const TInvocationList& LocalInvocationList = Super::GetInvocationList(); @@ -1011,12 +1049,12 @@ protected: if ((DelegateInstanceInterface != nullptr) && DelegateInstanceInterface->GetHandle() == Handle) { DelegateBase.Unbind(); - - break; // each delegate binding has a unique handle, so once we find it, we can stop + Super::CompactInvocationList(); + return true; // each delegate binding has a unique handle, so once we find it, we can stop } } - Super::CompactInvocationList(); + return false; } }; diff --git a/Engine/Source/Runtime/Core/Public/Delegates/MulticastDelegateBase.h b/Engine/Source/Runtime/Core/Public/Delegates/MulticastDelegateBase.h index 7675d910f3a9..7cecceaa0846 100644 --- a/Engine/Source/Runtime/Core/Public/Delegates/MulticastDelegateBase.h +++ b/Engine/Source/Runtime/Core/Public/Delegates/MulticastDelegateBase.h @@ -81,12 +81,13 @@ public: * Note that the order of the delegates may not be preserved! * * @param InUserObject The object to remove all delegates for. + * @return The number of delegates successfully removed. */ - void RemoveAll( const void* InUserObject ) + int32 RemoveAll( const void* InUserObject ) { + int32 Result = 0; if (InvocationListLockCount > 0) { - bool NeedsCompacted = false; for (FDelegateBase& DelegateBaseRef : InvocationList) { IDelegateInstance* DelegateInstance = DelegateBaseRef.GetDelegateInstanceProtected(); @@ -94,12 +95,12 @@ public: { // Manually unbind the delegate here so the compaction will find and remove it. DelegateBaseRef.Unbind(); - NeedsCompacted = true; + ++Result; } } // can't compact at the moment, but set out threshold to zero so the next add will do it - if (NeedsCompacted) + if (Result > 0) { CompactionThreshold = 0; } @@ -117,6 +118,7 @@ public: || DelegateInstance->IsCompactable()) { InvocationList.RemoveAtSwap(InvocationListIndex, 1, false); + ++Result; } else { @@ -128,6 +130,8 @@ public: InvocationList.Shrink(); } + + return Result; } protected: diff --git a/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformAtomics.h b/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformAtomics.h index 97384beb7348..cacd092d751c 100644 --- a/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformAtomics.h +++ b/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformAtomics.h @@ -141,7 +141,7 @@ struct FGenericPlatformAtomics /** * Atomically swaps two pointers returning the original pointer to the caller */ - static FORCEINLINE void* InterlockedExchangePtr( void** Dest, void* Exchange ) + static FORCEINLINE void* InterlockedExchangePtr( void*volatile* Dest, void* Exchange ) { #if PLATFORM_64BITS #error must implement @@ -187,6 +187,48 @@ struct FGenericPlatformAtomics #error must implement } + /** + * Atomically ands AndValue to the value pointed to and returns the old + * value to the caller. Implemented for int8, int16 and int64 as well. + */ + static FORCEINLINE int32 InterlockedAnd(volatile int32* Value, const int32 AndValue) + { + int32 RetVal; + do + { + RetVal = *Value; + } while (InterlockedCompareExchange((int32*)Value, RetVal & AndValue, RetVal) != RetVal); + return RetVal; + } + + /** + * Atomically ors OrValue to the value pointed to and returns the old + * value to the caller. Implemented for int8, int16 and int64 as well. + */ + static FORCEINLINE int32 InterlockedOr(volatile int32* Value, const int32 OrValue) + { + int32 RetVal; + do + { + RetVal = *Value; + } while (InterlockedCompareExchange((int32*)Value, RetVal | OrValue, RetVal) != RetVal); + return RetVal; + } + + /** + * Atomically xors XorValue to the value pointed to and returns the old + * value to the caller. Implemented for int8, int16 and int64 as well. + */ + static FORCEINLINE int32 InterlockedXor(volatile int32* Value, const int32 XorValue) + { + int32 RetVal; + do + { + RetVal = *Value; + } while (InterlockedCompareExchange((int32*)Value, RetVal ^ XorValue, RetVal) != RetVal); + return RetVal; + } + /** * Atomic read of 32 bit value with an implicit memory barrier. */ @@ -232,7 +274,7 @@ struct FGenericPlatformAtomics * Atomically compares the pointer to comparand and replaces with the exchange * pointer if they are equal and returns the original value */ - static FORCEINLINE void* InterlockedCompareExchangePointer(void** Dest,void* Exchange,void* Comperand) + static FORCEINLINE void* InterlockedCompareExchangePointer(void*volatile* Dest,void* Exchange,void* Comperand) { #if PLATFORM_64BITS #error must implement diff --git a/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformCompilerPreSetup.h b/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformCompilerPreSetup.h index 44db4111d6e4..c3815c8ce442 100644 --- a/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformCompilerPreSetup.h +++ b/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformCompilerPreSetup.h @@ -87,3 +87,11 @@ #ifndef DEPRECATED_MACRO #define DEPRECATED_MACRO(Version, Message) EMIT_CUSTOM_WARNING(Message " Please update your code to the new API before upgrading to the next release, otherwise your project will no longer compile.") #endif + +#ifndef PRAGMA_DEFAULT_VISIBILITY_START + #define PRAGMA_DEFAULT_VISIBILITY_START +#endif + +#ifndef PRAGMA_DEFAULT_VISIBILITY_END + #define PRAGMA_DEFAULT_VISIBILITY_END +#endif diff --git a/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformOutputDevices.h b/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformOutputDevices.h index f16734d8e476..832ca935991b 100644 --- a/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformOutputDevices.h +++ b/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformOutputDevices.h @@ -27,5 +27,6 @@ struct CORE_API FGenericPlatformOutputDevices static FFeedbackContext* GetFeedbackContext(); protected: - static TCHAR CachedAbsoluteFilename[1024]; + static const SIZE_T AbsoluteFileNameMaxLength = 1024; + static TCHAR CachedAbsoluteFilename[AbsoluteFileNameMaxLength]; }; diff --git a/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformString.h b/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformString.h index 012d0f5c9b04..2a819e75eeb7 100644 --- a/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformString.h +++ b/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformString.h @@ -145,34 +145,29 @@ struct FGenericPlatformString : public FGenericPlatformStricmp DestEncoding* >::Type Convert(DestEncoding* Dest, int32 DestSize, const SourceEncoding* Src, int32 SrcSize, DestEncoding BogusChar = (DestEncoding)'?') { - const SourceEncoding* InSrc = Src; - int32 InSrcSize = SrcSize; - bool bInvalidChars = false; - while (SrcSize) + const int32 Size = DestSize <= SrcSize ? DestSize : SrcSize; + bool bInvalidChars = false; + for (int I = 0; I < Size; ++I) { - if (!DestSize) - return nullptr; - - SourceEncoding SrcCh = *Src++; - if (CanConvertChar(SrcCh)) - { - *Dest++ = (DestEncoding)SrcCh; - } - else - { - *Dest++ = BogusChar; - bInvalidChars = true; - } - --SrcSize; - --DestSize; + SourceEncoding SrcCh = Src[I]; + Dest[I] = (DestEncoding)SrcCh; + bInvalidChars |= !CanConvertChar(SrcCh); } if (bInvalidChars) { - LogBogusChars(InSrc, InSrcSize); + for (int I = 0; I < Size; ++I) + { + if (!CanConvertChar(Src[I])) + { + Dest[I] = BogusChar; + } + } + + LogBogusChars(Src, Size); } - return Dest; + return DestSize < SrcSize ? nullptr : Dest + Size; } /** diff --git a/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericWidePlatformString.h b/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericWidePlatformString.h index dc49ad727edd..31b2ca25d7b0 100644 --- a/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericWidePlatformString.h +++ b/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericWidePlatformString.h @@ -13,7 +13,7 @@ /** * Standard implementation **/ -struct FGenericWidePlatformString : public FGenericPlatformString +struct CORE_VTABLE FGenericWidePlatformString : public FGenericPlatformString { template static inline CharType* Strupr(CharType* Dest, SIZE_T DestCount) @@ -30,11 +30,11 @@ public: /** * Unicode implementation **/ - static WIDECHAR* Strcpy(WIDECHAR* Dest, SIZE_T DestCount, const WIDECHAR* Src); - static WIDECHAR* Strncpy(WIDECHAR* Dest, const WIDECHAR* Src, SIZE_T MaxLen); - static WIDECHAR* Strcat(WIDECHAR* Dest, SIZE_T DestCount, const WIDECHAR* Src); + CORE_API static WIDECHAR* Strcpy(WIDECHAR* Dest, SIZE_T DestCount, const WIDECHAR* Src); + CORE_API static WIDECHAR* Strncpy(WIDECHAR* Dest, const WIDECHAR* Src, SIZE_T MaxLen); + CORE_API static WIDECHAR* Strcat(WIDECHAR* Dest, SIZE_T DestCount, const WIDECHAR* Src); - static int32 Strcmp( const WIDECHAR* String1, const WIDECHAR* String2 ) + CORE_API static int32 Strcmp( const WIDECHAR* String1, const WIDECHAR* String2 ) { // walk the strings, comparing them case sensitively for (; *String1 || *String2; String1++, String2++) @@ -48,7 +48,7 @@ public: return 0; } - static int32 Strncmp( const WIDECHAR* String1, const WIDECHAR* String2, SIZE_T Count ) + CORE_API static int32 Strncmp( const WIDECHAR* String1, const WIDECHAR* String2, SIZE_T Count ) { // walk the strings, comparing them case sensitively, up to a max size for (; (*String1 || *String2) && Count; String1++, String2++, Count--) @@ -62,7 +62,7 @@ public: return 0; } - static int32 Strlen( const WIDECHAR* String ) + CORE_API static int32 Strlen( const WIDECHAR* String ) { int32 Length = -1; @@ -75,6 +75,7 @@ public: return Length; } + #if PLATFORM_TCHAR_IS_CHAR16 static int32 Strlen( const wchar_t* String ) { @@ -89,8 +90,8 @@ public: return Length; } #endif - - static const WIDECHAR* Strstr( const WIDECHAR* String, const WIDECHAR* Find) + + CORE_API static const WIDECHAR* Strstr( const WIDECHAR* String, const WIDECHAR* Find) { WIDECHAR Char1, Char2; if ((Char1 = *Find++) != 0) @@ -116,7 +117,7 @@ public: return String; } - static const WIDECHAR* Strchr( const WIDECHAR* String, WIDECHAR C) + CORE_API static const WIDECHAR* Strchr( const WIDECHAR* String, WIDECHAR C) { while (*String != C && *String != 0) { @@ -126,7 +127,7 @@ public: return (*String == C) ? String : nullptr; } - static const WIDECHAR* Strrchr( const WIDECHAR* String, WIDECHAR C) + CORE_API static const WIDECHAR* Strrchr( const WIDECHAR* String, WIDECHAR C) { const WIDECHAR *Last = nullptr; @@ -148,28 +149,28 @@ public: return Last; } - static int32 Strtoi( const WIDECHAR* Start, WIDECHAR** End, int32 Base ); - static int64 Strtoi64( const WIDECHAR* Start, WIDECHAR** End, int32 Base ); - static uint64 Strtoui64( const WIDECHAR* Start, WIDECHAR** End, int32 Base ); - static float Atof(const WIDECHAR* String); - static double Atod(const WIDECHAR* String); + CORE_API static int32 Strtoi( const WIDECHAR* Start, WIDECHAR** End, int32 Base ); + CORE_API static int64 Strtoi64( const WIDECHAR* Start, WIDECHAR** End, int32 Base ); + CORE_API static uint64 Strtoui64( const WIDECHAR* Start, WIDECHAR** End, int32 Base ); + CORE_API static float Atof(const WIDECHAR* String); + CORE_API static double Atod(const WIDECHAR* String); - static FORCEINLINE int32 Atoi(const WIDECHAR* String) + CORE_API static FORCEINLINE int32 Atoi(const WIDECHAR* String) { return Strtoi( String, NULL, 10 ); } - static FORCEINLINE int64 Atoi64(const WIDECHAR* String) + CORE_API static FORCEINLINE int64 Atoi64(const WIDECHAR* String) { return Strtoi64( String, NULL, 10 ); } - static WIDECHAR* Strtok( WIDECHAR* StrToken, const WIDECHAR* Delim, WIDECHAR** Context ); + static CORE_API WIDECHAR* Strtok( WIDECHAR* StrToken, const WIDECHAR* Delim, WIDECHAR** Context ); UE_DEPRECATED(4.22, "GetVarArgs with DestSize and Count arguments has been deprecated - only DestSize should be passed") - static int32 GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, int32 Count, const WIDECHAR*& Fmt, va_list ArgPtr ) + static CORE_API int32 GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, int32 Count, const WIDECHAR*& Fmt, va_list ArgPtr ) { return GetVarArgs(Dest, DestSize, Fmt, ArgPtr); } @@ -179,100 +180,100 @@ public: /** * Ansi implementation **/ - static FORCEINLINE ANSICHAR* Strcpy(ANSICHAR* Dest, SIZE_T DestCount, const ANSICHAR* Src) + CORE_API static FORCEINLINE ANSICHAR* Strcpy(ANSICHAR* Dest, SIZE_T DestCount, const ANSICHAR* Src) { return strcpy( Dest, Src ); } - static FORCEINLINE ANSICHAR* Strncpy(ANSICHAR* Dest, const ANSICHAR* Src, int32 MaxLen) + CORE_API static FORCEINLINE ANSICHAR* Strncpy(ANSICHAR* Dest, const ANSICHAR* Src, int32 MaxLen) { ::strncpy(Dest, Src, MaxLen); Dest[MaxLen-1]=0; return Dest; } - static FORCEINLINE ANSICHAR* Strcat(ANSICHAR* Dest, SIZE_T DestCount, const ANSICHAR* Src) + CORE_API static FORCEINLINE ANSICHAR* Strcat(ANSICHAR* Dest, SIZE_T DestCount, const ANSICHAR* Src) { return strcat( Dest, Src ); } - static FORCEINLINE int32 Strcmp( const ANSICHAR* String1, const ANSICHAR* String2 ) + CORE_API static FORCEINLINE int32 Strcmp( const ANSICHAR* String1, const ANSICHAR* String2 ) { return strcmp(String1, String2); } - static FORCEINLINE int32 Strncmp( const ANSICHAR* String1, const ANSICHAR* String2, SIZE_T Count ) + CORE_API static FORCEINLINE int32 Strncmp( const ANSICHAR* String1, const ANSICHAR* String2, SIZE_T Count ) { return strncmp( String1, String2, Count ); } - static FORCEINLINE int32 Strlen( const ANSICHAR* String ) + CORE_API static FORCEINLINE int32 Strlen( const ANSICHAR* String ) { return strlen( String ); } - static FORCEINLINE const ANSICHAR* Strstr( const ANSICHAR* String, const ANSICHAR* Find) + CORE_API static FORCEINLINE const ANSICHAR* Strstr( const ANSICHAR* String, const ANSICHAR* Find) { return strstr(String, Find); } - static FORCEINLINE const ANSICHAR* Strchr( const ANSICHAR* String, ANSICHAR C) + CORE_API static FORCEINLINE const ANSICHAR* Strchr( const ANSICHAR* String, ANSICHAR C) { return strchr(String, C); } - static FORCEINLINE const ANSICHAR* Strrchr( const ANSICHAR* String, ANSICHAR C) + CORE_API static FORCEINLINE const ANSICHAR* Strrchr( const ANSICHAR* String, ANSICHAR C) { return strrchr(String, C); } - static FORCEINLINE int32 Atoi(const ANSICHAR* String) + CORE_API static FORCEINLINE int32 Atoi(const ANSICHAR* String) { return atoi( String ); } - static FORCEINLINE int64 Atoi64(const ANSICHAR* String) + CORE_API static FORCEINLINE int64 Atoi64(const ANSICHAR* String) { return strtoll( String, NULL, 10 ); } - static FORCEINLINE float Atof(const ANSICHAR* String) + CORE_API static FORCEINLINE float Atof(const ANSICHAR* String) { return (float)atof( String ); } - static FORCEINLINE double Atod(const ANSICHAR* String) + CORE_API static FORCEINLINE double Atod(const ANSICHAR* String) { return atof( String ); } - static FORCEINLINE int32 Strtoi( const ANSICHAR* Start, ANSICHAR** End, int32 Base ) + CORE_API static FORCEINLINE int32 Strtoi( const ANSICHAR* Start, ANSICHAR** End, int32 Base ) { return strtol( Start, End, Base ); } - static FORCEINLINE int64 Strtoi64( const ANSICHAR* Start, ANSICHAR** End, int32 Base ) + CORE_API static FORCEINLINE int64 Strtoi64( const ANSICHAR* Start, ANSICHAR** End, int32 Base ) { return strtoll(Start, End, Base); } - static FORCEINLINE uint64 Strtoui64( const ANSICHAR* Start, ANSICHAR** End, int32 Base ) + CORE_API static FORCEINLINE uint64 Strtoui64( const ANSICHAR* Start, ANSICHAR** End, int32 Base ) { return strtoull(Start, End, Base); } - static FORCEINLINE ANSICHAR* Strtok(ANSICHAR* StrToken, const ANSICHAR* Delim, ANSICHAR** Context) + CORE_API static FORCEINLINE ANSICHAR* Strtok(ANSICHAR* StrToken, const ANSICHAR* Delim, ANSICHAR** Context) { return strtok(StrToken, Delim); } UE_DEPRECATED(4.22, "GetVarArgs with DestSize and Count arguments has been deprecated - only DestSize should be passed") - static int32 GetVarArgs( ANSICHAR* Dest, SIZE_T DestSize, int32 Count, const ANSICHAR*& Fmt, va_list ArgPtr ) + static CORE_API int32 GetVarArgs( ANSICHAR* Dest, SIZE_T DestSize, int32 Count, const ANSICHAR*& Fmt, va_list ArgPtr ) { return GetVarArgs(Dest, DestSize, Fmt, ArgPtr); } - static int32 GetVarArgs( ANSICHAR* Dest, SIZE_T DestSize, const ANSICHAR*& Fmt, va_list ArgPtr ) + static CORE_API int32 GetVarArgs( ANSICHAR* Dest, SIZE_T DestSize, const ANSICHAR*& Fmt, va_list ArgPtr ) { int32 Result = vsnprintf(Dest, DestSize, Fmt, ArgPtr); va_end( ArgPtr ); @@ -283,7 +284,7 @@ public: * UCS2 implementation **/ - static FORCEINLINE int32 Strlen( const UCS2CHAR* String ) + CORE_API static FORCEINLINE int32 Strlen( const UCS2CHAR* String ) { int32 Result = 0; while (*String++) diff --git a/Engine/Source/Runtime/Core/Public/GenericPlatform/StandardPlatformString.h b/Engine/Source/Runtime/Core/Public/GenericPlatform/StandardPlatformString.h index 57bce54f18f6..4ae40f97dd4f 100644 --- a/Engine/Source/Runtime/Core/Public/GenericPlatform/StandardPlatformString.h +++ b/Engine/Source/Runtime/Core/Public/GenericPlatform/StandardPlatformString.h @@ -121,13 +121,13 @@ public: } UE_DEPRECATED(4.22, "GetVarArgs with DestSize and Count arguments has been deprecated - only DestSize should be passed") - static int32 GetVarArgs(WIDECHAR* Dest, SIZE_T DestSize, int32 Count, const WIDECHAR*& Fmt, va_list ArgPtr) + static CORE_API int32 GetVarArgs(WIDECHAR* Dest, SIZE_T DestSize, int32 Count, const WIDECHAR*& Fmt, va_list ArgPtr) { return GetVarArgs(Dest, DestSize, Fmt, ArgPtr); } #if PLATFORM_USE_SYSTEM_VSWPRINTF - static int32 GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, const WIDECHAR*& Fmt, va_list ArgPtr ) + static CORE_API int32 GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, const WIDECHAR*& Fmt, va_list ArgPtr ) { #if PLATFORM_USE_LS_SPEC_FOR_WIDECHAR // fix up the Fmt string, as fast as possible, without using an FString @@ -184,7 +184,7 @@ public: return Result; } #else // PLATFORM_USE_SYSTEM_VSWPRINTF - static int32 GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, const WIDECHAR*& Fmt, va_list ArgPtr ); + static CORE_API int32 GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, const WIDECHAR*& Fmt, va_list ArgPtr ); #endif // PLATFORM_USE_SYSTEM_VSWPRINTF /** @@ -278,12 +278,12 @@ public: } UE_DEPRECATED(4.22, "GetVarArgs with DestSize and Count arguments has been deprecated - only DestSize should be passed") - static int32 GetVarArgs( ANSICHAR* Dest, SIZE_T DestSize, int32 Count, const ANSICHAR*& Fmt, va_list ArgPtr ) + static CORE_API int32 GetVarArgs( ANSICHAR* Dest, SIZE_T DestSize, int32 Count, const ANSICHAR*& Fmt, va_list ArgPtr ) { return GetVarArgs(Dest, DestSize, Fmt, ArgPtr); } - static int32 GetVarArgs( ANSICHAR* Dest, SIZE_T DestSize, const ANSICHAR*& Fmt, va_list ArgPtr ) + static CORE_API int32 GetVarArgs( ANSICHAR* Dest, SIZE_T DestSize, const ANSICHAR*& Fmt, va_list ArgPtr ) { int32 Result = vsnprintf(Dest, DestSize, Fmt, ArgPtr); va_end( ArgPtr ); diff --git a/Engine/Source/Runtime/Core/Public/HAL/FMemory.inl b/Engine/Source/Runtime/Core/Public/HAL/FMemory.inl index 8e3f1a3e17ea..4866c44b1739 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/FMemory.inl +++ b/Engine/Source/Runtime/Core/Public/HAL/FMemory.inl @@ -33,9 +33,10 @@ FMEMORY_INLINE_FUNCTION_DECORATOR void* FMemory::Malloc(SIZE_T Count, uint32 Ali FMEMORY_INLINE_FUNCTION_DECORATOR void* FMemory::Realloc(void* Original, SIZE_T Count, uint32 Alignment) { - // optional tracking of every allocation + // optional tracking -- a realloc with an Original pointer of null is equivalent + // to malloc() so there's nothing to free LLM_REALLOC_SCOPE(Original); - LLM_IF_ENABLED(FLowLevelMemTracker::Get().OnLowLevelFree(ELLMTracker::Default, Original, ELLMAllocType::FMalloc)); + LLM_IF_ENABLED(if (Original != nullptr) FLowLevelMemTracker::Get().OnLowLevelFree(ELLMTracker::Default, Original, ELLMAllocType::FMalloc)); void* Ptr; if (!FMEMORY_INLINE_GMalloc) @@ -49,8 +50,10 @@ FMEMORY_INLINE_FUNCTION_DECORATOR void* FMemory::Realloc(void* Original, SIZE_T Ptr = FMEMORY_INLINE_GMalloc->Realloc(Original, Count, Alignment); } - // optional tracking of every allocation - LLM_IF_ENABLED(FLowLevelMemTracker::Get().OnLowLevelAlloc(ELLMTracker::Default, Ptr, Count, ELLMTag::Untagged, ELLMAllocType::FMalloc)); + // optional tracking of every allocation - a realloc with a Count of zero is equivalent to a call + // to free() and will return a null pointer which does not require tracking. If realloc returns null + // for some other reason (like failure to allocate) there's also no reason to track it + LLM_IF_ENABLED(if (Ptr != nullptr) FLowLevelMemTracker::Get().OnLowLevelAlloc(ELLMTracker::Default, Ptr, Count, ELLMTag::Untagged, ELLMAllocType::FMalloc)); return Ptr; } diff --git a/Engine/Source/Runtime/Core/Public/HAL/IConsoleManager.h b/Engine/Source/Runtime/Core/Public/HAL/IConsoleManager.h index d687eb242f38..329b05ed20dc 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/IConsoleManager.h +++ b/Engine/Source/Runtime/Core/Public/HAL/IConsoleManager.h @@ -214,6 +214,8 @@ public: } virtual bool IsVariableInt() const { return false; } + virtual bool IsVariableFloat() const { return false; } + virtual bool IsVariableString() const { return false; } virtual class TConsoleVariableData* AsVariableInt() { @@ -1226,5 +1228,25 @@ public: }; +/** + * Autoregistering console command with a world, arguments, and output device + */ +class CORE_API FAutoConsoleCommandWithWorldArgsAndOutputDevice : private FAutoConsoleObject +{ +public: + /** + * Register a console command that takes arguments, a world argument and an output device + * + * @param Name The name of this command (must not be nullptr) + * @param Help Help text for this command + * @param Command The user function to call when this command is executed + * @param Flags Optional flags bitmask + */ + FAutoConsoleCommandWithWorldArgsAndOutputDevice(const TCHAR* Name, const TCHAR* Help, const FConsoleCommandWithWorldArgsAndOutputDeviceDelegate& Command, uint32 Flags = ECVF_Default) + : FAutoConsoleObject(IConsoleManager::Get().RegisterConsoleCommand(Name, Help, Command, Flags)) + { + } +}; + CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogConsoleResponse, Log, All); diff --git a/Engine/Source/Runtime/Core/Public/HAL/LowLevelMemTracker.h b/Engine/Source/Runtime/Core/Public/HAL/LowLevelMemTracker.h index 4a8dac8fa60f..c5b412dd8320 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/LowLevelMemTracker.h +++ b/Engine/Source/Runtime/Core/Public/HAL/LowLevelMemTracker.h @@ -287,8 +287,8 @@ extern FName LLMGetTagStat(ELLMTag Tag); /** * LLM Pause scope macros */ -#define LLM_SCOPED_PAUSE_TRACKING(AllocType) FLLMPauseScope SCOPE_NAME(NAME_None, 0, ELLMTracker::Max, AllocType); -#define LLM_SCOPED_PAUSE_TRACKING_FOR_TRACKER(Tracker, AllocType) FLLMPauseScope SCOPE_NAME(NAME_None, 0, Tracker, AllocType); +#define LLM_SCOPED_PAUSE_TRACKING(AllocType) FLLMPauseScope SCOPE_NAME(ELLMTag::Untagged, 0, ELLMTracker::Max, AllocType); +#define LLM_SCOPED_PAUSE_TRACKING_FOR_TRACKER(Tracker, AllocType) FLLMPauseScope SCOPE_NAME(ELLMTag::Untagged, 0, Tracker, AllocType); #define LLM_SCOPED_PAUSE_TRACKING_WITH_ENUM_AND_AMOUNT(Tag, Amount, Tracker, AllocType) FLLMPauseScope SCOPE_NAME(Tag, Amount, Tracker, AllocType); /** diff --git a/Engine/Source/Runtime/Core/Public/HAL/MallocBinnedArena.h b/Engine/Source/Runtime/Core/Public/HAL/MallocBinnedArena.h index e05555172240..e857dbb14bfd 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/MallocBinnedArena.h +++ b/Engine/Source/Runtime/Core/Public/HAL/MallocBinnedArena.h @@ -6,6 +6,7 @@ #if PLATFORM_64BITS && PLATFORM_HAS_FPlatformVirtualMemoryBlock #include "Misc/AssertionMacros.h" +#include "Misc/ScopeLock.h" #include "HAL/MemoryBase.h" #include "HAL/UnrealMemory.h" #include "Math/NumericLimits.h" diff --git a/Engine/Source/Runtime/Core/Public/HAL/MallocBinnedGPU.h b/Engine/Source/Runtime/Core/Public/HAL/MallocBinnedGPU.h index c55781fec9e8..6ca0f3cb67b9 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/MallocBinnedGPU.h +++ b/Engine/Source/Runtime/Core/Public/HAL/MallocBinnedGPU.h @@ -6,6 +6,7 @@ #if PLATFORM_64BITS && PLATFORM_HAS_FPlatformVirtualMemoryBlock #include "Misc/AssertionMacros.h" +#include "Misc/ScopeLock.h" #include "HAL/MemoryBase.h" #include "HAL/UnrealMemory.h" #include "Math/NumericLimits.h" diff --git a/Engine/Source/Runtime/Core/Public/HAL/PThreadEvent.h b/Engine/Source/Runtime/Core/Public/HAL/PThreadEvent.h index b2009d52cca6..b0d40fd3e88e 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/PThreadEvent.h +++ b/Engine/Source/Runtime/Core/Public/HAL/PThreadEvent.h @@ -8,7 +8,7 @@ /** * This is the PThreads version of FEvent. */ -class FPThreadEvent +class CORE_VTABLE FPThreadEvent : public FEvent { // This is a little complicated, in an attempt to match Win32 Event semantics... diff --git a/Engine/Source/Runtime/Core/Public/HAL/Platform.h b/Engine/Source/Runtime/Core/Public/HAL/Platform.h index 93f4077534b1..484740662380 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/Platform.h +++ b/Engine/Source/Runtime/Core/Public/HAL/Platform.h @@ -688,6 +688,11 @@ #define DLLIMPORT #endif +#ifndef DLLEXPORT_VTABLE + #define DLLEXPORT_VTABLE + #define DLLIMPORT_VTABLE +#endif + // embedded app is not default (embedding UE4 in a native view, right now just for IOS and Android) #ifndef BUILD_EMBEDDED_APP #define BUILD_EMBEDDED_APP 0 diff --git a/Engine/Source/Runtime/Core/Public/HAL/PlatformMisc.h b/Engine/Source/Runtime/Core/Public/HAL/PlatformMisc.h index 45ad33607f67..97d8180fcccd 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/PlatformMisc.h +++ b/Engine/Source/Runtime/Core/Public/HAL/PlatformMisc.h @@ -30,6 +30,8 @@ #if ENABLE_NAMED_EVENTS +#include "ProfilingDebugging/CpuProfilerTrace.h" + class CORE_API FScopedNamedEvent { public: @@ -110,9 +112,16 @@ public: #define NAMED_EVENT_STR(x) L##x #endif -#define SCOPED_NAMED_EVENT(Name, Color) FScopedNamedEventStatic ANONYMOUS_VARIABLE(NamedEvent_##Name##_)(Color, NAMED_EVENT_STR(#Name)); +#define SCOPED_NAMED_EVENT(Name, Color) \ + TRACE_CPUPROFILER_EVENT_SCOPE(Name) \ + FScopedNamedEventStatic ANONYMOUS_VARIABLE(NamedEvent_##Name##_)(Color, NAMED_EVENT_STR(#Name)); + #define SCOPED_NAMED_EVENT_FSTRING(Text, Color) FScopedNamedEvent ANONYMOUS_VARIABLE(NamedEvent_) (Color, *Text); -#define SCOPED_NAMED_EVENT_TEXT(Text, Color) FScopedNamedEventStatic ANONYMOUS_VARIABLE(NamedEvent_) (Color, NAMED_EVENT_STR(Text)); + +#define SCOPED_NAMED_EVENT_TEXT(Text, Color) \ + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT(Text)) \ + FScopedNamedEventStatic ANONYMOUS_VARIABLE(NamedEvent_) (Color, NAMED_EVENT_STR(Text)); + #define SCOPED_NAMED_EVENT_F(Format, Color, ...) FScopedNamedEvent ANONYMOUS_VARIABLE(NamedEvent_) (Color, *FString::Printf(Format, __VA_ARGS__)); #define SCOPED_PROFILER_COLOR(Color) FScopedProfilerColor ANONYMOUS_VARIABLE(ProfilerColor_##Name##_)(Color); diff --git a/Engine/Source/Runtime/Core/Public/HAL/ThreadSingleton.h b/Engine/Source/Runtime/Core/Public/HAL/ThreadSingleton.h index f7b0242f0452..056cb10f6aeb 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/ThreadSingleton.h +++ b/Engine/Source/Runtime/Core/Public/HAL/ThreadSingleton.h @@ -35,6 +35,16 @@ public: template < class T > class TThreadSingleton : public FTlsAutoCleanup { +#if PLATFORM_UNIX || PLATFORM_APPLE + /** + * @return TLS slot that holds a TThreadSingleton. + */ + CORE_API static uint32& GetTlsSlot() + { + static uint32 TlsSlot = 0xFFFFFFFF; + return TlsSlot; + } +#else /** * @return TLS slot that holds a TThreadSingleton. */ @@ -43,6 +53,7 @@ class TThreadSingleton : public FTlsAutoCleanup static uint32 TlsSlot = 0xFFFFFFFF; return TlsSlot; } +#endif protected: @@ -79,5 +90,4 @@ public: { return (T*)FThreadSingletonInitializer::TryGet( T::GetTlsSlot() ); } -}; - +}; \ No newline at end of file diff --git a/Engine/Source/Runtime/Core/Public/HTML5/HTML5PlatformAtomics.h b/Engine/Source/Runtime/Core/Public/HTML5/HTML5PlatformAtomics.h index d391603a8a26..3d5c65a2f9cb 100644 --- a/Engine/Source/Runtime/Core/Public/HTML5/HTML5PlatformAtomics.h +++ b/Engine/Source/Runtime/Core/Public/HTML5/HTML5PlatformAtomics.h @@ -105,7 +105,7 @@ struct CORE_API FHTML5PlatformAtomics : public FGenericPlatformAtomics // return __sync_lock_test_and_set(Value, Exchange); } - static FORCEINLINE void* InterlockedExchangePtr(void** Dest, void* Exchange) + static FORCEINLINE void* InterlockedExchangePtr(void*volatile* Dest, void* Exchange) { return __sync_lock_test_and_set(Dest, Exchange); } @@ -131,6 +131,66 @@ struct CORE_API FHTML5PlatformAtomics : public FGenericPlatformAtomics // return __sync_val_compare_and_swap(Dest, Comperand, Exchange); } + static FORCEINLINE int8 InterlockedAnd(volatile int8* Value, const int8 AndValue) + { + return __sync_fetch_and_and(Value, AndValue); + } + + static FORCEINLINE int16 InterlockedAnd(volatile int16* Value, const int16 AndValue) + { + return __sync_fetch_and_and(Value, AndValue); + } + + static FORCEINLINE int32 InterlockedAnd(volatile int32* Value, const int32 AndValue) + { + return __sync_fetch_and_and(Value, AndValue); + } + + static FORCEINLINE int64 InterlockedAnd(volatile int64* Value, const int64 AndValue) + { + return __sync_fetch_and_and(Value, AndValue); + } + + static FORCEINLINE int8 InterlockedOr(volatile int8* Value, const int8 OrValue) + { + return __sync_fetch_and_or(Value, OrValue); + } + + static FORCEINLINE int16 InterlockedOr(volatile int16* Value, const int16 OrValue) + { + return __sync_fetch_and_or(Value, OrValue); + } + + static FORCEINLINE int32 InterlockedOr(volatile int32* Value, const int32 OrValue) + { + return __sync_fetch_and_or(Value, OrValue); + } + + static FORCEINLINE int64 InterlockedOr(volatile int64* Value, const int64 OrValue) + { + return __sync_fetch_and_or(Value, OrValue); + } + + static FORCEINLINE int8 InterlockedXor(volatile int8* Value, const int8 XorValue) + { + return __sync_fetch_and_xor(Value, XorValue); + } + + static FORCEINLINE int16 InterlockedXor(volatile int16* Value, const int16 XorValue) + { + return __sync_fetch_and_xor(Value, XorValue); + } + + static FORCEINLINE int32 InterlockedXor(volatile int32* Value, const int32 XorValue) + { + return __sync_fetch_and_xor(Value, XorValue); + } + + static FORCEINLINE int64 InterlockedXor(volatile int64* Value, const int64 XorValue) + { + return __sync_fetch_and_xor(Value, XorValue); + } + static FORCEINLINE int8 AtomicRead(volatile const int8* Src) { int8 Result; @@ -227,7 +287,7 @@ struct CORE_API FHTML5PlatformAtomics : public FGenericPlatformAtomics __atomic_store((volatile int64*)Src, &Val, __ATOMIC_RELAXED); } - static FORCEINLINE void* InterlockedCompareExchangePointer(void** Dest, void* Exchange, void* Comperand) + static FORCEINLINE void* InterlockedCompareExchangePointer(void*volatile* Dest, void* Exchange, void* Comperand) { return __sync_val_compare_and_swap(Dest, Comperand, Exchange); } @@ -245,77 +305,85 @@ struct CORE_API FHTML5PlatformAtomics : public FGenericPlatformAtomics { static FORCEINLINE int8 InterlockedIncrement( volatile int8* Value ) { - *Value += 1; - return *Value; + const int8 TempValue = static_cast(static_cast(*Value) + 1U); + *Value = TempValue; + return TempValue; } static FORCEINLINE int16 InterlockedIncrement( volatile int16* Value ) { - *Value += 1; - return *Value; + const int16 TempValue = static_cast(static_cast(*Value) + 1U); + *Value = TempValue; + return TempValue; } static FORCEINLINE int32 InterlockedIncrement( volatile int32* Value ) { - *Value += 1; - return *Value; + const int32 TempValue = static_cast(static_cast(*Value) + 1U); + *Value = TempValue; + return TempValue; } static FORCEINLINE int64 InterlockedIncrement( volatile int64* Value ) { - *Value += 1; - return *Value; + const int64 TempValue = static_cast(static_cast(*Value) + 1ULL); + *Value = TempValue; + return TempValue; } static FORCEINLINE int8 InterlockedDecrement( volatile int8* Value ) { - *Value -= 1; - return *Value; + const int8 TempValue = static_cast(static_cast(*Value) - 1U); + *Value = TempValue; + return TempValue; } static FORCEINLINE int16 InterlockedDecrement( volatile int16* Value ) { - *Value -= 1; - return *Value; + const int16 TempValue = static_cast(static_cast(*Value) - 1U); + *Value = TempValue; + return TempValue; } static FORCEINLINE int32 InterlockedDecrement( volatile int32* Value ) { - *Value -= 1; - return *Value; + const int32 TempValue = static_cast(static_cast(*Value) - 1U); + *Value = TempValue; + return TempValue; } static FORCEINLINE int64 InterlockedDecrement( volatile int64* Value ) { - *Value -=1; - return *Value; + const int64 TempValue = static_cast(static_cast(*Value) - 1ULL); + *Value = TempValue; + return TempValue; } static FORCEINLINE int8 InterlockedAdd( volatile int8* Value, int8 Amount ) { int8 Result = *Value; - *Value += Amount; + *Value = static_cast(static_cast(Result) + static_cast(Amount)); return Result; } static FORCEINLINE int16 InterlockedAdd( volatile int16* Value, int16 Amount ) { int16 Result = *Value; - *Value += Amount; + *Value = static_cast(static_cast(Result) + static_cast(Amount)); return Result; } static FORCEINLINE int32 InterlockedAdd( volatile int32* Value, int32 Amount ) { int32 Result = *Value; - *Value += Amount; + *Value = static_cast(static_cast(Result) + static_cast(Amount)); return Result; } static FORCEINLINE int64 InterlockedAdd( volatile int64* Value, int64 Amount ) { int64 Result = *Value; - *Value += Amount; + *Value = static_cast(static_cast(Result) + static_cast(Amount)); return Result; } @@ -347,7 +415,7 @@ struct CORE_API FHTML5PlatformAtomics : public FGenericPlatformAtomics return Result; } - static FORCEINLINE void* InterlockedExchangePtr( void** Dest, void* Exchange ) + static FORCEINLINE void* InterlockedExchangePtr( void*volatile* Dest, void* Exchange ) { void* Result = *Dest; *Dest = Exchange; @@ -394,6 +462,90 @@ struct CORE_API FHTML5PlatformAtomics : public FGenericPlatformAtomics return Result; } + static FORCEINLINE int8 InterlockedAnd(volatile int8* Value, const int8 AndValue) + { + const int8 Result = *Value; + *Value = Result & AndValue; + return Result; + } + + static FORCEINLINE int16 InterlockedAnd(volatile int16* Value, const int16 AndValue) + { + const int16 Result = *Value; + *Value = Result & AndValue; + return Result; + } + + static FORCEINLINE int32 InterlockedAnd(volatile int32* Value, const int32 AndValue) + { + const int32 Result = *Value; + *Value = Result & AndValue; + return Result; + } + + static FORCEINLINE int64 InterlockedAnd(volatile int64* Value, const int64 AndValue) + { + const int64 Result = *Value; + *Value = Result & AndValue; + return Result; + } + + static FORCEINLINE int8 InterlockedOr(volatile int8* Value, const int8 OrValue) + { + const int8 Result = *Value; + *Value = Result | OrValue; + return Result; + } + + static FORCEINLINE int16 InterlockedOr(volatile int16* Value, const int16 OrValue) + { + const int16 Result = *Value; + *Value = Result | OrValue; + return Result; + } + + static FORCEINLINE int32 InterlockedOr(volatile int32* Value, const int32 OrValue) + { + const int32 Result = *Value; + *Value = Result | OrValue; + return Result; + } + + static FORCEINLINE int64 InterlockedOr(volatile int64* Value, const int64 OrValue) + { + const int64 Result = *Value; + *Value = Result | OrValue; + return Result; + } + + static FORCEINLINE int8 InterlockedXor(volatile int8* Value, const int8 XorValue) + { + const int8 Result = *Value; + *Value = Result ^ XorValue; + return Result; + } + + static FORCEINLINE int16 InterlockedXor(volatile int16* Value, const int16 XorValue) + { + const int16 Result = *Value; + *Value = Result ^ XorValue; + return Result; + } + + static FORCEINLINE int32 InterlockedXor(volatile int32* Value, const int32 XorValue) + { + const int32 Result = *Value; + *Value = Result ^ XorValue; + return Result; + } + + static FORCEINLINE int64 InterlockedXor(volatile int64* Value, const int64 XorValue) + { + const int64 Result = *Value; + *Value = Result ^ XorValue; + return Result; + } + static FORCEINLINE int8 AtomicRead(volatile const int8* Src) { return *Src; @@ -474,7 +626,7 @@ struct CORE_API FHTML5PlatformAtomics : public FGenericPlatformAtomics *Src = Val; } - static FORCEINLINE void* InterlockedCompareExchangePointer( void** Dest, void* Exchange, void* Comperand ) + static FORCEINLINE void* InterlockedCompareExchangePointer( void*volatile* Dest, void* Exchange, void* Comperand ) { void* Result = *Dest; if (Result == Comperand) diff --git a/Engine/Source/Runtime/Core/Public/HTML5/HTML5PlatformString.h b/Engine/Source/Runtime/Core/Public/HTML5/HTML5PlatformString.h index 01f2f38506e5..74c238d610e1 100644 --- a/Engine/Source/Runtime/Core/Public/HTML5/HTML5PlatformString.h +++ b/Engine/Source/Runtime/Core/Public/HTML5/HTML5PlatformString.h @@ -20,3 +20,45 @@ struct FHTML5PlatformString : public FStandardPlatformString // default implementation typedef FHTML5PlatformString FPlatformString; +// Format specifiers to be able to print values of these types correctly, for example when using UE_LOG. +// SIZE_T format specifier +#define SIZE_T_FMT "lu" +// SIZE_T format specifier for lowercase hexadecimal output +#define SIZE_T_x_FMT "lx" +// SIZE_T format specifier for uppercase hexadecimal output +#define SIZE_T_X_FMT "lX" + +// SSIZE_T format specifier +#define SSIZE_T_FMT "ld" +// SSIZE_T format specifier for lowercase hexadecimal output +#define SSIZE_T_x_FMT "lx" +// SSIZE_T format specifier for uppercase hexadecimal output +#define SSIZE_T_X_FMT "lX" + +// PTRINT format specifier for decimal output +#define PTRINT_FMT "d" +// PTRINT format specifier for lowercase hexadecimal output +#define PTRINT_x_FMT "x" +// PTRINT format specifier for uppercase hexadecimal output +#define PTRINT_X_FMT "X" + +// UPTRINT format specifier for decimal output +#define UPTRINT_FMT "u" +// UPTRINT format specifier for lowercase hexadecimal output +#define UPTRINT_x_FMT "x" +// UPTRINT format specifier for uppercase hexadecimal output +#define UPTRINT_X_FMT "X" + +// int64 format specifier for decimal output +#define INT64_FMT "lld" +// int64 format specifier for lowercase hexadecimal output +#define INT64_x_FMT "llx" +// int64 format specifier for uppercase hexadecimal output +#define INT64_X_FMT "llX" + +// uint64 format specifier for decimal output +#define UINT64_FMT "llu" +// uint64 format specifier for lowercase hexadecimal output +#define UINT64_x_FMT "llx" +// uint64 format specifier for uppercase hexadecimal output +#define UINT64_X_FMT "llX" diff --git a/Engine/Source/Runtime/Core/Public/IOS/IOSPlatform.h b/Engine/Source/Runtime/Core/Public/IOS/IOSPlatform.h index 8322809f9aba..d67e706d6e44 100644 --- a/Engine/Source/Runtime/Core/Public/IOS/IOSPlatform.h +++ b/Engine/Source/Runtime/Core/Public/IOS/IOSPlatform.h @@ -73,13 +73,7 @@ typedef FIOSPlatformTypes FPlatformTypes; #define PLATFORM_GLOBAL_LOG_CATEGORY LogIOS -#if WITH_SIMULATOR - #define PLATFORM_BREAK() __asm__("int $3") -#elif PLATFORM_64BITS - #define PLATFORM_BREAK() __asm__("svc 0") -#else - #define PLATFORM_BREAK() __asm__("trap") -#endif +#define PLATFORM_BREAK() __builtin_trap() #define PLATFORM_CODE_SECTION(Name) __attribute__((section("__TEXT,__" Name ",regular,pure_instructions"))) \ __attribute__((aligned(4))) diff --git a/Engine/Source/Runtime/Core/Public/Internationalization/StringTableCore.h b/Engine/Source/Runtime/Core/Public/Internationalization/StringTableCore.h index 46dbb8d8c933..a254bb8155da 100644 --- a/Engine/Source/Runtime/Core/Public/Internationalization/StringTableCore.h +++ b/Engine/Source/Runtime/Core/Public/Internationalization/StringTableCore.h @@ -178,6 +178,15 @@ public: */ typedef TFunction FLoadStringTableAssetCallback; + /** + * Check to see whether it is currently safe to attempt to find or load a string table asset. + * @return True if it is safe to attempt to find or load a string table asset, false otherwise. + */ + static bool CanFindOrLoadStringTableAsset() + { + return !InstancePtr || InstancePtr->CanFindOrLoadStringTableAssetImpl(); + } + /** * Load a string table asset by its name, potentially doing so asynchronously. * @note If the string table is already loaded, or loading is perform synchronously, then the callback will be called before this function returns. @@ -185,7 +194,7 @@ public: */ static int32 LoadStringTableAsset(const FName InTableId, FLoadStringTableAssetCallback InLoadedCallback = FLoadStringTableAssetCallback()) { - check(IsInGameThread()); + check(CanFindOrLoadStringTableAsset()); if (InstancePtr) { @@ -206,7 +215,7 @@ public: */ static void FullyLoadStringTableAsset(FName& InOutTableId) { - check(IsInGameThread()); + check(CanFindOrLoadStringTableAsset()); if (InstancePtr) { @@ -217,6 +226,8 @@ public: /** Redirect string table asset by its name */ static void RedirectStringTableAsset(FName& InOutTableId) { + check(CanFindOrLoadStringTableAsset()); + if (InstancePtr) { InstancePtr->RedirectStringTableAssetImpl(InOutTableId); @@ -247,6 +258,7 @@ public: protected: virtual ~IStringTableEngineBridge() {} + virtual bool CanFindOrLoadStringTableAssetImpl() = 0; virtual int32 LoadStringTableAssetImpl(const FName InTableId, FLoadStringTableAssetCallback InLoadedCallback) = 0; virtual void FullyLoadStringTableAssetImpl(FName& InOutTableId) = 0; virtual void RedirectStringTableAssetImpl(FName& InOutTableId) = 0; diff --git a/Engine/Source/Runtime/Core/Public/Internationalization/Text.h b/Engine/Source/Runtime/Core/Public/Internationalization/Text.h index 99fdfe8e927f..52d701f6cb62 100644 --- a/Engine/Source/Runtime/Core/Public/Internationalization/Text.h +++ b/Engine/Source/Runtime/Core/Public/Internationalization/Text.h @@ -498,17 +498,17 @@ public: FText ToUpper() const; /** - * Removes whitespace characters from the front of the string. + * Removes any whitespace characters from the start of the text. */ static FText TrimPreceding( const FText& ); /** - * Removes trailing whitespace characters + * Removes any whitespace characters from the end of the text. */ static FText TrimTrailing( const FText& ); /** - * Does both of the above without needing to create an additional FText in the interim. + * Removes any whitespace characters from the start and end of the text. */ static FText TrimPrecedingAndTrailing( const FText& ); diff --git a/Engine/Source/Runtime/Core/Public/Logging/LogMacros.h b/Engine/Source/Runtime/Core/Public/Logging/LogMacros.h index 6a87e74bfa18..cb831fc5a2a8 100644 --- a/Engine/Source/Runtime/Core/Public/Logging/LogMacros.h +++ b/Engine/Source/Runtime/Core/Public/Logging/LogMacros.h @@ -8,6 +8,7 @@ #include "Containers/UnrealString.h" #include "Logging/LogCategory.h" #include "Logging/LogScopedCategoryAndVerbosityOverride.h" +#include "Logging/LogTrace.h" #include "Templates/IsValidVariadicFunctionArg.h" #include "Templates/AndOrNot.h" #include "Templates/IsArrayOrRefOfType.h" @@ -195,6 +196,7 @@ private: { \ UE_LOG_EXPAND_IS_FATAL(Verbosity, PREPROCESSOR_NOTHING, if (!CategoryName.IsSuppressed(ELogVerbosity::Verbosity))) \ { \ + TRACE_LOG_MESSAGE(CategoryName, Verbosity, Format, ##__VA_ARGS__) \ UE_LOG_EXPAND_IS_FATAL(Verbosity, \ { \ FMsg::Logf_Internal(UE_LOG_SOURCE_FILE(__FILE__), __LINE__, CategoryName.GetCategoryName(), ELogVerbosity::Verbosity, Format, ##__VA_ARGS__); \ @@ -240,6 +242,7 @@ private: { \ if (Condition) \ { \ + TRACE_LOG_MESSAGE(CategoryName, Verbosity, Format, ##__VA_ARGS__) \ UE_LOG_EXPAND_IS_FATAL(Verbosity, \ { \ FMsg::Logf_Internal(UE_LOG_SOURCE_FILE(__FILE__), __LINE__, CategoryName.GetCategoryName(), ELogVerbosity::Verbosity, Format, ##__VA_ARGS__); \ diff --git a/Engine/Source/Runtime/Core/Public/Logging/LogTrace.h b/Engine/Source/Runtime/Core/Public/Logging/LogTrace.h new file mode 100644 index 000000000000..68a69a48eb28 --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/Logging/LogTrace.h @@ -0,0 +1,53 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" + +#if !IS_PROGRAM && !UE_BUILD_SHIPPING && (PLATFORM_WINDOWS || PLATFORM_PS4 || PLATFORM_XBOXONE) +#define LOGTRACE_ENABLED 1 +#else +#define LOGTRACE_ENABLED 0 +#endif + +#if LOGTRACE_ENABLED +#include "ProfilingDebugging/FormatArgsTrace.h" + +struct FLogCategoryBase; + +struct FLogTrace +{ + CORE_API static void OutputLogCategory(const FLogCategoryBase* Category, const TCHAR* Name, ELogVerbosity::Type DefaultVerbosity); + CORE_API static void OutputLogMessageSpec(const void* LogPoint, const FLogCategoryBase* Category, ELogVerbosity::Type Verbosity, const ANSICHAR* File, int32 Line, const TCHAR* Format); + + template + static void OutputLogMessage(const void* LogPoint, Types... FormatArgs) + { + uint8 FormatArgsBuffer[4096]; + uint16 FormatArgsSize = FFormatArgsTrace::EncodeArguments(FormatArgsBuffer, FormatArgs...); + if (FormatArgsSize) + { + OutputLogMessageInternal(LogPoint, FormatArgsSize, FormatArgsBuffer); + } + } + +private: + CORE_API static void OutputLogMessageInternal(const void* LogPoint, uint16 EncodedFormatArgsSize, uint8* EncodedFormatArgs); +}; + +#define TRACE_LOG_CATEGORY(Category, Name, DefaultVerbosity) \ + FLogTrace::OutputLogCategory(Category, Name, DefaultVerbosity); + +#define TRACE_LOG_MESSAGE(Category, Verbosity, Format, ...) \ + static bool PREPROCESSOR_JOIN(__LogPoint, __LINE__); \ + if (!PREPROCESSOR_JOIN(__LogPoint, __LINE__)) \ + { \ + FLogTrace::OutputLogMessageSpec(&PREPROCESSOR_JOIN(__LogPoint, __LINE__), &Category, ELogVerbosity::Verbosity, __FILE__, __LINE__, Format); \ + PREPROCESSOR_JOIN(__LogPoint, __LINE__) = true; \ + } \ + FLogTrace::OutputLogMessage(&PREPROCESSOR_JOIN(__LogPoint, __LINE__), ##__VA_ARGS__); + +#else +#define TRACE_LOG_CATEGORY(Category, Name, DefaultVerbosity) +#define TRACE_LOG_MESSAGE(Category, Verbosity, Format, ...) +#endif \ No newline at end of file diff --git a/Engine/Source/Runtime/Core/Public/Mac/CocoaThread.h b/Engine/Source/Runtime/Core/Public/Mac/CocoaThread.h index cbf0c8dc2d71..be1769c18132 100644 --- a/Engine/Source/Runtime/Core/Public/Mac/CocoaThread.h +++ b/Engine/Source/Runtime/Core/Public/Mac/CocoaThread.h @@ -4,12 +4,12 @@ #include "CoreMinimal.h" /* Custom run-loop modes for UE4 that process only certain kinds of events to simulate Windows event ordering. */ -extern NSString* UE4NilEventMode; /* Process only mandatory events */ -extern NSString* UE4ShowEventMode; /* Process only show window events */ -extern NSString* UE4ResizeEventMode; /* Process only resize/move window events */ -extern NSString* UE4FullscreenEventMode; /* Process only fullscreen mode events */ -extern NSString* UE4CloseEventMode; /* Process only close window events */ -extern NSString* UE4IMEEventMode; /* Process only input method events */ +CORE_API extern NSString* UE4NilEventMode; /* Process only mandatory events */ +CORE_API extern NSString* UE4ShowEventMode; /* Process only show window events */ +CORE_API extern NSString* UE4ResizeEventMode; /* Process only resize/move window events */ +CORE_API extern NSString* UE4FullscreenEventMode; /* Process only fullscreen mode events */ +CORE_API extern NSString* UE4CloseEventMode; /* Process only close window events */ +CORE_API extern NSString* UE4IMEEventMode; /* Process only input method events */ @interface NSThread (FCocoaThread) + (NSThread*) gameThread; // Returns the main game thread, or nil if has yet to be constructed. @@ -24,7 +24,7 @@ extern NSString* UE4IMEEventMode; /* Process only input method events */ - (void)main; // Override that sets the variable backing +[NSRunLoop gameRunLoop], do not override in a subclass. @end -void MainThreadCall(dispatch_block_t Block, NSString* WaitMode = NSDefaultRunLoopMode, bool const bWait = true); +CORE_API void MainThreadCall(dispatch_block_t Block, NSString* WaitMode = NSDefaultRunLoopMode, bool const bWait = true); template ReturnType MainThreadReturn(ReturnType (^Block)(void), NSString* WaitMode = NSDefaultRunLoopMode) @@ -34,7 +34,7 @@ ReturnType MainThreadReturn(ReturnType (^Block)(void), NSString* WaitMode = NSDe return ReturnValue; } -void GameThreadCall(dispatch_block_t Block, NSArray* SendModes = @[ NSDefaultRunLoopMode ], bool const bWait = true); +CORE_API void GameThreadCall(dispatch_block_t Block, NSArray* SendModes = @[ NSDefaultRunLoopMode ], bool const bWait = true); template ReturnType GameThreadReturn(ReturnType (^Block)(void), NSArray* SendModes = @[ NSDefaultRunLoopMode ]) @@ -44,6 +44,6 @@ ReturnType GameThreadReturn(ReturnType (^Block)(void), NSArray* SendModes = @[ N return ReturnValue; } -void RunGameThread(id Target, SEL Selector); +CORE_API void RunGameThread(id Target, SEL Selector); -void ProcessGameThreadEvents(void); +CORE_API void ProcessGameThreadEvents(void); diff --git a/Engine/Source/Runtime/Core/Public/Mac/MacCriticalSection.h b/Engine/Source/Runtime/Core/Public/Mac/MacCriticalSection.h index a5a709aa592d..fd684aaa3cd7 100644 --- a/Engine/Source/Runtime/Core/Public/Mac/MacCriticalSection.h +++ b/Engine/Source/Runtime/Core/Public/Mac/MacCriticalSection.h @@ -14,20 +14,20 @@ class FMacSystemWideCriticalSection { public: /** Construct a named, system-wide critical section and attempt to get access/ownership of it */ - explicit FMacSystemWideCriticalSection(const FString& InName, FTimespan InTimeout = FTimespan::Zero()); + CORE_API explicit FMacSystemWideCriticalSection(const FString& InName, FTimespan InTimeout = FTimespan::Zero()); /** Destructor releases system-wide critical section if it is currently owned */ - ~FMacSystemWideCriticalSection(); + CORE_API ~FMacSystemWideCriticalSection(); /** * Does the calling thread have ownership of the system-wide critical section? * * @return True if the system-wide lock is obtained. WARNING: Returns true for abandoned locks so shared resources can be in undetermined states. */ - bool IsValid() const; + CORE_API bool IsValid() const; /** Releases system-wide critical section if it is currently owned */ - void Release(); + CORE_API void Release(); private: FMacSystemWideCriticalSection(const FMacSystemWideCriticalSection&); diff --git a/Engine/Source/Runtime/Core/Public/Mac/MacPlatform.h b/Engine/Source/Runtime/Core/Public/Mac/MacPlatform.h index c58c4bf9a1a3..ccaf1e0f2d8a 100644 --- a/Engine/Source/Runtime/Core/Public/Mac/MacPlatform.h +++ b/Engine/Source/Runtime/Core/Public/Mac/MacPlatform.h @@ -91,8 +91,12 @@ typedef FMacPlatformTypes FPlatformTypes; #define OPERATOR_NEW_NOTHROW_SPEC _NOEXCEPT #define OPERATOR_DELETE_NOTHROW_SPEC _NOEXCEPT +// DLL export and import for tpyes definitions +#define DLLEXPORT_VTABLE __attribute__ ((__type_visibility__("default"))) +#define DLLIMPORT_VTABLE __attribute__ ((__type_visibility__("default"))) + // DLL export and import definitions -#define DLLEXPORT -#define DLLIMPORT +#define DLLEXPORT __attribute__((visibility("default"))) +#define DLLIMPORT __attribute__((visibility("default"))) #define MAC_MAX_PATH 1024 diff --git a/Engine/Source/Runtime/Core/Public/Math/Box.h b/Engine/Source/Runtime/Core/Public/Math/Box.h index 66a416e9ca18..cb0d0d4e093b 100644 --- a/Engine/Source/Runtime/Core/Public/Math/Box.h +++ b/Engine/Source/Runtime/Core/Public/Math/Box.h @@ -32,13 +32,6 @@ public: /** Default constructor (no initialization). */ FBox() { } - /** Creates and initializes a new box with zero extent and marks it as invalid. */ - UE_DEPRECATED(4.16, "Use ForceInit constructor instead.") - FBox( int32 ) - { - Init(); - } - /** * Creates and initializes a new box with zero extent and marks it as invalid. * diff --git a/Engine/Source/Runtime/Core/Public/Math/RandomStream.h b/Engine/Source/Runtime/Core/Public/Math/RandomStream.h index b39bf7a5e20b..f4bb05270a49 100644 --- a/Engine/Source/Runtime/Core/Public/Math/RandomStream.h +++ b/Engine/Source/Runtime/Core/Public/Math/RandomStream.h @@ -8,6 +8,7 @@ #include "Math/Matrix.h" #include "Math/RotationMatrix.h" #include "Math/Transform.h" +#include "HAL/PlatformTime.h" /** * Implements a thread-safe SRand based RNG. @@ -36,9 +37,20 @@ public: * @param InSeed The seed value. */ FRandomStream( int32 InSeed ) - : InitialSeed(InSeed) - , Seed(InSeed) - { } + { + Initialize(InSeed); + } + + /** + * Creates and initializes a new random stream from the specified name. + * + * @note If NAME_None is provided, the stream will be seeded using the current time. + * @param InName The name value from which the stream will be initialized. + */ + FRandomStream( FName InName ) + { + Initialize(InName); + } public: @@ -53,6 +65,26 @@ public: Seed = InSeed; } + /** + * Initializes this random stream using the specified name. + * + * @note If NAME_None is provided, the stream will be seeded using the current time. + * @param InName The name value from which the stream will be initialized. + */ + void Initialize( FName InName ) + { + if (InName != NAME_None) + { + InitialSeed = GetTypeHash(InName.ToString()); + } + else + { + InitialSeed = FPlatformTime::Cycles(); + } + + Seed = InitialSeed; + } + /** * Resets this random stream to the initial seed value. */ diff --git a/Engine/Source/Runtime/Core/Public/Math/Ray.h b/Engine/Source/Runtime/Core/Public/Math/Ray.h new file mode 100644 index 000000000000..a5e47ebd727a --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/Math/Ray.h @@ -0,0 +1,112 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Math/Vector.h" + + +/** + * 3D Ray represented by Origin and (normalized) Direction + */ +class FRay +{ +public: + + /** Ray origin point */ + FVector Origin; + + /** Ray direction vector (always normalized) */ + FVector Direction; + +public: + + /** Default constructor initializes ray to Zero origin and Z-axis direction */ + FRay() + { + Origin = FVector::ZeroVector; + Direction = FVector(0, 0, 1); + } + + /** + * Initialize Ray with origin and direction + * + * @param Origin Ray Origin Point + * @param Direction Ray Direction Vector + * @param bDirectionIsNormalized Direction will be normalized unless this is passed as true (default false) + */ + FRay(const FVector& Origin, const FVector& Direction, bool bDirectionIsNormalized = false) + { + this->Origin = Origin; + this->Direction = Direction; + if (bDirectionIsNormalized == false) + { + this->Direction.Normalize(); // is this a full-accuracy sqrt? + } + } + + +public: + + /** + * Calculate position on ray at given distance/parameter + * + * @param RayParameter Scalar distance along Ray + * @return Point on Ray + */ + FVector PointAt(float RayParameter) const + { + return Origin + RayParameter * Direction; + } + + /** + * Calculate ray parameter (distance from origin to closest point) for query Point + * + * @param Point query Point + * @return distance along ray from origin to closest point + */ + float GetParameter(const FVector& Point) const + { + return FVector::DotProduct((Point - Origin), Direction); + } + + /** + * Find minimum squared distance from query point to ray + * + * @param Point query Point + * @return squared distance to Ray + */ + float DistSquared(const FVector& Point) const + { + float RayParameter = FVector::DotProduct((Point - Origin), Direction); + if (RayParameter < 0) + { + return FVector::DistSquared(Origin, Point); + } + else + { + FVector ProjectionPt = Origin + RayParameter * Direction; + return FVector::DistSquared(ProjectionPt, Point); + } + } + + /** + * Find closest point on ray to query point + * @param Point query point + * @return closest point on Ray + */ + FVector ClosestPoint(const FVector& Point) const + { + float RayParameter = FVector::DotProduct((Point - Origin), Direction); + if (RayParameter < 0) + { + return Origin; + } + else + { + return Origin + RayParameter * Direction; + } + } + + +}; + diff --git a/Engine/Source/Runtime/Core/Public/Math/TransformVectorized.h b/Engine/Source/Runtime/Core/Public/Math/TransformVectorized.h index db32814a34f4..d38e451fe9cd 100644 --- a/Engine/Source/Runtime/Core/Public/Math/TransformVectorized.h +++ b/Engine/Source/Runtime/Core/Public/Math/TransformVectorized.h @@ -1373,7 +1373,7 @@ private: checkSlow(VectorAnyGreaterThan(VectorAbs(Scale3D), GlobalVectorConstants::SmallNumber)); // Invert the scale - const VectorRegister InvScale = VectorSet_W0(GetSafeScaleReciprocal(Scale3D , ScalarRegister(GlobalVectorConstants::SmallNumber))); + const VectorRegister InvScale = VectorSet_W0(GetSafeScaleReciprocal(VectorSet_W1(Scale3D), ScalarRegister(GlobalVectorConstants::SmallNumber))); // Invert the rotation const VectorRegister InvRotation = VectorQuaternionInverse(Rotation); diff --git a/Engine/Source/Runtime/Core/Public/Math/UnrealMath.h b/Engine/Source/Runtime/Core/Public/Math/UnrealMath.h index e4b6e81f537a..75e2fab157d3 100644 --- a/Engine/Source/Runtime/Core/Public/Math/UnrealMath.h +++ b/Engine/Source/Runtime/Core/Public/Math/UnrealMath.h @@ -14,6 +14,7 @@ #include "Math/Vector4.h" #include "Math/VectorRegister.h" #include "Math/TwoVectors.h" +#include "Math/Ray.h" #include "Math/Edge.h" #include "Math/Plane.h" #include "Math/Sphere.h" diff --git a/Engine/Source/Runtime/Core/Public/Math/UnrealMathDirectX.h b/Engine/Source/Runtime/Core/Public/Math/UnrealMathDirectX.h index 0ea11009c548..471f3bbede48 100644 --- a/Engine/Source/Runtime/Core/Public/Math/UnrealMathDirectX.h +++ b/Engine/Source/Runtime/Core/Public/Math/UnrealMathDirectX.h @@ -1059,10 +1059,10 @@ FORCEINLINE VectorRegister VectorSign(const VectorRegister& X) FORCEINLINE VectorRegister VectorStep(const VectorRegister& X) { return MakeVectorRegister( - (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : -1.0f)); + (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : 0.0f)); } ////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Runtime/Core/Public/Math/UnrealMathFPU.h b/Engine/Source/Runtime/Core/Public/Math/UnrealMathFPU.h index ffc1e8ca9657..0f8d27ba75e2 100644 --- a/Engine/Source/Runtime/Core/Public/Math/UnrealMathFPU.h +++ b/Engine/Source/Runtime/Core/Public/Math/UnrealMathFPU.h @@ -1279,20 +1279,20 @@ FORCEINLINE VectorRegister VectorMod(const VectorRegister& X, const VectorRegist FORCEINLINE VectorRegister VectorSign(const VectorRegister& X) { return MakeVectorRegister( - (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : 0.0f), - (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : 0.0f), - (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : 0.0f), - (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : 0.0f)); + (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : -1.0f), + (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : -1.0f), + (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : -1.0f), + (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : -1.0f)); } //TODO: Vectorize FORCEINLINE VectorRegister VectorStep(const VectorRegister& X) { return MakeVectorRegister( - (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : -1.0f)); + (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : 0.0f)); } /** diff --git a/Engine/Source/Runtime/Core/Public/Math/UnrealMathNeon.h b/Engine/Source/Runtime/Core/Public/Math/UnrealMathNeon.h index d371ae521d00..4de1ee86e107 100644 --- a/Engine/Source/Runtime/Core/Public/Math/UnrealMathNeon.h +++ b/Engine/Source/Runtime/Core/Public/Math/UnrealMathNeon.h @@ -1294,20 +1294,20 @@ FORCEINLINE VectorRegister VectorMod(const VectorRegister& X, const VectorRegist FORCEINLINE VectorRegister VectorSign(const VectorRegister& X) { return MakeVectorRegister( - (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : 0.0f), - (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : 0.0f), - (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : 0.0f), - (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : 0.0f)); + (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : -1.0f), + (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : -1.0f), + (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : -1.0f), + (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : -1.0f)); } //TODO: Vectorize FORCEINLINE VectorRegister VectorStep(const VectorRegister& X) { return MakeVectorRegister( - (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : -1.0f)); + (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : 0.0f)); } /** diff --git a/Engine/Source/Runtime/Core/Public/Math/UnrealMathUtility.h b/Engine/Source/Runtime/Core/Public/Math/UnrealMathUtility.h index c9ed8ebf865b..f8a9e194a7bc 100644 --- a/Engine/Source/Runtime/Core/Public/Math/UnrealMathUtility.h +++ b/Engine/Source/Runtime/Core/Public/Math/UnrealMathUtility.h @@ -1310,6 +1310,15 @@ struct FMath : public FPlatformMath */ static CORE_API bool PointsAreCoplanar(const TArray& Points, const float Tolerance = 0.1f); + /** + * Truncates a floating point number to half if closer than the given tolerance. + * @param Value Floating point number to truncate + * @param Tolerance Maximum allowed difference to 0.5 in order to truncate + * @return The truncated value + */ + static CORE_API float TruncateToHalfIfClose(float F, float Tolerance = SMALL_NUMBER); + static CORE_API double TruncateToHalfIfClose(double F, double Tolerance = SMALL_NUMBER); + /** * Converts a floating point number to the nearest integer, equidistant ties go to the value which is closest to an even value: 1.5 becomes 2, 0.5 becomes 0 * @param F Floating point value to convert diff --git a/Engine/Source/Runtime/Core/Public/Misc/AsciiSet.h b/Engine/Source/Runtime/Core/Public/Misc/AsciiSet.h new file mode 100644 index 000000000000..cbf0c6937734 --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/Misc/AsciiSet.h @@ -0,0 +1,163 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Misc/Char.h" + +/** + * ASCII character bitset useful for fast and readable parsing + * + * Entirely constexpr. Works with both wide and narrow strings. + * + * Example use cases: + * + * constexpr FAsciiSet WhitespaceCharacters(" \v\f\t\r\n"); + * bool bIsWhitespace = WhitespaceCharacters.Test(MyChar); + * const char* HelloWorld = FAsciiSet::Skip(" \t\tHello world!", WhitespaceCharacters); + * + * constexpr FAsciiSet XmlEscapeChars("&<>\"'"); + * check(FAsciiSet::HasNone(EscapedXmlString, XmlEscapeChars)); + * + * constexpr FAsciiSet Delimiters(".:;"); + * const TCHAR* DelimiterOrEnd = FAsciiSet::FindFirstOrEnd(PrefixedName, Delimiters); + * FString Prefix(PrefixedName, DelimiterOrEnd - PrefixedName); + * + * constexpr FAsciiSet Slashes("/\\"); + * const TCHAR* SlashOrEnd = FAsciiSet::FindLastOrEnd(PathName, Slashes); + * const TCHAR* FileName = *SlashOrEnd ? SlashOrEnd + 1 : PathName; + */ +class FAsciiSet +{ +public: + template + constexpr FAsciiSet(const CharType(&Chars)[N]) + : FAsciiSet(StringToBitset(Chars)) + {} + + /** Returns true if a character is part of the set */ + template + constexpr FORCEINLINE bool Contains(CharType Char) const + { + return !!TestImpl(TChar::ToUnsigned(Char)); + } + + /** Returns non-zero if a character is part of the set. Prefer Contains() to avoid VS2019 conversion warnings. */ + template + constexpr FORCEINLINE uint64 Test(CharType Char) const + { + return TestImpl(TChar::ToUnsigned(Char)); + } + + /** Create new set with specified character in it */ + constexpr FORCEINLINE FAsciiSet operator+(char Char) const + { + InitData Bitset = { LoMask, HiMask }; + SetImpl(Bitset, TChar::ToUnsigned(Char)); + return FAsciiSet(Bitset); + } + + /** Create new set containing inverse set of characters - likely including null-terminator */ + constexpr FORCEINLINE FAsciiSet operator~() const + { + return FAsciiSet(~LoMask, ~HiMask); + } + + // C string util functions + + /** Find first character of string inside set or end pointer. Never returns null. */ + template + static constexpr const CharType* FindFirstOrEnd(const CharType* Str, FAsciiSet Set) + { + for (FAsciiSet SetOrNil(Set.LoMask | NilMask, Set.HiMask); !SetOrNil.Test(*Str); ++Str); + + return Str; + } + + /** Find last character of string inside set or end pointer. Never returns null. */ + template + static constexpr const CharType* FindLastOrEnd(const CharType* Str, FAsciiSet Set) + { + const CharType* Last = FindFirstOrEnd(Str, Set); + + for (const CharType* It = Last; *It; It = FindFirstOrEnd(It + 1, Set)) + { + Last = It; + } + + return Last; + } + + /** Find first character of string outside set or end pointer. Never returns null. */ + template + static constexpr const CharType* Skip(const CharType* Str, FAsciiSet Set) + { + return FindFirstOrEnd(Str, ~Set); + } + + /** Test if string contains any character in set */ + template + static constexpr bool HasAny(const CharType* Str, FAsciiSet Set) + { + return *FindFirstOrEnd(Str, Set) != '\0'; + } + + /** Test if string contains no character in set */ + template + static constexpr bool HasNone(const CharType* Str, FAsciiSet Set) + { + return *FindFirstOrEnd(Str, Set) == '\0'; + } + + /** Test if string contains any character outside of set */ + template + static constexpr bool HasOnly(const CharType* Str, FAsciiSet Set) + { + return *Skip(Str, Set) == '\0'; + } + +private: + // Work-around for constexpr limitations + struct InitData { uint64 Lo, Hi; }; + static constexpr uint64 NilMask = uint64(1) << '\0'; + + static constexpr FORCEINLINE void SetImpl(InitData& Bitset, uint32 Char) + { + uint64 IsLo = uint64(0) - (Char >> 6 == 0); + uint64 IsHi = uint64(0) - (Char >> 6 == 1); + uint64 Bit = uint64(1) << uint8(Char & 0x3f); + + Bitset.Lo |= Bit & IsLo; + Bitset.Hi |= Bit & IsHi; + } + + constexpr FORCEINLINE uint64 TestImpl(uint32 Char) const + { + uint64 IsLo = uint64(0) - (Char >> 6 == 0); + uint64 IsHi = uint64(0) - (Char >> 6 == 1); + uint64 Bit = uint64(1) << (Char & 0x3f); + + return (Bit & IsLo & LoMask) | (Bit & IsHi & HiMask); + } + + template + static constexpr InitData StringToBitset(const CharType(&Chars)[N]) + { + InitData Bitset = { 0, 0 }; + for (int I = 0; I < N - 1; ++I) + { + SetImpl(Bitset, TChar::ToUnsigned(Chars[I])); + } + + return Bitset; + } + + constexpr FAsciiSet(InitData Bitset) + : LoMask(Bitset.Lo), HiMask(Bitset.Hi) + {} + + constexpr FAsciiSet(uint64 Lo, uint64 Hi) + : LoMask(Lo), HiMask(Hi) + {} + + uint64 LoMask, HiMask; +}; diff --git a/Engine/Source/Runtime/Core/Public/Misc/AssertionMacros.h b/Engine/Source/Runtime/Core/Public/Misc/AssertionMacros.h index 7f70b0fe2ef1..caa8378a4d1c 100644 --- a/Engine/Source/Runtime/Core/Public/Misc/AssertionMacros.h +++ b/Engine/Source/Runtime/Core/Public/Misc/AssertionMacros.h @@ -47,6 +47,9 @@ struct CORE_API FDebug /** Dumps the stack trace into the log, meant to be used for debugging purposes. */ static void DumpStackTraceToLog(); + /** Dumps the stack trace into the log with a custom heading, meant to be used for debugging purposes. */ + static void DumpStackTraceToLog(const TCHAR* Heading); + #if DO_CHECK || DO_GUARD_SLOW private: static void VARARGS CheckVerifyFailedImpl(const ANSICHAR* Expr, const char* File, int32 Line, const TCHAR* Format, ...); diff --git a/Engine/Source/Runtime/Core/Public/Misc/AutomationTest.h b/Engine/Source/Runtime/Core/Public/Misc/AutomationTest.h index 07424b1a0b94..03d4ee260bf1 100644 --- a/Engine/Source/Runtime/Core/Public/Misc/AutomationTest.h +++ b/Engine/Source/Runtime/Core/Public/Misc/AutomationTest.h @@ -1241,7 +1241,9 @@ public: public: void TestEqual(const TCHAR* What, int32 Actual, int32 Expected); + void TestEqual(const TCHAR* What, int64 Actual, int64 Expected); void TestEqual(const TCHAR* What, float Actual, float Expected, float Tolerance = KINDA_SMALL_NUMBER); + void TestEqual(const TCHAR* What, double Actual, double Expected, double Tolerance = KINDA_SMALL_NUMBER); void TestEqual(const TCHAR* What, FVector Actual, FVector Expected, float Tolerance = KINDA_SMALL_NUMBER); void TestEqual(const TCHAR* What, FColor Actual, FColor Expected); void TestEqual(const TCHAR* What, const TCHAR* A, const TCHAR* B); @@ -1257,6 +1259,11 @@ public: TestEqual(*What, Actual, Expected, Tolerance); } + void TestEqual(const FString& What, double Actual, double Expected, double Tolerance = KINDA_SMALL_NUMBER) + { + TestEqual(*What, Actual, Expected, Tolerance); + } + void TestEqual(const FString& What, FVector Actual, FVector Expected, float Tolerance = KINDA_SMALL_NUMBER) { TestEqual(*What, Actual, Expected, Tolerance); diff --git a/Engine/Source/Runtime/Core/Public/Misc/Build.h b/Engine/Source/Runtime/Core/Public/Misc/Build.h index c6a16da19e57..5e03083392d5 100644 --- a/Engine/Source/Runtime/Core/Public/Misc/Build.h +++ b/Engine/Source/Runtime/Core/Public/Misc/Build.h @@ -91,6 +91,13 @@ #error UBT should always define WITH_PLUGIN_SUPPORT to be 0 or 1 #endif +/** + * Whether we are compiling with Slate accessibility and automation support + */ +#ifndef WITH_ACCESSIBILITY + #define WITH_ACCESSIBILITY 1 +#endif + /** Enable perf counters */ #ifndef WITH_PERFCOUNTERS #define WITH_PERFCOUNTERS 0 diff --git a/Engine/Source/Runtime/Core/Public/Misc/Change.h b/Engine/Source/Runtime/Core/Public/Misc/Change.h index e079fb57b9ab..f3769c838063 100644 --- a/Engine/Source/Runtime/Core/Public/Misc/Change.h +++ b/Engine/Source/Runtime/Core/Public/Misc/Change.h @@ -5,16 +5,42 @@ #include "Templates/UniquePtr.h" #include "Containers/Array.h" -// @todo mesheditor: Comment these classes and enums! - +/** + * FChange modifies a UObject and is meant to be used to implement undo/redo. + * The change is embedded in an FTransaction which executes it *instead* of the standard + * serialization transaction (cannot be combined - see FTransaction). + * + * The original FChange style (used by MeshEditor) was that calling Execute() would return a new + * FChange that applies the opposite action, and FTransaction would swap the two at each undo/redo + * step (eg a "DeleteObject" FChange would return a "CreateObject" FChange) + * + * The alternative "Command Pattern"-style FChange calls Apply() and Revert() on a single FChange. + * + * FChange may eventually be deprecated. You should subclass + * FSwapChange and FCommandChange to implement these different styles. + */ class CORE_API FChange { public: + enum class EChangeStyle + { + InPlaceSwap, // Call Execute() which returns new "opposite" FChange (default) + CommandPattern // call Revert() to Undo and Apply() to Redo + }; + + /** What style of change is this */ + virtual EChangeStyle GetChangeType() = 0; /** Makes the change to the object, returning a new change that can be used to perfectly roll back this change */ virtual TUniquePtr Execute( UObject* Object ) = 0; + /** Makes the change to the object */ + virtual void Apply( UObject* Object ) = 0; + + /** Reverts change to the object */ + virtual void Revert( UObject* Object ) = 0; + /** Describes this change (for debugging) */ virtual FString ToString() const = 0; @@ -42,6 +68,62 @@ private: +/** + * To use FSwapChange you must implement Execute(). + * This function must do two things: + * 1) apply the change to the given UObject + * 2) return a new FSwapChange that does the "opposite" action + */ +class CORE_API FSwapChange : public FChange +{ + +public: + virtual EChangeStyle GetChangeType() override + { + return FChange::EChangeStyle::InPlaceSwap; + } + + /** Makes the change to the object */ + virtual void Apply(UObject* Object) override + { + check(false); + } + + /** Reverts change to the object */ + virtual void Revert(UObject* Object) override + { + check(false); + } + +}; + + +/** + * To use FCommandChange you must implement Apply() and Revert() + * Revert() is called to "Undo" and Apply() is called to "Redo" + */ +class CORE_API FCommandChange : public FChange +{ + +public: + virtual EChangeStyle GetChangeType() override + { + return FChange::EChangeStyle::CommandPattern; + } + + virtual TUniquePtr Execute(UObject* Object) + { + check(false); + return nullptr; + } + +}; + + + + + + struct CORE_API FCompoundChangeInput { FCompoundChangeInput() @@ -63,7 +145,12 @@ private: }; -class CORE_API FCompoundChange : public FChange + +/** + * FCompoundChange applies a sequence of FSwapChanges. + * The changes are executed in reverse order (this is like a mini undo stack) + */ +class CORE_API FCompoundChange : public FSwapChange { public: diff --git a/Engine/Source/Runtime/Core/Public/Misc/Char.h b/Engine/Source/Runtime/Core/Public/Misc/Char.h index ead1f4e0b583..2d95999cad83 100644 --- a/Engine/Source/Runtime/Core/Public/Misc/Char.h +++ b/Engine/Source/Runtime/Core/Public/Misc/Char.h @@ -128,7 +128,7 @@ struct TChar : TCharBase * * Mainly needed for subtraction and addition. */ - static uint32 ToUnsigned(CharType Char) + static constexpr FORCEINLINE uint32 ToUnsigned(CharType Char) { return (typename TUnsignedIntType::Type)Char; } diff --git a/Engine/Source/Runtime/Core/Public/Misc/CoreMisc.h b/Engine/Source/Runtime/Core/Public/Misc/CoreMisc.h index de1528d4b448..7da936cb38bf 100644 --- a/Engine/Source/Runtime/Core/Public/Misc/CoreMisc.h +++ b/Engine/Source/Runtime/Core/Public/Misc/CoreMisc.h @@ -265,6 +265,14 @@ struct CORE_API FScopedScriptExceptionHandler #endif #endif +#ifndef SCRIPT_AUDIT_ROUTINES + #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + #define SCRIPT_AUDIT_ROUTINES 1 + #else + #define SCRIPT_AUDIT_ROUTINES 0 + #endif +#endif + #if DO_BLUEPRINT_GUARD struct FFrame; diff --git a/Engine/Source/Runtime/Core/Public/Misc/IEngineCrypto.h b/Engine/Source/Runtime/Core/Public/Misc/IEngineCrypto.h new file mode 100644 index 000000000000..08c19cf75c16 --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/Misc/IEngineCrypto.h @@ -0,0 +1,62 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "Features/IModularFeatures.h" +#include "Containers/ArrayView.h" + +typedef void* FRSAKeyHandle; +static const FRSAKeyHandle InvalidRSAKeyHandle = nullptr; + +struct IEngineCrypto : public IModularFeature +{ + /** + * Get the name of this modular feature + */ + static FORCEINLINE FName GetFeatureName() + { + static const FName Name(TEXT("EngineCryptoFeature")); + return Name; + } + + /** + * Create a new RSA key from the given little-endian exponents and modulus + */ + virtual FRSAKeyHandle CreateRSAKey(const TArrayView InPublicExponent, const TArrayView InPrivateExponent, const TArrayView InModulus) = 0; + + /** + * Destroy the given RSA key + */ + virtual void DestroyRSAKey(FRSAKeyHandle InKey) = 0; + + /** + * Get the size of bytes of the given RSA key + */ + virtual int32 GetKeySize(FRSAKeyHandle InKey) = 0; + + /** + * Get the maximum amount of data that can be encrypted using the given key, taking into account minimum padding requirements + */ + virtual int32 GetMaxDataSize(FRSAKeyHandle InKey) = 0; + + /** + * Encrypt the supplied byte data using the given public key + */ + virtual int32 EncryptPublic(const TArrayView InSource, TArray& OutDestination, FRSAKeyHandle InKey) = 0; + + /** + * Encrypt the supplied byte data using the given private key + */ + virtual int32 EncryptPrivate(const TArrayView InSource, TArray& OutDestination, FRSAKeyHandle InKey) = 0; + + /** + * Decrypt the supplied byte data using the given public key + */ + virtual int32 DecryptPublic(const TArrayView InSource, TArray& OutDestination, FRSAKeyHandle InKey) = 0; + + /** + * Encrypt the supplied byte data using the given private key + */ + virtual int32 DecryptPrivate(const TArrayView InSource, TArray& OutDestination, FRSAKeyHandle InKey) = 0; +}; \ No newline at end of file diff --git a/Engine/Source/Runtime/Core/Public/Misc/MemStack.h b/Engine/Source/Runtime/Core/Public/Misc/MemStack.h index 0bea2ff45c29..1a2f9450bc68 100644 --- a/Engine/Source/Runtime/Core/Public/Misc/MemStack.h +++ b/Engine/Source/Runtime/Core/Public/Misc/MemStack.h @@ -271,6 +271,7 @@ template class TMemStackAllocator { public: + using SizeType = int32; enum { NeedsElementType = true }; enum { RequireRangeCheck = true }; @@ -303,7 +304,7 @@ public: { return Data; } - void ResizeAllocation(int32 PreviousNumElements,int32 NumElements,int32 NumBytesPerElement) + void ResizeAllocation(SizeType PreviousNumElements, SizeType NumElements,SIZE_T NumBytesPerElement) { void* OldData = Data; if( NumElements ) @@ -317,30 +318,30 @@ public: // If the container previously held elements, copy them into the new allocation. if(OldData && PreviousNumElements) { - const int32 NumCopiedElements = FMath::Min(NumElements,PreviousNumElements); + const SizeType NumCopiedElements = FMath::Min(NumElements,PreviousNumElements); FMemory::Memcpy(Data,OldData,NumCopiedElements * NumBytesPerElement); } } } - FORCEINLINE int32 CalculateSlackReserve(int32 NumElements, int32 NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackReserve(SizeType NumElements, SIZE_T NumBytesPerElement) const { return DefaultCalculateSlackReserve(NumElements, NumBytesPerElement, false, Alignment); } - FORCEINLINE int32 CalculateSlackShrink(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackShrink(SizeType NumElements, SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { return DefaultCalculateSlackShrink(NumElements, NumAllocatedElements, NumBytesPerElement, false, Alignment); } - FORCEINLINE int32 CalculateSlackGrow(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const + FORCEINLINE SizeType CalculateSlackGrow(SizeType NumElements, SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { return DefaultCalculateSlackGrow(NumElements, NumAllocatedElements, NumBytesPerElement, false, Alignment); } - FORCEINLINE int32 GetAllocatedSize(int32 NumAllocatedElements, int32 NumBytesPerElement) const + FORCEINLINE SIZE_T GetAllocatedSize(SizeType NumAllocatedElements, SIZE_T NumBytesPerElement) const { return NumAllocatedElements * NumBytesPerElement; } - bool HasAllocation() + bool HasAllocation() const { return !!Data; } diff --git a/Engine/Source/Runtime/Core/Public/Misc/SecureHash.h b/Engine/Source/Runtime/Core/Public/Misc/SecureHash.h index 92df6aedc9c5..af72101a7f41 100644 --- a/Engine/Source/Runtime/Core/Public/Misc/SecureHash.h +++ b/Engine/Source/Runtime/Core/Public/Misc/SecureHash.h @@ -75,7 +75,7 @@ public: * * @param String hex representation of the hash (32 lower-case hex digits) **/ - static FString HashBytes(const uint8* input, int32 inputLen) + static FString HashBytes(const uint8* input, uint64 inputLen) { uint8 Digest[16]; @@ -193,7 +193,7 @@ typedef union class CORE_API FSHAHash { public: - uint8 Hash[20]; + alignas(uint32) uint8 Hash[20]; FSHAHash() { @@ -222,7 +222,10 @@ public: friend CORE_API FArchive& operator<<( FArchive& Ar, FSHAHash& G ); - friend CORE_API uint32 GetTypeHash(FSHAHash const& InKey); + friend uint32 GetTypeHash(const FSHAHash& InKey) + { + return *reinterpret_cast(InKey.Hash); + } friend CORE_API FString LexToString(const FSHAHash&); friend CORE_API void LexFromString(FSHAHash& Hash, const TCHAR*); @@ -287,7 +290,7 @@ public: * @param BufferSize Size of Buffer * @param bDuplicateKeyMemory If Buffer is not always loaded, pass true so that the 20 byte hashes are duplicated */ - static void InitializeFileHashesFromBuffer(uint8* Buffer, int32 BufferSize, bool bDuplicateKeyMemory=false); + static void InitializeFileHashesFromBuffer(uint8* Buffer, uint64 BufferSize, bool bDuplicateKeyMemory=false); /** * Gets the stored SHA hash from the platform, if it exists. This function @@ -327,7 +330,7 @@ protected: void* Buffer; /** Size of Buffer */ - int32 BufferSize; + uint64 BufferSize; /** Hash to compare against */ uint8 Hash[20]; @@ -355,7 +358,7 @@ public: */ FAsyncSHAVerify( void* InBuffer, - int32 InBufferSize, + uint64 InBufferSize, bool bInShouldDeleteBuffer, const TCHAR* InPathname, bool bInIsUnfoundHashAnError) @@ -428,7 +431,7 @@ public: */ FBufferReaderWithSHA( void* Data, - int32 Size, + int64 Size, bool bInFreeOnClose, const TCHAR* SHASourcePathname, bool bIsPersistent=false, diff --git a/Engine/Source/Runtime/Core/Public/Modules/Boilerplate/ModuleBoilerplate.h b/Engine/Source/Runtime/Core/Public/Modules/Boilerplate/ModuleBoilerplate.h index 249069cdb2c4..209642d73e6c 100644 --- a/Engine/Source/Runtime/Core/Public/Modules/Boilerplate/ModuleBoilerplate.h +++ b/Engine/Source/Runtime/Core/Public/Modules/Boilerplate/ModuleBoilerplate.h @@ -49,7 +49,7 @@ class FChunkedFixedUObjectArray; #define UE4_VISUALIZERS_HELPERS #else #define UE4_VISUALIZERS_HELPERS \ - FNameEntry*** GFNameTableForDebuggerVisualizers_MT = FName::GetNameTableForDebuggerVisualizers_MT(); \ + uint8** GNameBlocksDebug = FNameDebugVisualizer::GetBlocks(); \ FChunkedFixedUObjectArray*& GObjectArrayForDebugVisualizers = GCoreObjectArrayForDebugVisualizers; #endif diff --git a/Engine/Source/Runtime/Core/Public/ProfilingDebugging/CpuProfilerTrace.h b/Engine/Source/Runtime/Core/Public/ProfilingDebugging/CpuProfilerTrace.h new file mode 100644 index 000000000000..b91663ca9f92 --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/ProfilingDebugging/CpuProfilerTrace.h @@ -0,0 +1,67 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" + +#if !IS_PROGRAM && !UE_BUILD_SHIPPING && (PLATFORM_WINDOWS || PLATFORM_PS4 || PLATFORM_XBOXONE) +#define CPUPROFILERTRACE_ENABLED 1 +#else +#define CPUPROFILERTRACE_ENABLED 0 +#endif + +enum ECpuProfilerGroup +{ + CpuProfilerGroup_Default, + CpuProfilerGroup_Stats, + CpuProfilerGroup_LoadTime, + CpuProfilerGroup_CsvProfiler, +}; + +#if CPUPROFILERTRACE_ENABLED + +struct FCpuProfilerTrace +{ + CORE_API static void Init(bool bStartEnabled); + CORE_API static uint16 OutputEventType(const TCHAR* Name, ECpuProfilerGroup Group); + CORE_API static void OutputBeginEvent(uint16 SpecId); + CORE_API static void OutputEndEvent(); + + struct FEventScope + { + FEventScope(uint16 InSpecId) + { + OutputBeginEvent(InSpecId); + } + + ~FEventScope() + { + OutputEndEvent(); + } + }; +}; + +#define TRACE_CPUPROFILER_EVENT_SCOPE_TEXT_GROUP(Name, Group) \ + static uint16 PREPROCESSOR_JOIN(__CpuProfilerEventSpecId, __LINE__); \ + if (PREPROCESSOR_JOIN(__CpuProfilerEventSpecId, __LINE__) == 0) { \ + PREPROCESSOR_JOIN(__CpuProfilerEventSpecId, __LINE__) = FCpuProfilerTrace::OutputEventType(Name, Group); \ + } \ + FCpuProfilerTrace::FEventScope PREPROCESSOR_JOIN(__CpuProfilerEventScope, __LINE__)(PREPROCESSOR_JOIN(__CpuProfilerEventSpecId, __LINE__)); + +#define TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(Name) \ + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT_GROUP(Name, CpuProfilerGroup_Default) + +#define TRACE_CPUPROFILER_EVENT_SCOPE_GROUP(Name, Group) \ + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT_GROUP(TEXT(#Name), Group); + +#define TRACE_CPUPROFILER_EVENT_SCOPE(Name) \ + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT_GROUP(TEXT(#Name), CpuProfilerGroup_Default); + +#else + +#define TRACE_CPUPROFILER_EVENT_SCOPE_GROUP(Name, Group) +#define TRACE_CPUPROFILER_EVENT_SCOPE(Name) +#define TRACE_CPUPROFILER_EVENT_SCOPE_TEXT_GROUP(Name, Group) +#define TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(Name) + +#endif diff --git a/Engine/Source/Runtime/Core/Public/ProfilingDebugging/CsvProfiler.h b/Engine/Source/Runtime/Core/Public/ProfilingDebugging/CsvProfiler.h index 8b5266c88fd9..141cf81221ea 100644 --- a/Engine/Source/Runtime/Core/Public/ProfilingDebugging/CsvProfiler.h +++ b/Engine/Source/Runtime/Core/Public/ProfilingDebugging/CsvProfiler.h @@ -14,6 +14,7 @@ #include "Async/Future.h" #include "Async/TaskGraphInterfaces.h" #include "Misc/EnumClassFlags.h" +#include "ProfilingDebugging/MiscTrace.h" // Whether to allow the CSV profiler in shipping builds. // Enable in a .Target.cs file if required. @@ -44,9 +45,15 @@ #define CSV_STAT_FNAME(StatName) (_GCsvStat_##StatName.Name) // Inline stats (no up front definition) -#define CSV_SCOPED_TIMING_STAT(Category,StatName) FScopedCsvStat _ScopedCsvStat_ ## StatName (#StatName, CSV_CATEGORY_INDEX(Category)); -#define CSV_SCOPED_TIMING_STAT_GLOBAL(StatName) FScopedCsvStat _ScopedCsvStat_ ## StatName (#StatName, CSV_CATEGORY_INDEX_GLOBAL); -#define CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StatName) FScopedCsvStatExclusive _ScopedCsvStatExclusive_ ## StatName (#StatName); +#define CSV_SCOPED_TIMING_STAT(Category,StatName) \ + FScopedCsvStat _ScopedCsvStat_ ## StatName (#StatName, CSV_CATEGORY_INDEX(Category)); \ + TRACE_CPUPROFILER_EVENT_SCOPE_GROUP(StatName, CpuProfilerGroup_CsvProfiler) +#define CSV_SCOPED_TIMING_STAT_GLOBAL(StatName) \ + FScopedCsvStat _ScopedCsvStat_ ## StatName (#StatName, CSV_CATEGORY_INDEX_GLOBAL); \ + TRACE_CPUPROFILER_EVENT_SCOPE_GROUP(StatName, CpuProfilerGroup_CsvProfiler) +#define CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StatName) \ + FScopedCsvStatExclusive _ScopedCsvStatExclusive_ ## StatName (#StatName); \ + TRACE_CPUPROFILER_EVENT_SCOPE_GROUP(StatName, CpuProfilerGroup_CsvProfiler) #define CSV_SCOPED_TIMING_STAT_EXCLUSIVE_CONDITIONAL(StatName,Condition) FScopedCsvStatExclusiveConditional _ScopedCsvStatExclusive_ ## StatName (#StatName,Condition); #define CSV_SCOPED_WAIT_CONDITIONAL(Condition) FScopedCsvWaitConditional _ScopedCsvWait(Condition); @@ -70,8 +77,13 @@ #define CSV_DECLARE_CATEGORY_MODULE_EXTERN(Module_API,CategoryName) extern Module_API FCsvCategory _GCsvCategory_##CategoryName // Events -#define CSV_EVENT(Category, Format, ...) FCsvProfiler::RecordEventf( CSV_CATEGORY_INDEX(Category), Format, ##__VA_ARGS__ ) -#define CSV_EVENT_GLOBAL(Format, ...) FCsvProfiler::RecordEventf( CSV_CATEGORY_INDEX_GLOBAL, Format, ##__VA_ARGS__ ) +#define CSV_EVENT(Category, Format, ...) \ + FCsvProfiler::RecordEventf( CSV_CATEGORY_INDEX(Category), Format, ##__VA_ARGS__ ); \ + TRACE_BOOKMARK(Format, ##__VA_ARGS__) + +#define CSV_EVENT_GLOBAL(Format, ...) \ + FCsvProfiler::RecordEventf( CSV_CATEGORY_INDEX_GLOBAL, Format, ##__VA_ARGS__ ); \ + TRACE_BOOKMARK(Format, ##__VA_ARGS__) // Metadata #define CSV_METADATA(Key,Value) FCsvProfiler::SetMetadata( Key, Value ) diff --git a/Engine/Source/Runtime/Core/Public/ProfilingDebugging/FormatArgsTrace.h b/Engine/Source/Runtime/Core/Public/ProfilingDebugging/FormatArgsTrace.h new file mode 100644 index 000000000000..7be6aee4f715 --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/ProfilingDebugging/FormatArgsTrace.h @@ -0,0 +1,130 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "Misc/CString.h" +#include "Templates/IsFloatingPoint.h" +#include "Templates/UnrealTypeTraits.h" +#include "Templates/UnrealTemplate.h" + +struct FFormatArgsTrace +{ + enum EFormatArgTypeCode + { + FormatArgTypeCode_CategoryBitShift = 6, + FormatArgTypeCode_SizeBitMask = (1 << FormatArgTypeCode_CategoryBitShift) - 1, + FormatArgTypeCode_CategoryBitMask = ~FormatArgTypeCode_SizeBitMask, + FormatArgTypeCode_CategoryInteger = 1 << FormatArgTypeCode_CategoryBitShift, + FormatArgTypeCode_CategoryFloatingPoint = 2 << FormatArgTypeCode_CategoryBitShift, + FormatArgTypeCode_CategoryString = 3 << FormatArgTypeCode_CategoryBitShift, + }; + + template + static uint16 EncodeArguments(uint8(&Buffer)[BufferSize], Types... FormatArgs) + { + static_assert(BufferSize < 65536, "Maximum buffer size of 16 bits exceeded"); + uint64 FormatArgsCount = sizeof...(FormatArgs); + if (FormatArgsCount >= 256) + { + // Nope + return 0; + } + uint64 FormatArgsSize = 1 + FormatArgsCount + GetArgumentsEncodedSize(FormatArgs...); + if (FormatArgsSize > BufferSize) + { + // Nope + return 0; + } + uint8* TypeCodesBufferPtr = Buffer; + *TypeCodesBufferPtr++ = uint8(FormatArgsCount); + uint8* PayloadBufferPtr = TypeCodesBufferPtr + FormatArgsCount; + EncodeArgumentsInternal(TypeCodesBufferPtr, PayloadBufferPtr, FormatArgs...); + check(PayloadBufferPtr - Buffer == FormatArgsSize); + return FormatArgsSize; + } + +private: + template + struct TIsStringArgument + { + enum { Value = TAnd, TIsCharType::Type>::Type>>::Value }; + }; + + template + constexpr static typename TEnableIf::Value, uint64>::Type GetArgumentEncodedSize(T Argument) + { + return sizeof(T); + } + + template ::Type>::Type> + static typename TEnableIf::Value, uint64>::Type GetArgumentEncodedSize(T Argument) + { + return (TCString::Strlen(Argument) + 1) * sizeof(CharType); + } + + constexpr static uint64 GetArgumentsEncodedSize() + { + return 0; + } + + template + static uint64 GetArgumentsEncodedSize(T Head, Types... Tail) + { + return GetArgumentEncodedSize(Head) + GetArgumentsEncodedSize(Tail...); + } + + template + static typename TEnableIf>, TNot>>::Value>::Type EncodeArgumentInternal(uint8*& TypeCodesPtr, uint8*& PayloadPtr, T Argument) + { + *TypeCodesPtr++ = FormatArgTypeCode_CategoryInteger | sizeof(T); + +#if PLATFORM_SUPPORTS_UNALIGNED_LOADS + *reinterpret_cast(PayloadPtr) = Argument; +#else + // For ARM targets, it's possible that using __packed here would be preferable + // but I have not checked the codegen -- it's possible that the compiler generates + // the same code for this fixed size memcpy + memcpy(PayloadPtr, &Argument, sizeof Argument); +#endif + + PayloadPtr += sizeof(T); + } + + template + static typename TEnableIf::Value>::Type EncodeArgumentInternal(uint8*& TypeCodesPtr, uint8*& PayloadPtr, T Argument) + { + *TypeCodesPtr++ = FormatArgTypeCode_CategoryFloatingPoint | sizeof(T); + +#if PLATFORM_SUPPORTS_UNALIGNED_LOADS + *reinterpret_cast(PayloadPtr) = Argument; +#else + // For ARM targets, it's possible that using __packed here would be preferable + // but I have not checked the codegen -- it's possible that the compiler generates + // the same code for this fixed size memcpy + memcpy(PayloadPtr, &Argument, sizeof Argument); +#endif + + PayloadPtr += sizeof(T); + } + + template ::Type>::Type> + static typename TEnableIf::Value>::Type EncodeArgumentInternal(uint8*& TypeCodesPtr, uint8*& PayloadPtr, T Argument) + { + *TypeCodesPtr++ = FormatArgTypeCode_CategoryString | sizeof(CharType); + uint16 Length = (TCString::Strlen(Argument) + 1) * sizeof(CharType); + memcpy(PayloadPtr, Argument, Length); + PayloadPtr += Length; + } + + constexpr static void EncodeArgumentsInternal(uint8*& TypeCodesPtr, uint8*& PayloadPtr) + { + } + + template + static void EncodeArgumentsInternal(uint8*& ArgDescriptorsPtr, uint8*& ArgPayloadPtr, T Head, Types... Tail) + { + EncodeArgumentInternal(ArgDescriptorsPtr, ArgPayloadPtr, Head); + EncodeArgumentsInternal(ArgDescriptorsPtr, ArgPayloadPtr, Tail...); + } +}; \ No newline at end of file diff --git a/Engine/Source/Runtime/Core/Public/ProfilingDebugging/Histogram.h b/Engine/Source/Runtime/Core/Public/ProfilingDebugging/Histogram.h index 1005e1387fc5..acfdaf7b1a95 100644 --- a/Engine/Source/Runtime/Core/Public/ProfilingDebugging/Histogram.h +++ b/Engine/Source/Runtime/Core/Public/ProfilingDebugging/Histogram.h @@ -35,6 +35,12 @@ struct CORE_API FHistogram /** Inits histogram with the specified bin boundaries, with the final bucket extending to infinity (e.g., passing in 0,5 creates a [0..5) bucket and a [5..infinity) bucket) */ void InitFromArray(TArrayView Thresholds); + /** Inits histogram with the specified bin boundaries, with the final bucket extending to infinity (e.g., passing in 0,5 creates a [0..5) bucket and a [5..infinity) bucket) */ + FORCEINLINE void InitFromArray(std::initializer_list Thresholds) + { + InitFromArray(MakeArrayView(Thresholds)); + } + /** Resets measurements, without resetting the configured bins. */ void Reset(); diff --git a/Engine/Source/Runtime/Core/Public/ProfilingDebugging/LoadTimeTracker.h b/Engine/Source/Runtime/Core/Public/ProfilingDebugging/LoadTimeTracker.h index 5f1120927262..fb9f5a621c70 100644 --- a/Engine/Source/Runtime/Core/Public/ProfilingDebugging/LoadTimeTracker.h +++ b/Engine/Source/Runtime/Core/Public/ProfilingDebugging/LoadTimeTracker.h @@ -11,6 +11,7 @@ #include "Containers/Map.h" #include "UObject/NameTypes.h" #include "ProfilingDebugging/ScopedTimers.h" +#include "Trace/Trace.h" #ifndef ENABLE_LOADTIME_TRACKING #define ENABLE_LOADTIME_TRACKING 0 @@ -211,6 +212,7 @@ struct CORE_API FScopedLoadTimeAccumulatorTimer : public FScopedDurationTimer #define SCOPED_LOADTIMER(TimerName) FScopedDurationTimer DurationTimer_##TimerName(FLoadTimeTracker::Get().TimerName); #define SCOPED_LOADTIMER_CNT(TimerName) FScopedDurationTimer DurationTimer_##TimerName(FLoadTimeTracker::Get().TimerName); FLoadTimeTracker::Get().TimerName##Cnt++; #else -#define SCOPED_LOADTIMER(TimerName) + +#define SCOPED_LOADTIMER(TimerName) TRACE_CPUPROFILER_EVENT_SCOPE_GROUP(TimerName, CpuProfilerGroup_LoadTime) #define SCOPED_LOADTIMER_CNT(TimerName) #endif diff --git a/Engine/Source/Runtime/Core/Public/ProfilingDebugging/MiscTrace.h b/Engine/Source/Runtime/Core/Public/ProfilingDebugging/MiscTrace.h new file mode 100644 index 000000000000..aa5df58c7fa5 --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/ProfilingDebugging/MiscTrace.h @@ -0,0 +1,122 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "Trace/Trace.h" + +#if UE_TRACE_ENABLED && !UE_BUILD_SHIPPING +#define MISCTRACE_ENABLED 1 +#else +#define MISCTRACE_ENABLED 0 +#endif + +enum ETraceFrameType +{ + TraceFrameType_Game, + TraceFrameType_Rendering, + + TraceFrameType_Count +}; + +struct FTraceUtils +{ + static void Encode7bit(uint64 Value, uint8*& BufferPtr) + { + do + { + uint8 HasMoreBytes = (Value > uint64(0x7F)) << 7; + *(BufferPtr++) = (uint8)(Value & 0x7F) | HasMoreBytes; + Value >>= 7; + } while (Value > 0); + } + + static void EncodeZigZag(int64 Value, uint8*& BufferPtr) + { + Encode7bit((Value << 1) ^ (Value >> 63), BufferPtr); + } +}; + +#if MISCTRACE_ENABLED + +#include "ProfilingDebugging/FormatArgsTrace.h" + +class FName; + +struct FMiscTrace +{ + CORE_API static void OutputRegisterGameThread(uint32 Id); + CORE_API static void OutputCreateThread(uint32 Id, const TCHAR* Name, uint32 Priority); + CORE_API static void OutputSetThreadGroup(uint32 Id, const ANSICHAR* GroupName); + CORE_API static void OutputBeginThreadGroupScope(const ANSICHAR* GroupName); + CORE_API static void OutputEndThreadGroupScope(); + CORE_API static void OutputBookmarkSpec(const void* BookmarkPoint, const ANSICHAR* File, int32 Line, const TCHAR* Format); + template + static void OutputBookmark(const void* BookmarkPoint, Types... FormatArgs) + { + uint8 FormatArgsBuffer[4096]; + uint16 FormatArgsSize = FFormatArgsTrace::EncodeArguments(FormatArgsBuffer, FormatArgs...); + if (FormatArgsSize) + { + OutputBookmarkInternal(BookmarkPoint, FormatArgsSize, FormatArgsBuffer); + } + } + + CORE_API static void OutputBeginFrame(ETraceFrameType FrameType); + CORE_API static void OutputEndFrame(ETraceFrameType FrameType); + + struct FThreadGroupScope + { + FThreadGroupScope(const ANSICHAR* GroupName) + { + FMiscTrace::OutputBeginThreadGroupScope(GroupName); + } + + ~FThreadGroupScope() + { + FMiscTrace::OutputEndThreadGroupScope(); + } + }; + +private: + CORE_API static void OutputBookmarkInternal(const void* BookmarkPoint, uint16 EncodedFormatArgsSize, uint8* EncodedFormatArgs); +}; + +#define TRACE_REGISTER_GAME_THREAD(Id) \ + FMiscTrace::OutputRegisterGameThread(Id); + +#define TRACE_CREATE_THREAD(Id, Name, Priority) \ + FMiscTrace::OutputCreateThread(Id, Name, Priority); + +#define TRACE_SET_THREAD_GROUP(Id, Group) \ + FMiscTrace::OutputSetThreadGroup(Id, Group); + +#define TRACE_THREAD_GROUP_SCOPE(Group) \ + FMiscTrace::FThreadGroupScope ANONYMOUS_VARIABLE(ThreadGroupScope) (Group); + +#define TRACE_BOOKMARK(Format, ...) \ + static bool PREPROCESSOR_JOIN(__BookmarkPoint, __LINE__); \ + if (!PREPROCESSOR_JOIN(__BookmarkPoint, __LINE__)) \ + { \ + FMiscTrace::OutputBookmarkSpec(&PREPROCESSOR_JOIN(__BookmarkPoint, __LINE__), __FILE__, __LINE__, Format); \ + PREPROCESSOR_JOIN(__BookmarkPoint, __LINE__) = true; \ + } \ + FMiscTrace::OutputBookmark(&PREPROCESSOR_JOIN(__BookmarkPoint, __LINE__), ##__VA_ARGS__); + +#define TRACE_BEGIN_FRAME(FrameType) \ + FMiscTrace::OutputBeginFrame(FrameType); + +#define TRACE_END_FRAME(FrameType) \ + FMiscTrace::OutputEndFrame(FrameType); + +#else + +#define TRACE_REGISTER_GAME_THREAD(...) +#define TRACE_CREATE_THREAD(...) +#define TRACE_SET_THREAD_GROUP(...) +#define TRACE_THREAD_GROUP_SCOPE(...) +#define TRACE_BOOKMARK(...) +#define TRACE_BEGIN_FRAME(...) +#define TRACE_END_FRAME(...) + +#endif \ No newline at end of file diff --git a/Engine/Source/Runtime/Core/Public/ProfilingDebugging/PlatformFileTrace.h b/Engine/Source/Runtime/Core/Public/ProfilingDebugging/PlatformFileTrace.h new file mode 100644 index 000000000000..129e78e82432 --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/ProfilingDebugging/PlatformFileTrace.h @@ -0,0 +1,65 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "Trace/Trace.h" + +#if UE_TRACE_ENABLED && !UE_BUILD_SHIPPING +#define PLATFORMFILETRACE_ENABLED 0 +#else +#define PLATFORMFILETRACE_ENABLED 0 +#endif + +#if PLATFORMFILETRACE_ENABLED + +struct FPlatformFileTrace +{ + static void BeginOpen(uint64 TempHandle, const TCHAR* Path); + static void EndOpen(uint64 TempHandle, uint64 FileHandle); + static void BeginClose(uint64 TempHandle, uint64 FileHandle); + static void EndClose(uint64 TempHandle); + static void BeginRead(uint64 ReadHandle, uint64 FileHandle, uint64 Offset, uint64 Size); + static void EndRead(uint64 ReadHandle, uint64 SizeRead); + static void BeginWrite(uint64 WriteHandle, uint64 FileHandle, uint64 Offset, uint64 Size); + static void EndWrite(uint64 WriteHandle, uint64 SizeWritten); +}; + +#define TRACE_PLATFORMFILE_BEGIN_OPEN(Path) \ + uint64 __TempHandle; \ + FPlatformFileTrace::BeginOpen(uint64(&__TempHandle), Path); + +#define TRACE_PLATFORMFILE_END_OPEN(FileHandle) \ + FPlatformFileTrace::EndOpen(uint64(&__TempHandle), uint64(FileHandle)); + +#define TRACE_PLATFORMFILE_BEGIN_CLOSE(FileHandle) \ + uint64 __TempHandle; \ + FPlatformFileTrace::BeginClose(uint64(&__TempHandle), uint64(FileHandle)); + +#define TRACE_PLATFORMFILE_END_CLOSE() \ + FPlatformFileTrace::EndClose(uint64(&__TempHandle)); + +#define TRACE_PLATFORMFILE_BEGIN_READ(ReadHandle, FileHandle, Offset, Size) \ + FPlatformFileTrace::BeginRead(uint64(ReadHandle), uint64(FileHandle), Offset, Size); + +#define TRACE_PLATFORMFILE_END_READ(ReadHandle, SizeRead) \ + FPlatformFileTrace::EndRead(uint64(ReadHandle), SizeRead); + +#define TRACE_PLATFORMFILE_BEGIN_WRITE(WriteHandle, FileHandle, Offset, Size) \ + FPlatformFileTrace::BeginWrite(uint64(WriteHandle), uint64(FileHandle), Offset, Size); + +#define TRACE_PLATFORMFILE_END_WRITE(WriteHandle, SizeWritten) \ + FPlatformFileTrace::EndWrite(uint64(WriteHandle), SizeWritten); + +#else + +#define TRACE_PLATFORMFILE_BEGIN_OPEN(Path) +#define TRACE_PLATFORMFILE_END_OPEN(FileHandle) +#define TRACE_PLATFORMFILE_BEGIN_CLOSE(FileHandle) +#define TRACE_PLATFORMFILE_END_CLOSE() +#define TRACE_PLATFORMFILE_BEGIN_READ(ReadHandle, FileHandle, Offset, Size) +#define TRACE_PLATFORMFILE_END_READ(ReadHandle, SizeRead) +#define TRACE_PLATFORMFILE_BEGIN_WRITE(WriteHandle, FileHandle, Offset, Size) +#define TRACE_PLATFORMFILE_END_WRITE(WriteHandle, SizeWritten) + +#endif \ No newline at end of file diff --git a/Engine/Source/Runtime/Core/Public/Serialization/Archive.h b/Engine/Source/Runtime/Core/Public/Serialization/Archive.h index 9d74e2ebab75..45664feba035 100644 --- a/Engine/Source/Runtime/Core/Public/Serialization/Archive.h +++ b/Engine/Source/Runtime/Core/Public/Serialization/Archive.h @@ -746,8 +746,6 @@ public: * @param bTreatBufferAsFileReader true if V is actually an FArchive, which is used when saving to read data - helps to avoid single huge allocations of source data * @param bUsePlatformBitWindow use a platform specific bitwindow setting */ - UE_DEPRECATED(4.20, "Use the FName based version of SerializeCompressed (which also removes the basically-unused bUsePlatformBitWindow)") - void SerializeCompressed(void* V, int64 Length, ECompressionFlags Flags = COMPRESS_NoFlags, bool bTreatBufferAsFileReader = false, bool bUsePlatformBitWindow = false); void SerializeCompressed(void* V, int64 Length, FName CompressionFormat, ECompressionFlags Flags=COMPRESS_NoFlags, bool bTreatBufferAsFileReader=false); @@ -895,7 +893,6 @@ public: return this; } -PRAGMA_DISABLE_DEPRECATION_WARNINGS FORCEINLINE bool IsLoading() const { return ArIsLoading; @@ -920,7 +917,7 @@ PRAGMA_DISABLE_DEPRECATION_WARNINGS FORCEINLINE bool IsTextFormat() const { - return ArIsTextFormat; + return (ArIsTextFormat && WITH_TEXT_ARCHIVE_SUPPORT); } FORCEINLINE bool WantBinaryPropertySerialization() const @@ -937,7 +934,6 @@ PRAGMA_DISABLE_DEPRECATION_WARNINGS { return ArIsPersistent; } -PRAGMA_ENABLE_DEPRECATION_WARNINGS FORCEINLINE bool IsError() const { @@ -1140,6 +1136,7 @@ PRAGMA_ENABLE_DEPRECATION_WARNINGS /** * Indicates whether this archive is saving or loading game state * + * @note This is intended for game-specific archives and is not true for any of the build in save methods * @return true if the archive is dealing with save games, false otherwise. */ bool IsSaveGame() @@ -1369,35 +1366,29 @@ private: /** Copies all of the members except CustomVersionContainer */ void CopyTrivialFArchiveStatusMembers(const FArchive& ArchiveStatusToCopy); -public: /** Whether this archive is for loading data. */ - UE_DEPRECATED(4.20, "Direct access to ArIsLoading has been deprecated - please use IsLoading() and SetIsLoading() instead.") uint8 ArIsLoading : 1; /** Whether this archive is for saving data. */ - UE_DEPRECATED(4.20, "Direct access to ArIsSaving has been deprecated - please use IsSaving() and SetIsSaving() instead.") uint8 ArIsSaving : 1; /** Whether archive is transacting. */ - UE_DEPRECATED(4.20, "Direct access to ArIsTransacting has been deprecated - please use IsTransacting() and SetIsTransacting() instead.") uint8 ArIsTransacting : 1; /** Whether this archive serializes to a text format. Text format archives should use high level constructs from FStructuredArchive for delimiting data rather than manually seeking through the file. */ - UE_DEPRECATED(4.20, "Direct access to ArIsTextFormat has been deprecated - please use IsTextFormat() and SetIsTextFormat() instead.") uint8 ArIsTextFormat : 1; /** Whether this archive wants properties to be serialized in binary form instead of tagged. */ - UE_DEPRECATED(4.20, "Direct access to ArWantBinaryPropertySerialization has been deprecated - please use WantBinaryPropertySerialization() and SetWantBinaryPropertySerialization() instead.") uint8 ArWantBinaryPropertySerialization : 1; /** Whether this archive wants to always save strings in unicode format */ - UE_DEPRECATED(4.20, "Direct access to ArForceUnicode has been deprecated - please use ForceUnicode() and SetForceUnicode() instead.") uint8 ArForceUnicode : 1; /** Whether this archive saves to persistent storage. */ - UE_DEPRECATED(4.20, "Direct access to ArIsPersistent has been deprecated - please use IsPersistent() and SetIsPersistent() instead.") uint8 ArIsPersistent : 1; +public: + /** Whether this archive contains errors. */ uint8 ArIsError : 1; diff --git a/Engine/Source/Runtime/Core/Public/Serialization/ArchiveAdapters.h b/Engine/Source/Runtime/Core/Public/Serialization/ArchiveAdapters.h new file mode 100644 index 000000000000..99c373c2aeb3 --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/Serialization/ArchiveAdapters.h @@ -0,0 +1,47 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Concepts/Insertable.h" +#include "Templates/Models.h" +//#include "ArchiveFromStructuredArchive.h" +//#include "StructuredArchiveFromArchive.h" + +/** + * Adapter operator which allows a type to stream to an FStructuredArchive::FSlot when it already supports streaming to an FArchive. + * + * @param Slot The slot to read from or write to. + * @param Obj The object to read or write. + */ +template +typename TEnableIf< + TModels, T>::Value && + !TModels, T>::Value +>::Type operator<<(FStructuredArchive::FSlot Slot, T& Obj) +{ +#if WITH_TEXT_ARCHIVE_SUPPORT + FArchiveFromStructuredArchive Ar(Slot); +#else + FArchive& Ar = Slot.GetUnderlyingArchive(); +#endif + Ar << Obj; +} + +/** + * Adapter operator which allows a type to stream to an FArchive when it already supports streaming to an FStructuredArchive::FSlot. + * + * @param Ar The archive to read from or write to. + * @param Obj The object to read or write. + * + * @return A reference to the same archive as Ar. + */ +template +typename TEnableIf< + !TModels, T>::Value && TModels, T>::Value, + FArchive& +>::Type operator<<(FArchive& Ar, T& Obj) +{ + FStructuredArchiveFromArchive ArAdapt(Ar); + ArAdapt.GetSlot() << Obj; + return Ar; +} \ No newline at end of file diff --git a/Engine/Source/Runtime/Core/Public/Serialization/ArchiveFromStructuredArchive.h b/Engine/Source/Runtime/Core/Public/Serialization/ArchiveFromStructuredArchive.h index 0dec07c5b2f9..dd50defd7dd7 100644 --- a/Engine/Source/Runtime/Core/Public/Serialization/ArchiveFromStructuredArchive.h +++ b/Engine/Source/Runtime/Core/Public/Serialization/ArchiveFromStructuredArchive.h @@ -9,6 +9,10 @@ #include "UObject/NameTypes.h" #include "Containers/Map.h" #include "Containers/BitArray.h" +#include "Concepts/Insertable.h" +#include "Templates/Models.h" + +#if WITH_TEXT_ARCHIVE_SUPPORT class CORE_API FArchiveFromStructuredArchive : public FArchiveProxy { @@ -65,4 +69,25 @@ private: TMap ObjectToIndex; FStructuredArchive::FSlot RootSlot; -}; \ No newline at end of file +}; + +#else + +class CORE_API FArchiveFromStructuredArchive +{ +public: + + FArchiveFromStructuredArchive(FStructuredArchive::FSlot InSlot) + : Ar(InSlot.GetUnderlyingArchive()) + { + + } + + operator FArchive& () { return Ar; } + +private: + + FArchive& Ar; +}; + +#endif \ No newline at end of file diff --git a/Engine/Source/Runtime/Core/Public/Serialization/ArchiveProxy.h b/Engine/Source/Runtime/Core/Public/Serialization/ArchiveProxy.h index 9453c9891ef2..9a8d60f3f1f6 100644 --- a/Engine/Source/Runtime/Core/Public/Serialization/ArchiveProxy.h +++ b/Engine/Source/Runtime/Core/Public/Serialization/ArchiveProxy.h @@ -12,7 +12,7 @@ * * Archive proxies are archive types that modify the behavior of another archive type. */ -class FArchiveProxy : public FArchive +class CORE_VTABLE FArchiveProxy : public FArchive { public: /** diff --git a/Engine/Source/Runtime/Core/Public/Serialization/BufferArchive.h b/Engine/Source/Runtime/Core/Public/Serialization/BufferArchive.h index 6e678f9c43c8..85a1d48a87ee 100644 --- a/Engine/Source/Runtime/Core/Public/Serialization/BufferArchive.h +++ b/Engine/Source/Runtime/Core/Public/Serialization/BufferArchive.h @@ -15,7 +15,7 @@ class FBufferArchive : public FMemoryWriter, public TArray { public: FBufferArchive( bool bIsPersistent = false, const FName InArchiveName = NAME_None ) - : FMemoryWriter( (TArray&)*this, bIsPersistent, false, InArchiveName ) + : FMemoryWriter( *static_cast*>(this), bIsPersistent, false, InArchiveName ) {} /** * Returns the name of the Archive. Useful for getting the name of the package a struct or object diff --git a/Engine/Source/Runtime/Core/Public/Serialization/CustomVersion.h b/Engine/Source/Runtime/Core/Public/Serialization/CustomVersion.h index c142a6a7e59a..d489ae80d4fa 100644 --- a/Engine/Source/Runtime/Core/Public/Serialization/CustomVersion.h +++ b/Engine/Source/Runtime/Core/Public/Serialization/CustomVersion.h @@ -144,6 +144,11 @@ public: */ void Empty(); + /** + * Sorts the custom version container by key. + */ + void SortByKey(); + /** Return a string representation of custom versions. Used for debug. */ FString ToString(const FString& Indent) const; diff --git a/Engine/Source/Runtime/Core/Public/Serialization/NameAsStringProxyArchive.h b/Engine/Source/Runtime/Core/Public/Serialization/NameAsStringProxyArchive.h index b8fe227edf35..877ba950eb71 100644 --- a/Engine/Source/Runtime/Core/Public/Serialization/NameAsStringProxyArchive.h +++ b/Engine/Source/Runtime/Core/Public/Serialization/NameAsStringProxyArchive.h @@ -8,7 +8,7 @@ /** * Implements a proxy archive that serializes FNames as string data. */ -struct FNameAsStringProxyArchive : public FArchiveProxy +struct CORE_VTABLE FNameAsStringProxyArchive : public FArchiveProxy { /** * Creates and initializes a new instance. diff --git a/Engine/Source/Runtime/Core/Public/Serialization/StructuredArchive.h b/Engine/Source/Runtime/Core/Public/Serialization/StructuredArchive.h index f6472094f792..cf553573a5ee 100644 --- a/Engine/Source/Runtime/Core/Public/Serialization/StructuredArchive.h +++ b/Engine/Source/Runtime/Core/Public/Serialization/StructuredArchive.h @@ -311,7 +311,7 @@ public: }; /** - * Represents a map in the structured archive. A map is similar to an object, but keys can be read back out from an archive. + * Represents a map in the structured archive. A map is similar to a record, but keys can be read back out from an archive. * (This is an important distinction for binary archives). */ class CORE_API FMap diff --git a/Engine/Source/Runtime/Core/Public/Stats/Stats2.h b/Engine/Source/Runtime/Core/Public/Stats/Stats2.h index da7b8cff5935..4fb6a4903ffa 100644 --- a/Engine/Source/Runtime/Core/Public/Stats/Stats2.h +++ b/Engine/Source/Runtime/Core/Public/Stats/Stats2.h @@ -19,6 +19,9 @@ #include "Math/Color.h" #include "StatsCommon.h" #include "Templates/UniquePtr.h" +#include "ProfilingDebugging/CpuProfilerTrace.h" +#include "ProfilingDebugging/MiscTrace.h" +#include "StatsTrace.h" class FScopeCycleCounter; class FThreadStats; @@ -120,6 +123,10 @@ struct TStatIdData /** const WIDECHAR* pointer to a string describing the stat */ TUniquePtr StatDescriptionAnsi; + +#if CPUPROFILERTRACE_ENABLED + uint32 TraceCpuProfilerSpecId = 0; +#endif }; struct TStatId @@ -155,6 +162,13 @@ struct TStatId return MinimalNameToName(StatIdPtr->Name); } +#if CPUPROFILERTRACE_ENABLED + FORCEINLINE uint16 GetTraceCpuProfilerSpecId() const + { + return StatIdPtr->TraceCpuProfilerSpecId; + } +#endif + FORCEINLINE static const TStatIdData& GetStatNone() { return TStatId_NAME_None; @@ -651,7 +665,7 @@ private: /** For FName. */ CORE_API const FString GetName() const { - return FName::SafeString( (int32)Cycles ); + return FName::SafeString(FNameEntryId::FromUnstableInt(static_cast(Cycles))); } }; @@ -1545,6 +1559,9 @@ class FCycleCounter { /** Name of the stat, usually a short name **/ FName StatId; +#if CPUPROFILERTRACE_ENABLED + uint16 TraceCpuProfilerSpecId = 0; +#endif public: @@ -1559,6 +1576,13 @@ public: { return; } +#if CPUPROFILERTRACE_ENABLED + if (bAlways || GCycleStatsShouldEmitNamedEvents > 0) + { + TraceCpuProfilerSpecId = InStatId.GetTraceCpuProfilerSpecId(); + FCpuProfilerTrace::OutputBeginEvent(TraceCpuProfilerSpecId); + } +#endif if( (bAlways && FThreadStats::WillEverCollectData()) || FThreadStats::IsCollectingData() ) { @@ -1583,6 +1607,13 @@ public: */ FORCEINLINE_STATS void Stop() { +#if CPUPROFILERTRACE_ENABLED + if (TraceCpuProfilerSpecId) + { + FCpuProfilerTrace::OutputEndEvent(); + TraceCpuProfilerSpecId = 0; + } +#endif if( !StatId.IsNone() ) { FThreadStats::AddMessage(StatId, EStatOperation::CycleScopeEnd); @@ -1946,74 +1977,126 @@ struct FStat_##StatName\ #define INC_DWORD_STAT(Stat) \ {\ if (FThreadStats::IsCollectingData() || !GET_STATISEVERYFRAME(Stat)) \ + { \ FThreadStats::AddMessage(GET_STATFNAME(Stat), EStatOperation::Add, int64(1));\ + TRACE_STAT_INCREMENT(GET_STATFNAME(Stat)); \ + } \ } #define INC_FLOAT_STAT_BY(Stat, Amount) \ {\ if (Amount != 0.0f) \ + { \ if (FThreadStats::IsCollectingData() || !GET_STATISEVERYFRAME(Stat)) \ - FThreadStats::AddMessage(GET_STATFNAME(Stat), EStatOperation::Add, double(Amount));\ + { \ + FThreadStats::AddMessage(GET_STATFNAME(Stat), EStatOperation::Add, double(Amount));\ + TRACE_STAT_ADD(GET_STATFNAME(Stat), double(Amount)); \ + } \ + } \ } #define INC_DWORD_STAT_BY(Stat, Amount) \ {\ if (Amount != 0) \ + { \ if (FThreadStats::IsCollectingData() || !GET_STATISEVERYFRAME(Stat)) \ + { \ FThreadStats::AddMessage(GET_STATFNAME(Stat), EStatOperation::Add, int64(Amount));\ + TRACE_STAT_ADD(GET_STATFNAME(Stat), int64(Amount)); \ + } \ + } \ } #define INC_DWORD_STAT_FNAME_BY(StatFName, Amount) \ {\ if (Amount != 0) \ - FThreadStats::AddMessage(StatFName, EStatOperation::Add, int64(Amount));\ + { \ + FThreadStats::AddMessage(StatFName, EStatOperation::Add, int64(Amount));\ + TRACE_STAT_ADD(StatFName, int64(Amount)); \ + } \ } #define INC_MEMORY_STAT_BY(Stat, Amount) \ {\ if (Amount != 0) \ + { \ if (FThreadStats::IsCollectingData() || !GET_STATISEVERYFRAME(Stat)) \ + { \ FThreadStats::AddMessage(GET_STATFNAME(Stat), EStatOperation::Add, int64(Amount));\ + TRACE_STAT_ADD(GET_STATFNAME(Stat), int64(Amount)); \ + } \ + } \ } #define DEC_DWORD_STAT(Stat) \ {\ if (FThreadStats::IsCollectingData() || !GET_STATISEVERYFRAME(Stat)) \ + { \ FThreadStats::AddMessage(GET_STATFNAME(Stat), EStatOperation::Subtract, int64(1));\ + TRACE_STAT_DECREMENT(GET_STATFNAME(Stat)); \ + } \ } #define DEC_FLOAT_STAT_BY(Stat,Amount) \ {\ if (Amount != 0.0f) \ + { \ if (FThreadStats::IsCollectingData() || !GET_STATISEVERYFRAME(Stat)) \ + { \ FThreadStats::AddMessage(GET_STATFNAME(Stat), EStatOperation::Subtract, double(Amount));\ + TRACE_STAT_ADD(GET_STATFNAME(Stat), -double(Amount)); \ + } \ + } \ } #define DEC_DWORD_STAT_BY(Stat,Amount) \ {\ if (Amount != 0) \ + { \ if (FThreadStats::IsCollectingData() || !GET_STATISEVERYFRAME(Stat)) \ + { \ FThreadStats::AddMessage(GET_STATFNAME(Stat), EStatOperation::Subtract, int64(Amount));\ + TRACE_STAT_ADD(GET_STATFNAME(Stat), -int64(Amount)); \ + } \ + } \ } #define DEC_DWORD_STAT_FNAME_BY(StatFName,Amount) \ {\ if (Amount != 0) \ - FThreadStats::AddMessage(StatFName, EStatOperation::Subtract, int64(Amount));\ + { \ + FThreadStats::AddMessage(StatFName, EStatOperation::Subtract, int64(Amount));\ + TRACE_STAT_ADD(StatFName, -int64(Amount)); \ + } \ } #define DEC_MEMORY_STAT_BY(Stat,Amount) \ {\ if (Amount != 0) \ + { \ if (FThreadStats::IsCollectingData() || !GET_STATISEVERYFRAME(Stat)) \ + { \ FThreadStats::AddMessage(GET_STATFNAME(Stat), EStatOperation::Subtract, int64(Amount));\ + TRACE_STAT_ADD(GET_STATFNAME(Stat), -int64(Amount)); \ + } \ + } \ } #define SET_MEMORY_STAT(Stat,Value) \ {\ if (FThreadStats::IsCollectingData() || !GET_STATISEVERYFRAME(Stat)) \ + { \ FThreadStats::AddMessage(GET_STATFNAME(Stat), EStatOperation::Set, int64(Value));\ + TRACE_STAT_SET(GET_STATFNAME(Stat), int64(Value)); \ + } \ } #define SET_DWORD_STAT(Stat,Value) \ {\ if (FThreadStats::IsCollectingData() || !GET_STATISEVERYFRAME(Stat)) \ + { \ FThreadStats::AddMessage(GET_STATFNAME(Stat), EStatOperation::Set, int64(Value));\ + TRACE_STAT_SET(GET_STATFNAME(Stat), int64(Value)); \ + } \ } #define SET_FLOAT_STAT(Stat,Value) \ {\ if (FThreadStats::IsCollectingData() || !GET_STATISEVERYFRAME(Stat)) \ + { \ FThreadStats::AddMessage(GET_STATFNAME(Stat), EStatOperation::Set, double(Value));\ + TRACE_STAT_SET(GET_STATFNAME(Stat), double(Value)); \ + } \ } + #define STAT_ADD_CUSTOMMESSAGE_NAME(Stat,Value) \ {\ FThreadStats::AddMessage(GET_STATFNAME(Stat), EStatOperation::SpecialMessageMarker, FName(Value));\ @@ -2031,52 +2114,75 @@ struct FStat_##StatName\ #define INC_DWORD_STAT_FName(Stat) \ {\ FThreadStats::AddMessage(Stat, EStatOperation::Add, int64(1));\ + TRACE_STAT_INCREMENT(Stat); \ } #define INC_FLOAT_STAT_BY_FName(Stat, Amount) \ {\ if (Amount != 0.0f) \ + { \ FThreadStats::AddMessage(Stat, EStatOperation::Add, double(Amount));\ + TRACE_STAT_ADD(Stat, double(Amount)); \ + } \ } #define INC_DWORD_STAT_BY_FName(Stat, Amount) \ {\ if (Amount != 0) \ + { \ FThreadStats::AddMessage(Stat, EStatOperation::Add, int64(Amount));\ + TRACE_STAT_ADD(Stat, int64(Amount)); \ + } \ } #define INC_MEMORY_STAT_BY_FName(Stat, Amount) \ {\ if (Amount != 0) \ + { \ FThreadStats::AddMessage(Stat, EStatOperation::Add, int64(Amount));\ + TRACE_STAT_ADD(Stat, int64(Amount)); \ + } \ } #define DEC_DWORD_STAT_FName(Stat) \ {\ FThreadStats::AddMessage(Stat, EStatOperation::Subtract, int64(1));\ + TRACE_STAT_DECREMENT(Stat); \ } #define DEC_FLOAT_STAT_BY_FName(Stat,Amount) \ {\ if (Amount != 0.0f) \ + { \ FThreadStats::AddMessage(Stat, EStatOperation::Subtract, double(Amount));\ + TRACE_STAT_ADD(Stat, -double(Amount)); \ + } \ } #define DEC_DWORD_STAT_BY_FName(Stat,Amount) \ {\ if (Amount != 0) \ + { \ FThreadStats::AddMessage(Stat, EStatOperation::Subtract, int64(Amount));\ + TRACE_STAT_ADD(Stat, -int64(Amount)); \ + } \ } #define DEC_MEMORY_STAT_BY_FName(Stat,Amount) \ {\ if (Amount != 0) \ + { \ FThreadStats::AddMessage(Stat, EStatOperation::Subtract, int64(Amount));\ + TRACE_STAT_ADD(Stat, -int64(Amount)); \ + } \ } #define SET_MEMORY_STAT_FName(Stat,Value) \ {\ FThreadStats::AddMessage(Stat, EStatOperation::Set, int64(Value));\ + TRACE_STAT_SET(Stat, int64(Value)); \ } #define SET_DWORD_STAT_FName(Stat,Value) \ {\ FThreadStats::AddMessage(Stat, EStatOperation::Set, int64(Value));\ + TRACE_STAT_SET(Stat, int64(Value)); \ } #define SET_FLOAT_STAT_FName(Stat,Value) \ {\ FThreadStats::AddMessage(Stat, EStatOperation::Set, double(Value));\ + TRACE_STAT_SET(Stat, double(Value)); \ } diff --git a/Engine/Source/Runtime/Core/Public/Stats/StatsFile.h b/Engine/Source/Runtime/Core/Public/Stats/StatsFile.h index d85f550a93e2..d5aa6faf9def 100644 --- a/Engine/Source/Runtime/Core/Public/Stats/StatsFile.h +++ b/Engine/Source/Runtime/Core/Public/Stats/StatsFile.h @@ -363,8 +363,10 @@ protected: { FName RawName = NameAndInfo.GetRawName(); bool bSendFName = !FNamesSent.Contains( RawName.GetComparisonIndex() ); - int32 Index = RawName.GetComparisonIndex(); - Ar << Index; + + int32 ComparisonInt = RawName.GetComparisonIndex().ToUnstableInt(); + Ar << ComparisonInt; + int32 Number = NameAndInfo.GetRawNumber(); if (bSendFName) { @@ -415,7 +417,7 @@ protected: } /** Set of names already sent. */ - TSet FNamesSent; + TSet FNamesSent; /** Data to write. */ TArray OutData; @@ -612,7 +614,7 @@ public: FStatsStreamHeader Header; /** FNames have a different index on each machine, so we translate via this map. **/ - TMap FNamesIndexMap; + TMap FNamesIndexMap; /** Array of stats frame info. Empty for the raw stats. */ TArray FramesInfo; @@ -702,7 +704,7 @@ public: { if( FNamesIndexMap.Contains( Index ) ) { - const int32 MyIndex = FNamesIndexMap.FindChecked( Index ); + const FNameEntryId MyIndex = FNamesIndexMap.FindChecked( Index ); TheFName = FName( MyIndex, MyIndex, 0 ); } else @@ -723,7 +725,7 @@ public: } if( FNamesIndexMap.Contains( Index ) ) { - const int32 MyIndex = FNamesIndexMap.FindChecked( Index ); + const FNameEntryId MyIndex = FNamesIndexMap.FindChecked( Index ); TheFName = FName( MyIndex, MyIndex, 0 ); } else diff --git a/Engine/Source/Runtime/Core/Public/Stats/StatsTrace.h b/Engine/Source/Runtime/Core/Public/Stats/StatsTrace.h new file mode 100644 index 000000000000..3da7ab6ffa2d --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/Stats/StatsTrace.h @@ -0,0 +1,50 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "Trace/Trace.h" + +#define EXPERIMENTAL_STATSTRACE_ENABLED 0 + +#if UE_TRACE_ENABLED && STATS && EXPERIMENTAL_STATSTRACE_ENABLED +#define STATSTRACE_ENABLED 1 +#else +#define STATSTRACE_ENABLED 0 +#endif + +#if STATSTRACE_ENABLED + +class FName; + +struct FStatsTrace +{ + CORE_API static void DeclareStat(const FName& Stat, const ANSICHAR* Name, const TCHAR* Description, bool IsFloatingPoint, bool IsMemory, bool ShouldClearEveryFrame); + CORE_API static void Increment(const FName& Stat); + CORE_API static void Decrement(const FName& Stat); + CORE_API static void Add(const FName& Stat, int64 Amount); + CORE_API static void Add(const FName& Stat, double Amount); + CORE_API static void Set(const FName& Stat, int64 Value); + CORE_API static void Set(const FName& Stat, double Value); +}; + +#define TRACE_STAT_INCREMENT(Stat) \ + FStatsTrace::Increment(Stat); + +#define TRACE_STAT_DECREMENT(Stat) \ + FStatsTrace::Decrement(Stat); + +#define TRACE_STAT_ADD(Stat, Amount) \ + FStatsTrace::Add(Stat, Amount); + +#define TRACE_STAT_SET(Stat, Value) \ + FStatsTrace::Set(Stat, Value); + +#else + +#define TRACE_STAT_INCREMENT(...) +#define TRACE_STAT_DECREMENT(...) +#define TRACE_STAT_ADD(...) +#define TRACE_STAT_SET(...) + +#endif diff --git a/Engine/Source/Runtime/Core/Public/Templates/Atomic.h b/Engine/Source/Runtime/Core/Public/Templates/Atomic.h index 2c618796b412..950c8ff88e6f 100644 --- a/Engine/Source/Runtime/Core/Public/Templates/Atomic.h +++ b/Engine/Source/Runtime/Core/Public/Templates/Atomic.h @@ -154,6 +154,27 @@ namespace UE4Atomic_Private return *(const T*)&Result; } + template + FORCEINLINE T AndExchange(volatile T* Element, T AndValue) + { + auto Result = FPlatformAtomics::InterlockedAnd((volatile TUnderlyingIntegerType_T*)Element, (TUnderlyingIntegerType_T)AndValue); + return *(const T*)&Result; + } + + template + FORCEINLINE T OrExchange(volatile T* Element, T OrValue) + { + auto Result = FPlatformAtomics::InterlockedOr((volatile TUnderlyingIntegerType_T*)Element, (TUnderlyingIntegerType_T)OrValue); + return *(const T*)&Result; + } + + template + FORCEINLINE T XorExchange(volatile T* Element, T XorValue) + { + auto Result = FPlatformAtomics::InterlockedXor((volatile TUnderlyingIntegerType_T*)Element, (TUnderlyingIntegerType_T)XorValue); + return *(const T*)&Result; + } + template struct TAtomicBaseType { @@ -341,7 +362,7 @@ public: /** * Performs Element -= Value, returning a copy of the new value of the element. * - * @param Value The value to add to the element. + * @param Value The value to subtract from the element. * * @return A copy of the new, decremented value. */ @@ -350,6 +371,50 @@ public: return UE4Atomic_Private::SubExchange(&this->Element, Value) - Value; } + /** + * Increments Element and returns a copy of the previous value of the element. + * + * @return A copy of the previous, unincremented value. + */ + FORCEINLINE T IncrementExchange() + { + return UE4Atomic_Private::IncrementExchange(&this->Element); + } + + /** + * Decrements Element and returns a copy of the previous value of the element. + * + * @return A copy of the previous, undecremented value. + */ + FORCEINLINE T DecrementExchange() + { + return UE4Atomic_Private::DecrementExchange(&this->Element); + } + + /** + * Adds Value to Element and returns a copy of the previous value of the element. + * + * @param Value The value to add to the element. + * + * @return A copy of the previous, unincremented value. + */ + FORCEINLINE T AddExchange(DiffType Value) + { + return UE4Atomic_Private::AddExchange(&this->Element, Value); + } + + /** + * Subtracts Value from Element and returns a copy of the previous value of the element. + * + * @param Value The value to subtract from the element. + * + * @return A copy of the previous, undecremented value. + */ + FORCEINLINE T SubExchange(DiffType Value) + { + return UE4Atomic_Private::SubExchange(&this->Element, Value); + } + protected: TAtomicBase_Arithmetic() = default; @@ -377,7 +442,78 @@ template struct TAtomicBase_Integral : public TAtomicBase_Arithmetic { public: - // TODO: Bitwise ops + /** + * Performs Element &= Value, returning a copy of the new value of the element. + * + * @param Value The value to and the element with. + * + * @return A copy of the new value. + */ + FORCEINLINE T operator&=(const T Value) + { + return UE4Atomic_Private::AndExchange(&this->Element, Value) & Value; + } + + /** + * Performs Element |= Value, returning a copy of the new value of the element. + * + * @param Value The value to or the element with. + * + * @return A copy of the new value. + */ + FORCEINLINE T operator|=(const T Value) + { + return UE4Atomic_Private::OrExchange(&this->Element, Value) | Value; + } + + /** + * Performs Element ^= Value, returning a copy of the new value of the element. + * + * @param Value The value to xor the element with. + * + * @return A copy of the new value. + */ + FORCEINLINE T operator^=(const T Value) + { + return UE4Atomic_Private::XorExchange(&this->Element, Value) ^ Value; + } + + /** + * Performs Element &= Value, returning a copy of the previous value of the element. + * + * @param Value The value to and the element with. + * + * @return A copy of the previous value. + */ + FORCEINLINE T AndExchange(const T Value) + { + return UE4Atomic_Private::AndExchange(&this->Element, Value); + } + + /** + * Performs Element |= Value, returning a copy of the previous value of the element. + * + * @param Value The value to or the element with. + * + * @return A copy of the previous value. + */ + FORCEINLINE T OrExchange(const T Value) + { + return UE4Atomic_Private::OrExchange(&this->Element, Value); + } + + /** + * Performs Element ^= Value, returning a copy of the previous value of the element. + * + * @param Value The value to xor the element with. + * + * @return A copy of the previous value. + */ + FORCEINLINE T XorExchange(const T Value) + { + return UE4Atomic_Private::XorExchange(&this->Element, Value); + } + protected: TAtomicBase_Integral() = default; diff --git a/Engine/Source/Runtime/Core/Public/Templates/Function.h b/Engine/Source/Runtime/Core/Public/Templates/Function.h index e39310631638..0af1d08fb93a 100644 --- a/Engine/Source/Runtime/Core/Public/Templates/Function.h +++ b/Engine/Source/Runtime/Core/Public/Templates/Function.h @@ -270,23 +270,23 @@ namespace UE4Function_Private return true; } - template + template struct TStorageOwnerType; - template - struct TStorageOwnerType + template + struct TStorageOwnerType { - using Type = TFunction_UniqueOwnedObject::Type, bInline>; + using Type = TFunction_UniqueOwnedObject::Type, bOnHeap>; }; - template - struct TStorageOwnerType + template + struct TStorageOwnerType { - using Type = TFunction_CopyableOwnedObject::Type, bInline>; + using Type = TFunction_CopyableOwnedObject::Type, bOnHeap>; }; - template - using TStorageOwnerTypeT = typename TStorageOwnerType::Type; + template + using TStorageOwnerTypeT = typename TStorageOwnerType::Type; struct FFunctionStorage { diff --git a/Engine/Source/Runtime/Core/Public/Templates/HasGetTypeHash.h b/Engine/Source/Runtime/Core/Public/Templates/HasGetTypeHash.h index e3c4b6dae517..895c0bf62796 100644 --- a/Engine/Source/Runtime/Core/Public/Templates/HasGetTypeHash.h +++ b/Engine/Source/Runtime/Core/Public/Templates/HasGetTypeHash.h @@ -48,7 +48,7 @@ namespace UE4GetTypeHashExists_Private * Traits class which tests if a type has a GetTypeHash overload. */ template -struct THasGetTypeHash +struct UE_DEPRECATED(4.23, "THasGetTypeHash has been deprecated, please use TModels instead.") THasGetTypeHash { enum { Value = UE4GetTypeHashExists_Private::GetTypeHashQuery::Value }; }; diff --git a/Engine/Source/Runtime/Core/Public/Templates/HasInserterOperator.h b/Engine/Source/Runtime/Core/Public/Templates/HasInserterOperator.h index 21a27fd5a0b8..0857414a3a6f 100644 --- a/Engine/Source/Runtime/Core/Public/Templates/HasInserterOperator.h +++ b/Engine/Source/Runtime/Core/Public/Templates/HasInserterOperator.h @@ -41,7 +41,7 @@ namespace UE4HasInserterOperator_Private * Traits class which tests if a type has an operator<< overload between two types. */ template -struct THasInserterOperator +struct UE_DEPRECATED(4.23, "THasInserterOperator has been deprecated, please use TModels, T> instead.") THasInserterOperator { enum { Value = UE4HasInserterOperator_Private::THasInserterOperator::Value }; }; diff --git a/Engine/Source/Runtime/Core/Public/Templates/HasOperatorEquals.h b/Engine/Source/Runtime/Core/Public/Templates/HasOperatorEquals.h index 64900cf704f2..7e180d703a14 100644 --- a/Engine/Source/Runtime/Core/Public/Templates/HasOperatorEquals.h +++ b/Engine/Source/Runtime/Core/Public/Templates/HasOperatorEquals.h @@ -44,7 +44,7 @@ namespace UE4HasOperatorEquals_Private * Traits class which tests if a type has an operator== overload. */ template -struct THasOperatorEquals +struct UE_DEPRECATED(4.23, "THasOperatorEquals has been deprecated, please use TModels instead.") THasOperatorEquals { enum { Value = UE4HasOperatorEquals_Private::Equals::Value }; }; @@ -53,7 +53,7 @@ struct THasOperatorEquals * Traits class which tests if a type has an operator!= overload. */ template -struct THasOperatorNotEquals +struct UE_DEPRECATED(4.23, "THasOperatorNotEquals has been deprecated, please use TModels instead.") THasOperatorNotEquals { enum { Value = UE4HasOperatorEquals_Private::NotEquals::Value }; }; diff --git a/Engine/Source/Runtime/Core/Public/Templates/Identity.h b/Engine/Source/Runtime/Core/Public/Templates/Identity.h new file mode 100644 index 000000000000..05fcfd21bf84 --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/Templates/Identity.h @@ -0,0 +1,18 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +/** + * Returns the same type passed to it. This is useful in a few cases, but mainly for inhibiting template argument deduction in function arguments, e.g.: + * + * template + * void Func1(T Val); // Can be called like Func(123) or Func(123); + * + * template + * void Func2(typename TIdentity::Type Val); // Must be called like Func(123) + */ +template +struct TIdentity +{ + typedef T Type; +}; diff --git a/Engine/Source/Runtime/Core/Public/Templates/IsConst.h b/Engine/Source/Runtime/Core/Public/Templates/IsConst.h new file mode 100644 index 000000000000..a0925bf85883 --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/Templates/IsConst.h @@ -0,0 +1,18 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +/** + * Traits class which tests if a type is const. + */ +template +struct TIsConst +{ + static constexpr bool Value = false; +}; + +template +struct TIsConst +{ + static constexpr bool Value = true; +}; diff --git a/Engine/Source/Runtime/Core/Public/Templates/IsInitializerList.h b/Engine/Source/Runtime/Core/Public/Templates/IsInitializerList.h new file mode 100644 index 000000000000..df827cb37df7 --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/Templates/IsInitializerList.h @@ -0,0 +1,24 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include + +/** + * Traits class which tests if a type is an initializer list. + */ +template +struct TIsInitializerList +{ + static constexpr bool Value = false; +}; + +template +struct TIsInitializerList> +{ + static constexpr bool Value = true; +}; + +template struct TIsInitializerList { enum { Value = TIsInitializerList::Value }; }; +template struct TIsInitializerList< volatile T> { enum { Value = TIsInitializerList::Value }; }; +template struct TIsInitializerList { enum { Value = TIsInitializerList::Value }; }; diff --git a/Engine/Source/Runtime/Core/Public/Templates/MemoryOps.h b/Engine/Source/Runtime/Core/Public/Templates/MemoryOps.h index b427e92698ae..5475e30022c5 100644 --- a/Engine/Source/Runtime/Core/Public/Templates/MemoryOps.h +++ b/Engine/Source/Runtime/Core/Public/Templates/MemoryOps.h @@ -39,8 +39,8 @@ namespace UE4MemoryOps_Private * @param Elements The address of the first memory location to construct at. * @param Count The number of elements to destruct. */ -template -FORCEINLINE typename TEnableIf::Value>::Type DefaultConstructItems(void* Address, int32 Count) +template +FORCEINLINE typename TEnableIf::Value>::Type DefaultConstructItems(void* Address, SizeType Count) { ElementType* Element = (ElementType*)Address; while (Count) @@ -52,8 +52,8 @@ FORCEINLINE typename TEnableIf::Value>::Type } -template -FORCEINLINE typename TEnableIf::Value>::Type DefaultConstructItems(void* Elements, int32 Count) +template +FORCEINLINE typename TEnableIf::Value>::Type DefaultConstructItems(void* Elements, SizeType Count) { FMemory::Memset(Elements, 0, sizeof(ElementType) * Count); } @@ -90,8 +90,8 @@ FORCEINLINE typename TEnableIf::Value>::Ty * * @note: This function is optimized for values of T, and so will not dynamically dispatch destructor calls if T's destructor is virtual. */ -template -FORCEINLINE typename TEnableIf::Value>::Type DestructItems(ElementType* Element, int32 Count) +template +FORCEINLINE typename TEnableIf::Value>::Type DestructItems(ElementType* Element, SizeType Count) { while (Count) { @@ -105,8 +105,8 @@ FORCEINLINE typename TEnableIf::Value>::T } -template -FORCEINLINE typename TEnableIf::Value>::Type DestructItems(ElementType* Elements, int32 Count) +template +FORCEINLINE typename TEnableIf::Value>::Type DestructItems(ElementType* Elements, SizeType Count) { } @@ -118,8 +118,8 @@ FORCEINLINE typename TEnableIf::Value>::Ty * @param Source A pointer to the first argument to pass to the constructor. * @param Count The number of elements to copy. */ -template -FORCEINLINE typename TEnableIf::Value>::Type ConstructItems(void* Dest, const SourceElementType* Source, int32 Count) +template +FORCEINLINE typename TEnableIf::Value>::Type ConstructItems(void* Dest, const SourceElementType* Source, SizeType Count) { while (Count) { @@ -131,8 +131,8 @@ FORCEINLINE typename TEnableIf -FORCEINLINE typename TEnableIf::Value>::Type ConstructItems(void* Dest, const SourceElementType* Source, int32 Count) +template +FORCEINLINE typename TEnableIf::Value>::Type ConstructItems(void* Dest, const SourceElementType* Source, SizeType Count) { FMemory::Memcpy(Dest, Source, sizeof(SourceElementType) * Count); } @@ -145,8 +145,8 @@ FORCEINLINE typename TEnableIf -FORCEINLINE typename TEnableIf::Value>::Type CopyAssignItems(ElementType* Dest, const ElementType* Source, int32 Count) +template +FORCEINLINE typename TEnableIf::Value>::Type CopyAssignItems(ElementType* Dest, const ElementType* Source, SizeType Count) { while (Count) { @@ -158,8 +158,8 @@ FORCEINLINE typename TEnableIf::Value>: } -template -FORCEINLINE typename TEnableIf::Value>::Type CopyAssignItems(ElementType* Dest, const ElementType* Source, int32 Count) +template +FORCEINLINE typename TEnableIf::Value>::Type CopyAssignItems(ElementType* Dest, const ElementType* Source, SizeType Count) { FMemory::Memcpy(Dest, Source, sizeof(ElementType) * Count); } @@ -173,8 +173,8 @@ FORCEINLINE typename TEnableIf::Value>:: * @param Source A pointer to the first item to relocate. * @param Count The number of elements to relocate. */ -template -FORCEINLINE typename TEnableIf::Value>::Type RelocateConstructItems(void* Dest, const SourceElementType* Source, int32 Count) +template +FORCEINLINE typename TEnableIf::Value>::Type RelocateConstructItems(void* Dest, const SourceElementType* Source, SizeType Count) { while (Count) { @@ -188,8 +188,8 @@ FORCEINLINE typename TEnableIf -FORCEINLINE typename TEnableIf::Value>::Type RelocateConstructItems(void* Dest, const SourceElementType* Source, int32 Count) +template +FORCEINLINE typename TEnableIf::Value>::Type RelocateConstructItems(void* Dest, const SourceElementType* Source, SizeType Count) { /* All existing UE containers seem to assume trivial relocatability (i.e. memcpy'able) of their members, * so we're going to assume that this is safe here. However, it's not generally possible to assume this @@ -209,8 +209,8 @@ FORCEINLINE typename TEnableIf -FORCEINLINE typename TEnableIf::Value>::Type MoveConstructItems(void* Dest, const ElementType* Source, int32 Count) +template +FORCEINLINE typename TEnableIf::Value>::Type MoveConstructItems(void* Dest, const ElementType* Source, SizeType Count) { while (Count) { @@ -221,8 +221,8 @@ FORCEINLINE typename TEnableIf::Valu } } -template -FORCEINLINE typename TEnableIf::Value>::Type MoveConstructItems(void* Dest, const ElementType* Source, int32 Count) +template +FORCEINLINE typename TEnableIf::Value>::Type MoveConstructItems(void* Dest, const ElementType* Source, SizeType Count) { FMemory::Memmove(Dest, Source, sizeof(ElementType) * Count); } @@ -234,8 +234,8 @@ FORCEINLINE typename TEnableIf::Value * @param Source A pointer to the first item to move assign. * @param Count The number of elements to move assign. */ -template -FORCEINLINE typename TEnableIf::Value>::Type MoveAssignItems(ElementType* Dest, const ElementType* Source, int32 Count) +template +FORCEINLINE typename TEnableIf::Value>::Type MoveAssignItems(ElementType* Dest, const ElementType* Source, SizeType Count) { while (Count) { @@ -246,21 +246,21 @@ FORCEINLINE typename TEnableIf::Value>: } } -template -FORCEINLINE typename TEnableIf::Value>::Type MoveAssignItems(ElementType* Dest, const ElementType* Source, int32 Count) +template +FORCEINLINE typename TEnableIf::Value>::Type MoveAssignItems(ElementType* Dest, const ElementType* Source, SizeType Count) { FMemory::Memmove(Dest, Source, sizeof(ElementType) * Count); } -template -FORCEINLINE typename TEnableIf::IsBytewiseComparable, bool>::Type CompareItems(const ElementType* A, const ElementType* B, int32 Count) +template +FORCEINLINE typename TEnableIf::IsBytewiseComparable, bool>::Type CompareItems(const ElementType* A, const ElementType* B, SizeType Count) { return !FMemory::Memcmp(A, B, sizeof(ElementType) * Count); } -template -FORCEINLINE typename TEnableIf::IsBytewiseComparable, bool>::Type CompareItems(const ElementType* A, const ElementType* B, int32 Count) +template +FORCEINLINE typename TEnableIf::IsBytewiseComparable, bool>::Type CompareItems(const ElementType* A, const ElementType* B, SizeType Count) { while (Count) { diff --git a/Engine/Source/Runtime/Core/Public/Templates/Models.h b/Engine/Source/Runtime/Core/Public/Templates/Models.h new file mode 100644 index 000000000000..61fba842e84e --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/Templates/Models.h @@ -0,0 +1,193 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Templates/Identity.h" + +/** + * Utilities for concepts checks. + * + * In this case, a successful concept check means that a given C++ expression is well-formed. + * No guarantees are given about the correctness, behavior or complexity of the runtime behaviour of that expression. + * + * Concepts are structs with a rather unusual definition: + * + * struct CConcept + * { + * template <[...concept parameters...]> + * auto Requires([...placeholder variables...]) -> decltype( + * [...expression(s) to test the validity of...] + * ); + * }; + * + * The prefix C is reserved for concepts, and concepts should be directly named as an adjective and not like a predicate, i.e.: + * CEqualityComparable - good + * CIsComparable - bad + * CHasEqualsOperator - bad + * + * Concepts can be checked using the TModels trait: + * + * TModels::Value + * + * The arguments are forwarded to the template parameters of the concept's Requires() function, which will attempt to + * compile the expression(s) in the return value, and SFINAE is utilized to test whether that succeeded. + * + * The placeholders are simply any variable declarations you need to write your expression(s). + * + * Note that 'legal C++' doesn't necessarily mean that the expression will compile when used. The concept + * check only tests that the syntax is valid. Instantiation of function template bodies may still fail. + * See the CContainerLvalueAddable example below. + */ + + +/** + * Traits class which does concept checking. + */ +template +struct TModels +{ + template + static char (&Resolve(decltype(&Concept::template Requires)*))[2]; + + template + static char (&Resolve(...))[1]; + + static constexpr bool Value = sizeof(Resolve(0)) == 2; +}; + +/** + * Helper function which can be used as an expression in a concept to refine ('inherit') another concept. + * It should be used as expression-based variant of the TModels traits class. If the arguments model + * the given concept, Refines is a valid expression, otherwise it is not. + * + * See the CCopyablePointer example below. + */ +template +auto Refines() -> int(&)[!!TModels::Value * 2 - 1]; + + +/************ + * Examples * + ************/ + +/** + * // Definition of a negatable type. + * struct CNegatable + * { + * template + * auto Requires(const T& Val) -> decltype( + * -Val + * ); + * }; + * + * static_assert( TModels::Value); // ints are negatable + * static_assert(!TModels::Value); // pointers are not negatable + */ + +/** + * // Definition of an incrementable type. + * // Pre-increment and post-increment must both be legal. + * struct CIncrementable + * { + * template + * auto Requires(T& Val) -> decltype( + * ++Val, + * Val++ + * ); + * }; + * + * static_assert( TModels::Value); // ints are incrementable + * static_assert( TModels::Value); // pointers are incrementable + * static_assert(!TModels::Value); // arrays are not incrementable + */ + +/** + * // Definition of comparability between two types. + * // Requires both == and != and commutability. + * struct CEqualityComparable + * { + * template + * auto Requires(const T& A, const U& B) -> decltype( + * A == B, + * B == A, + * A != B, + * B != A + * ); + * }; + * + * static_assert( TModels::Value); // base pointers are comparable with derived pointers + * static_assert(!TModels::Value); // unrelated pointers are not comparable + */ + +/** + * // Definition of a type with a nested ElementType and can have lvalues of that type added to it. + * // Note: this is just an example, not a good description of a container. + * struct CLvalueAddableContainer + * { + * template + * auto Requires(ContainerType& Container, typename ContainerType::ElementType& Element) -> decltype( + * Container.Add(Element) + * ); + * }; + * + * static_assert(TModels>>::Value); // success, but... + * + * TUniquePtr Temp; + * Array.Add(Temp); // compile error when TArray attempts to copy the TUniquePtr + * + * This is because TArray doesn't remove its Add(const ElementType&) overload when the element type + * is non-copyable - instead we get a compile error inside the template instantiation when we attempt the call. + */ + +/** + * // Definition of a type subtractable from itself and whose result is also (convertible to) itself. + * struct CGroupSubtractable + * { + * template + * auto Requires(T& Result, const T& A, const T& B) -> decltype( + * Result = A - B + * ); + * }; + * + * static_assert( TModels::Value); // ints form a group under subtraction + * static_assert(!TModels::Value); // pointers do not result in another pointer under subtraction and so do not form a group + */ + +/** + * // Definition of a copyable type. + * struct CCopyable + * { + * template + * auto Requires(T& Dest, const T& Val) -> decltype( + * T(Val), + * Dest = Val + * ); + * }; + * + * // Definition of a dereferencable type. + * struct CDereferencable + * { + * template + * auto Requires(const T& Val) -> decltype( + * *Val + * ); + * }; + * + * // Definition of a copyable pointer-like type which requires copyability and dereferencability (refining existing concepts) and bool-testability (stated directly). + * // Note: this is just an example, not a great concept. + * struct CCopyablePointer + * { + * template + * auto Requires(bool& Result, bool AnyBool, const T& Val) -> decltype( + * Refines(), + * Refines(), + * Result = AnyBool && Val + * ); + * }; + * + * static_assert( TModels::Value); // raw pointers model this concept + * static_assert( TModels>::Value); // TSharedPtrs model this concept + * static_assert(!TModels>::Value); // TUniquePtrs are dereferenceable and bool-testable, but not copyable + * static_assert(!TModels::Value); // ints are copyable and bool-testable, but not dereferencable + * static_assert(!TModels::Value); // FStrings are copyable and dereferencable (to get the const TCHAR*), but not bool-testable + */ diff --git a/Engine/Source/Runtime/Core/Public/Templates/ReversePredicate.h b/Engine/Source/Runtime/Core/Public/Templates/ReversePredicate.h index ef6c65b550bc..ef99f20f1c42 100644 --- a/Engine/Source/Runtime/Core/Public/Templates/ReversePredicate.h +++ b/Engine/Source/Runtime/Core/Public/Templates/ReversePredicate.h @@ -2,6 +2,7 @@ #pragma once +#include "Templates/Invoke.h" #include "Templates/UnrealTemplate.h" /** @@ -16,8 +17,12 @@ class TReversePredicate public: TReversePredicate( const PredicateType& InPredicate ) : Predicate( InPredicate ) - {} + { + } template - FORCEINLINE bool operator()( T&& A, T&& B ) const { return Predicate( Forward(B), Forward(A) ); } -}; \ No newline at end of file + FORCEINLINE bool operator()( T&& A, T&& B ) const + { + return Invoke( Predicate, Forward(B), Forward(A) ); + } +}; diff --git a/Engine/Source/Runtime/Core/Public/Templates/SharedPointer.h b/Engine/Source/Runtime/Core/Public/Templates/SharedPointer.h index f7b6bb8df6c1..160bd3dad386 100644 --- a/Engine/Source/Runtime/Core/Public/Templates/SharedPointer.h +++ b/Engine/Source/Runtime/Core/Public/Templates/SharedPointer.h @@ -1396,6 +1396,30 @@ FORCEINLINE bool operator==( TSharedPtr< ObjectTypeA, Mode > const& InSharedPtrA } +/** + * Global equality operator for TSharedPtr + * + * @return True if the shared pointer is null + */ +template< class ObjectTypeA, ESPMode Mode > +FORCEINLINE bool operator==( TSharedPtr< ObjectTypeA, Mode > const& InSharedPtrA, TYPE_OF_NULLPTR ) +{ + return !InSharedPtrA.IsValid(); +} + + +/** + * Global equality operator for TSharedPtr + * + * @return True if the shared pointer is null + */ +template< class ObjectTypeB, ESPMode Mode > +FORCEINLINE bool operator==( TYPE_OF_NULLPTR, TSharedPtr< ObjectTypeB, Mode > const& InSharedPtrB ) +{ + return !InSharedPtrB.IsValid(); +} + + /** * Global inequality operator for TSharedPtr * @@ -1408,6 +1432,30 @@ FORCEINLINE bool operator!=( TSharedPtr< ObjectTypeA, Mode > const& InSharedPtrA } +/** + * Global inequality operator for TSharedPtr + * + * @return True if the shared pointer is not null + */ +template< class ObjectTypeA, ESPMode Mode > +FORCEINLINE bool operator!=( TSharedPtr< ObjectTypeA, Mode > const& InSharedPtrA, TYPE_OF_NULLPTR ) +{ + return InSharedPtrA.IsValid(); +} + + +/** + * Global inequality operator for TSharedPtr + * + * @return True if the shared pointer is not null + */ +template< class ObjectTypeB, ESPMode Mode > +FORCEINLINE bool operator!=( TYPE_OF_NULLPTR, TSharedPtr< ObjectTypeB, Mode > const& InSharedPtrB ) +{ + return InSharedPtrB.IsValid(); +} + + /** * Tests to see if a TSharedRef is "equal" to a TSharedPtr (both are valid and refer to the same object) * @@ -1522,7 +1570,7 @@ FORCEINLINE bool operator==( TSharedPtr< ObjectTypeA, Mode > const& InSharedPtrA * @return True if the weak pointer is null */ template< class ObjectTypeA, ESPMode Mode > -FORCEINLINE bool operator==( TWeakPtr< ObjectTypeA, Mode > const& InWeakPtrA, decltype(nullptr) ) +FORCEINLINE bool operator==( TWeakPtr< ObjectTypeA, Mode > const& InWeakPtrA, TYPE_OF_NULLPTR ) { return !InWeakPtrA.IsValid(); } @@ -1534,7 +1582,7 @@ FORCEINLINE bool operator==( TWeakPtr< ObjectTypeA, Mode > const& InWeakPtrA, de * @return True if the weak pointer is null */ template< class ObjectTypeB, ESPMode Mode > -FORCEINLINE bool operator==( decltype(nullptr), TWeakPtr< ObjectTypeB, Mode > const& InWeakPtrB ) +FORCEINLINE bool operator==( TYPE_OF_NULLPTR, TWeakPtr< ObjectTypeB, Mode > const& InWeakPtrB ) { return !InWeakPtrB.IsValid(); } @@ -1606,7 +1654,7 @@ FORCEINLINE bool operator!=( TSharedPtr< ObjectTypeA, Mode > const& InSharedPtrA * @return True if the weak pointer is not null */ template< class ObjectTypeA, ESPMode Mode > -FORCEINLINE bool operator!=( TWeakPtr< ObjectTypeA, Mode > const& InWeakPtrA, decltype(nullptr) ) +FORCEINLINE bool operator!=( TWeakPtr< ObjectTypeA, Mode > const& InWeakPtrA, TYPE_OF_NULLPTR ) { return InWeakPtrA.IsValid(); } @@ -1618,7 +1666,7 @@ FORCEINLINE bool operator!=( TWeakPtr< ObjectTypeA, Mode > const& InWeakPtrA, de * @return True if the weak pointer is not null */ template< class ObjectTypeB, ESPMode Mode > -FORCEINLINE bool operator!=( decltype(nullptr), TWeakPtr< ObjectTypeB, Mode > const& InWeakPtrB ) +FORCEINLINE bool operator!=( TYPE_OF_NULLPTR, TWeakPtr< ObjectTypeB, Mode > const& InWeakPtrB ) { return InWeakPtrB.IsValid(); } diff --git a/Engine/Source/Runtime/Core/Public/Templates/Tuple.h b/Engine/Source/Runtime/Core/Public/Templates/Tuple.h index 5811a48cc700..1a1c24bd2688 100644 --- a/Engine/Source/Runtime/Core/Public/Templates/Tuple.h +++ b/Engine/Source/Runtime/Core/Public/Templates/Tuple.h @@ -10,16 +10,6 @@ #include "Templates/Invoke.h" #include "Serialization/StructuredArchive.h" -// VS2015 Update 2 (and seemingly earlier) erroneously complains about multiple versions -// of special member functions, so we disable the use of defaulting in that case. -// -// http://stackoverflow.com/questions/36657243/c-multiple-versions-of-a-defaulted-special-member-functions-error-in-msvc-2 -#if defined(_MSC_VER) && _MSC_FULL_VER <= 190023918 - #define TUPLES_USE_DEFAULTED_FUNCTIONS 0 -#else - #define TUPLES_USE_DEFAULTED_FUNCTIONS 1 -#endif - class FArchive; template @@ -73,36 +63,10 @@ namespace UE4Tuple_Private { } - #if TUPLES_USE_DEFAULTED_FUNCTIONS - - TTupleElement(TTupleElement&&) = default; - TTupleElement(const TTupleElement&) = default; - TTupleElement& operator=(TTupleElement&&) = default; - TTupleElement& operator=(const TTupleElement&) = default; - - #else - - TTupleElement(TTupleElement&& Other) - : Value(MoveTemp(Other.Value)) - { - } - - TTupleElement(const TTupleElement& Other) - : Value(Other.Value) - { - } - - void operator=(TTupleElement&& Other) - { - Value = MoveTemp(Other.Value); - } - - void operator=(const TTupleElement& Other) - { - Value = Other.Value; - } - - #endif + TTupleElement(TTupleElement&&) = default; + TTupleElement(const TTupleElement&) = default; + TTupleElement& operator=(TTupleElement&&) = default; + TTupleElement& operator=(const TTupleElement&) = default; T Value; }; @@ -211,44 +175,11 @@ namespace UE4Tuple_Private { } - #if TUPLES_USE_DEFAULTED_FUNCTIONS - - TTupleStorage() = default; - TTupleStorage(TTupleStorage&&) = default; - TTupleStorage(const TTupleStorage&) = default; - TTupleStorage& operator=(TTupleStorage&&) = default; - TTupleStorage& operator=(const TTupleStorage&) = default; - - #else - - TTupleStorage() - : TTupleElement()... - { - } - - TTupleStorage(TTupleStorage&& Other) - : TTupleElement(MoveTemp(*(TTupleElement*)&Other))... - { - } - - TTupleStorage(const TTupleStorage& Other) - : TTupleElement(*(const TTupleElement*)&Other)... - { - } - - void operator=(TTupleStorage&& Other) - { - int Temp[] = { 0, (*(TTupleElement*)this = MoveTemp(*(TTupleElement*)&Other), 0)... }; - (void)Temp; - } - - void operator=(const TTupleStorage& Other) - { - int Temp[] = { 0, (*(TTupleElement*)this = *(const TTupleElement*)&Other, 0)... }; - (void)Temp; - } - - #endif + TTupleStorage() = default; + TTupleStorage(TTupleStorage&&) = default; + TTupleStorage(const TTupleStorage&) = default; + TTupleStorage& operator=(TTupleStorage&&) = default; + TTupleStorage& operator=(const TTupleStorage&) = default; template FORCEINLINE const typename TTupleElementHelper::Type& Get() const { return TTupleElementHelper::Get(*this); } template FORCEINLINE typename TTupleElementHelper::Type& Get() { return TTupleElementHelper::Get(*this); } @@ -297,40 +228,10 @@ namespace UE4Tuple_Private { } - #if TUPLES_USE_DEFAULTED_FUNCTIONS - - TTupleStorage(TTupleStorage&&) = default; - TTupleStorage(const TTupleStorage&) = default; - TTupleStorage& operator=(TTupleStorage&&) = default; - TTupleStorage& operator=(const TTupleStorage&) = default; - - #else - - TTupleStorage(TTupleStorage&& Other) - : Key (MoveTemp(Other.Key)) - , Value(MoveTemp(Other.Value)) - { - } - - TTupleStorage(const TTupleStorage& Other) - : Key (Other.Key) - , Value(Other.Value) - { - } - - void operator=(TTupleStorage&& Other) - { - Key = MoveTemp(Other.Key); - Value = MoveTemp(Other.Value); - } - - void operator=(const TTupleStorage& Other) - { - Key = Other.Key; - Value = Other.Value; - } - - #endif + TTupleStorage(TTupleStorage&&) = default; + TTupleStorage(const TTupleStorage&) = default; + TTupleStorage& operator=(TTupleStorage&&) = default; + TTupleStorage& operator=(const TTupleStorage&) = default; template FORCEINLINE const typename TGetHelper::ResultType& Get() const { return TGetHelper::Get(*this); } template FORCEINLINE typename TGetHelper::ResultType& Get() { return TGetHelper::Get(*this); } @@ -368,42 +269,11 @@ namespace UE4Tuple_Private { } - #if TUPLES_USE_DEFAULTED_FUNCTIONS - - TTupleImpl() = default; - TTupleImpl(TTupleImpl&& Other) = default; - TTupleImpl(const TTupleImpl& Other) = default; - TTupleImpl& operator=(TTupleImpl&& Other) = default; - TTupleImpl& operator=(const TTupleImpl& Other) = default; - - #else - - TTupleImpl() - : Super() - { - } - - TTupleImpl(TTupleImpl&& Other) - : Super(MoveTemp(*(Super*)&Other)) - { - } - - TTupleImpl(const TTupleImpl& Other) - : Super(*(const Super*)&Other) - { - } - - void operator=(TTupleImpl&& Other) - { - *(Super*)this = MoveTemp(*(Super*)&Other); - } - - void operator=(const TTupleImpl& Other) - { - *(Super*)this = *(const Super*)&Other; - } - - #endif + TTupleImpl() = default; + TTupleImpl(TTupleImpl&& Other) = default; + TTupleImpl(const TTupleImpl& Other) = default; + TTupleImpl& operator=(TTupleImpl&& Other) = default; + TTupleImpl& operator=(const TTupleImpl& Other) = default; template #if PLATFORM_COMPILER_HAS_DECLTYPE_AUTO @@ -473,79 +343,6 @@ namespace UE4Tuple_Private } }; - #ifdef _MSC_VER - - // Not strictly necessary, but some VC versions give a 'syntax error: ' error - // for empty tuples. - template <> - struct TTupleImpl> - { - explicit TTupleImpl() - { - } - - // Doesn't matter what these return, or even have a function body, but they need to be declared - template FORCEINLINE const int32& Get() const; - template FORCEINLINE int32& Get(); - - template - #if PLATFORM_COMPILER_HAS_DECLTYPE_AUTO - decltype(auto) ApplyAfter(FuncType&& Func, ArgTypes&&... Args) const - #else - auto ApplyAfter(FuncType&& Func, ArgTypes&&... Args) const -> decltype(Func(Forward(Args)...)) - #endif - { - return Func(Forward(Args)...); - } - - template - #if PLATFORM_COMPILER_HAS_DECLTYPE_AUTO - decltype(auto) ApplyBefore(FuncType&& Func, ArgTypes&&... Args) const - #else - auto ApplyBefore(FuncType&& Func, ArgTypes&&... Args) const -> decltype(Func(Forward(Args)...)) - #endif - { - return Func(Forward(Args)...); - } - - FORCEINLINE friend FArchive& operator<<(FArchive& Ar, TTupleImpl& Tuple) - { - return Ar; - } - - FORCEINLINE friend bool operator==(const TTupleImpl& Lhs, const TTupleImpl& Rhs) - { - return true; - } - - FORCEINLINE friend bool operator!=(const TTupleImpl& Lhs, const TTupleImpl& Rhs) - { - return false; - } - - FORCEINLINE friend bool operator<(const TTupleImpl& Lhs, const TTupleImpl& Rhs) - { - return false; - } - - FORCEINLINE friend bool operator<=(const TTupleImpl& Lhs, const TTupleImpl& Rhs) - { - return true; - } - - FORCEINLINE friend bool operator>(const TTupleImpl& Lhs, const TTupleImpl& Rhs) - { - return false; - } - - FORCEINLINE friend bool operator>=(const TTupleImpl& Lhs, const TTupleImpl& Rhs) - { - return true; - } - }; - - #endif - template FORCEINLINE TTuple::Type...> MakeTupleImpl(Types&&... Args) @@ -629,43 +426,11 @@ public: // This constructor is disabled for TTuple and zero parameters because VC is incorrectly instantiating it as a move/copy/default constructor. } - #if TUPLES_USE_DEFAULTED_FUNCTIONS - - TTuple() = default; - TTuple(TTuple&&) = default; - TTuple(const TTuple&) = default; - TTuple& operator=(TTuple&&) = default; - TTuple& operator=(const TTuple&) = default; - - #else - - TTuple() - { - } - - TTuple(TTuple&& Other) - : Super(MoveTemp(*(Super*)&Other)) - { - } - - TTuple(const TTuple& Other) - : Super(*(const Super*)&Other) - { - } - - TTuple& operator=(TTuple&& Other) - { - *(Super*)this = MoveTemp(*(Super*)&Other); - return *this; - } - - TTuple& operator=(const TTuple& Other) - { - *(Super*)this = *(const Super*)&Other; - return *this; - } - - #endif + TTuple() = default; + TTuple(TTuple&&) = default; + TTuple(const TTuple&) = default; + TTuple& operator=(TTuple&&) = default; + TTuple& operator=(const TTuple&) = default; }; diff --git a/Engine/Source/Runtime/Core/Public/Templates/UnrealTemplate.h b/Engine/Source/Runtime/Core/Public/Templates/UnrealTemplate.h index 4810f5cabeb9..d53c138fdbcd 100644 --- a/Engine/Source/Runtime/Core/Public/Templates/UnrealTemplate.h +++ b/Engine/Source/Runtime/Core/Public/Templates/UnrealTemplate.h @@ -12,6 +12,7 @@ #include "Templates/UnrealTypeTraits.h" #include "Templates/RemoveReference.h" #include "Templates/TypeCompatibleBytes.h" +#include "Templates/Identity.h" #include "Traits/IsContiguousContainer.h" /*----------------------------------------------------------------------------- @@ -583,21 +584,6 @@ struct TLosesQualifiersFromTo enum { Value = !TAreTypesEqual::Type, To>::Value }; }; -/** - * Returns the same type passed to it. This is useful in a few cases, but mainly for inhibiting template argument deduction in function arguments, e.g.: - * - * template - * void Func1(T Val); // Can be called like Func(123) or Func(123); - * - * template - * void Func2(typename TIdentity::Type Val); // Must be called like Func(123) - */ -template -struct TIdentity -{ - typedef T Type; -}; - /** * Equivalent to std::declval. * diff --git a/Engine/Source/Runtime/Core/Public/UObject/EditorObjectVersion.h b/Engine/Source/Runtime/Core/Public/UObject/EditorObjectVersion.h index 3e674ded9a9a..885c2228856f 100644 --- a/Engine/Source/Runtime/Core/Public/UObject/EditorObjectVersion.h +++ b/Engine/Source/Runtime/Core/Public/UObject/EditorObjectVersion.h @@ -71,6 +71,14 @@ struct CORE_API FEditorObjectVersion MeshDescriptionBulkDataGuid, // Change to MeshDescription serialization (removed FMeshPolygon::HoleContours) MeshDescriptionRemovedHoles, + // Change to the WidgetCompoent WindowVisibilty default value + ChangedWidgetComponentWindowVisibilityDefault, + // Avoid keying culture invariant display strings during serialization to avoid non-deterministic cooking issues + CultureInvariantTextSerializationKeyStability, + // Change to UScrollBar and UScrollBox thickness property (removed implicit padding of 2, so thickness value must be incremented by 4). + ScrollBarThicknessChange, + // Deprecated LandscapeHoleMaterial + RemoveLandscapeHoleMaterial, // ------------------------------------------------------ VersionPlusOne, LatestVersion = VersionPlusOne - 1 diff --git a/Engine/Source/Runtime/Core/Public/UObject/NameTypes.h b/Engine/Source/Runtime/Core/Public/UObject/NameTypes.h index 21942195741e..59b1ba32d7ae 100644 --- a/Engine/Source/Runtime/Core/Public/UObject/NameTypes.h +++ b/Engine/Source/Runtime/Core/Public/UObject/NameTypes.h @@ -33,8 +33,60 @@ class FText; /** Maximum size of name. */ enum {NAME_SIZE = 1024}; -/** Name index. */ -typedef int32 NAME_INDEX; +/** Opaque id to a deduplicated name */ +struct FNameEntryId +{ + FNameEntryId() : Value(0) {} + FNameEntryId(ENoInit) {} + + /** Slow alphabetical order that is stable / deterministic over process runs */ + CORE_API int32 CompareLexical(FNameEntryId Rhs) const; + bool LexicalLess(FNameEntryId Rhs) const { return CompareLexical(Rhs) < 0; } + + /** Fast non-alphabetical order that is only stable during this process' lifetime */ + int32 CompareFast(FNameEntryId Rhs) const { return Value - Rhs.Value; }; + bool FastLess(FNameEntryId Rhs) const { return CompareFast(Rhs) < 0; } + + /** Fast non-alphabetical order that is only stable during this process' lifetime */ + bool operator<(FNameEntryId Rhs) const { return Value < Rhs.Value; } + + /** Fast non-alphabetical order that is only stable during this process' lifetime */ + bool operator>(FNameEntryId Rhs) const { return Rhs.Value < Value; } + bool operator==(FNameEntryId Rhs) const { return Value == Rhs.Value; } + bool operator!=(FNameEntryId Rhs) const { return Value != Rhs.Value; } + + explicit operator bool() const { return Value != 0; } + + UE_DEPRECATED(4.23, "NAME_INDEX is replaced by FNameEntryId, which is no longer a contiguous integer. " + "Please use 'GetTypeHash(MyId)' instead of 'MyId' for hash functions. " + "ToUnstableInt() can be used in other advanced cases.") + operator int32() const; + + /** Get process specific integer */ + uint32 ToUnstableInt() const { return Value; } + + /** Create from unstable int produced by this process */ + CORE_API static FNameEntryId FromUnstableInt(uint32 UnstableInt); +private: + uint32 Value; +}; + +CORE_API uint32 GetTypeHash(FNameEntryId Id); +CORE_API bool operator==(FNameEntryId Id, EName Ename); +inline bool operator==(EName Ename, FNameEntryId Id) { return Id == Ename; } +inline bool operator!=(EName Ename, FNameEntryId Id) { return !(Id == Ename); } +inline bool operator!=(FNameEntryId Id, EName Ename) { return !(Id == Ename); } + +/** Serialize as process specific unstable int */ +CORE_API FArchive& operator<<(FArchive& Ar, FNameEntryId& InId); + +/** + * Legacy typedef - this is no longer an index + * + * Use GetTypeHash(FName) or GetTypeHash(FNameEntryId) for hashing + * To compare with ENames use FName(EName) or FName::ToEName() instead + */ +typedef FNameEntryId NAME_INDEX; #define checkName checkSlow @@ -78,18 +130,6 @@ enum class ENameCase : uint8 IgnoreCase, }; -namespace FNameDefs -{ -#if !WITH_EDITORONLY_DATA - // Use a modest bucket count on consoles - static const uint32 NameHashBucketCount = 65536; -#else - // On PC platform we use a large number of name hash buckets to accommodate the editor's - // use of FNames to store asset path and content tags - static const uint32 NameHashBucketCount = 65536; -#endif -} - enum ELinkerNameTableConstructor {ENAME_LinkerConstructor}; /** Enumeration for finding name. */ @@ -111,40 +151,34 @@ enum EFindName FNameEntry. ----------------------------------------------------------------------------*/ -/** - * Mask for index bit used to determine whether string is encoded as TCHAR or ANSICHAR. We don't - * add an extra bool in order to keep the name size to a minimum and 2 billion names is impractical - * so there are a few bits left in the index. - */ -#define NAME_WIDE_MASK 0x1 -#define NAME_INDEX_SHIFT 1 +/** Implementation detail exposed for debug visualizers */ +struct FNameEntryHeader +{ + uint16 bIsWide : 1; +#if WITH_CASE_PRESERVING_NAME + uint16 Len : 15; +#else + static constexpr uint32 ProbeHashBits = 5; + uint16 LowercaseProbeHash : ProbeHashBits; + uint16 Len : 10; +#endif +}; /** - * A global name, as stored in the global name table. + * A global deduplicated name stored in the global name table. */ struct FNameEntry { -public: - /** Pointer to the next entry in this hash bin's linked list. */ - TAtomic HashNext; - private: - /** Index of name in hash. */ - NAME_INDEX Index; - -protected: - /** Name, variable-sized - note that AllocateNameEntry only allocates memory as needed. */ +#if WITH_CASE_PRESERVING_NAME + FNameEntryId ComparisonId; +#endif + FNameEntryHeader Header; union { ANSICHAR AnsiName[NAME_SIZE]; WIDECHAR WideName[NAME_SIZE]; }; - // DO NOT ADD VARIABLES BELOW! - -private: - /** Default constructor doesn't do anything. AllocateNameEntry is responsible for work. */ - FNameEntry() - {} FNameEntry(const FNameEntry&) = delete; FNameEntry(FNameEntry&&) = delete; @@ -152,87 +186,42 @@ private: FNameEntry& operator=(FNameEntry&&) = delete; public: - - /** - * Sets whether or not the NameEntry will have a wide string, or an ansi string - * - * @param bIsWide true if we are going to serialize a wide string - */ - FORCEINLINE void PreSetIsWideForSerialization(bool bIsWide) - { - Index = bIsWide ? NAME_WIDE_MASK : 0; - } - - /** - * Returns index of name in hash passed to FNameEntry via AllocateNameEntry. The lower bits - * are used for internal state, which is why we need to shift. - * - * @return Index of name in hash - */ - FORCEINLINE int32 GetIndex() const - { - return Index >> NAME_INDEX_SHIFT; - } - - /** - * Returns whether this name entry is represented via TCHAR or ANSICHAR - */ + /** Returns whether this name entry is represented via WIDECHAR or ANSICHAR. */ FORCEINLINE bool IsWide() const { - return (Index & NAME_WIDE_MASK); + return Header.bIsWide; + } + + FORCEINLINE int32 GetNameLength() const + { + return Header.Len; } /** - * @return FString of name portion minus number. - */ - CORE_API FString GetPlainNameString() const; - - /** - * Appends this name entry to the passed in string. + * Copy unterminated name to TCHAR buffer without allocating. * - * @param String String to append this name to + * @param OutSize must be at least GetNameLength() */ - CORE_API void AppendNameToString( FString& String ) const; + void GetUnterminatedName(TCHAR* OutName, uint32 OutSize) const; - /** - * Appends this name entry to the passed in string, adding path separator between strings (with FString operator/). - * - * @param String String to append this name to - */ - CORE_API void AppendNameToPathString( FString& String ) const; + /** Copy null-terminated name to TCHAR buffer without allocating. */ + void GetName(TCHAR(&OutName)[NAME_SIZE]) const; - /** - * @return length of name - */ - CORE_API int32 GetNameLength() const; + /** Copy null-terminated name to ANSICHAR buffer without allocating. Entry must not be wide. */ + CORE_API void GetAnsiName(ANSICHAR(&OutName)[NAME_SIZE]) const; - /** - * Compares name using the compare method provided. - * - * @param InName Name to compare to - * @return true if equal, false otherwise - */ - bool IsEqual( const ANSICHAR* InName, const ENameCase CompareMethod ) const; + /** Copy null-terminated name to WIDECHAR buffer without allocating. Entry must be wide. */ + CORE_API void GetWideName(WIDECHAR(&OutName)[NAME_SIZE]) const; - /** - * Compares name using the compare method provided. - * - * @param InName Name to compare to - * @return true if equal, false otherwise - */ - bool IsEqual( const WIDECHAR* InName, const ENameCase CompareMethod ) const; + /** Copy name to a dynamically allocated FString. */ + CORE_API FString GetPlainNameString() const; - /** - * @return create a copy of the ANSI name - */ - void GetAnsiName(ANSICHAR(&OutName)[NAME_SIZE]) const; + /** Appends name to string. May allocate. */ + CORE_API void AppendNameToString(FString& OutString) const; - /** - * @return direct access to WIDE name if stored in widechars - */ - void GetWideName(WIDECHAR(&OutName)[NAME_SIZE]) const; + /** Appends name to string with path separator using FString::PathAppend(). */ + CORE_API void AppendNameToPathString(FString& OutString) const; - static CORE_API int32 GetSize( const TCHAR* Name ); /** * Returns the size in bytes for FNameEntry structure. This is != sizeof(FNameEntry) as we only allocated as needed. @@ -241,43 +230,34 @@ public: * @param bIsPureAnsi Whether name is pure ANSI or not * @return required size of FNameEntry structure to hold this string (might be wide or ansi) */ - static int32 GetSize( int32 Length, bool bIsPureAnsi ); + static int32 GetSize(int32 Length, bool bIsPureAnsi); + static CORE_API int32 GetSize(const TCHAR* Name); + + CORE_API int32 GetSizeInBytes() const; - // Functions. CORE_API void Write(FArchive& Ar) const; CORE_API void Write(FStructuredArchive::FSlot Slot) const; - // Friend for access to Flags. - template - friend FNameEntry* AllocateNameEntry(const TCharType* Name, NAME_INDEX Index); - + static int32 GetDataOffset(); + struct FNameStringView MakeView(union FNameBuffer& OptionalDecodeBuffer) const; private: friend class FName; + friend struct FNameHelper; + friend class FNameEntryAllocator; + friend class FNamePoolShardBase; - friend struct FNameEntrySerialized; + static void Encode(ANSICHAR* Name, uint32 Len); + static void Encode(WIDECHAR* Name, uint32 Len); + static void Decode(ANSICHAR* Name, uint32 Len); + static void Decode(WIDECHAR* Name, uint32 Len); - template - friend struct FNameInitHelper; - - /** - * @return direct access to ANSI name if stored in ANSI - */ - ANSICHAR const* GetAnsiNamePtr(ANSICHAR(&OptionalTempBuffer)[NAME_SIZE]) const; - - /** - * @return direct access to wide name if stored in widechars - */ - WIDECHAR const* GetWideNamePtr(WIDECHAR(&OptionalTempBuffer)[NAME_SIZE]) const; - - /** - * updates the stored name if ANSI - */ - void SetAnsiName(const ANSICHAR* SrcName, int32 SrcNameLen); - - /** - * updates the stored name in widechars - */ - void SetWideName(const WIDECHAR* SrcName, int32 SrcNameLen); + void StoreName(const ANSICHAR* InName, uint32 Len); + void StoreName(const WIDECHAR* InName, uint32 Len); + void CopyUnterminatedName(ANSICHAR* OutName) const; + void CopyUnterminatedName(WIDECHAR* OutName) const; + void CopyAndConvertUnterminatedName(TCHAR* OutName) const; + const ANSICHAR* GetUnterminatedName(ANSICHAR(&OptionalDecodeBuffer)[NAME_SIZE]) const; + const WIDECHAR* GetUnterminatedName(WIDECHAR(&OptionalDecodeBuffer)[NAME_SIZE]) const; }; /** @@ -285,7 +265,8 @@ private: */ struct FNameEntrySerialized { - NAME_INDEX Index; + FNameEntryId Index; + bool bIsWide = false; union { @@ -293,248 +274,60 @@ struct FNameEntrySerialized WIDECHAR WideName[NAME_SIZE]; }; - uint16 NonCasePreservingHash; - uint16 CasePreservingHash; - bool bWereHashesLoaded; + // These are not used anymore but recalculated on save to maintain serialization format + uint16 NonCasePreservingHash = 0; + uint16 CasePreservingHash = 0; FNameEntrySerialized(const FNameEntry& NameEntry); - FNameEntrySerialized(enum ELinkerNameTableConstructor) : - NonCasePreservingHash(0), - CasePreservingHash(0), - bWereHashesLoaded(false) - { - } - - /** - * Sets whether or not the NameEntry will have a wide string, or an ansi string - * - * @param bIsWide true if we are going to serialize a wide string - */ - FORCEINLINE void PreSetIsWideForSerialization(bool bIsWide) - { - Index = bIsWide ? NAME_WIDE_MASK : 0; - } + FNameEntrySerialized(enum ELinkerNameTableConstructor) {} /** - * Returns whether this name entry is represented via TCHAR or ANSICHAR + * Returns direct access to null-terminated name if narrow */ - FORCEINLINE bool IsWide() const + ANSICHAR const* GetAnsiName() const { - return (Index & NAME_WIDE_MASK); - } - - /** - * @return direct access to ANSI name if stored in ANSI - */ - inline ANSICHAR const* GetAnsiName() const - { - check(!IsWide()); + check(!bIsWide); return AnsiName; } /** - * @return direct access to wide name if stored in widechars + * Returns direct access to null-terminated name if wide */ - inline WIDECHAR const* GetWideName() const + WIDECHAR const* GetWideName() const { - check(IsWide()); + check(bIsWide); return WideName; } /** - * @return FString of name portion minus number. + * Returns FString of name portion minus number. */ CORE_API FString GetPlainNameString() const; friend CORE_API FArchive& operator<<(FArchive& Ar, FNameEntrySerialized& E); - friend CORE_API FArchive& operator<<(FArchive& Ar, FNameEntrySerialized* E) + friend FArchive& operator<<(FArchive& Ar, FNameEntrySerialized* E) { return Ar << *E; } friend CORE_API void operator<<(FStructuredArchive::FSlot Slot, FNameEntrySerialized& E); - friend CORE_API void operator<<(FStructuredArchive::FSlot Slot, FNameEntrySerialized* E) + friend void operator<<(FStructuredArchive::FSlot Slot, FNameEntrySerialized* E) { Slot << *E; } }; -/** - * Simple array type that can be expanded without invalidating existing entries. - * This is critical to thread safe FNames. - * @param ElementType Type of the pointer we are storing in the array - * @param MaxTotalElements absolute maximum number of elements this array can ever hold - * @param ElementsPerChunk how many elements to allocate in a chunk - **/ - template -class TStaticIndirectArrayThreadSafeRead -{ - enum - { - // figure out how many elements we need in the master table - ChunkTableSize = (MaxTotalElements + ElementsPerChunk - 1) / ElementsPerChunk - }; - /** Static master table to chunks of pointers **/ - ElementType** Chunks[ChunkTableSize]; - /** Number of elements we currently have **/ - TAtomic NumElements; - /** Number of chunks we currently have **/ - int32 NumChunks; - - /** - * Expands the array so that Element[Index] is allocated. New pointers are all zero. - * @param Index The Index of an element we want to be sure is allocated - **/ - void ExpandChunksToIndex(int32 Index) - { - check(Index >= 0 && Index < MaxTotalElements); - int32 ChunkIndex = Index / ElementsPerChunk; - while (1) - { - if (ChunkIndex < NumChunks) - { - break; - } - // add a chunk, and make sure nobody else tries - ElementType*** Chunk = &Chunks[ChunkIndex]; - ElementType** NewChunk = (ElementType**)FMemory::Malloc(sizeof(ElementType*) * ElementsPerChunk); - FMemory::Memzero(NewChunk, sizeof(ElementType*) * ElementsPerChunk); - if (FPlatformAtomics::InterlockedCompareExchangePointer((void**)Chunk, NewChunk, nullptr)) - { - // someone else beat us to the add, we don't support multiple concurrent adds - check(0) - } - else - { - NumChunks++; - } - } - check(ChunkIndex < NumChunks && Chunks[ChunkIndex]); // should have a valid pointer now - } - - /** - * Return a pointer to the pointer to a given element - * @param Index The Index of an element we want to retrieve the pointer-to-pointer for - **/ - FORCEINLINE_DEBUGGABLE ElementType const* const* GetItemPtr(int32 Index) const - { - int32 ChunkIndex = Index / ElementsPerChunk; - int32 WithinChunkIndex = Index % ElementsPerChunk; - checkf(IsValidIndex(Index), TEXT("IsValidIndex(%d)"), Index); - checkf(ChunkIndex < NumChunks, TEXT("ChunkIndex (%d) < NumChunks (%d)"), ChunkIndex, NumChunks); - checkf(Index < MaxTotalElements, TEXT("Index (%d) < MaxTotalElements (%d)"), Index, MaxTotalElements); - ElementType** Chunk = Chunks[ChunkIndex]; - check(Chunk); - return Chunk + WithinChunkIndex; - } - -public: - /** Constructor : Probably not thread safe **/ - TStaticIndirectArrayThreadSafeRead() - : NumElements(0) - , NumChunks(0) - { - FMemory::Memzero(Chunks); - } - /** - * Return the number of elements in the array - * Thread safe, but you know, someone might have added more elements before this even returns - * @return the number of elements in the array - **/ - FORCEINLINE int32 Num() const - { - return NumElements.Load(EMemoryOrder::Relaxed); - } - /** - * Return if this index is valid - * Thread safe, if it is valid now, it is valid forever. Other threads might be adding during this call. - * @param Index Index to test - * @return true, if this is a valid - **/ - FORCEINLINE bool IsValidIndex(int32 Index) const - { - return Index < Num() && Index >= 0; - } - /** - * Return a reference to an element - * @param Index Index to return - * @return a reference to the pointer to the element - * Thread safe, if it is valid now, it is valid forever. This might return nullptr, but by then, some other thread might have made it non-nullptr. - **/ - FORCEINLINE ElementType const* const& operator[](int32 Index) const - { - ElementType const* const* ItemPtr = GetItemPtr(Index); - check(ItemPtr); - return *ItemPtr; - } - /** - * Add more elements to the array - * @param NumToAdd Number of elements to add - * @return the number of elements in the container before we did the add. In other words, the add index. - * Not thread safe. This should only be called by one thread, but the other methods can be called while this is going on. - **/ - int32 AddZeroed(int32 NumToAdd) - { - int32 Result = NumElements; - check(Result + NumToAdd <= MaxTotalElements); - ExpandChunksToIndex(Result + NumToAdd - 1); - NumElements += NumToAdd; - return Result; - } - /** - * Return a naked pointer to the fundamental data structure for debug visualizers. - **/ - ElementType*** GetRootBlockForDebuggerVisualizers() - { - return Chunks; - } - /** - * Make sure chunks are allocated to hold the specified capacity of items. This is NOT thread safe. - **/ - void Reserve(int32 Capacity) - { - check(Capacity >= 0 && Capacity <= MaxTotalElements); - if (Capacity > NumElements.Load(EMemoryOrder::Relaxed)) - { - int32 MaxChunks = (Capacity + ElementsPerChunk - 1) / ElementsPerChunk; - check(MaxChunks >= NumChunks); - for (int32 ChunkIndex = 0; ChunkIndex < MaxChunks; ++ChunkIndex) - { - if (!Chunks[ChunkIndex]) - { - ElementType** NewChunk = (ElementType**)FMemory::Malloc(sizeof(ElementType*) * ElementsPerChunk); - FMemory::Memzero(NewChunk, sizeof(ElementType*) * ElementsPerChunk); - Chunks[ChunkIndex] = NewChunk; - } - } - NumChunks = MaxChunks; - } - } -}; - -// Typedef for the threadsafe master name table. -// CAUTION: If you change those constants, you probably need to update the debug visualizers. -typedef TStaticIndirectArrayThreadSafeRead TNameEntryArray; - /** * The minimum amount of data required to reconstruct a name * This is smaller than FName, but you lose the case-preserving behavior */ -struct CORE_API FMinimalName +struct FMinimalName { - FORCEINLINE FMinimalName() - : Index(0) - , Number(NAME_NO_NUMBER_INTERNAL) - { - } + FMinimalName() {} - FORCEINLINE FMinimalName(const EName N) - : Index(N) - , Number(NAME_NO_NUMBER_INTERNAL) - { - } + CORE_API FMinimalName(EName N); - FORCEINLINE FMinimalName(const NAME_INDEX InIndex, const int32 InNumber) + FMinimalName(FNameEntryId InIndex, int32 InNumber) : Index(InIndex) , Number(InNumber) { @@ -542,13 +335,13 @@ struct CORE_API FMinimalName FORCEINLINE bool IsNone() const { - return Index == 0 && Number == 0; + return !Index && Number == NAME_NO_NUMBER_INTERNAL; } /** Index into the Names array (used to find String portion of the string/number pair) */ - NAME_INDEX Index; + FNameEntryId Index; /** Number portion of the string/number pair (stored internally as 1 more than actual, so zero'd memory will be the default, no-instance case) */ - int32 Number; + int32 Number = NAME_NO_NUMBER_INTERNAL; }; /** @@ -556,35 +349,25 @@ struct CORE_API FMinimalName * This will be the same size as FName when WITH_CASE_PRESERVING_NAME is 1, and is used to store an FName in cases where * the size of FName must be constant between build configurations (eg, blueprint bytecode) */ -struct CORE_API FScriptName +struct FScriptName { - FORCEINLINE FScriptName() - : ComparisonIndex(0) - , DisplayIndex(0) - , Number(NAME_NO_NUMBER_INTERNAL) - { - } + FScriptName() {} - FORCEINLINE FScriptName(const EName N) - : ComparisonIndex(N) - , DisplayIndex(N) - , Number(NAME_NO_NUMBER_INTERNAL) - { - } + CORE_API FScriptName(EName N); - FORCEINLINE FScriptName(const NAME_INDEX InComparisonIndex, const NAME_INDEX InDisplayIndex, const int32 InNumber) - : ComparisonIndex(InComparisonIndex) - , DisplayIndex(InDisplayIndex) - , Number(InNumber) + FScriptName(FNameEntryId InComparisonIndex, FNameEntryId InDisplayIndex, int32 InNumber) + : ComparisonIndex(InComparisonIndex) + , DisplayIndex(InDisplayIndex) + , Number(InNumber) { } /** Index into the Names array (used to find String portion of the string/number pair used for comparison) */ - NAME_INDEX ComparisonIndex; + FNameEntryId ComparisonIndex; /** Index into the Names array (used to find String portion of the string/number pair used for display) */ - NAME_INDEX DisplayIndex; + FNameEntryId DisplayIndex; /** Number portion of the string/number pair (stored internally as 1 more than actual, so zero'd memory will be the default, no-instance case) */ - uint32 Number; + uint32 Number = NAME_NO_NUMBER_INTERNAL; }; /** @@ -595,20 +378,16 @@ struct CORE_API FScriptName class CORE_API FName { public: - - FORCEINLINE NAME_INDEX GetComparisonIndex() const + FORCEINLINE FNameEntryId GetComparisonIndex() const { - const NAME_INDEX Index = GetComparisonIndexFast(); - checkName(Index >= 0 && Index < GetNames().Num()); - checkName(GetNames()[Index]); - return Index; + checkName(IsWithinBounds(ComparisonIndex)); + return ComparisonIndex; } - FORCEINLINE NAME_INDEX GetDisplayIndex() const + FORCEINLINE FNameEntryId GetDisplayIndex() const { - const NAME_INDEX Index = GetDisplayIndexFast(); - checkName(Index >= 0 && Index < GetNames().Num()); - checkName(GetNames()[Index]); + const FNameEntryId Index = GetDisplayIndexFast(); + checkName(IsWithinBounds(Index)); return Index; } @@ -622,32 +401,17 @@ public: Number = NewNumber; } - /** Returns the pure name string without any trailing numbers */ - FString GetPlainNameString() const - { - if (const FNameEntry* CurEntry = GetDisplayNameEntry()) - { - return CurEntry->GetPlainNameString(); - } + /** Get name without number part as a dynamically allocated string */ + FString GetPlainNameString() const; - return TEXT("*INVALID*"); - } + /** Convert name without number part into TCHAR buffer and returns string length. Doesn't allocate. */ + uint32 GetPlainNameString(TCHAR(&OutName)[NAME_SIZE]) const; - /** - * Returns the underlying ANSI string pointer. No allocations. Will fail if this is actually a wide name. - */ - FORCEINLINE void GetPlainANSIString(ANSICHAR (&AnsiName)[NAME_SIZE]) const - { - GetDisplayNameEntry()->GetAnsiName(AnsiName); - } + /** Copy ANSI name without number part. Must *only* be used for ANSI FNames. Doesn't allocate. */ + void GetPlainANSIString(ANSICHAR(&AnsiName)[NAME_SIZE]) const; - /** - * Returns the underlying WIDE string pointer. No allocations. Will fail if this is actually an ANSI name. - */ - FORCEINLINE void GetPlainWIDEString(WIDECHAR (&WideName)[NAME_SIZE]) const - { - GetDisplayNameEntry()->GetWideName(WideName); - } + /** Copy wide name without number part. Must *only* be used for wide FNames. Doesn't allocate. */ + void GetPlainWIDEString(WIDECHAR(&WideName)[NAME_SIZE]) const; const FNameEntry* GetComparisonNameEntry() const; const FNameEntry* GetDisplayNameEntry() const; @@ -666,6 +430,31 @@ public: */ void ToString(FString& Out) const; + /** + * Get the number of characters, excluding null-terminator, that ToString() would yield + */ + uint32 GetStringLength() const; + + /** + * Buffer size required for any null-terminated FName string, i.e. [name] '_' [digits] '\0' + */ + static constexpr uint32 StringBufferSize = NAME_SIZE + 1 + 10; // NAME_SIZE includes null-terminator + + /** + * Convert to string buffer to avoid dynamic allocations and returns string length + * + * Fails hard if OutLen < GetStringLength() + 1. StringBufferSize guarantees success. + * + * Note that a default constructed FName returns "None" instead of "" + */ + uint32 ToString(TCHAR* Out, uint32 OutSize) const; + + template + uint32 ToString(TCHAR (&Out)[N]) const + { + return ToString(Out, N); + } + /** * Converts an FName to a readable format, in place, appending to an existing string (ala GetFullName) * @@ -678,55 +467,72 @@ public: */ FORCEINLINE bool IsEqual(const FName& Other, const ENameCase CompareMethod = ENameCase::IgnoreCase, const bool bCompareNumber = true ) const { - return ((CompareMethod == ENameCase::IgnoreCase) ? GetComparisonIndexFast() == Other.GetComparisonIndexFast() : GetDisplayIndexFast() == Other.GetDisplayIndexFast()) + return ((CompareMethod == ENameCase::IgnoreCase) ? ComparisonIndex == Other.ComparisonIndex : GetDisplayIndexFast() == Other.GetDisplayIndexFast()) && (!bCompareNumber || GetNumber() == Other.GetNumber()); } FORCEINLINE bool operator==(const FName& Other) const { - return (GetComparisonIndexFast() == Other.GetComparisonIndexFast()) & (GetNumber() == Other.GetNumber()); + return (ComparisonIndex == Other.ComparisonIndex) & (GetNumber() == Other.GetNumber()); } + FORCEINLINE bool operator!=(const FName& Other) const { return !(*this == Other); } - /** - * Comparison operator used for sorting alphabetically. - */ + FORCEINLINE bool operator==(EName Ename) const + { + return (ComparisonIndex == Ename) & (GetNumber() == 0); + } + + FORCEINLINE bool operator!=(EName Ename) const + { + return (ComparisonIndex != Ename) | (GetNumber() != 0); + } + + UE_DEPRECATED(4.23, "Please use FastLess() / FNameFastLess or LexicalLess() / FNameLexicalLess instead. " + "Default lexical sort order is deprecated to avoid unintended expensive sorting. ") FORCEINLINE bool operator<( const FName& Other ) const + { + return LexicalLess(Other); + } + + UE_DEPRECATED(4.23, "Please use B.FastLess(A) or B.LexicalLess(A) instead of A > B.") + FORCEINLINE bool operator>(const FName& Other) const + { + return Other.LexicalLess(*this); + } + + /** Fast non-alphabetical order that is only stable during this process' lifetime. */ + FORCEINLINE bool FastLess(const FName& Other) const + { + return CompareIndexes(Other) < 0; + } + + /** Slow alphabetical order that is stable / deterministic over process runs. */ + FORCEINLINE bool LexicalLess(const FName& Other) const { return Compare(Other) < 0; } - /** - * Comparison operator used for sorting alphabetically. - */ - FORCEINLINE bool operator>(const FName& Other) const - { - return Compare(Other) > 0; - } - + /** True for FName(), FName(NAME_None) and FName("None") */ FORCEINLINE bool IsNone() const { - return GetComparisonIndexFast() == 0 && GetNumber() == 0; - } - - FORCEINLINE bool IsValid() const - { - TNameEntryArray& Names = GetNames(); - return GetComparisonIndexFast() >= 0 && GetComparisonIndexFast() < Names.Num() && Names[GetComparisonIndexFast()] != nullptr - && GetDisplayIndexFast() >= 0 && GetDisplayIndexFast() < Names.Num() && Names[GetDisplayIndexFast()] != nullptr; + return !ComparisonIndex && GetNumber() == NAME_NO_NUMBER_INTERNAL; } /** - * Helper function to check if the index is valid. Does not check if the name itself is valid. + * Paranoid sanity check + * + * All FNames are valid except for stomped memory, dangling pointers, etc. + * Should only be used to investigate such bugs and not in production code. */ - FORCEINLINE bool IsValidIndexFast() const - { - return GetComparisonIndexFast() >= 0 && GetComparisonIndexFast() < GetNames().Num() - && GetDisplayIndexFast() >= 0 && GetDisplayIndexFast() < GetNames().Num(); - } + bool IsValid() const { return IsWithinBounds(ComparisonIndex); } + + /** Paranoid sanity check, same as IsValid() */ + bool IsValidIndexFast() const { return IsValid(); } + /** * Checks to see that a given name-like string follows the rules that Unreal requires. @@ -792,20 +598,6 @@ public: return IsValidXName(ToString(), INVALID_LONGPACKAGE_CHARACTERS, &OutReason); } -#ifdef IMPLEMENT_ASSIGNMENT_OPERATOR_MANUALLY - // Assignment operator - FORCEINLINE FName& operator=(const FName& Other) - { - this->ComparisonIndex = Other.ComparisonIndex; -#if WITH_CASE_PRESERVING_NAME - this->DisplayIndex = Other.DisplayIndex; -#endif - this->Number = Other.Number; - - return *this; - } -#endif - /** * Compares name to passed in one. Sort is alphabetical ascending. * @@ -822,13 +614,12 @@ public: */ FORCEINLINE int32 CompareIndexes(const FName& Other) const { - int32 ComparisonDiff = GetComparisonIndexFast() - Other.GetComparisonIndexFast(); - - if (ComparisonDiff == 0) + if (int32 ComparisonDiff = ComparisonIndex.CompareFast(Other.ComparisonIndex)) { - return GetNumber() - Other.GetNumber(); + return ComparisonDiff; } - return ComparisonDiff; + + return GetNumber() - Other.GetNumber(); } /** @@ -836,15 +627,7 @@ public: * * @param N The hardcoded value the string portion of the name will have. The number portion will be NAME_NO_NUMBER */ - FORCEINLINE FName( EName N ) - : ComparisonIndex( N ) -#if WITH_CASE_PRESERVING_NAME - , DisplayIndex( N ) -#endif - , Number( NAME_NO_NUMBER_INTERNAL ) - { - check(N < NAME_MaxHardcodedNameIndex); - } + FName(EName N); /** * Create an FName with a hardcoded string index and (instance). @@ -852,18 +635,7 @@ public: * @param N The hardcoded value the string portion of the name will have * @param InNumber The hardcoded value for the number portion of the name */ - FORCEINLINE FName( EName N, int32 InNumber ) - : ComparisonIndex( N ) -#if WITH_CASE_PRESERVING_NAME - , DisplayIndex( N ) -#endif - , Number( InNumber ) - { - // If this fires the enum was out of bounds - did you pass an index instead? - // If you want to clone an FName with a new number, FName( const FName& Other, int32 InNumber ) is the thing you want - check(N < NAME_MaxHardcodedNameIndex); - check(InNumber >= 0 && InNumber <= 0xffffff); - } + FName(EName N, int32 InNumber); /** * Create an FName from an existing string, but with a different instance. @@ -884,7 +656,7 @@ public: * Create an FName from its component parts * Only call this if you *really* know what you're doing */ - FORCEINLINE FName( const NAME_INDEX InComparisonIndex, const NAME_INDEX InDisplayIndex, const int32 InNumber ) + FORCEINLINE FName( const FNameEntryId InComparisonIndex, const FNameEntryId InDisplayIndex, const int32 InNumber ) : ComparisonIndex( InComparisonIndex ) #if WITH_CASE_PRESERVING_NAME , DisplayIndex( InDisplayIndex ) @@ -893,15 +665,25 @@ public: { } +#if WITH_CASE_PRESERVING_NAME + static FNameEntryId GetComparisonIdFromDisplayId(FNameEntryId DisplayId); +#else + static FNameEntryId GetComparisonIdFromDisplayId(FNameEntryId DisplayId) { return DisplayId; } +#endif + + /** + * Only call this if you *really* know what you're doing + */ + static FName CreateFromDisplayId(FNameEntryId DisplayId, int32 Number) + { + return FName(GetComparisonIdFromDisplayId(DisplayId), DisplayId, Number); + } + /** * Default constructor, initialized to None */ FORCEINLINE FName() - : ComparisonIndex( 0 ) -#if WITH_CASE_PRESERVING_NAME - , DisplayIndex( 0 ) -#endif - , Number( NAME_NO_NUMBER_INTERNAL ) + : Number(NAME_NO_NUMBER_INTERNAL) { } @@ -909,8 +691,11 @@ public: * Scary no init constructor, used for something obscure in UObjectBase */ explicit FName(ENoInit) - { - } + : ComparisonIndex(NoInit) +#if WITH_CASE_PRESERVING_NAME + , DisplayIndex(NoInit) +#endif + {} /** * Create an FName. If FindType is FNAME_Find, and the string part of the name @@ -922,9 +707,9 @@ public: FName(const WIDECHAR* Name, EFindName FindType=FNAME_Add); FName(const ANSICHAR* Name, EFindName FindType=FNAME_Add); - // Deprecated bUnused - UE_DEPRECATED(4.12, "Removed bUnused from FName") FName(const WIDECHAR* Name, EFindName FindType, bool bUnused) : FName(Name, FindType) { } - UE_DEPRECATED(4.12, "Removed bUnused from FName") FName(const ANSICHAR* Name, EFindName FindType, bool bUnused) : FName(Name, FindType) { } + /** Create FName from non-null string with known length */ + FName(int32 Len, const WIDECHAR* Name, EFindName FindType=FNAME_Add); + FName(int32 Len, const ANSICHAR* Name, EFindName FindType=FNAME_Add); /** * Create an FName. If FindType is FNAME_Find, and the string part of the name @@ -935,7 +720,21 @@ public: * @param FindType Action to take (see EFindName) * @param bSplitName true if the trailing number should be split from the name when Number == NAME_NO_NUMBER_INTERNAL, or false to always use the name as-is */ - FName( const TCHAR* Name, int32 InNumber, EFindName FindType=FNAME_Add, const bool bSplitName=true ); + FName(const WIDECHAR* Name, int32 InNumber, EFindName FindType = FNAME_Add); + FName(const ANSICHAR* Name, int32 InNumber, EFindName FindType = FNAME_Add); + FName(int32 Len, const WIDECHAR* Name, int32 Number, EFindName FindType = FNAME_Add); + FName(int32 Len, const ANSICHAR* Name, int32 InNumber, EFindName FindType = FNAME_Add); + + /** + * Create an FName. If FindType is FNAME_Find, and the string part of the name + * doesn't already exist, then the name will be NAME_None + * + * @param Name Value for the string portion of the name + * @param Number Value for the number portion of the name + * @param FindType Action to take (see EFindName) + * @param bSplitName true if the trailing number should be split from the name when Number == NAME_NO_NUMBER_INTERNAL, or false to always use the name as-is + */ + FName( const TCHAR* Name, int32 InNumber, EFindName FindType, bool bSplitName); /** * Constructor used by FLinkerLoad when loading its name table; Creates an FName with an instance @@ -943,68 +742,15 @@ public: * this version skips calculating the hashes of the names if possible */ FName(const FNameEntrySerialized& LoadedEntry); - - /** - * Create an FName with a hardcoded string index. - * - * @param HardcodedIndex The hardcoded value the string portion of the name will have. - * @param Name The hardcoded name to initialize - */ - explicit FName( EName HardcodedIndex, const TCHAR* Name ); - + /** * Equality operator. * * @param Other String to compare this name to * @return true if name matches the string, false otherwise */ - template - bool operator==(const CharType* Other) const - { - // Make NAME_None == TEXT("") or nullptr consistent with NAME_None == FName(TEXT("")) or FName(nullptr) - if (Other == nullptr || Other[0] == 0) - { - return (*this == NAME_None); - } - - // Find name entry associated with this FName. - bool bAreNamesMatching = false; - const FNameEntry* const Entry = GetComparisonNameEntry(); - - if (Entry != nullptr) - { - // Temporary buffer to hold split name in case passed in name is of Name_Number format. - WIDECHAR TempBuffer[NAME_SIZE]; - int32 InNumber = NAME_NO_NUMBER_INTERNAL; - int32 TempNumber = NAME_NO_NUMBER_INTERNAL; - - // Check whether we need to split the passed in string into name and number portion. - auto WideOther = StringCast(Other); - const WIDECHAR* WideOtherPtr = WideOther.Get(); - if (SplitNameWithCheck(WideOtherPtr, TempBuffer, ARRAY_COUNT(TempBuffer), TempNumber)) - { - WideOtherPtr = TempBuffer; - InNumber = NAME_EXTERNAL_TO_INTERNAL(TempNumber); - } - - // Report a match if both the number and string portion match. - if (InNumber == GetNumber()) - { - if (Entry->IsWide()) - { - WIDECHAR EntryTempBuffer[NAME_SIZE]; - bAreNamesMatching = !FPlatformString::Stricmp(WideOtherPtr, Entry->GetWideNamePtr(EntryTempBuffer)); - } - else - { - ANSICHAR EntryTempBuffer[NAME_SIZE]; - bAreNamesMatching = !FPlatformString::Stricmp(WideOtherPtr, Entry->GetAnsiNamePtr(EntryTempBuffer)); - } - } - } - - return bAreNamesMatching; - } + bool operator==(const ANSICHAR* Other) const; + bool operator==(const WIDECHAR* Other) const; /** * Inequality operator. @@ -1018,76 +764,36 @@ public: return !operator==(Other); } - template - static uint16 GetCasePreservingHash(const TCharType* Source); - template - static uint16 GetNonCasePreservingHash(const TCharType* Source); - static void DisplayHash( class FOutputDevice& Ar ); - static FString SafeString( int32 InDisplayIndex, int32 InstanceNumber=NAME_NO_NUMBER_INTERNAL ) - { - TNameEntryArray& Names = GetNames(); - return GetIsInitialized() - ? (Names.IsValidIndex(InDisplayIndex) && Names[InDisplayIndex]) - ? FName(InDisplayIndex, InDisplayIndex, InstanceNumber).ToString() - : FString(TEXT("*INVALID*")) - : FString(TEXT("*UNINITIALIZED*")); - } - static int32 GetMaxNames() - { - return GetNames().Num(); - } + static FString SafeString(FNameEntryId InDisplayIndex, int32 InstanceNumber = NAME_NO_NUMBER_INTERNAL); + /** * @return Size of all name entries. */ - static int32 GetNameEntryMemorySize() - { - return NameEntryMemorySize; - } + static int32 GetNameEntryMemorySize(); + /** * @return Size of Name Table object as a whole */ - static int32 GetNameTableMemorySize() - { - return GetNameEntryMemorySize() + (GetMaxNames() * sizeof(FNameEntry*)) + sizeof(NameHashHead) + sizeof(NameHashTail); - } + static int32 GetNameTableMemorySize(); /** * @return number of ansi names in name table */ - static int32 GetNumAnsiNames() - { - return NumAnsiNames; - } + static int32 GetNumAnsiNames(); + /** * @return number of wide names in name table */ - static int32 GetNumWideNames() - { - return NumWideNames; - } - static FNameEntry const* GetEntry( int i ) - { - return GetNames()[i]; - } + static int32 GetNumWideNames(); + + static TArray DebugDump(); + + static FNameEntry const* GetEntry(EName Ename); + static FNameEntry const* GetEntry(FNameEntryId Id); + //@} - - /** - * Helper function to split an old-style name (Class_Number, ie Rocket_17) into - * the component parts usable by new-style FNames. Only use results if this function - * returns true. - * - * @param OldName Old-style name - * @param NewName Output string portion of the name/number pair - * @param NewNameLen Size of NewName buffer (in TCHAR units) - * @param NewNumber Number portion of the name/number pair - * - * @return true if the name was split, only then will NewName/NewNumber have valid values - */ - static bool SplitNameWithCheck(const WIDECHAR* OldName, WIDECHAR* NewName, int32 NewNameLen, int32& NewNumber); - - /** Singleton to retrieve a table of all names (multithreaded) for debug visualizers. */ - static FNameEntry*** GetNameTableForDebuggerVisualizers_MT(); + /** Run autotest on FNames. */ static void AutoTest(); @@ -1102,105 +808,25 @@ public: */ static FString NameToDisplayString( const FString& InDisplayName, const bool bIsBool ); + /** Get the EName that this FName represents or nullptr */ + const EName* ToEName() const; + private: - union - { - struct - { - /** Index into the Names array (used to find String portion of the string/number pair used for comparison) */ - NAME_INDEX ComparisonIndex; - #if WITH_CASE_PRESERVING_NAME - /** Index into the Names array (used to find String portion of the string/number pair used for display) */ - NAME_INDEX DisplayIndex; - #endif - /** Number portion of the string/number pair (stored internally as 1 more than actual, so zero'd memory will be the default, no-instance case) */ - uint32 Number; - }; - }; - - /** Name hash head - used to iterate the single-linked list. */ - static TAtomic NameHashHead[FNameDefs::NameHashBucketCount]; - /** Name hash tail - insert new entries after this - NON ATOMIC! */ - static TAtomic NameHashTail[FNameDefs::NameHashBucketCount]; - /** Size of all name entries. */ - static int32 NameEntryMemorySize; - /** Number of ANSI names in name table. */ - static int32 NumAnsiNames; - /** Number of wide names in name table. */ - static int32 NumWideNames; - - /** Singleton to retrieve a table of all names. */ - static TNameEntryArray& GetNames(); - /** - * Return the static initialized flag. Must be in a function like this so we don't have problems with - * different initialization order of static variables across the codebase. Use this function to get or set the variable. - */ - static bool& GetIsInitialized(); - static void StaticInit(); - + /** Index into the Names array (used to find String portion of the string/number pair used for comparison) */ + FNameEntryId ComparisonIndex; +#if WITH_CASE_PRESERVING_NAME + /** Index into the Names array (used to find String portion of the string/number pair used for display) */ + FNameEntryId DisplayIndex; +#endif + /** Number portion of the string/number pair (stored internally as 1 more than actual, so zero'd memory will be the default, no-instance case) */ + uint32 Number; + friend const TCHAR* DebugFName(int32); friend const TCHAR* DebugFName(int32, int32); friend const TCHAR* DebugFName(FName&); - template - friend FNameEntry* AllocateNameEntry(const TCharType* Name, NAME_INDEX Index); - /** Used to increment the correct counter based upon TCharType */ - template friend void IncrementNameCount(); - /** - * Initialization from a wide string - * - * @param InName String name of the name/number pair - * @param InNumber Number part of the name/number pair - * @param FindType Operation to perform on names - * @param bSplitName If true, this function will attempt to split a number off of the string portion (turning Rocket_17 to Rocket and number 17) - * @param HardcodeIndex If >= 0, this represents a hardcoded FName and so automatically gets this index - */ - void Init(const WIDECHAR* InName, int32 InNumber, EFindName FindType, bool bSplitName=true, int32 HardcodeIndex = -1); - /** - * Version that takes the hashes as part of the constructor (serialized previously) - */ - void Init(const WIDECHAR* InName, int32 InNumber, EFindName FindType, const uint16 NonCasePreservingHash, const uint16 CasePreservingHash); - - /** - * Initialization from an ANSI string - * - * @param InName String name of the name/number pair - * @param InNumber Number part of the name/number pair - * @param FindType Operation to perform on names - * @param bSplitName If true, this function will attempt to split a number off of the string portion (turning Rocket_17 to Rocket and number 17) - * @param HardcodeIndex If >= 0, this represents a hardcoded FName and so automatically gets this index - */ - void Init(const ANSICHAR* InName, int32 InNumber, EFindName FindType, bool bSplitName=true, int32 HardcodeIndex = -1); - /** - * Version that takes the hashes as part of the constructor (serialized previously). Skips InitInternal_HashSplit for loading perf reasons - */ - void Init(const ANSICHAR* InName, int32 InNumber, EFindName FindType, const uint16 NonCasePreservingHash, const uint16 CasePreservingHash); - - template - void InitInternal(const TCharType* InName, int32 InNumber, const EFindName FindType, const int32 HardcodeIndex, const uint16 NonCasePreservingHash, const uint16 CasePreservingHash); - - /** - * Version of InitInternal that calculates the hash after splitting the string. Used by runtime FName construction - */ - template - void InitInternal_HashSplit(const TCharType* InName, int32 InNumber, const EFindName FindType, bool bSplitName, const int32 HardcodeIndex); - - template - static bool InitInternal_FindOrAdd(const TCharType* InName, const EFindName FindType, const int32 HardcodeIndex, const uint16 NonCasePreservingHash, const uint16 CasePreservingHash, int32& OutComparisonIndex, int32& OutDisplayIndex); - - template - static bool InitInternal_FindOrAddNameEntry(const TCharType* InName, const EFindName FindType, const ENameCase ComparisonMode, const uint16 iHash, int32& OutIndex); - - template - static bool SplitNameWithCheckImpl(const TCharType* OldName, TCharType* NewName, int32 NewNameLen, int32& NewNumber); - - FORCEINLINE NAME_INDEX GetComparisonIndexFast() const - { - return ComparisonIndex; - } - - FORCEINLINE NAME_INDEX GetDisplayIndexFast() const + FORCEINLINE FNameEntryId GetDisplayIndexFast() const { #if WITH_CASE_PRESERVING_NAME return DisplayIndex; @@ -1209,17 +835,16 @@ private: #endif } - /** Singleton to retrieve the critical section. */ - static FCriticalSection* GetCriticalSection(); + static bool IsWithinBounds(FNameEntryId Id); }; template<> struct TIsZeroConstructType { enum { Value = true }; }; Expose_TNameOf(FName) -inline uint32 GetTypeHash( const FName N ) +FORCEINLINE uint32 GetTypeHash(FName Name) { - return N.GetComparisonIndex() + N.GetNumber(); + return GetTypeHash(Name.GetComparisonIndex()) + Name.GetNumber(); } FORCEINLINE FString LexToString(const FName& Name) @@ -1242,7 +867,6 @@ FORCEINLINE FName MinimalNameToName(const FMinimalName& InName) return FName(InName.Index, InName.Index, InName.Number); } - FORCEINLINE FScriptName NameToScriptName(const FName& InName) { return FScriptName(InName.GetComparisonIndex(), InName.GetDisplayIndex(), InName.GetNumber()); @@ -1283,52 +907,52 @@ inline bool operator!=(const CharType *LHS, const FName &RHS) /** FNames act like PODs. */ template <> struct TIsPODType { enum { Value = true }; }; - -/** Sort predicate to sort FName by index instead of alphabetically, pass to anything that wants TLess */ -struct FNameSortIndexes +/** Fast non-alphabetical order that is only stable during this process' lifetime */ +struct FNameFastLess { FORCEINLINE bool operator()(const FName& A, const FName& B) const { return A.CompareIndexes(B) < 0; } + + FORCEINLINE bool operator()(FNameEntryId A, FNameEntryId B) const + { + return A.FastLess(B); + } +}; + +UE_DEPRECATED(4.23, "Please use FNameFastLess instead.") +typedef FNameFastLess FNameSortIndexes; + +/** Slow alphabetical order that is stable / deterministic over process runs */ +struct FNameLexicalLess +{ + FORCEINLINE bool operator()(const FName& A, const FName& B) const + { + return A.Compare(B) < 0; + } + + FORCEINLINE bool operator()(FNameEntryId A, FNameEntryId B) const + { + return A.LexicalLess(B); + } }; #ifndef WITH_CUSTOM_NAME_ENCODING - - inline void FNameEntry::GetAnsiName(ANSICHAR(&OutName)[NAME_SIZE]) const - { - check(!IsWide()); - FCStringAnsi::Strcpy(OutName, AnsiName); - } - - inline void FNameEntry::GetWideName(WIDECHAR(&OutName)[NAME_SIZE]) const - { - check(IsWide()); - FCStringWide::Strcpy(OutName, WideName); - } - - inline ANSICHAR const* FNameEntry::GetAnsiNamePtr(ANSICHAR(&OptionalTempBuffer)[NAME_SIZE]) const - { - check(!IsWide()); - return AnsiName; - } - - inline WIDECHAR const* FNameEntry::GetWideNamePtr(WIDECHAR(&OptionalTempBuffer)[NAME_SIZE]) const - { - check(IsWide()); - return WideName; - } - - inline void FNameEntry::SetAnsiName(const ANSICHAR* SrcName, int32 SrcNameLen) - { - check(!IsWide()); - FCStringAnsi::Strcpy(AnsiName, SrcNameLen + 1, SrcName); - } - - inline void FNameEntry::SetWideName(const WIDECHAR* SrcName, int32 SrcNameLen) - { - check(IsWide()); - FCStringWide::Strcpy(WideName, SrcNameLen + 1, SrcName); - } - +inline void FNameEntry::Encode(ANSICHAR*, uint32) {} +inline void FNameEntry::Encode(WIDECHAR*, uint32) {} +inline void FNameEntry::Decode(ANSICHAR*, uint32) {} +inline void FNameEntry::Decode(WIDECHAR*, uint32) {} #endif + +struct FNameDebugVisualizer +{ + CORE_API static uint8** GetBlocks(); +private: + static constexpr uint32 EntryStride = alignof(FNameEntry); + static constexpr uint32 OffsetBits = 16; + static constexpr uint32 BlockBits = 13; + static constexpr uint32 OffsetMask = (1 << OffsetBits) - 1; + static constexpr uint32 UnusedMask = UINT32_MAX << BlockBits << OffsetBits; + static constexpr uint32 MaxLength = NAME_SIZE; +}; \ No newline at end of file diff --git a/Engine/Source/Runtime/Core/Public/UObject/PropertyPortFlags.h b/Engine/Source/Runtime/Core/Public/UObject/PropertyPortFlags.h index fce67cbeb8d8..8841c76be383 100644 --- a/Engine/Source/Runtime/Core/Public/UObject/PropertyPortFlags.h +++ b/Engine/Source/Runtime/Core/Public/UObject/PropertyPortFlags.h @@ -32,7 +32,11 @@ enum EPropertyPortFlags /** Indicates that this is a blueprint pin or something else that is saved to disk as import text */ PPF_SerializedAsImportText = 0x00000040, - // = 0x00000080, + /** + * Set if this is being exported/imported for an external editor format like CSV. + * If true, it uses authored names instead of internal names and default values are always written out. + */ + PPF_ExternalEditor = 0x00000080, /** only include properties which are marked CPF_InstancedReference */ PPF_SubobjectsOnly = 0x00000100, diff --git a/Engine/Source/Runtime/Core/Public/UObject/UnrealNames.h b/Engine/Source/Runtime/Core/Public/UObject/UnrealNames.h index 955f26c93048..f534e5b20273 100644 --- a/Engine/Source/Runtime/Core/Public/UObject/UnrealNames.h +++ b/Engine/Source/Runtime/Core/Public/UObject/UnrealNames.h @@ -4,22 +4,12 @@ #include "CoreTypes.h" +struct FNameEntryId; + // // Macros. // -/** Index of highest hardcoded name to be replicated by index by the networking code - * @warning: changing this number or making any change to the list of hardcoded names with index - * less than this value breaks network compatibility, which by default checks for the same changelist - * @note: names with a greater value than this can still be replicated, but they are sent as - * strings instead of an efficient index - */ -#define MAX_NETWORKED_HARDCODED_NAME 410 - -// -// Hardcoded names. -// - // Define a message as an enumeration. #define REGISTER_NAME(num,name) NAME_##name = num, enum EName @@ -31,3 +21,20 @@ enum EName NAME_MaxHardcodedNameIndex, }; #undef REGISTER_NAME + +CORE_API const TCHAR* LexToString(EName Ename); + +/** Index of highest hardcoded name to be replicated by index by the networking code + * @warning: changing this number or making any change to the list of hardcoded names with index + * less than this value breaks network compatibility, which by default checks for the same changelist + * @note: names with a greater value than this can still be replicated, but they are sent as + * strings instead of an efficient index + * + * @see ShouldReplicateENameAsInteger() + */ +#define MAX_NETWORKED_HARDCODED_NAME 410 + +inline bool ShouldReplicateAsInteger(EName Ename) +{ + return Ename <= MAX_NETWORKED_HARDCODED_NAME; +} diff --git a/Engine/Source/Runtime/Core/Public/UObject/WeakObjectPtrTemplates.h b/Engine/Source/Runtime/Core/Public/UObject/WeakObjectPtrTemplates.h index 3ac46601aa67..cc2f0217f06f 100644 --- a/Engine/Source/Runtime/Core/Public/UObject/WeakObjectPtrTemplates.h +++ b/Engine/Source/Runtime/Core/Public/UObject/WeakObjectPtrTemplates.h @@ -9,28 +9,22 @@ #include "Templates/AndOrNot.h" #include "Containers/Map.h" -struct FWeakObjectPtr; - -/*** - * +/** * FWeakObjectPtr is a weak pointer to a UObject. * It can return nullptr later if the object is garbage collected. * It has no impact on if the object is garbage collected or not. * It can't be directly used across a network. * * Most often it is used when you explicitly do NOT want to prevent something from being garbage collected. - **/ + */ struct FWeakObjectPtr; template struct TWeakObjectPtr; - -/*** -* -* TWeakObjectPtr is templatized version of the generic FWeakObjectPtr -* -**/ +/** + * TWeakObjectPtr is the templated version of the generic FWeakObjectPtr + */ template struct TWeakObjectPtr : private TWeakObjectPtrBase { @@ -46,7 +40,7 @@ public: /** * Construct from a null pointer - **/ + */ FORCEINLINE TWeakObjectPtr(TYPE_OF_NULLPTR) : TWeakObjectPtrBase((UObject*)nullptr) { @@ -55,7 +49,7 @@ public: /** * Construct from an object pointer * @param Object object to create a weak pointer to - **/ + */ template < typename U, typename = typename TEnableIf::Value>::Type @@ -88,7 +82,7 @@ public: /** * Construct from another weak pointer of another type, intended for derived-to-base conversions * @param Other weak pointer to copy from - **/ + */ template FORCEINLINE TWeakObjectPtr(const TWeakObjectPtr& Other) : TWeakObjectPtrBase(*(TWeakObjectPtrBase*)&Other) // we do a C-style cast to private base here to avoid clang 3.6.0 compilation problems with friend declarations @@ -99,7 +93,7 @@ public: } /** - * Reset the weak pointer back to the NULL state + * Reset the weak pointer back to the null state */ FORCEINLINE void Reset() { @@ -109,7 +103,7 @@ public: /** * Copy from an object pointer * @param Object object to create a weak pointer to - **/ + */ template FORCEINLINE typename TEnableIf::Value, TWeakObjectPtr&>::Type operator=(U* Object) { @@ -129,7 +123,7 @@ public: /** * Assign from another weak pointer, intended for derived-to-base conversions * @param Other weak pointer to copy from - **/ + */ template FORCEINLINE TWeakObjectPtr& operator=(const TWeakObjectPtr& Other) { @@ -144,9 +138,9 @@ public: /** * Dereference the weak pointer - * @param bEvenIfPendingKill, if this is true, pendingkill objects are considered valid - * @return NULL if this object is gone or the weak pointer was NULL, otherwise a valid uobject pointer - **/ + * @param bEvenIfPendingKill if this is true, pendingkill objects are considered valid + * @return nullptr if this object is gone or the weak pointer is explicitly null, otherwise a valid uobject pointer + */ FORCEINLINE T* Get(bool bEvenIfPendingKill) const { return (T*)TWeakObjectPtrBase::Get(bEvenIfPendingKill); @@ -168,27 +162,27 @@ public: /** * Dereference the weak pointer - **/ - FORCEINLINE T & operator*() const + */ + FORCEINLINE T& operator*() const { return *Get(); } /** * Dereference the weak pointer - **/ - FORCEINLINE T * operator->() const + */ + FORCEINLINE T* operator->() const { return Get(); } /** * Test if this points to a live UObject - * @param bEvenIfPendingKill, if this is true, pendingkill objects are considered valid - * @param bThreadsafeTest, if true then function will just give you information whether referenced - * UObject is gone forever (@return false) or if it is still there (@return true, no object flags checked). + * @param bEvenIfPendingKill if this is true, pendingkill objects are considered valid + * @param bThreadsafeTest if true then function will just give you information whether referenced + * UObject is gone forever (return false) or if it is still there (return true, no object flags checked). * @return true if Get() would return a valid non-null pointer - **/ + */ FORCEINLINE bool IsValid(bool bEvenIfPendingKill, bool bThreadsafeTest = false) const { return TWeakObjectPtrBase::IsValid(bEvenIfPendingKill, bThreadsafeTest); @@ -205,15 +199,28 @@ public: /** * Slightly different than !IsValid(), returns true if this used to point to a UObject, but doesn't any more and has not been assigned or reset in the mean time. - * @param bIncludingIfPendingKill, if this is true, pendingkill objects are considered stale - * @param bThreadsafeTest, set it to true when testing outside of Game Thread. Results in false if WeakObjPtr point to an existing object (no flags checked) + * @param bIncludingIfPendingKill if this is true, pendingkill objects are considered stale + * @param bThreadsafeTest set it to true when testing outside of Game Thread. Results in false if WeakObjPtr point to an existing object (no flags checked) * @return true if this used to point at a real object but no longer does. - **/ + */ FORCEINLINE bool IsStale(bool bIncludingIfPendingKill = true, bool bThreadsafeTest = false) const { return TWeakObjectPtrBase::IsStale(bIncludingIfPendingKill, bThreadsafeTest); } + + /** + * Returns true if this pointer was explicitly assigned to null, was reset, or was never initialized. + * If this returns true, IsValid() and IsStale() will both return false. + */ + FORCEINLINE bool IsExplicitlyNull() const + { + return TWeakObjectPtrBase::IsExplicitlyNull(); + } + /** + * Returns true if two weak pointers were originally set to the same object, even if they are now stale + * @param Other weak pointer to compare to + */ FORCEINLINE bool HasSameIndexAndSerialNumber(const TWeakObjectPtr& Other) const { return static_cast(*this).HasSameIndexAndSerialNumber(static_cast(Other)); @@ -225,6 +232,9 @@ public: return GetTypeHash(static_cast(WeakObjectPtr)); } + /** + * Weak object pointer serialization, this forwards to FArchive::operator<<(struct FWeakObjectPtr&) or an override + */ friend FArchive& operator<<( FArchive& Ar, TWeakObjectPtr& WeakObjectPtr ) { Ar << static_cast(WeakObjectPtr); @@ -239,6 +249,11 @@ FORCEINLINE TWeakObjectPtr MakeWeakObjectPtr(T* Ptr) return TWeakObjectPtr(Ptr); } +/** + * Compare weak pointers for equality. + * If both pointers would return nullptr from Get() they count as equal even if they were not initialized to the same object. + * @param Other weak pointer to compare to + */ template FORCENOINLINE bool operator==(const TWeakObjectPtr& Lhs, const TWeakObjectPtr& Rhs) { @@ -285,6 +300,10 @@ FORCENOINLINE bool operator==(TYPE_OF_NULLPTR, const TWeakObjectPtr FORCENOINLINE bool operator!=(const TWeakObjectPtr& Lhs, const TWeakObjectPtr& Rhs) { @@ -402,6 +421,7 @@ template struct TIsPODType > { enum { Value = tru template struct TIsZeroConstructType > { enum { Value = true }; }; template struct TIsWeakPointerType > { enum { Value = true }; }; +/** Utility function to fill in a TArray from a TArray> */ template void CopyFromWeakArray(DestArrayType& Dest, const SourceArrayType& Src) { diff --git a/Engine/Source/Runtime/Core/Public/Unix/UnixCriticalSection.h b/Engine/Source/Runtime/Core/Public/Unix/UnixCriticalSection.h index 7770af969290..b3e9c0daa8e4 100644 --- a/Engine/Source/Runtime/Core/Public/Unix/UnixCriticalSection.h +++ b/Engine/Source/Runtime/Core/Public/Unix/UnixCriticalSection.h @@ -14,20 +14,20 @@ class FUnixSystemWideCriticalSection { public: /** Construct a named, system-wide critical section and attempt to get access/ownership of it */ - explicit FUnixSystemWideCriticalSection(const FString& InName, FTimespan InTimeout = FTimespan::Zero()); + explicit CORE_API FUnixSystemWideCriticalSection(const FString& InName, FTimespan InTimeout = FTimespan::Zero()); /** Destructor releases system-wide critical section if it is currently owned */ - ~FUnixSystemWideCriticalSection(); + CORE_API ~FUnixSystemWideCriticalSection(); /** * Does the calling thread have ownership of the system-wide critical section? * * @return True if the system-wide lock is obtained. WARNING: Returns true for abandoned locks so shared resources can be in undetermined states. */ - bool IsValid() const; + CORE_API bool IsValid() const; /** Releases system-wide critical section if it is currently owned */ - void Release(); + CORE_API void Release(); private: FUnixSystemWideCriticalSection(const FUnixSystemWideCriticalSection&); diff --git a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatform.h b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatform.h index 2c9b00ced1fc..344faba049ef 100644 --- a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatform.h +++ b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatform.h @@ -110,6 +110,12 @@ typedef FUnixPlatformTypes FPlatformTypes; #define ABSTRACT abstract +// DLL export and import for types, only supported on clang +#if defined(__clang__) +#define DLLEXPORT_VTABLE __attribute__ ((__type_visibility__("default"))) +#define DLLIMPORT_VTABLE __attribute__ ((__type_visibility__("default"))) +#endif + // DLL export and import definitions #define DLLEXPORT __attribute__((visibility("default"))) #define DLLIMPORT __attribute__((visibility("default"))) diff --git a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformCrashContext.h b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformCrashContext.h index 7cc9a5365a1d..40ecb01d9533 100644 --- a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformCrashContext.h +++ b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformCrashContext.h @@ -5,6 +5,23 @@ #include "CoreTypes.h" #include "GenericPlatform/GenericPlatformCrashContext.h" +/** Passed in through sigqueue for gathering of a callstack from a signal */ +struct ThreadStackUserData +{ + // If we want a backtrace or a callstack + // Backtrace is just a list of program counters and callstack is a symbolicated backtrace + bool bCaptureCallStack; + union + { + ANSICHAR* CallStack; + uint64* BackTrace; + }; + + int32 BackTraceCount; + SIZE_T CallStackSize; + TAtomic bDone; +}; + struct CORE_API FUnixCrashContext : public FGenericCrashContext { /** Signal number */ diff --git a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformMisc.h b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformMisc.h index e3890b7bc524..b06ead99d47d 100644 --- a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformMisc.h +++ b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformMisc.h @@ -134,6 +134,8 @@ struct CORE_API FUnixPlatformMisc : public FGenericPlatformMisc static FString GetOSVersion(); static FString GetLoginId(); + static void CreateGuid(FGuid& Result); + #if STATS || ENABLE_STATNAMEDEVENTS static void BeginNamedEventFrame(); static void BeginNamedEvent(const struct FColor& Color, const TCHAR* Text); diff --git a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformRealTimeSignals.h b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformRealTimeSignals.h index c827b9f4dd3d..b2fdef4d702e 100644 --- a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformRealTimeSignals.h +++ b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformRealTimeSignals.h @@ -10,3 +10,8 @@ // Used in UnixSignalGameHitchHeartBeat.cpp for a hitch signal handler #define HEART_BEAT_SIGNAL SIGRTMIN + 3 + +// Skip SIGRTMIN + 4 default signal used in VTune + +// Using in UnixPlatformCrashContext.cpp/UnixPlatformStackWalk.cpp for gathering current callstack from a thread +#define THREAD_CALLSTACK_GENERATOR SIGRTMIN + 5 diff --git a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformStackWalk.h b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformStackWalk.h index e5e15bd69e99..d00b57f57e30 100644 --- a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformStackWalk.h +++ b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformStackWalk.h @@ -18,6 +18,7 @@ struct CORE_API FUnixPlatformStackWalk : public FGenericPlatformStackWalk static uint32 CaptureStackBackTrace( uint64* BackTrace, uint32 MaxDepth, void* Context = nullptr ); static void StackWalkAndDump( ANSICHAR* HumanReadableString, SIZE_T HumanReadableStringSize, int32 IgnoreCount, void* Context = nullptr ); static void StackWalkAndDumpEx(ANSICHAR* HumanReadableString, SIZE_T HumanReadableStringSize, int32 IgnoreCount, uint32 Flags, void* Context = nullptr); + static uint32 CaptureThreadStackBackTrace(uint64 ThreadId, uint64* BackTrace, uint32 MaxDepth); static void ThreadStackWalkAndDump(ANSICHAR* HumanReadableString, SIZE_T HumanReadableStringSize, int32 IgnoreCount, uint32 ThreadId); static int32 GetProcessModuleCount(); static int32 GetProcessModuleSignatures(FStackWalkModuleInfo *ModuleSignatures, const int32 ModuleSignaturesSize); diff --git a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformString.h b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformString.h index 273dbe07bb30..73d03a1f09e1 100644 --- a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformString.h +++ b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformString.h @@ -36,3 +36,62 @@ struct FUnixPlatformString : public }; typedef FUnixPlatformString FPlatformString; + +// Format specifiers to be able to print values of these types correctly, for example when using UE_LOG. +// SIZE_T format specifier +#define SIZE_T_FMT "zu" +// SIZE_T format specifier for lowercase hexadecimal output +#define SIZE_T_x_FMT "zx" +// SIZE_T format specifier for uppercase hexadecimal output +#define SIZE_T_X_FMT "zX" + +#if PLATFORM_64BITS +// SSIZE_T format specifier +#define SSIZE_T_FMT "lld" +// SSIZE_T format specifier for lowercase hexadecimal output +#define SSIZE_T_x_FMT "llx" +// SSIZE_T format specifier for uppercase hexadecimal output +#define SSIZE_T_X_FMT "llX" + +// UPTRINT format specifier for decimal output +#define UPTRINT_FMT "llu" +// UPTRINT format specifier for lowercase hexadecimal output +#define UPTRINT_x_FMT "llx" +// UPTRINT format specifier for uppercase hexadecimal output +#define UPTRINT_X_FMT "llX" +#else +// SSIZE_T format specifier +#define SSIZE_T_FMT "d" +// SSIZE_T format specifier for lowercase hexadecimal output +#define SSIZE_T_x_FMT "x" +// SSIZE_T format specifier for uppercase hexadecimal output +#define SSIZE_T_X_FMT "X" + +// UPTRINT format specifier for decimal output +#define UPTRINT_FMT "u" +// UPTRINT format specifier for lowercase hexadecimal output +#define UPTRINT_x_FMT "x" +// UPTRINT format specifier for uppercase hexadecimal output +#define UPTRINT_X_FMT "X" +#endif + +// PTRINT format specifier for decimal output +#define PTRINT_FMT SSIZE_T_FMT +// PTRINT format specifier for lowercase hexadecimal output +#define PTRINT_x_FMT SSIZE_T_x_FMT +// PTRINT format specifier for uppercase hexadecimal output +#define PTRINT_X_FMT SSIZE_T_X_FMT + +// int64 format specifier for decimal output +#define INT64_FMT "lld" +// int64 format specifier for lowercase hexadecimal output +#define INT64_x_FMT "llx" +// int64 format specifier for uppercase hexadecimal output +#define INT64_X_FMT "llX" + +// uint64 format specifier for decimal output +#define UINT64_FMT "llu" +// uint64 format specifier for lowercase hexadecimal output +#define UINT64_x_FMT "llx" +// uint64 format specifier for uppercase hexadecimal output +#define UINT64_X_FMT "llX" diff --git a/Engine/Source/Runtime/Core/Public/Windows/AllowWindowsPlatformAtomics.h b/Engine/Source/Runtime/Core/Public/Windows/AllowWindowsPlatformAtomics.h index 6702abd45e1f..45041a68f0df 100644 --- a/Engine/Source/Runtime/Core/Public/Windows/AllowWindowsPlatformAtomics.h +++ b/Engine/Source/Runtime/Core/Public/Windows/AllowWindowsPlatformAtomics.h @@ -12,6 +12,9 @@ #define InterlockedExchange _InterlockedExchange #define InterlockedExchangeAdd _InterlockedExchangeAdd #define InterlockedCompareExchange _InterlockedCompareExchange +#define InterlockedAnd _InterlockedAnd +#define InterlockedOr _InterlockedOr +#define InterlockedXor _InterlockedXor #if PLATFORM_64BITS #define InterlockedCompareExchangePointer _InterlockedCompareExchangePointer diff --git a/Engine/Source/Runtime/Core/Public/Windows/COMPointer.h b/Engine/Source/Runtime/Core/Public/Windows/COMPointer.h index e4179c659f7e..db919a7151cf 100644 --- a/Engine/Source/Runtime/Core/Public/Windows/COMPointer.h +++ b/Engine/Source/Runtime/Core/Public/Windows/COMPointer.h @@ -3,6 +3,7 @@ #pragma once #include +#include "Misc/AssertionMacros.h" /** diff --git a/Engine/Source/Runtime/Core/Public/Windows/HideWindowsPlatformAtomics.h b/Engine/Source/Runtime/Core/Public/Windows/HideWindowsPlatformAtomics.h index 4d9590cd671c..cf725675a921 100644 --- a/Engine/Source/Runtime/Core/Public/Windows/HideWindowsPlatformAtomics.h +++ b/Engine/Source/Runtime/Core/Public/Windows/HideWindowsPlatformAtomics.h @@ -18,4 +18,7 @@ #undef InterlockedCompareExchange64 #undef InterlockedIncrement64 #undef InterlockedDecrement64 +#undef InterlockedAnd +#undef InterlockedOr +#undef InterlockedXor diff --git a/Engine/Source/Runtime/Core/Public/Windows/PostWindowsApi.h b/Engine/Source/Runtime/Core/Public/Windows/PostWindowsApi.h index f649af03f0bb..9988a68e9ac2 100644 --- a/Engine/Source/Runtime/Core/Public/Windows/PostWindowsApi.h +++ b/Engine/Source/Runtime/Core/Public/Windows/PostWindowsApi.h @@ -59,6 +59,9 @@ THIRD_PARTY_INCLUDES_END #undef InterlockedCompareExchange64 #undef InterlockedIncrement64 #undef InterlockedDecrement64 + #undef InterlockedAnd + #undef InterlockedOr + #undef InterlockedXor #endif // Restore any previously defined macros diff --git a/Engine/Source/Runtime/Core/Public/Windows/WIndowsPlatform.h b/Engine/Source/Runtime/Core/Public/Windows/WIndowsPlatform.h index 230e9a5b02ad..45f78044d5e2 100644 --- a/Engine/Source/Runtime/Core/Public/Windows/WIndowsPlatform.h +++ b/Engine/Source/Runtime/Core/Public/Windows/WIndowsPlatform.h @@ -64,6 +64,7 @@ typedef FWindowsPlatformTypes FPlatformTypes; #define PLATFORM_SUPPORTS_VIRTUAL_TEXTURE_STREAMING 1 #define PLATFORM_SUPPORTS_STACK_SYMBOLS 1 +#define PLATFORM_COMPILER_HAS_DECLTYPE_AUTO 1 #define PLATFORM_GLOBAL_LOG_CATEGORY LogWindows @@ -76,15 +77,6 @@ typedef FWindowsPlatformTypes FPlatformTypes; // unknowingly debugging an undefined process. #define PLATFORM_BREAK() (__nop(), __debugbreak()) -#if defined(__INTEL_COMPILER) || _MSC_VER > 1900 - #define PLATFORM_COMPILER_HAS_DECLTYPE_AUTO 1 -#else - // Static analysis causes internal compiler errors with auto-deduced return types, - // but some older VC versions still have return type deduction failures inside the delegate code - // when they are enabled. So we currently only enable them for static analysis builds. - #define PLATFORM_COMPILER_HAS_DECLTYPE_AUTO USING_CODE_ANALYSIS -#endif - // Intrinsics for 128-bit atomics on Windows platform requires Windows 8 or higher (WINVER>=0x0602) // http://msdn.microsoft.com/en-us/library/windows/desktop/hh972640.aspx #define PLATFORM_HAS_128BIT_ATOMICS (!HACK_HEADER_GENERATOR && PLATFORM_64BITS && (WINVER >= 0x602)) diff --git a/Engine/Source/Runtime/Core/Public/Windows/WindowsCriticalSection.h b/Engine/Source/Runtime/Core/Public/Windows/WindowsCriticalSection.h index 73e3ef9e1bf2..08e6893295a9 100644 --- a/Engine/Source/Runtime/Core/Public/Windows/WindowsCriticalSection.h +++ b/Engine/Source/Runtime/Core/Public/Windows/WindowsCriticalSection.h @@ -44,11 +44,7 @@ public: */ FORCEINLINE void Lock() { - // Spin first before entering critical section, causing ring-0 transition and context switch. - if(Windows::TryEnterCriticalSection(&CriticalSection) == 0 ) - { - Windows::EnterCriticalSection(&CriticalSection); - } + Windows::EnterCriticalSection(&CriticalSection); } /** diff --git a/Engine/Source/Runtime/Core/Public/Windows/WindowsPlatformAtomics.h b/Engine/Source/Runtime/Core/Public/Windows/WindowsPlatformAtomics.h index 7130b5428b36..d007f9e33f33 100644 --- a/Engine/Source/Runtime/Core/Public/Windows/WindowsPlatformAtomics.h +++ b/Engine/Source/Runtime/Core/Public/Windows/WindowsPlatformAtomics.h @@ -108,7 +108,7 @@ struct CORE_API FWindowsPlatformAtomics int64 OldValue = *Value; if (_InterlockedCompareExchange64(Value, OldValue + Amount, OldValue) == OldValue) { - return OldValue + Amount; + return OldValue; } } #endif @@ -146,20 +146,16 @@ struct CORE_API FWindowsPlatformAtomics #endif } - static FORCEINLINE void* InterlockedExchangePtr( void** Dest, void* Exchange ) + static FORCEINLINE void* InterlockedExchangePtr( void*volatile* Dest, void* Exchange ) { #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) - if (IsAligned(Dest) == false) + if (IsAligned(Dest, alignof(void*)) == false) { - HandleAtomicsFailure(TEXT("InterlockedExchangePointer requires Dest pointer to be aligned to %d bytes"), sizeof(void*)); + HandleAtomicsFailure(TEXT("InterlockedExchangePointer requires Dest pointer to be aligned to %d bytes"), (int)alignof(void*)); } #endif - #if PLATFORM_64BITS - return (void*)::_InterlockedExchange64((int64*)(Dest), (int64)(Exchange)); - #else - return (void*)::_InterlockedExchange((long*)(Dest), (long)(Exchange)); - #endif + return ::_InterlockedExchangePointer(Dest, Exchange); } static FORCEINLINE int8 InterlockedCompareExchange( volatile int8* Dest, int8 Exchange, int8 Comparand ) @@ -180,15 +176,111 @@ struct CORE_API FWindowsPlatformAtomics static FORCEINLINE int64 InterlockedCompareExchange( volatile int64* Dest, int64 Exchange, int64 Comparand ) { #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) - if (IsAligned(Dest) == false) + if (IsAligned(Dest, alignof(int64)) == false) { - HandleAtomicsFailure(TEXT("InterlockedCompareExchangePointer requires Dest pointer to be aligned to %d bytes"), sizeof(void*)); + HandleAtomicsFailure(TEXT("InterlockedCompareExchange int64 requires Dest pointer to be aligned to %d bytes"), (int)alignof(int64)); } #endif return (int64)::_InterlockedCompareExchange64(Dest, Exchange, Comparand); } + static FORCEINLINE int8 InterlockedAnd(volatile int8* Value, const int8 AndValue) + { + return (int8)::_InterlockedAnd8((volatile char*)Value, (char)AndValue); + } + + static FORCEINLINE int16 InterlockedAnd(volatile int16* Value, const int16 AndValue) + { + return (int16)::_InterlockedAnd16((volatile short*)Value, (short)AndValue); + } + + static FORCEINLINE int32 InterlockedAnd(volatile int32* Value, const int32 AndValue) + { + return (int32)::_InterlockedAnd((volatile long*)Value, (long)AndValue); + } + + static FORCEINLINE int64 InterlockedAnd(volatile int64* Value, const int64 AndValue) + { + #if PLATFORM_64BITS + return (int64)::_InterlockedAnd64((volatile long long*)Value, (long long)AndValue); + #else + // No explicit instruction for 64-bit atomic and on 32-bit processors; has to be implemented in terms of CMPXCHG8B + for (;;) + { + const int64 OldValue = *Value; + if (_InterlockedCompareExchange64(Value, OldValue & AndValue, OldValue) == OldValue) + { + return OldValue; + } + } + #endif + } + + static FORCEINLINE int8 InterlockedOr(volatile int8* Value, const int8 OrValue) + { + return (int8)::_InterlockedOr8((volatile char*)Value, (char)OrValue); + } + + static FORCEINLINE int16 InterlockedOr(volatile int16* Value, const int16 OrValue) + { + return (int16)::_InterlockedOr16((volatile short*)Value, (short)OrValue); + } + + static FORCEINLINE int32 InterlockedOr(volatile int32* Value, const int32 OrValue) + { + return (int32)::_InterlockedOr((volatile long*)Value, (long)OrValue); + } + + static FORCEINLINE int64 InterlockedOr(volatile int64* Value, const int64 OrValue) + { + #if PLATFORM_64BITS + return (int64)::_InterlockedOr64((volatile long long*)Value, (long long)OrValue); + #else + // No explicit instruction for 64-bit atomic or on 32-bit processors; has to be implemented in terms of CMPXCHG8B + for (;;) + { + const int64 OldValue = *Value; + if (_InterlockedCompareExchange64(Value, OldValue | OrValue, OldValue) == OldValue) + { + return OldValue; + } + } + #endif + } + + static FORCEINLINE int8 InterlockedXor(volatile int8* Value, const int8 XorValue) + { + return (int8)::_InterlockedXor8((volatile char*)Value, (char)XorValue); + } + + static FORCEINLINE int16 InterlockedXor(volatile int16* Value, const int16 XorValue) + { + return (int16)::_InterlockedXor16((volatile short*)Value, (short)XorValue); + } + + static FORCEINLINE int32 InterlockedXor(volatile int32* Value, const int32 XorValue) + { + return (int32)::_InterlockedXor((volatile long*)Value, (int32)XorValue); + } + + static FORCEINLINE int64 InterlockedXor(volatile int64* Value, const int64 XorValue) + { + #if PLATFORM_64BITS + return (int64)::_InterlockedXor64((volatile long long*)Value, (long long)XorValue); + #else + // No explicit instruction for 64-bit atomic xor on 32-bit processors; has to be implemented in terms of CMPXCHG8B + for (;;) + { + const int64 OldValue = *Value; + if (_InterlockedCompareExchange64(Value, OldValue ^ XorValue, OldValue) == OldValue) + { + return OldValue; + } + } + #endif + } + static FORCEINLINE int8 AtomicRead(volatile const int8* Src) { return InterlockedCompareExchange((int8*)Src, 0, 0); @@ -299,11 +391,11 @@ struct CORE_API FWindowsPlatformAtomics #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) if (IsAligned(Dest,16) == false) { - HandleAtomicsFailure(TEXT("InterlockedCompareExchangePointer requires Dest pointer to be aligned to 16 bytes") ); + HandleAtomicsFailure(TEXT("InterlockedCompareExchange128 requires Dest pointer to be aligned to 16 bytes") ); } if (IsAligned(Comparand,16) == false) { - HandleAtomicsFailure(TEXT("InterlockedCompareExchangePointer requires Comparand pointer to be aligned to 16 bytes") ); + HandleAtomicsFailure(TEXT("InterlockedCompareExchange128 requires Comparand pointer to be aligned to 16 bytes") ); } #endif @@ -323,20 +415,16 @@ struct CORE_API FWindowsPlatformAtomics #endif // PLATFORM_HAS_128BIT_ATOMICS - static FORCEINLINE void* InterlockedCompareExchangePointer( void** Dest, void* Exchange, void* Comparand ) + static FORCEINLINE void* InterlockedCompareExchangePointer( void*volatile* Dest, void* Exchange, void* Comparand ) { #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) - if (IsAligned(Dest) == false) + if (IsAligned(Dest, alignof(void*)) == false) { - HandleAtomicsFailure(TEXT("InterlockedCompareExchangePointer requires Dest pointer to be aligned to %d bytes"), sizeof(void*)); + HandleAtomicsFailure(TEXT("InterlockedCompareExchangePointer requires Dest pointer to be aligned to %d bytes"), (int)alignof(void*)); } #endif - #if PLATFORM_64BITS - return (void*)::_InterlockedCompareExchange64((int64*)Dest, (int64)Exchange, (int64)Comparand); - #else - return (void*)::_InterlockedCompareExchange((long*)Dest, (long)Exchange, (long)Comparand); - #endif + return ::_InterlockedCompareExchangePointer(Dest, Exchange, Comparand); } /** diff --git a/Engine/Source/Runtime/Core/Public/Windows/WindowsPlatformCompilerSetup.h b/Engine/Source/Runtime/Core/Public/Windows/WindowsPlatformCompilerSetup.h index 93e19c96bbfe..cb936ba67d78 100644 --- a/Engine/Source/Runtime/Core/Public/Windows/WindowsPlatformCompilerSetup.h +++ b/Engine/Source/Runtime/Core/Public/Windows/WindowsPlatformCompilerSetup.h @@ -236,6 +236,7 @@ static_assert(_MSC_VER != 1914 && _MSC_VER != 1915, "Visual Studio 2017 versions #pragma warning(default: 4928) // illegal copy-initialization; more than one user-defined conversion has been implicitly applied https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-1-c4928 #pragma warning(default: 4931) // we are assuming the type library was built for number-bit pointers https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4931 #pragma warning(default: 4946) // reinterpret_cast used between related classes: 'class1' and 'class2' https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-1-c4946 +#pragma warning(default: 5038) // data member 'A::y' will be initialized after data member 'A::x' https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/c5038 #pragma warning(default: 4996) diff --git a/Engine/Source/Runtime/Core/Public/Windows/WindowsPlatformString.h b/Engine/Source/Runtime/Core/Public/Windows/WindowsPlatformString.h index bda293ee9682..609c562da986 100644 --- a/Engine/Source/Runtime/Core/Public/Windows/WindowsPlatformString.h +++ b/Engine/Source/Runtime/Core/Public/Windows/WindowsPlatformString.h @@ -25,3 +25,76 @@ struct FWindowsPlatformString typedef FWindowsPlatformString FPlatformString; + +// Format specifiers to be able to print values of these types correctly, for example when using UE_LOG. +#if PLATFORM_64BITS +// SIZE_T format specifier +#define SIZE_T_FMT "I64u" +// SIZE_T format specifier for lowercase hexadecimal output +#define SIZE_T_x_FMT "I64x" +// SIZE_T format specifier for uppercase hexadecimal output +#define SIZE_T_X_FMT "I64X" + +// SSIZE_T format specifier +#define SSIZE_T_FMT "I64d" +// SSIZE_T format specifier for lowercase hexadecimal output +#define SSIZE_T_x_FMT "I64x" +// SSIZE_T format specifier for uppercase hexadecimal output +#define SSIZE_T_X_FMT "I64X" + +// PTRINT format specifier for decimal output +#define PTRINT_FMT "lld" +// PTRINT format specifier for lowercase hexadecimal output +#define PTRINT_x_FMT "llx" +// PTRINT format specifier for uppercase hexadecimal output +#define PTRINT_X_FMT "llX" + +// UPTRINT format specifier for decimal output +#define UPTRINT_FMT "llu" +// UPTRINT format specifier for lowercase hexadecimal output +#define UPTRINT_x_FMT "llx" +// UPTRINT format specifier for uppercase hexadecimal output +#define UPTRINT_X_FMT "llX" +#else +// SIZE_T format specifier +#define SIZE_T_FMT "lu" +// SIZE_T format specifier for lowercase hexadecimal output +#define SIZE_T_x_FMT "lx" +// SIZE_T format specifier for uppercase hexadecimal output +#define SIZE_T_X_FMT "lX" + +// SSIZE_T format specifier +#define SSIZE_T_FMT "ld" +// SSIZE_T format specifier for lowercase hexadecimal output +#define SSIZE_T_x_FMT "lx" +// SSIZE_T format specifier for uppercase hexadecimal output +#define SSIZE_T_X_FMT "lX" + +// PTRINT format specifier for decimal output +#define PTRINT_FMT "d" +// PTRINT format specifier for lowercase hexadecimal output +#define PTRINT_x_FMT "x" +// PTRINT format specifier for uppercase hexadecimal output +#define PTRINT_X_FMT "X" + +// UPTRINT format specifier for decimal output +#define UPTRINT_FMT "u" +// UPTRINT format specifier for lowercase hexadecimal output +#define UPTRINT_x_FMT "x" +// UPTRINT format specifier for uppercase hexadecimal output +#define UPTRINT_X_FMT "X" +#endif + +// int64 format specifier for decimal output +#define INT64_FMT "lld" +// int64 format specifier for lowercase hexadecimal output +#define INT64_x_FMT "llx" +// int64 format specifier for uppercase hexadecimal output +#define INT64_X_FMT "llX" + +// uint64 format specifier for decimal output +#define UINT64_FMT "llu" +// uint64 format specifier for lowercase hexadecimal output +#define UINT64_x_FMT "llx" +// uint64 format specifier for uppercase hexadecimal output +#define UINT64_X_FMT "llX" diff --git a/Engine/Source/Runtime/CoreUObject/CoreUObject.Build.cs b/Engine/Source/Runtime/CoreUObject/CoreUObject.Build.cs index 01bd817ec612..b9bc7b3979e0 100644 --- a/Engine/Source/Runtime/CoreUObject/CoreUObject.Build.cs +++ b/Engine/Source/Runtime/CoreUObject/CoreUObject.Build.cs @@ -20,6 +20,7 @@ public class CoreUObject : ModuleRules ); PublicDependencyModuleNames.Add("Core"); + PublicDependencyModuleNames.Add("TraceLog"); PrivateDependencyModuleNames.Add("Projects"); PrivateDependencyModuleNames.Add("Json"); diff --git a/Engine/Source/Runtime/CoreUObject/Private/Blueprint/BlueprintSupport.cpp b/Engine/Source/Runtime/CoreUObject/Private/Blueprint/BlueprintSupport.cpp index cae025965b2e..89a8b86c34a9 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Blueprint/BlueprintSupport.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/Blueprint/BlueprintSupport.cpp @@ -2068,6 +2068,7 @@ void FLinkerLoad::ResolveDeferredExports(UClass* LoadClass) if (ensure(PlaceholderExport)) { // replace the placeholder with the proper object instance + PlaceholderExport->SetLinker(nullptr, INDEX_NONE); Export.ResetObject(); UObject* ExportObj = CreateExport(ExportIndex); diff --git a/Engine/Source/Runtime/CoreUObject/Private/Internationalization/PackageLocalizationManager.cpp b/Engine/Source/Runtime/CoreUObject/Private/Internationalization/PackageLocalizationManager.cpp index bb9965a9cce8..a36501e294d6 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Internationalization/PackageLocalizationManager.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/Internationalization/PackageLocalizationManager.cpp @@ -145,6 +145,14 @@ FName FPackageLocalizationManager::FindLocalizedPackageNameNoCache(const FName I return NAME_None; } +void FPackageLocalizationManager::ConditionalUpdateCache() +{ + if (ActiveCache.IsValid()) + { + ActiveCache->ConditionalUpdateCache(); + } +} + FPackageLocalizationManager& FPackageLocalizationManager::Get() { static FPackageLocalizationManager PackageLocalizationManager; diff --git a/Engine/Source/Runtime/CoreUObject/Private/Misc/ExclusiveLoadPackageTimeTracker.cpp b/Engine/Source/Runtime/CoreUObject/Private/Misc/ExclusiveLoadPackageTimeTracker.cpp index 8ff5b46a88ea..57322c2889c1 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Misc/ExclusiveLoadPackageTimeTracker.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/Misc/ExclusiveLoadPackageTimeTracker.cpp @@ -219,7 +219,7 @@ void FExclusiveLoadPackageTimeTracker::InternalDumpReport(const TArray& ReportAr->Logf(TEXT("Dumping all loaded assets by exclusive load time:")); if (bAlphaSort) { - SortedLoadTimes.Sort([](const FLoadTime& A, const FLoadTime& B) { return A.TimeName < B.TimeName; }); + SortedLoadTimes.Sort([](const FLoadTime& A, const FLoadTime& B) { return A.TimeName.LexicalLess(B.TimeName); }); } else { @@ -255,7 +255,7 @@ void FExclusiveLoadPackageTimeTracker::InternalDumpReport(const TArray& ReportAr->Logf(TEXT("Dumping all loaded assets by inclusive load time:")); if (bAlphaSort) { - SortedLoadTimes.Sort([](const FLoadTime& A, const FLoadTime& B) { return A.TimeName < B.TimeName; }); + SortedLoadTimes.Sort([](const FLoadTime& A, const FLoadTime& B) { return A.TimeName.LexicalLess(B.TimeName); }); } else { diff --git a/Engine/Source/Runtime/CoreUObject/Private/Misc/PackageName.cpp b/Engine/Source/Runtime/CoreUObject/Private/Misc/PackageName.cpp index 53640b41d1d5..eb7b46c92599 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Misc/PackageName.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/Misc/PackageName.cpp @@ -24,8 +24,8 @@ DEFINE_LOG_CATEGORY_STATIC(LogPackageName, Log, All); FString FPackageName::AssetPackageExtension = TEXT(".uasset"); FString FPackageName::MapPackageExtension = TEXT(".umap"); -FString FPackageName::TextAssetPackageExtension = TEXT(".utextasset"); -FString FPackageName::TextMapPackageExtension = TEXT(".utextmap"); +FString FPackageName::TextAssetPackageExtension = TEXT(".utxt"); +FString FPackageName::TextMapPackageExtension = TEXT(".utxtmap"); /** Event that is triggered when a new content path is mounted */ FPackageName::FOnContentPathMountedEvent FPackageName::OnContentPathMountedEvent; diff --git a/Engine/Source/Runtime/CoreUObject/Private/Serialization/ArchiveHasReferences.cpp b/Engine/Source/Runtime/CoreUObject/Private/Serialization/ArchiveHasReferences.cpp index 247bbd6f72a4..3e71fefb10fe 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Serialization/ArchiveHasReferences.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/Serialization/ArchiveHasReferences.cpp @@ -12,6 +12,7 @@ FArchiveHasReferences::FArchiveHasReferences(UObject* InTarget, const TSetSerialize(*this); class FArchiveProxyCollector : public FReferenceCollector diff --git a/Engine/Source/Runtime/CoreUObject/Private/Serialization/ArchiveStackTrace.cpp b/Engine/Source/Runtime/CoreUObject/Private/Serialization/ArchiveStackTrace.cpp index b1d8c4702505..3cb0b7b909f9 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Serialization/ArchiveStackTrace.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/Serialization/ArchiveStackTrace.cpp @@ -19,6 +19,7 @@ #include "UObject/LinkerManager.h" #include "Misc/PackageName.h" #include "Templates/UniquePtr.h" +#include "UObject/UObjectGlobals.h" DEFINE_LOG_CATEGORY_STATIC(LogArchiveDiff, Log, All); @@ -968,6 +969,11 @@ static inline FString GetTableKey(const FLinkerLoad* Linker, const FName& Name) return *Name.ToString(); } +static inline FString GetTableKey(const FLinkerLoad* Linker, FNameEntryId Id) +{ + return FName::GetEntry(Id)->GetPlainNameString(); +} + static inline FString GetTableKeyForIndex(const FLinkerLoad* Linker, FPackageIndex Index) { if (Index.IsNull()) @@ -991,11 +997,21 @@ bool CompareTableItem(FLinkerLoad* SourceLinker, FLinkerLoad* DestLinker, const return SourceName == DestName; } +bool CompareTableItem(FLinkerLoad* SourceLinker, FLinkerLoad* DestLinker, FNameEntryId SourceName, FNameEntryId DestName) +{ + return SourceName == DestName; +} + FString ConvertItemToText(const FName& Name, FLinkerLoad* Linker) { return Name.ToString(); } +FString ConvertItemToText(FNameEntryId Id, FLinkerLoad* Linker) +{ + return FName::GetEntry(Id)->GetPlainNameString(); +} + bool CompareTableItem(FLinkerLoad* SourceLinker, FLinkerLoad* DestLinker, const FObjectImport& SourceImport, const FObjectImport& DestImport) { if (SourceImport.ObjectName != DestImport.ObjectName || @@ -1287,8 +1303,6 @@ static void DumpTableDifferences( #endif // !NO_LOGGING } -extern int32 GAllowCookedDataInEditorBuilds; - void FArchiveStackTrace::DumpPackageHeaderDiffs(const FPackageData& SourcePackage, const FPackageData& DestPackage, const FString& AssetFilename, const int32 MaxDiffsToLog) { #if !NO_LOGGING @@ -1321,7 +1335,7 @@ void FArchiveStackTrace::DumpPackageHeaderDiffs(const FPackageData& SourcePackag { if (SourceLinker->NameMap != DestLinker->NameMap) { - DumpTableDifferences(SourceLinker, DestLinker, SourceLinker->NameMap, DestLinker->NameMap, *AssetFilename, TEXT("Name"), MaxDiffsToLog); + DumpTableDifferences(SourceLinker, DestLinker, SourceLinker->NameMap, DestLinker->NameMap, *AssetFilename, TEXT("Name"), MaxDiffsToLog); } if (!IsImportMapIdentical(SourceLinker, DestLinker)) diff --git a/Engine/Source/Runtime/CoreUObject/Private/Serialization/ArchiveUObjectFromStructuredArchive.cpp b/Engine/Source/Runtime/CoreUObject/Private/Serialization/ArchiveUObjectFromStructuredArchive.cpp index f8ed3864c40d..8f45acbe55c1 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Serialization/ArchiveUObjectFromStructuredArchive.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/Serialization/ArchiveUObjectFromStructuredArchive.cpp @@ -2,6 +2,8 @@ #include "Serialization/ArchiveUObjectFromStructuredArchive.h" +#if WITH_TEXT_ARCHIVE_SUPPORT + FArchiveUObjectFromStructuredArchive::FArchiveUObjectFromStructuredArchive(FStructuredArchive::FSlot Slot) : FArchiveFromStructuredArchive(Slot) , bPendingSerialize(true) @@ -182,4 +184,6 @@ void FArchiveUObjectFromStructuredArchive::SerializeInternal(FStructuredArchive: bPendingSerialize = true; } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/Engine/Source/Runtime/CoreUObject/Private/Serialization/AsyncLoading.cpp b/Engine/Source/Runtime/CoreUObject/Private/Serialization/AsyncLoading.cpp index a5397abffc34..5218a7f04977 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Serialization/AsyncLoading.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/Serialization/AsyncLoading.cpp @@ -41,6 +41,8 @@ #include "HAL/LowLevelMemTracker.h" #include "ProfilingDebugging/CsvProfiler.h" #include "UObject/GCScopeLock.h" +#include "ProfilingDebugging/MiscTrace.h" +#include "Serialization/LoadTimeTracePrivate.h" #define FIND_MEMORY_STOMPS (1 && (PLATFORM_WINDOWS || PLATFORM_UNIX) && !WITH_EDITORONLY_DATA) @@ -68,7 +70,7 @@ DECLARE_CYCLE_STAT(TEXT("CreateImports AsyncPackage"),STAT_FAsyncPackage_CreateI DECLARE_CYCLE_STAT(TEXT("CreateMetaData AsyncPackage"),STAT_FAsyncPackage_CreateMetaData,STATGROUP_AsyncLoad); DECLARE_CYCLE_STAT(TEXT("CreateExports AsyncPackage"),STAT_FAsyncPackage_CreateExports,STATGROUP_AsyncLoad); DECLARE_CYCLE_STAT(TEXT("FreeReferencedImports AsyncPackage"), STAT_FAsyncPackage_FreeReferencedImports, STATGROUP_AsyncLoad); -DECLARE_CYCLE_STAT(TEXT("Precache ArchiveAsync"), STAT_FArchiveAsync_Precache, STATGROUP_AsyncLoad); +DECLARE_CYCLE_STAT(TEXT("Precache AsyncArchive"), STAT_FAsyncArchive_Precache, STATGROUP_AsyncLoad); DECLARE_CYCLE_STAT(TEXT("PreLoadObjects AsyncPackage"),STAT_FAsyncPackage_PreLoadObjects,STATGROUP_AsyncLoad); DECLARE_CYCLE_STAT(TEXT("ExternalReadDependencies AsyncPackage"),STAT_FAsyncPackage_ExternalReadDependencies,STATGROUP_AsyncLoad); DECLARE_CYCLE_STAT(TEXT("PostLoadObjects AsyncPackage"),STAT_FAsyncPackage_PostLoadObjects,STATGROUP_AsyncLoad); @@ -296,7 +298,7 @@ FORCEINLINE bool FAsyncPackage::IsTimeLimitExceeded() } DEFINE_LOG_CATEGORY_STATIC(LogAsyncArchive, Display, All); -DECLARE_MEMORY_STAT(TEXT("FArchiveAsync2 Buffers"), STAT_FArchiveAsync2Mem, STATGROUP_Memory); +DECLARE_MEMORY_STAT(TEXT("FAsyncArchive Buffers"), STAT_FAsyncArchiveMem, STATGROUP_Memory); //#define ASYNC_WATCH_FILE "SM_Boots_Wings.uasset" #define TRACK_SERIALIZE (0) @@ -305,7 +307,7 @@ DECLARE_MEMORY_STAT(TEXT("FArchiveAsync2 Buffers"), STAT_FArchiveAsync2Mem, STAT -FORCEINLINE void FArchiveAsync2::LogItem(const TCHAR* Item, int64 Offset, int64 Size, double StartTime) +FORCEINLINE void FAsyncArchive::LogItem(const TCHAR* Item, int64 Offset, int64 Size, double StartTime) { if (UE_LOG_ACTIVE(LogAsyncArchive, Verbose) #if defined(ASYNC_WATCH_FILE) @@ -1788,7 +1790,11 @@ void FAsyncLoadingThread::QueueEvent_CreateLinker(FAsyncPackage* Package, int32 check(Package); Package->AddNode(EEventLoadNode::Package_LoadSummary); FWeakAsyncPackagePtr WeakPtr(Package); - EventQueue.AddAsyncEvent(Package->GetPriority(), GRandomizeLoadOrder ? GetRandomSerialNumber() : Package->SerialNumber, EventSystemPriority, + + TAsyncLoadPriority UserPriority = Package->GetPriority(); + int32 PackageSerialNumber = GRandomizeLoadOrder ? GetRandomSerialNumber() : Package->SerialNumber; + TRACE_LOADTIME_QUEUE_EVENT(Package, LoadTimeProfilerPackageEventType_CreateLinker, UserPriority, PackageSerialNumber, EventSystemPriority); + EventQueue.AddAsyncEvent(UserPriority, PackageSerialNumber, EventSystemPriority, TFunction( [WeakPtr, this](FAsyncLoadEventArgs& Args) { @@ -1796,6 +1802,7 @@ void FAsyncLoadingThread::QueueEvent_CreateLinker(FAsyncPackage* Package, int32 check(Pkg); if (Pkg) { + TRACE_LOADTIME_ASYNC_PACKAGE_SCOPE(Pkg, LoadTimeProfilerPackageEventType_CreateLinker); Pkg->SetTimeLimit(Args, TEXT("Create Linker")); Pkg->Event_CreateLinker(); Args.OutLastObjectWorkWasPerformedOn = Pkg->GetLinkerRoot(); @@ -1850,7 +1857,10 @@ void FAsyncLoadingThread::QueueEvent_FinishLinker(FWeakAsyncPackagePtr WeakPtr, FAsyncPackage* Pkg = GetPackage(WeakPtr); if (Pkg) { - EventQueue.AddAsyncEvent(Pkg->GetPriority(), GRandomizeLoadOrder ? GetRandomSerialNumber() : Pkg->SerialNumber, EventSystemPriority, + TAsyncLoadPriority UserPriority = Pkg->GetPriority(); + int32 PackageSerialNumber = GRandomizeLoadOrder ? GetRandomSerialNumber() : Pkg->SerialNumber; + TRACE_LOADTIME_QUEUE_EVENT(Pkg, LoadTimeProfilerPackageEventType_FinishLinker, UserPriority, PackageSerialNumber, EventSystemPriority) + EventQueue.AddAsyncEvent(UserPriority, PackageSerialNumber, EventSystemPriority, TFunction( [WeakPtr, this](FAsyncLoadEventArgs& Args) { @@ -1858,6 +1868,7 @@ void FAsyncLoadingThread::QueueEvent_FinishLinker(FWeakAsyncPackagePtr WeakPtr, check(PkgInner); if (PkgInner) { + TRACE_LOADTIME_ASYNC_PACKAGE_SCOPE(PkgInner, LoadTimeProfilerPackageEventType_FinishLinker); PkgInner->SetTimeLimit(Args, TEXT("Finish Linker")); PkgInner->Event_FinishLinker(); } @@ -1994,13 +2005,18 @@ void FAsyncLoadingThread::QueueEvent_StartImportPackages(FAsyncPackage* Package, { check(Package); FWeakAsyncPackagePtr WeakPtr(Package); - EventQueue.AddAsyncEvent(Package->GetPriority(), GRandomizeLoadOrder ? GetRandomSerialNumber() : Package->SerialNumber, EventSystemPriority, + + TAsyncLoadPriority UserPriority = Package->GetPriority(); + int32 PackageSerialNumber = GRandomizeLoadOrder ? GetRandomSerialNumber() : Package->SerialNumber; + TRACE_LOADTIME_QUEUE_EVENT(Package, LoadTimeProfilerPackageEventType_StartImportPackages, UserPriority, PackageSerialNumber, EventSystemPriority); + EventQueue.AddAsyncEvent(UserPriority, PackageSerialNumber, EventSystemPriority, TFunction( [WeakPtr, this](FAsyncLoadEventArgs& Args) { FAsyncPackage* Pkg = GetPackage(WeakPtr); if (Pkg) { + TRACE_LOADTIME_ASYNC_PACKAGE_SCOPE(Pkg, LoadTimeProfilerPackageEventType_StartImportPackages); Pkg->SetTimeLimit(Args, TEXT("Start Import Packages")); Pkg->Event_StartImportPackages(); } @@ -2161,6 +2177,7 @@ static const FName PrestreamPackageClassNameLoad = FName("PrestreamPackage"); EAsyncPackageState::Type FAsyncPackage::LoadImports_Event() { SCOPE_CYCLE_COUNTER(STAT_FAsyncPackage_LoadImports); + SCOPED_LOADTIMER(LoadImports_Event); LastObjectWorkWasPerformedOn = LinkerRoot; LastTypeOfWorkPerformed = TEXT("loading imports event"); @@ -2332,13 +2349,17 @@ void FAsyncLoadingThread::QueueEvent_SetupImports(FAsyncPackage* Package, int32 Package->AsyncPackageLoadingState = EAsyncPackageLoadingState::SetupImports; check(Package); FWeakAsyncPackagePtr WeakPtr(Package); - EventQueue.AddAsyncEvent(Package->GetPriority(), GRandomizeLoadOrder ? GetRandomSerialNumber() : Package->SerialNumber, EventSystemPriority, + TAsyncLoadPriority UserPriority = Package->GetPriority(); + int32 PackageSerialNumber = GRandomizeLoadOrder ? GetRandomSerialNumber() : Package->SerialNumber; + TRACE_LOADTIME_QUEUE_EVENT(Package, LoadTimeProfilerPackageEventType_SetupImports, UserPriority, PackageSerialNumber, EventSystemPriority); + EventQueue.AddAsyncEvent(UserPriority, PackageSerialNumber, EventSystemPriority, TFunction( [WeakPtr, this](FAsyncLoadEventArgs& Args) { FAsyncPackage* Pkg = GetPackage(WeakPtr); if (Pkg) { + TRACE_LOADTIME_ASYNC_PACKAGE_SCOPE(Pkg, LoadTimeProfilerPackageEventType_SetupImports); Pkg->SetTimeLimit(Args, TEXT("Setup Imports")); Pkg->Event_SetupImports(); } @@ -2676,10 +2697,10 @@ EAsyncPackageState::Type FAsyncPackage::SetupImports_Event() } #if 0 - if (bAnyImportArcsAdded && Linker->GetFArchiveAsync2Loader()) + if (bAnyImportArcsAdded && Linker->GetAsyncLoader()) { // we are waiting for imports, so drop our precache requests - Linker->GetFArchiveAsync2Loader()->FlushCache(); + Linker->GetAsyncLoader()->FlushCache(); } #endif return ImportIndex == Linker->ImportMap.Num() ? EAsyncPackageState::Complete : EAsyncPackageState::TimeOut; @@ -2691,13 +2712,17 @@ void FAsyncLoadingThread::QueueEvent_SetupExports(FAsyncPackage* Package, int32 check(Package->AsyncPackageLoadingState == EAsyncPackageLoadingState::SetupExports); check(Package); FWeakAsyncPackagePtr WeakPtr(Package); - EventQueue.AddAsyncEvent(Package->GetPriority(), GRandomizeLoadOrder ? GetRandomSerialNumber() : Package->SerialNumber, EventSystemPriority, + TAsyncLoadPriority UserPriority = Package->GetPriority(); + int32 PackageSerialNumber = GRandomizeLoadOrder ? GetRandomSerialNumber() : Package->SerialNumber; + TRACE_LOADTIME_QUEUE_EVENT(Package, LoadTimeProfilerPackageEventType_SetupExports, UserPriority, PackageSerialNumber, EventSystemPriority); + EventQueue.AddAsyncEvent(UserPriority, PackageSerialNumber, EventSystemPriority, TFunction( [WeakPtr, this](FAsyncLoadEventArgs& Args) { FAsyncPackage* Pkg = GetPackage(WeakPtr); if (Pkg) { + TRACE_LOADTIME_ASYNC_PACKAGE_SCOPE(Pkg, LoadTimeProfilerPackageEventType_SetupExports); Pkg->SetTimeLimit(Args, TEXT("Setup Exports")); Pkg->Event_SetupExports(); } @@ -2725,6 +2750,9 @@ void FAsyncLoadingThread::QueueEvent_ProcessImportsAndExports(FAsyncPackage* Pac check(Package->AsyncPackageLoadingState == EAsyncPackageLoadingState::ProcessNewImportsAndExports); check(Package); FWeakAsyncPackagePtr WeakPtr(Package); + TAsyncLoadPriority UserPriority = Package->GetPriority(); + int32 PackageSerialNumber = GRandomizeLoadOrder ? GetRandomSerialNumber() : Package->SerialNumber; + TRACE_LOADTIME_QUEUE_EVENT(Package, LoadTimeProfilerPackageEventType_ProcessImportsAndExports, UserPriority, PackageSerialNumber, EventSystemPriority); EventQueue.AddAsyncEvent(Package->GetPriority(), GRandomizeLoadOrder ? GetRandomSerialNumber() : Package->SerialNumber, EventSystemPriority, TFunction( [WeakPtr, this](FAsyncLoadEventArgs& Args) @@ -2732,6 +2760,7 @@ void FAsyncLoadingThread::QueueEvent_ProcessImportsAndExports(FAsyncPackage* Pac FAsyncPackage* Pkg = GetPackage(WeakPtr); if (Pkg) { + TRACE_LOADTIME_ASYNC_PACKAGE_SCOPE(Pkg, LoadTimeProfilerPackageEventType_ProcessImportsAndExports); Pkg->SetTimeLimit(Args, TEXT("ProcessImportsAndExports")); Pkg->Event_ProcessImportsAndExports(); } @@ -2744,13 +2773,17 @@ void FAsyncLoadingThread::QueueEvent_ProcessPostloadWait(FAsyncPackage* Package, check(Package->AsyncPackageLoadingState == EAsyncPackageLoadingState::WaitingForPostLoad); check(Package); FWeakAsyncPackagePtr WeakPtr(Package); - EventQueue.AddAsyncEvent(Package->GetPriority(), GRandomizeLoadOrder ? GetRandomSerialNumber() : Package->SerialNumber, EventSystemPriority, + TAsyncLoadPriority UserPriority = Package->GetPriority(); + int32 PackageSerialNumber = GRandomizeLoadOrder ? GetRandomSerialNumber() : Package->SerialNumber; + TRACE_LOADTIME_QUEUE_EVENT(Package, LoadTimeProfilerPackageEventType_PostLoadWait, UserPriority, PackageSerialNumber, EventSystemPriority); + EventQueue.AddAsyncEvent(UserPriority, PackageSerialNumber, EventSystemPriority, TFunction( [WeakPtr, this](FAsyncLoadEventArgs& Args) { FAsyncPackage* Pkg = GetPackage(WeakPtr); if (Pkg) { + TRACE_LOADTIME_ASYNC_PACKAGE_SCOPE(Pkg, LoadTimeProfilerPackageEventType_PostLoadWait); Pkg->SetTimeLimit(Args, TEXT("Process Process Postload Wait")); Pkg->Event_ProcessPostloadWait(); } @@ -2763,13 +2796,17 @@ void FAsyncLoadingThread::QueueEvent_ExportsDone(FAsyncPackage* Package, int32 E check(Package->AsyncPackageLoadingState == EAsyncPackageLoadingState::ProcessNewImportsAndExports); check(Package); FWeakAsyncPackagePtr WeakPtr(Package); - EventQueue.AddAsyncEvent(Package->GetPriority(), GRandomizeLoadOrder ? GetRandomSerialNumber() : Package->SerialNumber, EventSystemPriority, + TAsyncLoadPriority UserPriority = Package->GetPriority(); + int32 PackageSerialNumber = GRandomizeLoadOrder ? GetRandomSerialNumber() : Package->SerialNumber; + TRACE_LOADTIME_QUEUE_EVENT(Package, LoadTimeProfilerPackageEventType_ExportsDone, UserPriority, PackageSerialNumber, EventSystemPriority); + EventQueue.AddAsyncEvent(UserPriority, PackageSerialNumber, EventSystemPriority, TFunction( [WeakPtr, this](FAsyncLoadEventArgs& Args) { FAsyncPackage* Pkg = GetPackage(WeakPtr); if (Pkg) { + TRACE_LOADTIME_ASYNC_PACKAGE_SCOPE(Pkg, LoadTimeProfilerPackageEventType_ExportsDone); Pkg->SetTimeLimit(Args, TEXT("Exports Done")); Pkg->Event_ExportsDone(); } @@ -2782,6 +2819,9 @@ void FAsyncLoadingThread::QueueEvent_StartPostLoad(FAsyncPackage* Package, int32 check(Package->AsyncPackageLoadingState == EAsyncPackageLoadingState::ReadyForPostLoad); check(Package); FWeakAsyncPackagePtr WeakPtr(Package); + TAsyncLoadPriority UserPriority = Package->GetPriority(); + int32 PackageSerialNumber = GRandomizeLoadOrder ? GetRandomSerialNumber() : Package->SerialNumber; + TRACE_LOADTIME_QUEUE_EVENT(Package, LoadTimeProfilerPackageEventType_StartPostLoad, UserPriority, PackageSerialNumber, EventSystemPriority); EventQueue.AddAsyncEvent(Package->GetPriority(), GRandomizeLoadOrder ? GetRandomSerialNumber() : Package->SerialNumber, EventSystemPriority, TFunction( [WeakPtr, this](FAsyncLoadEventArgs& Args) @@ -2789,6 +2829,7 @@ void FAsyncLoadingThread::QueueEvent_StartPostLoad(FAsyncPackage* Package, int32 FAsyncPackage* Pkg = GetPackage(WeakPtr); if (Pkg) { + TRACE_LOADTIME_ASYNC_PACKAGE_SCOPE(Pkg, LoadTimeProfilerPackageEventType_StartPostLoad); Pkg->SetTimeLimit(Args, TEXT("Start Post Load")); Pkg->Event_StartPostload(); } @@ -2847,7 +2888,7 @@ EAsyncPackageState::Type FAsyncPackage::SetupExports_Event() FGCScopeGuard GCGuard; FCheckedWeakAsyncPackagePtr WeakThis(this); - Linker->GetFArchiveAsync2Loader()->LogItem(TEXT("SetupExports_Event")); + Linker->GetAsyncLoader()->LogItem(TEXT("SetupExports_Event")); LastTypeOfWorkPerformed = TEXT("SetupExports_Event"); LastObjectWorkWasPerformedOn = LinkerRoot; @@ -2963,9 +3004,9 @@ void FAsyncPackage::LinkImport(int32 LocalImportIndex) if (!Import.XObject && !Import.bImportFailed) { FScopedAddObjectreference OnExitAddReference(*this, Import.XObject); - if (Linker->GetFArchiveAsync2Loader()) + if (Linker->GetAsyncLoader()) { - Linker->GetFArchiveAsync2Loader()->LogItem(TEXT("LinkImport")); + Linker->GetAsyncLoader()->LogItem(TEXT("LinkImport")); } if (Import.SourceLinker) { @@ -3207,6 +3248,8 @@ void FAsyncPackage::EventDrivenCreateExport(int32 LocalExportIndex) SCOPED_LOADTIMER(Package_CreateExports); FObjectExport& Export = Linker->ExportMap[LocalExportIndex]; + TRACE_LOADTIME_CREATE_EXPORT_SCOPE(Linker, &Export.Object, Export.SerialOffset, Export.SerialSize, Export.bIsAsset); + LLM_SCOPE(ELLMTag::AsyncLoading); LLM_SCOPED_TAG_WITH_OBJECT_IN_SET(GetLinkerRoot(), ELLMTagSet::Assets); LLM_SCOPED_TAG_WITH_OBJECT_IN_SET((Export.DynamicType == FObjectExport::EDynamicType::DynamicType) ? UDynamicClass::StaticClass() : @@ -3224,9 +3267,9 @@ void FAsyncPackage::EventDrivenCreateExport(int32 LocalExportIndex) { SCOPED_ACCUM_LOADTIME(Construction, StaticGetNativeClassName(CastEventDrivenIndexToObject(Export.ClassIndex, false))); - if (Linker->GetFArchiveAsync2Loader()) + if (Linker->GetAsyncLoader()) { - Linker->GetFArchiveAsync2Loader()->LogItem(TEXT("EventDrivenCreateExport"), Export.SerialOffset, Export.SerialSize); + Linker->GetAsyncLoader()->LogItem(TEXT("EventDrivenCreateExport"), Export.SerialOffset, Export.SerialSize); } LastTypeOfWorkPerformed = TEXT("EventDrivenCreateExport"); LastObjectWorkWasPerformedOn = nullptr; @@ -3547,7 +3590,7 @@ void FAsyncPackage::EventDrivenSerializeExport(int32 LocalExportIndex) else if (Object && Object->HasAnyFlags(RF_NeedLoad)) { - Linker->GetFArchiveAsync2Loader()->LogItem(TEXT("EventDrivenSerializeExport"), Export.SerialOffset, Export.SerialSize); + Linker->GetAsyncLoader()->LogItem(TEXT("EventDrivenSerializeExport"), Export.SerialOffset, Export.SerialSize); LastTypeOfWorkPerformed = TEXT("EventDrivenSerializeExport"); LastObjectWorkWasPerformedOn = Object; @@ -3581,14 +3624,16 @@ void FAsyncPackage::EventDrivenSerializeExport(int32 LocalExportIndex) } check(Export.SerialOffset >= CurrentBlockOffset && Export.SerialOffset + Export.SerialSize <= CurrentBlockOffset + CurrentBlockBytes); - FArchiveAsync2* FAA2 = Linker->GetFArchiveAsync2Loader(); - check(FAA2); + FAsyncArchive* AsyncLoader = Linker->GetAsyncLoader(); + check(AsyncLoader); - const int64 SavedPos = FAA2->Tell(); - FAA2->Seek(Export.SerialOffset); + const int64 SavedPos = AsyncLoader->Tell(); + AsyncLoader->Seek(Export.SerialOffset); Object->ClearFlags(RF_NeedLoad); + TRACE_LOADTIME_OBJECT_SCOPE(Object, LoadTimeProfilerObjectEventType_Serialize); + FUObjectSerializeContext* LoadContext = GetSerializeContext(); UObject* PrevSerializedObject = LoadContext->SerializedObject; LoadContext->SerializedObject = Object; @@ -3619,19 +3664,19 @@ void FAsyncPackage::EventDrivenSerializeExport(int32 LocalExportIndex) LoadContext->SerializedObject = PrevSerializedObject; Linker->bForceSimpleIndexToObject = false; - if (FAA2->Tell() - Export.SerialOffset != Export.SerialSize) + if (AsyncLoader->Tell() - Export.SerialOffset != Export.SerialSize) { if (Object->GetClass()->HasAnyClassFlags(CLASS_Deprecated)) { - UE_LOG(LogStreaming, Warning, TEXT("%s"), *FString::Printf(TEXT("%s: Serial size mismatch: Got %d, Expected %d"), *Object->GetFullName(), (int32)(FAA2->Tell() - Export.SerialOffset), Export.SerialSize)); + UE_LOG(LogStreaming, Warning, TEXT("%s"), *FString::Printf(TEXT("%s: Serial size mismatch: Got %d, Expected %d"), *Object->GetFullName(), (int32)(AsyncLoader->Tell() - Export.SerialOffset), Export.SerialSize)); } else { - UE_LOG(LogStreaming, Fatal, TEXT("%s"), *FString::Printf(TEXT("%s: Serial size mismatch: Got %d, Expected %d"), *Object->GetFullName(), (int32)(FAA2->Tell() - Export.SerialOffset), Export.SerialSize)); + UE_LOG(LogStreaming, Fatal, TEXT("%s"), *FString::Printf(TEXT("%s: Serial size mismatch: Got %d, Expected %d"), *Object->GetFullName(), (int32)(AsyncLoader->Tell() - Export.SerialOffset), Export.SerialSize)); } } - FAA2->Seek(SavedPos); + AsyncLoader->Seek(SavedPos); #if DO_CHECK if (Object->HasAnyFlags(RF_ClassDefaultObject) && Object->GetClass()->HasAnyClassFlags(CLASS_CompiledFromBlueprint)) { @@ -3651,6 +3696,7 @@ void FAsyncPackage::EventDrivenSerializeExport(int32 LocalExportIndex) void FAsyncPackage::StartPrecacheRequest() { + SCOPED_LOADTIMER(StartPrecacheRequests); if (Linker->bDynamicClassLinker) { //native blueprint @@ -3764,10 +3810,10 @@ void FAsyncPackage::StartPrecacheRequest() } } check(NewReq.ExportsToRead.Num()); - FArchiveAsync2* FAA2 = Linker->GetFArchiveAsync2Loader(); - check(FAA2); + FAsyncArchive* AsyncLoader = Linker->GetAsyncLoader(); + check(AsyncLoader); - IAsyncReadRequest* Precache = FAA2->MakeEventDrivenPrecacheRequest(NewReq.Offset, NewReq.BytesToRead,GPrecacheCallbackHandler.GetCompletionCallback()); + IAsyncReadRequest* Precache = AsyncLoader->MakeEventDrivenPrecacheRequest(NewReq.Offset, NewReq.BytesToRead,GPrecacheCallbackHandler.GetCompletionCallback()); NewReq.FirstExportCovered = LocalExportIndex; NewReq.LastExportCovered = LastExportIndex; @@ -3792,6 +3838,7 @@ int64 FAsyncPackage::PrecacheRequestReady(IAsyncReadRequest * Read) void FAsyncPackage::MakeNextPrecacheRequestCurrent() { + SCOPED_LOADTIMER(MakeNextPrecacheRequestCurrent); LLM_SCOPE(ELLMTag::AsyncLoading); check(ReadyPrecacheRequests.Num()); @@ -3803,11 +3850,11 @@ void FAsyncPackage::MakeNextPrecacheRequestCurrent() GPrecacheCallbackHandler.FinishRequest(Req.BytesToRead); - FArchiveAsync2* FAA2 = Linker->GetFArchiveAsync2Loader(); - check(FAA2); + FAsyncArchive* AsyncLoader = Linker->GetAsyncLoader(); + check(AsyncLoader); Read->WaitCompletion(); - bool bReady = FAA2->PrecacheForEvent(CurrentBlockOffset, CurrentBlockBytes); + bool bReady = AsyncLoader->PrecacheForEvent(CurrentBlockOffset, CurrentBlockBytes); UE_CLOG(!bReady, LogStreaming, Warning, TEXT("Precache request should have been hot %s."), *Linker->Filename); for (int32 Index = Req.FirstExportCovered; Index <= Req.LastExportCovered; Index++) { @@ -3824,13 +3871,14 @@ void FAsyncPackage::MakeNextPrecacheRequestCurrent() void FAsyncPackage::FlushPrecacheBuffer() { + SCOPED_LOADTIMER(FlushPrecacheBuffer); CurrentBlockOffset = -1; CurrentBlockBytes = -1; if (!Linker->bDynamicClassLinker) { - FArchiveAsync2* FAA2 = Linker->GetFArchiveAsync2Loader(); - check(FAA2); - FAA2->FlushPrecacheBlock(); + FAsyncArchive* AsyncLoader = Linker->GetAsyncLoader(); + check(AsyncLoader); + AsyncLoader->FlushPrecacheBlock(); } } @@ -3838,6 +3886,7 @@ int32 GCurrentExportIndex = -1; EAsyncPackageState::Type FAsyncPackage::ProcessImportsAndExports_Event() { + SCOPED_LOADTIMER(ProcessImportsAndExports_Event); check(Linker); bool bDidSomething = true; int32 LoopIterations = 0; @@ -3976,7 +4025,7 @@ EAsyncPackageState::Type FAsyncPackage::ProcessImportsAndExports_Event() void FAsyncPackage::Event_ExportsDone() { - Linker->GetFArchiveAsync2Loader()->LogItem(TEXT("Event_ExportsDone")); + Linker->GetAsyncLoader()->LogItem(TEXT("Event_ExportsDone")); check(AsyncPackageLoadingState == EAsyncPackageLoadingState::ProcessNewImportsAndExports); bAllExportsSerialized = true; RemoveNode(EEventLoadNode::Package_ExportsSerialized); @@ -4006,7 +4055,7 @@ void FAsyncPackage::Event_ExportsDone() void FAsyncPackage::Event_ProcessPostloadWait() { - Linker->GetFArchiveAsync2Loader()->LogItem(TEXT("Event_ProcessPostloadWait")); + Linker->GetAsyncLoader()->LogItem(TEXT("Event_ProcessPostloadWait")); check(AsyncPackageLoadingState == EAsyncPackageLoadingState::WaitingForPostLoad); check(bAllExportsSerialized && !OtherPackagesWaitingForMeBeforePostload.Num()); bProcessPostloadWaitInFlight = false; @@ -4123,7 +4172,7 @@ void FAsyncPackage::Event_StartPostload() { LLM_SCOPE(ELLMTag::AsyncLoading); - Linker->GetFArchiveAsync2Loader()->LogItem(TEXT("Event_StartPostload")); + Linker->GetAsyncLoader()->LogItem(TEXT("Event_StartPostload")); check(AsyncPackageLoadingState == EAsyncPackageLoadingState::ReadyForPostLoad); check(!PackagesIMayBeWaitingForBeforePostload.Num()); check(!PackagesIAmWaitingForBeforePostload.Num()); @@ -4739,8 +4788,6 @@ bool FAsyncPackage::AreAllDependenciesFullyLoaded(TSet& VisitedPackag EAsyncPackageState::Type FAsyncLoadingThread::ProcessLoadedPackages(bool bUseTimeLimit, bool bUseFullTimeLimit, float TimeLimit, bool& bDidSomething, FFlushTree* FlushTree) { - SCOPED_LOADTIMER(TickAsyncLoading_ProcessLoadedPackages); - EAsyncPackageState::Type Result = EAsyncPackageState::Complete; // This is for debugging purposes only. @todo remove @@ -4753,10 +4800,16 @@ EAsyncPackageState::Type FAsyncLoadingThread::ProcessLoadedPackages(bool bUseTim FScopeLock LoadedPackagesLock(&LoadedPackagesCritical); FScopeLock LoadedPackagesToProcessLock(&LoadedPackagesToProcessCritical); #endif - LoadedPackagesToProcess.Append(LoadedPackages); - LoadedPackagesToProcessNameLookup.Append(LoadedPackagesNameLookup); - LoadedPackages.Reset(); - LoadedPackagesNameLookup.Reset(); + if (LoadedPackages.Num() != 0) + { + LoadedPackagesToProcess.Append(LoadedPackages); + LoadedPackages.Reset(); + } + if (LoadedPackagesNameLookup.Num() != 0) + { + LoadedPackagesToProcessNameLookup.Append(LoadedPackagesNameLookup); + LoadedPackagesNameLookup.Reset(); + } } #if USE_EVENT_DRIVEN_ASYNC_LOAD_AT_BOOT_TIME if (IsMultithreaded() && GEventDrivenLoaderEnabled && @@ -4776,11 +4829,12 @@ EAsyncPackageState::Type FAsyncLoadingThread::ProcessLoadedPackages(bool bUseTim bDidSomething = LoadedPackagesToProcess.Num() > 0; for (int32 PackageIndex = 0; PackageIndex < LoadedPackagesToProcess.Num() && !IsAsyncLoadingSuspended(); ++PackageIndex) { - SCOPED_LOADTIMER(ProcessLoadedPackagesTime); - FAsyncPackage* Package = LoadedPackagesToProcess[PackageIndex]; if (Package->GetDependencyRefCount() == 0) { + TRACE_LOADTIME_ASYNC_PACKAGE_SCOPE(Package, LoadTimeProfilerPackageEventType_DeferredPostLoad); + SCOPED_LOADTIMER(ProcessLoadedPackagesTime); + Result = Package->PostLoadDeferredObjects(TickStartTime, bUseTimeLimit, TimeLimit); if (Result == EAsyncPackageState::Complete) { @@ -4827,7 +4881,6 @@ EAsyncPackageState::Type FAsyncLoadingThread::ProcessLoadedPackages(bool bUseTim const bool bInternalCallbacks = false; const EAsyncLoadingResult::Type LoadingResult = Package->HasLoadFailed() ? EAsyncLoadingResult::Failed : EAsyncLoadingResult::Succeeded; Package->CallCompletionCallbacks(bInternalCallbacks, LoadingResult); - #if WITH_EDITOR // In the editor we need to find any assets and add them to list for later callback Package->GetLoadedAssets(LoadedAssets); @@ -5076,6 +5129,10 @@ FAsyncLoadingThread::FAsyncLoadingThread() TEXT("Event driven async loader is NOT being used but it seems to be enabled in project settings.")); } +#if LOADTIMEPROFILERTRACE_ENABLED + FLoadTimeProfilerTracePrivate::Init(FParse::Param(FCommandLine::Get(), TEXT("loadtimetrace"))); +#endif + QueuedRequestsEvent = FPlatformProcess::GetSynchEventFromPool(); CancelLoadingEvent = FPlatformProcess::GetSynchEventFromPool(); ThreadSuspendedEvent = FPlatformProcess::GetSynchEventFromPool(); @@ -5162,6 +5219,10 @@ void FAsyncLoadingThread::StartThread() bThreadStarted = true; FPlatformMisc::MemoryBarrier(); Thread = FRunnableThread::Create(this, TEXT("FAsyncLoadingThread"), 0, TPri_Normal); + if (Thread) + { + TRACE_SET_THREAD_GROUP(Thread->GetThreadID(), "AsyncLoading"); + } } } @@ -5176,6 +5237,8 @@ uint32 FAsyncLoadingThread::Run() AsyncLoadingThreadID = FPlatformTLS::GetCurrentThreadId(); + TRACE_LOADTIME_START_ASYNC_LOADING(); + if (!IsInGameThread()) { FPlatformProcess::SetThreadAffinityMask(FPlatformAffinity::GetAsyncLoadingThreadMask()); @@ -5344,6 +5407,7 @@ void FAsyncLoadingThread::SuspendLoading() #endif if (IsMultithreaded() && SuspendCount == 1) { + TRACE_LOADTIME_SUSPEND_ASYNC_LOADING(); ThreadSuspendedEvent->Wait(); } } @@ -5359,6 +5423,7 @@ void FAsyncLoadingThread::ResumeLoading() if (IsMultithreaded() && SuspendCount == 0) { ThreadResumedEvent->Wait(); + TRACE_LOADTIME_RESUME_ASYNC_LOADING(); } } @@ -5498,11 +5563,13 @@ FAsyncPackage::FAsyncPackage(const FAsyncPackageDesc& InDesc) , FinishObjectsTime(0.0) #endif // PERF_TRACK_DETAILED_ASYNC_STATS { + TRACE_LOADTIME_NEW_ASYNC_PACKAGE(this, *InDesc.Name.ToString()); AddRequestID(InDesc.RequestID); } FAsyncPackage::~FAsyncPackage() { + TRACE_LOADTIME_DESTROY_ASYNC_PACKAGE(this); #if DO_CHECK if (GEventDrivenLoaderEnabled) { @@ -5583,6 +5650,7 @@ void FAsyncPackage::AddRequestID(int32 Id) } RequestIDs.Add(Id); AsyncLoadingThread.AddPendingRequest(Id); + TRACE_LOADTIME_ASYNC_PACKAGE_REQUEST_ASSOCIATION(this, Id); } } @@ -5733,6 +5801,7 @@ EAsyncPackageState::Type FAsyncPackage::TickAsyncPackage(bool InbUseTimeLimit, b ReentryCount++; + TRACE_LOADTIME_ASYNC_PACKAGE_SCOPE(this, LoadTimeProfilerPackageEventType_Tick); SCOPE_CYCLE_COUNTER(STAT_FAsyncPackage_Tick); SCOPED_LOADTIMER(Package_Tick); @@ -6083,7 +6152,7 @@ EAsyncPackageState::Type FAsyncPackage::CreateLinker() if (Linker->bDynamicClassLinker) { //native blueprint - check(!Linker->GetFArchiveAsync2Loader()); + check(!Linker->GetAsyncLoader()); GPrecacheCallbackHandler.SummaryComplete(WeakPtr); } } @@ -6098,6 +6167,7 @@ EAsyncPackageState::Type FAsyncPackage::CreateLinker() check(Linker); check(Linker->AsyncRoot == nullptr || Linker->AsyncRoot == this); Linker->AsyncRoot = this; + TRACE_LOADTIME_ASYNC_PACKAGE_LINKER_ASSOCIATION(this, Linker); UE_LOG(LogStreaming, Verbose, TEXT("FAsyncPackage::CreateLinker for %s finished."), *Desc.NameToLoad.ToString()); } @@ -6460,10 +6530,10 @@ EAsyncPackageState::Type FAsyncPackage::CreateExports() // Precache data and see whether it's already finished. bool bReady; - FArchiveAsync2* FAA2 = Linker->GetFArchiveAsync2Loader(); - if (FAA2) + FAsyncArchive* AsyncLoader = Linker->GetAsyncLoader(); + if (AsyncLoader) { - bReady = FAA2->Precache(Export.SerialOffset, Export.SerialSize, bUseTimeLimit, bUseFullTimeLimit, TickStartTime, TimeLimit); + bReady = AsyncLoader->PrecacheWithTimeLimit(Export.SerialOffset, Export.SerialSize, bUseTimeLimit, bUseFullTimeLimit, TickStartTime, TimeLimit); } else { @@ -6666,7 +6736,10 @@ EAsyncPackageState::Type FAsyncPackage::PostLoadObjects() check(!GEventDrivenLoaderEnabled || !Object->HasAnyFlags(RF_NeedLoad)); ThreadContext.CurrentlyPostLoadedObjectByALT = Object; - Object->ConditionalPostLoad(); + { + TRACE_LOADTIME_OBJECT_SCOPE(Object, LoadTimeProfilerObjectEventType_PostLoad); + Object->ConditionalPostLoad(); + } ThreadContext.CurrentlyPostLoadedObjectByALT = nullptr; LastObjectWorkWasPerformedOn = Object; @@ -6731,7 +6804,10 @@ EAsyncPackageState::Type FAsyncPackage::PostLoadDeferredObjects(double InTickSta FScopeCycleCounterUObject ConstructorScope(Object, GET_STATID(STAT_FAsyncPackage_PostLoadObjectsGameThread)); PackageScope.ThreadContext.CurrentlyPostLoadedObjectByALT = Object; - Object->ConditionalPostLoad(); + { + TRACE_LOADTIME_OBJECT_SCOPE(Object, LoadTimeProfilerObjectEventType_PostLoad); + Object->ConditionalPostLoad(); + } PackageScope.ThreadContext.CurrentlyPostLoadedObjectByALT = nullptr; if (ObjLoadedInPostLoad.Num()) @@ -7167,6 +7243,7 @@ int32 LoadPackageAsync(const FString& InName, const FGuid* InGuid /*= nullptr*/, // Generate new request ID and add it immediately to the global request list (it needs to be there before we exit // this function, otherwise it would be added when the packages are being processed on the async thread). RequestID = GPackageRequestID.Increment(); + TRACE_LOADTIME_BEGIN_REQUEST(RequestID); FAsyncLoadingThread::Get().AddPendingRequest(RequestID); // Allocate delegate on Game Thread, it is not safe to copy delegates by value on other threads @@ -7250,7 +7327,7 @@ void FlushAsyncLoading(int32 PackageID /* = INDEX_NONE */) checkf(IsInGameThread(), TEXT("Unable to FlushAsyncLoading from any thread other than the game thread.")); - if (IsAsyncLoading()) + if (IsAsyncLoading()) { FAsyncLoadingThread& AsyncThread = FAsyncLoadingThread::Get(); // Flushing async loading while loading is suspend will result in infinite stall @@ -7263,6 +7340,8 @@ void FlushAsyncLoading(int32 PackageID /* = INDEX_NONE */) return; } + TRACE_LOADTIME_FLUSH_ASYNCLOADING_SCOPE(PackageID); + FCoreDelegates::OnAsyncLoadingFlush.Broadcast(); #if NO_LOGGING == 0 @@ -7475,10 +7554,10 @@ void NotifyRegistrationComplete() GetGEDLBootNotificationManager().NotifyRegistrationComplete(); } -#define USE_DETAILED_FARCHIVEASYNC2_MEMORY_TRACKING 0 +#define USE_DETAILED_FASYNCARCHIVE_MEMORY_TRACKING 0 -#if USE_DETAILED_FARCHIVEASYNC2_MEMORY_TRACKING -class FArchiveAsync2MemTracker +#if USE_DETAILED_FASYNCARCHIVE_MEMORY_TRACKING +class FAsyncArchiveMemTracker { TMap AllocatedMem; FCriticalSection AllocatedMemCritical; @@ -7486,14 +7565,14 @@ class FArchiveAsync2MemTracker public: void Allocate(const FString& Filename, int64 Mem) -{ + { FScopeLock AllocatedMemLock(&AllocatedMemCritical); int64& AllocatedMemAmount = AllocatedMem.FindOrAdd(Filename); AllocatedMemAmount += Mem; -} + } void Deallocate(const FString& Filename, int64 Mem) -{ + { FScopeLock AllocatedMemLock(&AllocatedMemCritical); int64& AllocatedMemAmount = AllocatedMem.FindOrAdd(Filename); AllocatedMemAmount -= Mem; @@ -7501,36 +7580,36 @@ public: if (AllocatedMemAmount == 0) { AllocatedMem.Remove(Filename); + } } -} void Dump() -{ + { FScopeLock AllocatedMemLock(&AllocatedMemCritical); - UE_LOG(LogStreaming, Display, TEXT("Dumping FArchiveAsync2 allocated memory (%d)"), AllocatedMem.Num()); + UE_LOG(LogStreaming, Display, TEXT("Dumping FAsyncArchie allocated memory (%d)"), AllocatedMem.Num()); for (TPair& ArchiveMem : AllocatedMem) -{ + { UE_LOG(LogStreaming, Display, TEXT(" %s %lldb"), *ArchiveMem.Key, ArchiveMem.Value); -} -} -} GArchiveAsync2MemTracker; + } + } +} GAsyncArchiveMemTracker; -void DumpArchiveAsync2Mem(const TArray& Args) +void DumpAsyncArchiveMem(const TArray& Args) { - GArchiveAsync2MemTracker.Dump(); + GAsyncArchiveMemTracker.Dump(); } static FAutoConsoleCommand GDumpSerializeCmd( - TEXT("DumpFArchiveAsync2Mem"), - TEXT("Debug command to dump the memory allocated by existing FArhiveAsync2."), - FConsoleCommandWithArgsDelegate::CreateStatic(&DumpArchiveAsync2Mem) + TEXT("DumpAsyncArchiveMem"), + TEXT("Debug command to dump the memory allocated by existing FAsyncArchive."), + FConsoleCommandWithArgsDelegate::CreateStatic(&DumpAsyncArchiveMem) ); -#endif // USE_DETAILED_FARCHIVEASYNC2_MEMORY_TRACKING +#endif // USE_DETAILED_FASYNCARCHIVE_MEMORY_TRACKING static FCriticalSection SummaryRacePreventer; -FArchiveAsync2::FArchiveAsync2(const TCHAR* InFileName, TFunction&& InSummaryReadyCallback) +FAsyncArchive::FAsyncArchive(const TCHAR* InFileName, TFunction&& InSummaryReadyCallback) : Handle(nullptr) , SizeRequestPtr(nullptr) , EditorPrecacheRequestPtr(nullptr) @@ -7577,7 +7656,7 @@ FArchiveAsync2::FArchiveAsync2(const TCHAR* InFileName, TFunction&& InSu } -FArchiveAsync2::~FArchiveAsync2() +FAsyncArchive::~FAsyncArchive() { // Invalidate any precached data and free memory. FlushCache(); @@ -7586,10 +7665,10 @@ FArchiveAsync2::~FArchiveAsync2() delete Handle; Handle = nullptr; } - LogItem(TEXT("~FArchiveAsync2"), 0, 0); + LogItem(TEXT("~FAsyncArchive"), 0, 0); } -void FArchiveAsync2::ReadCallback(bool bWasCancelled, IAsyncReadRequest* Request) +void FAsyncArchive::ReadCallback(bool bWasCancelled, IAsyncReadRequest* Request) { if (bWasCancelled || ArIsError) { @@ -7680,17 +7759,17 @@ void FArchiveAsync2::ReadCallback(bool bWasCancelled, IAsyncReadRequest* Request } } -void FArchiveAsync2::FlushPrecacheBlock() +void FAsyncArchive::FlushPrecacheBlock() { #if DEVIRTUALIZE_FLinkerLoad_Serialize DiscardInlineBufferAndUpdateCurrentPos(); #endif if (PrecacheBuffer) { - DEC_MEMORY_STAT_BY(STAT_FArchiveAsync2Mem, PrecacheEndPos - PrecacheStartPos); + DEC_MEMORY_STAT_BY(STAT_FAsyncArchiveMem, PrecacheEndPos - PrecacheStartPos); FMemory::Free(PrecacheBuffer); -#if USE_DETAILED_FARCHIVEASYNC2_MEMORY_TRACKING - GArchiveAsync2MemTracker.Deallocate(FileName, PrecacheEndPos - PrecacheStartPos); +#if USE_DETAILED_FASYNCARCHIVE_MEMORY_TRACKING + GAsyncArchieMemTracker.Deallocate(FileName, PrecacheEndPos - PrecacheStartPos); #endif check(!GEventDrivenLoaderEnabled || LoadPhase > ELoadPhase::WaitingForHeader); } @@ -7699,7 +7778,7 @@ void FArchiveAsync2::FlushPrecacheBlock() PrecacheEndPos = 0; } -void FArchiveAsync2::FlushCache() +void FAsyncArchive::FlushCache() { bool nNonRedundantFlush = PrecacheEndPos || PrecacheBuffer || ReadRequestPtr; LogItem(TEXT("Flush")); @@ -7749,7 +7828,7 @@ void FArchiveAsync2::FlushCache() } -bool FArchiveAsync2::Close() +bool FAsyncArchive::Close() { // Invalidate any precached data and free memory. FlushCache(); @@ -7757,13 +7836,13 @@ bool FArchiveAsync2::Close() return !ArIsError; } -bool FArchiveAsync2::SetCompressionMap(TArray* InCompressedChunks, ECompressionFlags InCompressionFlags) +bool FAsyncArchive::SetCompressionMap(TArray* InCompressedChunks, ECompressionFlags InCompressionFlags) { check(0); // no support for compression return false; } -int64 FArchiveAsync2::TotalSize() +int64 FAsyncArchive::TotalSize() { if (SizeRequestPtr) { @@ -7780,7 +7859,7 @@ int64 FArchiveAsync2::TotalSize() } #if DEVIRTUALIZE_FLinkerLoad_Serialize -FORCEINLINE void FArchiveAsync2::SetPosAndUpdatePrecacheBuffer(int64 Pos) +FORCEINLINE void FAsyncArchive::SetPosAndUpdatePrecacheBuffer(int64 Pos) { check(Pos >= 0 && Pos <= TotalSizeOrMaxInt64IfNotReady()); if (Pos < PrecacheStartPos || Pos >= PrecacheEndPos) @@ -7800,7 +7879,7 @@ FORCEINLINE void FArchiveAsync2::SetPosAndUpdatePrecacheBuffer(int64 Pos) } #endif -void FArchiveAsync2::Seek(int64 InPos) +void FAsyncArchive::Seek(int64 InPos) { if ((GEventDrivenLoaderEnabled || bCookedForEDLInEditor) && LoadPhase < ELoadPhase::ProcessingExports) { @@ -7810,7 +7889,7 @@ void FArchiveAsync2::Seek(int64 InPos) FirstExportStarting(); } } - checkf(InPos >= 0 && InPos <= TotalSizeOrMaxInt64IfNotReady(), TEXT("Bad position in FArchiveAsync2::Seek. Filename:%s InPos:%lu, Size:%lu"), *FileName, InPos, TotalSizeOrMaxInt64IfNotReady()); + checkf(InPos >= 0 && InPos <= TotalSizeOrMaxInt64IfNotReady(), TEXT("Bad position in FAsyncArchive::Seek. Filename:%s InPos:%lu, Size:%lu"), *FileName, InPos, TotalSizeOrMaxInt64IfNotReady()); #if DEVIRTUALIZE_FLinkerLoad_Serialize SetPosAndUpdatePrecacheBuffer(InPos); #else @@ -7818,7 +7897,7 @@ void FArchiveAsync2::Seek(int64 InPos) #endif } -bool FArchiveAsync2::WaitRead(float TimeLimit) +bool FAsyncArchive::WaitRead(float TimeLimit) { if (ReadRequestPtr) { @@ -7838,7 +7917,7 @@ bool FArchiveAsync2::WaitRead(float TimeLimit) return true; } -void FArchiveAsync2::CompleteRead() +void FAsyncArchive::CompleteRead() { double StartTime = FPlatformTime::Seconds(); check(LoadPhase != ELoadPhase::WaitingForSize && LoadPhase != ELoadPhase::WaitingForSummary); @@ -7860,10 +7939,10 @@ void FArchiveAsync2::CompleteRead() PrecacheStartPos = ReadRequestOffset; PrecacheEndPos = ReadRequestOffset + ReadRequestSize; check(ReadRequestSize > 0 && PrecacheStartPos >= 0); - INC_MEMORY_STAT_BY(STAT_FArchiveAsync2Mem, PrecacheEndPos - PrecacheStartPos); + INC_MEMORY_STAT_BY(STAT_FAsyncArchiveMem, PrecacheEndPos - PrecacheStartPos); DEC_MEMORY_STAT_BY(STAT_AsyncFileMemory, ReadRequestSize); -#if USE_DETAILED_FARCHIVEASYNC2_MEMORY_TRACKING - GArchiveAsync2MemTracker.Allocate(FileName, PrecacheEndPos - PrecacheStartPos); +#if USE_DETAILED_FASYNCARCHIVE_MEMORY_TRACKING + GAsyncArchiveMemTracker.Allocate(FileName, PrecacheEndPos - PrecacheStartPos); #endif // keeps the last cache block of the header around until we process the first export if (LoadPhase != ELoadPhase::ProcessingExports) @@ -7881,7 +7960,7 @@ void FArchiveAsync2::CompleteRead() ReadRequestSize = 0; } -void FArchiveAsync2::CompleteCancel() +void FAsyncArchive::CompleteCancel() { if (CanceledReadRequestPtr) { @@ -7895,7 +7974,7 @@ void FArchiveAsync2::CompleteCancel() } -void FArchiveAsync2::CancelRead() +void FAsyncArchive::CancelRead() { if (ReadRequestPtr) { @@ -7908,7 +7987,7 @@ void FArchiveAsync2::CancelRead() ReadRequestSize = 0; } -bool FArchiveAsync2::WaitForIntialPhases(float InTimeLimit) +bool FAsyncArchive::WaitForIntialPhases(float InTimeLimit) { if (SizeRequestPtr || GEventDrivenLoaderEnabled || SummaryRequestPtr || SummaryPrecacheRequestPtr @@ -7981,7 +8060,7 @@ bool FArchiveAsync2::WaitForIntialPhases(float InTimeLimit) return true; } -bool FArchiveAsync2::PrecacheInternal(int64 RequestOffset, int64 RequestSize, bool bApplyMinReadSize) +bool FAsyncArchive::PrecacheInternal(int64 RequestOffset, int64 RequestSize, bool bApplyMinReadSize) { // CAUTION! This is possibly called the first time from a random IO thread. @@ -8008,7 +8087,7 @@ bool FArchiveAsync2::PrecacheInternal(int64 RequestOffset, int64 RequestSize, bo if (ReadRequestPtr) { // this one does not have what we need - UE_LOG(LogStreaming, Warning, TEXT("FArchiveAsync2::PrecacheInternal Canceled read for %s Offset = %lld Size = %lld"), *FileName, RequestOffset, ReadRequestSize); + UE_LOG(LogStreaming, Warning, TEXT("FAsyncArchive::PrecacheInternal Canceled read for %s Offset = %lld Size = %lld"), *FileName, RequestOffset, ReadRequestSize); CancelRead(); } } @@ -8068,7 +8147,7 @@ bool FArchiveAsync2::PrecacheInternal(int64 RequestOffset, int64 RequestSize, bo return false; } -void FArchiveAsync2::FirstExportStarting() +void FAsyncArchive::FirstExportStarting() { ExportReadTime = FPlatformTime::Seconds(); LogItem(TEXT("Exports")); @@ -8098,7 +8177,7 @@ void FArchiveAsync2::FirstExportStarting() } } -IAsyncReadRequest* FArchiveAsync2::MakeEventDrivenPrecacheRequest(int64 Offset, int64 BytesToRead, FAsyncFileCallBack* CompleteCallback) +IAsyncReadRequest* FAsyncArchive::MakeEventDrivenPrecacheRequest(int64 Offset, int64 BytesToRead, FAsyncFileCallBack* CompleteCallback) { check(GEventDrivenLoaderEnabled); if (LoadPhase == ELoadPhase::WaitingForFirstExport) @@ -8143,7 +8222,7 @@ IAsyncReadRequest* FArchiveAsync2::MakeEventDrivenPrecacheRequest(int64 Offset, } -bool FArchiveAsync2::Precache(int64 RequestOffset, int64 RequestSize, bool bUseTimeLimit, bool bUseFullTimeLimit, double TickStartTime, float TimeLimit) +bool FAsyncArchive::PrecacheWithTimeLimit(int64 RequestOffset, int64 RequestSize, bool bUseTimeLimit, bool bUseFullTimeLimit, double TickStartTime, float TimeLimit) { #if defined(ASYNC_WATCH_FILE) if (FileName.Contains(TEXT(ASYNC_WATCH_FILE)) && RequestOffset == 878129) @@ -8176,7 +8255,7 @@ bool FArchiveAsync2::Precache(int64 RequestOffset, int64 RequestSize, bool bUseT return bResult; } -bool FArchiveAsync2::Precache(int64 RequestOffset, int64 RequestSize) +bool FAsyncArchive::Precache(int64 RequestOffset, int64 RequestSize) { if (LoadPhase == ELoadPhase::WaitingForSize || LoadPhase == ELoadPhase::WaitingForSummary) { @@ -8190,14 +8269,14 @@ bool FArchiveAsync2::Precache(int64 RequestOffset, int64 RequestSize) return PrecacheInternal(RequestOffset, RequestSize); } -bool FArchiveAsync2::PrecacheForEvent(int64 RequestOffset, int64 RequestSize) +bool FAsyncArchive::PrecacheForEvent(int64 RequestOffset, int64 RequestSize) { check(int32(LoadPhase) > int32(ELoadPhase::WaitingForHeader)); return PrecacheInternal(RequestOffset, RequestSize, false); } -void FArchiveAsync2::StartReadingHeader() +void FAsyncArchive::StartReadingHeader() { //LogItem(TEXT("Start Header")); WaitForIntialPhases(); @@ -8212,7 +8291,7 @@ void FArchiveAsync2::StartReadingHeader() } } -void FArchiveAsync2::EndReadingHeader() +void FAsyncArchive::EndReadingHeader() { LogItem(TEXT("End Header")); check(LoadPhase == ELoadPhase::WaitingForHeader); @@ -8220,7 +8299,7 @@ void FArchiveAsync2::EndReadingHeader() FlushPrecacheBlock(); } -bool FArchiveAsync2::ReadyToStartReadingHeader(bool bUseTimeLimit, bool bUseFullTimeLimit, double TickStartTime, float TimeLimit) +bool FAsyncArchive::ReadyToStartReadingHeader(bool bUseTimeLimit, bool bUseFullTimeLimit, double TickStartTime, float TimeLimit) { if (SummaryReadTime == 0.0) { @@ -8254,7 +8333,7 @@ bool FArchiveAsync2::ReadyToStartReadingHeader(bool bUseTimeLimit, bool bUseFull void CallSerializeHook(); #endif -void FArchiveAsync2::Serialize(void* Data, int64 Count) +void FAsyncArchive::Serialize(void* Data, int64 Count) { if (!Count || ArIsError) { @@ -8342,7 +8421,7 @@ void FArchiveAsync2::Serialize(void* Data, int64 Count) } if (BeforeBlockSize) { - UE_CLOG(GEventDrivenLoaderEnabled, LogAsyncArchive, Warning, TEXT("FArchiveAsync2::Serialize Backwards streaming in %s CurrentPos = %lld BeforeBlockOffset = %lld"), *FileName, CurrentPos, BeforeBlockOffset); + UE_CLOG(GEventDrivenLoaderEnabled, LogAsyncArchive, Warning, TEXT("FAsyncArchive::Serialize Backwards streaming in %s CurrentPos = %lld BeforeBlockOffset = %lld"), *FileName, CurrentPos, BeforeBlockOffset); LogItem(TEXT("Sync Before Block"), BeforeBlockOffset, BeforeBlockSize); if (!PrecacheInternal(BeforeBlockOffset, BeforeBlockSize)) { @@ -8410,7 +8489,7 @@ void FArchiveAsync2::Serialize(void* Data, int64 Count) } #if DEVIRTUALIZE_FLinkerLoad_Serialize -void FArchiveAsync2::DiscardInlineBufferAndUpdateCurrentPos() +void FAsyncArchive::DiscardInlineBufferAndUpdateCurrentPos() { CurrentPos += (ActiveFPLB->StartFastPathLoadBuffer - ActiveFPLB->OriginalFastPathLoadBuffer); ActiveFPLB->Reset(); @@ -8423,17 +8502,17 @@ void FArchiveAsync2::DiscardInlineBufferAndUpdateCurrentPos() static TAutoConsoleVariable CVarLogAsyncArchiveSerializeChurn( TEXT("LogAsyncArchiveSerializeChurn.Enable"), 0, - TEXT("If > 0, then sample game thread FArchiveAsync2::Serialize calls, periodically print a report of the worst offenders.")); + TEXT("If > 0, then sample game thread FAsyncArchive::Serialize calls, periodically print a report of the worst offenders.")); static TAutoConsoleVariable CVarLogAsyncArchiveSerializeChurn_Threshhold( TEXT("LogAsyncArchiveSerializeChurn.Threshhold"), 1000, - TEXT("Minimum average number of FArchiveAsync2::Serialize calls to include in the report.")); + TEXT("Minimum average number of FAsyncArchive::Serialize calls to include in the report.")); static TAutoConsoleVariable CVarLogAsyncArchiveSerializeChurn_SampleFrequency( TEXT("LogAsyncArchiveSerializeChurn.SampleFrequency"), 1000, - TEXT("Number of FArchiveAsync2::Serialize calls per sample. This is used to prevent sampling from slowing the game down too much.")); + TEXT("Number of FAsyncArchive::Serialize calls per sample. This is used to prevent sampling from slowing the game down too much.")); static TAutoConsoleVariable CVarLogAsyncArchiveSerializeChurn_StackIgnore( TEXT("LogAsyncArchiveSerializeChurn.StackIgnore"), diff --git a/Engine/Source/Runtime/CoreUObject/Private/Serialization/AsyncLoadingPrivate.h b/Engine/Source/Runtime/CoreUObject/Private/Serialization/AsyncLoadingPrivate.h index 923f16edc291..e2e8d66b0008 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Serialization/AsyncLoadingPrivate.h +++ b/Engine/Source/Runtime/CoreUObject/Private/Serialization/AsyncLoadingPrivate.h @@ -8,7 +8,7 @@ DECLARE_LOG_CATEGORY_EXTERN(LogLoadingDev, Fatal, All); -class COREUOBJECT_API FArchiveAsync2 final: public FArchive +class COREUOBJECT_API FAsyncArchive final: public FArchive { public: enum class ELoadPhase @@ -20,10 +20,10 @@ public: ProcessingExports, }; - FArchiveAsync2(const TCHAR* InFileName - , TFunction&& InSummaryReadyCallback - ); - virtual ~FArchiveAsync2(); + FAsyncArchive(const TCHAR* InFileName, TFunction&& InSummaryReadyCallback); + virtual ~FAsyncArchive (); + + /** Archive overrides */ virtual bool Close() override; virtual bool SetCompressionMap(TArray* CompressedChunks, ECompressionFlags CompressionFlags) override; virtual bool Precache(int64 PrecacheOffset, int64 PrecacheSize) override; @@ -44,16 +44,14 @@ public: return FileName; } - bool Precache(int64 PrecacheOffset, int64 PrecacheSize, bool bUseTimeLimit, bool bUseFullTimeLimit, double TickStartTime, float TimeLimit); + /** AsyncArchive interface */ + bool PrecacheWithTimeLimit(int64 PrecacheOffset, int64 PrecacheSize, bool bUseTimeLimit, bool bUseFullTimeLimit, double TickStartTime, float TimeLimit); bool PrecacheForEvent(int64 PrecacheOffset, int64 PrecacheSize); void FlushPrecacheBlock(); bool ReadyToStartReadingHeader(bool bUseTimeLimit, bool bUseFullTimeLimit, double TickStartTime, float TimeLimit); void StartReadingHeader(); void EndReadingHeader(); - void FirstExportStarting(); - IAsyncReadRequest* MakeEventDrivenPrecacheRequest(int64 Offset, int64 BytesToRead, FAsyncFileCallBack* CompleteCallback); - void LogItem(const TCHAR* Item, int64 Offset = 0, int64 Size = 0, double StartTime = 0.0); bool IsCookedForEDLInEditor() const @@ -71,7 +69,7 @@ private: void SetPosAndUpdatePrecacheBuffer(int64 Pos); #endif - + void FirstExportStarting(); bool WaitRead(float TimeLimit = 0.0f); void CompleteRead(); void CancelRead(); diff --git a/Engine/Source/Runtime/CoreUObject/Private/Serialization/AsyncLoadingThread.h b/Engine/Source/Runtime/CoreUObject/Private/Serialization/AsyncLoadingThread.h index 6886cbd746f0..fa565431e604 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Serialization/AsyncLoadingThread.h +++ b/Engine/Source/Runtime/CoreUObject/Private/Serialization/AsyncLoadingThread.h @@ -9,6 +9,7 @@ #include "HAL/ThreadSafeCounter.h" #include "UObject/ObjectMacros.h" #include "Serialization/AsyncLoading.h" +#include "Serialization/LoadTimeTracePrivate.h" #include "Misc/ScopeLock.h" #include "Misc/CommandLine.h" #include "Misc/App.h" @@ -517,6 +518,7 @@ public: for (int32 ID : RequestIDs) { PendingRequests.Remove(ID); + TRACE_LOADTIME_END_REQUEST(ID); } } diff --git a/Engine/Source/Runtime/CoreUObject/Private/Serialization/BulkData.cpp b/Engine/Source/Runtime/CoreUObject/Private/Serialization/BulkData.cpp index 33b82fc850cb..049d6dc1335d 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Serialization/BulkData.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/Serialization/BulkData.cpp @@ -1538,7 +1538,7 @@ void FUntypedBulkData::LoadDataIntoMemory( void* Dest ) checkf( AttachedAr, TEXT( "Attempted to load bulk data without an attached archive. Most likely the bulk data was loaded twice on console, which is not supported" ) ); FArchive* BulkDataArchive = nullptr; - if (Linker && Linker->GetFArchiveAsync2Loader() && Linker->GetFArchiveAsync2Loader()->IsCookedForEDLInEditor() && + if (Linker && Linker->GetAsyncLoader() && Linker->GetAsyncLoader()->IsCookedForEDLInEditor() && (BulkDataFlags & BULKDATA_PayloadInSeperateFile)) { // The attached archive is a package cooked for EDL loaded in the editor so the actual bulk data sits in a separate ubulk file. diff --git a/Engine/Source/Runtime/CoreUObject/Private/Serialization/Formatters/JsonArchiveInputFormatter.cpp b/Engine/Source/Runtime/CoreUObject/Private/Serialization/Formatters/JsonArchiveInputFormatter.cpp index b4b5f7cc49bf..925b85bab5af 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Serialization/Formatters/JsonArchiveInputFormatter.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/Serialization/Formatters/JsonArchiveInputFormatter.cpp @@ -24,6 +24,9 @@ FJsonArchiveInputFormatter::FJsonArchiveInputFormatter(FArchive& InInner, TFunct TSharedRef< TJsonReader > Reader = TJsonReaderFactory::Create(&InInner); ensure(FJsonSerializer::Deserialize(Reader, RootObject)); ValueStack.Add(MakeShared(RootObject)); + + ValueStack.Reserve(64); + ArrayValuesRemainingStack.Reserve(64); } FJsonArchiveInputFormatter::~FJsonArchiveInputFormatter() @@ -41,6 +44,7 @@ FStructuredArchiveFormatter* FJsonArchiveInputFormatter::CreateSubtreeReader() Cloned->ObjectStack.Empty(); Cloned->ValueStack.Empty(); Cloned->MapIteratorStack.Empty(); + Cloned->ArrayValuesRemainingStack.Empty(); Cloned->ValueStack.Push(ValueStack.Top()); return Cloned; @@ -115,14 +119,26 @@ void FJsonArchiveInputFormatter::EnterArray(int32& NumElements) } NumElements = ArrayValue.Num(); + ArrayValuesRemainingStack.Add(NumElements); } void FJsonArchiveInputFormatter::LeaveArray() { + check(ArrayValuesRemainingStack.Num() > 0); + int32 Remaining = ArrayValuesRemainingStack.Top(); + ArrayValuesRemainingStack.Pop(); + check(Remaining >= 0); + check(Remaining <= ValueStack.Num()); + while (Remaining-- > 0) + { + ValueStack.Pop(); + } } void FJsonArchiveInputFormatter::EnterArrayElement() { + check(ArrayValuesRemainingStack.Num() > 0); + check(ArrayValuesRemainingStack.Top() > 0); } void FJsonArchiveInputFormatter::EnterArrayElement_TextOnly(EArchiveValueType& OutType) @@ -133,6 +149,7 @@ void FJsonArchiveInputFormatter::EnterArrayElement_TextOnly(EArchiveValueType& O void FJsonArchiveInputFormatter::LeaveArrayElement() { ValueStack.Pop(); + ArrayValuesRemainingStack.Top()--; } void FJsonArchiveInputFormatter::EnterStream() @@ -148,6 +165,7 @@ void FJsonArchiveInputFormatter::EnterStream_TextOnly(int32& NumElements) void FJsonArchiveInputFormatter::LeaveStream() { + LeaveArray(); } void FJsonArchiveInputFormatter::EnterStreamElement() @@ -232,7 +250,7 @@ void FJsonArchiveInputFormatter::Serialize(int32& Value) void FJsonArchiveInputFormatter::Serialize(int64& Value) { - Value = (uint64)ValueStack.Top()->AsNumber(); + Value = (int64)ValueStack.Top()->AsNumber(); } void FJsonArchiveInputFormatter::Serialize(float& Value) diff --git a/Engine/Source/Runtime/CoreUObject/Private/Serialization/Formatters/JsonArchiveOutputFormatter.cpp b/Engine/Source/Runtime/CoreUObject/Private/Serialization/Formatters/JsonArchiveOutputFormatter.cpp index 573a69e503e3..d44ec85bd80f 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Serialization/Formatters/JsonArchiveOutputFormatter.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/Serialization/Formatters/JsonArchiveOutputFormatter.cpp @@ -314,14 +314,8 @@ void FJsonArchiveOutputFormatter::Serialize(FWeakObjectPtr& Value) void FJsonArchiveOutputFormatter::Serialize(FSoftObjectPtr& Value) { - if (Value.IsValid()) - { - SerializeStringInternal(FString::Printf(TEXT("Object:%s"), *Value.Get()->GetFullName())); - } - else - { - WriteValue(TEXT("null")); - } + FSoftObjectPath Path = Value.ToSoftObjectPath(); + Serialize(Path); } void FJsonArchiveOutputFormatter::Serialize(FSoftObjectPath& Value) diff --git a/Engine/Source/Runtime/CoreUObject/Private/Serialization/LoadTimeTrace.cpp b/Engine/Source/Runtime/CoreUObject/Private/Serialization/LoadTimeTrace.cpp new file mode 100644 index 000000000000..622d18583e20 --- /dev/null +++ b/Engine/Source/Runtime/CoreUObject/Private/Serialization/LoadTimeTrace.cpp @@ -0,0 +1,522 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Serialization/LoadTimeTrace.h" + +#if LOADTIMEPROFILERTRACE_ENABLED + +#include "LoadTimeTracePrivate.h" +#include "Trace/Trace.h" +#include "Misc/CString.h" +#include "HAL/PlatformTime.h" +#include "HAL/PlatformTLS.h" +#include "UObject/Object.h" +#include "UObject/UObjectGlobals.h" + +UE_TRACE_EVENT_BEGIN(LoadTime, StartAsyncLoading, Always) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, SuspendAsyncLoading) + UE_TRACE_EVENT_FIELD(uint64, Cycle) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, ResumeAsyncLoading) + UE_TRACE_EVENT_FIELD(uint64, Cycle) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, NewPackage) + UE_TRACE_EVENT_FIELD(const UPackage*, Package) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, NewLinker) + UE_TRACE_EVENT_FIELD(const FLinkerLoad*, Linker) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, DestroyLinker) + UE_TRACE_EVENT_FIELD(const FLinkerLoad*, Linker) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, PackageSummary) + UE_TRACE_EVENT_FIELD(const FLinkerLoad*, Linker) + UE_TRACE_EVENT_FIELD(uint32, TotalHeaderSize) + UE_TRACE_EVENT_FIELD(uint32, NameCount) + UE_TRACE_EVENT_FIELD(uint32, ImportCount) + UE_TRACE_EVENT_FIELD(uint32, ExportCount) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, BeginLinkerScope) + UE_TRACE_EVENT_FIELD(const FLinkerLoad*, Linker) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, EndLinkerScope) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, BeginCreateExport) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(const FLinkerLoad*, Linker) + UE_TRACE_EVENT_FIELD(uint64, SerialOffset) + UE_TRACE_EVENT_FIELD(uint64, SerialSize) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) + UE_TRACE_EVENT_FIELD(bool, IsAsset) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, EndCreateExport) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(const UObject*, Object) + UE_TRACE_EVENT_FIELD(const UClass*, Class) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, BeginObjectScope) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(const UObject*, Object) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) + UE_TRACE_EVENT_FIELD(uint8, EventType) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, EndObjectScope) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, LinkerArchiveAssociation) + UE_TRACE_EVENT_FIELD(const FLinkerLoad*, Linker) + UE_TRACE_EVENT_FIELD(const FArchive*, Archive) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, BeginRequest) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint64, RequestId) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, EndRequest) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint64, RequestId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, BeginLoadMap) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, EndLoadMap) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, NewStreamableHandle) + UE_TRACE_EVENT_FIELD(const FStreamableHandle*, StreamableHandle) + UE_TRACE_EVENT_FIELD(bool, IsCombined) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, DestroyStreamableHandle) + UE_TRACE_EVENT_FIELD(const FStreamableHandle*, StreamableHandle) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, BeginLoadStreamableHandle) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(const FStreamableHandle*, StreamableHandle) + UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, EndLoadStreamableHandle) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(const FStreamableHandle*, StreamableHandle) + UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, BeginWaitForStreamableHandle) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(const FStreamableHandle*, StreamableHandle) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, EndWaitForStreamableHandle) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, NewAsyncPackage) + UE_TRACE_EVENT_FIELD(const FAsyncPackage*, AsyncPackage) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, DestroyAsyncPackage) + UE_TRACE_EVENT_FIELD(const FAsyncPackage*, AsyncPackage) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, StreamableHandleRequestAssociation) + UE_TRACE_EVENT_FIELD(const FStreamableHandle*, StreamableHandle) + UE_TRACE_EVENT_FIELD(uint64, RequestId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, AsyncPackageRequestAssociation) + UE_TRACE_EVENT_FIELD(const FAsyncPackage*, AsyncPackage) + UE_TRACE_EVENT_FIELD(uint64, RequestId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, LoadImportDependency) + UE_TRACE_EVENT_FIELD(const FAsyncPackage*, Package) + UE_TRACE_EVENT_FIELD(const FAsyncPackage*, DependentPackage) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, AsyncPackageLinkerAssociation) + UE_TRACE_EVENT_FIELD(const FAsyncPackage*, AsyncPackage) + UE_TRACE_EVENT_FIELD(const FLinkerLoad*, Linker) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, BeginAsyncPackageScope) + UE_TRACE_EVENT_FIELD(const FAsyncPackage*, AsyncPackage) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) + UE_TRACE_EVENT_FIELD(uint8, EventType) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, EndAsyncPackageScope) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, BeginFlushAsyncLoading) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint64, RequestId) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, EndFlushAsyncLoading) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) +UE_TRACE_EVENT_END() + +UE_TRACE_EVENT_BEGIN(LoadTime, QueueEvent) + UE_TRACE_EVENT_FIELD(uint64, Cycle) + UE_TRACE_EVENT_FIELD(const FAsyncPackage*, AsyncPackage) + UE_TRACE_EVENT_FIELD(uint32, ThreadId) + UE_TRACE_EVENT_FIELD(uint32, UserPriority) + UE_TRACE_EVENT_FIELD(uint32, PackageSerialNumber) + UE_TRACE_EVENT_FIELD(uint32, EventSystemPriority) + UE_TRACE_EVENT_FIELD(uint8, EventType) +UE_TRACE_EVENT_END(); + +UE_TRACE_EVENT_BEGIN(LoadTime, ClassInfo, Always) + UE_TRACE_EVENT_FIELD(const UClass*, Class) +UE_TRACE_EVENT_END() + +void FLoadTimeProfilerTracePrivate::Init(bool bStartEnabled) +{ + UE_TRACE_EVENT_IS_ENABLED(LoadTime, StartAsyncLoading); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, SuspendAsyncLoading); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, ResumeAsyncLoading); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, NewPackage); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, NewLinker); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, DestroyLinker); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, PackageSummary); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, BeginLinkerScope); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, EndLinkerScope); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, BeginCreateExport); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, EndCreateExport); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, BeginObjectScope); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, EndObjectScope); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, LinkerArchiveAssociation); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, BeginRequest); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, EndRequest); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, BeginLoadMap); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, EndLoadMap); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, NewStreamableHandle); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, DestroyStreamableHandle); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, BeginLoadStreamableHandle); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, EndLoadStreamableHandle); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, BeginWaitForStreamableHandle); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, EndWaitForStreamableHandle); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, NewAsyncPackage); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, DestroyAsyncPackage); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, StreamableHandleRequestAssociation); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, AsyncPackageRequestAssociation); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, LoadImportDependency); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, AsyncPackageLinkerAssociation); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, BeginAsyncPackageScope); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, EndAsyncPackageScope); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, BeginFlushAsyncLoading); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, EndFlushAsyncLoading); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, QueueEvent); + UE_TRACE_EVENT_IS_ENABLED(LoadTime, ClassInfo); + if (bStartEnabled) + { + Trace::ToggleEvent(TEXT("LoadTime"), true); + } +} + +void FLoadTimeProfilerTracePrivate::OutputStartAsyncLoading() +{ + UE_TRACE_LOG(LoadTime, StartAsyncLoading) + << StartAsyncLoading.Cycle(FPlatformTime::Cycles64()) + << StartAsyncLoading.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +PRAGMA_DISABLE_SHADOW_VARIABLE_WARNINGS +void FLoadTimeProfilerTracePrivate::OutputSuspendAsyncLoading() +{ + UE_TRACE_LOG(LoadTime, SuspendAsyncLoading) + << SuspendAsyncLoading.Cycle(FPlatformTime::Cycles64()); +} + +void FLoadTimeProfilerTracePrivate::OutputResumeAsyncLoading() +{ + UE_TRACE_LOG(LoadTime, ResumeAsyncLoading) + << ResumeAsyncLoading.Cycle(FPlatformTime::Cycles64()); +} +PRAGMA_ENABLE_SHADOW_VARIABLE_WARNINGS + +void FLoadTimeProfilerTracePrivate::OutputNewLinker(const FLinkerLoad* Linker) +{ + UE_TRACE_LOG(LoadTime, NewLinker) + << NewLinker.Linker(Linker); +} + +void FLoadTimeProfilerTracePrivate::OutputDestroyLinker(const FLinkerLoad* Linker) +{ + UE_TRACE_LOG(LoadTime, DestroyLinker) + << DestroyLinker.Linker(Linker); +} + +void FLoadTimeProfilerTracePrivate::OutputBeginRequest(uint64 RequestId) +{ + UE_TRACE_LOG(LoadTime, BeginRequest) + << BeginRequest.Cycle(FPlatformTime::Cycles64()) + << BeginRequest.RequestId(RequestId) + << BeginRequest.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +void FLoadTimeProfilerTracePrivate::OutputEndRequest(uint64 RequestId) +{ + UE_TRACE_LOG(LoadTime, EndRequest) + << EndRequest.Cycle(FPlatformTime::Cycles64()) + << EndRequest.RequestId(RequestId); +} + +void FLoadTimeProfilerTrace::OutputNewStreamableHandle(const FStreamableHandle* StreamableHandle, const TCHAR* DebugName, bool IsCombined) +{ + uint16 NameSize = (FCString::Strlen(DebugName) + 1) * sizeof(TCHAR); + UE_TRACE_LOG(LoadTime, NewStreamableHandle, NameSize) + << NewStreamableHandle.StreamableHandle(StreamableHandle) + << NewStreamableHandle.IsCombined(IsCombined) + << NewStreamableHandle.Attachment(DebugName, NameSize); +} + +void FLoadTimeProfilerTrace::OutputDestroyStreamableHandle(const FStreamableHandle* StreamableHandle) +{ + UE_TRACE_LOG(LoadTime, DestroyStreamableHandle) + << DestroyStreamableHandle.StreamableHandle(StreamableHandle); +} + +void FLoadTimeProfilerTrace::OutputBeginLoadStreamableHandle(const FStreamableHandle* StreamableHandle) +{ + UE_TRACE_LOG(LoadTime, BeginLoadStreamableHandle) + << BeginLoadStreamableHandle.StreamableHandle(StreamableHandle) + << BeginLoadStreamableHandle.Cycle(FPlatformTime::Cycles64()); +} + +void FLoadTimeProfilerTrace::OutputEndLoadStreamableHandle(const FStreamableHandle* StreamableHandle) +{ + UE_TRACE_LOG(LoadTime, EndLoadStreamableHandle) + << EndLoadStreamableHandle.StreamableHandle(StreamableHandle) + << EndLoadStreamableHandle.Cycle(FPlatformTime::Cycles64()); +} + +void FLoadTimeProfilerTracePrivate::OutputNewAsyncPackage(const FAsyncPackage* AsyncPackage, const TCHAR* PackageName) +{ + uint16 NameSize = (FCString::Strlen(PackageName) + 1) * sizeof(TCHAR); + UE_TRACE_LOG(LoadTime, NewAsyncPackage, NameSize) + << NewAsyncPackage.AsyncPackage(AsyncPackage) + << NewAsyncPackage.Attachment(PackageName, NameSize); +} + +void FLoadTimeProfilerTracePrivate::OutputDestroyAsyncPackage(const FAsyncPackage* AsyncPackage) +{ + UE_TRACE_LOG(LoadTime, DestroyAsyncPackage) + << DestroyAsyncPackage.AsyncPackage(AsyncPackage); +} + +void FLoadTimeProfilerTracePrivate::OutputPackageSummary(const FLinkerLoad* Linker, uint32 TotalHeaderSize, uint32 NameCount, uint32 ImportCount, uint32 ExportCount) +{ + UE_TRACE_LOG(LoadTime, PackageSummary) + << PackageSummary.Linker(Linker) + << PackageSummary.TotalHeaderSize(TotalHeaderSize) + << PackageSummary.NameCount(NameCount) + << PackageSummary.ImportCount(ImportCount) + << PackageSummary.ExportCount(ExportCount); +} + +void FLoadTimeProfilerTrace::OutputStreamableHandleRequestAssociation(const FStreamableHandle* StreamableHandle, uint64 RequestId) +{ + UE_TRACE_LOG(LoadTime, StreamableHandleRequestAssociation) + << StreamableHandleRequestAssociation.StreamableHandle(StreamableHandle) + << StreamableHandleRequestAssociation.RequestId(RequestId); +} + +void FLoadTimeProfilerTracePrivate::OutputAsyncPackageRequestAssociation(const FAsyncPackage* AsyncPackage, uint64 RequestId) +{ + UE_TRACE_LOG(LoadTime, AsyncPackageRequestAssociation) + << AsyncPackageRequestAssociation.AsyncPackage(AsyncPackage) + << AsyncPackageRequestAssociation.RequestId(RequestId); +} + +void FLoadTimeProfilerTracePrivate::OutputLoadImportDependency(const FAsyncPackage* Package, const FAsyncPackage* DependentPackage) +{ + UE_TRACE_LOG(LoadTime, LoadImportDependency) + << LoadImportDependency.Package(Package) + << LoadImportDependency.DependentPackage(DependentPackage); +} + +void FLoadTimeProfilerTracePrivate::OutputAsyncPackageLinkerAssociation(const FAsyncPackage* AsyncPackage, const FLinkerLoad* Linker) +{ + UE_TRACE_LOG(LoadTime, AsyncPackageLinkerAssociation) + << AsyncPackageLinkerAssociation.AsyncPackage(AsyncPackage) + << AsyncPackageLinkerAssociation.Linker(Linker); +} + +void FLoadTimeProfilerTracePrivate::OutputLinkerArchiveAssociation(const FLinkerLoad* Linker, const FArchive* Archive) +{ + UE_TRACE_LOG(LoadTime, LinkerArchiveAssociation) + << LinkerArchiveAssociation.Linker(Linker) + << LinkerArchiveAssociation.Archive(Archive); +} + +void FLoadTimeProfilerTracePrivate::OutputQueueEvent(const FAsyncPackage* AsyncPackage, ELoadTimeProfilerPackageEventType EventType, TAsyncLoadPriority UserPriority, int32 PackageSerialNumber, int32 EventSystemPriority) +{ + UE_TRACE_LOG(LoadTime, QueueEvent) + << QueueEvent.Cycle(FPlatformTime::Cycles64()) + << QueueEvent.AsyncPackage(AsyncPackage) + << QueueEvent.ThreadId(FPlatformTLS::GetCurrentThreadId()) + << QueueEvent.UserPriority(UserPriority) + << QueueEvent.PackageSerialNumber(PackageSerialNumber) + << QueueEvent.EventSystemPriority(EventSystemPriority) + << QueueEvent.EventType(EventType); +} + +void FLoadTimeProfilerTracePrivate::OutputClassInfo(const UClass* Class, const TCHAR* Name) +{ + uint16 NameSize = (FCString::Strlen(Name) + 1) * sizeof(TCHAR); + UE_TRACE_LOG(LoadTime, ClassInfo, NameSize) + << ClassInfo.Class(Class) + << ClassInfo.Attachment(Name, NameSize); +} + +FLoadTimeProfilerTracePrivate::FAsyncPackageScope::FAsyncPackageScope(const FAsyncPackage* AsyncPackage, ELoadTimeProfilerPackageEventType EventType) +{ + UE_TRACE_LOG(LoadTime, BeginAsyncPackageScope) + << BeginAsyncPackageScope.AsyncPackage(AsyncPackage) + << BeginAsyncPackageScope.Cycle(FPlatformTime::Cycles64()) + << BeginAsyncPackageScope.ThreadId(FPlatformTLS::GetCurrentThreadId()) + << BeginAsyncPackageScope.EventType(EventType); +} + +FLoadTimeProfilerTracePrivate::FAsyncPackageScope::~FAsyncPackageScope() +{ + UE_TRACE_LOG(LoadTime, EndAsyncPackageScope) + << EndAsyncPackageScope.Cycle(FPlatformTime::Cycles64()) + << EndAsyncPackageScope.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +FLoadTimeProfilerTracePrivate::FCreateExportScope::FCreateExportScope(const FLinkerLoad* Linker, uint64 SerialOffset, uint64 SerialSize, bool IsAsset, const UObject* const* InObject) + : Object(InObject) +{ + UE_TRACE_LOG(LoadTime, BeginCreateExport) + << BeginCreateExport.Cycle(FPlatformTime::Cycles64()) + << BeginCreateExport.Linker(Linker) + << BeginCreateExport.SerialOffset(SerialOffset) + << BeginCreateExport.SerialSize(SerialSize) + << BeginCreateExport.ThreadId(FPlatformTLS::GetCurrentThreadId()) + << BeginCreateExport.IsAsset(IsAsset); +} + +FLoadTimeProfilerTracePrivate::FCreateExportScope::~FCreateExportScope() +{ + UE_TRACE_LOG(LoadTime, EndCreateExport) + << EndCreateExport.Cycle(FPlatformTime::Cycles64()) + << EndCreateExport.Object(*Object) + << EndCreateExport.Class(*Object ? (*Object)->GetClass() : nullptr) + << EndCreateExport.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +FLoadTimeProfilerTracePrivate::FObjectScope::FObjectScope(const UObject* Object, ELoadTimeProfilerObjectEventType EventType) +{ + UE_TRACE_LOG(LoadTime, BeginObjectScope) + << BeginObjectScope.Cycle(FPlatformTime::Cycles64()) + << BeginObjectScope.Object(Object) + << BeginObjectScope.ThreadId(FPlatformTLS::GetCurrentThreadId()) + << BeginObjectScope.EventType(EventType); +} + +FLoadTimeProfilerTracePrivate::FObjectScope::~FObjectScope() +{ + UE_TRACE_LOG(LoadTime, EndObjectScope) + << EndObjectScope.Cycle(FPlatformTime::Cycles64()) + << EndObjectScope.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +FLoadTimeProfilerTracePrivate::FLinkerScope::FLinkerScope(const FLinkerLoad* Linker) +{ + UE_TRACE_LOG(LoadTime, BeginLinkerScope) + << BeginLinkerScope.Linker(Linker) + << BeginLinkerScope.Cycle(FPlatformTime::Cycles64()) + << BeginLinkerScope.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +FLoadTimeProfilerTracePrivate::FLinkerScope::~FLinkerScope() +{ + UE_TRACE_LOG(LoadTime, EndLinkerScope) + << EndLinkerScope.Cycle(FPlatformTime::Cycles64()) + << EndLinkerScope.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +FLoadTimeProfilerTracePrivate::FFlushAsyncLoadingScope::FFlushAsyncLoadingScope(uint64 RequestId) +{ + UE_TRACE_LOG(LoadTime, BeginFlushAsyncLoading) + << BeginFlushAsyncLoading.Cycle(FPlatformTime::Cycles64()) + << BeginFlushAsyncLoading.RequestId(RequestId) + << BeginFlushAsyncLoading.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +FLoadTimeProfilerTracePrivate::FFlushAsyncLoadingScope::~FFlushAsyncLoadingScope() +{ + UE_TRACE_LOG(LoadTime, EndFlushAsyncLoading) + << EndFlushAsyncLoading.Cycle(FPlatformTime::Cycles64()) + << EndFlushAsyncLoading.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +FLoadTimeProfilerTrace::FWaitForStreamableHandleScope::FWaitForStreamableHandleScope(const FStreamableHandle* StreamableHandle) +{ + UE_TRACE_LOG(LoadTime, BeginWaitForStreamableHandle) + << BeginWaitForStreamableHandle.Cycle(FPlatformTime::Cycles64()) + << BeginWaitForStreamableHandle.StreamableHandle(StreamableHandle) + << BeginWaitForStreamableHandle.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +FLoadTimeProfilerTrace::FWaitForStreamableHandleScope::~FWaitForStreamableHandleScope() +{ + UE_TRACE_LOG(LoadTime, EndWaitForStreamableHandle) + << EndWaitForStreamableHandle.Cycle(FPlatformTime::Cycles64()) + << EndWaitForStreamableHandle.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +FLoadTimeProfilerTrace::FLoadMapScope::FLoadMapScope(const TCHAR* Name) +{ + uint16 NameSize = (FCString::Strlen(Name) + 1) * sizeof(TCHAR); + UE_TRACE_LOG(LoadTime, BeginLoadMap, NameSize) + << BeginLoadMap.Cycle(FPlatformTime::Cycles64()) + << BeginLoadMap.ThreadId(FPlatformTLS::GetCurrentThreadId()) + << BeginLoadMap.Attachment(Name, NameSize); +} + +FLoadTimeProfilerTrace::FLoadMapScope::~FLoadMapScope() +{ + UE_TRACE_LOG(LoadTime, EndLoadMap) + << EndLoadMap.Cycle(FPlatformTime::Cycles64()) + << EndLoadMap.ThreadId(FPlatformTLS::GetCurrentThreadId()); +} + +#endif \ No newline at end of file diff --git a/Engine/Source/Runtime/CoreUObject/Private/Serialization/LoadTimeTracePrivate.h b/Engine/Source/Runtime/CoreUObject/Private/Serialization/LoadTimeTracePrivate.h new file mode 100644 index 000000000000..65887e9e737f --- /dev/null +++ b/Engine/Source/Runtime/CoreUObject/Private/Serialization/LoadTimeTracePrivate.h @@ -0,0 +1,152 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Serialization/LoadTimeTrace.h" + +#if LOADTIMEPROFILERTRACE_ENABLED + +#include "UObject/UObjectGlobals.h" + +class FArchive; +struct FAsyncPackage; +class FLinkerLoad; +class UObject; +class UPackage; + +struct FLoadTimeProfilerTracePrivate +{ + static void Init(bool bStartEnabled); + static void OutputStartAsyncLoading(); + static void OutputSuspendAsyncLoading(); + static void OutputResumeAsyncLoading(); + static void OutputNewLinker(const FLinkerLoad* Linker); + static void OutputDestroyLinker(const FLinkerLoad* Linker); + static void OutputNewAsyncPackage(const FAsyncPackage* AsyncPackage, const TCHAR* PackageName); + static void OutputDestroyAsyncPackage(const FAsyncPackage* AsyncPackage); + static void OutputBeginRequest(uint64 RequestId); + static void OutputEndRequest(uint64 RequestId); + static void OutputPackageSummary(const FLinkerLoad* Linker, uint32 TotalHeaderSize, uint32 NameCount, uint32 ImportCount, uint32 ExportCount); + static void OutputLoadImportDependency(const FAsyncPackage* Package, const FAsyncPackage* DependentPackage); + static void OutputAsyncPackageRequestAssociation(const FAsyncPackage* AsyncPackage, uint64 RequestId); + static void OutputAsyncPackageLinkerAssociation(const FAsyncPackage* AsyncPackage, const FLinkerLoad* Linker); + static void OutputLinkerArchiveAssociation(const FLinkerLoad* Linker, const FArchive* Archive); + static void OutputQueueEvent(const FAsyncPackage* AsyncPackage, ELoadTimeProfilerPackageEventType EventType, TAsyncLoadPriority UserPriority, int32 PackageSerialNumber, int32 EventSystemPriority); + static void OutputClassInfo(const UClass* Class, const TCHAR* Name); + + struct FAsyncPackageScope + { + FAsyncPackageScope(const FAsyncPackage* AsyncPackage, ELoadTimeProfilerPackageEventType EventType); + ~FAsyncPackageScope(); + }; + + struct FCreateExportScope + { + FCreateExportScope(const FLinkerLoad* Linker, uint64 SerialOffset, uint64 SerialSize, bool IsAsset, const UObject* const* InObject); + ~FCreateExportScope(); + + private: + const UObject* const* Object; + }; + + struct FObjectScope + { + FObjectScope(const UObject* Object, ELoadTimeProfilerObjectEventType EventType); + ~FObjectScope(); + }; + + struct FLinkerScope + { + FLinkerScope(const FLinkerLoad* Linker); + ~FLinkerScope(); + }; + + struct FFlushAsyncLoadingScope + { + FFlushAsyncLoadingScope(uint64 RequestId); + ~FFlushAsyncLoadingScope(); + }; +}; + +#define TRACE_LOADTIME_START_ASYNC_LOADING() \ + FLoadTimeProfilerTracePrivate::OutputStartAsyncLoading(); + +#define TRACE_LOADTIME_SUSPEND_ASYNC_LOADING() \ + FLoadTimeProfilerTracePrivate::OutputSuspendAsyncLoading(); + +#define TRACE_LOADTIME_RESUME_ASYNC_LOADING() \ + FLoadTimeProfilerTracePrivate::OutputResumeAsyncLoading(); + +#define TRACE_LOADTIME_NEW_LINKER(Linker) \ + FLoadTimeProfilerTracePrivate::OutputNewLinker(Linker); + +#define TRACE_LOADTIME_DESTROY_LINKER(Linker) \ + FLoadTimeProfilerTracePrivate::OutputDestroyLinker(Linker); + +#define TRACE_LOADTIME_PACKAGE_SUMMARY(Linker, TotalHeaderSize, NameCount, ImportCount, ExportCount) \ + FLoadTimeProfilerTracePrivate::OutputPackageSummary(Linker, TotalHeaderSize, NameCount, ImportCount, ExportCount); + +#define TRACE_LOADTIME_LINKER_ARCHIVE_ASSOCIATION(Linker, Archive) \ + FLoadTimeProfilerTracePrivate::OutputLinkerArchiveAssociation(Linker, Archive); + +#define TRACE_LOADTIME_BEGIN_REQUEST(RequestId) \ + FLoadTimeProfilerTracePrivate::OutputBeginRequest(RequestId); + +#define TRACE_LOADTIME_END_REQUEST(RequestId) \ + FLoadTimeProfilerTracePrivate::OutputEndRequest(RequestId); + +#define TRACE_LOADTIME_NEW_ASYNC_PACKAGE(AsyncPackage, PackageName) \ + FLoadTimeProfilerTracePrivate::OutputNewAsyncPackage(AsyncPackage, PackageName) + +#define TRACE_LOADTIME_DESTROY_ASYNC_PACKAGE(AsyncPackage) \ + FLoadTimeProfilerTracePrivate::OutputDestroyAsyncPackage(AsyncPackage); + +#define TRACE_LOADTIME_ASYNC_PACKAGE_REQUEST_ASSOCIATION(AsyncPackage, RequestId) \ + FLoadTimeProfilerTracePrivate::OutputAsyncPackageRequestAssociation(AsyncPackage, RequestId); + +#define TRACE_LOADTIME_ASYNC_PACKAGE_LINKER_ASSOCIATION(AsyncPackage, Linker) \ + FLoadTimeProfilerTracePrivate::OutputAsyncPackageLinkerAssociation(AsyncPackage, Linker); + +#define TRACE_LOADTIME_QUEUE_EVENT(AsyncPackage, EventType, UserPriority, PackageSerialNumber, EventSystemPriority) +// FLoadTimeProfilerTracePrivate::OutputQueueEvent(AsyncPackage, EventType, UserPriority, PackageSerialNumber, EventSystemPriority); + +#define TRACE_LOADTIME_ASYNC_PACKAGE_SCOPE(AsyncPackage, EventType) \ + FLoadTimeProfilerTracePrivate::FAsyncPackageScope __LoadTimeTraceAsyncPackageScope(AsyncPackage, EventType); + +#define TRACE_LOADTIME_CREATE_EXPORT_SCOPE(Linker, Object, SerialOffset, SerialSize, IsAsset) \ + FLoadTimeProfilerTracePrivate::FCreateExportScope __LoadTimeTraceCreateExportScope(Linker, SerialOffset, SerialSize, IsAsset, Object); + +#define TRACE_LOADTIME_OBJECT_SCOPE(Object, EventType) \ + FLoadTimeProfilerTracePrivate::FObjectScope __LoadTimeTraceObjectScope(Object, EventType); + +#define TRACE_LOADTIME_FLUSH_ASYNCLOADING_SCOPE(RequestId) \ + FLoadTimeProfilerTracePrivate::FFlushAsyncLoadingScope __LoadTimeTraceFlushAsyncLoadingScope(RequestId); + +#define TRACE_LOADTIME_CLASS_INFO(Class, Name) \ + FLoadTimeProfilerTracePrivate::OutputClassInfo(Class, Name); + +#else + +#define TRACE_LOADTIME_START_ASYNC_LOADING(...) +#define TRACE_LOADTIME_SUSPEND_ASYNC_LOADING(...) +#define TRACE_LOADTIME_RESUME_ASYNC_LOADING(...) +#define TRACE_LOADTIME_NEW_PACKAGE(...) +#define TRACE_LOADTIME_NEW_LINKER(...) +#define TRACE_LOADTIME_DESTROY_LINKER(...) +#define TRACE_LOADTIME_PACKAGE_SUMMARY(...) +#define TRACE_LOADTIME_LINKER_ARCHIVE_ASSOCIATION(...) +#define TRACE_LOADTIME_BEGIN_REQUEST(...) +#define TRACE_LOADTIME_END_REQUEST(...) +#define TRACE_LOADTIME_NEW_ASYNC_PACKAGE(...) +#define TRACE_LOADTIME_DESTROY_ASYNC_PACKAGE(...) +#define TRACE_LOADTIME_ASYNC_PACKAGE_REQUEST_ASSOCIATION(...) +#define TRACE_LOADTIME_ASYNC_PACKAGE_LINKER_ASSOCIATION(...) +#define TRACE_LOADTIME_FLUSH_ASYNCLOADING_SCOPE(...) +#define TRACE_LOADTIME_QUEUE_EVENT(...) +#define TRACE_LOADTIME_ASYNC_PACKAGE_SCOPE(...) +#define TRACE_LOADTIME_TIMER_SCOPE(...) +#define TRACE_LOADTIME_CREATE_EXPORT_SCOPE(...) +#define TRACE_LOADTIME_OBJECT_SCOPE(...) +#define TRACE_LOADTIME_CLASS_INFO(...) + +#endif \ No newline at end of file diff --git a/Engine/Source/Runtime/CoreUObject/Private/Serialization/TraceReferences.cpp b/Engine/Source/Runtime/CoreUObject/Private/Serialization/TraceReferences.cpp index 277410191e0e..575e54f74422 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Serialization/TraceReferences.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/Serialization/TraceReferences.cpp @@ -3,6 +3,8 @@ #include "Serialization/TraceReferences.h" #include "UObject/UnrealType.h" +PRAGMA_DISABLE_DEPRECATION_WARNINGS + // This traces referenced/referencer of an object using FArchiveObjectGraph FTraceReferences::FTraceReferences( bool bIncludeTransients, EObjectFlags KeepFlags ) : ArchiveObjectGraph ( bIncludeTransients, KeepFlags ) @@ -185,3 +187,4 @@ void FTraceReferences::GetReferencedInternal( UObject* CurrentObject, TArrayGetAuthoredNameForField(this); + } + return FString(); +} + void UField::Bind() { } @@ -220,10 +232,7 @@ struct FDisplayNameHelper if (auto Property = dynamic_cast(&Object)) { - if (auto OwnerStruct = Property->GetOwnerStruct()) - { - return OwnerStruct->PropertyNameToDisplayName(Property->GetFName()); - } + return Property->GetAuthoredName(); } return Object.GetName(); @@ -244,12 +253,8 @@ FText UField::GetDisplayNameText() const const FString Key = GetFullGroupName(false); - FString NativeDisplayName; - if( HasMetaData(NAME_DisplayName) ) - { - NativeDisplayName = GetMetaData(NAME_DisplayName); - } - else + FString NativeDisplayName = GetMetaData(NAME_DisplayName); + if (NativeDisplayName.IsEmpty()) { NativeDisplayName = FName::NameToDisplayString(FDisplayNameHelper::Get(*this), IsA()); } @@ -499,12 +504,12 @@ IMPLEMENT_CORE_INTRINSIC_CLASS(UField, UObject, // // Constructors. // -UStruct::UStruct( EStaticConstructor, int32 InSize, EObjectFlags InFlags ) +UStruct::UStruct( EStaticConstructor, int32 InSize, int32 InMinAlignment, EObjectFlags InFlags ) : UField ( EC_StaticConstructor, InFlags ) , SuperStruct ( nullptr ) , Children ( NULL ) , PropertiesSize ( InSize ) -, MinAlignment ( 1 ) +, MinAlignment ( InMinAlignment ) , PropertyLink ( NULL ) , RefLink ( NULL ) , DestructorLink ( NULL ) @@ -851,10 +856,6 @@ void UStruct::DestroyStruct(void* Dest, int32 ArrayDim) const } } -void UStruct::SerializeBin(FArchive& Ar, void* Data) const -{ - SerializeBin(FStructuredArchiveFromArchive(Ar).GetSlot(), Data); -} // // Serialize all of the class's data that belongs in a particular // bin and resides in Data. @@ -913,11 +914,6 @@ void UStruct::SerializeBinEx( FStructuredArchive::FSlot Slot, void* Data, void c } } -void UStruct::SerializeTaggedProperties(FArchive& Ar, uint8* Data, UStruct* DefaultsStruct, uint8* Defaults, const UObject* BreakRecursionIfFullyLoad) const -{ - SerializeTaggedProperties(FStructuredArchiveFromArchive(Ar).GetSlot(), Data, DefaultsStruct, Defaults, BreakRecursionIfFullyLoad); -} - void UStruct::SerializeTaggedProperties(FStructuredArchive::FSlot Slot, uint8* Data, UStruct* DefaultsStruct, uint8* Defaults, const UObject* BreakRecursionIfFullyLoad) const { FArchive& UnderlyingArchive = Slot.GetUnderlyingArchive(); @@ -929,21 +925,7 @@ void UStruct::SerializeTaggedProperties(FStructuredArchive::FSlot Slot, uint8* D if( UnderlyingArchive.IsLoading() ) { // Load tagged properties. - TArray FieldNames; - - TOptional PropertiesRecord; - TOptional PropertiesStream; - - if (UnderlyingArchive.IsTextFormat()) - { - PropertiesRecord.Emplace(Slot.EnterRecord_TextOnly(FieldNames)); - } - else - { - PropertiesStream.Emplace(Slot.EnterStream()); - } - - int32 CurrentFieldNameIdx = UnderlyingArchive.IsTextFormat() ? 0 : -1; + FStructuredArchive::FStream PropertiesStream = Slot.EnterStream(); // This code assumes that properties are loaded in the same order they are saved in. This removes a n^2 search // and makes it an O(n) when properties are saved in the same order as they are loaded (default case). In the @@ -953,9 +935,9 @@ void UStruct::SerializeTaggedProperties(FStructuredArchive::FSlot Slot, uint8* D int32 RemainingArrayDim = Property ? Property->ArrayDim : 0; // Load all stored properties, potentially skipping unknown ones. - while (CurrentFieldNameIdx < FieldNames.Num()) + while (true) { - FStructuredArchive::FRecord PropertyRecord = UnderlyingArchive.IsTextFormat() ? PropertiesRecord->EnterRecord(FIELD_NAME(*FieldNames[CurrentFieldNameIdx++])) : PropertiesStream->EnterElement().EnterRecord(); + FStructuredArchive::FRecord PropertyRecord = PropertiesStream.EnterElement().EnterRecord(); FPropertyTag Tag; PropertyRecord << NAMED_FIELD(Tag); @@ -1050,12 +1032,11 @@ void UStruct::SerializeTaggedProperties(FStructuredArchive::FSlot Slot, uint8* D RemainingArrayDim = Property ? Property->ArrayDim : 0; } -#if WITH_EDITOR + if (!Property) { Property = CustomFindProperty(Tag.Name); } -#endif // WITH_EDITOR FName PropID = Property ? Property->GetID() : NAME_None; FName ArrayInnerID = NAME_None; @@ -1150,7 +1131,7 @@ void UStruct::SerializeTaggedProperties(FStructuredArchive::FSlot Slot, uint8* D } else { - FStructuredArchive::FRecord PropertiesRecord = Slot.EnterRecord(); + FStructuredArchive::FStream PropertiesStream = Slot.EnterStream(); check(UnderlyingArchive.IsSaving() || UnderlyingArchive.IsCountingMemory()); @@ -1198,7 +1179,7 @@ void UStruct::SerializeTaggedProperties(FStructuredArchive::FSlot Slot, uint8* D Tag.SetPropertyGuid(PropertyGuid); } - FStructuredArchive::FRecord PropertyRecord = PropertiesRecord.EnterRecord(FIELD_NAME(*Tag.Name.ToString())); + FStructuredArchive::FRecord PropertyRecord = PropertiesStream.EnterElement().EnterRecord(); PropertyRecord << NAMED_FIELD(Tag); @@ -1246,11 +1227,8 @@ void UStruct::SerializeTaggedProperties(FStructuredArchive::FSlot Slot, uint8* D } } - if (!UnderlyingArchive.IsTextFormat()) - { - static FName Temp(NAME_None); - UnderlyingArchive << Temp; - } + static FName Temp(NAME_None); + PropertiesStream.EnterElement().EnterRecord().EnterField(FIELD_NAME_TEXT("Tag")).EnterRecord() << NAMED_ITEM("Name", Temp); } } void UStruct::FinishDestroy() @@ -1487,6 +1465,21 @@ void UStruct::SetSuperStruct(UStruct* NewSuperStruct) #endif } +FString UStruct::PropertyNameToDisplayName(FName InName) const +{ + const UField* FoundField = FindField(this, InName); + return GetAuthoredNameForField(FoundField); +} + +FString UStruct::GetAuthoredNameForField(const UField* Field) const +{ + if (Field) + { + return Field->GetName(); + } + return FString(); +} + #if WITH_EDITOR || HACK_HEADER_GENERATOR bool UStruct::GetBoolMetaDataHierarchical(const FName& Key) const { @@ -1901,8 +1894,8 @@ bool FindConstructorUninitialized(UStruct* BaseClass,uint8* Data,uint8* Defaults #endif -UScriptStruct::UScriptStruct( EStaticConstructor, int32 InSize, EObjectFlags InFlags ) - : UStruct( EC_StaticConstructor, InSize, InFlags ) +UScriptStruct::UScriptStruct( EStaticConstructor, int32 InSize, int32 InAlignment, EObjectFlags InFlags ) + : UStruct( EC_StaticConstructor, InSize, InAlignment, InFlags ) , StructFlags(STRUCT_NoFlags) #if HACK_HEADER_GENERATOR , StructMacroDeclaredLineNumber(INDEX_NONE) @@ -2009,7 +2002,7 @@ void UScriptStruct::PrepareCppStructOps() } check(!(StructFlags & STRUCT_ComputedFlags)); - if (CppStructOps->HasSerializer()) + if (CppStructOps->HasSerializer() || CppStructOps->HasStructuredSerializer()) { UE_LOG(LogClass, Verbose, TEXT("Native struct %s has a custom serializer."),*GetName()); StructFlags = EStructFlags(StructFlags | STRUCT_SerializeNative ); @@ -2348,13 +2341,15 @@ void UScriptStruct::ExportText(FString& ValueStr, const void* Value, const void* ValueStr += TEXT(","); } + const FString PropertyName = (PortFlags & PPF_ExternalEditor) != 0 ? *It->GetAuthoredName() : It->GetName(); + if (It->ArrayDim == 1) { - ValueStr += FString::Printf(TEXT("%s="), *It->GetName()); + ValueStr += FString::Printf(TEXT("%s="), *PropertyName); } else { - ValueStr += FString::Printf(TEXT("%s[%i]="), *It->GetName(), Index); + ValueStr += FString::Printf(TEXT("%s[%i]="), *PropertyName, Index); } ValueStr += InnerValue; } @@ -3887,11 +3882,6 @@ bool UClass::ImplementsInterface( const class UClass* SomeInterface ) const return false; } -void UClass::SerializeDefaultObject(UObject* Object, FArchive& Ar) -{ - SerializeDefaultObject(Object, FStructuredArchiveFromArchive(Ar).GetSlot()); -} - /** serializes the passed in object as this class's default object using the given archive * @param Object the object to serialize as default * @param Ar the archive to serialize from @@ -4008,9 +3998,9 @@ UClass* UClass::FindCommonBase(const TArray& InClasses) return CommonClass; } -bool UClass::IsFunctionImplementedInBlueprint(FName InFunctionName) const +bool UClass::IsFunctionImplementedInScript(FName InFunctionName) const { - // Implemented in UBlueprintGeneratedClass + // Implemented in classes such as UBlueprintGeneratedClass return false; } @@ -4092,6 +4082,7 @@ UClass::UClass EStaticConstructor, FName InName, uint32 InSize, + uint32 InAlignment, EClassFlags InClassFlags, EClassCastFlags InClassCastFlags, const TCHAR* InConfigName, @@ -4100,7 +4091,7 @@ UClass::UClass ClassVTableHelperCtorCallerType InClassVTableHelperCtorCaller, ClassAddReferencedObjectsType InClassAddReferencedObjects ) -: UStruct ( EC_StaticConstructor, InSize, InFlags ) +: UStruct ( EC_StaticConstructor, InSize, InAlignment, InFlags ) , ClassConstructor ( InClassConstructor ) , ClassVTableHelperCtorCaller(InClassVTableHelperCtorCaller) , ClassAddReferencedObjects( InClassAddReferencedObjects ) @@ -4556,6 +4547,7 @@ void GetPrivateStaticClassBody( UClass*& ReturnClass, void(*RegisterNativeFunc)(), uint32 InSize, + uint32 InAlignment, EClassFlags InClassFlags, EClassCastFlags InClassCastFlags, const TCHAR* InConfigName, @@ -4615,6 +4607,7 @@ void GetPrivateStaticClassBody( EC_StaticConstructor, Name, InSize, + InAlignment, InClassFlags, InClassCastFlags, InConfigName, @@ -4634,6 +4627,7 @@ void GetPrivateStaticClassBody( EC_StaticConstructor, Name, InSize, + InAlignment, InClassFlags|CLASS_CompiledFromBlueprint, InClassCastFlags, InConfigName, @@ -5220,6 +5214,7 @@ UDynamicClass::UDynamicClass( EStaticConstructor, FName InName, uint32 InSize, + uint32 InAlignment, EClassFlags InClassFlags, EClassCastFlags InClassCastFlags, const TCHAR* InConfigName, @@ -5231,6 +5226,7 @@ UDynamicClass::UDynamicClass( EC_StaticConstructor , InName , InSize +, InAlignment , InClassFlags , InClassCastFlags , InConfigName diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/CoreNet.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/CoreNet.cpp index d29adabc8127..9a2b9638156f 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/CoreNet.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/CoreNet.cpp @@ -311,13 +311,14 @@ bool UPackageMap::StaticSerializeName(FArchive& Ar, FName& InName) } else if (Ar.IsSaving()) { - uint8 bHardcoded = InName.GetComparisonIndex() <= MAX_NETWORKED_HARDCODED_NAME; + const EName* InEName = InName.ToEName(); + uint8 bHardcoded = InEName && ShouldReplicateAsInteger(*InEName); Ar.SerializeBits(&bHardcoded, 1); - if (bHardcoded) + if (bHardcoded && /* silence static analyzer */ InEName) { // send by hardcoded index checkSlow(InName.GetNumber() <= 0); // hardcoded names should never have a Number - uint32 NameIndex = uint32(InName.GetComparisonIndex()); + uint32 NameIndex = *InEName; Ar.SerializeIntPacked(NameIndex); } else diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/Enum.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/Enum.cpp index b5bccdce05c9..be95eaa9e64b 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/Enum.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/Enum.cpp @@ -414,6 +414,17 @@ FText UEnum::GetDisplayNameTextByValue(int64 Value) const return GetDisplayNameTextByIndex(Index); } +FString UEnum::GetAuthoredNameStringByIndex(int32 InIndex) const +{ + return GetNameStringByIndex(InIndex); +} + +FString UEnum::GetAuthoredNameStringByValue(int64 Value) const +{ + int32 Index = GetIndexByValue(Value); + return GetAuthoredNameStringByIndex(Index); +} + int32 UEnum::GetIndexByNameString(const FString& InSearchString, EGetByNameFlags Flags) const { ENameCase NameComparisonMethod = !!(Flags & EGetByNameFlags::CaseSensitive) ? ENameCase ::CaseSensitive : ENameCase ::IgnoreCase; @@ -469,6 +480,9 @@ int32 UEnum::GetIndexByNameString(const FString& InSearchString, EGetByNameFlags FName SearchName = FName(*SearchEnumEntryString); FName ModifiedName = FName(*ModifiedEnumEntryString); + // Check authored name, but only if this is a subclass of Enum that might have implemented it + const bool bCheckAuthoredName = !!(Flags & EGetByNameFlags::CheckAuthoredName) && (GetClass() != UEnum::StaticClass()); + const int32 Count = Names.Num(); for (int32 Counter = 0; Counter < Count; ++Counter) { @@ -476,6 +490,16 @@ int32 UEnum::GetIndexByNameString(const FString& InSearchString, EGetByNameFlags { return Counter; } + + if (bCheckAuthoredName) + { + FString AuthoredName = GetAuthoredNameStringByIndex(Counter); + + if (AuthoredName.Equals(SearchEnumEntryString, StringComparisonMethod) || AuthoredName.Equals(ModifiedEnumEntryString, StringComparisonMethod)) + { + return Counter; + } + } } if (!InSearchString.Equals(SearchEnumEntryString, StringComparisonMethod)) diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/EnumProperty.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/EnumProperty.cpp index dd65a098738c..0632900f89ce 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/EnumProperty.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/EnumProperty.cpp @@ -238,6 +238,10 @@ void UEnumProperty::ExportTextItem(FString& ValueStr, const void* PropertyValue, { ValueStr += Enum->GetDisplayNameTextByValue(Value).ToString(); } + else if (PortFlags & PPF_ExternalEditor) + { + ValueStr += Enum->GetAuthoredNameStringByValue(Value); + } else { ValueStr += Enum->GetNameStringByValue(Value); @@ -264,7 +268,7 @@ const TCHAR* UEnumProperty::ImportText_Internal(const TCHAR* InBuffer, void* Dat FString Temp; if (const TCHAR* Buffer = UPropertyHelpers::ReadToken(InBuffer, Temp, true)) { - int32 EnumIndex = Enum->GetIndexByName(*Temp); + int32 EnumIndex = Enum->GetIndexByName(*Temp, EGetByNameFlags::CheckAuthoredName); if (EnumIndex == INDEX_NONE && (Temp.IsNumeric() && !Algo::Find(Temp, TEXT('.')))) { int64 EnumValue = INDEX_NONE; diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/GarbageCollection.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/GarbageCollection.cpp index 74459df1f70c..ef6e61a31217 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/GarbageCollection.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/GarbageCollection.cpp @@ -23,8 +23,11 @@ #include "UObject/UObjectClusters.h" #include "HAL/LowLevelMemTracker.h" #include "UObject/GarbageCollectionVerification.h" +#include "UObject/Package.h" #include "Async/ParallelFor.h" #include "ProfilingDebugging/CsvProfiler.h" +#include "HAL/Runnable.h" +#include "HAL/RunnableThread.h" /*----------------------------------------------------------------------------- Garbage collection. @@ -37,8 +40,6 @@ DEFINE_LOG_CATEGORY(LogGarbage); /** Object count during last mark phase */ FThreadSafeCounter GObjectCountDuringLastMarkPhase; -/** Count of objects purged since last mark phase */ -int32 GPurgedObjectCountSinceLastMarkPhase = 0; /** Whether incremental object purge is in progress */ bool GObjIncrementalPurgeIsInProgress = false; /** Whether GC is currently routing BeginDestroy to objects */ @@ -57,13 +58,9 @@ static int32 GGCObjectsPendingDestructionCount = 0; /** Whether we need to purge objects or not. */ static bool GObjPurgeIsRequired = false; /** Current object index for incremental purge. */ -static FRawObjectIterator GObjCurrentPurgeObjectIndex; +static int32 GObjCurrentPurgeObjectIndex = 0; /** Current object index for incremental purge. */ static bool GObjCurrentPurgeObjectIndexNeedsReset = true; -static bool GObjCurrentPurgeObjectIndexResetPastPermanent = false; - -/** Whether we are currently purging an object in the GC purge pass. */ -static bool GIsPurgingObject = false; /** Contains a list of objects that stayed marked as unreachable after the last reachability analysis */ static TArray GUnreachableObjects; @@ -137,12 +134,6 @@ bool IsGarbageCollectionWaiting() return FGCCSyncObject::Get().IsGCWaiting(); } -/** Called on shutdown to free GC memory */ -void CleanupGCArrayPools() -{ - FGCArrayPool::Get().Cleanup(); -} - // Minimum number of objects to spawn a GC sub-task for static int32 GMinDesiredObjectsPerSubTask = 128; static FAutoConsoleVariableRef CVarMinDesiredObjectsPerSubTask( @@ -160,6 +151,14 @@ static FAutoConsoleVariableRef CIncrementalBeginDestroyEnabled( ECVF_Default ); +static int32 GMultithreadedDestructionEnabled = 1; +static FAutoConsoleVariableRef CMultithreadedDestructionEnabled( + TEXT("gc.MultithreadedDestructionEnabled"), + GMultithreadedDestructionEnabled, + TEXT("If true, the engine will free objects' memory from a worker thread"), + ECVF_Default +); + #if PERF_DETAILED_PER_CLASS_GC_STATS /** Map from a UClass' FName to the number of objects that were purged during the last purge phase of this class. */ static TMap GClassToPurgeCountMap; @@ -240,6 +239,273 @@ static void LogClassCountInfo( const TCHAR* LogText, TMap& C }; #endif +/** + * Helper class for destroying UObjects on a worker thread + */ +class FAsyncPurge : public FRunnable +{ + /** Thread to run the worker FRunnable on. Destroys objects. */ + volatile FRunnableThread* Thread; + /** Id of the worker thread */ + uint32 AsyncPurgeThreadId; + /** Stops this thread */ + FThreadSafeCounter StopTaskCounter; + /** Event that triggers the UObject destruction */ + FEvent* BeginPurgeEvent; + /** Event that signales the UObject destruction is finished */ + FEvent* FinishedPurgeEvent; + /** Current index into the global unreachable objects array (GUnreachableObjects) of the object being destroyed */ + int32 ObjCurrentPurgeObjectIndex; + /** Number of unreachable objects the last time single-threaded tick was called */ + int32 LastUnreachableObjectsCount; + /** A list of objects that were not thread safe to destroy on the worker thread */ + TLockFreePointerListUnordered GameThreadObjects; + /** Stats for the number of objects destroyed */ + int32 ObjectsDestroyedSinceLastMarkPhase; + + /** [PURGE/GAME THREAD] Destroys objects that are unreachable */ + bool TickDestroyObjects(bool bUseTimeLimit, float TimeLimit, double StartTime) + { + const int32 TimeLimitEnforcementGranularityForDeletion = 100; + int32 ProcessedObjectsCount = 0; + bool bFinishedDestroyingObjects = true; + + while (ObjCurrentPurgeObjectIndex < GUnreachableObjects.Num()) + { + FUObjectItem* ObjectItem = GUnreachableObjects[ObjCurrentPurgeObjectIndex]; + check(ObjectItem->IsUnreachable()); + + UObject* Object = (UObject*)ObjectItem->Object; + check(Object->HasAllFlags(RF_FinishDestroyed | RF_BeginDestroyed)); + if (!Thread || Object->IsDestructionThreadSafe()) + { + Object->~UObject(); + GUObjectAllocator.FreeUObject(Object); + } + else + { + // This object will be destroyed incrementally on the game thread + GameThreadObjects.Push(Object); + } + ++ProcessedObjectsCount; + ++ObjectsDestroyedSinceLastMarkPhase; + ++ObjCurrentPurgeObjectIndex; + + // Time slicing when running on the game thread + if (bUseTimeLimit && (ProcessedObjectsCount == TimeLimitEnforcementGranularityForDeletion)) + { + ProcessedObjectsCount = 0; + if ((FPlatformTime::Seconds() - StartTime) > TimeLimit) + { + bFinishedDestroyingObjects = false; + break; + } + } + } + return bFinishedDestroyingObjects; + } + + /** [GAME THREAD] Destroys objects that are unreachable and couldn't be destroyed on the worker thread */ + bool TickDestroyGameThreadObjects(bool bUseTimeLimit, float TimeLimit, double StartTime) + { + const int32 TimeLimitEnforcementGranularityForDeletion = 100; + int32 ProcessedObjectsCount = 0; + bool bFinishedDestroyingObjects = true; + + while (UObject* Object = GameThreadObjects.Pop()) + { + Object->~UObject(); + GUObjectAllocator.FreeUObject(Object); + + if (bUseTimeLimit && (ProcessedObjectsCount == TimeLimitEnforcementGranularityForDeletion)) + { + ProcessedObjectsCount = 0; + if ((FPlatformTime::Seconds() - StartTime) > TimeLimit) + { + bFinishedDestroyingObjects = false; + break; + } + } + } + + return bFinishedDestroyingObjects; + } + + /** Waits for the worker thread to finish destroying objects */ + void WaitForAsyncDestructionToFinish() + { + FinishedPurgeEvent->Wait(); + } + +public: + + /** + * Constructor + * @param bMultithreaded if true, the destruction of objects will happen on a worker thread + */ + FAsyncPurge(bool bMultithreaded) + : Thread(nullptr) + , AsyncPurgeThreadId(0) + , BeginPurgeEvent(nullptr) + , FinishedPurgeEvent(nullptr) + , ObjCurrentPurgeObjectIndex(0) + , LastUnreachableObjectsCount(0) + , ObjectsDestroyedSinceLastMarkPhase(0) + { + BeginPurgeEvent = FPlatformProcess::GetSynchEventFromPool(true); + FinishedPurgeEvent = FPlatformProcess::GetSynchEventFromPool(true); + FinishedPurgeEvent->Trigger(); + if (bMultithreaded) + { + check(FPlatformProcess::SupportsMultithreading()); + FPlatformAtomics::InterlockedExchangePtr((void**)&Thread, FRunnableThread::Create(this, TEXT("FAsyncPurge"), 0, TPri_BelowNormal)); + } + else + { + AsyncPurgeThreadId = GGameThreadId; + } + } + + virtual ~FAsyncPurge() + { + check(IsFinished()); + delete Thread; + Thread = nullptr; + FPlatformProcess::ReturnSynchEventToPool(BeginPurgeEvent); + FPlatformProcess::ReturnSynchEventToPool(FinishedPurgeEvent); + BeginPurgeEvent = nullptr; + FinishedPurgeEvent = nullptr; + } + + /** [MAIN THREAD] Adds objects to the purge queue */ + void BeginPurge() + { + check(IsFinished()); // In single-threaded mode we need to be finished or the condition below will hang + if (FinishedPurgeEvent->Wait()) + { + FinishedPurgeEvent->Reset(); + + ObjCurrentPurgeObjectIndex = 0; + ObjectsDestroyedSinceLastMarkPhase = 0; + + BeginPurgeEvent->Trigger(); + } + } + + /** [GAME THREAD] Ticks the purge process on the game thread */ + void TickPurge(bool bUseTimeLimit, float TimeLimit, double StartTime) + { + bool bCanStartDestroyingGameThreadObjects = true; + if (!Thread) + { + // If we're running single-threaded we need to tick the main loop here too + LastUnreachableObjectsCount = GUnreachableObjects.Num(); + bCanStartDestroyingGameThreadObjects = TickDestroyObjects(bUseTimeLimit, TimeLimit, StartTime); + } + else if (!bUseTimeLimit) + { + // Wait for the worker thread to finish processing all objects + WaitForAsyncDestructionToFinish(); + } + if (bCanStartDestroyingGameThreadObjects) + { + // Deal with objects that couldn't be destroyed on the worker thread. This will do nothing when running single-threaded + bool bFinishedDestroyingObjectsOnGameThread = TickDestroyGameThreadObjects(bUseTimeLimit, TimeLimit, StartTime); + if (!Thread && bFinishedDestroyingObjectsOnGameThread) + { + FinishedPurgeEvent->Trigger(); + } + } + } + + /** Returns true if the destruction process is finished */ + bool IsFinished() const + { + if (Thread) + { + return FinishedPurgeEvent->Wait(0, true) && GameThreadObjects.IsEmpty(); + } + else + { + return (ObjCurrentPurgeObjectIndex >= LastUnreachableObjectsCount && GameThreadObjects.IsEmpty()); + } + } + + /** Returns the number of objects already destroyed */ + int32 GetObjectsDestroyedSinceLastMarkPhase() const + { + return ObjectsDestroyedSinceLastMarkPhase; + } + + /** Resets the number of objects already destroyed */ + void ResetObjectsDestroyedSinceLastMarkPhase() + { + ObjectsDestroyedSinceLastMarkPhase = 0; + } + + /** + * Returns true if this function is called from the async destruction thread. + * It will also return true if we're running single-threaded and this function is called on the game thread + */ + bool IsInAsyncPurgeThread() const + { + return AsyncPurgeThreadId == FPlatformTLS::GetCurrentThreadId(); + } + + /* Returns true if it can run multi-threaded destruction */ + bool IsMultithreaded() const + { + return !!Thread; + } + + //~ Begin FRunnable Interface. + virtual bool Init() + { + return true; + } + + virtual uint32 Run() + { + AsyncPurgeThreadId = FPlatformTLS::GetCurrentThreadId(); + + while (StopTaskCounter.GetValue() == 0) + { + if (BeginPurgeEvent->Wait(15, true)) + { + BeginPurgeEvent->Reset(); + TickDestroyObjects(/* bUseTimeLimit = */ false, /* TimeLimit = */ 0.0f, /* StartTime = */ 0.0); + FinishedPurgeEvent->Trigger(); + } + } + FinishedPurgeEvent->Trigger(); + return 0; + } + + virtual void Stop() + { + StopTaskCounter.Increment(); + } + //~ End FRunnable Interface +}; +static FAsyncPurge* GAsyncPurge = nullptr; + +/** + * Returns true if this function is called from the async destruction thread. + * It will also return true if we're running single-threaded and this function is called on the game thread + */ +bool IsInGarbageCollectorThread() +{ + return GAsyncPurge ? GAsyncPurge->IsInAsyncPurgeThread() : IsInGameThread(); +} + +/** Called on shutdown to free GC memory */ +void ShutdownGarbageCollection() +{ + FGCArrayPool::Get().Cleanup(); + delete GAsyncPurge; + GAsyncPurge = nullptr; +} + /** * Handles UObject references found by TFastReferenceCollector */ @@ -501,11 +767,15 @@ public: { #if ENABLE_GC_DEBUG_OUTPUT // this message is to help track down culprits behind "Object in PIE world still referenced" errors - if (GIsEditor && !GIsPlayInEditorWorld && ReferencingObject != NULL && !ReferencingObject->RootPackageHasAnyFlags(PKG_PlayInEditor) && Object->RootPackageHasAnyFlags(PKG_PlayInEditor)) + if (GIsEditor && !GIsPlayInEditorWorld && ReferencingObject != nullptr && !ReferencingObject->HasAnyFlags(RF_Transient) && Object->RootPackageHasAnyFlags(PKG_PlayInEditor)) { - UE_LOG(LogGarbage, Warning, TEXT("GC detected illegal reference to PIE object from content [possibly via [todo]]:")); - UE_LOG(LogGarbage, Warning, TEXT(" PIE object: %s"), *Object->GetFullName()); - UE_LOG(LogGarbage, Warning, TEXT(" NON-PIE object: %s"), *ReferencingObject->GetFullName()); + UPackage* ReferencingPackage = ReferencingObject->GetOutermost(); + if (!ReferencingPackage->HasAnyPackageFlags(PKG_PlayInEditor) && !ReferencingPackage->HasAnyFlags(RF_Transient)) + { + UE_LOG(LogGarbage, Warning, TEXT("GC detected illegal reference to PIE object from content [possibly via [todo]]:")); + UE_LOG(LogGarbage, Warning, TEXT(" PIE object: %s"), *Object->GetFullName()); + UE_LOG(LogGarbage, Warning, TEXT(" NON-PIE object: %s"), *ReferencingObject->GetFullName()); + } } #endif @@ -946,6 +1216,27 @@ public: } }; +// Allow parallel GC to be overridden to single threaded via console command. +static int32 GAllowParallelGC = 1; + +static FAutoConsoleVariableRef CVarAllowParallelGC( + TEXT("gc.AllowParallelGC"), + GAllowParallelGC, + TEXT("sed to control parallel GC."), + ECVF_Default +); + +/** Returns true if Garbage Collection should be forced to run on a single thread */ +static bool ShouldForceSingleThreadedGC() +{ + const bool bForceSingleThreadedGC = !FApp::ShouldUseThreadingForPerformance() || !FPlatformProcess::SupportsMultithreading() || +#if PLATFORM_SUPPORTS_MULTITHREADED_GC + (FPlatformMisc::NumberOfCores() < 2 || GAllowParallelGC == 0 || PERF_DETAILED_PER_CLASS_GC_STATS); +#else //PLATFORM_SUPPORTS_MULTITHREADED_GC + true; +#endif //PLATFORM_SUPPORTS_MULTITHREADED_GC + return bForceSingleThreadedGC; +} static void AcquireGCLock() { @@ -985,6 +1276,8 @@ struct FConditionalGCLock } }; +static bool IncrementalDestroyGarbage(bool bUseTimeLimit, float TimeLimit); + /** * Incrementally purge garbage by deleting all unreferenced objects after routing Destroy. * @@ -994,7 +1287,7 @@ struct FConditionalGCLock * @param bUseTimeLimit whether the time limit parameter should be used * @param TimeLimit soft time limit for this function call */ -void IncrementalPurgeGarbage( bool bUseTimeLimit, float TimeLimit ) +void IncrementalPurgeGarbage(bool bUseTimeLimit, float TimeLimit) { SCOPED_NAMED_EVENT(IncrementalPurgeGarbage, FColor::Red); DECLARE_SCOPE_CYCLE_COUNTER(TEXT("IncrementalPurgeGarbage"), STAT_IncrementalPurgeGarbage, STATGROUP_GC); @@ -1005,10 +1298,9 @@ void IncrementalPurgeGarbage( bool bUseTimeLimit, float TimeLimit ) GObjPurgeIsRequired = true; GUObjectArray.DisableDisregardForGC(); GObjCurrentPurgeObjectIndexNeedsReset = true; - GObjCurrentPurgeObjectIndexResetPastPermanent = false; } // Early out if there is nothing to do. - if( !GObjPurgeIsRequired ) + if (!GObjPurgeIsRequired) { return; } @@ -1037,14 +1329,9 @@ void IncrementalPurgeGarbage( bool bUseTimeLimit, float TimeLimit ) } ResetPurgeProgress(bCompleted); // Keep track of start time to enforce time limit unless bForceFullPurge is true; - GCStartTime = FPlatformTime::Seconds(); - bool bTimeLimitReached = false; - // Depending on platform FPlatformTime::Seconds might take a noticeable amount of time if called thousands of times so we avoid - // enforcing the time limit too often, especially as neither Destroy nor actual deletion should take significant - // amounts of time. - const int32 TimeLimitEnforcementGranularityForDestroy = 10; - const int32 TimeLimitEnforcementGranularityForDeletion = 100; + GCStartTime = FPlatformTime::Seconds(); + bool bTimeLimitReached = false; if (GUnrechableObjectIndex < GUnreachableObjects.Num()) { { @@ -1057,6 +1344,38 @@ void IncrementalPurgeGarbage( bool bUseTimeLimit, float TimeLimit ) } } + if (!bTimeLimitReached) + { + bCompleted = IncrementalDestroyGarbage(bUseTimeLimit, TimeLimit); + } +} + +bool IncrementalDestroyGarbage(bool bUseTimeLimit, float TimeLimit) +{ + const bool bMultithreadedPurge = !ShouldForceSingleThreadedGC() && GMultithreadedDestructionEnabled; + if (!GAsyncPurge) + { + GAsyncPurge = new FAsyncPurge(bMultithreadedPurge); + } + else if (GAsyncPurge->IsMultithreaded() != bMultithreadedPurge) + { + check(GAsyncPurge->IsFinished()); + delete GAsyncPurge; + GAsyncPurge = new FAsyncPurge(bMultithreadedPurge); + } + + bool bCompleted = false; + bool bTimeLimitReached = false; + + // Keep track of time it took to destroy objects for stats + double IncrementalDestroyGarbageStartTime = FPlatformTime::Seconds(); + + // Depending on platform FPlatformTime::Seconds might take a noticeable amount of time if called thousands of times so we avoid + // enforcing the time limit too often, especially as neither Destroy nor actual deletion should take significant + // amounts of time. + const int32 TimeLimitEnforcementGranularityForDestroy = 10; + const int32 TimeLimitEnforcementGranularityForDeletion = 100; + // Set 'I'm garbage collecting' flag - might be checked inside UObject::Destroy etc. FGCScopeLock GCLock; @@ -1072,21 +1391,18 @@ void IncrementalPurgeGarbage( bool bUseTimeLimit, float TimeLimit ) if (GObjCurrentPurgeObjectIndexNeedsReset) { - // iterators don't have an op=, so we destroy it and reconstruct it with a placement new - // GObjCurrentPurgeObjectIndex = FRawObjectIterator(GObjCurrentPurgeObjectIndexResetPastPermanent); - GObjCurrentPurgeObjectIndex.~FRawObjectIterator(); - new (&GObjCurrentPurgeObjectIndex) FRawObjectIterator(GObjCurrentPurgeObjectIndexResetPastPermanent); + GObjCurrentPurgeObjectIndex = 0; GObjCurrentPurgeObjectIndexNeedsReset = false; } - while( GObjCurrentPurgeObjectIndex ) + while (GObjCurrentPurgeObjectIndex < GUnreachableObjects.Num()) { - FUObjectItem* ObjectItem = *GObjCurrentPurgeObjectIndex; + FUObjectItem* ObjectItem = GUnreachableObjects[GObjCurrentPurgeObjectIndex]; checkSlow(ObjectItem); //@todo UE4 - A prefetch was removed here. Re-add it. It wasn't right anyway, since it was ten items ahead and the consoles on have 8 prefetch slots - if (ObjectItem->IsUnreachable()) + check(ObjectItem->IsUnreachable()); { UObject* Object = static_cast(ObjectItem->Object); // Object should always have had BeginDestroy called on it and never already be destroyed @@ -1131,7 +1447,7 @@ void IncrementalPurgeGarbage( bool bUseTimeLimit, float TimeLimit ) } // Have we finished the first round of attempting to call FinishDestroy on unreachable objects? - if( !GObjCurrentPurgeObjectIndex ) + if (GObjCurrentPurgeObjectIndex >= GUnreachableObjects.Num()) { // We've finished iterating over all unreachable objects, but we need still need to handle // objects that were deferred. @@ -1263,75 +1579,54 @@ void IncrementalPurgeGarbage( bool bUseTimeLimit, float TimeLimit ) // Destroy has been routed to all objects so it's safe to delete objects now. GObjFinishDestroyHasBeenRoutedToAllObjects = true; GObjCurrentPurgeObjectIndexNeedsReset = true; - GObjCurrentPurgeObjectIndexResetPastPermanent = !GExitPurge; } } } - - if( GObjFinishDestroyHasBeenRoutedToAllObjects && !bTimeLimitReached ) + + if (GObjFinishDestroyHasBeenRoutedToAllObjects && !bTimeLimitReached) { // Perform actual object deletion. - // @warning: Can't use FObjectIterator here because classes may be destroyed before objects. int32 ProcessCount = 0; if (GObjCurrentPurgeObjectIndexNeedsReset) { - // iterators don't have an op=, so we destroy it and reconstruct it with a placement new - // GObjCurrentPurgeObjectIndex = FRawObjectIterator(GObjCurrentPurgeObjectIndexResetPastPermanent); - GObjCurrentPurgeObjectIndex.~FRawObjectIterator(); - new (&GObjCurrentPurgeObjectIndex) FRawObjectIterator(GObjCurrentPurgeObjectIndexResetPastPermanent); + GAsyncPurge->BeginPurge(); + // Reset the reset flag but don't reset the actual index yet for stat purposes GObjCurrentPurgeObjectIndexNeedsReset = false; } - while( GObjCurrentPurgeObjectIndex ) - { - //@todo UE4 - A prefetch was removed here. Re-add it. It wasn't right anyway, since it was ten items ahead and the consoles on have 8 prefetch slots - FUObjectItem* ObjectItem = *GObjCurrentPurgeObjectIndex; - checkSlow(ObjectItem); - if (ObjectItem->IsUnreachable()) - { - UObject* Object = (UObject*)ObjectItem->Object; - check(Object->HasAllFlags(RF_FinishDestroyed|RF_BeginDestroyed)); - GIsPurgingObject = true; - Object->~UObject(); - GUObjectAllocator.FreeUObject(Object); - GIsPurgingObject = false; - // Keep track of purged stats. - GPurgedObjectCountSinceLastMarkPhase++; - } + GAsyncPurge->TickPurge(bUseTimeLimit, TimeLimit, GCStartTime); - // Advance to the next object. - ++GObjCurrentPurgeObjectIndex; - - ProcessCount++; - - // Only check time limit every so often to avoid calling FPlatformTime::Seconds too often. - if( bUseTimeLimit && (ProcessCount == TimeLimitEnforcementGranularityForDeletion)) - { - if ((FPlatformTime::Seconds() - GCStartTime) > TimeLimit) - { - bTimeLimitReached = true; - break; - } - ProcessCount = 0; - } - } - - if( !GObjCurrentPurgeObjectIndex ) + if (GAsyncPurge->IsFinished()) { bCompleted = true; // Incremental purge is finished, time to reset variables. GObjFinishDestroyHasBeenRoutedToAllObjects = false; GObjPurgeIsRequired = false; GObjCurrentPurgeObjectIndexNeedsReset = true; - GObjCurrentPurgeObjectIndexResetPastPermanent = true; // Log status information. - UE_LOG(LogGarbage, Log, TEXT("GC purged %i objects (%i -> %i)"), GPurgedObjectCountSinceLastMarkPhase, GObjectCountDuringLastMarkPhase.GetValue(), GObjectCountDuringLastMarkPhase.GetValue() - GPurgedObjectCountSinceLastMarkPhase ); + const int32 PurgedObjectCountSinceLastMarkPhase = GAsyncPurge->GetObjectsDestroyedSinceLastMarkPhase(); + UE_LOG(LogGarbage, Log, TEXT("GC purged %i objects (%i -> %i) in %.3fms"), PurgedObjectCountSinceLastMarkPhase, + GObjectCountDuringLastMarkPhase.GetValue(), + GObjectCountDuringLastMarkPhase.GetValue() - PurgedObjectCountSinceLastMarkPhase, + (FPlatformTime::Seconds() - IncrementalDestroyGarbageStartTime) * 1000); #if PERF_DETAILED_PER_CLASS_GC_STATS - LogClassCountInfo( TEXT("objects of"), GClassToPurgeCountMap, 10, GPurgedObjectCountSinceLastMarkPhase ); + LogClassCountInfo( TEXT("objects of"), GClassToPurgeCountMap, 10, PurgedObjectCountSinceLastMarkPhase); #endif + GAsyncPurge->ResetObjectsDestroyedSinceLastMarkPhase(); } } + + if (bUseTimeLimit && !bCompleted) + { + UE_LOG(LogGarbage, Log, TEXT("%.3f ms for incrementally purging unreachable objects (FinishDestroyed: %d, Destroyed: %d / %d)"), + (FPlatformTime::Seconds() - IncrementalDestroyGarbageStartTime) * 1000, + GObjCurrentPurgeObjectIndex, + GAsyncPurge->GetObjectsDestroyedSinceLastMarkPhase(), + GUnreachableObjects.Num()); + } + + return bCompleted; } /** @@ -1344,15 +1639,6 @@ bool IsIncrementalPurgePending() return GObjIncrementalPurgeIsInProgress || GObjPurgeIsRequired; } -// Allow parallel GC to be overridden to single threaded via console command. -static int32 GAllowParallelGC = 1; - -static FAutoConsoleVariableRef CVarAllowParallelGC( - TEXT("gc.AllowParallelGC"), - GAllowParallelGC, - TEXT("sed to control parallel GC."), - ECVF_Default - ); // This counts how many times GC was skipped static int32 GNumAttemptsSinceLastGC = 0; @@ -1384,7 +1670,7 @@ void GatherUnreachableObjects(bool bForceSingleThreaded) GUnreachableObjects.Reset(); GUnrechableObjectIndex = 0; - int32 MaxNumberOfObjects = GUObjectArray.GetObjectArrayNum() - GUObjectArray.GetFirstGCIndex(); + int32 MaxNumberOfObjects = GUObjectArray.GetObjectArrayNum() - (GExitPurge ? 0 : GUObjectArray.GetFirstGCIndex()); int32 NumThreads = FMath::Max(1, FTaskGraphInterface::Get().GetNumWorkerThreads()); int32 NumberOfObjectsPerThread = (MaxNumberOfObjects / NumThreads) + 1; @@ -1395,7 +1681,7 @@ void GatherUnreachableObjects(bool bForceSingleThreaded) // are part of the array so we don't suffer from cache misses as much as we would if we were to check ObjectFlags. ParallelFor(NumThreads, [&ClusterItemsToDestroy, NumberOfObjectsPerThread, NumThreads, MaxNumberOfObjects](int32 ThreadIndex) { - int32 FirstObjectIndex = ThreadIndex * NumberOfObjectsPerThread + GUObjectArray.GetFirstGCIndex(); + int32 FirstObjectIndex = ThreadIndex * NumberOfObjectsPerThread + (GExitPurge ? 0 : GUObjectArray.GetFirstGCIndex()); int32 NumObjects = (ThreadIndex < (NumThreads - 1)) ? NumberOfObjectsPerThread : (MaxNumberOfObjects - (NumThreads - 1) * NumberOfObjectsPerThread); int32 LastObjectIndex = FMath::Min(GUObjectArray.GetObjectArrayNum() - 1, FirstObjectIndex + NumObjects - 1); TArray ThisThreadUnreachableObjects; @@ -1535,12 +1821,7 @@ void CollectGarbageInternal(EObjectFlags KeepFlags, bool bPerformFullPurge) // Fall back to single threaded GC if processor count is 1 or parallel GC is disabled // or detailed per class gc stats are enabled (not thread safe) // Temporarily forcing single-threaded GC in the editor until Modify() can be safely removed from HandleObjectReference. - const bool bForceSingleThreadedGC = !FApp::ShouldUseThreadingForPerformance() || !FPlatformProcess::SupportsMultithreading() || -#if PLATFORM_SUPPORTS_MULTITHREADED_GC - (FPlatformMisc::NumberOfCores() < 2 || GAllowParallelGC == 0 || PERF_DETAILED_PER_CLASS_GC_STATS); -#else //PLATFORM_SUPPORTS_MULTITHREADED_GC - true; -#endif //PLATFORM_SUPPORTS_MULTITHREADED_GC + const bool bForceSingleThreadedGC = ShouldForceSingleThreadedGC(); // Perform reachability analysis. { @@ -1575,9 +1856,6 @@ void CollectGarbageInternal(EObjectFlags KeepFlags, bool bPerformFullPurge) // Set flag to indicate that we are relying on a purge to be performed. GObjPurgeIsRequired = true; - // Reset purged count. - GPurgedObjectCountSinceLastMarkPhase = 0; - GObjCurrentPurgeObjectIndexResetPastPermanent = true; // Perform a full purge by not using a time limit for the incremental purge. The Editor always does a full purge. if (bPerformFullPurge || GIsEditor) diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/Linker.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/Linker.cpp index 84e45f2599bf..a159d3e8b219 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/Linker.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/Linker.cpp @@ -123,7 +123,7 @@ void FLinkerTables::SerializeSearchableNamesMap(FStructuredArchive::FSlot Slot) for (TPair >& Pair : SearchableNamesMap) { - Pair.Value.Sort(); + Pair.Value.Sort(FNameLexicalLess()); } } diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerLoad.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerLoad.cpp index 9bd24b81631a..53b1f28a489c 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerLoad.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerLoad.cpp @@ -35,6 +35,7 @@ #include "Serialization/Formatters/BinaryArchiveFormatter.h" #include "Serialization/Formatters/JsonArchiveInputFormatter.h" #include "Serialization/ArchiveUObjectFromStructuredArchive.h" +#include "Serialization/LoadTimeTracePrivate.h" #include "HAL/FileManager.h" #include "UObject/CoreRedirects.h" @@ -383,7 +384,9 @@ static FTexture2DResourceMem* CreateResourceMem(int32 SizeX, int32 SizeY, int32 static inline int32 HashNames(FName Object, FName Class, FName Package) { - return Object.GetComparisonIndex() + 7 * Class.GetComparisonIndex() + 31 * FPackageName::GetShortFName(Package).GetComparisonIndex(); + return GetTypeHash(Object.GetComparisonIndex()) + + 7 * GetTypeHash(Class.GetComparisonIndex()) + + 31 * GetTypeHash(FPackageName::GetShortFName(Package).GetComparisonIndex()); } static FORCEINLINE bool IsCoreUObjectPackage(const FName& PackageName) @@ -466,10 +469,13 @@ void FLinkerLoad::SetLoader(FArchive* InLoader) check(StructuredArchive == nullptr); check(!StructuredArchiveRootRecord.IsSet()); - check(StructuredArchiveFormatter == nullptr); - - // Create structured archive wrapper - StructuredArchiveFormatter = new FBinaryArchiveFormatter(*this); + + if (StructuredArchiveFormatter == nullptr) + { + // Create structured archive wrapper + StructuredArchiveFormatter = new FBinaryArchiveFormatter(*this); + } + StructuredArchive = new FStructuredArchive(*StructuredArchiveFormatter); StructuredArchiveRootRecord.Emplace(StructuredArchive->Open().EnterRecord()); } @@ -811,12 +817,11 @@ FLinkerLoad::FLinkerLoad(UPackage* InParent, const TCHAR* InFilename, uint32 InL , TemplateForGetArchetypeFromLoader(nullptr) , bForceSimpleIndexToObject(false) , bLockoutLegacyOperations(false) -, bLoaderIsFArchiveAsync2(false) +, bIsAsyncLoader(false) , StructuredArchive(nullptr) , StructuredArchiveFormatter(nullptr) , Loader(nullptr) , AsyncRoot(nullptr) -, NameMapIndex(0) , GatherableTextDataMapIndex(0) , ImportMapIndex(0) , ExportMapIndex(0) @@ -852,10 +857,12 @@ FLinkerLoad::FLinkerLoad(UPackage* InParent, const TCHAR* InFilename, uint32 InL #endif OwnerThread = FPlatformTLS::GetCurrentThreadId(); + TRACE_LOADTIME_NEW_LINKER(this); } FLinkerLoad::~FLinkerLoad() { + TRACE_LOADTIME_DESTROY_LINKER(this); #if !UE_BUILD_SHIPPING && !UE_BUILD_TEST FLinkerManager::Get().GetLiveLinkers().Remove(this); #endif @@ -1011,12 +1018,12 @@ FLinkerLoad::ELinkerStatus FLinkerLoad::CreateLoader( else #endif { - // If want to be able to load cooked data in the editor we need to use FArchiveAsync2 which supports EDL cooked packages, + // If want to be able to load cooked data in the editor we need to use FAsyncArchive which supports EDL cooked packages, // otherwise the generic file reader is faster in the editor so use that - bool bCanUseFArchiveAsync2 = FPlatformProperties::RequiresCookedData() || GAllowCookedDataInEditorBuilds; - if (bCanUseFArchiveAsync2) + bool bCanUseAsyncLoader = FPlatformProperties::RequiresCookedData() || GAllowCookedDataInEditorBuilds; + if (bCanUseAsyncLoader) { - Loader = new FArchiveAsync2(*Filename + Loader = new FAsyncArchive(*Filename , GEventDrivenLoaderEnabled ? Forward>(InSummaryReadyCallback) : TFunction([]() {}) ); } @@ -1025,6 +1032,8 @@ FLinkerLoad::ELinkerStatus FLinkerLoad::CreateLoader( Loader = IFileManager::Get().CreateFileReader(*Filename); } + TRACE_LOADTIME_LINKER_ARCHIVE_ASSOCIATION(this, Loader); + if (!Loader) { UE_LOG(LogLinker, Warning, TEXT("Error opening file '%s'."), *Filename); @@ -1065,7 +1074,7 @@ FLinkerLoad::ELinkerStatus FLinkerLoad::CreateLoader( } else { - bLoaderIsFArchiveAsync2 = bCanUseFArchiveAsync2; + bIsAsyncLoader = bCanUseAsyncLoader; } } } @@ -1096,9 +1105,9 @@ FLinkerLoad::ELinkerStatus FLinkerLoad::CreateLoader( bool bExecuteNextStep = true; if( bHasSerializedPackageFileSummary == false ) { - if (bLoaderIsFArchiveAsync2) + if (bIsAsyncLoader) { - bExecuteNextStep = GetFArchiveAsync2Loader()->ReadyToStartReadingHeader(bUseTimeLimit, bUseFullTimeLimit, TickStartTime, TimeLimit); + bExecuteNextStep = GetAsyncLoader()->ReadyToStartReadingHeader(bUseTimeLimit, bUseFullTimeLimit, TickStartTime, TimeLimit); } else { @@ -1141,9 +1150,9 @@ FLinkerLoad::ELinkerStatus FLinkerLoad::SerializePackageFileSummary() UE_LOG(LogLinker, Warning, TEXT("The file '%s' contains unrecognizable data, check that it is of the expected type."), *Filename); return LINKER_Failed; } - if (bLoaderIsFArchiveAsync2) + if (bIsAsyncLoader) { - GetFArchiveAsync2Loader()->StartReadingHeader(); + GetAsyncLoader()->StartReadingHeader(); } #if WITH_EDITOR @@ -1155,6 +1164,8 @@ FLinkerLoad::ELinkerStatus FLinkerLoad::SerializePackageFileSummary() // Read summary from file. StructuredArchiveRootRecord.GetValue() << NAMED_FIELD(Summary); + TRACE_LOADTIME_PACKAGE_SUMMARY(this, Summary.TotalHeaderSize, Summary.NameCount, Summary.ImportCount, Summary.ExportCount); + // Check tag. if( Summary.Tag != PACKAGE_FILE_TAG ) { @@ -1385,69 +1396,62 @@ FLinkerLoad::ELinkerStatus FLinkerLoad::SerializeNameMap() { DECLARE_SCOPE_CYCLE_COUNTER( TEXT( "FLinkerLoad::SerializeNameMap" ), STAT_LinkerLoad_SerializeNameMap, STATGROUP_LinkerLoad ); + // Text archives don't have name tables + if (IsTextFormat()) + { + return LINKER_Loaded; + } + // The name map is the first item serialized. We wait till all the header information is read // before any serialization. @todo async, @todo seamless: this could be spread out across name, // import and export maps if the package file summary contained more detailed information on // serialized size of individual entries. - bool bFinishedPrecaching = true; - - if( NameMapIndex == 0 && Summary.NameCount > 0 ) + const int32 NameCount = Summary.NameCount; + if (NameMap.Num() == 0 && NameCount > 0) { - if (!IsTextFormat()) - { - Seek(Summary.NameOffset); - } + Seek(Summary.NameOffset); // Make sure there is something to precache first. - if( Summary.TotalHeaderSize > 0 ) + if (Summary.TotalHeaderSize > 0) { + bool bFinishedPrecaching = true; + // Precache name, import and export map. - if (bLoaderIsFArchiveAsync2) + if (bIsAsyncLoader) { - bFinishedPrecaching = GetFArchiveAsync2Loader()->ReadyToStartReadingHeader(bUseTimeLimit, bUseFullTimeLimit, TickStartTime, TimeLimit); + bFinishedPrecaching = GetAsyncLoader()->ReadyToStartReadingHeader(bUseTimeLimit, bUseFullTimeLimit, TickStartTime, TimeLimit); check(!GEventDrivenLoaderEnabled || bFinishedPrecaching || !EVENT_DRIVEN_ASYNC_LOAD_ACTIVE_AT_RUNTIME); } else { bFinishedPrecaching = Loader->Precache(Summary.NameOffset, Summary.TotalHeaderSize - Summary.NameOffset); } - } - // Backward compat code for VER_MOVED_EXPORTIMPORTMAPS_ADDED_TOTALHEADERSIZE. - else - { - bFinishedPrecaching = true; + + if (!bFinishedPrecaching) + { + return LINKER_TimedOut; + } } } - FStructuredArchive::FStream NameStream = StructuredArchiveRootRecord->EnterStream(FIELD_NAME_TEXT("Names")); - const bool bIsTextFormat = StructuredArchiveRootRecord->GetUnderlyingArchive().IsTextFormat(); + SCOPED_LOADTIMER(LinkerLoad_SerializeNameMap_ProcessingEntries); - NameMap.Reserve(Summary.NameCount); - - while (bFinishedPrecaching && NameMapIndex < Summary.NameCount && !IsTimeLimitExceeded(TEXT("serializing name map"), 100)) + NameMap.Reserve(NameCount); + FNameEntrySerialized NameEntry(ENAME_LinkerConstructor); + for (int32 Idx = NameMap.Num(); Idx < NameCount; ++Idx) { - SCOPED_LOADTIMER(LinkerLoad_SerializeNameMap_ProcessingEntries); + *this << NameEntry; + NameMap.Emplace(FName(NameEntry).GetDisplayIndex()); - FNameEntrySerialized NameEntry(ENAME_LinkerConstructor); - - if (bIsTextFormat) + constexpr int32 TimeSliceGranularity = 128; + if (Idx % TimeSliceGranularity == TimeSliceGranularity - 1 && + NameMap.Num() != NameCount && IsTimeLimitExceeded(TEXT("serializing name map"))) { - NameStream.EnterElement() << NameEntry; + return LINKER_TimedOut; } - else - { - // Read the name from the underlying Archive when the format is binary to avoid the overhead from ArchiveProxy - NameStream.GetUnderlyingArchive() << NameEntry; - } - - // Add it to the name table with no splitting and no hash calculations - NameMap.Emplace(NameEntry); - - NameMapIndex++; } - - // Return whether we finished this step and it's safe to start with the next. - return ((NameMapIndex == Summary.NameCount) && !IsTimeLimitExceeded( TEXT("serializing name map") )) ? LINKER_Loaded : LINKER_TimedOut; + + return LINKER_Loaded; } /** @@ -2082,9 +2086,9 @@ FLinkerLoad::ELinkerStatus FLinkerLoad::FinalizeCreation() } } - if (bLoaderIsFArchiveAsync2) + if (bIsAsyncLoader) { - GetFArchiveAsync2Loader()->EndReadingHeader(); + GetAsyncLoader()->EndReadingHeader(); } if ( !(LoadFlags & LOAD_NoVerify) ) @@ -3425,7 +3429,6 @@ UObject* FLinkerLoad::Create( UClass* ObjectClass, FName ObjectName, UObject* Ou void FLinkerLoad::Preload( UObject* Object ) { - //check(IsValidLowLevel()); check(Object); @@ -3512,14 +3515,14 @@ void FLinkerLoad::Preload( UObject* Object ) // is stored Seek(Export.SerialOffset); - FArchiveAsync2* FAA2 = GetFArchiveAsync2Loader(); + FAsyncArchive* AsyncLoader = GetAsyncLoader(); { SCOPE_CYCLE_COUNTER(STAT_LinkerPrecache); // tell the file reader to read the raw data from disk - if (FAA2) + if (AsyncLoader) { - bool bReady = FAA2->Precache(Export.SerialOffset, Export.SerialSize, bUseTimeLimit, bUseFullTimeLimit, TickStartTime, TimeLimit); + bool bReady = AsyncLoader->PrecacheWithTimeLimit(Export.SerialOffset, Export.SerialSize, bUseTimeLimit, bUseFullTimeLimit, TickStartTime, TimeLimit); UE_CLOG(!(bReady || !bUseTimeLimit || !FPlatformProperties::RequiresCookedData()), LogLinker, Warning, TEXT("Hitch on async loading of %s; this export was not properly precached."), *Object->GetFullName()); } else @@ -3532,6 +3535,7 @@ void FLinkerLoad::Preload( UObject* Object ) Object->ClearFlags ( RF_NeedLoad ); { + TRACE_LOADTIME_OBJECT_SCOPE(Export.Object, LoadTimeProfilerObjectEventType_Serialize); SCOPE_CYCLE_COUNTER(STAT_LinkerSerialize); #if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING // communicate with FLinkerPlaceholderBase, what object is currently serializing in @@ -3863,6 +3867,8 @@ UObject* FLinkerLoad::CreateExport( int32 Index ) // Check whether we already loaded the object and if not whether the context flags allow loading it. if( !Export.Object && !FilterExport(Export) ) // for some acceptable position, it was not "not for" { + TRACE_LOADTIME_CREATE_EXPORT_SCOPE(this, &Export.Object, Export.SerialOffset, Export.SerialSize, Export.bIsAsset); + FUObjectSerializeContext* CurrentLoadContext = GetSerializeContext(); check(!GEventDrivenLoaderEnabled || !bLockoutLegacyOperations || !EVENT_DRIVEN_ASYNC_LOAD_ACTIVE_AT_RUNTIME); check(Export.ObjectName!=NAME_None || !(Export.ObjectFlags&RF_Public)); @@ -4754,10 +4760,10 @@ void FLinkerLoad::Detach() CurrentLoadContext->RemoveDelayedLinkerClosePackage(this); } - delete StructuredArchive; - StructuredArchive = nullptr; - delete StructuredArchiveFormatter; - StructuredArchiveFormatter = nullptr; + delete StructuredArchive; + StructuredArchive = nullptr; + delete StructuredArchiveFormatter; + StructuredArchiveFormatter = nullptr; if (Loader) { @@ -4885,7 +4891,7 @@ FArchive& FLinkerLoad::operator<<( UObject*& Object ) return *this; } -void FLinkerLoad::BadNameIndexError(NAME_INDEX NameIndex) +void FLinkerLoad::BadNameIndexError(int32 NameIndex) { UE_LOG(LogLinker, Error, TEXT("Bad name index %i/%i"), NameIndex, NameMap.Num()); } @@ -5140,14 +5146,9 @@ bool AreObjectExportsEqualForDuplicateChecks(const FObjectExport& Lhs, const FOb bool ExportMapSorter(const FObjectExport& Lhs, const FObjectExport& Rhs) { // Check names first. - if (Lhs.ObjectName < Rhs.ObjectName) + if (Lhs.ObjectName != Rhs.ObjectName) { - return true; - } - - if (Lhs.ObjectName > Rhs.ObjectName) - { - return false; + return Lhs.ObjectName.LexicalLess(Rhs.ObjectName); } // Names are equal, check classes. diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerManager.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerManager.cpp index 28da2ab8efa1..a76fa6ae3853 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerManager.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerManager.cpp @@ -49,9 +49,9 @@ bool FLinkerManager::Exec(class UWorld* InWorld, const TCHAR* Cmd, FOutputDevice int32 NameSize = 0; for (int32 j = 0; j < Linker->NameMap.Num(); j++) { - if (Linker->NameMap[j] != NAME_None) + if (FNameEntryId Id = Linker->NameMap[j]) { - NameSize += FNameEntry::GetSize(*Linker->NameMap[j].ToString()); + NameSize += FName::GetEntry(Id)->GetSizeInBytes(); } } Ar.Logf diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerSave.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerSave.cpp index f18ced951abd..13727918cd0a 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerSave.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerSave.cpp @@ -189,9 +189,9 @@ FLinkerSave::~FLinkerSave() Detach(); } -int32 FLinkerSave::MapName(const FName& Name) const +int32 FLinkerSave::MapName(FNameEntryId Id) const { - const int32* IndexPtr = NameIndices.Find(Name); + const int32* IndexPtr = NameIndices.Find(Id); if (IndexPtr) { @@ -275,7 +275,7 @@ FString FLinkerSave::GetArchiveName() const FArchive& FLinkerSave::operator<<( FName& InName ) { - int32 Save = MapName(InName); + int32 Save = MapName(InName.GetDisplayIndex()); check(GetSerializeContext()); ensureMsgf(Save != INDEX_NONE, TEXT("Name \"%s\" is not mapped when saving %s (object: %s, property: %s)"), diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/Obj.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/Obj.cpp index 526ab1e9787a..6ab6564a3657 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/Obj.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/Obj.cpp @@ -38,7 +38,6 @@ #include "Serialization/ArchiveCountMem.h" #include "Serialization/ArchiveShowReferences.h" #include "Serialization/ArchiveFindCulprit.h" -#include "Serialization/ArchiveTraceRoute.h" #include "Misc/PackageName.h" #include "Serialization/BulkData.h" #include "UObject/LinkerLoad.h" @@ -101,12 +100,12 @@ void UObject::EnsureNotRetrievingVTablePtr() const UE_CLOG(GIsRetrievingVTablePtr, LogCore, Fatal, TEXT("We are currently retrieving VTable ptr. Please use FVTableHelper constructor instead.")); } -UObject* UObject::CreateDefaultSubobject(FName SubobjectFName, UClass* ReturnType, UClass* ClassToCreateByDefault, bool bIsRequired, bool bAbstract, bool bIsTransient) +UObject* UObject::CreateDefaultSubobject(FName SubobjectFName, UClass* ReturnType, UClass* ClassToCreateByDefault, bool bIsRequired, bool bIsTransient) { FObjectInitializer* CurrentInitializer = FUObjectThreadContext::Get().TopInitializer(); UE_CLOG(!CurrentInitializer, LogObj, Fatal, TEXT("No object initializer found during construction.")); UE_CLOG(CurrentInitializer->Obj != this, LogObj, Fatal, TEXT("Using incorrect object initializer.")); - return CurrentInitializer->CreateDefaultSubobject(this, SubobjectFName, ReturnType, ClassToCreateByDefault, bIsRequired, bAbstract, bIsTransient); + return CurrentInitializer->CreateDefaultSubobject(this, SubobjectFName, ReturnType, ClassToCreateByDefault, bIsRequired, bIsTransient); } UObject* UObject::CreateEditorOnlyDefaultSubobjectImpl(FName SubobjectName, UClass* ReturnType, bool bTransient) @@ -755,8 +754,6 @@ void UObject::BeginDestroy() ); } - LowLevelRename(NAME_None); - #if WITH_EDITORONLY_DATA // Make sure the linker entry stays as 'bExportLoadFailed' if the entry was marked as such, // doing this prevents the object from being reloaded by subsequent load calls: @@ -781,6 +778,8 @@ void UObject::BeginDestroy() } #endif // WITH_EDITORONLY_DATA + LowLevelRename(NAME_None); + // ensure BeginDestroy has been routed back to UObject::BeginDestroy. #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) DebugBeginDestroyed.RemoveSingle(this); @@ -2859,16 +2858,6 @@ void UObject::OutputReferencers( FOutputDevice& Ar, FReferencerInformationList* Ar.Logf(TEXT("\r\n") ); -#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) - Ar.Logf(TEXT("Shortest reachability from root to %s:\r\n"), *GetFullName() ); - TMap Rt = FArchiveTraceRoute::FindShortestRootPath(this,true,GARBAGE_COLLECTION_KEEPFLAGS); - - FString RootPath = FArchiveTraceRoute::PrintRootPath(Rt, this); - Ar.Log(*RootPath); - - Ar.Logf(TEXT("\r\n") ); -#endif - if (bTempReferencers) { delete Referencers; @@ -4344,7 +4333,7 @@ void StaticUObjectInit() } // Internal cleanup functions -void CleanupGCArrayPools(); +void ShutdownGarbageCollection(); void CleanupLinkerAnnotations(); void CleanupCachedArchetypes(); @@ -4368,7 +4357,9 @@ void StaticExit() GObjTransientPkg = NULL; } - IncrementalPurgeGarbage( false ); + GatherUnreachableObjects(false); + IncrementalPurgeGarbage(false); + GUObjectClusters.DissolveClusters(true); // Keep track of how many objects there are for GC stats as we simulate a mark pass. extern FThreadSafeCounter GObjectCountDuringLastMarkPhase; @@ -4403,19 +4394,8 @@ void StaticExit() // set on all objects that are about to be deleted. One example is FLinkerLoad detaching textures - the SetLinker call needs to // not kick off texture streaming. // - for ( FRawObjectIterator It; It; ++It ) - { - FUObjectItem* ObjItem = *It; - checkSlow(ObjItem); - if (ObjItem->IsUnreachable()) - { - // Begin the object's asynchronous destruction. - UObject* Obj = static_cast(ObjItem->Object); - Obj->ConditionalBeginDestroy(); - } - } - - IncrementalPurgeGarbage( false ); + GatherUnreachableObjects(false); + IncrementalPurgeGarbage(false); { //Repeat GC for every object, including structures and properties. @@ -4425,25 +4405,15 @@ void StaticExit() It->SetUnreachable(); } - for (FRawObjectIterator It; It; ++It) - { - FUObjectItem* ObjItem = *It; - checkSlow(ObjItem); - if (ObjItem->IsUnreachable()) - { - // Begin the object's asynchronous destruction. - UObject* Obj = static_cast(ObjItem->Object); - Obj->ConditionalBeginDestroy(); - } - } - + GatherUnreachableObjects(false); IncrementalPurgeGarbage(false); } + ShutdownGarbageCollection(); UObjectBaseShutdown(); + // Empty arrays to prevent falsely-reported memory leaks. - FDeferredMessageLog::Cleanup(); - CleanupGCArrayPools(); + FDeferredMessageLog::Cleanup(); CleanupLinkerAnnotations(); CleanupCachedArchetypes(); diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/PackageFileSummary.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/PackageFileSummary.cpp index 63f827cb873a..cf6151d3b9cb 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/PackageFileSummary.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/PackageFileSummary.cpp @@ -350,3 +350,9 @@ FArchive& operator<<( FArchive& Ar, FPackageFileSummary& Sum ) FStructuredArchiveFromArchive(Ar).GetSlot() << Sum; return Ar; } + +void FPackageFileSummary::SetCustomVersionContainer(const FCustomVersionContainer& InContainer) +{ + CustomVersionContainer = InContainer; + CustomVersionContainer.SortByKey(); +} diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/Property.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/Property.cpp index df2adfc75f41..0504ca4f4e4f 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/Property.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/Property.cpp @@ -1000,12 +1000,11 @@ const TCHAR* UProperty::ImportSingleProperty( const TCHAR* Str, void* DestData, { Property = FindField(ObjectStruct, NewPropertyName); } -#if WITH_EDITOR + if (!Property) { Property = ObjectStruct->CustomFindProperty(PropertyName); } -#endif // WITH_EDITOR } delete[] Token; diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyArray.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyArray.cpp index 4e9449e0165a..724e25716da9 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyArray.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyArray.cpp @@ -335,18 +335,21 @@ void UArrayProperty::ExportTextInnerItem(FString& ValueStr, UProperty* Inner, co uint8* StructDefaults = NULL; UStructProperty* StructProperty = dynamic_cast(Inner); + + const bool bReadableForm = (0 != (PPF_BlueprintDebugView & PortFlags)); + const bool bExternalEditor = (0 != (PPF_ExternalEditor & PortFlags)); + // ArrayProperties only export a diff because array entries are cleared and recreated upon import. Static arrays are overwritten when importing, // so we export the entire struct to ensure all data is copied over correctly. Behavior is currently inconsistent when copy/pasting between the two types. // In the future, static arrays could export diffs if the property being imported to is reset to default before the import. - if ( StructProperty != NULL && Inner->ArrayDim == 1 ) + // When exporting to an external editor, we want to save defaults so all information is available for editing + if ( StructProperty != NULL && Inner->ArrayDim == 1 && !bExternalEditor ) { checkSlow(StructProperty->Struct); StructDefaults = (uint8*)FMemory::Malloc(StructProperty->Struct->GetStructureSize() * Inner->ArrayDim); StructProperty->InitializeValue(StructDefaults); } - const bool bReadableForm = (0 != (PPF_BlueprintDebugView & PortFlags)); - int32 Count = 0; for( int32 i=0; iGetDisplayNameTextByValue(*(const uint8*)PropertyValue).ToString(); } + else if (PortFlags & PPF_ExternalEditor) + { + ValueStr += Enum->GetAuthoredNameStringByValue(*(const uint8*)PropertyValue); + } else { ValueStr += Enum->GetNameStringByValue(*(const uint8*)PropertyValue); @@ -360,7 +364,7 @@ const TCHAR* UByteProperty::ImportText_Internal( const TCHAR* InBuffer, void* Da FString Temp; if (const TCHAR* Buffer = UPropertyHelpers::ReadToken(InBuffer, Temp, true)) { - int32 EnumIndex = Enum->GetIndexByName(*Temp); + int32 EnumIndex = Enum->GetIndexByName(*Temp, EGetByNameFlags::CheckAuthoredName); if (EnumIndex == INDEX_NONE && (Temp.IsNumeric() && !Algo::Find(Temp, TEXT('.')))) { int64 EnumValue = INDEX_NONE; diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyMap.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyMap.cpp index 516bc8775535..21ab97474671 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyMap.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyMap.cpp @@ -552,13 +552,19 @@ void UMapProperty::ExportTextItem(FString& ValueStr, const void* PropertyValue, return; } + const bool bExternalEditor = (0 != (PPF_ExternalEditor & PortFlags)); + uint8* StructDefaults = nullptr; if (UStructProperty* StructValueProp = dynamic_cast(ValueProp)) { checkSlow(StructValueProp->Struct); - StructDefaults = (uint8*)FMemory::Malloc(MapLayout.SetLayout.Size); - ValueProp->InitializeValue(StructDefaults + MapLayout.ValueOffset); + if (!bExternalEditor) + { + // For external editor, we always export all fields + StructDefaults = (uint8*)FMemory::Malloc(MapLayout.SetLayout.Size); + ValueProp->InitializeValue(StructDefaults + MapLayout.ValueOffset); + } } ON_SCOPE_EXIT { @@ -596,6 +602,12 @@ void UMapProperty::ExportTextItem(FString& ValueStr, const void* PropertyValue, // Always use struct defaults if the inner is a struct, for symmetry with the import of array inner struct defaults uint8* PropDefault = StructDefaults ? StructDefaults : DefaultValue ? DefaultMapHelper.FindMapPairPtrWithKey(PropData) : nullptr; + if (bExternalEditor) + { + // For external editor, always write + PropDefault = PropData; + } + ValueProp->ExportTextItem(ValueStr, PropData + MapLayout.ValueOffset, PropDefault + MapLayout.ValueOffset, Parent, PortFlags | PPF_Delimited, ExportRootScope); --Count; @@ -629,6 +641,12 @@ void UMapProperty::ExportTextItem(FString& ValueStr, const void* PropertyValue, // Always use struct defaults if the inner is a struct, for symmetry with the import of array inner struct defaults uint8* PropDefault = StructDefaults ? StructDefaults : DefaultValue ? DefaultMapHelper.FindMapPairPtrWithKey(PropData) : nullptr; + if (bExternalEditor) + { + // For external editor, always write + PropDefault = PropData; + } + ValueProp->ExportTextItem(ValueStr, PropData + MapLayout.ValueOffset, PropDefault + MapLayout.ValueOffset, Parent, PortFlags | PPF_Delimited, ExportRootScope); ValueStr += TEXT(")"); diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyMulticastDelegate.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyMulticastDelegate.cpp index 89317518f102..c870e642b212 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyMulticastDelegate.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyMulticastDelegate.cpp @@ -470,7 +470,11 @@ FMulticastScriptDelegate::FInvocationList& UMulticastSparseDelegateProperty::Get void UMulticastSparseDelegateProperty::SerializeItem(FStructuredArchive::FSlot Slot, void* Value, void const* Defaults) const { FArchiveUObjectFromStructuredArchive Ar(Slot); + SerializeItemInternal(Ar, Value, Defaults); +} +void UMulticastSparseDelegateProperty::SerializeItemInternal(FArchive& Ar, void* Value, void const* Defaults) const +{ FSparseDelegate& SparseDelegate = *(FSparseDelegate*)Value; if (Ar.IsLoading()) diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyObject.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyObject.cpp index ce6cde931019..9b61e4a34aa8 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyObject.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyObject.cpp @@ -93,6 +93,11 @@ void UObjectProperty::SerializeItem( FStructuredArchive::FSlot Slot, void* Value // Serialize in place UObject** ObjectPtr = GetPropertyValuePtr(Value); Slot << (*ObjectPtr); + + if(!UnderlyingArchive.IsSaving()) + { + CheckValidObject(ObjectPtr); + } } else { diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertySet.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertySet.cpp index 6dd2fad7f005..6c4b4a135285 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertySet.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertySet.cpp @@ -483,13 +483,19 @@ void USetProperty::ExportTextItem(FString& ValueStr, const void* PropertyValue, return; } + const bool bExternalEditor = (0 != (PPF_ExternalEditor & PortFlags)); + uint8* StructDefaults = nullptr; if (UStructProperty* StructElementProp = Cast(ElementProp)) { checkSlow(StructElementProp->Struct); - StructDefaults = (uint8*)FMemory::Malloc(SetLayout.Size); - ElementProp->InitializeValue(StructDefaults); + if (!bExternalEditor) + { + // For external editor, we always export all fields + StructDefaults = (uint8*)FMemory::Malloc(SetLayout.Size); + ElementProp->InitializeValue(StructDefaults); + } } ON_SCOPE_EXIT @@ -524,6 +530,12 @@ void USetProperty::ExportTextItem(FString& ValueStr, const void* PropertyValue, // Always use struct defaults if the element is a struct, for symmetry with the import of array inner struct defaults uint8* PropDefault = StructDefaults ? StructDefaults : DefaultValue ? DefaultSetHelper.FindElementPtr(PropData) : nullptr; + if (bExternalEditor) + { + // For external editor, always write + PropDefault = PropData; + } + ElementProp->ExportTextItem(ValueStr, PropData, PropDefault, Parent, PortFlags | PPF_Delimited, ExportRootScope); --Count; @@ -548,8 +560,15 @@ void USetProperty::ExportTextItem(FString& ValueStr, const void* PropertyValue, ValueStr += TCHAR(','); } - ElementProp->ExportTextItem(ValueStr, PropData, nullptr, Parent, PortFlags | PPF_Delimited, ExportRootScope); + uint8* PropDefault = nullptr; + if (bExternalEditor) + { + // For external editor, always write + PropDefault = PropData; + } + + ElementProp->ExportTextItem(ValueStr, PropData, PropDefault, Parent, PortFlags | PPF_Delimited, ExportRootScope); --Count; } diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyTag.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyTag.cpp index 1e761fe5f774..dc5753c74b72 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyTag.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyTag.cpp @@ -112,7 +112,7 @@ void operator<<(FStructuredArchive::FSlot Slot, FPropertyTag& Tag) // Name. Record << NAMED_ITEM("Name", Tag.Name); - if ((Tag.Name == NAME_None) || !Tag.Name.IsValid()) + if (Tag.Name.IsNone()) { return; } @@ -128,52 +128,57 @@ void operator<<(FStructuredArchive::FSlot Slot, FPropertyTag& Tag) FArchive::FScopeSetDebugSerializationFlags S(UnderlyingArchive, DSF_IgnoreDiff); Record << NAMED_ITEM("Size", Tag.Size) << NAMED_ITEM("ArrayIndex", Tag.ArrayIndex); } - // only need to serialize this for structs - if (Tag.Type == NAME_StructProperty) - { - Record << NAMED_ITEM("StructName", Tag.StructName); - if (Version >= VER_UE4_STRUCT_GUID_IN_PROPERTY_TAG) - { - Record << NAMED_ITEM("StructGuid", Tag.StructGuid); - } - } - // only need to serialize this for bools - else if (Tag.Type == NAME_BoolProperty && !UnderlyingArchive.IsTextFormat()) - { - if (UnderlyingArchive.IsSaving()) - { - FSerializedPropertyScope SerializedProperty(UnderlyingArchive, Tag.Prop); - Record << NAMED_ITEM("BoolVal", Tag.BoolVal); - } - else - { - Record << NAMED_ITEM("BoolVal", Tag.BoolVal); - } - } - // only need to serialize this for bytes/enums - else if (Tag.Type == NAME_ByteProperty || Tag.Type == NAME_EnumProperty) - { - Record << NAMED_ITEM("EnumName", Tag.EnumName); - } - // only need to serialize this for arrays - else if (Tag.Type == NAME_ArrayProperty) - { - if (Version >= VAR_UE4_ARRAY_PROPERTY_INNER_TAGS) - { - Record << NAMED_ITEM("InnerType", Tag.InnerType); - } - } - if (Version >= VER_UE4_PROPERTY_TAG_SET_MAP_SUPPORT) + if (Tag.Type.GetNumber() == 0) { - if (Tag.Type == NAME_SetProperty) + FNameEntryId TagType = Tag.Type.GetComparisonIndex(); + + // only need to serialize this for structs + if (TagType == NAME_StructProperty) { - Record << NAMED_ITEM("InnerType", Tag.InnerType); + Record << NAMED_ITEM("StructName", Tag.StructName); + if (Version >= VER_UE4_STRUCT_GUID_IN_PROPERTY_TAG) + { + Record << NAMED_ITEM("StructGuid", Tag.StructGuid); + } } - else if (Tag.Type == NAME_MapProperty) + // only need to serialize this for bools + else if (TagType == NAME_BoolProperty && !UnderlyingArchive.IsTextFormat()) { - Record << NAMED_ITEM("InnerType", Tag.InnerType); - Record << NAMED_ITEM("ValueType", Tag.ValueType); + if (UnderlyingArchive.IsSaving()) + { + FSerializedPropertyScope SerializedProperty(UnderlyingArchive, Tag.Prop); + Record << NAMED_ITEM("BoolVal", Tag.BoolVal); + } + else + { + Record << NAMED_ITEM("BoolVal", Tag.BoolVal); + } + } + // only need to serialize this for bytes/enums + else if (TagType == NAME_ByteProperty || TagType == NAME_EnumProperty) + { + Record << NAMED_ITEM("EnumName", Tag.EnumName); + } + // only need to serialize this for arrays + else if (TagType == NAME_ArrayProperty) + { + if (Version >= VAR_UE4_ARRAY_PROPERTY_INNER_TAGS) + { + Record << NAMED_ITEM("InnerType", Tag.InnerType); + } + } + else if (Version >= VER_UE4_PROPERTY_TAG_SET_MAP_SUPPORT) + { + if (TagType == NAME_SetProperty) + { + Record << NAMED_ITEM("InnerType", Tag.InnerType); + } + else if (TagType == NAME_MapProperty) + { + Record << NAMED_ITEM("InnerType", Tag.InnerType); + Record << NAMED_ITEM("ValueType", Tag.ValueType); + } } } diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/SavePackage.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/SavePackage.cpp index 7fb75f7c69c8..73623e45c9db 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/SavePackage.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/SavePackage.cpp @@ -722,10 +722,12 @@ public: */ void MarkNameAsReferenced(const FName& Name) { - // We need to store the FName without the number, as the number is stored separately by FLinkerSave - // and we don't want duplicate entries in the name table just because of the number - const FName NameNoNumber(Name, 0); - ReferencedNames.Add(NameNoNumber); + ReferencedNames.Add(Name.GetDisplayIndex()); + } + + void MarkNameAsReferenced(FNameEntryId Name) + { + ReferencedNames.Add(Name); } #if WITH_EDITOR @@ -762,14 +764,14 @@ public: void UpdateLinkerWithMarkedNames(FLinkerSave* Linker) { Linker->NameMap.Reserve(Linker->NameMap.Num() + ReferencedNames.Num()); - for (const FName& Name : ReferencedNames) + for (FNameEntryId Name : ReferencedNames) { Linker->NameMap.Add(Name); } } private: - TSet ReferencedNames; + TSet ReferencedNames; }; bool IsEditorOnlyObject(const UObject* InObject, bool bCheckRecursive, bool bCheckMarks) @@ -1509,7 +1511,7 @@ struct FObjectNameSortHelper { private: /** the linker that we're sorting names for */ - friend struct TDereferenceWrapper; + friend struct TDereferenceWrapper; /** Comparison function used by Sort */ FORCEINLINE bool operator()( const FName& A, const FName& B ) const @@ -1517,6 +1519,13 @@ private: return A.Compare(B) < 0; } + /** Comparison function used by Sort */ + FORCEINLINE bool operator()(FNameEntryId A, FNameEntryId B) const + { + // Could be implemented without constructing FName but would new FNameEntry comparison API + return A != B && operator()(FName::CreateFromDisplayId(A, 0), FName::CreateFromDisplayId(B, 0)); + } + public: @@ -1534,10 +1543,10 @@ public: if ( LinkerToConformTo != nullptr ) { SortStartPosition = LinkerToConformTo->NameMap.Num(); - TArray ConformedNameMap = LinkerToConformTo->NameMap; + TArray ConformedNameMap = LinkerToConformTo->NameMap; for ( int32 NameIndex = 0; NameIndex < Linker->NameMap.Num(); NameIndex++ ) { - FName& CurrentName = Linker->NameMap[NameIndex]; + FNameEntryId CurrentName = Linker->NameMap[NameIndex]; if ( !ConformedNameMap.Contains(CurrentName) ) { ConformedNameMap.Add(CurrentName); @@ -1547,7 +1556,7 @@ public: Linker->NameMap = ConformedNameMap; for ( int32 NameIndex = 0; NameIndex < Linker->NameMap.Num(); NameIndex++ ) { - FName& CurrentName = Linker->NameMap[NameIndex]; + FNameEntryId CurrentName = Linker->NameMap[NameIndex]; SavePackageState.MarkNameAsReferenced(CurrentName); } } @@ -2283,7 +2292,6 @@ public: * Constructor */ FExportReferenceSorter() - : FArchiveUObject(), CurrentInsertIndex(INDEX_NONE), CoreReferencesOffset(INDEX_NONE), bIgnoreFieldReferences(false), CurrentClass(nullptr) { ArIsObjectReferenceCollector = true; this->SetIsPersistent(true); @@ -2620,12 +2628,12 @@ private: /** * The index into the ReferencedObjects array to insert new objects */ - int32 CurrentInsertIndex; + int32 CurrentInsertIndex = INDEX_NONE; /** * The index into the ReferencedObjects array for the first object not referenced by one of the core classes */ - int32 CoreReferencesOffset; + int32 CoreReferencesOffset = INDEX_NONE; /** * The classes which are pre-added to the array of ReferencedObjects. Used for resolving a number of circular dependecy issues between @@ -2679,7 +2687,7 @@ private: /** * Controls whether to process UField objects encountered during serialization of an object. */ - bool bIgnoreFieldReferences; + bool bIgnoreFieldReferences = false; /** * The UClass currently being processed. This is used to prevent serialization of a UStruct's Children member causing other fields of the same class to be processed too early due @@ -2688,10 +2696,10 @@ private: * the "second" function would be created first, which would end up force-loading the struct. This would cause an unacceptible seek because the struct appears later in the export list, thus * hasn't been created yet. */ - UClass* CurrentClass; + UClass* CurrentClass = nullptr; /** Package to constrain checks to */ - UPackage* PackageToSort; + UPackage* PackageToSort = nullptr; }; /** @@ -3139,31 +3147,21 @@ EObjectMark UPackage::GetExcludedObjectMarksForTargetPlatform( const class ITarg bool ExportObjectSorter(const UObject& Lhs, const UObject& Rhs) { // Check names first. - if (Lhs.GetFName() < Rhs.GetFName()) + if (Lhs.GetFName() != Rhs.GetFName()) { - return true; - } - - if (Lhs.GetFName() > Rhs.GetFName()) - { - return false; + return Lhs.GetFName().LexicalLess(Rhs.GetFName()); } // Names equal, compare class names. - if (Lhs.GetClass()->GetFName() < Rhs.GetClass()->GetFName()) + if (Lhs.GetClass()->GetFName() != Rhs.GetClass()->GetFName()) { - return true; - } - - if (Lhs.GetClass()->GetFName() > Rhs.GetClass()->GetFName()) - { - return false; + return Lhs.GetClass()->GetFName().LexicalLess(Rhs.GetClass()->GetFName()); } // Compare by outers if they exist. if (Lhs.GetOuter() && Rhs.GetOuter()) { - return Lhs.GetOuter()->GetFName() < Rhs.GetOuter()->GetFName(); + return Lhs.GetOuter()->GetFName().LexicalLess(Rhs.GetOuter()->GetFName()); } if (Lhs.GetOuter()) @@ -3976,7 +3974,7 @@ FSavePackageResultStruct UPackage::Save(UPackage* InOuter, UObject* Base, EObjec Linker = TUniquePtr(new FLinkerSave(InOuter, *TempFilename, bForceByteSwapping, bSaveUnversioned)); } -#if WITH_EDITOR +#if WITH_TEXT_ARCHIVE_SUPPORT if (bTextFormat) { TextFormatArchive = IFileManager::Get().CreateFileWriter(*TextFormatTempFilename); @@ -4539,17 +4537,17 @@ FSavePackageResultStruct UPackage::Save(UPackage* InOuter, UObject* Base, EObjec if ( GOutputCookingWarnings ) { // check the name list for uniqueobjectnamefor cooking - static FName NAME_UniqueObjectNameForCooking(TEXT("UniqueObjectNameForCooking")); + static FNameEntryId NAME_UniqueObjectNameForCooking = FName("UniqueObjectNameForCooking").GetComparisonIndex(); - for (const auto& NameInUse : Linker->NameMap) + for (FNameEntryId NameInUse : Linker->NameMap) { - if (NameInUse.GetComparisonIndex() == NAME_UniqueObjectNameForCooking.GetComparisonIndex()) + if (FName::GetComparisonIdFromDisplayId(NameInUse) == NAME_UniqueObjectNameForCooking) { //UObject *Object = FindObject( ANY_PACKAGE, *NameInUse.ToString()); // error // check(Object); - UE_LOG(LogSavePackage, Warning, TEXT("Saving object into cooked package %s which was created at cook time, Object Name %s"), Filename, *NameInUse.ToString()); + UE_LOG(LogSavePackage, Warning, TEXT("Saving object into cooked package %s which was created at cook time"), Filename); } } } @@ -4572,6 +4570,7 @@ FSavePackageResultStruct UPackage::Save(UPackage* InOuter, UObject* Base, EObjec SlowTask.EnterProgressFrame(); // Save names. + if (!bTextFormat) { FStructuredArchive::FStream NameStream = StructuredArchiveRoot.EnterStream(FIELD_NAME_TEXT("Names")); #if WITH_EDITOR @@ -4581,7 +4580,7 @@ FSavePackageResultStruct UPackage::Save(UPackage* InOuter, UObject* Base, EObjec Linker->Summary.NameCount = Linker->NameMap.Num(); for (int32 i = 0; i < Linker->NameMap.Num(); i++) { - Linker->NameMap[i].GetDisplayNameEntry()->Write(NameStream.EnterElement()); + FName::GetEntry(Linker->NameMap[i])->Write(NameStream.EnterElement()); Linker->NameIndices.Add(Linker->NameMap[i], i); } } @@ -5461,6 +5460,9 @@ FSavePackageResultStruct UPackage::Save(UPackage* InOuter, UObject* Base, EObjec Linker->StartScriptSHAGeneration(); } +#if WITH_EDITOR + TArray> AdditionalFilesFromExports; +#endif { COOK_STAT(FScopedDurationTimer SaveTimer(SavePackageStats::SerializeExportsTimeSec)); #if WITH_EDITOR @@ -5529,9 +5531,14 @@ FSavePackageResultStruct UPackage::Save(UPackage* InOuter, UObject* Base, EObjec } #if WITH_EDITOR - if (Linker->IsCooking()) + if (bIsCooking) { - Export.Object->CookAdditionalFiles(Filename, Linker->CookingTarget()); + Export.Object->CookAdditionalFiles(Filename, TargetPlatform, + [&AdditionalFilesFromExports](const TCHAR* Filename, void* Data, int64 Size) + { + FLargeMemoryWriter& Writer = AdditionalFilesFromExports.Emplace_GetRef(0, true, Filename); + Writer.Serialize(Data, Size); + }); } #endif } @@ -5776,6 +5783,30 @@ FSavePackageResultStruct UPackage::Save(UPackage* InOuter, UObject* Base, EObjec } Linker->BulkDataToAppend.Empty(); + +#if WITH_EDITOR + if (bIsCooking && AdditionalFilesFromExports.Num() > 0) + { + const bool bWriteFileToDisk = !bDiffing; + for (FLargeMemoryWriter& Writer : AdditionalFilesFromExports) + { + const int64 Size = Writer.TotalSize(); + TotalPackageSizeUncompressed += Size; + if (bComputeHash) + { + CookedPackageHash.Update(Writer.GetData(), Size); + } + if (bWriteFileToDisk) + { + FLargeMemoryPtr DataPtr(Writer.GetData()); + Writer.ReleaseOwnership(); + AsyncWriteFile(MoveTemp(DataPtr), Size, *Writer.GetArchiveName(), FDateTime::MinValue(), false); + } + } + AdditionalFilesFromExports.Empty(); + } +#endif + // write the package post tag if (!bTextFormat) diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/ScriptCore.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/ScriptCore.cpp index 7819915828df..12e499388439 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/ScriptCore.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/ScriptCore.cpp @@ -16,6 +16,7 @@ #include "UObject/Script.h" #include "UObject/ObjectMacros.h" #include "UObject/UObjectBaseUtility.h" +#include "UObject/UObjectIterator.h" #include "UObject/Object.h" #include "UObject/CoreNative.h" #include "UObject/Class.h" @@ -82,6 +83,23 @@ COREUOBJECT_API void GInitRunaway() COREUOBJECT_API void GInitRunaway() {} #endif +#define STORE_INSTRUCTION_NAMES SCRIPT_AUDIT_ROUTINES + +#if STORE_INSTRUCTION_NAMES +const char* GNativeFuncNames[EX_Max]; + +#define STORE_INSTRUCTION_NAME(inst) \ +static struct F##inst##Registrar \ +{ \ + F##inst##Registrar() \ + { \ + GNativeFuncNames[inst] = #inst; \ + } \ +} inst##RegistrarInst; +#else +#define STORE_INSTRUCTION_NAME(inst) +#endif//STORE_INSTRUCTION_NAMES + #define IMPLEMENT_FUNCTION(func) \ static FNativeFunctionRegistrar UObject##func##Registar(UObject::StaticClass(),#func,&UObject::func); @@ -90,6 +108,7 @@ COREUOBJECT_API void GInitRunaway() {} static uint8 UObject##func##CastTemp = GRegisterCast( CastIndex, &UObject::func ); #define IMPLEMENT_VM_FUNCTION(BytecodeIndex, func) \ + STORE_INSTRUCTION_NAME(BytecodeIndex) \ IMPLEMENT_FUNCTION(func) \ static uint8 UObject##func##BytecodeTemp = GRegisterNative( BytecodeIndex, &UObject::func ); @@ -1293,6 +1312,431 @@ FBlueprintEventTimer::FScopedNativeTimer::~FScopedNativeTimer() #endif +#if SCRIPT_AUDIT_ROUTINES + +// heap would be more time efficient: +template +void NBest(TArray& OutBest, const T& NewEntry, TFunctionRef IsBetter) +{ + if(IsBetter(NewEntry, OutBest.Last())) + { + // find insertion point: + int32 InsertIdx = INDEX_NONE; + // O(n): + for(int32 I = 0; I < OutBest.Num(); ++I) + { + if(IsBetter( NewEntry, OutBest[I] )) + { + InsertIdx = I; + break; + } + } + + // O(n): + OutBest.Insert(NewEntry, InsertIdx); + OutBest.Pop(); + } +} + +static void OutputLongestFunctions(FOutputDevice& Ar, int32 Num) +{ + // max heap would be more efficient + TArray LongestFunctions; + LongestFunctions.AddDefaulted(Num); + + for( TObjectIterator It; It; ++It) + { + UClass* BPGC = *It; + for(TFieldIterator FuncIt(BPGC, EFieldIteratorFlags::ExcludeSuper); FuncIt; ++FuncIt) + { + UFunction* Fn = *FuncIt; + int32 LenScript = Fn->Script.Num(); + + NBest(LongestFunctions, Fn, + [LenScript](UFunction* A, UFunction* B) + { + return B == nullptr || LenScript > B->Script.Num(); + } + ); + } + } + + if(LongestFunctions.Num() == 0) + { + Ar.Log(TEXT("No script functions found when looking for longest functions.")); + } + else + { + for(UFunction* Fn : LongestFunctions) + { + if(!Fn) + { + break; + } + + Ar.Logf(TEXT("%s %s %d"), *Fn->GetName(), *Fn->GetOuter()->GetName(), Fn->Script.Num()); + } + } +} + +static void OutputMostFrequentlyCalledFunctions(FOutputDevice& OutputAr, int32 Num) +{ + // Script serialization is recursive and requires certain symbols (e.g. Script, a reference + // to the bytecode), so we declare a type so that we have some scope: + struct FCallFrequencyCounter + { + FCallFrequencyCounter(TArray& InScript) + : Script(InScript) + { + } + + TArray& Script; + TMap* FunctionCallCounts = nullptr; + // Could try and get more context on vcalls, but for + // this macro auditing tool name should be enough: + TMap* VirtualFunctionCallCounts = nullptr; + + void* GetLinker() { return nullptr; } + + EExprToken SerializeExpr(int32& iCode, FArchive& Ar) + { + #define SERIALIZEEXPR_INC + #define SERIALIZEEXPR_AUTO_UNDEF_XFER_MACROS + + if(iCode < Script.Num()) + { + switch((EExprToken)Script[iCode]) + { + case EX_CallMath: + case EX_LocalFinalFunction: + case EX_FinalFunction: + { + // peak UFunction*: + if(FunctionCallCounts) + { + UFunction* Fn = nullptr; + FMemory::Memcpy( &Fn, &Script[iCode+1], sizeof(UFunction*) ); + if(ensure(Fn)) + { + check(Fn->IsValidLowLevel()); + FunctionCallCounts->FindOrAdd(Fn)++; + } + } + } + break; + case EX_VirtualFunction: + case EX_LocalVirtualFunction: + { + // peak function name: + if(VirtualFunctionCallCounts) + { + FScriptName ScriptName; + FMemory::Memcpy( &ScriptName, &Script[iCode+1], sizeof(FScriptName) ); + VirtualFunctionCallCounts->FindOrAdd(ScriptNameToName(ScriptName))++; + } + } + break; + + } + } + + #include "UObject/ScriptSerialization.h" + return Expr; + #undef SERIALIZEEXPR_INC + #undef SERIALIZEEXPR_AUTO_UNDEF_XFER_MACROS + } + + void CountCalls(TMap* InFunctionCallCounts, TMap* InVirtualFunctionCallCounts) + { + FunctionCallCounts = InFunctionCallCounts; + VirtualFunctionCallCounts = InVirtualFunctionCallCounts; + + int32 iCode = 0; + const int32 ScriptSizeBytes = Script.Num(); + FArchive DummyArchive; + + while (iCode < ScriptSizeBytes) + { + SerializeExpr(iCode, DummyArchive); + } + } + }; + + TMap FunctionCallCounts; + TMap VirtualFunctionCallCounts; + + for( TObjectIterator It; It; ++It) + { + UClass* BPGC = *It; + for(TFieldIterator FuncIt(BPGC, EFieldIteratorFlags::ExcludeSuper); FuncIt; ++FuncIt) + { + UFunction* Fn = *FuncIt; + + // disassem and log function calls: + FCallFrequencyCounter Counter(Fn->Script); + Counter.CountCalls(&FunctionCallCounts, &VirtualFunctionCallCounts); + } + } + + // sort by # calls: + { + TArray< TPair > FunctionCallsSorted; + FunctionCallsSorted.AddDefaulted(Num); + for(const TPair& Calls : FunctionCallCounts ) + { + NBest>(FunctionCallsSorted, Calls, + [](const TPair& A, const TPair& B) -> bool + { + return B.Key == nullptr || A.Value > B.Value; + } + ); + } + + if(FunctionCallsSorted.Num()) + { + OutputAr.Logf(TEXT("Top %d function call targets"), FunctionCallsSorted.Num()); + for(TPair& Calls : FunctionCallsSorted ) + { + if(Calls.Key == nullptr) + { + break; + } + + OutputAr.Logf(TEXT("%s %s %d"), *Calls.Key->GetName(), *Calls.Key->GetOuter()->GetName(), Calls.Value); + } + } + else + { + OutputAr.Log(TEXT("No function call instructions found in memory")); + } + } + + { + TArray< TPair> VirtualFunctionCallsSorted; + VirtualFunctionCallsSorted.AddDefaulted(Num); + for(const TPair& Calls : VirtualFunctionCallCounts ) + { + NBest>(VirtualFunctionCallsSorted, Calls, + [](const TPair& A, const TPair& B) -> bool + { + return B.Key == FName() || A.Value > B.Value; + } + ); + } + + if(VirtualFunctionCallsSorted.Num()) + { + OutputAr.Logf(TEXT("Top %d virtual function call targets"), VirtualFunctionCallsSorted.Num()); + for(TPair& Calls : VirtualFunctionCallsSorted ) + { + if(Calls.Key == FName()) + { + break; + } + + OutputAr.Logf(TEXT("%s %d"), *(Calls.Key.ToString()), Calls.Value); + } + } + else + { + OutputAr.Log(TEXT("No virtual function call instructions in memory")); + } + } +} + +static void OutputMostFrequentlyUsedInstructions(FOutputDevice& OutputAr, int32 Num) +{ + // Script serialization is recursive and requires certain symbols (e.g. Script, a reference + // to the bytecode), so we declare a type so that we have some scope: + struct FInstructionFrequencyCounter + { + FInstructionFrequencyCounter(TArray& InScript) + : Script(InScript) + { + } + + TArray& Script; + TMap* InstructionCallCounts; + + void* GetLinker() { return nullptr; } + + + EExprToken SerializeExpr(int32& iCode, FArchive& Ar) + { + #define SERIALIZEEXPR_INC + #define SERIALIZEEXPR_AUTO_UNDEF_XFER_MACROS + + if(iCode < Script.Num()) + { + if(InstructionCallCounts) + { + InstructionCallCounts->FindOrAdd((EExprToken)Script[iCode])++; + } + } + + #include "UObject/ScriptSerialization.h" + return Expr; + #undef SERIALIZEEXPR_INC + #undef SERIALIZEEXPR_AUTO_UNDEF_XFER_MACROS + } + + void CountInstructions(TMap* InInstructionCallCounts) + { + InstructionCallCounts = InInstructionCallCounts; + + int32 iCode = 0; + const int32 ScriptSizeBytes = Script.Num(); + FArchive DummyArchive; + + while (iCode < ScriptSizeBytes) + { + SerializeExpr(iCode, DummyArchive); + } + } + }; + + + TMap InstructionCallCounts; + + for( TObjectIterator It; It; ++It) + { + UClass* BPGC = *It; + for(TFieldIterator FuncIt(BPGC, EFieldIteratorFlags::ExcludeSuper); FuncIt; ++FuncIt) + { + UFunction* Fn = *FuncIt; + + // disassem and log function calls: + FInstructionFrequencyCounter Counter(Fn->Script); + Counter.CountInstructions(&InstructionCallCounts); + } + } + + // sort by #: + { + TArray> InstructionCountsSorted; + InstructionCountsSorted.AddDefaulted(Num); + + for(const TPair& Instruction : InstructionCallCounts ) + { + NBest>(InstructionCountsSorted, Instruction, + [](const TPair& A, const TPair& B) -> bool + { + return A.Value > B.Value; + } + ); + } + + if(InstructionCountsSorted.Num()) + { + OutputAr.Logf(TEXT("Top %d bytecode instructions"), InstructionCountsSorted.Num()); + for(TPair& Instruction : InstructionCountsSorted ) + { + if(Instruction.Value == 0) + { + break; + } + +#if STORE_INSTRUCTION_NAMES + if(GNativeFuncNames[Instruction.Key]) + { + FString AsString = GNativeFuncNames[Instruction.Key]; + OutputAr.Logf(TEXT("%s %d"), *AsString, Instruction.Value); + } + else + { + OutputAr.Logf(TEXT("0x%x %d"), Instruction.Key, Instruction.Value); + } +#else + OutputAr.Logf(TEXT("0x%x %d"), Instruction.Key, Instruction.Value); +#endif + } + } + else + { + OutputAr.Log(TEXT("No instructions found in memory")); + } + } +} + +static void OutputTotalBytecodeSize(FOutputDevice& Ar) +{ + uint32 TotalSize = 0; + + for( TObjectIterator It; It; ++It) + { + UClass* BPGC = *It; + for(TFieldIterator FuncIt(BPGC, EFieldIteratorFlags::ExcludeSuper); FuncIt; ++FuncIt) + { + UFunction* Fn = *FuncIt; + + TotalSize += Fn->Script.Num(); + } + } + + Ar.Logf(TEXT("Total bytecode size: %d"), TotalSize); +} + +struct FScriptAuditExec + : public FSelfRegisteringExec +{ + // FSelfRegisteringExec: + virtual bool Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) override; +} ScriptAudit; + +bool FScriptAuditExec::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) +{ + if (FParse::Command(&Cmd, TEXT("ScriptAudit"))) + { + FString ParsedCommand = FParse::Token(Cmd, 0); + + if (ParsedCommand.Equals(TEXT("LongestFunctions"),ESearchCase::IgnoreCase)) + { + int32 NumToOutput = 20; + + FString Num = FParse::Token(Cmd, 0); + if(!Num.IsEmpty()) + { + NumToOutput = FCString::Atoi(*Num); + } + OutputLongestFunctions(Ar, NumToOutput); + return true; + } + else if (ParsedCommand.Equals(TEXT("FrequentFunctionsCalled"),ESearchCase::IgnoreCase)) + { + int32 NumToOutput = 20; + + FString Num = FParse::Token(Cmd, 0); + if(!Num.IsEmpty()) + { + NumToOutput = FCString::Atoi(*Num); + } + OutputMostFrequentlyCalledFunctions(Ar, NumToOutput); + return true; + } + else if (ParsedCommand.Equals(TEXT("FrequentInstructions"),ESearchCase::IgnoreCase)) + { + int32 NumToOutput = 20; + + FString Num = FParse::Token(Cmd, 0); + if(!Num.IsEmpty()) + { + NumToOutput = FCString::Atoi(*Num); + } + OutputMostFrequentlyUsedInstructions(Ar, NumToOutput); + return true; + } + else if (ParsedCommand.Equals(TEXT("TotalBytecodeSize"),ESearchCase::IgnoreCase)) + { + OutputTotalBytecodeSize(Ar); + return true; + } + } + + return false; +} + +#endif //SCRIPT_AUDIT_ROUTINES + // Switch for a lightweight process event counter, useful when disabling the blueprint guard // which can taint profiling results: #define LIGHTWEIGHT_PROCESS_EVENT_COUNTER 0 && !DO_BLUEPRINT_GUARD diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/SoftObjectPath.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/SoftObjectPath.cpp index b91fd86750a8..77620cce8fa1 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/SoftObjectPath.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/SoftObjectPath.cpp @@ -350,7 +350,30 @@ UObject* FSoftObjectPath::TryLoad(FUObjectSerializeContext* InLoadContext) const if (!IsNull()) { - LoadedObject = StaticLoadObject(UObject::StaticClass(), nullptr, *ToString(), nullptr, LOAD_None, nullptr, true, InLoadContext); + if (IsSubobject()) + { + // For subobjects, it's not safe to call LoadObject directly, so we want to load the parent object and then resolve again + FSoftObjectPath TopLevelPath = FSoftObjectPath(AssetPathName, FString()); + UObject* TopLevelObject = TopLevelPath.TryLoad(InLoadContext); + + // This probably loaded the top-level object, so re-resolve ourselves + return ResolveObject(); + } + + FString PathString = ToString(); +#if WITH_EDITOR + if (GPlayInEditorID != INDEX_NONE) + { + // If we are in PIE and this hasn't already been fixed up, we need to fixup at resolution time. We cannot modify the path as it may be somewhere like a blueprint CDO + FSoftObjectPath FixupObjectPath = *this; + if (FixupObjectPath.FixupForPIE()) + { + PathString = FixupObjectPath.ToString(); + } + } +#endif + + LoadedObject = StaticLoadObject(UObject::StaticClass(), nullptr, *PathString, nullptr, LOAD_None, nullptr, true, InLoadContext); #if WITH_EDITOR // Look at core redirects if we didn't find the object @@ -382,7 +405,6 @@ UObject* FSoftObjectPath::ResolveObject() const return nullptr; } - FString PathString = ToString(); #if WITH_EDITOR if (GPlayInEditorID != INDEX_NONE) { @@ -390,12 +412,31 @@ UObject* FSoftObjectPath::ResolveObject() const FSoftObjectPath FixupObjectPath = *this; if (FixupObjectPath.FixupForPIE()) { - PathString = FixupObjectPath.ToString(); + return FixupObjectPath.ResolveObjectInternal(); } } #endif - UObject* FoundObject = FindObject(nullptr, *PathString); + return ResolveObjectInternal(); +} + +UObject* FSoftObjectPath::ResolveObjectInternal() const +{ + if (SubPathString.IsEmpty()) + { + TCHAR PathString[FName::StringBufferSize]; + AssetPathName.ToString(PathString); + return ResolveObjectInternal(PathString); + } + else + { + return ResolveObjectInternal(*ToString()); + } +} + +UObject* FSoftObjectPath::ResolveObjectInternal(const TCHAR* PathString) const +{ + UObject* FoundObject = FindObject(nullptr, PathString); #if WITH_EDITOR // Look at core redirects if we didn't find the object @@ -404,8 +445,7 @@ UObject* FSoftObjectPath::ResolveObject() const FSoftObjectPath FixupObjectPath = *this; if (FixupObjectPath.FixupCoreRedirects()) { - PathString = FixupObjectPath.ToString(); - FoundObject = FindObject(nullptr, *PathString); + FoundObject = FindObject(nullptr, *FixupObjectPath.ToString()); } } #endif diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/SparseDelegate.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/SparseDelegate.cpp index d1e0acc9f495..3c045ca83653 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/SparseDelegate.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/SparseDelegate.cpp @@ -29,6 +29,13 @@ void FSparseDelegateStorage::FObjectListener::NotifyUObjectDeleted(const UObject } } +void FSparseDelegateStorage::FObjectListener::OnUObjectArrayShutdown() +{ + FScopeLock SparseDelegateMapLock(&FSparseDelegateStorage::SparseDelegateMapCritical); + FSparseDelegateStorage::SparseDelegates.Empty(); + DisableListener(); +} + void FSparseDelegateStorage::FObjectListener::EnableListener() { GUObjectArray.AddUObjectDeleteListener(this); diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectArray.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectArray.cpp index 6b3a2c4543db..8c1525d1544a 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectArray.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectArray.cpp @@ -197,7 +197,7 @@ void FUObjectArray::AllocateUObjectIndex(UObjectBase* Object, bool bMergingThrea void FUObjectArray::FreeUObjectIndex(UObjectBase* Object) { // This should only be happening on the game thread (GC runs only on game thread when it's freeing objects) - check(IsInGameThread()); + check(IsInGameThread() || IsInGarbageCollectorThread()); int32 Index = Object->InternalIndex; // At this point no two objects exist with the same index so no need to lock here @@ -206,17 +206,21 @@ void FUObjectArray::FreeUObjectIndex(UObjectBase* Object) UE_LOG(LogUObjectArray, Fatal, TEXT("Unexpected concurency while adding new object")); } - // @todo: threading: delete listeners should be locked while we're doing this - // Iterate in reverse order so that when one of the listeners removes itself from the array inside of NotifyUObjectDeleted we don't skip the next listener. - for (int32 ListenerIndex = UObjectDeleteListeners.Num() - 1; ListenerIndex >= 0; --ListenerIndex) { - UObjectDeleteListeners[ListenerIndex]->NotifyUObjectDeleted(Object, Index); +#if THREADSAFE_UOBJECTS + FScopeLock UObjectDeleteListenersLock(&UObjectDeleteListenersCritical); +#endif + // Iterate in reverse order so that when one of the listeners removes itself from the array inside of NotifyUObjectDeleted we don't skip the next listener. + for (int32 ListenerIndex = UObjectDeleteListeners.Num() - 1; ListenerIndex >= 0; --ListenerIndex) + { + UObjectDeleteListeners[ListenerIndex]->NotifyUObjectDeleted(Object, Index); + } } // You cannot safely recycle indicies in the non-GC range // No point in filling this list when doing exit purge. Nothing should be allocated afterwards anyway. + IndexToObject(Index)->ResetSerialNumberAndFlags(); if (Index > ObjLastNonGCIndex && !GExitPurge) { - IndexToObject(Index)->ResetSerialNumberAndFlags(); ObjAvailableList.Push((int32*)(uintptr_t)Index); #if UE_GC_TRACK_OBJ_AVAILABLE ObjAvailableCount.Increment(); @@ -335,4 +339,23 @@ int32 FUObjectArray::AllocateSerialNumber(int32 Index) */ void FUObjectArray::ShutdownUObjectArray() { + { +#if THREADSAFE_UOBJECTS + FScopeLock UObjectDeleteListenersLock(&UObjectDeleteListenersCritical); +#endif + for (int32 Index = UObjectDeleteListeners.Num() - 1; Index >= 0; --Index) + { + FUObjectDeleteListener* Listener = UObjectDeleteListeners[Index]; + Listener->OnUObjectArrayShutdown(); + } + UE_CLOG(UObjectDeleteListeners.Num(), LogUObjectArray, Fatal, TEXT("All UObject delete listeners should be unregistered when shutting down the UObject array")); + } + { + for (int32 Index = UObjectCreateListeners.Num() - 1; Index >= 0; --Index) + { + FUObjectCreateListener* Listener = UObjectCreateListeners[Index]; + Listener->OnUObjectArrayShutdown(); + } + UE_CLOG(UObjectCreateListeners.Num(), LogUObjectArray, Fatal, TEXT("All UObject delete listeners should be unregistered when shutting down the UObject array")); + } } diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectBase.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectBase.cpp index 142947745c15..4025c7f45dce 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectBase.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectBase.cpp @@ -125,7 +125,7 @@ UObjectBase::~UObjectBase() { // Validate it. check(IsValidLowLevel()); - LowLevelRename(NAME_None); + check(GetFName() == NAME_None); GUObjectArray.FreeUObjectIndex(this); } diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectClusters.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectClusters.cpp index 511eb292fe77..4b2b529e3972 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectClusters.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectClusters.cpp @@ -203,11 +203,11 @@ void FUObjectClusterContainer::DissolveClusterAndMarkObjectsAsUnreachable(FUObje } } -void FUObjectClusterContainer::DissolveClusters() +void FUObjectClusterContainer::DissolveClusters(bool bForceDissolveAllClusters /* = false */) { for (FUObjectCluster& Cluster : Clusters) { - if (Cluster.RootIndex >= 0 && Cluster.bNeedsDissolving) + if (Cluster.RootIndex >= 0 && (Cluster.bNeedsDissolving || bForceDissolveAllClusters)) { DissolveCluster(Cluster); } @@ -383,7 +383,7 @@ void ListClusters(const TArray& Args) Algo::SortBy(AllClusters, [](FUObjectCluster* A) { return GUObjectArray.IndexToObject(A->RootIndex)->Object->GetFName(); - }); + }, FNameLexicalLess()); } else if (Arg == TEXT("SortByObjectCount")) { diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectGlobals.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectGlobals.cpp index fd6820bc6117..d06c3e3b541e 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectGlobals.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectGlobals.cpp @@ -37,6 +37,7 @@ #include "Serialization/DuplicatedObject.h" #include "Serialization/DuplicatedDataReader.h" #include "Serialization/DuplicatedDataWriter.h" +#include "Serialization/LoadTimeTracePrivate.h" #include "Misc/PackageName.h" #include "UObject/LinkerLoad.h" #include "Blueprint/BlueprintSupport.h" @@ -467,9 +468,9 @@ void StaticTick( float DeltaTime, bool bUseFullTimeLimit, float AsyncLoadingTime #if STATS // Set name table stats. - int32 NameTableEntries = FName::GetMaxNames(); int32 NameTableAnsiEntries = FName::GetNumAnsiNames(); int32 NameTableWideEntries = FName::GetNumWideNames(); + int32 NameTableEntries = NameTableAnsiEntries + NameTableWideEntries; int32 NameTableMemorySize = FName::GetNameTableMemorySize(); SET_DWORD_STAT( STAT_NameTableEntries, NameTableEntries ); SET_DWORD_STAT( STAT_NameTableAnsiEntries, NameTableAnsiEntries ); @@ -1761,12 +1762,13 @@ FName MakeUniqueObjectName( UObject* Parent, UClass* Class, FName InBaseName/*=N do { // create the next name in the sequence for this class - if (BaseName.GetComparisonIndex() == NAME_Package) + static const FName NamePackage(NAME_Package); + if (BaseName == NamePackage) { if (Parent == NULL) { //package names should default to "/Temp/Untitled" when their parent is NULL. Otherwise they are a group. - TestName = FName(*FString::Printf(TEXT("/Temp/%s"), *FName(NAME_Untitled).ToString()), ++Class->ClassUnique); + TestName = FName(*FString::Printf(TEXT("/Temp/%s"), LexToString(NAME_Untitled)), ++Class->ClassUnique); } else { @@ -3075,12 +3077,13 @@ UObject* StaticConstructObject_Internal !InTemplate || (InName != NAME_None && (bAssumeTemplateIsArchetype || InTemplate == UObject::GetArchetypeFromRequiredInfo(InClass, InOuter, InName, InFlags))) ); + const bool bCanRecycleSubobjects = bIsNativeFromCDO && (!(InFlags & RF_DefaultSubObject) || !FUObjectThreadContext::Get().IsInConstructor) #if WITH_HOT_RELOAD // Do not recycle subobjects when performing hot-reload as they may contain old property values. - const bool bCanRecycleSubobjects = bIsNativeFromCDO && !GIsHotReload; -#else - const bool bCanRecycleSubobjects = bIsNativeFromCDO; + && !GIsHotReload #endif + ; + bool bRecycledSubobject = false; Result = StaticAllocateObject(InClass, InOuter, InName, InFlags, InternalSetFlags, bCanRecycleSubobjects, &bRecycledSubobject); check(Result != NULL); @@ -3482,11 +3485,15 @@ private: { #if ENABLE_GC_DEBUG_OUTPUT // this message is to help track down culprits behind "Object in PIE world still referenced" errors - if ( GIsEditor && !GIsPlayInEditorWorld && !CurrentObject->RootPackageHasAnyFlags(PKG_PlayInEditor) && Object->RootPackageHasAnyFlags(PKG_PlayInEditor) ) + if ( GIsEditor && !GIsPlayInEditorWorld && !CurrentObject->HasAnyFlags(RF_Transient) && Object->RootPackageHasAnyFlags(PKG_PlayInEditor) ) { - UE_LOG(LogGarbage, Warning, TEXT("GC detected illegal reference to PIE object from content [possibly via %s]:"), *ReferencingProperty->GetFullName()); - UE_LOG(LogGarbage, Warning, TEXT(" PIE object: %s"), *Object->GetFullName()); - UE_LOG(LogGarbage, Warning, TEXT(" NON-PIE object: %s"), *CurrentObject->GetFullName()); + UPackage* ReferencingPackage = CurrentObject->GetOutermost(); + if (!ReferencingPackage->HasAnyPackageFlags(PKG_PlayInEditor) && !ReferencingPackage->HasAnyFlags(RF_Transient)) + { + UE_LOG(LogGarbage, Warning, TEXT("GC detected illegal reference to PIE object from content [possibly via %s]:"), *ReferencingProperty->GetFullName()); + UE_LOG(LogGarbage, Warning, TEXT(" PIE object: %s"), *Object->GetFullName()); + UE_LOG(LogGarbage, Warning, TEXT(" NON-PIE object: %s"), *CurrentObject->GetFullName()); + } } #endif @@ -3635,7 +3642,7 @@ UScriptStruct* GetFallbackStruct() return TBaseStructure::Get(); } -UObject* FObjectInitializer::CreateDefaultSubobject(UObject* Outer, FName SubobjectFName, UClass* ReturnType, UClass* ClassToCreateByDefault, bool bIsRequired, bool bAbstract, bool bIsTransient) const +UObject* FObjectInitializer::CreateDefaultSubobject(UObject* Outer, FName SubobjectFName, UClass* ReturnType, UClass* ClassToCreateByDefault, bool bIsRequired, bool bIsTransient) const { UE_CLOG(!FUObjectThreadContext::Get().IsInConstructor, LogClass, Fatal, TEXT("Subobjects cannot be created outside of UObject constructors. UObject constructing subobjects cannot be created using new or placement new operator.")); if (SubobjectFName == NAME_None) @@ -3654,11 +3661,18 @@ UObject* FObjectInitializer::CreateDefaultSubobject(UObject* Outer, FName Subobj { check(OverrideClass->IsChildOf(ReturnType)); - // Abstract sub-objects are only allowed when explicitly created with CreateAbstractDefaultSubobject. - if (!OverrideClass->HasAnyClassFlags(CLASS_Abstract) || !bAbstract) + if (OverrideClass->HasAnyClassFlags(CLASS_Abstract)) + { + // Attempts to create an abstract class will return null. If it is not optional or the owning class is not also abstract report a warning. + if (!bIsRequired && !Outer->GetClass()->HasAnyClassFlags(CLASS_Abstract)) + { + UE_LOG(LogClass, Warning, TEXT("Required default subobject %s not created as requested class %s is abstract. Returning null."), *SubobjectFName.ToString(), *OverrideClass->GetName()); + } + } + else { UObject* Template = OverrideClass->GetDefaultObject(); // force the CDO to be created if it hasn't already - EObjectFlags SubobjectFlags = Outer->GetMaskedFlags(RF_PropagateToSubObjects); + EObjectFlags SubobjectFlags = Outer->GetMaskedFlags(RF_PropagateToSubObjects) | RF_DefaultSubObject; bool bOwnerArchetypeIsNotNative; UClass* OuterArchetypeClass; @@ -3708,7 +3722,6 @@ UObject* FObjectInitializer::CreateDefaultSubobject(UObject* Outer, FName Subobj #endif Outer->GetClass()->AddDefaultSubobject(Result, ReturnType); } - Result->SetFlags(RF_DefaultSubObject); // Clear PendingKill flag in case we recycled a subobject of a dead object. // @todo: we should not be recycling subobjects unless we're currently loading from a package Result->ClearInternalFlags(EInternalObjectFlags::PendingKill); @@ -3721,7 +3734,7 @@ UObject* FObjectInitializer::CreateEditorOnlyDefaultSubobject(UObject* Outer, FN #if WITH_EDITOR if (GIsEditor) { - UObject* EditorSubobject = CreateDefaultSubobject(Outer, SubobjectName, ReturnType, ReturnType, /*bIsRequired =*/ false, /*bIsAbstract =*/ false, bTransient); + UObject* EditorSubobject = CreateDefaultSubobject(Outer, SubobjectName, ReturnType, ReturnType, /*bIsRequired =*/ false, bTransient); if (EditorSubobject) { EditorSubobject->MarkAsEditorOnlySubobject(); diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectHash.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectHash.cpp index 2e683497f5f9..701ad9d6d1db 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectHash.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectHash.cpp @@ -7,6 +7,7 @@ #include "UObject/UObjectHash.h" #include "UObject/Class.h" #include "UObject/Package.h" +#include "Misc/AsciiSet.h" #include "Misc/PackageName.h" #include "HAL/IConsoleManager.h" @@ -409,7 +410,7 @@ public: */ static FORCEINLINE int32 GetObjectHash(FName ObjName) { - return (ObjName.GetComparisonIndex() ^ ObjName.GetNumber()); + return GetTypeHash(ObjName); } /** @@ -423,7 +424,7 @@ static FORCEINLINE int32 GetObjectHash(FName ObjName) */ static FORCEINLINE int32 GetObjectOuterHash(FName ObjName,PTRINT Outer) { - return ((ObjName.GetComparisonIndex() ^ ObjName.GetNumber()) ^ (Outer >> 6)); + return GetTypeHash(ObjName) + (Outer >> 6); } UObject* StaticFindObjectFastExplicitThreadSafe(FUObjectHashTables& ThreadHash, UClass* ObjectClass, FName ObjectName, const FString& ObjectPathName, bool bExactClass, EObjectFlags ExcludeFlags/*=0*/) @@ -536,13 +537,20 @@ UObject* StaticFindObjectFastInternalThreadSafe(FUObjectHashTables& ThreadHash, { // Find an object with the specified name and (optional) class, in any package; if bAnyPackage is false, only matches top-level packages FName ActualObjectName = ObjectName; - const FString ObjectNameString = ObjectName.ToString(); - const int32 DotIndex = FMath::Max(ObjectNameString.Find(TEXT("."), ESearchCase::CaseSensitive, ESearchDir::FromEnd), - ObjectNameString.Find(TEXT(":"), ESearchCase::CaseSensitive, ESearchDir::FromEnd)); - if (DotIndex != INDEX_NONE) + FName VerifyOuterName; + TCHAR PlainObjectName[NAME_SIZE]; + int32 PlainObjectNameLen = ObjectName.GetPlainNameString(PlainObjectName); + + // Drop part prefixed by '.' or ':' and keep the suffix part + constexpr FAsciiSet DotColon(".:"); + const TCHAR* DelimiterOrEnd = FAsciiSet::FindLastOrEnd(PlainObjectName, DotColon); + if (*DelimiterOrEnd) { - ActualObjectName = FName(*ObjectNameString.Mid(DotIndex + 1)); + ActualObjectName = FName(DelimiterOrEnd + 1, ObjectName.GetNumber()); + int32 OuterLen = static_cast(DelimiterOrEnd - PlainObjectName); + VerifyOuterName = FName(OuterLen, PlainObjectName); } + const int32 Hash = GetObjectHash(ActualObjectName); FHashTableLock HashLock(ThreadHash); @@ -570,7 +578,7 @@ UObject* StaticFindObjectFastInternalThreadSafe(FUObjectHashTables& ThreadHash, && !Object->HasAnyInternalFlags(ExclusiveInternalFlags) /** Ensure that the partial path provided matches the object found */ - && (Object->GetPathName().EndsWith(ObjectNameString))) + && (VerifyOuterName.IsNone() || (Object->GetOuter() && Object->GetOuter()->GetFName() == VerifyOuterName))) { checkf(!Object->IsUnreachable(), TEXT("%s"), *Object->GetFullName()); if (Result) @@ -711,6 +719,8 @@ static FAutoConsoleCommand ShrinkUObjectHashTablesCmd( void GetObjectsWithOuter(const class UObjectBase* Outer, TArray& Results, bool bIncludeNestedObjects, EObjectFlags ExclusionFlags, EInternalObjectFlags ExclusionInternalFlags) { + checkf(Outer != nullptr, TEXT("Getting objects with a null outer is no longer supported. If you want to get all packages you might consider using GetObjectsOfClass instead.")); + // We don't want to return any objects that are currently being background loaded unless we're using the object iterator during async loading. ExclusionInternalFlags |= EInternalObjectFlags::Unreachable; if (!IsInAsyncLoadingThread()) @@ -759,6 +769,8 @@ void GetObjectsWithOuter(const class UObjectBase* Outer, TArray& Resu void ForEachObjectWithOuter(const class UObjectBase* Outer, TFunctionRef Operation, bool bIncludeNestedObjects, EObjectFlags ExclusionFlags, EInternalObjectFlags ExclusionInternalFlags) { + checkf(Outer != nullptr, TEXT("Getting objects with a null outer is no longer supported. If you want to get all packages you might consider using GetObjectsOfClass instead.")); + // We don't want to return any objects that are currently being background loaded unless we're using the object iterator during async loading. ExclusionInternalFlags |= EInternalObjectFlags::Unreachable; if (!IsInAsyncLoadingThread()) @@ -996,11 +1008,15 @@ void HashObject(UObjectBase* Object) checkSlow(!ThreadHash.PairExistsInHash(Hash, Object)); // if it already exists, something is wrong with the external code ThreadHash.AddToHash(Hash, Object); - Hash = GetObjectOuterHash( Name, (PTRINT)Object->GetOuter() ); - checkSlow( !ThreadHash.HashOuter.FindPair( Hash, Object ) ); // if it already exists, something is wrong with the external code - ThreadHash.HashOuter.Add( Hash, Object ); + if (PTRINT Outer = (PTRINT)Object->GetOuter()) + { + Hash = GetObjectOuterHash(Name, Outer); + checkSlow(!ThreadHash.HashOuter.FindPair(Hash, Object)); // if it already exists, something is wrong with the external code + ThreadHash.HashOuter.Add(Hash, Object); + + AddToOuterMap(ThreadHash, Object); + } - AddToOuterMap( ThreadHash, Object ); AddToClassMap( ThreadHash, Object ); } } @@ -1027,11 +1043,15 @@ void UnhashObject(UObjectBase* Object) NumRemoved = ThreadHash.RemoveFromHash(Hash, Object); check(NumRemoved == 1); // must have existed, else something is wrong with the external code - Hash = GetObjectOuterHash( Name, (PTRINT)Object->GetOuter() ); - NumRemoved = ThreadHash.HashOuter.RemoveSingle( Hash, Object ); - check( NumRemoved == 1 ); // must have existed, else something is wrong with the external code + if (PTRINT Outer = (PTRINT)Object->GetOuter()) + { + Hash = GetObjectOuterHash(Name, Outer); + NumRemoved = ThreadHash.HashOuter.RemoveSingle(Hash, Object); + check(NumRemoved == 1); // must have existed, else something is wrong with the external code + + RemoveFromOuterMap(ThreadHash, Object); + } - RemoveFromOuterMap( ThreadHash, Object ); RemoveFromClassMap( ThreadHash, Object ); } } @@ -1214,5 +1234,13 @@ void LogHashOuterStatistics(FOutputDevice& Ar, const bool bShowHashBucketCollisi FHashTableLock HashLock(FUObjectHashTables::Get()); LogHashStatisticsInternal(FUObjectHashTables::Get().HashOuter, Ar, bShowHashBucketCollisionInfo); Ar.Logf(TEXT("")); + + uint32 HashOuterMapSize = 0; + for (TPair& OuterMapEntry : FUObjectHashTables::Get().ObjectOuterMap) + { + HashOuterMapSize += OuterMapEntry.Value.GetItemsSize(); + } + Ar.Logf(TEXT("Total memory allocated for Object Outer Map: %u bytes."), HashOuterMapSize); + Ar.Logf(TEXT("")); } diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectMarks.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectMarks.cpp index 3617ad860fe9..f1360f8e970b 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectMarks.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectMarks.cpp @@ -38,6 +38,12 @@ public: RemoveAnnotation(Object); } + virtual void OnUObjectArrayShutdown() override + { + RemoveAllAnnotations(); + GUObjectArray.RemoveUObjectDeleteListener(this); + } + FUObjectAnnotationSparseNoSync() : AnnotationCacheKey(NULL) { diff --git a/Engine/Source/Runtime/CoreUObject/Public/CoreUObject.h b/Engine/Source/Runtime/CoreUObject/Public/CoreUObject.h index f1eccdd8eda8..7dadcff7f4f4 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/CoreUObject.h +++ b/Engine/Source/Runtime/CoreUObject/Public/CoreUObject.h @@ -62,9 +62,6 @@ MONOLITHIC_HEADER_BOILERPLATE() #include "Serialization/FindReferencersArchive.h" #include "Serialization/FindObjectReferencers.h" #include "Serialization/ArchiveFindCulprit.h" -#include "Serialization/ArchiveObjectGraph.h" -#include "Serialization/TraceReferences.h" -#include "Serialization/ArchiveTraceRoute.h" #include "Serialization/DuplicatedObject.h" #include "Serialization/DuplicatedDataReader.h" #include "Serialization/DuplicatedDataWriter.h" diff --git a/Engine/Source/Runtime/CoreUObject/Public/Internationalization/PackageLocalizationManager.h b/Engine/Source/Runtime/CoreUObject/Public/Internationalization/PackageLocalizationManager.h index 3a5ba598fe3e..c4628221e766 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Internationalization/PackageLocalizationManager.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Internationalization/PackageLocalizationManager.h @@ -56,6 +56,11 @@ public: */ FName FindLocalizedPackageNameForCulture(const FName InSourcePackageName, const FString& InCultureName); + /** + * Update this cache, but only if it is dirty. + */ + void ConditionalUpdateCache(); + /** * Singleton accessor. * diff --git a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ArchiveObjectGraph.h b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ArchiveObjectGraph.h index d81399307052..d8e9ab59fae5 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ArchiveObjectGraph.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ArchiveObjectGraph.h @@ -87,6 +87,7 @@ class FArchiveObjectGraph : public FArchiveUObject EObjectFlags RequiredFlags; public: + UE_DEPRECATED(4.23, "This class is out of date and misses many references, replace with FReferenceChainSearch or FFindReferencersArchive") FArchiveObjectGraph(bool IncludeTransients, EObjectFlags KeepFlags); ~FArchiveObjectGraph(); diff --git a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ArchiveTraceRoute.h b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ArchiveTraceRoute.h index 5a5b4763ff31..934d24b789a5 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ArchiveTraceRoute.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ArchiveTraceRoute.h @@ -6,10 +6,7 @@ #include "Serialization/ArchiveUObject.h" #include "Serialization/ArchiveObjectGraph.h" -/** - * Archive for finding shortest path from root to a particular object. - * Depth-first search. - */ +// DEPRECATED: This class is out of date and misses many GC references, replace with FReferenceChainSearch class FArchiveTraceRoute : public FArchiveUObject { /** @@ -36,6 +33,8 @@ class FArchiveTraceRoute : public FArchiveUObject }; public: + + UE_DEPRECATED(4.23, "This function is out of date and misses many GC references, replace with FReferenceChainSearch") static COREUOBJECT_API TMap FindShortestRootPath( UObject* Object, bool bIncludeTransients, EObjectFlags KeepFlags ); /** diff --git a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ArchiveUObjectFromStructuredArchive.h b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ArchiveUObjectFromStructuredArchive.h index 1ef40370b2c1..57bb620261d4 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ArchiveUObjectFromStructuredArchive.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ArchiveUObjectFromStructuredArchive.h @@ -11,6 +11,8 @@ #include "UObject/LazyObjectPtr.h" #include "UObject/WeakObjectPtr.h" +#if WITH_TEXT_ARCHIVE_SUPPORT + class COREUOBJECT_API FArchiveUObjectFromStructuredArchive : public FArchiveFromStructuredArchive { public: @@ -42,4 +44,19 @@ private: TMap SoftObjectPathToIndex; virtual void SerializeInternal(FStructuredArchive::FRecord Record) override; -}; \ No newline at end of file +}; + +#else + +class COREUOBJECT_API FArchiveUObjectFromStructuredArchive : public FArchiveFromStructuredArchive +{ +public: + + FArchiveUObjectFromStructuredArchive(FStructuredArchive::FSlot InSlot) + : FArchiveFromStructuredArchive(InSlot) + { + + } +}; + +#endif \ No newline at end of file diff --git a/Engine/Source/Runtime/CoreUObject/Public/Serialization/BulkData.h b/Engine/Source/Runtime/CoreUObject/Public/Serialization/BulkData.h index 83b8f27bfa9b..04f58c5b3006 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Serialization/BulkData.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Serialization/BulkData.h @@ -744,7 +744,7 @@ class FFormatContainer { friend class UBodySetup; - TSortedMap Formats; + TSortedMap Formats; uint32 Alignment; public: ~FFormatContainer() diff --git a/Engine/Source/Runtime/CoreUObject/Public/Serialization/FindReferencersArchive.h b/Engine/Source/Runtime/CoreUObject/Public/Serialization/FindReferencersArchive.h index 0aa72b5c4ad0..0391e55f02cc 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Serialization/FindReferencersArchive.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Serialization/FindReferencersArchive.h @@ -11,7 +11,7 @@ /** * Archive for mapping out the referencers of a collection of objects. */ -class FFindReferencersArchive : public FArchiveUObject +class COREUOBJECT_VTABLE FFindReferencersArchive : public FArchiveUObject { public: /** diff --git a/Engine/Source/Runtime/CoreUObject/Public/Serialization/Formatters/JsonArchiveInputFormatter.h b/Engine/Source/Runtime/CoreUObject/Public/Serialization/Formatters/JsonArchiveInputFormatter.h index 609a5330f051..3b7d944771e3 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Serialization/Formatters/JsonArchiveInputFormatter.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Serialization/Formatters/JsonArchiveInputFormatter.h @@ -90,6 +90,7 @@ private: TArray ObjectStack; TArray> ValueStack; TArray>::TIterator> MapIteratorStack; + TArray ArrayValuesRemainingStack; static FString EscapeFieldName(const TCHAR* Name); static FString UnescapeFieldName(const TCHAR* Name); diff --git a/Engine/Source/Runtime/CoreUObject/Public/Serialization/LoadTimeTrace.h b/Engine/Source/Runtime/CoreUObject/Public/Serialization/LoadTimeTrace.h new file mode 100644 index 000000000000..316516fd6b0d --- /dev/null +++ b/Engine/Source/Runtime/CoreUObject/Public/Serialization/LoadTimeTrace.h @@ -0,0 +1,93 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "Trace/Trace.h" + +#if UE_TRACE_ENABLED && !UE_BUILD_SHIPPING +#define LOADTIMEPROFILERTRACE_ENABLED 1 +#else +#define LOADTIMEPROFILERTRACE_ENABLED 0 +#endif + +enum ELoadTimeProfilerPackageEventType +{ + LoadTimeProfilerPackageEventType_CreateLinker, + LoadTimeProfilerPackageEventType_FinishLinker, + LoadTimeProfilerPackageEventType_StartImportPackages, + LoadTimeProfilerPackageEventType_SetupImports, + LoadTimeProfilerPackageEventType_SetupExports, + LoadTimeProfilerPackageEventType_ProcessImportsAndExports, + LoadTimeProfilerPackageEventType_ExportsDone, + LoadTimeProfilerPackageEventType_PostLoadWait, + LoadTimeProfilerPackageEventType_StartPostLoad, + LoadTimeProfilerPackageEventType_Tick, + LoadTimeProfilerPackageEventType_Finish, + LoadTimeProfilerPackageEventType_DeferredPostLoad, + LoadTimeProfilerPackageEventType_None, +}; + +enum ELoadTimeProfilerObjectEventType +{ + LoadTimeProfilerObjectEventType_Create, + LoadTimeProfilerObjectEventType_Serialize, + LoadTimeProfilerObjectEventType_PostLoad, + LoadTimeProfilerObjectEventType_None +}; + +#if LOADTIMEPROFILERTRACE_ENABLED + +struct FStreamableHandle; + +struct FLoadTimeProfilerTrace +{ + COREUOBJECT_API static void OutputNewStreamableHandle(const FStreamableHandle* StreamableHandle, const TCHAR* DebugName, bool IsCombined); + COREUOBJECT_API static void OutputDestroyStreamableHandle(const FStreamableHandle* StreamableHandle); + COREUOBJECT_API static void OutputBeginLoadStreamableHandle(const FStreamableHandle* StreamableHandle); + COREUOBJECT_API static void OutputEndLoadStreamableHandle(const FStreamableHandle* StreamableHandle); + COREUOBJECT_API static void OutputStreamableHandleRequestAssociation(const FStreamableHandle* StreamableHandle, uint64 RequestId); + + struct FLoadMapScope + { + COREUOBJECT_API FLoadMapScope(const TCHAR* Name); + COREUOBJECT_API ~FLoadMapScope(); + }; + + struct FWaitForStreamableHandleScope + { + COREUOBJECT_API FWaitForStreamableHandleScope(const FStreamableHandle* StreamableHandle); + COREUOBJECT_API ~FWaitForStreamableHandleScope(); + }; +}; + +#define TRACE_LOADTIME_LOAD_MAP_SCOPE(Name) \ + FLoadTimeProfilerTrace::FLoadMapScope __LoadTimeTraceLoadMapScope(Name); + +#define TRACE_LOADTIME_NEW_STREAMABLE_HANDLE(StreamableHandle, DebugName, IsCombined) \ + FLoadTimeProfilerTrace::OutputNewStreamableHandle(StreamableHandle, DebugName, IsCombined); + +#define TRACE_LOADTIME_DESTROY_STREAMABLE_HANDLE(StreamableHandle) \ + FLoadTimeProfilerTrace::OutputDestroyStreamableHandle(StreamableHandle); + +#define TRACE_LOADTIME_BEGIN_LOAD_STREAMABLE_HANDLE(StreamableHandle) \ + FLoadTimeProfilerTrace::OutputBeginLoadStreamableHandle(StreamableHandle); + +#define TRACE_LOADTIME_END_LOAD_STREAMABLE_HANDLE(StreamableHandle) \ + FLoadTimeProfilerTrace::OutputEndLoadStreamableHandle(StreamableHandle); + +#define TRACE_LOADTIME_STREAMABLE_HANDLE_REQUEST_ASSOCIATION(StreamableHandle, RequestId) \ + FLoadTimeProfilerTrace::OutputStreamableHandleRequestAssociation(StreamableHandle, RequestId); + +#define TRACE_LOADTIME_WAIT_FOR_STREAMABLE_HANDLE_SCOPE(StreamableHandle) \ + FLoadTimeProfilerTrace::FWaitForStreamableHandleScope __LoadTimeTraceWaitForStreamableHandleScope(StreamableHandle); + +#else +#define TRACE_LOADTIME_LOAD_MAP_SCOPE(...) +#define TRACE_LOADTIME_NEW_STREAMABLE_HANDLE(...) +#define TRACE_LOADTIME_DESTROY_STREAMABLE_HANDLE(...) +#define TRACE_LOADTIME_BEGIN_LOAD_STREAMABLE_HANDLE(...) +#define TRACE_LOADTIME_END_LOAD_STREAMABLE_HANDLE(...) +#define TRACE_LOADTIME_STREAMABLE_HANDLE_REQUEST_ASSOCIATION(...) +#define TRACE_LOADTIME_WAIT_FOR_STREAMABLE_HANDLE_SCOPE(...) +#endif diff --git a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectAndNameAsStringProxyArchive.h b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectAndNameAsStringProxyArchive.h index eeb5a76c5946..f1ee3758f1f2 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectAndNameAsStringProxyArchive.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectAndNameAsStringProxyArchive.h @@ -18,7 +18,7 @@ class UObject; * * @param InInnerArchive The actual FArchive object to serialize normal data types (FStrings, INTs, etc) */ -struct FObjectAndNameAsStringProxyArchive : public FNameAsStringProxyArchive +struct COREUOBJECT_VTABLE FObjectAndNameAsStringProxyArchive : public FNameAsStringProxyArchive { /** * Creates and initializes a new instance. diff --git a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectReader.h b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectReader.h index 9e13ba863dfa..d6d036d31190 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectReader.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectReader.h @@ -16,7 +16,7 @@ struct FWeakObjectPtr; /** * UObject Memory Reader Archive. Reads from InBytes, writes to Obj. */ -class FObjectReader : public FMemoryArchive +class COREUOBJECT_VTABLE FObjectReader : public FMemoryArchive { public: FObjectReader(UObject* Obj, TArray& InBytes, bool bIgnoreClassRef = false, bool bIgnoreArchetypeRef = false) diff --git a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectWriter.h b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectWriter.h index 44fef71f06d5..891fb8eca378 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectWriter.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectWriter.h @@ -16,7 +16,7 @@ struct FWeakObjectPtr; /** * UObject Memory Writer Archive. */ -class FObjectWriter : public FMemoryWriter +class COREUOBJECT_VTABLE FObjectWriter : public FMemoryWriter { public: diff --git a/Engine/Source/Runtime/CoreUObject/Public/Serialization/TraceReferences.h b/Engine/Source/Runtime/CoreUObject/Public/Serialization/TraceReferences.h index aba067d9a30f..f6d776d1a8c9 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Serialization/TraceReferences.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Serialization/TraceReferences.h @@ -16,6 +16,7 @@ class FTraceReferences void GetReferencedInternal( UObject* CurrentObject, TArray &OutReferenced, int32 CurrentDepth, int32 TargetDepth ); public: + UE_DEPRECATED(4.23, "This class is out of date and misses many references, replace with FReferenceChainSearch or FFindReferencersArchive") FTraceReferences( bool bIncludeTransients = false, EObjectFlags KeepFlags = RF_AllFlags ); // returns referencer string of an object diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/Class.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/Class.h index 0de292306520..d3ae4a889ec6 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/Class.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/Class.h @@ -7,22 +7,24 @@ #pragma once #include "CoreMinimal.h" -#include "UObject/Script.h" -#include "UObject/ObjectMacros.h" -#include "UObject/UObjectGlobals.h" -#include "UObject/Object.h" + +#include "Concepts/GetTypeHashable.h" +#include "Math/RandomStream.h" +#include "Misc/EnumClassFlags.h" #include "Misc/FallbackStruct.h" #include "Misc/Guid.h" -#include "Math/RandomStream.h" -#include "UObject/GarbageCollection.h" -#include "UObject/CoreNative.h" -#include "UObject/ReflectedTypeAccessors.h" -#include "Templates/HasGetTypeHash.h" +#include "Misc/Optional.h" +#include "Misc/ScopeRWLock.h" #include "Templates/IsAbstract.h" #include "Templates/IsEnum.h" -#include "Misc/Optional.h" -#include "Misc/EnumClassFlags.h" -#include "Misc/ScopeRWLock.h" +#include "Templates/Models.h" +#include "UObject/CoreNative.h" +#include "UObject/GarbageCollection.h" +#include "UObject/Object.h" +#include "UObject/ObjectMacros.h" +#include "UObject/ReflectedTypeAccessors.h" +#include "UObject/Script.h" +#include "UObject/UObjectGlobals.h" struct FCustomPropertyListNode; struct FFrame; @@ -82,6 +84,13 @@ class COREUOBJECT_API UField : public UObject /** Goes up the outer chain to look for a UStruct */ UStruct* GetOwnerStruct() const; + /** + * Returns a human readable string that was assigned to this field at creation. + * By default this is the same as GetName() but it can be overridden if that is an internal-only name. + * This name is consistent in editor/cooked builds, is not localized, and is useful for data import/export. + */ + FString GetAuthoredName() const; + #if WITH_EDITOR || HACK_HEADER_GENERATOR /** * Finds the localized display name or native display name as a fallback. @@ -284,7 +293,7 @@ public: public: // Constructors. - UStruct( EStaticConstructor, int32 InSize, EObjectFlags InFlags ); + UStruct( EStaticConstructor, int32 InSize, int32 InAlignment, EObjectFlags InFlags ); explicit UStruct(UStruct* InSuperStruct, SIZE_T ParamsSize = 0, SIZE_T Alignment = 0); explicit UStruct(const FObjectInitializer& ObjectInitializer, UStruct* InSuperStruct, SIZE_T ParamsSize = 0, SIZE_T Alignment = 0 ); @@ -323,9 +332,14 @@ public: /** Creates the field/property links and gets structure ready for use at runtime */ virtual void Link(FArchive& Ar, bool bRelinkExistingProperties); + /** Serializes struct properties, does not handle defaults*/ + virtual void SerializeBin(FArchive& Ar, void* Data) const final + { + SerializeBin(FStructuredArchiveFromArchive(Ar).GetSlot(), Data); + } + /** Serializes struct properties, does not handle defaults */ virtual void SerializeBin(FStructuredArchive::FSlot Slot, void* Data) const; - virtual void SerializeBin(FArchive& Ar, void* Data) const; /** * Serializes the class properties that reside in Data if they differ from the corresponding values in DefaultData @@ -338,8 +352,13 @@ public: void SerializeBinEx( FStructuredArchive::FSlot Slot, void* Data, void const* DefaultData, UStruct* DefaultStruct ) const; /** Serializes list of properties, using property tags to handle mismatches */ - virtual void SerializeTaggedProperties( FArchive& Ar, uint8* Data, UStruct* DefaultsStruct, uint8* Defaults, const UObject* BreakRecursionIfFullyLoad=nullptr) const; - virtual void SerializeTaggedProperties( FStructuredArchive::FSlot Slot, uint8* Data, UStruct* DefaultsStruct, uint8* Defaults, const UObject* BreakRecursionIfFullyLoad = nullptr) const; + virtual void SerializeTaggedProperties(FArchive& Ar, uint8* Data, UStruct* DefaultsStruct, uint8* Defaults, const UObject* BreakRecursionIfFullyLoad = nullptr) const final + { + SerializeTaggedProperties(FStructuredArchiveFromArchive(Ar).GetSlot(), Data, DefaultsStruct, Defaults, BreakRecursionIfFullyLoad); + } + + /** Serializes list of properties, using property tags to handle mismatches */ + virtual void SerializeTaggedProperties(FStructuredArchive::FSlot Slot, uint8* Data, UStruct* DefaultsStruct, uint8* Defaults, const UObject* BreakRecursionIfFullyLoad = nullptr) const; /** * Initialize a struct over uninitialized memory. This may be done by calling the native constructor or individually initializing properties @@ -360,10 +379,8 @@ public: virtual void DestroyStruct(void* Dest, int32 ArrayDim = 1) const; public: -#if WITH_EDITOR - /** Used to search for properties in user defined structs */ + /** Look up a property by an alternate name if it was not found in the first search, this is overridden for user structs */ virtual UProperty* CustomFindProperty(const FName InName) const { return nullptr; }; -#endif // WITH_EDITOR /** Serialize an expression to an archive. Returns expression token */ virtual EExprToken SerializeExpr(int32& iCode, FArchive& Ar); @@ -428,11 +445,11 @@ public: */ virtual void SetSuperStruct(UStruct* NewSuperStruct); - /** Returns the a human readable string for a property, overridden for user defined structs */ - virtual FString PropertyNameToDisplayName(FName InName) const - { - return InName.ToString(); - } + UE_DEPRECATED(4.23, "Replace with GetAuthoredNameForField or UField::GetAuthoredName") + virtual FString PropertyNameToDisplayName(FName InName) const; + + /** Returns a human readable string for a given field, overridden for user defined structs */ + virtual FString GetAuthoredNameForField(const UField* Field) const; #if WITH_EDITOR || HACK_HEADER_GENERATOR /** Try and find boolean metadata with the given key. If not found on this class, work up hierarchy looking for it. */ @@ -542,14 +559,11 @@ enum EStructFlags /** If set, this struct can share net serialization state across connections */ STRUCT_NetSharedSerialization = 0x00400000, - /** */ - STRUCT_SerializeNativeStructured = 0x00800000, - /** Struct flags that are automatically inherited */ STRUCT_Inherit = STRUCT_HasInstancedReference|STRUCT_Atomic, /** Flags that are always computed, never loaded or done with code generation */ - STRUCT_ComputedFlags = STRUCT_NetDeltaSerializeNative | STRUCT_NetSerializeNative | STRUCT_SerializeNative | STRUCT_SerializeNativeStructured | STRUCT_PostSerializeNative | STRUCT_CopyNative | STRUCT_IsPlainOldData | STRUCT_NoDestructor | STRUCT_ZeroConstructor | STRUCT_IdenticalNative | STRUCT_AddStructReferencedObjects | STRUCT_ExportTextItemNative | STRUCT_ImportTextItemNative | STRUCT_SerializeFromMismatchedTag | STRUCT_PostScriptConstruct | STRUCT_NetSharedSerialization + STRUCT_ComputedFlags = STRUCT_NetDeltaSerializeNative | STRUCT_NetSerializeNative | STRUCT_SerializeNative | STRUCT_PostSerializeNative | STRUCT_CopyNative | STRUCT_IsPlainOldData | STRUCT_NoDestructor | STRUCT_ZeroConstructor | STRUCT_IdenticalNative | STRUCT_AddStructReferencedObjects | STRUCT_ExportTextItemNative | STRUCT_ImportTextItemNative | STRUCT_SerializeFromMismatchedTag | STRUCT_PostScriptConstruct | STRUCT_NetSharedSerialization }; @@ -826,13 +840,13 @@ FORCEINLINE typename TEnableIf::WithStructuredSe * Selection of GetTypeHash call. */ template -FORCEINLINE typename TEnableIf::Value, uint32>::Type GetTypeHashOrNot(const CPPSTRUCT *Data) +FORCEINLINE typename TEnableIf::Value, uint32>::Type GetTypeHashOrNot(const CPPSTRUCT *Data) { return 0; } template -FORCEINLINE typename TEnableIf::Value, uint32>::Type GetTypeHashOrNot(const CPPSTRUCT *Data) +FORCEINLINE typename TEnableIf::Value, uint32>::Type GetTypeHashOrNot(const CPPSTRUCT *Data) { return GetTypeHash(*Data); } @@ -841,7 +855,7 @@ FORCEINLINE typename TEnableIf::Value, uint32>::Type /** * Reflection data for a standalone structure declared in a header or as a user defined struct */ -class UScriptStruct : public UStruct +class COREUOBJECT_VTABLE UScriptStruct : public UStruct { public: /** Interface to template to manage dynamic access to C++ struct construction and destruction **/ @@ -1154,7 +1168,7 @@ public: virtual bool HasGetTypeHash() override { - return THasGetTypeHash::Value; + return TModels::Value; } uint32 GetTypeHash(const void* Src) override { @@ -1167,7 +1181,7 @@ public: (TIsPODType::Value ? CPF_IsPlainOldData : CPF_None) | (TIsTriviallyDestructible::Value ? CPF_NoDestructor : CPF_None) | (TIsZeroConstructType::Value ? CPF_ZeroConstructor : CPF_None) - | (THasGetTypeHash::Value ? CPF_HasGetValueTypeHash : CPF_None); + | (TModels::Value ? CPF_HasGetValueTypeHash : CPF_None); } bool IsAbstract() const override { @@ -1189,7 +1203,7 @@ public: DECLARE_CASTED_CLASS_INTRINSIC_NO_CTOR(UScriptStruct, UStruct, CLASS_MatchedSerializers, TEXT("/Script/CoreUObject"), CASTCLASS_UScriptStruct, COREUOBJECT_API) - COREUOBJECT_API UScriptStruct( EStaticConstructor, int32 InSize, EObjectFlags InFlags ); + COREUOBJECT_API UScriptStruct( EStaticConstructor, int32 InSize, int32 InAlignment, EObjectFlags InFlags ); COREUOBJECT_API explicit UScriptStruct(const FObjectInitializer& ObjectInitializer, UScriptStruct* InSuperStruct, ICppStructOps* InCppStructOps = nullptr, EStructFlags InStructFlags = STRUCT_NoFlags, SIZE_T ExplicitSize = 0, SIZE_T ExplicitAlignment = 0); COREUOBJECT_API explicit UScriptStruct(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); @@ -1274,7 +1288,7 @@ public: /** Returns true if this struct has a native serialize function */ bool UseNativeSerialization() const { - if ((StructFlags&(STRUCT_SerializeNative | STRUCT_SerializeNativeStructured)) != 0) + if ((StructFlags&(STRUCT_SerializeNative)) != 0) { return true; } @@ -1578,13 +1592,19 @@ public: typedef FText(*FEnumDisplayNameFn)(int32); -// Optional flags for the UEnum::Get*ByName() functions. +/** Optional flags for the UEnum::Get*ByName() functions. */ enum class EGetByNameFlags { None = 0, - ErrorIfNotFound = 0x01, - CaseSensitive = 0x02 + /** Outputs an warning if the enum lookup fails */ + ErrorIfNotFound = 0x01, + + /** Does a case sensitive match */ + CaseSensitive = 0x02, + + /** Checks the GetAuthoredNameStringByIndex value as well as normal names */ + CheckAuthoredName = 0x04, }; ENUM_CLASS_FLAGS(EGetByNameFlags) @@ -1671,6 +1691,20 @@ public: /** Version of GetDisplayNameTextByIndex that takes a value instead */ FText GetDisplayNameTextByValue(int64 InValue) const; + /** + * Returns the unlocalized logical name originally assigned to the enum at creation. + * By default this is the same as the short name but it is overridden in child classes with different internal name storage. + * This name is consistent in cooked and editor builds and is useful for things like external data import/export. + * + * @param InIndex Index of the enum value to get Display Name for + * + * @return The author-specified name, or an empty string if Index is invalid + */ + virtual FString GetAuthoredNameStringByIndex(int32 InIndex) const; + + /** Version of GetAuthoredNameByIndex that takes a value instead */ + FString GetAuthoredNameStringByValue(int64 InValue) const; + /** Gets max value of Enum. Defaults to zero if there are no entries. */ int64 GetMaxEnumValue() const; @@ -2323,7 +2357,7 @@ public: // Constructors UClass(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); explicit UClass(const FObjectInitializer& ObjectInitializer, UClass* InSuperClass); - UClass( EStaticConstructor, FName InName, uint32 InSize, EClassFlags InClassFlags, EClassCastFlags InClassCastFlags, + UClass( EStaticConstructor, FName InName, uint32 InSize, uint32 InAlignment, EClassFlags InClassFlags, EClassCastFlags InClassCastFlags, const TCHAR* InClassConfigName, EObjectFlags InFlags, ClassConstructorType InClassConstructor, ClassVTableHelperCtorCallerType InClassVTableHelperCtorCaller, ClassAddReferencedObjectsType InClassAddReferencedObjects); @@ -2686,10 +2720,18 @@ public: /** serializes the passed in object as this class's default object using the given archive * @param Object the object to serialize as default - * @param Ar the archive to serialize from + * @param Slot the structured archive slot to serialize from */ virtual void SerializeDefaultObject(UObject* Object, FStructuredArchive::FSlot Slot); - virtual void SerializeDefaultObject(UObject* Object, FArchive& Ar); + + /** serializes the passed in object as this class's default object using the given archive + * @param Object the object to serialize as default + * @param Ar the archive to serialize from + */ + virtual void SerializeDefaultObject(UObject* Object, FArchive& Ar) final + { + SerializeDefaultObject(Object, FStructuredArchiveFromArchive(Ar).GetSlot()); + } /** Wraps the PostLoad() call for the class default object. * @param Object the default object to call PostLoad() on @@ -2725,7 +2767,10 @@ public: * @param InFunctionName The name of the function to test * @return True if the specified function exists and is implemented in a blueprint generated class */ - virtual bool IsFunctionImplementedInBlueprint(FName InFunctionName) const; + virtual bool IsFunctionImplementedInScript(FName InFunctionName) const; + + UE_DEPRECATED(4.23, "IsFunctionImplementedInBlueprint is deprecated, call IsFunctionImplementedInScript instead") + bool IsFunctionImplementedInBlueprint(FName InFunctionName) const { return IsFunctionImplementedInScript(InFunctionName); } /** * Checks if the property exists on this class or a parent class. @@ -2820,7 +2865,7 @@ public: UDynamicClass(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); explicit UDynamicClass(const FObjectInitializer& ObjectInitializer, UClass* InSuperClass); - UDynamicClass(EStaticConstructor, FName InName, uint32 InSize, EClassFlags InClassFlags, EClassCastFlags InClassCastFlags, + UDynamicClass(EStaticConstructor, FName InName, uint32 InSize, uint32 InAlignment, EClassFlags InClassFlags, EClassCastFlags InClassCastFlags, const TCHAR* InClassConfigName, EObjectFlags InFlags, ClassConstructorType InClassConstructor, ClassVTableHelperCtorCallerType InClassVTableHelperCtorCaller, ClassAddReferencedObjectsType InClassAddReferencedObjects); @@ -2889,6 +2934,7 @@ COREUOBJECT_API void InitializePrivateStaticClass( * @param ReturnClass reference to pointer to result. This must be PrivateStaticClass. * @param RegisterNativeFunc Native function registration function pointer. * @param InSize Size of the class + * @param InAlignment Alignment of the class * @param InClassFlags Class flags * @param InClassCastFlags Class cast flags * @param InConfigName Class config name @@ -2905,6 +2951,7 @@ COREUOBJECT_API void GetPrivateStaticClassBody( UClass*& ReturnClass, void(*RegisterNativeFunc)(), uint32 InSize, + uint32 InAlignment, EClassFlags InClassFlags, EClassCastFlags InClassCastFlags, const TCHAR* InConfigName, @@ -3336,4 +3383,4 @@ struct FPolyglotTextData; template<> struct TBaseStructure { COREUOBJECT_API static UScriptStruct* Get(); -}; \ No newline at end of file +}; diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/ConstructorHelpers.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/ConstructorHelpers.h index 900d1270cac2..8d2d635c8827 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/ConstructorHelpers.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/ConstructorHelpers.h @@ -114,10 +114,15 @@ public: return !!Object; } - virtual void AddReferencedObjects( FReferenceCollector& Collector ) + virtual void AddReferencedObjects( FReferenceCollector& Collector ) override { Collector.AddReferencedObject(Object); } + + virtual FString GetReferencerName() const override + { + return TEXT("FObjectFinder"); + } }; template @@ -152,10 +157,15 @@ public: return !!Get(); } - virtual void AddReferencedObjects( FReferenceCollector& Collector ) + virtual void AddReferencedObjects( FReferenceCollector& Collector ) override { Collector.AddReferencedObject(Object); } + + virtual FString GetReferencerName() const override + { + return TEXT("FObjectFinderOptional"); + } }; template @@ -175,12 +185,17 @@ public: return !!*Class; } - virtual void AddReferencedObjects( FReferenceCollector& Collector ) + virtual void AddReferencedObjects( FReferenceCollector& Collector ) override { UClass* ReferencedClass = Class.Get(); Collector.AddReferencedObject(ReferencedClass); Class = ReferencedClass; } + + virtual FString GetReferencerName() const override + { + return TEXT("FClassFinder"); + } }; public: diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/CoreNet.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/CoreNet.h index 8b132e0eaafd..30fc0301165e 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/CoreNet.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/CoreNet.h @@ -502,6 +502,12 @@ struct FNetDeltaSerializeInfo /** Whether or not we support FFastArraySerializer::FastArrayDeltaSerialize_DeltaSerializeStructs */ bool bSupportsFastArrayDeltaStructSerialization = false; + /** + * Whether or the connection is completely reliable. + * We cache this off separate from UNetConnection so we can limit usage. + */ + bool bInternalAck = false; + /** The object that owns the struct we're serializing. */ UObject* Object = nullptr; @@ -522,7 +528,7 @@ struct FNetDeltaSerializeInfo /** When non-null, this indicates the given Guid has become unmapped and any references to it should be updated. */ const FNetworkGUID* MoveGuidToUnmapped = nullptr; - int32 PropertyRepIndex = INDEX_NONE; + uint16 CustomDeltaIndex = INDEX_NONE; // Debugging variables FString DebugName; diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/GCObjectScopeGuard.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/GCObjectScopeGuard.h index a7f93377e521..99f047d82d66 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/GCObjectScopeGuard.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/GCObjectScopeGuard.h @@ -30,6 +30,11 @@ public: Collector.AddReferencedObject(Object); } + virtual FString GetReferencerName() const override + { + return TEXT("FGCObjectScopeGuard"); + } + private: const UObject* Object; }; @@ -62,6 +67,11 @@ public: Collector.AddReferencedObjects(Objects); } + virtual FString GetReferencerName() const override + { + return TEXT("TGCObjectsScopeGuard"); + } + private: TArray Objects; }; diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/LazyObjectPtr.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/LazyObjectPtr.h index 030e04349c9e..b1816bc0bd6d 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/LazyObjectPtr.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/LazyObjectPtr.h @@ -171,7 +171,6 @@ public: template <> struct TIsPODType { enum { Value = TIsPODType >::Value }; }; template <> struct TIsWeakPointerType { enum { Value = TIsWeakPointerType >::Value }; }; -template <> struct THasGetTypeHash { enum { Value = THasGetTypeHash >::Value }; }; /** * TLazyObjectPtr is templatized version of the generic FLazyObjectPtr diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/Linker.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/Linker.h index fdde770962b1..bf73b1d7d4e3 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/Linker.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/Linker.h @@ -166,35 +166,35 @@ public: }; -struct FLinkerNamePairKeyFuncs : DefaultKeyFuncs +struct UE_DEPRECATED(4.23, "Outdated since display index replaced FName as key") FLinkerNamePairKeyFuncs : DefaultKeyFuncs { static FORCEINLINE bool Matches(FName A, FName B) { // The linker requires that FNames preserve case, but the numeric suffix can be ignored since // that is stored separately for each FName instance saved - return A.IsEqual(B, ENameCase::CaseSensitive, false/*bCompareNumber*/); + return A.GetDisplayIndex() == B.GetDisplayIndex(); } static FORCEINLINE uint32 GetKeyHash(FName Key) { - return Key.GetComparisonIndex(); + return GetTypeHash(Key.GetDisplayIndex()); } }; template -struct TLinkerNameMapKeyFuncs : TDefaultMapKeyFuncs +struct UE_DEPRECATED(4.23, "Outdated since display indexes replaced FNames as keys") TLinkerNameMapKeyFuncs : TDefaultMapKeyFuncs { static FORCEINLINE bool Matches(FName A, FName B) { // The linker requires that FNames preserve case, but the numeric suffix can be ignored since // that is stored separately for each FName instance saved - return A.IsEqual(B, ENameCase::CaseSensitive, false/*bCompareNumber*/); + return A.GetDisplayIndex() == B.GetDisplayIndex(); } static FORCEINLINE uint32 GetKeyHash(FName Key) { - return Key.GetComparisonIndex(); + return GetTypeHash(Key.GetDisplayIndex()); } }; @@ -231,7 +231,7 @@ public: FPackageFileSummary Summary; /** Names used by objects contained within this package */ - TArray NameMap; + TArray NameMap; /** Gatherable text data contained within this package */ TArray GatherableTextDataMap; diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/LinkerLoad.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/LinkerLoad.h index 3c4d381f6caf..86c795d696a5 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/LinkerLoad.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/LinkerLoad.h @@ -70,7 +70,7 @@ struct FScopedCreateImportCounter /** * Handles loading Unreal package files, including reading UObject data from disk. */ -class FArchiveAsync2; +class FAsyncArchive; class FLinkerLoad #if !WITH_EDITOR @@ -127,11 +127,11 @@ public: bool bForceSimpleIndexToObject; bool bLockoutLegacyOperations; - /** True if Loader is FArchiveAsync2 */ - bool bLoaderIsFArchiveAsync2; - FORCEINLINE FArchiveAsync2* GetFArchiveAsync2Loader() + /** True if Loader is FAsyncArchive */ + bool bIsAsyncLoader; + FORCEINLINE FAsyncArchive* GetAsyncLoader() { - return bLoaderIsFArchiveAsync2 ? (FArchiveAsync2*)Loader : nullptr; + return bIsAsyncLoader ? (FAsyncArchive*)Loader : nullptr; } private: @@ -258,8 +258,6 @@ private: // Variables used during async linker creation. - /** Current index into name map, used by async linker creation for spreading out serializing name entries. */ - int32 NameMapIndex; /** Current index into gatherable text data map, used by async linker creation for spreading out serializing text entries. */ int32 GatherableTextDataMapIndex; /** Current index into import map, used by async linker creation for spreading out serializing importmap entries. */ @@ -806,32 +804,31 @@ private: Value = ID; return Ar; } - void BadNameIndexError(NAME_INDEX NameIndex); + void BadNameIndexError(int32 NameIndex); FORCEINLINE virtual FArchive& operator<<(FName& Name) override { - Name = NAME_None; FArchive& Ar = *this; - NAME_INDEX NameIndex; + int32 NameIndex; Ar << NameIndex; - int32 Number; + int32 Number = 0; Ar << Number; - if (!NameMap.IsValidIndex(NameIndex)) + if (NameMap.IsValidIndex(NameIndex)) { + // if the name wasn't loaded (because it wasn't valid in this context) + FNameEntryId MappedName = NameMap[NameIndex]; + + // simply create the name from the NameMap's name and the serialized instance number + Name = FName::CreateFromDisplayId(MappedName, Number); + } + else + { + Name = FName(); BadNameIndexError(NameIndex); ArIsError = true; ArIsCriticalError = true; } - else - { - // if the name wasn't loaded (because it wasn't valid in this context) - const FName& MappedName = NameMap[NameIndex]; - if (!MappedName.IsNone()) - { - // simply create the name from the NameMap's name and the serialized instance number - Name = FName(MappedName, Number); - } - } + return *this; } diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/LinkerSave.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/LinkerSave.h index baf155b5ea75..6dc0a331e9c5 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/LinkerSave.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/LinkerSave.h @@ -43,7 +43,7 @@ public: TMap > SearchableNamesObjectMap; /** Index array - location of the name in the NameMap array for each FName is stored in the NameIndices array using the FName's Index */ - TMap> NameIndices; + TMap NameIndices; /** Save context associated with this linker */ TRefCountPtr SaveContext; @@ -75,7 +75,7 @@ public: FLinkerSave(UPackage* InParent, FArchive *InSaver, bool bForceByteSwapping, bool bInSaveUnversioned = false); /** Returns the appropriate name index for the source name, or 0 if not found in NameIndices */ - int32 MapName(const FName& Name) const; + int32 MapName( FNameEntryId Name) const; /** Returns the appropriate package index for the source object, or default value if not found in ObjectIndicesMap */ FPackageIndex MapObject(const UObject* Object) const; diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/NoExportTypes.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/NoExportTypes.h index a0ede45cf9bd..e5a212fee855 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/NoExportTypes.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/NoExportTypes.h @@ -1427,6 +1427,19 @@ struct FAutomationExecutionEntry FDateTime Timestamp; }; +/** Enum used by DataValidation plugin to see if an asset has been validated for correctness (mirrored in UObjectGlobals.h)*/ +UENUM(BlueprintType) +enum class EDataValidationResult : uint8 +{ + /** Asset has failed validation */ + Invalid, + /** Asset has passed validation */ + Valid, + /** Asset has not yet been validated */ + NotValidated +}; + + /** * Direct base class for all UE4 objects * @note The full C++ class is located here: Engine\Source\Runtime\CoreUObject\Public\UObject\Object.h diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/Object.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/Object.h index a13c347c1099..5228cdd534bc 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/Object.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/Object.h @@ -72,8 +72,14 @@ class COREUOBJECT_API UObject : public UObjectBaseUtility /** DO NOT USE. This constructor is for internal usage only for hot-reload purposes. */ UObject(FVTableHelper& Helper); + UE_DEPRECATED(4.23, "CreateDefaultSubobject no longer takes bAbstract as a parameter.") + UObject* CreateDefaultSubobject(FName SubobjectFName, UClass* ReturnType, UClass* ClassToCreateByDefault, bool bIsRequired, bool bAbstract, bool bIsTransient) + { + return CreateDefaultSubobject(SubobjectFName, ReturnType, ClassToCreateByDefault, bIsRequired, bIsTransient); + } + /** Utility function for templates below */ - UObject* CreateDefaultSubobject(FName SubobjectFName, UClass* ReturnType, UClass* ClassToCreateByDefault, bool bIsRequired, bool bAbstract, bool bIsTransient); + UObject* CreateDefaultSubobject(FName SubobjectFName, UClass* ReturnType, UClass* ClassToCreateByDefault, bool bIsRequired, bool bIsTransient); /** * Create a component or subobject only to be used with the editor. They will be stripped out in packaged builds. @@ -98,7 +104,7 @@ class COREUOBJECT_API UObject : public UObjectBaseUtility TReturnType* CreateDefaultSubobject(FName SubobjectName, bool bTransient = false) { UClass* ReturnType = TReturnType::StaticClass(); - return static_cast(CreateDefaultSubobject(SubobjectName, ReturnType, ReturnType, /*bIsRequired =*/ true, /*bIsAbstract =*/ false, bTransient)); + return static_cast(CreateDefaultSubobject(SubobjectName, ReturnType, ReturnType, /*bIsRequired =*/ true, bTransient)); } /** @@ -111,7 +117,7 @@ class COREUOBJECT_API UObject : public UObjectBaseUtility template TReturnType* CreateDefaultSubobject(FName SubobjectName, bool bTransient = false) { - return static_cast(CreateDefaultSubobject(SubobjectName, TReturnType::StaticClass(), TClassToConstructByDefault::StaticClass(), /*bIsRequired =*/ true, /*bIsAbstract =*/ false, bTransient)); + return static_cast(CreateDefaultSubobject(SubobjectName, TReturnType::StaticClass(), TClassToConstructByDefault::StaticClass(), /*bIsRequired =*/ true, bTransient)); } /** @@ -125,20 +131,21 @@ class COREUOBJECT_API UObject : public UObjectBaseUtility TReturnType* CreateOptionalDefaultSubobject(FName SubobjectName, bool bTransient = false) { UClass* ReturnType = TReturnType::StaticClass(); - return static_cast(CreateDefaultSubobject(SubobjectName, ReturnType, ReturnType, /*bIsRequired =*/ false, /*bIsAbstract =*/ false, bTransient)); + return static_cast(CreateDefaultSubobject(SubobjectName, ReturnType, ReturnType, /*bIsRequired =*/ false, bTransient)); } /** - * Create a subobject that has the Abstract class flag, child classes are expected to override this by calling CreateDEfaultObject with the same name and a non-abstract class. + * Create a subobject that has the Abstract class flag, child classes are expected to override this by calling SetDefaultSubobjectClass with the same name and a non-abstract class. * @param TReturnType Class of return type, all overrides must be of this type * @param SubobjectName Name of the new component * @param bTransient True if the component is being assigned to a transient property. This does not make the component itself transient, but does stop it from inheriting parent defaults */ template + UE_DEPRECATED(4.23, "CreateAbstract did not work as intended and has been deprecated in favor of CreateDefaultObject") TReturnType* CreateAbstractDefaultSubobject(FName SubobjectName, bool bTransient = false) { UClass* ReturnType = TReturnType::StaticClass(); - return static_cast(CreateDefaultSubobject(SubobjectName, ReturnType, ReturnType, /*bIsRequired =*/ true, /*bIsAbstract =*/ true, bTransient)); + return static_cast(CreateDefaultSubobject(SubobjectName, ReturnType, ReturnType, /*bIsRequired =*/ true, bTransient)); } /** @@ -439,6 +446,16 @@ public: return false; } + /** + * Called during garbage collection to determine if an object can have its destructor called on a worker thread. + * + * @return true if this object's destructor is thread safe + */ + virtual bool IsDestructionThreadSafe() const + { + return true; + } + /** * Called during cooking. Must return all objects that will be Preload()ed when this is serialized at load time. Only used by the EDL. * @@ -919,7 +936,43 @@ public: * @param PackageFilename full path to the package that this object is being saved to on disk * @param TargetPlatform target platform to cook additional files for */ - virtual void CookAdditionalFiles( const TCHAR* PackageFilename, const ITargetPlatform* TargetPlatform ) { } + UE_DEPRECATED(4.23, "Use the new CookAdditionalFilesOverride that provides a function to write the files") + virtual void CookAdditionalFiles(const TCHAR* PackageFilename, const ITargetPlatform* TargetPlatform) { } + + /** + * Called during cook to allow objects to generate additional cooked files alongside their cooked package. + * @note Implement CookAdditionalFilesOverride to define sub class behavior. + * + * @param PackageFilename full path to the package that this object is being saved to on disk + * @param TargetPlatform target platform to cook additional files for + * @param WriteAdditionalFile function for writing the additional files + */ + void CookAdditionalFiles(const TCHAR* PackageFilename, const ITargetPlatform* TargetPlatform, + TFunctionRef WriteAdditionalFile) + { + CookAdditionalFilesOverride(PackageFilename, TargetPlatform, WriteAdditionalFile); + } + +private: + /** + * Called during cook to allow objects to generate additional cooked files alongside their cooked package. + * Files written using the provided function will be handled as part of the saved cooked package + * and contribute to package total file size, and package hash when enabled. + * @note These should typically match the name of the package, but with a different extension. + * + * @param PackageFilename full path to the package that this object is being saved to on disk + * @param TargetPlatform target platform to cook additional files for + * @param WriteAdditionalFile function for writing the additional files + */ + virtual void CookAdditionalFilesOverride(const TCHAR* PackageFilename, const ITargetPlatform* TargetPlatform, + TFunctionRef WriteAdditionalFile) + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS; + CookAdditionalFiles(PackageFilename, TargetPlatform); + PRAGMA_ENABLE_DEPRECATION_WARNINGS; + } + +public: #endif /** * Determine if this object has SomeObject in its archetype chain. diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/ObjectMacros.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/ObjectMacros.h index 927a18d50600..987a1e21014f 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/ObjectMacros.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/ObjectMacros.h @@ -385,7 +385,7 @@ enum EPropertyFlags : uint64 CPF_DuplicateTransient = 0x0000000000200000, ///< Property should always be reset to the default value during any type of duplication (copy/paste, binary duplication, etc.) CPF_SubobjectReference = 0x0000000000400000, ///< Property contains subobject references (TSubobjectPtr) //CPF_ = 0x0000000000800000, ///< - CPF_SaveGame = 0x0000000001000000, ///< Property should be serialized for save games + CPF_SaveGame = 0x0000000001000000, ///< Property should be serialized for save games, this is only checked for game-specific archives with ArIsSaveGame CPF_NoClear = 0x0000000002000000, ///< Hide clear (and browse) button. //CPF_ = 0x0000000004000000, ///< CPF_ReferenceParm = 0x0000000008000000, ///< Value is passed by reference; CPF_OutParam and CPF_Param should also be set. @@ -936,7 +936,8 @@ namespace UP /// to use on struct properties or parameters. AssetRegistrySearchable, - /// Property should be serialized for save game. + /// Property should be serialized for save games. + /// This is only checked for game-specific archives with ArIsSaveGame set SaveGame, /// MC Delegates only. Property should be exposed for calling in blueprint code @@ -1145,6 +1146,9 @@ namespace UM /// [PropertyMetadata] Used for FColor and FLinearColor properties. Indicates that the Alpha property should be hidden when displaying the property widget in the details. HideAlphaChannel, + /// [PropertyMetadata] Indicates that the property should be hidden in the details panel. Currently only used by events. + HideInDetailPanel, + /// [PropertyMetadata] Used for Subclass and SoftClass properties. Specifies to hide the ability to change view options in the class picker HideViewOptions, @@ -1573,6 +1577,7 @@ public: \ PrivateStaticClass, \ StaticRegisterNatives##TClass, \ sizeof(TClass), \ + alignof(TClass), \ (EClassFlags)TClass::StaticClassFlags, \ TClass::StaticClassCastFlags(), \ TClass::StaticConfigName(), \ @@ -1641,6 +1646,7 @@ public: \ PrivateStaticClass, \ StaticRegisterNatives##TClass, \ sizeof(TClass), \ + alignof(TClass), \ (EClassFlags)TClass::StaticClassFlags, \ TClass::StaticClassCastFlags(), \ TClass::StaticConfigName(), \ diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/PackageFileSummary.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/PackageFileSummary.h index 304e840eab94..33ff07d4751e 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/PackageFileSummary.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/PackageFileSummary.h @@ -224,10 +224,7 @@ public: return CustomVersionContainer; } - void SetCustomVersionContainer(const FCustomVersionContainer& InContainer) - { - CustomVersionContainer = InContainer; - } + void SetCustomVersionContainer(const FCustomVersionContainer& InContainer); void SetFileVersions(const int32 EpicUE4, const int32 LicenseeUE4, const bool bInSaveUnversioned = false) { diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/PersistentObjectPtr.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/PersistentObjectPtr.h index 2d082e5732b8..f16a374c397f 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/PersistentObjectPtr.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/PersistentObjectPtr.h @@ -116,7 +116,9 @@ public: FORCEINLINE UObject* Get() const { UObject* Object = WeakPtr.Get(); - if (!Object && TObjectID::GetCurrentTag() != TagAtLastTest && ObjectID.IsValid()) + + // Do a full resolve if the returned object is null and either we think we've loaded new objects, or the weak ptr may be stale + if (!Object && ObjectID.IsValid() && (TObjectID::GetCurrentTag() != TagAtLastTest || !WeakPtr.IsExplicitlyNull())) { Object = ObjectID.ResolveObject(); WeakPtr = Object; diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/ScriptInterface.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/ScriptInterface.h index 5d620b785297..03ae9f77849e 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/ScriptInterface.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/ScriptInterface.h @@ -220,7 +220,7 @@ public: * * @return true if InterfacePointer is non-NULL. */ - FORCEINLINE operator bool() const + FORCEINLINE explicit operator bool() const { return GetInterface() != NULL; } diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/SoftObjectPath.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/SoftObjectPath.h index fb8561b54902..8fed68cb5f15 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/SoftObjectPath.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/SoftObjectPath.h @@ -110,32 +110,32 @@ struct COREUOBJECT_API FSoftObjectPath /** Resets reference to point to null */ void Reset() { - AssetPathName = NAME_None; + AssetPathName = FName(); SubPathString.Reset(); } /** Check if this could possibly refer to a real object, or was initialized to null */ FORCEINLINE bool IsValid() const { - return AssetPathName != NAME_None; + return !AssetPathName.IsNone(); } /** Checks to see if this is initialized to null */ FORCEINLINE bool IsNull() const { - return AssetPathName == NAME_None; + return AssetPathName.IsNone(); } /** Check if this represents an asset, meaning it is not null but does not have a sub path */ FORCEINLINE bool IsAsset() const { - return AssetPathName != NAME_None && SubPathString.IsEmpty(); + return !AssetPathName.IsNone() && SubPathString.IsEmpty(); } /** Check if this represents a sub object, meaning it has a sub path */ FORCEINLINE bool IsSubobject() const { - return AssetPathName != NAME_None && !SubPathString.IsEmpty(); + return !AssetPathName.IsNone() && !SubPathString.IsEmpty(); } /** Struct overrides */ @@ -211,6 +211,9 @@ private: /** Package names currently being duplicated, needed by FixupForPIE */ static TSet PIEPackageNames; + UObject* ResolveObjectInternal() const; + UObject* ResolveObjectInternal(const TCHAR* PathString) const; + friend struct Z_Construct_UScriptStruct_FSoftObjectPath_Statics; }; diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/SoftObjectPtr.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/SoftObjectPtr.h index f3c3d29d0aaf..4862d7df2b27 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/SoftObjectPtr.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/SoftObjectPtr.h @@ -107,7 +107,6 @@ public: template <> struct TIsPODType { enum { Value = TIsPODType >::Value }; }; template <> struct TIsWeakPointerType { enum { Value = TIsWeakPointerType >::Value }; }; -template <> struct THasGetTypeHash { enum { Value = THasGetTypeHash >::Value }; }; /** * TSoftObjectPtr is templatized wrapper of the generic FSoftObjectPtr, it can be used in UProperties diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/SparseDelegate.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/SparseDelegate.h index 0a53f659d63d..d535d283eb73 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/SparseDelegate.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/SparseDelegate.h @@ -70,6 +70,7 @@ private: { virtual ~FObjectListener(); virtual void NotifyUObjectDeleted(const UObjectBase* Object, int32 Index) override; + virtual void OnUObjectArrayShutdown(); void EnableListener(); void DisableListener(); }; diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/StrongObjectPtr.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/StrongObjectPtr.h index 72233584577f..c3faf119835e 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/StrongObjectPtr.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/StrongObjectPtr.h @@ -3,7 +3,52 @@ #pragma once #include "UObject/GCObject.h" -#include "Templates/UniquePtr.h" +#include "Templates/EnableIf.h" +#include "Templates/PointerIsConvertibleFromTo.h" +#include "Templates/UniqueObj.h" + +namespace UE4StrongObjectPtr_Private +{ + class FInternalReferenceCollector : public FGCObject + { + public: + FInternalReferenceCollector(const volatile UObject* InObject = nullptr) + : Object(InObject) + { + check(IsInGameThread()); + } + + virtual ~FInternalReferenceCollector() + { + check(IsInGameThread() || IsInGarbageCollectorThread()); + } + + bool IsValid() const + { + return Object != nullptr; + } + + template + FORCEINLINE UObjectType* GetAs() const + { + return (UObjectType*)Object; + } + + FORCEINLINE void Set(const volatile UObject* InObject) + { + Object = InObject; + } + + //~ FGCObject interface + virtual void AddReferencedObjects(FReferenceCollector& Collector) override + { + Collector.AddReferencedObject(Object); + } + + private: + const volatile UObject* Object; + }; +} /** * Specific implementation of FGCObject that prevents a single UObject-based pointer from being GC'd while this guard is in scope. @@ -13,66 +58,48 @@ template class TStrongObjectPtr { public: - FORCEINLINE_DEBUGGABLE TStrongObjectPtr() - : ReferenceCollector(MakeUnique(nullptr)) + TStrongObjectPtr(TStrongObjectPtr&& InOther) = default; + TStrongObjectPtr(const TStrongObjectPtr& InOther) = default; + TStrongObjectPtr& operator=(TStrongObjectPtr&& InOther) = default; + ~TStrongObjectPtr() = default; + + FORCEINLINE_DEBUGGABLE TStrongObjectPtr(TYPE_OF_NULLPTR = nullptr) { static_assert(TPointerIsConvertibleFromTo::Value, "TStrongObjectPtr can only be constructed with UObject types"); } FORCEINLINE_DEBUGGABLE explicit TStrongObjectPtr(ObjectType* InObject) - : ReferenceCollector(MakeUnique(InObject)) + : ReferenceCollector(InObject) { static_assert(TPointerIsConvertibleFromTo::Value, "TStrongObjectPtr can only be constructed with UObject types"); } - FORCEINLINE_DEBUGGABLE TStrongObjectPtr(const TStrongObjectPtr& InOther) - : ReferenceCollector(MakeUnique(InOther.Get())) - { - } - - template + template < + typename OtherObjectType, + typename = typename TEnableIf::Value>::Type + > FORCEINLINE_DEBUGGABLE TStrongObjectPtr(const TStrongObjectPtr& InOther) - : ReferenceCollector(MakeUnique(InOther.Get())) + : ReferenceCollector(InOther.Get()) { } FORCEINLINE_DEBUGGABLE TStrongObjectPtr& operator=(const TStrongObjectPtr& InOther) { + // TUniqueObj is not assignable so we need to implement this instead of defaulting it. ReferenceCollector->Set(InOther.Get()); return *this; } template - FORCEINLINE_DEBUGGABLE TStrongObjectPtr& operator=(const TStrongObjectPtr& InOther) + FORCEINLINE_DEBUGGABLE typename TEnableIf< + TPointerIsConvertibleFromTo::Value, + TStrongObjectPtr& + >::Type operator=(const TStrongObjectPtr& InOther) { ReferenceCollector->Set(InOther.Get()); return *this; } - FORCEINLINE_DEBUGGABLE TStrongObjectPtr(TStrongObjectPtr&& InOther) - { - ReferenceCollector = MoveTemp(InOther.ReferenceCollector); - } - - template - FORCEINLINE_DEBUGGABLE TStrongObjectPtr(TStrongObjectPtr&& InOther) - { - ReferenceCollector = MoveTemp(InOther.ReferenceCollector); - } - - FORCEINLINE_DEBUGGABLE TStrongObjectPtr& operator=(TStrongObjectPtr&& InOther) - { - ReferenceCollector = MoveTemp(InOther.ReferenceCollector); - return *this; - } - - template - FORCEINLINE_DEBUGGABLE TStrongObjectPtr& operator=(TStrongObjectPtr&& InOther) - { - ReferenceCollector = MoveTemp(InOther.ReferenceCollector); - return *this; - } - FORCEINLINE_DEBUGGABLE ObjectType& operator*() const { check(IsValid()); @@ -87,17 +114,17 @@ public: FORCEINLINE_DEBUGGABLE bool IsValid() const { - return Get() != nullptr; + return ReferenceCollector->IsValid(); } FORCEINLINE_DEBUGGABLE explicit operator bool() const { - return Get() != nullptr; + return ReferenceCollector->IsValid(); } FORCEINLINE_DEBUGGABLE ObjectType* Get() const { - return ReferenceCollector->Get(); + return ReferenceCollector->GetAs(); } FORCEINLINE_DEBUGGABLE void Reset(ObjectType* InNewObject = nullptr) @@ -111,41 +138,7 @@ public: } private: - class FInternalReferenceCollector : public FGCObject - { - public: - FInternalReferenceCollector(ObjectType* InObject) - : Object(InObject) - { - check(IsInGameThread()); - } - - virtual ~FInternalReferenceCollector() - { - check(IsInGameThread()); - } - - FORCEINLINE ObjectType* Get() const - { - return Object; - } - - FORCEINLINE void Set(ObjectType* InObject) - { - Object = InObject; - } - - //~ FGCObject interface - virtual void AddReferencedObjects(FReferenceCollector& Collector) override - { - Collector.AddReferencedObject(Object); - } - - private: - ObjectType* Object; - }; - - TUniquePtr ReferenceCollector; + TUniqueObj ReferenceCollector; }; template diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/StructOnScope.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/StructOnScope.h index 2ca6f379a98d..e0b1a3c75559 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/StructOnScope.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/StructOnScope.h @@ -226,7 +226,7 @@ public: return Get(); } - operator bool() const + explicit operator bool() const { return IsValid(); } diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/UObjectAnnotation.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/UObjectAnnotation.h index c681bdbf344e..fa95dadadfe8 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/UObjectAnnotation.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/UObjectAnnotation.h @@ -35,7 +35,7 @@ public: * @param Object object that has been destroyed * @param Index index of object that is being deleted */ - virtual void NotifyUObjectDeleted(const UObjectBase *Object, int32 Index) + virtual void NotifyUObjectDeleted(const UObjectBase *Object, int32 Index) override { #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) if (!bAutoRemove) @@ -51,6 +51,12 @@ public: } } + virtual void OnUObjectArrayShutdown() override + { + RemoveAllAnnotations(); + GUObjectArray.RemoveUObjectDeleteListener(this); + } + /** * Constructor, initializes to nothing */ @@ -66,7 +72,7 @@ public: */ virtual ~FUObjectAnnotationSparse() { - RemoveAllAnnotations(); + RemoveAllAnnotations(); } /** @@ -259,11 +265,17 @@ public: * @param Object object that has been destroyed * @param Index index of object that is being deleted */ - virtual void NotifyUObjectDeleted(const UObjectBase *Object, int32 Index) + virtual void NotifyUObjectDeleted(const UObjectBase *Object, int32 Index) override { RemoveAnnotation(Object); } + virtual void OnUObjectArrayShutdown() override + { + RemoveAllAnnotations(); + GUObjectArray.RemoveUObjectDeleteListener(this); + } + /** * Destructor, removes all annotations, which removes the annotation as a uobject destruction listener */ @@ -466,14 +478,9 @@ public: * @param TAnnotation type of the annotation * @param bAutoRemove if true, annotation will automatically be removed, otherwise in non-final builds it will verify that the annotation was removed by other means prior to destruction. **/ -template +template class FUObjectAnnotationChunked : public FUObjectArray::FUObjectDeleteListener { - enum - { - NumAnnotationsPerChunk = 64 * 1024, - }; - struct TAnnotationChunk { int32 Num; @@ -876,6 +883,12 @@ public: RemoveAnnotation(Index); } } + + virtual void OnUObjectArrayShutdown() override + { + RemoveAllAnnotations(); + GUObjectArray.RemoveUObjectDeleteListener(this); + } }; /** @@ -917,6 +930,12 @@ public: } } + virtual void OnUObjectArrayShutdown() override + { + RemoveAllAnnotations(); + GUObjectArray.RemoveUObjectDeleteListener(this); + } + /** * Destructor, removes all annotations, which removes the annotation as a uobject destruction listener */ @@ -1127,6 +1146,12 @@ public: RemoveAnnotation(Index); } + virtual void OnUObjectArrayShutdown() override + { + RemoveAllAnnotations(); + GUObjectArray.RemoveUObjectDeleteListener(this); + } + /** * Destructor, removes all annotations, which removes the annotation as a uobject destruction listener */ diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/UObjectArray.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/UObjectArray.h index 0aa158cbadad..91ca007f32a8 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/UObjectArray.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/UObjectArray.h @@ -561,6 +561,11 @@ public: * @param Index index of object that is being deleted */ virtual void NotifyUObjectCreated(const class UObjectBase *Object, int32 Index)=0; + + /** + * Called when UObject Array is being shut down, this is where all listeners should be removed from it + */ + virtual void OnUObjectArrayShutdown()=0; }; /** @@ -578,6 +583,11 @@ public: * @param Index index of object that is being deleted */ virtual void NotifyUObjectDeleted(const class UObjectBase *Object, int32 Index)=0; + + /** + * Called when UObject Array is being shut down, this is where all listeners should be removed from it + */ + virtual void OnUObjectArrayShutdown() = 0; }; /** @@ -1085,8 +1095,11 @@ public: */ void DissolveCluster(UObjectBaseUtility* ClusterRootOrObjectFromCluster); - /** Dissolve all clusters marked for dissolving */ - void DissolveClusters(); + /** + * Dissolve all clusters marked for dissolving + * @param bForceDissolveAllClusters if true, dissolves all clusters even if they're not marked for dissolving + */ + void DissolveClusters(bool bForceDissolveAllClusters = false); /** Dissolve the specified cluster and all clusters that reference it */ void DissolveClusterAndMarkObjectsAsUnreachable(FUObjectItem* RootObjectItem); diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/UObjectGlobals.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/UObjectGlobals.h index b8e9f359b99c..85632886f453 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/UObjectGlobals.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/UObjectGlobals.h @@ -44,6 +44,8 @@ DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("FindObjectFast"),STAT_FindObjectFast,STA extern COREUOBJECT_API bool GIsSavingPackage; /** This allows loading unversioned cooked content in the editor */ extern COREUOBJECT_API bool GAllowUnversionedContentInEditor; +/** This allows loading cooked content in the editor */ +extern COREUOBJECT_API int32 GAllowCookedDataInEditorBuilds; /** Enum used in StaticDuplicateObject() and related functions to describe why something is being duplicated */ namespace EDuplicateMode @@ -424,6 +426,11 @@ COREUOBJECT_API float GetAsyncLoadPercentage( const FName& PackageName ); */ COREUOBJECT_API bool IsGarbageCollecting(); +/** +* Whether we are running on the Garbage Collector Thread +*/ +COREUOBJECT_API bool IsInGarbageCollectorThread(); + /** * Deletes all unreferenced objects, keeping objects that have any of the passed in KeepFlags set. Will wait for other threads to unlock GC. * @@ -464,6 +471,13 @@ COREUOBJECT_API bool IsIncrementalUnhashPending(); */ COREUOBJECT_API bool IsIncrementalPurgePending(); +/** + * Gathers unreachable objects for IncrementalPurgeGarbage. + * + * @param bForceSingleThreaded true to force the process to just one thread + */ +COREUOBJECT_API void GatherUnreachableObjects(bool bForceSingleThreaded); + /** * Incrementally purge garbage by deleting all unreferenced objects after routing Destroy. * @@ -685,7 +699,7 @@ public: return !!Object && Object != (UObject*)InvalidPtrValue; } /** Convenience operator. Does the same thing as IsValid(). */ - FORCEINLINE operator bool() const + FORCEINLINE explicit operator bool() const { return IsValid(); } @@ -814,7 +828,7 @@ public: TReturnType* CreateDefaultSubobject(UObject* Outer, FName SubobjectName, bool bTransient = false) const { UClass* ReturnType = TReturnType::StaticClass(); - return static_cast(CreateDefaultSubobject(Outer, SubobjectName, ReturnType, ReturnType, /*bIsRequired =*/ true, /*bIsAbstract =*/ false, bTransient)); + return static_cast(CreateDefaultSubobject(Outer, SubobjectName, ReturnType, ReturnType, /*bIsRequired =*/ true, bTransient)); } /** @@ -829,7 +843,7 @@ public: TReturnType* CreateOptionalDefaultSubobject(UObject* Outer, FName SubobjectName, bool bTransient = false) const { UClass* ReturnType = TReturnType::StaticClass(); - return static_cast(CreateDefaultSubobject(Outer, SubobjectName, ReturnType, ReturnType, /*bIsRequired =*/ false, /*bIsAbstract =*/ false, bTransient)); + return static_cast(CreateDefaultSubobject(Outer, SubobjectName, ReturnType, ReturnType, /*bIsRequired =*/ false, bTransient)); } /** @@ -840,11 +854,19 @@ public: * @param SubobjectName name of the new component * @param bTransient true if the component is being assigned to a transient property */ + + /** + * Create a subobject that has the Abstract class flag, child classes are expected to override this by calling SetDefaultSubobjectClass with the same name and a non-abstract class. + * @param TReturnType Class of return type, all overrides must be of this type + * @param SubobjectName Name of the new component + * @param bTransient True if the component is being assigned to a transient property. This does not make the component itself transient, but does stop it from inheriting parent defaults + */ template + UE_DEPRECATED(4.23, "CreateAbstract did not work as intended and has been deprecated in favor of CreateDefaultObject") TReturnType* CreateAbstractDefaultSubobject(UObject* Outer, FName SubobjectName, bool bTransient = false) const { UClass* ReturnType = TReturnType::StaticClass(); - return static_cast(CreateDefaultSubobject(Outer, SubobjectName, ReturnType, ReturnType, /*bIsRequired =*/ true, /*bIsAbstract =*/ true, bTransient)); + return static_cast(CreateDefaultSubobject(Outer, SubobjectName, ReturnType, ReturnType, /*bIsRequired =*/ true, bTransient)); } /** @@ -858,7 +880,7 @@ public: template TReturnType* CreateDefaultSubobject(UObject* Outer, FName SubobjectName, bool bTransient = false) const { - return static_cast(CreateDefaultSubobject(Outer, SubobjectName, TReturnType::StaticClass(), TClassToConstructByDefault::StaticClass(), /*bIsRequired =*/ true, /*bIsAbstract =*/ false, bTransient)); + return static_cast(CreateDefaultSubobject(Outer, SubobjectName, TReturnType::StaticClass(), TClassToConstructByDefault::StaticClass(), /*bIsRequired =*/ true, bTransient)); } /** @@ -891,10 +913,17 @@ public: * @param TClassToConstructByDefault if the derived class has not overridden, create a component of this type (default is TReturnType) * @param Outer outer to construct the subobject in * @param SubobjectName name of the new component - * @param bIsRequired true if the component is required and will always be created even if DoNotCreateDefaultSubobject was sepcified. + * @param bIsRequired true if the component is required and will always be created even if DoNotCreateDefaultSubobject was specified. * @param bIsTransient true if the component is being assigned to a transient property */ - UObject* CreateDefaultSubobject(UObject* Outer, FName SubobjectFName, UClass* ReturnType, UClass* ClassToCreateByDefault, bool bIsRequired, bool bAbstract, bool bIsTransient) const; + UObject* CreateDefaultSubobject(UObject* Outer, FName SubobjectFName, UClass* ReturnType, UClass* ClassToCreateByDefault, bool bIsRequired, bool bIsTransient) const; + + UE_DEPRECATED(4.23, "CreateDefaultSubobject no longer takes bAbstract as a parameter.") + UObject* CreateDefaultSubobject(UObject* Outer, FName SubobjectFName, UClass* ReturnType, UClass* ClassToCreateByDefault, bool bIsRequired, bool bAbstract, bool bIsTransient) const + { + return CreateDefaultSubobject(Outer, SubobjectFName, ReturnType, ClassToCreateByDefault, bIsRequired, bIsTransient); + } + /** * Sets the class of a subobject for a base class @@ -2647,7 +2676,7 @@ namespace UE4CodeGen_Private #define IF_WITH_EDITORONLY_DATA(x, y) y #endif -/** Enum used by UDataValidationManager to see if an asset has been validated for correctness */ +/** Enum used by DataValidation plugin to see if an asset has been validated for correctness */ enum class EDataValidationResult : uint8 { /** Asset has failed validation */ diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/UnrealType.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/UnrealType.h index add969008916..a9b6bd6a299e 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/UnrealType.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/UnrealType.h @@ -7,24 +7,27 @@ #pragma once #include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" -#include "UObject/UObjectGlobals.h" -#include "UObject/Object.h" -#include "UObject/Class.h" -#include "UObject/WeakObjectPtr.h" -#include "UObject/CoreNetTypes.h" -#include "UObject/ScriptInterface.h" -#include "UObject/SparseDelegate.h" + +#include "Concepts/GetTypeHashable.h" +#include "Containers/List.h" +#include "Serialization/SerializedPropertyScope.h" #include "Templates/Casts.h" +#include "Templates/Greater.h" #include "Templates/IsFloatingPoint.h" #include "Templates/IsIntegral.h" #include "Templates/IsSigned.h" -#include "Templates/Greater.h" -#include "Containers/List.h" +#include "Templates/Models.h" +#include "UObject/Class.h" +#include "UObject/CoreNetTypes.h" #include "UObject/LazyObjectPtr.h" -#include "UObject/SoftObjectPtr.h" +#include "UObject/Object.h" +#include "UObject/ObjectMacros.h" #include "UObject/PropertyTag.h" -#include "Serialization/SerializedPropertyScope.h" +#include "UObject/ScriptInterface.h" +#include "UObject/SoftObjectPtr.h" +#include "UObject/SparseDelegate.h" +#include "UObject/UObjectGlobals.h" +#include "UObject/WeakObjectPtr.h" COREUOBJECT_API DECLARE_LOG_CATEGORY_EXTERN(LogType, Log, All); @@ -967,7 +970,7 @@ protected: (TIsPODType::Value ? CPF_IsPlainOldData : CPF_None) | (TIsTriviallyDestructible::Value ? CPF_NoDestructor : CPF_None) | (TIsZeroConstructType::Value ? CPF_ZeroConstructor : CPF_None) - | (THasGetTypeHash::Value ? CPF_HasGetValueTypeHash : CPF_None); + | (TModels::Value ? CPF_HasGetValueTypeHash : CPF_None); } }; @@ -4343,6 +4346,9 @@ public: protected: virtual FMulticastScriptDelegate::FInvocationList& GetInvocationList(const void* PropertyValue) const; // End of UMulticastDelegateProperty interface + +private: + virtual void SerializeItemInternal(FArchive& Ar, void* Value, void const* Defaults) const; }; /** Describes a single node in a custom property list. */ diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/WeakObjectPtr.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/WeakObjectPtr.h index 999a296814ad..c02216e40421 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/WeakObjectPtr.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/WeakObjectPtr.h @@ -9,21 +9,19 @@ #include "CoreMinimal.h" #include "UObject/UObjectArray.h" -/*** - * +/** * FWeakObjectPtr is a weak pointer to a UObject. - * It can return NULL later if the object is garbage collected. + * It can return nullptr later if the object is garbage collected. * It has no impact on if the object is garbage collected or not. * It can't be directly used across a network. * * Most often it is used when you explicitly do NOT want to prevent something from being garbage collected. - **/ - + */ struct FWeakObjectPtr { public: - /** NULL constructor **/ + /** Null constructor **/ FORCEINLINE FWeakObjectPtr() { Reset(); @@ -31,8 +29,8 @@ public: /** * Construct from an object pointer * @param Object object to create a weak pointer to - **/ - FORCEINLINE FWeakObjectPtr(const class UObject *Object) + */ + FORCEINLINE FWeakObjectPtr(const class UObject* Object) { (*this)=Object; } @@ -40,14 +38,14 @@ public: /** * Construct from another weak pointer * @param Other weak pointer to copy from - **/ - FORCEINLINE FWeakObjectPtr(const FWeakObjectPtr &Other) + */ + FORCEINLINE FWeakObjectPtr(const FWeakObjectPtr& Other) { (*this)=Other; } /** - * Reset the weak pointer back to the NULL state + * Reset the weak pointer back to the null state */ FORCEINLINE void Reset() { @@ -58,24 +56,25 @@ public: /** * Copy from an object pointer * @param Object object to create a weak pointer to - **/ - COREUOBJECT_API void operator=(const class UObject *Object); + */ + COREUOBJECT_API void operator=(const class UObject* Object); /** * Construct from another weak pointer * @param Other weak pointer to copy from - **/ - FORCEINLINE void operator=(const FWeakObjectPtr &Other) + */ + FORCEINLINE void operator=(const FWeakObjectPtr& Other) { ObjectIndex = Other.ObjectIndex; ObjectSerialNumber = Other.ObjectSerialNumber; } /** - * Compare weak pointers for equality + * Compare weak pointers for equality. + * If both pointers would return nullptr from Get() they count as equal even if they were not initialized to the same object. * @param Other weak pointer to compare to - **/ - FORCEINLINE bool operator==(const FWeakObjectPtr &Other) const + */ + FORCEINLINE bool operator==(const FWeakObjectPtr& Other) const { return (ObjectIndex == Other.ObjectIndex && ObjectSerialNumber == Other.ObjectSerialNumber) || @@ -85,14 +84,18 @@ public: /** * Compare weak pointers for inequality * @param Other weak pointer to compare to - **/ - FORCEINLINE bool operator!=(const FWeakObjectPtr &Other) const + */ + FORCEINLINE bool operator!=(const FWeakObjectPtr& Other) const { return (ObjectIndex != Other.ObjectIndex || ObjectSerialNumber != Other.ObjectSerialNumber) && (IsValid() || Other.IsValid()); } + /** + * Returns true if two weak pointers were originally set to the same object, even if they are now stale + * @param Other weak pointer to compare to + */ FORCEINLINE bool HasSameIndexAndSerialNumber(const FWeakObjectPtr& Other) const { return ObjectIndex == Other.ObjectIndex && ObjectSerialNumber == Other.ObjectSerialNumber; @@ -100,27 +103,27 @@ public: /** * Dereference the weak pointer. - * @param bEvenIfPendingKill, if this is true, pendingkill objects are considered valid - * @return NULL if this object is gone or the weak pointer was NULL, otherwise a UObject pointer + * @param bEvenIfPendingKill if this is true, pendingkill objects are considered valid + * @return nullptr if this object is gone or the weak pointer is explicitly null, otherwise a valid uobject pointer */ - COREUOBJECT_API class UObject *Get(bool bEvenIfPendingKill) const; + COREUOBJECT_API class UObject* Get(bool bEvenIfPendingKill) const; /** * Dereference the weak pointer. This is an optimized version implying bEvenIfPendingKill=false. - * @return NULL if this object is gone or the weak pointer was NULL, otherwise a UObject pointer + * @return nullptr if this object is gone or the weak pointer is explicitly null, otherwise a valid uobject pointer */ - COREUOBJECT_API class UObject *Get(/*bool bEvenIfPendingKill = false*/) const; + COREUOBJECT_API class UObject* Get(/*bool bEvenIfPendingKill = false*/) const; /** Dereference the weak pointer even if it is RF_PendingKill or RF_Unreachable */ - COREUOBJECT_API class UObject *GetEvenIfUnreachable() const; + COREUOBJECT_API class UObject* GetEvenIfUnreachable() const; /** * Test if this points to a live UObject - * @param bEvenIfPendingKill, if this is true, pendingkill are not considered invalid - * @param bThreadsafeTest, if true then function will just give you information whether referenced - * UObject is gone forever (@return false) or if it is still there (@return true, no object flags checked). - * @return true if Get() would return a valid non-null pointer, if bThreadsafeTest == true then @see @param bThreadsafeTest - **/ + * @param bEvenIfPendingKill if this is true, pendingkill are not considered invalid + * @param bThreadsafeTest if true then function will just give you information whether referenced + * UObject is gone forever (return false) or if it is still there (return true, no object flags checked). + * @return true if Get() would return a valid non-null pointer + */ COREUOBJECT_API bool IsValid(bool bEvenIfPendingKill, bool bThreadsafeTest = false) const; /** @@ -131,12 +134,21 @@ public: /** * Slightly different than !IsValid(), returns true if this used to point to a UObject, but doesn't any more and has not been assigned or reset in the mean time. - * @param bIncludingIfPendingKill, if this is false, pendingkill objects are not considered stale - * @param bThreadsafeTest, set it to true when testing outside of Game Thread. Results in false if WeakObjPtr point to an existing object (no flags checked) + * @param bIncludingIfPendingKill if this is false, pendingkill objects are not considered stale + * @param bThreadsafeTest set it to true when testing outside of Game Thread. Results in false if WeakObjPtr point to an existing object (no flags checked) * @return true if this used to point at a real object but no longer does. - **/ + */ COREUOBJECT_API bool IsStale(bool bIncludingIfPendingKill = true, bool bThreadsafeTest = false) const; + /** + * Returns true if this pointer was explicitly assigned to null, was reset, or was never initialized. + * If this returns true, IsValid() and IsStale() will both return false. + */ + FORCEINLINE bool IsExplicitlyNull() const + { + return ObjectIndex == INDEX_NONE; + } + /** Hash function. */ friend uint32 GetTypeHash(const FWeakObjectPtr& WeakObjectPtr) { @@ -164,7 +176,7 @@ private: /** * internal function to test for serial number matches * @return true if the serial number in this matches the central table - **/ + */ FORCEINLINE_DEBUGGABLE bool SerialNumbersMatch() const { checkSlow(ObjectSerialNumber > FUObjectArray::START_SERIAL_NUMBER && ObjectIndex >= 0); // otherwise this is a corrupted weak pointer diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimBlueprint.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimBlueprint.h index 129a8663d5cd..b27561b196dc 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimBlueprint.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimBlueprint.h @@ -152,6 +152,7 @@ class ENGINE_API UAnimBlueprint : public UBlueprint, public IInterface_PreviewMe } virtual void PostLoad() override; + virtual bool FindDiffs(const UBlueprint* OtherBlueprint, FDiffResults& Results) const override; protected: // Broadcast when an override is changed, allowing derived blueprints to be updated diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress.h index aae55fbbc427..66aa7e09c100 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress.h @@ -17,6 +17,8 @@ #include "AnimationCompression.h" #include "AnimCompress.generated.h" +struct FCompressibleAnimData; + //Helper function for ddc key generation uint8 MakeBitForFlag(uint32 Item, uint32 Position); @@ -183,9 +185,9 @@ class ENGINE_API FCompressionMemorySummary public: FCompressionMemorySummary(bool bInEnabled); - void GatherPreCompressionStats(UAnimSequence* Seq, int32 ProgressNumerator, int32 ProgressDenominator); + void GatherPreCompressionStats(const FCompressibleAnimData& CompressibleAnimData, int32 PreCompressedSize, int32 ProgressNumerator, int32 ProgressDenominator); - void GatherPostCompressionStats(UAnimSequence* Seq, const TArray& BoneData, double CompressionTime); + void GatherPostCompressionStats(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& CompressedData, double CompressionTime); ~FCompressionMemorySummary(); @@ -220,9 +222,9 @@ struct ENGINE_API FAnimCompressContext private: FCompressionMemorySummary CompressionSummary; - void GatherPreCompressionStats(UAnimSequence* Seq); + void GatherPreCompressionStats(const FCompressibleAnimData& CompressibleAnimData, int32 PreviousCompressionSize); - void GatherPostCompressionStats(UAnimSequence* Seq, const TArray& BoneData, double CompressionTime); + void GatherPostCompressionStats(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& CompressedData, double CompressionTime); public: @@ -231,14 +233,28 @@ public: bool bAllowAlternateCompressor; bool bOutput; - FAnimCompressContext(bool bInAllowAlternateCompressor, bool bInOutput, uint32 InMaxAnimations = 1) : CompressionSummary(bInOutput), AnimIndex(0), MaxAnimations(InMaxAnimations), bAllowAlternateCompressor(bInAllowAlternateCompressor), bOutput(bInOutput) {} + FAnimCompressContext(bool bInAllowAlternateCompressor, bool bInOutput, uint32 InMaxAnimations = 1) + : CompressionSummary(bInOutput) + , AnimIndex(0) + , MaxAnimations(InMaxAnimations) + , bAllowAlternateCompressor(bInAllowAlternateCompressor) + , bOutput(bInOutput) + {} // If we are duping a compression context we don't want the CompressionSummary to output - FAnimCompressContext(const FAnimCompressContext& Rhs) : CompressionSummary(false), AnimIndex(Rhs.AnimIndex), MaxAnimations(Rhs.MaxAnimations), bAllowAlternateCompressor(Rhs.bAllowAlternateCompressor), bOutput(Rhs.bOutput) {} + FAnimCompressContext(const FAnimCompressContext& Rhs) + : CompressionSummary(false) + , AnimIndex(Rhs.AnimIndex) + , MaxAnimations(Rhs.MaxAnimations) + , bAllowAlternateCompressor(Rhs.bAllowAlternateCompressor) + , bOutput(Rhs.bOutput) + {} friend class FAnimationUtils; + friend class FDerivedDataAnimationCompression; }; +#if USE_SEGMENTING_CONTEXT ////////////////////////////////////////////////////////////////////////// // FAnimSegmentContext - This holds the relevant intermediate information // when compressing animation sequence segments. @@ -260,6 +276,7 @@ struct FAnimSegmentContext TArray CompressedByteStream; TArray CompressedTrivialTracksByteStream; }; +#endif UCLASS(abstract, hidecategories=Object, MinimalAPI, EditInlineNew) class UAnimCompress : public UObject @@ -305,15 +322,6 @@ class UAnimCompress : public UObject #if WITH_EDITOR public: - /** - * Reduce the number of keyframes and bitwise compress the specified sequence. - * - * @param AnimSeq The animation sequence to compress. - * @param bOutput If false don't generate output or compute memory savings. - * @return false if a skeleton was needed by the algorithm but not provided. - */ - ENGINE_API bool Reduce(class UAnimSequence* AnimSeq, bool bOutput, const TArray& BoneData); - /** * Reduce the number of keyframes and bitwise compress all sequences in the specified array. * @@ -321,7 +329,8 @@ public: * @param bOutput If false don't generate output or compute memory savings. * @return false if a skeleton was needed by the algorithm but not provided. */ - ENGINE_API bool Reduce(class UAnimSequence* AnimSeq, FAnimCompressContext& Context, const TArray& BoneData); + ENGINE_API bool Reduce(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult); + #endif // WITH_EDITOR protected: #if WITH_EDITOR @@ -331,7 +340,7 @@ protected: * * @return true if the keyframe reduction was successful. */ - virtual void DoReduction(class UAnimSequence* AnimSeq, const TArray& BoneData) PURE_VIRTUAL(UAnimCompress::DoReduction,); + virtual void DoReduction(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) PURE_VIRTUAL(UAnimCompress::DoReduction,); #endif // WITH_EDITOR /** * Common compression utility to remove 'redundant' position keys based on the provided delta threshold @@ -420,11 +429,13 @@ protected: * @param MaxRotDelta Maximum angle threshold to consider stationary motion * @param MaxScaleDelta Maximum scale threshold to consider stationary motion */ +#if USE_SEGMENTING_CONTEXT static void FilterTrivialKeys( TArray& RawSegments, float MaxPosDelta, float MaxRotDelta, float MaxScaleDelta); +#endif /** * Common compression utility to retain only intermittent position keys. For example, @@ -521,6 +532,7 @@ protected: * @param MaxNumFramesPerSegment The maximum number of frames within a segment * @param OutRawSegments The array of segments to populate */ +#if USE_SEGMENTING_CONTEXT static void SeparateRawDataIntoTracks( const UAnimSequence& AnimSeq, const TArray& TranslationData, @@ -529,7 +541,7 @@ protected: int32 IdealNumFramesPerSegment, int32 MaxNumFramesPerSegment, TArray& OutRawSegments); - +#endif /** * Common compression utility to walk an array of rotation tracks and enforce * that all adjacent rotation keys are represented by shortest-arc quaternion pairs. @@ -553,7 +565,8 @@ public: * @param IncludeKeyTable true if the compressed data should also contain a table of frame indices for each key. (required by some codecs) */ static void BitwiseCompressAnimationTracks( - UAnimSequence* Seq, + const FCompressibleAnimData& CompressibleAnimData, + FCompressibleAnimDataResult& OutCompressedData, AnimationCompressionFormat TargetTranslationFormat, AnimationCompressionFormat TargetRotationFormat, AnimationCompressionFormat TargetScaleFormat, @@ -573,6 +586,7 @@ public: * @param RawSegments The array of segments to compress * @param bIsSorted For variable interpolation, is the compressed data sorted or not? */ +#if USE_SEGMENTING_CONTEXT static void BitwiseCompressAnimationTracks( UAnimSequence& AnimSeq, AnimationCompressionFormat TargetTranslationFormat, @@ -616,6 +630,7 @@ public: * @param bIsSorted For variable interpolation, is the compressed data sorted or not? */ static void CoalesceCompressedSegments(UAnimSequence& AnimSeq, const TArray& RawSegments, bool bIsSorted = false); +#endif #if WITH_EDITOR FString MakeDDCKey(); @@ -688,6 +703,7 @@ protected: const float* Mins, const float* Ranges); +#if USE_SEGMENTING_CONTEXT /** * Utility function that performs minimal sanity checks. * @@ -695,6 +711,7 @@ protected: * @param Segment The segment to check */ static void SanityCheckTrackData(const UAnimSequence& AnimSeq, const FAnimSegmentContext& Segment); +#endif /** * Calculates the translation track range. @@ -726,6 +743,7 @@ protected: */ static void CalculateTrackRange(const FScaleTrack& ScaleData, AnimationCompressionFormat Format, FVector& OutMin, FVector& OutExtent); +#if USE_SEGMENTING_CONTEXT /** * Calculates the track ranges within a segment. * @@ -741,6 +759,7 @@ protected: AnimationCompressionFormat TargetScaleFormat, const FAnimSegmentContext& Segment, TArray& TrackRanges); +#endif /** * Structure to wrap and represent track key flags. @@ -759,6 +778,7 @@ protected: uint8 Flags; }; +#if USE_SEGMENTING_CONTEXT /** * Writes the necessary track ranges to a byte stream. * @@ -881,6 +901,7 @@ protected: TFunction& ByteStream, AnimationCompressionFormat Format, const FVector& Key, const float* Mins, const float* Ranges, int32 TrackIndex)> PackScaleKeyFun, const FAnimSegmentContext& Segment, const TArray& TrackRanges); +#endif /** * Pads a byte stream to force a particular alignment for the data to follow. diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_Automatic.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_Automatic.h index bc52c3346f1a..79bfe6e5ef3f 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_Automatic.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_Automatic.h @@ -51,7 +51,7 @@ class UAnimCompress_Automatic : public UAnimCompress protected: //~ Begin UAnimCompress Interface #if WITH_EDITOR - virtual void DoReduction(class UAnimSequence* AnimSeq, const TArray& BoneData) override; + virtual void DoReduction(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) override; virtual void PopulateDDCKey(FArchive& Ar) override; #endif // WITH_EDITOR //~ Begin UAnimCompress Interface diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_BitwiseCompressOnly.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_BitwiseCompressOnly.h index d0e4039fea03..ac57584467ba 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_BitwiseCompressOnly.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_BitwiseCompressOnly.h @@ -21,7 +21,7 @@ class UAnimCompress_BitwiseCompressOnly : public UAnimCompress protected: //~ Begin UAnimCompress Interface #if WITH_EDITOR - virtual void DoReduction(class UAnimSequence* AnimSeq, const TArray& BoneData) override; + virtual void DoReduction(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) override; #endif // WITH_EDITOR //~ Begin UAnimCompress Interface }; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_LeastDestructive.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_LeastDestructive.h index 9a9a5f80415b..cd5229cc36a0 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_LeastDestructive.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_LeastDestructive.h @@ -21,7 +21,7 @@ class UAnimCompress_LeastDestructive : public UAnimCompress protected: //~ Begin UAnimCompress Interface #if WITH_EDITOR - virtual void DoReduction(class UAnimSequence* AnimSeq, const TArray& BoneData) override; + virtual void DoReduction(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) override; #endif // WITH_EDITOR //~ Begin UAnimCompress Interface }; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_PerTrackCompression.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_PerTrackCompression.h index 8f90ea80338c..bb9ebb60640f 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_PerTrackCompression.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_PerTrackCompression.h @@ -139,7 +139,7 @@ protected: //~ Begin UAnimCompress Interface #if WITH_EDITOR virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; - virtual void DoReduction(class UAnimSequence* AnimSeq, const TArray& BoneData) override; + virtual void DoReduction(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) override; virtual void PopulateDDCKey(FArchive& Ar) override; #endif // WITH_EDITOR //~ Begin UAnimCompress Interface @@ -147,18 +147,20 @@ protected: #if WITH_EDITOR //~ Begin UAnimCompress_RemoveLinearKeys Interface virtual void CompressUsingUnderlyingCompressor( - UAnimSequence* AnimSeq, - const TArray& BoneData, + const FCompressibleAnimData& CompressibleAnimData, + FCompressibleAnimDataResult& OutCompressedData, const TArray& TranslationData, const TArray& RotationData, const TArray& ScaleData, const bool bFinalPass) override; +#if USE_SEGMENTING_CONTEXT virtual void CompressUsingUnderlyingCompressor( UAnimSequence& AnimSeq, const TArray& BoneData, TArray& RawSegments, const bool bFinalPass) override; +#endif /** * Performs the per track compression optimization for a single segment. @@ -167,8 +169,7 @@ protected: void OptimizeSegmentTracks(struct FOptimizeSegmentTracksContext& Context) const; virtual void FilterBeforeMainKeyRemoval( - UAnimSequence* AnimSeq, - const TArray& BoneData, + const FCompressibleAnimData& CompressibleAnimData, TArray& TranslationData, TArray& RotationData, TArray& ScaleData) override; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_RemoveEverySecondKey.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_RemoveEverySecondKey.h index f44e008a249b..339eaed4eca7 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_RemoveEverySecondKey.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_RemoveEverySecondKey.h @@ -32,7 +32,7 @@ class UAnimCompress_RemoveEverySecondKey : public UAnimCompress protected: //~ Begin UAnimCompress Interface #if WITH_EDITOR - virtual void DoReduction(class UAnimSequence* AnimSeq, const TArray& BoneData) override; + virtual void DoReduction(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) override; virtual void PopulateDDCKey(FArchive& Ar) override; #endif // WITH_EDITOR //~ Begin UAnimCompress Interface diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_RemoveLinearKeys.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_RemoveLinearKeys.h index 8ea8fb05568b..9e069e185c95 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_RemoveLinearKeys.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_RemoveLinearKeys.h @@ -106,7 +106,7 @@ class UAnimCompress_RemoveLinearKeys : public UAnimCompress protected: //~ Begin UAnimCompress Interface #if WITH_EDITOR - virtual void DoReduction(class UAnimSequence* AnimSeq, const TArray& BoneData) override; + virtual void DoReduction(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) override; virtual void PopulateDDCKey(FArchive& Ar) override; #endif // WITH_EDITOR //~ Begin UAnimCompress Interface @@ -116,8 +116,7 @@ protected: * Pre-filters the tracks before running the main key removal algorithm */ virtual void FilterBeforeMainKeyRemoval( - UAnimSequence* AnimSeq, - const TArray& BoneData, + const FCompressibleAnimData& CompressibleAnimData, TArray& TranslationData, TArray& RotationData, TArray& ScaleData); @@ -126,13 +125,14 @@ protected: * Compresses the tracks passed in using the underlying compressor for this key removal codec */ virtual void CompressUsingUnderlyingCompressor( - UAnimSequence* AnimSeq, - const TArray& BoneData, + const FCompressibleAnimData& CompressibleAnimData, + FCompressibleAnimDataResult& OutCompressedData, const TArray& TranslationData, const TArray& RotationData, const TArray& ScaleData, const bool bFinalPass); +#if USE_SEGMENTING_CONTEXT /** * Compresses the tracks passed in using the underlying compressor for this key removal codec for a single segment */ @@ -141,13 +141,14 @@ protected: const TArray& BoneData, TArray& RawSegments, const bool bFinalPass); +#endif /** * Updates the world bone transforms for a range of bone indices */ void UpdateWorldBoneTransformRange( - UAnimSequence* AnimSeq, - const TArray& BoneData, + const FCompressibleAnimData& CompressibleAnimData, + FCompressibleAnimDataResult& OutCompressedData, const TArray& RefPose, const TArray& PositionTracks, const TArray& RotationTracks, @@ -157,6 +158,32 @@ protected: bool UseRaw, TArray& OutputWorldBones); + /** + * To guide the key removal process, we need to maintain a table of world transforms + * for the bones we are investigating. This helper function fills a row of the + * table for a specified bone. + */ + void UpdateWorldBoneTransformTable( + const FCompressibleAnimData& CompressibleAnimData, + FCompressibleAnimDataResult& OutCompressedData, + const TArray& RefPose, + int32 BoneIndex, + bool UseRaw, + TArray& OutputWorldBones); + + /** + * Creates a list of the bone atom result for every frame of a given track + */ + static void UpdateBoneAtomList( + const FCompressibleAnimData& CompressibleAnimData, + FCompressibleAnimDataResult& OutCompressedData, + int32 BoneIndex, + int32 TrackIndex, + int32 NumFrames, + float TimePerFrame, + TArray& BoneAtoms); + +#if USE_SEGMENTING_CONTEXT /** * Updates the world bone transforms for a range of bone indices for a single segment */ @@ -165,19 +192,6 @@ protected: int32 StartingBoneIndex, int32 EndingBoneIndex); - /** - * To guide the key removal process, we need to maintain a table of world transforms - * for the bones we are investigating. This helper function fills a row of the - * table for a specified bone. - */ - void UpdateWorldBoneTransformTable( - UAnimSequence* AnimSeq, - const TArray& BoneData, - const TArray& RefPose, - int32 BoneIndex, - bool UseRaw, - TArray& OutputWorldBones); - /** * To guide the key removal process, we need to maintain a table of world transforms * for the bones we are investigating. This helper function fills a row of the @@ -189,17 +203,6 @@ protected: bool UseRaw, TArray& OutputWorldBones); - /** - * Creates a list of the bone atom result for every frame of a given track - */ - static void UpdateBoneAtomList( - UAnimSequence* AnimSeq, - int32 BoneIndex, - int32 TrackIndex, - int32 NumFrames, - float TimePerFrame, - TArray& BoneAtoms); - /** * Creates a list of the bone atom result for every frame of a given track for a single segment */ @@ -207,6 +210,7 @@ protected: const FProcessAnimationTracksContext& Context, int32 TrackIndex, TArray& BoneAtoms) const; +#endif /** * If the passed in animation sequence is additive, converts it to absolute (using the frame 0 pose) and returns true @@ -214,9 +218,8 @@ protected: * * @param AnimSeq The animation sequence being compressed * - * @return true if the animation was additive and has been converted to absolute space. */ - bool ConvertFromRelativeSpace(UAnimSequence* AnimSeq); + void ConvertFromRelativeSpace(FCompressibleAnimData& CompressibleAnimData); /** * Converts an absolute animation sequence and matching track data to a relative (additive) one. @@ -227,7 +230,7 @@ protected: * @param ScaleData Scale Tracks to convert to relative space * */ - void ConvertToRelativeSpace(UAnimSequence* AnimSeq, TArray& TranslationData, TArray& RotationData, TArray& ScaleData); + void ConvertToRelativeSpaceBoth(FCompressibleAnimData& CompressibleAnimData, TArray& TranslationData, TArray& RotationData, TArray& ScaleData); /** * Converts an absolute animation sequence to a relative (additive) one. @@ -235,7 +238,7 @@ protected: * @param AnimSeq The animation sequence being compressed and converted * */ - void ConvertToRelativeSpace(UAnimSequence& AnimSeq) const; + void ConvertToRelativeSpace(FCompressibleAnimData& CompressibleAnimData) const; /** * Converts track data to relative (additive) space. @@ -246,7 +249,7 @@ protected: * @param ScaleData Scale Tracks to convert to relative space * */ - void ConvertToRelativeSpace(const UAnimSequence& AnimSeq, TArray& TranslationData, TArray& RotationData, TArray& ScaleData) const; + void ConvertToRelativeSpace(const FCompressibleAnimData& CompressibleAnimData, TArray& TranslationData, TArray& RotationData, TArray& ScaleData) const; /** @@ -262,12 +265,13 @@ protected: * @return None. */ void ProcessAnimationTracks( - UAnimSequence* AnimSeq, - const TArray& BoneData, + const FCompressibleAnimData& CompressibleAnimData, + FCompressibleAnimDataResult& OutCompressedData, TArray& PositionTracks, TArray& RotationTracks, TArray& ScaleTracks); +#if USE_SEGMENTING_CONTEXT /** * Locates spans of keys within the position and rotation tracks provided which can be estimated * through linear interpolation of the surrounding keys. The remaining key values are bit packed into @@ -295,8 +299,10 @@ protected: * Samples a track within a segment at a specified time with the current track format. */ static FTransform SampleSegment(const FProcessAnimationTracksContext& Context, int32 TrackIndex, float Time); +#endif private: +#if USE_SEGMENTING_CONTEXT /** * Performs the linear key reduction and retargetting for a single segment. * This can be called from multiple threads concurrently. @@ -304,6 +310,7 @@ private: void ProcessAnimationTracks(FProcessAnimationTracksContext& Context); friend struct FAsyncProcessAnimationTracksTaskGroupContext; +#endif #endif // WITH_EDITOR }; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_RemoveTrivialKeys.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_RemoveTrivialKeys.h index e040bc8693c0..a6f06f0aa9ef 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_RemoveTrivialKeys.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCompress_RemoveTrivialKeys.h @@ -31,7 +31,7 @@ class UAnimCompress_RemoveTrivialKeys : public UAnimCompress protected: //~ Begin UAnimCompress Interface #if WITH_EDITOR - virtual void DoReduction(class UAnimSequence* AnimSeq, const TArray& BoneData) override; + virtual void DoReduction(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) override; virtual void PopulateDDCKey(FArchive& Ar) override; #endif // WITH_EDITOR //~ Begin UAnimCompress Interface diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec.h index 2c2f5b67d05d..685b6a224462 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec.h @@ -4,6 +4,7 @@ #include "CoreMinimal.h" #include "Animation/AnimTypes.h" +#include "Animation/AnimCompressionTypes.h" #include "AnimCurveCompressionCodec.generated.h" class UAnimCurveCompressionCodec; @@ -53,7 +54,7 @@ class ENGINE_API UAnimCurveCompressionCodec : public UObject virtual bool IsCodecValid() const { return true; } /** Compresses the curve data from an animation sequence. */ - virtual bool Compress(const UAnimSequence& AnimSeq, FAnimCurveCompressionResult& OutResult) PURE_VIRTUAL(UAnimCurveCompressionCodec::Compress, return false;); + virtual bool Compress(const FCompressibleAnimData& AnimSeq, FAnimCurveCompressionResult& OutResult) PURE_VIRTUAL(UAnimCurveCompressionCodec::Compress, return false;); /* * Called to generate a unique DDC key for this codec instance. @@ -67,12 +68,12 @@ class ENGINE_API UAnimCurveCompressionCodec : public UObject * Note: Codecs should _NOT_ rely on any member properties during decompression. Decompression * behavior should entirely be driven by code and the compressed data. */ - virtual void DecompressCurves(const UAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const PURE_VIRTUAL(UAnimCurveCompressionCodec::DecompressCurves, ); + virtual void DecompressCurves(const FCompressedAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const PURE_VIRTUAL(UAnimCurveCompressionCodec::DecompressCurves, ); /* * Decompress a single curve. * Note: Codecs should _NOT_ rely on any member properties during decompression. Decompression * behavior should entirely be driven by code and the compressed data. */ - virtual float DecompressCurve(const UAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const PURE_VIRTUAL(UAnimCurveCompressionCodec::DecompressCurve, return 0.0f;); + virtual float DecompressCurve(const FCompressedAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const PURE_VIRTUAL(UAnimCurveCompressionCodec::DecompressCurve, return 0.0f;); }; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_CompressedRichCurve.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_CompressedRichCurve.h index 7555890a98de..c1b9fd6d9d46 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_CompressedRichCurve.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_CompressedRichCurve.h @@ -33,10 +33,10 @@ class ENGINE_API UAnimCurveCompressionCodec_CompressedRichCurve : public UAnimCu #if WITH_EDITORONLY_DATA // UAnimCurveCompressionCodec overrides - virtual bool Compress(const UAnimSequence& AnimSeq, FAnimCurveCompressionResult& OutResult) override; + virtual bool Compress(const FCompressibleAnimData& AnimSeq, FAnimCurveCompressionResult& OutResult) override; virtual void PopulateDDCKey(FArchive& Ar) override; #endif - virtual void DecompressCurves(const UAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const override; - virtual float DecompressCurve(const UAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const override; + virtual void DecompressCurves(const FCompressedAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const override; + virtual float DecompressCurve(const FCompressedAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const override; }; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_UniformlySampled.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_UniformlySampled.h index ead4a0f26ae3..a5f8fe872ca7 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_UniformlySampled.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_UniformlySampled.h @@ -29,10 +29,10 @@ class ENGINE_API UAnimCurveCompressionCodec_UniformlySampled : public UAnimCurve #if WITH_EDITORONLY_DATA // UAnimCurveCompressionCodec overrides - virtual bool Compress(const UAnimSequence& AnimSeq, FAnimCurveCompressionResult& OutResult) override; + virtual bool Compress(const FCompressibleAnimData& AnimSeq, FAnimCurveCompressionResult& OutResult) override; virtual void PopulateDDCKey(FArchive& Ar) override; #endif - virtual void DecompressCurves(const UAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const override; - virtual float DecompressCurve(const UAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const override; + virtual void DecompressCurves(const FCompressedAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const override; + virtual float DecompressCurve(const FCompressedAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const override; }; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionSettings.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionSettings.h index 48da018de23a..b7bde7576ca1 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionSettings.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionSettings.h @@ -3,6 +3,7 @@ #pragma once #include "CoreMinimal.h" +#include "Animation/AnimCompressionTypes.h" #include "AnimCurveCompressionSettings.generated.h" class UAnimCurveCompressionCodec; @@ -31,11 +32,10 @@ class ENGINE_API UAnimCurveCompressionSettings : public UObject bool AreSettingsValid() const; /* - * Compresses the animation curves inside the supplied sequence. - * Note that this will modify the animation sequence by populating the compressed bytes - * and the codec used but it is left unchanged if compression fails. + * Compresses the animation curves inside the supplied sequence data. + * The resultant compressed data is applied to the OutCompressedData structure. */ - bool Compress(UAnimSequence& AnimSeq) const; + bool Compress(const FCompressibleAnimData& AnimSeq, FCompressedAnimSequence& OutCompressedData) const; /** Generates a DDC key that takes into account the current settings and selected codec. */ FString MakeDDCKey() const; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimLinkableElement.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimLinkableElement.h index 66d1f36c95f6..dd89a45d2071 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimLinkableElement.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimLinkableElement.h @@ -36,7 +36,7 @@ namespace EAnimLinkMethod * @see FAnimNotifyEvent */ USTRUCT() -struct FAnimLinkableElement +struct ENGINE_VTABLE FAnimLinkableElement { GENERATED_USTRUCT_BODY() diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequence.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequence.h index 94de086aa622..2526656b2efa 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequence.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequence.h @@ -15,15 +15,11 @@ #include "Animation/AnimationAsset.h" #include "Animation/AnimCurveTypes.h" #include "Animation/AnimSequenceBase.h" - -#include "Async/MappedFileHandle.h" -#include "HAL/PlatformFilemanager.h" -#include "GenericPlatform/GenericPlatformFile.h" -#include "Misc/Paths.h" -#include "Serialization/BulkData.h" +#include "Animation/AnimCompressionTypes.h" #include "AnimSequence.generated.h" +#define USE_SEGMENTING_CONTEXT 0 // Uses segmenting in anim compression + context in decompression typedef TArray FTransformArrayA2; @@ -32,54 +28,6 @@ struct FAnimCompressContext; struct FAnimSequenceDecompressionContext; struct FCompactPose; -/** - * Indicates animation data key format. - */ -UENUM() -enum AnimationKeyFormat -{ - AKF_ConstantKeyLerp, - AKF_VariableKeyLerp, - AKF_PerTrackCompression, - AKF_MAX, -}; - -/** - * Raw keyframe data for one track. Each array will contain either NumFrames elements or 1 element. - * One element is used as a simple compression scheme where if all keys are the same, they'll be - * reduced to 1 key that is constant over the entire sequence. - */ -USTRUCT() -struct ENGINE_API FRawAnimSequenceTrack -{ - GENERATED_USTRUCT_BODY() - - /** Position keys. */ - UPROPERTY() - TArray PosKeys; - - /** Rotation keys. */ - UPROPERTY() - TArray RotKeys; - - /** Scale keys. */ - UPROPERTY() - TArray ScaleKeys; - - // Serializer. - friend FArchive& operator<<( FArchive& Ar, FRawAnimSequenceTrack& T ) - { - T.PosKeys.BulkSerialize(Ar); - T.RotKeys.BulkSerialize(Ar); - - if (Ar.UE4Ver() >= VER_UE4_ANIM_SUPPORT_NONUNIFORM_SCALE_ANIMATION) - { - T.ScaleKeys.BulkSerialize(Ar); - } - - return Ar; - } -}; // These two always should go together, but it is not right now. // I wonder in the future, we change all compressed to be inside as well, so they all stay together @@ -119,33 +67,6 @@ struct ENGINE_API FAnimSequenceTrackContainer } }; -// @note We have a plan to support skeletal hierarchy. When that happens, we'd like to keep skeleton indexing. -USTRUCT() -struct ENGINE_API FTrackToSkeletonMap -{ - GENERATED_USTRUCT_BODY() - - // Index of Skeleton.BoneTree this Track belongs to. - UPROPERTY() - int32 BoneTreeIndex; - - - FTrackToSkeletonMap() - : BoneTreeIndex(0) - { - } - - FTrackToSkeletonMap(int32 InBoneTreeIndex) - : BoneTreeIndex(InBoneTreeIndex) - { - } - - friend FArchive& operator<<(FArchive& Ar, FTrackToSkeletonMap &Item) - { - return Ar << Item.BoneTreeIndex; - } -}; - /** * Keyframe position data for one track. Pos(i) occurs at Time(i). Pos.Num() always equals Time.Num(). */ @@ -249,61 +170,6 @@ struct ENGINE_API FCompressedTrack }; -struct ENGINE_API FCompressedOffsetData -{ - TArray OffsetData; - - int32 StripSize; - - FCompressedOffsetData( int32 InStripSize=2 ) - : StripSize (InStripSize) - {} - - void SetStripSize(int32 InStripSize) - { - ensure (InStripSize > 0 ); - StripSize = InStripSize; - } - - const int32 GetOffsetData(int32 StripIndex, int32 Offset) const - { - checkSlow(OffsetData.IsValidIndex(StripIndex * StripSize + Offset)); - - return OffsetData[StripIndex * StripSize + Offset]; - } - - void SetOffsetData(int32 StripIndex, int32 Offset, int32 Value) - { - checkSlow(OffsetData.IsValidIndex(StripIndex * StripSize + Offset)); - OffsetData[StripIndex * StripSize + Offset] = Value; - } - - void AddUninitialized(int32 NumOfTracks) - { - OffsetData.AddUninitialized(NumOfTracks*StripSize); - } - - void Empty(int32 NumOfTracks=0) - { - OffsetData.Empty(NumOfTracks*StripSize); - } - - int32 GetMemorySize() const - { - return sizeof(int32)*OffsetData.Num() + sizeof(int32); - } - - int32 GetNumTracks() const - { - return OffsetData.Num()/StripSize; - } - - bool IsValid() const - { - return (OffsetData.Num() > 0); - } -}; - // Param structure for UAnimSequence::RequestAnimCompressionParams struct ENGINE_API FRequestAnimCompressionParams { @@ -377,228 +243,6 @@ private: FArchive& operator<<(FArchive& Ar, FCompressedOffsetData& D); -/** - * Represents a segment of the anim sequence that is compressed. - */ -USTRUCT() -struct ENGINE_API FCompressedSegment -{ - GENERATED_USTRUCT_BODY() - - // Frame where the segment begins in the anim sequence - int32 StartFrame; - - // Num of frames contained in the segment - int32 NumFrames; - - // Segment data offset in CompressedByteStream - int32 ByteStreamOffset; - - /** The compression format that was used to compress translation tracks. */ - TEnumAsByte TranslationCompressionFormat; - - /** The compression format that was used to compress rotation tracks. */ - TEnumAsByte RotationCompressionFormat; - - /** The compression format that was used to compress rotation tracks. */ - TEnumAsByte ScaleCompressionFormat; - - FCompressedSegment() - : StartFrame(0) - , NumFrames(0) - , ByteStreamOffset(0) - , TranslationCompressionFormat(ACF_None) - , RotationCompressionFormat(ACF_None) - , ScaleCompressionFormat(ACF_None) - { - } - - friend FArchive& operator<<(FArchive& Ar, FCompressedSegment &Segment) - { - return Ar << Segment.StartFrame << Segment.NumFrames << Segment.ByteStreamOffset - << Segment.TranslationCompressionFormat << Segment.RotationCompressionFormat << Segment.ScaleCompressionFormat; - } -}; - - -template -class TMaybeMappedAllocator -{ -public: - - enum { NeedsElementType = false }; - enum { RequireRangeCheck = true }; - - class ForAnyElementType - { - public: - - /** Default constructor. */ - ForAnyElementType() - : Data(nullptr) - , MappedHandle(nullptr) - , MappedRegion(nullptr) - {} - - /** - * Moves the state of another allocator into this one. - * Assumes that the allocator is currently empty, i.e. memory may be allocated but any existing elements have already been destructed (if necessary). - * @param Other - The allocator to move the state from. This allocator should be left in a valid empty state. - */ - void MoveToEmpty(ForAnyElementType& Other) - { - checkSlow(this != &Other); - - Reset(); - - Data = Other.Data; - Other.Data = nullptr; - - MappedRegion = Other.MappedRegion; - Other->MappedRegion = nullptr; - - MappedHandle = Other.MappedHandle; - Other->MappedHandle = nullptr; - } - - /** Destructor. */ - ~ForAnyElementType() - { - Reset(); - } - - // FContainerAllocatorInterface - FScriptContainerElement* GetAllocation() const - { - return Data; - } - void ResizeAllocation( - int32 PreviousNumElements, - int32 NumElements, - SIZE_T NumBytesPerElement - ) - { - // Avoid calling FMemory::Realloc( nullptr, 0 ) as ANSI C mandates returning a valid pointer which is not what we want. - if (Data || NumElements) - { - check(!MappedHandle && !MappedRegion); // this could be supported, but it probably is never what you want, so we will just assert. - //checkSlow(((uint64)NumElements*(uint64)ElementTypeInfo.GetSize() < (uint64)INT_MAX)); - Data = (FScriptContainerElement*)FMemory::Realloc(Data, NumElements*NumBytesPerElement, Alignment); - } - } - int32 CalculateSlackReserve(int32 NumElements, int32 NumBytesPerElement) const - { - check(!MappedHandle && !MappedRegion); // this could be supported, but it probably is never what you want, so we will just assert. - return DefaultCalculateSlackReserve(NumElements, NumBytesPerElement, true, Alignment); - } - int32 CalculateSlackShrink(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const - { - check(!MappedHandle && !MappedRegion); // this could be supported, but it probably is never what you want, so we will just assert. - return DefaultCalculateSlackShrink(NumElements, NumAllocatedElements, NumBytesPerElement, true, Alignment); - } - int32 CalculateSlackGrow(int32 NumElements, int32 NumAllocatedElements, int32 NumBytesPerElement) const - { - check(!MappedHandle && !MappedRegion); // this could be supported, but it probably is never what you want, so we will just assert. - return DefaultCalculateSlackGrow(NumElements, NumAllocatedElements, NumBytesPerElement, true, Alignment); - } - - SIZE_T GetAllocatedSize(int32 NumAllocatedElements, SIZE_T NumBytesPerElement) const - { - return NumAllocatedElements * NumBytesPerElement; - } - - bool HasAllocation() - { - return !!Data; - } - - void AcceptFileMapping(IMappedFileHandle* InMappedHandle, IMappedFileRegion* InMappedRegion, void *MallocPtr) - { - check(!MappedHandle && !Data); // we could support stuff like this, but that usually isn't what we want for streamlined loading - Reset(); // just in case - if (InMappedHandle || InMappedRegion) - { - MappedHandle = InMappedHandle; - MappedRegion = InMappedRegion; - Data = (FScriptContainerElement*)MappedRegion->GetMappedPtr(); //@todo mapped files should probably be const-correct - check(IsAligned(Data, FPlatformProperties::GetMemoryMappingAlignment())); - } - else - { - Data = (FScriptContainerElement*)MallocPtr; - } - } - - bool IsMapped() const - { - return MappedRegion || MappedHandle; - } - private: - - FScriptContainerElement* Data; - IMappedFileHandle* MappedHandle; - IMappedFileRegion* MappedRegion; - - void Reset() - { - if (MappedRegion || MappedHandle) - { - delete MappedRegion; - delete MappedHandle; - MappedRegion = nullptr; - MappedHandle = nullptr; - Data = nullptr; // make sure we don't try to free this pointer - } - if (Data) - { - FMemory::Free(Data); - Data = nullptr; - } - } - - - ForAnyElementType(const ForAnyElementType&); - ForAnyElementType& operator=(const ForAnyElementType&); - }; - - template - class ForElementType : public ForAnyElementType - { - public: - - /** Default constructor. */ - ForElementType() - {} - - ElementType* GetAllocation() const - { - return (ElementType*)ForAnyElementType::GetAllocation(); - } - }; -}; - -template -class TMaybeMappedArray : public TArray> -{ -public: - TMaybeMappedArray() - { - } - TMaybeMappedArray(TMaybeMappedArray&&) = default; - TMaybeMappedArray(const TMaybeMappedArray&) = default; - TMaybeMappedArray& operator=(TMaybeMappedArray&&) = default; - TMaybeMappedArray& operator=(const TMaybeMappedArray&) = default; - - void AcceptOwnedBulkDataPtr(FOwnedBulkDataPtr* OwnedPtr, int32 Num) - { - this->ArrayNum = Num; - this->ArrayMax = Num; - this->AllocatorInstance.AcceptFileMapping(OwnedPtr->GetMappedHandle(), OwnedPtr->GetMappedRegion(), (void*)OwnedPtr->GetPointer()); - OwnedPtr->RelinquishOwnership(); - } -}; - - UCLASS(config=Engine, hidecategories=(UObject, Length), BlueprintType) class ENGINE_API UAnimSequence : public UAnimSequenceBase { @@ -621,9 +265,6 @@ protected: UPROPERTY(AssetRegistrySearchable, meta = (DisplayName = "Number of Keys")) int32 NumFrames; - // The number of frames that the animation had when compressed data was created (may have been resampled for instance) - int32 CompressedNumFrames; - /** * In the future, maybe keeping RawAnimSequenceTrack + TrackMap as one would be good idea to avoid inconsistent array size * TrackToSkeletonMapTable(i) should contains track mapping data for RawAnimationData(i). @@ -631,19 +272,6 @@ protected: UPROPERTY() TArray TrackToSkeletonMapTable; - /** - * Version of TrackToSkeletonMapTable for the compressed tracks. Due to baking additive data - * we can end up with a different amount of tracks to the original raw animation and so we must index into the - * compressed data using this - */ - TArray CompressedTrackToSkeletonMapTable; - - /** - * Much like track indices above, we need to be able to remap curve names. The FName inside FSmartName - * is serialized and the UID is generated at runtime. Stored in the DDC. - */ - TArray CompressedCurveNames; - /** * Raw uncompressed keyframe data. */ @@ -664,12 +292,6 @@ protected: * Source RawAnimationData. Only can be overridden by when transform curves are added first time OR imported */ TArray SourceRawAnimationData; - - /** - * Temporary base pose buffer for additive animation that is used by compression. Do not use this unless within compression. It is not available. - * This is used by remove linear key that has to rebuild to full transform in order to compress - */ - TArray TemporaryAdditiveBaseAnimationData; #endif public: @@ -694,80 +316,11 @@ public: UPROPERTY(Category=Compression, EditAnywhere) class UAnimCurveCompressionSettings* CurveCompressionSettings; - /** The codec used by the compressed data as determined by the compression settings. */ - class UAnimCurveCompressionCodec* CurveCompressionCodec; - - /** The compression format that was used to compress translation tracks. */ - TEnumAsByte TranslationCompressionFormat; - - /** The compression format that was used to compress rotation tracks. */ - TEnumAsByte RotationCompressionFormat; - - /** The compression format that was used to compress rotation tracks. */ - TEnumAsByte ScaleCompressionFormat; - - /** - * An array of 4*NumTrack ints, arranged as follows: - PerTrack is 2*NumTrack, so this isn't true any more - * [0] Trans0.Offset - * [1] Trans0.NumKeys - * [2] Rot0.Offset - * [3] Rot0.NumKeys - * [4] Trans1.Offset - * . . . - */ - TArray CompressedTrackOffsets; - - /** - * An array of 2*NumTrack ints, arranged as follows: - if identity, it is offset - if not, it is num of keys - * [0] Scale0.Offset or NumKeys - * [1] Scale1.Offset or NumKeys - - * @TODO NOTE: first implementation is offset is [0], numkeys [1] - * . . . - */ - FCompressedOffsetData CompressedScaleOffsets; - - /** - * ByteStream for compressed animation data. - * The memory layout is dependent on the algorithm used to compress the anim sequence. - */ -#if WITH_EDITOR - TArray CompressedByteStream; - FByteBulkData OptionalBulk; -#else - TMaybeMappedArray CompressedByteStream; -#endif - - - /** - * Array of segment descriptors for this compressed anim sequence. - */ - TArray CompressedSegments; - - TEnumAsByte KeyEncodingFormat; - - /** - * The runtime interface to decode and byte swap the compressed animation - * May be NULL. Set at runtime - does not exist in editor - */ - class AnimEncoding* TranslationCodec; - - class AnimEncoding* RotationCodec; - - class AnimEncoding* ScaleCodec; - - /* Compressed curve data stream used by AnimCurveCompressionCodec */ - TArray CompressedCurveByteStream; - - // The size of the raw data used to create the compressed data - int32 CompressedRawDataSize; + FCompressedAnimSequence CompressedData; // Accessors for animation frame count int32 GetRawNumberOfFrames() const { return NumFrames; } void SetRawNumberOfFrame(int32 InNumFrames) { NumFrames = InNumFrames; } - int32 GetCompressedNumberOfFrames() const { return CompressedNumFrames; } /** Additive animation type. **/ UPROPERTY(EditAnywhere, Category=AdditiveSettings, AssetRegistrySearchable) @@ -785,11 +338,6 @@ public: UPROPERTY(EditAnywhere, Category=AdditiveSettings) int32 RefFrameIndex; - // Versioning Support - /** The version of the global encoding package used at the time of import */ - UPROPERTY() - int32 EncodingPkgVersion; - /** Base pose to use when retargeting */ UPROPERTY(EditAnywhere, AssetRegistrySearchable, Category=Animation) FName RetargetSource; @@ -830,12 +378,6 @@ public: UPROPERTY(EditAnywhere, Category=Compression) uint32 bDoNotOverrideCompression:1; - /** - * Used to track whether, or not, this sequence was compressed with it's full translation tracks - */ - UPROPERTY() - uint32 bWasCompressedWithoutTranslations:1; - /** Importing data and options used for this mesh */ UPROPERTY(VisibleAnywhere, Instanced, Category=ImportSettings) class UAssetImportData* AssetImportData; @@ -949,10 +491,7 @@ public: #if WITH_EDITORONLY_DATA bool HasSourceRawData() const { return SourceRawAnimationData.Num() > 0; } const TArray& GetAnimationTrackNames() const { return AnimationTrackNames; } - const TArray& GetAdditiveBaseAnimationData() const { return TemporaryAdditiveBaseAnimationData; } - void UpdateCompressedTrackMapFromRaw() { CompressedTrackToSkeletonMapTable = TrackToSkeletonMapTable; } - void UpdateCompressedNumFramesFromRaw() { CompressedNumFrames = NumFrames; } - void UpdateCompressedCurveNames(); + void UpdateCompressedCurveName(SmartName::UID_Type CurveUID, const struct FSmartName& NewCurveName); // Adds a new track (if no track of the supplied name is found) to the raw animation data, optionally setting it to TrackData. @@ -960,8 +499,8 @@ public: #endif const TArray& GetRawTrackToSkeletonMapTable() const { return TrackToSkeletonMapTable; } - const TArray& GetCompressedTrackToSkeletonMapTable() const { return CompressedTrackToSkeletonMapTable; } - const TArray& GetCompressedCurveNames() const { return CompressedCurveNames; } + const TArray& GetCompressedTrackToSkeletonMapTable() const { return CompressedData.CompressedTrackToSkeletonMapTable; } + const TArray& GetCompressedCurveNames() const { return CompressedData.CompressedCurveNames; } FRawAnimSequenceTrack& GetRawAnimationTrack(int32 TrackIndex) { return RawAnimationData[TrackIndex]; } const FRawAnimSequenceTrack& GetRawAnimationTrack(int32 TrackIndex) const { return RawAnimationData[TrackIndex]; } @@ -1060,6 +599,11 @@ public: */ int32 GetApproxRawSize() const; + /** + * @return The approximate size of compressed animation data for only bones. + */ + int32 GetApproxBoneCompressedSize() const; + /** * @return The approximate size of compressed animation data. */ @@ -1068,12 +612,6 @@ public: // Initialize curve compression settings, does nothing if scheme already valid void InitCurveCompressionScheme(); - /** - * Utility function for lossless compression of a FRawAnimSequenceTrack - * @return true if keys were removed. - **/ - bool CompressRawAnimSequenceTrack(FRawAnimSequenceTrack& RawTrack, float MaxPosDiff, float MaxAngleDiff); - /** * Removes trivial frames -- frames of tracks when position or orientation is constant * over the entire animation -- from the raw animation data. If both position and rotation @@ -1119,7 +657,7 @@ public: */ int32 GetSkeletonIndexFromCompressedDataTrackIndex(const int32 TrackIndex) const { - return CompressedTrackToSkeletonMapTable[TrackIndex].BoneTreeIndex; + return GetCompressedTrackToSkeletonMapTable()[TrackIndex].BoneTreeIndex; } /** Clears any data in the AnimSequence */ @@ -1130,12 +668,6 @@ public: void CleanAnimSequenceForImport(); #endif - /** - * Utility function to copy all UAnimSequence properties from Source to Destination. - * Does not copy however RawAnimData, CompressedAnimData and AdditiveBasePose. - */ - static bool CopyAnimSequenceProperties(UAnimSequence* SourceAnimSeq, UAnimSequence* DestAnimSeq, bool bSkipCopyingNotifies=false); - /** * Copy AnimNotifies from one UAnimSequence to another. */ @@ -1274,13 +806,13 @@ public: bool CanBakeAdditive() const; // Bakes out track data for the skeletons virtual bones into the raw data - void BakeOutVirtualBoneTracks(); + void BakeOutVirtualBoneTracks(TArray& NewRawTracks, TArray& NewAnimationTrackNames, TArray& NewTrackToSkeletonMapTable); // Performs multiple evaluations of the animation as a test of compressed data validatity void TestEvalauteAnimation() const; // Bakes out the additive version of this animation into the raw data. - void BakeOutAdditiveIntoRawData(); + void BakeOutAdditiveIntoRawData(TArray& NewRawTracks, TArray& NewAnimationTrackNames, TArray& NewTrackToSkeletonMapTable, FRawCurveTracks& NewCurveTracks, TArray& AdditiveBaseAnimationData); // Test whether at any point we will scale a bone to 0 (needed for validating additive anims) bool DoesSequenceContainZeroScale(); @@ -1390,54 +922,4 @@ public: friend class UAnimationBlueprintLibrary; }; -struct FScopedAnimSequenceRawDataCache -{ - UAnimSequence* SrcAnim; - TArray RawAnimationData; - TArray TemporaryAdditiveBaseAnimationData; - TArray AnimationTrackNames; - TArray TrackToSkeletonMapTable; - FRawCurveTracks RawCurveData; - bool bWasEmpty; - int32 NumFrames; - - FScopedAnimSequenceRawDataCache() : SrcAnim(nullptr), bWasEmpty(false) {} - ~FScopedAnimSequenceRawDataCache() - { - if (SrcAnim) - { - RestoreTo(SrcAnim); - } - } - - void InitFrom(UAnimSequence* Src) - { -#if WITH_EDITORONLY_DATA - check(!SrcAnim); - SrcAnim = Src; - RawAnimationData = Src->RawAnimationData; - TemporaryAdditiveBaseAnimationData = Src->TemporaryAdditiveBaseAnimationData; - bWasEmpty = RawAnimationData.Num() == 0; - AnimationTrackNames = Src->AnimationTrackNames; - TrackToSkeletonMapTable = Src->TrackToSkeletonMapTable; - RawCurveData = Src->RawCurveData; - NumFrames = Src->NumFrames; -#endif - } - - void RestoreTo(UAnimSequence* Src) - { -#if WITH_EDITORONLY_DATA - Src->RawAnimationData = MoveTemp(RawAnimationData); - Src->TemporaryAdditiveBaseAnimationData = MoveTemp(TemporaryAdditiveBaseAnimationData); - check(bWasEmpty || Src->RawAnimationData.Num() > 0); - Src->AnimationTrackNames = MoveTemp(AnimationTrackNames); - Src->TrackToSkeletonMapTable = MoveTemp(TrackToSkeletonMapTable); - Src->RawCurveData = RawCurveData; - Src->NumFrames = NumFrames; -#endif - } - -}; - diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimationTypes.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimationTypes.h index 2d4873391141..efe727d49feb 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimationTypes.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimationTypes.h @@ -88,4 +88,4 @@ public: UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Names") TArray Names; -}; +}; \ No newline at end of file diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/Skeleton.h b/Engine/Source/Runtime/Engine/Classes/Animation/Skeleton.h index 0f35798ca4b4..12cf4a04dffd 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/Skeleton.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/Skeleton.h @@ -373,7 +373,7 @@ public: const TArray& GetExistingMarkerNames() const { return ExistingMarkerNames; } // Register a new sync marker name - void RegisterMarkerName(FName MarkerName) { ExistingMarkerNames.AddUnique(MarkerName); ExistingMarkerNames.Sort(); } + void RegisterMarkerName(FName MarkerName) { ExistingMarkerNames.AddUnique(MarkerName); ExistingMarkerNames.Sort(FNameLexicalLess()); } // Remove a sync marker name void RemoveMarkerName(FName MarkerName) { ExistingMarkerNames.Remove(MarkerName); } @@ -711,7 +711,7 @@ public: * * @return Index of Track of Animation Sequence */ - ENGINE_API int32 GetAnimationTrackIndex(const int32 InSkeletonBoneIndex, const UAnimSequence* InAnimSeq, const bool bUseRawData); + ENGINE_API int32 GetRawAnimationTrackIndex(const int32 InSkeletonBoneIndex, const UAnimSequence* InAnimSeq); /** * Get Bone Tree Index from Reference Bone Index diff --git a/Engine/Source/Runtime/Engine/Classes/Atmosphere/AtmosphericFogComponent.h b/Engine/Source/Runtime/Engine/Classes/Atmosphere/AtmosphericFogComponent.h index 8fc322d8f510..20bf87ae91ca 100644 --- a/Engine/Source/Runtime/Engine/Classes/Atmosphere/AtmosphericFogComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/Atmosphere/AtmosphericFogComponent.h @@ -358,6 +358,11 @@ public: private: #if WITH_EDITORONLY_DATA class FAtmospherePrecomputeDataHandler* PrecomputeDataHandler; + +public: + // Because FAtmospherePrecomputeDataHandler is a FTicableEditorObject and its destruction is not thread safe + virtual bool IsDestructionThreadSafe() const override { return false; } +private: #endif friend class FAtmosphericFogSceneInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Camera/PlayerCameraManager.h b/Engine/Source/Runtime/Engine/Classes/Camera/PlayerCameraManager.h index 90da82c8d8bc..a212843f462b 100644 --- a/Engine/Source/Runtime/Engine/Classes/Camera/PlayerCameraManager.h +++ b/Engine/Source/Runtime/Engine/Classes/Camera/PlayerCameraManager.h @@ -859,6 +859,10 @@ public: /** Returns first existing instance of the specified camera anim, or NULL if none exists. */ class UCameraAnimInst* FindInstanceOfCameraAnim(class UCameraAnim const* Anim) const; + /** Sets the bGameCameraCutThisFrame flag to true (indicating we did a camera cut this frame; useful for game code to call, e.g., when performing a teleport that should be seamless) */ + UFUNCTION(BlueprintCallable, Category = "Camera") + void SetGameCameraCutThisFrame() { bGameCameraCutThisFrame = true; } + protected: /** Gets specified temporary CameraActor ready to update the specified Anim. */ void InitTempCameraActor(class ACameraActor* CamActor, class UCameraAnimInst const* AnimInstToInitFor) const; diff --git a/Engine/Source/Runtime/Engine/Classes/Components/AudioComponent.h b/Engine/Source/Runtime/Engine/Classes/Components/AudioComponent.h index ee4ccf395d18..916e08b1c879 100644 --- a/Engine/Source/Runtime/Engine/Classes/Components/AudioComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/Components/AudioComponent.h @@ -8,6 +8,7 @@ #include "Sound/SoundAttenuation.h" #include "Sound/SoundWave.h" #include "UObject/ObjectMacros.h" +#include "Math/RandomStream.h" #include "AudioComponent.generated.h" @@ -616,6 +617,8 @@ private: void UpdateSpriteTexture(); #endif + FRandomStream RandomStream; + static uint64 AudioComponentIDCounter; static TMap AudioIDToComponentMap; static FCriticalSection AudioIDToComponentMapLock; diff --git a/Engine/Source/Runtime/Engine/Classes/Components/ForceFeedbackComponent.h b/Engine/Source/Runtime/Engine/Classes/Components/ForceFeedbackComponent.h index 59fda101df37..2c2c28486bb1 100644 --- a/Engine/Source/Runtime/Engine/Classes/Components/ForceFeedbackComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/Components/ForceFeedbackComponent.h @@ -34,6 +34,7 @@ public: private: virtual void AddReferencedObjects( FReferenceCollector& Collector ) override; + virtual FString GetReferencerName() const override; virtual UWorld* GetTickableGameObjectWorld() const override; virtual void Tick( float DeltaTime ) override; virtual bool IsTickable() const override; diff --git a/Engine/Source/Runtime/Engine/Classes/Components/LocalLightComponent.h b/Engine/Source/Runtime/Engine/Classes/Components/LocalLightComponent.h index 74910ee6dc99..4ce0af3fae1c 100644 --- a/Engine/Source/Runtime/Engine/Classes/Components/LocalLightComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/Components/LocalLightComponent.h @@ -23,7 +23,7 @@ class ENGINE_API ULocalLightComponent : public ULightComponent * The peak luminous intensity is measured in candelas, * while the luminous power is measured in lumens. */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Light, AdvancedDisplay, meta=(DisplayName="Intensity Units", EditCondition="bUseInverseSquaredFalloff")) + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Light, AdvancedDisplay, meta=(DisplayName="Intensity Units")) ELightUnits IntensityUnits; UPROPERTY() diff --git a/Engine/Source/Runtime/Engine/Classes/Components/MeshComponent.h b/Engine/Source/Runtime/Engine/Classes/Components/MeshComponent.h index 3adb9d559803..123c27f7dacb 100644 --- a/Engine/Source/Runtime/Engine/Classes/Components/MeshComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/Components/MeshComponent.h @@ -141,7 +141,7 @@ protected: float ScalarParameterDefaultValue = 0.f; }; - TSortedMap MaterialParameterCache; + TSortedMap MaterialParameterCache; UPROPERTY(EditAnywhere, AdvancedDisplay, BlueprintReadOnly, Category = MaterialParameters) bool bEnableMaterialParameterCaching; diff --git a/Engine/Source/Runtime/Engine/Classes/Components/PrimitiveComponent.h b/Engine/Source/Runtime/Engine/Classes/Components/PrimitiveComponent.h index 8cbe81e70fb8..5b038678f241 100644 --- a/Engine/Source/Runtime/Engine/Classes/Components/PrimitiveComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/Components/PrimitiveComponent.h @@ -773,10 +773,15 @@ protected: private: /** Convert a set of overlaps from a sweep to a subset that includes only those at the end location (filling in OverlapsAtEndLocation). */ - const TArray* ConvertSweptOverlapsToCurrentOverlaps(TArray& OverlapsAtEndLocation, const TArray& SweptOverlaps, int32 SweptOverlapsIndex, const FVector& EndLocation, const FQuat& EndRotationQuat); + template + bool ConvertSweptOverlapsToCurrentOverlaps(TArray& OutOverlapsAtEndLocation, const TOverlapArrayView& SweptOverlaps, int32 SweptOverlapsIndex, const FVector& EndLocation, const FQuat& EndRotationQuat); /** Convert a set of overlaps from a symmetric change in rotation to a subset that includes only those at the end location (filling in OverlapsAtEndLocation). */ - const TArray* ConvertRotationOverlapsToCurrentOverlaps(TArray& OverlapsAtEndLocation, const TArray& CurrentOverlaps); + template + bool ConvertRotationOverlapsToCurrentOverlaps(TArray& OutOverlapsAtEndLocation, const TOverlapArrayView& CurrentOverlaps); + + template + bool GetOverlapsWithActor_Template(const AActor* Actor, TArray& OutOverlaps) const; // FScopedMovementUpdate needs access to the above two functions. friend FScopedMovementUpdate; @@ -855,7 +860,7 @@ public: * Generally this should only be used if this component is the RootComponent of the owning actor and overlaps with other descendant components have been verified. * @return True if we can skip calling this in the future (i.e. no useful work is being done.) */ - virtual bool UpdateOverlapsImpl(TArray const* NewPendingOverlaps=nullptr, bool bDoNotifies=true, const TArray* OverlapsAtEndLocation=nullptr) override; + virtual bool UpdateOverlapsImpl(const TOverlapArrayView* NewPendingOverlaps=nullptr, bool bDoNotifies=true, const TOverlapArrayView* OverlapsAtEndLocation=nullptr) override; /** Update current physics volume for this component, if bShouldUpdatePhysicsVolume is true. Overridden to use the overlaps to find the physics volume. */ virtual void UpdatePhysicsVolume( bool bTriggerNotifiers ) override; diff --git a/Engine/Source/Runtime/Engine/Classes/Components/SceneComponent.h b/Engine/Source/Runtime/Engine/Classes/Components/SceneComponent.h index 8a66072eeecf..d103f9ed1cc1 100644 --- a/Engine/Source/Runtime/Engine/Classes/Components/SceneComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/Components/SceneComponent.h @@ -45,6 +45,9 @@ struct ENGINE_API FOverlapInfo // All added members of FOverlapInfo are PODs. template<> struct TIsPODType { enum { Value = TIsPODType::Value }; }; +typedef TArray> TInlineOverlapInfoArray; +typedef TArrayView TOverlapArrayView; + /** Detail mode for scene component rendering, corresponds with the integer value of UWorld::GetDetailMode() */ UENUM() enum EDetailMode @@ -146,11 +149,11 @@ public: FBoxSphereBounds Bounds; /** Location of the component relative to its parent */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing=OnRep_RelativeLocation, Category = Transform) + UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing=OnRep_Transform, Category = Transform) FVector RelativeLocation; /** Rotation of the component relative to its parent */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing=OnRep_RelativeRotation, Category=Transform) + UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing=OnRep_Transform, Category=Transform) FRotator RelativeRotation; /** @@ -202,6 +205,12 @@ private: UPROPERTY(Transient, Replicated) uint8 bShouldBeAttached : 1; + UPROPERTY(Transient, Replicated) + uint8 bShouldSnapLocationWhenAttached : 1; + + UPROPERTY(Transient, Replicated) + uint8 bShouldSnapRotationWhenAttached : 1; + /** * Whether or not the cached PhysicsVolume this component overlaps should be updated when the component is moved. * @see GetPhysicsVolume() @@ -247,8 +256,6 @@ protected: private: uint8 bNetUpdateTransform : 1; uint8 bNetUpdateAttachment : 1; - uint8 bNetHasReceivedRelativeLocation : 1; - uint8 bNetHasReceivedRelativeRotation : 1; public: /** Global flag to enable/disable overlap optimizations, settable with p.SkipUpdateOverlapsOptimEnabled cvar */ @@ -311,12 +318,6 @@ private: UFUNCTION() void OnRep_Transform(); - UFUNCTION() - void OnRep_RelativeLocation(); - - UFUNCTION() - void OnRep_RelativeRotation(); - UFUNCTION() void OnRep_AttachParent(); @@ -891,7 +892,7 @@ protected: bool CheckStaticMobilityAndWarn(const FText& ActionText) const; /** Internal helper for UpdateOverlaps */ - virtual bool UpdateOverlapsImpl(TArray const* PendingOverlaps = nullptr, bool bDoNotifies = true, const TArray* OverlapsAtEndLocation = nullptr); + virtual bool UpdateOverlapsImpl(const TOverlapArrayView* PendingOverlaps = nullptr, bool bDoNotifies = true, const TOverlapArrayView* OverlapsAtEndLocation = nullptr); private: void PropagateTransformUpdate(bool bTransformChanged, EUpdateTransformFlags UpdateTransformFlags = EUpdateTransformFlags::None, ETeleportType Teleport = ETeleportType::None); @@ -900,7 +901,7 @@ private: public: /** Queries world and updates overlap tracking state for this component */ - bool UpdateOverlaps(TArray const* PendingOverlaps = nullptr, bool bDoNotifies = true, const TArray* OverlapsAtEndLocation = nullptr); + bool UpdateOverlaps(const TOverlapArrayView* PendingOverlaps = nullptr, bool bDoNotifies = true, const TOverlapArrayView* OverlapsAtEndLocation = nullptr); /** * Tries to move the component by a movement vector (Delta) and sets rotation to NewRotation. @@ -1435,7 +1436,8 @@ class ENGINE_API FScopedMovementUpdate : private FNoncopyable { public: - typedef TArray> TBlockingHitArray; + typedef TArray> TScopedBlockingHitArray; + typedef TArray> TScopedOverlapInfoArray; FScopedMovementUpdate( USceneComponent* Component, EScopedUpdate::Type ScopeBehavior = EScopedUpdate::DeferredUpdates, bool bRequireOverlapsEventFlagToQueueOverlaps = true ); ~FScopedMovementUpdate(); @@ -1479,16 +1481,16 @@ public: bool RequiresOverlapsEventFlag() const; /** Returns the pending overlaps within this scope. */ - const TArray& GetPendingOverlaps() const; + const TScopedOverlapInfoArray& GetPendingOverlaps() const; /** Returns the list of pending blocking hits, which will be used for notifications once the move is committed. */ - const TBlockingHitArray& GetPendingBlockingHits() const; + const TScopedBlockingHitArray& GetPendingBlockingHits() const; //--------------------------------------------------------------------------------------------------------// // These methods are intended only to be used by SceneComponent and derived classes. /** Add overlaps to the queued overlaps array. This is intended for use only by SceneComponent and its derived classes whenever movement is performed. */ - void AppendOverlapsAfterMove(const TArray& NewPendingOverlaps, bool bSweep, bool bIncludesOverlapsAtEnd); + void AppendOverlapsAfterMove(const TOverlapArrayView& NewPendingOverlaps, bool bSweep, bool bIncludesOverlapsAtEnd); /** Keep current pending overlaps after a move but make note that there was movement (just a symmetric rotation). */ void KeepCurrentOverlapsAfterRotation(bool bSweep); @@ -1507,7 +1509,8 @@ public: protected: /** Fills in the list of overlaps at the end location (in EndOverlaps). Returns pointer to the list, or null if it can't be computed. */ - const TArray* GetOverlapsAtEnd(class UPrimitiveComponent& PrimComponent, TArray& EndOverlaps, bool bTransformChanged) const; + template + TOptional GetOverlapsAtEnd(class UPrimitiveComponent& PrimComponent, TArray& OutEndOverlaps, bool bTransformChanged) const; bool SetWorldLocationAndRotation(FVector NewLocation, const FQuat& NewQuat, bool bNoPhysics = false, ETeleportType Teleport = ETeleportType::None); @@ -1534,9 +1537,9 @@ protected: FRotator InitialRelativeRotation; FVector InitialRelativeScale; - int32 FinalOverlapCandidatesIndex; // If not INDEX_NONE, overlaps at this index and beyond in PendingOverlaps are at the final destination - TArray PendingOverlaps; // All overlaps encountered during the scope of moves. - TBlockingHitArray BlockingHits; // All blocking hits encountered during the scope of moves. + int32 FinalOverlapCandidatesIndex; // If not INDEX_NONE, overlaps at this index and beyond in PendingOverlaps are at the final destination + TScopedOverlapInfoArray PendingOverlaps; // All overlaps encountered during the scope of moves. + TScopedBlockingHitArray BlockingHits; // All blocking hits encountered during the scope of moves. uint8 bDeferUpdates:1; uint8 bHasMoved:1; @@ -1573,12 +1576,12 @@ FORCEINLINE bool FScopedMovementUpdate::RequiresOverlapsEventFlag() const return bRequireOverlapsEventFlag; } -FORCEINLINE const TArray& FScopedMovementUpdate::GetPendingOverlaps() const +FORCEINLINE const FScopedMovementUpdate::TScopedOverlapInfoArray& FScopedMovementUpdate::GetPendingOverlaps() const { return PendingOverlaps; } -FORCEINLINE const FScopedMovementUpdate::TBlockingHitArray& FScopedMovementUpdate::GetPendingBlockingHits() const +FORCEINLINE const FScopedMovementUpdate::TScopedBlockingHitArray& FScopedMovementUpdate::GetPendingBlockingHits() const { return BlockingHits; } @@ -1643,7 +1646,7 @@ FORCEINLINE_DEBUGGABLE void USceneComponent::BeginScopedMovementUpdate(class FSc ScopedMovementStack.Push(&ScopedUpdate); } -FORCEINLINE_DEBUGGABLE bool USceneComponent::UpdateOverlaps(TArray const* PendingOverlaps /* = nullptr */, bool bDoNotifies /* = true */, const TArray* OverlapsAtEndLocation /* = nullptr */) +FORCEINLINE_DEBUGGABLE bool USceneComponent::UpdateOverlaps(const TOverlapArrayView* PendingOverlaps /* = nullptr */, bool bDoNotifies /* = true */, const TOverlapArrayView* OverlapsAtEndLocation /* = nullptr */) { if (IsDeferringMovementUpdates()) { diff --git a/Engine/Source/Runtime/Engine/Classes/Components/SkeletalMeshComponent.h b/Engine/Source/Runtime/Engine/Classes/Components/SkeletalMeshComponent.h index 2cd827c0fa00..a311db90a5bd 100644 --- a/Engine/Source/Runtime/Engine/Classes/Components/SkeletalMeshComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/Components/SkeletalMeshComponent.h @@ -1371,7 +1371,7 @@ public: virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override; virtual bool IsAnySimulatingPhysics() const override; virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport = ETeleportType::None) override; - virtual bool UpdateOverlapsImpl(TArray const* PendingOverlaps=NULL, bool bDoNotifies=true, const TArray* OverlapsAtEndLocation=NULL) override; + virtual bool UpdateOverlapsImpl(const TOverlapArrayView* PendingOverlaps=NULL, bool bDoNotifies=true, const TOverlapArrayView* OverlapsAtEndLocation=NULL) override; //~ End USceneComponent Interface. //~ Begin UPrimitiveComponent Interface. diff --git a/Engine/Source/Runtime/Engine/Classes/Components/SkinnedMeshComponent.h b/Engine/Source/Runtime/Engine/Classes/Components/SkinnedMeshComponent.h index 65b2c585b53c..3106a583a534 100644 --- a/Engine/Source/Runtime/Engine/Classes/Components/SkinnedMeshComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/Components/SkinnedMeshComponent.h @@ -308,7 +308,7 @@ protected: /** * Mapping for socket overrides, key is the Source socket name and the value is the override socket name */ - TSortedMap SocketOverrideLookup; + TSortedMap SocketOverrideLookup; public: #if WITH_EDITORONLY_DATA @@ -784,7 +784,7 @@ public: virtual bool DoesSocketExist(FName InSocketName) const override; virtual bool HasAnySockets() const override; virtual void QuerySupportedSockets(TArray& OutSockets) const override; - virtual bool UpdateOverlapsImpl(TArray const* PendingOverlaps=NULL, bool bDoNotifies=true, const TArray* OverlapsAtEndLocation=NULL) override; + virtual bool UpdateOverlapsImpl(const TOverlapArrayView* PendingOverlaps=NULL, bool bDoNotifies=true, const TOverlapArrayView* OverlapsAtEndLocation=NULL) override; //~ End USceneComponent Interface //~ Begin UPrimitiveComponent Interface diff --git a/Engine/Source/Runtime/Engine/Classes/Components/SkyLightComponent.h b/Engine/Source/Runtime/Engine/Classes/Components/SkyLightComponent.h index 83eeabd0fc14..d237d1cb6e22 100644 --- a/Engine/Source/Runtime/Engine/Classes/Components/SkyLightComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/Components/SkyLightComponent.h @@ -178,6 +178,7 @@ class ENGINE_API USkyLightComponent : public ULightComponentBase #endif // WITH_EDITOR virtual void BeginDestroy() override; virtual bool IsReadyForFinishDestroy() override; + virtual bool IsDestructionThreadSafe() const override { return false; } //~ End UObject Interface virtual TStructOnScope GetComponentInstanceData() const override; diff --git a/Engine/Source/Runtime/Engine/Classes/Components/SplineComponent.h b/Engine/Source/Runtime/Engine/Classes/Components/SplineComponent.h index 24e27f3fd8df..c8f4b4d2797d 100644 --- a/Engine/Source/Runtime/Engine/Classes/Components/SplineComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/Components/SplineComponent.h @@ -37,6 +37,19 @@ namespace ESplineCoordinateSpace }; } +UCLASS(Abstract) +class ENGINE_API USplineMetadata : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + virtual void InsertPoint(float InputKey, int32 Index) PURE_VIRTUAL(USplineMetadata::InsertPoint, ); + virtual void AddPoint(float InputKey) PURE_VIRTUAL(USplineMetadata::AddPoint, ); + virtual void RemovePoint(int32 Index) PURE_VIRTUAL(USplineMetadata::RemovePoint, ); + virtual void DuplicatePoint(int32 Index) PURE_VIRTUAL(USplineMetadata::DuplicatePoint, ); + virtual void Reset(int32 NumPoints) PURE_VIRTUAL(USplineMetadata::Reset, ); +}; + USTRUCT() struct ENGINE_API FSplineCurves { @@ -58,6 +71,9 @@ struct ENGINE_API FSplineCurves UPROPERTY() FInterpCurveFloat ReparamTable; + UPROPERTY() + USplineMetadata* Metadata = nullptr; + bool operator==(const FSplineCurves& Other) const { return Position == Other.Position && Rotation == Other.Rotation && Scale == Other.Scale; @@ -170,6 +186,9 @@ UCLASS(ClassGroup=Utility, ShowCategories = (Mobility), HideCategories = (Physic class ENGINE_API USplineComponent : public UPrimitiveComponent { GENERATED_UCLASS_BODY() + + /** Child class can optionally provide their metadata object through this constructor */ + USplineComponent(const FObjectInitializer& ObjectInitializer, USplineMetadata* Metadata); UPROPERTY(EditAnywhere, Category=Points) FSplineCurves SplineCurves; @@ -294,6 +313,8 @@ public: const FInterpCurveQuat& GetSplinePointsRotation() const { return SplineCurves.Rotation; } FInterpCurveVector& GetSplinePointsScale() { return SplineCurves.Scale; } const FInterpCurveVector& GetSplinePointsScale() const { return SplineCurves.Scale; } + USplineMetadata* GetSplinePointsMetadata() { return SplineCurves.Metadata; } + const USplineMetadata* GetSplinePointsMetadata() const { return SplineCurves.Metadata; } void ApplyComponentInstanceData(struct FSplineInstanceData* ComponentInstanceData, const bool bPostUCS); @@ -494,6 +515,10 @@ public: UFUNCTION(BlueprintCallable, Category=Spline) float GetDistanceAlongSplineAtSplinePoint(int32 PointIndex) const; + /** Get a metadata property float value along the spline at spline point */ + UFUNCTION(BlueprintCallable, Category = Spline) + float GetFloatPropertyAtSplinePoint(int32 Index, FName PropertyName) const; + /** Returns total length along this spline */ UFUNCTION(BlueprintCallable, Category=Spline) float GetSplineLength() const; diff --git a/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphNode.h b/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphNode.h index a7313f1d4992..88cfe1850ab7 100644 --- a/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphNode.h +++ b/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphNode.h @@ -170,6 +170,36 @@ struct FGraphNodeContextMenuBuilder FGraphNodeContextMenuBuilder(const UEdGraph* InGraph, const UEdGraphNode* InNode, const UEdGraphPin* InPin, class FMenuBuilder* InMenuBuilder, bool bInDebuggingMode); }; +/** Deprecation types for node response. */ +enum class EEdGraphNodeDeprecationType +{ + /** The node type is deprecated. */ + NodeTypeIsDeprecated, + /** The node references a deprecated member or type (e.g. variable or function). */ + NodeHasDeprecatedReference +}; + +/** Deprecation response message types. */ +enum class EEdGraphNodeDeprecationMessageType +{ + /** No message. The Blueprint will compile successfully. */ + None, + /** Emit the message as a note at compile time. This will appear as a note on the node and in the compiler log. */ + Note, + /** Emit the message as a Blueprint compiler warning. This will appear as a warning on the node and in the compiler log. */ + Warning +}; + +/** Deprecation response data. */ +struct FEdGraphNodeDeprecationResponse +{ + /** Message type. */ + EEdGraphNodeDeprecationMessageType MessageType = EEdGraphNodeDeprecationMessageType::None; + + /** Message text to display on the node and/or emit to the compile log. */ + FText MessageText; +}; + UCLASS() class ENGINE_API UEdGraphNode : public UObject { @@ -245,24 +275,33 @@ private: /** Whether the node was created as part of an expansion step */ uint8 bIsIntermediateNode : 1; +#if WITH_EDITORONLY_DATA + /** Whether this node is unrelated to the selected nodes or not */ + uint8 bUnrelated : 1; + +#endif + public: + /** Flag to check for compile error/warning */ UPROPERTY() uint8 bHasCompilerMessage:1; /** Comment bubble pinned state */ + + +#if WITH_EDITORONLY_DATA UPROPERTY() - uint8 bCommentBubblePinned:1; + uint8 bCommentBubblePinned : 1; /** Comment bubble visibility */ UPROPERTY() - uint8 bCommentBubbleVisible:1; + uint8 bCommentBubbleVisible : 1; /** Make comment bubble visible */ UPROPERTY(Transient) - uint8 bCommentBubbleMakeVisible:1; + uint8 bCommentBubbleMakeVisible : 1; -#if WITH_EDITORONLY_DATA /** If true, this node can be renamed in the editor */ UPROPERTY() uint8 bCanRenameNode:1; @@ -325,6 +364,20 @@ public: return bUserSetEnabledState; } +#if WITH_EDITOR + /** Set this node unrelated or not. */ + FORCEINLINE void SetNodeUnrelated(bool bNodeUnrelated) + { + bUnrelated = bNodeUnrelated; + } + + /** Determines whether this node is unrelated to the selected nodes or not. */ + FORCEINLINE bool IsNodeUnrelated() const + { + return bUnrelated; + } +#endif + /** Determines whether or not the node will compile in development mode. */ virtual bool IsInDevelopmentMode() const; @@ -359,7 +412,7 @@ public: TWeakPtr DEPRECATED_NodeWidget; /** Get all pins this node owns */ - TArray GetAllPins() { return Pins; } + const TArray& GetAllPins() const { return Pins; } struct FNameParameterHelper { @@ -723,10 +776,18 @@ public: // Returns true if this node is deprecated virtual bool IsDeprecated() const; + // Returns true if this node references a deprecated type or member + virtual bool HasDeprecatedReference() const { return false; } + + // Returns the response to use when reporting a deprecation + virtual FEdGraphNodeDeprecationResponse GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const; + // Returns true if this node should produce a compiler warning on deprecation - virtual bool ShouldWarnOnDeprecation() const { return true; } + UE_DEPRECATED(4.23, "Use GetDeprecationResponse instead.") + virtual bool ShouldWarnOnDeprecation() const; // Returns the string to use when reporting the deprecation + UE_DEPRECATED(4.23, "Use GetDeprecationResponse instead.") virtual FString GetDeprecationMessage() const; // Returns the object that should be focused when double-clicking on this node @@ -835,7 +896,7 @@ public: protected: /** - * Finds the difference in properties of node instance + * Finds the difference in properties of node instance, for subobjects * * @param StructA The struct of the class we are looking at LHS * @param StructB The struct of the class we are looking at RHS @@ -846,6 +907,18 @@ protected: */ virtual void DiffProperties(UClass* StructA, UClass* StructB, UObject* DataA, UObject* DataB, FDiffResults& Results, FDiffSingleResult& Diff) const; + /** + * Finds the difference in properties of node instance, for arbitrary UStructs + * + * @param StructA The struct we are looking at LHS + * @param StructB The struct we are looking at RHS + * @param DataA The raw data we are comparing LHS + * @param DataB The raw data we are comparing RHS + * @param Results The Results where differences are stored + * @param Diff The single result with default parameters setup + */ + virtual void DiffProperties(UStruct* StructA, UStruct* StructB, uint8* DataA, uint8* DataB, FDiffResults& Results, FDiffSingleResult& Diff) const; + // Returns a human-friendly description of the property in the form "PropertyName: Value" virtual FString GetPropertyNameAndValueForDiff(const UProperty* Prop, const uint8* PropertyAddr) const; diff --git a/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphSchema.h b/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphSchema.h index 02d0f4db8bc2..c81ae7c3a0f4 100644 --- a/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphSchema.h +++ b/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphSchema.h @@ -418,7 +418,7 @@ private: // This object is a base class helper used when building a list of actions for some menu or palette -struct FGraphActionListBuilderBase +struct ENGINE_VTABLE FGraphActionListBuilderBase { public: /** A single entry in the list - can contain multiple actions */ @@ -545,7 +545,7 @@ public: }; /** Used to nest all added action under one root category */ -struct FCategorizedGraphActionListBuilder : public FGraphActionListBuilderBase +struct ENGINE_VTABLE FCategorizedGraphActionListBuilder : public FGraphActionListBuilderBase { public: ENGINE_API FCategorizedGraphActionListBuilder(FString Category = FString()); diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/Blueprint.h b/Engine/Source/Runtime/Engine/Classes/Engine/Blueprint.h index a4708d4d25c8..555aa0a79691 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/Blueprint.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/Blueprint.h @@ -21,6 +21,7 @@ class UActorComponent; class UEdGraph; class UInheritableComponentHandler; class FBlueprintActionDatabaseRegistrar; +struct FDiffResults; /** * Enumerates states a blueprint can be in. @@ -657,9 +658,11 @@ public: protected: /** Current object being debugged for this blueprint */ + UPROPERTY(transient, duplicatetransient) TWeakObjectPtr< class UObject > CurrentObjectBeingDebugged; /** Current world being debugged for this blueprint */ + UPROPERTY(transient, duplicatetransient) TWeakObjectPtr< class UWorld > CurrentWorldBeingDebugged; /** Delegate called when the debug object is set */ @@ -849,6 +852,16 @@ public: /** Returns Valid if this object has data validation rules set up for it and the data for this object is valid. Returns Invalid if it does not pass the rules. Returns NotValidated if no rules are set for this object. */ virtual EDataValidationResult IsDataValid(TArray& ValidationErrors) override; + /** + * Fills in a list of differences between this blueprint and another blueprint. + * Default blueprints are handled by SBlueprintDiff, this should be overridden for specific blueprint types. + * + * @param OtherBlueprint Other blueprint to compare this to, should be the same type + * @param Results List of diff results to fill in with type-specific differences + * @return True if these blueprints were checked for specific differences, false if they are not comparable + */ + virtual bool FindDiffs(const UBlueprint* OtherBlueprint, FDiffResults& Results) const; + #endif //#if WITH_EDITOR //~ Begin UObject Interface diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/BlueprintGeneratedClass.h b/Engine/Source/Runtime/Engine/Classes/Engine/BlueprintGeneratedClass.h index 86c630e4477f..e624b73812ef 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/BlueprintGeneratedClass.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/BlueprintGeneratedClass.h @@ -702,9 +702,9 @@ public: virtual void FlushCompilationQueueForLevel() override; virtual UObject* GetArchetypeForCDO() const override; #endif //WITH_EDITOR - virtual void SerializeDefaultObject(UObject* Object, FArchive& Ar) override; + virtual void SerializeDefaultObject(UObject* Object, FStructuredArchive::FSlot Slot) override; virtual void PostLoadDefaultObject(UObject* Object) override; - virtual bool IsFunctionImplementedInBlueprint(FName InFunctionName) const override; + virtual bool IsFunctionImplementedInScript(FName InFunctionName) const override; virtual uint8* GetPersistentUberGraphFrame(UObject* Obj, UFunction* FuncToCheck) const override; virtual void CreatePersistentUberGraphFrame(UObject* Obj, bool bCreateOnlyIfEmpty = false, bool bSkipSuperClass = false, UClass* OldClass = nullptr) const override; virtual void DestroyPersistentUberGraphFrame(UObject* Obj, bool bSkipSuperClass = false) const override; diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/CompositeDataTable.h b/Engine/Source/Runtime/Engine/Classes/Engine/CompositeDataTable.h index 44732095ce7b..906340ae8f50 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/CompositeDataTable.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/CompositeDataTable.h @@ -9,7 +9,7 @@ /** * Data table composed of a stack of other data tables. */ -UCLASS(MinimalAPI, BlueprintType) +UCLASS(MinimalAPI, BlueprintType, hideCategories=(ImportOptions,ImportSource)) class UCompositeDataTable : public UDataTable { @@ -67,9 +67,9 @@ protected: // if bClearParentTables is false then the row map will be cleared but the parent table array won't be changed void EmptyCompositeTable(bool bClearParentTables); - void UpdateCachedRowMap(); + void UpdateCachedRowMap(bool bWarnOnInvalidChildren = true); - void OnParentTablesUpdated(); + void OnParentTablesUpdated(EPropertyChangeType::Type ChangeType = EPropertyChangeType::Unspecified); // true if this asset is currently being loaded; false otherwise uint8 bIsLoading : 1; diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/CoreSettings.h b/Engine/Source/Runtime/Engine/Classes/Engine/CoreSettings.h index e830fef3f88d..1e31f4c114a9 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/CoreSettings.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/CoreSettings.h @@ -175,6 +175,11 @@ protected: ToolTip = "If true, the engine will destroy objects incrementally using time limit each frame.")) uint32 IncrementalBeginDestroyEnabled : 1; + UPROPERTY(EditAnywhere, config, Category = Optimization, meta = ( + ConsoleVariable = "gc.MultithreadedDestructionEnabled", DisplayName = "Multithreaded Destruction Enabled", + ToolTip = "If true, the engine will free objects' memory on a worker thread.")) + uint32 MultithreadedDestructionEnabled : 1; + UPROPERTY(EditAnywhere, config, Category = Optimization, meta = ( ConsoleVariable = "gc.CreateGCClusters", DisplayName = "Create Garbage Collector UObject Clusters", ToolTip = "If true, the engine will attempt to create clusters of objects for better garbage collection performance.")) diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/CurveTable.h b/Engine/Source/Runtime/Engine/Classes/Engine/CurveTable.h index b139b9622ddb..93eb58c1a67e 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/CurveTable.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/CurveTable.h @@ -36,7 +36,7 @@ enum class ECurveTableMode : uint8 * Imported spreadsheet table as curves. */ UCLASS(MinimalAPI) -class UCurveTable +class ENGINE_VTABLE UCurveTable : public UObject , public FCurveOwnerInterface { @@ -210,7 +210,7 @@ protected: */ static FName MakeValidName(const FString& InString); - static int32 GlobalCachedCurveID; + ENGINE_API static int32 GlobalCachedCurveID; private: diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/DataAsset.h b/Engine/Source/Runtime/Engine/Classes/Engine/DataAsset.h index ebe1d6bf3c44..5aa3cbe339f6 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/DataAsset.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/DataAsset.h @@ -36,7 +36,7 @@ private: * To override this behavior, override GetPrimaryAssetId in your native class */ UCLASS(abstract, MinimalAPI, Blueprintable) -class UPrimaryDataAsset : public UDataAsset +class ENGINE_VTABLE UPrimaryDataAsset : public UDataAsset { GENERATED_BODY() diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/DataTable.h b/Engine/Source/Runtime/Engine/Classes/Engine/DataTable.h index 311e401af33e..33e51c6217b9 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/DataTable.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/DataTable.h @@ -52,8 +52,8 @@ struct FTableRowBase /** * Imported spreadsheet table. */ -UCLASS(MinimalAPI, BlueprintType) -class UDataTable +UCLASS(MinimalAPI, BlueprintType, AutoExpandCategories = "DataTable,ImportOptions") +class ENGINE_VTABLE UDataTable : public UObject { GENERATED_UCLASS_BODY() @@ -66,8 +66,9 @@ class UDataTable friend FDataTableImporterJSON; /** Structure to use for each row of the table, must inherit from FTableRowBase */ - UPROPERTY() + UPROPERTY(VisibleAnywhere, Category=DataTable, meta=(DisplayThumbnail="false")) UScriptStruct* RowStruct; + protected: /** Map of name of row to row data structure. */ TMap RowMap; @@ -78,6 +79,7 @@ protected: /** Called to add rows to the data table */ ENGINE_API virtual void AddRowInternal(FName RowName, uint8* RowDataPtr); public: + virtual const TMap& GetRowMap() const { return RowMap; } virtual const TMap& GetRowMap() { return RowMap; } @@ -90,6 +92,18 @@ public: UPROPERTY(EditAnywhere, Category=DataTable) uint8 bStripFromClientBuilds : 1; + /** Set to true to ignore extra fields in the import data, if false it will warn about them */ + UPROPERTY(EditAnywhere, Category=ImportOptions) + uint8 bIgnoreExtraFields : 1; + + /** Set to true to ignore any fields that are expected but missing, if false it will warn about them */ + UPROPERTY(EditAnywhere, Category = ImportOptions) + uint8 bIgnoreMissingFields : 1; + + /** Explicit field in import data to use as key. If this is empty it uses Name for JSON and the first field found for CSV */ + UPROPERTY(EditAnywhere, Category=ImportOptions) + FString ImportKeyField; + #if WITH_EDITOR ENGINE_API virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; #endif // WITH_EDITOR @@ -109,7 +123,8 @@ public: ENGINE_API virtual void PostLoad() override; //~ End UObject Interface - UPROPERTY(VisibleAnywhere, Instanced, Category=ImportSettings) + /** The file this data table was imported from, may be empty */ + UPROPERTY(VisibleAnywhere, Instanced, Category=ImportSource) class UAssetImportData* AssetImportData; /** The filename imported to create this object. Relative to this object's package, BaseDir() or absolute */ @@ -127,10 +142,9 @@ protected: UPROPERTY(Transient) TSet TemporarilyReferencedObjects; - #endif // WITH_EDITORONLY_DATA -private: +private: /** A multicast delegate that is called any time the data table changes. */ FOnDataTableChanged OnDataTableChangedDelegate; @@ -146,11 +160,11 @@ public: { if (RowStruct == nullptr) { - UE_LOG(LogDataTable, Error, TEXT("UDataTable::FindRow : DataTable '%s' has no RowStruct specified (%s)."), *GetPathName(), ContextString); + UE_LOG(LogDataTable, Error, TEXT("UDataTable::GetAllRows : DataTable '%s' has no RowStruct specified (%s)."), *GetPathName(), ContextString); } else if (!RowStruct->IsChildOf(T::StaticStruct())) { - UE_LOG(LogDataTable, Error, TEXT("UDataTable::FindRow : Incorrect type specified for DataTable '%s' (%s)."), *GetPathName(), ContextString); + UE_LOG(LogDataTable, Error, TEXT("UDataTable::GetAllRows : Incorrect type specified for DataTable '%s' (%s)."), *GetPathName(), ContextString); } else { @@ -218,11 +232,11 @@ public: { if (RowStruct == nullptr) { - UE_LOG(LogDataTable, Error, TEXT("UDataTable::FindRow : DataTable '%s' has no RowStruct specified (%s)."), *GetPathName(), ContextString); + UE_LOG(LogDataTable, Error, TEXT("UDataTable::ForeachRow : DataTable '%s' has no RowStruct specified (%s)."), *GetPathName(), ContextString); } else if (!RowStruct->IsChildOf(T::StaticStruct())) { - UE_LOG(LogDataTable, Error, TEXT("UDataTable::FindRow : Incorrect type specified for DataTable '%s' (%s)."), *GetPathName(), ContextString); + UE_LOG(LogDataTable, Error, TEXT("UDataTable::ForeachRow : Incorrect type specified for DataTable '%s' (%s)."), *GetPathName(), ContextString); } else { @@ -247,13 +261,13 @@ public: { if(RowStruct == nullptr) { - //UE_CLOG(MustExist, LogDataTable, Error, TEXT("UDataTable::FindRow : DataTable '%s' has no RowStruct specified (%s)."), *GetPathName(), *ContextString); + //UE_CLOG(MustExist, LogDataTable, Error, TEXT("UDataTable::FindRowUnchecked : DataTable '%s' has no RowStruct specified (%s)."), *GetPathName(), *ContextString); return nullptr; } if(RowName == NAME_None) { - //UE_CLOG(MustExist, LogDataTable, Warning, TEXT("UDataTable::FindRow : NAME_None is invalid row name for DataTable '%s' (%s)."), *GetPathName(), *ContextString); + //UE_CLOG(MustExist, LogDataTable, Warning, TEXT("UDataTable::FindRowUnchecked : NAME_None is invalid row name for DataTable '%s' (%s)."), *GetPathName(), *ContextString); return nullptr; } @@ -302,6 +316,9 @@ public: /** Output the fields from a particular row (use RowMap to get RowData) to an existing JsonWriter */ ENGINE_API bool WriteRowAsJSON(const TSharedRef< TJsonWriter > >& JsonWriter, const void* RowData, const EDataTableExportFlags InDTExportFlags = EDataTableExportFlags::None) const; + + /** Copies all the import options from another table, this does not copy row dawta */ + ENGINE_API bool CopyImportOptions(UDataTable* SourceTable); #endif /** * Create table from CSV style comma-separated string. @@ -317,7 +334,8 @@ public: */ ENGINE_API TArray CreateTableFromJSONString(const FString& InString); - TArray GetTablePropertyArray(const TArray& Cells, UStruct* RowStruct, TArray& OutProblems); + /** Get array of UProperties that corresponds to columns in the table */ + TArray GetTablePropertyArray(const TArray& Cells, UStruct* RowStruct, TArray& OutProblems, int32 KeyColumn = 0); /** * Create table from another Data Table @@ -390,7 +408,7 @@ struct ENGINE_API FDataTableRowHandle { if (RowName != NAME_None) { - UE_LOG(LogDataTable, Warning, TEXT("FDataTableRowHandle::FindRow : No DataTable for row %s (%s)."), *RowName.ToString(), ContextString); + UE_LOG(LogDataTable, Warning, TEXT("FDataTableRowHandle::GetRow : No DataTable for row %s (%s)."), *RowName.ToString(), ContextString); } return nullptr; } @@ -461,7 +479,7 @@ struct ENGINE_API FDataTableCategoryHandle { if (RowContents != NAME_None) { - UE_LOG(LogDataTable, Warning, TEXT("FDataTableCategoryHandle::FindRow : No DataTable for row %s (%s)."), *RowContents.ToString(), *ContextString); + UE_LOG(LogDataTable, Warning, TEXT("FDataTableCategoryHandle::GetRows : No DataTable for row %s (%s)."), *RowContents.ToString(), *ContextString); } return; @@ -471,7 +489,7 @@ struct ENGINE_API FDataTableCategoryHandle { if (RowContents != NAME_None) { - UE_LOG(LogDataTable, Warning, TEXT("FDataTableCategoryHandle::FindRow : No Column selected for row %s (%s)."), *RowContents.ToString(), *ContextString); + UE_LOG(LogDataTable, Warning, TEXT("FDataTableCategoryHandle::GetRows : No Column selected for row %s (%s)."), *RowContents.ToString(), *ContextString); } return; diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/DebugCameraController.h b/Engine/Source/Runtime/Engine/Classes/Engine/DebugCameraController.h index d62ed84c3861..7cb6e81983be 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/DebugCameraController.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/DebugCameraController.h @@ -53,6 +53,10 @@ class ENGINE_API ADebugCameraController UPROPERTY() uint32 bIsBufferVisualizationInputSetup : 1; + /** Last display enabled setting before toggling buffer visualization overview */ + UPROPERTY() + uint32 bLastDisplayEnabled : 1; + /** Visualizes the frustum of the camera */ UPROPERTY() class UDrawFrustumComponent* DrawFrustum; @@ -83,6 +87,12 @@ class ENGINE_API ADebugCameraController UFUNCTION(BlueprintCallable, Category="Debug Camera") void ToggleDisplay(); + /** Sets display of debug info and input commands. */ + void SetDisplay(bool bEnabled); + + /** Returns whether debug info display is enabled */ + bool IsDisplayEnabled(); + /** * function called from key bindings command to save information about * turning on/off FreezeRendering command. @@ -150,6 +160,9 @@ class ENGINE_API ADebugCameraController #endif + /** Get selected visualization buffer */ + FString GetSelectedBufferName(); + public: /** Currently selected actor, may be invalid */ @@ -295,9 +308,9 @@ private: /** Last index in settings array for cycle view modes */ int32 LastViewModeSettingsIndex; - - /** Last buffer selected in buffer visualization overview */ - FString LastSelectedBuffer; + + /** Buffer selected in buffer visualization overview or full screen view */ + FString CurrSelectedBuffer; void OnTouchBegin(ETouchIndex::Type FingerIndex, FVector Location); void OnTouchEnd(ETouchIndex::Type FingerIndex, FVector Location); diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/DemoNetConnection.h b/Engine/Source/Runtime/Engine/Classes/Engine/DemoNetConnection.h index 369e8d8c1ff3..d0ade960dec1 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/DemoNetConnection.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/DemoNetConnection.h @@ -75,7 +75,7 @@ public: virtual int32 IsNetReady( bool Saturate ) override; virtual void FlushNet( bool bIgnoreSimulation = false ) override; virtual void HandleClientPlayer( APlayerController* PC, class UNetConnection* NetConnection ) override; - virtual TSharedPtr GetInternetAddr() override; + virtual TSharedPtr GetRemoteAddr() override { return nullptr; } virtual bool ClientHasInitializedLevelFor( const AActor* TestActor ) const override; virtual TSharedPtr CreateReplicatorForNewActorChannel(UObject* Object); virtual FString RemoteAddressToString() override { return TEXT("Demo"); } diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/DemoNetDriver.h b/Engine/Source/Runtime/Engine/Classes/Engine/DemoNetDriver.h index 2df1d9dc5c61..736cb9946b34 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/DemoNetDriver.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/DemoNetDriver.h @@ -355,6 +355,21 @@ struct ENGINE_API FDemoSavedRepObjectState typedef TArray FDemoSavedPropertyState; +USTRUCT() +struct FMulticastRecordOptions +{ + GENERATED_BODY() + + UPROPERTY() + FString FuncPathName; + + UPROPERTY() + bool bServerSkip; + + UPROPERTY() + bool bClientSkip; +}; + /** * Simulated network driver for recording and playing back game sessions. */ @@ -590,6 +605,10 @@ private: /** Called during a normal demoFrame*/ void TickDemoRecordFrame(float DeltaSeconds); + /** Config data for multicast RPCs we might want to skip recording. */ + UPROPERTY(config) + TArray MulticastRecordOptions; + public: // UNetDriver interface. diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/Engine.h b/Engine/Source/Runtime/Engine/Classes/Engine/Engine.h index 3fe8b3425e32..34ec65661797 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/Engine.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/Engine.h @@ -1949,6 +1949,7 @@ public: virtual void FinishDestroy() override; virtual void Serialize(FArchive& Ar) override; static void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector); + virtual bool IsDestructionThreadSafe() const override { return false; } //~ End UObject Interface. /** Initialize the game engine. */ diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/EngineBaseTypes.h b/Engine/Source/Runtime/Engine/Classes/Engine/EngineBaseTypes.h index f4fbe1b644e8..78566e79d671 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/EngineBaseTypes.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/EngineBaseTypes.h @@ -853,6 +853,11 @@ struct ENGINE_API FURL */ FString ToString( bool FullyQualified=0 ) const; + /** + * Prepares the Host and Port values into a standards compliant string + */ + FString GetHostPortString() const; + /** * Serializes a FURL to or from an archive. */ diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/FontFace.h b/Engine/Source/Runtime/Engine/Classes/Engine/FontFace.h index 28275cb89e8b..2f685ce8091a 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/FontFace.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/FontFace.h @@ -31,7 +31,10 @@ public: virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; virtual void PostEditUndo() override; virtual void GetAssetRegistryTags(TArray& OutTags) const override; - virtual void CookAdditionalFiles(const TCHAR* PackageFilename, const ITargetPlatform* TargetPlatform) override; +private: + virtual void CookAdditionalFilesOverride(const TCHAR* PackageFilename, const ITargetPlatform* TargetPlatform, + TFunctionRef WriteAdditionalFile); +public: #endif // WITH_EDITOR //~ End UObject interface diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/GameViewportClient.h b/Engine/Source/Runtime/Engine/Classes/Engine/GameViewportClient.h index 21799e4516a6..e060cad958b6 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/GameViewportClient.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/GameViewportClient.h @@ -138,6 +138,7 @@ public: //~ Begin UObject Interface virtual void PostInitProperties() override; virtual void BeginDestroy() override; + virtual bool IsDestructionThreadSafe() const override { return false; } //~ End UObject Interface //~ Begin FViewportClient Interface. diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/InheritableComponentHandler.h b/Engine/Source/Runtime/Engine/Classes/Engine/InheritableComponentHandler.h index 2349bda85f4b..8205160d18f6 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/InheritableComponentHandler.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/InheritableComponentHandler.h @@ -29,7 +29,7 @@ struct ENGINE_API FComponentKey FComponentKey(UBlueprint* Blueprint, const FUCSComponentId& UCSComponentID); #endif - bool Match(const FComponentKey OtherKey) const; + bool Match(const FComponentKey& OtherKey) const; bool IsSCSKey() const { @@ -105,19 +105,19 @@ private: public: - UActorComponent* CreateOverridenComponentTemplate(FComponentKey Key); - void RemoveOverridenComponentTemplate(FComponentKey Key); + UActorComponent* CreateOverridenComponentTemplate(const FComponentKey& Key); + void RemoveOverridenComponentTemplate(const FComponentKey& Key); void UpdateOwnerClass(UBlueprintGeneratedClass* OwnerClass); void ValidateTemplates(); bool IsValid() const; - UActorComponent* FindBestArchetype(FComponentKey Key) const; + UActorComponent* FindBestArchetype(const FComponentKey& Key) const; bool IsEmpty() const { return 0 == Records.Num(); } - bool RefreshTemplateName(FComponentKey OldKey); + bool RefreshTemplateName(const FComponentKey& OldKey); FComponentKey FindKey(UActorComponent* ComponentTemplate) const; #endif @@ -134,8 +134,8 @@ public: FComponentKey FindKey(const FName VariableName) const; - UActorComponent* GetOverridenComponentTemplate(FComponentKey Key) const; - const FBlueprintCookedComponentInstancingData* GetOverridenComponentTemplateData(FComponentKey Key) const; + UActorComponent* GetOverridenComponentTemplate(const FComponentKey& Key) const; + const FBlueprintCookedComponentInstancingData* GetOverridenComponentTemplateData(const FComponentKey& Key) const; TArray::TIterator CreateRecordIterator() { @@ -144,14 +144,15 @@ public: void GetAllTemplates(TArray& OutArray) const { - for (auto Record : Records) + OutArray.Reserve(OutArray.Num() + Records.Num()); + for (const FComponentOverrideRecord& Record : Records) { OutArray.Add(Record.ComponentTemplate); } } private: - const FComponentOverrideRecord* FindRecord(const FComponentKey Key) const; + const FComponentOverrideRecord* FindRecord(const FComponentKey& Key) const; /** Helper method used to assist with fixing up component template names at load time. */ void FixComponentTemplateName(UActorComponent* ComponentTemplate, const FString& NewName); diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/LevelBounds.h b/Engine/Source/Runtime/Engine/Classes/Engine/LevelBounds.h index 6663c8ab7742..441f276358c1 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/LevelBounds.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/LevelBounds.h @@ -37,6 +37,9 @@ class ALevelBounds //~ Begin UObject Interface virtual void PostLoad() override; +#if WITH_EDITOR // because of FEditorTickableLevelBounds + virtual bool IsDestructionThreadSafe() const override { return false; } +#endif //~ End UObject Interface //~ Begin AActor Interface. diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/LevelStreaming.h b/Engine/Source/Runtime/Engine/Classes/Engine/LevelStreaming.h index 02ca03c94037..3a2a113b5d7c 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/LevelStreaming.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/LevelStreaming.h @@ -15,7 +15,7 @@ class ULevel; class ULevelStreaming; // Stream Level Action -class FStreamLevelAction : public FPendingLatentAction +class ENGINE_API FStreamLevelAction : public FPendingLatentAction { public: bool bLoading; @@ -150,11 +150,14 @@ private: ETargetState TargetState; /** Whether this level streaming object's level should be unloaded and the object be removed from the level list. */ - uint8 bIsRequestingUnloadAndRemoval : 1; + uint8 bIsRequestingUnloadAndRemoval:1; /* Whether CachedWorldAssetPackageFName is valid */ mutable uint8 bHasCachedWorldAssetPackageFName:1; + /* Whether CachedLoadedLevelPackageName is valid */ + mutable uint8 bHasCachedLoadedLevelPackageName:1; + #if WITH_EDITORONLY_DATA /** Whether this level should be visible in the Editor */ UPROPERTY() @@ -476,6 +479,9 @@ private: /** @return Name of the LOD package on disk to load to the new package named PackageName, Name_None otherwise */ FName GetLODPackageNameToLoad() const; + /** @return Name of the level package that is currently loaded. */ + FName GetLoadedLevelPackageName() const; + /** * Try to find loaded level in memory, issue a loading request otherwise * @@ -517,7 +523,8 @@ private: /** The cached package name of the world asset that is loaded by the levelstreaming */ mutable FName CachedWorldAssetPackageFName; - FName CachedLoadedLevelPackageName; + /** The cached package name of the currently loaded level. */ + mutable FName CachedLoadedLevelPackageName; friend struct FStreamingLevelPrivateAccessor; }; diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/MapBuildDataRegistry.h b/Engine/Source/Runtime/Engine/Classes/Engine/MapBuildDataRegistry.h index 7c2f112aa6ee..17bdf0833721 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/MapBuildDataRegistry.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/MapBuildDataRegistry.h @@ -289,6 +289,10 @@ public: ENGINE_API virtual void BeginDestroy() override; ENGINE_API virtual bool IsReadyForFinishDestroy() override; ENGINE_API virtual void FinishDestroy() override; + ENGINE_API virtual bool IsDestructionThreadSafe() const + { + return false; + } //~ End UObject Interface /** diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/NetConnection.h b/Engine/Source/Runtime/Engine/Classes/Engine/NetConnection.h index 8977ec0c3900..61bf8d995ffb 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/NetConnection.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/NetConnection.h @@ -240,7 +240,7 @@ public: }; UCLASS(customConstructor, Abstract, MinimalAPI, transient, config=Engine) -class UNetConnection : public UPlayer +class ENGINE_VTABLE UNetConnection : public UPlayer { GENERATED_UCLASS_BODY() @@ -283,6 +283,9 @@ class UNetConnection : public UPlayer uint32 InternalAck:1; // Internally ack all packets, for 100% reliable connections. struct FURL URL; // URL of the other side. + + /** The remote address of this connection, typically generated from the URL. */ + TSharedPtr RemoteAddr; // Track each type of bit used per-packet for bandwidth profiling @@ -365,6 +368,7 @@ public: double LastTickTime; // Last time of polling. int32 QueuedBits; // Bits assumed to be queued up. int32 TickCount; // Count of ticks. + uint32 LastProcessedFrame; // The last frame where we gathered and processed actors for this connection /** The last time an ack was received */ float LastRecvAckTime; /** Time when connection request was first initiated */ @@ -754,14 +758,28 @@ public: ENGINE_API virtual void HandleClientPlayer( class APlayerController* PC, class UNetConnection* NetConnection ); /** @return the address of the connection as an integer */ + UE_DEPRECATED(4.23, "Use GetRemoteAddr as it allows direct access to the RemoteAddr and allows for dynamic address sizing.") virtual int32 GetAddrAsInt(void) { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + if (RemoteAddr.IsValid()) + { + uint32 OutAddr = 0; + // Get the host byte order ip addr + RemoteAddr->GetIp(OutAddr); + return (int32)OutAddr; + } + PRAGMA_ENABLE_DEPRECATION_WARNINGS return 0; } /** @return the port of the connection as an integer */ virtual int32 GetAddrPort(void) { + if (RemoteAddr.IsValid()) + { + return RemoteAddr->GetPort(); + } return 0; } @@ -771,7 +789,16 @@ public: * * @return The platform specific FInternetAddr containing this connections address */ - virtual TSharedPtr GetInternetAddr() PURE_VIRTUAL(UNetConnection::GetInternetAddr,return TSharedPtr();); + UE_DEPRECATED(4.23, "Use GetRemoteAddr to safely get the FInternetAddr tied to this connection") + virtual TSharedPtr GetInternetAddr() { return ConstCastSharedPtr(GetRemoteAddr()); } + + /** + * Return the platform specific FInternetAddr type, containing this connections address. + * If nullptr is returned, connection is not added to MappedClientConnections, and can't receive net packets which depend on this. + * + * @return The platform specific FInternetAddr containing this connections address + */ + virtual TSharedPtr GetRemoteAddr() { return RemoteAddr; } /** closes the connection (including sending a close notify across the network) */ ENGINE_API void Close(); @@ -886,7 +913,14 @@ public: * Gets a unique ID for the connection, this ID depends on the underlying connection * For IP connections this is an IP Address and port, for steam this is a SteamID */ - ENGINE_API virtual FString RemoteAddressToString() PURE_VIRTUAL(UNetConnection::RemoteAddressToString, return TEXT("Error");); + ENGINE_API virtual FString RemoteAddressToString() + { + if (RemoteAddr.IsValid()) + { + return RemoteAddr->ToString(true); + } + return TEXT("Invalid"); + } /** Called by UActorChannel. Handles creating a new replicator for an actor */ @@ -899,7 +933,8 @@ public: void PurgeAcks(); /** Send package map to the remote. */ - void SendPackageMap(); + UE_DEPRECATED(4.23, "This method will be removed.") + void SendPackageMap() {} /** * Appends the passed in data to the SendBuffer to be sent when FlushNet is called @@ -1297,7 +1332,6 @@ public: virtual FString LowLevelGetRemoteAddress(bool bAppendPort=false) override { return FString(); } virtual bool ClientHasInitializedLevelFor(const AActor* TestActor) const { return true; } - - virtual TSharedPtr GetInternetAddr() override { return TSharedPtr(); } + virtual TSharedPtr GetRemoteAddr() override { return nullptr; } }; diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/NetDriver.h b/Engine/Source/Runtime/Engine/Classes/Engine/NetDriver.h index ce8d6ddcc414..ee5732bf889f 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/NetDriver.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/NetDriver.h @@ -5,6 +5,7 @@ #pragma once #include "CoreMinimal.h" +#include "Math/RandomStream.h" #include "UObject/ObjectMacros.h" #include "UObject/UObjectGlobals.h" #include "UObject/Object.h" @@ -328,7 +329,7 @@ class UChannel; class IAnalyticsProvider; class FNetAnalyticsAggregator; -using FConnectionMap = TMap, UNetConnection*, FDefaultSetAllocator, FInternetAddrKeyMapFuncs>; +using FConnectionMap = TMap, UNetConnection*, FDefaultSetAllocator, FInternetAddrConstKeyMapFuncs>; extern ENGINE_API TAutoConsoleVariable CVarNetAllowEncryption; extern ENGINE_API int32 GNumSaturatedConnections; @@ -584,11 +585,18 @@ struct ENGINE_API FChannelDefinition struct FDisconnectedClient { /** The address of the client */ - TSharedRef Address; + TSharedRef Address; /** The time at which the client disconnected */ double DisconnectTime; + FDisconnectedClient(TSharedRef& InAddress, double InDisconnectTime) + : Address(InAddress) + , DisconnectTime(InDisconnectTime) + { + } + + UE_DEPRECATED(4.23, "This object does not update address information, and should have const addresses passed in") FDisconnectedClient(TSharedRef& InAddress, double InDisconnectTime) : Address(InAddress) , DisconnectTime(InDisconnectTime) @@ -598,7 +606,7 @@ struct FDisconnectedClient UCLASS(Abstract, customConstructor, transient, MinimalAPI, config=Engine) -class UNetDriver : public UObject, public FExec +class ENGINE_VTABLE UNetDriver : public UObject, public FExec { GENERATED_UCLASS_BODY() @@ -627,6 +635,10 @@ public: UPROPERTY(Config) int32 NetServerMaxTickRate; + /** Limit tick rate of replication to allow very high frame rates to still replicate data. A value less or equal to zero means use the engine tick rate. A value greater than zero will clamp the net tick rate to this value. */ + UPROPERTY(Config) + int32 MaxNetTickRate; + /** @todo document */ UPROPERTY(Config) int32 MaxInternetClientRate; @@ -1019,7 +1031,8 @@ public: /** DDoS detection management */ FDDoSDetection DDoS; - + /** Local address this net driver is associated with */ + TSharedPtr LocalAddr; /** * Updates the standby cheat information and @@ -1138,7 +1151,10 @@ public: ENGINE_API virtual void LowLevelDestroy(); /* @return network number */ - virtual FString LowLevelGetNetworkNumber() PURE_VIRTUAL(UNetDriver::LowLevelGetNetworkNumber,return TEXT("");); + ENGINE_API virtual FString LowLevelGetNetworkNumber(); + + /* @return local addr of this machine if set */ + ENGINE_API virtual TSharedPtr GetLocalAddr() { return LocalAddr; } /** Make sure this connection is in a reasonable state. */ ENGINE_API virtual void AssertValid(); @@ -1203,9 +1219,14 @@ public: ENGINE_API virtual void LowLevelSend(FString Address, void* Data, int32 CountBits) { FOutPacketTraits EmptyTraits; + PRAGMA_DISABLE_DEPRECATION_WARNINGS LowLevelSend(Address, Data, CountBits, EmptyTraits); + PRAGMA_ENABLE_DEPRECATION_WARNINGS } + UE_DEPRECATED(4.22, "Change arguments to support FInternetAddr instead") + ENGINE_API virtual void LowLevelSend(FString Address, void* Data, int32 CountBits, FOutPacketTraits& Traits); + /** * Sends a 'connectionless' (not associated with a UNetConection) packet, to the specified address. * NOTE: Address is an abstract format defined by subclasses. Anything calling this, must use an address supplied by the net driver. @@ -1215,7 +1236,7 @@ public: * @param CountBits The size of the packet data, in bits * @param Traits Traits for the packet, if applicable */ - ENGINE_API virtual void LowLevelSend(FString Address, void* Data, int32 CountBits, FOutPacketTraits& Traits) + ENGINE_API virtual void LowLevelSend(TSharedPtr Address, void* Data, int32 CountBits, FOutPacketTraits& Traits) PURE_VIRTUAL(UNetDriver::LowLevelSend,); /** @@ -1534,6 +1555,9 @@ protected: bool bMaySendProperties; + /** Stream of random numbers to be used by this instance of UNetDriver */ + FRandomStream UpdateDelayRandomStream; + private: FDelegateHandle PostGarbageCollectHandle; @@ -1564,4 +1588,4 @@ private: /** Count the number of notified packets, i.e. packets that we know if they are delivered or not. Used to reliably measure outgoing packet loss */ uint32 OutTotalNotifiedPackets; -}; \ No newline at end of file +}; diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/NetSerialization.h b/Engine/Source/Runtime/Engine/Classes/Engine/NetSerialization.h index 33faba9d5cc0..684389c04317 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/NetSerialization.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/NetSerialization.h @@ -963,19 +963,23 @@ void FFastArraySerializer::TFastArraySerializeHelper::Post // Look for implicit deletes that would happen due to Naks // --------------------------------------------------------- - for (int32 idx = 0; idx < Items.Num(); ++idx) + // If we're sending data completely reliably, there's no need to do this. + if (!Parms.bInternalAck) { - Type& Item = Items[idx]; - if (Item.MostRecentArrayReplicationKey < Header.ArrayReplicationKey && Item.MostRecentArrayReplicationKey > Header.BaseReplicationKey) + for (int32 idx = 0; idx < Items.Num(); ++idx) { - // Make sure this wasn't an explicit delete in this bunch (otherwise we end up deleting an extra element!) - if (!Header.DeletedIndices.Contains(idx)) + Type& Item = Items[idx]; + if (Item.MostRecentArrayReplicationKey < Header.ArrayReplicationKey && Item.MostRecentArrayReplicationKey > Header.BaseReplicationKey) { - // This will happen in normal conditions in network replays. - UE_LOG(LogNetFastTArray, Log, TEXT("Adding implicit delete for ElementID: %d. MostRecentArrayReplicationKey: %d. Current Payload: [%d/%d]"), - Item.ReplicationID, Item.MostRecentArrayReplicationKey, Header.ArrayReplicationKey, Header.BaseReplicationKey); + // Make sure this wasn't an explicit delete in this bunch (otherwise we end up deleting an extra element!) + if (!Header.DeletedIndices.Contains(idx)) + { + // This will happen in normal conditions in network replays. + UE_LOG(LogNetFastTArray, Log, TEXT("Adding implicit delete for ElementID: %d. MostRecentArrayReplicationKey: %d. Current Payload: [%d/%d]"), + Item.ReplicationID, Item.MostRecentArrayReplicationKey, Header.ArrayReplicationKey, Header.BaseReplicationKey); - Header.DeletedIndices.Add(idx); + Header.DeletedIndices.Add(idx); + } } } } diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/NetworkObjectList.h b/Engine/Source/Runtime/Engine/Classes/Engine/NetworkObjectList.h index 65b76b6e76c8..e909b0ba5377 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/NetworkObjectList.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/NetworkObjectList.h @@ -46,11 +46,15 @@ struct FNetworkObjectInfo uint8 bPendingNetUpdate : 1; /** Force this object to be considered relevant for at least one update */ + UE_DEPRECATED(4.23, "Use the ForceRelevantFrame variable since this variable is not set anymore.") uint8 bForceRelevantNextUpdate : 1; /** Should this object be considered for replay checkpoint writes */ uint8 bDirtyForReplay : 1; + /** Force this object to be considered relevant for at least one update */ + uint32 ForceRelevantFrame = 0; + FNetworkObjectInfo() : Actor(nullptr) , NextUpdateTime(0.0) diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/PointLight.h b/Engine/Source/Runtime/Engine/Classes/Engine/PointLight.h index e7b64ebc5edb..5a6bb74907bb 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/PointLight.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/PointLight.h @@ -12,7 +12,7 @@ class APointLight : public ALight { GENERATED_UCLASS_BODY() - UPROPERTY(BlueprintReadOnly, Category="Light", meta=(ExposeFunctionCategories="PointLight,Rendering|Lighting")) + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Light", meta=(ExposeFunctionCategories="PointLight,Rendering|Lighting")) class UPointLightComponent* PointLightComponent; // BEGIN DEPRECATED (use component functions now in level script) diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/RectLight.h b/Engine/Source/Runtime/Engine/Classes/Engine/RectLight.h index 765a5644b3ec..fe7a06e5d093 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/RectLight.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/RectLight.h @@ -12,7 +12,7 @@ class ARectLight : public ALight { GENERATED_UCLASS_BODY() - UPROPERTY(BlueprintReadOnly, Category="Light", meta=(ExposeFunctionCategories="RectLight,Rendering|Lighting")) + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Light", meta=(ExposeFunctionCategories="RectLight,Rendering|Lighting")) class URectLightComponent* RectLightComponent; #if WITH_EDITOR diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/Selection.h b/Engine/Source/Runtime/Engine/Classes/Engine/Selection.h index e503e8e546f7..6d392b524f81 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/Selection.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/Selection.h @@ -464,7 +464,7 @@ public: } /** Returns true if the iterator has not yet reached the end of the selection set. */ - operator bool() const + explicit operator bool() const { return IsIndexValid(); } diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/SpotLight.h b/Engine/Source/Runtime/Engine/Classes/Engine/SpotLight.h index fadcf93c9b05..aea83f368cdf 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/SpotLight.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/SpotLight.h @@ -12,7 +12,7 @@ class ASpotLight : public ALight { GENERATED_UCLASS_BODY() - UPROPERTY(BlueprintReadOnly, Category="Light", meta=(ExposeFunctionCategories="SpotLight,Rendering|Lighting")) + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Light", meta=(ExposeFunctionCategories="SpotLight,Rendering|Lighting")) class USpotLightComponent* SpotLightComponent; #if WITH_EDITORONLY_DATA diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/StaticMesh.h b/Engine/Source/Runtime/Engine/Classes/Engine/StaticMesh.h index 4342a322046f..ff70f2459209 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/StaticMesh.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/StaticMesh.h @@ -1124,6 +1124,12 @@ public: #endif // WITH_EDITORONLY_DATA } + /** + * Add a socket object in this StaticMesh. + */ + UFUNCTION(BlueprintCallable, Category = "StaticMesh") + ENGINE_API void AddSocket(UStaticMeshSocket* Socket); + /** * Find a socket object in this StaticMesh by name. * Entering NAME_None will return NULL. If there are multiple sockets with the same name, will return the first one. @@ -1131,6 +1137,12 @@ public: UFUNCTION(BlueprintCallable, Category = "StaticMesh") ENGINE_API class UStaticMeshSocket* FindSocket(FName InSocketName) const; + /** + * Remove a socket object in this StaticMesh by providing it's pointer. Use FindSocket() if needed. + */ + UFUNCTION(BlueprintCallable, Category = "StaticMesh") + ENGINE_API void RemoveSocket(UStaticMeshSocket* Socket); + /** * Returns vertex color data by position. * For matching to reimported meshes that may have changed or copying vertex paint data from mesh to mesh. diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/StreamableManager.h b/Engine/Source/Runtime/Engine/Classes/Engine/StreamableManager.h index 4686a61211da..c6305128147c 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/StreamableManager.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/StreamableManager.h @@ -313,27 +313,15 @@ struct ENGINE_API FStreamableManager : public FGCObject /** Checks for any redirectors that were previously loaded, and returns the redirected target if found. This will not handle redirects that it doesn't yet know about */ FSoftObjectPath ResolveRedirects(const FSoftObjectPath& Target) const; - UE_DEPRECATED(4.16, "Call LoadSynchronous with bManageActiveHandle=true instead if you want the manager to keep the handle alive") - UObject* SynchronousLoad(FSoftObjectPath const& Target); + /** Returns the debug name for this manager */ + const FString& GetManagerName() const; - template< typename T > - UE_DEPRECATED(4.16, "Call LoadSynchronous with bManageActiveHandle=true instead if you want the manager to keep the handle alive") - T* SynchronousLoadType(FSoftObjectPath const& Target) - { - PRAGMA_DISABLE_DEPRECATION_WARNINGS - return Cast< T >(SynchronousLoad(Target)); - PRAGMA_ENABLE_DEPRECATION_WARNINGS - } - - UE_DEPRECATED(4.16, "Call RequestAsyncLoad with bManageActiveHandle=true instead if you want the manager to keep the handle alive") - void SimpleAsyncLoad(const FSoftObjectPath& Target, TAsyncLoadPriority Priority = DefaultAsyncLoadPriority); - - UE_DEPRECATED(4.16, "AddStructReferencedObjects is no longer necessary, as it is a GCObject now") - void AddStructReferencedObjects(class FReferenceCollector& Collector) const {} + /** Modifies the debug name of this manager, used for debugging GC references */ + void SetManagerName(FString InName); /** Add referenced objects to stop them from GCing */ virtual void AddReferencedObjects(FReferenceCollector& Collector) override; - virtual FString GetReferencerName() const override { return TEXT("FStreamableManager"); } + virtual FString GetReferencerName() const override { return ManagerName; } virtual bool GetReferencerPropertyName(UObject* Object, FString& OutPropertyName) const override; FStreamableManager(); @@ -384,6 +372,9 @@ private: /** If True, temporarily force synchronous loading */ bool bForceSynchronousLoads; + + /** Debug name of this manager */ + FString ManagerName; }; diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/Texture.h b/Engine/Source/Runtime/Engine/Classes/Engine/Texture.h index 94b29eacb1c1..80ee47b1555d 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/Texture.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/Texture.h @@ -520,7 +520,7 @@ struct FTextureFormatSettings }; UCLASS(abstract, MinimalAPI, BlueprintType) -class UTexture : public UStreamableRenderAsset, public IInterface_AssetUserData +class ENGINE_VTABLE UTexture : public UStreamableRenderAsset, public IInterface_AssetUserData { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/TextureCube.h b/Engine/Source/Runtime/Engine/Classes/Engine/TextureCube.h index dbd32ac2c695..5415b201e56c 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/TextureCube.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/TextureCube.h @@ -11,7 +11,7 @@ class FTextureResource; UCLASS(hidecategories=Object, MinimalAPI) -class UTextureCube : public UTexture +class ENGINE_VTABLE UTextureCube : public UTexture { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/TimelineTemplate.h b/Engine/Source/Runtime/Engine/Classes/Engine/TimelineTemplate.h index 956c038738f4..4310e9c5534c 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/TimelineTemplate.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/TimelineTemplate.h @@ -13,7 +13,7 @@ class UTimelineTemplate; USTRUCT() -struct FTTTrackBase +struct ENGINE_VTABLE FTTTrackBase { GENERATED_USTRUCT_BODY() @@ -43,7 +43,7 @@ public: /** Structure storing information about one event track */ USTRUCT() -struct FTTEventTrack : public FTTTrackBase +struct ENGINE_VTABLE FTTEventTrack : public FTTTrackBase { GENERATED_USTRUCT_BODY() @@ -69,7 +69,7 @@ public: }; USTRUCT() -struct FTTPropertyTrack : public FTTTrackBase +struct ENGINE_VTABLE FTTPropertyTrack : public FTTTrackBase { GENERATED_USTRUCT_BODY() @@ -166,10 +166,6 @@ class UTimelineTemplate : public UObject UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=TimelineTemplate) uint8 bReplicated:1; - /** Compiler Validated As Wired up */ - UPROPERTY() - uint8 bValidatedAsWired:1; - /** If we want the timeline to ignore global time dilation */ UPROPERTY(EditAnywhere, Category = TimelineTemplate) uint8 bIgnoreTimeDilation : 1; @@ -257,7 +253,7 @@ class UTimelineTemplate : public UObject private: /** Helper function to make sure all the cached FNames for the timeline template are updated relative to the current name of the template. */ - void UpdateCachedNames(); + ENGINE_API void UpdateCachedNames(); friend struct FUpdateTimelineCachedNames; @@ -293,4 +289,4 @@ private: /** Grant external permission to Blueprint editor utility methods. For example, duplicating a Blueprint for conversion to C++ requires that cached names be updated. */ friend class FBlueprintEditorUtils; -}; \ No newline at end of file +}; diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/UserDefinedEnum.h b/Engine/Source/Runtime/Engine/Classes/Engine/UserDefinedEnum.h index c508d738255c..26d703611c5c 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/UserDefinedEnum.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/UserDefinedEnum.h @@ -62,6 +62,7 @@ public: /** Overridden to read DisplayNameMap*/ virtual FText GetDisplayNameTextByIndex(int32 InIndex) const override; + virtual FString GetAuthoredNameStringByIndex(int32 InIndex) const override; virtual bool SetEnums(TArray>& InNames, ECppForm InCppForm, bool bAddMaxKeyIfMissing = true) override; diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/UserDefinedStruct.h b/Engine/Source/Runtime/Engine/Classes/Engine/UserDefinedStruct.h index fed3d49927a6..b99b01b7891f 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/UserDefinedStruct.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/UserDefinedStruct.h @@ -88,10 +88,6 @@ public: virtual void PostLoad() override; // End of UObject interface. - // UScriptStruct interface. - virtual UProperty* CustomFindProperty(const FName Name) const override; - // End of UScriptStruct interface. - /** Creates a new guid if needed */ void ValidateGuid(); @@ -101,7 +97,7 @@ public: // UObject interface. virtual void Serialize(FStructuredArchive::FRecord Record) override; virtual void SerializeTaggedProperties(FStructuredArchive::FSlot Slot, uint8* Data, UStruct* DefaultsStruct, uint8* Defaults, const UObject* BreakRecursionIfFullyLoad = nullptr) const override; - virtual FString PropertyNameToDisplayName(FName InName) const override; + virtual FString GetAuthoredNameForField(const UField* Field) const override; // End of UObject interface. // UScriptStruct interface. @@ -110,6 +106,7 @@ public: virtual void RecursivelyPreload() override; virtual FGuid GetCustomGuid() const override; virtual FString GetStructCPPName() const override; + virtual UProperty* CustomFindProperty(const FName Name) const override; // End of UScriptStruct interface. /** Returns the raw memory of the default instance */ diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/World.h b/Engine/Source/Runtime/Engine/Classes/Engine/World.h index ac8e16be847e..7e21029f97c4 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/World.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/World.h @@ -483,7 +483,6 @@ struct TStructOpsTypeTraits : public TStructOpsTypeTrai }; /* Struct of optional parameters passed to SpawnActor function(s). */ -PRAGMA_DISABLE_DEPRECATION_WARNINGS // Required for auto-generated functions referencing bNoCollisionFail struct ENGINE_API FActorSpawnParameters { FActorSpawnParameters(); @@ -511,33 +510,32 @@ private: friend class UPackageMapClient; /* Is the actor remotely owned. This should only be set true by the package map when it is creating an actor on a client that was replicated from the server. */ - uint16 bRemoteOwned:1; + uint8 bRemoteOwned:1; public: bool IsRemoteOwned() const { return bRemoteOwned; } /* Determines whether spawning will not fail if certain conditions are not met. If true, spawning will not fail because the class being spawned is `bStatic=true` or because the class of the template Actor is not the same as the class of the Actor being spawned. */ - uint16 bNoFail:1; + uint8 bNoFail:1; /* Determines whether the construction script will be run. If true, the construction script will not be run on the spawned Actor. Only applicable if the Actor is being spawned from a Blueprint. */ - uint16 bDeferConstruction:1; + uint8 bDeferConstruction:1; /* Determines whether or not the actor may be spawned when running a construction script. If true spawning will fail if a construction script is being run. */ - uint16 bAllowDuringConstructionScript:1; + uint8 bAllowDuringConstructionScript:1; #if WITH_EDITOR /** Determines whether the begin play cycle will run on the spawned actor when in the editor. */ - uint16 bTemporaryEditorActor:1; + uint8 bTemporaryEditorActor:1; /* Determines wether or not the actor should be hidden from the Scene Outliner */ - uint16 bHideFromSceneOutliner : 1; + uint8 bHideFromSceneOutliner:1; #endif /* Flags used to describe the spawned actor/object instance. */ EObjectFlags ObjectFlags; }; -PRAGMA_ENABLE_DEPRECATION_WARNINGS /** @@ -2389,10 +2387,11 @@ public: /** * Cleans up components, streaming data and assorted other intermediate data. - * @param bSessionEnded whether to notify the viewport that the game session has ended + * @param bSessionEnded whether to notify the viewport that the game session has ended. * @param NewWorld Optional new world that will be loaded after this world is cleaned up. Specify a new world to prevent it and it's sublevels from being GCed during map transitions. + * @param bResetCleanedUpFlag wheter to reset the bCleanedUpWorld flag or not. */ - void CleanupWorld(bool bSessionEnded = true, bool bCleanupResources = true, UWorld* NewWorld = nullptr); + void CleanupWorld(bool bSessionEnded = true, bool bCleanupResources = true, UWorld* NewWorld = nullptr, bool bResetCleanedUpFlag = true); /** * Invalidates the cached data used to render the levels' UModel. diff --git a/Engine/Source/Runtime/Engine/Classes/Exporters/Exporter.h b/Engine/Source/Runtime/Engine/Classes/Exporters/Exporter.h index 1516c958b837..37cb1c7472f7 100644 --- a/Engine/Source/Runtime/Engine/Classes/Exporters/Exporter.h +++ b/Engine/Source/Runtime/Engine/Classes/Exporters/Exporter.h @@ -19,7 +19,7 @@ class UActorComponent; class UAssetExportTask; UCLASS(abstract, transient, MinimalAPI) -class UExporter : public UObject +class ENGINE_VTABLE UExporter : public UObject { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Runtime/Engine/Classes/GameFramework/Actor.h b/Engine/Source/Runtime/Engine/Classes/GameFramework/Actor.h index b5546e3d5a37..239ee9ded31b 100644 --- a/Engine/Source/Runtime/Engine/Classes/GameFramework/Actor.h +++ b/Engine/Source/Runtime/Engine/Classes/GameFramework/Actor.h @@ -1286,7 +1286,7 @@ public: /** The number of seconds (in game time) since this Actor was created, relative to Get Game Time In Seconds. */ UFUNCTION(BlueprintPure, Category=Actor) - float GetGameTimeSinceCreation(); + float GetGameTimeSinceCreation() const; protected: /** Event when play begins for this actor. */ diff --git a/Engine/Source/Runtime/Engine/Classes/GameFramework/AsyncActionHandleSaveGame.h b/Engine/Source/Runtime/Engine/Classes/GameFramework/AsyncActionHandleSaveGame.h new file mode 100644 index 000000000000..4098f41543a3 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Classes/GameFramework/AsyncActionHandleSaveGame.h @@ -0,0 +1,76 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Engine/StreamableManager.h" +#include "Kismet/BlueprintAsyncActionBase.h" +#include "Templates/SubclassOf.h" + +#include "AsyncActionHandleSaveGame.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnAsyncHandleSaveGame, USaveGame*, SaveGame, bool, bSuccess); + +/** Async action to handle async load/save of a USaveGame. This can be subclassed by a specific game */ +UCLASS() +class ENGINE_API UAsyncActionHandleSaveGame : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() +public: + + /** + * Schedule an async save to a specific slot. UGameplayStatics::AsyncSaveGameToSlot is the native version of this. + * When the save has succeeded or failed, the completed pin is activated with success/failure and the save game object. + * Keep in mind that some platforms may not support trying to load and save at the same time. + * + * @param SaveGameObject Object that contains data about the save game that we want to write out. + * @param SlotName Name of the save game slot to load from. + * @param UserIndex For some platforms, master user index to identify the user doing the loading. + */ + UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly="true", Category = "SaveGame", WorldContext = "WorldContextObject")) + static UAsyncActionHandleSaveGame* AsyncSaveGameToSlot(UObject* WorldContextObject, USaveGame* SaveGameObject, const FString& SlotName, const int32 UserIndex); + + /** + * Schedule an async load of a specific slot. UGameplayStatics::AsyncLoadGameFromSlot is the native version of this. + * When the load has succeeded or failed, the completed pin is activated with success/failure and the newly loaded save game object if valid. + * Keep in mind that some platforms may not support trying to load and save at the same time. + * + * @param SlotName Name of the save game slot to load from. + * @param UserIndex For some platforms, master user index to identify the user doing the loading. + */ + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", Category = "SaveGame", WorldContext = "WorldContextObject")) + static UAsyncActionHandleSaveGame* AsyncLoadGameFromSlot(UObject* WorldContextObject, const FString& SlotName, const int32 UserIndex); + + /** Delegate called when the save/load completes */ + UPROPERTY(BlueprintAssignable) + FOnAsyncHandleSaveGame Completed; + + /** Execute the actual operation */ + virtual void Activate() override; + +protected: + enum class ESaveGameOperation : uint8 + { + Save, + Load, + }; + + /** Which operation is being run */ + ESaveGameOperation Operation; + + /** Slot/user to use */ + FString SlotName; + int32 UserIndex; + + /** The object that was either saved or loaded */ + UPROPERTY() + USaveGame* SaveGameObject; + + /** Function callbacks for load/save */ + virtual void HandleAsyncSave(const FString& SlotName, const int32 UserIndex, bool bSuccess); + virtual void HandleAsyncLoad(const FString& SlotName, const int32 UserIndex, USaveGame* LoadedSave); + + /** Called at completion of save/load to execute delegate */ + virtual void ExecuteCompleted(bool bSuccess); +}; diff --git a/Engine/Source/Runtime/Engine/Classes/GameFramework/Character.h b/Engine/Source/Runtime/Engine/Classes/GameFramework/Character.h index 0167b528ae7a..8f8c45f65afa 100644 --- a/Engine/Source/Runtime/Engine/Classes/GameFramework/Character.h +++ b/Engine/Source/Runtime/Engine/Classes/GameFramework/Character.h @@ -732,7 +732,8 @@ public: virtual void UnCrouch(bool bClientSimulation = false); /** @return true if this character is currently able to crouch (and is not currently crouched) */ - virtual bool CanCrouch(); + UFUNCTION(BlueprintCallable, Category=Character) + virtual bool CanCrouch() const; /** * Called when Character stops crouching. Called on non-owned Characters through bIsCrouched replication. diff --git a/Engine/Source/Runtime/Engine/Classes/GameFramework/CharacterMovementComponent.h b/Engine/Source/Runtime/Engine/Classes/GameFramework/CharacterMovementComponent.h index 477c97b6bce9..3f2719f3a669 100644 --- a/Engine/Source/Runtime/Engine/Classes/GameFramework/CharacterMovementComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/GameFramework/CharacterMovementComponent.h @@ -3,6 +3,7 @@ #pragma once #include "CoreMinimal.h" +#include "Math/RandomStream.h" #include "UObject/ObjectMacros.h" #include "UObject/UObjectGlobals.h" #include "Engine/NetSerialization.h" @@ -2126,6 +2127,8 @@ protected: class FNetworkPredictionData_Client_Character* ClientPredictionData; class FNetworkPredictionData_Server_Character* ServerPredictionData; + FRandomStream RandomStream; + /** * Smooth mesh location for network interpolation, based on values set up by SmoothCorrection. * Internally this simply calls SmoothClientPosition_Interpolate() then SmoothClientPosition_UpdateVisuals(). diff --git a/Engine/Source/Runtime/Engine/Classes/GameFramework/Controller.h b/Engine/Source/Runtime/Engine/Classes/GameFramework/Controller.h index 75f35de9ee97..c5cb51006202 100644 --- a/Engine/Source/Runtime/Engine/Classes/GameFramework/Controller.h +++ b/Engine/Source/Runtime/Engine/Classes/GameFramework/Controller.h @@ -92,7 +92,7 @@ protected: * an Actor that follows the possessed Pawn location, but that still has the full aim rotation (since a Pawn might * update only some components of the rotation). */ - UPROPERTY(EditDefaultsOnly, AdvancedDisplay, Category="Controller|Transform") + UPROPERTY(EditDefaultsOnly, Category="Controller|Transform") uint8 bAttachToPawn:1; /** Whether this controller is a PlayerController. */ diff --git a/Engine/Source/Runtime/Engine/Classes/GameFramework/Pawn.h b/Engine/Source/Runtime/Engine/Classes/GameFramework/Pawn.h index f3e146debbb8..87a38188f836 100644 --- a/Engine/Source/Runtime/Engine/Classes/GameFramework/Pawn.h +++ b/Engine/Source/Runtime/Engine/Classes/GameFramework/Pawn.h @@ -93,7 +93,7 @@ public: /** * Determines when the Pawn creates and is possessed by an AI Controller (on level start, when spawned, etc). - * Only possible if AIControllerClass is set, and ignored if AutoPossessPlayer is enabled. + * Only possible if AIControllerClassRef is set, and ignored if AutoPossessPlayer is enabled. * @see AutoPossessPlayer */ UPROPERTY(EditAnywhere, Category=Pawn) diff --git a/Engine/Source/Runtime/Engine/Classes/GameFramework/PlayerInput.h b/Engine/Source/Runtime/Engine/Classes/GameFramework/PlayerInput.h index cfaf3edbb5a4..0dc86e1e9ec5 100644 --- a/Engine/Source/Runtime/Engine/Classes/GameFramework/PlayerInput.h +++ b/Engine/Source/Runtime/Engine/Classes/GameFramework/PlayerInput.h @@ -181,7 +181,7 @@ struct FInputActionKeyMapping bool operator<(const FInputActionKeyMapping& Other) const { bool bResult = false; - if (ActionName < Other.ActionName) + if (ActionName.LexicalLess(Other.ActionName)) { bResult = true; } @@ -234,7 +234,7 @@ struct FInputAxisKeyMapping bool operator<(const FInputAxisKeyMapping& Other) const { bool bResult = false; - if (AxisName < Other.AxisName) + if (AxisName.LexicalLess(Other.AxisName)) { bResult = true; } diff --git a/Engine/Source/Runtime/Engine/Classes/GameFramework/ProjectileMovementComponent.h b/Engine/Source/Runtime/Engine/Classes/GameFramework/ProjectileMovementComponent.h index 2f68690a98b2..b1b9c27e36d3 100644 --- a/Engine/Source/Runtime/Engine/Classes/GameFramework/ProjectileMovementComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/GameFramework/ProjectileMovementComponent.h @@ -98,7 +98,8 @@ class ENGINE_API UProjectileMovementComponent : public UMovementComponent /** * If true and there is an interpolated component set, location (and optionally rotation) interpolation is enabled which allows the interpolated object to smooth uneven updates - * of the UpdatedComponent's location (usually to smooth network updates). + * of the UpdatedComponent's location (usually to smooth network updates). This requires using SetInterpolatedComponent() to indicate the visual component that lags behind the collision, + * and using MoveInterpolationTarget() when the new target location/rotation is received (usually on a net update). * @see SetInterpolatedComponent(), MoveInterpolationTarget() */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ProjectileInterpolation) diff --git a/Engine/Source/Runtime/Engine/Classes/GameFramework/SpringArmComponent.h b/Engine/Source/Runtime/Engine/Classes/GameFramework/SpringArmComponent.h index 652aef88c080..2b52ccf173d8 100644 --- a/Engine/Source/Runtime/Engine/Classes/GameFramework/SpringArmComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/GameFramework/SpringArmComponent.h @@ -152,6 +152,10 @@ class ENGINE_API USpringArmComponent : public USceneComponent /** The name of the socket at the end of the spring arm (looking back towards the spring arm origin) */ static const FName SocketName; + + /** Returns the desired rotation for the spring arm, before the rotation constraints such as bInheritPitch etc are enforced. */ + virtual FRotator GetDesiredRotation() const; + protected: /** Cached component-space socket location */ FVector RelativeSocketLocation; diff --git a/Engine/Source/Runtime/Engine/Classes/GameFramework/WorldSettings.h b/Engine/Source/Runtime/Engine/Classes/GameFramework/WorldSettings.h index f42009e59aaa..54340c1a15eb 100644 --- a/Engine/Source/Runtime/Engine/Classes/GameFramework/WorldSettings.h +++ b/Engine/Source/Runtime/Engine/Classes/GameFramework/WorldSettings.h @@ -386,15 +386,15 @@ struct FBroadphaseSettings bool bUseMBPOuterBounds; /** Total bounds for MBP, must cover the game world or collisions are disabled for out of bounds actors */ - UPROPERTY(EditAnywhere, Category = Broadphase, meta = (EditCondition = bUseMBP)) + UPROPERTY(EditAnywhere, Category = Broadphase, meta = (EditCondition = "bUseMBPOnClient || bUseMBPOnServer")) FBox MBPBounds; /** Total bounds for MBP, should cover absolute maximum bounds of the game world where physics is required */ - UPROPERTY(EditAnywhere, Category = Broadphase, meta = (EditCondition = bUseMBP)) + UPROPERTY(EditAnywhere, Category = Broadphase, meta = (EditCondition = "bUseMBPOnClient || bUseMBPOnServer")) FBox MBPOuterBounds; /** Number of times to subdivide the MBP bounds, final number of regions is MBPNumSubdivs^2 */ - UPROPERTY(EditAnywhere, Category = Broadphase, meta = (EditCondition = bUseMBP, ClampMin=1, ClampMax=16)) + UPROPERTY(EditAnywhere, Category = Broadphase, meta = (EditCondition = "bUseMBPOnClient || bUseMBPOnServer", ClampMin=1, ClampMax=16)) uint32 MBPNumSubdivs; }; diff --git a/Engine/Source/Runtime/Engine/Classes/Kismet/GameplayStatics.h b/Engine/Source/Runtime/Engine/Classes/Kismet/GameplayStatics.h index 0a46b121e086..4e624e9094b0 100644 --- a/Engine/Source/Runtime/Engine/Classes/Kismet/GameplayStatics.h +++ b/Engine/Source/Runtime/Engine/Classes/Kismet/GameplayStatics.h @@ -36,6 +36,13 @@ class FMemoryReader; struct FDialogueContext; +/** Delegate called from AsyncLoadGameFromSlot. First two parameters are passed in SlotName and UserIndex, third parameter is the returned SaveGame, or null if it failed to load */ +DECLARE_DELEGATE_ThreeParams(FAsyncSaveGameToSlotDelegate, const FString&, const int32, bool); + +/** Delegate called from AsyncLoadGameFromSlot. First two parameters are passed in SlotName and UserIndex, third parameter is the returned SaveGame, or null if it failed to load */ +DECLARE_DELEGATE_ThreeParams(FAsyncLoadGameFromSlotDelegate, const FString&, const int32, USaveGame*); + +/** Static class with useful gameplay utility functions that can be called from both Blueprint and C++ */ UCLASS() class ENGINE_API UGameplayStatics : public UBlueprintFunctionLibrary { @@ -72,6 +79,15 @@ class ENGINE_API UGameplayStatics : public UBlueprintFunctionLibrary /** Bind the bounds of an array of Actors */ UFUNCTION(BlueprintCallable, Category="Collision") static void GetActorArrayBounds(const TArray& Actors, bool bOnlyCollidingComponents, FVector& Center, FVector& BoxExtent); + + /** + * Find the first Actor in the world of the specified class. + * This is a slow operation, use with caution e.g. do not use every frame. + * @param ActorClass Class of Actor to find. Must be specified or result will be empty. + * @return Actor of the specified class. + */ + UFUNCTION(BlueprintCallable, Category="Utilities", meta=(WorldContext="WorldContextObject", DeterminesOutputType="ActorClass")) + static class AActor* GetActorOfClass(const UObject* WorldContextObject, TSubclassOf ActorClass); /** * Find all Actors in the world of the specified class. @@ -255,6 +271,18 @@ class ENGINE_API UGameplayStatics : public UBlueprintFunctionLibrary UFUNCTION(BlueprintCallable, Category = "Rendering", meta = (WorldContext = "WorldContextObject")) static bool GetEnableWorldRendering(const UObject* WorldContextObject); + /** + * Returns the current viewport mouse capture mode + */ + UFUNCTION(BlueprintPure, Category = "Utilities", meta = (WorldContext = "WorldContextObject")) + static EMouseCaptureMode GetViewportMouseCaptureMode(const UObject* WorldContextObject); + + /** + * Sets the current viewport mouse capture mode + */ + UFUNCTION(BlueprintCallable, Category = "Utilities", meta = (WorldContext = "WorldContextObject")) + static void SetViewportMouseCaptureMode(const UObject* WorldContextObject, const EMouseCaptureMode MouseCaptureMode); + /** Hurt locally authoritative actors within the radius. Will only hit components that block the Visibility channel. * @param BaseDamage - The base damage to apply, i.e. the damage at the origin. * @param Origin - Epicenter of the damage area. @@ -805,87 +833,118 @@ public: // --- Save Game functions ------------------------------ /** - * Create a new, empty SaveGame object to set data on and then pass to SaveGameToSlot. - * @param SaveGameClass Class of SaveGame to create - * @return New SaveGame object to write data to + * Create a new, empty SaveGame object to set data on and then pass to SaveGameToSlot. + * @param SaveGameClass Class of SaveGame to create + * @return New SaveGame object to write data to */ - UFUNCTION(BlueprintCallable, Category="Game", meta=(DeterminesOutputType="SaveGameClass")) + UFUNCTION(BlueprintCallable, Category="SaveGame", meta=(DeterminesOutputType="SaveGameClass")) static USaveGame* CreateSaveGameObject(TSubclassOf SaveGameClass); /** - * Create a new, empty SaveGame object to set data on and then pass to SaveGameToSlot. - * @param SaveGameBlueprint Blueprint of SaveGame to create - * @return New SaveGame object to write data to + * Serialize our USaveGame object into a given array of bytes + * @note This will write out all non-transient properties, the SaveGame property flag is not checked + * + * @param SaveGameObject Object that contains data about the save game that we want to write out + * @param OutSaveData Byte array that is written into + * @return Whether we successfully wrote data */ - UFUNCTION(BlueprintCallable, Category="Game", meta=(DeprecatedFunction, DeprecationMessage="Use GameplayStatics.CreateSaveGameObject instead.")) - static USaveGame* CreateSaveGameObjectFromBlueprint(UBlueprint* SaveGameBlueprint); + static bool SaveGameToMemory(USaveGame* SaveGameObject, TArray& OutSaveData); /** - * Serialize our USaveGame object into a given array of bytes - * @param SaveGameObject Object that contains data about the save game that we want to write out - * @return Whether we successfully wrote data + * Save the contents of the buffer to a platform-specific save slot/file + * @param InSaveData Data to save + * @param SlotName Name of save game slot to save to. + * @param UserIndex For some platforms, master user index to identify the user doing the saving. + * @return Whether we successfully saved this information */ - static bool SaveGameToMemory(USaveGame * SaveGameObject, TArray& OutSaveData); + static bool SaveDataToSlot(const TArray& InSaveData, const FString& SlotName, const int32 UserIndex); /** - * Save the contents of the buffer to a slot/file - * @param InSaveData Data to save - * @param SlotName Name of save game slot to save to. - * @param UserIndex For some platforms, master user index to identify the user doing the saving. + * Schedule an async save to a specific slot. UAsyncActionHandleSaveGame::AsyncSaveGameToSlot is the blueprint version of this. + * This will do the serialize on the game thread, the platform-specific write on a worker thread, then call the complete delegate on the game thread. + * The passed in delegate will be copied to a worker thread so make sure any payload is thread safe to copy by value. + * + * @param SaveGameObject Object that contains data about the save game that we want to write out. + * @param SlotName Name of the save game slot to load from. + * @param UserIndex For some platforms, master user index to identify the user doing the loading. + * @param SavedDelegate Delegate that will be called on game thread when save succeeds or fails. */ - static bool SaveDataToSlot(const TArray & InSaveData, const FString & SlotName, const int32 UserIndex); + static void AsyncSaveGameToSlot(USaveGame* SaveGameObject, const FString& SlotName, const int32 UserIndex, FAsyncSaveGameToSlotDelegate SavedDelegate = FAsyncSaveGameToSlotDelegate()); /** - * Save the contents of the SaveGameObject to a slot. - * @param SaveGameObject Object that contains data about the save game that we want to write out - * @param SlotName Name of save game slot to save to. - * @param UserIndex For some platforms, master user index to identify the user doing the saving. - * @return Whether we successfully saved this information + * Save the contents of the SaveGameObject to a platform-specific save slot/file. + * @note This will write out all non-transient properties, the SaveGame property flag is not checked + * + * @param SaveGameObject Object that contains data about the save game that we want to write out + * @param SlotName Name of save game slot to save to. + * @param UserIndex For some platforms, master user index to identify the user doing the saving. + * @return Whether we successfully saved this information */ - UFUNCTION(BlueprintCallable, Category="Game") + UFUNCTION(BlueprintCallable, Category="SaveGame") static bool SaveGameToSlot(USaveGame* SaveGameObject, const FString& SlotName, const int32 UserIndex); /** - * See if a save game exists with the specified name. - * @param SlotName Name of save game slot. - * @param UserIndex For some platforms, master user index to identify the user doing the saving. + * See if a save game exists with the specified name. + * @param SlotName Name of save game slot. + * @param UserIndex For some platforms, master user index to identify the user doing the saving. */ - UFUNCTION(BlueprintCallable, Category="Game") + UFUNCTION(BlueprintCallable, Category="SaveGame") static bool DoesSaveGameExist(const FString& SlotName, const int32 UserIndex); /** - * Load the contents from a given array of bytes. - * @param InSaveData The array containing the serialized USaveGame data to load - * @return SaveGameObject Object containing loaded game state (NULL if load fails) + * Tries to load a SaveGame object from a given array of bytes. + * @param InSaveData The array containing the serialized USaveGame data to load + * @return Object containing loaded game state (nullptr if load fails) */ static USaveGame* LoadGameFromMemory(const TArray& InSaveData); - /** - * Load the contents from a given slot. - * @param SlotName Name of the save game slot to load from. - * @param UserIndex For some platforms, master user index to identify the user doing the loading. - * @return SaveGameObject Object containing loaded game state (NULL if load fails) + /** + * Load contents from a slot/file into a buffer of save data. + * @param OutSaveData Data buffer to load into + * @param SlotName Name of save game slot to save to. + * @param UserIndex For some platforms, master user index to identify the user doing the saving. + * @return Whether valid save data was found and loaded. */ - UFUNCTION(BlueprintCallable, Category="Game") + static bool LoadDataFromSlot(TArray& OutSaveData, const FString& SlotName, const int32 UserIndex); + + /** + * Schedule an async load of a specific slot. UAsyncActionHandleSaveGame::AsyncLoadGameFromSlot is the blueprint version of this. + * This will do the platform-specific read on a worker thread, the serialize and creation on the game thread, and then will call the passed in delegate + * The passed in delegate will be copied to a worker thread so make sure any payload is thread safe to copy by value + * + * @param SlotName Name of the save game slot to load from. + * @param UserIndex For some platforms, master user index to identify the user doing the loading. + * @param LoadedDelegate Delegate that will be called on game thread when load succeeds or fails. + */ + static void AsyncLoadGameFromSlot(const FString& SlotName, const int32 UserIndex, FAsyncLoadGameFromSlotDelegate LoadedDelegate); + + /** + * Load the contents from a given slot. + * @param SlotName Name of the save game slot to load from. + * @param UserIndex For some platforms, master user index to identify the user doing the loading. + * @return Object containing loaded game state (nullptr if load fails) + */ + UFUNCTION(BlueprintCallable, Category="SaveGame") static USaveGame* LoadGameFromSlot(const FString& SlotName, const int32 UserIndex); /** - * Takes the provided buffer and consumes it, parsing past the internal header data, returning a MemoryReader - * that has: 1) been set up with all the related header information, and 2) offset to where tagged USaveGame object - * serialization begins. NOTE: that the returned object has a reference to the supplied data - scope them - * accordingly. - * @param SaveData A byte array, presumably produced by one of the SaveGame functions here. - * @return A memory reader, wrapping SaveData, offset to the point past the header data. + * Takes the provided buffer and consumes it, parsing past the internal header data, returning a MemoryReader + * that has: 1) been set up with all the related header information, and 2) offset to where tagged USaveGame object + * serialization begins. NOTE: that the returned object has a reference to the supplied data - scope them + * accordingly. + * + * @param SaveData A byte array, presumably produced by one of the SaveGame functions here. + * @return A memory reader, wrapping SaveData, offset to the point past the header data. */ static FMemoryReader StripSaveGameHeader(const TArray& SaveData); /** * Delete a save game in a particular slot. - * @param SlotName Name of save game slot to delete. - * @param UserIndex For some platforms, master user index to identify the user doing the deletion. - * @return True if a file was actually able to be deleted. use DoesSaveGameExist to distinguish between delete failures and failure due to file not existing. + * @param SlotName Name of save game slot to delete. + * @param UserIndex For some platforms, master user index to identify the user doing the deletion. + * @return True if a file was actually able to be deleted. use DoesSaveGameExist to distinguish between delete failures and failure due to file not existing. */ - UFUNCTION(BlueprintCallable, Category = "Game") + UFUNCTION(BlueprintCallable, Category = "SaveGame") static bool DeleteGameInSlot(const FString& SlotName, const int32 UserIndex); /** Returns the frame delta time in seconds, adjusted by time dilation. */ @@ -908,6 +967,7 @@ public: UFUNCTION(BlueprintPure, Category="Utilities|Time", meta=(WorldContext="WorldContextObject")) static float GetAudioTimeSeconds(const UObject* WorldContextObject); + /** Returns time in seconds since the application was started. Unlike the other time functions this is accurate to the exact time this function is called instead of set once per frame. */ UFUNCTION(BlueprintPure, Category="Utilities|Time", meta=(WorldContext="WorldContextObject")) static void GetAccurateRealTime(const UObject* WorldContextObject, int32& Seconds, float& PartialSeconds); diff --git a/Engine/Source/Runtime/Engine/Classes/Kismet/KismetInputLibrary.h b/Engine/Source/Runtime/Engine/Classes/Kismet/KismetInputLibrary.h index 0553db858b10..c215d64542c9 100644 --- a/Engine/Source/Runtime/Engine/Classes/Kismet/KismetInputLibrary.h +++ b/Engine/Source/Runtime/Engine/Classes/Kismet/KismetInputLibrary.h @@ -90,6 +90,18 @@ class ENGINE_API UKismetInputLibrary : public UBlueprintFunctionLibrary UFUNCTION(BlueprintPure, meta = (DisplayName = "Is Valid"), Category = "Utilities|Key") static bool Key_IsValid(const FKey& Key); + /** Returns the navigation action corresponding to this key, or Invalid if not found */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Key Navigation Action"), Category = "Utilities|Key") + static EUINavigationAction Key_GetNavigationAction(const FKey& InKey); + + /** Returns the navigation action corresponding to this key, or Invalid if not found */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Key Event Navigation Direction"), Category = "Utilities|KeyEvent") + static EUINavigation Key_GetNavigationDirectionFromKey(const FKeyEvent& InKeyEvent); + + /** Returns the navigation action corresponding to this key, or Invalid if not found */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Analog Event Navigation Direction"), Category = "Utilities|AnalogEvent") + static EUINavigation Key_GetNavigationDirectionFromAnalog(const FAnalogInputEvent& InAnalogEvent); + /** * Returns the display name of the key. */ @@ -174,6 +186,9 @@ class ENGINE_API UKismetInputLibrary : public UBlueprintFunctionLibrary UFUNCTION(BlueprintPure, meta=( DisplayName = "Is Right Command Down" ), Category="Utilities|InputEvent") static bool InputEvent_IsRightCommandDown(const FInputEvent& Input); + /** @return The display name of the input chord */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Input Chord Display Name"), Category = "Utilities|Key") + static FText InputChord_GetDisplayName(const FInputChord& Key); /** * Returns the key for this event. diff --git a/Engine/Source/Runtime/Engine/Classes/Kismet/KismetMathLibrary.h b/Engine/Source/Runtime/Engine/Classes/Kismet/KismetMathLibrary.h index e839dac8c117..2bbbc85db2a3 100644 --- a/Engine/Source/Runtime/Engine/Classes/Kismet/KismetMathLibrary.h +++ b/Engine/Source/Runtime/Engine/Classes/Kismet/KismetMathLibrary.h @@ -590,7 +590,7 @@ class ENGINE_API UKismetMathLibrary : public UBlueprintFunctionLibrary /** Returns the inverse tan (atan2) of A/B (result is in Radians)*/ UFUNCTION(BlueprintPure, meta=(DisplayName = "Atan2 (Radians)"), Category="Math|Trig") - static float Atan2(float A, float B); + static float Atan2(float Y, float X); /** Returns exponential(e) to the power A (e^A)*/ UFUNCTION(BlueprintPure, Category="Math|Float", meta=(CompactNodeTitle = "e")) @@ -662,7 +662,7 @@ class ENGINE_API UKismetMathLibrary : public UBlueprintFunctionLibrary /** Returns the inverse tan (atan2) of A/B (result is in Degrees)*/ UFUNCTION(BlueprintPure, meta=(DisplayName = "Atan2 (Degrees)"), Category="Math|Trig") - static float DegAtan2(float A, float B); + static float DegAtan2(float Y, float X); /** * Clamps an arbitrary angle to be between the given angles. Will clamp to nearest boundary. diff --git a/Engine/Source/Runtime/Engine/Classes/Kismet/KismetMathLibrary.inl b/Engine/Source/Runtime/Engine/Classes/Kismet/KismetMathLibrary.inl index 95f27b7c1ac4..c0c6cad265f8 100644 --- a/Engine/Source/Runtime/Engine/Classes/Kismet/KismetMathLibrary.inl +++ b/Engine/Source/Runtime/Engine/Classes/Kismet/KismetMathLibrary.inl @@ -590,9 +590,9 @@ float UKismetMathLibrary::Atan(float A) } KISMET_MATH_FORCEINLINE -float UKismetMathLibrary::Atan2(float A, float B) +float UKismetMathLibrary::Atan2(float Y, float X) { - return FMath::Atan2(A, B); + return FMath::Atan2(Y, X); } KISMET_MATH_FORCEINLINE @@ -632,9 +632,9 @@ float UKismetMathLibrary::DegAtan(float A) } KISMET_MATH_FORCEINLINE -float UKismetMathLibrary::DegAtan2(float A, float B) +float UKismetMathLibrary::DegAtan2(float Y, float X) { - return (180.f)/PI * FMath::Atan2(A, B); + return (180.f)/PI * FMath::Atan2(Y, X); } KISMET_MATH_FORCEINLINE diff --git a/Engine/Source/Runtime/Engine/Classes/Kismet/KismetSystemLibrary.h b/Engine/Source/Runtime/Engine/Classes/Kismet/KismetSystemLibrary.h index 548bcf8231b5..96a55cec7ff7 100644 --- a/Engine/Source/Runtime/Engine/Classes/Kismet/KismetSystemLibrary.h +++ b/Engine/Source/Runtime/Engine/Classes/Kismet/KismetSystemLibrary.h @@ -208,6 +208,7 @@ class ENGINE_API UKismetSystemLibrary : public UBlueprintFunctionLibrary UFUNCTION(BlueprintPure, Category = "SoftObjectPath", meta = (NativeBreakFunc, BlueprintThreadSafe)) static void BreakSoftObjectPath(FSoftObjectPath InSoftObjectPath, FString& PathString); + /** Converts a Soft Object Path into a base Soft Object Reference, this is not guaranteed to be resolvable */ UFUNCTION(BlueprintPure, meta = (DisplayName = "ToSoftObjectReference (SoftObjectPath)", CompactNodeTitle = "->"), Category = "Utilities") static TSoftObjectPtr Conv_SoftObjPathToSoftObjRef(const FSoftObjectPath& SoftObjectPath); @@ -219,6 +220,10 @@ class ENGINE_API UKismetSystemLibrary : public UBlueprintFunctionLibrary UFUNCTION(BlueprintPure, Category = "SoftClassPath", meta = (NativeBreakFunc, BlueprintThreadSafe)) static void BreakSoftClassPath(FSoftClassPath InSoftClassPath, FString& PathString); + /** Converts a Soft Class Path into a base Soft Class Reference, this is not guaranteed to be resolvable */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToSoftClassReference (SoftClassPath)", CompactNodeTitle = "->"), Category = "Utilities") + static TSoftClassPtr Conv_SoftClassPathToSoftClassRef(const FSoftClassPath& SoftClassPath); + /** Returns true if the Soft Object Reference is not null */ UFUNCTION(BlueprintPure, Category = "Utilities", meta = (BlueprintThreadSafe)) static bool IsValidSoftObjectReference(const TSoftObjectPtr& SoftObjectReference); @@ -235,6 +240,10 @@ class ENGINE_API UKismetSystemLibrary : public UBlueprintFunctionLibrary UFUNCTION(BlueprintPure, meta = (DisplayName = "NotEqual (SoftObjectReference)", CompactNodeTitle = "!=", BlueprintThreadSafe), Category = "Utilities") static bool NotEqual_SoftObjectReference(const TSoftObjectPtr& A, const TSoftObjectPtr& B); + /** Resolves or loads a Soft Object Reference immediately, this will cause hitches and Async Load Asset should be used if possible */ + UFUNCTION(BlueprintCallable, Category = "Utilities", meta = (DeterminesOutputType = "Asset")) + static UObject* LoadAsset_Blocking(TSoftObjectPtr Asset); + /** Returns true if the Soft Class Reference is not null */ UFUNCTION(BlueprintPure, Category = "Utilities", meta = (BlueprintThreadSafe)) static bool IsValidSoftClassReference(const TSoftClassPtr& SoftClassReference); @@ -251,6 +260,12 @@ class ENGINE_API UKismetSystemLibrary : public UBlueprintFunctionLibrary UFUNCTION(BlueprintPure, meta = (DisplayName = "NotEqual (SoftClassReference)", CompactNodeTitle = "!=", BlueprintThreadSafe), Category = "Utilities") static bool NotEqual_SoftClassReference(const TSoftClassPtr& A, const TSoftClassPtr& B); + /** Resolves or loads a Soft Class Reference immediately, this will cause hitches and Async Load Class Asset should be used if possible */ + UFUNCTION(BlueprintCallable, Category = "Utilities", meta = (DeterminesOutputType = "AssetClass")) + static UClass* LoadClassAsset_Blocking(TSoftClassPtr AssetClass); + + // Internal functions used by K2Node_LoadAsset and K2Node_ConvertAsset + UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Utilities") static UObject* Conv_SoftObjectReferenceToObject(const TSoftObjectPtr& SoftObject); @@ -268,9 +283,6 @@ class ENGINE_API UKismetSystemLibrary : public UBlueprintFunctionLibrary UFUNCTION(BlueprintCallable, meta = (Latent, LatentInfo = "LatentInfo", WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"), Category = "Utilities") static void LoadAsset(UObject* WorldContextObject, TSoftObjectPtr Asset, FOnAssetLoaded OnLoaded, FLatentActionInfo LatentInfo); - UFUNCTION(BlueprintCallable, Category="Utilities", meta=(DeterminesOutputType = "Asset")) - static UObject* LoadAsset_Blocking(TSoftObjectPtr Asset); - DECLARE_DYNAMIC_DELEGATE_OneParam(FOnAssetClassLoaded, TSubclassOf, Loaded); UFUNCTION(BlueprintCallable, meta = (Latent, LatentInfo = "LatentInfo", WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"), Category = "Utilities") diff --git a/Engine/Source/Runtime/Engine/Classes/Materials/Material.h b/Engine/Source/Runtime/Engine/Classes/Materials/Material.h index 18ff2de027a8..9b6f550ef2ed 100644 --- a/Engine/Source/Runtime/Engine/Classes/Materials/Material.h +++ b/Engine/Source/Runtime/Engine/Classes/Materials/Material.h @@ -335,7 +335,7 @@ struct FParameterGroupData * Warning: Creating new materials directly increases shader compile times! Consider creating a Material Instance off of an existing material instead. */ UCLASS(hidecategories=Object, MinimalAPI, BlueprintType) -class UMaterial : public UMaterialInterface +class ENGINE_VTABLE UMaterial : public UMaterialInterface { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInstance.h b/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInstance.h index 720668811ece..f82f362402a8 100644 --- a/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInstance.h +++ b/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInstance.h @@ -252,7 +252,7 @@ bool CompareValueArraysByExpressionGUID(const TArray& InA, const TArray& I UCLASS(abstract, BlueprintType,MinimalAPI) -class UMaterialInstance : public UMaterialInterface +class ENGINE_VTABLE UMaterialInstance : public UMaterialInterface { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInstanceConstant.h b/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInstanceConstant.h index a8fb97f4fcf9..e1f9e9b1373e 100644 --- a/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInstanceConstant.h +++ b/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInstanceConstant.h @@ -15,7 +15,7 @@ * predefined material parameters. The parameters are statically defined in the compiled material by a unique name, type and default value. */ UCLASS(hidecategories=Object, collapsecategories, BlueprintType,MinimalAPI) -class UMaterialInstanceConstant : public UMaterialInstance +class ENGINE_VTABLE UMaterialInstanceConstant : public UMaterialInstance { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInterface.h b/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInterface.h index 94e65edad2dc..1c0f1e200fa7 100644 --- a/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInterface.h +++ b/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInterface.h @@ -210,7 +210,7 @@ struct FMaterialTextureInfo }; UCLASS(abstract, BlueprintType, MinimalAPI, HideCategories = (Thumbnail)) -class UMaterialInterface : public UObject, public IBlendableInterface, public IInterface_AssetUserData +class ENGINE_VTABLE UMaterialInterface : public UObject, public IBlendableInterface, public IInterface_AssetUserData { GENERATED_UCLASS_BODY() @@ -285,7 +285,7 @@ private: private: /** Feature level bitfield to compile for all materials */ - static uint32 FeatureLevelsForAllMaterials; + ENGINE_API static uint32 FeatureLevelsForAllMaterials; public: /** Set which feature levels this material instance should compile. GMaxRHIFeatureLevel is always compiled! */ ENGINE_API void SetFeatureLevelToCompile(ERHIFeatureLevel::Type FeatureLevel, bool bShouldCompile); diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Color/ParticleModuleColor_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Color/ParticleModuleColor_Seeded.h index df63b4ca5624..df55e90570ba 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Color/ParticleModuleColor_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Color/ParticleModuleColor_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleColor_Seeded : public UParticleModuleColor //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Lifetime/ParticleModuleLifetime_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Lifetime/ParticleModuleLifetime_Seeded.h index 156863fd868d..97e87601ee9b 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Lifetime/ParticleModuleLifetime_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Lifetime/ParticleModuleLifetime_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleLifetime_Seeded : public UParticleModuleLifetime //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Light/ParticleModuleLight_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Light/ParticleModuleLight_Seeded.h index 10846ca7ba5f..6147091cfd1f 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Light/ParticleModuleLight_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Light/ParticleModuleLight_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleLight_Seeded : public UParticleModuleLight //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationBoneSocket.h b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationBoneSocket.h index fdb8e4a90eb8..37df1b651b48 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationBoneSocket.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationBoneSocket.h @@ -179,8 +179,8 @@ class ENGINE_API UParticleModuleLocationBoneSocket : public UParticleModuleLocat bool GetBoneInfoForSourceIndex(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent, int32 SourceIndex, FMatrix& OutBoneMatrix, FVector& OutOffset)const; /** Selects the next socket or bone index to spawn from. */ - int32 SelectNextSpawnIndex(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent); - void RegeneratePreSelectedIndices(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent); + int32 SelectNextSpawnIndex(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent, FRandomStream& InRandomStream); + void RegeneratePreSelectedIndices(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent, FRandomStream& InRandomStream); void UpdatePrevBoneLocationsAndVelocities(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent, float DeltaTime); diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationPrimitiveCylinder_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationPrimitiveCylinder_Seeded.h index 95bf4fde0e08..88a7e2ba10f6 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationPrimitiveCylinder_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationPrimitiveCylinder_Seeded.h @@ -21,9 +21,6 @@ class ENGINE_API UParticleModuleLocationPrimitiveCylinder_Seeded : public UParti //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationPrimitiveSphere_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationPrimitiveSphere_Seeded.h index 906613086723..b0d3948d6239 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationPrimitiveSphere_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationPrimitiveSphere_Seeded.h @@ -21,9 +21,6 @@ class ENGINE_API UParticleModuleLocationPrimitiveSphere_Seeded : public UParticl //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationWorldOffset_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationWorldOffset_Seeded.h index 7751370811fb..275e6504a880 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationWorldOffset_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationWorldOffset_Seeded.h @@ -21,9 +21,6 @@ class ENGINE_API UParticleModuleLocationWorldOffset_Seeded : public UParticleMod //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocation_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocation_Seeded.h index 1ce9d0527e5f..8897e8c489cb 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocation_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocation_Seeded.h @@ -21,9 +21,6 @@ class ENGINE_API UParticleModuleLocation_Seeded : public UParticleModuleLocation //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Parameter/ParticleModuleParameterDynamic_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Parameter/ParticleModuleParameterDynamic_Seeded.h index 14620dd419d3..71b49c072e57 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Parameter/ParticleModuleParameterDynamic_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Parameter/ParticleModuleParameterDynamic_Seeded.h @@ -19,9 +19,6 @@ class UParticleModuleParameterDynamic_Seeded : public UParticleModuleParameterDy //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleEmitter.h b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleEmitter.h index f474340d445f..9157117b4abd 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleEmitter.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleEmitter.h @@ -217,6 +217,9 @@ class UParticleEmitter : public UObject /** Map module pointers to their offset into the instance data. */ TMap ModuleInstanceOffsetMap; + /** Map module pointers to their offset into the instance data. */ + TMap ModuleRandomSeedInstanceOffsetMap; + /** Materials collected from any MeshMaterial modules */ TArray MeshMaterials; @@ -236,6 +239,9 @@ class UParticleEmitter : public UObject // Array of modules that want emitter instance data TArray ModulesNeedingInstanceData; + // Array of modules that want emitter random seed instance data + TArray ModulesNeedingRandomSeedInstanceData; + /** SubUV animation asset to use for cutout geometry. */ class USubUVAnimation* RESTRICT SubUVAnimation; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleModule.h b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleModule.h index f4f225d1d094..cc1ac9a15633 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleModule.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleModule.h @@ -545,6 +545,15 @@ class ENGINE_API UParticleModule : public UObject /** Returns whether this module is used in any GPU emitters. */ bool IsUsedInGPUEmitter()const; + /** + * Retreive the random stream that should be used for the provided instance. + * + * @param Owner The emitter instance that owns this module + * + * @return FRandomStream& The random stream to use for the provided instance. + */ + FRandomStream& GetRandomStream(FParticleEmitterInstance* Owner); + #if WITH_EDITOR virtual void PostLoadSubobjects( FObjectInstancingGraph* OuterInstanceGraph ) override; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleModuleRequired.h b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleModuleRequired.h index 4c7bdb68b064..857583d4b5fa 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleModuleRequired.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleModuleRequired.h @@ -37,24 +37,48 @@ enum class EParticleUVFlipMode : uint8 }; /** Flips the sign of a particle's base size based on it's UV flip mode. */ -FORCEINLINE void AdjustParticleBaseSizeForUVFlipping(FVector& OutSize, EParticleUVFlipMode FlipMode) +FORCEINLINE void AdjustParticleBaseSizeForUVFlipping(FVector& OutSize, EParticleUVFlipMode FlipMode, FRandomStream& InRandomStream) { - static const int32 HalfRandMax = RAND_MAX / 2; + static const float HalfRandMax = 0.5f; + switch (FlipMode) { - case EParticleUVFlipMode::FlipUV: OutSize = -OutSize; return; - case EParticleUVFlipMode::FlipUOnly: OutSize.X = -OutSize.X; return; - case EParticleUVFlipMode::FlipVOnly: OutSize.Y = -OutSize.Y; return; - case EParticleUVFlipMode::RandomFlipUV: OutSize = FMath::Rand() > HalfRandMax ? -OutSize : OutSize; return; - case EParticleUVFlipMode::RandomFlipUOnly: OutSize.X = FMath::Rand() > HalfRandMax ? -OutSize.X : OutSize.X; return; - case EParticleUVFlipMode::RandomFlipVOnly: OutSize.Y = FMath::Rand() > HalfRandMax ? -OutSize.Y : OutSize.Y; return; - case EParticleUVFlipMode::RandomFlipUVIndependent: - { - OutSize.X = FMath::Rand() > HalfRandMax ? -OutSize.X : OutSize.X; - OutSize.Y = FMath::Rand() > HalfRandMax ? -OutSize.Y : OutSize.Y; - return; + case EParticleUVFlipMode::None: + return; + + case EParticleUVFlipMode::FlipUV: + OutSize = -OutSize; + return; + + case EParticleUVFlipMode::FlipUOnly: + OutSize.X = -OutSize.X; + return; + + case EParticleUVFlipMode::FlipVOnly: + OutSize.Y = -OutSize.Y; + return; + + case EParticleUVFlipMode::RandomFlipUV: + OutSize = InRandomStream.FRand() > HalfRandMax ? -OutSize : OutSize; + return; + + case EParticleUVFlipMode::RandomFlipUOnly: + OutSize.X = InRandomStream.FRand() > HalfRandMax ? -OutSize.X : OutSize.X; + return; + + case EParticleUVFlipMode::RandomFlipVOnly: + OutSize.Y = InRandomStream.FRand() > HalfRandMax ? -OutSize.Y : OutSize.Y; + return; + + case EParticleUVFlipMode::RandomFlipUVIndependent: + OutSize.X = InRandomStream.FRand() > HalfRandMax ? -OutSize.X : OutSize.X; + OutSize.Y = InRandomStream.FRand() > HalfRandMax ? -OutSize.Y : OutSize.Y; + return; + + default: + checkNoEntry(); + break; } - }; } UENUM() diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleSystemComponent.h b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleSystemComponent.h index ef231159df00..f1184a86cea2 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleSystemComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleSystemComponent.h @@ -34,6 +34,7 @@ class UAnimNotifyState; class FParticleDynamicData; class FParticleSystemSceneProxy; class UAnimNotifyState; +class UFXSystemAsset; struct FDynamicEmitterDataBase; struct FDynamicEmitterReplayDataBase; struct FParticleAnimTrailEmitterInstance; @@ -354,6 +355,11 @@ public: */ UFUNCTION(BlueprintCallable, Category="Effects|Components|ParticleSystem") virtual void SetActorParameter(FName ParameterName, class AActor* Param) {} + + /** + * Get the referenced FXSystem asset. + */ + virtual UFXSystemAsset* GetFXSystemAsset() const { return nullptr; }; }; @@ -1020,6 +1026,11 @@ public: */ void SetActorParameter(FName ParameterName, class AActor* Param) override; + /** + * Get the referenced FXSystem asset. + */ + virtual UFXSystemAsset* GetFXSystemAsset() const { return Template; }; + /** * Set a named material instance parameter on this ParticleSystemComponent. * Updates the parameter if it already exists, or creates a new entry if not. @@ -1164,6 +1175,9 @@ public: /** Static delegate called for all systems on an activation change. */ static FOnSystemPreActivationChange OnSystemPreActivationChange; + /** Stream of random values to use with this component */ + FRandomStream RandomStream; + private: /** In some cases the async work for this PSC can be created externally by the manager. */ FORCEINLINE void SetAsyncWork(FGraphEventRef& InAsyncWork) { AsyncWork = InAsyncWork; } diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleSystemManager.h b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleSystemManager.h index 05b1c9c31df3..d48452dfb4dd 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleSystemManager.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleSystemManager.h @@ -137,6 +137,7 @@ public: // FGCObject interface virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + virtual FString GetReferencerName() const override; void Dump(); diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Rotation/ParticleModuleMeshRotation_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Rotation/ParticleModuleMeshRotation_Seeded.h index 14ffd8d5d224..28db7b1ee8b9 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Rotation/ParticleModuleMeshRotation_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Rotation/ParticleModuleMeshRotation_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleMeshRotation_Seeded : public UParticleModuleMeshRotation //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Rotation/ParticleModuleRotation_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Rotation/ParticleModuleRotation_Seeded.h index e638296b2848..4d95698bf77b 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Rotation/ParticleModuleRotation_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Rotation/ParticleModuleRotation_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleRotation_Seeded : public UParticleModuleRotation //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/RotationRate/ParticleModuleMeshRotationRate_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/RotationRate/ParticleModuleMeshRotationRate_Seeded.h index 874e1f93efb7..e04beca774ac 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/RotationRate/ParticleModuleMeshRotationRate_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/RotationRate/ParticleModuleMeshRotationRate_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleMeshRotationRate_Seeded : public UParticleModuleMeshRotatio //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/RotationRate/ParticleModuleRotationRate_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/RotationRate/ParticleModuleRotationRate_Seeded.h index 02d9bcd509e9..a36638c490eb 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/RotationRate/ParticleModuleRotationRate_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/RotationRate/ParticleModuleRotationRate_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleRotationRate_Seeded : public UParticleModuleRotationRate //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Size/ParticleModuleSize_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Size/ParticleModuleSize_Seeded.h index 3f4d62eb214e..a5b21b4ac40f 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Size/ParticleModuleSize_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Size/ParticleModuleSize_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleSize_Seeded : public UParticleModuleSize //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Velocity/ParticleModuleVelocity_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Velocity/ParticleModuleVelocity_Seeded.h index 4177e9ea6865..cd2f2b97849e 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Velocity/ParticleModuleVelocity_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Velocity/ParticleModuleVelocity_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleVelocity_Seeded : public UParticleModuleVelocity //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/PhysicsEngine/PhysicsSettings.h b/Engine/Source/Runtime/Engine/Classes/PhysicsEngine/PhysicsSettings.h index 90657179573c..5bc774dee7e5 100644 --- a/Engine/Source/Runtime/Engine/Classes/PhysicsEngine/PhysicsSettings.h +++ b/Engine/Source/Runtime/Engine/Classes/PhysicsEngine/PhysicsSettings.h @@ -106,7 +106,7 @@ class ENGINE_API UPhysicsSettings : public UDeveloperSettings float TriangleMeshTriangleMinAreaThreshold; /** Enables shape sharing between sync and async scene for static rigid actors */ - UPROPERTY(config, EditAnywhere, AdvancedDisplay, Category = Simulation, meta = (editcondition="bEnableAsyncScene")) + UPROPERTY(config, EditAnywhere, AdvancedDisplay, Category = Simulation) bool bEnableShapeSharing; /** Enables persistent contact manifolds. This will generate fewer contact points, but with more accuracy. Reduces stability of stacking, but can help energy conservation.*/ diff --git a/Engine/Source/Runtime/Engine/Classes/Sound/SampleBuffer.h b/Engine/Source/Runtime/Engine/Classes/Sound/SampleBuffer.h index 0382a8962706..37f75ec3f94d 100644 --- a/Engine/Source/Runtime/Engine/Classes/Sound/SampleBuffer.h +++ b/Engine/Source/Runtime/Engine/Classes/Sound/SampleBuffer.h @@ -539,7 +539,8 @@ namespace Audio void Update(); //~ GCObject Interface - void AddReferencedObjects(FReferenceCollector& Collector) override; + virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + virtual FString GetReferencerName() const override; //~ GCObject Interface private: diff --git a/Engine/Source/Runtime/Engine/Classes/Sound/SoundNode.h b/Engine/Source/Runtime/Engine/Classes/Sound/SoundNode.h index f233e68ef654..3d414293a349 100644 --- a/Engine/Source/Runtime/Engine/Classes/Sound/SoundNode.h +++ b/Engine/Source/Runtime/Engine/Classes/Sound/SoundNode.h @@ -4,6 +4,7 @@ #pragma once #include "CoreMinimal.h" +#include "Math/RandomStream.h" #include "UObject/ObjectMacros.h" #include "UObject/Object.h" #include "UObject/Class.h" @@ -69,6 +70,9 @@ class ENGINE_API USoundNode : public UObject class UEdGraphNode* GetGraphNode() const; #endif + /** Stream of random numbers to be used by this instance of USoundNode */ + FRandomStream RandomStream; + public: //~ Begin UObject Interface #if WITH_EDITOR diff --git a/Engine/Source/Runtime/Engine/Classes/Sound/SoundWave.h b/Engine/Source/Runtime/Engine/Classes/Sound/SoundWave.h index f6d50186435f..886db0eb929f 100644 --- a/Engine/Source/Runtime/Engine/Classes/Sound/SoundWave.h +++ b/Engine/Source/Runtime/Engine/Classes/Sound/SoundWave.h @@ -677,7 +677,7 @@ public: /** * Handle any special requirements when the sound starts (e.g. subtitles) */ - FWaveInstance* HandleStart( FActiveSound& ActiveSound, const UPTRINT WaveInstanceHash ) const; + FWaveInstance& HandleStart(FActiveSound& ActiveSound, const UPTRINT WaveInstanceHash) const; /** * This is only used for DTYPE_Procedural audio. It's recommended to use USynthComponent base class diff --git a/Engine/Source/Runtime/Engine/Engine.Build.cs b/Engine/Source/Runtime/Engine/Engine.Build.cs index 123945e858e2..1bfd59f5f60a 100644 --- a/Engine/Source/Runtime/Engine/Engine.Build.cs +++ b/Engine/Source/Runtime/Engine/Engine.Build.cs @@ -303,6 +303,7 @@ public class Engine : ModuleRules DynamicallyLoadedModuleNames.AddRange( new string[] { "NullNetworkReplayStreaming", + "LocalFileNetworkReplayStreaming", "HttpNetworkReplayStreaming", "Advertising" } diff --git a/Engine/Source/Runtime/Engine/Private/ActiveSound.cpp b/Engine/Source/Runtime/Engine/Private/ActiveSound.cpp index 64a872b6699c..5a760d85ca6f 100644 --- a/Engine/Source/Runtime/Engine/Private/ActiveSound.cpp +++ b/Engine/Source/Runtime/Engine/Private/ActiveSound.cpp @@ -125,6 +125,8 @@ FActiveSound::~FActiveSound() FActiveSound* FActiveSound::CreateVirtualCopy(const FActiveSound& InActiveSoundToCopy, FAudioDevice& InAudioDevice) { + check(!InActiveSoundToCopy.bIsStopping); + FActiveSound* ActiveSound = new FActiveSound(InActiveSoundToCopy); ActiveSound->bAsyncOcclusionPending = false; @@ -1090,9 +1092,11 @@ void FActiveSound::HandleInteriorVolumes( const FListener& Listener, FSoundParse } } -void FActiveSound::AddWaveInstance(const UPTRINT WaveInstanceHash, FWaveInstance& WaveInstance) +FWaveInstance& FActiveSound::AddWaveInstance(const UPTRINT WaveInstanceHash) { - WaveInstances.Add(WaveInstanceHash, &WaveInstance); + FWaveInstance* WaveInstance = new FWaveInstance(WaveInstanceHash, *this); + WaveInstances.Add(WaveInstanceHash, WaveInstance); + return *WaveInstance; } void FActiveSound::ApplyRadioFilter(const FSoundParseParameters& ParseParams ) diff --git a/Engine/Source/Runtime/Engine/Private/Actor.cpp b/Engine/Source/Runtime/Engine/Private/Actor.cpp index edabcc4d07f9..e8bf76f9cf83 100644 --- a/Engine/Source/Runtime/Engine/Private/Actor.cpp +++ b/Engine/Source/Runtime/Engine/Private/Actor.cpp @@ -3382,10 +3382,7 @@ void AActor::EnableInput(APlayerController* PlayerController) InputComponent->bBlockInput = bBlockInput; InputComponent->Priority = InputPriority; - if (UInputDelegateBinding::SupportsInputDelegate(GetClass())) - { - UInputDelegateBinding::BindInputDelegates(GetClass(), InputComponent); - } + UInputDelegateBinding::BindInputDelegates(GetClass(), InputComponent); } else { @@ -4790,7 +4787,7 @@ void AActor::K2_SetActorRelativeTransform(const FTransform& NewRelativeTransform SetActorRelativeTransform(NewRelativeTransform, bSweep, (bSweep ? &SweepHitResult : nullptr), TeleportFlagToEnum(bTeleport)); } -float AActor::GetGameTimeSinceCreation() +float AActor::GetGameTimeSinceCreation() const { if (UWorld* MyWorld = GetWorld()) { diff --git a/Engine/Source/Runtime/Engine/Private/ActorConstruction.cpp b/Engine/Source/Runtime/Engine/Private/ActorConstruction.cpp index e7941175efe9..96964db1b321 100644 --- a/Engine/Source/Runtime/Engine/Private/ActorConstruction.cpp +++ b/Engine/Source/Runtime/Engine/Private/ActorConstruction.cpp @@ -29,6 +29,7 @@ #include "Engine/SimpleConstructionScript.h" #include "Components/ChildActorComponent.h" #include "ProfilingDebugging/CsvProfiler.h" +#include "Algo/Transform.h" #if WITH_EDITOR #include "Editor.h" @@ -420,6 +421,19 @@ void AActor::RerunConstructionScripts() // In this case we need to "tunnel out" to find the parent component which has been created by the construction script. if (UActorComponent* CSAddedComponent = GetComponentAddedByConstructionScript(Component)) { + // If we have any instanced components attached to us and we're going to be destroyed we need to explicitly detach them so they don't choose a new + // parent component and the attachment data we stored in the component instance data cache won't get applied + if (USceneComponent* SceneComp = Cast(Component)) + { + TInlineComponentArray InstancedChildren; + Algo::TransformIf(SceneComp->GetAttachChildren(), InstancedChildren, [](USceneComponent* SC) { return SC && SC->CreationMethod == EComponentCreationMethod::Instance; }, [](USceneComponent* SC) { return SC; }); + for (USceneComponent* InstancedChild : InstancedChildren) + { + InstancedChild->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + } + } + + // Determine if this component is an inner of a component added by the construction script const bool bIsInnerComponent = (CSAddedComponent != Component); diff --git a/Engine/Source/Runtime/Engine/Private/AnimBlueprint.cpp b/Engine/Source/Runtime/Engine/Private/AnimBlueprint.cpp index eecfb553847a..4462d25e90ee 100644 --- a/Engine/Source/Runtime/Engine/Private/AnimBlueprint.cpp +++ b/Engine/Source/Runtime/Engine/Private/AnimBlueprint.cpp @@ -180,6 +180,19 @@ bool UAnimBlueprint::CanRecompileWhilePlayingInEditor() const { return true; } + +bool UAnimBlueprint::FindDiffs(const UBlueprint* OtherBlueprint, FDiffResults& Results) const +{ + const UAnimBlueprint* OtherAnimBP = Cast(OtherBlueprint); + if (!OtherAnimBP) + { + return false; + } + + // Anim BPs should diff correctly, as all the info is stored in graphs or the parent + return true; +} + #endif USkeletalMesh* UAnimBlueprint::GetPreviewMesh(bool bFindIfNotSet/*=false*/) diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress.cpp index ae9b167bd022..d8d01efe3b3b 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress.cpp @@ -28,33 +28,32 @@ UAnimCompress::UAnimCompress(const FObjectInitializer& ObjectInitializer) MaxNumFramesPerSegment = (IdealNumFramesPerSegment * 2) - 1; } -void FCompressionMemorySummary::GatherPostCompressionStats(UAnimSequence* Seq, const TArray& BoneData, double CompressionTime) +void FCompressionMemorySummary::GatherPostCompressionStats(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& CompressedData, double CompressionTime) { +#if WITH_EDITOR if (bEnabled) { - TotalAfterCompressed += Seq->GetApproxCompressedSize(); + TotalAfterCompressed += CompressedData.GetApproxBoneCompressedSize(); TotalCompressionExecutionTime += CompressionTime; - if (Seq->GetSkeleton() != NULL) - { - // determine the error added by the compression - AnimationErrorStats ErrorStats; - FAnimationUtils::ComputeCompressionError(Seq, BoneData, ErrorStats); + // determine the error added by the compression + AnimationErrorStats ErrorStats; + FAnimationUtils::ComputeCompressionError(CompressibleAnimData, CompressedData, ErrorStats); - ErrorTotal += ErrorStats.AverageError; - ErrorCount += 1.0f; - AverageError = ErrorTotal / ErrorCount; + ErrorTotal += ErrorStats.AverageError; + ErrorCount += 1.0f; + AverageError = ErrorTotal / ErrorCount; - WorstBoneError.StoreErrorStat(ErrorStats.MaxError, ErrorStats.MaxError, ErrorStats.MaxErrorTime, ErrorStats.MaxErrorBone, BoneData[ErrorStats.MaxErrorBone].Name, Seq->GetFName()); + WorstBoneError.StoreErrorStat(ErrorStats.MaxError, ErrorStats.MaxError, ErrorStats.MaxErrorTime, ErrorStats.MaxErrorBone, CompressibleAnimData.BoneData[ErrorStats.MaxErrorBone].Name, CompressibleAnimData.AnimFName); - WorstAnimationError.StoreErrorStat(ErrorStats.AverageError, ErrorStats.AverageError, Seq->GetFName()); - } + WorstAnimationError.StoreErrorStat(ErrorStats.AverageError, ErrorStats.AverageError, CompressibleAnimData.AnimFName); } +#endif } -void FAnimCompressContext::GatherPostCompressionStats(UAnimSequence* Seq, const TArray& BoneData, double CompressionTime) +void FAnimCompressContext::GatherPostCompressionStats(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& CompressedData, double CompressionTime) { - CompressionSummary.GatherPostCompressionStats(Seq, BoneData, CompressionTime); + CompressionSummary.GatherPostCompressionStats(CompressibleAnimData, CompressedData, CompressionTime); } FCompressionMemorySummary::FCompressionMemorySummary(bool bInEnabled) @@ -204,13 +203,13 @@ uint8 MakeBitForFlag(uint32 Item, uint32 Position) // FCompressionMemorySummary -void FCompressionMemorySummary::GatherPreCompressionStats(UAnimSequence* Seq, int32 ProgressNumerator, int32 ProgressDenominator) +void FCompressionMemorySummary::GatherPreCompressionStats(const FCompressibleAnimData& CompressibleAnimData, int32 PreCompressedSize, int32 ProgressNumerator, int32 ProgressDenominator) { if (bEnabled) { bUsed = true; FFormatNamedArguments Args; - Args.Add(TEXT("AnimSequenceName"), FText::FromString(Seq->GetName())); + Args.Add(TEXT("AnimSequenceName"), FText::FromString(CompressibleAnimData.Name)); Args.Add(TEXT("ProgressNumerator"), ProgressNumerator); Args.Add(TEXT("ProgressDenominator"), ProgressDenominator); @@ -218,17 +217,17 @@ void FCompressionMemorySummary::GatherPreCompressionStats(UAnimSequence* Seq, in ProgressDenominator, FText::Format(NSLOCTEXT("CompressionMemorySummary", "CompressingTaskStatusMessageFormat", "Compressing {AnimSequenceName} ({ProgressNumerator}/{ProgressDenominator})"), Args)); - TotalRaw += Seq->GetApproxRawSize(); - TotalBeforeCompressed += Seq->GetApproxCompressedSize(); + TotalRaw += CompressibleAnimData.GetApproxRawBoneSize(); + TotalBeforeCompressed += PreCompressedSize; ++NumberOfAnimations; } } ////////////////////////////////////////////////////////////////////////////////////// -void FAnimCompressContext::GatherPreCompressionStats(UAnimSequence* Seq) +void FAnimCompressContext::GatherPreCompressionStats(const FCompressibleAnimData& CompressibleAnimData, int32 PreviousCompressionSize) { - CompressionSummary.GatherPreCompressionStats(Seq, AnimIndex, MaxAnimations); + CompressionSummary.GatherPreCompressionStats(CompressibleAnimData, PreviousCompressionSize, AnimIndex, MaxAnimations); } @@ -270,7 +269,8 @@ void UAnimCompress::PadByteStream(TArray& ByteStream, const int32 Alignme void UAnimCompress::BitwiseCompressAnimationTracks( - class UAnimSequence* Seq, + const FCompressibleAnimData& CompressibleAnimData, + FCompressibleAnimDataResult& OutCompressedData, AnimationCompressionFormat TargetTranslationFormat, AnimationCompressionFormat TargetRotationFormat, AnimationCompressionFormat TargetScaleFormat, @@ -298,19 +298,18 @@ void UAnimCompress::BitwiseCompressAnimationTracks( } if (bInvalidCompressionFormat) { - Seq->TranslationCompressionFormat = ACF_None; - Seq->RotationCompressionFormat = ACF_None; - Seq->ScaleCompressionFormat = ACF_None; - Seq->CompressedTrackOffsets.Empty(); - Seq->CompressedScaleOffsets.Empty(); - Seq->CompressedByteStream.Empty(); - Seq->CompressedSegments.Empty(); + OutCompressedData.TranslationCompressionFormat = ACF_None; + OutCompressedData.RotationCompressionFormat = ACF_None; + OutCompressedData.ScaleCompressionFormat = ACF_None; + OutCompressedData.CompressedTrackOffsets.Empty(); + OutCompressedData.CompressedScaleOffsets.Empty(); + OutCompressedData.CompressedByteStream.Empty(); } else { - Seq->RotationCompressionFormat = TargetRotationFormat; - Seq->TranslationCompressionFormat = TargetTranslationFormat; - Seq->ScaleCompressionFormat = TargetScaleFormat; + OutCompressedData.RotationCompressionFormat = TargetRotationFormat; + OutCompressedData.TranslationCompressionFormat = TargetTranslationFormat; + OutCompressedData.ScaleCompressionFormat = TargetScaleFormat; check(TranslationData.Num() == RotationData.Num()); const int32 NumTracks = RotationData.Num(); @@ -318,41 +317,40 @@ void UAnimCompress::BitwiseCompressAnimationTracks( if (NumTracks == 0) { - UE_LOG(LogAnimationCompression, Warning, TEXT("When compressing %s: no key-reduced data"), *Seq->GetName()); + UE_LOG(LogAnimationCompression, Warning, TEXT("When compressing %s: no key-reduced data"), *CompressibleAnimData.Name); } - Seq->CompressedTrackOffsets.Empty(NumTracks * 4); - Seq->CompressedTrackOffsets.AddUninitialized(NumTracks * 4); + OutCompressedData.CompressedTrackOffsets.Empty(NumTracks * 4); + OutCompressedData.CompressedTrackOffsets.AddUninitialized(NumTracks * 4); // just empty it since there is chance this can be 0 - Seq->CompressedScaleOffsets.Empty(); + OutCompressedData.CompressedScaleOffsets.Empty(); // only do this if Scale exists; if (bHasScale) { - Seq->CompressedScaleOffsets.SetStripSize(2); - Seq->CompressedScaleOffsets.AddUninitialized(NumTracks); + OutCompressedData.CompressedScaleOffsets.SetStripSize(2); + OutCompressedData.CompressedScaleOffsets.AddUninitialized(NumTracks); } - Seq->CompressedByteStream.Empty(); - Seq->CompressedSegments.Empty(); + OutCompressedData.CompressedByteStream.Empty(); for (int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex) { // Translation data. const FTranslationTrack& SrcTrans = TranslationData[TrackIndex]; - const int32 OffsetTrans = Seq->CompressedByteStream.Num(); + const int32 OffsetTrans = OutCompressedData.CompressedByteStream.Num(); const int32 NumKeysTrans = SrcTrans.PosKeys.Num(); // Warn on empty data. if (NumKeysTrans == 0) { - UE_LOG(LogAnimationCompression, Warning, TEXT("When compressing %s track %i: no translation keys"), *Seq->GetName(), TrackIndex); + UE_LOG(LogAnimationCompression, Warning, TEXT("When compressing %s track %i: no translation keys"), *CompressibleAnimData.Name, TrackIndex); } checkf((OffsetTrans % 4) == 0, TEXT("CompressedByteStream not aligned to four bytes")); - Seq->CompressedTrackOffsets[TrackIndex * 4] = OffsetTrans; - Seq->CompressedTrackOffsets[TrackIndex * 4 + 1] = NumKeysTrans; + OutCompressedData.CompressedTrackOffsets[TrackIndex * 4] = OffsetTrans; + OutCompressedData.CompressedTrackOffsets[TrackIndex * 4 + 1] = NumKeysTrans; // Calculate the bounding box of the translation keys FBox PositionBounds(SrcTrans.PosKeys); @@ -368,31 +366,31 @@ void UAnimCompress::BitwiseCompressAnimationTracks( // Write the mins and ranges if they'll be used on the other side if (TargetTranslationFormat == ACF_IntervalFixed32NoW) { - UnalignedWriteToStream(Seq->CompressedByteStream, TransMins, sizeof(float) * 3); - UnalignedWriteToStream(Seq->CompressedByteStream, TransRanges, sizeof(float) * 3); + UnalignedWriteToStream(OutCompressedData.CompressedByteStream, TransMins, sizeof(float) * 3); + UnalignedWriteToStream(OutCompressedData.CompressedByteStream, TransRanges, sizeof(float) * 3); } // Pack the positions into the stream for (int32 KeyIndex = 0; KeyIndex < NumKeysTrans; ++KeyIndex) { const FVector& Vec = SrcTrans.PosKeys[KeyIndex]; - PackVectorToStream(Seq->CompressedByteStream, TargetTranslationFormat, Vec, TransMins, TransRanges); + PackVectorToStream(OutCompressedData.CompressedByteStream, TargetTranslationFormat, Vec, TransMins, TransRanges); } if (IncludeKeyTable) { // Align to four bytes. - PadByteStream(Seq->CompressedByteStream, 4, AnimationPadSentinel); + PadByteStream(OutCompressedData.CompressedByteStream, 4, AnimationPadSentinel); // write the key table - const int32 NumFrames = Seq->GetRawNumberOfFrames(); - const int32 LastFrame = Seq->GetRawNumberOfFrames()-1; - const size_t FrameSize = Seq->GetRawNumberOfFrames() > 0xff ? sizeof(uint16) : sizeof(uint8); - const float FrameRate = LastFrame / Seq->SequenceLength; + const int32 NumFrames = CompressibleAnimData.NumFrames; + const int32 LastFrame = NumFrames-1; + const size_t FrameSize = NumFrames > 0xff ? sizeof(uint16) : sizeof(uint8); + const float FrameRate = LastFrame / CompressibleAnimData.SequenceLength; const int32 TableSize = NumKeysTrans*FrameSize; const int32 TableDwords = (TableSize + 3) >> 2; - const int32 StartingOffset = Seq->CompressedByteStream.Num(); + const int32 StartingOffset = OutCompressedData.CompressedByteStream.Num(); for (int32 KeyIndex = 0; KeyIndex < NumKeysTrans; ++KeyIndex) { @@ -400,37 +398,37 @@ void UAnimCompress::BitwiseCompressAnimationTracks( float KeyTime = SrcTrans.Times[KeyIndex]; float FrameTime = KeyTime * FrameRate; int32 FrameIndex = FMath::Clamp(FMath::TruncToInt(FrameTime + 0.5f), 0, LastFrame); - UnalignedWriteToStream(Seq->CompressedByteStream, &FrameIndex, FrameSize); + UnalignedWriteToStream(OutCompressedData.CompressedByteStream, &FrameIndex, FrameSize); } // Align to four bytes. Padding with 0's to round out the key table - PadByteStream(Seq->CompressedByteStream, 4, 0); + PadByteStream(OutCompressedData.CompressedByteStream, 4, 0); - const int32 EndingOffset = Seq->CompressedByteStream.Num(); + const int32 EndingOffset = OutCompressedData.CompressedByteStream.Num(); check((EndingOffset - StartingOffset) == (TableDwords * 4)); } } else if (NumKeysTrans == 1) { // A single translation key gets written out a single uncompressed float[3]. - UnalignedWriteToStream(Seq->CompressedByteStream, &(SrcTrans.PosKeys[0]), sizeof(FVector)); + UnalignedWriteToStream(OutCompressedData.CompressedByteStream, &(SrcTrans.PosKeys[0]), sizeof(FVector)); } else { - UE_LOG(LogAnimationCompression, Warning, TEXT("When compressing %s track %i: no translation keys"), *Seq->GetName(), TrackIndex); + UE_LOG(LogAnimationCompression, Warning, TEXT("When compressing %s track %i: no translation keys"), *CompressibleAnimData.Name, TrackIndex); } // Align to four bytes. - PadByteStream(Seq->CompressedByteStream, 4, AnimationPadSentinel); + PadByteStream(OutCompressedData.CompressedByteStream, 4, AnimationPadSentinel); // Compress rotation data. const FRotationTrack& SrcRot = RotationData[TrackIndex]; - const int32 OffsetRot = Seq->CompressedByteStream.Num(); + const int32 OffsetRot = OutCompressedData.CompressedByteStream.Num(); const int32 NumKeysRot = SrcRot.RotKeys.Num(); checkf((OffsetRot % 4) == 0, TEXT("CompressedByteStream not aligned to four bytes")); - Seq->CompressedTrackOffsets[TrackIndex * 4 + 2] = OffsetRot; - Seq->CompressedTrackOffsets[TrackIndex * 4 + 3] = NumKeysRot; + OutCompressedData.CompressedTrackOffsets[TrackIndex * 4 + 2] = OffsetRot; + OutCompressedData.CompressedTrackOffsets[TrackIndex * 4 + 3] = NumKeysRot; if (NumKeysRot > 1) { @@ -469,32 +467,32 @@ void UAnimCompress::BitwiseCompressAnimationTracks( // Write the mins and ranges if they'll be used on the other side if (TargetRotationFormat == ACF_IntervalFixed32NoW) { - UnalignedWriteToStream(Seq->CompressedByteStream, Mins, sizeof(float) * 3); - UnalignedWriteToStream(Seq->CompressedByteStream, Ranges, sizeof(float) * 3); + UnalignedWriteToStream(OutCompressedData.CompressedByteStream, Mins, sizeof(float) * 3); + UnalignedWriteToStream(OutCompressedData.CompressedByteStream, Ranges, sizeof(float) * 3); } // n elements of the compressed type. for (int32 KeyIndex = 0; KeyIndex < SrcRot.RotKeys.Num(); ++KeyIndex) { const FQuat& Quat = SrcRot.RotKeys[KeyIndex]; - PackQuaternionToStream(Seq->CompressedByteStream, TargetRotationFormat, Quat, Mins, Ranges); + PackQuaternionToStream(OutCompressedData.CompressedByteStream, TargetRotationFormat, Quat, Mins, Ranges); } // n elements of frame indices if (IncludeKeyTable) { // Align to four bytes. - PadByteStream(Seq->CompressedByteStream, 4, AnimationPadSentinel); + PadByteStream(OutCompressedData.CompressedByteStream, 4, AnimationPadSentinel); // write the key table - const int32 NumFrames = Seq->GetRawNumberOfFrames(); - const int32 LastFrame= Seq->GetRawNumberOfFrames()-1; - const size_t FrameSize= Seq->GetRawNumberOfFrames() > 0xff ? sizeof(uint16) : sizeof(uint8); - const float FrameRate = LastFrame / Seq->SequenceLength; + const int32 NumFrames = CompressibleAnimData.NumFrames; + const int32 LastFrame= NumFrames-1; + const size_t FrameSize= NumFrames > 0xff ? sizeof(uint16) : sizeof(uint8); + const float FrameRate = LastFrame / CompressibleAnimData.SequenceLength; const int32 TableSize = NumKeysRot*FrameSize; const int32 TableDwords = (TableSize + 3) >> 2; - const int32 StartingOffset = Seq->CompressedByteStream.Num(); + const int32 StartingOffset = OutCompressedData.CompressedByteStream.Num(); for (int32 KeyIndex = 0; KeyIndex < NumKeysRot; ++KeyIndex) { @@ -502,13 +500,13 @@ void UAnimCompress::BitwiseCompressAnimationTracks( float KeyTime = SrcRot.Times[KeyIndex]; float FrameTime = KeyTime * FrameRate; int32 FrameIndex = FMath::Clamp(FMath::TruncToInt(FrameTime + 0.5f), 0, LastFrame); - UnalignedWriteToStream(Seq->CompressedByteStream, &FrameIndex, FrameSize); + UnalignedWriteToStream(OutCompressedData.CompressedByteStream, &FrameIndex, FrameSize); } // Align to four bytes. Padding with 0's to round out the key table - PadByteStream(Seq->CompressedByteStream, 4, 0); + PadByteStream(OutCompressedData.CompressedByteStream, 4, 0); - const int32 EndingOffset = Seq->CompressedByteStream.Num(); + const int32 EndingOffset = OutCompressedData.CompressedByteStream.Num(); check((EndingOffset - StartingOffset) == (TableDwords * 4)); } @@ -518,28 +516,28 @@ void UAnimCompress::BitwiseCompressAnimationTracks( // For a rotation track of n=1 keys, the single key is packed as an FQuatFloat96NoW. const FQuat& Quat = SrcRot.RotKeys[0]; const FQuatFloat96NoW QuatFloat96NoW(Quat); - UnalignedWriteToStream(Seq->CompressedByteStream, &QuatFloat96NoW, sizeof(FQuatFloat96NoW)); + UnalignedWriteToStream(OutCompressedData.CompressedByteStream, &QuatFloat96NoW, sizeof(FQuatFloat96NoW)); } else { - UE_LOG(LogAnimationCompression, Warning, TEXT("When compressing %s track %i: no rotation keys"), *Seq->GetName(), TrackIndex); + UE_LOG(LogAnimationCompression, Warning, TEXT("When compressing %s track %i: no rotation keys"), *CompressibleAnimData.Name, TrackIndex); } // Align to four bytes. - PadByteStream(Seq->CompressedByteStream, 4, AnimationPadSentinel); + PadByteStream(OutCompressedData.CompressedByteStream, 4, AnimationPadSentinel); // we also should do this only when scale exists. if (bHasScale) { const FScaleTrack& SrcScale = ScaleData[TrackIndex]; - const int32 OffsetScale = Seq->CompressedByteStream.Num(); + const int32 OffsetScale = OutCompressedData.CompressedByteStream.Num(); const int32 NumKeysScale = SrcScale.ScaleKeys.Num(); checkf((OffsetScale % 4) == 0, TEXT("CompressedByteStream not aligned to four bytes")); - Seq->CompressedScaleOffsets.SetOffsetData(TrackIndex, 0, OffsetScale); - Seq->CompressedScaleOffsets.SetOffsetData(TrackIndex, 1, NumKeysScale); + OutCompressedData.CompressedScaleOffsets.SetOffsetData(TrackIndex, 0, OffsetScale); + OutCompressedData.CompressedScaleOffsets.SetOffsetData(TrackIndex, 1, NumKeysScale); // Calculate the bounding box of the Scalelation keys FBox ScaleBoundsBounds(SrcScale.ScaleKeys); @@ -556,31 +554,31 @@ void UAnimCompress::BitwiseCompressAnimationTracks( // Write the mins and ranges if they'll be used on the other side if (TargetScaleFormat == ACF_IntervalFixed32NoW) { - UnalignedWriteToStream(Seq->CompressedByteStream, ScaleMins, sizeof(float) * 3); - UnalignedWriteToStream(Seq->CompressedByteStream, ScaleRanges, sizeof(float) * 3); + UnalignedWriteToStream(OutCompressedData.CompressedByteStream, ScaleMins, sizeof(float) * 3); + UnalignedWriteToStream(OutCompressedData.CompressedByteStream, ScaleRanges, sizeof(float) * 3); } // Pack the positions into the stream for (int32 KeyIndex = 0; KeyIndex < NumKeysScale; ++KeyIndex) { const FVector& Vec = SrcScale.ScaleKeys[KeyIndex]; - PackVectorToStream(Seq->CompressedByteStream, TargetScaleFormat, Vec, ScaleMins, ScaleRanges); + PackVectorToStream(OutCompressedData.CompressedByteStream, TargetScaleFormat, Vec, ScaleMins, ScaleRanges); } if (IncludeKeyTable) { // Align to four bytes. - PadByteStream(Seq->CompressedByteStream, 4, AnimationPadSentinel); + PadByteStream(OutCompressedData.CompressedByteStream, 4, AnimationPadSentinel); // write the key table - const int32 NumFrames = Seq->GetRawNumberOfFrames(); - const int32 LastFrame = Seq->GetRawNumberOfFrames()-1; - const size_t FrameSize = Seq->GetRawNumberOfFrames() > 0xff ? sizeof(uint16) : sizeof(uint8); - const float FrameRate = LastFrame / Seq->SequenceLength; + const int32 NumFrames = CompressibleAnimData.NumFrames; + const int32 LastFrame = NumFrames-1; + const size_t FrameSize = NumFrames > 0xff ? sizeof(uint16) : sizeof(uint8); + const float FrameRate = LastFrame / CompressibleAnimData.SequenceLength; const int32 TableSize = NumKeysScale*FrameSize; const int32 TableDwords = (TableSize + 3) >> 2; - const int32 StartingOffset = Seq->CompressedByteStream.Num(); + const int32 StartingOffset = OutCompressedData.CompressedByteStream.Num(); for (int32 KeyIndex = 0; KeyIndex < NumKeysScale; ++KeyIndex) { @@ -588,36 +586,37 @@ void UAnimCompress::BitwiseCompressAnimationTracks( float KeyTime = SrcScale.Times[KeyIndex]; float FrameTime = KeyTime * FrameRate; int32 FrameIndex = FMath::Clamp(FMath::TruncToInt(FrameTime + 0.5f), 0, LastFrame); - UnalignedWriteToStream(Seq->CompressedByteStream, &FrameIndex, FrameSize); + UnalignedWriteToStream(OutCompressedData.CompressedByteStream, &FrameIndex, FrameSize); } // Align to four bytes. Padding with 0's to round out the key table - PadByteStream(Seq->CompressedByteStream, 4, 0); + PadByteStream(OutCompressedData.CompressedByteStream, 4, 0); - const int32 EndingOffset = Seq->CompressedByteStream.Num(); + const int32 EndingOffset = OutCompressedData.CompressedByteStream.Num(); check((EndingOffset - StartingOffset) == (TableDwords * 4)); } } else if (NumKeysScale == 1) { // A single Scalelation key gets written out a single uncompressed float[3]. - UnalignedWriteToStream(Seq->CompressedByteStream, &(SrcScale.ScaleKeys[0]), sizeof(FVector)); + UnalignedWriteToStream(OutCompressedData.CompressedByteStream, &(SrcScale.ScaleKeys[0]), sizeof(FVector)); } else { - UE_LOG(LogAnimationCompression, Warning, TEXT("When compressing %s track %i: no Scale keys"), *Seq->GetName(), TrackIndex); + UE_LOG(LogAnimationCompression, Warning, TEXT("When compressing %s track %i: no Scale keys"), *CompressibleAnimData.Name, TrackIndex); } // Align to four bytes. - PadByteStream(Seq->CompressedByteStream, 4, AnimationPadSentinel); + PadByteStream(OutCompressedData.CompressedByteStream, 4, AnimationPadSentinel); } } // Trim unused memory. - Seq->CompressedByteStream.Shrink(); + OutCompressedData.CompressedByteStream.Shrink(); } } +#if USE_SEGMENTING_CONTEXT void UAnimCompress::BitwiseCompressAnimationTracks( class UAnimSequence& AnimSeq, AnimationCompressionFormat TargetTranslationFormat, @@ -705,6 +704,7 @@ void UAnimCompress::SanityCheckTrackData(const UAnimSequence& AnimSeq, const FAn } } } +#endif void UAnimCompress::CalculateTrackRange(const FTranslationTrack& TranslationData, AnimationCompressionFormat Format, FVector& OutMin, FVector& OutExtent) { @@ -806,6 +806,7 @@ void UAnimCompress::CalculateTrackRange(const FScaleTrack& ScaleData, AnimationC OutExtent = Ranges; } +#if USE_SEGMENTING_CONTEXT void UAnimCompress::CalculateTrackRanges( AnimationCompressionFormat TargetTranslationFormat, AnimationCompressionFormat TargetRotationFormat, @@ -1576,6 +1577,7 @@ void UAnimCompress::CoalesceCompressedSegments(UAnimSequence& AnimSeq, const TAr Header.SequenceCRC = FCrc::MemCrc32(AnimSeq.CompressedByteStream.GetData(), AnimSeq.CompressedByteStream.Num()); memcpy(AnimSeq.CompressedByteStream.GetData(), &Header, sizeof(FAnimSequenceCompressionHeader)); } +#endif #if WITH_EDITOR @@ -1621,42 +1623,24 @@ void UAnimCompress::PopulateDDCKey(FArchive& Ar) * Tracks */ -bool UAnimCompress::Reduce(UAnimSequence* AnimSeq, bool bOutput, const TArray& BoneData) +bool UAnimCompress::Reduce(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) { - bool bResult = false; #if WITH_EDITORONLY_DATA - USkeleton* AnimSkeleton = AnimSeq->GetSkeleton(); - const bool bSkeletonExistsIfNeeded = ( AnimSkeleton || !bNeedsSkeleton); + const bool bSkeletonExistsIfNeeded = ( CompressibleAnimData.Skeleton || !bNeedsSkeleton); if ( bSkeletonExistsIfNeeded ) { - FAnimCompressContext CompressContext(false, bOutput); - Reduce(AnimSeq, CompressContext, BoneData); + OutResult.CompressedNumberOfFrames = CompressibleAnimData.NumFrames; - bResult = true; + DoReduction(CompressibleAnimData, OutResult); + + OutResult.CompressionScheme = this; + return true; } #endif // WITH_EDITORONLY_DATA - return bResult; + return false; } -bool UAnimCompress::Reduce(class UAnimSequence* AnimSeq, FAnimCompressContext& Context, const TArray& BoneData) -{ - bool bResult = false; - -#if WITH_EDITORONLY_DATA - - DoReduction(AnimSeq, BoneData); - - AnimSeq->bWasCompressedWithoutTranslations = false; // @fixmelh : bAnimRotationOnly - - AnimSeq->EncodingPkgVersion = CURRENT_ANIMATION_ENCODING_PACKAGE_VERSION; - AnimSeq->MarkPackageDirty(); - - bResult = true; -#endif // WITH_EDITORONLY_DATA - - return bResult; -} #endif // WITH_EDITOR void UAnimCompress::FilterTrivialPositionKeys( @@ -1819,7 +1803,7 @@ void UAnimCompress::FilterTrivialKeys( FilterTrivialScaleKeys(ScaleTracks, MaxScaleDelta); } - +#if USE_SEGMENTING_CONTEXT void UAnimCompress::FilterTrivialKeys( TArray& RawSegments, float MaxPosDelta, @@ -1833,7 +1817,7 @@ void UAnimCompress::FilterTrivialKeys( FilterTrivialScaleKeys(Segment.ScaleData, MaxScaleDelta); } } - +#endif void UAnimCompress::FilterIntermittentPositionKeys( FTranslationTrack& Track, @@ -2058,6 +2042,7 @@ void UAnimCompress::SeparateRawDataIntoTracks( } } +#if USE_SEGMENTING_CONTEXT static void GenerateAnimSequenceSegments(const UAnimSequence& AnimSeq, int32 NumFrames, int32 IdealNumFramesPerSegment, int32 MaxNumFramesPerSegment, TArray& OutRawSegments) { int32 NumSegments; @@ -2118,6 +2103,7 @@ static void GenerateAnimSequenceSegments(const UAnimSequence& AnimSeq, int32 Num NumPreviousFrames += NumFramesInSegment; } } +#endif static int32 GetNumFrames(const UAnimSequence& AnimSeq, const TArray& TranslationData, const TArray& RotationData, const TArray& ScaleData) { @@ -2149,6 +2135,7 @@ static int32 GetNumFrames(const UAnimSequence& AnimSeq, const TArray& TranslationData, @@ -2283,4 +2270,6 @@ void UAnimCompress::SeparateRawDataIntoTracks( } } } +#endif + #endif \ No newline at end of file diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_Automatic.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_Automatic.cpp index 48890d9f4c2c..efaac825f304 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_Automatic.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_Automatic.cpp @@ -17,12 +17,13 @@ UAnimCompress_Automatic::UAnimCompress_Automatic(const FObjectInitializer& Objec } #if WITH_EDITOR -void UAnimCompress_Automatic::DoReduction(UAnimSequence* AnimSeq, const TArray& BoneData) +void UAnimCompress_Automatic::DoReduction(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) { FAnimCompressContext CompressContext(MaxEndEffectorError > 0.0f, false); #if WITH_EDITORONLY_DATA FAnimationUtils::CompressAnimSequenceExplicit( - AnimSeq, + CompressibleAnimData, + OutResult, CompressContext, MaxEndEffectorError, bRunCurrentDefaultCompressor, @@ -31,8 +32,7 @@ void UAnimCompress_Automatic::DoReduction(UAnimSequence* AnimSeq, const TArray& BoneData) +void UAnimCompress_BitwiseCompressOnly::DoReduction(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) { #if WITH_EDITORONLY_DATA // split the raw data into tracks TArray TranslationData; TArray RotationData; TArray ScaleData; - SeparateRawDataIntoTracks( AnimSeq->GetRawAnimationData(), AnimSeq->SequenceLength, TranslationData, RotationData, ScaleData ); + SeparateRawDataIntoTracks( CompressibleAnimData.RawAnimationData, CompressibleAnimData.SequenceLength, TranslationData, RotationData, ScaleData ); // remove obviously redundant keys from the source data FilterTrivialKeys(TranslationData, RotationData, ScaleData, TRANSLATION_ZEROING_THRESHOLD, QUATERNION_ZEROING_THRESHOLD, SCALE_ZEROING_THRESHOLD); // record the proper runtime decompressor to use - AnimSeq->KeyEncodingFormat = AKF_ConstantKeyLerp; - AnimSeq->RotationCompressionFormat = RotationCompressionFormat; - AnimSeq->TranslationCompressionFormat = TranslationCompressionFormat; - AnimSeq->ScaleCompressionFormat = ScaleCompressionFormat; - AnimationFormat_SetInterfaceLinks(*AnimSeq); + OutResult.KeyEncodingFormat = AKF_ConstantKeyLerp; + OutResult.RotationCompressionFormat = RotationCompressionFormat; + OutResult.TranslationCompressionFormat = TranslationCompressionFormat; + OutResult.ScaleCompressionFormat = ScaleCompressionFormat; + AnimationFormat_SetInterfaceLinks(OutResult); #if USE_SEGMENTING_CONTEXT if (bEnableSegmenting) @@ -58,7 +58,8 @@ void UAnimCompress_BitwiseCompressOnly::DoReduction(UAnimSequence* AnimSeq, cons { // bitwise compress the tracks into the anim sequence buffers BitwiseCompressAnimationTracks( - AnimSeq, + CompressibleAnimData, + OutResult, static_cast(TranslationCompressionFormat), static_cast(RotationCompressionFormat), static_cast(ScaleCompressionFormat), @@ -68,7 +69,7 @@ void UAnimCompress_BitwiseCompressOnly::DoReduction(UAnimSequence* AnimSeq, cons } // We could be invalid, set the links again - AnimationFormat_SetInterfaceLinks(*AnimSeq); + AnimationFormat_SetInterfaceLinks(OutResult); #endif // WITH_EDITORONLY_DATA } diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_LeastDestructive.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_LeastDestructive.cpp index 7fa778cf4aae..f496353210f1 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_LeastDestructive.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_LeastDestructive.cpp @@ -17,13 +17,11 @@ UAnimCompress_LeastDestructive::UAnimCompress_LeastDestructive(const FObjectInit #if WITH_EDITOR -void UAnimCompress_LeastDestructive::DoReduction(UAnimSequence* AnimSeq, const TArray& BoneData) +void UAnimCompress_LeastDestructive::DoReduction(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) { UAnimCompress* BitwiseCompressor = NewObject(); BitwiseCompressor->RotationCompressionFormat = ACF_Float96NoW; BitwiseCompressor->TranslationCompressionFormat = ACF_None; - BitwiseCompressor->Reduce(AnimSeq, false, BoneData); - - AnimSeq->CompressionScheme = BitwiseCompressor; + BitwiseCompressor->Reduce(CompressibleAnimData, OutResult); } #endif // WITH_EDITOR diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_PerTrackCompression.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_PerTrackCompression.cpp index 08acf35e794d..193352e57821 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_PerTrackCompression.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_PerTrackCompression.cpp @@ -31,7 +31,9 @@ struct FPerTrackParams { float MaxZeroingThreshold; - const UAnimSequence* AnimSeq; + int32 NumFrames; + + float SequenceLength; bool bIncludeKeyTable; }; @@ -802,8 +804,8 @@ protected: { if (bReallyNeedsFrameTable && (CompressedBytes.Num() > 0)) { - const int32 NumFrames = Params.AnimSeq->GetRawNumberOfFrames(); - const float SequenceLength = Params.AnimSeq->SequenceLength; + const int32 NumFrames = Params.NumFrames; + const float SequenceLength = Params.SequenceLength; const float FramesPerSecond = (NumFrames - 1) / SequenceLength; if (NumFrames <= 0xFF) @@ -822,7 +824,7 @@ public: FPerTrackCompressor(int32 InCompressionType, const FTranslationTrack& TranslationData, const FPerTrackParams& Params) { Reset(); - bReallyNeedsFrameTable = Params.bIncludeKeyTable && (TranslationData.PosKeys.Num() > 1) && (TranslationData.PosKeys.Num() < Params.AnimSeq->GetRawNumberOfFrames()); + bReallyNeedsFrameTable = Params.bIncludeKeyTable && (TranslationData.PosKeys.Num() > 1) && (TranslationData.PosKeys.Num() < Params.NumFrames); switch (InCompressionType) { @@ -856,7 +858,7 @@ public: FPerTrackCompressor(int32 InCompressionType, const FRotationTrack& RotationData, const FPerTrackParams& Params) { Reset(); - bReallyNeedsFrameTable = Params.bIncludeKeyTable && (RotationData.RotKeys.Num() > 1) && (RotationData.RotKeys.Num() < Params.AnimSeq->GetRawNumberOfFrames()); + bReallyNeedsFrameTable = Params.bIncludeKeyTable && (RotationData.RotKeys.Num() > 1) && (RotationData.RotKeys.Num() < Params.NumFrames); switch (InCompressionType) { @@ -893,7 +895,7 @@ public: FPerTrackCompressor(int32 InCompressionType, const FScaleTrack& ScaleData, const FPerTrackParams& Params) { Reset(); - bReallyNeedsFrameTable = Params.bIncludeKeyTable && (ScaleData.ScaleKeys.Num() > 1) && (ScaleData.ScaleKeys.Num() < Params.AnimSeq->GetRawNumberOfFrames()); + bReallyNeedsFrameTable = Params.bIncludeKeyTable && (ScaleData.ScaleKeys.Num() > 1) && (ScaleData.ScaleKeys.Num() < Params.NumFrames); switch (InCompressionType) { @@ -963,8 +965,8 @@ UAnimCompress_PerTrackCompression::UAnimCompress_PerTrackCompression(const FObje #if WITH_EDITOR void UAnimCompress_PerTrackCompression::CompressUsingUnderlyingCompressor( - UAnimSequence* AnimSeq, - const TArray& BoneData, + const FCompressibleAnimData& CompressibleAnimData, + FCompressibleAnimDataResult& OutCompressedData, const TArray& TranslationData, const TArray& RotationData, const TArray& ScaleData, @@ -975,8 +977,8 @@ void UAnimCompress_PerTrackCompression::CompressUsingUnderlyingCompressor( if( !bFinalPass ) { UAnimCompress_RemoveLinearKeys::CompressUsingUnderlyingCompressor( - AnimSeq, - BoneData, + CompressibleAnimData, + OutCompressedData, TranslationData, RotationData, ScaleData, @@ -989,35 +991,36 @@ void UAnimCompress_PerTrackCompression::CompressUsingUnderlyingCompressor( FPerTrackCachedInfo* Cache = (FPerTrackCachedInfo*)PerReductionCachedData; // record the proper runtime decompressor to use - AnimSeq->KeyEncodingFormat = AKF_PerTrackCompression; - AnimSeq->RotationCompressionFormat = ACF_Identity; - AnimSeq->TranslationCompressionFormat = ACF_Identity; - AnimSeq->ScaleCompressionFormat = ACF_Identity; - AnimationFormat_SetInterfaceLinks(*AnimSeq); + OutCompressedData.KeyEncodingFormat = AKF_PerTrackCompression; + OutCompressedData.RotationCompressionFormat = ACF_Identity; + OutCompressedData.TranslationCompressionFormat = ACF_Identity; + OutCompressedData.ScaleCompressionFormat = ACF_Identity; + AnimationFormat_SetInterfaceLinks(OutCompressedData); // Prime the compression buffers check(TranslationData.Num() == RotationData.Num()); const int32 NumTracks = TranslationData.Num(); const bool bHasScale = ScaleData.Num() > 0; - AnimSeq->CompressedTrackOffsets.Empty(NumTracks*2); - AnimSeq->CompressedTrackOffsets.AddUninitialized(NumTracks*2); - AnimSeq->CompressedScaleOffsets.Empty(0); + OutCompressedData.CompressedTrackOffsets.Empty(NumTracks*2); + OutCompressedData.CompressedTrackOffsets.AddUninitialized(NumTracks*2); + OutCompressedData.CompressedScaleOffsets.Empty(0); if ( bHasScale ) { - AnimSeq->CompressedScaleOffsets.SetStripSize(1); - AnimSeq->CompressedScaleOffsets.AddUninitialized(NumTracks); + OutCompressedData.CompressedScaleOffsets.SetStripSize(1); + OutCompressedData.CompressedScaleOffsets.AddUninitialized(NumTracks); } - AnimSeq->CompressedByteStream.Empty(); + OutCompressedData.CompressedByteStream.Empty(); // Compress each track independently for (int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex) { // Compression parameters / thresholds FPerTrackParams Params; - Params.AnimSeq = AnimSeq; + Params.NumFrames = CompressibleAnimData.NumFrames; + Params.SequenceLength = CompressibleAnimData.SequenceLength; Params.MaxZeroingThreshold = MaxZeroingThreshold; // Determine the local-space error cutoffs @@ -1070,7 +1073,7 @@ void UAnimCompress_PerTrackCompression::CompressUsingUnderlyingCompressor( // Start compressing translation using a totally lossless float32x3 const FTranslationTrack& TranslationTrack = TranslationData[TrackIndex]; - Params.bIncludeKeyTable = bActuallyFilterLinearKeys && !FAnimationUtils::HasUniformKeySpacing(AnimSeq, TranslationTrack.Times); + Params.bIncludeKeyTable = bActuallyFilterLinearKeys && !FAnimationUtils::HasUniformKeySpacing(CompressibleAnimData.NumFrames, TranslationTrack.Times); FPerTrackCompressor BestTranslation(ACF_Float96NoW, TranslationTrack, Params); // Try the other translation formats @@ -1094,7 +1097,7 @@ void UAnimCompress_PerTrackCompression::CompressUsingUnderlyingCompressor( // Start compressing rotation, first using lossless float32x3 const FRotationTrack& RotationTrack = RotationData[TrackIndex]; - Params.bIncludeKeyTable = bActuallyFilterLinearKeys && !FAnimationUtils::HasUniformKeySpacing(AnimSeq, RotationTrack.Times); + Params.bIncludeKeyTable = bActuallyFilterLinearKeys && !FAnimationUtils::HasUniformKeySpacing(CompressibleAnimData.NumFrames, RotationTrack.Times); FPerTrackCompressor BestRotation(ACF_Float96NoW, RotationTrack, Params); //bool bLeaveRotationUncompressed = (RotationTrack.Times.Num() <= 1) && (GHighQualityEmptyTracks != 0); @@ -1124,7 +1127,7 @@ void UAnimCompress_PerTrackCompression::CompressUsingUnderlyingCompressor( { const FScaleTrack& ScaleTrack = ScaleData[TrackIndex]; - Params.bIncludeKeyTable = bActuallyFilterLinearKeys && !FAnimationUtils::HasUniformKeySpacing(AnimSeq, ScaleTrack.Times); + Params.bIncludeKeyTable = bActuallyFilterLinearKeys && !FAnimationUtils::HasUniformKeySpacing(CompressibleAnimData.NumFrames, ScaleTrack.Times); FPerTrackCompressor BestScale(ACF_Float96NoW, ScaleTrack, Params); //bool bLeaveScaleUncompressed = (ScaleTrack.Times.Num() <= 1) && (GHighQualityEmptyTracks != 0); @@ -1153,10 +1156,10 @@ void UAnimCompress_PerTrackCompression::CompressUsingUnderlyingCompressor( if (BestScale.CompressedBytes.Num() > 0) { check(BestScale.ActualCompressionMode < ACF_MAX); - ScaleOffset = AnimSeq->CompressedByteStream.Num(); - AnimSeq->CompressedByteStream.Append(BestScale.CompressedBytes); + ScaleOffset = OutCompressedData.CompressedByteStream.Num(); + OutCompressedData.CompressedByteStream.Append(BestScale.CompressedBytes); } - AnimSeq->CompressedScaleOffsets.SetOffsetData(TrackIndex, 0, ScaleOffset); + OutCompressedData.CompressedScaleOffsets.SetOffsetData(TrackIndex, 0, ScaleOffset); } // Now write out compression and translation frames into the stream @@ -1164,19 +1167,19 @@ void UAnimCompress_PerTrackCompression::CompressUsingUnderlyingCompressor( if (BestTranslation.CompressedBytes.Num() > 0 ) { check(BestTranslation.ActualCompressionMode < ACF_MAX); - TranslationOffset = AnimSeq->CompressedByteStream.Num(); - AnimSeq->CompressedByteStream.Append(BestTranslation.CompressedBytes); + TranslationOffset = OutCompressedData.CompressedByteStream.Num(); + OutCompressedData.CompressedByteStream.Append(BestTranslation.CompressedBytes); } - AnimSeq->CompressedTrackOffsets[TrackIndex*2 + 0] = TranslationOffset; + OutCompressedData.CompressedTrackOffsets[TrackIndex*2 + 0] = TranslationOffset; int32 RotationOffset = INDEX_NONE; if (BestRotation.CompressedBytes.Num() > 0) { check(BestRotation.ActualCompressionMode < ACF_MAX); - RotationOffset = AnimSeq->CompressedByteStream.Num(); - AnimSeq->CompressedByteStream.Append(BestRotation.CompressedBytes); + RotationOffset = OutCompressedData.CompressedByteStream.Num(); + OutCompressedData.CompressedByteStream.Append(BestRotation.CompressedBytes); } - AnimSeq->CompressedTrackOffsets[TrackIndex*2 + 1] = RotationOffset; + OutCompressedData.CompressedTrackOffsets[TrackIndex*2 + 1] = RotationOffset; #if 0 // This block outputs information about each individual track during compression, which is useful for debugging the compressors @@ -1407,6 +1410,7 @@ void UAnimCompress_PerTrackCompression::PackScaleKey(TArray& ByteStream, } } +#if USE_SEGMENTING_CONTEXT /** * Structure that holds the necessary information for performing the per track compression. * Each segment will have its own instance. Each instance is independent. @@ -1546,7 +1550,8 @@ void UAnimCompress_PerTrackCompression::OptimizeSegmentTracks(FOptimizeSegmentTr { // Compression parameters / thresholds FPerTrackParams Params; - Params.AnimSeq = &Context.AnimSeq; + Params.NumFrames = Context.AnimSeq.GetRawNumberOfFrames(); + Params.SequenceLength = Context.AnimSeq.SequenceLength; Params.MaxZeroingThreshold = MaxZeroingThreshold; FPerTrackFormat& TrackFormats = BestTrackFormats[TrackIndex]; @@ -1879,6 +1884,7 @@ void UAnimCompress_PerTrackCompression::CompressUsingUnderlyingCompressor( CoalesceCompressedSegments(AnimSeq, RawSegments, bOptimizeForForwardPlayback); } +#endif /** Resamples a track of position keys */ void ResamplePositionKeys( @@ -2123,8 +2129,7 @@ void ResampleKeys( void UAnimCompress_PerTrackCompression::FilterBeforeMainKeyRemoval( - UAnimSequence* AnimSeq, - const TArray& BoneData, + const FCompressibleAnimData& CompressibleAnimData, TArray& TranslationData, TArray& RotationData, TArray& ScaleData) @@ -2132,12 +2137,12 @@ void UAnimCompress_PerTrackCompression::FilterBeforeMainKeyRemoval( const int32 NumTracks = TranslationData.Num(); // Downsample the keys if enabled - if ((AnimSeq->GetRawNumberOfFrames() >= MinKeysForResampling) && bResampleAnimation) + if ((CompressibleAnimData.NumFrames >= MinKeysForResampling) && bResampleAnimation) { - if(AnimSeq->SequenceLength > 0) + if(CompressibleAnimData.SequenceLength > 0) { //Make sure we aren't going to oversample the original animation - const float CurrentFramerate = (AnimSeq->GetRawNumberOfFrames() - 1) / AnimSeq->SequenceLength; + const float CurrentFramerate = (CompressibleAnimData.NumFrames - 1) / CompressibleAnimData.SequenceLength; if (CurrentFramerate > ResampledFramerate) { ResampleKeys(TranslationData, RotationData, ScaleData, 1.0f / ResampledFramerate, 0.0f); @@ -2153,7 +2158,7 @@ void UAnimCompress_PerTrackCompression::FilterBeforeMainKeyRemoval( // Calculate how far each track is from controlling an end effector if (bUseAdaptiveError) { - FAnimationUtils::CalculateTrackHeights(AnimSeq, BoneData, NumTracks, /*OUT*/ Cache->TrackHeights); + FAnimationUtils::CalculateTrackHeights(CompressibleAnimData, NumTracks, /*OUT*/ Cache->TrackHeights); } // Find out how a small change affects the maximum error in the end effectors @@ -2164,9 +2169,8 @@ void UAnimCompress_PerTrackCompression::FilterBeforeMainKeyRemoval( FVector ScaleProbe(PerturbationProbeSize, PerturbationProbeSize, PerturbationProbeSize); FAnimationUtils::TallyErrorsFromPerturbation( - AnimSeq, + CompressibleAnimData, NumTracks, - BoneData, TranslationProbe, RotationProbe, ScaleProbe, @@ -2215,7 +2219,7 @@ void UAnimCompress_PerTrackCompression::PostEditChangeProperty(struct FPropertyC } } -void UAnimCompress_PerTrackCompression::DoReduction(UAnimSequence* AnimSeq, const TArray& BoneData) +void UAnimCompress_PerTrackCompression::DoReduction(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) { if (FPlatformProperties::HasEditorOnlyData()) { @@ -2224,7 +2228,7 @@ void UAnimCompress_PerTrackCompression::DoReduction(UAnimSequence* AnimSeq, cons ensure(!(bUseAdaptiveError2 && bUseAdaptiveError)); // Compress - UAnimCompress_RemoveLinearKeys::DoReduction(AnimSeq, BoneData); + UAnimCompress_RemoveLinearKeys::DoReduction(CompressibleAnimData, OutResult); // Delete the cache if (PerReductionCachedData != NULL) diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_RemoveEverySecondKey.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_RemoveEverySecondKey.cpp index 285ee77d2d47..ae64e73954fb 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_RemoveEverySecondKey.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_RemoveEverySecondKey.cpp @@ -16,7 +16,7 @@ UAnimCompress_RemoveEverySecondKey::UAnimCompress_RemoveEverySecondKey(const FOb } #if WITH_EDITOR -void UAnimCompress_RemoveEverySecondKey::DoReduction(UAnimSequence* AnimSeq, const TArray& BoneData) +void UAnimCompress_RemoveEverySecondKey::DoReduction(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) { #if WITH_EDITORONLY_DATA const int32 StartIndex = bStartAtSecondKey ? 1 : 0; @@ -26,7 +26,7 @@ void UAnimCompress_RemoveEverySecondKey::DoReduction(UAnimSequence* AnimSeq, con TArray TranslationData; TArray RotationData; TArray ScaleData; - SeparateRawDataIntoTracks( AnimSeq->GetRawAnimationData(), AnimSeq->SequenceLength, TranslationData, RotationData, ScaleData ); + SeparateRawDataIntoTracks(CompressibleAnimData.RawAnimationData, CompressibleAnimData.SequenceLength, TranslationData, RotationData, ScaleData ); // remove obviously redundant keys from the source data FilterTrivialKeys(TranslationData, RotationData, ScaleData, TRANSLATION_ZEROING_THRESHOLD, QUATERNION_ZEROING_THRESHOLD, SCALE_ZEROING_THRESHOLD); @@ -35,11 +35,11 @@ void UAnimCompress_RemoveEverySecondKey::DoReduction(UAnimSequence* AnimSeq, con FilterIntermittentKeys(TranslationData, RotationData, StartIndex, Interval); // record the proper runtime decompressor to use - AnimSeq->KeyEncodingFormat = AKF_ConstantKeyLerp; - AnimSeq->RotationCompressionFormat = RotationCompressionFormat; - AnimSeq->TranslationCompressionFormat = TranslationCompressionFormat; - AnimSeq->ScaleCompressionFormat = ScaleCompressionFormat; - AnimationFormat_SetInterfaceLinks(*AnimSeq); + OutResult.KeyEncodingFormat = AKF_ConstantKeyLerp; + OutResult.RotationCompressionFormat = RotationCompressionFormat; + OutResult.TranslationCompressionFormat = TranslationCompressionFormat; + OutResult.ScaleCompressionFormat = ScaleCompressionFormat; + AnimationFormat_SetInterfaceLinks(OutResult); #if USE_SEGMENTING_CONTEXT if (bEnableSegmenting) @@ -65,7 +65,8 @@ void UAnimCompress_RemoveEverySecondKey::DoReduction(UAnimSequence* AnimSeq, con { // bitwise compress the tracks into the anim sequence buffers BitwiseCompressAnimationTracks( - AnimSeq, + CompressibleAnimData, + OutResult, static_cast(TranslationCompressionFormat), static_cast(RotationCompressionFormat), static_cast(ScaleCompressionFormat), @@ -75,7 +76,7 @@ void UAnimCompress_RemoveEverySecondKey::DoReduction(UAnimSequence* AnimSeq, con } // We could be invalid, set the links again - AnimationFormat_SetInterfaceLinks(*AnimSeq); + AnimationFormat_SetInterfaceLinks(OutResult); #endif // WITH_EDITORONLY_DATA } diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_RemoveLinearKeys.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_RemoveLinearKeys.cpp index a4d9cf61aa97..7f18aa52f4d1 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_RemoveLinearKeys.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_RemoveLinearKeys.cpp @@ -68,6 +68,8 @@ UAnimCompress_RemoveLinearKeys::UAnimCompress_RemoveLinearKeys(const FObjectInit } #if WITH_EDITOR + +#if USE_SEGMENTING_CONTEXT /** * Structure that holds the necessary information for performing linear key reduction. * Each segment will have its own instance. Each instance is independent. @@ -106,6 +108,7 @@ struct FProcessAnimationTracksContext , SegmentList(SegmentList_) {} }; +#endif struct RotationAdapter { @@ -341,6 +344,7 @@ void FilterLinearKeysTemplate( } } +#if USE_SEGMENTING_CONTEXT template void FilterLinearKeysTemplate( TArray& Keys, @@ -546,20 +550,21 @@ void FilterLinearKeysTemplate( Keys = NewKeys; } } +#endif void UAnimCompress_RemoveLinearKeys::UpdateWorldBoneTransformTable( - UAnimSequence* AnimSeq, - const TArray& BoneData, + const FCompressibleAnimData& CompressibleAnimData, + FCompressibleAnimDataResult& OutCompressedData, const TArray& RefPose, int32 BoneIndex, // this bone index should be of skeleton, not mesh bool UseRaw, TArray& OutputWorldBones) { - const FBoneData& Bone = BoneData[BoneIndex]; - const int32 NumFrames = AnimSeq->GetRawNumberOfFrames(); - const float SequenceLength = AnimSeq->SequenceLength; + const FBoneData& Bone = CompressibleAnimData.BoneData[BoneIndex]; + const int32 NumFrames = CompressibleAnimData.NumFrames; + const float SequenceLength = CompressibleAnimData.SequenceLength; const int32 FrameStart = (BoneIndex*NumFrames); - const int32 TrackIndex = AnimSeq->GetSkeleton()->GetAnimationTrackIndex(BoneIndex, AnimSeq, UseRaw); + const int32 TrackIndex = FAnimationUtils::GetAnimTrackIndexForSkeletonBone(BoneIndex, CompressibleAnimData.TrackToSkeletonMapTable); check(OutputWorldBones.Num() >= (FrameStart+NumFrames)); @@ -573,7 +578,7 @@ void UAnimCompress_RemoveLinearKeys::UpdateWorldBoneTransformTable( float Time = (float)FrameIndex * TimePerFrame; FTransform LocalAtom; - AnimSeq->GetBoneTransform(LocalAtom, TrackIndex, Time, UseRaw); + FAnimationUtils::ExtractTransformFromCompressionData(CompressibleAnimData, OutCompressedData, Time, TrackIndex, UseRaw, LocalAtom); FQuat Rot = LocalAtom.GetRotation(); LocalAtom.SetRotation(EnforceShortestArc(FQuat::Identity, Rot)); @@ -781,6 +786,7 @@ static float FindKeyInterpolationData(const UAnimSequence& AnimSeq, const TArray return InterpolationAlpha; } +#if USE_SEGMENTING_CONTEXT FTransform UAnimCompress_RemoveLinearKeys::SampleSegment(const FProcessAnimationTracksContext& Context, int32 TrackIndex, float Time) { FTransform Result; @@ -903,6 +909,56 @@ FTransform UAnimCompress_RemoveLinearKeys::SampleSegment(const FProcessAnimation return Result; } +void UAnimCompress_RemoveLinearKeys::UpdateWorldBoneTransformRange( + FProcessAnimationTracksContext& Context, + int32 StartingBoneIndex, // this bone index should be of skeleton, not mesh + int32 EndingBoneIndex) // this bone index should be of skeleton, not mesh +{ + if (bUseDecompression) + { + // bitwise compress the tracks into the anim sequence buffers + // to make sure the data we've compressed so far is ready for solving + CompressUsingUnderlyingCompressor(Context.AnimSeq, Context.BoneData, Context.SegmentList, false); + } + + // build all world-space transforms from this bone to the target end effector we are monitoring + // all parent transforms have been built already + for (int32 Index = StartingBoneIndex; Index <= EndingBoneIndex; ++Index) + { + UpdateWorldBoneTransformTable(Context, Index, false, Context.NewWorldBones); + } +} + +void UAnimCompress_RemoveLinearKeys::UpdateBoneAtomList( + const FProcessAnimationTracksContext& Context, + int32 TrackIndex, + TArray& BoneAtoms) const +{ + const float FrameRate = GetFrameRate(Context.AnimSeq); + FAnimSequenceDecompressionContext DecompContext(&Context.AnimSeq); + + BoneAtoms.Reset(Context.Segment.NumFrames); + for (int32 FrameIndex = 0; FrameIndex < Context.Segment.NumFrames; ++FrameIndex) + { + const float Time = (float)(FrameIndex + Context.Segment.StartFrame) / FrameRate; + + FTransform LocalAtom; + if (bUseDecompression) + { + DecompContext.Seek(Time); + Context.AnimSeq.GetBoneTransform(LocalAtom, TrackIndex, DecompContext, false); + } + else + { + LocalAtom = SampleSegment(Context, TrackIndex, Time); + } + + FQuat Rot = LocalAtom.GetRotation(); + LocalAtom.SetRotation(EnforceShortestArc(FQuat::Identity, Rot)); + BoneAtoms.Add(LocalAtom); + } +} + void UAnimCompress_RemoveLinearKeys::UpdateWorldBoneTransformTable( const FProcessAnimationTracksContext& Context, int32 BoneIndex, @@ -975,10 +1031,10 @@ void UAnimCompress_RemoveLinearKeys::UpdateWorldBoneTransformTable( } } } +#endif void UAnimCompress_RemoveLinearKeys::FilterBeforeMainKeyRemoval( - UAnimSequence* AnimSeq, - const TArray& BoneData, + const FCompressibleAnimData& CompressibleAnimData, TArray& TranslationData, TArray& RotationData, TArray& ScaleData) @@ -990,8 +1046,8 @@ void UAnimCompress_RemoveLinearKeys::FilterBeforeMainKeyRemoval( void UAnimCompress_RemoveLinearKeys::UpdateWorldBoneTransformRange( - UAnimSequence* AnimSeq, - const TArray& BoneData, + const FCompressibleAnimData& CompressibleAnimData, + FCompressibleAnimDataResult& OutCompressedData, const TArray& RefPose, const TArray& PositionTracks, const TArray& RotationTracks, @@ -1004,8 +1060,8 @@ void UAnimCompress_RemoveLinearKeys::UpdateWorldBoneTransformRange( // bitwise compress the tracks into the anim sequence buffers // to make sure the data we've compressed so far is ready for solving CompressUsingUnderlyingCompressor( - AnimSeq, - BoneData, + CompressibleAnimData, + OutCompressedData, PositionTracks, RotationTracks, ScaleTracks, @@ -1016,8 +1072,8 @@ void UAnimCompress_RemoveLinearKeys::UpdateWorldBoneTransformRange( for ( int32 Index = StartingBoneIndex; Index <= EndingBoneIndex; ++Index ) { UpdateWorldBoneTransformTable( - AnimSeq, - BoneData, + CompressibleAnimData, + OutCompressedData, RefPose, Index, UseRaw, @@ -1025,30 +1081,9 @@ void UAnimCompress_RemoveLinearKeys::UpdateWorldBoneTransformRange( } } - -void UAnimCompress_RemoveLinearKeys::UpdateWorldBoneTransformRange( - FProcessAnimationTracksContext& Context, - int32 StartingBoneIndex, // this bone index should be of skeleton, not mesh - int32 EndingBoneIndex) // this bone index should be of skeleton, not mesh -{ - if (bUseDecompression) - { - // bitwise compress the tracks into the anim sequence buffers - // to make sure the data we've compressed so far is ready for solving - CompressUsingUnderlyingCompressor(Context.AnimSeq, Context.BoneData, Context.SegmentList, false); - } - - // build all world-space transforms from this bone to the target end effector we are monitoring - // all parent transforms have been built already - for (int32 Index = StartingBoneIndex; Index <= EndingBoneIndex; ++Index) - { - UpdateWorldBoneTransformTable(Context, Index, false, Context.NewWorldBones); - } -} - - void UAnimCompress_RemoveLinearKeys::UpdateBoneAtomList( - UAnimSequence* AnimSeq, + const FCompressibleAnimData& CompressibleAnimData, + FCompressibleAnimDataResult& OutCompressedData, int32 BoneIndex, int32 TrackIndex, int32 NumFrames, @@ -1060,7 +1095,7 @@ void UAnimCompress_RemoveLinearKeys::UpdateBoneAtomList( { float Time = (float)FrameIndex * TimePerFrame; FTransform LocalAtom; - AnimSeq->GetBoneTransform(LocalAtom, TrackIndex, Time, false); + FAnimationUtils::ExtractTransformFromCompressionData(CompressibleAnimData, OutCompressedData, Time, TrackIndex, false, LocalAtom); FQuat Rot = LocalAtom.GetRotation(); LocalAtom.SetRotation( EnforceShortestArc(FQuat::Identity, Rot) ); @@ -1068,107 +1103,70 @@ void UAnimCompress_RemoveLinearKeys::UpdateBoneAtomList( } } - -void UAnimCompress_RemoveLinearKeys::UpdateBoneAtomList( - const FProcessAnimationTracksContext& Context, - int32 TrackIndex, - TArray& BoneAtoms) const -{ - const float FrameRate = GetFrameRate(Context.AnimSeq); - FAnimSequenceDecompressionContext DecompContext(&Context.AnimSeq); - - BoneAtoms.Reset(Context.Segment.NumFrames); - for (int32 FrameIndex = 0; FrameIndex < Context.Segment.NumFrames; ++FrameIndex) - { - const float Time = (float)(FrameIndex + Context.Segment.StartFrame) / FrameRate; - - FTransform LocalAtom; - if (bUseDecompression) - { - DecompContext.Seek(Time); - Context.AnimSeq.GetBoneTransform(LocalAtom, TrackIndex, DecompContext, false); - } - else - { - LocalAtom = SampleSegment(Context, TrackIndex, Time); - } - - FQuat Rot = LocalAtom.GetRotation(); - LocalAtom.SetRotation(EnforceShortestArc(FQuat::Identity, Rot)); - BoneAtoms.Add(LocalAtom); - } -} - - -bool UAnimCompress_RemoveLinearKeys::ConvertFromRelativeSpace(UAnimSequence* AnimSeq) +void UAnimCompress_RemoveLinearKeys::ConvertFromRelativeSpace(FCompressibleAnimData& CompressibleAnimData) { // if this is an additive animation, temporarily convert it out of relative-space - const bool bAdditiveAnimation = AnimSeq->IsValidAdditive(); - if (bAdditiveAnimation) + check(CompressibleAnimData.bIsValidAdditive); + // convert the raw tracks out of additive-space + const int32 NumTracks = CompressibleAnimData.RawAnimationData.Num(); + for (int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex) { - // convert the raw tracks out of additive-space - const int32 NumTracks = AnimSeq->GetRawAnimationData().Num(); - for (int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex) + // bone index of skeleton + int32 const BoneIndex = CompressibleAnimData.TrackToSkeletonMapTable[TrackIndex].BoneTreeIndex; + bool const bIsRootBone = (BoneIndex == 0); + + const FRawAnimSequenceTrack& BasePoseTrack = CompressibleAnimData.AdditiveBaseAnimationData[TrackIndex]; + FRawAnimSequenceTrack& RawTrack = CompressibleAnimData.RawAnimationData[TrackIndex]; + + // @note: we only extract the first frame, as we don't want to induce motion from the base pose + // only the motion from the additive data should matter. + const FVector& RefBonePos = BasePoseTrack.PosKeys[0]; + const FQuat& RefBoneRotation = BasePoseTrack.RotKeys[0]; + + // Transform position keys. + for (int32 PosIndex = 0; PosIndex < RawTrack.PosKeys.Num(); ++PosIndex) { - // bone index of skeleton - int32 const BoneIndex = AnimSeq->GetRawTrackToSkeletonMapTable()[TrackIndex].BoneTreeIndex; - bool const bIsRootBone = (BoneIndex == 0); + RawTrack.PosKeys[PosIndex] += RefBonePos; + } - const FRawAnimSequenceTrack& BasePoseTrack = AnimSeq->GetAdditiveBaseAnimationData()[TrackIndex]; - FRawAnimSequenceTrack& RawTrack = AnimSeq->GetRawAnimationTrack(TrackIndex); + // Transform rotation keys. + for (int32 RotIndex = 0; RotIndex < RawTrack.RotKeys.Num(); ++RotIndex) + { + RawTrack.RotKeys[RotIndex] = RawTrack.RotKeys[RotIndex] * RefBoneRotation; + RawTrack.RotKeys[RotIndex].Normalize(); + } - // @note: we only extract the first frame, as we don't want to induce motion from the base pose - // only the motion from the additive data should matter. - const FVector& RefBonePos = BasePoseTrack.PosKeys[0]; - const FQuat& RefBoneRotation = BasePoseTrack.RotKeys[0]; - - // Transform position keys. - for (int32 PosIndex = 0; PosIndex < RawTrack.PosKeys.Num(); ++PosIndex) + // make sure scale key exists + if (RawTrack.ScaleKeys.Num() > 0) + { + const FVector DefaultScale(1.f); + const FVector& RefBoneScale = (BasePoseTrack.ScaleKeys.Num() > 0)? BasePoseTrack.ScaleKeys[0] : DefaultScale; + for (int32 ScaleIndex = 0; ScaleIndex < RawTrack.ScaleKeys.Num(); ++ScaleIndex) { - RawTrack.PosKeys[PosIndex] += RefBonePos; - } - - // Transform rotation keys. - for (int32 RotIndex = 0; RotIndex < RawTrack.RotKeys.Num(); ++RotIndex) - { - RawTrack.RotKeys[RotIndex] = RawTrack.RotKeys[RotIndex] * RefBoneRotation; - RawTrack.RotKeys[RotIndex].Normalize(); - } - - // make sure scale key exists - if (RawTrack.ScaleKeys.Num() > 0) - { - const FVector DefaultScale(1.f); - const FVector& RefBoneScale = (BasePoseTrack.ScaleKeys.Num() > 0)? BasePoseTrack.ScaleKeys[0] : DefaultScale; - for (int32 ScaleIndex = 0; ScaleIndex < RawTrack.ScaleKeys.Num(); ++ScaleIndex) - { - RawTrack.ScaleKeys[ScaleIndex] = RefBoneScale * (DefaultScale + RawTrack.ScaleKeys[ScaleIndex]); - } + RawTrack.ScaleKeys[ScaleIndex] = RefBoneScale * (DefaultScale + RawTrack.ScaleKeys[ScaleIndex]); } } } - - return bAdditiveAnimation; } -void UAnimCompress_RemoveLinearKeys::ConvertToRelativeSpace( - UAnimSequence* AnimSeq, +void UAnimCompress_RemoveLinearKeys::ConvertToRelativeSpaceBoth( + FCompressibleAnimData& CompressibleAnimData, TArray& TranslationData, TArray& RotationData, TArray& ScaleData) { - ConvertToRelativeSpace(*AnimSeq); - ConvertToRelativeSpace(*AnimSeq, TranslationData, RotationData, ScaleData); + ConvertToRelativeSpace(CompressibleAnimData); + ConvertToRelativeSpace(CompressibleAnimData, TranslationData, RotationData, ScaleData); } -void UAnimCompress_RemoveLinearKeys::ConvertToRelativeSpace(UAnimSequence& AnimSeq) const +void UAnimCompress_RemoveLinearKeys::ConvertToRelativeSpace(FCompressibleAnimData& CompressibleAnimData) const { // convert the raw tracks back to additive-space - const int32 NumTracks = AnimSeq.GetRawAnimationData().Num(); + const int32 NumTracks = CompressibleAnimData.RawAnimationData.Num(); for (int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex) { - const FRawAnimSequenceTrack& BasePoseTrack = AnimSeq.GetAdditiveBaseAnimationData()[TrackIndex]; - FRawAnimSequenceTrack& RawTrack = AnimSeq.GetRawAnimationTrack(TrackIndex); + const FRawAnimSequenceTrack& BasePoseTrack = CompressibleAnimData.AdditiveBaseAnimationData[TrackIndex]; + FRawAnimSequenceTrack& RawTrack = CompressibleAnimData.RawAnimationData[TrackIndex]; // @note: we only extract the first frame, as we don't want to induce motion from the base pose // only the motion from the additive data should matter. @@ -1205,16 +1203,16 @@ void UAnimCompress_RemoveLinearKeys::ConvertToRelativeSpace(UAnimSequence& AnimS } void UAnimCompress_RemoveLinearKeys::ConvertToRelativeSpace( - const UAnimSequence& AnimSeq, + const FCompressibleAnimData& CompressibleAnimData, TArray& TranslationData, TArray& RotationData, TArray& ScaleData) const { // convert the raw tracks back to additive-space - const int32 NumTracks = AnimSeq.GetRawAnimationData().Num(); + const int32 NumTracks = CompressibleAnimData.RawAnimationData.Num(); for (int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex) { - const FRawAnimSequenceTrack& BasePoseTrack = AnimSeq.GetAdditiveBaseAnimationData()[TrackIndex]; + const FRawAnimSequenceTrack& BasePoseTrack = CompressibleAnimData.AdditiveBaseAnimationData[TrackIndex]; // @note: we only extract the first frame, as we don't want to induce motion from the base pose // only the motion from the additive data should matter. @@ -1252,21 +1250,21 @@ void UAnimCompress_RemoveLinearKeys::ConvertToRelativeSpace( } void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( - UAnimSequence* AnimSeq, - const TArray& BoneData, + const FCompressibleAnimData& CompressibleAnimData, + FCompressibleAnimDataResult& OutCompressedData, TArray& PositionTracks, TArray& RotationTracks, TArray& ScaleTracks) { // extract all the data we'll need about the skeleton and animation sequence - const int32 NumBones = BoneData.Num(); - const int32 NumFrames = AnimSeq->GetRawNumberOfFrames(); - const float SequenceLength = AnimSeq->SequenceLength; + const int32 NumBones = CompressibleAnimData.BoneData.Num(); + const int32 NumFrames = CompressibleAnimData.NumFrames; + const float SequenceLength = CompressibleAnimData.SequenceLength; const int32 LastFrame = NumFrames-1; const float FrameRate = (float)(LastFrame) / SequenceLength; const float TimePerFrame = SequenceLength / (float)(LastFrame); - const TArray& RefPose = AnimSeq->GetSkeleton()->GetRefLocalPoses(); + const TArray& RefPose = CompressibleAnimData.Skeleton->GetRefLocalPoses(); const bool bHasScale = (ScaleTracks.Num() > 0); // make sure the parent key scale is properly bound to 1.0 or more @@ -1292,15 +1290,15 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( { // get the raw world-atoms for this bone UpdateWorldBoneTransformTable( - AnimSeq, - BoneData, + CompressibleAnimData, + OutCompressedData, RefPose, BoneIndex, true, RawWorldBones); // also record all end-effectors we find - const FBoneData& Bone = BoneData[BoneIndex]; + const FBoneData& Bone = CompressibleAnimData.BoneData[BoneIndex]; if (Bone.IsEndEffector()) { EndEffectors.Add(BoneIndex); @@ -1311,10 +1309,10 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( // for each bone... for ( int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex ) { - const FBoneData& Bone = BoneData[BoneIndex]; + const FBoneData& Bone = CompressibleAnimData.BoneData[BoneIndex]; const int32 ParentBoneIndex = Bone.GetParent(); - const int32 TrackIndex = AnimSeq->GetSkeleton()->GetAnimationTrackIndex(BoneIndex, AnimSeq, true); + const int32 TrackIndex = FAnimationUtils::GetAnimTrackIndexForSkeletonBone(BoneIndex, CompressibleAnimData.TrackToSkeletonMapTable); if (TrackIndex != INDEX_NONE) { @@ -1337,7 +1335,7 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( for (int32 EffectorIndex=0; EffectorIndex < EndEffectors.Num(); ++EffectorIndex) { const int32 EffectorBoneIndex = EndEffectors[EffectorIndex]; - const FBoneData& EffectorBoneData = BoneData[EffectorBoneIndex]; + const FBoneData& EffectorBoneData = CompressibleAnimData.BoneData[EffectorBoneIndex]; int32 RootIndex = EffectorBoneData.BonesToRoot.Find(BoneIndex); if (RootIndex != INDEX_NONE) @@ -1371,8 +1369,8 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( { // update our bone table from the current bone through the last end effector we need to test UpdateWorldBoneTransformRange( - AnimSeq, - BoneData, + CompressibleAnimData, + OutCompressedData, RefPose, PositionTracks, RotationTracks, @@ -1381,7 +1379,7 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( HighestTargetBoneIndex, false, NewWorldBones); - + FScaleTrack& ScaleTrack = ScaleTracks[TrackIndex]; // adjust all translation keys to align better with the destination @@ -1422,8 +1420,8 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( { // update our bone table from the current bone through the last end effector we need to test UpdateWorldBoneTransformRange( - AnimSeq, - BoneData, + CompressibleAnimData, + OutCompressedData, RefPose, PositionTracks, RotationTracks, @@ -1480,8 +1478,8 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( { // update our bone table from the current bone through the last end effector we need to test UpdateWorldBoneTransformRange( - AnimSeq, - BoneData, + CompressibleAnimData, + OutCompressedData, RefPose, PositionTracks, RotationTracks, @@ -1490,7 +1488,7 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( HighestTargetBoneIndex, false, NewWorldBones); - + // adjust all translation keys to align better with the destination for ( int32 KeyIndex = 0; KeyIndex < NumPosKeys; ++KeyIndex ) { @@ -1517,14 +1515,14 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( { const int32 NextParentBoneIndex= Bone.BonesToRoot[FamilyIndex]; - GuideTrackIndex = AnimSeq->GetSkeleton()->GetAnimationTrackIndex(NextParentBoneIndex, AnimSeq, true); + GuideTrackIndex = FAnimationUtils::GetAnimTrackIndexForSkeletonBone(NextParentBoneIndex, CompressibleAnimData.TrackToSkeletonMapTable); } } // update our bone table from the current bone through the last end effector we need to test UpdateWorldBoneTransformRange( - AnimSeq, - BoneData, + CompressibleAnimData, + OutCompressedData, RefPose, PositionTracks, RotationTracks, @@ -1533,9 +1531,9 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( HighestTargetBoneIndex, false, NewWorldBones); - + // rebuild the BoneAtoms table using the current set of keys - UpdateBoneAtomList(AnimSeq, BoneIndex, TrackIndex, NumFrames, TimePerFrame, BoneAtoms); + UpdateBoneAtomList(CompressibleAnimData, OutCompressedData, BoneIndex, TrackIndex, NumFrames, TimePerFrame, BoneAtoms); // determine the EndEffectorTolerance. // We use the Maximum value by default, and the Minimum value @@ -1584,12 +1582,12 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( MaxScaleDiff, EndEffectorTolerance, EffectorDiffSocket, - BoneData); + CompressibleAnimData.BoneData); // update our bone table from the current bone through the last end effector we need to test UpdateWorldBoneTransformRange( - AnimSeq, - BoneData, + CompressibleAnimData, + OutCompressedData, RefPose, PositionTracks, RotationTracks, @@ -1598,9 +1596,9 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( HighestTargetBoneIndex, false, NewWorldBones); - + // rebuild the BoneAtoms table using the current set of keys - UpdateBoneAtomList(AnimSeq, BoneIndex, TrackIndex, NumFrames, TimePerFrame, BoneAtoms); + UpdateBoneAtomList(CompressibleAnimData, OutCompressedData, BoneIndex, TrackIndex, NumFrames, TimePerFrame, BoneAtoms); } // filter out translations we can approximate through interpolation @@ -1619,12 +1617,12 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( MaxPosDiff, EndEffectorTolerance, EffectorDiffSocket, - BoneData); + CompressibleAnimData.BoneData); // update our bone table from the current bone through the last end effector we need to test UpdateWorldBoneTransformRange( - AnimSeq, - BoneData, + CompressibleAnimData, + OutCompressedData, RefPose, PositionTracks, RotationTracks, @@ -1633,9 +1631,9 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( HighestTargetBoneIndex, false, NewWorldBones); - + // rebuild the BoneAtoms table using the current set of keys - UpdateBoneAtomList(AnimSeq, BoneIndex, TrackIndex, NumFrames, TimePerFrame, BoneAtoms); + UpdateBoneAtomList(CompressibleAnimData, OutCompressedData, BoneIndex, TrackIndex, NumFrames, TimePerFrame, BoneAtoms); // filter out rotations we can approximate through interpolation FilterLinearKeysTemplate( @@ -1653,14 +1651,14 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( MaxAngleDiff, EndEffectorTolerance, EffectorDiffSocket, - BoneData); + CompressibleAnimData.BoneData); } } // make sure the final compressed keys are repesented in our NewWorldBones table UpdateWorldBoneTransformRange( - AnimSeq, - BoneData, + CompressibleAnimData, + OutCompressedData, RefPose, PositionTracks, RotationTracks, @@ -1672,7 +1670,7 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( } }; - +#if USE_SEGMENTING_CONTEXT static void CalculateBoneChainInformation(const FProcessAnimationTracksContext& Context, int32 BoneIndex, int32& OutHighestTargetBoneIndex, int32& OutFurthestTargetBoneIndex, int32& OutShortestChain, TArray& OutTargetBoneIndices) { const int32 NumBones = Context.BoneData.Num(); @@ -1839,6 +1837,7 @@ void UAnimCompress_RemoveLinearKeys::PerformRetargeting( } } } +#endif static int32 FindGuideTrackIndex(const FBoneData& Bone, const TArray& BoneIndexToTrackIndex, float ParentKeyScale) { @@ -1855,7 +1854,7 @@ static int32 FindGuideTrackIndex(const FBoneData& Bone, const TArray& Bon return GuideTrackIndex; } - +#if USE_SEGMENTING_CONTEXT void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks(FProcessAnimationTracksContext& Context) { const int32 NumBones = Context.BoneData.Num(); @@ -2130,7 +2129,7 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( BoneIndexToTrackIndex.AddUninitialized(NumBones); for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) { - BoneIndexToTrackIndex[BoneIndex] = AnimSeq.GetSkeleton()->GetAnimationTrackIndex(BoneIndex, &AnimSeq, true); + BoneIndexToTrackIndex[BoneIndex] = AnimSeq.GetSkeleton()->GetRawAnimationTrackIndex(BoneIndex, &AnimSeq); } // generate an array to hold the indices of our end effectors @@ -2201,18 +2200,19 @@ void UAnimCompress_RemoveLinearKeys::ProcessAnimationTracks( TGraphTask::CreateTask(&AsyncTaskCompletionEvents, ENamedThreads::AnyThread).ConstructAndDispatchWhenReady(TaskGroupContext); } } - +#endif void UAnimCompress_RemoveLinearKeys::CompressUsingUnderlyingCompressor( - UAnimSequence* AnimSeq, - const TArray& BoneData, + const FCompressibleAnimData& CompressibleAnimData, + FCompressibleAnimDataResult& OutCompressedData, const TArray& TranslationData, const TArray& RotationData, const TArray& ScaleData, const bool bFinalPass) { BitwiseCompressAnimationTracks( - AnimSeq, + CompressibleAnimData, + OutCompressedData, static_cast(TranslationCompressionFormat), static_cast(RotationCompressionFormat), static_cast(ScaleCompressionFormat), @@ -2222,10 +2222,11 @@ void UAnimCompress_RemoveLinearKeys::CompressUsingUnderlyingCompressor( true); // record the proper runtime decompressor to use - AnimSeq->KeyEncodingFormat = AKF_VariableKeyLerp; - AnimationFormat_SetInterfaceLinks(*AnimSeq); + OutCompressedData.KeyEncodingFormat = AKF_VariableKeyLerp; + AnimationFormat_SetInterfaceLinks(OutCompressedData); } +#if USE_SEGMENTING_CONTEXT void UAnimCompress_RemoveLinearKeys::CompressUsingUnderlyingCompressor( UAnimSequence& AnimSeq, const TArray& BoneData, @@ -2254,8 +2255,52 @@ void UAnimCompress_RemoveLinearKeys::CompressUsingUnderlyingCompressor( // We could be invalid, set the links again AnimationFormat_SetInterfaceLinks(AnimSeq); } +#endif -void UAnimCompress_RemoveLinearKeys::DoReduction(UAnimSequence* AnimSeq, const TArray& BoneData) +#define DO_RAW_DATA_DEBUG_LOGGING 0 + +#if DO_RAW_DATA_DEBUG_LOGGING +TArray& GetKeysArray(const FTranslationTrack& Track) +{ + return Track.PosKeys; +} + +TArray& GetKeysArray(const FRotationTrack& Track) +{ + return Track.RotKeys; +} + +TArray& GetKeysArray(const FScaleTrack& Track) +{ + return Track.ScaleKeys; +} + +template +void DebugLogTrack(const TArray& Data, int32 Track, const TCHAR* TrackName) +{ + if (Data.IsValidIndex(Track)) + { + const ArrayType& Item = Data[Track]; + FPlatformMisc::LowLevelOutputDebugStringf(TEXT("%s : %i\n"), TrackName, Track); + DebugLogArray(GetKeysArray(Item)); + } +} + +void DebugLogTrackData(const TArray& TranslationData, const TArray& RotationData, const TArray& ScaleData, const TCHAR* Section) +{ + FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Post Section: %s\n"), Section); + const int32 ArrayMax = FMath::Max(FMath::Max(TranslationData.Num(), RotationData.Num()), ScaleData.Num()); + + for (int i = 0; i < ArrayMax; ++i) + { + DebugLogTrack(TranslationData, i, TEXT("Translation")); + DebugLogTrack(RotationData, i, TEXT("Rotation")); + DebugLogTrack(ScaleData, i, TEXT("Scale")); + } +} +#endif + +void UAnimCompress_RemoveLinearKeys::DoReduction(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) { #if WITH_EDITORONLY_DATA // Only need to do the heavy lifting if it will have some impact @@ -2263,14 +2308,25 @@ void UAnimCompress_RemoveLinearKeys::DoReduction(UAnimSequence* AnimSeq, const T const bool bRunningProcessor = bRetarget || bActuallyFilterLinearKeys; // If the processor is to be run, then additive animations need to be converted from relative to absolute - const bool bNeedToConvertBackToAdditive = bRunningProcessor ? ConvertFromRelativeSpace(AnimSeq) : false; + const bool bNeedToConvertBackToAdditive = bRunningProcessor ? CompressibleAnimData.bIsValidAdditive : false; + + FCompressibleAnimData TempConvertedAnimData; + + if (bNeedToConvertBackToAdditive) + { + TempConvertedAnimData = CompressibleAnimData; // duplicate so we can safely convert to additive + ConvertFromRelativeSpace(TempConvertedAnimData); + } + + const FCompressibleAnimData& CompressibleDataToOperateOn = bNeedToConvertBackToAdditive ? TempConvertedAnimData : CompressibleAnimData; // Separate the raw data into tracks and remove trivial tracks (all the same value) TArray TranslationData; TArray RotationData; TArray ScaleData; - SeparateRawDataIntoTracks(AnimSeq->GetRawAnimationData(), AnimSeq->SequenceLength, TranslationData, RotationData, ScaleData); - FilterBeforeMainKeyRemoval(AnimSeq, BoneData, TranslationData, RotationData, ScaleData); + SeparateRawDataIntoTracks(CompressibleDataToOperateOn.RawAnimationData, CompressibleDataToOperateOn.SequenceLength, TranslationData, RotationData, ScaleData); + + FilterBeforeMainKeyRemoval(CompressibleDataToOperateOn, TranslationData, RotationData, ScaleData); #if USE_SEGMENTING_CONTEXT if (bEnableSegmenting) @@ -2325,17 +2381,6 @@ void UAnimCompress_RemoveLinearKeys::DoReduction(UAnimSequence* AnimSeq, const T double ElapsedTime = FPlatformTime::Seconds() - TimeStart; UE_LOG(LogAnimationCompression, Log, TEXT("ProcessAnimationTracks time is (%f) seconds"), ElapsedTime); #endif - - // if previously additive, convert back to relative-space - if (bNeedToConvertBackToAdditive) - { - ConvertToRelativeSpace(*AnimSeq); - - for (FAnimSegmentContext& Segment : RawSegments) - { - ConvertToRelativeSpace(*AnimSeq, Segment.TranslationData, Segment.RotationData, Segment.ScaleData); - } - } } // compress the final (possibly key-reduced) tracks into the anim sequence buffers @@ -2355,8 +2400,8 @@ void UAnimCompress_RemoveLinearKeys::DoReduction(UAnimSequence* AnimSeq, const T #endif // compress this animation without any key-reduction to prime the codec CompressUsingUnderlyingCompressor( - AnimSeq, - BoneData, + CompressibleDataToOperateOn, + OutResult, TranslationData, RotationData, ScaleData, @@ -2364,27 +2409,27 @@ void UAnimCompress_RemoveLinearKeys::DoReduction(UAnimSequence* AnimSeq, const T // now remove the keys which can be approximated with linear interpolation ProcessAnimationTracks( - AnimSeq, - BoneData, + CompressibleDataToOperateOn, + OutResult, TranslationData, RotationData, ScaleData); + #if TIME_LINEAR_KEY_REMOVAL double ElapsedTime = FPlatformTime::Seconds() - TimeStart; UE_LOG(LogAnimationCompression, Log, TEXT("ProcessAnimationTracks time is (%f) seconds"),ElapsedTime); #endif - // if previously additive, convert back to relative-space - if( bNeedToConvertBackToAdditive ) + if (bNeedToConvertBackToAdditive) { - ConvertToRelativeSpace(AnimSeq, TranslationData, RotationData, ScaleData); + ConvertToRelativeSpace(CompressibleDataToOperateOn, TranslationData, RotationData, ScaleData); } } // compress the final (possibly key-reduced) tracks into the anim sequence buffers CompressUsingUnderlyingCompressor( - AnimSeq, - BoneData, + CompressibleAnimData, + OutResult, TranslationData, RotationData, ScaleData, diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_RemoveTrivialKeys.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_RemoveTrivialKeys.cpp index 9045498c9368..a323ed0b95e1 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_RemoveTrivialKeys.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompress_RemoveTrivialKeys.cpp @@ -18,21 +18,21 @@ UAnimCompress_RemoveTrivialKeys::UAnimCompress_RemoveTrivialKeys(const FObjectIn } #if WITH_EDITOR -void UAnimCompress_RemoveTrivialKeys::DoReduction(UAnimSequence* AnimSeq, const TArray& BoneData) +void UAnimCompress_RemoveTrivialKeys::DoReduction(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutResult) { #if WITH_EDITORONLY_DATA // split the filtered data into tracks TArray TranslationData; TArray RotationData; TArray ScaleData; - SeparateRawDataIntoTracks( AnimSeq->GetRawAnimationData(), AnimSeq->SequenceLength, TranslationData, RotationData, ScaleData ); + SeparateRawDataIntoTracks( CompressibleAnimData.RawAnimationData, CompressibleAnimData.SequenceLength, TranslationData, RotationData, ScaleData ); // remove obviously redundant keys from the source data FilterTrivialKeys(TranslationData, RotationData, ScaleData, TRANSLATION_ZEROING_THRESHOLD, QUATERNION_ZEROING_THRESHOLD, SCALE_ZEROING_THRESHOLD); // record the proper runtime decompressor to use - AnimSeq->KeyEncodingFormat = AKF_ConstantKeyLerp; - AnimationFormat_SetInterfaceLinks(*AnimSeq); + OutResult.KeyEncodingFormat = AKF_ConstantKeyLerp; + AnimationFormat_SetInterfaceLinks(OutResult); #if USE_SEGMENTING_CONTEXT if (bEnableSegmenting) @@ -58,7 +58,8 @@ void UAnimCompress_RemoveTrivialKeys::DoReduction(UAnimSequence* AnimSeq, const { // bitwise compress the tracks into the anim sequence buffers BitwiseCompressAnimationTracks( - AnimSeq, + CompressibleAnimData, + OutResult, static_cast(TranslationCompressionFormat), static_cast(RotationCompressionFormat), static_cast(ScaleCompressionFormat), @@ -68,7 +69,7 @@ void UAnimCompress_RemoveTrivialKeys::DoReduction(UAnimSequence* AnimSeq, const } // We could be invalid, set the links again - AnimationFormat_SetInterfaceLinks(*AnimSeq); + AnimationFormat_SetInterfaceLinks(OutResult); #endif // WITH_EDITORONLY_DATA } diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.cpp index 90d5a158dab2..86c231439788 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.cpp @@ -72,44 +72,32 @@ void StripFramesOdd(TArray& Keys, const int32 NumFrames) } } -FDerivedDataAnimationCompression::FDerivedDataAnimationCompression(UAnimSequence* InAnimSequence, TSharedPtr InCompressContext, bool bInDoCompressionInPlace, bool bInTryFrameStripping, bool bTryStrippingOnOddFramedAnims) - : OriginalAnimSequence(InAnimSequence) - , DuplicateSequence(nullptr) +FDerivedDataAnimationCompression::FDerivedDataAnimationCompression(const FCompressibleAnimData& InDataToCompress, TSharedPtr InCompressContext, int32 InPreviousCompressedSize, bool bInTryFrameStripping, bool bTryStrippingOnOddFramedAnims) + : DataToCompress(InDataToCompress) , CompressContext(InCompressContext) - , bDoCompressionInPlace(bInDoCompressionInPlace) + , PreviousCompressedSize(InPreviousCompressedSize) { - check(InAnimSequence != nullptr && InAnimSequence->GetSkeleton() != nullptr); - InAnimSequence->AddToRoot(); //Keep this around until we are finished + check(DataToCompress.Skeleton != nullptr); // Can only do stripping on animations that have an even number of frames once the end frame is removed) - bIsEvenFramed = ((OriginalAnimSequence->GetRawNumberOfFrames() - 1) % 2) == 0; + bIsEvenFramed = ((DataToCompress.NumFrames - 1) % 2) == 0; const bool bIsValidForStripping = bIsEvenFramed || bTryStrippingOnOddFramedAnims; - const bool bStripCandidate = (OriginalAnimSequence->GetRawNumberOfFrames() > 10) && bIsValidForStripping; + const bool bStripCandidate = (DataToCompress.NumFrames > 10) && bIsValidForStripping; bPerformStripping = bStripCandidate && bInTryFrameStripping; } FDerivedDataAnimationCompression::~FDerivedDataAnimationCompression() { - OriginalAnimSequence->RemoveFromRoot(); - if (DuplicateSequence) - { - DuplicateSequence->ClearFlags(RF_Standalone | RF_Public); - DuplicateSequence->RemoveFromRoot(); - DuplicateSequence->MarkPendingKill(); - } } FString FDerivedDataAnimationCompression::GetPluginSpecificCacheKeySuffix() const { enum { UE_ANIMCOMPRESSION_DERIVEDDATA_VER = 1 }; - const bool bCanBakeAdditive = OriginalAnimSequence->CanBakeAdditive(); - UAnimSequence* AdditiveBase = OriginalAnimSequence->RefPoseSeq; - //Make up our content key consisting of: - // * Our plugin verison + // * Our plugin version // * Global animation compression version // * Our raw data GUID // * Our skeleton GUID: If our skeleton changes our compressed data may now be stale @@ -118,148 +106,112 @@ FString FDerivedDataAnimationCompression::GetPluginSpecificCacheKeySuffix() cons // * Compression Settings // * Curve compression settings - uint8 AdditiveSettings = bCanBakeAdditive ? (OriginalAnimSequence->RefPoseType << 4) + OriginalAnimSequence->AdditiveAnimType : 0; - - char AdditiveType = bCanBakeAdditive ? NibbleToTChar(OriginalAnimSequence->AdditiveAnimType) : '0'; - char RefType = bCanBakeAdditive ? NibbleToTChar(OriginalAnimSequence->RefPoseType) : '0'; + char AdditiveType = DataToCompress.bIsValidAdditive ? NibbleToTChar(DataToCompress.AdditiveAnimType) : '0'; + char RefType = DataToCompress.bIsValidAdditive ? NibbleToTChar(DataToCompress.RefPoseType) : '0'; const int32 StripFrame = bPerformStripping ? 1 : 0; FString Ret = FString::Printf(TEXT("%i_%i_%i_%i_%s%s%s_%c%c%i_%s_%s_%s"), (int32)UE_ANIMCOMPRESSION_DERIVEDDATA_VER, (int32)CURRENT_ANIMATION_ENCODING_PACKAGE_VERSION, - OriginalAnimSequence->CompressCommandletVersion, + DataToCompress.CompressCommandletVersion, StripFrame, - *OriginalAnimSequence->GetRawDataGuid().ToString(), - *OriginalAnimSequence->GetSkeleton()->GetGuid().ToString(), - *OriginalAnimSequence->GetSkeleton()->GetVirtualBoneGuid().ToString(), + *DataToCompress.RawDataGuid.ToString(), + *DataToCompress.Skeleton->GetGuid().ToString(), + *DataToCompress.Skeleton->GetVirtualBoneGuid().ToString(), AdditiveType, RefType, - OriginalAnimSequence->RefFrameIndex, - (bCanBakeAdditive && AdditiveBase) ? *AdditiveBase->GetRawDataGuid().ToString() : TEXT("NoAdditiveBase"), - *OriginalAnimSequence->CompressionScheme->MakeDDCKey(), - *OriginalAnimSequence->CurveCompressionSettings->MakeDDCKey() + DataToCompress.RefFrameIndex, + (DataToCompress.bIsValidAdditive) ? *DataToCompress.AdditiveDataGuid.ToString() : TEXT("NotAdditive"), + *DataToCompress.RequestedCompressionScheme->MakeDDCKey(), + *DataToCompress.CurveCompressionSettings->MakeDDCKey() ); return Ret; } -bool FDerivedDataAnimationCompression::Build( TArray& OutData ) +bool FDerivedDataAnimationCompression::Build( TArray& OutDataArray ) { + FCompressedAnimSequence OutData; + SCOPE_CYCLE_COUNTER(STAT_AnimCompressionDerivedData); - UE_LOG(LogAnimationCompression, Log, TEXT("Building Anim DDC data for %s"), *OriginalAnimSequence->GetFullName()); - check(OriginalAnimSequence != NULL); + UE_LOG(LogAnimationCompression, Log, TEXT("Building Anim DDC data for %s"), *DataToCompress.FullName); - UAnimSequence* AnimToOperateOn; - - if (!bDoCompressionInPlace) - { - DuplicateSequence = DuplicateObject(OriginalAnimSequence, GetTransientPackage(), OriginalAnimSequence->GetFName()); - DuplicateSequence->AddToRoot(); - AnimToOperateOn = DuplicateSequence; - } - else - { - AnimToOperateOn = OriginalAnimSequence; - } + FCompressibleAnimDataResult CompressionResult; bool bCompressionSuccessful = false; { - FScopedAnimSequenceRawDataCache RawDataCache; - const bool bHasVirtualBones = AnimToOperateOn->GetSkeleton()->GetVirtualBones().Num() > 0; - const bool bNeedToModifyRawData = AnimToOperateOn->CanBakeAdditive() || bHasVirtualBones || bPerformStripping; - if (bDoCompressionInPlace && bNeedToModifyRawData) - { - //Cache original raw data before we mess with it - RawDataCache.InitFrom(AnimToOperateOn); - } - - if (AnimToOperateOn->CanBakeAdditive()) - { - AnimToOperateOn->BakeOutAdditiveIntoRawData(); - } - else if(bHasVirtualBones)// If we aren't additive we must bake virtual bones - { - AnimToOperateOn->BakeOutVirtualBoneTracks(); - } - if (bPerformStripping) { - const int32 NumFrames = AnimToOperateOn->GetRawNumberOfFrames(); - const int32 NumTracks = AnimToOperateOn->GetRawAnimationData().Num(); + const int32 NumFrames = DataToCompress.NumFrames; + const int32 NumTracks = DataToCompress.RawAnimationData.Num(); //Strip every other frame from tracks if(bIsEvenFramed) { - for (int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex) + for (FRawAnimSequenceTrack& Track : DataToCompress.RawAnimationData) { - FRawAnimSequenceTrack& Track = AnimToOperateOn->GetRawAnimationTrack(TrackIndex); - StripFramesEven(Track.PosKeys, NumFrames); StripFramesEven(Track.RotKeys, NumFrames); StripFramesEven(Track.ScaleKeys, NumFrames); } - const int32 ActualFrames = AnimToOperateOn->GetRawNumberOfFrames() - 1; // strip bookmark end frame - AnimToOperateOn->SetRawNumberOfFrame((ActualFrames / 2) + 1); + const int32 ActualFrames = DataToCompress.NumFrames - 1; // strip bookmark end frame + DataToCompress.NumFrames = (ActualFrames / 2) + 1; } else { - for (int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex) + for (FRawAnimSequenceTrack& Track : DataToCompress.RawAnimationData) { - FRawAnimSequenceTrack& Track = AnimToOperateOn->GetRawAnimationTrack(TrackIndex); - StripFramesOdd(Track.PosKeys, NumFrames); StripFramesOdd(Track.RotKeys, NumFrames); StripFramesOdd(Track.ScaleKeys, NumFrames); } - const int32 ActualFrames = AnimToOperateOn->GetRawNumberOfFrames(); // strip bookmark end frame - AnimToOperateOn->SetRawNumberOfFrame((ActualFrames / 2)); + const int32 ActualFrames = DataToCompress.NumFrames; + DataToCompress.NumFrames = (ActualFrames / 2); } } - AnimToOperateOn->UpdateCompressedTrackMapFromRaw(); - AnimToOperateOn->UpdateCompressedCurveNames(); + DataToCompress.Update(OutData); - bool bCurveCompressionSuccess = FAnimationUtils::CompressAnimCurves(*AnimToOperateOn); + bool bCurveCompressionSuccess = FAnimationUtils::CompressAnimCurves(DataToCompress, OutData); #if DO_CHECK - FString CompressionName = AnimToOperateOn->CompressionScheme->GetFullName(); + FString CompressionName = DataToCompress.RequestedCompressionScheme->GetFullName(); const TCHAR* AAC = CompressContext.Get()->bAllowAlternateCompressor ? TEXT("true") : TEXT("false"); const TCHAR* OutputStr = CompressContext.Get()->bOutput ? TEXT("true") : TEXT("false"); #endif - AnimToOperateOn->UpdateCompressedNumFramesFromRaw(); //Do this before compression so compress code can read the correct value + CompressionResult.CompressedNumberOfFrames = DataToCompress.NumFrames; //Do this before compression so compress code can read the correct value - FAnimationUtils::CompressAnimSequence(AnimToOperateOn, *CompressContext.Get()); - bCompressionSuccessful = AnimToOperateOn->IsCompressedDataValid() && bCurveCompressionSuccess; + CompressContext->GatherPreCompressionStats(DataToCompress, PreviousCompressedSize); + + FAnimationUtils::CompressAnimSequence(DataToCompress, CompressionResult, *CompressContext.Get()); + bCompressionSuccessful = (CompressionResult.IsCompressedDataValid() || DataToCompress.RawAnimationData.Num() == 0) && bCurveCompressionSuccess; ensureMsgf(bCompressionSuccessful, TEXT("Anim Compression failed for Sequence '%s' with compression scheme '%s': compressed data empty\n\tAnimIndex: %i\n\tMaxAnim:%i\n\tAllowAltCompressor:%s\n\tOutput:%s"), - *AnimToOperateOn->GetFullName(), + *DataToCompress.FullName, *CompressionName, CompressContext.Get()->AnimIndex, CompressContext.Get()->MaxAnimations, AAC, OutputStr); - AnimToOperateOn->CompressedRawDataSize = AnimToOperateOn->GetApproxRawSize(); - AnimToOperateOn->TestEvalauteAnimation(); //Validate that compressed data is readable. - } + if (CompressionResult.IsCompressedDataValid()) + { + CompressionResult.BuildFinalBuffer(OutData.CompressedByteStream); // Build final compressed data buffer - //Our compression scheme may change so copy the new one back - if (OriginalAnimSequence != AnimToOperateOn) - { - CA_SUPPRESS(6011); // See https://connect.microsoft.com/VisualStudio/feedback/details/3007725 - OriginalAnimSequence->CompressionScheme = static_cast(StaticDuplicateObject(AnimToOperateOn->CompressionScheme, OriginalAnimSequence)); - OriginalAnimSequence->CurveCompressionSettings = AnimToOperateOn->CurveCompressionSettings; + OutData.CompressedDataStructure.CopyFrom(CompressionResult); //Copy header info + OutData.CompressedDataStructure.InitViewsFromBuffer(OutData.CompressedByteStream); //Init views to CompressedByteStream + + } } if (bCompressionSuccessful) { - AnimToOperateOn->SetSkeletonVirtualBoneGuid(AnimToOperateOn->GetSkeleton()->GetVirtualBoneGuid()); - FMemoryWriter Ar(OutData, true); - AnimToOperateOn->SerializeCompressedData(Ar, true); //Save out compressed + FMemoryWriter Ar(OutDataArray, true); + OutData.SerializeCompressedData(Ar, true, nullptr, DataToCompress.CurveCompressionSettings); //Save out compressed } return bCompressionSuccessful; diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.h b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.h index f85a20004a9f..de13ccd51842 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.h +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.h @@ -8,6 +8,8 @@ #include "DerivedDataPluginInterface.h" #endif +#include "Animation/AnimCompressionTypes.h" + class UAnimSequence; struct FAnimCompressContext; @@ -18,17 +20,13 @@ struct FAnimCompressContext; class FDerivedDataAnimationCompression : public FDerivedDataPluginInterface { private: - // Anim sequence we are providing DDC data for - UAnimSequence* OriginalAnimSequence; - - // Possible duplicate animation for doing actual build work on - UAnimSequence* DuplicateSequence; + FCompressibleAnimData DataToCompress; // FAnimCompressContext to use during compression if we don't pull from the DDC TSharedPtr CompressContext; - // Whether to do compression work on original animation or duplicate it first. - bool bDoCompressionInPlace; + // Size of the previous compressed data (for stat tracking) + int32 PreviousCompressedSize; // Whether we should frame strip (remove every other frame from even frames animations) bool bPerformStripping; @@ -37,12 +35,12 @@ private: bool bIsEvenFramed; public: - FDerivedDataAnimationCompression(UAnimSequence* InAnimSequence, TSharedPtr InCompressContext, bool bInDoCompressionInPlace, bool bInTryFrameStripping, bool bTryStrippingOnOddFramedAnims); + FDerivedDataAnimationCompression(const FCompressibleAnimData& InDataToCompress, TSharedPtr InCompressContext, int32 InPreviousCompressedSize, bool bInTryFrameStripping, bool bTryStrippingOnOddFramedAnims); virtual ~FDerivedDataAnimationCompression(); virtual const TCHAR* GetPluginName() const override { - return TEXT("AnimSeq"); + return *DataToCompress.TypeName; } virtual const TCHAR* GetVersionString() const override @@ -50,7 +48,7 @@ public: // This is a version string that mimics the old versioning scheme. If you // want to bump this version, generate a new guid using VS->Tools->Create GUID and // return it here. Ex. - return TEXT("267426BCDCE54E129C9D584B35330967"); + return TEXT("1F1656B9E10142729AB16650D9821B1F"); } virtual FString GetPluginSpecificCacheKeySuffix() const override; @@ -61,12 +59,12 @@ public: return false; } - virtual bool Build( TArray& OutData ) override; + virtual bool Build( TArray& OutDataArray) override; /** Return true if we can build **/ bool CanBuild() { - return !!OriginalAnimSequence; + return true; } }; diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionTypes.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionTypes.cpp new file mode 100644 index 000000000000..9b3783b070d0 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionTypes.cpp @@ -0,0 +1,440 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Animation/AnimCompressionTypes.h" +#include "AnimationUtils.h" +#include "AnimEncoding.h" +#include "Interfaces/ITargetPlatform.h" +#include "Animation/AnimCurveCompressionCodec.h" +#include "Animation/AnimCurveCompressionSettings.h" +#include "UObject/FortniteMainBranchObjectVersion.h" + +FCompressibleAnimData::FCompressibleAnimData(class UAnimSequence* InSeq) : +#if WITH_EDITOR + RequestedCompressionScheme(InSeq->CompressionScheme) , +#endif + CurveCompressionSettings(InSeq->CurveCompressionSettings) + , Skeleton(InSeq->GetSkeleton()) + , TrackToSkeletonMapTable(InSeq->GetRawTrackToSkeletonMapTable()) + , Interpolation(InSeq->Interpolation) + , SequenceLength(InSeq->SequenceLength) + , NumFrames(InSeq->GetRawNumberOfFrames()) + , bIsValidAdditive(InSeq->IsValidAdditive()) +#if WITH_EDITOR + , CompressCommandletVersion(InSeq->CompressCommandletVersion) + , RawDataGuid(InSeq->GetRawDataGuid()) +#endif + , RefFrameIndex(InSeq->RefFrameIndex) + , RefPoseType(InSeq->RefPoseType) + , AdditiveAnimType(InSeq->AdditiveAnimType) + , Name(InSeq->GetName()) + , FullName(InSeq->GetFullName()) + , AnimFName(InSeq->GetFName()) +{ +#if WITH_EDITOR + FAnimationUtils::BuildSkeletonMetaData(Skeleton, BoneData); + + const bool bHasVirtualBones = InSeq->GetSkeleton()->GetVirtualBones().Num() > 0; + + if (InSeq->CanBakeAdditive()) + { + TArray TempTrackNames; + InSeq->BakeOutAdditiveIntoRawData(RawAnimationData, TempTrackNames, TrackToSkeletonMapTable, RawCurveData, AdditiveBaseAnimationData); + + if (InSeq->RefPoseSeq) + { + AdditiveDataGuid = InSeq->RefPoseSeq->GetRawDataGuid(); + } + } + else if (bHasVirtualBones)// If we aren't additive we must bake virtual bones + { + TArray TempTrackNames; + InSeq->BakeOutVirtualBoneTracks(RawAnimationData, TempTrackNames, TrackToSkeletonMapTable); + RawCurveData = InSeq->RawCurveData; + } + else + { + RawAnimationData = InSeq->GetRawAnimationData(); + TrackToSkeletonMapTable = InSeq->GetRawTrackToSkeletonMapTable(); + RawCurveData = InSeq->RawCurveData; + } + + TypeName = TEXT("AnimSeq"); +#endif +} + +void FCompressibleAnimData::Update(FCompressedAnimSequence& InOutCompressedData) const +{ + InOutCompressedData.CompressedTrackToSkeletonMapTable = TrackToSkeletonMapTable; + InOutCompressedData.CompressedRawDataSize = GetApproxRawSize(); + + const int32 NumCurves = RawCurveData.FloatCurves.Num(); + InOutCompressedData.CompressedCurveNames.Reset(NumCurves); + InOutCompressedData.CompressedCurveNames.AddUninitialized(NumCurves); + for (int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FFloatCurve& Curve = RawCurveData.FloatCurves[CurveIndex]; + InOutCompressedData.CompressedCurveNames[CurveIndex] = Curve.Name; + } +} + +template +void WriteArray(FMemoryWriter& MemoryWriter, TArray& Array) +{ + const int64 NumBytes = (Array.GetTypeSize() * Array.Num()); + MemoryWriter.Serialize(Array.GetData(), NumBytes); +} + +void FCompressibleAnimDataResult::BuildFinalBuffer(TArray& OutBuffer) +{ + OutBuffer.Reset(); + FMemoryWriter MemoryWriter(OutBuffer); + + WriteArray(MemoryWriter, CompressedTrackOffsets); + WriteArray(MemoryWriter, CompressedScaleOffsets.OffsetData); + WriteArray(MemoryWriter, CompressedByteStream); +} + +template +void InitArrayView(TArrayView& View, uint8*& DataPtr) +{ + View = TArrayView((T*)DataPtr, View.Num()); + DataPtr += (View.Num() * View.GetTypeSize()); +} + +template +void ResetArrayView(TArrayView& ArrayView) +{ + ArrayView = TArrayView(); +} + +void FUECompressedAnimData::Reset() +{ + ResetArrayView(CompressedTrackOffsets); + ResetArrayView(CompressedScaleOffsets.OffsetData); + ResetArrayView(CompressedByteStream); + + TranslationCompressionFormat = RotationCompressionFormat = ScaleCompressionFormat = ACF_None; + TranslationCodec = RotationCodec = ScaleCodec = nullptr; +} + +void FUECompressedAnimData::InitViewsFromBuffer(const TArrayView BulkData) +{ + check(BulkData.Num() > 0); + + uint8* BulkDataPtr = BulkData.GetData(); + + InitArrayView(CompressedTrackOffsets, BulkDataPtr); + InitArrayView(CompressedScaleOffsets.OffsetData, BulkDataPtr); + InitArrayView(CompressedByteStream, BulkDataPtr); + + check((BulkDataPtr - BulkData.GetData()) == BulkData.Num()); +} + +template +void InitArrayViewSize(TArrayView& Dest, const TArray& Src) +{ + Dest = TArrayView((T*)nullptr, Src.Num()); +} + +#if WITH_EDITOR +void FUECompressedAnimData::CopyFrom(const FCompressibleAnimDataResult& Other) +{ + InitArrayViewSize(CompressedTrackOffsets, Other.CompressedTrackOffsets); + InitArrayViewSize(CompressedScaleOffsets.OffsetData, Other.CompressedScaleOffsets.OffsetData); + InitArrayViewSize(CompressedByteStream, Other.CompressedByteStream); + + CompressedScaleOffsets.StripSize = Other.CompressedScaleOffsets.StripSize; + + CopyFromSettings(Other); +} +#endif + +template +void SerializeView(class FArchive& Ar, TArrayView& View) +{ + int32 Size = View.Num(); + if (Ar.IsLoading()) + { + Ar << Size; + View = TArrayView((T*)nullptr, Size); + } + else + { + Ar << Size; + } +} + +template +void SerializeEnum(FArchive& Ar, EnumType& Val) +{ + uint8 Temp = (uint8)Val; + if (Ar.IsLoading()) + { + Ar << Temp; + Val = (EnumType)Temp; + } + else + { + Ar << Temp; + } +} + +FArchive& operator<<(FArchive& Ar, AnimationCompressionFormat& Fmt) +{ + SerializeEnum(Ar, Fmt); + return Ar; +} + +FArchive& operator<<(FArchive& Ar, AnimationKeyFormat& Fmt) +{ + SerializeEnum(Ar, Fmt); + return Ar; +} + +void FUECompressedAnimData::SerializeCompressedData(class FArchive& Ar) +{ + Ar << KeyEncodingFormat; + Ar << TranslationCompressionFormat; + Ar << RotationCompressionFormat; + Ar << ScaleCompressionFormat; + + Ar << CompressedNumberOfFrames; + + SerializeView(Ar, CompressedTrackOffsets); + SerializeView(Ar, CompressedScaleOffsets.OffsetData); + Ar << CompressedScaleOffsets.StripSize; + SerializeView(Ar, CompressedByteStream); + + AnimationFormat_SetInterfaceLinks(*this); +} + +template +void ByteSwapArray(TArchive& MemoryStream, uint8*& StartOfArray, TArrayView& ArrayView) +{ + for (int32 ItemIndex = 0; ItemIndex < ArrayView.Num(); ++ItemIndex) + { + AC_UnalignedSwap(MemoryStream, StartOfArray, ArrayView.GetTypeSize()); + } +} + +template +void ByteSwapCodecData(class AnimEncoding& Codec, TArchive& MemoryStream, FUECompressedAnimData& CompressedData) +{ + check(false); +} + +template<> +void ByteSwapCodecData(class AnimEncoding& Codec, FMemoryWriter& MemoryStream, FUECompressedAnimData& CompressedData) +{ + Codec.ByteSwapOut(CompressedData, MemoryStream); +} + +template<> +void ByteSwapCodecData(class AnimEncoding& Codec, FMemoryReader& MemoryStream, FUECompressedAnimData& CompressedData) +{ + Codec.ByteSwapIn(CompressedData, MemoryStream); +} + +template +void FUECompressedAnimData::ByteSwapData(TArrayView CompressedData, TArchive& MemoryStream) +{ + //Handle Array Header + uint8* MovingCompressedDataPtr = CompressedData.GetData(); + + ByteSwapArray(MemoryStream, MovingCompressedDataPtr, CompressedTrackOffsets); + ByteSwapArray(MemoryStream, MovingCompressedDataPtr, CompressedScaleOffsets.OffsetData); + + AnimationFormat_SetInterfaceLinks(*this); + check(RotationCodec); + + ByteSwapCodecData(*RotationCodec, MemoryStream, *this); +} + +template void FUECompressedAnimData::ByteSwapData(TArrayView CompressedData, FMemoryReader& MemoryStream); +template void FUECompressedAnimData::ByteSwapData(TArrayView CompressedData, FMemoryWriter& MemoryStream); + +void FCompressedAnimSequence::SerializeCompressedData(FArchive& Ar, bool bDDCData, UObject* DataOwner, UAnimCurveCompressionSettings* CurveCompressionSettings) +{ + Ar << CompressedTrackToSkeletonMapTable; + Ar << CompressedCurveNames; + + CompressedDataStructure.SerializeCompressedData(Ar); + + // Serialize the compressed byte stream from the archive to the buffer. + int32 NumBytes = CompressedByteStream.Num(); + Ar << NumBytes; + + if (Ar.IsLoading()) + { + bool bUseBulkDataForLoad = false; + if (!bDDCData && Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) >= FFortniteMainBranchObjectVersion::FortMappedCookedAnimation) + { + Ar << bUseBulkDataForLoad; + } + if (bUseBulkDataForLoad) + { +#if !WITH_EDITOR + FByteBulkData OptionalBulk; +#endif + bool bUseMapping = FPlatformProperties::SupportsMemoryMappedFiles() && FPlatformProperties::SupportsMemoryMappedAnimation(); + OptionalBulk.Serialize(Ar, DataOwner, -1, bUseMapping); + + if (!bUseMapping) + { + OptionalBulk.ForceBulkDataResident(); + } + + size_t Size = OptionalBulk.GetBulkDataSize(); + + FOwnedBulkDataPtr* OwnedPtr = OptionalBulk.StealFileMapping(); + +#if WITH_EDITOR + check(!bUseMapping && !OwnedPtr->GetMappedHandle()); + CompressedByteStream.Empty(Size); + CompressedByteStream.AddUninitialized(Size); + if (Size) + { + FMemory::Memcpy(&CompressedByteStream[0], OwnedPtr->GetPointer(), Size); + } +#else + CompressedByteStream.AcceptOwnedBulkDataPtr(OwnedPtr, Size); +#endif + delete OwnedPtr; + + CompressedDataStructure.InitViewsFromBuffer(CompressedByteStream); + } + else + { + CompressedByteStream.Empty(NumBytes); + CompressedByteStream.AddUninitialized(NumBytes); + + if (CompressedByteStream.Num() > 0) + { + CompressedDataStructure.InitViewsFromBuffer(CompressedByteStream); + } + + if (FPlatformProperties::RequiresCookedData()) + { + Ar.Serialize(CompressedByteStream.GetData(), NumBytes); + } + else + { + TArray SerializedData; + SerializedData.Empty(NumBytes); + SerializedData.AddUninitialized(NumBytes); + Ar.Serialize(SerializedData.GetData(), NumBytes); + + // Swap the buffer into the byte stream. + FMemoryReader MemoryReader(SerializedData, true); + MemoryReader.SetByteSwapping(Ar.ForceByteSwapping()); + + CompressedDataStructure.ByteSwapIn(CompressedByteStream, MemoryReader); + } + } + + FString CurveCodecPath; + Ar << CurveCodecPath; + + CurveCompressionCodec = CurveCompressionSettings->GetCodec(CurveCodecPath); + + int32 NumCurveBytes; + Ar << NumCurveBytes; + + CompressedCurveByteStream.Empty(NumCurveBytes); + CompressedCurveByteStream.AddUninitialized(NumCurveBytes); + Ar.Serialize(CompressedCurveByteStream.GetData(), NumCurveBytes); + } + else if (Ar.IsSaving() || Ar.IsCountingMemory()) + { + // Swap the byte stream into a buffer. + TArray SerializedData; + + const bool bIsCooking = !bDDCData && Ar.IsCooking(); + + // and then use the codecs to byte swap + FMemoryWriter MemoryWriter(SerializedData, true); + MemoryWriter.SetByteSwapping(Ar.ForceByteSwapping()); + CompressedDataStructure.ByteSwapOut(CompressedByteStream, MemoryWriter); + + // Make sure the entire byte stream was serialized. + check(NumBytes == SerializedData.Num()); + + bool bUseBulkDataForSave = NumBytes && bIsCooking && Ar.CookingTarget()->SupportsFeature(ETargetPlatformFeatures::MemoryMappedFiles) && Ar.CookingTarget()->SupportsFeature(ETargetPlatformFeatures::MemoryMappedAnimation); + + bool bSavebUseBulkDataForSave = false; + if (!bDDCData) + { + Ar.UsingCustomVersion(FFortniteMainBranchObjectVersion::GUID); + if (Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::FortMappedCookedAnimation) + { + bUseBulkDataForSave = false; + } + else + { + bSavebUseBulkDataForSave = true; + } + } + + // Count compressed data. + Ar.CountBytes(SerializedData.Num(), SerializedData.Num()); + + if (bSavebUseBulkDataForSave) + { + Ar << bUseBulkDataForSave; + } + else + { + check(!bUseBulkDataForSave); + } + +#define TEST_IS_CORRECTLY_FORMATTED_FOR_MEMORY_MAPPING 0 //Need to fix this +#if TEST_IS_CORRECTLY_FORMATTED_FOR_MEMORY_MAPPING + if (!IsTemplate() && bIsCooking) + { + TArray TempSerialized; + FMemoryWriter MemoryWriter(TempSerialized, true); + MemoryWriter.SetByteSwapping(Ar.ForceByteSwapping()); + + check(RotationCodec != NULL); + + FMemoryReader MemoryReader(TempSerialized, true); + MemoryReader.SetByteSwapping(Ar.ForceByteSwapping()); + + TArray SavedCompressedByteStream = CompressedByteStream; + CompressedByteStream.Empty(); + + check(CompressedByteStream.Num() == Num); + + check(FMemory::Memcmp(SerializedData.GetData(), CompressedByteStream.GetData(), Num) == 0); + + CompressedByteStream = SavedCompressedByteStream; + } +#endif + + if (bUseBulkDataForSave) + { +#if WITH_EDITOR + OptionalBulk.Lock(LOCK_READ_WRITE); + void* Dest = OptionalBulk.Realloc(NumBytes); + FMemory::Memcpy(Dest, &(SerializedData[0]), NumBytes); + OptionalBulk.Unlock(); + OptionalBulk.SetBulkDataFlags(BULKDATA_PayloadAtEndOfFile | BULKDATA_PayloadInSeperateFile | BULKDATA_Force_NOT_InlinePayload | BULKDATA_MemoryMappedPayload); + OptionalBulk.ClearBulkDataFlags(BULKDATA_ForceInlinePayload); + OptionalBulk.Serialize(Ar, DataOwner); +#else + UE_LOG(LogAnimation, Fatal, TEXT("Can't save animation as bulk data in non-editor builds!")); +#endif + } + else + { + Ar.Serialize(SerializedData.GetData(), SerializedData.Num()); + } + + FString CurveCodecPath = CurveCompressionCodec->GetPathName(); + Ar << CurveCodecPath; + + int32 NumCurveBytes = CompressedCurveByteStream.Num(); + Ar << NumCurveBytes; + Ar.Serialize(CompressedCurveByteStream.GetData(), NumCurveBytes); + } +} diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_CompressedRichCurve.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_CompressedRichCurve.cpp index 8af0b5b2dba2..4709e1ea2634 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_CompressedRichCurve.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_CompressedRichCurve.cpp @@ -26,7 +26,7 @@ struct FCurveDesc }; #if WITH_EDITORONLY_DATA -bool UAnimCurveCompressionCodec_CompressedRichCurve::Compress(const UAnimSequence& AnimSeq, FAnimCurveCompressionResult& OutResult) +bool UAnimCurveCompressionCodec_CompressedRichCurve::Compress(const FCompressibleAnimData& AnimSeq, FAnimCurveCompressionResult& OutResult) { int32 NumCurves = AnimSeq.RawCurveData.FloatCurves.Num(); @@ -37,7 +37,7 @@ bool UAnimCurveCompressionCodec_CompressedRichCurve::Compress(const UAnimSequenc int32 KeyDataOffset = 0; KeyDataOffset += sizeof(FCurveDesc) * NumCurves; - const FAnimKeyHelper Helper(AnimSeq.SequenceLength, AnimSeq.GetRawNumberOfFrames()); + const FAnimKeyHelper Helper(AnimSeq.SequenceLength, AnimSeq.NumFrames); const float SampleRate = UseAnimSequenceSampleRate ? Helper.KeysPerSecond() : ErrorSampleRate; TArray KeyData; @@ -94,12 +94,12 @@ void UAnimCurveCompressionCodec_CompressedRichCurve::PopulateDDCKey(FArchive& Ar } #endif -void UAnimCurveCompressionCodec_CompressedRichCurve::DecompressCurves(const UAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const +void UAnimCurveCompressionCodec_CompressedRichCurve::DecompressCurves(const FCompressedAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const { const uint8* Buffer = AnimSeq.CompressedCurveByteStream.GetData(); const FCurveDesc* CurveDescriptions = (const FCurveDesc*)(Buffer); - const TArray& CompressedCurveNames = AnimSeq.GetCompressedCurveNames(); + const TArray& CompressedCurveNames = AnimSeq.CompressedCurveNames; const int32 NumCurves = CompressedCurveNames.Num(); for (int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) { @@ -114,12 +114,12 @@ void UAnimCurveCompressionCodec_CompressedRichCurve::DecompressCurves(const UAni } } -float UAnimCurveCompressionCodec_CompressedRichCurve::DecompressCurve(const UAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const +float UAnimCurveCompressionCodec_CompressedRichCurve::DecompressCurve(const FCompressedAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const { const uint8* Buffer = AnimSeq.CompressedCurveByteStream.GetData(); const FCurveDesc* CurveDescriptions = (const FCurveDesc*)(Buffer); - const TArray& CompressedCurveNames = AnimSeq.GetCompressedCurveNames(); + const TArray& CompressedCurveNames = AnimSeq.CompressedCurveNames; const int32 NumCurves = CompressedCurveNames.Num(); for (int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) { diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_UniformlySampled.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_UniformlySampled.cpp index 911c6f9d5511..f492ee86377c 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_UniformlySampled.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_UniformlySampled.cpp @@ -14,7 +14,7 @@ UAnimCurveCompressionCodec_UniformlySampled::UAnimCurveCompressionCodec_Uniforml } #if WITH_EDITORONLY_DATA -bool UAnimCurveCompressionCodec_UniformlySampled::Compress(const UAnimSequence& AnimSeq, FAnimCurveCompressionResult& OutResult) +bool UAnimCurveCompressionCodec_UniformlySampled::Compress(const FCompressibleAnimData& AnimSeq, FAnimCurveCompressionResult& OutResult) { const int32 NumCurves = AnimSeq.RawCurveData.FloatCurves.Num(); const float Duration = AnimSeq.SequenceLength; @@ -23,7 +23,7 @@ bool UAnimCurveCompressionCodec_UniformlySampled::Compress(const UAnimSequence& float SampleRate_; if (UseAnimSequenceSampleRate) { - const FAnimKeyHelper Helper(AnimSeq.SequenceLength, AnimSeq.GetRawNumberOfFrames()); + const FAnimKeyHelper Helper(AnimSeq.SequenceLength, AnimSeq.NumFrames); SampleRate_ = Helper.KeysPerSecond(); NumSamples = FMath::RoundToInt(Duration * SampleRate_) + 1; } @@ -160,9 +160,9 @@ void UAnimCurveCompressionCodec_UniformlySampled::PopulateDDCKey(FArchive& Ar) } #endif -void UAnimCurveCompressionCodec_UniformlySampled::DecompressCurves(const UAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const +void UAnimCurveCompressionCodec_UniformlySampled::DecompressCurves(const FCompressedAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const { - const TArray& CompressedCurveNames = AnimSeq.GetCompressedCurveNames(); + const TArray& CompressedCurveNames = AnimSeq.CompressedCurveNames; const int32 NumCurves = CompressedCurveNames.Num(); if (NumCurves == 0) @@ -232,9 +232,9 @@ void UAnimCurveCompressionCodec_UniformlySampled::DecompressCurves(const UAnimSe } } -float UAnimCurveCompressionCodec_UniformlySampled::DecompressCurve(const UAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const +float UAnimCurveCompressionCodec_UniformlySampled::DecompressCurve(const FCompressedAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const { - const TArray& CompressedCurveNames = AnimSeq.GetCompressedCurveNames(); + const TArray& CompressedCurveNames = AnimSeq.CompressedCurveNames; const int32 NumCurves = CompressedCurveNames.Num(); if (NumCurves == 0) diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionSettings.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionSettings.cpp index c80be02478bf..ce8a165ceefe 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionSettings.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionSettings.cpp @@ -23,7 +23,7 @@ bool UAnimCurveCompressionSettings::AreSettingsValid() const return Codec != nullptr && Codec->IsCodecValid(); } -bool UAnimCurveCompressionSettings::Compress(UAnimSequence& AnimSeq) const +bool UAnimCurveCompressionSettings::Compress(const FCompressibleAnimData& AnimSeq, FCompressedAnimSequence& OutCompressedData) const { if (Codec == nullptr || !AreSettingsValid()) { @@ -34,8 +34,8 @@ bool UAnimCurveCompressionSettings::Compress(UAnimSequence& AnimSeq) const bool Success = Codec->Compress(AnimSeq, CompressionResult); if (Success) { - AnimSeq.CompressedCurveByteStream = CompressionResult.CompressedBytes; - AnimSeq.CurveCompressionCodec = CompressionResult.Codec; + OutCompressedData.CompressedCurveByteStream = CompressionResult.CompressedBytes; + OutCompressedData.CurveCompressionCodec = CompressionResult.Codec; } return Success; diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimEncoding.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimEncoding.cpp index b3edc627ad35..a66ad0959742 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimEncoding.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimEncoding.cpp @@ -128,26 +128,26 @@ inline int32 GetCompressedScaleStride(AnimationCompressionFormat ScaleCompressio /** * Compressed translation data will be byte swapped in chunks of this size. */ -inline int32 GetCompressedTranslationStride(const UAnimSequence* Seq) +inline int32 GetCompressedTranslationStride(const FUECompressedAnimData& CompressedData) { - return CompressedTranslationStrides[static_cast(Seq->TranslationCompressionFormat)]; + return CompressedTranslationStrides[static_cast(CompressedData.TranslationCompressionFormat)]; } /** * Compressed rotation data will be byte swapped in chunks of this size. */ -inline int32 GetCompressedRotationStride(const UAnimSequence* Seq) +inline int32 GetCompressedRotationStride(const FUECompressedAnimData& CompressedData) { - return CompressedRotationStrides[static_cast(Seq->RotationCompressionFormat)]; + return CompressedRotationStrides[static_cast(CompressedData.RotationCompressionFormat)]; } /** * Compressed Scale data will be byte swapped in chunks of this size. */ -inline int32 GetCompressedScaleStride(const UAnimSequence* Seq) +inline int32 GetCompressedScaleStride(const FUECompressedAnimData& CompressedData) { // @todo fixme change this? - return CompressedScaleStrides[static_cast(Seq->ScaleCompressionFormat)]; + return CompressedScaleStrides[static_cast(CompressedData.ScaleCompressionFormat)]; } @@ -264,7 +264,7 @@ void AnimEncodingLegacyBase::GetBoneAtom(FTransform& OutAtom, FAnimSequenceDecom /** * Handles Byte-swapping incoming animation data from a MemoryReader * - * @param Seq An Animation Sequence to contain the read data. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The MemoryReader object to read from. */ //@todo.VC10: Apparent VC10 compiler bug here causes an access violation in optimized builds @@ -272,42 +272,32 @@ void AnimEncodingLegacyBase::GetBoneAtom(FTransform& OutAtom, FAnimSequenceDecom PRAGMA_DISABLE_OPTIMIZATION #endif void AnimEncodingLegacyBase::ByteSwapIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader) { - const int32 NumTracks = Seq.CompressedTrackOffsets.Num() / 4; + const int32 BufferStart = MemoryReader.Tell(); - int32 OriginalNumBytes = MemoryReader.TotalSize(); - Seq.CompressedByteStream.Empty(OriginalNumBytes); - Seq.CompressedByteStream.AddUninitialized(OriginalNumBytes); + const int32 NumTracks = CompressedData.CompressedTrackOffsets.Num() / 4; - if (Seq.CompressedSegments.Num() != 0) - { -#if !PLATFORM_LITTLE_ENDIAN -#error "Byte swapping needs to be implemented here to support big-endian platforms" -#endif - - // TODO: Byte swap the new format - MemoryReader.Serialize(Seq.CompressedByteStream.GetData(), Seq.CompressedByteStream.Num()); - return; - } + int32 OriginalNumBytes = MemoryReader.TotalSize() - BufferStart; + check(CompressedData.CompressedByteStream.Num() == OriginalNumBytes); // Read and swap - uint8* StreamBase = Seq.CompressedByteStream.GetData(); - bool bHasValidScale = Seq.CompressedScaleOffsets.IsValid(); + uint8* StreamBase = CompressedData.CompressedByteStream.GetData(); + bool bHasValidScale = CompressedData.CompressedScaleOffsets.IsValid(); for ( int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex ) { - const int32 OffsetTrans = Seq.CompressedTrackOffsets[TrackIndex*4+0]; - const int32 NumKeysTrans = Seq.CompressedTrackOffsets[TrackIndex*4+1]; - const int32 OffsetRot = Seq.CompressedTrackOffsets[TrackIndex*4+2]; - const int32 NumKeysRot = Seq.CompressedTrackOffsets[TrackIndex*4+3]; + const int32 OffsetTrans = CompressedData.CompressedTrackOffsets[TrackIndex*4+0]; + const int32 NumKeysTrans = CompressedData.CompressedTrackOffsets[TrackIndex*4+1]; + const int32 OffsetRot = CompressedData.CompressedTrackOffsets[TrackIndex*4+2]; + const int32 NumKeysRot = CompressedData.CompressedTrackOffsets[TrackIndex*4+3]; // Translation data. checkSlow( (OffsetTrans % 4) == 0 && "CompressedByteStream not aligned to four bytes" ); uint8* TransTrackData = StreamBase + OffsetTrans; - checkSlow(Seq.TranslationCodec != NULL); - ((AnimEncodingLegacyBase*)Seq.TranslationCodec)->ByteSwapTranslationIn(Seq, MemoryReader, TransTrackData, NumKeysTrans); + checkSlow(CompressedData.TranslationCodec != NULL); + ((AnimEncodingLegacyBase*)CompressedData.TranslationCodec)->ByteSwapTranslationIn(CompressedData, MemoryReader, TransTrackData, NumKeysTrans); // Like the compressed byte stream, pad the serialization stream to four bytes. // As a sanity check, each pad byte can be checked to be the PadSentinel. @@ -316,8 +306,8 @@ void AnimEncodingLegacyBase::ByteSwapIn( // Rotation data. checkSlow( (OffsetRot % 4) == 0 && "CompressedByteStream not aligned to four bytes" ); uint8* RotTrackData = StreamBase + OffsetRot; - checkSlow(Seq.RotationCodec != NULL); - ((AnimEncodingLegacyBase*)Seq.RotationCodec)->ByteSwapRotationIn(Seq, MemoryReader, RotTrackData, NumKeysRot); + checkSlow(CompressedData.RotationCodec != NULL); + ((AnimEncodingLegacyBase*)CompressedData.RotationCodec)->ByteSwapRotationIn(CompressedData, MemoryReader, RotTrackData, NumKeysRot); // Like the compressed byte stream, pad the serialization stream to four bytes. // As a sanity check, each pad byte can be checked to be the PadSentinel. @@ -325,14 +315,14 @@ void AnimEncodingLegacyBase::ByteSwapIn( if (bHasValidScale) { - const int32 OffsetScale = Seq.CompressedScaleOffsets.GetOffsetData(TrackIndex, 0); - const int32 NumKeysScale = Seq.CompressedScaleOffsets.GetOffsetData(TrackIndex, 1); + const int32 OffsetScale = CompressedData.CompressedScaleOffsets.GetOffsetData(TrackIndex, 0); + const int32 NumKeysScale = CompressedData.CompressedScaleOffsets.GetOffsetData(TrackIndex, 1); // Scale data. checkSlow( (OffsetScale % 4) == 0 && "CompressedByteStream not aligned to four bytes" ); uint8* ScaleTrackData = StreamBase + OffsetScale; - checkSlow(Seq.ScaleCodec != NULL); - ((AnimEncodingLegacyBase*)Seq.ScaleCodec)->ByteSwapScaleIn(Seq, MemoryReader, ScaleTrackData, NumKeysScale); + checkSlow(CompressedData.ScaleCodec != NULL); + ((AnimEncodingLegacyBase*)CompressedData.ScaleCodec)->ByteSwapScaleIn(CompressedData, MemoryReader, ScaleTrackData, NumKeysScale); // Like the compressed byte stream, pad the serialization stream to four bytes. // As a sanity check, each pad byte can be checked to be the PadSentinel. @@ -354,85 +344,68 @@ PRAGMA_ENABLE_OPTIMIZATION * @param ForceByteSwapping true is byte swapping is not optional. */ void AnimEncodingLegacyBase::ByteSwapOut( - UAnimSequence& Seq, - TArray& SerializedData, - bool ForceByteSwapping, - bool bMaintainComponentOrder) + FUECompressedAnimData& CompressedData, + FMemoryWriter& MemoryWriter) { - FMemoryWriter MemoryWriter( SerializedData, true ); - MemoryWriter.SetByteSwapping( ForceByteSwapping ); + const int32 BufferStart = MemoryWriter.Tell(); - if (Seq.CompressedSegments.Num() != 0) - { - // TODO: Byte swap the new format - MemoryWriter.Serialize(Seq.CompressedByteStream.GetData(), Seq.CompressedByteStream.Num()); - return; - } + uint8* StreamBase = CompressedData.CompressedByteStream.GetData(); + const int32 NumTracks = CompressedData.CompressedTrackOffsets.Num()/4; - uint8* StreamBase = Seq.CompressedByteStream.GetData(); - const int32 NumTracks = Seq.CompressedTrackOffsets.Num()/4; - - bool bHasValidScale = Seq.CompressedScaleOffsets.IsValid(); + bool bHasValidScale = CompressedData.CompressedScaleOffsets.IsValid(); for ( int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex ) { - const int32 OffsetTrans = Seq.CompressedTrackOffsets[TrackIndex*4]; - const int32 NumKeysTrans = Seq.CompressedTrackOffsets[TrackIndex*4+1]; - const int32 OffsetRot = Seq.CompressedTrackOffsets[TrackIndex*4+2]; - const int32 NumKeysRot = Seq.CompressedTrackOffsets[TrackIndex*4+3]; + const int32 OffsetTrans = CompressedData.CompressedTrackOffsets[TrackIndex*4]; + const int32 NumKeysTrans = CompressedData.CompressedTrackOffsets[TrackIndex*4+1]; + const int32 OffsetRot = CompressedData.CompressedTrackOffsets[TrackIndex*4+2]; + const int32 NumKeysRot = CompressedData.CompressedTrackOffsets[TrackIndex*4+3]; + + MemoryWriter.Seek(BufferStart + OffsetTrans); - if (bMaintainComponentOrder) - { - MemoryWriter.Seek(OffsetTrans); - } // Translation data. checkSlow( (OffsetTrans % 4) == 0 && "CompressedByteStream not aligned to four bytes" ); uint8* TransTrackData = StreamBase + OffsetTrans; - if (Seq.TranslationCodec != NULL) + if (CompressedData.TranslationCodec != NULL) { - ((AnimEncodingLegacyBase*)Seq.TranslationCodec)->ByteSwapTranslationOut(Seq, MemoryWriter, TransTrackData, NumKeysTrans); + ((AnimEncodingLegacyBase*)CompressedData.TranslationCodec)->ByteSwapTranslationOut(CompressedData, MemoryWriter, TransTrackData, NumKeysTrans); } else { - UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported animation format"), (int32)Seq.KeyEncodingFormat ); + UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported animation format"), (int32)CompressedData.KeyEncodingFormat ); }; // Like the compressed byte stream, pad the serialization stream to four bytes. PadMemoryWriter(&MemoryWriter, TransTrackData, 4); - if (bMaintainComponentOrder) - { - MemoryWriter.Seek(OffsetRot); - } + MemoryWriter.Seek(BufferStart + OffsetRot); + // Rotation data. checkSlow( (OffsetRot % 4) == 0 && "CompressedByteStream not aligned to four bytes" ); uint8* RotTrackData = StreamBase + OffsetRot; - checkSlow(Seq.RotationCodec != NULL); - ((AnimEncodingLegacyBase*)Seq.RotationCodec)->ByteSwapRotationOut(Seq, MemoryWriter, RotTrackData, NumKeysRot); + checkSlow(CompressedData.RotationCodec != NULL); + ((AnimEncodingLegacyBase*)CompressedData.RotationCodec)->ByteSwapRotationOut(CompressedData, MemoryWriter, RotTrackData, NumKeysRot); // Like the compressed byte stream, pad the serialization stream to four bytes. PadMemoryWriter(&MemoryWriter, RotTrackData, 4); if (bHasValidScale) { - const int32 OffsetScale = Seq.CompressedScaleOffsets.GetOffsetData(TrackIndex, 0); - const int32 NumKeysScale = Seq.CompressedScaleOffsets.GetOffsetData(TrackIndex, 1); + const int32 OffsetScale = CompressedData.CompressedScaleOffsets.GetOffsetData(TrackIndex, 0); + const int32 NumKeysScale = CompressedData.CompressedScaleOffsets.GetOffsetData(TrackIndex, 1); - if (bMaintainComponentOrder) - { - MemoryWriter.Seek(OffsetScale); - } + MemoryWriter.Seek(BufferStart + OffsetScale); // Scale data. checkSlow( (OffsetScale % 4) == 0 && "CompressedByteStream not aligned to four bytes" ); uint8* ScaleTrackData = StreamBase + OffsetScale; - if (Seq.ScaleCodec != NULL) + if (CompressedData.ScaleCodec != NULL) { - ((AnimEncodingLegacyBase*)Seq.ScaleCodec)->ByteSwapScaleOut(Seq, MemoryWriter, ScaleTrackData, NumKeysScale); + ((AnimEncodingLegacyBase*)CompressedData.ScaleCodec)->ByteSwapScaleOut(CompressedData, MemoryWriter, ScaleTrackData, NumKeysScale); } else { - UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported animation format"), (int32)Seq.KeyEncodingFormat ); + UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported animation format"), (int32)CompressedData.KeyEncodingFormat ); }; // Like the compressed byte stream, pad the serialization stream to four bytes. @@ -456,7 +429,7 @@ void AnimEncodingLegacyBase::ByteSwapOut( * @param NumRotTracksWithOneKey The total number of Rotation Tracks found containing a single key. */ void AnimationFormat_GetStats( - const UAnimSequence* Seq, + const FUECompressedAnimData& CompressedData, int32& NumTransTracks, int32& NumRotTracks, int32& NumScaleTracks, @@ -471,245 +444,243 @@ void AnimationFormat_GetStats( int32& NumRotTracksWithOneKey, int32& NumScaleTracksWithOneKey) { - if (Seq) + OverheadSize = CompressedData.CompressedTrackOffsets.Num() * sizeof(int32); + const size_t KeyFrameLookupSize = (CompressedData.CompressedNumberOfFrames > 0xFF) ? sizeof(uint16) : sizeof(uint8); + + if (CompressedData.KeyEncodingFormat != AKF_PerTrackCompression) { - OverheadSize = Seq->CompressedTrackOffsets.Num() * sizeof(int32); - const size_t KeyFrameLookupSize = (Seq->GetCompressedNumberOfFrames() > 0xFF) ? sizeof(uint16) : sizeof(uint8); + const int32 TransStride = GetCompressedTranslationStride(CompressedData); + const int32 RotStride = GetCompressedRotationStride(CompressedData); + const int32 ScaleStride = GetCompressedScaleStride(CompressedData); + const int32 TransNum = CompressedTranslationNum[CompressedData.TranslationCompressionFormat]; + const int32 RotNum = CompressedRotationNum[CompressedData.RotationCompressionFormat]; + const int32 ScaleNum = CompressedScaleNum[CompressedData.ScaleCompressionFormat]; - if (Seq->KeyEncodingFormat != AKF_PerTrackCompression) + TranslationKeySize = TransStride * TransNum; + RotationKeySize = RotStride * RotNum; + ScaleKeySize = ScaleStride * ScaleNum; + + // Track number of tracks. + NumTransTracks = CompressedData.CompressedTrackOffsets.Num()/4; + NumRotTracks = CompressedData.CompressedTrackOffsets.Num()/4; + NumScaleTracks = CompressedData.CompressedScaleOffsets.GetNumTracks(); + + // Track total number of keys. + TotalNumTransKeys = 0; + TotalNumRotKeys = 0; + TotalNumScaleKeys = 0; + + // Track number of tracks with a single key. + NumTransTracksWithOneKey = 0; + NumRotTracksWithOneKey = 0; + NumScaleTracksWithOneKey = 0; + + // Translation. + for ( int32 TrackIndex = 0; TrackIndex < NumTransTracks; ++TrackIndex ) { - const int32 TransStride = GetCompressedTranslationStride(Seq); - const int32 RotStride = GetCompressedRotationStride(Seq); - const int32 ScaleStride = GetCompressedScaleStride(Seq); - const int32 TransNum = CompressedTranslationNum[Seq->TranslationCompressionFormat]; - const int32 RotNum = CompressedRotationNum[Seq->RotationCompressionFormat]; - const int32 ScaleNum = CompressedScaleNum[Seq->ScaleCompressionFormat]; - - TranslationKeySize = TransStride * TransNum; - RotationKeySize = RotStride * RotNum; - ScaleKeySize = ScaleStride * ScaleNum; - - // Track number of tracks. - NumTransTracks = Seq->CompressedTrackOffsets.Num()/4; - NumRotTracks = Seq->CompressedTrackOffsets.Num()/4; - NumScaleTracks = Seq->CompressedScaleOffsets.GetNumTracks(); - - // Track total number of keys. - TotalNumTransKeys = 0; - TotalNumRotKeys = 0; - TotalNumScaleKeys = 0; - - // Track number of tracks with a single key. - NumTransTracksWithOneKey = 0; - NumRotTracksWithOneKey = 0; - NumScaleTracksWithOneKey = 0; - - // Translation. - for ( int32 TrackIndex = 0; TrackIndex < NumTransTracks; ++TrackIndex ) + const int32 NumKeys = CompressedData.CompressedTrackOffsets[TrackIndex*4+1]; + TotalNumTransKeys += NumKeys; + if ( NumKeys == 1 ) { - const int32 NumKeys = Seq->CompressedTrackOffsets[TrackIndex*4+1]; - TotalNumTransKeys += NumKeys; - if ( NumKeys == 1 ) - { - ++NumTransTracksWithOneKey; - } - else - { - OverheadSize += (Seq->KeyEncodingFormat == AKF_VariableKeyLerp) ? NumKeys * KeyFrameLookupSize : 0; - } + ++NumTransTracksWithOneKey; } - - // Rotation. - for ( int32 TrackIndex = 0; TrackIndex < NumRotTracks; ++TrackIndex ) + else { - const int32 NumKeys = Seq->CompressedTrackOffsets[TrackIndex*4+3]; - TotalNumRotKeys += NumKeys; - if ( NumKeys == 1 ) - { - ++NumRotTracksWithOneKey; - } - else - { - OverheadSize += (Seq->KeyEncodingFormat == AKF_VariableKeyLerp) ? NumKeys * KeyFrameLookupSize : 0; - } + OverheadSize += (CompressedData.KeyEncodingFormat == AKF_VariableKeyLerp) ? NumKeys * KeyFrameLookupSize : 0; } - - // Scale. - for ( int32 ScaleIndex = 0; ScaleIndex < NumScaleTracks; ++ScaleIndex ) - { - const int32 NumKeys = Seq->CompressedScaleOffsets.GetOffsetData(ScaleIndex, 1); - TotalNumScaleKeys += NumKeys; - if ( NumKeys == 1 ) - { - ++NumScaleTracksWithOneKey; - } - else - { - OverheadSize += (Seq->KeyEncodingFormat == AKF_VariableKeyLerp) ? NumKeys * KeyFrameLookupSize : 0; - } - } - - // Add in scaling values (min+range for interval encoding) - OverheadSize += (Seq->RotationCompressionFormat == ACF_IntervalFixed32NoW) ? (NumRotTracks - NumRotTracksWithOneKey) * sizeof(float) * 6 : 0; - OverheadSize += (Seq->TranslationCompressionFormat == ACF_IntervalFixed32NoW) ? (NumTransTracks - NumTransTracksWithOneKey) * sizeof(float) * 6 : 0; - OverheadSize += (Seq->ScaleCompressionFormat == ACF_IntervalFixed32NoW) ? (NumScaleTracks - NumScaleTracksWithOneKey) * sizeof(float) * 6 : 0; } - else + + // Rotation. + for ( int32 TrackIndex = 0; TrackIndex < NumRotTracks; ++TrackIndex ) { - int32 TotalTransKeysThatContributedSize = 0; - int32 TotalRotKeysThatContributedSize = 0; - int32 TotalScaleKeysThatContributedSize = 0; - - TranslationKeySize = 0; - RotationKeySize = 0; - ScaleKeySize = 0; - - // Track number of tracks. - NumTransTracks = Seq->CompressedTrackOffsets.Num() / 2; - NumRotTracks = Seq->CompressedTrackOffsets.Num() / 2; - // should be without divided by 2 - NumScaleTracks = Seq->CompressedScaleOffsets.GetNumTracks(); - - // Track total number of keys. - TotalNumTransKeys = 0; - TotalNumRotKeys = 0; - TotalNumScaleKeys = 0; - - // Track number of tracks with a single key. - NumTransTracksWithOneKey = 0; - NumRotTracksWithOneKey = 0; - NumScaleTracksWithOneKey = 0; - - // Translation. - for (int32 TrackIndex = 0; TrackIndex < NumTransTracks; ++TrackIndex) + const int32 NumKeys = CompressedData.CompressedTrackOffsets[TrackIndex*4+3]; + TotalNumRotKeys += NumKeys; + if ( NumKeys == 1 ) { - const int32 TransOffset = Seq->CompressedTrackOffsets[TrackIndex*2+0]; - if (TransOffset == INDEX_NONE) + ++NumRotTracksWithOneKey; + } + else + { + OverheadSize += (CompressedData.KeyEncodingFormat == AKF_VariableKeyLerp) ? NumKeys * KeyFrameLookupSize : 0; + } + } + + // Scale. + for ( int32 ScaleIndex = 0; ScaleIndex < NumScaleTracks; ++ScaleIndex ) + { + const int32 NumKeys = CompressedData.CompressedScaleOffsets.GetOffsetData(ScaleIndex, 1); + TotalNumScaleKeys += NumKeys; + if ( NumKeys == 1 ) + { + ++NumScaleTracksWithOneKey; + } + else + { + OverheadSize += (CompressedData.KeyEncodingFormat == AKF_VariableKeyLerp) ? NumKeys * KeyFrameLookupSize : 0; + } + } + + // Add in scaling values (min+range for interval encoding) + OverheadSize += (CompressedData.RotationCompressionFormat == ACF_IntervalFixed32NoW) ? (NumRotTracks - NumRotTracksWithOneKey) * sizeof(float) * 6 : 0; + OverheadSize += (CompressedData.TranslationCompressionFormat == ACF_IntervalFixed32NoW) ? (NumTransTracks - NumTransTracksWithOneKey) * sizeof(float) * 6 : 0; + OverheadSize += (CompressedData.ScaleCompressionFormat == ACF_IntervalFixed32NoW) ? (NumScaleTracks - NumScaleTracksWithOneKey) * sizeof(float) * 6 : 0; + } + else + { + int32 TotalTransKeysThatContributedSize = 0; + int32 TotalRotKeysThatContributedSize = 0; + int32 TotalScaleKeysThatContributedSize = 0; + + TranslationKeySize = 0; + RotationKeySize = 0; + ScaleKeySize = 0; + + // Track number of tracks. + NumTransTracks = CompressedData.CompressedTrackOffsets.Num() / 2; + NumRotTracks = CompressedData.CompressedTrackOffsets.Num() / 2; + // should be without divided by 2 + NumScaleTracks = CompressedData.CompressedScaleOffsets.GetNumTracks(); + + // Track total number of keys. + TotalNumTransKeys = 0; + TotalNumRotKeys = 0; + TotalNumScaleKeys = 0; + + // Track number of tracks with a single key. + NumTransTracksWithOneKey = 0; + NumRotTracksWithOneKey = 0; + NumScaleTracksWithOneKey = 0; + + // Translation. + for (int32 TrackIndex = 0; TrackIndex < NumTransTracks; ++TrackIndex) + { + const int32 TransOffset = CompressedData.CompressedTrackOffsets[TrackIndex*2+0]; + if (TransOffset == INDEX_NONE) + { + ++TotalNumTransKeys; + ++NumTransTracksWithOneKey; + } + else + { + const int32 Header = *((int32*)(CompressedData.CompressedByteStream.GetData() + TransOffset)); + + int32 KeyFormat; + int32 FormatFlags; + int32 TrackBytesPerKey; + int32 TrackFixedBytes; + int32 NumKeys; + + FAnimationCompression_PerTrackUtils::DecomposeHeader(Header, /*OUT*/ KeyFormat, /*OUT*/ NumKeys, /*OUT*/ FormatFlags, /*OUT*/ TrackBytesPerKey, /*OUT*/ TrackFixedBytes); + TranslationKeySize += TrackBytesPerKey * NumKeys; + TotalTransKeysThatContributedSize += NumKeys; + OverheadSize += TrackFixedBytes; + OverheadSize += ((FormatFlags & 0x08) != 0) ? NumKeys * KeyFrameLookupSize : 0; + + TotalNumTransKeys += NumKeys; + if (NumKeys <= 1) { - ++TotalNumTransKeys; ++NumTransTracksWithOneKey; } - else - { - const int32 Header = *((int32*)(Seq->CompressedByteStream.GetData() + TransOffset)); - - int32 KeyFormat; - int32 FormatFlags; - int32 TrackBytesPerKey; - int32 TrackFixedBytes; - int32 NumKeys; - - FAnimationCompression_PerTrackUtils::DecomposeHeader(Header, /*OUT*/ KeyFormat, /*OUT*/ NumKeys, /*OUT*/ FormatFlags, /*OUT*/ TrackBytesPerKey, /*OUT*/ TrackFixedBytes); - TranslationKeySize += TrackBytesPerKey * NumKeys; - TotalTransKeysThatContributedSize += NumKeys; - OverheadSize += TrackFixedBytes; - OverheadSize += ((FormatFlags & 0x08) != 0) ? NumKeys * KeyFrameLookupSize : 0; - - TotalNumTransKeys += NumKeys; - if (NumKeys <= 1) - { - ++NumTransTracksWithOneKey; - } - } } + } - // Rotation. - for (int32 TrackIndex = 0; TrackIndex < NumRotTracks; ++TrackIndex) + // Rotation. + for (int32 TrackIndex = 0; TrackIndex < NumRotTracks; ++TrackIndex) + { + const int32 RotOffset = CompressedData.CompressedTrackOffsets[TrackIndex*2+1]; + if (RotOffset == INDEX_NONE) { - const int32 RotOffset = Seq->CompressedTrackOffsets[TrackIndex*2+1]; - if (RotOffset == INDEX_NONE) + ++TotalNumRotKeys; + ++NumRotTracksWithOneKey; + } + else + { + const int32 Header = *((int32*)(CompressedData.CompressedByteStream.GetData() + RotOffset)); + + int32 KeyFormat; + int32 FormatFlags; + int32 TrackBytesPerKey; + int32 TrackFixedBytes; + int32 NumKeys; + + FAnimationCompression_PerTrackUtils::DecomposeHeader(Header, /*OUT*/ KeyFormat, /*OUT*/ NumKeys, /*OUT*/ FormatFlags, /*OUT*/ TrackBytesPerKey, /*OUT*/ TrackFixedBytes); + RotationKeySize += TrackBytesPerKey * NumKeys; + TotalRotKeysThatContributedSize += NumKeys; + OverheadSize += TrackFixedBytes; + OverheadSize += ((FormatFlags & 0x08) != 0) ? NumKeys * KeyFrameLookupSize : 0; + + TotalNumRotKeys += NumKeys; + if (NumKeys <= 1) { - ++TotalNumRotKeys; ++NumRotTracksWithOneKey; } - else - { - const int32 Header = *((int32*)(Seq->CompressedByteStream.GetData() + RotOffset)); - - int32 KeyFormat; - int32 FormatFlags; - int32 TrackBytesPerKey; - int32 TrackFixedBytes; - int32 NumKeys; - - FAnimationCompression_PerTrackUtils::DecomposeHeader(Header, /*OUT*/ KeyFormat, /*OUT*/ NumKeys, /*OUT*/ FormatFlags, /*OUT*/ TrackBytesPerKey, /*OUT*/ TrackFixedBytes); - RotationKeySize += TrackBytesPerKey * NumKeys; - TotalRotKeysThatContributedSize += NumKeys; - OverheadSize += TrackFixedBytes; - OverheadSize += ((FormatFlags & 0x08) != 0) ? NumKeys * KeyFrameLookupSize : 0; - - TotalNumRotKeys += NumKeys; - if (NumKeys <= 1) - { - ++NumRotTracksWithOneKey; - } - } } + } - // Scale. - for (int32 TrackIndex = 0; TrackIndex < NumScaleTracks; ++TrackIndex) + // Scale. + for (int32 TrackIndex = 0; TrackIndex < NumScaleTracks; ++TrackIndex) + { + const int32 ScaleOffset = CompressedData.CompressedScaleOffsets.GetOffsetData(TrackIndex, 0); + if (ScaleOffset == INDEX_NONE) { - const int32 ScaleOffset = Seq->CompressedScaleOffsets.GetOffsetData(TrackIndex, 0); - if (ScaleOffset == INDEX_NONE) + ++TotalNumScaleKeys; + ++NumScaleTracksWithOneKey; + } + else + { + const int32 Header = *((int32*)(CompressedData.CompressedByteStream.GetData() + ScaleOffset)); + + int32 KeyFormat; + int32 FormatFlags; + int32 TrackBytesPerKey; + int32 TrackFixedBytes; + int32 NumKeys; + + FAnimationCompression_PerTrackUtils::DecomposeHeader(Header, /*OUT*/ KeyFormat, /*OUT*/ NumKeys, /*OUT*/ FormatFlags, /*OUT*/ TrackBytesPerKey, /*OUT*/ TrackFixedBytes); + ScaleKeySize += TrackBytesPerKey * NumKeys; + TotalScaleKeysThatContributedSize += NumKeys; + OverheadSize += TrackFixedBytes; + OverheadSize += ((FormatFlags & 0x08) != 0) ? NumKeys * KeyFrameLookupSize : 0; + + TotalNumScaleKeys += NumKeys; + if (NumKeys <= 1) { - ++TotalNumScaleKeys; ++NumScaleTracksWithOneKey; } - else - { - const int32 Header = *((int32*)(Seq->CompressedByteStream.GetData() + ScaleOffset)); - - int32 KeyFormat; - int32 FormatFlags; - int32 TrackBytesPerKey; - int32 TrackFixedBytes; - int32 NumKeys; - - FAnimationCompression_PerTrackUtils::DecomposeHeader(Header, /*OUT*/ KeyFormat, /*OUT*/ NumKeys, /*OUT*/ FormatFlags, /*OUT*/ TrackBytesPerKey, /*OUT*/ TrackFixedBytes); - ScaleKeySize += TrackBytesPerKey * NumKeys; - TotalScaleKeysThatContributedSize += NumKeys; - OverheadSize += TrackFixedBytes; - OverheadSize += ((FormatFlags & 0x08) != 0) ? NumKeys * KeyFrameLookupSize : 0; - - TotalNumScaleKeys += NumKeys; - if (NumKeys <= 1) - { - ++NumScaleTracksWithOneKey; - } - } } + } - // Average key sizes - if (TotalRotKeysThatContributedSize > 0) - { - RotationKeySize = RotationKeySize / TotalRotKeysThatContributedSize; - } + // Average key sizes + if (TotalRotKeysThatContributedSize > 0) + { + RotationKeySize = RotationKeySize / TotalRotKeysThatContributedSize; + } - if (TotalTransKeysThatContributedSize > 0) - { - TranslationKeySize = TranslationKeySize / TotalTransKeysThatContributedSize; - } + if (TotalTransKeysThatContributedSize > 0) + { + TranslationKeySize = TranslationKeySize / TotalTransKeysThatContributedSize; + } - if (TotalScaleKeysThatContributedSize > 0) - { - ScaleKeySize = ScaleKeySize / TotalScaleKeysThatContributedSize; - } + if (TotalScaleKeysThatContributedSize > 0) + { + ScaleKeySize = ScaleKeySize / TotalScaleKeysThatContributedSize; } } } /** - * Sets the internal Animation Codec Interface Links within an Animation Sequence + * Sets the internal Animation Codec Interface Links within an Animation CompressedDatauence * * @param Seq An Animation Sequence to setup links within. */ -void AnimationFormat_SetInterfaceLinks(UAnimSequence& Seq) +template +void AnimationFormat_SetInterfaceLinks(CompressedDataType& CompressedData) { - Seq.TranslationCodec = NULL; - Seq.RotationCodec = NULL; - Seq.ScaleCodec = NULL; + CompressedData.TranslationCodec = NULL; + CompressedData.RotationCodec = NULL; + CompressedData.ScaleCodec = NULL; - if (Seq.KeyEncodingFormat == AKF_ConstantKeyLerp) + if (CompressedData.KeyEncodingFormat == AKF_ConstantKeyLerp) { static AEFConstantKeyLerp AEFConstantKeyLerp_None; static AEFConstantKeyLerp AEFConstantKeyLerp_Float96NoW; @@ -720,75 +691,75 @@ void AnimationFormat_SetInterfaceLinks(UAnimSequence& Seq) static AEFConstantKeyLerp AEFConstantKeyLerp_Identity; // setup translation codec - switch(Seq.TranslationCompressionFormat) + switch(CompressedData.TranslationCompressionFormat) { case ACF_None: - Seq.TranslationCodec = &AEFConstantKeyLerp_None; + CompressedData.TranslationCodec = &AEFConstantKeyLerp_None; break; case ACF_Float96NoW: - Seq.TranslationCodec = &AEFConstantKeyLerp_Float96NoW; + CompressedData.TranslationCodec = &AEFConstantKeyLerp_Float96NoW; break; case ACF_IntervalFixed32NoW: - Seq.TranslationCodec = &AEFConstantKeyLerp_IntervalFixed32NoW; + CompressedData.TranslationCodec = &AEFConstantKeyLerp_IntervalFixed32NoW; break; case ACF_Identity: - Seq.TranslationCodec = &AEFConstantKeyLerp_Identity; + CompressedData.TranslationCodec = &AEFConstantKeyLerp_Identity; break; default: - UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported translation compression"), (int32)Seq.TranslationCompressionFormat ); + UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported translation compression"), (int32)CompressedData.TranslationCompressionFormat ); }; // setup rotation codec - switch(Seq.RotationCompressionFormat) + switch(CompressedData.RotationCompressionFormat) { case ACF_None: - Seq.RotationCodec = &AEFConstantKeyLerp_None; + CompressedData.RotationCodec = &AEFConstantKeyLerp_None; break; case ACF_Float96NoW: - Seq.RotationCodec = &AEFConstantKeyLerp_Float96NoW; + CompressedData.RotationCodec = &AEFConstantKeyLerp_Float96NoW; break; case ACF_Fixed48NoW: - Seq.RotationCodec = &AEFConstantKeyLerp_Fixed48NoW; + CompressedData.RotationCodec = &AEFConstantKeyLerp_Fixed48NoW; break; case ACF_IntervalFixed32NoW: - Seq.RotationCodec = &AEFConstantKeyLerp_IntervalFixed32NoW; + CompressedData.RotationCodec = &AEFConstantKeyLerp_IntervalFixed32NoW; break; case ACF_Fixed32NoW: - Seq.RotationCodec = &AEFConstantKeyLerp_Fixed32NoW; + CompressedData.RotationCodec = &AEFConstantKeyLerp_Fixed32NoW; break; case ACF_Float32NoW: - Seq.RotationCodec = &AEFConstantKeyLerp_Float32NoW; + CompressedData.RotationCodec = &AEFConstantKeyLerp_Float32NoW; break; case ACF_Identity: - Seq.RotationCodec = &AEFConstantKeyLerp_Identity; + CompressedData.RotationCodec = &AEFConstantKeyLerp_Identity; break; default: - UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported rotation compression"), (int32)Seq.RotationCompressionFormat ); + UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported rotation compression"), (int32)CompressedData.RotationCompressionFormat ); }; // setup Scale codec - switch(Seq.ScaleCompressionFormat) + switch(CompressedData.ScaleCompressionFormat) { case ACF_None: - Seq.ScaleCodec = &AEFConstantKeyLerp_None; + CompressedData.ScaleCodec = &AEFConstantKeyLerp_None; break; case ACF_Float96NoW: - Seq.ScaleCodec = &AEFConstantKeyLerp_Float96NoW; + CompressedData.ScaleCodec = &AEFConstantKeyLerp_Float96NoW; break; case ACF_IntervalFixed32NoW: - Seq.ScaleCodec = &AEFConstantKeyLerp_IntervalFixed32NoW; + CompressedData.ScaleCodec = &AEFConstantKeyLerp_IntervalFixed32NoW; break; case ACF_Identity: - Seq.ScaleCodec = &AEFConstantKeyLerp_Identity; + CompressedData.ScaleCodec = &AEFConstantKeyLerp_Identity; break; default: - UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported Scale compression"), (int32)Seq.ScaleCompressionFormat ); + UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported Scale compression"), (int32)CompressedData.ScaleCompressionFormat ); }; } - else if (Seq.KeyEncodingFormat == AKF_VariableKeyLerp) + else if (CompressedData.KeyEncodingFormat == AKF_VariableKeyLerp) { static AEFVariableKeyLerp AEFVariableKeyLerp_None; static AEFVariableKeyLerp AEFVariableKeyLerp_Float96NoW; @@ -799,91 +770,94 @@ void AnimationFormat_SetInterfaceLinks(UAnimSequence& Seq) static AEFVariableKeyLerp AEFVariableKeyLerp_Identity; // setup translation codec - switch(Seq.TranslationCompressionFormat) + switch(CompressedData.TranslationCompressionFormat) { case ACF_None: - Seq.TranslationCodec = &AEFVariableKeyLerp_None; + CompressedData.TranslationCodec = &AEFVariableKeyLerp_None; break; case ACF_Float96NoW: - Seq.TranslationCodec = &AEFVariableKeyLerp_Float96NoW; + CompressedData.TranslationCodec = &AEFVariableKeyLerp_Float96NoW; break; case ACF_IntervalFixed32NoW: - Seq.TranslationCodec = &AEFVariableKeyLerp_IntervalFixed32NoW; + CompressedData.TranslationCodec = &AEFVariableKeyLerp_IntervalFixed32NoW; break; case ACF_Identity: - Seq.TranslationCodec = &AEFVariableKeyLerp_Identity; + CompressedData.TranslationCodec = &AEFVariableKeyLerp_Identity; break; default: - UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported translation compression"), (int32)Seq.TranslationCompressionFormat ); + UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported translation compression"), (int32)CompressedData.TranslationCompressionFormat ); }; // setup rotation codec - switch(Seq.RotationCompressionFormat) + switch(CompressedData.RotationCompressionFormat) { case ACF_None: - Seq.RotationCodec = &AEFVariableKeyLerp_None; + CompressedData.RotationCodec = &AEFVariableKeyLerp_None; break; case ACF_Float96NoW: - Seq.RotationCodec = &AEFVariableKeyLerp_Float96NoW; + CompressedData.RotationCodec = &AEFVariableKeyLerp_Float96NoW; break; case ACF_Fixed48NoW: - Seq.RotationCodec = &AEFVariableKeyLerp_Fixed48NoW; + CompressedData.RotationCodec = &AEFVariableKeyLerp_Fixed48NoW; break; case ACF_IntervalFixed32NoW: - Seq.RotationCodec = &AEFVariableKeyLerp_IntervalFixed32NoW; + CompressedData.RotationCodec = &AEFVariableKeyLerp_IntervalFixed32NoW; break; case ACF_Fixed32NoW: - Seq.RotationCodec = &AEFVariableKeyLerp_Fixed32NoW; + CompressedData.RotationCodec = &AEFVariableKeyLerp_Fixed32NoW; break; case ACF_Float32NoW: - Seq.RotationCodec = &AEFVariableKeyLerp_Float32NoW; + CompressedData.RotationCodec = &AEFVariableKeyLerp_Float32NoW; break; case ACF_Identity: - Seq.RotationCodec = &AEFVariableKeyLerp_Identity; + CompressedData.RotationCodec = &AEFVariableKeyLerp_Identity; break; default: - UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported rotation compression"), (int32)Seq.RotationCompressionFormat ); + UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported rotation compression"), (int32)CompressedData.RotationCompressionFormat ); }; // setup Scale codec - switch(Seq.ScaleCompressionFormat) + switch(CompressedData.ScaleCompressionFormat) { case ACF_None: - Seq.ScaleCodec = &AEFVariableKeyLerp_None; + CompressedData.ScaleCodec = &AEFVariableKeyLerp_None; break; case ACF_Float96NoW: - Seq.ScaleCodec = &AEFVariableKeyLerp_Float96NoW; + CompressedData.ScaleCodec = &AEFVariableKeyLerp_Float96NoW; break; case ACF_IntervalFixed32NoW: - Seq.ScaleCodec = &AEFVariableKeyLerp_IntervalFixed32NoW; + CompressedData.ScaleCodec = &AEFVariableKeyLerp_IntervalFixed32NoW; break; case ACF_Identity: - Seq.ScaleCodec = &AEFVariableKeyLerp_Identity; + CompressedData.ScaleCodec = &AEFVariableKeyLerp_Identity; break; default: - UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported Scale compression"), (int32)Seq.ScaleCompressionFormat ); + UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported Scale compression"), (int32)CompressedData.ScaleCompressionFormat ); }; } - else if (Seq.KeyEncodingFormat == AKF_PerTrackCompression) + else if (CompressedData.KeyEncodingFormat == AKF_PerTrackCompression) { static AEFPerTrackCompressionCodec StaticCodec; - Seq.RotationCodec = &StaticCodec; - Seq.TranslationCodec = &StaticCodec; - Seq.ScaleCodec = &StaticCodec; + CompressedData.RotationCodec = &StaticCodec; + CompressedData.TranslationCodec = &StaticCodec; + CompressedData.ScaleCodec = &StaticCodec; - check(Seq.RotationCompressionFormat == ACF_Identity); - check(Seq.TranslationCompressionFormat == ACF_Identity); + check(CompressedData.RotationCompressionFormat == ACF_Identity); + check(CompressedData.TranslationCompressionFormat == ACF_Identity); // commenting out scale check because the older versions won't have this set correctly // and I can't get the version VER_UE4_ANIM_SUPPORT_NONUNIFORM_SCALE_ANIMATION here because this function // is called in Serialize where GetLinker is too early to call - //checkf(Seq.ScaleCompressionFormat == ACF_Identity); + //checkf(CompressedData.ScaleCompressionFormat == ACF_Identity); } else { - UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported animation format"), (int32)Seq.KeyEncodingFormat ); + UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported animation format"), (int32)CompressedData.KeyEncodingFormat ); } } + +template void AnimationFormat_SetInterfaceLinks(FUECompressedAnimData& CompressedData); +template void AnimationFormat_SetInterfaceLinks(FCompressibleAnimDataResult& CompressedData); \ No newline at end of file diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimEncoding_ConstantKeyLerp.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimEncoding_ConstantKeyLerp.cpp index c2e263c17d7c..6d8c479e302a 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimEncoding_ConstantKeyLerp.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimEncoding_ConstantKeyLerp.cpp @@ -11,19 +11,19 @@ /** * Handles the ByteSwap of compressed rotation data on import * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The FMemoryReader to read from. * @param TrackData The compressed data stream. * @param NumKeys The number of keys present in the stream. */ void AEFConstantKeyLerpShared::ByteSwapRotationIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader, uint8*& TrackData, int32 NumKeys) { // Calculate the effective compression (in a track with only one key, it's always stored lossless) - const int32 EffectiveFormat = (NumKeys == 1) ? ACF_Float96NoW : (int32) Seq.RotationCompressionFormat; + const int32 EffectiveFormat = (NumKeys == 1) ? ACF_Float96NoW : (int32)CompressedData.RotationCompressionFormat; const int32 KeyComponentSize = CompressedRotationStrides[EffectiveFormat]; const int32 KeyNumComponents = CompressedRotationNum[EffectiveFormat]; @@ -49,19 +49,19 @@ void AEFConstantKeyLerpShared::ByteSwapRotationIn( /** * Handles the ByteSwap of compressed translation data on import * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The FMemoryReader to read from. * @param TrackData The compressed data stream. * @param NumKeys The number of keys present in the stream. */ void AEFConstantKeyLerpShared::ByteSwapTranslationIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader, uint8*& TrackData, int32 NumKeys) { // Calculate the effective compression (in a track with only one key, it's always stored lossless) - const int32 EffectiveFormat = (NumKeys == 1) ? ACF_None : (int32) Seq.TranslationCompressionFormat; + const int32 EffectiveFormat = (NumKeys == 1) ? ACF_None : (int32) CompressedData.TranslationCompressionFormat; const int32 KeyComponentSize = CompressedTranslationStrides[EffectiveFormat]; const int32 KeyNumComponents = CompressedTranslationNum[EffectiveFormat]; @@ -87,19 +87,19 @@ void AEFConstantKeyLerpShared::ByteSwapTranslationIn( /** * Handles the ByteSwap of compressed Scale data on import * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The FMemoryReader to read from. * @param TrackData The compressed data stream. * @param NumKeys The number of keys present in the stream. */ void AEFConstantKeyLerpShared::ByteSwapScaleIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader, uint8*& TrackData, int32 NumKeys) { // Calculate the effective compression (in a track with only one key, it's always stored lossless) - const int32 EffectiveFormat = (NumKeys == 1) ? ACF_None : (int32) Seq.ScaleCompressionFormat; + const int32 EffectiveFormat = (NumKeys == 1) ? ACF_None : (int32)CompressedData.ScaleCompressionFormat; const int32 KeyComponentSize = CompressedScaleStrides[EffectiveFormat]; const int32 KeyNumComponents = CompressedScaleNum[EffectiveFormat]; @@ -124,19 +124,19 @@ void AEFConstantKeyLerpShared::ByteSwapScaleIn( /** * Handles the ByteSwap of compressed rotation data on export * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryWriter The FMemoryWriter to write to. * @param TrackData The compressed data stream. * @param NumKeys The number of keys to write to the stream. */ void AEFConstantKeyLerpShared::ByteSwapRotationOut( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryWriter& MemoryWriter, uint8*& TrackData, int32 NumKeys) { // Calculate the effective compression (in a track with only one key, it's always stored lossless) - const int32 EffectiveFormat = (NumKeys == 1) ? ACF_Float96NoW : (int32) Seq.RotationCompressionFormat; + const int32 EffectiveFormat = (NumKeys == 1) ? ACF_Float96NoW : (int32) CompressedData.RotationCompressionFormat; const int32 KeyComponentSize = CompressedRotationStrides[EffectiveFormat]; const int32 KeyNumComponents = CompressedRotationNum[EffectiveFormat]; @@ -162,19 +162,19 @@ void AEFConstantKeyLerpShared::ByteSwapRotationOut( /** * Handles the ByteSwap of compressed translation data on export * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryWriter The FMemoryWriter to write to. * @param TrackData The compressed data stream. * @param NumKeys The number of keys to write to the stream. */ void AEFConstantKeyLerpShared::ByteSwapTranslationOut( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryWriter& MemoryWriter, uint8*& TrackData, int32 NumKeys) { // Calculate the effective compression (in a track with only one key, it's always stored lossless) - const int32 EffectiveFormat = (NumKeys == 1) ? ACF_None : (int32) Seq.TranslationCompressionFormat; + const int32 EffectiveFormat = (NumKeys == 1) ? ACF_None : (int32) CompressedData.TranslationCompressionFormat; const int32 KeyComponentSize = CompressedTranslationStrides[EffectiveFormat]; const int32 KeyNumComponents = CompressedTranslationNum[EffectiveFormat]; @@ -200,19 +200,19 @@ void AEFConstantKeyLerpShared::ByteSwapTranslationOut( /** * Handles the ByteSwap of compressed Scale data on export * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryWriter The FMemoryWriter to write to. * @param TrackData The compressed data stream. * @param NumKeys The number of keys to write to the stream. */ void AEFConstantKeyLerpShared::ByteSwapScaleOut( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryWriter& MemoryWriter, uint8*& TrackData, int32 NumKeys) { // Calculate the effective compression (in a track with only one key, it's always stored lossless) - const int32 EffectiveFormat = (NumKeys == 1) ? ACF_None : (int32) Seq.ScaleCompressionFormat; + const int32 EffectiveFormat = (NumKeys == 1) ? ACF_None : (int32) CompressedData.ScaleCompressionFormat; const int32 KeyComponentSize = CompressedScaleStrides[EffectiveFormat]; const int32 KeyNumComponents = CompressedScaleNum[EffectiveFormat]; diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimEncoding_PerTrackCompression.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimEncoding_PerTrackCompression.cpp index a646efbc443f..46fa56ed8c70 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimEncoding_PerTrackCompression.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimEncoding_PerTrackCompression.cpp @@ -345,26 +345,17 @@ static FORCEINLINE_DEBUGGABLE VectorRegister DecompressSingleTrackRotationVector #endif // USE_VECTOR_PTC_DECOMPRESSOR -/** - * Handles Byte-swapping a single track of animation data from a MemoryReader or to a MemoryWriter - * - * @param Seq The Animation Sequence being operated on. - * @param MemoryStream The MemoryReader or MemoryWriter object to read from/write to. - * @param Offset The starting offset into the compressed byte stream for this track (can be INDEX_NONE to indicate an identity track) - */ template -void AEFPerTrackCompressionCodec::ByteSwapOneTrack(UAnimSequence& Seq, TArchive& MemoryStream, int32 Offset, bool bMaintainComponentOrder) +void AEFPerTrackCompressionCodec::ByteSwapOneTrack(FUECompressedAnimData& CompressedData, TArchive& MemoryStream, int32 BufferStart, int32 Offset) { // Translation data. if (Offset != INDEX_NONE) { checkSlow( (Offset % 4) == 0 && "CompressedByteStream not aligned to four bytes" ); - if (bMaintainComponentOrder) - { - MemoryStream.Seek(Offset); - } - uint8* TrackData = Seq.CompressedByteStream.GetData() + Offset; + MemoryStream.Seek(BufferStart+Offset); + + uint8* TrackData = CompressedData.CompressedByteStream.GetData() + Offset; // Read the header AC_UnalignedSwap(MemoryStream, TrackData, sizeof(int32)); @@ -383,6 +374,9 @@ void AEFPerTrackCompressionCodec::ByteSwapOneTrack(UAnimSequence& Seq, TArchive& int32 KeyComponentCount = 0; FAnimationCompression_PerTrackUtils::GetAllSizesFromFormat(KeyFormat, FormatFlags, /*OUT*/ KeyComponentCount, /*OUT*/ KeyComponentSize, /*OUT*/ FixedComponentCount, /*OUT*/ FixedComponentSize); + check(FixedComponentSize != 0); + check(KeyComponentSize != 0); + // Handle per-track metadata for (int32 i = 0; i < FixedComponentCount; ++i) { @@ -404,7 +398,7 @@ void AEFPerTrackCompressionCodec::ByteSwapOneTrack(UAnimSequence& Seq, TArchive& // Make sure the key->frame table is 4 byte aligned PreservePadding(TrackData, MemoryStream); - const int32 FrameTableEntrySize = (Seq.GetCompressedNumberOfFrames() <= 0xFF) ? sizeof(uint8) : sizeof(uint16); + const int32 FrameTableEntrySize = (CompressedData.CompressedNumberOfFrames <= 0xFF) ? sizeof(uint8) : sizeof(uint16); for (int32 i = 0; i < NumKeys; ++i) { AC_UnalignedSwap(MemoryStream, TrackData, FrameTableEntrySize); @@ -416,8 +410,8 @@ void AEFPerTrackCompressionCodec::ByteSwapOneTrack(UAnimSequence& Seq, TArchive& } } -template void AEFPerTrackCompressionCodec::ByteSwapOneTrack(UAnimSequence& Seq, FMemoryReader& MemoryStream, int32 Offset, bool bMaintainComponentOrder); -template void AEFPerTrackCompressionCodec::ByteSwapOneTrack(UAnimSequence& Seq, FMemoryWriter& MemoryStream, int32 Offset, bool bMaintainComponentOrder); +template void AEFPerTrackCompressionCodec::ByteSwapOneTrack(FUECompressedAnimData& CompressedData, FMemoryReader& MemoryStream, int32 BufferStart, int32 Offset); +template void AEFPerTrackCompressionCodec::ByteSwapOneTrack(FUECompressedAnimData& CompressedData, FMemoryWriter& MemoryStream, int32 BufferStart, int32 Offset); /** @@ -451,42 +445,33 @@ void AEFPerTrackCompressionCodec::PreservePadding(uint8*& TrackData, FMemoryArch /** * Handles Byte-swapping incoming animation data from a MemoryReader * - * @param Seq An Animation Sequence to contain the read data. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The MemoryReader object to read from. */ void AEFPerTrackCompressionCodec::ByteSwapIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader) { - int32 OriginalNumBytes = MemoryReader.TotalSize(); - Seq.CompressedByteStream.Empty(OriginalNumBytes); - Seq.CompressedByteStream.AddUninitialized(OriginalNumBytes); + const int64 BufferStart = MemoryReader.Tell(); + int64 OriginalNumBytes = MemoryReader.TotalSize() - BufferStart; + check(OriginalNumBytes < INT_MAX); + int32 OriginalNumBytes32 = (int32)OriginalNumBytes; + check(CompressedData.CompressedByteStream.Num() == OriginalNumBytes); - if (Seq.CompressedSegments.Num() != 0) - { -#if !PLATFORM_LITTLE_ENDIAN -#error "Byte swapping needs to be implemented here to support big-endian platforms" -#endif - - // TODO: Byte swap the new format - MemoryReader.Serialize(Seq.CompressedByteStream.GetData(), Seq.CompressedByteStream.Num()); - return; - } - - const int32 NumTracks = Seq.CompressedTrackOffsets.Num() / 2; - const bool bHasScaleData = Seq.CompressedScaleOffsets.IsValid(); + const int32 NumTracks = CompressedData.CompressedTrackOffsets.Num() / 2; + const bool bHasScaleData = CompressedData.CompressedScaleOffsets.IsValid(); for ( int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex ) { - const int32 OffsetTrans = Seq.CompressedTrackOffsets[TrackIndex*2+0]; - ByteSwapOneTrack(Seq, MemoryReader, OffsetTrans); + const int32 OffsetTrans = CompressedData.CompressedTrackOffsets[TrackIndex*2+0]; + ByteSwapOneTrack(CompressedData, MemoryReader, BufferStart, OffsetTrans); - const int32 OffsetRot = Seq.CompressedTrackOffsets[TrackIndex*2+1]; - ByteSwapOneTrack(Seq, MemoryReader, OffsetRot); + const int32 OffsetRot = CompressedData.CompressedTrackOffsets[TrackIndex*2+1]; + ByteSwapOneTrack(CompressedData, MemoryReader, BufferStart, OffsetRot); if (bHasScaleData) { - const int32 OffsetScale = Seq.CompressedScaleOffsets.GetOffsetData(TrackIndex, 0); - ByteSwapOneTrack(Seq, MemoryReader, OffsetScale); + const int32 OffsetScale = CompressedData.CompressedScaleOffsets.GetOffsetData(TrackIndex, 0); + ByteSwapOneTrack(CompressedData, MemoryReader, BufferStart, OffsetScale); } } } @@ -495,40 +480,30 @@ void AEFPerTrackCompressionCodec::ByteSwapIn( /** * Handles Byte-swapping outgoing animation data to an array of BYTEs * - * @param Seq An Animation Sequence to write. + * @param CompressedData The compressed animation data being operated on. * @param SerializedData The output buffer. * @param ForceByteSwapping true is byte swapping is not optional. */ void AEFPerTrackCompressionCodec::ByteSwapOut( - UAnimSequence& Seq, - TArray& SerializedData, - bool ForceByteSwapping, - bool bMaintainComponentOrder) + FUECompressedAnimData& CompressedData, + FMemoryWriter& MemoryWriter) { - FMemoryWriter MemoryWriter(SerializedData, true); - MemoryWriter.SetByteSwapping(ForceByteSwapping); + const int32 BufferStart = MemoryWriter.Tell(); - if (Seq.CompressedSegments.Num() != 0) - { - // TODO: Byte swap the new format - MemoryWriter.Serialize(Seq.CompressedByteStream.GetData(), Seq.CompressedByteStream.Num()); - return; - } - - const int32 NumTracks = Seq.CompressedTrackOffsets.Num() / 2; - const bool bHasScaleData = Seq.CompressedScaleOffsets.IsValid(); + const int32 NumTracks = CompressedData.CompressedTrackOffsets.Num() / 2; + const bool bHasScaleData = CompressedData.CompressedScaleOffsets.IsValid(); for ( int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex ) { - const int32 OffsetTrans = Seq.CompressedTrackOffsets[TrackIndex*2+0]; - ByteSwapOneTrack(Seq, MemoryWriter, OffsetTrans, bMaintainComponentOrder); + const int32 OffsetTrans = CompressedData.CompressedTrackOffsets[TrackIndex*2+0]; + ByteSwapOneTrack(CompressedData, MemoryWriter, BufferStart, OffsetTrans); - const int32 OffsetRot = Seq.CompressedTrackOffsets[TrackIndex*2+1]; - ByteSwapOneTrack(Seq, MemoryWriter, OffsetRot, bMaintainComponentOrder); + const int32 OffsetRot = CompressedData.CompressedTrackOffsets[TrackIndex*2+1]; + ByteSwapOneTrack(CompressedData, MemoryWriter, BufferStart, OffsetRot); if (bHasScaleData) { - const int32 OffsetScale = Seq.CompressedScaleOffsets.GetOffsetData(TrackIndex, 0); - ByteSwapOneTrack(Seq, MemoryWriter, OffsetScale, bMaintainComponentOrder); + const int32 OffsetScale = CompressedData.CompressedScaleOffsets.GetOffsetData(TrackIndex, 0); + ByteSwapOneTrack(CompressedData, MemoryWriter, BufferStart, OffsetScale); } } } @@ -608,12 +583,12 @@ void AEFPerTrackCompressionCodec::GetBoneAtomRotation( { if ((FormatFlags & 0x8) == 0) { - Alpha = TimeToIndex(*DecompContext.AnimSeq, DecompContext.RelativePos, NumKeys, Index0, Index1); + Alpha = TimeToIndex(DecompContext.GetSequenceLength(), DecompContext.RelativePos, NumKeys, DecompContext.GetInterpolation(), Index0, Index1); } else { const uint8* RESTRICT FrameTable = Align(TrackData + FixedBytes + BytesPerKey * NumKeys, 4); - Alpha = TimeToIndex(*DecompContext.AnimSeq, FrameTable, DecompContext.RelativePos, NumKeys, Index0, Index1); + Alpha = TimeToIndex(DecompContext.GetInterpolation(), DecompContext.GetCompressedNumberOfFrames(), FrameTable, DecompContext.RelativePos, NumKeys, Index0, Index1); } } @@ -702,8 +677,9 @@ void AEFPerTrackCompressionCodec::GetBoneAtomTranslation( int32 FixedBytes; FAnimationCompression_PerTrackUtils::DecomposeHeader(Header, /*OUT*/ KeyFormat, /*OUT*/ NumKeys, /*OUT*/ FormatFlags, /*OUT*/BytesPerKey, /*OUT*/ FixedBytes); - checkf(KeyFormat != ACF_None, TEXT("[%s] contians invalid keyformat. NumKeys (%d), FormatFlags (%d), BytesPerKeys (%d), FixedBytes (%d)"), *DecompContext.AnimSeq->GetName(), NumKeys, FormatFlags, BytesPerKey, FixedBytes); + checkf(KeyFormat != ACF_None, TEXT("[%s] contians invalid keyformat. NumKeys (%d), FormatFlags (%d), BytesPerKeys (%d), FixedBytes (%d)"), *DecompContext.GetAnimFName().ToString(), NumKeys, FormatFlags, BytesPerKey, FixedBytes); + checkf(KeyFormat < ACF_MAX, TEXT("[%s] contians invalid keyformat. NumKeys (%d), FormatFlags (%d), BytesPerKeys (%d), FixedBytes (%d), PosKeysOffset (%d), TrackIndex (%d)"), *DecompContext.GetAnimFName().ToString(), NumKeys, FormatFlags, BytesPerKey, FixedBytes, PosKeysOffset, TrackIndex) // Figure out the key indexes int32 Index0 = 0; int32 Index1 = 0; @@ -715,12 +691,12 @@ void AEFPerTrackCompressionCodec::GetBoneAtomTranslation( { if ((FormatFlags & 0x8) == 0) { - Alpha = TimeToIndex(*DecompContext.AnimSeq, DecompContext.RelativePos, NumKeys, Index0, Index1); + Alpha = TimeToIndex(DecompContext.GetSequenceLength(), DecompContext.RelativePos, NumKeys, DecompContext.GetInterpolation(), Index0, Index1); } else { const uint8* RESTRICT FrameTable = Align(TrackData + FixedBytes + BytesPerKey * NumKeys, 4); - Alpha = TimeToIndex(*DecompContext.AnimSeq, FrameTable, DecompContext.RelativePos, NumKeys, Index0, Index1); + Alpha = TimeToIndex(DecompContext.GetInterpolation(), DecompContext.GetCompressedNumberOfFrames(), FrameTable, DecompContext.RelativePos, NumKeys, Index0, Index1); } } @@ -819,12 +795,12 @@ void AEFPerTrackCompressionCodec::GetBoneAtomScale( { if ((FormatFlags & 0x8) == 0) { - Alpha = TimeToIndex(*DecompContext.AnimSeq, DecompContext.RelativePos, NumKeys, Index0, Index1); + Alpha = TimeToIndex(DecompContext.GetSequenceLength(), DecompContext.RelativePos, NumKeys, DecompContext.GetInterpolation(), Index0, Index1); } else { const uint8* RESTRICT FrameTable = Align(TrackData + FixedBytes + BytesPerKey * NumKeys, 4); - Alpha = TimeToIndex(*DecompContext.AnimSeq, FrameTable, DecompContext.RelativePos, NumKeys, Index0, Index1); + Alpha = TimeToIndex(DecompContext.GetInterpolation(), DecompContext.GetCompressedNumberOfFrames(), FrameTable, DecompContext.RelativePos, NumKeys, Index0, Index1); } } diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimEncoding_VariableKeyLerp.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimEncoding_VariableKeyLerp.cpp index 33d723911967..02cae338f212 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimEncoding_VariableKeyLerp.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimEncoding_VariableKeyLerp.cpp @@ -11,18 +11,18 @@ /** * Handles the ByteSwap of compressed rotation data on import * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The FMemoryReader to read from. * @param TrackData The compressed data stream. * @param NumKeys The number of keys present in the stream. */ void AEFVariableKeyLerpShared::ByteSwapRotationIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader, uint8*& TrackData, int32 NumKeys) { - AEFConstantKeyLerpShared::ByteSwapRotationIn(Seq, MemoryReader, TrackData, NumKeys); + AEFConstantKeyLerpShared::ByteSwapRotationIn(CompressedData, MemoryReader, TrackData, NumKeys); // Load the track table if present if (NumKeys > 1) @@ -32,7 +32,7 @@ void AEFVariableKeyLerpShared::ByteSwapRotationIn( PadMemoryReader(&MemoryReader, TrackData, 4); // swap the track table - const size_t EntryStride = (Seq.GetCompressedNumberOfFrames() > 0xFF) ? sizeof(uint16) : sizeof(uint8); + const size_t EntryStride = (CompressedData.CompressedNumberOfFrames > 0xFF) ? sizeof(uint16) : sizeof(uint8); for (int32 KeyIndex = 0; KeyIndex < NumKeys; ++KeyIndex) { AC_UnalignedSwap(MemoryReader, TrackData, EntryStride); @@ -43,18 +43,18 @@ void AEFVariableKeyLerpShared::ByteSwapRotationIn( /** * Handles the ByteSwap of compressed translation data on import * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The FMemoryReader to read from. * @param TrackData The compressed data stream. * @param NumKeys The number of keys present in the stream. */ void AEFVariableKeyLerpShared::ByteSwapTranslationIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader, uint8*& TrackData, int32 NumKeys) { - AEFConstantKeyLerpShared::ByteSwapTranslationIn(Seq, MemoryReader, TrackData, NumKeys); + AEFConstantKeyLerpShared::ByteSwapTranslationIn(CompressedData, MemoryReader, TrackData, NumKeys); // Load the track table if present if (NumKeys > 1) @@ -64,7 +64,7 @@ void AEFVariableKeyLerpShared::ByteSwapTranslationIn( PadMemoryReader(&MemoryReader, TrackData, 4); // swap the track table - const size_t EntryStride = (Seq.GetCompressedNumberOfFrames() > 0xFF) ? sizeof(uint16) : sizeof(uint8); + const size_t EntryStride = (CompressedData.CompressedNumberOfFrames > 0xFF) ? sizeof(uint16) : sizeof(uint8); for (int32 KeyIndex = 0; KeyIndex < NumKeys; ++KeyIndex) { AC_UnalignedSwap(MemoryReader, TrackData, EntryStride); @@ -75,18 +75,18 @@ void AEFVariableKeyLerpShared::ByteSwapTranslationIn( /** * Handles the ByteSwap of compressed Scale data on import * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The FMemoryReader to read from. * @param TrackData The compressed data stream. * @param NumKeys The number of keys present in the stream. */ void AEFVariableKeyLerpShared::ByteSwapScaleIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader, uint8*& TrackData, int32 NumKeys) { - AEFConstantKeyLerpShared::ByteSwapScaleIn(Seq, MemoryReader, TrackData, NumKeys); + AEFConstantKeyLerpShared::ByteSwapScaleIn(CompressedData, MemoryReader, TrackData, NumKeys); // Load the track table if present if (NumKeys > 1) @@ -96,7 +96,7 @@ void AEFVariableKeyLerpShared::ByteSwapScaleIn( PadMemoryReader(&MemoryReader, TrackData, 4); // swap the track table - const size_t EntryStride = (Seq.GetCompressedNumberOfFrames() > 0xFF) ? sizeof(uint16) : sizeof(uint8); + const size_t EntryStride = (CompressedData.CompressedNumberOfFrames > 0xFF) ? sizeof(uint16) : sizeof(uint8); for (int32 KeyIndex = 0; KeyIndex < NumKeys; ++KeyIndex) { AC_UnalignedSwap(MemoryReader, TrackData, EntryStride); @@ -106,18 +106,18 @@ void AEFVariableKeyLerpShared::ByteSwapScaleIn( /** * Handles the ByteSwap of compressed rotation data on export * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryWriter The FMemoryWriter to write to. * @param TrackData The compressed data stream. * @param NumKeys The number of keys to write to the stream. */ void AEFVariableKeyLerpShared::ByteSwapRotationOut( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryWriter& MemoryWriter, uint8*& TrackData, int32 NumKeys) { - AEFConstantKeyLerpShared::ByteSwapRotationOut(Seq, MemoryWriter, TrackData, NumKeys); + AEFConstantKeyLerpShared::ByteSwapRotationOut(CompressedData, MemoryWriter, TrackData, NumKeys); // Store the track table if needed if (NumKeys > 1) @@ -127,7 +127,7 @@ void AEFVariableKeyLerpShared::ByteSwapRotationOut( PadMemoryWriter(&MemoryWriter, TrackData, 4); // swap the track table - const size_t EntryStride = (Seq.GetCompressedNumberOfFrames() > 0xFF) ? sizeof(uint16) : sizeof(uint8); + const size_t EntryStride = (CompressedData.CompressedNumberOfFrames > 0xFF) ? sizeof(uint16) : sizeof(uint8); for (int32 KeyIndex = 0; KeyIndex < NumKeys; ++KeyIndex) { AC_UnalignedSwap(MemoryWriter, TrackData, EntryStride); @@ -138,18 +138,18 @@ void AEFVariableKeyLerpShared::ByteSwapRotationOut( /** * Handles the ByteSwap of compressed translation data on export * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryWriter The FMemoryWriter to write to. * @param TrackData The compressed data stream. * @param NumKeys The number of keys to write to the stream. */ void AEFVariableKeyLerpShared::ByteSwapTranslationOut( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryWriter& MemoryWriter, uint8*& TrackData, int32 NumKeys) { - AEFConstantKeyLerpShared::ByteSwapTranslationOut(Seq, MemoryWriter, TrackData, NumKeys); + AEFConstantKeyLerpShared::ByteSwapTranslationOut(CompressedData, MemoryWriter, TrackData, NumKeys); // Store the track table if needed if (NumKeys > 1) @@ -159,7 +159,7 @@ void AEFVariableKeyLerpShared::ByteSwapTranslationOut( PadMemoryWriter(&MemoryWriter, TrackData, 4); // swap the track table - const size_t EntryStride = (Seq.GetCompressedNumberOfFrames() > 0xFF) ? sizeof(uint16) : sizeof(uint8); + const size_t EntryStride = (CompressedData.CompressedNumberOfFrames > 0xFF) ? sizeof(uint16) : sizeof(uint8); for (int32 KeyIndex = 0; KeyIndex < NumKeys; ++KeyIndex) { AC_UnalignedSwap(MemoryWriter, TrackData, EntryStride); @@ -171,18 +171,18 @@ void AEFVariableKeyLerpShared::ByteSwapTranslationOut( /** * Handles the ByteSwap of compressed Scale data on export * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryWriter The FMemoryWriter to write to. * @param TrackData The compressed data stream. * @param NumKeys The number of keys to write to the stream. */ void AEFVariableKeyLerpShared::ByteSwapScaleOut( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryWriter& MemoryWriter, uint8*& TrackData, int32 NumKeys) { - AEFConstantKeyLerpShared::ByteSwapScaleOut(Seq, MemoryWriter, TrackData, NumKeys); + AEFConstantKeyLerpShared::ByteSwapScaleOut(CompressedData, MemoryWriter, TrackData, NumKeys); // Store the track table if needed if (NumKeys > 1) @@ -192,7 +192,7 @@ void AEFVariableKeyLerpShared::ByteSwapScaleOut( PadMemoryWriter(&MemoryWriter, TrackData, 4); // swap the track table - const size_t EntryStride = (Seq.GetCompressedNumberOfFrames() > 0xFF) ? sizeof(uint16) : sizeof(uint8); + const size_t EntryStride = (CompressedData.CompressedNumberOfFrames > 0xFF) ? sizeof(uint16) : sizeof(uint8); for (int32 KeyIndex = 0; KeyIndex < NumKeys; ++KeyIndex) { AC_UnalignedSwap(MemoryWriter, TrackData, EntryStride); diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimInstance.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimInstance.cpp index 8ea5da524834..e4fe4227498a 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimInstance.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimInstance.cpp @@ -721,7 +721,7 @@ void OutputCurveMap(TMap& CurveMap, UCanvas* Canvas, FDisplayDebug { TArray Names; CurveMap.GetKeys(Names); - Names.Sort(); + Names.Sort(FNameLexicalLess()); for (FName CurveName : Names) { FString CurveEntry = FString::Printf(TEXT("%s: %.3f"), *CurveName.ToString(), CurveMap[CurveName]); diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp index 18a4d8b2ca6c..dd7e6a0e8889 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp @@ -66,14 +66,65 @@ static FAutoConsoleVariableRef CVarOddFrameStripping( GPerformFrameStrippingOddFramedAnimations, TEXT("1 = When frame stripping apply to animations with an odd number of frames too. 0 = only even framed animations")); + + #if WITH_EDITOR +template +FGuid GetArrayGuid(TArrayView Array) +{ + FSHA1 Sha; + Sha.Update((uint8*)Array.GetData(), Array.Num() * Array.GetTypeSize()); + + Sha.Final(); + + uint32 Hash[5]; + Sha.GetHash((uint8*)Hash); + FGuid Guid(Hash[0] ^ Hash[4], Hash[1], Hash[2], Hash[3]); + return Guid; +} + void OnCVarsChanged() { if (GIsInitialLoad) { return; // not initialized } + + /*static TArray SequenceCache; + static FString OutputMessage; + + SequenceCache.Reset(); + + for (TObjectIterator It; It; ++It) + { + SequenceCache.Add(*It); + } + + SequenceCache.Sort([](const UAnimSequence& A, const UAnimSequence& B) + { + return A.GetFName() > B.GetFName(); + }); + + OutputMessage.Reset(); + + for (UAnimSequence* Seq : SequenceCache) + { + const FCompressedAnimSequence& AnimData = Seq->CompressedData; + const FUECompressedAnimData& UEAnimData = AnimData.CompressedDataStructure; + const int32 Additive = Seq->IsValidAdditive() ? 1 : 0; + OutputMessage += FString::Printf(TEXT("%s - %.2f Fr:%i Add:%i TO:%i SO:%i CBS:%i\n"), *Seq->GetName(), Seq->SequenceLength, Seq->GetRawNumberOfFrames(), Additive, UEAnimData.CompressedTrackOffsets.Num(), UEAnimData.CompressedScaleOffsets.OffsetData.Num(), UEAnimData.CompressedByteStream.Num()); + OutputMessage += FString::Printf(TEXT("\t K:%i (%i : %i : %i)\n"), (int32)UEAnimData.KeyEncodingFormat, (int32)UEAnimData.TranslationCompressionFormat, (int32)UEAnimData.RotationCompressionFormat, (int32)UEAnimData.ScaleCompressionFormat); + OutputMessage += FString::Printf(TEXT("\t Curve Codec:%s\n"), AnimData.CurveCompressionCodec ? *AnimData.CurveCompressionCodec->GetPathName() : TEXT("nullptr")); + OutputMessage += FString::Printf(TEXT("\t TrackOff:%s\n"), *GetArrayGuid(UEAnimData.CompressedTrackOffsets).ToString()); + OutputMessage += FString::Printf(TEXT("\t ScaleOff:%s\n"), *GetArrayGuid(UEAnimData.CompressedScaleOffsets.OffsetData).ToString()); + OutputMessage += FString::Printf(TEXT("\t BoneByteStream:%s\n"), *GetArrayGuid(UEAnimData.CompressedByteStream).ToString()); + OutputMessage += FString::Printf(TEXT("\t CurveByteStream:%s\n"), *GetArrayGuid(AnimData.CompressedCurveByteStream).ToString()); + } + + OutputMessage += FString::Printf(TEXT("\n\nTotalAnims: %i"), SequenceCache.Num()); + FPlatformApplicationMisc::ClipboardCopy(*OutputMessage);*/ + static bool bFirstRun = true; static bool bCompressionFrameStrip = (GPerformFrameStripping == 1); @@ -154,6 +205,170 @@ void OnCVarsChanged() FAutoConsoleVariableSink AnimationCVarSink(FConsoleCommandDelegate::CreateStatic(&OnCVarsChanged)); #endif +bool CompressRawAnimSequenceTrack(FRawAnimSequenceTrack& RawTrack, int32 NumFrames, FName ErrorName, float MaxPosDiff, float MaxAngleDiff) +{ + bool bRemovedKeys = false; + + // First part is to make sure we have valid input + bool const bPosTrackIsValid = (RawTrack.PosKeys.Num() == 1 || RawTrack.PosKeys.Num() == NumFrames); + if (!bPosTrackIsValid) + { + UE_LOG(LogAnimation, Warning, TEXT("Found non valid position track for %s, %d frames, instead of %d. Chopping!"), *ErrorName.ToString(), RawTrack.PosKeys.Num(), NumFrames); + bRemovedKeys = true; + RawTrack.PosKeys.RemoveAt(1, RawTrack.PosKeys.Num() - 1); + RawTrack.PosKeys.Shrink(); + check(RawTrack.PosKeys.Num() == 1); + } + + bool const bRotTrackIsValid = (RawTrack.RotKeys.Num() == 1 || RawTrack.RotKeys.Num() == NumFrames); + if (!bRotTrackIsValid) + { + UE_LOG(LogAnimation, Warning, TEXT("Found non valid rotation track for %s, %d frames, instead of %d. Chopping!"), *ErrorName.ToString(), RawTrack.RotKeys.Num(), NumFrames); + bRemovedKeys = true; + RawTrack.RotKeys.RemoveAt(1, RawTrack.RotKeys.Num() - 1); + RawTrack.RotKeys.Shrink(); + check(RawTrack.RotKeys.Num() == 1); + } + + // scale keys can be empty, and that is valid + bool const bScaleTrackIsValid = (RawTrack.ScaleKeys.Num() == 0 || RawTrack.ScaleKeys.Num() == 1 || RawTrack.ScaleKeys.Num() == NumFrames); + if (!bScaleTrackIsValid) + { + UE_LOG(LogAnimation, Warning, TEXT("Found non valid Scaleation track for %s, %d frames, instead of %d. Chopping!"), *ErrorName.ToString(), RawTrack.ScaleKeys.Num(), NumFrames); + bRemovedKeys = true; + RawTrack.ScaleKeys.RemoveAt(1, RawTrack.ScaleKeys.Num() - 1); + RawTrack.ScaleKeys.Shrink(); + check(RawTrack.ScaleKeys.Num() == 1); + } + + // Second part is actual compression. + + // Check variation of position keys + if ((RawTrack.PosKeys.Num() > 1) && (MaxPosDiff >= 0.0f)) + { + FVector FirstPos = RawTrack.PosKeys[0]; + bool bFramesIdentical = true; + for (int32 j = 1; j < RawTrack.PosKeys.Num() && bFramesIdentical; j++) + { + if ((FirstPos - RawTrack.PosKeys[j]).SizeSquared() > FMath::Square(MaxPosDiff)) + { + bFramesIdentical = false; + } + } + + // If all keys are the same, remove all but first frame + if (bFramesIdentical) + { + bRemovedKeys = true; + RawTrack.PosKeys.RemoveAt(1, RawTrack.PosKeys.Num() - 1); + RawTrack.PosKeys.Shrink(); + check(RawTrack.PosKeys.Num() == 1); + } + } + + // Check variation of rotational keys + if ((RawTrack.RotKeys.Num() > 1) && (MaxAngleDiff >= 0.0f)) + { + FQuat FirstRot = RawTrack.RotKeys[0]; + bool bFramesIdentical = true; + for (int32 j = 1; j < RawTrack.RotKeys.Num() && bFramesIdentical; j++) + { + if (FQuat::Error(FirstRot, RawTrack.RotKeys[j]) > MaxAngleDiff) + { + bFramesIdentical = false; + } + } + + // If all keys are the same, remove all but first frame + if (bFramesIdentical) + { + bRemovedKeys = true; + RawTrack.RotKeys.RemoveAt(1, RawTrack.RotKeys.Num() - 1); + RawTrack.RotKeys.Shrink(); + check(RawTrack.RotKeys.Num() == 1); + } + } + + float MaxScaleDiff = 0.0001f; + + // Check variation of Scaleition keys + if ((RawTrack.ScaleKeys.Num() > 1) && (MaxScaleDiff >= 0.0f)) + { + FVector FirstScale = RawTrack.ScaleKeys[0]; + bool bFramesIdentical = true; + for (int32 j = 1; j < RawTrack.ScaleKeys.Num() && bFramesIdentical; j++) + { + if ((FirstScale - RawTrack.ScaleKeys[j]).SizeSquared() > FMath::Square(MaxScaleDiff)) + { + bFramesIdentical = false; + } + } + + // If all keys are the same, remove all but first frame + if (bFramesIdentical) + { + bRemovedKeys = true; + RawTrack.ScaleKeys.RemoveAt(1, RawTrack.ScaleKeys.Num() - 1); + RawTrack.ScaleKeys.Shrink(); + check(RawTrack.ScaleKeys.Num() == 1); + } + } + + return bRemovedKeys; +} + +bool StaticCompressRawAnimData(TArray& RawAnimationData, int32 NumFrames, FName ErrorName, float MaxPosDiff, float MaxAngleDiff) +{ + bool bRemovedKeys = false; + +#if WITH_EDITORONLY_DATA + if (ensureMsgf(RawAnimationData.Num() > 0, TEXT("%s is trying to compress while raw animation is missing"), *ErrorName.ToString())) + { + // This removes trivial keys, and this has to happen before the removing tracks + for (int32 TrackIndex = 0; TrackIndex < RawAnimationData.Num(); TrackIndex++) + { + bRemovedKeys |= CompressRawAnimSequenceTrack(RawAnimationData[TrackIndex], NumFrames, ErrorName, MaxPosDiff, MaxAngleDiff); + } + + bool bCompressScaleKeys = false; + // go through remove keys if not needed + for (int32 TrackIndex = 0; TrackIndex < RawAnimationData.Num(); TrackIndex++) + { + FRawAnimSequenceTrack const& RawData = RawAnimationData[TrackIndex]; + if (RawData.ScaleKeys.Num() > 0) + { + // if scale key exists, see if we can just empty it + if ((RawData.ScaleKeys.Num() > 1) || (RawData.ScaleKeys[0].Equals(FVector(1.f)) == false)) + { + bCompressScaleKeys = true; + break; + } + } + } + + // if we don't have scale, we should delete all scale keys + // if you have one track that has scale, we still should support scale, so compress scale + if (!bCompressScaleKeys) + { + // then remove all scale keys + for (int32 TrackIndex = 0; TrackIndex < RawAnimationData.Num(); TrackIndex++) + { + FRawAnimSequenceTrack& RawData = RawAnimationData[TrackIndex]; + RawData.ScaleKeys.Empty(); + } + } + } +#endif + return bRemovedKeys; +} + +bool StaticCompressRawAnimData(TArray& RawAnimationData, int32 NumFrames, FName ErrorName) +{ + const float MaxPosDiff = 0.0001f; + const float MaxAngleDiff = 0.0003f; + return StaticCompressRawAnimData(RawAnimationData, NumFrames, ErrorName, MaxPosDiff, MaxAngleDiff); +} + ///////////////////////////////////////////////////// // FRequestAnimCompressionParams FRequestAnimCompressionParams::FRequestAnimCompressionParams(bool bInAsyncCompression, bool bInAllowAlternateCompressor, bool bInOutput) @@ -296,7 +511,6 @@ UAnimSequence::UAnimSequence(const FObjectInitializer& ObjectInitializer) , bUseRawDataOnly(!FPlatformProperties::RequiresCookedData()) { RateScale = 1.0; - CompressedRawDataSize = 0.f; #if WITH_EDITORONLY_DATA ImportFileFramerate = 0.0f; @@ -340,7 +554,7 @@ void UAnimSequence::AddReferencedObjects(UObject* This, FReferenceCollector& Col Super::AddReferencedObjects(This, Collector); UAnimSequence* AnimSeq = CastChecked(This); - Collector.AddReferencedObject(AnimSeq->CurveCompressionCodec); + Collector.AddReferencedObject(AnimSeq->CompressedData.CurveCompressionCodec); } int32 UAnimSequence::GetUncompressedRawSize() const @@ -374,10 +588,15 @@ int32 UAnimSequence::GetApproxRawSize() const return Total; } +int32 UAnimSequence::GetApproxBoneCompressedSize() const +{ + return CompressedData.CompressedDataStructure.GetApproxBoneCompressedSize(); +} + int32 UAnimSequence::GetApproxCompressedSize() const { - int32 BoneTotal = sizeof(int32)*CompressedTrackOffsets.Num() + CompressedByteStream.Num() + CompressedScaleOffsets.GetMemorySize() + sizeof(FCompressedSegment)*CompressedSegments.Num(); - int32 CurveTotal = CompressedCurveByteStream.Num(); + int32 BoneTotal = GetApproxBoneCompressedSize(); + int32 CurveTotal = CompressedData.CompressedCurveByteStream.Num(); return BoneTotal + CurveTotal; } @@ -710,7 +929,7 @@ void UAnimSequence::PostLoad() { SetSkeletonVirtualBoneGuid(GetSkeleton()->GetVirtualBoneGuid()); } - if( RawAnimationData.Num() > 0 && CompressedByteStream.Num() > 0 ) + if( RawAnimationData.Num() > 0 && CompressedData.CompressedByteStream.Num() > 0 ) { #if 0//@todo.Cooker/Package... // Don't do this on consoles; raw animation data should have been stripped during cook! @@ -730,10 +949,6 @@ void UAnimSequence::PostLoad() } } -#if WITH_EDITORONLY_DATA - bWasCompressedWithoutTranslations = false; //@todoanim: @fixmelh : AnimRotationOnly - GetAnimSet()->bAnimRotationOnly; -#endif // WITH_EDITORONLY_DATA - for(FAnimNotifyEvent& Notify : Notifies) { if(Notify.DisplayTime_DEPRECATED != 0.0f) @@ -754,7 +969,7 @@ void UAnimSequence::PostLoad() if (USkeleton* CurrentSkeleton = GetSkeleton()) { - for (FSmartName& CurveName : CompressedCurveNames) + for (FSmartName& CurveName : CompressedData.CompressedCurveNames) { CurrentSkeleton->VerifySmartName(USkeleton::AnimCurveMappingName, CurveName); } @@ -884,9 +1099,8 @@ void UAnimSequence::BeginDestroy() { Super::BeginDestroy(); - // clear any active codec links - RotationCodec = NULL; - TranslationCodec = NULL; + CompressedData.CompressedDataStructure.TranslationCodec = nullptr; + CompressedData.CompressedDataStructure.RotationCodec = nullptr; } #if WITH_EDITOR @@ -987,7 +1201,7 @@ void UAnimSequence::GetBoneTransform(FTransform& OutAtom, int32 TrackIndex, floa // If the caller didn't request that raw animation data be used . . . if ( !bUseRawData && IsCompressedDataValid() ) { - FAnimSequenceDecompressionContext DecompContext(this); + FAnimSequenceDecompressionContext DecompContext(SequenceLength, Interpolation, GetFName(), CompressedData.CompressedDataStructure); DecompContext.Seek(Time); AnimationFormat_GetBoneAtom( OutAtom, DecompContext, TrackIndex ); return; @@ -1050,95 +1264,7 @@ void UAnimSequence::ExtractBoneTransform(const struct FRawAnimSequenceTrack& Raw void UAnimSequence::ExtractBoneTransform(const struct FRawAnimSequenceTrack& RawTrack, FTransform& OutAtom, float Time) const { - // Bail out (with rather wacky data) if data is empty for some reason. - if(RawTrack.PosKeys.Num() == 0 || RawTrack.RotKeys.Num() == 0) - { - UE_LOG(LogAnimation, Log, TEXT("UAnimSequence::GetBoneTransform : No anim data in AnimSequence[%s]!"),*GetFullName()); - OutAtom.SetIdentity(); - return; - } - - int32 KeyIndex1, KeyIndex2; - float Alpha; - FAnimationRuntime::GetKeyIndicesFromTime(KeyIndex1, KeyIndex2, Alpha, Time, NumFrames, SequenceLength); - // @Todo fix me: this change is not good, it has lots of branches. But we'd like to save memory for not saving scale if no scale change exists - const bool bHasScaleKey = (RawTrack.ScaleKeys.Num() > 0); - static const FVector DefaultScale3D = FVector(1.f); - - if (Interpolation == EAnimInterpolationType::Step) - { - Alpha = 0.f; - } - - if(Alpha <= 0.f) - { - const int32 PosKeyIndex1 = FMath::Min(KeyIndex1, RawTrack.PosKeys.Num()-1); - const int32 RotKeyIndex1 = FMath::Min(KeyIndex1, RawTrack.RotKeys.Num()-1); - if(bHasScaleKey) - { - const int32 ScaleKeyIndex1 = FMath::Min(KeyIndex1, RawTrack.ScaleKeys.Num()-1); - OutAtom = FTransform(RawTrack.RotKeys[RotKeyIndex1], RawTrack.PosKeys[PosKeyIndex1], RawTrack.ScaleKeys[ScaleKeyIndex1]); - } - else - { - OutAtom = FTransform(RawTrack.RotKeys[RotKeyIndex1], RawTrack.PosKeys[PosKeyIndex1], DefaultScale3D); - } - return; - } - else if(Alpha >= 1.f) - { - const int32 PosKeyIndex2 = FMath::Min(KeyIndex2, RawTrack.PosKeys.Num()-1); - const int32 RotKeyIndex2 = FMath::Min(KeyIndex2, RawTrack.RotKeys.Num()-1); - if(bHasScaleKey) - { - const int32 ScaleKeyIndex2 = FMath::Min(KeyIndex2, RawTrack.ScaleKeys.Num()-1); - OutAtom = FTransform(RawTrack.RotKeys[RotKeyIndex2], RawTrack.PosKeys[PosKeyIndex2], RawTrack.ScaleKeys[ScaleKeyIndex2]); - } - else - { - OutAtom = FTransform(RawTrack.RotKeys[RotKeyIndex2], RawTrack.PosKeys[PosKeyIndex2], DefaultScale3D); - } - return; - } - - const int32 PosKeyIndex1 = FMath::Min(KeyIndex1, RawTrack.PosKeys.Num()-1); - const int32 RotKeyIndex1 = FMath::Min(KeyIndex1, RawTrack.RotKeys.Num()-1); - - const int32 PosKeyIndex2 = FMath::Min(KeyIndex2, RawTrack.PosKeys.Num()-1); - const int32 RotKeyIndex2 = FMath::Min(KeyIndex2, RawTrack.RotKeys.Num()-1); - - FTransform KeyAtom1, KeyAtom2; - - if(bHasScaleKey) - { - const int32 ScaleKeyIndex1 = FMath::Min(KeyIndex1, RawTrack.ScaleKeys.Num()-1); - const int32 ScaleKeyIndex2 = FMath::Min(KeyIndex2, RawTrack.ScaleKeys.Num()-1); - - KeyAtom1 = FTransform(RawTrack.RotKeys[RotKeyIndex1], RawTrack.PosKeys[PosKeyIndex1], RawTrack.ScaleKeys[ScaleKeyIndex1]); - KeyAtom2 = FTransform(RawTrack.RotKeys[RotKeyIndex2], RawTrack.PosKeys[PosKeyIndex2], RawTrack.ScaleKeys[ScaleKeyIndex2]); - } - else - { - KeyAtom1 = FTransform(RawTrack.RotKeys[RotKeyIndex1], RawTrack.PosKeys[PosKeyIndex1], DefaultScale3D); - KeyAtom2 = FTransform(RawTrack.RotKeys[RotKeyIndex2], RawTrack.PosKeys[PosKeyIndex2], DefaultScale3D); - } - - // UE_LOG(LogAnimation, Log, TEXT(" * * * Position. PosKeyIndex1: %3d, PosKeyIndex2: %3d, Alpha: %f"), PosKeyIndex1, PosKeyIndex2, Alpha); - // UE_LOG(LogAnimation, Log, TEXT(" * * * Rotation. RotKeyIndex1: %3d, RotKeyIndex2: %3d, Alpha: %f"), RotKeyIndex1, RotKeyIndex2, Alpha); - - // Ensure rotations are normalized (Added for Jira UE-53971) - if (!ensureMsgf(KeyAtom1.IsRotationNormalized(), TEXT("Rotation isn't normalized (Anim:%s)"), *GetPathName())) - { - KeyAtom1.NormalizeRotation(); - } - - if (!ensureMsgf(KeyAtom2.IsRotationNormalized(), TEXT("Rotation isn't normalized (Anim:%s)"), *GetPathName())) - { - KeyAtom2.NormalizeRotation(); - } - - OutAtom.Blend(KeyAtom1, KeyAtom2, Alpha); - OutAtom.NormalizeRotation(); + FAnimationUtils::ExtractTransformFromTrack(Time, NumFrames, SequenceLength, RawTrack, Interpolation, OutAtom); } void UAnimSequence::HandleAssetPlayerTickedInternal(FAnimAssetTickContext &Context, const float PreviousTime, const float MoveDelta, const FAnimTickRecord &Instance, struct FAnimNotifyQueue& NotifyQueue) const @@ -1153,7 +1279,7 @@ void UAnimSequence::HandleAssetPlayerTickedInternal(FAnimAssetTickContext &Conte FTransform UAnimSequence::ExtractRootTrackTransform(float Pos, const FBoneContainer * RequiredBones) const { - const TArray & TrackToSkeletonMap = bUseRawDataOnly ? TrackToSkeletonMapTable : CompressedTrackToSkeletonMapTable; + const TArray & TrackToSkeletonMap = bUseRawDataOnly ? TrackToSkeletonMapTable : CompressedData.CompressedTrackToSkeletonMapTable; // we assume root is in first data if available = SkeletonIndex == 0 && BoneTreeIndex == 0) if ((TrackToSkeletonMap.Num() > 0) && (TrackToSkeletonMap[0].BoneTreeIndex == 0)) @@ -1615,7 +1741,7 @@ void UAnimSequence::GetBonePose(FCompactPose& OutPose, FBlendedCurve& OutCurve, // extract curve data . Even if no track, it can contain curve data EvaluateCurveData(OutCurve, ExtractionContext.CurrentTime, bUseRawDataForPoseExtraction); - const int32 NumTracks = bUseRawDataForPoseExtraction ? TrackToSkeletonMapTable.Num() : CompressedTrackToSkeletonMapTable.Num(); + const int32 NumTracks = bUseRawDataForPoseExtraction ? TrackToSkeletonMapTable.Num() : CompressedData.CompressedTrackToSkeletonMapTable.Num(); if (NumTracks == 0) { return; @@ -1716,7 +1842,7 @@ void UAnimSequence::GetBonePose(FCompactPose& OutPose, FBlendedCurve& OutCurve, CSV_SCOPED_TIMING_STAT(Animation, ExtractPoseFromAnimData); CSV_CUSTOM_STAT(Animation, NumberOfExtractedAnimations, 1, ECsvCustomStatOp::Accumulate); - FAnimSequenceDecompressionContext EvalDecompContext(this); + FAnimSequenceDecompressionContext EvalDecompContext(SequenceLength, Interpolation, GetFName(), CompressedData.CompressedDataStructure); EvalDecompContext.Seek(ExtractionContext.CurrentTime); // Handle Root Bone separately @@ -1835,22 +1961,10 @@ void UAnimSequence::GetBonePose(FCompactPose& OutPose, FBlendedCurve& OutCurve, } #if WITH_EDITORONLY_DATA -void UAnimSequence::UpdateCompressedCurveNames() -{ - // Copy our curve names to the compressed array - const int32 NumCurves = RawCurveData.FloatCurves.Num(); - CompressedCurveNames.Reset(NumCurves); - CompressedCurveNames.AddUninitialized(NumCurves); - for (int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) - { - const FFloatCurve& Curve = RawCurveData.FloatCurves[CurveIndex]; - CompressedCurveNames[CurveIndex] = Curve.Name; - } -} void UAnimSequence::UpdateCompressedCurveName(SmartName::UID_Type CurveUID, const struct FSmartName& NewCurveName) { - for (FSmartName& CurveName : CompressedCurveNames) + for (FSmartName& CurveName : CompressedData.CompressedCurveNames) { if (CurveName.UID == CurveUID) { @@ -2315,179 +2429,22 @@ bool UAnimSequence::CropRawAnimData( float CurrentTime, bool bFromStart ) return true; } -bool UAnimSequence::CompressRawAnimSequenceTrack(FRawAnimSequenceTrack& RawTrack, float MaxPosDiff, float MaxAngleDiff) -{ - bool bRemovedKeys = false; - - // First part is to make sure we have valid input - bool const bPosTrackIsValid = (RawTrack.PosKeys.Num() == 1 || RawTrack.PosKeys.Num() == NumFrames); - if( !bPosTrackIsValid ) - { - UE_LOG(LogAnimation, Warning, TEXT("Found non valid position track for %s, %d frames, instead of %d. Chopping!"), *GetName(), RawTrack.PosKeys.Num(), NumFrames); - bRemovedKeys = true; - RawTrack.PosKeys.RemoveAt(1, RawTrack.PosKeys.Num()- 1); - RawTrack.PosKeys.Shrink(); - check( RawTrack.PosKeys.Num() == 1); - } - - bool const bRotTrackIsValid = (RawTrack.RotKeys.Num() == 1 || RawTrack.RotKeys.Num() == NumFrames); - if( !bRotTrackIsValid ) - { - UE_LOG(LogAnimation, Warning, TEXT("Found non valid rotation track for %s, %d frames, instead of %d. Chopping!"), *GetName(), RawTrack.RotKeys.Num(), NumFrames); - bRemovedKeys = true; - RawTrack.RotKeys.RemoveAt(1, RawTrack.RotKeys.Num()- 1); - RawTrack.RotKeys.Shrink(); - check( RawTrack.RotKeys.Num() == 1); - } - - // scale keys can be empty, and that is valid - bool const bScaleTrackIsValid = (RawTrack.ScaleKeys.Num() == 0 || RawTrack.ScaleKeys.Num() == 1 || RawTrack.ScaleKeys.Num() == NumFrames); - if( !bScaleTrackIsValid ) - { - UE_LOG(LogAnimation, Warning, TEXT("Found non valid Scaleation track for %s, %d frames, instead of %d. Chopping!"), *GetName(), RawTrack.ScaleKeys.Num(), NumFrames); - bRemovedKeys = true; - RawTrack.ScaleKeys.RemoveAt(1, RawTrack.ScaleKeys.Num()- 1); - RawTrack.ScaleKeys.Shrink(); - check( RawTrack.ScaleKeys.Num() == 1); - } - - // Second part is actual compression. - - // Check variation of position keys - if( (RawTrack.PosKeys.Num() > 1) && (MaxPosDiff >= 0.0f) ) - { - FVector FirstPos = RawTrack.PosKeys[0]; - bool bFramesIdentical = true; - for(int32 j=1; j FMath::Square(MaxPosDiff) ) - { - bFramesIdentical = false; - } - } - - // If all keys are the same, remove all but first frame - if( bFramesIdentical ) - { - bRemovedKeys = true; - RawTrack.PosKeys.RemoveAt(1, RawTrack.PosKeys.Num()- 1); - RawTrack.PosKeys.Shrink(); - check( RawTrack.PosKeys.Num() == 1); - } - } - - // Check variation of rotational keys - if( (RawTrack.RotKeys.Num() > 1) && (MaxAngleDiff >= 0.0f) ) - { - FQuat FirstRot = RawTrack.RotKeys[0]; - bool bFramesIdentical = true; - for(int32 j=1; j MaxAngleDiff ) - { - bFramesIdentical = false; - } - } - - // If all keys are the same, remove all but first frame - if( bFramesIdentical ) - { - bRemovedKeys = true; - RawTrack.RotKeys.RemoveAt(1, RawTrack.RotKeys.Num()- 1); - RawTrack.RotKeys.Shrink(); - check( RawTrack.RotKeys.Num() == 1); - } - } - - float MaxScaleDiff = 0.0001f; - - // Check variation of Scaleition keys - if( (RawTrack.ScaleKeys.Num() > 1) && (MaxScaleDiff >= 0.0f) ) - { - FVector FirstScale = RawTrack.ScaleKeys[0]; - bool bFramesIdentical = true; - for(int32 j=1; j FMath::Square(MaxScaleDiff) ) - { - bFramesIdentical = false; - } - } - - // If all keys are the same, remove all but first frame - if( bFramesIdentical ) - { - bRemovedKeys = true; - RawTrack.ScaleKeys.RemoveAt(1, RawTrack.ScaleKeys.Num()- 1); - RawTrack.ScaleKeys.Shrink(); - check( RawTrack.ScaleKeys.Num() == 1); - } - } - - return bRemovedKeys; -} - bool UAnimSequence::CompressRawAnimData(float MaxPosDiff, float MaxAngleDiff) { - bool bRemovedKeys = false; -#if WITH_EDITORONLY_DATA - - if (AnimationTrackNames.Num() > 0 && ensureMsgf(RawAnimationData.Num() > 0, TEXT("%s is trying to compress while raw animation is missing"), *GetName())) + if (RawAnimationData.Num() > 0) { - // This removes trivial keys, and this has to happen before the removing tracks - for(int32 TrackIndex=0; TrackIndex 0) - { - // if scale key exists, see if we can just empty it - if((RawData.ScaleKeys.Num() > 1) || (RawData.ScaleKeys[0].Equals(FVector(1.f)) == false)) - { - bCompressScaleKeys = true; - break; - } - } - } - - // if we don't have scale, we should delete all scale keys - // if you have one track that has scale, we still should support scale, so compress scale - if(!bCompressScaleKeys) - { - // then remove all scale keys - for(int32 TrackIndex=0; TrackIndex 0) + { + return StaticCompressRawAnimData(RawAnimationData, NumFrames, GetFName()); + } + return false; } /** @@ -2573,8 +2530,6 @@ void UAnimSequence::RequestAnimCompression(FRequestAnimCompressionParams Params) TGuardValue CompressGuard(bCompressionInProgress, true); - const bool bDoCompressionInPlace = FUObjectThreadContext::Get().IsRoutingPostLoad; - // Need to make sure this is up to date. VerifyCurveNames(*CurrentSkeleton, USkeleton::AnimCurveMappingName, RawCurveData.FloatCurves); VerifyTrackMap(CurrentSkeleton); @@ -2587,8 +2542,16 @@ void UAnimSequence::RequestAnimCompression(FRequestAnimCompressionParams Params) const bool bPerformFrameStripping = Params.bPerformFrameStripping && bAllowFrameStripping; const bool bPerformStrippingOnOddFramedAnims = Params.bPerformFrameStrippingOnOddNumberedFrames; + const int32 PreviousCompressionSize = GetApproxCompressedSize(); + + // Filter RAW data to get rid of mismatched tracks (translation/rotation data with a different number of keys than there are frames) + // No trivial key removal is done at this point (impossible error metrics of -1), since all of the techniques will perform it themselves + CompressRawAnimData(-1.0f, -1.0f); + + FCompressibleAnimData CompressibleData(this); //Build the data that the compression code will compress + TArray OutData; - FDerivedDataAnimationCompression* AnimCompressor = new FDerivedDataAnimationCompression(this, Params.CompressContext, bDoCompressionInPlace, bPerformFrameStripping, bPerformStrippingOnOddFramedAnims); + FDerivedDataAnimationCompression* AnimCompressor = new FDerivedDataAnimationCompression(CompressibleData, Params.CompressContext, PreviousCompressionSize, bPerformFrameStripping, bPerformStrippingOnOddFramedAnims); // For debugging DDC/Compression issues const bool bSkipDDC = false; if (bSkipDDC || (CompressCommandletVersion == INDEX_NONE)) @@ -2612,7 +2575,7 @@ void UAnimSequence::RequestAnimCompression(FRequestAnimCompressionParams Params) } } - if (bUseRawDataOnly && OutData.Num() > 0) + if (OutData.Num() > 0) { FMemoryReader MemAr(OutData); SerializeCompressedData(MemAr, true); @@ -2624,260 +2587,12 @@ void UAnimSequence::RequestAnimCompression(FRequestAnimCompressionParams Params) #endif } -#if WITH_EDITOR -struct FAnimDDCDebugData -{ - FString FullName; - uint8 AdditiveSetting; - FString CompressionSchemeName; - FGuid RawDataGuid; - - FAnimDDCDebugData(const UAnimSequence* AnimSequence, FArchive& Ar) - { - if (Ar.IsSaving()) - { - FullName = AnimSequence->GetFullName(); - AdditiveSetting = (uint8)AnimSequence->AdditiveAnimType.GetValue(); - CompressionSchemeName = AnimSequence->CompressionScheme->GetFullName(); - RawDataGuid = AnimSequence->GetRawDataGuid(); - } - - Ar << FullName; - Ar << AdditiveSetting; - Ar << CompressionSchemeName; - Ar << RawDataGuid; - } -}; -#endif - void UAnimSequence::SerializeCompressedData(FArchive& Ar, bool bDDCData) { - Ar << KeyEncodingFormat; - Ar << TranslationCompressionFormat; - Ar << RotationCompressionFormat; - Ar << ScaleCompressionFormat; - - Ar << CompressedTrackOffsets; - Ar << CompressedScaleOffsets; - Ar << CompressedSegments; - - Ar << CompressedTrackToSkeletonMapTable; - Ar << CompressedCurveNames; - - Ar << CompressedRawDataSize; - Ar << CompressedNumFrames; - - if (Ar.IsLoading()) + if (!HasAnyFlags(RF_ClassDefaultObject)) { - // Serialize the compressed byte stream from the archive to the buffer. - int32 NumBytes; - Ar << NumBytes; - - // we must know the proper codecs to use - AnimationFormat_SetInterfaceLinks(*this); - check(RotationCodec != NULL); - - bool bUseBulkDataForLoad = false; - if (!bDDCData && Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) >= FFortniteMainBranchObjectVersion::FortMappedCookedAnimation) - { - Ar << bUseBulkDataForLoad; - } - if (bUseBulkDataForLoad) - { -#if !WITH_EDITOR - FByteBulkData OptionalBulk; -#endif - bool bUseMapping = FPlatformProperties::SupportsMemoryMappedFiles() && FPlatformProperties::SupportsMemoryMappedAnimation(); - OptionalBulk.Serialize(Ar, this, -1, bUseMapping); - - if (!bUseMapping) - { - OptionalBulk.ForceBulkDataResident(); - } - - size_t Size = OptionalBulk.GetBulkDataSize(); - - FOwnedBulkDataPtr* OwnedPtr = OptionalBulk.StealFileMapping(); - -#if WITH_EDITOR - check(!bUseMapping && !OwnedPtr->GetMappedHandle()); - CompressedByteStream.Empty(Size); - CompressedByteStream.AddUninitialized(Size); - if (Size) - { - FMemory::Memcpy(&CompressedByteStream[0], OwnedPtr->GetPointer(), Size); - } -#else - CompressedByteStream.AcceptOwnedBulkDataPtr(OwnedPtr, Size); -#endif - delete OwnedPtr; - } - else - { - if (FPlatformProperties::RequiresCookedData()) - { - CompressedByteStream.Empty(NumBytes); - CompressedByteStream.AddUninitialized(NumBytes); - Ar.Serialize(CompressedByteStream.GetData(), NumBytes); - } - else - { - TArray SerializedData; - SerializedData.Empty(NumBytes); - SerializedData.AddUninitialized(NumBytes); - Ar.Serialize(SerializedData.GetData(), NumBytes); - - // Swap the buffer into the byte stream. - FMemoryReader MemoryReader(SerializedData, true); - MemoryReader.SetByteSwapping(Ar.ForceByteSwapping()); - ((AnimEncoding*)RotationCodec)->ByteSwapIn(*this, MemoryReader); - } - } - - FString CurveCodecPath; - Ar << CurveCodecPath; - - CurveCompressionCodec = CurveCompressionSettings->GetCodec(CurveCodecPath); - - int32 NumCurveBytes; - Ar << NumCurveBytes; - - CompressedCurveByteStream.Empty(NumCurveBytes); - CompressedCurveByteStream.AddUninitialized(NumCurveBytes); - Ar.Serialize(CompressedCurveByteStream.GetData(), NumCurveBytes); + CompressedData.SerializeCompressedData(Ar, bDDCData, this, CurveCompressionSettings); } - else if (Ar.IsSaving() || Ar.IsCountingMemory()) - { - // Swap the byte stream into a buffer. - TArray SerializedData; - - const bool bIsCooking = !bDDCData && Ar.IsCooking(); - - if (!HasAnyFlags(RF_ClassDefaultObject)) - { - // we must know the proper codecs to use - AnimationFormat_SetInterfaceLinks(*this); - - // and then use the codecs to byte swap - check(RotationCodec != NULL); - ((AnimEncoding*)RotationCodec)->ByteSwapOut(*this, SerializedData, Ar.ForceByteSwapping(), bIsCooking); - } - - // Make sure the entire byte stream was serialized. - //check( CompressedByteStream.Num() == SerializedData.Num() ); - - // Serialize the buffer to archive. - int32 Num = SerializedData.Num(); - Ar << Num; - - bool bUseBulkDataForSave = Num && bIsCooking && Ar.CookingTarget()->SupportsFeature(ETargetPlatformFeatures::MemoryMappedFiles) && Ar.CookingTarget()->SupportsFeature(ETargetPlatformFeatures::MemoryMappedAnimation); - - bool bSavebUseBulkDataForSave = false; - if (!bDDCData) - { - Ar.UsingCustomVersion(FFortniteMainBranchObjectVersion::GUID); - if (Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::FortMappedCookedAnimation) - { - bUseBulkDataForSave = false; - } - else - { - bSavebUseBulkDataForSave = true; - } - } - - // Count compressed data. - Ar.CountBytes(SerializedData.Num(), SerializedData.Num()); - - if (bSavebUseBulkDataForSave) - { - Ar << bUseBulkDataForSave; - } - else - { - check(!bUseBulkDataForSave); - } - -#define TEST_IS_CORRECTLY_FORMATTED_FOR_MEMORY_MAPPING WITH_EDITOR -#if TEST_IS_CORRECTLY_FORMATTED_FOR_MEMORY_MAPPING - if(!IsTemplate() && bIsCooking) - { - TArray TempSerialized; - check(RotationCodec != NULL); - ((AnimEncoding*)RotationCodec)->ByteSwapOut(*this, TempSerialized, Ar.ForceByteSwapping()); - - FMemoryReader MemoryReader(TempSerialized, true); - MemoryReader.SetByteSwapping(Ar.ForceByteSwapping()); - - TArray SavedCompressedByteStream = CompressedByteStream; - CompressedByteStream.Empty(); - - ((AnimEncoding*)RotationCodec)->ByteSwapIn(*this, MemoryReader); - - check(CompressedByteStream.Num() == Num); - - check(FMemory::Memcmp(SerializedData.GetData(), CompressedByteStream.GetData(), Num) == 0); - - CompressedByteStream = SavedCompressedByteStream; - } -#endif - - if (bUseBulkDataForSave) - { -#if WITH_EDITOR - OptionalBulk.Lock(LOCK_READ_WRITE); - void* Dest = OptionalBulk.Realloc(Num); - FMemory::Memcpy(Dest, &(SerializedData[0]), Num); - OptionalBulk.Unlock(); - OptionalBulk.SetBulkDataFlags(BULKDATA_PayloadAtEndOfFile | BULKDATA_PayloadInSeperateFile | BULKDATA_Force_NOT_InlinePayload | BULKDATA_MemoryMappedPayload); - OptionalBulk.ClearBulkDataFlags(BULKDATA_ForceInlinePayload); - OptionalBulk.Serialize(Ar, this); -#else - UE_LOG(LogAnimation, Fatal, TEXT("Can't save animation as bulk data in non-editor builds!")); -#endif - } - else - { - Ar.Serialize(SerializedData.GetData(), SerializedData.Num()); - } - - FString CurveCodecPath = CurveCompressionCodec->GetPathName(); - Ar << CurveCodecPath; - - int32 NumCurveBytes = CompressedCurveByteStream.Num(); - Ar << NumCurveBytes; - Ar.Serialize(CompressedCurveByteStream.GetData(), NumCurveBytes); - } - -#if WITH_EDITOR - if (bDDCData) - { - //Skip ddc debug data if we are cooking - FAnimDDCDebugData DebugData(this, Ar); - - if (Ar.IsLoading()) - { - if(USkeleton* CurrentSkeleton = GetSkeleton()) - { - // Refresh the compressed curve names since the IDs might have changed since - for (FSmartName& CurveName : CompressedCurveNames) - { - CurrentSkeleton->VerifySmartName(USkeleton::AnimCurveMappingName, CurveName); - } - - bUseRawDataOnly = !IsCompressedDataValid() || !IsCurveCompressedDataValid(); - - ensureMsgf(!bUseRawDataOnly, TEXT("Anim Compression failed for Sequence '%s' Guid:%s CompressedDebugData:\n\tOriginal Anim:%s\n\tAdditiveSetting:%i\n\tCompression Scheme:%s\n\tRawDataGuid:%s"), - *GetFullName(), - *RawDataGuid.ToString(), - *DebugData.FullName, - DebugData.AdditiveSetting, - *DebugData.CompressionSchemeName, - *DebugData.RawDataGuid.ToString()); - } - } - } -#endif } #if WITH_EDITOR @@ -2975,9 +2690,6 @@ void CopyTransformToRawAnimationData(const FTransform& BoneTransform, FRawAnimSe struct FByFramePoseEvalContext { -private: - const UAnimSequence* AnimToEval; - public: FBoneContainer RequiredBones; @@ -2987,36 +2699,38 @@ public: TArray RequiredBoneIndexArray; FByFramePoseEvalContext(const UAnimSequence* InAnimToEval) - : AnimToEval(InAnimToEval) - , IntervalTime(InAnimToEval->SequenceLength / ((float)InAnimToEval->GetRawNumberOfFrames() - 1)) + : FByFramePoseEvalContext(InAnimToEval->SequenceLength, InAnimToEval->GetRawNumberOfFrames(), InAnimToEval->GetSkeleton()) + {} + + FByFramePoseEvalContext(float InSequenceLength, int32 InRawNumOfFrames, USkeleton* InSkeleton) + : IntervalTime(InSequenceLength / ((float)InRawNumOfFrames - 1)) { // Initialize RequiredBones for pose evaluation RequiredBones.SetUseRAWData(true); - USkeleton* MySkeleton = AnimToEval->GetSkeleton(); - check(MySkeleton); + check(InSkeleton); - RequiredBoneIndexArray.AddUninitialized(MySkeleton->GetReferenceSkeleton().GetNum()); + RequiredBoneIndexArray.AddUninitialized(InSkeleton->GetReferenceSkeleton().GetNum()); for (int32 BoneIndex = 0; BoneIndex < RequiredBoneIndexArray.Num(); ++BoneIndex) { RequiredBoneIndexArray[BoneIndex] = BoneIndex; } - RequiredBones.InitializeTo(RequiredBoneIndexArray, FCurveEvaluationOption(true), *MySkeleton); + RequiredBones.InitializeTo(RequiredBoneIndexArray, FCurveEvaluationOption(true), *InSkeleton); } }; -void UAnimSequence::BakeOutVirtualBoneTracks() +void UAnimSequence::BakeOutVirtualBoneTracks(TArray& NewRawTracks, TArray& NewAnimationTrackNames, TArray& NewTrackToSkeletonMapTable) { const int32 NumVirtualBones = GetSkeleton()->GetVirtualBones().Num(); check( (RawAnimationData.Num() == TrackToSkeletonMapTable.Num()) && (RawAnimationData.Num() == AnimationTrackNames.Num()) ); //Make sure starting data is valid - TArray NewRawTracks = TArray(RawAnimationData, NumVirtualBones); + NewRawTracks = TArray(RawAnimationData, NumVirtualBones); - TArray NewTrackToSkeletonMapTable = TArray(TrackToSkeletonMapTable, NumVirtualBones); + NewTrackToSkeletonMapTable = TArray(TrackToSkeletonMapTable, NumVirtualBones); - TArray NewAnimationTrackNames = TArray(AnimationTrackNames, NumVirtualBones); + NewAnimationTrackNames = TArray(AnimationTrackNames, NumVirtualBones); for (int32 VBIndex = 0; VBIndex < NumVirtualBones; ++VBIndex) { @@ -3060,11 +2774,7 @@ void UAnimSequence::BakeOutVirtualBoneTracks() } } - RawAnimationData = MoveTemp(NewRawTracks); - AnimationTrackNames = MoveTemp(NewAnimationTrackNames); - TrackToSkeletonMapTable = MoveTemp(NewTrackToSkeletonMapTable); - - CompressRawAnimData(); + StaticCompressRawAnimData(NewRawTracks, NumFrames, GetFName()); } bool IsIdentity(const FVector& Pos) @@ -3114,7 +2824,7 @@ void UAnimSequence::TestEvalauteAnimation() const } } -void UAnimSequence::BakeOutAdditiveIntoRawData() +void UAnimSequence::BakeOutAdditiveIntoRawData(TArray& NewRawTracks, TArray& NewAnimationTrackNames, TArray& NewTrackToSkeletonMapTable, FRawCurveTracks& NewCurveTracks, TArray& AdditiveBaseAnimationData) { AddAnimLoadingDebugEntry(TEXT("BakeOutAdditiveIntoRawData")); if (!CanBakeAdditive()) @@ -3134,10 +2844,7 @@ void UAnimSequence::BakeOutAdditiveIntoRawData() FByFramePoseEvalContext EvalContext(this); - //New raw data - FRawCurveTracks NewCurveTracks; - - TArray NewRawTracks; + NewRawTracks.Reset(EvalContext.RequiredBoneIndexArray.Num()); NewRawTracks.SetNum(EvalContext.RequiredBoneIndexArray.Num()); for (FRawAnimSequenceTrack& RawTrack : NewRawTracks) @@ -3148,12 +2855,12 @@ void UAnimSequence::BakeOutAdditiveIntoRawData() } // keep the same buffer size - TemporaryAdditiveBaseAnimationData = NewRawTracks; + AdditiveBaseAnimationData = NewRawTracks; - TArray NewTrackToSkeletonMapTable; + NewTrackToSkeletonMapTable.Reset(EvalContext.RequiredBoneIndexArray.Num()); NewTrackToSkeletonMapTable.SetNumUninitialized(EvalContext.RequiredBoneIndexArray.Num()); - TArray NewAnimationTrackNames; + NewAnimationTrackNames.SetNumUninitialized(EvalContext.RequiredBoneIndexArray.Num()); NewAnimationTrackNames.SetNumUninitialized(EvalContext.RequiredBoneIndexArray.Num()); for (int32 TrackIndex = 0; TrackIndex < EvalContext.RequiredBoneIndexArray.Num(); ++TrackIndex) @@ -3189,7 +2896,7 @@ void UAnimSequence::BakeOutAdditiveIntoRawData() for (FCompactPoseBoneIndex TrackIndex(0); TrackIndex < NewRawTracks.Num(); ++TrackIndex) { CopyTransformToRawAnimationData(Pose[TrackIndex], NewRawTracks[TrackIndex.GetInt()], Frame); - CopyTransformToRawAnimationData(BasePose[TrackIndex], TemporaryAdditiveBaseAnimationData[TrackIndex.GetInt()], Frame); + CopyTransformToRawAnimationData(BasePose[TrackIndex], AdditiveBaseAnimationData[TrackIndex.GetInt()], Frame); } //Write out curve data for this frame @@ -3234,17 +2941,12 @@ void UAnimSequence::BakeOutAdditiveIntoRawData() } } - RawAnimationData = MoveTemp(NewRawTracks); - AnimationTrackNames = MoveTemp(NewAnimationTrackNames); - TrackToSkeletonMapTable = MoveTemp(NewTrackToSkeletonMapTable); - RawCurveData = NewCurveTracks; - const FSmartNameMapping* Mapping = GetSkeleton()->GetSmartNameContainer(USkeleton::AnimCurveMappingName); check(Mapping); // Should always exist - RawCurveData.RefreshName(Mapping); + NewCurveTracks.RefreshName(Mapping); #if 0 //Validate baked data - for (FRawAnimSequenceTrack& RawTrack : RawAnimationData) + for (FRawAnimSequenceTrack& RawTrack : NewRawTracks) { for (FQuat& Rot : RawTrack.RotKeys) { @@ -3253,17 +2955,17 @@ void UAnimSequence::BakeOutAdditiveIntoRawData() } #endif - CompressRawAnimData(); + StaticCompressRawAnimData(NewRawTracks,NumFrames, GetFName()); // Note on (TrackIndex > 0) below : deliberately stop before track 0, compression code doesn't like getting a completely empty animation - for (int32 TrackIndex = RawAnimationData.Num() - 1; TrackIndex > 0; --TrackIndex) + for (int32 TrackIndex = NewRawTracks.Num() - 1; TrackIndex > 0; --TrackIndex) { - const FRawAnimSequenceTrack& Track = RawAnimationData[TrackIndex]; + const FRawAnimSequenceTrack& Track = NewRawTracks[TrackIndex]; if (IsRawTrackValidForRemoval(Track)) { - RawAnimationData.RemoveAtSwap(TrackIndex, 1, false); - AnimationTrackNames.RemoveAtSwap(TrackIndex, 1, false); - TrackToSkeletonMapTable.RemoveAtSwap(TrackIndex, 1, false); + NewRawTracks.RemoveAtSwap(TrackIndex, 1, false); + NewAnimationTrackNames.RemoveAtSwap(TrackIndex, 1, false); + NewTrackToSkeletonMapTable.RemoveAtSwap(TrackIndex, 1, false); } } @@ -3291,22 +2993,17 @@ void UAnimSequence::RecycleAnimSequence() RawDataGuid.Invalidate(); AnimationTrackNames.Empty(); TrackToSkeletonMapTable.Empty(); - CompressedTrackToSkeletonMapTable.Empty(); - CompressedTrackOffsets.Empty(0); - CompressedByteStream.Empty(0); - CompressedScaleOffsets.Empty(0); - CompressedSegments.Empty(0); + CompressedData.CompressedDataStructure.Reset(); SourceRawAnimationData.Empty(0); RawCurveData.Empty(); - CompressedCurveByteStream.Empty(0); - CompressedCurveNames.Empty(0); + CompressedData.CompressedCurveByteStream.Empty(0); + CompressedData.CompressedCurveNames.Empty(0); AuthoredSyncMarkers.Empty(); UniqueMarkerNames.Empty(); Notifies.Empty(); AnimNotifyTracks.Empty(); CompressionScheme = nullptr; - CurveCompressionCodec = nullptr; - TranslationCompressionFormat = RotationCompressionFormat = ScaleCompressionFormat = ACF_None; + CompressedData.CurveCompressionCodec = nullptr; #endif // WITH_EDITORONLY_DATA } @@ -3318,46 +3015,11 @@ void UAnimSequence::CleanAnimSequenceForImport() RawDataGuid.Invalidate(); AnimationTrackNames.Empty(); TrackToSkeletonMapTable.Empty(); - CompressedTrackOffsets.Empty(0); - CompressedByteStream.Empty(0); - CompressedScaleOffsets.Empty(0); + CompressedData.CompressedDataStructure.Reset(); SourceRawAnimationData.Empty(0); } #endif // WITH_EDITOR -bool UAnimSequence::CopyAnimSequenceProperties(UAnimSequence* SourceAnimSeq, UAnimSequence* DestAnimSeq, bool bSkipCopyingNotifies) -{ -#if WITH_EDITORONLY_DATA - // Copy parameters - DestAnimSeq->SequenceLength = SourceAnimSeq->SequenceLength; - DestAnimSeq->NumFrames = SourceAnimSeq->NumFrames; - DestAnimSeq->RateScale = SourceAnimSeq->RateScale; - DestAnimSeq->bDoNotOverrideCompression = SourceAnimSeq->bDoNotOverrideCompression; - - // Copy Compression Settings - DestAnimSeq->CompressionScheme = static_cast(StaticDuplicateObject(SourceAnimSeq->CompressionScheme, DestAnimSeq, NAME_None, RF_AllFlags, nullptr, EDuplicateMode::Normal, ~EInternalObjectFlags::RootSet)); - DestAnimSeq->TranslationCompressionFormat = SourceAnimSeq->TranslationCompressionFormat; - DestAnimSeq->RotationCompressionFormat = SourceAnimSeq->RotationCompressionFormat; - DestAnimSeq->AdditiveAnimType = SourceAnimSeq->AdditiveAnimType; - DestAnimSeq->RefPoseType = SourceAnimSeq->RefPoseType; - DestAnimSeq->RefPoseSeq = SourceAnimSeq->RefPoseSeq; - DestAnimSeq->RefFrameIndex = SourceAnimSeq->RefFrameIndex; - - if( !bSkipCopyingNotifies ) - { - // Copy Metadata information - CopyNotifies(SourceAnimSeq, DestAnimSeq); - } - - DestAnimSeq->MarkPackageDirty(); - - // Copy Curve Data - DestAnimSeq->RawCurveData = SourceAnimSeq->RawCurveData; -#endif // WITH_EDITORONLY_DATA - - return true; -} - bool UAnimSequence::CopyNotifies(UAnimSequence* SourceAnimSeq, UAnimSequence* DestAnimSeq, bool bShowDialogs /*= true */) { @@ -5078,10 +4740,7 @@ void UAnimSequence::ResetAnimation() SourceRawAnimationData.Empty(); AnimationTrackNames.Empty(); TrackToSkeletonMapTable.Empty(); - CompressedTrackOffsets.Empty(); - CompressedScaleOffsets.Empty(); - CompressedSegments.Empty(); - CompressedByteStream.Empty(); + CompressedData.CompressedDataStructure.Reset(); Notifies.Empty(); AuthoredSyncMarkers.Empty(); @@ -5285,7 +4944,7 @@ void UAnimSequence::EvaluateCurveData(FBlendedCurve& OutCurve, float CurrentTime else { CSV_SCOPED_TIMING_STAT(Animation, EvaluateCurveData); - CurveCompressionCodec->DecompressCurves(*this, OutCurve, CurrentTime); + CompressedData.CurveCompressionCodec->DecompressCurves(CompressedData, OutCurve, CurrentTime); } } @@ -5299,7 +4958,7 @@ float UAnimSequence::EvaluateCurveData(SmartName::UID_Type CurveUID, float Curre } else { - return CurveCompressionCodec->DecompressCurve(*this, CurveUID, CurrentTime); + return CompressedData.CurveCompressionCodec->DecompressCurve(CompressedData, CurveUID, CurrentTime); } } @@ -5310,7 +4969,7 @@ bool UAnimSequence::HasCurveData(SmartName::UID_Type CurveUID, bool bForceUseRaw return Super::HasCurveData(CurveUID, bForceUseRawData); } - for (const FSmartName& CurveName : CompressedCurveNames) + for (const FSmartName& CurveName : CompressedData.CompressedCurveNames) { if (CurveName.UID == CurveUID) { @@ -6056,10 +5715,7 @@ void UAnimSequence::EnableRootMotionSettingFromMontage(bool bInEnableRootMotion, #if WITH_EDITOR void UAnimSequence::OnRawDataChanged() { - CompressedTrackOffsets.Empty(); - CompressedScaleOffsets.Empty(); - CompressedByteStream.Empty(); - CompressedSegments.Empty(); + CompressedData.CompressedDataStructure.Reset(); bUseRawDataOnly = true; RequestSyncAnimRecompression(false); @@ -6070,19 +5726,18 @@ void UAnimSequence::OnRawDataChanged() bool UAnimSequence::IsCompressedDataValid() const { - return CompressedByteStream.Num() > 0 || RawAnimationData.Num() == 0 || - (TranslationCompressionFormat == ACF_Identity && RotationCompressionFormat == ACF_Identity && ScaleCompressionFormat == ACF_Identity); + return CompressedData.CompressedDataStructure.IsCompressedDataValid() || RawAnimationData.Num() == 0; } bool UAnimSequence::IsCurveCompressedDataValid() const { - if (CurveCompressionCodec == nullptr) + if (CompressedData.CurveCompressionCodec == nullptr) { // No codec return false; } - if (CompressedCurveByteStream.Num() == 0 && RawCurveData.FloatCurves.Num() != 0) + if (CompressedData.CompressedCurveByteStream.Num() == 0 && RawCurveData.FloatCurves.Num() != 0) { // No compressed data but we have raw data if (!IsValidAdditive()) @@ -6146,7 +5801,7 @@ void GatherAnimSequenceStats(FOutputDevice& Ar) int32 NumScaleTracksWithOneKey = 0; AnimationFormat_GetStats( - Seq, + Seq->CompressedData.CompressedDataStructure, NumTransTracks, NumRotTracks, NumScaleTracks, @@ -6181,7 +5836,7 @@ void GatherAnimSequenceStats(FOutputDevice& Ar) NumTransTracks, NumRotTracks, NumScaleTracks, NumTransTracksWithOneKey, NumRotTracksWithOneKey, NumScaleTracksWithOneKey, TotalNumTransKeys, TotalNumRotKeys, TotalNumScaleKeys, - *FAnimationUtils::GetAnimationKeyFormatString(static_cast(Seq->KeyEncodingFormat)), + *FAnimationUtils::GetAnimationKeyFormatString(static_cast(Seq->CompressedData.CompressedDataStructure.KeyEncodingFormat)), (int32)Seq->GetResourceSizeBytes(EResourceSizeMode::EstimatedTotal) ); } Ar.Logf( TEXT("======================================================================") ); diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimTypes.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimTypes.cpp index c5018a8fad31..9fa72bd6c97c 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimTypes.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimTypes.cpp @@ -229,3 +229,19 @@ void FMarkerSyncData::CollectMarkersInRange(float PrevPosition, float NewPositio } } } + +#if 0 // Debug logging +template <> +void DebugLogArray(const TArray& RawData) +{ + for (int32 i = 0; i < RawData.Num(); ++i) + { + FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Track :%i\nTran\n"), i); + DebugLogArray(RawData[i].PosKeys); + FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Rot\n")); + DebugLogArray(RawData[i].RotKeys); + FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Scale\n")); + DebugLogArray(RawData[i].ScaleKeys); + } +} +#endif \ No newline at end of file diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimationUtils.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimationUtils.cpp index 1412f94ed2ea..b0fcae33eb17 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimationUtils.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimationUtils.cpp @@ -7,6 +7,7 @@ #include "AnimationUtils.h" #include "Misc/ConfigCacheIni.h" #include "UObject/Package.h" +#include "AnimationRuntime.h" #include "Animation/AnimCompress.h" #include "Animation/AnimCompress_BitwiseCompressOnly.h" #include "Animation/AnimCompress_RemoveLinearKeys.h" @@ -213,6 +214,15 @@ void FAnimationUtils::BuildComponentSpaceTransform(FTransform& OutTransform, } } +int32 FAnimationUtils::GetAnimTrackIndexForSkeletonBone(const int32 InSkeletonBoneIndex, const TArray& TrackToSkelMap) +{ + return TrackToSkelMap.IndexOfByPredicate([&](const FTrackToSkeletonMap& TrackToSkel) + { + return TrackToSkel.BoneTreeIndex == InSkeletonBoneIndex; + }); +} + +#if WITH_EDITOR /** * Utility function to measure the accuracy of a compressed animation. Each end-effector is checked for * world-space movement as a result of compression. @@ -222,7 +232,7 @@ void FAnimationUtils::BuildComponentSpaceTransform(FTransform& OutTransform, * @param ErrorStats Output structure containing the final compression error values * @return None. */ -void FAnimationUtils::ComputeCompressionError(const UAnimSequence* AnimSeq, const TArray& BoneData, AnimationErrorStats& ErrorStats) +void FAnimationUtils::ComputeCompressionError(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& CompressedData, AnimationErrorStats& ErrorStats) { ErrorStats.AverageError = 0.0f; ErrorStats.MaxError = 0.0f; @@ -230,21 +240,21 @@ void FAnimationUtils::ComputeCompressionError(const UAnimSequence* AnimSeq, cons ErrorStats.MaxErrorTime = 0.0f; int32 MaxErrorTrack = -1; - if (AnimSeq->GetCompressedNumberOfFrames() > 0) + if (CompressedData.CompressedNumberOfFrames > 0) { - const bool bCanUseCompressedData = (AnimSeq->CompressedByteStream.Num() > 0); + const bool bCanUseCompressedData = (CompressedData.CompressedByteStream.Num() > 0); if (!bCanUseCompressedData) { // If we can't use CompressedData, there's not much point in being here. return; } - const int32 NumBones = BoneData.Num(); + const int32 NumBones = CompressibleAnimData.BoneData.Num(); float ErrorCount = 0.0f; float ErrorTotal = 0.0f; - USkeleton* Skeleton = AnimSeq->GetSkeleton(); + USkeleton* Skeleton = CompressibleAnimData.Skeleton; check ( Skeleton ); const TArray& RefPose = Skeleton->GetRefLocalPoses(); @@ -258,8 +268,7 @@ void FAnimationUtils::ComputeCompressionError(const UAnimSequence* AnimSeq, cons // We do this only once, instead of every frame. struct FCachedBoneIndexData { - int32 RawTrackIndex; - int32 CompressedTrackIndex; + int32 TrackIndex; int32 ParentIndex; }; TArray CachedBoneIndexData; @@ -267,8 +276,7 @@ void FAnimationUtils::ComputeCompressionError(const UAnimSequence* AnimSeq, cons for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) { FCachedBoneIndexData& BoneIndexData = CachedBoneIndexData[BoneIndex]; - BoneIndexData.RawTrackIndex = Skeleton->GetAnimationTrackIndex(BoneIndex, AnimSeq, true); - BoneIndexData.CompressedTrackIndex = Skeleton->GetAnimationTrackIndex(BoneIndex, AnimSeq, false); + BoneIndexData.TrackIndex = GetAnimTrackIndexForSkeletonBone(BoneIndex, CompressibleAnimData.TrackToSkeletonMapTable); BoneIndexData.ParentIndex = Skeleton->GetReferenceSkeleton().GetParentIndex(BoneIndex); } @@ -282,12 +290,15 @@ void FAnimationUtils::ComputeCompressionError(const UAnimSequence* AnimSeq, cons const FTransform EndEffectorDummyBoneSocket(FQuat::Identity, FVector(END_EFFECTOR_DUMMY_BONE_LENGTH_SOCKET)); const FTransform EndEffectorDummyBone(FQuat::Identity, FVector(END_EFFECTOR_DUMMY_BONE_LENGTH)); - const FAnimKeyHelper Helper(AnimSeq->SequenceLength, AnimSeq->GetCompressedNumberOfFrames()); + const FAnimKeyHelper Helper(CompressibleAnimData.SequenceLength, CompressedData.CompressedNumberOfFrames); const float KeyLength = Helper.TimePerKey() + SMALL_NUMBER; - FAnimSequenceDecompressionContext DecompContext(AnimSeq); + const FUECompressedAnimData CompressedDataWrapper(CompressedData); + FAnimSequenceDecompressionContext DecompContext(CompressibleAnimData, CompressedDataWrapper); - for (int32 FrameIndex = 0; FrameIndexGetCompressedNumberOfFrames(); FrameIndex++) + const TArray& BoneData = CompressibleAnimData.BoneData; + + for (int32 FrameIndex = 0; FrameIndex< CompressedData.CompressedNumberOfFrames; FrameIndex++) { const float Time = (float)FrameIndex * KeyLength; DecompContext.Seek(Time); @@ -296,7 +307,7 @@ void FAnimationUtils::ComputeCompressionError(const UAnimSequence* AnimSeq, cons for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) { const FCachedBoneIndexData& BoneIndexData = CachedBoneIndexData[BoneIndex]; - if (BoneIndexData.RawTrackIndex == INDEX_NONE) + if (BoneIndexData.TrackIndex == INDEX_NONE) { // No track for the bone was found, use default transform const FTransform& RefPoseTransform = RefPose[BoneIndex]; @@ -309,7 +320,7 @@ void FAnimationUtils::ComputeCompressionError(const UAnimSequence* AnimSeq, cons // This is because additive animations are mostly rotation. // And for the error metric we measure distance between end effectors. // So that means additive animations by default will all be balled up at the origin and not show any error. - if (AnimSeq->IsValidAdditive()) + if (CompressibleAnimData.bIsValidAdditive) { const FTransform& RefPoseTransform = RefPose[BoneIndex]; RawTransforms[BoneIndex] = RefPoseTransform; @@ -317,8 +328,9 @@ void FAnimationUtils::ComputeCompressionError(const UAnimSequence* AnimSeq, cons FTransform AdditiveRawTransform; FTransform AdditiveNewTransform; - AnimSeq->GetBoneTransform(AdditiveRawTransform, BoneIndexData.RawTrackIndex, DecompContext, true); - AnimSeq->GetBoneTransform(AdditiveNewTransform, BoneIndexData.CompressedTrackIndex, DecompContext, false); + FAnimationUtils::ExtractTransformFromTrack(Time, CompressibleAnimData.NumFrames, CompressibleAnimData.SequenceLength, CompressibleAnimData.RawAnimationData[BoneIndexData.TrackIndex], CompressibleAnimData.Interpolation, AdditiveRawTransform); + + AnimationFormat_GetBoneAtom(AdditiveNewTransform, DecompContext, BoneIndexData.TrackIndex); const ScalarRegister VBlendWeight(1.f); RawTransforms[BoneIndex].AccumulateWithAdditiveScale(AdditiveRawTransform, VBlendWeight); @@ -326,8 +338,8 @@ void FAnimationUtils::ComputeCompressionError(const UAnimSequence* AnimSeq, cons } else { - AnimSeq->GetBoneTransform(RawTransforms[BoneIndex], BoneIndexData.RawTrackIndex, DecompContext, true); - AnimSeq->GetBoneTransform(NewTransforms[BoneIndex], BoneIndexData.CompressedTrackIndex, DecompContext, false); + FAnimationUtils::ExtractTransformFromTrack(Time, CompressibleAnimData.NumFrames, CompressibleAnimData.SequenceLength, CompressibleAnimData.RawAnimationData[BoneIndexData.TrackIndex], CompressibleAnimData.Interpolation, RawTransforms[BoneIndex]); + AnimationFormat_GetBoneAtom(NewTransforms[BoneIndex], DecompContext, BoneIndexData.TrackIndex); } } @@ -372,7 +384,7 @@ void FAnimationUtils::ComputeCompressionError(const UAnimSequence* AnimSeq, cons { ErrorStats.MaxError = Error; ErrorStats.MaxErrorBone = BoneIndex; - MaxErrorTrack = BoneIndexData.RawTrackIndex; + MaxErrorTrack = BoneIndexData.TrackIndex; ErrorStats.MaxErrorTime = Time; } } @@ -383,26 +395,9 @@ void FAnimationUtils::ComputeCompressionError(const UAnimSequence* AnimSeq, cons { ErrorStats.AverageError = ErrorTotal / ErrorCount; } - -#if 0 - // That's a big error, log out some information! - if( ErrorStats.MaxError > 10.f ) - { - UE_LOG(LogAnimation, Log, TEXT("!!! Big error found: %f, Time: %f, BoneIndex: %d, Track: %d, CompressionScheme: %s, additive: %d"), - ErrorStats.MaxError, - ErrorStats.MaxErrorTime, - ErrorStats.MaxErrorBone, - MaxErrorTrack, - AnimSeq->CompressionScheme ? *AnimSeq->CompressionScheme->GetFName().ToString() : TEXT("NULL"), - AnimSeq->bIsAdditive ); - UE_LOG(LogAnimation, Log, TEXT(" RawOrigin: %s, NormalOrigin: %s"), *RawTransforms(ErrorStats.MaxErrorBone).GetOrigin().ToString(), *NewTransforms(ErrorStats.MaxErrorBone).GetOrigin().ToString()); - - // We shouldn't have a big error with no compression. - check( AnimSeq->CompressionScheme != NULL ); - } -#endif } } +#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // @@ -483,51 +478,31 @@ bool FAnimationUtils::GetForcedRecompressionSetting() struct FAnimCompressionJobContext { // Inputs - int64 OriginalSize; - float MasterTolerance; bool bForceBelowThreshold; - const TArray* BoneData; FAnimCompressContext* CompressContext; UAnimCompress* CompressionAlgorithm; - UAnimSequence* AnimSeq; + + const FCompressibleAnimData* CompressibleAnimData; + FCompressibleAnimDataResult CompressionResult; + const TCHAR* CompressionName; int32* WinningCompressor_Count; float* WinningCompressor_Error; int64* WinningCompressor_Margin; - // Input/Output - int64 CurrentSize; - bool bSavedUseRawDataOnly; - - int64 WinningCompressorMarginalSavings; - int32* WinningCompressorCounter; - float* WinningCompressorErrorSum; - int64* WinningCompressorMarginalSavingsSum; - FString WinningCompressorName; - int32 WinningCompressorSavings; - float WinningCompressorError; - - UAnimCompress* SavedCompressionScheme; - AnimationCompressionFormat SavedTranslationCompressionFormat; - AnimationCompressionFormat SavedRotationCompressionFormat; - AnimationCompressionFormat SavedScaleCompressionFormat; - AnimationKeyFormat SavedKeyEncodingFormat; - TArray SavedCompressedTrackOffsets; - FCompressedOffsetData SavedCompressedScaleOffsets; - TArray SavedCompressedSegments; - TArray SavedCompressedByteStream; - AnimEncoding* SavedTranslationCodec; - AnimEncoding* SavedRotationCodec; - AnimEncoding* SavedScaleCodec; - // Outputs - AnimationErrorStats NewErrorStats; + AnimationErrorStats ErrorStats; float PctSaving; FAnimCompressionJobContext() { memset(this, 0, sizeof(FAnimCompressionJobContext)); } + + void UpdatePctSaving(int64 OriginalSize) + { + PctSaving = (OriginalSize > 0) ? (100.f - (100.f * float(CompressionResult.GetApproxBoneCompressedSize()) / float(OriginalSize))) : 0.f; + } }; static void TryCompressionInner(FAnimCompressionJobContext& JobContext, bool is_async); @@ -554,211 +529,50 @@ public: FAnimCompressionJobContext* JobContext; }; -static void TryCompressionInner(FAnimCompressionJobContext& JobContext, bool is_async) +bool ShouldKeepNewCompressionMethod(const FAnimCompressionJobContext& JobContext, SIZE_T OriginalSize, SIZE_T CurrentSize, float WinningCompressorError, float MasterTolerance) { -#if WITH_EDITOR - UAnimSequence* AnimSeq = JobContext.AnimSeq; + const SIZE_T NewSize = JobContext.CompressionResult.GetApproxBoneCompressedSize(); - /* try the alternative compressor */ - AnimSeq->CompressionScheme = JobContext.CompressionAlgorithm; - JobContext.CompressionAlgorithm->Reduce(AnimSeq, *JobContext.CompressContext, *JobContext.BoneData); - AnimSeq->SetUseRawDataOnly(false); - const SIZE_T NewSize = AnimSeq->GetApproxCompressedSize(); + /* compute the savings and compression error*/ + const int64 MemorySavingsFromOriginal = OriginalSize - NewSize; + const int64 MemorySavingsFromPrevious = CurrentSize - NewSize; - if (is_async) - { - JobContext.CurrentSize = NewSize; - } - else - { - /* compute the savings and compression error*/ - const int64 MemorySavingsFromOriginal = JobContext.OriginalSize - NewSize; - const int64 MemorySavingsFromPrevious = JobContext.CurrentSize - NewSize; + /* figure out our new compression error*/ + const AnimationErrorStats& ErrorStats = JobContext.ErrorStats; - /* figure out our new compression error*/ - FAnimationUtils::ComputeCompressionError(AnimSeq, *JobContext.BoneData, JobContext.NewErrorStats); + const bool bLowersError = ErrorStats.MaxError < WinningCompressorError; + const bool bErrorUnderThreshold = ErrorStats.MaxError <= MasterTolerance; - const bool bLowersError = JobContext.NewErrorStats.MaxError < JobContext.WinningCompressorError; - const bool bErrorUnderThreshold = JobContext.NewErrorStats.MaxError <= JobContext.MasterTolerance; + /* keep it if it we want to force the error below the threshold and it reduces error */ + bool bKeepNewCompressionMethod = false; + const bool bReducesErrorBelowThreshold = (bLowersError && (WinningCompressorError > MasterTolerance) && JobContext.bForceBelowThreshold); + bKeepNewCompressionMethod |= bReducesErrorBelowThreshold; + /* or if has an acceptable error and saves space */ + const bool bHasAcceptableErrorAndSavesSpace = bErrorUnderThreshold && (MemorySavingsFromPrevious > 0); + bKeepNewCompressionMethod |= bHasAcceptableErrorAndSavesSpace; + /* or if saves the same amount and an acceptable error that is lower than the previous best */ + const bool bLowersErrorAndSavesSameOrBetter = bErrorUnderThreshold && bLowersError && (MemorySavingsFromPrevious >= 0); + bKeepNewCompressionMethod |= bLowersErrorAndSavesSameOrBetter; - /* keep it if it we want to force the error below the threshold and it reduces error */ - bool bKeepNewCompressionMethod = false; - const bool bReducesErrorBelowThreshold = (bLowersError && (JobContext.WinningCompressorError > JobContext.MasterTolerance) && JobContext.bForceBelowThreshold); - bKeepNewCompressionMethod |= bReducesErrorBelowThreshold; - /* or if has an acceptable error and saves space */ - const bool bHasAcceptableErrorAndSavesSpace = bErrorUnderThreshold && (MemorySavingsFromPrevious > 0); - bKeepNewCompressionMethod |= bHasAcceptableErrorAndSavesSpace; - /* or if saves the same amount and an acceptable error that is lower than the previous best */ - const bool bLowersErrorAndSavesSameOrBetter = bErrorUnderThreshold && bLowersError && (MemorySavingsFromPrevious >= 0); - bKeepNewCompressionMethod |= bLowersErrorAndSavesSameOrBetter; + UE_LOG(LogAnimationCompression, Verbose, TEXT("- %s - bytes saved(%i) (%.1f%%) from previous(%i) MaxError(%.2f) bLowersError(%d) %s"), + JobContext.CompressionName, MemorySavingsFromOriginal, JobContext.PctSaving, MemorySavingsFromPrevious, ErrorStats.MaxError, bLowersError, bKeepNewCompressionMethod ? TEXT("(**Best so far**)") : TEXT("")); - JobContext.PctSaving = (JobContext.OriginalSize > 0) ? (100.f - (100.f * float(NewSize) / float(JobContext.OriginalSize))) : 0.f; - UE_LOG(LogAnimationCompression, Verbose, TEXT("- %s - bytes saved(%i) (%.1f%%) from previous(%i) MaxError(%.2f) bLowersError(%d) %s"), - JobContext.CompressionName, MemorySavingsFromOriginal, JobContext.PctSaving, MemorySavingsFromPrevious, JobContext.NewErrorStats.MaxError, bLowersError, bKeepNewCompressionMethod ? TEXT("(**Best so far**)") : TEXT("")); + UE_LOG(LogAnimationCompression, Verbose, TEXT(" bReducesErrorBelowThreshold(%d) bHasAcceptableErrorAndSavesSpace(%d) bLowersErrorAndSavesSameOrBetter(%d)"), + bReducesErrorBelowThreshold, bHasAcceptableErrorAndSavesSpace, bLowersErrorAndSavesSameOrBetter); - UE_LOG(LogAnimationCompression, Verbose, TEXT(" bReducesErrorBelowThreshold(%d) bHasAcceptableErrorAndSavesSpace(%d) bLowersErrorAndSavesSameOrBetter(%d)"), - bReducesErrorBelowThreshold, bHasAcceptableErrorAndSavesSpace, bLowersErrorAndSavesSameOrBetter); - - UE_LOG(LogAnimationCompression, Verbose, TEXT(" WinningCompressorError(%f) MasterTolerance(%f) bForceBelowThreshold(%d) bErrorUnderThreshold(%d)"), - JobContext.WinningCompressorError, JobContext.MasterTolerance, JobContext.bForceBelowThreshold, bErrorUnderThreshold); - - if (bKeepNewCompressionMethod) - { - JobContext.WinningCompressorMarginalSavings = MemorySavingsFromPrevious; - JobContext.WinningCompressorCounter = JobContext.WinningCompressor_Count; - JobContext.WinningCompressorErrorSum = JobContext.WinningCompressor_Error; - JobContext.WinningCompressorMarginalSavingsSum = JobContext.WinningCompressor_Margin; - JobContext.WinningCompressorName = JobContext.CompressionName; - JobContext.CurrentSize = NewSize; - JobContext.WinningCompressorSavings = MemorySavingsFromOriginal; - JobContext.WinningCompressorError = JobContext.NewErrorStats.MaxError; - - /* backup key information from the sequence */ - JobContext.SavedCompressionScheme = AnimSeq->CompressionScheme; - JobContext.SavedTranslationCompressionFormat = AnimSeq->TranslationCompressionFormat; - JobContext.SavedRotationCompressionFormat = AnimSeq->RotationCompressionFormat; - JobContext.SavedScaleCompressionFormat = AnimSeq->ScaleCompressionFormat; - JobContext.SavedKeyEncodingFormat = AnimSeq->KeyEncodingFormat; - JobContext.SavedCompressedTrackOffsets = AnimSeq->CompressedTrackOffsets; - JobContext.SavedCompressedScaleOffsets = AnimSeq->CompressedScaleOffsets; - JobContext.SavedCompressedSegments = AnimSeq->CompressedSegments; - JobContext.SavedCompressedByteStream = AnimSeq->CompressedByteStream; - JobContext.SavedTranslationCodec = AnimSeq->TranslationCodec; - JobContext.SavedRotationCodec = AnimSeq->RotationCodec; - JobContext.SavedScaleCodec = AnimSeq->ScaleCodec; - JobContext.bSavedUseRawDataOnly = false; - } - else - { - /* revert back to the old method by copying back the data we cached */ - AnimSeq->CompressionScheme = JobContext.SavedCompressionScheme; - AnimSeq->TranslationCompressionFormat = JobContext.SavedTranslationCompressionFormat; - AnimSeq->RotationCompressionFormat = JobContext.SavedRotationCompressionFormat; - AnimSeq->ScaleCompressionFormat = JobContext.SavedScaleCompressionFormat; - AnimSeq->KeyEncodingFormat = JobContext.SavedKeyEncodingFormat; - AnimSeq->CompressedTrackOffsets = JobContext.SavedCompressedTrackOffsets; - AnimSeq->CompressedByteStream = JobContext.SavedCompressedByteStream; - AnimSeq->CompressedScaleOffsets = JobContext.SavedCompressedScaleOffsets; - AnimSeq->CompressedSegments = JobContext.SavedCompressedSegments; - AnimSeq->TranslationCodec = JobContext.SavedTranslationCodec; - AnimSeq->RotationCodec = JobContext.SavedRotationCodec; - AnimSeq->ScaleCodec = JobContext.SavedScaleCodec; - AnimSeq->SetUseRawDataOnly(JobContext.bSavedUseRawDataOnly); - AnimationFormat_SetInterfaceLinks(*AnimSeq); - - const SIZE_T RestoredSize = AnimSeq->GetApproxCompressedSize(); - check(RestoredSize == JobContext.CurrentSize); - } - } -#endif // WITH_EDITOR + UE_LOG(LogAnimationCompression, Verbose, TEXT(" WinningCompressorError(%f) MasterTolerance(%f) bForceBelowThreshold(%d) bErrorUnderThreshold(%d)"), + WinningCompressorError, MasterTolerance, JobContext.bForceBelowThreshold, bErrorUnderThreshold); + return bKeepNewCompressionMethod; } -#define POPULATE_ANIM_COMPRESSION_JOB_CONTEXT(Name_, CompressionAlgorithm_, AnimSeq_, ParentAnimSeq_, CompressContext_, JobContext_) \ - (JobContext_).OriginalSize = OriginalSize; \ - (JobContext_).MasterTolerance = MasterTolerance; \ - (JobContext_).bForceBelowThreshold = bForceBelowThreshold; \ - (JobContext_).BoneData = &BoneData; \ - (JobContext_).CompressContext = &CompressContext_; \ - (JobContext_).CompressionAlgorithm = DuplicateObject((CompressionAlgorithm_), ParentAnimSeq_); \ - (JobContext_).CompressionAlgorithm->bEnableSegmenting = bEnableSegmenting; \ - (JobContext_).CompressionAlgorithm->IdealNumFramesPerSegment = IdealNumFramesPerSegment; \ - (JobContext_).CompressionAlgorithm->MaxNumFramesPerSegment = MaxNumFramesPerSegment; \ - (JobContext_).AnimSeq = AnimSeq_; \ - (JobContext_).CompressionName = TEXT(#Name_); \ - (JobContext_).WinningCompressor_Count = &(Name_ ## CompressorWins); \ - (JobContext_).WinningCompressor_Error = &(Name_ ## CompressorSumError); \ - (JobContext_).WinningCompressor_Margin = &(Name_ ## CompressorWinMargin); - -#define TRYCOMPRESSION(Name, CompressionAlgorithm_) \ - do \ - { \ - FAnimCompressionJobContext JobContext; \ - POPULATE_ANIM_COMPRESSION_JOB_CONTEXT(Name, CompressionAlgorithm_, AnimSeq, AnimSeq, CompressContext, JobContext); \ - \ - JobContext.CurrentSize = CompressorStats.CurrentSize; \ - JobContext.bSavedUseRawDataOnly = bSavedUseRawDataOnly; \ - JobContext.WinningCompressorMarginalSavings = CompressorStats.WinningCompressorMarginalSavings; \ - JobContext.WinningCompressorCounter = CompressorStats.WinningCompressorCounter; \ - JobContext.WinningCompressorErrorSum = CompressorStats.WinningCompressorErrorSum; \ - JobContext.WinningCompressorMarginalSavingsSum = CompressorStats.WinningCompressorMarginalSavingsSum; \ - JobContext.WinningCompressorName = CompressorStats.WinningCompressorName; \ - JobContext.WinningCompressorSavings = CompressorStats.WinningCompressorSavings; \ - JobContext.WinningCompressorError = CompressorStats.WinningCompressorError; \ - JobContext.SavedCompressionScheme = SavedCompressionScheme; \ - JobContext.SavedTranslationCompressionFormat = SavedTranslationCompressionFormat; \ - JobContext.SavedRotationCompressionFormat = SavedRotationCompressionFormat; \ - JobContext.SavedScaleCompressionFormat = SavedScaleCompressionFormat; \ - JobContext.SavedKeyEncodingFormat = SavedKeyEncodingFormat; \ - JobContext.SavedCompressedTrackOffsets = SavedCompressedTrackOffsets; \ - JobContext.SavedCompressedScaleOffsets = SavedCompressedScaleOffsets; \ - JobContext.SavedCompressedByteStream = SavedCompressedByteStream; \ - JobContext.SavedCompressedSegments = SavedCompressedSegments; \ - JobContext.SavedTranslationCodec = SavedTranslationCodec; \ - JobContext.SavedRotationCodec = SavedRotationCodec; \ - JobContext.SavedScaleCodec = SavedScaleCodec; \ - \ - TryCompressionInner(JobContext, false); \ - \ - CompressorStats.CurrentSize = JobContext.CurrentSize; \ - bSavedUseRawDataOnly = JobContext.bSavedUseRawDataOnly; \ - CompressorStats.WinningCompressorMarginalSavings = JobContext.WinningCompressorMarginalSavings; \ - CompressorStats.WinningCompressorCounter = JobContext.WinningCompressorCounter; \ - CompressorStats.WinningCompressorErrorSum = JobContext.WinningCompressorErrorSum; \ - CompressorStats.WinningCompressorMarginalSavingsSum = JobContext.WinningCompressorMarginalSavingsSum; \ - CompressorStats.WinningCompressorName = JobContext.WinningCompressorName; \ - CompressorStats.WinningCompressorSavings = JobContext.WinningCompressorSavings; \ - CompressorStats.WinningCompressorError = JobContext.WinningCompressorError; \ - SavedCompressionScheme = JobContext.SavedCompressionScheme; \ - SavedTranslationCompressionFormat = JobContext.SavedTranslationCompressionFormat; \ - SavedRotationCompressionFormat = JobContext.SavedRotationCompressionFormat; \ - SavedScaleCompressionFormat = JobContext.SavedScaleCompressionFormat; \ - SavedKeyEncodingFormat = JobContext.SavedKeyEncodingFormat; \ - SavedCompressedTrackOffsets = JobContext.SavedCompressedTrackOffsets; \ - SavedCompressedScaleOffsets = JobContext.SavedCompressedScaleOffsets; \ - SavedCompressedByteStream = JobContext.SavedCompressedByteStream; \ - SavedCompressedSegments = JobContext.SavedCompressedSegments; \ - SavedTranslationCodec = JobContext.SavedTranslationCodec; \ - SavedRotationCodec = JobContext.SavedRotationCodec; \ - SavedScaleCodec = JobContext.SavedScaleCodec; \ - \ - NewErrorStats = JobContext.NewErrorStats; \ - CompressorStats.PctSaving = JobContext.PctSaving; \ - } \ - while (false) - -#define TRYCOMPRESSION_SYNC(Name, CompressionAlgorithm_) TRYCOMPRESSION(Name, CompressionAlgorithm_) - -// TODO: Async compression is DISABLED for additive sequences because UAnimCompress_RemoveLinearKeys::ConvertFromRelativeSpace() -// modifies the RAW data! This is bad... Even though we duplicate the anim sequence, some additive information isn't -// copied over and it doesn't seem safe to generate it by calling UAnimSequence::BakeOutAdditiveIntoRawData() -#define TRYCOMPRESSION_ASYNC(Name, CompressionAlgorithm_) \ - do \ - { \ - if (!AnimSeq->IsValidAdditive()) \ - { \ - FAnimCompressionJobContext* JobContext = new FAnimCompressionJobContext(); \ - FAnimCompressContext CompressContextCopy(CompressContext); \ - CompressContextCopy.CompressionSummary = FCompressionMemorySummary(false); \ - UAnimSequence* AnimSeqCopy = DuplicateObject(AnimSeq, GetTransientPackage()); \ - POPULATE_ANIM_COMPRESSION_JOB_CONTEXT(Name, CompressionAlgorithm_, AnimSeqCopy, AnimSeq, CompressContextCopy, *JobContext); \ - AnimCompressionJobContexes.Add(JobContext); \ - \ - AnimCompressionTask_CompletionEvents.Add(TGraphTask::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(JobContext)); \ - } \ - else \ - { \ - TRYCOMPRESSION_SYNC(Name, CompressionAlgorithm_); \ - } \ - } \ - while (false) - -#define LOG_COMPRESSION_STATUS(Name) \ - UE_LOG(LogAnimationCompression, Log, TEXT("\t\tWins for '%32s': %4i\t\t%f\t%i bytes"), TEXT(#Name), Name ## CompressorWins, (Name ## CompressorWins > 0) ? Name ## CompressorSumError / Name ## CompressorWins : 0.0f, Name ## CompressorWinMargin) - -#define DECLARE_ANIM_COMP_ALGORITHM(Algorithm) \ - static int32 Algorithm ## CompressorWins = 0; \ - static float Algorithm ## CompressorSumError = 0.0f; \ - static int64 Algorithm ## CompressorWinMargin = 0 +static void TryCompressionInner(FAnimCompressionJobContext& JobContext, bool bIsAsync) +{ +#if WITH_EDITOR + /* try the alternative compressor */ + JobContext.CompressionAlgorithm->Reduce(*JobContext.CompressibleAnimData, JobContext.CompressionResult); + FAnimationUtils::ComputeCompressionError(*JobContext.CompressibleAnimData, JobContext.CompressionResult, JobContext.ErrorStats); +#endif // WITH_EDITOR +} #if WITH_EDITORONLY_DATA struct WinningCompressorStats @@ -780,6 +594,94 @@ struct WinningCompressorStats } }; +void HandlePostTryCompression(FAnimCompressionJobContext& JobContext, int64 OriginalSize, float MasterTolerance, WinningCompressorStats& OutCompressorStats, FCompressibleAnimDataResult& OutCompressedData, AnimationErrorStats& OutNewErrorStats) +{ + JobContext.UpdatePctSaving(OriginalSize); + + bool bKeepNewCompressionMethod = ShouldKeepNewCompressionMethod(JobContext, OriginalSize, OutCompressorStats.CurrentSize, OutCompressorStats.WinningCompressorError, MasterTolerance); + if (bKeepNewCompressionMethod) + { + OutCompressedData = JobContext.CompressionResult; + + OutNewErrorStats = JobContext.ErrorStats; + + int64 NewSize = JobContext.CompressionResult.GetApproxBoneCompressedSize(); + const int64 MemorySavingsFromOriginal = OriginalSize - NewSize; + const int64 MemorySavingsFromPrevious = OutCompressorStats.CurrentSize - NewSize; + + OutCompressorStats.CurrentSize = NewSize; + OutCompressorStats.WinningCompressorMarginalSavings = MemorySavingsFromPrevious; + OutCompressorStats.WinningCompressorCounter = JobContext.WinningCompressor_Count; + OutCompressorStats.WinningCompressorErrorSum = JobContext.WinningCompressor_Error; + OutCompressorStats.WinningCompressorMarginalSavingsSum = JobContext.WinningCompressor_Margin; + OutCompressorStats.WinningCompressorName = JobContext.CompressionName; + OutCompressorStats.WinningCompressorSavings = MemorySavingsFromOriginal; + OutCompressorStats.WinningCompressorError = JobContext.ErrorStats.MaxError; + OutCompressorStats.PctSaving = JobContext.PctSaving; + } +} +#endif + +#define POPULATE_ANIM_COMPRESSION_JOB_CONTEXT(Name_, CompressionAlgorithm_, CompressContext_, JobContext_) \ + (JobContext_).bForceBelowThreshold = bForceBelowThreshold; \ + (JobContext_).CompressContext = &CompressContext_; \ + (JobContext_).CompressibleAnimData = &CompressibleAnimData; \ + (JobContext_).CompressionAlgorithm = DuplicateObject((CompressionAlgorithm_), GetTransientPackage()); \ + (JobContext_).CompressionAlgorithm->bEnableSegmenting = bEnableSegmenting; \ + (JobContext_).CompressionAlgorithm->IdealNumFramesPerSegment = IdealNumFramesPerSegment; \ + (JobContext_).CompressionAlgorithm->MaxNumFramesPerSegment = MaxNumFramesPerSegment; \ + (JobContext_).CompressionName = TEXT(#Name_); \ + (JobContext_).WinningCompressor_Count = &(Name_ ## CompressorWins); \ + (JobContext_).WinningCompressor_Error = &(Name_ ## CompressorSumError); \ + (JobContext_).WinningCompressor_Margin = &(Name_ ## CompressorWinMargin); + +#define TRYCOMPRESSION(Name, CompressionAlgorithm_) \ + do \ + { \ + FAnimCompressionJobContext JobContext; \ + POPULATE_ANIM_COMPRESSION_JOB_CONTEXT(Name, CompressionAlgorithm_, CompressContext, JobContext); \ + \ + TryCompressionInner(JobContext, false); \ + \ + HandlePostTryCompression(JobContext, OriginalSize, MasterTolerance, CompressorStats, OutCompressedData, NewErrorStats); \ + } \ + while (false) + +#define TRYCOMPRESSION_SYNC(Name, CompressionAlgorithm_) TRYCOMPRESSION(Name, CompressionAlgorithm_) + +// TODO: Async compression is DISABLED for additive sequences because UAnimCompress_RemoveLinearKeys::ConvertFromRelativeSpace() +// modifies the RAW data! This is bad... Even though we duplicate the anim sequence, some additive information isn't +// copied over and it doesn't seem safe to generate it by calling UAnimSequence::BakeOutAdditiveIntoRawData() +#define TRYCOMPRESSION_ASYNC(Name, CompressionAlgorithm_) \ + do \ + { \ + if (!CompressibleAnimData.bIsValidAdditive) \ + { \ + FAnimCompressionJobContext* JobContext = new FAnimCompressionJobContext(); \ + FAnimCompressContext CompressContextCopy(CompressContext); \ + CompressContextCopy.CompressionSummary = FCompressionMemorySummary(false); \ + POPULATE_ANIM_COMPRESSION_JOB_CONTEXT(Name, CompressionAlgorithm_, CompressContextCopy, *JobContext); \ + AnimCompressionJobContexes.Add(JobContext); \ + \ + AnimCompressionTask_CompletionEvents.Add(TGraphTask::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(JobContext)); \ + } \ + else \ + { \ + TRYCOMPRESSION_SYNC(Name, CompressionAlgorithm_); \ + } \ + } \ + while (false) + +#define LOG_COMPRESSION_STATUS(Name) \ + UE_LOG(LogAnimationCompression, Log, TEXT("\t\tWins for '%32s': %4i\t\t%f\t%i bytes"), TEXT(#Name), Name ## CompressorWins, (Name ## CompressorWins > 0) ? Name ## CompressorSumError / Name ## CompressorWins : 0.0f, Name ## CompressorWinMargin) + +#define DECLARE_ANIM_COMP_ALGORITHM(Algorithm) \ + static int32 Algorithm ## CompressorWins = 0; \ + static float Algorithm ## CompressorSumError = 0.0f; \ + static int64 Algorithm ## CompressorWinMargin = 0 + +#if WITH_EDITORONLY_DATA + static void WaitForAnimCompressionJobs(const FGraphEventArray& AnimCompressionTask_CompletionEvents) { FTaskGraphInterface::Get().WaitUntilTasksComplete(AnimCompressionTask_CompletionEvents, ENamedThreads::GameThread); @@ -789,9 +691,6 @@ static void ClearAnimCompressionJobs(FGraphEventArray& AnimCompressionTask_Compl { for (FAnimCompressionJobContext* Context : AnimCompressionJobContexes) { - Context->AnimSeq->RecycleAnimSequence(); - Context->AnimSeq->ClearFlags(RF_Standalone | RF_Public); - Context->AnimSeq->MarkPendingKill(); delete Context; } AnimCompressionJobContexes.Reset(); @@ -805,52 +704,22 @@ static FAnimCompressionJobContext* FindBestAnimCompression(TArrayGetApproxCompressedSize(); - - /* compute the savings and compression error*/ - const int64 MemorySavingsFromOriginal = OriginalSize - NewSize; - const int64 MemorySavingsFromPrevious = CurrentSize - NewSize; - - /* figure out our new compression error*/ - FAnimationUtils::ComputeCompressionError(JobContext.AnimSeq, *JobContext.BoneData, JobContext.NewErrorStats); - - const bool bLowersError = JobContext.NewErrorStats.MaxError < WinningCompressorError; - const bool bErrorUnderThreshold = JobContext.NewErrorStats.MaxError <= MasterTolerance; - - /* keep it if it we want to force the error below the threshold and it reduces error */ - bool bKeepNewCompressionMethod = false; - const bool bReducesErrorBelowThreshold = (bLowersError && (WinningCompressorError > MasterTolerance) && JobContext.bForceBelowThreshold); - bKeepNewCompressionMethod |= bReducesErrorBelowThreshold; - /* or if has an acceptable error and saves space */ - const bool bHasAcceptableErrorAndSavesSpace = bErrorUnderThreshold && (MemorySavingsFromPrevious > 0); - bKeepNewCompressionMethod |= bHasAcceptableErrorAndSavesSpace; - /* or if saves the same amount and an acceptable error that is lower than the previous best */ - const bool bLowersErrorAndSavesSameOrBetter = bErrorUnderThreshold && bLowersError && (MemorySavingsFromPrevious >= 0); - bKeepNewCompressionMethod |= bLowersErrorAndSavesSameOrBetter; - - JobContext.PctSaving = (OriginalSize > 0) ? (100.f - (100.f * float(NewSize) / float(OriginalSize))) : 0.f; - UE_LOG(LogAnimationCompression, Verbose, TEXT("- %s - bytes saved(%i) (%.1f%%) from previous(%i) MaxError(%.2f) bLowersError(%d) %s"), - JobContext.CompressionName, MemorySavingsFromOriginal, JobContext.PctSaving, MemorySavingsFromPrevious, JobContext.NewErrorStats.MaxError, bLowersError, bKeepNewCompressionMethod ? TEXT("(**Best so far**)") : TEXT("")); - - UE_LOG(LogAnimationCompression, Verbose, TEXT(" bReducesErrorBelowThreshold(%d) bHasAcceptableErrorAndSavesSpace(%d) bLowersErrorAndSavesSameOrBetter(%d)"), - bReducesErrorBelowThreshold, bHasAcceptableErrorAndSavesSpace, bLowersErrorAndSavesSameOrBetter); - - UE_LOG(LogAnimationCompression, Verbose, TEXT(" WinningCompressorError(%f) MasterTolerance(%f) bForceBelowThreshold(%d) bErrorUnderThreshold(%d)"), - WinningCompressorError, JobContext.MasterTolerance, JobContext.bForceBelowThreshold, bErrorUnderThreshold); + bool bKeepNewCompressionMethod = ShouldKeepNewCompressionMethod(JobContext, OriginalSize, CurrentSize, WinningCompressorError, MasterTolerance); if (bKeepNewCompressionMethod) { BestJobContext = context; - WinningCompressorError = JobContext.NewErrorStats.MaxError; - CurrentSize = NewSize; + WinningCompressorError = JobContext.ErrorStats.MaxError; + CurrentSize = JobContext.CompressionResult.GetApproxBoneCompressedSize(); } } return BestJobContext; } -static void UpdateAnimCompressionFromAsyncJobs(UAnimSequence* AnimSeq, FGraphEventArray& AnimCompressionTask_CompletionEvents, TArray& AnimCompressionJobContexes, SIZE_T OriginalSize, WinningCompressorStats& CompressorStats, float MasterTolerance) +static void UpdateAnimCompressionFromAsyncJobs(FCompressibleAnimDataResult& OutCompressedData, FGraphEventArray& AnimCompressionTask_CompletionEvents, TArray& AnimCompressionJobContexes, SIZE_T OriginalSize, WinningCompressorStats& CompressorStats, float MasterTolerance) { // Pick the best const FAnimCompressionJobContext* BestJobContext = FindBestAnimCompression(AnimCompressionJobContexes, OriginalSize, CompressorStats.CurrentSize, CompressorStats.WinningCompressorError, MasterTolerance); @@ -860,34 +729,21 @@ static void UpdateAnimCompressionFromAsyncJobs(UAnimSequence* AnimSeq, FGraphEve const FAnimCompressionJobContext& JobContext = *BestJobContext; /* Copy our data */ - AnimSeq->CompressionScheme = JobContext.AnimSeq->CompressionScheme; - AnimSeq->TranslationCompressionFormat = JobContext.AnimSeq->TranslationCompressionFormat; - AnimSeq->RotationCompressionFormat = JobContext.AnimSeq->RotationCompressionFormat; - AnimSeq->KeyEncodingFormat = JobContext.AnimSeq->KeyEncodingFormat; - AnimSeq->CompressedTrackOffsets = JobContext.AnimSeq->CompressedTrackOffsets; - AnimSeq->CompressedByteStream = JobContext.AnimSeq->CompressedByteStream; - AnimSeq->CompressedScaleOffsets = JobContext.AnimSeq->CompressedScaleOffsets; - AnimSeq->CompressedSegments = JobContext.AnimSeq->CompressedSegments; - AnimSeq->TranslationCodec = JobContext.AnimSeq->TranslationCodec; - AnimSeq->RotationCodec = JobContext.AnimSeq->RotationCodec; - AnimSeq->ScaleCodec = JobContext.AnimSeq->ScaleCodec; - AnimSeq->SetUseRawDataOnly(false); - AnimationFormat_SetInterfaceLinks(*AnimSeq); + OutCompressedData = JobContext.CompressionResult; - const SIZE_T RestoredSize = AnimSeq->GetApproxCompressedSize(); - check(RestoredSize == JobContext.CurrentSize); + const SIZE_T NewSize = OutCompressedData.GetApproxBoneCompressedSize(); - const int64 MemorySavingsFromOriginal = OriginalSize - RestoredSize; - const SIZE_T MemorySavingsFromPrevious = CompressorStats.CurrentSize - RestoredSize; + const int64 MemorySavingsFromOriginal = OriginalSize - NewSize; + const SIZE_T MemorySavingsFromPrevious = CompressorStats.CurrentSize - NewSize; CompressorStats.WinningCompressorMarginalSavings = MemorySavingsFromPrevious; CompressorStats.WinningCompressorCounter = JobContext.WinningCompressor_Count; CompressorStats.WinningCompressorErrorSum = JobContext.WinningCompressor_Error; CompressorStats.WinningCompressorMarginalSavingsSum = JobContext.WinningCompressor_Margin; CompressorStats.WinningCompressorName = JobContext.CompressionName; - CompressorStats.CurrentSize = RestoredSize; + CompressorStats.CurrentSize = NewSize; CompressorStats.WinningCompressorSavings = MemorySavingsFromOriginal; - CompressorStats.WinningCompressorError = JobContext.NewErrorStats.MaxError; + CompressorStats.WinningCompressorError = JobContext.ErrorStats.MaxError; } ClearAnimCompressionJobs(AnimCompressionTask_CompletionEvents, AnimCompressionJobContexes); @@ -929,25 +785,15 @@ public: * @param bOutput If false don't generate output or compute memory savings. * @return None. */ -void FAnimationUtils::CompressAnimSequence(UAnimSequence* AnimSeq, FAnimCompressContext& CompressContext) +void FAnimationUtils::CompressAnimSequence(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& OutCompressedData, FAnimCompressContext& CompressContext) { if (FPlatformProperties::HasEditorOnlyData()) { - // the underlying code won't work right without skeleton. - if ( !AnimSeq->GetSkeleton() ) - { - return; - } - // get the master tolerance we will use to guide recompression float MasterTolerance = GetAlternativeCompressionThreshold(); bool bOnlyCheckForMissingSkeletalMeshes = UAnimationSettings::Get()->bOnlyCheckForMissingSkeletalMeshes; - if (bOnlyCheckForMissingSkeletalMeshes) - { - TestForMissingMeshes(AnimSeq); - } - else + if (!bOnlyCheckForMissingSkeletalMeshes) { UAnimationSettings* AnimSetting = UAnimationSettings::Get(); bool bForceBelowThreshold = AnimSetting->bForceBelowThreshold; @@ -964,19 +810,8 @@ void FAnimationUtils::CompressAnimSequence(UAnimSequence* AnimSeq, FAnimCompress int32 IdealNumFramesPerSegment = 64; int32 MaxNumFramesPerSegment = (IdealNumFramesPerSegment * 2) - 1; - // Build skeleton metadata to use during the key reduction. - TArray BoneData; - FAnimationUtils::BuildSkeletonMetaData(AnimSeq->GetSkeleton(), BoneData); - #if WITH_EDITORONLY_DATA - UAnimCompress_Automatic* AutoCompressionScheme = Cast(AnimSeq->CompressionScheme); - - CompressContext.GatherPreCompressionStats(AnimSeq); - - AnimSeq->CompressedByteStream.Reset(); - AnimSeq->CompressedTrackOffsets.Reset(); - AnimSeq->CompressedScaleOffsets.OffsetData.Reset(); - AnimSeq->CompressedSegments.Reset(); + UAnimCompress_Automatic* AutoCompressionScheme = Cast(CompressibleAnimData.RequestedCompressionScheme); if (AutoCompressionScheme != nullptr) { @@ -985,7 +820,7 @@ void FAnimationUtils::CompressAnimSequence(UAnimSequence* AnimSeq, FAnimCompress IdealNumFramesPerSegment = AutoCompressionScheme->IdealNumFramesPerSegment; MaxNumFramesPerSegment = AutoCompressionScheme->MaxNumFramesPerSegment; } - else if (AnimSeq->CompressionScheme != nullptr) + else if (CompressibleAnimData.RequestedCompressionScheme != nullptr) { bTryExhaustiveSearch = AnimSetting->bTryExhaustiveSearch; } @@ -996,7 +831,8 @@ void FAnimationUtils::CompressAnimSequence(UAnimSequence* AnimSeq, FAnimCompress //Scoped timing of compression, make sure nothing else is added to this scope FCompressionTimeElapsed TimeTracker(CompressionTime); CompressAnimSequenceExplicit( - AnimSeq, + CompressibleAnimData, + OutCompressedData, CompressContext, CompressContext.bAllowAlternateCompressor ? MasterTolerance : 0.0f, bFirstRecompressUsingCurrentOrDefault, @@ -1005,11 +841,10 @@ void FAnimationUtils::CompressAnimSequence(UAnimSequence* AnimSeq, FAnimCompress bTryExhaustiveSearch, bEnableSegmenting, IdealNumFramesPerSegment, - MaxNumFramesPerSegment, - BoneData); + MaxNumFramesPerSegment); } - CompressContext.GatherPostCompressionStats(AnimSeq, BoneData, CompressionTime); + CompressContext.GatherPostCompressionStats(CompressibleAnimData, OutCompressedData, CompressionTime); } } } @@ -1027,7 +862,8 @@ void FAnimationUtils::CompressAnimSequence(UAnimSequence* AnimSeq, FAnimCompress * @return None. */ void FAnimationUtils::CompressAnimSequenceExplicit( - UAnimSequence* AnimSeq, + const FCompressibleAnimData& CompressibleAnimData, + FCompressibleAnimDataResult& OutCompressedData, FAnimCompressContext& CompressContext, float MasterTolerance, bool bFirstRecompressUsingCurrentOrDefault, @@ -1036,8 +872,7 @@ void FAnimationUtils::CompressAnimSequenceExplicit( bool bTryExhaustiveSearch, bool bEnableSegmenting, int32 IdealNumFramesPerSegment, - int32 MaxNumFramesPerSegment, - const TArray& BoneData) + int32 MaxNumFramesPerSegment) { #if WITH_EDITORONLY_DATA DECLARE_ANIM_COMP_ALGORITHM(BitwiseACF_Float96); @@ -1085,14 +920,7 @@ void FAnimationUtils::CompressAnimSequenceExplicit( DECLARE_ANIM_COMP_ALGORITHM(Linear_PerTrackExp1); DECLARE_ANIM_COMP_ALGORITHM(Linear_PerTrackExp2); - check(AnimSeq != nullptr); - if (AnimSeq->HasAnyFlags(RF_NeedLoad)) - { - AnimSeq->GetLinker()->Preload(AnimSeq); - } - - // attempt to find the default skeletal mesh associated with this sequence - USkeleton* Skeleton = AnimSeq->GetSkeleton(); + USkeleton* Skeleton = CompressibleAnimData.Skeleton; check (Skeleton); if (Skeleton->HasAnyFlags(RF_NeedLoad)) { @@ -1110,13 +938,13 @@ void FAnimationUtils::CompressAnimSequenceExplicit( static int64 TotalSizeNow = 0; static int64 TotalUncompressed = 0; - const int32 NumRawDataTracks = AnimSeq->GetRawAnimationData().Num(); + const int32 NumRawDataTracks = CompressibleAnimData.RawAnimationData.Num(); // we must have raw data to continue if(NumRawDataTracks > 0 ) { // If compression Scheme is automatic, then we definitely want to 'TryAlternateCompressor'. - if (AnimSeq->CompressionScheme && AnimSeq->CompressionScheme->IsA(UAnimCompress_Automatic::StaticClass())) + if (CompressibleAnimData.RequestedCompressionScheme && CompressibleAnimData.RequestedCompressionScheme->IsA(UAnimCompress_Automatic::StaticClass())) { MasterTolerance = GetAlternativeCompressionThreshold(); } @@ -1124,24 +952,20 @@ void FAnimationUtils::CompressAnimSequenceExplicit( // See if we're trying alternate compressors const bool bTryAlternateCompressor = MasterTolerance > 0.0f; - // Filter RAW data to get rid of mismatched tracks (translation/rotation data with a different number of keys than there are frames) - // No trivial key removal is done at this point (impossible error metrics of -1), since all of the techniques will perform it themselves - AnimSeq->CompressRawAnimData(-1.0f, -1.0f); + AnimationErrorStats TrueOriginalErrorStats; + FAnimationUtils::ComputeCompressionError(CompressibleAnimData, OutCompressedData, TrueOriginalErrorStats); AnimationErrorStats OriginalErrorStats; - AnimationErrorStats TrueOriginalErrorStats; - - FAnimationUtils::ComputeCompressionError(AnimSeq, BoneData, TrueOriginalErrorStats); int32 AfterOriginalRecompression = 0; - if( (bFirstRecompressUsingCurrentOrDefault && !bTryAlternateCompressor) || (AnimSeq->CompressedByteStream.Num() == 0)) + if( (bFirstRecompressUsingCurrentOrDefault && !bTryAlternateCompressor) || (OutCompressedData.CompressedByteStream.Num() == 0)) { - UAnimCompress* OriginalCompressionAlgorithm = AnimSeq->CompressionScheme ? AnimSeq->CompressionScheme : FAnimationUtils::GetDefaultAnimationCompressionAlgorithm(); + UAnimCompress* OriginalCompressionAlgorithm = CompressibleAnimData.RequestedCompressionScheme ? CompressibleAnimData.RequestedCompressionScheme : FAnimationUtils::GetDefaultAnimationCompressionAlgorithm(); // Automatic compression brings us back here, so don't create an infinite loop and pick bitwise compress instead. if (!OriginalCompressionAlgorithm || OriginalCompressionAlgorithm->IsA(UAnimCompress_Automatic::StaticClass())) { - UAnimCompress* CompressionAlgorithm = NewObject(AnimSeq); + UAnimCompress* CompressionAlgorithm = NewObject(); if (OriginalCompressionAlgorithm != nullptr) { // Keep the same segmenting settings @@ -1154,45 +978,42 @@ void FAnimationUtils::CompressAnimSequenceExplicit( } UE_LOG(LogAnimationCompression, Log, TEXT("Recompressing (%s) using current/default (%s) bFirstRecompressUsingCurrentOrDefault(%d) bTryAlternateCompressor(%d) IsCompressedDataValid(%d)"), - *AnimSeq->GetFullName(), + *CompressibleAnimData.FullName, *OriginalCompressionAlgorithm->GetName(), bFirstRecompressUsingCurrentOrDefault, bTryAlternateCompressor, - AnimSeq->IsCompressedDataValid()); + OutCompressedData.IsCompressedDataValid()); - AnimSeq->CompressionScheme = DuplicateObject(OriginalCompressionAlgorithm, AnimSeq); - OriginalCompressionAlgorithm->Reduce(AnimSeq, CompressContext, BoneData); - AnimSeq->SetUseRawDataOnly(false); - AfterOriginalRecompression = AnimSeq->GetApproxCompressedSize(); + OriginalCompressionAlgorithm->Reduce(CompressibleAnimData, OutCompressedData); + + AfterOriginalRecompression = OutCompressedData.GetApproxBoneCompressedSize(); // figure out our current compression error - FAnimationUtils::ComputeCompressionError(AnimSeq, BoneData, OriginalErrorStats); + FAnimationUtils::ComputeCompressionError(CompressibleAnimData, OutCompressedData, OriginalErrorStats); } else { - AfterOriginalRecompression = AnimSeq->GetApproxCompressedSize(); + AfterOriginalRecompression = OutCompressedData.GetApproxBoneCompressedSize(); OriginalErrorStats = TrueOriginalErrorStats; } + //For logging + FString OriginalKeyEncodingFormat = GetAnimationKeyFormatString(static_cast(OutCompressedData.KeyEncodingFormat)); + FString OriginalRotationFormat = FAnimationUtils::GetAnimationCompressionFormatString(static_cast(OutCompressedData.TranslationCompressionFormat)); + FString OriginalTranslationFormat = FAnimationUtils::GetAnimationCompressionFormatString(static_cast(OutCompressedData.RotationCompressionFormat)); + // Get the current size - SIZE_T OriginalSize = AnimSeq->GetApproxCompressedSize(); + SIZE_T OriginalSize = AfterOriginalRecompression; TotalSizeBefore += OriginalSize; // Estimate total uncompressed - TotalUncompressed += ((sizeof(FVector) + sizeof(FQuat) + sizeof(FVector)) * NumRawDataTracks * AnimSeq->GetRawNumberOfFrames()); - - // start with the current technique, or the default if none exists. - // this will serve as our fallback if no better technique can be found - const int32 OriginalKeyEncodingFormat = AnimSeq->KeyEncodingFormat; - const int32 OriginalTranslationFormat = AnimSeq->TranslationCompressionFormat; - const int32 OriginalRotationFormat = AnimSeq->RotationCompressionFormat; + TotalUncompressed += ((sizeof(FVector) + sizeof(FQuat) + sizeof(FVector)) * NumRawDataTracks * CompressibleAnimData.NumFrames); // Check for global permission to try an alternative compressor - // we don't check for bDoNotOverrideCompression here, as that is now used as part of the UAnimCompress_Automatic compressor // And it's valid to manually recompress animations - if( bTryAlternateCompressor /*&& (!AnimSeq->bDoNotOverrideCompression */) + if( bTryAlternateCompressor ) { - ensure(AnimSeq->CompressedByteStream.Num() > 0); + ensure(OutCompressedData.CompressedByteStream.Num() > 0); AnimationErrorStats NewErrorStats = OriginalErrorStats; if (bRaiseMaxErrorToExisting) @@ -1214,21 +1035,6 @@ void FAnimationUtils::CompressAnimSequenceExplicit( FGraphEventArray AnimCompressionTask_CompletionEvents; TArray AnimCompressionJobContexes; - // backup key information from the sequence - class UAnimCompress* SavedCompressionScheme = AnimSeq->CompressionScheme; - AnimationCompressionFormat SavedTranslationCompressionFormat = AnimSeq->TranslationCompressionFormat; - AnimationCompressionFormat SavedRotationCompressionFormat = AnimSeq->RotationCompressionFormat; - AnimationCompressionFormat SavedScaleCompressionFormat = AnimSeq->ScaleCompressionFormat; - AnimationKeyFormat SavedKeyEncodingFormat = AnimSeq->KeyEncodingFormat; - TArray SavedCompressedTrackOffsets = AnimSeq->CompressedTrackOffsets; - TArray SavedCompressedByteStream = AnimSeq->CompressedByteStream; - FCompressedOffsetData SavedCompressedScaleOffsets = AnimSeq->CompressedScaleOffsets; - TArray SavedCompressedSegments = AnimSeq->CompressedSegments; - AnimEncoding* SavedTranslationCodec = AnimSeq->TranslationCodec; - AnimEncoding* SavedRotationCodec = AnimSeq->RotationCodec; - AnimEncoding* SavedScaleCodec = AnimSeq->ScaleCodec; - bool bSavedUseRawDataOnly = AnimSeq->OnlyUseRawData(); - if (!bTryExhaustiveSearch) { // Dispatch our async compression @@ -1256,7 +1062,7 @@ void FAnimationUtils::CompressAnimSequenceExplicit( UAnimCompress_PerTrackCompression* PerTrackCompressor = NewObject(); PerTrackCompressor->bUseAdaptiveError = true; - if (AnimSeq->GetRawNumberOfFrames() > 1) + if (CompressibleAnimData.NumFrames > 1) { PerTrackCompressor->bActuallyFilterLinearKeys = true; PerTrackCompressor->bRetarget = true; @@ -1295,7 +1101,7 @@ void FAnimationUtils::CompressAnimSequenceExplicit( PerTrackCompressor->bUseAdaptiveError = true; // Try the decimation algorithms - if (AnimSeq->GetRawNumberOfFrames() >= PerTrackCompressor->MinKeysForResampling) + if (CompressibleAnimData.NumFrames >= PerTrackCompressor->MinKeysForResampling) { PerTrackCompressor->bActuallyFilterLinearKeys = false; PerTrackCompressor->bRetarget = false; @@ -1308,7 +1114,7 @@ void FAnimationUtils::CompressAnimSequenceExplicit( } } - if (AnimSeq->GetRawNumberOfFrames() > 1) + if (CompressibleAnimData.NumFrames > 1) { UAnimCompress_RemoveLinearKeys* LinearKeyRemover = NewObject(); { @@ -1335,32 +1141,32 @@ void FAnimationUtils::CompressAnimSequenceExplicit( } WaitForAnimCompressionJobs(AnimCompressionTask_CompletionEvents); - UpdateAnimCompressionFromAsyncJobs(AnimSeq, AnimCompressionTask_CompletionEvents, AnimCompressionJobContexes, OriginalSize, CompressorStats, MasterTolerance); + UpdateAnimCompressionFromAsyncJobs(OutCompressedData, AnimCompressionTask_CompletionEvents, AnimCompressionJobContexes, OriginalSize, CompressorStats, MasterTolerance); } else { // Prepare to compress UE_LOG(LogAnimationCompression, Log, TEXT("Compressing %s (%s)\n\tSkeleton: %s\n\tOriginal Size: %i MaxDiff: %f"), - *AnimSeq->GetName(), - *AnimSeq->GetFullName(), + *CompressibleAnimData.Name, + *CompressibleAnimData.FullName, Skeleton ? *Skeleton->GetFName().ToString() : TEXT("NULL - Not all compression techniques can be used!"), OriginalSize, TrueOriginalErrorStats.MaxError); UE_LOG(LogAnimationCompression, Log, TEXT("Original Key Encoding: %s\n\tOriginal Rotation Format: %s\n\tOriginal Translation Format: %s\n\tNumFrames: %i\n\tSequenceLength: %f (%2.1f fps)"), - *GetAnimationKeyFormatString(static_cast(OriginalKeyEncodingFormat)), - *FAnimationUtils::GetAnimationCompressionFormatString(static_cast(OriginalRotationFormat)), - *FAnimationUtils::GetAnimationCompressionFormatString(static_cast(OriginalTranslationFormat)), - AnimSeq->GetCompressedNumberOfFrames(), - AnimSeq->SequenceLength, - (AnimSeq->GetCompressedNumberOfFrames() > 1) ? (AnimSeq->GetCompressedNumberOfFrames()-1) / AnimSeq->SequenceLength : DEFAULT_SAMPLERATE); + *OriginalKeyEncodingFormat, + *OriginalRotationFormat, + *OriginalTranslationFormat, + OutCompressedData.CompressedNumberOfFrames, + CompressibleAnimData.SequenceLength, + (OutCompressedData.CompressedNumberOfFrames > 1) ? (OutCompressedData.CompressedNumberOfFrames -1) / CompressibleAnimData.SequenceLength : DEFAULT_SAMPLERATE); if (bFirstRecompressUsingCurrentOrDefault) { UE_LOG(LogAnimationCompression, Log, TEXT("Recompressed using current/default\n\tRecompress Size: %i MaxDiff: %f\n\tRecompress Scheme: %s"), AfterOriginalRecompression, OriginalErrorStats.MaxError, - AnimSeq->CompressionScheme ? *AnimSeq->CompressionScheme->GetClass()->GetName() : TEXT("NULL")); + OutCompressedData.CompressionScheme ? *OutCompressedData.CompressionScheme->GetClass()->GetName() : TEXT("NULL")); } // Progressive Algorithm @@ -1379,14 +1185,11 @@ void FAnimationUtils::CompressAnimSequenceExplicit( if( NewErrorStats.MaxError >= MasterTolerance ) { UE_LOG(LogAnimationCompression, Log, TEXT("\tStandard bitwise compressor too aggressive, lower default settings.")); - - AnimationErrorStats TestErrorStats; - FAnimationUtils::ComputeCompressionError(AnimSeq, BoneData, TestErrorStats); } else { // First, start by finding most downsampling factor. - if( AnimSeq->GetRawNumberOfFrames() >= PerTrackCompressor->MinKeysForResampling ) + if (CompressibleAnimData.NumFrames >= PerTrackCompressor->MinKeysForResampling) { PerTrackCompressor->bResampleAnimation = true; @@ -1425,7 +1228,7 @@ void FAnimationUtils::CompressAnimSequenceExplicit( } // Now do Linear Key Removal - if (AnimSeq->GetRawNumberOfFrames() > 1) + if (CompressibleAnimData.NumFrames > 1) { PerTrackCompressor->bActuallyFilterLinearKeys = true; PerTrackCompressor->bRetarget = true; @@ -1534,7 +1337,7 @@ void FAnimationUtils::CompressAnimSequenceExplicit( // this compressor has a minimum number of frames requirement. So no need to go there if we don't meet that... { UAnimCompress_RemoveEverySecondKey* RemoveEveryOtherKeyCompressor = NewObject(); - if( AnimSeq->GetRawNumberOfFrames() > RemoveEveryOtherKeyCompressor->MinKeys ) + if(CompressibleAnimData.NumFrames > RemoveEveryOtherKeyCompressor->MinKeys ) { RemoveEveryOtherKeyCompressor->bStartAtSecondKey = false; { @@ -1586,7 +1389,7 @@ void FAnimationUtils::CompressAnimSequenceExplicit( } // construct the proposed compressor - if( AnimSeq->GetRawNumberOfFrames() > 1 ) + if(CompressibleAnimData.NumFrames > 1 ) { UAnimCompress_RemoveLinearKeys* LinearKeyRemover = NewObject(); { @@ -1648,7 +1451,7 @@ void FAnimationUtils::CompressAnimSequenceExplicit( UAnimCompress_PerTrackCompression* PerTrackCompressor = NewObject(); PerTrackCompressor->bUseAdaptiveError = true; - if ( AnimSeq->GetRawNumberOfFrames() > 1 ) + if (CompressibleAnimData.NumFrames > 1 ) { PerTrackCompressor->bActuallyFilterLinearKeys = true; PerTrackCompressor->bRetarget = true; @@ -1681,7 +1484,7 @@ void FAnimationUtils::CompressAnimSequenceExplicit( PerTrackCompressor->bUseAdaptiveError = true; // Try the decimation algorithms - if (AnimSeq->GetRawNumberOfFrames() >= PerTrackCompressor->MinKeysForResampling) + if (CompressibleAnimData.NumFrames >= PerTrackCompressor->MinKeysForResampling) { PerTrackCompressor->bActuallyFilterLinearKeys = false; PerTrackCompressor->bRetarget = false; @@ -1726,7 +1529,7 @@ void FAnimationUtils::CompressAnimSequenceExplicit( { // Try the decimation algorithms - if (AnimSeq->GetRawNumberOfFrames() >= 3) + if (CompressibleAnimData.NumFrames >= 3) { UAnimCompress_PerTrackCompression* NewPerTrackCompressor = NewObject(); @@ -1768,7 +1571,7 @@ void FAnimationUtils::CompressAnimSequenceExplicit( } WaitForAnimCompressionJobs(AnimCompressionTask_CompletionEvents); - UpdateAnimCompressionFromAsyncJobs(AnimSeq, AnimCompressionTask_CompletionEvents, AnimCompressionJobContexes, OriginalSize, CompressorStats, MasterTolerance); + UpdateAnimCompressionFromAsyncJobs(OutCompressedData, AnimCompressionTask_CompletionEvents, AnimCompressionJobContexes, OriginalSize, CompressorStats, MasterTolerance); } // Increase winning compressor. @@ -1783,7 +1586,7 @@ void FAnimationUtils::CompressAnimSequenceExplicit( check(CompressorStats.WinningCompressorSavings == SizeDecrease); UE_LOG(LogAnimationCompression, Log, TEXT(" Recompressing(%s) with compressor('%s') saved %i bytes (%i -> %i -> %i) (max diff=%f)\n"), - *AnimSeq->GetName(), + *CompressibleAnimData.Name, *CompressorStats.WinningCompressorName, SizeDecrease, OriginalSize, AfterOriginalRecompression,CompressorStats. CurrentSize, @@ -1792,24 +1595,24 @@ void FAnimationUtils::CompressAnimSequenceExplicit( else { UE_LOG(LogAnimationCompression, Log, TEXT(" No compressor suitable! Recompressing(%s) with original/default compressor(%s) saved %i bytes (%i -> %i -> %i) (max diff=%f)\n"), - *AnimSeq->GetName(), - *AnimSeq->CompressionScheme->GetName(), + *CompressibleAnimData.Name, + *OutCompressedData.CompressionScheme->GetName(), SizeDecrease, OriginalSize, AfterOriginalRecompression, CompressorStats.CurrentSize, CompressorStats.WinningCompressorError); UE_LOG(LogAnimation, Warning, TEXT(" CompressedTrackOffsets(%d) CompressedByteStream(%d) CompressedScaleOffsets(%d) CompressedSegments(%d)"), - AnimSeq->CompressedTrackOffsets.Num(), - AnimSeq->CompressedByteStream.Num(), - AnimSeq->CompressedScaleOffsets.GetMemorySize(), - AnimSeq->CompressedSegments.Num()); + OutCompressedData.CompressedTrackOffsets.Num(), + OutCompressedData.CompressedByteStream.Num(), + OutCompressedData.CompressedScaleOffsets.GetMemorySize(), + 0); TotalNoWinnerRounds++; } } // Make sure we got that right. - check(CompressorStats.CurrentSize == AnimSeq->GetApproxCompressedSize()); + check(CompressorStats.CurrentSize == OutCompressedData.GetApproxBoneCompressedSize()); TotalSizeNow += CompressorStats.CurrentSize; CompressorStats.PctSaving = TotalSizeBefore > 0 ? 100.f - (100.f * float(TotalSizeNow) / float(TotalSizeBefore)) : 0.f; @@ -1884,32 +1687,17 @@ void FAnimationUtils::CompressAnimSequenceExplicit( // Do not recompress - Still take into account size for stats. else { - TotalSizeNow += AnimSeq->GetApproxCompressedSize(); + TotalSizeNow += OutCompressedData.GetApproxBoneCompressedSize(); } } else { // this can happen if the animation only contains curve - i.e. blendshape curves - UE_LOG(LogAnimationCompression, Log, TEXT("Compression Requested for Empty Animation %s"), *AnimSeq->GetName() ); + UE_LOG(LogAnimationCompression, Log, TEXT("Compression Requested for Empty Animation %s"), *CompressibleAnimData.Name ); } #endif // WITH_EDITORONLY_DATA } - -void FAnimationUtils::TestForMissingMeshes(UAnimSequence* AnimSeq) -{ - if (FPlatformProperties::HasEditorOnlyData()) - { - check(AnimSeq != NULL); - - USkeleton* Skeleton = AnimSeq->GetSkeleton(); - check( Skeleton ); - - static int32 MissingSkeletonCount = 0; - static TArray MissingSkeletonArray; - } -} - static void GetBindPoseAtom(FTransform &OutBoneAtom, int32 BoneIndex, USkeleton *Skeleton) { OutBoneAtom = Skeleton->GetRefLocalPoses()[BoneIndex]; @@ -2026,7 +1814,7 @@ FString FAnimationUtils::GetAnimationKeyFormatString(AnimationKeyFormat InFormat * @param TrackHeights [OUT] The computed track heights * */ -void FAnimationUtils::CalculateTrackHeights(UAnimSequence* AnimSeq, const TArray& BoneData, int32 NumTracks, TArray& TrackHeights) +void FAnimationUtils::CalculateTrackHeights(const FCompressibleAnimData& CompressibleAnimData, int32 NumTracks, TArray& TrackHeights) { TrackHeights.Empty(); TrackHeights.AddUninitialized(NumTracks); @@ -2035,8 +1823,7 @@ void FAnimationUtils::CalculateTrackHeights(UAnimSequence* AnimSeq, const TArray TrackHeights[TrackIndex] = 0; } - USkeleton* Skeleton = AnimSeq->GetSkeleton(); - check(Skeleton); + const TArray& BoneData = CompressibleAnimData.BoneData; // Populate the bone 'height' table (distance from closest end effector, with 0 indicating an end effector) // setup the raw bone transformation and find all end effectors @@ -2051,7 +1838,7 @@ void FAnimationUtils::CalculateTrackHeights(UAnimSequence* AnimSeq, const TArray for (int32 FamilyIndex = 0; FamilyIndex < EffectorBoneData.BonesToRoot.Num(); ++FamilyIndex) { const int32 NextParentBoneIndex = EffectorBoneData.BonesToRoot[FamilyIndex]; - const int32 NextParentTrackIndex = Skeleton->GetAnimationTrackIndex(NextParentBoneIndex, AnimSeq, true); + const int32 NextParentTrackIndex = GetAnimTrackIndexForSkeletonBone(NextParentBoneIndex, CompressibleAnimData.TrackToSkeletonMapTable); if (NextParentTrackIndex != INDEX_NONE) { const int32 CurHeight = TrackHeights[NextParentTrackIndex]; @@ -2072,9 +1859,9 @@ void FAnimationUtils::CalculateTrackHeights(UAnimSequence* AnimSeq, const TArray * * @return true if the keys are uniformly spaced (or one of the trivial conditions is detected). false if any key spacing is greater than 1e-4 off. */ -bool FAnimationUtils::HasUniformKeySpacing(UAnimSequence* AnimSeq, const TArray& Times) +bool FAnimationUtils::HasUniformKeySpacing(int32 NumFrames, const TArray& Times) { - if ((Times.Num() <= 2) || (Times.Num() == AnimSeq->GetRawNumberOfFrames())) + if ((Times.Num() <= 2) || (Times.Num() == NumFrames)) { return true; } @@ -2097,19 +1884,18 @@ bool FAnimationUtils::HasUniformKeySpacing(UAnimSequence* AnimSeq, const TArray< * Perturbs the bone(s) associated with each track in turn, measuring the maximum error introduced in end effectors as a result */ void FAnimationUtils::TallyErrorsFromPerturbation( - const UAnimSequence* AnimSeq, + const FCompressibleAnimData& CompressibleAnimData, int32 NumTracks, - const TArray& BoneData, const FVector& PositionNudge, const FQuat& RotationNudge, const FVector& ScaleNudge, TArray& InducedErrors) { - const float TimeStep = (float)AnimSeq->SequenceLength / (float)AnimSeq->GetRawNumberOfFrames(); - const int32 NumBones = BoneData.Num(); + const float TimeStep = (float)CompressibleAnimData.SequenceLength / CompressibleAnimData.NumFrames; + const int32 NumBones = CompressibleAnimData.BoneData.Num(); - USkeleton* Skeleton = AnimSeq->GetSkeleton(); + USkeleton* Skeleton = CompressibleAnimData.Skeleton; check ( Skeleton ); const TArray& RefPose = Skeleton->GetRefLocalPoses(); @@ -2149,12 +1935,12 @@ void FAnimationUtils::TallyErrorsFromPerturbation( float MaxErrorS_DueToS = 0.0f; // for each whole increment of time (frame stepping) - for (float Time = 0.0f; Time < AnimSeq->SequenceLength; Time += TimeStep) + for (float Time = 0.0f; Time < CompressibleAnimData.SequenceLength; Time += TimeStep) { // get the raw and compressed atom for each bone for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) { - const int32 TrackIndex = Skeleton->GetAnimationTrackIndex(BoneIndex, AnimSeq, true); + const int32 TrackIndex = GetAnimTrackIndexForSkeletonBone(BoneIndex, CompressibleAnimData.TrackToSkeletonMapTable); if (TrackIndex == INDEX_NONE) { @@ -2166,7 +1952,7 @@ void FAnimationUtils::TallyErrorsFromPerturbation( } else { - AnimSeq->GetBoneTransform(RawAtoms[BoneIndex], TrackIndex, Time, true); + ExtractTransformFromTrack(Time, CompressibleAnimData.NumFrames, CompressibleAnimData.SequenceLength, CompressibleAnimData.RawAnimationData[TrackIndex], CompressibleAnimData.Interpolation, RawAtoms[BoneIndex]); NewAtomsT[BoneIndex] = RawAtoms[BoneIndex]; NewAtomsR[BoneIndex] = RawAtoms[BoneIndex]; @@ -2208,7 +1994,7 @@ void FAnimationUtils::TallyErrorsFromPerturbation( } // Only look at the error that occurs in end effectors - if (BoneData[BoneIndex].IsEndEffector()) + if (CompressibleAnimData.BoneData[BoneIndex].IsEndEffector()) { MaxErrorT_DueToT = FMath::Max(MaxErrorT_DueToT, (RawTransforms[BoneIndex].GetLocation() - NewTransformsT[BoneIndex].GetLocation()).Size()); MaxErrorT_DueToR = FMath::Max(MaxErrorT_DueToR, (RawTransforms[BoneIndex].GetLocation() - NewTransformsR[BoneIndex].GetLocation()).Size()); @@ -2296,13 +2082,113 @@ UAnimCurveCompressionSettings* FAnimationUtils::GetDefaultAnimationCurveCompress return DefaultCurveCompressionSettings; } +void FAnimationUtils::ExtractTransformFromTrack(float Time, int32 NumFrames, float SequenceLength, const struct FRawAnimSequenceTrack& RawTrack, EAnimInterpolationType Interpolation, FTransform &OutAtom) +{ + // Bail out (with rather wacky data) if data is empty for some reason. + if (RawTrack.PosKeys.Num() == 0 || RawTrack.RotKeys.Num() == 0) + { + OutAtom.SetIdentity(); + return; + } + + int32 KeyIndex1, KeyIndex2; + float Alpha; + FAnimationRuntime::GetKeyIndicesFromTime(KeyIndex1, KeyIndex2, Alpha, Time, NumFrames, SequenceLength); + // @Todo fix me: this change is not good, it has lots of branches. But we'd like to save memory for not saving scale if no scale change exists + const bool bHasScaleKey = (RawTrack.ScaleKeys.Num() > 0); + static const FVector DefaultScale3D = FVector(1.f); + + if (Interpolation == EAnimInterpolationType::Step) + { + Alpha = 0.f; + } + + if (Alpha <= 0.f) + { + const int32 PosKeyIndex1 = FMath::Min(KeyIndex1, RawTrack.PosKeys.Num() - 1); + const int32 RotKeyIndex1 = FMath::Min(KeyIndex1, RawTrack.RotKeys.Num() - 1); + if (bHasScaleKey) + { + const int32 ScaleKeyIndex1 = FMath::Min(KeyIndex1, RawTrack.ScaleKeys.Num() - 1); + OutAtom = FTransform(RawTrack.RotKeys[RotKeyIndex1], RawTrack.PosKeys[PosKeyIndex1], RawTrack.ScaleKeys[ScaleKeyIndex1]); + } + else + { + OutAtom = FTransform(RawTrack.RotKeys[RotKeyIndex1], RawTrack.PosKeys[PosKeyIndex1], DefaultScale3D); + } + return; + } + else if (Alpha >= 1.f) + { + const int32 PosKeyIndex2 = FMath::Min(KeyIndex2, RawTrack.PosKeys.Num() - 1); + const int32 RotKeyIndex2 = FMath::Min(KeyIndex2, RawTrack.RotKeys.Num() - 1); + if (bHasScaleKey) + { + const int32 ScaleKeyIndex2 = FMath::Min(KeyIndex2, RawTrack.ScaleKeys.Num() - 1); + OutAtom = FTransform(RawTrack.RotKeys[RotKeyIndex2], RawTrack.PosKeys[PosKeyIndex2], RawTrack.ScaleKeys[ScaleKeyIndex2]); + } + else + { + OutAtom = FTransform(RawTrack.RotKeys[RotKeyIndex2], RawTrack.PosKeys[PosKeyIndex2], DefaultScale3D); + } + return; + } + + const int32 PosKeyIndex1 = FMath::Min(KeyIndex1, RawTrack.PosKeys.Num() - 1); + const int32 RotKeyIndex1 = FMath::Min(KeyIndex1, RawTrack.RotKeys.Num() - 1); + + const int32 PosKeyIndex2 = FMath::Min(KeyIndex2, RawTrack.PosKeys.Num() - 1); + const int32 RotKeyIndex2 = FMath::Min(KeyIndex2, RawTrack.RotKeys.Num() - 1); + + FTransform KeyAtom1, KeyAtom2; + + if (bHasScaleKey) + { + const int32 ScaleKeyIndex1 = FMath::Min(KeyIndex1, RawTrack.ScaleKeys.Num() - 1); + const int32 ScaleKeyIndex2 = FMath::Min(KeyIndex2, RawTrack.ScaleKeys.Num() - 1); + + KeyAtom1 = FTransform(RawTrack.RotKeys[RotKeyIndex1], RawTrack.PosKeys[PosKeyIndex1], RawTrack.ScaleKeys[ScaleKeyIndex1]); + KeyAtom2 = FTransform(RawTrack.RotKeys[RotKeyIndex2], RawTrack.PosKeys[PosKeyIndex2], RawTrack.ScaleKeys[ScaleKeyIndex2]); + } + else + { + KeyAtom1 = FTransform(RawTrack.RotKeys[RotKeyIndex1], RawTrack.PosKeys[PosKeyIndex1], DefaultScale3D); + KeyAtom2 = FTransform(RawTrack.RotKeys[RotKeyIndex2], RawTrack.PosKeys[PosKeyIndex2], DefaultScale3D); + } + + // UE_LOG(LogAnimation, Log, TEXT(" * * * Position. PosKeyIndex1: %3d, PosKeyIndex2: %3d, Alpha: %f"), PosKeyIndex1, PosKeyIndex2, Alpha); + // UE_LOG(LogAnimation, Log, TEXT(" * * * Rotation. RotKeyIndex1: %3d, RotKeyIndex2: %3d, Alpha: %f"), RotKeyIndex1, RotKeyIndex2, Alpha); + + // Ensure rotations are normalized (Added for Jira UE-53971) + KeyAtom1.NormalizeRotation(); + KeyAtom2.NormalizeRotation(); + + OutAtom.Blend(KeyAtom1, KeyAtom2, Alpha); + OutAtom.NormalizeRotation(); +} + #if WITH_EDITOR -bool FAnimationUtils::CompressAnimCurves(UAnimSequence& AnimSeq) +void FAnimationUtils::ExtractTransformFromCompressionData(const FCompressibleAnimData& CompressibleAnimData, FCompressibleAnimDataResult& CompressedAnimData, float Time, int32 TrackIndex, bool bUseRawData, FTransform& OutBoneTransform) +{ + // If the caller didn't request that raw animation data be used . . . + if (!bUseRawData && CompressedAnimData.IsCompressedDataValid()) + { + const FUECompressedAnimData CompressedDataWrapper(CompressedAnimData); + FAnimSequenceDecompressionContext DecompContext(CompressibleAnimData, CompressedDataWrapper); + DecompContext.Seek(Time); + AnimationFormat_GetBoneAtom(OutBoneTransform, DecompContext, TrackIndex); + return; + } + + FAnimationUtils::ExtractTransformFromTrack(Time, CompressibleAnimData.NumFrames, CompressibleAnimData.SequenceLength, CompressibleAnimData.RawAnimationData[TrackIndex], CompressibleAnimData.Interpolation, OutBoneTransform); +} + +bool FAnimationUtils::CompressAnimCurves(FCompressibleAnimData& AnimSeq, FCompressedAnimSequence& Target) { // Clear any previous data we might have even if we end up failing to compress - AnimSeq.CompressedCurveByteStream.Empty(); - AnimSeq.CurveCompressionCodec = nullptr; + Target.CompressedCurveByteStream.Empty(); + Target.CurveCompressionCodec = nullptr; if (AnimSeq.CurveCompressionSettings == nullptr || !AnimSeq.CurveCompressionSettings->AreSettingsValid()) { @@ -2310,6 +2196,6 @@ bool FAnimationUtils::CompressAnimCurves(UAnimSequence& AnimSeq) } check(AnimSeq.CurveCompressionSettings->AreSettingsValid()); - return AnimSeq.CurveCompressionSettings->Compress(AnimSeq); + return AnimSeq.CurveCompressionSettings->Compress(AnimSeq, Target); } #endif diff --git a/Engine/Source/Runtime/Engine/Private/Animation/Skeleton.cpp b/Engine/Source/Runtime/Engine/Private/Animation/Skeleton.cpp index 6809f5288f84..aedc34d0d41f 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/Skeleton.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/Skeleton.cpp @@ -744,19 +744,14 @@ FName USkeleton::GetRetargetSourceForMesh(USkeletalMesh* InMesh) const #endif -int32 USkeleton::GetAnimationTrackIndex(const int32 InSkeletonBoneIndex, const UAnimSequence* InAnimSeq, const bool bUseRawData) +int32 USkeleton::GetRawAnimationTrackIndex(const int32 InSkeletonBoneIndex, const UAnimSequence* InAnimSeq) { - const TArray& TrackToSkelMap = bUseRawData ? InAnimSeq->GetRawTrackToSkeletonMapTable() : InAnimSeq->GetCompressedTrackToSkeletonMapTable(); if( InSkeletonBoneIndex != INDEX_NONE ) { - for (int32 TrackIndex = 0; TrackIndexGetRawTrackToSkeletonMapTable().IndexOfByPredicate([&](const FTrackToSkeletonMap& TrackToSkel) { - const FTrackToSkeletonMap& TrackToSkeleton = TrackToSkelMap[TrackIndex]; - if( TrackToSkeleton.BoneTreeIndex == InSkeletonBoneIndex ) - { - return TrackIndex; - } - } + return TrackToSkel.BoneTreeIndex == InSkeletonBoneIndex; + }); } return INDEX_NONE; diff --git a/Engine/Source/Runtime/Engine/Private/Animation/SkinWeightProfile.cpp b/Engine/Source/Runtime/Engine/Private/Animation/SkinWeightProfile.cpp index d19a254fcdea..8bb869cbfac5 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/SkinWeightProfile.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/SkinWeightProfile.cpp @@ -302,14 +302,17 @@ void FSkinWeightProfilesData::ApplyOverrideProfile(FSkinWeightVertexBuffer* Over const bool bExtraWeights = BaseBuffer->HasExtraBoneInfluences(); OverrideBuffer->SetHasExtraBoneInfluences(bExtraWeights); - const FRuntimeSkinWeightProfileData& Profile = OverrideData.FindChecked(ProfileName); - if (bExtraWeights) + const FRuntimeSkinWeightProfileData* ProfilePtr = OverrideData.Find(ProfileName); + if (ProfilePtr) { - Profile.ApplyOverrides(OverrideBuffer, BaseBuffer); - } - else - { - Profile.ApplyOverrides(OverrideBuffer, BaseBuffer); + if (bExtraWeights) + { + ProfilePtr->ApplyOverrides(OverrideBuffer, BaseBuffer); + } + else + { + ProfilePtr->ApplyOverrides(OverrideBuffer, BaseBuffer); + } } } diff --git a/Engine/Source/Runtime/Engine/Private/AnimationAsset.cpp b/Engine/Source/Runtime/Engine/Private/AnimationAsset.cpp index 1cc64cc7bed8..3f004e290a31 100644 --- a/Engine/Source/Runtime/Engine/Private/AnimationAsset.cpp +++ b/Engine/Source/Runtime/Engine/Private/AnimationAsset.cpp @@ -153,7 +153,7 @@ void FAnimGroupInstance::Prepare(const FAnimGroupInstance* PreviousGroup) bCanUseMarkerSync = ValidMarkers.Num() > 0; - ValidMarkers.Sort(); + ValidMarkers.Sort(FNameLexicalLess()); if (!PreviousGroup || (ValidMarkers != PreviousGroup->ValidMarkers)) { diff --git a/Engine/Source/Runtime/Engine/Private/AssetManager.cpp b/Engine/Source/Runtime/Engine/Private/AssetManager.cpp index 2369e58de5e5..b9e8ea104d7a 100644 --- a/Engine/Source/Runtime/Engine/Private/AssetManager.cpp +++ b/Engine/Source/Runtime/Engine/Private/AssetManager.cpp @@ -166,6 +166,8 @@ void UAssetManager::PostInitProperties() } LoadRedirectorMaps(); + + StreamableManager.SetManagerName(FString::Printf(TEXT("%s.StreamableManager"), *GetPathName())); } } @@ -1153,7 +1155,7 @@ TSharedPtr UAssetManager::ChangeBundleStateForPrimaryAssets(c NewBundleState.AddUnique(AddBundle); } - NewBundleState.Sort(); + NewBundleState.Sort(FNameLexicalLess()); // If the pending state is valid, check if it is different if (NameData->PendingState.IsValid()) @@ -2377,7 +2379,7 @@ void UAssetManager::DumpAssetTypeSummary() Manager.GetPrimaryAssetTypeInfoList(TypeInfos); - TypeInfos.Sort([](const FPrimaryAssetTypeInfo& LHS, const FPrimaryAssetTypeInfo& RHS) { return LHS.PrimaryAssetType < RHS.PrimaryAssetType; }); + TypeInfos.Sort([](const FPrimaryAssetTypeInfo& LHS, const FPrimaryAssetTypeInfo& RHS) { return LHS.PrimaryAssetType.LexicalLess(RHS.PrimaryAssetType); }); UE_LOG(LogAssetManager, Log, TEXT("=========== Asset Manager Type Summary ===========")); @@ -2405,7 +2407,7 @@ void UAssetManager::DumpLoadedAssetState() Manager.GetPrimaryAssetTypeInfoList(TypeInfos); - TypeInfos.Sort([](const FPrimaryAssetTypeInfo& LHS, const FPrimaryAssetTypeInfo& RHS) { return LHS.PrimaryAssetType < RHS.PrimaryAssetType; }); + TypeInfos.Sort([](const FPrimaryAssetTypeInfo& LHS, const FPrimaryAssetTypeInfo& RHS) { return LHS.PrimaryAssetType.LexicalLess(RHS.PrimaryAssetType); }); UE_LOG(LogAssetManager, Log, TEXT("=========== Asset Manager Loaded Asset State ===========")); @@ -2451,7 +2453,7 @@ void UAssetManager::DumpLoadedAssetState() { UE_LOG(LogAssetManager, Log, TEXT(" Type %s:"), *TypeInfo.PrimaryAssetType.ToString()); - LoadedInfos.Sort([](const FLoadedInfo& LHS, const FLoadedInfo& RHS) { return LHS.AssetName < RHS.AssetName; }); + LoadedInfos.Sort([](const FLoadedInfo& LHS, const FLoadedInfo& RHS) { return LHS.AssetName.LexicalLess(RHS.AssetName); }); for (FLoadedInfo& LoadedInfo : LoadedInfos) { diff --git a/Engine/Source/Runtime/Engine/Private/Audio.cpp b/Engine/Source/Runtime/Engine/Private/Audio.cpp index c4a2142c9980..ec631a73838e 100644 --- a/Engine/Source/Runtime/Engine/Private/Audio.cpp +++ b/Engine/Source/Runtime/Engine/Private/Audio.cpp @@ -816,7 +816,6 @@ FWaveInstance::FWaveInstance(const UPTRINT InWaveInstanceHash, FActiveSound& InA , UserIndex(0) { TypeHash = ++TypeHashCounter; - ActiveSound->AddWaveInstance(WaveInstanceHash, *this); } /** diff --git a/Engine/Source/Runtime/Engine/Private/AudioDebug.cpp b/Engine/Source/Runtime/Engine/Private/AudioDebug.cpp index c70abf7acf1a..8d1fc7142466 100644 --- a/Engine/Source/Runtime/Engine/Private/AudioDebug.cpp +++ b/Engine/Source/Runtime/Engine/Private/AudioDebug.cpp @@ -731,7 +731,7 @@ int32 FAudioDebugger::RenderStatSounds(UWorld* World, FViewport* Viewport, FCanv } else if (AudioStats.DisplayFlags & static_cast(FAudioStats::EDisplayFlags::Sort_Class)) { - AudioStats.StatSoundInfos.Sort([](const FAudioStats::FStatSoundInfo& A, const FAudioStats::FStatSoundInfo& B) { return A.SoundClassName < B.SoundClassName; }); + AudioStats.StatSoundInfos.Sort([](const FAudioStats::FStatSoundInfo& A, const FAudioStats::FStatSoundInfo& B) { return A.SoundClassName.LexicalLess(B.SoundClassName); }); SortingName = TEXT("class"); } else if (AudioStats.DisplayFlags & static_cast(FAudioStats::EDisplayFlags::Sort_WavesNum)) diff --git a/Engine/Source/Runtime/Engine/Private/AudioDevice.cpp b/Engine/Source/Runtime/Engine/Private/AudioDevice.cpp index 729a154dec31..47abd52b020b 100644 --- a/Engine/Source/Runtime/Engine/Private/AudioDevice.cpp +++ b/Engine/Source/Runtime/Engine/Private/AudioDevice.cpp @@ -4264,6 +4264,8 @@ bool FAudioDevice::RemoveVirtualLoop(FActiveSound& InActiveSound) if (FAudioVirtualLoop* VirtualLoop = VirtualLoops.Find(&InActiveSound)) { + check(InActiveSound.bIsStopping); + const uint64 ComponentID = InActiveSound.GetAudioComponentID(); if (ComponentID > 0) { @@ -4394,7 +4396,11 @@ void FAudioDevice::AddSoundToStop(FActiveSound* SoundToStop) AudioComponentIDToActiveSoundMap.Remove(AudioComponentID); } - if (!VirtualLoops.Contains(SoundToStop)) + if (VirtualLoops.Contains(SoundToStop)) + { + SoundToStop->bIsStopping = true; + } + else { ConcurrencyManager.RemoveActiveSound(*SoundToStop); } @@ -5737,20 +5743,12 @@ void FAudioDevice::UpdateVirtualLoops() { VirtualLoopsToRetrigger.Add(VirtualLoop); } - - // If set to fade out and set to play when silent, add to pending stop list. - if (ActiveSound.bFadingOut) - { - AddSoundToStop(&ActiveSound); - } } for (FAudioVirtualLoop& RetriggerLoop : VirtualLoopsToRetrigger) { RetriggerVirtualLoop(RetriggerLoop); } - - VirtualLoopsToRetrigger.Reset(); } // if !FAudioVirtualLoop::IsEnabled(), attempt to realize/re-trigger diff --git a/Engine/Source/Runtime/Engine/Private/Blueprint.cpp b/Engine/Source/Runtime/Engine/Private/Blueprint.cpp index ac08c0289d59..7bcd57c7b5d6 100644 --- a/Engine/Source/Runtime/Engine/Private/Blueprint.cpp +++ b/Engine/Source/Runtime/Engine/Private/Blueprint.cpp @@ -24,6 +24,7 @@ #include "Engine/SimpleConstructionScript.h" #include "Engine/SCS_Node.h" #include "Kismet2/BlueprintEditorUtils.h" +#include "Kismet2/ComponentEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "Kismet2/CompilerResultsLog.h" #include "Kismet2/StructureEditorUtils.h" @@ -70,50 +71,21 @@ static void ConformNativeComponents(UBlueprint* Blueprint) // native super-class) TInlineComponentArray NewNativeComponents; NativeCDO->GetComponents(NewNativeComponents); - - // utility lambda for finding named components in a supplied list - auto FindNamedComponentLambda = [](FName const ComponentName, TInlineComponentArray const& ComponentList)->UActorComponent* - { - UActorComponent* FoundComponent = nullptr; - for (UActorComponent* Component : ComponentList) - { - if (Component->GetFName() == ComponentName) - { - FoundComponent = Component; - break; - } - } - return FoundComponent; - }; - - // utility lambda for finding matching components in the NewNativeComponents list - auto FindNativeComponentLambda = [&NewNativeComponents, &FindNamedComponentLambda](UActorComponent* BlueprintComponent)->UActorComponent* - { - UActorComponent* MatchingComponent = nullptr; - if (BlueprintComponent != nullptr) - { - FName const ComponentName = BlueprintComponent->GetFName(); - MatchingComponent = FindNamedComponentLambda(ComponentName, NewNativeComponents); - } - return MatchingComponent; - }; - + // loop through all components that this blueprint thinks come from its // native super-class (last time it was saved) for (UActorComponent* Component : OldNativeComponents) { - // if we found this component also listed for the native class - if (UActorComponent* NativeComponent = FindNativeComponentLambda(Component)) + if (UActorComponent* NativeComponent = FComponentEditorUtils::FindMatchingComponent(Component, NewNativeComponents)) { - USceneComponent* BlueprintSceneComponent = Cast(Component); - if (BlueprintSceneComponent == nullptr) + USceneComponent* SceneComponent = Cast(Component); + if (SceneComponent == nullptr) { // if this isn't a scene-component, then we don't care // (we're looking to fixup scene-component parents) continue; } - UActorComponent* OldNativeParent = FindNativeComponentLambda(BlueprintSceneComponent->GetAttachParent()); - + USceneComponent* OldNativeParent = Cast(FComponentEditorUtils::FindMatchingComponent(SceneComponent->GetAttachParent(), NewNativeComponents)); USceneComponent* NativeSceneComponent = CastChecked(NativeComponent); // if this native component has since been reparented, we need // to make sure that this blueprint reflects that change @@ -122,20 +94,21 @@ static void ConformNativeComponents(UBlueprint* Blueprint) USceneComponent* NewParent = nullptr; if (NativeSceneComponent->GetAttachParent() != nullptr) { - NewParent = CastChecked(FindNamedComponentLambda(NativeSceneComponent->GetAttachParent()->GetFName(), OldNativeComponents)); + NewParent = CastChecked(FComponentEditorUtils::FindMatchingComponent(NativeSceneComponent->GetAttachParent(), OldNativeComponents)); } - BlueprintSceneComponent->SetupAttachment(NewParent, BlueprintSceneComponent->GetAttachSocketName()); + SceneComponent->SetupAttachment(NewParent, SceneComponent->GetAttachSocketName()); } } - else // the component has been removed from the native class + else { - // @TODO: I think we already handle removed native components elsewhere, so maybe we should error here? -// BlueprintCDO->RemoveOwnedComponent(Component); -// -// USimpleConstructionScript* BlueprintSCS = Blueprint->SimpleConstructionScript; -// USCS_Node* ComponentNode = BlueprintSCS->CreateNode(Component, Component->GetFName()); -// -// BlueprintSCS->AddNode(ComponentNode); + // the component has been removed from the native class + // @TODO: I think we already handle removed native components elsewhere, so maybe we should error here? + // BlueprintCDO->RemoveOwnedComponent(Component); + // + // USimpleConstructionScript* BlueprintSCS = Blueprint->SimpleConstructionScript; + // USCS_Node* ComponentNode = BlueprintSCS->CreateNode(Component, Component->GetFName()); + // + // BlueprintSCS->AddNode(ComponentNode); } } } @@ -1937,6 +1910,11 @@ EDataValidationResult UBlueprint::IsDataValid(TArray& ValidationErrors) return GeneratedClass ? GeneratedClass->GetDefaultObject()->IsDataValid(ValidationErrors) : EDataValidationResult::Invalid; } +bool UBlueprint::FindDiffs(const UBlueprint* OtherBlueprint, FDiffResults& Results) const +{ + return false; +} + FName UBlueprint::GetFunctionNameFromClassByGuid(const UClass* InClass, const FGuid FunctionGuid) { return FBlueprintEditorUtils::GetFunctionNameFromClassByGuid(InClass, FunctionGuid); diff --git a/Engine/Source/Runtime/Engine/Private/BlueprintGeneratedClass.cpp b/Engine/Source/Runtime/Engine/Private/BlueprintGeneratedClass.cpp index a2e4ce10ac86..c2531bd43500 100644 --- a/Engine/Source/Runtime/Engine/Private/BlueprintGeneratedClass.cpp +++ b/Engine/Source/Runtime/Engine/Private/BlueprintGeneratedClass.cpp @@ -116,11 +116,6 @@ void UBlueprintGeneratedClass::PostLoad() } } - if (Package && Package->HasAnyPackageFlags(PKG_ForDiffing)) - { - ClassFlags |= CLASS_Deprecated; - } - #if UE_BLUEPRINT_EVENTGRAPH_FASTCALLS // Patch the fast calls (needed as we can't bump engine version to serialize it directly in UFunction right now) for (const FEventGraphFastCallPair& Pair : FastCallPairs_DEPRECATED) @@ -304,13 +299,14 @@ UObject* UBlueprintGeneratedClass::GetArchetypeForCDO() const } #endif //WITH_EDITOR -void UBlueprintGeneratedClass::SerializeDefaultObject(UObject* Object, FArchive& Ar) +void UBlueprintGeneratedClass::SerializeDefaultObject(UObject* Object, FStructuredArchive::FSlot Slot) { FScopeLock SerializeAndPostLoadLock(&SerializeAndPostLoadCritical); + FArchive& UnderlyingArchive = Slot.GetUnderlyingArchive(); - Super::SerializeDefaultObject(Object, Ar); + Super::SerializeDefaultObject(Object, Slot); - if (Ar.IsLoading() && !Ar.IsObjectReferenceCollector() && Object == ClassDefaultObject) + if (UnderlyingArchive.IsLoading() && !UnderlyingArchive.IsObjectReferenceCollector() && Object == ClassDefaultObject) { // On load, build the custom property list used in post-construct initialization logic. Note that in the editor, this will be refreshed during compile-on-load. // @TODO - Potentially make this serializable (or cooked data) to eliminate the slight load time cost we'll incur below to generate this list in a cooked build. For now, it's not serialized since the raw UProperty references cannot be saved out. @@ -684,7 +680,7 @@ void UBlueprintGeneratedClass::InitArrayPropertyFromCustomList(const UArrayPrope } } -bool UBlueprintGeneratedClass::IsFunctionImplementedInBlueprint(FName InFunctionName) const +bool UBlueprintGeneratedClass::IsFunctionImplementedInScript(FName InFunctionName) const { UFunction* Function = FindFunctionByName(InFunctionName); return Function && Function->GetOuter() && Function->GetOuter()->IsA(UBlueprintGeneratedClass::StaticClass()); @@ -1004,7 +1000,6 @@ void UBlueprintGeneratedClass::CreateTimelineComponent(AActor* Actor, const UTim { if (!Actor || !TimelineTemplate - || !TimelineTemplate->bValidatedAsWired || Actor->IsTemplate() || Actor->IsPendingKill()) { @@ -1132,7 +1127,7 @@ void UBlueprintGeneratedClass::CreateComponentsForActor(const UClass* ThisClass, for (UTimelineTemplate* TimelineTemplate : BPGC->Timelines) { // Not fatal if NULL, but shouldn't happen and ignored if not wired up in graph - if (TimelineTemplate && TimelineTemplate->bValidatedAsWired) + if (TimelineTemplate) { CreateTimelineComponent(Actor, TimelineTemplate); } @@ -1144,7 +1139,7 @@ void UBlueprintGeneratedClass::CreateComponentsForActor(const UClass* ThisClass, { const UTimelineTemplate* TimelineTemplate = Cast(MiscObj); // Not fatal if NULL, but shouldn't happen and ignored if not wired up in graph - if (TimelineTemplate && TimelineTemplate->bValidatedAsWired) + if (TimelineTemplate) { CreateTimelineComponent(Actor, TimelineTemplate); } @@ -1629,6 +1624,13 @@ void UBlueprintGeneratedClass::Serialize(FArchive& Ar) if (Ar.IsLoading() && 0 == (Ar.GetPortFlags() & PPF_Duplicate)) { CreatePersistentUberGraphFrame(ClassDefaultObject, true); + + UPackage* Package = GetOutermost(); + if (Package && Package->HasAnyPackageFlags(PKG_ForDiffing)) + { + // If this is a diff package, set class to deprecated. This happens here to make sure it gets hit in all load cases + ClassFlags |= CLASS_Deprecated; + } } } diff --git a/Engine/Source/Runtime/Engine/Private/Brush.cpp b/Engine/Source/Runtime/Engine/Private/Brush.cpp index b375304b62ec..95b20e44e3e7 100644 --- a/Engine/Source/Runtime/Engine/Private/Brush.cpp +++ b/Engine/Source/Runtime/Engine/Private/Brush.cpp @@ -157,8 +157,8 @@ void ABrush::SetIsTemporarilyHiddenInEditor( bool bIsHidden ) { Super::SetIsTemporarilyHiddenInEditor(bIsHidden); - auto* Level = GetLevel(); - auto* Model = Level ? Level->Model : nullptr; + ULevel* Level = GetLevel(); + UModel* Model = Level ? Level->Model : nullptr; if (Level && Model) { diff --git a/Engine/Source/Runtime/Engine/Private/Character.cpp b/Engine/Source/Runtime/Engine/Private/Character.cpp index 09c3ecb63918..1e3c096a6e75 100644 --- a/Engine/Source/Runtime/Engine/Private/Character.cpp +++ b/Engine/Source/Runtime/Engine/Private/Character.cpp @@ -376,7 +376,7 @@ void ACharacter::SetReplicateMovement(bool bInReplicateMovement) } } -bool ACharacter::CanCrouch() +bool ACharacter::CanCrouch() const { return !bIsCrouched && CharacterMovement && CharacterMovement->CanEverCrouch() && GetRootComponent() && !GetRootComponent()->IsSimulatingPhysics(); } diff --git a/Engine/Source/Runtime/Engine/Private/Collision/Collision.cpp b/Engine/Source/Runtime/Engine/Private/Collision/Collision.cpp index b732f4878a50..d52b2d7bd7c8 100644 --- a/Engine/Source/Runtime/Engine/Private/Collision/Collision.cpp +++ b/Engine/Source/Runtime/Engine/Private/Collision/Collision.cpp @@ -1028,7 +1028,7 @@ namespace CollisionResponseConsoleCommands // Display Data if (Results.Num() > 0) { - Results.Sort([](const FName& A, const FName& B) { return A < B; }); + Results.Sort(FNameLexicalLess()); for (FName& ResultName : Results) { UE_LOG(LogCollisionCommands, Log, TEXT("%s"), *ResultName.ToString()); diff --git a/Engine/Source/Runtime/Engine/Private/Collision/CollisionConversions.cpp b/Engine/Source/Runtime/Engine/Private/Collision/CollisionConversions.cpp index 46d60cb18640..bc765b0cc884 100644 --- a/Engine/Source/Runtime/Engine/Private/Collision/CollisionConversions.cpp +++ b/Engine/Source/Runtime/Engine/Private/Collision/CollisionConversions.cpp @@ -404,7 +404,7 @@ static bool ConvertOverlappedShapeToImpactHit(const UWorld* World, const FHitLoc // Return start location as 'safe location' OutResult.Location = QueryTM.GetLocation(); - const bool bValidPosition = (GetFlags(Hit) & EHitFlags::Position); + const bool bValidPosition = !!(GetFlags(Hit) & EHitFlags::Position); if (bValidPosition) { const FVector HitPosition = GetPosition(Hit); diff --git a/Engine/Source/Runtime/Engine/Private/ComponentInstanceDataCache.cpp b/Engine/Source/Runtime/Engine/Private/ComponentInstanceDataCache.cpp index 7a1f0056271c..d1f1f7ccefdb 100644 --- a/Engine/Source/Runtime/Engine/Private/ComponentInstanceDataCache.cpp +++ b/Engine/Source/Runtime/Engine/Private/ComponentInstanceDataCache.cpp @@ -558,11 +558,11 @@ void FComponentInstanceDataCache::ApplyToActor(AActor* Actor, const ECacheApplyP // components within a child actor are handled by applying the instance data to the child actor component if (ChildComponent->GetOwner() == Actor) { - Components.Add(ChildComponent); - } + Components.Add(ChildComponent); } } } + } // next loop start with the nodes we just added FirstProcessIndex = StartingProcessedCount; diff --git a/Engine/Source/Runtime/Engine/Private/Components/ActorComponent.cpp b/Engine/Source/Runtime/Engine/Private/Components/ActorComponent.cpp index f03b9f6fe1f1..6a9d0b8013e3 100644 --- a/Engine/Source/Runtime/Engine/Private/Components/ActorComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/Components/ActorComponent.cpp @@ -586,6 +586,7 @@ void UActorComponent::PreEditChange(UProperty* PropertyThatWillChange) // Don't do do a full recreate in this situation, and instead simply detach. if( !IsPendingKill() ) { + // One way this check can fail is that component subclass does not call Super::PostEditChangeProperty check(!EditReregisterContexts.Find(this)); EditReregisterContexts.Add(this,new FComponentReregisterContext(this)); } diff --git a/Engine/Source/Runtime/Engine/Private/Components/AudioComponent.cpp b/Engine/Source/Runtime/Engine/Private/Components/AudioComponent.cpp index 2850da348d6b..2688e02d5679 100644 --- a/Engine/Source/Runtime/Engine/Private/Components/AudioComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/Components/AudioComponent.cpp @@ -10,6 +10,7 @@ #include "Sound/SoundCue.h" #include "Components/BillboardComponent.h" #include "UObject/FrameworkObjectVersion.h" +#include "Misc/App.h" DECLARE_CYCLE_STAT(TEXT("AudioComponent Play"), STAT_AudioComp_Play, STATGROUP_Audio); @@ -68,6 +69,8 @@ UAudioComponent::UAudioComponent(const FObjectInitializer& ObjectInitializer) AudioDeviceHandle = INDEX_NONE; AudioComponentID = FPlatformAtomics::InterlockedIncrement(reinterpret_cast(&AudioComponentIDCounter)); + RandomStream.Initialize(FApp::bUseFixedSeed ? GetFName() : NAME_None); + { // TODO: Consider only putting played/active components in to the map FScopeLock Lock(&AudioIDToComponentMapLock); @@ -399,7 +402,7 @@ void UAudioComponent::PlayInternal(const float StartTime, const float FadeInDura NewActiveSound.SetSoundClass(SoundClassOverride); NewActiveSound.ConcurrencySet = ConcurrencySet; - const float Volume = (VolumeModulationMax + ((VolumeModulationMin - VolumeModulationMax) * FMath::SRand())) * VolumeMultiplier; + const float Volume = (VolumeModulationMax + ((VolumeModulationMin - VolumeModulationMax) * RandomStream.FRand())) * VolumeMultiplier; NewActiveSound.SetVolume(Volume); // The priority used for the active sound is the audio component's priority scaled with the sound's priority @@ -412,7 +415,7 @@ void UAudioComponent::PlayInternal(const float StartTime, const float FadeInDura NewActiveSound.Priority = Sound->Priority; } - const float Pitch = (PitchModulationMax + ((PitchModulationMin - PitchModulationMax) * FMath::SRand())) * PitchMultiplier; + const float Pitch = (PitchModulationMax + ((PitchModulationMin - PitchModulationMax) * RandomStream.FRand())) * PitchMultiplier; NewActiveSound.SetPitch(Pitch); NewActiveSound.bEnableLowPassFilter = bEnableLowPassFilter; diff --git a/Engine/Source/Runtime/Engine/Private/Components/CharacterMovementComponent.cpp b/Engine/Source/Runtime/Engine/Private/Components/CharacterMovementComponent.cpp index 95ccf3507a01..8e9a268a5e44 100644 --- a/Engine/Source/Runtime/Engine/Private/Components/CharacterMovementComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/Components/CharacterMovementComponent.cpp @@ -24,6 +24,7 @@ #include "AI/Navigation/PathFollowingAgentInterface.h" #include "AI/Navigation/AvoidanceManager.h" #include "Components/BrushComponent.h" +#include "Misc/App.h" #include "Engine/DemoNetDriver.h" #include "Engine/NetworkObjectList.h" @@ -379,6 +380,8 @@ FName FCharacterMovementComponentPostPhysicsTickFunction::DiagnosticContext(bool UCharacterMovementComponent::UCharacterMovementComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { + RandomStream.Initialize(FApp::bUseFixedSeed ? GetFName() : NAME_None); + PostPhysicsTickFunction.bCanEverTick = true; PostPhysicsTickFunction.bStartWithTickEnabled = false; PostPhysicsTickFunction.SetTickFunctionEnable(false); @@ -941,7 +944,7 @@ FVector UCharacterMovementComponent::GetBestDirectionOffActor(AActor* BaseActor) float UCharacterMovementComponent::GetNetworkSafeRandomAngleDegrees() const { - float Angle = FMath::SRand() * 360.f; + float Angle = RandomStream.FRand() * 360.f; if (!IsNetMode(NM_Standalone)) { @@ -4403,8 +4406,8 @@ void UCharacterMovementComponent::PhysFalling(float deltaTime, int32 Iterations) const float MovedDist2DSq = (PawnLocation - OldLocation).SizeSquared2D(); if (ZMovedDist <= 0.2f * timeTick && MovedDist2DSq <= 4.f * timeTick) { - Velocity.X += 0.25f * GetMaxSpeed() * (FMath::FRand() - 0.5f); - Velocity.Y += 0.25f * GetMaxSpeed() * (FMath::FRand() - 0.5f); + Velocity.X += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f); + Velocity.Y += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f); Velocity.Z = FMath::Max(JumpZVelocity * 0.25f, 1.f); Delta = Velocity * timeTick; SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit); @@ -8075,7 +8078,7 @@ void UCharacterMovementComponent::ReplicateMoveToServer(float DeltaTime, const F bSendServerMove = false; UE_LOG(LogNetPlayerMovement, Log, TEXT("Drop ServerMove, %.2f time remains"), CharacterMovementCVars::NetForceClientServerMoveLossDuration - TimeSinceLossStart); } - else if (CharacterMovementCVars::NetForceClientServerMoveLossPercent != 0.f && (FMath::SRand() < CharacterMovementCVars::NetForceClientServerMoveLossPercent)) + else if (CharacterMovementCVars::NetForceClientServerMoveLossPercent != 0.f && (RandomStream.FRand() < CharacterMovementCVars::NetForceClientServerMoveLossPercent)) { bSendServerMove = false; ClientData->DebugForcedPacketLossTimerStart = (CharacterMovementCVars::NetForceClientServerMoveLossDuration > 0) ? MyWorld->RealTimeSeconds : 0.0f; @@ -8822,7 +8825,7 @@ bool UCharacterMovementComponent::ServerCheckClientError(float ClientTimeStamp, #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) if (CharacterMovementCVars::NetForceClientAdjustmentPercent > SMALL_NUMBER) { - if (FMath::SRand() < CharacterMovementCVars::NetForceClientAdjustmentPercent) + if (RandomStream.FRand() < CharacterMovementCVars::NetForceClientAdjustmentPercent) { UE_LOG(LogNetPlayerMovement, VeryVerbose, TEXT("** ServerCheckClientError forced by p.NetForceClientAdjustmentPercent")); return true; diff --git a/Engine/Source/Runtime/Engine/Private/Components/ChildActorComponent.cpp b/Engine/Source/Runtime/Engine/Private/Components/ChildActorComponent.cpp index d25060f80576..b7c6f33434ca 100644 --- a/Engine/Source/Runtime/Engine/Private/Components/ChildActorComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/Components/ChildActorComponent.cpp @@ -466,11 +466,20 @@ void UChildActorComponent::SetChildActorClass(TSubclassOf Class) ChildActorTemplate = nullptr; } } - else if (IsRegistered()) + else { - ChildActorName = NAME_None; - DestroyChildActor(); - CreateChildActor(); + // Clear actor template if it no longer matches the set class + if (ChildActorTemplate && ChildActorTemplate->GetClass() != ChildActorClass) + { + ChildActorTemplate = nullptr; + } + + if (IsRegistered()) + { + ChildActorName = NAME_None; + DestroyChildActor(); + CreateChildActor(); + } } } diff --git a/Engine/Source/Runtime/Engine/Private/Components/DirectionalLightComponent.cpp b/Engine/Source/Runtime/Engine/Private/Components/DirectionalLightComponent.cpp index d5f1b8eea039..74a14cd99863 100644 --- a/Engine/Source/Runtime/Engine/Private/Components/DirectionalLightComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/Components/DirectionalLightComponent.cpp @@ -38,6 +38,12 @@ static TAutoConsoleVariable CVarCSMShadowDistanceFadeoutMultiplier( TEXT("Multiplier for the CSM distance fade"), ECVF_RenderThreadSafe | ECVF_Scalability ); +static TAutoConsoleVariable CVarPerObjectCastDistanceRadiusScale( + TEXT("r.Shadow.PerObjectCastDistanceRadiusScale"), + 8.0f, + TEXT("PerObjectCastDistanceRadiusScale The scale factor multiplied with the radius of the object to calculate the maximum distance a per-object directional shadow can reach. This will only take effect after a certain (large) radius. Default is 8 times the object radius."), + ECVF_RenderThreadSafe + ); /** * The scene info for a directional light. @@ -349,7 +355,9 @@ public: OutInitializer.MinLightW = -HALF_WORLD_MAX; // Reduce casting distance on a directional light // This is necessary to improve floating point precision in several places, especially when deriving frustum verts from InvReceiverMatrix - OutInitializer.MaxDistanceToCastInLightW = HALF_WORLD_MAX / 32.0f; + // This takes the object size into account to ensure that large objects get an extended distance + OutInitializer.MaxDistanceToCastInLightW = FMath::Clamp(SubjectBounds.SphereRadius * CVarPerObjectCastDistanceRadiusScale.GetValueOnRenderThread(), (float)HALF_WORLD_MAX / 32.0f, (float)WORLD_MAX); + return true; } diff --git a/Engine/Source/Runtime/Engine/Private/Components/ForceFeedbackComponent.cpp b/Engine/Source/Runtime/Engine/Private/Components/ForceFeedbackComponent.cpp index de08007b60c1..5561cfb27d1c 100644 --- a/Engine/Source/Runtime/Engine/Private/Components/ForceFeedbackComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/Components/ForceFeedbackComponent.cpp @@ -67,6 +67,11 @@ void FForceFeedbackManager::AddReferencedObjects(FReferenceCollector& Collector) Collector.AddReferencedObjects(ActiveForceFeedbackComponents); } +FString FForceFeedbackManager::GetReferencerName() const +{ + return TEXT("FForceFeedbackManager"); +} + UWorld* FForceFeedbackManager::GetTickableGameObjectWorld() const { return World; diff --git a/Engine/Source/Runtime/Engine/Private/Components/PrimitiveComponent.cpp b/Engine/Source/Runtime/Engine/Private/Components/PrimitiveComponent.cpp index e10ce652d08b..18d87ae0cb74 100644 --- a/Engine/Source/Runtime/Engine/Private/Components/PrimitiveComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/Components/PrimitiveComponent.cpp @@ -53,7 +53,7 @@ namespace PrimitiveComponentStatics static const FText MobilityWarnText = LOCTEXT("InvalidMove", "move"); } -typedef TArray> TInlineOverlapInfoArray; +typedef TArray> TInlineOverlapPointerArray; DEFINE_LOG_CATEGORY_STATIC(LogPrimitiveComponent, Log, All); @@ -154,6 +154,12 @@ struct FFastOverlapInfoCompare && MyBaseInfo.GetBodyIndex() == Info.GetBodyIndex(); } + bool operator() (const FOverlapInfo* Info) + { + return MyBaseInfo.OverlapInfo.Component.HasSameIndexAndSerialNumber(Info->OverlapInfo.Component) + && MyBaseInfo.GetBodyIndex() == Info->GetBodyIndex(); + } + private: const FOverlapInfo& MyBaseInfo; @@ -167,6 +173,13 @@ FORCEINLINE_DEBUGGABLE int32 IndexOfOverlapFast(const TArray +FORCEINLINE_DEBUGGABLE int32 IndexOfOverlapFast(const TArray& OverlapPtrArray, const FOverlapInfo* SearchItem) +{ + return OverlapPtrArray.IndexOfByPredicate(FFastOverlapInfoCompare(*SearchItem)); +} + // Helper for adding an FOverlapInfo uniquely to an Array, using IndexOfOverlapFast and knowing that at least one overlap is valid (non-null). template FORCEINLINE_DEBUGGABLE void AddUniqueOverlapFast(TArray& OverlapArray, FOverlapInfo& NewOverlap) @@ -196,7 +209,24 @@ FORCEINLINE_DEBUGGABLE static bool CanComponentsGenerateOverlap(const UPrimitive && MyComponent->GetCollisionResponseToComponent(OtherComp) == ECR_Overlap; } -// Predicate to remove components from overlaps array that can no longer overlap +// Predicate to identify components from overlaps array that can overlap +struct FPredicateFilterCanOverlap +{ + FPredicateFilterCanOverlap(const UPrimitiveComponent& OwningComponent) + : MyComponent(OwningComponent) + { + } + + bool operator() (const FOverlapInfo& Info) const + { + return CanComponentsGenerateOverlap(&MyComponent, Info.OverlapInfo.GetComponent()); + } + +private: + const UPrimitiveComponent& MyComponent; +}; + +// Predicate to identify components from overlaps array that can no longer overlap struct FPredicateFilterCannotOverlap { FPredicateFilterCannotOverlap(const UPrimitiveComponent& OwningComponent) @@ -213,6 +243,55 @@ private: const UPrimitiveComponent& MyComponent; }; +// Helper to initialize an array to point to data members in another array. +template +FORCEINLINE_DEBUGGABLE static void GetPointersToArrayData(TArray& Pointers, const TArray& DataArray) +{ + const int32 NumItems = DataArray.Num(); + Pointers.SetNumUninitialized(NumItems); + for (int32 i=0; i < NumItems; i++) + { + Pointers[i] = &(DataArray[i]); + } +} + +template +FORCEINLINE_DEBUGGABLE static void GetPointersToArrayData(TArray& Pointers, const TArrayView& DataArray) +{ + const int32 NumItems = DataArray.Num(); + Pointers.SetNumUninitialized(NumItems); + for (int32 i=0; i < NumItems; i++) + { + Pointers[i] = &(DataArray[i]); + } +} + +// Helper to initialize an array to point to data members in another array which satisfy a predicate. +template +FORCEINLINE_DEBUGGABLE static void GetPointersToArrayDataByPredicate(TArray& Pointers, const TArray& DataArray, PredicateT Predicate) +{ + Pointers.Reserve(Pointers.Num() + DataArray.Num()); + for (const ElementType& Item : DataArray) + { + if (Invoke(Predicate, Item)) + { + Pointers.Add(&Item); + } + } +} + +template +FORCEINLINE_DEBUGGABLE static void GetPointersToArrayDataByPredicate(TArray& Pointers, const TArrayView& DataArray, PredicateT Predicate) +{ + Pointers.Reserve(Pointers.Num() + DataArray.Num()); + for (const ElementType& Item : DataArray) + { + if (Invoke(Predicate, Item)) + { + Pointers.Add(&Item); + } + } +} /////////////////////////////////////////////////////////////////////////////// // PRIMITIVE COMPONENT @@ -1789,7 +1868,7 @@ static FORCEINLINE_DEBUGGABLE bool ShouldCheckOverlapFlagToQueueOverlaps(const U } -static bool ShouldIgnoreOverlapResult(const UWorld* World, const AActor* ThisActor, const UPrimitiveComponent& ThisComponent, const AActor* OtherActor, const UPrimitiveComponent& OtherComponent, bool bCheckOverlapFlags) +static FORCEINLINE_DEBUGGABLE bool ShouldIgnoreOverlapResult(const UWorld* World, const AActor* ThisActor, const UPrimitiveComponent& ThisComponent, const AActor* OtherActor, const UPrimitiveComponent& OtherComponent, bool bCheckOverlapFlags) { // Don't overlap with self if (&ThisComponent == &OtherComponent) @@ -1910,7 +1989,7 @@ bool UPrimitiveComponent::MoveComponentImpl( const FVector& Delta, const FQuat& bool bMoved = false; bool bIncludesOverlapsAtEnd = false; bool bRotationOnly = false; - TArray PendingOverlaps; + TInlineOverlapInfoArray PendingOverlaps; AActor* const Actor = GetOwner(); if ( !bSweep ) @@ -2116,21 +2195,24 @@ bool UPrimitiveComponent::MoveComponentImpl( const FVector& Delta, const FQuat& { if (bIncludesOverlapsAtEnd) { - TArray OverlapsAtEndLocation; - const TArray* OverlapsAtEndLocationPtr = nullptr; // When non-null, used as optimization to avoid work in UpdateOverlaps. + TInlineOverlapInfoArray OverlapsAtEndLocation; + bool bHasEndOverlaps = false; if (bRotationOnly) { - OverlapsAtEndLocationPtr = ConvertRotationOverlapsToCurrentOverlaps(OverlapsAtEndLocation, GetOverlapInfos()); + bHasEndOverlaps = ConvertRotationOverlapsToCurrentOverlaps(OverlapsAtEndLocation, OverlappingComponents); } else { - OverlapsAtEndLocationPtr = ConvertSweptOverlapsToCurrentOverlaps(OverlapsAtEndLocation, PendingOverlaps, 0, GetComponentLocation(), GetComponentQuat()); + bHasEndOverlaps = ConvertSweptOverlapsToCurrentOverlaps(OverlapsAtEndLocation, PendingOverlaps, 0, GetComponentLocation(), GetComponentQuat()); } - UpdateOverlaps(&PendingOverlaps, true, OverlapsAtEndLocationPtr); + TOverlapArrayView PendingOverlapsView(PendingOverlaps); + TOverlapArrayView OverlapsAtEndView(OverlapsAtEndLocation); + UpdateOverlaps(&PendingOverlapsView, true, bHasEndOverlaps ? &OverlapsAtEndView : nullptr); } else { - UpdateOverlaps(&PendingOverlaps, true, nullptr); + TOverlapArrayView PendingOverlapsView(PendingOverlaps); + UpdateOverlaps(&PendingOverlapsView, true, nullptr); } } } @@ -2360,7 +2442,8 @@ bool UPrimitiveComponent::IsOverlappingActor(const AActor* Other) const return false; } -bool UPrimitiveComponent::GetOverlapsWithActor(const AActor* Actor, TArray& OutOverlaps) const +template +bool UPrimitiveComponent::GetOverlapsWithActor_Template(const AActor* Actor, TArray& OutOverlaps) const { const int32 InitialCount = OutOverlaps.Num(); if (Actor) @@ -2378,6 +2461,11 @@ bool UPrimitiveComponent::GetOverlapsWithActor(const AActor* Actor, TArray& OutOverlaps) const +{ + return GetOverlapsWithActor_Template(Actor, OutOverlaps); +} + #if WITH_EDITOR bool UPrimitiveComponent::ComponentIsTouchingSelectionBox(const FBox& InSelBBox, const FEngineShowFlags& ShowFlags, const bool bConsiderOnlyBSP, const bool bMustEncompassEntireComponent) const { @@ -2449,15 +2537,13 @@ void UPrimitiveComponent::BeginComponentOverlap(const FOverlapInfo& OtherOverlap return; } - // UE_LOG(LogActor, Log, TEXT("BEGIN OVERLAP! Self=%s SelfComp=%s, Other=%s, OtherComp=%s"), *GetNameSafe(this), *GetNameSafe(MyComp), *GetNameSafe(OtherComp->GetOwner()), *GetNameSafe(OtherComp)); - - UPrimitiveComponent* OtherComp = OtherOverlap.OverlapInfo.Component.Get(); - - if (OtherComp) + const bool bComponentsAlreadyTouching = (IndexOfOverlapFast(OverlappingComponents, OtherOverlap) != INDEX_NONE); + if (!bComponentsAlreadyTouching) { - bool const bComponentsAlreadyTouching = (IndexOfOverlapFast(OverlappingComponents, OtherOverlap) != INDEX_NONE); - if (!bComponentsAlreadyTouching && CanComponentsGenerateOverlap(this, OtherComp)) + UPrimitiveComponent* OtherComp = OtherOverlap.OverlapInfo.Component.Get(); + if (CanComponentsGenerateOverlap(this, OtherComp)) { + //UE_LOG(LogActor, Log, TEXT("BEGIN OVERLAP! Self=%s SelfComp=%s, Other=%s, OtherComp=%s"), *GetNameSafe(this), *GetNameSafe(MyComp), *GetNameSafe(OtherComp->GetOwner()), *GetNameSafe(OtherComp)); GlobalOverlapEventsCounter++; AActor* const OtherActor = OtherComp->GetOwner(); AActor* const MyActor = GetOwner(); @@ -2523,8 +2609,6 @@ void UPrimitiveComponent::EndComponentOverlap(const FOverlapInfo& OtherOverlap, return; } - // UE_LOG(LogActor, Log, TEXT("END OVERLAP! Self=%s SelfComp=%s, Other=%s, OtherComp=%s"), *GetNameSafe(this), *GetNameSafe(MyComp), *GetNameSafe(OtherActor), *GetNameSafe(OtherComp)); - const int32 OtherOverlapIdx = IndexOfOverlapFast(OtherComp->OverlappingComponents, FOverlapInfo(this, INDEX_NONE)); if (OtherOverlapIdx != INDEX_NONE) { @@ -2534,6 +2618,7 @@ void UPrimitiveComponent::EndComponentOverlap(const FOverlapInfo& OtherOverlap, const int32 OverlapIdx = IndexOfOverlapFast(OverlappingComponents, OtherOverlap); if (OverlapIdx != INDEX_NONE) { + //UE_LOG(LogActor, Log, TEXT("END OVERLAP! Self=%s SelfComp=%s, Other=%s, OtherComp=%s"), *GetNameSafe(this), *GetNameSafe(MyComp), *GetNameSafe(OtherActor), *GetNameSafe(OtherComp)); GlobalOverlapEventsCounter++; OverlappingComponents.RemoveAtSwap(OverlapIdx, 1, false); @@ -2669,13 +2754,14 @@ void UPrimitiveComponent::GetOverlappingComponents(TSet& O } } -const TArray* UPrimitiveComponent::ConvertSweptOverlapsToCurrentOverlaps( - TArray& OverlapsAtEndLocation, const TArray& SweptOverlaps, int32 SweptOverlapsIndex, +template +bool UPrimitiveComponent::ConvertSweptOverlapsToCurrentOverlaps( + TArray& OverlapsAtEndLocation, const TOverlapArrayView& SweptOverlaps, int32 SweptOverlapsIndex, const FVector& EndLocation, const FQuat& EndRotationQuat) { checkSlow(SweptOverlapsIndex >= 0); - const TArray* Result = nullptr; + bool bResult = false; const bool bForceGatherOverlaps = !ShouldCheckOverlapFlagToQueueOverlaps(*this); if ((GetGenerateOverlapEvents() || bForceGatherOverlaps) && bAllowCachedOverlapsCVar) { @@ -2689,7 +2775,9 @@ const TArray* UPrimitiveComponent::ConvertSweptOverlapsToCurrentOv // Check components we hit during the sweep, keep only those still overlapping const FCollisionQueryParams UnusedQueryParams(NAME_None, FCollisionQueryParams::GetUnknownStatId()); - for (int32 Index = SweptOverlapsIndex; Index < SweptOverlaps.Num(); ++Index) + const int32 NumSweptOverlaps = SweptOverlaps.Num(); + OverlapsAtEndLocation.Reserve(OverlapsAtEndLocation.Num() + NumSweptOverlaps); + for (int32 Index = SweptOverlapsIndex; Index < NumSweptOverlaps; ++Index) { const FOverlapInfo& OtherOverlap = SweptOverlaps[Index]; UPrimitiveComponent* OtherPrimitive = OtherOverlap.OverlapInfo.GetComponent(); @@ -2698,12 +2786,12 @@ const TArray* UPrimitiveComponent::ConvertSweptOverlapsToCurrentOv if (OtherPrimitive->bMultiBodyOverlap) { // Not handled yet. We could do it by checking every body explicitly and track each body index in the overlap test, but this seems like a rare need. - return nullptr; + return false; } else if (Cast(OtherPrimitive) || Cast(this)) { // SkeletalMeshComponent does not support this operation, and would return false in the test when an actual query could return true. - return nullptr; + return false; } else if (OtherPrimitive->ComponentOverlapComponent(this, EndLocation, EndRotationQuat, UnusedQueryParams)) { @@ -2717,27 +2805,27 @@ const TArray* UPrimitiveComponent::ConvertSweptOverlapsToCurrentOv checkfSlow(OverlapsAtEndLocation.FindByPredicate(FPredicateOverlapHasSameActor(*Actor)) == nullptr, TEXT("Child overlaps should not be included in the SweptOverlaps() array in UPrimitiveComponent::ConvertSweptOverlapsToCurrentOverlaps().")); - Result = &OverlapsAtEndLocation; + bResult = true; } else { if (SweptOverlaps.Num() == 0 && AreAllCollideableDescendantsRelative()) { // Add overlaps with components in this actor. - GetOverlapsWithActor(Actor, OverlapsAtEndLocation); - Result = &OverlapsAtEndLocation; + GetOverlapsWithActor_Template(Actor, OverlapsAtEndLocation); + bResult = true; } } } } - return Result; + return bResult; } - -const TArray* UPrimitiveComponent::ConvertRotationOverlapsToCurrentOverlaps(TArray& OverlapsAtEndLocation, const TArray& CurrentOverlaps) +template +bool UPrimitiveComponent::ConvertRotationOverlapsToCurrentOverlaps(TArray& OutOverlapsAtEndLocation, const TOverlapArrayView& CurrentOverlaps) { - const TArray* Result = nullptr; + bool bResult = false; const bool bForceGatherOverlaps = !ShouldCheckOverlapFlagToQueueOverlaps(*this); if ((GetGenerateOverlapEvents() || bForceGatherOverlaps) && bAllowCachedOverlapsCVar) { @@ -2747,13 +2835,14 @@ const TArray* UPrimitiveComponent::ConvertRotationOverlapsToCurren if (bEnableFastOverlapCheck) { // Add all current overlaps that are not children. Children test for their own overlaps after we update our own, and we ignore children in our own update. - Algo::CopyIf(CurrentOverlaps, OverlapsAtEndLocation, FPredicateOverlapHasDifferentActor(*Actor)); - Result = &OverlapsAtEndLocation; + OutOverlapsAtEndLocation.Reserve(OutOverlapsAtEndLocation.Num() + CurrentOverlaps.Num()); + Algo::CopyIf(CurrentOverlaps, OutOverlapsAtEndLocation, FPredicateOverlapHasDifferentActor(*Actor)); + bResult = true; } } } - return Result; + return bResult; } @@ -2889,7 +2978,7 @@ TArray UPrimitiveComponent::CopyArrayOfMoveIgnoreComponent return MoveIgnoreComponents; } -bool UPrimitiveComponent::UpdateOverlapsImpl(const TArray* NewPendingOverlaps, bool bDoNotifies, const TArray* OverlapsAtEndLocation) +bool UPrimitiveComponent::UpdateOverlapsImpl(const TOverlapArrayView* NewPendingOverlaps, bool bDoNotifies, const TOverlapArrayView* OverlapsAtEndLocation) { SCOPE_CYCLE_COUNTER(STAT_UpdateOverlaps); SCOPE_CYCLE_UOBJECT(ComponentScope, this); @@ -2913,36 +3002,39 @@ bool UPrimitiveComponent::UpdateOverlapsImpl(const TArray* NewPend if (NewPendingOverlaps) { // Note: BeginComponentOverlap() only triggers overlaps where GetGenerateOverlapEvents() is true on both components. - for (int32 Idx=0; IdxNum(); ++Idx) + const int32 NumNewPendingOverlaps = NewPendingOverlaps->Num(); + for (int32 Idx=0; Idx < NumNewPendingOverlaps; ++Idx) { BeginComponentOverlap( (*NewPendingOverlaps)[Idx], bDoNotifies ); } } - // now generate full list of new touches, so we can compare to existing list and - // determine what changed - TInlineOverlapInfoArray NewOverlappingComponents; + // now generate full list of new touches, so we can compare to existing list and determine what changed + TInlineOverlapInfoArray OverlapMultiResult; + TInlineOverlapPointerArray NewOverlappingComponentPtrs; - // If pending kill, we should not generate any new overlaps - if (!IsPendingKill()) + // If pending kill, we should not generate any new overlaps. Also not if overlaps were just disabled during BeginComponentOverlap. + if (!IsPendingKill() && GetGenerateOverlapEvents()) { // Might be able to avoid testing for new overlaps at the end location. - if (OverlapsAtEndLocation != NULL && bAllowCachedOverlapsCVar && PrevTransform.Equals(GetComponentTransform())) + if (OverlapsAtEndLocation != nullptr && bAllowCachedOverlapsCVar && PrevTransform.Equals(GetComponentTransform())) { UE_LOG(LogPrimitiveComponent, VeryVerbose, TEXT("%s->%s Skipping overlap test!"), *GetNameSafe(GetOwner()), *GetName()); - NewOverlappingComponents = *OverlapsAtEndLocation; - - // BeginComponentOverlap may have disabled what we thought were valid overlaps at the end (collision response or overlap flags could change). - // Or we have overlaps from a scoped update that didn't require overlap events, but we need to remove those now. - if (NewPendingOverlaps && NewPendingOverlaps->Num() > 0) + const bool bCheckForInvalid = (NewPendingOverlaps && NewPendingOverlaps->Num() > 0); + if (bCheckForInvalid) { - NewOverlappingComponents.RemoveAllSwap(FPredicateFilterCannotOverlap(*this), /*bAllowShrinking*/ false); + // BeginComponentOverlap may have disabled what we thought were valid overlaps at the end (collision response or overlap flags could change). + GetPointersToArrayDataByPredicate(NewOverlappingComponentPtrs, *OverlapsAtEndLocation, FPredicateFilterCanOverlap(*this)); + } + else + { + GetPointersToArrayData(NewOverlappingComponentPtrs, *OverlapsAtEndLocation); } } else { UE_LOG(LogPrimitiveComponent, VeryVerbose, TEXT("%s->%s Performing overlaps!"), *GetNameSafe(GetOwner()), *GetName()); - UWorld* const MyWorld = MyActor->GetWorld(); + UWorld* const MyWorld = GetWorld(); TArray Overlaps; // note this will optionally include overlaps with components in the same actor (depending on bIgnoreChildren). FComponentQueryParams Params(SCENE_QUERY_STAT(UpdateOverlaps), bIgnoreChildren ? MyActor : nullptr); @@ -2951,33 +3043,37 @@ bool UPrimitiveComponent::UpdateOverlapsImpl(const TArray* NewPend InitSweepCollisionParams(Params, ResponseParam); ComponentOverlapMulti(Overlaps, MyWorld, GetComponentLocation(), GetComponentQuat(), GetCollisionObjectType(), Params); - for( int32 ResultIdx=0; ResultIdxGetGenerateOverlapEvents()) { - if (!ShouldIgnoreOverlapResult(MyWorld, MyActor, *this, Result.GetActor(), *HitComp, /*bCheckOverlapFlags=*/ true)) + const bool bCheckOverlapFlags = false; // Already checked above + if (!ShouldIgnoreOverlapResult(MyWorld, MyActor, *this, Result.GetActor(), *HitComp, bCheckOverlapFlags)) { - NewOverlappingComponents.Add(FOverlapInfo(HitComp, Result.ItemIndex)); // don't need to add unique unless the overlap check can return dupes + OverlapMultiResult.Emplace(HitComp, Result.ItemIndex); // don't need to add unique unless the overlap check can return dupes } } } + + // Fill pointers to overlap results. We ensure below that OverlapMultiResult stays in scope so these pointers remain valid. + GetPointersToArrayData(NewOverlappingComponentPtrs, OverlapMultiResult); } } + // If we have any overlaps from BeginComponentOverlap() (from now or in the past), see if anything has changed by filtering NewOverlappingComponents if (OverlappingComponents.Num() > 0) { - // make a copy of the old that we can manipulate to avoid n^2 searching later - TInlineOverlapInfoArray OldOverlappingComponents; + TInlineOverlapPointerArray OldOverlappingComponentPtrs; if (bIgnoreChildren) { - Algo::CopyIf(OverlappingComponents, OldOverlappingComponents, FPredicateOverlapHasDifferentActor(*MyActor)); + GetPointersToArrayDataByPredicate(OldOverlappingComponentPtrs, OverlappingComponents, FPredicateOverlapHasDifferentActor(*MyActor)); } else { - OldOverlappingComponents = OverlappingComponents; + GetPointersToArrayData(OldOverlappingComponentPtrs, OverlappingComponents); } // Now we want to compare the old and new overlap lists to determine @@ -2985,47 +3081,61 @@ bool UPrimitiveComponent::UpdateOverlapsImpl(const TArray* NewPend // what overlaps are in new and not in old (need begin overlap notifies). // We do this by removing common entries from both lists, since overlapping status has not changed for them. // What is left over will be what has changed. - for (int32 CompIdx=0; CompIdx < OldOverlappingComponents.Num() && NewOverlappingComponents.Num() > 0; ++CompIdx) + for (int32 CompIdx=0; CompIdx < OldOverlappingComponentPtrs.Num() && NewOverlappingComponentPtrs.Num() > 0; ++CompIdx) { - // RemoveSingleSwap is ok, since it is not necessary to maintain order + // RemoveAtSwap is ok, since it is not necessary to maintain order const bool bAllowShrinking = false; - const FOverlapInfo& SearchItem = OldOverlappingComponents[CompIdx]; - const int32 NewElementIdx = IndexOfOverlapFast(NewOverlappingComponents, SearchItem); + const FOverlapInfo* SearchItem = OldOverlappingComponentPtrs[CompIdx]; + const int32 NewElementIdx = IndexOfOverlapFast(NewOverlappingComponentPtrs, SearchItem); if (NewElementIdx != INDEX_NONE) { - NewOverlappingComponents.RemoveAtSwap(NewElementIdx, 1, bAllowShrinking); - OldOverlappingComponents.RemoveAtSwap(CompIdx, 1, bAllowShrinking); + NewOverlappingComponentPtrs.RemoveAtSwap(NewElementIdx, 1, bAllowShrinking); + OldOverlappingComponentPtrs.RemoveAtSwap(CompIdx, 1, bAllowShrinking); --CompIdx; } } - // OldOverlappingComponents now contains only previous overlaps that are confirmed to no longer be valid. - for (auto CompIt = OldOverlappingComponents.CreateIterator(); CompIt; ++CompIt) + const int32 NumOldOverlaps = OldOverlappingComponentPtrs.Num(); + if (NumOldOverlaps > 0) { - const FOverlapInfo& OtherOverlap = *CompIt; - if (OtherOverlap.OverlapInfo.Component.IsValid()) + // Now we have to make a copy of the overlaps because we can't keep pointers to them, that list is about to be manipulated in EndComponentOverlap(). + TInlineOverlapInfoArray OldOverlappingComponents; + OldOverlappingComponents.SetNumUninitialized(NumOldOverlaps); + for (int32 i=0; i < NumOldOverlaps; i++) { - EndComponentOverlap(OtherOverlap, bDoNotifies, false); + OldOverlappingComponents[i] = *(OldOverlappingComponentPtrs[i]); } - else + + // OldOverlappingComponents now contains only previous overlaps that are confirmed to no longer be valid. + for (const FOverlapInfo& OtherOverlap : OldOverlappingComponents) { - // Remove stale item. Reclaim memory only if it's getting large, to try to avoid churn but avoid bloating component's memory usage. - const bool bAllowShrinking = (OverlappingComponents.Max() >= 24); - const int32 StaleElementIndex = IndexOfOverlapFast(OverlappingComponents, OtherOverlap); - if (StaleElementIndex != INDEX_NONE) + if (OtherOverlap.OverlapInfo.Component.IsValid()) { - OverlappingComponents.RemoveAtSwap(StaleElementIndex, 1, bAllowShrinking); + EndComponentOverlap(OtherOverlap, bDoNotifies, false); + } + else + { + // Remove stale item. Reclaim memory only if it's getting large, to try to avoid churn but avoid bloating component's memory usage. + const bool bAllowShrinking = (OverlappingComponents.Max() >= 24); + const int32 StaleElementIndex = IndexOfOverlapFast(OverlappingComponents, OtherOverlap); + if (StaleElementIndex != INDEX_NONE) + { + OverlappingComponents.RemoveAtSwap(StaleElementIndex, 1, bAllowShrinking); + } } } } } + // Ensure these arrays are still in scope, because we kept pointers to them in NewOverlappingComponentPtrs. + static_assert(sizeof(OverlapMultiResult) != 0, "Variable must be in this scope"); + static_assert(sizeof(OverlapsAtEndLocation) != 0, "Variable must be in this scope"); + // NewOverlappingComponents now contains only new overlaps that didn't exist previously. - for (auto CompIt = NewOverlappingComponents.CreateIterator(); CompIt; ++CompIt) + for (const FOverlapInfo* NewOverlap : NewOverlappingComponentPtrs) { - const FOverlapInfo& OtherOverlap = *CompIt; - BeginComponentOverlap(OtherOverlap, bDoNotifies); + BeginComponentOverlap(*NewOverlap, bDoNotifies); } } } diff --git a/Engine/Source/Runtime/Engine/Private/Components/ReflectionCaptureComponent.cpp b/Engine/Source/Runtime/Engine/Private/Components/ReflectionCaptureComponent.cpp index c2f060134a09..a2dd467d9fa1 100644 --- a/Engine/Source/Runtime/Engine/Private/Components/ReflectionCaptureComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/Components/ReflectionCaptureComponent.cpp @@ -123,7 +123,7 @@ void UReflectionCaptureComponent::PropagateLightingScenarioChange() AReflectionCapture::AReflectionCapture(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { - CaptureComponent = CreateAbstractDefaultSubobject(TEXT("NewReflectionComponent")); + CaptureComponent = CreateDefaultSubobject(TEXT("NewReflectionComponent")); bCanBeInCluster = true; diff --git a/Engine/Source/Runtime/Engine/Private/Components/SceneComponent.cpp b/Engine/Source/Runtime/Engine/Private/Components/SceneComponent.cpp index 8af481209608..c06cd87e475a 100644 --- a/Engine/Source/Runtime/Engine/Private/Components/SceneComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/Components/SceneComponent.cpp @@ -75,9 +75,6 @@ USceneComponent::USceneComponent(const FObjectInitializer& ObjectInitializer /*= // default behavior is visible bVisible = true; bAutoActivate = false; - - bNetHasReceivedRelativeLocation = false; - bNetHasReceivedRelativeRotation = false; bShouldBeAttached = AttachParent != nullptr; } @@ -662,6 +659,8 @@ void USceneComponent::OnRegister() AttachParent = nullptr; AttachSocketName = NAME_None; bShouldBeAttached = false; + bShouldSnapLocationWhenAttached = false; + bShouldSnapRotationWhenAttached = false; } } @@ -835,9 +834,10 @@ void USceneComponent::EndScopedMovementUpdate(class FScopedMovementUpdate& Compl if (PrimitiveThis) { // NOTE: UpdateOverlaps filters events to only consider overlaps where bGenerateOverlapEvents is true for both components, so it's ok if we queued up other overlaps. - TArray EndOverlaps; - const TArray* EndOverlapsPtr = CurrentScopedUpdate->GetOverlapsAtEnd(*PrimitiveThis, EndOverlaps, bTransformChanged); - UpdateOverlaps(&CurrentScopedUpdate->GetPendingOverlaps(), true, EndOverlapsPtr); + TInlineOverlapInfoArray EndOverlaps; + const TOverlapArrayView PendingOverlaps(CurrentScopedUpdate->GetPendingOverlaps()); + const TOptional EndOverlapsOptional = CurrentScopedUpdate->GetOverlapsAtEnd(*PrimitiveThis, EndOverlaps, bTransformChanged); + UpdateOverlaps(&PendingOverlaps, true, EndOverlapsOptional.IsSet() ? &(EndOverlapsOptional.GetValue()) : nullptr); } else { @@ -1710,10 +1710,9 @@ void USceneComponent::SetupAttachment(class USceneComponent* InParent, FName InS { if (ensureMsgf(InParent != this, TEXT("Cannot attach a component to itself."))) { - // Paragon hack - if (/*ensureMsgf*/(InParent == nullptr || !InParent->IsAttachedTo(this))) //, TEXT("Setting up attachment would create a cycle."))) + if (ensureMsgf(InParent == nullptr || !InParent->IsAttachedTo(this), TEXT("Setting up attachment would create a cycle."))) { - if (/*ensureMsgf*/(AttachParent == nullptr || !AttachParent->AttachChildren.Contains(this)))// , TEXT("SetupAttachment cannot be used once a component has already had AttachTo used to connect it to a parent."))) + if (ensureMsgf(AttachParent == nullptr || !AttachParent->AttachChildren.Contains(this), TEXT("SetupAttachment cannot be used once a component has already had AttachTo used to connect it to a parent."))) { AttachParent = InParent; AttachSocketName = InSocketName; @@ -1785,6 +1784,9 @@ bool USceneComponent::AttachToComponent(USceneComponent* Parent, const FAttachme ensureMsgf(!AttachmentRules.bWeldSimulatedBodies, TEXT("AttachToComponent when called from a constructor cannot weld simulated bodies. Consider calling SetupAttachment directly instead.")); ensureMsgf(AttachmentRules.LocationRule == EAttachmentRule::KeepRelative && AttachmentRules.RotationRule == EAttachmentRule::KeepRelative && AttachmentRules.ScaleRule == EAttachmentRule::KeepRelative, TEXT("AttachToComponent when called from a constructor is only setting up attachment and will always be treated as KeepRelative. Consider calling SetupAttachment directly instead.")); SetupAttachment(Parent, SocketName); + bShouldSnapLocationWhenAttached = false; + bShouldSnapRotationWhenAttached = false; + return true; } @@ -1949,6 +1951,9 @@ bool USceneComponent::AttachToComponent(USceneComponent* Parent, const FAttachme AttachSocketName = SocketName; bShouldBeAttached = AttachParent != nullptr; + bShouldSnapLocationWhenAttached = AttachmentRules.LocationRule == EAttachmentRule::SnapToTarget; + bShouldSnapRotationWhenAttached = AttachmentRules.RotationRule == EAttachmentRule::SnapToTarget; + OnAttachmentChanged(); // Preserve order of previous attachment if valid (in case we're doing a reattach operation inside a loop that might assume the AttachChildren order won't change) @@ -2150,6 +2155,9 @@ void USceneComponent::DetachFromComponent(const FDetachmentTransformRules& Detac AttachSocketName = NAME_None; bShouldBeAttached = 0; + bShouldSnapLocationWhenAttached = false; + bShouldSnapRotationWhenAttached = false; + OnAttachmentChanged(); // If desired, update RelativeLocation and RelativeRotation to maintain current world position after detachment @@ -2760,7 +2768,7 @@ bool USceneComponent::InternalSetWorldLocationAndRotation(FVector NewLocation, c return false; } -bool USceneComponent::UpdateOverlapsImpl(TArray const* PendingOverlaps, bool bDoNotifies, const TArray* OverlapsAtEndLocation) +bool USceneComponent::UpdateOverlapsImpl(const TOverlapArrayView* PendingOverlaps, bool bDoNotifies, const TOverlapArrayView* OverlapsAtEndLocation) { SCOPE_CYCLE_COUNTER(STAT_UpdateOverlaps); @@ -3121,18 +3129,6 @@ void USceneComponent::OnRep_Transform() bNetUpdateTransform = true; } -void USceneComponent::OnRep_RelativeLocation() -{ - bNetHasReceivedRelativeLocation = true; - OnRep_Transform(); -} - -void USceneComponent::OnRep_RelativeRotation() -{ - bNetHasReceivedRelativeRotation = true; - OnRep_Transform(); -} - void USceneComponent::OnRep_AttachParent() { bNetUpdateAttachment = true; @@ -3226,16 +3222,14 @@ void USceneComponent::PostRepNotifies() Exchange(NetOldAttachParent, AttachParent); Exchange(NetOldAttachSocketName, AttachSocketName); - // Note: This is a local fix for JIRA UE-43355. If we receive bNetUpdateAttachment without having received a updated transform, assume that we intend to snap and that the relative location/rotation should defaulted - if (!bNetHasReceivedRelativeLocation) + // Note: This is a local fix for JIRA UE-43355. + if (bShouldSnapLocationWhenAttached) { RelativeLocation = FVector::ZeroVector; - bNetHasReceivedRelativeLocation = true; } - if (!bNetHasReceivedRelativeRotation) + if (bShouldSnapRotationWhenAttached) { RelativeRotation = FRotator::ZeroRotator; - bNetHasReceivedRelativeRotation = true; } // Check if this is a detach @@ -3247,11 +3241,15 @@ void USceneComponent::PostRepNotifies() else { const uint8 bOldShouldBeAttached = bShouldBeAttached; + const uint8 bOldShouldSnapLocationWhenAttached = bShouldSnapLocationWhenAttached; + const uint8 bOldShouldSnapRotationWhenAttached = bShouldSnapRotationWhenAttached; AttachToComponent(NetOldAttachParent, FAttachmentTransformRules::KeepRelativeTransform, NetOldAttachSocketName); - // restore bShouldBeAttached to what we have received + // restore to what we have received from the server bShouldBeAttached = bOldShouldBeAttached; + bShouldSnapLocationWhenAttached = bOldShouldSnapLocationWhenAttached; + bShouldSnapRotationWhenAttached = bOldShouldSnapRotationWhenAttached; } bNetUpdateAttachment = false; @@ -3273,6 +3271,8 @@ void USceneComponent::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & O DOREPLIFETIME(USceneComponent, bAbsoluteScale); DOREPLIFETIME(USceneComponent, bVisible); DOREPLIFETIME(USceneComponent, bShouldBeAttached); + DOREPLIFETIME(USceneComponent, bShouldSnapLocationWhenAttached); + DOREPLIFETIME(USceneComponent, bShouldSnapRotationWhenAttached); DOREPLIFETIME(USceneComponent, AttachParent); DOREPLIFETIME(USceneComponent, AttachChildren); DOREPLIFETIME(USceneComponent, AttachSocketName); @@ -3464,7 +3464,7 @@ void FScopedMovementUpdate::RevertMove() TeleportType = ETeleportType::None; } -void FScopedMovementUpdate::AppendOverlapsAfterMove(const TArray& NewPendingOverlaps, bool bSweep, bool bIncludesOverlapsAtEnd) +void FScopedMovementUpdate::AppendOverlapsAfterMove(const TOverlapArrayView& NewPendingOverlaps, bool bSweep, bool bIncludesOverlapsAtEnd) { bHasMoved = true; const bool bWasForcing = (CurrentOverlapState == EOverlapState::eForceUpdate); @@ -3475,7 +3475,7 @@ void FScopedMovementUpdate::AppendOverlapsAfterMove(const TArray& if (NewPendingOverlaps.Num()) { FinalOverlapCandidatesIndex = PendingOverlaps.Num(); - PendingOverlaps.Append(NewPendingOverlaps); + PendingOverlaps.Append(NewPendingOverlaps.GetData(), NewPendingOverlaps.Num()); } else { @@ -3488,7 +3488,7 @@ void FScopedMovementUpdate::AppendOverlapsAfterMove(const TArray& // We don't know about the final overlaps in the case of a teleport. CurrentOverlapState = EOverlapState::eUnknown; FinalOverlapCandidatesIndex = INDEX_NONE; - PendingOverlaps.Append(NewPendingOverlaps); + PendingOverlaps.Append(NewPendingOverlaps.GetData(), NewPendingOverlaps.Num()); } if (bWasForcing) @@ -3547,9 +3547,10 @@ void FScopedMovementUpdate::OnInnerScopeComplete(const FScopedMovementUpdate& In } } -const TArray* FScopedMovementUpdate::GetOverlapsAtEnd(class UPrimitiveComponent& PrimComponent, TArray& EndOverlaps, bool bTransformChanged) const +template +TOptional FScopedMovementUpdate::GetOverlapsAtEnd(class UPrimitiveComponent& PrimComponent, TArray& OutEndOverlaps, bool bTransformChanged) const { - const TArray* EndOverlapsPtr = nullptr; + TOptional Result; switch (CurrentOverlapState) { case FScopedMovementUpdate::EOverlapState::eUseParent: @@ -3557,19 +3558,21 @@ const TArray* FScopedMovementUpdate::GetOverlapsAtEnd(class UPrimi // Only rotation could have possibly changed if (bTransformChanged && PrimComponent.AreSymmetricRotations(InitialTransform.GetRotation(), PrimComponent.GetComponentQuat(), PrimComponent.GetComponentScale())) { - EndOverlapsPtr = PrimComponent.ConvertRotationOverlapsToCurrentOverlaps(EndOverlaps, PrimComponent.GetOverlapInfos()); + if (PrimComponent.ConvertRotationOverlapsToCurrentOverlaps(OutEndOverlaps, PrimComponent.GetOverlapInfos())) + { + Result = OutEndOverlaps; + } } else { // Use current overlaps (unchanged) - EndOverlapsPtr = &PrimComponent.GetOverlapInfos(); + Result = PrimComponent.GetOverlapInfos(); } break; } case FScopedMovementUpdate::EOverlapState::eUnknown: case FScopedMovementUpdate::EOverlapState::eForceUpdate: { - EndOverlapsPtr = nullptr; break; } case FScopedMovementUpdate::EOverlapState::eIncludesOverlaps: @@ -3577,7 +3580,7 @@ const TArray* FScopedMovementUpdate::GetOverlapsAtEnd(class UPrimi if (FinalOverlapCandidatesIndex == INDEX_NONE) { // Overlapping nothing - EndOverlapsPtr = &EndOverlaps; + Result = OutEndOverlaps; } else { @@ -3585,9 +3588,14 @@ const TArray* FScopedMovementUpdate::GetOverlapsAtEnd(class UPrimi const bool bMatchingScale = FTransform::AreScale3DsEqual(InitialTransform, PrimComponent.GetComponentTransform()); if (bMatchingScale) { - EndOverlapsPtr = PrimComponent.ConvertSweptOverlapsToCurrentOverlaps( - EndOverlaps, GetPendingOverlaps(), FinalOverlapCandidatesIndex, + const bool bHasEndOverlaps = PrimComponent.ConvertSweptOverlapsToCurrentOverlaps( + OutEndOverlaps, PendingOverlaps, FinalOverlapCandidatesIndex, PrimComponent.GetComponentLocation(), PrimComponent.GetComponentQuat()); + + if (bHasEndOverlaps) + { + Result = OutEndOverlaps; + } } } break; @@ -3599,7 +3607,7 @@ const TArray* FScopedMovementUpdate::GetOverlapsAtEnd(class UPrimi } } - return EndOverlapsPtr; + return Result; } diff --git a/Engine/Source/Runtime/Engine/Private/Components/SkinnedMeshComponent.cpp b/Engine/Source/Runtime/Engine/Private/Components/SkinnedMeshComponent.cpp index 0f8161c91658..bd8beb346171 100644 --- a/Engine/Source/Runtime/Engine/Private/Components/SkinnedMeshComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/Components/SkinnedMeshComponent.cpp @@ -2140,7 +2140,7 @@ void USkinnedMeshComponent::QuerySupportedSockets(TArray const* PendingOverlaps, bool bDoNotifies, const TArray* OverlapsAtEndLocation) +bool USkinnedMeshComponent::UpdateOverlapsImpl(const TOverlapArrayView* PendingOverlaps, bool bDoNotifies, const TOverlapArrayView* OverlapsAtEndLocation) { // we don't support overlap test on destructible or physics asset // so use SceneComponent::UpdateOverlaps to handle children diff --git a/Engine/Source/Runtime/Engine/Private/Components/SplineComponent.cpp b/Engine/Source/Runtime/Engine/Private/Components/SplineComponent.cpp index 7a5f2dd1bdf4..67ff2aa9458e 100644 --- a/Engine/Source/Runtime/Engine/Private/Components/SplineComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/Components/SplineComponent.cpp @@ -16,8 +16,19 @@ const FInterpCurvePointVector USplineComponent::DummyPointPosition(0.0f, FVector const FInterpCurvePointQuat USplineComponent::DummyPointRotation(0.0f, FQuat::Identity); const FInterpCurvePointVector USplineComponent::DummyPointScale(0.0f, FVector::OneVector); +USplineMetadata::USplineMetadata(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} USplineComponent::USplineComponent(const FObjectInitializer& ObjectInitializer) + : USplineComponent(ObjectInitializer, nullptr) +{ + +} + +USplineComponent::USplineComponent(const FObjectInitializer& ObjectInitializer, USplineMetadata* Metadata) : Super(ObjectInitializer) , bAllowSplineEditingPerInstance_DEPRECATED(true) , ReparamStepsPerSegment(10) @@ -37,6 +48,8 @@ USplineComponent::USplineComponent(const FObjectInitializer& ObjectInitializer) , ScaleVisualizationWidth(30.0f) #endif { + SplineCurves.Metadata = Metadata; + SplineCurves.Position.Points.Reset(10); SplineCurves.Rotation.Points.Reset(10); SplineCurves.Scale.Points.Reset(10); @@ -44,11 +57,18 @@ USplineComponent::USplineComponent(const FObjectInitializer& ObjectInitializer) SplineCurves.Position.Points.Emplace(0.0f, FVector(0, 0, 0), FVector::ZeroVector, FVector::ZeroVector, CIM_CurveAuto); SplineCurves.Rotation.Points.Emplace(0.0f, FQuat::Identity, FQuat::Identity, FQuat::Identity, CIM_CurveAuto); SplineCurves.Scale.Points.Emplace(0.0f, FVector(1.0f), FVector::ZeroVector, FVector::ZeroVector, CIM_CurveAuto); - + SplineCurves.Position.Points.Emplace(1.0f, FVector(100, 0, 0), FVector::ZeroVector, FVector::ZeroVector, CIM_CurveAuto); SplineCurves.Rotation.Points.Emplace(1.0f, FQuat::Identity, FQuat::Identity, FQuat::Identity, CIM_CurveAuto); SplineCurves.Scale.Points.Emplace(1.0f, FVector(1.0f), FVector::ZeroVector, FVector::ZeroVector, CIM_CurveAuto); + if (SplineCurves.Metadata) + { + SplineCurves.Metadata->Reset(10); + SplineCurves.Metadata->AddPoint(0.0f); + SplineCurves.Metadata->AddPoint(1.0f); + } + UpdateSpline(); // Set these deprecated values up so that old assets with default values load correctly (and are subsequently upgraded during Serialize) @@ -562,6 +582,12 @@ void USplineComponent::AddPoint(const FSplinePoint& InSplinePoint, bool bUpdateS ), Index); + USplineMetadata* Metadata = GetSplinePointsMetadata(); + if (Metadata) + { + Metadata->AddPoint(InSplinePoint.InputKey); + } + if (bLoopPositionOverride && LoopPosition <= SplineCurves.Position.Points.Last().InVal) { bLoopPositionOverride = false; @@ -603,6 +629,12 @@ void USplineComponent::AddSplinePoint(const FVector& Position, ESplineCoordinate SplineCurves.Position.Points.Emplace(InKey, TransformedPosition, FVector::ZeroVector, FVector::ZeroVector, CIM_CurveAuto); SplineCurves.Rotation.Points.Emplace(InKey, FQuat::Identity, FQuat::Identity, FQuat::Identity, CIM_CurveAuto); SplineCurves.Scale.Points.Emplace(InKey, FVector(1.0f), FVector::ZeroVector, FVector::ZeroVector, CIM_CurveAuto); + USplineMetadata* Metadata = GetSplinePointsMetadata(); + if (Metadata) + { + Metadata->AddPoint(InKey); + } + if (bLoopPositionOverride) { @@ -629,6 +661,12 @@ void USplineComponent::AddSplinePointAtIndex(const FVector& Position, int32 Inde SplineCurves.Position.Points.Insert(FInterpCurvePoint(InKey, TransformedPosition, FVector::ZeroVector, FVector::ZeroVector, CIM_CurveAuto), Index); SplineCurves.Rotation.Points.Insert(FInterpCurvePoint(InKey, FQuat::Identity, FQuat::Identity, FQuat::Identity, CIM_CurveAuto), Index); SplineCurves.Scale.Points.Insert(FInterpCurvePoint(InKey, FVector(1.0f), FVector::ZeroVector, FVector::ZeroVector, CIM_CurveAuto), Index); + USplineMetadata* Metadata = GetSplinePointsMetadata(); + if (Metadata) + { + Metadata->InsertPoint(InKey, Index); + } + NumPoints++; // Adjust subsequent points' input keys to make room for the value just added @@ -660,6 +698,12 @@ void USplineComponent::RemoveSplinePoint(int32 Index, bool bUpdateSpline) SplineCurves.Position.Points.RemoveAt(Index, 1, false); SplineCurves.Rotation.Points.RemoveAt(Index, 1, false); SplineCurves.Scale.Points.RemoveAt(Index, 1, false); + USplineMetadata* Metadata = GetSplinePointsMetadata(); + if (Metadata) + { + Metadata->RemovePoint(Index); + } + NumPoints--; // Adjust all following spline point input keys to close the gap left by the removed point @@ -690,6 +734,12 @@ void USplineComponent::SetSplinePoints(const TArray& Points, ESplineCoo SplineCurves.Rotation.Points.Reset(NumPoints); SplineCurves.Scale.Points.Reset(NumPoints); + USplineMetadata* Metadata = GetSplinePointsMetadata(); + if (Metadata) + { + Metadata->Reset(NumPoints); + } + float InputKey = 0.0f; for (const auto& Point : Points) { @@ -700,6 +750,11 @@ void USplineComponent::SetSplinePoints(const TArray& Points, ESplineCoo SplineCurves.Rotation.Points.Emplace(InputKey, FQuat::Identity, FQuat::Identity, FQuat::Identity, CIM_CurveAuto); SplineCurves.Scale.Points.Emplace(InputKey, FVector(1.0f), FVector::ZeroVector, FVector::ZeroVector, CIM_CurveAuto); + if (Metadata) + { + Metadata->AddPoint(InputKey); + } + InputKey += 1.0f; } @@ -1356,6 +1411,32 @@ FTransform USplineComponent::FindTransformClosestToWorldLocation(const FVector& return GetTransformAtSplineInputKey(Param, CoordinateSpace, bUseScale); } +template +T GetPropertyValueAtSplinePoint(const USplineMetadata* Metadata, int32 Index, FName PropertyName) +{ + if (Metadata) + { + if (UProperty* Property = Metadata->GetClass()->FindPropertyByName(PropertyName)) + { + const FInterpCurve* Curve = Property->ContainerPtrToValuePtr>(Metadata); + const TArray>& Points = Curve->Points; + const int32 NumPoints = Points.Num(); + if (NumPoints > 0) + { + const int32 ClampedIndex = FMath::Clamp(Index, 0, NumPoints - 1); + return Points[ClampedIndex].OutVal; + } + } + } + + return T(); +} + +float USplineComponent::GetFloatPropertyAtSplinePoint(int32 Index, FName PropertyName) const +{ + return GetPropertyValueAtSplinePoint(GetSplinePointsMetadata(), Index, PropertyName); +} + #if !UE_BUILD_SHIPPING FPrimitiveSceneProxy* USplineComponent::CreateSceneProxy() { diff --git a/Engine/Source/Runtime/Engine/Private/Components/TextRenderComponent.cpp b/Engine/Source/Runtime/Engine/Private/Components/TextRenderComponent.cpp index 73b1cf0c313f..3b3dbb7de819 100644 --- a/Engine/Source/Runtime/Engine/Private/Components/TextRenderComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/Components/TextRenderComponent.cpp @@ -465,6 +465,11 @@ public: } } + virtual FString GetReferencerName() const override + { + return TEXT("FTextRenderComponentMIDCache"); + } + private: /** Key identifying an array of MIDs */ struct FKey diff --git a/Engine/Source/Runtime/Engine/Private/CompositeDataTable.cpp b/Engine/Source/Runtime/Engine/Private/CompositeDataTable.cpp index f8b6b1cc36d1..fe8899ca1e97 100644 --- a/Engine/Source/Runtime/Engine/Private/CompositeDataTable.cpp +++ b/Engine/Source/Runtime/Engine/Private/CompositeDataTable.cpp @@ -52,22 +52,25 @@ UCompositeDataTable::ERowState UCompositeDataTable::GetRowState(FName RowName) c } #endif -void UCompositeDataTable::UpdateCachedRowMap() +void UCompositeDataTable::UpdateCachedRowMap(bool bWarnOnInvalidChildren) { bool bLeaveEmpty = false; // Throw up an error message and stop if any loops are found if (const UCompositeDataTable* LoopTable = FindLoops(TArray())) { - const FText ErrorMsg = FText::Format(LOCTEXT("FoundLoopError", "Cyclic dependency found. Table {0} depends on itself. Please fix your data"), FText::FromString(LoopTable->GetPathName())); + if (bWarnOnInvalidChildren) + { + const FText ErrorMsg = FText::Format(LOCTEXT("FoundLoopError", "Cyclic dependency found. Table {0} depends on itself. Please fix your data"), FText::FromString(LoopTable->GetPathName())); #if WITH_EDITOR - if (!bIsLoading) - { - FMessageDialog::Open(EAppMsgType::Ok, ErrorMsg); - } - else + if (!bIsLoading) + { + FMessageDialog::Open(EAppMsgType::Ok, ErrorMsg); + } + else #endif - { - UE_LOG(LogDataTable, Warning, TEXT("%s"), *ErrorMsg.ToString()); + { + UE_LOG(LogDataTable, Warning, TEXT("%s"), *ErrorMsg.ToString()); + } } bLeaveEmpty = true; @@ -95,10 +98,13 @@ void UCompositeDataTable::UpdateCachedRowMap() } if (ParentTable->RowStruct != RowStruct) { - bParentsHaveDifferentRowStruct = true; - FString CompositeRowStructName = RowStruct ? RowStruct->GetName() : "Missing row struct"; - FString ParentRowStructName = ParentTable->RowStruct ? ParentTable->RowStruct->GetName() : "Missing row struct"; - UE_LOG(LogDataTable, Error, TEXT("Composite tables must have the same row struct as their parent tables. Composite Table: %s, Composite Row Struct: %s, Parent Table: %s, Parent Row Struct: %s."), *GetName(), *CompositeRowStructName, *ParentTable->GetName(), *ParentRowStructName); + if (bWarnOnInvalidChildren) + { + bParentsHaveDifferentRowStruct = true; + FString CompositeRowStructName = RowStruct ? RowStruct->GetName() : "Missing row struct"; + FString ParentRowStructName = ParentTable->RowStruct ? ParentTable->RowStruct->GetName() : "Missing row struct"; + UE_LOG(LogDataTable, Error, TEXT("Composite tables must have the same row struct as their parent tables. Composite Table: %s, Composite Row Struct: %s, Parent Table: %s, Parent Row Struct: %s."), *GetName(), *CompositeRowStructName, *ParentTable->GetName(), *ParentRowStructName); + } continue; } @@ -245,14 +251,14 @@ void UCompositeDataTable::PostEditChangeProperty(FPropertyChangedEvent& Property if (PropertyName == Name_ParentTables) { - OnParentTablesUpdated(); + OnParentTablesUpdated(PropertyChangedEvent.ChangeType); } Super::PostEditChangeProperty(PropertyChangedEvent); } #endif // WITH_EDITOR -void UCompositeDataTable::OnParentTablesUpdated() +void UCompositeDataTable::OnParentTablesUpdated(EPropertyChangeType::Type ChangeType) { for (UDataTable* Table : OldParentTables) { @@ -262,13 +268,13 @@ void UCompositeDataTable::OnParentTablesUpdated() } } - UpdateCachedRowMap(); + UpdateCachedRowMap(ChangeType == EPropertyChangeType::ValueSet || ChangeType == EPropertyChangeType::Duplicate); for (UDataTable* Table : ParentTables) { if (Table && OldParentTables.Find(Table) == INDEX_NONE) { - Table->OnDataTableChanged().AddUObject(this, &UCompositeDataTable::UpdateCachedRowMap); + Table->OnDataTableChanged().AddUObject(this, &UCompositeDataTable::UpdateCachedRowMap, true); } } diff --git a/Engine/Source/Runtime/Engine/Private/CoreSettings.cpp b/Engine/Source/Runtime/Engine/Private/CoreSettings.cpp index 37da948e249e..b2e7ba83a632 100644 --- a/Engine/Source/Runtime/Engine/Private/CoreSettings.cpp +++ b/Engine/Source/Runtime/Engine/Private/CoreSettings.cpp @@ -169,6 +169,7 @@ UGarbageCollectionSettings::UGarbageCollectionSettings() FlushStreamingOnGC = false; AllowParallelGC = true; IncrementalBeginDestroyEnabled = true; + MultithreadedDestructionEnabled = true; NumRetriesBeforeForcingGC = 0; MaxObjectsNotConsideredByGC = 0; SizeOfPermanentObjectPool = 0; diff --git a/Engine/Source/Runtime/Engine/Private/Curves/CurveLinearColorAtlas.cpp b/Engine/Source/Runtime/Engine/Private/Curves/CurveLinearColorAtlas.cpp index a5bbddc9e370..b87bd33cd86f 100644 --- a/Engine/Source/Runtime/Engine/Private/Curves/CurveLinearColorAtlas.cpp +++ b/Engine/Source/Runtime/Engine/Private/Curves/CurveLinearColorAtlas.cpp @@ -13,9 +13,9 @@ UCurveLinearColorAtlas::UCurveLinearColorAtlas(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { -#if WITH_EDITOR TextureSize = 256; GradientPixelSize = 1; +#if WITH_EDITORONLY_DATA bHasAnyDirtyTextures = false; bShowDebugColorsForNullGradients = false; SizeXY = { (float)TextureSize, (float)GradientPixelSize }; @@ -209,4 +209,4 @@ bool UCurveLinearColorAtlas::GetCurvePosition(UCurveLinearColor* InCurve, float& return true; } return false; -} \ No newline at end of file +} diff --git a/Engine/Source/Runtime/Engine/Private/DataReplication.cpp b/Engine/Source/Runtime/Engine/Private/DataReplication.cpp index 3bb30faca2a8..b7e8b67a1c52 100644 --- a/Engine/Source/Runtime/Engine/Private/DataReplication.cpp +++ b/Engine/Source/Runtime/Engine/Private/DataReplication.cpp @@ -205,41 +205,18 @@ public: static bool SendCustomDeltaProperty( const FRepLayout& RepLayout, FNetDeltaSerializeInfo& Params, - uint16 CustomDeltaProperty) + uint16 CustomDeltaIndex) { - return RepLayout.SendCustomDeltaProperty(Params, CustomDeltaProperty); + return RepLayout.SendCustomDeltaProperty(Params, CustomDeltaIndex); } static bool ReceiveCustomDeltaProperty( const FRepLayout& RepLayout, + FReceivingRepState* ReceivingRepState, FNetDeltaSerializeInfo& Params, - UStructProperty* ReplicatedProp, - uint32& StaticArrayIndex, - int32& Offset) + UStructProperty* ReplicatedProp) { - return RepLayout.ReceiveCustomDeltaProperty(Params, ReplicatedProp, StaticArrayIndex, Offset); - } - - static void GatherGuidReferencesForCustomDeltaProperties(const FRepLayout& RepLayout, FNetDeltaSerializeInfo& Params) - { - RepLayout.GatherGuidReferencesForCustomDeltaProperties(Params); - } - - static bool MoveMappedObjectToUnmappedForCustomDeltaProperties( - const FRepLayout& RepLayout, - FNetDeltaSerializeInfo& Params, - TMap& UnmappedCustomProperties) - { - return RepLayout.MoveMappedObjectToUnmappedForCustomDeltaProperties(Params, UnmappedCustomProperties); - } - - static void UpdateUnmappedObjectsForCustomDeltaProperties( - const FRepLayout& RepLayout, - FNetDeltaSerializeInfo& Params, - TArray>& CompletelyMappedProperties, - TArray>& UpdatedProperties) - { - RepLayout.UpdateUnmappedObjectsForCustomDeltaProperties(Params, CompletelyMappedProperties, UpdatedProperties); + return RepLayout.ReceiveCustomDeltaProperty(ReceivingRepState, Params, ReplicatedProp); } static void PreSendCustomDeltaProperties( @@ -262,6 +239,33 @@ public: RepLayout.PostSendCustomDeltaProperties(Object, Connection, ChangelistMgr, CustomDeltaStates); } + static uint16 GetNumLifetimeCustomDeltaProperties(const FRepLayout& RepLayout) + { + return RepLayout.GetNumLifetimeCustomDeltaProperties(); + } + + static UProperty* GetLifetimeCustomDeltaProperty(const FRepLayout& RepLayout, const uint16 CustomDeltaPropertyIndex) + { + return RepLayout.GetLifetimeCustomDeltaProperty(CustomDeltaPropertyIndex); + } + + static void UpdateChangelistMgr( + const FRepLayout& RepLayout, + FSendingRepState* RESTRICT RepState, + FReplicationChangelistMgr& InChangelistMgr, + const UObject* InObject, + const uint32 ReplicationFrame, + const FReplicationFlags& RepFlags, + const bool bForceCompare) + { + RepLayout.UpdateChangelistMgr(RepState, InChangelistMgr, InObject, ReplicationFrame, RepFlags, bForceCompare); + } + + static const ELifetimeCondition GetLifetimeCustomDeltaPropertyCondition(const FRepLayout& RepLayout, const uint16 CustomDeltaPropertyIndex) + { + return RepLayout.GetLifetimeCustomDeltaPropertyCondition(CustomDeltaPropertyIndex); + } + private: UNetDriver* Driver; @@ -271,16 +275,17 @@ private: PRAGMA_ENABLE_DEPRECATION_WARNINGS PRAGMA_DISABLE_DEPRECATION_WARNINGS -FObjectReplicator::FObjectReplicator() : - ObjectClass(nullptr), - ObjectPtr(nullptr), - bLastUpdateEmpty(false), - bOpenAckCalled(false), - bForceUpdateUnmapped(false), - Connection(nullptr), - OwningChannel(nullptr), - RepState(nullptr), - RemoteFunctions(nullptr) +FObjectReplicator::FObjectReplicator() + : bLastUpdateEmpty(false) + , bOpenAckCalled(false) + , bForceUpdateUnmapped(false) + , bHasReplicatedProperties(false) + , bSupportsFastArrayDelta(false) + , ObjectClass(nullptr) + , ObjectPtr(nullptr) + , Connection(nullptr) + , OwningChannel(nullptr) + , RemoteFunctions(nullptr) { } @@ -291,9 +296,16 @@ FObjectReplicator::~FObjectReplicator() PRAGMA_ENABLE_DEPRECATION_WARNINGS PRAGMA_DISABLE_DEPRECATION_WARNINGS -bool FObjectReplicator::SerializeCustomDeltaProperty( UNetConnection * Connection, void* Src, UProperty * Property, uint32 ArrayIndex, FNetBitWriter & OutBunch, TSharedPtr &NewFullState, TSharedPtr & OldState ) +bool FObjectReplicator::SerializeCustomDeltaProperty( + UNetConnection* Connection, + void* Src, + UProperty* Property, + uint32 ArrayIndex, + FNetBitWriter& OutBunch, + TSharedPtr& NewFullState, + TSharedPtr& OldState) { - check( NewFullState.IsValid() == false ); // NewState is passed in as NULL and instantiated within this function if necessary + check( NewFullState.IsValid() == false ); // NewState is passed in as nullptr and instantiated within this function if necessary SCOPE_CYCLE_COUNTER( STAT_NetSerializeItemDeltaTime ); @@ -313,6 +325,7 @@ bool FObjectReplicator::SerializeCustomDeltaProperty( UNetConnection * Connectio Parms.Data = Property->ContainerPtrToValuePtr(Src, ArrayIndex); Parms.Object = reinterpret_cast(Src); Parms.Connection = Connection; + Parms.bInternalAck = Connection->InternalAck; Parms.Writer = &OutBunch; Parms.Map = Connection->PackageMap; Parms.OldState = OldState.Get(); @@ -340,9 +353,9 @@ bool FObjectReplicator::SendCustomDeltaProperty(UObject* InObject, UProperty* Pr return SendCustomDeltaProperty(InObject, Property->RepIndex + ArrayIndex, OutBunch, NewFullState, OldState); } -bool FObjectReplicator::SendCustomDeltaProperty(UObject* InObject, uint16 CustomDeltaProperty, FNetBitWriter& OutBunch, TSharedPtr& NewFullState, TSharedPtr& OldState) +bool FObjectReplicator::SendCustomDeltaProperty(UObject* InObject, uint16 CustomDeltaIndex, FNetBitWriter& OutBunch, TSharedPtr& NewFullState, TSharedPtr& OldState) { - check(!NewFullState.IsValid()); // NewState is passed in as NULL and instantiated within this function if necessary + check(!NewFullState.IsValid()); // NewState is passed in as nullptr and instantiated within this function if necessary check(RepLayout); SCOPE_CYCLE_COUNTER(STAT_NetSerializeItemDeltaTime); @@ -359,11 +372,12 @@ bool FObjectReplicator::SendCustomDeltaProperty(UObject* InObject, uint16 Custom Parms.NewState = &NewFullState; Parms.NetSerializeCB = &NetSerializeCB; Parms.bIsWritingOnClient = ConnectionDriver && ConnectionDriver->GetWorld() && ConnectionDriver->GetWorld()->IsRecordingClientReplay(); - Parms.PropertyRepIndex = CustomDeltaProperty; + Parms.CustomDeltaIndex = CustomDeltaIndex; Parms.bSupportsFastArrayDeltaStructSerialization = bSupportsFastArrayDelta; Parms.Connection = Connection; + Parms.bInternalAck = Connection->InternalAck; - return FNetSerializeCB::SendCustomDeltaProperty(*RepLayout, Parms, CustomDeltaProperty); + return FNetSerializeCB::SendCustomDeltaProperty(*RepLayout, Parms, CustomDeltaIndex); } /** @@ -405,27 +419,27 @@ void FObjectReplicator::InitRecentProperties(uint8* Source) bSupportsFastArrayDelta = !!GSupportsFastArrayDelta; - // TODO: CDOCustomDeltaState, CheckpointCustomDeltaState, RecentCustomDeltaState, and Retirement could all be moved into SendingRepState. - // This would allow us to skip allocating these containers for receivers completely. - // This logic would also be easily moved to FRepLayout::CreateRepState. - - // We should just update this method to accept an object pointer. - UObject* UseObject = reinterpret_cast(Source); - - // Init custom delta property state - for (const uint16 CustomDeltaProperty : LocalRepLayout.GetLifetimeCustomDeltaProperties()) + if (FSendingRepState* SendingRepState = RepState->GetSendingRepState()) { - FOutBunch DeltaState(Connection->PackageMap); - TSharedPtr& NewState = RecentCustomDeltaState.FindOrAdd(CustomDeltaProperty); - NewState.Reset(); + // We should just update this method to accept an object pointer. + UObject* UseObject = reinterpret_cast(Source); - TSharedPtr OldState; + // Init custom delta property state + const uint16 NumLifetimeCustomDeltaProperties = FNetSerializeCB::GetNumLifetimeCustomDeltaProperties(LocalRepLayout); + for (uint16 CustomDeltaProperty = 0; CustomDeltaProperty < NumLifetimeCustomDeltaProperties; ++CustomDeltaProperty) + { + FOutBunch DeltaState(Connection->PackageMap); + TSharedPtr& NewState = SendingRepState->RecentCustomDeltaState.FindOrAdd(CustomDeltaProperty); + NewState.Reset(); - SendCustomDeltaProperty(UseObject, CustomDeltaProperty, DeltaState, NewState, OldState); + TSharedPtr OldState; - // Store the initial delta state in case we need it for when we're asked to resend all data since channel was first opened (bResendAllDataSinceOpen) - CDOCustomDeltaState.Add(CustomDeltaProperty, NewState); - CheckpointCustomDeltaState.Add(CustomDeltaProperty, NewState); + SendCustomDeltaProperty(UseObject, CustomDeltaProperty, DeltaState, NewState, OldState); + + // Store the initial delta state in case we need it for when we're asked to resend all data since channel was first opened (bResendAllDataSinceOpen) + SendingRepState->CDOCustomDeltaState.Add(CustomDeltaProperty, NewState); + SendingRepState->CheckpointCustomDeltaState.Add(CustomDeltaProperty, NewState); + } } } @@ -471,32 +485,32 @@ bool FObjectReplicator::ValidateAgainstState( const UObject* ObjectState ) void FObjectReplicator::InitWithObject( UObject* InObject, UNetConnection * InConnection, bool bUseDefaultState ) { - check( GetObject() == NULL ); - check( ObjectClass == NULL ); + check( GetObject() == nullptr ); + check( ObjectClass == nullptr ); check( bLastUpdateEmpty == false ); - check( Connection == NULL ); - check( OwningChannel == NULL ); + check( Connection == nullptr ); + check( OwningChannel == nullptr ); check( !RepState.IsValid() ); - check( RemoteFunctions == NULL ); + check( RemoteFunctions == nullptr ); check( !RepLayout.IsValid() ); SetObject( InObject ); - if ( GetObject() == NULL ) + if ( GetObject() == nullptr ) { - // This may seem weird that we're checking for NULL, but the SetObject above will wrap this object with TWeakObjectPtr - // If the object is pending kill, it will switch to NULL, we're just making sure we handle this invalid edge case - UE_LOG(LogRep, Error, TEXT("InitWithObject: Object == NULL")); + // This may seem weird that we're checking for nullptr, but the SetObject above will wrap this object with TWeakObjectPtr + // If the object is pending kill, it will switch to nullptr, we're just making sure we handle this invalid edge case + UE_LOG(LogRep, Error, TEXT("InitWithObject: Object == nullptr")); return; } ObjectClass = InObject->GetClass(); Connection = InConnection; - RemoteFunctions = NULL; + RemoteFunctions = nullptr; bHasReplicatedProperties = false; bOpenAckCalled = false; - RepState = NULL; - OwningChannel = NULL; // Initially NULL until StartReplicating is called + RepState = nullptr; + OwningChannel = nullptr; // Initially nullptr until StartReplicating is called TrackedGuidMemoryBytes = 0; RepLayout = Connection->Driver->GetObjectClassRepLayout( ObjectClass ); @@ -506,16 +520,12 @@ void FObjectReplicator::InitWithObject( UObject* InObject, UNetConnection * InCo InitRecentProperties( Source ); -PRAGMA_DISABLE_DEPRECATION_WARNINGS - RepLayout->GetLifetimeCustomDeltaProperties( LifetimeCustomDeltaProperties, LifetimeCustomDeltaPropertyConditions ); -PRAGMA_ENABLE_DEPRECATION_WARNINGS - Connection->Driver->AllOwnedReplicators.Add(this); } void FObjectReplicator::CleanUp() { - if ( OwningChannel != NULL ) + if ( OwningChannel != nullptr ) { StopReplicating( OwningChannel ); // We shouldn't get here, but just in case } @@ -549,23 +559,14 @@ void FObjectReplicator::CleanUp() ReferencedGuids.Empty(); TrackedGuidMemoryBytes = 0; - SetObject( NULL ); + SetObject( nullptr ); - ObjectClass = NULL; - Connection = NULL; - RemoteFunctions = NULL; + ObjectClass = nullptr; + Connection = nullptr; + RemoteFunctions = nullptr; bHasReplicatedProperties = false; bOpenAckCalled = false; -PRAGMA_DISABLE_DEPRECATION_WARNINGS - // Cleanup custom delta state - RecentCustomDeltaState.Empty(); - CheckpointCustomDeltaState.Empty(); - - LifetimeCustomDeltaProperties.Empty(); - LifetimeCustomDeltaPropertyConditions.Empty(); -PRAGMA_ENABLE_DEPRECATION_WARNINGS - RepState = nullptr; CheckpointRepState = nullptr; } @@ -607,9 +608,13 @@ void FObjectReplicator::StartReplicating(class UActorChannel * InActorChannel) if (ConnectionNetDriver->IsServer() || ConnectionNetDriver->MaySendProperties()) { - // Allocate retirement list. - // SetNum now constructs, so this is safe - Retirement.SetNum(ObjectClass->ClassReps.Num()); + if (FSendingRepState* SendingRepState = RepState.IsValid() ? RepState->GetSendingRepState() : nullptr) + { + // Allocate retirement list. + // SetNum now constructs, so this is safe + + SendingRepState->Retirement.SetNum(ObjectClass->ClassReps.Num()); + } const UWorld* const World = ConnectionNetDriver->GetWorld(); UNetDriver* const WorldNetDriver = World ? World->GetNetDriver() : nullptr; @@ -626,7 +631,7 @@ void FObjectReplicator::StartReplicating(class UActorChannel * InActorChannel) } } -static FORCEINLINE void ValidateRetirementHistory( const FPropertyRetirement & Retire, const UObject* Object ) +void ValidateRetirementHistory(const FPropertyRetirement & Retire, const UObject* Object) { #if !UE_BUILD_SHIPPING checkf( Retire.SanityTag == FPropertyRetirement::ExpectedSanityTag, TEXT( "Invalid Retire.SanityTag. Object: %s" ), *GetFullNameSafe(Object) ); @@ -635,7 +640,7 @@ static FORCEINLINE void ValidateRetirementHistory( const FPropertyRetirement & R FPacketIdRange LastRange; - while ( Rec != NULL ) + while ( Rec != nullptr ) { checkf( Rec->SanityTag == FPropertyRetirement::ExpectedSanityTag, TEXT( "Invalid Rec->SanityTag. Object: %s" ), *GetFullNameSafe(Object) ); checkf( Rec->OutPacketIdRange.Last >= Rec->OutPacketIdRange.First, TEXT( "Invalid packet id range (Last < First). Object: %s" ), *GetFullNameSafe(Object) ); @@ -650,38 +655,43 @@ static FORCEINLINE void ValidateRetirementHistory( const FPropertyRetirement & R void FObjectReplicator::StopReplicating( class UActorChannel * InActorChannel ) { - check( OwningChannel != NULL ); - check( OwningChannel->Connection == Connection ); - check( OwningChannel == InActorChannel ); + check(OwningChannel != nullptr); + check(OwningChannel->Connection == Connection); + check(OwningChannel == InActorChannel); - OwningChannel = NULL; + OwningChannel = nullptr; const UObject* Object = GetObject(); - // Cleanup retirement records - for ( int32 i = Retirement.Num() - 1; i >= 0; i-- ) + if (FSendingRepState* SendingRepState = RepState.IsValid() ? RepState->GetSendingRepState() : nullptr) { - ValidateRetirementHistory( Retirement[i], Object ); - - FPropertyRetirement * Rec = Retirement[i].Next; - Retirement[i].Next = NULL; - - // We dont need to explicitly delete Retirement[i], but anything in the Next chain needs to be. - while ( Rec != NULL ) + // Cleanup retirement records + for (int32 i = SendingRepState->Retirement.Num() - 1; i >= 0; i--) { - FPropertyRetirement * Next = Rec->Next; - delete Rec; - Rec = Next; + FPropertyRetirement& Retirement = SendingRepState->Retirement[i]; + ValidateRetirementHistory(Retirement, Object); + + FPropertyRetirement* Rec = Retirement.Next; + Retirement.Next = nullptr; + + // We dont need to explicitly delete Retirement, but anything in the Next chain needs to be. + while (Rec != nullptr) + { + FPropertyRetirement* Next = Rec->Next; + delete Rec; + Rec = Next; + } } + + SendingRepState->Retirement.Empty(); } - Retirement.Empty(); PendingLocalRPCs.Empty(); - if ( RemoteFunctions != NULL ) + if (RemoteFunctions != nullptr) { delete RemoteFunctions; - RemoteFunctions = NULL; + RemoteFunctions = nullptr; } } @@ -783,75 +793,88 @@ void FObjectReplicator::ReceivedNak( int32 NakPacketId ) { const UObject* Object = GetObject(); - if ( Object == NULL ) + if (Object == nullptr) { - UE_LOG(LogNet, Verbose, TEXT("ReceivedNak: Object == NULL")); + UE_LOG(LogNet, Verbose, TEXT("FObjectReplicator::ReceivedNak: Object == nullptr")); return; } - - if ( Object != NULL && ObjectClass != NULL ) + else if (ObjectClass == nullptr) { - RepLayout->ReceivedNak( RepState.Get(), NakPacketId ); - - for ( int32 i = Retirement.Num() - 1; i >= 0; i-- ) + UE_LOG(LogNet, Verbose, TEXT("FObjectReplicator::ReceivedNak: ObjectClass == nullptr")); + } + else if (ERepLayoutState::Normal == RepLayout->GetRepLayoutState()) + { + if (FSendingRepState* SendingRepState = RepState.IsValid() ? RepState->GetSendingRepState() : nullptr) { - ValidateRetirementHistory( Retirement[i], Object ); - - // If this is a dynamic array property, we have to look through the list of retirement records to see if we need to reset the base state - FPropertyRetirement * Rec = Retirement[i].Next; // Retirement[i] is head and not actually used in this case - while ( Rec != NULL ) + // Go over properties tracked with histories, and mark them as needing to be resent. + for (int32 i = SendingRepState->HistoryStart; i < SendingRepState->HistoryEnd; ++i) { - if ( NakPacketId > Rec->OutPacketIdRange.Last ) + const int32 HistoryIndex = i % FSendingRepState::MAX_CHANGE_HISTORY; + + FRepChangedHistory& HistoryItem = SendingRepState->ChangeHistory[HistoryIndex]; + + if (!HistoryItem.Resend && HistoryItem.OutPacketIdRange.InRange(NakPacketId)) { - // We can assume this means this record's packet was ack'd, so we can get rid of the old state - check( Retirement[i].Next == Rec ); - Retirement[i].Next = Rec->Next; - delete Rec; - Rec = Retirement[i].Next; - continue; + check(HistoryItem.Changed.Num() > 0); + HistoryItem.Resend = true; + ++SendingRepState->NumNaks; } - else if ( NakPacketId >= Rec->OutPacketIdRange.First && NakPacketId <= Rec->OutPacketIdRange.Last ) - { - UE_LOG(LogNet, Verbose, TEXT("Restoring Previous Base State of dynamic property. Channel: %s, NakId: %d, First: %d, Last: %d, Address: %s)"), *OwningChannel->Describe(), NakPacketId, Rec->OutPacketIdRange.First, Rec->OutPacketIdRange.Last, *Connection->LowLevelGetRemoteAddress(true)); - - // The Nack'd packet did update this property, so we need to replace the buffer in RecentDynamic - // with the buffer we used to create this update (which was dropped), so that the update will be recreated on the next replicate actor - if ( Rec->DynamicState.IsValid() ) - { - TSharedPtr & RecentState = RecentCustomDeltaState.FindChecked( i ); - - RecentState.Reset(); - RecentState = Rec->DynamicState; - } - - // We can get rid of the rest of the saved off base states since we will be regenerating these updates on the next replicate actor - while ( Rec != NULL ) - { - FPropertyRetirement * DeleteNext = Rec->Next; - delete Rec; - Rec = DeleteNext; - } - - // Finished - Retirement[i].Next = NULL; - break; - } - Rec = Rec->Next; } - - ValidateRetirementHistory( Retirement[i], Object ); + + // Go over our Custom Delta Properties and update their retirements + for (int32 i = SendingRepState->Retirement.Num() - 1; i >= 0; i--) + { + FPropertyRetirement& Retirement = SendingRepState->Retirement[i]; + ValidateRetirementHistory(Retirement, Object); + + // If this is a dynamic array property, we have to look through the list of retirement records to see if we need to reset the base state + FPropertyRetirement* Rec = Retirement.Next; // Retirement[i] is head and not actually used in this case + while (Rec != nullptr) + { + if (NakPacketId > Rec->OutPacketIdRange.Last) + { + // We can assume this means this record's packet was ack'd, so we can get rid of the old state + check(Retirement.Next == Rec); + Retirement.Next = Rec->Next; + delete Rec; + Rec = Retirement.Next; + continue; + } + else if (NakPacketId >= Rec->OutPacketIdRange.First && NakPacketId <= Rec->OutPacketIdRange.Last) + { + UE_LOG(LogNet, Verbose, TEXT("Restoring Previous Base State of dynamic property. Channel: %s, NakId: %d, First: %d, Last: %d, Address: %s)"), *OwningChannel->Describe(), NakPacketId, Rec->OutPacketIdRange.First, Rec->OutPacketIdRange.Last, *Connection->LowLevelGetRemoteAddress(true)); + + // The Nack'd packet did update this property, so we need to replace the buffer in RecentDynamic + // with the buffer we used to create this update (which was dropped), so that the update will be recreated on the next replicate actor + if (Rec->DynamicState.IsValid()) + { + TSharedPtr & RecentState = SendingRepState->RecentCustomDeltaState.FindChecked(i); + + RecentState.Reset(); + RecentState = Rec->DynamicState; + } + + // We can get rid of the rest of the saved off base states since we will be regenerating these updates on the next replicate actor + while (Rec != nullptr) + { + FPropertyRetirement * DeleteNext = Rec->Next; + delete Rec; + Rec = DeleteNext; + } + + // Finished + Retirement.Next = nullptr; + break; + } + Rec = Rec->Next; + } + + ValidateRetirementHistory(Retirement, Object); + } } } } -#define HANDLE_INCOMPATIBLE_PROP \ - if ( bIsServer ) \ - { \ - return false; \ - } \ - FieldCache->bIncompatible = true; \ - continue; \ - bool FObjectReplicator::ReceivedBunch(FNetBitReader& Bunch, const FReplicationFlags& RepFlags, const bool bHasRepLayout, bool& bOutHasUnmapped) { check(RepLayout); @@ -860,7 +883,7 @@ bool FObjectReplicator::ReceivedBunch(FNetBitReader& Bunch, const FReplicationFl if (!Object) { - UE_LOG(LogNet, Verbose, TEXT("ReceivedBunch: Object == NULL")); + UE_LOG(LogNet, Verbose, TEXT("ReceivedBunch: Object == nullptr")); return false; } @@ -874,12 +897,13 @@ bool FObjectReplicator::ReceivedBunch(FNetBitReader& Bunch, const FReplicationFl if (!ClassCache) { - UE_LOG(LogNet, Error, TEXT("ReceivedBunch: ClassCache == NULL: %s"), *Object->GetFullName()); + UE_LOG(LogNet, Error, TEXT("ReceivedBunch: ClassCache == nullptr: %s"), *Object->GetFullName()); return false; } const FRepLayout& LocalRepLayout = *RepLayout; bool bGuidsChanged = false; + FReceivingRepState* ReceivingRepState = RepState->GetReceivingRepState(); // Handle replayout properties if (bHasRepLayout) @@ -1010,33 +1034,23 @@ bool FObjectReplicator::ReceivedBunch(FNetBitReader& Bunch, const FReplicationFl Parms.Reader = &Reader; Parms.NetSerializeCB = &NetSerializeCB; Parms.Connection = Connection; + Parms.bInternalAck = Connection->InternalAck; Parms.Object = Object; - uint32 StaticArrayIndex = 0; - int32 Offset = 0; - if (!FNetSerializeCB::ReceiveCustomDeltaProperty(LocalRepLayout, Parms, ReplicatedProp, StaticArrayIndex, Offset)) + if (!FNetSerializeCB::ReceiveCustomDeltaProperty(LocalRepLayout, ReceivingRepState, Parms, ReplicatedProp)) { - // RepLayout should have already logged the error. - HANDLE_INCOMPATIBLE_PROP; - } - else if (UNLIKELY(Reader.IsError())) - { - UE_LOG(LogNet, Error, TEXT("ReceivedBunch: NetDeltaSerialize - Reader.IsError() == true. Property: %s, Object: %s"), *Parms.DebugName, *Object->GetFullName()); - HANDLE_INCOMPATIBLE_PROP - } + // Should have already logged the error. + if (bIsServer) + { + return false; + } - else if (UNLIKELY(Reader.GetBitsLeft() != 0)) - { - UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: NetDeltaSerialize - Mismatch read. Property: %s, Object: %s" ), *Parms.DebugName, *Object->GetFullName() ); - HANDLE_INCOMPATIBLE_PROP + FieldCache->bIncompatible = true; + continue; } if (Parms.bOutHasMoreUnmapped) { -PRAGMA_DISABLE_DEPRECATION_WARNINGS - UnmappedCustomProperties.Add(Offset, ReplicatedProp); -PRAGMA_ENABLE_DEPRECATION_WARNINGS - bOutHasUnmapped = true; } @@ -1047,10 +1061,6 @@ PRAGMA_ENABLE_DEPRECATION_WARNINGS // Successfully received it. UE_LOG(LogRepTraffic, Log, TEXT(" %s - %s"), *Object->GetName(), *Parms.DebugName); - - // Notify the Object if this var is RepNotify - TArray MetaData; - QueuePropertyRepNotify(Object, ReplicatedProp, StaticArrayIndex, MetaData); } // Handle function call else if ( Cast< UFunction >( FieldCache->Field ) ) @@ -1265,11 +1275,6 @@ void FObjectReplicator::UpdateGuidToReplicatorMap() // Gather guids on rep layout if (RepState.IsValid()) - { - LocalRepLayout.GatherGuidReferences(RepState->GetReceivingRepState(), LocalReferencedGuids, LocalTrackedGuidMemoryBytes); - } - - if (UObject* Object = GetObject()) { FNetSerializeCB NetSerializeCB(Connection->Driver); @@ -1277,9 +1282,10 @@ void FObjectReplicator::UpdateGuidToReplicatorMap() Parms.NetSerializeCB = &NetSerializeCB; Parms.GatherGuidReferences = &LocalReferencedGuids; Parms.TrackedGuidMemoryBytes = &LocalTrackedGuidMemoryBytes; - Parms.Object = Object; + Parms.Object = GetObject(); + Parms.bInternalAck = Connection->InternalAck; - FNetSerializeCB::GatherGuidReferencesForCustomDeltaProperties(LocalRepLayout, Parms); + LocalRepLayout.GatherGuidReferences(RepState->GetReceivingRepState(), Parms, LocalReferencedGuids, LocalTrackedGuidMemoryBytes); } // Gather RPC guids @@ -1331,38 +1337,28 @@ bool FObjectReplicator::MoveMappedObjectToUnmapped(const FNetworkGUID& GUID) check(RepLayout); const FRepLayout& LocalRepLayout = *RepLayout; - bool bFound = LocalRepLayout.MoveMappedObjectToUnmapped(RepState->GetReceivingRepState(), GUID); + FNetSerializeCB NetSerializeCB(Connection->Driver); + FNetDeltaSerializeInfo Parms; + Parms.Connection = Connection; + Parms.bInternalAck = Connection->InternalAck; + Parms.Map = Connection->PackageMap; + Parms.Object = GetObject(); + Parms.NetSerializeCB = &NetSerializeCB; + Parms.MoveGuidToUnmapped = &GUID; - if (UObject* Object = GetObject()) - { - FNetSerializeCB NetSerializeCB(Connection->Driver); - - FNetDeltaSerializeInfo Parms; - Parms.Connection = Connection; - Parms.Map = Connection->PackageMap; - Parms.Object = Object; - Parms.NetSerializeCB = &NetSerializeCB; - Parms.MoveGuidToUnmapped = &GUID; - Parms.Object = Object; - -PRAGMA_DISABLE_DEPRECATION_WARNINGS - bFound |= FNetSerializeCB::MoveMappedObjectToUnmappedForCustomDeltaProperties(LocalRepLayout, Parms, UnmappedCustomProperties); -PRAGMA_ENABLE_DEPRECATION_WARNINGS - } - - return bFound; + return LocalRepLayout.MoveMappedObjectToUnmapped(RepState->GetReceivingRepState(), Parms, GUID); } void FObjectReplicator::PostReceivedBunch() { - if ( GetObject() == NULL ) + if ( GetObject() == nullptr ) { - UE_LOG(LogNet, Verbose, TEXT("PostReceivedBunch: Object == NULL")); + UE_LOG(LogNet, Verbose, TEXT("PostReceivedBunch: Object == nullptr")); return; } // Call PostNetReceive - const bool bIsServer = (OwningChannel->Connection->Driver->ServerConnection == NULL); + const bool bIsServer = (OwningChannel->Connection->Driver->ServerConnection == nullptr); if (!bIsServer && bHasReplicatedProperties) { PostNetReceive(); @@ -1373,21 +1369,24 @@ void FObjectReplicator::PostReceivedBunch() CallRepNotifies(true); } -static FORCEINLINE FPropertyRetirement ** UpdateAckedRetirements( FPropertyRetirement & Retire, const int32 OutAckPacketId, const UObject* Object ) +static FORCEINLINE FPropertyRetirement** UpdateAckedRetirements( + FPropertyRetirement& Retire, + const int32 OutAckPacketId, + const UObject* Object) { - ValidateRetirementHistory( Retire, Object ); + ValidateRetirementHistory(Retire, Object); - FPropertyRetirement ** Rec = &Retire.Next; // Note the first element is 'head' that we dont actually use + FPropertyRetirement** Rec = &Retire.Next; // Note the first element is 'head' that we don't actually use - while ( *Rec != NULL ) + while (*Rec != nullptr) { - if ( OutAckPacketId >= (*Rec)->OutPacketIdRange.Last ) + if (OutAckPacketId >= (*Rec)->OutPacketIdRange.Last) { UE_LOG(LogRepTraffic, Verbose, TEXT("Deleting Property Record (%d >= %d)"), OutAckPacketId, (*Rec)->OutPacketIdRange.Last); // They've ack'd this packet so we can ditch this record (easier to do it here than look for these every Ack) - FPropertyRetirement * ToDelete = *Rec; - check( Retire.Next == ToDelete ); // This should only be able to happen to the first record in the list + FPropertyRetirement* ToDelete = *Rec; + check(Retire.Next == ToDelete); // This should only be able to happen to the first record in the list Retire.Next = ToDelete->Next; Rec = &Retire.Next; @@ -1407,9 +1406,9 @@ void FObjectReplicator::ReplicateCustomDeltaProperties( FNetBitWriter & Bunch, F check(RepLayout); const FRepLayout& LocalRepLayout = *RepLayout; - const TArrayView LocalLifetimeCustomDeltaProperties = LocalRepLayout.GetLifetimeCustomDeltaProperties(); + const int32 NumLifetimeCustomDeltaProperties = FNetSerializeCB::GetNumLifetimeCustomDeltaProperties(LocalRepLayout); - if (!LocalLifetimeCustomDeltaProperties.Num()) + if (NumLifetimeCustomDeltaProperties <= 0) { // No custom properties return; @@ -1419,15 +1418,16 @@ void FObjectReplicator::ReplicateCustomDeltaProperties( FNetBitWriter & Bunch, F // the receiving end, and make things more consistent. UObject* Object = GetObject(); + FSendingRepState* SendingRepState = RepState->GetSendingRepState(); check(Object); check(OwningChannel); check(Connection == OwningChannel->Connection); TMap>& UsingCustomDeltaStates = - EResendAllDataState::None == Connection->ResendAllDataState ? RecentCustomDeltaState : - EResendAllDataState::SinceOpen == Connection->ResendAllDataState ? CDOCustomDeltaState : - CheckpointCustomDeltaState; + EResendAllDataState::None == Connection->ResendAllDataState ? SendingRepState->RecentCustomDeltaState : + EResendAllDataState::SinceOpen == Connection->ResendAllDataState ? SendingRepState->CDOCustomDeltaState : + SendingRepState->CheckpointCustomDeltaState; FNetSerializeCB::PreSendCustomDeltaProperties(LocalRepLayout, Object, Connection, *ChangelistMgr, UsingCustomDeltaStates); @@ -1445,16 +1445,9 @@ void FObjectReplicator::ReplicateCustomDeltaProperties( FNetBitWriter & Bunch, F FNetBitWriter TempBitWriter( Connection->PackageMap, 1024 ); // Replicate those properties. - for (const uint16 CustomDeltaProperty : LocalLifetimeCustomDeltaProperties) + for (uint16 CustomDeltaProperty = 0; CustomDeltaProperty < NumLifetimeCustomDeltaProperties; ++CustomDeltaProperty) { - // Get info. - FPropertyRetirement& Retire = Retirement[CustomDeltaProperty]; - -PRAGMA_DISABLE_DEPRECATION_WARNINGS - UProperty* It = LocalRepLayout.GetPropertyForRepIndex(CustomDeltaProperty); -PRAGMA_ENABLE_DEPRECATION_WARNINGS - - const ELifetimeCondition RepCondition = LocalRepLayout.GetPropertyLifetimeCondition(CustomDeltaProperty); + const ELifetimeCondition RepCondition = FNetSerializeCB::GetLifetimeCustomDeltaPropertyCondition(LocalRepLayout, CustomDeltaProperty); check(RepCondition >= 0 && RepCondition < COND_Max); @@ -1464,6 +1457,8 @@ PRAGMA_ENABLE_DEPRECATION_WARNINGS continue; } + UProperty* Property = FNetSerializeCB::GetLifetimeCustomDeltaProperty(LocalRepLayout, CustomDeltaProperty); + // If this is a dynamic array, we do the delta here TSharedPtr NewState; @@ -1473,7 +1468,7 @@ PRAGMA_ENABLE_DEPRECATION_WARNINGS { if (Connection->ResendAllDataState == EResendAllDataState::SinceCheckpoint) { - TSharedPtr& OldState = CheckpointCustomDeltaState.FindChecked(CustomDeltaProperty); + TSharedPtr& OldState = UsingCustomDeltaStates.FindChecked(CustomDeltaProperty); if (!SendCustomDeltaProperty(Object, CustomDeltaProperty, TempBitWriter, NewState, OldState)) { @@ -1487,7 +1482,7 @@ PRAGMA_ENABLE_DEPRECATION_WARNINGS { // If we are resending data since open, we don't want to affect the current state of channel/replication, so just do the minimum and send the data, and return // In this case, we'll send all of the properties since the CDO, so use the initial CDO delta state - TSharedPtr& OldState = CDOCustomDeltaState.FindChecked(CustomDeltaProperty); + TSharedPtr& OldState = UsingCustomDeltaStates.FindChecked(CustomDeltaProperty); if (!SendCustomDeltaProperty(Object, CustomDeltaProperty, TempBitWriter, NewState, OldState)) { @@ -1496,11 +1491,14 @@ PRAGMA_ENABLE_DEPRECATION_WARNINGS } // Write property header and payload to the bunch - WritePropertyHeaderAndPayload(Object, It, NetFieldExportGroup, Bunch, TempBitWriter); + WritePropertyHeaderAndPayload(Object, Property, NetFieldExportGroup, Bunch, TempBitWriter); continue; } + // Get info. + FPropertyRetirement& Retire = SendingRepState->Retirement[CustomDeltaProperty]; + // Update Retirement records with this new state so we can handle packet drops. // LastNext will be pointer to the last "Next" pointer in the list (so pointer to a pointer) FPropertyRetirement** LastNext = UpdateAckedRetirements(Retire, Connection->OutAckPacketId, Object); @@ -1510,7 +1508,7 @@ PRAGMA_ENABLE_DEPRECATION_WARNINGS ValidateRetirementHistory(Retire, Object); - TSharedPtr& OldState = RecentCustomDeltaState.FindOrAdd(CustomDeltaProperty); + TSharedPtr& OldState = UsingCustomDeltaStates.FindOrAdd(CustomDeltaProperty); //----------------------------------------- // Do delta serialization on dynamic properties @@ -1531,9 +1529,9 @@ PRAGMA_ENABLE_DEPRECATION_WARNINGS OldState = NewState; // Write property header and payload to the bunch - WritePropertyHeaderAndPayload(Object, It, NetFieldExportGroup, Bunch, TempBitWriter); + WritePropertyHeaderAndPayload(Object, Property, NetFieldExportGroup, Bunch, TempBitWriter); - NETWORK_PROFILER(GNetworkProfiler.TrackReplicateProperty(It, TempBitWriter.GetNumBits(), Connection)); + NETWORK_PROFILER(GNetworkProfiler.TrackReplicateProperty(Property, TempBitWriter.GetNumBits(), Connection)); } } @@ -1542,9 +1540,9 @@ bool FObjectReplicator::ReplicateProperties( FOutBunch & Bunch, FReplicationFlag { UObject* Object = GetObject(); - if ( Object == NULL ) + if ( Object == nullptr ) { - UE_LOG(LogRep, Verbose, TEXT("ReplicateProperties: Object == NULL")); + UE_LOG(LogRep, Verbose, TEXT("ReplicateProperties: Object == nullptr")); return false; } @@ -1570,7 +1568,7 @@ bool FObjectReplicator::ReplicateProperties( FOutBunch & Bunch, FReplicationFlag // Update change list (this will re-use work done by previous connections) FSendingRepState* SendingRepState = ((Connection->ResendAllDataState == EResendAllDataState::SinceCheckpoint) && CheckpointRepState.IsValid()) ? CheckpointRepState->GetSendingRepState() : RepState->GetSendingRepState(); - RepLayout->UpdateChangelistMgr(SendingRepState, *ChangelistMgr, Object, Connection->Driver->ReplicationFrame, RepFlags, OwningChannel->bForceCompareProperties); + FNetSerializeCB::UpdateChangelistMgr(*RepLayout, SendingRepState, *ChangelistMgr, Object, Connection->Driver->ReplicationFrame, RepFlags, OwningChannel->bForceCompareProperties); // Replicate properties in the layout const bool bHasRepLayout = RepLayout->ReplicateProperties(SendingRepState, ChangelistMgr->GetRepChangelistState(), (uint8*)Object, ObjectClass, OwningChannel, Writer, RepFlags); @@ -1603,7 +1601,7 @@ bool FObjectReplicator::ReplicateProperties( FOutBunch & Bunch, FReplicationFlag bLastUpdateEmpty = Writer.GetNumBits() == 0; // Replicate Queued (unreliable functions) - if ( RemoteFunctions != NULL && RemoteFunctions->GetNumBits() > 0 ) + if ( RemoteFunctions != nullptr && RemoteFunctions->GetNumBits() > 0 ) { if ( UNLIKELY(GNetRPCDebug == 1) ) { @@ -1632,15 +1630,18 @@ bool FObjectReplicator::ReplicateProperties( FOutBunch & Bunch, FReplicationFlag void FObjectReplicator::ForceRefreshUnreliableProperties() { - if ( GetObject() == NULL ) + if (GetObject() == nullptr) { - UE_LOG( LogRep, Verbose, TEXT( "ForceRefreshUnreliableProperties: Object == NULL" ) ); + UE_LOG(LogRep, Verbose, TEXT("ForceRefreshUnreliableProperties: Object == nullptr")); return; } - check( !bOpenAckCalled ); + check(!bOpenAckCalled); - RepLayout->OpenAcked( RepState->GetSendingRepState() ); + if (FSendingRepState* SendingRepState = RepState->GetSendingRepState()) + { + SendingRepState->bOpenAckedCalled = true; + } bOpenAckCalled = true; } @@ -1651,7 +1652,7 @@ void FObjectReplicator::PostSendBunch( FPacketIdRange & PacketRange, uint8 bReli if ( Object == nullptr ) { - UE_LOG(LogNet, Verbose, TEXT("PostSendBunch: Object == NULL")); + UE_LOG(LogNet, Verbose, TEXT("PostSendBunch: Object == nullptr")); return; } @@ -1662,49 +1663,67 @@ void FObjectReplicator::PostSendBunch( FPacketIdRange & PacketRange, uint8 bReli const FRepLayout& LocalRepLayout = *RepLayout; - if (!SkipRetirementUpdate) + if (FSendingRepState* SendingRepState = RepState.IsValid() ? RepState->GetSendingRepState() : nullptr) { - // Don't call if reliable, since the bunch will be resent. We dont want this to end up in the changelist history - // But is that enough? How does it know to delta against this latest state? - - LocalRepLayout.PostReplicate(RepState->GetSendingRepState(), PacketRange, bReliable ? true : false); - } - - for (uint16 LifetimePropertyIndex : LocalRepLayout.GetLifetimeCustomDeltaProperties()) - { - FPropertyRetirement & Retire = Retirement[LifetimePropertyIndex]; - - FPropertyRetirement* Next = Retire.Next; - FPropertyRetirement* Prev = &Retire; - - while ( Next != nullptr ) + if (!SkipRetirementUpdate) { - // This is updating the dynamic properties retirement record that was created above during property replication - // (we have to wait until we actually send the bunch to know the packetID, which is why we look for .First==INDEX_NONE) - if ( Next->OutPacketIdRange.First == INDEX_NONE ) + // Don't call if reliable, since the bunch will be resent. We dont want this to end up in the changelist history + // But is that enough? How does it know to delta against this latest state? + + for (int32 i = SendingRepState->HistoryStart; i < SendingRepState->HistoryEnd; ++i) { + const int32 HistoryIndex = i % FSendingRepState::MAX_CHANGE_HISTORY; - if (!SkipRetirementUpdate) - { - Next->OutPacketIdRange = PacketRange; + FRepChangedHistory & HistoryItem = SendingRepState->ChangeHistory[HistoryIndex]; - // Mark the last time on this retirement slot that a property actually changed - Retire.OutPacketIdRange = PacketRange; - } - else + if (HistoryItem.OutPacketIdRange.First == INDEX_NONE) { - // We need to remove the retirement entry here! - Prev->Next = Next->Next; - delete Next; - Next = Prev; + check(HistoryItem.Changed.Num() > 0); + check(!HistoryItem.Resend); + + HistoryItem.OutPacketIdRange = PacketRange; + + if (!bReliable && !SendingRepState->bOpenAckedCalled) + { + SendingRepState->PreOpenAckHistory.Add(HistoryItem); + } } } - - Prev = Next; - Next = Next->Next; } - ValidateRetirementHistory( Retire, Object ); + for (FPropertyRetirement& Retirement : SendingRepState->Retirement) + { + FPropertyRetirement* Next = Retirement.Next; + FPropertyRetirement* Prev = &Retirement; + + while (Next != nullptr) + { + // This is updating the dynamic properties retirement record that was created above during property replication + // (we have to wait until we actually send the bunch to know the packetID, which is why we look for .First==INDEX_NONE) + if (Next->OutPacketIdRange.First == INDEX_NONE) + { + if (!SkipRetirementUpdate) + { + Next->OutPacketIdRange = PacketRange; + + // Mark the last time on this retirement slot that a property actually changed + Retirement.OutPacketIdRange = PacketRange; + } + else + { + // We need to remove the retirement entry here! + Prev->Next = Next->Next; + delete Next; + Next = Prev; + } + } + + Prev = Next; + Next = Next->Next; + } + + ValidateRetirementHistory(Retirement, Object); + } } } @@ -1727,48 +1746,6 @@ void FObjectReplicator::CountBytes(FArchive& Ar) const { GRANULAR_NETWORK_MEMORY_TRACKING_INIT(Ar, "FObjectReplicator::CountBytes"); - GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("Retirement", Retirement.CountBytes(Ar)); - - GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("RecentCustomDeltaState", - RecentCustomDeltaState.CountBytes(Ar); - for (const auto& RecentCustomDeltaStatePair : RecentCustomDeltaState) - { - if (INetDeltaBaseState const * const BaseState = RecentCustomDeltaStatePair.Value.Get()) - { - BaseState->CountBytes(Ar); - } - } - ); - - GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("CDOCustomDeltaState", - CDOCustomDeltaState.CountBytes(Ar); - for (const auto& CDOCustomDeltaStatePair : CDOCustomDeltaState) - { - if (INetDeltaBaseState const * const BaseState = CDOCustomDeltaStatePair.Value.Get()) - { - BaseState->CountBytes(Ar); - } - } - ); - -PRAGMA_DISABLE_DEPRECATION_WARNINGS - GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("LifetimeCustomDeltaProperties", LifetimeCustomDeltaProperties.CountBytes(Ar)); - - GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("LifetimeCustomDeltaPropertyConditions", LifetimeCustomDeltaPropertyConditions.CountBytes(Ar)); - - GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("UnmappedCustomProperties", UnmappedCustomProperties.CountBytes(Ar)); -PRAGMA_ENABLE_DEPRECATION_WARNINGS - - GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("RepNotifies", RepNotifies.CountBytes(Ar)); - - GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("RepNotifyMetaData", - RepNotifyMetaData.CountBytes(Ar); - for (const auto& MetaDataPair : RepNotifyMetaData) - { - MetaDataPair.Value.CountBytes(Ar); - } - ); - // FObjectReplicator has a shared pointer to an FRepLayout, but since it's shared with // the UNetDriver, the memory isn't tracked here. @@ -1815,8 +1792,8 @@ void FObjectReplicator::QueueRemoteFunctionBunch( UFunction* Func, FOutBunch &Bu // // Long term we want to have priorities and stronger cross channel traffic management that // can handle this better - int32 InfoIdx=INDEX_NONE; - for (int32 i=0; i < RemoteFuncInfo.Num(); ++i) + int32 InfoIdx = INDEX_NONE; + for (int32 i = 0; i < RemoteFuncInfo.Num(); ++i) { if (RemoteFuncInfo[i].FuncName == Func->GetFName()) { @@ -1824,7 +1801,8 @@ void FObjectReplicator::QueueRemoteFunctionBunch( UFunction* Func, FOutBunch &Bu break; } } - if (InfoIdx==INDEX_NONE) + + if (InfoIdx == INDEX_NONE) { InfoIdx = RemoteFuncInfo.AddUninitialized(); RemoteFuncInfo[InfoIdx].FuncName = Func->GetFName(); @@ -1837,7 +1815,7 @@ void FObjectReplicator::QueueRemoteFunctionBunch( UFunction* Func, FOutBunch &Bu RemoteFuncInfo[InfoIdx].Calls, *Func->GetName(), *GetPathNameSafe(GetObject()), RemoteFuncInfo[InfoIdx].LastCallTime, OwningChannel->Connection->Driver->Time, OwningChannel->RelevantTime, OwningChannel->LastUpdateTime); // The MustBeMappedGuids can just be dropped, because we aren't actually going to send a bunch. If we don't clear it, then we will get warnings when the next channel tries to replicate - CastChecked< UPackageMapClient >( Connection->PackageMap )->GetMustBeMappedGuidsInLastBunch().Reset(); + CastChecked(Connection->PackageMap)->GetMustBeMappedGuidsInLastBunch().Reset(); return; } @@ -1848,67 +1826,91 @@ void FObjectReplicator::QueueRemoteFunctionBunch( UFunction* Func, FOutBunch &Bu RemoteFunctions = new FOutBunch(OwningChannel, 0); } - RemoteFunctions->SerializeBits( Bunch.GetData(), Bunch.GetNumBits() ); + RemoteFunctions->SerializeBits(Bunch.GetData(), Bunch.GetNumBits()); - if ( Connection->PackageMap != nullptr ) + if (Connection->PackageMap != nullptr) { - UPackageMapClient * PackageMapClient = CastChecked< UPackageMapClient >( Connection->PackageMap ); + UPackageMapClient* PackageMapClient = CastChecked(Connection->PackageMap); // We need to copy over any info that was obtained on the package map during serialization, and remember it until we actually call SendBunch - if ( PackageMapClient->GetMustBeMappedGuidsInLastBunch().Num() ) + if (PackageMapClient->GetMustBeMappedGuidsInLastBunch().Num()) { - OwningChannel->QueuedMustBeMappedGuidsInLastBunch.Append( PackageMapClient->GetMustBeMappedGuidsInLastBunch() ); + OwningChannel->QueuedMustBeMappedGuidsInLastBunch.Append(PackageMapClient->GetMustBeMappedGuidsInLastBunch()); PackageMapClient->GetMustBeMappedGuidsInLastBunch().Reset(); } if (!Connection->InternalAck) { // Copy over any exported bunches - PackageMapClient->AppendExportBunches( OwningChannel->QueuedExportBunches ); + PackageMapClient->AppendExportBunches(OwningChannel->QueuedExportBunches); } } } -bool FObjectReplicator::ReadyForDormancy(bool suppressLogs) +bool FObjectReplicator::ReadyForDormancy(bool bSuppressLogs) { - if ( GetObject() == NULL ) + if (GetObject() == nullptr) { - UE_LOG( LogRep, Verbose, TEXT( "ReadyForDormancy: Object == NULL" ) ); + UE_LOG(LogRep, Verbose, TEXT("ReadyForDormancy: Object == nullptr")); return true; // Technically, we don't want to hold up dormancy, but the owner needs to clean us up, so we warn } // Can't go dormant until last update produced no new property updates - if ( !bLastUpdateEmpty ) + if (!bLastUpdateEmpty) { - if ( !suppressLogs ) + if (!bSuppressLogs) { - UE_LOG( LogRepTraffic, Verbose, TEXT( " [%d] Not ready for dormancy. bLastUpdateEmpty = false" ), OwningChannel->ChIndex ); + UE_LOG(LogRepTraffic, Verbose, TEXT(" [%d] Not ready for dormancy. bLastUpdateEmpty = false"), OwningChannel->ChIndex); } return false; } - // Can't go dormant if there are unAckd property updates - for ( int32 i = 0; i < Retirement.Num(); ++i ) + if (FSendingRepState* SendingRepState = RepState.IsValid() ? RepState->GetSendingRepState() : nullptr) { - if ( Retirement[i].Next != NULL ) + if (SendingRepState->HistoryStart != SendingRepState->HistoryEnd) { - if ( !suppressLogs ) - { - UE_LOG( LogRepTraffic, Verbose, TEXT( " [%d] OutAckPacketId: %d First: %d Last: %d " ), OwningChannel->ChIndex, OwningChannel->Connection->OutAckPacketId, Retirement[i].OutPacketIdRange.First, Retirement[i].OutPacketIdRange.Last ); - } + // We have change lists that haven't been acked return false; } + + if (SendingRepState->NumNaks > 0) + { + return false; + } + + if (!SendingRepState->bOpenAckedCalled) + { + return false; + } + + if (SendingRepState->PreOpenAckHistory.Num() > 0) + { + return false; + } + + // Can't go dormant if there are unAckd property updates + for (FPropertyRetirement& Retirement : SendingRepState->Retirement) + { + if (Retirement.Next != nullptr) + { + if (!bSuppressLogs) + { + UE_LOG(LogRepTraffic, Verbose, TEXT(" [%d] OutAckPacketId: %d First: %d Last: %d "), OwningChannel->ChIndex, OwningChannel->Connection->OutAckPacketId, Retirement.OutPacketIdRange.First, Retirement.OutPacketIdRange.Last); + } + return false; + } + } } - return RepLayout->ReadyForDormancy( RepState.Get() ); + return true; } void FObjectReplicator::StartBecomingDormant() { - if ( GetObject() == NULL ) + if ( GetObject() == nullptr ) { - UE_LOG( LogRep, Verbose, TEXT( "StartBecomingDormant: Object == NULL" ) ); + UE_LOG( LogRep, Verbose, TEXT( "StartBecomingDormant: Object == nullptr" ) ); return; } @@ -1940,69 +1942,6 @@ void FObjectReplicator::CallRepNotifies(bool bSkipIfChannelHasQueuedBunches) FReceivingRepState* ReceivingRepState = RepState->GetReceivingRepState(); RepLayout->CallRepNotifies(ReceivingRepState, Object); - if (RepNotifies.Num() > 0) - { - for (UProperty* RepProperty : RepNotifies) - { - //UE_LOG(LogRep, Log, TEXT("Calling Object->%s with %s"), *RepNotifies(RepNotifyIdx)->RepNotifyFunc.ToString(), *RepNotifies(RepNotifyIdx)->GetName()); - UFunction* RepNotifyFunc = Object->FindFunction(RepProperty->RepNotifyFunc); - - if (RepNotifyFunc == nullptr) - { - UE_LOG(LogRep, Warning, TEXT("FObjectReplicator::CallRepNotifies: Can't find RepNotify function %s for property %s on object %s."), - *RepProperty->RepNotifyFunc.ToString(), *RepProperty->GetName(), *Object->GetName()); - continue; - } - - if (RepNotifyFunc->NumParms == 0) - { - Object->ProcessEvent(RepNotifyFunc, NULL); - } - else if (RepNotifyFunc->NumParms == 1) - { - Object->ProcessEvent(RepNotifyFunc, RepProperty->ContainerPtrToValuePtr(ReceivingRepState->StaticBuffer.GetData()) ); - } - else if (RepNotifyFunc->NumParms == 2) - { - // Fixme: this isn't as safe as it could be. Right now we have two types of parameters: MetaData (a TArray) - // and the last local value (pointer into the Recent[] array). - // - // Arrays always expect MetaData. Everything else, including structs, expect last value. - // This is enforced with UHT only. If a ::NetSerialize function ever starts producing a MetaData array thats not in UArrayProperty, - // we have no static way of catching this and the replication system could pass the wrong thing into ProcessEvent here. - // - // But this is all sort of an edge case feature anyways, so its not worth tearing things up too much over. - - FMemMark Mark(FMemStack::Get()); - uint8* Parms = new(FMemStack::Get(), MEM_Zeroed, RepNotifyFunc->ParmsSize)uint8; - - TFieldIterator Itr(RepNotifyFunc); - check(Itr); - - Itr->CopyCompleteValue(Itr->ContainerPtrToValuePtr(Parms), RepProperty->ContainerPtrToValuePtr(ReceivingRepState->StaticBuffer.GetData())); - ++Itr; - check(Itr); - - TArray *NotifyMetaData = RepNotifyMetaData.Find(RepProperty); - check(NotifyMetaData); - Itr->CopyCompleteValue(Itr->ContainerPtrToValuePtr(Parms), NotifyMetaData); - - Object->ProcessEvent(RepNotifyFunc, Parms); - - Mark.Pop(); - } - - if (Object->IsPendingKill()) - { - // script event destroyed Object - break; - } - } - } - - RepNotifies.Reset(); - RepNotifyMetaData.Empty(); - if (!Object->IsPendingKill()) { Object->PostRepNotifies(); @@ -2028,8 +1967,10 @@ void FObjectReplicator::UpdateUnmappedObjects(bool & bOutHasMoreUnmapped) // Since RepNotifies aren't processed while a channel has queued bunches, don't assert in that case. FReceivingRepState* ReceivingRepState = RepState->GetReceivingRepState(); const bool bHasQueuedBunches = OwningChannel && OwningChannel->QueuedBunches.Num() > 0; - checkf( bHasQueuedBunches || ReceivingRepState->RepNotifies.Num() == 0, TEXT("Failed RepState RepNotifies check. Num=%d. Object=%s. Channel QueuedBunches=%d"), ReceivingRepState->RepNotifies.Num(), *Object->GetFullName(), OwningChannel ? OwningChannel->QueuedBunches.Num() : 0 ); - checkf( bHasQueuedBunches || RepNotifies.Num() == 0, TEXT("Failed replicator RepNotifies check. Num=%d. Object=%s. Channel QueuedBunches=%d"), RepNotifies.Num(), *Object->GetFullName(), OwningChannel ? OwningChannel->QueuedBunches.Num() : 0 ); + + checkf(bHasQueuedBunches || ReceivingRepState->RepNotifies.Num() == 0, + TEXT("Failed RepState RepNotifies check. Num=%d. Object=%s. Channel QueuedBunches=%d"), + ReceivingRepState->RepNotifies.Num(), *Object->GetFullName(), OwningChannel ? OwningChannel->QueuedBunches.Num() : 0); bool bCalledPreNetReceive = false; bool bSomeObjectsWereMapped = false; @@ -2038,46 +1979,24 @@ void FObjectReplicator::UpdateUnmappedObjects(bool & bOutHasMoreUnmapped) const FRepLayout& LocalRepLayout = *RepLayout; - // Let the rep layout update any unmapped properties - LocalRepLayout.UpdateUnmappedObjects(ReceivingRepState, Connection->PackageMap, Object, bCalledPreNetReceive, bSomeObjectsWereMapped, bOutHasMoreUnmapped); - FNetSerializeCB NetSerializeCB(Connection->Driver); FNetDeltaSerializeInfo Parms; Parms.Object = Object; Parms.Connection = Connection; + Parms.bInternalAck = Connection->InternalAck; Parms.Map = Connection->PackageMap; Parms.NetSerializeCB = &NetSerializeCB; Parms.bUpdateUnmappedObjects = true; - Parms.bCalledPreNetReceive = bCalledPreNetReceive; - - TArray> CompletelyMappedProperties; - TArray> UpdatedProperties; - FNetSerializeCB::UpdateUnmappedObjectsForCustomDeltaProperties(LocalRepLayout, Parms, CompletelyMappedProperties, UpdatedProperties); + // Let the rep layout update any unmapped properties + LocalRepLayout.UpdateUnmappedObjects(ReceivingRepState, Connection->PackageMap, Object, Parms, bCalledPreNetReceive, bSomeObjectsWereMapped, bOutHasMoreUnmapped); bSomeObjectsWereMapped |= Parms.bOutSomeObjectsWereMapped; bOutHasMoreUnmapped |= Parms.bOutHasMoreUnmapped; bCalledPreNetReceive |= Parms.bCalledPreNetReceive; - // This should go away when UnmappedCustomProperties goes away, and when RepNotifies - // are merged with RepState RepNotifies. -PRAGMA_DISABLE_DEPRECATION_WARNINGS - for (const TPair& KVP : UpdatedProperties) - { - TArray MetaData; - QueuePropertyRepNotify(Object, KVP.Value, 0, MetaData); - } - - // This is just for the sake of keeping UnmappedCustomProperties up to date. - // Remove this when that property goes away. - for (const TPair& KVP : CompletelyMappedProperties) - { - UnmappedCustomProperties.Remove(KVP.Key); - } -PRAGMA_ENABLE_DEPRECATION_WARNINGS - if (bCalledPreNetReceive) { // If we mapped some objects, make sure to call PostNetReceive (some game code will need to think this was actually replicated to work) @@ -2091,18 +2010,18 @@ PRAGMA_ENABLE_DEPRECATION_WARNINGS // the RepNotifies will remain in the list and the check for 0 RepNotifies above will fail next time. CallRepNotifies(false); - UPackageMapClient * PackageMapClient = Cast< UPackageMapClient >(Connection->PackageMap); + UPackageMapClient* PackageMapClient = Cast(Connection->PackageMap); if (PackageMapClient && OwningChannel) { const bool bIsServer = Connection->Driver->IsServer(); - const FClassNetCache * ClassCache = Connection->Driver->NetCache->GetClassNetCache(ObjectClass); + const FClassNetCache* ClassCache = Connection->Driver->NetCache->GetClassNetCache(ObjectClass); // Handle pending RPCs, in order for (int32 RPCIndex = 0; RPCIndex < PendingLocalRPCs.Num(); RPCIndex++) { FRPCPendingLocalCall& Pending = PendingLocalRPCs[RPCIndex]; - const FFieldNetCache * FieldCache = ClassCache->GetFromIndex(Pending.RPCFieldIndex); + const FFieldNetCache* FieldCache = ClassCache->GetFromIndex(Pending.RPCFieldIndex); FNetBitReader Reader(Connection->PackageMap, Pending.Buffer.GetData(), Pending.NumBits); @@ -2174,72 +2093,81 @@ PRAGMA_ENABLE_DEPRECATION_WARNINGS } } -void FObjectReplicator::QueuePropertyRepNotify( UObject* Object, UProperty * Property, const int32 ElementIndex, TArray< uint8 > & MetaData ) +void FObjectReplicator::QueuePropertyRepNotify( + UObject* Object, + UProperty * Property, + const int32 ElementIndex, + TArray& MetaData) { - if ( !Property->HasAnyPropertyFlags( CPF_RepNotify ) ) + if (!Property->HasAnyPropertyFlags(CPF_RepNotify)) { return; } - //@note: AddUniqueItem() here for static arrays since RepNotify() currently doesn't indicate index, - // so reporting the same property multiple times is not useful and wastes CPU - // were that changed, this should go back to AddItem() for efficiency - // @todo UE4 - not checking if replicated value is changed from old. Either fix or document, as may get multiple repnotifies of unacked properties. - RepNotifies.AddUnique( Property ); - - UFunction * RepNotifyFunc = Object->FindFunctionChecked( Property->RepNotifyFunc ); - - if ( RepNotifyFunc->NumParms > 0 ) + FReceivingRepState* ReceivingRepState = RepState.IsValid() ? RepState->GetReceivingRepState() : nullptr; + if (ensureMsgf(ReceivingRepState, TEXT("FObjectReplicator::QueuePropertyRepNotifiy: No receiving RepState. Object=%s, Property=%s"), + *GetPathNameSafe(Object), *Property->GetName())) { - if ( Property->ArrayDim != 1 ) + //@note: AddUniqueItem() here for static arrays since RepNotify() currently doesn't indicate index, + // so reporting the same property multiple times is not useful and wastes CPU + // were that changed, this should go back to AddItem() for efficiency + // @todo UE4 - not checking if replicated value is changed from old. Either fix or document, as may get multiple repnotifies of unacked properties. + ReceivingRepState->RepNotifies.AddUnique(Property); + + UFunction* RepNotifyFunc = Object->FindFunctionChecked(Property->RepNotifyFunc); + + if (RepNotifyFunc->NumParms > 0) { - // For static arrays, we build the meta data here, but adding the Element index that was just read into the PropMetaData array. - UE_LOG( LogRepTraffic, Verbose, TEXT("Property %s had ArrayDim: %d change"), *Property->GetName(), ElementIndex ); + if (Property->ArrayDim != 1) + { + // For static arrays, we build the meta data here, but adding the Element index that was just read into the PropMetaData array. + UE_LOG(LogRepTraffic, Verbose, TEXT("Property %s had ArrayDim: %d change"), *Property->GetName(), ElementIndex); - // Property is multi dimensional, keep track of what elements changed - TArray< uint8 > & PropMetaData = RepNotifyMetaData.FindOrAdd( Property ); - PropMetaData.Add( ElementIndex ); - } - else if ( MetaData.Num() > 0 ) - { - // For other properties (TArrays only now) the MetaData array is build within ::NetSerialize. Just add it to the RepNotifyMetaData map here. + // Property is multi dimensional, keep track of what elements changed + TArray< uint8 > & PropMetaData = ReceivingRepState->RepNotifyMetaData.FindOrAdd(Property); + PropMetaData.Add(ElementIndex); + } + else if (MetaData.Num() > 0) + { + // For other properties (TArrays only now) the MetaData array is build within ::NetSerialize. Just add it to the RepNotifyMetaData map here. - //UE_LOG(LogRepTraffic, Verbose, TEXT("Property %s had MetaData: "), *Property->GetName() ); - //for (auto MetaIt = MetaData.CreateIterator(); MetaIt; ++MetaIt) - // UE_LOG(LogRepTraffic, Verbose, TEXT(" %d"), *MetaIt ); + //UE_LOG(LogRepTraffic, Verbose, TEXT("Property %s had MetaData: "), *Property->GetName() ); + //for (auto MetaIt = MetaData.CreateIterator(); MetaIt; ++MetaIt) + // UE_LOG(LogRepTraffic, Verbose, TEXT(" %d"), *MetaIt ); - // Property included some meta data about what was serialized. - TArray< uint8 > & PropMetaData = RepNotifyMetaData.FindOrAdd( Property ); - PropMetaData = MetaData; + // Property included some meta data about what was serialized. + TArray< uint8 > & PropMetaData = ReceivingRepState->RepNotifyMetaData.FindOrAdd(Property); + PropMetaData = MetaData; + } } } } void FObjectReplicator::WritePropertyHeaderAndPayload( - UObject* Object, - UProperty* Property, - FNetFieldExportGroup* NetFieldExportGroup, - FNetBitWriter& Bunch, - FNetBitWriter& Payload ) const + UObject* Object, + UProperty* Property, + FNetFieldExportGroup* NetFieldExportGroup, + FNetBitWriter& Bunch, + FNetBitWriter& Payload) const { // Get class network info cache. - const FClassNetCache* ClassCache = Connection->Driver->NetCache->GetClassNetCache( ObjectClass ); + const FClassNetCache* ClassCache = Connection->Driver->NetCache->GetClassNetCache(ObjectClass); - check( ClassCache ); + check(ClassCache); // Get the network friend property index to replicate - const FFieldNetCache * FieldCache = ClassCache->GetFromField( Property ); + const FFieldNetCache * FieldCache = ClassCache->GetFromField(Property); - checkSlow( FieldCache ); + checkSlow(FieldCache); // Send property name and optional array index. - check( FieldCache->FieldNetIndex <= ClassCache->GetMaxIndex() ); + check(FieldCache->FieldNetIndex <= ClassCache->GetMaxIndex()); // WriteFieldHeaderAndPayload will return the total number of bits written. // So, we subtract out the Payload size to get the actual number of header bits. - const int32 HeaderBits = static_cast(OwningChannel->WriteFieldHeaderAndPayload( Bunch, ClassCache, FieldCache, NetFieldExportGroup, Payload )) - Payload.GetNumBits(); + const int32 HeaderBits = static_cast(OwningChannel->WriteFieldHeaderAndPayload(Bunch, ClassCache, FieldCache, NetFieldExportGroup, Payload)) - Payload.GetNumBits(); - NETWORK_PROFILER( GNetworkProfiler.TrackWritePropertyHeader( Property, HeaderBits, nullptr ) ); + NETWORK_PROFILER(GNetworkProfiler.TrackWritePropertyHeader(Property, HeaderBits, nullptr)); } void FObjectReplicator::UpdateCheckpoint() diff --git a/Engine/Source/Runtime/Engine/Private/DataTable.cpp b/Engine/Source/Runtime/Engine/Private/DataTable.cpp index 192189b1276b..32535318cb7f 100644 --- a/Engine/Source/Runtime/Engine/Private/DataTable.cpp +++ b/Engine/Source/Runtime/Engine/Private/DataTable.cpp @@ -68,6 +68,10 @@ namespace UDataTable::UDataTable(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { + bIgnoreExtraFields = false; + bIgnoreMissingFields = false; + bStripFromClientBuilds = false; + #if WITH_EDITORONLY_DATA { static const FAutoRegisterLocalizationDataGatheringCallback AutomaticRegistrationOfLocalizationGatherer(UDataTable::StaticClass(), &GatherDataTableForLocalization); } #endif @@ -89,7 +93,7 @@ void UDataTable::LoadStructData(FArchive& Ar) UScriptStruct* LoadUsingStruct = RowStruct; if (!LoadUsingStruct) { - if (!HasAnyFlags(RF_ClassDefaultObject)) + if (!HasAnyFlags(RF_ClassDefaultObject) && GetOutermost() != GetTransientPackage()) { UE_LOG(LogDataTable, Error, TEXT("Missing RowStruct while loading DataTable '%s'!"), *GetPathName()); } @@ -125,7 +129,7 @@ void UDataTable::SaveStructData(FArchive& Ar) UScriptStruct* SaveUsingStruct = RowStruct; if (!SaveUsingStruct) { - if (!HasAnyFlags(RF_ClassDefaultObject)) + if (!HasAnyFlags(RF_ClassDefaultObject) && GetOutermost() != GetTransientPackage()) { UE_LOG(LogDataTable, Error, TEXT("Missing RowStruct while saving DataTable '%s'!"), *GetPathName()); } @@ -314,7 +318,7 @@ UScriptStruct& UDataTable::GetEmptyUsingStruct() const UScriptStruct* EmptyUsingStruct = RowStruct; if (!EmptyUsingStruct) { - if (!HasAnyFlags(RF_ClassDefaultObject)) + if (!HasAnyFlags(RF_ClassDefaultObject) && GetOutermost() != GetTransientPackage()) { UE_LOG(LogDataTable, Error, TEXT("Missing RowStruct while emptying DataTable '%s'!"), *GetPathName()); } @@ -394,7 +398,7 @@ UProperty* UDataTable::FindTableProperty(const FName& PropertyName) const for (TFieldIterator It(RowStruct); It; ++It) { - if (PropertyNameStr == RowStruct->PropertyNameToDisplayName(It->GetFName())) + if (PropertyNameStr == RowStruct->GetAuthoredNameForField(*It)) { Property = *It; break; @@ -547,6 +551,33 @@ bool UDataTable::WriteRowAsJSON(const TSharedRef< TJsonWriterbStripFromClientBuilds; + bIgnoreExtraFields = SourceTable->bIgnoreExtraFields; + bIgnoreMissingFields = SourceTable->bIgnoreMissingFields; + ImportKeyField = SourceTable->ImportKeyField; + RowStruct = SourceTable->RowStruct; + + if (RowStruct) + { + RowStructName = RowStruct->GetFName(); + } + + if (SourceTable->AssetImportData) + { + AssetImportData->SourceData = SourceTable->AssetImportData->SourceData; + } + + return true; +} + bool UDataTable::WriteTableAsJSON(const TSharedRef< TJsonWriter > >& JsonWriter, const EDataTableExportFlags InDTExportFlags) const { return FDataTableExporterJSON(InDTExportFlags, JsonWriter).WriteTable(*this); @@ -558,23 +589,26 @@ bool UDataTable::WriteTableAsJSONObject(const TSharedRef< TJsonWriter UDataTable::GetTablePropertyArray(const TArray& Cells, UStruct* InRowStruct, TArray& OutProblems) +TArray UDataTable::GetTablePropertyArray(const TArray& Cells, UStruct* InRowStruct, TArray& OutProblems, int32 KeyColumn) { TArray ColumnProps; // Get list of all expected properties from the struct TArray ExpectedPropNames = DataTableUtils::GetStructPropertyNames(InRowStruct); - // Need at least 2 columns, first column is skipped, will contain row names + // Need at least 2 columns, first column will contain row names if(Cells.Num() > 1) { ColumnProps.AddZeroed( Cells.Num() ); - // first element always NULL - as first column is row names - - for (int32 ColIdx = 1; ColIdx < Cells.Num(); ++ColIdx) + // Skip first column depending on option + for (int32 ColIdx = 0; ColIdx < Cells.Num(); ++ColIdx) { + if (ColIdx == KeyColumn) + { + continue; + } + const TCHAR* ColumnValue = Cells[ColIdx]; FName PropName = DataTableUtils::MakeValidName(ColumnValue); @@ -594,7 +628,10 @@ TArray UDataTable::GetTablePropertyArray(const TArray& // Didn't find a property with this name, problem.. if(ColumnProp == nullptr) { - OutProblems.Add(FString::Printf(TEXT("Cannot find Property for column '%s' in struct '%s'."), *PropName.ToString(), *InRowStruct->GetName())); + if (!bIgnoreExtraFields) + { + OutProblems.Add(FString::Printf(TEXT("Cannot find Property for column '%s' in struct '%s'."), *PropName.ToString(), *InRowStruct->GetName())); + } } // Found one! else @@ -622,23 +659,26 @@ TArray UDataTable::GetTablePropertyArray(const TArray& } } - // Generate warning for any properties in struct we are not filling in - for (int32 PropIdx=0; PropIdx < ExpectedPropNames.Num(); PropIdx++) + if (!bIgnoreMissingFields) { - const UProperty* const ColumnProp = FindField(InRowStruct, ExpectedPropNames[PropIdx]); + // Generate warning for any properties in struct we are not filling in + for (int32 PropIdx = 0; PropIdx < ExpectedPropNames.Num(); PropIdx++) + { + const UProperty* const ColumnProp = FindField(InRowStruct, ExpectedPropNames[PropIdx]); #if WITH_EDITOR - // If the structure has specified the property as optional for import (gameplay code likely doing a custom fix-up or parse of that property), - // then avoid warning about it - static const FName DataTableImportOptionalMetadataKey(TEXT("DataTableImportOptional")); - if (ColumnProp->HasMetaData(DataTableImportOptionalMetadataKey)) - { - continue; - } + // If the structure has specified the property as optional for import (gameplay code likely doing a custom fix-up or parse of that property), + // then avoid warning about it + static const FName DataTableImportOptionalMetadataKey(TEXT("DataTableImportOptional")); + if (ColumnProp->HasMetaData(DataTableImportOptionalMetadataKey)) + { + continue; + } #endif // WITH_EDITOR - const FString DisplayName = DataTableUtils::GetPropertyDisplayName(ColumnProp, ExpectedPropNames[PropIdx].ToString()); - OutProblems.Add(FString::Printf(TEXT("Expected column '%s' not found in input."), *DisplayName)); + const FString DisplayName = DataTableUtils::GetPropertyExportName(ColumnProp); + OutProblems.Add(FString::Printf(TEXT("Expected column '%s' not found in input."), *DisplayName)); + } } return ColumnProps; @@ -719,7 +759,7 @@ TArray UDataTable::GetColumnTitles() const { UProperty* Prop = *It; check(Prop != nullptr); - const FString DisplayName = DataTableUtils::GetPropertyDisplayName(Prop, Prop->GetName()); + const FString DisplayName = DataTableUtils::GetPropertyExportName(Prop); Result.Add(DisplayName); } } diff --git a/Engine/Source/Runtime/Engine/Private/DataTableCSV.cpp b/Engine/Source/Runtime/Engine/Private/DataTableCSV.cpp index 81b7ed6efe09..5c52ec7d6127 100644 --- a/Engine/Source/Runtime/Engine/Private/DataTableCSV.cpp +++ b/Engine/Source/Runtime/Engine/Private/DataTableCSV.cpp @@ -26,14 +26,35 @@ bool FDataTableExporterCSV::WriteTable(const UDataTable& InDataTable) } // Write the header (column titles) - ExportedText += TEXT("---"); + FString ImportKeyField; + if (!InDataTable.ImportKeyField.IsEmpty()) + { + // Write actual name if we have it + ImportKeyField = InDataTable.ImportKeyField; + ExportedText += ImportKeyField; + } + else + { + ExportedText += TEXT("---"); + } + + UProperty* SkipProperty = nullptr; for (TFieldIterator It(InDataTable.RowStruct); It; ++It) { UProperty* BaseProp = *It; check(BaseProp); + FString ColumnHeader = DataTableUtils::GetPropertyExportName(BaseProp, DTExportFlags); + + if (ColumnHeader == ImportKeyField) + { + // Don't write header again if this is the name field, and save for skipping later + SkipProperty = BaseProp; + continue; + } + ExportedText += TEXT(","); - ExportedText += DataTableUtils::GetPropertyExportName(BaseProp, DTExportFlags); + ExportedText += ColumnHeader; } ExportedText += TEXT("\n"); @@ -52,7 +73,7 @@ bool FDataTableExporterCSV::WriteTable(const UDataTable& InDataTable) return true; } -bool FDataTableExporterCSV::WriteRow(const UScriptStruct* InRowStruct, const void* InRowData) +bool FDataTableExporterCSV::WriteRow(const UScriptStruct* InRowStruct, const void* InRowData, const UProperty* SkipProperty) { if (!InRowStruct) { @@ -64,6 +85,11 @@ bool FDataTableExporterCSV::WriteRow(const UScriptStruct* InRowStruct, const voi UProperty* BaseProp = *It; check(BaseProp); + if (BaseProp == SkipProperty) + { + continue; + } + const void* Data = BaseProp->ContainerPtrToValuePtr(InRowData, 0); WriteStructEntry(InRowData, BaseProp, Data); } @@ -124,7 +150,22 @@ bool FDataTableImporterCSV::ReadTable() } // Find property for each column - TArray ColumnProps = DataTable->GetTablePropertyArray(Rows[0], DataTable->RowStruct, ImportProblems); + int32 KeyColumn = 0; + if (!DataTable->ImportKeyField.IsEmpty()) + { + // Search for key column by name + for (int32 ColIdx = 0; ColIdx < Rows[0].Num(); ++ColIdx) + { + const TCHAR* ColumnValue = Rows[0][ColIdx]; + if (DataTable->ImportKeyField == FString(ColumnValue)) + { + KeyColumn = ColIdx; + break; + } + } + } + + TArray ColumnProps = DataTable->GetTablePropertyArray(Rows[0], DataTable->RowStruct, ImportProblems, KeyColumn); // Empty existing data DataTable->EmptyTable(); @@ -134,8 +175,8 @@ bool FDataTableImporterCSV::ReadTable() { const TArray& Cells = Rows[RowIdx]; - // Need at least 1 cells (row name) - if(Cells.Num() < 1) + // Need at least the key column + if (Cells.Num() <= KeyColumn) { ImportProblems.Add(FString::Printf(TEXT("Row '%d' has too few cells."), RowIdx)); continue; @@ -149,12 +190,20 @@ bool FDataTableImporterCSV::ReadTable() } // Get row name - FName RowName = DataTableUtils::MakeValidName(Cells[0]); + FName RowName = DataTableUtils::MakeValidName(Cells[KeyColumn]); // Check its not 'none' if(RowName == NAME_None) { - ImportProblems.Add(FString::Printf(TEXT("Row '%d' missing a name."), RowIdx)); + if (!DataTable->ImportKeyField.IsEmpty()) + { + ImportProblems.Add(FString::Printf(TEXT("Row '%d' missing key field '%s'."), RowIdx, *DataTable->ImportKeyField)); + } + else + { + ImportProblems.Add(FString::Printf(TEXT("Row '%d' missing a name."), RowIdx)); + } + continue; } @@ -173,9 +222,14 @@ bool FDataTableImporterCSV::ReadTable() // Add to row map DataTable->AddRowInternal(RowName, RowData); - // Now iterate over cells (skipping first cell, that was row name) - for(int32 CellIdx=1; CellIdx 0) { FString ColumnName = (ColumnProp != nullptr) - ? DataTableUtils::GetPropertyDisplayName(ColumnProp, ColumnProp->GetName()) + ? DataTableUtils::GetPropertyExportName(ColumnProp) : FString(TEXT("NONE")); ImportProblems.Add(FString::Printf(TEXT("Problem assigning string '%s' to property '%s' on row '%s' : %s"), *CellValue, *ColumnName, *RowName.ToString(), *Error)); } diff --git a/Engine/Source/Runtime/Engine/Private/DataTableCSV.h b/Engine/Source/Runtime/Engine/Private/DataTableCSV.h index 0d9be740c1c6..6fedae152ae1 100644 --- a/Engine/Source/Runtime/Engine/Private/DataTableCSV.h +++ b/Engine/Source/Runtime/Engine/Private/DataTableCSV.h @@ -18,7 +18,7 @@ public: bool WriteTable(const UDataTable& InDataTable); - bool WriteRow(const UScriptStruct* InRowStruct, const void* InRowData); + bool WriteRow(const UScriptStruct* InRowStruct, const void* InRowData, const UProperty* SkipProperty = nullptr); private: bool WriteStructEntry(const void* InRowData, UProperty* InProperty, const void* InPropertyData); diff --git a/Engine/Source/Runtime/Engine/Private/DataTableJSON.cpp b/Engine/Source/Runtime/Engine/Private/DataTableJSON.cpp index 88e19a0f526b..e194827bf522 100644 --- a/Engine/Source/Runtime/Engine/Private/DataTableJSON.cpp +++ b/Engine/Source/Runtime/Engine/Private/DataTableJSON.cpp @@ -65,6 +65,19 @@ namespace } +FString DataTableJSONUtils::GetKeyFieldName(const UDataTable& InDataTable) +{ + FString ExplicitString = InDataTable.ImportKeyField; + if (ExplicitString.IsEmpty()) + { + return TEXT("Name"); + } + else + { + return ExplicitString; + } +} + #if WITH_EDITOR @@ -97,6 +110,7 @@ bool FDataTableExporterJSON::WriteTable(const UDataTable& InDataTable) return false; } + FString KeyField = DataTableJSONUtils::GetKeyFieldName(InDataTable); JsonWriter->WriteArrayStart(); // Iterate over rows @@ -106,11 +120,11 @@ bool FDataTableExporterJSON::WriteTable(const UDataTable& InDataTable) { // RowName const FName RowName = RowIt.Key(); - JsonWriter->WriteValue(TEXT("Name"), RowName.ToString()); + JsonWriter->WriteValue(KeyField, RowName.ToString()); // Now the values uint8* RowData = RowIt.Value(); - WriteRow(InDataTable.RowStruct, RowData); + WriteRow(InDataTable.RowStruct, RowData, &KeyField); } JsonWriter->WriteObjectEnd(); } @@ -147,23 +161,30 @@ bool FDataTableExporterJSON::WriteTableAsObject(const UDataTable& InDataTable) return true; } -bool FDataTableExporterJSON::WriteRow(const UScriptStruct* InRowStruct, const void* InRowData) +bool FDataTableExporterJSON::WriteRow(const UScriptStruct* InRowStruct, const void* InRowData, const FString* FieldToSkip) { if (!InRowStruct) { return false; } - return WriteStruct(InRowStruct, InRowData); + return WriteStruct(InRowStruct, InRowData, FieldToSkip); } -bool FDataTableExporterJSON::WriteStruct(const UScriptStruct* InStruct, const void* InStructData) +bool FDataTableExporterJSON::WriteStruct(const UScriptStruct* InStruct, const void* InStructData, const FString* FieldToSkip) { for (TFieldIterator It(InStruct); It; ++It) { const UProperty* BaseProp = *It; check(BaseProp); + const FString Identifier = DataTableUtils::GetPropertyExportName(BaseProp, DTExportFlags); + if (FieldToSkip && *FieldToSkip == Identifier) + { + // Skip this field + continue; + } + if (BaseProp->ArrayDim == 1) { const void* Data = BaseProp->ContainerPtrToValuePtr(InStructData, 0); @@ -171,8 +192,6 @@ bool FDataTableExporterJSON::WriteStruct(const UScriptStruct* InStruct, const vo } else { - const FString Identifier = DataTableUtils::GetPropertyExportName(BaseProp, DTExportFlags); - JsonWriter->WriteArrayStart(Identifier); for (int32 ArrayEntryIndex = 0; ArrayEntryIndex < BaseProp->ArrayDim; ++ArrayEntryIndex) @@ -424,12 +443,13 @@ bool FDataTableImporterJSON::ReadTable() bool FDataTableImporterJSON::ReadRow(const TSharedRef& InParsedTableRowObject, const int32 InRowIdx) { // Get row name - FName RowName = DataTableUtils::MakeValidName(InParsedTableRowObject->GetStringField(TEXT("Name"))); + FString RowKey = DataTableJSONUtils::GetKeyFieldName(*DataTable); + FName RowName = DataTableUtils::MakeValidName(InParsedTableRowObject->GetStringField(RowKey)); // Check its not 'none' if (RowName.IsNone()) { - ImportProblems.Add(FString::Printf(TEXT("Row '%d' missing a name."), InRowIdx)); + ImportProblems.Add(FString::Printf(TEXT("Row '%d' missing key field '%s'."), InRowIdx, *RowKey)); return false; } @@ -459,7 +479,7 @@ bool FDataTableImporterJSON::ReadStruct(const TSharedRef& InParsedO UProperty* BaseProp = *It; check(BaseProp); - const FString ColumnName = DataTableUtils::GetPropertyDisplayName(BaseProp, BaseProp->GetName()); + const FString ColumnName = DataTableUtils::GetPropertyExportName(BaseProp); TSharedPtr ParsedPropertyValue; for (const FString& PropertyName : DataTableUtils::GetPropertyImportNames(BaseProp)) @@ -473,7 +493,21 @@ bool FDataTableImporterJSON::ReadStruct(const TSharedRef& InParsedO if (!ParsedPropertyValue.IsValid()) { - ImportProblems.Add(FString::Printf(TEXT("Row '%s' is missing an entry for '%s'."), *InRowName.ToString(), *ColumnName)); +#if WITH_EDITOR + // If the structure has specified the property as optional for import (gameplay code likely doing a custom fix-up or parse of that property), + // then avoid warning about it + static const FName DataTableImportOptionalMetadataKey(TEXT("DataTableImportOptional")); + if (BaseProp->HasMetaData(DataTableImportOptionalMetadataKey)) + { + continue; + } +#endif // WITH_EDITOR + + if (!DataTable->bIgnoreMissingFields) + { + ImportProblems.Add(FString::Printf(TEXT("Row '%s' is missing an entry for '%s'."), *InRowName.ToString(), *ColumnName)); + } + continue; } diff --git a/Engine/Source/Runtime/Engine/Private/DataTableJSON.h b/Engine/Source/Runtime/Engine/Private/DataTableJSON.h index 00d70911c49f..d038bede586f 100644 --- a/Engine/Source/Runtime/Engine/Private/DataTableJSON.h +++ b/Engine/Source/Runtime/Engine/Private/DataTableJSON.h @@ -18,6 +18,12 @@ struct TPrettyJsonPrintPolicy; template class TJsonWriter; +namespace DataTableJSONUtils +{ + /** Returns what string is used as the key/name field for a data table */ + FString GetKeyFieldName(const UDataTable& InDataTable); +} + #if WITH_EDITOR class FDataTableExporterJSON @@ -31,14 +37,17 @@ public: ~FDataTableExporterJSON(); + /** Writes the data table out as an array of objects */ bool WriteTable(const UDataTable& InDataTable); /** Writes the data table out as a named object with each row being a sub value on that object */ bool WriteTableAsObject(const UDataTable& InDataTable); - bool WriteRow(const UScriptStruct* InRowStruct, const void* InRowData); + /** Writes out a single row */ + bool WriteRow(const UScriptStruct* InRowStruct, const void* InRowData, const FString* FieldToSkip = nullptr); - bool WriteStruct(const UScriptStruct* InStruct, const void* InStructData); + /** Writes the contents of a single row */ + bool WriteStruct(const UScriptStruct* InStruct, const void* InStructData, const FString* FieldToSkip = nullptr); private: bool WriteStructEntry(const void* InRowData, const UProperty* InProperty, const void* InPropertyData); diff --git a/Engine/Source/Runtime/Engine/Private/DataTableUtils.cpp b/Engine/Source/Runtime/Engine/Private/DataTableUtils.cpp index f51f3256ae6a..23a6733c4ba8 100644 --- a/Engine/Source/Runtime/Engine/Private/DataTableUtils.cpp +++ b/Engine/Source/Runtime/Engine/Private/DataTableUtils.cpp @@ -90,27 +90,6 @@ void AssignStringToProperty(const FString& InString, const UProperty* InProp, ui void GetPropertyValueAsStringDirect(const UProperty* InProp, const uint8* InData, const int32 InPortFlags, const EDataTableExportFlags InDTExportFlags, FString& OutString) { - if (!!(InDTExportFlags & EDataTableExportFlags::UsePrettyEnumNames)) - { - UEnum* Enum = nullptr; - int64 Val = 0; - if (const UEnumProperty* EnumProp = Cast(InProp)) - { - Enum = EnumProp->GetEnum(); - Val = EnumProp->GetUnderlyingProperty()->GetSignedIntPropertyValue(InData); - } - else if (const UByteProperty* ByteProp = Cast(InProp)) - { - Enum = ByteProp->GetIntPropertyEnum(); - Val = *InData; - } - - if (UUserDefinedEnum* UDEnum = Cast(Enum)) - { - OutString.Append(GetSourceString(UDEnum->GetDisplayNameTextByValue(Val))); - return; - } - } #if WITH_EDITOR if (InPortFlags & PPF_PropertyWindow) { @@ -249,7 +228,7 @@ FString DataTableUtils::AssignStringToPropertyDirect(const FString& InString, co FStringOutputDevice ImportError; if(InProp && IsSupportedTableProperty(InProp)) { - DataTableUtilsImpl::AssignStringToPropertyDirect(InString, InProp, InData, PPF_None, ImportError); + DataTableUtilsImpl::AssignStringToPropertyDirect(InString, InProp, InData, PPF_ExternalEditor, ImportError); } FString Error = ImportError; @@ -263,7 +242,7 @@ FString DataTableUtils::AssignStringToProperty(const FString& InString, const UP { if(InProp->ArrayDim == 1) { - DataTableUtilsImpl::AssignStringToProperty(InString, InProp, InData, 0, PPF_None, ImportError); + DataTableUtilsImpl::AssignStringToProperty(InString, InProp, InData, 0, PPF_ExternalEditor, ImportError); } else { @@ -305,7 +284,7 @@ FString DataTableUtils::GetPropertyValueAsStringDirect(const UProperty* InProp, if(InProp && IsSupportedTableProperty(InProp)) { - DataTableUtilsImpl::GetPropertyValueAsStringDirect(InProp, InData, PPF_None, InDTExportFlags, Result); + DataTableUtilsImpl::GetPropertyValueAsStringDirect(InProp, InData, PPF_ExternalEditor, InDTExportFlags, Result); } return Result; @@ -319,7 +298,7 @@ FString DataTableUtils::GetPropertyValueAsString(const UProperty* InProp, const { if(InProp->ArrayDim == 1) { - DataTableUtilsImpl::GetPropertyValueAsString(InProp, InData, 0, PPF_None, InDTExportFlags, Result); + DataTableUtilsImpl::GetPropertyValueAsString(InProp, InData, 0, PPF_ExternalEditor, InDTExportFlags, Result); } else { @@ -327,7 +306,7 @@ FString DataTableUtils::GetPropertyValueAsString(const UProperty* InProp, const for(int32 Index = 0; Index < InProp->ArrayDim; ++Index) { - DataTableUtilsImpl::GetPropertyValueAsString(InProp, InData, Index, PPF_Delimited, InDTExportFlags, Result); + DataTableUtilsImpl::GetPropertyValueAsString(InProp, InData, Index, PPF_Delimited | PPF_ExternalEditor, InDTExportFlags, Result); if((Index + 1) < InProp->ArrayDim) { @@ -349,7 +328,7 @@ FText DataTableUtils::GetPropertyValueAsTextDirect(const UProperty* InProp, cons if(InProp && IsSupportedTableProperty(InProp)) { FString ExportedString; - DataTableUtilsImpl::GetPropertyValueAsStringDirect(InProp, InData, PPF_PropertyWindow, EDataTableExportFlags::UsePrettyPropertyNames | EDataTableExportFlags::UsePrettyEnumNames | EDataTableExportFlags::UseJsonObjectsForStructs, ExportedString); + DataTableUtilsImpl::GetPropertyValueAsStringDirect(InProp, InData, PPF_PropertyWindow, EDataTableExportFlags::UseJsonObjectsForStructs, ExportedString); Result = FText::FromString(MoveTemp(ExportedString)); } @@ -367,7 +346,7 @@ FText DataTableUtils::GetPropertyValueAsText(const UProperty* InProp, const uint if(InProp->ArrayDim == 1) { - DataTableUtilsImpl::GetPropertyValueAsString(InProp, InData, 0, PPF_PropertyWindow, EDataTableExportFlags::UsePrettyPropertyNames | EDataTableExportFlags::UsePrettyEnumNames | EDataTableExportFlags::UseJsonObjectsForStructs, ExportedString); + DataTableUtilsImpl::GetPropertyValueAsString(InProp, InData, 0, PPF_PropertyWindow, EDataTableExportFlags::UseJsonObjectsForStructs, ExportedString); } else { @@ -375,7 +354,7 @@ FText DataTableUtils::GetPropertyValueAsText(const UProperty* InProp, const uint for(int32 Index = 0; Index < InProp->ArrayDim; ++Index) { - DataTableUtilsImpl::GetPropertyValueAsString(InProp, InData, Index, PPF_PropertyWindow | PPF_Delimited, EDataTableExportFlags::UsePrettyPropertyNames | EDataTableExportFlags::UsePrettyEnumNames | EDataTableExportFlags::UseJsonObjectsForStructs, ExportedString); + DataTableUtilsImpl::GetPropertyValueAsString(InProp, InData, Index, PPF_PropertyWindow | PPF_Delimited, EDataTableExportFlags::UseJsonObjectsForStructs, ExportedString); if((Index + 1) < InProp->ArrayDim) { @@ -453,11 +432,7 @@ FString DataTableUtils::GetPropertyExportName(const UProperty* Prop, const EData { return FString(); } - if (Prop->GetOwnerStruct()->IsA(UUserDefinedStruct::StaticClass()) && !!(InDTExportFlags & EDataTableExportFlags::UsePrettyPropertyNames)) - { - return GetPropertyDisplayName(Prop, Prop->GetName()); - } - return Prop->GetName(); + return Prop->GetAuthoredName(); } TArray DataTableUtils::GetPropertyImportNames(const UProperty* Prop) @@ -467,18 +442,19 @@ TArray DataTableUtils::GetPropertyImportNames(const UProperty* Prop) { Result.AddUnique(Prop->GetName()); } - Result.AddUnique(GetPropertyExportName(Prop, EDataTableExportFlags::UsePrettyPropertyNames)); + Result.AddUnique(GetPropertyExportName(Prop)); return Result; } -FString DataTableUtils::GetPropertyDisplayName(const UProperty* Prop, const FString& DefaultName) +FText DataTableUtils::GetPropertyDisplayName(const UProperty* Prop, const FString& DefaultName) { #if WITH_EDITOR - static const FName DisplayNameKey(TEXT("DisplayName")); - return (Prop && Prop->HasMetaData(DisplayNameKey)) ? Prop->GetMetaData(DisplayNameKey) : DefaultName; -#else // WITH_EDITOR - return DefaultName; -#endif // WITH_EDITOR + if (Prop) + { + return Prop->GetDisplayNameText(); + } +#endif // WITH_EDITOR + return FText::FromString(DefaultName); } TArray DataTableUtils::GetColumnDataAsString(const UDataTable* InTable, const FName& PropertyName, const EDataTableExportFlags InDTExportFlags) diff --git a/Engine/Source/Runtime/Engine/Private/DebugCameraController.cpp b/Engine/Source/Runtime/Engine/Private/DebugCameraController.cpp index 897a9dfdedb8..b9ac60f08823 100644 --- a/Engine/Source/Runtime/Engine/Private/DebugCameraController.cpp +++ b/Engine/Source/Runtime/Engine/Private/DebugCameraController.cpp @@ -60,6 +60,7 @@ ADebugCameraController::ADebugCameraController(const FObjectInitializer& ObjectI bEnableBufferVisualization = false; bEnableBufferVisualizationFullMode = false; bIsBufferVisualizationInputSetup = false; + bLastDisplayEnabled = true; LastViewModeIndex = VMI_Lit; LastViewModeSettingsIndex = 0; } @@ -308,7 +309,8 @@ ASpectatorPawn* ADebugCameraController::SpawnSpectatorPawn() SpawnParams.Owner = this; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; SpawnParams.ObjectFlags |= RF_Transient; // We never want to save spectator pawns into a map - SpawnedSpectator = GetWorld()->SpawnActor(ASpectatorPawn::StaticClass(), GetSpawnLocation(), GetControlRotation(), SpawnParams); + + SpawnedSpectator = GetWorld()->SpawnActor((*GameState->SpectatorClass ? *GameState->SpectatorClass : ASpectatorPawn::StaticClass()), GetSpawnLocation(), GetControlRotation(), SpawnParams); if (SpawnedSpectator) { SpawnedSpectator->PossessedBy(this); @@ -748,7 +750,6 @@ void ADebugCameraController::ToggleBufferVisualizationOverviewMode() if (UGameViewportClient* GameViewportClient = GetWorld()->GetGameViewport()) { bEnableBufferVisualization = !bEnableBufferVisualization; - bEnableBufferVisualizationFullMode = false; FString Cmd(TEXT("VIEWMODE ")); @@ -759,14 +760,18 @@ void ADebugCameraController::ToggleBufferVisualizationOverviewMode() TArray SelectedBuffers = GetBufferVisualizationOverviewTargets(); - if (LastSelectedBuffer.IsEmpty() || !SelectedBuffers.Contains(LastSelectedBuffer)) + if (CurrSelectedBuffer.IsEmpty() || !SelectedBuffers.Contains(CurrSelectedBuffer)) { GetNextBuffer(SelectedBuffers, 1); } + + bLastDisplayEnabled = IsDisplayEnabled(); + SetDisplay(false); } else { Cmd += GetViewModeName((EViewModeIndex)LastViewModeIndex); + SetDisplay(bLastDisplayEnabled); } GameViewportClient->ConsoleCommand(Cmd); @@ -781,11 +786,11 @@ void ADebugCameraController::UpdateVisualizeBufferPostProcessing(FFinalPostProce { if (bEnableBufferVisualization) { - FName TargetBufferName = *LastSelectedBuffer; - if (UMaterial* Material = GetBufferVisualizationData().GetMaterial(TargetBufferName)) + FString BufferMaterialName = GetSelectedBufferName(); + if (!BufferMaterialName.IsEmpty()) { InOutPostProcessingSettings.bBufferVisualizationOverviewTargetIsSelected = true; - InOutPostProcessingSettings.BufferVisualizationOverviewSelectedTargetMaterialName = Material->GetName(); + InOutPostProcessingSettings.BufferVisualizationOverviewSelectedTargetMaterialName = BufferMaterialName; return; } } @@ -811,13 +816,13 @@ void ADebugCameraController::GetNextBuffer(const TArray& OverviewBuffer { int32 BufferIndex = 0; - if (!LastSelectedBuffer.IsEmpty()) + if (!CurrSelectedBuffer.IsEmpty()) { bool bFoundIndex = false; for (int32 Index = 0; Index < OverviewBuffers.Num(); Index++) { - if (OverviewBuffers[Index] == LastSelectedBuffer) + if (OverviewBuffers[Index] == CurrSelectedBuffer) { BufferIndex = Index; bFoundIndex = true; @@ -827,17 +832,17 @@ void ADebugCameraController::GetNextBuffer(const TArray& OverviewBuffer if (!bFoundIndex) { - LastSelectedBuffer.Empty(); + CurrSelectedBuffer.Empty(); } } - if (LastSelectedBuffer.IsEmpty()) + if (CurrSelectedBuffer.IsEmpty()) { for (FString Buffer : OverviewBuffers) { if (!Buffer.IsEmpty()) { - LastSelectedBuffer = Buffer; + CurrSelectedBuffer = Buffer; break; } } @@ -869,7 +874,7 @@ void ADebugCameraController::GetNextBuffer(const TArray& OverviewBuffer { if (!OverviewBuffers[NextIndex].IsEmpty()) { - LastSelectedBuffer = OverviewBuffers[NextIndex]; + CurrSelectedBuffer = OverviewBuffers[NextIndex]; break; } NextIndex = Wrap(NextIndex + Step); @@ -898,6 +903,16 @@ void ADebugCameraController::BufferVisualizationMoveLeft() GetNextBuffer(-1); } +FString ADebugCameraController::GetSelectedBufferName() +{ + if (UMaterial* Material = GetBufferVisualizationData().GetMaterial(*CurrSelectedBuffer)) + { + return Material->GetName(); + } + + return TEXT(""); +} + void ADebugCameraController::ConsumeAxisMotion(float Val) { // Just ignore the axis motion. @@ -906,18 +921,23 @@ void ADebugCameraController::ConsumeAxisMotion(float Val) void ADebugCameraController::ToggleBufferVisualizationFullMode() { SetBufferVisualizationFullMode(!bEnableBufferVisualizationFullMode); - SetupBufferVisualizationOverviewInput(); } void ADebugCameraController::SetBufferVisualizationFullMode(bool bFullMode) { - bEnableBufferVisualizationFullMode = bFullMode; - - static IConsoleVariable* ICVar = IConsoleManager::Get().FindConsoleVariable(FBufferVisualizationData::GetVisualizationTargetConsoleCommandName()); - if (ICVar) + if (bEnableBufferVisualizationFullMode != bFullMode) { - static const FName EmptyName = NAME_None; - ICVar->Set(bFullMode ? *LastSelectedBuffer : *EmptyName.ToString(), ECVF_SetByCode); + bEnableBufferVisualizationFullMode = bFullMode; + + static IConsoleVariable* ICVar = IConsoleManager::Get().FindConsoleVariable(FBufferVisualizationData::GetVisualizationTargetConsoleCommandName()); + if (ICVar) + { + static const FName EmptyName = NAME_None; + ICVar->Set(bFullMode ? *CurrSelectedBuffer : *EmptyName.ToString(), ECVF_SetByCode); + } + + SetupBufferVisualizationOverviewInput(); + SetDisplay(bEnableBufferVisualizationFullMode); } } @@ -996,3 +1016,16 @@ void ADebugCameraController::ToggleDisplay() MyHUD->ShowHUD(); } } + +bool ADebugCameraController::IsDisplayEnabled() +{ + return (MyHUD && MyHUD->bShowHUD); +} + +void ADebugCameraController::SetDisplay(bool bEnabled) +{ + if (IsDisplayEnabled() != bEnabled) + { + ToggleDisplay(); + } +} diff --git a/Engine/Source/Runtime/Engine/Private/DebugCameraHUD.cpp b/Engine/Source/Runtime/Engine/Private/DebugCameraHUD.cpp index 7c762d5adb13..30ae2083aac3 100644 --- a/Engine/Source/Runtime/Engine/Private/DebugCameraHUD.cpp +++ b/Engine/Source/Runtime/Engine/Private/DebugCameraHUD.cpp @@ -66,6 +66,22 @@ void ADebugCameraHUD::PostRender() UFont* RenderFont = GEngine->GetSmallFont(); if( DCC != NULL ) { + if (DCC->bEnableBufferVisualizationFullMode) + { + RenderFont = GEngine->GetMediumFont(); + FFontRenderInfo FontRenderInfo = Canvas->CreateFontRenderInfo(false, true); + + Canvas->SetDrawColor(255, 255, 64, 255); + FString BufferText = DCC->GetSelectedBufferName(); + float xl, yl; + Canvas->StrLen(RenderFont, BufferText, xl, yl); + float X = Canvas->SizeX * 0.05f; + float Y = Canvas->SizeY < 3.0f * yl ? 0.0f : Canvas->SizeY - 3.0f * yl; + Canvas->DrawText(RenderFont, BufferText, X, Y, 1.f, 1.f, FontRenderInfo); + + return; + } + FFontRenderInfo FontRenderInfo = Canvas->CreateFontRenderInfo(false, true); Canvas->SetDrawColor(64, 64, 255, 255); @@ -199,7 +215,7 @@ void ADebugCameraHUD::PostRender() // controls display - yl += Y*10; + yl += Y*8; Canvas->SetDrawColor(64, 64, 255, 255); Canvas->DrawText(RenderFont, TEXT("Controls"), X, yl, 1.f, 1.f, FontRenderInfo); diff --git a/Engine/Source/Runtime/Engine/Private/DemoNetDriver.cpp b/Engine/Source/Runtime/Engine/Private/DemoNetDriver.cpp index 1668ce458717..ac29a91dc587 100644 --- a/Engine/Source/Runtime/Engine/Private/DemoNetDriver.cpp +++ b/Engine/Source/Runtime/Engine/Private/DemoNetDriver.cpp @@ -2672,9 +2672,7 @@ void UDemoNetDriver::TickDemoRecordFrame(float DeltaSeconds) // We check ActorInfo->LastNetUpdateTime < KINDA_SMALL_NUMBER to force at least one update for each actor const bool bWasRecentlyRelevant = (ActorInfo->LastNetUpdateTime < KINDA_SMALL_NUMBER) || ((Time - ActorInfo->LastNetUpdateTime) < RelevantTimeout); - bool bIsRelevant = !bUseNetRelevancy || Actor->bAlwaysRelevant || Actor == ClientConnection->PlayerController || ActorInfo->bForceRelevantNextUpdate; - - ActorInfo->bForceRelevantNextUpdate = false; + bool bIsRelevant = !bUseNetRelevancy || Actor->bAlwaysRelevant || Actor == ClientConnection->PlayerController || (ActorInfo->ForceRelevantFrame >= ReplicationFrame); if (!bIsRelevant) { @@ -2915,7 +2913,7 @@ bool UDemoNetDriver::ReplicatePrioritizedActor(const FActorPriority& ActorPriori const double ClampedExtraTime = FMath::Clamp(ExtraTime, 0.0, NetUpdateDelay); // Try to spread the updates across multiple frames to smooth out spikes. - ActorInfo->NextUpdateTime = (DemoCurrentTime + NextUpdateDelta - ClampedExtraTime + ((FMath::SRand() - 0.5) * Params.ServerTickTime)); + ActorInfo->NextUpdateTime = (DemoCurrentTime + NextUpdateDelta - ClampedExtraTime + ((UpdateDelayRandomStream.FRand() - 0.5) * Params.ServerTickTime)); Actor->CallPreReplication(this); @@ -5819,12 +5817,6 @@ void UDemoNetConnection::HandleClientPlayer(APlayerController* PC, UNetConnectio } } -TSharedPtr UDemoNetConnection::GetInternetAddr() -{ - // Does not use MappedClientConnections - return TSharedPtr(); -} - bool UDemoNetConnection::ClientHasInitializedLevelFor(const AActor* TestActor) const { // We save all currently streamed levels into the demo stream so we can force the demo playback client @@ -6316,8 +6308,25 @@ bool UDemoNetDriver::UpdateExternalDataForActor(AActor* Actor) bool UDemoNetDriver::ShouldReplicateFunction(AActor* Actor, UFunction* Function) const { - const bool bRecordingMulticast = (Function && Function->FunctionFlags & FUNC_NetMulticast) && IsRecording(); - return bRecordingMulticast || Super::ShouldReplicateFunction(Actor, Function); + bool bShouldRecordMulticast = (Function && Function->FunctionFlags & FUNC_NetMulticast) && IsRecording(); + if (bShouldRecordMulticast) + { + FString FuncPathName = GetPathNameSafe(Function); + int32 Idx = MulticastRecordOptions.IndexOfByPredicate([FuncPathName](const FMulticastRecordOptions& Options) { return (Options.FuncPathName == FuncPathName); }); + if (Idx != INDEX_NONE) + { + if (World && World->IsRecordingClientReplay()) + { + bShouldRecordMulticast = bShouldRecordMulticast && !MulticastRecordOptions[Idx].bClientSkip; + } + else + { + bShouldRecordMulticast = bShouldRecordMulticast && !MulticastRecordOptions[Idx].bServerSkip; + } + } + } + + return bShouldRecordMulticast || Super::ShouldReplicateFunction(Actor, Function); } bool UDemoNetDriver::ShouldReplicateActor(AActor* Actor) const diff --git a/Engine/Source/Runtime/Engine/Private/DistanceFieldAtlas.cpp b/Engine/Source/Runtime/Engine/Private/DistanceFieldAtlas.cpp index e186bd2ec18d..103f2e62dcb9 100644 --- a/Engine/Source/Runtime/Engine/Private/DistanceFieldAtlas.cpp +++ b/Engine/Source/Runtime/Engine/Private/DistanceFieldAtlas.cpp @@ -899,6 +899,11 @@ void FDistanceFieldAsyncQueue::AddReferencedObjects(FReferenceCollector& Collect } } +FString FDistanceFieldAsyncQueue::GetReferencerName() const +{ + return TEXT("FDistanceFieldAsyncQueue"); +} + void FDistanceFieldAsyncQueue::ProcessAsyncTasks() { #if WITH_EDITOR diff --git a/Engine/Source/Runtime/Engine/Private/EdGraph/EdGraphNode.cpp b/Engine/Source/Runtime/Engine/Private/EdGraph/EdGraphNode.cpp index 060e3236ff9e..2e48df87362c 100644 --- a/Engine/Source/Runtime/Engine/Private/EdGraph/EdGraphNode.cpp +++ b/Engine/Source/Runtime/Engine/Private/EdGraph/EdGraphNode.cpp @@ -14,7 +14,7 @@ #include "UObject/PropertyPortFlags.h" #include "ScopedTransaction.h" #include "FindInBlueprintManager.h" -#include "Editor/GraphEditor/Public/DiffResults.h" +#include "DiffResults.h" #endif #define LOCTEXT_NAMESPACE "EdGraph" @@ -158,10 +158,12 @@ UEdGraphNode::UEdGraphNode(const FObjectInitializer& ObjectInitializer) , bIsNodeEnabled_DEPRECATED(true) #if WITH_EDITORONLY_DATA , bCanResizeNode(false) -#endif // WITH_EDITORONLY_DATA + , bUnrelated(false) , bCommentBubblePinned(false) , bCommentBubbleVisible(false) , bCommentBubbleMakeVisible(false) +#endif // WITH_EDITORONLY_DATA + { #if WITH_EDITORONLY_DATA { static const FAutoRegisterLocalizationDataGatheringCallback AutomaticRegistrationOfLocalizationGatherer(UEdGraphNode::StaticClass(), &GatherGraphNodeForLocalization); } @@ -229,6 +231,7 @@ FString UEdGraphNode::GetPropertyNameAndValueForDiff(const UProperty* Prop, cons return FString::Printf(TEXT("%s: %s"), *FName::NameToDisplayString(Prop->GetName(), bIsBool), *ExportedStringValue); } + void UEdGraphNode::DiffProperties(UClass* StructA, UClass* StructB, UObject* DataA, UObject* DataB, FDiffResults& Results, FDiffSingleResult& Diff) const { // Find the common parent class in case the other node isn't of the same type @@ -238,10 +241,23 @@ void UEdGraphNode::DiffProperties(UClass* StructA, UClass* StructB, UObject* Dat ClassToViewAs = ClassToViewAs->GetSuperClass(); } - // Run through all the properties - for (TFieldIterator PropertyIt(ClassToViewAs, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) + DiffProperties(ClassToViewAs, ClassToViewAs, (uint8*)DataA, (uint8*)DataB, Results, Diff); +} + +void UEdGraphNode::DiffProperties(UStruct* StructA, UStruct* StructB, uint8* DataA, uint8* DataB, FDiffResults& Results, FDiffSingleResult& Diff) const +{ + // Run through all the properties in the first struct + for (TFieldIterator PropertyIt(StructA, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) { UProperty* Prop = *PropertyIt; + UProperty* PropB = StructB->FindPropertyByName(Prop->GetFName()); + + if (!PropB || Prop->GetClass() != PropB->GetClass()) + { + // Skip if properties don't match + continue; + } + // skip properties we cant see if (!Prop->HasAnyPropertyFlags(CPF_Edit | CPF_BlueprintVisible) || Prop->HasAnyPropertyFlags(CPF_Transient) || @@ -253,7 +269,7 @@ void UEdGraphNode::DiffProperties(UClass* StructA, UClass* StructB, UObject* Dat } const FString ValueStringA = GetPropertyNameAndValueForDiff(Prop, Prop->ContainerPtrToValuePtr(DataA)); - const FString ValueStringB = GetPropertyNameAndValueForDiff(Prop, Prop->ContainerPtrToValuePtr(DataB)); + const FString ValueStringB = GetPropertyNameAndValueForDiff(PropB, PropB->ContainerPtrToValuePtr(DataB)); if (ValueStringA != ValueStringB) { @@ -521,9 +537,54 @@ bool UEdGraphNode::IsDeprecated() const return GetClass()->HasAnyClassFlags(CLASS_Deprecated); } +FEdGraphNodeDeprecationResponse UEdGraphNode::GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const +{ + FEdGraphNodeDeprecationResponse Response; + + if (DeprecationType == EEdGraphNodeDeprecationType::NodeTypeIsDeprecated) + { + Response.MessageType = EEdGraphNodeDeprecationMessageType::Warning; + Response.MessageText = NSLOCTEXT("EdGraphCompiler", "NodeDeprecated_Warning", "@@ is deprecated; please replace or remove it."); + } + else if (DeprecationType == EEdGraphNodeDeprecationType::NodeHasDeprecatedReference) + { + Response.MessageType = EEdGraphNodeDeprecationMessageType::Warning; + Response.MessageText = NSLOCTEXT("EdGraphCompiler", "NodeDeprecatedReference_Note", "@@ has a deprecated reference; please replace or remove it."); + } + + return Response; +} + +// Deprecated; implemented for backwards-compatibility. +bool UEdGraphNode::ShouldWarnOnDeprecation() const +{ + if (IsDeprecated()) + { + return GetDeprecationResponse(EEdGraphNodeDeprecationType::NodeTypeIsDeprecated).MessageType == EEdGraphNodeDeprecationMessageType::Warning; + } + else if (HasDeprecatedReference()) + { + return GetDeprecationResponse(EEdGraphNodeDeprecationType::NodeHasDeprecatedReference).MessageType == EEdGraphNodeDeprecationMessageType::Warning; + } + + return false; +} + +// Deprecated; implemented for backwards-compatibility. FString UEdGraphNode::GetDeprecationMessage() const { - return NSLOCTEXT("EdGraphCompiler", "NodeDeprecated_Warning", "@@ is deprecated; please replace or remove it.").ToString(); + FText MessageText; + + if (IsDeprecated()) + { + MessageText = GetDeprecationResponse(EEdGraphNodeDeprecationType::NodeTypeIsDeprecated).MessageText; + } + else if (HasDeprecatedReference()) + { + MessageText = GetDeprecationResponse(EEdGraphNodeDeprecationType::NodeHasDeprecatedReference).MessageText; + } + + return MessageText.ToString(); } void UEdGraphNode::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) diff --git a/Engine/Source/Runtime/Engine/Private/FontFace.cpp b/Engine/Source/Runtime/Engine/Private/FontFace.cpp index fd6e0fb281cd..f15dd7502187 100644 --- a/Engine/Source/Runtime/Engine/Private/FontFace.cpp +++ b/Engine/Source/Runtime/Engine/Private/FontFace.cpp @@ -122,22 +122,26 @@ void UFontFace::GetAssetRegistryTags(TArray& OutTags) const OutTags.Add(FAssetRegistryTag(SourceFileTagName(), ImportInfo.ToJson(), FAssetRegistryTag::TT_Hidden)); } -void UFontFace::CookAdditionalFiles(const TCHAR* PackageFilename, const ITargetPlatform* TargetPlatform) +void UFontFace::CookAdditionalFilesOverride(const TCHAR* PackageFilename, const ITargetPlatform* TargetPlatform, + TFunctionRef WriteAdditionalFile) { - Super::CookAdditionalFiles(PackageFilename, TargetPlatform); - if (LoadingPolicy != EFontLoadingPolicy::Inline) { - // Iterative COTF can't handle the .ufont files generated when this UFontFace is within a UFont asset (rather than its own asset) so emit a warning about that + // Iterative COTF can't handle the .ufont files generated when this UFontFace is within a UFont asset (rather than its own asset) if (UFont* OuterFont = GetTypedOuter()) { - UE_LOG(LogFontFace, Warning, TEXT("The font asset '%s' contains nested font faces which can cause issues for iterative cook-on-the-fly. Please edit the font asset and split the font faces into their own assets."), *OuterFont->GetPathName()); + UE_LOG(LogFontFace, Warning, + TEXT("The font asset '%s' contains nested font faces which can cause issues for iterative cook-on-the-fly." + "Please edit the font asset and split the font faces into their own assets."), + *OuterFont->GetPathName()); } // We replace the package name with the cooked font face name // Note: This must match the replacement logic in UFontFace::GetCookedFilename const FString CookedFontFilename = FPaths::GetPath(PackageFilename) / GetName() + TEXT(".ufont"); - FFileHelper::SaveArrayToFile(FontFaceData->GetData(), *CookedFontFilename); + const TArray& Data = FontFaceData->GetData(); + const int32 NumBytes = Data.Num() * Data.GetTypeSize(); + WriteAdditionalFile(*CookedFontFilename, (void*)Data.GetData(), NumBytes); } } #endif // WITH_EDITOR diff --git a/Engine/Source/Runtime/Engine/Private/GameFramework/AsyncActionHandleSaveGame.cpp b/Engine/Source/Runtime/Engine/Private/GameFramework/AsyncActionHandleSaveGame.cpp new file mode 100644 index 000000000000..77838959ab46 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Private/GameFramework/AsyncActionHandleSaveGame.cpp @@ -0,0 +1,64 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "GameFramework/AsyncActionHandleSaveGame.h" +#include "Kismet/GameplayStatics.h" + +UAsyncActionHandleSaveGame* UAsyncActionHandleSaveGame::AsyncSaveGameToSlot(UObject* WorldContextObject, USaveGame* SaveGameObject, const FString& SlotName, const int32 UserIndex) +{ + UAsyncActionHandleSaveGame* Action = NewObject(); + Action->Operation = ESaveGameOperation::Save; + Action->SaveGameObject = SaveGameObject; + Action->SlotName = SlotName; + Action->UserIndex = UserIndex; + Action->RegisterWithGameInstance(WorldContextObject); + + return Action; +} + +UAsyncActionHandleSaveGame* UAsyncActionHandleSaveGame::AsyncLoadGameFromSlot(UObject* WorldContextObject, const FString& SlotName, const int32 UserIndex) +{ + UAsyncActionHandleSaveGame* Action = NewObject(); + Action->Operation = ESaveGameOperation::Load; + Action->SaveGameObject = nullptr; + Action->SlotName = SlotName; + Action->UserIndex = UserIndex; + Action->RegisterWithGameInstance(WorldContextObject); + + return Action; +} + +void UAsyncActionHandleSaveGame::Activate() +{ + switch (Operation) + { + case ESaveGameOperation::Save: + UGameplayStatics::AsyncSaveGameToSlot(SaveGameObject, SlotName, UserIndex, FAsyncSaveGameToSlotDelegate::CreateUObject(this, &UAsyncActionHandleSaveGame::HandleAsyncSave)); + return; + case ESaveGameOperation::Load: + UGameplayStatics::AsyncLoadGameFromSlot(SlotName, UserIndex, FAsyncLoadGameFromSlotDelegate::CreateUObject(this, &UAsyncActionHandleSaveGame::HandleAsyncLoad)); + return; + } + + UE_LOG(LogScript, Error, TEXT("UAsyncActionHandleSaveGame Created with invalid operation!")); + + ExecuteCompleted(false); +} + +void UAsyncActionHandleSaveGame::HandleAsyncSave(const FString& InSlotName, const int32 InUserIndex, bool bSuccess) +{ + ExecuteCompleted(bSuccess); +} + +void UAsyncActionHandleSaveGame::HandleAsyncLoad(const FString& InSlotName, const int32 InUserIndex, USaveGame* LoadedSave) +{ + SaveGameObject = LoadedSave; + ExecuteCompleted(SaveGameObject != nullptr); +} + +void UAsyncActionHandleSaveGame::ExecuteCompleted(bool bSuccess) +{ + Completed.Broadcast(SaveGameObject, bSuccess); + + SaveGameObject = nullptr; + SetReadyToDestroy(); +} diff --git a/Engine/Source/Runtime/Engine/Private/GameFramework/SpringArmComponent.cpp b/Engine/Source/Runtime/Engine/Private/GameFramework/SpringArmComponent.cpp index bd8b45029669..a702d54ffb59 100644 --- a/Engine/Source/Runtime/Engine/Private/GameFramework/SpringArmComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/GameFramework/SpringArmComponent.cpp @@ -42,9 +42,14 @@ USpringArmComponent::USpringArmComponent(const FObjectInitializer& ObjectInitial UnfixedCameraPosition = FVector::ZeroVector; } +FRotator USpringArmComponent::GetDesiredRotation() const +{ + return GetComponentRotation(); +} + FRotator USpringArmComponent::GetTargetRotation() const { - FRotator DesiredRot = GetComponentRotation(); + FRotator DesiredRot = GetDesiredRotation(); if (bUsePawnControlRotation) { diff --git a/Engine/Source/Runtime/Engine/Private/GameModeBase.cpp b/Engine/Source/Runtime/Engine/Private/GameModeBase.cpp index bfe2ac0170c4..722e8f82e47f 100644 --- a/Engine/Source/Runtime/Engine/Private/GameModeBase.cpp +++ b/Engine/Source/Runtime/Engine/Private/GameModeBase.cpp @@ -444,37 +444,24 @@ void AGameModeBase::ProcessServerTravel(const FString& URL, bool bAbsolute) #if WITH_SERVER_CODE StartToLeaveMap(); - // Force an old style load screen if the server has been up for a long time so that TimeSeconds doesn't overflow and break everything - bool bSeamless = (bUseSeamlessTravel && GetWorld()->TimeSeconds < 172800.0f); // 172800 seconds == 48 hours - - FString NextMap; - if (URL.ToUpper().Contains(TEXT("?RESTART"))) - { - NextMap = UWorld::RemovePIEPrefix(GetOutermost()->GetName()); - } - else - { - int32 OptionStart = URL.Find(TEXT("?")); - if (OptionStart == INDEX_NONE) - { - NextMap = URL; - } - else - { - NextMap = URL.Left(OptionStart); - } - } - - FGuid NextMapGuid = UEngine::GetPackageGuid(FName(*NextMap), GetWorld()->IsPlayInEditor()); - - // Notify clients we're switching level and give them time to receive. - FString URLMod = URL; - APlayerController* LocalPlayer = ProcessClientTravel(URLMod, NextMapGuid, bSeamless, bAbsolute); - UE_LOG(LogGameMode, Log, TEXT("ProcessServerTravel: %s"), *URL); UWorld* World = GetWorld(); check(World); - World->NextURL = URL; + FWorldContext& WorldContext = GEngine->GetWorldContextFromWorldChecked(World); + + // Force an old style load screen if the server has been up for a long time so that TimeSeconds doesn't overflow and break everything + bool bSeamless = (bUseSeamlessTravel && GetWorld()->TimeSeconds < 172800.0f); // 172800 seconds == 48 hours + + // Compute the next URL, and pull the map out of it. This handles short->long package name conversion + FURL NextURL = FURL(&WorldContext.LastURL, *URL, bAbsolute ? TRAVEL_Absolute : TRAVEL_Relative); + + FGuid NextMapGuid = UEngine::GetPackageGuid(FName(*NextURL.Map), GetWorld()->IsPlayInEditor()); + + // Notify clients we're switching level and give them time to receive. + FString URLMod = NextURL.ToString(); + APlayerController* LocalPlayer = ProcessClientTravel(URLMod, NextMapGuid, bSeamless, bAbsolute); + + World->NextURL = URLMod; ENetMode NetMode = GetNetMode(); if (bSeamless) diff --git a/Engine/Source/Runtime/Engine/Private/GameplayStatics.cpp b/Engine/Source/Runtime/Engine/Private/GameplayStatics.cpp index 4ee5c7231ed6..8fbf91536626 100644 --- a/Engine/Source/Runtime/Engine/Private/GameplayStatics.cpp +++ b/Engine/Source/Runtime/Engine/Private/GameplayStatics.cpp @@ -41,6 +41,7 @@ #include "PhysicsEngine/BodySetup.h" #include "Misc/EngineVersion.h" #include "ContentStreaming.h" +#include "Async/Async.h" #include "Engine/SceneCapture2D.h" #include "Components/SceneCaptureComponent2D.h" @@ -392,6 +393,34 @@ bool UGameplayStatics::GetEnableWorldRendering(const UObject* WorldContextObject return false; } +EMouseCaptureMode UGameplayStatics::GetViewportMouseCaptureMode(const UObject* WorldContextObject) +{ + UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); + if (World) + { + UGameViewportClient* const GameViewportClient = World->GetGameViewport(); + if (GameViewportClient) + { + return GameViewportClient->CaptureMouseOnClick(); + } + } + + return EMouseCaptureMode::NoCapture; +} + +void UGameplayStatics::SetViewportMouseCaptureMode(const UObject* WorldContextObject, const EMouseCaptureMode MouseCaptureMode) +{ + UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); + if (World) + { + UGameViewportClient* const GameViewportClient = World->GetGameViewport(); + if (GameViewportClient) + { + GameViewportClient->SetCaptureMouseOnClick(MouseCaptureMode); + } + } +} + /** @RETURN True if weapon trace from Origin hits component VictimComp. OutHitResult will contain properties of the hit. */ static bool ComponentIsDamageableFrom(UPrimitiveComponent* VictimComp, FVector const& Origin, AActor const* IgnoredActor, const TArray& IgnoreActors, ECollisionChannel TraceChannel, FHitResult& OutHitResult) { @@ -782,21 +811,39 @@ void UGameplayStatics::GetActorArrayBounds(const TArray& Actors, bool b } } +AActor* UGameplayStatics::GetActorOfClass(const UObject* WorldContextObject, TSubclassOf ActorClass) +{ + QUICK_SCOPE_CYCLE_COUNTER(UGameplayStatics_GetActorOfClass); + + // We do nothing if no is class provided + if (ActorClass) + { + if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)) + { + for (TActorIterator It(World, ActorClass); It; ++It) + { + AActor* Actor = *It; + return Actor; + } + } + } + + return nullptr; +} + void UGameplayStatics::GetAllActorsOfClass(const UObject* WorldContextObject, TSubclassOf ActorClass, TArray& OutActors) { QUICK_SCOPE_CYCLE_COUNTER(UGameplayStatics_GetAllActorsOfClass); OutActors.Reset(); - UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); - // We do nothing if no is class provided, rather than giving ALL actors! - if (ActorClass && World) + if (ActorClass) + { + if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)) { for(TActorIterator It(World, ActorClass); It; ++It) { AActor* Actor = *It; - if(!Actor->IsPendingKill()) - { OutActors.Add(Actor); } } @@ -805,44 +852,47 @@ void UGameplayStatics::GetAllActorsOfClass(const UObject* WorldContextObject, TS void UGameplayStatics::GetAllActorsWithInterface(const UObject* WorldContextObject, TSubclassOf Interface, TArray& OutActors) { - QUICK_SCOPE_CYCLE_COUNTER(UGameplayStatics_GetAllActorsWithTag); - OutActors.Empty(); + QUICK_SCOPE_CYCLE_COUNTER(UGameplayStatics_GetAllActorsWithInterface); + OutActors.Reset(); - UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); - // We do nothing if not class provided, rather than giving ALL actors! - if (Interface && World) + // We do nothing if no interface provided, rather than giving ALL actors! + if (Interface) + { + if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)) { for(FActorIterator It(World); It; ++It) { AActor* Actor = *It; - if (Actor && !Actor->IsPendingKill() && Actor->GetClass()->ImplementsInterface(Interface)) + if (Actor->GetClass()->ImplementsInterface(Interface)) { OutActors.Add(Actor); } } } } +} void UGameplayStatics::GetAllActorsWithTag(const UObject* WorldContextObject, FName Tag, TArray& OutActors) { QUICK_SCOPE_CYCLE_COUNTER(UGameplayStatics_GetAllActorsWithTag); - OutActors.Empty(); - - UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); + OutActors.Reset(); // We do nothing if no tag is provided, rather than giving ALL actors! - if (!Tag.IsNone() && World) + if (!Tag.IsNone()) + { + if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)) { for (FActorIterator It(World); It; ++It) { AActor* Actor = *It; - if (Actor && !Actor->IsPendingKill() && Actor->ActorHasTag(Tag)) + if (Actor->ActorHasTag(Tag)) { OutActors.Add(Actor); } } } } +} void UGameplayStatics::PlayWorldCameraShake(const UObject* WorldContextObject, TSubclassOf Shake, FVector Epicenter, float InnerRadius, float OuterRadius, float Falloff, bool bOrientShakeTowardsEpicenter) { @@ -1798,15 +1848,6 @@ USaveGame* UGameplayStatics::CreateSaveGameObject(TSubclassOf SaveGam return nullptr; } -USaveGame* UGameplayStatics::CreateSaveGameObjectFromBlueprint(UBlueprint* SaveGameBlueprint) -{ - if (SaveGameBlueprint && SaveGameBlueprint->GeneratedClass && SaveGameBlueprint->GeneratedClass->IsChildOf(USaveGame::StaticClass())) - { - return NewObject(GetTransientPackage(), SaveGameBlueprint->GeneratedClass); - } - return nullptr; -} - bool UGameplayStatics::SaveGameToMemory(USaveGame* SaveGameObject, TArray& OutSaveData ) { FMemoryWriter MemoryWriter(OutSaveData, true); @@ -1834,24 +1875,33 @@ bool UGameplayStatics::SaveDataToSlot(const TArray& InSaveData, const FSt return false; } -bool UGameplayStatics::SaveGameToSlot(USaveGame* SaveGameObject, const FString& SlotName, const int32 UserIndex) -{ - ISaveGameSystem* SaveSystem = IPlatformFeaturesModule::Get().GetSaveGameSystem(); - // If we have a system and an object to save and a save name... - if(SaveSystem && SaveGameObject && (SlotName.Len() > 0)) +void UGameplayStatics::AsyncSaveGameToSlot(USaveGame* SaveGameObject, const FString& SlotName, const int32 UserIndex, FAsyncSaveGameToSlotDelegate SavedDelegate) { TArray ObjectBytes; - FMemoryWriter MemoryWriter(ObjectBytes, true); + SaveGameToMemory(SaveGameObject, ObjectBytes); - FSaveGameHeader SaveHeader(SaveGameObject->GetClass()); - SaveHeader.Write(MemoryWriter); + AsyncTask(ENamedThreads::AnyHiPriThreadNormalTask, [SlotName, UserIndex, SavedDelegate, ObjectBytes]() + { + bool bSuccess = SaveDataToSlot(ObjectBytes, SlotName, UserIndex); - // Then save the object state, replacing object refs and names with strings - FObjectAndNameAsStringProxyArchive Ar(MemoryWriter, false); - SaveGameObject->Serialize(Ar); + // Now schedule the callback on the game thread, but only if it was bound to anything + if (SavedDelegate.IsBound()) + { + AsyncTask(ENamedThreads::GameThread, [SlotName, UserIndex, SavedDelegate, bSuccess]() + { + SavedDelegate.ExecuteIfBound(SlotName, UserIndex, bSuccess); + }); + } + }); +} - // Stuff that data into the save system with the desired file name - return SaveSystem->SaveGame(false, *SlotName, UserIndex, ObjectBytes); +bool UGameplayStatics::SaveGameToSlot(USaveGame* SaveGameObject, const FString& SlotName, const int32 UserIndex) +{ + // This is a wrapper around the functions reading to/from a byte array + TArray ObjectBytes; + if (SaveGameToMemory(SaveGameObject, ObjectBytes)) + { + return SaveDataToSlot(ObjectBytes, SlotName, UserIndex); } return false; } @@ -1874,26 +1924,14 @@ bool UGameplayStatics::DeleteGameInSlot(const FString& SlotName, const int32 Use return false; } -USaveGame* UGameplayStatics::LoadGameFromSlot(const FString& SlotName, const int32 UserIndex) -{ - ISaveGameSystem* SaveSystem = IPlatformFeaturesModule::Get().GetSaveGameSystem(); - // If we have a save system and a valid name.. - if (SaveSystem && (SlotName.Len() > 0)) +USaveGame* UGameplayStatics::LoadGameFromMemory(const TArray& InSaveData) { - // Load raw data from slot - TArray ObjectBytes; - bool bSuccess = SaveSystem->LoadGame(false, *SlotName, UserIndex, ObjectBytes); - if (bSuccess) + if (InSaveData.Num() == 0) { - return LoadGameFromMemory(ObjectBytes); - } - } - + // Empty buffer, return instead of causing a bad serialize that could crash return nullptr; } -USaveGame* UGameplayStatics::LoadGameFromMemory(const TArray& InSaveData) -{ USaveGame* OutSaveGameObject = nullptr; FMemoryReader MemoryReader(InSaveData, true); @@ -1920,6 +1958,57 @@ USaveGame* UGameplayStatics::LoadGameFromMemory(const TArray& InSaveData) return OutSaveGameObject; } +bool UGameplayStatics::LoadDataFromSlot(TArray& OutSaveData, const FString& SlotName, const int32 UserIndex) +{ + ISaveGameSystem* SaveSystem = IPlatformFeaturesModule::Get().GetSaveGameSystem(); + // If we have a save system and a valid name.. + if (SaveSystem && (SlotName.Len() > 0)) + { + if (SaveSystem->LoadGame(false, *SlotName, UserIndex, OutSaveData)) + { + return true; + } + } + + // Clear buffer on a failed read + OutSaveData.Reset(); + return false; +} + +void UGameplayStatics::AsyncLoadGameFromSlot(const FString& SlotName, const int32 UserIndex, FAsyncLoadGameFromSlotDelegate LoadedDelegate) +{ + AsyncTask(ENamedThreads::AnyHiPriThreadNormalTask, [SlotName, UserIndex, LoadedDelegate]() + { + // Do the actual I/O on the background thread + TArray ObjectBytes; + LoadDataFromSlot(ObjectBytes, SlotName, UserIndex); + + // Now schedule the serialize and callback on the game thread + AsyncTask(ENamedThreads::GameThread, [SlotName, UserIndex, LoadedDelegate, ObjectBytes]() + { + USaveGame* LoadedGame = nullptr; + if (ObjectBytes.Num() > 0) + { + LoadedGame = LoadGameFromMemory(ObjectBytes); + } + + LoadedDelegate.ExecuteIfBound(SlotName, UserIndex, LoadedGame); + }); + }); +} + +USaveGame* UGameplayStatics::LoadGameFromSlot(const FString& SlotName, const int32 UserIndex) +{ + // This is a wrapper around the functions reading to/from a byte array + TArray ObjectBytes; + if (LoadDataFromSlot(ObjectBytes, SlotName, UserIndex)) + { + return LoadGameFromMemory(ObjectBytes); + } + + return nullptr; +} + FMemoryReader UGameplayStatics::StripSaveGameHeader(const TArray& SaveData) { FMemoryReader MemoryReader(SaveData, /*bIsPersistent =*/true); diff --git a/Engine/Source/Runtime/Engine/Private/HierarchicalInstancedStaticMesh.cpp b/Engine/Source/Runtime/Engine/Private/HierarchicalInstancedStaticMesh.cpp index 29511a960007..dfd756e4cf3c 100644 --- a/Engine/Source/Runtime/Engine/Private/HierarchicalInstancedStaticMesh.cpp +++ b/Engine/Source/Runtime/Engine/Private/HierarchicalInstancedStaticMesh.cpp @@ -2381,19 +2381,25 @@ void UHierarchicalInstancedStaticMeshComponent::ClearInstances() InstanceUpdateCmdBuffer.Reset(); - // Hide all instance until the build tree is completed - int32 NumInstances = PerInstanceSMData.Num(); - - for (int32 Index = 0; Index < NumInstances; ++Index) + // Don't try to queue hide command if there is no render instances + if (PerInstanceRenderData.IsValid() && PerInstanceRenderData->InstanceBuffer_GameThread->GetNumInstances() > 0) { - int32 RenderIndex = InstanceReorderTable.IsValidIndex(Index) ? InstanceReorderTable[Index] : Index; - if (RenderIndex == INDEX_NONE) - { - // could be skipped by density settings - continue; - } + // Hide all instance until the build tree is completed but if there is a mismatch between game thread data and render thread data, only add command matching render thread data, + // this can happen in a case where you perform many time add, clear, add, clear, in the same frame, so you might get a mismatch between both thread data + int32 NumInstances = FMath::Clamp(PerInstanceSMData.Num(), 0, PerInstanceRenderData->InstanceBuffer_GameThread->GetNumInstances()); - InstanceUpdateCmdBuffer.HideInstance(RenderIndex); + for (int32 Index = 0; Index < NumInstances; ++Index) + { + int32 RenderIndex = InstanceReorderTable.IsValidIndex(Index) ? InstanceReorderTable[Index] : Index; + + if (RenderIndex == INDEX_NONE) + { + // could be skipped by density settings + continue; + } + + InstanceUpdateCmdBuffer.HideInstance(RenderIndex); + } } // Clear all the per-instance data @@ -3034,9 +3040,10 @@ void UHierarchicalInstancedStaticMeshComponent::OnPostLoadPerInstanceData() ULevel* OwnerLevel = Owner->GetLevel(); UWorld* OwnerWorld = OwnerLevel ? OwnerLevel->OwningWorld : nullptr; - if (OwnerWorld && OwnerWorld->GetActiveLightingScenario() != nullptr && OwnerWorld->GetActiveLightingScenario() != OwnerLevel) + //update the instance data if the lighting scenario isn't the owner level or if the reorder table do not match the per instance sm data + if ((OwnerWorld && OwnerWorld->GetActiveLightingScenario() != nullptr && OwnerWorld->GetActiveLightingScenario() != OwnerLevel) + || (PerInstanceSMData.Num() > 0 && PerInstanceSMData.Num() != InstanceReorderTable.Num())) { - //update the instance data if the lighting scenario isn't the owner level bForceTreeBuild = true; } diff --git a/Engine/Source/Runtime/Engine/Private/InheritableComponentHandler.cpp b/Engine/Source/Runtime/Engine/Private/InheritableComponentHandler.cpp index 38f4812a1891..ce3dfc9baeaa 100644 --- a/Engine/Source/Runtime/Engine/Private/InheritableComponentHandler.cpp +++ b/Engine/Source/Runtime/Engine/Private/InheritableComponentHandler.cpp @@ -77,7 +77,7 @@ void UInheritableComponentHandler::PostLoad() } #if WITH_EDITOR -UActorComponent* UInheritableComponentHandler::CreateOverridenComponentTemplate(FComponentKey Key) +UActorComponent* UInheritableComponentHandler::CreateOverridenComponentTemplate(const FComponentKey& Key) { for (int32 Index = 0; Index < Records.Num(); ++Index) { @@ -93,7 +93,7 @@ UActorComponent* UInheritableComponentHandler::CreateOverridenComponentTemplate( } } - auto BestArchetype = FindBestArchetype(Key); + UActorComponent* BestArchetype = FindBestArchetype(Key); if (!BestArchetype) { UE_LOG(LogBlueprint, Warning, TEXT("CreateOverridenComponentTemplate '%s': cannot find archetype for component '%s' from '%s'"), @@ -144,30 +144,34 @@ UActorComponent* UInheritableComponentHandler::CreateOverridenComponentTemplate( } // Clear transient flag if it was transient before and re copy off archetype - if (NewComponentTemplate->HasAnyFlags(RF_Transient) && UnnecessaryComponents.Contains(NewComponentTemplate)) + if (NewComponentTemplate->HasAnyFlags(RF_Transient)) + { + const int32 ComponentIndex = UnnecessaryComponents.Find(NewComponentTemplate); + if (ComponentIndex != INDEX_NONE) { NewComponentTemplate->ClearFlags(RF_Transient); - UnnecessaryComponents.Remove(NewComponentTemplate); + UnnecessaryComponents.RemoveAtSwap(ComponentIndex); UEngine::FCopyPropertiesForUnrelatedObjectsParams CopyParams; CopyParams.bDoDelta = false; UEngine::CopyPropertiesForUnrelatedObjects(BestArchetype, NewComponentTemplate, CopyParams); } + } FComponentOverrideRecord NewRecord; NewRecord.ComponentKey = Key; NewRecord.ComponentClass = NewComponentTemplate->GetClass(); NewRecord.ComponentTemplate = NewComponentTemplate; - Records.Add(NewRecord); + Records.Emplace(MoveTemp(NewRecord)); return NewComponentTemplate; } -void UInheritableComponentHandler::RemoveOverridenComponentTemplate(FComponentKey Key) +void UInheritableComponentHandler::RemoveOverridenComponentTemplate(const FComponentKey& Key) { for (int32 Index = 0; Index < Records.Num(); ++Index) { - FComponentOverrideRecord& Record = Records[Index]; + const FComponentOverrideRecord& Record = Records[Index]; if (Record.ComponentKey.Match(Key)) { Record.ComponentTemplate->MarkPendingKill(); // hack needed to be able to identify if NewObject returns this back to us in the future @@ -179,9 +183,9 @@ void UInheritableComponentHandler::RemoveOverridenComponentTemplate(FComponentKe void UInheritableComponentHandler::UpdateOwnerClass(UBlueprintGeneratedClass* OwnerClass) { - for (auto& Record : Records) + for (FComponentOverrideRecord& Record : Records) { - auto OldComponentTemplate = Record.ComponentTemplate; + UActorComponent* OldComponentTemplate = Record.ComponentTemplate; if (OldComponentTemplate && (OwnerClass != OldComponentTemplate->GetOuter())) { Record.ComponentTemplate = DuplicateObject(OldComponentTemplate, OwnerClass, OldComponentTemplate->GetFName()); @@ -248,7 +252,7 @@ void UInheritableComponentHandler::ValidateTemplates() bool UInheritableComponentHandler::IsValid() const { - for (auto& Record : Records) + for (const FComponentOverrideRecord& Record : Records) { if (!IsRecordValid(Record)) { @@ -358,18 +362,18 @@ bool UInheritableComponentHandler::IsRecordNecessary(const FComponentOverrideRec return false; } - auto ChildComponentTemplate = Record.ComponentTemplate; - auto ParentComponentTemplate = FindBestArchetype(Record.ComponentKey); + UActorComponent* ChildComponentTemplate = Record.ComponentTemplate; + UActorComponent* ParentComponentTemplate = FindBestArchetype(Record.ComponentKey); check(ChildComponentTemplate && ParentComponentTemplate && (ParentComponentTemplate != ChildComponentTemplate)); return !FComponentComparisonHelper::AreIdentical(ChildComponentTemplate, ParentComponentTemplate); } } -UActorComponent* UInheritableComponentHandler::FindBestArchetype(FComponentKey Key) const +UActorComponent* UInheritableComponentHandler::FindBestArchetype(const FComponentKey& Key) const { UActorComponent* ClosestArchetype = nullptr; - auto ActualBPGC = Cast(GetOuter()); + UBlueprintGeneratedClass* ActualBPGC = Cast(GetOuter()); if (ActualBPGC && Key.GetComponentOwner() && (ActualBPGC != Key.GetComponentOwner())) { ActualBPGC = Cast(ActualBPGC->GetSuperClass()); @@ -391,9 +395,9 @@ UActorComponent* UInheritableComponentHandler::FindBestArchetype(FComponentKey K return ClosestArchetype; } -bool UInheritableComponentHandler::RefreshTemplateName(FComponentKey OldKey) +bool UInheritableComponentHandler::RefreshTemplateName(const FComponentKey& OldKey) { - for (auto& Record : Records) + for (FComponentOverrideRecord& Record : Records) { if (Record.ComponentKey.Match(OldKey)) { @@ -406,7 +410,7 @@ bool UInheritableComponentHandler::RefreshTemplateName(FComponentKey OldKey) FComponentKey UInheritableComponentHandler::FindKey(UActorComponent* ComponentTemplate) const { - for (auto& Record : Records) + for (const FComponentOverrideRecord& Record : Records) { if (Record.ComponentTemplate == ComponentTemplate) { @@ -420,12 +424,11 @@ FComponentKey UInheritableComponentHandler::FindKey(UActorComponent* ComponentTe void UInheritableComponentHandler::PreloadAllTempates() { - for (auto Record : Records) + for (const FComponentOverrideRecord& Record : Records) { if (Record.ComponentTemplate && Record.ComponentTemplate->HasAllFlags(RF_NeedLoad)) { - auto Linker = Record.ComponentTemplate->GetLinker(); - if (Linker) + if (FLinkerLoad* Linker = Record.ComponentTemplate->GetLinker()) { Linker->Preload(Record.ComponentTemplate); } @@ -437,8 +440,7 @@ void UInheritableComponentHandler::PreloadAll() { if (HasAllFlags(RF_NeedLoad)) { - auto Linker = GetLinker(); - if (Linker) + if (FLinkerLoad* Linker = GetLinker()) { Linker->Preload(this); } @@ -458,21 +460,21 @@ FComponentKey UInheritableComponentHandler::FindKey(const FName VariableName) co return FComponentKey(); } -UActorComponent* UInheritableComponentHandler::GetOverridenComponentTemplate(FComponentKey Key) const +UActorComponent* UInheritableComponentHandler::GetOverridenComponentTemplate(const FComponentKey& Key) const { - auto Record = FindRecord(Key); + const FComponentOverrideRecord* Record = FindRecord(Key); return Record ? Record->ComponentTemplate : nullptr; } -const FBlueprintCookedComponentInstancingData* UInheritableComponentHandler::GetOverridenComponentTemplateData(FComponentKey Key) const +const FBlueprintCookedComponentInstancingData* UInheritableComponentHandler::GetOverridenComponentTemplateData(const FComponentKey& Key) const { - auto Record = FindRecord(Key); + const FComponentOverrideRecord* Record = FindRecord(Key); return Record ? &Record->CookedComponentInstancingData : nullptr; } -const FComponentOverrideRecord* UInheritableComponentHandler::FindRecord(const FComponentKey Key) const +const FComponentOverrideRecord* UInheritableComponentHandler::FindRecord(const FComponentKey& Key) const { - for (auto& Record : Records) + for (const FComponentOverrideRecord& Record : Records) { if (Record.ComponentKey.Match(Key)) { @@ -486,12 +488,12 @@ void UInheritableComponentHandler::FixComponentTemplateName(UActorComponent* Com { // Look for a collision with the template we're trying to rename here. It's possible that names were swapped on the // original component template objects that were inherited from the associated Blueprint's parent class, for example. - FComponentOverrideRecord* MatchingRecord = Records.FindByPredicate([ComponentTemplate, NewName](FComponentOverrideRecord& Record) + FComponentOverrideRecord* MatchingRecord = Records.FindByPredicate([ComponentTemplate, &NewName](FComponentOverrideRecord& Record) { if (Record.ComponentTemplate && Record.ComponentTemplate != ComponentTemplate && Record.ComponentTemplate->GetName() == NewName) { const UActorComponent* OriginalTemplate = Record.ComponentKey.GetOriginalTemplate(); - return ensureMsgf(OriginalTemplate && OriginalTemplate->GetName() != Record.ComponentTemplate->GetName(), + return ensureMsgf(OriginalTemplate && OriginalTemplate->GetFName() != Record.ComponentTemplate->GetFName(), TEXT("Found a collision with an existing override record, but its associated template object is either invalid or already matches its inherited template's name (%s). This is unexpected."), *NewName); } @@ -531,7 +533,7 @@ FComponentKey::FComponentKey(UBlueprint* Blueprint, const FUCSComponentId& UCSCo } #endif // WITH_EDITOR -bool FComponentKey::Match(const FComponentKey OtherKey) const +bool FComponentKey::Match(const FComponentKey& OtherKey) const { return (OwnerClass == OtherKey.OwnerClass) && (AssociatedGuid == OtherKey.AssociatedGuid); } diff --git a/Engine/Source/Runtime/Engine/Private/InputDelegateBinding.cpp b/Engine/Source/Runtime/Engine/Private/InputDelegateBinding.cpp index c5424d9a64d8..d1db542baf19 100644 --- a/Engine/Source/Runtime/Engine/Private/InputDelegateBinding.cpp +++ b/Engine/Source/Runtime/Engine/Private/InputDelegateBinding.cpp @@ -31,7 +31,7 @@ void UInputDelegateBinding::BindInputDelegates(const UClass* InClass, UInputComp UInputVectorAxisDelegateBinding::StaticClass(), }; - if (InClass) + if (SupportsInputDelegate(InClass)) { BindInputDelegates(InClass->GetSuperClass(), InputComponent); diff --git a/Engine/Source/Runtime/Engine/Private/InstancedStaticMesh.cpp b/Engine/Source/Runtime/Engine/Private/InstancedStaticMesh.cpp index 53c58dc35b11..d9ff7e66a886 100644 --- a/Engine/Source/Runtime/Engine/Private/InstancedStaticMesh.cpp +++ b/Engine/Source/Runtime/Engine/Private/InstancedStaticMesh.cpp @@ -2590,7 +2590,8 @@ void UInstancedStaticMeshComponent::OnComponentCreated() { // if we are pasting/duplicating this component, it may be created with some instances already in place // in this case, need to ensure that the instance render data is properly created - const bool InitializeFromCurrentData = PerInstanceSMData.Num() > 0; + // We only need to only init from current data if the reorder table == per instance data, but only for the HISM Component, in the case of ISM, the reorder table is never used. + const bool InitializeFromCurrentData = PerInstanceSMData.Num() > 0 && (InstanceReorderTable.Num() == PerInstanceSMData.Num() || InstanceReorderTable.Num() == 0); InitPerInstanceRenderData(InitializeFromCurrentData); } } diff --git a/Engine/Source/Runtime/Engine/Private/Internationalization/StringTable.cpp b/Engine/Source/Runtime/Engine/Private/Internationalization/StringTable.cpp index de0ca77c98bb..8bd214471d08 100644 --- a/Engine/Source/Runtime/Engine/Private/Internationalization/StringTable.cpp +++ b/Engine/Source/Runtime/Engine/Private/Internationalization/StringTable.cpp @@ -124,6 +124,11 @@ private: } //~ IStringTableEngineBridge interface + virtual bool CanFindOrLoadStringTableAssetImpl() override + { + return IsInGameThread() && !IsGarbageCollecting() && !GIsSavingPackage; + } + virtual int32 LoadStringTableAssetImpl(const FName InTableId, FLoadStringTableAssetCallback InLoadedCallback) override { const FSoftObjectPath StringTableAssetReference = GetAssetReference(InTableId); @@ -288,6 +293,11 @@ private: Collector.AddReferencedObjects(KeepAliveStringTables); } + virtual FString GetReferencerName() const override + { + return TEXT("FStringTableEngineBridge"); + } + private: struct FAsyncLoadingStringTable { diff --git a/Engine/Source/Runtime/Engine/Private/Interpolation.cpp b/Engine/Source/Runtime/Engine/Private/Interpolation.cpp index e1b764483e9e..af8f0d9acc90 100644 --- a/Engine/Source/Runtime/Engine/Private/Interpolation.cpp +++ b/Engine/Source/Runtime/Engine/Private/Interpolation.cpp @@ -6774,7 +6774,7 @@ void UInterpTrackDirector::UpdateTrack(float NewPosition, UInterpTrackInst* TrIn if ( PC->PlayerCameraManager ) { - PC->PlayerCameraManager->bGameCameraCutThisFrame = true; + PC->PlayerCameraManager->SetGameCameraCutThisFrame(); } DirInst->OldViewTarget = BackupViewTarget; diff --git a/Engine/Source/Runtime/Engine/Private/KismetInputLibrary.cpp b/Engine/Source/Runtime/Engine/Private/KismetInputLibrary.cpp index 738bbb0cb618..2746602a93df 100644 --- a/Engine/Source/Runtime/Engine/Private/KismetInputLibrary.cpp +++ b/Engine/Source/Runtime/Engine/Private/KismetInputLibrary.cpp @@ -3,6 +3,7 @@ #include "Kismet/KismetInputLibrary.h" #include "EngineGlobals.h" #include "Engine/Engine.h" +#include "Framework/Application/SlateApplication.h" ////////////////////////////////////////////////////////////////////////// @@ -67,6 +68,37 @@ bool UKismetInputLibrary::Key_IsValid(const FKey& Key) return Key.IsValid(); } +EUINavigationAction UKismetInputLibrary::Key_GetNavigationAction(const FKey& InKey) +{ + if (FSlateApplication::IsInitialized()) + { + return FSlateApplication::Get().GetNavigationActionForKey(InKey); + } + + return EUINavigationAction::Invalid; +} + +EUINavigation UKismetInputLibrary::Key_GetNavigationDirectionFromKey(const FKeyEvent& InKeyEvent) +{ + if (FSlateApplication::IsInitialized()) + { + return FSlateApplication::Get().GetNavigationDirectionFromKey(InKeyEvent); + } + + return EUINavigation::Invalid; +} + +EUINavigation UKismetInputLibrary::Key_GetNavigationDirectionFromAnalog(const FAnalogInputEvent& InAnalogEvent) +{ + if (FSlateApplication::IsInitialized()) + { + return FSlateApplication::Get().GetNavigationDirectionFromAnalog(InAnalogEvent); + } + + return EUINavigation::Invalid; +} + + FText UKismetInputLibrary::Key_GetDisplayName(const FKey& Key) { return Key.GetDisplayName(); @@ -137,6 +169,10 @@ bool UKismetInputLibrary::InputEvent_IsRightCommandDown(const FInputEvent& Input return Input.IsRightCommandDown(); } +FText UKismetInputLibrary::InputChord_GetDisplayName(const FInputChord& Key) +{ + return Key.GetInputText(); +} FKey UKismetInputLibrary::GetKey(const FKeyEvent& Input) { diff --git a/Engine/Source/Runtime/Engine/Private/KismetRenderingLibrary.cpp b/Engine/Source/Runtime/Engine/Private/KismetRenderingLibrary.cpp index 9ecd2cf307df..ec4418155155 100644 --- a/Engine/Source/Runtime/Engine/Private/KismetRenderingLibrary.cpp +++ b/Engine/Source/Runtime/Engine/Private/KismetRenderingLibrary.cpp @@ -99,15 +99,15 @@ void UKismetRenderingLibrary::DrawMaterialToRenderTarget(UObject* WorldContextOb } else if (!Material) { - FMessageLog("Blueprint").Warning(LOCTEXT("DrawMaterialToRenderTarget_InvalidMaterial", "DrawMaterialToRenderTarget: Material must be non-null.")); + FMessageLog("Blueprint").Warning(FText::Format(LOCTEXT("DrawMaterialToRenderTarget_InvalidMaterial", "DrawMaterialToRenderTarget[{0}]: Material must be non-null."), FText::FromString(GetPathNameSafe(WorldContextObject)))); } else if (!TextureRenderTarget) { - FMessageLog("Blueprint").Warning(LOCTEXT("DrawMaterialToRenderTarget_InvalidTextureRenderTarget", "DrawMaterialToRenderTarget: TextureRenderTarget must be non-null.")); + FMessageLog("Blueprint").Warning(FText::Format(LOCTEXT("DrawMaterialToRenderTarget_InvalidTextureRenderTarget", "DrawMaterialToRenderTarget[{0}]: TextureRenderTarget must be non-null."), FText::FromString(GetPathNameSafe(WorldContextObject)))); } else if (!TextureRenderTarget->Resource) { - FMessageLog("Blueprint").Warning(LOCTEXT("DrawMaterialToRenderTarget_ReleasedTextureRenderTarget", "DrawMaterialToRenderTarget: render target has been released.")); + FMessageLog("Blueprint").Warning(FText::Format(LOCTEXT("DrawMaterialToRenderTarget_ReleasedTextureRenderTarget", "DrawMaterialToRenderTarget[{0}]: render target has been released."), FText::FromString(GetPathNameSafe(WorldContextObject)))); } else { diff --git a/Engine/Source/Runtime/Engine/Private/KismetSystemLibrary.cpp b/Engine/Source/Runtime/Engine/Private/KismetSystemLibrary.cpp index 39d1f8846d94..a6caf0ba91f1 100644 --- a/Engine/Source/Runtime/Engine/Private/KismetSystemLibrary.cpp +++ b/Engine/Source/Runtime/Engine/Private/KismetSystemLibrary.cpp @@ -988,6 +988,11 @@ void UKismetSystemLibrary::BreakSoftClassPath(FSoftClassPath InSoftClassPath, FS PathString = InSoftClassPath.ToString(); } +TSoftClassPtr UKismetSystemLibrary::Conv_SoftClassPathToSoftClassRef(const FSoftClassPath& SoftClassPath) +{ + return TSoftClassPtr(SoftClassPath); +} + bool UKismetSystemLibrary::IsValidSoftObjectReference(const TSoftObjectPtr& SoftObjectReference) { return !SoftObjectReference.IsNull(); @@ -1008,6 +1013,11 @@ bool UKismetSystemLibrary::NotEqual_SoftObjectReference(const TSoftObjectPtr Asset) +{ + return Asset.LoadSynchronous(); +} + bool UKismetSystemLibrary::IsValidSoftClassReference(const TSoftClassPtr& SoftClassReference) { return !SoftClassReference.IsNull(); @@ -1028,6 +1038,11 @@ bool UKismetSystemLibrary::NotEqual_SoftClassReference(const TSoftClassPtr AssetClass) +{ + return AssetClass.LoadSynchronous(); +} + UObject* UKismetSystemLibrary::Conv_SoftObjectReferenceToObject(const TSoftObjectPtr& SoftObject) { return SoftObject.Get(); @@ -2561,11 +2576,6 @@ void UKismetSystemLibrary::LoadAsset(UObject* WorldContextObject, TSoftObjectPtr } } -UObject* UKismetSystemLibrary::LoadAsset_Blocking(TSoftObjectPtr Asset) -{ - return Asset.ToSoftObjectPath().TryLoad(); -} - void UKismetSystemLibrary::LoadAssetClass(UObject* WorldContextObject, TSoftClassPtr AssetClass, UKismetSystemLibrary::FOnAssetClassLoaded OnLoaded, FLatentActionInfo LatentInfo) { struct FLoadAssetClassAction : public FLoadAssetActionBase diff --git a/Engine/Source/Runtime/Engine/Private/LevelStreaming.cpp b/Engine/Source/Runtime/Engine/Private/LevelStreaming.cpp index 9aabc2445485..86d419396d4a 100644 --- a/Engine/Source/Runtime/Engine/Private/LevelStreaming.cpp +++ b/Engine/Source/Runtime/Engine/Private/LevelStreaming.cpp @@ -4,7 +4,7 @@ #include "ContentStreaming.h" #include "Misc/App.h" #include "UObject/Package.h" -#include "Serialization/ArchiveTraceRoute.h" +#include "UObject/ReferenceChainSearch.h" #include "Misc/PackageName.h" #include "UObject/LinkerLoad.h" #include "EngineGlobals.h" @@ -743,7 +743,7 @@ void ULevelStreaming::SetLoadedLevel(ULevel* Level) check(PendingUnloadLevel == nullptr); PendingUnloadLevel = LoadedLevel; LoadedLevel = Level; - CachedLoadedLevelPackageName = (LoadedLevel ? LoadedLevel->GetOutermost()->GetFName() : NAME_None); + bHasCachedLoadedLevelPackageName = false; // Cancel unloading for this level, in case it was queued for it FLevelStreamingGCHelper::CancelUnloadRequest(LoadedLevel); @@ -800,7 +800,9 @@ bool ULevelStreaming::IsDesiredLevelLoaded() const { const bool bIsGameWorld = GetWorld()->IsGameWorld(); const FName DesiredPackageName = bIsGameWorld ? GetLODPackageName() : GetWorldAssetPackageFName(); - return (CachedLoadedLevelPackageName == DesiredPackageName); + const FName LoadedLevelPackageName = GetLoadedLevelPackageName(); + + return (LoadedLevelPackageName == DesiredPackageName); } return false; @@ -826,9 +828,10 @@ bool ULevelStreaming::RequestLevel(UWorld* PersistentWorld, bool bAllowLevelLoad // Package name we want to load const bool bIsGameWorld = PersistentWorld->IsGameWorld(); const FName DesiredPackageName = bIsGameWorld ? GetLODPackageName() : GetWorldAssetPackageFName(); + const FName LoadedLevelPackageName = GetLoadedLevelPackageName(); // Check if currently loaded level is what we want right now - if (LoadedLevel && CachedLoadedLevelPackageName == DesiredPackageName) + if (LoadedLevel && LoadedLevelPackageName == DesiredPackageName) { return true; } @@ -957,16 +960,14 @@ bool ULevelStreaming::RequestLevel(UWorld* PersistentWorld, bool bAllowLevelLoad } #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) - if (World->PersistentLevel == NULL) + if (World->PersistentLevel == nullptr) { - UE_LOG(LogLevelStreaming, Log, TEXT("World exists but PersistentLevel doesn't for %s, most likely caused by reference to world of unloaded level and GC setting reference to NULL while keeping world object"), *World->GetOutermost()->GetName()); - // print out some debug information... - StaticExec(World, *FString::Printf(TEXT("OBJ REFS CLASS=WORLD NAME=%s shortest"), *World->GetPathName())); - TMap Route = FArchiveTraceRoute::FindShortestRootPath( World, true, GARBAGE_COLLECTION_KEEPFLAGS ); - FString ErrorString = FArchiveTraceRoute::PrintRootPath( Route, World ); - UE_LOG(LogLevelStreaming, Log, TEXT("%s"), *ErrorString); - // before asserting - checkf(World->PersistentLevel,TEXT("Most likely caused by reference to world of unloaded level and GC setting reference to NULL while keeping world object")); + UE_LOG(LogLevelStreaming, Error, TEXT("World exists but PersistentLevel doesn't for %s, most likely caused by reference to world of unloaded level and GC setting reference to null while keeping world object"), *World->GetOutermost()->GetName()); + UE_LOG(LogLevelStreaming, Error, TEXT("Most likely caused by reference to world of unloaded level and GC setting reference to null while keeping world object. Referenced by:")); + + FReferenceChainSearch RefChainSearch(World, EReferenceChainSearchMode::Shortest | EReferenceChainSearchMode::PrintResults); + UE_LOG(LogLoad, Fatal, TEXT("World exists but PersistentLevel doesn't for %s! Referenced by:") LINE_TERMINATOR TEXT("%s"), *World->GetPathName(), *RefChainSearch.GetRootPath()); + return false; } #endif @@ -1008,6 +1009,7 @@ bool ULevelStreaming::RequestLevel(UWorld* PersistentWorld, bool bAllowLevelLoad // Kick off async load request. STAT_ADD_CUSTOMMESSAGE_NAME( STAT_NamedMarker, *(FString( TEXT( "RequestLevel - " ) + DesiredPackageName.ToString() )) ); + TRACE_BOOKMARK(TEXT("RequestLevel - %s"), *DesiredPackageName.ToString()); LoadPackageAsync(DesiredPackageName.ToString(), nullptr, *PackageNameToLoadFrom, FLoadPackageAsyncDelegate::CreateUObject(this, &ULevelStreaming::AsyncLevelLoadComplete), PackageFlags, PIEInstanceID); // streamingServer: server loads everything? @@ -1217,6 +1219,7 @@ void ULevelStreaming::AsyncLevelLoadComplete(const FName& InPackageName, UPackag ULevel::StreamedLevelsOwningWorld.Remove(InPackageName); STAT_ADD_CUSTOMMESSAGE_NAME( STAT_NamedMarker, *(FString( TEXT( "RequestLevelComplete - " ) + InPackageName.ToString() )) ); + TRACE_BOOKMARK(TEXT("RequestLevelComplete - %s"), *InPackageName.ToString()); } bool ULevelStreaming::IsLevelVisible() const @@ -1363,6 +1366,7 @@ void ULevelStreaming::SetWorldAsset(const TSoftObjectPtr& NewWorldAsset) { WorldAsset = NewWorldAsset; bHasCachedWorldAssetPackageFName = false; + bHasCachedLoadedLevelPackageName = false; if (CurrentState == ECurrentState::FailedToLoad) { @@ -1391,6 +1395,17 @@ FName ULevelStreaming::GetWorldAssetPackageFName() const return CachedWorldAssetPackageFName; } +FName ULevelStreaming::GetLoadedLevelPackageName() const +{ + if( !bHasCachedLoadedLevelPackageName ) + { + CachedLoadedLevelPackageName = (LoadedLevel ? LoadedLevel->GetOutermost()->GetFName() : NAME_None); + bHasCachedLoadedLevelPackageName = true; + } + + return CachedLoadedLevelPackageName; +} + void ULevelStreaming::SetWorldAssetByPackageName(FName InPackageName) { const FString TargetWorldPackageName = InPackageName.ToString(); @@ -1557,6 +1572,7 @@ void ULevelStreaming::PostEditChangeProperty(FPropertyChangedEvent& PropertyChan else if (PropertyName == GET_MEMBER_NAME_CHECKED(ULevelStreaming, WorldAsset)) { bHasCachedWorldAssetPackageFName = false; + bHasCachedLoadedLevelPackageName = false; } } diff --git a/Engine/Source/Runtime/Engine/Private/LevelTick.cpp b/Engine/Source/Runtime/Engine/Private/LevelTick.cpp index 4ecf07240691..8362d8ebfe06 100644 --- a/Engine/Source/Runtime/Engine/Private/LevelTick.cpp +++ b/Engine/Source/Runtime/Engine/Private/LevelTick.cpp @@ -141,8 +141,6 @@ DEFINE_STAT(STAT_NetBroadcastPostTickTime); DEFINE_STAT(STAT_NetRebuildConditionalTime); DEFINE_STAT(STAT_PackageMap_SerializeObjectTime); -DECLARE_CYCLE_STAT(TEXT("TickableGameObjects Time"), STAT_TickableGameObjectsTime, STATGROUP_Game); - /*----------------------------------------------------------------------------- Externs. @@ -151,13 +149,6 @@ DECLARE_CYCLE_STAT(TEXT("TickableGameObjects Time"), STAT_TickableGameObjectsTim extern bool GShouldLogOutAFrameOfMoveComponent; extern bool GShouldLogOutAFrameOfSetBodyTransform; -/*----------------------------------------------------------------------------- - FTickableGameObject implementation. ------------------------------------------------------------------------------*/ - -/** Static array of tickable objects */ -bool FTickableGameObject::bIsTickingObjects = false; - #if LOG_DETAILED_PATHFINDING_STATS /** Global detailed pathfinding stats. */ FDetailedTickStats GDetailedPathFindingStats(30, 10, 1, 20, TEXT("pathfinding")); @@ -1248,7 +1239,7 @@ static void RecordWorldCountsToCSV(UWorld* World) bool bDetailed = (CVarDetailedTickContextForCSV.GetValueOnGameThread() != 0); - TSortedMap TickContextToCountMap; + TSortedMap TickContextToCountMap; int32 EnabledCount; FTickTaskManagerInterface::Get().GetEnabledTickFunctionCounts(World, TickContextToCountMap, EnabledCount, bDetailed); @@ -1318,68 +1309,6 @@ void EndTickDrawEvent(TDrawEvent* TickDrawEvent) }); } - -void FTickableGameObject::TickObjects(UWorld* World, const int32 InTickType, const bool bIsPaused, const float DeltaSeconds) -{ - SCOPE_CYCLE_COUNTER(STAT_TickableGameObjectsTime); - CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Tickables); - - TArray& PendingTickableObjects = GetPendingTickableObjects(); - TArray& TickableObjects = GetTickableObjects(); - - for (FTickableGameObject* PendingTickable : PendingTickableObjects) - { - AddTickableObject(TickableObjects, PendingTickable); - } - PendingTickableObjects.Empty(); - - if (TickableObjects.Num() > 0) - { - check(!bIsTickingObjects); - bIsTickingObjects = true; - - bool bNeedsCleanup = false; - const ELevelTick TickType = (ELevelTick)InTickType; - - for (const FTickableObjectEntry& TickableEntry : TickableObjects) - { - if (FTickableGameObject* TickableObject = static_cast(TickableEntry.TickableObject)) - { - // If it is tickable and in this world - if (((TickableEntry.TickType == ETickableTickType::Always) || TickableObject->IsTickable()) && (TickableObject->GetTickableGameObjectWorld() == World)) - { - const bool bIsGameWorld = InTickType == LEVELTICK_All || (World && World->IsGameWorld()); - // If we are in editor and it is editor tickable, always tick - // If this is a game world then tick if we are not doing a time only (paused) update and we are not paused or the object is tickable when paused - if ((GIsEditor && TickableObject->IsTickableInEditor()) || - (bIsGameWorld && ((!bIsPaused && TickType != LEVELTICK_TimeOnly) || (bIsPaused && TickableObject->IsTickableWhenPaused())))) - { - FScopeCycleCounter Context(TickableObject->GetStatId()); - TickableObject->Tick(DeltaSeconds); - - // In case it was removed during tick - if (TickableEntry.TickableObject == nullptr) - { - bNeedsCleanup = true; - } - } - } - } - else - { - bNeedsCleanup = true; - } - } - - if (bNeedsCleanup) - { - TickableObjects.RemoveAll([](const FTickableObjectEntry& Entry) { return Entry.TickableObject == nullptr; }); - } - - bIsTickingObjects = false; - } -} - /** * Update the level after a variable amount of time, DeltaSeconds, has passed. * All child actors are ticked after their owners have been ticked. diff --git a/Engine/Source/Runtime/Engine/Private/Light.cpp b/Engine/Source/Runtime/Engine/Private/Light.cpp index 9861a90999b2..76bbd08db7fa 100644 --- a/Engine/Source/Runtime/Engine/Private/Light.cpp +++ b/Engine/Source/Runtime/Engine/Private/Light.cpp @@ -16,7 +16,7 @@ ALight::ALight(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { - LightComponent = CreateAbstractDefaultSubobject(TEXT("LightComponent0")); + LightComponent = CreateDefaultSubobject(TEXT("LightComponent0")); bCollideWhenPlacing = true; SpawnCollisionHandlingMethod = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding; diff --git a/Engine/Source/Runtime/Engine/Private/Materials/Material.cpp b/Engine/Source/Runtime/Engine/Private/Materials/Material.cpp index 4772eb291b2c..08be7ad39d4c 100644 --- a/Engine/Source/Runtime/Engine/Private/Materials/Material.cpp +++ b/Engine/Source/Runtime/Engine/Private/Materials/Material.cpp @@ -1535,7 +1535,7 @@ bool UMaterial::SetMaterialUsage(bool &bNeedsRecompile, EMaterialUsage Usage) //Do not warn the user during automation testing if (!GIsAutomationTesting) { - UE_LOG(LogMaterial, Warning, TEXT("Material %s needed to have new flag set %s !"), *GetPathName(), *GetUsageName(Usage)); + UE_LOG(LogMaterial, Display, TEXT("Material %s needed to have new flag set %s !"), *GetPathName(), *GetUsageName(Usage)); } // Open a material update context so this material can be modified safely. @@ -2963,7 +2963,7 @@ void UMaterial::CacheShadersForResources(EShaderPlatform ShaderPlatform, const T for (int32 ErrorIndex = 0; ErrorIndex < CompileErrors.Num(); ErrorIndex++) { // Always log material errors in an unsuppressed category - UE_LOG(LogMaterial, Log, TEXT(" %s"), *CompileErrors[ErrorIndex]); + UE_LOG(LogMaterial, Display, TEXT(" %s"), *CompileErrors[ErrorIndex]); } #endif } diff --git a/Engine/Source/Runtime/Engine/Private/Materials/MaterialExpressions.cpp b/Engine/Source/Runtime/Engine/Private/Materials/MaterialExpressions.cpp index 3c4ff8acae24..fb19d0fabc86 100644 --- a/Engine/Source/Runtime/Engine/Private/Materials/MaterialExpressions.cpp +++ b/Engine/Source/Runtime/Engine/Private/Materials/MaterialExpressions.cpp @@ -9098,8 +9098,10 @@ uint32 UMaterialExpressionIf::GetInputType(int32 InputIndex) bool UMaterialExpressionIf::IsResultMaterialAttributes(int32 OutputIndex) { - if ((A.GetTracedInput().Expression && !A.Expression->ContainsInputLoop() && A.Expression->IsResultMaterialAttributes(A.OutputIndex)) || - (B.GetTracedInput().Expression && !B.Expression->ContainsInputLoop() && B.Expression->IsResultMaterialAttributes(B.OutputIndex))) + if ((AGreaterThanB.GetTracedInput().Expression && !AGreaterThanB.Expression->ContainsInputLoop() && AGreaterThanB.Expression->IsResultMaterialAttributes(AGreaterThanB.OutputIndex)) + && (!AEqualsB.GetTracedInput().Expression || (!AEqualsB.Expression->ContainsInputLoop() && AEqualsB.Expression->IsResultMaterialAttributes(AEqualsB.OutputIndex))) + && (ALessThanB.GetTracedInput().Expression && !ALessThanB.Expression->ContainsInputLoop() && ALessThanB.Expression->IsResultMaterialAttributes(ALessThanB.OutputIndex)) + ) { return true; } diff --git a/Engine/Source/Runtime/Engine/Private/Materials/MaterialInterface.cpp b/Engine/Source/Runtime/Engine/Private/Materials/MaterialInterface.cpp index b6bf243ac843..d042b368d6d8 100644 --- a/Engine/Source/Runtime/Engine/Private/Materials/MaterialInterface.cpp +++ b/Engine/Source/Runtime/Engine/Private/Materials/MaterialInterface.cpp @@ -565,7 +565,7 @@ void UMaterialInterface::SortTextureStreamingData(bool bForceSort, bool bFinalSo } // Sort by name to be compatible with FindTextureStreamingDataIndexRange - TextureStreamingData.Sort([](const FMaterialTextureInfo& Lhs, const FMaterialTextureInfo& Rhs) { return Lhs.TextureName < Rhs.TextureName; }); + TextureStreamingData.Sort([](const FMaterialTextureInfo& Lhs, const FMaterialTextureInfo& Rhs) { return Lhs.TextureName.LexicalLess(Rhs.TextureName); }); bTextureStreamingDataSorted = true; } #endif @@ -587,7 +587,7 @@ bool UMaterialInterface::FindTextureStreamingDataIndexRange(FName TextureName, i return false; } - const int32 MatchingIndex = Algo::BinarySearchBy(TextureStreamingData, TextureName, &FMaterialTextureInfo::TextureName); + const int32 MatchingIndex = Algo::BinarySearchBy(TextureStreamingData, TextureName, &FMaterialTextureInfo::TextureName, FNameLexicalLess()); if (MatchingIndex != INDEX_NONE) { // Find the range of entries for this texture. diff --git a/Engine/Source/Runtime/Engine/Private/NetConnection.cpp b/Engine/Source/Runtime/Engine/Private/NetConnection.cpp index 68af190c2b19..2cba6c0e2e44 100644 --- a/Engine/Source/Runtime/Engine/Private/NetConnection.cpp +++ b/Engine/Source/Runtime/Engine/Private/NetConnection.cpp @@ -34,6 +34,7 @@ #include "UObject/ObjectKey.h" #include "UObject/UObjectIterator.h" #include "Net/NetworkGranularMemoryLogging.h" +#include "SocketSubsystem.h" static TAutoConsoleVariable CVarPingExcludeFrameTime( TEXT( "net.PingExcludeFrameTime" ), 0, TEXT( "Calculate RTT time between NIC's of server and client." ) ); @@ -105,6 +106,7 @@ UNetConnection::UNetConnection(const FObjectInitializer& ObjectInitializer) , OwningActor ( nullptr ) , MaxPacket ( 0 ) , InternalAck ( false ) +, RemoteAddr ( nullptr ) , MaxPacketHandlerBits ( 0 ) , State ( USOCK_Invalid ) , Handler() @@ -114,6 +116,7 @@ UNetConnection::UNetConnection(const FObjectInitializer& ObjectInitializer) , QueuedBits ( 0 ) , TickCount ( 0 ) +, LastProcessedFrame ( 0 ) , ConnectTime ( 0.0 ) , AllowMerge ( false ) @@ -347,6 +350,11 @@ void UNetConnection::InitHandler() { Handler::Mode Mode = Driver->ServerConnection != nullptr ? Handler::Mode::Client : Handler::Mode::Server; + PRAGMA_DISABLE_DEPRECATION_WARNINGS + Handler->InitializeAddressSerializer([this](const FString& InAddress){ + return Driver->GetSocketSubsystem()->GetAddressFromString(InAddress); + }); + PRAGMA_ENABLE_DEPRECATION_WARNINGS Handler->InitializeDelegates(FPacketHandlerLowLevelSendTraits::CreateUObject(this, &UNetConnection::LowLevelSend)); Handler->NotifyAnalyticsProvider(Driver->AnalyticsProvider, Driver->AnalyticsAggregator); Handler->Initialize(Mode, MaxPacket * 8, false, nullptr, nullptr, Driver->NetDriverName); @@ -836,9 +844,6 @@ void UNetConnection::AssertValid() check(State==USOCK_Closed || State==USOCK_Pending || State==USOCK_Open); } -void UNetConnection::SendPackageMap() -{ -} bool UNetConnection::ClientHasInitializedLevelFor(const AActor* TestActor) const { @@ -2697,6 +2702,18 @@ void UNetConnection::Tick() } FrameTime = CurrentRealtimeSeconds - LastTime; + const int32 MaxNetTickRate = Driver->MaxNetTickRate; + const float MaxNetTickRateFloat = MaxNetTickRate > 0 ? float(MaxNetTickRate) : FLT_MAX; + const float DesiredTickRate = FMath::Clamp(GEngine->GetMaxTickRate(0.0f, false), 0.0f, MaxNetTickRateFloat); + if (!InternalAck && MaxNetTickRate > 0 && DesiredTickRate > 0.0f) + { + const float MinNetFrameTime = 1.0f/DesiredTickRate; + if (FrameTime < MinNetFrameTime) + { + return; + } + } + LastTime = CurrentRealtimeSeconds; CumulativeTime += FrameTime; CountedFrames++; @@ -2966,7 +2983,6 @@ void UNetConnection::Tick() // Clamp DeltaTime for bandwidth limiting so that if there is a hitch, we don't try to send // a large burst on the next frame, which can cause another hitch if a lot of additional replication occurs. - const float DesiredTickRate = GEngine->GetMaxTickRate(0.0f, false); float BandwidthDeltaTime = DeltaTime; if (DesiredTickRate != 0.0f) { diff --git a/Engine/Source/Runtime/Engine/Private/NetworkDriver.cpp b/Engine/Source/Runtime/Engine/Private/NetworkDriver.cpp index cff1e323eb4c..fef9eb3ea0b8 100644 --- a/Engine/Source/Runtime/Engine/Private/NetworkDriver.cpp +++ b/Engine/Source/Runtime/Engine/Private/NetworkDriver.cpp @@ -9,6 +9,7 @@ #include "Misc/CommandLine.h" #include "Misc/NetworkGuid.h" #include "Stats/Stats.h" +#include "Misc/App.h" #include "Misc/MemStack.h" #include "HAL/IConsoleManager.h" #include "HAL/LowLevelMemTracker.h" @@ -64,6 +65,8 @@ #include "ProfilingDebugging/CsvProfiler.h" #include "Engine/LevelScriptActor.h" #include "Net/NetworkGranularMemoryLogging.h" +#include "SocketSubsystem.h" +#include "AddressInfoTypes.h" #if USE_SERVER_PERF_COUNTERS #include "PerfCountersModule.h" @@ -267,6 +270,7 @@ UNetDriver::UNetDriver(const FObjectInitializer& ObjectInitializer) #endif , ProcessQueuedBunchesCurrentFrameMilliseconds(0.0f) , DDoS() +, LocalAddr(nullptr) , NetworkObjects(new FNetworkObjectList) , LagState(ENetworkLagState::NotLagging) , DuplicateLevelID(INDEX_NONE) @@ -278,6 +282,8 @@ PRAGMA_DISABLE_DEPRECATION_WARNINGS ChannelClasses[CHTYPE_Actor] = UActorChannel::StaticClass(); ChannelClasses[CHTYPE_Voice] = UVoiceChannel::StaticClass(); PRAGMA_ENABLE_DEPRECATION_WARNINGS + + UpdateDelayRandomStream.Initialize(FApp::bUseFixedSeed ? GetFName() : NAME_None); } void UNetDriver::InitPacketSimulationSettings() @@ -362,7 +368,8 @@ void UNetDriver::LoadChannelDefinitions() for (FChannelDefinition& ChannelDef : ChannelDefinitions) { - UE_CLOG((ChannelDef.ChannelName.GetComparisonIndex() > MAX_NETWORKED_HARDCODED_NAME), LogNet, Warning, TEXT("Channel name will be serialized as a string: %s"), *ChannelDef.ChannelName.ToString()); + UE_CLOG(!ChannelDef.ChannelName.ToEName() || !ShouldReplicateAsInteger(*ChannelDef.ChannelName.ToEName()), + LogNet, Warning, TEXT("Channel name will be serialized as a string: %s"), *ChannelDef.ChannelName.ToString()); UE_CLOG(ChannelDefinitionMap.Contains(ChannelDef.ChannelName), LogNet, Error, TEXT("Channel name is defined multiple times: %s"), *ChannelDef.ChannelName.ToString()); UE_CLOG(StaticChannelIndices.Contains(ChannelDef.StaticChannelIndex), LogNet, Error, TEXT("Channel static index is already in use: %s %i"), *ChannelDef.ChannelName.ToString(), ChannelDef.StaticChannelIndex); @@ -1318,6 +1325,22 @@ void UNetDriver::PostTickFlush() } } +PRAGMA_DISABLE_DEPRECATION_WARNINGS +void UNetDriver::LowLevelSend(FString Address, void* Data, int32 CountBits, FOutPacketTraits& Traits) +{ + if (GetSocketSubsystem() != nullptr) + { + TSharedPtr NetAddress = GetSocketSubsystem()->GetAddressFromString(*Address); + if (NetAddress.IsValid()) + { + LowLevelSend(NetAddress, Data, CountBits, Traits); + return; + } + } + UE_LOG(LogNet, Warning, TEXT("Could not infer the address to use for LowLevelSend, please use the one that takes an FInternetAddr to avoid this problem")); +} +PRAGMA_ENABLE_DEPRECATION_WARNINGS + bool UNetDriver::InitConnectionClass(void) { if (NetConnectionClass == NULL && NetConnectionClassName != TEXT("")) @@ -1407,6 +1430,11 @@ void UNetDriver::InitConnectionlessHandler() if (ConnectionlessHandler.IsValid()) { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + ConnectionlessHandler->InitializeAddressSerializer([this](const FString& InAddress) { + return GetSocketSubsystem()->GetAddressFromString(InAddress); + }); + PRAGMA_ENABLE_DEPRECATION_WARNINGS ConnectionlessHandler->NotifyAnalyticsProvider(AnalyticsProvider, AnalyticsAggregator); ConnectionlessHandler->Initialize(Handler::Mode::Server, MAX_PACKET_SIZE, true, nullptr, nullptr, NetDriverName); @@ -2412,6 +2440,10 @@ void UNetDriver::LowLevelDestroy() SetWorld(NULL); } +FString UNetDriver::LowLevelGetNetworkNumber() +{ + return LocalAddr.IsValid() ? LocalAddr->ToString(true) : FString(TEXT("")); +} #if !UE_BUILD_SHIPPING bool UNetDriver::HandleSocketsCommand( const TCHAR* Cmd, FOutputDevice& Ar ) @@ -3669,7 +3701,7 @@ void UNetDriver::ServerReplicateActors_BuildConsiderList( TArrayOptimalNetUpdateDelta : 1.0f / Actor->NetUpdateFrequency; // then set the next update time - ActorInfo->NextUpdateTime = World->TimeSeconds + FMath::SRand() * ServerTickTime + NextUpdateDelta; + ActorInfo->NextUpdateTime = World->TimeSeconds + UpdateDelayRandomStream.FRand() * ServerTickTime + NextUpdateDelta; // and mark when the actor first requested an update //@note: using Time because it's compared against UActorChannel.LastUpdateTime which also uses that value @@ -3996,9 +4028,7 @@ int32 UNetDriver::ServerReplicateActors_ProcessPrioritizedActors( UNetConnection } // if the actor is now relevant or was recently relevant - const bool bIsRecentlyRelevant = bIsRelevant || ( Channel && Time - Channel->RelevantTime < RelevantTimeout ) || ActorInfo->bForceRelevantNextUpdate; - - ActorInfo->bForceRelevantNextUpdate = false; + const bool bIsRecentlyRelevant = bIsRelevant || ( Channel && Time - Channel->RelevantTime < RelevantTimeout ) || (ActorInfo->ForceRelevantFrame >= Connection->LastProcessedFrame); if ( bIsRecentlyRelevant ) { @@ -4032,7 +4062,7 @@ int32 UNetDriver::ServerReplicateActors_ProcessPrioritizedActors( UNetConnection // if it is relevant then mark the channel as relevant for a short amount of time if ( bIsRelevant ) { - Channel->RelevantTime = Time + 0.5f * FMath::SRand(); + Channel->RelevantTime = Time + 0.5f * UpdateDelayRandomStream.FRand(); } // if the channel isn't saturated if ( Channel->IsNetReady( 0 ) ) @@ -4434,14 +4464,22 @@ int32 UNetDriver::ServerReplicateActors(float DeltaSeconds) PriorityActors[k]->ActorInfo->bPendingNetUpdate = true; if ( Channel != NULL ) { - Channel->RelevantTime = Time + 0.5f * FMath::SRand(); + Channel->RelevantTime = Time + 0.5f * UpdateDelayRandomStream.FRand(); } } + + // If the actor was forced to relevant and didn't get processed, try again on the next update; + if (PriorityActors[k]->ActorInfo->ForceRelevantFrame >= Connection->LastProcessedFrame) + { + PriorityActors[k]->ActorInfo->ForceRelevantFrame = ReplicationFrame+1; + } } RelevantActorMark.Pop(); ConnectionViewers.Reset(); + Connection->LastProcessedFrame = ReplicationFrame; + const bool bWasSaturated = GNumSaturatedConnections > LocalNumSaturated; Connection->TrackReplicationForAnalytics(bWasSaturated); } @@ -4792,20 +4830,17 @@ void UNetDriver::AddClientConnection(UNetConnection * NewConnection) ClientConnections.Add(NewConnection); - TSharedPtr ConnAddr = NewConnection->GetInternetAddr(); + TSharedPtr ConnAddr = NewConnection->GetRemoteAddr(); if (ConnAddr.IsValid()) { - TSharedRef ConnAddrRef = ConnAddr.ToSharedRef(); - - MappedClientConnections.Add(ConnAddrRef, NewConnection); - + MappedClientConnections.Add(ConnAddr.ToSharedRef(), NewConnection); // On the off-chance of the same IP:Port being reused, check RecentlyDisconnectedClients int32 RecentDisconnectIdx = RecentlyDisconnectedClients.IndexOfByPredicate( - [&ConnAddrRef](const FDisconnectedClient& CurElement) + [&ConnAddr](const FDisconnectedClient& CurElement) { - return *ConnAddrRef == *CurElement.Address; + return *ConnAddr == *CurElement.Address; }); if (RecentDisconnectIdx != INDEX_NONE) @@ -4917,28 +4952,28 @@ void UNetDriver::RemoveClientConnection(UNetConnection* ClientConnectionToRemove { verify(ClientConnections.Remove(ClientConnectionToRemove) == 1); - TSharedPtr AddrToRemove = ClientConnectionToRemove->GetInternetAddr(); + TSharedPtr AddrToRemove = ClientConnectionToRemove->GetRemoteAddr(); if (AddrToRemove.IsValid()) { - TSharedRef AddrRef = AddrToRemove.ToSharedRef(); + TSharedRef ConstAddrRef = AddrToRemove.ToSharedRef(); if (RecentlyDisconnectedTrackingTime > 0) { - UNetConnection** FoundVal = MappedClientConnections.Find(AddrRef); + UNetConnection** FoundVal = MappedClientConnections.Find(ConstAddrRef); // Mark recently disconnected clients as nullptr (don't wait for GC), and keep the MappedClientConections entry for a while. // Required for identifying/ignoring packets from recently disconnected clients, with the same performance as for NetConnection's (important for DDoS detection) if (ensure(FoundVal != nullptr)) { - RecentlyDisconnectedClients.Add(FDisconnectedClient(AddrRef, FPlatformTime::Seconds())); + RecentlyDisconnectedClients.Add(FDisconnectedClient(ConstAddrRef, FPlatformTime::Seconds())); *FoundVal = nullptr; } } else { - verify(MappedClientConnections.Remove(AddrRef) == 1); + verify(MappedClientConnections.Remove(ConstAddrRef) == 1); } } diff --git a/Engine/Source/Runtime/Engine/Private/NetworkObjectList.cpp b/Engine/Source/Runtime/Engine/Private/NetworkObjectList.cpp index c4138a8061c6..ee54f0ad7343 100644 --- a/Engine/Source/Runtime/Engine/Private/NetworkObjectList.cpp +++ b/Engine/Source/Runtime/Engine/Private/NetworkObjectList.cpp @@ -327,8 +327,8 @@ void FNetworkObjectList::ForceActorRelevantNextUpdate(AActor* const Actor, UNetD } FNetworkObjectInfo* NetworkObjectInfo = NetworkObjectInfoPtr->Get(); - - NetworkObjectInfo->bForceRelevantNextUpdate = true; + + NetworkObjectInfo->ForceRelevantFrame = NetDriver->ReplicationFrame + 1; } void FNetworkObjectList::Reset() diff --git a/Engine/Source/Runtime/Engine/Private/NetworkProfiler.cpp b/Engine/Source/Runtime/Engine/Private/NetworkProfiler.cpp index 018cf5f8ba21..6c5e44338efc 100644 --- a/Engine/Source/Runtime/Engine/Private/NetworkProfiler.cpp +++ b/Engine/Source/Runtime/Engine/Private/NetworkProfiler.cpp @@ -229,7 +229,7 @@ void FNetworkProfiler::SetCurrentConnection( UNetConnection* Connection ) { if ( bIsTrackingEnabled && Connection != nullptr ) { - const TSharedPtr ConnectionAddr = Connection->GetInternetAddr(); + const TSharedPtr ConnectionAddr = Connection->GetRemoteAddr(); if ( ConnectionAddr.IsValid() ) { if ( LastAddress != ConnectionAddr ) diff --git a/Engine/Source/Runtime/Engine/Private/PacketHandlers/StatelessConnectHandlerComponent.cpp b/Engine/Source/Runtime/Engine/Private/PacketHandlers/StatelessConnectHandlerComponent.cpp index b21721d14ab1..e5fbf19b695c 100644 --- a/Engine/Source/Runtime/Engine/Private/PacketHandlers/StatelessConnectHandlerComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/PacketHandlers/StatelessConnectHandlerComponent.cpp @@ -204,7 +204,7 @@ StatelessConnectHandlerComponent::StatelessConnectHandlerComponent() , HandshakeSecret() , ActiveSecret(255) , LastSecretUpdateTimestamp(0.f) - , LastChallengeSuccessAddress(TEXT("")) + , LastChallengeSuccessAddress(nullptr) , LastServerSequence(0) , LastClientSequence(0) , LastClientSendTimestamp(0.0) @@ -263,8 +263,6 @@ void StatelessConnectHandlerComponent::CountBytes(FArchive& Ar) const { HandshakeSecret[i].CountBytes(Ar); } - - LastChallengeSuccessAddress.CountBytes(Ar); } void StatelessConnectHandlerComponent::NotifyHandshakeBegin() @@ -335,7 +333,7 @@ void StatelessConnectHandlerComponent::NotifyHandshakeBegin() } } -void StatelessConnectHandlerComponent::SendConnectChallenge(const FString& ClientAddress) +void StatelessConnectHandlerComponent::SendConnectChallenge(TSharedPtr ClientAddress) { if (Driver != nullptr) { @@ -485,7 +483,7 @@ void StatelessConnectHandlerComponent::SendChallengeResponse(uint8 InSecretId, f } } -void StatelessConnectHandlerComponent::SendChallengeAck(const FString& ClientAddress, uint8 InCookie[COOKIE_BYTE_SIZE]) +void StatelessConnectHandlerComponent::SendChallengeAck(TSharedPtr ClientAddress, uint8 InCookie[COOKIE_BYTE_SIZE]) { if (Driver != nullptr) { @@ -554,7 +552,7 @@ void StatelessConnectHandlerComponent::SendChallengeAck(const FString& ClientAdd } } -void StatelessConnectHandlerComponent::SendRestartHandshakeRequest(const FString& ClientAddress) +void StatelessConnectHandlerComponent::SendRestartHandshakeRequest(const TSharedPtr ClientAddress) { if (Driver != nullptr) { @@ -789,14 +787,17 @@ void StatelessConnectHandlerComponent::Incoming(FBitReader& Packet) } else if (Handler->Mode == Handler::Mode::Server) { - // The server should not be receiving handshake packets at this stage - resend the ack in case it was lost. - // In this codepath, this component is linked to a UNetConnection, and the Last* values below, cache the handshake info. + if (LastChallengeSuccessAddress.IsValid()) + { + // The server should not be receiving handshake packets at this stage - resend the ack in case it was lost. + // In this codepath, this component is linked to a UNetConnection, and the Last* values below, cache the handshake info. #if !UE_BUILD_SHIPPING - UE_LOG(LogHandshake, Log, TEXT("Received unexpected post-connect handshake packet - resending ack for LastChallengeSuccessAddress %s and LastCookie %s."), - *LastChallengeSuccessAddress, *FString::FromBlob(AuthorisedCookie, COOKIE_BYTE_SIZE)); + UE_LOG(LogHandshake, Log, TEXT("Received unexpected post-connect handshake packet - resending ack for LastChallengeSuccessAddress %s and LastCookie %s."), + *LastChallengeSuccessAddress->ToString(true), *FString::FromBlob(AuthorisedCookie, COOKIE_BYTE_SIZE)); #endif - SendChallengeAck(LastChallengeSuccessAddress, AuthorisedCookie); + SendChallengeAck(LastChallengeSuccessAddress, AuthorisedCookie); + } } } else @@ -814,6 +815,11 @@ void StatelessConnectHandlerComponent::Incoming(FBitReader& Packet) UE_LOG(LogHandshake, Log, TEXT("Incoming: Error reading handshake bit from packet.")); } #endif + // Servers should wipe LastChallengeSuccessAddress when the first non-handshake packet is received by the client, in order to disable challenge ack resending + else if (LastChallengeSuccessAddress.IsValid() && Handler->Mode == Handler::Mode::Server) + { + LastChallengeSuccessAddress.Reset(); + } } void StatelessConnectHandlerComponent::Outgoing(FBitWriter& Packet, FOutPacketTraits& Traits) @@ -833,7 +839,7 @@ void StatelessConnectHandlerComponent::Outgoing(FBitWriter& Packet, FOutPacketTr Packet = MoveTemp(NewPacket); } -void StatelessConnectHandlerComponent::IncomingConnectionless(const FString& Address, FBitReader& Packet) +void StatelessConnectHandlerComponent::IncomingConnectionless(const TSharedPtr& Address, FBitReader& Packet) { if (MagicHeader.Num() > 0) { @@ -844,7 +850,7 @@ void StatelessConnectHandlerComponent::IncomingConnectionless(const FString& Add bool bHandshakePacket = !!Packet.ReadBit() && !Packet.IsError(); - LastChallengeSuccessAddress.Empty(); + LastChallengeSuccessAddress = nullptr; if (bHandshakePacket) { @@ -980,16 +986,17 @@ bool StatelessConnectHandlerComponent::ParseHandshakePacket(FBitReader& Packet, return bValidPacket; } -void StatelessConnectHandlerComponent::GenerateCookie(FString ClientAddress, uint8 SecretId, float Timestamp, uint8 (&OutCookie)[20]) +void StatelessConnectHandlerComponent::GenerateCookie(TSharedPtr ClientAddress, uint8 SecretId, float Timestamp, uint8 (&OutCookie)[20]) { // @todo #JohnB: Add cpu stats tracking, like what Oodle does upon compression // NOTE: Being serverside, will only show up in .uprof, not on any 'stat' commands. Still necessary though. TArray CookieData; FMemoryWriter CookieArc(CookieData); + FString ClientAddressString(ClientAddress->ToString(true)); CookieArc << Timestamp; - CookieArc << ClientAddress; + CookieArc << ClientAddressString; FSHA1::HMACBuffer(HandshakeSecret[!!SecretId].GetData(), SECRET_BYTE_SIZE, CookieData.GetData(), CookieData.Num(), OutCookie); } diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleBeamModules.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleBeamModules.cpp index 9f46f31a2b4e..a552d6fe6a68 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleBeamModules.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleBeamModules.cpp @@ -1363,6 +1363,8 @@ void UParticleModuleBeamNoise::Spawn(FParticleEmitterInstance* Owner, int32 Offs UParticleSystemComponent* Component = Owner->Component; UParticleModuleTypeDataBeam2* BeamTD = BeamInst->BeamTypeData; + FRandomStream& RandomStream = GetRandomStream(Owner); + SPAWN_INIT; FBeam2TypeDataPayload* BeamData = NULL; @@ -1395,7 +1397,7 @@ void UParticleModuleBeamNoise::Spawn(FParticleEmitterInstance* Owner, int32 Offs int32 CalcFreq = Frequency; if (Frequency_LowRange > 0) { - CalcFreq = FMath::TruncToInt((FMath::SRand() * (Frequency - Frequency_LowRange)) + Frequency_LowRange); + CalcFreq = FMath::TruncToInt((RandomStream.FRand() * (Frequency - Frequency_LowRange)) + Frequency_LowRange); } BEAM2_TYPEDATA_SETFREQUENCY(BeamData->Lock_Max_NumNoisePoints, CalcFreq); diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleComponents.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleComponents.cpp index fcd83c876400..49e57ca4cd99 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleComponents.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleComponents.cpp @@ -1702,7 +1702,9 @@ void UParticleEmitter::CacheEmitterModuleInfo() LockAxisFlags = EPAL_NONE; ModuleOffsetMap.Empty(); ModuleInstanceOffsetMap.Empty(); + ModuleRandomSeedInstanceOffsetMap.Empty(); ModulesNeedingInstanceData.Empty(); + ModulesNeedingRandomSeedInstanceData.Empty(); MeshMaterials.Empty(); DynamicParameterDataOffset = 0; LightDataOffset = 0; @@ -1792,6 +1794,25 @@ void UParticleEmitter::CacheEmitterModuleInfo() } ReqInstanceBytes += TempInstanceBytes; } + + // Add space for per instance random seed value if required + if (FApp::bUseFixedSeed || ParticleModule->bSupportsRandomSeed) + { + // Add the high-lodlevel offset to the lookup map + ModuleRandomSeedInstanceOffsetMap.Add(ParticleModule, ReqInstanceBytes); + // Remember that this module has emitter-instance data + ModulesNeedingRandomSeedInstanceData.Add(ParticleModule); + + // Add all the other LODLevel modules, using the same offset. + // This removes the need to always also grab the HighestLODLevel pointer. + for (int32 LODIdx = 1; LODIdx < LODLevels.Num(); LODIdx++) + { + UParticleLODLevel* CurLODLevel = LODLevels[LODIdx]; + ModuleRandomSeedInstanceOffsetMap.Add(CurLODLevel->Modules[ModuleIdx], ReqInstanceBytes); + } + + ReqInstanceBytes += sizeof(FParticleRandomSeedInstancePayload); + } } if (ParticleModule->IsA(UParticleModuleOrientationAxisLock::StaticClass())) @@ -3336,6 +3357,8 @@ UParticleSystemComponent::UParticleSystemComponent(const FObjectInitializer& Obj bResetOnDetach = false; OldPosition = FVector(0.0f, 0.0f, 0.0f); + RandomStream.Initialize(FApp::bUseFixedSeed ? GetFName() : NAME_None); + PartSysVelocity = FVector(0.0f, 0.0f, 0.0f); WarmupTime = 0.0f; @@ -6555,7 +6578,7 @@ void UParticleSystemComponent::InitializeSystem() if( Template->bUseDelayRange ) { - const float Rand = FMath::FRand(); + const float Rand = RandomStream.FRand(); EmitterDelay = Template->DelayLow + ((Template->Delay - Template->DelayLow) * Rand); } } @@ -7228,8 +7251,7 @@ bool UParticleSystemComponent::GetFloatParameter(const FName InName,float& OutFl } else if (Param.ParamType == PSPT_ScalarRand) { - // check(IsInGameThread()); this isn't exactly cool to call from multiple threads, but it isn't terrible. - OutFloat = Param.Scalar + (Param.Scalar_Low - Param.Scalar) * FMath::SRand(); + OutFloat = Param.Scalar + (Param.Scalar_Low - Param.Scalar) * RandomStream.FRand(); return true; } } @@ -7260,9 +7282,7 @@ bool UParticleSystemComponent::GetVectorParameter(const FName InName,FVector& Ou } else if (Param.ParamType == PSPT_VectorRand) { - check(IsInGameThread()); - FVector RandValue(FMath::SRand(), FMath::SRand(), FMath::SRand()); - OutVector = Param.Vector + (Param.Vector_Low - Param.Vector) * RandValue; + OutVector = Param.Vector + (Param.Vector_Low - Param.Vector) * RandomStream.VRand(); return true; } } @@ -7292,9 +7312,7 @@ bool UParticleSystemComponent::GetAnyVectorParameter(const FName InName,FVector& } if (Param.ParamType == PSPT_VectorRand) { - //check(IsInGameThread()); - FVector RandValue(FMath::SRand(), FMath::SRand(), FMath::SRand()); - OutVector = Param.Vector + (Param.Vector_Low - Param.Vector) * RandValue; + OutVector = Param.Vector + (Param.Vector_Low - Param.Vector) * RandomStream.VRand(); return true; } if (Param.ParamType == PSPT_Scalar) @@ -7305,8 +7323,7 @@ bool UParticleSystemComponent::GetAnyVectorParameter(const FName InName,FVector& } if (Param.ParamType == PSPT_ScalarRand) { - // check(IsInGameThread()); this isn't exactly cool to call from multiple threads, but it isn't terrible. - float OutFloat = Param.Scalar + (Param.Scalar_Low - Param.Scalar) * FMath::SRand(); + float OutFloat = Param.Scalar + (Param.Scalar_Low - Param.Scalar) * RandomStream.FRand(); OutVector = FVector(OutFloat, OutFloat, OutFloat); return true; } diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleEmitterInstances.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleEmitterInstances.cpp index b90adab2ab8d..8beb149743c5 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleEmitterInstances.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleEmitterInstances.cpp @@ -528,12 +528,21 @@ void FParticleEmitterInstance::Init() for (UParticleModule* ParticleModule : SpriteTemplate->ModulesNeedingInstanceData) { - check(ParticleModule); - uint8* PrepInstData = GetModuleInstanceData(ParticleModule); - check(PrepInstData != nullptr); // Shouldn't be in the list if it doesn't have data - ParticleModule->PrepPerInstanceBlock(this, (void*)PrepInstData); + check(ParticleModule); + uint8* PrepInstData = GetModuleInstanceData(ParticleModule); + check(PrepInstData != nullptr); // Shouldn't be in the list if it doesn't have data + ParticleModule->PrepPerInstanceBlock(this, (void*)PrepInstData); } - + + for (UParticleModule* ParticleModule : SpriteTemplate->ModulesNeedingRandomSeedInstanceData) + { + check(ParticleModule); + FParticleRandomSeedInstancePayload* SeedInstancePayload = GetModuleRandomSeedInstanceData(ParticleModule); + check(SeedInstancePayload != nullptr); // Shouldn't be in the list if it doesn't have data + FParticleRandomSeedInfo* RandomSeedInfo = ParticleModule->GetRandomSeedInfo(); + ParticleModule->PrepRandomSeedInstancePayload(this, SeedInstancePayload, RandomSeedInfo ? *RandomSeedInfo : FParticleRandomSeedInfo()); + } + // Offset into emitter specific payload (e.g. TrailComponent requires extra bytes). PayloadOffset = ParticleSize; @@ -544,7 +553,7 @@ void FParticleEmitterInstance::Init() ParticleSize = Align(ParticleSize, 16); // E.g. trail emitters store trailing particles directly after leading one. - ParticleStride = CalculateParticleStride(ParticleSize); + ParticleStride = CalculateParticleStride(ParticleSize); } // Setup the emitter instance material array... @@ -1555,9 +1564,25 @@ uint8* FParticleEmitterInstance::GetModuleInstanceData(UParticleModule* Module) if (Offset) { check(*Offset < (uint32)InstancePayloadSize); - return &(InstanceData[*Offset]); - } + return &(InstanceData[*Offset]); } + } + return NULL; +} + +/** Get pointer to emitter instance random seed payload data for a particular module */ +FParticleRandomSeedInstancePayload* FParticleEmitterInstance::GetModuleRandomSeedInstanceData(UParticleModule* Module) +{ + // If there is instance data present, look up the modules offset + if (InstanceData) + { + uint32* Offset = SpriteTemplate->ModuleRandomSeedInstanceOffsetMap.Find(Module); + if (Offset) + { + check(*Offset < (uint32)InstancePayloadSize); + return (FParticleRandomSeedInstancePayload*)&(InstanceData[*Offset]); + } + } return NULL; } @@ -1620,6 +1645,8 @@ float FParticleEmitterInstance::GetCurrentBurstRateOffset(float& DeltaTime, int3 UParticleLODLevel* LODLevel = GetCurrentLODLevelChecked(); if (LODLevel->SpawnModule->BurstList.Num() > 0) { + FRandomStream& RandomStream = LODLevel->SpawnModule->GetRandomStream(this); + // For each burst in the list for (int32 BurstIdx = 0; BurstIdx < LODLevel->SpawnModule->BurstList.Num(); BurstIdx++) { @@ -1644,7 +1671,7 @@ float FParticleEmitterInstance::GetCurrentBurstRateOffset(float& DeltaTime, int3 int32 Count = BurstEntry->Count; if (BurstEntry->CountLow > -1) { - Count = BurstEntry->CountLow + FMath::RoundToInt(FMath::SRand() * (float)(BurstEntry->Count - BurstEntry->CountLow)); + Count = RandomStream.RandRange(BurstEntry->CountLow, BurstEntry->Count); } // Take in to account scale. float Scale = LODLevel->SpawnModule->BurstScale.GetValue(EmitterTime, Component); @@ -2630,10 +2657,12 @@ void FParticleEmitterInstance::SetupEmitterDuration() UParticleLODLevel* TempLOD = SpriteTemplate->LODLevels[LODIndex]; UParticleModuleRequired* RequiredModule = TempLOD->RequiredModule; + FRandomStream& RandomStream = RequiredModule->GetRandomStream(this); + CurrentDelay = RequiredModule->EmitterDelay + Component->EmitterDelay; if (RequiredModule->bEmitterDelayUseRange) { - const float Rand = FMath::FRand(); + const float Rand = RandomStream.FRand(); CurrentDelay = RequiredModule->EmitterDelayLow + ((RequiredModule->EmitterDelay - RequiredModule->EmitterDelayLow) * Rand) + Component->EmitterDelay; } @@ -2641,7 +2670,7 @@ void FParticleEmitterInstance::SetupEmitterDuration() if (RequiredModule->bEmitterDurationUseRange) { - const float Rand = FMath::FRand(); + const float Rand = RandomStream.FRand(); const float Duration = RequiredModule->EmitterDurationLow + ((RequiredModule->EmitterDuration - RequiredModule->EmitterDurationLow) * Rand); EmitterDurations[TempLOD->Level] = Duration + CurrentDelay; diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleGpuSimulation.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleGpuSimulation.cpp index 2a57e96d5406..e176bc9e0151 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleGpuSimulation.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleGpuSimulation.cpp @@ -3565,7 +3565,7 @@ FGPUSpriteParticleEmitterInstance(FFXSystem* InFXSystem, FGPUSpriteEmitterInfo& check(AllocatedTiles.Num() == TileTimeOfDeath.Num()); FreeParticlesInTile = 0; - RandomStream.Initialize( FMath::Rand() ); + RandomStream.Initialize(Component->RandomStream.FRand()); EmitterInstRandom = RandomStream.GetFraction(); FParticleSimulationResources* ParticleSimulationResources = FXSystem->GetParticleSimulationResources(); diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules.cpp index c08978b363d2..9ae7cc7f2cd2 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules.cpp @@ -14,6 +14,7 @@ #include "UObject/RenderingObjectVersion.h" #include "UObject/UObjectHash.h" #include "UObject/Package.h" +#include "Misc/App.h" #include "GameFramework/WorldSettings.h" #include "Particles/ParticleSystem.h" #include "ParticleHelper.h" @@ -666,12 +667,18 @@ void UParticleModule::GetParticleParametersUtilized(TArray& ParticlePar uint32 UParticleModule::PrepRandomSeedInstancePayload(FParticleEmitterInstance* Owner, FParticleRandomSeedInstancePayload* InRandSeedPayload, const FParticleRandomSeedInfo& InRandSeedInfo) { - if (InRandSeedPayload != NULL) + // These should never be null + if (!ensure( Owner != nullptr && Owner->Component != nullptr )) { - FMemory::Memzero(InRandSeedPayload, sizeof(FParticleRandomSeedInstancePayload)); + return 0xffffffff; + } + + if (InRandSeedPayload != nullptr) + { + new(InRandSeedPayload) FParticleRandomSeedInstancePayload(); // See if the parameter is set on the instance... - if ((Owner != NULL) && (Owner->Component != NULL) && (InRandSeedInfo.bGetSeedFromInstance == true)) + if (InRandSeedInfo.bGetSeedFromInstance == true) { float SeedValue; if (Owner->Component->GetFloatParameter(InRandSeedInfo.ParameterName, SeedValue) == true) @@ -700,15 +707,19 @@ uint32 UParticleModule::PrepRandomSeedInstancePayload(FParticleEmitterInstance* // Pick a seed to use and initialize it!!!! if (InRandSeedInfo.RandomSeeds.Num() > 0) { + int32 Index = 0; + if (InRandSeedInfo.bRandomlySelectSeedArray) { - int32 Index = FMath::RandRange(0, InRandSeedInfo.RandomSeeds.Num() - 1); - InRandSeedPayload->RandomStream.Initialize(InRandSeedInfo.RandomSeeds[Index]); - } - else - { - InRandSeedPayload->RandomStream.Initialize(InRandSeedInfo.RandomSeeds[0]); + Index = Owner->Component->RandomStream.RandHelper(InRandSeedInfo.RandomSeeds.Num()); } + + InRandSeedPayload->RandomStream.Initialize(InRandSeedInfo.RandomSeeds[Index]); + return 0; + } + else if (FApp::bUseFixedSeed) + { + InRandSeedPayload->RandomStream.Initialize(GetFName()); return 0; } } @@ -789,6 +800,13 @@ bool UParticleModule::IsUsedInGPUEmitter()const return false; } +FRandomStream& UParticleModule::GetRandomStream(FParticleEmitterInstance* Owner) +{ + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); + FRandomStream& RandomStream = (Payload != nullptr) ? Payload->RandomStream : Owner->Component->RandomStream; + return RandomStream; +} + #if WITH_EDITOR void UParticleModule::SetTransactionFlag() { @@ -1404,7 +1422,7 @@ void UParticleModuleMeshRotation::PostEditChangeProperty(FPropertyChangedEvent& void UParticleModuleMeshRotation::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } @@ -1445,27 +1463,11 @@ UParticleModuleMeshRotation_Seeded::UParticleModuleMeshRotation_Seeded(const FOb bRequiresLoopingNotification = true; } -void UParticleModuleMeshRotation_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleMeshRotation_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleMeshRotation_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleMeshRotation_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -1518,7 +1520,7 @@ void UParticleModuleMeshRotationRate::PostEditChangeProperty(FPropertyChangedEve void UParticleModuleMeshRotationRate::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleMeshRotationRate::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -1563,28 +1565,11 @@ UParticleModuleMeshRotationRate_Seeded::UParticleModuleMeshRotationRate_Seeded(c bRequiresLoopingNotification = true; } -void UParticleModuleMeshRotationRate_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleMeshRotationRate_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - - -uint32 UParticleModuleMeshRotationRate_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleMeshRotationRate_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -1810,7 +1795,7 @@ void UParticleModuleRotation::PostEditChangeProperty(FPropertyChangedEvent& Prop void UParticleModuleRotation::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleRotation::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -1832,27 +1817,11 @@ UParticleModuleRotation_Seeded::UParticleModuleRotation_Seeded(const FObjectInit bRequiresLoopingNotification = true; } -void UParticleModuleRotation_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleRotation_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleRotation_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleRotation_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -1905,7 +1874,7 @@ void UParticleModuleRotationRate::PostEditChangeProperty(FPropertyChangedEvent& void UParticleModuleRotationRate::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleRotationRate::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -1941,27 +1910,11 @@ UParticleModuleRotationRate_Seeded::UParticleModuleRotationRate_Seeded(const FOb bRequiresLoopingNotification = true; } -void UParticleModuleRotationRate_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleRotationRate_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleRotationRate_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleRotationRate_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -2248,8 +2201,7 @@ float UParticleModuleSubUV::DetermineImageIndex(FParticleEmitterInstance* Owner, ((Particle->RelativeTime - SubUVPayload.RandomImageTime) > LODLevel->RequiredModule->RandomImageTime) || (SubUVPayload.RandomImageTime == 0.0f)) { - const float RandomNumber = FMath::SRand(); - ImageIndex = FMath::TruncToInt(RandomNumber * TotalSubImages); + ImageIndex = GetRandomStream(Owner).RandHelper(TotalSubImages); SubUVPayload.RandomImageTime = Particle->RelativeTime; } @@ -2381,7 +2333,8 @@ void UParticleModuleSubUVMovie::Spawn(FParticleEmitterInstance* Owner, int32 Off } else if (StartingFrame == 0) { - MoviePayload.Time = FMath::TruncToFloat(FMath::SRand() * (TotalSubImages-1)); + FRandomStream& RandomStream = GetRandomStream(Owner); + MoviePayload.Time = FMath::TruncToFloat(RandomStream.FRand() * (TotalSubImages-1)); } // Update the payload @@ -3117,7 +3070,7 @@ void UParticleModuleLight::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset LightData.RadiusScale = RadiusScale.GetValue(Owner->EmitterTime, Owner->Component, InRandomStream); // Exponent of 0 is interpreted by renderer as inverse squared falloff LightData.LightExponent = bUseInverseSquaredFalloff ? 0 : LightExponent.GetValue(Owner->EmitterTime, Owner->Component, InRandomStream); - const float RandomNumber = InRandomStream ? InRandomStream->GetFraction() : FMath::SRand(); + const float RandomNumber = InRandomStream->GetFraction(); LightData.bValid = RandomNumber < SpawnFraction; LightData.bAffectsTranslucency = bAffectsTranslucency; LightData.bHighQuality = bHighQualityLights; @@ -3240,7 +3193,7 @@ void UParticleModuleLight::UpdateHQLight(UPointLightComponent* PointLightCompone void UParticleModuleLight::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleLight::Update(FParticleEmitterInstance* Owner, int32 Offset, float DeltaTime) @@ -3389,27 +3342,11 @@ UParticleModuleLight_Seeded::UParticleModuleLight_Seeded(const FObjectInitialize bRequiresLoopingNotification = true; } -void UParticleModuleLight_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this) + Super::RequiredBytesPerInstance()); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleLight_Seeded::RequiredBytesPerInstance() -{ - return Super::RequiredBytesPerInstance() + RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleLight_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)((uint8*)InstData + Super::RequiredBytesPerInstance()), RandomSeedInfo); -} - void UParticleModuleLight_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this) + Super::RequiredBytesPerInstance()); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -3876,7 +3813,7 @@ void UParticleModuleLifetime::CompileModule( struct FParticleEmitterBuildInfo& E void UParticleModuleLifetime::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleLifetime::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -3942,39 +3879,18 @@ UParticleModuleLifetime_Seeded::UParticleModuleLifetime_Seeded(const FObjectInit bRequiresLoopingNotification = true; } -void UParticleModuleLifetime_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleLifetime_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleLifetime_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleLifetime_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } float UParticleModuleLifetime_Seeded::GetLifetimeValue(FParticleEmitterInstance* Owner, float InTime, UObject* Data ) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - if (Payload != NULL) - { - return Lifetime.GetValue(InTime, Data, &(Payload->RandomStream)); - } - return UParticleModuleLifetime::GetLifetimeValue(Owner, InTime, Data); + return Lifetime.GetValue(InTime, Data, &GetRandomStream(Owner)); } /*----------------------------------------------------------------------------- @@ -4208,7 +4124,7 @@ void UParticleModuleAttractorParticle::Spawn(FParticleEmitterInstance* Owner, in switch (SelectionMethod) { case EAPSM_Random: - LastSelIndex = FMath::TruncToInt(FMath::SRand() * AttractorEmitterInst->ActiveParticles); + LastSelIndex = GetRandomStream(Owner).RandHelper(AttractorEmitterInst->ActiveParticles); Data.SourceIndex = LastSelIndex; break; case EAPSM_Sequential: diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Color.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Color.cpp index 568c3eaf4fc9..a2736831839a 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Color.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Color.cpp @@ -155,7 +155,7 @@ bool UParticleModuleColor::AddModuleCurvesToEditor(UInterpCurveEdSetup* EdSetup, void UParticleModuleColor::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } @@ -192,27 +192,11 @@ UParticleModuleColor_Seeded::UParticleModuleColor_Seeded(const FObjectInitialize bRequiresLoopingNotification = true; } -void UParticleModuleColor_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleColor_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleColor_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleColor_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Location.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Location.cpp index 4af17d5d0118..2a61259c39b9 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Location.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Location.cpp @@ -90,7 +90,7 @@ void UParticleModuleLocation::PostEditChangeProperty(FPropertyChangedEvent& Prop void UParticleModuleLocation::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleLocation::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -103,7 +103,7 @@ void UParticleModuleLocation::SpawnEx(FParticleEmitterInstance* Owner, int32 Off // Avoid divide by zero. if ((DistributeOverNPoints != 0.0f) && (DistributeOverNPoints != 1.f)) { - float RandomNum = FMath::SRand() * FMath::Fractional(Owner->EmitterTime); + float RandomNum = InRandomStream->FRand() * FMath::Fractional(Owner->EmitterTime); if(RandomNum > DistributeThreshold) { @@ -113,7 +113,7 @@ void UParticleModuleLocation::SpawnEx(FParticleEmitterInstance* Owner, int32 Off { FVector Min, Max; StartLocation.GetRange(Min, Max); - FVector Lerped = FMath::Lerp(Min, Max, FMath::TruncToFloat((FMath::SRand() * (DistributeOverNPoints - 1.0f)) + 0.5f)/(DistributeOverNPoints - 1.0f)); + FVector Lerped = FMath::Lerp(Min, Max, FMath::TruncToFloat((InRandomStream->FRand() * (DistributeOverNPoints - 1.0f)) + 0.5f)/(DistributeOverNPoints - 1.0f)); LocationOffset.Set(Lerped.X, Lerped.Y, Lerped.Z); } } @@ -187,27 +187,11 @@ UParticleModuleLocation_Seeded::UParticleModuleLocation_Seeded(const FObjectInit bRequiresLoopingNotification = true; } -void UParticleModuleLocation_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleLocation_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleLocation_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleLocation_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -251,27 +235,11 @@ UParticleModuleLocationWorldOffset_Seeded::UParticleModuleLocationWorldOffset_Se bRequiresLoopingNotification = true; } -void UParticleModuleLocationWorldOffset_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleLocationWorldOffset_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleLocationWorldOffset_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleLocationWorldOffset_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -448,6 +416,8 @@ void UParticleModuleLocationEmitter::Spawn(FParticleEmitterInstance* Owner, int3 bool bSourceIsInLocalSpace = LocationEmitterInst->CurrentLODLevel->RequiredModule->bUseLocalSpace; bool bInLocalSpace = Owner->CurrentLODLevel->RequiredModule->bUseLocalSpace; + FRandomStream& RandomStream = GetRandomStream(Owner); + SPAWN_INIT; { int32 Index = 0; @@ -456,11 +426,7 @@ void UParticleModuleLocationEmitter::Spawn(FParticleEmitterInstance* Owner, int3 { case ELESM_Random: { - Index = FMath::TruncToInt(FMath::SRand() * LocationEmitterInst->ActiveParticles); - if (Index >= LocationEmitterInst->ActiveParticles) - { - Index = LocationEmitterInst->ActiveParticles - 1; - } + Index = RandomStream.RandHelper(LocationEmitterInst->ActiveParticles); } break; case ELESM_Sequential: @@ -719,18 +685,9 @@ void UParticleModuleLocationPrimitiveBase::DetermineUnitDirection(FParticleEmitt FVector vRand; // Grab 3 random numbers for the axes - if (InRandomStream == NULL) - { - vRand.X = FMath::SRand(); - vRand.Y = FMath::SRand(); - vRand.Z = FMath::SRand(); - } - else - { - vRand.X = InRandomStream->GetFraction(); - vRand.Y = InRandomStream->GetFraction(); - vRand.Z = InRandomStream->GetFraction(); - } + vRand.X = InRandomStream->GetFraction(); + vRand.Y = InRandomStream->GetFraction(); + vRand.Z = InRandomStream->GetFraction(); // Set the unit dir if (Positive_X && Negative_X) @@ -846,7 +803,7 @@ void UParticleModuleLocationPrimitiveTriangle::PostEditChangeProperty(FPropertyC void UParticleModuleLocationPrimitiveTriangle::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleLocationPrimitiveTriangle::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -868,21 +825,12 @@ void UParticleModuleLocationPrimitiveTriangle::SpawnEx(FParticleEmitterInstance* float BarycentricCoords[3] = {0}; float ZPos = 0.0f; - if (InRandomStream) - { - BarycentricCoords[0] = InRandomStream->GetFraction(); - BarycentricCoords[1] = InRandomStream->GetFraction(); - BarycentricCoords[2] = InRandomStream->GetFraction(); - ZPos = InRandomStream->GetFraction(); - } - else - { - BarycentricCoords[0] = FMath::SRand(); - BarycentricCoords[1] = FMath::SRand(); - BarycentricCoords[2] = FMath::SRand(); - ZPos = FMath::SRand(); - } + BarycentricCoords[0] = InRandomStream->GetFraction(); + BarycentricCoords[1] = InRandomStream->GetFraction(); + BarycentricCoords[2] = InRandomStream->GetFraction(); + ZPos = InRandomStream->GetFraction(); + FVector LocationOffset = FVector::ZeroVector; float Sum = FMath::Max(KINDA_SMALL_NUMBER, BarycentricCoords[0] + BarycentricCoords[1] + BarycentricCoords[2]); for (int32 i = 0; i < 3; i++) @@ -1000,7 +948,7 @@ void UParticleModuleLocationPrimitiveCylinder::PostEditChangeProperty(FPropertyC void UParticleModuleLocationPrimitiveCylinder::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleLocationPrimitiveCylinder::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -1240,27 +1188,11 @@ UParticleModuleLocationPrimitiveCylinder_Seeded::UParticleModuleLocationPrimitiv bRequiresLoopingNotification = true; } -void UParticleModuleLocationPrimitiveCylinder_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleLocationPrimitiveCylinder_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleLocationPrimitiveCylinder_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleLocationPrimitiveCylinder_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -1304,7 +1236,7 @@ void UParticleModuleLocationPrimitiveSphere::PostEditChangeProperty(FPropertyCha void UParticleModuleLocationPrimitiveSphere::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleLocationPrimitiveSphere::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -1456,27 +1388,11 @@ UParticleModuleLocationPrimitiveSphere_Seeded::UParticleModuleLocationPrimitiveS bRequiresLoopingNotification = true; } -void UParticleModuleLocationPrimitiveSphere_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleLocationPrimitiveSphere_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleLocationPrimitiveSphere_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleLocationPrimitiveSphere_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -1511,7 +1427,7 @@ UParticleModuleLocationBoneSocket::UParticleModuleLocationBoneSocket(const FObje InheritVelocityScale = 1.0f; } -int32 UParticleModuleLocationBoneSocket::SelectNextSpawnIndex(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent) +int32 UParticleModuleLocationBoneSocket::SelectNextSpawnIndex(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent, FRandomStream& InRandomStream) { check(InstancePayload && SourceComponent); @@ -1531,7 +1447,7 @@ int32 UParticleModuleLocationBoneSocket::SelectNextSpawnIndex(FModuleLocationBon else if (SelectionMethod == BONESOCKETSEL_Random) { // Note: This can select the same socket over and over... - SourceIndex = FMath::TruncToInt(FMath::SRand() * ((float)MaxIndex - 0.5f)); + SourceIndex = FMath::TruncToInt(InRandomStream.FRand() * ((float)MaxIndex - 0.5f)); InstancePayload->LastSelectedIndex = SourceIndex; } @@ -1547,7 +1463,7 @@ int32 UParticleModuleLocationBoneSocket::SelectNextSpawnIndex(FModuleLocationBon return SourceIndex; } -void UParticleModuleLocationBoneSocket::RegeneratePreSelectedIndices(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent) +void UParticleModuleLocationBoneSocket::RegeneratePreSelectedIndices(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent, FRandomStream& InRandomStream) { if (SourceIndexMode == EBoneSocketSourceIndexMode::PreSelectedIndices) { @@ -1555,7 +1471,7 @@ void UParticleModuleLocationBoneSocket::RegeneratePreSelectedIndices(FModuleLoca for (int32 i = 0; i < NumPreSelectedIndices; ++i) { //Should we provide sequential selection here? Does that make sense for the pre selected list? - InstancePayload->PreSelectedBoneSocketIndices[i] = FMath::TruncToInt(FMath::SRand() * ((float)MaxIndex - 0.5f)); + InstancePayload->PreSelectedBoneSocketIndices[i] = FMath::TruncToInt(InRandomStream.FRand() * ((float)MaxIndex - 0.5f)); } if (InheritingBoneVelocity()) @@ -1637,6 +1553,8 @@ void UParticleModuleLocationBoneSocket::Spawn(FParticleEmitterInstance* Owner, i return; } + FRandomStream& RandomStream = GetRandomStream(Owner); + // Setup the source skeletal mesh component... GetSkeletalMeshComponentSource(Owner, InstancePayload); @@ -1647,7 +1565,7 @@ void UParticleModuleLocationBoneSocket::Spawn(FParticleEmitterInstance* Owner, i } USkeletalMeshComponent* SourceComponent = InstancePayload->SourceComponent.Get(); - int32 SourceIndex = SelectNextSpawnIndex(InstancePayload, SourceComponent); + int32 SourceIndex = SelectNextSpawnIndex(InstancePayload, SourceComponent, RandomStream); if (SourceIndex == INDEX_NONE) { return; @@ -1821,7 +1739,7 @@ void UParticleModuleLocationBoneSocket::FinalUpdate(FParticleEmitterInstance* Ow } //Select a new set of bones to spawn from next frame. - RegeneratePreSelectedIndices(InstancePayload, SourceComponent); + RegeneratePreSelectedIndices(InstancePayload, SourceComponent, GetRandomStream(Owner)); } uint32 UParticleModuleLocationBoneSocket::RequiredBytes(UParticleModuleTypeDataBase* TypeData) @@ -2070,7 +1988,7 @@ void UParticleModuleLocationBoneSocket::GetSkeletalMeshComponentSource(FParticle if (NewSkelComp) { InstancePayload->SourceComponent = NewSkelComp; - RegeneratePreSelectedIndices(InstancePayload, NewSkelComp); + RegeneratePreSelectedIndices(InstancePayload, NewSkelComp, GetRandomStream(Owner)); } } } @@ -2324,6 +2242,8 @@ void UParticleModuleLocationSkelVertSurface::Spawn(FParticleEmitterInstance* Own { return; } + + FRandomStream& RandomStream = GetRandomStream(Owner); GetSkeletalMeshComponentSource(Owner, InstancePayload); @@ -2353,7 +2273,7 @@ void UParticleModuleLocationSkelVertSurface::Spawn(FParticleEmitterInstance* Own { int32 SourceLocationsCount(LODData.GetNumVertices()); - SourceIndex = FMath::TruncToInt(FMath::SRand() * ((float)SourceLocationsCount) - 1); + SourceIndex = FMath::TruncToInt(RandomStream.FRand() * ((float)SourceLocationsCount) - 1); InstancePayload->VertIndex = SourceIndex; if(SourceIndex != -1) @@ -2372,10 +2292,10 @@ void UParticleModuleLocationSkelVertSurface::Spawn(FParticleEmitterInstance* Own else if(SourceType == VERTSURFACESOURCE_Surface) { int32 SectionCount = LODData.RenderSections.Num(); - int32 RandomSection = FMath::RoundToInt(FMath::SRand() * ((float)SectionCount-1)); + int32 RandomSection = FMath::RoundToInt(RandomStream.FRand() * ((float)SectionCount-1)); SourceIndex = LODData.RenderSections[RandomSection].BaseIndex + - (FMath::TruncToInt(FMath::SRand() * ((float)LODData.RenderSections[RandomSection].NumTriangles))*3); + (FMath::TruncToInt(RandomStream.FRand() * ((float)LODData.RenderSections[RandomSection].NumTriangles))*3); InstancePayload->VertIndex = SourceIndex; @@ -2506,10 +2426,7 @@ void UParticleModuleLocationSkelVertSurface::Spawn(FParticleEmitterInstance* Own { //We have the mesh oriented to the normal of the triangle it's on but this looks fugly as particles on each triangle are facing the same way. //The only valid orientation reference should be the normal. So add an additional random rotation around it. - int32 OldRandSeed = FMath::GetRandSeed(); - FMath::SRandInit((int32)((intptr_t)ParticleBase)); - SourceRotation = SourceRotation * FQuat(FVector::UpVector, FMath::SRand()*(PI*2.0f)); - FMath::SRandInit(OldRandSeed); + SourceRotation = SourceRotation * FQuat(FVector::UpVector, RandomStream.FRand()*(PI*2.0f)); } FVector Rot = SourceRotation.Euler(); @@ -2581,6 +2498,8 @@ void UParticleModuleLocationSkelVertSurface::Update(FParticleEmitterInstance* Ow const bool bMeshRotationActive = MeshRotationOffset > 0 && Owner->IsMeshRotationActive(); const FTransform& OwnerTM = Owner->Component->GetAsyncComponentToWorld(); + FRandomStream& RandomStream = GetRandomStream(Owner); + BEGIN_UPDATE_LOOP; { FModuleLocationVertSurfaceParticlePayload* ParticlePayload = (FModuleLocationVertSurfaceParticlePayload*)((uint8*)&Particle + Offset); @@ -2594,11 +2513,7 @@ void UParticleModuleLocationSkelVertSurface::Update(FParticleEmitterInstance* Ow { //We have the mesh oriented to the normal of the triangle it's on but this looks fugly as particles on each triangle are facing the same way. //The only valid orientation reference should be the normal. So add an additional random rotation around it. - - int32 OldRandSeed = FMath::GetRandSeed(); - FMath::SRandInit((int32)((intptr_t)ParticleBase)); - SourceRotation = SourceRotation * FQuat(FVector::UpVector, FMath::SRand()*(PI*2.0f)); - FMath::SRandInit(OldRandSeed); + SourceRotation = SourceRotation * FQuat(FVector::UpVector, RandomStream.FRand()*(PI*2.0f)); } FMeshRotationPayloadData* PayloadData = (FMeshRotationPayloadData*)((uint8*)&Particle + MeshRotationOffset); diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Parameter.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Parameter.cpp index 3cc78e995c7e..938b438abd0a 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Parameter.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Parameter.cpp @@ -137,7 +137,7 @@ bool UParticleModuleParameterDynamic::CanTickInAnyThread() void UParticleModuleParameterDynamic::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleParameterDynamic::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -178,6 +178,8 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in return; } + FRandomStream& RandomStream = GetRandomStream(Owner); + // int32 ParameterIndex = ParticleDynamicParameter_GetIndexFromFlag(UpdateFlags); @@ -201,7 +203,7 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[ParameterIndex] = GetParameterValue_UserSet(DynParam, Particle, Owner, NULL); + DynamicPayload.DynamicParameterValue[ParameterIndex] = GetParameterValue_UserSet(DynParam, Particle, Owner, &RandomStream); } END_UPDATE_LOOP; } @@ -212,7 +214,7 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[ParameterIndex] = GetParameterValue(DynParam, Particle, Owner, NULL); + DynamicPayload.DynamicParameterValue[ParameterIndex] = GetParameterValue(DynParam, Particle, Owner, &RandomStream); } END_UPDATE_LOOP; } @@ -230,8 +232,8 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[0] = GetParameterValue_UserSet(DynParam0, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[1] = GetParameterValue_UserSet(DynParam1, Particle, Owner, NULL); + DynamicPayload.DynamicParameterValue[0] = GetParameterValue_UserSet(DynParam0, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[1] = GetParameterValue_UserSet(DynParam1, Particle, Owner, &RandomStream); } END_UPDATE_LOOP; } @@ -242,8 +244,8 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[0] = GetParameterValue(DynParam0, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[1] = GetParameterValue(DynParam1, Particle, Owner, NULL); + DynamicPayload.DynamicParameterValue[0] = GetParameterValue(DynParam0, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[1] = GetParameterValue(DynParam1, Particle, Owner, &RandomStream); } END_UPDATE_LOOP; } @@ -262,9 +264,9 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[0] = GetParameterValue_UserSet(DynParam0, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[1] = GetParameterValue_UserSet(DynParam1, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[2] = GetParameterValue_UserSet(DynParam2, Particle, Owner, NULL); + DynamicPayload.DynamicParameterValue[0] = GetParameterValue_UserSet(DynParam0, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[1] = GetParameterValue_UserSet(DynParam1, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[2] = GetParameterValue_UserSet(DynParam2, Particle, Owner, &RandomStream); } END_UPDATE_LOOP; } @@ -275,9 +277,9 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[0] = GetParameterValue(DynParam0, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[1] = GetParameterValue(DynParam1, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[2] = GetParameterValue(DynParam2, Particle, Owner, NULL); + DynamicPayload.DynamicParameterValue[0] = GetParameterValue(DynParam0, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[1] = GetParameterValue(DynParam1, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[2] = GetParameterValue(DynParam2, Particle, Owner, &RandomStream); } END_UPDATE_LOOP; } @@ -296,10 +298,10 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[0] = GetParameterValue_UserSet(DynParam0, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[1] = GetParameterValue_UserSet(DynParam1, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[2] = GetParameterValue_UserSet(DynParam2, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[3] = GetParameterValue_UserSet(DynParam3, Particle, Owner, NULL); + DynamicPayload.DynamicParameterValue[0] = GetParameterValue_UserSet(DynParam0, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[1] = GetParameterValue_UserSet(DynParam1, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[2] = GetParameterValue_UserSet(DynParam2, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[3] = GetParameterValue_UserSet(DynParam3, Particle, Owner, &RandomStream); } END_UPDATE_LOOP; } @@ -310,10 +312,10 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[0] = GetParameterValue(DynParam0, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[1] = GetParameterValue(DynParam1, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[2] = GetParameterValue(DynParam2, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[3] = GetParameterValue(DynParam3, Particle, Owner, NULL); + DynamicPayload.DynamicParameterValue[0] = GetParameterValue(DynParam0, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[1] = GetParameterValue(DynParam1, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[2] = GetParameterValue(DynParam2, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[3] = GetParameterValue(DynParam3, Particle, Owner, &RandomStream); } END_UPDATE_LOOP; } @@ -332,10 +334,10 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[0] = (UpdateFlags & EDPU_UPDATE_0) ? GetParameterValue_UserSet(DynParam0, Particle, Owner, NULL) : DynamicPayload.DynamicParameterValue[0]; - DynamicPayload.DynamicParameterValue[1] = (UpdateFlags & EDPU_UPDATE_1) ? GetParameterValue_UserSet(DynParam1, Particle, Owner, NULL) : DynamicPayload.DynamicParameterValue[1]; - DynamicPayload.DynamicParameterValue[2] = (UpdateFlags & EDPU_UPDATE_2) ? GetParameterValue_UserSet(DynParam2, Particle, Owner, NULL) : DynamicPayload.DynamicParameterValue[2]; - DynamicPayload.DynamicParameterValue[3] = (UpdateFlags & EDPU_UPDATE_3) ? GetParameterValue_UserSet(DynParam3, Particle, Owner, NULL) : DynamicPayload.DynamicParameterValue[3]; + DynamicPayload.DynamicParameterValue[0] = (UpdateFlags & EDPU_UPDATE_0) ? GetParameterValue_UserSet(DynParam0, Particle, Owner, &RandomStream) : DynamicPayload.DynamicParameterValue[0]; + DynamicPayload.DynamicParameterValue[1] = (UpdateFlags & EDPU_UPDATE_1) ? GetParameterValue_UserSet(DynParam1, Particle, Owner, &RandomStream) : DynamicPayload.DynamicParameterValue[1]; + DynamicPayload.DynamicParameterValue[2] = (UpdateFlags & EDPU_UPDATE_2) ? GetParameterValue_UserSet(DynParam2, Particle, Owner, &RandomStream) : DynamicPayload.DynamicParameterValue[2]; + DynamicPayload.DynamicParameterValue[3] = (UpdateFlags & EDPU_UPDATE_3) ? GetParameterValue_UserSet(DynParam3, Particle, Owner, &RandomStream) : DynamicPayload.DynamicParameterValue[3]; } END_UPDATE_LOOP; } @@ -346,10 +348,10 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[0] = (UpdateFlags & EDPU_UPDATE_0) ? GetParameterValue(DynParam0, Particle, Owner, NULL) : DynamicPayload.DynamicParameterValue[0]; - DynamicPayload.DynamicParameterValue[1] = (UpdateFlags & EDPU_UPDATE_1) ? GetParameterValue(DynParam1, Particle, Owner, NULL) : DynamicPayload.DynamicParameterValue[1]; - DynamicPayload.DynamicParameterValue[2] = (UpdateFlags & EDPU_UPDATE_2) ? GetParameterValue(DynParam2, Particle, Owner, NULL) : DynamicPayload.DynamicParameterValue[2]; - DynamicPayload.DynamicParameterValue[3] = (UpdateFlags & EDPU_UPDATE_3) ? GetParameterValue(DynParam3, Particle, Owner, NULL) : DynamicPayload.DynamicParameterValue[3]; + DynamicPayload.DynamicParameterValue[0] = (UpdateFlags & EDPU_UPDATE_0) ? GetParameterValue(DynParam0, Particle, Owner, &RandomStream) : DynamicPayload.DynamicParameterValue[0]; + DynamicPayload.DynamicParameterValue[1] = (UpdateFlags & EDPU_UPDATE_1) ? GetParameterValue(DynParam1, Particle, Owner, &RandomStream) : DynamicPayload.DynamicParameterValue[1]; + DynamicPayload.DynamicParameterValue[2] = (UpdateFlags & EDPU_UPDATE_2) ? GetParameterValue(DynParam2, Particle, Owner, &RandomStream) : DynamicPayload.DynamicParameterValue[2]; + DynamicPayload.DynamicParameterValue[3] = (UpdateFlags & EDPU_UPDATE_3) ? GetParameterValue(DynParam3, Particle, Owner, &RandomStream) : DynamicPayload.DynamicParameterValue[3]; } END_UPDATE_LOOP; } @@ -575,22 +577,6 @@ UParticleModuleParameterDynamic_Seeded::UParticleModuleParameterDynamic_Seeded(c bRequiresLoopingNotification = true; } -void UParticleModuleParameterDynamic_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleParameterDynamic_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleParameterDynamic_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleParameterDynamic_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Size.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Size.cpp index 0b89b12b1b20..79761cd38292 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Size.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Size.cpp @@ -84,7 +84,7 @@ void UParticleModuleSize::PostEditChangeProperty(FPropertyChangedEvent& Property void UParticleModuleSize::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleSize::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -93,7 +93,7 @@ void UParticleModuleSize::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, FVector Size = StartSize.GetValue(Owner->EmitterTime, Owner->Component, 0, InRandomStream); Particle.Size += Size; - AdjustParticleBaseSizeForUVFlipping(Size, Owner->CurrentLODLevel->RequiredModule->UVFlippingMode); + AdjustParticleBaseSizeForUVFlipping(Size, Owner->CurrentLODLevel->RequiredModule->UVFlippingMode, *InRandomStream); Particle.BaseSize += Size; } @@ -108,27 +108,11 @@ UParticleModuleSize_Seeded::UParticleModuleSize_Seeded(const FObjectInitializer& bRequiresLoopingNotification = true; } -void UParticleModuleSize_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleSize_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleSize_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleSize_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Velocity.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Velocity.cpp index 6a94923a6fad..779814fc189b 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Velocity.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Velocity.cpp @@ -73,7 +73,7 @@ void UParticleModuleVelocity::PostEditChangeProperty(FPropertyChangedEvent& Prop void UParticleModuleVelocity::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleVelocity::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -124,22 +124,6 @@ UParticleModuleVelocity_Seeded::UParticleModuleVelocity_Seeded(const FObjectInit bRequiresLoopingNotification = true; } -void UParticleModuleVelocity_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleVelocity_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleVelocity_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleVelocity_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) @@ -415,7 +399,7 @@ void UParticleModuleVelocityCone::PostEditChangeProperty(FPropertyChangedEvent& void UParticleModuleVelocityCone::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleVelocityCone::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -440,7 +424,7 @@ void UParticleModuleVelocityCone::SpawnEx(FParticleEmitterInstance* Owner, int32 // Calculate the default position (prior to the Direction vector being factored in) const float SpawnAngle = Angle.GetValue(Owner->EmitterTime, Owner->Component, InRandomStream); const float SpawnVelocity = Velocity.GetValue(Owner->EmitterTime, Owner->Component, InRandomStream); - const float LatheAngle = FMath::SRand() * TwoPI; + const float LatheAngle = InRandomStream->FRand() * TwoPI; const FRotator DefaultDirectionRotater((int32)(SpawnAngle * ToRads * UUPerRad), (int32)(LatheAngle * UUPerRad), 0); const FRotationMatrix DefaultDirectionRotation(DefaultDirectionRotater); const FVector DefaultSpawnDirection = DefaultDirectionRotation.TransformVector(DefaultDirection); diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleSystemManager.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleSystemManager.cpp index 0ff4411a3cbe..2ee0ffffd957 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleSystemManager.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleSystemManager.cpp @@ -256,6 +256,11 @@ void FParticleSystemWorldManager::AddReferencedObjects(FReferenceCollector& Coll } } +FString FParticleSystemWorldManager::GetReferencerName() const +{ + return TEXT("FParticleSystemWorldManager"); +} + void FParticleSystemWorldManager::HandlePostGarbageCollect() { //UE_LOG(LogParticles, Warning, TEXT("| HandlePostGarbageCollect |")); diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleTrail2EmitterInstance.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleTrail2EmitterInstance.cpp index 9b51b1c89b29..0a3c5129c96e 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleTrail2EmitterInstance.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleTrail2EmitterInstance.cpp @@ -2223,7 +2223,8 @@ bool FParticleRibbonEmitterInstance::ResolveSourcePoint(int32 InTrailIdx, { case EPSSM_Random: { - Index = FMath::TruncToInt(FMath::FRand() * SourceEmitter->ActiveParticles); + FRandomStream& RandomStream = SourceModule->GetRandomStream(this); + Index = RandomStream.RandHelper(SourceEmitter->ActiveParticles); } break; case EPSSM_Sequential: diff --git a/Engine/Source/Runtime/Engine/Private/Pawn.cpp b/Engine/Source/Runtime/Engine/Private/Pawn.cpp index d53d665cd108..674ae35a3bf3 100644 --- a/Engine/Source/Runtime/Engine/Private/Pawn.cpp +++ b/Engine/Source/Runtime/Engine/Private/Pawn.cpp @@ -42,6 +42,7 @@ APawn::APawn(const FObjectInitializer& ObjectInitializer) if (HasAnyFlags(RF_ClassDefaultObject) && GetClass() == APawn::StaticClass()) { + // WARNING: This line is why the AISupport plugin has to load the AIModule before UObject initialization, otherwise this load fails and CDOs are corrupt in the editor AIControllerClass = LoadClass(nullptr, *((UEngine*)(UEngine::StaticClass()->GetDefaultObject()))->AIControllerClassName.ToString(), nullptr, LOAD_None, nullptr); } else @@ -307,7 +308,8 @@ void APawn::SpawnDefaultController() { return; } - if ( AIControllerClass != nullptr ) + + if (AIControllerClass != nullptr) { FActorSpawnParameters SpawnInfo; SpawnInfo.Instigator = Instigator; diff --git a/Engine/Source/Runtime/Engine/Private/PerPlatformProperties.cpp b/Engine/Source/Runtime/Engine/Private/PerPlatformProperties.cpp index e80aa4d16bad..8ae52c9604f0 100644 --- a/Engine/Source/Runtime/Engine/Private/PerPlatformProperties.cpp +++ b/Engine/Source/Runtime/Engine/Private/PerPlatformProperties.cpp @@ -85,7 +85,7 @@ FString FPerPlatformInt::ToString() const #if WITH_EDITORONLY_DATA TArray SortedPlatforms; PerPlatform.GetKeys(/*out*/ SortedPlatforms); - SortedPlatforms.Sort(); + SortedPlatforms.Sort(FNameLexicalLess()); for (FName Platform : SortedPlatforms) { diff --git a/Engine/Source/Runtime/Engine/Private/PreviewScene.cpp b/Engine/Source/Runtime/Engine/Private/PreviewScene.cpp index 1fe1ef218379..d126cf492216 100644 --- a/Engine/Source/Runtime/Engine/Private/PreviewScene.cpp +++ b/Engine/Source/Runtime/Engine/Private/PreviewScene.cpp @@ -148,6 +148,11 @@ void FPreviewScene::AddReferencedObjects( FReferenceCollector& Collector ) Collector.AddReferencedObject( PreviewWorld ); } +FString FPreviewScene::GetReferencerName() const +{ + return TEXT("FPreviewScene"); +} + void FPreviewScene::UpdateCaptureContents() { USkyLightComponent::UpdateSkyCaptureContents(PreviewWorld); diff --git a/Engine/Source/Runtime/Engine/Private/RepLayout.cpp b/Engine/Source/Runtime/Engine/Private/RepLayout.cpp index 6b2d386e000d..e3dde0b183b5 100644 --- a/Engine/Source/Runtime/Engine/Private/RepLayout.cpp +++ b/Engine/Source/Runtime/Engine/Private/RepLayout.cpp @@ -20,7 +20,6 @@ #include "Algo/Sort.h" #include "Net/NetworkGranularMemoryLogging.h" #include "Serialization/ArchiveCountMem.h" -#include "Containers/SortedMap.h" #include "Templates/AndOrNot.h" DECLARE_CYCLE_STAT(TEXT("RepLayout AddPropertyCmd"), STAT_RepLayout_AddPropertyCmd, STATGROUP_Game); @@ -128,10 +127,10 @@ FConsoleVariableSinkHandle MaxRepArrayMemorySink = CreateMaxArrayMemoryCVarAndRe namespace UE4_RepLayout_Private { template - static typename TTranslateConstVolatile::Type* + static typename TCopyQualifiersFromTo::Type* GetTypedProperty(const BufferType& Buffer, const CommandType& Cmd) { - using ConstOrNotOutputType = typename TTranslateConstVolatile::Type; + using ConstOrNotOutputType = typename TCopyQualifiersFromTo::Type; using BaseBufferType = typename TRemovePointer::Type>::Type; @@ -143,16 +142,74 @@ namespace UE4_RepLayout_Private } template - static typename TTranslateConstVolatile::ConstOrNotVoid, OutputType>::Type* + static typename TCopyQualifiersFromTo::ConstOrNotVoid, OutputType>::Type* GetTypedProperty(const TRepDataBufferBase& Buffer, const CommandType& Cmd) { - using ConstOrNotOutputType = typename TTranslateConstVolatile::ConstOrNotVoid, OutputType>::Type; + using ConstOrNotOutputType = typename TCopyQualifiersFromTo::ConstOrNotVoid, OutputType>::Type; static_assert(!TIsPointer::Value, "GetTypedProperty invalid OutputType! Don't specify output as a pointer."); // TODO: Conditionally compilable runtime type validation. return reinterpret_cast((Buffer + Cmd).Data); } + + static void QueueRepNotifyForCustomDeltaProperty( + FReceivingRepState* RESTRICT ReceivingRepState, + FNetDeltaSerializeInfo& Params, + UProperty* Property, + uint32 StaticArrayIndex) + { + //@note: AddUniqueItem() here for static arrays since RepNotify() currently doesn't indicate index, + // so reporting the same property multiple times is not useful and wastes CPU + // were that changed, this should go back to AddItem() for efficiency + // @todo UE4 - not checking if replicated value is changed from old. Either fix or document, as may get multiple repnotifies of unacked properties. + ReceivingRepState->RepNotifies.AddUnique(Property); + + UFunction* RepNotifyFunc = Params.Object->FindFunctionChecked(Property->RepNotifyFunc); + + if (RepNotifyFunc->NumParms > 0) + { + if (Property->ArrayDim != 1) + { + // For static arrays, we build the meta data here, but adding the Element index that was just read into the PropMetaData array. + UE_LOG(LogRepTraffic, Verbose, TEXT("Property %s had ArrayDim: %d change"), *Property->GetName(), StaticArrayIndex); + + // Property is multi dimensional, keep track of what elements changed + TArray& PropMetaData = ReceivingRepState->RepNotifyMetaData.FindOrAdd(Property); + PropMetaData.Add(StaticArrayIndex); + } + } + } + + static void WritePropertyHeaderAndPayload( + UObject* Object, + UClass* ObjectClass, + UProperty* Property, + UNetConnection* Connection, + UActorChannel* OwningChannel, + FNetFieldExportGroup* NetFieldExportGroup, + FNetBitWriter& Bunch, + FNetBitWriter& Payload) + { + // Get class network info cache. + const FClassNetCache* ClassCache = Connection->Driver->NetCache->GetClassNetCache(ObjectClass); + + check(ClassCache); + + // Get the network friend property index to replicate + const FFieldNetCache * FieldCache = ClassCache->GetFromField(Property); + + checkSlow(FieldCache); + + // Send property name and optional array index. + check(FieldCache->FieldNetIndex <= ClassCache->GetMaxIndex()); + + // WriteFieldHeaderAndPayload will return the total number of bits written. + // So, we subtract out the Payload size to get the actual number of header bits. + const int32 HeaderBits = static_cast(OwningChannel->WriteFieldHeaderAndPayload(Bunch, ClassCache, FieldCache, NetFieldExportGroup, Payload)) - Payload.GetNumBits(); + + NETWORK_PROFILER(GNetworkProfiler.TrackWritePropertyHeader(Property, HeaderBits, nullptr)); + } } //~ TODO: Consider moving the FastArray members into their own sub-struct to save memory for non fast array @@ -161,32 +218,57 @@ namespace UE4_RepLayout_Private struct FLifetimeCustomDeltaProperty { + FLifetimeCustomDeltaProperty(const uint16 InPropertyRepIndex) + : PropertyRepIndex(InPropertyRepIndex) + { + } + + FLifetimeCustomDeltaProperty( + const uint16 InPropertyRepIndex, + const int32 InFastArrayItemsCommand, + const int32 InFastArrayNumber, + const int32 InFastArrayDeltaFlagsOffset, + const int32 InFastArrayArrayReplicationKeyOffset, + const int32 InFastArrayItemReplicationIdOffset + ) + : PropertyRepIndex(InPropertyRepIndex) + , FastArrayItemsCommand(InFastArrayItemsCommand) + , FastArrayNumber(InFastArrayNumber) + , FastArrayDeltaFlagsOffset(InFastArrayDeltaFlagsOffset) + , FastArrayArrayReplicationKeyOffset(InFastArrayArrayReplicationKeyOffset) + , FastArrayItemReplicationIdOffset(InFastArrayItemReplicationIdOffset) + { + } + + /** The RepIndex of the corresponding Property. This can be used as an index into FRepLayout::Parents. */ + const uint16 PropertyRepIndex = INDEX_NONE; + /** If this is a Fast Array Serializer property, this will be the command index for the Fast Array Item array. */ - int32 FastArrayItemsCommand = INDEX_NONE; + const int32 FastArrayItemsCommand = INDEX_NONE; /** * If this is a Fast Array Serializer property, this will be the instance number in the class. * This is used to lookup Changelists. */ - int32 FastArrayNumber = INDEX_NONE; + const int32 FastArrayNumber = INDEX_NONE; /** * If this is a Fast Array Serializer property (and it is set up correctly for Delta Serialization), this will be an * offset from to the property. */ - int32 FastArrayDeltaFlagsOffset = INDEX_NONE; + const int32 FastArrayDeltaFlagsOffset = INDEX_NONE; /** * If this is a Fast Array Serializer property (and it is set up correctly for Delta Serialization), this will be a pointer * to the FFastArraySerializer::ArrayReplicationKey property. */ - int32 FastArrayArrayReplicationKeyOffset = INDEX_NONE; + const int32 FastArrayArrayReplicationKeyOffset = INDEX_NONE; /** * If this is a Fast Array Serializer property (and it is set up correctly for Delta Serialization), this will be a pointer * to the FFastArraySerializerItem::ReplicationID property. */ - int32 FastArrayItemReplicationIdOffset = INDEX_NONE; + const int32 FastArrayItemReplicationIdOffset = INDEX_NONE; const EFastArraySerializerDeltaFlags GetFastArrayDeltaFlags(void const * const FastArray) const { @@ -230,24 +312,77 @@ private: */ struct FLifetimeCustomDeltaState { - TSortedMap LifetimeCustomDeltaProperties; - - /** The number of valid FFastArraySerializer properties we found. */ - int32 NumFastArrayItems = 0; +public: - /** - * This is less than ideal. - * For now, we want a fast way to let users query the list of lifetime custom delta property indices. - * This is mostly to support FObjectReplicator since we're going to remove its (per object per connection!!) cache. - * However, we wouldn't even need this if Custom Delta Properties were more completely handled by RepLayout. - */ - TArray LifetimeCustomDeltaPropertiesParentCache; + FLifetimeCustomDeltaState(uint16 TotalNumberOfLifetimeProperties) + { + LifetimeCustomDeltaIndexLookup.Init(static_cast(INDEX_NONE), TotalNumberOfLifetimeProperties); + } void CountBytes(FArchive& Ar) const { LifetimeCustomDeltaProperties.CountBytes(Ar); - LifetimeCustomDeltaPropertiesParentCache.CountBytes(Ar); + LifetimeCustomDeltaIndexLookup.CountBytes(Ar); } + + const uint16 GetNumCustomDeltaProperties() const + { + return LifetimeCustomDeltaProperties.Num(); + } + + const uint16 GetNumFastArrayProperties() const + { + return NumFastArrayProperties; + } + + const FLifetimeCustomDeltaProperty& GetCustomDeltaProperty(const uint16 CustomDeltaIndex) const + { + return LifetimeCustomDeltaProperties[CustomDeltaIndex]; + } + + const uint16 GetCustomDeltaIndexFromPropertyRepIndex(const uint16 PropertyRepIndex) const + { + const uint16 CustomDeltaIndex = LifetimeCustomDeltaIndexLookup[PropertyRepIndex]; + check(static_cast(INDEX_NONE) != CustomDeltaIndex); + return CustomDeltaIndex; + } + + void Add(FLifetimeCustomDeltaProperty&& ToAdd) + { + check(static_cast(INDEX_NONE) == LifetimeCustomDeltaIndexLookup[ToAdd.PropertyRepIndex]); + + if (ToAdd.FastArrayNumber != INDEX_NONE) + { + ++NumFastArrayProperties; + } + + LifetimeCustomDeltaIndexLookup[ToAdd.PropertyRepIndex] = LifetimeCustomDeltaProperties.Num(); + LifetimeCustomDeltaProperties.Emplace(MoveTemp(ToAdd)); + } + + void CompactMemory() + { + LifetimeCustomDeltaProperties.Shrink(); + LifetimeCustomDeltaIndexLookup.Shrink(); + } + +private: + + //~ Since there is only 1 RepLayout per class, and we will only create a FLifetimeCustomDeltaState for a RepLayout whose owning class + //~ has Custom Delta Properties, using 2 arrays here seems like a good trade off for performance, memory, and convenience as opposed + //~ to a TMap or TSortedMap. + //~ + //~ Having just a map alone makes it harder for external code to iterate over custom delta properties without exposing these internal + //~ classes. + //~ + //~ However, maintaining just an array of CustomDeltaProperties makes it less efficient to perform lookups (we either need to keep + //~ the list sorted or do linear searches). + + TArray LifetimeCustomDeltaProperties; + TArray LifetimeCustomDeltaIndexLookup; + + /** The number of valid FFastArraySerializer properties we found. */ + uint16 NumFastArrayProperties = 0; }; //~ Some of this complexity could go away if we introduced a new Compare step to Custom Delta Serializers @@ -1386,109 +1521,6 @@ void FRepLayout::UpdateChangelistHistory( check(RepState->NumNaks == 0); } -void FRepLayout::OpenAcked(FSendingRepState* RepState) const -{ - check(RepState); - RepState->bOpenAckedCalled = true; -} - -void FRepLayout::PostReplicate( - FSendingRepState* RepState, - FPacketIdRange& PacketRange, - bool bReliable) const -{ - if (LayoutState == ERepLayoutState::Normal) - { - for (int32 i = RepState->HistoryStart; i < RepState->HistoryEnd; ++i) - { - const int32 HistoryIndex = i % FSendingRepState::MAX_CHANGE_HISTORY; - - FRepChangedHistory & HistoryItem = RepState->ChangeHistory[HistoryIndex]; - - if (HistoryItem.OutPacketIdRange.First == INDEX_NONE) - { - check(HistoryItem.Changed.Num() > 0); - check(!HistoryItem.Resend); - - HistoryItem.OutPacketIdRange = PacketRange; - - if (!bReliable && !RepState->bOpenAckedCalled) - { - RepState->PreOpenAckHistory.Add(HistoryItem); - } - } - } - } -} - -void FRepLayout::ReceivedNak(FRepState* RepState, int32 NakPacketId) const -{ - if (RepState == nullptr) - { - return; // I'm not 100% certain why this happens, the only think I can think of is this is a bNetTemporary? - } - - if (LayoutState == ERepLayoutState::Normal) - { - if (FSendingRepState* SendingRepState = RepState->GetSendingRepState()) - { - for (int32 i = SendingRepState->HistoryStart; i < SendingRepState->HistoryEnd; ++i) - { - const int32 HistoryIndex = i % FSendingRepState::MAX_CHANGE_HISTORY; - - FRepChangedHistory & HistoryItem = SendingRepState->ChangeHistory[HistoryIndex]; - - if (!HistoryItem.Resend && HistoryItem.OutPacketIdRange.InRange(NakPacketId)) - { - check(HistoryItem.Changed.Num() > 0); - HistoryItem.Resend = true; - ++SendingRepState->NumNaks; - } - } - } - } -} - -bool FRepLayout::AllAcked(FRepState* RepState) const -{ - if (FSendingRepState* SendingRepState = RepState->GetSendingRepState()) - { - if (SendingRepState->HistoryStart != SendingRepState->HistoryEnd) - { - // We have change lists that haven't been acked - return false; - } - - if (SendingRepState->NumNaks > 0) - { - return false; - } - - if (!SendingRepState->bOpenAckedCalled) - { - return false; - } - - if (SendingRepState->PreOpenAckHistory.Num() > 0) - { - return false; - } - } - - return true; -} - -bool FRepLayout::ReadyForDormancy(FRepState* RepState) const -{ - // Clients should never go dormant. - if (RepState == nullptr || RepState->GetSendingRepState() == nullptr) - { - return false; - } - - return AllAcked(RepState); -} - void FRepLayout::SerializeObjectReplicatedProperties(UObject* Object, FBitArchive & Ar) const { static FRepSerializationSharedInfo Empty; @@ -3262,34 +3294,36 @@ void FRepLayout::GatherGuidReferences_r( void FRepLayout::GatherGuidReferences( FReceivingRepState* RESTRICT RepState, + FNetDeltaSerializeInfo& Params, TSet& OutReferencedGuids, int32& OutTrackedGuidMemoryBytes) const { if (LayoutState == ERepLayoutState::Normal) { GatherGuidReferences_r(&RepState->GuidReferencesMap, OutReferencedGuids, OutTrackedGuidMemoryBytes); - } -} -void FRepLayout::GatherGuidReferencesForCustomDeltaProperties(FNetDeltaSerializeInfo& Params) const -{ - if (LifetimeCustomPropertyState) - { - FRepObjectDataBuffer ObjectData(Params.Object); - for (const auto& KVP : LifetimeCustomPropertyState->LifetimeCustomDeltaProperties) + // Custom Delta Properties + if (LifetimeCustomPropertyState) { - const FRepParentCmd& Parent = Parents[KVP.Key]; + FRepObjectDataBuffer ObjectData(Params.Object); + const int32 NumLifetimeCustomDeltaProperties = LifetimeCustomPropertyState->GetNumCustomDeltaProperties(); - // Static cast is safe here, because this property wouldn't have been marked CustomDelta otherwise. - UStructProperty* StructProperty = static_cast(Parent.Property); - UScriptStruct::ICppStructOps* CppStructOps = StructProperty->Struct->GetCppStructOps(); + for (int32 CustomDeltaIndex = 0; CustomDeltaIndex < NumLifetimeCustomDeltaProperties; ++CustomDeltaIndex) + { + const FLifetimeCustomDeltaProperty& CustomDeltaProperty = LifetimeCustomPropertyState->GetCustomDeltaProperty(CustomDeltaIndex); + const FRepParentCmd& Parent = Parents[CustomDeltaProperty.PropertyRepIndex]; - FNetDeltaSerializeInfo TempParams = Params; - TempParams.Struct = StructProperty->Struct; - TempParams.PropertyRepIndex = KVP.Key; - TempParams.Data = ObjectData + Parent; + // Static cast is safe here, because this property wouldn't have been marked CustomDelta otherwise. + UStructProperty* StructProperty = static_cast(Parent.Property); + UScriptStruct::ICppStructOps* CppStructOps = StructProperty->Struct->GetCppStructOps(); - CppStructOps->NetDeltaSerialize(TempParams, TempParams.Data); + FNetDeltaSerializeInfo TempParams = Params; + TempParams.Struct = StructProperty->Struct; + TempParams.CustomDeltaIndex = CustomDeltaIndex; + TempParams.Data = ObjectData + Parent; + + CppStructOps->NetDeltaSerialize(TempParams, TempParams.Data); + } } } } @@ -3324,55 +3358,50 @@ bool FRepLayout::MoveMappedObjectToUnmapped_r(FGuidReferencesMap* GuidReferences return bFoundGUID; } -bool FRepLayout::MoveMappedObjectToUnmapped(FReceivingRepState* RESTRICT RepState, const FNetworkGUID& GUID) const -{ - return ERepLayoutState::Normal == LayoutState && MoveMappedObjectToUnmapped_r(&RepState->GuidReferencesMap, GUID); -} - -PRAGMA_DISABLE_DEPRECATION_WARNINGS -bool FRepLayout::MoveMappedObjectToUnmappedForCustomDeltaProperties(FNetDeltaSerializeInfo& Params) const -{ - TMap Dummy; - return MoveMappedObjectToUnmappedForCustomDeltaProperties(Params, Dummy); -} - -bool FRepLayout::MoveMappedObjectToUnmappedForCustomDeltaProperties(FNetDeltaSerializeInfo& Params, TMap& UnmappedCustomProperties) const +bool FRepLayout::MoveMappedObjectToUnmapped(FReceivingRepState* RESTRICT RepState, FNetDeltaSerializeInfo& Params, const FNetworkGUID& GUID) const { bool bFound = false; - if (LifetimeCustomPropertyState) + if (ERepLayoutState::Normal == LayoutState) { - FRepObjectDataBuffer ObjectData(Params.Object); - for (const auto& KVP : LifetimeCustomPropertyState->LifetimeCustomDeltaProperties) + bFound = MoveMappedObjectToUnmapped_r(&RepState->GuidReferencesMap, GUID); + + // Custom Delta Properties + if (LifetimeCustomPropertyState && Params.Object) { - const FRepParentCmd& Parent = Parents[KVP.Key]; + FRepObjectDataBuffer ObjectData(Params.Object); + const int32 NumLifetimeCustomDeltaProperties = LifetimeCustomPropertyState->GetNumCustomDeltaProperties(); - // Static cast is safe here, because this property wouldn't have been marked CustomDelta otherwise. - UStructProperty* StructProperty = static_cast(Parent.Property); - UScriptStruct::ICppStructOps* CppStructOps = StructProperty->Struct->GetCppStructOps(); - - FNetDeltaSerializeInfo TempParams = Params; - - TempParams.Struct = StructProperty->Struct; - TempParams.Data = ObjectData + Parent; - TempParams.PropertyRepIndex = KVP.Key; - TempParams.bOutHasMoreUnmapped = false; - TempParams.bOutSomeObjectsWereMapped = false; - - if (CppStructOps->NetDeltaSerialize(TempParams, TempParams.Data)) + for (int32 CustomDeltaIndex = 0; CustomDeltaIndex < NumLifetimeCustomDeltaProperties; ++CustomDeltaIndex) { - UnmappedCustomProperties.Add(Parent.Offset, StructProperty); - bFound = true; - } + const FLifetimeCustomDeltaProperty& CustomDeltaProperty = LifetimeCustomPropertyState->GetCustomDeltaProperty(CustomDeltaIndex); + const FRepParentCmd& Parent = Parents[CustomDeltaProperty.PropertyRepIndex]; - Params.bOutHasMoreUnmapped |= TempParams.bOutHasMoreUnmapped; - Params.bOutSomeObjectsWereMapped |= TempParams.bOutSomeObjectsWereMapped; + // Static cast is safe here, because this property wouldn't have been marked CustomDelta otherwise. + UStructProperty* StructProperty = static_cast(Parent.Property); + UScriptStruct::ICppStructOps* CppStructOps = StructProperty->Struct->GetCppStructOps(); + + FNetDeltaSerializeInfo TempParams = Params; + + TempParams.Struct = StructProperty->Struct; + TempParams.Data = ObjectData + Parent; + TempParams.CustomDeltaIndex = CustomDeltaIndex; + TempParams.bOutHasMoreUnmapped = false; + TempParams.bOutSomeObjectsWereMapped = false; + + if (CppStructOps->NetDeltaSerialize(TempParams, TempParams.Data)) + { + bFound = true; + } + + Params.bOutHasMoreUnmapped |= TempParams.bOutHasMoreUnmapped; + Params.bOutSomeObjectsWereMapped |= TempParams.bOutSomeObjectsWereMapped; + } } } return bFound; } -PRAGMA_ENABLE_DEPRECATION_WARNINGS void FRepLayout::UpdateUnmappedObjects_r( FReceivingRepState* RESTRICT RepState, @@ -3522,6 +3551,7 @@ void FRepLayout::UpdateUnmappedObjects( FReceivingRepState* RESTRICT RepState, UPackageMap* PackageMap, UObject* OriginalObject, + FNetDeltaSerializeInfo& Params, bool& bCalledPreNetReceive, bool& bOutSomeObjectsWereMapped, bool& bOutHasMoreUnmapped) const @@ -3543,71 +3573,54 @@ void FRepLayout::UpdateUnmappedObjects( bCalledPreNetReceive, bOutSomeObjectsWereMapped, bOutHasMoreUnmapped); - } -} -PRAGMA_DISABLE_DEPRECATION_WARNINGS -void FRepLayout::UpdateUnmappedObjectsForCustomDeltaProperties(FNetDeltaSerializeInfo& Params) const -{ - TArray> Dummy; - UpdateUnmappedObjectsForCustomDeltaProperties(Params, Dummy, Dummy); -} -PRAGMA_ENABLE_DEPRECATION_WARNINGS + Params.bCalledPreNetReceive = bCalledPreNetReceive; -void FRepLayout::UpdateUnmappedObjectsForCustomDeltaProperties(FNetDeltaSerializeInfo& Params, TArray>& CompletelyUpdated, TArray>& Updated) const -{ - if (LifetimeCustomPropertyState) - { - FRepObjectDataBuffer ObjectData(Params.Object); - for (const auto& KVP : LifetimeCustomPropertyState->LifetimeCustomDeltaProperties) + // Custom Delta Properties + if (LifetimeCustomPropertyState) { - const FRepParentCmd& Parent = Parents[KVP.Key]; + FRepObjectDataBuffer ObjectData(Params.Object); + const int32 NumLifetimeCustomDeltaProperties = LifetimeCustomPropertyState->GetNumCustomDeltaProperties(); - // Static cast is safe here, because this property wouldn't have been marked CustomDelta otherwise. - UStructProperty* StructProperty = static_cast(Parent.Property); - UScriptStruct::ICppStructOps* CppStructOps = StructProperty->Struct->GetCppStructOps(); - - FNetDeltaSerializeInfo TempParams = Params; - - TempParams.DebugName = Parent.CachedPropertyName.ToString(); - TempParams.Struct = StructProperty->Struct; - TempParams.bOutSomeObjectsWereMapped = false; - TempParams.bOutHasMoreUnmapped = false; - TempParams.PropertyRepIndex = KVP.Key; - TempParams.Data = ObjectData + Parent; - - // Call the custom delta serialize function to handle it - CppStructOps->NetDeltaSerialize(TempParams, TempParams.Data); - - if (TempParams.bOutSomeObjectsWereMapped) + for (int32 CustomDeltaIndex = 0; CustomDeltaIndex < NumLifetimeCustomDeltaProperties; ++CustomDeltaIndex) { - Updated.Emplace(Parent.Offset, StructProperty); - } + const FLifetimeCustomDeltaProperty& CustomDeltaProperty = LifetimeCustomPropertyState->GetCustomDeltaProperty(CustomDeltaIndex); + const FRepParentCmd& Parent = Parents[CustomDeltaProperty.PropertyRepIndex]; - if (!TempParams.bOutHasMoreUnmapped) - { - CompletelyUpdated.Emplace(Parent.Offset, StructProperty); - } + // Static cast is safe here, because this property wouldn't have been marked CustomDelta otherwise. + UStructProperty* StructProperty = static_cast(Parent.Property); + UScriptStruct::ICppStructOps* CppStructOps = StructProperty->Struct->GetCppStructOps(); - Params.bOutSomeObjectsWereMapped |= TempParams.bOutSomeObjectsWereMapped; - Params.bOutHasMoreUnmapped |= TempParams.bOutHasMoreUnmapped; - Params.bCalledPreNetReceive |= TempParams.bCalledPreNetReceive; + FNetDeltaSerializeInfo TempParams = Params; + + TempParams.DebugName = Parent.CachedPropertyName.ToString(); + TempParams.Struct = StructProperty->Struct; + TempParams.bOutSomeObjectsWereMapped = false; + TempParams.bOutHasMoreUnmapped = false; + TempParams.CustomDeltaIndex = CustomDeltaIndex; + TempParams.Data = ObjectData + Parent; + + // Call the custom delta serialize function to handle it + CppStructOps->NetDeltaSerialize(TempParams, TempParams.Data); + + if (TempParams.bOutSomeObjectsWereMapped && INDEX_NONE != Parent.RepNotifyNumParams) + { + UE4_RepLayout_Private::QueueRepNotifyForCustomDeltaProperty(RepState, Params, StructProperty, Parent.ArrayIndex); + } + + Params.bOutSomeObjectsWereMapped |= TempParams.bOutSomeObjectsWereMapped; + Params.bOutHasMoreUnmapped |= TempParams.bOutHasMoreUnmapped; + Params.bCalledPreNetReceive |= TempParams.bCalledPreNetReceive; + } } } } -bool FRepLayout::SendCustomDeltaProperty( - FNetDeltaSerializeInfo& Params, - UProperty* Property, - int32 StaticArrayIndex) const +bool FRepLayout::SendCustomDeltaProperty(FNetDeltaSerializeInfo& Params, uint16 CustomDeltaIndex) const { - check(Property->ArrayDim > StaticArrayIndex); - return SendCustomDeltaProperty(Params, Property->RepIndex + StaticArrayIndex); -} + const FLifetimeCustomDeltaProperty& CustomDeltaProperty = LifetimeCustomPropertyState->GetCustomDeltaProperty(CustomDeltaIndex); + const FRepParentCmd& Parent = Parents[CustomDeltaProperty.PropertyRepIndex]; -bool FRepLayout::SendCustomDeltaProperty(FNetDeltaSerializeInfo& Params, uint16 CustomDeltaRepIndex) const -{ - const FRepParentCmd& Parent = Parents[CustomDeltaRepIndex]; if (!ensure(EnumHasAnyFlags(Parent.Flags, ERepParentFlags::IsCustomDelta))) { return false; @@ -3620,16 +3633,15 @@ bool FRepLayout::SendCustomDeltaProperty(FNetDeltaSerializeInfo& Params, uint16 Params.DebugName = Parent.CachedPropertyName.ToString(); Params.Struct = StructProperty->Struct; - Params.PropertyRepIndex = CustomDeltaRepIndex; + Params.CustomDeltaIndex = CustomDeltaIndex; Params.Data = FRepObjectDataBuffer(Params.Object) + Parent; bool bSupportsFastArrayDelta = Params.bSupportsFastArrayDeltaStructSerialization; if (Params.bSupportsFastArrayDeltaStructSerialization && EnumHasAnyFlags(Parent.Flags, ERepParentFlags::IsFastArray) && - !!LifetimeCustomPropertyState->NumFastArrayItems) + !!LifetimeCustomPropertyState->GetNumFastArrayProperties()) { - const FLifetimeCustomDeltaProperty& CustomDeltaProperty = LifetimeCustomPropertyState->LifetimeCustomDeltaProperties.FindChecked(CustomDeltaRepIndex); bSupportsFastArrayDelta = CustomDeltaProperty.FastArrayNumber != INDEX_NONE; } @@ -3645,16 +3657,10 @@ bool FRepLayout::SendCustomDeltaProperty(FNetDeltaSerializeInfo& Params, uint16 return CppStructOps->NetDeltaSerialize(Params, Params.Data); } -PRAGMA_DISABLE_DEPRECATION_WARNINGS -bool FRepLayout::ReceiveCustomDeltaProperty(FNetDeltaSerializeInfo& Params, UStructProperty* Property) const -{ - uint32 StaticArrayIndex = 0; - int32 Offset = 0; - return ReceiveCustomDeltaProperty(Params, Property, StaticArrayIndex, Offset); -} -PRAGMA_ENABLE_DEPRECATION_WARNINGS - -bool FRepLayout::ReceiveCustomDeltaProperty(FNetDeltaSerializeInfo& Params, UStructProperty* Property, uint32& StaticArrayIndex, int32& Offset) const +bool FRepLayout::ReceiveCustomDeltaProperty( + FReceivingRepState* RESTRICT ReceivingRepState, + FNetDeltaSerializeInfo& Params, + UStructProperty* Property) const { if (Params.Connection->EngineNetworkProtocolVersion >= EEngineNetworkVersionHistory::HISTORY_FAST_ARRAY_DELTA_STRUCT) { @@ -3665,6 +3671,8 @@ bool FRepLayout::ReceiveCustomDeltaProperty(FNetDeltaSerializeInfo& Params, UStr Params.bSupportsFastArrayDeltaStructSerialization = false; } + uint32 StaticArrayIndex = 0; + // Receive array index (static sized array, i.e. MemberVariable[4]) if (Property->ArrayDim != 1) { @@ -3688,7 +3696,6 @@ bool FRepLayout::ReceiveCustomDeltaProperty(FNetDeltaSerializeInfo& Params, UStr return false; } - Offset = Parent.Offset; UScriptStruct* InnerStruct = Property->Struct; UScriptStruct::ICppStructOps* CppStructOps = InnerStruct->GetCppStructOps(); @@ -3697,10 +3704,32 @@ bool FRepLayout::ReceiveCustomDeltaProperty(FNetDeltaSerializeInfo& Params, UStr Params.DebugName = Parent.CachedPropertyName.ToString(); Params.Struct = InnerStruct; - Params.PropertyRepIndex = Property->RepIndex + StaticArrayIndex; + Params.CustomDeltaIndex = LifetimeCustomPropertyState->GetCustomDeltaIndexFromPropertyRepIndex(Property->RepIndex + StaticArrayIndex); Params.Data = FRepObjectDataBuffer(Params.Object) + Parent; - return CppStructOps->NetDeltaSerialize(Params, Params.Data); + if (CppStructOps->NetDeltaSerialize(Params, Params.Data)) + { + if (UNLIKELY(Params.Reader->IsError())) + { + UE_LOG(LogNet, Error, TEXT("FRepLayout::ReceiveCustomDeltaProperty: NetDeltaSerialize - Reader.IsError() == true. Property: %s, Object: %s"), *Params.DebugName, *Params.Object->GetFullName()); + return false; + } + if (UNLIKELY(Params.Reader->GetBitsLeft() != 0)) + { + UE_LOG(LogNet, Error, TEXT("FRepLayout::ReceiveCustomDeltaProperty: NetDeltaSerialize - Mismatch read. Property: %s, Object: %s"), *Params.DebugName, *Params.Object->GetFullName()); + return false; + } + + // Successfully received it. + if (INDEX_NONE != Parent.RepNotifyNumParams) + { + UE4_RepLayout_Private::QueueRepNotifyForCustomDeltaProperty(ReceivingRepState, Params, Property, StaticArrayIndex); + } + + return true; + } + + return false; } void FRepLayout::CallRepNotifies(FReceivingRepState* RepState, UObject* Object) const @@ -3723,6 +3752,13 @@ void FRepLayout::CallRepNotifies(FReceivingRepState* RepState, UObject* Object) for (UProperty* RepProperty : RepState->RepNotifies) { + if (!Parents.IsValidIndex(RepProperty->RepIndex)) + { + UE_LOG(LogRep, Warning, TEXT("FRepLayout::CallRepNotifies: Called with invalid property %s on object %s."), + *RepProperty->GetName(), *Object->GetName()); + continue; + } + UFunction* RepNotifyFunc = Object->FindFunction(RepProperty->RepNotifyFunc); if (RepNotifyFunc == nullptr) @@ -3732,46 +3768,90 @@ void FRepLayout::CallRepNotifies(FReceivingRepState* RepState, UObject* Object) continue; } - check(RepNotifyFunc->NumParms <= 1); // 2 parms not supported yet + const FRepParentCmd& Parent = Parents[RepProperty->RepIndex]; + const int32 NumParms = RepNotifyFunc->NumParms; - if (RepNotifyFunc->NumParms == 0) + switch (NumParms) { - Object->ProcessEvent(RepNotifyFunc, nullptr); - } - else if (RepNotifyFunc->NumParms == 1) - { - // TODO_JDN: If this turns out to be too slow to be practical, - // we should consider a TSortedMap of *just* these RepNotify properties, or even just - // an Array of indices of RepNotify properties. - const FRepParentCmd* Parent = Parents.FindByPredicate([RepProperty](const FRepParentCmd& InParent) + case 0: { - return InParent.Property == RepProperty; - }); - - check(Parent); - - FRepShadowDataBuffer PropertyData = ShadowData + (*Parent); - - // This could be cached off as a Parent flag, to avoid touching the Commands array. - if (ERepLayoutCmdType::PropertyBool == Cmds[Parent->CmdStart].Type) - { - bool BoolPropertyValue = !!static_cast(Parent->Property)->GetPropertyValue(PropertyData); - Object->ProcessEvent(RepNotifyFunc, &BoolPropertyValue); + Object->ProcessEvent(RepNotifyFunc, nullptr); + break; } - else + case 1: { - Object->ProcessEvent(RepNotifyFunc, PropertyData); + FRepShadowDataBuffer PropertyData = ShadowData + Parent; + + if (EnumHasAnyFlags(Parent.Flags, ERepParentFlags::IsCustomDelta)) + { + Object->ProcessEvent(RepNotifyFunc, PropertyData); + } + else + { + // This could be cached off as a Parent flag, to avoid touching the Commands array. + if (ERepLayoutCmdType::PropertyBool == Cmds[Parent.CmdStart].Type) + { + bool BoolPropertyValue = !!static_cast(Parent.Property)->GetPropertyValue(PropertyData); + Object->ProcessEvent(RepNotifyFunc, &BoolPropertyValue); + } + else + { + Object->ProcessEvent(RepNotifyFunc, PropertyData); + } + + // now store the complete value in the shadow buffer + if (!EnumHasAnyFlags(Parent.Flags, ERepParentFlags::IsNetSerialize)) + { + RepProperty->CopyCompleteValue(ShadowData + Parent, ObjectData + Parent); + } + } + break; } - - // now store the complete value in the shadow buffer - if (!EnumHasAnyFlags(Parent->Flags, ERepParentFlags::IsNetSerialize | ERepParentFlags::IsCustomDelta)) + case 2: { - RepProperty->CopyCompleteValue(ShadowData + (*Parent), ObjectData + (*Parent)); + check(EnumHasAnyFlags(Parent.Flags, ERepParentFlags::IsCustomDelta)); + + // Fixme: this isn't as safe as it could be. Right now we have two types of parameters: MetaData (a TArray) + // and the last local value (pointer into the Recent[] array). + // + // Arrays always expect MetaData. Everything else, including structs, expect last value. + // This is enforced with UHT only. If a ::NetSerialize function ever starts producing a MetaData array thats not in UArrayProperty, + // we have no static way of catching this and the replication system could pass the wrong thing into ProcessEvent here. + // + // But this is all sort of an edge case feature anyways, so its not worth tearing things up too much over. + + FMemMark Mark(FMemStack::Get()); + uint8* Parms = new(FMemStack::Get(), MEM_Zeroed, RepNotifyFunc->ParmsSize)uint8; + + TFieldIterator Itr(RepNotifyFunc); + check(Itr); + + FRepShadowDataBuffer PropertyData = ShadowData + Parent; + + Itr->CopyCompleteValue(Itr->ContainerPtrToValuePtr(Parms), PropertyData); + ++Itr; + check(Itr); + + TArray *NotifyMetaData = RepState->RepNotifyMetaData.Find(RepProperty); + check(NotifyMetaData); + Itr->CopyCompleteValue(Itr->ContainerPtrToValuePtr(Parms), NotifyMetaData); + + Object->ProcessEvent(RepNotifyFunc, Parms); + + Mark.Pop(); + break; + } + default: + { + checkf(false, TEXT("FRepLayout::CallRepNotifies: Invalid number of parameters for property %s on object %s. NumParms=%d, CustomDelta=%d"), + *RepProperty->GetName(), *Object->GetName(), NumParms, !!EnumHasAnyFlags(Parent.Flags, ERepParentFlags::IsCustomDelta)); + break; } } } RepState->RepNotifies.Empty(); + RepState->RepNotifyMetaData.Empty(); } template @@ -4965,12 +5045,9 @@ void FRepLayout::InitFromClass(UClass* InObjectClass, const UNetConnection* Serv if (!LifetimeCustomPropertyState) { - LifetimeCustomPropertyState.Reset(new FLifetimeCustomDeltaState()); + LifetimeCustomPropertyState.Reset(new FLifetimeCustomDeltaState(LifetimeProps.Num())); } - const int32 CustomIndex = LifetimeCustomPropertyState->LifetimeCustomDeltaPropertiesParentCache.Add(ParentIndex); - FLifetimeCustomDeltaProperty& CustomDeltaProperty = LifetimeCustomPropertyState->LifetimeCustomDeltaProperties.Add(ParentIndex); - // If we're a FastArraySerializer, we'll look for our replicated item type. // We do this by looking for an array property whose inner type is an FFastArraySerializerItem. // Note, this isn't perfect. With the way the interface is set up now, there's no technically @@ -4981,6 +5058,8 @@ void FRepLayout::InitFromClass(UClass* InObjectClass, const UNetConnection* Serv // However, comments imply these, and typically they are true (certainly, any engine cases follow this). // Further, these layouts are only needed for the new Delta Struct Serialization feature, so this won't break backwards compat. + bool bAddedFastArray = false; + if (EnumHasAnyFlags(Parents[ParentIndex].Flags, ERepParentFlags::IsFastArray)) { int32 FastArrayItemArrayCmd = INDEX_NONE; @@ -5003,13 +5082,16 @@ void FRepLayout::InitFromClass(UClass* InObjectClass, const UNetConnection* Serv // This better be a script struct, otherwise our flags aren't set up correctly! UScriptStruct* FastArray = CastChecked(MaybeFastArrayItemsArray->GetOwnerStruct()); - CustomDeltaProperty.FastArrayItemsCommand = CmdIndex; - CustomDeltaProperty.FastArrayItemReplicationIdOffset = MaybeFastArrayItem->FindPropertyByName(FastArrayItemReplicationIDName)->GetOffset_ForGC(); - CustomDeltaProperty.FastArrayArrayReplicationKeyOffset = FastArray->FindPropertyByName(FastArrayArrayReplicationKeyName)->GetOffset_ForGC(); - CustomDeltaProperty.FastArrayDeltaFlagsOffset = FastArray->FindPropertyByName(FastArrayDeltaFlagsName)->GetOffset_ForGC(); + LifetimeCustomPropertyState->Add(FLifetimeCustomDeltaProperty( + /*PropertyRepIndex=*/ParentIndex, + /*FastArrayItemsCommand=*/CmdIndex, + /*FastArrayNumber=*/LifetimeCustomPropertyState->GetNumFastArrayProperties(), + /*FastArrayDeltaFlagsOffset=*/FastArray->FindPropertyByName(FastArrayDeltaFlagsName)->GetOffset_ForGC(), + /*FastArrayReplicationKeyOffset=*/FastArray->FindPropertyByName(FastArrayArrayReplicationKeyName)->GetOffset_ForGC(), + /*FastArrayItemReplicationIdOffset=*/MaybeFastArrayItem->FindPropertyByName(FastArrayItemReplicationIDName)->GetOffset_ForGC() + )); - CustomDeltaProperty.FastArrayNumber = LifetimeCustomPropertyState->NumFastArrayItems; - ++LifetimeCustomPropertyState->NumFastArrayItems; + bAddedFastArray = true; break; } } @@ -5018,12 +5100,17 @@ void FRepLayout::InitFromClass(UClass* InObjectClass, const UNetConnection* Serv } } - if (CustomDeltaProperty.FastArrayItemsCommand == INDEX_NONE) - { + if (!bAddedFastArray) + { UE_LOG(LogRep, Warning, TEXT("FRepLayout::InitFromClass: Unable to find Fast Array Item array in Fast Array Serializer: %s"), *Parents[ParentIndex].CachedPropertyName.ToString()); } } + if (!bAddedFastArray) + { + LifetimeCustomPropertyState->Add(FLifetimeCustomDeltaProperty(ParentIndex)); + } + continue; } @@ -5049,19 +5136,6 @@ void FRepLayout::InitFromClass(UClass* InObjectClass, const UNetConnection* Serv BuildHandleToCmdIndexTable_r(0, Cmds.Num() - 1, BaseHandleToCmdIndex); } - if (LifetimeCustomPropertyState && !!LifetimeCustomPropertyState->NumFastArrayItems) - { - int32 Counter = 0; - for (auto& KVP : LifetimeCustomPropertyState->LifetimeCustomDeltaProperties) - { - if (EnumHasAnyFlags(Parents[KVP.Key].Flags, ERepParentFlags::IsFastArray) && - INDEX_NONE != KVP.Value.FastArrayItemsCommand) - { - KVP.Value.FastArrayNumber = Counter++; - } - } - } - BuildShadowOffsets(InObjectClass, Parents, Cmds, ShadowDataBufferSize, LayoutState); Owner = InObjectClass; @@ -5240,7 +5314,7 @@ void FRepLayout::SerializeProperties_r( if ((GNetSharedSerializedData != 0) && Ar.IsSaving() && ((Cmd.Flags & ERepLayoutFlags::IsSharedSerialization) != ERepLayoutFlags::None)) { - FGuid PropertyGuid(CmdIndex, ArrayIndex, ArrayDepth, (int32)((PTRINT)(Data + Cmd) & 0xFFFFFFFF)); + FGuid PropertyGuid(CmdIndex, ArrayIndex, ArrayDepth, (int32)((PTRINT)(const uint8*)(Data + Cmd) & 0xFFFFFFFF)); SharedPropInfo = SharedInfo.SharedPropertyInfo.FindByPredicate([&](const FRepSerializedPropertyInfo& Info) { @@ -5481,7 +5555,7 @@ void FRepLayout::BuildSharedSerializationForRPC_r( if (!Parents[Cmd.ParentIndex].Property->HasAnyPropertyFlags(CPF_OutParm) && ((Cmd.Flags & ERepLayoutFlags::IsSharedSerialization) != ERepLayoutFlags::None)) { - FGuid PropertyGuid(CmdIndex, ArrayIndex, ArrayDepth, (int32)((PTRINT)(Data + Cmd) & 0xFFFFFFFF)); + FGuid PropertyGuid(CmdIndex, ArrayIndex, ArrayDepth, (int32)((PTRINT)(const uint8*)(Data + Cmd) & 0xFFFFFFFF)); SharedInfo.WriteSharedProperty(Cmd, PropertyGuid, CmdIndex, 0, (Data + Cmd).Data, false, false); } @@ -5802,9 +5876,9 @@ TSharedPtr FRepLayout::CreateReplicationChangelistMgr // so no need to worry about deleting it here. FCustomDeltaChangelistState* DeltaChangelistState = nullptr; - if (LifetimeCustomPropertyState && !!LifetimeCustomPropertyState->NumFastArrayItems) + if (LifetimeCustomPropertyState && !!LifetimeCustomPropertyState->GetNumFastArrayProperties()) { - DeltaChangelistState = new FCustomDeltaChangelistState(LifetimeCustomPropertyState->NumFastArrayItems); + DeltaChangelistState = new FCustomDeltaChangelistState(LifetimeCustomPropertyState->GetNumFastArrayProperties()); } const uint8* ShadowStateSource = (const uint8*)InObject->GetArchetype(); @@ -5917,37 +5991,6 @@ void FRepLayout::DestructProperties(FRepStateStaticBuffer& InShadowData) const InShadowData.Buffer.Empty(); } -PRAGMA_DISABLE_DEPRECATION_WARNINGS - -void FRepLayout::GetLifetimeCustomDeltaProperties(TArray& OutCustom, TArray& OutConditions) const -{ - OutCustom.Empty(); - OutConditions.Empty(); - - if (LifetimeCustomPropertyState) - { - for (const auto& KVP : LifetimeCustomPropertyState->LifetimeCustomDeltaProperties) - { - OutCustom.Add(KVP.Key); - OutConditions.Add(Parents[KVP.Key].Condition); - } - } -} - -PRAGMA_ENABLE_DEPRECATION_WARNINGS - -const TArrayView FRepLayout::GetLifetimeCustomDeltaProperties() const -{ - if (LifetimeCustomPropertyState) - { - return LifetimeCustomPropertyState->LifetimeCustomDeltaPropertiesParentCache; - } - else - { - return TArrayView(); - } -} - void FRepLayout::AddReferencedObjects(FReferenceCollector& Collector) { UProperty* Current = nullptr; @@ -5970,6 +6013,11 @@ void FRepLayout::AddReferencedObjects(FReferenceCollector& Collector) } } +FString FRepLayout::GetReferencerName() const +{ + return TEXT("FRepLayout"); +} + // TODO: There's a better way to do this, but it requires more changes. // Ideally, we bring Retirements management, etc., into RepLayout. // What we could do instead of using standard changelists is use individual circular buffers @@ -6023,7 +6071,7 @@ void FRepLayout::PreSendCustomDeltaProperties( { const FLifetimeCustomDeltaState& LocalLifetimeCustomPropertyState = *LifetimeCustomPropertyState; - if (LocalLifetimeCustomPropertyState.NumFastArrayItems) + if (LocalLifetimeCustomPropertyState.GetNumFastArrayProperties()) { FRepChangelistState& ChangelistState = *ChangelistMgr.GetRepChangelistState(); FCustomDeltaChangelistState& CustomDeltaChangelistState = *ChangelistState.CustomDeltaChangelistState; @@ -6036,11 +6084,12 @@ void FRepLayout::PreSendCustomDeltaProperties( CustomDeltaChangelistState.CompareIndex = GFrameCounter; const FConstRepObjectDataBuffer ObjectData(Object); + const uint16 NumLifetimeCustomDeltaProperties = LocalLifetimeCustomPropertyState.GetNumCustomDeltaProperties(); - for (const auto& KVP : LocalLifetimeCustomPropertyState.LifetimeCustomDeltaProperties) + for (uint16 CustomDeltaIndex = 0; CustomDeltaIndex < NumLifetimeCustomDeltaProperties; ++CustomDeltaIndex) { - const uint16 RepIndex = KVP.Key; - const FLifetimeCustomDeltaProperty& CustomDeltaProperty = KVP.Value; + const FLifetimeCustomDeltaProperty& CustomDeltaProperty = LocalLifetimeCustomPropertyState.GetCustomDeltaProperty(CustomDeltaIndex); + const uint16 RepIndex = CustomDeltaProperty.PropertyRepIndex; // If our Fast Array Items Command is invalid, we can't do anything. // This should have been logged on RepLayout creation. @@ -6121,8 +6170,10 @@ bool FRepLayout::DeltaSerializeFastArrayProperty(FFastArrayDeltaSerializeParams& check(LifetimeCustomPropertyState); FNetDeltaSerializeInfo& DeltaSerializeInfo = Params.DeltaSerializeInfo; - const int32 ParentIndex = DeltaSerializeInfo.PropertyRepIndex; - const FLifetimeCustomDeltaProperty& CustomDeltaProperty = LifetimeCustomPropertyState->LifetimeCustomDeltaProperties.FindChecked(ParentIndex); + + const FLifetimeCustomDeltaProperty& CustomDeltaProperty = LifetimeCustomPropertyState->GetCustomDeltaProperty(DeltaSerializeInfo.CustomDeltaIndex); + const uint16 ParentIndex = CustomDeltaProperty.PropertyRepIndex; + const FRepParentCmd& Parent = Parents[ParentIndex]; const int32 CmdIndex = CustomDeltaProperty.FastArrayItemsCommand; @@ -6711,7 +6762,8 @@ void FRepLayout::GatherGuidReferencesForFastArray(FFastArrayDeltaSerializeParams using namespace UE4_RepLayout_Private; const FConstRepObjectDataBuffer ObjectData(Params.DeltaSerializeInfo.Object); - const FRepParentCmd& Parent = Parents[Params.DeltaSerializeInfo.PropertyRepIndex]; + const FLifetimeCustomDeltaProperty& CustomDeltaProperty = LifetimeCustomPropertyState->GetCustomDeltaProperty(Params.DeltaSerializeInfo.CustomDeltaIndex); + const FRepParentCmd& Parent = Parents[CustomDeltaProperty.PropertyRepIndex]; const FFastArraySerializer& ArraySerializer = Params.ArraySerializer; TSet& GatherGuids = *Params.DeltaSerializeInfo.GatherGuidReferences; @@ -6733,7 +6785,8 @@ bool FRepLayout::MoveMappedObjectToUnmappedForFastArray(FFastArrayDeltaSerialize using namespace UE4_RepLayout_Private; const FRepObjectDataBuffer ObjectData(Params.DeltaSerializeInfo.Object); - const FRepParentCmd& Parent = Parents[Params.DeltaSerializeInfo.PropertyRepIndex]; + const FLifetimeCustomDeltaProperty& CustomDeltaProperty = LifetimeCustomPropertyState->GetCustomDeltaProperty(Params.DeltaSerializeInfo.CustomDeltaIndex); + const FRepParentCmd& Parent = Parents[CustomDeltaProperty.PropertyRepIndex]; FFastArraySerializer& ArraySerializer = Params.ArraySerializer; const FNetworkGUID& MoveToUnmapped = *Params.DeltaSerializeInfo.MoveGuidToUnmapped; @@ -6753,8 +6806,9 @@ void FRepLayout::UpdateUnmappedGuidsForFastArray(FFastArrayDeltaSerializeParams& check(LifetimeCustomPropertyState); FNetDeltaSerializeInfo& DeltaSerializeInfo = Params.DeltaSerializeInfo; - const int32 ParentIndex = DeltaSerializeInfo.PropertyRepIndex; - const FLifetimeCustomDeltaProperty& CustomDeltaProperty = LifetimeCustomPropertyState->LifetimeCustomDeltaProperties.FindChecked(ParentIndex); + + const FLifetimeCustomDeltaProperty& CustomDeltaProperty = LifetimeCustomPropertyState->GetCustomDeltaProperty(DeltaSerializeInfo.CustomDeltaIndex); + const int32 ParentIndex = CustomDeltaProperty.PropertyRepIndex; const FRepParentCmd& Parent = Parents[ParentIndex]; const int32 CmdIndex = CustomDeltaProperty.FastArrayItemsCommand; @@ -6824,6 +6878,23 @@ void FRepLayout::CountBytes(FArchive& Ar) const ); } +const uint16 FRepLayout::GetNumLifetimeCustomDeltaProperties() const +{ + return LifetimeCustomPropertyState.IsValid() ? LifetimeCustomPropertyState->GetNumCustomDeltaProperties() : 0; +} + +UProperty* FRepLayout::GetLifetimeCustomDeltaProperty(const uint16 CustomDeltaPropertyIndex) const +{ + const FLifetimeCustomDeltaProperty& CustomDeltaProperty = LifetimeCustomPropertyState->GetCustomDeltaProperty(CustomDeltaPropertyIndex); + return Parents[CustomDeltaProperty.PropertyRepIndex].Property; +} + +const ELifetimeCondition FRepLayout::GetLifetimeCustomDeltaPropertyCondition(const uint16 CustomDeltaPropertyIndex) const +{ + const FLifetimeCustomDeltaProperty& CustomDeltaProperty = LifetimeCustomPropertyState->GetCustomDeltaProperty(CustomDeltaPropertyIndex); + return Parents[CustomDeltaProperty.PropertyRepIndex].Condition; +} + void FReceivingRepState::CountBytes(FArchive& Ar) const { GRANULAR_NETWORK_MEMORY_TRACKING_INIT(Ar, "FReceivingRepState::CountBytes"); @@ -6837,8 +6908,16 @@ void FReceivingRepState::CountBytes(FArchive& Ar) const GuidRefPair.Value.CountBytes(Ar); } ); - + GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("RepNotifies", RepNotifies.CountBytes(Ar)); + + GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("RepNotifyMetaData", + RepNotifyMetaData.CountBytes(Ar); + for (const auto& MetaDataPair : RepNotifyMetaData) + { + MetaDataPair.Value.CountBytes(Ar); + } + ); } void FSendingRepState::CountBytes(FArchive& Ar) const @@ -6864,6 +6943,30 @@ void FSendingRepState::CountBytes(FArchive& Ar) const GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("LifetimeChangelist", LifetimeChangelist.CountBytes(Ar)); GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("InactiveChangelist", InactiveChangelist.CountBytes(Ar)); GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("InactiveParents", InactiveParents.CountBytes(Ar)); + + GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("Retirement", Retirement.CountBytes(Ar)); + + GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("RecentCustomDeltaState", + RecentCustomDeltaState.CountBytes(Ar); + for (const auto& RecentCustomDeltaStatePair : RecentCustomDeltaState) + { + if (INetDeltaBaseState const * const BaseState = RecentCustomDeltaStatePair.Value.Get()) + { + BaseState->CountBytes(Ar); + } + } + ); + + GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("CDOCustomDeltaState", + CDOCustomDeltaState.CountBytes(Ar); + for (const auto& CDOCustomDeltaStatePair : CDOCustomDeltaState) + { + if (INetDeltaBaseState const * const BaseState = CDOCustomDeltaStatePair.Value.Get()) + { + BaseState->CountBytes(Ar); + } + } + ); } void FRepState::CountBytes(FArchive& Ar) const diff --git a/Engine/Source/Runtime/Engine/Private/SampleBuffer.cpp b/Engine/Source/Runtime/Engine/Private/SampleBuffer.cpp index a94a17d6a010..eb2a4fffd217 100644 --- a/Engine/Source/Runtime/Engine/Private/SampleBuffer.cpp +++ b/Engine/Source/Runtime/Engine/Private/SampleBuffer.cpp @@ -81,6 +81,11 @@ namespace Audio } } + FString FSoundWavePCMLoader::GetReferencerName() const + { + return TEXT("FSoundWavePCMLoader"); + } + FSoundWavePCMWriter::FSoundWavePCMWriter(int32 InChunkSize) : CurrentSoundWave(0) , CurrentState(ESoundWavePCMWriterState::Idle) diff --git a/Engine/Source/Runtime/Engine/Private/SceneView.cpp b/Engine/Source/Runtime/Engine/Private/SceneView.cpp index b7113a465806..7a305cdd8ec7 100644 --- a/Engine/Source/Runtime/Engine/Private/SceneView.cpp +++ b/Engine/Source/Runtime/Engine/Private/SceneView.cpp @@ -1710,15 +1710,15 @@ void FSceneView::StartFinalPostprocessSettings(FVector InViewLocation) } } - if(State) + if (State != nullptr) { State->OnStartPostProcessing(*this); } - UWorld* World = Family->Scene->GetWorld(); + UWorld* World = ((Family != nullptr) && (Family->Scene != nullptr)) ? Family->Scene->GetWorld() : nullptr; // Some views have no world (e.g. material preview) - if (World) + if (World != nullptr) { for (auto VolumeIt = World->PostProcessVolumes.CreateIterator(); VolumeIt; ++VolumeIt) { diff --git a/Engine/Source/Runtime/Engine/Private/SkeletalMesh.cpp b/Engine/Source/Runtime/Engine/Private/SkeletalMesh.cpp index af8aab5befa6..dfa97d204ae2 100644 --- a/Engine/Source/Runtime/Engine/Private/SkeletalMesh.cpp +++ b/Engine/Source/Runtime/Engine/Private/SkeletalMesh.cpp @@ -3656,7 +3656,7 @@ public: } while (NotValidPreviewSection()); return *this; } - FORCEINLINE operator bool() const + FORCEINLINE explicit operator bool() const { return ((SectionIndex < Sections.Num()) && LODSectionElements.SectionElements.IsValidIndex(GetSectionElementIndex())); } diff --git a/Engine/Source/Runtime/Engine/Private/SkeletalMeshComponentPhysics.cpp b/Engine/Source/Runtime/Engine/Private/SkeletalMeshComponentPhysics.cpp index d3f0d7a8d279..158a6fece510 100644 --- a/Engine/Source/Runtime/Engine/Private/SkeletalMeshComponentPhysics.cpp +++ b/Engine/Source/Runtime/Engine/Private/SkeletalMeshComponentPhysics.cpp @@ -1317,7 +1317,7 @@ void USkeletalMeshComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTrans } } -bool USkeletalMeshComponent::UpdateOverlapsImpl(TArray const* PendingOverlaps, bool bDoNotifies, const TArray* OverlapsAtEndLocation) +bool USkeletalMeshComponent::UpdateOverlapsImpl(const TOverlapArrayView* PendingOverlaps, bool bDoNotifies, const TOverlapArrayView* OverlapsAtEndLocation) { // Parent class (USkinnedMeshComponent) routes only to children, but we really do want to test our own bodies for overlaps. return UPrimitiveComponent::UpdateOverlapsImpl(PendingOverlaps, bDoNotifies, OverlapsAtEndLocation); diff --git a/Engine/Source/Runtime/Engine/Private/SkeletalMeshLODRenderData.cpp b/Engine/Source/Runtime/Engine/Private/SkeletalMeshLODRenderData.cpp index 0913301140ba..043557edc269 100644 --- a/Engine/Source/Runtime/Engine/Private/SkeletalMeshLODRenderData.cpp +++ b/Engine/Source/Runtime/Engine/Private/SkeletalMeshLODRenderData.cpp @@ -765,10 +765,16 @@ void FSkeletalMeshLODRenderData::Serialize(FArchive& Ar, UObject* Owner, int32 I if (Ar.IsLoading() && !!GSkinWeightProfilesLoadByDefaultMode) { #if !WITH_EDITOR - // Only allow overriding the base buffer in non-editor builds as it could otherwise be serialized into the asset - SkinWeightProfilesData.OverrideBaseBufferSkinWeightData(OwnerMesh, Idx); + if ( GSkinWeightProfilesLoadByDefaultMode == 1) + { + // Only allow overriding the base buffer in non-editor builds as it could otherwise be serialized into the asset + SkinWeightProfilesData.OverrideBaseBufferSkinWeightData(OwnerMesh, Idx); + } #endif - SkinWeightProfilesData.SetDynamicDefaultSkinWeightProfile(OwnerMesh, Idx); + if (GSkinWeightProfilesLoadByDefaultMode == 3) + { + SkinWeightProfilesData.SetDynamicDefaultSkinWeightProfile(OwnerMesh, Idx); + } } } diff --git a/Engine/Source/Runtime/Engine/Private/Slate/DeferredCleanupSlateBrush.cpp b/Engine/Source/Runtime/Engine/Private/Slate/DeferredCleanupSlateBrush.cpp index 4597604faae7..f7b67a23cc03 100644 --- a/Engine/Source/Runtime/Engine/Private/Slate/DeferredCleanupSlateBrush.cpp +++ b/Engine/Source/Runtime/Engine/Private/Slate/DeferredCleanupSlateBrush.cpp @@ -48,5 +48,5 @@ void FDeferredCleanupSlateBrush::AddReferencedObjects(FReferenceCollector& Colle FString FDeferredCleanupSlateBrush::GetReferencerName() const { - return "FDeferredCleanupSlateBrush"; + return TEXT("FDeferredCleanupSlateBrush"); } \ No newline at end of file diff --git a/Engine/Source/Runtime/Engine/Private/Slate/SceneViewport.cpp b/Engine/Source/Runtime/Engine/Private/Slate/SceneViewport.cpp index 4842d5b6afa7..cfd66270d987 100644 --- a/Engine/Source/Runtime/Engine/Private/Slate/SceneViewport.cpp +++ b/Engine/Source/Runtime/Engine/Private/Slate/SceneViewport.cpp @@ -198,7 +198,7 @@ void FSceneViewport::SetMouse( int32 X, int32 Y ) { const FVector2D NormalizedLocalMousePosition = FVector2D(X, Y) / GetSizeXY(); FVector2D AbsolutePos = CachedGeometry.LocalToAbsolute(NormalizedLocalMousePosition * CachedGeometry.GetLocalSize()); - FSlateApplication::Get().SetCursorPos( AbsolutePos ); + FSlateApplication::Get().SetCursorPos( AbsolutePos.RoundToVector() ); CachedCursorPos = FIntPoint(X, Y); } @@ -487,7 +487,7 @@ FReply FSceneViewport::OnMouseButtonDown( const FGeometry& InGeometry, const FPo !bNewMenuWasOpened && // We should not focus the viewport if a menu was opened as it would close the menu (bPermanentCapture || bTemporaryCapture)) { - CurrentReplyState = AcquireFocusAndCapture(FIntPoint(InMouseEvent.GetScreenSpacePosition().X, InMouseEvent.GetScreenSpacePosition().Y)); + CurrentReplyState = AcquireFocusAndCapture(FIntPoint(InMouseEvent.GetScreenSpacePosition().X, InMouseEvent.GetScreenSpacePosition().Y), EFocusCause::Mouse); } } @@ -497,7 +497,7 @@ FReply FSceneViewport::OnMouseButtonDown( const FGeometry& InGeometry, const FPo return CurrentReplyState; } -FReply FSceneViewport::AcquireFocusAndCapture(FIntPoint MousePosition) +FReply FSceneViewport::AcquireFocusAndCapture(FIntPoint MousePosition, EFocusCause FocusCause) { bShouldCaptureMouseOnActivate = false; @@ -506,7 +506,7 @@ FReply FSceneViewport::AcquireFocusAndCapture(FIntPoint MousePosition) TSharedRef ViewportWidgetRef = ViewportWidget.Pin().ToSharedRef(); // Mouse down should focus viewport for user input - ReplyState.SetUserFocus(ViewportWidgetRef, EFocusCause::SetDirectly, true); + ReplyState.SetUserFocus(ViewportWidgetRef, FocusCause, true); UWorld* World = ViewportClient->GetWorld(); if (World && World->IsGameWorld() && World->GetGameInstance() && (World->GetGameInstance()->GetFirstLocalPlayerController() || World->IsPlayInEditor())) @@ -741,7 +741,7 @@ FReply FSceneViewport::OnTouchStarted( const FGeometry& MyGeometry, const FPoint const bool bTemporaryCapture = ViewportClient->CaptureMouseOnClick() == EMouseCaptureMode::CaptureDuringMouseDown; if (bTemporaryCapture) { - CurrentReplyState = AcquireFocusAndCapture(FIntPoint(TouchEvent.GetScreenSpacePosition().X, TouchEvent.GetScreenSpacePosition().Y)); + CurrentReplyState = AcquireFocusAndCapture(FIntPoint(TouchEvent.GetScreenSpacePosition().X, TouchEvent.GetScreenSpacePosition().Y), EFocusCause::Mouse); } } else @@ -1090,7 +1090,7 @@ FReply FSceneViewport::OnFocusReceived(const FFocusEvent& InFocusEvent) FWidgetPath PathToWidget; SlateApp.GeneratePathToWidgetUnchecked(ViewportWidgetRef, PathToWidget); - return AcquireFocusAndCapture(GetSizeXY() / 2); + return AcquireFocusAndCapture(GetSizeXY() / 2, EFocusCause::Mouse); } } } @@ -1169,7 +1169,7 @@ FReply FSceneViewport::OnViewportActivated(const FWindowActivateEvent& InActivat // - the user clicked in our window but not an area our viewport covers. if (InActivateEvent.GetActivationType() == FWindowActivateEvent::EA_Activate && (bShouldCaptureMouseOnActivate || bPermanentCapture)) { - return AcquireFocusAndCapture(GetSizeXY() / 2); + return AcquireFocusAndCapture(GetSizeXY() / 2, EFocusCause::WindowActivate); } } diff --git a/Engine/Source/Runtime/Engine/Private/Slate/SlateGameResources.cpp b/Engine/Source/Runtime/Engine/Private/Slate/SlateGameResources.cpp index 9d46e4b1da31..796523314dd7 100644 --- a/Engine/Source/Runtime/Engine/Private/Slate/SlateGameResources.cpp +++ b/Engine/Source/Runtime/Engine/Private/Slate/SlateGameResources.cpp @@ -306,3 +306,8 @@ void FSlateGameResources::AddReferencedObjects( FReferenceCollector& Collector ) { Collector.AddReferencedObjects( UIResources ); } + +FString FSlateGameResources::GetReferencerName() const +{ + return TEXT("FSlateGameResources"); +} diff --git a/Engine/Source/Runtime/Engine/Private/SoundConcurrency.cpp b/Engine/Source/Runtime/Engine/Private/SoundConcurrency.cpp index 4c8f6d83ee86..d7c79306b3a2 100644 --- a/Engine/Source/Runtime/Engine/Private/SoundConcurrency.cpp +++ b/Engine/Source/Runtime/Engine/Private/SoundConcurrency.cpp @@ -705,42 +705,17 @@ FActiveSound* FSoundConcurrencyManager::CreateAndEvictActiveSounds(const FActive TArray Handles; SoundToEvict->GetConcurrencyHandles(Handles); - bool bAllowVirtual = true; - for (const FConcurrencyHandle& Handle : Handles) - { - switch (Handle.Settings.ResolutionRule) - { - // Stop oldest resolution rules will cause an undesired - // cycling through virtual loops from this frame to the next, - // so don't permit virtualization in this case. - case EMaxConcurrentResolutionRule::StopOldest: - case EMaxConcurrentResolutionRule::StopFarthestThenOldest: - { - bAllowVirtual = false; - break; - } - default: - { - continue; - } - } - } - AudioDevice->AddSoundToStop(SoundToEvict); - // If using a mode where we support loop virtualization, attempt to re-trigger and update boolean state. - if (bAllowVirtual) + const bool bDoRangeCheck = false; + FAudioVirtualLoop VirtualLoop; + if (FAudioVirtualLoop::Virtualize(*SoundToEvict, bDoRangeCheck, VirtualLoop)) { - const bool bDoRangeCheck = true; - FAudioVirtualLoop VirtualLoop; - if (FAudioVirtualLoop::Virtualize(*SoundToEvict, bDoRangeCheck, VirtualLoop)) + SoundToEvict->ClearAudioComponent(); + if (USoundBase* Sound = SoundToEvict->GetSound()) { - SoundToEvict->ClearAudioComponent(); - if (USoundBase* Sound = SoundToEvict->GetSound()) - { - UE_LOG(LogAudio, Verbose, TEXT("Playing ActiveSound %s Virtualizing: Evicted due to concurrency rules."), *Sound->GetName()); - } - AudioDevice->AddVirtualLoop(VirtualLoop); + UE_LOG(LogAudio, Verbose, TEXT("Playing ActiveSound %s Virtualizing: Evicted due to concurrency rules."), *Sound->GetName()); } + AudioDevice->AddVirtualLoop(VirtualLoop); } } diff --git a/Engine/Source/Runtime/Engine/Private/SoundNode.cpp b/Engine/Source/Runtime/Engine/Private/SoundNode.cpp index adda9f3c8b25..bcdc235c42fa 100644 --- a/Engine/Source/Runtime/Engine/Private/SoundNode.cpp +++ b/Engine/Source/Runtime/Engine/Private/SoundNode.cpp @@ -4,6 +4,7 @@ #include "Sound/SoundNode.h" #include "EngineUtils.h" #include "Sound/SoundCue.h" +#include "Misc/App.h" /*----------------------------------------------------------------------------- USoundNode implementation. @@ -11,6 +12,7 @@ USoundNode::USoundNode(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { + RandomStream.Initialize(FApp::bUseFixedSeed ? GetFName() : NAME_None); } diff --git a/Engine/Source/Runtime/Engine/Private/SoundNodeDelay.cpp b/Engine/Source/Runtime/Engine/Private/SoundNodeDelay.cpp index db4ff37ba221..68daa7dd1fc0 100644 --- a/Engine/Source/Runtime/Engine/Private/SoundNodeDelay.cpp +++ b/Engine/Source/Runtime/Engine/Private/SoundNodeDelay.cpp @@ -31,7 +31,7 @@ void USoundNodeDelay::ParseNodes( FAudioDevice* AudioDevice, const UPTRINT NodeW { *RequiresInitialization = false; - const float ActualDelay = FMath::Max(0.f, DelayMax + ( ( DelayMin - DelayMax ) * FMath::SRand() )); + const float ActualDelay = FMath::Max(0.f, DelayMax + ( ( DelayMin - DelayMax ) * RandomStream.FRand() )); if (ActualDelay > 0.0f && ParseParams.StartTime >= ActualDelay) { diff --git a/Engine/Source/Runtime/Engine/Private/SoundNodeEnveloper.cpp b/Engine/Source/Runtime/Engine/Private/SoundNodeEnveloper.cpp index 70b670736373..40f7293fc8a5 100644 --- a/Engine/Source/Runtime/Engine/Private/SoundNodeEnveloper.cpp +++ b/Engine/Source/Runtime/Engine/Private/SoundNodeEnveloper.cpp @@ -68,8 +68,8 @@ void USoundNodeEnveloper::ParseNodes( FAudioDevice* AudioDevice, const UPTRINT N if( *RequiresInitialization ) { StartTime = ActiveSound.PlaybackTime - ParseParams.StartTime; - UsedVolumeModulation = VolumeMax + ( ( VolumeMin - VolumeMax ) * FMath::SRand() ); - UsedPitchModulation = PitchMax + ( ( PitchMin - PitchMax ) * FMath::SRand() ); + UsedVolumeModulation = VolumeMax + ( ( VolumeMin - VolumeMax ) * RandomStream.FRand() ); + UsedPitchModulation = PitchMax + ( ( PitchMin - PitchMax ) * RandomStream.FRand() ); LastLoopCount = -1; *RequiresInitialization = false; @@ -95,8 +95,8 @@ void USoundNodeEnveloper::ParseNodes( FAudioDevice* AudioDevice, const UPTRINT N else if ( CurrentLoopCount != LastLoopCount ) { // randomize multipliers for the new repeat - UsedVolumeModulation = VolumeMax + ( ( VolumeMin - VolumeMax ) * FMath::SRand() ); - UsedPitchModulation = PitchMax + ( ( PitchMin - PitchMax ) * FMath::SRand() ); + UsedVolumeModulation = VolumeMax + ( ( VolumeMin - VolumeMax ) * RandomStream.FRand() ); + UsedPitchModulation = PitchMax + ( ( PitchMin - PitchMax ) * RandomStream.FRand() ); LastLoopCount = CurrentLoopCount; } } diff --git a/Engine/Source/Runtime/Engine/Private/SoundNodeModulator.cpp b/Engine/Source/Runtime/Engine/Private/SoundNodeModulator.cpp index 0fb4be3cea0a..991fa725b4b2 100644 --- a/Engine/Source/Runtime/Engine/Private/SoundNodeModulator.cpp +++ b/Engine/Source/Runtime/Engine/Private/SoundNodeModulator.cpp @@ -24,8 +24,8 @@ void USoundNodeModulator::ParseNodes( FAudioDevice* AudioDevice, const UPTRINT N if( *RequiresInitialization ) { - UsedVolumeModulation = VolumeMax + ( ( VolumeMin - VolumeMax ) * FMath::SRand() ); - UsedPitchModulation = PitchMax + ( ( PitchMin - PitchMax ) * FMath::SRand() ); + UsedVolumeModulation = VolumeMax + ( ( VolumeMin - VolumeMax ) * RandomStream.FRand() ); + UsedPitchModulation = PitchMax + ( ( PitchMin - PitchMax ) * RandomStream.FRand() ); *RequiresInitialization = 0; } diff --git a/Engine/Source/Runtime/Engine/Private/SoundNodeOscillator.cpp b/Engine/Source/Runtime/Engine/Private/SoundNodeOscillator.cpp index 113afe3153c0..08c5eacf97a4 100644 --- a/Engine/Source/Runtime/Engine/Private/SoundNodeOscillator.cpp +++ b/Engine/Source/Runtime/Engine/Private/SoundNodeOscillator.cpp @@ -32,10 +32,10 @@ void USoundNodeOscillator::ParseNodes( FAudioDevice* AudioDevice, const UPTRINT if( *RequiresInitialization ) { - UsedAmplitude = AmplitudeMax + ( ( AmplitudeMin - AmplitudeMax ) * FMath::SRand() ); - UsedFrequency = FrequencyMax + ( ( FrequencyMin - FrequencyMax ) * FMath::SRand() ); - UsedOffset = OffsetMax + ( ( OffsetMin - OffsetMax ) * FMath::SRand() ); - UsedCenter = CenterMax + ( ( CenterMin - CenterMax ) * FMath::SRand() ); + UsedAmplitude = AmplitudeMax + ( ( AmplitudeMin - AmplitudeMax ) * RandomStream.FRand() ); + UsedFrequency = FrequencyMax + ( ( FrequencyMin - FrequencyMax ) * RandomStream.FRand() ); + UsedOffset = OffsetMax + ( ( OffsetMin - OffsetMax ) * RandomStream.FRand() ); + UsedCenter = CenterMax + ( ( CenterMin - CenterMax ) * RandomStream.FRand() ); *RequiresInitialization = 0; } diff --git a/Engine/Source/Runtime/Engine/Private/SoundWave.cpp b/Engine/Source/Runtime/Engine/Private/SoundWave.cpp index a98297f46624..d767edc2daae 100644 --- a/Engine/Source/Runtime/Engine/Private/SoundWave.cpp +++ b/Engine/Source/Runtime/Engine/Private/SoundWave.cpp @@ -1383,10 +1383,10 @@ bool USoundWave::CleanupDecompressor(bool bForceWait) return false; } -FWaveInstance* USoundWave::HandleStart( FActiveSound& ActiveSound, const UPTRINT WaveInstanceHash ) const +FWaveInstance& USoundWave::HandleStart( FActiveSound& ActiveSound, const UPTRINT WaveInstanceHash ) const { // Create a new wave instance and associate with the ActiveSound - FWaveInstance* WaveInstance = new FWaveInstance(WaveInstanceHash, ActiveSound); + FWaveInstance& WaveInstance = ActiveSound.AddWaveInstance(WaveInstanceHash); // Add in the subtitle if they exist if (ActiveSound.bHandleSubtitles && Subtitles.Num() > 0) @@ -1395,7 +1395,7 @@ FWaveInstance* USoundWave::HandleStart( FActiveSound& ActiveSound, const UPTRINT { QueueSubtitleParams.AudioComponentID = ActiveSound.GetAudioComponentID(); QueueSubtitleParams.WorldPtr = ActiveSound.GetWeakWorld(); - QueueSubtitleParams.WaveInstance = (PTRINT)WaveInstance; + QueueSubtitleParams.WaveInstance = (PTRINT)&WaveInstance; QueueSubtitleParams.SubtitlePriority = ActiveSound.SubtitlePriority; QueueSubtitleParams.Duration = Duration; QueueSubtitleParams.bManualWordWrap = bManualWordWrap; @@ -1464,7 +1464,7 @@ void USoundWave::Parse(FAudioDevice* AudioDevice, const UPTRINT NodeWaveInstance ActiveSound.ApplyRadioFilter(ParseParams); } - WaveInstance = HandleStart(ActiveSound, NodeWaveInstanceHash); + WaveInstance = &HandleStart(ActiveSound, NodeWaveInstanceHash); } // Looping sounds are never actually finished diff --git a/Engine/Source/Runtime/Engine/Private/StaticMesh.cpp b/Engine/Source/Runtime/Engine/Private/StaticMesh.cpp index af827b69c20d..64fe91c93be5 100644 --- a/Engine/Source/Runtime/Engine/Private/StaticMesh.cpp +++ b/Engine/Source/Runtime/Engine/Private/StaticMesh.cpp @@ -1710,7 +1710,7 @@ void FStaticMeshLODSettings::Initialize(const FConfigFile& IniFile) }; } - Groups.KeySort(TLess()); + Groups.KeySort(FNameLexicalLess()); GroupName2Index.Empty(Groups.Num()); { int32 GroupIdx = 0; @@ -2290,6 +2290,8 @@ void FStaticMeshRenderData::Cache(UStaticMesh* Owner, const FStaticMeshLODSettin { + TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("StaticMesh_Cache")); + COOK_STAT(auto Timer = StaticMeshCookStats::UsageStats.TimeSyncWork()); int32 T0 = FPlatformTime::Cycles(); int32 NumLODs = Owner->SourceModels.Num(); @@ -6016,6 +6018,11 @@ void UStaticMesh::GenerateLodsInPackage() #endif // #if WITH_EDITOR +void UStaticMesh::AddSocket(UStaticMeshSocket* Socket) +{ + Sockets.AddUnique(Socket); +} + UStaticMeshSocket* UStaticMesh::FindSocket(FName InSocketName) const { if(InSocketName == NAME_None) @@ -6034,6 +6041,11 @@ UStaticMeshSocket* UStaticMesh::FindSocket(FName InSocketName) const return NULL; } +void UStaticMesh::RemoveSocket(UStaticMeshSocket* Socket) +{ + Sockets.Remove(Socket); +} + /*----------------------------------------------------------------------------- UStaticMeshSocket -----------------------------------------------------------------------------*/ diff --git a/Engine/Source/Runtime/Engine/Private/StreamableManager.cpp b/Engine/Source/Runtime/Engine/Private/StreamableManager.cpp index a234c5178a0b..b0a723d5e29b 100644 --- a/Engine/Source/Runtime/Engine/Private/StreamableManager.cpp +++ b/Engine/Source/Runtime/Engine/Private/StreamableManager.cpp @@ -7,6 +7,7 @@ #include "UObject/UObjectThreadContext.h" #include "HAL/IConsoleManager.h" #include "Tickable.h" +#include "Serialization/LoadTimeTrace.h" DEFINE_LOG_CATEGORY_STATIC(LogStreamableManager, Log, All); @@ -199,6 +200,8 @@ bool FStreamableHandle::BindUpdateDelegate(FStreamableUpdateDelegate NewDelegate EAsyncPackageState::Type FStreamableHandle::WaitUntilComplete(float Timeout, bool bStartStalledHandles) { + TRACE_LOADTIME_WAIT_FOR_STREAMABLE_HANDLE_SCOPE(this); + if (HasLoadCompleted()) { return EAsyncPackageState::Complete; @@ -220,7 +223,7 @@ EAsyncPackageState::Type FStreamableHandle::WaitUntilComplete(float Timeout, boo Handle->StartStalledHandle(); } - for (TSharedPtr ChildHandle : Handle->ChildHandles) + for (const TSharedPtr& ChildHandle : Handle->ChildHandles) { if (ChildHandle.IsValid()) { @@ -265,7 +268,7 @@ void FStreamableHandle::GetRequestedAssets(TArray& AssetList) c AssetList = RequestedAssets; // Check child handles - for (TSharedPtr ChildHandle : ChildHandles) + for (const TSharedPtr& ChildHandle : ChildHandles) { TArray ChildAssetList; @@ -310,7 +313,7 @@ void FStreamableHandle::GetLoadedAssets(TArray& LoadedAssets) const } // Check child handles - for (TSharedPtr ChildHandle : ChildHandles) + for (const TSharedPtr& ChildHandle : ChildHandles) { for (const FSoftObjectPath& Ref : ChildHandle->RequestedAssets) { @@ -334,7 +337,7 @@ void FStreamableHandle::GetLoadedCount(int32& LoadedCount, int32& RequestedCount LoadedCount = RequestedCount - StreamablesLoading; // Check child handles - for (TSharedPtr ChildHandle : ChildHandles) + for (const TSharedPtr& ChildHandle : ChildHandles) { int32 ChildRequestedCount = 0; int32 ChildLoadedCount = 0; @@ -418,7 +421,7 @@ void FStreamableHandle::CancelHandle() OwningManager->ManagedActiveHandles.Remove(SharedThis); // Remove child handles - for (TSharedPtr ChildHandle : ChildHandles) + for (TSharedPtr& ChildHandle : ChildHandles) { ChildHandle->ParentHandles.Remove(SharedThis); } @@ -431,7 +434,7 @@ void FStreamableHandle::CancelHandle() { // Update any meta handles that are still active. Copy the array first as elements may be removed from original while iterating TArray> ParentHandlesCopy = ParentHandles; - for (TWeakPtr WeakHandle : ParentHandlesCopy) + for (TWeakPtr& WeakHandle : ParentHandlesCopy) { TSharedPtr Handle = WeakHandle.Pin(); @@ -503,7 +506,8 @@ void FStreamableHandle::StartStalledHandle() FStreamableHandle::~FStreamableHandle() { - check(IsInGameThread()); + TRACE_LOADTIME_DESTROY_STREAMABLE_HANDLE(this); + check(IsInGameThread() || IsInGarbageCollectorThread()); if (IsActive()) { @@ -516,6 +520,7 @@ FStreamableHandle::~FStreamableHandle() void FStreamableHandle::CompleteLoad() { + TRACE_LOADTIME_END_LOAD_STREAMABLE_HANDLE(this); // Only complete if it's still active if (IsActive()) { @@ -528,7 +533,7 @@ void FStreamableHandle::CompleteLoad() { // Update any meta handles that are still active. Copy the array first as elements may be removed from original while iterating TArray> ParentHandlesCopy = ParentHandles; - for (TWeakPtr WeakHandle : ParentHandlesCopy) + for (TWeakPtr& WeakHandle : ParentHandlesCopy) { TSharedPtr Handle = WeakHandle.Pin(); @@ -556,10 +561,15 @@ void FStreamableHandle::UpdateCombinedHandle() // Check all our children, complete if done bool bAllCompleted = true; bool bAllCanceled = true; - for (TSharedPtr& ChildHandle : ChildHandles) + for (const TSharedPtr& ChildHandle : ChildHandles) { bAllCompleted = bAllCompleted && !ChildHandle->IsLoadingInProgress(); bAllCanceled = bAllCanceled && ChildHandle->WasCanceled(); + + if (!bAllCompleted && !bAllCanceled) + { + return; + } } // If all our sub handles were canceled, cancel us. Otherwise complete us if at least one was completed and there are none in progress @@ -593,7 +603,7 @@ void FStreamableHandle::CallUpdateDelegate() UpdateDelegate.ExecuteIfBound(AsShared()); // Update any meta handles that are still active - for (TWeakPtr WeakHandle : ParentHandles) + for (TWeakPtr& WeakHandle : ParentHandles) { TSharedPtr Handle = WeakHandle.Pin(); @@ -721,6 +731,7 @@ FStreamableManager::FStreamableManager() { FCoreUObjectDelegates::GetPreGarbageCollectDelegate().AddRaw(this, &FStreamableManager::OnPreGarbageCollect); bForceSynchronousLoads = false; + ManagerName = TEXT("StreamableManager"); } FStreamableManager::~FStreamableManager() @@ -796,6 +807,16 @@ void FStreamableManager::AddReferencedObjects(FReferenceCollector& Collector) } } +const FString& FStreamableManager::GetManagerName() const +{ + return ManagerName; +} + +void FStreamableManager::SetManagerName(FString InName) +{ + ManagerName = MoveTemp(InName); +} + bool FStreamableManager::GetReferencerPropertyName(UObject* Object, FString& OutPropertyName) const { if (Object) @@ -918,22 +939,13 @@ FStreamable* FStreamableManager::StreamInternal(const FSoftObjectPath& InTargetN Existing->bAsyncLoadRequestOutstanding = true; Existing->bLoadFailed = false; - LoadPackageAsync(Package, FLoadPackageAsyncDelegate::CreateSP(Handle, &FStreamableHandle::AsyncLoadCallbackWrapper, TargetName), Priority); + int32 RequestId = LoadPackageAsync(Package, FLoadPackageAsyncDelegate::CreateSP(Handle, &FStreamableHandle::AsyncLoadCallbackWrapper, TargetName), Priority); + TRACE_LOADTIME_STREAMABLE_HANDLE_REQUEST_ASSOCIATION(&Handle.Get(), RequestId); } } return Existing; } -void FStreamableManager::SimpleAsyncLoad(const FSoftObjectPath& Target, TAsyncLoadPriority Priority) -{ - RequestAsyncLoad(Target, FStreamableDelegate(), Priority, true); -} - -UObject* FStreamableManager::SynchronousLoad(FSoftObjectPath const& Target) -{ - return LoadSynchronous(Target, true); -} - TSharedPtr FStreamableManager::RequestAsyncLoad(const TArray& TargetsToStream, FStreamableDelegate DelegateToCall, TAsyncLoadPriority Priority, bool bManageActiveHandle, bool bStartStalled, const FString& DebugName) { LLM_SCOPE(ELLMTag::StreamingManager); @@ -946,6 +958,8 @@ TSharedPtr FStreamableManager::RequestAsyncLoad(const TArray< NewRequest->DebugName = DebugName; NewRequest->Priority = Priority; + TRACE_LOADTIME_NEW_STREAMABLE_HANDLE(&NewRequest.Get(), *DebugName, false); + // Remove null requests for (int32 i = NewRequest->RequestedAssets.Num() - 1; i >= 0 ; i--) @@ -1075,6 +1089,8 @@ TSharedPtr FStreamableManager::RequestSyncLoad(const FSoftObj void FStreamableManager::StartHandleRequests(TSharedRef Handle) { + TRACE_LOADTIME_BEGIN_LOAD_STREAMABLE_HANDLE(&Handle.Get()); + TArray ExistingStreamables; ExistingStreamables.Reserve(Handle->RequestedAssets.Num()); @@ -1204,7 +1220,7 @@ void FStreamableManager::CheckCompletedRequests(const FSoftObjectPath& Target, s TArray> HandlesToComplete; TArray> HandlesToRelease; - for (TSharedRef Handle : Existing->LoadingHandles) + for (TSharedRef& Handle : Existing->LoadingHandles) { ensure(Handle->WasCanceled() || Handle->OwningManager == this); @@ -1222,12 +1238,12 @@ void FStreamableManager::CheckCompletedRequests(const FSoftObjectPath& Target, s } Existing->LoadingHandles.Empty(); - for (TSharedRef Handle : HandlesToComplete) + for (TSharedRef& Handle : HandlesToComplete) { Handle->CompleteLoad(); } - for (TSharedRef Handle : HandlesToRelease) + for (TSharedRef& Handle : HandlesToRelease) { Handle->ReleaseHandle(); } @@ -1318,6 +1334,7 @@ TSharedPtr FStreamableManager::CreateCombinedHandle(const TAr NewRequest->OwningManager = this; NewRequest->bIsCombinedHandle = true; NewRequest->DebugName = DebugName; + TRACE_LOADTIME_NEW_STREAMABLE_HANDLE(&NewRequest.Get(), *DebugName, true); for (TSharedPtr ChildHandle : ChildHandles) { diff --git a/Engine/Source/Runtime/Engine/Private/Streaming/StreamingManagerTexture.cpp b/Engine/Source/Runtime/Engine/Private/Streaming/StreamingManagerTexture.cpp index 13865d42277a..6a5537f27749 100644 --- a/Engine/Source/Runtime/Engine/Private/Streaming/StreamingManagerTexture.cpp +++ b/Engine/Source/Runtime/Engine/Private/Streaming/StreamingManagerTexture.cpp @@ -1616,7 +1616,8 @@ bool FRenderAssetStreamingManager::HandleDumpTextureStreamingStatsCommand( const } #endif // STATS_FAST -#if STATS +#if !UE_BUILD_SHIPPING + bool FRenderAssetStreamingManager::HandleListStreamingRenderAssetsCommand( const TCHAR* Cmd, FOutputDevice& Ar ) { FScopeLock ScopeLock(&CriticalSection); @@ -1700,9 +1701,6 @@ bool FRenderAssetStreamingManager::HandleListStreamingRenderAssetsCommand( const } return true; } -#endif // STATS - -#if !UE_BUILD_SHIPPING bool FRenderAssetStreamingManager::HandleResetMaxEverRequiredRenderAssetMemoryCommand(const TCHAR* Cmd, FOutputDevice& Ar) { @@ -2110,14 +2108,12 @@ bool FRenderAssetStreamingManager::Exec( UWorld* InWorld, const TCHAR* Cmd, FOut return HandleDumpTextureStreamingStatsCommand( Cmd, Ar ); } #endif -#if STATS +#if !UE_BUILD_SHIPPING if (FParse::Command(&Cmd,TEXT("ListStreamingTextures")) || FParse::Command(&Cmd, TEXT("ListStreamingRenderAssets"))) { return HandleListStreamingRenderAssetsCommand( Cmd, Ar ); } -#endif -#if !UE_BUILD_SHIPPING if (FParse::Command(&Cmd, TEXT("ResetMaxEverRequiredTextures")) || FParse::Command(&Cmd, TEXT("ResetMaxEverRequiredRenderAssetMemory"))) { diff --git a/Engine/Source/Runtime/Engine/Private/Streaming/StreamingManagerTexture.h b/Engine/Source/Runtime/Engine/Private/Streaming/StreamingManagerTexture.h index f90f20a2ed42..33028990bed3 100644 --- a/Engine/Source/Runtime/Engine/Private/Streaming/StreamingManagerTexture.h +++ b/Engine/Source/Runtime/Engine/Private/Streaming/StreamingManagerTexture.h @@ -107,10 +107,8 @@ struct FRenderAssetStreamingManager final : public IRenderAssetStreamingManager #if STATS_FAST bool HandleDumpTextureStreamingStatsCommand( const TCHAR* Cmd, FOutputDevice& Ar ); #endif // STATS_FAST -#if STATS - bool HandleListStreamingRenderAssetsCommand(const TCHAR* Cmd, FOutputDevice& Ar); -#endif // STATS #if !UE_BUILD_SHIPPING + bool HandleListStreamingRenderAssetsCommand(const TCHAR* Cmd, FOutputDevice& Ar); bool HandleResetMaxEverRequiredRenderAssetMemoryCommand(const TCHAR* Cmd, FOutputDevice& Ar); bool HandleLightmapStreamingFactorCommand( const TCHAR* Cmd, FOutputDevice& Ar ); bool HandleCancelRenderAssetStreamingCommand( const TCHAR* Cmd, FOutputDevice& Ar ); diff --git a/Engine/Source/Runtime/Engine/Private/Streaming/TextureInstanceView.h b/Engine/Source/Runtime/Engine/Private/Streaming/TextureInstanceView.h index 2def50a4a0b4..cc074d61489a 100644 --- a/Engine/Source/Runtime/Engine/Private/Streaming/TextureInstanceView.h +++ b/Engine/Source/Runtime/Engine/Private/Streaming/TextureInstanceView.h @@ -132,7 +132,7 @@ public: public: FRenderAssetLinkConstIterator(const FRenderAssetInstanceView& InState, const UStreamableRenderAsset* InAsset); - FORCEINLINE operator bool() const { return CurrElementIndex != INDEX_NONE; } + FORCEINLINE explicit operator bool() const { return CurrElementIndex != INDEX_NONE; } FORCEINLINE void operator++() { CurrElementIndex = State.Elements[CurrElementIndex].NextRenderAssetLink; } void OutputToLog(float MaxNormalizedSize, float MaxNormalizedSize_VisibleOnly, const TCHAR* Prefix) const; @@ -170,7 +170,7 @@ public: public: FRenderAssetIterator(const FRenderAssetInstanceView& InState) : MapIt(InState.RenderAssetMap) {} - operator bool() const { return (bool)MapIt; } + explicit operator bool() const { return (bool)MapIt; } void operator++() { ++MapIt; } const UStreamableRenderAsset* operator*() const { return MapIt.Key(); } diff --git a/Engine/Source/Runtime/Engine/Private/Subsystems/LocalPlayerSubsystem.cpp b/Engine/Source/Runtime/Engine/Private/Subsystems/LocalPlayerSubsystem.cpp index 3b5aece1cbaf..1717f19d37a8 100644 --- a/Engine/Source/Runtime/Engine/Private/Subsystems/LocalPlayerSubsystem.cpp +++ b/Engine/Source/Runtime/Engine/Private/Subsystems/LocalPlayerSubsystem.cpp @@ -6,11 +6,4 @@ ULocalPlayerSubsystem::ULocalPlayerSubsystem() : USubsystem() { - } - -ULocalPlayer* ULocalPlayerSubsystem::GetLocalPlayer() const -{ - return Cast(GetOuter()); -} - diff --git a/Engine/Source/Runtime/Engine/Private/Subsystems/SubsystemCollection.cpp b/Engine/Source/Runtime/Engine/Private/Subsystems/SubsystemCollection.cpp index 9eff9f3dfe1e..f35ab99683e9 100644 --- a/Engine/Source/Runtime/Engine/Private/Subsystems/SubsystemCollection.cpp +++ b/Engine/Source/Runtime/Engine/Private/Subsystems/SubsystemCollection.cpp @@ -173,6 +173,11 @@ void FSubsystemCollectionBase::AddReferencedObjects(FReferenceCollector& Collect Collector.AddReferencedObjects(SubsystemMap); } +FString FSubsystemCollectionBase::GetReferencerName() const +{ + return TEXT("FSubsystemCollectionBase"); +} + bool FSubsystemCollectionBase::AddAndInitializeSubsystem(UClass* SubsystemClass) { if (!SubsystemMap.Contains(SubsystemClass)) @@ -287,6 +292,7 @@ void FSubsystemModuleWatcher::DeinitializeModuleWatcher() if (ModulesChangedHandle.IsValid()) { FModuleManager::Get().OnModulesChanged().Remove(ModulesChangedHandle); + ModulesChangedHandle.Reset(); } } diff --git a/Engine/Source/Runtime/Engine/Private/Texture2D.cpp b/Engine/Source/Runtime/Engine/Private/Texture2D.cpp index af0be57e55ab..e30e12dcafec 100644 --- a/Engine/Source/Runtime/Engine/Private/Texture2D.cpp +++ b/Engine/Source/Runtime/Engine/Private/Texture2D.cpp @@ -40,6 +40,7 @@ #if WITH_EDITOR #include "Settings/EditorExperimentalSettings.h" +#include "Landscape/Classes/LandscapeProxy.h" #endif UTexture2D::UTexture2D(const FObjectInitializer& ObjectInitializer) @@ -1245,9 +1246,10 @@ bool UTexture2D::ShouldMipLevelsBeForcedResident() const } #if WITH_EDITOR - if (GIsEditor && GetMutableDefault()->bLandscapeLayerSystem && (LODGroup == TEXTUREGROUP_Terrain_Heightmap || LODGroup == TEXTUREGROUP_Terrain_Weightmap)) + if (GIsEditor && (LODGroup == TEXTUREGROUP_Terrain_Heightmap || LODGroup == TEXTUREGROUP_Terrain_Weightmap)) { - return true; + ALandscapeProxy* Proxy = Cast(GetOuter()); + return Proxy && Proxy->HasLayersContent(); } #endif diff --git a/Engine/Source/Runtime/Engine/Private/TickTaskManager.cpp b/Engine/Source/Runtime/Engine/Private/TickTaskManager.cpp index 079b559a7481..b656d0a6dafb 100644 --- a/Engine/Source/Runtime/Engine/Private/TickTaskManager.cpp +++ b/Engine/Source/Runtime/Engine/Private/TickTaskManager.cpp @@ -1188,7 +1188,7 @@ public: DisabledCount += AllDisabledTickFunctions.Num(); } - FORCEINLINE void AddTickFunctionToMap(TSortedMap& ClassNameToCountMap, FTickFunction* Function, bool bDetailed) + FORCEINLINE void AddTickFunctionToMap(TSortedMap& ClassNameToCountMap, FTickFunction* Function, bool bDetailed) { FName ContextName = Function->DiagnosticContext(bDetailed); // Find entry for this context (or add it if not present) @@ -1197,7 +1197,7 @@ public: CurrentCount++; } - void AddTickFunctionsToMap(TSortedMap& ClassNameToCountMap, int32& EnabledCount, bool bDetailed) + void AddTickFunctionsToMap(TSortedMap& ClassNameToCountMap, int32& EnabledCount, bool bDetailed) { // Add ticks from AllEnabledTickFunctions for (TSet::TIterator It(AllEnabledTickFunctions); It; ++It) @@ -1639,7 +1639,7 @@ private: if (bGrouped) { - TSortedMap TickContextToCountMap; + TSortedMap TickContextToCountMap; GetEnabledTickFunctionCounts(InWorld, TickContextToCountMap, EnabledCount, true); // Build sorted array of tick context by count @@ -1691,7 +1691,7 @@ private: } - virtual void GetEnabledTickFunctionCounts(UWorld* InWorld, TSortedMap& TickContextToCountMap, int32& EnabledCount, bool bDetailed) + virtual void GetEnabledTickFunctionCounts(UWorld* InWorld, TSortedMap& TickContextToCountMap, int32& EnabledCount, bool bDetailed) { check(InWorld); check(InWorld->TickTaskLevel); diff --git a/Engine/Source/Runtime/Engine/Private/Tickable.cpp b/Engine/Source/Runtime/Engine/Private/Tickable.cpp new file mode 100644 index 000000000000..09d6d128ff9f --- /dev/null +++ b/Engine/Source/Runtime/Engine/Private/Tickable.cpp @@ -0,0 +1,152 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Tickable.h" +#include "Engine/World.h" +#include "ProfilingDebugging/CsvProfiler.h" + +DECLARE_CYCLE_STAT(TEXT("TickableGameObjects Time"), STAT_TickableGameObjectsTime, STATGROUP_Game); + +struct FTickableStatics +{ + FCriticalSection TickableObjectsCritical; + TArray TickableObjects; + TLockFreePointerListUnordered NewTickableObjects; + TSet DeletedTickableObjects; + bool bIsTickingObjects = false; + + static FTickableStatics& Get() + { + static FTickableStatics Singleton; + return Singleton; + } +}; + +void FTickableObjectBase::AddTickableObject(TArray& TickableObjects, FTickableObjectBase* TickableObject) +{ + check(!TickableObjects.Contains(TickableObject)); + const ETickableTickType TickType = TickableObject->GetTickableTickType(); + if (TickType != ETickableTickType::Never) + { + TickableObjects.Add({ TickableObject, TickType }); + } +} + +void FTickableObjectBase::RemoveTickableObject(TArray& TickableObjects, FTickableObjectBase* TickableObject, const bool bIsTickingObjects) +{ + const int32 Pos = TickableObjects.IndexOfByKey(TickableObject); + +#if 0 // virtual from destructor doesn't work ... need to rethink how to do warning + // ensure that GetTickableTickType did not change over time + switch (TickableObject->GetTickableTickType()) + { + case ETickableTickType::Always: + ensureMsgf(Pos != INDEX_NONE && TickableObjects[Pos].TickType == ETickableTickType::Always, TEXT("TickType has changed since object was created. Result of GetTickableTickType must be invariant for a given object.")); + break; + + case ETickableTickType::Conditional: + ensureMsgf(Pos != INDEX_NONE && TickableObjects[Pos].TickType == ETickableTickType::Conditional, TEXT("TickType has changed since object was created. Result of GetTickableTickType must be invariant for a given object.")); + break; + + case ETickableTickType::Never: + ensureMsgf(Pos == INDEX_NONE, TEXT("TickType has changed since object was created. Result of GetTickableTickType must be invariant for a given object.")); + break; + } +#endif + + if (Pos != INDEX_NONE) + { + if (bIsTickingObjects) + { + TickableObjects[Pos].TickableObject = nullptr; + } + else + { + TickableObjects.RemoveAt(Pos); + } + } +} + +FTickableGameObject::FTickableGameObject() +{ + FTickableStatics& Statics = FTickableStatics::Get(); + + Statics.NewTickableObjects.Push(this); +} + +FTickableGameObject::~FTickableGameObject() +{ + FTickableStatics& Statics = FTickableStatics::Get(); + + FScopeLock LockTickableObjects(&Statics.TickableObjectsCritical); + Statics.DeletedTickableObjects.Add(this); + RemoveTickableObject(Statics.TickableObjects, this, Statics.bIsTickingObjects); +} + +void FTickableGameObject::TickObjects(UWorld* World, const int32 InTickType, const bool bIsPaused, const float DeltaSeconds) +{ + SCOPE_CYCLE_COUNTER(STAT_TickableGameObjectsTime); + CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Tickables); + + FTickableStatics& Statics = FTickableStatics::Get(); + + check(IsInGameThread()); + + // It's a long lock but it's ok, the only thing we can block here is the GC worker thread that destroys UObjects + FScopeLock LockTickableObjects(&Statics.TickableObjectsCritical); + + while (FTickableGameObject* NewTickableObject = Statics.NewTickableObjects.Pop()) + { + // If the new tickable object has already been deleted, don't add it to the tickable objects list + if (!Statics.DeletedTickableObjects.Contains(NewTickableObject)) + { + AddTickableObject(Statics.TickableObjects, NewTickableObject); + } + } + Statics.DeletedTickableObjects.Empty(); + + if (Statics.TickableObjects.Num() > 0) + { + check(!Statics.bIsTickingObjects); + Statics.bIsTickingObjects = true; + + bool bNeedsCleanup = false; + const ELevelTick TickType = (ELevelTick)InTickType; + + for (const FTickableObjectEntry& TickableEntry : Statics.TickableObjects) + { + if (FTickableGameObject* TickableObject = static_cast(TickableEntry.TickableObject)) + { + // If it is tickable and in this world + if (((TickableEntry.TickType == ETickableTickType::Always) || TickableObject->IsTickable()) && (TickableObject->GetTickableGameObjectWorld() == World)) + { + const bool bIsGameWorld = InTickType == LEVELTICK_All || (World && World->IsGameWorld()); + // If we are in editor and it is editor tickable, always tick + // If this is a game world then tick if we are not doing a time only (paused) update and we are not paused or the object is tickable when paused + if ((GIsEditor && TickableObject->IsTickableInEditor()) || + (bIsGameWorld && ((!bIsPaused && TickType != LEVELTICK_TimeOnly) || (bIsPaused && TickableObject->IsTickableWhenPaused())))) + { + FScopeCycleCounter Context(TickableObject->GetStatId()); + TickableObject->Tick(DeltaSeconds); + + // In case it was removed during tick + if (TickableEntry.TickableObject == nullptr) + { + bNeedsCleanup = true; + } + } + } + } + else + { + bNeedsCleanup = true; + } + } + + if (bNeedsCleanup) + { + Statics.TickableObjects.RemoveAll([](const FTickableObjectEntry& Entry) { return Entry.TickableObject == nullptr; }); + } + + Statics.bIsTickingObjects = false; + } +} diff --git a/Engine/Source/Runtime/Engine/Private/TimelineTemplate.cpp b/Engine/Source/Runtime/Engine/Private/TimelineTemplate.cpp index 890f3423e769..6c8c0af5f477 100644 --- a/Engine/Source/Runtime/Engine/Private/TimelineTemplate.cpp +++ b/Engine/Source/Runtime/Engine/Private/TimelineTemplate.cpp @@ -39,7 +39,6 @@ UTimelineTemplate::UTimelineTemplate(const FObjectInitializer& ObjectInitializer TimelineLength = 5.0f; TimelineGuid = FGuid::NewGuid(); bReplicated = false; - bValidatedAsWired = false; UpdateCachedNames(); } diff --git a/Engine/Source/Runtime/Engine/Private/TimerManager.cpp b/Engine/Source/Runtime/Engine/Private/TimerManager.cpp index 753ff07c81dc..7a6885bcc228 100644 --- a/Engine/Source/Runtime/Engine/Private/TimerManager.cpp +++ b/Engine/Source/Runtime/Engine/Private/TimerManager.cpp @@ -397,6 +397,9 @@ FTimerData& FTimerManager::GetTimer(FTimerHandle const& InHandle) FTimerData* FTimerManager::FindTimer(FTimerHandle const& InHandle) { + // not currently threadsafe + check(IsInGameThread()); + if (!InHandle.IsValid()) { return nullptr; @@ -524,7 +527,7 @@ FTimerHandle FTimerManager::InternalSetTimerForNextTick(FTimerUnifiedDelegate&& return NewTimerHandle; } -void FTimerManager::InternalClearTimer(FTimerHandle const& InHandle) +void FTimerManager::InternalClearTimer(FTimerHandle InHandle) { SCOPE_CYCLE_COUNTER(STAT_ClearTimer); @@ -861,10 +864,12 @@ void FTimerManager::Tick(float DeltaTime) RunTimerDelegates.Add(Top->TimerDelegate); #endif + checkf(!WillRemoveTimerAssert(CurrentlyExecutingTimer), TEXT("RemoveTimer(CurrentlyExecutingTimer) - due to fail before Execute()")); Top->TimerDelegate.Execute(); // Update Top pointer, in case it has been invalidated by the Execute call Top = FindTimer(CurrentlyExecutingTimer); + checkf(!Top || !WillRemoveTimerAssert(CurrentlyExecutingTimer), TEXT("RemoveTimer(CurrentlyExecutingTimer) - due to fail after Execute()")); if (!Top || Top->Status != ETimerStatus::Executing) { break; @@ -949,6 +954,9 @@ TStatId FTimerManager::GetStatId() const void FTimerManager::SetGameInstance(UGameInstance* InGameInstance) { + // not currently threadsafe + check(IsInGameThread()); + OwningGameInstance = InGameInstance; #if UE_ENABLE_TRACKING_TIMER_SOURCES @@ -961,6 +969,9 @@ void FTimerManager::SetGameInstance(UGameInstance* InGameInstance) void FTimerManager::ListTimers() const { + // not currently threadsafe + check(IsInGameThread()); + TArray ValidActiveTimers; ValidActiveTimers.Reserve(ActiveTimerHeap.Num()); for (FTimerHandle Handle : ActiveTimerHeap) @@ -1039,6 +1050,29 @@ void FTimerManager::RemoveTimer(FTimerHandle Handle) Timers.RemoveAt(Handle.GetIndex()); } +bool FTimerManager::WillRemoveTimerAssert(FTimerHandle Handle) const +{ + const FTimerData& Data = GetTimer(Handle); + + // Remove TimerIndicesByObject entry if necessary + if (const void* TimerIndicesByObjectKey = Data.TimerIndicesByObjectKey) + { + const TSet* TimersForObject = ObjectToTimers.Find(TimerIndicesByObjectKey); + if (!TimersForObject) + { + return true; + } + + const FTimerHandle* Found = TimersForObject->Find(Handle); + if (!Found) + { + return true; + } + } + + return false; +} + FTimerHandle FTimerManager::GenerateHandle(int32 Index) { uint64 NewSerialNumber = ++LastAssignedSerialNumber; diff --git a/Engine/Source/Runtime/Engine/Private/TriggerBase.cpp b/Engine/Source/Runtime/Engine/Private/TriggerBase.cpp index e4419561c85e..0853669106ef 100644 --- a/Engine/Source/Runtime/Engine/Private/TriggerBase.cpp +++ b/Engine/Source/Runtime/Engine/Private/TriggerBase.cpp @@ -14,7 +14,7 @@ ATriggerBase::ATriggerBase(const FObjectInitializer& ObjectInitializer) // ATriggerBase is requesting UShapeComponent which is abstract, however it is responsibility // of a derived class to override this type with ObjectInitializer.SetDefaultSubobjectClass. - CollisionComponent = CreateAbstractDefaultSubobject(TEXT("CollisionComp")); + CollisionComponent = CreateDefaultSubobject(TEXT("CollisionComp")); if (CollisionComponent) { RootComponent = CollisionComponent; diff --git a/Engine/Source/Runtime/Engine/Private/URL.cpp b/Engine/Source/Runtime/Engine/Private/URL.cpp index b517e729bb4b..94e06425df52 100644 --- a/Engine/Source/Runtime/Engine/Private/URL.cpp +++ b/Engine/Source/Runtime/Engine/Private/URL.cpp @@ -231,7 +231,7 @@ FURL::FURL( FURL* Base, const TCHAR* TextURL, ETravelType Type ) // Handle pure filenames & Posix paths. bool FarHost=0; bool FarMap=0; - if( FCString::Strlen(URL)>2 && ((URL[0] != '[' && URL[1]==':') || (URL[0]=='/' && !FPackageName::IsValidLongPackageName(URL, true))) ) + if( FCString::Strlen(URL)>2 && ((URL[0] != '[' && URL[0] != ':' && URL[1]==':') || (URL[0]=='/' && !FPackageName::IsValidLongPackageName(URL, true))) ) { // Pure filename. Protocol = UrlConfig.DefaultProtocol; @@ -245,16 +245,19 @@ FURL::FURL( FURL* Base, const TCHAR* TextURL, ETravelType Type ) else { // Determine location of the first opening square bracket. - // Square brackets enclose an IPv6 address. + // Square brackets enclose an IPv6 address if they have a port. const TCHAR* SquareBracket = FCString::Strchr(URL, '['); + const TCHAR* FirstColonLocation = FCString::Strchr(URL, ':'); + const TCHAR* LastColonLocation = FCString::Strrchr(URL, ':'); + const bool bHasMultipleColons = FirstColonLocation != nullptr && LastColonLocation != nullptr && FirstColonLocation != LastColonLocation + && FCString::Strchr(FirstColonLocation + 1, ':') != LastColonLocation; // Parse protocol. Don't consider colons that occur after the opening square // brace, because they are valid characters in an IPv6 address. - if - ( (FCString::Strchr(URL,':')!=NULL) - && (FCString::Strchr(URL,':')>URL+1) - && (!SquareBracket || (FCString::Strchr(URL,':')URL+1) + && ((!SquareBracket && !bHasMultipleColons) || (FirstColonLocation TestCases; + if (FParse::Command(&Cmd, TEXT("URLSERIALIZATION"))) + { + TestCases.Push(FURLTestCase(TEXT("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), TEXT("2001:0db8:85a3:0000:0000:8a2e:0370:7334"))); + TestCases.Push(FURLTestCase(TEXT("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:7778"), TEXT("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), TEXT(""), 7778)); + TestCases.Push(FURLTestCase(TEXT("[2001:db8:85a3::8a2e:370:7334]"), TEXT("2001:db8:85a3::8a2e:370:7334"))); + TestCases.Push(FURLTestCase(TEXT("epic://2001:db8:85a3::8a2e:370:7334"), TEXT("2001:db8:85a3::8a2e:370:7334"), TEXT("epic"))); + TestCases.Push(FURLTestCase(TEXT("192.168.0.1"), TEXT("192.168.0.1"))); + TestCases.Push(FURLTestCase(TEXT("test://192.168.0.1"), TEXT("192.168.0.1"), TEXT("test"))); + TestCases.Push(FURLTestCase(TEXT("::ffff:192.168.0.1"), TEXT("::ffff:192.168.0.1"))); + TestCases.Push(FURLTestCase(TEXT("[::ffff:192.168.0.1]"), TEXT("::ffff:192.168.0.1"))); + TestCases.Push(FURLTestCase(TEXT("[::ffff:192.168.0.1]:7778"), TEXT("::ffff:192.168.0.1"), TEXT(""), 7778)); + TestCases.Push(FURLTestCase(TEXT("test://::ffff:192.168.0.1"), TEXT("::ffff:192.168.0.1"), TEXT("test"))); + TestCases.Push(FURLTestCase(TEXT("unreal:192.168.0.1:7776"), TEXT("192.168.0.1"), TEXT("unreal"), 7776)); + TestCases.Push(FURLTestCase(TEXT("192.168.0.1"), TEXT("192.168.0.1"))); + TestCases.Push(FURLTestCase(TEXT("http://[::ffff:192.168.0.1]:8080"), TEXT("::ffff:192.168.0.1"), TEXT("http"), 8080)); + TestCases.Push(FURLTestCase(TEXT("https:[2001:db8:85a3::8a2e:370:7334]:443"), TEXT("2001:db8:85a3::8a2e:370:7334"), TEXT("https"), 443)); + TestCases.Push(FURLTestCase(TEXT("steam.76561197993275299:20/"), TEXT("steam.76561197993275299"), TEXT(""), 20)); + + bool bAllCasesPassed = true; + for (const auto& TestCase : TestCases) + { + FURL TestURL(nullptr, *TestCase.QueryString, TRAVEL_Absolute); + // This is ugly, but we use it to report which cases failed and why. + const bool bHostMatched = (TestURL.Host == TestCase.Host); + const bool bPortMatched = (TestCase.Port == -1 || TestCase.Port == TestURL.Port); + const bool bProtocolMatched = (TestCase.Protocol.IsEmpty() || TestCase.Protocol == TestURL.Protocol); + + if (bHostMatched && bPortMatched && bProtocolMatched) + { + UE_LOG(LogCore, Log, TEXT("Test %s passed!"), *TestCase.QueryString); + } + else + { + bAllCasesPassed = false; + UE_LOG(LogCore, Warning, TEXT("Test %s failed! Matching flags: Host[%d] Port[%d] Protocol[%d]"), *TestCase.QueryString, bHostMatched, bPortMatched, bProtocolMatched); + if (!bHostMatched) + { + UE_LOG(LogCore, Warning, TEXT("URL had host %s, expected %s"), *TestURL.Host, *TestCase.Host); + } + + if (!bPortMatched) + { + UE_LOG(LogCore, Warning, TEXT("URL had port %d, expected %d"), TestURL.Port, TestCase.Port); + } + + if (!bProtocolMatched) + { + UE_LOG(LogCore, Warning, TEXT("URL had protocol %s, expected %s"), *TestURL.Protocol, *TestCase.Protocol); + } + } + } + + if (bAllCasesPassed) + { + UE_LOG(LogCore, Log, TEXT("All URL serialization cases passed.")); + } + else + { + UE_LOG(LogCore, Warning, TEXT("An URL serialization case failed!")); + } + + return true; + } + + return false; +} + +FStaticSelfRegisteringExec FURLTests(URLSerializationTests); +#endif // WITH_DEV_AUTOMATION_TESTS && !UE_BUILD_SHIPPING + diff --git a/Engine/Source/Runtime/Engine/Private/UnrealClient.cpp b/Engine/Source/Runtime/Engine/Private/UnrealClient.cpp index 63b7b782b7e1..554a4c1ab2ef 100644 --- a/Engine/Source/Runtime/Engine/Private/UnrealClient.cpp +++ b/Engine/Source/Runtime/Engine/Private/UnrealClient.cpp @@ -2079,6 +2079,11 @@ void FViewport::FHitProxyMap::AddReferencedObjects( FReferenceCollector& Collect } } +FString FViewport::FHitProxyMap::GetReferencerName() const +{ + return TEXT("FViewport::FHitProxyMap"); +} + /** * Globally enables/disables rendering * diff --git a/Engine/Source/Runtime/Engine/Private/UnrealEngine.cpp b/Engine/Source/Runtime/Engine/Private/UnrealEngine.cpp index f97c5058c256..fa267a8f2c77 100644 --- a/Engine/Source/Runtime/Engine/Private/UnrealEngine.cpp +++ b/Engine/Source/Runtime/Engine/Private/UnrealEngine.cpp @@ -44,7 +44,6 @@ UnrealEngine.cpp: Implements the UEngine class and helpers. #include "Serialization/ArchiveCountMem.h" #include "Serialization/ObjectWriter.h" #include "Serialization/ObjectReader.h" -#include "Serialization/ArchiveTraceRoute.h" #include "Misc/PackageName.h" #include "Misc/EngineVersion.h" #include "UObject/LinkerLoad.h" @@ -139,6 +138,7 @@ UnrealEngine.cpp: Implements the UEngine class and helpers. #include "Sound/AudioSettings.h" #include "Streaming/Texture2DUpdate.h" #include "Rendering/SkeletalMeshRenderData.h" +#include "Serialization/LoadTimeTrace.h" #if WITH_EDITOR #include "Settings/LevelEditorPlaySettings.h" @@ -223,6 +223,7 @@ UnrealEngine.cpp: Implements the UEngine class and helpers. #include "HAL/FileManagerGeneric.h" #include "UObject/UObjectThreadContext.h" +#include "UObject/ReferenceChainSearch.h" #include "Particles/ParticleSystemManager.h" #include "Components/SkinnedMeshComponent.h" @@ -4516,17 +4517,19 @@ bool UEngine::HandleCountDisabledParticleItemsCommand( const TCHAR* Cmd, FOutput return true; } -// View the last N number of names added to the name table. Useful for tracking down name table bloat +// View all names or the last N names added to the name table. Useful for tracking down name table bloat bool UEngine::HandleViewnamesCommand( const TCHAR* Cmd, FOutputDevice& Ar ) { - int32 NumNames = 0; - if (FParse::Value(Cmd,TEXT("NUM="),NumNames)) + const TArray Entries = FName::DebugDump(); + + int32 NumLast = 0; + int32 BeginIdx = FParse::Value(Cmd, TEXT("NUM="), NumLast) ? FMath::Max(Entries.Num() - NumLast, 0) : 0; + + for (int32 I = BeginIdx; I < Entries.Num(); ++I) { - for (int32 NameIndex = FMath::Max(FName::GetMaxNames() - NumNames, 0); NameIndex < FName::GetMaxNames(); NameIndex++) - { - Ar.Logf(TEXT("%d->%s"), NameIndex, *FName::SafeString(NameIndex)); - } + Ar.Log(*Entries[I]->GetPlainNameString()); } + return true; } @@ -5458,9 +5461,10 @@ bool UEngine::HandleListAnimsCommand(const TCHAR* Cmd, FOutputDevice& Ar) RateScale = AnimSeq->RateScale; NumCurves = AnimSeq->RawCurveData.FloatCurves.Num(); - TranslationFormat = FAnimationUtils::GetAnimationCompressionFormatString(AnimSeq->TranslationCompressionFormat); - RotationFormat = FAnimationUtils::GetAnimationCompressionFormatString(AnimSeq->RotationCompressionFormat); - ScaleFormat = FAnimationUtils::GetAnimationCompressionFormatString(AnimSeq->ScaleCompressionFormat); + const FUECompressedAnimData& CompressedData = AnimSeq->CompressedData.CompressedDataStructure; + TranslationFormat = FAnimationUtils::GetAnimationCompressionFormatString(CompressedData.TranslationCompressionFormat); + RotationFormat = FAnimationUtils::GetAnimationCompressionFormatString(CompressedData.RotationCompressionFormat); + ScaleFormat = FAnimationUtils::GetAnimationCompressionFormatString(CompressedData.ScaleCompressionFormat); } new(SortedAnimAssets) FSortedAnimAsset( @@ -9312,7 +9316,7 @@ struct FSoundInfo bool CompareClass( const FSoundInfo& Other ) const { - return ClassName < Other.ClassName; + return ClassName.LexicalLess(Other.ClassName); } bool CompareWaveInstancesNum( const FSoundInfo& Other ) const @@ -12084,7 +12088,9 @@ void UEngine::TickWorldTravel(FWorldContext& Context, float DeltaSeconds) bool UEngine::LoadMap( FWorldContext& WorldContext, FURL URL, class UPendingNetGame* Pending, FString& Error ) { + TRACE_LOADTIME_LOAD_MAP_SCOPE(*URL.Map); STAT_ADD_CUSTOMMESSAGE_NAME( STAT_NamedMarker, *(FString( TEXT( "LoadMap - " ) + URL.Map )) ); + TRACE_BOOKMARK(TEXT("LoadMap - %s"), *URL.Map); DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UEngine::LoadMap"), STAT_LoadMap, STATGROUP_LoadTime); @@ -12632,6 +12638,7 @@ bool UEngine::LoadMap( FWorldContext& WorldContext, FURL URL, class UPendingNetG } STAT_ADD_CUSTOMMESSAGE_NAME( STAT_NamedMarker, *(FString( TEXT( "LoadMapComplete - " ) + URL.Map )) ); + TRACE_BOOKMARK(TEXT("LoadMapComplete - %s"), *URL.Map); MALLOC_PROFILER( FMallocProfiler::SnapshotMemoryLoadMapEnd( URL.Map ); ) double StopTime = FPlatformTime::Seconds(); @@ -13267,15 +13274,11 @@ void UEngine::VerifyLoadMapWorldCleanup() { if ((World->PersistentLevel == nullptr || !WorldHasValidContext(World->PersistentLevel->OwningWorld)) && !IsWorldDuplicate(World)) { - // Print some debug information... - UE_LOG(LogLoad, Log, TEXT("%s not cleaned up by garbage collection! "), *World->GetFullName()); - StaticExec(World, *FString::Printf(TEXT("OBJ REFS CLASS=WORLD NAME=%s"), *World->GetPathName())); - TMap Route = FArchiveTraceRoute::FindShortestRootPath( World, true, GARBAGE_COLLECTION_KEEPFLAGS ); - FString ErrorString = FArchiveTraceRoute::PrintRootPath( Route, World ); - UE_LOG(LogLoad, Log, TEXT("%s"),*ErrorString); - // before asserting. + UE_LOG(LogLoad, Error, TEXT("Previously active world %s not cleaned up by garbage collection!"), *World->GetPathName()); + UE_LOG(LogLoad, Error, TEXT("Once a world has become active, it cannot be reused and must be destroyed and reloaded. World referenced by:")); - UE_LOG(LogLoad, Fatal, TEXT("%s not cleaned up by garbage collection!") LINE_TERMINATOR TEXT("%s") , *World->GetFullName(), *ErrorString ); + FReferenceChainSearch RefChainSearch(World, EReferenceChainSearchMode::Shortest | EReferenceChainSearchMode::PrintResults); + UE_LOG(LogLoad, Fatal, TEXT("Previously active world %s not cleaned up by garbage collection! Referenced by:") LINE_TERMINATOR TEXT("%s"), *World->GetPathName(), *RefChainSearch.GetRootPath()); } } } @@ -13334,6 +13337,7 @@ static void AsyncMapChangeLevelLoadCompletionCallback(const FName& PackageName, } STAT_ADD_CUSTOMMESSAGE_NAME( STAT_NamedMarker, *(FString( TEXT( "PrepareMapChangeComplete - " ) + PackageName.ToString() )) ); + TRACE_BOOKMARK(TEXT("PrepareMapChangeComplete - %s"), *PackageName.ToString()); } @@ -13379,6 +13383,7 @@ bool UEngine::PrepareMapChange(FWorldContext &Context, const TArray& Leve for (const FName LevelName : Context.LevelsToLoadForPendingMapChange) { STAT_ADD_CUSTOMMESSAGE_NAME( STAT_NamedMarker, *(FString( TEXT( "PrepareMapChange - " ) + LevelName.ToString() )) ); + TRACE_BOOKMARK(TEXT("PrepareMapChange - %s"), *LevelName.ToString()); LoadPackageAsync(LevelName.ToString(), FLoadPackageAsyncDelegate::CreateStatic(&AsyncMapChangeLevelLoadCompletionCallback, Context.ContextHandle) ); @@ -13450,6 +13455,11 @@ public: { Collector.AddReferencedObjects( Levels ); } + + virtual FString GetReferencerName() const override + { + return TEXT("FPendingStreamingLevelHolder"); + } }; @@ -13567,14 +13577,18 @@ bool UEngine::CommitMapChange( FWorldContext &Context ) // Rename the newly loaded streaming levels so that their outer is correctly set to the main context's world, // rather than the fake world. - for (ULevelStreaming* const FakeWorldStreamingLevel : FakeWorld->GetStreamingLevels()) + TArray StreamingLevelsToMove; + StreamingLevelsToMove.SetNumUninitialized(FakeWorld->GetStreamingLevels().Num()); + for (int32 Index = FakeWorld->GetStreamingLevels().Num() - 1; Index >= 0; --Index) { + ULevelStreaming* const FakeWorldStreamingLevel = FakeWorld->GetStreamingLevels()[Index]; FakeWorldStreamingLevel->Rename(nullptr, Context.World(), REN_ForceNoResetLoaders | REN_DontCreateRedirectors); + FakeWorld->RemoveStreamingLevelAt(Index); + StreamingLevelsToMove[Index] = FakeWorldStreamingLevel; } // Move the secondary levels to the world info levels array. - Context.World()->AddStreamingLevels(FakeWorld->GetStreamingLevels()); - FakeWorld->ClearStreamingLevels(); + Context.World()->AddStreamingLevels(StreamingLevelsToMove); // fixup up any kismet streaming objects to force them to be loaded if they were preloaded, this // will keep streaming volumes from immediately unloading the levels that were just loaded diff --git a/Engine/Source/Runtime/Engine/Private/UserDefinedEnum.cpp b/Engine/Source/Runtime/Engine/Private/UserDefinedEnum.cpp index d69adc8b9f58..4f43b7203760 100644 --- a/Engine/Source/Runtime/Engine/Private/UserDefinedEnum.cpp +++ b/Engine/Source/Runtime/Engine/Private/UserDefinedEnum.cpp @@ -160,6 +160,21 @@ FText UUserDefinedEnum::GetDisplayNameTextByIndex(int32 InIndex) const return Super::GetDisplayNameTextByIndex(InIndex); } +FString UUserDefinedEnum::GetAuthoredNameStringByIndex(int32 InIndex) const +{ + const FName EnumEntryName = *GetNameStringByIndex(InIndex); + + if (const FText* EnumEntryDisplayName = DisplayNameMap.Find(EnumEntryName)) + { + if (const FString* SourceString = FTextInspector::GetSourceString(*EnumEntryDisplayName)) + { + return *SourceString; + } + } + + return Super::GetAuthoredNameStringByIndex(InIndex); +} + bool UUserDefinedEnum::SetEnums(TArray>& InNames, ECppForm InCppForm, bool bAddMaxKeyIfMissing) { ensure(bAddMaxKeyIfMissing); diff --git a/Engine/Source/Runtime/Engine/Private/UserDefinedStruct.cpp b/Engine/Source/Runtime/Engine/Private/UserDefinedStruct.cpp index 8f2cf4743ba1..ec8c5f4768e1 100644 --- a/Engine/Source/Runtime/Engine/Private/UserDefinedStruct.cpp +++ b/Engine/Source/Runtime/Engine/Private/UserDefinedStruct.cpp @@ -2,7 +2,6 @@ #include "Engine/UserDefinedStruct.h" #include "UObject/UObjectHash.h" -#include "Serialization/PropertyLocalizationDataGathering.h" #include "UObject/StructOnScope.h" #include "UObject/UnrealType.h" #include "UObject/LinkerLoad.h" @@ -10,7 +9,6 @@ #include "Misc/SecureHash.h" #include "UObject/PropertyPortFlags.h" #include "Misc/PackageName.h" -#include "Serialization/TextReferenceCollector.h" #include "Blueprint/BlueprintSupport.h" #if WITH_EDITOR @@ -48,56 +46,10 @@ void FUserStructOnScopeIgnoreDefaults::Initialize() } } -#if WITH_EDITORONLY_DATA -namespace -{ - void GatherUserDefinedStructForLocalization(const UObject* const Object, FPropertyLocalizationDataGatherer& PropertyLocalizationDataGatherer, const EPropertyLocalizationGathererTextFlags GatherTextFlags) - { - const UUserDefinedStruct* const UserDefinedStruct = CastChecked(Object); - - PropertyLocalizationDataGatherer.GatherLocalizationDataFromObject(UserDefinedStruct, GatherTextFlags); - - const FString PathToObject = UserDefinedStruct->GetPathName(); - - PropertyLocalizationDataGatherer.GatherLocalizationDataFromStructFields(PathToObject, UserDefinedStruct, UserDefinedStruct->GetDefaultInstance(), nullptr, GatherTextFlags); - } - - void CollectUserDefinedStructTextReferences(UObject* Object, FArchive& Ar) - { - UUserDefinedStruct* const UserDefinedStruct = CastChecked(Object); - - // User Defined Structs need some special handling as they store their default data in a way that serialize doesn't pick up - UUserDefinedStructEditorData* UDSEditorData = Cast(UserDefinedStruct->EditorData); - if (UDSEditorData) - { - for (const FStructVariableDescription& StructVariableDesc : UDSEditorData->VariablesDescriptions) - { - static const FName TextCategory = TEXT("text"); // Must match UEdGraphSchema_K2::PC_Text - if (StructVariableDesc.Category == TextCategory) - { - FText StructVariableValue; - if (FTextStringHelper::ReadFromBuffer(*StructVariableDesc.DefaultValue, StructVariableValue)) - { - Ar << StructVariableValue; - } - } - } - } - - UserDefinedStruct->Serialize(Ar); - } -} -#endif - UUserDefinedStruct::UUserDefinedStruct(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { DefaultStructInstance.SetPackage(GetOutermost()); - -#if WITH_EDITORONLY_DATA - { static const FAutoRegisterLocalizationDataGatheringCallback AutomaticRegistrationOfLocalizationGatherer(UUserDefinedStruct::StaticClass(), &GatherUserDefinedStructForLocalization); } - { static const FAutoRegisterTextReferenceCollectorCallback AutomaticRegistrationOfTextReferenceCollector(UUserDefinedStruct::StaticClass(), &CollectUserDefinedStructTextReferences); } -#endif } void UUserDefinedStruct::Serialize(FStructuredArchive::FRecord Record) @@ -212,14 +164,6 @@ void UUserDefinedStruct::GetAssetRegistryTags(TArray& OutTags OutTags.Add(FAssetRegistryTag(TEXT("Tooltip"), FStructureEditorUtils::GetTooltip(this), FAssetRegistryTag::TT_Hidden)); } -UProperty* UUserDefinedStruct::CustomFindProperty(const FName Name) const -{ - const FGuid PropertyGuid = FStructureEditorUtils::GetGuidFromPropertyName(Name); - UProperty* Property = PropertyGuid.IsValid() ? FStructureEditorUtils::GetPropertyByGuid(this, PropertyGuid) : FStructureEditorUtils::GetPropertyByDisplayName(this, Name.ToString()); - ensure(!Property || !PropertyGuid.IsValid() || PropertyGuid == FStructureEditorUtils::GetGuidForProperty(Property)); - return Property; -} - void UUserDefinedStruct::ValidateGuid() { // Backward compatibility: @@ -238,22 +182,56 @@ void UUserDefinedStruct::ValidateGuid() #endif // WITH_EDITOR -FString UUserDefinedStruct::PropertyNameToDisplayName(FName Name) const +UProperty* UUserDefinedStruct::CustomFindProperty(const FName Name) const { #if WITH_EDITOR - FGuid PropertyGuid = FStructureEditorUtils::GetGuidFromPropertyName(Name); - return FStructureEditorUtils::GetVariableDisplayName(this, PropertyGuid); -#endif // WITH_EDITOR + // If we have the editor data, check that first as it's more up to date + const FGuid PropertyGuid = FStructureEditorUtils::GetGuidFromPropertyName(Name); + UProperty* EditorProperty = PropertyGuid.IsValid() ? FStructureEditorUtils::GetPropertyByGuid(this, PropertyGuid) : FStructureEditorUtils::GetPropertyByFriendlyName(this, Name.ToString()); + ensure(!EditorProperty || !PropertyGuid.IsValid() || PropertyGuid == FStructureEditorUtils::GetGuidForProperty(EditorProperty)); + if (EditorProperty) + { + return EditorProperty; + } +#endif // WITH_EDITOR + + // Check the authored names for each field + FString NameString = Name.ToString(); + for (UProperty* CurrentProp : TFieldRange(this)) + { + if (GetAuthoredNameForField(CurrentProp) == NameString) + { + return CurrentProp; + } + } + return nullptr; +} + +FString UUserDefinedStruct::GetAuthoredNameForField(const UField* Field) const +{ + const UProperty* Property = Cast(Field); + if (!Property) + { + return Super::GetAuthoredNameForField(Field); + } + +#if WITH_EDITOR + const FString EditorName = FStructureEditorUtils::GetVariableFriendlyNameForProperty(this, Property); + if (!EditorName.IsEmpty()) + { + return EditorName; + } +#endif // WITH_EDITOR const int32 GuidStrLen = 32; const int32 MinimalPostfixlen = GuidStrLen + 3; - const FString OriginalName = Name.ToString(); + const FString OriginalName = Property->GetName(); if (OriginalName.Len() > MinimalPostfixlen) { FString DisplayName = OriginalName.LeftChop(GuidStrLen + 1); int FirstCharToRemove = -1; const bool bCharFound = DisplayName.FindLastChar(TCHAR('_'), FirstCharToRemove); - if(bCharFound && (FirstCharToRemove > 0)) + if (bCharFound && (FirstCharToRemove > 0)) { return DisplayName.Mid(0, FirstCharToRemove); } diff --git a/Engine/Source/Runtime/Engine/Private/World.cpp b/Engine/Source/Runtime/Engine/Private/World.cpp index aac0d156998c..c2cd930183db 100644 --- a/Engine/Source/Runtime/Engine/Private/World.cpp +++ b/Engine/Source/Runtime/Engine/Private/World.cpp @@ -17,7 +17,7 @@ #include "UObject/Package.h" #include "UObject/ObjectRedirector.h" #include "UObject/UObjectAnnotation.h" -#include "Serialization/ArchiveTraceRoute.h" +#include "UObject/ReferenceChainSearch.h" #include "Misc/PackageName.h" #include "Serialization/AsyncLoading.h" #include "GameMapsSettings.h" @@ -2654,12 +2654,8 @@ void FLevelStreamingGCHelper::VerifyLevelsGotRemovedByGC() // But disregard package object itself. && !Object->IsA(UPackage::StaticClass()) ) { - UE_LOG(LogWorld, Log, TEXT("%s didn't get garbage collected! Trying to find culprit, though this might crash. Try increasing stack size if it does."), *Object->GetFullName()); - StaticExec(NULL, *FString::Printf(TEXT("OBJ REFS CLASS=%s NAME=%s shortest"),*Object->GetClass()->GetName(), *Object->GetPathName())); - TMap Route = FArchiveTraceRoute::FindShortestRootPath( Object, true, GARBAGE_COLLECTION_KEEPFLAGS ); - FString ErrorString = FArchiveTraceRoute::PrintRootPath( Route, Object ); - // Print out error message. We don't assert here as there might be multiple culprits. - UE_LOG(LogWorld, Warning, TEXT("%s didn't get garbage collected!") LINE_TERMINATOR TEXT("%s"), *Object->GetFullName(), *ErrorString ); + UE_LOG(LogWorld, Warning, TEXT("Level object %s didn't get garbage collected! Trying to find culprit, though this might crash. Try increasing stack size if it does. Referenced by:"), *Object->GetFullName()); + FReferenceChainSearch RefChainSearch(Object, EReferenceChainSearchMode::Shortest | EReferenceChainSearchMode::PrintResults); FailCount++; } } @@ -3969,13 +3965,22 @@ bool UWorld::IsNavigationRebuilt() const return GetNavigationSystem() == NULL || GetNavigationSystem()->IsNavigationBuilt(GetWorldSettings()); } -void UWorld::CleanupWorld(bool bSessionEnded, bool bCleanupResources, UWorld* NewWorld) +void UWorld::CleanupWorld(bool bSessionEnded, bool bCleanupResources, UWorld* NewWorld, bool bResetCleanedUpFlag) { UE_LOG(LogWorld, Log, TEXT("UWorld::CleanupWorld for %s, bSessionEnded=%s, bCleanupResources=%s"), *GetName(), bSessionEnded ? TEXT("true") : TEXT("false"), bCleanupResources ? TEXT("true") : TEXT("false")); + TArray WorldsToResetCleanedUpFlag; + check(IsVisibilityRequestPending() == false); + + check(!bCleanedUpWorld); bCleanedUpWorld = true; + if (bResetCleanedUpFlag) + { + WorldsToResetCleanedUpFlag.Add(this); + } + // Wait on current physics scenes if they are processing if(FPhysScene* CurrPhysicsScene = GetPhysicsScene()) { @@ -4069,7 +4074,12 @@ void UWorld::CleanupWorld(bool bSessionEnded, bool bCleanupResources, UWorld* Ne UWorld* World = CastChecked(GetLevel(LevelIndex)->GetOuter()); if (!World->bCleanedUpWorld) { - World->CleanupWorld(bSessionEnded, bCleanupResources, NewWorld); + World->CleanupWorld(bSessionEnded, bCleanupResources, NewWorld, false); + + if (bResetCleanedUpFlag) + { + WorldsToResetCleanedUpFlag.Add(World); + } } } @@ -4080,7 +4090,12 @@ void UWorld::CleanupWorld(bool bSessionEnded, bool bCleanupResources, UWorld* Ne UWorld* World = CastChecked(Level->GetOuter()); if (!World->bCleanedUpWorld) { - World->CleanupWorld(bSessionEnded, bCleanupResources, NewWorld); + World->CleanupWorld(bSessionEnded, bCleanupResources, NewWorld, false); + + if (bResetCleanedUpFlag) + { + WorldsToResetCleanedUpFlag.Add(World); + } } } } @@ -4099,7 +4114,12 @@ void UWorld::CleanupWorld(bool bSessionEnded, bool bCleanupResources, UWorld* Ne UWorld* const LevelWorld = CastChecked(Level->GetOuter()); if (!LevelWorld->bCleanedUpWorld) { - LevelWorld->CleanupWorld(bSessionEnded, bCleanupResources, NewWorld); + LevelWorld->CleanupWorld(bSessionEnded, bCleanupResources, NewWorld, false); + + if (bResetCleanedUpFlag) + { + WorldsToResetCleanedUpFlag.Add(LevelWorld); + } } } } @@ -4107,6 +4127,12 @@ void UWorld::CleanupWorld(bool bSessionEnded, bool bCleanupResources, UWorld* Ne PSCPool.Cleanup(); FWorldDelegates::OnPostWorldCleanup.Broadcast(this, bSessionEnded, bCleanupResources); + + for (UWorld* WorldToResetCleanedUpFlag: WorldsToResetCleanedUpFlag) + { + check(WorldToResetCleanedUpFlag->bCleanedUpWorld); + WorldToResetCleanedUpFlag->bCleanedUpWorld = false; + } } UGameViewportClient* UWorld::GetGameViewport() const @@ -4617,7 +4643,6 @@ void UWorld::WelcomePlayer(UNetConnection* Connection) #endif check(CurrentLevel); - Connection->SendPackageMap(); FString LevelName; @@ -5184,7 +5209,7 @@ bool UWorld::Listen( FURL& InURL ) } } - if (NetDriver == NULL) + if (NetDriver == nullptr) { GEngine->BroadcastNetworkFailure(this, NULL, ENetworkFailure::NetDriverCreateFailure); return false; @@ -5195,8 +5220,8 @@ bool UWorld::Listen( FURL& InURL ) { GEngine->BroadcastNetworkFailure(this, NetDriver, ENetworkFailure::NetDriverListenFailure, Error); UE_LOG(LogWorld, Log, TEXT("Failed to listen: %s"), *Error ); - NetDriver->SetWorld(NULL); - NetDriver = NULL; + GEngine->DestroyNamedNetDriver(this, NetDriver->NetDriverName); + NetDriver = nullptr; FLevelCollection* SourceCollection = FindCollectionByType(ELevelCollectionType::DynamicSourceLevels); if (SourceCollection) { @@ -5507,6 +5532,7 @@ void FSeamlessTravelHandler::SeamlessTravelLoadCallback(const FName& PackageName } STAT_ADD_CUSTOMMESSAGE_NAME( STAT_NamedMarker, *(FString( TEXT( "StartTravelComplete - " ) + PackageName.ToString() )) ); + TRACE_BOOKMARK(TEXT("StartTravelComplete - %s"), *PackageName.ToString()); } bool FSeamlessTravelHandler::StartTravel(UWorld* InCurrentWorld, const FURL& InURL, const FGuid& InGuid) @@ -5626,6 +5652,7 @@ bool FSeamlessTravelHandler::StartTravel(UWorld* InCurrentWorld, const FURL& InU // first, load the entry level package STAT_ADD_CUSTOMMESSAGE_NAME( STAT_NamedMarker, *(FString( TEXT( "StartTravel - " ) + TransitionMap )) ); + TRACE_BOOKMARK(TEXT("StartTravel - %s"), *TransitionMap); LoadPackageAsync(TransitionMap, FLoadPackageAsyncDelegate::CreateRaw(this, &FSeamlessTravelHandler::SeamlessTravelLoadCallback), 0, @@ -6477,7 +6504,7 @@ bool UWorld::UsesGameHiddenFlags() const FString UWorld::GetAddressURL() const { - return FString::Printf( TEXT("%s:%i"), *URL.Host, URL.Port ); + return FString::Printf( TEXT("%s"), *URL.GetHostPortString() ); } FString UWorld::RemovePIEPrefix(const FString &Source) diff --git a/Engine/Source/Runtime/Engine/Public/ActiveSound.h b/Engine/Source/Runtime/Engine/Public/ActiveSound.h index a505c8a03fb3..037483d9c0ed 100644 --- a/Engine/Source/Runtime/Engine/Public/ActiveSound.h +++ b/Engine/Source/Runtime/Engine/Public/ActiveSound.h @@ -512,7 +512,7 @@ public: /** * Add newly created wave instance to active sound */ - void AddWaveInstance(const UPTRINT WaveInstanceHash, FWaveInstance& WaveInstance); + FWaveInstance& AddWaveInstance(const UPTRINT WaveInstanceHash); /** * Check whether to apply the radio filter diff --git a/Engine/Source/Runtime/Engine/Public/AnimEncoding.h b/Engine/Source/Runtime/Engine/Public/AnimEncoding.h index 82ddaad09856..e821a1dad773 100644 --- a/Engine/Source/Runtime/Engine/Public/AnimEncoding.h +++ b/Engine/Source/Runtime/Engine/Public/AnimEncoding.h @@ -219,7 +219,7 @@ void AnimationFormat_GetAnimationPose( * @param NumTransTracksWithOneKey The total number of Translation Tracks found containing a single key. * @param NumRotTracksWithOneKey The total number of Rotation Tracks found containing a single key. */ -ENGINE_API void AnimationFormat_GetStats(const UAnimSequence* Seq, +ENGINE_API void AnimationFormat_GetStats(const FUECompressedAnimData& CompressedData, int32& NumTransTracks, int32& NumRotTracks, int32& NumScaleTracks, @@ -239,7 +239,8 @@ ENGINE_API void AnimationFormat_GetStats(const UAnimSequence* Seq, * * @param Seq An Animation Sequence to setup links within. */ -void AnimationFormat_SetInterfaceLinks(UAnimSequence& Seq); +template +void AnimationFormat_SetInterfaceLinks(CompressedDataType& CompressedData); #if WITH_EDITORONLY_DATA #define AC_UnalignedSwap( MemoryArchive, Data, Len ) \ @@ -273,26 +274,24 @@ public: /** * Handles Byte-swapping incoming animation data from a MemoryReader * - * @param Seq An Animation Sequence to contain the read data. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The MemoryReader object to read from. * @param SourceArVersion The version of the archive that the data is coming from. */ virtual void ByteSwapIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader) PURE_VIRTUAL(AnimEncoding::ByteSwapIn,); /** * Handles Byte-swapping outgoing animation data to an array of BYTEs * - * @param Seq An Animation Sequence to write. + * @param CompressedData The compressed animation data being operated on. * @param SerializedData The output buffer. * @param ForceByteSwapping true is byte swapping is not optional. */ virtual void ByteSwapOut( - UAnimSequence& Seq, - TArray& SerializedData, - bool ForceByteSwapping, - bool bMaintainComponentOrder=false) PURE_VIRTUAL(AnimEncoding::ByteSwapOut, ); + FUECompressedAnimData& CompressedData, + FMemoryWriter& MemoryWriter) PURE_VIRTUAL(AnimEncoding::ByteSwapOut, ); /** * Extracts a single BoneAtom from an Animation Sequence. @@ -350,6 +349,7 @@ public: FAnimSequenceDecompressionContext& DecompContext) PURE_VIRTUAL(AnimEncoding::GetPoseScales,); #endif +#if USE_SEGMENTING_CONTEXT static FORCEINLINE float TimeToIndex( const FAnimSequenceDecompressionContext& DecompContext, const uint8* TimeMarkers, @@ -359,30 +359,34 @@ public: float SegmentRelativePos, int32& FrameIndex0Out, int32& FrameIndex1Out); +#endif protected: /** * Utility function to determine the two key indices to interpolate given a relative position in the animation * - * @param Seq The UAnimSequence container. + * @param SequenceLength The length of the anim sequence * @param RelativePos The relative position to solve in the range [0,1] inclusive. * @param NumKeys The number of keys present in the track being solved. + * @param Interpolation The Interpolation type of the sequence * @param PosIndex0Out Output value for the closest key index before the RelativePos specified. * @param PosIndex1Out Output value for the closest key index after the RelativePos specified. * @return The rate at which to interpolate the two keys returned to obtain the final result. */ static float TimeToIndex( - const UAnimSequence& Seq, + float SequenceLength, float RelativePos, int32 NumKeys, + EAnimInterpolationType Interpolation, int32 &PosIndex0Out, int32 &PosIndex1Out); /** * Utility function to determine the two key indices to interpolate given a relative position in the animation * - * @param Seq The UAnimSequence container. + * @param Interpolation The Interpolation type of the sequence + * @param NumberOfFrames The number of frames in the original sequence * @param FrameTable The frame table containing a frame index for each key. * @param RelativePos The relative position to solve in the range [0,1] inclusive. * @param NumKeys The number of keys present in the track being solved. @@ -391,7 +395,8 @@ protected: * @return The rate at which to interpolate the two keys returned to obtain the final result. */ static float TimeToIndex( - const UAnimSequence& Seq, + EAnimInterpolationType Interpolation, + int32 NumberOfFrames, const uint8* FrameTable, float RelativePos, int32 NumKeys, @@ -461,30 +466,28 @@ public: /** * Handles Byte-swapping incoming animation data from a MemoryReader * - * @param Seq An Animation Sequence to contain the read data. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The MemoryReader object to read from. */ virtual void ByteSwapIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader) override; /** * Handles Byte-swapping outgoing animation data to an array of BYTEs * - * @param Seq An Animation Sequence to write. + * @param CompressedData The compressed animation data being operated on. * @param SerializedData The output buffer. * @param ForceByteSwapping true is byte swapping is not optional. */ virtual void ByteSwapOut( - UAnimSequence& Seq, - TArray& SerializedData, - bool ForceByteSwapping, - bool bMaintainComponentOrder=false) override; + FUECompressedAnimData& CompressedData, + FMemoryWriter& MemoryWriter) override; /** * Handles the ByteSwap of compressed animation data on import * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The FMemoryReader to read from. * @param Stream The compressed animation data. * @param NumKeys The number of keys present in Stream. @@ -492,7 +495,7 @@ public: * @return The adjusted Stream position after import. */ virtual void ByteSwapRotationIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader, uint8*& Stream, int32 NumKeys) PURE_VIRTUAL(AnimEncoding::ByteSwapRotationIn,); @@ -500,7 +503,7 @@ public: /** * Handles the ByteSwap of compressed animation data on import * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The FMemoryReader to read from. * @param Stream The compressed animation data. * @param NumKeys The number of keys present in Stream. @@ -508,7 +511,7 @@ public: * @return The adjusted Stream position after import. */ virtual void ByteSwapTranslationIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader, uint8*& Stream, int32 NumKeys) PURE_VIRTUAL(AnimEncoding::ByteSwapTranslationIn,); @@ -516,7 +519,7 @@ public: /** * Handles the ByteSwap of compressed animation data on import * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The FMemoryReader to read from. * @param Stream The compressed animation data. * @param NumKeys The number of keys present in Stream. @@ -524,7 +527,7 @@ public: * @return The adjusted Stream position after import. */ virtual void ByteSwapScaleIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader, uint8*& Stream, int32 NumKeys) PURE_VIRTUAL(AnimEncoding::ByteSwapScaleIn,); @@ -532,14 +535,14 @@ public: /** * Handles the ByteSwap of compressed animation data on export * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryWriter The FMemoryReader to write to. * @param Stream The compressed animation data. * @param NumKeys The number of keys present in Stream. * @return The adjusted Stream position after export. */ virtual void ByteSwapRotationOut( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryWriter& MemoryWriter, uint8*& Stream, int32 NumKeys) PURE_VIRTUAL(AnimEncoding::ByteSwapRotationOut,); @@ -547,14 +550,14 @@ public: /** * Handles the ByteSwap of compressed animation data on export * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryWriter The FMemoryReader to write to. * @param Stream The compressed animation data. * @param NumKeys The number of keys present in Stream. * @return The adjusted Stream position after export. */ virtual void ByteSwapTranslationOut( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryWriter& MemoryWriter, uint8*& Stream, int32 NumKeys) PURE_VIRTUAL(AnimEncoding::ByteSwapTranslationOut,); @@ -562,14 +565,14 @@ public: /** * Handles the ByteSwap of compressed animation data on export * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryWriter The FMemoryReader to write to. * @param Stream The compressed animation data. * @param NumKeys The number of keys present in Stream. * @return The adjusted Stream position after export. */ virtual void ByteSwapScaleOut( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryWriter& MemoryWriter, uint8*& Stream, int32 NumKeys) PURE_VIRTUAL(AnimEncoding::ByteSwapScaleOut,); @@ -587,13 +590,13 @@ public: * @return The rate at which to interpolate the two keys returned to obtain the final result. */ FORCEINLINE float AnimEncoding::TimeToIndex( - const UAnimSequence& Seq, + float SequenceLength, float RelativePos, int32 NumKeys, + EAnimInterpolationType Interpolation, int32 &PosIndex0Out, int32 &PosIndex1Out) { - const float SequenceLength= Seq.SequenceLength; float Alpha; if (NumKeys < 2) @@ -628,7 +631,7 @@ FORCEINLINE float AnimEncoding::TimeToIndex( checkSlow(KeyPos >= 0.0f); const float KeyPosFloor = floorf(KeyPos); PosIndex0Out = FMath::Min( FMath::TruncToInt(KeyPosFloor), NumKeys ); - Alpha = Seq.Interpolation == EAnimInterpolationType::Step ? 0.0f : KeyPos - KeyPosFloor; + Alpha = (Interpolation == EAnimInterpolationType::Step) ? 0.0f : KeyPos - KeyPosFloor; PosIndex1Out = FMath::Min( PosIndex0Out + 1, NumKeys ); } } @@ -702,7 +705,8 @@ FORCEINLINE_DEBUGGABLE int32 FindLowKeyIndex( * @return The rate at which to interpolate the two keys returned to obtain the final result. */ FORCEINLINE float AnimEncoding::TimeToIndex( - const UAnimSequence& Seq, + EAnimInterpolationType Interpolation, + int32 NumberOfFrames, const uint8* FrameTable, float RelativePos, int32 NumKeys, @@ -715,7 +719,7 @@ FORCEINLINE float AnimEncoding::TimeToIndex( const int32 LastKey= NumKeys-1; - int32 TotalFrames = Seq.GetCompressedNumberOfFrames()-1; + int32 TotalFrames = NumberOfFrames -1; int32 EndingKey = LastKey; if (NumKeys < 2 || RelativePos <= 0.f) @@ -745,7 +749,7 @@ FORCEINLINE float AnimEncoding::TimeToIndex( int32 HighFrame = 0; // find the pair of keys which surround our target frame index - if (Seq.GetCompressedNumberOfFrames() > 0xFF) + if (NumberOfFrames > 0xFF) { const uint16* Frames= (uint16*)FrameTable; PosIndex0Out = FindLowKeyIndex(Frames, NumKeys, FramePosFloor, KeyEstimate); @@ -775,12 +779,13 @@ FORCEINLINE float AnimEncoding::TimeToIndex( // compute the blend parameters for the keys we have found int32 Delta= FMath::Max(HighFrame - LowFrame, 1); const float Remainder = (FramePos - (float)LowFrame); - Alpha = Seq.Interpolation == EAnimInterpolationType::Step ? 0.f : (Remainder / (float)Delta); + Alpha = Interpolation == EAnimInterpolationType::Step ? 0.f : (Remainder / (float)Delta); } return Alpha; } +#if USE_SEGMENTING_CONTEXT float AnimEncoding::TimeToIndex( const FAnimSequenceDecompressionContext& DecompContext, const uint8* TimeMarkers, @@ -845,9 +850,9 @@ float AnimEncoding::TimeToIndex( // compute the blend parameters for the keys we have found const int32 Delta = FMath::Max(HighFrame - LowFrame, 1); const float Remainder = FramePos - (float)LowFrame; - Alpha = DecompContext.AnimSeq->Interpolation == EAnimInterpolationType::Step ? 0.f : (Remainder / (float)Delta); + Alpha = DecompContext.GetInterpolation() == EAnimInterpolationType::Step ? 0.f : (Remainder / (float)Delta); } return Alpha; } - +#endif diff --git a/Engine/Source/Runtime/Engine/Public/AnimEncoding_ConstantKeyLerp.h b/Engine/Source/Runtime/Engine/Public/AnimEncoding_ConstantKeyLerp.h index aafcb8e240c9..a4c3cf3188d6 100644 --- a/Engine/Source/Runtime/Engine/Public/AnimEncoding_ConstantKeyLerp.h +++ b/Engine/Source/Runtime/Engine/Public/AnimEncoding_ConstantKeyLerp.h @@ -40,13 +40,13 @@ public: /** * Handles the ByteSwap of compressed rotation data on import * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The FMemoryReader to read from. * @param RotTrackData The compressed rotation data stream. * @param NumKeysRot The number of keys present in the stream. */ virtual void ByteSwapRotationIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader, uint8*& RotTrackData, int32 NumKeysRot) override; @@ -54,13 +54,13 @@ public: /** * Handles the ByteSwap of compressed translation data on import * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The FMemoryReader to read from. * @param TransTrackData The compressed translation data stream. * @param NumKeysTrans The number of keys present in the stream. */ virtual void ByteSwapTranslationIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader, uint8*& TransTrackData, int32 NumKeysTrans) override; @@ -68,13 +68,13 @@ public: /** * Handles the ByteSwap of compressed Scale data on import * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The FMemoryReader to read from. * @param ScaleTrackData The compressed Scale data stream. * @param NumKeysScale The number of keys present in the stream. */ virtual void ByteSwapScaleIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader, uint8*& ScaleTrackData, int32 NumKeysScale) override; @@ -83,13 +83,13 @@ public: /** * Handles the ByteSwap of compressed rotation data on export * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryWriter The FMemoryWriter to write to. * @param RotTrackData The compressed rotation data stream. * @param NumKeysRot The number of keys to write to the stream. */ virtual void ByteSwapRotationOut( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryWriter& MemoryWriter, uint8*& RotTrackData, int32 NumKeysRot) override; @@ -97,13 +97,13 @@ public: /** * Handles the ByteSwap of compressed translation data on export * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryWriter The FMemoryWriter to write to. * @param TransTrackData The compressed translation data stream. * @param NumKeysTrans The number of keys to write to the stream. */ virtual void ByteSwapTranslationOut( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryWriter& MemoryWriter, uint8*& TransTrackData, int32 NumKeysTrans) override; @@ -111,13 +111,13 @@ public: /** * Handles the ByteSwap of compressed Scale data on export * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryWriter The FMemoryWriter to write to. * @param TransTrackData The compressed Scale data stream. * @param NumKeysTrans The number of keys to write to the stream. */ virtual void ByteSwapScaleOut( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryWriter& MemoryWriter, uint8*& ScaleTrackData, int32 NumKeysScale) override; @@ -261,7 +261,7 @@ FORCEINLINE void AEFConstantKeyLerp::GetBoneAtomRotation(FTransform& Out { int32 Index0; int32 Index1; - float Alpha = TimeToIndex(*DecompContext.AnimSeq, DecompContext.RelativePos, NumRotKeys, Index0, Index1); + float Alpha = TimeToIndex(DecompContext.GetSequenceLength(), DecompContext.RelativePos, NumRotKeys, DecompContext.GetInterpolation(), Index0, Index1); const int32 RotationStreamOffset = (FORMAT == ACF_IntervalFixed32NoW) ? (sizeof(float)*6) : 0; // offset past Min and Range data @@ -340,7 +340,7 @@ FORCEINLINE void AEFConstantKeyLerp::GetBoneAtomTranslation(FTransform& int32 Index0; int32 Index1; - float Alpha = TimeToIndex(*DecompContext.AnimSeq, DecompContext.RelativePos, NumTransKeys, Index0, Index1); + float Alpha = TimeToIndex(DecompContext.GetSequenceLength(), DecompContext.RelativePos, NumTransKeys, DecompContext.GetInterpolation(), Index0, Index1); const int32 TransStreamOffset = ((FORMAT == ACF_IntervalFixed32NoW) && NumTransKeys > 1) ? (sizeof(float)*6) : 0; // offset past Min and Range data @@ -410,7 +410,7 @@ FORCEINLINE void AEFConstantKeyLerp::GetBoneAtomScale(FTransform& OutAto int32 Index0; int32 Index1; - float Alpha = TimeToIndex(*DecompContext.AnimSeq, DecompContext.RelativePos, NumScaleKeys, Index0, Index1); + float Alpha = TimeToIndex(DecompContext.GetSequenceLength(), DecompContext.RelativePos, NumScaleKeys, DecompContext.GetInterpolation(), Index0, Index1); const int32 ScaleStreamOffset = ((FORMAT == ACF_IntervalFixed32NoW) && NumScaleKeys > 1) ? (sizeof(float)*6) : 0; // offset past Min and Range data diff --git a/Engine/Source/Runtime/Engine/Public/AnimEncoding_PerTrackCompression.h b/Engine/Source/Runtime/Engine/Public/AnimEncoding_PerTrackCompression.h index 7cd3695e6efd..9cbbea6c7e5b 100644 --- a/Engine/Source/Runtime/Engine/Public/AnimEncoding_PerTrackCompression.h +++ b/Engine/Source/Runtime/Engine/Public/AnimEncoding_PerTrackCompression.h @@ -20,24 +20,22 @@ public: /** * Handles Byte-swapping incoming animation data from a MemoryReader * - * @param Seq An Animation Sequence to contain the read data. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The MemoryReader object to read from. * @param SourceArVersion The version of the archive that the data is coming from. */ - virtual void ByteSwapIn(UAnimSequence& Seq, FMemoryReader& MemoryReader) override; + virtual void ByteSwapIn(FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader) override; /** * Handles Byte-swapping outgoing animation data to an array of BYTEs * - * @param Seq An Animation Sequence to write. + * @param CompressedData The compressed animation data being operated on. * @param SerializedData The output buffer. * @param ForceByteSwapping true is byte swapping is not optional. */ virtual void ByteSwapOut( - UAnimSequence& Seq, - TArray& SerializedData, - bool ForceByteSwapping, - bool bMaintainComponentOrder=false) override; + FUECompressedAnimData& CompressedData, + FMemoryWriter& MemoryWriter) override; /** * Extracts a single BoneAtom from an Animation Sequence. @@ -99,13 +97,12 @@ protected: /** * Handles Byte-swapping a single track of animation data from a MemoryReader or to a MemoryWriter * - * @param Seq The Animation Sequence being operated on. + * @param CompressedData The compressed animation data being operated on. * @param MemoryStream The MemoryReader or MemoryWriter object to read from/write to. * @param Offset The starting offset into the compressed byte stream for this track (can be INDEX_NONE to indicate an identity track) - * @param bMaintainComponentOrder Should we maintain the order of track components (trans/rot/scale) in the byte stream (if false the output will always be T/R/S regardless of input order) */ template - static void ByteSwapOneTrack(UAnimSequence& Seq, TArchive& MemoryStream, int32 Offset, bool bMaintainComponentOrder = false); + static void ByteSwapOneTrack(FUECompressedAnimData& CompressedData, TArchive& MemoryStream, int32 BufferStart, int32 Offset); /** * Preserves 4 byte alignment within a stream diff --git a/Engine/Source/Runtime/Engine/Public/AnimEncoding_VariableKeyLerp.h b/Engine/Source/Runtime/Engine/Public/AnimEncoding_VariableKeyLerp.h index 536da8fb17a8..763e20caf55a 100644 --- a/Engine/Source/Runtime/Engine/Public/AnimEncoding_VariableKeyLerp.h +++ b/Engine/Source/Runtime/Engine/Public/AnimEncoding_VariableKeyLerp.h @@ -82,13 +82,13 @@ public: /** * Handles the ByteSwap of compressed rotation data on import * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The FMemoryReader to read from. * @param RotTrackData The compressed rotation data stream. * @param NumKeysRot The number of keys present in the stream. */ virtual void ByteSwapRotationIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader, uint8*& RotTrackData, int32 NumKeysRot) override; @@ -96,13 +96,13 @@ public: /** * Handles the ByteSwap of compressed translation data on import * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The FMemoryReader to read from. * @param TransTrackData The compressed translation data stream. * @param NumKeysTrans The number of keys present in the stream. */ virtual void ByteSwapTranslationIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader, uint8*& TransTrackData, int32 NumKeysTransn) override; @@ -110,13 +110,13 @@ public: /** * Handles the ByteSwap of compressed Scale data on import * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryReader The FMemoryReader to read from. * @param ScaleTrackData The compressed Scale data stream. * @param NumKeysScale The number of keys present in the stream. */ virtual void ByteSwapScaleIn( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryReader& MemoryReader, uint8*& ScaleTrackData, int32 NumKeysScale) override; @@ -124,13 +124,13 @@ public: /** * Handles the ByteSwap of compressed rotation data on export * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryWriter The FMemoryWriter to write to. * @param RotTrackData The compressed rotation data stream. * @param NumKeysRot The number of keys to write to the stream. */ virtual void ByteSwapRotationOut( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryWriter& MemoryWriter, uint8*& RotTrackData, int32 NumKeysRot) override; @@ -138,13 +138,13 @@ public: /** * Handles the ByteSwap of compressed translation data on export * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryWriter The FMemoryWriter to write to. * @param TransTrackData The compressed translation data stream. * @param NumKeysTrans The number of keys to write to the stream. */ virtual void ByteSwapTranslationOut( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryWriter& MemoryWriter, uint8*& TransTrackData, int32 NumKeysTrans) override; @@ -152,13 +152,13 @@ public: /** * Handles the ByteSwap of compressed Scale data on export * - * @param Seq The UAnimSequence container. + * @param CompressedData The compressed animation data being operated on. * @param MemoryWriter The FMemoryWriter to write to. * @param ScaleTrackData The compressed Scale data stream. * @param NumKeysScale The number of keys to write to the stream. */ virtual void ByteSwapScaleOut( - UAnimSequence& Seq, + FUECompressedAnimData& CompressedData, FMemoryWriter& MemoryWriter, uint8*& ScaleTrackData, int32 NumKeysScale) override; @@ -302,7 +302,7 @@ FORCEINLINE_DEBUGGABLE void AEFVariableKeyLerp::GetBoneAtomRotation(FTra int32 Index0; int32 Index1; - float Alpha = TimeToIndex(*DecompContext.AnimSeq, FrameTable, DecompContext.RelativePos, NumRotKeys, Index0, Index1); + float Alpha = TimeToIndex(DecompContext.GetInterpolation(), DecompContext.GetCompressedNumberOfFrames(), FrameTable, DecompContext.RelativePos, NumRotKeys, Index0, Index1); if (Index0 != Index1) @@ -384,7 +384,7 @@ FORCEINLINE_DEBUGGABLE void AEFVariableKeyLerp::GetBoneAtomTranslation(F int32 Index0; int32 Index1; - float Alpha = TimeToIndex(*DecompContext.AnimSeq, FrameTable, DecompContext.RelativePos, NumTransKeys, Index0, Index1); + float Alpha = TimeToIndex(DecompContext.GetInterpolation(), DecompContext.GetCompressedNumberOfFrames(), FrameTable, DecompContext.RelativePos, NumTransKeys, Index0, Index1); const int32 TransStreamOffset = ((FORMAT == ACF_IntervalFixed32NoW) && NumTransKeys > 1) ? (sizeof(float)*6) : 0; // offset past Min and Range data if (Index0 != Index1) @@ -455,7 +455,7 @@ FORCEINLINE_DEBUGGABLE void AEFVariableKeyLerp::GetBoneAtomScale(FTransf int32 Index0; int32 Index1; - float Alpha = TimeToIndex(*DecompContext.AnimSeq, FrameTable, DecompContext.RelativePos, NumScaleKeys, Index0, Index1); + float Alpha = TimeToIndex(DecompContext.GetInterpolation(), DecompContext.GetCompressedNumberOfFrames(), FrameTable, DecompContext.RelativePos, NumScaleKeys, Index0, Index1); const int32 ScaleStreamOffset = ((FORMAT == ACF_IntervalFixed32NoW) && NumScaleKeys > 1) ? (sizeof(float)*6) : 0; // offset past Min and Range data if (Index0 != Index1) diff --git a/Engine/Source/Runtime/Engine/Public/Animation/AnimCompressionTypes.h b/Engine/Source/Runtime/Engine/Public/Animation/AnimCompressionTypes.h new file mode 100644 index 000000000000..1ff0a6ea7e11 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Public/Animation/AnimCompressionTypes.h @@ -0,0 +1,672 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Misc/MemStack.h" +#include "Animation/AnimTypes.h" +#include "Animation/AnimCurveTypes.h" + +#include "Async/MappedFileHandle.h" +#include "HAL/PlatformFilemanager.h" +#include "GenericPlatform/GenericPlatformFile.h" +#include "Misc/Paths.h" +#include "Serialization/BulkData.h" + +#include "Serialization/MemoryReader.h" +#include "Serialization/MemoryWriter.h" + +#include "AnimCompressionTypes.generated.h" + +/** + * Indicates animation data key format. + */ +UENUM() +enum AnimationKeyFormat +{ + AKF_ConstantKeyLerp, + AKF_VariableKeyLerp, + AKF_PerTrackCompression, + AKF_MAX, +}; + +class FMemoryReader; +class FMemoryWriter; + +template +struct ENGINE_API FCompressedOffsetDataBase +{ + ArrayClass OffsetData; + + int32 StripSize; + + FCompressedOffsetDataBase(int32 InStripSize = 2) + : StripSize(InStripSize) + {} + + void SetStripSize(int32 InStripSize) + { + ensure(InStripSize > 0); + StripSize = InStripSize; + } + + const int32 GetOffsetData(int32 StripIndex, int32 Offset) const + { + checkSlow(OffsetData.IsValidIndex(StripIndex * StripSize + Offset)); + + return OffsetData[StripIndex * StripSize + Offset]; + } + + void SetOffsetData(int32 StripIndex, int32 Offset, int32 Value) + { + checkSlow(OffsetData.IsValidIndex(StripIndex * StripSize + Offset)); + OffsetData[StripIndex * StripSize + Offset] = Value; + } + + void AddUninitialized(int32 NumOfTracks) + { + OffsetData.AddUninitialized(NumOfTracks*StripSize); + } + + void Empty(int32 NumOfTracks = 0) + { + OffsetData.Empty(NumOfTracks*StripSize); + } + + int32 GetMemorySize() const + { + return sizeof(int32)*OffsetData.Num() + sizeof(int32); + } + + int32 GetNumTracks() const + { + return OffsetData.Num() / StripSize; + } + + bool IsValid() const + { + return (OffsetData.Num() > 0); + } +}; + +struct FCompressedOffsetData : public FCompressedOffsetDataBase> +{ + +}; + + +/** + * Represents a segment of the anim sequence that is compressed. + */ +USTRUCT() +struct ENGINE_API FCompressedSegment +{ + GENERATED_USTRUCT_BODY() + + // Frame where the segment begins in the anim sequence + int32 StartFrame; + + // Num of frames contained in the segment + int32 NumFrames; + + // Segment data offset in CompressedByteStream + int32 ByteStreamOffset; + + /** The compression format that was used to compress translation tracks. */ + TEnumAsByte TranslationCompressionFormat; + + /** The compression format that was used to compress rotation tracks. */ + TEnumAsByte RotationCompressionFormat; + + /** The compression format that was used to compress rotation tracks. */ + TEnumAsByte ScaleCompressionFormat; + + FCompressedSegment() + : StartFrame(0) + , NumFrames(0) + , ByteStreamOffset(0) + , TranslationCompressionFormat(ACF_None) + , RotationCompressionFormat(ACF_None) + , ScaleCompressionFormat(ACF_None) + { + } + + friend FArchive& operator<<(FArchive& Ar, FCompressedSegment &Segment) + { + return Ar << Segment.StartFrame << Segment.NumFrames << Segment.ByteStreamOffset + << Segment.TranslationCompressionFormat << Segment.RotationCompressionFormat << Segment.ScaleCompressionFormat; + } +}; + +struct ENGINE_API FCompressibleAnimData +{ +public: + FCompressibleAnimData() + : RequestedCompressionScheme(nullptr) + , CurveCompressionSettings(nullptr) + , Skeleton(nullptr) + , Interpolation((EAnimInterpolationType)0) + , SequenceLength(0.f) + , NumFrames(0) + , bIsValidAdditive(false) + , CompressCommandletVersion(0) + , RefFrameIndex(0) + , RefPoseType((EAdditiveBasePoseType)0) + , AdditiveAnimType((EAdditiveAnimationType)0) + { + } + + FCompressibleAnimData(class UAnimSequence* InSeq); + + class UAnimCompress* RequestedCompressionScheme; + + class UAnimCurveCompressionSettings* CurveCompressionSettings; + + class USkeleton* Skeleton; + + TArray TrackToSkeletonMapTable; + + TArray RawAnimationData; + + TArray AdditiveBaseAnimationData; + + EAnimInterpolationType Interpolation; + + TArray BoneData; + + FRawCurveTracks RawCurveData; + + float SequenceLength; + + int32 NumFrames; + + bool bIsValidAdditive; + + //For DDC + FString TypeName; + FString DDCKey; + + int32 CompressCommandletVersion; + FGuid RawDataGuid; + + //Additive + FGuid AdditiveDataGuid; + int32 RefFrameIndex; + EAdditiveBasePoseType RefPoseType; + EAdditiveAnimationType AdditiveAnimType; + + //For Logging + FString Name; + FString FullName; + FName AnimFName; + + int32 GetApproxRawBoneSize() const + { + int32 Total = sizeof(FRawAnimSequenceTrack) * RawAnimationData.Num(); + for (int32 i = 0; i < RawAnimationData.Num(); ++i) + { + const FRawAnimSequenceTrack& RawTrack = RawAnimationData[i]; + Total += + sizeof(FVector) * RawTrack.PosKeys.Num() + + sizeof(FQuat) * RawTrack.RotKeys.Num() + + sizeof(FVector) * RawTrack.ScaleKeys.Num(); + } + return Total; + } + + int32 GetApproxRawCurveSize() const + { + int32 Total = 0; + for (const FFloatCurve& Curve : RawCurveData.FloatCurves) + { + Total += sizeof(FFloatCurve); + Total += sizeof(FRichCurveKey) * Curve.FloatCurve.Keys.Num(); + } + return Total; + } + + int32 GetApproxRawSize() const + { + return GetApproxRawBoneSize() + GetApproxRawCurveSize(); + } + + void Update(struct FCompressedAnimSequence& CompressedData) const; + +private: + +}; + +// Wrapper Code +template +struct TArrayMaker +{ + using Type = TArray; +}; + + +template +struct TNonConstArrayViewMaker +{ + using Type = TArrayView; +}; + +template +struct TArrayViewMaker +{ + using Type = TArrayView; +}; + +template